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
This commit is contained in:
@@ -11,6 +11,7 @@ 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,
|
||||
@@ -60,10 +61,20 @@ interface PatientFormInput {
|
||||
|
||||
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>({
|
||||
@@ -208,6 +219,7 @@ watch(
|
||||
label="Suku"
|
||||
placeholder="Pilih suku bangsa"
|
||||
:is-disabled="isReadonly || values.nationality !== 'WNI'"
|
||||
:items="ethnicOptions || []"
|
||||
/>
|
||||
<SelectLanguage
|
||||
field-name="language"
|
||||
@@ -215,6 +227,7 @@ watch(
|
||||
placeholder="Pilih preferensi bahasa"
|
||||
is-required
|
||||
:is-disabled="isReadonly"
|
||||
:items="languageOptions || []"
|
||||
/>
|
||||
<SelectReligion
|
||||
field-name="religion"
|
||||
@@ -256,22 +269,112 @@ watch(
|
||||
:col-count="2"
|
||||
:cell-flex="false"
|
||||
>
|
||||
<FileUpload
|
||||
field-name="identityCardFile"
|
||||
label="Dokumen KTP"
|
||||
placeholder="Unggah scan dokumen KTP"
|
||||
:accept="['pdf', 'jpg', 'png']"
|
||||
:max-size-mb="1"
|
||||
:is-disabled="isReadonly"
|
||||
/>
|
||||
<FileUpload
|
||||
field-name="familyCardFile"
|
||||
label="Dokumen KK"
|
||||
placeholder="Unggah scan dokumen KK"
|
||||
:accept="['pdf', 'jpg', 'png']"
|
||||
:max-size-mb="1"
|
||||
:is-disabled="isReadonly"
|
||||
/>
|
||||
<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>
|
||||
|
||||
@@ -6,6 +6,7 @@ import { cn } from '~/lib/utils'
|
||||
import * as DE from '~/components/pub/my-ui/doc-entry'
|
||||
|
||||
const props = defineProps<{
|
||||
items: { value: string; label: string }[]
|
||||
fieldName?: string
|
||||
label?: string
|
||||
placeholder?: string
|
||||
@@ -28,23 +29,23 @@ const {
|
||||
labelClass,
|
||||
} = props
|
||||
|
||||
const ethnicOptions = [
|
||||
{ label: 'Tidak diketahui', value: 'unknown', priority: 1 },
|
||||
{ label: 'Jawa', value: 'jawa' },
|
||||
{ label: 'Sunda', value: 'sunda' },
|
||||
{ label: 'Batak', value: 'batak' },
|
||||
{ label: 'Betawi', value: 'betawi' },
|
||||
{ label: 'Minangkabau', value: 'minangkabau' },
|
||||
{ label: 'Bugis', value: 'bugis' },
|
||||
{ label: 'Madura', value: 'madura' },
|
||||
{ label: 'Banjar', value: 'banjar' },
|
||||
{ label: 'Bali', value: 'bali' },
|
||||
{ label: 'Dayak', value: 'dayak' },
|
||||
{ label: 'Aceh', value: 'aceh' },
|
||||
{ label: 'Sasak', value: 'sasak' },
|
||||
{ label: 'Papua', value: 'papua' },
|
||||
{ label: 'Lainnya', value: 'lainnya', priority: -100 },
|
||||
]
|
||||
// const ethnicOptions = [
|
||||
// { label: 'Tidak diketahui', value: 'unknown', priority: 1 },
|
||||
// { label: 'Jawa', value: 'jawa' },
|
||||
// { label: 'Sunda', value: 'sunda' },
|
||||
// { label: 'Batak', value: 'batak' },
|
||||
// { label: 'Betawi', value: 'betawi' },
|
||||
// { label: 'Minangkabau', value: 'minangkabau' },
|
||||
// { label: 'Bugis', value: 'bugis' },
|
||||
// { label: 'Madura', value: 'madura' },
|
||||
// { label: 'Banjar', value: 'banjar' },
|
||||
// { label: 'Bali', value: 'bali' },
|
||||
// { label: 'Dayak', value: 'dayak' },
|
||||
// { label: 'Aceh', value: 'aceh' },
|
||||
// { label: 'Sasak', value: 'sasak' },
|
||||
// { label: 'Papua', value: 'papua' },
|
||||
// { label: 'Lainnya', value: 'lainnya', priority: -100 },
|
||||
// ]
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -70,7 +71,7 @@ const ethnicOptions = [
|
||||
<Combobox
|
||||
:id="fieldName"
|
||||
v-bind="componentField"
|
||||
:items="ethnicOptions"
|
||||
:items="items"
|
||||
:placeholder="placeholder"
|
||||
:is-disabled="isDisabled"
|
||||
search-placeholder="Cari..."
|
||||
|
||||
@@ -6,6 +6,7 @@ import { cn } from '~/lib/utils'
|
||||
import * as DE from '~/components/pub/my-ui/doc-entry'
|
||||
|
||||
const props = defineProps<{
|
||||
items: { value: string; label: string }[]
|
||||
fieldName?: string
|
||||
label?: string
|
||||
placeholder?: string
|
||||
@@ -29,15 +30,15 @@ const {
|
||||
labelClass,
|
||||
} = props
|
||||
|
||||
const langOptions = [
|
||||
{ label: 'Bahasa Indonesia', value: 'id', priority: 1 },
|
||||
{ label: 'Bahasa Jawa', value: 'jawa' },
|
||||
{ label: 'Bahasa Sunda', value: 'sunda' },
|
||||
{ label: 'Bahasa Bali', value: 'bali' },
|
||||
{ label: 'Bahasa Jaksel', value: 'jaksel' },
|
||||
{ label: 'Bahasa Inggris', value: 'en' },
|
||||
{ label: 'Tidak Diketahui', value: 'unknown', priority: 100 },
|
||||
]
|
||||
// const langOptions = [
|
||||
// { label: 'Bahasa Indonesia', value: 'id', priority: 1 },
|
||||
// { label: 'Bahasa Jawa', value: 'jawa' },
|
||||
// { label: 'Bahasa Sunda', value: 'sunda' },
|
||||
// { label: 'Bahasa Bali', value: 'bali' },
|
||||
// { label: 'Bahasa Jaksel', value: 'jaksel' },
|
||||
// { label: 'Bahasa Inggris', value: 'en' },
|
||||
// { label: 'Tidak Diketahui', value: 'unknown', priority: 100 },
|
||||
// ]
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -64,7 +65,7 @@ const langOptions = [
|
||||
:is-disabled="isDisabled"
|
||||
:id="fieldName"
|
||||
v-bind="componentField"
|
||||
:items="langOptions"
|
||||
:items="items"
|
||||
:placeholder="placeholder"
|
||||
:preserve-order="false"
|
||||
:class="
|
||||
|
||||
@@ -38,6 +38,7 @@ const props = defineProps<{
|
||||
const emit = defineEmits<{
|
||||
(e: 'back'): void
|
||||
(e: 'edit'): void
|
||||
(e: 'preview', url: string): void
|
||||
}>()
|
||||
|
||||
// #endregion
|
||||
@@ -70,6 +71,14 @@ const patientAge = computed(() => {
|
||||
}
|
||||
return calculateAge(props.patient.person.birthDate)
|
||||
})
|
||||
|
||||
const familiyCardFile = computed(() => {
|
||||
return props.patient.person.familyIdentityFileUrl || ''
|
||||
})
|
||||
const identityFile = computed(() => {
|
||||
return props.patient.person.residentIdentityFileUrl || ''
|
||||
})
|
||||
|
||||
// #endregion
|
||||
|
||||
// #region Lifecycle Hooks
|
||||
@@ -166,13 +175,85 @@ function onNavigate(type: ClickType) {
|
||||
<AccordionTrigger>{{ section }}</AccordionTrigger>
|
||||
<AccordionContent>
|
||||
<div class="grid grid-cols-2 gap-4">
|
||||
<!-- Dokumen KTP -->
|
||||
<div class="flex flex-col gap-2">
|
||||
<span class="text-sm text-muted-foreground">Dokumen KTP</span>
|
||||
<Skeleton class="h-32 w-64 rounded-md" />
|
||||
<div
|
||||
class="relative h-32 w-64 cursor-pointer overflow-hidden rounded-md border bg-muted"
|
||||
:class="{ 'cursor-not-allowed opacity-60': !identityFile }"
|
||||
@click="identityFile && emit('preview', identityFile)"
|
||||
>
|
||||
<img
|
||||
v-if="identityFile"
|
||||
:src="identityFile"
|
||||
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="identityFile ? 'opacity-0 hover:opacity-100' : 'opacity-100'"
|
||||
>
|
||||
<div
|
||||
v-if="identityFile"
|
||||
class="flex flex-col items-center gap-1 text-white"
|
||||
>
|
||||
<span class="i-lucide-eye h-6 w-6" />
|
||||
<span class="text-sm 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-6 w-6" />
|
||||
<span class="text-sm font-medium">No Data</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Dokumen KK -->
|
||||
<div class="flex flex-col gap-2">
|
||||
<span class="text-sm text-muted-foreground">Dokumen KK</span>
|
||||
<Skeleton class="h-32 w-64 rounded-md" />
|
||||
<div
|
||||
class="relative h-32 w-64 cursor-pointer overflow-hidden rounded-md border bg-muted"
|
||||
:class="{ 'cursor-not-allowed opacity-60': !familiyCardFile }"
|
||||
@click="familiyCardFile && emit('preview', familiyCardFile)"
|
||||
>
|
||||
<img
|
||||
v-if="familiyCardFile"
|
||||
:src="familiyCardFile"
|
||||
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="familiyCardFile ? 'opacity-0 hover:opacity-100' : 'opacity-100'"
|
||||
>
|
||||
<div
|
||||
v-if="familiyCardFile"
|
||||
class="flex flex-col items-center gap-1 text-white"
|
||||
>
|
||||
<span class="i-lucide-eye h-6 w-6" />
|
||||
<span class="text-sm 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-6 w-6" />
|
||||
<span class="text-sm font-medium">No Data</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</AccordionContent>
|
||||
|
||||
@@ -6,6 +6,8 @@ import type { Person } from '~/models/person'
|
||||
|
||||
// Components
|
||||
import Header from '~/components/pub/my-ui/nav-header/prep.vue'
|
||||
import Dialog from '~/components/pub/my-ui/modal/dialog.vue'
|
||||
import DocPreviewDialog from '~/components/pub/my-ui/modal/doc-preview-dialog.vue'
|
||||
|
||||
import { getPatientDetail } from '~/services/patient.service'
|
||||
|
||||
@@ -17,6 +19,9 @@ const props = defineProps<{
|
||||
// #endregion
|
||||
|
||||
// #region State & Computed
|
||||
const isDocPreviewDialogOpen = ref<boolean>(false)
|
||||
const docPreviewUrl = ref<string>('')
|
||||
|
||||
const patient = ref(
|
||||
withBase<PatientEntity>({
|
||||
person: {} as Person,
|
||||
@@ -80,6 +85,19 @@ async function onEdit() {
|
||||
:patient="patient"
|
||||
@back="onBack"
|
||||
@edit="onEdit"
|
||||
@preview="
|
||||
(url: string) => {
|
||||
docPreviewUrl = url
|
||||
isDocPreviewDialogOpen = true
|
||||
}
|
||||
"
|
||||
/>
|
||||
<Dialog
|
||||
v-model:open="isDocPreviewDialogOpen"
|
||||
title="Preview Dokumen"
|
||||
size="2xl"
|
||||
>
|
||||
<DocPreviewDialog :link="docPreviewUrl" />
|
||||
</Dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -17,6 +17,8 @@ import AppPersonAddressEntryFormRelative from '~/components/app/person-address/e
|
||||
import AppPersonFamilyParentsForm from '~/components/app/person/family-parents-form.vue'
|
||||
import AppPersonContactEntryForm from '~/components/app/person-contact/entry-form.vue'
|
||||
import AppPersonRelativeEntryForm from '~/components/app/person-relative/entry-form.vue'
|
||||
import Dialog from '~/components/pub/my-ui/modal/dialog.vue'
|
||||
import DocPreviewDialog from '~/components/pub/my-ui/modal/doc-preview-dialog.vue'
|
||||
|
||||
// helper
|
||||
import { format, parseISO } from 'date-fns'
|
||||
@@ -24,6 +26,8 @@ import { id as localeID } from 'date-fns/locale'
|
||||
|
||||
// services
|
||||
import { getPatientDetail, uploadAttachment } from '~/services/patient.service'
|
||||
import { getValueLabelList as getEthnicOpts } from '~/services/ethnic.service'
|
||||
import { getValueLabelList as getLanguageOpts } from '~/services/language.service'
|
||||
|
||||
import { isReadonly, isProcessing, handleActionSave, handleActionEdit } from '~/handlers/patient.handler'
|
||||
|
||||
@@ -47,6 +51,23 @@ const props = defineProps<{
|
||||
const residentIdentityFile = ref<File>()
|
||||
const familyCardFile = ref<File>()
|
||||
|
||||
// Dialog preview dokumen
|
||||
const isDocPreviewDialogOpen = ref<boolean>(false)
|
||||
const docPreviewUrl = ref<string>('')
|
||||
|
||||
// Computed untuk URL dokumen tersimpan
|
||||
const identityFileUrl = computed(() => {
|
||||
return patientDetail.value.person?.residentIdentityFileUrl || ''
|
||||
})
|
||||
const familyCardFileUrl = computed(() => {
|
||||
return patientDetail.value.person?.familyIdentityFileUrl || ''
|
||||
})
|
||||
|
||||
function handlePreviewDoc(url: string) {
|
||||
docPreviewUrl.value = url
|
||||
isDocPreviewDialogOpen.value = true
|
||||
}
|
||||
|
||||
// form related state
|
||||
const personAddressForm = ref<ExposedForm<any> | null>(null)
|
||||
const personAddressRelativeForm = ref<ExposedForm<any> | null>(null)
|
||||
@@ -58,6 +79,9 @@ const personPatientForm = ref<ExposedForm<any> | null>(null)
|
||||
// #endregion
|
||||
|
||||
// #region State & Computed
|
||||
const ethnicOptions = ref<{ value: string; label: string }[]>([])
|
||||
const languageOptions = ref<{ value: string; label: string }[]>([])
|
||||
|
||||
const patientDetail = ref(
|
||||
withBase<PatientEntity>({
|
||||
person: {} as Person,
|
||||
@@ -223,6 +247,13 @@ const responsibleFormInitialValues = computed(() => {
|
||||
|
||||
// #region Lifecycle Hooks
|
||||
onMounted(async () => {
|
||||
const optsReq = {
|
||||
'page-no-limit': true,
|
||||
}
|
||||
|
||||
ethnicOptions.value = await getEthnicOpts(optsReq, true)
|
||||
languageOptions.value = await getLanguageOpts(optsReq, true)
|
||||
|
||||
// if edit mode, fetch patient detail
|
||||
if (props.mode === 'edit' && props.patientId) {
|
||||
await loadInitData(props.patientId)
|
||||
@@ -309,7 +340,9 @@ async function handleActionClick(eventType: string) {
|
||||
const patient: Patient = await composeFormData()
|
||||
|
||||
let createdPatientId = 0
|
||||
let createdPersonId = 0
|
||||
let response: any
|
||||
|
||||
// return
|
||||
if (props.mode === 'edit' && props.patientId) {
|
||||
response = await handleActionEdit(
|
||||
@@ -330,13 +363,15 @@ async function handleActionClick(eventType: string) {
|
||||
|
||||
const data = (response?.body?.data ?? null) as PatientBase | null
|
||||
if (!data) return
|
||||
|
||||
createdPatientId = data.id
|
||||
createdPersonId = data.person_id!
|
||||
|
||||
if (residentIdentityFile.value) {
|
||||
void uploadAttachment(residentIdentityFile.value, createdPatientId, 'ktp')
|
||||
void uploadAttachment(residentIdentityFile.value, createdPersonId, 'ktp')
|
||||
}
|
||||
if (familyCardFile.value) {
|
||||
void uploadAttachment(familyCardFile.value, createdPatientId, 'kk')
|
||||
void uploadAttachment(familyCardFile.value, createdPersonId, 'kk')
|
||||
}
|
||||
|
||||
// If has callback provided redirect to callback with patientData
|
||||
@@ -435,10 +470,16 @@ watch(
|
||||
{{ mode === 'edit' ? 'Edit Pasien' : 'Tambah Pasien' }}
|
||||
</div>
|
||||
<AppPatientEntryForm
|
||||
:key="`patient-${formKey}`"
|
||||
ref="personPatientForm"
|
||||
:key="`patient-${formKey}`"
|
||||
:is-readonly="isProcessing || isReadonly"
|
||||
:initial-values="patientFormInitialValues"
|
||||
:language-options="languageOptions"
|
||||
:ethnic-options="ethnicOptions"
|
||||
:mode="mode"
|
||||
:identity-file-url="identityFileUrl"
|
||||
:family-card-file-url="familyCardFileUrl"
|
||||
@preview="handlePreviewDoc"
|
||||
/>
|
||||
<div class="h-6"></div>
|
||||
<AppPersonAddressEntryForm
|
||||
@@ -483,6 +524,15 @@ watch(
|
||||
<div class="my-2 flex justify-end py-2">
|
||||
<Action @click="handleActionClick" />
|
||||
</div>
|
||||
|
||||
<!-- Dialog Preview Dokumen -->
|
||||
<Dialog
|
||||
v-model:open="isDocPreviewDialogOpen"
|
||||
title="Preview Dokumen"
|
||||
size="2xl"
|
||||
>
|
||||
<DocPreviewDialog :link="docPreviewUrl" />
|
||||
</Dialog>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { type Base, genBase } from "./_base"
|
||||
import { type Base, genBase } from './_base'
|
||||
|
||||
export interface Ethnic extends Base {
|
||||
code: string
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import { type Base, genBase } from "./_base"
|
||||
import { type Base, genBase } from './_base'
|
||||
|
||||
export interface Language extends Base {
|
||||
code: string
|
||||
name: string
|
||||
}
|
||||
|
||||
export function genMcuSrc(): Language {
|
||||
export function genLanguage(): Language {
|
||||
return {
|
||||
...genBase(),
|
||||
code: '',
|
||||
|
||||
44
app/services/ethnic.service.ts
Normal file
44
app/services/ethnic.service.ts
Normal file
@@ -0,0 +1,44 @@
|
||||
// Base
|
||||
import * as base from './_crud-base'
|
||||
|
||||
// Types
|
||||
import type { Ethnic } from '~/models/ethnic'
|
||||
|
||||
const path = '/api/v1/ethnic'
|
||||
const name = 'ethnic'
|
||||
|
||||
export function create(data: any) {
|
||||
return base.create(path, data, name)
|
||||
}
|
||||
|
||||
export function getList(params: any = null) {
|
||||
return base.getList(path, params, name)
|
||||
}
|
||||
|
||||
export function getDetail(id: number | string) {
|
||||
return base.getDetail(path, id, name)
|
||||
}
|
||||
|
||||
export function update(id: number | string, data: any) {
|
||||
return base.update(path, id, data, name)
|
||||
}
|
||||
|
||||
export function remove(id: number | string) {
|
||||
return base.remove(path, id, name)
|
||||
}
|
||||
|
||||
export async function getValueLabelList(
|
||||
params: any = null,
|
||||
useCodeAsValue = false,
|
||||
): Promise<{ value: string; label: string }[]> {
|
||||
let data: { value: string; label: string }[] = []
|
||||
const result = await getList(params)
|
||||
if (result.success) {
|
||||
const resultData = result.body?.data || []
|
||||
data = resultData.map((item: Ethnic) => ({
|
||||
value: useCodeAsValue ? item.code : item.id ? Number(item.id) : item.code,
|
||||
label: item.name,
|
||||
}))
|
||||
}
|
||||
return data
|
||||
}
|
||||
44
app/services/language.service.ts
Normal file
44
app/services/language.service.ts
Normal file
@@ -0,0 +1,44 @@
|
||||
// Base
|
||||
import * as base from './_crud-base'
|
||||
|
||||
// Types
|
||||
import type { Language } from '~/models/language'
|
||||
|
||||
const path = '/api/v1/language'
|
||||
const name = 'language'
|
||||
|
||||
export function create(data: any) {
|
||||
return base.create(path, data, name)
|
||||
}
|
||||
|
||||
export function getList(params: any = null) {
|
||||
return base.getList(path, params, name)
|
||||
}
|
||||
|
||||
export function getDetail(id: number | string) {
|
||||
return base.getDetail(path, id, name)
|
||||
}
|
||||
|
||||
export function update(id: number | string, data: any) {
|
||||
return base.update(path, id, data, name)
|
||||
}
|
||||
|
||||
export function remove(id: number | string) {
|
||||
return base.remove(path, id, name)
|
||||
}
|
||||
|
||||
export async function getValueLabelList(
|
||||
params: any = null,
|
||||
useCodeAsValue = false,
|
||||
): Promise<{ value: string; label: string }[]> {
|
||||
let data: { value: string; label: string }[] = []
|
||||
const result = await getList(params)
|
||||
if (result.success) {
|
||||
const resultData = result.body?.data || []
|
||||
data = resultData.map((item: Language) => ({
|
||||
value: useCodeAsValue ? item.code : item.id ? Number(item.id) : item.code,
|
||||
label: item.name,
|
||||
}))
|
||||
}
|
||||
return data
|
||||
}
|
||||
@@ -101,7 +101,7 @@ export async function removePatient(id: number) {
|
||||
}
|
||||
}
|
||||
|
||||
export async function uploadAttachment(file: File, userId: number, key: UploadCodeKey) {
|
||||
export async function uploadAttachment(file: File, personId: number, key: UploadCodeKey) {
|
||||
try {
|
||||
const resolvedKey = uploadCode[key]
|
||||
if (!resolvedKey) {
|
||||
@@ -110,11 +110,14 @@ export async function uploadAttachment(file: File, userId: number, key: UploadCo
|
||||
|
||||
// siapkan form-data body
|
||||
const formData = new FormData()
|
||||
formData.append('code', resolvedKey)
|
||||
formData.append('content', file)
|
||||
formData.append('entityType_code', 'person')
|
||||
formData.append('ref_id', String(personId))
|
||||
// formData.append('upload_employee_id', String(userId))
|
||||
formData.append('type_code', resolvedKey)
|
||||
|
||||
// kirim via xfetch
|
||||
const resp = await xfetch(`${mainUrl}/${userId}/upload`, 'POST', formData)
|
||||
const resp = await xfetch(`/api/v1/upload-file`, 'POST', formData)
|
||||
|
||||
// struktur hasil sama seperti patchPatient
|
||||
const result: any = {}
|
||||
|
||||
Reference in New Issue
Block a user