initial commit

This commit is contained in:
Inshal
2024-10-25 01:05:27 +05:00
commit 94cd8a1dc9
1710 changed files with 273609 additions and 0 deletions

View File

@@ -0,0 +1,113 @@
<script setup>
const logisticData = ref([
{
icon: 'ri-car-line',
color: 'primary',
title: 'On route vehicles',
value: 42,
change: 18.2,
isHover: false,
},
{
icon: 'ri-alert-line',
color: 'warning',
title: 'Vehicles with errors',
value: 8,
change: -8.7,
isHover: false,
},
{
icon: 'ri-stackshare-line',
color: 'error',
title: 'Deviated from route',
value: 27,
change: 4.3,
isHover: false,
},
// {
// icon: 'ri-timer-line',
// color: 'info',
// title: 'Late vehicles',
// value: 13,
// change: -2.5,
// isHover: false,
// },
])
</script>
<template>
<VRow>
<VCol v-for="(data, index) in logisticData" :key="index" cols="12" md="4" sm="6">
<div>
<VCard class="logistics-card-statistics cursor-pointer"
:style="data.isHover ? `border-block-end-color: rgb(var(--v-theme-${data.color}))` : `border-block-end-color: rgba(var(--v-theme-${data.color}),0.7)`"
@mouseenter="data.isHover = true" @mouseleave="data.isHover = false">
<VCardText>
<div class="d-flex align-center gap-x-4 mb-2">
<VAvatar variant="tonal" :color="data.color" rounded>
<VIcon :icon="data.icon" size="24" />
</VAvatar>
<h4 class="text-h4">
{{ data.value }}
</h4>
</div>
<h6 class="text-body-1 mb-2">
{{ data.title }}
</h6>
<div class="d-flex gap-x-2 align-center">
<div class="text-body-1 font-weight-medium me-2">
{{ data.change }}%
</div>
<span class="text-sm text-disabled">than last week</span>
</div>
</VCardText>
</VCard>
</div>
</VCol>
</VRow>
</template>
<style lang="scss" scoped>
@use "@core-scss/base/mixins" as mixins;
.text-h4 {
font-size: 1.5rem !important;
font-weight: 500;
line-height: 2.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-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;
}
.logistics-card-statistics {
border-block-end-style: solid;
border-block-end-width: 2px;
&:hover {
border-block-end-width: 3px;
margin-block-end: -1px;
@include mixins.elevation(10);
transition: all 0.1s ease-out;
}
}
.skin--bordered {
.logistics-card-statistics {
&:hover {
margin-block-end: -2px;
}
}
}
</style>

View File

@@ -0,0 +1,315 @@
<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>

View File

@@ -0,0 +1,126 @@
<script setup>
import store from '@/store';
import { useRoute, useRouter } from 'vue-router';
const router = useRouter()
const route = useRoute()
const props = defineProps({
orderData: {
type: Object,
required: true,
},
})
const notes = ref([]);
const historyNotes = computed(async () => {
let notesData = props.orderData.appointment_notes;
console.log("notesData", notesData);
for (let data of notesData) {
if (data.note_type == 'Notes') {
let dataObject = {}
dataObject.note = data.note
dataObject.doctor = props.orderData.appointment_details.provider_name;
dataObject.date = formatDateDate(data.created_at)
dataObject.id = data.id
//notes.value.push(dataObject)
}
}
notes.value.sort((a, b) => {
return b.id - a.id;
});
console.log("getNotes", notes.value);
store.dispatch('updateIsLoading', false)
return notes.value
});
const formatDateDate = (date) => {
const messageDate = new Date(date);
const options = {
year: 'numeric',
month: 'long',
day: 'numeric',
hour: '2-digit',
minute: '2-digit'
};
return messageDate.toLocaleDateString('en-US', options).replace(/\//g, '-');
};
onMounted(async () => {
let notesData = props.orderData.appointment_notes;
console.log("notesData", notesData);
for (let data of notesData) {
if (data.note_type == 'Notes') {
let dataObject = {}
dataObject.note = data.note
dataObject.doctor = props.orderData.appointment_details.provider_name;
dataObject.date = formatDateDate(data.created_at)
dataObject.id = data.id
notes.value.push(dataObject)
}
}
notes.value.sort((a, b) => {
return b.id - a.id;
});
console.log("getNotes", notes.value);
});
</script>
<template>
<VDialog v-model="store.getters.getIsLoading" width="110" height="150" color="primary">
<VCardText class="" style="color: white !important;">
<div class="demo-space-x">
<VProgressCircular :size="40" color="primary" indeterminate />
</div>
</VCardText>
</VDialog>
<VCard title="Notes" v-if="notes.length > 0">
<VCardText>
<VTimeline truncate-line="both" align="start" side="end" line-inset="10" line-color="primary"
density="compact" class="v-timeline-density-compact">
<template v-if="historyNotes">
<VTimelineItem dot-color="yellow" size="x-small" v-for="(p_note, index) of notes" :key="index">
<div class="d-flex justify-space-between align-center mb-3">
<span class="app-timeline-title">{{ p_note.note }}</span>
<span class="app-timeline-meta">{{ p_note.date }}</span>
</div>
<p class="app-timeline-text mb-0">
{{ p_note.doctor }}
</p>
</VTimelineItem>
</template>
</VTimeline>
</VCardText>
</VCard>
<VCard v-else>
<VAlert border="start" color="rgb(var(--v-theme-yellow))" variant="tonal">
<div class="text-center">No data found</div>
</VAlert>
</VCard>
<!-- <VList class="pb-0" lines="two" v-if="historyNotes">
<template v-if="notes.length > 0" v-for="(p_note, index) of notes" :key="index">
<VListItem class="pb-0" border>
<VListItemTitle>
<span class="pb-0">{{ p_note.note }}</span>
<p class="text-start fs-5 mb-0 pb-0 text-grey">
<small> {{ p_note.doctor }}</small>
</p>
<p class="text-end fs-5 mb-0 pb-0 text-grey">
<small> {{ p_note.date }}</small>
</p>
</VListItemTitle>
</VListItem>
<VDivider v-if="index !== notes.length - 1" />
</template>
<template v-else>
<VCard>
<VAlert border="start" color="rgb(var(--v-theme-yellow))" variant="tonal">
<div class="text-center">No data found</div>
</VAlert>
</VCard>
</template>
</VList> -->
</template>

View File

@@ -0,0 +1,358 @@
<script setup>
import { computed, onMounted, ref } from "vue";
import { useRoute, useRouter } from 'vue-router';
import { useStore } from "vuex";
const store = useStore();
const props = defineProps({
orderData: {
type: Object,
required: true,
},
})
const router = useRouter()
const route = useRoute()
const itemsPrescriptions = ref([]);
// const patientId = route.params.patient_id;
const prescriptionLoaded = ref(false)
const doctorName = ref('');
const prescription = computed(async () => {
await fetchPrescriptions()
return prescriptionLoaded.value ? itemsPrescriptions.value : null
})
const fetchPrescriptions = async () => {
store.dispatch('updateIsLoading', true)
await getprescriptionList()
doctorName.value = props.orderData.appointment_details.provider_name
store.dispatch('updateIsLoading', false)
prescriptionLoaded.value = true
}
const getprescriptionList = async () => {
let prescriptions = props.orderData.prescription;
// itemsPrescriptions.value = store.getters.getPrescriptionList
for (let data of prescriptions) {
let dataObject = {}
dataObject.brand = data.brand
dataObject.direction_one = data.direction_one
dataObject.direction_quantity = data.direction_quantity
dataObject.direction_two = data.direction_two
dataObject.date = formatDateDate(data.created_at)
dataObject.dosage = data.dosage
dataObject.from = data.from
dataObject.name = data.name
dataObject.quantity = data.quantity
dataObject.refill_quantity = data.refill_quantity
dataObject.status = data.status
dataObject.comments = data.comments
itemsPrescriptions.value.push(dataObject)
}
itemsPrescriptions.value.sort((a, b) => {
return b.id - a.id;
});
console.log("itemsPrescriptions", itemsPrescriptions.value);
};
const formatDate = (dateString) => {
return new Date(dateString).toLocaleDateString('en-US', {
year: 'numeric',
month: 'short',
day: 'numeric'
})
}
const formatTime = (dateString) => {
return new Date(dateString).toLocaleTimeString('en-US', {
hour: 'numeric',
minute: 'numeric'
})
}
const getStatusColor = (status) => {
switch (status) {
case 'pending':
return 'warning'; // Use Vuetify's warning color (typically yellow)
case 'shipped':
return '#45B8AC'; // Use Vuetify's primary color (typically blue)
case 'delivered':
return 'green';
case 'returned':
return 'red';
case 'results':
return 'blue';
default:
return 'grey'; // Use Vuetify's grey color for any other status
}
};
onMounted(async () => {
let prescriptions = props.orderData.prescription;
console.log('props.orderData', props.orderData.prescription)
itemsPrescriptions.value = prescriptions
});
</script>
<template>
<VDialog v-model="store.getters.getIsLoading" width="110" height="150" color="primary">
<VCardText class="" style="color: white !important;">
<div class="demo-space-x">
<VProgressCircular :size="40" color="primary" indeterminate />
</div>
</VCardText>
</VDialog>
<template v-if="itemsPrescriptions.length > 0">
<v-row>
<v-col v-for="prescription in itemsPrescriptions" :key="prescription.prescription_id" cols="12" md="4">
<v-card class="mx-auto mb-4" elevation="4" hover>
<v-img height="200" src="https://cdn.pixabay.com/photo/2016/11/23/15/03/medication-1853400_1280.jpg"
class="white--text align-end" gradient="to bottom, rgba(0,0,0,.1), rgba(0,0,0,.5)">
<v-card-title class="text-h5" style="color: #fff;">{{ prescription.prescription_name
}}</v-card-title>
</v-img>
<v-card-text>
<v-chip :color="getStatusColor(prescription.status)" text-color="white" small class="mr-2">
{{ prescription.status }}
</v-chip>
</v-card-text>
<v-divider class="mx-4"></v-divider>
<v-card-text>
<v-row dense>
<v-col cols="6">
<v-icon small color="rgb(var(--v-theme-yellow))">mdi-pill</v-icon>
<span class="ml-1">Dosage:{{ prescription.dosage }}</span>
</v-col>
<v-col cols="6">
<v-icon small color="rgb(var(--v-theme-yellow))">mdi-bottle-tonic</v-icon>
<span class="ml-1">Quantity:{{ prescription.quantity }}</span>
</v-col>
<v-col cols="6">
<v-icon small color="rgb(var(--v-theme-yellow))">mdi-calendar</v-icon>
<span class="ml-1">{{ formatDate(prescription.prescription_date) }}</span>
</v-col>
<v-col cols="6">
<v-icon small color="rgb(var(--v-theme-yellow))">mdi-clock-outline</v-icon>
<span class="ml-1">{{ formatTime(prescription.prescription_date) }}</span>
</v-col>
</v-row>
</v-card-text>
<v-card-actions>
<v-btn color="rgb(var(--v-theme-yellow-theme-button))" text>
More Details
</v-btn>
<v-spacer></v-spacer>
<v-btn icon @click="prescription.show = !prescription.show">
<v-icon color="rgb(var(--v-theme-yellow-theme-button))">{{ prescription.show ?
'mdi-chevron-up' :
'mdi-chevron-down'
}}</v-icon>
</v-btn>
</v-card-actions>
<v-expand-transition>
<div v-show="prescription.show">
<v-divider></v-divider>
<v-card-text>
<v-row dense>
<v-col cols="12">
<strong>Provider:</strong> {{ props.orderData.appointment_details.provider_name
}}
</v-col>
<v-col cols="12">
<strong>Brand:</strong> {{ prescription.brand }}
</v-col>
<v-col cols="12">
<strong>From:</strong> {{ prescription.from }}
</v-col>
<v-col cols="12">
<strong>Direction One:</strong> {{ prescription.direction_one }}
</v-col>
<v-col cols="12">
<strong>Direction Two:</strong> {{ prescription.direction_two }}
</v-col>
<v-col cols="12">
<strong>Refill Quantity:</strong> {{ prescription.refill_quantity }}
</v-col>
<v-col cols="12">
<strong>Direction Quantity:</strong> {{ prescription.direction_quantity }}
</v-col>
<v-col cols="12" v-if="prescription.comments">
<strong>Comments:</strong> {{ prescription.comments }}
</v-col>
</v-row>
</v-card-text>
</div>
</v-expand-transition>
</v-card>
</v-col>
</v-row>
<VExpansionPanels variant="accordion" style="display: none;">
<VExpansionPanel v-for="(item, index) in itemsPrescriptions" :key="index">
<div>
<VExpansionPanelTitle collapse-icon="mdi-chevron-down" expand-icon="mdi-chevron-right"
style="margin-left: 0px !important;">
<p class=""><b> {{ item.name }}</b>
<br />
<div class=" pt-2"> {{ doctorName }}</div>
<div class=" pt-2">{{ item.date }}</div>
</p>
<v-row>
</v-row>
<span class="v-expansion-panel-title__icon badge text-warning"
v-if="item.status == null">Pending</span>
<span class="v-expansion-panel-title__icon badge" v-else>
<v-chip :color="getStatusColor(item.status)" label size="small" variant="text">
{{ item.status }}
</v-chip></span>
</VExpansionPanelTitle>
<VExpansionPanelText class="pt-0">
<v-row class='mt-1'>
<v-col cols="12" md="4" sm="6">
<p class='heading'><b>Brand:</b></p>
</v-col>
<v-col cols="12" md="4" sm="6">
<p>{{ item.brand }}</p>
</v-col>
</v-row>
<v-row class='mt-1'>
<v-col cols="12" md="4" sm="6">
<p class='heading'><b>From:</b></p>
</v-col>
<v-col cols="12" md="4" sm="6">
<p>{{ item.from }}</p>
</v-col>
</v-row>
<v-row class='mt-1'>
<v-col cols="12" md="4" sm="6">
<p class='heading'><b>Dosage:</b></p>
</v-col>
<v-col cols="12" md="4" sm="6">
<p>{{ item.dosage }}</p>
</v-col>
</v-row>
<v-row class='mt-1'>
<v-col cols="12" md="4" sm="6">
<p class='heading'><b>Quantity:</b></p>
</v-col>
<v-col cols="12" md="4" sm="6">
<p>{{ item.quantity }}</p>
</v-col>
</v-row>
<v-row class='mt-1'>
<v-col cols="12" md="4" sm="6">
<p class='heading'><b>Direction Quantity:</b></p>
</v-col>
<v-col cols="12" md="8" sm="6">
<p>{{ item.direction_quantity }}</p>
</v-col>
</v-row>
<v-row class='mt-1'>
<v-col cols="12" md="4" sm="6">
<p class='heading'><b>Direction One:</b></p>
</v-col>
<v-col cols="12" md="8" sm="6">
<p>{{ item.direction_one }} </p>
</v-col>
</v-row>
<v-row class='mt-1'>
<v-col cols="12" md="4" sm="6">
<p class='heading'><b>Direction Two:</b></p>
</v-col>
<v-col cols="12" md="8" sm="6">
<p>{{ item.direction_two }} </p>
</v-col>
</v-row>
<v-row class='mt-1'>
<v-col cols="12" md="4" sm="6">
<p class='heading'><b>Refill Quantity:</b></p>
</v-col>
<v-col cols="12" md="8" sm="6">
<p>{{ item.refill_quantity }}</p>
</v-col>
</v-row>
<v-row class='mt-1'>
<v-col cols="12" md="4" sm="6">
<p class='heading'><b>Status:</b></p>
</v-col>
<v-col cols="12" md="8" sm="6">
<p v-if="item.status == null" class="text-warning">Pending</p>
<p v-else>{{ item.status }}</p>
</v-col>
</v-row>
<v-row class='mt-1'>
<v-col cols="12" md="4" sm="6">
<p class='heading'><b>Comments:</b></p>
</v-col>
<v-col cols="12" md="8" sm="8">
<p>{{ item.comments }} </p>
</v-col>
</v-row>
</VExpansionPanelText>
</div>
</VExpansionPanel>
<br />
</VExpansionPanels>
</template>
<template v-else="prescriptionLoaded">
<VCard>
<VCard>
<VAlert border="start" color="rgb(var(--v-theme-yellow-theme-button))" variant="tonal">
<div class="text-center">No data found</div>
</VAlert>
</VCard>
</VCard>
</template>
</template>
<style lang="scss">
button.v-expansion-panel-title {
background-color: rgb(var(--v-theme-yellow)) !important;
color: #fff;
}
button.v-expansion-panel-title.bg-secondary {
background-color: rgb(var(--v-theme-yellow)) !important;
color: #fff;
}
button.v-expansion-panel-title {
background-color: rgb(var(--v-theme-yellow)) !important;
color: #fff;
}
span.v-expansion-panel-title__icon {
margin-left: 0px !important;
}
span.v-expansion-panel-title__icon {
color: #fff
}
.v-expansion-panel {
background-color: #fff;
border-radius: 16px;
box-shadow: 0px 4px 12px rgba(0, 0, 0, 0.1);
margin-bottom: 10px;
overflow: hidden;
transition: box-shadow 0.3s ease;
}
</style>

View File

@@ -0,0 +1,444 @@
<script setup>
import vuetifyInitialThemes from '@/plugins/vuetify/theme'
import ChatActiveChatUserProfileSidebarContent from '@/views/apps/chat/ChatActiveChatUserProfileSidebarContent.vue'
import ChatLeftSidebarContent from '@/views/apps/chat/ChatLeftSidebarContent.vue'
import ChatLog from '@/views/apps/chat/ChatLog.vue'
import ChatUserProfileSidebarContent from '@/views/apps/chat/ChatUserProfileSidebarContent.vue'
import { useChat } from '@/views/apps/chat/useChat'
import { useChatStore } from '@/views/apps/chat/useChatStore'
import { useResponsiveLeftSidebar } from '@core/composable/useResponsiveSidebar'
import { avatarText } from '@core/utils/formatters'
import 'perfect-scrollbar/css/perfect-scrollbar.css'
import { computed, nextTick, onMounted, ref, watch } from "vue"
import {
useDisplay,
useTheme,
} from 'vuetify'
import { useStore } from 'vuex'
const store1 = useStore()
const vuetifyDisplays = useDisplay()
const store = useChatStore()
const { isLeftSidebarOpen } = useResponsiveLeftSidebar(vuetifyDisplays.smAndDown)
const { resolveAvatarBadgeVariant } = useChat()
// Perfect scrollbar
const chatLogPS = ref(null)
const scrollToBottomInChatLog = () => {
const scrollEl = chatLogPS.value.$el || chatLogPS.value
scrollEl.scrollTop = scrollEl.scrollHeight
}
// Search query
const q = ref('')
watch(q, val => store.fetchChatsAndContacts(val), { immediate: true })
// Open Sidebar in smAndDown when "start conversation" is clicked
const startConversation = () => {
if (vuetifyDisplays.mdAndUp.value)
return
isLeftSidebarOpen.value = true
}
const chatLogContainer = ref(null)
onMounted(async () => {
await store1.dispatch("getPtaientForChat");
console.log('getPtaientForChat', store1.getters.getPatientChat)
nextTick(() => {
if (chatLogContainer.value) {
chatLogContainer.value.scrollTop = chatLogContainer.value.scrollHeight
}
})
})
// If you need to scroll to bottom after content changes:
const scrollToBottom = () => {
nextTick(() => {
if (chatLogContainer.value) {
chatLogContainer.value.scrollTop = chatLogContainer.value.scrollHeight
}
})
}
// Chat message
const msg = ref('')
const sendMessage = async () => {
if (!msg.value)
return
await store.sendMsg(msg.value)
// Reset message input
msg.value = ''
// Scroll to bottom
nextTick(() => {
scrollToBottom()
})
}
const openChatOfContact = async userId => {
await store.getChat(userId)
// Reset message input
msg.value = ''
// Set unseenMsgs to 0
const contact = store.chatsContacts.find(c => c.id === userId)
if (contact)
contact.chat.unseenMsgs = 0
// if smAndDown => Close Chat & Contacts left sidebar
if (vuetifyDisplays.smAndDown.value)
isLeftSidebarOpen.value = false
// Scroll to bottom
nextTick(() => {
scrollToBottom()
})
}
// User profile sidebar
const isUserProfileSidebarOpen = ref(false)
// Active chat user profile sidebar
const isActiveChatUserProfileSidebarOpen = ref(false)
// file input
const refInputEl = ref()
const moreList = [
{
title: 'View Contact',
value: 'View Contact',
},
{
title: 'Mute Notifications',
value: 'Mute Notifications',
},
{
title: 'Block Contact',
value: 'Block Contact',
},
{
title: 'Clear Chat',
value: 'Clear Chat',
},
{
title: 'Report',
value: 'Report',
},
]
const { name } = useTheme()
const chatContentContainerBg = computed(() => {
let color = 'transparent'
if (vuetifyInitialThemes)
color = vuetifyInitialThemes.themes?.[name.value].colors?.background
return color
})
</script>
<template>
<VLayout class="chat-app-layout">
<!-- 👉 user profile sidebar -->
<VNavigationDrawer v-model="isUserProfileSidebarOpen" temporary touchless absolute class="user-profile-sidebar"
location="start" width="370">
<ChatUserProfileSidebarContent @close="isUserProfileSidebarOpen = false" />
</VNavigationDrawer>
<!-- 👉 Active Chat sidebar -->
<VNavigationDrawer v-model="isActiveChatUserProfileSidebarOpen" width="374" absolute temporary location="end"
touchless class="active-chat-user-profile-sidebar">
<ChatActiveChatUserProfileSidebarContent @close="isActiveChatUserProfileSidebarOpen = false" />
</VNavigationDrawer>
<!-- 👉 Left sidebar -->
<VNavigationDrawer v-model="isLeftSidebarOpen" absolute touchless location="start" width="370"
:temporary="$vuetify.display.smAndDown" class="chat-list-sidebar" :permanent="$vuetify.display.mdAndUp">
<ChatLeftSidebarContent v-model:isDrawerOpen="isLeftSidebarOpen" v-model:search="q"
@open-chat-of-contact="openChatOfContact" @show-user-profile="isUserProfileSidebarOpen = true"
@close="isLeftSidebarOpen = false" />
</VNavigationDrawer>
<!-- 👉 Chat content -->
<VMain class="chat-content-container">
<!-- 👉 Right content: Active Chat -->
<div v-if="store.activeChat" class="d-flex flex-column h-100">
<!-- 👉 Active chat header -->
<div class="active-chat-header d-flex align-center text-medium-emphasis bg-surface">
<!-- Sidebar toggler -->
<IconBtn class="d-md-none me-3" @click="isLeftSidebarOpen = true">
<VIcon icon="tabler-menu-2" />
</IconBtn>
<!-- avatar -->
<div class="d-flex align-center cursor-pointer" @click="isActiveChatUserProfileSidebarOpen = true">
<VBadge dot location="bottom right" offset-x="3" offset-y="0"
:color="resolveAvatarBadgeVariant(store.activeChat.contact.status)" bordered>
<VAvatar size="38" :variant="!store.activeChat.contact.avatar ? 'tonal' : undefined"
:color="!store.activeChat.contact.avatar ? resolveAvatarBadgeVariant(store.activeChat.contact.status) : undefined"
class="cursor-pointer">
<VImg v-if="store.activeChat.contact.avatar" :src="store.activeChat.contact.avatar"
:alt="store.activeChat.contact.fullName" />
<span v-else>{{ avatarText(store.activeChat.contact.fullName) }}</span>
</VAvatar>
</VBadge>
<div class="flex-grow-1 ms-4 overflow-hidden">
<p class="text-h6 mb-0 font-weight-regular">
{{ store.activeChat.contact.fullName }}
</p>
<p class="text-truncate mb-0 text-body-2">
{{ store.activeChat.contact.role }}
</p>
</div>
</div>
<VSpacer />
<!-- Header right content -->
<div class="d-sm-flex align-center d-none">
<IconBtn>
<VIcon icon="tabler-phone-call" />
</IconBtn>
<IconBtn>
<VIcon icon="tabler-video" />
</IconBtn>
<IconBtn>
<VIcon icon="tabler-search" />
</IconBtn>
</div>
<MoreBtn :menu-list="moreList" density="comfortable" color="undefined" />
</div>
<VDivider />
<!-- Chat log -->
<div ref="chatLogContainer" class="chat-log-container">
<ChatLog />
</div>
<!-- Message form -->
<VForm class="chat-log-message-form mb-5 mx-5" @submit.prevent="sendMessage">
<VTextField :key="store.activeChat?.contact.id" v-model="msg" variant="solo"
class="chat-message-input" placeholder="Type your message..." density="default" autofocus>
<template #append-inner>
<IconBtn>
<VIcon icon="tabler-microphone" />
</IconBtn>
<IconBtn class="me-2" @click="refInputEl?.click()">
<VIcon icon="tabler-photo" />
</IconBtn>
<VBtn @click="sendMessage">
Send
</VBtn>
</template>
</VTextField>
<input ref="refInputEl" type="file" name="file" accept=".jpeg,.png,.jpg,GIF" hidden>
</VForm>
</div>
<!-- 👉 Start conversation -->
<div v-else class="d-flex h-100 align-center justify-center flex-column">
<VAvatar size="109" class="elevation-3 mb-6 bg-surface">
<VIcon size="50" class="rounded-0 text-high-emphasis" icon="tabler-message" />
</VAvatar>
<p class="mb-0 px-6 py-1 font-weight-medium text-lg elevation-3 rounded-xl text-high-emphasis bg-surface"
:class="[{ 'cursor-pointer': $vuetify.display.smAndDown }]" @click="startConversation">
Start Conversation
</p>
</div>
</VMain>
</VLayout>
</template>
<style lang="scss">
@use "@styles/variables/_vuetify.scss";
@use "@core/scss/base/_mixins.scss";
@use "@layouts/styles/mixins" as layoutsMixins;
// Variables
$chat-app-header-height: 62px;
// Placeholders
%chat-header {
display: flex;
align-items: center;
min-block-size: $chat-app-header-height;
padding-inline: 1rem;
}
.chat-app-layout {
border-radius: vuetify.$card-border-radius;
@include mixins.elevation(vuetify.$card-elevation);
$sel-chat-app-layout: &;
@at-root {
.skin--bordered {
@include mixins.bordered-skin($sel-chat-app-layout);
}
}
.active-chat-user-profile-sidebar,
.user-profile-sidebar {
.v-navigation-drawer__content {
display: flex;
flex-direction: column;
}
}
.chat-list-header,
.active-chat-header {
@extend %chat-header;
}
.chat-list-search {
.v-field__outline__start {
flex-basis: 20px !important;
border-radius: 28px 0 0 28px !important;
}
.v-field__outline__end {
border-radius: 0 28px 28px 0 !important;
}
@include layoutsMixins.rtl {
.v-field__outline__start {
flex-basis: 20px !important;
border-radius: 0 28px 28px 0 !important;
}
.v-field__outline__end {
border-radius: 28px 0 0 28px !important;
}
}
}
.chat-list-sidebar {
.v-navigation-drawer__content {
display: flex;
flex-direction: column;
}
}
}
.chat-content-container {
/* stylelint-disable-next-line value-keyword-case */
background-color: v-bind(chatContentContainerBg);
min-height: 450px;
// Adjust the padding so text field height stays 48px
.chat-message-input {
.v-field__append-inner {
align-items: center;
padding-block-start: 0;
}
.v-field--appended {
padding-inline-end: 9px;
}
}
}
.chat-user-profile-badge {
.v-badge__badge {
/* stylelint-disable liberty/use-logical-spec */
min-width: 12px !important;
height: 0.75rem;
/* stylelint-enable liberty/use-logical-spec */
}
}
.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;
}
.text-body-2 {
font-size: .8125rem !important;
font-weight: 400;
line-height: 1.25rem;
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;
}
.chat-log-container {
height: 400px;
/* Adjust as needed */
overflow-y: auto;
padding-right: 15px;
scrollbar-width: none;
/* For Firefox */
-ms-overflow-style: none;
/* For Internet Explorer and Edge */
transition: all 0.3s ease;
}
/* Hide scrollbar for Chrome, Safari and Opera */
.chat-log-container::-webkit-scrollbar {
width: 0;
background: transparent;
}
/* Show scrollbar on hover */
.chat-log-container:hover {
scrollbar-width: thin;
/* For Firefox */
-ms-overflow-style: auto;
/* For Internet Explorer and Edge */
}
.chat-log-container:hover::-webkit-scrollbar {
width: 2px;
}
.chat-log-container:hover::-webkit-scrollbar-track {
background: rgba(0, 0, 0, 0.05);
border-radius: 10px;
}
.chat-log-container:hover::-webkit-scrollbar-thumb {
background-color: rgba(0, 0, 0, 0.2);
border-radius: 10px;
transition: background-color 0.3s ease;
}
.chat-log-container:hover::-webkit-scrollbar-thumb:hover {
background-color: rgba(0, 0, 0, 0.4);
}
/* Add a subtle shadow to indicate scrollable content */
.chat-log-container::after {
content: '';
position: absolute;
top: 0;
right: 0;
width: 2px;
height: 100%;
background: linear-gradient(to right, transparent, rgba(0, 0, 0, 0.05));
opacity: 0;
transition: opacity 0.3s ease;
pointer-events: none;
}
.chat-log-container:hover::after {
opacity: 1;
}
</style>

