rejuvallife/resources/js/pages/provider/MeetingsDashboard.vue
2024-10-25 01:02:11 +05:00

316 lines
11 KiB
Vue

<script setup>
import moment from 'moment';
import { computed, onBeforeUnmount, onMounted, ref } from 'vue';
import { useDisplay } from 'vuetify';
import { useStore } from 'vuex';
const appoinmentList = ref([])
const store = useStore()
const options = ref({ page: 1, itemsPerPage: 5, sortBy: [''], sortDesc: [false] })
const { smAndDown } = useDisplay()
const showNotification = ref(false)
const isLoading = ref(true);
// Blinking logic
const isBlinking = ref(false)
let blinkInterval = null
const startBlinkInterval = () => {
blinkInterval = setInterval(() => {
isBlinking.value = true
setTimeout(() => {
isBlinking.value = false
}, 10 * 1000) // Blink for 1 second
}, 5000) // Trigger every 20 seconds
}
const headers = [
{ title: 'Patient Name', key: 'patient_name' },
{ title: 'Start Date', key: 'start_time' },
{ title: 'End Date', key: 'end_time' },
{ title: 'Duration', key: 'duration' },
{ title: 'Status', key: 'appointment_status' },
{ title: 'Action', key: 'action' },
]
const formattedHistory = computed(() => {
appoinmentList.value.sort((a, b) => {
return b.order_id - a.order_id;
});
return appoinmentList.value.map(history => ({
...history,
appointment_date: changeFormat(history.appointment_date),
start_time: formatDate(history.start_time),
end_time: formatDate(history.end_time),
duration: totalCallDuration(history.start_time, history.end_time),
}));
});
const resolveStatusVariant = (status) => {
switch (status.toLowerCase()) {
case 'completed':
return { color: 'success', text: 'Completed' }
case 'scheduled':
return { color: 'primary', text: 'Scheduled' }
case 'cancelled':
return { color: 'error', text: 'Cancelled' }
default:
return { color: 'warning', text: 'Pending' }
}
}
onMounted(async () => {
await store.dispatch('dashboardOrderAppoinmentList')
appoinmentList.value = store.getters.getProviderAppointmentOrderList
let getNotifications = store.getters.getNotifications
console.log("refChart", getNotifications);
isLoading.value = false;
startNotificationInterval()
startBlinkInterval()
});
onBeforeUnmount(() => {
if (notificationInterval) {
clearInterval(notificationInterval)
}
if (blinkInterval) {
clearInterval(blinkInterval)
}
})
// You might want to expose this method if you need to reset the timer from a parent component
let notificationInterval = null
const startNotificationInterval = () => {
notificationInterval = setInterval(() => {
showNotification.value = true
setTimeout(() => {
showNotification.value = false
}, 5 * 1000) // Show notification for 2 seconds
}, 10000) // Trigger every 10 seconds
}
const formatDate = (date) => {
const messageDate = new Date(date);
const options = {
year: "numeric",
month: "numeric",
day: "numeric",
hour: "numeric",
minute: "2-digit",
hour12: false,
};
const formattedDate = messageDate
.toLocaleString("en-US", options)
.replace(/\//g, "-").replace(',', '');
return `${formattedDate}`;
}
function changeFormat(dateFormat) {
const dateParts = dateFormat.split('-'); // Assuming date is in yyyy-mm-dd format
const year = parseInt(dateParts[0]);
const month = parseInt(dateParts[1]); // No need for padding
const day = parseInt(dateParts[2]); // No need for padding
// Create a new Date object with the parsed values
const date = new Date(year, month - 1, day); // Month is zero-based in JavaScript Date object
// Format the date as mm-dd-yyyy
const formattedDate = month + '-' + day + '-' + date.getFullYear();
return formattedDate;
}
function totalCallDuration(start_time, end_time) {
console.log(start_time, end_time);
const startMoment = moment(start_time);
const endMoment = moment(end_time);
// Calculate the duration
const duration = moment.duration(endMoment.diff(startMoment));
const hours = duration.hours();
const thours = `${String(hours).padStart(2, "0")}`;
const minutes = duration.minutes();
const tminutes = `${String(minutes).padStart(2, "0")}`;
const seconds = duration.seconds();
const tsecond = `${String(seconds).padStart(2, "0")}`;
let durationText;
if (hours === 0 && minutes === 0) {
//for second
durationText = ` 00:00:${tsecond}`;
} else if (hours === 0 && minutes > 0) {
//for minutes
durationText = `00:${tminutes}:${tsecond}`;
} else if (hours > 0) {
//for hours
durationText = `${thours}:${tminutes}:${tsecond}`;
}
const totalDuration = durationText;
console.log("Duration:", durationText);
// You may need to adjust this function based on your actual data structure
// For example, if you have separate first name and last name properties in each appointment object
return totalDuration; // For now, just return the first name
}
const handleActionClick = async (item) => {
console.log('Action clicked for item:', item)
await store.dispatch('agentMeetingSatausUpdate', { id: item.appointment_id })
await store.dispatch('dashboardOrderAppoinmentList')
appoinmentList.value = store.getters.getProviderAppointmentOrderList
// Add your logic here, e.g., opening a dialog, changing the status, etc.
}
</script>
<template>
<div>
<VCard>
<VCardTitle class="text-h5 font-weight-bold pa-4">
Meetings
</VCardTitle>
<VCardText>
<VDataTable :loading="isLoading" :headers="headers" :items="formattedHistory"
:items-per-page="options.itemsPerPage" :page="options.page" :options="options">
<!-- full name -->
<template #item.patient_name="{ item }">
<div class="d-flex align-center">
<VAvatar size="32" :color="item.profile_picture ? '' : 'primary'"
:class="item.profile_picture ? '' : 'v-avatar-light-bg primary--text'"
:variant="!item.profile_picture ? 'tonal' : undefined">
<VImg v-if="item.profile_picture" :src="item.profile_picture" />
<span v-else>{{ item.patient_name.charAt(0) }}</span>
</VAvatar>
<div class="d-flex flex-column ms-3">
<RouterLink
:to="{ name: 'provider-patient-profile-detail', params: { id: item.patient_id } }">
<span class="d-block font-weight-medium text-high-emphasis text-truncate">
{{ item.patient_name }}
</span>
</RouterLink>
<small>Order ID:#<RouterLink
:to="{ name: 'provider-order-detail', params: { id: item.order_id } }">
#{{ item.order_id }}
</RouterLink></small>
</div>
</div>
</template>
<!-- status -->
<template #item.appointment_status="{ item }">
<VChip :color="resolveStatusVariant(item.appointment_status).color" class="font-weight-medium"
size="small"
:class="{ 'blink-status': item.appointment_status.toLowerCase() !== 'completed' && isBlinking }">
{{ resolveStatusVariant(item.appointment_status).text }}
</VChip>
</template>
<template #item.action="{ item }">
<VBtn size="small" :disabled="item.appointment_status.toLowerCase() == 'completed'"
color="primary" @click="handleActionClick(item)">
<VIcon v-if="item.appointment_status.toLowerCase() !== 'completed' && showNotification"
color="error" icon="mdi-arrow-right" size="small" style="display: none;" />
Mark as Completed
</VBtn>
</template>
<template #bottom>
<VCardText class="pt-2">
<div class="d-flex flex-wrap justify-center justify-sm-space-between gap-y-2 mt-2">
<VSelect v-model="options.itemsPerPage" :items="[5, 10, 25, 50, 100]"
label="Rows per page:" variant="underlined"
style="max-inline-size: 8rem;min-inline-size: 5rem;" />
<VPagination v-model="options.page" :total-visible="smAndDown ? 2 : 5"
:length="Math.ceil(formattedHistory.length / options.itemsPerPage)" />
</div>
</VCardText>
</template>
</VDataTable>
</VCardText>
</VCard>
</div>
</template>
<style lang="scss" scoped>
@keyframes blink {
0% {
opacity: 1;
}
50% {
opacity: 0.5;
}
100% {
opacity: 1;
}
}
.blink-status {
animation: blink 1s linear infinite;
}
.text-body-1 {
font-size: .9375rem !important;
font-weight: 400;
line-height: 1.375rem;
letter-spacing: normal !important;
font-family: Public Sans, sans-serif, -apple-system, blinkmacsystemfont, Segoe UI, roboto, Helvetica Neue, arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", Segoe UI Symbol;
text-transform: none !important;
}
.text-h6 {
font-size: .9375rem !important;
font-weight: 500;
line-height: 1.375rem;
letter-spacing: normal !important;
font-family: Public Sans, sans-serif, -apple-system, blinkmacsystemfont, Segoe UI, roboto, Helvetica Neue, arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", Segoe UI Symbol;
text-transform: none !important;
}
.vehicle-progress-label {
padding-block-end: 1rem;
&::after {
position: absolute;
display: inline-block;
background-color: rgba(var(--v-theme-on-surface), var(--v-border-opacity));
block-size: 10px;
content: "";
inline-size: 2px;
inset-block-end: 0;
inset-inline-start: 0;
[dir="rtl"] & {
inset-inline: unset 0;
}
}
}
.v-progress-linear__content {
justify-content: start;
padding-inline-start: 1rem;
}
@media (max-width: 1080px) {
.v-progress-linear__content {
padding-inline-start: 0.75rem !important;
}
}
@media (max-width: 576px) {
.v-progress-linear__content {
padding-inline-start: 0.3rem !important;
}
}
</style>