feat(patient): enhance patient detail view with accordion and timezone support
- Add date-fns-tz for timezone-aware date formatting - Refactor patient detail view to use accordion components - Improve date display formatting with locale support - Update navigation handling for edit and back actions - Extend ClickType enum to include 'edit' action
This commit is contained in:
@@ -1,9 +1,14 @@
|
||||
<script setup lang="ts">
|
||||
import type { Patient } from '~/models/patient'
|
||||
import DetailRow from '~/components/pub/my-ui/form/view/detail-row.vue'
|
||||
import DetailSection from '~/components/pub/my-ui/form/view/detail-section.vue'
|
||||
import { formatAddress } from '~/models/person-address'
|
||||
import { format } from 'date-fns'
|
||||
import { formatInTimeZone } from 'date-fns-tz'
|
||||
import { id } from 'date-fns/locale'
|
||||
|
||||
// types
|
||||
import type { Patient } from '~/models/patient'
|
||||
import type { ClickType } from '~/components/pub/my-ui/nav-footer'
|
||||
|
||||
// helper
|
||||
import { formatAddress } from '~/models/person-address'
|
||||
import {
|
||||
addressLocationTypeCode,
|
||||
educationCodes,
|
||||
@@ -15,13 +20,21 @@ import {
|
||||
} from '~/lib/constants'
|
||||
import { mapToComboboxOptList } from '~/lib/utils'
|
||||
|
||||
// components
|
||||
import { Accordion, AccordionContent, AccordionItem, AccordionTrigger } from '~/components/pub/ui/accordion'
|
||||
import { Fragment } from '~/components/pub/my-ui/form/'
|
||||
import DetailRow from '~/components/pub/my-ui/form/view/detail-row.vue'
|
||||
import DetailSection from '~/components/pub/my-ui/form/view/detail-section.vue'
|
||||
import { toZoned } from '@internationalized/date'
|
||||
|
||||
// #region Props & Emits
|
||||
const props = defineProps<{
|
||||
patient: Patient
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'click', type: string): void
|
||||
(e: 'back'): void
|
||||
(e: 'edit'): void
|
||||
}>()
|
||||
|
||||
// #endregion
|
||||
@@ -70,8 +83,9 @@ const patientAge = computed(() => {
|
||||
// #endregion region
|
||||
|
||||
// #region Utilities & event handlers
|
||||
function onClick(type: string) {
|
||||
emit('click', type)
|
||||
function onNavigate(type: ClickType) {
|
||||
if (type == 'back') emit('back')
|
||||
if (type == 'edit') emit('edit')
|
||||
}
|
||||
// #endregion
|
||||
|
||||
@@ -80,120 +94,189 @@ function onClick(type: string) {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<DetailSection title="Data Pasien">
|
||||
<DetailRow label="Nomor">{{ patient.number || '-' }}</DetailRow>
|
||||
<DetailRow label="Nama Lengkap">{{ patient.person.name || '-' }}</DetailRow>
|
||||
<DetailRow label="Tempat, tanggal lahir">
|
||||
{{ patient.person.birthRegency?.name || '-' }},
|
||||
{{ patient.person.birthDate ? new Date(patient.person.birthDate).toLocaleDateString('id-ID') : '-' }}
|
||||
</DetailRow>
|
||||
<DetailRow label="Usia">{{ patientAge || '-' }} Tahun</DetailRow>
|
||||
<DetailRow label="Tanggal Daftar">
|
||||
{{ patient.person.createdAt ? new Date(patient.person.createdAt).toLocaleDateString('id-ID') : '-' }}
|
||||
</DetailRow>
|
||||
<DetailRow label="Jenis Kelamin">
|
||||
{{ genderOptions.find((item) => item.code === patient.person.gender_code)?.label || '-' }}
|
||||
</DetailRow>
|
||||
<Accordion
|
||||
type="multiple"
|
||||
class="w-full"
|
||||
collapsible
|
||||
:defaultValue="['item-patient', 'item-address', 'item-contact', 'item-parents', 'item-relative']"
|
||||
>
|
||||
<Fragment
|
||||
v-slot="{ section }"
|
||||
title="Data Pasien"
|
||||
>
|
||||
<AccordionItem value="item-patient">
|
||||
<AccordionTrigger>{{ section }}</AccordionTrigger>
|
||||
<AccordionContent>
|
||||
<DetailRow label="Nomor">{{ patient.number || '-' }}</DetailRow>
|
||||
<DetailRow label="Nama Lengkap">{{ patient.person.name || '-' }}</DetailRow>
|
||||
<DetailRow label="Tempat, tanggal lahir">
|
||||
{{ patient.person.birthRegency?.name || '-' }},
|
||||
{{
|
||||
patient.person.birthDate
|
||||
? format(new Date(patient.person.birthDate), 'dd MMMM yyyy', { locale: id })
|
||||
: '-'
|
||||
}}
|
||||
</DetailRow>
|
||||
<DetailRow label="Usia">{{ patientAge || '-' }} Tahun</DetailRow>
|
||||
<DetailRow label="Tanggal Daftar">
|
||||
{{
|
||||
patient.person.createdAt
|
||||
? formatInTimeZone(new Date(patient.person.createdAt), 'Asia/Jakarta', "dd MMMM yyyy, HH:mm:ss 'WIB'", {
|
||||
locale: id,
|
||||
})
|
||||
: '-'
|
||||
}}
|
||||
</DetailRow>
|
||||
<DetailRow label="Jenis Kelamin">
|
||||
{{ genderOptions.find((item) => item.code === patient.person.gender_code)?.label || '-' }}
|
||||
</DetailRow>
|
||||
|
||||
<DetailRow label="NIK">{{ patient.person.residentIdentityNumber || '-' }}</DetailRow>
|
||||
<DetailRow label="No. SIM">{{ patient.person.drivingLicenseNumber || '-' }}</DetailRow>
|
||||
<DetailRow label="No. Paspor">{{ patient.person.passportNumber || '-' }}</DetailRow>
|
||||
<DetailRow label="NIK">{{ patient.person.residentIdentityNumber || '-' }}</DetailRow>
|
||||
<DetailRow label="No. SIM">{{ patient.person.drivingLicenseNumber || '-' }}</DetailRow>
|
||||
<DetailRow label="No. Paspor">{{ patient.person.passportNumber || '-' }}</DetailRow>
|
||||
|
||||
<DetailRow label="Agama">
|
||||
{{ religionOptions.find((item) => item.code === patient.person.religion_code)?.label || '-' }}
|
||||
</DetailRow>
|
||||
<DetailRow label="Suku">{{ patient.person.ethnic?.name || '-' }}</DetailRow>
|
||||
<DetailRow label="Bahasa">{{ patient.person.language?.name || '-' }}</DetailRow>
|
||||
<DetailRow label="Pendidikan">
|
||||
{{ educationOptions.find((item) => item.code === patient.person.education_code)?.label || '-' }}
|
||||
</DetailRow>
|
||||
<DetailRow label="Pekerjaan">
|
||||
{{
|
||||
occupationOptions.find((item) => item.code === patient.person.occupation_code)?.label ||
|
||||
patient.person.occupation_name ||
|
||||
'-'
|
||||
}}
|
||||
</DetailRow>
|
||||
</DetailSection>
|
||||
<DetailRow label="Agama">
|
||||
{{ religionOptions.find((item) => item.code === patient.person.religion_code)?.label || '-' }}
|
||||
</DetailRow>
|
||||
<DetailRow label="Suku">{{ patient.person.ethnic?.name || '-' }}</DetailRow>
|
||||
<DetailRow label="Bahasa">{{ patient.person.language?.name || '-' }}</DetailRow>
|
||||
<DetailRow label="Pendidikan">
|
||||
{{ educationOptions.find((item) => item.code === patient.person.education_code)?.label || '-' }}
|
||||
</DetailRow>
|
||||
<DetailRow label="Pekerjaan">
|
||||
{{
|
||||
occupationOptions.find((item) => item.code === patient.person.occupation_code)?.label ||
|
||||
patient.person.occupation_name ||
|
||||
'-'
|
||||
}}
|
||||
</DetailRow>
|
||||
</AccordionContent>
|
||||
</AccordionItem>
|
||||
</Fragment>
|
||||
|
||||
<DetailSection title="Alamat">
|
||||
<DetailRow :label="addressLocationTypeCode.domicile || 'Alamat Domisili'">{{ domicileAddress || '-' }}</DetailRow>
|
||||
<DetailRow :label="addressLocationTypeCode.identity || 'Alamat KTP'">{{ identityAddress || '-' }}</DetailRow>
|
||||
</DetailSection>
|
||||
<DetailSection title="Kontak">
|
||||
<template v-if="patient.person.contacts && patient.person.contacts.length > 0">
|
||||
<template
|
||||
v-for="contactType in personContactTypeOptions"
|
||||
:key="contactType.code"
|
||||
>
|
||||
<DetailRow :label="contactType.label">
|
||||
{{ patient.person.contacts.find((item) => item.type_code === contactType.code)?.value || '-' }}
|
||||
</DetailRow>
|
||||
</template>
|
||||
</template>
|
||||
<template v-else>
|
||||
<DetailRow label="Kontak">-</DetailRow>
|
||||
</template>
|
||||
</DetailSection>
|
||||
<DetailSection title="Orang Tua">
|
||||
<template v-if="patient.person.relatives && patient.person.relatives.filter((rel) => !rel.responsible).length > 0">
|
||||
<template
|
||||
v-for="(relative, index) in patient.person.relatives.filter((rel) => !rel.responsible)"
|
||||
:key="relative.id"
|
||||
>
|
||||
<div
|
||||
v-if="index > 0"
|
||||
class="mt-3 border-t border-gray-200 pt-3"
|
||||
></div>
|
||||
<DetailRow label="Nama">{{ relative.name || '-' }}</DetailRow>
|
||||
<DetailRow label="Hubungan">
|
||||
{{ relationshipOptions.find((item) => item.code === relative.relationship_code)?.label || '-' }}
|
||||
</DetailRow>
|
||||
<!-- <DetailRow label="Jenis Kelamin">
|
||||
<Fragment
|
||||
v-slot="{ section }"
|
||||
title="Alamat"
|
||||
>
|
||||
<AccordionItem value="item-address">
|
||||
<AccordionTrigger>{{ section }}</AccordionTrigger>
|
||||
<AccordionContent>
|
||||
<DetailRow :label="addressLocationTypeCode.domicile || 'Alamat Domisili'">
|
||||
{{ domicileAddress || '-' }}
|
||||
</DetailRow>
|
||||
<DetailRow :label="addressLocationTypeCode.identity || 'Alamat KTP'">
|
||||
{{ identityAddress || '-' }}
|
||||
</DetailRow>
|
||||
</AccordionContent>
|
||||
</AccordionItem>
|
||||
</Fragment>
|
||||
|
||||
<Fragment
|
||||
v-slot="{ section }"
|
||||
title="Kontak"
|
||||
>
|
||||
<AccordionItem value="item-contact">
|
||||
<AccordionTrigger>{{ section }}</AccordionTrigger>
|
||||
<AccordionContent>
|
||||
<template v-if="patient.person.contacts && patient.person.contacts.length > 0">
|
||||
<template
|
||||
v-for="contactType in personContactTypeOptions"
|
||||
:key="contactType.code"
|
||||
>
|
||||
<DetailRow :label="contactType.label">
|
||||
{{ patient.person.contacts.find((item) => item.type_code === contactType.code)?.value || '-' }}
|
||||
</DetailRow>
|
||||
</template>
|
||||
</template>
|
||||
<template v-else>
|
||||
<DetailRow label="Kontak">-</DetailRow>
|
||||
</template>
|
||||
</AccordionContent>
|
||||
</AccordionItem>
|
||||
</Fragment>
|
||||
|
||||
<Fragment
|
||||
v-slot="{ section }"
|
||||
title="Data Orang Tua"
|
||||
>
|
||||
<AccordionItem value="item-parents">
|
||||
<AccordionTrigger>{{ section }}</AccordionTrigger>
|
||||
<AccordionContent>
|
||||
<template
|
||||
v-if="patient.person.relatives && patient.person.relatives.filter((rel) => !rel.responsible).length > 0"
|
||||
>
|
||||
<template
|
||||
v-for="(relative, index) in patient.person.relatives.filter((rel) => !rel.responsible)"
|
||||
:key="relative.id"
|
||||
>
|
||||
<div
|
||||
v-if="index > 0"
|
||||
class="mt-3 border-t border-gray-200 pt-3"
|
||||
></div>
|
||||
<DetailRow label="Nama">{{ relative.name || '-' }}</DetailRow>
|
||||
<DetailRow label="Hubungan">
|
||||
{{ relationshipOptions.find((item) => item.code === relative.relationship_code)?.label || '-' }}
|
||||
</DetailRow>
|
||||
<!-- <DetailRow label="Jenis Kelamin">
|
||||
{{ genderOptions.find((item) => item.code === relative.gender_code)?.label || '-' }}
|
||||
</DetailRow> -->
|
||||
<DetailRow label="Pendidikan">
|
||||
{{ educationOptions.find((item) => item.code === relative.education_code)?.label || '-' }}
|
||||
</DetailRow>
|
||||
<DetailRow label="Pekerjaan">
|
||||
{{
|
||||
occupationOptions.find((item) => item.code === relative.occupation_code)?.label ||
|
||||
relative.occupation_name ||
|
||||
'-'
|
||||
}}
|
||||
</DetailRow>
|
||||
<!-- <DetailRow label="Alamat">{{ relative.address || '-' }}</DetailRow> -->
|
||||
<!-- <DetailRow label="Nomor HP">{{ relative.phoneNumber || '-' }}</DetailRow> -->
|
||||
</template>
|
||||
</template>
|
||||
<template v-else>
|
||||
<DetailRow label="Orang Tua">-</DetailRow>
|
||||
</template>
|
||||
</DetailSection>
|
||||
<DetailSection title="Penanggung Jawab">
|
||||
<template v-if="patient.person.relatives && patient.person.relatives.filter((rel) => rel.responsible).length > 0">
|
||||
<template
|
||||
v-for="(relative, index) in patient.person.relatives.filter((rel) => rel.responsible)"
|
||||
:key="relative.id"
|
||||
>
|
||||
<div
|
||||
v-if="index > 0"
|
||||
class="mt-3 border-t border-gray-200 pt-3"
|
||||
></div>
|
||||
<DetailRow label="Nama">{{ relative.name || '-' }}</DetailRow>
|
||||
<DetailRow label="Hubungan">
|
||||
{{ relationshipOptions.find((item) => item.code === relative.relationship_code)?.label || '-' }}
|
||||
</DetailRow>
|
||||
<DetailRow label="Alamat">{{ relative.address || '-' }}</DetailRow>
|
||||
<DetailRow label="Nomor HP">{{ relative.phoneNumber || '-' }}</DetailRow>
|
||||
</template>
|
||||
</template>
|
||||
<template v-else>
|
||||
<DetailRow label="Penanggung Jawab">-</DetailRow>
|
||||
</template>
|
||||
</DetailSection>
|
||||
<div class="border-t-1 my-2 flex justify-end border-t-slate-300 py-2">
|
||||
<PubMyUiNavFooterBaEd @click="onClick" />
|
||||
<DetailRow label="Pendidikan">
|
||||
{{ educationOptions.find((item) => item.code === relative.education_code)?.label || '-' }}
|
||||
</DetailRow>
|
||||
<DetailRow label="Pekerjaan">
|
||||
{{
|
||||
occupationOptions.find((item) => item.code === relative.occupation_code)?.label ||
|
||||
relative.occupation_name ||
|
||||
'-'
|
||||
}}
|
||||
</DetailRow>
|
||||
<!-- <DetailRow label="Alamat">{{ relative.address || '-' }}</DetailRow> -->
|
||||
<!-- <DetailRow label="Nomor HP">{{ relative.phoneNumber || '-' }}</DetailRow> -->
|
||||
</template>
|
||||
</template>
|
||||
<template v-else>
|
||||
<DetailRow label="Orang Tua">-</DetailRow>
|
||||
</template>
|
||||
</AccordionContent>
|
||||
</AccordionItem>
|
||||
</Fragment>
|
||||
|
||||
<Fragment
|
||||
v-slot="{ section }"
|
||||
title="Data Penanggung Jawab"
|
||||
>
|
||||
<AccordionItem value="item-relative">
|
||||
<AccordionTrigger>{{ section }}</AccordionTrigger>
|
||||
<AccordionContent>
|
||||
<template
|
||||
v-if="patient.person.relatives && patient.person.relatives.filter((rel) => rel.responsible).length > 0"
|
||||
>
|
||||
<template
|
||||
v-for="(relative, index) in patient.person.relatives.filter((rel) => rel.responsible)"
|
||||
:key="relative.id"
|
||||
>
|
||||
<div
|
||||
v-if="index > 0"
|
||||
class="mt-3 border-t border-gray-200 pt-3"
|
||||
></div>
|
||||
<DetailRow label="Nama">{{ relative.name || '-' }}</DetailRow>
|
||||
<DetailRow label="Hubungan">
|
||||
{{ relationshipOptions.find((item) => item.code === relative.relationship_code)?.label || '-' }}
|
||||
</DetailRow>
|
||||
<DetailRow label="Alamat">{{ relative.address || '-' }}</DetailRow>
|
||||
<DetailRow label="Nomor HP">{{ relative.phoneNumber || '-' }}</DetailRow>
|
||||
</template>
|
||||
</template>
|
||||
<template v-else>
|
||||
<DetailRow label="Penanggung Jawab">-</DetailRow>
|
||||
</template>
|
||||
</AccordionContent>
|
||||
</AccordionItem>
|
||||
</Fragment>
|
||||
</Accordion>
|
||||
|
||||
<div class="my-2 flex justify-end py-2">
|
||||
<PubMyUiNavFooterBaEd @click="onNavigate" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<script setup lang="ts">
|
||||
import { withBase } from '~/models/_base'
|
||||
import type { HeaderPrep } from '~/components/pub/my-ui/data/types'
|
||||
import type { Patient } from '~/models/patient'
|
||||
import type { PatientEntity } from '~/models/patient'
|
||||
import type { Person } from '~/models/person'
|
||||
|
||||
// Components
|
||||
@@ -18,7 +18,7 @@ const props = defineProps<{
|
||||
|
||||
// #region State & Computed
|
||||
const patient = ref(
|
||||
withBase<Patient>({
|
||||
withBase<PatientEntity>({
|
||||
person: {} as Person,
|
||||
personAddresses: [],
|
||||
personContacts: [],
|
||||
@@ -47,19 +47,19 @@ onMounted(async () => {
|
||||
// #endregion region
|
||||
|
||||
// #region Utilities & event handlers
|
||||
function handleAction(type: string) {
|
||||
switch (type) {
|
||||
case 'edit':
|
||||
// TODO: Handle edit action
|
||||
console.log('editing data')
|
||||
break
|
||||
|
||||
case 'cancel':
|
||||
navigateTo({
|
||||
name: 'client-patient',
|
||||
})
|
||||
break
|
||||
}
|
||||
async function onBack() {
|
||||
await navigateTo({
|
||||
name: 'client-patient',
|
||||
})
|
||||
}
|
||||
async function onEdit() {
|
||||
console.log(props.patientId)
|
||||
await navigateTo({
|
||||
name: 'client-patient-id-edit',
|
||||
params: {
|
||||
id: props.patientId,
|
||||
},
|
||||
})
|
||||
}
|
||||
// #endregion
|
||||
|
||||
@@ -68,13 +68,19 @@ function handleAction(type: string) {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Header
|
||||
:prep="headerPrep"
|
||||
:ref-search-nav="headerPrep.refSearchNav"
|
||||
/>
|
||||
<div
|
||||
v-if="patient"
|
||||
:key="patient.id"
|
||||
>
|
||||
<Header
|
||||
:prep="headerPrep"
|
||||
:ref-search-nav="headerPrep.refSearchNav"
|
||||
/>
|
||||
|
||||
<AppPatientPreview
|
||||
:patient="patient"
|
||||
@click="handleAction"
|
||||
/>
|
||||
<AppPatientPreview
|
||||
:patient="patient"
|
||||
@back="onBack"
|
||||
@edit="onEdit"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -1 +1 @@
|
||||
export type ClickType = 'back' | 'draft' | 'submit'
|
||||
export type ClickType = 'back' | 'draft' | 'submit' | 'edit'
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
"@unovis/ts": "^1.5.1",
|
||||
"@unovis/vue": "^1.5.1",
|
||||
"date-fns": "^4.1.0",
|
||||
"date-fns-tz": "^3.2.0",
|
||||
"embla-carousel": "^8.5.2",
|
||||
"embla-carousel-vue": "^8.5.2",
|
||||
"file-saver": "^2.0.5",
|
||||
|
||||
Generated
+11
@@ -26,6 +26,9 @@ dependencies:
|
||||
date-fns:
|
||||
specifier: ^4.1.0
|
||||
version: 4.1.0
|
||||
date-fns-tz:
|
||||
specifier: ^3.2.0
|
||||
version: 3.2.0(date-fns@4.1.0)
|
||||
embla-carousel:
|
||||
specifier: ^8.5.2
|
||||
version: 8.6.0
|
||||
@@ -5709,6 +5712,14 @@ packages:
|
||||
d3-zoom: 3.0.0
|
||||
dev: false
|
||||
|
||||
/date-fns-tz@3.2.0(date-fns@4.1.0):
|
||||
resolution: {integrity: sha512-sg8HqoTEulcbbbVXeg84u5UnlsQa8GS5QXMqjjYIhS4abEVVKIUwe0/l/UhrZdKaL/W5eWZNlbTeEIiOXTcsBQ==}
|
||||
peerDependencies:
|
||||
date-fns: ^3.0.0 || ^4.0.0
|
||||
dependencies:
|
||||
date-fns: 4.1.0
|
||||
dev: false
|
||||
|
||||
/date-fns@4.1.0:
|
||||
resolution: {integrity: sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg==}
|
||||
dev: false
|
||||
|
||||
Reference in New Issue
Block a user