View File

@@ -0,0 +1,253 @@
<script setup>
import MeetingsDashboard from "@/pages/provider/MeetingsDashboard.vue";
import store from '@/store';
import EcommerceStatistics from '@/views/dashboard/provider/EcommerceStatistics.vue';
import PatientQueueMobile from '@/views/dashboard/provider/PatientQueueMobile.vue';
import videoCameraOff from '@images/svg/video-camera-off.svg';
import { computed, nextTick, onMounted, onUnmounted, ref } from 'vue';
const isLoadingVisible = ref(false);
const getIsTonalSnackbarVisible = ref(false);
const errorMessage = ref(null);
const videoElement = ref(null);
const isCameraPermissionGranted = ref(false);
const isMicrophonePermissionGranted = ref(false);
const isCameraDisabled = ref(false);
const isMuted = ref(false);
const isMobile = ref(window.innerWidth <= 768);
const profileData = ref(null);
const profileName = ref(null);
const mediaStream = ref(null);
const enableCamBtn = ref(true)
const camIcon = computed(() => {
return isCameraDisabled.value ? 'mdi-video-off' : 'mdi-video';
});
const checkMobile = () => {
isMobile.value = window.innerWidth <= 768;
};
const camText = computed(() => {
return isCameraDisabled.value ? 'Turn on camera' : 'Turn off camera';
});
const camIconColor = computed(() => {
return isCameraDisabled.value ? 'red' : 'black';
});
const micIcon = computed(() => {
return isMuted.value ? 'mdi-microphone-off' : 'mdi-microphone';
});
const micText = computed(() => {
return isMuted.value ? 'Unmute Myself' : 'Mute Myself';
});
const micIconColor = computed(() => {
return isMuted.value ? 'red' : 'black';
});
const checkPermissions = async () => {
try {
const stream = await navigator.mediaDevices.getUserMedia({ video: true, audio: true });
if (stream) {
isCameraPermissionGranted.value = true;
isMicrophonePermissionGranted.value = true;
enableCamBtn.value = false
await nextTick();
videoElement.value.srcObject = stream;
mediaStream.value = stream;
}
} catch (error) {
isCameraDisabled.value = true;
enableCamBtn.value = false
getIsTonalSnackbarVisible.value = true;
errorMessage.value = "We can't access your camera and microphone.";
console.error('Error accessing media devices:', error);
}
};
onMounted(async () => {
window.addEventListener('resize', checkMobile);
await store.dispatch('getAgentProfile');
profileData.value = store.getters.getAgentProfile.ai_switch;
profileName.value = profileData.value.name;
console.log("profileData", profileData.value);
// await checkPermissions();
});
onUnmounted(() => {
if (mediaStream.value) {
const tracks = mediaStream.value.getTracks();
tracks.forEach((track) => track.stop());
mediaStream.value = null;
}
});
const toggleCamera = () => {
if (!isCameraPermissionGranted.value) {
getIsTonalSnackbarVisible.value = true;
errorMessage.value = "We can't access your camera and microphone.";
} else {
isCameraDisabled.value = !isCameraDisabled.value;
// Stop the stream if the camera is being turned off
if (isCameraDisabled.value && mediaStream.value) {
const tracks = mediaStream.value.getTracks();
tracks.forEach((track) => track.stop());
mediaStream.value = null;
videoElement.value.srcObject = null;
}
// Start the stream if the camera is being turned on
if (!isCameraDisabled.value) {
checkPermissions();
}
}
};
const toggleMic = () => {
if (!isMicrophonePermissionGranted.value) {
getIsTonalSnackbarVisible.value = true;
errorMessage.value = "We can't access your camera and microphone.";
} else {
isMuted.value = !isMuted.value;
}
};
</script>
<template>
<VDialog v-model="isLoadingVisible" width="110" height="150" color="primary">
<VCardText class="" style="color: white !important;">
<div class="demo-space-x">
<VProgressCircular :size="40" color="primary" indeterminate />
</div>
</VCardText>
</VDialog>
<VSnackbar v-model="getIsTonalSnackbarVisible" :timeout="5000" location="top end" variant="flat" color="red">
{{ errorMessage }}
</VSnackbar>
<VRow>
<VCol cols="12" md="12">
<PatientQueueMobile v-if="isMobile" />
</VCol>
<VCol cols="12" md="9">
<EcommerceStatistics class="h-100" />
</VCol>
<VCol cols="12" md="3">
<VCard class="auth-card rounded-5 bg-black" :class="isCameraDisabled || enableCamBtn ? 'pa-2' : ''">
<div class="align-items-center justify-content-center" style="min-height:200px">
<!-- <v-menu v-if="isCameraPermissionGranted && isMicrophonePermissionGranted">
<template v-slot:activator="{ props }">
<v-btn icon="mdi-dots-horizontal" v-bind="props" class="menu-btn" size="small"
rounded="xl"></v-btn>
</template>
<v-list>
<v-list-item @click="toggleCamera()">
<v-list-item-title>
<VIcon :color="camIconColor" :icon="camIcon"></VIcon> {{ camText }}
</v-list-item-title>
</v-list-item>
<v-list-item @click="toggleMic()">
<v-list-item-title>
<VIcon :color="micIconColor" :icon="micIcon"></VIcon> {{ micText }}
</v-list-item-title>
</v-list-item>
</v-list>
</v-menu> -->
<div v-if="isCameraPermissionGranted && isMicrophonePermissionGranted" class="video-icons">
<v-btn @click="toggleCamera()" :class="isCameraDisabled ? 'video-icon-red' : 'video-icon-black'"
:color="camIconColor" :icon="camIcon" size="small" rounded="xl"></v-btn>
<v-btn @click="toggleMic()" class="ml-1"
:class="isMuted ? 'video-icon-red' : 'video-icon-black'" :color="micIconColor"
:icon="micIcon" size="small" rounded="xl"></v-btn>
</div>
<div class="text-center" :class="isCameraDisabled ? 'video-single-icon mb-1 pb-0 px-2' : ''">
<VImg v-if="isCameraDisabled" :src="videoCameraOff" height="50" width="55"
class="video-camera mb-1 mt-3" />
<div v-if="isCameraPermissionGranted">
<!-- <video :ref="!isCameraDisabled ? 'videoElement' : ''" autoplay
:style="{ display: !isCameraDisabled ? 'block' : 'none' }" :muted="isMuted"></video> -->
<video ref="videoElement" autoplay
:style="{ display: !isCameraDisabled ? 'block' : 'none' }" :muted="isMuted"></video>
</div>
<div v-if="!enableCamBtn">
<small v-if="!isCameraPermissionGranted && !isMicrophonePermissionGranted">Allow access to
your cam/mic by
clicking on blocked cam icon in address bar.</small>
</div>
<div v-if="enableCamBtn" class="text-center"
:class="enableCamBtn ? 'video-single-icon mb-1 pb-0 px-2' : ''">
<VImg v-if="enableCamBtn" :src="videoCameraOff" height="50" width="55"
class="video-camera mb-1 mt-3" />
<small>Your webcam isn't enabled yet.</small>
</div>
</div>
</div>
<v-btn v-if="enableCamBtn" class=" d-grid w-100 waves-effect waves-light text-capitalize" color="error"
variant="flat" @click="checkPermissions()">
Enable Camera
</v-btn>
</VCard>
<!-- <v-btn class="btn btn-primary mt-2 d-grid w-100 waves-effect waves-light text-capitalize" color="primary"
variant="flat">
Pre-call Test
</v-btn> -->
</VCol>
<VCol cols="12" md="9">
<MeetingsDashboard></MeetingsDashboard>
</VCol>
</VRow>
</template>
<style>
.video-single-icon {
margin: 0 auto;
position: absolute;
top: calc(35% - 10px);
width: 100%;
}
.menu-btn {
position: absolute;
background-color: #0000009c !important;
left: 10px;
z-index: 999;
top: 5px;
}
.video-icons {
position: absolute;
right: 10px;
z-index: 9999;
top: 5px;
}
.video-icon-red {
background-color: red;
color: white !important;
border-radius: 12px;
}
.video-icon-black {
background-color: #0000009c !important;
color: white !important;
border-radius: 12px;
}
.video-camera {
display: block;
position: relative;
margin: 0 auto;
}
video {
width: 100%;
height: auto;
}
</style>

