refactor(patient-contact): improve contact form components and validation
- Remove hardcoded contact limit and use prop instead - Add ButtonAction component to form exports - Enhance contact schema validation with better error messages - Refactor contact type select component to use doc-entry components - Improve form layout and consistency between contact and relative forms
This commit is contained in:
No files matched your search
@@ -2,16 +2,18 @@
|
|||||||
import { toTypedSchema } from '@vee-validate/zod'
|
import { toTypedSchema } from '@vee-validate/zod'
|
||||||
|
|
||||||
// components
|
// components
|
||||||
|
import * as DE from '~/components/pub/my-ui/doc-entry'
|
||||||
import { Form } from '~/components/pub/ui/form'
|
import { Form } from '~/components/pub/ui/form'
|
||||||
import { FieldArray } from 'vee-validate'
|
import { FieldArray } from 'vee-validate'
|
||||||
import { SelectContactType } from './fields'
|
import { SelectContactType } from './fields'
|
||||||
import { InputBase } from '~/components/pub/my-ui/form'
|
import { ButtonAction, InputBase } from '~/components/pub/my-ui/form'
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
title: string
|
title: string
|
||||||
schema: any
|
schema: any
|
||||||
contactLimit: number
|
contactLimit?: number
|
||||||
initialValues?: any
|
initialValues?: any
|
||||||
|
isReadonly?: boolean
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
const { contactLimit = 5 } = props
|
const { contactLimit = 5 } = props
|
||||||
@@ -24,6 +26,8 @@ defineExpose({
|
|||||||
setValues: (values: any, shouldValidate = true) => formRef.value?.setValues(values, shouldValidate),
|
setValues: (values: any, shouldValidate = true) => formRef.value?.setValues(values, shouldValidate),
|
||||||
values: computed(() => formRef.value?.values),
|
values: computed(() => formRef.value?.values),
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const { title = 'Kontak Pasien', isReadonly = false } = props
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@@ -33,79 +37,67 @@ defineExpose({
|
|||||||
keep-values
|
keep-values
|
||||||
:validation-schema="formSchema"
|
:validation-schema="formSchema"
|
||||||
:validate-on-mount="false"
|
:validate-on-mount="false"
|
||||||
|
:initial-values="initialValues ? initialValues : {}"
|
||||||
validation-mode="onSubmit"
|
validation-mode="onSubmit"
|
||||||
:initial-values="initialValues || { contacts: [{ contactType: '', contactNumber: '' }] }"
|
|
||||||
>
|
>
|
||||||
<div>
|
<div>
|
||||||
<p
|
<p class="text-md mb-2 mt-1 font-semibold">
|
||||||
v-if="props.title"
|
{{ title }}
|
||||||
class="mb-2 text-sm font-semibold 2xl:mb-3 2xl:text-base"
|
|
||||||
>
|
|
||||||
{{ props.title || 'Kontak Pasien' }}
|
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="mb-5 border-b border-b-slate-300 pb-3 text-lg xl:text-xl">
|
<div class="mb-5 space-y-4">
|
||||||
<div class="grid grid-cols-1 gap-4 md:grid-cols-2 lg:grid-cols-2 xl:grid-cols-3">
|
<FieldArray
|
||||||
<FieldArray
|
v-slot="{ fields, push, remove }"
|
||||||
v-slot="{ fields, push, remove }"
|
name="contacts"
|
||||||
name="contacts"
|
>
|
||||||
>
|
<template v-if="fields.length === 0">
|
||||||
<div
|
{{ push({ relation: '', name: '', address: '', phone: '' }) }}
|
||||||
|
</template>
|
||||||
|
<div class="space-y-4">
|
||||||
|
<DE.Block
|
||||||
v-for="(field, idx) in fields"
|
v-for="(field, idx) in fields"
|
||||||
:key="field.key"
|
:key="field.key"
|
||||||
class="flex-row gap-2 md:flex"
|
:col-count="5"
|
||||||
|
:cell-flex="false"
|
||||||
>
|
>
|
||||||
<div class="min-w-0 flex-[2]">
|
<SelectContactType
|
||||||
<SelectContactType
|
:label="idx === 0 ? 'Jenis Kontak' : undefined"
|
||||||
:field-name="`contacts[${idx}].contactType`"
|
:field-name="`contacts[${idx}].contactType`"
|
||||||
:label="`Kontak ${idx + 1}`"
|
:is-disabled="isReadonly"
|
||||||
:is-required="idx === 0"
|
/>
|
||||||
/>
|
<InputBase
|
||||||
</div>
|
:label="idx === 0 ? 'Nomor Kontak' : undefined"
|
||||||
<div class="min-w-0 flex-[1.5]">
|
:field-name="`contacts[${idx}].contactNumber`"
|
||||||
<InputBase
|
placeholder="081234567890"
|
||||||
:field-name="`contacts[${idx}].contactNumber`"
|
:max-length="15"
|
||||||
placeholder="081234567890"
|
numeric-only
|
||||||
label="No"
|
:is-disabled="isReadonly"
|
||||||
numeric-only
|
/>
|
||||||
:max-length="15"
|
<DE.Cell class="flex items-start justify-start">
|
||||||
:is-required="idx === 0"
|
<DE.Field :class="idx === 0 ? 'mt-[30px]' : 'mt-0'">
|
||||||
/>
|
<ButtonAction
|
||||||
</div>
|
v-if="idx !== 0"
|
||||||
<div class="flex-1 self-start md:pt-8">
|
:disabled="isReadonly"
|
||||||
<Button
|
preset="delete"
|
||||||
type="button"
|
:title="`Hapus Kontak ${idx + 1}`"
|
||||||
variant="outline"
|
icon-only
|
||||||
size="icon"
|
@click="remove(idx)"
|
||||||
class="text-red-600 hover:border-red-400 hover:bg-red-50 hover:text-red-700 dark:border-red-400 dark:text-red-400 dark:hover:border-red-300 dark:hover:bg-red-900/20"
|
|
||||||
:class="{ invisible: idx === 0 }"
|
|
||||||
@click="remove(idx)"
|
|
||||||
>
|
|
||||||
<Icon
|
|
||||||
name="i-lucide-trash-2"
|
|
||||||
class="h-5 w-5"
|
|
||||||
/>
|
/>
|
||||||
</Button>
|
</DE.Field>
|
||||||
</div>
|
</DE.Cell>
|
||||||
</div>
|
</DE.Block>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="self-center pt-3">
|
<ButtonAction
|
||||||
<Button
|
preset="add"
|
||||||
:disabled="fields.length >= contactLimit"
|
label="Tambah Kontak"
|
||||||
type="button"
|
title="Tambah Kontak ke Daftar Kontak"
|
||||||
variant="outline"
|
:disabled="fields.length >= contactLimit || isReadonly"
|
||||||
size="sm"
|
:full-width-mobile="true"
|
||||||
@click="push({ contactType: '', contactNumber: '' })"
|
class="mt-4"
|
||||||
>
|
@click="push({ contactType: '', contactNumber: '' })"
|
||||||
<Icon
|
/>
|
||||||
name="i-lucide-plus"
|
</FieldArray>
|
||||||
class="h-5 w-5"
|
|
||||||
/>
|
|
||||||
Tambah Kontak
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</FieldArray>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</Form>
|
</Form>
|
||||||
</template>
|
</template>
|
||||||
@@ -1,14 +1,13 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import type { FormErrors } from '~/types/error'
|
import type { FormErrors } from '~/types/error'
|
||||||
import FieldGroup from '~/components/pub/my-ui/form/field-group.vue'
|
|
||||||
import Field from '~/components/pub/my-ui/form/field.vue'
|
|
||||||
import Label from '~/components/pub/my-ui/form/label.vue'
|
|
||||||
import Select from '~/components/pub/my-ui/form/select.vue'
|
|
||||||
import { cn } from '~/lib/utils'
|
import { cn } from '~/lib/utils'
|
||||||
|
|
||||||
|
import * as DE from '~/components/pub/my-ui/doc-entry'
|
||||||
|
import Select from '~/components/pub/my-ui/form/select.vue'
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
fieldName: string
|
fieldName: string
|
||||||
label: string
|
label?: string
|
||||||
placeholder?: string
|
placeholder?: string
|
||||||
errors?: FormErrors
|
errors?: FormErrors
|
||||||
class?: string
|
class?: string
|
||||||
@@ -17,11 +16,12 @@ const props = defineProps<{
|
|||||||
labelClass?: string
|
labelClass?: string
|
||||||
isRequired?: boolean
|
isRequired?: boolean
|
||||||
isDisabled?: boolean
|
isDisabled?: boolean
|
||||||
|
colSpan?: number
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
const { fieldName = 'phoneNumber', errors, class: containerClass, selectClass, fieldGroupClass, labelClass } = props
|
const { fieldName = 'phoneNumber', errors, class: containerClass, selectClass, fieldGroupClass, labelClass } = props
|
||||||
|
|
||||||
const contactOptions = [
|
const opts = [
|
||||||
{ label: 'Nomor HP', value: 'phoneNumber' },
|
{ label: 'Nomor HP', value: 'phoneNumber' },
|
||||||
{ label: 'Nomor Telepon Kantor', value: 'officePhoneNumber' },
|
{ label: 'Nomor Telepon Kantor', value: 'officePhoneNumber' },
|
||||||
{ label: 'Nomor Telepon Rumah', value: 'homePhoneNumber' },
|
{ label: 'Nomor Telepon Rumah', value: 'homePhoneNumber' },
|
||||||
@@ -29,17 +29,20 @@ const contactOptions = [
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<FieldGroup :class="cn('select-field-group', fieldGroupClass, containerClass)">
|
<DE.Cell
|
||||||
<Label
|
:col-span="colSpan"
|
||||||
|
:class="cn('select-field-group', fieldGroupClass, containerClass)"
|
||||||
|
>
|
||||||
|
<DE.Label
|
||||||
|
v-if="label"
|
||||||
:label-for="fieldName"
|
:label-for="fieldName"
|
||||||
:class="cn('select-field-label', labelClass)"
|
:class="cn('select-field-label', labelClass)"
|
||||||
:is-required="isRequired && !isDisabled"
|
:is-required="isRequired"
|
||||||
>
|
>
|
||||||
{{ label }}
|
{{ label }}
|
||||||
</Label>
|
</DE.Label>
|
||||||
<Field
|
<DE.Field
|
||||||
:id="fieldName"
|
:id="fieldName"
|
||||||
:errors="errors"
|
|
||||||
:class="cn('select-field-wrapper')"
|
:class="cn('select-field-wrapper')"
|
||||||
>
|
>
|
||||||
<FormField
|
<FormField
|
||||||
@@ -49,15 +52,15 @@ const contactOptions = [
|
|||||||
<FormItem>
|
<FormItem>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<Select
|
<Select
|
||||||
|
:disabled="isDisabled"
|
||||||
:id="fieldName"
|
:id="fieldName"
|
||||||
v-bind="componentField"
|
v-bind="componentField"
|
||||||
:is-disabled="isDisabled"
|
:items="opts"
|
||||||
:items="contactOptions"
|
:placeholder="placeholder"
|
||||||
:placeholder="placeholder || 'Pilih jenis kontak'"
|
|
||||||
:preserve-order="true"
|
:preserve-order="true"
|
||||||
:class="
|
:class="
|
||||||
cn(
|
cn(
|
||||||
'min-w-[190px] text-sm transition-all duration-200 focus:outline-none focus:ring-1 focus:ring-black focus:ring-offset-0',
|
'text-sm transition-all duration-200 focus:outline-none focus:ring-1 focus:ring-black focus:ring-offset-0',
|
||||||
selectClass,
|
selectClass,
|
||||||
)
|
)
|
||||||
"
|
"
|
||||||
@@ -66,6 +69,6 @@ const contactOptions = [
|
|||||||
<FormMessage />
|
<FormMessage />
|
||||||
</FormItem>
|
</FormItem>
|
||||||
</FormField>
|
</FormField>
|
||||||
</Field>
|
</DE.Field>
|
||||||
</FieldGroup>
|
</DE.Cell>
|
||||||
</template>
|
</template>
|
||||||
@@ -6,14 +6,14 @@ import { FieldArray } from 'vee-validate'
|
|||||||
import * as DE from '~/components/pub/my-ui/doc-entry'
|
import * as DE from '~/components/pub/my-ui/doc-entry'
|
||||||
import { Form } from '~/components/pub/ui/form'
|
import { Form } from '~/components/pub/ui/form'
|
||||||
import { SelectRelations } from './fields'
|
import { SelectRelations } from './fields'
|
||||||
import InputBase from '~/components/pub/my-ui/form/input-base.vue'
|
import { ButtonAction, InputBase } from '~/components/pub/my-ui/form'
|
||||||
import ButtonAction from '~/components/pub/my-ui/form/button-action.vue'
|
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
title: string
|
title: string
|
||||||
schema: any
|
schema: any
|
||||||
isReadonly?: boolean
|
isReadonly?: boolean
|
||||||
initialValues?: any
|
initialValues?: any
|
||||||
|
contactLimit?: number
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
const formSchema = toTypedSchema(props.schema)
|
const formSchema = toTypedSchema(props.schema)
|
||||||
@@ -26,7 +26,7 @@ defineExpose({
|
|||||||
values: computed(() => formRef.value?.values),
|
values: computed(() => formRef.value?.values),
|
||||||
})
|
})
|
||||||
|
|
||||||
const { title = 'Kontak Pasien', isReadonly = false } = props
|
const { title = 'Kontak Pasien', isReadonly = false, contactLimit = 5 } = props
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@@ -102,9 +102,9 @@ const { title = 'Kontak Pasien', isReadonly = false } = props
|
|||||||
|
|
||||||
<ButtonAction
|
<ButtonAction
|
||||||
preset="add"
|
preset="add"
|
||||||
label="Tambah Kontak"
|
label="Tambah Penanggung Jawab"
|
||||||
title="Tambah Kontak ke Daftar Kontak"
|
title="Tambah Penanggung Jawab"
|
||||||
:disabled="fields.length >= 5 || isReadonly"
|
:disabled="fields.length >= contactLimit || isReadonly"
|
||||||
:full-width-mobile="true"
|
:full-width-mobile="true"
|
||||||
class="mt-4"
|
class="mt-4"
|
||||||
@click="push({ relation: '', name: '', address: '', phone: '' })"
|
@click="push({ relation: '', name: '', address: '', phone: '' })"
|
||||||
|
|||||||
@@ -303,7 +303,6 @@ watch(
|
|||||||
<AppPersonContactEntryForm
|
<AppPersonContactEntryForm
|
||||||
ref="personContactForm"
|
ref="personContactForm"
|
||||||
title="Kontak Pasien"
|
title="Kontak Pasien"
|
||||||
:contact-limit="10"
|
|
||||||
:schema="PersonContactListSchema"
|
:schema="PersonContactListSchema"
|
||||||
/>
|
/>
|
||||||
<AppPersonRelativeEntryForm
|
<AppPersonRelativeEntryForm
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
export { default as Block } from './block.vue'
|
export { default as Block } from './block.vue'
|
||||||
|
export { default as ButtonAction } from './button-action.vue'
|
||||||
export { default as FieldGroup } from './field-group.vue'
|
export { default as FieldGroup } from './field-group.vue'
|
||||||
export { default as Field } from './field.vue'
|
export { default as Field } from './field.vue'
|
||||||
export { default as FileField } from './file-field.vue'
|
export { default as FileField } from './file-field.vue'
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import { z } from 'zod'
|
import { z } from 'zod'
|
||||||
|
|
||||||
const PersonContactBaseSchema = z.object({
|
const PersonContactBaseSchema = z.object({
|
||||||
contactType: z.string().min(1, 'Mohon pilih tipe kontak'),
|
contactType: z.string({ required_error: 'Mohon pilih tipe kontak' }).min(1, 'Mohon pilih tipe kontak'),
|
||||||
contactNumber: z.string().min(8, 'Nomor minimal 8 digit'),
|
contactNumber: z.string({ required_error: 'Nomor kontak harus diisi' }).min(8, 'Nomor minimal 8 digit'),
|
||||||
})
|
})
|
||||||
|
|
||||||
const PersonContactListSchema = z.object({
|
const PersonContactListSchema = z.object({
|
||||||
|
|||||||
Reference in New Issue
Block a user