initial commit
This commit is contained in:
113
resources/js/pages/provider/LogisticsCardStatistics.vue
Normal file
113
resources/js/pages/provider/LogisticsCardStatistics.vue
Normal 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>
|
315
resources/js/pages/provider/MeetingsDashboard.vue
Normal file
315
resources/js/pages/provider/MeetingsDashboard.vue
Normal 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>
|
126
resources/js/pages/provider/OrderDetailNotes.vue
Normal file
126
resources/js/pages/provider/OrderDetailNotes.vue
Normal 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>
|
358
resources/js/pages/provider/OrderDetailPrecrption.vue
Normal file
358
resources/js/pages/provider/OrderDetailPrecrption.vue
Normal 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>
|
444
resources/js/pages/provider/chat.vue
Normal file
444
resources/js/pages/provider/chat.vue
Normal 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>
|
253
resources/js/pages/provider/dashboard.vue
Normal file
253
resources/js/pages/provider/dashboard.vue
Normal 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>
|
521
resources/js/pages/provider/detail-history.vue
Normal file
521
resources/js/pages/provider/detail-history.vue
Normal 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>
|
85
resources/js/pages/provider/main-orders-detail-tabs.vue
Normal file
85
resources/js/pages/provider/main-orders-detail-tabs.vue
Normal 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>
|
235
resources/js/pages/provider/meeting-history.vue
Normal file
235
resources/js/pages/provider/meeting-history.vue
Normal 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>
|
95
resources/js/pages/provider/notes.vue
Normal file
95
resources/js/pages/provider/notes.vue
Normal 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>
|
817
resources/js/pages/provider/orders-detail.vue
Normal file
817
resources/js/pages/provider/orders-detail.vue
Normal 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>
|
602
resources/js/pages/provider/orders-list-new.vue
Normal file
602
resources/js/pages/provider/orders-list-new.vue
Normal 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>
|
552
resources/js/pages/provider/orders-list.vue
Normal file
552
resources/js/pages/provider/orders-list.vue
Normal 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>
|
133
resources/js/pages/provider/patient-profile/About.vue
Normal file
133
resources/js/pages/provider/patient-profile/About.vue
Normal 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>
|
@@ -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>
|
@@ -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>
|
@@ -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>
|
@@ -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>
|
250
resources/js/pages/provider/prescription.vue
Normal file
250
resources/js/pages/provider/prescription.vue
Normal 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>
|
Reference in New Issue
Block a user