View File

@@ -0,0 +1,521 @@
<script setup>
import avatar1 from "@images/avatars/avatar-1.png";
import moment from 'moment-timezone';
import { computed, onMounted, ref } from "vue";
import { useStore } from "vuex";
const route = useRoute();
const isConfirmDialogVisible = ref(false);
const isUserInfoEditDialogVisible = ref(false);
const isEditAddressDialogVisible = ref(false);
// const scheduleDate = ref('');
// const scheduleTime = ref('');
const patientId = route.params.patient_id;
const appointmentId = route.params.id;
const headers = [
{
title: "Product",
key: "title",
},
{
title: "Price",
key: "price",
},
{
title: "Quantity",
key: "quantity",
},
{
title: "status",
key: "status",
},
{
title: "Total",
key: "total",
sortable: false,
},
];
const store = useStore();
const orderData = ref(null);
const pateintDetail = ref({});
const productItems = ref([]);
const appointmentData = ref([]);
const orderId = ref();
const orderDetail = ref();
const scheduleDate = ref();
const scheduleTime = ref();
const telemed_pro = ref();
const subTotalAmount = ref();
const filteredOrders = computed(() => {
let filtered = store.getters.getSinglePatientAppointment;
// filtered.order_items.forEach(product => {
// const price = parseFloat(product.total_price);
// const shippingPrice = parseFloat(product.shipping_cost);
// totalShipping.value += price * shippingPrice;
// totalAmount.value += product.qty * price;
// });
return filtered;
});
const formatDateActviy1 = (date) => {
const messageDate = new Date(date);
const dayFormatter = new Intl.DateTimeFormat('en-US', { weekday: 'long' });
const timeFormatter = new Intl.DateTimeFormat('en-US', {
hour: 'numeric',
minute: '2-digit',
hour12: true
});
return `${dayFormatter.format(messageDate)} ${timeFormatter.format(messageDate)}`;
};
const formatDateActviy = (date) => {
const messageDate = new Date(date);
const options = {
year: 'numeric',
month: 'long',
day: 'numeric',
hour: '2-digit',
minute: '2-digit'
};
return messageDate.toLocaleDateString('en-US', options).replace(/\//g, '-');
};
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, "-");
return `${formattedDate}`;
};
const convertUtcDateTimeToLocal = (utcDate, utcTime, type) => {
const utcDateTime = `${utcDate}T${utcTime}Z`; // Use Z to denote UTC timezone explicitly
const momentObj = moment.utc(utcDateTime).local(); // Convert UTC to local time
if (type === 'date') {
return momentObj.format('YYYY-MM-DD'); // Return local date
} else if (type === 'time') {
return momentObj.format('HH:mm:ss'); // Return local time
} else {
throw new Error("Invalid type specified. Use 'date' or 'time'.");
}
};
function changeDateFormat(dateFormat) {
console.log("startTimeFormat", dateFormat);
if (dateFormat) {
const [datePart, timePart] = dateFormat.split(" "); // Split date and time parts
const [year, month, day] = datePart.split("-"); // Split date into year, month, and day
const formattedMonth = parseInt(month).toString(); // Convert month to integer and then string to remove leading zeros
const formattedDay = parseInt(day).toString(); // Convert day to integer and then string to remove leading zeros
const formattedDate = `${formattedMonth}-${formattedDay}-${year}`; // Format date as mm-dd-yyyy
return `${formattedDate} ${timePart}`; // Combine formatted date with original time part
}
}
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
}
onMounted(async () => {
store.dispatch("updateIsLoading", true);
// await store.dispatch("orderDetailPatient", {
// id: route.params.id,
// });
// orderData.value = store.getters.getPatientOrderDetail;
// console.log(orderData.value);
await store.dispatch('getAppointmentByIdAgent', {
patient_id: patientId,
appointment_id: appointmentId,
})
orderData.value = store.getters.getSinglePatientAppointment;
orderDetail.value = store.getters.getSinglePatientAppointment.order;
console.log("appointmentData", orderData.value);
orderId.value = orderData.value.order.id;
let appointmentDate = convertUtcDateTimeToLocal(orderData.value.appointment_date, orderData.value.appointment_time, 'date')
let appointmentTime = convertUtcDateTimeToLocal(orderData.value.appointment_date, orderData.value.appointment_time, 'time')
scheduleDate.value = moment(appointmentDate, "YYYY-MM-DD").format("MMMM DD, YYYY")
scheduleTime.value = moment(appointmentTime, "HH:mm:ss").format("hh:mm A");
});
const getStatusColor = (status) => {
switch (status) {
case "pending":
return "orange";
case "Shipped":
return "blue";
case "Delivered":
return "green";
case "Cancelled":
return "red";
default:
return "gray";
}
};
</script>
<template>
<VDialog v-model="store.getters.getIsLoading" width="110" height="150" color="primary">
<VCardText class="" style="color: white !important">
<div class="demo-space-x">
<VProgressCircular :size="40" color="primary" indeterminate />
</div>
</VCardText>
</VDialog>
<div>
<div class="d-flex justify-space-between align-center flex-wrap gap-y-4 mb-6">
<div>
<div class="d-flex gap-2 align-center mb-2 flex-wrap">
<h5 class="text-h5">Order #{{ orderId }}</h5>
<div class="d-flex gap-x-2">
<!-- <VChip variant="tonal" color="success" size="small">
Paid
</VChip>
<VChip variant="tonal" color="info" size="small">
Ready to Pickup
</VChip> -->
</div>
</div>
<div>
<span class="text-body-1"> </span>
</div>
</div>
</div>
<VRow v-if="filteredOrders">
<VCol cols="12" md="8">
<!-- 👉 Order Details -->
<VCard class="mb-6">
<VCardItem>
<template #title>
<h5>Order Details</h5>
</template>
</VCardItem>
<div class="table-container">
<VDataTable :headers="headers" :items="filteredOrders.order_items" item-value="id"
class="text-no-wrap ">
<template #item.title="{ item }">
<div class="d-flex gap-x-3">
<!-- <VAvatar size="34" variant="tonal" :image="item.image_url" rounded /> -->
<div class="d-flex flex-column align-center">
<h6 style="margin-bottom: 0px;">
{{ item.plans_v1.title }}
</h6>
<span class="text-sm text-start align-self-start">
{{ item.plans_v1.list_two_title }}
</span>
</div>
</div>
</template>
<template #item.price="{ item }">
<span>${{ item.plans_v1.price }}</span>
</template>
<template #item.status="{ item }">
<span>
<VChip variant="tonal" :color="getStatusColor(item.status)" size="small">
{{ item.status }}
</VChip>
</span>
</template>
<template #item.total="{ item }">
<span> ${{ item.plans_v1.price * item.plans_v1.qty }} </span>
</template>
<template #bottom />
</VDataTable>
</div>
<VDivider />
<VCardText>
<div class="d-flex align-end flex-column">
<table class="text-high-emphasis">
<tbody>
<tr>
<td width="200px">Subtotal:</td>
<td class="font-weight-medium">
${{
parseFloat(
filteredOrders.order
.order_total_amount
).toFixed(2)
}}
</td>
</tr>
<tr>
<td>Shipping fee:</td>
<td class="font-weight-medium">
${{ parseFloat(filteredOrders.order.order_total_shipping).toFixed(2)
}}
</td>
</tr>
<tr>
<td class="font-weight-medium">
Total:
</td>
<td class="font-weight-medium">
${{
parseFloat(
filteredOrders.order
.order_total_amount + filteredOrders.order.order_total_shipping
).toFixed(2)
}}
</td>
</tr>
</tbody>
</table>
</div>
</VCardText>
</VCard>
<!-- 👉 Shipping Activity -->
<VCard title="Shipping Activity">
<VCardText>
<VTimeline truncate-line="both" align="start" side="end" line-inset="10" line-color="primary"
density="compact" class="v-timeline-density-compact">
<VTimelineItem dot-color="primary" size="x-small"
v-for="item in filteredOrders.shipping_activity" :key="item.id">
<div class="d-flex justify-space-between align-center mb-3">
<span class="app-timeline-title"> {{ item.note }}</span>
<span class="app-timeline-meta">{{ formatDateActviy(item.created_at) }}</span>
</div>
<p class="app-timeline-text mb-0">
{{ item.short_description }}
</p>
</VTimelineItem>
</VTimeline>
</VCardText>
</VCard>
</VCol>
<VCol cols="12" md="4">
<VCard class="mb-6">
<VCardText>
<div class="d-flex align-center justify-space-between gap-1 mb-6">
<div class="text-body-1 text-high-emphasis font-weight-medium">
<v-icon class="mr-2" color="rgb(var(--v-theme-yellow))">mdi-calendar-clock</v-icon>
Appointment Details
</div>
</div>
<div class="appointment-details">
<div class="detail-item">
<span class="detail-label">Appointment At:</span>
<span class="detail-value">{{ scheduleDate + ' ' + scheduleTime }}</span>
</div>
<div class="detail-item" v-if="filteredOrders.start_time && filteredOrders.end_time">
<span class="detail-label">Start Time:</span>
<span class="detail-value">{{
formatDate(filteredOrders.start_time)
}}</span>
</div>
<div class="detail-item" v-if="filteredOrders.start_time && filteredOrders.end_time">
<span class="detail-label">End Time:</span>
<span class="detail-value">{{
formatDate(filteredOrders.end_time)
}}</span>
</div>
<div class="detail-item" v-if="filteredOrders.start_time && filteredOrders.end_time">
<span class="detail-label">Duration:</span>
<span class="detail-value">{{
totalCallDuration(filteredOrders.start_time,
filteredOrders.end_time) }}</span>
</div>
</div>
</VCardText>
</VCard>
<!-- 👉 Customer Details -->
<VCard class="mb-6" v-if="filteredOrders.telemed_pro.name">
<VCardText class="d-flex flex-column gap-y-6">
<h3>Provider Details</h3>
<div class="d-flex align-center">
<VAvatar :image="avatar1" class="me-3" style="display: none;" />
<VAvatar color="primary" class="me-3" size="30">
<VIcon icon="mdi-account" size="25" color="white" />
</VAvatar>
<div>
<div class="text-body-1 text-high-emphasis font-weight-medium">
{{ filteredOrders.telemed_pro.name }}
</div>
</div>
</div>
<div class="d-flex align-center" style="display: none;">
<VAvatar variant="tonal" color="success" class="me-3" style="display: none;">
<VIcon icon="ri-shopping-cart-line" />
</VAvatar>
<!-- <h4 style="display: none;">
{{ filteredOrders.order_items.total_products }}
Products
</h4> -->
</div>
<div class="d-flex flex-column gap-y-1">
<div class="d-flex justify-space-between gap-1 text-body-2">
<h5>Contact Info</h5>
</div>
<span>Email:
{{ filteredOrders.telemed_pro.email }}</span>
<span>Mobile:
{{ filteredOrders.telemed_pro.phone_number }}</span>
</div>
</VCardText>
</VCard>
<VCard class="mb-6">
<VCardText>
<div class="d-flex align-center justify-space-between gap-1 mb-6">
<div class="text-body-1 text-high-emphasis font-weight-medium">
<v-icon class="mr-2" color="rgb(var(--v-theme-yellow))">mdi-truck-delivery</v-icon>
Shipping Address
</div>
<!-- <span
class="text-base text-primary font-weight-medium cursor-pointer"
@click="
isEditAddressDialogVisible =
!isEditAddressDialogVisible
"
>Edit</span
> -->
</div>
<div>
{{ filteredOrders.order.shipping_address1 }}
<br />
{{ filteredOrders.order.shipping_city }}
<br />
{{ filteredOrders.order.shipping_state }},
{{ filteredOrders.order.shipping_zipcode }}
<br />
{{ filteredOrders.order.shipping_country }}
</div>
</VCardText>
</VCard>
<!-- 👉 Billing Address -->
<VCard style="display: none;">
<VCardText>
<div class="d-flex align-center justify-space-between gap-1 mb-3">
<div class="text-body-1 text-high-emphasis font-weight-medium">
Billing Address
</div>
<!-- <span
class="text-base text-primary font-weight-medium cursor-pointer"
@click="
isEditAddressDialogVisible =
!isEditAddressDialogVisible
"
>Edit</span
> -->
</div>
<div>
{{ filteredOrders.order.billing_address1 }}
<br />
{{ filteredOrders.order.billing_city }}
<br />
{{ filteredOrders.order.billing_state }},
{{ filteredOrders.order.billing_zipcode }}
<br />
{{ filteredOrders.order.billing_country }}
</div>
<!-- <div class="mt-6">
<h6 class="text-h6 mb-1">Mastercard</h6>
<div class="text-base">Card Number: ******4291</div>
</div> -->
</VCardText>
</VCard>
</VCol>
</VRow>
</div>
</template>
<style scoped>
.appointment-details {
display: flex;
flex-direction: column;
}
.detail-item {
display: flex;
margin-bottom: 10px;
}
.detail-label {
font-weight: bold;
min-width: 120px;
}
.detail-value {
flex: 1;
}
::-webkit-scrollbar {
width: 10px;
/* Width of the scrollbar */
}
/* Track */
::-webkit-scrollbar-track {
background: #f1f1f1;
/* Color of the track */
}
/* Handle */
::-webkit-scrollbar-thumb {
background: #888;
/* Color of the handle */
border-radius: 5px;
/* Roundness of the handle */
}
/* Handle on hover */
::-webkit-scrollbar-thumb:hover {
background: #555;
/* Color of the handle on hover */
}
</style>

View File

