Merge branch 'feat/consultation-82' into fe-prescription-56

This commit is contained in:
2025-10-08 08:00:01 +07:00
252 changed files with 10818 additions and 997 deletions
+3 -1
View File
@@ -5,5 +5,7 @@
"trailingComma": "all",
"printWidth": 120,
"semi": false,
"plugins": ["prettier-plugin-tailwindcss"]
"plugins": ["prettier-plugin-tailwindcss"],
"htmlWhitespaceSensitivity": "ignore",
"singleAttributePerLine": true
}
+2 -1
View File
@@ -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>
+105
View File
@@ -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>
+47
View File
@@ -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 = {}
+36
View File
@@ -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>
+108
View File
@@ -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>
+37
View File
@@ -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 = {}
+41
View File
@@ -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>
+105
View File
@@ -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>
+47
View File
@@ -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 = {}
+36
View File
@@ -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>
+122
View File
@@ -0,0 +1,122 @@
<script setup lang="ts">
// Components
import * as DE from '~/components/pub/my-ui/doc-entry'
// Types
import type { ConsultationFormData } from '~/schemas/consultation.schema.ts'
// Helpers
import type z from 'zod'
import { toTypedSchema } from '@vee-validate/zod'
import { useForm } from 'vee-validate'
import Textarea from '~/components/pub/ui/textarea/Textarea.vue'
interface Props {
schema: z.ZodSchema<any>
values: any
units: { value: string; label: string }[]
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: ConsultationFormData, resetForm: () => void]
cancel: [resetForm: () => void]
}>()
const { defineField, errors, meta } = useForm({
validationSchema: toTypedSchema(props.schema),
initialValues: {
id: 0,
encounter_id: 0,
problem: '',
unit_id: 0,
} as Partial<ConsultationFormData>,
})
const [unit_id, unitAttrs] = defineField('unit_id')
const [problem, problemAttrs] = defineField('problem')
// Fill fields from props.values if provided
if (props.values) {
if (props.values.unit_id !== undefined) unit_id.value = props.values.unit_id
if (props.values.code !== undefined) problem.value = props.values.problem
}
const resetForm = () => {
unit_id.value = 0
problem.value = ''
}
// Form submission handler
function onSubmitForm(values: any) {
const formData: ConsultationFormData = {
id: 0,
encounter_id: 0,
problem: problem.value || '',
unit_id: unit_id.value || 0,
}
emit('submit', formData, resetForm)
}
// Form cancel handler
function onCancelForm() {
emit('cancel', resetForm)
}
</script>
<template>
<form id="form-division" @submit.prevent>
<DE.Block labelSize="thin" class="!mb-2.5 !pt-0 xl:!mb-3" :colCount="3" :cellFlex="false">
<DE.Cell>
<DE.Label>Dokter Asal</DE.Label>
<DE.Field :errMessage="errors.code">
<Input readonly />
</DE.Field>
</DE.Cell>
<DE.Cell>
<DE.Label>Tanggal</DE.Label>
<DE.Field :errMessage="errors.code">
<Input readonly />
</DE.Field>
</DE.Cell>
<DE.Cell>
<DE.Label>Unit</DE.Label>
<DE.Field :errMessage="errors.unit_id">
{{ errors.unit_id }}
<Select
id="strUnit_id"
v-model.number="unit_id"
icon-name="i-lucide-chevron-down"
placeholder="Pilih kelompok obat"
v-bind="unitAttrs"
:items="props.units || []"
:disabled="isLoading || isReadonly"
/>
<!-- <Input type="number" id="unit_id" v-model.number="unit_id" v-bind="unitAttrs" :disabled="isLoading || isReadonly" /> -->
</DE.Field>
</DE.Cell>
<DE.Cell :colSpan="3">
<DE.Label>Uraian</DE.Label>
<DE.Field :errMessage="errors.problem">
<Textarea id="problem" v-model="problem" v-bind="problemAttrs" :disabled="isLoading || isReadonly" />
</DE.Field>
</DE.Cell>
</DE.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>
+40
View File
@@ -0,0 +1,40 @@
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-ud.vue'))
export const cols: Col[] = [{ width: 100 }, {}, {}, {}, { width: 50 }]
export const header: Th[][] = [[{ label: 'Tanggal' }, { label: 'Dokter' }, { label: 'Tujuan' }, { label: 'Pertanyaan' }, { label: 'Jawaban' }, { label: '' }]]
export const keys = ['date', 'dstDoctor.name', 'dstUnit.name', 'case', 'solution', 'action']
export const delKeyNames: KeyLabel[] = [
{ key: 'data', label: 'Tanggal' },
{ key: 'dstDoctor.name', label: 'Dokter' },
]
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 = {}
+36
View File
@@ -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, header, keys } from './list'
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-html="funcHtml"
:func-component="funcComponent"
:skeleton-size="paginationMeta?.pageSize"
/>
<!-- FIXME: pindahkan ke content/division/list.vue -->
<PaginationView :pagination-meta="paginationMeta" @page-change="handlePageChange" />
</div>
</template>
+108
View File
@@ -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>
+37
View File
@@ -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 = {}
+41
View File
@@ -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>
+6 -2
View File
@@ -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>
+36 -33
View File
@@ -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>
+6 -1
View File
@@ -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 {
+125
View File
@@ -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>
+44
View File
@@ -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 = {}
+41
View File
@@ -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>
+43
View File
@@ -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>
+131
View File
@@ -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 '-'
},
}
+19
View File
@@ -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>
+46
View File
@@ -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>
View File
+29
View File
@@ -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>
+7 -2
View File
@@ -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 {
@@ -21,7 +26,7 @@ function handlePageChange(page: number) {
<template>
<div class="space-y-4">
<PubBaseDataTable
<PubMyUiDataTable
:rows="data"
:cols="cols"
:header="header"
+6 -1
View File
@@ -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 {
+6 -1
View File
@@ -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 {
+25 -12
View File
@@ -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>
+1 -1
View File
@@ -41,7 +41,7 @@ export const funcParsed: RecStrFuncUnknown = {
return (rec as SmallDetailDto).medicineMethod?.name || '-'
},
unit: (rec: unknown): unknown => {
return (rec as SmallDetailDto).uom?.name || '-'
return (rec as SmallDetailDto).uom?.name || '-'
},
}
+6 -1
View File
@@ -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>
+218 -36
View File
@@ -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>
+34 -65
View File
@@ -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 = {
+27 -10
View File
@@ -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>
+153
View File
@@ -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>
+236 -139
View File
@@ -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>
+127 -207
View File
@@ -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>
+177 -42
View File
@@ -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>
+195
View File
@@ -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>
+61
View File
@@ -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 = {}
+41
View File
@@ -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>
+1 -1
View File
@@ -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'
+222
View File
@@ -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>
+28
View File
@@ -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>
+172
View File
@@ -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>
+119
View File
@@ -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 '-'
},
}
+19
View File
@@ -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>
View File
View File
+31 -7
View File
@@ -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,18 +73,35 @@ 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>
@@ -104,7 +121,14 @@ function onCancelForm() {
</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"
+6 -1
View File
@@ -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 {
@@ -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,18 +74,35 @@ 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>
@@ -104,7 +122,14 @@ function onCancelForm() {
</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"
+6 -1
View File
@@ -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 {
+6 -1
View File
@@ -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 {
+3 -2
View File
@@ -47,7 +47,8 @@ const [installation, installationAttrs] = defineField('installation_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.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 = () => {
@@ -61,7 +62,7 @@ function onSubmitForm(values: any) {
const formData: UnitFormData = {
name: name.value || '',
code: code.value || '',
installation_id: installation.value || '',
installation_id: installation.value ? Number(installation.value) : null,
}
emit('submit', formData, resetForm)
}
+6 -1
View File
@@ -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 {
+6 -1
View File
@@ -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 {
+105
View File
@@ -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>
+47
View File
@@ -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 = {}
+36
View File
@@ -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,3 @@
<template>
<div>halo</div>
</template>
@@ -0,0 +1,64 @@
<script setup lang="ts">
import type { DataTableLoader } from '~/components/pub/my-ui/data-table/type'
import type { HeaderPrep, RefSearchNav } from '~/components/pub/my-ui/data/types'
import AssesmentFunctionList from '~/components/app/assesment-function/list.vue'
import Header from '~/components/pub/my-ui/nav-header/prep.vue'
const props = defineProps<{
label: string
}>()
const data = ref([])
const refSearchNav: RefSearchNav = {
onClick: () => {
// open filter modal
},
onInput: (_val: string) => {
// filter patient list
},
onClear: () => {
// clear url param
},
}
// Loading state management
const isLoading = reactive<DataTableLoader>({
isTableLoading: false,
})
const recId = ref<number>(0)
const recAction = ref<string>('')
const recItem = ref<any>(null)
const hreaderPrep: HeaderPrep = {
title: props.label,
icon: 'i-lucide-users',
addNav: {
label: 'Tambah',
onClick: () => navigateTo('/rehab/registration-queue/sep-prosedur/add'),
},
}
async function getPatientList() {
const resp = await xfetch('/api/v1/patient')
if (resp.success) {
data.value = (resp.body as Record<string, any>).data
}
}
onMounted(() => {
getPatientList()
})
provide('rec_id', recId)
provide('rec_action', recAction)
provide('rec_item', recItem)
provide('table_data_loader', isLoading)
</script>
<template>
<Header :prep="{ ...hreaderPrep }" :ref-search-nav="refSearchNav" />
<div class="my-4 flex flex-1 flex-col gap-4 md:gap-8">
<AssesmentFunctionList :data="data" />
</div>
</template>

Some files were not shown because too many files have changed in this diff Show More