initial commit

This commit is contained in:
Inshal
2024-10-25 01:02:11 +05:00
commit 6e65bc3a62
1710 changed files with 273609 additions and 0 deletions

View File

@@ -0,0 +1,442 @@
<script setup>
import QueueComponent from '@/layouts/components/QueueComponent.vue';
import { useRouter } from 'vue-router';
import { useTheme } from 'vuetify';
import { useStore } from 'vuex';
const router = useRouter()
const store = useStore()
const isMobile = ref(window.innerWidth <= 768);
const prescriptionModelForm = ref(false)
const search = ref('');
const loading = ref(true);
const page = ref(1);
const itemsPerPage = ref(10);
const pageCount = ref(0);
const patientNotes = ref([])
const headers = [
{ key: 'note', title: 'Notes' },
{ key: 'date', title: 'Date' },
];
// Components
import { computed } from 'vue';
const vuetifyTheme = useTheme()
const userRole = localStorage.getItem('user_role'); // Fetch user role from local storage
const isAgent = computed(() => userRole.toLowerCase() === 'agent');
const queueComp = ref(null);
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 historyNotes = computed(async () => {
console.log('update')
await store.dispatch('getHistoryPatientNotes', {
patient_id: localStorage.getItem('patient_id'),
appointment_id: localStorage.getItem('patient_appiontment_id'),
})
let notes = store.getters.getPatientNotes
for (let data of notes) {
if (data.note_type == 'Notes') {
let dataObject = {}
dataObject.file_url = data.file_url
dataObject.note = data.note
dataObject.date = formatDateDate(data.created_at)
dataObject.id = data.id
dataObject.agent = data.telemedPro.name
patientNotes.value.push(dataObject)
}
}
patientNotes.value.sort((a, b) => {
return b.id - a.id;
});
console.log('patientNotes', patientNotes.value)
store.dispatch('updateIsLoading', false)
});
onMounted(async () => {
const navbar = document.querySelector('.layout-navbar');
const callDiv = document.querySelector('.layout-page-content');
if (navbar) {
navbar.style.display = 'block';
}
if (callDiv)
callDiv.style.padding = '1.5rem';
console.log("router", store.getters.getCurrentPage)
// if (userRole === 'agent') {
// await store.dispatch('getHistoryPatientNotes')
// let notes = store.getters.getPatientNotes
// for(let data of notes ){
// if(data.note_type=='Notes'){
// let dataObject = {}
// dataObject.note=data.note
// dataObject.date=formatDateDate(data.created_at)
// dataObject.id=data.id
// patientNotes.value.push(dataObject)
// }
// }
// patientNotes.value.sort((a, b) => {
// return b.id - a.id;
// });
// console.log('patientNotes', patientNotes.value)
// loading.value = false;
// }
window.addEventListener('resize', checkMobile);
});
const checkMobile = () => {
isMobile.value = window.innerWidth <= 768;
};
const openNotes = async () => {
}
const openCallModel = async (type) => {
console.log('here')
if (queueComp.value) {
console.log(queueComp.value)
let Calluser = localStorage.getItem('call_user')
// console.log('Calluser', Calluser)
queueComp.value.openDialog(JSON.parse(Calluser), type);
}
};
const handleFormSubmitted = (updatData) => {
console.log('updatData', updatData)
for (let data of updatData.notes) {
if (data.note_type == 'Notes') {
let foundPatient = patientNotes.value.find(patient => patient.id === data.id);
if (!foundPatient) {
let dataObject = {}
dataObject.note = data.note
dataObject.file_url = data.file_url
dataObject.date = formatDateDate(data.created_at)
dataObject.id = data.id
dataObject.agent = data.telemedPro.name
patientNotes.value.push(dataObject)
}
}
}
patientNotes.value.sort((a, b) => {
return b.id - a.id;
});
};
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>
<QueueComponent ref="queueComp" :isVisable="false" @formSubmitted="handleFormSubmitted" />
<v-row class='mb-2'>
<v-col cols="12" class="text-left " md="6">
<v-btn color="primary" size="small" @click="openCallModel('Notes')" class="mr-2">
Add Notes
</v-btn>
<!-- <v-btn color="primary" small @click="openCallModel('Request For Documents')">
<v-icon>mdi-plus</v-icon> Request For Docs
</v-btn> -->
</v-col>
<v-col cols="12" class="text-right cross" md="6">
</v-col>
</v-row>
<VList class="pb-0" lines="two" border v-if="historyNotes" style="border: 0px;">
<template v-if="patientNotes.length > 0" v-for="(p_note, index) of patientNotes" :key="index">
<VListItem class="pb-0">
<VListItemTitle>
<span class="pb-0">{{ p_note.note }}</span>
<p class="fs-5 mb-0 pb-0 text-grey"><small class="float-left">{{ p_note.agent }}</small>
<small class="float-right">
<span v-if="p_note.file_url" style="font-size: 12px;color:#696cff;top: 0px;
position: relative;" color="primary">
<a type="button" @click="downloadFile(p_note.file_url)" title="Download Attachment">
<VIcon>mdi-cloud-download</VIcon>
</a>
</span> <span v-if="p_note.file_url"> | </span> {{ p_note.date }}
</small>
</p>
</VListItemTitle>
</VListItem>
<VDivider v-if="index !== patientNotes.length - 1" />
</template>
<template v-else>
<p class="text-center pt-0 pb-0 mb-0"> No data found</p>
</template>
</VList>
</template>
<style lang="scss" scoped>
.v-list-item-title {
white-space: unset;
}
.hidden-component {
display: none
}
.meta-key {
border: thin solid rgba(var(--v-border-color), var(--v-border-opacity));
border-radius: 6px;
block-size: 1.5625rem;
line-height: 1.3125rem;
padding-block: 0.125rem;
padding-inline: 0.25rem;
}
::v-deep .custom-menu {
position: relative;
}
::v-deep .custom-menu::before {
content: "" !important;
position: absolute !important;
transform: translateY(-50%);
top: 50% !important;
left: -8px !important;
border-left: 8px solid transparent !important;
border-right: 8px solid transparent !important;
border-bottom: 8px solid #fff !important;
}
// Styles for the VList component
.more .v-list-item-title {
color: rgb(106 109 255);
}
.more .menu-item:hover {
cursor: pointer;
}
.slide-enter-active,
.slide-leave-active {
transition: transform 0.3s ease;
}
.slide-enter,
.slide-leave-to {
transform: translateX(-100%);
}
.start-call-btn {
opacity: 0;
display: none;
transition: opacity 0.3s ease;
}
.button_margin {
margin: 2px;
}
.dialog_padding {
padding: 5px;
}
.custom-menu .v-menu__content {
background-color: #333;
color: #fff;
border-radius: 4px;
padding: 8px 0;
}
.user-info {
display: flex;
flex-direction: column;
transition: opacity 0.3s ease;
}
.list-item-hover {
transition: background-color 0.3s ease;
&:hover {
background-color: rgba(var(--v-theme-primary), 0.1);
.start-call-btn {
opacity: 1;
display: block;
position: relative;
left: -35px;
}
.user-info {
opacity: 0;
display: none;
}
}
}
.pop_card {
overflow: hidden !important;
padding: 10px;
}
.v-overlay__content {
max-height: 706.4px;
max-width: 941.6px;
min-width: 24px;
--v-overlay-anchor-origin: bottom left;
transform-origin: left top;
top: 154.4px !important;
left: 204px !important;
}
.button_margin {
margin-top: 10px;
font-size: 10px;
}
/* Responsive Styles */
@media screen and (max-width: 768px) {
.pop_card {
max-width: 100%;
margin: 0 auto;
}
}
.container_img {
display: flex;
flex-direction: column;
width: 100%;
align-items: center;
}
.image {
order: 2;
/* Change the order to 2 in mobile view */
}
.text {
order: 1;
/* Change the order to 1 in mobile view */
}
/* Media query for mobile view */
@media (max-width: 768px) {
.container_img {
flex-direction: row;
margin-top: 10px;
}
.button_margin_mobile {
width: 100%;
}
.image {
width: 20%;
padding: 0 10px;
}
.text {
width: 80%;
/* Each takes 50% width */
padding: 0 10px;
/* Optional padding */
}
}
::-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 */
}
/* Container for the content */
.scroll-container {
max-height: 191px;
/* Maximum height of the scrollable content */
overflow-y: scroll;
/* Enable vertical scrolling */
}
/* Content within the scroll container */
.scroll-content {
padding: 20px;
}
/* Example of additional styling for content */
.scroll-content p {
margin-bottom: 20px;
}
.cross button {
padding: 0px;
margin: 0px;
/* font-size: 10px; */
background: none;
border: none;
box-shadow: none;
height: 23px;
}
.v-data-table-header {
display: table-header-group;
}
</style>

View File

@@ -0,0 +1,548 @@
<script setup>
import Appiontment from '@/pages/patientAppiontmentDetail.vue';
import profile from '@/pages/patientInfo.vue';
import { useRouter } from 'vue-router';
import { useStore } from 'vuex';
import questionsJson from '../views/pages/questionere/questions_parse.json';
const router = useRouter()
const store = useStore()
const questionsJsonData = ref(questionsJson)
const answers = ref(null)
const QuestionsAnswers = ref([]);
const username = ref(null);
const email = ref(null);
const phone = ref(null);
const address1 = ref(null);
const dob = ref(null);
const agePatient = ref(null);
const isMobile = ref(window.innerWidth <= 768);
const currentTab = ref(0)
const tabItemContent = 'Candy canes donut chupa chups candy canes lemon drops oat cake wafer. Cotton candy candy canes marzipan carrot cake. Sesame snaps lemon drops candy marzipan donut brownie tootsie roll. Icing croissant bonbon biscuit gummi bears. Pudding candy canes sugar plum cookie chocolate cake powder croissant.'
onMounted(async () => {
const navbar = document.querySelector('.layout-navbar');
const callDiv = document.querySelector('.layout-page-content');
if (navbar) {
navbar.style.display = 'block';
}
if (callDiv)
callDiv.style.padding = '1.5rem';
store.dispatch('updateIsLoading', true)
await store.dispatch('getPatientInfoByID')
await store.dispatch('getAgentQuestionsAnswers')
username.value = store.getters.getPatient.first_name + ' ' + store.getters.getPatient.last_name;
email.value = store.getters.getPatient.email
phone.value = store.getters.getPatient.phone_no
dob.value = changeFormat(store.getters.getPatient.dob)
agePatient.value = calculateAge(store.getters.getPatient.dob) + " Year"
address1.value = store.getters.getPatient.address + ' ' +
store.getters.getPatient.city + ' ' +
store.getters.getPatient.state + ' ' +
store.getters.getPatient.country;
// address.value = patient_address;
answers.value = store.getters.getPatientAnswers
// console.log('questionsJsonData', questionsJsonData.value)
// console.log('API Answers', answers.value)
createFinalArray();
store.dispatch('updateIsLoading', false)
window.addEventListener('resize', checkMobile);
});
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) {
// 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 createFinalArray = () => {
questionsJsonData.value.forEach(question => {
const { label, key, type } = question;
if (answers.value.hasOwnProperty(key)) {
QuestionsAnswers.value.push({
label,
key,
type,
value: answers.value[key]
});
}
});
// console.log('------finalArray ', QuestionsAnswers.value)
};
const checkMobile = () => {
isMobile.value = window.innerWidth <= 768;
};
const calculateAge = (dateOfBirth) => {
const today = new Date();
const birthDate = new Date(dateOfBirth);
let age = today.getFullYear() - birthDate.getFullYear();
const monthDiff = today.getMonth() - birthDate.getMonth();
if (
monthDiff < 0 ||
(monthDiff === 0 && today.getDate() < birthDate.getDate())
) {
age--;
}
return age;
}
</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>
<VTabs v-model="currentTab" class="v-tabs-pill">
<VTab>Profile</VTab>
<VTab>Appiontment</VTab>
<!-- <VTab>Tab Three</VTab> -->
</VTabs>
<VCard class="mt-5">
<VCardText>
<VWindow v-model="currentTab">
<VWindowItem>
<profile />
</VWindowItem>
<VWindowItem>
<Appiontment />
</VWindowItem>
</VWindow>
</VCardText>
</VCard>
<!-- <v-row class='mb-2'>
<VCol cols="12" md="12" class="mb-4 ml-5" v-if="username || phone" style="font-size: 16px;">
<div>
<h3 class="mb-2"> {{ username }}</h3>
<div class="mb-1">
<VTooltip location="left" activator="parent" transition="scroll-x-transition">
Email
</VTooltip>
<VIcon icon="mdi-email" size="20" class="me-2" />{{ email }}
</div>
<div class="mb-2">
<VTooltip location="left" activator="parent" transition="scroll-x-transition">
Age
</VTooltip>
<VIcon icon="mdi-calendar-account" title="Age" size="20" class="me-2" />{{ agePatient }}
</div>
<div class="mb-2">
<VTooltip location="left" activator="parent" transition="scroll-x-transition">
Date of birth
</VTooltip>
<VIcon icon="mdi-calendar" size="20" class="me-2" />{{ dob }}
</div>
<div class="mb-2">
<VTooltip location="left" activator="parent" transition="scroll-x-transition">
Address
</VTooltip>
<VIcon icon="mdi-map-marker" size="20" class="me-2" />{{ address1 }}
</div>
<div>
<VTooltip location="left" activator="parent" transition="scroll-x-transition">
Contact Number
</VTooltip>
<VIcon icon="mdi-phone" size="20" class="me-2" />{{ phone }}
</div>
</div>
</VCol>
<v-col cols="12" md="12">
<VList class="">
<VListItem class="">
<VListItemTitle class="d-flex align-center justify-space-between bg-dark mt-1"
style="padding: 5px;">
<div :class="isMobile ? '' : 'w-40'">
<p class="mb-0" :class="isMobile ? 'heading-text-m' : 'heading-text-d'"><b>SYMPTOM
CHECKLIST</b></p>
</div>
<div class="d-flex align-center" :class="isMobile ? 'heading-text-m' : 'heading-text-d'">
<p class="mb-0"><b>MILD</b>
</p>
</div>
<div class="d-flex align-center" :class="isMobile ? 'heading-text-m' : 'heading-text-d'">
<p class="mb-0"><b>MODERATE</b>
</p>
</div>
<div class="d-flex align-center" :class="isMobile ? 'heading-text-m' : 'heading-text-d'">
<p class="mb-0"><b>SEVERE</b>
</p>
</div>
</VListItemTitle>
</VListItem>
<VListItem class="pt-0 pb-0 ht-li" v-for="(item, index) of QuestionsAnswers" :key="index">
<VListItemTitle class="d-flex align-center justify-space-between">
<div class="border-right"
:class="{ 'bg-silver': (index + 1) % 2 === 0, 'w-custom-m': isMobile, 'w-40': !isMobile }"
style="padding-left: 5px;">
<p class="text-wrap mb-0" :class="isMobile ? 'heading-text-m' : 'heading-text-d'">{{
item.label
}}</p>
</div>
<div class="d-flex align-center" :class="isMobile ? 'w-custom-d' : 'w-60'">
<QuestionProgressBar :type="item.type" :value="item.value"></QuestionProgressBar>
</div>
</VListItemTitle>
</VListItem>
</VList>
<v-table density="compact" fixed-header>
<thead>
<tr class="text-right">
<th class="bg-dark">
<b>SYMPTOM CHECKLIST</b>
</th>
<th class="text-right bg-dark">
<b>MILD</b>
<VIcon icon="mdi-menu-down"></VIcon>
</th>
<th class="text-right bg-dark">
<b>MODERATE</b>
<VIcon icon="mdi-menu-down"></VIcon>
</th>
<th class="text-right bg-dark">
<b>SEVERE</b>
<VIcon icon="mdi-menu-down"></VIcon>
</th>
</tr>
</thead>
<tbody>
<tr v-for="(item, index) of QuestionsAnswers" :key="index">
<td class="border-right" v-if="!isMobile">{{ item.label }}</td>
<td :colspan="isMobile ? '4' : '3'">
<p v-if="isMobile" class="mb-0">{{ item.label }}</p>
<QuestionProgressBar :type="item.type" :value="item.value"></QuestionProgressBar>
</td>
</tr>
</tbody>
</v-table>
</v-col>
</v-row> -->
</template>
<style lang="scss" scoped>
.mdi-email {
margin-bottom: 5px;
}
.ht-li {
min-height: 20px !important;
}
.bg-silver {
background-color: silver;
}
.text-wrap {
text-wrap: balance;
}
.w-40 {
width: 40%;
}
.w-custom-m {
width: 36%;
}
.w-60 {
width: 60%;
}
.w-custom-d {
width: 65%;
}
.v-list-item--density-default.v-list-item--one-line {
min-height: 40px;
}
.heading-text-m {
font-size: 9px;
}
.heading-text-d {
font-size: 12px;
}
.bg-dark {
background-color: #808080b3 !important;
}
.text-right {
text-align: right !important;
width: 23%;
}
.border-right {
border-right: 1.5px solid black;
}
.hidden-component {
display: none
}
.meta-key {
border: thin solid rgba(var(--v-border-color), var(--v-border-opacity));
border-radius: 6px;
block-size: 1.5625rem;
line-height: 1.3125rem;
padding-block: 0.125rem;
padding-inline: 0.25rem;
}
::v-deep .custom-menu {
position: relative;
}
::v-deep .custom-menu::before {
content: "" !important;
position: absolute !important;
transform: translateY(-50%);
top: 50% !important;
left: -8px !important;
border-left: 8px solid transparent !important;
border-right: 8px solid transparent !important;
border-bottom: 8px solid #fff !important;
}
// Styles for the VList component
.more .v-list-item-title {
color: rgb(106 109 255);
}
.more .menu-item:hover {
cursor: pointer;
}
.slide-enter-active,
.slide-leave-active {
transition: transform 0.3s ease;
}
.slide-enter,
.slide-leave-to {
transform: translateX(-100%);
}
.start-call-btn {
opacity: 0;
display: none;
transition: opacity 0.3s ease;
}
.button_margin {
margin: 2px;
}
.dialog_padding {
padding: 5px;
}
.custom-menu .v-menu__content {
background-color: #333;
color: #fff;
border-radius: 4px;
padding: 8px 0;
}
.user-info {
display: flex;
flex-direction: column;
transition: opacity 0.3s ease;
}
.list-item-hover {
transition: background-color 0.3s ease;
&:hover {
background-color: rgba(var(--v-theme-primary), 0.1);
.start-call-btn {
opacity: 1;
display: block;
position: relative;
left: -35px;
}
.user-info {
opacity: 0;
display: none;
}
}
}
.pop_card {
overflow: hidden !important;
padding: 10px;
}
.v-overlay__content {
max-height: 706.4px;
max-width: 941.6px;
min-width: 24px;
--v-overlay-anchor-origin: bottom left;
transform-origin: left top;
top: 154.4px !important;
left: 204px !important;
}
.button_margin {
margin-top: 10px;
font-size: 10px;
}
/* Responsive Styles */
@media screen and (max-width: 768px) {
.pop_card {
max-width: 100%;
margin: 0 auto;
}
}
.container_img {
display: flex;
flex-direction: column;
width: 100%;
align-items: center;
}
.image {
order: 2;
/* Change the order to 2 in mobile view */
}
.text {
order: 1;
/* Change the order to 1 in mobile view */
}
/* Media query for mobile view */
@media (max-width: 768px) {
.container_img {
flex-direction: row;
margin-top: 10px;
}
.button_margin_mobile {
width: 100%;
}
.image {
width: 20%;
padding: 0 10px;
}
.text {
width: 80%;
/* Each takes 50% width */
padding: 0 10px;
/* Optional padding */
}
}
::-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 */
}
/* Container for the content */
.scroll-container {
max-height: 191px;
/* Maximum height of the scrollable content */
overflow-y: scroll;
/* Enable vertical scrolling */
}
/* Content within the scroll container */
.scroll-content {
padding: 20px;
}
/* Example of additional styling for content */
.scroll-content p {
margin-bottom: 20px;
}
.cross button {
padding: 0px;
margin: 0px;
/* font-size: 10px; */
background: none;
border: none;
box-shadow: none;
height: 23px;
}
.v-data-table-header {
display: table-header-group;
}
</style>

View File

@@ -0,0 +1,527 @@
<script setup>
import PatientPrescriptionDetail from '@/layouts/components/PatientPrescriptionDetail.vue';
import QueueComponent from '@/layouts/components/QueueComponent.vue';
import { computed } from 'vue';
import { useRouter } from 'vue-router';
import { useTheme } from 'vuetify';
import { useStore } from 'vuex';
const router = useRouter()
const store = useStore()
const isMobile = ref(window.innerWidth <= 768);
const prescriptionModelForm = ref(false)
const search = ref('');
const loading = ref(true);
const page = ref(1);
const itemsPerPage = ref(10);
const pageCount = ref(0);
const itemsPrescriptions = ref([]);
const show = ref(false)
const prescriptionDetail = ref(false);
const selectedItem = ref(null);
const PrescriptionData = ref('');
const isSelected = ref(false);
const isPrintable = ref(false);
// Components
const selectedRows = ref([]);
const vuetifyTheme = useTheme()
const userRole = localStorage.getItem('user_role'); // Fetch user role from local storage
const isAgent = computed(() => userRole.toLowerCase() === 'agent');
const queueComp = ref(null);
const headers = [
{ key: 'action', title: '', sortable: false },
{ key: 'name', title: 'Name' },
{ key: 'brand', title: 'Brand' },
{ key: 'from', title: 'From' },
{ key: 'direction_quantity', title: 'Direction Quantity' },
{ key: 'dosage', title: 'Dosage' },
{ key: 'quantity', title: 'Quantity' },
{ key: 'refill_quantity', title: 'Refill Quantity', align: 'center' },
{ key: 'status', title: 'status' },
];
onMounted(async () => {
const navbar = document.querySelector('.layout-navbar');
const callDiv = document.querySelector('.layout-page-content');
if (navbar) {
navbar.style.display = 'block';
}
if (callDiv)
callDiv.style.padding = '1.5rem';
console.log("router", store.getters.getCurrentPage)
if (userRole === 'agent') {
await getprescriptionList()
}
window.addEventListener('resize', checkMobile);
});
const checkMobile = () => {
isMobile.value = window.innerWidth <= 768;
};
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 '#34568B';
default:
return 'warning'; // Use Vuetify's grey color for any other status
}
};
const getprescriptionList = async () => {
let Calluser = localStorage.getItem('call_user')
let patient = JSON.parse(Calluser)
await store.dispatch('getPrescriptions', {
patient_id: patient.id,
appointment_id: patient.appointment.id
})
loading.value = false;
itemsPrescriptions.value = store.getters.getPrescriptionList
// await axios.post(`/agent/api/get-patient-prescriptions/${patient.id}/${patient.appointments.id}`, {}, {
// headers: {
// 'Authorization': `Bearer ${localStorage.getItem('access_token')}`,
// }
// }).then(response => {
// console.log('Response prescriptions:', response.data);
// for (let data of response.data) {
// let dataObject = {}
// dataObject.name = data.prescription.name
// dataObject.brand = data.prescription.brand
// dataObject.from = data.prescription.from
// dataObject.direction_quantity = data.prescription.direction_quantity
// dataObject.dosage = data.prescription.dosage
// dataObject.quantity = data.prescription.quantity
// dataObject.refill_quantity = data.prescription.refill_quantity
// dataObject.actions = ''
// dataObject.id = data.prescription.id
// dataObject.comments = data.comments
// dataObject.direction_one = data.direction_one
// dataObject.direction_two= data.direction_two
// itemsPrescriptions.value.push(dataObject)
// }
// //itemsPrescriptions.value = response.data
// //itemsPrescriptions.value.push(dataObject)
//
// })
// .catch(error => {
// loading.value = false;
// console.error('Error:', error);
// });
};
const formattedPrescription = computed(() => {
console.log('>>>>itemsPrescriptions.value<<<', itemsPrescriptions.value)
return itemsPrescriptions.value;
});
const updateStatus = (status) => {
console.log('status', status);
if (status == '' || status == null) {
return 'Pending';
}
}
const openCallModel = async (type) => {
console.log('here')
if (queueComp.value) {
console.log(queueComp.value)
let Calluser = localStorage.getItem('call_user')
console.log('Calluser', Calluser)
queueComp.value.openDialog(JSON.parse(Calluser), type);
}
};
computed(async () => {
console.log('computed=====')
await getprescriptionList()
});
const handleFormSubmitted = (updatData) => {
itemsPrescriptions.value = updatData.getPrescriptionList
}
const openPrint = async (item) => {
console.log('itemP', item);
selectedItem.value = item;
PrescriptionData.value = item;
prescriptionDetail.value = true;
// localStorage.setItem('printData', JSON.stringify(item))
// isSelected.value = true;
// printReecpt();
};
// const printReecpt = () => {
// if (isSelected.value == true) {
// prescriptionDetail.value = true;
// } else {
// isPrintable.value = true;
// }
// }
// const selectedRows = ref([]);
// Watcher to detect changes in selection
const handleRowSelection = (selectedRos) => {
console.log('Selected items:', selectedRos, selectedRows.value);
// Detect rows that were added to the selection
};
const options = ref({});
const handleOptionsUpdate = (newOptions) => {
options.value = newOptions;
if (options.value.selectedAll) {
selectedRows.value = formattedPrescription.value;
console.log('Selected items:', selectedRows.value)// Set all items as selected
} else {
selectedRows.value = []; // Clear selected rows
}
};
</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>
<QueueComponent ref="queueComp" :isVisable="false" @addPrescription="handleFormSubmitted" />
<v-dialog v-model="prescriptionDetail" max-width="600"
:class="isMobile ? 'dialog_padding_mobile' : 'dialog_padding'">
<v-card class="pa-3 printReceipt">
<v-row>
<v-col class="text-right cross pb-5 ">
<v-btn icon color="transparent" small
@click="[prescriptionDetail = false, isSelected = false, selectedItem = '']">
<v-icon>mdi-close</v-icon>
</v-btn>
</v-col>
</v-row>
<PatientPrescriptionDetail :itemProps="PrescriptionData"></PatientPrescriptionDetail>
</v-card>
</v-dialog>
<v-row class='mb-1'>
<v-col cols="12" class="text-left" md="6">
<v-btn color="primary" size="small" class="p-5" @click="openCallModel('Prescription')">
New Prescriptions
</v-btn>
</v-col>
<v-col cols="12" class="text-right cross mr-5 pr-2">
<!-- <Img :src="PrintImg" height="60" width="60" ></Img> -->
<!-- <router-link to="/print" target="_blank">
<VIcon icon="tabler-printer" color="primary" class="mr-3"></VIcon>
</router-link> -->
</v-col>
</v-row>
<template v-if="formattedPrescription">
<v-row>
<!-- <VSnackbar v-model="isPrintable" :timeout="5000" location="top end" variant="flat" color="red">
Please select an item in the list.
</VSnackbar> -->
<v-col cols="12" md="12" class="pt-0">
<v-data-table :item-value="item => item" :headers="headers" :items="formattedPrescription"
:search="search" :loading="loading" :items-per-page.sync="itemsPerPage"
@page-count="pageCount = $event">
<template v-slot:top>
<v-toolbar flat :height="30">
<v-toolbar-title>Prescriptions</v-toolbar-title>
<v-divider class="mx-4" inset vertical></v-divider>
<v-spacer></v-spacer>
<v-text-field v-model="search" label="Search" single-line hide-details></v-text-field>
</v-toolbar>
</template>
<template v-slot:item.status="{ item }">
<v-chip :color="getStatusColor(item.status)" label size="small" variant="text">
{{ item.status }}
</v-chip>
</template>
<template v-slot:item.action="{ item }">
<VCheckbox :value="item === selectedItem" @click="openPrint(item)">
</VCheckbox>
</template>
</v-data-table>
</v-col>
</v-row>
<!-- <VExpansionPanels variant="accordion">
<VExpansionPanel v-for="item in itemsPrescriptions" :key="item">
<VExpansionPanelTitle collapse-icon="mdi-chevron-down" expand-icon="mdi-chevron-right">
<div class="order-header d-flex align-center">
<div class="order-info">
<span class="status-icon mr-2">
</span>
<div>
<h3 class="flex-grow-1 mb-1">{{ item.name }}</h3>
<small class="text-grey mt-3 fs-5">by {{ item.doctor.name
}} </small>
<br>
<small class="text-grey mt-3 fs-5">{{ formatDateDate(item.date
) }} </small>
</div>
</div>
<div class="order-status">
<p class="text-end fs-5 mb-0 pb-0 text-grey">
<v-chip :color="getStatusColor(item.status)" label size="small" variant="text">
{{ item.status }}
</v-chip>
</p>
</div>
</div>
</VExpansionPanelTitle>
<VExpansionPanelText class="pt-0">
<v-row class='mt-1'>
<v-col cols="12" md="4">
<p><span class='heading'>Brand:</span>{{ item.brand }}</p>
</v-col>
<v-col cols="12" md="4">
<p><span class='heading'>From:</span> {{ item.from }}</p>
</v-col>
<v-col cols="12" md="4">
<p><span class='heading'>Dosage:</span> {{ item.dosage }}</p>
</v-col>
</v-row>
<v-row class='mt-1'>
<v-col cols="12" md="4">
<p><span class='heading'>Quantity:</span> {{ item.quantity }}</p>
</v-col>
<v-col cols="12" md="4">
<p><span class='heading'>Direction Quantity:</span> {{ item.direction_quantity }}</p>
</v-col>
<v-col cols="12" md="4">
<p><span class='heading'>Direction One:</span> {{ item.direction_one }}</p>
</v-col>
</v-row>
<v-row class='mt-1'>
<v-col cols="12" md="4">
<p><span class='heading'>Direction Two:</span> {{ item.direction_two }}</p>
</v-col>
<v-col cols="12" md="4">
<p><span class='heading'>Refill Quantity:</span> {{ item.refill_quantity }}</p>
</v-col>
</v-row>
<v-row class='mt-1'>
<v-col cols="12" md="12">
<p><span class='heading'>Comments:</span> {{ item.comments }}</p>
</v-col>
</v-row>
</VExpansionPanelText>
</VExpansionPanel>
</VExpansionPanels> -->
</template>
<template v-else>
<v-card>
<v-card-text>
<p class="text-center pt-0 pb-0 mb-0">No data found</p>
</v-card-text>
</v-card>
</template>
</template>
<style lang="scss" scoped>
.printReceipt {
overflow: hidden !important;
}
.status-color {
background-color: rgb(228 228 255);
// color: red;
padding: 2px 10px;
border-radius: 5px
}
.success-color {
background-color: rgb(224 245 219);
// color: red;
padding: 2px 10px;
border-radius: 5px
}
.heading {
font-size: 13px;
font-weight: 600;
}
.status-arrow {
font-weight: bold;
color: green;
/* or any other desired color */
}
.order-header {
width: 100%;
display: flex;
justify-content: space-between;
align-items: center;
}
.order-info {
display: flex;
align-items: center;
}
.status-icon {
margin-right: 10px;
}
h3 {
margin: 0;
flex-grow: 1;
}
.order-status {
text-align: right;
}
.status-text {
display: flex;
align-items: center;
}
.status-arrow {
margin-left: 5px;
}
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;
}
.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;
}
span.v-expansion-panel-title__icon {
color: #fff
}
.v-expansion-panel-title {
padding: 15px;
font-size: 13px;
font-weight: 600;
color: #333;
cursor: pointer;
display: flex;
justify-content: space-between;
align-items: center;
transition: background-color 0.3s ease, color 0.3s ease;
}
.v-expansion-panel-title.pt-0 {
padding-top: 0;
}
// .v-expansion-panel-title:hover {
// background-color: #f9f9f9;
// color: #222;
// }
.v-expansion-panel-title .mdi {
font-size: 20px;
color: #666;
transition: transform 0.3s ease, color 0.3s ease;
}
.v-expansion-panel-title.v-expansion-panel-title--active .mdi {
transform: rotate(180deg);
color: #222;
}
.cross {
padding: 0;
}
.v-expansion-panel-text {
padding: 10px 15px;
font-size: 13px;
color: #555;
border-top: 1px solid #eee;
}
.v-expansion-panel-text .d-flex {
margin-bottom: 24px;
}
.v-expansion-panel-text .d-flex>div {
flex: 1;
}
.v-expansion-panel-text p {
margin-bottom: 16px;
font-size: 14px;
}
.v-expansion-panel-text h3 {
font-size: 20px;
font-weight: 600;
margin-bottom: 16px;
color: #333;
}
</style>

View File

@@ -0,0 +1,34 @@
<script setup>
import ErrorHeader from '@/components/ErrorHeader.vue'
import misc404 from '@images/pages/404.png'
</script>
<template>
<div class="misc-wrapper">
<ErrorHeader
error-code="404"
error-title="Page Not Found "
error-description="We couldn't find the page you are looking for."
/>
<!-- 👉 Image -->
<div class="misc-avatar w-100 text-center">
<VImg
:src="misc404"
alt="Coming Soon"
:max-width="800"
class="mx-auto"
/>
<VBtn
to="/"
class="mt-10"
>
Back to Home
</VBtn>
</div>
</div>
</template>
<style lang="scss">
@use "@core-scss/template/pages/misc.scss";
</style>

View File

@@ -0,0 +1,63 @@
<script setup>
import ChangePassword from '@/views/pages/account-settings/ChangePassword.vue';
import DeliveryAddress from '@/views/pages/account-settings/DeliveryAddress.vue';
import MyInfo from '@/views/pages/account-settings/MyInfo.vue';
import PaymentDetails from '@/views/pages/account-settings/PaymentDetails.vue';
import { useRoute } from 'vue-router';
const route = useRoute()
const activeTab = ref(route.params.tab)
// tabs
const tabs = [
{
title: 'My Info',
tab: 'info',
},
{
title: 'Delivery Address',
tab: 'delivery',
},
{
title: 'Payment Details',
tab: 'payment',
},
{
title: 'Change Password',
tab: 'change',
}
]
</script>
<template>
<div>
<VTabs v-model="activeTab" show-arrows>
<VTab v-for="item in tabs" :value="item.tab">
<VIcon size="20" start />
{{ item.title }}
</VTab>
</VTabs>
<VDivider />
<VWindow v-model="activeTab" class="mt-5 disable-tab-transition">
<!-- Account -->
<VWindowItem value="info">
<MyInfo />
</VWindowItem>
<!-- Security -->
<VWindowItem value="delivery">
<DeliveryAddress />
</VWindowItem>
<!-- Notification -->
<VWindowItem value="payment">
<PaymentDetails />
</VWindowItem>
<!-- Notification -->
<VWindowItem value="change">
<ChangePassword />
</VWindowItem>
</VWindow>
</div>
</template>

View File

@@ -0,0 +1,748 @@
<script setup>
import StartOverPupup from '@/views/pages/home/StartOverPupup.vue';
import cardiologyCategory from '@/views/pages/questionere/form-category';
import formview from '@/views/pages/questionere/form.vue';
import CustomNav from '../layouts/components/navbar-custom.vue';
import axios from '@axios';
import {
requiredValidator
} from '@validators';
import { onBeforeMount, onMounted } from 'vue';
import { useRoute, useRouter } from 'vue-router';
import { useStore } from 'vuex';
import Cart from '../layouts/components/cart.vue';
const store = useStore()
const router = useRouter()
const route = useRoute()
// const categoryName = 'doctor_intake_request';
// const currentForm = cardiologyCategory[categoryName.replace(/[\s_]/g, '')]
const planName = ref(null)
const planAmount = ref(null)
const list_one_title = ref(null)
const list_sub_title = ref(null)
const list_two_title = ref(null)
const prescription_required = ref(null)
const shipping_price = ref(null)
const categoryName = ref(null);
const currentForm = ref(null);
const isMobile = ref(window.innerWidth <= 768); // Assuming mobile width is less than or equal to 768px
const currentDialoagkey = ref('')
const isTonalSnackbarVisible = ref(false)
const patientResponse = ref(false)
const isLoadingVisible = ref(false)
const questionForm = ref()
const answers = ref([]);
const storeAnswers = ref();
const isDialogVisible = ref(false)
const patient_id = localStorage.getItem('patient_id')
const access_token = store.access_token;
const seetingPlanLogo = ref(null);
const redirectTo = '/thankyou'
const redirectBack = '/review-appointment'
const patientExist = localStorage.getItem('exist')
const products = JSON.parse(localStorage.getItem('cart_products'));
const productCategoryName = ref(null)
const questions = ref([
{
question_key: 'why_interested_hrt',
question: "What aspects of hormone replacement therapy (HRT) intrigue you?",
type: "text",
},
{
question_key: 'goal_loss_weight',
question: "Is your objective with the program to achieve weight loss?",
type: "radio",
options: ["Yes", "No"],
},
{
question_key: 'what_biological_sex',
question: "What is your assigned sex at birth?",
type: "radio",
options: ["Male", "Female"],
},
{
question_key: '3_years_physical_test',
question: "Have you undergone a comprehensive physical examination by a medical professional within the past three years, which included assessments of vital signs such as weight, blood pressure, and heart rate?",
type: "radio",
options: ["Yes", "No"],
},
{
question_key: 'medical_problems',
question: "Did you experience any medical issues? If so, could you please elaborate?",
type: "radio",
options: ["Yes", "No"],
},
{
question_key: 'have_prostate_cancer',
question: "Have you ever received a diagnosis of prostate cancer?",
type: "radio",
options: ["Yes", "No"],
},
// {
// question_key: 'what_height',
// question: "How tall are you?",
// type: "dropdown",
// options: ["5 ft 1 in",
// "5 ft 2 in",
// "5 ft 3 in",
// "5 ft 4 in",
// "5 ft 5 in",
// "5 ft 6 in",
// "5 ft 7 in",
// "5 ft 8 in",
// ],
// },
// {
// question_key: 'whight',
// question: "What is your weight?",
// type: "text",
// },
// {
// question_key: 'birthdate',
// question: "When were you born?",
// type: "date",
// sub_title: 'To proceed with medication, kindly input your accurate date of birth using the format mm/dd/yyyy.'
// },
{
question_key: 'past_harmone_treatments',
question: "Have you been prescribed any hormone treatments currently or in the past?",
type: "radio",
options: ["Thyroid Medication", "Testosterone Treatment", "Estrogen Blocker", "HGH", "Ipamoreline", "Colomipheine", "HCG", "Other", "None"],
},
{
question_key: 'take_medications',
question: "Are you currently using or have you used any over-the-counter or prescription medications, excluding hormone treatments? (Please note that your responses will be cross-checked against prescription and insurance records. Failure to disclose current prescriptions and/or medical conditions may result in disapproval for your safety.)",
type: "radio",
options: ["Yes", "No"],
},
{
question_key: 'have_medications_allegies',
question: "Do you have any allergies to medications?",
type: "radio",
options: ["Yes", "No"],
},
{
question_key: 'plan_children',
question: "Do you intend to have children at some point in the future?",
type: "radio",
options: ["Yes", "No"],
},
{
question_key: 'partner_pregnant',
question: "Is your partner currently pregnant or breastfeeding?",
type: "radio",
options: ["Yes", "No", "Not Applicable"],
},
{
question_key: 'experience_ed',
question: "Are you currently experiencing any erectile dysfunction (ED) issues?",
type: "radio",
options: ["Never", "Almost Never", "Occasionally", "Almost Always", "Always"],
},
{
question_key: 'thyroid_disorders',
question: "Have you ever been diagnosed with thyroid disorders such as hypothyroidism (underactive thyroid), hyperthyroidism (overactive thyroid), Hashimoto's Disease, or Graves' Disease?",
type: "radio",
options: ["Yes", "No"],
},
{
question_key: 'family_history',
question: "Does your family have a history of any of the following disorders?",
type: "radio",
options: ["Drug Alcohol", "Cancer", "Heart Disease", "Thyroid Disease", "Low Testosterone",
"None"],
},
{
question_key: 'patient_concent',
question: "Please read and accept",
linktext: "Patient Content",
type: "signalCheckbox",
},
{
question_key: 'appointment_cancel',
question: "Please read and accept",
linktext: "Appointment Cancel",
type: "signalCheckbox",
},
{
question_key: 'medicare_disclaimer',
question: "Please read and accept",
linktext: "Medicare Disclaimer",
type: "signalCheckbox",
},
{
question_key: 'telehealth_concent',
question: "Please read and accept",
linktext: "Telehealth Concent",
type: "signalCheckbox",
},
{
question_key: 'secondary_contact',
question: "Please read and accept",
linktext: "Secondary Contact Disclosure(optional)",
type: "signalCheckbox",
},
]);
onBeforeMount(async () => {
store.dispatch('updateIsLoading', true)
store.dispatch('updateCurrentPage', 'additional-information')
localStorage.setItem('currentPage', 'additional-information')
await store.dispatch('getPatientInfo')
await store.dispatch('getPlanInfo')
// await store.dispatch('getPatientAppointment')
await store.dispatch('getAdditionalInformation')
planName.value = store.getters.getPatientPlan.plan_name
planAmount.value = store.getters.getPatientPlan.plan_amount
list_one_title.value = store.getters.getPatientPlan.list_one_title
list_sub_title.value = store.getters.getPatientPlan.list_sub_title
list_two_title.value = store.getters.getPatientPlan.list_two_title
prescription_required.value = store.getters.getPatientPlan.prescription_required
shipping_price.value = store.getters.getPatientPlan.shipping_price
storeAnswers.value = store.getters.getAdditionalInformation.answers
if (storeAnswers.value) {
let tempAns = storeAnswers.value
for (let answer of tempAns) {
answers.value[answer.question_key] = answer.answer
}
}
let productsData = products.filter(item => item.title !== "Doctor Visit")
productsData.forEach(product => {
if(product.category_name)
productCategoryName.value = product.category_name.toLowerCase().replace(/\s+|-/g, '_')
})
console.log('---->', productCategoryName.value)
if (productCategoryName.value) {
categoryName.value = productCategoryName.value
} else {
categoryName.value = 'weight_loss';
}
currentForm.value = cardiologyCategory[categoryName.value.replace(/[\s_]/g, '')]
store.dispatch('updateIsLoading', false)
})
const validateQuestion = async () => {
const { valid: isValid } = await questionForm.value?.validate();
console.log('isValid ', isValid);
if (isValid)
saveAnswers()
};
onMounted(async () => {
window.addEventListener('resize', checkIfMobile);
let setting = await axios.post('/api/settings', {})
// console.log(setting.data)
seetingPlanLogo.value = '/assets/logo/' + setting.data.logo
})
// Detach event listener on component unmount
onUnmounted(() => {
window.removeEventListener('resize', checkIfMobile);
});
const checkIfMobile = () => {
isMobile.value = window.innerWidth <= 768;
};
const saveAnswers = async () => {
isLoadingVisible.value = true;
const patient_id = localStorage.getItem('patient_id');
const jsonData = {
questions: questions.value.reduce((accumulator, question, index) => {
const answerValue = answers.value[question.question_key];
if (answerValue !== undefined) {
accumulator.push({
question_key: question.question_key,
type: question.type,
answer: answerValue,
});
}
return accumulator;
}, []),
};
let qaData = Array({ 'answers': jsonData.questions });
console.log('answers ', JSON.stringify(qaData));
await store.dispatch('saveAdditionalInformation', {
answers: jsonData.questions,
})
router.replace(route.query.to && route.query.to != '/additional-information' ? String(route.query.to) : '/thankyou')
};
const getCurrentDate = () => {
const today = new Date();
const year = today.getFullYear();
let month = today.getMonth() + 1;
let day = today.getDate();
// Format the date to match the input type="date" format
month = month < 10 ? `0${month}` : month;
day = day < 10 ? `0${day}` : day;
return `${month}-${day}-${year}`;
};
const openDialog = (question_key) => {
currentDialoagkey.value = question_key;
isDialogVisible.value = true;
window.addEventListener('click', closeDialogOnOutsideClick);
};
const closeDialogOnOutsideClick = (event) => {
const dialogElement = refs.myDialog.$el;
if (!dialogElement.contains(event.target)) {
isDialogVisible.value = false;
window.removeEventListener('click', closeDialogOnOutsideClick);
}
};
const handleContinueClick = (currentDialoagkey) => {
isDialogVisible.value = false; // Hide the dialog
answers.value[currentDialoagkey] = true; // Update the answers object
};
const backFun = () => {
store.dispatch('updateIsLoading', true)
router.replace(route.query.to && route.query.to != '/additional-information' ? String(route.query.to) : '/review-appointment')
}
</script>
<template>
<StartOverPupup :showPopup="store.getters.getShowStartOverPupup"></StartOverPupup>
<!-- <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> -->
<VRow><CustomNav :logo='seetingPlanLogo'></CustomNav></VRow>
<VRow
style="min-height: 100dvh; margin: 0px;"
:style="isMobile ? { marginTop: '90px' } : { marginTop: '60px' }"
>
<!-- <VCol cols="12" md="5" class="bg-custom col-order-1"
:class="isMobile ? '' : 'auth-wrapper d-flex align-center justify-center pa-4'">
<Cart></Cart>
</VCol> -->
<VCol cols="12" md="12" class="bg-custom-color col-order-2"
:class="isMobile ? '' : 'auth-wrapper d-flex align-center justify-center pa-4'">
<!-- <div class="auth-wrapper d-flex align-center justify-center pa-4"> -->
<VCard class="auth-card pa-2 rounded-5" style="" :class="isMobile ? '' : 'card-wid'">
<VRow class=" mx-0 gy-3 px-lg-5">
<VCol cols="12" lg="12" md="12">
<router-link to="/" class="text-center mb-2 mt-2"
style="width: 100%;position: relative;display: block;padding-top: 20px;">
<span class="text-center">
<VImg :src="seetingPlanLogo" width="250" height="50" class="logo-img" />
<!-- <svg width="32" height="22" viewBox="0 0 32 22" fill="none"
xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd"
d="M0.00172773 0V6.85398C0.00172773 6.85398 -0.133178 9.01207 1.98092 10.8388L13.6912 21.9964L19.7809 21.9181L18.8042 9.88248L16.4951 7.17289L9.23799 0H0.00172773Z"
fill="#7367F0" />
<path opacity="0.06" fill-rule="evenodd" clip-rule="evenodd"
d="M7.69824 16.4364L12.5199 3.23696L16.5541 7.25596L7.69824 16.4364Z"
fill="#161616" />
<path opacity="0.06" fill-rule="evenodd" clip-rule="evenodd"
d="M8.07751 15.9175L13.9419 4.63989L16.5849 7.28475L8.07751 15.9175Z"
fill="#161616" />
<path fill-rule="evenodd" clip-rule="evenodd"
d="M7.77295 16.3566L23.6563 0H32V6.88383C32 6.88383 31.8262 9.17836 30.6591 10.4057L19.7824 22H13.6938L7.77295 16.3566Z"
fill="#7367F0" />
</svg> -->
</span>
</router-link>
</VCol>
<VCol cols="12" md="12">
<!-- 👉 Title and subtitle -->
<div class="text-left">
<h5 class="text-h5 pricing-title mb-0">
Doctor Intake Request
</h5>
<p>Please complete the medical intake form before your appointment to ensure a smooth consultation with the doctor.</p>
</div>
</VCol>
<VCol cols="12" md="12">
<formview v-if="currentForm" :questionCategory="categoryName" :steps="currentForm.steps"
:schema="currentForm.schema" :redirectTo="redirectTo" :redirectBack="redirectBack"
:finishLabel="'All done'" :newForm="true" :returningUser="patientExist">
</formview>
</VCol>
</VRow>
<VRow>
<VDialog v-model="isDialogVisible" refs="myDialog" persistent width="500">
<!-- Dialog Content -->
<VCard>
<VCardText v-if="currentDialoagkey == 'patient_concent'"
title="Male Harmone therapy Patient consent">
<!-- <DialogCloseBtn variant="text" size="small" @click="isDialogVisible = false" /> -->
<VCardText class="pt-0"><b>Med Excel Clinic, P.A. ("Hgh HRT")</b>
</VCardText>
<VCardText><b>Nature of Hormone replacement and supplements therapy</b>
</VCardText>
<VDivider class="m-0"></VDivider>
<VCardText class="scrollable-text px-0 py-3">
I hereby give my consent to be treated using hormone replacement
therapy.
(antioxidants), and other medications prescribed to aid in improving my
overall health. I further consent to receive hormone replacement
therapy.
</VCardText>
<VCardText class="p-0">
<VRow>
<VCol cols="12" lg="6" md="6">
<VTextField v-model="patient_concent_Signature" label="Full Name Signature"
:rules="[requiredValidator]" />
</VCol>
<VCol cols="12" lg="6" md="6">
<VTextField v-model="date" type="date" disabled label="date"
:rules="[requiredValidator]" />
</VCol>
</VRow>
</VCardText>
<VCardText class="text-end pt-0 mt-5">
<VBtn @click.prevent="handleContinueClick(currentDialoagkey)">
Continue
</VBtn>
</VCardText>
</VCardText>
<VCardText v-if="currentDialoagkey == 'appointment_cancel'" title="">
<v-card-title class="p-0">
<span class="headline"><b>Medical Provider consultation
cancellation</b></span>
</v-card-title>
<VDivider></VDivider>
<!-- <DialogCloseBtn variant="text" size="small" @click="isDialogVisible = false" /> -->
<VCardText class="scrollable-text px-0 py-3">
I acknowledge that Hgh Medical charges a $59 for missed
appiontments.
</VCardText>
<VCardText class="p-0">
<VRow>
<VCol cols="12" lg="6" md="6">
<VTextField v-model="appointment_cancel_Signature"
label="Full Name Signature" :rules="[requiredValidator]" />
</VCol>
<VCol cols="12" lg="6" md="6">
<VTextField v-model="date" type="date" disabled label="date"
:rules="[requiredValidator]" />
</VCol>
</VRow>
</VCardText>
<VCardText class="text-end pt-0 mt-5">
<VBtn @click.prevent="handleContinueClick(currentDialoagkey)">
Continue
</VBtn>
</VCardText>
</VCardText>
<VCardText v-if="currentDialoagkey == 'medicare_disclaimer'">
<v-card-title class="p-0">
<span class="headline"><b>DISCLAIMER OF MEDICARE / <br> INSURANCE
BENEFIT</b></span>
</v-card-title>
<VDivider></VDivider>
<!-- <DialogCloseBtn variant="text" size="small" @click="isDialogVisible = false" /> -->
<VCardText class="scrollable-text px-0 py-3">
The patient acknowledge Hgh HRT or any of its
representatives
have made no representation or warranty that the treatment or any
portion
thereof qualifies or will qualify for reimbursement or assignment under
any
other government or private insurance program.
</VCardText>
<VCardText class="p-0">
<VRow>
<VCol cols="12" lg="6" md="6">
<VTextField v-model="medicare_disclaimer_Signature"
label="Full Name Signature" :rules="[requiredValidator]" />
</VCol>
<VCol cols="12" lg="6" md="6">
<VTextField v-model="date" type="date" disabled label="date"
:rules="[requiredValidator]" />
</VCol>
</VRow>
</VCardText>
<VCardText class="text-end pt-0 mt-5">
<VBtn @click.prevent="handleContinueClick(currentDialoagkey)">
Continue
</VBtn>
</VCardText>
</VCardText>
<VCardText v-if="currentDialoagkey == 'telehealth_concent'" title="">
<v-card-title class="p-0">
<span class="headline"><b>Hgh Medical, P.A. Telehealth <br> Informed
Consent</b></span>
</v-card-title>
<VDivider></VDivider>
<!-- <DialogCloseBtn variant="text" size="small" @click="isDialogVisible = false" /> -->
<VCardText class="scrollable-text px-0 py-3">
Telehealth involves the use of secure electronic communications,
information technology, or other means to enable a healthcare provider
and
a patient at different locations to communicate and share individual
patient
health information for the purpose of rendering clinical care. This
"Telehealth Informed Consent" informs the patient ("patient," "you")
</VCardText>
<VCardText class="p-0">
<VRow>
<VCol cols="12" lg="6" md="6">
<VTextField v-model="telehealth_concent_Signature"
label="Full Name Signature" :rules="[requiredValidator]" />
</VCol>
<VCol cols="12" lg="6" md="6">
<VTextField v-model="date" type="date" disabled label="date"
:rules="[requiredValidator]" />
</VCol>
</VRow>
</VCardText>
<VCardText class="text-end pt-0 mt-5">
<VBtn @click.prevent="handleContinueClick(currentDialoagkey)">
Continue
</VBtn>
</VCardText>
</VCardText>
<VCardText v-if="currentDialoagkey == 'secondary_contact'" title="">
<!-- <DialogCloseBtn variant="text" size="small" @click="isDialogVisible = false" /> -->
<v-card-title class="p-0">
<span class="headline"><b>Secondary Disclosure</b></span>
</v-card-title>
<VDivider></VDivider>
<VCardText class="scrollable-text pt-0 px-0 py-0">
Hgh HRT , PC is not permitted by law to provide information
</VCardText>
<VCardText class="p-0">
<VRow>
<VCol cols="12" lg="12" md="6">
<VTextField v-model="auth_information"
label="Authorize Release of Medical Information to (enter name) "
:rules="[requiredValidator]" />
</VCol>
</VRow>
<VRow>
<VCol cols="12" md="6">
<VTextField v-model="dob_auth_person" type="date" :max="getCurrentDate()"
label="DOB of authorized person above " :rules="[requiredValidator]" />
</VCol>
<VCol cols="12" md="6">
<VTextField v-model="relationship_auth_person"
label="Relationship of authorize person" :rules="[requiredValidator]" />
</VCol>
</VRow>
</VCardText>
<VCardText class="p-0">
<VRow>
<VCol cols="12" lg="6" md="6">
<VTextField v-model="secondary_contact_Signature"
label="Full Name Signature" :rules="[requiredValidator]" />
</VCol>
<VCol cols="12" lg="6" md="6">
<VTextField v-model="date" type="date" disabled label="date"
:rules="[requiredValidator]" />
</VCol>
</VRow>
</VCardText>
<VCardText class="text-end pt-0 mt-5">
<VBtn @click.prevent="handleContinueClick(currentDialoagkey)">
Continue
</VBtn>
</VCardText>
</VCardText>
</VCard>
</VDialog>
</VRow>
</VCard>
</VCol>
</VRow>
</template>
<style scoped>
@media only screen and (max-width: 768px) {
.card-wid {
max-width: 600px !important;
min-width: auto !important;
}
.col-order-1 {
order: 2;
}
.col-order-2 {
order: 1;
}
}
@media only screen and (min-width: 769px) {
.col-order-1 {
order: 1;
}
.col-order-2 {
order: 2;
}
}
.total-font {
font-size: 20px;
margin-bottom: 5px;
}
.bg-custom {
background: #F3F3F3;
}
.bg-custom-color {
background: #E0F0E3;
}
.bg-white bg-change-bk .current-plan {
border: 2px solid rgb(var(--v-theme-primary));
}
.cut-text {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
text-decoration: line-through;
text-decoration-color: red;
text-decoration-thickness: 1px;
}
.plan-card {
margin: 0rem;
margin-bottom: 0;
}
.card-wid {
max-width: 600px;
border-radius: 10px !important;
}
.layout-wrapper {
justify-content: center;
}
.error-message {
color: #ff2f2f;
font-size: 15px;
}
</style>
<style lang="scss">
.bg-custom {
background: #F3F3F3;
}
.bg-custom-color {
background: #E0F0E3;
}
.total-font {
font-size: 20px;
margin-bottom: 5px;
}
.card-title {
font-family: "Public Sans", -apple-system, BlinkMacSystemFont, "Segoe UI", "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif;
}
.v-list-item-title {
white-space: inherit !important;
;
}
.logo-img {
display: block;
position: relative;
margin: 0 auto;
}
.v-list {
min-height: 168px;
}
@media (min-width: 320px) and (max-width: 768px) {
.v-container {
padding: 0px !important;
}
.auth-wrapper {
padding: 0px !important;
}
.input-width {
max-width: 100% !important;
}
}
// .input-width {
// max-width: 50%;
// }
.form-check-input[type=checkbox] {
border-radius: 0.25em;
}
.form-check .form-check-input {
float: left;
margin-right: 0.5em;
}
.form-check-input {
cursor: pointer;
}
.form-check-input {
width: 1.2em;
height: 1.2em;
margin-top: 0.135em;
vertical-align: top;
background-color: #fff;
background-repeat: no-repeat;
background-position: center;
background-size: contain;
border: 2px solid #dbdade;
appearance: none;
}
.form-check .form-check-input:checked,
.form-check .form-check-input[type=checkbox]:indeterminate {
box-shadow: 0 0.125rem 0.25rem rgba(165, 163, 174, 0.3);
}
.form-check-input:checked[type=checkbox] {
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='11' height='13' viewBox='0 0 15 14' fill='none'%3E%3Cpath d='M3.41667 7L6.33333 9.91667L12.1667 4.08333' stroke='%23fff' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
}
.form-check-input:checked,
.form-check-input[type=checkbox]:indeterminate {
background-color: #7367f0;
border-color: #7367f0;
}
.form-check-input:checked {
background-color: #7367f0;
border-color: #7367f0;
}
.form-check-input[type=checkbox] {
border-radius: 0.25em;
}
</style>

View File

@@ -0,0 +1,238 @@
<script setup>
import leaftmap from '@/pages/leaftmap.vue';
import { ref } from 'vue';
const count = ref(5);
const showIframe = ref(true);
const activeTab1 = ref('info');
const overlay = ref(false);
const userAbout = ref('');
const userStatus = ref('active');
const settingsGroup = ref([]);
const twoStepVerification = ref(true);
const editLocation = ref('');
const editAddress = ref('');
const editCity = ref('');
const picker = ref();
const isDialogVisible = ref(false)
const isLoadingVisible = ref(false)
const isSuccessVisible = ref(false)
const selectedDate = ref();
const isDisabled = ref(true)
const chooseDate = ref([]);
const closeSidebar = () => {
// Implement close sidebar logic
};
const logout = () => {
// Implement logout logic
};
const findLab = () => {
console.log('Test');
activeTab1.value = 'lab_locator';
// Implement find lab logic
};
const editInfo = () => {
// Implement edit info logic
activeTab1.value = 'edit_form';
};
const updateInfo = () => {
// Implement update info logic
};
const backToInfo = () => {
// Implement back to info logic
activeTab1.value = 'info';
};
const backToLabLoc = () => {
// Implement back to lab locator logic
activeTab1.value = 'info';
};
const responseUrl = ref(localStorage.getItem('meeting-url'));
const datepick = () => {
console.log("ppicker",picker.value);
// isLoadingVisible.value = true;
chooseDate.value = '';
selectedDate.value = '';
if (picker.value) {
const selectedDate = new Date(picker.value);
const dateWithoutTime = new Date(selectedDate.getFullYear(), selectedDate.getMonth(), selectedDate.getDate());
// Format the date as needed
const formattedDate = dateWithoutTime.toISOString().slice(0, 10);
console.log("formattedDate",formattedDate);
// if (formattedDate) {
// console.log('in');
// axios.get('/api/available-slots/' + formattedDate)
// .then(response => {
// console.log("Data", response.data.available_slots);
// chooseDate.value = Object.values(response.data.available_slots);
// // isLoadingVisible.value = false;
// // console.log(chooseDate);
// })
// .catch(error => {
// console.error(error.response.data);
// });
// } else {
// chooseDate.value = [];
// }
}
}
const isBookSlotDisabled = computed(() => {
// Check if a date is selected
return !selectedDate.value;
});
const bookSlot = () => {
isLoadingVisible.value = true;
console.log("selectedData", selectedDate.value);
if (selectedDate.value) {
const userData = localStorage.getItem('userData');
isLoadingVisible.value = false;
axios.post('/api/book-appointment/', {
patient_id: userData,
appointment_time: selectedDate.value
})
.then(response => {
console.log(response.data.message);
isDialogVisible.value = true;
isSuccessVisible.value = true;
chooseDate.value = '';
selectedDate.value = '';
})
.catch(error => {
console.error(error.response.data);
isLoadingVisible.value = false;
});
}
}
const done = () => {
isDialogVisible.value = false;
isLoadingVisible.value = true;
router.replace(route.query.to && route.query.to != '/login' ? String(route.query.to) : '/appointments')
}
</script>
<template>
<VRow>
<VCol cols="12" md="8">
<VCard class="text-center text-sm-start rounded-0">
<!-- Show the iframe when countdown reaches 1 -->
<iframe v-if="showIframe" ref="myIframe" :src="responseUrl" width="100%" height="400" style="border:0"></iframe>
<div v-else>
Loading iframe...
</div>
</VCard>
</VCol>
<!-- 👉 Profit -->
<VCol cols="4">
<VRow>
<VCol cols="12">
<VCard rounded="0">
<!-- About User -->
<v-col v-if="activeTab1 === 'info'">
<VList col="4">
<v-list-item class="m-0">
<span class="text-heading"><b>Location:</b></span> <span>Court Street 78</span>
</v-list-item>
<v-list-item>
<span class="text-heading"><b>Address:</b></span> <span>Court Street 78</span>
</v-list-item>
<v-list-item>
<span class="text-heading"><b>City:</b></span> <span>Court Street 78</span>
</v-list-item>
</VList>
<!-- Add other user info here -->
<VRow>
<v-col>
<v-btn @click="findLab" class="btn btn-primary waves-effect waves-light">
Find Lab
</v-btn>
</v-col>
<v-col>
<v-btn @click="editInfo" class="btn btn-primary waves-effect waves-light">
Edit Info
</v-btn>
</v-col>
</VRow>
</v-col>
<v-col xs="12" v-else-if="activeTab1 === 'edit_form'" id="edit_form">
<v-form @submit.prevent="updateInfo">
<v-text-field class="mb-3" v-model="editLocation" label="Location"></v-text-field>
<v-text-field class="mb-3" v-model="editAddress" label="Address"></v-text-field>
<v-text-field class="mb-3" v-model="editCity" label="City"></v-text-field>
<v-btn type="submit" class="btn btn-primary waves-effect waves-light mr-2">Update</v-btn>
<v-btn @click="backToInfo">Back</v-btn>
</v-form>
</v-col>
<v-col xs="12" v-else-if="activeTab1 === 'lab_locator'" style="display: block;" id="lab_locator">
<small class="text-uppercase">Lab Locate</small>
<!-- Leaflet Map goes here -->
<leaftmap></leaftmap>
<v-btn @click="backToLabLoc">Back</v-btn>
</v-col>
</VCard>
</VCol>
<VCol cols="12">
<v-date-picker v-model="selectedDate" @update:modelValue="datepick"></v-date-picker>
<VCard rounded="0">
<div class="ml-3 mr-3">
<v-flex xs12 sm6 d-flex>
<v-select v-model="selectedDate" :items="chooseDate" label="Slots" width="1000"></v-select>
</v-flex>
<div class="text-right mt-5 mb-4">
<v-btn outline color="primary" @click=bookSlot :disabled="isBookSlotDisabled">
Book Slot
</v-btn>
</div>
</div>
</VCard>
</VCol>
</VRow>
</VCol>
<!-- 👉 Sales -->
</VRow>
</template>
<style scoped>
.v-list-item--density-default.v-list-item--one-line {
min-height: 30px;
}
</style>

View File

@@ -0,0 +1,181 @@
<script setup>
import { useAppAbility } from '@/plugins/casl/useAppAbility';
import axios from '@axios';
import {
emailValidator,
requiredEmail,
requiredPassword
} from '@validators';
import { useRoute, useRouter } from 'vue-router';
const router = useRouter()
const route = useRoute()
const ability = useAppAbility()
const errors = ref({
password: undefined,
email: undefined,
})
const inavlid = ref(false);
const InvalidCredential = ref()
const isLoadingVisible = ref(false)
const isPasswordVisible = ref(false)
const isPolicy = ref(false)
const isDisabled = ref(true)
const isDialogVisible = ref(false)
const refVForm = ref()
const password = ref()
const email = ref()
const seetingPlanLogo = ref();
const onSubmit = () => {
inavlid.value = false;
InvalidCredential.value = '';
refVForm.value?.validate().then(({ valid: isValid }) => {
if (isValid)
loginPatient()
})
}
onMounted(async () => {
const layoutWrapper = document.querySelector('.layout-wrapper');
// Check if the element exists
if (layoutWrapper) {
// Add the class you want
layoutWrapper.classList.add('regbg');
}
localStorage.setItem('thank-you-page', 'no')
let setting = await axios.post('/api/settings', {})
console.log(setting.data)
seetingPlanLogo.value = '/assets/logo/' + setting.data.logo
})
onUnmounted(() => {
// Select the element by class name
const layoutWrapper = document.querySelector('.layout-wrapper');
// Check if the element exists
if (layoutWrapper) {
// Remove the class
layoutWrapper.classList.remove('regbg');
}
});
const loginPatient = () => {
isLoadingVisible.value = true;
axios.post('/agent/login-agent', {
email: email.value,
password: password.value,
}).then(r => {
console.log(r.data);
localStorage.setItem('access_token', r.data.access_token)
localStorage.setItem('agent_id', r.data.data.id)
localStorage.setItem('user_role', 'agent')
localStorage.setItem('userAbilities', '[{"action":"manage","subject":"all"}]')
const userAbilities = [{ "action": "manage", "subject": "all" }];
ability.update(userAbilities)
router.replace(route.query.to && route.query.to != '/provider/login' ? String(route.query.to) : '/provider/dashboard')
}).catch(error => {
console.error("Invalid Credentials", error);
console.error("Invalid Credentials", error.response.data.message);
if (error.response && error.response.data.message) {
inavlid.value = true;
console.error("Invalid Credentials");
InvalidCredential.value = "Sorry, that email or password didn't work.";
}
isLoadingVisible.value = false;
});
}
</script>
<template>
<div class="auth-wrapper d-flex align-center justify-center pa-4">
<VCard class="auth-card pa-2 pt-0 rounded-5 regbx" max-width="320">
<VCardItem class="justify-center">
<VCardTitle class="text-2xl font-weight-bold text-primary">
<VImg :src="seetingPlanLogo" width="250" height="50" class="logo-img" />
</VCardTitle>
<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>
</VCardItem>
<VCardText>
<VForm ref="refVForm" @submit.prevent="onSubmit">
<VRow>
<VCol cols="12"></VCol>
<VRow class="bg-white">
<VCol cols="12">
<VTextField v-model="email" label="Email" type="email"
:rules="[requiredEmail, emailValidator]" :error-messages="errors.email" />
</VCol>
<!-- email -->
<VCol cols="12">
<VTextField v-model="password" label="Password" placeholder="············"
:rules="[requiredPassword]" :type="isPasswordVisible ? 'text' : 'password'"
:append-inner-icon="isPasswordVisible ? 'bx-show' : 'bx-hide'"
@click:append-inner="isPasswordVisible = !isPasswordVisible" />
</VCol>
<!-- password -->
<VCol cols="12">
<p class="error-message" v-if="inavlid">{{ InvalidCredential }}</p>
<!-- login button -->
<VBtn block type="submit"
style="background-color: rgb(var(--v-theme-yellow-theme-button)) !important;">
Login
</VBtn>
</VCol>
<!-- <VCol cols="12">
<VBtn block to="/provider/register" >
Register
</VBtn>
</VCol> -->
<!-- <VCol cols="12" class="text-center text-base px-0 py-0 mb-3" style="background-color: rgb(var(--v-theme-yellow-theme-button)) !important;">
<span>Don't have an account?</span>
<RouterLink class="text-primary" to="/register">
Register
</RouterLink>
</VCol> -->
</VRow>
<VCol cols="12" class="text-center text-base px-0 py-0 mb-0 mt-7 text-yellow-theme-button">
<span> Don't have an account? </span>
<RouterLink class="text-yellow-theme-button" to="/provider/register"
style="text-decoration: underline;">
Register
</RouterLink>
</VCol>
</VRow>
</VForm>
</VCardText>
</VCard>
</div>
</template>
<style lang="scss">
@use "@core/scss/template/pages/page-auth.scss";
.regbg {
background-image: url('/assets/images/reg_bg.png');
background-size: cover;
background-repeat: no-repeat;
}
.regbx {
background-color: rgb(var(--v-theme-yellow));
box-shadow: 0px 0px 10px #ccc;
border-radius: 10px;
}
.error-message {
color: #ff2f2f;
font-size: 15px;
}
.bg-primary {
background-color: rgb(var(--v-theme-grey-800)) !important;
}
</style>

View File

@@ -0,0 +1,830 @@
<script setup>
import { useAppAbility } from '@/plugins/casl/useAppAbility';
import axios from '@axios';
import {
emailValidator,
requiredAddress,
requiredCity,
requiredEmail,
requiredFirstName,
requiredGender,
requiredLastName,
requiredLicenseNumber,
requiredPassword,
requiredPhone,
requiredSpecialty,
requiredState,
requiredYearsofExperience,
requiredZip,
validUSAPhone
} from '@validators';
import { useRoute, useRouter } from 'vue-router';
const seetingPlanLogo = ref();
const currentStep = ref(1)
const router = useRouter()
const route = useRoute()
const ability = useAppAbility()
const errors = ref({
name: undefined,
email: undefined,
})
const inavlid = ref(false);
const InvalidCredential = ref()
const isLoadingVisible = ref(false)
const isPasswordVisible = ref(false)
const isPolicy = ref(false)
const isDisabled = ref(true)
const isDialogVisible = ref(false)
const refVForm = ref()
const verfyEmail = ref(false)
const first_name = ref(null)
const last_name = ref(null)
const email = ref(null)
const phone = ref(null)
const city = ref(null)
const stateAddress = ref('AL')
const zip = ref(null)
const password = ref(null)
const home_address = ref(null)
const medical_license_number = ref(null)
const years_of_experience = ref(null)
const gender = ref(null)
const specialty = ref(null)
const getIsTonalSnackbarVisible = ref(false);
const getIsTonalSnackbarVisibleError = ref(false);
const verificationCode = ref(['', '', '', '']);
const state = ref([])
const availabilityFrom = ref(null)
const availabilityTo = ref(null)
const medicalLicenseNumbers = ref({})
const currentUserRegister = ref(null)
const successMessage = ref(null)
const errorMessage = ref(null)
const user = ref()
const genders = [
{ value: 'male', title: 'Male' },
{ value: 'female', title: 'Female' },
{ value: 'other', title: 'Other' }
]
const requiredAvailability = (value) =>
!!value || 'Availability is required';
const specialties = ['General Practice', 'Cardiology', 'Dermatology', 'Pediatrics', /* add more options */]
const validateTimeRange = (value) => {
if (availabilityFrom.value && availabilityTo.value) {
const [fromTime, fromPeriod] = availabilityFrom.value.split(' ');
const [toTime, toPeriod] = availabilityTo.value.split(' ');
const fromHour = parseInt(fromTime.split(':')[0]);
const toHour = parseInt(toTime.split(':')[0]);
const fromMinute = parseInt(fromTime.split(':')[1]);
const toMinute = parseInt(toTime.split(':')[1]);
const fromValue = fromPeriod === 'PM' ? fromHour + 12 : fromHour;
const toValue = toPeriod === 'PM' ? toHour + 12 : toHour;
const timeDiff =
(toValue * 60 + toMinute) - (fromValue * 60 + fromMinute);
//return timeDiff >= 15 || 'Minimum time difference should be 15 minutes';
}
return true;
};
// const timeOptions = computed(() => {
// const options = [];
// for (let hour = 0; hour < 24; hour++) {
// for (let minute = 0; minute < 60; minute += 30) {
// let period = 'AM';
// let displayHour = hour;
// if (hour === 0) {
// displayHour = 12; // Convert 0 to 12 for 12:00 AM
// } else if (hour === 12) {
// period = 'PM'; // 12:00 PM
// } else if (hour > 12) {
// displayHour = hour - 12; // Convert to 12-hour format
// period = 'PM';
// }
// const timeString = `${displayHour.toString().padStart(2, '0')}:${minute
// .toString()
// .padStart(2, '0')} ${period}`;
// options.push(timeString);
// }
// }
// return options;
// });
const timeOptions = computed(() => {
const options = [];
// Loop through the hours from 12 to 23 (for 12:00 PM to 11:00 PM)
for (let hour = 12; hour < 24; hour++) {
// Loop through the minutes (0 and 30)
for (let minute of [0, 30]) {
// Construct the time string
const timeString = `${hour.toString().padStart(2, '0')}:${minute.toString().padStart(2, '0')}`;
options.push(timeString);
}
}
// Add the time option for 24:00 (midnight)
options.push('24:00');
return options;
});
const states = ref([
{ name: 'Alabama', abbreviation: 'AL' },
{ name: 'Alaska', abbreviation: 'AK' },
{ name: 'Arizona', abbreviation: 'AZ' },
{ name: 'Arkansas', abbreviation: 'AR' },
{ name: 'Howland Island', abbreviation: 'UM-84' },
{ name: 'Delaware', abbreviation: 'DE' },
{ name: 'Maryland', abbreviation: 'MD' },
{ name: 'Baker Island', abbreviation: 'UM-81' },
{ name: 'Kingman Reef', abbreviation: 'UM-89' },
{ name: 'New Hampshire', abbreviation: 'NH' },
{ name: 'Wake Island', abbreviation: 'UM-79' },
{ name: 'Kansas', abbreviation: 'KS' },
{ name: 'Texas', abbreviation: 'TX' },
{ name: 'Nebraska', abbreviation: 'NE' },
{ name: 'Vermont', abbreviation: 'VT' },
{ name: 'Jarvis Island', abbreviation: 'UM-86' },
{ name: 'Hawaii', abbreviation: 'HI' },
{ name: 'Guam', abbreviation: 'GU' },
{ name: 'United States Virgin Islands', abbreviation: 'VI' },
{ name: 'Utah', abbreviation: 'UT' },
{ name: 'Oregon', abbreviation: 'OR' },
{ name: 'California', abbreviation: 'CA' },
{ name: 'New Jersey', abbreviation: 'NJ' },
{ name: 'North Dakota', abbreviation: 'ND' },
{ name: 'Kentucky', abbreviation: 'KY' },
{ name: 'Minnesota', abbreviation: 'MN' },
{ name: 'Oklahoma', abbreviation: 'OK' },
{ name: 'Pennsylvania', abbreviation: 'PA' },
{ name: 'New Mexico', abbreviation: 'NM' },
{ name: 'American Samoa', abbreviation: 'AS' },
{ name: 'Illinois', abbreviation: 'IL' },
{ name: 'Michigan', abbreviation: 'MI' },
{ name: 'Virginia', abbreviation: 'VA' },
{ name: 'Johnston Atoll', abbreviation: 'UM-67' },
{ name: 'West Virginia', abbreviation: 'WV' },
{ name: 'Mississippi', abbreviation: 'MS' },
{ name: 'Northern Mariana Islands', abbreviation: 'MP' },
{ name: 'United States Minor Outlying Islands', abbreviation: 'UM' },
{ name: 'Massachusetts', abbreviation: 'MA' },
{ name: 'Connecticut', abbreviation: 'CT' },
{ name: 'Florida', abbreviation: 'FL' },
{ name: 'District of Columbia', abbreviation: 'DC' },
{ name: 'Midway Atoll', abbreviation: 'UM-71' },
{ name: 'Navassa Island', abbreviation: 'UM-76' },
{ name: 'Indiana', abbreviation: 'IN' },
{ name: 'Wisconsin', abbreviation: 'WI' },
{ name: 'Wyoming', abbreviation: 'WY' },
{ name: 'South Carolina', abbreviation: 'SC' },
{ name: 'South Dakota', abbreviation: 'SD' },
{ name: 'Montana', abbreviation: 'MT' },
{ name: 'North Carolina', abbreviation: 'NC' },
{ name: 'Palmyra Atoll', abbreviation: 'UM-95' },
{ name: 'Puerto Rico', abbreviation: 'PR' },
{ name: 'Colorado', abbreviation: 'CO' },
{ name: 'Missouri', abbreviation: 'MO' },
{ name: 'New York', abbreviation: 'NY' },
{ name: 'Maine', abbreviation: 'ME' },
{ name: 'Tennessee', abbreviation: 'TN' },
{ name: 'Georgia', abbreviation: 'GA' },
{ name: 'Louisiana', abbreviation: 'LA' },
{ name: 'Nevada', abbreviation: 'NV' },
{ name: 'Iowa', abbreviation: 'IA' },
{ name: 'Idaho', abbreviation: 'ID' },
{ name: 'Rhode Island', abbreviation: 'RI' },
{ name: 'Washington', abbreviation: 'WA' },
{ name: 'Ohio', abbreviation: 'OH' },
// ... (add the rest of the states)
]);
const sortedStates = computed(() => {
return states.value.slice().sort((a, b) => {
return a.name.localeCompare(b.name);
});
});
const selectedStateName = (abbreviation) => {
const selectedState = states.value.find(
(s) => s.abbreviation === abbreviation
);
return selectedState ? selectedState.name : abbreviation;
};
const onSubmit = () => {
inavlid.value = false;
InvalidCredential.value = '';
refVForm.value?.validate().then(({ valid: isValid }) => {
if (isValid)
saveInformation()
})
}
const formatPhoneNumber = () => {
// Remove non-numeric characters from the input
const numericValue = phone.value.replace(/\D/g, '');
// Apply formatting logic
if (numericValue.length <= 10) {
phone.value = numericValue.replace(/(\d{3})(\d{3})(\d{4})/, '($1) $2-$3');
} else {
// Limit the input to a maximum of 14 characters
const truncatedValue = numericValue.slice(0, 10);
phone.value = truncatedValue.replace(/(\d{3})(\d{3})(\d{4})/, '($1) $2-$3');
}
};
const validateAndProceed = async (nextStep, stepText) => {
console.log("availabilityTo", availabilityTo.value);
const { valid } = await refVForm.value.validate()
localStorage.setItem('step-register', nextStep)
let formData = {
home_address: home_address.value,
city: city.value,
state: stateAddress.value,
zip_code: zip.value,
medical_license_number: medicalLicenseNumbers.value,
years_of_experience: years_of_experience.value,
specialty: specialty.value,
gender: gender.value,
practice_state: state.value,
phone: phone.value,
availabilityFrom: '08:00:00',
availabilityTo: '10:00:00'
}
localStorage.setItem('register-form-data', JSON.stringify(formData))
if (valid) {
if (stepText == 'verification') {
let userAgent = await registerAgent()
console.log('userAgent', verfyEmail.value)
if (verfyEmail.value) {
errorMessage.value = null
InvalidCredential.value = '';
currentStep.value = nextStep
}
}
if (stepText == 'Personal Information') {
errorMessage.value = null
InvalidCredential.value = '';
currentStep.value = nextStep
}
}
//currentStep.value = nextStep
}
onMounted(async () => {
const layoutWrapper = document.querySelector('.layout-wrapper');
// Check if the element exists
if (layoutWrapper) {
// Add the class you want
layoutWrapper.classList.add('regbg');
}
let setting = await axios.post('/api/settings', {})
console.log(setting.data)
seetingPlanLogo.value = '/assets/logo/' + setting.data.logo
const storedStep = localStorage.getItem('step-register');
if (storedStep) {
currentStep.value = parseInt(storedStep, 10);
}
currentUserRegister.value = localStorage.getItem('register_provider_id')
let formData = localStorage.getItem('register-form-data')
let storeFormdata = JSON.parse(formData)
if (storeFormdata) {
home_address.value = storeFormdata.home_address
city.value = storeFormdata.city
stateAddress.value = storeFormdata.state
zip.value = storeFormdata.zip_code
medicalLicenseNumbers.value = storeFormdata.medical_license_number
years_of_experience.value = storeFormdata.years_of_experience
specialty.value = storeFormdata.specialty
gender.value = storeFormdata.gender
state.value = storeFormdata.practice_state
phone.value = storeFormdata.phone
availabilityFrom.value = storeFormdata.availabilityFrom
availabilityTo.value = storeFormdata.availabilityTo
console.log(JSON.parse(formData))
}
localStorage.setItem('thank-you-page', 'no')
});
onUnmounted(() => {
// Select the element by class name
const layoutWrapper = document.querySelector('.layout-wrapper');
// Check if the element exists
if (layoutWrapper) {
// Remove the class
layoutWrapper.classList.remove('regbg');
}
});
const verifyCode = async (step, text) => {
const code = verificationCode.value.join('');
console.log('Verifying code:', code);
console.log(currentUserRegister.value)
let user_id = localStorage.getItem('register_provider_id')
let codeVerify = await axios.post(`/agent/verify-email/${user_id}`, {
token: Number(code),
}).then(r => {
if (r.data.message == "Account verified") {
localStorage.setItem('step-register', step)
currentStep.value = step
}
}).catch(error => {
if (error.response && error.response.data.message == 'already verified') {
getIsTonalSnackbarVisibleError.value = true;
errorMessage.value = 'Otp not match';
}
})
};
const registerAgent = async () => {
isLoadingVisible.value = true;
await axios.post('/agent/registerPost', {
first_name: first_name.value,
last_name: last_name.value,
email: email.value,
password: password.value,
}).then(r => {
console.log(r.data);
verfyEmail.value = true
isLoadingVisible.value = false;
user.value = r.data.user
localStorage.setItem('register_provider_id', r.data.user.id)
// getIsTonalSnackbarVisible.value = true;
//successMessage.value = r.data.message
// email.value = null
// phone.value = null
// password.value = null
//name.value = null
// home_address.value = null
// medical_license_number.value = null
// years_of_experience.value = null
// gender.value = null
// specialty.value = null
// gender.value = null
//router.push('/provider/thank-you')
// localStorage.setItem('access_token', r.data.access_token)
// localStorage.setItem('agent_id', r.data.data.id)
// localStorage.setItem('user_role', 'agent')
// localStorage.setItem('userAbilities', '[{"action":"manage","subject":"all"}]')
// const userAbilities = [{ "action": "manage", "subject": "all" }];
// ability.update(userAbilities)
// router.replace(route.query.to && route.query.to != '/agent/login' ? String(route.query.to) : '/agent/dashboard')
}).catch(error => {
inavlid.value = true;
verfyEmail.value = false
console.error("Invalid Credentials", error.response.data.message);
localStorage.setItem('step-register', 1)
if (error.response && error.response.data.message) {
const isDuplicateEntryError = /1062 Duplicate entry/.test(error.response.data.message);
if (isDuplicateEntryError) {
getIsTonalSnackbarVisibleError.value = true;
errorMessage.value = 'Email already taken!';
}
}
isLoadingVisible.value = false;
let errors = error.response.data.errors
for (let data in errors) {
InvalidCredential.value = data + ": " + errors[data];
isLoadingVisible.value = false;
getIsTonalSnackbarVisibleError.value = true;
errorMessage.value = data + ": " + errors[data];
}
});
}
const saveInformation = async () => {
let formData = {
home_address: home_address.value,
city: city.value,
state: stateAddress.value,
zip_code: zip.value,
medical_license_number: medicalLicenseNumbers.value,
years_of_experience: years_of_experience.value,
specialty: specialty.value,
gender: gender.value,
practice_state: state.value,
phone: phone.value,
availabilityFrom: availabilityFrom.value,
availabilityTo: availabilityTo.value
}
console.log(currentUserRegister.value)
isLoadingVisible.value = true;
let user_id = localStorage.getItem('register_provider_id')
await axios.post(`/agent/save-profile/${user_id}`, {
home_address: home_address.value,
city: city.value,
state: stateAddress.value,
zip_code: zip.value,
medical_license_number: medicalLicenseNumbers.value,
years_of_experience: years_of_experience.value,
specialty: specialty.value,
gender: gender.value,
practice_state: state.value,
phone: phone.value,
availabilityFrom: availabilityFrom.value,
availabilityTo: availabilityTo.value
}).then(r => {
console.log(r.data);
verfyEmail.value = true
isLoadingVisible.value = false;
localStorage.setItem('register-form-data', null)
localStorage.setItem('register_provider_id', null)
localStorage.setItem('step-register', 1)
localStorage.setItem('thank-you-page', 'yes')
// getIsTonalSnackbarVisible.value = true;
//successMessage.value = r.data.message
// email.value = null
// phone.value = null
// password.value = null
//name.value = null
// home_address.value = null
// medical_license_number.value = null
// years_of_experience.value = null
// gender.value = null
// specialty.value = null
// gender.value = null
router.push('/provider/thank-you')
// localStorage.setItem('access_token', r.data.access_token)
// localStorage.setItem('agent_id', r.data.data.id)
// localStorage.setItem('user_role', 'agent')
// localStorage.setItem('userAbilities', '[{"action":"manage","subject":"all"}]')
// const userAbilities = [{ "action": "manage", "subject": "all" }];
// ability.update(userAbilities)
// router.replace(route.query.to && route.query.to != '/agent/login' ? String(route.query.to) : '/agent/dashboard')
}).catch(error => {
inavlid.value = true;
verfyEmail.value = false
console.error("Invalid Credentials", error.response.data.message);
if (error.response && error.response.data.message) {
const isDuplicateEntryError = /1062 Duplicate entry/.test(error.response.data.message);
if (isDuplicateEntryError) {
getIsTonalSnackbarVisibleError.value = true;
errorMessage.value = 'Email already taken!';
}
}
isLoadingVisible.value = false;
let errors = error.response.data.errors
for (let data in errors) {
InvalidCredential.value = data + ": " + errors[data];
isLoadingVisible.value = false;
getIsTonalSnackbarVisibleError.value = true;
errorMessage.value = data + ": " + errors[data];
}
});
}
const reSendCode = async () => {
isLoadingVisible.value = true;
let user_id = localStorage.getItem('register_provider_id')
let codeVerify = await axios.post(`/agent/resend-code/${user_id}`, {}).then(r => {
console.log(r.data)
isLoadingVisible.value = false;
getIsTonalSnackbarVisible.value = true
successMessage.value = r.data.message
}).catch(error => {
console.log(error)
isLoadingVisible.value = false;
})
}
const verificationFields = ref([])
const handleInput = (index, event) => {
const inputValue = event.target.value.replace(/\D/g, '')
const sanitizedValue = inputValue.slice(0, 1)
verificationCode.value[index] = sanitizedValue
if (sanitizedValue && index < verificationCode.value.length - 1) {
const nextField = verificationFields.value[index + 1].$el.querySelector('input')
nextField.focus()
}
}
</script>
<template>
<VSnackbar v-model="getIsTonalSnackbarVisibleError" :timeout="15000" location="top end" variant="flat" color="red">
{{ errorMessage }}
</VSnackbar>
<VSnackbar v-model="getIsTonalSnackbarVisible" :timeout="5000" location="top end" variant="flat" color="green">
{{ successMessage }}
</VSnackbar>
<div class="auth-wrapper d-flex align-center justify-center pa-4">
<VCard class="auth-card pa-2 rounded-5 regbx" max-width="600">
<VCardItem class="justify-center">
<VCardTitle class="text-2xl font-weight-bold text-primary">
<VImg :src='seetingPlanLogo' width="250" height="50" class="logo-img" />
</VCardTitle>
<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>
</VCardItem>
<VCardText>
<VForm ref="refVForm" @submit.prevent="onSubmit">
<VRow>
<VCol cols="12">
<!-- <v-alert v-if="inavlid" :text=InvalidCredential type="error"></v-alert> -->
</VCol>
<VRow class="bg-white" v-if="currentStep === 1">
<VCol cols="12" class="d-flex align-center text-center">
<VIcon icon="mdi-lock" size="30" class="mr-2" />
<h4 class="section-title">Basic Information</h4>
</VCol>
<VCol cols="12" md="6">
<VTextField v-model="first_name" label="First Name" type="name"
:rules="[requiredFirstName]" :error-messages="errors.first_name"
density="comfortable" />
</VCol>
<VCol cols="12" md="6">
<VTextField v-model="last_name" label="Last Name" type="name"
:rules="[requiredLastName]" :error-messages="errors.last_name"
density="comfortable" />
</VCol>
<VCol cols="12" md="6">
<VTextField v-model="email" label="Email" type="email"
:rules="[requiredEmail, emailValidator]" :error-messages="errors.email"
density="comfortable" />
</VCol>
<VCol cols="12" md="6">
<VTextField v-model="password" label="Password" placeholder="············"
:rules="[requiredPassword]" :type="isPasswordVisible ? 'text' : 'password'"
:append-inner-icon="isPasswordVisible ? 'bx-show' : 'bx-hide'"
@click:append-inner="isPasswordVisible = !isPasswordVisible"
density="comfortable" />
</VCol>
<VCol class="text-center" cols="12">
<VBtn color="rgb(var(--v-theme-yellow-theme-button))"
class="text-center text-white px-5" @click="validateAndProceed(2, 'verification')">
Next</VBtn>
</VCol>
</VRow>
<VRow v-if="currentStep === 2" class=" text-center bg-white">
<VCol>
<VRow class="verfication_row">
<h3>Kindly input the four-digit verification code that was sent to you via email.
</h3>
<v-col cols="3" v-for="(digit, index) in verificationCode" :key="index">
<v-text-field v-model="verificationCode[index]" maxlength="1" type="number"
min="0" max="9" outlined dense required class="verification-field"
@input="handleInput(index, $event)" ref="verificationFields"></v-text-field>
</v-col>
<VCol cols="12">
<VBtn @click="verifyCode(3, 'verfication')"
color="rgb(var(--v-theme-yellow-theme-button))" class="text-white">
Verify
</VBtn>
</VCol>
</VRow>
</VCol>
</VRow>
<VRow class="bg-white" v-if="currentStep === 3">
<VCol cols="12" class="d-flex align-center text-center">
<VIcon icon="mdi-account-circle" size="32" class="mr-2" />
<h4 class="section-title">Personal Information</h4>
</VCol>
<VCol cols="12" md="12">
<VTextField v-model="home_address" label="Home Address" :rules="[requiredAddress]"
:error-messages="errors.home_address" density="comfortable" />
</VCol>
<VCol cols="12" md="6">
<VTextField v-model="city" label="City" density="comfortable" :rules="[requiredCity]"
:error-messages="errors.city" />
</VCol>
<VCol cols="12" md="6">
<v-autocomplete clearable v-model="stateAddress" label="State" density="comfortable"
:items="sortedStates" item-title="name" item-value="abbreviation"
:rules="[requiredState]" :error-messages="errors.stateAddress">
</v-autocomplete>
</VCol>
<VCol cols="12" md="6">
<VTextField v-model="zip" label="Zip" density="comfortable" :rules="[requiredZip]"
:error-messages="errors.zip" />
</VCol>
<VCol cols="12" md="6">
<VTextField v-model="phone" label="Phone Number" pattern="^\(\d{3}\) \d{3}-\d{4}$"
:rules="[requiredPhone, validUSAPhone]" :error-messages="errors.phone"
placeholder="i.e. (000) 000-0000" @input="formatPhoneNumber" max="14"
density="comfortable" />
</VCol>
<VCol cols="12" md="6">
<VSelect v-model="gender" label="Gender" :items="genders" :rules="[requiredGender]"
:error-messages="errors.gender" item-title="title" item-value="value"
density="comfortable" />
</VCol>
<VCol class="text-center" cols="12">
<VBtn color="rgb(var(--v-theme-yellow-theme-button))"
class="text-center text-white px-5"
@click="validateAndProceed(4, 'Personal Information')" block>
Next</VBtn>
</VCol>
</VRow>
<VRow class="bg-white" v-if="currentStep === 4">
<VCol cols="12" class="d-flex align-center text-center">
<VIcon icon="mdi-briefcase" size="32" class="mr-2" />
<h4 class="section-title">Professional Information</h4>
</VCol>
<VCol cols="12" md="6">
<VTextField v-model="years_of_experience" label="Years of Experience" type="number"
:rules="[requiredYearsofExperience]" :error-messages="errors.years_of_experience"
density="comfortable" />
</VCol>
<VCol cols="12" md="6">
<VTextField v-model="specialty" label="Practice or Provider's Specialty" type="text"
:rules="[requiredSpecialty]" :error-messages="errors.specialty"
density="comfortable" />
</VCol>
<VCol cols="12" md="6">
<VSelect v-model="availabilityFrom" label="Availability From" :items="timeOptions"
density="comfortable" :rules="[requiredAvailability]"
:error-messages="errors.availabilityFrom" />
</VCol>
<VCol cols="12" md="6">
<!-- timeOptions.filter((time) => time > availabilityFrom) -->
<VSelect v-model="availabilityTo" label="Availability To" :items="timeOptions"
density="comfortable" :rules="[requiredAvailability]"
:error-messages="errors.availabilityTo" />
</VCol>
<VCol cols="12">
<v-select v-model="state" label="Region Currently Practicing" :items="sortedStates"
item-title="name" item-value="abbreviation" multiple :rules="[requiredState]"
:error-messages="errors.state" density="comfortable">
</v-select>
</VCol>
<!-- <VCol cols="12" md="6">
<VTextField v-model="medical_license_number"
:label="`Medical License Number for ${selectedStatesString}`" :rules="[requiredLicenseNumber]"
:error-messages="errors.medical_license_number" />
</VCol> -->
<VCol cols="12" v-if="state.length > 0">
<div class="d-flex align-center mb-4">
<VIcon icon="mdi-certificate" size="32" class="mr-2" />
<h4 class="section-title">Medical License Number</h4>
</div>
<div v-for="(stat, index) in state" :key="index">
<VTextField v-model="medicalLicenseNumbers[stat]"
:label="`${selectedStateName(stat)}`" :rules="[requiredLicenseNumber]"
density="comfortable"
:error-messages="errors[`medicalLicenseNumber_${selectedStateName(stat)}`]"
class="mb-2" />
</div>
</VCol>
<!-- email -->
<VCol class="text-center px-5" cols="12">
<!-- <VBtn color="rgb(var(--v-theme-yellow))" @click="currentStep = 3" class="mb-3" style="background-color: rgb(var(--v-theme-yellow)) !important"
block>Back</VBtn> -->
<VBtn type=" submit" style="background-color: rgb(var(--v-theme-yellow-theme-button)) !important;"
block>
Register
</VBtn>
</VCol>
</VRow>
<VCol cols="12" class="text-center text-base px-0 py-0 mb-0 mt-7 text-yellow-theme-button"
v-if="currentStep === 1">
<span> Already have an account? </span>
<RouterLink class="text-yellow-theme-button" to="/provider/login"
style="text-decoration: underline;">
Login
</RouterLink>
</VCol>
<VCol cols="12" class="text-center text-base px-0 py-0 mb-0 mt-7 text-yellow-theme-button"
v-if="currentStep === 2">
<span> Didn't received code </span>
<RouterLink class="text-yellow-theme-button" @click.prevent="reSendCode" to="#"
style="text-decoration: underline;">
Send Again
</RouterLink>
</VCol>
</VRow>
</VForm>
</VCardText>
</VCard>
</div>
</template>
<style lang="scss">
@use "@core/scss/template/pages/page-auth.scss";
.regbg {
background-image: url('/assets/images/reg_bg.png');
background-size: cover;
background-repeat: no-repeat;
}
.regbx {
background-color: rgb(var(--v-theme-yellow));
box-shadow: 0px 0px 10px #ccc;
border-radius: 10px;
}
.error-message {
color: #ff2f2f;
font-size: 15px;
}
.verification-field[type="number"]::-webkit-inner-spin-button,
.verification-field[type="number"]::-webkit-outer-spin-button {
-webkit-appearance: none;
margin: 0;
}
input::-webkit-outer-spin-button,
input::-webkit-inner-spin-button {
-webkit-appearance: none;
margin: 0;
}
.verfication_row {
max-width: 400px;
margin: 0 auto !important;
input {
text-align: center;
line-height: 80px;
font-size: 50px;
border: solid 1px #ccc;
box-shadow: 0 0 5px #ccc inset;
outline: none;
width: 20%;
transition: all .2s ease-in-out;
border-radius: 3px;
&:focus {
border-color: purple;
box-shadow: 0 0 5px purple inset;
}
&::selection {
background: transparent;
}
}
button {
margin: 30px 0 50px;
width: 100%;
padding: 6px;
background-color: #B85FC6;
border: none;
text-transform: uppercase;
}
}
</style>

View File

@@ -0,0 +1,14 @@
<script setup>
import AppointmentsLists from '@/views/pages/tables/appointments.vue';
</script>
<template>
<VRow>
<VCol cols="12">
<VCard title="Appointments">
<AppointmentsLists />
</VCard>
</VCol>
</VRow>
</template>

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,152 @@
<script setup>
import axios from '@axios';
// import VDatePicker from 'v-date-picker';
import { ref } from 'vue';
import { useRoute, useRouter } from 'vue-router';
const router = useRouter()
const route = useRoute()
const picker = ref();
const isDialogVisible = ref(false)
const isLoadingVisible = ref(false)
const isSuccessVisible = ref(false)
const selectedDate = ref();
const isDisabled = ref(true)
const chooseDate = ref([]);
const datepick = () => {
console.log('Test');
// isLoadingVisible.value = true;
chooseDate.value = '';
selectedDate.value = '';
if (picker.value) {
const selectedDate = new Date(picker.value);
const dateWithoutTime = new Date(selectedDate.getFullYear(), selectedDate.getMonth(), selectedDate.getDate());
// Format the date as needed
const formattedDate = dateWithoutTime.toISOString().slice(0, 10);
console.log("formattedDate", formattedDate);
if (formattedDate) {
console.log('in');
axios.get('/agent/api/available-slots/' + formattedDate)
.then(response => {
console.log("Data", response.data.available_slots);
chooseDate.value = Object.values(response.data.available_slots);
isLoadingVisible.value = false;
// console.log(chooseDate);
})
.catch(error => {
console.error(error.response.data);
});
} else {
chooseDate.value = [];
}
}
}
const isBookSlotDisabled = computed(() => {
// Check if a date is selected
return !selectedDate.value;
});
const bookSlot = () => {
isLoadingVisible.value = true;
console.log("selectedData", selectedDate.value);
if (selectedDate.value) {
const userData = localStorage.getItem('userData');
isLoadingVisible.value = false;
axios.post('/api/book-appointment/', {
patient_id: userData,
appointment_time: selectedDate.value
})
.then(response => {
console.log(response.data.message);
isDialogVisible.value = true;
isSuccessVisible.value = true;
chooseDate.value = '';
selectedDate.value = '';
})
.catch(error => {
console.error(error.response.data);
isLoadingVisible.value = false;
});
}
}
const done = () => {
isDialogVisible.value = false;
isLoadingVisible.value = true;
router.replace(route.query.to && route.query.to != '/login' ? String(route.query.to) : '/appointments')
}
</script>
<template>
<VRow>
<VCol>
<VCard>
<div class="d-flex flex-wrap flex-md-wrap flex-column flex-md-row book-slo">
<div class="">
<v-date-picker v-model="picker" @update:modelValue="datepick"></v-date-picker>
</div>
<VDivider :vertical="$vuetify.display.mdAndUp" />
<VCol>
<VCardItem>
<VCardTitle class="mb-5">Available Slot</VCardTitle>
<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>
<v-flex xs12 sm6 d-flex>
<v-select v-model="selectedDate" :items="chooseDate" label="Slots" width="1000"></v-select>
</v-flex>
<div class="text-xs-center mt-5">
<v-btn outline color="primary" @click=bookSlot :disabled="isBookSlotDisabled">
Book Slot
</v-btn>
</div>
</VCardItem>
</VCol>
</div>
</VCard>
</VCol>
<VDialog v-model="isDialogVisible" refs="myDialog" persistent width="500">
<!-- Activator -->
<!-- Dialog Content -->
<VCard>
<VCardItem class="justify-center">
<VCardTitle class=" mb-4 font-weight-bold text-primary">
HGH
</VCardTitle>
</VCardItem>
<!-- <DialogCloseBtn variant="text" size="small" @click="isDialogVisible = false" /> -->
<v-sheet elevation="12" max-width="600" rounded="lg" width="100%" class="pa-2 text-center mx-auto">
<v-icon class="mb-0" color="primary" icon="mdi-check-circle" size="112"></v-icon>
<h2 class="text-h5 mb-6" v-if="isSuccessVisible">Appointment Booked Successfully</h2>
<v-divider class="mb-4"></v-divider>
<div class="text-end">
<v-btn class="text-none text-white" color="primary" rounded variant="flat" width="90" @click="done">
OK
</v-btn>
</div>
</v-sheet>
</VCard>
</VDialog>
</VRow>
</template>
<style>
.book-slot {
min-height: 460px;
}
</style>

View File

@@ -0,0 +1,14 @@
<script setup>
import { ref } from 'vue';
import videocall from '../views/videocall/new-videocall.vue';
const recordVid = ref(true)
const agentId = ref(localStorage.getItem('agent_id'))
const token = ref(localStorage.getItem('meeting_id'))
const questions = ref([]);
const call_type = ref(localStorage.getItem('call_type'));
onMounted(() => { });
</script>
<template>
<videocall :token="token" :call_type="call_type" :recording="false"></videocall>
</template>

View File

@@ -0,0 +1,27 @@
<script setup>
import CardBasic from '@/views/pages/cards/card-basic/CardBasic.vue'
import CardNavigation from '@/views/pages/cards/card-basic/CardNavigation.vue'
import CardSolid from '@/views/pages/cards/card-basic/CardSolid.vue'
</script>
<template>
<div>
<p class="text-2xl mb-6">
Basic Cards
</p>
<CardBasic />
<p class="text-2xl mb-6 mt-14">
Navigation Cards
</p>
<CardNavigation />
<p class="text-2xl mt-14 mb-6 ">
Solid Cards
</p>
<CardSolid />
</div>
</template>

View File

@@ -0,0 +1,81 @@
<script setup>
const breadcrums = [
{
title: 'Category',
disabled: false,
to: 'category',
},
{
title: 'Groups',
disabled: true,
},
];
</script>
<template>
<VRow>
<v-breadcrumbs :items="breadcrums" class="text-primary pt-0 pb-0">
<template v-slot:divider style="padding-top:0px; padding-bottom:0px">
<v-icon icon="mdi-chevron-right" style="padding-top:0px;"></v-icon>
</template>
</v-breadcrumbs>
<VCol cols="12" md="12" style="padding-top:0px;">
<VRow class="justify-space-between pb-0">
<VCol class="pb-0">
<VCardTitle class="text-primary pl-0 pb-0">
<b>
Groups
</b>
</VCardTitle>
</VCol>
</VRow>
</VCol>
<VCol cols="12">
<VCard>
<VCardTitle class="text-primary">
<RouterLink to="/group-question"><b>Medical History</b></RouterLink>
</VCardTitle>
<VCardText>
<p><b>Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown
printer took a galley of type and scrambled it to make a type .</b></p>
</VCardText>
<VCardText>
<p>Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown
printer took a galley of type and scrambled it to make a type .</p>
</VCardText>
<VCardText>
<VRow>
<VCol class="border-end text-center">
<h3>17</h3>
<p class="">Total questions</p>
</VCol>
<VDivider class="bold-divider" vertical />
<VCol class=" border-end text-center">
<h3>17</h3>
<p class="">Answered Questions</p>
</VCol>
<VDivider class="bold-divider" vertical />
<VCol class="text-center">
<h3>100</h3>
<p class="">Average %</p>
</VCol>
</VRow>
</VCardText>
</VCard>
</VCol>
</VRow>
</template>
<style lang="scss">
.bold-divider::after {
content: '';
border-right: 1px solid black;
/* Adjust thickness and color as needed */
}
</style>

View File

@@ -0,0 +1,672 @@
<script setup>
import axios from '@axios';
import previousButton from '@images/svg/previous.svg';
import { onMounted } from 'vue';
import { useRoute, useRouter } from 'vue-router';
import phpUnserialize from 'phpunserialize';
import { useStore } from 'vuex';
const store = useStore()
const router = useRouter()
const route = useRoute()
const isMobile = ref(window.innerWidth <= 768); // Assuming mobile width is less than or equal to 768px
const currentDialoagkey = ref('')
const isTonalSnackbarVisible = ref(false)
const patientResponse = ref(false)
const isLoadingVisible = ref(false)
const questionForm = ref()
const answers = ref([]);
const medical_question = ref([]);
const question_length = ref([]);
const isDialogVisible = ref(false)
const selectedQuestion = ref([]);
const previousQid = ref(null)
onBeforeMount(async () => {
await store.dispatch('getMedicalHistoryQuestion')
// medical_question.value.push({ answers: [] });
medical_question.value = store.getters.getMedicalCategoryQuestion.data;
question_length.value = medical_question.value.length;
console.log("medical_question", medical_question.value);
});
const questions = ref([
{
question_key: 'why_interested_hrt',
question: "What aspects of hormone replacement therapy (HRT) intrigue you?",
type: "text",
},
{
question_key: 'goal_loss_weight',
question: "Is your objective with the program to achieve weight loss?",
type: "radio",
options: ["Yes", "No"],
},
{
question_key: 'what_biological_sex',
question: "What is your assigned sex at birth?",
type: "radio",
options: ["Male", "Female"],
},
{
question_key: '3_years_physical_test',
question: "Have you undergone a comprehensive physical examination by a medical professional within the past three years, which included assessments of vital signs such as weight, blood pressure, and heart rate?",
type: "radio",
options: ["Yes", "No"],
},
{
question_key: 'medical_problems',
question: "Did you experience any medical issues? If so, could you please elaborate?",
type: "radio",
options: ["Yes", "No"],
},
{
question_key: 'have_prostate_cancer',
question: "Have you ever received a diagnosis of prostate cancer?",
type: "radio",
options: ["Yes", "No"],
},
// {
// question_key: 'what_height',
// question: "How tall are you?",
// type: "dropdown",
// options: ["5 ft 1 in",
// "5 ft 2 in",
// "5 ft 3 in",
// "5 ft 4 in",
// "5 ft 5 in",
// "5 ft 6 in",
// "5 ft 7 in",
// "5 ft 8 in",
// ],
// },
// {
// question_key: 'whight',
// question: "What is your weight?",
// type: "text",
// },
// {
// question_key: 'birthdate',
// question: "When were you born?",
// type: "date",
// sub_title: 'To proceed with medication, kindly input your accurate date of birth using the format mm/dd/yyyy.'
// },
{
question_key: 'past_harmone_treatments',
question: "Have you been prescribed any hormone treatments currently or in the past?",
type: "radio",
options: ["Thyroid Medication", "Testosterone Treatment", "Estrogen Blocker", "HGH", "Ipamoreline", "Colomipheine", "HCG", "Other", "None"],
},
{
question_key: 'take_medications',
question: "Are you currently using or have you used any over-the-counter or prescription medications, excluding hormone treatments? (Please note that your responses will be cross-checked against prescription and insurance records. Failure to disclose current prescriptions and/or medical conditions may result in disapproval for your safety.)",
type: "radio",
options: ["Yes", "No"],
},
{
question_key: 'have_medications_allegies',
question: "Do you have any allergies to medications?",
type: "radio",
options: ["Yes", "No"],
},
{
question_key: 'plan_children',
question: "Do you intend to have children at some point in the future?",
type: "radio",
options: ["Yes", "No"],
},
{
question_key: 'partner_pregnant',
question: "Is your partner currently pregnant or breastfeeding?",
type: "radio",
options: ["Yes", "No", "Not Applicable"],
},
{
question_key: 'experience_ed',
question: "Are you currently experiencing any erectile dysfunction (ED) issues?",
type: "radio",
options: ["Never", "Almost Never", "Occasionally", "Almost Always", "Always"],
},
{
question_key: 'thyroid_disorders',
question: "Have you ever been diagnosed with thyroid disorders such as hypothyroidism (underactive thyroid), hyperthyroidism (overactive thyroid), Hashimoto's Disease, or Graves' Disease?",
type: "radio",
options: ["Yes", "No"],
},
{
question_key: 'family_history',
question: "Does your family have a history of any of the following disorders?",
type: "radio",
options: ["Drug Alcohol", "Cancer", "Heart Disease", "Thyroid Disease", "Low Testosterone",
"None"],
},
{
question_key: 'patient_concent',
question: "Please read and accept",
linktext: "Patient Content",
type: "signalCheckbox",
},
{
question_key: 'appointment_cancel',
question: "Please read and accept",
linktext: "Appointment Cancel",
type: "signalCheckbox",
},
{
question_key: 'medicare_disclaimer',
question: "Please read and accept",
linktext: "Medicare Disclaimer",
type: "signalCheckbox",
},
{
question_key: 'telehealth_concent',
question: "Please read and accept",
linktext: "Telehealth Concent",
type: "signalCheckbox",
},
{
question_key: 'secondary_contact',
question: "Please read and accept",
linktext: "Secondary Contact Disclosure(optional)",
type: "signalCheckbox",
},
]);
const validateQuestion = async () => {
const { valid: isValid } = await questionForm.value?.validate();
console.log('isValid ', isValid);
if (isValid)
saveAnswers()
};
onMounted(async () => {
window.addEventListener('resize', checkIfMobile);
await getPatientInfo()
})
// Detach event listener on component unmount
onUnmounted(() => {
window.removeEventListener('resize', checkIfMobile);
});
const checkIfMobile = () => {
isMobile.value = window.innerWidth <= 768;
};
const getPatientInfo = async () => {
const patient_id = localStorage.getItem('patient_id')
const access_token = localStorage.getItem('access_token');
console.log()
isLoadingVisible.value = true;
await axios.post('/api/get-patient-detail/' + patient_id, {
headers: {
'Authorization': `Bearer ${access_token}`,
}
})
.then(response => {
console.log('Response:', response.data);
if (response.data) {
let patientData = response.data.patient
isLoadingVisible.value = false;
} else {
isLoadingVisible.value = false;
}
})
.catch(error => {
console.error('Error:', error);
});
}
const saveAnswers = () => {
isLoadingVisible.value = true;
const patient_id = localStorage.getItem('patient_id');
const jsonData = {
questions: questions.value.reduce((accumulator, question, index) => {
const answerValue = answers.value[question.question_key];
if (answerValue !== undefined) {
accumulator.push({
question_key: question.question_key,
type: question.type,
answer: answerValue,
});
}
return accumulator;
}, []),
};
let qaData = Array({ 'answers': jsonData.questions });
console.log('answers ', JSON.stringify(qaData));
// You can send the jsonData to your server or use it as needed
axios.post('/api/medical-history-question-post/' + patient_id,
{
answers: jsonData.questions
}
)
.then(response => {
console.log('answer api', response.data)
// console.log(chooseDate);
isLoadingVisible.value = false;
router.replace(route.query.to && route.query.to != '/additional-information' ? String(route.query.to) : '/shipping-information')
})
.catch(error => {
isLoadingVisible.value = false;
// console.error(error);
});
};
const getCurrentDate = () => {
const today = new Date();
const year = today.getFullYear();
let month = today.getMonth() + 1;
let day = today.getDate();
// Format the date to match the input type="date" format
month = month < 10 ? `0${month}` : month;
day = day < 10 ? `0${day}` : day;
return `${year}-${month}-${day}`;
};
const openDialog = (question_key) => {
currentDialoagkey.value = question_key;
isDialogVisible.value = true;
window.addEventListener('click', closeDialogOnOutsideClick);
};
const closeDialogOnOutsideClick = (event) => {
const dialogElement = refs.myDialog.$el;
if (!dialogElement.contains(event.target)) {
isDialogVisible.value = false;
window.removeEventListener('click', closeDialogOnOutsideClick);
}
};
const handleContinueClick = (currentDialoagkey) => {
isDialogVisible.value = false; // Hide the dialog
answers.value[currentDialoagkey] = true; // Update the answers object
};
const breadcrums = [
{
title: 'Category',
disabled: false,
to: '/category',
},
{
title: localStorage.getItem('category_name'),
disabled: false,
},
];
const radioContent = [
{
title: 'Basic',
subtitle: 'Free',
desc: 'Get 1 project with 1 team member.',
value: 'basic',
},
{
title: 'Premium',
subtitle: '$45.80',
value: 'premium',
desc: 'Get 5 projects with 5 team members.',
},
]
const currentTab = ref(0);
const percentage = ref(0);
const tabItemText = 'hortbread chocolate bar marshmallow bear claw tiramisu chocolate cookie wafer. Gummies sweet brownie brownie marshmallow chocolate cake pastry. Topping macaroon shortbread liquorice dragée macaroon.'
const changeTabOnArrowPress = (event) => {
if (event.key === 'ArrowRight' || event.key === 'ArrowDown') {
currentTab.value = (currentTab.value + 1) % 10;
} else if (event.key === 'ArrowLeft' || event.key === 'ArrowUp') {
currentTab.value = (currentTab.value - 1 + 10) % 10;
}
};
watchEffect(() => {
window.addEventListener('keydown', changeTabOnArrowPress);
return () => {
window.removeEventListener('keydown', changeTabOnArrowPress);
};
});
const nextTab = (category_id, id, value) => {
// await store.dispatch('saveCategoryAnswer')
previousQid.value = id;
// console.log("nextTabid", previousQid.value);
selectedQuestion.value.push({ id: id, value: value });
// console.log("selectedQuestion", selectedQuestion.value);
store.dispatch('setSelectedQuestionAnswer', {
category_answers: selectedQuestion.value,
})
currentTab.value = currentTab.value + 1;
percentage.value = (currentTab.value / question_length.value) * 100;
// console.log("per", percentage.value);
}
const backTab = (id, indx) => {
let prevIndex = 0;
console.log("backTabid", previousQid.value, id, indx);
const data = store.getters.getSelectedQuestionAnswer;
const entries = Object.entries(data);
console.log("entries", entries[indx]);
for (const [index, [key, value]] of entries.entries()){
console.log("ans", index, value, key);
if(value.id == previousQid.value ){
if(index > 0){
previousQid.value = entries[index - 1][1].id;
console.log("abc",previousQid.value ,value.id, entries[index - 1][1]);
}else{
previousQid.value = entries[0][1].id;
console.log("abc",previousQid.value ,value.id, entries[0][1]);
}
selectedQuestionIndex.value[value.id] = value.value;
}
}
if (currentTab.value == 0) {
} else {
currentTab.value = currentTab.value - 1;
percentage.value = (currentTab.value / question_length.value) * 100;
}
}
const parseOptions = (optionsString) => {
try {
const optionsArray = phpUnserialize(optionsString);
return optionsArray.map((option, index) => ({ key: index, value: option }));
} catch (error) {
console.error('Error parsing options:', error);
return [];
}
};
const selectedQuestionIndex = ref(new Array(5).fill(false));
const subquestion1 = ref([
{ title: 'Test 1', key: 'Test 1' },
{ title: 'Test 2', key: 'Test 2' },
{ title: 'Test 3', key: 'Test 3' }
]);
const checked = ref([]);
const visibleQuestion = ref(-1); // Initially, no question is visible
const toggleSubquestion = (index) => {
alert(index);
checked.value[index] = !checked.value[index];
if (checked.value[index]) {
// If the checkbox is checked, set the visibleQuestion index to the next question
visibleQuestion.value = Math.min(index + 1, subquestion1.value.length - 1);
}
};
</script>
<template>
<v-breadcrumbs :items="breadcrums" class="text-primary pt-0 pb-0 mb-5">
<template v-slot:divider style="padding-top:0px; padding-bottom:0px">
<v-icon icon="mdi-chevron-right" style="padding-top:0px;"></v-icon>
</template>
</v-breadcrumbs>
<div class="auth-wrapper d-flex align-center justify-center">
<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 class="auth-card pt-0 rounded-5" max-width="400">
<div class="d-flex align-items-center">
<VProgressLinear v-model="percentage" height="10" width="100" color="success" :rounded="true" />
</div>
<div class="pt-2 pb-3 text-end">{{ currentTab + 1 }}/{{ question_length }}</div>
<VCard max-width="400" class="category-card">
<p style="width: 350px;"></p>
<VCardText class="pt-0 px-0">
<VWindow v-model="currentTab" style="max-width:400px">
<VWindowItem v-for="(item,index) in medical_question" :key="item" :value="item">
<VImg :src="previousButton" width="25" height="25" @click="backTab(item.id, index)"
style="cursor: pointer;">
</VImg>
<div v-if="item.question_type == 'checkbox'">
<h4 class="text-center mt-0 mb-2">{{ item.question }}</h4>
<VCard class="mb-2 selectedCard"
v-for="( option, optionIndex ) in item.question_options" :key="optionIndex"
:value="optionIndex" v-model="answers[item.id]">
<VRow class="text-center pb-2 px-4 pt-4" :class="selectedQuestionIndex[item.id] === option ? 'selected-category-card' : ''" >
<VCol col="auto" class="category-card-option">
<div class="text-center flex-wrap gap-0 pb-2 text-capitalize">
{{ option }}
</div>
</VCol>
</VRow>
</VCard>
<div class="text-center py-2" @click="() => nextTab(item.id, option)">
<VBtn size="small">Submit</VBtn>
</div>
</div>
<div v-if="item.question_type === 'radio'">
<h4 class="text-center mt-0 mb-2">{{ item.question }}</h4>
<VCard class="mb-2 selectedCard" @click="() => nextTab(item.id, option)"
v-for="( option, optionIndex ) in item.question_options" :key="optionIndex"
:value="optionIndex" v-model="answers[item.id]">
<VRow class="text-center pb-2 px-4 pt-4" :class="selectedQuestionIndex[item.id] === option ? 'selected-category-card' : ''" >
<VCol col="auto" class="category-card-option">
<div class="text-center flex-wrap gap-0 pb-2 text-capitalize">
{{ option }}
</div>
</VCol>
</VRow>
</VCard>
</div>
<div v-if="item.question_type == 'text'">
<h4 class="text-center mt-5 mb-2">{{ item.question }}</h4>
<VCard class="mb-2">
<VRow class="text-center pb-0 px-4 pt-4">
<VCol col="12">
<div class="text-center flex-wrap gap-0">
<v-text-field v-model="answers[item.id]"/>
</div>
<div class="text-center py-2" @click="nextTab()">
<VBtn size="small">Submit</VBtn>
</div>
</VCol>
</VRow>
</VCard>
</div>
<div v-if="item.sub_questions" v-for="subquestion in item.sub_questions"
:key="subquestion">
<div v-if="subquestion.question && subquestion.sub_question_type == 'checkbox'">
<h4 class="text-center mt-5 mb-2">{{ subquestion.question }}</h4>
<VCard class="mb-2 selectedCard" @click="() => nextTab(item.id, opt)"
v-for="( opt, optionIndex ) in parseOptions(subquestion.sub_question_options)" :key="optionIndex">
<VRow class=" text-center pb-2 px-4 pt-4 selectedCard">
<VCol col="auto" class="category-card-option selectedCard">
<div class="text-center flex-wrap gap-0 pb-2">
{{ opt.value }}
</div>
</VCol>
</VRow>
</VCard>
</div>
<div v-if="subquestion.question && subquestion.sub_question_type == 'radio'">
<h4 class="text-center mt-5 mb-2">{{ subquestion.question }}</h4>
<VCard class="mb-2 selectedCard" @click="() => nextTab(item.id, opt)"
v-for="( opt, optionIndex ) in parseOptions(subquestion.sub_question_options)" :key="optionIndex">
<VRow class=" text-center pb-2 px-4 pt-4 selectedCard">
<VCol col="auto" class="category-card-option selectedCard">
<div class="text-center flex-wrap gap-0 pb-2">
{{ opt.value }}
</div>
</VCol>
</VRow>
</VCard>
</div>
</div>
<div v-if="item.sub_questions.sub_questions" v-for="sub_subquestion in item.sub_questions.sub_questions"
:key="subquestion">
<div v-if="sub_subquestion.question && subquestion.sub_question_type == 'checkbox'">
<h4 class="text-center mt-5 mb-2">{{ subquestion.question }}</h4>
<VCard class="mb-2 selectedCard"
v-for="( opt, optionIndex ) in parseOptions(sub_subquestion.sub_question_options)" :key="optionIndex">
<VRow class=" text-center pb-2 px-4 pt-4 selectedCard">
<VCol col="auto" class="category-card-option selectedCard">
<div class="text-center flex-wrap gap-0 pb-2">
{{ opt.value }} Test
</div>
</VCol>
</VRow>
</VCard>
</div>
<div v-if="subquestion.question && subquestion.sub_question_type == 'radio'">
<h4 class="text-center mt-5 mb-2">{{ subquestion.question }}</h4>
<VCard class="mb-2 selectedCard" @click="() => nextTab(item.id, opt)"
v-for="( opt, optionIndex ) in parseOptions(subquestion.sub_question_options)" :key="optionIndex">
<VRow class=" text-center pb-2 px-4 pt-4 selectedCard">
<VCol col="auto" class="category-card-option selectedCard">
<div class="text-center flex-wrap gap-0 pb-2">
{{ opt.value }}
</div>
</VCol>
</VRow>
</VCard>
</div>
</div>
</VWindowItem>
</VWindow>
</VCardText>
</VCard>
</div>
</div>
<div>
</div>
</template>
<style lang="scss">
.selectedCard div :hover,
.selectedCard:hover,
.selectedCard h4:hover {
background-color: rgb(105, 108, 255);
color: #fff !important;
cursor: pointer;
}
.category-card-option {
padding: 11px 15px;
}
.selected-category-card{
background-color: rgb(105, 108, 255);
color: #fff !important;
cursor: pointer;
}
// .selectedCard h4:hover {
// color: #fff;
// }
.category-card {
background: none !important;
box-shadow: none !important;
border: none !important;
}
.v-slide-group__next {
display: none !important;
}
.card-title {
font-family: "Public Sans", -apple-system, BlinkMacSystemFont, "Segoe UI", "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif;
}
.v-list-item-title {
white-space: inherit !important;
;
}
.v-list {
min-height: 168px;
}
@media (min-width: 320px) and (max-width: 768px) {
.v-container {
padding: 0px !important;
}
.auth-wrapper {
padding: 0px !important;
}
.input-width {
max-width: 100% !important;
}
}
// .input-width {
// max-width: 50%;
// }
.form-check-input[type=checkbox] {
border-radius: 0.25em;
}
.form-check .form-check-input {
float: left;
margin-right: 0.5em;
}
.form-check-input {
cursor: pointer;
}
.form-check-input {
width: 1.2em;
height: 1.2em;
margin-top: 0.135em;
vertical-align: top;
background-color: #fff;
background-repeat: no-repeat;
background-position: center;
background-size: contain;
border: 2px solid #dbdade;
appearance: none;
}
.form-check .form-check-input:checked,
.form-check .form-check-input[type=checkbox]:indeterminate {
box-shadow: 0 0.125rem 0.25rem rgba(165, 163, 174, 0.3);
}
.form-check-input:checked[type=checkbox] {
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='11' height='13' viewBox='0 0 15 14' fill='none'%3E%3Cpath d='M3.41667 7L6.33333 9.91667L12.1667 4.08333' stroke='%23fff' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
}
.form-check-input:checked,
.form-check-input[type=checkbox]:indeterminate {
background-color: #7367f0;
border-color: #7367f0;
}
.form-check-input:checked {
background-color: #7367f0;
border-color: #7367f0;
}
.form-check-input[type=checkbox] {
border-radius: 0.25em;
}
</style>

View File

@@ -0,0 +1,120 @@
<script setup>
import { onMounted, onUnmounted } from 'vue';
import { useStore } from 'vuex';
const store = useStore()
const isDesktop = ref(true)
const categories = ref([]);
const isMobile = ref(window.innerWidth <= 768);
const answers = ref([]);
onBeforeMount(async () => {
await store.dispatch('getCategory')
categories.value = store.getters.getCategories.data;
console.log("categories", categories.value);
});
const updateCategory = async (id, name) => {
// answers.value.push({ category_id:id})
await store.dispatch('setSelectCategoryId', true);
localStorage.setItem('category_id', id)
localStorage.setItem('category_name', name)
store.dispatch('updateIsLoading', false)
}
onMounted(async () => {
window.addEventListener('resize', checkIfMobile);
})
onUnmounted(() => {
window.removeEventListener('resize', checkIfMobile);
});
const checkIfMobile = () => {
isMobile.value = window.innerWidth <= 768;
};
</script>
<template>
<div class="auth-wrapper d-flex align-center justify-center">
<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 class="auth-card pt-0 rounded-5" max-width="350">
<VRow>
<VCol cols="12" class="p-0">
<div v-for="category in categories" @click="updateCategory(category.id, category.name)"
:key="category">
<RouterLink :to="'/questionere/' + category.category_link"
v-if="category.name != 'Family history'">
<VCard :width="isMobile ? '360' : '500'" class="mb-2">
<VRow class="justify-space-between pb-2 px-4 pt-4">
<Vcol col="auto" style="padding:11px 15px;">
<div class="d-flex flex-wrap gap-0 pb-2">
<img :src="'/assets/images/asthma/' + category.icon"
class=" mx-3 ml-0 category-icon" width="24" height="24">
<h4>{{ category.name }}</h4>
</div>
</Vcol>
<Vcol col="auto">
<div class="d-flex align-center gap-0 pt-3 pb-2">
<div class="" v-if="category.question_count > 0">
<v-icon color="rgb(var(--v-theme-yellow))" size="20"
style="margin-top: -9px;">mdi-check-circle</v-icon>
<!-- <VImg :src="Success" class="success-image" style="top: -2px;"></VImg> -->
</div>
<div class="">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24"
viewBox="0 0 24 24" fill="none" stroke="currentColor"
stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
class="icon icon-tabler icons-tabler-outline icon-tabler-chevron-right">
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
<path d="M9 6l6 6l-6 6" />
</svg>
</div>
</div>
</Vcol>
</VRow>
<div class="d-flex align-items-center">
<VProgressLinear model-value="100" height="2" color="rgb(var(--v-theme-yellow))"
:rounded="true" />
</div>
</VCard>
</RouterLink>
</div>
</VCol>
</VRow>
</div>
</div>
</template>
<style lang="scss">
.success-image {
display: block;
position: relative;
height: 30px;
width: 30px;
margin: 0 auto;
// top: -1px/;
}
@media (min-width: 992px) {
.category-cards {
max-width: 500px !important;
}
}
@media (max-width: 991px) {
.category-cards {
max-width: 300px !important;
}
}
.category-icon {
color: rgb(105, 108, 255);
}
</style>

View File

@@ -0,0 +1,730 @@
<script setup>
import StartOverPupup from '@/views/pages/home/StartOverPupup.vue';
import axios from '@axios';
import {
cardNumberValidator,
cvvValidator,
expiryValidator,
requiredValidator,
} from '@validators';
import { onBeforeMount, onMounted } from 'vue';
import { useRoute, useRouter } from 'vue-router';
import { useStore } from 'vuex';
import Cart from '../layouts/components/cart.vue';
import CustomNav from '../layouts/components/navbar-custom.vue';
const store = useStore()
const router = useRouter()
const route = useRoute()
const isMobile = ref(window.innerWidth <= 768); // Assuming mobile width is less than or equal to 768px
const patient_id = localStorage.getItem('patient_id')
const access_token = localStorage.getItem('access_token');
const isTonalSnackbarVisible = ref(false)
const patientResponse = ref(false)
const isLoadingVisible = ref(false)
const paymentForm = ref()
const cardNumber = ref('')
const expiry = ref('')
const cvv = ref('')
const firstName = ref('')
const lastName = ref('')
const email = ref('')
const phone = ref('')
const address = ref('')
const apt = ref('')
const city = ref('')
const state = ref('')
const zipcode = ref('')
const billingaddress = ref('')
const billingapt = ref('')
const billingcity = ref('')
const billingstate = ref('')
const billingzipcode = ref('')
const termAndCondtiton = ref(true)
const billingSection = ref(false)
const schedule_consultant = ref(true)
const order_complete = ref(false);
const planName = ref(null)
const planAmount = ref(null)
const list_one_title = ref(null)
const list_sub_title = ref(null)
const list_two_title = ref(null)
const prescription_required = ref(null)
const shipping_price = ref(null)
const confirmPopup = ref(false)
const verifiedAddress = ref(null);
const suggestedStreet = ref(null);
const suggestedCity = ref(null);
const suggestedState = ref(null);
const suggestedZip = ref(null);
const suggestedAddress = ref(null);
const selectedAddress = ref('notsuggested')
const typedAddress = ref(null)
const seetingPlanLogo = ref();
const products = JSON.parse(localStorage.getItem('cart_products'));
const labKits = JSON.parse(localStorage.getItem('labkits'));
const totalShipping = ref(0)
const totalAmount = ref(0)
const grandTotal = ref(0)
const prescreptionRequired = ref(false)
const paymentPopup = ref(false)
const paymentPopupText = "Your payment for $"+labKits[0].amount+".00 was successful"
const states = ref([
{ name: 'Alabama', abbreviation: 'AL' },
{ name: 'Alaska', abbreviation: 'AK' },
{ name: 'Arizona', abbreviation: 'AZ' },
{ name: 'Arkansas', abbreviation: 'AR' },
{ name: 'Howland Island', abbreviation: 'UM-84' },
{ name: 'Delaware', abbreviation: 'DE' },
{ name: 'Maryland', abbreviation: 'MD' },
{ name: 'Baker Island', abbreviation: 'UM-81' },
{ name: 'Kingman Reef', abbreviation: 'UM-89' },
{ name: 'New Hampshire', abbreviation: 'NH' },
{ name: 'Wake Island', abbreviation: 'UM-79' },
{ name: 'Kansas', abbreviation: 'KS' },
{ name: 'Texas', abbreviation: 'TX' },
{ name: 'Nebraska', abbreviation: 'NE' },
{ name: 'Vermont', abbreviation: 'VT' },
{ name: 'Jarvis Island', abbreviation: 'UM-86' },
{ name: 'Hawaii', abbreviation: 'HI' },
{ name: 'Guam', abbreviation: 'GU' },
{ name: 'United States Virgin Islands', abbreviation: 'VI' },
{ name: 'Utah', abbreviation: 'UT' },
{ name: 'Oregon', abbreviation: 'OR' },
{ name: 'California', abbreviation: 'CA' },
{ name: 'New Jersey', abbreviation: 'NJ' },
{ name: 'North Dakota', abbreviation: 'ND' },
{ name: 'Kentucky', abbreviation: 'KY' },
{ name: 'Minnesota', abbreviation: 'MN' },
{ name: 'Oklahoma', abbreviation: 'OK' },
{ name: 'Pennsylvania', abbreviation: 'PA' },
{ name: 'New Mexico', abbreviation: 'NM' },
{ name: 'American Samoa', abbreviation: 'AS' },
{ name: 'Illinois', abbreviation: 'IL' },
{ name: 'Michigan', abbreviation: 'MI' },
{ name: 'Virginia', abbreviation: 'VA' },
{ name: 'Johnston Atoll', abbreviation: 'UM-67' },
{ name: 'West Virginia', abbreviation: 'WV' },
{ name: 'Mississippi', abbreviation: 'MS' },
{ name: 'Northern Mariana Islands', abbreviation: 'MP' },
{ name: 'United States Minor Outlying Islands', abbreviation: 'UM' },
{ name: 'Massachusetts', abbreviation: 'MA' },
{ name: 'Connecticut', abbreviation: 'CT' },
{ name: 'Florida', abbreviation: 'FL' },
{ name: 'District of Columbia', abbreviation: 'DC' },
{ name: 'Midway Atoll', abbreviation: 'UM-71' },
{ name: 'Navassa Island', abbreviation: 'UM-76' },
{ name: 'Indiana', abbreviation: 'IN' },
{ name: 'Wisconsin', abbreviation: 'WI' },
{ name: 'Wyoming', abbreviation: 'WY' },
{ name: 'South Carolina', abbreviation: 'SC' },
{ name: 'Arkansas', abbreviation: 'AR' },
{ name: 'South Dakota', abbreviation: 'SD' },
{ name: 'Montana', abbreviation: 'MT' },
{ name: 'North Carolina', abbreviation: 'NC' },
{ name: 'Palmyra Atoll', abbreviation: 'UM-95' },
{ name: 'Puerto Rico', abbreviation: 'PR' },
{ name: 'Colorado', abbreviation: 'CO' },
{ name: 'Missouri', abbreviation: 'MO' },
{ name: 'New York', abbreviation: 'NY' },
{ name: 'Maine', abbreviation: 'ME' },
{ name: 'Tennessee', abbreviation: 'TN' },
{ name: 'Georgia', abbreviation: 'GA' },
{ name: 'Louisiana', abbreviation: 'LA' },
{ name: 'Nevada', abbreviation: 'NV' },
{ name: 'Iowa', abbreviation: 'IA' },
{ name: 'Idaho', abbreviation: 'ID' },
{ name: 'Rhode Island', abbreviation: 'RI' },
{ name: 'Washington', abbreviation: 'WA' },
{ name: 'Ohio', abbreviation: 'OH' },
// ... (add the rest of the states)
]);
const sortedStates = computed(() => {
return states.value.slice().sort((a, b) => {
return a.name.localeCompare(b.name);
});
});
const errors = ref({
address: undefined,
city: undefined,
state: undefined,
zipcode: undefined,
country: undefined,
})
onBeforeMount(async () => {
products.forEach(product => {
var price = 0
if (product.is_prescription_required == 1) {
prescreptionRequired.value = true
if (labKits[0].amount)
price = labKits[0].amount
product.title = 'LabKit For ' + product.title
product.price = productTotalPreReq(price)
totalAmount.value += price;
console.log('Prescreption Req Price', price)
} else {
prescreptionRequired.value = false
price = parseFloat(product.price);
product.price = productTotal(product)
totalAmount.value += product.qty * price;
console.log('Not Req Price', price)
}
const shippingPrice = parseFloat(product.shipping_cost);
totalShipping.value += product.qty * shippingPrice;
});
let options = { style: 'currency', currency: 'USD', minimumFractionDigits: 2, maximumFractionDigits: 2 };
grandTotal.value = new Intl.NumberFormat('en-US', options).format(parseFloat(totalAmount.value) + parseFloat(totalShipping.value));
totalAmount.value = new Intl.NumberFormat('en-US', options).format(totalAmount.value);
totalShipping.value = new Intl.NumberFormat('en-US', options).format(totalShipping.value);
// grandTotal.value = parseFloat(totalAmount.value) + parseFloat(totalShipping.value)
store.dispatch('updateIsLoading', true)
store.dispatch('updateCurrentPage', 'checkout')
localStorage.setItem('currentPage', 'checkout')
await store.dispatch('getPatientInfo')
await store.dispatch('getPlanInfo')
// await store.dispatch('getPatientAppointment')
await store.dispatch('getAdditionalInformation')
//Plan Information
planName.value = store.getters.getPatientPlan.plan_name
planAmount.value = store.getters.getPatientPlan.plan_amount
list_one_title.value = store.getters.getPatientPlan.list_one_title
list_sub_title.value = store.getters.getPatientPlan.list_sub_title
list_two_title.value = store.getters.getPatientPlan.list_two_title
prescription_required.value = store.getters.getPatientPlan.prescription_required
shipping_price.value = store.getters.getPatientPlan.shipping_price
firstName.value = store.getters.getPatient.first_name
lastName.value = store.getters.getPatient.last_name
email.value = store.getters.getPatient.email
phone.value = store.getters.getPatient.phone_no
address.value = store.getters.getShippingInformation.address
apt.value = store.getters.getShippingInformation.shipping_address2
city.value = store.getters.getShippingInformation.shipping_city
state.value = store.getters.getShippingInformation.shipping_state
zipcode.value = store.getters.getShippingInformation.shipping_zipcode
billingaddress.value = store.getters.getShippingInformation.billing_address1
billingapt.value = store.getters.getShippingInformation.billing_address2
billingcity.value = store.getters.getShippingInformation.billing_city
billingstate.value = store.getters.getShippingInformation.billing_state
billingzipcode.value = store.getters.getShippingInformation.billing_zipcode
store.dispatch('updateIsLoading', false)
})
onMounted(async () => {
window.addEventListener('resize', checkIfMobile);
let setting = await axios.post('/api/settings', {})
console.log(setting.data)
seetingPlanLogo.value = '/assets/logo/' + setting.data.logo
})
// Detach event listener on component unmount
onUnmounted(() => {
window.removeEventListener('resize', checkIfMobile);
});
const productTotalPreReq = (price) => {
let options = { style: 'currency', currency: 'USD', minimumFractionDigits: 2, maximumFractionDigits: 2 };
return new Intl.NumberFormat('en-US', options).format(parseFloat(price));
};
const productTotal = (product) => {
let options = { style: 'currency', currency: 'USD', minimumFractionDigits: 2, maximumFractionDigits: 2 };
return new Intl.NumberFormat('en-US', options).format(product.qty * parseFloat(product.price));
};
const checkIfMobile = () => {
isMobile.value = window.innerWidth <= 768;
};
const billingAddressShow = () => {
if (billingSection.value == false) {
billingSection.value = true;
} else {
billingSection.value = false;
}
}
const corfirmFun = async () => {
confirmPopup.value = true;
if (address.value && city.value && state.value && zipcode.value)
typedAddress.value = address.value + ' ' + city.value + ' ' + state.value + ' ' + zipcode.value
if (address.value)
await verfiFyAddress(address.value);
}
const validatePayment = async () => {
const { valid: isValid } = await paymentForm.value?.validate();
console.log('isValid ', isValid);
if (isValid) {
await saveOrderInfo()
await processPayment()
// await updatePatientAddress()
// if (prescreptionRequired.value)
if (!store.getters.getErrorMessage) {
if (store.getters.getPaymentProcessed) {
paymentPopup.value = store.getters.getPaymentProcessed
setTimeout(() => {
router.replace(route.query.to && route.query.to !== '/checkout' ? String(route.query.to) : '/book-appointment')
}, 5000)
}
}
// else
// router.replace(route.query.to && route.query.to != '/checkout' ? String(route.query.to) : '/thankyou')
}
};
// const processPayment = async () => {
// isLoadingVisible.value = true;
// await axios.post('/api/process-payment')
// .then(response => {
// console.log(response.data)
// isLoadingVisible.value = false;
// })
// .catch(error => {
// isLoadingVisible.value = false;
// console.error(error);
// });
// }
const updatePatientAddress = async () => {
console.log('updatePatientAddress');
await store.dispatch('updatePatientAddress', {
address: address.value,
city: city.value,
state: state.value,
zip_code: zipcode.value,
country: 'United States',
})
}
const saveOrderInfo = async () => {
const productIds = products.map(product => ({ plans_id: product.id, quantity: product.qty, subscription: product.subscription, onetime: product.onetime }));
console.log('saveOrderInfo');
isLoadingVisible.value = true;
let addressVal = null
let cityVal = null
let stateVal = null
let zipcodeVal = null
if (selectedAddress.value == 'suggested') {
addressVal = suggestedAddress.value
cityVal = suggestedCity.value
stateVal = suggestedState.value
zipcodeVal = suggestedZip.value
} else {
addressVal = address.value
cityVal = city.value
stateVal = state.value
zipcodeVal = zipcode.value
}
await store.dispatch('saveShippingInformation', {
first_name: firstName.value,
last_name: lastName.value,
email: email.value,
phone: phone.value,
patient_id: patient_id,
shipping_address1: addressVal,
shipping_address2: apt.value,
shipping_city: cityVal,
shipping_state: stateVal,
shipping_zipcode: zipcodeVal,
shipping_country: 'United States',
billing_address1: billingaddress.value,
billing_address2: billingapt.value,
billing_city: billingcity.value,
billing_state: billingstate.value,
billing_zipcode: billingzipcode.value,
billing_country: "",
shipping_amount: parseFloat(totalShipping.value.replace(/[^0-9.-]+/g, "")),
total_amount: parseFloat(totalAmount.value.replace(/[^0-9.-]+/g, "")),
items: productIds
})
}
const verfiFyAddress = async (address) => {
isLoadingVisible.value = true;
suggestedAddress.value = null
let addressT = address
// let addressT = '11 pinewood Pi'
let cityt = 'BOWLING GREEN'
let statet = 'KY'
let zipt = '42101'
const geocoder = new google.maps.Geocoder();
geocoder.geocode({ address: addressT }, (results, status) => {
if (status === 'OK' && results.length > 0) {
verifiedAddress.value = results[0];
} else {
console.error('Geocode was not successful for the following reason:', status);
verifiedAddress.value = null;
}
console.log(verifiedAddress.value)
suggestedStreet.value = getStreetAddress()
suggestedCity.value = getAddressComponent('locality')
suggestedState.value = getAddressComponent('administrative_area_level_1', true)
suggestedZip.value = getAddressComponent('postal_code')
if (suggestedStreet.value && suggestedCity.value && suggestedState.value && suggestedZip.value)
suggestedAddress.value = `${suggestedStreet.value}, ${suggestedCity.value}, ${suggestedState.value} ${suggestedZip.value}`;
console.log(suggestedAddress.value)
console.log('street : ', getStreetAddress())
console.log('city : ', getAddressComponent('locality'))
console.log('state : ', getAddressComponent('administrative_area_level_1'))
console.log('zip : ', getAddressComponent('postal_code'))
// const response = await axios.get('http://production.shippingapis.com/ShippingAPI.dll', {
// params: {
// API: 'Verify',
// XML: `<AddressValidateRequest USERID="201CBSUR1218"><Address>${addressT}</Address><City>${cityt}</City><State>${statet}</State><Zip5>${zipt}</Zip5></AddressValidateRequest>`
// }
// });
isLoadingVisible.value = false;
})
}
const getStreetAddress = () => {
if (!verifiedAddress.value) return '';
return verifiedAddress.value.formatted_address.split(',')[0];
};
const getAddressComponent = (type, shortname) => {
if (!verifiedAddress.value) return '';
for (const component of verifiedAddress.value.address_components) {
if (component.types.includes(type)) {
if (shortname) {
return component.short_name
} else {
return component.long_name;
}
}
}
return '';
};
const processPayment = async () => {
// Split the string by "/"
let [month, year] = expiry.value.split("/");
year = year.length === 2 ? (year >= "50" ? "19" + year : "20" + year) : year;
console.log('year',expiry.value,year)
await store.dispatch('processPayment',{
card_number: cardNumber.value,
cvv: cvv.value,
expiration_month: month,
expiration_year: year
})
}
const cardNumberFormat = () => {
cardNumber.value = cardNumber.value.replace(/\D/g, '').substring(0, 16);
};
const formatExpiry = () => {
// Automatically format the input to MM/YY format
expiry.value = expiry.value.replace(/\D/g, '').slice(0, 4).replace(/(\d{2})(\d{2})/, '$1/$2');
};
const handleCVVInput = () => {
// Remove non-digit characters from input
cvv.value = cvv.value.replace(/\D/g, '');
};
watch(selectedAddress, (newValue) => {
console.log('selectedAddress.value',selectedAddress.value)
if (newValue === 'suggested') {
address.value = suggestedStreet.value;
apt.value = suggestedStreet.value;
city.value = suggestedCity.value;
state.value = suggestedState.value;
zipcode.value = suggestedZip.value;
} else if (newValue === 'notsuggested') {
// Split the typedAddress into its components
const addressParts = typedAddress.value.split(' ');
address.value = addressParts.slice(0, -3).join(' ');
apt.value = addressParts.slice(0, -3).join(' ');
city.value = addressParts[addressParts.length - 3];
state.value = addressParts[addressParts.length - 2];
zipcode.value = addressParts[addressParts.length - 1];
}
});
const backFun = () => {
store.dispatch('updateIsLoading', true)
router.replace(route.query.to && route.query.to != '/checkout' ? String(route.query.to) : '/additional-information')
}
</script>
<template>
<StartOverPupup :showPopup="store.getters.getShowStartOverPupup"></StartOverPupup>
<VDialog v-model="store.getters.getIsLoading" width="110" height="150" color="yellow-theme-button">
<VCardText class="" style="color: white !important;">
<div class="demo-space-x">
<VProgressCircular :size="40" color="yellow-theme-button" indeterminate />
</div>
</VCardText>
</VDialog>
<VSnackbar
v-model="store.getters.getIsTonalSnackbarVisible"
:timeout="5000"
location="top end"
variant="flat"
color="red"
>
{{ store.getters.getErrorMessage }}
</VSnackbar>
<v-dialog
v-model="paymentPopup"
width="auto"
>
<v-card
max-width="400"
prepend-icon="mdi-checkbox-marked-circle"
:text=paymentPopupText
title="Payment Success"
>
</v-card>
</v-dialog>
<VRow><CustomNav :logo='seetingPlanLogo'></CustomNav></VRow>
<VRow
style="min-height: 100dvh; margin: 0px;"
:style="isMobile ? { marginTop: '90px' } : { marginTop: '80px' }"
>
<VCol cols="12" md="6" class="bg-custom col-order-1"
:class="isMobile ? '' : ' d-flex align-center justify-center pa-4'">
<Cart></Cart>
</VCol>
<VCol cols="12" md="6" class="bg-custom-color col-order-2"
:class="isMobile ? '' : 'auth-wrapper d-flex align-center justify-center pa-4'">
<VCard class="auth-card pa-2 rounded-5" style="" :class="isMobile ? '' : 'card-wid'">
<VRow class=" mx-0 gy-3">
<!-- <VCol cols="12" lg="12" md="12">
<router-link to="/" class="text-center mb-2 mt-2"
style="width: 100%;position: relative;display: block;padding-top: 20px;">
<span class="text-center">
<VImg :src="seetingPlanLogo" width="250" height="50" class="logo-img" />
</span>
</router-link>
</VCol> -->
<VCol cols="12" md="12">
<v-card class="px-0" flat>
<VForm ref="paymentForm" @submit.prevent="() => { }">
<!-- <v-card class="" flat> -->
<div class="mb-3">
<h5 class="text-h5 mb-1 text-left mb-4">Shipping Information</h5>
<small>Please provide your shipping details below so that we can
promptly send you
the
product:</small>
</div>
<VRow>
<VCol cols="12" md="12">
<VTextField v-model="address" label="Address" :rules="[requiredValidator]"
density="comfortable" />
</VCol>
<VCol cols="12" md="12">
<VTextField v-model="apt" label="APT/Suite #" density="comfortable" />
</VCol>
</VRow>
<VRow>
<VCol cols="12" md="4">
<VTextField v-model="city" label="City" :rules="[requiredValidator]"
density="comfortable" />
</VCol>
<VCol cols="12" md="5">
<v-autocomplete clearable v-model="state" label="Select State"
:items="sortedStates" item-title="name" item-value="abbreviation"
:rules="[requiredValidator]" :error-messages="errors.state"
density="comfortable">
</v-autocomplete>
</VCol>
<VCol cols="12" md="3">
<VTextField type="number" v-model="zipcode" :rules="[requiredValidator]"
label="ZipCode" density="comfortable" />
</VCol>
</VRow>
<div class="mb-3">
<h4 class="mb-2 mt-4">
Card Information &nbsp;<VIcon>mdi-credit-card</VIcon>
</h4>
</div>
<VRow>
<VCol cols="12" lg="12" md="12">
<VTextField v-model="cardNumber" label="Credit Card Number*"
:rules="[requiredValidator, cardNumberValidator]"
placeholder="xxxxxxxxxxxxxxxx" @input="cardNumberFormat"
density="comfortable" />
</VCol>
<!-- <VCol cols="12" lg="4" md="4">
<VTextField v-model="zipcode" label="Zipcode*" type="number"
:rules="[requiredValidator]" placeholder="zipcode" density="comfortable"/>
</VCol> -->
<VCol cols="12" lg="6" md="6">
<VTextField v-model="expiry" label="Expiration Date*"
:rules="[requiredValidator, expiryValidator]" placeholder="MM/YY"
@input="formatExpiry" density="comfortable" />
</VCol>
<VCol cols="12" lg="6" md="6">
<VTextField v-model="cvv" :rules="[requiredValidator, cvvValidator]"
label="CVV*" maxlength="3" @input="handleCVVInput" density="comfortable" />
</VCol>
</VRow>
<!-- <VRow>
<VCol cols="12" md="12" class="px-4 mt-3">
<VCheckbox v-model="termAndCondtiton" @click=billingAddressShow
label="Billing Address same as shipping." />
</VCol>
</VRow> -->
<!-- </v-card> -->
<v-card class="px-2 mt-2 mb-2" flat v-if="billingSection">
<h3 class="mb-3">Billing Information</h3>
<VRow>
<VCol cols="12" md="6">
<VTextField v-model="billingaddress" label="Address"
:rules="[requiredValidator]" density="comfortable" />
</VCol>
<VCol cols="12" md="6">
<VTextField v-model="billingapt" label="APT/Suite #"
density="comfortable" />
</VCol>
</VRow>
<VRow>
<VCol cols="12" md="6">
<VTextField v-model="billingcity" label="City" :rules="[requiredValidator]"
density="comfortable" />
</VCol>
</VRow>
</v-card>
<div class="text-center mb-2 mt-4">
<VBtn class="px-4 mb-2" color="primary" variant="flat" block @click="corfirmFun"
style="background-color: rgb(var(--v-theme-yellow-theme-button)) !important;">
Checkout</VBtn>
<!-- <VBtn class="px-4" color="grey" variant="flat" @click="backFun"
:class="isMobile ? '' : 'mr-2'" block>
Back</VBtn> -->
</div>
<VDialog v-model="confirmPopup" refs="myDialog" persistent width="500">
<!-- <template v-slot:default="{ isActive }"> -->
<v-card>
<v-card-text>
<div class="mt-2 mb-2">
<h4>We've Found a Match from your Address.</h4>
<small>Select the correct address that match your current
address</small>
</div>
<v-radio-group v-model="selectedAddress" :rules="[requiredValidator]"
v-if="typedAddress || suggestedAddress">
<v-radio :label="suggestedAddress" v-if="suggestedAddress"
value="suggested"></v-radio>
<v-radio :label="typedAddress" value="notsuggested"
v-if="typedAddress"></v-radio>
</v-radio-group>
</v-card-text>
<v-card-actions>
<v-spacer></v-spacer>
<v-btn type="submit" text="Confirm" @click="validatePayment"></v-btn>
<v-btn text="Close" @click="confirmPopup = false"></v-btn>
</v-card-actions>
</v-card>
<!-- </template> -->
</VDialog>
</VForm>
</v-card>
</VCol>
</VRow>
</VCard>
</VCol>
</VRow>
</template>
<style scoped>
@media only screen and (max-width: 768px) {
.card-wid {
max-width: 600px !important;
min-width: auto !important;
}
.col-order-1 {
order: 2;
}
.col-order-2 {
order: 1;
}
}
@media only screen and (min-width: 769px) {
.col-order-1 {
order: 1;
}
.col-order-2 {
order: 2;
}
}
.total-font {
font-size: 20px;
margin-bottom: 5px;
}
.bg-custom {
background: #F3F3F3;
}
.bg-custom-color {
background: #E0F0E3;
}
.bg-white bg-change-bk .current-plan {
border: 2px solid rgb(var(--v-theme-primary));
}
.cut-text {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
text-decoration: line-through;
text-decoration-color: red;
text-decoration-thickness: 1px;
}
.plan-card {
margin: 0rem;
margin-bottom: 0;
}
.card-wid {
max-width: 600px;
}
.layout-wrapper {
justify-content: center;
}
.error-message {
color: #ff2f2f;
font-size: 15px;
}
</style>
<style lang="scss">
.bg-custom {
background: #F3F3F3;
}
.bg-custom-color {
background: #E0F0E3;
}
.total-font {
font-size: 20px;
margin-bottom: 5px;
}
.card-title {
font-family: "Public Sans", -apple-system, BlinkMacSystemFont, "Segoe UI", "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif;
}
.logo-img {
display: block;
position: relative;
margin: 0 auto;
}
</style>

View File

@@ -0,0 +1,225 @@
<script setup>
import axios from '@axios';
import {
requiredValidator
} from '@validators';
import { onMounted } from 'vue';
import { useRoute, useRouter } from 'vue-router';
const router = useRouter()
const route = useRoute()
const errors = ref({
dob: undefined,
gender: undefined,
marital_status: undefined,
height: undefined,
weight: undefined,
})
const isTonalSnackbarVisible = ref(false)
const patientResponse = ref(false)
const isLoadingVisible = ref(false)
const infoVForm = ref()
const dob = ref()
const gender = ref()
const martialStatus = ref(null)
const height = ref(null)
const weight = ref(null)
const patientId = ref()
const genders = ref([
{ name: 'Male', abbreviation: 'Male' },
{ name: 'Female', abbreviation: 'Female' },
{ name: 'Other', abbreviation: 'Other' },
]);
const maritalStatuses = ref([
{ name: 'Single', abbreviation: 'Single' },
{ name: 'Married', abbreviation: 'Married' },
{ name: 'Divorced', abbreviation: 'Divorced' },
]);
const heights = ref([
{ name: '5 ft 0 in', abbreviation: '5 ft 0 in' },
{ name: '5 ft 1 in', abbreviation: '5 ft 1 in' },
{ name: '5 ft 2 in', abbreviation: '5 ft 2 in' },
{ name: '5 ft 3 in', abbreviation: '5 ft 3 in' },
{ name: '5 ft 4 in', abbreviation: '5 ft 4 in' },
{ name: '5 ft 5 in', abbreviation: '5 ft 5 in' },
{ name: '5 ft 6 in', abbreviation: '5 ft 6 in' },
{ name: '5 ft 7 in', abbreviation: '5 ft 7 in' },
{ name: '5 ft 8 in', abbreviation: '5 ft 8 in' },
{ name: '5 ft 9 in', abbreviation: '5 ft 9 in' },
{ name: '5 ft 10 in', abbreviation: '5 ft 10 in' },
{ name: '5 ft 11 in', abbreviation: '5 ft 11 in' },
{ name: '6 ft 0 in', abbreviation: '6 ft 0 in' },
{ name: '6 ft 1 in', abbreviation: '6 ft 1 in' },
{ name: '6 ft 2 in', abbreviation: '6 ft 2 in' },
{ name: '6 ft 3 in', abbreviation: '6 ft 3 in' },
{ name: '6 ft 4 in', abbreviation: '6 ft 4 in' },
{ name: '6 ft 5 in', abbreviation: '6 ft 5 in' },
{ name: '6 ft 6 in', abbreviation: '6 ft 6 in' },
{ name: '6 ft 7 in', abbreviation: '6 ft 7 in' },
{ name: '6 ft 8 in', abbreviation: '6 ft 8 in' },
{ name: '6 ft 9 in', abbreviation: '6 ft 9 in' },
{ name: '6 ft 10 in', abbreviation: '6 ft 10 in' },
{ name: '6 ft 11 in', abbreviation: '6 ft 11 in' },
{ name: '7 ft 0 in', abbreviation: '7 ft 0 in' },
]);
const isMobile = ref(window.innerWidth <= 768); // Assuming mobile width is less than or equal to 768px
const checkIfMobile = () => {
isMobile.value = window.innerWidth <= 768;
};
// Attach event listener on component mount
onMounted(async () => {
window.addEventListener('resize', checkIfMobile);
await getPatientInfo()
});
// Detach event listener on component unmount
onUnmounted(() => {
window.removeEventListener('resize', checkIfMobile);
});
const getCurrentDate = () => {
const today = new Date();
const year = today.getFullYear();
let month = today.getMonth() + 1;
let day = today.getDate();
// Format the date to match the input type="date" format
month = month < 10 ? `0${month}` : month;
day = day < 10 ? `0${day}` : day;
return `${month}-${day}-${year}`;
};
const onSubmit = async () => {
infoVForm.value?.validate().then(async ({ valid: isValid }) => {
console.log('isValid ', isValid)
if (isValid)
await updatePatientDetails()
})
}
const getPatientInfo = async () => {
const patient_id = localStorage.getItem('patient_id')
const access_token = localStorage.getItem('access_token');
isLoadingVisible.value = true;
await axios.post('/api/get-patient-detail/' + patient_id, {
headers: {
'Authorization': `Bearer ${access_token}`,
}
})
.then(response => {
console.log('Response:', response.data);
if (response.data) {
let patientData = response.data.patient
patientId.value = patientData.id
dob.value = patientData.dob;
gender.value = patientData.gender;
martialStatus.value = patientData.marital_status;
height.value = patientData.height;
weight.value = patientData.weight;
isLoadingVisible.value = false;
} else {
isLoadingVisible.value = false;
}
})
.catch(error => {
console.error('Error:', error);
});
}
const updatePatientDetails = async () => {
console.log('updatePatientDetails');
await axios.post('/api/update-patient/' + patientId.value, {
dob: dob.value,
gender: gender.value,
marital_status: martialStatus.value,
height: height.value,
weight: weight.value,
}).then(r => {
console.log("updated patient ", r.data);
localStorage.setItem('profileCompleted', '1')
router.push('/overview');
isTonalSnackbarVisible.value = true;
patientResponse.value = r.data.message;
isLoadingVisible.value = false;
}).catch(e => {
console.log(e.response);
const { errors: formErrors } = e.response.data.errors;
console.error("Error", e.response.data.errors)
});
}
</script>
<template>
<VSnackbar v-model="isTonalSnackbarVisible" :timeout="5000" location="top end" variant="flat" color="success">
{{ patientResponse }}
</VSnackbar>
<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>
<VAlert color="red text-white mb-0">
<p class="text-white mb-0">Please finish updating your profile.</p>
</VAlert>
<VRow>
<VCol cols="12">
<VCard class="mt-2">
<VCardText>
<h4>Personal Details</h4>
<VForm class="mt-6" ref="infoVForm" @submit.prevent="onSubmit">
<VRow>
<VCol md="4" cols="12">
<VTextField v-model="dob" :max="getCurrentDate()" label="Date of Birth"
placeholder="Date of Birth" type="date" :rules="[requiredValidator]" />
</VCol>
<VCol md="4" cols="12">
<v-select v-model="gender" label="Select Gender" :items="genders" item-title="name"
item-value="abbreviation" :rules="[requiredValidator]"
:error-messages="errors.gender">
</v-select>
</VCol>
<VCol cols="12" md="4">
<v-select v-model="martialStatus" label="Select Marital Status" :items="maritalStatuses"
item-title="name" item-value="abbreviation" :rules="[requiredValidator]"
:error-messages="errors.martial_status">
</v-select>
</VCol>
<VCol cols="12" md="4">
<v-select v-model="height" label="Select Height" :items="heights" item-title="name"
item-value="abbreviation" :rules="[requiredValidator]"
:error-messages="errors.height">
</v-select>
</VCol>
<VCol cols="12" md="4">
<VTextField v-model="weight" label="Weight" :rules="[requiredValidator]" />
</VCol>
<!-- 👉 Form Actions -->
<VCol cols="12" class="d-flex flex-wrap gap-4">
<VBtn type="submit">Save changes</VBtn>
<VBtn color="secondary" variant="tonal" type="reset">
Reset
</VBtn>
</VCol>
</VRow>
</VForm>
</VCardText>
</VCard>
</VCol>
</VRow>
</template>
<style>
.border-r {
border-right: 1px solid silver;
}
</style>

View File

@@ -0,0 +1,143 @@
<script setup>
import axios from '@axios';
import { onBeforeMount, onMounted, ref } from 'vue';
import { onBeforeRouteLeave, useRoute, useRouter } from 'vue-router';
import { useStore } from 'vuex';
const store = useStore()
const router = useRouter()
const route = useRoute()
const recordVid = ref(true)
const agentId = ref(localStorage.getItem('agent_id'))
const token = ref(localStorage.getItem('agent_meeting_id'))
const questions = ref([]);
const call_type = ref(localStorage.getItem('call_type'));
onBeforeMount(async () => {
console.log('call type', call_type.value)
});
onMounted(() => {
const mainContainer = document.querySelector('.layout-content-wrapper');
const callContainer = document.querySelector('.draggable-off');
if (callContainer) {
callContainer.style.width = 'calc(100vw - 260px)'
callContainer.style.left = '260px'
}
console.log('mainContainer.offsetWidth', mainContainer.offsetWidth)
if (mainContainer) {
mainContainer.style.display = 'none';
}
axios.post('/agent/api/patient-recording-switch-get/' + agentId.value)
.then(response => {
console.log('rec ', response.data.recording_switch)
if (response.data.recording_switch == 1)
recordVid.value = true
else
recordVid.value = false
})
.catch(error => {
console.error('error recording ', error.response.data);
});
axios.post('/agent/api/questions-list')
.then(r => {
console.log("Question", r.data);
questions.value = r.data;
}).catch(error => {
console.log("ErrorResponse", error);
isLoadingVisible.value = false;
});
});
onUnmounted(async () => {
const mainContainer = document.querySelector('.layout-content-wrapper');
const callContainer = document.querySelector('.draggable');
console.log('callContainer', callContainer);
if (mainContainer)
mainContainer.style.display = 'block';
if (callContainer) {
callContainer.style.width = '15%'
callContainer.style.left = 'unset'
}
});
const groupedQuestions = computed(() => {
const groups = {};
for (const key in questions.value) {
if (questions.value.hasOwnProperty(key)) {
let groupQuestions = questions.value[key];
groupQuestions.forEach(question => {
const groupId = question.group_id;
if (!groups[groupId]) {
groups[groupId] = {
name: key,
questions: [],
};
}
groups[groupId].questions.push(question);
});
}
}
console.log("groups", groups);
// Convert groups object to an array
return Object.values(groups);
});
const getTranscript = (transcriptData) => {
// Parse the JSON string
const responseObj = JSON.parse(transcriptData);
// Extract the questions_and_answers array
const questionsAndAnswers = JSON.parse(responseObj.text);
if (Array.isArray(questionsAndAnswers) && questionsAndAnswers.length > 0) {
questions.value.forEach(item => {
const result = findCommonQuestion(item.question, questionsAndAnswers);
if (result.hasCommonQuestion) {
console.log(`Common question found at index ${result.indexInArray2} in array2.`);
answers.value[item.id] = questionsAndAnswers[result.indexInArray2].answer
}
});
// questions.value.forEach(item => {
// if (result.hasCommonQuestion) {
// console.log(`Common question found at index ${result.indexInArray2} in array2.`);
// answers.value[item.question.id] = questionsAndAnswers[result.indexInArray2].answer
// }
// });
// transcript.value.push(questionsAndAnswers);
// console.log('transcript ', transcript.value, hasCommonQuestion(questions.value, questionsAndAnswers))
}
};
onBeforeRouteLeave(async () => {
if (store.getters.getCallStarted) {
// const confirmation = window.confirm('Are you sure you want to leave this page?')
// if (!confirmation) {
// Prevent navigation if the user cancels
// return false
// } else {
store.dispatch('updateFloatingWindow', true)
// }
}
});
</script>
<template>
<!-- <videocall :token="token" :recording="recordVid" :call_type="call_type" @update:transcriptData="getTranscript">
</videocall> -->
<div class="videoCallContainer">
</div>
</template>
<style scoped>
.layout-content-wrapper {
display: none !important;
}
</style>

View File

@@ -0,0 +1,111 @@
<script setup>
const isMobile = ref(window.innerWidth <= 768); // Assuming mobile width is less than or equal to 768px
const checkIfMobile = () => {
isMobile.value = window.innerWidth <= 768;
};
// Attach event listener on component mount
onMounted(() => {
window.addEventListener('resize', checkIfMobile);
});
// Detach event listener on component unmount
onUnmounted(() => {
window.removeEventListener('resize', checkIfMobile);
});
</script>
<template>
<VRow>
<VCol cols="12" md="12">
<VCard>
<VContainer>
<VRow class="mb-4 mt-4">
<VCol cols="12" md="6" :class="{ 'border-r': !isMobile }">
<h4>Get in touch with us</h4>
<VRow class="mt-3" :class="{ 'border-b': isMobile }">
<VCol cols="12" md="3" class="pb-0">
<p class="mb-0 text-black">Text:</p>
</VCol>
<VCol cols="12" md="9" class="pb-0">
<tel href="sms:+1241231324" class="float-right text-red"><b>(124)
123-1324</b></tel>
</VCol>
<VCol cols="12" md="3" class="pb-0">
<p class="mb-0 text-black">Phone:</p>
</VCol>
<VCol cols="12" md="9" class="pb-0">
<a href="tel:+1241231324" class="float-right text-red"><b>(124) 123-1324</b></a>
</VCol>
<VCol cols="12" md="12">
<p>Business hours are</p>
<p>Monday - Sunday, 8AM - 8PM EST</p>
</VCol>
</VRow>
</VCol>
<VCol cols="12" md="6">
<h4>Address</h4>
<VRow class="mt-3">
<VCol cols="12" md="12">
<p class="mb-0">
809 Westmere Ave, Suite A <br>Charlotte, NC 28208
</p>
</VCol>
</VRow>
</VCol>
</VRow>
<VRow class="pt-4 border-t">
<VCol cols="12" md="4"></VCol>
<VCol cols="12" md="4">
<p>
<RouterLink class="text-primary" to="/">
<span class="text-black">Terms And Conditions</span>
</RouterLink>
<RouterLink class="text-primary ml-4" to="/">
<span class="text-black">Privacy Policy</span>
</RouterLink>
</p>
<p>
<RouterLink class="text-primary" to="/">
<span class="text-black"> Refund Practices</span>
</RouterLink>
</p>
<p>
<RouterLink class="text-primary " to="/">
<span class="text-black">Refund Policy</span>
</RouterLink>
</p>
</VCol>
<VCol cols="12" md="4">
<p class="text-black">809 Westmere Ave, Suite A, Charlotte, NC 28208</p>
<p>
<span><b>Phone: </b></span> &nbsp;
<span>
<a href="tel:+1241231324" class="text-red">
<b>(124) 123-1324</b>
</a>
</span>
</p>
<p>
<span><b>Text: </b></span> &nbsp;
<span>
<a href="tel:+1241231324" class="text-red">
<b>(124) 123-1324</b>
</a>
</span>
</p>
</VCol>
</VRow>
</VContainer>
</VCard>
</VCol>
</VRow>
</template>
<style>
.border-r {
border-right: 1px solid silver;
}
</style>

View File

@@ -0,0 +1,146 @@
<script setup>
import AnalyticsCongratulations from '@/views/dashboard/AnalyticsCongratulations.vue';
import axios from '@axios';
// import HeathCare from '@images/pages/healthcare-wellness-wellbeing-first-aid-box-word-graphic.jpg';
const isLoadingVisible = ref(false);
const loginuser = ref('');
const scheduleDate = ref('');
const scheduleTime = ref('');
const timeZone = ref('');
onMounted(() => {
const patient_id = localStorage.getItem('patient_id')
const access_token = localStorage.getItem('access_token');
isLoadingVisible.value = true;
axios.post('/api/agent-last-appointment-detail/' + patient_id, {
headers: {
'Authorization': `Bearer ${access_token}`,
}
})
.then(response => {
console.log('Response:', response.data);
if (response.data) {
let appointmentData = response.data.appointment
let patientData = response.data.patient
loginuser.value = patientData.first_name + ' ' + patientData.last_name;
scheduleTime.value = appointmentData.appointment_time;
timeZone.value = appointmentData.timezone;
console.log(scheduleTime.value)
const appointment_date = new Date(appointmentData.appointment_date);
const formattedDate = new Intl.DateTimeFormat('en-US', {
year: 'numeric',
month: 'long',
day: 'numeric',
}).format(appointment_date);
console.log('formattedDate', formattedDate)
scheduleDate.value = formattedDate;
isLoadingVisible.value = false;
} else {
isLoadingVisible.value = false;
}
})
.catch(error => {
// console.error('Error:', error);
});
});
</script>
<template>
<VRow>
<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>
<VCol cols="12" md="12" color="error">
<VCardTitle class="text-center">
<b>Hi {{ loginuser }}</b>
<p>You can access your order status, treatment plan, and product updates from HGH right here.</p>
</VCardTitle>
</VCol>
<!-- 👉 Congratulations -->
<VCol cols="12" lg="12" md="8">
<VCardTitle class="text-wrap">Personalized treatment program <RouterLink to="/">
<span>(See all)</span>
</RouterLink>
</VCardTitle>
<AnalyticsCongratulations v-bind="{
date: scheduleDate,
time: scheduleTime,
timezone: timeZone
}" />
</VCol>
<!-- <VCol cols="12" sm="4">
<VCardTitle>You might find fascinating.</VCardTitle>
<VRow>
<VCol cols="12" md="">
<CardStatisticsVertical v-bind="{
title: 'Hormone Therapy For Women is Here, Offering',
image: HeathCare,
content: 'improve libido, energy, fat loss, sleep and more!',
subcontent: ''
}" />
</VCol>
</VRow>
</VCol> -->
<!-- 👉 Total Revenue -->
<!-- <VCol cols="12" md="8" order="2" order-md="1">
<AnalyticsTotalRevenue />
</VCol> -->
<!-- <VCol cols="12" sm="8" md="4" order="1" order-md="2">
<VRow>
👉 Payments -->
<!-- <VCol cols="12" sm="6">
<CardStatisticsVertical v-bind="{
title: 'Payments',
image: paypal,
stats: '$2,468',
change: -14.82,
}" />
</VCol> -->
<!-- 👉 Revenue -->
<!-- <VCol cols="12" sm="6">
<CardStatisticsVertical v-bind="{
title: 'Transactions',
image: card,
stats: '$14,857',
change: 28.14,
}" />
</VCol> -->
<!-- </VRow> -->
<!-- <VRow>
<VCol cols="12" sm="12">
<AnalyticsProfitReport />
</VCol>
</VRow> -->
<!-- </VCol> -->
<!-- 👉 Order Statistics -->
<!-- <VCol cols="12" md="4" sm="6" order="3">
<AnalyticsOrderStatistics />
</VCol> -->
<!-- 👉 Tabs chart -->
<!-- <VCol cols="12" md="4" sm="6" order="3">
<AnalyticsFinanceTabs />
</VCol> -->
<!-- 👉 Transactions -->
<!-- <VCol cols="12" md="4" sm="6" order="3">
<AnalyticsTransactions />
</VCol> -->
</VRow>
</template>

View File

@@ -0,0 +1,15 @@
<script setup>
import DoctorAppointmentsDetail from '@/views/pages/tables/doctor-appiontment-detail.vue';
</script>
<template>
<VRow>
<VCol cols="12">
<VCard class="text-primary" title="Appointment Detail">
<DoctorAppointmentsDetail />
</VCard>
</VCol>
</VRow>
</template>

View File

@@ -0,0 +1,16 @@
<script setup>
import DoctorAppointmentsLists from '@/views/pages/tables/DoctorAppointments.vue';
</script>
<template>
<VRow>
<VCol cols="12">
<VCard title="Doctor Appointments">
<DoctorAppointmentsLists />
</VCard>
</VCol>
</VRow>
</template>

View File

@@ -0,0 +1,283 @@
<script setup>
import { useAppAbility } from '@/plugins/casl/useAppAbility';
import axios from '@axios';
import {
emailValidator,
requiredEmail
} from '@validators';
import { onMounted } from 'vue';
import { useRoute, useRouter } from 'vue-router';
const router = useRouter()
const route = useRoute()
const ability = useAppAbility()
const errors = ref({
email: undefined,
})
const seetingPlanLogo = ref();
const inavlid = ref(false);
const InvalidCredential = ref()
const isLoadingVisible = ref(false)
const isPasswordVisible = ref(false)
const isPolicy = ref(false)
const isDisabled = ref(true)
const isDialogVisible = ref(false)
const refVForm = ref()
const password = ref()
const email = ref()
const isContent = ref()
const emailError = ref(false);
const emailMessage = ref('');
const succesMessage = ref('');
const isSuccess = ref(false);
const isSentLink = ref(false);
const isCardVisible = ref(true);
onMounted(async () => {
const baseUrl = window.location.hostname;
// alert(baseUrl);
if (baseUrl === 'localhost') {
isContent.value = 'd-block';
} else {
isContent.value = 'd-none';
}
let setting = await axios.post('/api/settings', {})
// console.log(setting.data)
seetingPlanLogo.value = '/assets/logo/' + setting.data.logo
})
const onSubmit = () => {
refVForm.value?.validate().then(({ valid: isValid }) => {
if (isValid)
forgotPassword()
})
}
const forgotPassword = () => {
emailError.value = false;
emailMessage.value = '';
isLoadingVisible.value = true;
axios.post('/api/forgot-password', {
email: email.value,
}).then(r => {
// console.log("Response", r.data.message);
isCardVisible.value = false; // Corrected this line
isSentLink.value = true;
succesMessage.value = r.data.message;
isSuccess.value = true;
isLoadingVisible.value = false;
console.log("isForgotCard", isCardVisible.value);
// let patientData = r.data.data;
}).catch(error => {
console.log('error', error.response.data.message);
emailMessage.value = error.response.data.message;
if (emailMessage.value)
emailError.value = true;
isLoadingVisible.value = false;
});
};
const textField = () => {
emailError.value = false;
emailMessage.value = '';
isSuccess.value = false;
succesMessage.value = '';
}
</script>
<template>
<div class="auth-wrapper d-flex align-center justify-center">
<VCard class="auth-card pt-0 rounded-5" max-width="320" v-if="isCardVisible">
<VCardText>
<div class="text-center mb-0 cursor-pointer"
style="width: 100%;position: relative;display: block; padding-top: 0px;">
<span class="text-center">
<VImg :src='seetingPlanLogo' width="250" height="50" class="logo-img"
@click="isUserAuthenticate" />
</span>
<h5 class="mb-2 mt-2">
Forgot Password ?
</h5>
</div>
<p class="mb-0">To reset your password, enter your email and we'll send you instructions.</p>
</VCardText>
<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>
<VCardText>
<VForm ref="refVForm" @submit.prevent="onSubmit">
<VRow>
<VCol cols="12">
<VTextField v-model="email" label="Email Address" type="email" @click="textField()"
:rules="[requiredEmail, emailValidator]" :error-messages="errors.email"
density="compact" />
<p v-if="emailError" class="email-error error">{{ emailMessage }}</p>
</VCol>
<VCol cols="12">
<!-- <p class="error-message" v-if="inavlid">{{ InvalidCredential }}</p> -->
<!-- login button -->
<VBtn block type="submit" class="text-capitalize">
Send instructions
</VBtn>
</VCol>
<VCol cols="12" class="text-center px-0 py-0 mb-3">
<RouterLink to="/login">
<h>Return to Login</h>
</RouterLink>
</VCol>
</VRow>
</VForm>
</VCardText>
</VCard>
<!-- <VDialog refs="myDialog" persistent width="500"> -->
<!-- <template v-slot:default="{ isActive }"> -->
<VCard v-if="isSentLink" width="400">
<VCardText class="text-center">
<div class="text-center mb-2 cursor-pointer"
style="width: 100%;position: relative;display: block; padding-top: 5px;">
<span class="text-center">
<VImg :src='seetingPlanLogo' width="250" height="50" class="logo-img"
@click="isUserAuthenticate" />
</span>
</div>
</VCardText>
<v-card-text class="text-center">
<span v-if="isSuccess" class="text-center">{{ succesMessage
}} to your email.</span>
</v-card-text>
<VCol cols="12" class="text-center px-0 py-0 mb-3">
<RouterLink to="/login">
<h>Return to Login</h>
</RouterLink>
</VCol>
<!-- <v-card-actions>
<v-spacer></v-spacer>
<v-btn text="Close" @click="openNotesModel = false"></v-btn>
</v-card-actions> -->
</VCard>
<!-- </template> -->
<!-- </VDialog> -->
</div>
<!-- <Footer></Footer> -->
</template>
<style lang="scss">
@use "@core/scss/template/pages/page-auth.scss";
@import "@vendor/fonts/fontawesome.css";
@import "@vendor/fonts/tabler-icons.css";
@import "@vendor/css/rtl/core.css";
@import "@vendor/css/rtl/theme-default.css";
@import "@styles/css/demo.css";
@import "@vendor/libs/node-waves/node-waves.css";
@import "@vendor/css/pages/front-page-help-center.css";
@import "@vendor/css/pages/help-center-front-page.css";
.error-message {
color: #ff2f2f;
font-size: 15px;
}
.v-card.v-card--flat.v-theme--light.v-card--density-default.v-card--variant-elevated.text-center.search-header.rounded-0 {
block-size: 357px;
inset-block-start: -185px;
}
.logo-img {
display: block;
position: relative;
margin: 0 auto;
padding: 0;
}
body {
display: block !important;
}
.bg-primary {
background-color: rgb(var(--v-theme-yellow-theme-button)) !important;
}
a.nav-link.fw-medium {
color: rgba(var(--v-theme-on-background), var(--v-high-emphasis-opacity));
}
.light-style .layout-navbar .menu-text {
color: #5d596c !important;
}
span.app-brand-text.demo.menu-text.fw-bold.ms-2.ps-1 {
color: #5d596c !important;
}
.navbar.landing-navbar {
border-color: rgba(255, 255, 255, 68%) !important;
background: white;
margin: 0;
border-radius: 0;
box-shadow: 0px 10px 10px #00000029;
}
.landing-footer .footer-top {
background-color: #1C5580;
border-radius: none !important;
/* background: url("/assets/img/front-pages/backgrounds/footer-bg-dark.png"); */
}
.footer-bottom.py-3 {
background-color: #282c3e;
}
.light-style .landing-footer .footer-link,
.light-style .landing-footer .footer-text {
color: #d3d4dc !important;
}
.light-style .landing-footer .footer-link,
.light-style .landing-footer .footer-text {
color: #d3d4dc;
}
a.footer-link {
color: #d3d4dc;
}
.light-style .landing-footer .footer-title {
color: #fff;
}
.footer-title {
color: #fff;
}
.footer-text {
color: #d3d4dc;
}
@media (max-width: 355px) {
.first-section-pt {
margin-block-start: -121px !important;
}
}
/* // @use "@core/scss/template/pages/page-auth.scss"; */
</style>

View File

@@ -0,0 +1,81 @@
<script setup>
import axios from '@axios';
import { onBeforeMount, onMounted, onUnmounted, ref } from 'vue';
import { useRoute, useRouter } from 'vue-router';
import { useStore } from 'vuex';
const store = useStore()
const router = useRouter()
const route = useRoute()
const cartEncoded = ref([
{
"product_id": 31,
"qty": 2
},
{
"product_id": 32,
"qty": 3
}
]);
const cartJson = ref()
const cart = route.query.cart
const plans = ref([]);
onBeforeMount(() => {
// Example usage
const encodedCart = encodeCartJson();
console.log('Encoded:', encodedCart);
decodeCartJson(encodedCart);
console.log('Decoded:', cartJson.value);
});
onMounted(async () => {
const fullRoute = {
path: route.path,
params: route.params,
query: route.query,
name: route.name,
fullPath: route.fullPath,
}
console.log('Current route:', fullRoute.fullPath)
localStorage.setItem('go_checkout', fullRoute.fullPath)
let plansapi = await axios.post('/api/plans', {})
console.log('Plans Data', plansapi.data)
const finalArray = cartJson.value.map(cartItem => {
const matchedProduct = plansapi.data.find(plan => plan.id === cartItem.product_id);
if (matchedProduct) {
return {
...matchedProduct,
qty: cartItem.qty
};
}
return null;
}).filter(item => item !== null);
const finalArrayJson = JSON.stringify(finalArray);
// Save the JSON string to localStorage
localStorage.setItem('cart_products', finalArrayJson);
router.push('/pre-register');
// plans.value = plansapi.data
});
onUnmounted(() => { });
const encodeCartJson = () => {
const jsonStr = JSON.stringify(cartEncoded.value);
const encodedStr = btoa(jsonStr);
return encodedStr;
}
const decodeCartJson = (encodedStr) => {
console.log(encodedStr)
const jsonStr = atob(encodedStr);
const decodedJson = JSON.parse(jsonStr);
cartJson.value = decodedJson;
}
</script>
<template>
<!-- Template content -->
</template>

View File

@@ -0,0 +1,81 @@
<script setup>
import axios from '@axios';
import { onBeforeMount, onMounted, onUnmounted, ref } from 'vue';
import { useRoute, useRouter } from 'vue-router';
import { useStore } from 'vuex';
const store = useStore()
const router = useRouter()
const route = useRoute()
const cartEncoded = ref([
{
"product_id": 1,
"qty": 2
},
{
"product_id": 2,
"qty": 3
}
]);
const cartJson = ref()
const cart = route.query.cart
const plans = ref([]);
onBeforeMount(() => {
// Example usage
const encodedCart = encodeCartJson();
console.log('Encoded:', encodedCart);
decodeCartJson(encodedCart);
console.log('Decoded:', cartJson.value);
});
onMounted(async () => {
const fullRoute = {
path: route.path,
params: route.params,
query: route.query,
name: route.name,
fullPath: route.fullPath,
}
console.log('Current route:', fullRoute.fullPath)
localStorage.setItem('go_checkout', fullRoute.fullPath)
let plansapi = await axios.post('/api/plans', {})
console.log('Plans Data', plansapi.data)
const finalArray = cartJson.value.map(cartItem => {
const matchedProduct = plansapi.data.find(plan => plan.id === cartItem.product_id);
if (matchedProduct) {
return {
...matchedProduct,
qty: cartItem.qty
};
}
return null;
}).filter(item => item !== null);
const finalArrayJson = JSON.stringify(finalArray);
// Save the JSON string to localStorage
localStorage.setItem('cart_products', finalArrayJson);
router.push('/pre-register');
// plans.value = plansapi.data
});
onUnmounted(() => { });
const encodeCartJson = () => {
const jsonStr = JSON.stringify(cartEncoded.value);
const encodedStr = btoa(jsonStr);
return encodedStr;
}
const decodeCartJson = (encodedStr) => {
console.log(encodedStr)
const jsonStr = atob(encodedStr);
const decodedJson = JSON.parse(jsonStr);
cartJson.value = decodedJson;
}
</script>
<template>
<!-- Template content -->
</template>

View File

@@ -0,0 +1,66 @@
<script setup>
import DemoFormLayoutHorizontalForm from '@/views/pages/form-layouts/DemoFormLayoutHorizontalForm.vue'
import DemoFormLayoutHorizontalFormWithIcons from '@/views/pages/form-layouts/DemoFormLayoutHorizontalFormWithIcons.vue'
import DemoFormLayoutMultipleColumn from '@/views/pages/form-layouts/DemoFormLayoutMultipleColumn.vue'
import DemoFormLayoutVerticalForm from '@/views/pages/form-layouts/DemoFormLayoutVerticalForm.vue'
import DemoFormLayoutVerticalFormWithIcons from '@/views/pages/form-layouts/DemoFormLayoutVerticalFormWithIcons.vue'
</script>
<template>
<div>
<VRow>
<VCol
cols="12"
md="6"
>
<!-- 👉 Horizontal Form -->
<VCard title="Horizontal Form">
<VCardText>
<DemoFormLayoutHorizontalForm />
</VCardText>
</VCard>
</VCol>
<VCol
cols="12"
md="6"
>
<!-- 👉 Horizontal Form with Icons -->
<VCard title="Horizontal Form with Icons">
<VCardText>
<DemoFormLayoutHorizontalFormWithIcons />
</VCardText>
</VCard>
</VCol>
<VCol
cols="12"
md="6"
>
<!-- 👉 Vertical Form -->
<VCard title="Vertical Form">
<VCardText>
<DemoFormLayoutVerticalForm />
</VCardText>
</VCard>
</VCol>
<VCol
cols="12"
md="6"
>
<!-- 👉 Vertical Form with Icons -->
<VCard title="Vertical Form with Icons">
<VCardText>
<DemoFormLayoutVerticalFormWithIcons />
</VCardText>
</VCard>
</VCol>
<VCol cols="12">
<!-- 👉 Multiple Column -->
<VCard title="Multiple Column">
<VCardText>
<DemoFormLayoutMultipleColumn />
</VCardText>
</VCard>
</VCol>
</VRow>
</div>
</template>

View File

@@ -0,0 +1,109 @@
<script setup>
import axios from '@axios';
import { onBeforeMount, onMounted, onUnmounted, ref } from 'vue';
import { useRoute, useRouter } from 'vue-router';
import { useStore } from 'vuex';
const store = useStore()
const router = useRouter()
const route = useRoute()
const cartEncoded = ref(
{
"gender": "male",
"doctor_visit": "89.00",
"products": [
{
"product_id": 6616,
"qty": 1,
"subscription": true,
"onetime": false
},
{
"product_id": 6618,
"qty": 1,
"subscription": true,
"onetime": false
},
{
"product_id": 6620,
"qty": 1,
"subscription": false,
"onetime": true
}
]
}
);
const cartJson = ref()
const gender = ref(null)
const doctor_visit = ref(null)
const cart = route.query.cart
const plans = ref([]);
onBeforeMount(async () => {
await store.dispatch('getLabKits', {})
console.log('Labkit ', store.getters.getLabOrderProductList)
localStorage.setItem('labkits', JSON.stringify(store.getters.getLabOrderProductList))
// Example usage
const encodedCart = encodeCartJson();
console.log('Encoded:', encodedCart);
decodeCartJson(cart);
console.log('Decoded:', cartJson.value);
});
onMounted(async () => {
const fullRoute = {
path: route.path,
params: route.params,
query: route.query,
name: route.name,
fullPath: route.fullPath,
}
console.log('Current route:', fullRoute.fullPath)
localStorage.setItem('go_checkout', fullRoute.fullPath)
let plansapi = await axios.post('/api/plans', {})
console.log('Plans Data', plansapi.data)
const finalArray = cartJson.value.map(cartItem => {
const matchedProduct = plansapi.data.find(plan => plan.id === cartItem.product_id);
if (matchedProduct) {
return {
...matchedProduct,
qty: cartItem.qty,
subscription: cartItem.subscription,
onetime: cartItem.onetime
};
}
return null;
}).filter(item => item !== null);
const finalArrayJson = JSON.stringify(finalArray);
// Save the JSON string to localStorage
localStorage.setItem('cart_products', finalArrayJson);
localStorage.setItem('gender', gender.value);
localStorage.setItem('doctor_visit', doctor_visit.value);
router.push('/pre-register');
// plans.value = plansapi.data
});
onUnmounted(() => { });
const encodeCartJson = () => {
const jsonStr = JSON.stringify(cartEncoded.value);
const encodedStr = btoa(jsonStr);
return encodedStr;
}
const decodeCartJson = (encodedStr) => {
console.log(encodedStr)
const jsonStr = atob(encodedStr);
const decodedJson = JSON.parse(jsonStr);
console.log('decodedJson',decodedJson)
cartJson.value = decodedJson.products;
gender.value = decodedJson.gender
doctor_visit.value = decodedJson.doctor_visit
}
</script>
<template>
<!-- Template content -->
</template>

113
resources/js/pages/home.vue Normal file
View File

@@ -0,0 +1,113 @@
<script setup>
import BenefitsOfTestosterone from '@/views/pages/home/BenefitsOfTestosterone.vue';
import Footer from '@/views/pages/home/Footer.vue';
import HeaderTopBar from '@/views/pages/home/HeaderTopBar.vue';
import HelpArea from '@/views/pages/home/HelpArea.vue';
import HelpCenterHeader from '@/views/pages/home/HelpCenterHeader.vue';
import Symptoms from '@/views/pages/home/Symptoms.vue';
import LetTalk from '@/views/pages/home/LetTalk.vue';
import MaleExcel from '@/views/pages/home/MaleExcel.vue';
import OurMission from '@/views/pages/home/OurMission.vue';
import OurPatientsLoveBest from '@/views/pages/home/OurPatientsLoveBest.vue';
import Testimonial from '@/views/pages/home/Testimonial.vue';
import TestosteroneOnline from '@/views/pages/home/TestosteroneOnline.vue';
</script>
<template>
<HeaderTopBar></HeaderTopBar>
<HelpCenterHeader></HelpCenterHeader>
<!-- <MaleExcel></MaleExcel> -->
<TestosteroneOnline></TestosteroneOnline>
<BenefitsOfTestosterone></BenefitsOfTestosterone>
<!-- <OurMission></OurMission> -->
<Symptoms></Symptoms>
<OurPatientsLoveBest></OurPatientsLoveBest>
<!-- <Testimonial></Testimonial> -->
<LetTalk></LetTalk>
<HelpArea></HelpArea>
<Footer></Footer>
</template>
<style>
@import "@vendor/fonts/fontawesome.css";
@import "@vendor/fonts/tabler-icons.css";
@import "@vendor/css/rtl/core.css";
@import "@vendor/css/rtl/theme-default.css";
@import "@styles/css/demo.css";
@import "@vendor/libs/node-waves/node-waves.css";
@import "@vendor/css/pages/front-page-help-center.css";
@import "@vendor/css/pages/help-center-front-page.css";
.v-card.v-card--flat.v-theme--light.v-card--density-default.v-card--variant-elevated.text-center.search-header.rounded-0 {
block-size: 357px;
inset-block-start: -185px;
}
body {
display: block !important;
}
a.nav-link.fw-medium {
color: rgba(var(--v-theme-on-background), var(--v-high-emphasis-opacity));
}
.light-style .layout-navbar .menu-text {
color: #5d596c !important;
}
span.app-brand-text.demo.menu-text.fw-bold.ms-2.ps-1 {
color: #5d596c !important;
}
.navbar.landing-navbar {
border-color: rgba(255, 255, 255, 68%) !important;
border-radius: 0;
margin: 0;
background: white;
box-shadow: 0 10px 10px #00000029;
}
.landing-footer .footer-top {
border-radius: none !important;
background-color: #1c5580;
/* background: url("/assets/img/front-pages/backgrounds/footer-bg-dark.png"); */
}
.footer-bottom.py-3 {
background-color: #282c3e;
}
.light-style .landing-footer .footer-link,
.light-style .landing-footer .footer-text {
color: #d3d4dc !important;
}
.light-style .landing-footer .footer-link,
.light-style .landing-footer .footer-text {
color: #d3d4dc;
}
a.footer-link {
color: #d3d4dc;
}
.light-style .landing-footer .footer-title {
color: #fff;
}
.footer-title {
color: #fff;
}
.footer-text {
color: #d3d4dc;
}
@media (max-width: 355px) {
.first-section-pt {
margin-block-start: -121px !important;
}
}
</style>

View File

@@ -0,0 +1,91 @@
<script setup>
const iconsList = [
'bx-abacus',
'bx-accessibility',
'bx-add-to-queue',
'bx-adjust',
'bx-alarm',
'bx-alarm-add',
'bx-alarm-exclamation',
'bx-alarm-off',
'bx-alarm-snooze',
'bx-album',
'bx-align-justify',
'bx-align-left',
'bx-align-middle',
'bx-align-right',
'bx-analyse',
'bx-anchor',
'bx-angry',
'bx-aperture',
'bx-arch',
'bx-archive',
'bx-archive-in',
'bx-archive-out',
'bx-area',
'bx-arrow-back',
'bx-arrow-from-bottom',
'bx-arrow-from-left',
'bx-arrow-from-right',
'bx-arrow-from-top',
'bx-arrow-to-bottom',
'bx-arrow-to-left',
'bx-arrow-to-right',
'bx-arrow-to-top',
'bx-at',
'bx-atom',
'bx-award',
'bx-badge',
'bx-badge-check',
'bx-baguette',
'bx-ball',
'bx-band-aid',
'bx-bar-chart',
'bx-bar-chart-alt',
'bx-bar-chart-alt-2',
'bx-bar-chart-square',
'bx-barcode',
'bx-barcode-reader',
'bx-baseball',
'bx-basket',
]
</script>
<template>
<div>
<div class="d-flex align-center flex-wrap">
<VCard
v-for="icon in iconsList"
:key="icon"
class="mb-6 me-6"
>
<VCardText class="py-3 px-4">
<VIcon
size="30"
:icon="icon"
/>
</VCardText>
<!-- tooltips -->
<VTooltip
location="top"
activator="parent"
>
{{ icon }}
</VTooltip>
</VCard>
</div>
<!-- more icons -->
<div class="text-center">
<VBtn
href="https://boxicons.com/"
rel="noopener noreferrer"
color="primary"
target="_blank"
>
View All Box Icons
</VBtn>
</div>
</div>
</template>

View File

@@ -0,0 +1,292 @@
<script setup>
const purchasedProducts = [
{
name: 'Vuexy Admin Template',
description: 'HTML Admin Template',
qty: 1,
cost: 32,
},
{
name: 'Frest Admin Template',
description: 'Angular Admin Template',
qty: 1,
cost: 22,
},
{
name: 'Apex Admin Template',
description: 'HTML Admin Template',
qty: 2,
cost: 17,
},
{
name: 'Robust Admin Template',
description: 'React Admin Template',
qty: 1,
cost: 66,
},
];
</script>
<template>
<section>
<VRow>
<VCol cols="12" md="9">
<VCard>
<!-- SECTION Header -->
<VCardText class="d-flex flex-wrap justify-space-between flex-column flex-sm-row print-row">
<!-- 👉 Left Content -->
<div class="ma-sm-4">
<div class="d-flex align-center mb-6">
<!-- 👉 Logo -->
<router-link to="/" class="mb-2 mt-2"
style="width: 100%;position: relative;display: block;">
<span class="text-center">
<VImg src="/assets/logo/logo.png" width="50" height="50" class="logo-img" />
</span>
</router-link>
</div>
<!-- 👉 Address -->
</div>
<!-- 👉 Right Content -->
<div class="mt-5 ma-sm-4">
<!-- 👉 Invoice ID -->
<h6 class="font-weight-medium text-h4">
Invoice #5036
</h6>
<!-- 👉 Issue Date -->
<p class="my-3">
<span>Date: </span>
<span class="font-weight-medium"> 2024-03-19</span>
</p>
<!-- 👉 Due Date -->
</div>
</VCardText>
<!-- !SECTION -->
<VDivider />
<!-- 👉 Payment Details -->
<VCardText class="d-flex justify-space-between flex-wrap flex-column flex-sm-row print-row">
<div class="mt-4 ma-sm-4">
<h6 class="text-h6 font-weight-medium mb-6">
Shipping Information :
</h6>
<table>
<tr>
<td class="pe-6 pb-1">
Name:
</td>
<td class="pb-1">
<span class="font-weight-medium">
Mr. John
</span>
</td>
</tr>
<tr>
<td class="pe-6 pb-1">
Address:
</td>
<td class="pb-1">
Model Town,American Bank, NA United States 12345
</td>
</tr>
</table>
</div>
</VCardText>
<!-- <VCardText class="d-flex justify-space-between flex-wrap flex-column flex-sm-row print-row">
<div class="mt-4 ma-sm-4">
<h6 class="text-h6 font-weight-medium mb-6">
Shipping Information :
</h6>
<table>
<tr>
<td class="pe-6 pb-1">
Address:
</td>
<td class="pb-1">
<span class="font-weight-medium">
Model Town
</span>
</td>
</tr>
<tr>
<td class="pe-6 pb-1">
APT/Suite:
</td>
<td class="pb-1">
American Bank
</td>
</tr>
<tr>
<td class="pe-6 pb-1">
City:
</td>
<td class="pb-1">
United States
</td>
</tr>
<tr>
<td class="pe-6 pb-1">
State:
</td>
<td class="pb-1">
ETD95476213874685
</td>
</tr>
<tr>
<td class="pe-6 pb-1">
ZipCode:
</td>
<td class="pb-1">
BR91905
</td>
</tr>
</table>
</div>
</VCardText> -->
<!-- 👉 Table -->
<VDivider />
<VTable class="invoice-preview-table">
<thead>
<tr>
<th scope="col">
ITEM
</th>
<th scope="col" class="text-center">
COST
</th>
</tr>
</thead>
<tbody>
<tr v-for="item in purchasedProducts" :key="item.name">
<td class="text-no-wrap">
{{ item.name }}
</td>
<td class="text-center">
${{ item.cost }}
</td>
</tr>
</tbody>
</VTable>
<VDivider class="mb-2" />
<!-- Total -->
<VCardText class="d-flex justify-space-between flex-column flex-sm-row print-row">
<div class="my-2 mx-sm-4 text-base">
</div>
<div class="my-2 mx-sm-4">
<table>
<tr>
<td class="text-end">
<div class="me-5">
<p class="mb-2">
Subtotal:
</p>
<p class="mb-2">
Discount:
</p>
<p class="mb-2">
Tax:
</p>
<p class="mb-2">
Total:
</p>
</div>
</td>
<td class="font-weight-medium text-high-emphasis">
<p class="mb-2">
$154.25
</p>
<p class="mb-2">
$00.00
</p>
<p class="mb-2">
$50.00
</p>
<p class="mb-2">
$204.25
</p>
</td>
</tr>
</table>
</div>
</VCardText>
<VDivider />
</VCard>
</VCol>
</VRow>
<!-- 👉 Add Payment Sidebar -->
<!-- <InvoiceAddPaymentDrawer v-model:isDrawerOpen="isAddPaymentSidebarVisible" /> -->
<!-- 👉 Send Invoice Sidebar -->
<!-- <InvoiceSendInvoiceDrawer v-model:isDrawerOpen="isSendPaymentSidebarVisible" /> -->
</section>
</template>
<style lang="scss">
.invoice-preview-table {
--v-table-row-height: 44px !important;
}
.logo-img {
display: block;
position: relative;
margin: 0 auto;
}
@media print {
.v-application {
background: none !important;
}
@page {
margin: 0;
size: auto;
}
.layout-page-content,
.v-row,
.v-col-md-9 {
padding: 0;
margin: 0;
}
.product-buy-now {
display: none;
}
.v-navigation-drawer,
.layout-vertical-nav,
.app-customizer-toggler,
.layout-footer,
.layout-navbar,
.layout-navbar-and-nav-container {
display: none;
}
.v-card {
box-shadow: none !important;
.print-row {
flex-direction: row !important;
}
}
.layout-content-wrapper {
padding-inline-start: 0 !important;
}
}
</style>

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,14 @@
<script setup>
import LabKitLists from '@/views/pages/tables/Labkit.vue';
</script>
<template>
<VRow>
<VCol cols="12">
<VCard title="Lab kits">
<LabKitLists />
</VCard>
</VCol>
</VRow>
</template>

107
resources/js/pages/labs.vue Normal file
View File

@@ -0,0 +1,107 @@
<script setup>
import LabKit from '@/views/pages/overview/LabKit.vue';
import axios from '@axios';
import { useStore } from 'vuex';
const isLoadingVisible = ref(false);
const loginuser = ref('');
const scheduleDate = ref('');
const scheduleTime = ref('');
const timeZone = ref('');
const timeDifference = ref();
const timeUntilMeeting = ref('');
const labKitStatus = ref();
const store = useStore()
onMounted(async () => {
localStorage.setItem('isLogin', true);
const patient_id = localStorage.getItem('patient_id')
const access_token = localStorage.getItem('access_token');
isLoadingVisible.value = true;
await store.dispatch('getLabKitOrderStatus')
labKitStatus.value = store.getters.getLabOrderStatus.cart.status;
console.log("labkit", labKitStatus.value);
axios.post('/api/agent-last-appointment-detail/' + patient_id, {
headers: {
'Authorization': `Bearer ${access_token}`,
}
})
.then(response => {
console.log('Response:', response.data);
if (response.data) {
let diffInMinutes = response.data.time_diff;
let appointmentData = response.data.appointment
let patientData = response.data.patient
loginuser.value = patientData.first_name + ' ' + patientData.last_name;
scheduleTime.value = appointmentData.appointment_time;
timeZone.value = appointmentData.timezone;
timeDifference.value = diffInMinutes;
console.log(scheduleTime.value);
const appointment_date = new Date(appointmentData.appointment_date);
const formattedDate = new Intl.DateTimeFormat('en-US', {
year: 'numeric',
month: 'long',
day: 'numeric',
}).format(appointment_date);
console.log('formattedDate', formattedDate)
scheduleDate.value = formattedDate;
isLoadingVisible.value = false;
} else {
isLoadingVisible.value = false;
}
})
.catch(error => {
console.error('Error:', error);
});
});
</script>
<template>
<VContainer>
<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>
<VRow>
<VCol cols="12" md="7">
<LabKit :itemProps="labKitStatus"></LabKit>
</VCol>
<VCol cols="12" md="5">
<VCard>
<VCardTitle><b>Tips</b></VCardTitle>
<VCardText>
<ul class="ml-5">
<li> Drink a glass of water in the hour before you take your sample
relax</li>
<li> Take a hot shower or bath just before.</li>
<li> Stay standing and
Keep your arm
straight, with your hand below your waist</li>
<li>
Push the lancet firmly against your finger </li>
<li> Aim for the side of the tip of your finger,
not too close to your finger nail</li>
<li> If you're nervous, talk to us or ask someone to help</li>
</ul>
</VCardText>
</VCard>
</VCol>
</VRow>
</VContainer>
</template>

View File

@@ -0,0 +1,39 @@
<!-- LeafletMap.vue -->
<template>
<v-card-text>
<div class="leaflet-map" ref="map"></div>
</v-card-text>
</template>
<script>
import L from 'leaflet';
export default {
mounted() {
// Ensure that Leaflet assets are loaded before creating the map
import('leaflet/dist/leaflet.css').then(() => {
this.initializeMap();
});
},
methods: {
initializeMap() {
// Initialize the map
this.map = L.map(this.$refs.map).setView([51.505, -0.09], 13);
// Add a tile layer (OpenStreetMap)
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png').addTo(this.map);
// Add a marker
L.marker([51.505, -0.09]).addTo(this.map);
},
},
};
</script>
<style scoped>
.leaflet-map {
height: 250px;
}
</style>

View File

@@ -0,0 +1,303 @@
<script setup>
import { useAppAbility } from '@/plugins/casl/useAppAbility';
import axios from '@axios';
import {
emailValidator,
requiredEmail,
requiredPassword
} from '@validators';
import { onMounted } from 'vue';
import { useRoute, useRouter } from 'vue-router';
const router = useRouter()
const route = useRoute()
const ability = useAppAbility()
const errors = ref({
password: undefined,
email: undefined,
})
const inavlid = ref(false);
const InvalidCredential = ref()
const isLoadingVisible = ref(false)
const isPasswordVisible = ref(false)
const isPolicy = ref(false)
const isDisabled = ref(true)
const isDialogVisible = ref(false)
const refVForm = ref()
const password = ref()
const email = ref()
const isContent = ref()
const seetingPlanLogo = ref();
onMounted(async () => {
const layoutWrapper = document.querySelector('.layout-wrapper');
// Check if the element exists
if (layoutWrapper) {
// Add the class you want
layoutWrapper.classList.add('regbg');
}
const baseUrl = window.location.hostname;
// alert(baseUrl);
if (baseUrl === 'localhost') {
isContent.value = 'd-block';
} else {
isContent.value = 'd-none';
}
let setting = await axios.post('/api/settings', {})
// console.log(setting.data)
seetingPlanLogo.value = '/assets/logo/' + setting.data.logo
})
onUnmounted(() => {
// Select the element by class name
const layoutWrapper = document.querySelector('.layout-wrapper');
// Check if the element exists
if (layoutWrapper) {
// Remove the class
layoutWrapper.classList.remove('regbg');
}
});
const onSubmit = () => {
inavlid.value = false;
InvalidCredential.value = '';
refVForm.value?.validate().then(({ valid: isValid }) => {
if (isValid)
loginPatient()
})
}
const loginPatient = () => {
isLoadingVisible.value = true;
axios.post('/api/login-patient', {
email: email.value,
password: password.value,
}).then(r => {
console.log("Response", r.data);
let patientData = r.data.data;
localStorage.setItem('access_token', r.data.access_token)
localStorage.setItem('patient_id', patientData.id)
localStorage.setItem('user_role', 'patient')
localStorage.setItem('cominguser', 'login')
if (!patientData.dob || !patientData.gender || !patientData.marital_status || !patientData.height || !patientData.weight) {
// localStorage.setItem('profileCompleted', '0')
}
localStorage.setItem('isLogin', 'true')
localStorage.setItem('userAbilities', '[{"action":"manage","subject":"all"}]')
const userAbilities = [{ "action": "manage", "subject": "all" }];
ability.update(userAbilities)
router.replace(route.query.to && route.query.to != '/login' ? String(route.query.to) : '/overview')
}).catch(error => {
inavlid.value = true;
InvalidCredential.value = "Sorry, that email or password didn't work.";
isLoadingVisible.value = false;
});
};
const getStartedFun = () => {
// isLoadingVisible.value = true
// router.replace(route.query.to && route.query.to != '/' ? String(route.query.to) : '/plans')
};
</script>
<template>
<div class="auth-wrapper d-flex align-center justify-center">
<VCard class="auth-card pa-2 pt-0 rounded-5 regbx" max-width="320">
<VCardItem class="justify-center">
<VCardTitle class="text-2xl font-weight-bold text-primary">
<VImg :src="seetingPlanLogo" width="250" height="50" class="logo-img" />
</VCardTitle>
<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>
</VCardItem>
<!-- <VCardText>
<div class="text-center cursor-pointer "
style="width: 100%;position: relative;display: block; padding-top: 0%;">
<span class="text-center mr-2">
<VImg :src='seetingPlanLogo' width="250" height="50" class="logo-img" />
</span>
<p class="mb-0 mt-0">
Log in to your Account
</p>
</div>
</VCardText>
<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> -->
<VCardText>
<VForm ref="refVForm" @submit.prevent="onSubmit">
<VRow>
<VCol cols="12"></VCol>
<VRow class="bg-white">
<VCol cols="12">
<VTextField v-model="email" label="Email Address" type="email"
:rules="[requiredEmail, emailValidator]" :error-messages="errors.email"
density="compact" />
</VCol>
<!-- email -->
<VCol cols="12">
<VTextField v-model="password" label="Password" placeholder="············"
:rules="[requiredPassword]" :type="isPasswordVisible ? 'text' : 'password'"
:append-inner-icon="isPasswordVisible ? 'bx-show' : 'bx-hide'"
@click:append-inner="isPasswordVisible = !isPasswordVisible" density="compact" />
</VCol>
<!-- password -->
<VCol cols="12" class="pt-0 pb-0">
<router-link to="/forgot" class="text-primary underline">Forgot Password?</router-link>
<!-- <RouterLink class="text-primary ms-2 mb-1">
Forgot Password?
</RouterLink> -->
</VCol>
<VCol cols="12">
<p class="error-message" v-if="inavlid">{{ InvalidCredential }}</p>
<!-- login button -->
<VBtn block type="submit">
Login
</VBtn>
</VCol>
<VCol cols="12" :class="isContent" class="pt-0">
<!-- login button -->
<!-- <VBtn block @click="getStartedFun()">
Get Started
</VBtn> -->
</VCol>
<!-- <VCol cols="12" class="text-center text-base px-0 py-0 mb-3">
<span>Don't have an account?</span>
<RouterLink class="text-primary" to="/register">
Register
</RouterLink>
</VCol> -->
</VRow>
</VRow>
</VForm>
</VCardText>
</VCard>
</div>
<!-- <Footer></Footer> -->
</template>
<style lang="scss">
@use "@core/scss/template/pages/page-auth.scss";
@import "@vendor/fonts/fontawesome.css";
@import "@vendor/fonts/tabler-icons.css";
@import "@vendor/css/rtl/core.css";
@import "@vendor/css/rtl/theme-default.css";
@import "@styles/css/demo.css";
@import "@vendor/libs/node-waves/node-waves.css";
@import "@vendor/css/pages/front-page-help-center.css";
@import "@vendor/css/pages/help-center-front-page.css";
.regbg {
background-image: url('/assets/images/reg_bg.png');
background-size: cover;
background-repeat: no-repeat;
}
.regbx {
background-color: rgb(var(--v-theme-yellow));
box-shadow: 0px 0px 10px #ccc;
border-radius: 10px;
}
.error-message {
color: #ff2f2f;
font-size: 15px;
}
.logo-img {
display: block;
position: relative;
margin: 0 auto;
}
.v-card.v-card--flat.v-theme--light.v-card--density-default.v-card--variant-elevated.text-center.search-header.rounded-0 {
block-size: 357px;
inset-block-start: -185px;
}
body {
display: block !important;
}
.bg-primary {
background-color: rgb(var(--v-theme-yellow-theme-button)) !important;
}
a.nav-link.fw-medium {
color: rgba(var(--v-theme-on-background), var(--v-high-emphasis-opacity));
}
.light-style .layout-navbar .menu-text {
color: #5d596c !important;
}
span.app-brand-text.demo.menu-text.fw-bold.ms-2.ps-1 {
color: #5d596c !important;
}
.navbar.landing-navbar {
border-color: rgba(255, 255, 255, 68%) !important;
background: white;
margin: 0;
border-radius: 0;
box-shadow: 0px 10px 10px #00000029;
}
.landing-footer .footer-top {
background-color: #1C5580;
border-radius: none !important;
/* background: url("/assets/img/front-pages/backgrounds/footer-bg-dark.png"); */
}
.footer-bottom.py-3 {
background-color: #282c3e;
}
.light-style .landing-footer .footer-link,
.light-style .landing-footer .footer-text {
color: #d3d4dc !important;
}
.light-style .landing-footer .footer-link,
.light-style .landing-footer .footer-text {
color: #d3d4dc;
}
a.footer-link {
color: #d3d4dc;
}
.light-style .landing-footer .footer-title {
color: #fff;
}
.footer-title {
color: #fff;
}
.footer-text {
color: #d3d4dc;
}
@media (max-width: 355px) {
.first-section-pt {
margin-block-start: -121px !important;
}
}
/* // @use "@core/scss/template/pages/page-auth.scss"; */
</style>

View File

@@ -0,0 +1,135 @@
<script setup>
import "@core/utils/external_api";
import { onMounted, onUnmounted, ref } from 'vue';
import videocall from './../views/videocall/videocall.vue';
const token = ref(localStorage.getItem('meeting_id'))
const count = ref(5);
const isDialogVisible = ref(true);
const api = ref();
const microphones = ref([
{
title: "Default Microphone",
id: 1,
index: 1
}, {
title: "Default Microphone 2",
id: 2,
index: 2
}
])
const speakers = ref([
{
title: "Default Speaker",
id: 1,
index: 1
}, {
title: "Default Speaker 2",
id: 2,
index: 2
}
])
onMounted(() => {
document.addEventListener('mousemove', handleMouseMove)
const name = "xyz";
const domain = "8x8.vc";
const options = {
roomName: 'vpaas-magic-cookie-769c471e6c614755b51aea9447a554fc/' + localStorage.getItem('meeting_id'),
// jwt: "{{ $jassToken }}",
userInfo: {
displayName: name,
},
parentNode: document.querySelector("#jaas-container"),
};
// api.value = new JitsiMeetExternalAPI(domain, options);
// api.value.addEventListener('readyToClose', (event) => {
// // The call is ready to be closed, perform actions accordingly
// console.log('Jitsi call ended:', event);
// });
// api.value.addEventListener('videoConferenceLeft', (event) => {
// // The user has left the video conference, perform actions accordingly
// console.log('patient left the Jitsi call:', event);
// // router.push('/overview');
// });
});
onUnmounted(() => {
document.removeEventListener('mousemove', handleMouseMove)
// Clean up the API instance when the component is unmounted
if (api.value) {
api.value.dispose();
}
});
const x = ref(0)
const y = ref(0)
const dragging = ref(false)
const rect = ref(null)
const style = computed(() => {
return {
transform: `translate(${x.value}px, ${y.value}px)`
}
})
const handleMouseDown = e => {
rect.value = e.currentTarget.getBoundingClientRect()
dragging.value = true
}
const handleMouseMove = e => {
if (!dragging.value) return
x.value = Math.min(Math.max(0, e.clientX - rect.value.left), window.innerWidth - rect.value.width) - 25
y.value = Math.min(Math.max(0, e.clientY - rect.value.top), window.innerHeight - rect.value.height) - 25
}
</script>
<template>
<!-- <div class="auth-wrapper d-flex align-center justify-center pa-4"> -->
<!-- <div id="jaas-container" >
</div> -->
<!-- Show the iframe when countdown reaches 1 -->
<!-- <iframe v-if="!isDialogVisible" width="100%" height="500px" frameborder="0" allowfullscreen></iframe> -->
<!-- </div> -->
<videocall :token="token" :recording="false" height="calc(100vh - 80px)"></videocall>
</template>
<style lang="scss">
@use "@core/scss/template/pages/page-auth.scss";
#jaas-container {
height: 550px;
width: 100%
}
.small-screen {
background-color: white;
min-height: 150px;
max-width: 24%;
}
.livekit-container {
min-height: 600px;
height: 100%;
background-color: black;
position: relative;
}
.draggable-card {
cursor: move;
position: relative;
}
.box {
position: absolute;
cursor: move;
width: 150px;
height: 150px;
background: #313131;
border-radius: 10px;
margin-left: 10px;
}
</style>

View File

@@ -0,0 +1,247 @@
<script setup>
import axios from '@axios';
import { onMounted } from 'vue';
import { useRoute, useRouter } from 'vue-router';
const router = useRouter()
const route = useRoute()// Assuming mobile width is less than or equal to 768px
const patient_id = localStorage.getItem('patient_id')
const access_token = localStorage.getItem('access_token');
const isLoadingVisible = ref(false)
const firstName = ref('')
const lastName = ref('')
const email = ref('')
const phone = ref('')
const planName = ref('')
const planAmount = ref('')
const planList = ref([])
const plansubTitle = ref('')
const currentPath = window.location.origin
onMounted(async () => {
await getPatientInfo()
await getPlanInfo()
})
const getPatientInfo = async () => {
console.log()
isLoadingVisible.value = true;
await axios.post('/api/get-patient-detail/' + patient_id, {
headers: {
'Authorization': `Bearer ${access_token}`,
}
})
.then(response => {
console.log('Response:', response.data);
if (response.data) {
var patientdata = response.data.patient
firstName.value = patientdata.first_name
lastName.value = patientdata.last_name
email.value = patientdata.email
phone.value = patientdata.phone_no
isLoadingVisible.value = false;
} else {
isLoadingVisible.value = false;
}
})
.catch(error => {
isLoadingVisible.value = false;
console.log('Error:', error);
});
}
const getPlanInfo = async () => {
console.log()
isLoadingVisible.value = true;
await axios.post('/api/get-plan-by-patient/' + patient_id, {
headers: {
'Authorization': `Bearer ${access_token}`,
}
})
.then(response => {
console.log('Response:', response.data);
if (response.data) {
let planData = response.data
console.log('plan ', planData)
planList.value = planData
planName.value = planData.title
planAmount.value = planData.price
isLoadingVisible.value = false;
} else {
isLoadingVisible.value = false;
}
})
.catch(error => {
isLoadingVisible.value = false;
console.log('Error:', error);
});
}
const imageSrc = (src) => {
console.log(`${currentPath}/product/${src}`)
return `${currentPath}/product/${src}`
};
</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>
<VRow>
<VCol cols="12" md="9" v-for="plan in planList" :key="plan.id">
<v-card class="mx-auto">
<v-img :src='imageSrc(plan.image_url)' height="200px" class="membershipImage" cover
style="width:auto"></v-img>
<v-card-subtitle class="mt-4">
{{ plan.title }} Status: Active
</v-card-subtitle>
<v-card-title>
{{ plan.list_sub_title }}
</v-card-title>
<VCardText class="ml-3">
<h3>{{ plan.list_one_title }}</h3>
<ul class="ml-5">
<li>{{ plan.list_two_title }}</li>
</ul>
</VCardText>
<VCardText>
<p> </p>
<p class="text-black">Your membership will start with your first medication purchase</p>
</VCardText>
</v-card>
</VCol>
<VCol cols="12" md="9" v-if="planName == 'Pro Plan'">
<v-card class="mx-auto">
<v-img :src="'/assets/images/pro_plan.jpg'" height="200px" cover></v-img>
<v-card-subtitle class="mt-4">
TRT Membership Status: Active
</v-card-subtitle>
<v-card-title>
Testosterone Replacement Therapy For Men
</v-card-title>
<VCardText class="ml-3">
<h3>Membership Benifits Overview</h3>
<ul class="ml-5">
<li>Customized treatment based on your specific needs and health status.</li>
<li>Ongoing monitoring and adjustments by your healthcare provider to ensure safety and
effectiveness.</li>
<li>Consistent supply of necessary medications.</li>
<li>Access to specialized testosterone formulations.</li>
<li>Coordinated care and clinical oversight from your provider.</li>
</ul>
</VCardText>
<VCardText>
<p>The main advantages of a Pro Plan are the personalized, supervised, and sustainable approach to
managing low testosterone, which can lead to better outcomes compared to self-management.</p>
<p class="text-black">Your membership will start with your first medication purchase</p>
</VCardText>
</v-card>
</VCol>
<VCol cols="12" md="9" v-if="planName == 'Basic Plan'">
<v-card class="mx-auto">
<v-img :src="'/assets/images/basic_plan.jpg'" height="200px" cover></v-img>
<v-card-subtitle class="mt-4">
Membership Status: Active
</v-card-subtitle>
<v-card-title>
Weight Loss
</v-card-title>
<VCardText class="ml-3">
<h3>Membership Benifits Overview</h3>
<ul class="ml-5">
<li>Exclusive Access to Medical Weight Loss Experts</li>
<li>As a member, you'll have direct access to our team of nationally-recognized weight loss
physicians and nutritionists. Get personalized treatment plans, prescription medication
management, and ongoing virtual coaching from true experts.</li>
<li>We take a comprehensive, individualized approach. Your membership includes a full medical
evaluation, metabolic testing, body composition analysis, and personal counseling to design
the ideal program for your goals and lifestyle.</li>
<li>Physician-prescribed weight loss medications, lipotropic injections, appetite suppressants,
metabolic boosters, and high-quality supplements are included based on your custom treatment
plan.</li>
<li>Forget dieting on your own. Your membership provides tailored meal plans from registered
dietitians, along with customized exercise regimens created by certified fitness coaches.
</li>
</ul>
</VCardText>
<VCardText>
<p>At HGH, we take an integrative approach to weight loss that combines advanced science with
personalized support. Our programs are designed by leading medical experts and tailored to your
specific needs.
We focus on promoting safe, sustainable weight loss by optimizing hormone levels, boosting
metabolism, curbing cravings, and building better habits.</p>
<p class="text-black">Your membership will start with your first medication purchase</p>
</VCardText>
</v-card>
</VCol>
<VCol cols="12" md="3" v-for="plan in planList" :key="plan.id">
<VCard class="auth-card pa-2 rounded-5">
<div class="col-lg mb-md-0 mb-4">
<v-card-title class="headline text-center">
<h3 class="text-center text-capitalize mb-1 card-title text-wrap">{{ plan.title }}</h3>
</v-card-title>
<div class="d-flex justify-center pt-3 pb-3">
<div class="text-body-1 align-self-start font-weight-medium"> $ </div>
<h4 class="text-h2 font-weight-medium text-primary"
style="color: rgb(var(--v-theme-yellow)) !important">{{
plan.price }}</h4>
<!-- <div class="text-body-1 font-weight-medium align-self-end"> /month </div> -->
</div>
<v-list lines="one">
<v-list-item color="primary" style="padding: 5px;">
<template v-slot:prepend>
<v-icon class="mb-0" color="rgb(var(--v-theme-yellow))" icon="mdi-check-circle"
size="20"></v-icon>
</template>
<v-list-item-title text="">
{{ plan.list_one_title }}
<div class="text-wrap">
<small>{{ plan.list_sub_title }}</small>
</div>
</v-list-item-title>
</v-list-item>
<v-list-item color="primary" style="padding: 5px;">
<template v-slot:prepend>
<v-icon class="mb-0" color="rgb(var(--v-theme-yellow))" icon="mdi-check-circle"
size="20"></v-icon>
</template>
<v-list-item-title class="text-wrap">{{ plan.list_two_title }}</v-list-item-title>
</v-list-item>
</v-list>
<div class="text-center mb-2">
<v-btn class="btn btn-primary d-grid w-100 waves-effect waves-light" color="primary"
variant="flat">
Upgrade Plan
</v-btn>
</div>
</div>
</VCard>
</VCol>
</VRow>
</template>
<style lang="scss">
.membershipImage {
background-image: url('@images/pages/membershipBanner.jpg');
background-size: cover;
}
img.v-img__img.v-img__img--cover {
width: auto;
}
</style>

View File

@@ -0,0 +1,5 @@
<template>
<h1>
test
</h1>
</template>

View File

@@ -0,0 +1,5 @@
<template>
<h1>
test
</h1>
</template>

View File

@@ -0,0 +1,109 @@
<script setup>
import BenefitsOfTestosterone from '@/views/pages/home/BenefitsOfTestosterone.vue';
import Footer from '@/views/pages/home/Footer.vue';
import HeaderTopBar from '@/views/pages/home/HeaderTopBar.vue';
import HelpArea from '@/views/pages/home/HelpArea.vue';
import HelpCenterHeader from '@/views/pages/home/OurProviders/HelpCenterHeader.vue';
import OurExperties from '@/views/pages/home/OurStory/OurExperties.vue';
import SeeYourSelf from '@/views/pages/home/OurProviders/SeeYourSelf.vue';
import MaleExcel from '@/views/pages/home/MaleExcel.vue';
// import OurMission from '@/views/pages/home/OurMission.vue';
import OurPatientsLoveBest from '@/views/pages/home/OurPatientsLoveBest.vue';
import Testimonial from '@/views/pages/home/OurProviders/Testimonial.vue';
import OurTeam from '@/views/pages/home/OurProviders/ourTeam.vue';
import OurTeamMember from '@/views/pages/home/OurProviders/OurTeamMember.vue';
import FaqInfo from '@/views/pages/home/OurStory/FaqInfo.vue';
</script>
<template>
<HeaderTopBar></HeaderTopBar>
<HelpCenterHeader></HelpCenterHeader>
<OurTeam></OurTeam>
<OurTeamMember></OurTeamMember>
<Testimonial></Testimonial>
<SeeYourSelf></SeeYourSelf>
<Footer></Footer>
</template>
<style>
@import "@vendor/fonts/fontawesome.css";
@import "@vendor/fonts/tabler-icons.css";
@import "@vendor/css/rtl/core.css";
@import "@vendor/css/rtl/theme-default.css";
@import "@styles/css/demo.css";
@import "@vendor/libs/node-waves/node-waves.css";
@import "@vendor/css/pages/front-page-help-center.css";
@import "@vendor/css/pages/help-center-front-page.css";
.v-card.v-card--flat.v-theme--light.v-card--density-default.v-card--variant-elevated.text-center.search-header.rounded-0 {
block-size: 357px;
inset-block-start: -185px;
}
body {
display: block !important;
}
a.nav-link.fw-medium {
color: rgba(var(--v-theme-on-background), var(--v-high-emphasis-opacity));
}
.light-style .layout-navbar .menu-text {
color: #5d596c !important;
}
span.app-brand-text.demo.menu-text.fw-bold.ms-2.ps-1 {
color: #5d596c !important;
}
.navbar.landing-navbar {
border-color: rgba(255, 255, 255, 68%) !important;
border-radius: 0;
margin: 0;
background: white;
box-shadow: 0 10px 10px #00000029;
}
.landing-footer .footer-top {
border-radius: none !important;
background-color: #1c5580;
/* background: url("/assets/img/front-pages/backgrounds/footer-bg-dark.png"); */
}
.footer-bottom.py-3 {
background-color: #282c3e;
}
.light-style .landing-footer .footer-link,
.light-style .landing-footer .footer-text {
color: #d3d4dc !important;
}
.light-style .landing-footer .footer-link,
.light-style .landing-footer .footer-text {
color: #d3d4dc;
}
a.footer-link {
color: #d3d4dc;
}
.light-style .landing-footer .footer-title {
color: #fff;
}
.footer-title {
color: #fff;
}
.footer-text {
color: #d3d4dc;
}
@media (max-width: 355px) {
.first-section-pt {
margin-block-start: -121px !important;
}
}
</style>

View File

@@ -0,0 +1,110 @@
<script setup>
import BenefitsOfTestosterone from '@/views/pages/home/BenefitsOfTestosterone.vue';
import Footer from '@/views/pages/home/Footer.vue';
import HeaderTopBar from '@/views/pages/home/HeaderTopBar.vue';
import HelpArea from '@/views/pages/home/HelpArea.vue';
import HelpCenterHeader from '@/views/pages/home/OurStory/HelpCenterHeader.vue';
import OurExperties from '@/views/pages/home/OurStory/OurExperties.vue';
import SeeYourSelf from '@/views/pages/home/OurStory/SeeYourSelf.vue';
import MaleExcel from '@/views/pages/home/MaleExcel.vue';
// import OurMission from '@/views/pages/home/OurMission.vue';
import OurPatientsLoveBest from '@/views/pages/home/OurPatientsLoveBest.vue';
import Testimonial from '@/views/pages/home/Testimonial.vue';
import OurMission from '@/views/pages/home/OurStory/ourMission.vue';
import OurLeaderShip from '@/views/pages/home/OurStory/OurLeaderShip.vue';
import FaqInfo from '@/views/pages/home/OurStory/FaqInfo.vue';
</script>
<template>
<HeaderTopBar></HeaderTopBar>
<HelpCenterHeader></HelpCenterHeader>
<OurMission></OurMission>
<!-- <OurLeaderShip></OurLeaderShip> -->
<OurExperties></OurExperties>
<SeeYourSelf></SeeYourSelf>
<FaqInfo></FaqInfo>
<Footer></Footer>
</template>
<style>
@import "@vendor/fonts/fontawesome.css";
@import "@vendor/fonts/tabler-icons.css";
@import "@vendor/css/rtl/core.css";
@import "@vendor/css/rtl/theme-default.css";
@import "@styles/css/demo.css";
@import "@vendor/libs/node-waves/node-waves.css";
@import "@vendor/css/pages/front-page-help-center.css";
@import "@vendor/css/pages/help-center-front-page.css";
.v-card.v-card--flat.v-theme--light.v-card--density-default.v-card--variant-elevated.text-center.search-header.rounded-0 {
block-size: 357px;
inset-block-start: -185px;
}
body {
display: block !important;
}
a.nav-link.fw-medium {
color: rgba(var(--v-theme-on-background), var(--v-high-emphasis-opacity));
}
.light-style .layout-navbar .menu-text {
color: #5d596c !important;
}
span.app-brand-text.demo.menu-text.fw-bold.ms-2.ps-1 {
color: #5d596c !important;
}
.navbar.landing-navbar {
border-color: rgba(255, 255, 255, 68%) !important;
border-radius: 0;
margin: 0;
background: white;
box-shadow: 0 10px 10px #00000029;
}
.landing-footer .footer-top {
border-radius: none !important;
background-color: #1c5580;
/* background: url("/assets/img/front-pages/backgrounds/footer-bg-dark.png"); */
}
.footer-bottom.py-3 {
background-color: #282c3e;
}
.light-style .landing-footer .footer-link,
.light-style .landing-footer .footer-text {
color: #d3d4dc !important;
}
.light-style .landing-footer .footer-link,
.light-style .landing-footer .footer-text {
color: #d3d4dc;
}
a.footer-link {
color: #d3d4dc;
}
.light-style .landing-footer .footer-title {
color: #fff;
}
.footer-title {
color: #fff;
}
.footer-text {
color: #d3d4dc;
}
@media (max-width: 355px) {
.first-section-pt {
margin-block-start: -121px !important;
}
}
</style>

View File

@@ -0,0 +1,36 @@
<script setup>
// import { useGenerateImageVariant } from '@core/composable/useGenerateImageVariant';
// import page401 from '@images/pages/401.png';
// const authThemeMask = useGenerateImageVariant(miscMaskLight, miscMaskDark)
</script>
<template>
<div class="misc-wrapper">
<div class="misc-center-content text-center mt-12">
<!-- 👉 Title and subtitle -->
<h4 class="text-h4 font-weight-medium mt-3">
You are not authorized! 🔐
</h4>
<p>You don't have permission to access this page.</p>
<!--
<VBtn to="/login">
Back to Home
</VBtn>
-->
</div>
<VImg
:src="authThemeMask"
class="misc-footer-img d-none d-md-block"
/>
</div>
</template>
<style lang="scss">
@use "@core-scss/template/pages/misc.scss";
</style>

View File

@@ -0,0 +1,873 @@
<script setup>
// import LabKit from '@/views/pages/overview/LabKit.vue';
import UpcomingAppiontment from "@/pages/patient/upcomingAppiontment.vue";
// import Prescription from '@/views/pages/patient/prescription.vue';
import Echo from "laravel-echo";
import { useRouter } from "vue-router";
const router = useRouter();
// import moment from 'moment';
import moment from "moment-timezone";
import Pusher from "pusher-js";
import { onMounted } from "vue";
import { useStore } from "vuex";
const store = useStore();
// import HeathCare from '@images/pages/healthcare-wellness-wellbeing-first-aid-box-word-graphic.jpg';
const isLoadingVisible = ref(false);
const showTimeBar = ref(false);
const planName = ref("");
const planAmount = ref("");
const loginuser = ref("");
const email = ref("");
const phoneNumber = ref("");
const shippingAddress = ref("");
const address = ref("");
const city = ref("");
const state = ref("");
const zip_code = ref("");
const country = ref("");
const dob = ref("");
const gender = ref("");
const height = ref("");
const weight = ref("");
const scheduleDate = ref("");
const scheduleTime = ref("");
const timeZone = ref("");
const timeDifference = ref();
const timeUntilMeeting = ref("");
const callEnd = ref("");
const patientId = ref("");
const appiontmentId = ref("");
const doctorName = ref("");
const itemsPrescriptions = ref([]);
const orders = ref([]);
const patientLabkit = ref([]);
const order_id = ref();
const notifications = ref([]);
const notes = ref([]);
const items = ref([]);
const status = ref();
const shippingAmmount = ref();
const appoinmentList = ref([]);
const upcomingAppoinmentList = ref([]);
const patientsStats = ref([]);
onMounted(async () => {
await fetchApiData();
await getPatientprescription();
await orderPtaientList();
await historyNotes();
store.dispatch("updateIsLoading", false);
});
const fetchApiData = async () => {
store.dispatch("updateIsLoading", true);
await store.dispatch("getPatientInfo");
await store.dispatch("getPlanInfo");
await store.dispatch("getPatientAppointment");
await store.dispatch("getDashboardStats");
patientsStats.value = store.getters.getPatientsStats;
// appendItemHistoryToStats(patientsStats.value);
// console.log("patientsStats", patientsStats.value);
appoinmentList.value = store.getters.getBookedAppointment;
upcomingAppoinmentList.value =
store.getters.getPatientUpcomingAppiontments.upcoming_appointments;
console.log("final", upcomingAppoinmentList.value);
loginuser.value =
store.getters.getPatient.first_name +
" " +
store.getters.getPatient.last_name;
let diffInMinutes = store.getters.getTimeDiff;
// console.log("diffInMinutes", diffInMinutes);
timeZone.value = store.getters.getBookedAppointment.timezone;
order_id.value = store.getters.getBookedAppointment.order_id;
doctorName.value = store.getters.getBookedAppointment.agent_name;
status.value = store.getters.getBookedAppointment.status;
appiontmentId.value = store.getters.getBookedAppointment.appiontmentId;
console.log("appiontmentId", store.getters.getBookedAppointment);
const time = store.getters.getBookedAppointment.appointment_time;
callEnd.value = store.getters.getBookedAppointment.end_time;
let dateString = store.getters.getPatient.dob;
let parts = dateString.split("-");
dob.value = `${parts[1]}-${parts[2]}-${parts[0]}`;
let appointmentDate = convertUtcDateTimeToLocal(
store.getters.getBookedAppointment.appointment_date,
store.getters.getBookedAppointment.appointment_time,
"date"
);
let appointmentTime = convertUtcDateTimeToLocal(
store.getters.getBookedAppointment.appointment_date,
store.getters.getBookedAppointment.appointment_time,
"time"
);
// console.log("appointmentOverview------", appointmentDate, appointmentTime);
scheduleDate.value = moment(appointmentDate, "YYYY-MM-DD").format(
"MMMM DD, YYYY"
);
scheduleTime.value = moment(appointmentTime, "HH:mm:ss").format("hh:mm A");
// console.log("scheduleDate------------------", scheduleDate.value, scheduleTime.value);
timeDifference.value = relativeTimeFromDate(
appointmentDate,
appointmentTime
);
// console.log("timeDifference--------", timeDifference.value);
items.value = store.getters.getBookedAppointment.items.items_list;
shippingAmmount.value = store.getters.getBookedAppointment.items.total;
// console.log("shippingAmmount", shippingAmmount.value);
};
const upComing = computed(() => {
appoinmentList.value = store.getters.getBookedAppointment;
return appoinmentList.value.map((appiontment) => ({
...appiontment,
timeZone: appiontment.timezone,
order_id: appiontment.order_id,
doctorName: appiontment.agent_name,
status: appiontment.status,
appiontmentId: appiontment.appiontmentId,
// console.log("appiontmentId", store.getters.getBookedAppointment);
time: appiontment.appointment_time,
callEnd: appiontment.end_time,
// let dateString = store.getters.getPatient.dob
// let parts = dateString.split("-");
// dob.value = `${parts[1]}-${parts[2]}-${parts[0]}`;
appointmentDate: convertUtcDateTimeToLocal(
appiontment.appointment_date,
appiontment.appointment_time,
"date"
),
appointmentTime: convertUtcDateTimeToLocal(
appiontment.appointment_date,
appiontment.appointment_time,
"time"
),
timeDifference: relativeTimeFromDate(appointmentDate, appointmentTime),
// scheduleDate.value = moment(appointmentDate, "YYYY-MM-DD").format("MMMM DD, YYYY"),
// scheduleTime.value = moment(appointmentTime, "HH:mm:ss").format("hh:mm A")
// items = store.getters.getBookedAppointment.items.items_list,
// shippingAmmount = store.getters.getBookedAppointment.items.total,
// console.log("shippingAmmount", shippingAmmount.value);
}));
});
const relativeTimeFromDate = (dateString, timeString) => {
// Combine the date and time into a full datetime, treated as local time
const eventDateTime = new Date(dateString + "T" + timeString);
// Get the current date and time
const now = new Date();
// Calculate the difference in milliseconds between the event time and now
const diffMs = eventDateTime - now;
// Check if the event time is in the past and log it to the console if it is
if (diffMs > 0) {
showTimeBar.value = true;
console.log("The event time is in the past.");
}
// Convert the difference to an absolute value
const absDiffMs = Math.abs(diffMs);
// Calculate differences in days, hours, and minutes
const minutes = Math.floor(absDiffMs / 60000) % 60;
const hours = Math.floor(absDiffMs / 3600000) % 24;
const days = Math.floor(absDiffMs / 86400000);
// Determine the appropriate suffix based on whether the date is in the future or past
const suffix = diffMs < 0 ? " ago" : " left";
// Result formulation based on the above logic
if (days > 0) {
return `${days} day${days > 1 ? "s" : ""}${suffix}`;
} else {
// Compose hours and minutes
let result = [];
if (hours > 0) {
result.push(`${hours} hour${hours > 1 ? "s" : ""}`);
}
if (minutes > 0) {
result.push(`${minutes} minute${minutes > 1 ? "s" : ""}`);
}
if (result.length === 0) {
return "just now";
}
return result.join(" ") + suffix;
}
};
const historyNotes = async () => {
// console.log('...........', patientId, appointmentId);
await store.dispatch("patientsShippingActivity");
// notes.value = store.getters.getPatientNotes;
notifications.value =
store.getters.getPatientsShippingActivity.item_history;
console.log("notifications", notifications.value);
};
const orderPtaientList = async () => {
await store.dispatch("orderPtaientList");
orders.value = store.getters.getPatientOrderList;
console.log("orders Test", orders.value);
// await store.dispatch("getPatientLabKit");
// patientLabkit.value = store.getters.getPatientLabKit;
store.dispatch("updateIsLoading", false);
};
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'.");
}
};
const getPatientprescription = async () => {
// await store.dispatch('getPatientPrescriptions')
// itemsPrescriptions.value = store.getters.getPrescription
await store.dispatch("getPatientPrescriptionsByID", {
appointment_id: appiontmentId.value,
});
let prescriptions = store.getters.getPrescriptionList;
if (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.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;
});
} else {
itemsPrescriptions.value = "";
}
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, "-");
};
onMounted(() => {
store.dispatch("updateIsLoading", true);
window.Pusher = Pusher;
const key = "bc8bffbbbc49cfa39818";
const cluster = "mt1";
let echo = new Echo({
broadcaster: "pusher",
key: key,
cluster: cluster,
forceTLS: true,
auth: {
headers: {
Authorization: "Bearer " + localStorage.getItem("access_token"),
},
},
});
echo.private(
"patient-end-call-" + localStorage.getItem("patient_id")
).listen("AppointmentCallEnded", async (e) => {
console.log("OverviewAppointmentCallEnded", e);
fetchApiData();
});
store.dispatch("updateIsLoading", false);
});
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 desserts = [
{
dessert: "Blood Test",
calories: "pending",
},
{
dessert: "Cancer",
calories: "pending",
},
];
const viewOrder = (orderId) => {
router.push({ name: "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} `;
};
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 headers = [
{
title: "#Order",
key: "id",
},
{
title: "Date",
key: "updated_at",
},
{
title: "Price",
key: "total_amount",
},
{
title: "status",
key: "status",
},
];
const historyDetail = (history) => {
console.log("history", history.id);
// router.push("/patient/complete-appintment-detail/" + history.id);
router.push("/order-detail/" + history.id);
};
</script>
<template>
<VContainer class="px-0">
<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>
<!-- <VAlert color="rgb(var(--v-theme-yellow))" text-white v-if="showTimeBar">
<span>
<p class="text-white mb-0"><b>Remaining Time: </b> &nbsp; {{ timeDifference }}</p>
</span>
</VAlert> -->
<!-- <VAlert type="success" variant="outlined">Password Has been set
</VAlert> -->
<h3 class="text-center mt-3"></h3>
<VRow class="match-height">
<!-- 👉 Congratulation John -->
<!-- 👉 Ecommerce Transition -->
<VCol cols="12" md="7" lg="12">
<VCard>
<VCardTitle class="pb-0"
><b> Welcome {{ loginuser }},</b>
</VCardTitle>
<VCardText class="m-0">
<p class="mt-0">
Here you'll find your order, Appointments, and
subcriptions from HGH.
</p>
</VCardText>
<VCardText class="pt-6">
<VRow class="">
<VCol cols="6" md="3">
<div class="d-flex align-center gap-4">
<VAvatar
color="info"
variant="tonal"
size="42"
>
<VIcon icon="tabler-chart-pie-2" />
</VAvatar>
<div class="d-flex flex-column">
<span
class="text-h5 font-weight-medium"
>{{
patientsStats.total_meetings
}}</span
>
<span class="text-sm"> Completed </span>
</div>
</div>
</VCol>
<VCol cols="6" md="3">
<div class="d-flex align-center gap-4">
<VAvatar
color="primary"
variant="tonal"
size="42"
>
<VIcon icon="tabler-chart-pie-2" />
</VAvatar>
<div class="d-flex flex-column">
<span
class="text-h5 font-weight-medium"
>{{
patientsStats.upcoming_meetings
}}</span
>
<span class="text-sm"> Upcoming </span>
</div>
</div>
</VCol>
<VCol cols="6" md="3">
<div class="d-flex align-center gap-4">
<VAvatar
color="error"
variant="tonal"
size="42"
>
<VIcon icon="tabler-shopping-cart" />
</VAvatar>
<div class="d-flex flex-column">
<span
class="text-h5 font-weight-medium"
>{{
patientsStats.total_orders
}}</span
>
<span class="text-sm"> Orders </span>
</div>
</div>
</VCol>
<VCol cols="6" md="3">
<div class="d-flex align-center gap-4">
<VAvatar
color="success"
variant="tonal"
size="42"
>
<VIcon icon="tabler-currency-dollar" />
</VAvatar>
<div class="d-flex flex-column">
<span
class="text-h5 font-weight-medium"
>{{
patientsStats.total_subscription
}}</span
>
<span class="text-sm">
Subscription
</span>
</div>
</div>
</VCol>
</VRow>
</VCardText>
<!-- </VCardText>-->
</VCard>
</VCol>
</VRow>
<VRow>
<VCol cols="12" md="6">
<!-- <VCardTitle class="text-wrap" color="errors">Lab Kit
<RouterLink class="text-primary" to="/labs">
<small style="font-size: 12px; font-weight: normal;">(see all)</small>
</RouterLink>
</VCardTitle>
<LabKit :time="scheduleTime" :timezone="timeZone" :timeDiff="timeDifference" /> -->
<VCardTitle class="text-wrap"
><b>Upcoming Appointment</b>
<RouterLink class="text-primary" to="/complete">
<small style="font-size: 12px; font-weight: normal">
(see all)
</small>
</RouterLink>
</VCardTitle>
<!-- <UpcomingAppiontment /> -->
<UpcomingAppiontment
:upcommingAppiontmetns="upcomingAppoinmentList"
:iscallEnd="callEnd"
:orderid="order_id"
:date="scheduleDate"
:time="scheduleTime"
:timezone="timeZone"
:isStatus="status"
:timeDiff="timeDifference"
:isShippmentAmmount="shippingAmmount"
:showShipment="false"
color="rgb(var(--v-theme-yellow))"
:isItem="items"
/>
</VCol>
<VCol cols="12" md="6">
<VCardTitle class="text-wrap"
><b>Orders</b>
<RouterLink class="text-primary" to="/orders-list">
<small style="font-size: 12px; font-weight: normal">
(see all)
</small>
</RouterLink>
</VCardTitle>
<template v-if="items && items.length > 0">
<VCard
class="rounded-5"
style="
min-height: 150px;
overflow-y: scroll;
overflow: hidden;
border-radius: 10px;
"
>
<VDataTable
:headers="headers"
:items="orders"
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.title }}
</h6>
<span class="text-sm text-start align-self-start">
{{ item.list_sub_title }}
</span>
</div>
</div>
</template> -->
<template #item.id="{ item }">
<span
@click="historyDetail(item)"
style="cursor: pointer"
><b> {{ item.id }} </b></span
>
</template>
<template #item.total_amount="{ item }">
<span>
<b
>${{
formatCurrency(item.total_amount)
}}
</b></span
>
</template>
<template #item.updated_at="{ item }">
<span>{{ formatDate(item.updated_at) }}</span>
</template>
<template #item.status="{ item }">
<span>
<VChip
variant="tonal"
:color="getStatusColor(item.status)"
size="small"
>
{{ item.status }}
</VChip>
</span>
</template>
<template #bottom />
</VDataTable>
<!-- <VTable class="text-no-wrap">
<thead>
<tr>
<th class="text-uppercase">
<b>Order ID</b>
</th>
<th class="text-uppercase">
<b>Date</b>
</th>
<th class="text-uppercase">
<b>Amount</b>
</th>
<th class="text-uppercase text-center">
<b>Status</b>
</th>
</tr>
</thead>
<tbody>
<tr v-for=" order in orders " :key="order">
<td>
<div @click="viewOrder(order.id)">{{ order.id }} </div>
</td>
<td class="text-center">
{{ formatDate(order.updated_at) }}
</td>
<td class="text-center">
${{ formatCurrency(order.total_amount) }}
</td>
<td class="text-center">
<v-chip x-small class="mr-2" outlined>{{ order.status }} </v-chip>
</td>
</tr>
</tbody>
</VTable>
<VDivider class="m-0" /> -->
</VCard>
</template>
<template v-else>
<VAlert border="start" color="rgb(var(--v-theme-yellow))">
<div class="text-center">No data found</div>
</VAlert>
</template>
<!-- <VCardTitle class="pt-3">
<b>
Lab Kits
</b>
</VCardTitle>
<template v-if="patientLabkit && patientLabkit.length > 0">
<VCard class="rounded-5"
style="min-height:150px; overflow-y:scroll; overflow: hidden; border-radius: 10px; ">
<VTable class="text-no-wrap">
<thead>
<tr>
<th class="text-uppercase">
<b>Name</b>
</th>
<th class="text-uppercase text-center">
<b>Status</b>
</th>
</tr>
</thead>
<tbody>
<tr v-for=" item in patientLabkit " :key="item.id">
<td>
{{ item.name }}
</td>
<td class="text-center">
<VChip variant="tonal" :color="getStatusColor(item.status)" size="small">
{{ item.status }}
</VChip>
</td>
</tr>
</tbody>
</VTable>
</VCard>
</template>
<template v-else>
<VAlert border="start" color="rgb(var(--v-theme-yellow))" variant="tonal">
<div class="text-center">No data found</div>
</VAlert>
</template> -->
<!-- <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="primary" 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>
<VDivider />
</VTimelineItem>
</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>
</VTimeline> -->
<!-- </VCardText> -->
<!-- </VCard> -->
<!-- <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> -->
</VCol>
</VRow>
</VContainer>
</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;
}
button.v-expansion-panel-title.v-expansion-panel-title--active {
background-color: rgb(var(--v-theme-yellow)) !important;
color: #fff;
}
span.v-expansion-panel-title__icon {
color: #fff;
display: none !important;
}
.v-expansion-panel {
background-color: #fff;
border-radius: 10px;
box-shadow: 0px 4px 12px rgba(0, 0, 0, 0.1);
margin-bottom: 10px;
overflow: hidden;
transition: box-shadow 0.3s ease;
}
.v-expansion-panel:hover {
box-shadow: 0px 6px 16px rgba(0, 0, 0, 0.15);
}
.v-expansion-panel-title {
padding: 15px;
font-size: 13px;
font-weight: 600;
color: #333;
cursor: pointer;
display: flex;
justify-content: space-between;
align-items: center;
transition: background-color 0.3s ease, color 0.3s ease;
}
.v-expansion-panel-title.pt-0 {
padding-top: 0;
}
// .v-expansion-panel-title:hover {
// background-color: #f9f9f9;
// color: #222;
// }
.v-expansion-panel-title .mdi {
font-size: 20px;
color: #666;
transition: transform 0.3s ease, color 0.3s ease;
}
.v-expansion-panel-title.v-expansion-panel-title--active .mdi {
transform: rotate(180deg);
color: #222;
}
.v-expansion-panel-text {
padding: 10px 15px;
font-size: 13px;
color: #555;
border-top: 1px solid #eee;
}
.v-expansion-panel-text .d-flex {
margin-bottom: 24px;
}
.v-expansion-panel-text .d-flex > div {
flex: 1;
}
.v-expansion-panel-text p {
margin-bottom: 16px;
font-size: 14px;
}
.v-expansion-panel-text h3 {
font-size: 20px;
font-weight: 600;
margin-bottom: 16px;
color: #333;
}
.bg-primary {
background-color: rgb(var(--v-theme-yellow-theme-button)) !important;
}
</style>

View File

@@ -0,0 +1,15 @@
<script setup>
import PatientAppointmentsDetail from '@/views/pages/tables/patient-appiontments-detail.vue';
</script>
<template>
<VRow>
<VCol cols="12">
<VCard class="text-primary" title="Appointment Detail">
<PatientAppointmentsDetail />
</VCard>
</VCol>
</VRow>
</template>

View File

@@ -0,0 +1,527 @@
<script setup>
import { useRouter } from 'vue-router';
import { useTheme } from 'vuetify';
import { useStore } from 'vuex';
const router = useRouter()
const store = useStore()
const isMobile = ref(window.innerWidth <= 768);
const prescriptionModelForm = ref(false)
const search = ref('');
const loading = ref(true);
const page = ref(1);
const itemsPerPage = ref(10);
const pageCount = ref(0);
const form = ref(null);
const headers = ref([
{ title: 'Patient Name', key: 'name' },
{ title: 'Lab Kit', key: 'testName' },
{ title: 'Address', key: 'address' },
{ title: 'Date', key: 'date' },
{ title: 'Status', key: 'status' },
// { title: 'Actions', key: 'actions', sortable: false },
]);
const labOrders = ref([
]);
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 openDialog = () => {
state.addLabOrder = true
}
const state = reactive({
addLabOrder: false,
selectedTestKitId: null,
valid: false,
testKits: [],
});
const testKits = computed(async () => {
let Calluser = localStorage.getItem('call_user')
let patient = JSON.parse(Calluser)
await store.dispatch('getLabKitProductList', {})
console.log(store.getters.getLabOrderProductList)
state.testKits = store.getters.getLabOrderProductList
});
const getFieldRules = (value, message) => {
return [(v) => !!v || message || `Field is required`];
};
const storeTestKit = async () => {
let Calluser = localStorage.getItem('call_user')
let patient = JSON.parse(Calluser)
loading.value = true
await store.dispatch('saveLabOrderProductList', {
labkit: state.selectedTestKitId,
patient_id: patient.id
})
console.log('Selected Test Kit:', state.selectedTestKitId);
await store.dispatch('getLabOrderList', {
patient_id: patient.id,
})
labOrders.value = [];
let testKitOrder = store.getters.getLabOrderKitAgent
for (let data of testKitOrder) {
let dataObject = {}
dataObject.name = data.name
dataObject.address = data.address
dataObject.total_amount = data.total_amount
dataObject.shipping_amount = data.shipping_amount
dataObject.status = data.status
dataObject.phone = data.phone
dataObject.date = formatDate(data.date)
dataObject.email = data.email
dataObject.testName = data.testName
dataObject.actions = ''
dataObject.id = data.id
labOrders.value.push(dataObject)
}
labOrders.value.sort((a, b) => {
return b.id - a.id;
});
state.addLabOrder = false;
state.selectedTestKitId = ''
loading.value = false
};
const laborderList = computed(async () => {
let Calluser = localStorage.getItem('call_user')
let patient = JSON.parse(Calluser)
await store.dispatch('getLabOrderList', {
patient_id: patient.id,
})
labOrders.value = [];
let testKitOrder = store.getters.getLabOrderKitAgent
for (let data of testKitOrder) {
let dataObject = {}
dataObject.name = data.name
dataObject.address = data.address
dataObject.total_amount = data.total_amount
dataObject.shipping_amount = data.shipping_amount
dataObject.status = data.status
dataObject.phone = data.phone
dataObject.date = formatDate(data.date)
dataObject.email = data.email
dataObject.testName = data.testName
dataObject.actions = ''
dataObject.id = data.id
labOrders.value.push(dataObject)
}
labOrders.value.sort((a, b) => {
return b.id - a.id;
});
console.log('patientNotes', labOrders.value)
store.dispatch('updateIsLoading', false)
loading.value = false
});
// Components
import { computed } from 'vue';
const vuetifyTheme = useTheme()
const userRole = localStorage.getItem('user_role'); // Fetch user role from local storage
const isAgent = computed(() => userRole.toLowerCase() === 'agent');
const formatDate = (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 () => {
const navbar = document.querySelector('.layout-navbar');
const callDiv = document.querySelector('.layout-page-content');
if (navbar) {
navbar.style.display = 'block';
}
if (callDiv)
callDiv.style.padding = '1.5rem';
console.log("router", store.getters.getCurrentPage)
// if (userRole === 'agent') {
// await store.dispatch('getHistoryPatientNotes')
// let notes = store.getters.getPatientNotes
// for(let data of notes ){
// if(data.note_type=='Notes'){
// let dataObject = {}
// dataObject.note=data.note
// dataObject.date=formatDateDate(data.created_at)
// dataObject.id=data.id
// patientNotes.value.push(dataObject)
// }
// }
// patientNotes.value.sort((a, b) => {
// return b.id - a.id;
// });
// console.log('patientNotes', patientNotes.value)
// loading.value = false;
// }
window.addEventListener('resize', checkMobile);
});
const checkMobile = () => {
isMobile.value = window.innerWidth <= 768;
};
const openNotes = async () => {
}
</script>
<template>
<v-row class='mb-2'>
<v-col cols="12" class="text-left " md="12">
<v-btn color="primary" size="small" @click="openDialog()" class="mr-2">
<v-icon>mdi-plus</v-icon> Add Lab Order
</v-btn>
<v-btn color="primary" size="small" class="mr-2"><v-icon start icon="tabler-report-medical"></v-icon> GLP-1
Lab
Order</v-btn>
<v-btn color="primary" size="small"><v-icon start icon="tabler-report-medical"></v-icon> TRT Lab
order</v-btn>
<!-- <v-btn color="primary" small @click="openCallModel('Request For Documents')">
<v-icon>mdi-plus</v-icon> Request For Docs
</v-btn> -->
</v-col>
<!-- <v-col cols="12" :class="isMobile ? 'text-left' : 'text-right'" md="3">
</v-col>
<v-col cols="12" class="text-left" md="3">
</v-col> -->
</v-row>
<v-row>
<v-col cols="12" md="12" v-if="laborderList">
<v-data-table :headers="headers" :items="labOrders" :search="search" :loading="loading" :page.sync="page"
:items-per-page.sync="itemsPerPage" @page-count="pageCount = $event" class="elevation-1">
<template v-slot:top>
<v-toolbar flat :height="30">
<v-toolbar-title>Lab Kits</v-toolbar-title>
<v-divider class="mx-4" inset vertical></v-divider>
<v-spacer></v-spacer>
<v-text-field v-model="search" label="Search" single-line hide-details></v-text-field>
</v-toolbar>
</template>
<template v-slot:item.status="{ item }">
<v-chip :color="getStatusColor(item.status)" label size="small" variant="text">
{{ item.status }}
</v-chip>
</template>
<!-- <template v-slot:item.actions="{ item }">
<v-btn v-if="item.status === 'In Progress'" color="success" size="small" class="mr-2"
@click="completeOrder(item.id)">
Complete
</v-btn>
<v-btn v-if="item.status === 'In Progress'" color="error" size="small" @click="cancelOrder(item.id)">
Cancel
</v-btn>
</template> -->
</v-data-table>
</v-col>
</v-row>
<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 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>
</template>
<style lang="scss" scoped>
.hidden-component {
display: none
}
.meta-key {
border: thin solid rgba(var(--v-border-color), var(--v-border-opacity));
border-radius: 6px;
block-size: 1.5625rem;
line-height: 1.3125rem;
padding-block: 0.125rem;
padding-inline: 0.25rem;
}
::v-deep .custom-menu {
position: relative;
}
::v-deep .custom-menu::before {
content: "" !important;
position: absolute !important;
transform: translateY(-50%);
top: 50% !important;
left: -8px !important;
border-left: 8px solid transparent !important;
border-right: 8px solid transparent !important;
border-bottom: 8px solid #fff !important;
}
// Styles for the VList component
.more .v-list-item-title {
color: rgb(106 109 255);
}
.more .menu-item:hover {
cursor: pointer;
}
.slide-enter-active,
.slide-leave-active {
transition: transform 0.3s ease;
}
.slide-enter,
.slide-leave-to {
transform: translateX(-100%);
}
.start-call-btn {
opacity: 0;
display: none;
transition: opacity 0.3s ease;
}
.button_margin {
margin: 2px;
}
.dialog_padding {
padding: 5px;
}
.custom-menu .v-menu__content {
background-color: #333;
color: #fff;
border-radius: 4px;
padding: 8px 0;
}
.user-info {
display: flex;
flex-direction: column;
transition: opacity 0.3s ease;
}
.list-item-hover {
transition: background-color 0.3s ease;
&:hover {
background-color: rgba(var(--v-theme-primary), 0.1);
.start-call-btn {
opacity: 1;
display: block;
position: relative;
left: -35px;
}
.user-info {
opacity: 0;
display: none;
}
}
}
.pop_card {
overflow: hidden !important;
padding: 10px;
}
.v-overlay__content {
max-height: 706.4px;
max-width: 941.6px;
min-width: 24px;
--v-overlay-anchor-origin: bottom left;
transform-origin: left top;
top: 154.4px !important;
left: 204px !important;
}
.button_margin {
margin-top: 10px;
font-size: 10px;
}
/* Responsive Styles */
@media screen and (max-width: 768px) {
.pop_card {
max-width: 100%;
margin: 0 auto;
}
}
.container_img {
display: flex;
flex-direction: column;
width: 100%;
align-items: center;
}
.image {
order: 2;
/* Change the order to 2 in mobile view */
}
.text {
order: 1;
/* Change the order to 1 in mobile view */
}
/* Media query for mobile view */
@media (max-width: 768px) {
.container_img {
flex-direction: row;
margin-top: 10px;
}
.button_margin_mobile {
width: 100%;
}
.image {
width: 20%;
padding: 0 10px;
}
.text {
width: 80%;
/* Each takes 50% width */
padding: 0 10px;
/* Optional padding */
}
}
::-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 */
}
/* Container for the content */
.scroll-container {
max-height: 191px;
/* Maximum height of the scrollable content */
overflow-y: scroll;
/* Enable vertical scrolling */
}
/* Content within the scroll container */
.scroll-content {
padding: 20px;
}
/* Example of additional styling for content */
.scroll-content p {
margin-bottom: 20px;
}
.cross button {
padding: 0px;
margin: 0px;
/* font-size: 10px; */
background: none;
border: none;
box-shadow: none;
height: 23px;
}
.v-data-table-header {
display: table-header-group;
}
</style>

View File

@@ -0,0 +1,14 @@
<script setup>
import PatientLabKitDetail from '@/views/pages/tables/patient-labkit-detail.vue';
</script>
<template>
<VRow>
<VCol cols="12">
<VCard class="text-primary" title="LabKit Detail">
<PatientLabKitDetail />
</VCard>
</VCol>
</VRow>
</template>

View File

@@ -0,0 +1,320 @@
<script setup>
import avatar1 from "@images/avatars/avatar-1.png";
import { ref, reactive, computed, onMounted } from "vue";
import {
emailValidator,
requiredEmail,
requiredName,
requiredPhone,
validUSAPhone,
} from "@validators";
import { useStore } from "vuex";
const store = useStore();
let imageBlob = null;
const profileData = ref([]);
const errors = ref({
name: undefined,
email: undefined,
phone_no: undefined,
});
const accountData = {
avatarImg: avatar1,
first_name: "",
last_name: "",
email: "",
phone_no: "",
};
const refVForm = ref();
const refInputEl = ref();
const isConfirmDialogOpen = ref(false);
const accountDataLocal = ref(structuredClone(accountData));
const isAccountDeactivated = ref(false);
const validateAccountDeactivation = [
(v) => !!v || "Please confirm account deactivation",
];
const getIsTonalSnackbarVisible = ref(false);
const ImageBase64 = ref();
const resetForm = () => {
accountDataLocal.value = structuredClone(accountData);
};
const changeAvatar = async (file) => {
const fileReader = new FileReader();
const { files } = file.target;
if (files && files.length) {
fileReader.readAsDataURL(files[0]);
fileReader.onload = () => {
if (typeof fileReader.result === "string") {
accountDataLocal.value.avatarImg = fileReader.result;
}
ImageBase64.value = fileReader.result.split(",")[1];
store.dispatch("profilePicPatient", {
image: ImageBase64.value, //ecelData,
});
};
}
};
onMounted(async () => {
await store.dispatch("patientDetial");
let list = await store.getters.getPatientDetail;
console.log("list", list);
accountDataLocal.value.email = list.email;
accountDataLocal.value.first_name = list.first_name;
accountDataLocal.value.last_name = list.last_name;
accountDataLocal.value.phone_no = list.phone_no;
if (!list.profile_picture) {
accountDataLocal.value.avatarImg = avatar1;
} else {
accountDataLocal.value.avatarImg = list.profile_picture;
}
});
// reset avatar image
const resetAvatar = () => {
accountDataLocal.value.avatarImg = accountData.avatarImg;
};
const timezones = [
"(GMT-11:00) International Date Line West",
"(GMT-11:00) Midway Island",
"(GMT-10:00) Hawaii",
"(GMT-09:00) Alaska",
"(GMT-08:00) Pacific Time (US & Canada)",
"(GMT-08:00) Tijuana",
"(GMT-07:00) Arizona",
"(GMT-07:00) Chihuahua",
"(GMT-07:00) La Paz",
"(GMT-07:00) Mazatlan",
"(GMT-07:00) Mountain Time (US & Canada)",
"(GMT-06:00) Central America",
"(GMT-06:00) Central Time (US & Canada)",
"(GMT-06:00) Guadalajara",
"(GMT-06:00) Mexico City",
"(GMT-06:00) Monterrey",
"(GMT-06:00) Saskatchewan",
"(GMT-05:00) Bogota",
"(GMT-05:00) Eastern Time (US & Canada)",
"(GMT-05:00) Indiana (East)",
"(GMT-05:00) Lima",
"(GMT-05:00) Quito",
"(GMT-04:00) Atlantic Time (Canada)",
"(GMT-04:00) Caracas",
"(GMT-04:00) La Paz",
"(GMT-04:00) Santiago",
"(GMT-03:30) Newfoundland",
"(GMT-03:00) Brasilia",
"(GMT-03:00) Buenos Aires",
"(GMT-03:00) Georgetown",
"(GMT-03:00) Greenland",
"(GMT-02:00) Mid-Atlantic",
"(GMT-01:00) Azores",
"(GMT-01:00) Cape Verde Is.",
"(GMT+00:00) Casablanca",
"(GMT+00:00) Dublin",
"(GMT+00:00) Edinburgh",
"(GMT+00:00) Lisbon",
"(GMT+00:00) London",
];
const currencies = [
"USD",
"EUR",
"GBP",
"AUD",
"BRL",
"CAD",
"CNY",
"CZK",
"DKK",
"HKD",
"HUF",
"INR",
];
const onSubmit = async () => {
const { valid } = await refVForm.value.validate();
console.log(valid);
if (valid) {
try {
console.log(ImageBase64.value);
await store.dispatch("profileUpdatePatient", {
first_name: accountDataLocal.value.first_name,
last_name: accountDataLocal.value.last_name,
phone_no: accountDataLocal.value.phone_no,
image: ImageBase64.value, //ecelData,
});
} catch (error) {
console.error(error);
}
// await store.dispatch("patientDetial");
//let list = await store.getters.getPatientDetail;
//console.log("list", list);
//accountDataLocal.value.avatarImg = list.profile_picture;
// accountDataLocal.value.name = list.name;
//accountDataLocal.value.last_name = list.last_name;
// accountDataLocal.value.phone_no = list.phone_no;
}
};
const formatPhoneNumber = () => {
// Remove non-numeric characters from the input
const numericValue = accountDataLocal.value.phone_no.replace(/\D/g, "");
// Apply formatting logic
if (numericValue.length <= 10) {
accountDataLocal.value.phone_no = numericValue.replace(
/(\d{3})(\d{3})(\d{4})/,
"($1) $2-$3"
);
} else {
// Limit the input to a maximum of 14 characters
const truncatedValue = numericValue.slice(0, 10);
accountDataLocal.value.phone_no = truncatedValue.replace(
/(\d{3})(\d{3})(\d{4})/,
"($1) $2-$3"
);
}
};
</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>
<VRow>
<VCol cols="12">
<VCard>
<VCardText>
<div class="d-flex mb-10">
<VSnackbar
v-model="getIsTonalSnackbarVisible"
:timeout="5000"
location="top end"
variant="flat"
color="success"
>
<VIcon class="ri-success-line success-icon" />
Profile Update Successfully
</VSnackbar>
<!-- 👉 Avatar -->
<VAvatar
rounded
size="100"
class="me-6"
:image="accountDataLocal.avatarImg"
/>
<!-- 👉 Upload Photo -->
<form class="d-flex flex-column justify-center gap-4">
<div class="d-flex flex-wrap gap-4">
<VBtn
color="primary"
@click="refInputEl?.click()"
>
<VIcon
icon="ri-upload-cloud-line"
class="d-sm-none"
/>
<span class="d-none d-sm-block"
>Upload Profile Image</span
>
</VBtn>
<input
ref="refInputEl"
type="file"
name="file"
accept=".jpeg,.png,.jpg,GIF"
hidden
@input="changeAvatar"
/>
</div>
<p class="text-body-1 mb-0">
Allowed JPG, GIF or PNG. Max size of 800K
</p>
</form>
</div>
<!-- 👉 Form -->
<VForm ref="refVForm">
<VRow>
<!-- 👉 First Name -->
<VCol md="6" cols="12">
<VTextField
v-model="accountDataLocal.first_name"
label="Name"
:rules="[requiredName]"
:error-messages="errors.name"
/>
</VCol>
<!-- 👉 Last Name -->
<VCol md="6" cols="12">
<VTextField
v-model="accountDataLocal.last_name"
placeholder="Doe"
label="Last Name"
/>
</VCol>
<!-- 👉 Email -->
<VCol cols="12" md="6">
<VTextField
readonly
v-model="accountDataLocal.email"
label="E-mail"
placeholder="johndoe@gmail.com"
type="email"
:rules="[requiredEmail, emailValidator]"
:error-messages="errors.email"
/>
</VCol>
<!-- 👉 Organization -->
<!-- 👉 Phone -->
<VCol cols="12" md="6">
<VTextField
v-model="accountDataLocal.phone_no"
label="Phone Number"
placeholder="+1 (917) 543-9876"
:rules="[requiredPhone, validUSAPhone]"
:error-messages="errors.phone"
@input="formatPhoneNumber"
max="14"
pattern="^\(\d{3}\) \d{3}-\d{4}$"
/>
</VCol>
<!-- 👉 Form Actions -->
<VCol cols="12" class="d-flex flex-wrap gap-4">
<VBtn @click.prevent="onSubmit"
>Save changes</VBtn
>
</VCol>
</VRow>
</VForm>
</VCardText>
</VCard>
</VCol>
</VRow>
<!-- Confirm Dialog -->
<!-- <ConfirmDialog
v-model:isDialogVisible="isConfirmDialogOpen"
confirmation-question="Are you sure you want to deactivate your account?"
confirm-title="Deactivated!"
confirm-msg="Your account has been deactivated successfully."
cancel-title="Cancelled"
cancel-msg="Account Deactivation Cancelled!"
/> -->
</template>

View File

@@ -0,0 +1,262 @@
<script setup>
import { confirmedValidator, passwordValidator } from "@validators";
import { computed, ref } from "vue";
import { useStore } from "vuex";
const store = useStore();
const refVForm = ref(null);
const isCurrentPasswordVisible = ref(false);
const isNewPasswordVisible = ref(false);
const isConfirmPasswordVisible = ref(false);
const currentPassword = ref("");
const newPassword = ref("");
const confirmPassword = ref("");
const passwordConfirmationTarget = computed(() => newPassword.value);
const passwordRequirements = [
"Minimum 8 characters long - the more, the better",
"At least one lowercase character",
"At least one number, symbol, or whitespace character",
];
const serverKeys = [
{
name: "Server Key 1",
key: "23eaf7f0-f4f7-495e-8b86-fad3261282ac",
createdOn: "28 Apr 2021, 18:20 GTM+4:10",
permission: "Full Access",
},
{
name: "Server Key 2",
key: "bb98e571-a2e2-4de8-90a9-2e231b5e99",
createdOn: "12 Feb 2021, 10:30 GTM+2:30",
permission: "Read Only",
},
{
name: "Server Key 3",
key: "2e915e59-3105-47f2-8838-6e46bf83b711",
createdOn: "28 Dec 2020, 12:21 GTM+4:10",
permission: "Full Access",
},
];
const recentDevicesHeaders = [
{
title: "BROWSER",
key: "browser",
},
{
title: "DEVICE",
key: "device",
},
{
title: "LOCATION",
key: "location",
},
{
title: "RECENT ACTIVITY",
key: "recentActivity",
},
];
const recentDevices = [
{
browser: "Chrome on Windows",
device: "HP Spectre 360",
location: "New York, NY",
recentActivity: "28 Apr 2022, 18:20",
deviceIcon: {
icon: "ri-macbook-line",
color: "primary",
},
},
{
browser: "Chrome on iPhone",
device: "iPhone 12x",
location: "Los Angeles, CA",
recentActivity: "20 Apr 2022, 10:20",
deviceIcon: {
icon: "ri-android-line",
color: "error",
},
},
{
browser: "Chrome on Android",
device: "Oneplus 9 Pro",
location: "San Francisco, CA",
recentActivity: "16 Apr 2022, 04:20",
deviceIcon: {
icon: "ri-smartphone-line",
color: "success",
},
},
{
browser: "Chrome on macOS",
device: "Apple iMac",
location: "New York, NY",
recentActivity: "28 Apr 2022, 18:20",
deviceIcon: {
icon: "ri-mac-line",
color: "secondary",
},
},
{
browser: "Chrome on Windows",
device: "HP Spectre 360",
location: "Los Angeles, CA",
recentActivity: "20 Apr 2022, 10:20",
deviceIcon: {
icon: "ri-macbook-line",
color: "primary",
},
},
{
browser: "Chrome on Android",
device: "Oneplus 9 Pro",
location: "San Francisco, CA",
recentActivity: "16 Apr 2022, 04:20",
deviceIcon: {
icon: "ri-android-line",
color: "success",
},
},
];
const save = async () => {
const { valid } = await refVForm.value.validate();
console.log(valid);
if (valid) {
try {
await store.dispatch("patientPasswordupdate", {
password: currentPassword.value,
new_password: newPassword.value,
confirm_password: confirmPassword.value,
});
} catch (error) {
console.error(error);
}
currentPassword.value = null;
newPassword.value = null;
confirmPassword.value = null;
}
};
const isOneTimePasswordDialogVisible = ref(false);
</script>
<template>
<VRow>
<!-- SECTION: Change Password -->
<VCol cols="12">
<VCard>
<VCardItem class="pb-6">
<VCardTitle>Change Password</VCardTitle>
</VCardItem>
<VForm ref="refVForm">
<VCardText class="pt-0">
<!-- 👉 Current Password -->
<VRow>
<VCol cols="12" md="6">
<!-- 👉 current password -->
<VTextField v-model="currentPassword" :type="isCurrentPasswordVisible
? 'text'
: 'password'
" :append-inner-icon="isCurrentPasswordVisible
? 'ri-eye-off-line'
: 'ri-eye-line'
" autocomplete="on" label="Current Password" placeholder="············"
@click:append-inner="
isCurrentPasswordVisible =
!isCurrentPasswordVisible
" />
</VCol>
</VRow>
<!-- 👉 New Password -->
<VRow>
<VCol cols="12" md="6">
<!-- 👉 new password -->
<VTextField v-model="newPassword" :type="isNewPasswordVisible
? 'text'
: 'password'
" :append-inner-icon="isNewPasswordVisible
? 'ri-eye-off-line'
: 'ri-eye-line'
" label="New Password" autocomplete="on" placeholder="············"
@click:append-inner="
isNewPasswordVisible =
!isNewPasswordVisible
" :rules="[passwordValidator]" />
</VCol>
<VCol cols="12" md="6">
<!-- 👉 confirm password -->
<VTextField v-model="confirmPassword" :type="isConfirmPasswordVisible
? 'text'
: 'password'
" :append-inner-icon="isConfirmPasswordVisible
? 'ri-eye-off-line'
: 'ri-eye-line'
" autocomplete="on" label="Confirm New Password" placeholder="············"
@click:append-inner="
isConfirmPasswordVisible =
!isConfirmPasswordVisible
" :rules="[
(value) =>
confirmedValidator(
value,
passwordConfirmationTarget
),
]" />
</VCol>
</VRow>
</VCardText>
<!-- 👉 Password Requirements -->
<VCardText>
<h6 class="text-h6 text-medium-emphasis mt-1">
Password Requirements:
</h6>
<VList>
<VListItem v-for="(item, index) in passwordRequirements" :key="index" class="px-0">
<template #prepend>
<VIcon size="8" icon="ri-circle-fill"
color="rgba(var(--v-theme-on-surface), var(--v-medium-emphasis-opacity))" />
</template>
<VListItemTitle class="text-medium-emphasis text-wrap">
{{ item }}
</VListItemTitle>
</VListItem>
</VList>
<!-- 👉 Action Buttons -->
<div class="d-flex flex-wrap gap-4">
<VBtn @click="save">Save changes</VBtn>
<!-- <VBtn
type="reset"
color="secondary"
variant="outlined"
>
Reset
</VBtn>-->
</div>
</VCardText>
</VForm>
</VCard>
</VCol>
<!-- !SECTION -->
<!-- SECTION Two-steps verification -->
<!-- !SECTION -->
<!-- SECTION Recent Devices -->
<!-- !SECTION -->
</VRow>
<!-- SECTION Enable One time password -->
<!-- !SECTION -->
</template>

View File

@@ -0,0 +1,205 @@
<script setup>
const props = defineProps({
billingAddress: {
type: Object,
required: false,
default: () => ({
firstName: "",
lastName: "",
selectedCountry: null,
addressLine1: "",
addressLine2: "",
landmark: "",
contact: "",
country: null,
state: "",
zipCode: null,
}),
},
isDialogVisible: {
type: Boolean,
required: true,
},
});
const emit = defineEmits(["update:isDialogVisible", "submit"]);
const billingAddress = ref(structuredClone(toRaw(props.billingAddress)));
const resetForm = () => {
emit("update:isDialogVisible", false);
billingAddress.value = structuredClone(toRaw(props.billingAddress));
};
const onFormSubmit = () => {
emit("update:isDialogVisible", false);
emit("submit", billingAddress.value);
};
const selectedAddress = ref("Home");
const addressTypes = [
{
title: "Home",
desc: "Delivery Time (7am - 9pm)",
value: "Home",
icon: "ri-home-smile-2-line",
},
{
title: "Office",
desc: "Delivery Time (10am - 6pm)",
value: "Office",
icon: "ri-building-line",
},
];
</script>
<template>
<VDialog
:width="$vuetify.display.smAndDown ? 'auto' : 900"
:model-value="props.isDialogVisible"
@update:model-value="(val) => $emit('update:isDialogVisible', val)"
>
<VCard v-if="props.billingAddress" class="pa-sm-11 pa-3">
<VCardText class="pt-5">
<!-- 👉 dialog close btn -->
<!-- 👉 Title -->
<div class="text-center mb-6">
<h4 class="text-h4 mb-2">
{{
props.billingAddress.firstName ? "Edit" : "Add New"
}}
Address
</h4>
<p class="text-body-1">Add Address for future billing</p>
</div>
<CustomRadios
v-model:selected-radio="selectedAddress"
:radio-content="addressTypes"
:grid-column="{ sm: '6', cols: '12' }"
class="mb-5"
>
<template #default="items">
<div class="d-flex flex-column">
<div class="d-flex mb-2 align-center gap-x-1">
<VIcon :icon="items.item.icon" size="20" />
<div
class="text-body-1 font-weight-medium text-high-emphasis"
>
{{ items.item.title }}
</div>
</div>
<p class="text-body-2 mb-0">
{{ items.item.desc }}
</p>
</div>
</template>
</CustomRadios>
<!-- 👉 Form -->
<VForm @submit.prevent="onFormSubmit">
<VRow>
<!-- 👉 First Name -->
<VCol cols="12" md="6">
<VTextField
v-model="billingAddress.firstName"
label="First Name"
placeholder="John"
/>
</VCol>
<!-- 👉 Last Name -->
<VCol cols="12" md="6">
<VTextField
v-model="billingAddress.lastName"
label="Last Name"
placeholder="Doe"
/>
</VCol>
<!-- 👉 Select country -->
<VCol cols="12">
<VSelect
v-model="billingAddress.selectedCountry"
label="Select Country"
placeholder="Select Country"
:items="['USA', 'Canada', 'NZ', 'Aus']"
/>
</VCol>
<!-- 👉 Address Line 1 -->
<VCol cols="12">
<VTextField
v-model="billingAddress.addressLine1"
label="Address Line 1"
placeholder="1, New Street"
/>
</VCol>
<!-- 👉 Address Line 2 -->
<VCol cols="12">
<VTextField
v-model="billingAddress.addressLine2"
label="Address Line 2"
placeholder="Near hospital"
/>
</VCol>
<!-- 👉 Landmark -->
<VCol cols="12">
<VTextField
v-model="billingAddress.landmark"
label="Landmark & City"
placeholder="Near hospital, New York"
/>
</VCol>
<!-- 👉 State -->
<VCol cols="12" md="6">
<VTextField
v-model="billingAddress.state"
label="State/Province"
placeholder="New York"
/>
</VCol>
<!-- 👉 Zip Code -->
<VCol cols="12" md="6">
<VTextField
v-model="billingAddress.zipCode"
label="Zip Code"
placeholder="123123"
type="number"
/>
</VCol>
<VCol cols="12">
<VSwitch
label="Make this default shipping address"
/>
</VCol>
<!-- 👉 Submit and Cancel button -->
<VCol cols="12" class="text-center">
<VBtn type="submit" class="me-3"> submit </VBtn>
<VBtn
variant="outlined"
color="secondary"
@click="resetForm"
>
Cancel
</VBtn>
</VCol>
</VRow>
</VForm>
</VCardText>
</VCard>
</VDialog>
</template>

View File

@@ -0,0 +1,138 @@
<script setup>
const props = defineProps({
confirmationQuestion: {
type: String,
required: true,
},
isDialogVisible: {
type: Boolean,
required: true,
},
confirmTitle: {
type: String,
required: true,
},
confirmMsg: {
type: String,
required: true,
},
cancelTitle: {
type: String,
required: true,
},
cancelMsg: {
type: String,
required: true,
},
});
const emit = defineEmits(["update:isDialogVisible", "confirm"]);
const unsubscribed = ref(false);
const cancelled = ref(false);
const updateModelValue = (val) => {
emit("update:isDialogVisible", val);
};
const onConfirmation = () => {
emit("confirm", true);
updateModelValue(false);
unsubscribed.value = true;
};
const onCancel = () => {
emit("confirm", false);
emit("update:isDialogVisible", false);
cancelled.value = true;
};
</script>
<template>
<!-- 👉 Confirm Dialog -->
<VDialog
max-width="500"
:model-value="props.isDialogVisible"
@update:model-value="updateModelValue"
>
<VCard class="text-center px-10 py-6">
<VCardText>
<VBtn
icon
variant="outlined"
color="warning"
class="my-4"
size="x-large"
>
<span class="text-4xl">!</span>
</VBtn>
<h6 class="text-lg font-weight-medium">
{{ props.confirmationQuestion }}
</h6>
</VCardText>
<VCardText class="d-flex align-center justify-center gap-4">
<VBtn variant="elevated" @click="onConfirmation">
Confirm
</VBtn>
<VBtn color="secondary" variant="outlined" @click="onCancel">
Cancel
</VBtn>
</VCardText>
</VCard>
</VDialog>
<!-- Unsubscribed -->
<VDialog v-model="unsubscribed" max-width="500">
<VCard>
<VCardText class="text-center px-10 py-6">
<VBtn
icon
variant="outlined"
color="success"
class="my-4"
size="x-large"
>
<span class="text-xl">
<VIcon icon="ri-check-line" />
</span>
</VBtn>
<h1 class="text-h4 mb-4">
{{ props.confirmTitle }}
</h1>
<p>{{ props.confirmMsg }}</p>
<VBtn color="success" @click="unsubscribed = false"> Ok </VBtn>
</VCardText>
</VCard>
</VDialog>
<!-- Cancelled -->
<VDialog v-model="cancelled" max-width="500">
<VCard>
<VCardText class="text-center px-10 py-6">
<VBtn
icon
variant="outlined"
color="error"
class="my-4"
size="x-large"
>
<span class="text-2xl font-weight-light">X</span>
</VBtn>
<h1 class="text-h4 mb-4">
{{ props.cancelTitle }}
</h1>
<p>{{ props.cancelMsg }}</p>
<VBtn color="success" @click="cancelled = false"> Ok </VBtn>
</VCardText>
</VCard>
</VDialog>
</template>

View File

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

View File

@@ -0,0 +1,362 @@
<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'
})
}
onMounted(async () => {
let prescriptions = props.orderData.prescription;
console.log('props.orderData', props.orderData.prescription)
itemsPrescriptions.value = prescriptions
});
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.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>
<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-card-text>
<v-divider class="mx-4"></v-divider>
<v-card-text>
<v-row dense>
<v-col cols="6">
<v-icon small color="rgb(var(--v-theme-yellow))">mdi-pill</v-icon>
<span class="ml-1">Dosage:{{ prescription.dosage }}</span>
</v-col>
<v-col cols="6">
<v-icon small color="rgb(var(--v-theme-yellow))">mdi-bottle-tonic</v-icon>
<span class="ml-1">Quantity:{{ prescription.quantity }}</span>
</v-col>
<v-col cols="6">
<v-icon small color="rgb(var(--v-theme-yellow))">mdi-calendar</v-icon>
<span class="ml-1">{{ formatDate(prescription.prescription_date) }}</span>
</v-col>
<v-col cols="6">
<v-icon small color="rgb(var(--v-theme-yellow))">mdi-clock-outline</v-icon>
<span class="ml-1">{{ formatTime(prescription.prescription_date) }}</span>
</v-col>
</v-row>
</v-card-text>
<v-card-actions>
<v-btn color="rgb(var(--v-theme-yellow-theme-button))" text>
More Details
</v-btn>
<v-spacer></v-spacer>
<v-btn icon @click="prescription.show = !prescription.show">
<v-icon color="rgb(var(--v-theme-yellow-theme-button))">{{ prescription.show ?
'mdi-chevron-up' :
'mdi-chevron-down'
}}</v-icon>
</v-btn>
</v-card-actions>
<v-expand-transition>
<div v-show="prescription.show">
<v-divider></v-divider>
<v-card-text>
<v-row dense>
<v-col cols="12">
<strong>Provider:</strong> {{ props.orderData.appointment_details.provider_name
}}
</v-col>
<v-col cols="12">
<strong>Brand:</strong> {{ prescription.brand }}
</v-col>
<v-col cols="12">
<strong>From:</strong> {{ prescription.from }}
</v-col>
<v-col cols="12">
<strong>Direction One:</strong> {{ prescription.direction_one }}
</v-col>
<v-col cols="12">
<strong>Direction Two:</strong> {{ prescription.direction_two }}
</v-col>
<v-col cols="12">
<strong>Refill Quantity:</strong> {{ prescription.refill_quantity }}
</v-col>
<v-col cols="12">
<strong>Direction Quantity:</strong> {{ prescription.direction_quantity }}
</v-col>
<v-col cols="12" v-if="prescription.comments">
<strong>Comments:</strong> {{ prescription.comments }}
</v-col>
</v-row>
</v-card-text>
</div>
</v-expand-transition>
</v-card>
</v-col>
</v-row>
<VExpansionPanels variant="accordion" style="display: none;">
<VExpansionPanel v-for="(item, index) in itemsPrescriptions" :key="index">
<div>
<VExpansionPanelTitle collapse-icon="mdi-chevron-down" expand-icon="mdi-chevron-right"
style="margin-left: 0px !important;">
<p class=""><b> {{ item.name }}</b>
<br />
<div class=" pt-2"> {{ doctorName }}</div>
<div class=" pt-2">{{ item.date }}</div>
</p>
<v-row>
</v-row>
<span class="v-expansion-panel-title__icon badge text-warning"
v-if="item.status == null">Pending</span>
<span class="v-expansion-panel-title__icon badge" v-else>
<v-chip :color="getStatusColor(item.status)" label size="small" variant="text">
{{ item.status }}
</v-chip></span>
</VExpansionPanelTitle>
<VExpansionPanelText class="pt-0">
<v-row class='mt-1'>
<v-col cols="12" md="4" sm="6">
<p class='heading'><b>Brand:</b></p>
</v-col>
<v-col cols="12" md="4" sm="6">
<p>{{ item.brand }}</p>
</v-col>
</v-row>
<v-row class='mt-1'>
<v-col cols="12" md="4" sm="6">
<p class='heading'><b>From:</b></p>
</v-col>
<v-col cols="12" md="4" sm="6">
<p>{{ item.from }}</p>
</v-col>
</v-row>
<v-row class='mt-1'>
<v-col cols="12" md="4" sm="6">
<p class='heading'><b>Dosage:</b></p>
</v-col>
<v-col cols="12" md="4" sm="6">
<p>{{ item.dosage }}</p>
</v-col>
</v-row>
<v-row class='mt-1'>
<v-col cols="12" md="4" sm="6">
<p class='heading'><b>Quantity:</b></p>
</v-col>
<v-col cols="12" md="4" sm="6">
<p>{{ item.quantity }}</p>
</v-col>
</v-row>
<v-row class='mt-1'>
<v-col cols="12" md="4" sm="6">
<p class='heading'><b>Direction Quantity:</b></p>
</v-col>
<v-col cols="12" md="8" sm="6">
<p>{{ item.direction_quantity }}</p>
</v-col>
</v-row>
<v-row class='mt-1'>
<v-col cols="12" md="4" sm="6">
<p class='heading'><b>Direction One:</b></p>
</v-col>
<v-col cols="12" md="8" sm="6">
<p>{{ item.direction_one }} </p>
</v-col>
</v-row>
<v-row class='mt-1'>
<v-col cols="12" md="4" sm="6">
<p class='heading'><b>Direction Two:</b></p>
</v-col>
<v-col cols="12" md="8" sm="6">
<p>{{ item.direction_two }} </p>
</v-col>
</v-row>
<v-row class='mt-1'>
<v-col cols="12" md="4" sm="6">
<p class='heading'><b>Refill Quantity:</b></p>
</v-col>
<v-col cols="12" md="8" sm="6">
<p>{{ item.refill_quantity }}</p>
</v-col>
</v-row>
<v-row class='mt-1'>
<v-col cols="12" md="4" sm="6">
<p class='heading'><b>Status:</b></p>
</v-col>
<v-col cols="12" md="8" sm="6">
<p v-if="item.status == null" class="text-warning">Pending</p>
<p v-else>{{ item.status }}</p>
</v-col>
</v-row>
<v-row class='mt-1'>
<v-col cols="12" md="4" sm="6">
<p class='heading'><b>Comments:</b></p>
</v-col>
<v-col cols="12" md="8" sm="8">
<p>{{ item.comments }} </p>
</v-col>
</v-row>
</VExpansionPanelText>
</div>
</VExpansionPanel>
<br />
</VExpansionPanels>
</template>
<template v-else="prescriptionLoaded">
<VCard>
<VCard>
<VAlert border="start" color="rgb(var(--v-theme-yellow-theme-button))" variant="tonal">
<div class="text-center">No data found</div>
</VAlert>
</VCard>
</VCard>
</template>
</template>
<style lang="scss">
button.v-expansion-panel-title {
background-color: rgb(var(--v-theme-yellow)) !important;
color: #fff;
}
button.v-expansion-panel-title.bg-secondary {
background-color: rgb(var(--v-theme-yellow)) !important;
color: #fff;
}
button.v-expansion-panel-title {
background-color: rgb(var(--v-theme-yellow)) !important;
color: #fff;
}
span.v-expansion-panel-title__icon {
margin-left: 0px !important;
}
span.v-expansion-panel-title__icon {
color: #fff
}
.v-expansion-panel {
background-color: #fff;
border-radius: 16px;
box-shadow: 0px 4px 12px rgba(0, 0, 0, 0.1);
margin-bottom: 10px;
overflow: hidden;
transition: box-shadow 0.3s ease;
}
</style>

View File

@@ -0,0 +1,193 @@
<script setup>
const props = defineProps({
userData: {
type: Object,
required: false,
default: () => ({
avatar: "",
company: "",
contact: "",
country: null,
currentPlan: "",
email: "",
fullName: "",
id: 0,
role: "",
status: null,
username: "",
language: [],
projectDone: 0,
taskDone: 0,
taxId: "",
}),
},
isDialogVisible: {
type: Boolean,
required: true,
},
});
const emit = defineEmits(["submit", "update:isDialogVisible"]);
const userData = ref(structuredClone(toRaw(props.userData)));
watch(props, () => {
userData.value = structuredClone(toRaw(props.userData));
});
const onFormSubmit = () => {
emit("update:isDialogVisible", false);
emit("submit", userData.value);
};
const onFormReset = () => {
userData.value = structuredClone(toRaw(props.userData));
emit("update:isDialogVisible", false);
};
const dialogVisibleUpdate = (val) => {
emit("update:isDialogVisible", val);
};
</script>
<template>
<VDialog
:width="$vuetify.display.smAndDown ? 'auto' : 900"
:model-value="props.isDialogVisible"
@update:model-value="dialogVisibleUpdate"
>
<VCard class="pa-sm-11 pa-3">
<!-- 👉 dialog close btn -->
<VCardText class="pt-5">
<div class="text-center pb-6">
<h4 class="text-h4 mb-2">Edit User Information</h4>
<div class="text-body-1">
Updating user details will receive a privacy audit.
</div>
</div>
<!-- 👉 Form -->
<VForm class="mt-4" @submit.prevent="onFormSubmit">
<VRow>
<!-- 👉 First Name -->
<VCol cols="12" md="6">
<VTextField
v-model="userData.fullName.split(' ')[0]"
label="First Name"
placeholder="John"
/>
</VCol>
<!-- 👉 Last Name -->
<VCol cols="12" md="6">
<VTextField
v-model="userData.fullName.split(' ')[1]"
label="Last Name"
placeholder="doe"
/>
</VCol>
<!-- 👉 User Name -->
<VCol cols="12">
<VTextField
v-model="userData.username"
label="Username"
placeholder="John Doe"
/>
</VCol>
<!-- 👉 Billing Email -->
<VCol cols="12" md="6">
<VTextField
v-model="userData.email"
label="Billing Email"
placeholder="johndoe@email.com"
/>
</VCol>
<!-- 👉 Status -->
<VCol cols="12" md="6">
<VSelect
v-model="userData.status"
:items="['Active', 'Inactive', 'Pending']"
label="Status"
placeholder="Status"
/>
</VCol>
<!-- 👉 Tax Id -->
<VCol cols="12" md="6">
<VTextField
v-model="userData.taxId"
label="Tax Id"
placeholder="Tax-3456789"
/>
</VCol>
<!-- 👉 Contact -->
<VCol cols="12" md="6">
<VTextField
v-model="userData.contact"
label="Contact"
placeholder="+1 9876543210"
/>
</VCol>
<!-- 👉 Language -->
<VCol cols="12" md="6">
<VSelect
v-model="userData.language"
:items="['English', 'Spanish', 'French']"
label="Language"
placeholder="English"
chips
closable-chips
multiple
/>
</VCol>
<!-- 👉 Country -->
<VCol cols="12" md="6">
<VSelect
v-model="userData.country"
:items="[
'United States',
'United Kingdom',
'France',
]"
label="Country"
placeholder="United States"
/>
</VCol>
<!-- 👉 Switch -->
<VCol cols="12">
<VSwitch
density="compact"
label="Use as a billing address?"
/>
</VCol>
<!-- 👉 Submit and Cancel -->
<VCol
cols="12"
class="d-flex flex-wrap justify-center gap-4"
>
<VBtn type="submit"> Submit </VBtn>
<VBtn
color="secondary"
variant="outlined"
@click="onFormReset"
>
Cancel
</VBtn>
</VCol>
</VRow>
</VForm>
</VCardText>
</VCard>
</VDialog>
</template>

View File

@@ -0,0 +1,170 @@
<script setup>
import Notes from '@/pages/patient/notes.vue';
import Prescription from '@/pages/patient/prescription.vue';
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.appiontment_id;
const currentTab = ref(0)
const appiontmentID = ref();
const dcotorName = ref();
const starttime = ref();
const endtime = ref();
const duration = ref();
const patientName = ref();
const patientEmail = ref();
const patientPhone = ref();
const patientAddress = ref();
const patientDob = ref();
const patientData = ref(null);
const his = computed(async () => {
// store.dispatch('updateIsLoading', true)
await store.dispatch('getPatientInfo')
// notes.value = store.getters.getPatientNotes;
patientData.value = store.getters.getPatient;
console.log("appointmentData1", patientData.value);
// appiontmentID.value = appointmentId;
patientName.value = patientData.value.first_name + ' ' + patientData.value.last_name;
patientEmail.value = patientData.value.email;
patientDob.value = changeFormat(patientData.value.dob);
patientPhone.value = patientData.value.phone_no;
patientAddress.value = patientData.value.address + ' ' +
patientData.value.city + ' ' +
patientData.value.state + ' ' +
patientData.value.country;
// localStorage.setItem('meetingPatientAppiontmentId', appiontmentID.value)
// console.log("notesData", notesData.value[0].appointment.id);
// store.dispatch('updateIsLoading', false)
});
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;
}
</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 class="">
<VCardTitle class="my-1"><b>Appiontments Detail</b></VCardTitle>
<VRow>
<VCol cols="12" md="8" v-if="his">
<Prescription></Prescription>
<Notes></Notes>
</VCol>
<VCol cols="12" md="4">
<VCard class="mb-4">
<VCardText>
<h5 class="mt-2 mb-2">ABOUT</h5>
<VList class="card-list text-medium-emphasis mb-2">
<VListItem>
<template #prepend>
<VIcon icon="mdi-account" size="20" class="me-2" />
</template>
<VListItemTitle>
<!-- <span class="font-weight-medium me-1">Full Name:</span> -->
<span> {{ patientName }}</span>
</VListItemTitle>
</VListItem>
<VListItem>
<template #prepend>
<VIcon icon="mdi-email" size="20" class="me-2" />
</template>
<VListItemTitle>
<!-- <span class="font-weight-medium me-1">Email:</span> -->
<span>{{ patientEmail }}</span>
</VListItemTitle>
</VListItem>
<VListItem>
<template #prepend>
<VIcon icon="tabler-calendar" size="20" class="me-2" />
</template>
<VListItemTitle>
<!-- <span class="font-weight-medium me-1">DOB:</span> -->
<span>
{{ patientDob }}</span>
</VListItemTitle>
</VListItem>
<VListItem>
<template #prepend>
<VIcon icon="mdi-phone" size="20" class="me-2" />
</template>
<VListItemTitle>
<!-- <span class="font-weight-medium me-1">Phone:</span> -->
<span>{{
patientPhone }}</span>
</VListItemTitle>
</VListItem>
<VListItem>
<template #prepend>
<VIcon icon="mdi-address-marker" size="20" class="me-2" />
</template>
<VListItemTitle>
<!-- <span class="font-weight-medium me-1">Address:</span> -->
<span>{{ patientAddress }}</span>
</VListItemTitle>
</VListItem>
</VList>
<!-- <VDivider></VDivider> -->
<!-- <h5 class="mt-2 mb-2"></h5> -->
<VList class="card-list text-medium-emphasis">
</VList>
</VCardText>
</VCard>
</VCol>
</VRow>
<!-- <VTabs v-model="currentTab" direction="vertical">
<VTab>
<VIcon start icon="tabler-edit" />
Notes
</VTab>
<VTab>
<VIcon start icon="tabler-lock" />
Prescriptions
</VTab>
</VTabs> -->
<!-- </div> -->
<!-- <VCardText> -->
<!-- <VWindow v-model="currentTab" class="ms-3"> -->
<!-- <VWindowItem> -->
<!-- <Notes></Notes> -->
<!-- </VWindowItem> -->
<!-- <VWindowItem> -->
<!-- <Prescription></Prescription> -->
<!-- </VWindowItem> -->
<!-- </VWindow> -->
<!-- </VCardText> -->
</div>
</template>

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,278 @@
<script setup>
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 props = defineProps({
orderID: {
type: Number,
required: true,
},
})
const getFieldRules = (fieldName, errorMessage) => {
if (fieldName) {
return [
v => !!v || `${errorMessage}`,
// Add more validation rules as needed
];
}
};
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 testKits = computed(async () => {
//await store.dispatch('getLabKitProductList', {})
// console.log(store.getters.getLabOrderProductList)
//state.testKits = store.getters.getLabOrderProductList
});
onMounted(async () => {
await store.dispatch('getOrderLabKitPatient', { cart_id: route.params.id })
state.labKitList = store.getters.getOrderLabKitPatient
console.log('state.testKits', state.labKitList)
isLoading.value = false
});
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";
}
};
</script>
<template>
<!-- 👉 Order Details -->
<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>
<VCard title="Lab Kits" style="margin-bottom: 10px;" 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; font-size: 0.83em;">
{{ 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; font-size: 0.83em;">
{{ 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>
</template>
<style scoped>
.appointment-details {
display: flex;
flex-direction: column;
}
.detail-item {
display: flex;
margin-bottom: 10px;
}
.detail-label {
font-weight: bold;
min-width: 120px;
}
.detail-value {
flex: 1;
}
::-webkit-scrollbar {
width: 10px;
/* Width of the scrollbar */
}
/* Track */
::-webkit-scrollbar-track {
background: #f1f1f1;
/* Color of the track */
}
/* Handle */
::-webkit-scrollbar-thumb {
background: #888;
/* Color of the handle */
border-radius: 5px;
/* Roundness of the handle */
}
/* Handle on hover */
::-webkit-scrollbar-thumb:hover {
background: #555;
/* Color of the handle on hover */
}
</style>

View File

@@ -0,0 +1,87 @@
<script setup>
import { computed, onMounted, ref } from "vue";
import Notes from "@/pages/patient/OrderDetailNotes.vue";
import Prescription from "@/pages/patient/OrderDetailPrecrption.vue";
import OrderDetail from "@/pages/patient/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("orderDetailPatient", {
id: route.params.id,
});
orderData.value = store.getters.getPatientOrderDetail;
console.log(orderData.value);
});
</script>
<template>
<VTabs v-model="userTab" grow>
<VTab v-for="tab in tabs" :key="tab.icon">
<VIcon start :icon="tab.icon" />
<span>{{ tab.title }}</span>
</VTab>
</VTabs>
<VWindow
v-model="userTab"
class="mt-6 disable-tab-transition"
:touch="false"
>
<VWindowItem>
<OrderDetail />
</VWindowItem>
<VWindowItem>
<Notes :order-data="orderData" />
</VWindowItem>
<VWindowItem>
<Prescription :order-data="orderData" />
</VWindowItem>
</VWindow>
</template>
<style scoped>
.text-primary {
color: rgb(var(--v-theme-yellow)) !important;
}
.text-primary span {
color: rgb(var(--v-theme-yellow-theme-button)) !important;
}
.text-primary svg {
color: rgb(var(--v-theme-yellow-theme-button)) !important;
}
</style>

View File

@@ -0,0 +1,104 @@
<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.appiontment_id;
const notes = ref([]);
const historyNotes = computed(async () => {
// console.log('...........', patientId, appointmentId);
await store.dispatch('getPatientPrescriptionNotes', {
appointment_id: appointmentId,
})
// notes.value = store.getters.getPatientNotes;
let notesData = store.getters.getPatientNotes;
console.log("notesData", notesData);
for (let data of notesData) {
if (data.note_type == 'Notes') {
let dataObject = {}
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("getNotes", 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, '-');
};
</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">
<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="primary" 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>
<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>
</VTimeline>
</VCardText>
</VCard>
<!-- <VList class="pb-0" lines="two" v-if="historyNotes">
<template v-if="notes.length > 0" v-for="(p_note, index) of notes" :key="index">
<VListItem class="pb-0" border>
<VListItemTitle>
<span class="pb-0">{{ p_note.note }}</span>
<p class="text-start fs-5 mb-0 pb-0 text-grey">
<small> {{ p_note.doctor }}</small>
</p>
<p class="text-end fs-5 mb-0 pb-0 text-grey">
<small> {{ p_note.date }}</small>
</p>
</VListItemTitle>
</VListItem>
<VDivider v-if="index !== notes.length - 1" />
</template>
<template v-else>
<VCard>
<VAlert border="start" color="rgb(var(--v-theme-yellow))" variant="tonal">
<div class="text-center">No data found</div>
</VAlert>
</VCard>
</template>
</VList> -->
</template>

View File

@@ -0,0 +1,926 @@
<script setup>
import LabKits from "@/pages/patient/lab-kits.vue";
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 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 isMeeting = 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 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);
if (orderData.value.appointment_details) {
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
)
);
isMeeting.value = convertAndCheckTime(
convertAndGetTimeDifference(
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");
});
const convertAndCheckTime = (str) => {
return str.includes("ago");
};
const convertAndGetTimeDifference = (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);
// Get the current date and time
const now = new Date();
// Calculate the time difference in milliseconds
const timeDifference = dateObj - now;
// Convert the time difference to a human-readable format
const diffInSeconds = Math.abs(timeDifference) / 1000;
const diffInMinutes = Math.floor(diffInSeconds / 60) % 60;
const diffInHours = Math.floor(diffInSeconds / 3600);
let timeLeftOrAgo;
if (timeDifference > 0) {
if (diffInHours > 0) {
timeLeftOrAgo = `${diffInHours} hour${
diffInHours > 1 ? "s" : ""
} ${diffInMinutes} minute${diffInMinutes > 1 ? "s" : ""} left`;
} else {
timeLeftOrAgo = `${diffInMinutes} minute${
diffInMinutes > 1 ? "s" : ""
} left`;
}
} else {
if (diffInHours > 0) {
timeLeftOrAgo = `${diffInHours} hour${
diffInHours > 1 ? "s" : ""
} ${diffInMinutes} minute${diffInMinutes > 1 ? "s" : ""} ago`;
} else {
timeLeftOrAgo = `${diffInMinutes} minute${
diffInMinutes > 1 ? "s" : ""
} ago`;
}
}
return timeLeftOrAgo;
};
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 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 align-left 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
>${{
parseFloat(item.price).toLocaleString(
"en-US",
{
minimumFractionDigits: 2,
maximumFractionDigits: 2,
}
)
}}</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>
<!-- <LabKits :order-id="route.params.id" /> -->
<!-- 👉 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="yellow" size="x-small">
<div
class="d-flex justify-space-between align-center mb-3"
>
<span class="app-timeline-title">
Order was placed (Order ID: #{{
filteredOrders.order_details.id
}})</span
>
<span class="app-timeline-meta">{{
formatDateActviy(
filteredOrders.order_details
.created_at
)
}}</span>
</div>
<p class="app-timeline-text mb-0">
{{
filteredOrders.order_details
.short_description
}}
</p>
</VTimelineItem>
<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.short_description }}
</p>
</VTimelineItem>
</VTimeline>
</VCardText>
</VCard>
</VCol>
<VCol cols="12" md="4">
<VCard class="mb-6" v-if="filteredOrders.appointment_details">
<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>
<span v-if="isMeeting">
<RouterLink to="/queue" target="_blank">
<VBtn
style="border-radius: 20px; color: #fff"
block
class="mt-3"
color="rgb(var(--v-theme-yellow-theme-button))"
>
Go to Meeting
</VBtn>
</RouterLink>
</span>
<span v-else>
<VBtn
block
style="border-radius: 20px; color: #fff"
class="mt-3"
color="rgb(var(--v-theme-yellow-theme-button))"
disabled
>Go to Meeting
</VBtn>
</span>
</div>
</VCardText>
</VCard>
<!-- 👉 Customer Details -->
<VCard class="mb-6" v-if="filteredOrders.appointment_details">
<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="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.appointment_details
.provider_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.appointment_details
.provider_email
}}</span
>
<span
>Mobile:
{{
filteredOrders.appointment_details
.provider_phone
}}</span
>
</div>
</VCardText>
</VCard>
<!-- 👉 Shipping Address -->
<VCard class="mb-6">
<VCardText>
<div
class="d-flex align-center justify-space-between gap-1 mb-6"
>
<div
class="text-body-1 text-high-emphasis font-weight-medium"
>
<v-icon
class="mr-2"
color="rgb(var(--v-theme-yellow))"
>mdi-truck-delivery</v-icon
>
Shipping Address
</div>
<!-- <span
class="text-base text-primary font-weight-medium cursor-pointer"
@click="
isEditAddressDialogVisible =
!isEditAddressDialogVisible
"
>Edit</span
> -->
</div>
<div>
{{ filteredOrders.order_details.shipping_address1 }}
<br />
{{ filteredOrders.order_details.shipping_city }}
<br />
{{ filteredOrders.order_details.shipping_state }},
{{ filteredOrders.order_details.shipping_zipcode }}
<br />
{{ filteredOrders.order_details.shipping_country }}
</div>
</VCardText>
</VCard>
<!-- 👉 Billing Address -->
<VCard style="display: none">
<VCardText>
<div
class="d-flex align-center justify-space-between gap-1 mb-3"
>
<div
class="text-body-1 text-high-emphasis font-weight-medium"
>
Billing Address
</div>
<!-- <span
class="text-base text-primary font-weight-medium cursor-pointer"
@click="
isEditAddressDialogVisible =
!isEditAddressDialogVisible
"
>Edit</span
> -->
</div>
<div>
{{ filteredOrders.order_details.billing_address1 }}
<br />
{{ filteredOrders.order_details.billing_city }}
<br />
{{ filteredOrders.order_details.billing_state }},
{{ filteredOrders.order_details.billing_zipcode }}
<br />
{{ filteredOrders.order_details.billing_country }}
</div>
<!-- <div class="mt-6">
<h6 class="text-h6 mb-1">Mastercard</h6>
<div class="text-base">Card Number: ******4291</div>
</div> -->
</VCardText>
</VCard>
</VCol>
</VRow>
<!-- <ConfirmDialog
v-model:isDialogVisible="isConfirmDialogVisible"
confirmation-question="Are you sure to cancel your Order?"
cancel-msg="Order cancelled!!"
cancel-title="Cancelled"
confirm-msg="Your order cancelled successfully."
confirm-title="Cancelled!"
/> -->
<!-- <UserInfoEditDialog
v-model:isDialogVisible="isUserInfoEditDialogVisible"
/>
<AddEditAddressDialog
v-model:isDialogVisible="isEditAddressDialogVisible"
/> -->
</div>
</template>
<style scoped>
.appointment-details {
display: flex;
flex-direction: column;
}
.detail-item {
display: flex;
margin-bottom: 10px;
}
.detail-label {
font-weight: bold;
min-width: 120px;
}
.detail-value {
flex: 1;
}
::-webkit-scrollbar {
width: 10px;
/* Width of the scrollbar */
}
/* Track */
::-webkit-scrollbar-track {
background: #f1f1f1;
/* Color of the track */
}
/* Handle */
::-webkit-scrollbar-thumb {
background: #888;
/* Color of the handle */
border-radius: 5px;
/* Roundness of the handle */
}
/* Handle on hover */
::-webkit-scrollbar-thumb:hover {
background: #555;
/* Color of the handle on hover */
}
</style>

View File

@@ -0,0 +1,557 @@
<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("orderPtaientList");
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;
// });
// }
filtered.sort((a, b) => {
return b.id - a.id;
});
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: "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('orderPtaientListFilter', {
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("orderPtaientList");
orders.value = store.getters.getPatientOrderList;
orders.value.sort((a, b) => {
return b.id - a.id;
});
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="rgb(var(--v-theme-yellow))" size="56"
class="text-grey-800 text-h6 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-theme-button)) !important;
}
.v-date-picker-month__day .v-btn {
--v-btn-height: 23px !important;
--v-btn-size: 0.85rem;
}
.custom-date-picker {
font-size: 0.85em;
}
.custom-date-picker :deep(.v-date-picker-month) {
width: 100%;
}
.custom-date-picker :deep(.v-date-picker-month__day) {
width: 30px;
height: 30px;
}
.custom-date-picker :deep(.v-date-picker-month) {
min-width: 300px;
}
.custom-date-picker :deep(.v-date-picker-month__day .v-btn) {
--v-btn-height: 20px !important;
}
.order-card {
transition: all 0.3s;
}
.order-card:hover {
transform: translateY(-5px);
box-shadow: 0 12px 20px -10px rgba(0, 0, 0, 0.1), 0 4px 20px 0px rgba(0, 0, 0, 0.1), 0 7px 8px -5px rgba(0, 0, 0, 0.1) !important;
}
.order-header {
background-color: #f5f5f5;
}
.order-items-container {
height: 155px;
/* Set a fixed height */
overflow-y: auto;
/* Enable vertical scrolling */
}
.order-items-list {
padding-right: 16px;
/* Add some padding for the scrollbar */
}
.order-card .v-list-item {
border: 1px solid rgba(0, 0, 0, 0.12);
}
/* Custom scrollbar styles */
.order-items-container::-webkit-scrollbar {
width: 8px;
}
.order-items-container::-webkit-scrollbar-track {
background: #f1f1f1;
border-radius: 4px;
}
.order-items-container::-webkit-scrollbar-thumb {
background: #888;
border-radius: 4px;
}
.order-items-container::-webkit-scrollbar-thumb:hover {
background: #555;
}
/* Mobile styles */
@media (max-width: 600px) {
.order-header {
flex-direction: column;
}
.order-header .v-avatar {
margin-bottom: 16px;
}
.order-header .v-col {
text-align: center;
}
.order-header .ml-auto {
margin: 16px auto 0 auto;
}
.order-items-container {
height: auto;
}
}
</style>

View File

@@ -0,0 +1,105 @@
<script setup>
import Notes from '@/pages/patient/notes.vue';
import Prescription from '@/pages/patient/prescription.vue';
import store from '@/store';
import moment from 'moment';
import { useRoute, useRouter } from 'vue-router';
const router = useRouter()
const route = useRoute()
// const patientId = route.params.patient_id;
const appointmentId = route.params.appiontment_id;
const currentTab = ref(0)
const appiontmentID = ref();
const dcotorName = ref();
const starttime = ref();
const endtime = ref();
const duration = ref();
const appointmentData = ref(null);
const his = computed(async () => {
store.dispatch('updateIsLoading', true)
await store.dispatch('getAppointmentByIdPatient', {
appointment_id: appointmentId,
})
// notes.value = store.getters.getPatientNotes;
appointmentData.value = store.getters.getSinglePatientAppointment;
// console.log("appointmentData", appointmentData.value);
appiontmentID.value = appointmentId;
dcotorName.value = appointmentData.value.telemedPro.name;
starttime.value = appointmentData.value.appointment.start_time;
endtime.value = appointmentData.value.appointment.end_time;
duration.value = totalCallDuration(starttime.value, endtime.value);
localStorage.setItem('meetingPatientAppiontmentId', appiontmentID.value)
// console.log("notesData", notesData.value[0].appointment.id);
store.dispatch('updateIsLoading', false)
});
const 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
}
</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 cols="6">
<VCardText>
<h3 v-if="his"> #{{ appiontmentID }} By {{ dcotorName }} </h3>
<span> Meeting duration: <b> {{ duration }}</b></span>
</VCardText>
<div class="d-flex">
<div>
<VTabs v-model="currentTab" direction="vertical">
<VTab>
<VIcon start icon="tabler-edit" />
Notes
</VTab>
<VTab>
<VIcon start icon="tabler-lock" />
Prescriptions
</VTab>
</VTabs>
</div>
<VCardText>
<VWindow v-model="currentTab" class="ms-3">
<VWindowItem>
<Notes></Notes>
</VWindowItem>
<VWindowItem>
<Prescription></Prescription>
</VWindowItem>
</VWindow>
</VCardText>
</div>
</VCard>
</template>

View File

@@ -0,0 +1,247 @@
<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.appiontment_id;
const doctorName = ref('');
const prescription = computed(async () => {
store.dispatch('updateIsLoading', true)
await store.dispatch('getPatientAppointment')
await getprescriptionList()
doctorName.value = store.getters.getBookedAppointment.agent_name;
store.dispatch('updateIsLoading', false)
});
const getprescriptionList = async () => {
await store.dispatch('getPatientPrescriptionsByID', {
appointment_id: appointmentId,
})
let prescriptions = store.getters.getPrescriptionList
// 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 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="prescription">
<VExpansionPanels variant="accordion">
<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">#{{ appointmentId }} By {{ 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>
<VCard>
<VCard>
<VAlert border="start" color="rgb(var(--v-theme-yellow))" variant="tonal">
<div class="text-center">No data found</div>
</VAlert>
</VCard>
</VCard>
</template>
</template>
<style lang="scss">
button.v-expansion-panel-title {
background-color: rgb(var(--v-theme-yellow)) !important;
color: #fff;
}
button.v-expansion-panel-title.bg-secondary {
background-color: rgb(var(--v-theme-yellow)) !important;
color: #fff;
}
button.v-expansion-panel-title {
background-color: rgb(var(--v-theme-yellow)) !important;
color: #fff;
}
span.v-expansion-panel-title__icon {
margin-left: 0px !important;
}
span.v-expansion-panel-title__icon {
color: #fff
}
.v-expansion-panel {
background-color: #fff;
border-radius: 16px;
box-shadow: 0px 4px 12px rgba(0, 0, 0, 0.1);
margin-bottom: 10px;
overflow: hidden;
transition: box-shadow 0.3s ease;
}
</style>

View File

@@ -0,0 +1,208 @@
<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 getPrescriptionHistory = ref([]);
const doctorAppiontments = ref([]);
const selectedFilter = ref(null);
const currentMonth = ref(null);
onMounted(async () => {
await store.dispatch("getPrescriptionHistory");
getPrescriptionHistory.value = store.getters.getPrescriptionHistory.history;
console.log("getPrescriptionHistory", getPrescriptionHistory.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 handleDateInput = async () => {
getHistory.value = [];
// await store.dispatch('getHistoryFilter', {
// filter: selectedFilter.value,
// })
// getHistory.value = store.getters.getHistoryFilter.patients;
// console.log("getHistoryFilter", getHistory.value);
// store.dispatch('updateIsLoading', false)
};
const search = ref("");
const headers = [
// { align: "start", key: "id", title: "Appiontment Id" },
// { key: "patient_name", title: "Patient" },
{ key: "appointment_date", sortable: false, title: "Date" },
{ key: "start_time", title: "Start Time" },
{ key: "end_time", title: "End Time" },
{ key: "duration", title: "Duration" },
{ key: "action", title: "Action" },
];
const formattedHistory = computed(() => {
return getPrescriptionHistory.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) {
// 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("/patient/prescription-history/" + item.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="Prescriptions">
<v-card flat>
<v-card-title class="d-flex align-center pe-2">
<v-select
label="April(Month to date)"
v-model="selectedFilter"
:items="filter"
item-title="value"
item-value="key"
@update:modelValue="handleDateInput"
></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 v-slot:item.action="{ item }">
<VBtn
class="text-capitalize text-white"
@click="historyDetail(item)"
>Detail
</VBtn>
</template>
</v-data-table>
</v-card>
</VCard>
</VCol>
</VRow>
</template>

View File

@@ -0,0 +1,297 @@
<script setup>
import moment from 'moment-timezone';
import { ref } from 'vue';
import { useRoute, useRouter } from 'vue-router';
import { useStore } from 'vuex';
const store = useStore()
const router = useRouter()
const route = useRoute()
const isMeetingEnable = ref(false);
const isMeetingEnd = ref(true);
const meetingInFuture = ref(true);
const scheduleTime = ref();
const scheduleDate = ref();
const timeDifference = ref();
const callEnd = ref('');
const timeZone = ref();
const scheduleData = ref([]);
const expanded = ref([]);
const panel = ref(0)
const appId = ref();
const dessertHeaders = ref(
[
// { title: '', key: 'data-table-expand' },
{
title: 'Order ID',
align: 'start',
sortable: false,
key: 'name',
},
{ title: 'Date', key: 'calories' },
{ title: 'Product', key: 'fat' },
]
);
const desserts = ref([
{
name: '24',
calories: 'July 04, 2024 02:00 AM',
fat: 2
},
]);
onMounted(async () => {
await fetchApiData();
})
const fetchApiData = async () => {
store.dispatch('updateIsLoading', true)
await store.dispatch('getPatientAppointment')
appId.value = store.getters.getBookedAppointment.appiontmentId;
let appointmentDate = convertUtcDateTimeToLocal(store.getters.getBookedAppointment.appointment_date, store.getters.getBookedAppointment.appointment_time, 'date')
let appointmentTime = convertUtcDateTimeToLocal(store.getters.getBookedAppointment.appointment_date, store.getters.getBookedAppointment.appointment_time, 'time')
// timeDifference.value = relativeTimeFromDate(appointmentDate, appointmentTime)
scheduleDate.value = moment(appointmentDate, "YYYY-MM-DD").format("MMMM DD, YYYY")
scheduleTime.value = moment(appointmentTime, "HH:mm:ss").format("hh:mm A");
timeZone.value = store.getters.getBookedAppointment.timezone;
console.log("sch", scheduleDate.value, scheduleTime.value);
// scheduleDate.value = formatDateToISO(store.getters.getBookedAppointment.appointment_date);
// scheduleTime.value = store.getters.getBookedAppointment.appointment_time;
callEnd.value = store.getters.getBookedAppointment.end_time;
meetingInFuture.value = isDateTimeInFuture(scheduleDate.value, scheduleTime.value);
iscallEnd();
store.dispatch('updateIsLoading', false)
}
const iscallEnd = async () => {
if (callEnd && !meetingInFuture.value) {
isMeetingEnable.value = false;
console.log('Bth Conditin');
}
else if (callEnd) { // Call has been end
isMeetingEnable.value = false;
console.log('callEnd');
} else if (!meetingInFuture.value) { // Patient can Join Meeting
isMeetingEnable.value = true;
console.log('timeDiff');
} else { // Call has been end
console.log('else');
isMeetingEnable.value = false;
}
await nextTick();
// isAgentCall();
};
const isDateTimeInFuture = (dateString, timeString) => {
// Combine the date and time strings into a datetime string
const dateTimeString = dateString + " " + timeString;
// Convert the datetime string into a Date object
const eventDateTime = new Date(dateTimeString);
// Get the current date and time
const now = new Date();
// Compare the eventDateTime with the current date and time
return eventDateTime > now;
};
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'.");
}
};
</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>
<VCol cols="12" class="px-0">
<VCardTitle class="my-1">Upcoming Appiontments</VCardTitle>
<VExpansionPanels multiple v-model="panel">
<VExpansionPanel class="p-4">
<VExpansionPanelTitle class="pb-5">
<span>
<p><b>#Order:</b> {{ appId }} </p>
<div class="pl-5">
<p><b> Appiontment Date:</b> July 04, 2024 02:00 AM </p>
</div>
<div class="pl-5">
<p><b> Timezone:</b> Asia Karachi</p>
</div>
<div class="pl-5">
<b>Product:</b> 2
</div>
</span>
</VExpansionPanelTitle>
<VExpansionPanelText>
<VRow>
<VDivider />
<VCol cols="12" md="8">
<VCard class="rounder-4">
<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-shopping-cart</v-icon>
Order Details
</div>
</div>
<v-data-table :headers="dessertHeaders" :items="desserts">
</v-data-table>
</VCard>
</VCol>
<VCol cols="12" md="4">
<VCard class="mb-6" border>
<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">July 04, 2024 02:00 AM</span>
</div>
</div>
<div class="appointment-details">
<div class="detail-item pt-1">
<span class="detail-label">Timezone:</span>
<span class="detail-value pl-4">Asia/Karachi</span>
</div>
</div>
</VCardText>
</VCard>
</VCol>
</VRow>
</VExpansionPanelText>
</VExpansionPanel>
</VExpansionPanels>
<!-- <VCardText class="d-flex flex-column gap-y-4 p-0 rounded-3"> -->
<VCard v-if="scheduleDate && scheduleTime && callEnd == null" border class="rounded-3" flat>
<VCardText class="d-flex flex-sm-row flex-column pa-5 py-2">
<div class="text-no-wrap text-base m-0 pr-9 mr-9" color="secondary">
<!-- <VImg
:src="card.image"
:width="60"
:height="25"
/> -->
<p class="text-secondary mb-2 my-0">DateTime</p>
<p class="text-base text-bold">
<b>{{ scheduleDate }} {{ scheduleTime }}</b>
</p>
<!-- <span class="text-body-1">**** **** **** {{ card.number.substring(card.number.length - 4) }}</span> -->
</div>
<div class="text-no-wrap text-base m-0" color="secondary">
<!-- <VImg
:src="card.image"
:width="60"
:height="25"
/> -->
<p class="text-secondary mb-2 my-0">Timezone</p>
<p class="text-base">
<b>{{ timeZone }} </b>
</p>
</div>
<VSpacer />
<div class="d-flex flex-column text-sm-end gap-2">
<div class="order-sm-0 order-1 pt-0 mt-3">
<RouterLink v-if="isMeetingEnable" to="/queue" target="_blank">
<VBtn color="primary" class="me-2">
Go to Meeting
</VBtn>
</RouterLink>
<span v-else>
<VBtn class="" color="primary" disabled>Go to Meeting
</VBtn>
</span>
</div>
<!-- <span class="mt-auto order-sm-1 order-0">Card expires at {{ card.expiry }}</span> -->
</div>
</VCardText>
</VCard>
<VCard border flat v-else>
<VAlert border="start" color="rgb(var(--v-theme-yellow))" variant="tonal">
<div class="text-center">No data found</div>
</VAlert>
</VCard>
<!-- </VCardText> -->
</VCol>
</template>
<style scoped>
.v-card-title {
font-weight: 700 !important;
}
.v-table {
border-radius: 10px !important;
line-height: 4.5;
max-width: 100%;
display: flex;
flex-direction: column;
background-color: #fff !important;
}
.v-table__wrapper {
background-color: #fff !important;
}
th.v-data-table__td.v-data-table-column--align-start.v-data-table__th {
background-color: #fff !important;
font-weight: 800px;
}
th.v-data-table__td.v-data-table-column--align-start.v-data-table__th {
background-color: #fff !important;
font-weight: 800px !important;
}
.v-data-table-header__content>span {
font-weight: 700 !important;
}
button.v-btn.v-btn--icon.v-theme--light.text-primary.v-btn--density-default.v-btn--size-small.v-btn--variant-text {
font-size: 16px;
}
.v-data-table-header__content {
font-weight: 700 !important;
background-color: white !important;
}
.v-table.v-table--has-top.v-table--has-bottom.v-theme--light.v-table--density-default.v-data-table {
border-radius: 10px !important;
}
</style>

View File

@@ -0,0 +1,389 @@
<script setup>
import { computed, onMounted, reactive, ref } from "vue";
import { useRoute, useRouter } from "vue-router";
import { useStore } from "vuex";
const route = useRoute();
const router = useRouter();
const store = useStore();
const statusOptions = ["Active", "Cancelled"];
let filters = reactive({
status: [],
})
let foundText = ref('')
const subscriptions = ref([]);
const search = ref("");
const filteredSubscriptions = computed(() => {
let subscriptionList = store.getters.getsubscriptionList;
// Check if subscriptionList is null or undefined
if (!subscriptionList) {
return [];
}
subscriptions.value = subscriptionList;
if (filters.status.length > 0) {
foundText.value = filters.status.join(', ')
subscriptionList = subscriptionList.filter((order) =>
filters.status.includes(order.status)
);
}
console.log('subscriptionList', subscriptionList)
const searchLower = search.value.toLowerCase();
console.log(searchLower)
return subscriptionList.filter(
(sub) =>
(sub.order_number && String(sub.order_number).toLowerCase().includes(searchLower)) ||
(sub.product_title && String(sub.product_title).toLowerCase().includes(searchLower))
);
});
const formatDate = (date) => {
const messageDate = new Date(date);
const options = {
year: "numeric",
month: "numeric",
day: "numeric",
};
const formattedDate = messageDate
.toLocaleString("en-US", options)
.replace(/\//g, "-");
return `${formattedDate} `;
};
const getStatusColor = (status) => {
if (status === "Active") return "green";
if (status === "Cancelled") return "orange";
return "red";
};
const getStatusIcon = (status) => {
switch (status.toLowerCase()) {
case 'active':
return 'mdi-check-circle';
case 'pending':
return 'mdi-clock-outline';
case 'cancelled':
return 'mdi-cancel';
case 'expired':
return 'mdi-alert-circle-outline';
default:
return 'mdi-help-circle-outline';
}
}
const viewDetails = (subscription) => {
// Implement view details logic here
console.log("View details for subscription:", subscription.id);
router.push("/subscriptions-detail/" + subscription.id);
};
const subscriptionList = computed(() => {
let filtered = store.getters.getsubscriptionList;
subscriptions.value = store.getters.getsubscriptionList;
return subscriptions.value;
});
onMounted(async () => {
store.dispatch("updateIsLoading", true);
await store.dispatch("subscriptionList");
});
const getPlanIcon = (plan) => {
switch (plan.toLowerCase()) {
case "basic":
return "mdi-star-outline";
case "premium":
return "mdi-star-half";
case "pro":
return "mdi-star";
case "enterprise":
return "mdi-medal";
default:
return "mdi-help-circle";
}
};
const cancelSubscription = async (subscriptionData) => {
// Implement cancellation logic here
store.dispatch("updateIsLoading", true);
await store.dispatch("subscriptionCancel", { id: subscriptionData.subscription_id });
console.log("Cancelling subscription:", subscriptionData.subscription_id);
await store.dispatch("subscriptionList");
await store.getters.getsubscriptionList;
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>
<v-container fluid class="subscription-container pa-6">
<v-row justify="center" align="center" class="mb-8" style="display: none;">
<v-col cols="12" md="8">
<h4 class="text-h4 text-center gradient-text mb-4">
Active Subscriptions
</h4>
<v-text-field v-model="search" prepend-inner-icon="mdi-magnify" label="Search subscriptions" outlined
rounded hide-details dense class="search-field"></v-text-field>
</v-col>
</v-row>
<v-row>
<v-col cols="12">
<VCardTitle class="pt-0"><b>Subscriptions </b></VCardTitle>
<v-card>
<v-card-title>
<v-row class="px-0 py-4">
<v-col cols="12" md="4">
<v-text-field v-model="search" prepend-inner-icon="mdi-magnify"
label="Search subscriptions" outlined density="compact" hide-details
class="search-field"></v-text-field>
</v-col>
<v-col cols="12" md="4">
<v-select v-model="filters.status" :items="statusOptions" label="Status" outlined
density="compact" multiple></v-select>
</v-col>
</v-row>
</v-card-title>
</v-card>
</v-col>
</v-row>
<v-row v-if="filteredSubscriptions.length > 0">
<VCol sm="6" cols="12" v-for="subscription in filteredSubscriptions" :key="subscription.order_number"
style="display: none;">
<VCard>
<div class="d-flex justify-space-between flex-wrap flex-md-nowrap flex-column flex-md-row">
<div class="ma-auto pa-5">
<VImg width="137" height="176" :src="subscription.image_url" />
</div>
<VDivider :vertical="$vuetify.display.mdAndUp" />
<div>
<VCardItem>
</VCardItem>
<VCardText>
<h3>{{ subscription }}</h3>
</VCardText>
<VCardText class="text-subtitle-1">
<span>Price :</span> <span class="font-weight-medium">${{
subscription.product_price.amount }}</span>
<span v-if="subscription.subscription_start_date">Start :</span> <span
class="font-weight-medium" v-if="subscription.subscription_start_date">{{
formatDate(subscription.subscription_start_date)
}}</span>
<span v-if="subscription.subscription_renewal_date">Renewal Date :</span> <span
class="font-weight-medium" v-if="subscription.subscription_renewal_date">{{
formatDate(subscription.subscription_renewal_date)
}}</span>
<span>Status :</span> <span class="font-weight-medium">{{
subscription.status }}</span>
</VCardText>
<VCardActions class="justify-space-between">
<VBtn>
<VIcon icon="bx-cart-add" />
<span class="ms-2">Add to cart</span>
</VBtn>
<VBtn color="secondary" icon="bx-share-alt" />
</VCardActions>
</div>
</div>
</VCard>
</VCol>
<v-col v-for="subscription in filteredSubscriptions" :key="subscription.order_number" cols="12" md="4">
<v-hover v-slot:default="{ isHovering }">
<v-card class="subscription-card mx-auto" :elevation="isHovering ? 8 : 2"
:class="{ 'on-hover': isHovering }">
<v-card-title class="text-h5 d-flex flex-column align-center">
<v-avatar size="80" class="mb-3" color="rgb(var(--v-theme-yellow))"
v-if="subscription.image_url">
<v-img :src="subscription.image_url" :alt="subscription.title"></v-img>
</v-avatar>
<v-avatar color="rgb(var(--v-theme-yellow))" size="56" v-if="!subscription.image_url"
class="text-grey-800 text-h6 font-weight-bold mr-4">
#{{ subscription.order_number }}
</v-avatar>
</v-card-title>
<v-card-text>
<h4 class="text-center title">{{ subscription.product_title }}</h4>
<v-list dense class="details-list">
<v-list-item>
<v-row align="center" no-gutters>
<v-col cols="12" md="2">
</v-col>
<v-col cols="12" md="8">
<v-icon color="primary">mdi-cash</v-icon> <span>Price: ${{
subscription.product_price.amount }}</span>
</v-col>
<v-col cols="12" md="2">
</v-col>
</v-row>
</v-list-item>
<v-list-item v-if="subscription.subscription_start_date">
<v-row align="center" no-gutters>
<v-col cols="12" md="2">
</v-col>
<v-col cols="12" md="8">
<v-icon color="primary">mdi-calendar-start</v-icon><span>Start: {{
formatDate(subscription.subscription_start_date) }}</span>
</v-col>
<v-col cols="12" md="2">
</v-col>
</v-row>
</v-list-item>
<v-list-item v-if="subscription.subscription_renewal_date">
<v-row align="center" no-gutters>
<v-col cols="12" md="2">
</v-col>
<v-col cols="12" md="8">
<v-icon color="primary">mdi-calendar-clock</v-icon> <span>Next Billing: {{
formatDate(subscription.subscription_renewal_date) }}</span>
</v-col>
<v-col cols="12" md="2">
</v-col>
</v-row>
</v-list-item>
<v-list-item>
<v-row align="center" no-gutters>
<v-col cols="12" md="2">
</v-col>
<v-col cols="12" md="8">
<v-icon :color="getStatusColor(subscription.status)">
{{ getStatusIcon(subscription.status) }}
</v-icon>
<span>Status: {{ subscription.status }}</span>
</v-col>
<v-col cols="12" md="2">
</v-col>
</v-row>
</v-list-item>
</v-list>
</v-card-text>
<v-card-actions>
<v-btn color="rgb(var(--v-theme-yellow))" @click="cancelSubscription(subscription)"
prepend-icon="mdi-cancel" block>Cancel
Subscription</v-btn>
<v-btn color="primary" block @click="viewDetails(subscription)">
View Details
</v-btn>
</v-card-actions>
</v-card>
</v-hover>
</v-col>
</v-row>
<v-row v-else justify="center" align="center">
<v-col cols="12" class="text-center">
<v-icon size="64" color="grey">mdi-playlist-remove</v-icon>
<h3 class="mt-4">No {{ foundText }} Subscriptions</h3>
<p class="text-subtitle-1">You don't have any {{foundText}} subscriptions at the moment.</p>
</v-col>
</v-row>
</v-container>
</template>
<style scoped>
.subscription-container {
background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);
min-height: 100vh;
}
.subscription-card {
transition: all 0.3s ease;
}
.subscription-card.on-hover {
transform: translateY(-5px);
}
.v-avatar {
background-color: var(--v-primary-base);
}
.subscription-container .v-card .v-card-text {
line-height: 1.25rem;
min-height: 200px;
}
.title {
font-size: 13px;
}
.gradient-text {
background: linear-gradient(45deg, rgb(var(--v-theme-yellow)), #173320);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
font-weight: bold;
}
.search-field {
max-width: 500px;
margin: 0 auto;
}
.subscription-card {
transition: all 0.3s cubic-bezier(0.25, 0.8, 0.25, 1);
border-radius: 16px;
overflow: hidden;
}
.subscription-card.on-hover {
transform: translateY(-5px);
}
.v-list-item {
min-height: 40px;
}
.details-list .v-list-item {
padding: 0;
}
.details-list .v-row {
align-items: center;
}
.details-list .v-col {
display: flex;
align-items: center;
}
.details-list .v-icon {
margin-right: 0.5rem;
font-size: 1.5rem;
}
.details-list .v-col span {
font-size: 1rem;
font-weight: 500;
color: var(--v-grey-darken1);
}
</style>

View File

@@ -0,0 +1,250 @@
<script setup>
import { computed } from "vue";
import { useRoute, useRouter } from "vue-router";
import { useStore } from "vuex";
const store = useStore();
const route = useRoute();
const router = useRouter();
const subscription = computed(async () => {
const id = route.params.id;
await store.dispatch("subscriptionByID", { id: id });
return store.getters.getSubscriptionById(id);
});
const getPlanIcon = (title) => {
if (!title) return "mdi-help-circle";
switch (title.toLowerCase()) {
case "basic":
return "mdi-star-outline";
case "premium":
return "mdi-star-half";
case "pro":
return "mdi-star";
case "enterprise":
return "mdi-medal";
default:
return "mdi-help-circle";
}
};
const getPlanColor = (title) => {
if (!title) return "grey";
switch (title.toLowerCase()) {
case "basic":
return "blue";
case "premium":
return "purple";
case "pro":
return "green";
case "enterprise":
return "orange";
default:
return "grey";
}
};
const getStatusColor = (status) => {
if (!status) return "grey";
switch (status.toLowerCase()) {
case "active":
return "success";
case "paused":
return "warning";
case "cancelled":
return "error";
default:
return "grey";
}
};
const formatDate = (dateString) => {
return new Date(dateString).toLocaleDateString("en-US", {
year: "numeric",
month: "long",
day: "numeric",
});
};
const formatPrice = (price) => {
return new Intl.NumberFormat("en-US", {
style: "currency",
currency: "USD",
}).format(price);
};
const goBack = () => {
router.go(-1);
};
const cancelSubscription = () => {
// Implement cancellation logic here
console.log("Cancelling subscription:", subscription.value.id);
};
</script>
<template>
<v-container v-if="subscription" class="subscription-detail-container">
<v-row justify="center">
<v-col cols="12" md="10" lg="8">
<v-card class="subscription-detail-card">
<v-card-title
class="text-h3 d-flex justify-center pa-6 gradient-background"
>
<v-avatar size="80" color="white" class="elevation-3">
<v-icon
:color="getPlanColor(subscription.title)"
size="50"
>
{{ getPlanIcon(subscription.title) }}
</v-icon>
</v-avatar>
</v-card-title>
<v-card-text class="pa-6">
<h2 class="text-h4 text-center mb-6">
{{ subscription.title }} Plan
</h2>
<v-row>
<v-col cols="12" md="6">
<v-list>
<v-list-item>
<template v-slot:prepend>
<v-icon color="primary"
>mdi-identifier</v-icon
>
</template>
<v-list-item-title
>ID:
{{
subscription.id
}}</v-list-item-title
>
</v-list-item>
<v-list-item>
<template v-slot:prepend>
<v-icon color="primary"
>mdi-calendar-start</v-icon
>
</template>
<v-list-item-title
>Start Date:
{{
formatDate(
subscription.startDate
)
}}</v-list-item-title
>
</v-list-item>
<v-list-item>
<template v-slot:prepend>
<v-icon color="primary"
>mdi-calendar-clock</v-icon
>
</template>
<v-list-item-title
>Next Billing:
{{
formatDate(
subscription.nextBillingDate
)
}}</v-list-item-title
>
</v-list-item>
</v-list>
</v-col>
<v-col cols="12" md="6">
<v-list>
<v-list-item>
<template v-slot:prepend>
<v-icon color="primary"
>mdi-currency-usd</v-icon
>
</template>
<v-list-item-title
>Price:
{{
formatPrice(subscription.price)
}}</v-list-item-title
>
</v-list-item>
<v-list-item>
<template v-slot:prepend>
<v-icon color="primary"
>mdi-information</v-icon
>
</template>
<v-list-item-title>
Status:
<v-chip
:color="
getStatusColor(
subscription.status
)
"
small
class="ml-2"
>
{{ subscription.status }}
</v-chip>
</v-list-item-title>
</v-list-item>
<v-list-item>
<template v-slot:prepend>
<v-icon color="primary"
>mdi-refresh</v-icon
>
</template>
<v-list-item-title
>Billing Cycle:
{{
subscription.billingCycle
}}</v-list-item-title
>
</v-list-item>
</v-list>
</v-col>
</v-row>
<v-divider class="my-6"></v-divider>
<h3 class="text-h5 mb-4">Plan Features</h3>
<v-chip-group column>
<v-chip
v-for="feature in subscription.features"
:key="feature"
color="primary"
outlined
>
{{ feature }}
</v-chip>
</v-chip-group>
</v-card-text>
<v-card-actions class="pa-6">
<v-btn
color="primary"
@click="goBack"
prepend-icon="mdi-arrow-left"
>Go Back</v-btn
>
<v-spacer></v-spacer>
<v-btn
color="error"
@click="cancelSubscription"
prepend-icon="mdi-cancel"
>Cancel Subscription</v-btn
>
</v-card-actions>
</v-card>
</v-col>
</v-row>
</v-container>
</template>

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,620 @@
<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 markAsCompleted = ref(false);
// const scheduleDate = ref('');
// const scheduleTime = ref('');
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 scheduleTimeDate = ref('');
const scheduleTime = ref();
const telemed_pro = ref();
const subTotalAmount = ref();
const status = ref('false');
const filteredOrders = computed(() => {
let filtered = store.getters.getSinglePatientAppointment;
// scheduleTimeDate.value = getConvertedDate(convertUtcTime(filtered.appointment_time,
// filtered.appointment_date, filtered.timezone));
console.log("filtered>>>>>>>>>>>>",filtered);
status.value = true;
if(filtered && filtered.status == 'pending'){
status.value = true;
}
return filtered;
});
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 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: localStorage.getItem('patient_id'),
appointment_id: localStorage.getItem('patient_appiontment_id'),
})
orderData.value = store.getters.getSinglePatientAppointment;
orderDetail.value = store.getters.getSinglePatientAppointment.order;
console.log("appointmentData-------------------------", orderData.value);
orderId.value = orderData.value.order.id;
status.value = true;
if (orderData.value && orderData.value.status == 'pending') {
status.value = false;
}
markAsCompleted.value = true;
// 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";
}
};
const handleActionClick = async (item) => {
await store.dispatch('agentMeetingSatausUpdate', { id: item.id });
await store.dispatch('getAppointmentByIdAgent', {
patient_id: localStorage.getItem('patient_id'),
appointment_id: localStorage.getItem('patient_appiontment_id'),
});
};
</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>
<VSnackbar v-if="markAsCompleted" :timeout="5000" location="top end" variant="flat" color="success">
Mark as Completed
</VSnackbar>
<div>
<VRow>
<VCol cols="6">
<h5 class="text-h5">Order #{{ orderId }}</h5>
</VCol>
<VCol cols="6" class="text-end">
<VBtn :disabled="status" @click="handleActionClick(filteredOrders)"> Mark as Completed</VBtn>
<!-- <VBtn :disabled="filteredOrders.status.toLowerCase() == 'completed'"
color="primary" @click="handleActionClick(filteredOrders)">
<VIcon v-if="filteredOrders.status.toLowerCase() !== 'completed'"
color="error" icon="mdi-arrow-right" size="small" />
Mark as Completed
</VBtn> -->
</VCol>
</VRow>
<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">
<h5 style="margin-bottom: 0px;">
{{ item.plans_v1.title }}
</h5>
<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">
{{ getConvertedDate(convertUtcTime(filteredOrders.appointment_time, filteredOrders.appointment_date, filteredOrders.timezone)) }}
at {{ getConvertedTime(convertUtcTime(filteredOrders.appointment_time, filteredOrders.appointment_date, filteredOrders.timezone)) }}
</span>
</div>
<div class="detail-item" v-if="filteredOrders.start_time && filteredOrders.end_time">
<span class="detail-label">Start Time:</span>
<span class="detail-value">{{
formatDate(filteredOrders.start_time)
}}</span>
</div>
<div class="detail-item" v-if="filteredOrders.start_time && filteredOrders.end_time">
<span class="detail-label">End Time:</span>
<span class="detail-value">{{
formatDate(filteredOrders.end_time)
}}</span>
</div>
<div class="detail-item" v-if="filteredOrders.start_time && filteredOrders.end_time">
<span class="detail-label">Duration:</span>
<span class="detail-value">{{
totalCallDuration(filteredOrders.start_time,
filteredOrders.end_time) }}</span>
</div>
</div>
</VCardText>
</VCard>
<!-- 👉 Customer Details -->
<VCard class="mb-6" v-if="filteredOrders.telemed_pro.name">
<VCardText class="d-flex flex-column gap-y-6">
<h3>Provider Details</h3>
<div class="d-flex align-center">
<VAvatar :image="avatar1" class="me-3" style="display: none;" />
<VAvatar color="primary" class="me-3" size="30">
<VIcon icon="mdi-account" size="25" color="white" />
</VAvatar>
<div>
<div class="text-body-1 text-high-emphasis font-weight-medium">
{{ filteredOrders.telemed_pro.name }}
</div>
</div>
</div>
<div class="d-flex align-center" style="display: none;">
<VAvatar variant="tonal" color="success" class="me-3" style="display: none;">
<VIcon icon="ri-shopping-cart-line" />
</VAvatar>
<!-- <h4 style="display: none;">
{{ filteredOrders.order_items.total_products }}
Products
</h4> -->
</div>
<div class="d-flex flex-column gap-y-1">
<div class="d-flex justify-space-between gap-1 text-body-2">
<h5>Contact Info</h5>
</div>
<span>Email:
{{ filteredOrders.telemed_pro.email }}</span>
<span>Mobile:
{{ filteredOrders.telemed_pro.phone_number }}</span>
</div>
</VCardText>
</VCard>
<VCard class="mb-6">
<VCardText>
<div class="d-flex align-center justify-space-between gap-1 mb-6">
<div class="text-body-1 text-high-emphasis font-weight-medium">
<v-icon class="mr-2" color="rgb(var(--v-theme-yellow))">mdi-truck-delivery</v-icon>
Shipping Address
</div>
<!-- <span
class="text-base text-primary font-weight-medium cursor-pointer"
@click="
isEditAddressDialogVisible =
!isEditAddressDialogVisible
"
>Edit</span
> -->
</div>
<div>
{{ filteredOrders.order.shipping_address1 }}
<br />
{{ filteredOrders.order.shipping_city }}
<br />
{{ filteredOrders.order.shipping_state }},
{{ filteredOrders.order.shipping_zipcode }}
<br />
{{ filteredOrders.order.shipping_country }}
</div>
</VCardText>
</VCard>
<!-- 👉 Billing Address -->
<VCard style="display: none;">
<VCardText>
<div class="d-flex align-center justify-space-between gap-1 mb-3">
<div class="text-body-1 text-high-emphasis font-weight-medium">
Billing Address
</div>
<!-- <span
class="text-base text-primary font-weight-medium cursor-pointer"
@click="
isEditAddressDialogVisible =
!isEditAddressDialogVisible
"
>Edit</span
> -->
</div>
<div>
{{ filteredOrders.order.billing_address1 }}
<br />
{{ filteredOrders.order.billing_city }}
<br />
{{ filteredOrders.order.billing_state }},
{{ filteredOrders.order.billing_zipcode }}
<br />
{{ filteredOrders.order.billing_country }}
</div>
<!-- <div class="mt-6">
<h6 class="text-h6 mb-1">Mastercard</h6>
<div class="text-base">Card Number: ******4291</div>
</div> -->
</VCardText>
</VCard>
</VCol>
</VRow>
</div>
</template>
<style scoped>
.appointment-details {
display: flex;
flex-direction: column;
}
.detail-item {
display: flex;
margin-bottom: 10px;
}
.detail-label {
font-weight: bold;
min-width: 120px;
}
.detail-value {
flex: 1;
}
::-webkit-scrollbar {
width: 10px;
/* Width of the scrollbar */
}
/* Track */
::-webkit-scrollbar-track {
background: #f1f1f1;
/* Color of the track */
}
/* Handle */
::-webkit-scrollbar-thumb {
background: #888;
/* Color of the handle */
border-radius: 5px;
/* Roundness of the handle */
}
/* Handle on hover */
::-webkit-scrollbar-thumb:hover {
background: #555;
/* Color of the handle on hover */
}
</style>

View File

@@ -0,0 +1,528 @@
<script setup>
import { useRouter } from 'vue-router';
import { useStore } from 'vuex';
import QuestionProgressBar from '../views/pages/questionere/QuestionProgressBar.vue';
import questionsJson from '../views/pages/questionere/questions_parse.json';
const router = useRouter()
const store = useStore()
const questionsJsonData = ref(questionsJson)
const answers = ref(null)
const QuestionsAnswers = ref([]);
const username = ref(null);
const email = ref(null);
const phone = ref(null);
const address1 = ref(null);
const dob = ref(null);
const agePatient = ref(null);
const isMobile = ref(window.innerWidth <= 768);
onMounted(async () => {
const navbar = document.querySelector('.layout-navbar');
const callDiv = document.querySelector('.layout-page-content');
if (navbar) {
navbar.style.display = 'block';
}
if (callDiv)
callDiv.style.padding = '1.5rem';
store.dispatch('updateIsLoading', true)
await store.dispatch('getPatientInfoByID')
await store.dispatch('getAgentQuestionsAnswers')
username.value = store.getters.getPatient.first_name + ' ' + store.getters.getPatient.last_name;
email.value = store.getters.getPatient.email
phone.value = store.getters.getPatient.phone_no
dob.value = changeFormat(store.getters.getPatient.dob)
agePatient.value = calculateAge(store.getters.getPatient.dob) + " Year"
address1.value = store.getters.getPatient.address + ' ' +
store.getters.getPatient.city + ' ' +
store.getters.getPatient.state + ' ' +
store.getters.getPatient.country;
// address.value = patient_address;
answers.value = store.getters.getPatientAnswers
// console.log('questionsJsonData', questionsJsonData.value)
// console.log('API Answers', answers.value)
createFinalArray();
store.dispatch('updateIsLoading', false)
window.addEventListener('resize', checkMobile);
});
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) {
// 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 createFinalArray = () => {
questionsJsonData.value.forEach(question => {
const { label, key, type } = question;
if (answers.value.hasOwnProperty(key)) {
QuestionsAnswers.value.push({
label,
key,
type,
value: answers.value[key]
});
}
});
// console.log('------finalArray ', QuestionsAnswers.value)
};
const checkMobile = () => {
isMobile.value = window.innerWidth <= 768;
};
const calculateAge = (dateOfBirth) => {
const today = new Date();
const birthDate = new Date(dateOfBirth);
let age = today.getFullYear() - birthDate.getFullYear();
const monthDiff = today.getMonth() - birthDate.getMonth();
if (
monthDiff < 0 ||
(monthDiff === 0 && today.getDate() < birthDate.getDate())
) {
age--;
}
return age;
}
</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-row class='mb-2'>
<VCol cols="12" md="12" class="mb-4 ml-5" v-if="username || phone" style="font-size: 16px;">
<div>
<h3 class="mb-2"> {{ username }}</h3>
<div class="mb-1">
<VTooltip location="left" activator="parent" transition="scroll-x-transition">
Email
</VTooltip>
<VIcon icon="mdi-email" size="20" class="me-2" />{{ email }}
</div>
<div class="mb-2">
<VTooltip location="left" activator="parent" transition="scroll-x-transition">
Age
</VTooltip>
<VIcon icon="mdi-calendar-account" title="Age" size="20" class="me-2" />{{ agePatient }}
</div>
<div class="mb-2">
<VTooltip location="left" activator="parent" transition="scroll-x-transition">
Date of birth
</VTooltip>
<VIcon icon="mdi-calendar" size="20" class="me-2" />{{ dob }}
</div>
<div class="mb-2">
<VTooltip location="left" activator="parent" transition="scroll-x-transition">
Address
</VTooltip>
<VIcon icon="mdi-map-marker" size="20" class="me-2" />{{ address1 }}
</div>
<div>
<VTooltip location="left" activator="parent" transition="scroll-x-transition">
Contact Number
</VTooltip>
<VIcon icon="mdi-phone" size="20" class="me-2" />{{ phone }}
</div>
</div>
</VCol>
<v-col cols="12" md="12">
<VList class="">
<VListItem class="">
<VListItemTitle class="d-flex align-center justify-space-between bg-dark mt-1"
style="padding: 5px;">
<div :class="isMobile ? '' : 'w-40'">
<p class="mb-0" :class="isMobile ? 'heading-text-m' : 'heading-text-d'"><b>SYMPTOM
CHECKLIST</b></p>
</div>
<div class="d-flex align-center" :class="isMobile ? 'heading-text-m' : 'heading-text-d'">
<p class="mb-0"><b>MILD</b>
</p>
</div>
<div class="d-flex align-center" :class="isMobile ? 'heading-text-m' : 'heading-text-d'">
<p class="mb-0"><b>MODERATE</b>
</p>
</div>
<div class="d-flex align-center" :class="isMobile ? 'heading-text-m' : 'heading-text-d'">
<p class="mb-0"><b>SEVERE</b>
</p>
</div>
</VListItemTitle>
</VListItem>
<!-- Move the second list item inside the loop -->
<VListItem class="pt-0 pb-0 ht-li" v-for="(item, index) of QuestionsAnswers" :key="index">
<VListItemTitle class="d-flex align-center justify-space-between">
<div class="border-right"
:class="{ 'bg-silver': (index + 1) % 2 === 0, 'w-custom-m': isMobile, 'w-40': !isMobile }"
style="padding-left: 5px;">
<p class="text-wrap mb-0" :class="isMobile ? 'heading-text-m' : 'heading-text-d'">{{
item.label
}}</p>
</div>
<div class="d-flex align-center" :class="isMobile ? 'w-custom-d' : 'w-60'">
<QuestionProgressBar :type="item.type" :value="item.value"></QuestionProgressBar>
</div>
</VListItemTitle>
</VListItem>
</VList>
<!-- <v-table density="compact" fixed-header>
<thead>
<tr class="text-right">
<th class="bg-dark">
<b>SYMPTOM CHECKLIST</b>
</th>
<th class="text-right bg-dark">
<b>MILD</b>
<VIcon icon="mdi-menu-down"></VIcon>
</th>
<th class="text-right bg-dark">
<b>MODERATE</b>
<VIcon icon="mdi-menu-down"></VIcon>
</th>
<th class="text-right bg-dark">
<b>SEVERE</b>
<VIcon icon="mdi-menu-down"></VIcon>
</th>
</tr>
</thead>
<tbody>
<tr v-for="(item, index) of QuestionsAnswers" :key="index">
<td class="border-right" v-if="!isMobile">{{ item.label }}</td>
<td :colspan="isMobile ? '4' : '3'">
<p v-if="isMobile" class="mb-0">{{ item.label }}</p>
<QuestionProgressBar :type="item.type" :value="item.value"></QuestionProgressBar>
</td>
</tr>
</tbody>
</v-table> -->
</v-col>
</v-row>
</template>
<style lang="scss" scoped>
.mdi-email {
margin-bottom: 5px;
}
.ht-li {
min-height: 20px !important;
}
.bg-silver {
background-color: silver;
}
.text-wrap {
text-wrap: balance;
}
.w-40 {
width: 40%;
}
.w-custom-m {
width: 36%;
}
.w-60 {
width: 60%;
}
.w-custom-d {
width: 65%;
}
.v-list-item--density-default.v-list-item--one-line {
min-height: 40px;
}
.heading-text-m {
font-size: 9px;
}
.heading-text-d {
font-size: 12px;
}
.bg-dark {
background-color: #808080b3 !important;
}
.text-right {
text-align: right !important;
width: 23%;
}
.border-right {
border-right: 1.5px solid black;
}
.hidden-component {
display: none
}
.meta-key {
border: thin solid rgba(var(--v-border-color), var(--v-border-opacity));
border-radius: 6px;
block-size: 1.5625rem;
line-height: 1.3125rem;
padding-block: 0.125rem;
padding-inline: 0.25rem;
}
::v-deep .custom-menu {
position: relative;
}
::v-deep .custom-menu::before {
content: "" !important;
position: absolute !important;
transform: translateY(-50%);
top: 50% !important;
left: -8px !important;
border-left: 8px solid transparent !important;
border-right: 8px solid transparent !important;
border-bottom: 8px solid #fff !important;
}
// Styles for the VList component
.more .v-list-item-title {
color: rgb(106 109 255);
}
.more .menu-item:hover {
cursor: pointer;
}
.slide-enter-active,
.slide-leave-active {
transition: transform 0.3s ease;
}
.slide-enter,
.slide-leave-to {
transform: translateX(-100%);
}
.start-call-btn {
opacity: 0;
display: none;
transition: opacity 0.3s ease;
}
.button_margin {
margin: 2px;
}
.dialog_padding {
padding: 5px;
}
.custom-menu .v-menu__content {
background-color: #333;
color: #fff;
border-radius: 4px;
padding: 8px 0;
}
.user-info {
display: flex;
flex-direction: column;
transition: opacity 0.3s ease;
}
.list-item-hover {
transition: background-color 0.3s ease;
&:hover {
background-color: rgba(var(--v-theme-primary), 0.1);
.start-call-btn {
opacity: 1;
display: block;
position: relative;
left: -35px;
}
.user-info {
opacity: 0;
display: none;
}
}
}
.pop_card {
overflow: hidden !important;
padding: 10px;
}
.v-overlay__content {
max-height: 706.4px;
max-width: 941.6px;
min-width: 24px;
--v-overlay-anchor-origin: bottom left;
transform-origin: left top;
top: 154.4px !important;
left: 204px !important;
}
.button_margin {
margin-top: 10px;
font-size: 10px;
}
/* Responsive Styles */
@media screen and (max-width: 768px) {
.pop_card {
max-width: 100%;
margin: 0 auto;
}
}
.container_img {
display: flex;
flex-direction: column;
width: 100%;
align-items: center;
}
.image {
order: 2;
/* Change the order to 2 in mobile view */
}
.text {
order: 1;
/* Change the order to 1 in mobile view */
}
/* Media query for mobile view */
@media (max-width: 768px) {
.container_img {
flex-direction: row;
margin-top: 10px;
}
.button_margin_mobile {
width: 100%;
}
.image {
width: 20%;
padding: 0 10px;
}
.text {
width: 80%;
/* Each takes 50% width */
padding: 0 10px;
/* Optional padding */
}
}
::-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 */
}
/* Container for the content */
.scroll-container {
max-height: 191px;
/* Maximum height of the scrollable content */
overflow-y: scroll;
/* Enable vertical scrolling */
}
/* Content within the scroll container */
.scroll-content {
padding: 20px;
}
/* Example of additional styling for content */
.scroll-content p {
margin-bottom: 20px;
}
.cross button {
padding: 0px;
margin: 0px;
/* font-size: 10px; */
background: none;
border: none;
box-shadow: none;
height: 23px;
}
.v-data-table-header {
display: table-header-group;
}
</style>

View File

@@ -0,0 +1,263 @@
<script setup>
import StartOverPupup from '@/views/pages/home/StartOverPupup.vue';
import axios from '@axios';
import { onBeforeMount, onMounted, onUnmounted } from 'vue';
import { useRoute, useRouter } from 'vue-router';
import { useStore } from 'vuex';
// import store from '../store.js';
const router = useRouter()
const route = useRoute()
const isMobile = ref(window.innerWidth <= 768); // Assuming mobile width is less than or equal to 768px
// const patient_id = localStorage.getItem('patient_id')
// const access_token = localStorage.getItem('access_token');
//import plans from '@/views/pages/products/peptied';
const currentPath = window.location.origin
const isTonalSnackbarVisible = ref(false)
const patientResponse = ref(false)
const isLoadingVisible = ref(false)
const infoVForm = ref()
const store = useStore()
const dialog = ref(false);
const selectedPlan = ref({});
const plans = ref([]);
const seetingPlanPage = ref({});
const seetingPlanPara = ref(null);
const seetingPlanLogo = ref();
const isConent = ref();
onBeforeMount(() => {
store.dispatch('updateCurrentPage', 'plans')
localStorage.setItem('currentPage', 'plans')
})
onMounted(async () => {
const baseUrl = window.location.hostname;
if (baseUrl === 'localhost') {
isConent.value = 'd-block';
} else {
isConent.value = 'd-none';
}
// if (baseUrl == )
console.log(route)
const id = route.query.id
const slug = route.query.slug
if (slug && id) {
let plansapi = await axios.post('/api/plans', {})
plans.value = plansapi.data
let getPlan = plans.value.find(plan => plan.id === parseInt(id) && plan.slug === slug)
console.log(getPlan)
localStorage.setItem('plan_name', getPlan.title)
localStorage.setItem('plan_price', getPlan.price)
localStorage.setItem('plan_id', getPlan.id)
localStorage.setItem('list_one_title', getPlan.list_one_title)
localStorage.setItem('list_sub_title', getPlan.list_sub_title)
localStorage.setItem('list_two_title', getPlan.list_two_title)
localStorage.setItem('plan_image', getPlan.image_url)
localStorage.setItem('prescription_required', getPlan.is_prescription_required)
localStorage.setItem('shipping_price', getPlan.shipping_cost)
router.push('/pre-register');
}
isLoadingVisible.value = true
console.log('Current Page', store.getters.getCurrentPage)
let plansapi = await axios.post('/api/plans', {})
plans.value = plansapi.data
console.log(plans.value)
let setting = await axios.post('/api/settings', {})
seetingPlanPage.value = setting.data
let para = seetingPlanPage.value.plan_description.replace(/\n/g, '<br>');
console.log(para)
seetingPlanPara.value = para
seetingPlanLogo.value = '/assets/logo/' + setting.data.logo
console.log(seetingPlanLogo.value)
isLoadingVisible.value = false
window.addEventListener('resize', checkIfMobile);
})
// Detach event listener on component unmount
onUnmounted(() => {
window.removeEventListener('resize', checkIfMobile);
});
const checkIfMobile = () => {
isMobile.value = window.innerWidth <= 768;
};
const openDialog = (plan) => {
selectedPlan.value = plan;
dialog.value = true;
};
const imageSrc = (src) => {
console.log(`${currentPath}/product/${src}`)
return `${currentPath}/product/${src}`
};
const choosePlan = async (plan, price, id) => {
isLoadingVisible.value = true
console.log('plan', plan)
store.dispatch('updatePatientPlan', {
plan_name: plan,
plan_amount: price,
plan_id: id
})
let getPlan = plans.value.find(plan => plan.id === parseInt(id))
console.log(getPlan)
localStorage.setItem('plan_name', plan)
localStorage.setItem('plan_price', price)
localStorage.setItem('plan_id', id)
localStorage.setItem('list_one_title', getPlan.list_one_title)
localStorage.setItem('list_sub_title', getPlan.list_sub_title)
localStorage.setItem('list_two_title', getPlan.list_two_title)
localStorage.setItem('plan_image', getPlan.image_url)
router.replace(route.query.to && route.query.to != '/plans' ? String(route.query.to) : '/pre-register')
}
const formattedDescription = computed(async () => {
return para
});
</script>
<template>
<StartOverPupup :showPopup="store.getters.getShowStartOverPupup"></StartOverPupup>
<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>
<div class="pb-sm-5 rounded-top" :class="isConent">
<VContainer>
<div class="auth-wrapper d-flex align-center justify-center pa-4">
<VCard class="auth-card pa-2 rounded-5" max-width="800">
<VCardItem class="justify-center">
<VCardTitle class="text-2xl font-weight-bold text-primary">
<VImg :src="seetingPlanLogo" width="250" height="50" />
</VCardTitle>
</VCardItem>
<VCardText>
<VRow class=" mx-0 gy-3 px-lg-5">
<VCol cols="12" md="12" class="pb-0">
<!-- 👉 Title and subtitle -->
<div class="text-center">
<h4 class="text-h5 pricing-title mb-1">
{{ seetingPlanPage.plan_main_title }}
</h4>
<p class="mb-3" v-html="seetingPlanPara"></p>
</div>
</VCol>
<v-row justify="center">
<v-col cols="12" md="10"
class="d-flex align-center justify-space-between py-4 pl-4 rounded-lg border border-gray-200 elevation-0 my-2"
v-for="plan in plans" :key="plan.id">
<div class="d-flex align-center">
<div class="align-self-flex-start">
<v-img :src='imageSrc(plan.image_url)' max-width="120" max-height="120"
class="thumbnail"
style="overflow: hidden; flex-shrink: 0; flex-grow: 0"></v-img>
</div>
<div class="ml-8">
<h3 class="text-h6 font-weight-semibold mb-0">{{ plan.title }}</h3>
<span class="text-subtitle-1 font-weight-medium">{{ plan.currency }} {{
plan.price
}} -
<a href="#" class="text-decoration-none text-primary"
@click.prevent="openDialog(plan)">More Info</a>
</span>
</div>
</div>
<div>
<v-btn color="primary" variant="outlined"
@click="choosePlan(plan.title, plan.price, plan.id)">
<v-icon left>mdi-plus</v-icon>
Book Now
</v-btn>
</div>
</v-col>
</v-row>
<v-dialog v-model="dialog" max-width="600px">
<v-card>
<v-card-title class="headline">{{ selectedPlan.title }}</v-card-title>
<v-card-text>
<p><strong>{{ selectedPlan.list_sub_title }}</strong></p>
<p>{{ selectedPlan.list_one_title }}</p>
<p>{{ selectedPlan.list_two_title }}</p>
</v-card-text>
<v-card-actions>
<v-spacer></v-spacer>
<v-btn color="blue darken-1" text @click="dialog = false">Close</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
<!--
<VCol v-for="plan in plans.plans_list" :key="plan.title" cols="12" md="4">
<VCard class="auth-card rounded-5" style="margin-top: 10px;">
<div class="col-lg mb-md-0 mb-4">
<v-card-title class="headline text-center">
<h2 class="text-center text-capitalize mb-1 card-title">{{ plan.title }}</h2>
</v-card-title>
<div class="d-flex justify-center pt-3 pb-0">
<div class="text-body-1 align-self-start font-weight-medium">
{{ plan.currency }}
</div>
<h4 class="text-h2 font-weight-medium text-primary">{{ plan.price }}</h4>
</div>
<v-list lines="one">
<v-list-item :title="plan.list_one_title"
:subtitle="plan.list_sub_title"></v-list-item>
<v-list-item :title="plan.list_two_title"></v-list-item>
</v-list>
</div>
</VCard>
<div class="text-center mb-2 mt-5">
<v-btn class="btn btn-primary d-grid w-100 waves-effect waves-light" color="primary"
variant="flat" @click="choosePlan(plan.title, plan.price)">
Choose
</v-btn>
</div>
</VCol> -->
</VRow>
</VCardText>
</VCard>
</div>
</VContainer>
</div>
</template>
<style lang="scss">
.card-title {
font-family: "Public Sans", -apple-system, BlinkMacSystemFont, "Segoe UI", "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif;
}
.v-list-item-title {
white-space: inherit !important;
;
}
.thumbnail {
object-fit: contain;
overflow: hidden;
flex-shrink: 0;
flex-grow: 0;
}
.v-list {
min-height: 220px;
}
</style>

View File

@@ -0,0 +1,502 @@
<script setup>
import { useAppAbility } from '@/plugins/casl/useAppAbility';
import StartOverPupup from '@/views/pages/home/StartOverPupup.vue';
import DefinedSteps from '../layouts/components/DefinedSteps.vue';
import CustomNav from '../layouts/components/navbar-custom.vue';
// import { useAppAbility } from '@/plugins/casl/useAppAbility';
import axios from '@axios';
import {
emailValidator,
requiredEmail,
requiredPassword
} from '@validators';
import { onBeforeMount, onMounted, onUnmounted } from 'vue';
import { useRoute, useRouter } from 'vue-router';
import { useStore } from 'vuex';
const store = useStore()
const router = useRouter()
const route = useRoute()
const ability = useAppAbility()
const isMobile = ref(window.innerWidth <= 768);
const emailExist = ref(false)
const isTopLoadingVisible = ref(false);
const isLoadingVisible = ref(false)
const refVForm = ref()
const email = ref()
const password = ref(null)
const isPolicy = ref(false);
const user_role = ref();
const seetingPlanLogo = ref();
const settingSite = ref();
const isPasswordVisible = ref(false)
const canLogin = ref(false)
const inavlid = ref(false);
const InvalidCredential = ref()
const passwordCheck = ref(false)
const products = JSON.parse(localStorage.getItem('cart_products'));
const prescreptionRequired = ref(false)
const errors = ref({
password: undefined,
email: undefined,
})
watch(isPolicy, (newVal) => {
isTopLoadingVisible.value = false;
});
const checkIfMobile = () => {
isMobile.value = window.innerWidth <= 768;
};
const onSubmit = async () => {
refVForm.value?.validate().then(async ({ valid: isValid }) => {
console.log('isValid ', isValid)
if (isValid) {
store.dispatch('updateIsTonalSnackbar', false)
store.dispatch('updateErrorMessage', null)
if (!canLogin.value) {
await registerPatient()
} else {
passwordCheck.value = false
await loginPatient()
}
// await register()
// console.log(store.getters.getErrors.email)
// if (!store.getters.getErrors.email) {
// await savePlan()
// router.replace(route.query.to && route.query.to != '/register' ? String(route.query.to) : 'book-appointment')
// }
}
})
}
onBeforeMount(async () => {
products.forEach(product => {
if (product.is_prescription_required == 1) {
prescreptionRequired.value = true
} else {
prescreptionRequired.value = false
}
});
// grandTotal.value = parseFloat(totalAmount.value) + parseFloat(totalShipping.value)
store.dispatch('updateCurrentPage', 'pre-register')
localStorage.setItem('currentPage', 'pre-register')
})
onMounted(async () => {
window.addEventListener('resize', checkIfMobile);
let setting = await axios.post('/api/settings', {})
// console.log(setting.data)
settingSite.value = setting.data
seetingPlanLogo.value = '/assets/logo/' + setting.data.logo
// getLocation()
})
onUnmounted(() => {
});
const registerPatient = async () => {
errors.value = [];
store.dispatch('updateIsLoading', true)
isLoadingVisible.value = true;
await axios.post('/api/check-email', {
email: email.value,
}).then(r => {
console.log("Reigster", r.data);
if (r.data.email === "Exist") {
emailExist.value = true
canLogin.value = true
localStorage.setItem('exist', true)
} else {
localStorage.setItem('email', email.value)
localStorage.setItem('exist', false)
emailExist.value = false
canLogin.value = false
passwordCheck.value = true
if (password.value) {
localStorage.setItem('password', password.value)
router.replace(route.query.to && route.query.to != '/pre-register' ? String(route.query.to) : 'register')
}
}
store.dispatch('updateIsLoading', false)
}).catch(e => {
store.dispatch('updateIsLoading', false)
console.log(e.response);
const { errors: formErrors } = e.response.data.errors;
errors.value = e.response.data.errors;
isLoadingVisible.value = false;
console.error("Error", e.response.data.errors)
});
};
const loginPatient = async () => {
store.dispatch('updateIsLoading', true)
InvalidCredential.value = '';
isLoadingVisible.value = true;
axios.post('/api/login-patient', {
email: email.value,
password: password.value,
}).then(r => {
console.log("Response", r.data, prescreptionRequired.value);
let patientData = r.data.data;
localStorage.setItem('access_token', r.data.access_token)
localStorage.setItem('patient_id', patientData.id)
localStorage.setItem('user_role', 'patient')
localStorage.setItem('cominguser', 'register')
if (!patientData.dob || !patientData.gender || !patientData.marital_status || !patientData.height || !patientData.weight) {
// localStorage.setItem('profileCompleted', '0')
}
localStorage.setItem('isLogin', 'true')
localStorage.setItem('userAbilities', '[{"action":"manage","subject":"all"}]')
const userAbilities = [{ "action": "manage", "subject": "all" }];
ability.update(userAbilities)
// if (prescreptionRequired.value)
// router.replace(route.query.to && route.query.to != '/pre-register' ? String(route.query.to) : '/additional-information')
// else
router.replace(route.query.to && route.query.to != '/pre-register' ? String(route.query.to) : '/checkout')
}).catch(error => {
store.dispatch('updateIsLoading', false)
console.error('Login Error', error)
inavlid.value = true;
// if (error.response.data.message)
// errors.value.email = error.response.data.message;
// else
InvalidCredential.value = "Sorry, that email or password didn't work.";
// errors.value.email = 'Invalid credentials'
isLoadingVisible.value = false;
});
};
const isUserAuthenticate = () => {
user_role.value = localStorage.getItem('user_role');
if (user_role.value === 'agent') {
router.push('/provider/dashboard');
} else if (user_role.value === 'patient') {
router.push('/overview');
} else {
window.location.href = 'https://hgh.codelfi.com/';
};
};
const changeEmail = () => {
canLogin.value = false
InvalidCredential.value = null
password.value = null
};
const changeEmailSignUp = () => {
canLogin.value = false
passwordCheck.value = false
};
</script>
<template>
<StartOverPupup :showPopup="store.getters.getShowStartOverPupup"></StartOverPupup>
<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>
<!-- <HeaderTopBar></HeaderTopBar> -->
<!-- <div class="auth-wrapper d-flex align-center justify-center pa-4"> -->
<VRow><CustomNav :logo='seetingPlanLogo'></CustomNav></VRow>
<VRow
style="min-height: 101.7dvh; max-height: 101.7dvh; margin: 0px;"
:style="isMobile ? { marginTop: '90px' } : { marginTop: '0px' }"
>
<VCol cols="12" md="6" class="bg-custom col-order-1"
:class="isMobile ? '' : 'auth-wrapper d-flex align-center justify-center pa-4'">
<DefinedSteps></DefinedSteps>
</VCol>
<VCol cols="12" md="6" class="bg-custom-color col-order-2"
:class="isMobile ? '' : 'auth-wrapper d-flex align-center justify-center pa-4'">
<!-- <div class="auth-wrapper d-flex align-center justify-center pa-4"> -->
<VCard class="auth-card pa-2 rounded-5" style="" :class="isMobile ? '' : 'card-wid'">
<VCardItem class="py-2">
<VCardText class="p-0">
<!-- <VRow>
<VCol cols="12" lg="12" md="12" class="pb-4">
<div class="text-center mb-2 cursor-pointer"
style="width: 100%;position: relative;display: block; padding-top: 20px;">
<span class="text-center">
<VImg :src='seetingPlanLogo' width="250" height="50" class="logo-img"
@click="isUserAuthenticate" />
</span>
</div>
</VCol>
</VRow> -->
<h5 class="text-h5 mb-1 text-left">
Welcome! Need Our MD?
</h5>
<p v-if="canLogin">That's great, you've got an account! Welcome back!</p>
<p v-if="!canLogin">You'll need an account. Let get you setup!
</p>
</VCardText>
<VSnackbar v-model="store.getters.getIsTonalSnackbarVisible" :timeout="5000" location="top end"
variant="flat" color="red">
{{ store.getters.getErrorMessage }}
</VSnackbar>
</VCardItem>
<VCardText>
<VForm ref="refVForm" @submit.prevent="onSubmit">
<VRow>
<VCol cols="12" md="12" class="pb-0" v-if="canLogin">
<div class="float-right">
<span style="color: #b1c3d5;">This is not your email? </span><span><a
class=" cursor-pointer" @click="changeEmail()">(Change)</a></span>
</div>
</VCol>
<VCol cols="12" md="12" class="pb-0" v-if="passwordCheck">
<div class="float-right">
<span style="color: #b1c3d5;">This is not your email? </span><span><a
class=" cursor-pointer" @click="changeEmailSignUp()">(Change)</a></span>
</div>
</VCol>
<VCol cols="12" md="12" v-if="canLogin">
<VTextField v-model="email" label="Email Address" type="email"
:rules="[requiredEmail, emailValidator]" :error-messages="errors.email"
density="comfortable" :disabled="canLogin" />
</VCol>
<VCol cols="12" md="12" v-if="!canLogin">
<VTextField v-model="email" label="Email Address" type="email"
:rules="[requiredEmail, emailValidator]" :error-messages="errors.email"
density="comfortable" :disabled="passwordCheck" />
</VCol>
<VCol cols="12" v-if="canLogin">
<VTextField v-model="password" label="Password" placeholder="············"
:rules="[requiredPassword]" :type="isPasswordVisible ? 'text' : 'password'"
:append-inner-icon="isPasswordVisible ? 'bx-show' : 'bx-hide'"
@click:append-inner="isPasswordVisible = !isPasswordVisible" />
</VCol>
<VCol cols="12" class="pt-0 pb-0" v-if="canLogin">
<router-link to="/forgot" class="text-primary underline">Forgot Password?</router-link>
</VCol>
<VCol cols="12" v-if="passwordCheck">
<VTextField v-model="password" label="Password" placeholder="············"
:rules="[requiredPassword]" :type="isPasswordVisible ? 'text' : 'password'"
:append-inner-icon="isPasswordVisible ? 'bx-show' : 'bx-hide'"
@click:append-inner="isPasswordVisible = !isPasswordVisible" />
</VCol>
</VRow>
<VRow>
<VCol class="text-center" cols="12">
<p class="error-message" v-if="inavlid">{{ InvalidCredential }}</p>
<VBtn v-if="!canLogin" type="submit" class="text-center px-5"
style="background-color: rgb(var(--v-theme-yellow-theme-button)) !important;" block>
Continue
</VBtn>
<!-- Can Login and proceed -->
<VBtn v-if="canLogin" type="submit" class="text-center px-5"
style="background-color: rgb(var(--v-theme-yellow-theme-button)) !important;" block>
Continue
</VBtn>
</VCol>
</VRow>
<VRow>
</VRow>
</VForm>
</VCardText>
</VCard>
<!-- </div> -->
</VCol>
</VRow>
<!-- </div> -->
<!-- <Footer></Footer> -->
</template>
<style scoped>
@media only screen and (max-width: 768px) {
.card-wid {
max-width: 600px !important;
min-width: auto !important;
}
.col-order-1 {
order: 2;
}
.col-order-2 {
order: 1;
}
}
@media (min-width: 960px) and (max-width: 1200px){
.card-wid{
min-width: auto !important;
}
}
@media only screen and (min-width: 769px) {
.col-order-1 {
order: 1;
}
.col-order-2 {
order: 2;
}
}
.total-font {
font-size: 20px;
margin-bottom: 5px;
}
.bg-custom {
background: #F3F3F3;
}
.bg-custom-color {
background: #E0F0E3;
}
.bg-white bg-change-bk .current-plan {
border: 2px solid rgb(var(--v-theme-primary));
}
.cut-text {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
text-decoration: line-through;
text-decoration-color: red;
text-decoration-thickness: 1px;
}
.plan-card {
margin: 0rem;
margin-bottom: 0;
}
.card-wid {
min-width: 600px;
}
.layout-wrapper {
justify-content: center;
}
.error-message {
color: #ff2f2f;
font-size: 15px;
}
</style>
<style>
@import "@vendor/fonts/fontawesome.css";
@import "@vendor/fonts/tabler-icons.css";
@import "@vendor/css/rtl/core.css";
@import "@vendor/css/rtl/theme-default.css";
@import "@styles/css/demo.css";
@import "@vendor/libs/node-waves/node-waves.css";
@import "@vendor/css/pages/front-page-help-center.css";
@import "@vendor/css/pages/help-center-front-page.css";
.v-card.v-card--flat.v-theme--light.v-card--density-default.v-card--variant-elevated.text-center.search-header.rounded-0 {
block-size: 357px;
inset-block-start: -185px;
}
body {
display: block !important;
}
a.nav-link.fw-medium {
color: rgba(var(--v-theme-on-background), var(--v-high-emphasis-opacity));
}
.light-style .layout-navbar .menu-text {
color: #5d596c !important;
}
span.app-brand-text.demo.menu-text.fw-bold.ms-2.ps-1 {
color: #5d596c !important;
}
.navbar.landing-navbar {
border-color: rgba(255, 255, 255, 68%) !important;
background: white;
margin: 0;
border-radius: 0;
box-shadow: 0px 10px 10px #00000029;
}
.landing-footer .footer-top {
background-color: #1C5580;
border-radius: none !important;
/* background: url("/assets/img/front-pages/backgrounds/footer-bg-dark.png"); */
}
.footer-bottom.py-3 {
background-color: #282c3e;
}
.light-style .landing-footer .footer-link,
.light-style .landing-footer .footer-text {
color: #d3d4dc !important;
}
.light-style .landing-footer .footer-link,
.light-style .landing-footer .footer-text {
color: #d3d4dc;
}
a.footer-link {
color: #d3d4dc;
}
.light-style .landing-footer .footer-title {
color: #fff;
}
.footer-title {
color: #fff;
}
.footer-text {
color: #d3d4dc;
}
@media (max-width: 355px) {
.first-section-pt {
margin-block-start: -121px !important;
}
}
.logo-img {
display: block;
position: relative;
margin: 0 auto;
}
/* // @use "@core/scss/template/pages/page-auth.scss"; */
</style>

View File

@@ -0,0 +1,120 @@
<script setup>
import store from '@/store';
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);
onMounted(async () => {
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 handleDateInput = async () => {
getHistory.value = [];
await store.dispatch('getHistoryFilter', {
filter: selectedFilter.value,
})
getHistory.value = store.getters.getHistoryFilter.patients;
console.log("getHistoryFilter", getHistory.value);
store.dispatch('updateIsLoading', false)
}
const search = ref('');
const headers = [
{ align: 'start', key: 'name', title: 'Name' },
{ key: 'description', title: 'Description' },
{ key: 'price', title: 'Price' },
{ key: 'image', title: 'Image' },
{ key: 'status', title: 'Status' },
];
// const formattedAppointments = computed(() => {
// return getHistory.value.map(getHistory => ({
// ...getHistory,
// // patient_name: getFullName(appointment.first_name, appointment.last_name),
// }));
// });
function getFullName(firstName, lastName) {
// 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 firstName + ' ' + lastName; // For now, just return the first name
}
// const filteredDesserts = computed(() => {
// // Perform filtering based on the search term
// return getHistory.value.filter(history =>
// history.start_time.toLowerCase().includes(search.value.toLowerCase())
// );
// });
</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="Preceptions">
<v-card flat>
<v-card-title class="d-flex align-center pe-2">
<!-- <v-select label="April(Month to date)" v-model="selectedFilter" :items="filter"
item-title="value" item-value="key" @update:modelValue="handleDateInput"></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="getHistory">
</v-data-table>
</v-card>
</VCard>
</VCol>
</VRow>
</template>

View File

@@ -0,0 +1,93 @@
<script setup>
import Footer from '@/views/pages/home/Footer.vue';
import HeaderTopBar from '@/views/pages/home/HeaderTopBar.vue';
import PrivacyPolicy from '@/views/pages/home/privacy-policy.vue';
</script>
<template>
<HeaderTopBar></HeaderTopBar>
<PrivacyPolicy></PrivacyPolicy>
<Footer></Footer>
</template>
<style>
@import "@vendor/fonts/fontawesome.css";
@import "@vendor/fonts/tabler-icons.css";
@import "@vendor/css/rtl/core.css";
@import "@vendor/css/rtl/theme-default.css";
@import "@styles/css/demo.css";
@import "@vendor/libs/node-waves/node-waves.css";
@import "@vendor/css/pages/front-page-help-center.css";
@import "@vendor/css/pages/help-center-front-page.css";
.v-card.v-card--flat.v-theme--light.v-card--density-default.v-card--variant-elevated.text-center.search-header.rounded-0 {
block-size: 357px;
inset-block-start: -185px;
}
body {
display: block !important;
}
a.nav-link.fw-medium {
color: rgba(var(--v-theme-on-background), var(--v-high-emphasis-opacity));
}
.light-style .layout-navbar .menu-text {
color: #5d596c !important;
}
span.app-brand-text.demo.menu-text.fw-bold.ms-2.ps-1 {
color: #5d596c !important;
}
.navbar.landing-navbar {
border-color: rgba(255, 255, 255, 68%) !important;
border-radius: 0;
margin: 0;
background: white;
box-shadow: 0 10px 10px #00000029;
}
.landing-footer .footer-top {
border-radius: none !important;
background-color: #1c5580;
/* background: url("/assets/img/front-pages/backgrounds/footer-bg-dark.png"); */
}
.footer-bottom.py-3 {
background-color: #282c3e;
}
.light-style .landing-footer .footer-link,
.light-style .landing-footer .footer-text {
color: #d3d4dc !important;
}
.light-style .landing-footer .footer-link,
.light-style .landing-footer .footer-text {
color: #d3d4dc;
}
a.footer-link {
color: #d3d4dc;
}
.light-style .landing-footer .footer-title {
color: #fff;
}
.footer-title {
color: #fff;
}
.footer-text {
color: #d3d4dc;
}
@media (max-width: 355px) {
.first-section-pt {
margin-block-start: -121px !important;
}
}
</style>

View File

@@ -0,0 +1,364 @@
<script setup>
import StartOverPupup from '@/views/pages/home/StartOverPupup.vue';
import axios from '@axios';
import {
cardNumberValidator,
cvvValidator,
expiryValidator,
requiredValidator
} from '@validators';
import moment from 'moment';
import { onMounted } from 'vue';
import { useRoute, useRouter } from 'vue-router';
import { useStore } from 'vuex';
const store = useStore()
const router = useRouter()
const route = useRoute()
const isMobile = ref(window.innerWidth <= 768); // Assuming mobile width is less than or equal to 768px
const patient_id = localStorage.getItem('patient_id')
const access_token = store.access_token;
const seetingPlanLogo = ref();
const isLoadingVisible = ref(false)
const paymentForm = ref()
const cardNumber = ref('')
const expiry = ref('')
const cvv = ref('')
const zipcode = ref('')
const firstName = ref('')
const lastName = ref('')
const email = ref('');
const phoneNumber = ref('');
const address = ref('');
const city = ref('');
const state = ref('');
const zip_code = ref('');
const country = ref('');
const dob = ref('')
const gender = ref('')
const marital_status = ref('')
const height = ref('')
const weight = ref('')
const scheduleDate = ref('');
const scheduleTime = ref('');
const timeZone = ref('');
const timeDifference = ref();
const shippingAddress = ref();
const todayDate = ref()
const planName = ref('')
const planAmount = ref('')
const myPlanName = ref(localStorage.getItem('plan_name'))
const myPlanPrice = ref(localStorage.getItem('plan_price'))
const myPlanTitle1 = ref(localStorage.getItem('list_sub_title'))
const myPlanTitle2 = ref(localStorage.getItem('list_one_title'))
const myPlanTitle3 = ref(localStorage.getItem('list_two_title'))
const myPlanImage = ref(localStorage.getItem('plan_image'))
const currentPath = window.location.origin
onBeforeMount(async () => {
store.dispatch('updateIsLoading', true)
store.dispatch('updateCurrentPage', 'process-payment')
localStorage.setItem('currentPage', 'process-payment')
await store.dispatch('getPatientInfo')
await store.dispatch('getPlanInfo')
await store.dispatch('getPatientAppointment')
await store.dispatch('getAdditionalInformation')
planName.value = store.getters.getPatientPlan.plan_name
planAmount.value = store.getters.getPatientPlan.plan_amount
firstName.value = store.getters.getPatient.first_name;
lastName.value = store.getters.getPatient.last_name
email.value = store.getters.getPatient.email
phoneNumber.value = store.getters.getPatient.phone_no
dob.value = store.getters.getPatient.dob
gender.value = store.getters.getPatient.gender
marital_status.value = store.getters.getPatient.marital_status
weight.value = store.getters.getPatient.weight
height.value = store.getters.getPatient.height
address.value = store.getters.getShippingInformation.shipping_address1
city.value = store.getters.getShippingInformation.shipping_city
state.value = store.getters.getShippingInformation.shipping_state
zip_code.value = store.getters.getShippingInformation.shipping_zipcode
country.value = store.getters.getShippingInformation.billing_address1
shippingAddress.value = (address.value ? address.value + ', ' : '') +
(city.value ? city.value + ', ' : '') +
(state.value ? state.value + ' ' : '') +
(zip_code.value ? zip_code.value + ', ' : '') +
(country.value ? country.value : '');
let diffInMinutes = store.getters.getTimeDiff.time_diff;
// scheduleTime.value = appointmentData.appointment_time;
const time = moment(store.getters.getBookedAppointment.appointment_time, 'HH:mm:ss');
// Check if the time is AM or PM
scheduleTime.value = time.format('HH:mm a');
timeZone.value = store.getters.getBookedAppointment.timezone;
timeDifference.value = diffInMinutes;
// console.log(gender.value);
let dateString = store.getters.getPatient.dob
let parts = dateString.split("-");
dob.value = `${parts[1]}-${parts[2]}-${parts[0]}`;
const appointment_date = new Date(store.getters.getBookedAppointment.appointment_date);
const formattedDate = new Intl.DateTimeFormat('en-US', {
year: 'numeric',
month: 'long',
day: 'numeric',
}).format(appointment_date);
scheduleDate.value = formattedDate;
store.dispatch('updateIsLoading', false)
})
onMounted(async () => {
todayDate.value = moment().format('MM-DD-YYYY');
window.addEventListener('resize', checkIfMobile);
let setting = await axios.post('/api/settings', {})
console.log(setting.data)
seetingPlanLogo.value = '/assets/logo/' + setting.data.logo
})
// Detach event listener on component unmount
onUnmounted(() => {
window.removeEventListener('resize', checkIfMobile);
});
const checkIfMobile = () => {
isMobile.value = window.innerWidth <= 768;
};
const cardNumberFormat = () => {
cardNumber.value = cardNumber.value.replace(/\D/g, '').substring(0, 16);
}
const formatExpiry = () => {
// Automatically format the input to MM/YY format
expiry.value = expiry.value.replace(/\D/g, '').slice(0, 4).replace(/(\d{2})(\d{2})/, '$1/$2');
}
const handleCVVInput = () => {
// Remove non-digit characters from input
cvv.value = cvv.value.replace(/\D/g, '');
}
const validatePayment = async () => {
const { valid: isValid } = await paymentForm.value?.validate();
console.log('isValid ', isValid);
if (isValid) {
await processPayment()
router.replace(route.query.to && route.query.to != '/process-payment' ? String(route.query.to) : '/thankyou')
}
};
const processPayment = async () => {
await store.dispatch('processPayment')
}
const imageSrc = (src) => {
return `${currentPath}/product/${src}`
};
const backFun = () => {
store.dispatch('updateIsLoading', true)
router.replace(route.query.to && route.query.to != '/process-payment' ? String(route.query.to) : '/checkout')
}
</script>
<template>
<StartOverPupup :showPopup="store.getters.getShowStartOverPupup"></StartOverPupup>
<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 class="pb-sm-5 pb-2 rounded-top">
<!-- <VContainer> -->
<div class="auth-wrapper d-flex align-center justify-center pa-4 mt-4" style="flex-direction: column;">
<h3 class="mb-8" style="max-width: 600px;width: 100%">Review your treatment and pay</h3>
<VCard class="auth-card pa-2 rounded-3 mt-1 pb-4 pt-4" style="max-width: 600px;width: 100%;">
<p class="mb-0" style="font-family: system-ui;">$0 Due Now - Pay Upon Medical Approval</p>
</VCard>
<h5 class="mt-8" style="max-width: 600px;width: 100%">Your Plan</h5>
<VCard class="auth-card pa-2 rounded-3 mt-1 pb-2" style="max-width: 600px;width: 100%">
<v-row>
<v-col cols="12" md="3">
<v-img :src="imageSrc(myPlanImage)" alt="Selected Item" width="200px" height="150px"></v-img>
</v-col>
<v-col cols="12" md="9">
<v-card-title class="pb-0">
<div style="font-size: 14px;">
<strong>{{ myPlanName }}</strong>
<span class="float-right"><strong>${{ myPlanPrice }}</strong></span>
</div>
</v-card-title>
<v-card-subtitle class="pt-0">0.25mg</v-card-subtitle>
<v-card-text class="pt-0 pb-0">
<p>1ml x 1 Month</p>
</v-card-text>
</v-col>
</v-row>
<v-list dense>
<v-list-item style="border-top: 1px solid #c0c0c075;">
<v-list-item-content>
<v-list-item-title>
<span>{{ myPlanTitle1 }}</span>
<span class="float-right"><b>Included</b></span>
</v-list-item-title>
</v-list-item-content>
</v-list-item>
<v-list-item style="border-top: 1px solid #c0c0c075;">
<v-list-item-content>
<v-list-item-title>
<span>{{ myPlanTitle2 }}</span>
<span class="float-right"><b>Included</b></span>
</v-list-item-title>
</v-list-item-content>
</v-list-item>
<v-list-item style="border-top: 1px solid #c0c0c075;">
<v-list-item-content>
<v-list-item-title>
<span>{{ myPlanTitle3 }}</span>
</v-list-item-title>
</v-list-item-content>
</v-list-item>
<!-- Add more features as needed -->
</v-list>
</VCard>
<h5 class="mt-8" style="max-width: 600px;width: 100%">Payment</h5>
<VCard class="auth-card pa-2 rounded-3 mt-1 pb-2" style="max-width: 600px;width: 100%">
<h4 class="mb-2 mt-2">
<VIcon>mdi-credit-card</VIcon> Card
</h4>
<VForm ref="paymentForm" @submit.prevent="() => { }">
<VRow>
<VCol cols="12" lg="12" md="12">
<VRow>
<VCol cols="12" lg="12" md="12">
<VTextField v-model="cardNumber" label="Credit Card Number*"
:rules="[requiredValidator, cardNumberValidator]" placeholder="xxxxxxxxxxxxxxxx"
@input="cardNumberFormat" density="comfortable" />
</VCol>
<!-- <VCol cols="12" lg="4" md="4">
<VTextField v-model="zipcode" label="Zipcode*" type="number"
:rules="[requiredValidator]" placeholder="zipcode" density="comfortable"/>
</VCol> -->
<VCol cols="12" lg="6" md="6">
<VTextField v-model="expiry" label="Expiration Date*"
:rules="[requiredValidator, expiryValidator]" placeholder="MM/YY"
@input="formatExpiry" density="comfortable" />
</VCol>
<VCol cols="12" lg="6" md="6">
<VTextField v-model="cvv" :rules="[requiredValidator, cvvValidator]" label="CVV*"
maxlength="3" @input="handleCVVInput" density="comfortable" />
</VCol>
</VRow>
</VCol>
<!-- <VCol cols="12" lg="6" md="6">
<svg v-if="!isMobile" xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="auto" height="150"
viewBox="0 0 256 256" xml:space="preserve">
<defs>
</defs>
<g style="stroke: none; stroke-width: 0; stroke-dasharray: none; stroke-linecap: butt; stroke-linejoin: miter; stroke-miterlimit: 10; fill: none; fill-rule: nonzero; opacity: 1;"
transform="translate(1.4065934065934016 1.4065934065934016) scale(2.81 2.81)">
<path
d="M 81.159 25.776 l -0.9 -10.05 c -0.101 -1.123 -1.092 -1.952 -2.215 -1.851 L 1.86 20.698 c -1.123 0.101 -1.952 1.092 -1.851 2.215 l 4.128 46.09 c 0.101 1.123 1.092 1.952 2.215 1.851 l 3.076 -0.276 L 81.159 25.776 z"
style="stroke: none; stroke-width: 1; stroke-dasharray: none; stroke-linecap: butt; stroke-linejoin: miter; stroke-miterlimit: 10; fill: rgb(176,182,188); fill-rule: nonzero; opacity: 1;"
transform=" matrix(1 0 0 1 0 0) " stroke-linecap="round" />
<path
d="M 9.428 74.189 c 0 1.074 0.871 1.944 1.944 1.944 h 76.683 c 1.074 0 1.944 -0.871 1.944 -1.944 V 49.73 c -26.255 -4.947 -53.138 -4.734 -80.572 0 V 74.189 z"
style="stroke: none; stroke-width: 1; stroke-dasharray: none; stroke-linecap: butt; stroke-linejoin: miter; stroke-miterlimit: 10; fill: rgb(64,89,107); fill-rule: nonzero; opacity: 1;"
transform=" matrix(1 0 0 1 0 0) " stroke-linecap="round" />
<path
d="M 88.056 25.776 H 11.372 c -1.074 0 -1.944 0.871 -1.944 1.945 v 7.719 c 26.857 6.175 53.715 6.175 80.572 0 v -7.719 C 90 26.647 89.129 25.776 88.056 25.776 z"
style="stroke: none; stroke-width: 1; stroke-dasharray: none; stroke-linecap: butt; stroke-linejoin: miter; stroke-miterlimit: 10; fill: rgb(64,89,107); fill-rule: nonzero; opacity: 1;"
transform=" matrix(1 0 0 1 0 0) " stroke-linecap="round" />
<rect x="9.43" y="35.44" rx="0" ry="0" width="80.57" height="14.29"
style="stroke: none; stroke-width: 1; stroke-dasharray: none; stroke-linecap: butt; stroke-linejoin: miter; stroke-miterlimit: 10; fill: rgb(242,242,242); fill-rule: nonzero; opacity: 1;"
transform=" matrix(1 0 0 1 0 0) " />
<path
d="M 51.045 60.484 H 19.469 c -0.552 0 -1 -0.447 -1 -1 s 0.448 -1 1 -1 h 31.576 c 0.553 0 1 0.447 1 1 S 51.598 60.484 51.045 60.484 z"
style="stroke: none; stroke-width: 1; stroke-dasharray: none; stroke-linecap: butt; stroke-linejoin: miter; stroke-miterlimit: 10; fill: rgb(242,242,242); fill-rule: nonzero; opacity: 1;"
transform=" matrix(1 0 0 1 0 0) " stroke-linecap="round" />
<path
d="M 37.707 68.001 H 19.469 c -0.552 0 -1 -0.447 -1 -1 s 0.448 -1 1 -1 h 18.238 c 0.552 0 1 0.447 1 1 S 38.259 68.001 37.707 68.001 z"
style="stroke: none; stroke-width: 1; stroke-dasharray: none; stroke-linecap: butt; stroke-linejoin: miter; stroke-miterlimit: 10; fill: rgb(242,242,242); fill-rule: nonzero; opacity: 1;"
transform=" matrix(1 0 0 1 0 0) " stroke-linecap="round" />
<path
d="M 51.045 68.001 h -8.574 c -0.552 0 -1 -0.447 -1 -1 s 0.448 -1 1 -1 h 8.574 c 0.553 0 1 0.447 1 1 S 51.598 68.001 51.045 68.001 z"
style="stroke: none; stroke-width: 1; stroke-dasharray: none; stroke-linecap: butt; stroke-linejoin: miter; stroke-miterlimit: 10; fill: rgb(242,242,242); fill-rule: nonzero; opacity: 1;"
transform=" matrix(1 0 0 1 0 0) " stroke-linecap="round" />
<path
d="M 81.153 58.484 H 65.638 c -0.553 0 -1 0.447 -1 1 v 7.517 c 0 0.553 0.447 1 1 1 h 15.516 c 0.553 0 1 -0.447 1 -1 v -7.517 C 82.153 58.932 81.706 58.484 81.153 58.484 z"
style="stroke: none; stroke-width: 1; stroke-dasharray: none; stroke-linecap: butt; stroke-linejoin: miter; stroke-miterlimit: 10; fill: rgb(242,242,242); fill-rule: nonzero; opacity: 1;"
transform=" matrix(1 0 0 1 0 0) " stroke-linecap="round" />
</g>
</svg>
</VCol> -->
</VRow>
<div class="text-center mb-2 mt-2">
<VBtn type="submit" class="px-4 mt-4 mb-2" color="primary" variant="flat"
@click="validatePayment" style="background-color: rgb(var(--v-theme-yellow)) !important;"
block>
Confirm</VBtn>
<VBtn class="px-4" color="grey" variant="flat" @click="backFun" :class="isMobile ? '' : 'mr-2'"
block>
Back</VBtn>
</div>
</VForm>
</VCard>
</div>
<!-- </VContainer> -->
</div>
</template>
<style lang="scss">
.logo-img {
display: block;
position: relative;
margin: 0 auto;
}
.card-title {
font-family: "Public Sans", -apple-system, BlinkMacSystemFont, "Segoe UI", "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif;
}
.v-list-item-title {
white-space: inherit !important;
;
}
.v-list {
min-height: 176px;
}
.no-border {
border: none !important;
}
td {
border: none !important;
}
th {
border: none !important;
}
.cut-text {
white-space: nowrap;
/* Prevents text from wrapping */
overflow: hidden;
/* Hides any content that overflows the container */
text-overflow: ellipsis;
/* Displays an ellipsis (...) to represent the clipped text */
// width: 100px;
text-decoration: line-through;
text-decoration-color: red;
/* Set the color of the line */
text-decoration-thickness: 1px;
/* Adjust the width as needed */
}
</style>../store1.js

View File

@@ -0,0 +1,47 @@
<script setup>
import ProfileInfo from '@/views/pages/profile/profile-info.vue';
import { useRoute } from 'vue-router';
const route = useRoute()
const activeTab = ref('info')
const skill = ref(20)
const knowledge = ref(33)
const power = ref(78)
// tabs
const tabs = [
{
title: 'Profile',
tab: 'info',
},
]
</script>
<template>
<div>
<VTabs v-model="activeTab" show-arrows>
<VTab v-for="item in tabs" :value="item.tab">
<VIcon size="20" icon="mdi-account" start />
{{ item.title }}
</VTab>
</VTabs>
<VDivider />
<!-- <div class="demo-space-y mt-3">
<VProgressLinear v-model="skill" color="primary" class="rounded" height="20">
<template #default="{ value }">
<strong>{{ Math.ceil(value) }}% profile complete</strong>
</template>
</VProgressLinear>
</div> -->
<VWindow v-model="activeTab" class="mt-5 disable-tab-transition">
<!-- Account -->
<VWindowItem value="info">
<ProfileInfo />
</VWindowItem>
</VWindow>
</div>
</template>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,235 @@
<script setup>
import store from '@/store';
import moment from 'moment';
import { ref } from 'vue';
import { useRoute, useRouter } from 'vue-router';
const router = useRouter()
const route = useRoute()
const getHistory = ref([]);
const doctorAppiontments = ref([]);
const selectedFilter = ref(null);
const currentMonth = ref('');
onMounted(async () => {
currentMonth.value = moment().format('MMMM') + '(Month to date)';
await store.dispatch('getHistory')
getHistory.value = store.getters.getHistory.history;
console.log("getHistory", getHistory.value);
store.dispatch('updateIsLoading', false)
})
const filter = [
{
value: 'This Month',
key: 'current_month'
},
{
value: 'Past Month',
key: '1_month'
},
{
value: 'Past 2 Month from today',
key: '2_months'
},
{
value: 'Past 3 Month from today',
key: '3_months'
},
{
value: 'Past 6 Month from today',
key: '6_months'
},
{
value: 'Past 12 Month from today',
key: '12_months'
},
{
value: 'All Time',
key: 'all_time'
},
];
const patient_name = ref();
const patient_email = ref();
const handleDateInput = async () => {
getHistory.value = [];
await store.dispatch('getHistoryFilter', {
filter: selectedFilter.value,
})
getHistory.value = store.getters.getHistoryFilter.patients;
// patient_name.value = store.getters.getHistoryFilter.patients.patient;
// patient_email.value = store.getters.getHistoryFilter.patients.patient;
console.log("getHistoryFilter", getHistory.value);
store.dispatch('updateIsLoading', false)
}
const search = ref('');
const headers = [
{ align: 'start', key: 'order_id', title: 'Order' },
{ align: 'start', key: 'id', title: 'Appiontment' },
{ key: 'appointment_date', sortable: false, title: 'Date' },
{ key: 'patient_name', title: 'Patient' },
// { key: 'start_time', title: 'Start Time' },
// { key: 'end_time', title: 'End Time' },
// { key: 'duration', title: 'Duration' },
{ key: 'action', title: 'Action' },
];
const formattedHistory = computed(() => {
return getHistory.value.map(history => ({
...history,
appointment_date: changeFormat(history.appointment_date),
start_time: changeDateFormat(history.start_time),
end_time: changeDateFormat(history.end_time),
duration: totalCallDuration(history.start_time, history.end_time),
}));
});
function changeDateFormat(dateFormat) {
console.log("startTimeFormat", dateFormat);
if (dateFormat) {
const [datePart, timePart] = dateFormat.split(' '); // Split date and time parts
const [year, month, day] = datePart.split('-'); // Split date into year, month, and day
const formattedMonth = parseInt(month).toString(); // Convert month to integer and then string to remove leading zeros
const formattedDay = parseInt(day).toString(); // Convert day to integer and then string to remove leading zeros
const formattedDate = `${formattedMonth}-${formattedDay}-${year}`; // Format date as mm-dd-yyyy
return `${formattedDate} ${timePart}`; // Combine formatted date with original time part
}
}
function changeFormat(dateFormat) {
const dateParts = dateFormat.split('-'); // Assuming date is in yyyy-mm-dd format
const year = parseInt(dateParts[0]);
const month = parseInt(dateParts[1]); // No need for padding
const day = parseInt(dateParts[2]); // No need for padding
// Create a new Date object with the parsed values
const date = new Date(year, month - 1, day); // Month is zero-based in JavaScript Date object
// Format the date as mm-dd-yyyy
const formattedDate = month + '-' + day + '-' + date.getFullYear();
return formattedDate;
}
// function changeFormat(dateFormat) {
// if (dateFormat) {
// const dateParts = dateFormat.split('-'); // Assuming date is in yyyy-mm-dd format
// const year = parseInt(dateParts[0]);
// const month = String(dateParts[1]).padStart(2, '0'); // Pad single-digit months with leading zero
// const day = String(dateParts[2]).padStart(2, '0'); // Pad single-digit days with leading zero
// // Create a new Date object with the parsed values
// const date = new Date(year, month - 1, day); // Month is zero-based in JavaScript Date object
// // Format the date as mm-dd-yyyy
// const formattedDate = month + '-' + day + '-' + date.getFullYear();
// return formattedDate;
// }
// }
function totalCallDuration(start_time, end_time) {
console.log(start_time, end_time);
const startMoment = moment(start_time);
const endMoment = moment(end_time);
// Calculate the duration
const duration = moment.duration(endMoment.diff(startMoment));
const hours = duration.hours();
const thours = `${String(hours).padStart(2, '0')}`;
const minutes = duration.minutes();
const tminutes = `${String(minutes).padStart(2, '0')}`;
const seconds = duration.seconds();
const tsecond = `${String(seconds).padStart(2, '0')}`;
let durationText;
if (hours === 0 && minutes === 0) { //for second
durationText = ` 00:00:${tsecond}`;
} else if (hours === 0 && minutes > 0) { //for minutes
durationText = `00:${tminutes}:${tsecond}`;
} else if (hours > 0) { //for hours
durationText = `${thours}:${tminutes}:${tsecond}`;
}
const totalDuration = durationText;
console.log('Duration:', durationText);
// You may need to adjust this function based on your actual data structure
// For example, if you have separate first name and last name properties in each appointment object
return totalDuration; // For now, just return the first name
}
const historyDetail = (item) => {
console.log('item', item)
router.push('/provider/order-detail/' + item.order_id);
}
</script>
<template>
<VRow>
<VDialog v-model="store.getters.getIsLoading" width="110" height="150" color="primary">
<VCardText class="" style="color: white !important;">
<div class="demo-space-x">
<VProgressCircular :size="40" color="primary" indeterminate />
</div>
</VCardText>
</VDialog>
<VCol cols="12">
<VCard title="Meeting History">
<v-card flat>
<v-card-title class="d-flex align-center pe-2">
<!--<v-select label="Search" v-model="selectedFilter" :items="filter" item-title="value"
item-value="key" @update:modelValue="handleDateInput" density="compact"></v-select>
<v-text-field v-model="search" prepend-inner-icon="mdi-magnify" density="compact" label="Search"
single-line flat hide-details variant="solo-filled"></v-text-field> -->
<v-spacer></v-spacer>
<v-spacer></v-spacer>
<v-spacer></v-spacer>
</v-card-title>
<v-data-table :headers="headers" :items="formattedHistory">
<template #item.order_id="{ item }">
<RouterLink :to="{ name: 'provider-order-detail', params: { id: item.order_id } }">
#{{ item.order_id }}
</RouterLink>
</template>
<template #item.patient_name="{ item }">
<div class="d-flex align-center">
<VAvatar size="32" :color="item.profile_picture ? '' : 'primary'"
:class="item.profile_picture ? '' : 'v-avatar-light-bg primary--text'"
:variant="!item.profile_picture ? 'tonal' : undefined">
<VImg v-if="item.profile_picture" :src="item.profile_picture" />
<span v-else>{{ item.patient_name.charAt(0) }}</span>
</VAvatar>
<div class="d-flex flex-column ms-3">
<RouterLink :to="{ name: 'provider-order-detail', params: { id: item.id } }">
<div class=" font-weight-medium">
{{ item.patient_name }}
</div>
</RouterLink>
<small>{{ item.patient_email }}</small>
</div>
</div>
</template>
<template #item.action="{ item }">
<IconBtn size="small">
<VIcon icon="ri-more-2-line" />
<VMenu activator="parent">
<VList>
<VListItem value="view">
<RouterLink
:to="{ name: 'provider-order-detail', params: { id: item.order_id } }"
class="text-high-emphasis">
View
</RouterLink>
</VListItem>
</VList>
</VMenu>
</IconBtn>
</template>
</v-data-table>
</v-card>
</VCard>
</VCol>
</VRow>
</template>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,115 @@
<script setup>
import { onMounted, onUnmounted } from 'vue';
import { useStore } from 'vuex';
const store = useStore()
const isDesktop = ref(true)
const categories = ref([]);
const isMobile = ref(window.innerWidth <= 768);
const answers = ref([]);
onBeforeMount(async () => {
store.dispatch('updateCurrentPage', '/provider/telehealth')
console.log('current pt', store.getters.getCurrentCallPatient)
await store.dispatch('getAgentCategory')
categories.value = store.getters.getCategories.data;
console.log("categories", categories.value);
});
const updateCategory = async (id, name) => {
// answers.value.push({ category_id:id})
await store.dispatch('setSelectCategoryId', true);
localStorage.setItem('category_id', id)
localStorage.setItem('category_name', name)
store.dispatch('updateIsLoading', false)
}
onMounted(async () => {
window.addEventListener('resize', checkIfMobile);
})
onUnmounted(() => {
window.removeEventListener('resize', checkIfMobile);
});
const checkIfMobile = () => {
isMobile.value = window.innerWidth <= 768;
};
</script>
<template>
<div class="auth-wrapper d-flex align-center justify-center">
<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 class="auth-card pt-0 rounded-5" max-width="350">
<VRow>
<VCol cols="12" class="p-0">
<div v-for="category in categories" @click="updateCategory(category.id, category.name)"
:key="category">
<RouterLink :to="'/provider/questionere/' + category.category_link"
v-if="category.name != 'Family history'">
<VCard :width="isMobile ? '360' : '500'" class="mb-2">
<VRow class="justify-space-between pb-2 px-4 pt-4">
<Vcol col="auto" style="padding:11px 15px;">
<div class="d-flex flex-wrap gap-0 pb-2">
<img :src="'/assets/images/asthma/' + category.icon"
class=" mx-3 ml-0 category-icon" width="24" height="24">
<!-- <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24"
viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
stroke-linecap="round" stroke-linejoin="round"
class="mx-3 ml-0 icon icon-tabler icons-tabler-outline icon-tabler-users-group category-icon">
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
<path d="M10 13a2 2 0 1 0 4 0a2 2 0 0 0 -4 0" />
<path d="M8 21v-1a2 2 0 0 1 2 -2h4a2 2 0 0 1 2 2v1" />
<path d="M15 5a2 2 0 1 0 4 0a2 2 0 0 0 -4 0" />
<path d="M17 10h2a2 2 0 0 1 2 2v1" />
<path d="M5 5a2 2 0 1 0 4 0a2 2 0 0 0 -4 0" />
<path d="M3 13v-1a2 2 0 0 1 2 -2h2" />
</svg> -->
<h4>{{ category.name }}</h4>
</div>
</Vcol>
<Vcol col="auto">
<div class="flex-wrap gap-0 pt-3 pb-2">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24"
viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
stroke-linecap="round" stroke-linejoin="round"
class="icon icon-tabler icons-tabler-outline icon-tabler-chevron-right">
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
<path d="M9 6l6 6l-6 6" />
</svg>
</div>
</Vcol>
</VRow>
<div class="d-flex align-items-center">
<VProgressLinear model-value="100" height="2" color="success" :rounded="true" />
</div>
</VCard>
</RouterLink>
</div>
</VCol>
</VRow>
</div>
</div>
</template>
<style lang="scss">
@media (min-width: 992px) {
.category-cards {
max-width: 500px !important;
}
}
@media (max-width: 991px) {
.category-cards {
max-width: 300px !important;
}
}
.category-icon {
color: rgb(105, 108, 255);
}
</style>

View File

@@ -0,0 +1,25 @@
<script setup>
// import cardiologyFormData from '@/views/pages/questionere/cardiology-form';
import cardiologyCategory from '@/views/pages/questionere/form-category';
import formview from '@/views/pages/questionere/form.vue';
import { useRoute, useRouter } from 'vue-router';
const router = useRouter()
const route = useRoute()
const categoryName = route.params.categoryLink;
const currentForm = cardiologyCategory[categoryName.replace(/[\s_]/g, '')]
</script>
<template>
<VContainer>
<VRow>
<VCol cols="12" md="2"></VCol>
<VCol cols="12" md="8">
<VCard class="px-2 py-2">
<formview :questionCategory="categoryName" :steps="currentForm.steps" :schema="currentForm.schema"
:newForm="false">
</formview>
</VCard>
</VCol>
</VRow>
</VContainer>
</template>

View File

@@ -0,0 +1,14 @@
<script setup>
import UsersLists from '@/views/pages/queue/queue-users.vue';
</script>
<template>
<VRow>
<VCol cols="12">
<VCard title="Queue Users">
<UsersLists />
</VCard>
</VCol>
</VRow>
</template>

View File

@@ -0,0 +1,463 @@
<script setup>
import videoCameraOff from '@images/svg/video-camera-off.svg';
import Echo from 'laravel-echo';
import Pusher from 'pusher-js';
import { computed, nextTick, onMounted, onUnmounted, ref } from 'vue';
import { useRoute, useRouter } from 'vue-router';
import { useStore } from 'vuex';
const store = useStore()
const isLoadingVisible = ref(false);
const getIsTonalSnackbarVisible = ref(false);
const errorMessage = ref(null);
const videoElement = ref(null);
const isCameraEnable = ref(false);
const isMicrophoneEnable = ref(false);
const isCameraPermissionGranted = ref(false);
const isMicrophonePermissionGranted = ref(false);
const isCameraDisabled = ref(false);
const isMuted = ref(false);
const mediaStream = ref(null);
const enableCamBtn = ref(true)
const permissionModel = ref(false)
store.dispatch('updateCurrentPage', '/queue')
const camIcon = computed(() => {
return isCameraDisabled.value ? 'mdi-video-off' : 'mdi-video';
});
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 router = useRouter()
const route = useRoute()
const queueUsers = ref([]);
const isDialogVisible = ref(true);
const patientId = ref(localStorage.getItem('patient_id'));
const currentQueue = computed(() => {
return queueUsers.value.findIndex(user => user.id == localStorage.getItem('patient_id')) + 1
});
const currentRoute = route.path;
console.log('currentRoute', currentRoute)
onMounted(async () => {
console.log('store mic cam permissions', store.getters.getIsMicEnabled, store.getters.getIsCamEnabled)
// permissionModel.value = true
// await checkPermissions();
// try {
// const camera = await navigator.permissions.query({ name: 'camera' });
// const microphone = await navigator.permissions.query({ name: 'microphone' });
// if (camera.state === 'granted') isCameraEnable.value = true;
// if (microphone.state === 'granted') isMicrophoneEnable.value = true;
// console.log(camera.state, microphone.state); // 'granted', 'denied', or 'prompt'
// } catch (error) { }
await checkMicCamPermissions();
window.Pusher = Pusher;
const key = 'bc8bffbbbc49cfa39818';
const cluster = 'mt1';
let echo = new Echo({
broadcaster: 'pusher',
key: key,
cluster: cluster,
forceTLS: true,
auth: {
headers: {
Authorization: 'Bearer ' + localStorage.getItem('access_token'),
},
},
});
//presence channel ((user bio show))
const presenceChannel = echo.join(`dhkjkiplqe84sdaqf17nqg`)
window.presenceChannel = presenceChannel
presenceChannel.here((users) => { //existing user/ including current user
// queueUsers.value = users.filter(user => user.type == "patient").sort((a, b) => a.time - b.time);
// queueUsers.value.forEach(user => {
// user.mic = isMicrophoneEnable.value;
// });
// console.log(queueUsers.value);
presenceChannel.whisper('DeviceCurrentStatus', {
mic: isMicrophoneEnable.value,
cam: isCameraEnable.value,
})
})
.joining((user) => {
//new user
console.log('joining', user.name);
queueUsers.value.push(user);
queueUsers.value.sort((a, b) => a.time - b.time);
console.log('queueUsers.value', queueUsers.value);
})
.leaving((user) => {
console.log('leaving', user);
queueUsers.value = queueUsers.value.filter(u => u.id !== user.id);
console.log("CurrentUser", queueUsers);
})
.listenForWhisper('getPermission-' + patientId.value, (e, metadata) => {
console.log('...', e, metadata)
presenceChannel.whisper('DeviceCurrentStatus-' + metadata.user_id, {
mic: store.getters.getIsMicEnabled,
cam: store.getters.getIsCamEnabled,
})
})
.error((error) => {
console.error(error);
});
// (private channel)
echo.private('patient-' + patientId.value)
.listen('AppointmentCreated', (e) => {
console.log("Patient AppointmentCreatedTest", e);
echo.leave('dhkjkiplqe84sdaqf17nqg')
localStorage.setItem('meeting_id', e.meeting_id);
localStorage.setItem('call_type', e.call_type);
console.log('Patient_meetingId', e.meeting_id);
if (!store.getters.getIsMicEnabled && !store.getters.getIsCamEnabled) {
permissionModel.value = true
} else {
router.push('/telehealth');
}
});
echo.private('patient-end-call-' + patientId.value)
.listen('AppointmentCallEnded', (e) => {
console.log("AppointmentCallEnded", e);
localStorage.setItem('call-end-message', 'Password Sent to Email Address')
router.push('/overview');
// echo.leave('dhkjkiplqe84sdaqf17nqg')
});
});
onUnmounted(() => {
if (mediaStream.value) {
const tracks = mediaStream.value.getTracks();
tracks.forEach((track) => track.stop());
mediaStream.value = null;
}
});
const checkPermissionStatus = async (permissionName) => {
try {
const permissionStatus = await navigator.permissions.query({ name: permissionName });
return permissionStatus.state;
} catch (error) {
console.error(`Error querying ${permissionName} permission status:`, error);
return 'unknown';
}
};
const checkMicCamPermissions = async () => {
if (navigator.mediaDevices && navigator.mediaDevices.getUserMedia) {
try {
const videoPermissionStatus = await checkPermissionStatus('camera');
const audioPermissionStatus = await checkPermissionStatus('microphone');
if (videoPermissionStatus === 'granted' && audioPermissionStatus === 'granted') {
console.log('Camera and microphone permissions already granted.');
isCameraEnable.value = true;
isMicrophoneEnable.value = true;
store.dispatch('updateIsCamEnabled', true);
store.dispatch('updateIsMicEnabled', true);
return;
}
const constraints = {
video: videoPermissionStatus !== 'granted' ? true : false,
audio: audioPermissionStatus !== 'granted' ? true : false
};
const mediaStream = await navigator.mediaDevices.getUserMedia(constraints);
if (constraints.video && mediaStream.getVideoTracks().length > 0) {
isCameraEnable.value = true;
store.dispatch('updateIsCamEnabled', true);
console.log('Camera permission granted.');
mediaStream.getVideoTracks().forEach(track => track.stop());
}
if (constraints.audio && mediaStream.getAudioTracks().length > 0) {
isMicrophoneEnable.value = true;
store.dispatch('updateIsMicEnabled', true);
console.log('Microphone permission granted.');
mediaStream.getAudioTracks().forEach(track => track.stop());
}
} catch (error) {
console.error('Error requesting camera or microphone permissions:', error);
if (error.name === 'NotAllowedError') {
console.log('Camera or microphone permissions were not granted.');
}
}
} else {
console.error('navigator.mediaDevices is not supported in this browser.');
}
};
const checkPermissions = async () => {
try {
const stream = await navigator.mediaDevices.getUserMedia({ video: true, audio: true });
console.log('stream', stream)
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);
}
};
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;
}
};
const enablePermission = async () => {
await checkMicCamPermissions();
await nextTick()
if (store.getters.getIsMicEnabled && store.getters.getIsCamEnabled) {
permissionModel.value = false
router.push('/telehealth');
}
};
</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>
<VDialog v-model="permissionModel" refs="myDialog" persistent width="500">
<!-- <template v-slot:default="{ isActive }"> -->
<v-card>
<v-card-text>
<div class="mt-2 mb-2">
<h4 class="text-center">Enable your Camera & Microphone permission from your browser</h4>
</div>
</v-card-text>
<v-card-actions>
<v-spacer></v-spacer>
<v-btn text="Grant Pesmission" @click="enablePermission()"></v-btn>
</v-card-actions>
</v-card>
<!-- </template> -->
</VDialog>
<VSnackbar v-model="getIsTonalSnackbarVisible" :timeout="5000" location="top end" variant="flat" color="red">
{{ errorMessage }}
</VSnackbar>
<VRow>
<VCol cols="12" md="9">
<VAlert border="start" color="red" variant="tonal" class="mb-2"
v-if="!store.getters.getIsMicEnabled && !store.getters.getIsCamEnabled">
<div class="">To proceed, please enable camera and microphone permissions in your browser's settings.
</div>
</VAlert>
<VCard>
<VCardText>
<p>
<b>Improved Access:</b><br>
Telehealth can provide access to healthcare for patients living in remote or underserved areas,
as well as those with mobility issues or busy schedules that make it difficult to attend
in-person appointments. This can be especially beneficial for individuals with chronic
conditions or those requiring specialized care that may not be readily available locally.
<br><br>
<b>Increased Convenience:</b><br>
Telehealth appointments allow patients to receive care from the comfort of their own homes,
eliminating the need for travel and reducing the time and costs associated with in-person
visits. This can be particularly advantageous for patients with limited transportation options
or those juggling work, family, and other commitments.
<br><br>
<b>Reduced Costs:</b><br>
Telehealth has the potential to lower healthcare costs by reducing the need for in-person
visits, transportation expenses, and time off work. This can benefit both patients and the
healthcare system as a whole.
<br><br>
<b>Expanded Care Options:</b><br>
Telehealth can enable access to specialists and healthcare providers who may not be available
locally, giving patients the opportunity to receive care from a broader range of medical
professionals.
<br><br>
<b>Increased Efficiency:</b><br>
Telehealth appointments can often be scheduled more easily and quickly than in-person visits,
potentially reducing wait times and improving the overall efficiency of the healthcare system.
</p>
</VCardText>
</VCard>
</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>
<VCard v-if="isDialogVisible" class="mt-4">
<!-- <VCardItem class="justify-center">
<VCardTitle class="font-weight-bold text-primary">
HGH
</VCardTitle>
</VCardItem> -->
<v-sheet elevation="20" max-width="" rounded="lg" width="100%" class="pa-2 px-4">
<h4> Doctor is</h4>
<v-badge color="success" content="Online" inline></v-badge>
<p class="mb-3">Your call will start soon.<br></p>
<!-- <p>You are number {{ currentQueue }} in line. </p> -->
</v-sheet>
</VCard>
</VCol>
</VRow>
</template>
<style lang="scss">
@use "@core/scss/template/pages/page-auth.scss";
</style>
<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>

Some files were not shown because too many files have changed in this diff Show More