@@ -0,0 +1,85 @@
<script setup>
import { computed, onMounted, ref } from "vue";
import Notes from "@/pages/provider/OrderDetailNotes.vue";
import Prescription from "@/pages/provider/OrderDetailPrecrption.vue";
import OrderDetail from "@/pages/provider/orders-detail.vue";
const route = useRoute();
const isConfirmDialogVisible = ref(false);
const isUserInfoEditDialogVisible = ref(false);
const isEditAddressDialogVisible = ref(false);
const currentTab = ref("tab-1");
import { useStore } from "vuex";
const store = useStore();
const orderData = ref(null);
const pateintDetail = ref({});
const productItems = ref([]);
const userTab = ref(null);
const tabs = [
{
icon: "mdi-clipboard-text-outline",
title: "Order Detail",
},
{
icon: "mdi-note-text-outline",
title: "Notes",
},
{
icon: "mdi-prescription",
title: "Prescriptions",
},
];
const filteredOrders = computed(() => {
let filtered = store.getters.getPatientOrderDetail;
return filtered;
});
onMounted(async () => {
store.dispatch("updateIsLoading", true);
await store.dispatch("orderDetailAgent", {
id: route.params.id,
});
orderData.value = store.getters.getPatientOrderDetail;
console.log(orderData.value);
});
</script>
<template>
<VTabs v-model="userTab" grow>
<VTab v-for="tab in tabs" :key="tab.icon">
<VIcon start :icon="tab.icon" />
<span>{{ tab.title }}</span>
</VTab>
</VTabs>
<VWindow
v-model="userTab"
class="mt-6 disable-tab-transition"
:touch="false"
>
<VWindowItem>
<OrderDetail />
</VWindowItem>
<VWindowItem>
<Notes :order-data="orderData" />
</VWindowItem>
<VWindowItem>
<Prescription :order-data="orderData" />
</VWindowItem>
</VWindow>
</template>
<style scoped>
.text-primary {
color: rgb(var(--v-theme-yellow)) !important;
}
.text-primary span {
color: rgb(var(--v-theme-yellow-theme-button)) !important;
}
.text-primary svg {
color: rgb(var(--v-theme-yellow-theme-button)) !important;
}
</style>

View File

@@ -0,0 +1,235 @@
<script setup>
import store from '@/store';
import moment from 'moment';
import { ref } from 'vue';
import { useRoute, useRouter } from 'vue-router';
const router = useRouter()
const route = useRoute()
const getHistory = ref([]);
const doctorAppiontments = ref([]);
const selectedFilter = ref(null);
const currentMonth = ref('');
onMounted(async () => {
currentMonth.value = moment().format('MMMM') + '(Month to date)';
await store.dispatch('getHistory')
getHistory.value = store.getters.getHistory.history;
console.log("getHistory", getHistory.value);
store.dispatch('updateIsLoading', false)
})
const filter = [
{
value: 'This Month',
key: 'current_month'
},
{
value: 'Past Month',
key: '1_month'
},
{
value: 'Past 2 Month from today',
key: '2_months'
},
{
value: 'Past 3 Month from today',
key: '3_months'
},
{
value: 'Past 6 Month from today',
key: '6_months'
},
{
value: 'Past 12 Month from today',
key: '12_months'
},
{
value: 'All Time',
key: 'all_time'
},
];
const patient_name = ref();
const patient_email = ref();
const handleDateInput = async () => {
getHistory.value = [];
await store.dispatch('getHistoryFilter', {
filter: selectedFilter.value,
})
getHistory.value = store.getters.getHistoryFilter.patients;
// patient_name.value = store.getters.getHistoryFilter.patients.patient;
// patient_email.value = store.getters.getHistoryFilter.patients.patient;
console.log("getHistoryFilter", getHistory.value);
store.dispatch('updateIsLoading', false)
}
const search = ref('');
const headers = [
{ align: 'start', key: 'order_id', title: 'Order' },
{ align: 'start', key: 'id', title: 'Appiontment' },
{ key: 'appointment_date', sortable: false, title: 'Date' },
{ key: 'patient_name', title: 'Patient' },
// { key: 'start_time', title: 'Start Time' },
// { key: 'end_time', title: 'End Time' },
// { key: 'duration', title: 'Duration' },
{ key: 'action', title: 'Action' },
];
const formattedHistory = computed(() => {
return getHistory.value.map(history => ({
...history,
appointment_date: changeFormat(history.appointment_date),
start_time: changeDateFormat(history.start_time),
end_time: changeDateFormat(history.end_time),
duration: totalCallDuration(history.start_time, history.end_time),
}));
});
function changeDateFormat(dateFormat) {
console.log("startTimeFormat", dateFormat);
if (dateFormat) {
const [datePart, timePart] = dateFormat.split(' '); // Split date and time parts
const [year, month, day] = datePart.split('-'); // Split date into year, month, and day
const formattedMonth = parseInt(month).toString(); // Convert month to integer and then string to remove leading zeros
const formattedDay = parseInt(day).toString(); // Convert day to integer and then string to remove leading zeros
const formattedDate = `${formattedMonth}-${formattedDay}-${year}`; // Format date as mm-dd-yyyy
return `${formattedDate} ${timePart}`; // Combine formatted date with original time part
}
}
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 changeFormat(dateFormat) {
// if (dateFormat) {
// const dateParts = dateFormat.split('-'); // Assuming date is in yyyy-mm-dd format
// const year = parseInt(dateParts[0]);
// const month = String(dateParts[1]).padStart(2, '0'); // Pad single-digit months with leading zero
// const day = String(dateParts[2]).padStart(2, '0'); // Pad single-digit days with leading zero
// // 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 historyDetail = (item) => {
console.log('item', item)
router.push('/provider/order-detail/' + item.order_id);
}
</script>
<template>
<VRow>
<VDialog v-model="store.getters.getIsLoading" width="110" height="150" color="primary">
<VCardText class="" style="color: white !important;">
<div class="demo-space-x">
<VProgressCircular :size="40" color="primary" indeterminate />
</div>
</VCardText>
</VDialog>
<VCol cols="12">
<VCard title="Meeting History">
<v-card flat>
<v-card-title class="d-flex align-center pe-2">
<!--<v-select label="Search" v-model="selectedFilter" :items="filter" item-title="value"
item-value="key" @update:modelValue="handleDateInput" density="compact"></v-select>
<v-text-field v-model="search" prepend-inner-icon="mdi-magnify" density="compact" label="Search"
single-line flat hide-details variant="solo-filled"></v-text-field> -->
<v-spacer></v-spacer>
<v-spacer></v-spacer>
<v-spacer></v-spacer>
</v-card-title>
<v-data-table :headers="headers" :items="formattedHistory">
<template #item.order_id="{ item }">
<RouterLink :to="{ name: 'provider-order-detail', params: { id: item.order_id } }">
#{{ item.order_id }}
</RouterLink>
</template>
<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-order-detail', params: { id: item.id } }">
<div class=" font-weight-medium">
{{ item.patient_name }}
</div>
</RouterLink>
<small>{{ item.patient_email }}</small>
</div>
</div>
</template>
<template #item.action="{ item }">
<IconBtn size="small">
<VIcon icon="ri-more-2-line" />
<VMenu activator="parent">
<VList>
<VListItem value="view">
<RouterLink
:to="{ name: 'provider-order-detail', params: { id: item.order_id } }"
class="text-high-emphasis">
View
</RouterLink>
</VListItem>
</VList>
</VMenu>
</IconBtn>
</template>
</v-data-table>
</v-card>
</VCard>
</VCol>
</VRow>
</template>

View File

