316 lines
11 KiB
Vue
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>
|