refactor(glob:form): update form handling and type definitions

- Migrate from Form component to vee-validate useForm
- Update combobox component to support number values
- Modify base model ID type for mock data
- Improve type safety in treatment report schema
- Add proper form submission handling
This commit is contained in:
Khafid Prayoga
2025-11-25 20:46:04 +07:00
parent 3fbcdf9e2a
commit 6a29fdfd50
7 changed files with 86 additions and 40 deletions
@@ -27,7 +27,7 @@ const { class: containerClass, labelClass, colSpan = 1, doctors = [] } = props
const opts = computed(() => {
return doctors.map((doc) => ({
value: doc.id.toString(),
value: doc.id,
label: parseName(doc.employee.person as Person),
}))
})
@@ -1,7 +1,10 @@
<script setup lang="ts">
import { useForm } from 'vee-validate'
import { toTypedSchema } from '@vee-validate/zod'
import { z } from 'zod'
import { TreatmentReportSchema } from '~/schemas/treatment-report.schema'
// schema
import { type TreatmentReportFormData, TreatmentReportSchema } from '~/schemas/treatment-report.schema'
// type
import type { Doctor } from '~/models/doctor'
@@ -16,18 +19,50 @@ import { SelectDoctor } from '~/components/app/doctor/fields'
// Helpers
// #region Props & Emits
interface FormData extends TreatmentReportFormData {
hiddenNyc: number
}
interface Props {
isLoading: boolean
mode?: 'create' | 'update' | 'view'
initialValues?: any
initialValues?: Partial<FormData>
// form related
doctors: Doctor[]
}
const props = defineProps<Props>()
const { mode = 'create' } = props
const emit = defineEmits<{
(e: 'submit', payload: FormData): void
}>()
const { isLoading, mode = 'create' } = props
const isReadonly = computed(() => {
if (isLoading) {
return true
}
if (mode === 'view') {
return true
}
return false
})
const formSchema = toTypedSchema(TreatmentReportSchema)
const { handleSubmit, values, resetForm, setFieldValue, setValues, validate } = useForm<FormData>({
name: 'treatmentReportForm',
validationSchema: formSchema,
initialValues: props.initialValues ? props.initialValues : {},
validateOnMount: false,
})
defineExpose({
validate,
resetForm,
setValues,
values,
})
// #endregion
// #region State & Computed
@@ -40,28 +75,12 @@ const { mode = 'create' } = props
// #endregion region
// #region Utilities & event handlers
const onSubmit = handleSubmit((formValues: FormData) => emit('submit', formValues))
// #endregion
const formSchema = toTypedSchema(TreatmentReportSchema)
const formRef = ref()
defineExpose({
validate: () => formRef.value?.validate(),
resetForm: () => formRef.value?.resetForm(),
setValues: (values: any, shouldValidate = true) => formRef.value?.setValues(values, shouldValidate),
values: computed(() => formRef.value?.values),
})
</script>
<template>
<Form
ref="formRef"
as=""
keep-values
:validation-schema="formSchema"
:validate-on-mount="false"
:initial-values="initialValues ? initialValues : {}"
validation-mode="onSubmit"
>
<form @submit="onSubmit">
<Fragment
v-slot="{ section }"
title="Tim Pelaksana Tindakan"
@@ -73,13 +92,14 @@ defineExpose({
:cell-flex="false"
>
<SelectDoctor
:doctors="doctors"
fieldName="dpjp"
fieldName="operatorTeam.dpjpId"
label="Dokter Pemeriksa"
placeholder="Pilih dokter"
:doctors="doctors"
:is-disabled="isReadonly"
/>
<InputBase
field-name="operatorId"
field-name="operatorTeam.operatorId"
label="Operator"
placeholder="Masukkan operator"
/>
@@ -160,5 +180,14 @@ defineExpose({
/>
</DE.Block>
</Fragment>
</Form>
<div class="mt-4 flex justify-end">
<button
type="submit"
class="rounded bg-blue-600 px-4 py-2 text-white hover:bg-blue-700 disabled:opacity-50"
:disabled="isLoading"
>
{{ isLoading ? 'Menyimpan...' : 'Simpan' }}
</button>
</div>
</form>
</template>
@@ -4,6 +4,7 @@ import { genDoctor, type Doctor } from '~/models/doctor'
// components
import AppTreatmentReportEntry from '~/components/app/treatment-report/entry-form.vue'
import type { TreatmentReportFormData } from '~/schemas/treatment-report.schema'
const doctors = ref<Doctor[]>([])
@@ -17,5 +18,12 @@ const doctors = ref<Doctor[]>([])
<AppTreatmentReportEntry
:isLoading="false"
:doctors="doctors"
:initialValues="
{
operatorTeam: {
// dpjpId: -1,
},
} as TreatmentReportFormData
"
/>
</template>
@@ -5,7 +5,7 @@ import { type Item } from './index'
const props = defineProps<{
id?: string
modelValue?: string
modelValue?: string | number
items: Item[]
placeholder?: string
searchPlaceholder?: string
@@ -15,8 +15,8 @@ const props = defineProps<{
}>()
const emit = defineEmits<{
'update:modelValue': [value: string]
'update:searchText': [value: string]
'update:modelValue': [value: string | number]
'update:searchText': [value: string | number]
}>()
const open = ref(false)
+4 -4
View File
@@ -1,5 +1,5 @@
export interface Item {
value: string
value: string | number
label: string
code?: string
priority?: number
@@ -7,12 +7,12 @@ export interface Item {
export function recStrToItem(input: Record<string, string>): Item[] {
const items: Item[] = []
let idx = 0;
let idx = 0
for (const key in input) {
if (input.hasOwnProperty(key)) {
items.push({
value: key || ('unknown-' + idx),
label: input[key] || ('unknown-' + idx),
value: key || 'unknown-' + idx,
label: input[key] || 'unknown-' + idx,
})
}
idx++
+3 -2
View File
@@ -1,4 +1,3 @@
export interface Base {
id: number
createdAt: string | null
@@ -20,7 +19,9 @@ export interface TreeItem {
export function genBase(): Base {
return {
id: 0,
// -1 buat mock data
// backend harusnya non-negative/ > 0 (untuk auto increment constraint) jadi harusnya aman ya
id: -1,
createdAt: '',
updatedAt: '',
}
+12 -4
View File
@@ -4,10 +4,18 @@ const isoDateTime = z.string().min(1, 'Tanggal / waktu wajib diisi')
const positiveInt = z.number().int().nonnegative()
const OperatorTeamSchema = z.object({
dpjpId: z.number().int(),
operatorId: z.number().int(),
assistantOperatorId: z.number().int().optional().nullable(),
instrumentNurseId: z.number().int().optional().nullable(),
dpjpId: z.coerce
.number({
invalid_type_error: 'Dokter Pemeriksa wajib diisi',
})
.int(),
operatorId: z.coerce
.number({
invalid_type_error: 'Operator wajib diisi',
})
.int(),
assistantOperatorId: z.coerce.number().int().optional().nullable(),
instrumentNurseId: z.coerce.number().int().optional().nullable(),
surgeryDate: isoDateTime,
actionDiagnosis: z.string().min(1),