@@ -0,0 +1,95 @@
<script setup>
import store from '@/store';
import { useRoute, useRouter } from 'vue-router';
const router = useRouter()
const route = useRoute()
const patientId = route.params.patient_id;
const appointmentId = route.params.id;
const notes = ref([]);
const historyNotes = computed(async () => {
store.dispatch('updateIsLoading', true)
// console.log('...........', patientId, appointmentId);
await store.dispatch('getHistoryPatientNotes', {
patient_id: patientId,
appointment_id: appointmentId,
})
// notes.value = store.getters.getPatientNotes;
let notesData = store.getters.getPatientNotes;
for (let data of notesData) {
if (data.note_type == 'Notes') {
let dataObject = {}
dataObject.file_url = data.file_url
dataObject.note = data.note
dataObject.doctor = data.telemedPro.name
dataObject.date = formatDateDate(data.created_at)
dataObject.id = data.id
notes.value.push(dataObject)
}
}
notes.value.sort((a, b) => {
return b.id - a.id;
});
// console.log("getHistoryNotes", notes.value);
store.dispatch('updateIsLoading', false)
});
const formatDateDate = (date) => {
const messageDate = new Date(date);
const options = {
year: 'numeric',
month: 'long',
day: 'numeric',
hour: '2-digit',
minute: '2-digit'
};
return messageDate.toLocaleDateString('en-US', options).replace(/\//g, '-');
};
const downloadFile = (fileUrl) => {
const link = document.createElement('a');
link.href = fileUrl;
// Optional: Provide a filename; defaults to the last segment of the path if omitted
link.download = 'noteFile.png';
// Append link to the body, click it, and then remove it
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
};
</script>
<template>
<VDialog v-model="store.getters.getIsLoading" width="110" height="150" color="primary">
<VCardText class="" style="color: white !important;">
<div class="demo-space-x">
<VProgressCircular :size="40" color="primary" indeterminate />
</div>
</VCardText>
</VDialog>
<VList class="pb-0" lines="two" v-if="historyNotes">
<template v-if="notes.length > 0" v-for="(p_note, index) of notes" :key="index">
<VListItem class="pb-0" border>
<VListItemTitle>
<span class="pb-0">{{ p_note.note }}</span>
<span v-if="p_note.file_url" style="font-size: 12px;float: right;">
<a type="button" @click="downloadFile(p_note.file_url)">
<VIcon>mdi-file-image</VIcon>
</a>
</span>
<p class="text-start fs-5 mb-0 pb-0 text-grey">
<small> {{ p_note.doctor }}</small>
</p>
<p class="text-end fs-5 mb-0 pb-0 text-grey"><small> {{ p_note.date }}</small></p>
</VListItemTitle>
</VListItem>
<VDivider v-if="index !== notes.length - 1" />
</template>
<template v-else>
<VCard>
<VAlert border="start" color="rgb(var(--v-theme-yellow))" variant="tonal">
<div class="text-center">No data found</div>
</VAlert>
</VCard>
</template>
</VList>
</template>

View File

@@ -0,0 +1,817 @@
<script setup>
import avatar1 from "@images/avatars/avatar-1.png";
import moment from 'moment-timezone';
import { computed, onMounted, reactive, ref } from "vue";
import { useStore } from "vuex";
const route = useRoute();
const isConfirmDialogVisible = ref(false);
const isUserInfoEditDialogVisible = ref(false);
const isEditAddressDialogVisible = ref(false);
const scheduleDate = ref('');
const scheduleTime = ref('');
const isLoading = ref(true);
const state = reactive({
addLabOrder: false,
selectedTestKitId: null,
valid: false,
testKits: [],
item_id: null,
labKitList: []
});
const getFieldRules = (fieldName, errorMessage) => {
if (fieldName) {
return [
v => !!v || `${errorMessage}`,
// Add more validation rules as needed
];
}
};
const headers = [
{
title: "Product",
key: "title",
},
{
title: "Price",
key: "price",
},
{
title: "Quantity",
key: "quantity",
},
{
title: "status",
key: "status",
},
{
title: "Total",
key: "total",
sortable: false,
},
];
const headersLab = [
{
title: "Product",
key: "item_name",
},
{
title: "Lab Kit",
key: "lab_kit_name",
},
{
title: "Status",
key: "status",
},
{
title: "Results",
key: "result",
},
];
const openDialog = (item) => {
state.item_id = item.id
state.addLabOrder = true
};
const openPdfInNewTab = (url) => {
window.open(url, '_blank')
}
const storeTestKit = async () => {
await store.dispatch('saveOrderLabKitBYitems', {
lab_kit_id: state.selectedTestKitId,
item_id: state.item_id,
cart_id: route.params.id
})
console.log('Selected Test Kit:', state.selectedTestKitId);
state.addLabOrder = false;
state.selectedTestKitId = null
state.item_id = null
};
const store = useStore();
const orderData = ref(null);
const pateintDetail = ref({});
const productItems = ref([]);
const filteredOrders = computed(() => {
let filtered = store.getters.getPatientOrderDetail;
return filtered;
});
const formatDateActviy1 = (date) => {
const messageDate = new Date(date);
const dayFormatter = new Intl.DateTimeFormat('en-US', { weekday: 'long' });
const timeFormatter = new Intl.DateTimeFormat('en-US', {
hour: 'numeric',
minute: '2-digit',
hour12: true
});
return `${dayFormatter.format(messageDate)} ${timeFormatter.format(messageDate)}`;
};
const formatDateActviy = (date) => {
const messageDate = new Date(date);
const options = {
year: 'numeric',
month: 'long',
day: 'numeric',
hour: '2-digit',
minute: '2-digit'
};
return messageDate.toLocaleDateString('en-US', options).replace(/\//g, '-');
};
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, "-");
return `${formattedDate}`;
};
const convertUtcDateTimeToLocal = (utcDate, utcTime, type) => {
const utcDateTime = `${utcDate}T${utcTime}Z`; // Use Z to denote UTC timezone explicitly
const momentObj = moment.utc(utcDateTime).local(); // Convert UTC to local time
if (type === 'date') {
return momentObj.format('YYYY-MM-DD'); // Return local date
} else if (type === 'time') {
return momentObj.format('HH:mm:ss'); // Return local time
} else {
throw new Error("Invalid type specified. Use 'date' or 'time'.");
}
};
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 testKits = computed(async () => {
//await store.dispatch('getLabKitProductList', {})
// console.log(store.getters.getLabOrderProductList)
//state.testKits = store.getters.getLabOrderProductList
});
onMounted(async () => {
await store.dispatch("orderDetailAgent", {
id: route.params.id,
});
orderData.value = store.getters.getPatientOrderDetail;
console.log(orderData.value);
scheduleDate.value = getConvertedDate(convertUtcTime(orderData.value.appointment_details.appointment_time, orderData.value.appointment_details.appointment_date, orderData.value.appointment_details.timezone)),
scheduleTime.value = getConvertedTime(convertUtcTime(orderData.value.appointment_details.appointment_time, orderData.value.appointment_details.appointment_date, orderData.value.appointment_details.timezone)),
// let appointmentDate = convertUtcDateTimeToLocal(orderData.value.appointment_details.appointment_date, orderData.value.appointment_details.appointment_time, 'date')
// let appointmentTime = convertUtcDateTimeToLocal(orderData.value.appointment_details.appointment_date, orderData.value.appointment_details.appointment_time, 'time')
// scheduleDate.value = moment(appointmentDate, "YYYY-MM-DD").format("MMMM DD, YYYY")
// scheduleTime.value = moment(appointmentTime, "HH:mm:ss").format("hh:mm A");
await store.dispatch('getOrderLabKit', { cart_id: route.params.id })
state.labKitList = store.getters.getOrderLabKit
console.log('state.testKits', state.labKitList)
isLoading.value = false
});
const convertUtcTime = (time, date, timezone) => {
const timezones = {
"EST": "America/New_York",
"CST": "America/Chicago",
"MST": "America/Denver",
"PST": "America/Los_Angeles",
// Add more mappings as needed
};
// Get the IANA timezone identifier from the abbreviation
const ianaTimeZone = timezones[timezone];
if (!ianaTimeZone) {
throw new Error(`Unknown timezone abbreviation: ${timezone}`);
}
// Combine date and time into a single string
const dateTimeString = `${date}T${time}Z`; // Assuming the input date and time are in UTC
// Create a Date object from the combined string
const dateObj = new Date(dateTimeString);
// Options for the formatter
const options = {
timeZone: ianaTimeZone,
year: 'numeric',
month: '2-digit',
day: '2-digit',
hour: '2-digit',
minute: '2-digit',
second: '2-digit',
hour12: false,
};
// Create the formatter
const formatter = new Intl.DateTimeFormat('en-US', options);
// Format the date
const convertedDateTime = formatter.format(dateObj);
return convertedDateTime;
};
const getConvertedTime = (inputDate) => {
// Split the input date string into date and time components
const [datePart, timePart] = inputDate.split(', ');
// Split the time component into hours, minutes, and seconds
let [hours, minutes, seconds] = timePart.split(':');
// Convert the hours to an integer
hours = parseInt(hours);
// Determine the period (AM/PM) and adjust the hours if necessary
const period = hours >= 12 ? 'PM' : 'AM';
hours = hours % 12 || 12; // Convert 0 and 12 to 12, and other hours to 1-11
// Format the time as desired
const formattedTime = `${hours.toString().padStart(2, '0')}:${minutes}${period}`;
return formattedTime;
}
const getConvertedDate = (inputDate) => {
// Split the input date string into date and time components
const [datePart, timePart] = inputDate.split(', ');
// Split the date component into month, day, and year
const [month, day, year] = datePart.split('/');
// Create a new Date object from the parsed components
const dateObject = new Date(`${year}-${month}-${day}T${timePart}`);
// Define an array of month names
const monthNames = [
"January", "February", "March", "April", "May", "June",
"July", "August", "September", "October", "November", "December"
];
// Format the date as desired
const formattedDate = `${monthNames[dateObject.getMonth()]} ${day}, ${year}`;
return formattedDate;
};
const getStatusColor = (status) => {
switch (status) {
case "pending":
return "orange";
case "Shipped":
return "blue";
case "Delivered":
return "green";
case "Cancelled":
return "red";
default:
return "gray";
}
};
const getStatusColorLabKit = (status) => {
switch (status) {
case "Ordered":
return "orange";
case "Shipped":
return "blue";
case "Delivered":
return "green";
case "Cancelled":
return "red";
case "Waiting For Results":
return "red";
default:
return "gray";
}
};
const formatCurrency = (amount) => {
let formattedAmount = amount.toString();
// Remove '.00' if present
if (formattedAmount.includes('.00')) {
formattedAmount = formattedAmount.replace('.00', '');
}
// Split into parts for integer and decimal
let parts = formattedAmount.split('.');
// Format integer part with commas
parts[0] = parts[0].replace(/\B(?=(\d{3})+(?!\d))/g, ',');
// Return formatted number
return parts.join('.');
}
const formatTotalCurrency = (amount) => {
let formattedAmount = amount.toString();
// Remove '.00' if present
// if (formattedAmount.includes('.00')) {
// formattedAmount = formattedAmount.replace('.00', '');
// }
// Split into parts for integer and decimal
let parts = formattedAmount.split('.');
// Format integer part with commas
parts[0] = parts[0].replace(/\B(?=(\d{3})+(?!\d))/g, ',');
// Return formatted number
return parts.join('.');
}
</script>
<template>
<VDialog v-model="store.getters.getIsLoading" width="110" height="150" color="primary">
<VCardText class="" style="color: white !important">
<div class="demo-space-x">
<VProgressCircular :size="40" color="primary" indeterminate />
</div>
</VCardText>
</VDialog>
<div>
<div class="d-flex justify-space-between align-center flex-wrap gap-y-4 mb-6">
<div>
<div class="d-flex gap-2 align-center mb-2 flex-wrap">
<h5 class="text-h5">Order #{{ route.params.id }}</h5>
<div class="d-flex gap-x-2">
<!-- <VChip variant="tonal" color="success" size="small">
Paid
</VChip>
<VChip variant="tonal" color="info" size="small">
Ready to Pickup
</VChip> -->
</div>
</div>
<div>
<span class="text-body-1"> </span>
</div>
</div>
</div>
<VRow v-if="filteredOrders">
<VCol cols="12" md="8">
<!-- 👉 Order Details -->
<VCard class="mb-6">
<VCardItem>
<template #title>
<h5>Order Details</h5>
</template>
</VCardItem>
<div class="table-container">
<VDataTable :headers="headers" :items="filteredOrders.order_items.items"
item-value="productName" class="text-no-wrap ">
<template #item.title="{ item }">
<div class="d-flex gap-x-3">
<VAvatar size="34" variant="tonal" :image="item.image_url" rounded />
<div class="d-flex flex-column text-left">
<h5 style="margin-bottom: 0px; font-size: 0.83em;">
{{ item.title }}
</h5>
<span class="text-sm text-start align-self-start">
{{ item.list_sub_title }}
</span>
</div>
</div>
</template>
<template #item.price="{ item }">
<span>${{ item.price }}</span>
</template>
<!-- <template #item.lab_kit_order="{ item }">
<span>
<v-btn color="primary" size="small" @click.stop="openDialog(item)" class="mr-2">
<v-icon>mdi-plus</v-icon>Lab Order
</v-btn>
</span>
</template>-->
<template #item.status="{ item }">
<span>
<VChip variant="tonal" :color="getStatusColor(item.status)" size="small">
{{ item.status }}
</VChip>
</span>
</template>
<template #item.total="{ item }">
<span> ${{ parseFloat(item.price * item.quantity).toLocaleString('en-US', {
minimumFractionDigits: 2,
maximumFractionDigits: 2
}) }} </span>
</template>
<template #bottom />
</VDataTable>
</div>
<VDivider />
<VCardText>
<div class="d-flex align-end flex-column">
<table class="text-high-emphasis">
<tbody>
<tr>
<td width="200px">Subtotal:</td>
<td class="font-weight-medium">
${{
formatTotalCurrency(parseFloat(
filteredOrders.order_items
.total_amount
).toFixed(2))
}}
</td>
</tr>
<tr>
<td>Shipping fee:</td>
<td class="font-weight-medium">
${{
parseFloat(
filteredOrders.order_items
.total_shipping_cost
).toFixed(2)
}}
</td>
</tr>
<tr>
<td class="font-weight-medium">
Total:
</td>
<td class="font-weight-medium">
${{
formatTotalCurrency(parseFloat(
filteredOrders.order_items
.total
).toFixed(2))
}}
</td>
</tr>
</tbody>
</table>
</div>
</VCardText>
</VCard>
<v-dialog v-model="state.addLabOrder" max-width="400">
<v-card>
<v-card-title>Add Lab Kit</v-card-title>
<v-card-text>
<v-form ref="form" v-model="state.valid" class="mt-1">
<v-row v-if="testKits">
<v-col cols="12" md="12">
<v-autocomplete label="Test Kit" v-model="state.selectedTestKitId"
style="column-gap: 0px;" :items="state.testKits" item-title="name"
item-value="id"
:rules="getFieldRules('Test Kit', 'Test Kit is required')"></v-autocomplete>
</v-col>
</v-row>
</v-form>
</v-card-text>
<v-card-actions>
<v-spacer></v-spacer>
<v-btn color="primary" text @click="state.addLabOrder = false">Cancel</v-btn>
<v-btn color="primary" @click="storeTestKit" :disabled="!state.valid">Save</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
<!-- 👉 Shipping Activity -->
<VCard title="Shipping Activity" class="mb-6" style="display: none;">
<VCardText>
<VTimeline truncate-line="both" align="start" side="end" line-inset="10" line-color="primary"
density="compact" class="v-timeline-density-compact">
<VTimelineItem dot-color="yellow" size="x-small"
v-for="item in filteredOrders.items_activity" :key="item.id">
<div class="d-flex justify-space-between align-center mb-3">
<span class="app-timeline-title"> {{ item.note }}</span>
<span class="app-timeline-meta">{{ formatDateActviy(item.created_at) }}</span>
</div>
<p class="app-timeline-text mb-0">
{{ item.item_name }} {{ item.short_description }}
</p>
</VTimelineItem>
</VTimeline>
</VCardText>
</VCard>
<VCard title="Lab Kits" v-if="state.labKitList.length > 0">
<VCardText>
<div class="table-container">
<VDataTable :headers="headersLab" :loading="isLoading" :items="state.labKitList"
class="text-no-wrap ">
<template #item.item_name="{ item }">
<div class="d-flex gap-x-3">
<div class="d-flex flex-column align-center">
<h5 style="margin-bottom: 0px;">
{{ item.item_name }}
</h5>
</div>
</div>
</template>
<template #item.lab_kit_name="{ item }">
<div class="d-flex gap-x-3">
<div class="d-flex flex-column align-center">
<h5 style="margin-bottom: 0px;">
{{ item.lab_kit_name }}
</h5>
</div>
</div>
</template>
<template #item.status="{ item }">
<span>
<VChip variant="tonal" :color="getStatusColorLabKit(item.status)" size="small">
{{ item.status }}
</VChip>
</span>
</template>
<template #item.result="{ item }">
<span v-if="item.result">
<a href="#" @click="openPdfInNewTab(item.result)" target="_blank"
class="custom-link">
<div class="d-inline-flex align-center">
<img :src="pdf" height="20" class="me-2" alt="img">
<span class="app-timeline-text font-weight-medium">
results.pdf
</span>
</div>
</a>
</span>
<span v-else>
Waiting For Result
</span>
</template>
<template #bottom />
</VDataTable>
</div>
</VCardText>
</VCard>
</VCol>
<VCol cols="12" md="4">
<VCard class="mb-6">
<VCardText>
<div class="d-flex align-center justify-space-between gap-1 mb-6">
<div class="text-body-1 text-high-emphasis font-weight-medium">
<v-icon class="mr-2" color="rgb(var(--v-theme-yellow))">mdi-calendar-clock</v-icon>
Appointment Details
</div>
</div>
<div class="appointment-details">
<div class="detail-item">
<span class="detail-label">Appointment At:</span>
<span class="detail-value">{{ scheduleDate + ' ' + scheduleTime }}</span>
</div>
<div class="detail-item"
v-if="filteredOrders.appointment_details.start_time && filteredOrders.appointment_details.end_time">
<span class="detail-label">Start Time:</span>
<span class="detail-value">{{
formatDate(filteredOrders.appointment_details.start_time)
}}</span>
</div>
<div class="detail-item"
v-if="filteredOrders.appointment_details.start_time && filteredOrders.appointment_details.end_time">
<span class="detail-label">End Time:</span>
<span class="detail-value">{{
formatDate(filteredOrders.appointment_details.end_time)
}}</span>
</div>
<div class="detail-item"
v-if="filteredOrders.appointment_details.start_time && filteredOrders.appointment_details.end_time">
<span class="detail-label">Duration:</span>
<span class="detail-value">{{
totalCallDuration(filteredOrders.appointment_details.start_time,
filteredOrders.appointment_details.end_time) }}</span>
</div>
</div>
</VCardText>
</VCard>
<!-- 👉 Customer Details -->
<VCard class="mb-6" v-if="filteredOrders.appointment_details.provider_name">
<VCardText class="d-flex flex-column gap-y-6">
<h3>Patient Details</h3>
<div class="d-flex align-center">
<VAvatar :image="avatar1" class="me-3" style="display: none;" />
<VAvatar color="rgb(var(--v-theme-yellow))" class="me-3" size="30">
<VIcon icon="mdi-account" size="25" color="white" />
</VAvatar>
<div>
<div class="text-body-1 text-high-emphasis font-weight-medium">
{{
filteredOrders.patient_details.first_name + ' ' +
filteredOrders.patient_details.last_name
}}
</div>
</div>
</div>
<div class="d-flex align-center" style="display: none;">
<VAvatar variant="tonal" color="success" class="me-3" style="display: none;">
<VIcon icon="ri-shopping-cart-line" />
</VAvatar>
<h4 style="display: none;">
{{ filteredOrders.order_items.total_products }}
Products
</h4>
</div>
<div class="d-flex flex-column gap-y-1">
<div class="d-flex justify-space-between gap-1 text-body-2">
<h5>Contact Info</h5>
</div>
<span>Email:
{{ filteredOrders.patient_details.email }}</span>
<span>Mobile:
{{ filteredOrders.patient_details.phone_no }}</span>
</div>
</VCardText>
</VCard>
<!-- 👉 Shipping Address -->
<VCard class="mb-6">
<VCardText>
<div class="d-flex align-center justify-space-between gap-1 mb-6">
<div class="text-body-1 text-high-emphasis font-weight-medium">
<v-icon class="mr-2" color="rgb(var(--v-theme-yellow))">mdi-truck-delivery</v-icon>
Shipping Address
</div>
<!-- <span
class="text-base text-primary font-weight-medium cursor-pointer"
@click="
isEditAddressDialogVisible =
!isEditAddressDialogVisible
"
>Edit</span
> -->
</div>
<div>
{{ filteredOrders.order_details.shipping_address1 }}
<br />
{{ filteredOrders.order_details.shipping_city }}
<br />
{{ filteredOrders.order_details.shipping_state }},
{{ filteredOrders.order_details.shipping_zipcode }}
<br />
{{ filteredOrders.order_details.shipping_country }}
</div>
</VCardText>
</VCard>
<!-- 👉 Billing Address -->
<VCard style="display: none;">
<VCardText>
<div class="d-flex align-center justify-space-between gap-1 mb-3">
<div class="text-body-1 text-high-emphasis font-weight-medium">
Billing Address
</div>
<!-- <span
class="text-base text-primary font-weight-medium cursor-pointer"
@click="
isEditAddressDialogVisible =
!isEditAddressDialogVisible
"
>Edit</span
> -->
</div>
<div>
{{ filteredOrders.order_details.billing_address1 }}
<br />
{{ filteredOrders.order_details.billing_city }}
<br />
{{ filteredOrders.order_details.billing_state }},
{{ filteredOrders.order_details.billing_zipcode }}
<br />
{{ filteredOrders.order_details.billing_country }}
</div>
<!-- <div class="mt-6">
<h6 class="text-h6 mb-1">Mastercard</h6>
<div class="text-base">Card Number: ******4291</div>
</div> -->
</VCardText>
</VCard>
</VCol>
</VRow>
<!-- <ConfirmDialog
v-model:isDialogVisible="isConfirmDialogVisible"
confirmation-question="Are you sure to cancel your Order?"
cancel-msg="Order cancelled!!"
cancel-title="Cancelled"
confirm-msg="Your order cancelled successfully."
confirm-title="Cancelled!"
/> -->
<!-- <UserInfoEditDialog
v-model:isDialogVisible="isUserInfoEditDialogVisible"
/>
<AddEditAddressDialog
v-model:isDialogVisible="isEditAddressDialogVisible"
/> -->
</div>
</template>
<style scoped>
.appointment-details {
display: flex;
flex-direction: column;
}
.detail-item {
display: flex;
margin-bottom: 10px;
}
.detail-label {
font-weight: bold;
min-width: 120px;
}
.detail-value {
flex: 1;
}
::-webkit-scrollbar {
width: 10px;
/* Width of the scrollbar */
}
/* Track */
::-webkit-scrollbar-track {
background: #f1f1f1;
/* Color of the track */
}
/* Handle */
::-webkit-scrollbar-thumb {
background: #888;
/* Color of the handle */
border-radius: 5px;
/* Roundness of the handle */
}
/* Handle on hover */
::-webkit-scrollbar-thumb:hover {
background: #555;
/* Color of the handle on hover */
}
</style>

View File

