purityselect_admin/resources/js/pages/dashboards/analytics.vue
2024-10-25 19:58:19 +05:00

563 lines
17 KiB
Vue

<script setup>
import LineChart from '@core/libs/chartjs/components/LineChart';
import { endOfMonth, format, startOfMonth, subDays, subMonths } from 'date-fns';
import { computed, onBeforeMount, ref, watch } from 'vue';
import { useTheme } from 'vuetify';
import { useStore } from 'vuex';
const vuetifyTheme = useTheme();
const store = useStore();
const selectedPeriod = ref('this_month');
const date = ref('');
const userData = useCookie('userData');
const showDateRange = computed(() => selectedPeriod.value === 'custom');
const periodOptions = ref([
{ abbreviation: 'today', name: 'Today' },
{ abbreviation: 'last7_days', name: 'Last 7 Days' },
{ abbreviation: 'last30_days', name: 'Last 30 Days' },
{ abbreviation: 'this_month', name: 'This Month' },
{ abbreviation: 'last_month', name: 'Last Month' },
{ abbreviation: 'this_quarter', name: 'Current Quarter' },
{ abbreviation: 'last_quarter', name: 'Last Quarter' },
{ abbreviation: 'this_year', name: 'This Year' },
{ abbreviation: 'last_year', name: 'Last Year' },
{ abbreviation: 'last365_days', name: 'Last 365 Days' },
{ abbreviation: 'custom', name: 'Custom' },
]);
const setPeriod = (period) => {
selectedPeriod.value = period;
setDateRange();
};
const setDateRange = () => {
const today = new Date();
let start, end;
switch (selectedPeriod.value) {
case 'today':
start = end = today;
break;
case 'last7_days':
start = subDays(today, 7);
end = today;
break;
case 'last30_days':
start = subDays(today, 30);
end = today;
break;
case 'this_month':
start = startOfMonth(today);
end = endOfMonth(today);
break;
case 'last_month':
start = startOfMonth(subMonths(today, 1));
end = endOfMonth(subMonths(today, 1));
break;
case 'this_quarter':
end = startOfMonth(subMonths(today, today.getMonth() % 3));
start = endOfMonth(subMonths(today, today.getMonth() % 3 + 2));
break;
case 'last_quarter':
end = startOfMonth(subMonths(today, today.getMonth() % 3 + 3));
start = endOfMonth(subMonths(today, today.getMonth() % 3 + 5));
break;
case 'this_year':
start = new Date(today.getFullYear(), 0, 1);
end = new Date(today.getFullYear(), 11, 31);
break;
case 'last_year':
start = new Date(today.getFullYear() - 1, 0, 1);
end = new Date(today.getFullYear() - 1, 11, 31);
break;
case 'last365_days':
start = subDays(today, 365);
end = today;
break;
case 'custom':
return;
default:
start = end = today;
}
date.value = [format(start, 'yyyy-MM-dd'), format(end, 'yyyy-MM-dd')];
};
onBeforeMount(async () => {
setDateRange();
const [startDate, endDate] = date.value;
await store.dispatch('getAdminDashboardData', {
start_date: startDate,
end_date: endDate,
});
});
watch(selectedPeriod, async () => {
setDateRange();
if (selectedPeriod.value !== 'custom') {
const [startDate, endDate] = date.value;
await store.dispatch('getAdminDashboardData', {
start_date: startDate,
end_date: endDate,
});
}
});
const getButtonColor = (buttonPeriod) => {
return selectedPeriod.value === buttonPeriod ? 'primary' : 'secondary';
};
const changeDateRange = async () => {
console.log('changed date', date.value, 'type:', typeof date.value);
try {
const dateString = typeof date.value === 'string' ? date.value : '';
const [startDate, endDate] = dateString.split(" to ");
if (startDate && endDate) {
await store.dispatch('getAdminDashboardData', {
start_date: startDate,
end_date: endDate,
});
} else {
console.warn('Invalid date range');
}
} catch (error) {
console.error('Error processing date range:', error);
}
};
const assignmentData = computed(() => [
{ title: 'Patients', amount: store.getters.getDashboardData.totals.total_patints, color: 'primary' },
{ title: 'Orders', amount: store.getters.getDashboardData.totals.total_orders, color: 'success' },
{ title: 'Amount', amount: store.getters.getDashboardData.totals.total_amount ? formatAmount(store.getters.getDashboardData.totals.total_amount) : '$0.00', color: 'secondary' },
{ title: 'Products Sold', amount: store.getters.getDashboardData.totals.total_products_sold, color: 'error' },
]);
const recentActivity = [
{ title: 'Patient created by Provider', subtitle: '31/Jul/2024 00:33 39.50.134.183' },
{ type: 'divider', inset: true },
{ title: 'Order Created By Patient William', subtitle: '31/Jul/2024 00:19 39.50.134.183' },
{ type: 'divider', inset: true },
];
const OrdersData = ref([]);
const ordersHeaders = [
{ title: 'Date', key: 'date' },
{ title: '#Order', key: 'order_id' },
{ title: 'Patient', key: 'patient_name' },
{ title: 'Amount', key: 'amount' },
];
const completedMeetingsHeaders = [
{ title: 'Date', key: 'appointment_date' },
{ title: 'Time', key: 'appointment_time' },
{ title: '#Order', key: 'order_id' },
{ title: 'Patient', key: 'patient_name' },
{ title: 'Provider', key: 'provider_name' },
{ title: 'Start Time', key: 'start_time' },
{ title: 'End Time', key: 'end_time' },
{ title: 'Duration', key: 'duration' },
];
const productsHeaders = [
{ title: 'ID', key: 'product_id' },
{ title: 'Name', key: 'product_name' },
{ title: 'Amount', key: 'total_amount' },
{ title: 'Orders', key: 'total_orders' },
];
function changeFormat(dateFormat) {
const dateParts = dateFormat.split('-');
const year = parseInt(dateParts[0]);
const month = parseInt(dateParts[1]);
const day = parseInt(dateParts[2]);
const date = new Date(year, month - 1, day);
return `${month}-${day}-${date.getFullYear()}`;
};
const formatDateTime = (dateStr) => {
const [date, time] = dateStr.split(' ');
const [year, month, day] = date.split('-');
const [hours, minutes] = time.split(':');
return `${month}-${day}-${year} ${hours}:${minutes}`;
};
const formattedDuration = (startTime, endTime) => {
const start = new Date(startTime);
const end = new Date(endTime);
const diffInSeconds = Math.floor((end - start) / 1000); // Difference in seconds
const hours = Math.floor(diffInSeconds / 3600);
const minutes = Math.floor((diffInSeconds % 3600) / 60);
const seconds = diffInSeconds % 60;
if (hours > 0) {
return `${hours}h ${minutes}m ${seconds}s`;
} else if (minutes > 0) {
return `${minutes}m ${seconds}s`;
} else {
return `${seconds}s`;
}
};
const formatAmount = (amount) => {
return new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'USD'
}).format(amount);
};
const data = ref({
labels: [],
datasets: [
{
fill: false,
tension: 0,
pointRadius: 4,
label: 'Meetings',
pointHoverRadius: 6,
pointStyle: 'circle',
borderColor: 'green',
backgroundColor: 'green',
pointHoverBorderWidth: 5,
pointHoverBorderColor: 'white',
pointBorderColor: 'transparent',
pointHoverBackgroundColor: 'primary',
data: [],
yAxisID: 'y-axis-meetings',
},
{
fill: false,
tension: 0,
label: 'Sales',
pointRadius: 4,
pointHoverRadius: 6,
pointStyle: 'circle',
borderColor: 'orange',
backgroundColor: 'orange',
pointHoverBorderWidth: 5,
pointHoverBorderColor: 'white',
pointBorderColor: 'transparent',
pointHoverBackgroundColor: 'warning',
data: [],
yAxisID: 'y-axis-sales',
},
],
});
const updateChartData = (newData) => {
const isSingleDate = newData.graph_data.dates.length === 1;
let labels = newData.graph_data.dates;
let meetingsData = newData.graph_data.data.total_meetings;
let salesData = newData.graph_data.data.total_sales;
if (isSingleDate) {
labels = [labels[0], labels[0]];
meetingsData = [meetingsData[0], meetingsData[0]];
salesData = [salesData[0], salesData[0]];
}
data.value = {
labels: labels,
datasets: [
{
...data.value.datasets[0],
data: meetingsData,
},
{
...data.value.datasets[1],
data: salesData,
},
],
};
};
const formatOrderId = (id) => {
if (id >= 1 && id <= 9) {
return id.toString().padStart(4, '0');
} else if (id >= 10 && id <= 99) {
return id.toString().padStart(4, '0');
} else if (id >= 100 && id <= 999) {
return id.toString().padStart(4, '0');
} else {
return id; // or handle cases for IDs outside these ranges
}
}
watch(() => store.getters.getDashboardData, updateChartData, { immediate: true });
const chartConfig = computed(() => {
const maxMeetings = Math.max(...data.value.datasets[0].data, 1);
const maxSales = Math.max(...data.value.datasets[1].data, 1);
const isSingleDate = store.getters.getDashboardData.graph_data.dates.length === 1;
return {
responsive: true,
maintainAspectRatio: false,
scales: {
'y-axis-meetings': {
type: 'linear',
position: 'left',
beginAtZero: true,
min: 0,
max: maxMeetings * 2,
ticks: {
stepSize: Math.max(1, Math.floor(maxMeetings / 4)),
},
title: {
display: true,
text: 'Meetings',
},
},
'y-axis-sales': {
type: 'linear',
position: 'right',
beginAtZero: true,
min: 0,
max: maxSales * 1.5,
ticks: {
callback: (value) => `$${value.toLocaleString()}`,
stepSize: Math.max(1, Math.floor(maxSales / 4)),
},
title: {
display: true,
text: 'Sales',
},
},
x: {
ticks: {
autoSkip: true,
maxTicksLimit: isSingleDate ? 2 : 10,
maxRotation: 0,
minRotation: 0,
},
},
},
plugins: {
legend: {
position: 'top',
},
tooltip: {
callbacks: {
label: (context) => {
let label = context.dataset.label || '';
if (label) {
label += ': ';
}
if (context.parsed.y !== null) {
label += context.dataset.yAxisID === 'y-axis-sales'
? `$${context.parsed.y.toLocaleString()}`
: context.parsed.y;
}
return label;
},
},
},
},
};
});
</script>
<template>
<VRow class="match-height d-flex" v-if="$can('read', 'Dashboard Filters')">
<VCol cols="12" :md="selectedPeriod === 'custom' ? '9' : '12'"
class="d-flex align-center justify-end flex-wrap pull-right">
<VBtn class="mx-2" :color="getButtonColor('today')" @click="setPeriod('today')">Day</VBtn>
<VBtn class="mx-2" :color="getButtonColor('last7_days')" @click="setPeriod('last7_days')">Week</VBtn>
<VBtn class="mx-2" :color="getButtonColor('this_month')" @click="setPeriod('this_month')">Month</VBtn>
<VSelect v-model="selectedPeriod" label="Filter By" density="comfortable" item-title="name"
item-value="abbreviation" :items="periodOptions" class="medium-width" />
</VCol>
<VCol cols="12" :md="selectedPeriod === 'custom' ? '3' : ''" v-if="selectedPeriod === 'custom'">
<AppDateTimePicker v-model="date" label="Date Range" :config="{ mode: 'range' }" @change="changeDateRange()"
class="ml-4 date-range-picker" />
</VCol>
</VRow>
<VRow>
<VCol cols="12" md="4">
<VCard title="Welcome! Glad to see you." class="main-cards">
<VCardText>
<h1 class="mb-2 mt-10">{{ userData.fullName || userData.username }}</h1>
<p>Here are your company's most recent statistics:</p>
</VCardText>
<VCardItem>
</VCardItem>
<VCardText>
<VList class="card-list mb-0">
<VListItem v-for="assignment in assignmentData" :key="assignment.title" class="pb-2">
<template #title>
<div class="text-h6 me-4 mb-0 text-truncate">
{{ assignment.title }}
</div>
</template>
<template #append>
<VBtn :color="assignment.color" class="rounded" size="small" width="100">
{{ assignment.amount }}
</VBtn>
</template>
</VListItem>
</VList>
</VCardText>
</VCard>
</VCol>
<VCol cols="12" md="8">
<VCard title="Overview" class="main-cards">
<VCardText>
<!-- <ApexChart /> -->
<LineChart :chart-options="chartConfig" :height="400" :chart-data="data" />
</VCardText>
</VCard>
</VCol>
</VRow>
<VRow>
<VCol cols="12" md="6">
<VCard title="Recent Activity" class="activity-card">
<VCardText>
<template v-if="store.getters.getDashboardData.patient_reg_activity.length">
<v-list v-for="recentAct in store.getters.getDashboardData.patient_reg_activity" :key="recentAct.id"
lines="two" style="border-bottom: 1px solid silver;padding-bottom: 0px;">
<v-list-item :title="recentAct.activity" :subtitle="changeFormat(recentAct.created_at)"
style="padding-bottom: 0px;"></v-list-item>
</v-list>
</template>
<template v-else>
<div style="display: flex; justify-content: center; align-items: center; height: 200px;">
No Record
</div>
</template>
</VCardText>
</VCard>
</VCol>
<VCol cols="12" md="6">
<VCard title="Recent Orders" class="recent-cards ">
<VCardText>
<VDataTable :headers="ordersHeaders" :items="store.getters.getDashboardData.orders_data" :items-per-page="5"
class="text-no-wrap">
<template #item.order_id="{ item }">
<RouterLink :to="{ name: 'admin-order-detail', params: { id: item.order_id } }">
<span class="text-h6 text-primary">{{ formatOrderId(item.order_id) }}</span>
</RouterLink>
</template>
<template #item.date="{ item }">
<span class="text-h6">{{ changeFormat(item.date) }}</span>
</template>
<template #item.amount="{ item }">
<span class="text-h6">{{ formatAmount(item.amount) }}</span>
</template>
</VDataTable>
</VCardText>
</VCard>
</VCol>
</VRow>
<VRow>
<VCol cols="12" md="6">
<VCard title="Completed Appointments" class="recent-cards ">
<VCardText>
<VDataTable :headers="completedMeetingsHeaders" :items="store.getters.getDashboardData.completed_meetings"
:items-per-page="5" class="text-no-wrap">
<template #item.appointment_date="{ item }">
<span class="text-h6">{{ changeFormat(item.appointment_date) }}</span>
</template>
<template #item.order_id="{ item }">
<span class="text-h6">{{ item.order_id }}</span>
</template>
<template #item.start_time="{ item }">
<span class="text-h6">{{ formatDateTime(item.start_time) }}</span>
</template>
<template #item.end_time="{ item }">
<span class="text-h6">{{ formatDateTime(item.end_time) }}</span>
</template>
<template #item.duration="{ item }">
<span class="text-h6">{{ formattedDuration(item.start_time, item.end_time) }}</span>
</template>
</VDataTable>
</VCardText>
</VCard>
</VCol>
<VCol cols="12" md="6">
<VCard title="Products" class="recent-cards">
<VCardText>
<VDataTable :headers="productsHeaders" :items="store.getters.getDashboardData.products" :items-per-page="5"
class="text-no-wrap">
<template #item.product_id="{ item }">
<span class="text-h6">{{ item.product_id }}</span>
</template>
<template #item.total_amount="{ item }">
<span class="text-h6">{{ formatAmount(item.total_amount) }}</span>
</template>
</VDataTable>
</VCardText>
</VCard>
</VCol>
</VRow>
</template>
<style lang="scss">
@use "@core-scss/template/libs/apex-chart.scss";
.activity-card {
min-height: 480px;
max-height: 480px;
overflow-y: scroll !important;
}
.main-cards {
min-height: 508px;
}
.recent-cards {
min-height: 480px;
}
.medium-width {
min-width: 200px;
max-width: 250px;
}
.date-range-picker {
max-width: 300px;
}
.ml-4 {
margin-left: 8px;
}
.d-flex {
display: flex;
align-items: center;
flex-wrap: wrap;
}
.mx-2 {
margin-left: 8px;
margin-right: 8px;
}
.v-btn {
min-width: 80px;
text-transform: none;
}
.v-autocomplete {
min-width: 200px;
max-width: 250px;
}
.v-autocomplete__content {
max-width: 250px;
}
@media (max-width: 600px) {
.v-autocomplete {
min-width: 100px;
}
.v-btn {
min-width: 60px;
}
.date-range-picker {
max-width: 100%;
}
}
</style>