initial commit
This commit is contained in:
Vendored
BIN
Binary file not shown.
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user