@@ -0,0 +1,602 @@
<script setup>
import moment from "moment";
import { computed, onMounted, ref } from "vue";
import { useDisplay } from "vuetify";
import { useStore } from "vuex";
const router = useRouter();
const store = useStore();
const ordersList = ref([]);
const widgetData = ref([
{
title: "Pending Payment",
value: 56,
icon: "ri-calendar-2-line",
},
{
title: "Completed",
value: 12689,
icon: "ri-check-double-line",
},
{
title: "Refunded",
value: 124,
icon: "ri-wallet-3-line",
},
{
title: "Failed",
value: 32,
icon: "ri-error-warning-line",
},
]);
const searchQuery = ref("");
// Data table options
const itemsPerPage = ref(10);
const page = ref(1);
const sortBy = ref();
const orderBy = ref();
const { smAndDown } = useDisplay();
const options = ref({
page: 1,
itemsPerPage: 5,
sortBy: [""],
sortDesc: [false],
});
const isLoading = ref(true);
// Data table Headers
const headers = [
{
title: "Order",
key: "id",
},
// {
// title: 'Order Date',
// key: 'date',
// },
{
title: "Patient",
key: "patient_name",
},
{
title: "Meeting Date",
key: "appointment_date",
},
{
title: "Meeting Time",
key: "appointment_time",
},
{
title: "Perscrption Status",
key: "status",
},
{
title: "Meeting status",
key: "appointment_status",
},
{
title: "Actions",
key: "actions",
sortable: false,
},
];
const updateOptions = (options) => {
page.value = options.page;
sortBy.value = options.sortBy[0]?.key;
orderBy.value = options.sortBy[0]?.order;
};
const resolvePaymentStatus = (status) => {
if (status === 1)
return {
text: "Paid",
color: "success",
};
if (status === 2)
return {
text: "Pending",
color: "warning",
};
if (status === 3)
return {
text: "Cancelled",
color: "secondary",
};
if (status === 4)
return {
text: "Failed",
color: "error",
};
};
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" };
}
};
const formatDateDa = (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 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 ordersGetList = computed(() => {
return ordersList.value.map((history) => ({
...history,
// appointment_date: getConvertedDate(convertUtcTime(history.appointment_time, history.appointment_date, history.timezone)),
//appointment_time: getConvertedTime(convertUtcTime(history.appointment_time, history.appointment_date, history.timezone)),
// appointment_date: changeFormat(history.appointment_date),
// appointment_time: convertUtcDateTimeToLocal(history.appointment_date, history.appointment_time, 'time'),
// start_time: changeDateFormat(history.start_time),
// end_time: changeDateFormat(history.end_time),
// duration: totalCallDuration(history.start_time, history.end_time),
}));
// isLoading.value - false
// ordersList.value.sort((a, b) => {
// return b.id - a.id;
// });
// return ordersList.value
});
onMounted(async () => {
store.dispatch("updateIsLoading", true);
await store.dispatch("orderAgentList");
ordersList.value = store.getters.getPatientOrderList;
console.log("ordersList", ordersList.value);
isLoading.value = false;
});
const convertUtcTime = (time, date, timezone) => {
const timezones = {
EST: "America/New_York",
CST: "America/Chicago",
MST: "America/Denver",
PST: "America/Los_Angeles",
// Add more mappings as needed
};
// Get the IANA timezone identifier from the abbreviation
const ianaTimeZone = timezones[timezone];
if (!ianaTimeZone) {
throw new Error(`Unknown timezone abbreviation: ${timezone}`);
}
// Combine date and time into a single string
const dateTimeString = `${date}T${time}Z`; // Assuming the input date and time are in UTC
// Create a Date object from the combined string
const dateObj = new Date(dateTimeString);
// Options for the formatter
const options = {
timeZone: ianaTimeZone,
year: "numeric",
month: "2-digit",
day: "2-digit",
hour: "2-digit",
minute: "2-digit",
second: "2-digit",
hour12: false,
};
// Create the formatter
const formatter = new Intl.DateTimeFormat("en-US", options);
// Format the date
const convertedDateTime = formatter.format(dateObj);
return convertedDateTime;
};
const getConvertedTime = (inputDate) => {
// Split the input date string into date and time components
const [datePart, timePart] = inputDate.split(", ");
// Split the time component into hours, minutes, and seconds
let [hours, minutes, seconds] = timePart.split(":");
// Convert the hours to an integer
hours = parseInt(hours);
// Determine the period (AM/PM) and adjust the hours if necessary
const period = hours >= 12 ? "PM" : "AM";
hours = hours % 12 || 12; // Convert 0 and 12 to 12, and other hours to 1-11
// Format the time as desired
const formattedTime = `${hours
.toString()
.padStart(2, "0")}:${minutes}${period}`;
return formattedTime;
};
const getConvertedDate = (inputDate) => {
// Split the input date string into date and time components
const [datePart, timePart] = inputDate.split(", ");
// Split the date component into month, day, and year
const [month, day, year] = datePart.split("/");
// Create a new Date object from the parsed components
const dateObject = new Date(`${year}-${month}-${day}T${timePart}`);
// Define an array of month names
const monthNames = [
"January",
"February",
"March",
"April",
"May",
"June",
"July",
"August",
"September",
"October",
"November",
"December",
];
// Format the date as desired
const formattedDate = `${
monthNames[dateObject.getMonth()]
} ${day}, ${year}`;
return formattedDate;
};
const formatDate = (date) => {
const messageDate = new Date(date);
const options = {
year: "numeric",
month: "numeric",
day: "numeric",
hour: "numeric", // Change from '2-digit' to 'numeric'
minute: "2-digit",
hour12: true, // Add hour12: true to get 12-hour format with AM/PM
};
const formattedDate = messageDate
.toLocaleString("en-US", options)
.replace(/\//g, "-")
.replace(",", ""); // Remove the comma
return formattedDate.trim();
};
const viewOrder = (orderId) => {
router.push({ name: "provider-order-detail", params: { id: orderId } });
};
function changeFormat(dateFormat) {
const dateParts = dateFormat.split("-"); // Assuming date is in yyyy-mm-dd format
const year = parseInt(dateParts[0]);
const month = String(dateParts[1]).padStart(2, "0"); // Pad single-digit months with leading zero
const day = String(dateParts[2]).padStart(2, "0"); // Pad single-digit days with leading zero
// 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;
}
const getStatusColor = (status) => {
switch (status) {
case "pending":
return "orange";
case "Shipped":
return "blue";
case "Delivered":
return "green";
case "Cancelled":
return "red";
default:
return "gray";
}
};
</script>
<template>
<div>
<VDialog
v-model="store.getters.getIsLoading"
width="110"
height="150"
color="primary"
>
<VCardText class="" style="color: white !important">
<div class="demo-space-x">
<VProgressCircular
:size="40"
color="primary"
indeterminate
/>
</div>
</VCardText>
</VDialog>
<VCard class="mb-6" style="display: none">
<VCardText class="px-2">
<VRow>
<template v-for="(data, index) in widgetData" :key="index">
<VCol cols="12" sm="6" md="3" class="px-6">
<div
class="d-flex justify-space-between"
:class="
$vuetify.display.xs
? 'product-widget'
: $vuetify.display.sm
? index < 2
? 'product-widget'
: ''
: ''
"
>
<div class="d-flex flex-column gap-y-1">
<h4 class="text-h4">
{{ data.value }}
</h4>
<span class="text-base text-capitalize">
{{ data.title }}
</span>
</div>
<VAvatar variant="tonal" rounded size="42">
<VIcon :icon="data.icon" size="26" />
</VAvatar>
</div>
</VCol>
<VDivider
v-if="
$vuetify.display.mdAndUp
? index !== widgetData.length - 1
: $vuetify.display.smAndUp
? index % 2 === 0
: false
"
vertical
inset
length="100"
/>
</template>
</VRow>
</VCardText>
</VCard>
<VRow>
<VCol cols="12" md="12">
<VCard title="Prescriptions">
<VCardText>
<div
class="d-flex justify-sm-space-between align-center justify-start flex-wrap gap-4"
>
<VTextField
v-model="searchQuery"
placeholder="Search Order"
density="compact"
style="
max-inline-size: 200px;
min-inline-size: 200px;
"
/>
</div>
</VCardText>
<VCardText>
<v-data-table
:loading="isLoading"
:headers="headers"
:items="ordersGetList"
:search="searchQuery"
>
<!-- full name -->
<template #item.id="{ item }">
<RouterLink
:to="{
name: 'provider-order-detail',
params: { id: item.id },
}"
>
#{{ item.id }}
</RouterLink>
</template>
<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 },
}"
>
<div class="font-weight-medium">
{{ item.patient_name }}
</div>
</RouterLink>
<small>{{ item.patient_email }}</small>
</div>
</div>
</template>
<template #item.appointment_date="{ item }">
{{
getConvertedDate(
convertUtcTime(
item.appointment_time,
item.appointment_date,
item.timezone
)
)
}}
</template>
<template #item.appointment_time="{ item }">
{{
getConvertedTime(
convertUtcTime(
item.appointment_time,
item.appointment_date,
item.timezone
)
)
}}
</template>
<template #item.status="{ item }">
<span>
<VChip
variant="tonal"
:color="getStatusColor(item.status)"
size="small"
>
{{ item.status }}
</VChip>
</span>
</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',
}"
>
{{
resolveStatusVariant(
item.appointment_status
).text
}}
</VChip>
</template>
<template #item.actions="{ item }">
<IconBtn size="small">
<VIcon icon="ri-more-2-line" />
<VMenu activator="parent">
<VList>
<VListItem value="view">
<RouterLink
:to="{
name: 'provider-order-detail',
params: { id: item.id },
}"
class="text-high-emphasis"
>
View
</RouterLink>
</VListItem>
<VListItem
value="complete"
v-if="
item.appointment_status.toLowerCase() !==
'completed'
"
>
<span
@click="
markAsCompleted(item)
"
class="text-high-emphasis cursor-pointer"
>
Mark as Complete
</span>
</VListItem>
</VList>
</VMenu>
</IconBtn>
</template>
</v-data-table>
</VCardText>
</VCard>
</VCol>
</VRow>
</div>
</template>
<style lang="scss" scoped>
#customer-link {
&:hover {
color: "#000" !important;
}
}
.product-widget {
border-block-end: 1px solid
rgba(var(--v-theme-on-surface), var(--v-border-opacity));
padding-block-end: 1rem;
}
</style>

View File

