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:
@@ -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>
|
||||||
Reference in New Issue
Block a user