* 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
381 lines
10 KiB
Vue
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>
|