@@ -0,0 +1,552 @@
<script setup>
import { computed, onMounted, reactive, ref } from "vue";
import { useStore } from "vuex";
const router = useRouter();
const store = useStore();
const orders = ref([]);
const selectedDate = ref();
const headers = [
{ title: "Order Number", key: "id" },
{ title: "Customer", key: "customer" },
{ title: "Total", key: "total" },
{ title: "Status", key: "status" },
{ title: "Actions", key: "actions" },
];
const loading = ref(false);
const search = ref("");
const filterDialog = ref(false);
const isShown = ref(false);
const startDateMenu = ref(null)
const endDateMenu = ref(null)
const showCustomRangePicker = ref(false);
const dateRange = ref([]);
const filters = reactive({
startDate: null,
endDate: null,
dateRangeText: computed(() => {
if (filters.startDate && filters.endDate) {
return `${formatDateDate(filters.startDate)} - ${formatDateDate(filters.endDate)}`;
}
return 'Select Date';
}),
});
const statusOptions = ["Pending", "Shipped", "Delivered", "Cancelled"];
const getStatusColor = (status) => {
switch (status) {
case "Pending":
return "orange";
case "Shipped":
return "blue";
case "Delivered":
return "green";
case "Cancelled":
return "red";
default:
return "gray";
}
};
const openFilterDialog = () => {
isShown.value = true;
};
const resetFilters = async () => {
filters.search = "";
filters.status = [];
filters.startDate = null
filters.endDate = null
startDateMenu.value = null
endDateMenu.value = null
store.dispatch("updateIsLoading", true);
await store.dispatch("orderAgentList");
orders.value = store.getters.getPatientOrderList;
console.log(orders.value);
store.dispatch("updateIsLoading", false);
};
const applyFilters = async () => {
search.value = filters.search;
filterDialog.value = false;
await getFilter()
};
const filteredOrders = computed(() => {
let filtered = store.getters.getPatientOrderList;
if (filters.search) {
filtered = filtered.filter((order) =>
order.orderNumber
.toLowerCase()
.includes(filters.search.toLowerCase())
);
}
// if (filters.status.length > 0) {
// filtered = filtered.filter((order) =>
// filters.status.includes(order.status)
// );
// }
// if (filters.startDate) {
// const startDate = new Date(filters.startDate);
// filtered = filtered.filter((order) => {
// const orderDate = new Date(order.created_at);
// return orderDate >= startDate;
// });
// }
// if (filters.endDate) {
// const endDate = new Date(filters.endDate);
// filtered = filtered.filter((order) => {
// const orderDate = new Date(order.created_at);
// return orderDate <= endDate;
// });
// }
return filtered;
});
const datepickStart = async () => {
console.log("ppicker", startDateMenu.value);
if (startDateMenu.value) {
const selectedDate = new Date(startDateMenu.value);
const dateWithoutTime = new Date(selectedDate.getFullYear(), selectedDate.getMonth(), selectedDate.getDate());
// Format the date as needed
console.log("formattedDate",);
// const formattedDate = selectedDate.getFullYear() + '-' + selectedDate.getMonth() + '-' + selectedDate.getDate() //dateWithoutTime.toISOString().slice(0, 10);
const formattedDate = formatDateDate(selectedDate)
console.log("formattedDate", formattedDate);
filters.startDate = formattedDate
showStartDatePicker.value = false;
// await getFilter()
}
}
const formatDateDate = (date) => {
const messageDate = new Date(date);
const options = {
year: 'numeric',
month: 'numeric',
day: 'numeric',
};
return messageDate.toLocaleDateString('en-US', options).replace(/\//g, '-');
};
const datepickendDate = async () => {
console.log("ppicker", filters.endDate);
if (endDateMenu.value) {
const selectedDate = new Date(endDateMenu.value);
const dateWithoutTime = new Date(selectedDate.getFullYear(), selectedDate.getMonth(), selectedDate.getDate());
// Format the date as needed
console.log("formattedDate", dateWithoutTime);
const formattedDate = formatDateDate(selectedDate)//dateWithoutTime.toISOString().slice(0, 10);
console.log("formattedDate", formattedDate);
filters.endDate = formattedDate
showEndDatePicker.value = false;
//await getFilter()
}
}
const viewOrder = (orderId) => {
router.push({ name: "provider-order-detail", params: { id: orderId } });
};
const formatDate = (date) => {
const messageDate = new Date(date);
const options = {
year: "numeric",
month: "numeric",
day: "numeric",
hour: "numeric", // Change from '2-digit' to 'numeric'
minute: "2-digit",
hour12: true, // Add hour12: true to get 12-hour format with AM/PM
};
const formattedDate = messageDate
.toLocaleString("en-US", options)
.replace(/\//g, "-")
.replace(',', ''); // Remove the comma
return formattedDate.trim();
};
const getFilter = async () => {
console.log("filter", filters.startDate, filters.endDate);
await store.dispatch('orderAgentListFilter', {
from_date: formatDateDate(filters.startDate),
to_date: formatDateDate(filters.endDate),
})
orders.value = store.getters.getPatientOrderList;
store.dispatch('updateIsLoading', false)
}
onMounted(async () => {
store.dispatch("updateIsLoading", true);
await store.dispatch("orderAgentList");
orders.value = store.getters.getPatientOrderList;
console.log(orders.value);
});
const selectToday = () => {
const today = new Date().toISOString().split('T')[0];
filters.startDate = today;
filters.endDate = today;
showCustomRangePicker.value = false;
};
const selectYesterday = () => {
const today = new Date();
const yesterday = new Date(today);
yesterday.setDate(yesterday.getDate() - 1);
const formattedYesterday = yesterday.toISOString().split('T')[0];
filters.startDate = formattedYesterday;
filters.endDate = formattedYesterday;
showCustomRangePicker.value = false;
};
const selectLast7Days = () => {
const today = new Date();
const sevenDaysAgo = new Date(today);
sevenDaysAgo.setDate(sevenDaysAgo.getDate() - 6);
filters.startDate = sevenDaysAgo.toISOString().split('T')[0];
filters.endDate = today.toISOString().split('T')[0];
showCustomRangePicker.value = false;
};
const minDate = computed(() => {
const date = new Date();
date.setFullYear(date.getFullYear() - 1);
return date.toISOString().substr(0, 10);
});
const maxDate = computed(() => {
const date = new Date();
return date.toISOString().substr(0, 10);
});
const applyCustomRange = (dates) => {
console.log(dateRange.value)
dateRange.value.sort();
if (dates.length === 2) {
[filters.startDate, filters.endDate] = dates;
showCustomRangePicker.value = false;
}
};
const showCustomRangePickerFunction = (state) => {
if (state) {
dateRange.value = []
showCustomRangePicker.value = true;
}
}
</script>
<template>
<VDialog v-model="store.getters.getIsLoading" width="110" height="150" color="primary">
<VCardText class="" style="color: white !important">
<div class="demo-space-x">
<VProgressCircular :size="40" color="primary" indeterminate />
</div>
</VCardText>
</VDialog>
<v-container class="pt-0">
<v-row>
<v-col cols="12">
<VCardTitle class="pt-0"><b>ORDERS </b></VCardTitle>
<v-card>
<v-card-title>
<v-row class="px-0 py-4">
<v-col cols="12" md="4">
<VMenu location="bottom" :close-on-content-click="false" :nudge-right="40"
transition="scale-transition" offset-y min-width="auto" density="compact">
<template #activator="{ props }">
<v-text-field v-model="filters.dateRangeText" label="Select Date Range"
v-bind="props" outlined density="compact" readonly></v-text-field>
</template>
<v-card>
<v-list>
<v-list-item @click="selectToday">Today</v-list-item>
<v-list-item @click="selectYesterday">Yesterday</v-list-item>
<v-list-item @click="selectLast7Days">Last 7 Days</v-list-item>
<v-list-item @click="showCustomRangePickerFunction(true)">Custom
Range</v-list-item>
</v-list>
<v-date-picker v-if="showCustomRangePicker" v-model="dateRange" :max="maxDate"
:min="minDate" multiple class="custom-date-picker" mode="range"
@update:model-value="applyCustomRange" hide-header>
</v-date-picker>
</v-card>
</VMenu>
</v-col>
<v-col cols="4">
<v-btn color="primary" class="text-capitalize mr-1" text @click="applyFilters">
Filter
</v-btn>
<v-btn color="primary" class="text-capitalize" text @click="resetFilters">
Reset
</v-btn>
</v-col>
</v-row>
</v-card-title>
</v-card>
</v-col>
</v-row>
<v-row v-if="filteredOrders.length > 0">
<v-col v-for="order in filteredOrders" :key="order.id" cols="12" md="6">
<v-card class="order-card mb-6 rounded-lg elevation-3">
<div class="order-header pa-4">
<v-row no-gutters align="center">
<v-col>
<div class="d-flex align-center">
<v-avatar color="primary" size="56"
class="white--text text-h5 font-weight-bold mr-4">
#{{ order.id }}
</v-avatar>
<div>
<div class="text-subtitle-1 font-weight-medium">{{ formatDate(order.created_at)
}}</div>
</div>
</div>
</v-col>
<v-col cols="auto" class="ml-auto">
<v-chip color="primary" label x-large class="font-weight-bold">
Total: ${{ parseFloat(order.order_total_amount +
order.order_total_shipping).toLocaleString('en-US', {
minimumFractionDigits: 2,
maximumFractionDigits: 2
}) }}
</v-chip>
</v-col>
</v-row>
</div>
<v-divider></v-divider>
<v-card-text class="pa-4">
<h3 class="text-h6 font-weight-bold mb-4">Order Items</h3>
<div class="order-items-container">
<v-list class="order-items-list">
<v-list-item v-for="item in order.order_items" :key="item.id" class="mb-2 rounded-lg"
two-line>
<v-list-item-avatar tile size="80" class="rounded-lg">
<v-img :src="item.image_url" cover></v-img>
</v-list-item-avatar>
<v-list-item-content>
<v-list-item-title class="text-subtitle-1 font-weight-medium">{{
item.title
}}</v-list-item-title>
<v-list-item-subtitle>
<v-chip x-small class="mr-2" outlined>Qty: {{ item.qty }} </v-chip>
<v-chip x-small outlined>${{ parseFloat(item.price).toLocaleString('en-US',
{
minimumFractionDigits: 2, maximumFractionDigits: 2
}) }}
each</v-chip>
<v-chip color="primary" x-small>$ {{ parseFloat(item.qty *
item.price).toLocaleString('en-US', {
minimumFractionDigits: 2,
maximumFractionDigits: 2
})
}}</v-chip>
</v-list-item-subtitle>
</v-list-item-content>
<v-list-item-action>
</v-list-item-action>
</v-list-item>
</v-list>
</div>
</v-card-text>
<v-divider></v-divider>
<v-card-actions class="pa-4">
<v-spacer></v-spacer>
<v-btn @click="viewOrder(order.id)" color="primary" outlined rounded>
<v-icon left>mdi-eye</v-icon>
View Details
</v-btn>
</v-card-actions>
</v-card>
</v-col>
</v-row>
<v-row v-else>
<v-col cols="12" md="12">
<v-card class="mb-4 rounded">
<v-card-title class="d-flex justify-space-between align-center">
</v-card-title>
<v-card-text>
<v-row>
<v-col cols="12" class="text-center">no data found </v-col>
</v-row>
</v-card-text>
</v-card>
</v-col>
</v-row>
<v-dialog v-model="filterDialog" max-width="500">
<v-card>
<v-card-title>
<span class="text-h5">Filter Orders</span>
</v-card-title>
<v-card-text>
<v-container>
<v-row>
<v-col cols="12">
<v-text-field v-model="filters.search" label="Search" outlined dense></v-text-field>
</v-col>
<v-col cols="12">
<v-select v-model="filters.status" :items="statusOptions" label="Status" outlined dense
multiple></v-select>
</v-col>
</v-row>
</v-container>
</v-card-text>
<v-card-actions>
<v-btn color="primary" text @click="resetFilters">
Reset Filters
</v-btn>
<v-spacer></v-spacer>
<v-btn color="primary" text @click="applyFilters">
Apply
</v-btn>
<v-btn text @click="filterDialog = false"> Cancel </v-btn>
</v-card-actions>
</v-card>
</v-dialog>
</v-container>
</template>
<style scoped>
.custom-select {
min-height: 44px;
/* Adjust the minimum height as needed */
padding-top: 8px;
/* Adjust top padding as needed */
padding-bottom: 8px;
/* Adjust bottom padding as needed */
}
.text-primary {
color: rgb(var(--v-theme-yellow)) !important;
}
.v-date-picker-month__day .v-btn {
--v-btn-height: 23px !important;
--v-btn-size: 0.85rem;
}
.custom-date-picker {
font-size: 0.85em;
}
.custom-date-picker :deep(.v-date-picker-month) {
width: 100%;
}
.custom-date-picker :deep(.v-date-picker-month__day) {
width: 30px;
height: 30px;
}
.custom-date-picker :deep(.v-date-picker-month) {
min-width: 300px;
}
.custom-date-picker :deep(.v-date-picker-month__day .v-btn) {
--v-btn-height: 20px !important;
}
.order-card {
transition: all 0.3s;
}
.order-card:hover {
transform: translateY(-5px);
box-shadow: 0 12px 20px -10px rgba(0, 0, 0, 0.1), 0 4px 20px 0px rgba(0, 0, 0, 0.1), 0 7px 8px -5px rgba(0, 0, 0, 0.1) !important;
}
.order-header {
background-color: #f5f5f5;
}
.order-items-container {
height: 155px;
/* Set a fixed height */
overflow-y: auto;
/* Enable vertical scrolling */
}
.order-items-list {
padding-right: 16px;
/* Add some padding for the scrollbar */
}
.order-card .v-list-item {
border: 1px solid rgba(0, 0, 0, 0.12);
}
/* Custom scrollbar styles */
.order-items-container::-webkit-scrollbar {
width: 8px;
}
.order-items-container::-webkit-scrollbar-track {
background: #f1f1f1;
border-radius: 4px;
}
.order-items-container::-webkit-scrollbar-thumb {
background: #888;
border-radius: 4px;
}
.order-items-container::-webkit-scrollbar-thumb:hover {
background: #555;
}
/* Mobile styles */
@media (max-width: 600px) {
.order-header {
flex-direction: column;
}
.order-header .v-avatar {
margin-bottom: 16px;
}
.order-header .v-col {
text-align: center;
}
.order-header .ml-auto {
margin: 16px auto 0 auto;
}
.order-items-container {
height: auto;
}
}
</style>

View File

@@ -0,0 +1,133 @@
<script setup>
const props = defineProps({
userData: {
type: Object,
required: true,
},
})
import { useStore } from "vuex";
const store = useStore();
const notes = ref([]);
const historyNotes = computed(async () => {
let notesData = props.userData.notes_history;
for (let data of notesData) {
if (data.note_type == 'Notes') {
let dataObject = {}
dataObject.note = data.note
dataObject.doctor = data.provider_name;
dataObject.date = formatDateDate(data.note_date)
dataObject.appointment_id = data.appointment_id
dataObject.originalDate = new Date(data.note_date)
//notes.value.push(dataObject)
}
}
notes.value.sort((a, b) => {
return b.id - a.id;
});
store.dispatch('updateIsLoading', false)
return notes.value
});
const formatDateDate = (date) => {
const messageDate = new Date(date);
const options = {
year: 'numeric',
month: 'long',
day: 'numeric',
hour: '2-digit',
minute: '2-digit'
};
return messageDate.toLocaleDateString('en-US', options).replace(/\//g, '-');
};
onMounted(async () => {
let notesData = props.userData.notes_history;
console.log("notesData", notesData);
for (let data of notesData) {
let dataObject = {}
dataObject.note = data.note
dataObject.doctor = data.provider_name;
dataObject.date = formatDateDate(data.note_date)
dataObject.appointment_id = data.appointment_id
dataObject.originalDate = new Date(data.note_date)
notes.value.push(dataObject)
}
notes.value.sort((a, b) => {
return b.originalDate - a.originalDate;
});
console.log("getNotes", notes.value);
});
</script>
<template>
<VRow>
<VCol cols="12" md="4">
<VCard class="mb-4">
<VCardText>
<div class="text-body-2 text-disabled mt-6 mb-4">
CONTACTS
</div>
<div class="d-flex flex-column gap-y-4">
<div class="d-flex align-center gap-x-2">
<v-icon size="24">mdi-email-outline</v-icon>
<div class="font-weight-medium">
Email :
</div>
<div class="text-truncate">
{{ props.userData.patient_details.email }}
</div>
</div>
<div class="d-flex align-center gap-x-2">
<v-icon size="24">mdi-phone-outline</v-icon>
<div class="font-weight-medium">
Phone :
</div>
<div class="text-truncate">
{{ props.userData.patient_details.phone_no }}
</div>
</div>
</div>
</VCardText>
</VCard>
</VCol>
<VCol cols="12" md="8">
<!-- 👉 Activity timeline -->
<VCard title="Notes">
<VCardText>
<VTimeline truncate-line="both" align="start" side="end" line-inset="10" line-color="primary"
density="compact" class="v-timeline-density-compact">
<template v-if="historyNotes">
<VTimelineItem dot-color="yellow" size="x-small" v-for="(p_note, index) of notes"
:key="index">
<div class="d-flex justify-space-between align-center mb-3">
<span class="app-timeline-title">{{ p_note.note }}</span>
<span class="app-timeline-meta">{{ p_note.date }}</span>
</div>
<p class="app-timeline-text mb-0">
Provider Name: {{ p_note.doctor }}
</p>
</VTimelineItem>
</template>
</VTimeline>
</VCardText>
</VCard>
</VCol>
</VRow>
</template>
<style lang="scss" scoped>
.card-list {
--v-card-list-gap: 16px;
}
</style>

View File

@@ -0,0 +1,85 @@
<script setup>
import coverImg from "@images/pages/membershipBanner.jpg";
import { onMounted } from "vue";
import { useStore } from "vuex";
const store = useStore();
const profileHeaderData = ref(null)
const props = defineProps({
userData: {
type: Object,
required: true,
},
})
onMounted(async () => {
store.dispatch("updateIsLoading", true);
profileHeaderData.value = props.userData;
console.log('profileHeaderData', profileHeaderData)
store.dispatch("updateIsLoading", false);
});
</script>
<template>
<VDialog v-model="store.getters.getIsLoading" width="110" height="150" color="primary">
<VCardText class="" style="color: white !important;">
<div class="demo-space-x">
<VProgressCircular :size="40" color="primary" indeterminate />
</div>
</VCardText>
</VDialog>
<VCard v-if="profileHeaderData">
<VImg :src="coverImg" min-height="125" max-height="250" cover />
<VCardText class="d-flex align-bottom flex-sm-row flex-column justify-center gap-x-6">
<div class="d-flex h-0">
<VAvatar rounded size="130" :image="profileHeaderData.profile_picture"
class="user-profile-avatar mx-auto">
<VImg :src="profileHeaderData.profile_picture" height="120" width="120" />
</VAvatar>
</div>
<div class="user-profile-info w-100 mt-16 pt-6 pt-sm-0 mt-sm-0">
<h4 class="text-h4 text-center text-sm-start mb-2">
{{ profileHeaderData.first_name + ' ' + profileHeaderData.last_name }}
</h4>
<div class="d-flex align-center justify-center justify-sm-space-between flex-wrap gap-4">
<div class="d-flex flex-wrap justify-center justify-sm-start flex-grow-1 gap-6">
<div class="d-flex align-center gap-x-2">
<VIcon size="24" icon="ri-palette-line" />
<div class="text-body-1 font-weight-medium">
{{ profileHeaderData.marital_status }}
</div>
</div>
<div class="d-flex align-center gap-x-2">
<VIcon size="24" icon="ri-calendar-line" />
<div class="text-body-1 font-weight-medium">
{{ profileHeaderData.dob }}
</div>
</div>
<div class="d-flex align-center gap-x-2">
<VIcon size="24" icon="ri-map-pin-line" />
<div class="text-body-1 font-weight-medium">
{{ profileHeaderData.city + ',' + profileHeaderData.country }}
</div>
</div>
</div>
</div>
</div>
</VCardText>
</VCard>
</template>
<style lang="scss">
.user-profile-avatar {
border: 5px solid rgb(var(--v-theme-surface));
background-color: rgb(var(--v-theme-surface)) !important;
inset-block-start: -3rem;
.v-img__img {
border-radius: 0.375rem;
}
}
</style>

View File

@@ -0,0 +1,370 @@
<script setup>
import { computed, onMounted, ref } from "vue";
import { useRoute, useRouter } from 'vue-router';
import { useStore } from "vuex";
const store = useStore();
const props = defineProps({
orderData: {
type: Object,
required: true,
},
})
const router = useRouter()
const route = useRoute()
const itemsPrescriptions = ref([]);
// const patientId = route.params.patient_id;
const prescriptionLoaded = ref(false)
const doctorName = ref('');
const prescription = computed(async () => {
await fetchPrescriptions()
return prescriptionLoaded.value ? itemsPrescriptions.value : null
})
const show = ref(false)
onMounted(async () => {
let prescriptions = props.orderData.prescriptions;
console.log('props.orderData', props.orderData.prescriptions)
itemsPrescriptions.value = prescriptions
});
const formatDateDate = (date) => {
const messageDate = new Date(date);
const options = {
year: 'numeric',
month: 'long',
day: 'numeric',
hour: '2-digit',
minute: '2-digit'
};
return messageDate.toLocaleDateString('en-US', options).replace(/\//g, '-');
};
const getStatusColor = (status) => {
switch (status) {
case 'pending':
return 'warning'; // Use Vuetify's warning color (typically yellow)
case 'shipped':
return '#45B8AC'; // Use Vuetify's primary color (typically blue)
case 'delivered':
return 'green';
case 'returned':
return 'red';
case 'results':
return 'blue';
default:
return 'grey'; // Use Vuetify's grey color for any other status
}
};
const toggleShow = () => {
show.value = !show.value
}
const statusColor = (appointment_status) => {
console.log(appointment_status)
switch (appointment_status.toLowerCase()) {
case 'pending': return 'orange'
case 'confirmed': return 'green'
case 'cancelled': return 'red'
default: return 'grey'
}
}
const formatDate = (dateString) => {
return new Date(dateString).toLocaleDateString('en-US', {
year: 'numeric',
month: 'short',
day: 'numeric'
})
}
const formatTime = (dateString) => {
return new Date(dateString).toLocaleTimeString('en-US', {
hour: 'numeric',
minute: 'numeric'
})
}
const getImageUrl = (prescriptionName) => {
// This is a placeholder function. In a real application, you'd have a way to map prescription names to image URLs
return `https://picsum.photos/500/300?random=${prescriptionName.replace(/\s+/g, '')}`
}
</script>
<template>
<VDialog v-model="store.getters.getIsLoading" width="110" height="150" color="primary">
<VCardText class="" style="color: white !important;">
<div class="demo-space-x">
<VProgressCircular :size="40" color="primary" indeterminate />
</div>
</VCardText>
</VDialog>
<template v-if="itemsPrescriptions.length > 0">
<v-container fluid>
<v-row>
<v-col v-for="prescription in itemsPrescriptions" :key="prescription.prescription_id" cols="12" md="4">
<v-card class="mx-auto mb-4" elevation="4" hover>
<v-img height="200"
src="https://cdn.pixabay.com/photo/2016/11/23/15/03/medication-1853400_1280.jpg"
class="white--text align-end" gradient="to bottom, rgba(0,0,0,.1), rgba(0,0,0,.5)">
<v-card-title class="text-h5" style="color: #fff;">{{ prescription.prescription_name
}}</v-card-title>
</v-img>
<v-card-text>
<div class="my-4 text-subtitle-1">
{{ prescription.prescription_price }}
</div>
<v-chip :color="getStatusColor(prescription.status)" text-color="white" small class="mr-2">
{{ prescription.status }}
</v-chip>
<v-chip color="blue-grey" text-color="white" small>
Order ID: {{ prescription.order_id }}
</v-chip>
</v-card-text>
<v-divider class="mx-4"></v-divider>
<v-card-text>
<v-row dense>
<v-col cols="6">
<v-icon small color="rgb(var(--v-theme-yellow))">mdi-pill</v-icon>
<span class="ml-1">Dosage:{{ prescription.dosage }}</span>
</v-col>
<v-col cols="6">
<v-icon small color="rgb(var(--v-theme-yellow))">mdi-bottle-tonic</v-icon>
<span class="ml-1">Quantity:{{ prescription.quantity }}</span>
</v-col>
<v-col cols="6">
<v-icon small color="rgb(var(--v-theme-yellow))">mdi-calendar</v-icon>
<span class="ml-1">{{ formatDate(prescription.prescription_date) }}</span>
</v-col>
<v-col cols="6">
<v-icon small color="rgb(var(--v-theme-yellow))">mdi-clock-outline</v-icon>
<span class="ml-1">{{ formatTime(prescription.prescription_date) }}</span>
</v-col>
</v-row>
</v-card-text>
<v-card-actions>
<v-btn color="rgb(var(--v-theme-yellow-theme-button))" text>
More Details
</v-btn>
<v-spacer></v-spacer>
<v-btn icon @click="prescription.show = !prescription.show">
<v-icon color="rgb(var(--v-theme-yellow-theme-button))">{{ prescription.show ?
'mdi-chevron-up' :
'mdi-chevron-down'
}}</v-icon>
</v-btn>
</v-card-actions>
<v-expand-transition>
<div v-show="prescription.show">
<v-divider></v-divider>
<v-card-text>
<v-row dense>
<v-col cols="12">
<strong>Provider:</strong> {{ prescription.name }}
</v-col>
<v-col cols="12">
<strong>Brand:</strong> {{ prescription.brand }}
</v-col>
<v-col cols="12">
<strong>From:</strong> {{ prescription.from }}
</v-col>
<v-col cols="12">
<strong>Direction One:</strong> {{ prescription.direction_one }}
</v-col>
<v-col cols="12">
<strong>Direction Two:</strong> {{ prescription.direction_two }}
</v-col>
<v-col cols="12">
<strong>Refill Quantity:</strong> {{ prescription.refill_quantity }}
</v-col>
<v-col cols="12">
<strong>Direction Quantity:</strong> {{ prescription.direction_quantity }}
</v-col>
<v-col cols="12" v-if="prescription.comments">
<strong>Comments:</strong> {{ prescription.comments }}
</v-col>
</v-row>
</v-card-text>
</div>
</v-expand-transition>
</v-card>
</v-col>
</v-row>
</v-container>
<VExpansionPanels variant="accordion" style="display: none;">
<VExpansionPanel v-for="(item, index) in itemsPrescriptions" :key="index">
<div>
<VExpansionPanelTitle collapse-icon="mdi-chevron-down" expand-icon="mdi-chevron-right"
style="margin-left: 0px !important;">
<p class=""><b> {{ item.name }}</b>
<br />
<div class=" pt-2"> {{ doctorName }}</div>
<div class=" pt-2">{{ item.date }}</div>
</p>
<v-row>
</v-row>
<span class="v-expansion-panel-title__icon badge text-warning"
v-if="item.status == null">Pending</span>
<span class="v-expansion-panel-title__icon badge" v-else>
<v-chip :color="getStatusColor(item.status)" label size="small" variant="text">
{{ item.status }}
</v-chip></span>
</VExpansionPanelTitle>
<VExpansionPanelText class="pt-0">
<v-row class='mt-1'>
<v-col cols="12" md="4" sm="6">
<p class='heading'><b>Brand:</b></p>
</v-col>
<v-col cols="12" md="4" sm="6">
<p>{{ item.brand }}</p>
</v-col>
</v-row>
<v-row class='mt-1'>
<v-col cols="12" md="4" sm="6">
<p class='heading'><b>From:</b></p>
</v-col>
<v-col cols="12" md="4" sm="6">
<p>{{ item.from }}</p>
</v-col>
</v-row>
<v-row class='mt-1'>
<v-col cols="12" md="4" sm="6">
<p class='heading'><b>Dosage:</b></p>
</v-col>
<v-col cols="12" md="4" sm="6">
<p>{{ item.dosage }}</p>
</v-col>
</v-row>
<v-row class='mt-1'>
<v-col cols="12" md="4" sm="6">
<p class='heading'><b>Quantity:</b></p>
</v-col>
<v-col cols="12" md="4" sm="6">
<p>{{ item.quantity }}</p>
</v-col>
</v-row>
<v-row class='mt-1'>
<v-col cols="12" md="4" sm="6">
<p class='heading'><b>Direction Quantity:</b></p>
</v-col>
<v-col cols="12" md="8" sm="6">
<p>{{ item.direction_quantity }}</p>
</v-col>
</v-row>
<v-row class='mt-1'>
<v-col cols="12" md="4" sm="6">
<p class='heading'><b>Direction One:</b></p>
</v-col>
<v-col cols="12" md="8" sm="6">
<p>{{ item.direction_one }} </p>
</v-col>
</v-row>
<v-row class='mt-1'>
<v-col cols="12" md="4" sm="6">
<p class='heading'><b>Direction Two:</b></p>
</v-col>
<v-col cols="12" md="8" sm="6">
<p>{{ item.direction_two }} </p>
</v-col>
</v-row>
<v-row class='mt-1'>
<v-col cols="12" md="4" sm="6">
<p class='heading'><b>Refill Quantity:</b></p>
</v-col>
<v-col cols="12" md="8" sm="6">
<p>{{ item.refill_quantity }}</p>
</v-col>
</v-row>
<v-row class='mt-1'>
<v-col cols="12" md="4" sm="6">
<p class='heading'><b>Status:</b></p>
</v-col>
<v-col cols="12" md="8" sm="6">
<p v-if="item.status == null" class="text-warning">Pending</p>
<p v-else>{{ item.status }}</p>
</v-col>
</v-row>
<v-row class='mt-1'>
<v-col cols="12" md="4" sm="6">
<p class='heading'><b>Comments:</b></p>
</v-col>
<v-col cols="12" md="8" sm="8">
<p>{{ item.comments }} </p>
</v-col>
</v-row>
</VExpansionPanelText>
</div>
</VExpansionPanel>
<br />
</VExpansionPanels>
</template>
<template v-else="prescriptionLoaded">
<VCard>
<VCard>
<VAlert border="start" color="rgb(var(--v-theme-yellow-theme-button))" variant="tonal">
<div class="text-center">No data found</div>
</VAlert>
</VCard>
</VCard>
</template>
</template>
<style lang="scss">
button.v-expansion-panel-title {
background-color: rgb(var(--v-theme-yellow)) !important;
color: #fff;
}
button.v-expansion-panel-title.bg-secondary {
background-color: rgb(var(--v-theme-yellow)) !important;
color: #fff;
}
button.v-expansion-panel-title {
background-color: rgb(var(--v-theme-yellow)) !important;
color: #fff;
}
span.v-expansion-panel-title__icon {
margin-left: 0px !important;
}
span.v-expansion-panel-title__icon {
color: #fff
}
.v-expansion-panel {
background-color: #fff;
border-radius: 16px;
box-shadow: 0px 4px 12px rgba(0, 0, 0, 0.1);
margin-bottom: 10px;
overflow: hidden;
transition: box-shadow 0.3s ease;
}
</style>

View File

@@ -0,0 +1,69 @@
<script setup>
import About from "@/pages/provider/patient-profile/About.vue";
import UserProfileHeader from "@/pages/provider/patient-profile/UserProfileHeader.vue";
import patienProfilePrecrption from "@/pages/provider/patient-profile/patienProfilePrecrption.vue";
import { onMounted, ref } from "vue";
import { useStore } from "vuex";
const route = useRoute();
const store = useStore();
const profileDataPatient = ref(null)
onMounted(async () => {
store.dispatch("updateIsLoading", true);
await store.dispatch("OrderPatientProfile", {
id: route.params.id,
});
profileDataPatient.value = store.getters.getOrderPatientProfile;
console.log('profileDataPatient', profileDataPatient.value);
store.dispatch("updateIsLoading", false);
});
const userTab = ref(null);
const tabs = [
{
icon: "ri-user-line",
title: "Profile",
},
{
icon: "mdi-prescription",
title: "Prescriptions",
},
];
</script>
<template>
<VDialog v-model="store.getters.getIsLoading" width="110" height="150" color="primary">
<VCardText class="" style="color: white !important;">
<div class="demo-space-x">
<VProgressCircular :size="40" color="primary" indeterminate />
</div>
</VCardText>
</VDialog>
<div v-if="profileDataPatient">
<UserProfileHeader class="mb-5" :user-data="profileDataPatient.patient_details" style="margin-bottom: 10px;" />
<VTabs v-model="userTab" class="v-tabs-pill mt-5">
<VTab v-for="tab in tabs" :key="tab.icon">
<VIcon start :icon="tab.icon" />
<span>{{ tab.title }}</span>
</VTab>
</VTabs>
<VWindow v-model="userTab" class="mt-6 disable-tab-transition" :touch="false">
<VWindowItem>
<About :user-data="profileDataPatient" />
</VWindowItem>
<!-- <VWindowItem>
<patientProfileNotes :order-data="profileDataPatient" />
</VWindowItem>-->
<VWindowItem>
<patienProfilePrecrption :order-data="profileDataPatient" />
</VWindowItem>
</VWindow>
</div>
</template>

View File

@@ -0,0 +1,128 @@
<script setup>
import store from '@/store';
import { useRoute, useRouter } from 'vue-router';
const router = useRouter()
const route = useRoute()
const props = defineProps({
orderData: {
type: Object,
required: true,
},
})
const notes = ref([]);
const historyNotes = computed(async () => {
let notesData = props.orderData.notes_history;
for (let data of notesData) {
if (data.note_type == 'Notes') {
let dataObject = {}
dataObject.note = data.note
dataObject.doctor = data.provider_name;
dataObject.date = formatDateDate(data.note_date)
dataObject.appointment_id = data.appointment_id
dataObject.originalDate = new Date(data.note_date)
//notes.value.push(dataObject)
}
}
notes.value.sort((a, b) => {
return b.id - a.id;
});
store.dispatch('updateIsLoading', false)
return notes.value
});
const formatDateDate = (date) => {
const messageDate = new Date(date);
const options = {
year: 'numeric',
month: 'long',
day: 'numeric',
hour: '2-digit',
minute: '2-digit'
};
return messageDate.toLocaleDateString('en-US', options).replace(/\//g, '-');
};
onMounted(async () => {
let notesData = props.orderData.notes_history;
console.log("notesData", notesData);
for (let data of notesData) {
let dataObject = {}
dataObject.note = data.note
dataObject.doctor = data.provider_name;
dataObject.date = formatDateDate(data.note_date)
dataObject.appointment_id = data.appointment_id
dataObject.originalDate = new Date(data.note_date)
notes.value.push(dataObject)
}
notes.value.sort((a, b) => {
return b.originalDate - a.originalDate;
});
console.log("getNotes", notes.value);
});
</script>
<template>
<VDialog v-model="store.getters.getIsLoading" width="110" height="150" color="primary">
<VCardText class="" style="color: white !important;">
<div class="demo-space-x">
<VProgressCircular :size="40" color="primary" indeterminate />
</div>
</VCardText>
</VDialog>
<VCard title="Notes" v-if="notes.length > 0">
<VCardText>
<VTimeline truncate-line="both" align="start" side="end" line-inset="10" line-color="primary"
density="compact" class="v-timeline-density-compact">
<template v-if="historyNotes">
<VTimelineItem dot-color="yellow" size="x-small" v-for="(p_note, index) of notes" :key="index">
<div class="d-flex justify-space-between align-center mb-3">
<span class="app-timeline-title">{{ p_note.note }}</span>
<span class="app-timeline-meta">{{ p_note.date }}</span>
</div>
<p class="app-timeline-text mb-0">
{{ p_note.doctor }}
</p>
</VTimelineItem>
</template>
</VTimeline>
</VCardText>
</VCard>
<VCard v-else>
<VAlert border="start" color="rgb(var(--v-theme-yellow-theme-button))" variant="tonal">
<div class="text-center">No data found</div>
</VAlert>
</VCard>
<!-- <VList class="pb-0" lines="two" v-if="historyNotes">
<template v-if="notes.length > 0" v-for="(p_note, index) of notes" :key="index">
<VListItem class="pb-0" border>
<VListItemTitle>
<span class="pb-0">{{ p_note.note }}</span>
<p class="text-start fs-5 mb-0 pb-0 text-grey">
<small> {{ p_note.doctor }}</small>
</p>
<p class="text-end fs-5 mb-0 pb-0 text-grey">
<small> {{ p_note.date }}</small>
</p>
</VListItemTitle>
</VListItem>
<VDivider v-if="index !== notes.length - 1" />
</template>
<template v-else>
<VCard>
<VAlert border="start" color="rgb(var(--v-theme-yellow))" variant="tonal">
<div class="text-center">No data found</div>
</VAlert>
</VCard>
</template>
</VList> -->
</template>

View File

@@ -0,0 +1,250 @@
<script setup>
import store from '@/store';
import { useRoute, useRouter } from 'vue-router';
const router = useRouter()
const route = useRoute()
const itemsPrescriptions = ref([]);
const patientId = route.params.patient_id;
const appointmentId = route.params.id;
const prescription = computed(async () => {
console.log('computed=====')
await getprescriptionList()
// await store.dispatch('getHistoryPrescription')
// notes.value = store.getters.getHistoryPrescription;
// console.log("getHistoryPrescription", notes.value);
// store.dispatch('updateIsLoading', false)
});
const getprescriptionList = async () => {
await store.dispatch('getPrescriptions', {
patient_id: patientId,
appointment_id: appointmentId,
})
let prescriptions = store.getters.getPrescriptionList
console.log("BeforeOverviewItem", prescriptions);
// itemsPrescriptions.value = store.getters.getPrescriptionList
for (let data of prescriptions) {
let dataObject = {}
dataObject.brand = data.brand
dataObject.direction_one = data.direction_one
dataObject.direction_quantity = data.direction_quantity
dataObject.direction_two = data.direction_two
dataObject.date = formatDateDate(data.date)
dataObject.dosage = data.dosage
dataObject.from = data.from
dataObject.name = data.name
dataObject.quantity = data.quantity
dataObject.doctor = data.doctor.name
dataObject.refill_quantity = data.refill_quantity
dataObject.status = data.status
dataObject.comments = data.comments
itemsPrescriptions.value.push(dataObject)
}
itemsPrescriptions.value.sort((a, b) => {
return b.id - a.id;
});
console.log("OverviewItem", itemsPrescriptions.value);
};
const formatDateDate = (date) => {
const messageDate = new Date(date);
const options = {
year: 'numeric',
month: 'long',
day: 'numeric',
hour: '2-digit',
minute: '2-digit'
};
return messageDate.toLocaleDateString('en-US', options).replace(/\//g, '-');
};
const getStatusColor = (status) => {
switch (status) {
case 'pending':
return 'warning'; // Use Vuetify's warning color (typically yellow)
case 'shipped':
return '#45B8AC'; // Use Vuetify's primary color (typically blue)
case 'delivered':
return 'green';
case 'returned':
return 'red';
case 'results':
return 'blue';
default:
return 'grey'; // Use Vuetify's grey color for any other status
}
};
</script>
<template>
<VDialog v-model="store.getters.getIsLoading" width="110" height="150" color="primary">
<VCardText class="" style="color: white !important;">
<div class="demo-space-x">
<VProgressCircular :size="40" color="primary" indeterminate />
</div>
</VCardText>
</VDialog>
<template v-if="itemsPrescriptions">
<VExpansionPanels variant="accordion" v-if="prescription">
<VExpansionPanel v-for="item in itemsPrescriptions" :key="item">
<VExpansionPanelTitle collapse-icon="mdi-chevron-down" expand-icon="mdi-chevron-right">
<p class=""><b> {{ item.name }}</b>
<br />
<div class=" pt-2">#{{ appointmentId }} By {{ item.doctor }}</div>
<div class=" pt-2">{{ item.date }}</div>
</p>
<v-row>
</v-row>
<span class="v-expansion-panel-title__icon badge text-warning"
v-if="item.status == null">Pending</span>
<span class="v-expansion-panel-title__icon badge" v-else>
<v-chip :color="getStatusColor(item.status)" label size="small" variant="text">
{{ item.status }}
</v-chip></span>
</VExpansionPanelTitle>
<VExpansionPanelText class="pt-0">
<v-row class='mt-1'>
<v-col cols="12" md="4" sm="6">
<p class='heading'><b>Brand:</b></p>
</v-col>
<v-col cols="12" md="4" sm="6">
<p>{{ item.brand }}</p>
</v-col>
</v-row>
<v-row class='mt-1'>
<v-col cols="12" md="4" sm="6">
<p class='heading'><b>From:</b></p>
</v-col>
<v-col cols="12" md="4" sm="6">
<p>{{ item.from }}</p>
</v-col>
</v-row>
<v-row class='mt-1'>
<v-col cols="12" md="4" sm="6">
<p class='heading'><b>Dosage:</b></p>
</v-col>
<v-col cols="12" md="4" sm="6">
<p>{{ item.dosage }}</p>
</v-col>
</v-row>
<v-row class='mt-1'>
<v-col cols="12" md="4" sm="6">
<p class='heading'><b>Quantity:</b></p>
</v-col>
<v-col cols="12" md="4" sm="6">
<p>{{ item.quantity }}</p>
</v-col>
</v-row>
<v-row class='mt-1'>
<v-col cols="12" md="4" sm="6">
<p class='heading'><b>Direction Quantity:</b></p>
</v-col>
<v-col cols="12" md="8" sm="6">
<p>{{ item.direction_quantity }}</p>
</v-col>
</v-row>
<v-row class='mt-1'>
<v-col cols="12" md="4" sm="6">
<p class='heading'><b>Direction One:</b></p>
</v-col>
<v-col cols="12" md="8" sm="6">
<p>{{ item.direction_one }} </p>
</v-col>
</v-row>
<v-row class='mt-1'>
<v-col cols="12" md="4" sm="6">
<p class='heading'><b>Direction Two:</b></p>
</v-col>
<v-col cols="12" md="8" sm="6">
<p>{{ item.direction_two }} </p>
</v-col>
</v-row>
<v-row class='mt-1'>
<v-col cols="12" md="4" sm="6">
<p class='heading'><b>Refill Quantity:</b></p>
</v-col>
<v-col cols="12" md="8" sm="6">
<p>{{ item.refill_quantity }}</p>
</v-col>
</v-row>
<v-row class='mt-1'>
<v-col cols="12" md="4" sm="6">
<p class='heading'><b>Status:</b></p>
</v-col>
<v-col cols="12" md="8" sm="6">
<p v-if="item.status == null" class="text-warning">Pending</p>
<p v-else>{{ item.status }}</p>
</v-col>
</v-row>
<v-row class='mt-1'>
<v-col cols="12" md="4" sm="6">
<p class='heading'><b>Comments:</b></p>
</v-col>
<v-col cols="12" md="8" sm="8">
<p>{{ item.comments }} </p>
</v-col>
</v-row>
</VExpansionPanelText>
</VExpansionPanel>
</VExpansionPanels>
</template>
<template v-else>
<VCard>
<VAlert border="start" color="rgb(var(--v-theme-yellow))" variant="tonal">
<div class="text-center">No data found</div>
</VAlert>
</VCard>
</template>
</template>
<style lang="scss">
button.v-expansion-panel-title {
background-color: rgb(var(--v-theme-yellow)) !important;
color: #fff;
}
button.v-expansion-panel-title.bg-secondary {
background-color: rgb(var(--v-theme-yellow)) !important;
color: #fff;
}
button.v-expansion-panel-title {
background-color: rgb(var(--v-theme-yellow)) !important;
color: #fff;
}
span.v-expansion-panel-title__icon {
color: #fff
}
// button.v-expansion-panel-title.bg-secondary {
// background-color: rgba(var(--v-theme-primary), var(--v-activated-opacity)) !important;
// }
// button.v-expansion-panel-title {
// background-color: rgba(var(--v-theme-primary), var(--v-activated-opacity)) !important;
// }
// button.v-expansion-panel-title.v-expansion-panel-title--active {
// background-color: rgba(var(--v-theme-primary), var(--v-activated-opacity)) !important;
// }
.v-expansion-panel {
background-color: #fff;
border-radius: 16px;
box-shadow: 0px 4px 12px rgba(0, 0, 0, 0.1);
margin-bottom: 10px;
overflow: hidden;
transition: box-shadow 0.3s ease;
}
</style>