initial commit

This commit is contained in:
2026-06-23 13:28:38 +07:00
commit 966ed115b2
590 changed files with 111412 additions and 0 deletions
@@ -0,0 +1,39 @@
<script setup>
import { useTheme } from 'vuetify'
const { global } = useTheme()
const authProviders = [
{
icon: 'fa-facebook',
color: '#4267b2',
},
{
icon: 'fa-google',
color: '#dd4b39',
},
{
icon: 'fa-twitter',
color: '#1da1f2',
},
]
</script>
<template>
<div class="d-flex justify-center flex-wrap gap-3">
<VBtn
v-for="link in authProviders"
:key="link.icon"
icon
variant="tonal"
size="38"
:color="global.name.value === 'dark' ? link.colorInDark : link.color"
class="rounded"
>
<VIcon
size="18"
:icon="link.icon"
/>
</VBtn>
</div>
</template>
@@ -0,0 +1,104 @@
<script setup>
import axiosIns from '@/plugins/axios'
import { getLineChartSimpleConfig } from '@core/libs/apex-chart/apexCharConfig'
import { watchEffect } from 'vue'
import VueApexCharts from 'vue3-apexcharts'
import { useTheme } from 'vuetify'
const vuetifyTheme = useTheme()
const loading = ref(false)
const statistics = ref([])
const selected = ref('weekly')
const customDateRange = ref('')
const chartConfig = computed(() => {
const config = getLineChartSimpleConfig(vuetifyTheme.current.value)
config.xaxis.categories = Object.keys(statistics.value)
return config
})
const series = computed(() => [
{
name: 'Count',
data: Object.values(statistics.value),
},
])
watchEffect(() => {
let selectedQuery = '?filter=weekly' // default weekly
if (selected.value === 'monthly') {
selectedQuery = '?filter=monthly'
} else if (selected.value === 'custom') {
const [startDate, endDate] = customDateRange.value.split(' to ')
if (!startDate || !endDate) return
selectedQuery = `?filter=custom&start_date=${startDate}&end_date=${endDate}`
}
loading.value = true
axiosIns.get(`/api/v1/admin/dashboard/total-statistic-graph${selectedQuery}`).then(response => {
statistics.value = response.data.data.patient
loading.value = false
})
})
</script>
<template>
<VCol
cols="12"
md="8"
>
<VCard :loading="loading">
<VCardItem class="d-flex flex-wrap justify-space-between gap-4">
<VCardTitle>Patient Statistics</VCardTitle>
<template #append>
<VBtnToggle
v-model="selected"
density="compact"
color="primary"
variant="outlined"
divided
mandatory
:disabled="loading"
>
<VBtn value="weekly">
Weekly
</VBtn>
<VBtn value="monthly">
Monthly
</VBtn>
<VBtn value="custom">
Custom
</VBtn>
</VBtnToggle>
</template>
</VCardItem>
<VExpandTransition>
<VCardText v-if="selected === 'custom'">
<AppDateTimePicker
v-model="customDateRange"
:label="null"
placeholder="Select date range"
:config="{ mode: 'range' }"
/>
</VCardText>
</VExpandTransition>
<VCardText>
<VueApexCharts
type="line"
height="300"
:options="chartConfig"
:series="series"
/>
</VCardText>
</VCard>
</VCol>
</template>
<style lang="scss">
@use "@core-scss/template/libs/apex-chart.scss";
</style>
@@ -0,0 +1,146 @@
<script setup>
import axiosIns from '@/plugins/axios'
import { computed, onMounted } from 'vue'
import AdminChart from './admin-chart.vue'
const totalStatistics = ref(null)
const patientsStatistics = computed(() => {
return [
{
title: 'Total',
stats: totalStatistics.value?.total_patient,
icon: 'tabler-users',
color: 'primary',
},
{
title: 'Today',
stats: totalStatistics.value?.total_doctor,
icon: 'tabler-24-hours',
color: 'info',
},
{
title: 'Normal',
stats: totalStatistics.value?.total_patient_normal,
icon: 'tabler-checks',
color: 'success',
},
{
title: 'Myocardial Infarction',
stats: totalStatistics.value?.total_patient_mi,
icon: 'tabler-alert-triangle',
color: 'error',
},
{
title: 'Male',
stats: totalStatistics.value?.total_patient_male,
icon: 'tabler-gender-male',
color: 'primary',
},
{
title: 'Female',
stats: totalStatistics.value?.total_patient_female,
icon: 'tabler-gender-female',
color: 'info',
},
]
})
const doctorStatistics = computed(()=>{
return [
{
title: 'Total',
stats: totalStatistics.value?.total_doctor,
icon: 'tabler-stethoscope',
color: 'info',
},
]
})
onMounted(async () => {
axiosIns.get('/api/v1/admin/dashboard/total-statistic').then(response => {
totalStatistics.value = response.data.data
})
})
</script>
<template>
<VCol
cols="12"
sm="6"
md="8"
>
<VCard title="Patients">
<VCardText>
<VRow>
<VCol
v-for="item in patientsStatistics"
:key="item.title"
cols="6"
md="4"
>
<div class="d-flex align-center gap-4">
<VAvatar
:color="item.color"
variant="tonal"
size="64"
rounded="sm"
>
<VIcon
:icon="item.icon"
size="32"
/>
</VAvatar>
<div class="d-flex flex-column">
<span class="text-h4 font-weight-medium">{{ item.stats }}</span>
<span class="text-body">
{{ item.title }}
</span>
</div>
</div>
</VCol>
</VRow>
</VCardText>
</VCard>
</VCol>
<VCol
cols="12"
md="4"
>
<VCard title="Doctors">
<VCardText>
<VRow>
<VCol
v-for="item in doctorStatistics"
:key="item.title"
cols="12"
>
<div class="d-flex align-center gap-4">
<VAvatar
:color="item.color"
variant="tonal"
size="64"
rounded="sm"
>
<VIcon
:icon="item.icon"
size="32"
/>
</VAvatar>
<div class="d-flex flex-column">
<span class="text-h4 font-weight-medium">{{ item.stats }}</span>
<span class="text-body">
{{ item.title }}
</span>
</div>
</div>
</VCol>
</VRow>
</VCardText>
</VCard>
</VCol>
<AdminChart />
</template>
@@ -0,0 +1,237 @@
<script setup>
import axiosIns from '@/plugins/axios'
import uDiagnose from '@/utils/diagnose'
import { computed, onMounted } from 'vue'
import { VDataTable } from 'vuetify/labs/VDataTable'
const totalStatistics = ref(null)
const latestDiagnoses = ref([])
const patientsStatistics = computed(() => {
return [
{
title: 'Assigned',
stats: totalStatistics.value?.total_patient,
icon: 'tabler-users',
color: 'info',
},
{
title: 'Normal',
stats: totalStatistics.value?.total_patient_normal,
icon: 'tabler-checks',
color: 'success',
},
{
title: 'Myocardial Infarction',
stats: totalStatistics.value?.total_patient_mi,
icon: 'tabler-alert-triangle',
color: 'error',
},
{
title: 'Male',
stats: totalStatistics.value?.total_patient_male,
icon: 'tabler-gender-male',
color: 'primary',
},
{
title: 'Female',
stats: totalStatistics.value?.total_patient_female,
icon: 'tabler-gender-female',
color: 'info',
},
]
})
const diagnosesStatistics = computed(() => {
return [
{
title: 'Normal',
stats: totalStatistics.value?.total_verified,
icon: 'tabler-checks',
color: 'success',
},
{
title: 'Myocardial Infarction',
stats: totalStatistics.value?.total_unverified,
icon: 'tabler-alert-triangle',
color: 'error',
},
]
})
onMounted(async () => {
axiosIns.get('/api/v1/doctor/dashboard/total-statistic').then(response => {
totalStatistics.value = response.data.data
})
axiosIns.get('/api/v1/doctor/dashboard/latest-diagnose').then(response => {
latestDiagnoses.value = response.data.data.diagnoses?.map(item => ({
...item,
is_verified: item.is_verified == 1,
}))
})
})
const headers = [
{ title: 'Updated', key: 'updated_at' },
{ title: 'Patient', key: 'patient.fullname' },
{ title: 'Diagnoses', key: 'diagnoses' },
{ title: 'Verification', key: 'is_verified' },
{ title: 'Actions', key: 'actions', sortable: false },
]
const sortBy = ref([{ key: 'updated_at', order: 'desc' }])
function formatTimestamp(timestamp) {
const date = new Date(timestamp)
return date.toLocaleString(navigator.language)
}
</script>
<template>
<VCol
cols="12"
sm="6"
md="8"
>
<VCard title="Patients">
<VCardText>
<VRow>
<VCol
v-for="item in patientsStatistics"
:key="item.title"
cols="6"
md="4"
>
<div class="d-flex align-center gap-4">
<VAvatar
:color="item.color"
variant="tonal"
size="64"
rounded="sm"
>
<VIcon
:icon="item.icon"
size="32"
/>
</VAvatar>
<div class="d-flex flex-column">
<span class="text-h4 font-weight-medium">{{ item.stats }}</span>
<span class="text-body">
{{ item.title }}
</span>
</div>
</div>
</VCol>
</VRow>
</VCardText>
</VCard>
</VCol>
<VCol
cols="12"
md="2"
>
<VCard
title="Diagnoses"
density="compact"
>
<VCardText>
<VRow>
<VCol
v-for="item in diagnosesStatistics"
:key="item.title"
cols="6"
md="12"
>
<div class="d-flex align-center gap-4">
<VAvatar
:color="item.color"
variant="tonal"
rounded="sm"
size="40"
>
<VIcon
:icon="item.icon"
size="20"
/>
</VAvatar>
<div class="d-flex flex-column">
<span class="text-h6 font-weight-medium">{{ item.stats }}</span>
<span class="text-sm">
{{ item.title }}
</span>
</div>
</div>
</VCol>
</VRow>
</VCardText>
</VCard>
</VCol>
<VCol>
<VCard
title="Latest Diagnoses"
density="compact"
>
<VCardText>
<VRow>
<VCol>
<VDataTable
v-model:sort-by="sortBy"
:headers="headers"
:items="latestDiagnoses"
density="compact"
>
<template #item.updated_at="{ item }">
{{ formatTimestamp(item.raw.updated_at) }}
</template>
<template #item.diagnoses="{ item }">
<VChip
:color="uDiagnose.color(item.raw.diagnoses)"
label
variant="elevated"
>
{{ uDiagnose.text(item.raw.diagnoses) }}
</VChip>
</template>
<template #item.is_verified="{ item }">
<VChip
:color="item.raw.is_verified ? 'success' : 'error'"
label
variant="elevated"
>
{{ item.raw.is_verified ? 'Verified' : 'Not Verified' }}
</VChip>
</template>
<template #item.actions="{ item }">
<div class="d-flex gap-1">
<RouterLink :to="`/doctor/patient/${item.raw.patient_id}?diagnoses_id=${item.raw.id}`">
<IconBtn>
<VIcon
icon="mdi-information-outline"
color="primary"
/>
</IconBtn>
</RouterLink>
</div>
</template>
<!-- remove footer pagination -->
<template #bottom />
</VDataTable>
</VCol>
</VRow>
</VCardText>
</VCard>
</VCol>
</template>
<style lang="scss">
@use "@core-scss/template/libs/apex-chart.scss";
</style>
@@ -0,0 +1,171 @@
<script setup>
import axiosIns from '@/plugins/axios'
import uDiagnose from '@/utils/diagnose'
import { computed, onMounted } from 'vue'
import { VDataTable } from 'vuetify/labs/VDataTable'
const totalStatistics = ref(null)
const latestDiagnoses = ref([])
const diagnosesStatistics = computed(() => {
return [
{
title: 'Verified',
stats: totalStatistics.value?.total_verified,
icon: 'tabler-checks',
color: 'primary',
},
{
title: 'Not Verified',
stats: totalStatistics.value?.total_unverified,
icon: 'tabler-x',
color: 'warning',
},
{
title: 'Normal',
stats: totalStatistics.value?.total_patient_normal,
icon: 'tabler-checks',
color: 'success',
},
{
title: 'Myocardial Infarction',
stats: totalStatistics.value?.total_patient_mi,
icon: 'tabler-alert-triangle',
color: 'error',
},
]
})
onMounted(async () => {
axiosIns.get('/api/v1/patient/dashboard/total-statistic').then(response => {
totalStatistics.value = response.data.data
})
axiosIns.get('/api/v1/patient/dashboard/latest-diagnose').then(response => {
latestDiagnoses.value = response.data.data.diagnoses?.map(item => ({
...item,
is_verified: item.is_verified == 1,
}))
})
})
const headers = [
{ title: 'Updated', key: 'updated_at' },
{ title: 'Notes', key: 'notes' },
{ title: 'Diagnoses', key: 'diagnoses' },
{ title: 'Verification', key: 'is_verified' },
{ title: 'Actions', key: 'actions', sortable: false },
]
const sortBy = ref([{ key: 'updated_at', order: 'desc' }])
function formatTimestamp(timestamp) {
const date = new Date(timestamp)
return date.toLocaleString(navigator.language)
}
</script>
<template>
<VCol
cols="12"
sm="6"
md="8"
>
<VCard title="Diagnoses">
<VCardText>
<VRow>
<VCol
v-for="item in diagnosesStatistics"
:key="item.title"
cols="6"
>
<div class="d-flex align-center gap-4">
<VAvatar
:color="item.color"
variant="tonal"
size="64"
rounded="sm"
>
<VIcon
:icon="item.icon"
size="32"
/>
</VAvatar>
<div class="d-flex flex-column">
<span class="text-h4 font-weight-medium">{{ item.stats }}</span>
<span class="text-body">
{{ item.title }}
</span>
</div>
</div>
</VCol>
</VRow>
</VCardText>
</VCard>
</VCol>
<VCol>
<VCard
title="Latest Diagnoses"
density="compact"
>
<VCardText>
<VRow>
<VCol>
<VDataTable
v-model:sort-by="sortBy"
:headers="headers"
:items="latestDiagnoses"
density="compact"
>
<template #item.updated_at="{ item }">
{{ formatTimestamp(item.raw.updated_at) }}
</template>
<template #item.diagnoses="{ item }">
<VChip
:color="uDiagnose.color(item.raw.diagnoses)"
label
variant="elevated"
>
{{ uDiagnose.text(item.raw.diagnoses) }}
</VChip>
</template>
<template #item.is_verified="{ item }">
<VChip
:color="item.raw.is_verified ? 'success' : 'error'"
label
variant="elevated"
>
{{ item.raw.is_verified ? 'Verified' : 'Not Verified' }}
</VChip>
</template>
<template #item.actions="{ item }">
<div class="d-flex gap-1">
<RouterLink :to="`/patient/diagnoses?diagnoses_id=${item.raw.id}`">
<IconBtn>
<VIcon
icon="mdi-information-outline"
color="primary"
/>
</IconBtn>
</RouterLink>
</div>
</template>
<!-- remove footer pagination -->
<template #bottom />
</VDataTable>
</VCol>
</VRow>
</VCardText>
</VCard>
</VCol>
</template>
<style lang="scss">
@use "@core-scss/template/libs/apex-chart.scss";
</style>