Merge branch 'dev' into feat/consultation-82
This commit is contained in:
+3
-1
@@ -5,5 +5,7 @@
|
||||
"trailingComma": "all",
|
||||
"printWidth": 120,
|
||||
"semi": false,
|
||||
"plugins": ["prettier-plugin-tailwindcss"]
|
||||
"plugins": ["prettier-plugin-tailwindcss"],
|
||||
"htmlWhitespaceSensitivity": "ignore",
|
||||
"singleAttributePerLine": true
|
||||
}
|
||||
|
||||
@@ -346,6 +346,7 @@ body, table, label {
|
||||
color: hsl(var(--destructive));
|
||||
/* font-size: 0.875rem; */
|
||||
margin-top: 0.25rem;
|
||||
min-height: 1rem; /* Reserve space to prevent CLS */
|
||||
line-height: 1.25;
|
||||
}
|
||||
|
||||
@@ -374,4 +375,4 @@ body, table, label {
|
||||
grid-template-columns: repeat(4, minmax(0, 1fr));
|
||||
gap: 1.25rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
<script setup lang="ts">
|
||||
import Block from '~/components/pub/form/block.vue'
|
||||
import FieldGroup from '~/components/pub/form/field-group.vue'
|
||||
import Field from '~/components/pub/form/field.vue'
|
||||
import Label from '~/components/pub/form/label.vue'
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<form id="entry-form">
|
||||
<div class="mb-5 border-b border-b-slate-300 pb-3 text-lg xl:text-xl">
|
||||
<Icon name="i-lucide-user" class="me-2" />
|
||||
<span class="font-semibold">Tambah</span> Pasien
|
||||
</div>
|
||||
|
||||
<div class="mb-5 border-b border-b-slate-300 pb-3 text-lg xl:text-xl">
|
||||
<Block>
|
||||
<FieldGroup :column="3">
|
||||
<Label>Nama</Label>
|
||||
<Field>
|
||||
<Input type="text" name="name" />
|
||||
</Field>
|
||||
</FieldGroup>
|
||||
<FieldGroup :column="3">
|
||||
<Label>Nama</Label>
|
||||
<Field>
|
||||
<Input type="text" name="name" />
|
||||
</Field>
|
||||
</FieldGroup>
|
||||
<FieldGroup :column="3">
|
||||
<Label>Nomor RM</Label>
|
||||
<Field>
|
||||
<Input type="text" name="name" />
|
||||
</Field>
|
||||
</FieldGroup>
|
||||
<FieldGroup>
|
||||
<Label dynamic>Alamat</Label>
|
||||
<Field>
|
||||
<Input type="text" name="name" />
|
||||
</Field>
|
||||
</FieldGroup>
|
||||
</Block>
|
||||
</div>
|
||||
<div class="my-2 flex justify-end py-2">
|
||||
<PubNavFooterCsd />
|
||||
</div>
|
||||
</form>
|
||||
</template>
|
||||
@@ -0,0 +1,119 @@
|
||||
import type {
|
||||
Col,
|
||||
KeyLabel,
|
||||
RecComponent,
|
||||
RecStrFuncComponent,
|
||||
RecStrFuncUnknown,
|
||||
Th,
|
||||
} from '~/components/pub/my-ui/data/types'
|
||||
import { defineAsyncComponent } from 'vue'
|
||||
|
||||
type SmallDetailDto = any
|
||||
|
||||
const action = defineAsyncComponent(() => import('~/components/pub/my-ui/data/dropdown-action-dud.vue'))
|
||||
|
||||
export const cols: Col[] = [
|
||||
{},
|
||||
{},
|
||||
{},
|
||||
{ width: 100 },
|
||||
{ width: 120 },
|
||||
{},
|
||||
{},
|
||||
{},
|
||||
{ width: 100 },
|
||||
{ width: 100 },
|
||||
{},
|
||||
{ width: 50 },
|
||||
]
|
||||
|
||||
export const header: Th[][] = [
|
||||
[
|
||||
{ label: 'Nama' },
|
||||
{ label: 'Rekam Medis' },
|
||||
{ label: 'KTP' },
|
||||
{ label: 'Tgl Lahir' },
|
||||
{ label: 'Umur' },
|
||||
{ label: 'JK' },
|
||||
{ label: 'Pendidikan' },
|
||||
{ label: 'Status' },
|
||||
{ label: '' },
|
||||
],
|
||||
]
|
||||
|
||||
export const keys = [
|
||||
'name',
|
||||
'medicalRecord_number',
|
||||
'identity_number',
|
||||
'birth_date',
|
||||
'patient_age',
|
||||
'gender',
|
||||
'education',
|
||||
'status',
|
||||
'action',
|
||||
]
|
||||
|
||||
export const delKeyNames: KeyLabel[] = [
|
||||
{ key: 'code', label: 'Kode' },
|
||||
{ key: 'name', label: 'Nama' },
|
||||
]
|
||||
|
||||
export const funcParsed: RecStrFuncUnknown = {
|
||||
name: (rec: unknown): unknown => {
|
||||
const recX = rec as SmallDetailDto
|
||||
return `${recX.firstName} ${recX.middleName || ''} ${recX.lastName || ''}`
|
||||
},
|
||||
identity_number: (rec: unknown): unknown => {
|
||||
const recX = rec as SmallDetailDto
|
||||
if (recX.identity_number?.substring(0, 5) === 'BLANK') {
|
||||
return '(TANPA NIK)'
|
||||
}
|
||||
return recX.identity_number
|
||||
},
|
||||
birth_date: (rec: unknown): unknown => {
|
||||
const recX = rec as SmallDetailDto
|
||||
if (typeof recX.birth_date == 'object' && recX.birth_date) {
|
||||
return (recX.birth_date as Date).toLocaleDateString()
|
||||
} else if (typeof recX.birth_date == 'string') {
|
||||
return (recX.birth_date as string).substring(0, 10)
|
||||
}
|
||||
return recX.birth_date
|
||||
},
|
||||
patient_age: (rec: unknown): unknown => {
|
||||
const recX = rec as SmallDetailDto
|
||||
return recX.birth_date?.split('T')[0]
|
||||
},
|
||||
gender: (rec: unknown): unknown => {
|
||||
const recX = rec as SmallDetailDto
|
||||
if (typeof recX?.gender_code !== 'number' && recX?.gender_code !== '') {
|
||||
return 'Tidak Diketahui'
|
||||
}
|
||||
return recX.gender_code
|
||||
},
|
||||
education: (rec: unknown): unknown => {
|
||||
const recX = rec as SmallDetailDto
|
||||
if (typeof recX.education_code == 'number' && recX.education_code >= 0) {
|
||||
return recX.education_code
|
||||
} else if (typeof recX.education_code) {
|
||||
return recX.education_code
|
||||
}
|
||||
return '-'
|
||||
},
|
||||
}
|
||||
|
||||
export const funcComponent: RecStrFuncComponent = {
|
||||
action(rec, idx) {
|
||||
const res: RecComponent = {
|
||||
idx,
|
||||
rec: rec as object,
|
||||
component: action,
|
||||
}
|
||||
return res
|
||||
},
|
||||
}
|
||||
|
||||
export const funcHtml: RecStrFuncUnknown = {
|
||||
patient_address(_rec) {
|
||||
return '-'
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
<script setup lang="ts">
|
||||
import { cols, funcComponent, funcHtml, funcParsed, header, keys } from './list-cfg'
|
||||
|
||||
defineProps<{
|
||||
data: any[]
|
||||
}>()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<PubMyUiDataTable
|
||||
:rows="data"
|
||||
:cols="cols"
|
||||
:header="header"
|
||||
:keys="keys"
|
||||
:func-parsed="funcParsed"
|
||||
:func-html="funcHtml"
|
||||
:func-component="funcComponent"
|
||||
/>
|
||||
</template>
|
||||
@@ -0,0 +1,105 @@
|
||||
<script setup lang="ts">
|
||||
// Components
|
||||
import Block from '~/components/pub/my-ui/doc-entry/block.vue'
|
||||
import Cell from '~/components/pub/my-ui/doc-entry/cell.vue'
|
||||
import Field from '~/components/pub/my-ui/doc-entry/field.vue'
|
||||
import Label from '~/components/pub/my-ui/doc-entry/label.vue'
|
||||
import Button from '~/components/pub/ui/button/Button.vue'
|
||||
|
||||
// Types
|
||||
import type { InfraFormData } from '~/schemas/infra.schema'
|
||||
|
||||
// Helpers
|
||||
import type z from 'zod'
|
||||
import { useForm } from 'vee-validate'
|
||||
import { toTypedSchema } from '@vee-validate/zod'
|
||||
|
||||
interface Props {
|
||||
schema: z.ZodSchema<any>
|
||||
values: any
|
||||
isLoading?: boolean
|
||||
isReadonly?: boolean
|
||||
}
|
||||
|
||||
const props = defineProps<Props>()
|
||||
const isLoading = props.isLoading !== undefined ? props.isLoading : false
|
||||
const isReadonly = props.isReadonly !== undefined ? props.isReadonly : false
|
||||
const emit = defineEmits<{
|
||||
submit: [values: InfraFormData, resetForm: () => void]
|
||||
cancel: [resetForm: () => void]
|
||||
}>()
|
||||
|
||||
const { defineField, errors, meta } = useForm({
|
||||
validationSchema: toTypedSchema(props.schema),
|
||||
initialValues: {
|
||||
code: '',
|
||||
name: '',
|
||||
infraGroup_code: 'counter',
|
||||
parent_id: null,
|
||||
} as Partial<InfraFormData>,
|
||||
})
|
||||
|
||||
const [code, codeAttrs] = defineField('code')
|
||||
const [name, nameAttrs] = defineField('name')
|
||||
const [infraGroup_code, infraGroupAttrs] = defineField('infraGroup_code')
|
||||
const [parent_id, parentIdAttrs] = defineField('parent_id')
|
||||
|
||||
if (props.values) {
|
||||
if (props.values.code !== undefined) code.value = props.values.code
|
||||
if (props.values.name !== undefined) name.value = props.values.name
|
||||
if (props.values.infraGroup_code !== undefined) infraGroup_code.value = props.values.infraGroup_code
|
||||
if (props.values.parent_id !== undefined) parent_id.value = props.values.parent_id
|
||||
}
|
||||
|
||||
const resetForm = () => {
|
||||
code.value = ''
|
||||
name.value = ''
|
||||
infraGroup_code.value = 'counter'
|
||||
parent_id.value = null
|
||||
}
|
||||
|
||||
function onSubmitForm() {
|
||||
const formData: InfraFormData = {
|
||||
code: code.value || '',
|
||||
name: name.value || '',
|
||||
infraGroup_code: infraGroup_code.value || 'counter',
|
||||
parent_id: parent_id.value || null,
|
||||
}
|
||||
emit('submit', formData, resetForm)
|
||||
}
|
||||
|
||||
function onCancelForm() {
|
||||
emit('cancel', resetForm)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<form id="form-counter" @submit.prevent>
|
||||
<Block labelSize="thin" class="!mb-2.5 !pt-0 xl:!mb-3" :colCount="1">
|
||||
<Cell>
|
||||
<Label height="compact">Kode</Label>
|
||||
<Field :errMessage="errors.code">
|
||||
<Input id="code" v-model="code" v-bind="codeAttrs" :disabled="isLoading || isReadonly" />
|
||||
</Field>
|
||||
</Cell>
|
||||
<Cell>
|
||||
<Label height="compact">Nama</Label>
|
||||
<Field :errMessage="errors.name">
|
||||
<Input id="name" v-model="name" v-bind="nameAttrs" :disabled="isLoading || isReadonly" />
|
||||
</Field>
|
||||
</Cell>
|
||||
</Block>
|
||||
<div class="my-2 flex justify-end gap-2 py-2">
|
||||
<Button type="button" variant="secondary" class="w-[120px]" @click="onCancelForm"> Kembali </Button>
|
||||
<Button
|
||||
v-if="!isReadonly"
|
||||
type="button"
|
||||
class="w-[120px]"
|
||||
:disabled="isLoading || !meta.valid"
|
||||
@click="onSubmitForm"
|
||||
>
|
||||
Simpan
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
</template>
|
||||
@@ -0,0 +1,47 @@
|
||||
import type {
|
||||
Col,
|
||||
KeyLabel,
|
||||
RecComponent,
|
||||
RecStrFuncComponent,
|
||||
RecStrFuncUnknown,
|
||||
Th,
|
||||
} from '~/components/pub/my-ui/data/types'
|
||||
import { defineAsyncComponent } from 'vue'
|
||||
|
||||
type SmallDetailDto = any
|
||||
|
||||
const action = defineAsyncComponent(() => import('~/components/pub/my-ui/data/dropdown-action-dud.vue'))
|
||||
|
||||
export const cols: Col[] = [{ width: 100 }, {}, {}, { width: 50 }]
|
||||
|
||||
export const header: Th[][] = [[{ label: 'Kode' }, { label: 'Nama' }, { label: 'Counter Induk' }, { label: '' }]]
|
||||
|
||||
export const keys = ['code', 'name', 'parent', 'action']
|
||||
|
||||
export const delKeyNames: KeyLabel[] = [
|
||||
{ key: 'code', label: 'Kode' },
|
||||
{ key: 'name', label: 'Nama' },
|
||||
]
|
||||
|
||||
export const funcParsed: RecStrFuncUnknown = {
|
||||
parent: (rec: unknown): unknown => {
|
||||
const recX = rec as SmallDetailDto
|
||||
return recX.parent?.name || '-'
|
||||
},
|
||||
}
|
||||
|
||||
export const funcComponent: RecStrFuncComponent = {
|
||||
action(rec, idx) {
|
||||
const res: RecComponent = {
|
||||
idx,
|
||||
rec: rec as object,
|
||||
component: action,
|
||||
props: {
|
||||
size: 'sm',
|
||||
},
|
||||
}
|
||||
return res
|
||||
},
|
||||
}
|
||||
|
||||
export const funcHtml: RecStrFuncUnknown = {}
|
||||
@@ -0,0 +1,36 @@
|
||||
<script setup lang="ts">
|
||||
import type { PaginationMeta } from '~/components/pub/my-ui/pagination/pagination.type'
|
||||
import PaginationView from '~/components/pub/my-ui/pagination/pagination-view.vue'
|
||||
import { cols, funcComponent, funcHtml, funcParsed, header, keys } from './list-cfg'
|
||||
|
||||
interface Props {
|
||||
data: any[]
|
||||
paginationMeta: PaginationMeta
|
||||
}
|
||||
|
||||
defineProps<Props>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
pageChange: [page: number]
|
||||
}>()
|
||||
|
||||
function handlePageChange(page: number) {
|
||||
emit('pageChange', page)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="space-y-4">
|
||||
<PubBaseDataTable
|
||||
:rows="data"
|
||||
:cols="cols"
|
||||
:header="header"
|
||||
:keys="keys"
|
||||
:func-parsed="funcParsed"
|
||||
:func-html="funcHtml"
|
||||
:func-component="funcComponent"
|
||||
:skeleton-size="paginationMeta?.pageSize"
|
||||
/>
|
||||
<PaginationView :pagination-meta="paginationMeta" @page-change="handlePageChange" />
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1,108 @@
|
||||
<script setup lang="ts">
|
||||
// Components
|
||||
import Block from '~/components/pub/my-ui/doc-entry/block.vue'
|
||||
import Cell from '~/components/pub/my-ui/doc-entry/cell.vue'
|
||||
import Field from '~/components/pub/my-ui/doc-entry/field.vue'
|
||||
import Label from '~/components/pub/my-ui/doc-entry/label.vue'
|
||||
import Button from '~/components/pub/ui/button/Button.vue'
|
||||
|
||||
// Constants
|
||||
import { infraGroupCodesKeys } from "~/lib/constants"
|
||||
|
||||
// Types
|
||||
import type { InfraFormData } from '~/schemas/infra.schema'
|
||||
|
||||
// Helpers
|
||||
import type z from 'zod'
|
||||
import { useForm } from 'vee-validate'
|
||||
import { toTypedSchema } from '@vee-validate/zod'
|
||||
|
||||
interface Props {
|
||||
schema: z.ZodSchema<any>
|
||||
values: any
|
||||
isLoading?: boolean
|
||||
isReadonly?: boolean
|
||||
}
|
||||
|
||||
const props = defineProps<Props>()
|
||||
const isLoading = props.isLoading !== undefined ? props.isLoading : false
|
||||
const isReadonly = props.isReadonly !== undefined ? props.isReadonly : false
|
||||
const emit = defineEmits<{
|
||||
submit: [values: InfraFormData, resetForm: () => void]
|
||||
cancel: [resetForm: () => void]
|
||||
}>()
|
||||
|
||||
const { defineField, errors, meta } = useForm({
|
||||
validationSchema: toTypedSchema(props.schema),
|
||||
initialValues: {
|
||||
code: '',
|
||||
name: '',
|
||||
infraGroup_code: infraGroupCodesKeys.building,
|
||||
parent_id: null,
|
||||
} as Partial<InfraFormData>,
|
||||
})
|
||||
|
||||
const [code, codeAttrs] = defineField('code')
|
||||
const [name, nameAttrs] = defineField('name')
|
||||
const [infraGroup_code] = defineField('infraGroup_code')
|
||||
const [parent_id] = defineField('parent_id')
|
||||
|
||||
if (props.values) {
|
||||
if (props.values.code !== undefined) code.value = props.values.code
|
||||
if (props.values.name !== undefined) name.value = props.values.name
|
||||
if (props.values.infraGroup_code !== undefined) infraGroup_code.value = props.values.infraGroup_code
|
||||
if (props.values.parent_id !== undefined) parent_id.value = props.values.parent_id
|
||||
}
|
||||
|
||||
const resetForm = () => {
|
||||
code.value = ''
|
||||
name.value = ''
|
||||
infraGroup_code.value = infraGroupCodesKeys.building
|
||||
parent_id.value = null
|
||||
}
|
||||
|
||||
function onSubmitForm() {
|
||||
const formData: InfraFormData = {
|
||||
code: code.value || '',
|
||||
name: name.value || '',
|
||||
infraGroup_code: infraGroup_code.value || infraGroupCodesKeys.building,
|
||||
parent_id: parent_id.value || null,
|
||||
}
|
||||
emit('submit', formData, resetForm)
|
||||
}
|
||||
|
||||
function onCancelForm() {
|
||||
emit('cancel', resetForm)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<form id="form-building" @submit.prevent>
|
||||
<Block labelSize="thin" class="!mb-2.5 !pt-0 xl:!mb-3" :colCount="1">
|
||||
<Cell>
|
||||
<Label height="compact">Kode</Label>
|
||||
<Field :errMessage="errors.code">
|
||||
<Input id="code" v-model="code" v-bind="codeAttrs" :disabled="isLoading || isReadonly" />
|
||||
</Field>
|
||||
</Cell>
|
||||
<Cell>
|
||||
<Label height="compact">Nama</Label>
|
||||
<Field :errMessage="errors.name">
|
||||
<Input id="name" v-model="name" v-bind="nameAttrs" :disabled="isLoading || isReadonly" />
|
||||
</Field>
|
||||
</Cell>
|
||||
</Block>
|
||||
<div class="my-2 flex justify-end gap-2 py-2">
|
||||
<Button type="button" variant="secondary" class="w-[120px]" @click="onCancelForm"> Kembali </Button>
|
||||
<Button
|
||||
v-if="!isReadonly"
|
||||
type="button"
|
||||
class="w-[120px]"
|
||||
:disabled="isLoading || !meta.valid"
|
||||
@click="onSubmitForm"
|
||||
>
|
||||
Simpan
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
</template>
|
||||
@@ -0,0 +1,37 @@
|
||||
import type {
|
||||
Col,
|
||||
KeyLabel,
|
||||
RecComponent,
|
||||
RecStrFuncComponent,
|
||||
RecStrFuncUnknown,
|
||||
Th,
|
||||
} from '~/components/pub/my-ui/data/types'
|
||||
import { defineAsyncComponent } from 'vue'
|
||||
|
||||
const action = defineAsyncComponent(() => import('~/components/pub/my-ui/data/dropdown-action-dud.vue'))
|
||||
|
||||
export const cols: Col[] = [{}, {}, { width: 50 }]
|
||||
|
||||
export const header: Th[][] = [[{ label: 'Kode' }, { label: 'Nama' }, { label: '' }]]
|
||||
|
||||
export const keys = ['code', 'name', 'action']
|
||||
|
||||
export const delKeyNames: KeyLabel[] = [
|
||||
{ key: 'code', label: 'Kode' },
|
||||
{ key: 'name', label: 'Nama' },
|
||||
]
|
||||
|
||||
export const funcParsed: RecStrFuncUnknown = {}
|
||||
|
||||
export const funcComponent: RecStrFuncComponent = {
|
||||
action(rec, idx) {
|
||||
const res: RecComponent = {
|
||||
idx,
|
||||
rec: rec as object,
|
||||
component: action,
|
||||
}
|
||||
return res
|
||||
},
|
||||
}
|
||||
|
||||
export const funcHtml: RecStrFuncUnknown = {}
|
||||
@@ -0,0 +1,41 @@
|
||||
<script setup lang="ts">
|
||||
// Components
|
||||
import PaginationView from '~/components/pub/my-ui/pagination/pagination-view.vue'
|
||||
|
||||
// Types
|
||||
import type { PaginationMeta } from '~/components/pub/my-ui/pagination/pagination.type'
|
||||
|
||||
// Configs
|
||||
import { cols, funcComponent, funcHtml, funcParsed, header, keys } from './list-cfg'
|
||||
|
||||
interface Props {
|
||||
data: any[]
|
||||
paginationMeta: PaginationMeta
|
||||
}
|
||||
|
||||
defineProps<Props>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
pageChange: [page: number]
|
||||
}>()
|
||||
|
||||
function handlePageChange(page: number) {
|
||||
emit('pageChange', page)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="space-y-4">
|
||||
<PubMyUiDataTable
|
||||
:rows="data"
|
||||
:cols="cols"
|
||||
:header="header"
|
||||
:keys="keys"
|
||||
:func-parsed="funcParsed"
|
||||
:func-html="funcHtml"
|
||||
:func-component="funcComponent"
|
||||
:skeleton-size="paginationMeta?.pageSize"
|
||||
/>
|
||||
<PaginationView :pagination-meta="paginationMeta" @page-change="handlePageChange" />
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1,105 @@
|
||||
<script setup lang="ts">
|
||||
// Components
|
||||
import Block from '~/components/pub/my-ui/doc-entry/block.vue'
|
||||
import Cell from '~/components/pub/my-ui/doc-entry/cell.vue'
|
||||
import Field from '~/components/pub/my-ui/doc-entry/field.vue'
|
||||
import Label from '~/components/pub/my-ui/doc-entry/label.vue'
|
||||
import Button from '~/components/pub/ui/button/Button.vue'
|
||||
|
||||
// Types
|
||||
import type { InfraFormData } from '~/schemas/infra.schema'
|
||||
|
||||
// Helpers
|
||||
import type z from 'zod'
|
||||
import { useForm } from 'vee-validate'
|
||||
import { toTypedSchema } from '@vee-validate/zod'
|
||||
|
||||
interface Props {
|
||||
schema: z.ZodSchema<any>
|
||||
values: any
|
||||
isLoading?: boolean
|
||||
isReadonly?: boolean
|
||||
}
|
||||
|
||||
const props = defineProps<Props>()
|
||||
const isLoading = props.isLoading !== undefined ? props.isLoading : false
|
||||
const isReadonly = props.isReadonly !== undefined ? props.isReadonly : false
|
||||
const emit = defineEmits<{
|
||||
submit: [values: InfraFormData, resetForm: () => void]
|
||||
cancel: [resetForm: () => void]
|
||||
}>()
|
||||
|
||||
const { defineField, errors, meta } = useForm({
|
||||
validationSchema: toTypedSchema(props.schema),
|
||||
initialValues: {
|
||||
code: '',
|
||||
name: '',
|
||||
infraGroup_code: 'counter',
|
||||
parent_id: null,
|
||||
} as Partial<InfraFormData>,
|
||||
})
|
||||
|
||||
const [code, codeAttrs] = defineField('code')
|
||||
const [name, nameAttrs] = defineField('name')
|
||||
const [infraGroup_code, infraGroupAttrs] = defineField('infraGroup_code')
|
||||
const [parent_id, parentIdAttrs] = defineField('parent_id')
|
||||
|
||||
if (props.values) {
|
||||
if (props.values.code !== undefined) code.value = props.values.code
|
||||
if (props.values.name !== undefined) name.value = props.values.name
|
||||
if (props.values.infraGroup_code !== undefined) infraGroup_code.value = props.values.infraGroup_code
|
||||
if (props.values.parent_id !== undefined) parent_id.value = props.values.parent_id
|
||||
}
|
||||
|
||||
const resetForm = () => {
|
||||
code.value = ''
|
||||
name.value = ''
|
||||
infraGroup_code.value = 'counter'
|
||||
parent_id.value = null
|
||||
}
|
||||
|
||||
function onSubmitForm() {
|
||||
const formData: InfraFormData = {
|
||||
code: code.value || '',
|
||||
name: name.value || '',
|
||||
infraGroup_code: infraGroup_code.value || 'counter',
|
||||
parent_id: parent_id.value || null,
|
||||
}
|
||||
emit('submit', formData, resetForm)
|
||||
}
|
||||
|
||||
function onCancelForm() {
|
||||
emit('cancel', resetForm)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<form id="form-counter" @submit.prevent>
|
||||
<Block labelSize="thin" class="!mb-2.5 !pt-0 xl:!mb-3" :colCount="1">
|
||||
<Cell>
|
||||
<Label height="compact">Kode</Label>
|
||||
<Field :errMessage="errors.code">
|
||||
<Input id="code" v-model="code" v-bind="codeAttrs" :disabled="isLoading || isReadonly" />
|
||||
</Field>
|
||||
</Cell>
|
||||
<Cell>
|
||||
<Label height="compact">Nama</Label>
|
||||
<Field :errMessage="errors.name">
|
||||
<Input id="name" v-model="name" v-bind="nameAttrs" :disabled="isLoading || isReadonly" />
|
||||
</Field>
|
||||
</Cell>
|
||||
</Block>
|
||||
<div class="my-2 flex justify-end gap-2 py-2">
|
||||
<Button type="button" variant="secondary" class="w-[120px]" @click="onCancelForm"> Kembali </Button>
|
||||
<Button
|
||||
v-if="!isReadonly"
|
||||
type="button"
|
||||
class="w-[120px]"
|
||||
:disabled="isLoading || !meta.valid"
|
||||
@click="onSubmitForm"
|
||||
>
|
||||
Simpan
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
</template>
|
||||
@@ -0,0 +1,47 @@
|
||||
import type {
|
||||
Col,
|
||||
KeyLabel,
|
||||
RecComponent,
|
||||
RecStrFuncComponent,
|
||||
RecStrFuncUnknown,
|
||||
Th,
|
||||
} from '~/components/pub/my-ui/data/types'
|
||||
import { defineAsyncComponent } from 'vue'
|
||||
|
||||
type SmallDetailDto = any
|
||||
|
||||
const action = defineAsyncComponent(() => import('~/components/pub/my-ui/data/dropdown-action-dud.vue'))
|
||||
|
||||
export const cols: Col[] = [{ width: 100 }, {}, {}, { width: 50 }]
|
||||
|
||||
export const header: Th[][] = [[{ label: 'Kode' }, { label: 'Nama' }, { label: 'Counter Induk' }, { label: '' }]]
|
||||
|
||||
export const keys = ['code', 'name', 'parent', 'action']
|
||||
|
||||
export const delKeyNames: KeyLabel[] = [
|
||||
{ key: 'code', label: 'Kode' },
|
||||
{ key: 'name', label: 'Nama' },
|
||||
]
|
||||
|
||||
export const funcParsed: RecStrFuncUnknown = {
|
||||
parent: (rec: unknown): unknown => {
|
||||
const recX = rec as SmallDetailDto
|
||||
return recX.parent?.name || '-'
|
||||
},
|
||||
}
|
||||
|
||||
export const funcComponent: RecStrFuncComponent = {
|
||||
action(rec, idx) {
|
||||
const res: RecComponent = {
|
||||
idx,
|
||||
rec: rec as object,
|
||||
component: action,
|
||||
props: {
|
||||
size: 'sm',
|
||||
},
|
||||
}
|
||||
return res
|
||||
},
|
||||
}
|
||||
|
||||
export const funcHtml: RecStrFuncUnknown = {}
|
||||
@@ -0,0 +1,36 @@
|
||||
<script setup lang="ts">
|
||||
import type { PaginationMeta } from '~/components/pub/my-ui/pagination/pagination.type'
|
||||
import PaginationView from '~/components/pub/my-ui/pagination/pagination-view.vue'
|
||||
import { cols, funcComponent, funcHtml, funcParsed, header, keys } from './list-cfg'
|
||||
|
||||
interface Props {
|
||||
data: any[]
|
||||
paginationMeta: PaginationMeta
|
||||
}
|
||||
|
||||
defineProps<Props>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
pageChange: [page: number]
|
||||
}>()
|
||||
|
||||
function handlePageChange(page: number) {
|
||||
emit('pageChange', page)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="space-y-4">
|
||||
<PubBaseDataTable
|
||||
:rows="data"
|
||||
:cols="cols"
|
||||
:header="header"
|
||||
:keys="keys"
|
||||
:func-parsed="funcParsed"
|
||||
:func-html="funcHtml"
|
||||
:func-component="funcComponent"
|
||||
:skeleton-size="paginationMeta?.pageSize"
|
||||
/>
|
||||
<PaginationView :pagination-meta="paginationMeta" @page-change="handlePageChange" />
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1,108 @@
|
||||
<script setup lang="ts">
|
||||
// Components
|
||||
import Block from '~/components/pub/my-ui/doc-entry/block.vue'
|
||||
import Cell from '~/components/pub/my-ui/doc-entry/cell.vue'
|
||||
import Field from '~/components/pub/my-ui/doc-entry/field.vue'
|
||||
import Label from '~/components/pub/my-ui/doc-entry/label.vue'
|
||||
import Button from '~/components/pub/ui/button/Button.vue'
|
||||
|
||||
// Constants
|
||||
import { infraGroupCodesKeys } from "~/lib/constants"
|
||||
|
||||
// Types
|
||||
import type { InfraFormData } from '~/schemas/infra.schema'
|
||||
|
||||
// Helpers
|
||||
import type z from 'zod'
|
||||
import { useForm } from 'vee-validate'
|
||||
import { toTypedSchema } from '@vee-validate/zod'
|
||||
|
||||
interface Props {
|
||||
schema: z.ZodSchema<any>
|
||||
values: any
|
||||
isLoading?: boolean
|
||||
isReadonly?: boolean
|
||||
}
|
||||
|
||||
const props = defineProps<Props>()
|
||||
const isLoading = props.isLoading !== undefined ? props.isLoading : false
|
||||
const isReadonly = props.isReadonly !== undefined ? props.isReadonly : false
|
||||
const emit = defineEmits<{
|
||||
submit: [values: InfraFormData, resetForm: () => void]
|
||||
cancel: [resetForm: () => void]
|
||||
}>()
|
||||
|
||||
const { defineField, errors, meta } = useForm({
|
||||
validationSchema: toTypedSchema(props.schema),
|
||||
initialValues: {
|
||||
code: '',
|
||||
name: '',
|
||||
infraGroup_code: infraGroupCodesKeys.building,
|
||||
parent_id: null,
|
||||
} as Partial<InfraFormData>,
|
||||
})
|
||||
|
||||
const [code, codeAttrs] = defineField('code')
|
||||
const [name, nameAttrs] = defineField('name')
|
||||
const [infraGroup_code] = defineField('infraGroup_code')
|
||||
const [parent_id] = defineField('parent_id')
|
||||
|
||||
if (props.values) {
|
||||
if (props.values.code !== undefined) code.value = props.values.code
|
||||
if (props.values.name !== undefined) name.value = props.values.name
|
||||
if (props.values.infraGroup_code !== undefined) infraGroup_code.value = props.values.infraGroup_code
|
||||
if (props.values.parent_id !== undefined) parent_id.value = props.values.parent_id
|
||||
}
|
||||
|
||||
const resetForm = () => {
|
||||
code.value = ''
|
||||
name.value = ''
|
||||
infraGroup_code.value = infraGroupCodesKeys.building
|
||||
parent_id.value = null
|
||||
}
|
||||
|
||||
function onSubmitForm() {
|
||||
const formData: InfraFormData = {
|
||||
code: code.value || '',
|
||||
name: name.value || '',
|
||||
infraGroup_code: infraGroup_code.value || infraGroupCodesKeys.building,
|
||||
parent_id: parent_id.value || null,
|
||||
}
|
||||
emit('submit', formData, resetForm)
|
||||
}
|
||||
|
||||
function onCancelForm() {
|
||||
emit('cancel', resetForm)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<form id="form-building" @submit.prevent>
|
||||
<Block labelSize="thin" class="!mb-2.5 !pt-0 xl:!mb-3" :colCount="1">
|
||||
<Cell>
|
||||
<Label height="compact">Kode</Label>
|
||||
<Field :errMessage="errors.code">
|
||||
<Input id="code" v-model="code" v-bind="codeAttrs" :disabled="isLoading || isReadonly" />
|
||||
</Field>
|
||||
</Cell>
|
||||
<Cell>
|
||||
<Label height="compact">Nama</Label>
|
||||
<Field :errMessage="errors.name">
|
||||
<Input id="name" v-model="name" v-bind="nameAttrs" :disabled="isLoading || isReadonly" />
|
||||
</Field>
|
||||
</Cell>
|
||||
</Block>
|
||||
<div class="my-2 flex justify-end gap-2 py-2">
|
||||
<Button type="button" variant="secondary" class="w-[120px]" @click="onCancelForm"> Kembali </Button>
|
||||
<Button
|
||||
v-if="!isReadonly"
|
||||
type="button"
|
||||
class="w-[120px]"
|
||||
:disabled="isLoading || !meta.valid"
|
||||
@click="onSubmitForm"
|
||||
>
|
||||
Simpan
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
</template>
|
||||
@@ -0,0 +1,37 @@
|
||||
import type {
|
||||
Col,
|
||||
KeyLabel,
|
||||
RecComponent,
|
||||
RecStrFuncComponent,
|
||||
RecStrFuncUnknown,
|
||||
Th,
|
||||
} from '~/components/pub/my-ui/data/types'
|
||||
import { defineAsyncComponent } from 'vue'
|
||||
|
||||
const action = defineAsyncComponent(() => import('~/components/pub/my-ui/data/dropdown-action-dud.vue'))
|
||||
|
||||
export const cols: Col[] = [{}, {}, { width: 50 }]
|
||||
|
||||
export const header: Th[][] = [[{ label: 'Kode' }, { label: 'Nama' }, { label: '' }]]
|
||||
|
||||
export const keys = ['code', 'name', 'action']
|
||||
|
||||
export const delKeyNames: KeyLabel[] = [
|
||||
{ key: 'code', label: 'Kode' },
|
||||
{ key: 'name', label: 'Nama' },
|
||||
]
|
||||
|
||||
export const funcParsed: RecStrFuncUnknown = {}
|
||||
|
||||
export const funcComponent: RecStrFuncComponent = {
|
||||
action(rec, idx) {
|
||||
const res: RecComponent = {
|
||||
idx,
|
||||
rec: rec as object,
|
||||
component: action,
|
||||
}
|
||||
return res
|
||||
},
|
||||
}
|
||||
|
||||
export const funcHtml: RecStrFuncUnknown = {}
|
||||
@@ -0,0 +1,41 @@
|
||||
<script setup lang="ts">
|
||||
// Components
|
||||
import PaginationView from '~/components/pub/my-ui/pagination/pagination-view.vue'
|
||||
|
||||
// Types
|
||||
import type { PaginationMeta } from '~/components/pub/my-ui/pagination/pagination.type'
|
||||
|
||||
// Configs
|
||||
import { cols, funcComponent, funcHtml, funcParsed, header, keys } from './list-cfg'
|
||||
|
||||
interface Props {
|
||||
data: any[]
|
||||
paginationMeta: PaginationMeta
|
||||
}
|
||||
|
||||
defineProps<Props>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
pageChange: [page: number]
|
||||
}>()
|
||||
|
||||
function handlePageChange(page: number) {
|
||||
emit('pageChange', page)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="space-y-4">
|
||||
<PubMyUiDataTable
|
||||
:rows="data"
|
||||
:cols="cols"
|
||||
:header="header"
|
||||
:keys="keys"
|
||||
:func-parsed="funcParsed"
|
||||
:func-html="funcHtml"
|
||||
:func-component="funcComponent"
|
||||
:skeleton-size="paginationMeta?.pageSize"
|
||||
/>
|
||||
<PaginationView :pagination-meta="paginationMeta" @page-change="handlePageChange" />
|
||||
</div>
|
||||
</template>
|
||||
@@ -4,6 +4,7 @@ import Block from '~/components/pub/my-ui/doc-entry/block.vue'
|
||||
import Cell from '~/components/pub/my-ui/doc-entry/cell.vue'
|
||||
import Field from '~/components/pub/my-ui/doc-entry/field.vue'
|
||||
import Label from '~/components/pub/my-ui/doc-entry/label.vue'
|
||||
import TreeSelect from '~/components/pub/my-ui/select-tree/tree-select.vue'
|
||||
|
||||
// Types
|
||||
import type { DivisionFormData } from '~/schemas/division.schema.ts'
|
||||
@@ -15,6 +16,7 @@ import { useForm } from 'vee-validate'
|
||||
|
||||
interface Props {
|
||||
schema: z.ZodSchema<any>
|
||||
divisions: any[]
|
||||
values: any
|
||||
isLoading?: boolean
|
||||
isReadonly?: boolean
|
||||
@@ -33,33 +35,38 @@ const { defineField, errors, meta } = useForm({
|
||||
initialValues: {
|
||||
code: '',
|
||||
name: '',
|
||||
division_id: '',
|
||||
parent_id: null,
|
||||
division_id: null,
|
||||
} as Partial<DivisionFormData>,
|
||||
})
|
||||
|
||||
const [code, codeAttrs] = defineField('code')
|
||||
const [name, nameAttrs] = defineField('name')
|
||||
const [division, divisionAttrs] = defineField('division_id')
|
||||
const [parent, parentAttrs] = defineField('parent_id')
|
||||
const [division] = defineField('division_id')
|
||||
|
||||
// Fill fields from props.values if provided
|
||||
if (props.values) {
|
||||
if (props.values.code !== undefined) code.value = props.values.code
|
||||
if (props.values.name !== undefined) name.value = props.values.name
|
||||
if (props.values.division_id !== undefined) division.value = props.values.division_id
|
||||
if (props.values.parent_id !== undefined) parent.value = String(props.values.parent_id)
|
||||
if (props.values.division_id !== undefined) division.value = String(props.values.division_id)
|
||||
}
|
||||
|
||||
const resetForm = () => {
|
||||
code.value = ''
|
||||
name.value = ''
|
||||
division.value = ''
|
||||
parent.value = null
|
||||
division.value = null
|
||||
}
|
||||
|
||||
// Form submission handler
|
||||
function onSubmitForm(values: any) {
|
||||
function onSubmitForm() {
|
||||
const formData: DivisionFormData = {
|
||||
name: name.value || '',
|
||||
code: code.value || '',
|
||||
division_id: division.value || '',
|
||||
parent_id: parent.value || null,
|
||||
division_id: division.value || null,
|
||||
}
|
||||
emit('submit', formData, resetForm)
|
||||
}
|
||||
@@ -85,6 +92,22 @@ function onCancelForm() {
|
||||
<Input id="name" v-model="name" v-bind="nameAttrs" :disabled="isLoading || isReadonly" />
|
||||
</Field>
|
||||
</Cell>
|
||||
<Cell>
|
||||
<Label height="compact">Divisi Induk</Label>
|
||||
<Field :errMessage="errors.division">
|
||||
<TreeSelect
|
||||
id="division"
|
||||
v-model="parent"
|
||||
v-bind="parentAttrs"
|
||||
:data="divisions"
|
||||
:disabled="isLoading || isReadonly"
|
||||
placeholder="Pilih Divisi Induk"
|
||||
search-placeholder="Cari divisi"
|
||||
empty-message="Divisi tidak ditemukan"
|
||||
:on-fetch-children="async (_parentId: string) => {}"
|
||||
/>
|
||||
</Field>
|
||||
</Cell>
|
||||
</Block>
|
||||
<div class="my-2 flex justify-end gap-2 py-2">
|
||||
<Button type="button" variant="secondary" class="w-[120px]" @click="onCancelForm"> Kembali </Button>
|
||||
|
||||
@@ -12,11 +12,11 @@ type SmallDetailDto = any
|
||||
|
||||
const action = defineAsyncComponent(() => import('~/components/pub/my-ui/data/dropdown-action-ud.vue'))
|
||||
|
||||
export const cols: Col[] = [{ width: 100 }, {}, {}, { width: 50 }]
|
||||
export const cols: Col[] = [{}, {}, {}, { width: 50 }]
|
||||
|
||||
export const header: Th[][] = [[{ label: 'Kode' }, { label: 'Nama' }, { label: 'Divisi Induk' }, { label: '' }]]
|
||||
|
||||
export const keys = ['code', 'name', 'ancestor', 'action']
|
||||
export const keys = ['code', 'name', 'parent', 'action']
|
||||
|
||||
export const delKeyNames: KeyLabel[] = [
|
||||
{ key: 'code', label: 'Kode' },
|
||||
@@ -24,13 +24,9 @@ export const delKeyNames: KeyLabel[] = [
|
||||
]
|
||||
|
||||
export const funcParsed: RecStrFuncUnknown = {
|
||||
ancestor: (rec: unknown): unknown => {
|
||||
parent: (rec: unknown): unknown => {
|
||||
const recX = rec as SmallDetailDto
|
||||
if (recX.meta === null) {
|
||||
return '-'
|
||||
}
|
||||
|
||||
return recX.meta.name
|
||||
return recX.parent?.name || '-'
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
<script setup lang="ts">
|
||||
import type { PaginationMeta } from '~/components/pub/my-ui/pagination/pagination.type'
|
||||
// Components
|
||||
import PaginationView from '~/components/pub/my-ui/pagination/pagination-view.vue'
|
||||
|
||||
// Types
|
||||
import type { PaginationMeta } from '~/components/pub/my-ui/pagination/pagination.type'
|
||||
|
||||
// Configs
|
||||
import { cols, funcComponent, funcHtml, funcParsed, header, keys } from './list-cfg'
|
||||
|
||||
interface Props {
|
||||
@@ -31,7 +36,6 @@ function handlePageChange(page: number) {
|
||||
:func-component="funcComponent"
|
||||
:skeleton-size="paginationMeta?.pageSize"
|
||||
/>
|
||||
<!-- FIXME: pindahkan ke content/division/list.vue -->
|
||||
<PaginationView :pagination-meta="paginationMeta" @page-change="handlePageChange" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
<script setup lang="ts">
|
||||
import type { FormErrors } from '~/types/error'
|
||||
import { toTypedSchema } from '@vee-validate/zod'
|
||||
import Block from '~/components/pub/my-ui/form/block.vue'
|
||||
import Block from '~/components/pub/my-ui/doc-entry/block.vue'
|
||||
import Cell from '~/components/pub/my-ui/doc-entry/cell.vue'
|
||||
import Field from '~/components/pub/my-ui/doc-entry/field.vue'
|
||||
import Label from '~/components/pub/my-ui/doc-entry/label.vue'
|
||||
import Combobox from '~/components/pub/my-ui/form/combobox.vue'
|
||||
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 { Form } from '~/components/pub/ui/form'
|
||||
import DatepickerSingle from '~/components/pub/my-ui/form/datepicker-single.vue'
|
||||
|
||||
@@ -127,8 +127,8 @@ function onAddSep() {
|
||||
</Button>
|
||||
</span>
|
||||
</div>
|
||||
<Block>
|
||||
<FieldGroup :column="3">
|
||||
<Block :colCount="3">
|
||||
<Cell>
|
||||
<Label label-for="patient_name">Nama Pasien</Label>
|
||||
<Field id="patient_name" :errors="errors">
|
||||
<FormField v-slot="{ componentField }" name="patient_name">
|
||||
@@ -145,10 +145,10 @@ function onAddSep() {
|
||||
</FormItem>
|
||||
</FormField>
|
||||
</Field>
|
||||
</FieldGroup>
|
||||
</Cell>
|
||||
|
||||
<!-- NIK -->
|
||||
<FieldGroup :column="3">
|
||||
<Cell :cosSpan="3">
|
||||
<Label label-for="nik">NIK</Label>
|
||||
<Field id="nik" :errors="errors">
|
||||
<FormField v-slot="{ componentField }" name="nik">
|
||||
@@ -160,10 +160,8 @@ function onAddSep() {
|
||||
</FormItem>
|
||||
</FormField>
|
||||
</Field>
|
||||
</FieldGroup>
|
||||
|
||||
<!-- No. RM -->
|
||||
<FieldGroup :column="3">
|
||||
</Cell>
|
||||
<Cell>
|
||||
<Label label-for="rm">No. RM</Label>
|
||||
<Field id="rm" :errors="errors">
|
||||
<FormField v-slot="{ componentField }" name="rm">
|
||||
@@ -175,16 +173,17 @@ function onAddSep() {
|
||||
</FormItem>
|
||||
</FormField>
|
||||
</Field>
|
||||
</FieldGroup>
|
||||
<Separator />
|
||||
</Cell>
|
||||
</Block>
|
||||
<Separator />
|
||||
|
||||
<div class="p-2">
|
||||
<h2 class="text-md font-semibold">Data Kunjungan</h2>
|
||||
</div>
|
||||
<Block>
|
||||
|
||||
<Block :colCount="3">
|
||||
<!-- Dokter (Combobox) -->
|
||||
<FieldGroup :column="3">
|
||||
<Cell :cosSpan="3">
|
||||
<Label label-for="doctor_id">Dokter</Label>
|
||||
<Field id="doctor_id" :errors="errors">
|
||||
<FormField v-slot="{ componentField }" name="doctor_id">
|
||||
@@ -196,10 +195,10 @@ function onAddSep() {
|
||||
</FormItem>
|
||||
</FormField>
|
||||
</Field>
|
||||
</FieldGroup>
|
||||
</Cell>
|
||||
|
||||
<!-- Tanggal Daftar (DatePicker) -->
|
||||
<FieldGroup :column="3">
|
||||
<Cell :cosSpan="3">
|
||||
<Label label-for="register_date">Tanggal Daftar</Label>
|
||||
<Field id="register_date" :errors="errors">
|
||||
<FormField v-slot="{ componentField }" name="register_date">
|
||||
@@ -211,10 +210,10 @@ function onAddSep() {
|
||||
</FormItem>
|
||||
</FormField>
|
||||
</Field>
|
||||
</FieldGroup>
|
||||
</Cell>
|
||||
|
||||
<!-- Jenis Pembayaran (Combobox) -->
|
||||
<FieldGroup :column="3">
|
||||
<Cell :cosSpan="3">
|
||||
<Label label-for="payment_type">Jenis Pembayaran</Label>
|
||||
<Field id="payment_type" :errors="errors">
|
||||
<FormField v-slot="{ componentField }" name="payment_type">
|
||||
@@ -227,9 +226,11 @@ function onAddSep() {
|
||||
</FormItem>
|
||||
</FormField>
|
||||
</Field>
|
||||
</FieldGroup>
|
||||
</Cell>
|
||||
</Block>
|
||||
|
||||
<FieldGroup :column="3">
|
||||
<Block :colCount="3">
|
||||
<Cell :cosSpan="3">
|
||||
<Label label-for="bpjs_number">Kelompok Peserta</Label>
|
||||
<Field id="bpjs_number" :errors="errors">
|
||||
<FormField v-slot="{ componentField }" name="bpjs_number">
|
||||
@@ -245,10 +246,10 @@ function onAddSep() {
|
||||
</FormItem>
|
||||
</FormField>
|
||||
</Field>
|
||||
</FieldGroup>
|
||||
</Cell>
|
||||
|
||||
<!-- No. Kartu BPJS -->
|
||||
<FieldGroup :column="3">
|
||||
<Cell :cosSpan="3">
|
||||
<Label label-for="bpjs_number">No. Kartu BPJS</Label>
|
||||
<Field id="bpjs_number" :errors="errors">
|
||||
<FormField v-slot="{ componentField }" name="bpjs_number">
|
||||
@@ -264,10 +265,10 @@ function onAddSep() {
|
||||
</FormItem>
|
||||
</FormField>
|
||||
</Field>
|
||||
</FieldGroup>
|
||||
</Cell>
|
||||
|
||||
<!-- Jenis SEP -->
|
||||
<FieldGroup :column="3">
|
||||
<Cell :cosSpan="3">
|
||||
<Label label-for="sep_type">Jenis SEP</Label>
|
||||
<Field id="sep_type" :errors="errors">
|
||||
<FormField v-slot="{ componentField }" name="sep_type">
|
||||
@@ -279,10 +280,12 @@ function onAddSep() {
|
||||
</FormItem>
|
||||
</FormField>
|
||||
</Field>
|
||||
</FieldGroup>
|
||||
</Cell>
|
||||
</Block>
|
||||
|
||||
<Block :colCount="3">
|
||||
<!-- No. SEP (input + tombol +) -->
|
||||
<FieldGroup :column="3">
|
||||
<Cell :cosSpan="3">
|
||||
<Label label-for="sep_number">No. SEP</Label>
|
||||
<Field id="sep_number" :errors="errors">
|
||||
<FormField v-slot="{ componentField }" name="sep_number">
|
||||
@@ -302,10 +305,10 @@ function onAddSep() {
|
||||
</FormItem>
|
||||
</FormField>
|
||||
</Field>
|
||||
</FieldGroup>
|
||||
</Cell>
|
||||
|
||||
<!-- Dokumen SEP (file) -->
|
||||
<FieldGroup :column="3">
|
||||
<Cell :cosSpan="3">
|
||||
<Label label-for="sep_file">Dokumen SEP</Label>
|
||||
<Field id="sep_file" :errors="errors">
|
||||
<FormField v-slot="{ componentField }" name="sep_file">
|
||||
@@ -323,10 +326,10 @@ function onAddSep() {
|
||||
</FormItem>
|
||||
</FormField>
|
||||
</Field>
|
||||
</FieldGroup>
|
||||
</Cell>
|
||||
|
||||
<!-- Dokumen SIPP (file) -->
|
||||
<FieldGroup :column="3">
|
||||
<Cell :cosSpan="3" labelSize="thin">
|
||||
<Label label-for="sipp_file">Dokumen SIPP</Label>
|
||||
<Field id="sipp_file" :errors="errors">
|
||||
<FormField v-slot="{ componentField }" name="sipp_file">
|
||||
@@ -344,7 +347,7 @@ function onAddSep() {
|
||||
</FormItem>
|
||||
</FormField>
|
||||
</Field>
|
||||
</FieldGroup>
|
||||
</Cell>
|
||||
</Block>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
<script setup lang="ts">
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="p-10 text-center">Hello World!!!</div>
|
||||
</template>
|
||||
@@ -1,6 +1,11 @@
|
||||
<script setup lang="ts">
|
||||
import type { PaginationMeta } from '~/components/pub/my-ui/pagination/pagination.type'
|
||||
// Components
|
||||
import PaginationView from '~/components/pub/my-ui/pagination/pagination-view.vue'
|
||||
|
||||
// Types
|
||||
import type { PaginationMeta } from '~/components/pub/my-ui/pagination/pagination.type'
|
||||
|
||||
// Configs
|
||||
import { cols, funcComponent, funcHtml, funcParsed, header, keys } from './list-cfg'
|
||||
|
||||
interface Props {
|
||||
|
||||
@@ -0,0 +1,125 @@
|
||||
<script setup lang="ts">
|
||||
// Components
|
||||
import Block from '~/components/pub/my-ui/doc-entry/block.vue'
|
||||
import Cell from '~/components/pub/my-ui/doc-entry/cell.vue'
|
||||
import Field from '~/components/pub/my-ui/doc-entry/field.vue'
|
||||
import Label from '~/components/pub/my-ui/doc-entry/label.vue'
|
||||
import Button from '~/components/pub/ui/button/Button.vue'
|
||||
import Combobox from '~/components/pub/my-ui/form/combobox.vue'
|
||||
|
||||
// Constants
|
||||
import { infraGroupCodesKeys } from "~/lib/constants"
|
||||
|
||||
// Types
|
||||
import type { InfraFormData } from '~/schemas/infra.schema'
|
||||
|
||||
// Helpers
|
||||
import type z from 'zod'
|
||||
import { useForm } from 'vee-validate'
|
||||
import { toTypedSchema } from '@vee-validate/zod'
|
||||
|
||||
interface Props {
|
||||
schema: z.ZodSchema<any>
|
||||
parents: any[]
|
||||
values: any
|
||||
isLoading?: boolean
|
||||
isReadonly?: boolean
|
||||
}
|
||||
|
||||
const props = defineProps<Props>()
|
||||
const isLoading = props.isLoading !== undefined ? props.isLoading : false
|
||||
const isReadonly = props.isReadonly !== undefined ? props.isReadonly : false
|
||||
const emit = defineEmits<{
|
||||
submit: [values: InfraFormData, resetForm: () => void]
|
||||
cancel: [resetForm: () => void]
|
||||
}>()
|
||||
|
||||
const { defineField, errors, meta } = useForm({
|
||||
validationSchema: toTypedSchema(props.schema),
|
||||
initialValues: {
|
||||
code: '',
|
||||
name: '',
|
||||
infraGroup_code: infraGroupCodesKeys.floor,
|
||||
parent_id: null,
|
||||
} as Partial<InfraFormData>,
|
||||
})
|
||||
|
||||
const [code, codeAttrs] = defineField('code')
|
||||
const [name, nameAttrs] = defineField('name')
|
||||
const [infraGroup_code] = defineField('infraGroup_code')
|
||||
const [parent_id, parentIdAttrs] = defineField('parent_id')
|
||||
|
||||
if (props.values) {
|
||||
if (props.values.code !== undefined) code.value = props.values.code
|
||||
if (props.values.name !== undefined) name.value = props.values.name
|
||||
if (props.values.infraGroup_code !== undefined) infraGroup_code.value = props.values.infraGroup_code
|
||||
if (props.values.parent_id !== undefined) parent_id.value = String(props.values.parent_id)
|
||||
}
|
||||
|
||||
const resetForm = () => {
|
||||
code.value = ''
|
||||
name.value = ''
|
||||
infraGroup_code.value = infraGroupCodesKeys.floor
|
||||
parent_id.value = null
|
||||
}
|
||||
|
||||
function onSubmitForm() {
|
||||
const formData: InfraFormData = {
|
||||
code: code.value || '',
|
||||
name: name.value || '',
|
||||
infraGroup_code: infraGroup_code.value || infraGroupCodesKeys.floor,
|
||||
parent_id: parent_id.value ? Number(parent_id.value) : null,
|
||||
}
|
||||
emit('submit', formData, resetForm)
|
||||
}
|
||||
|
||||
function onCancelForm() {
|
||||
emit('cancel', resetForm)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<form id="form-floor" @submit.prevent>
|
||||
<Block labelSize="thin" class="!mb-2.5 !pt-0 xl:!mb-3" :colCount="1">
|
||||
<Cell>
|
||||
<Label height="compact">Kode</Label>
|
||||
<Field :errMessage="errors.code">
|
||||
<Input id="code" v-model="code" v-bind="codeAttrs" :disabled="isLoading || isReadonly" />
|
||||
</Field>
|
||||
</Cell>
|
||||
<Cell>
|
||||
<Label height="compact">Nama</Label>
|
||||
<Field :errMessage="errors.name">
|
||||
<Input id="name" v-model="name" v-bind="nameAttrs" :disabled="isLoading || isReadonly" />
|
||||
</Field>
|
||||
</Cell>
|
||||
<Cell>
|
||||
<Label height="compact">Gedung</Label>
|
||||
<Field :errMessage="errors.parent_id">
|
||||
<Combobox
|
||||
id="parent"
|
||||
v-model="parent_id"
|
||||
v-bind="parentIdAttrs"
|
||||
:items="parents"
|
||||
:disabled="isLoading || isReadonly"
|
||||
placeholder="Pilih Gedung"
|
||||
search-placeholder="Cari Gedung"
|
||||
empty-message="Item tidak ditemukan"
|
||||
/>
|
||||
</Field>
|
||||
</Cell>
|
||||
</Block>
|
||||
<div class="my-2 flex justify-end gap-2 py-2">
|
||||
<Button type="button" variant="secondary" class="w-[120px]" @click="onCancelForm"> Kembali </Button>
|
||||
<Button
|
||||
v-if="!isReadonly"
|
||||
type="button"
|
||||
class="w-[120px]"
|
||||
:disabled="isLoading || !meta.valid"
|
||||
@click="onSubmitForm"
|
||||
>
|
||||
Simpan
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
</template>
|
||||
@@ -0,0 +1,44 @@
|
||||
import type {
|
||||
Col,
|
||||
KeyLabel,
|
||||
RecComponent,
|
||||
RecStrFuncComponent,
|
||||
RecStrFuncUnknown,
|
||||
Th,
|
||||
} from '~/components/pub/my-ui/data/types'
|
||||
import { defineAsyncComponent } from 'vue'
|
||||
|
||||
type SmallDetailDto = any
|
||||
|
||||
const action = defineAsyncComponent(() => import('~/components/pub/my-ui/data/dropdown-action-dud.vue'))
|
||||
|
||||
export const cols: Col[] = [{}, {}, {}, { width: 50 }]
|
||||
|
||||
export const header: Th[][] = [[{ label: 'Kode' }, { label: 'Nama' }, { label: 'Gedung' }, { label: '' }]]
|
||||
|
||||
export const keys = ['code', 'name', 'parent', 'action']
|
||||
|
||||
export const delKeyNames: KeyLabel[] = [
|
||||
{ key: 'code', label: 'Kode' },
|
||||
{ key: 'name', label: 'Nama' },
|
||||
]
|
||||
|
||||
export const funcParsed: RecStrFuncUnknown = {
|
||||
parent: (rec: unknown): unknown => {
|
||||
const recX = rec as SmallDetailDto
|
||||
return recX.parent?.name || '-'
|
||||
},
|
||||
}
|
||||
|
||||
export const funcComponent: RecStrFuncComponent = {
|
||||
action(rec, idx) {
|
||||
const res: RecComponent = {
|
||||
idx,
|
||||
rec: rec as object,
|
||||
component: action,
|
||||
}
|
||||
return res
|
||||
},
|
||||
}
|
||||
|
||||
export const funcHtml: RecStrFuncUnknown = {}
|
||||
@@ -0,0 +1,41 @@
|
||||
<script setup lang="ts">
|
||||
// Components
|
||||
import PaginationView from '~/components/pub/my-ui/pagination/pagination-view.vue'
|
||||
|
||||
// Types
|
||||
import type { PaginationMeta } from '~/components/pub/my-ui/pagination/pagination.type'
|
||||
|
||||
// Configs
|
||||
import { cols, funcComponent, funcHtml, funcParsed, header, keys } from './list-cfg'
|
||||
|
||||
interface Props {
|
||||
data: any[]
|
||||
paginationMeta: PaginationMeta
|
||||
}
|
||||
|
||||
defineProps<Props>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
pageChange: [page: number]
|
||||
}>()
|
||||
|
||||
function handlePageChange(page: number) {
|
||||
emit('pageChange', page)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="space-y-4">
|
||||
<PubMyUiDataTable
|
||||
:rows="data"
|
||||
:cols="cols"
|
||||
:header="header"
|
||||
:keys="keys"
|
||||
:func-parsed="funcParsed"
|
||||
:func-html="funcHtml"
|
||||
:func-component="funcComponent"
|
||||
:skeleton-size="paginationMeta?.pageSize"
|
||||
/>
|
||||
<PaginationView :pagination-meta="paginationMeta" @page-change="handlePageChange" />
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1,43 @@
|
||||
<script setup lang="ts">
|
||||
import Block from '~/components/pub/my-ui/form/block.vue'
|
||||
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 Action from '~/components/pub/my-ui/nav-footer/ba-dr-su.vue'
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<form id="entry-form">
|
||||
<div class="mb-5 border-b border-b-slate-300 pb-3 text-lg xl:text-xl">
|
||||
<Block>
|
||||
<FieldGroup :column="3">
|
||||
<Label>Nama</Label>
|
||||
<Field>
|
||||
<Input type="text" name="name" />
|
||||
</Field>
|
||||
</FieldGroup>
|
||||
<FieldGroup :column="3">
|
||||
<Label>Nama</Label>
|
||||
<Field>
|
||||
<Input type="text" name="name" />
|
||||
</Field>
|
||||
</FieldGroup>
|
||||
<FieldGroup :column="3">
|
||||
<Label>Nomor RM</Label>
|
||||
<Field>
|
||||
<Input type="text" name="name" />
|
||||
</Field>
|
||||
</FieldGroup>
|
||||
<FieldGroup>
|
||||
<Label dynamic>Alamat</Label>
|
||||
<Field>
|
||||
<Input type="text" name="name" />
|
||||
</Field>
|
||||
</FieldGroup>
|
||||
</Block>
|
||||
</div>
|
||||
<div class="my-2 flex justify-end py-2">
|
||||
<Action />
|
||||
</div>
|
||||
</form>
|
||||
</template>
|
||||
@@ -0,0 +1,131 @@
|
||||
import type {
|
||||
Col,
|
||||
KeyLabel,
|
||||
RecComponent,
|
||||
RecStrFuncComponent,
|
||||
RecStrFuncUnknown,
|
||||
Th,
|
||||
} from '~/components/pub/my-ui/data/types'
|
||||
import { defineAsyncComponent } from 'vue'
|
||||
|
||||
type SmallDetailDto = any
|
||||
|
||||
const action = defineAsyncComponent(() => import('~/components/pub/my-ui/data/dropdown-action-dud.vue'))
|
||||
const statusBadge = defineAsyncComponent(() => import('./status-badge.vue'))
|
||||
|
||||
export const cols: Col[] = [
|
||||
{},
|
||||
{},
|
||||
{},
|
||||
{ width: 100 },
|
||||
{ width: 120 },
|
||||
{},
|
||||
{},
|
||||
{},
|
||||
{ width: 100 },
|
||||
{ width: 100 },
|
||||
{},
|
||||
{ width: 50 },
|
||||
]
|
||||
|
||||
export const header: Th[][] = [
|
||||
[
|
||||
{ label: 'Nama' },
|
||||
{ label: 'Rekam Medis' },
|
||||
{ label: 'KTP' },
|
||||
{ label: 'Tgl Lahir' },
|
||||
{ label: 'Umur' },
|
||||
{ label: 'JK' },
|
||||
{ label: 'Pendidikan' },
|
||||
{ label: 'Status' },
|
||||
{ label: '' },
|
||||
],
|
||||
]
|
||||
|
||||
export const keys = [
|
||||
'name',
|
||||
'medicalRecord_number',
|
||||
'identity_number',
|
||||
'birth_date',
|
||||
'patient_age',
|
||||
'gender',
|
||||
'education',
|
||||
'status',
|
||||
'action',
|
||||
]
|
||||
|
||||
export const delKeyNames: KeyLabel[] = [
|
||||
{ key: 'code', label: 'Kode' },
|
||||
{ key: 'name', label: 'Nama' },
|
||||
]
|
||||
|
||||
export const funcParsed: RecStrFuncUnknown = {
|
||||
name: (rec: unknown): unknown => {
|
||||
const recX = rec as SmallDetailDto
|
||||
return `${recX.firstName} ${recX.middleName || ''} ${recX.lastName || ''}`
|
||||
},
|
||||
identity_number: (rec: unknown): unknown => {
|
||||
const recX = rec as SmallDetailDto
|
||||
if (recX.identity_number?.substring(0, 5) === 'BLANK') {
|
||||
return '(TANPA NIK)'
|
||||
}
|
||||
return recX.identity_number
|
||||
},
|
||||
birth_date: (rec: unknown): unknown => {
|
||||
const recX = rec as SmallDetailDto
|
||||
if (typeof recX.birth_date == 'object' && recX.birth_date) {
|
||||
return (recX.birth_date as Date).toLocaleDateString()
|
||||
} else if (typeof recX.birth_date == 'string') {
|
||||
return (recX.birth_date as string).substring(0, 10)
|
||||
}
|
||||
return recX.birth_date
|
||||
},
|
||||
patient_age: (rec: unknown): unknown => {
|
||||
const recX = rec as SmallDetailDto
|
||||
return recX.birth_date?.split('T')[0]
|
||||
},
|
||||
gender: (rec: unknown): unknown => {
|
||||
const recX = rec as SmallDetailDto
|
||||
if (typeof recX?.gender_code !== 'number' && recX?.gender_code !== '') {
|
||||
return 'Tidak Diketahui'
|
||||
}
|
||||
return recX.gender_code
|
||||
},
|
||||
education: (rec: unknown): unknown => {
|
||||
const recX = rec as SmallDetailDto
|
||||
if (typeof recX.education_code == 'number' && recX.education_code >= 0) {
|
||||
return recX.education_code
|
||||
} else if (typeof recX.education_code) {
|
||||
return recX.education_code
|
||||
}
|
||||
return '-'
|
||||
},
|
||||
}
|
||||
|
||||
export const funcComponent: RecStrFuncComponent = {
|
||||
action(rec, idx) {
|
||||
const res: RecComponent = {
|
||||
idx,
|
||||
rec: rec as object,
|
||||
component: action,
|
||||
}
|
||||
return res
|
||||
},
|
||||
status(rec, idx) {
|
||||
if (rec.status === null) {
|
||||
rec.status_code = 0
|
||||
}
|
||||
const res: RecComponent = {
|
||||
idx,
|
||||
rec: rec as object,
|
||||
component: statusBadge,
|
||||
}
|
||||
return res
|
||||
},
|
||||
}
|
||||
|
||||
export const funcHtml: RecStrFuncUnknown = {
|
||||
patient_address(_rec) {
|
||||
return '-'
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
<script setup lang="ts">
|
||||
import { cols, funcComponent, funcHtml, funcParsed, header, keys } from './list-cfg'
|
||||
|
||||
defineProps<{
|
||||
data: any[]
|
||||
}>()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<PubMyUiDataTable
|
||||
:rows="data"
|
||||
:cols="cols"
|
||||
:header="header"
|
||||
:keys="keys"
|
||||
:func-parsed="funcParsed"
|
||||
:func-html="funcHtml"
|
||||
:func-component="funcComponent"
|
||||
/>
|
||||
</template>
|
||||
@@ -0,0 +1,20 @@
|
||||
<script setup lang="ts">
|
||||
import { cols, funcComponent, funcHtml, funcParsed, header, keys } from './list-cfg'
|
||||
|
||||
defineProps<{ data: any[] }>()
|
||||
const modelValue = defineModel<any | null>()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<PubMyUiDataTable
|
||||
v-model="modelValue"
|
||||
select-mode="multi"
|
||||
:rows="data"
|
||||
:cols="cols"
|
||||
:header="header"
|
||||
:keys="keys"
|
||||
:func-parsed="funcParsed"
|
||||
:func-html="funcHtml"
|
||||
:func-component="funcComponent"
|
||||
/>
|
||||
</template>
|
||||
@@ -0,0 +1,46 @@
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import { Trash2 } from 'lucide-vue-next'
|
||||
// import { Button } from '@/components/ui/button'
|
||||
// import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table'
|
||||
|
||||
interface Diagnosa {
|
||||
id: number
|
||||
diagnosa: string
|
||||
icd: string
|
||||
}
|
||||
|
||||
const list = ref<Diagnosa[]>([{ id: 1, diagnosa: 'Acute appendicitis', icd: 'K35' }])
|
||||
|
||||
function removeItem(id: number) {
|
||||
list.value = list.value.filter((item) => item.id !== id)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="rounded-lg border bg-white shadow-sm">
|
||||
<Table>
|
||||
<TableHeader class="bg-gray-50">
|
||||
<TableRow>
|
||||
<TableHead class="w-[50px] text-center">NO.</TableHead>
|
||||
<TableHead>DIAGNOSA</TableHead>
|
||||
<TableHead class="w-[120px]">ICD-X</TableHead>
|
||||
<TableHead class="w-[80px] text-center">AKSI</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
|
||||
<TableBody>
|
||||
<TableRow v-for="(item, i) in list" :key="item.id">
|
||||
<TableCell class="text-center font-medium">{{ i + 1 }}</TableCell>
|
||||
<TableCell>{{ item.diagnosa }}</TableCell>
|
||||
<TableCell>{{ item.icd }}</TableCell>
|
||||
<TableCell class="text-center">
|
||||
<Button variant="ghost" size="icon" @click="removeItem(item.id)">
|
||||
<Trash2 class="h-4 w-4 text-gray-500 hover:text-red-500" />
|
||||
</Button>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
</TableBody>
|
||||
</Table>
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1,29 @@
|
||||
<script setup lang="ts">
|
||||
import { Badge } from '~/components/pub/ui/badge'
|
||||
|
||||
const props = defineProps<{
|
||||
rec: any
|
||||
idx?: number
|
||||
}>()
|
||||
|
||||
const doctorStatus = {
|
||||
0: 'Tidak Aktif',
|
||||
1: 'Aktif',
|
||||
}
|
||||
|
||||
const statusText = computed(() => {
|
||||
return doctorStatus[props.rec.status_code as keyof typeof doctorStatus]
|
||||
})
|
||||
|
||||
const badgeVariant = computed(() => {
|
||||
return props.rec.status_code === 1 ? 'default' : 'destructive'
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex justify-center">
|
||||
<Badge :variant="badgeVariant">
|
||||
{{ statusText }}
|
||||
</Badge>
|
||||
</div>
|
||||
</template>
|
||||
@@ -1,125 +1,119 @@
|
||||
<script setup lang="ts">
|
||||
import type { FormErrors } from '~/types/error'
|
||||
import { toTypedSchema } from '@vee-validate/zod'
|
||||
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 { Form } from '~/components/pub/ui/form'
|
||||
// Components
|
||||
import Block from '~/components/pub/my-ui/doc-entry/block.vue'
|
||||
import Cell from '~/components/pub/my-ui/doc-entry/cell.vue'
|
||||
import Field from '~/components/pub/my-ui/doc-entry/field.vue'
|
||||
import Label from '~/components/pub/my-ui/doc-entry/label.vue'
|
||||
import Combobox from '~/components/pub/my-ui/form/combobox.vue'
|
||||
|
||||
interface InstallationFormData {
|
||||
name: string
|
||||
code: string
|
||||
encounterClassCode: string
|
||||
// Types
|
||||
import type { InstallationFormData } from '~/schemas/installation.schema.ts'
|
||||
|
||||
// Helpers
|
||||
import type z from 'zod'
|
||||
import { toTypedSchema } from '@vee-validate/zod'
|
||||
import { useForm } from 'vee-validate'
|
||||
|
||||
interface Props {
|
||||
schema: z.ZodSchema<any>
|
||||
encounterClasses: any[]
|
||||
values: any
|
||||
isLoading?: boolean
|
||||
isReadonly?: boolean
|
||||
}
|
||||
|
||||
const props = defineProps<{
|
||||
installation: {
|
||||
msg: {
|
||||
placeholder: string
|
||||
}
|
||||
items: {
|
||||
value: string
|
||||
label: string
|
||||
code: string
|
||||
}[]
|
||||
}
|
||||
schema: any
|
||||
initialValues?: Partial<InstallationFormData>
|
||||
errors?: FormErrors
|
||||
}>()
|
||||
|
||||
const props = defineProps<Props>()
|
||||
const isLoading = props.isLoading !== undefined ? props.isLoading : false
|
||||
const isReadonly = props.isReadonly !== undefined ? props.isReadonly : false
|
||||
const emit = defineEmits<{
|
||||
'submit': [values: InstallationFormData, resetForm: () => void]
|
||||
'cancel': [resetForm: () => void]
|
||||
submit: [values: InstallationFormData, resetForm: () => void]
|
||||
cancel: [resetForm: () => void]
|
||||
}>()
|
||||
|
||||
const formSchema = toTypedSchema(props.schema)
|
||||
const { defineField, errors, meta } = useForm({
|
||||
validationSchema: toTypedSchema(props.schema),
|
||||
initialValues: {
|
||||
code: '',
|
||||
name: '',
|
||||
encounterClass_code: '',
|
||||
} as Partial<InstallationFormData>,
|
||||
})
|
||||
|
||||
const [code, codeAttrs] = defineField('code')
|
||||
const [name, nameAttrs] = defineField('name')
|
||||
const [encounterClassCode, encounterClassCodeAttrs] = defineField('encounterClass_code')
|
||||
|
||||
// Fill fields from props.values if provided
|
||||
if (props.values) {
|
||||
if (props.values.code !== undefined) code.value = props.values.code
|
||||
if (props.values.name !== undefined) name.value = props.values.name
|
||||
if (props.values.encounterClass_code !== undefined) encounterClassCode.value = props.values.encounterClass_code
|
||||
}
|
||||
|
||||
const resetForm = () => {
|
||||
code.value = ''
|
||||
name.value = ''
|
||||
encounterClassCode.value = ''
|
||||
}
|
||||
|
||||
// Form submission handler
|
||||
function onSubmitForm(values: any, { resetForm }: { resetForm: () => void }) {
|
||||
function onSubmitForm(values: any) {
|
||||
const formData: InstallationFormData = {
|
||||
name: values.name || '',
|
||||
code: values.code || '',
|
||||
encounterClassCode: values.encounterClassCode || '',
|
||||
name: name.value || '',
|
||||
code: code.value || '',
|
||||
encounterClass_code: encounterClassCode.value || '',
|
||||
}
|
||||
emit('submit', formData, resetForm)
|
||||
}
|
||||
|
||||
// Form cancel handler
|
||||
function onCancelForm({ resetForm }: { resetForm: () => void }) {
|
||||
function onCancelForm() {
|
||||
emit('cancel', resetForm)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Form
|
||||
v-slot="{ handleSubmit, resetForm }" as="" keep-values :validation-schema="formSchema"
|
||||
:initial-values="initialValues"
|
||||
>
|
||||
<form id="entry-form" @submit="handleSubmit($event, (values) => onSubmitForm(values, { resetForm }))">
|
||||
<div class="mb-5 border-b border-b-slate-300 pb-3 text-lg xl:text-xl">
|
||||
<div class="flex flex-col justify-between">
|
||||
<FieldGroup>
|
||||
<Label label-for="name">Nama</Label>
|
||||
<Field id="name" :errors="errors">
|
||||
<FormField v-slot="{ componentField }" name="name">
|
||||
<FormItem>
|
||||
<FormControl>
|
||||
<Input
|
||||
id="name" type="text" placeholder="Masukkan nama instalasi" autocomplete="off"
|
||||
v-bind="componentField"
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
</FormField>
|
||||
</Field>
|
||||
</FieldGroup>
|
||||
|
||||
<FieldGroup>
|
||||
<Label label-for="code">Kode</Label>
|
||||
<Field id="code" :errors="errors">
|
||||
<FormField v-slot="{ componentField }" name="code">
|
||||
<FormItem>
|
||||
<FormControl>
|
||||
<Input id="code" type="text" placeholder="Masukkan kode instalasi" autocomplete="off" v-bind="componentField" />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
</FormField>
|
||||
</Field>
|
||||
</FieldGroup>
|
||||
|
||||
<FieldGroup>
|
||||
<Label label-for="parentId">Encounter Class</Label>
|
||||
<Field id="encounterClassCode" :errors="errors">
|
||||
<FormField v-slot="{ componentField }" name="encounterClassCode">
|
||||
<FormItem>
|
||||
<FormControl>
|
||||
<Select
|
||||
v-bind="componentField"
|
||||
:items="installation.items"
|
||||
:placeholder="installation.msg.placeholder"
|
||||
/>
|
||||
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
</FormField>
|
||||
</Field>
|
||||
</FieldGroup>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex justify-end gap-2 mt-4">
|
||||
<Button type="button" variant="outline" @click="onCancelForm({ resetForm })">
|
||||
Batal
|
||||
</Button>
|
||||
<Button type="submit">
|
||||
Simpan
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
</Form>
|
||||
<form id="form-unit" @submit.prevent>
|
||||
<Block labelSize="thin" class="!mb-2.5 !pt-0 xl:!mb-3" :colCount="1">
|
||||
<Cell>
|
||||
<Label height="compact">Kode</Label>
|
||||
<Field :errMessage="errors.code">
|
||||
<Input id="code" v-model="code" v-bind="codeAttrs" :disabled="isLoading || isReadonly" />
|
||||
</Field>
|
||||
</Cell>
|
||||
<Cell>
|
||||
<Label height="compact">Nama</Label>
|
||||
<Field :errMessage="errors.name">
|
||||
<Input id="name" v-model="name" v-bind="nameAttrs" :disabled="isLoading || isReadonly" />
|
||||
</Field>
|
||||
</Cell>
|
||||
<Cell>
|
||||
<Label height="compact">Encounter Class</Label>
|
||||
<Field :errMessage="errors.encounterClass_code">
|
||||
<Combobox
|
||||
id="encounterClass_code"
|
||||
v-model="encounterClassCode"
|
||||
v-bind="encounterClassCodeAttrs"
|
||||
:items="encounterClasses"
|
||||
:disabled="isLoading || isReadonly"
|
||||
placeholder="Pilih Encounter Class"
|
||||
search-placeholder="Cari Encounter Class"
|
||||
empty-message="Encounter tidak ditemukan"
|
||||
/>
|
||||
</Field>
|
||||
</Cell>
|
||||
</Block>
|
||||
<div class="my-2 flex justify-end gap-2 py-2">
|
||||
<Button type="button" variant="secondary" class="w-[120px]" @click="onCancelForm"> Kembali </Button>
|
||||
<Button
|
||||
v-if="!isReadonly"
|
||||
type="button"
|
||||
class="w-[120px]"
|
||||
:disabled="isLoading || !meta.valid"
|
||||
@click="onSubmitForm"
|
||||
>
|
||||
Simpan
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
</template>
|
||||
|
||||
@@ -12,13 +12,11 @@ type SmallDetailDto = any
|
||||
|
||||
const action = defineAsyncComponent(() => import('~/components/pub/my-ui/data/dropdown-action-ud.vue'))
|
||||
|
||||
export const cols: Col[] = [{ width: 100 }, {}, {}, {}, { width: 50 }]
|
||||
export const cols: Col[] = [{}, {}, {}, { width: 50 }]
|
||||
|
||||
export const header: Th[][] = [
|
||||
[{ label: 'Id' }, { label: 'Nama' }, { label: 'Kode' }, { label: 'Encounter Class' }, { label: '' }],
|
||||
]
|
||||
export const header: Th[][] = [[{ label: 'Kode' }, { label: 'Nama' }, { label: 'Encounter Class' }, { label: '' }]]
|
||||
|
||||
export const keys = ['id', 'name', 'cellphone', 'religion_code', 'action']
|
||||
export const keys = ['code', 'name', 'encounterClass_code', 'action']
|
||||
|
||||
export const delKeyNames: KeyLabel[] = [
|
||||
{ key: 'code', label: 'Kode' },
|
||||
@@ -28,22 +26,7 @@ export const delKeyNames: KeyLabel[] = [
|
||||
export const funcParsed: RecStrFuncUnknown = {
|
||||
name: (rec: unknown): unknown => {
|
||||
const recX = rec as SmallDetailDto
|
||||
return `${recX.firstName} ${recX.lastName || ''}`.trim()
|
||||
},
|
||||
identity_number: (rec: unknown): unknown => {
|
||||
const recX = rec as SmallDetailDto
|
||||
if (recX.identity_number?.substring(0, 5) === 'BLANK') {
|
||||
return '(TANPA NIK)'
|
||||
}
|
||||
return recX.identity_number
|
||||
},
|
||||
inPatient_itemPrice: (rec: unknown): unknown => {
|
||||
const recX = rec as SmallDetailDto
|
||||
return Number(recX.inPatient_itemPrice.price).toLocaleString('id-ID')
|
||||
},
|
||||
outPatient_itemPrice: (rec: unknown): unknown => {
|
||||
const recX = rec as SmallDetailDto
|
||||
return Number(recX.outPatient_itemPrice.price).toLocaleString('id-ID')
|
||||
return `${recX.name}`.trim()
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
<script setup lang="ts">
|
||||
import type { PaginationMeta } from '~/components/pub/my-ui/pagination/pagination.type'
|
||||
// Components
|
||||
import PaginationView from '~/components/pub/my-ui/pagination/pagination-view.vue'
|
||||
|
||||
// Types
|
||||
import type { PaginationMeta } from '~/components/pub/my-ui/pagination/pagination.type'
|
||||
|
||||
// Configs
|
||||
import { cols, funcComponent, funcHtml, funcParsed, header, keys } from './list-cfg'
|
||||
|
||||
interface Props {
|
||||
@@ -22,10 +27,15 @@ function handlePageChange(page: number) {
|
||||
<template>
|
||||
<div class="space-y-4">
|
||||
<PubMyUiDataTable
|
||||
:rows="data" :cols="cols" :header="header" :keys="keys" :func-parsed="funcParsed"
|
||||
:func-html="funcHtml" :func-component="funcComponent" :skeleton-size="paginationMeta?.pageSize"
|
||||
:rows="data"
|
||||
:cols="cols"
|
||||
:header="header"
|
||||
:keys="keys"
|
||||
:func-parsed="funcParsed"
|
||||
:func-html="funcHtml"
|
||||
:func-component="funcComponent"
|
||||
:skeleton-size="paginationMeta?.pageSize"
|
||||
/>
|
||||
|
||||
<PaginationView :pagination-meta="paginationMeta" @page-change="handlePageChange" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
<script setup lang="ts">
|
||||
import type { PaginationMeta } from '~/components/pub/my-ui/pagination/pagination.type'
|
||||
// Components
|
||||
import PaginationView from '~/components/pub/my-ui/pagination/pagination-view.vue'
|
||||
|
||||
// Types
|
||||
import type { PaginationMeta } from '~/components/pub/my-ui/pagination/pagination.type'
|
||||
|
||||
// Configs
|
||||
import { cols, funcComponent, funcHtml, funcParsed, header, keys } from './list-cfg'
|
||||
|
||||
interface Props {
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
<script setup lang="ts">
|
||||
import type { PaginationMeta } from '~/components/pub/my-ui/pagination/pagination.type'
|
||||
// Components
|
||||
import PaginationView from '~/components/pub/my-ui/pagination/pagination-view.vue'
|
||||
|
||||
// Types
|
||||
import type { PaginationMeta } from '~/components/pub/my-ui/pagination/pagination.type'
|
||||
|
||||
// Configs
|
||||
import { cols, funcComponent, funcHtml, funcParsed, header, keys } from './list-cfg'
|
||||
|
||||
interface Props {
|
||||
|
||||
@@ -16,10 +16,9 @@ interface Props {
|
||||
values?: any
|
||||
isLoading?: boolean
|
||||
isReadonly?: boolean
|
||||
medicineGroups?: { value: string, label: string }[]
|
||||
medicineMethods?: { value: string, label: string }[]
|
||||
uoms?: { value: string, label: string }[]
|
||||
infras?: { value: number|null, label: string }[]
|
||||
medicineGroups?: { value: string; label: string }[]
|
||||
medicineMethods?: { value: string; label: string }[]
|
||||
uoms?: { value: string; label: string }[]
|
||||
}
|
||||
|
||||
const props = defineProps<Props>()
|
||||
@@ -38,7 +37,6 @@ const { defineField, errors, meta } = useForm({
|
||||
medicineGroup_code: '',
|
||||
medicineMethod_code: '',
|
||||
uom_code: '',
|
||||
infra_id: null,
|
||||
stock: 0,
|
||||
},
|
||||
})
|
||||
@@ -48,7 +46,6 @@ const [name, nameAttrs] = defineField('name')
|
||||
const [medicineGroup_code, medicineGroupAttrs] = defineField('medicineGroup_code')
|
||||
const [medicineMethod_code, medicineMethodAttrs] = defineField('medicineMethod_code')
|
||||
const [uom_code, uomAttrs] = defineField('uom_code')
|
||||
const [infra_id, infraAttrs] = defineField('infra_id')
|
||||
const [stock, stockAttrs] = defineField('stock')
|
||||
|
||||
if (props.values) {
|
||||
@@ -57,7 +54,6 @@ if (props.values) {
|
||||
if (props.values.medicineGroup_code !== undefined) medicineGroup_code.value = props.values.medicineGroup_code
|
||||
if (props.values.medicineMethod_code !== undefined) medicineMethod_code.value = props.values.medicineMethod_code
|
||||
if (props.values.uom_code !== undefined) uom_code.value = props.values.uom_code
|
||||
if (props.values.infra_id !== undefined) infra_id.value = props.values.infra_id
|
||||
if (props.values.stock !== undefined) stock.value = props.values.stock
|
||||
}
|
||||
|
||||
@@ -67,7 +63,6 @@ const resetForm = () => {
|
||||
medicineGroup_code.value = ''
|
||||
medicineMethod_code.value = ''
|
||||
uom_code.value = ''
|
||||
infra_id.value = null
|
||||
stock.value = 0
|
||||
}
|
||||
|
||||
@@ -78,7 +73,6 @@ function onSubmitForm() {
|
||||
medicineGroup_code: medicineGroup_code.value || '',
|
||||
medicineMethod_code: medicineMethod_code.value || '',
|
||||
uom_code: uom_code.value || '',
|
||||
infra_id: infra_id.value === '' ? null : infra_id.value,
|
||||
stock: stock.value || 0,
|
||||
}
|
||||
emit('submit', formData, resetForm)
|
||||
@@ -95,13 +89,25 @@ function onCancelForm() {
|
||||
<Cell>
|
||||
<Label height="">Kode</Label>
|
||||
<Field :errMessage="errors.code">
|
||||
<Input id="code" v-model="code" v-bind="codeAttrs" :disabled="isLoading || isReadonly" class="input input-bordered w-full" />
|
||||
<Input
|
||||
id="code"
|
||||
v-model="code"
|
||||
v-bind="codeAttrs"
|
||||
:disabled="isLoading || isReadonly"
|
||||
class="input input-bordered w-full"
|
||||
/>
|
||||
</Field>
|
||||
</Cell>
|
||||
<Cell>
|
||||
<Label height="compact">Nama</Label>
|
||||
<Field :errMessage="errors.name">
|
||||
<Input id="name" v-model="name" v-bind="nameAttrs" :disabled="isLoading || isReadonly" class="input input-bordered w-full" />
|
||||
<Input
|
||||
id="name"
|
||||
v-model="name"
|
||||
v-bind="nameAttrs"
|
||||
:disabled="isLoading || isReadonly"
|
||||
class="input input-bordered w-full"
|
||||
/>
|
||||
</Field>
|
||||
</Cell>
|
||||
<Cell>
|
||||
@@ -163,7 +169,14 @@ function onCancelForm() {
|
||||
<Cell>
|
||||
<Label height="compact">Stok</Label>
|
||||
<Field :errMessage="errors.stock">
|
||||
<Input id="stock" v-model="stock" type="number" v-bind="stockAttrs" :disabled="isLoading || isReadonly" class="input input-bordered w-full" />
|
||||
<Input
|
||||
id="stock"
|
||||
v-model="stock"
|
||||
type="number"
|
||||
v-bind="stockAttrs"
|
||||
:disabled="isLoading || isReadonly"
|
||||
class="input input-bordered w-full"
|
||||
/>
|
||||
</Field>
|
||||
</Cell>
|
||||
</Block>
|
||||
|
||||
@@ -35,13 +35,13 @@ export const delKeyNames: KeyLabel[] = [
|
||||
|
||||
export const funcParsed: RecStrFuncUnknown = {
|
||||
group: (rec: unknown): unknown => {
|
||||
return (rec as SmallDetailDto).medicineGroup_code || '-'
|
||||
return (rec as SmallDetailDto).medicineGroup?.name || '-'
|
||||
},
|
||||
method: (rec: unknown): unknown => {
|
||||
return (rec as SmallDetailDto).medicineMethod_code || '-'
|
||||
return (rec as SmallDetailDto).medicineMethod?.name || '-'
|
||||
},
|
||||
unit: (rec: unknown): unknown => {
|
||||
return (rec as SmallDetailDto).uom_code || '-'
|
||||
return (rec as SmallDetailDto).uom?.name || '-'
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
<script setup lang="ts">
|
||||
import type { PaginationMeta } from '~/components/pub/my-ui/pagination/pagination.type'
|
||||
// Components
|
||||
import PaginationView from '~/components/pub/my-ui/pagination/pagination-view.vue'
|
||||
|
||||
// Types
|
||||
import type { PaginationMeta } from '~/components/pub/my-ui/pagination/pagination.type'
|
||||
|
||||
// Configs
|
||||
import { cols, funcComponent, funcHtml, funcParsed, header, keys } from './list-cfg'
|
||||
|
||||
interface Props {
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
<script setup lang="ts">
|
||||
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 { Input } from '~/components/pub/ui/input'
|
||||
import { cn } from '~/lib/utils'
|
||||
|
||||
defineProps<{
|
||||
fieldName: string
|
||||
placeholder: string
|
||||
label: string
|
||||
errors?: FormErrors
|
||||
class?: string
|
||||
numericOnly?: boolean
|
||||
maxLength?: number
|
||||
isRequired?: boolean
|
||||
}>()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<FieldGroup>
|
||||
<Label
|
||||
:label-for="fieldName"
|
||||
:is-required="isRequired"
|
||||
>
|
||||
{{ label }}
|
||||
</Label>
|
||||
<Field
|
||||
:id="fieldName"
|
||||
:errors="errors"
|
||||
>
|
||||
<FormField
|
||||
v-slot="{ componentField }"
|
||||
:name="fieldName"
|
||||
>
|
||||
<FormItem>
|
||||
<FormControl>
|
||||
<Input
|
||||
v-bind="componentField"
|
||||
type="file"
|
||||
:placeholder="placeholder"
|
||||
:class="cn('focus:border-primary focus:ring-2 focus:ring-primary focus:ring-offset-0')"
|
||||
@change="
|
||||
(e: Event) => {
|
||||
const file = (e.target as HTMLInputElement).files?.[0] || null
|
||||
componentField.onChange(file)
|
||||
}
|
||||
"
|
||||
@blur.prevent
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
</FormField>
|
||||
</Field>
|
||||
</FieldGroup>
|
||||
</template>
|
||||
@@ -0,0 +1,102 @@
|
||||
<script setup lang="ts">
|
||||
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 { Input } from '~/components/pub/ui/input'
|
||||
import { cn } from '~/lib/utils'
|
||||
|
||||
defineProps<{
|
||||
fieldNameAlias: string
|
||||
fieldNameInput: string
|
||||
placeholder: string
|
||||
labelForAlias: string
|
||||
labelForInput: string
|
||||
errors?: FormErrors
|
||||
class?: string
|
||||
selectClass?: string
|
||||
fieldGroupClass?: string
|
||||
labelClass?: string
|
||||
maxLength?: number
|
||||
isRequired?: boolean
|
||||
}>()
|
||||
|
||||
const aliasOptions = [
|
||||
{ label: 'An', value: 'an' },
|
||||
{ label: 'By.Ny', value: 'byny' },
|
||||
{ label: 'Nn', value: 'nn' },
|
||||
{ label: 'Ny', value: 'ny' },
|
||||
{ label: 'Tn', value: 'tn' },
|
||||
]
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<FieldGroup>
|
||||
<Label
|
||||
:label-for="fieldNameAlias"
|
||||
:is-required="isRequired"
|
||||
>
|
||||
{{ labelForAlias }}
|
||||
</Label>
|
||||
|
||||
<Field
|
||||
:id="fieldNameAlias"
|
||||
:errors="errors"
|
||||
>
|
||||
<FormField
|
||||
v-slot="{ componentField }"
|
||||
:name="fieldNameAlias"
|
||||
>
|
||||
<FormItem>
|
||||
<FormControl>
|
||||
<Select
|
||||
:id="fieldNameAlias"
|
||||
:preserve-order="false"
|
||||
v-bind="componentField"
|
||||
:auto-width="true"
|
||||
:items="aliasOptions"
|
||||
:class="
|
||||
cn(
|
||||
'text-sm transition-all duration-200 focus:outline-none focus:ring-1 focus:ring-black focus:ring-offset-0',
|
||||
selectClass,
|
||||
)
|
||||
"
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
</FormField>
|
||||
</Field>
|
||||
</FieldGroup>
|
||||
<FieldGroup>
|
||||
<Label
|
||||
:label-for="fieldNameInput"
|
||||
:is-required="isRequired"
|
||||
>
|
||||
{{ labelForInput }}
|
||||
</Label>
|
||||
|
||||
<Field
|
||||
:id="fieldNameInput"
|
||||
:errors="errors"
|
||||
>
|
||||
<FormField
|
||||
v-slot="{ componentField }"
|
||||
:name="fieldNameInput"
|
||||
>
|
||||
<FormItem>
|
||||
<FormControl>
|
||||
<Input
|
||||
v-bind="componentField"
|
||||
type="text"
|
||||
:placeholder="placeholder"
|
||||
:class="cn('focus:border-primary focus:ring-2 focus:ring-primary focus:ring-offset-0')"
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
</FormField>
|
||||
</Field>
|
||||
</FieldGroup>
|
||||
</template>
|
||||
@@ -0,0 +1,94 @@
|
||||
<script setup lang="ts">
|
||||
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 { Label as RadioLabel } from '~/components/pub/ui/label'
|
||||
import { RadioGroup, RadioGroupItem } from '~/components/pub/ui/radio-group'
|
||||
import { cn } from '~/lib/utils'
|
||||
|
||||
const props = defineProps<{
|
||||
fieldName?: string
|
||||
label?: string
|
||||
errors?: FormErrors
|
||||
class?: string
|
||||
radioGroupClass?: string
|
||||
radioItemClass?: string
|
||||
labelClass?: string
|
||||
isRequired?: boolean
|
||||
}>()
|
||||
|
||||
const {
|
||||
fieldName = 'gender',
|
||||
label = 'Gender',
|
||||
errors,
|
||||
class: containerClass,
|
||||
radioGroupClass,
|
||||
radioItemClass,
|
||||
labelClass,
|
||||
} = props
|
||||
|
||||
const genderOptions = [
|
||||
{ label: 'Ya', value: 'YA' },
|
||||
{ label: 'Tidak', value: 'TIDAK' },
|
||||
]
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<FieldGroup :class="cn('radio-group-field', containerClass)">
|
||||
<Label
|
||||
:label-for="fieldName"
|
||||
:is-required="isRequired"
|
||||
height="compact"
|
||||
>
|
||||
{{ label }}
|
||||
</Label>
|
||||
<Field
|
||||
:id="fieldName"
|
||||
:errors="errors"
|
||||
>
|
||||
<FormField
|
||||
v-slot="{ componentField }"
|
||||
:name="fieldName"
|
||||
>
|
||||
<FormItem>
|
||||
<FormControl>
|
||||
<RadioGroup
|
||||
v-bind="componentField"
|
||||
:class="cn('flex flex-row flex-wrap gap-4 sm:gap-6', radioGroupClass)"
|
||||
>
|
||||
<div
|
||||
v-for="(option, index) in genderOptions"
|
||||
:key="option.value"
|
||||
:class="cn('flex min-w-fit items-center space-x-2', radioItemClass)"
|
||||
>
|
||||
<RadioGroupItem
|
||||
:id="`${fieldName}-${index}`"
|
||||
:value="option.value"
|
||||
:class="
|
||||
cn(
|
||||
'relative h-4 w-4 rounded-full border-2 border-muted-foreground before:absolute before:inset-1 before:rounded-full before:bg-primary before:opacity-0 before:transition-opacity data-[state=checked]:border-primary data-[state=checked]:bg-white data-[state=checked]:before:opacity-100 sm:h-5 sm:w-5',
|
||||
containerClass,
|
||||
)
|
||||
"
|
||||
/>
|
||||
<RadioLabel
|
||||
:for="`${fieldName}-${index}`"
|
||||
:class="
|
||||
cn(
|
||||
'cursor-pointer select-none text-xs font-medium leading-none transition-colors hover:text-primary peer-disabled:cursor-not-allowed peer-disabled:opacity-70 sm:text-sm',
|
||||
labelClass,
|
||||
)
|
||||
"
|
||||
>
|
||||
{{ option.label }}
|
||||
</RadioLabel>
|
||||
</div>
|
||||
</RadioGroup>
|
||||
</FormControl>
|
||||
<FormMessage class="ml-0 mt-1" />
|
||||
</FormItem>
|
||||
</FormField>
|
||||
</Field>
|
||||
</FieldGroup>
|
||||
</template>
|
||||
@@ -0,0 +1,94 @@
|
||||
<script setup lang="ts">
|
||||
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 { Label as RadioLabel } from '~/components/pub/ui/label'
|
||||
import { RadioGroup, RadioGroupItem } from '~/components/pub/ui/radio-group'
|
||||
import { cn } from '~/lib/utils'
|
||||
|
||||
const props = defineProps<{
|
||||
fieldName?: string
|
||||
label?: string
|
||||
errors?: FormErrors
|
||||
class?: string
|
||||
radioGroupClass?: string
|
||||
radioItemClass?: string
|
||||
labelClass?: string
|
||||
isRequired?: boolean
|
||||
}>()
|
||||
|
||||
const {
|
||||
fieldName = 'disability',
|
||||
label = 'Disabilitas',
|
||||
errors,
|
||||
class: containerClass,
|
||||
radioGroupClass,
|
||||
radioItemClass,
|
||||
labelClass,
|
||||
} = props
|
||||
|
||||
const dissabilityOptions = [
|
||||
{ label: 'Ya', value: 'YA' },
|
||||
{ label: 'Tidak', value: 'TIDAK' },
|
||||
]
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<FieldGroup :class="cn('radio-group-field', containerClass)">
|
||||
<Label
|
||||
:label-for="fieldName"
|
||||
:is-required="isRequired"
|
||||
height="compact"
|
||||
>
|
||||
{{ label }}
|
||||
</Label>
|
||||
<Field
|
||||
:id="fieldName"
|
||||
:errors="errors"
|
||||
>
|
||||
<FormField
|
||||
v-slot="{ componentField }"
|
||||
:name="fieldName"
|
||||
>
|
||||
<FormItem>
|
||||
<FormControl>
|
||||
<RadioGroup
|
||||
v-bind="componentField"
|
||||
:class="cn('flex flex-row flex-wrap gap-4 sm:gap-6', radioGroupClass)"
|
||||
>
|
||||
<div
|
||||
v-for="(option, index) in dissabilityOptions"
|
||||
:key="option.value"
|
||||
:class="cn('flex min-w-fit items-center space-x-2', radioItemClass)"
|
||||
>
|
||||
<RadioGroupItem
|
||||
:id="`${fieldName}-${index}`"
|
||||
:value="option.value"
|
||||
:class="
|
||||
cn(
|
||||
'relative h-4 w-4 rounded-full border-2 border-muted-foreground before:absolute before:inset-1 before:rounded-full before:bg-primary before:opacity-0 before:transition-opacity data-[state=checked]:border-primary data-[state=checked]:bg-white data-[state=checked]:before:opacity-100 sm:h-5 sm:w-5',
|
||||
containerClass,
|
||||
)
|
||||
"
|
||||
/>
|
||||
<RadioLabel
|
||||
:for="`${fieldName}-${index}`"
|
||||
:class="
|
||||
cn(
|
||||
'cursor-pointer select-none text-xs font-medium leading-none transition-colors hover:text-primary peer-disabled:cursor-not-allowed peer-disabled:opacity-70 sm:text-sm',
|
||||
labelClass,
|
||||
)
|
||||
"
|
||||
>
|
||||
{{ option.label }}
|
||||
</RadioLabel>
|
||||
</div>
|
||||
</RadioGroup>
|
||||
</FormControl>
|
||||
<FormMessage class="ml-0 mt-1" />
|
||||
</FormItem>
|
||||
</FormField>
|
||||
</Field>
|
||||
</FieldGroup>
|
||||
</template>
|
||||
@@ -0,0 +1,92 @@
|
||||
<script setup lang="ts">
|
||||
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 { Label as RadioLabel } from '~/components/pub/ui/label'
|
||||
import { RadioGroup, RadioGroupItem } from '~/components/pub/ui/radio-group'
|
||||
import { genderCodes } from '~/lib/constants'
|
||||
import { cn, mapToComboboxOptList } from '~/lib/utils'
|
||||
|
||||
const props = defineProps<{
|
||||
fieldName?: string
|
||||
label?: string
|
||||
errors?: FormErrors
|
||||
class?: string
|
||||
radioGroupClass?: string
|
||||
radioItemClass?: string
|
||||
labelClass?: string
|
||||
isRequired?: boolean
|
||||
}>()
|
||||
|
||||
const {
|
||||
fieldName = 'gender',
|
||||
label = 'Gender',
|
||||
errors,
|
||||
class: containerClass,
|
||||
radioGroupClass,
|
||||
radioItemClass,
|
||||
labelClass,
|
||||
} = props
|
||||
|
||||
const genderOptions = mapToComboboxOptList(genderCodes)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<FieldGroup :class="cn('radio-group-field', containerClass)">
|
||||
<Label
|
||||
:label-for="fieldName"
|
||||
:is-required="isRequired"
|
||||
height="compact"
|
||||
>
|
||||
{{ label }}
|
||||
</Label>
|
||||
<Field
|
||||
:id="fieldName"
|
||||
:errors="errors"
|
||||
>
|
||||
<FormField
|
||||
v-slot="{ componentField }"
|
||||
:name="fieldName"
|
||||
>
|
||||
<FormItem>
|
||||
<FormControl>
|
||||
<RadioGroup
|
||||
v-bind="componentField"
|
||||
:class="cn('flex flex-row flex-wrap gap-4 sm:gap-6', radioGroupClass)"
|
||||
>
|
||||
<div
|
||||
v-for="(option, index) in genderOptions"
|
||||
:key="option.value"
|
||||
:class="cn('flex min-w-fit items-center space-x-2', radioItemClass)"
|
||||
>
|
||||
<RadioGroupItem
|
||||
:id="`${fieldName}-${index}`"
|
||||
:value="option.value"
|
||||
:class="
|
||||
cn(
|
||||
'relative h-4 w-4 rounded-full border-2 border-muted-foreground before:absolute before:inset-1 before:rounded-full before:bg-primary before:opacity-0 before:transition-opacity data-[state=checked]:border-primary data-[state=checked]:bg-white data-[state=checked]:before:opacity-100 sm:h-5 sm:w-5',
|
||||
containerClass,
|
||||
)
|
||||
"
|
||||
/>
|
||||
<RadioLabel
|
||||
:for="`${fieldName}-${index}`"
|
||||
:class="
|
||||
cn(
|
||||
'cursor-pointer select-none text-xs font-medium leading-none transition-colors hover:text-primary peer-disabled:cursor-not-allowed peer-disabled:opacity-70 sm:text-sm',
|
||||
labelClass,
|
||||
)
|
||||
"
|
||||
>
|
||||
{{ option.label }}
|
||||
</RadioLabel>
|
||||
</div>
|
||||
</RadioGroup>
|
||||
</FormControl>
|
||||
<FormMessage class="ml-0 mt-1" />
|
||||
</FormItem>
|
||||
</FormField>
|
||||
</Field>
|
||||
</FieldGroup>
|
||||
</template>
|
||||
@@ -0,0 +1,94 @@
|
||||
<script setup lang="ts">
|
||||
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 { Label as RadioLabel } from '~/components/pub/ui/label'
|
||||
import { RadioGroup, RadioGroupItem } from '~/components/pub/ui/radio-group'
|
||||
import { cn } from '~/lib/utils'
|
||||
|
||||
const props = defineProps<{
|
||||
fieldName?: string
|
||||
label?: string
|
||||
errors?: FormErrors
|
||||
class?: string
|
||||
radioGroupClass?: string
|
||||
radioItemClass?: string
|
||||
labelClass?: string
|
||||
isRequired?: boolean
|
||||
}>()
|
||||
|
||||
const {
|
||||
fieldName = 'nationality',
|
||||
label = 'Kebangsaan',
|
||||
errors,
|
||||
class: containerClass,
|
||||
radioGroupClass,
|
||||
radioItemClass,
|
||||
labelClass,
|
||||
} = props
|
||||
|
||||
const nationalityOptions = [
|
||||
{ label: 'WNI', value: 'WNI' },
|
||||
{ label: 'WNA', value: 'WNA' },
|
||||
]
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<FieldGroup :class="cn('radio-group-field', containerClass)">
|
||||
<Label
|
||||
:label-for="fieldName"
|
||||
:is-required="isRequired"
|
||||
height="compact"
|
||||
>
|
||||
{{ label }}
|
||||
</Label>
|
||||
<Field
|
||||
:id="fieldName"
|
||||
:errors="errors"
|
||||
>
|
||||
<FormField
|
||||
v-slot="{ componentField }"
|
||||
:name="fieldName"
|
||||
>
|
||||
<FormItem>
|
||||
<FormControl>
|
||||
<RadioGroup
|
||||
v-bind="componentField"
|
||||
:class="cn('flex flex-row flex-wrap gap-4 sm:gap-6', radioGroupClass)"
|
||||
>
|
||||
<div
|
||||
v-for="(option, index) in nationalityOptions"
|
||||
:key="option.value"
|
||||
:class="cn('flex min-w-fit items-center space-x-2', radioItemClass)"
|
||||
>
|
||||
<RadioGroupItem
|
||||
:id="`${fieldName}-${index}`"
|
||||
:value="option.value"
|
||||
:class="
|
||||
cn(
|
||||
'relative h-4 w-4 rounded-full border-2 border-muted-foreground before:absolute before:inset-1 before:rounded-full before:bg-primary before:opacity-0 before:transition-opacity data-[state=checked]:border-primary data-[state=checked]:bg-white data-[state=checked]:before:opacity-100 sm:h-5 sm:w-5',
|
||||
containerClass,
|
||||
)
|
||||
"
|
||||
/>
|
||||
<RadioLabel
|
||||
:for="`${fieldName}-${index}`"
|
||||
:class="
|
||||
cn(
|
||||
'cursor-pointer select-none text-xs font-medium leading-none transition-colors hover:text-primary peer-disabled:cursor-not-allowed peer-disabled:opacity-70 sm:text-sm',
|
||||
labelClass,
|
||||
)
|
||||
"
|
||||
>
|
||||
{{ option.label }}
|
||||
</RadioLabel>
|
||||
</div>
|
||||
</RadioGroup>
|
||||
</FormControl>
|
||||
<FormMessage class="ml-0 mt-1" />
|
||||
</FormItem>
|
||||
</FormField>
|
||||
</Field>
|
||||
</FieldGroup>
|
||||
</template>
|
||||
@@ -0,0 +1,81 @@
|
||||
<script setup lang="ts">
|
||||
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'
|
||||
|
||||
const props = defineProps<{
|
||||
fieldName?: string
|
||||
label?: string
|
||||
isDisabled?: boolean
|
||||
isRequired?: boolean
|
||||
placeholder?: string
|
||||
errors?: FormErrors
|
||||
class?: string
|
||||
selectClass?: string
|
||||
fieldGroupClass?: string
|
||||
}>()
|
||||
|
||||
const {
|
||||
fieldName = 'disabilityType',
|
||||
placeholder = 'Pilih jenis disabilitas',
|
||||
errors,
|
||||
class: containerClass,
|
||||
selectClass,
|
||||
fieldGroupClass,
|
||||
} = props
|
||||
|
||||
const disabilityOptions = [
|
||||
{ label: 'Tuna Daksa', value: 'daksa' },
|
||||
{ label: 'Tuna Netra', value: 'netra' },
|
||||
{ label: 'Tuna Rungu', value: 'rungu' },
|
||||
{ label: 'Tuna Wicara', value: 'wicara' },
|
||||
{ label: 'Tuna Rungu-Wicara', value: 'rungu_wicara' },
|
||||
{ label: 'Tuna Grahita', value: 'grahita' },
|
||||
{ label: 'Tuna Laras', value: 'laras' },
|
||||
{ label: 'Lainnya', value: 'other', priority: -100 },
|
||||
]
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<FieldGroup :class="cn('select-field-group', fieldGroupClass, containerClass)">
|
||||
<Label
|
||||
label-for="fieldName"
|
||||
:is-required="isRequired"
|
||||
>
|
||||
{{ label }}
|
||||
</Label>
|
||||
<Field
|
||||
:id="fieldName"
|
||||
:errors="errors"
|
||||
:class="cn('select-field-wrapper')"
|
||||
>
|
||||
<FormField
|
||||
v-slot="{ componentField }"
|
||||
:name="fieldName"
|
||||
>
|
||||
<FormItem>
|
||||
<FormControl>
|
||||
<Select
|
||||
:id="fieldName"
|
||||
:is-disabled="isDisabled"
|
||||
v-bind="componentField"
|
||||
:items="disabilityOptions"
|
||||
:placeholder="placeholder"
|
||||
:preserve-order="false"
|
||||
:class="
|
||||
cn(
|
||||
'text-sm transition-all duration-200 focus:outline-none focus:ring-1 focus:ring-black focus:ring-offset-0',
|
||||
selectClass,
|
||||
)
|
||||
"
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
</FormField>
|
||||
</Field>
|
||||
</FieldGroup>
|
||||
</template>
|
||||
@@ -0,0 +1,142 @@
|
||||
<script setup lang="ts">
|
||||
import type { FormErrors } from '~/types/error'
|
||||
import { differenceInDays, differenceInMonths, differenceInYears, parseISO } from 'date-fns'
|
||||
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 { Input } from '~/components/pub/ui/input'
|
||||
import { cn } from '~/lib/utils'
|
||||
|
||||
const props = defineProps<{
|
||||
fieldName?: string
|
||||
label?: string
|
||||
placeholder?: string
|
||||
errors?: FormErrors
|
||||
class?: string
|
||||
selectClass?: string
|
||||
fieldGroupClass?: string
|
||||
labelClass?: string
|
||||
isRequired?: boolean
|
||||
}>()
|
||||
|
||||
const {
|
||||
fieldName = 'birthDate',
|
||||
label = 'Tanggal Lahir',
|
||||
placeholder = 'Pilih tanggal lahir',
|
||||
errors,
|
||||
class: containerClass,
|
||||
fieldGroupClass,
|
||||
labelClass,
|
||||
} = props
|
||||
|
||||
// Reactive variables for age calculation
|
||||
const patientAge = ref<string>('Masukkan tanggal lahir')
|
||||
|
||||
// Function to calculate age with years, months, and days
|
||||
function calculateAge(birthDate: string | Date | undefined): string {
|
||||
if (!birthDate) {
|
||||
return 'Masukkan tanggal lahir'
|
||||
}
|
||||
|
||||
try {
|
||||
let dateObj: Date
|
||||
|
||||
if (typeof birthDate === 'string') {
|
||||
dateObj = parseISO(birthDate)
|
||||
} else {
|
||||
dateObj = birthDate
|
||||
}
|
||||
|
||||
const today = new Date()
|
||||
|
||||
// Calculate years, months, and days
|
||||
const totalYears = differenceInYears(today, dateObj)
|
||||
|
||||
// Calculate remaining months after years
|
||||
const yearsPassed = new Date(dateObj)
|
||||
yearsPassed.setFullYear(yearsPassed.getFullYear() + totalYears)
|
||||
const remainingMonths = differenceInMonths(today, yearsPassed)
|
||||
|
||||
// Calculate remaining days after years and months
|
||||
const monthsPassed = new Date(yearsPassed)
|
||||
monthsPassed.setMonth(monthsPassed.getMonth() + remainingMonths)
|
||||
const remainingDays = differenceInDays(today, monthsPassed)
|
||||
|
||||
// Format the result
|
||||
const parts = []
|
||||
if (totalYears > 0) parts.push(`${totalYears} Tahun`)
|
||||
if (remainingMonths > 0) parts.push(`${remainingMonths} Bulan`)
|
||||
if (remainingDays > 0) parts.push(`${remainingDays} Hari`)
|
||||
|
||||
return parts.length > 0 ? parts.join(' ') : '0 Hari'
|
||||
} catch {
|
||||
return 'Masukkan tanggal lahir'
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<FieldGroup :class="cn('select-field-group', fieldGroupClass, containerClass)">
|
||||
<Label
|
||||
:label-for="fieldName"
|
||||
:class="cn('select-field-label', labelClass)"
|
||||
:is-required="isRequired"
|
||||
>
|
||||
{{ label }}
|
||||
</Label>
|
||||
<Field
|
||||
:id="fieldName"
|
||||
:errors="errors"
|
||||
:class="cn('select-field-wrapper')"
|
||||
>
|
||||
<FormField
|
||||
v-slot="{ componentField }"
|
||||
:name="fieldName"
|
||||
>
|
||||
<FormItem>
|
||||
<FormControl>
|
||||
<Input
|
||||
id="birthDate"
|
||||
type="date"
|
||||
min="1900-01-01"
|
||||
:max="new Date().toISOString().split('T')[0]"
|
||||
v-bind="componentField"
|
||||
:placeholder="placeholder"
|
||||
@update:model-value="
|
||||
(value: string | number) => {
|
||||
const dateStr = typeof value === 'number' ? String(value) : value
|
||||
patientAge = calculateAge(dateStr)
|
||||
}
|
||||
"
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
</FormField>
|
||||
</Field>
|
||||
</FieldGroup>
|
||||
<FieldGroup>
|
||||
<Label label-for="patientAge">Usia</Label>
|
||||
|
||||
<Field id="patientAge">
|
||||
<FormField name="patientAge">
|
||||
<FormItem>
|
||||
<FormControl>
|
||||
<Input
|
||||
:value="patientAge"
|
||||
disabled
|
||||
readonly
|
||||
placeholder="Masukkan tanggal lahir"
|
||||
:class="
|
||||
cn(
|
||||
'cursor-not-allowed bg-gray-50 focus:border-primary focus:ring-2 focus:ring-primary focus:ring-offset-0',
|
||||
)
|
||||
"
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
</FormField>
|
||||
</Field>
|
||||
</FieldGroup>
|
||||
</template>
|
||||
@@ -0,0 +1,88 @@
|
||||
<script setup lang="ts">
|
||||
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 { educationCodes } from '~/lib/constants'
|
||||
import { cn, mapToComboboxOptList } from '~/lib/utils'
|
||||
|
||||
const props = defineProps<{
|
||||
fieldName?: string
|
||||
label?: string
|
||||
placeholder?: string
|
||||
errors?: FormErrors
|
||||
class?: string
|
||||
selectClass?: string
|
||||
fieldGroupClass?: string
|
||||
labelClass?: string
|
||||
isRequired?: boolean
|
||||
isDisabled?: boolean
|
||||
}>()
|
||||
|
||||
const {
|
||||
fieldName = 'education',
|
||||
label = 'Pendidikan',
|
||||
placeholder = 'Pilih pendidikan terakhir',
|
||||
errors,
|
||||
class: containerClass,
|
||||
selectClass,
|
||||
fieldGroupClass,
|
||||
labelClass,
|
||||
} = props
|
||||
|
||||
const extendOptions = [
|
||||
{ label: 'Tidak diketahui', value: 'unknown', priority: 1 },
|
||||
{ label: 'Lainnya', value: 'other', priority: -1 },
|
||||
]
|
||||
|
||||
const educationOptions = [
|
||||
...mapToComboboxOptList(educationCodes).map(({ label, value }) => ({
|
||||
label,
|
||||
value,
|
||||
})),
|
||||
...extendOptions,
|
||||
]
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<FieldGroup :class="cn('select-field-group', fieldGroupClass, containerClass)">
|
||||
<Label
|
||||
:label-for="fieldName"
|
||||
:class="cn('select-field-label', labelClass)"
|
||||
:is-required="isRequired && !isDisabled"
|
||||
>
|
||||
{{ label }}
|
||||
</Label>
|
||||
<Field
|
||||
:id="fieldName"
|
||||
:errors="errors"
|
||||
:class="cn('select-field-wrapper')"
|
||||
>
|
||||
<FormField
|
||||
v-slot="{ componentField }"
|
||||
:name="fieldName"
|
||||
>
|
||||
<FormItem>
|
||||
<FormControl>
|
||||
<Select
|
||||
:id="fieldName"
|
||||
v-bind="componentField"
|
||||
:is-disabled="isDisabled"
|
||||
:items="educationOptions"
|
||||
:placeholder="placeholder"
|
||||
:preserve-order="true"
|
||||
:class="
|
||||
cn(
|
||||
'text-sm transition-all duration-200 focus:outline-none focus:ring-1 focus:ring-black focus:ring-offset-0',
|
||||
selectClass,
|
||||
)
|
||||
"
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
</FormField>
|
||||
</Field>
|
||||
</FieldGroup>
|
||||
</template>
|
||||
@@ -0,0 +1,86 @@
|
||||
<script setup lang="ts">
|
||||
import type { FormErrors } from '~/types/error'
|
||||
import Combobox from '~/components/pub/my-ui/form/combobox.vue'
|
||||
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 { cn } from '~/lib/utils'
|
||||
|
||||
const props = defineProps<{
|
||||
fieldName?: string
|
||||
label?: string
|
||||
placeholder?: string
|
||||
errors?: FormErrors
|
||||
class?: string
|
||||
selectClass?: string
|
||||
fieldGroupClass?: string
|
||||
labelClass?: string
|
||||
isRequired?: boolean
|
||||
isDisabled?: boolean
|
||||
}>()
|
||||
|
||||
const {
|
||||
fieldName = 'ethnicity',
|
||||
label = 'Suku',
|
||||
placeholder = 'Pilih suku',
|
||||
errors,
|
||||
class: containerClass,
|
||||
fieldGroupClass,
|
||||
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 },
|
||||
]
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<FieldGroup :class="cn('select-field-group', fieldGroupClass, containerClass)">
|
||||
<Label
|
||||
:label-for="fieldName"
|
||||
:class="cn('select-field-label', labelClass)"
|
||||
:is-required="isRequired && !isDisabled"
|
||||
>
|
||||
{{ label }}
|
||||
</Label>
|
||||
<Field
|
||||
:id="fieldName"
|
||||
:errors="errors"
|
||||
:class="cn('select-field-wrapper')"
|
||||
>
|
||||
<FormField
|
||||
v-slot="{ componentField }"
|
||||
:name="fieldName"
|
||||
>
|
||||
<FormItem>
|
||||
<FormControl>
|
||||
<Combobox
|
||||
:id="fieldName"
|
||||
v-bind="componentField"
|
||||
:items="ethnicOptions"
|
||||
:placeholder="placeholder"
|
||||
:is-disabled="isDisabled"
|
||||
search-placeholder="Cari..."
|
||||
empty-message="Suku tidak ditemukan"
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
</FormField>
|
||||
</Field>
|
||||
</FieldGroup>
|
||||
</template>
|
||||
@@ -0,0 +1,155 @@
|
||||
<script setup lang="ts">
|
||||
import type { FormErrors } from '~/types/error'
|
||||
import Combobox from '~/components/pub/my-ui/form/combobox.vue'
|
||||
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 { cn } from '~/lib/utils'
|
||||
|
||||
const props = defineProps<{
|
||||
fieldName?: string
|
||||
label?: string
|
||||
placeholder?: string
|
||||
errors?: FormErrors
|
||||
class?: string
|
||||
selectClass?: string
|
||||
fieldGroupClass?: string
|
||||
labelClass?: string
|
||||
isRequired?: boolean
|
||||
}>()
|
||||
|
||||
const {
|
||||
fieldName = 'job',
|
||||
label = 'Pekerjaan',
|
||||
placeholder = 'Pilih pekerjaan',
|
||||
errors,
|
||||
class: containerClass,
|
||||
fieldGroupClass,
|
||||
labelClass,
|
||||
} = props
|
||||
|
||||
const jobOptions = [
|
||||
{ label: 'Tidak diketahui', value: 'unknown', priority: 100 },
|
||||
{ label: 'Belum/Tidak Bekerja', value: 'tidak_bekerja', priority: 99 },
|
||||
{ label: 'Mengurus Rumah Tangga', value: 'mengurus_rumah_tangga' },
|
||||
{ label: 'Pelajar/Mahasiswa', value: 'pelajar' },
|
||||
{ label: 'Pensiunan', value: 'pensiunan' },
|
||||
{ label: 'Pegawai Negeri Sipil', value: 'pns' },
|
||||
{ label: 'Tentara Nasional Indonesia', value: 'tni' },
|
||||
{ label: 'Kepolisian RI', value: 'polri' },
|
||||
{ label: 'Perdagangan', value: 'perdagangan' },
|
||||
{ label: 'Petani/Pekebun', value: 'petani' },
|
||||
{ label: 'Peternak', value: 'peternak' },
|
||||
{ label: 'Nelayan/Perikanan', value: 'nelayan' },
|
||||
{ label: 'Industri', value: 'industri' },
|
||||
{ label: 'Konstruksi', value: 'konstruksi' },
|
||||
{ label: 'Transportasi', value: 'transportasi' },
|
||||
{ label: 'Karyawan Swasta', value: 'karyawan_swasta' },
|
||||
{ label: 'Karyawan BUMN', value: 'karyawan_bumn' },
|
||||
{ label: 'Karyawan BUMD', value: 'karyawan_bumd' },
|
||||
{ label: 'Karyawan Honorer', value: 'karyawan_honorer' },
|
||||
{ label: 'Buruh Harian Lepas', value: 'buruh_harian' },
|
||||
{ label: 'Buruh Tani/Perkebunan', value: 'buruh_tani' },
|
||||
{ label: 'Buruh Nelayan/Perikanan', value: 'buruh_nelayan' },
|
||||
{ label: 'Buruh Peternakan', value: 'buruh_peternakan' },
|
||||
{ label: 'Pembantu Rumah Tangga', value: 'pembantu_rumah_tangga' },
|
||||
{ label: 'Tukang Cukur', value: 'tukang_cukur' },
|
||||
{ label: 'Tukang Listrik', value: 'tukang_listrik' },
|
||||
{ label: 'Tukang Batu', value: 'tukang_batu' },
|
||||
{ label: 'Tukang Kayu', value: 'tukang_kayu' },
|
||||
{ label: 'Tukang Sol Sepatu', value: 'tukang_sol_sepatu' },
|
||||
{ label: 'Tukang Jahit', value: 'tukang_jahit' },
|
||||
{ label: 'Tukang Gigi', value: 'tukang_gigi' },
|
||||
{ label: 'Penata Rias', value: 'penata_rias' },
|
||||
{ label: 'Penata Busana', value: 'penata_busana' },
|
||||
{ label: 'Penata Rambut', value: 'penata_rambut' },
|
||||
{ label: 'Mekanik', value: 'mekanik' },
|
||||
{ label: 'Seniman', value: 'seniman' },
|
||||
{ label: 'Tabib', value: 'tabib' },
|
||||
{ label: 'Paraji', value: 'paraji' },
|
||||
{ label: 'Perancang Busana', value: 'perancang_busana' },
|
||||
{ label: 'Penterjemah', value: 'penterjemah' },
|
||||
{ label: 'Imam Mesjid', value: 'imam_mesjid' },
|
||||
{ label: 'Pendeta', value: 'pendeta' },
|
||||
{ label: 'Pastor', value: 'pastor' },
|
||||
{ label: 'Wartawan', value: 'wartawan' },
|
||||
{ label: 'Ustadz/Mubaligh', value: 'ustadz' },
|
||||
{ label: 'Juru Masak', value: 'juru_masak' },
|
||||
{ label: 'Promotor Acara', value: 'promotor' },
|
||||
{ label: 'Anggota DPR-RI', value: 'dpr_ri' },
|
||||
{ label: 'Anggota DPD', value: 'dpd' },
|
||||
{ label: 'Anggota BPK', value: 'bpk' },
|
||||
{ label: 'Presiden', value: 'presiden' },
|
||||
{ label: 'Wakil Presiden', value: 'wakil_presiden' },
|
||||
{ label: 'Anggota Mahkamah Konstitusi', value: 'mk' },
|
||||
{ label: 'Anggota Kabinet/Kementrian', value: 'kabinet' },
|
||||
{ label: 'Duta Besar', value: 'dubes' },
|
||||
{ label: 'Gubernur', value: 'gubernur' },
|
||||
{ label: 'Wakil Gubernur', value: 'wakil_gubernur' },
|
||||
{ label: 'Bupati', value: 'bupati' },
|
||||
{ label: 'Wakil Bupati', value: 'wakil_bupati' },
|
||||
{ label: 'Walikota', value: 'walikota' },
|
||||
{ label: 'Wakil Walikota', value: 'wakil_walikota' },
|
||||
{ label: 'Anggota DPRD Provinsi', value: 'dprd_provinsi' },
|
||||
{ label: 'Anggota DPRD Kabupaten/Kota', value: 'dprd_kabkota' },
|
||||
{ label: 'Dosen', value: 'dosen' },
|
||||
{ label: 'Guru', value: 'guru' },
|
||||
{ label: 'Pilot', value: 'pilot' },
|
||||
{ label: 'Pengacara', value: 'pengacara' },
|
||||
{ label: 'Arsitek', value: 'arsitek' },
|
||||
{ label: 'Akuntan', value: 'akuntan' },
|
||||
{ label: 'Konsultan', value: 'konsultan' },
|
||||
{ label: 'Dokter', value: 'dokter' },
|
||||
{ label: 'Bidan', value: 'bidan' },
|
||||
{ label: 'Apoteker', value: 'apoteker' },
|
||||
{ label: 'Psikiater/Psikolog', value: 'psikolog' },
|
||||
{ label: 'Penyiar Televisi', value: 'penyiar_tv' },
|
||||
{ label: 'Penyiar Radio', value: 'penyiar_radio' },
|
||||
{ label: 'Pelaut', value: 'pelaut' },
|
||||
{ label: 'Sopir', value: 'sopir' },
|
||||
{ label: 'Pialang', value: 'pialang' },
|
||||
{ label: 'Paranormal', value: 'paranormal' },
|
||||
{ label: 'Pedagang', value: 'pedagang' },
|
||||
{ label: 'Perangkat Desa', value: 'perangkat_desa' },
|
||||
{ label: 'Kepala Desa', value: 'kepala_desa' },
|
||||
{ label: 'Biarawati', value: 'biarawati' },
|
||||
{ label: 'Wiraswasta', value: 'wiraswasta' },
|
||||
{ label: 'Lainnya', value: 'lainnya', priority: -100 },
|
||||
]
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<FieldGroup :class="cn('select-field-group', fieldGroupClass, containerClass)">
|
||||
<Label
|
||||
:label-for="fieldName"
|
||||
:class="cn('select-field-label', labelClass)"
|
||||
:is-required="isRequired"
|
||||
>
|
||||
{{ label }}
|
||||
</Label>
|
||||
<Field
|
||||
:id="fieldName"
|
||||
:errors="errors"
|
||||
:class="cn('select-field-wrapper')"
|
||||
>
|
||||
<FormField
|
||||
v-slot="{ componentField }"
|
||||
:name="fieldName"
|
||||
>
|
||||
<FormItem>
|
||||
<FormControl>
|
||||
<Combobox
|
||||
:id="fieldName"
|
||||
v-bind="componentField"
|
||||
:items="jobOptions"
|
||||
:placeholder="placeholder"
|
||||
search-placeholder="Cari..."
|
||||
empty-message="Jenis Pekerjaan tidak ditemukan"
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
</FormField>
|
||||
</Field>
|
||||
</FieldGroup>
|
||||
</template>
|
||||
@@ -0,0 +1,82 @@
|
||||
<script setup lang="ts">
|
||||
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'
|
||||
|
||||
const props = defineProps<{
|
||||
fieldName?: string
|
||||
label?: string
|
||||
placeholder?: string
|
||||
errors?: FormErrors
|
||||
class?: string
|
||||
selectClass?: string
|
||||
fieldGroupClass?: string
|
||||
labelClass?: string
|
||||
isRequired?: boolean
|
||||
}>()
|
||||
|
||||
const {
|
||||
fieldName = 'language',
|
||||
label = 'Bahasa',
|
||||
placeholder = 'Pilih preferensi bahasa',
|
||||
errors,
|
||||
class: containerClass,
|
||||
selectClass,
|
||||
fieldGroupClass,
|
||||
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 },
|
||||
]
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<FieldGroup :class="cn('select-field-group', fieldGroupClass, containerClass)">
|
||||
<Label
|
||||
:label-for="fieldName"
|
||||
:class="cn('select-field-label', labelClass)"
|
||||
:is-required="isRequired"
|
||||
>
|
||||
{{ label }}
|
||||
</Label>
|
||||
<Field
|
||||
:id="fieldName"
|
||||
:errors="errors"
|
||||
:class="cn('select-field-wrapper')"
|
||||
>
|
||||
<FormField
|
||||
v-slot="{ componentField }"
|
||||
:name="fieldName"
|
||||
>
|
||||
<FormItem>
|
||||
<FormControl>
|
||||
<Select
|
||||
:id="fieldName"
|
||||
v-bind="componentField"
|
||||
:items="langOptions"
|
||||
:placeholder="placeholder"
|
||||
:preserve-order="false"
|
||||
:class="
|
||||
cn(
|
||||
'text-sm transition-all duration-200 focus:outline-none focus:ring-1 focus:ring-black focus:ring-offset-0',
|
||||
selectClass,
|
||||
)
|
||||
"
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
</FormField>
|
||||
</Field>
|
||||
</FieldGroup>
|
||||
</template>
|
||||
@@ -0,0 +1,78 @@
|
||||
<script setup lang="ts">
|
||||
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'
|
||||
|
||||
const props = defineProps<{
|
||||
fieldName?: string
|
||||
label?: string
|
||||
errors?: FormErrors
|
||||
class?: string
|
||||
selectClass?: string
|
||||
fieldGroupClass?: string
|
||||
labelClass?: string
|
||||
isRequired?: boolean
|
||||
placeholder?: string
|
||||
}>()
|
||||
|
||||
const {
|
||||
fieldName = 'maritalStatus',
|
||||
label = 'Status Pernikahan',
|
||||
errors,
|
||||
class: containerClass,
|
||||
labelClass,
|
||||
placeholder = 'Pilih',
|
||||
} = props
|
||||
|
||||
const maritalStatusOptions = [
|
||||
{ label: 'Tidak Diketahui', value: 'TIDAK_DIKETAHUI' },
|
||||
{ label: 'Belum Kawin', value: 'BELUM_KAWIN' },
|
||||
{ label: 'Kawin', value: 'KAWIN' },
|
||||
{ label: 'Cerai Hidup', value: 'CERAI_HIDUP' },
|
||||
{ label: 'Cerai Mati', value: 'CERAI_MATI' },
|
||||
]
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<FieldGroup :class="cn('select-field-group', fieldGroupClass, containerClass)">
|
||||
<Label
|
||||
:label-for="fieldName"
|
||||
:class="cn('select-field-label', labelClass)"
|
||||
:is-required="isRequired"
|
||||
>
|
||||
{{ label }}
|
||||
</Label>
|
||||
<Field
|
||||
:id="fieldName"
|
||||
:errors="errors"
|
||||
:class="cn('select-field-wrapper')"
|
||||
>
|
||||
<FormField
|
||||
v-slot="{ componentField }"
|
||||
:name="fieldName"
|
||||
>
|
||||
<FormItem>
|
||||
<FormControl>
|
||||
<Select
|
||||
:id="fieldName"
|
||||
v-bind="componentField"
|
||||
:items="maritalStatusOptions"
|
||||
:placeholder="placeholder"
|
||||
:preserve-order="true"
|
||||
:class="
|
||||
cn(
|
||||
'text-sm transition-all duration-200 focus:outline-none focus:ring-1 focus:ring-black focus:ring-offset-0',
|
||||
selectClass,
|
||||
)
|
||||
"
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
</FormField>
|
||||
</Field>
|
||||
</FieldGroup>
|
||||
</template>
|
||||
@@ -0,0 +1,86 @@
|
||||
<script setup lang="ts">
|
||||
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 { religionCodes } from '~/lib/constants'
|
||||
import { cn, mapToComboboxOptList } from '~/lib/utils'
|
||||
|
||||
const props = defineProps<{
|
||||
fieldName?: string
|
||||
label?: string
|
||||
placeholder?: string
|
||||
errors?: FormErrors
|
||||
class?: string
|
||||
selectClass?: string
|
||||
fieldGroupClass?: string
|
||||
labelClass?: string
|
||||
isRequired?: boolean
|
||||
}>()
|
||||
|
||||
const {
|
||||
fieldName = 'religion',
|
||||
label = 'Agama',
|
||||
placeholder = 'Pilih kepercayaan',
|
||||
errors,
|
||||
class: containerClass,
|
||||
selectClass,
|
||||
fieldGroupClass,
|
||||
labelClass,
|
||||
} = props
|
||||
|
||||
const extendOptions = [
|
||||
{ label: 'Tidak diketahui', value: 'unknown', priority: 1 },
|
||||
{ label: 'Kepercayaan Lain', value: 'other', priority: -1 },
|
||||
]
|
||||
|
||||
const religionOptions = [
|
||||
...mapToComboboxOptList(religionCodes).map(({ label, value }) => ({
|
||||
label,
|
||||
value,
|
||||
})),
|
||||
...extendOptions,
|
||||
]
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<FieldGroup :class="cn('select-field-group', fieldGroupClass, containerClass)">
|
||||
<Label
|
||||
:label-for="fieldName"
|
||||
:class="cn('select-field-label', labelClass)"
|
||||
:is-required="isRequired"
|
||||
>
|
||||
{{ label }}
|
||||
</Label>
|
||||
<Field
|
||||
:id="fieldName"
|
||||
:errors="errors"
|
||||
:class="cn('select-field-wrapper')"
|
||||
>
|
||||
<FormField
|
||||
v-slot="{ componentField }"
|
||||
:name="fieldName"
|
||||
>
|
||||
<FormItem>
|
||||
<FormControl>
|
||||
<Select
|
||||
:id="fieldName"
|
||||
v-bind="componentField"
|
||||
:items="religionOptions"
|
||||
:placeholder="placeholder"
|
||||
:preserve-order="false"
|
||||
:class="
|
||||
cn(
|
||||
'text-sm transition-all duration-200 focus:outline-none focus:ring-1 focus:ring-black focus:ring-offset-0',
|
||||
selectClass,
|
||||
)
|
||||
"
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
</FormField>
|
||||
</Field>
|
||||
</FieldGroup>
|
||||
</template>
|
||||
@@ -1,43 +1,225 @@
|
||||
<script setup lang="ts">
|
||||
import Block from '~/components/pub/my-ui/form/block.vue'
|
||||
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 Action from '~/components/pub/my-ui/nav-footer/ba-dr-su.vue'
|
||||
import type { FormErrors } from '~/types/error'
|
||||
import { toTypedSchema } from '@vee-validate/zod'
|
||||
import { Form } from '~/components/pub/ui/form'
|
||||
import InputBase from '~/components/pub/my-ui/form/input-base.vue'
|
||||
import InputFile from './_common/input-file.vue'
|
||||
import InputPatientName from './_common/input-patient-name.vue'
|
||||
import RadioCommunicationBarrier from './_common/radio-communication-barrier.vue'
|
||||
import RadioDisability from './_common/radio-disability.vue'
|
||||
import RadioGender from './_common/radio-gender.vue'
|
||||
import RadioNationality from './_common/radio-nationality.vue'
|
||||
import SelectDisability from './_common/select-disability.vue'
|
||||
import SelectDob from './_common/select-dob.vue'
|
||||
import SelectEducation from './_common/select-education.vue'
|
||||
import SelectEthnicity from './_common/select-ethnicity.vue'
|
||||
import SelectJob from './_common/select-job.vue'
|
||||
import SelectLanguage from './_common/select-lang.vue'
|
||||
import SelectMaritalStatus from './_common/select-marital-status.vue'
|
||||
import SelectReligion from './_common/select-religion.vue'
|
||||
|
||||
const props = defineProps<{
|
||||
schema: any
|
||||
initialValues?: any
|
||||
errors?: FormErrors
|
||||
}>()
|
||||
|
||||
const formSchema = toTypedSchema(props.schema)
|
||||
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 id="entry-form">
|
||||
<div class="mb-5 border-b border-b-slate-300 pb-3 text-lg xl:text-xl">
|
||||
<Block>
|
||||
<FieldGroup :column="3">
|
||||
<Label>Nama</Label>
|
||||
<Field>
|
||||
<Input type="text" name="name" />
|
||||
</Field>
|
||||
</FieldGroup>
|
||||
<FieldGroup :column="3">
|
||||
<Label>Nama</Label>
|
||||
<Field>
|
||||
<Input type="text" name="name" />
|
||||
</Field>
|
||||
</FieldGroup>
|
||||
<FieldGroup :column="3">
|
||||
<Label>Nomor RM</Label>
|
||||
<Field>
|
||||
<Input type="text" name="name" />
|
||||
</Field>
|
||||
</FieldGroup>
|
||||
<FieldGroup>
|
||||
<Label dynamic>Alamat</Label>
|
||||
<Field>
|
||||
<Input type="text" name="name" />
|
||||
</Field>
|
||||
</FieldGroup>
|
||||
</Block>
|
||||
<Form
|
||||
ref="formRef"
|
||||
v-slot="{ values }"
|
||||
as=""
|
||||
keep-values
|
||||
:validation-schema="formSchema"
|
||||
:validate-on-mount="false"
|
||||
validation-mode="onSubmit"
|
||||
:initial-values="initialValues ? initialValues : {}"
|
||||
>
|
||||
<div class="mb-3 border-b border-b-slate-300">
|
||||
<p class="text-md mt-1 font-semibold">Data Diri Pasien</p>
|
||||
<div class="grid grid-cols-1 md:grid-cols-[150px_1fr]">
|
||||
<InputPatientName
|
||||
field-name-alias="alias"
|
||||
field-name-input="fullName"
|
||||
label-for-alias="Alias"
|
||||
label-for-input="Nama Lengkap"
|
||||
placeholder="Masukkan nama lengkap pasien"
|
||||
:errors="errors"
|
||||
is-required
|
||||
/>
|
||||
</div>
|
||||
<div class="grid grid-cols-1 md:grid-cols-3">
|
||||
<InputBase
|
||||
field-name="birthPlace"
|
||||
label="Tempat Lahir"
|
||||
placeholder="Malang"
|
||||
:errors="errors"
|
||||
is-required
|
||||
/>
|
||||
<SelectDob
|
||||
label="Tanggal Lahir"
|
||||
:errors="errors"
|
||||
is-required
|
||||
/>
|
||||
</div>
|
||||
<div class="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3">
|
||||
<RadioGender
|
||||
field-name="gender"
|
||||
label="Jenis Kelamin"
|
||||
placeholder="Pilih jenis kelamin"
|
||||
:errors="errors"
|
||||
is-required
|
||||
/>
|
||||
<RadioNationality
|
||||
field-name="nationality"
|
||||
label="Kebangsaan"
|
||||
placeholder="Pilih kebangsaan"
|
||||
:errors="errors"
|
||||
is-required
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="my-2 flex justify-end py-2">
|
||||
<Action />
|
||||
|
||||
<div class="mb-3 border-b border-b-slate-300">
|
||||
<p class="text-md mt-1 font-semibold">Dokumen Identitas</p>
|
||||
|
||||
<div class="grid grid-cols-1 md:grid-cols-3">
|
||||
<InputBase
|
||||
field-name="identityNumber"
|
||||
label="No. KTP"
|
||||
placeholder="Masukkan NIK"
|
||||
:errors="errors"
|
||||
numeric-only
|
||||
:max-length="16"
|
||||
is-required
|
||||
/>
|
||||
<InputBase
|
||||
field-name="drivingLicenseNumber"
|
||||
label="No. SIM"
|
||||
placeholder="Masukkan nomor SIM"
|
||||
numeric-only
|
||||
:max-length="20"
|
||||
:errors="errors"
|
||||
/>
|
||||
|
||||
<InputBase
|
||||
field-name="passportNumber"
|
||||
label="No. Paspor"
|
||||
placeholder="Masukkan nomor paspor"
|
||||
:max-length="20"
|
||||
:errors="errors"
|
||||
/>
|
||||
</div>
|
||||
<div class="grid grid-cols-1 md:grid-cols-2">
|
||||
<InputFile
|
||||
field-name="identityCardFile"
|
||||
label="Dokumen KTP"
|
||||
placeholder="Unggah scan dokumen KTP"
|
||||
:errors="errors"
|
||||
/>
|
||||
<InputFile
|
||||
field-name="familyCardFile"
|
||||
label="Dokumen KK"
|
||||
placeholder="Unggah scan dokumen KK"
|
||||
:errors="errors"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<div class="mb-3 border-b border-b-slate-300">
|
||||
<p class="text-md mt-1 font-semibold">Data Demografis</p>
|
||||
|
||||
<div class="grid grid-cols-1 md:grid-cols-3">
|
||||
<SelectReligion
|
||||
field-name="religion"
|
||||
label="Agama"
|
||||
placeholder="Pilih agama"
|
||||
:errors="errors"
|
||||
is-required
|
||||
/>
|
||||
<SelectEthnicity
|
||||
field-name="ethnicity"
|
||||
label="Suku"
|
||||
placeholder="Pilih suku bangsa"
|
||||
:errors="errors"
|
||||
:is-disabled="values.nationality !== 'WNI'"
|
||||
/>
|
||||
<SelectLanguage
|
||||
field-name="language"
|
||||
label="Bahasa"
|
||||
placeholder="Pilih preferensi bahasa"
|
||||
:errors="errors"
|
||||
is-required
|
||||
/>
|
||||
</div>
|
||||
<div class="grid grid-cols-1 md:grid-cols-3">
|
||||
<SelectMaritalStatus
|
||||
field-name="maritalStatus"
|
||||
label="Status Perkawinan"
|
||||
placeholder="Pilih status Perkawinan"
|
||||
:errors="errors"
|
||||
is-required
|
||||
/>
|
||||
<SelectEducation
|
||||
field-name="education"
|
||||
label="Pendidikan"
|
||||
placeholder="Pilih pendidikan"
|
||||
:errors="errors"
|
||||
is-required
|
||||
/>
|
||||
<SelectJob
|
||||
field-name="job"
|
||||
label="Pekerjaan"
|
||||
placeholder="Pilih pekerjaan"
|
||||
:errors="errors"
|
||||
is-required
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-3 border-b border-b-slate-300">
|
||||
<p class="text-md mt-1 font-semibold">Kondisi Khusus</p>
|
||||
<div class="grid grid-cols-1 md:grid-cols-3">
|
||||
<RadioCommunicationBarrier
|
||||
field-name="communicationBarrier"
|
||||
label="Hambatan Berkomunikasi"
|
||||
:errors="errors"
|
||||
is-required
|
||||
/>
|
||||
<div class="cols-span-1">
|
||||
<RadioDisability
|
||||
field-name="disability"
|
||||
label="Disabilitas"
|
||||
:errors="errors"
|
||||
is-required
|
||||
/>
|
||||
<SelectDisability
|
||||
label="Jenis Disabilitas"
|
||||
field-name="disabilityType"
|
||||
:errors="errors"
|
||||
:is-disabled="values.disability !== 'YA'"
|
||||
:is-required="values.disability === 'YA'"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<InputBase
|
||||
field-name="note"
|
||||
label="Kepercayaan"
|
||||
placeholder="Contoh: tidak ingin diperiksa oleh dokter laki-laki"
|
||||
:errors="errors"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</Form>
|
||||
</template>
|
||||
|
||||
@@ -6,53 +6,28 @@ import type {
|
||||
RecStrFuncUnknown,
|
||||
Th,
|
||||
} from '~/components/pub/my-ui/data/types'
|
||||
import type { PatientEntity } from '~/models/patient'
|
||||
import { defineAsyncComponent } from 'vue'
|
||||
|
||||
type SmallDetailDto = any
|
||||
import { educationCodes, genderCodes } from '~/lib/constants'
|
||||
import { calculateAge } from '~/lib/utils'
|
||||
|
||||
const action = defineAsyncComponent(() => import('~/components/pub/my-ui/data/dropdown-action-dud.vue'))
|
||||
const statusBadge = defineAsyncComponent(() => import('./status-badge.vue'))
|
||||
|
||||
export const cols: Col[] = [
|
||||
{},
|
||||
{},
|
||||
{},
|
||||
{ width: 100 },
|
||||
{ width: 120 },
|
||||
{},
|
||||
{},
|
||||
{},
|
||||
{ width: 100 },
|
||||
{ width: 100 },
|
||||
{},
|
||||
{ width: 50 },
|
||||
]
|
||||
export const cols: Col[] = [{}, {}, {}, {}, {}, {}, {}, { width: 5 }]
|
||||
|
||||
export const header: Th[][] = [
|
||||
[
|
||||
{ label: 'Nama' },
|
||||
{ label: 'Rekam Medis' },
|
||||
{ label: 'KTP' },
|
||||
{ label: 'NIK' },
|
||||
{ label: 'Tgl Lahir' },
|
||||
{ label: 'Umur' },
|
||||
{ label: 'JK' },
|
||||
{ label: 'Jenis Kelamin' },
|
||||
{ label: 'Pendidikan' },
|
||||
{ label: 'Status' },
|
||||
{ label: '' },
|
||||
],
|
||||
]
|
||||
|
||||
export const keys = [
|
||||
'name',
|
||||
'medicalRecord_number',
|
||||
'identity_number',
|
||||
'birth_date',
|
||||
'patient_age',
|
||||
'gender',
|
||||
'education',
|
||||
'status',
|
||||
'action',
|
||||
]
|
||||
export const keys = ['name', 'identity_number', 'birth_date', 'patient_age', 'gender', 'education', 'action']
|
||||
|
||||
export const delKeyNames: KeyLabel[] = [
|
||||
{ key: 'code', label: 'Kode' },
|
||||
@@ -61,42 +36,47 @@ export const delKeyNames: KeyLabel[] = [
|
||||
|
||||
export const funcParsed: RecStrFuncUnknown = {
|
||||
name: (rec: unknown): unknown => {
|
||||
const recX = rec as SmallDetailDto
|
||||
return `${recX.firstName} ${recX.middleName || ''} ${recX.lastName || ''}`
|
||||
const { person } = rec as PatientEntity
|
||||
return person.name.trim()
|
||||
},
|
||||
identity_number: (rec: unknown): unknown => {
|
||||
const recX = rec as SmallDetailDto
|
||||
if (recX.identity_number?.substring(0, 5) === 'BLANK') {
|
||||
const { person } = rec as PatientEntity
|
||||
|
||||
if (person?.residentIdentityNumber?.substring(0, 5) === 'BLANK') {
|
||||
return '(TANPA NIK)'
|
||||
}
|
||||
return recX.identity_number
|
||||
return person.residentIdentityNumber
|
||||
},
|
||||
birth_date: (rec: unknown): unknown => {
|
||||
const recX = rec as SmallDetailDto
|
||||
if (typeof recX.birth_date == 'object' && recX.birth_date) {
|
||||
return (recX.birth_date as Date).toLocaleDateString()
|
||||
} else if (typeof recX.birth_date == 'string') {
|
||||
return (recX.birth_date as string).substring(0, 10)
|
||||
const { person } = rec as PatientEntity
|
||||
|
||||
if (typeof person.birthDate == 'object' && person.birthDate) {
|
||||
return (person.birthDate as Date).toLocaleDateString()
|
||||
} else if (typeof person.birthDate == 'string') {
|
||||
return (person.birthDate as string).substring(0, 10)
|
||||
}
|
||||
return recX.birth_date
|
||||
return person.birthDate
|
||||
},
|
||||
patient_age: (rec: unknown): unknown => {
|
||||
const recX = rec as SmallDetailDto
|
||||
return recX.birth_date?.split('T')[0]
|
||||
const { person } = rec as PatientEntity
|
||||
return calculateAge(person.birthDate)
|
||||
},
|
||||
gender: (rec: unknown): unknown => {
|
||||
const recX = rec as SmallDetailDto
|
||||
if (typeof recX?.gender_code !== 'number' && recX?.gender_code !== '') {
|
||||
return 'Tidak Diketahui'
|
||||
const { person } = rec as PatientEntity
|
||||
|
||||
if (typeof person.gender_code == 'number' && person.gender_code >= 0) {
|
||||
return person.gender_code
|
||||
} else if (typeof person.gender_code === 'string' && person.gender_code) {
|
||||
return genderCodes[person.gender_code] || '-'
|
||||
}
|
||||
return recX.gender_code
|
||||
return '-'
|
||||
},
|
||||
education: (rec: unknown): unknown => {
|
||||
const recX = rec as SmallDetailDto
|
||||
if (typeof recX.education_code == 'number' && recX.education_code >= 0) {
|
||||
return recX.education_code
|
||||
} else if (typeof recX.education_code) {
|
||||
return recX.education_code
|
||||
const { person } = rec as PatientEntity
|
||||
if (typeof person.education_code == 'number' && person.education_code >= 0) {
|
||||
return person.education_code
|
||||
} else if (typeof person.education_code === 'string' && person.education_code) {
|
||||
return educationCodes[person.education_code] || '-'
|
||||
}
|
||||
return '-'
|
||||
},
|
||||
@@ -111,17 +91,6 @@ export const funcComponent: RecStrFuncComponent = {
|
||||
}
|
||||
return res
|
||||
},
|
||||
status(rec, idx) {
|
||||
if (rec.status === null) {
|
||||
rec.status_code = 0
|
||||
}
|
||||
const res: RecComponent = {
|
||||
idx,
|
||||
rec: rec as object,
|
||||
component: statusBadge,
|
||||
}
|
||||
return res
|
||||
},
|
||||
}
|
||||
|
||||
export const funcHtml: RecStrFuncUnknown = {
|
||||
|
||||
@@ -1,19 +1,36 @@
|
||||
<script setup lang="ts">
|
||||
import type { PaginationMeta } from '~/components/pub/my-ui/pagination/pagination.type'
|
||||
import PaginationView from '~/components/pub/my-ui/pagination/pagination-view.vue'
|
||||
import { cols, funcComponent, funcHtml, funcParsed, header, keys } from './list-cfg'
|
||||
|
||||
defineProps<{
|
||||
interface Props {
|
||||
data: any[]
|
||||
paginationMeta: PaginationMeta
|
||||
}
|
||||
|
||||
defineProps<Props>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
pageChange: [page: number]
|
||||
}>()
|
||||
|
||||
function handlePageChange(page: number) {
|
||||
emit('pageChange', page)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<PubMyUiDataTable
|
||||
:rows="data"
|
||||
:cols="cols"
|
||||
:header="header"
|
||||
:keys="keys"
|
||||
:func-parsed="funcParsed"
|
||||
:func-html="funcHtml"
|
||||
:func-component="funcComponent"
|
||||
/>
|
||||
<div class="space-y-4">
|
||||
<PubMyUiDataTable
|
||||
:rows="data"
|
||||
:cols="cols"
|
||||
:header="header"
|
||||
:keys="keys"
|
||||
:func-parsed="funcParsed"
|
||||
:func-html="funcHtml"
|
||||
:func-component="funcComponent"
|
||||
:skeleton-size="paginationMeta?.pageSize"
|
||||
/>
|
||||
<PaginationView :pagination-meta="paginationMeta" @page-change="handlePageChange" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -0,0 +1,153 @@
|
||||
<script setup lang="ts">
|
||||
import type { Person } from '~/models/person'
|
||||
import type { PersonAddress } from '~/models/person-address'
|
||||
import type { PersonContact } from '~/models/person-contact'
|
||||
import type { PersonRelative } from '~/models/person-relative'
|
||||
|
||||
import DetailRow from '~/components/pub/my-ui/form/view/detail-row.vue'
|
||||
import DetailSection from '~/components/pub/my-ui/form/view/detail-section.vue'
|
||||
|
||||
import { educationCodes, genderCodes, personContactTypes, relationshipCodes, religionCodes } from '~/lib/constants'
|
||||
import { mapToComboboxOptList } from '~/lib/utils'
|
||||
|
||||
// #region Props & Emits
|
||||
const props = defineProps<{
|
||||
person: Person
|
||||
personAddresses: PersonAddress[]
|
||||
personContacts: PersonContact[]
|
||||
personRelatives: PersonRelative[]
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'click', type: string): void
|
||||
}>()
|
||||
|
||||
// #endregion
|
||||
|
||||
// #region State & Computed
|
||||
const genderOptions = mapToComboboxOptList(genderCodes)
|
||||
const religionOptions = mapToComboboxOptList(religionCodes)
|
||||
const educationOptions = mapToComboboxOptList(educationCodes)
|
||||
const relationshipOptions = mapToComboboxOptList(relationshipCodes)
|
||||
const personContactTypeOptions = mapToComboboxOptList(personContactTypes)
|
||||
|
||||
const residentAddress = 'Jl. Puncak Borobudur Blok M No. 321, Lowokwaru, Kota Malang, Jawa Timur'
|
||||
const primaryAddress = 'Perumahan Araya Cluster B, No 22, Blimbing, Kota Malang, Jawa Timur'
|
||||
|
||||
const patientAge = computed(() => {
|
||||
if (!props.person.birthDate) {
|
||||
return '-'
|
||||
}
|
||||
const birthDate = new Date(props.person.birthDate)
|
||||
const today = new Date()
|
||||
let age = today.getFullYear() - birthDate.getFullYear()
|
||||
const monthDiff = today.getMonth() - birthDate.getMonth()
|
||||
if (monthDiff < 0 || (monthDiff === 0 && today.getDate() < birthDate.getDate())) {
|
||||
age--
|
||||
}
|
||||
return age
|
||||
})
|
||||
// #endregion
|
||||
|
||||
// #region Lifecycle Hooks
|
||||
// #endregion
|
||||
|
||||
// #region Functions
|
||||
// #endregion region
|
||||
|
||||
// #region Utilities & event handlers
|
||||
function onClick(type: string) {
|
||||
emit('click', type)
|
||||
}
|
||||
// #endregion
|
||||
|
||||
// #region Watchers
|
||||
// #endregion
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<DetailSection title="Data Pasien">
|
||||
<DetailRow label="Nomor ID">{{ person.id || '-' }}</DetailRow>
|
||||
<DetailRow label="Sapaan">{{ person.alias || '-' }}</DetailRow>
|
||||
<DetailRow label="Nama Lengkap">{{ person.name || '-' }}</DetailRow>
|
||||
<DetailRow label="Tempat, tanggal lahir">
|
||||
{{ person.birthRegency_code || '-' }},
|
||||
{{ person.birthDate ? new Date(person.birthDate).toLocaleDateString() : '-' }}
|
||||
</DetailRow>
|
||||
<DetailRow label="Usia">{{ patientAge || '-' }}</DetailRow>
|
||||
<DetailRow label="Tanggal Daftar">
|
||||
{{ person.createdAt ? new Date(person.createdAt).toLocaleDateString() : '-' }}
|
||||
</DetailRow>
|
||||
<DetailRow label="Jenis Kelamin">
|
||||
{{ genderOptions.find((item) => item.code === person.gender_code)?.label || '-' }}
|
||||
</DetailRow>
|
||||
|
||||
<DetailRow label="NIK">{{ person.residentIdentityNumber || '-' }}</DetailRow>
|
||||
<DetailRow label="No. SIM">{{ person.drivingLicenseNumber || '-' }}</DetailRow>
|
||||
<DetailRow label="No. Paspor">{{ person.passportNumber || '-' }}</DetailRow>
|
||||
|
||||
<DetailRow label="Agama">
|
||||
{{ religionOptions.find((item) => item.code === person.religion_code)?.label || '-' }}
|
||||
</DetailRow>
|
||||
<DetailRow label="Suku">{{ person.ethnic_code || '-' }}</DetailRow>
|
||||
<DetailRow label="Bahasa">{{ person.language_code || '-' }}</DetailRow>
|
||||
<DetailRow label="Pendidikan">
|
||||
{{ educationOptions.find((item) => item.code === person.education_code)?.label || '-' }}
|
||||
</DetailRow>
|
||||
<DetailRow label="Pekerjaan">{{ person.occupation_name || '-' }}</DetailRow>
|
||||
</DetailSection>
|
||||
|
||||
<DetailSection title="Alamat">
|
||||
<DetailRow label="Alamat Domisili">{{ residentAddress || '-' }}</DetailRow>
|
||||
<DetailRow label="Alamat KTP">{{ primaryAddress || '-' }}</DetailRow>
|
||||
</DetailSection>
|
||||
<DetailSection title="Kontak">
|
||||
<template v-if="personContacts && personContacts.length > 0">
|
||||
<template
|
||||
v-for="contactType in personContactTypeOptions"
|
||||
:key="contactType.code"
|
||||
>
|
||||
<DetailRow :label="contactType.label">
|
||||
{{ personContacts.find((item) => item.type_code === contactType.code)?.value || '-' }}
|
||||
</DetailRow>
|
||||
</template>
|
||||
</template>
|
||||
<template v-else>
|
||||
<DetailRow label="Kontak">-</DetailRow>
|
||||
</template>
|
||||
</DetailSection>
|
||||
<DetailSection title="Penanggung Jawab">
|
||||
<template v-if="personRelatives && personRelatives.filter((rel) => rel.responsible).length > 0">
|
||||
<template
|
||||
v-for="(relative, index) in personRelatives.filter((rel) => rel.responsible)"
|
||||
:key="relative.id"
|
||||
>
|
||||
<div
|
||||
v-if="index > 0"
|
||||
class="mt-3 border-t border-gray-200 pt-3"
|
||||
></div>
|
||||
<DetailRow label="Nama">{{ relative.name || '-' }}</DetailRow>
|
||||
<DetailRow label="Hubungan">
|
||||
{{ relationshipOptions.find((item) => item.code === relative.relationship_code)?.label || '-' }}
|
||||
</DetailRow>
|
||||
<DetailRow label="Jenis Kelamin">
|
||||
{{ genderOptions.find((item) => item.code === relative.gender_code)?.label || '-' }}
|
||||
</DetailRow>
|
||||
<DetailRow label="Pendidikan">
|
||||
{{ educationOptions.find((item) => item.code === relative.education_code)?.label || '-' }}
|
||||
</DetailRow>
|
||||
<DetailRow label="Pekerjaan">{{ relative.occupation_name || '-' }}</DetailRow>
|
||||
<DetailRow label="Alamat">{{ relative.address || '-' }}</DetailRow>
|
||||
<DetailRow label="Nomor HP">{{ relative.phoneNumber || '-' }}</DetailRow>
|
||||
</template>
|
||||
</template>
|
||||
<template v-else>
|
||||
<DetailRow label="Penanggung Jawab">-</DetailRow>
|
||||
</template>
|
||||
</DetailSection>
|
||||
<div class="border-t-1 my-2 flex justify-end border-t-slate-300 py-2">
|
||||
<PubMyUiNavFooterBaEd @click="onClick" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped></style>
|
||||
@@ -0,0 +1,95 @@
|
||||
<script setup lang="ts">
|
||||
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 { Label as RadioLabel } from '~/components/pub/ui/label'
|
||||
import { RadioGroup, RadioGroupItem } from '~/components/pub/ui/radio-group'
|
||||
import { cn } from '~/lib/utils'
|
||||
|
||||
const props = defineProps<{
|
||||
fieldName?: string
|
||||
label?: string
|
||||
errors?: FormErrors
|
||||
class?: string
|
||||
radioGroupClass?: string
|
||||
radioItemClass?: string
|
||||
labelClass?: string
|
||||
isRequired?: boolean
|
||||
}>()
|
||||
|
||||
const {
|
||||
fieldName = 'residenceStatus',
|
||||
label = 'Apakah alamat KTP sama dengan alamat sekarang?',
|
||||
errors,
|
||||
class: containerClass,
|
||||
radioGroupClass,
|
||||
radioItemClass,
|
||||
labelClass,
|
||||
} = props
|
||||
|
||||
const residenceOptions = [
|
||||
{ label: 'Ya', value: '1' },
|
||||
{ label: 'Tidak', value: '0' },
|
||||
]
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<FieldGroup :class="cn('radio-group-field', containerClass)">
|
||||
<Label
|
||||
size="fit"
|
||||
height="compact"
|
||||
:label-for="fieldName"
|
||||
:is-required="isRequired"
|
||||
>
|
||||
{{ label }}
|
||||
</Label>
|
||||
<Field
|
||||
:id="fieldName"
|
||||
:errors="errors"
|
||||
>
|
||||
<FormField
|
||||
v-slot="{ componentField }"
|
||||
:name="fieldName"
|
||||
>
|
||||
<FormItem>
|
||||
<FormControl>
|
||||
<RadioGroup
|
||||
v-bind="componentField"
|
||||
:class="cn('flex flex-row flex-wrap gap-4 sm:gap-6', radioGroupClass)"
|
||||
>
|
||||
<div
|
||||
v-for="(option, index) in residenceOptions"
|
||||
:key="option.value"
|
||||
:class="cn('flex min-w-fit items-center space-x-2', radioItemClass)"
|
||||
>
|
||||
<RadioGroupItem
|
||||
:id="`${fieldName}-${index}`"
|
||||
:value="option.value"
|
||||
:class="
|
||||
cn(
|
||||
'relative h-4 w-4 rounded-full border-2 border-muted-foreground before:absolute before:inset-1 before:rounded-full before:bg-primary before:opacity-0 before:transition-opacity data-[state=checked]:border-primary data-[state=checked]:bg-white data-[state=checked]:before:opacity-100 sm:h-5 sm:w-5',
|
||||
containerClass,
|
||||
)
|
||||
"
|
||||
/>
|
||||
<RadioLabel
|
||||
:for="`${fieldName}-${index}`"
|
||||
:class="
|
||||
cn(
|
||||
'cursor-pointer select-none text-xs font-medium leading-none transition-colors hover:text-primary peer-disabled:cursor-not-allowed peer-disabled:opacity-70 sm:text-sm',
|
||||
labelClass,
|
||||
)
|
||||
"
|
||||
>
|
||||
{{ option.label }}
|
||||
</RadioLabel>
|
||||
</div>
|
||||
</RadioGroup>
|
||||
</FormControl>
|
||||
<FormMessage class="ml-0 mt-1" />
|
||||
</FormItem>
|
||||
</FormField>
|
||||
</Field>
|
||||
</FieldGroup>
|
||||
</template>
|
||||
@@ -0,0 +1,63 @@
|
||||
<script setup lang="ts">
|
||||
import type { FormErrors } from '~/types/error'
|
||||
import Combobox from '~/components/pub/my-ui/form/combobox.vue'
|
||||
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 { cn } from '~/lib/utils'
|
||||
|
||||
const props = defineProps<{
|
||||
fieldName: string
|
||||
isDisabled?: boolean
|
||||
placeholder?: string
|
||||
errors?: FormErrors
|
||||
class?: string
|
||||
selectClass?: string
|
||||
fieldGroupClass?: string
|
||||
isRequired?: boolean
|
||||
}>()
|
||||
|
||||
const { placeholder = 'Pilih Kecamatan', errors, class: containerClass, selectClass, fieldGroupClass } = props
|
||||
|
||||
const districtOptions = [
|
||||
{ label: 'Kecamatan Lowokwaru', value: '18' },
|
||||
{ label: 'Kecamatan Pakis', value: '33' },
|
||||
{ label: 'Kecamatan Blimbing', value: '35' },
|
||||
]
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<FieldGroup :class="cn('select-field-group', fieldGroupClass, containerClass)">
|
||||
<Label
|
||||
:label-for="fieldName"
|
||||
:is-required="isRequired"
|
||||
>
|
||||
Kecamatan
|
||||
</Label>
|
||||
<Field
|
||||
:id="fieldName"
|
||||
:errors="errors"
|
||||
:class="cn('select-field-wrapper')"
|
||||
>
|
||||
<FormField
|
||||
v-slot="{ componentField }"
|
||||
:name="fieldName"
|
||||
>
|
||||
<FormItem>
|
||||
<FormControl>
|
||||
<Combobox
|
||||
:id="fieldName"
|
||||
v-bind="componentField"
|
||||
:items="districtOptions"
|
||||
:placeholder="placeholder"
|
||||
:is-disabled="isDisabled"
|
||||
search-placeholder="Cari..."
|
||||
empty-message="Kecamatan tidak ditemukan"
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
</FormField>
|
||||
</Field>
|
||||
</FieldGroup>
|
||||
</template>
|
||||
@@ -0,0 +1,76 @@
|
||||
<script setup lang="ts">
|
||||
import type { FormErrors } from '~/types/error'
|
||||
import Combobox from '~/components/pub/my-ui/form/combobox.vue'
|
||||
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 { cn } from '~/lib/utils'
|
||||
|
||||
const props = defineProps<{
|
||||
fieldName: string
|
||||
isDisabled?: boolean
|
||||
placeholder?: string
|
||||
errors?: FormErrors
|
||||
class?: string
|
||||
selectClass?: string
|
||||
fieldGroupClass?: string
|
||||
isRequired?: boolean
|
||||
}>()
|
||||
|
||||
const {
|
||||
fieldName = 'zipCode',
|
||||
placeholder = 'Kode Pos',
|
||||
errors,
|
||||
class: containerClass,
|
||||
selectClass,
|
||||
fieldGroupClass,
|
||||
} = props
|
||||
|
||||
const postalCodeOptions = [
|
||||
{ label: '65120', value: '65120' },
|
||||
{ label: '65121', value: '65121' },
|
||||
{ label: '65123', value: '65123' },
|
||||
{ label: '65124', value: '65124' },
|
||||
{ label: '65125', value: '65125' },
|
||||
{ label: '65126', value: '65126' },
|
||||
{ label: '65127', value: '65127' },
|
||||
{ label: '65128', value: '65128' },
|
||||
{ label: '65129', value: '65129' },
|
||||
]
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<FieldGroup :class="cn('select-field-group', fieldGroupClass, containerClass)">
|
||||
<Label
|
||||
:label-for="fieldName"
|
||||
:is-required="isRequired"
|
||||
>
|
||||
Kode Pos
|
||||
</Label>
|
||||
<Field
|
||||
:id="fieldName"
|
||||
:errors="errors"
|
||||
:class="cn('select-field-wrapper')"
|
||||
>
|
||||
<FormField
|
||||
v-slot="{ componentField }"
|
||||
:name="fieldName"
|
||||
>
|
||||
<FormItem>
|
||||
<FormControl>
|
||||
<Combobox
|
||||
:id="fieldName"
|
||||
v-bind="componentField"
|
||||
:items="postalCodeOptions"
|
||||
:placeholder="placeholder"
|
||||
:is-disabled="isDisabled"
|
||||
search-placeholder="Cari..."
|
||||
empty-message="Kode pos tidak ditemukan"
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
</FormField>
|
||||
</Field>
|
||||
</FieldGroup>
|
||||
</template>
|
||||
@@ -0,0 +1,69 @@
|
||||
<script setup lang="ts">
|
||||
import type { FormErrors } from '~/types/error'
|
||||
import Combobox from '~/components/pub/my-ui/form/combobox.vue'
|
||||
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 { cn } from '~/lib/utils'
|
||||
|
||||
const props = defineProps<{
|
||||
fieldName: string
|
||||
isDisabled?: boolean
|
||||
placeholder?: string
|
||||
errors?: FormErrors
|
||||
class?: string
|
||||
selectClass?: string
|
||||
fieldGroupClass?: string
|
||||
isRequired?: boolean
|
||||
}>()
|
||||
|
||||
const {
|
||||
fieldName = 'provinceId',
|
||||
placeholder = 'Pilih provinsi',
|
||||
errors,
|
||||
class: containerClass,
|
||||
fieldGroupClass,
|
||||
} = props
|
||||
|
||||
const provinceList = [
|
||||
{ label: 'Jawa Barat', value: '18' },
|
||||
{ label: 'Jawa Tengah', value: '33' },
|
||||
{ label: 'Jawa Timur', value: '35' },
|
||||
]
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<FieldGroup :class="cn('select-field-group', fieldGroupClass, containerClass)">
|
||||
<Label
|
||||
:label-for="fieldName"
|
||||
:is-required="isRequired"
|
||||
>
|
||||
Provinsi
|
||||
</Label>
|
||||
<Field
|
||||
:id="fieldName"
|
||||
:errors="errors"
|
||||
:class="cn('select-field-wrapper')"
|
||||
>
|
||||
<FormField
|
||||
v-slot="{ componentField }"
|
||||
:name="fieldName"
|
||||
>
|
||||
<FormItem>
|
||||
<FormControl>
|
||||
<Combobox
|
||||
:id="fieldName"
|
||||
v-bind="componentField"
|
||||
:items="provinceList"
|
||||
:placeholder="placeholder"
|
||||
:is-disabled="isDisabled"
|
||||
search-placeholder="Cari..."
|
||||
empty-message="Kecamatan tidak ditemukan"
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
</FormField>
|
||||
</Field>
|
||||
</FieldGroup>
|
||||
</template>
|
||||
@@ -0,0 +1,65 @@
|
||||
<script setup lang="ts">
|
||||
import type { FormErrors } from '~/types/error'
|
||||
import Combobox from '~/components/pub/my-ui/form/combobox.vue'
|
||||
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 { cn } from '~/lib/utils'
|
||||
|
||||
const props = defineProps<{
|
||||
fieldName: string
|
||||
isDisabled?: boolean
|
||||
placeholder?: string
|
||||
errors?: FormErrors
|
||||
class?: string
|
||||
selectClass?: string
|
||||
fieldGroupClass?: string
|
||||
isRequired?: boolean
|
||||
}>()
|
||||
|
||||
const { placeholder = 'Pilih kabupaten/kota', errors, class: containerClass, selectClass, fieldGroupClass } = props
|
||||
|
||||
const regencyOptions = [
|
||||
{ label: 'Kab. Sidoarjo', value: '32' },
|
||||
{ label: 'Kab. Malang', value: '35' },
|
||||
{ label: 'Kab. Mojokerto', value: '31' },
|
||||
{ label: 'Kab. Lamongan', value: '30' },
|
||||
{ label: 'Kota Malang', value: '18' },
|
||||
]
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<FieldGroup :class="cn('select-field-group', fieldGroupClass, containerClass)">
|
||||
<Label
|
||||
:label-for="fieldName"
|
||||
:is-required="isRequired"
|
||||
>
|
||||
Kabupaten/Kota
|
||||
</Label>
|
||||
<Field
|
||||
:id="fieldName"
|
||||
:errors="errors"
|
||||
:class="cn('select-field-wrapper')"
|
||||
>
|
||||
<FormField
|
||||
v-slot="{ componentField }"
|
||||
:name="fieldName"
|
||||
>
|
||||
<FormItem>
|
||||
<FormControl>
|
||||
<Combobox
|
||||
:id="fieldName"
|
||||
v-bind="componentField"
|
||||
:items="regencyOptions"
|
||||
:placeholder="placeholder"
|
||||
:is-disabled="isDisabled"
|
||||
search-placeholder="Cari..."
|
||||
empty-message="Kabupaten/kota tidak ditemukan"
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
</FormField>
|
||||
</Field>
|
||||
</FieldGroup>
|
||||
</template>
|
||||
@@ -0,0 +1,64 @@
|
||||
<script setup lang="ts">
|
||||
import type { FormErrors } from '~/types/error'
|
||||
import Combobox from '~/components/pub/my-ui/form/combobox.vue'
|
||||
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 { cn } from '~/lib/utils'
|
||||
|
||||
const props = defineProps<{
|
||||
fieldName: string
|
||||
isDisabled?: boolean
|
||||
placeholder?: string
|
||||
errors?: FormErrors
|
||||
class?: string
|
||||
selectClass?: string
|
||||
fieldGroupClass?: string
|
||||
isRequired?: boolean
|
||||
}>()
|
||||
|
||||
const { placeholder = 'Pilih Kelurahan', errors, class: containerClass, selectClass, fieldGroupClass } = props
|
||||
|
||||
const villageOptions = [
|
||||
{ label: 'Lowokwaru', value: '18' },
|
||||
{ label: 'Dinoyo', value: '33' },
|
||||
{ label: 'Blimbing', value: '35' },
|
||||
{ label: 'Sawojajar', value: '36' },
|
||||
]
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<FieldGroup :class="cn('select-field-group', fieldGroupClass, containerClass)">
|
||||
<Label
|
||||
:label-for="fieldName"
|
||||
:is-required="isRequired"
|
||||
>
|
||||
Kelurahan
|
||||
</Label>
|
||||
<Field
|
||||
:id="fieldName"
|
||||
:errors="errors"
|
||||
:class="cn('select-field-wrapper')"
|
||||
>
|
||||
<FormField
|
||||
v-slot="{ componentField }"
|
||||
:name="fieldName"
|
||||
>
|
||||
<FormItem>
|
||||
<FormControl>
|
||||
<Combobox
|
||||
:id="fieldName"
|
||||
v-bind="componentField"
|
||||
:items="villageOptions"
|
||||
:placeholder="placeholder"
|
||||
:is-disabled="isDisabled"
|
||||
search-placeholder="Cari..."
|
||||
empty-message="Kelurahan tidak ditemukan"
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
</FormField>
|
||||
</Field>
|
||||
</FieldGroup>
|
||||
</template>
|
||||
@@ -0,0 +1,369 @@
|
||||
<script setup lang="ts">
|
||||
import type { FormErrors } from '~/types/error'
|
||||
import { toTypedSchema } from '@vee-validate/zod'
|
||||
import Block from '~/components/pub/my-ui/form/block.vue'
|
||||
import FieldGroup from '~/components/pub/my-ui/form/field-group.vue'
|
||||
import Field from '~/components/pub/my-ui/form/field.vue'
|
||||
import InputBase from '~/components/pub/my-ui/form/input-base.vue'
|
||||
import Label from '~/components/pub/my-ui/form/label.vue'
|
||||
import RadioResidence from './_common/radio-residence.vue'
|
||||
import SelectDistrict from './_common/select-district.vue'
|
||||
import SelectPostal from './_common/select-postal.vue'
|
||||
import SelectProvince from './_common/select-province.vue'
|
||||
import SelectRegency from './_common/select-regency.vue'
|
||||
import SelectVillage from './_common/select-village.vue'
|
||||
import { Form } from '~/components/pub/ui/form'
|
||||
|
||||
const props = defineProps<{
|
||||
title: string
|
||||
conf?: {
|
||||
withAddressName?: boolean
|
||||
}
|
||||
schema: any
|
||||
initialValues?: any
|
||||
errors?: FormErrors
|
||||
}>()
|
||||
|
||||
const formSchema = toTypedSchema(props.schema)
|
||||
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),
|
||||
})
|
||||
|
||||
// Watchers untuk cascading reset
|
||||
let isResetting = false
|
||||
|
||||
// Field dependency map for placeholder
|
||||
const fieldStates: Record<string, { dependsOn?: string; placeholder: string }> = {
|
||||
regencyId: { dependsOn: 'provinceId', placeholder: 'Pilih provinsi dahulu' },
|
||||
districtId: { dependsOn: 'regencyId', placeholder: 'Pilih kabupaten/kota dahulu' },
|
||||
villageId: { dependsOn: 'districtId', placeholder: 'Pilih kecamatan dahulu' },
|
||||
zipCode: { dependsOn: 'villageId', placeholder: 'Pilih kelurahan dahulu' },
|
||||
address: { placeholder: 'Masukkan alamat' },
|
||||
rt: { placeholder: '001' },
|
||||
rw: { placeholder: '002' },
|
||||
}
|
||||
// #region Function Helper
|
||||
function getFieldState(field: string) {
|
||||
const state = fieldStates[field]
|
||||
const isSame = formRef.value?.values?.isSameAddress === '1'
|
||||
|
||||
// Jika alamat sama, semua field kecuali provinsi disabled
|
||||
if (['address', 'rt', 'rw'].includes(field) && isSame) {
|
||||
return { placeholder: '-', disabled: true }
|
||||
}
|
||||
|
||||
// Untuk field yang tergantung pada field lain
|
||||
if (state?.dependsOn) {
|
||||
const dependencyValue = formRef.value?.values?.[state.dependsOn]
|
||||
const isDisabledByDependency = !dependencyValue
|
||||
|
||||
// Jika isSame, semua field location disabled
|
||||
if (isSame && ['regencyId', 'districtId', 'villageId', 'zipCode'].includes(field)) {
|
||||
return { placeholder: '-', disabled: true }
|
||||
}
|
||||
|
||||
if (isDisabledByDependency) {
|
||||
return { placeholder: state.placeholder, disabled: true }
|
||||
}
|
||||
}
|
||||
|
||||
// Jika isSame dan field location, disabled
|
||||
if (isSame && ['regencyId', 'districtId', 'villageId', 'zipCode'].includes(field)) {
|
||||
return { placeholder: '-', disabled: true }
|
||||
}
|
||||
|
||||
return { placeholder: state?.placeholder ?? '', disabled: false }
|
||||
}
|
||||
|
||||
// #endregion
|
||||
|
||||
// #region watch
|
||||
|
||||
// Watch provinceId changes
|
||||
watch(
|
||||
() => formRef.value?.values?.provinceId,
|
||||
(newValue, oldValue) => {
|
||||
if (isResetting || !formRef.value || newValue === oldValue) return
|
||||
|
||||
if (oldValue && newValue !== oldValue) {
|
||||
isResetting = true
|
||||
|
||||
formRef.value.setValues(
|
||||
{
|
||||
regencyId: undefined,
|
||||
districtId: undefined,
|
||||
villageId: undefined,
|
||||
zipCode: undefined,
|
||||
},
|
||||
false,
|
||||
)
|
||||
|
||||
nextTick(() => {
|
||||
isResetting = false
|
||||
})
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
// Watch regencyId changes
|
||||
watch(
|
||||
() => formRef.value?.values?.regencyId,
|
||||
(newValue, oldValue) => {
|
||||
if (isResetting || !formRef.value || newValue === oldValue) return
|
||||
|
||||
if (oldValue && newValue !== oldValue) {
|
||||
isResetting = true
|
||||
|
||||
formRef.value.setValues(
|
||||
{
|
||||
districtId: undefined,
|
||||
villageId: undefined,
|
||||
zipCode: undefined,
|
||||
},
|
||||
false,
|
||||
)
|
||||
|
||||
nextTick(() => {
|
||||
isResetting = false
|
||||
})
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
// Watch districtId changes
|
||||
watch(
|
||||
() => formRef.value?.values?.districtId,
|
||||
(newValue, oldValue) => {
|
||||
if (isResetting || !formRef.value || newValue === oldValue) return
|
||||
|
||||
if (oldValue && newValue !== oldValue) {
|
||||
isResetting = true
|
||||
|
||||
formRef.value.setValues(
|
||||
{
|
||||
villageId: undefined,
|
||||
zipCode: undefined,
|
||||
},
|
||||
false,
|
||||
)
|
||||
|
||||
nextTick(() => {
|
||||
isResetting = false
|
||||
})
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
// Watch villageId changes
|
||||
watch(
|
||||
() => formRef.value?.values?.villageId,
|
||||
(newValue, oldValue) => {
|
||||
if (isResetting || !formRef.value || newValue === oldValue) return
|
||||
|
||||
if (oldValue && newValue !== oldValue) {
|
||||
isResetting = true
|
||||
|
||||
formRef.value.setValues(
|
||||
{
|
||||
zipCode: undefined,
|
||||
},
|
||||
false,
|
||||
)
|
||||
|
||||
nextTick(() => {
|
||||
isResetting = false
|
||||
})
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
// Watch isSameAddress changes untuk trigger validasi
|
||||
watch(
|
||||
() => formRef.value?.values?.isSameAddress,
|
||||
(newValue, oldValue) => {
|
||||
if (!formRef.value || newValue === oldValue) return
|
||||
|
||||
// Ketika berubah dari '1' ke '0', clear empty strings dan trigger validasi
|
||||
if (oldValue === '1' && newValue === '0') {
|
||||
nextTick(() => {
|
||||
// Set empty strings ke undefined untuk trigger required validation
|
||||
const currentValues = formRef.value.values
|
||||
const updatedValues = { ...currentValues }
|
||||
|
||||
// Convert empty strings to undefined untuk field yang sekarang required
|
||||
if (updatedValues.provinceId === '') updatedValues.provinceId = undefined
|
||||
if (updatedValues.regencyId === '') updatedValues.regencyId = undefined
|
||||
if (updatedValues.districtId === '') updatedValues.districtId = undefined
|
||||
if (updatedValues.villageId === '') updatedValues.villageId = undefined
|
||||
if (updatedValues.zipCode === '') updatedValues.zipCode = undefined
|
||||
if (updatedValues.address === '') updatedValues.address = undefined
|
||||
|
||||
// Update values dan trigger validasi
|
||||
formRef.value.setValues(updatedValues, false)
|
||||
|
||||
// Trigger validasi untuk menampilkan error
|
||||
setTimeout(() => {
|
||||
formRef.value?.validate()
|
||||
}, 50)
|
||||
})
|
||||
}
|
||||
|
||||
// Ketika berubah dari '0' ke '1', clear error messages
|
||||
if (oldValue === '0' && newValue === '1') {
|
||||
nextTick(() => {
|
||||
// Clear error messages untuk field yang tidak lagi required
|
||||
formRef.value?.setFieldError('provinceId', undefined)
|
||||
formRef.value?.setFieldError('regencyId', undefined)
|
||||
formRef.value?.setFieldError('districtId', undefined)
|
||||
formRef.value?.setFieldError('villageId', undefined)
|
||||
formRef.value?.setFieldError('zipCode', undefined)
|
||||
formRef.value?.setFieldError('address', undefined)
|
||||
formRef.value?.setFieldError('rt', undefined)
|
||||
formRef.value?.setFieldError('rw', undefined)
|
||||
})
|
||||
}
|
||||
},
|
||||
)
|
||||
// #endregion
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Form
|
||||
ref="formRef"
|
||||
v-slot="{ values }"
|
||||
as=""
|
||||
keep-values
|
||||
:validation-schema="formSchema"
|
||||
:validate-on-mount="false"
|
||||
validation-mode="onSubmit"
|
||||
:initial-values="initialValues ?? { isSameAddress: '1' }"
|
||||
>
|
||||
<div>
|
||||
<p
|
||||
v-if="props.title"
|
||||
class="text-md mb-2 mt-1 font-semibold"
|
||||
>
|
||||
{{ props.title }}
|
||||
</p>
|
||||
</div>
|
||||
<div class="mb-5 border-b border-b-slate-300 pb-3 text-lg xl:text-xl">
|
||||
<div class="grid grid-cols-1 gap-4 md:grid-cols-2 lg:grid-cols-2 xl:grid-cols-2">
|
||||
<!-- LocationType -->
|
||||
<FieldGroup v-if="conf?.withAddressName">
|
||||
<Label label-for="locationType">Jenis Alamat</Label>
|
||||
<Field
|
||||
id="locationType"
|
||||
:errors="errors"
|
||||
>
|
||||
<FormField
|
||||
v-slot="{ componentField }"
|
||||
name="locationType"
|
||||
>
|
||||
<FormItem>
|
||||
<FormControl>
|
||||
<Select
|
||||
id="locationType"
|
||||
v-bind="componentField"
|
||||
:items="[
|
||||
{ label: 'Rumah', value: 'rumah' },
|
||||
{ label: 'Kantor', value: 'kantor' },
|
||||
{ label: 'Lainnya', value: 'lainnya' },
|
||||
]"
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
</FormField>
|
||||
</Field>
|
||||
</FieldGroup>
|
||||
<RadioResidence field-name="isSameAddress" />
|
||||
<Block></Block>
|
||||
|
||||
<div class="flex-row gap-2 md:flex">
|
||||
<div class="min-w-0 flex-1">
|
||||
<SelectProvince
|
||||
field-name="provinceId"
|
||||
placeholder="Pilih"
|
||||
:is-disabled="values.isSameAddress === '1'"
|
||||
:is-required="values.isSameAddress !== '1'"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="min-w-0 flex-1">
|
||||
<SelectRegency
|
||||
field-name="regencyId"
|
||||
:placeholder="getFieldState('regencyId').placeholder"
|
||||
:is-disabled="getFieldState('regencyId').disabled || !values.provinceId"
|
||||
:is-required="values.isSameAddress !== '1'"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex-row gap-2 md:flex">
|
||||
<div class="min-w-0 flex-1">
|
||||
<SelectDistrict
|
||||
field-name="districtId"
|
||||
:placeholder="getFieldState('districtId').placeholder"
|
||||
:is-disabled="getFieldState('districtId').disabled || !values.regencyId"
|
||||
:is-required="values.isSameAddress !== '1'"
|
||||
/>
|
||||
</div>
|
||||
<div class="min-w-0 flex-1">
|
||||
<SelectVillage
|
||||
field-name="villageId"
|
||||
:placeholder="getFieldState('villageId').placeholder"
|
||||
:is-disabled="getFieldState('villageId').disabled || !values.districtId"
|
||||
:is-required="values.isSameAddress !== '1'"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<InputBase
|
||||
field-name="address"
|
||||
label="Alamat"
|
||||
:placeholder="getFieldState('address').placeholder"
|
||||
:is-disabled="getFieldState('address').disabled"
|
||||
:errors="errors"
|
||||
:is-required="values.isSameAddress !== '1'"
|
||||
/>
|
||||
<div class="flex-row gap-2 md:flex">
|
||||
<div class="min-w-0 flex-1">
|
||||
<InputBase
|
||||
field-name="rt"
|
||||
label="RT"
|
||||
:errors="errors"
|
||||
numeric-only
|
||||
:max-length="3"
|
||||
:placeholder="getFieldState('rt').placeholder"
|
||||
:is-disabled="getFieldState('rt').disabled"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="min-w-0 flex-1">
|
||||
<InputBase
|
||||
field-name="rw"
|
||||
label="RW"
|
||||
:placeholder="getFieldState('rw').placeholder"
|
||||
:is-disabled="getFieldState('rw').disabled"
|
||||
:errors="errors"
|
||||
:max-length="3"
|
||||
numeric-only
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="min-w-0 flex-[2]">
|
||||
<SelectPostal
|
||||
field-name="zipCode"
|
||||
:placeholder="getFieldState('zipCode').placeholder"
|
||||
:is-disabled="getFieldState('zipCode').disabled || !values.villageId"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Form>
|
||||
</template>
|
||||
@@ -1,171 +1,268 @@
|
||||
<script setup lang="ts">
|
||||
import type { FormErrors } from '~/types/error'
|
||||
import { toTypedSchema } from '@vee-validate/zod'
|
||||
import Block from '~/components/pub/my-ui/form/block.vue'
|
||||
import Combobox from '~/components/pub/my-ui/form/combobox.vue'
|
||||
import FieldGroup from '~/components/pub/my-ui/form/field-group.vue'
|
||||
import Field from '~/components/pub/my-ui/form/field.vue'
|
||||
import InputBase from '~/components/pub/my-ui/form/input-base.vue'
|
||||
import Label from '~/components/pub/my-ui/form/label.vue'
|
||||
import { educationCodes, genderCodes, occupationCodes, religionCodes } from '~/lib/constants'
|
||||
import { mapToComboboxOptList } from '~/lib/utils'
|
||||
import SelectDistrict from './_common/select-district.vue'
|
||||
import SelectPostal from './_common/select-postal.vue'
|
||||
import SelectProvince from './_common/select-province.vue'
|
||||
import SelectRegency from './_common/select-regency.vue'
|
||||
import SelectVillage from './_common/select-village.vue'
|
||||
import { Form } from '~/components/pub/ui/form'
|
||||
|
||||
interface DivisionFormData {
|
||||
name: string
|
||||
code: string
|
||||
parentId: string
|
||||
}
|
||||
|
||||
const props = defineProps<{
|
||||
division: {
|
||||
msg: {
|
||||
placeholder: string
|
||||
search: string
|
||||
empty: string
|
||||
}
|
||||
title: string
|
||||
conf?: {
|
||||
withAddressName?: boolean
|
||||
}
|
||||
items: {
|
||||
value: string
|
||||
label: string
|
||||
code: string
|
||||
}[]
|
||||
schema: any
|
||||
initialValues?: Partial<DivisionFormData>
|
||||
initialValues?: any
|
||||
errors?: FormErrors
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
submit: [values: DivisionFormData, resetForm: () => void]
|
||||
cancel: [resetForm: () => void]
|
||||
}>()
|
||||
|
||||
const educationOpts = mapToComboboxOptList(educationCodes)
|
||||
const occupationOpts = mapToComboboxOptList(occupationCodes)
|
||||
const religionOpts = mapToComboboxOptList(religionCodes)
|
||||
const genderOpts = mapToComboboxOptList(genderCodes)
|
||||
|
||||
const formSchema = toTypedSchema(props.schema)
|
||||
const formRef = ref()
|
||||
|
||||
// Form submission handler
|
||||
function onSubmitForm(values: any, { resetForm }: { resetForm: () => void }) {
|
||||
const formData: DivisionFormData = {
|
||||
name: values.name || '',
|
||||
code: values.code || '',
|
||||
parentId: values.parentId || '',
|
||||
}
|
||||
emit('submit', formData, resetForm)
|
||||
}
|
||||
defineExpose({
|
||||
validate: () => formRef.value?.validate(),
|
||||
resetForm: () => formRef.value?.resetForm(),
|
||||
setValues: (values: any, shouldValidate = true) => formRef.value?.setValues(values, shouldValidate),
|
||||
values: computed(() => formRef.value?.values),
|
||||
})
|
||||
|
||||
// Watchers untuk cascading reset
|
||||
let isResetting = false
|
||||
|
||||
// #region Watch provinceId changes
|
||||
|
||||
watch(
|
||||
() => formRef.value?.values?.provinceId,
|
||||
(newValue, oldValue) => {
|
||||
if (isResetting || !formRef.value || newValue === oldValue) return
|
||||
|
||||
if (oldValue && newValue !== oldValue) {
|
||||
isResetting = true
|
||||
|
||||
formRef.value.setValues(
|
||||
{
|
||||
regencyId: undefined,
|
||||
districtId: undefined,
|
||||
villageId: undefined,
|
||||
zipCode: undefined,
|
||||
},
|
||||
false,
|
||||
)
|
||||
|
||||
nextTick(() => {
|
||||
isResetting = false
|
||||
})
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
// Watch regencyId changes
|
||||
watch(
|
||||
() => formRef.value?.values?.regencyId,
|
||||
(newValue, oldValue) => {
|
||||
if (isResetting || !formRef.value || newValue === oldValue) return
|
||||
|
||||
if (oldValue && newValue !== oldValue) {
|
||||
isResetting = true
|
||||
|
||||
formRef.value.setValues(
|
||||
{
|
||||
districtId: undefined,
|
||||
villageId: undefined,
|
||||
zipCode: undefined,
|
||||
},
|
||||
false,
|
||||
)
|
||||
|
||||
nextTick(() => {
|
||||
isResetting = false
|
||||
})
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
// Watch districtId changes
|
||||
watch(
|
||||
() => formRef.value?.values?.districtId,
|
||||
(newValue, oldValue) => {
|
||||
if (isResetting || !formRef.value || newValue === oldValue) return
|
||||
|
||||
if (oldValue && newValue !== oldValue) {
|
||||
isResetting = true
|
||||
|
||||
formRef.value.setValues(
|
||||
{
|
||||
villageId: undefined,
|
||||
zipCode: undefined,
|
||||
},
|
||||
false,
|
||||
)
|
||||
|
||||
nextTick(() => {
|
||||
isResetting = false
|
||||
})
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
// Watch villageId changes
|
||||
watch(
|
||||
() => formRef.value?.values?.villageId,
|
||||
(newValue, oldValue) => {
|
||||
if (isResetting || !formRef.value || newValue === oldValue) return
|
||||
|
||||
if (oldValue && newValue !== oldValue) {
|
||||
isResetting = true
|
||||
|
||||
formRef.value.setValues(
|
||||
{
|
||||
zipCode: undefined,
|
||||
},
|
||||
false,
|
||||
)
|
||||
|
||||
nextTick(() => {
|
||||
isResetting = false
|
||||
})
|
||||
}
|
||||
},
|
||||
)
|
||||
// #endregion
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Form
|
||||
v-slot="{ handleSubmit, resetForm }"
|
||||
ref="formRef"
|
||||
v-slot="{ values }"
|
||||
as=""
|
||||
keep-values
|
||||
:validation-schema="formSchema"
|
||||
:initial-values="initialValues"
|
||||
:validate-on-mount="false"
|
||||
validation-mode="onSubmit"
|
||||
:initial-values="initialValues ? initialValues : {}"
|
||||
>
|
||||
<form id="entry-form" @submit="handleSubmit($event, (values) => onSubmitForm(values, { resetForm }))">
|
||||
<div class="mb-5 border-b border-b-slate-300 pb-3 text-lg xl:text-xl">
|
||||
<div class="flex flex-col justify-between">
|
||||
<Block>
|
||||
<!-- LocationType -->
|
||||
<FieldGroup>
|
||||
<Label label-for="locationType">Jenis Alamat</Label>
|
||||
<Field id="locationType" :errors="errors">
|
||||
<FormField v-slot="{ componentField }" name="locationType">
|
||||
<FormItem>
|
||||
<FormControl>
|
||||
<Select id="locationType" v-bind="componentField" :items="genderOpts" />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
</FormField>
|
||||
</Field>
|
||||
</FieldGroup>
|
||||
<div>
|
||||
<p
|
||||
v-if="props.title"
|
||||
class="text-md mb-2 mt-1 font-semibold"
|
||||
>
|
||||
{{ props.title }}
|
||||
</p>
|
||||
</div>
|
||||
<div class="mb-5 border-b border-b-slate-300 pb-3 text-lg xl:text-xl">
|
||||
<div class="grid grid-cols-1 gap-4 md:grid-cols-2 lg:grid-cols-2 xl:grid-cols-2">
|
||||
<!-- LocationType -->
|
||||
<FieldGroup v-if="conf?.withAddressName">
|
||||
<Label label-for="locationType">Jenis Alamat</Label>
|
||||
<Field
|
||||
id="locationType"
|
||||
:errors="errors"
|
||||
>
|
||||
<FormField
|
||||
v-slot="{ componentField }"
|
||||
name="locationType"
|
||||
>
|
||||
<FormItem>
|
||||
<FormControl>
|
||||
<Select
|
||||
id="locationType"
|
||||
v-bind="componentField"
|
||||
:items="[
|
||||
{ label: 'Rumah', value: 'rumah' },
|
||||
{ label: 'Kantor', value: 'kantor' },
|
||||
{ label: 'Lainnya', value: 'lainnya' },
|
||||
]"
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
</FormField>
|
||||
</Field>
|
||||
</FieldGroup>
|
||||
|
||||
<!-- Address -->
|
||||
<FieldGroup>
|
||||
<Label label-for="address">Alamat</Label>
|
||||
<Field id="address" :errors="errors">
|
||||
<FormField v-slot="{ componentField }" name="address">
|
||||
<FormItem>
|
||||
<FormControl>
|
||||
<Input
|
||||
id="address"
|
||||
type="text"
|
||||
placeholder="Masukkan alamat lengkap"
|
||||
autocomplete="off"
|
||||
v-bind="componentField"
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
</FormField>
|
||||
</Field>
|
||||
</FieldGroup>
|
||||
<div class="flex-row gap-2 md:flex">
|
||||
<div class="min-w-0 flex-1">
|
||||
<SelectProvince
|
||||
field-name="provinceId"
|
||||
placeholder="Pilih"
|
||||
is-required
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Rt -->
|
||||
<FieldGroup>
|
||||
<Label label-for="rt">RT</Label>
|
||||
<Field id="rt" :errors="errors">
|
||||
<FormField v-slot="{ componentField }" name="rt">
|
||||
<FormItem>
|
||||
<FormControl>
|
||||
<Input
|
||||
id="rt"
|
||||
type="text"
|
||||
maxlength="2"
|
||||
placeholder="01"
|
||||
autocomplete="off"
|
||||
v-bind="componentField"
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
</FormField>
|
||||
</Field>
|
||||
</FieldGroup>
|
||||
<div class="min-w-0 flex-1">
|
||||
<SelectRegency
|
||||
field-name="regencyId"
|
||||
placeholder="Pilih provinsi dahulu"
|
||||
:is-disabled="!values.provinceId"
|
||||
is-required
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Rw -->
|
||||
<FieldGroup>
|
||||
<Label label-for="rw">RW</Label>
|
||||
<Field id="rw" :errors="errors">
|
||||
<FormField v-slot="{ componentField }" name="rw">
|
||||
<FormItem>
|
||||
<FormControl>
|
||||
<Input
|
||||
id="rw"
|
||||
type="text"
|
||||
maxlength="2"
|
||||
placeholder="01"
|
||||
autocomplete="off"
|
||||
v-bind="componentField"
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
</FormField>
|
||||
</Field>
|
||||
</FieldGroup>
|
||||
<div class="flex-row gap-2 md:flex">
|
||||
<div class="min-w-0 flex-1">
|
||||
<SelectDistrict
|
||||
field-name="districtId"
|
||||
placeholder="Pilih kabupaten/kota dahulu"
|
||||
:is-disabled="!values.regencyId"
|
||||
is-required
|
||||
/>
|
||||
</div>
|
||||
<div class="min-w-0 flex-1">
|
||||
<SelectVillage
|
||||
field-name="villageId"
|
||||
placeholder="Pilih kecamatan dahulu"
|
||||
:is-disabled="!values.districtId"
|
||||
is-required
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Village_Code -->
|
||||
<FieldGroup>
|
||||
<Label label-for="villageCode">Desa/Kelurahan</Label>
|
||||
<Field id="villageCode" :errors="errors">
|
||||
<FormField v-slot="{ componentField }" name="villageCode">
|
||||
<FormItem>
|
||||
<FormControl>
|
||||
<Combobox id="villageCode" v-bind="componentField" :items="genderOpts" />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
</FormField>
|
||||
</Field>
|
||||
</FieldGroup>
|
||||
</Block>
|
||||
<InputBase
|
||||
field-name="address"
|
||||
label="Alamat"
|
||||
placeholder="Masukkan alamat"
|
||||
:errors="errors"
|
||||
is-required
|
||||
/>
|
||||
<div class="flex-row gap-2 md:flex">
|
||||
<div class="min-w-0 flex-1">
|
||||
<InputBase
|
||||
field-name="rt"
|
||||
label="RT"
|
||||
placeholder="001"
|
||||
:errors="errors"
|
||||
numeric-only
|
||||
:max-length="3"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="min-w-0 flex-1">
|
||||
<InputBase
|
||||
field-name="rw"
|
||||
label="RW"
|
||||
placeholder="002"
|
||||
:errors="errors"
|
||||
:max-length="3"
|
||||
numeric-only
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="min-w-0 flex-[2]">
|
||||
<SelectPostal
|
||||
field-name="zipCode"
|
||||
placeholder="Pilih kelurahan dahulu"
|
||||
:is-disabled="!values.villageId"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</Form>
|
||||
</template>
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
<script setup lang="ts">
|
||||
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'
|
||||
|
||||
const props = defineProps<{
|
||||
fieldName: string
|
||||
label: string
|
||||
placeholder?: string
|
||||
errors?: FormErrors
|
||||
class?: string
|
||||
selectClass?: string
|
||||
fieldGroupClass?: string
|
||||
labelClass?: string
|
||||
isRequired?: boolean
|
||||
isDisabled?: boolean
|
||||
}>()
|
||||
|
||||
const { fieldName = 'phoneNumber', errors, class: containerClass, selectClass, fieldGroupClass, labelClass } = props
|
||||
|
||||
const contactOptions = [
|
||||
{ label: 'Nomor HP', value: 'phoneNumber' },
|
||||
{ label: 'Nomor Telepon Kantor', value: 'officePhoneNumber' },
|
||||
{ label: 'Nomor Telepon Rumah', value: 'homePhoneNumber' },
|
||||
]
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<FieldGroup :class="cn('select-field-group', fieldGroupClass, containerClass)">
|
||||
<Label
|
||||
:label-for="fieldName"
|
||||
:class="cn('select-field-label', labelClass)"
|
||||
:is-required="isRequired && !isDisabled"
|
||||
>
|
||||
{{ label }}
|
||||
</Label>
|
||||
<Field
|
||||
:id="fieldName"
|
||||
:errors="errors"
|
||||
:class="cn('select-field-wrapper')"
|
||||
>
|
||||
<FormField
|
||||
v-slot="{ componentField }"
|
||||
:name="fieldName"
|
||||
>
|
||||
<FormItem>
|
||||
<FormControl>
|
||||
<Select
|
||||
:id="fieldName"
|
||||
v-bind="componentField"
|
||||
:is-disabled="isDisabled"
|
||||
:items="contactOptions"
|
||||
:placeholder="placeholder || 'Pilih jenis kontak'"
|
||||
:preserve-order="true"
|
||||
:class="
|
||||
cn(
|
||||
'min-w-[190px] text-sm transition-all duration-200 focus:outline-none focus:ring-1 focus:ring-black focus:ring-offset-0',
|
||||
selectClass,
|
||||
)
|
||||
"
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
</FormField>
|
||||
</Field>
|
||||
</FieldGroup>
|
||||
</template>
|
||||
@@ -1,112 +1,110 @@
|
||||
<script setup lang="ts">
|
||||
import type { FormErrors } from '~/types/error'
|
||||
import { toTypedSchema } from '@vee-validate/zod'
|
||||
import Block from '~/components/pub/my-ui/form/block.vue'
|
||||
import Combobox from '~/components/pub/my-ui/form/combobox.vue'
|
||||
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 { educationCodes, genderCodes, occupationCodes, religionCodes } from '~/lib/constants'
|
||||
import { mapToComboboxOptList } from '~/lib/utils'
|
||||
import { FieldArray } from 'vee-validate'
|
||||
import InputBase from '~/components/pub/my-ui/form/input-base.vue'
|
||||
import SelectContactType from './_common/select-contact-type.vue'
|
||||
import { Form } from '~/components/pub/ui/form'
|
||||
|
||||
interface DivisionFormData {
|
||||
name: string
|
||||
code: string
|
||||
parentId: string
|
||||
}
|
||||
|
||||
const props = defineProps<{
|
||||
division: {
|
||||
msg: {
|
||||
placeholder: string
|
||||
search: string
|
||||
empty: string
|
||||
}
|
||||
}
|
||||
items: {
|
||||
value: string
|
||||
label: string
|
||||
code: string
|
||||
}[]
|
||||
title: string
|
||||
schema: any
|
||||
initialValues?: Partial<DivisionFormData>
|
||||
contactLimit: number
|
||||
initialValues?: any
|
||||
errors?: FormErrors
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
submit: [values: DivisionFormData, resetForm: () => void]
|
||||
cancel: [resetForm: () => void]
|
||||
}>()
|
||||
|
||||
const educationOpts = mapToComboboxOptList(educationCodes)
|
||||
const occupationOpts = mapToComboboxOptList(occupationCodes)
|
||||
const religionOpts = mapToComboboxOptList(religionCodes)
|
||||
const genderOpts = mapToComboboxOptList(genderCodes)
|
||||
|
||||
const { contactLimit = 5 } = props
|
||||
const formSchema = toTypedSchema(props.schema)
|
||||
const formRef = ref()
|
||||
|
||||
// Form submission handler
|
||||
function onSubmitForm(values: any, { resetForm }: { resetForm: () => void }) {
|
||||
const formData: DivisionFormData = {
|
||||
name: values.name || '',
|
||||
code: values.code || '',
|
||||
parentId: values.parentId || '',
|
||||
}
|
||||
emit('submit', formData, resetForm)
|
||||
}
|
||||
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
|
||||
v-slot="{ handleSubmit, resetForm }"
|
||||
ref="formRef"
|
||||
as=""
|
||||
keep-values
|
||||
:validation-schema="formSchema"
|
||||
:initial-values="initialValues"
|
||||
:validate-on-mount="false"
|
||||
validation-mode="onSubmit"
|
||||
:initial-values="initialValues || { contacts: [{ contactType: '', contactNumber: '' }] }"
|
||||
>
|
||||
<form id="entry-form" @submit="handleSubmit($event, (values) => onSubmitForm(values, { resetForm }))">
|
||||
<div class="mb-5 border-b border-b-slate-300 pb-3 text-lg xl:text-xl">
|
||||
<div class="flex flex-col justify-between">
|
||||
<Block>
|
||||
<!-- LocationType -->
|
||||
<FieldGroup>
|
||||
<Label label-for="type">Tipe</Label>
|
||||
<Field id="type" :errors="errors">
|
||||
<FormField v-slot="{ componentField }" name="type">
|
||||
<FormItem>
|
||||
<FormControl>
|
||||
<Select id="type" v-bind="componentField" :items="genderOpts" />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
</FormField>
|
||||
</Field>
|
||||
</FieldGroup>
|
||||
<div>
|
||||
<p
|
||||
v-if="props.title"
|
||||
class="text-md mb-2 mt-1 font-semibold"
|
||||
>
|
||||
{{ props.title || 'Kontak Pasien' }}
|
||||
</p>
|
||||
</div>
|
||||
<div class="mb-5 border-b border-b-slate-300 pb-3 text-lg xl:text-xl">
|
||||
<div class="grid grid-cols-1 gap-4 md:grid-cols-2 lg:grid-cols-2 xl:grid-cols-3">
|
||||
<FieldArray
|
||||
v-slot="{ fields, push, remove }"
|
||||
name="contacts"
|
||||
>
|
||||
<div
|
||||
v-for="(field, idx) in fields"
|
||||
:key="field.key"
|
||||
class="flex-row gap-2 md:flex"
|
||||
>
|
||||
<div class="min-w-0 flex-[2]">
|
||||
<SelectContactType
|
||||
:field-name="`contacts[${idx}].contactType`"
|
||||
:label="`Kontak ${idx + 1}`"
|
||||
:is-required="idx === 0"
|
||||
/>
|
||||
</div>
|
||||
<div class="min-w-0 flex-[1.5]">
|
||||
<InputBase
|
||||
:field-name="`contacts[${idx}].contactNumber`"
|
||||
placeholder="081234567890"
|
||||
label="No"
|
||||
numeric-only
|
||||
:max-length="15"
|
||||
:is-required="idx === 0"
|
||||
/>
|
||||
</div>
|
||||
<div class="flex-1 self-start md:pt-8">
|
||||
<Button
|
||||
type="button"
|
||||
variant="outline"
|
||||
size="icon"
|
||||
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>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Address -->
|
||||
<FieldGroup>
|
||||
<Label label-for="value">Value</Label>
|
||||
<Field id="value" :errors="errors">
|
||||
<FormField v-slot="{ componentField }" name="value">
|
||||
<FormItem>
|
||||
<FormControl>
|
||||
<Input
|
||||
id="value"
|
||||
type="text"
|
||||
placeholder="Masukkan alamat lengkap"
|
||||
autocomplete="off"
|
||||
v-bind="componentField"
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
</FormField>
|
||||
</Field>
|
||||
</FieldGroup>
|
||||
</Block>
|
||||
</div>
|
||||
<div class="self-center pt-3">
|
||||
<Button
|
||||
:disabled="fields.length >= contactLimit"
|
||||
type="button"
|
||||
variant="outline"
|
||||
@click="push({ contactType: '', contactNumber: '' })"
|
||||
>
|
||||
<Icon
|
||||
name="i-lucide-plus"
|
||||
class="h-5 w-5"
|
||||
/>
|
||||
Tambah Kontak
|
||||
</Button>
|
||||
</div>
|
||||
</FieldArray>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</Form>
|
||||
</template>
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
<script setup lang="ts">
|
||||
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 Select from '~/components/pub/my-ui/form/select.vue'
|
||||
import { cn } from '~/lib/utils'
|
||||
|
||||
const props = defineProps<{
|
||||
fieldName: string
|
||||
placeholder?: string
|
||||
errors?: FormErrors
|
||||
class?: string
|
||||
selectClass?: string
|
||||
fieldGroupClass?: string
|
||||
labelClass?: string
|
||||
isDisabled?: boolean
|
||||
}>()
|
||||
|
||||
const { fieldName = 'phoneNumber', errors, class: containerClass, selectClass, fieldGroupClass } = props
|
||||
|
||||
const emergencyContactOptions = [
|
||||
{ label: 'Diri sendiri', value: 'self' },
|
||||
{ label: 'Orang Tua', value: 'parent' },
|
||||
{ label: 'Anak', value: 'child' },
|
||||
{ label: 'Keluarga lain', value: 'relative' },
|
||||
{ label: 'Petugas instansi lainnya', value: 'institution_officer' },
|
||||
{ label: 'Petugas kesehatan', value: 'health_officer' },
|
||||
{ label: 'Lainnya', value: 'other', priority: -1 },
|
||||
]
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<FieldGroup :class="cn('select-field-group', fieldGroupClass, containerClass)">
|
||||
<Field
|
||||
:id="fieldName"
|
||||
:errors="errors"
|
||||
:class="cn('select-field-wrapper')"
|
||||
>
|
||||
<FormField
|
||||
v-slot="{ componentField }"
|
||||
:name="fieldName"
|
||||
>
|
||||
<FormItem>
|
||||
<FormControl>
|
||||
<Select
|
||||
:id="fieldName"
|
||||
v-bind="componentField"
|
||||
:is-disabled="isDisabled"
|
||||
:items="emergencyContactOptions"
|
||||
:placeholder="placeholder"
|
||||
:preserve-order="true"
|
||||
:class="
|
||||
cn(
|
||||
'text-sm transition-all duration-200 focus:outline-none focus:ring-1 focus:ring-black focus:ring-offset-0',
|
||||
selectClass,
|
||||
)
|
||||
"
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
</FormField>
|
||||
</Field>
|
||||
</FieldGroup>
|
||||
</template>
|
||||
@@ -0,0 +1,22 @@
|
||||
<script setup lang="ts">
|
||||
import { TableHead } from '~/components/pub/ui/table'
|
||||
import { cn } from '~/lib/utils'
|
||||
|
||||
const props = defineProps<{
|
||||
class?: string
|
||||
label?: string
|
||||
isRequired?: boolean
|
||||
}>()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<TableHead :class="cn('text-sm font-normal text-black dark:text-white', props.class)">
|
||||
{{ label || '' }}
|
||||
<span
|
||||
v-if="isRequired"
|
||||
class="text-red-600"
|
||||
>
|
||||
*
|
||||
</span>
|
||||
</TableHead>
|
||||
</template>
|
||||
@@ -1,235 +1,155 @@
|
||||
<script setup lang="ts">
|
||||
import type { FormErrors } from '~/types/error'
|
||||
import { toTypedSchema } from '@vee-validate/zod'
|
||||
import Block from '~/components/pub/my-ui/form/block.vue'
|
||||
import Combobox from '~/components/pub/my-ui/form/combobox.vue'
|
||||
import FieldGroup from '~/components/pub/my-ui/form/field-group.vue'
|
||||
import Field from '~/components/pub/my-ui/form/field.vue'
|
||||
import { FieldArray } from 'vee-validate'
|
||||
import InputBase from '~/components/pub/my-ui/form/input-base.vue'
|
||||
import Label from '~/components/pub/my-ui/form/label.vue'
|
||||
import { educationCodes, genderCodes, occupationCodes, religionCodes, relationshipCodes } from '~/lib/constants'
|
||||
import { mapToComboboxOptList } from '~/lib/utils'
|
||||
import SelectContactRelation from './_common/select-relations.vue'
|
||||
import { Form } from '~/components/pub/ui/form'
|
||||
|
||||
interface DivisionFormData {
|
||||
name: string
|
||||
code: string
|
||||
parentId: string
|
||||
}
|
||||
|
||||
const props = defineProps<{
|
||||
division: {
|
||||
msg: {
|
||||
placeholder: string
|
||||
search: string
|
||||
empty: string
|
||||
}
|
||||
}
|
||||
items: {
|
||||
value: string
|
||||
label: string
|
||||
code: string
|
||||
}[]
|
||||
title: string
|
||||
schema: any
|
||||
initialValues?: Partial<DivisionFormData>
|
||||
initialValues?: any
|
||||
errors?: FormErrors
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
submit: [values: DivisionFormData, resetForm: () => void]
|
||||
cancel: [resetForm: () => void]
|
||||
}>()
|
||||
|
||||
const relationshipOpts = mapToComboboxOptList(relationshipCodes)
|
||||
const educationOpts = mapToComboboxOptList(educationCodes)
|
||||
const occupationOpts = mapToComboboxOptList(occupationCodes)
|
||||
const genderOpts = mapToComboboxOptList(genderCodes)
|
||||
|
||||
const formSchema = toTypedSchema(props.schema)
|
||||
const formRef = ref()
|
||||
|
||||
// Form submission handler
|
||||
function onSubmitForm(values: any, { resetForm }: { resetForm: () => void }) {
|
||||
const formData: DivisionFormData = {
|
||||
name: values.name || '',
|
||||
code: values.code || '',
|
||||
parentId: values.parentId || '',
|
||||
}
|
||||
emit('submit', formData, resetForm)
|
||||
}
|
||||
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
|
||||
v-slot="{ handleSubmit, resetForm }"
|
||||
ref="formRef"
|
||||
as=""
|
||||
keep-values
|
||||
:validation-schema="formSchema"
|
||||
:initial-values="initialValues"
|
||||
:validate-on-mount="false"
|
||||
validation-mode="onSubmit"
|
||||
:initial-values="initialValues || { contacts: [{ relation: '', name: '', address: '', phone: '' }] }"
|
||||
>
|
||||
<form id="entry-form" @submit="handleSubmit($event, (values) => onSubmitForm(values, { resetForm }))">
|
||||
<div class="mb-5 border-b border-b-slate-300 pb-3 text-lg xl:text-xl">
|
||||
<div class="flex flex-col justify-between">
|
||||
<Block>
|
||||
<!-- Relationship_Code -->
|
||||
<FieldGroup>
|
||||
<Label label-for="relationshipCode">Hubungan</Label>
|
||||
<Field id="relationshipCode" :errors="errors">
|
||||
<FormField v-slot="{ componentField }" name="relationshipCode">
|
||||
<FormItem>
|
||||
<FormControl>
|
||||
<Select id="relationshipCode" v-bind="componentField" :items="relationshipOpts" />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
</FormField>
|
||||
</Field>
|
||||
</FieldGroup>
|
||||
<div>
|
||||
<p
|
||||
v-if="props.title"
|
||||
class="text-md mb-2 mt-1 font-semibold"
|
||||
>
|
||||
{{ props.title || 'Kontak Pasien' }}
|
||||
</p>
|
||||
</div>
|
||||
<div class="mb-5 space-y-4">
|
||||
<FieldArray
|
||||
v-slot="{ fields, push, remove }"
|
||||
name="contacts"
|
||||
>
|
||||
<div class="space-y-4">
|
||||
<div
|
||||
v-for="(field, idx) in fields"
|
||||
:key="field.key"
|
||||
class="rounded-lg border border-gray-200 bg-gray-50/50 p-4 dark:border-gray-700 dark:bg-gray-800/50"
|
||||
>
|
||||
<div class="mb-3 flex items-center justify-between">
|
||||
<h4 class="text-sm font-medium text-gray-700 dark:text-gray-300">Penanggung Jawab {{ idx + 1 }}</h4>
|
||||
<Button
|
||||
v-if="idx !== 0"
|
||||
type="button"
|
||||
variant="outline"
|
||||
size="sm"
|
||||
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"
|
||||
@click="remove(idx)"
|
||||
>
|
||||
<Icon
|
||||
name="i-lucide-trash-2"
|
||||
class="h-4 w-4"
|
||||
/>
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<!-- Name -->
|
||||
<FieldGroup>
|
||||
<Label label-for="relativeName">Nama</Label>
|
||||
<Field id="relativeName" :errors="errors">
|
||||
<FormField v-slot="{ componentField }" name="relativeName">
|
||||
<FormItem>
|
||||
<FormControl>
|
||||
<Input
|
||||
id="relativeName"
|
||||
type="text"
|
||||
placeholder="Masukkan nama kerabat"
|
||||
autocomplete="off"
|
||||
v-bind="componentField"
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
</FormField>
|
||||
</Field>
|
||||
</FieldGroup>
|
||||
<div class="grid grid-cols-1 gap-4 md:grid-cols-2 lg:grid-cols-4">
|
||||
<div class="space-y-2">
|
||||
<Label
|
||||
class="text-sm font-medium text-gray-700 dark:text-gray-300"
|
||||
is-required
|
||||
>
|
||||
Hubungan dengan Pasien
|
||||
</Label>
|
||||
<SelectContactRelation
|
||||
:field-name="`contacts[${idx}].relation`"
|
||||
placeholder="Pilih"
|
||||
:errors="errors"
|
||||
field-group-class="mb-0"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Address -->
|
||||
<FieldGroup>
|
||||
<Label label-for="relativeAddress">Alamat</Label>
|
||||
<Field id="relativeAddress" :errors="errors">
|
||||
<FormField v-slot="{ componentField }" name="relativeAddress">
|
||||
<FormItem>
|
||||
<FormControl>
|
||||
<Input
|
||||
id="relativeAddress"
|
||||
type="text"
|
||||
placeholder="Alamat kerabat"
|
||||
autocomplete="off"
|
||||
v-bind="componentField"
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
</FormField>
|
||||
</Field>
|
||||
</FieldGroup>
|
||||
<div class="space-y-2">
|
||||
<Label
|
||||
class="text-sm font-medium text-gray-700 dark:text-gray-300"
|
||||
is-required
|
||||
>
|
||||
Nama
|
||||
</Label>
|
||||
<InputBase
|
||||
label=""
|
||||
:field-name="`contacts[${idx}].name`"
|
||||
placeholder="Masukkan Nama"
|
||||
:errors="errors"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Village_Code -->
|
||||
<FieldGroup>
|
||||
<Label label-for="relativeVillageCode">Desa/Kelurahan</Label>
|
||||
<Field id="relativeVillageCode" :errors="errors">
|
||||
<FormField v-slot="{ componentField }" name="relativeVillageCode">
|
||||
<FormItem>
|
||||
<FormControl>
|
||||
<Combobox id="relativeVillageCode" v-bind="componentField" :items="genderOpts" />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
</FormField>
|
||||
</Field>
|
||||
</FieldGroup>
|
||||
<div class="space-y-2">
|
||||
<Label
|
||||
class="text-sm font-medium text-gray-700 dark:text-gray-300"
|
||||
is-required
|
||||
>
|
||||
Alamat
|
||||
</Label>
|
||||
<InputBase
|
||||
:field-name="`contacts[${idx}].address`"
|
||||
label=""
|
||||
placeholder="Masukkan Alamat"
|
||||
:errors="errors"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Gender_Code -->
|
||||
<FieldGroup>
|
||||
<Label label-for="relativeGender">Jenis Kelamin</Label>
|
||||
<Field id="relativeGender" :errors="errors">
|
||||
<FormField v-slot="{ componentField }" name="relativeGender">
|
||||
<FormItem>
|
||||
<FormControl>
|
||||
<Select id="relativeGender" v-bind="componentField" :items="genderOpts" />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
</FormField>
|
||||
</Field>
|
||||
</FieldGroup>
|
||||
|
||||
<!-- PhoneNumber -->
|
||||
<FieldGroup>
|
||||
<Label label-for="phoneNumber">Nomor Telepon</Label>
|
||||
<Field id="phoneNumber" :errors="errors">
|
||||
<FormField v-slot="{ componentField }" name="phoneNumber">
|
||||
<FormItem>
|
||||
<FormControl>
|
||||
<Input
|
||||
id="phoneNumber"
|
||||
type="text"
|
||||
placeholder="0812xxxxxx"
|
||||
autocomplete="off"
|
||||
v-bind="componentField"
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
</FormField>
|
||||
</Field>
|
||||
</FieldGroup>
|
||||
|
||||
<!-- Education_Code -->
|
||||
<FieldGroup>
|
||||
<Label label-for="relativeEducation">Pendidikan</Label>
|
||||
<Field id="relativeEducation" :errors="errors">
|
||||
<FormField v-slot="{ componentField }" name="relativeEducation">
|
||||
<FormItem>
|
||||
<FormControl>
|
||||
<Select id="relativeEducation" v-bind="componentField" :items="educationOpts" />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
</FormField>
|
||||
</Field>
|
||||
</FieldGroup>
|
||||
|
||||
<!-- Occupation_Code -->
|
||||
<FieldGroup>
|
||||
<Label label-for="relativeOccupationCode">Pekerjaan</Label>
|
||||
<Field id="relativeOccupationCode" :errors="errors">
|
||||
<FormField v-slot="{ componentField }" name="relativeOccupationCode">
|
||||
<FormItem>
|
||||
<FormControl>
|
||||
<Select id="relativeOccupationCode" v-bind="componentField" :items="occupationOpts" />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
</FormField>
|
||||
</Field>
|
||||
</FieldGroup>
|
||||
|
||||
<!-- Occupation_Name -->
|
||||
<FieldGroup>
|
||||
<Label label-for="relativeOccupationName">Nama Pekerjaan</Label>
|
||||
<Field id="relativeOccupationName" :errors="errors">
|
||||
<FormField v-slot="{ componentField }" name="relativeOccupationName">
|
||||
<FormItem>
|
||||
<FormControl>
|
||||
<Input
|
||||
id="relativeOccupationName"
|
||||
type="text"
|
||||
placeholder="Contoh: Guru, Dokter, Petani"
|
||||
autocomplete="off"
|
||||
v-bind="componentField"
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
</FormField>
|
||||
</Field>
|
||||
</FieldGroup>
|
||||
</Block>
|
||||
<div class="space-y-2">
|
||||
<Label
|
||||
class="text-sm font-medium text-gray-700 dark:text-gray-300"
|
||||
is-required
|
||||
>
|
||||
Nomor HP
|
||||
</Label>
|
||||
<InputBase
|
||||
:field-name="`contacts[${idx}].phone`"
|
||||
label=""
|
||||
placeholder="081234567890"
|
||||
:max-length="15"
|
||||
numeric-only
|
||||
:errors="errors"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<Button
|
||||
type="button"
|
||||
variant="outline"
|
||||
class="w-full rounded-md border border-primary bg-white px-4 py-2 text-primary hover:border-primary hover:bg-primary hover:text-white sm:w-auto sm:text-sm"
|
||||
@click="push({ relation: '', name: '', address: '', phone: '' })"
|
||||
>
|
||||
<Icon
|
||||
name="i-lucide-plus"
|
||||
class="mr-2 h-4 w-4 align-middle transition-colors"
|
||||
/>
|
||||
Tambah Penanggung Jawab
|
||||
</Button>
|
||||
</FieldArray>
|
||||
</div>
|
||||
</Form>
|
||||
</template>
|
||||
|
||||
@@ -0,0 +1,94 @@
|
||||
<script setup lang="ts">
|
||||
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 { Label as RadioLabel } from '~/components/pub/ui/label'
|
||||
import { RadioGroup, RadioGroupItem } from '~/components/pub/ui/radio-group'
|
||||
import { cn } from '~/lib/utils'
|
||||
|
||||
const props = defineProps<{
|
||||
fieldName?: string
|
||||
label?: string
|
||||
errors?: FormErrors
|
||||
class?: string
|
||||
radioGroupClass?: string
|
||||
radioItemClass?: string
|
||||
labelClass?: string
|
||||
isRequired?: boolean
|
||||
}>()
|
||||
|
||||
const {
|
||||
fieldName = 'inputParentsData',
|
||||
errors,
|
||||
class: containerClass,
|
||||
radioGroupClass,
|
||||
radioItemClass,
|
||||
labelClass,
|
||||
} = props
|
||||
|
||||
const residenceOptions = [
|
||||
{ label: 'Ya', value: '1' },
|
||||
{ label: 'Tidak', value: '0' },
|
||||
]
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<FieldGroup :class="cn('radio-group-field', containerClass)">
|
||||
<Label
|
||||
size="fit"
|
||||
height="compact"
|
||||
:label-for="fieldName"
|
||||
:is-required="isRequired"
|
||||
>
|
||||
{{ label || 'Identitas Orang Tua' }}
|
||||
</Label>
|
||||
<Field
|
||||
:id="fieldName"
|
||||
:errors="errors"
|
||||
>
|
||||
<FormField
|
||||
v-slot="{ componentField }"
|
||||
:name="fieldName"
|
||||
>
|
||||
<FormItem>
|
||||
<FormControl>
|
||||
<RadioGroup
|
||||
v-bind="componentField"
|
||||
:class="cn('flex flex-row flex-wrap gap-4 sm:gap-6', radioGroupClass)"
|
||||
>
|
||||
<div
|
||||
v-for="(option, index) in residenceOptions"
|
||||
:key="option.value"
|
||||
:class="cn('flex min-w-fit items-center space-x-2', radioItemClass)"
|
||||
>
|
||||
<RadioGroupItem
|
||||
:id="`${fieldName}-${index}`"
|
||||
:value="option.value"
|
||||
:class="
|
||||
cn(
|
||||
'relative h-4 w-4 rounded-full border-2 border-muted-foreground before:absolute before:inset-1 before:rounded-full before:bg-primary before:opacity-0 before:transition-opacity data-[state=checked]:border-primary data-[state=checked]:bg-white data-[state=checked]:before:opacity-100 sm:h-5 sm:w-5',
|
||||
containerClass,
|
||||
)
|
||||
"
|
||||
/>
|
||||
<RadioLabel
|
||||
:for="`${fieldName}-${index}`"
|
||||
:class="
|
||||
cn(
|
||||
'cursor-pointer select-none text-xs font-medium leading-none transition-colors hover:text-primary peer-disabled:cursor-not-allowed peer-disabled:opacity-70 sm:text-sm',
|
||||
labelClass,
|
||||
)
|
||||
"
|
||||
>
|
||||
{{ option.label }}
|
||||
</RadioLabel>
|
||||
</div>
|
||||
</RadioGroup>
|
||||
</FormControl>
|
||||
<FormMessage class="ml-0 mt-1" />
|
||||
</FormItem>
|
||||
</FormField>
|
||||
</Field>
|
||||
</FieldGroup>
|
||||
</template>
|
||||
@@ -1,5 +1,6 @@
|
||||
<script setup lang="ts">
|
||||
import type { FormErrors } from '~/types/error'
|
||||
import { Form } from '~/components/pub/ui/form'
|
||||
import { toTypedSchema } from '@vee-validate/zod'
|
||||
import Block from '~/components/pub/my-ui/form/block.vue'
|
||||
import Combobox from '~/components/pub/my-ui/form/combobox.vue'
|
||||
@@ -8,7 +9,6 @@ import Field from '~/components/pub/my-ui/form/field.vue'
|
||||
import Label from '~/components/pub/my-ui/form/label.vue'
|
||||
import { educationCodes, genderCodes, occupationCodes, religionCodes } from '~/lib/constants'
|
||||
import { mapToComboboxOptList } from '~/lib/utils'
|
||||
import { Form } from '~/components/pub/ui/form'
|
||||
|
||||
interface DivisionFormData {
|
||||
name: string
|
||||
@@ -65,14 +65,23 @@ function onSubmitForm(values: any, { resetForm }: { resetForm: () => void }) {
|
||||
:validation-schema="formSchema"
|
||||
:initial-values="initialValues"
|
||||
>
|
||||
<form id="entry-form" @submit="handleSubmit($event, (values) => onSubmitForm(values, { resetForm }))">
|
||||
<form
|
||||
id="entry-form"
|
||||
@submit="handleSubmit($event, (values) => onSubmitForm(values, { resetForm }))"
|
||||
>
|
||||
<div class="mb-5 border-b border-b-slate-300 pb-3 text-lg xl:text-xl">
|
||||
<div class="flex flex-col justify-between">
|
||||
<Block>
|
||||
<FieldGroup>
|
||||
<Label label-for="residentIdentityNumber">KTP</Label>
|
||||
<Field id="residentIdentityNumber" :errors="errors">
|
||||
<FormField v-slot="{ componentField }" name="residentIdentityNumber">
|
||||
<Field
|
||||
id="residentIdentityNumber"
|
||||
:errors="errors"
|
||||
>
|
||||
<FormField
|
||||
v-slot="{ componentField }"
|
||||
name="residentIdentityNumber"
|
||||
>
|
||||
<FormItem>
|
||||
<FormControl>
|
||||
<Input
|
||||
@@ -93,8 +102,14 @@ function onSubmitForm(values: any, { resetForm }: { resetForm: () => void }) {
|
||||
<!-- FrontTitle -->
|
||||
<FieldGroup :column="3">
|
||||
<Label label-for="frontTitle">Gelar Depan</Label>
|
||||
<Field id="frontTitle" :errors="errors">
|
||||
<FormField v-slot="{ componentField }" name="frontTitle">
|
||||
<Field
|
||||
id="frontTitle"
|
||||
:errors="errors"
|
||||
>
|
||||
<FormField
|
||||
v-slot="{ componentField }"
|
||||
name="frontTitle"
|
||||
>
|
||||
<FormItem>
|
||||
<FormControl>
|
||||
<Input
|
||||
@@ -112,9 +127,20 @@ function onSubmitForm(values: any, { resetForm }: { resetForm: () => void }) {
|
||||
</FieldGroup>
|
||||
|
||||
<FieldGroup :column="3">
|
||||
<Label label-for="name" position="dynamic">Nama</Label>
|
||||
<Field id="name" :errors="errors">
|
||||
<FormField v-slot="{ componentField }" name="name">
|
||||
<Label
|
||||
label-for="name"
|
||||
position="dynamic"
|
||||
>
|
||||
Nama
|
||||
</Label>
|
||||
<Field
|
||||
id="name"
|
||||
:errors="errors"
|
||||
>
|
||||
<FormField
|
||||
v-slot="{ componentField }"
|
||||
name="name"
|
||||
>
|
||||
<FormItem>
|
||||
<FormControl>
|
||||
<Input
|
||||
@@ -133,9 +159,20 @@ function onSubmitForm(values: any, { resetForm }: { resetForm: () => void }) {
|
||||
|
||||
<!-- EndTitle -->
|
||||
<FieldGroup :column="3">
|
||||
<Label label-for="endTitle" position="dynamic">Gelar Belakang</Label>
|
||||
<Field id="endTitle" :errors="errors">
|
||||
<FormField v-slot="{ componentField }" name="endTitle">
|
||||
<Label
|
||||
label-for="endTitle"
|
||||
position="dynamic"
|
||||
>
|
||||
Gelar Belakang
|
||||
</Label>
|
||||
<Field
|
||||
id="endTitle"
|
||||
:errors="errors"
|
||||
>
|
||||
<FormField
|
||||
v-slot="{ componentField }"
|
||||
name="endTitle"
|
||||
>
|
||||
<FormItem>
|
||||
<FormControl>
|
||||
<Input
|
||||
@@ -155,11 +192,21 @@ function onSubmitForm(values: any, { resetForm }: { resetForm: () => void }) {
|
||||
<!-- BirthDate -->
|
||||
<FieldGroup :column="2">
|
||||
<Label label-for="birthDate">Tanggal Lahir</Label>
|
||||
<Field id="birthDate" :errors="errors">
|
||||
<FormField v-slot="{ componentField }" name="birthDate">
|
||||
<Field
|
||||
id="birthDate"
|
||||
:errors="errors"
|
||||
>
|
||||
<FormField
|
||||
v-slot="{ componentField }"
|
||||
name="birthDate"
|
||||
>
|
||||
<FormItem>
|
||||
<FormControl>
|
||||
<Input id="birthDate" type="date" v-bind="componentField" />
|
||||
<Input
|
||||
id="birthDate"
|
||||
type="date"
|
||||
v-bind="componentField"
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
@@ -170,11 +217,21 @@ function onSubmitForm(values: any, { resetForm }: { resetForm: () => void }) {
|
||||
<!-- BirthRegency_Code -->
|
||||
<FieldGroup :column="2">
|
||||
<Label label-for="birthRegencyCode">Tempat Lahir</Label>
|
||||
<Field id="birthRegencyCode" :errors="errors">
|
||||
<FormField v-slot="{ componentField }" name="birthRegencyCode">
|
||||
<Field
|
||||
id="birthRegencyCode"
|
||||
:errors="errors"
|
||||
>
|
||||
<FormField
|
||||
v-slot="{ componentField }"
|
||||
name="birthRegencyCode"
|
||||
>
|
||||
<FormItem>
|
||||
<FormControl>
|
||||
<Combobox id="parentId" v-bind="componentField" :items="educationOpts" />
|
||||
<Combobox
|
||||
id="parentId"
|
||||
v-bind="componentField"
|
||||
:items="educationOpts"
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
@@ -185,11 +242,21 @@ function onSubmitForm(values: any, { resetForm }: { resetForm: () => void }) {
|
||||
<!-- Gender_Code -->
|
||||
<FieldGroup>
|
||||
<Label label-for="genderCode">Jenis Kelamin</Label>
|
||||
<Field id="genderCode" :errors="errors">
|
||||
<FormField v-slot="{ componentField }" name="genderCode">
|
||||
<Field
|
||||
id="genderCode"
|
||||
:errors="errors"
|
||||
>
|
||||
<FormField
|
||||
v-slot="{ componentField }"
|
||||
name="genderCode"
|
||||
>
|
||||
<FormItem>
|
||||
<FormControl>
|
||||
<Combobox id="genderCode" v-bind="componentField" :items="genderOpts" />
|
||||
<Combobox
|
||||
id="genderCode"
|
||||
v-bind="componentField"
|
||||
:items="genderOpts"
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
@@ -200,8 +267,14 @@ function onSubmitForm(values: any, { resetForm }: { resetForm: () => void }) {
|
||||
<!-- PassportNumber -->
|
||||
<FieldGroup :column="2">
|
||||
<Label label-for="passportNumber">Paspor</Label>
|
||||
<Field id="passportNumber" :errors="errors">
|
||||
<FormField v-slot="{ componentField }" name="passportNumber">
|
||||
<Field
|
||||
id="passportNumber"
|
||||
:errors="errors"
|
||||
>
|
||||
<FormField
|
||||
v-slot="{ componentField }"
|
||||
name="passportNumber"
|
||||
>
|
||||
<FormItem>
|
||||
<FormControl>
|
||||
<Input
|
||||
@@ -221,8 +294,14 @@ function onSubmitForm(values: any, { resetForm }: { resetForm: () => void }) {
|
||||
<!-- DrivingLicenseNumber -->
|
||||
<FieldGroup :column="2">
|
||||
<Label label-for="drivingLicenseNumber">SIM</Label>
|
||||
<Field id="drivingLicenseNumber" :errors="errors">
|
||||
<FormField v-slot="{ componentField }" name="drivingLicenseNumber">
|
||||
<Field
|
||||
id="drivingLicenseNumber"
|
||||
:errors="errors"
|
||||
>
|
||||
<FormField
|
||||
v-slot="{ componentField }"
|
||||
name="drivingLicenseNumber"
|
||||
>
|
||||
<FormItem>
|
||||
<FormControl>
|
||||
<Input
|
||||
@@ -242,11 +321,21 @@ function onSubmitForm(values: any, { resetForm }: { resetForm: () => void }) {
|
||||
<!-- Religion_Code -->
|
||||
<FieldGroup :column="2">
|
||||
<Label label-for="religionCode">Agama</Label>
|
||||
<Field id="religionCode" :errors="errors">
|
||||
<FormField v-slot="{ componentField }" name="religionCode">
|
||||
<Field
|
||||
id="religionCode"
|
||||
:errors="errors"
|
||||
>
|
||||
<FormField
|
||||
v-slot="{ componentField }"
|
||||
name="religionCode"
|
||||
>
|
||||
<FormItem>
|
||||
<FormControl>
|
||||
<Combobox id="religionCode" v-bind="componentField" :items="religionOpts" />
|
||||
<Combobox
|
||||
id="religionCode"
|
||||
v-bind="componentField"
|
||||
:items="religionOpts"
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
@@ -256,11 +345,21 @@ function onSubmitForm(values: any, { resetForm }: { resetForm: () => void }) {
|
||||
|
||||
<FieldGroup :column="2">
|
||||
<Label label-for="ethnicCode">Suku</Label>
|
||||
<Field id="ethnicCode" :errors="errors">
|
||||
<FormField v-slot="{ componentField }" name="ethnicCode">
|
||||
<Field
|
||||
id="ethnicCode"
|
||||
:errors="errors"
|
||||
>
|
||||
<FormField
|
||||
v-slot="{ componentField }"
|
||||
name="ethnicCode"
|
||||
>
|
||||
<FormItem>
|
||||
<FormControl>
|
||||
<Combobox id="ethnicCode" v-bind="componentField" :items="occupationOpts" />
|
||||
<Combobox
|
||||
id="ethnicCode"
|
||||
v-bind="componentField"
|
||||
:items="occupationOpts"
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
@@ -271,11 +370,21 @@ function onSubmitForm(values: any, { resetForm }: { resetForm: () => void }) {
|
||||
<!-- Language_Code -->
|
||||
<FieldGroup :column="2">
|
||||
<Label label-for="languageCode">Bahasa</Label>
|
||||
<Field id="languageCode" :errors="errors">
|
||||
<FormField v-slot="{ componentField }" name="languageCode">
|
||||
<Field
|
||||
id="languageCode"
|
||||
:errors="errors"
|
||||
>
|
||||
<FormField
|
||||
v-slot="{ componentField }"
|
||||
name="languageCode"
|
||||
>
|
||||
<FormItem>
|
||||
<FormControl>
|
||||
<Combobox id="parentId" v-bind="componentField" :items="educationOpts" />
|
||||
<Combobox
|
||||
id="parentId"
|
||||
v-bind="componentField"
|
||||
:items="educationOpts"
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
@@ -286,11 +395,21 @@ function onSubmitForm(values: any, { resetForm }: { resetForm: () => void }) {
|
||||
<!-- Education_Code -->
|
||||
<FieldGroup :column="2">
|
||||
<Label label-for="educationCode">Pendidikan</Label>
|
||||
<Field id="educationCode" :errors="errors">
|
||||
<FormField v-slot="{ componentField }" name="educationCode">
|
||||
<Field
|
||||
id="educationCode"
|
||||
:errors="errors"
|
||||
>
|
||||
<FormField
|
||||
v-slot="{ componentField }"
|
||||
name="educationCode"
|
||||
>
|
||||
<FormItem>
|
||||
<FormControl>
|
||||
<Combobox id="educationCode" v-bind="componentField" :items="educationOpts" />
|
||||
<Combobox
|
||||
id="educationCode"
|
||||
v-bind="componentField"
|
||||
:items="educationOpts"
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
@@ -301,11 +420,21 @@ function onSubmitForm(values: any, { resetForm }: { resetForm: () => void }) {
|
||||
<!-- Occupation_Code -->
|
||||
<FieldGroup :column="2">
|
||||
<Label label-for="occupationCode">Pekerjaan</Label>
|
||||
<Field id="occupationCode" :errors="errors">
|
||||
<FormField v-slot="{ componentField }" name="occupationCode">
|
||||
<Field
|
||||
id="occupationCode"
|
||||
:errors="errors"
|
||||
>
|
||||
<FormField
|
||||
v-slot="{ componentField }"
|
||||
name="occupationCode"
|
||||
>
|
||||
<FormItem>
|
||||
<FormControl>
|
||||
<Combobox id="occupationCode" v-bind="componentField" :items="occupationOpts" />
|
||||
<Combobox
|
||||
id="occupationCode"
|
||||
v-bind="componentField"
|
||||
:items="occupationOpts"
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
@@ -316,8 +445,14 @@ function onSubmitForm(values: any, { resetForm }: { resetForm: () => void }) {
|
||||
<!-- Occupation_Name -->
|
||||
<FieldGroup :column="2">
|
||||
<Label label-for="occupationName">Detail Pekerjaan</Label>
|
||||
<Field id="occupationName" :errors="errors">
|
||||
<FormField v-slot="{ componentField }" name="occupationName">
|
||||
<Field
|
||||
id="occupationName"
|
||||
:errors="errors"
|
||||
>
|
||||
<FormField
|
||||
v-slot="{ componentField }"
|
||||
name="occupationName"
|
||||
>
|
||||
<FormItem>
|
||||
<FormControl>
|
||||
<Input
|
||||
|
||||
@@ -0,0 +1,167 @@
|
||||
<script setup lang="ts">
|
||||
import type { PersonFamilyFormData as FamilyData } from '~/schemas/person-family.schema'
|
||||
import type { FormErrors } from '~/types/error'
|
||||
import { toTypedSchema } from '@vee-validate/zod'
|
||||
import { FieldArray } from 'vee-validate'
|
||||
import SelectEducation from '~/components/app/patient/_common/select-education.vue'
|
||||
import InputBase from '~/components/pub/my-ui/form/input-base.vue'
|
||||
import RadioParentsInput from './_common/radio-parents-input.vue'
|
||||
import { Form } from '~/components/pub/ui/form'
|
||||
|
||||
const props = defineProps<{
|
||||
title: string
|
||||
schema: any
|
||||
initialValues?: any
|
||||
errors?: FormErrors
|
||||
}>()
|
||||
|
||||
const formSchema = toTypedSchema(props.schema)
|
||||
const formRef = ref()
|
||||
|
||||
// Watcher untuk mengatur families ketika shareFamilyData berubah
|
||||
watch(
|
||||
() => formRef.value?.values?.shareFamilyData,
|
||||
(newValue) => {
|
||||
if (newValue === '1' && formRef.value) {
|
||||
// Ketika memilih "Ya", pastikan ada data families untuk mother dan father
|
||||
const currentFamilies = formRef.value.values?.families || []
|
||||
if (currentFamilies.length === 0) {
|
||||
formRef.value.setFieldValue('families', [
|
||||
{ relation: 'mother', name: undefined, education: undefined, occupation: undefined },
|
||||
{ relation: 'father', name: undefined, education: undefined, occupation: undefined },
|
||||
])
|
||||
}
|
||||
} else if (newValue === '0' && formRef.value) {
|
||||
// Ketika memilih "Tidak", kosongkan families
|
||||
formRef.value.setFieldValue('families', [])
|
||||
}
|
||||
},
|
||||
{ immediate: false },
|
||||
)
|
||||
|
||||
defineExpose({
|
||||
validate: () => formRef.value?.validate(),
|
||||
resetForm: () => formRef.value?.resetForm(),
|
||||
values: computed(() => formRef.value?.values),
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Form
|
||||
ref="formRef"
|
||||
v-slot="{ values }"
|
||||
as=""
|
||||
keep-values
|
||||
:validation-schema="formSchema"
|
||||
:validate-on-mount="false"
|
||||
validation-mode="onSubmit"
|
||||
:initial-values="
|
||||
initialValues || {
|
||||
shareFamilyData: '0',
|
||||
families: [],
|
||||
}
|
||||
"
|
||||
>
|
||||
<div>
|
||||
<p
|
||||
v-if="props.title"
|
||||
class="text-md mb-2 mt-1 font-semibold"
|
||||
>
|
||||
{{ props.title }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="mb-5 border-b border-b-slate-300 pb-3 text-lg xl:text-xl">
|
||||
<!-- Radio Button Section - Full Width -->
|
||||
<div class="mb-6">
|
||||
<RadioParentsInput field-name="shareFamilyData" />
|
||||
</div>
|
||||
|
||||
<!-- Form Fields Section - Only show when "Ya" is selected -->
|
||||
<div
|
||||
v-if="values.shareFamilyData === '1'"
|
||||
class="space-y-6"
|
||||
>
|
||||
<FieldArray
|
||||
v-slot="{ fields }"
|
||||
name="families"
|
||||
>
|
||||
<div class="grid grid-cols-1 gap-6 lg:grid-cols-2">
|
||||
<template
|
||||
v-for="(field, idx) in fields"
|
||||
:key="field.key"
|
||||
>
|
||||
<div
|
||||
class="space-y-4 rounded-lg border border-gray-200 bg-gray-50/50 p-4 dark:border-gray-700 dark:bg-gray-800/50"
|
||||
>
|
||||
<h4 class="text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||
{{ (values.families as FamilyData[])?.[idx]?.relation === 'mother' ? 'Data Ibu' : 'Data Ayah' }}
|
||||
</h4>
|
||||
|
||||
<div class="grid grid-cols-1 gap-4 md:grid-cols-2">
|
||||
<div>
|
||||
<InputBase
|
||||
:field-name="`families[${idx}].name`"
|
||||
:label="
|
||||
(values.families as FamilyData[])?.[idx]?.relation === 'mother'
|
||||
? 'Nama Ibu Kandung'
|
||||
: (values.families as FamilyData[])?.[idx]?.relation === 'father'
|
||||
? 'Nama Ayah Kandung'
|
||||
: 'Nama Keluarga'
|
||||
"
|
||||
:placeholder="
|
||||
(values.families as FamilyData[])?.[idx]?.relation === 'mother'
|
||||
? 'Masukkan nama ibu pasien'
|
||||
: (values.families as FamilyData[])?.[idx]?.relation === 'father'
|
||||
? 'Masukkan nama ayah pasien'
|
||||
: 'Masukkan nama'
|
||||
"
|
||||
:errors="errors"
|
||||
is-required
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<SelectEducation
|
||||
:field-name="`families[${idx}].education`"
|
||||
:label="
|
||||
(values.families as FamilyData[])?.[idx]?.relation === 'mother'
|
||||
? 'Pendidikan Ibu'
|
||||
: (values.families as FamilyData[])?.[idx]?.relation === 'father'
|
||||
? 'Pendidikan Ayah'
|
||||
: 'Pendidikan'
|
||||
"
|
||||
placeholder="Pilih"
|
||||
:errors="errors"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<InputBase
|
||||
:field-name="`families[${idx}].occupation`"
|
||||
:label="
|
||||
(values.families as FamilyData[])?.[idx]?.relation === 'mother'
|
||||
? 'Pekerjaan Ibu'
|
||||
: (values.families as FamilyData[])?.[idx]?.relation === 'father'
|
||||
? 'Pekerjaan Ayah'
|
||||
: 'Pekerjaan'
|
||||
"
|
||||
:placeholder="
|
||||
(values.families as FamilyData[])?.[idx]?.relation === 'mother'
|
||||
? 'Masukkan pekerjaan ibu'
|
||||
: (values.families as FamilyData[])?.[idx]?.relation === 'father'
|
||||
? 'Masukkan pekerjaan ayah'
|
||||
: 'Masukkan pekerjaan'
|
||||
"
|
||||
:errors="errors"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</FieldArray>
|
||||
</div>
|
||||
</div>
|
||||
</Form>
|
||||
</template>
|
||||
@@ -0,0 +1,195 @@
|
||||
<script setup lang="ts">
|
||||
// Components
|
||||
import Block from '~/components/pub/my-ui/doc-entry/block.vue'
|
||||
import Cell from '~/components/pub/my-ui/doc-entry/cell.vue'
|
||||
import Field from '~/components/pub/my-ui/doc-entry/field.vue'
|
||||
import Label from '~/components/pub/my-ui/doc-entry/label.vue'
|
||||
import Button from '~/components/pub/ui/button/Button.vue'
|
||||
import Combobox from '~/components/pub/my-ui/form/combobox.vue'
|
||||
|
||||
// Constants
|
||||
import { infraGroupCodesKeys } from '~/lib/constants'
|
||||
|
||||
// Types
|
||||
import type { InfraFormData } from '~/schemas/infra.schema'
|
||||
|
||||
// Helpers
|
||||
import type z from 'zod'
|
||||
import { useForm } from 'vee-validate'
|
||||
import { toTypedSchema } from '@vee-validate/zod'
|
||||
|
||||
interface Props {
|
||||
schema: z.ZodSchema<any>
|
||||
specialists: any[]
|
||||
subspecialists: any[]
|
||||
units: any[]
|
||||
parents: any[]
|
||||
values: any
|
||||
isLoading?: boolean
|
||||
isReadonly?: boolean
|
||||
}
|
||||
|
||||
const props = defineProps<Props>()
|
||||
const isLoading = props.isLoading !== undefined ? props.isLoading : false
|
||||
const isReadonly = props.isReadonly !== undefined ? props.isReadonly : false
|
||||
const emit = defineEmits<{
|
||||
'update:selected-unit': [val: any]
|
||||
'update:selected-specialist': [val: any]
|
||||
submit: [values: InfraFormData, resetForm: () => void]
|
||||
cancel: [resetForm: () => void]
|
||||
}>()
|
||||
|
||||
const { defineField, errors, meta } = useForm({
|
||||
validationSchema: toTypedSchema(props.schema),
|
||||
initialValues: {
|
||||
code: '',
|
||||
name: '',
|
||||
infraGroup_code: infraGroupCodesKeys.room,
|
||||
parent_id: null,
|
||||
specialist_id: 0,
|
||||
subspecialist_id: 0,
|
||||
unit_id: 0,
|
||||
} as Partial<InfraFormData>,
|
||||
})
|
||||
|
||||
const [code, codeAttrs] = defineField('code')
|
||||
const [name, nameAttrs] = defineField('name')
|
||||
const [infraGroup_code] = defineField('infraGroup_code')
|
||||
const [specialist_id, specialistAttrs] = defineField('specialist_id')
|
||||
const [subspecialist_id, subspecialistAttrs] = defineField('subspecialist_id')
|
||||
const [unit_id, unitAttrs] = defineField('unit_id')
|
||||
const [parent_id, parentAttrs] = defineField('parent_id')
|
||||
|
||||
if (props.values) {
|
||||
if (props.values.code !== undefined) code.value = props.values.code
|
||||
if (props.values.name !== undefined) name.value = props.values.name
|
||||
if (props.values.infraGroup_code !== undefined) infraGroup_code.value = props.values.infraGroup_code
|
||||
if (props.values.specialist_id !== undefined)
|
||||
specialist_id.value = props.values.specialist_id ? String(props.values.specialist_id) : null
|
||||
if (props.values.subspecialist_id !== undefined)
|
||||
subspecialist_id.value = props.values.subspecialist_id ? String(props.values.subspecialist_id) : null
|
||||
if (props.values.unit_id !== undefined) unit_id.value = props.values.unit_id ? String(props.values.unit_id) : null
|
||||
if (props.values.parent_id !== undefined)
|
||||
parent_id.value = props.values.parent_id ? String(props.values.parent_id) : null
|
||||
}
|
||||
|
||||
const resetForm = () => {
|
||||
code.value = ''
|
||||
name.value = ''
|
||||
infraGroup_code.value = infraGroupCodesKeys.room
|
||||
specialist_id.value = 0
|
||||
subspecialist_id.value = 0
|
||||
unit_id.value = 0
|
||||
parent_id.value = null
|
||||
}
|
||||
|
||||
function onSubmitForm() {
|
||||
const formData: InfraFormData = {
|
||||
code: code.value || '',
|
||||
name: name.value || '',
|
||||
infraGroup_code: infraGroup_code.value || infraGroupCodesKeys.room,
|
||||
specialist_id: specialist_id.value || 0,
|
||||
subspecialist_id: subspecialist_id.value || 0,
|
||||
unit_id: unit_id.value || 0,
|
||||
parent_id: parent_id.value ? Number(parent_id.value) : null,
|
||||
}
|
||||
emit('submit', formData, resetForm)
|
||||
}
|
||||
|
||||
function onCancelForm() {
|
||||
emit('cancel', resetForm)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<form id="form-building" @submit.prevent>
|
||||
<Block labelSize="thin" class="!mb-2.5 !pt-0 xl:!mb-3" :colCount="1">
|
||||
<Cell>
|
||||
<Label height="compact">Kode</Label>
|
||||
<Field :errMessage="errors.code">
|
||||
<Input id="code" v-model="code" v-bind="codeAttrs" :disabled="isLoading || isReadonly" />
|
||||
</Field>
|
||||
</Cell>
|
||||
<Cell>
|
||||
<Label height="compact">Nama</Label>
|
||||
<Field :errMessage="errors.name">
|
||||
<Input id="name" v-model="name" v-bind="nameAttrs" :disabled="isLoading || isReadonly" />
|
||||
</Field>
|
||||
</Cell>
|
||||
<Cell>
|
||||
<Label height="compact">Unit</Label>
|
||||
<Field :errMessage="errors.unit_id">
|
||||
<Combobox
|
||||
id="unit"
|
||||
v-model="unit_id"
|
||||
v-bind="unitAttrs"
|
||||
:items="units"
|
||||
:disabled="isLoading || isReadonly"
|
||||
placeholder="Pilih Unit"
|
||||
search-placeholder="Cari Unit"
|
||||
empty-message="Unit tidak ditemukan"
|
||||
@update:model-value="$emit('update:selected-unit', $event)"
|
||||
/>
|
||||
</Field>
|
||||
</Cell>
|
||||
<Cell>
|
||||
<Label height="compact">Spesialis</Label>
|
||||
<Field :errMessage="errors.specialist_id">
|
||||
<Combobox
|
||||
id="specialist"
|
||||
v-model="specialist_id"
|
||||
v-bind="specialistAttrs"
|
||||
:items="specialists"
|
||||
:disabled="isLoading || isReadonly"
|
||||
placeholder="Pilih Spesialis"
|
||||
search-placeholder="Cari spesialis"
|
||||
empty-message="Spesialis tidak ditemukan"
|
||||
@update:model-value="$emit('update:selected-specialist', $event)"
|
||||
/>
|
||||
</Field>
|
||||
</Cell>
|
||||
<Cell>
|
||||
<Label height="compact">Sub Spesialis</Label>
|
||||
<Field :errMessage="errors.subspecialist_id">
|
||||
<Combobox
|
||||
id="subspecialist"
|
||||
v-model="subspecialist_id"
|
||||
v-bind="subspecialistAttrs"
|
||||
:items="subspecialists"
|
||||
:disabled="isLoading || isReadonly"
|
||||
placeholder="Pilih Sub Spesialis"
|
||||
search-placeholder="Cari sub spesialis"
|
||||
empty-message="Sub Spesialis tidak ditemukan"
|
||||
/>
|
||||
</Field>
|
||||
</Cell>
|
||||
<Cell>
|
||||
<Label height="compact">Lantai</Label>
|
||||
<Field :errMessage="errors.parent_id">
|
||||
<Combobox
|
||||
id="parent"
|
||||
v-model="parent_id"
|
||||
v-bind="parentAttrs"
|
||||
:items="parents"
|
||||
:disabled="isLoading || isReadonly"
|
||||
placeholder="Pilih Lantai"
|
||||
search-placeholder="Cari Lantai"
|
||||
empty-message="Lantai tidak ditemukan"
|
||||
/>
|
||||
</Field>
|
||||
</Cell>
|
||||
</Block>
|
||||
<div class="my-2 flex justify-end gap-2 py-2">
|
||||
<Button type="button" variant="secondary" class="w-[120px]" @click="onCancelForm"> Kembali </Button>
|
||||
<Button
|
||||
v-if="!isReadonly"
|
||||
type="button"
|
||||
class="w-[120px]"
|
||||
:disabled="isLoading || !meta.valid"
|
||||
@click="onSubmitForm"
|
||||
>
|
||||
Simpan
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
</template>
|
||||
@@ -0,0 +1,61 @@
|
||||
import type {
|
||||
Col,
|
||||
KeyLabel,
|
||||
RecComponent,
|
||||
RecStrFuncComponent,
|
||||
RecStrFuncUnknown,
|
||||
Th,
|
||||
} from '~/components/pub/my-ui/data/types'
|
||||
import { defineAsyncComponent } from 'vue'
|
||||
|
||||
type SmallDetailDto = any
|
||||
|
||||
const action = defineAsyncComponent(() => import('~/components/pub/my-ui/data/dropdown-action-dud.vue'))
|
||||
|
||||
export const cols: Col[] = [{}, {}, {}, {}, {}, { width: 50 }]
|
||||
|
||||
export const header: Th[][] = [
|
||||
[
|
||||
{ label: 'Kode' },
|
||||
{ label: 'Nama' },
|
||||
{ label: 'Spesialis' },
|
||||
{ label: 'Sub Spesialis' },
|
||||
{ label: 'Unit' },
|
||||
{ label: '' },
|
||||
],
|
||||
]
|
||||
|
||||
export const keys = ['code', 'name', 'specialist', 'subspecialist', 'unit', 'action']
|
||||
|
||||
export const delKeyNames: KeyLabel[] = [
|
||||
{ key: 'code', label: 'Kode' },
|
||||
{ key: 'name', label: 'Nama' },
|
||||
]
|
||||
|
||||
export const funcParsed: RecStrFuncUnknown = {
|
||||
specialist: (rec: unknown): unknown => {
|
||||
const recX = rec as SmallDetailDto
|
||||
return recX.specialist?.name || '-'
|
||||
},
|
||||
subspecialist: (rec: unknown): unknown => {
|
||||
const recX = rec as SmallDetailDto
|
||||
return recX.subspecialist?.name || '-'
|
||||
},
|
||||
unit: (rec: unknown): unknown => {
|
||||
const recX = rec as SmallDetailDto
|
||||
return recX.unit?.name || '-'
|
||||
},
|
||||
}
|
||||
|
||||
export const funcComponent: RecStrFuncComponent = {
|
||||
action(rec, idx) {
|
||||
const res: RecComponent = {
|
||||
idx,
|
||||
rec: rec as object,
|
||||
component: action,
|
||||
}
|
||||
return res
|
||||
},
|
||||
}
|
||||
|
||||
export const funcHtml: RecStrFuncUnknown = {}
|
||||
@@ -0,0 +1,41 @@
|
||||
<script setup lang="ts">
|
||||
// Components
|
||||
import PaginationView from '~/components/pub/my-ui/pagination/pagination-view.vue'
|
||||
|
||||
// Types
|
||||
import type { PaginationMeta } from '~/components/pub/my-ui/pagination/pagination.type'
|
||||
|
||||
// Configs
|
||||
import { cols, funcComponent, funcHtml, funcParsed, header, keys } from './list-cfg'
|
||||
|
||||
interface Props {
|
||||
data: any[]
|
||||
paginationMeta: PaginationMeta
|
||||
}
|
||||
|
||||
defineProps<Props>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
pageChange: [page: number]
|
||||
}>()
|
||||
|
||||
function handlePageChange(page: number) {
|
||||
emit('pageChange', page)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="space-y-4">
|
||||
<PubMyUiDataTable
|
||||
:rows="data"
|
||||
:cols="cols"
|
||||
:header="header"
|
||||
:keys="keys"
|
||||
:func-parsed="funcParsed"
|
||||
:func-html="funcHtml"
|
||||
:func-component="funcComponent"
|
||||
:skeleton-size="paginationMeta?.pageSize"
|
||||
/>
|
||||
<PaginationView :pagination-meta="paginationMeta" @page-change="handlePageChange" />
|
||||
</div>
|
||||
</template>
|
||||
@@ -7,7 +7,7 @@ import * as z from 'zod'
|
||||
import { Button } from '~/components/pub/ui/button'
|
||||
import { Input } from '~/components/pub/ui/input'
|
||||
import { Label } from '~/components/pub/ui/label'
|
||||
import { Select } from '~/components/pub/ui/select'
|
||||
import Select from '~/components/pub/ui/select/Select.vue'
|
||||
import { RadioGroup, RadioGroupItem } from '~/components/pub/ui/radio-group'
|
||||
import { Textarea } from '~/components/pub/ui/textarea'
|
||||
import DatepickerSingle from '~/components/pub/my-ui/form/datepicker-single.vue'
|
||||
|
||||
@@ -0,0 +1,222 @@
|
||||
<script setup lang="ts">
|
||||
import Block from '~/components/pub/my-ui/doc-entry/block.vue'
|
||||
import Cell from '~/components/pub/my-ui/doc-entry/cell.vue'
|
||||
import Field from '~/components/pub/my-ui/doc-entry/field.vue'
|
||||
import Label from '~/components/pub/my-ui/doc-entry/label.vue'
|
||||
|
||||
const props = defineProps<{
|
||||
excludeFields?: string[]
|
||||
}>()
|
||||
|
||||
const emits = defineEmits(['click'])
|
||||
|
||||
const subject = ref({
|
||||
'prim-compl': '',
|
||||
'sec-compl': '',
|
||||
'cur-disea-hist': '',
|
||||
'pas-disea-hist': '',
|
||||
'fam-disea-hist': '',
|
||||
'alg-hist': '',
|
||||
'alg-react': '',
|
||||
'med-hist': '',
|
||||
'blood-type': '',
|
||||
})
|
||||
|
||||
const isExcluded = (key: string) => props.excludeFields?.includes(key)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<form id="entry-form">
|
||||
<div class="mb-5 border-b border-b-slate-300 pb-3 text-lg xl:text-xl">
|
||||
<Block>
|
||||
<Cell>
|
||||
<Label dynamic>Sudah Vaksin</Label>
|
||||
<Field>
|
||||
<RadioGroup class="flex gap-4">
|
||||
<div class="flex items-center space-x-2">
|
||||
<RadioGroupItem id="vaksin-yes" value="yes" />
|
||||
<Label for="vaksin-yes">Sudah</Label>
|
||||
</div>
|
||||
<div class="flex items-center space-x-2">
|
||||
<RadioGroupItem id="vaksin-no" value="no" />
|
||||
<Label for="vaksin-no">Belum</Label>
|
||||
</div>
|
||||
</RadioGroup>
|
||||
</Field>
|
||||
</Cell>
|
||||
|
||||
<Cell>
|
||||
<Label dynamic>Kasus</Label>
|
||||
<Field>
|
||||
<RadioGroup class="flex gap-4">
|
||||
<div class="flex items-center space-x-2">
|
||||
<RadioGroupItem id="kasus-baru" value="baru" />
|
||||
<Label for="kasus-baru">Baru</Label>
|
||||
</div>
|
||||
<div class="flex items-center space-x-2">
|
||||
<RadioGroupItem id="kasus-lama" value="lama" />
|
||||
<Label for="kasus-lama">Lama</Label>
|
||||
</div>
|
||||
</RadioGroup>
|
||||
</Field>
|
||||
</Cell>
|
||||
|
||||
<Cell>
|
||||
<Label dynamic>Kunjungan</Label>
|
||||
<Field>
|
||||
<RadioGroup class="flex gap-4">
|
||||
<div class="flex items-center space-x-2">
|
||||
<RadioGroupItem id="kunjungan-baru" value="baru" />
|
||||
<Label for="kunjungan-baru">Baru</Label>
|
||||
</div>
|
||||
<div class="flex items-center space-x-2">
|
||||
<RadioGroupItem id="kunjungan-lama" value="lama" />
|
||||
<Label for="kunjungan-lama">Lama</Label>
|
||||
</div>
|
||||
</RadioGroup>
|
||||
</Field>
|
||||
</Cell>
|
||||
</Block>
|
||||
|
||||
<Block :colCount="2">
|
||||
<!-- Riwayat Penyakit -->
|
||||
<Cell>
|
||||
<Label dynamic>Keluhan Utama</Label>
|
||||
<Field>
|
||||
<Textarea placeholder="Masukkan anamnesa pasien" />
|
||||
</Field>
|
||||
</Cell>
|
||||
<Cell>
|
||||
<Label dynamic>Riwayat Penyakit</Label>
|
||||
<Field>
|
||||
<Textarea
|
||||
placeholder="Masukkan anamnesa pasien (Riwayat Penyakit Sekarang, Dahulu, Pengobatan, Keluarga, dll)"
|
||||
/>
|
||||
</Field>
|
||||
</Cell>
|
||||
<Cell>
|
||||
<Label dynamic>SpO₂</Label>
|
||||
<Field>
|
||||
<Input type="number" placeholder="%" />
|
||||
</Field>
|
||||
</Cell>
|
||||
|
||||
<Cell>
|
||||
<Label dynamic>Tekanan Darah Sistol</Label>
|
||||
<Field>
|
||||
<Input type="number" placeholder="mmHg" />
|
||||
</Field>
|
||||
</Cell>
|
||||
|
||||
<Cell>
|
||||
<Label dynamic>Tekanan Darah Diastol</Label>
|
||||
<Field>
|
||||
<Input type="number" placeholder="mmHg" />
|
||||
</Field>
|
||||
</Cell>
|
||||
|
||||
<Cell>
|
||||
<Label dynamic>Respirasi</Label>
|
||||
<Field>
|
||||
<Input type="number" placeholder="kali/menit" />
|
||||
</Field>
|
||||
</Cell>
|
||||
|
||||
<Cell>
|
||||
<Label dynamic>Nadi</Label>
|
||||
<Field>
|
||||
<Input type="number" placeholder="kali/menit" />
|
||||
</Field>
|
||||
</Cell>
|
||||
|
||||
<Cell>
|
||||
<Label dynamic>Temperatur</Label>
|
||||
<Field>
|
||||
<Input type="number" placeholder="℃" />
|
||||
</Field>
|
||||
</Cell>
|
||||
|
||||
<Cell>
|
||||
<Label dynamic>Berat Badan</Label>
|
||||
<Field>
|
||||
<Input type="number" placeholder="Kg" />
|
||||
</Field>
|
||||
</Cell>
|
||||
|
||||
<Cell>
|
||||
<Label dynamic>Tinggi Badan</Label>
|
||||
<Field>
|
||||
<Input type="number" placeholder="Cm" />
|
||||
</Field>
|
||||
</Cell>
|
||||
|
||||
<Cell>
|
||||
<Label dynamic>Golongan Darah</Label>
|
||||
<Field>
|
||||
<Select :options="bloodGroups" />
|
||||
</Field>
|
||||
</Cell>
|
||||
|
||||
<Cell>
|
||||
<Label dynamic>Pemeriksaan Fisik (Yang Mendukung)</Label>
|
||||
<Field>
|
||||
<Textarea placeholder="Masukkan pemeriksaan fisik" />
|
||||
</Field>
|
||||
</Cell>
|
||||
|
||||
<!-- Prosedur -->
|
||||
<Cell>
|
||||
<Label dynamic>Diagnosa (ICD-X)</Label>
|
||||
<Field>
|
||||
<Button
|
||||
class="rounded bg-orange-100 px-3 py-1 text-orange-600"
|
||||
type="button"
|
||||
@click="emits('click', 'diagnosa')"
|
||||
>+ Pilih Diagnosa</Button
|
||||
>
|
||||
</Field>
|
||||
</Cell>
|
||||
<!-- Diagnosa -->
|
||||
<Cell>
|
||||
<Label dynamic>Diagnosa (ICD-X)</Label>
|
||||
<Field>
|
||||
<Button
|
||||
class="rounded bg-orange-100 px-3 py-1 text-orange-600"
|
||||
type="button"
|
||||
@click="emits('click', 'prosedur')"
|
||||
>+ Pilih Prosedur</Button
|
||||
>
|
||||
</Field>
|
||||
</Cell>
|
||||
</Block>
|
||||
|
||||
<div class="mb-8 grid grid-cols-2 gap-4">
|
||||
<AppIcdPreview />
|
||||
<AppIcdPreview />
|
||||
</div>
|
||||
|
||||
<Block :colCount="3">
|
||||
<Cell>
|
||||
<Label dynamic>Diagnosa Medis</Label>
|
||||
<Field>
|
||||
<Textarea />
|
||||
</Field>
|
||||
</Cell>
|
||||
|
||||
<Cell>
|
||||
<Label dynamic>Rencana Awal Medis</Label>
|
||||
<Field>
|
||||
<Textarea />
|
||||
</Field>
|
||||
</Cell>
|
||||
|
||||
<Cell>
|
||||
<Label dynamic>Terapi</Label>
|
||||
<Field>
|
||||
<Textarea />
|
||||
</Field>
|
||||
</Cell>
|
||||
</Block>
|
||||
</div>
|
||||
</form>
|
||||
</template>
|
||||
@@ -0,0 +1,101 @@
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import Block from '~/components/pub/my-ui/doc-entry/block.vue'
|
||||
import Cell from '~/components/pub/my-ui/doc-entry/cell.vue'
|
||||
import Field from '~/components/pub/my-ui/doc-entry/field.vue'
|
||||
import Label from '~/components/pub/my-ui/doc-entry/label.vue'
|
||||
|
||||
const props = defineProps<{
|
||||
excludeFields?: string[]
|
||||
}>()
|
||||
|
||||
const subject = ref({
|
||||
'prim-compl': '',
|
||||
'sec-compl': '',
|
||||
'cur-disea-hist': '',
|
||||
'pas-disea-hist': '',
|
||||
'fam-disea-hist': '',
|
||||
'alg-hist': '',
|
||||
'alg-react': '',
|
||||
'med-hist': '',
|
||||
'blood-type': '',
|
||||
})
|
||||
|
||||
const isExcluded = (key: string) => props.excludeFields?.includes(key)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="space-y-4">
|
||||
<!-- Keluhan Utama -->
|
||||
<Cell v-if="!isExcluded('prim-compl')">
|
||||
<Label dynamic>Keluhan Utama</Label>
|
||||
<Field>
|
||||
<Textarea placeholder="Masukkan anamnesa pasien" v-model="subject['prim-compl']" />
|
||||
</Field>
|
||||
</Cell>
|
||||
|
||||
<!-- Keluhan Tambahan -->
|
||||
<Cell v-if="!isExcluded('sec-compl')">
|
||||
<Label dynamic>Keluhan Tambahan</Label>
|
||||
<Field>
|
||||
<Textarea placeholder="Masukkan keluhan tambahan" v-model="subject['sec-compl']" />
|
||||
</Field>
|
||||
</Cell>
|
||||
|
||||
<!-- Riwayat Penyakit Sekarang -->
|
||||
<Cell v-if="!isExcluded('cur-disea-hist')">
|
||||
<Label dynamic>Riwayat Penyakit Sekarang</Label>
|
||||
<Field>
|
||||
<Textarea placeholder="Masukkan riwayat penyakit sekarang" v-model="subject['cur-disea-hist']" />
|
||||
</Field>
|
||||
</Cell>
|
||||
|
||||
<!-- Riwayat Penyakit Dahulu -->
|
||||
<Cell v-if="!isExcluded('pas-disea-hist')">
|
||||
<Label dynamic>Riwayat Penyakit Dahulu</Label>
|
||||
<Field>
|
||||
<Textarea placeholder="Masukkan riwayat penyakit dahulu" v-model="subject['pas-disea-hist']" />
|
||||
</Field>
|
||||
</Cell>
|
||||
|
||||
<!-- Riwayat Penyakit Keluarga -->
|
||||
<Cell v-if="!isExcluded('fam-disea-hist')">
|
||||
<Label dynamic>Riwayat Penyakit Keluarga</Label>
|
||||
<Field>
|
||||
<Textarea placeholder="Masukkan riwayat penyakit keluarga" v-model="subject['fam-disea-hist']" />
|
||||
</Field>
|
||||
</Cell>
|
||||
|
||||
<!-- Riwayat Alergi -->
|
||||
<Cell v-if="!isExcluded('alg-hist')">
|
||||
<Label dynamic>Riwayat Alergi</Label>
|
||||
<Field>
|
||||
<Textarea placeholder="Masukkan riwayat alergi" v-model="subject['alg-hist']" />
|
||||
</Field>
|
||||
</Cell>
|
||||
|
||||
<!-- Reaksi Alergi -->
|
||||
<Cell v-if="!isExcluded('alg-react')">
|
||||
<Label dynamic>Reaksi Alergi</Label>
|
||||
<Field>
|
||||
<Textarea placeholder="Masukkan reaksi alergi" v-model="subject['alg-react']" />
|
||||
</Field>
|
||||
</Cell>
|
||||
|
||||
<!-- Pengobatan yang Sedang Dijalani -->
|
||||
<Cell v-if="!isExcluded('med-hist')">
|
||||
<Label dynamic>Riwayat Pengobatan</Label>
|
||||
<Field>
|
||||
<Textarea placeholder="Masukkan pengobatan yang sedang dijalani" v-model="subject['med-hist']" />
|
||||
</Field>
|
||||
</Cell>
|
||||
|
||||
<!-- Golongan Darah -->
|
||||
<Cell v-if="!isExcluded('blood-type')">
|
||||
<Label dynamic>Golongan Darah</Label>
|
||||
<Field>
|
||||
<Input type="text" placeholder="Masukkan golongan darah" v-model="subject['blood-type']" />
|
||||
</Field>
|
||||
</Cell>
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1,28 @@
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import EarlyEntry from './early-entry.vue'
|
||||
import EarlyRehabEntry from './early-rehab-entry.vue'
|
||||
import FunctionEntry from './function-entry.vue'
|
||||
|
||||
/**
|
||||
* Props:
|
||||
* - excludeFields: daftar nama field yang ingin disembunyikan
|
||||
*/
|
||||
const props = defineProps<{
|
||||
type: 'early' | 'early-rehab' | 'function'
|
||||
excludeFields?: string[]
|
||||
}>()
|
||||
|
||||
const componentMap = {
|
||||
early: EarlyEntry,
|
||||
'early-rehab': EarlyRehabEntry,
|
||||
function: FunctionEntry,
|
||||
}
|
||||
|
||||
// Komponen aktif berdasarkan type
|
||||
const ActiveComponent = computed(() => componentMap[props.type])
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<component :is="ActiveComponent" :exclude-fields="excludeFields" />
|
||||
</template>
|
||||
@@ -0,0 +1,172 @@
|
||||
<script setup lang="ts">
|
||||
import Block from '~/components/pub/my-ui/form/block.vue'
|
||||
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'
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<form id="entry-form">
|
||||
<div class="mb-5 border-b border-b-slate-300 pb-3 text-lg xl:text-xl">
|
||||
<Block>
|
||||
<FieldGroup>
|
||||
<Label dynamic>Sudah Vaksin</Label>
|
||||
<Field>
|
||||
<Select
|
||||
:options="[
|
||||
{ label: 'Sudah', value: 'yes' },
|
||||
{ label: 'Belum', value: 'no' },
|
||||
]"
|
||||
/>
|
||||
</Field>
|
||||
</FieldGroup>
|
||||
|
||||
<FieldGroup>
|
||||
<Label dynamic>Kasus</Label>
|
||||
<Field>
|
||||
<Select
|
||||
:options="[
|
||||
{ label: 'Baru', value: 'baru' },
|
||||
{ label: 'Lama', value: 'lama' },
|
||||
]"
|
||||
/>
|
||||
</Field>
|
||||
</FieldGroup>
|
||||
|
||||
<FieldGroup>
|
||||
<Label dynamic>Kunjungan</Label>
|
||||
<Field>
|
||||
<Select
|
||||
:options="[
|
||||
{ label: 'Baru', value: 'baru' },
|
||||
{ label: 'Lama', value: 'lama' },
|
||||
]"
|
||||
/>
|
||||
</Field>
|
||||
</FieldGroup>
|
||||
|
||||
<!-- Riwayat Penyakit -->
|
||||
<FieldGroup :column="2">
|
||||
<Label dynamic>Keluhan Utama</Label>
|
||||
<Field>
|
||||
<Textarea placeholder="Masukkan anamnesa pasien" />
|
||||
</Field>
|
||||
</FieldGroup>
|
||||
<FieldGroup :column="2">
|
||||
<Label dynamic>Riwayat Penyakit</Label>
|
||||
<Field>
|
||||
<Textarea
|
||||
placeholder="Masukkan anamnesa pasien (Riwayat Penyakit Sekarang, Dahulu, Pengobatan, Keluarga, dll)"
|
||||
/>
|
||||
</Field>
|
||||
</FieldGroup>
|
||||
|
||||
<FieldGroup :column="2">
|
||||
<Label dynamic>SpO₂</Label>
|
||||
<Field>
|
||||
<Input type="number" placeholder="%" />
|
||||
</Field>
|
||||
</FieldGroup>
|
||||
|
||||
<FieldGroup :column="2">
|
||||
<Label dynamic>Tekanan Darah Sistol</Label>
|
||||
<Field>
|
||||
<Input type="number" placeholder="mmHg" />
|
||||
</Field>
|
||||
</FieldGroup>
|
||||
|
||||
<FieldGroup :column="2">
|
||||
<Label dynamic>Tekanan Darah Diastol</Label>
|
||||
<Field>
|
||||
<Input type="number" placeholder="mmHg" />
|
||||
</Field>
|
||||
</FieldGroup>
|
||||
|
||||
<FieldGroup :column="2">
|
||||
<Label dynamic>Respirasi</Label>
|
||||
<Field>
|
||||
<Input type="number" placeholder="kali/menit" />
|
||||
</Field>
|
||||
</FieldGroup>
|
||||
|
||||
<FieldGroup :column="2">
|
||||
<Label dynamic>Nadi</Label>
|
||||
<Field>
|
||||
<Input type="number" placeholder="kali/menit" />
|
||||
</Field>
|
||||
</FieldGroup>
|
||||
|
||||
<FieldGroup :column="2">
|
||||
<Label dynamic>Temperatur</Label>
|
||||
<Field>
|
||||
<Input type="number" placeholder="℃" />
|
||||
</Field>
|
||||
</FieldGroup>
|
||||
|
||||
<FieldGroup :column="2">
|
||||
<Label dynamic>Berat Badan</Label>
|
||||
<Field>
|
||||
<Input type="number" placeholder="Kg" />
|
||||
</Field>
|
||||
</FieldGroup>
|
||||
|
||||
<FieldGroup :column="2">
|
||||
<Label dynamic>Tinggi Badan</Label>
|
||||
<Field>
|
||||
<Input type="number" placeholder="Cm" />
|
||||
</Field>
|
||||
</FieldGroup>
|
||||
|
||||
<FieldGroup :column="2">
|
||||
<Label dynamic>Golongan Darah</Label>
|
||||
<Field>
|
||||
<Select :options="bloodGroups" />
|
||||
</Field>
|
||||
</FieldGroup>
|
||||
|
||||
<FieldGroup :column="2">
|
||||
<Label dynamic>Pemeriksaan Fisik (Yang Mendukung)</Label>
|
||||
<Field>
|
||||
<Textarea placeholder="Masukkan pemeriksaan fisik" />
|
||||
</Field>
|
||||
</FieldGroup>
|
||||
|
||||
<!-- Prosedur -->
|
||||
<FieldGroup :column="2">
|
||||
<Label dynamic>Diagnosa (ICD-X)</Label>
|
||||
<Field>
|
||||
<button class="rounded bg-orange-100 px-3 py-1 text-orange-600">+ Pilih Diagnosa</button>
|
||||
</Field>
|
||||
</FieldGroup>
|
||||
<!-- Diagnosa -->
|
||||
<FieldGroup :column="2">
|
||||
<Label dynamic>Diagnosa (ICD-X)</Label>
|
||||
<Field>
|
||||
<button class="rounded bg-orange-100 px-3 py-1 text-orange-600">+ Pilih Diagnosa</button>
|
||||
</Field>
|
||||
</FieldGroup>
|
||||
|
||||
<FieldGroup :column="3">
|
||||
<Label dynamic>Diagnosa Medis</Label>
|
||||
<Field>
|
||||
<Textarea />
|
||||
</Field>
|
||||
</FieldGroup>
|
||||
|
||||
<FieldGroup :column="3">
|
||||
<Label dynamic>Rencana Awal Medis</Label>
|
||||
<Field>
|
||||
<Textarea />
|
||||
</Field>
|
||||
</FieldGroup>
|
||||
|
||||
<FieldGroup :column="3">
|
||||
<Label dynamic>Terapi</Label>
|
||||
<Field>
|
||||
<Textarea />
|
||||
</Field>
|
||||
</FieldGroup>
|
||||
</Block>
|
||||
</div>
|
||||
</form>
|
||||
</template>
|
||||
@@ -0,0 +1,119 @@
|
||||
import type {
|
||||
Col,
|
||||
KeyLabel,
|
||||
RecComponent,
|
||||
RecStrFuncComponent,
|
||||
RecStrFuncUnknown,
|
||||
Th,
|
||||
} from '~/components/pub/my-ui/data/types'
|
||||
import { defineAsyncComponent } from 'vue'
|
||||
|
||||
type SmallDetailDto = any
|
||||
|
||||
const action = defineAsyncComponent(() => import('~/components/pub/my-ui/data/dropdown-action-dud.vue'))
|
||||
|
||||
export const cols: Col[] = [
|
||||
{},
|
||||
{},
|
||||
{},
|
||||
{ width: 100 },
|
||||
{ width: 120 },
|
||||
{},
|
||||
{},
|
||||
{},
|
||||
{ width: 100 },
|
||||
{ width: 100 },
|
||||
{},
|
||||
{ width: 50 },
|
||||
]
|
||||
|
||||
export const header: Th[][] = [
|
||||
[
|
||||
{ label: 'Nama' },
|
||||
{ label: 'Rekam Medis' },
|
||||
{ label: 'KTP' },
|
||||
{ label: 'Tgl Lahir' },
|
||||
{ label: 'Umur' },
|
||||
{ label: 'JK' },
|
||||
{ label: 'Pendidikan' },
|
||||
{ label: 'Status' },
|
||||
{ label: '' },
|
||||
],
|
||||
]
|
||||
|
||||
export const keys = [
|
||||
'name',
|
||||
'medicalRecord_number',
|
||||
'identity_number',
|
||||
'birth_date',
|
||||
'patient_age',
|
||||
'gender',
|
||||
'education',
|
||||
'status',
|
||||
'action',
|
||||
]
|
||||
|
||||
export const delKeyNames: KeyLabel[] = [
|
||||
{ key: 'code', label: 'Kode' },
|
||||
{ key: 'name', label: 'Nama' },
|
||||
]
|
||||
|
||||
export const funcParsed: RecStrFuncUnknown = {
|
||||
name: (rec: unknown): unknown => {
|
||||
const recX = rec as SmallDetailDto
|
||||
return `${recX.firstName} ${recX.middleName || ''} ${recX.lastName || ''}`
|
||||
},
|
||||
identity_number: (rec: unknown): unknown => {
|
||||
const recX = rec as SmallDetailDto
|
||||
if (recX.identity_number?.substring(0, 5) === 'BLANK') {
|
||||
return '(TANPA NIK)'
|
||||
}
|
||||
return recX.identity_number
|
||||
},
|
||||
birth_date: (rec: unknown): unknown => {
|
||||
const recX = rec as SmallDetailDto
|
||||
if (typeof recX.birth_date == 'object' && recX.birth_date) {
|
||||
return (recX.birth_date as Date).toLocaleDateString()
|
||||
} else if (typeof recX.birth_date == 'string') {
|
||||
return (recX.birth_date as string).substring(0, 10)
|
||||
}
|
||||
return recX.birth_date
|
||||
},
|
||||
patient_age: (rec: unknown): unknown => {
|
||||
const recX = rec as SmallDetailDto
|
||||
return recX.birth_date?.split('T')[0]
|
||||
},
|
||||
gender: (rec: unknown): unknown => {
|
||||
const recX = rec as SmallDetailDto
|
||||
if (typeof recX?.gender_code !== 'number' && recX?.gender_code !== '') {
|
||||
return 'Tidak Diketahui'
|
||||
}
|
||||
return recX.gender_code
|
||||
},
|
||||
education: (rec: unknown): unknown => {
|
||||
const recX = rec as SmallDetailDto
|
||||
if (typeof recX.education_code == 'number' && recX.education_code >= 0) {
|
||||
return recX.education_code
|
||||
} else if (typeof recX.education_code) {
|
||||
return recX.education_code
|
||||
}
|
||||
return '-'
|
||||
},
|
||||
}
|
||||
|
||||
export const funcComponent: RecStrFuncComponent = {
|
||||
action(rec, idx) {
|
||||
const res: RecComponent = {
|
||||
idx,
|
||||
rec: rec as object,
|
||||
component: action,
|
||||
}
|
||||
return res
|
||||
},
|
||||
}
|
||||
|
||||
export const funcHtml: RecStrFuncUnknown = {
|
||||
patient_address(_rec) {
|
||||
return '-'
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
<script setup lang="ts">
|
||||
import { cols, funcComponent, funcHtml, funcParsed, header, keys } from './list-cfg'
|
||||
|
||||
defineProps<{
|
||||
data: any[]
|
||||
}>()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<PubMyUiDataTable
|
||||
:rows="data"
|
||||
:cols="cols"
|
||||
:header="header"
|
||||
:keys="keys"
|
||||
:func-parsed="funcParsed"
|
||||
:func-html="funcHtml"
|
||||
:func-component="funcComponent"
|
||||
/>
|
||||
</template>
|
||||
@@ -4,7 +4,7 @@ import Block from '~/components/pub/my-ui/doc-entry/block.vue'
|
||||
import Cell from '~/components/pub/my-ui/doc-entry/cell.vue'
|
||||
import Field from '~/components/pub/my-ui/doc-entry/field.vue'
|
||||
import Label from '~/components/pub/my-ui/doc-entry/label.vue'
|
||||
// import Combobox from '~/components/pub/my-ui/form/combobox.vue'
|
||||
import Combobox from '~/components/pub/my-ui/form/combobox.vue'
|
||||
|
||||
// Types
|
||||
import type { SpecialistFormData } from '~/schemas/specialist.schema.ts'
|
||||
@@ -47,7 +47,7 @@ const [unit, unitAttrs] = defineField('unit_id')
|
||||
if (props.values) {
|
||||
if (props.values.code !== undefined) code.value = props.values.code
|
||||
if (props.values.name !== undefined) name.value = props.values.name
|
||||
if (props.values.unit_id !== undefined) unit.value = props.values.unit_id
|
||||
if (props.values.unit_id !== undefined) unit.value = props.values.unit_id ? String(props.values.unit_id) : null
|
||||
}
|
||||
|
||||
const resetForm = () => {
|
||||
@@ -61,7 +61,7 @@ function onSubmitForm(values: any) {
|
||||
const formData: SpecialistFormData = {
|
||||
name: name.value || '',
|
||||
code: code.value || '',
|
||||
unit_id: unit.value || '',
|
||||
unit_id: unit.value ? Number(unit.value) : null,
|
||||
}
|
||||
emit('submit', formData, resetForm)
|
||||
}
|
||||
@@ -73,47 +73,62 @@ function onCancelForm() {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<form id="form-specialist" @submit.prevent>
|
||||
<Block labelSize="thin" class="!mb-2.5 !pt-0 xl:!mb-3" :colCount="1">
|
||||
<form
|
||||
id="form-specialist"
|
||||
@submit.prevent
|
||||
>
|
||||
<Block
|
||||
labelSize="thin"
|
||||
class="!mb-2.5 !pt-0 xl:!mb-3"
|
||||
:colCount="1"
|
||||
>
|
||||
<Cell>
|
||||
<Label height="compact">Kode</Label>
|
||||
<Field :errMessage="errors.code">
|
||||
<Input id="code" v-model="code" v-bind="codeAttrs" :disabled="isLoading || isReadonly" />
|
||||
<Input
|
||||
id="code"
|
||||
v-model="code"
|
||||
v-bind="codeAttrs"
|
||||
:disabled="isLoading || isReadonly"
|
||||
/>
|
||||
</Field>
|
||||
</Cell>
|
||||
<Cell>
|
||||
<Label height="compact">Nama</Label>
|
||||
<Field :errMessage="errors.name">
|
||||
<Input id="name" v-model="name" v-bind="nameAttrs" :disabled="isLoading || isReadonly" />
|
||||
<Input
|
||||
id="name"
|
||||
v-model="name"
|
||||
v-bind="nameAttrs"
|
||||
:disabled="isLoading || isReadonly"
|
||||
/>
|
||||
</Field>
|
||||
</Cell>
|
||||
<Cell>
|
||||
<Label height="compact">Unit</Label>
|
||||
<Field :errMessage="errors.unit">
|
||||
<!-- <Combobox
|
||||
<Combobox
|
||||
id="unit"
|
||||
v-model="unit"
|
||||
v-bind="unitAttrs"
|
||||
:items="units"
|
||||
:disabled="isLoading || isReadonly"
|
||||
placeholder="Pilih Unit"
|
||||
search-placeholder="Cari unit"
|
||||
empty-message="Unit tidak ditemukan"
|
||||
v-model="unit"
|
||||
v-bind="unitAttrs"
|
||||
:items="units"
|
||||
:disabled="isLoading || isReadonly"
|
||||
/> -->
|
||||
<Select
|
||||
id="unit"
|
||||
icon-name="i-lucide-chevron-down"
|
||||
placeholder="Pilih unit"
|
||||
v-model="unit"
|
||||
v-bind="unitAttrs"
|
||||
:items="units"
|
||||
:disabled="isLoading || isReadonly"
|
||||
/>
|
||||
</Field>
|
||||
</Cell>
|
||||
</Block>
|
||||
<div class="my-2 flex justify-end gap-2 py-2">
|
||||
<Button type="button" variant="secondary" class="w-[120px]" @click="onCancelForm"> Kembali </Button>
|
||||
<Button
|
||||
type="button"
|
||||
variant="secondary"
|
||||
class="w-[120px]"
|
||||
@click="onCancelForm"
|
||||
>
|
||||
Kembali
|
||||
</Button>
|
||||
<Button
|
||||
v-if="!isReadonly"
|
||||
type="button"
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
<script setup lang="ts">
|
||||
import type { PaginationMeta } from '~/components/pub/my-ui/pagination/pagination.type'
|
||||
// Components
|
||||
import PaginationView from '~/components/pub/my-ui/pagination/pagination-view.vue'
|
||||
|
||||
// Types
|
||||
import type { PaginationMeta } from '~/components/pub/my-ui/pagination/pagination.type'
|
||||
|
||||
// Configs
|
||||
import { cols, funcComponent, funcHtml, funcParsed, header, keys } from './list-cfg'
|
||||
|
||||
interface Props {
|
||||
|
||||
@@ -4,7 +4,7 @@ import Block from '~/components/pub/my-ui/doc-entry/block.vue'
|
||||
import Cell from '~/components/pub/my-ui/doc-entry/cell.vue'
|
||||
import Field from '~/components/pub/my-ui/doc-entry/field.vue'
|
||||
import Label from '~/components/pub/my-ui/doc-entry/label.vue'
|
||||
// import Combobox from '~/components/pub/my-ui/form/combobox.vue'
|
||||
import Combobox from '~/components/pub/my-ui/form/combobox.vue'
|
||||
|
||||
// Types
|
||||
import type { SubspecialistFormData } from '~/schemas/subspecialist.schema.ts'
|
||||
@@ -47,7 +47,8 @@ const [specialist, specialistAttrs] = defineField('specialist_id')
|
||||
if (props.values) {
|
||||
if (props.values.code !== undefined) code.value = props.values.code
|
||||
if (props.values.name !== undefined) name.value = props.values.name
|
||||
if (props.values.specialist_id !== undefined) specialist.value = props.values.specialist_id
|
||||
if (props.values.specialist_id !== undefined)
|
||||
specialist.value = props.values.specialist_id ? String(props.values.specialist_id) : null
|
||||
}
|
||||
|
||||
const resetForm = () => {
|
||||
@@ -61,7 +62,7 @@ function onSubmitForm(values: any) {
|
||||
const formData: SubspecialistFormData = {
|
||||
name: name.value || '',
|
||||
code: code.value || '',
|
||||
specialist_id: specialist.value || '',
|
||||
specialist_id: specialist.value ? Number(specialist.value) : null,
|
||||
}
|
||||
emit('submit', formData, resetForm)
|
||||
}
|
||||
@@ -73,47 +74,62 @@ function onCancelForm() {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<form id="form-specialist" @submit.prevent>
|
||||
<Block labelSize="thin" class="!mb-2.5 !pt-0 xl:!mb-3" :colCount="1">
|
||||
<form
|
||||
id="form-specialist"
|
||||
@submit.prevent
|
||||
>
|
||||
<Block
|
||||
labelSize="thin"
|
||||
class="!mb-2.5 !pt-0 xl:!mb-3"
|
||||
:colCount="1"
|
||||
>
|
||||
<Cell>
|
||||
<Label height="compact">Kode</Label>
|
||||
<Field :errMessage="errors.code">
|
||||
<Input id="code" v-model="code" v-bind="codeAttrs" :disabled="isLoading || isReadonly" />
|
||||
<Input
|
||||
id="code"
|
||||
v-model="code"
|
||||
v-bind="codeAttrs"
|
||||
:disabled="isLoading || isReadonly"
|
||||
/>
|
||||
</Field>
|
||||
</Cell>
|
||||
<Cell>
|
||||
<Label height="compact">Nama</Label>
|
||||
<Field :errMessage="errors.name">
|
||||
<Input id="name" v-model="name" v-bind="nameAttrs" :disabled="isLoading || isReadonly" />
|
||||
<Input
|
||||
id="name"
|
||||
v-model="name"
|
||||
v-bind="nameAttrs"
|
||||
:disabled="isLoading || isReadonly"
|
||||
/>
|
||||
</Field>
|
||||
</Cell>
|
||||
<Cell>
|
||||
<Label height="compact">Spesialis</Label>
|
||||
<Field :errMessage="errors.specialist">
|
||||
<!-- <Combobox
|
||||
id="specialis"
|
||||
<Combobox
|
||||
id="specialist"
|
||||
v-model="specialist"
|
||||
v-bind="specialistAttrs"
|
||||
:items="specialists"
|
||||
:disabled="isLoading || isReadonly"
|
||||
placeholder="Pilih spesialis"
|
||||
search-placeholder="Cari spesialis"
|
||||
empty-message="Spesialis tidak ditemukan"
|
||||
v-model="specialist"
|
||||
v-bind="specialistAttrs"
|
||||
:items="specialists"
|
||||
:disabled="isLoading || isReadonly"
|
||||
/> -->
|
||||
<Select
|
||||
id="specialist"
|
||||
icon-name="i-lucide-chevron-down"
|
||||
placeholder="Pilih spesialis"
|
||||
v-model="specialist"
|
||||
v-bind="specialistAttrs"
|
||||
:items="specialists"
|
||||
:disabled="isLoading || isReadonly"
|
||||
/>
|
||||
</Field>
|
||||
</Cell>
|
||||
</Block>
|
||||
<div class="my-2 flex justify-end gap-2 py-2">
|
||||
<Button type="button" variant="secondary" class="w-[120px]" @click="onCancelForm"> Kembali </Button>
|
||||
<Button
|
||||
type="button"
|
||||
variant="secondary"
|
||||
class="w-[120px]"
|
||||
@click="onCancelForm"
|
||||
>
|
||||
Kembali
|
||||
</Button>
|
||||
<Button
|
||||
v-if="!isReadonly"
|
||||
type="button"
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
<script setup lang="ts">
|
||||
import type { PaginationMeta } from '~/components/pub/my-ui/pagination/pagination.type'
|
||||
// Components
|
||||
import PaginationView from '~/components/pub/my-ui/pagination/pagination-view.vue'
|
||||
|
||||
// Types
|
||||
import type { PaginationMeta } from '~/components/pub/my-ui/pagination/pagination.type'
|
||||
|
||||
// Configs
|
||||
import { cols, funcComponent, funcHtml, funcParsed, header, keys } from './list-cfg'
|
||||
|
||||
interface Props {
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
<script setup lang="ts">
|
||||
import type { PaginationMeta } from '~/components/pub/my-ui/pagination/pagination.type'
|
||||
// Components
|
||||
import PaginationView from '~/components/pub/my-ui/pagination/pagination-view.vue'
|
||||
|
||||
// Types
|
||||
import type { PaginationMeta } from '~/components/pub/my-ui/pagination/pagination.type'
|
||||
|
||||
// Configs
|
||||
import { cols, funcComponent, funcHtml, funcParsed, header, keys } from './list-cfg'
|
||||
|
||||
interface Props {
|
||||
|
||||
@@ -4,6 +4,7 @@ import Block from '~/components/pub/my-ui/doc-entry/block.vue'
|
||||
import Cell from '~/components/pub/my-ui/doc-entry/cell.vue'
|
||||
import Field from '~/components/pub/my-ui/doc-entry/field.vue'
|
||||
import Label from '~/components/pub/my-ui/doc-entry/label.vue'
|
||||
import Combobox from '~/components/pub/my-ui/form/combobox.vue'
|
||||
|
||||
// Types
|
||||
import type { UnitFormData } from '~/schemas/unit.schema.ts'
|
||||
@@ -15,6 +16,7 @@ import { useForm } from 'vee-validate'
|
||||
|
||||
interface Props {
|
||||
schema: z.ZodSchema<any>
|
||||
installations: any[]
|
||||
values: any
|
||||
isLoading?: boolean
|
||||
isReadonly?: boolean
|
||||
@@ -33,19 +35,20 @@ const { defineField, errors, meta } = useForm({
|
||||
initialValues: {
|
||||
code: '',
|
||||
name: '',
|
||||
installation: '',
|
||||
installation_id: 0,
|
||||
} as Partial<UnitFormData>,
|
||||
})
|
||||
|
||||
const [code, codeAttrs] = defineField('code')
|
||||
const [name, nameAttrs] = defineField('name')
|
||||
const [installation, installationAttrs] = defineField('installation')
|
||||
const [installation, installationAttrs] = defineField('installation_id')
|
||||
|
||||
// Fill fields from props.values if provided
|
||||
if (props.values) {
|
||||
if (props.values.code !== undefined) code.value = props.values.code
|
||||
if (props.values.name !== undefined) name.value = props.values.name
|
||||
if (props.values.installation !== undefined) installation.value = props.values.installation
|
||||
if (props.values.installation_id !== undefined)
|
||||
installation.value = props.values.installation_id ? String(props.values.installation_id) : null
|
||||
}
|
||||
|
||||
const resetForm = () => {
|
||||
@@ -59,7 +62,7 @@ function onSubmitForm(values: any) {
|
||||
const formData: UnitFormData = {
|
||||
name: name.value || '',
|
||||
code: code.value || '',
|
||||
installation: installation.value || '',
|
||||
installation_id: installation.value ? Number(installation.value) : null,
|
||||
}
|
||||
emit('submit', formData, resetForm)
|
||||
}
|
||||
@@ -85,6 +88,21 @@ function onCancelForm() {
|
||||
<Input id="name" v-model="name" v-bind="nameAttrs" :disabled="isLoading || isReadonly" />
|
||||
</Field>
|
||||
</Cell>
|
||||
<Cell>
|
||||
<Label height="compact">Instalasi</Label>
|
||||
<Field :errMessage="errors.installation">
|
||||
<Combobox
|
||||
id="installation"
|
||||
v-model="installation"
|
||||
v-bind="installationAttrs"
|
||||
:items="installations"
|
||||
:disabled="isLoading || isReadonly"
|
||||
placeholder="Pilih Instalasi"
|
||||
search-placeholder="Cari instalasi"
|
||||
empty-message="Instalasi tidak ditemukan"
|
||||
/>
|
||||
</Field>
|
||||
</Cell>
|
||||
</Block>
|
||||
<div class="my-2 flex justify-end gap-2 py-2">
|
||||
<Button type="button" variant="secondary" class="w-[120px]" @click="onCancelForm"> Kembali </Button>
|
||||
|
||||
@@ -28,8 +28,9 @@ export const funcParsed: RecStrFuncUnknown = {
|
||||
const recX = rec as SmallDetailDto
|
||||
return `${recX.name}`.trim()
|
||||
},
|
||||
installation: (_rec: unknown): unknown => {
|
||||
return '-'
|
||||
installation: (rec: unknown): unknown => {
|
||||
const recX = rec as SmallDetailDto
|
||||
return recX.installation?.name || recX.Installation?.name || '-'
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
<script setup lang="ts">
|
||||
import type { PaginationMeta } from '~/components/pub/my-ui/pagination/pagination.type'
|
||||
// Components
|
||||
import PaginationView from '~/components/pub/my-ui/pagination/pagination-view.vue'
|
||||
|
||||
// Types
|
||||
import type { PaginationMeta } from '~/components/pub/my-ui/pagination/pagination.type'
|
||||
|
||||
// Configs
|
||||
import { cols, funcComponent, funcHtml, funcParsed, header, keys } from './list-cfg'
|
||||
|
||||
interface Props {
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
<script setup lang="ts">
|
||||
import type { PaginationMeta } from '~/components/pub/my-ui/pagination/pagination.type'
|
||||
// Components
|
||||
import PaginationView from '~/components/pub/my-ui/pagination/pagination-view.vue'
|
||||
|
||||
// Types
|
||||
import type { PaginationMeta } from '~/components/pub/my-ui/pagination/pagination.type'
|
||||
|
||||
// Configs
|
||||
import { cols, funcComponent, funcHtml, funcParsed, header, keys } from './list-cfg'
|
||||
|
||||
interface Props {
|
||||
|
||||
@@ -0,0 +1,105 @@
|
||||
<script setup lang="ts">
|
||||
// Components
|
||||
import Block from '~/components/pub/my-ui/doc-entry/block.vue'
|
||||
import Cell from '~/components/pub/my-ui/doc-entry/cell.vue'
|
||||
import Field from '~/components/pub/my-ui/doc-entry/field.vue'
|
||||
import Label from '~/components/pub/my-ui/doc-entry/label.vue'
|
||||
import Button from '~/components/pub/ui/button/Button.vue'
|
||||
|
||||
// Types
|
||||
import type { InfraFormData } from '~/schemas/infra.schema'
|
||||
|
||||
// Helpers
|
||||
import type z from 'zod'
|
||||
import { useForm } from 'vee-validate'
|
||||
import { toTypedSchema } from '@vee-validate/zod'
|
||||
|
||||
interface Props {
|
||||
schema: z.ZodSchema<any>
|
||||
values: any
|
||||
isLoading?: boolean
|
||||
isReadonly?: boolean
|
||||
}
|
||||
|
||||
const props = defineProps<Props>()
|
||||
const isLoading = props.isLoading !== undefined ? props.isLoading : false
|
||||
const isReadonly = props.isReadonly !== undefined ? props.isReadonly : false
|
||||
const emit = defineEmits<{
|
||||
submit: [values: InfraFormData, resetForm: () => void]
|
||||
cancel: [resetForm: () => void]
|
||||
}>()
|
||||
|
||||
const { defineField, errors, meta } = useForm({
|
||||
validationSchema: toTypedSchema(props.schema),
|
||||
initialValues: {
|
||||
code: '',
|
||||
name: '',
|
||||
infraGroup_code: 'counter',
|
||||
parent_id: null,
|
||||
} as Partial<InfraFormData>,
|
||||
})
|
||||
|
||||
const [code, codeAttrs] = defineField('code')
|
||||
const [name, nameAttrs] = defineField('name')
|
||||
const [infraGroup_code, infraGroupAttrs] = defineField('infraGroup_code')
|
||||
const [parent_id, parentIdAttrs] = defineField('parent_id')
|
||||
|
||||
if (props.values) {
|
||||
if (props.values.code !== undefined) code.value = props.values.code
|
||||
if (props.values.name !== undefined) name.value = props.values.name
|
||||
if (props.values.infraGroup_code !== undefined) infraGroup_code.value = props.values.infraGroup_code
|
||||
if (props.values.parent_id !== undefined) parent_id.value = props.values.parent_id
|
||||
}
|
||||
|
||||
const resetForm = () => {
|
||||
code.value = ''
|
||||
name.value = ''
|
||||
infraGroup_code.value = 'counter'
|
||||
parent_id.value = null
|
||||
}
|
||||
|
||||
function onSubmitForm() {
|
||||
const formData: InfraFormData = {
|
||||
code: code.value || '',
|
||||
name: name.value || '',
|
||||
infraGroup_code: infraGroup_code.value || 'counter',
|
||||
parent_id: parent_id.value || null,
|
||||
}
|
||||
emit('submit', formData, resetForm)
|
||||
}
|
||||
|
||||
function onCancelForm() {
|
||||
emit('cancel', resetForm)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<form id="form-counter" @submit.prevent>
|
||||
<Block labelSize="thin" class="!mb-2.5 !pt-0 xl:!mb-3" :colCount="1">
|
||||
<Cell>
|
||||
<Label height="compact">Kode</Label>
|
||||
<Field :errMessage="errors.code">
|
||||
<Input id="code" v-model="code" v-bind="codeAttrs" :disabled="isLoading || isReadonly" />
|
||||
</Field>
|
||||
</Cell>
|
||||
<Cell>
|
||||
<Label height="compact">Nama</Label>
|
||||
<Field :errMessage="errors.name">
|
||||
<Input id="name" v-model="name" v-bind="nameAttrs" :disabled="isLoading || isReadonly" />
|
||||
</Field>
|
||||
</Cell>
|
||||
</Block>
|
||||
<div class="my-2 flex justify-end gap-2 py-2">
|
||||
<Button type="button" variant="secondary" class="w-[120px]" @click="onCancelForm"> Kembali </Button>
|
||||
<Button
|
||||
v-if="!isReadonly"
|
||||
type="button"
|
||||
class="w-[120px]"
|
||||
:disabled="isLoading || !meta.valid"
|
||||
@click="onSubmitForm"
|
||||
>
|
||||
Simpan
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
</template>
|
||||
@@ -0,0 +1,47 @@
|
||||
import type {
|
||||
Col,
|
||||
KeyLabel,
|
||||
RecComponent,
|
||||
RecStrFuncComponent,
|
||||
RecStrFuncUnknown,
|
||||
Th,
|
||||
} from '~/components/pub/my-ui/data/types'
|
||||
import { defineAsyncComponent } from 'vue'
|
||||
|
||||
type SmallDetailDto = any
|
||||
|
||||
const action = defineAsyncComponent(() => import('~/components/pub/my-ui/data/dropdown-action-dud.vue'))
|
||||
|
||||
export const cols: Col[] = [{ width: 100 }, {}, {}, { width: 50 }]
|
||||
|
||||
export const header: Th[][] = [[{ label: 'Kode' }, { label: 'Nama' }, { label: 'Counter Induk' }, { label: '' }]]
|
||||
|
||||
export const keys = ['code', 'name', 'parent', 'action']
|
||||
|
||||
export const delKeyNames: KeyLabel[] = [
|
||||
{ key: 'code', label: 'Kode' },
|
||||
{ key: 'name', label: 'Nama' },
|
||||
]
|
||||
|
||||
export const funcParsed: RecStrFuncUnknown = {
|
||||
parent: (rec: unknown): unknown => {
|
||||
const recX = rec as SmallDetailDto
|
||||
return recX.parent?.name || '-'
|
||||
},
|
||||
}
|
||||
|
||||
export const funcComponent: RecStrFuncComponent = {
|
||||
action(rec, idx) {
|
||||
const res: RecComponent = {
|
||||
idx,
|
||||
rec: rec as object,
|
||||
component: action,
|
||||
props: {
|
||||
size: 'sm',
|
||||
},
|
||||
}
|
||||
return res
|
||||
},
|
||||
}
|
||||
|
||||
export const funcHtml: RecStrFuncUnknown = {}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user