refactor(patient): extract age calculation logic to shared utility

Move age calculation logic from multiple components to a shared utility function in person model
Add age display to patient entry form and update preview component to use shared utility
Remove redundant age field from select-dob component
This commit is contained in:
Khafid Prayoga
2025-12-10 09:49:30 +07:00
parent e3680d183e
commit 9918501f29
4 changed files with 75 additions and 82 deletions
+25 -1
View File
@@ -5,6 +5,9 @@ import { toTypedSchema } from '@vee-validate/zod'
// types
import { type PatientFormData, PatientSchema } from '~/schemas/patient.schema'
// utils
import { calculateAge } from '~/models/person'
// components
import * as DE from '~/components/pub/my-ui/doc-entry'
import { InputBase, FileField as FileUpload } from '~/components/pub/my-ui/form'
@@ -26,7 +29,9 @@ import {
SelectReligion,
} from './fields'
interface FormData extends PatientFormData {}
interface FormData extends PatientFormData {
_calculatedAge: string
}
// Type untuk initial values (sebelum transform schema)
interface PatientFormInput {
@@ -74,6 +79,18 @@ defineExpose({
setValues,
values,
})
watch(
() => values.birthDate,
(newValue) => {
if (newValue) {
setFieldValue('_calculatedAge', calculateAge(newValue))
}
},
{
immediate: true,
},
)
</script>
<template>
@@ -140,6 +157,13 @@ defineExpose({
is-required
:is-disabled="isReadonly"
/>
<InputBase
field-name="_calculatedAge"
label="Usia"
placeholder="-"
numeric-only
is-disabled
/>
<SelectEducation
field-name="education"
label="Pendidikan"
@@ -1,16 +1,15 @@
<script setup lang="ts">
import type { FormErrors } from '~/types/error'
import { differenceInDays, differenceInMonths, differenceInYears, parseISO } from 'date-fns'
import { Input } from '~/components/pub/ui/input'
import { calculateAge } from '~/models/person'
import { cn } from '~/lib/utils'
// componenets
import * as DE from '~/components/pub/my-ui/doc-entry'
import { Input } from '~/components/pub/ui/input'
const props = defineProps<{
fieldName?: string
label?: string
placeholder?: string
errors?: FormErrors
class?: string
selectClass?: string
fieldGroupClass?: string
@@ -23,56 +22,13 @@ const {
fieldName = 'birthDate',
label = 'Tanggal Lahir',
placeholder = 'Pilih tanggal lahir',
errors,
class: containerClass,
fieldGroupClass,
labelClass,
} = props
// Reactive variables for age calculation
const patientAge = ref<string>('Masukkan tanggal lahir')
// Function to calculate age with years, months, and days
function calculateAge(birthDate: string | Date | undefined): string {
if (!birthDate) {
return 'Masukkan tanggal lahir'
}
try {
let dateObj: Date
if (typeof birthDate === 'string') {
dateObj = parseISO(birthDate)
} else {
dateObj = birthDate
}
const today = new Date()
// Calculate years, months, and days
const totalYears = differenceInYears(today, dateObj)
// Calculate remaining months after years
const yearsPassed = new Date(dateObj)
yearsPassed.setFullYear(yearsPassed.getFullYear() + totalYears)
const remainingMonths = differenceInMonths(today, yearsPassed)
// Calculate remaining days after years and months
const monthsPassed = new Date(yearsPassed)
monthsPassed.setMonth(monthsPassed.getMonth() + remainingMonths)
const remainingDays = differenceInDays(today, monthsPassed)
// Format the result
const parts = []
if (totalYears > 0) parts.push(`${totalYears} Tahun`)
if (remainingMonths > 0) parts.push(`${remainingMonths} Bulan`)
if (remainingDays > 0) parts.push(`${remainingDays} Hari`)
return parts.length > 0 ? parts.join(' ') : '0 Hari'
} catch {
return 'Masukkan tanggal lahir'
}
}
const patientAge = ref<string>('-')
</script>
<template>
@@ -86,7 +42,6 @@ function calculateAge(birthDate: string | Date | undefined): string {
</DE.Label>
<DE.Field
:id="fieldName"
:errors="errors"
:class="cn('select-field-wrapper')"
>
<FormField
@@ -116,27 +71,4 @@ function calculateAge(birthDate: string | Date | undefined): string {
</FormField>
</DE.Field>
</DE.Cell>
<DE.Cell>
<DE.Label label-for="patientAge">Usia</DE.Label>
<DE.Field id="patientAge">
<FormField name="patientAge">
<FormItem>
<FormControl>
<Input
:value="patientAge"
disabled
readonly
placeholder="Masukkan tanggal lahir"
:class="
cn(
'cursor-not-allowed bg-gray-50 focus:border-primary focus:ring-2 focus:ring-primary focus:ring-offset-0',
)
"
/>
</FormControl>
<FormMessage />
</FormItem>
</FormField>
</DE.Field>
</DE.Cell>
</template>
+3 -9
View File
@@ -19,6 +19,7 @@ import {
religionCodes,
} from '~/lib/constants'
import { mapToComboboxOptList } from '~/lib/utils'
import { calculateAge } from '~/models/person'
// components
import { Accordion, AccordionContent, AccordionItem, AccordionTrigger } from '~/components/pub/ui/accordion'
@@ -64,14 +65,7 @@ const patientAge = computed(() => {
if (!props.patient.person.birthDate) {
return '-'
}
const birthDate = new Date(props.patient.person.birthDate)
const today = new Date()
let age = today.getFullYear() - birthDate.getFullYear()
const monthDiff = today.getMonth() - birthDate.getMonth()
if (monthDiff < 0 || (monthDiff === 0 && today.getDate() < birthDate.getDate())) {
age--
}
return age
return calculateAge(props.patient.person.birthDate)
})
// #endregion
@@ -117,7 +111,7 @@ function onNavigate(type: ClickType) {
: '-'
}}
</DetailRow>
<DetailRow label="Usia">{{ patientAge || '-' }} Tahun</DetailRow>
<DetailRow label="Usia">{{ patientAge || '-' }}</DetailRow>
<DetailRow label="Tanggal Daftar">
{{
patient.person.createdAt
+43
View File
@@ -1,3 +1,5 @@
import { differenceInDays, differenceInMonths, differenceInYears, parseISO } from 'date-fns'
import { type Base, genBase } from './_base'
import type { PersonAddress } from './person-address'
import type { PersonContact } from './person-contact'
@@ -56,3 +58,44 @@ export function parseName(person: Person): string {
return fullName
}
export function calculateAge(birthDate: string | Date | undefined): string {
if (!birthDate) {
return 'Masukkan tanggal lahir'
}
try {
let dateObj: Date
if (typeof birthDate === 'string') {
dateObj = parseISO(birthDate)
} else {
dateObj = birthDate
}
const today = new Date()
// Calculate years, months, and days
const totalYears = differenceInYears(today, dateObj)
// Calculate remaining months after years
const yearsPassed = new Date(dateObj)
yearsPassed.setFullYear(yearsPassed.getFullYear() + totalYears)
const remainingMonths = differenceInMonths(today, yearsPassed)
// Calculate remaining days after years and months
const monthsPassed = new Date(yearsPassed)
monthsPassed.setMonth(monthsPassed.getMonth() + remainingMonths)
const remainingDays = differenceInDays(today, monthsPassed)
// Format the result
const parts = []
if (totalYears > 0) parts.push(`${totalYears} Tahun`)
if (remainingMonths > 0) parts.push(`${remainingMonths} Bulan`)
if (remainingDays > 0) parts.push(`${remainingDays} Hari`)
return parts.length > 0 ? parts.join(' ') : '0 Hari'
} catch {
return 'Masukkan tanggal lahir'
}
}