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,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