refactor(flow/patient): use Summary Card and loading state ♻️

- Replace inline cards with `PubBaseSummaryCard`
- Add `isLoading.summary` and mock `getPatientSummary()` 
- Render skeleton while loading; show data when ready 
- Update icons and remove `cn` 🧹
- Call `getPatientSummary()` and `getPatientList()` on mount 🚀
This commit is contained in:
Khafid Prayoga
2025-08-12 14:38:40 +07:00
parent 7f7dfe0a02
commit cc26415486
+62 -74
View File
@@ -1,7 +1,7 @@
<script setup lang="ts"> <script setup lang="ts">
import type { Summary } from '~/components/pub/base/summary-card.type'
import type { HeaderPrep, RefSearchNav } from '~/components/pub/nav/types' import type { HeaderPrep, RefSearchNav } from '~/components/pub/nav/types'
import { Calendar, ChevronDown, ChevronUp, Hospital, UserRoundCheck, UsersRound } from 'lucide-vue-next' import { Calendar, Hospital, UserCheck, UsersRound } from 'lucide-vue-next'
import { cn } from '~/lib/utils'
const refSearchNav: RefSearchNav = { const refSearchNav: RefSearchNav = {
onClick: () => { onClick: () => {
@@ -15,6 +15,12 @@ const refSearchNav: RefSearchNav = {
}, },
} }
// Loading state management
const isLoading = reactive({
summary: false,
table: false,
})
const hreaderPrep: HeaderPrep = { const hreaderPrep: HeaderPrep = {
title: 'Pasien', title: 'Pasien',
icon: 'i-lucide-add', icon: 'i-lucide-add',
@@ -24,92 +30,74 @@ const hreaderPrep: HeaderPrep = {
}, },
} }
// NOTE: example api // Initial/default data structure
const summaryData: Summary[] = [
{
title: 'Total Pasien',
icon: UsersRound,
metric: 23,
trend: 15,
timeframe: 'daily',
},
{
title: 'Pasien Aktif',
icon: UserCheck,
metric: 100,
trend: 9,
timeframe: 'daily',
},
{
title: 'Kunjungan Hari Ini',
icon: Calendar,
metric: 52,
trend: 1,
timeframe: 'daily',
},
{
title: 'Peserta BPJS',
icon: Hospital,
metric: 71,
trend: -3,
timeframe: 'daily',
},
]
// API call function
async function getPatientSummary() {
try {
isLoading.summary = true
await new Promise((resolve) => setTimeout(resolve, 1500))
} catch (error) {
console.error('Error fetching patient summary:', error)
// Keep default/existing data on error
} finally {
isLoading.summary = false
}
}
async function getPatientList() { async function getPatientList() {
// const response = await xfetch('/api/v1/patient') // const response = await xfetch('/api/v1/patient')
// console.log('data patient', response) // console.log('data patient', response)
} }
onMounted(() => { onMounted(() => {
getPatientSummary()
getPatientList() getPatientList()
}) })
// const patients = [
// {
// noRM: 'RM001',
// nama: 'Siti Nurhaliza',
// umur: 34,
// jenisKelamin: 'Perempuan',
// kontak: '081234567890',
// kunjunganTerakhir: '2025-08-01',
// status: 'active',
// bpjs: true,
// },
// ]
const summaryCard = computed(() => {
return [
{
title: 'Total Pasien',
icon: UsersRound,
value: 234,
change: 12.5,
isPositive: true,
period: 'from last month',
},
{
title: 'Pasien Aktif',
icon: UserRoundCheck,
value: 1340,
change: 8.3,
isPositive: true,
period: 'from last week',
},
{
title: 'Kunjungan Hari Ini',
icon: Calendar,
value: 200,
change: 5.2,
isPositive: false,
period: 'from yesterday',
},
{
title: 'Peserta BPJS',
icon: Hospital,
value: 10223,
change: 15.7,
isPositive: true,
period: 'from last month',
},
]
})
</script> </script>
<template> <template>
<PubNavHeaderPrep :prep="{ ...hreaderPrep }" :ref-search-nav="refSearchNav" /> <PubNavHeaderPrep :prep="{ ...hreaderPrep }" :ref-search-nav="refSearchNav" />
<main class="flex flex-1 flex-col gap-4 md:gap-8"> <main class="flex flex-1 flex-col gap-4 md:gap-8">
<div class="grid gap-4 md:grid-cols-2 md:gap-8 lg:grid-cols-4"> <div class="grid gap-4 md:grid-cols-2 md:gap-8 lg:grid-cols-4">
<Card v-for="report in summaryCard" :key="report.title"> <template v-if="isLoading.summary">
<CardHeader class="flex flex-row items-center justify-between space-y-0 pb-2"> <PubBaseSummaryCard v-for="n in 4" :key="n" is-skeleton />
<CardTitle class="text-sm font-medium"> {{ report.title }} </CardTitle> </template>
<component :is="report.icon" class="text-muted-foreground h-4 w-4" /> <template v-else>
</CardHeader> <PubBaseSummaryCard v-for="card in summaryData" :key="card.title" :stat="card" />
<CardContent> </template>
<div class="text-2xl font-bold">
{{ report.value.toLocaleString('id-ID') }}
</div>
<p class="text-muted-foreground flex items-center gap-1 text-xs">
<component
:is="report.isPositive ? ChevronUp : ChevronDown"
:class="cn('h-4 w-4', { 'text-green-500': report.isPositive }, { 'text-red-500': !report.isPositive })"
/>
<span class="font-medium" :class="report.isPositive ? 'text-green-500' : 'text-red-500'">
{{ report.change.toFixed(1) }}%
</span>
<span>{{ report.period }}</span>
</p>
</CardContent>
</Card>
</div> </div>
</main> </main>
</template> </template>