Files
simrsx-fe/app/components/app/patient/entry-form.vue
Khafid Prayoga 51725d7f73 feat(patient): doc preview, integration to select ethnic and lang (#229)
* impl: opts for ethnic and lang

* fix: doc related for preview

fix: upload dokumen kk dan ktp

fix dokumen preview

fix: add preview doc on edit form
2025-12-12 16:12:24 +07:00

381 lines
10 KiB
Vue

<script setup lang="ts">
import { useForm } from 'vee-validate'
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'
import { Skeleton } from '~/components/pub/ui/skeleton'
import { SelectBirthPlace } from '~/components/app/person/fields'
import {
InputName,
RadioCommunicationBarrier,
RadioDisability,
SelectGender,
RadioNationality,
RadioNewborn,
SelectDisability,
SelectDob,
SelectEducation,
SelectEthnicity,
SelectJob,
SelectLang as SelectLanguage,
SelectMaritalStatus,
SelectReligion,
} from './fields'
interface FormData extends PatientFormData {
_calculatedAge: string | Date
}
// Type untuk initial values (sebelum transform schema)
interface PatientFormInput {
identityNumber?: string
drivingLicenseNumber?: string
passportNumber?: string
fullName?: string
isNewBorn?: string
gender?: string
birthPlace?: string
birthDate?: string
education?: string
job?: string
maritalStatus?: string
nationality?: string
ethnicity?: string
language?: string
religion?: string
communicationBarrier?: string
disability?: string
disabilityType?: string
note?: string
residentIdentityFile?: File
familyIdentityFile?: File
}
interface Props {
isReadonly: boolean
languageOptions?: { value: string; label: string }[]
ethnicOptions?: { value: string; label: string }[]
initialValues?: PatientFormInput
mode?: 'add' | 'edit'
identityFileUrl?: string
familyCardFileUrl?: string
}
const props = defineProps<Props>()
const emit = defineEmits<{
(e: 'preview', url: string): void
}>()
const formSchema = toTypedSchema(PatientSchema)
const { values, resetForm, setValues, setFieldValue, validate, setFieldError } = useForm<FormData>({
name: 'patientForm',
validationSchema: formSchema,
initialValues: (props.initialValues ?? {}) as any,
validateOnMount: false,
})
defineExpose({
validate,
resetForm,
setValues,
values,
})
watch(
() => values.birthDate,
(newValue) => {
if (newValue) {
setFieldValue('_calculatedAge', calculateAge(newValue))
}
},
{
immediate: true,
},
)
watch(
() => values.disability,
(newValue) => {
if (newValue === 'no') {
setFieldValue('disabilityType', undefined)
}
},
)
</script>
<template>
<form @submit.prevent="">
<p class="mb-2 text-sm font-semibold 2xl:mb-3 2xl:text-base">Data Diri Pasien</p>
<DE.Block
:col-count="4"
:cell-flex="false"
>
<InputBase
field-name="identityNumber"
label="No. KTP"
placeholder="Masukkan NIK"
numeric-only
:is-disabled="isReadonly"
:max-length="16"
/>
<InputBase
field-name="drivingLicenseNumber"
label="No. SIM"
placeholder="Masukkan nomor SIM"
numeric-only
:max-length="20"
:is-disabled="isReadonly"
/>
<InputBase
field-name="passportNumber"
label="No. Paspor"
placeholder="Masukkan nomor paspor"
:max-length="20"
:is-disabled="isReadonly"
/>
<InputName
field-name-alias="alias"
field-name-input="fullName"
label-for-input="Nama Lengkap"
placeholder="Masukkan nama lengkap pasien"
is-required
:is-disabled="isReadonly"
/>
<RadioNewborn
field-name="isNewBorn"
label="Pasien Bayi"
placeholder="Pilih status pasien"
is-required
:is-disabled="isReadonly"
/>
<SelectGender
field-name="gender"
label="Jenis Kelamin"
placeholder="Pilih jenis kelamin"
is-required
:is-disabled="isReadonly"
/>
<SelectBirthPlace
field-name="birthPlace"
label="Tempat Lahir"
placeholder="Pilih tempat lahir"
is-required
:is-disabled="isReadonly"
/>
<SelectDob
field-name="birthDate"
label="Tanggal Lahir"
is-required
:is-disabled="isReadonly"
/>
<InputBase
field-name="_calculatedAge"
label="Usia"
placeholder="-"
numeric-only
is-disabled
/>
<SelectEducation
field-name="education"
label="Pendidikan"
placeholder="Pilih pendidikan"
is-required
:is-disabled="isReadonly"
/>
<SelectJob
field-name="job"
label="Pekerjaan"
placeholder="Pilih pekerjaan"
is-required
:is-disabled="isReadonly"
/>
<SelectMaritalStatus
field-name="maritalStatus"
label="Status Perkawinan"
placeholder="Pilih status Perkawinan"
is-required
:is-disabled="isReadonly"
/>
<DE.Cell />
<RadioNationality
field-name="nationality"
label="Kebangsaan"
placeholder="Pilih kebangsaan"
is-required
:is-disabled="isReadonly"
/>
<SelectEthnicity
field-name="ethnicity"
label="Suku"
placeholder="Pilih suku bangsa"
:is-disabled="isReadonly || values.nationality !== 'WNI'"
:items="ethnicOptions || []"
/>
<SelectLanguage
field-name="language"
label="Bahasa"
placeholder="Pilih preferensi bahasa"
is-required
:is-disabled="isReadonly"
:items="languageOptions || []"
/>
<SelectReligion
field-name="religion"
label="Agama"
placeholder="Pilih agama"
is-required
:is-disabled="isReadonly"
/>
<RadioCommunicationBarrier
field-name="communicationBarrier"
label="Hambatan Berkomunikasi"
is-required
:is-disabled="isReadonly"
/>
<RadioDisability
field-name="disability"
label="Disabilitas"
is-required
:is-disabled="isReadonly"
/>
<SelectDisability
label="Jenis Disabilitas"
field-name="disabilityType"
:is-disabled="isReadonly || values.disability !== 'yes'"
:is-required="values.disability === 'yes'"
/>
<InputBase
field-name="note"
label="Kepercayaan"
placeholder="Contoh: tidak ingin diperiksa oleh dokter laki-laki"
:is-disabled="isReadonly"
/>
</DE.Block>
<div class="h-6"></div>
<p class="mb-2 text-sm font-semibold 2xl:mb-3 2xl:text-base">Dokumen Identitas</p>
<DE.Block
:col-count="2"
:cell-flex="false"
>
<div class="flex flex-col gap-3">
<FileUpload
field-name="residentIdentityFile"
label="Dokumen KTP"
placeholder="Unggah scan dokumen KTP"
:accept="['pdf', 'jpg', 'png']"
:max-size-mb="1"
:is-disabled="isReadonly"
/>
<!-- Embed Preview Dokumen KTP (mode edit) -->
<div
v-if="mode === 'edit'"
class="flex flex-col gap-1"
>
<span class="text-xs text-muted-foreground">Dokumen Tersimpan</span>
<div
class="relative h-28 w-48 cursor-pointer overflow-hidden rounded-md border bg-muted"
:class="{ 'cursor-not-allowed opacity-60': !identityFileUrl }"
@click="identityFileUrl && emit('preview', identityFileUrl)"
>
<img
v-if="identityFileUrl"
:src="identityFileUrl"
alt="Dokumen KTP"
class="h-full w-full object-cover"
/>
<Skeleton
v-else
class="h-full w-full"
/>
<!-- Overlay -->
<div
class="absolute inset-0 flex items-center justify-center bg-black/50 transition-opacity"
:class="identityFileUrl ? 'opacity-0 hover:opacity-100' : 'opacity-100'"
>
<div
v-if="identityFileUrl"
class="flex flex-col items-center gap-1 text-white"
>
<span class="i-lucide-eye h-5 w-5" />
<span class="text-xs font-medium">Lihat Dokumen</span>
</div>
<div
v-else
class="flex flex-col items-center gap-1 text-white/70"
>
<span class="i-lucide-info h-5 w-5" />
<span class="text-xs font-medium">No Data</span>
</div>
</div>
</div>
</div>
</div>
<div class="flex flex-col gap-3">
<FileUpload
field-name="familyIdentityFile"
label="Dokumen KK"
placeholder="Unggah scan dokumen KK"
:accept="['pdf', 'jpg', 'png']"
:max-size-mb="1"
:is-disabled="isReadonly"
/>
<!-- Embed Preview Dokumen KK (mode edit) -->
<div
v-if="mode === 'edit'"
class="flex flex-col gap-1"
>
<span class="text-xs text-muted-foreground">Dokumen Tersimpan</span>
<div
class="relative h-28 w-48 cursor-pointer overflow-hidden rounded-md border bg-muted"
:class="{ 'cursor-not-allowed opacity-60': !familyCardFileUrl }"
@click="familyCardFileUrl && emit('preview', familyCardFileUrl)"
>
<img
v-if="familyCardFileUrl"
:src="familyCardFileUrl"
alt="Dokumen KK"
class="h-full w-full object-cover"
/>
<Skeleton
v-else
class="h-full w-full"
/>
<!-- Overlay -->
<div
class="absolute inset-0 flex items-center justify-center bg-black/50 transition-opacity"
:class="familyCardFileUrl ? 'opacity-0 hover:opacity-100' : 'opacity-100'"
>
<div
v-if="familyCardFileUrl"
class="flex flex-col items-center gap-1 text-white"
>
<span class="i-lucide-eye h-5 w-5" />
<span class="text-xs font-medium">Lihat Dokumen</span>
</div>
<div
v-else
class="flex flex-col items-center gap-1 text-white/70"
>
<span class="i-lucide-info h-5 w-5" />
<span class="text-xs font-medium">No Data</span>
</div>
</div>
</div>
</div>
</div>
</DE.Block>
</form>
</template>