form cleanup
feat(patient): add edit functionality to patient form - Modify genPatientEntity to accept existing patient data for updates - Add handleActionEdit handler for edit mode - Update form to handle both create and edit modes - Rename patient ref to patientDetail for clarity refactor(patient): update marital status codes and job options mapping - Change marital status enum values to standardized codes (S, M, D, W) - Simplify job options and marital status options mapping using mapToComboboxOptList - Add error handling in patient data loading ajust styling text based on combobox wip: edit patient redirect refactor(models): update type definitions and form field handling - Add field-name prop to SelectDob component for better form handling - Update Person and Patient interfaces to use null for optional fields - Add maritalStatus_code field to Person interface - Improve type safety by replacing undefined with null for optional fields fix casting radio str to boolean and parsing date error
This commit is contained in:
@@ -34,7 +34,7 @@ interface PatientFormInput {
|
||||
drivingLicenseNumber?: string
|
||||
passportNumber?: string
|
||||
fullName?: string
|
||||
isNewBorn?: 'YA' | 'TIDAK'
|
||||
isNewBorn?: string
|
||||
gender?: string
|
||||
birthPlace?: string
|
||||
birthDate?: string
|
||||
@@ -45,8 +45,8 @@ interface PatientFormInput {
|
||||
ethnicity?: string
|
||||
language?: string
|
||||
religion?: string
|
||||
communicationBarrier?: 'YA' | 'TIDAK'
|
||||
disability?: 'YA' | 'TIDAK'
|
||||
communicationBarrier?: string
|
||||
disability?: string
|
||||
disabilityType?: string
|
||||
note?: string
|
||||
residentIdentityFile?: File
|
||||
@@ -61,7 +61,7 @@ interface Props {
|
||||
const props = defineProps<Props>()
|
||||
const formSchema = toTypedSchema(PatientSchema)
|
||||
|
||||
const { values, resetForm, setValues, validate } = useForm<FormData>({
|
||||
const { values, resetForm, setValues, setFieldValue, validate } = useForm<FormData>({
|
||||
name: 'patientForm',
|
||||
validationSchema: formSchema,
|
||||
initialValues: (props.initialValues ?? {}) as any,
|
||||
@@ -135,6 +135,7 @@ defineExpose({
|
||||
:is-disabled="isReadonly"
|
||||
/>
|
||||
<SelectDob
|
||||
field-name="birthDate"
|
||||
label="Tanggal Lahir"
|
||||
is-required
|
||||
:is-disabled="isReadonly"
|
||||
@@ -203,8 +204,8 @@ defineExpose({
|
||||
<SelectDisability
|
||||
label="Jenis Disabilitas"
|
||||
field-name="disabilityType"
|
||||
:is-disabled="isReadonly || values.disability !== 'YA'"
|
||||
:is-required="values.disability === 'YA'"
|
||||
:is-disabled="isReadonly || values.disability !== 'yes'"
|
||||
:is-required="values.disability === 'yes'"
|
||||
/>
|
||||
<InputBase
|
||||
field-name="note"
|
||||
|
||||
@@ -29,8 +29,8 @@ const {
|
||||
} = props
|
||||
|
||||
const genderOptions = [
|
||||
{ label: 'Ya', value: 'YA' },
|
||||
{ label: 'Tidak', value: 'TIDAK' },
|
||||
{ label: 'Ya', value: 'yes' },
|
||||
{ label: 'Tidak', value: 'no' },
|
||||
]
|
||||
</script>
|
||||
|
||||
@@ -47,7 +47,7 @@ const genderOptions = [
|
||||
:id="fieldName"
|
||||
:errors="errors"
|
||||
class="pt-0.5"
|
||||
>
|
||||
>
|
||||
<FormField
|
||||
v-slot="{ componentField }"
|
||||
:name="fieldName"
|
||||
@@ -80,9 +80,7 @@ const genderOptions = [
|
||||
:class="
|
||||
cn(
|
||||
'select-none text-xs !font-normal leading-none transition-colors sm:text-sm',
|
||||
isDisabled
|
||||
? 'cursor-not-allowed opacity-70'
|
||||
: 'cursor-pointer hover:text-primary',
|
||||
isDisabled ? 'cursor-not-allowed opacity-70' : 'cursor-pointer hover:text-primary',
|
||||
labelClass,
|
||||
)
|
||||
"
|
||||
|
||||
@@ -28,9 +28,9 @@ const {
|
||||
labelClass,
|
||||
} = props
|
||||
|
||||
const dissabilityOptions = [
|
||||
{ label: 'Ya', value: 'YA' },
|
||||
{ label: 'Tidak', value: 'TIDAK' },
|
||||
const disabilityOptions = [
|
||||
{ label: 'Ya', value: 'yes' },
|
||||
{ label: 'Tidak', value: 'no' },
|
||||
]
|
||||
</script>
|
||||
|
||||
@@ -60,7 +60,7 @@ const dissabilityOptions = [
|
||||
:class="cn('flex flex-row flex-wrap gap-4 sm:gap-6', radioGroupClass)"
|
||||
>
|
||||
<div
|
||||
v-for="(option, index) in dissabilityOptions"
|
||||
v-for="(option, index) in disabilityOptions"
|
||||
:key="option.value"
|
||||
:class="cn('flex min-w-fit items-center space-x-2', radioItemClass)"
|
||||
>
|
||||
@@ -70,7 +70,7 @@ const dissabilityOptions = [
|
||||
:value="option.value"
|
||||
:class="
|
||||
cn(
|
||||
'peer border-1 relative h-4 w-4 rounded-full border-muted-foreground before:absolute before:inset-1 before:rounded-full before:bg-primary before:opacity-0 before:transition-opacity data-[state=checked]:border-primary data-[state=checked]:bg-white data-[state=checked]:before:opacity-100 sm:h-5 sm:w-5',
|
||||
'border-1 peer relative h-4 w-4 rounded-full border-muted-foreground before:absolute before:inset-1 before:rounded-full before:bg-primary before:opacity-0 before:transition-opacity data-[state=checked]:border-primary data-[state=checked]:bg-white data-[state=checked]:before:opacity-100 sm:h-5 sm:w-5',
|
||||
containerClass,
|
||||
)
|
||||
"
|
||||
@@ -80,9 +80,7 @@ const dissabilityOptions = [
|
||||
:class="
|
||||
cn(
|
||||
'select-none text-xs !font-normal leading-none transition-colors',
|
||||
isDisabled
|
||||
? 'cursor-not-allowed opacity-70'
|
||||
: 'cursor-pointer hover:text-primary',
|
||||
isDisabled ? 'cursor-not-allowed opacity-70' : 'cursor-pointer hover:text-primary',
|
||||
labelClass,
|
||||
)
|
||||
"
|
||||
|
||||
@@ -29,8 +29,8 @@ const {
|
||||
} = props
|
||||
|
||||
const newbornOptions = [
|
||||
{ label: 'Ya', value: 'YA' },
|
||||
{ label: 'Tidak', value: 'TIDAK' },
|
||||
{ label: 'Ya', value: 'yes' },
|
||||
{ label: 'Tidak', value: 'no' },
|
||||
]
|
||||
</script>
|
||||
|
||||
@@ -81,9 +81,7 @@ const newbornOptions = [
|
||||
:class="
|
||||
cn(
|
||||
'select-none text-xs !font-normal leading-none transition-colors',
|
||||
isDisabled
|
||||
? 'cursor-not-allowed opacity-70'
|
||||
: 'cursor-pointer hover:text-primary',
|
||||
isDisabled ? 'cursor-not-allowed opacity-70' : 'cursor-pointer hover:text-primary',
|
||||
labelClass,
|
||||
)
|
||||
"
|
||||
|
||||
@@ -30,7 +30,10 @@ const {
|
||||
} = props
|
||||
|
||||
// Generate job options from constants, sama seperti pola genderCodes
|
||||
const jobOptions = mapToComboboxOptList(occupationCodes)
|
||||
const jobOptions = mapToComboboxOptList(occupationCodes).map(({ label, value }) => ({
|
||||
label,
|
||||
value,
|
||||
}))
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
<script setup lang="ts">
|
||||
import type { FormErrors } from '~/types/error'
|
||||
import Select from '~/components/pub/my-ui/form/select.vue'
|
||||
import { cn } from '~/lib/utils'
|
||||
import { maritalStatusCodes } from '~/const/key-val/person'
|
||||
|
||||
import { cn, mapToComboboxOptList } from '~/lib/utils'
|
||||
|
||||
import * as DE from '~/components/pub/my-ui/doc-entry'
|
||||
|
||||
@@ -27,13 +29,10 @@ const {
|
||||
placeholder = 'Pilih',
|
||||
} = props
|
||||
|
||||
const maritalStatusOptions = [
|
||||
{ label: 'Tidak Diketahui', value: 'TIDAK_DIKETAHUI' },
|
||||
{ label: 'Belum Kawin', value: 'BELUM_KAWIN' },
|
||||
{ label: 'Kawin', value: 'KAWIN' },
|
||||
{ label: 'Cerai Hidup', value: 'CERAI_HIDUP' },
|
||||
{ label: 'Cerai Mati', value: 'CERAI_MATI' },
|
||||
]
|
||||
const maritalStatusOptions = mapToComboboxOptList(maritalStatusCodes).map(({ label, value }) => ({
|
||||
label,
|
||||
value,
|
||||
}))
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
||||
@@ -53,7 +53,6 @@ async function onBack() {
|
||||
})
|
||||
}
|
||||
async function onEdit() {
|
||||
console.log(props.patientId)
|
||||
await navigateTo({
|
||||
name: 'client-patient-id-edit',
|
||||
params: {
|
||||
|
||||
@@ -18,6 +18,10 @@ import AppPersonFamilyParentsForm from '~/components/app/person/family-parents-f
|
||||
import AppPersonContactEntryForm from '~/components/app/person-contact/entry-form.vue'
|
||||
import AppPersonRelativeEntryForm from '~/components/app/person-relative/entry-form.vue'
|
||||
|
||||
// helper
|
||||
import { format, parseISO } from 'date-fns'
|
||||
import { id as localeID } from 'date-fns/locale'
|
||||
|
||||
// services
|
||||
import { getPatientDetail, uploadAttachment } from '~/services/patient.service'
|
||||
|
||||
@@ -28,6 +32,7 @@ import {
|
||||
isRecordConfirmationOpen,
|
||||
onResetState,
|
||||
handleActionSave,
|
||||
handleActionEdit,
|
||||
handleCancelForm,
|
||||
} from '~/handlers/patient.handler'
|
||||
|
||||
@@ -62,7 +67,7 @@ const personPatientForm = ref<ExposedForm<any> | null>(null)
|
||||
// #endregion
|
||||
|
||||
// #region State & Computed
|
||||
const patient = ref(
|
||||
const patientDetail = ref(
|
||||
withBase<PatientEntity>({
|
||||
person: {} as Person,
|
||||
personAddresses: [],
|
||||
@@ -76,36 +81,40 @@ const formKey = ref(0)
|
||||
|
||||
// Computed: unwrap patient data untuk form patient
|
||||
const patientFormInitialValues = computed(() => {
|
||||
const p = patient.value
|
||||
const p = patientDetail.value
|
||||
const person = p.person
|
||||
if (!person || !person.name) return undefined
|
||||
|
||||
const hasDisability = String(person?.disability).length > 0
|
||||
const date = parseISO(person.birthDate!)
|
||||
const birthDate = format(date, 'yyyy-MM-dd', { locale: localeID })
|
||||
|
||||
return {
|
||||
identityNumber: person.residentIdentityNumber || '',
|
||||
drivingLicenseNumber: person.drivingLicenseNumber || '',
|
||||
passportNumber: person.passportNumber || '',
|
||||
identityNumber: person.residentIdentityNumber || undefined,
|
||||
drivingLicenseNumber: person.drivingLicenseNumber || undefined,
|
||||
passportNumber: person.passportNumber || undefined,
|
||||
fullName: person.name || '',
|
||||
isNewBorn: (p.newBornStatus ? 'YA' : 'TIDAK') as 'YA' | 'TIDAK',
|
||||
isNewBorn: p.newBornStatus ? 'yes' : 'no',
|
||||
gender: person.gender_code || '',
|
||||
birthPlace: person.birthRegency_code || '',
|
||||
birthDate: person.birthDate ? new Date(person.birthDate).toISOString().split('T')[0] : '',
|
||||
birthDate: birthDate,
|
||||
education: person.education_code || '',
|
||||
job: person.occupation_code || '',
|
||||
maritalStatus: '', // perlu mapping jika ada field ini
|
||||
nationality: person.nationality || 'WNI',
|
||||
ethnicity: person.ethnic_code || '',
|
||||
language: person.language_code || '',
|
||||
religion: person.religion_code || '',
|
||||
communicationBarrier: (person.communicationIssueStatus ? 'YA' : 'TIDAK') as 'YA' | 'TIDAK',
|
||||
disability: (person.disability ? 'YA' : 'TIDAK') as 'YA' | 'TIDAK',
|
||||
disabilityType: person.disability || '',
|
||||
maritalStatus: person?.maritalStatus_code,
|
||||
nationality: person?.nationality || 'WNI',
|
||||
ethnicity: person?.ethnic_code || '',
|
||||
language: person?.language_code || '',
|
||||
religion: person?.religion_code || '',
|
||||
communicationBarrier: person?.communicationIssueStatus ? 'yes' : 'no',
|
||||
disability: hasDisability ? 'yes' : 'no',
|
||||
disabilityType: hasDisability ? String(person?.disability) || '' : '',
|
||||
note: '',
|
||||
}
|
||||
})
|
||||
|
||||
// Computed: unwrap alamat domisili (alamat sekarang)
|
||||
const addressFormInitialValues = computed(() => {
|
||||
const addresses = patient.value.person?.addresses || patient.value.personAddresses || []
|
||||
const addresses = patientDetail.value.person?.addresses || patientDetail.value.personAddresses || []
|
||||
const domicileAddress = addresses.find((a: PersonAddress) => a.locationType_code === 'domicile')
|
||||
if (!domicileAddress) return undefined
|
||||
|
||||
@@ -130,7 +139,7 @@ const addressFormInitialValues = computed(() => {
|
||||
|
||||
// Computed: unwrap alamat KTP (identity)
|
||||
const addressRelativeFormInitialValues = computed(() => {
|
||||
const addresses = patient.value.person?.addresses || patient.value.personAddresses || []
|
||||
const addresses = patientDetail.value.person?.addresses || patientDetail.value.personAddresses || []
|
||||
const domicileAddress = addresses.find((a: PersonAddress) => a.locationType_code === 'domicile')
|
||||
const identityAddress = addresses.find((a: PersonAddress) => a.locationType_code === 'identity')
|
||||
|
||||
@@ -177,7 +186,7 @@ const addressRelativeFormInitialValues = computed(() => {
|
||||
|
||||
// Computed: unwrap data orang tua
|
||||
const familyFormInitialValues = computed(() => {
|
||||
const relatives = patient.value.person?.relatives || patient.value.personRelatives || []
|
||||
const relatives = patientDetail.value.person?.relatives || patientDetail.value.personRelatives || []
|
||||
const parents = relatives.filter(
|
||||
(r: PersonRelative) => !r.responsible && (r.relationship_code === 'mother' || r.relationship_code === 'father'),
|
||||
)
|
||||
@@ -202,7 +211,7 @@ const familyFormInitialValues = computed(() => {
|
||||
|
||||
// Computed: unwrap kontak pasien
|
||||
const contactFormInitialValues = computed(() => {
|
||||
const contacts = patient.value.person?.contacts || patient.value.personContacts || []
|
||||
const contacts = patientDetail.value.person?.contacts || patientDetail.value.personContacts || []
|
||||
if (contacts.length === 0) return undefined
|
||||
|
||||
return {
|
||||
@@ -215,7 +224,7 @@ const contactFormInitialValues = computed(() => {
|
||||
|
||||
// Computed: unwrap penanggung jawab
|
||||
const responsibleFormInitialValues = computed(() => {
|
||||
const relatives = patient.value.person?.relatives || patient.value.personRelatives || []
|
||||
const relatives = patientDetail.value.person?.relatives || patientDetail.value.personRelatives || []
|
||||
const responsibles = relatives.filter((r: PersonRelative) => r.responsible === true)
|
||||
|
||||
if (responsibles.length === 0) return undefined
|
||||
@@ -252,7 +261,6 @@ async function composeFormData(): Promise<PatientEntity> {
|
||||
])
|
||||
|
||||
const results = [patient, address, addressRelative, families, contacts, emergencyContact]
|
||||
console.log(results)
|
||||
const allValid = results.every((r) => r?.valid)
|
||||
|
||||
// exit, if form errors happend during validation
|
||||
@@ -260,7 +268,15 @@ async function composeFormData(): Promise<PatientEntity> {
|
||||
if (!allValid) return Promise.reject('Form validation failed')
|
||||
|
||||
const formDataRequest: genPatientProps = {
|
||||
patient: patient?.values,
|
||||
patient: {
|
||||
...patient?.values,
|
||||
|
||||
// casting comp. val to backend well known reflect value
|
||||
birthDate: parseISO(patient?.values.birthDate || ''),
|
||||
isNewBorn: patient?.values.isNewBorn === 'yes',
|
||||
communicationBarrier: patient?.values.communicationBarrier === 'yes',
|
||||
disability: patient?.values.disability === 'yes' ? patient?.values.disabilityType : '',
|
||||
},
|
||||
residentAddress: address?.values,
|
||||
cardAddress: addressRelative?.values,
|
||||
familyData: families?.values,
|
||||
@@ -268,7 +284,7 @@ async function composeFormData(): Promise<PatientEntity> {
|
||||
responsible: emergencyContact?.values,
|
||||
}
|
||||
|
||||
const formData = genPatientEntity(formDataRequest)
|
||||
const formData = genPatientEntity(formDataRequest, patientDetail.value)
|
||||
|
||||
if (patient?.values.residentIdentityFile) {
|
||||
residentIdentityFile.value = patient?.values.residentIdentityFile
|
||||
@@ -288,12 +304,19 @@ async function loadInitData(id: number | string) {
|
||||
try {
|
||||
const response = await getPatientDetail(id as number)
|
||||
if (response.success) {
|
||||
patient.value = response.body.data || {}
|
||||
patientDetail.value = response.body.data || {}
|
||||
// Increment key untuk memaksa re-render form dengan data baru
|
||||
formKey.value++
|
||||
}
|
||||
} finally {
|
||||
|
||||
isProcessing.value = false
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
toast({
|
||||
title: 'Error',
|
||||
description: 'Terjadi kesalahan saat mengambil data',
|
||||
variant: 'destructive',
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -301,14 +324,29 @@ async function handleActionClick(eventType: string) {
|
||||
try {
|
||||
if (eventType === 'submit') {
|
||||
const patient: Patient = await composeFormData()
|
||||
let createdPatientId = 0
|
||||
|
||||
const response = await handleActionSave(
|
||||
patient,
|
||||
() => {},
|
||||
() => {},
|
||||
toast,
|
||||
)
|
||||
let createdPatientId = 0
|
||||
let response: any
|
||||
|
||||
// If edit mode, update patient
|
||||
if (props.mode === 'edit' && props.patientId) {
|
||||
response = await handleActionEdit(
|
||||
patientDetail.value.id,
|
||||
patient,
|
||||
() => {},
|
||||
() => {},
|
||||
toast,
|
||||
)
|
||||
}
|
||||
// If create mode, create patient
|
||||
else {
|
||||
response = await handleActionSave(
|
||||
patient,
|
||||
() => {},
|
||||
() => {},
|
||||
toast,
|
||||
)
|
||||
}
|
||||
|
||||
const data = (response?.body?.data ?? null) as PatientBase | null
|
||||
if (!data) return
|
||||
|
||||
@@ -137,18 +137,22 @@ provide('table_data_loader', isLoading)
|
||||
// #endregion
|
||||
|
||||
// #region Watchers
|
||||
watch([recId, recAction], () => {
|
||||
switch (recAction.value) {
|
||||
watch([recId, recAction], ([newId, newAction]) => {
|
||||
switch (newAction) {
|
||||
case ActionEvents.showDetail:
|
||||
navigateTo({
|
||||
name: 'client-patient-id',
|
||||
params: { id: recId.value },
|
||||
params: { id: newId },
|
||||
})
|
||||
break
|
||||
|
||||
case ActionEvents.showEdit:
|
||||
// TODO: Handle edit action
|
||||
// isFormEntryDialogOpen.value = true
|
||||
navigateTo({
|
||||
name: 'client-patient-id-edit',
|
||||
params: {
|
||||
id: newId,
|
||||
},
|
||||
})
|
||||
break
|
||||
|
||||
case ActionEvents.showConfirmDelete:
|
||||
|
||||
@@ -122,7 +122,7 @@ watch(
|
||||
<SelectTrigger
|
||||
:class="
|
||||
cn(
|
||||
'rounded-md focus:outline-none focus:ring-1 focus:ring-black dark:focus:ring-white',
|
||||
'h-8 rounded-md font-normal focus:outline-none focus:ring-1 focus:ring-black dark:focus:ring-white md:text-xs 2xl:h-9 2xl:text-sm',
|
||||
{
|
||||
'cursor-not-allowed bg-gray-100 opacity-50': isDisabled,
|
||||
'bg-white text-black dark:bg-gray-800 dark:text-white': !isDisabled,
|
||||
|
||||
+23
-21
@@ -1,12 +1,12 @@
|
||||
import { type Base, genBase } from './_base'
|
||||
import { type Person, genPerson } from './person'
|
||||
|
||||
import type { PatientFormData } from '~/schemas/patient.schema'
|
||||
import type { PersonAddressFormData } from '~/schemas/person-address.schema'
|
||||
import type { PersonAddressRelativeFormData } from '~/schemas/person-address-relative.schema'
|
||||
import type { PersonFamiliesFormData } from '~/schemas/person-family.schema'
|
||||
import type { PersonContactFormData } from '~/schemas/person-contact.schema'
|
||||
import type { PersonRelativeFormData } from '~/schemas/person-relative.schema'
|
||||
|
||||
import { genPerson, type Person } from './person'
|
||||
import type { PersonAddress } from './person-address'
|
||||
import type { PersonContact } from './person-contact'
|
||||
import type { PersonRelative } from './person-relative'
|
||||
@@ -14,11 +14,11 @@ import type { PersonRelative } from './person-relative'
|
||||
import { contactTypeMapping } from '~/lib/constants'
|
||||
|
||||
export interface PatientBase extends Base {
|
||||
person_id?: number
|
||||
person_id?: number | null
|
||||
newBornStatus?: boolean
|
||||
registeredAt?: Date | string | null
|
||||
status_code?: string
|
||||
number?: string
|
||||
status_code?: string | null
|
||||
number?: string | null
|
||||
}
|
||||
|
||||
export interface PatientEntity extends PatientBase {
|
||||
@@ -37,10 +37,10 @@ export interface genPatientProps {
|
||||
responsible: PersonRelativeFormData
|
||||
}
|
||||
|
||||
export function genPatientEntity(props: genPatientProps): PatientEntity {
|
||||
export function genPatientEntity(props: genPatientProps, patientData: PatientEntity | null): PatientEntity {
|
||||
const { patient, residentAddress, cardAddress, familyData, contacts, responsible } = props
|
||||
|
||||
const addresses: PersonAddress[] = [{ ...genBase(), person_id: 0, ...residentAddress }]
|
||||
const addresses: PersonAddress[] = [{ ...genBase(), person_id: patientData?.person?.id || 0, ...residentAddress }]
|
||||
const familiesContact: PersonRelative[] = []
|
||||
const personContacts: PersonContact[] = []
|
||||
|
||||
@@ -50,14 +50,14 @@ export function genPatientEntity(props: genPatientProps): PatientEntity {
|
||||
...genBase(),
|
||||
...residentAddress,
|
||||
person_id: 0,
|
||||
locationType_code: cardAddress.locationType_code || 'identity'
|
||||
locationType_code: cardAddress.locationType_code || 'identity',
|
||||
})
|
||||
} else {
|
||||
// jika alamat berbeda, tambahkan alamat relatif
|
||||
// Pastikan semua field yang diperlukan ada
|
||||
const relativeAddress = {
|
||||
...genBase(),
|
||||
person_id: 0,
|
||||
person_id: patientData?.person?.id || 0,
|
||||
locationType_code: cardAddress.locationType_code || 'identity',
|
||||
address: cardAddress.address || '',
|
||||
province_code: cardAddress.province_code || '',
|
||||
@@ -94,7 +94,7 @@ export function genPatientEntity(props: genPatientProps): PatientEntity {
|
||||
|
||||
personContacts.push({
|
||||
...genBase(),
|
||||
person_id: 0,
|
||||
person_id: patientData?.person?.id || 0,
|
||||
type_code: mappedContactType || '',
|
||||
value: contact.contactNumber,
|
||||
})
|
||||
@@ -117,15 +117,15 @@ export function genPatientEntity(props: genPatientProps): PatientEntity {
|
||||
|
||||
return {
|
||||
person: {
|
||||
id: 0,
|
||||
id: patientData?.person?.id || 0,
|
||||
name: patient.fullName,
|
||||
// alias: patient.alias,
|
||||
birthDate: patient.birthDate,
|
||||
birthRegency_code: patient.birthPlace,
|
||||
gender_code: patient.gender,
|
||||
residentIdentityNumber: patient.identityNumber,
|
||||
passportNumber: patient.passportNumber,
|
||||
drivingLicenseNumber: patient.drivingLicenseNumber,
|
||||
residentIdentityNumber: patient.identityNumber || null,
|
||||
passportNumber: patient.passportNumber || null,
|
||||
drivingLicenseNumber: patient.drivingLicenseNumber || null,
|
||||
religion_code: patient.religion,
|
||||
education_code: patient.education,
|
||||
occupation_code: patient.job,
|
||||
@@ -139,6 +139,8 @@ export function genPatientEntity(props: genPatientProps): PatientEntity {
|
||||
// passportFileUrl: patient.passportFileUrl,
|
||||
// drivingLicenseFileUrl: patient.drivingLicenseFileUrl,
|
||||
// familyIdentityFileUrl: patient.familyIdentityFileUrl,
|
||||
maritalStatus_code: patient.maritalStatus,
|
||||
|
||||
createdAt: new Date().toISOString(),
|
||||
updatedAt: new Date().toISOString(),
|
||||
deletedAt: null,
|
||||
@@ -149,9 +151,9 @@ export function genPatientEntity(props: genPatientProps): PatientEntity {
|
||||
registeredAt: new Date(),
|
||||
status_code: 'active',
|
||||
newBornStatus: patient.isNewBorn,
|
||||
person_id: 0,
|
||||
id: 0,
|
||||
number: '',
|
||||
person_id: patientData?.person?.id || null,
|
||||
id: patientData?.id || 0,
|
||||
number: patientData?.number || null,
|
||||
createdAt: new Date().toISOString(),
|
||||
updatedAt: new Date().toISOString(),
|
||||
deletedAt: null,
|
||||
@@ -160,12 +162,12 @@ export function genPatientEntity(props: genPatientProps): PatientEntity {
|
||||
|
||||
// New model
|
||||
export interface Patient extends Base {
|
||||
person_id?: number
|
||||
person_id?: number | null
|
||||
person: Person
|
||||
newBornStatus?: boolean
|
||||
registeredAt?: Date | string | null
|
||||
status_code?: string
|
||||
number?: string
|
||||
status_code?: string | null
|
||||
number?: string | null
|
||||
}
|
||||
|
||||
export function genPatient(): Patient {
|
||||
@@ -178,4 +180,4 @@ export function genPatient(): Patient {
|
||||
newBornStatus: false,
|
||||
person: genPerson(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+20
-19
@@ -9,27 +9,28 @@ import type { Regency } from './regency'
|
||||
export interface Person extends Base {
|
||||
name: string
|
||||
// alias?: string
|
||||
frontTitle?: string
|
||||
endTitle?: string
|
||||
frontTitle?: string | null
|
||||
endTitle?: string | null
|
||||
birthDate?: string
|
||||
birthRegency_code?: string
|
||||
gender_code?: string
|
||||
residentIdentityNumber?: string
|
||||
passportNumber?: string
|
||||
drivingLicenseNumber?: string
|
||||
religion_code?: string
|
||||
education_code?: string
|
||||
occupation_code?: string
|
||||
occupation_name?: string
|
||||
ethnic_code?: string
|
||||
language_code?: string
|
||||
nationality?: string
|
||||
birthRegency_code?: string | null
|
||||
gender_code?: string | null
|
||||
residentIdentityNumber?: string | null
|
||||
passportNumber?: string | null
|
||||
drivingLicenseNumber?: string | null
|
||||
religion_code?: string | null
|
||||
education_code?: string | null
|
||||
occupation_code?: string | null
|
||||
occupation_name?: string | null
|
||||
ethnic_code?: string | null
|
||||
language_code?: string | null
|
||||
nationality?: string | null
|
||||
communicationIssueStatus?: boolean
|
||||
disability?: string
|
||||
residentIdentityFileUrl?: string
|
||||
passportFileUrl?: string
|
||||
drivingLicenseFileUrl?: string
|
||||
familyIdentityFileUrl?: string
|
||||
disability?: string | null
|
||||
residentIdentityFileUrl?: string | null
|
||||
passportFileUrl?: string | null
|
||||
drivingLicenseFileUrl?: string | null
|
||||
familyIdentityFileUrl?: string | null
|
||||
maritalStatus_code?: string
|
||||
|
||||
// preload data for detail patient
|
||||
birthRegency?: Regency | null
|
||||
|
||||
@@ -1,17 +1,5 @@
|
||||
import { z } from 'zod'
|
||||
|
||||
const CommunicationBarrierSchema = z
|
||||
.enum(['YA', 'TIDAK'], {
|
||||
required_error: 'Mohon lengkapi Status Hambatan Berkomunikasi',
|
||||
})
|
||||
.transform((val) => val === 'YA')
|
||||
|
||||
const IsNewBornSchema = z
|
||||
.enum(['YA', 'TIDAK'], {
|
||||
required_error: 'Mohon lengkapi status pasien',
|
||||
})
|
||||
.transform((val) => val === 'YA')
|
||||
|
||||
const ACCEPTED_UPLOAD_TYPES = ['image/jpeg', 'image/png', 'application/pdf']
|
||||
|
||||
const PatientSchema = z
|
||||
@@ -40,12 +28,7 @@ const PatientSchema = z
|
||||
message: 'Format file harus JPG, PNG, atau PDF',
|
||||
})
|
||||
.refine((f) => !f || f.size <= 1 * 1024 * 1024, { message: 'Maksimal 1MB' }),
|
||||
// .refine(f => ['image/jpeg', 'image/png'].includes(f.type), 'Hanya JPG/PNG')
|
||||
|
||||
// Informasi Dasar
|
||||
// alias: z.string({
|
||||
// required_error: 'Mohon pilih sapaan',
|
||||
// }),
|
||||
fullName: z.string({
|
||||
required_error: 'Mohon lengkapi Nama',
|
||||
}),
|
||||
@@ -54,35 +37,15 @@ const PatientSchema = z
|
||||
required_error: 'Mohon lengkapi Tempat Lahir',
|
||||
})
|
||||
.min(1, 'Mohon lengkapi Tempat Lahir'),
|
||||
birthDate: z
|
||||
.string({
|
||||
required_error: 'Mohon lengkapi Tanggal Lahir',
|
||||
})
|
||||
.refine(
|
||||
(date) => {
|
||||
// Jika kosong, return false untuk required validation
|
||||
if (!date || date.trim() === '') return false
|
||||
|
||||
// Jika ada isi, validasi format tanggal
|
||||
try {
|
||||
const dateObj = new Date(date)
|
||||
// Cek apakah tanggal valid dan tahun >= 1900
|
||||
return !isNaN(dateObj.getTime()) && dateObj.getFullYear() >= 1900
|
||||
} catch {
|
||||
return false
|
||||
}
|
||||
},
|
||||
{
|
||||
message: 'Mohon lengkapi Tanggal Lahir dengan format yang valid',
|
||||
},
|
||||
)
|
||||
.transform((dateStr) => new Date(dateStr).toISOString()),
|
||||
birthDate: z.string({
|
||||
required_error: 'Mohon lengkapi Tanggal Lahir',
|
||||
}),
|
||||
|
||||
// Jenis Kelamin & Status
|
||||
gender: z.string({
|
||||
required_error: 'Pilih Jenis Kelamin',
|
||||
}),
|
||||
maritalStatus: z.enum(['TIDAK_DIKETAHUI', 'BELUM_KAWIN', 'KAWIN', 'CERAI_HIDUP', 'CERAI_MATI'], {
|
||||
maritalStatus: z.enum(['S', 'M', 'D', 'W'], {
|
||||
required_error: 'Pilih Status Perkawinan',
|
||||
}),
|
||||
|
||||
@@ -95,7 +58,9 @@ const PatientSchema = z
|
||||
nationality: z.string({
|
||||
required_error: 'Pilih Kebangsaan',
|
||||
}),
|
||||
isNewBorn: IsNewBornSchema,
|
||||
isNewBorn: z.string({
|
||||
required_error: 'Mohon lengkapi status pasien',
|
||||
}),
|
||||
language: z.string({
|
||||
required_error: 'Mohon pilih Preferensi Bahasa',
|
||||
}),
|
||||
@@ -112,65 +77,40 @@ const PatientSchema = z
|
||||
ethnicity: z.string().optional(),
|
||||
|
||||
// Disabilitas
|
||||
disability: z.enum(['YA', 'TIDAK'], {
|
||||
disability: z.string({
|
||||
required_error: 'Mohon lengkapi Status Disabilitas',
|
||||
}),
|
||||
disabilityType: z.string().optional(),
|
||||
|
||||
// Informasi Kontak
|
||||
passportNumber: z.string().optional(),
|
||||
communicationBarrier: CommunicationBarrierSchema,
|
||||
communicationBarrier: z.string({
|
||||
required_error: 'Mohon lengkapi Status Hambatan Berkomunikasi',
|
||||
}),
|
||||
|
||||
note: z.string().optional(),
|
||||
drivingLicenseNumber: z.string().optional(),
|
||||
})
|
||||
.refine(
|
||||
(data) => {
|
||||
// Jika disability = 'TIDAK', maka disabilityType harus kosong atau undefined
|
||||
if (data.disability === 'TIDAK') {
|
||||
return !data.disabilityType || data.disabilityType.trim() === ''
|
||||
}
|
||||
return true
|
||||
},
|
||||
{
|
||||
message: "Jenis Disabilitas harus kosong jika Status Disabilitas = 'TIDAK'",
|
||||
path: ['disabilityType'],
|
||||
},
|
||||
)
|
||||
.refine(
|
||||
(data) => {
|
||||
// Jika disability = 'YA', maka disabilityType wajib diisi
|
||||
if (data.disability === 'YA') {
|
||||
if (data.disability === 'yes') {
|
||||
return !!data.disabilityType?.trim()
|
||||
}
|
||||
return true
|
||||
},
|
||||
{
|
||||
message: 'Mohon pilih Jenis Disabilitas',
|
||||
message: 'Mohon pilih jenis Disabilitas',
|
||||
path: ['disabilityType'],
|
||||
},
|
||||
)
|
||||
// .refine((data) => {
|
||||
// // Jika nationality = 'WNA', maka passportNumber wajib diisi
|
||||
// if (data.nationality === 'WNA') {
|
||||
// return !!data.passportNumber?.trim()
|
||||
// }
|
||||
// return true
|
||||
// }, {
|
||||
// message: 'Nomor Paspor wajib diisi untuk Warga Negara Asing',
|
||||
// path: ['passportNumber'],
|
||||
// })
|
||||
.transform((data) => {
|
||||
// Transform untuk backend: hanya kirim disabilityType sesuai kondisi
|
||||
return {
|
||||
...data,
|
||||
// Jika disability = 'YA', kirim disabilityType
|
||||
// Jika disability = 'TIDAK', kirim null untuk disabilityType
|
||||
disabilityType: data.disability === 'YA' ? data.disabilityType : null,
|
||||
// Hapus field disability karena yang dikirim ke backend adalah disabilityType
|
||||
disability: undefined,
|
||||
}
|
||||
})
|
||||
// .transform((data) => {
|
||||
// return {
|
||||
// ...data,
|
||||
// // newBornStatus: data.isNewBorn === 'yes',
|
||||
// // communicationBarrier: data.communicationBarrier === 'yes',
|
||||
// // disability: data.disability ? data.disabilityType : null,
|
||||
// }
|
||||
// })
|
||||
|
||||
type PatientFormData = z.infer<typeof PatientSchema>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user