Merge pull request #99 from dikstub-rssa/feat/fe-integrasi-2-90
Integrasi Part 2
This commit is contained in:
@@ -0,0 +1,102 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
// Components
|
||||||
|
import Block from '~/components/pub/custom-ui/doc-entry/block.vue'
|
||||||
|
import Cell from '~/components/pub/custom-ui/doc-entry/cell.vue'
|
||||||
|
import Field from '~/components/pub/custom-ui/doc-entry/field.vue'
|
||||||
|
import Label from '~/components/pub/custom-ui/doc-entry/label.vue'
|
||||||
|
|
||||||
|
// Types
|
||||||
|
import type { DivisionFormData } from '~/schemas/division.schema.ts'
|
||||||
|
|
||||||
|
// Helpers
|
||||||
|
import type z from 'zod'
|
||||||
|
import { toTypedSchema } from '@vee-validate/zod'
|
||||||
|
import { useForm } from 'vee-validate'
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
schema: z.ZodSchema<any>
|
||||||
|
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: DivisionFormData, resetForm: () => void]
|
||||||
|
cancel: [resetForm: () => void]
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const { defineField, errors, meta } = useForm({
|
||||||
|
validationSchema: toTypedSchema(props.schema),
|
||||||
|
initialValues: {
|
||||||
|
code: '',
|
||||||
|
name: '',
|
||||||
|
division_id: '',
|
||||||
|
} as Partial<DivisionFormData>,
|
||||||
|
})
|
||||||
|
|
||||||
|
const [code, codeAttrs] = defineField('code')
|
||||||
|
const [name, nameAttrs] = defineField('name')
|
||||||
|
const [division, divisionAttrs] = defineField('division_id')
|
||||||
|
|
||||||
|
// Fill fields from props.values if provided
|
||||||
|
if (props.values) {
|
||||||
|
if (props.values.code !== undefined) code.value = props.values.code
|
||||||
|
if (props.values.name !== undefined) name.value = props.values.name
|
||||||
|
if (props.values.division_id !== undefined) division.value = props.values.division_id
|
||||||
|
}
|
||||||
|
|
||||||
|
const resetForm = () => {
|
||||||
|
code.value = ''
|
||||||
|
name.value = ''
|
||||||
|
division.value = ''
|
||||||
|
}
|
||||||
|
|
||||||
|
// Form submission handler
|
||||||
|
function onSubmitForm(values: any) {
|
||||||
|
const formData: DivisionFormData = {
|
||||||
|
name: name.value || '',
|
||||||
|
code: code.value || '',
|
||||||
|
division_id: division.value || '',
|
||||||
|
}
|
||||||
|
emit('submit', formData, resetForm)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Form cancel handler
|
||||||
|
function onCancelForm() {
|
||||||
|
emit('cancel', resetForm)
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<form id="form-division" @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>
|
||||||
@@ -48,8 +48,4 @@ export const funcComponent: RecStrFuncComponent = {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
export const funcHtml: RecStrFuncUnknown = {
|
export const funcHtml: RecStrFuncUnknown = {}
|
||||||
patient_address(_rec) {
|
|
||||||
return '-'
|
|
||||||
},
|
|
||||||
}
|
|
||||||
@@ -6,7 +6,7 @@ import Field from '~/components/pub/custom-ui/doc-entry/field.vue'
|
|||||||
import Label from '~/components/pub/custom-ui/doc-entry/label.vue'
|
import Label from '~/components/pub/custom-ui/doc-entry/label.vue'
|
||||||
|
|
||||||
// Types
|
// Types
|
||||||
import type { MaterialFormData } from '~/schemas/material.schema'
|
import type { MaterialFormData } from '~/schemas/material.schema.ts'
|
||||||
|
|
||||||
// Helpers
|
// Helpers
|
||||||
import type z from 'zod'
|
import type z from 'zod'
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import Label from '~/components/pub/custom-ui/doc-entry/label.vue'
|
|||||||
import Button from '~/components/pub/ui/button/Button.vue'
|
import Button from '~/components/pub/ui/button/Button.vue'
|
||||||
|
|
||||||
// Types
|
// Types
|
||||||
import type { MedicineBaseFormData } from '~/schemas/medicine.schema'
|
import type { BaseFormData } from '~/schemas/base.schema.ts'
|
||||||
|
|
||||||
// Helpers
|
// Helpers
|
||||||
import type z from 'zod'
|
import type z from 'zod'
|
||||||
@@ -26,7 +26,7 @@ const props = defineProps<Props>()
|
|||||||
const isLoading = props.isLoading !== undefined ? props.isLoading : false
|
const isLoading = props.isLoading !== undefined ? props.isLoading : false
|
||||||
const isReadonly = props.isReadonly !== undefined ? props.isReadonly : false
|
const isReadonly = props.isReadonly !== undefined ? props.isReadonly : false
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
submit: [values: MedicineBaseFormData, resetForm: () => void]
|
submit: [values: BaseFormData, resetForm: () => void]
|
||||||
cancel: [resetForm: () => void]
|
cancel: [resetForm: () => void]
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
|
|||||||
@@ -10,11 +10,6 @@ import { defineAsyncComponent } from 'vue'
|
|||||||
|
|
||||||
const action = defineAsyncComponent(() => import('~/components/pub/custom-ui/data/dropdown-action-dud.vue'))
|
const action = defineAsyncComponent(() => import('~/components/pub/custom-ui/data/dropdown-action-dud.vue'))
|
||||||
|
|
||||||
const _doctorStatus = {
|
|
||||||
0: 'Tidak Aktif',
|
|
||||||
1: 'Aktif',
|
|
||||||
}
|
|
||||||
|
|
||||||
export const cols: Col[] = [{}, {}, { width: 50 }]
|
export const cols: Col[] = [{}, {}, { width: 50 }]
|
||||||
|
|
||||||
export const header: Th[][] = [[{ label: 'Kode' }, { label: 'Nama' }, { label: 'Aksi' }]]
|
export const header: Th[][] = [[{ label: 'Kode' }, { label: 'Nama' }, { label: 'Aksi' }]]
|
||||||
@@ -39,8 +34,4 @@ export const funcComponent: RecStrFuncComponent = {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
export const funcHtml: RecStrFuncUnknown = {
|
export const funcHtml: RecStrFuncUnknown = {}
|
||||||
patient_address(_rec) {
|
|
||||||
return '-'
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import Label from '~/components/pub/custom-ui/doc-entry/label.vue'
|
|||||||
import Button from '~/components/pub/ui/button/Button.vue'
|
import Button from '~/components/pub/ui/button/Button.vue'
|
||||||
|
|
||||||
// Types
|
// Types
|
||||||
import type { MedicineBaseFormData } from '~/schemas/medicine.schema'
|
import type { BaseFormData } from '~/schemas/base.schema.ts'
|
||||||
|
|
||||||
// Helpers
|
// Helpers
|
||||||
import type z from 'zod'
|
import type z from 'zod'
|
||||||
@@ -26,7 +26,7 @@ const props = defineProps<Props>()
|
|||||||
const isLoading = props.isLoading !== undefined ? props.isLoading : false
|
const isLoading = props.isLoading !== undefined ? props.isLoading : false
|
||||||
const isReadonly = props.isReadonly !== undefined ? props.isReadonly : false
|
const isReadonly = props.isReadonly !== undefined ? props.isReadonly : false
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
submit: [values: MedicineBaseFormData, resetForm: () => void]
|
submit: [values: BaseFormData, resetForm: () => void]
|
||||||
cancel: [resetForm: () => void]
|
cancel: [resetForm: () => void]
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
|
|||||||
@@ -10,11 +10,6 @@ import { defineAsyncComponent } from 'vue'
|
|||||||
|
|
||||||
const action = defineAsyncComponent(() => import('~/components/pub/custom-ui/data/dropdown-action-dud.vue'))
|
const action = defineAsyncComponent(() => import('~/components/pub/custom-ui/data/dropdown-action-dud.vue'))
|
||||||
|
|
||||||
const _doctorStatus = {
|
|
||||||
0: 'Tidak Aktif',
|
|
||||||
1: 'Aktif',
|
|
||||||
}
|
|
||||||
|
|
||||||
export const cols: Col[] = [{}, {}, { width: 50 }]
|
export const cols: Col[] = [{}, {}, { width: 50 }]
|
||||||
|
|
||||||
export const header: Th[][] = [[{ label: 'Kode' }, { label: 'Nama' }, { label: 'Aksi' }]]
|
export const header: Th[][] = [[{ label: 'Kode' }, { label: 'Nama' }, { label: 'Aksi' }]]
|
||||||
@@ -39,8 +34,4 @@ export const funcComponent: RecStrFuncComponent = {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
export const funcHtml: RecStrFuncUnknown = {
|
export const funcHtml: RecStrFuncUnknown = {}
|
||||||
patient_address(_rec) {
|
|
||||||
return '-'
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,80 +1,183 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import Block from '~/components/pub/custom-ui/form/block.vue'
|
// Components
|
||||||
import FieldGroup from '~/components/pub/custom-ui/form/field-group.vue'
|
import Block from '~/components/pub/custom-ui/doc-entry/block.vue'
|
||||||
import Field from '~/components/pub/custom-ui/form/field.vue'
|
import Cell from '~/components/pub/custom-ui/doc-entry/cell.vue'
|
||||||
import Label from '~/components/pub/custom-ui/form/label.vue'
|
import Field from '~/components/pub/custom-ui/doc-entry/field.vue'
|
||||||
|
import Label from '~/components/pub/custom-ui/doc-entry/label.vue'
|
||||||
|
import Button from '~/components/pub/ui/button/Button.vue'
|
||||||
|
|
||||||
const props = defineProps<{ modelValue: any }>()
|
// Helpers
|
||||||
const emit = defineEmits(['update:modelValue', 'event'])
|
import type z from 'zod'
|
||||||
|
import { useForm } from 'vee-validate'
|
||||||
|
import { toTypedSchema } from '@vee-validate/zod'
|
||||||
|
|
||||||
const data = computed({
|
interface Props {
|
||||||
get: () => props.modelValue,
|
schema?: z.ZodSchema<any>
|
||||||
set: (val) => emit('update:modelValue', val),
|
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 }[]
|
||||||
|
}
|
||||||
|
|
||||||
|
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: any, resetForm: () => void]
|
||||||
|
cancel: [resetForm: () => void]
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const { defineField, errors, meta } = useForm({
|
||||||
|
validationSchema: props.schema ? toTypedSchema(props.schema) : undefined,
|
||||||
|
initialValues: {
|
||||||
|
code: '',
|
||||||
|
name: '',
|
||||||
|
medicineGroup_code: '',
|
||||||
|
medicineMethod_code: '',
|
||||||
|
uom_code: '',
|
||||||
|
infra_id: null,
|
||||||
|
stock: 0,
|
||||||
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
const items = [
|
const [code, codeAttrs] = defineField('code')
|
||||||
{ value: '1', label: 'item 1' },
|
const [name, nameAttrs] = defineField('name')
|
||||||
{ value: '2', label: 'item 2' },
|
const [medicineGroup_code, medicineGroupAttrs] = defineField('medicineGroup_code')
|
||||||
{ value: '3', label: 'item 3' },
|
const [medicineMethod_code, medicineMethodAttrs] = defineField('medicineMethod_code')
|
||||||
{ value: '4', label: 'item 4' },
|
const [uom_code, uomAttrs] = defineField('uom_code')
|
||||||
]
|
const [infra_id, infraAttrs] = defineField('infra_id')
|
||||||
|
const [stock, stockAttrs] = defineField('stock')
|
||||||
|
|
||||||
|
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.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
|
||||||
|
}
|
||||||
|
|
||||||
|
const resetForm = () => {
|
||||||
|
code.value = ''
|
||||||
|
name.value = ''
|
||||||
|
medicineGroup_code.value = ''
|
||||||
|
medicineMethod_code.value = ''
|
||||||
|
uom_code.value = ''
|
||||||
|
infra_id.value = null
|
||||||
|
stock.value = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
function onSubmitForm() {
|
||||||
|
const formData = {
|
||||||
|
code: code.value || '',
|
||||||
|
name: name.value || '',
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
function onCancelForm() {
|
||||||
|
emit('cancel', resetForm)
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<form id="entry-form">
|
<form id="form-medicine" @submit.prevent>
|
||||||
<div class="mb-5 border-b border-b-slate-300 pb-3 text-lg xl:text-xl">
|
<Block labelSize="thin" class="!mb-2.5 !pt-0 xl:!mb-3" :colCount="1">
|
||||||
<div class="flex flex-col justify-between">
|
<Cell>
|
||||||
<Block>
|
<Label height="">Kode</Label>
|
||||||
<FieldGroup :column="2">
|
<Field :errMessage="errors.code">
|
||||||
<Label>Nama</Label>
|
<Input id="code" v-model="code" v-bind="codeAttrs" :disabled="isLoading || isReadonly" class="input input-bordered w-full" />
|
||||||
<Field>
|
</Field>
|
||||||
<Input type="text" name="identity_number" />
|
</Cell>
|
||||||
</Field>
|
<Cell>
|
||||||
</FieldGroup>
|
<Label height="compact">Nama</Label>
|
||||||
<FieldGroup :column="2">
|
<Field :errMessage="errors.name">
|
||||||
<Label>Kode</Label>
|
<Input id="name" v-model="name" v-bind="nameAttrs" :disabled="isLoading || isReadonly" class="input input-bordered w-full" />
|
||||||
<Field name="sip_number">
|
</Field>
|
||||||
<Input type="text" name="sip_no" />
|
</Cell>
|
||||||
</Field>
|
<Cell>
|
||||||
</FieldGroup>
|
<Label height="compact">Kelompok Obat</Label>
|
||||||
<FieldGroup :column="2">
|
<Field :errMessage="errors.medicineGroup_code">
|
||||||
<Label>Cara Pemberian</Label>
|
<Select
|
||||||
<Field name="phone">
|
id="medicineGroup_code"
|
||||||
<Select :items="items" />
|
v-model="medicineGroup_code"
|
||||||
</Field>
|
icon-name="i-lucide-chevron-down"
|
||||||
</FieldGroup>
|
placeholder="Pilih kelompok obat"
|
||||||
<FieldGroup :column="2">
|
v-bind="medicineGroupAttrs"
|
||||||
<Label>Bentuk Sediaan</Label>
|
:items="props.medicineGroups || []"
|
||||||
<Field>
|
:disabled="isLoading || isReadonly"
|
||||||
<Select :items="items" />
|
/>
|
||||||
</Field>
|
</Field>
|
||||||
</FieldGroup>
|
</Cell>
|
||||||
<FieldGroup :column="2">
|
<Cell>
|
||||||
<Label>Dosis</Label>
|
<Label height="compact">Metode Pemberian</Label>
|
||||||
<Field>
|
<Field :errMessage="errors.medicineMethod_code">
|
||||||
<Input type="number" name="outPatient_rate" />
|
<Select
|
||||||
</Field>
|
id="medicineMethod_code"
|
||||||
</FieldGroup>
|
v-model="medicineMethod_code"
|
||||||
<FieldGroup :column="2">
|
icon-name="i-lucide-chevron-down"
|
||||||
<Label>Infra</Label>
|
placeholder="Pilih metode pemberian"
|
||||||
<Field>
|
v-bind="medicineMethodAttrs"
|
||||||
<Input />
|
:items="props.medicineMethods || []"
|
||||||
</Field>
|
:disabled="isLoading || isReadonly"
|
||||||
</FieldGroup>
|
/>
|
||||||
<FieldGroup :column="2">
|
</Field>
|
||||||
<Label>Stock</Label>
|
</Cell>
|
||||||
<Field>
|
<Cell>
|
||||||
<Input type="number" />
|
<Label height="compact">Satuan</Label>
|
||||||
</Field>
|
<Field :errMessage="errors.uom_code">
|
||||||
</FieldGroup>
|
<Select
|
||||||
<FieldGroup :column="2">
|
id="uom_code"
|
||||||
<Label>Status</Label>
|
v-model="uom_code"
|
||||||
<Field>
|
icon-name="i-lucide-chevron-down"
|
||||||
<Input />
|
placeholder="Pilih satuan"
|
||||||
</Field>
|
v-bind="uomAttrs"
|
||||||
</FieldGroup>
|
:items="props.uoms || []"
|
||||||
</Block>
|
:disabled="isLoading || isReadonly"
|
||||||
</div>
|
/>
|
||||||
|
</Field>
|
||||||
|
</Cell>
|
||||||
|
<!-- <Cell>
|
||||||
|
<Label height="compact">Infra</Label>
|
||||||
|
<Field :errMessage="errors.infra_id">
|
||||||
|
<Select
|
||||||
|
id="infra_id"
|
||||||
|
v-model="infra_id"
|
||||||
|
icon-name="i-lucide-chevron-down"
|
||||||
|
placeholder="Tidak ada"
|
||||||
|
v-bind="infraAttrs"
|
||||||
|
:items="props.infras || []"
|
||||||
|
:disabled="isLoading || isReadonly"
|
||||||
|
/>
|
||||||
|
</Field>
|
||||||
|
</Cell> -->
|
||||||
|
<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" />
|
||||||
|
</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>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -18,16 +18,15 @@ export const header: Th[][] = [
|
|||||||
[
|
[
|
||||||
{ label: 'Kode' },
|
{ label: 'Kode' },
|
||||||
{ label: 'Name' },
|
{ label: 'Name' },
|
||||||
{ label: 'Kategori' },
|
|
||||||
{ label: 'Golongan' },
|
{ label: 'Golongan' },
|
||||||
{ label: 'Metode Pemberian' },
|
{ label: 'Metode Pemberian' },
|
||||||
{ label: 'Bentuk' },
|
{ label: "Satuan" },
|
||||||
{ label: 'Stok' },
|
{ label: 'Stok' },
|
||||||
{ label: 'Aksi' },
|
{ label: 'Aksi' },
|
||||||
],
|
],
|
||||||
]
|
]
|
||||||
|
|
||||||
export const keys = ['code', 'name', 'category', 'group', 'method', 'unit', 'total', 'action']
|
export const keys = ['code', 'name', 'group', 'method', 'unit', 'stock', 'action']
|
||||||
|
|
||||||
export const delKeyNames: KeyLabel[] = [
|
export const delKeyNames: KeyLabel[] = [
|
||||||
{ key: 'code', label: 'Kode' },
|
{ key: 'code', label: 'Kode' },
|
||||||
@@ -35,17 +34,14 @@ export const delKeyNames: KeyLabel[] = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
export const funcParsed: RecStrFuncUnknown = {
|
export const funcParsed: RecStrFuncUnknown = {
|
||||||
cateogry: (rec: unknown): unknown => {
|
|
||||||
return (rec as SmallDetailDto).medicineCategory?.name || '-'
|
|
||||||
},
|
|
||||||
group: (rec: unknown): unknown => {
|
group: (rec: unknown): unknown => {
|
||||||
return (rec as SmallDetailDto).medicineGroup?.name || '-'
|
return (rec as SmallDetailDto).medicineGroup_code || '-'
|
||||||
},
|
},
|
||||||
method: (rec: unknown): unknown => {
|
method: (rec: unknown): unknown => {
|
||||||
return (rec as SmallDetailDto).medicineMethod?.name || '-'
|
return (rec as SmallDetailDto).medicineMethod_code || '-'
|
||||||
},
|
},
|
||||||
unit: (rec: unknown): unknown => {
|
unit: (rec: unknown): unknown => {
|
||||||
return (rec as SmallDetailDto).medicineUnit?.name || '-'
|
return (rec as SmallDetailDto).uom_code || '-'
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -60,8 +56,4 @@ export const funcComponent: RecStrFuncComponent = {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
export const funcHtml: RecStrFuncUnknown = {
|
export const funcHtml: RecStrFuncUnknown = {}
|
||||||
// (_rec) {
|
|
||||||
// return '-'
|
|
||||||
// },
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,19 +1,35 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import type { PaginationMeta } from '~/components/pub/custom-ui/pagination/pagination.type'
|
||||||
|
import PaginationView from '~/components/pub/custom-ui/pagination/pagination-view.vue'
|
||||||
import { cols, funcComponent, funcHtml, funcParsed, header, keys } from './list-cfg'
|
import { cols, funcComponent, funcHtml, funcParsed, header, keys } from './list-cfg'
|
||||||
|
|
||||||
defineProps<{
|
interface Props {
|
||||||
data: any[]
|
data: any[]
|
||||||
|
paginationMeta: PaginationMeta
|
||||||
|
}
|
||||||
|
|
||||||
|
defineProps<Props>()
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
pageChange: [page: number]
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
|
function handlePageChange(page: number) {
|
||||||
|
emit('pageChange', page)
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<PubBaseDataTable
|
<div class="space-y-4">
|
||||||
:rows="data"
|
<PubBaseDataTable
|
||||||
:cols="cols"
|
:rows="data"
|
||||||
:header="header"
|
:cols="cols"
|
||||||
:keys="keys"
|
:header="header"
|
||||||
:func-parsed="funcParsed"
|
:keys="keys"
|
||||||
:func-html="funcHtml"
|
:func-parsed="funcParsed"
|
||||||
:func-component="funcComponent"
|
:func-html="funcHtml"
|
||||||
/>
|
:func-component="funcComponent"
|
||||||
|
/>
|
||||||
|
<PaginationView :pagination-meta="paginationMeta" @page-change="handlePageChange" />
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -0,0 +1,181 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import type { FormErrors } from '~/types/error'
|
||||||
|
import { toTypedSchema } from '@vee-validate/zod'
|
||||||
|
import Combobox from '~/components/pub/custom-ui/form/combobox.vue'
|
||||||
|
import FieldGroup from '~/components/pub/custom-ui/form/field-group.vue'
|
||||||
|
import Field from '~/components/pub/custom-ui/form/field.vue'
|
||||||
|
import Label from '~/components/pub/custom-ui/form/label.vue'
|
||||||
|
|
||||||
|
interface SpecialistFormData {
|
||||||
|
name: string
|
||||||
|
code: string
|
||||||
|
installationId: string
|
||||||
|
unitId: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
schema: any
|
||||||
|
initialValues?: Partial<SpecialistFormData>
|
||||||
|
errors?: FormErrors
|
||||||
|
installation: {
|
||||||
|
msg: {
|
||||||
|
placeholder: string
|
||||||
|
search: string
|
||||||
|
empty: string
|
||||||
|
}
|
||||||
|
items: {
|
||||||
|
value: string
|
||||||
|
label: string
|
||||||
|
code: string
|
||||||
|
}[]
|
||||||
|
}
|
||||||
|
unit: {
|
||||||
|
msg: {
|
||||||
|
placeholder: string
|
||||||
|
search: string
|
||||||
|
empty: string
|
||||||
|
}
|
||||||
|
items: {
|
||||||
|
value: string
|
||||||
|
label: string
|
||||||
|
code: string
|
||||||
|
}[]
|
||||||
|
}
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
'submit': [values: SpecialistFormData, resetForm: () => void]
|
||||||
|
'cancel': [resetForm: () => void]
|
||||||
|
'installationChanged': [id: string]
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const formSchema = toTypedSchema(props.schema)
|
||||||
|
|
||||||
|
// Form submission handler
|
||||||
|
function onSubmitForm(values: any, { resetForm }: { resetForm: () => void }) {
|
||||||
|
const formData: SpecialistFormData = {
|
||||||
|
name: values.name || '',
|
||||||
|
code: values.code || '',
|
||||||
|
installationId: values.installationId || '',
|
||||||
|
unitId: values.unitId || '',
|
||||||
|
}
|
||||||
|
emit('submit', formData, resetForm)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Form cancel handler
|
||||||
|
function onCancelForm({ resetForm }: { resetForm: () => void }) {
|
||||||
|
emit('cancel', resetForm)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Watch for installation changes
|
||||||
|
function onInstallationChanged(installationId: string, setFieldValue: any) {
|
||||||
|
setFieldValue('unitId', '', false)
|
||||||
|
emit('installationChanged', installationId || '')
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<Form
|
||||||
|
v-slot="{ handleSubmit, resetForm, values, setFieldValue }" as="" keep-values :validation-schema="formSchema"
|
||||||
|
:initial-values="initialValues"
|
||||||
|
>
|
||||||
|
<form id="entry-form" @submit="handleSubmit($event, (values) => onSubmitForm(values, { resetForm }))">
|
||||||
|
<div class="mb-5 border-b border-b-slate-300 pb-3 text-lg xl:text-xl">
|
||||||
|
<div class="flex flex-col justify-between">
|
||||||
|
<FieldGroup>
|
||||||
|
<Label label-for="name">Nama</Label>
|
||||||
|
<Field id="name" :errors="errors">
|
||||||
|
<FormField v-slot="{ componentField }" name="name">
|
||||||
|
<FormItem>
|
||||||
|
<FormControl>
|
||||||
|
<Input
|
||||||
|
id="name"
|
||||||
|
type="text"
|
||||||
|
placeholder="Masukkan nama spesialisasi"
|
||||||
|
autocomplete="off"
|
||||||
|
v-bind="componentField"
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
</FormField>
|
||||||
|
</Field>
|
||||||
|
</FieldGroup>
|
||||||
|
|
||||||
|
<FieldGroup>
|
||||||
|
<Label label-for="code">Kode</Label>
|
||||||
|
<Field id="code" :errors="errors">
|
||||||
|
<FormField v-slot="{ componentField }" name="code">
|
||||||
|
<FormItem>
|
||||||
|
<FormControl>
|
||||||
|
<Input
|
||||||
|
id="code"
|
||||||
|
type="text"
|
||||||
|
placeholder="Masukkan kode spesialisasi"
|
||||||
|
autocomplete="off"
|
||||||
|
v-bind="componentField"
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
</FormField>
|
||||||
|
</Field>
|
||||||
|
</FieldGroup>
|
||||||
|
|
||||||
|
<FieldGroup>
|
||||||
|
<Label label-for="installationId">Instalasi</Label>
|
||||||
|
<Field id="installationId" :errors="errors">
|
||||||
|
<FormField v-slot="{ componentField }" name="installationId">
|
||||||
|
<FormItem>
|
||||||
|
<FormControl>
|
||||||
|
<Combobox
|
||||||
|
id="installationId"
|
||||||
|
v-bind="componentField"
|
||||||
|
:items="installation.items"
|
||||||
|
:placeholder="installation.msg.placeholder"
|
||||||
|
:search-placeholder="installation.msg.search"
|
||||||
|
:empty-message="installation.msg.empty"
|
||||||
|
@update:model-value="(value) => onInstallationChanged(value, setFieldValue)"
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
</FormField>
|
||||||
|
</Field>
|
||||||
|
</FieldGroup>
|
||||||
|
|
||||||
|
<FieldGroup>
|
||||||
|
<Label label-for="unitId">Unit</Label>
|
||||||
|
<Field id="unitId" :errors="errors">
|
||||||
|
<FormField v-slot="{ componentField }" name="unitId">
|
||||||
|
<FormItem>
|
||||||
|
<FormControl>
|
||||||
|
<Combobox
|
||||||
|
id="unitId"
|
||||||
|
:disabled="!values.installationId"
|
||||||
|
v-bind="componentField"
|
||||||
|
:items="unit.items"
|
||||||
|
:placeholder="unit.msg.placeholder"
|
||||||
|
:search-placeholder="unit.msg.search"
|
||||||
|
:empty-message="unit.msg.empty"
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
</FormField>
|
||||||
|
</Field>
|
||||||
|
</FieldGroup>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex justify-end gap-2 mt-4">
|
||||||
|
<Button type="button" variant="outline" @click="onCancelForm({ resetForm })">
|
||||||
|
Batal
|
||||||
|
</Button>
|
||||||
|
<Button type="submit">
|
||||||
|
Simpan
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</Form>
|
||||||
|
</template>
|
||||||
@@ -1,182 +1,128 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import type { FormErrors } from '~/types/error'
|
// Components
|
||||||
import { toTypedSchema } from '@vee-validate/zod'
|
import Block from '~/components/pub/custom-ui/doc-entry/block.vue'
|
||||||
import Combobox from '~/components/pub/custom-ui/form/combobox.vue'
|
import Cell from '~/components/pub/custom-ui/doc-entry/cell.vue'
|
||||||
import FieldGroup from '~/components/pub/custom-ui/form/field-group.vue'
|
import Field from '~/components/pub/custom-ui/doc-entry/field.vue'
|
||||||
import Field from '~/components/pub/custom-ui/form/field.vue'
|
import Label from '~/components/pub/custom-ui/doc-entry/label.vue'
|
||||||
import Label from '~/components/pub/custom-ui/form/label.vue'
|
// import Combobox from '~/components/pub/custom-ui/form/combobox.vue'
|
||||||
import { Form } from '~/components/pub/ui/form'
|
|
||||||
|
|
||||||
interface SpecialistFormData {
|
// Types
|
||||||
name: string
|
import type { SpecialistFormData } from '~/schemas/specialist.schema.ts'
|
||||||
code: string
|
|
||||||
installationId: string
|
// Helpers
|
||||||
unitId: string
|
import type z from 'zod'
|
||||||
|
import { toTypedSchema } from '@vee-validate/zod'
|
||||||
|
import { useForm } from 'vee-validate'
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
schema: z.ZodSchema<any>
|
||||||
|
units: any[]
|
||||||
|
values: any
|
||||||
|
isLoading?: boolean
|
||||||
|
isReadonly?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<Props>()
|
||||||
schema: any
|
const isLoading = props.isLoading !== undefined ? props.isLoading : false
|
||||||
initialValues?: Partial<SpecialistFormData>
|
const isReadonly = props.isReadonly !== undefined ? props.isReadonly : false
|
||||||
errors?: FormErrors
|
|
||||||
installation: {
|
|
||||||
msg: {
|
|
||||||
placeholder: string
|
|
||||||
search: string
|
|
||||||
empty: string
|
|
||||||
}
|
|
||||||
items: {
|
|
||||||
value: string
|
|
||||||
label: string
|
|
||||||
code: string
|
|
||||||
}[]
|
|
||||||
}
|
|
||||||
unit: {
|
|
||||||
msg: {
|
|
||||||
placeholder: string
|
|
||||||
search: string
|
|
||||||
empty: string
|
|
||||||
}
|
|
||||||
items: {
|
|
||||||
value: string
|
|
||||||
label: string
|
|
||||||
code: string
|
|
||||||
}[]
|
|
||||||
}
|
|
||||||
}>()
|
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
'submit': [values: SpecialistFormData, resetForm: () => void]
|
submit: [values: SpecialistFormData, resetForm: () => void]
|
||||||
'cancel': [resetForm: () => void]
|
cancel: [resetForm: () => void]
|
||||||
'installationChanged': [id: string]
|
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
const formSchema = toTypedSchema(props.schema)
|
const { defineField, errors, meta } = useForm({
|
||||||
|
validationSchema: toTypedSchema(props.schema),
|
||||||
|
initialValues: {
|
||||||
|
code: '',
|
||||||
|
name: '',
|
||||||
|
unit_id: 0,
|
||||||
|
} as Partial<SpecialistFormData>,
|
||||||
|
})
|
||||||
|
|
||||||
|
const [code, codeAttrs] = defineField('code')
|
||||||
|
const [name, nameAttrs] = defineField('name')
|
||||||
|
const [unit, unitAttrs] = defineField('unit_id')
|
||||||
|
|
||||||
|
// Fill fields from props.values if provided
|
||||||
|
if (props.values) {
|
||||||
|
if (props.values.code !== undefined) code.value = props.values.code
|
||||||
|
if (props.values.name !== undefined) name.value = props.values.name
|
||||||
|
if (props.values.unit_id !== undefined) unit.value = props.values.unit_id
|
||||||
|
}
|
||||||
|
|
||||||
|
const resetForm = () => {
|
||||||
|
code.value = ''
|
||||||
|
name.value = ''
|
||||||
|
unit.value = ''
|
||||||
|
}
|
||||||
|
|
||||||
// Form submission handler
|
// Form submission handler
|
||||||
function onSubmitForm(values: any, { resetForm }: { resetForm: () => void }) {
|
function onSubmitForm(values: any) {
|
||||||
const formData: SpecialistFormData = {
|
const formData: SpecialistFormData = {
|
||||||
name: values.name || '',
|
name: name.value || '',
|
||||||
code: values.code || '',
|
code: code.value || '',
|
||||||
installationId: values.installationId || '',
|
unit_id: unit.value || '',
|
||||||
unitId: values.unitId || '',
|
|
||||||
}
|
}
|
||||||
emit('submit', formData, resetForm)
|
emit('submit', formData, resetForm)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Form cancel handler
|
// Form cancel handler
|
||||||
function onCancelForm({ resetForm }: { resetForm: () => void }) {
|
function onCancelForm() {
|
||||||
emit('cancel', resetForm)
|
emit('cancel', resetForm)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Watch for installation changes
|
|
||||||
function onInstallationChanged(installationId: string, setFieldValue: any) {
|
|
||||||
setFieldValue('unitId', '', false)
|
|
||||||
emit('installationChanged', installationId || '')
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<Form
|
<form id="form-specialist" @submit.prevent>
|
||||||
v-slot="{ handleSubmit, resetForm, values, setFieldValue }" as="" keep-values :validation-schema="formSchema"
|
<Block labelSize="thin" class="!mb-2.5 !pt-0 xl:!mb-3" :colCount="1">
|
||||||
:initial-values="initialValues"
|
<Cell>
|
||||||
>
|
<Label height="compact">Kode</Label>
|
||||||
<form id="entry-form" @submit="handleSubmit($event, (values) => onSubmitForm(values, { resetForm }))">
|
<Field :errMessage="errors.code">
|
||||||
<div class="mb-5 border-b border-b-slate-300 pb-3 text-lg xl:text-xl">
|
<Input id="code" v-model="code" v-bind="codeAttrs" :disabled="isLoading || isReadonly" />
|
||||||
<div class="flex flex-col justify-between">
|
</Field>
|
||||||
<FieldGroup>
|
</Cell>
|
||||||
<Label label-for="name">Nama</Label>
|
<Cell>
|
||||||
<Field id="name" :errors="errors">
|
<Label height="compact">Nama</Label>
|
||||||
<FormField v-slot="{ componentField }" name="name">
|
<Field :errMessage="errors.name">
|
||||||
<FormItem>
|
<Input id="name" v-model="name" v-bind="nameAttrs" :disabled="isLoading || isReadonly" />
|
||||||
<FormControl>
|
</Field>
|
||||||
<Input
|
</Cell>
|
||||||
id="name"
|
<Cell>
|
||||||
type="text"
|
<Label height="compact">Unit</Label>
|
||||||
placeholder="Masukkan nama spesialisasi"
|
<Field :errMessage="errors.unit">
|
||||||
autocomplete="off"
|
<!-- <Combobox
|
||||||
v-bind="componentField"
|
id="unit"
|
||||||
/>
|
placeholder="Pilih Unit"
|
||||||
</FormControl>
|
search-placeholder="Cari unit"
|
||||||
<FormMessage />
|
empty-message="Unit tidak ditemukan"
|
||||||
</FormItem>
|
v-model="unit"
|
||||||
</FormField>
|
v-bind="unitAttrs"
|
||||||
</Field>
|
:items="units"
|
||||||
</FieldGroup>
|
:disabled="isLoading || isReadonly"
|
||||||
|
/> -->
|
||||||
<FieldGroup>
|
<Select
|
||||||
<Label label-for="code">Kode</Label>
|
id="unit"
|
||||||
<Field id="code" :errors="errors">
|
icon-name="i-lucide-chevron-down"
|
||||||
<FormField v-slot="{ componentField }" name="code">
|
placeholder="Pilih unit"
|
||||||
<FormItem>
|
v-model="unit"
|
||||||
<FormControl>
|
v-bind="unitAttrs"
|
||||||
<Input
|
:items="units"
|
||||||
id="code"
|
:disabled="isLoading || isReadonly"
|
||||||
type="text"
|
/>
|
||||||
placeholder="Masukkan kode spesialisasi"
|
</Field>
|
||||||
autocomplete="off"
|
</Cell>
|
||||||
v-bind="componentField"
|
</Block>
|
||||||
/>
|
<div class="my-2 flex justify-end gap-2 py-2">
|
||||||
</FormControl>
|
<Button type="button" variant="secondary" class="w-[120px]" @click="onCancelForm"> Kembali </Button>
|
||||||
<FormMessage />
|
<Button
|
||||||
</FormItem>
|
v-if="!isReadonly"
|
||||||
</FormField>
|
type="button"
|
||||||
</Field>
|
class="w-[120px]"
|
||||||
</FieldGroup>
|
:disabled="isLoading || !meta.valid"
|
||||||
|
@click="onSubmitForm"
|
||||||
<FieldGroup>
|
>
|
||||||
<Label label-for="installationId">Instalasi</Label>
|
Simpan
|
||||||
<Field id="installationId" :errors="errors">
|
</Button>
|
||||||
<FormField v-slot="{ componentField }" name="installationId">
|
</div>
|
||||||
<FormItem>
|
</form>
|
||||||
<FormControl>
|
|
||||||
<Combobox
|
|
||||||
id="installationId"
|
|
||||||
v-bind="componentField"
|
|
||||||
:items="installation.items"
|
|
||||||
:placeholder="installation.msg.placeholder"
|
|
||||||
:search-placeholder="installation.msg.search"
|
|
||||||
:empty-message="installation.msg.empty"
|
|
||||||
@update:model-value="(value) => onInstallationChanged(value, setFieldValue)"
|
|
||||||
/>
|
|
||||||
</FormControl>
|
|
||||||
<FormMessage />
|
|
||||||
</FormItem>
|
|
||||||
</FormField>
|
|
||||||
</Field>
|
|
||||||
</FieldGroup>
|
|
||||||
|
|
||||||
<FieldGroup>
|
|
||||||
<Label label-for="unitId">Unit</Label>
|
|
||||||
<Field id="unitId" :errors="errors">
|
|
||||||
<FormField v-slot="{ componentField }" name="unitId">
|
|
||||||
<FormItem>
|
|
||||||
<FormControl>
|
|
||||||
<Combobox
|
|
||||||
id="unitId"
|
|
||||||
:disabled="!values.installationId"
|
|
||||||
v-bind="componentField"
|
|
||||||
:items="unit.items"
|
|
||||||
:placeholder="unit.msg.placeholder"
|
|
||||||
:search-placeholder="unit.msg.search"
|
|
||||||
:empty-message="unit.msg.empty"
|
|
||||||
/>
|
|
||||||
</FormControl>
|
|
||||||
<FormMessage />
|
|
||||||
</FormItem>
|
|
||||||
</FormField>
|
|
||||||
</Field>
|
|
||||||
</FieldGroup>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="flex justify-end gap-2 mt-4">
|
|
||||||
<Button type="button" variant="outline" @click="onCancelForm({ resetForm })">
|
|
||||||
Batal
|
|
||||||
</Button>
|
|
||||||
<Button type="submit">
|
|
||||||
Simpan
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</Form>
|
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -10,15 +10,13 @@ import { defineAsyncComponent } from 'vue'
|
|||||||
|
|
||||||
type SmallDetailDto = any
|
type SmallDetailDto = any
|
||||||
|
|
||||||
const action = defineAsyncComponent(() => import('~/components/pub/custom-ui/data/dropdown-action-ud.vue'))
|
const action = defineAsyncComponent(() => import('~/components/pub/custom-ui/data/dropdown-action-dud.vue'))
|
||||||
|
|
||||||
export const cols: Col[] = [{ width: 100 }, {}, {}, {}, { width: 50 }]
|
export const cols: Col[] = [{}, {}, {}, { width: 50 }]
|
||||||
|
|
||||||
export const header: Th[][] = [
|
export const header: Th[][] = [[{ label: 'Kode' }, { label: 'Nama' }, { label: 'Unit' }, { label: '' }]]
|
||||||
[{ label: 'Id' }, { label: 'Name' }, { label: 'Code' }, { label: 'Unit' }, { label: '' }],
|
|
||||||
]
|
|
||||||
|
|
||||||
export const keys = ['id', 'name', 'cellphone', 'religion_code', 'action']
|
export const keys = ['code', 'name', 'unit', 'action']
|
||||||
|
|
||||||
export const delKeyNames: KeyLabel[] = [
|
export const delKeyNames: KeyLabel[] = [
|
||||||
{ key: 'code', label: 'Kode' },
|
{ key: 'code', label: 'Kode' },
|
||||||
@@ -28,7 +26,11 @@ export const delKeyNames: KeyLabel[] = [
|
|||||||
export const funcParsed: RecStrFuncUnknown = {
|
export const funcParsed: RecStrFuncUnknown = {
|
||||||
name: (rec: unknown): unknown => {
|
name: (rec: unknown): unknown => {
|
||||||
const recX = rec as SmallDetailDto
|
const recX = rec as SmallDetailDto
|
||||||
return `${recX.firstName} ${recX.lastName || ''}`.trim()
|
return `${recX.name}`.trim()
|
||||||
|
},
|
||||||
|
unit: (rec: unknown): unknown => {
|
||||||
|
const recX = rec as SmallDetailDto
|
||||||
|
return recX.unit_id || '-'
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -38,13 +40,9 @@ export const funcComponent: RecStrFuncComponent = {
|
|||||||
idx,
|
idx,
|
||||||
rec: rec as object,
|
rec: rec as object,
|
||||||
component: action,
|
component: action,
|
||||||
props: {
|
|
||||||
size: 'sm',
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
return res
|
return res
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
export const funcHtml: RecStrFuncUnknown = {
|
export const funcHtml: RecStrFuncUnknown = {}
|
||||||
}
|
|
||||||
|
|||||||
@@ -22,10 +22,15 @@ function handlePageChange(page: number) {
|
|||||||
<template>
|
<template>
|
||||||
<div class="space-y-4">
|
<div class="space-y-4">
|
||||||
<PubBaseDataTable
|
<PubBaseDataTable
|
||||||
:rows="data" :cols="cols" :header="header" :keys="keys" :func-parsed="funcParsed"
|
:rows="data"
|
||||||
:func-html="funcHtml" :func-component="funcComponent" :skeleton-size="paginationMeta?.pageSize"
|
: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" />
|
<PaginationView :pagination-meta="paginationMeta" @page-change="handlePageChange" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -0,0 +1,213 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import type { FormErrors } from '~/types/error'
|
||||||
|
import { toTypedSchema } from '@vee-validate/zod'
|
||||||
|
import Combobox from '~/components/pub/custom-ui/form/combobox.vue'
|
||||||
|
import FieldGroup from '~/components/pub/custom-ui/form/field-group.vue'
|
||||||
|
import Field from '~/components/pub/custom-ui/form/field.vue'
|
||||||
|
import Label from '~/components/pub/custom-ui/form/label.vue'
|
||||||
|
|
||||||
|
interface SubSpecialistFormData {
|
||||||
|
name: string
|
||||||
|
code: string
|
||||||
|
installationId: string
|
||||||
|
unitId: string
|
||||||
|
specialistId: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
schema: any
|
||||||
|
initialValues?: Partial<SubSpecialistFormData>
|
||||||
|
errors?: FormErrors
|
||||||
|
installation: {
|
||||||
|
msg: {
|
||||||
|
placeholder: string
|
||||||
|
search: string
|
||||||
|
empty: string
|
||||||
|
}
|
||||||
|
items: {
|
||||||
|
value: string
|
||||||
|
label: string
|
||||||
|
code: string
|
||||||
|
}[]
|
||||||
|
}
|
||||||
|
unit: {
|
||||||
|
msg: {
|
||||||
|
placeholder: string
|
||||||
|
search: string
|
||||||
|
empty: string
|
||||||
|
}
|
||||||
|
items: {
|
||||||
|
value: string
|
||||||
|
label: string
|
||||||
|
code: string
|
||||||
|
}[]
|
||||||
|
}
|
||||||
|
specialist: {
|
||||||
|
msg: {
|
||||||
|
placeholder: string
|
||||||
|
search: string
|
||||||
|
empty: string
|
||||||
|
}
|
||||||
|
items: {
|
||||||
|
value: string
|
||||||
|
label: string
|
||||||
|
code: string
|
||||||
|
}[]
|
||||||
|
}
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
'submit': [values: SubSpecialistFormData, resetForm: () => void]
|
||||||
|
'cancel': [resetForm: () => void]
|
||||||
|
'installationChanged': [id: string]
|
||||||
|
'unitChanged': [id: string]
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const formSchema = toTypedSchema(props.schema)
|
||||||
|
|
||||||
|
// Form submission handler
|
||||||
|
function onSubmitForm(values: any, { resetForm }: { resetForm: () => void }) {
|
||||||
|
const formData: SubSpecialistFormData = {
|
||||||
|
name: values.name || '',
|
||||||
|
code: values.code || '',
|
||||||
|
installationId: values.installationId || '',
|
||||||
|
unitId: values.unitId || '',
|
||||||
|
specialistId: values.specialistId || '',
|
||||||
|
}
|
||||||
|
emit('submit', formData, resetForm)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Form cancel handler
|
||||||
|
function onCancelForm({ resetForm }: { resetForm: () => void }) {
|
||||||
|
emit('cancel', resetForm)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Watch for installation changes
|
||||||
|
function onInstallationChanged(installationId: string, setFieldValue: any) {
|
||||||
|
setFieldValue('unitId', '', false)
|
||||||
|
setFieldValue('specialistId', '', false)
|
||||||
|
emit('installationChanged', installationId || '')
|
||||||
|
}
|
||||||
|
|
||||||
|
function onUnitChanged(unitId: string, setFieldValue: any) {
|
||||||
|
setFieldValue('specialistId', '', false)
|
||||||
|
emit('unitChanged', unitId || '')
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<Form
|
||||||
|
v-slot="{
|
||||||
|
handleSubmit,
|
||||||
|
resetForm,
|
||||||
|
values,
|
||||||
|
setFieldValue,
|
||||||
|
}" as="" keep-values :validation-schema="formSchema"
|
||||||
|
:initial-values="initialValues"
|
||||||
|
>
|
||||||
|
<form id="entry-form" @submit="handleSubmit($event, (values) => onSubmitForm(values, { resetForm }))">
|
||||||
|
<div class="mb-5 border-b border-b-slate-300 pb-3 text-lg xl:text-xl">
|
||||||
|
<div class="flex flex-col justify-between">
|
||||||
|
<FieldGroup>
|
||||||
|
<Label label-for="name">Nama</Label>
|
||||||
|
<Field id="name" :errors="errors">
|
||||||
|
<FormField v-slot="{ componentField }" name="name">
|
||||||
|
<FormItem>
|
||||||
|
<FormControl>
|
||||||
|
<Input
|
||||||
|
id="name" type="text" placeholder="Masukkan nama spesialisasi" autocomplete="off"
|
||||||
|
v-bind="componentField"
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
</FormField>
|
||||||
|
</Field>
|
||||||
|
</FieldGroup>
|
||||||
|
|
||||||
|
<FieldGroup>
|
||||||
|
<Label label-for="code">Kode</Label>
|
||||||
|
<Field id="code" :errors="errors">
|
||||||
|
<FormField v-slot="{ componentField }" name="code">
|
||||||
|
<FormItem>
|
||||||
|
<FormControl>
|
||||||
|
<Input
|
||||||
|
id="code" type="text" placeholder="Masukkan kode spesialisasi" autocomplete="off"
|
||||||
|
v-bind="componentField"
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
</FormField>
|
||||||
|
</Field>
|
||||||
|
</FieldGroup>
|
||||||
|
|
||||||
|
<FieldGroup>
|
||||||
|
<Label label-for="installationId">Instalasi</Label>
|
||||||
|
<Field id="installationId" :errors="errors">
|
||||||
|
<FormField v-slot="{ componentField }" name="installationId">
|
||||||
|
<FormItem>
|
||||||
|
<FormControl>
|
||||||
|
<Combobox
|
||||||
|
id="installationId" v-bind="componentField" :items="installation.items"
|
||||||
|
:placeholder="installation.msg.placeholder" :search-placeholder="installation.msg.search"
|
||||||
|
:empty-message="installation.msg.empty"
|
||||||
|
@update:model-value="(value) => onInstallationChanged(value, setFieldValue)"
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
</FormField>
|
||||||
|
</Field>
|
||||||
|
</FieldGroup>
|
||||||
|
|
||||||
|
<FieldGroup>
|
||||||
|
<Label label-for="unitId">Unit</Label>
|
||||||
|
<Field id="unitId" :errors="errors">
|
||||||
|
<FormField v-slot="{ componentField }" name="unitId">
|
||||||
|
<FormItem>
|
||||||
|
<FormControl>
|
||||||
|
<Combobox
|
||||||
|
id="unitId" :disabled="!values.installationId" v-bind="componentField" :items="unit.items"
|
||||||
|
:placeholder="unit.msg.placeholder" :search-placeholder="unit.msg.search"
|
||||||
|
:empty-message="unit.msg.empty"
|
||||||
|
@update:model-value="(value) => onUnitChanged(value, setFieldValue)"
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
</FormField>
|
||||||
|
</Field>
|
||||||
|
</FieldGroup>
|
||||||
|
|
||||||
|
<FieldGroup>
|
||||||
|
<Label label-for="specialistId">Specialist</Label>
|
||||||
|
<Field id="specialistId" :errors="errors">
|
||||||
|
<FormField v-slot="{ componentField }" name="specialistId">
|
||||||
|
<FormItem>
|
||||||
|
<FormControl>
|
||||||
|
<Combobox
|
||||||
|
id="specialistId" :disabled="!values.unitId" v-bind="componentField" :items="specialist.items"
|
||||||
|
:placeholder="specialist.msg.placeholder" :search-placeholder="specialist.msg.search"
|
||||||
|
:empty-message="specialist.msg.empty"
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
</FormField>
|
||||||
|
</Field>
|
||||||
|
</FieldGroup>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex justify-end gap-2 mt-4">
|
||||||
|
<Button type="button" variant="outline" @click="onCancelForm({ resetForm })">
|
||||||
|
Batal
|
||||||
|
</Button>
|
||||||
|
<Button type="submit">
|
||||||
|
Simpan
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</Form>
|
||||||
|
</template>
|
||||||
@@ -1,214 +1,128 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import type { FormErrors } from '~/types/error'
|
// Components
|
||||||
import { toTypedSchema } from '@vee-validate/zod'
|
import Block from '~/components/pub/custom-ui/doc-entry/block.vue'
|
||||||
import Combobox from '~/components/pub/custom-ui/form/combobox.vue'
|
import Cell from '~/components/pub/custom-ui/doc-entry/cell.vue'
|
||||||
import FieldGroup from '~/components/pub/custom-ui/form/field-group.vue'
|
import Field from '~/components/pub/custom-ui/doc-entry/field.vue'
|
||||||
import Field from '~/components/pub/custom-ui/form/field.vue'
|
import Label from '~/components/pub/custom-ui/doc-entry/label.vue'
|
||||||
import Label from '~/components/pub/custom-ui/form/label.vue'
|
// import Combobox from '~/components/pub/custom-ui/form/combobox.vue'
|
||||||
import { Form } from '~/components/pub/ui/form'
|
|
||||||
|
|
||||||
interface SubSpecialistFormData {
|
// Types
|
||||||
name: string
|
import type { SubspecialistFormData } from '~/schemas/subspecialist.schema.ts'
|
||||||
code: string
|
|
||||||
installationId: string
|
// Helpers
|
||||||
unitId: string
|
import type z from 'zod'
|
||||||
specialistId: string
|
import { toTypedSchema } from '@vee-validate/zod'
|
||||||
|
import { useForm } from 'vee-validate'
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
schema: z.ZodSchema<any>
|
||||||
|
specialists: any[]
|
||||||
|
values: any
|
||||||
|
isLoading?: boolean
|
||||||
|
isReadonly?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<Props>()
|
||||||
schema: any
|
const isLoading = props.isLoading !== undefined ? props.isLoading : false
|
||||||
initialValues?: Partial<SubSpecialistFormData>
|
const isReadonly = props.isReadonly !== undefined ? props.isReadonly : false
|
||||||
errors?: FormErrors
|
|
||||||
installation: {
|
|
||||||
msg: {
|
|
||||||
placeholder: string
|
|
||||||
search: string
|
|
||||||
empty: string
|
|
||||||
}
|
|
||||||
items: {
|
|
||||||
value: string
|
|
||||||
label: string
|
|
||||||
code: string
|
|
||||||
}[]
|
|
||||||
}
|
|
||||||
unit: {
|
|
||||||
msg: {
|
|
||||||
placeholder: string
|
|
||||||
search: string
|
|
||||||
empty: string
|
|
||||||
}
|
|
||||||
items: {
|
|
||||||
value: string
|
|
||||||
label: string
|
|
||||||
code: string
|
|
||||||
}[]
|
|
||||||
}
|
|
||||||
specialist: {
|
|
||||||
msg: {
|
|
||||||
placeholder: string
|
|
||||||
search: string
|
|
||||||
empty: string
|
|
||||||
}
|
|
||||||
items: {
|
|
||||||
value: string
|
|
||||||
label: string
|
|
||||||
code: string
|
|
||||||
}[]
|
|
||||||
}
|
|
||||||
}>()
|
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
'submit': [values: SubSpecialistFormData, resetForm: () => void]
|
submit: [values: SubspecialistFormData, resetForm: () => void]
|
||||||
'cancel': [resetForm: () => void]
|
cancel: [resetForm: () => void]
|
||||||
'installationChanged': [id: string]
|
|
||||||
'unitChanged': [id: string]
|
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
const formSchema = toTypedSchema(props.schema)
|
const { defineField, errors, meta } = useForm({
|
||||||
|
validationSchema: toTypedSchema(props.schema),
|
||||||
|
initialValues: {
|
||||||
|
code: '',
|
||||||
|
name: '',
|
||||||
|
specialist_id: 0,
|
||||||
|
} as Partial<SubspecialistFormData>,
|
||||||
|
})
|
||||||
|
|
||||||
|
const [code, codeAttrs] = defineField('code')
|
||||||
|
const [name, nameAttrs] = defineField('name')
|
||||||
|
const [specialist, specialistAttrs] = defineField('specialist_id')
|
||||||
|
|
||||||
|
// Fill fields from props.values if provided
|
||||||
|
if (props.values) {
|
||||||
|
if (props.values.code !== undefined) code.value = props.values.code
|
||||||
|
if (props.values.name !== undefined) name.value = props.values.name
|
||||||
|
if (props.values.specialist_id !== undefined) specialist.value = props.values.specialist_id
|
||||||
|
}
|
||||||
|
|
||||||
|
const resetForm = () => {
|
||||||
|
code.value = ''
|
||||||
|
name.value = ''
|
||||||
|
specialist.value = ''
|
||||||
|
}
|
||||||
|
|
||||||
// Form submission handler
|
// Form submission handler
|
||||||
function onSubmitForm(values: any, { resetForm }: { resetForm: () => void }) {
|
function onSubmitForm(values: any) {
|
||||||
const formData: SubSpecialistFormData = {
|
const formData: SubspecialistFormData = {
|
||||||
name: values.name || '',
|
name: name.value || '',
|
||||||
code: values.code || '',
|
code: code.value || '',
|
||||||
installationId: values.installationId || '',
|
specialist_id: specialist.value || '',
|
||||||
unitId: values.unitId || '',
|
|
||||||
specialistId: values.specialistId || '',
|
|
||||||
}
|
}
|
||||||
emit('submit', formData, resetForm)
|
emit('submit', formData, resetForm)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Form cancel handler
|
// Form cancel handler
|
||||||
function onCancelForm({ resetForm }: { resetForm: () => void }) {
|
function onCancelForm() {
|
||||||
emit('cancel', resetForm)
|
emit('cancel', resetForm)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Watch for installation changes
|
|
||||||
function onInstallationChanged(installationId: string, setFieldValue: any) {
|
|
||||||
setFieldValue('unitId', '', false)
|
|
||||||
setFieldValue('specialistId', '', false)
|
|
||||||
emit('installationChanged', installationId || '')
|
|
||||||
}
|
|
||||||
|
|
||||||
function onUnitChanged(unitId: string, setFieldValue: any) {
|
|
||||||
setFieldValue('specialistId', '', false)
|
|
||||||
emit('unitChanged', unitId || '')
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<Form
|
<form id="form-specialist" @submit.prevent>
|
||||||
v-slot="{
|
<Block labelSize="thin" class="!mb-2.5 !pt-0 xl:!mb-3" :colCount="1">
|
||||||
handleSubmit,
|
<Cell>
|
||||||
resetForm,
|
<Label height="compact">Kode</Label>
|
||||||
values,
|
<Field :errMessage="errors.code">
|
||||||
setFieldValue,
|
<Input id="code" v-model="code" v-bind="codeAttrs" :disabled="isLoading || isReadonly" />
|
||||||
}" as="" keep-values :validation-schema="formSchema"
|
</Field>
|
||||||
:initial-values="initialValues"
|
</Cell>
|
||||||
>
|
<Cell>
|
||||||
<form id="entry-form" @submit="handleSubmit($event, (values) => onSubmitForm(values, { resetForm }))">
|
<Label height="compact">Nama</Label>
|
||||||
<div class="mb-5 border-b border-b-slate-300 pb-3 text-lg xl:text-xl">
|
<Field :errMessage="errors.name">
|
||||||
<div class="flex flex-col justify-between">
|
<Input id="name" v-model="name" v-bind="nameAttrs" :disabled="isLoading || isReadonly" />
|
||||||
<FieldGroup>
|
</Field>
|
||||||
<Label label-for="name">Nama</Label>
|
</Cell>
|
||||||
<Field id="name" :errors="errors">
|
<Cell>
|
||||||
<FormField v-slot="{ componentField }" name="name">
|
<Label height="compact">Spesialis</Label>
|
||||||
<FormItem>
|
<Field :errMessage="errors.specialist">
|
||||||
<FormControl>
|
<!-- <Combobox
|
||||||
<Input
|
id="specialis"
|
||||||
id="name" type="text" placeholder="Masukkan nama spesialisasi" autocomplete="off"
|
placeholder="Pilih spesialis"
|
||||||
v-bind="componentField"
|
search-placeholder="Cari spesialis"
|
||||||
/>
|
empty-message="Spesialis tidak ditemukan"
|
||||||
</FormControl>
|
v-model="specialist"
|
||||||
<FormMessage />
|
v-bind="specialistAttrs"
|
||||||
</FormItem>
|
:items="specialists"
|
||||||
</FormField>
|
:disabled="isLoading || isReadonly"
|
||||||
</Field>
|
/> -->
|
||||||
</FieldGroup>
|
<Select
|
||||||
|
id="specialist"
|
||||||
<FieldGroup>
|
icon-name="i-lucide-chevron-down"
|
||||||
<Label label-for="code">Kode</Label>
|
placeholder="Pilih spesialis"
|
||||||
<Field id="code" :errors="errors">
|
v-model="specialist"
|
||||||
<FormField v-slot="{ componentField }" name="code">
|
v-bind="specialistAttrs"
|
||||||
<FormItem>
|
:items="specialists"
|
||||||
<FormControl>
|
:disabled="isLoading || isReadonly"
|
||||||
<Input
|
/>
|
||||||
id="code" type="text" placeholder="Masukkan kode spesialisasi" autocomplete="off"
|
</Field>
|
||||||
v-bind="componentField"
|
</Cell>
|
||||||
/>
|
</Block>
|
||||||
</FormControl>
|
<div class="my-2 flex justify-end gap-2 py-2">
|
||||||
<FormMessage />
|
<Button type="button" variant="secondary" class="w-[120px]" @click="onCancelForm"> Kembali </Button>
|
||||||
</FormItem>
|
<Button
|
||||||
</FormField>
|
v-if="!isReadonly"
|
||||||
</Field>
|
type="button"
|
||||||
</FieldGroup>
|
class="w-[120px]"
|
||||||
|
:disabled="isLoading || !meta.valid"
|
||||||
<FieldGroup>
|
@click="onSubmitForm"
|
||||||
<Label label-for="installationId">Instalasi</Label>
|
>
|
||||||
<Field id="installationId" :errors="errors">
|
Simpan
|
||||||
<FormField v-slot="{ componentField }" name="installationId">
|
</Button>
|
||||||
<FormItem>
|
</div>
|
||||||
<FormControl>
|
</form>
|
||||||
<Combobox
|
|
||||||
id="installationId" v-bind="componentField" :items="installation.items"
|
|
||||||
:placeholder="installation.msg.placeholder" :search-placeholder="installation.msg.search"
|
|
||||||
:empty-message="installation.msg.empty"
|
|
||||||
@update:model-value="(value) => onInstallationChanged(value, setFieldValue)"
|
|
||||||
/>
|
|
||||||
</FormControl>
|
|
||||||
<FormMessage />
|
|
||||||
</FormItem>
|
|
||||||
</FormField>
|
|
||||||
</Field>
|
|
||||||
</FieldGroup>
|
|
||||||
|
|
||||||
<FieldGroup>
|
|
||||||
<Label label-for="unitId">Unit</Label>
|
|
||||||
<Field id="unitId" :errors="errors">
|
|
||||||
<FormField v-slot="{ componentField }" name="unitId">
|
|
||||||
<FormItem>
|
|
||||||
<FormControl>
|
|
||||||
<Combobox
|
|
||||||
id="unitId" :disabled="!values.installationId" v-bind="componentField" :items="unit.items"
|
|
||||||
:placeholder="unit.msg.placeholder" :search-placeholder="unit.msg.search"
|
|
||||||
:empty-message="unit.msg.empty"
|
|
||||||
@update:model-value="(value) => onUnitChanged(value, setFieldValue)"
|
|
||||||
/>
|
|
||||||
</FormControl>
|
|
||||||
<FormMessage />
|
|
||||||
</FormItem>
|
|
||||||
</FormField>
|
|
||||||
</Field>
|
|
||||||
</FieldGroup>
|
|
||||||
|
|
||||||
<FieldGroup>
|
|
||||||
<Label label-for="specialistId">Specialist</Label>
|
|
||||||
<Field id="specialistId" :errors="errors">
|
|
||||||
<FormField v-slot="{ componentField }" name="specialistId">
|
|
||||||
<FormItem>
|
|
||||||
<FormControl>
|
|
||||||
<Combobox
|
|
||||||
id="specialistId" :disabled="!values.unitId" v-bind="componentField" :items="specialist.items"
|
|
||||||
:placeholder="specialist.msg.placeholder" :search-placeholder="specialist.msg.search"
|
|
||||||
:empty-message="specialist.msg.empty"
|
|
||||||
/>
|
|
||||||
</FormControl>
|
|
||||||
<FormMessage />
|
|
||||||
</FormItem>
|
|
||||||
</FormField>
|
|
||||||
</Field>
|
|
||||||
</FieldGroup>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="flex justify-end gap-2 mt-4">
|
|
||||||
<Button type="button" variant="outline" @click="onCancelForm({ resetForm })">
|
|
||||||
Batal
|
|
||||||
</Button>
|
|
||||||
<Button type="submit">
|
|
||||||
Simpan
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</Form>
|
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -10,14 +10,13 @@ import { defineAsyncComponent } from 'vue'
|
|||||||
|
|
||||||
type SmallDetailDto = any
|
type SmallDetailDto = any
|
||||||
|
|
||||||
const action = defineAsyncComponent(() => import('~/components/pub/custom-ui/data/dropdown-action-ud.vue'))
|
const action = defineAsyncComponent(() => import('~/components/pub/custom-ui/data/dropdown-action-dud.vue'))
|
||||||
|
|
||||||
export const cols: Col[] = [{ width: 100 }, {}, {}, {}, { width: 50 }]
|
export const cols: Col[] = [{}, {}, {}, { width: 50 }]
|
||||||
|
|
||||||
export const header: Th[][] = [
|
export const header: Th[][] = [[{ label: 'Kode' }, { label: 'Nama' }, { label: 'Specialis' }, { label: '' }]]
|
||||||
[{ label: 'Id' }, { label: 'Nama' }, { label: 'Kode' }, { label: 'Specialist' }, { label: '' }],
|
|
||||||
]
|
export const keys = ['code', 'name', 'specialist', 'action']
|
||||||
export const keys = ['id', 'name', 'cellphone', 'religion_code', 'action']
|
|
||||||
|
|
||||||
export const delKeyNames: KeyLabel[] = [
|
export const delKeyNames: KeyLabel[] = [
|
||||||
{ key: 'code', label: 'Kode' },
|
{ key: 'code', label: 'Kode' },
|
||||||
@@ -27,15 +26,11 @@ export const delKeyNames: KeyLabel[] = [
|
|||||||
export const funcParsed: RecStrFuncUnknown = {
|
export const funcParsed: RecStrFuncUnknown = {
|
||||||
name: (rec: unknown): unknown => {
|
name: (rec: unknown): unknown => {
|
||||||
const recX = rec as SmallDetailDto
|
const recX = rec as SmallDetailDto
|
||||||
return `${recX.firstName} ${recX.lastName || ''}`.trim()
|
return `${recX.name}`.trim()
|
||||||
},
|
|
||||||
unit: (rec: unknown): unknown => {
|
|
||||||
const recX = rec as SmallDetailDto
|
|
||||||
return recX.unit?.name || '-'
|
|
||||||
},
|
},
|
||||||
specialist: (rec: unknown): unknown => {
|
specialist: (rec: unknown): unknown => {
|
||||||
const recX = rec as SmallDetailDto
|
const recX = rec as SmallDetailDto
|
||||||
return recX.specialist?.name || '-'
|
return recX.specialist_id || '-'
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -45,9 +40,6 @@ export const funcComponent: RecStrFuncComponent = {
|
|||||||
idx,
|
idx,
|
||||||
rec: rec as object,
|
rec: rec as object,
|
||||||
component: action,
|
component: action,
|
||||||
props: {
|
|
||||||
size: 'sm',
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
return res
|
return res
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -22,10 +22,15 @@ function handlePageChange(page: number) {
|
|||||||
<template>
|
<template>
|
||||||
<div class="space-y-4">
|
<div class="space-y-4">
|
||||||
<PubBaseDataTable
|
<PubBaseDataTable
|
||||||
:rows="data" :cols="cols" :header="header" :keys="keys" :func-parsed="funcParsed"
|
:rows="data"
|
||||||
:func-html="funcHtml" :func-component="funcComponent" :skeleton-size="paginationMeta?.pageSize"
|
: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" />
|
<PaginationView :pagination-meta="paginationMeta" @page-change="handlePageChange" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import Field from '~/components/pub/custom-ui/doc-entry/field.vue'
|
|||||||
import Label from '~/components/pub/custom-ui/doc-entry/label.vue'
|
import Label from '~/components/pub/custom-ui/doc-entry/label.vue'
|
||||||
|
|
||||||
// Types
|
// Types
|
||||||
import type { DeviceFormData } from '~/schemas/device.schema'
|
import type { DeviceFormData } from '~/schemas/device.schema.ts'
|
||||||
|
|
||||||
// Helpers
|
// Helpers
|
||||||
import type z from 'zod'
|
import type z from 'zod'
|
||||||
|
|||||||
@@ -0,0 +1,125 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import type { FormErrors } from '~/types/error'
|
||||||
|
import { toTypedSchema } from '@vee-validate/zod'
|
||||||
|
import Combobox from '~/components/pub/custom-ui/form/combobox.vue'
|
||||||
|
import FieldGroup from '~/components/pub/custom-ui/form/field-group.vue'
|
||||||
|
import Field from '~/components/pub/custom-ui/form/field.vue'
|
||||||
|
import Label from '~/components/pub/custom-ui/form/label.vue'
|
||||||
|
|
||||||
|
interface UnitFormData {
|
||||||
|
name: string
|
||||||
|
code: string
|
||||||
|
parentId: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
unit: {
|
||||||
|
msg: {
|
||||||
|
placeholder: string
|
||||||
|
search: string
|
||||||
|
empty: string
|
||||||
|
}
|
||||||
|
items: {
|
||||||
|
value: string
|
||||||
|
label: string
|
||||||
|
code: string
|
||||||
|
}[]
|
||||||
|
}
|
||||||
|
schema: any
|
||||||
|
initialValues?: Partial<UnitFormData>
|
||||||
|
errors?: FormErrors
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
'submit': [values: UnitFormData, resetForm: () => void]
|
||||||
|
'cancel': [resetForm: () => void]
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const formSchema = toTypedSchema(props.schema)
|
||||||
|
|
||||||
|
// Form submission handler
|
||||||
|
function onSubmitForm(values: any, { resetForm }: { resetForm: () => void }) {
|
||||||
|
const formData: UnitFormData = {
|
||||||
|
name: values.name || '',
|
||||||
|
code: values.code || '',
|
||||||
|
parentId: values.parentId || '',
|
||||||
|
}
|
||||||
|
emit('submit', formData, resetForm)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Form cancel handler
|
||||||
|
function onCancelForm({ resetForm }: { resetForm: () => void }) {
|
||||||
|
emit('cancel', resetForm)
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<Form
|
||||||
|
v-slot="{ handleSubmit, resetForm }" as="" keep-values :validation-schema="formSchema"
|
||||||
|
:initial-values="initialValues"
|
||||||
|
>
|
||||||
|
<form id="entry-form" @submit="handleSubmit($event, (values) => onSubmitForm(values, { resetForm }))">
|
||||||
|
<div class="mb-5 border-b border-b-slate-300 pb-3 text-lg xl:text-xl">
|
||||||
|
<div class="flex flex-col justify-between">
|
||||||
|
<FieldGroup>
|
||||||
|
<Label label-for="name">Nama</Label>
|
||||||
|
<Field id="name" :errors="errors">
|
||||||
|
<FormField v-slot="{ componentField }" name="name">
|
||||||
|
<FormItem>
|
||||||
|
<FormControl>
|
||||||
|
<Input
|
||||||
|
id="name" type="text" placeholder="Masukkan nama unit" autocomplete="off"
|
||||||
|
v-bind="componentField"
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
</FormField>
|
||||||
|
</Field>
|
||||||
|
</FieldGroup>
|
||||||
|
|
||||||
|
<FieldGroup>
|
||||||
|
<Label label-for="code">Kode</Label>
|
||||||
|
<Field id="code" :errors="errors">
|
||||||
|
<FormField v-slot="{ componentField }" name="code">
|
||||||
|
<FormItem>
|
||||||
|
<FormControl>
|
||||||
|
<Input id="code" type="text" placeholder="Masukkan kode unit" autocomplete="off" v-bind="componentField" />
|
||||||
|
</FormControl>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
</FormField>
|
||||||
|
</Field>
|
||||||
|
</FieldGroup>
|
||||||
|
|
||||||
|
<FieldGroup>
|
||||||
|
<Label label-for="parentId">Instalasi</Label>
|
||||||
|
<Field id="parentId" :errors="errors">
|
||||||
|
<FormField v-slot="{ componentField }" name="parentId">
|
||||||
|
<FormItem>
|
||||||
|
<FormControl>
|
||||||
|
<Combobox
|
||||||
|
id="parentId" v-bind="componentField" :items="unit.items"
|
||||||
|
:placeholder="unit.msg.placeholder" :search-placeholder="unit.msg.search"
|
||||||
|
:empty-message="unit.msg.empty"
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
</FormField>
|
||||||
|
</Field>
|
||||||
|
</FieldGroup>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex justify-end gap-2 mt-4">
|
||||||
|
<Button type="button" variant="outline" @click="onCancelForm({ resetForm })">
|
||||||
|
Batal
|
||||||
|
</Button>
|
||||||
|
<Button type="submit">
|
||||||
|
Simpan
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</Form>
|
||||||
|
</template>
|
||||||
@@ -1,126 +1,102 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import type { FormErrors } from '~/types/error'
|
// Components
|
||||||
import { toTypedSchema } from '@vee-validate/zod'
|
import Block from '~/components/pub/custom-ui/doc-entry/block.vue'
|
||||||
import Combobox from '~/components/pub/custom-ui/form/combobox.vue'
|
import Cell from '~/components/pub/custom-ui/doc-entry/cell.vue'
|
||||||
import FieldGroup from '~/components/pub/custom-ui/form/field-group.vue'
|
import Field from '~/components/pub/custom-ui/doc-entry/field.vue'
|
||||||
import Field from '~/components/pub/custom-ui/form/field.vue'
|
import Label from '~/components/pub/custom-ui/doc-entry/label.vue'
|
||||||
import Label from '~/components/pub/custom-ui/form/label.vue'
|
|
||||||
import { Form } from '~/components/pub/ui/form'
|
|
||||||
|
|
||||||
interface UnitFormData {
|
// Types
|
||||||
name: string
|
import type { UnitFormData } from '~/schemas/unit.schema.ts'
|
||||||
code: string
|
|
||||||
parentId: string
|
// Helpers
|
||||||
|
import type z from 'zod'
|
||||||
|
import { toTypedSchema } from '@vee-validate/zod'
|
||||||
|
import { useForm } from 'vee-validate'
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
schema: z.ZodSchema<any>
|
||||||
|
values: any
|
||||||
|
isLoading?: boolean
|
||||||
|
isReadonly?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<Props>()
|
||||||
unit: {
|
const isLoading = props.isLoading !== undefined ? props.isLoading : false
|
||||||
msg: {
|
const isReadonly = props.isReadonly !== undefined ? props.isReadonly : false
|
||||||
placeholder: string
|
|
||||||
search: string
|
|
||||||
empty: string
|
|
||||||
}
|
|
||||||
items: {
|
|
||||||
value: string
|
|
||||||
label: string
|
|
||||||
code: string
|
|
||||||
}[]
|
|
||||||
}
|
|
||||||
schema: any
|
|
||||||
initialValues?: Partial<UnitFormData>
|
|
||||||
errors?: FormErrors
|
|
||||||
}>()
|
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
'submit': [values: UnitFormData, resetForm: () => void]
|
submit: [values: UnitFormData, resetForm: () => void]
|
||||||
'cancel': [resetForm: () => void]
|
cancel: [resetForm: () => void]
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
const formSchema = toTypedSchema(props.schema)
|
const { defineField, errors, meta } = useForm({
|
||||||
|
validationSchema: toTypedSchema(props.schema),
|
||||||
|
initialValues: {
|
||||||
|
code: '',
|
||||||
|
name: '',
|
||||||
|
installation: '',
|
||||||
|
} as Partial<UnitFormData>,
|
||||||
|
})
|
||||||
|
|
||||||
|
const [code, codeAttrs] = defineField('code')
|
||||||
|
const [name, nameAttrs] = defineField('name')
|
||||||
|
const [installation, installationAttrs] = defineField('installation')
|
||||||
|
|
||||||
|
// Fill fields from props.values if provided
|
||||||
|
if (props.values) {
|
||||||
|
if (props.values.code !== undefined) code.value = props.values.code
|
||||||
|
if (props.values.name !== undefined) name.value = props.values.name
|
||||||
|
if (props.values.installation !== undefined) installation.value = props.values.installation
|
||||||
|
}
|
||||||
|
|
||||||
|
const resetForm = () => {
|
||||||
|
code.value = ''
|
||||||
|
name.value = ''
|
||||||
|
installation.value = ''
|
||||||
|
}
|
||||||
|
|
||||||
// Form submission handler
|
// Form submission handler
|
||||||
function onSubmitForm(values: any, { resetForm }: { resetForm: () => void }) {
|
function onSubmitForm(values: any) {
|
||||||
const formData: UnitFormData = {
|
const formData: UnitFormData = {
|
||||||
name: values.name || '',
|
name: name.value || '',
|
||||||
code: values.code || '',
|
code: code.value || '',
|
||||||
parentId: values.parentId || '',
|
installation: installation.value || '',
|
||||||
}
|
}
|
||||||
emit('submit', formData, resetForm)
|
emit('submit', formData, resetForm)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Form cancel handler
|
// Form cancel handler
|
||||||
function onCancelForm({ resetForm }: { resetForm: () => void }) {
|
function onCancelForm() {
|
||||||
emit('cancel', resetForm)
|
emit('cancel', resetForm)
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<Form
|
<form id="form-unit" @submit.prevent>
|
||||||
v-slot="{ handleSubmit, resetForm }" as="" keep-values :validation-schema="formSchema"
|
<Block labelSize="thin" class="!mb-2.5 !pt-0 xl:!mb-3" :colCount="1">
|
||||||
:initial-values="initialValues"
|
<Cell>
|
||||||
>
|
<Label height="compact">Kode</Label>
|
||||||
<form id="entry-form" @submit="handleSubmit($event, (values) => onSubmitForm(values, { resetForm }))">
|
<Field :errMessage="errors.code">
|
||||||
<div class="mb-5 border-b border-b-slate-300 pb-3 text-lg xl:text-xl">
|
<Input id="code" v-model="code" v-bind="codeAttrs" :disabled="isLoading || isReadonly" />
|
||||||
<div class="flex flex-col justify-between">
|
</Field>
|
||||||
<FieldGroup>
|
</Cell>
|
||||||
<Label label-for="name">Nama</Label>
|
<Cell>
|
||||||
<Field id="name" :errors="errors">
|
<Label height="compact">Nama</Label>
|
||||||
<FormField v-slot="{ componentField }" name="name">
|
<Field :errMessage="errors.name">
|
||||||
<FormItem>
|
<Input id="name" v-model="name" v-bind="nameAttrs" :disabled="isLoading || isReadonly" />
|
||||||
<FormControl>
|
</Field>
|
||||||
<Input
|
</Cell>
|
||||||
id="name" type="text" placeholder="Masukkan nama unit" autocomplete="off"
|
</Block>
|
||||||
v-bind="componentField"
|
<div class="my-2 flex justify-end gap-2 py-2">
|
||||||
/>
|
<Button type="button" variant="secondary" class="w-[120px]" @click="onCancelForm"> Kembali </Button>
|
||||||
</FormControl>
|
<Button
|
||||||
<FormMessage />
|
v-if="!isReadonly"
|
||||||
</FormItem>
|
type="button"
|
||||||
</FormField>
|
class="w-[120px]"
|
||||||
</Field>
|
:disabled="isLoading || !meta.valid"
|
||||||
</FieldGroup>
|
@click="onSubmitForm"
|
||||||
|
>
|
||||||
<FieldGroup>
|
Simpan
|
||||||
<Label label-for="code">Kode</Label>
|
</Button>
|
||||||
<Field id="code" :errors="errors">
|
</div>
|
||||||
<FormField v-slot="{ componentField }" name="code">
|
</form>
|
||||||
<FormItem>
|
|
||||||
<FormControl>
|
|
||||||
<Input id="code" type="text" placeholder="Masukkan kode unit" autocomplete="off" v-bind="componentField" />
|
|
||||||
</FormControl>
|
|
||||||
<FormMessage />
|
|
||||||
</FormItem>
|
|
||||||
</FormField>
|
|
||||||
</Field>
|
|
||||||
</FieldGroup>
|
|
||||||
|
|
||||||
<FieldGroup>
|
|
||||||
<Label label-for="parentId">Instalasi</Label>
|
|
||||||
<Field id="parentId" :errors="errors">
|
|
||||||
<FormField v-slot="{ componentField }" name="parentId">
|
|
||||||
<FormItem>
|
|
||||||
<FormControl>
|
|
||||||
<Combobox
|
|
||||||
id="parentId" v-bind="componentField" :items="unit.items"
|
|
||||||
:placeholder="unit.msg.placeholder" :search-placeholder="unit.msg.search"
|
|
||||||
:empty-message="unit.msg.empty"
|
|
||||||
/>
|
|
||||||
</FormControl>
|
|
||||||
<FormMessage />
|
|
||||||
</FormItem>
|
|
||||||
</FormField>
|
|
||||||
</Field>
|
|
||||||
</FieldGroup>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="flex justify-end gap-2 mt-4">
|
|
||||||
<Button type="button" variant="outline" @click="onCancelForm({ resetForm })">
|
|
||||||
Batal
|
|
||||||
</Button>
|
|
||||||
<Button type="submit">
|
|
||||||
Simpan
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</Form>
|
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -10,33 +10,13 @@ import { defineAsyncComponent } from 'vue'
|
|||||||
|
|
||||||
type SmallDetailDto = any
|
type SmallDetailDto = any
|
||||||
|
|
||||||
const action = defineAsyncComponent(() => import('~/components/pub/custom-ui/data/dropdown-action-ud.vue'))
|
const action = defineAsyncComponent(() => import('~/components/pub/custom-ui/data/dropdown-action-dud.vue'))
|
||||||
|
|
||||||
export const cols: Col[] = [
|
export const cols: Col[] = [{}, {}, {}, { width: 50 }]
|
||||||
{ width: 100 },
|
|
||||||
{ },
|
|
||||||
{ },
|
|
||||||
{ },
|
|
||||||
{ width: 50 },
|
|
||||||
]
|
|
||||||
|
|
||||||
export const header: Th[][] = [
|
export const header: Th[][] = [[{ label: 'Kode' }, { label: 'Nama' }, { label: 'Instalasi' }, { label: '' }]]
|
||||||
[
|
|
||||||
{ label: 'Id' },
|
|
||||||
{ label: 'Nama' },
|
|
||||||
{ label: 'Kode' },
|
|
||||||
{ label: 'Instalasi' },
|
|
||||||
{ label: '' },
|
|
||||||
],
|
|
||||||
]
|
|
||||||
|
|
||||||
export const keys = [
|
export const keys = ['code', 'name', 'installation', 'action']
|
||||||
'id',
|
|
||||||
'firstName',
|
|
||||||
'cellphone',
|
|
||||||
'birth_place',
|
|
||||||
'action',
|
|
||||||
]
|
|
||||||
|
|
||||||
export const delKeyNames: KeyLabel[] = [
|
export const delKeyNames: KeyLabel[] = [
|
||||||
{ key: 'code', label: 'Kode' },
|
{ key: 'code', label: 'Kode' },
|
||||||
@@ -46,22 +26,10 @@ export const delKeyNames: KeyLabel[] = [
|
|||||||
export const funcParsed: RecStrFuncUnknown = {
|
export const funcParsed: RecStrFuncUnknown = {
|
||||||
name: (rec: unknown): unknown => {
|
name: (rec: unknown): unknown => {
|
||||||
const recX = rec as SmallDetailDto
|
const recX = rec as SmallDetailDto
|
||||||
return `${recX.frontTitle} ${recX.name} ${recX.endTitle}`.trim()
|
return `${recX.name}`.trim()
|
||||||
},
|
},
|
||||||
identity_number: (rec: unknown): unknown => {
|
installation: (_rec: unknown): unknown => {
|
||||||
const recX = rec as SmallDetailDto
|
return '-'
|
||||||
if (recX.identity_number?.substring(0, 5) === 'BLANK') {
|
|
||||||
return '(TANPA NIK)'
|
|
||||||
}
|
|
||||||
return recX.identity_number
|
|
||||||
},
|
|
||||||
inPatient_itemPrice: (rec: unknown): unknown => {
|
|
||||||
const recX = rec as SmallDetailDto
|
|
||||||
return Number(recX.inPatient_itemPrice.price).toLocaleString('id-ID')
|
|
||||||
},
|
|
||||||
outPatient_itemPrice: (rec: unknown): unknown => {
|
|
||||||
const recX = rec as SmallDetailDto
|
|
||||||
return Number(recX.outPatient_itemPrice.price).toLocaleString('id-ID')
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -71,16 +39,9 @@ export const funcComponent: RecStrFuncComponent = {
|
|||||||
idx,
|
idx,
|
||||||
rec: rec as object,
|
rec: rec as object,
|
||||||
component: action,
|
component: action,
|
||||||
props: {
|
|
||||||
size: 'sm',
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
return res
|
return res
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
export const funcHtml: RecStrFuncUnknown = {
|
export const funcHtml: RecStrFuncUnknown = {}
|
||||||
patient_address(_rec) {
|
|
||||||
return '-'
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -22,10 +22,15 @@ function handlePageChange(page: number) {
|
|||||||
<template>
|
<template>
|
||||||
<div class="space-y-4">
|
<div class="space-y-4">
|
||||||
<PubBaseDataTable
|
<PubBaseDataTable
|
||||||
:rows="data" :cols="cols" :header="header" :keys="keys" :func-parsed="funcParsed"
|
:rows="data"
|
||||||
:func-html="funcHtml" :func-component="funcComponent" :skeleton-size="paginationMeta?.pageSize"
|
: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" />
|
<PaginationView :pagination-meta="paginationMeta" @page-change="handlePageChange" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import Label from '~/components/pub/custom-ui/doc-entry/label.vue'
|
|||||||
import Button from '~/components/pub/ui/button/Button.vue'
|
import Button from '~/components/pub/ui/button/Button.vue'
|
||||||
|
|
||||||
// Types
|
// Types
|
||||||
import type { UomFormData } from '~/schemas/uom.schema'
|
import type { UomFormData } from '~/schemas/uom.schema.ts'
|
||||||
|
|
||||||
// Helpers
|
// Helpers
|
||||||
import type z from 'zod'
|
import type z from 'zod'
|
||||||
|
|||||||
@@ -1,62 +0,0 @@
|
|||||||
<script setup lang="ts">
|
|
||||||
import { z, ZodError } from 'zod'
|
|
||||||
import Action from '~/components/pub/custom-ui/nav-footer/ba-dr-su.vue'
|
|
||||||
|
|
||||||
const errors = ref({})
|
|
||||||
const data = ref({
|
|
||||||
code: '',
|
|
||||||
name: '',
|
|
||||||
type: '',
|
|
||||||
})
|
|
||||||
|
|
||||||
const schema = z.object({
|
|
||||||
code: z.string().min(1, 'Code must be at least 1 characters long'),
|
|
||||||
name: z.string().min(1, 'Name must be at least 1 characters long'),
|
|
||||||
type: z.string(),
|
|
||||||
})
|
|
||||||
|
|
||||||
function onClick(type: string) {
|
|
||||||
if (type === 'cancel') {
|
|
||||||
navigateTo('/tools-equipment-src/device')
|
|
||||||
} else if (type === 'draft') {
|
|
||||||
// do something
|
|
||||||
} else if (type === 'submit') {
|
|
||||||
// do something
|
|
||||||
const input = data.value
|
|
||||||
console.log(input)
|
|
||||||
const errorsParsed: any = {}
|
|
||||||
try {
|
|
||||||
const result = schema.safeParse(input)
|
|
||||||
if (!result.success) {
|
|
||||||
// You can handle the error here, e.g. show a message
|
|
||||||
const errorsCaptures = result?.error?.errors || []
|
|
||||||
const errorMessage = result.error.errors[0]?.message ?? 'Validation error occurred'
|
|
||||||
errorsCaptures.forEach((value: any) => {
|
|
||||||
const keyName = value?.path?.length > 0 ? value.path[0] : 'key'
|
|
||||||
errorsParsed[keyName as string] = value.message || ''
|
|
||||||
})
|
|
||||||
console.log(errorMessage)
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
if (e instanceof ZodError) {
|
|
||||||
const jsonError = e.flatten()
|
|
||||||
console.log(JSON.stringify(jsonError, null, 2))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
setTimeout(() => {
|
|
||||||
errors.value = errorsParsed
|
|
||||||
}, 0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<div class="mb-5 border-b border-b-slate-300 pb-3 text-lg xl:text-xl">
|
|
||||||
<Icon name="i-lucide-paint-bucket" class="me-2" />
|
|
||||||
<span class="font-semibold">Tambah</span> Alat Kesehatan
|
|
||||||
</div>
|
|
||||||
<AppDeviceEntryForm v-model="data" :errors="errors" />
|
|
||||||
<div class="my-2 flex justify-end py-2">
|
|
||||||
<Action @click="onClick" />
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
@@ -1,65 +0,0 @@
|
|||||||
<script setup lang="ts">
|
|
||||||
import type { DataTableLoader } from '~/components/pub/base/data-table/type'
|
|
||||||
import type { HeaderPrep, RefSearchNav } from '~/components/pub/custom-ui/data/types'
|
|
||||||
import Header from '~/components/pub/custom-ui/nav-header/prep.vue'
|
|
||||||
|
|
||||||
const data = ref([])
|
|
||||||
|
|
||||||
const refSearchNav: RefSearchNav = {
|
|
||||||
onClick: () => {
|
|
||||||
// open filter modal
|
|
||||||
},
|
|
||||||
onInput: (_val: string) => {
|
|
||||||
// filter patient list
|
|
||||||
},
|
|
||||||
onClear: () => {
|
|
||||||
// clear url param
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
const isLoading = reactive<DataTableLoader>({
|
|
||||||
isTableLoading: false,
|
|
||||||
})
|
|
||||||
|
|
||||||
const recId = ref<number>(0)
|
|
||||||
const recAction = ref<string>('')
|
|
||||||
const recItem = ref<any>(null)
|
|
||||||
|
|
||||||
const headerPrep: HeaderPrep = {
|
|
||||||
title: 'Alat Kesehatan',
|
|
||||||
icon: 'i-lucide-paint-bucket',
|
|
||||||
addNav: {
|
|
||||||
label: 'Tambah',
|
|
||||||
onClick: () => navigateTo('/tools-equipment-src/device/add'),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
async function getMaterialList() {
|
|
||||||
isLoading.dataListLoading = true
|
|
||||||
|
|
||||||
const resp = await xfetch('/api/v1/device')
|
|
||||||
if (resp.success) {
|
|
||||||
data.value = (resp.body as Record<string, any>).data
|
|
||||||
}
|
|
||||||
|
|
||||||
isLoading.dataListLoading = false
|
|
||||||
}
|
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
getMaterialList()
|
|
||||||
})
|
|
||||||
|
|
||||||
provide('rec_id', recId)
|
|
||||||
provide('rec_action', recAction)
|
|
||||||
provide('rec_item', recItem)
|
|
||||||
provide('table_data_loader', isLoading)
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<div class="rounded-md border p-4">
|
|
||||||
<Header :prep="{ ...headerPrep }" :ref-search-nav="refSearchNav" />
|
|
||||||
<div class="rounded-md border p-4">
|
|
||||||
<AppMaterialList v-if="!isLoading.dataListLoading" :data="data" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
@@ -0,0 +1,208 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import type { HeaderPrep } from '~/components/pub/custom-ui/data/types'
|
||||||
|
import AppDivisionEntryForm from '~/components/app/division/entry-form.vue'
|
||||||
|
import Dialog from '~/components/pub/base/modal/dialog.vue'
|
||||||
|
import RecordConfirmation from '~/components/pub/custom-ui/confirmation/record-confirmation.vue'
|
||||||
|
import { ActionEvents } from '~/components/pub/custom-ui/data/types'
|
||||||
|
import Header from '~/components/pub/custom-ui/nav-header/header.vue'
|
||||||
|
import { usePaginatedList } from '~/composables/usePaginatedList'
|
||||||
|
import { divisionConf, divisionTreeConfig, schema } from './entry'
|
||||||
|
// #region State & Computed
|
||||||
|
// Dialog state
|
||||||
|
const isFormEntryDialogOpen = ref(false)
|
||||||
|
const isRecordConfirmationOpen = ref(false)
|
||||||
|
|
||||||
|
// Table action rowId provider
|
||||||
|
const recId = ref<number>(0)
|
||||||
|
const recAction = ref<string>('')
|
||||||
|
const recItem = ref<any>(null)
|
||||||
|
|
||||||
|
async function fetchDivisionData(_params: any) {
|
||||||
|
const endpoint = '/api/v1/_dev/division/list'
|
||||||
|
return await xfetch(endpoint)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Menggunakan composable untuk pagination
|
||||||
|
const {
|
||||||
|
data,
|
||||||
|
isLoading,
|
||||||
|
paginationMeta,
|
||||||
|
searchInput,
|
||||||
|
handlePageChange,
|
||||||
|
handleSearch,
|
||||||
|
fetchData: getDivisionList,
|
||||||
|
} = usePaginatedList({
|
||||||
|
fetchFn: fetchDivisionData,
|
||||||
|
entityName: 'division',
|
||||||
|
})
|
||||||
|
|
||||||
|
const headerPrep: HeaderPrep = {
|
||||||
|
title: 'Divisi',
|
||||||
|
icon: 'i-lucide-box',
|
||||||
|
refSearchNav: {
|
||||||
|
placeholder: 'Cari (min. 3 karakter)...',
|
||||||
|
minLength: 3,
|
||||||
|
debounceMs: 500,
|
||||||
|
showValidationFeedback: true,
|
||||||
|
onInput: (_val: string) => {
|
||||||
|
// Handle search input - this will be triggered by the header component
|
||||||
|
},
|
||||||
|
onClick: () => {
|
||||||
|
// Handle search button click if needed
|
||||||
|
},
|
||||||
|
onClear: () => {
|
||||||
|
// Handle search clear
|
||||||
|
},
|
||||||
|
},
|
||||||
|
addNav: {
|
||||||
|
label: 'Tambah Divisi',
|
||||||
|
icon: 'i-lucide-send',
|
||||||
|
onClick: () => {
|
||||||
|
isFormEntryDialogOpen.value = true
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
provide('rec_id', recId)
|
||||||
|
provide('rec_action', recAction)
|
||||||
|
provide('rec_item', recItem)
|
||||||
|
provide('table_data_loader', isLoading)
|
||||||
|
// #endregion
|
||||||
|
|
||||||
|
// #region Functions
|
||||||
|
|
||||||
|
async function handleDeleteRow(record: any) {
|
||||||
|
try {
|
||||||
|
// TODO : hit backend request untuk delete
|
||||||
|
console.log('Deleting record:', record)
|
||||||
|
|
||||||
|
// Simulate API call
|
||||||
|
// const response = await xfetch(`/api/v1/division/${record.id}`, {
|
||||||
|
// method: 'DELETE'
|
||||||
|
// })
|
||||||
|
|
||||||
|
// Refresh data setelah berhasil delete
|
||||||
|
await getDivisionList()
|
||||||
|
|
||||||
|
// TODO: Show success message
|
||||||
|
console.log('Record deleted successfully')
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error deleting record:', error)
|
||||||
|
// TODO: Show error message
|
||||||
|
} finally {
|
||||||
|
// Reset record state
|
||||||
|
recId.value = 0
|
||||||
|
recAction.value = ''
|
||||||
|
recItem.value = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// #endregion region
|
||||||
|
|
||||||
|
// #region Form event handlers
|
||||||
|
|
||||||
|
function onCancelForm(resetForm: () => void) {
|
||||||
|
isFormEntryDialogOpen.value = false
|
||||||
|
setTimeout(() => {
|
||||||
|
resetForm()
|
||||||
|
}, 500)
|
||||||
|
}
|
||||||
|
|
||||||
|
async function onSubmitForm(values: any, resetForm: () => void) {
|
||||||
|
let isSuccess = false
|
||||||
|
try {
|
||||||
|
// TODO: Implement form submission logic
|
||||||
|
console.log('Form submitted:', values)
|
||||||
|
|
||||||
|
// Simulate API call
|
||||||
|
// const response = await xfetch('/api/v1/division', {
|
||||||
|
// method: 'POST',
|
||||||
|
// body: JSON.stringify(values)
|
||||||
|
// })
|
||||||
|
|
||||||
|
// If successful, mark as success and close dialog
|
||||||
|
isFormEntryDialogOpen.value = false
|
||||||
|
isSuccess = true
|
||||||
|
|
||||||
|
// Refresh data after successful submission
|
||||||
|
await getDivisionList()
|
||||||
|
|
||||||
|
// TODO: Show success message
|
||||||
|
console.log('Division created successfully')
|
||||||
|
} catch (error: unknown) {
|
||||||
|
console.warn('Error submitting form:', error)
|
||||||
|
isSuccess = false
|
||||||
|
// Don't close dialog or reset form on error
|
||||||
|
// TODO: Show error message to user
|
||||||
|
} finally {
|
||||||
|
if (isSuccess) {
|
||||||
|
setTimeout(() => {
|
||||||
|
resetForm()
|
||||||
|
}, 500)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// #endregion
|
||||||
|
|
||||||
|
// #region Watchers
|
||||||
|
|
||||||
|
// Watch for row actions
|
||||||
|
watch(recId, () => {
|
||||||
|
switch (recAction.value) {
|
||||||
|
case ActionEvents.showEdit:
|
||||||
|
// TODO: Handle edit action
|
||||||
|
// isFormEntryDialogOpen.value = true
|
||||||
|
break
|
||||||
|
case ActionEvents.showConfirmDelete:
|
||||||
|
// Trigger confirmation modal open
|
||||||
|
isRecordConfirmationOpen.value = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// Handle confirmation result
|
||||||
|
function handleConfirmDelete(record: any, action: string) {
|
||||||
|
console.log('Confirmed action:', action, 'for record:', record)
|
||||||
|
handleDeleteRow(record)
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleCancelConfirmation() {
|
||||||
|
// Reset record state when cancelled
|
||||||
|
recId.value = 0
|
||||||
|
recAction.value = ''
|
||||||
|
recItem.value = null
|
||||||
|
}
|
||||||
|
// #endregion
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<Header v-model="searchInput" :prep="headerPrep" @search="handleSearch" />
|
||||||
|
<AppDivisonList :data="data" :pagination-meta="paginationMeta" @page-change="handlePageChange" />
|
||||||
|
|
||||||
|
<Dialog v-model:open="isFormEntryDialogOpen" title="Tambah Divisi" size="lg" prevent-outside>
|
||||||
|
|
||||||
|
<AppDivisionEntryFormPrev
|
||||||
|
:division="divisionConf" :division-tree="divisionTreeConfig" :schema="schema"
|
||||||
|
:initial-values="{ name: '', code: '', parentId: '' }" @submit="onSubmitForm" @cancel="onCancelForm"
|
||||||
|
/>
|
||||||
|
|
||||||
|
</Dialog>
|
||||||
|
|
||||||
|
<!-- Record Confirmation Modal -->
|
||||||
|
<RecordConfirmation
|
||||||
|
v-model:open="isRecordConfirmationOpen" action="delete" :record="recItem"
|
||||||
|
@confirm="handleConfirmDelete" @cancel="handleCancelConfirmation"
|
||||||
|
>
|
||||||
|
<template #default="{ record }">
|
||||||
|
<div class="text-sm">
|
||||||
|
<p><strong>ID:</strong> {{ record?.id }}</p>
|
||||||
|
<p v-if="record?.firstName"><strong>Nama:</strong> {{ record.firstName }}</p>
|
||||||
|
<p v-if="record?.code"><strong>Kode:</strong> {{ record.cellphone }}</p>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</RecordConfirmation>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
/* component style */
|
||||||
|
</style>
|
||||||
@@ -1,28 +1,39 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import type { HeaderPrep } from '~/components/pub/custom-ui/data/types'
|
// Components
|
||||||
import AppDivisionEntryForm from '~/components/app/divison/entry-form.vue'
|
|
||||||
import Dialog from '~/components/pub/base/modal/dialog.vue'
|
import Dialog from '~/components/pub/base/modal/dialog.vue'
|
||||||
|
import Header from '~/components/pub/custom-ui/nav-header/prep.vue'
|
||||||
import RecordConfirmation from '~/components/pub/custom-ui/confirmation/record-confirmation.vue'
|
import RecordConfirmation from '~/components/pub/custom-ui/confirmation/record-confirmation.vue'
|
||||||
import { ActionEvents } from '~/components/pub/custom-ui/data/types'
|
import AppDivisionList from '~/components/app/division/list.vue'
|
||||||
import Header from '~/components/pub/custom-ui/nav-header/header.vue'
|
import AppDivisionEntryForm from '~/components/app/division/entry-form.vue'
|
||||||
|
|
||||||
|
// Helpers
|
||||||
import { usePaginatedList } from '~/composables/usePaginatedList'
|
import { usePaginatedList } from '~/composables/usePaginatedList'
|
||||||
import { divisionConf, divisionTreeConfig, schema } from './entry'
|
import { toast } from '~/components/pub/ui/toast'
|
||||||
// #region State & Computed
|
|
||||||
// Dialog state
|
|
||||||
const isFormEntryDialogOpen = ref(false)
|
|
||||||
const isRecordConfirmationOpen = ref(false)
|
|
||||||
|
|
||||||
// Table action rowId provider
|
// Types
|
||||||
const recId = ref<number>(0)
|
import { ActionEvents, type HeaderPrep } from '~/components/pub/custom-ui/data/types'
|
||||||
const recAction = ref<string>('')
|
import { DivisionSchema, type DivisionFormData } from '~/schemas/division.schema'
|
||||||
const recItem = ref<any>(null)
|
|
||||||
|
|
||||||
async function fetchDivisionData(_params: any) {
|
// Handlers
|
||||||
const endpoint = '/api/v1/_dev/division/list'
|
import {
|
||||||
return await xfetch(endpoint)
|
recId,
|
||||||
}
|
recAction,
|
||||||
|
recItem,
|
||||||
|
isReadonly,
|
||||||
|
isProcessing,
|
||||||
|
isFormEntryDialogOpen,
|
||||||
|
isRecordConfirmationOpen,
|
||||||
|
handleActionSave,
|
||||||
|
handleActionEdit,
|
||||||
|
handleActionRemove,
|
||||||
|
handleCancelForm,
|
||||||
|
} from '~/handlers/division.handler'
|
||||||
|
|
||||||
|
// Services
|
||||||
|
import { getDivisions, getDivisionDetail } from '~/services/division.service'
|
||||||
|
|
||||||
|
const title = ref('')
|
||||||
|
|
||||||
// Menggunakan composable untuk pagination
|
|
||||||
const {
|
const {
|
||||||
data,
|
data,
|
||||||
isLoading,
|
isLoading,
|
||||||
@@ -32,7 +43,10 @@ const {
|
|||||||
handleSearch,
|
handleSearch,
|
||||||
fetchData: getDivisionList,
|
fetchData: getDivisionList,
|
||||||
} = usePaginatedList({
|
} = usePaginatedList({
|
||||||
fetchFn: fetchDivisionData,
|
fetchFn: async ({ page, search }) => {
|
||||||
|
const result = await getDivisions({ search, page })
|
||||||
|
return { success: result.success || false, body: result.body || {} }
|
||||||
|
},
|
||||||
entityName: 'division',
|
entityName: 'division',
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -44,21 +58,20 @@ const headerPrep: HeaderPrep = {
|
|||||||
minLength: 3,
|
minLength: 3,
|
||||||
debounceMs: 500,
|
debounceMs: 500,
|
||||||
showValidationFeedback: true,
|
showValidationFeedback: true,
|
||||||
onInput: (_val: string) => {
|
onInput: (value: string) => {
|
||||||
// Handle search input - this will be triggered by the header component
|
searchInput.value = value
|
||||||
},
|
|
||||||
onClick: () => {
|
|
||||||
// Handle search button click if needed
|
|
||||||
},
|
|
||||||
onClear: () => {
|
|
||||||
// Handle search clear
|
|
||||||
},
|
},
|
||||||
|
onClick: () => {},
|
||||||
|
onClear: () => {},
|
||||||
},
|
},
|
||||||
addNav: {
|
addNav: {
|
||||||
label: 'Tambah Divisi',
|
label: 'Tambah',
|
||||||
icon: 'i-lucide-send',
|
icon: 'i-lucide-plus',
|
||||||
onClick: () => {
|
onClick: () => {
|
||||||
|
recItem.value = null
|
||||||
|
recId.value = 0
|
||||||
isFormEntryDialogOpen.value = true
|
isFormEntryDialogOpen.value = true
|
||||||
|
isReadonly.value = false
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@@ -67,142 +80,83 @@ provide('rec_id', recId)
|
|||||||
provide('rec_action', recAction)
|
provide('rec_action', recAction)
|
||||||
provide('rec_item', recItem)
|
provide('rec_item', recItem)
|
||||||
provide('table_data_loader', isLoading)
|
provide('table_data_loader', isLoading)
|
||||||
// #endregion
|
|
||||||
|
|
||||||
// #region Functions
|
const getCurrentDivisionDetail = async (id: number | string) => {
|
||||||
|
const result = await getDivisionDetail(id)
|
||||||
async function handleDeleteRow(record: any) {
|
if (result.success) {
|
||||||
try {
|
const currentValue = result.body?.data || {}
|
||||||
// TODO : hit backend request untuk delete
|
recItem.value = currentValue
|
||||||
console.log('Deleting record:', record)
|
isFormEntryDialogOpen.value = true
|
||||||
|
|
||||||
// Simulate API call
|
|
||||||
// const response = await xfetch(`/api/v1/division/${record.id}`, {
|
|
||||||
// method: 'DELETE'
|
|
||||||
// })
|
|
||||||
|
|
||||||
// Refresh data setelah berhasil delete
|
|
||||||
await getDivisionList()
|
|
||||||
|
|
||||||
// TODO: Show success message
|
|
||||||
console.log('Record deleted successfully')
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error deleting record:', error)
|
|
||||||
// TODO: Show error message
|
|
||||||
} finally {
|
|
||||||
// Reset record state
|
|
||||||
recId.value = 0
|
|
||||||
recAction.value = ''
|
|
||||||
recItem.value = null
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// #endregion region
|
// Watch for row actions when recId or recAction changes
|
||||||
|
watch([recId, recAction], () => {
|
||||||
// #region Form event handlers
|
|
||||||
|
|
||||||
function onCancelForm(resetForm: () => void) {
|
|
||||||
isFormEntryDialogOpen.value = false
|
|
||||||
setTimeout(() => {
|
|
||||||
resetForm()
|
|
||||||
}, 500)
|
|
||||||
}
|
|
||||||
|
|
||||||
async function onSubmitForm(values: any, resetForm: () => void) {
|
|
||||||
let isSuccess = false
|
|
||||||
try {
|
|
||||||
// TODO: Implement form submission logic
|
|
||||||
console.log('Form submitted:', values)
|
|
||||||
|
|
||||||
// Simulate API call
|
|
||||||
// const response = await xfetch('/api/v1/division', {
|
|
||||||
// method: 'POST',
|
|
||||||
// body: JSON.stringify(values)
|
|
||||||
// })
|
|
||||||
|
|
||||||
// If successful, mark as success and close dialog
|
|
||||||
isFormEntryDialogOpen.value = false
|
|
||||||
isSuccess = true
|
|
||||||
|
|
||||||
// Refresh data after successful submission
|
|
||||||
await getDivisionList()
|
|
||||||
|
|
||||||
// TODO: Show success message
|
|
||||||
console.log('Division created successfully')
|
|
||||||
} catch (error: unknown) {
|
|
||||||
console.warn('Error submitting form:', error)
|
|
||||||
isSuccess = false
|
|
||||||
// Don't close dialog or reset form on error
|
|
||||||
// TODO: Show error message to user
|
|
||||||
} finally {
|
|
||||||
if (isSuccess) {
|
|
||||||
setTimeout(() => {
|
|
||||||
resetForm()
|
|
||||||
}, 500)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// #endregion
|
|
||||||
|
|
||||||
// #region Watchers
|
|
||||||
|
|
||||||
// Watch for row actions
|
|
||||||
watch(recId, () => {
|
|
||||||
switch (recAction.value) {
|
switch (recAction.value) {
|
||||||
|
case ActionEvents.showDetail:
|
||||||
|
getCurrentDivisionDetail(recId.value)
|
||||||
|
title.value = 'Detail Divisi'
|
||||||
|
isReadonly.value = true
|
||||||
|
break
|
||||||
case ActionEvents.showEdit:
|
case ActionEvents.showEdit:
|
||||||
// TODO: Handle edit action
|
getCurrentDivisionDetail(recId.value)
|
||||||
// isFormEntryDialogOpen.value = true
|
title.value = 'Edit Divisi'
|
||||||
|
isReadonly.value = false
|
||||||
break
|
break
|
||||||
case ActionEvents.showConfirmDelete:
|
case ActionEvents.showConfirmDelete:
|
||||||
// Trigger confirmation modal open
|
|
||||||
isRecordConfirmationOpen.value = true
|
isRecordConfirmationOpen.value = true
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
// Handle confirmation result
|
onMounted(async () => {
|
||||||
function handleConfirmDelete(record: any, action: string) {
|
await getDivisionList()
|
||||||
console.log('Confirmed action:', action, 'for record:', record)
|
})
|
||||||
handleDeleteRow(record)
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleCancelConfirmation() {
|
|
||||||
// Reset record state when cancelled
|
|
||||||
recId.value = 0
|
|
||||||
recAction.value = ''
|
|
||||||
recItem.value = null
|
|
||||||
}
|
|
||||||
// #endregion
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<Header v-model="searchInput" :prep="headerPrep" @search="handleSearch" />
|
<Header
|
||||||
<AppDivisonList :data="data" :pagination-meta="paginationMeta" @page-change="handlePageChange" />
|
v-model="searchInput"
|
||||||
|
:prep="headerPrep"
|
||||||
<Dialog v-model:open="isFormEntryDialogOpen" title="Tambah Divisi" size="lg" prevent-outside>
|
:ref-search-nav="headerPrep.refSearchNav"
|
||||||
|
@search="handleSearch"
|
||||||
|
class="mb-4 xl:mb-5"
|
||||||
|
/>
|
||||||
|
<AppDivisionList :data="data" :pagination-meta="paginationMeta" @page-change="handlePageChange" />
|
||||||
|
|
||||||
|
<Dialog v-model:open="isFormEntryDialogOpen" :title="!!recItem ? title : 'Tambah Divisi'" size="lg" prevent-outside>
|
||||||
<AppDivisionEntryForm
|
<AppDivisionEntryForm
|
||||||
:division="divisionConf" :division-tree="divisionTreeConfig" :schema="schema"
|
:schema="DivisionSchema"
|
||||||
:initial-values="{ name: '', code: '', parentId: '' }" @submit="onSubmitForm" @cancel="onCancelForm"
|
:values="recItem"
|
||||||
|
:is-loading="isProcessing"
|
||||||
|
:is-readonly="isReadonly"
|
||||||
|
@submit="
|
||||||
|
(values: DivisionFormData | Record<string, any>, resetForm: () => void) => {
|
||||||
|
if (recId > 0) {
|
||||||
|
handleActionEdit(recId, values, getDivisionList, resetForm, toast)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
handleActionSave(values, getDivisionList, resetForm, toast)
|
||||||
|
}
|
||||||
|
"
|
||||||
|
@cancel="handleCancelForm"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
</Dialog>
|
</Dialog>
|
||||||
|
|
||||||
<!-- Record Confirmation Modal -->
|
<!-- Record Confirmation Modal -->
|
||||||
<RecordConfirmation
|
<RecordConfirmation
|
||||||
v-model:open="isRecordConfirmationOpen" action="delete" :record="recItem"
|
v-model:open="isRecordConfirmationOpen"
|
||||||
@confirm="handleConfirmDelete" @cancel="handleCancelConfirmation"
|
action="delete"
|
||||||
>
|
:record="recItem"
|
||||||
|
@confirm="() => handleActionRemove(recId, getDivisionList, toast)"
|
||||||
|
@cancel=""
|
||||||
|
>
|
||||||
<template #default="{ record }">
|
<template #default="{ record }">
|
||||||
<div class="text-sm">
|
<div class="text-sm">
|
||||||
<p><strong>ID:</strong> {{ record?.id }}</p>
|
<p><strong>ID:</strong> {{ record?.id }}</p>
|
||||||
<p v-if="record?.firstName"><strong>Nama:</strong> {{ record.firstName }}</p>
|
<p v-if="record?.name"><strong>Nama:</strong> {{ record.name }}</p>
|
||||||
<p v-if="record?.code"><strong>Kode:</strong> {{ record.cellphone }}</p>
|
<p v-if="record?.code"><strong>Kode:</strong> {{ record.code }}</p>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</RecordConfirmation>
|
</RecordConfirmation>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
/* component style */
|
|
||||||
</style>
|
|
||||||
|
|||||||
@@ -2,8 +2,9 @@
|
|||||||
// Components
|
// Components
|
||||||
import Dialog from '~/components/pub/base/modal/dialog.vue'
|
import Dialog from '~/components/pub/base/modal/dialog.vue'
|
||||||
import Header from '~/components/pub/custom-ui/nav-header/prep.vue'
|
import Header from '~/components/pub/custom-ui/nav-header/prep.vue'
|
||||||
import AppEquipmentEntryForm from '~/components/app/equipment/entry-form.vue'
|
|
||||||
import RecordConfirmation from '~/components/pub/custom-ui/confirmation/record-confirmation.vue'
|
import RecordConfirmation from '~/components/pub/custom-ui/confirmation/record-confirmation.vue'
|
||||||
|
import AppEquipmentList from '~/components/app/equipment/list.vue'
|
||||||
|
import AppEquipmentEntryForm from '~/components/app/equipment/entry-form.vue'
|
||||||
|
|
||||||
// Helpers
|
// Helpers
|
||||||
import { usePaginatedList } from '~/composables/usePaginatedList'
|
import { usePaginatedList } from '~/composables/usePaginatedList'
|
||||||
@@ -60,15 +61,11 @@ const headerPrep: HeaderPrep = {
|
|||||||
minLength: 3,
|
minLength: 3,
|
||||||
debounceMs: 500,
|
debounceMs: 500,
|
||||||
showValidationFeedback: true,
|
showValidationFeedback: true,
|
||||||
onInput: (_val: string) => {
|
onInput: (value: string) => {
|
||||||
// Handle search input - this will be triggered by the header component
|
searchInput.value = value
|
||||||
},
|
|
||||||
onClick: () => {
|
|
||||||
// Handle search button click if needed
|
|
||||||
},
|
|
||||||
onClear: () => {
|
|
||||||
// Handle search clear
|
|
||||||
},
|
},
|
||||||
|
onClick: () => {},
|
||||||
|
onClear: () => {},
|
||||||
},
|
},
|
||||||
addNav: {
|
addNav: {
|
||||||
label: 'Tambah Perlengkapan',
|
label: 'Tambah Perlengkapan',
|
||||||
@@ -130,7 +127,13 @@ onMounted(async () => {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<Header v-model="searchInput" :prep="headerPrep" @search="handleSearch" class="mb-4 xl:mb-5" />
|
<Header
|
||||||
|
v-model="searchInput"
|
||||||
|
:prep="headerPrep"
|
||||||
|
@search="handleSearch"
|
||||||
|
:ref-search-nav="headerPrep.refSearchNav"
|
||||||
|
class="mb-4 xl:mb-5"
|
||||||
|
/>
|
||||||
<AppEquipmentList :data="data" :pagination-meta="paginationMeta" @page-change="handlePageChange" />
|
<AppEquipmentList :data="data" :pagination-meta="paginationMeta" @page-change="handlePageChange" />
|
||||||
|
|
||||||
<Dialog
|
<Dialog
|
||||||
|
|||||||
@@ -1,34 +0,0 @@
|
|||||||
<script setup lang="ts">
|
|
||||||
// types
|
|
||||||
import type { MaterialFormData } from '~/schemas/material.schema'
|
|
||||||
import { MaterialSchema } from '~/schemas/material.schema'
|
|
||||||
|
|
||||||
const isLoading = ref(false)
|
|
||||||
const uoms = [
|
|
||||||
{ value: 'uom-1', label: 'Satuan 1' },
|
|
||||||
{ value: 'uom-2', label: 'Satuan 2' },
|
|
||||||
{ value: 'uom-3', label: 'Satuan 3' },
|
|
||||||
]
|
|
||||||
const items = [
|
|
||||||
{ value: 'item-1', label: 'Item 1' },
|
|
||||||
{ value: 'item-2', label: 'Item 2' },
|
|
||||||
{ value: 'item-3', label: 'Item 3' },
|
|
||||||
]
|
|
||||||
|
|
||||||
function onBack() {
|
|
||||||
navigateTo('/tools-equipment-src/equipment')
|
|
||||||
}
|
|
||||||
|
|
||||||
async function onSubmit(data: MaterialFormData) {
|
|
||||||
console.log(data)
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<div class="mb-5 border-b border-b-slate-300 pb-3 text-lg xl:text-xl">
|
|
||||||
<Icon name="i-lucide-panel-bottom" class="me-2" />
|
|
||||||
<span class="font-semibold">Tambah</span> Perlengkapan (BMHP)
|
|
||||||
</div>
|
|
||||||
<AppMaterialEntryForm :is-loading="isLoading" :schema="MaterialSchema" :uoms="uoms" :items="items" @back="onBack"
|
|
||||||
@submit="onSubmit" />
|
|
||||||
</template>
|
|
||||||
@@ -1,65 +0,0 @@
|
|||||||
<script setup lang="ts">
|
|
||||||
import type { DataTableLoader } from '~/components/pub/base/data-table/type'
|
|
||||||
import type { HeaderPrep, RefSearchNav } from '~/components/pub/custom-ui/data/types'
|
|
||||||
import Header from '~/components/pub/custom-ui/nav-header/prep.vue'
|
|
||||||
|
|
||||||
const data = ref([])
|
|
||||||
|
|
||||||
const refSearchNav: RefSearchNav = {
|
|
||||||
onClick: () => {
|
|
||||||
// open filter modal
|
|
||||||
},
|
|
||||||
onInput: (_val: string) => {
|
|
||||||
// filter patient list
|
|
||||||
},
|
|
||||||
onClear: () => {
|
|
||||||
// clear url param
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
const isLoading = reactive<DataTableLoader>({
|
|
||||||
isTableLoading: false,
|
|
||||||
})
|
|
||||||
|
|
||||||
const recId = ref<number>(0)
|
|
||||||
const recAction = ref<string>('')
|
|
||||||
const recItem = ref<any>(null)
|
|
||||||
|
|
||||||
const headerPrep: HeaderPrep = {
|
|
||||||
title: 'Perlengkapan (BMHP)',
|
|
||||||
icon: 'i-lucide-panel-bottom',
|
|
||||||
addNav: {
|
|
||||||
label: 'Tambah',
|
|
||||||
onClick: () => navigateTo('/tools-equipment-src/equipment/add'),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
async function getMaterialList() {
|
|
||||||
isLoading.dataListLoading = true
|
|
||||||
|
|
||||||
// const resp = await xfetch('/api/v1/material')
|
|
||||||
// if (resp.success) {
|
|
||||||
// data.value = (resp.body as Record<string, any>).data
|
|
||||||
// }
|
|
||||||
|
|
||||||
isLoading.dataListLoading = false
|
|
||||||
}
|
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
getMaterialList()
|
|
||||||
})
|
|
||||||
|
|
||||||
provide('rec_id', recId)
|
|
||||||
provide('rec_action', recAction)
|
|
||||||
provide('rec_item', recItem)
|
|
||||||
provide('table_data_loader', isLoading)
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<div class="rounded-md border p-4">
|
|
||||||
<Header :prep="{ ...headerPrep }" :ref-search-nav="refSearchNav" />
|
|
||||||
<div class="rounded-md border p-4">
|
|
||||||
<AppMaterialList v-if="!isLoading.dataListLoading" :data="data" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
@@ -2,8 +2,9 @@
|
|||||||
// Components
|
// Components
|
||||||
import Dialog from '~/components/pub/base/modal/dialog.vue'
|
import Dialog from '~/components/pub/base/modal/dialog.vue'
|
||||||
import Header from '~/components/pub/custom-ui/nav-header/prep.vue'
|
import Header from '~/components/pub/custom-ui/nav-header/prep.vue'
|
||||||
import AppMedicineGroupEntryForm from '~/components/app/medicine-group/entry-form.vue'
|
|
||||||
import RecordConfirmation from '~/components/pub/custom-ui/confirmation/record-confirmation.vue'
|
import RecordConfirmation from '~/components/pub/custom-ui/confirmation/record-confirmation.vue'
|
||||||
|
import AppMedicineGroupList from '~/components/app/medicine-group/list.vue'
|
||||||
|
import AppMedicineGroupEntryForm from '~/components/app/medicine-group/entry-form.vue'
|
||||||
|
|
||||||
// Helpers
|
// Helpers
|
||||||
import { usePaginatedList } from '~/composables/usePaginatedList'
|
import { usePaginatedList } from '~/composables/usePaginatedList'
|
||||||
@@ -11,7 +12,7 @@ import { toast } from '~/components/pub/ui/toast'
|
|||||||
|
|
||||||
// Types
|
// Types
|
||||||
import { ActionEvents, type HeaderPrep } from '~/components/pub/custom-ui/data/types'
|
import { ActionEvents, type HeaderPrep } from '~/components/pub/custom-ui/data/types'
|
||||||
import { MedicineBaseSchema, type MedicineBaseFormData } from '~/schemas/medicine.schema'
|
import { BaseSchema, type BaseFormData } from '~/schemas/base.schema'
|
||||||
|
|
||||||
// Handlers
|
// Handlers
|
||||||
import {
|
import {
|
||||||
@@ -57,7 +58,9 @@ const headerPrep: HeaderPrep = {
|
|||||||
minLength: 3,
|
minLength: 3,
|
||||||
debounceMs: 500,
|
debounceMs: 500,
|
||||||
showValidationFeedback: true,
|
showValidationFeedback: true,
|
||||||
onInput: (_val: string) => {},
|
onInput: (value: string) => {
|
||||||
|
searchInput.value = value
|
||||||
|
},
|
||||||
onClick: () => {},
|
onClick: () => {},
|
||||||
onClear: () => {},
|
onClear: () => {},
|
||||||
},
|
},
|
||||||
@@ -112,7 +115,13 @@ onMounted(async () => {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<Header v-model="searchInput" :prep="headerPrep" @search="handleSearch" class="mb-4 xl:mb-5" />
|
<Header
|
||||||
|
v-model="searchInput"
|
||||||
|
:prep="headerPrep"
|
||||||
|
:ref-search-nav="headerPrep.refSearchNav"
|
||||||
|
@search="handleSearch"
|
||||||
|
class="mb-4 xl:mb-5"
|
||||||
|
/>
|
||||||
<AppMedicineGroupList :data="data" :pagination-meta="paginationMeta" @page-change="handlePageChange" />
|
<AppMedicineGroupList :data="data" :pagination-meta="paginationMeta" @page-change="handlePageChange" />
|
||||||
|
|
||||||
<Dialog
|
<Dialog
|
||||||
@@ -122,12 +131,12 @@ onMounted(async () => {
|
|||||||
prevent-outside
|
prevent-outside
|
||||||
>
|
>
|
||||||
<AppMedicineGroupEntryForm
|
<AppMedicineGroupEntryForm
|
||||||
:schema="MedicineBaseSchema"
|
:schema="BaseSchema"
|
||||||
:values="recItem"
|
:values="recItem"
|
||||||
:is-loading="isProcessing"
|
:is-loading="isProcessing"
|
||||||
:is-readonly="isReadonly"
|
:is-readonly="isReadonly"
|
||||||
@submit="
|
@submit="
|
||||||
(values: MedicineBaseFormData | Record<string, any>, resetForm: () => void) => {
|
(values: BaseFormData | Record<string, any>, resetForm: () => void) => {
|
||||||
if (recId > 0) {
|
if (recId > 0) {
|
||||||
handleActionEdit(recId, values, getMedicineGroupList, resetForm, toast)
|
handleActionEdit(recId, values, getMedicineGroupList, resetForm, toast)
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -2,8 +2,9 @@
|
|||||||
// Components
|
// Components
|
||||||
import Dialog from '~/components/pub/base/modal/dialog.vue'
|
import Dialog from '~/components/pub/base/modal/dialog.vue'
|
||||||
import Header from '~/components/pub/custom-ui/nav-header/prep.vue'
|
import Header from '~/components/pub/custom-ui/nav-header/prep.vue'
|
||||||
import AppMedicineMethodEntryForm from '~/components/app/medicine-method/entry-form.vue'
|
|
||||||
import RecordConfirmation from '~/components/pub/custom-ui/confirmation/record-confirmation.vue'
|
import RecordConfirmation from '~/components/pub/custom-ui/confirmation/record-confirmation.vue'
|
||||||
|
import AppMedicineMethodList from '~/components/app/medicine-method/list.vue'
|
||||||
|
import AppMedicineMethodEntryForm from '~/components/app/medicine-method/entry-form.vue'
|
||||||
|
|
||||||
// Helpers
|
// Helpers
|
||||||
import { usePaginatedList } from '~/composables/usePaginatedList'
|
import { usePaginatedList } from '~/composables/usePaginatedList'
|
||||||
@@ -11,7 +12,7 @@ import { toast } from '~/components/pub/ui/toast'
|
|||||||
|
|
||||||
// Types
|
// Types
|
||||||
import { ActionEvents, type HeaderPrep } from '~/components/pub/custom-ui/data/types'
|
import { ActionEvents, type HeaderPrep } from '~/components/pub/custom-ui/data/types'
|
||||||
import { MedicineBaseSchema, type MedicineBaseFormData } from '~/schemas/medicine.schema'
|
import { BaseSchema, type BaseFormData } from '~/schemas/base.schema'
|
||||||
|
|
||||||
// Handlers
|
// Handlers
|
||||||
import {
|
import {
|
||||||
@@ -57,7 +58,9 @@ const headerPrep: HeaderPrep = {
|
|||||||
minLength: 3,
|
minLength: 3,
|
||||||
debounceMs: 500,
|
debounceMs: 500,
|
||||||
showValidationFeedback: true,
|
showValidationFeedback: true,
|
||||||
onInput: (_val: string) => {},
|
onInput: (value: string) => {
|
||||||
|
searchInput.value = value
|
||||||
|
},
|
||||||
onClick: () => {},
|
onClick: () => {},
|
||||||
onClear: () => {},
|
onClear: () => {},
|
||||||
},
|
},
|
||||||
@@ -112,7 +115,13 @@ onMounted(async () => {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<Header v-model="searchInput" :prep="headerPrep" @search="handleSearch" class="mb-4 xl:mb-5" />
|
<Header
|
||||||
|
v-model="searchInput"
|
||||||
|
:prep="headerPrep"
|
||||||
|
:ref-search-nav="headerPrep.refSearchNav"
|
||||||
|
@search="handleSearch"
|
||||||
|
class="mb-4 xl:mb-5"
|
||||||
|
/>
|
||||||
<AppMedicineMethodList :data="data" :pagination-meta="paginationMeta" @page-change="handlePageChange" />
|
<AppMedicineMethodList :data="data" :pagination-meta="paginationMeta" @page-change="handlePageChange" />
|
||||||
|
|
||||||
<Dialog
|
<Dialog
|
||||||
@@ -122,12 +131,12 @@ onMounted(async () => {
|
|||||||
prevent-outside
|
prevent-outside
|
||||||
>
|
>
|
||||||
<AppMedicineMethodEntryForm
|
<AppMedicineMethodEntryForm
|
||||||
:schema="MedicineBaseSchema"
|
:schema="BaseSchema"
|
||||||
:values="recItem"
|
:values="recItem"
|
||||||
:is-loading="isProcessing"
|
:is-loading="isProcessing"
|
||||||
:is-readonly="isReadonly"
|
:is-readonly="isReadonly"
|
||||||
@submit="
|
@submit="
|
||||||
(values: MedicineBaseFormData | Record<string, any>, resetForm: () => void) => {
|
(values: BaseFormData | Record<string, any>, resetForm: () => void) => {
|
||||||
if (recId > 0) {
|
if (recId > 0) {
|
||||||
handleActionEdit(recId, values, getMedicineMethodList, resetForm, toast)
|
handleActionEdit(recId, values, getMedicineMethodList, resetForm, toast)
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -1,63 +1,206 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import type { DataTableLoader } from '~/components/pub/base/data-table/type'
|
// Components
|
||||||
import type { HeaderPrep, RefSearchNav } from '~/components/pub/custom-ui/data/types'
|
import Dialog from '~/components/pub/base/modal/dialog.vue'
|
||||||
import Header from '~/components/pub/custom-ui/nav-header/prep.vue'
|
import Header from '~/components/pub/custom-ui/nav-header/prep.vue'
|
||||||
|
import RecordConfirmation from '~/components/pub/custom-ui/confirmation/record-confirmation.vue'
|
||||||
|
import AppMedicineList from '~/components/app/medicine/list.vue'
|
||||||
|
import AppMedicineEntryForm from '~/components/app/medicine/entry-form.vue'
|
||||||
|
|
||||||
const data = ref([])
|
// Helpers
|
||||||
|
import { usePaginatedList } from '~/composables/usePaginatedList'
|
||||||
|
import { toast } from '~/components/pub/ui/toast'
|
||||||
|
|
||||||
const refSearchNav: RefSearchNav = {
|
// Types
|
||||||
onClick: () => {
|
import { ActionEvents, type HeaderPrep } from '~/components/pub/custom-ui/data/types'
|
||||||
// open filter modal
|
import { MedicineSchema, type MedicineFormData } from '~/schemas/medicine.schema'
|
||||||
},
|
import type { MedicineGroup } from '~/models/medicine-group'
|
||||||
onInput: (_val: string) => {
|
import type { MedicineMethod } from '~/models/medicine-method'
|
||||||
// filter patient list
|
import type { Uom } from '~/models/uom'
|
||||||
},
|
|
||||||
onClear: () => {
|
|
||||||
// clear url param
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
// Loading state management
|
// Handlers
|
||||||
const isLoading = reactive<DataTableLoader>({
|
import {
|
||||||
summary: false,
|
recId,
|
||||||
isTableLoading: false,
|
recAction,
|
||||||
|
recItem,
|
||||||
|
isReadonly,
|
||||||
|
isProcessing,
|
||||||
|
isFormEntryDialogOpen,
|
||||||
|
isRecordConfirmationOpen,
|
||||||
|
handleActionSave,
|
||||||
|
handleActionEdit,
|
||||||
|
handleActionRemove,
|
||||||
|
handleCancelForm,
|
||||||
|
} from '~/handlers/medicine.handler'
|
||||||
|
|
||||||
|
// Services
|
||||||
|
import { getMedicines, getMedicineDetail } from '~/services/medicine.service'
|
||||||
|
import { getMedicineGroups } from '~/services/medicine-group.service'
|
||||||
|
import { getMedicineMethods } from '~/services/medicine-method.service'
|
||||||
|
import { getUoms } from '~/services/uom.service'
|
||||||
|
|
||||||
|
const medicineGroups = ref<{ value: string; label: string }[]>([])
|
||||||
|
const medicineMethods = ref<{ value: string; label: string }[]>([])
|
||||||
|
const uoms = ref<{ value: string; label: string }[]>([])
|
||||||
|
const title = ref('')
|
||||||
|
|
||||||
|
const {
|
||||||
|
data,
|
||||||
|
isLoading,
|
||||||
|
paginationMeta,
|
||||||
|
searchInput,
|
||||||
|
handlePageChange,
|
||||||
|
handleSearch,
|
||||||
|
fetchData: getMedicineList,
|
||||||
|
} = usePaginatedList({
|
||||||
|
fetchFn: async ({ page, search }) => {
|
||||||
|
const result = await getMedicines({ search, page })
|
||||||
|
return { success: result.success || false, body: result.body || {} }
|
||||||
|
},
|
||||||
|
entityName: 'medicine',
|
||||||
})
|
})
|
||||||
|
|
||||||
const recId = ref<number>(0)
|
const headerPrep: HeaderPrep = {
|
||||||
const recAction = ref<string>('')
|
|
||||||
const recItem = ref<any>(null)
|
|
||||||
|
|
||||||
const hreaderPrep: HeaderPrep = {
|
|
||||||
title: 'Obat',
|
title: 'Obat',
|
||||||
icon: 'i-lucide-medicine-bottle',
|
icon: 'i-lucide-medicine-bottle',
|
||||||
|
refSearchNav: {
|
||||||
|
placeholder: 'Cari (min. 3 karakter)...',
|
||||||
|
minLength: 3,
|
||||||
|
debounceMs: 500,
|
||||||
|
showValidationFeedback: true,
|
||||||
|
onInput: (value: string) => {
|
||||||
|
searchInput.value = value
|
||||||
|
},
|
||||||
|
onClick: () => {},
|
||||||
|
onClear: () => {},
|
||||||
|
},
|
||||||
addNav: {
|
addNav: {
|
||||||
label: 'Tambah',
|
label: 'Tambah',
|
||||||
onClick: () => navigateTo('/tools-equipment-src/medicine/add'),
|
icon: 'i-lucide-plus',
|
||||||
|
onClick: () => {
|
||||||
|
recItem.value = null
|
||||||
|
recId.value = 0
|
||||||
|
isFormEntryDialogOpen.value = true
|
||||||
|
isReadonly.value = false
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getPatientList() {
|
|
||||||
isLoading.isTableLoading = true
|
|
||||||
const resp = await xfetch('/api/v1/medicine')
|
|
||||||
if (resp.success) {
|
|
||||||
data.value = (resp.body as Record<string, any>).data
|
|
||||||
}
|
|
||||||
isLoading.isTableLoading = false
|
|
||||||
}
|
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
getPatientList()
|
|
||||||
})
|
|
||||||
|
|
||||||
provide('rec_id', recId)
|
provide('rec_id', recId)
|
||||||
provide('rec_action', recAction)
|
provide('rec_action', recAction)
|
||||||
provide('rec_item', recItem)
|
provide('rec_item', recItem)
|
||||||
provide('table_data_loader', isLoading)
|
provide('table_data_loader', isLoading)
|
||||||
|
|
||||||
|
const getCurrentMedicineDetail = async (id: number | string) => {
|
||||||
|
const result = await getMedicineDetail(id)
|
||||||
|
if (result.success) {
|
||||||
|
const currentValue = result.body?.data || {}
|
||||||
|
recItem.value = currentValue
|
||||||
|
isFormEntryDialogOpen.value = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const getMedicineGroupList = async () => {
|
||||||
|
const result = await getMedicineGroups()
|
||||||
|
if (result.success) {
|
||||||
|
const currentMedicineGroups = result.body?.data || []
|
||||||
|
medicineGroups.value = currentMedicineGroups.map((medicineGroup: MedicineGroup) => ({
|
||||||
|
value: medicineGroup.code,
|
||||||
|
label: medicineGroup.name,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const getMedicineMethodList = async () => {
|
||||||
|
const result = await getMedicineMethods()
|
||||||
|
if (result.success) {
|
||||||
|
const currentMedicineMethods = result.body?.data || []
|
||||||
|
medicineMethods.value = currentMedicineMethods.map((medicineMethod: MedicineMethod) => ({
|
||||||
|
value: medicineMethod.code,
|
||||||
|
label: medicineMethod.name,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const getUomList = async () => {
|
||||||
|
const result = await getUoms()
|
||||||
|
if (result.success) {
|
||||||
|
const currentUoms = result.body?.data || []
|
||||||
|
uoms.value = currentUoms.map((uom: Uom) => ({ value: uom.code || uom.erp_id, label: uom.name }))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
watch([recId, recAction], () => {
|
||||||
|
switch (recAction.value) {
|
||||||
|
case ActionEvents.showDetail:
|
||||||
|
getCurrentMedicineDetail(recId.value)
|
||||||
|
title.value = 'Detail Obat'
|
||||||
|
isReadonly.value = true
|
||||||
|
break
|
||||||
|
case ActionEvents.showEdit:
|
||||||
|
getCurrentMedicineDetail(recId.value)
|
||||||
|
title.value = 'Edit Obat'
|
||||||
|
isReadonly.value = false
|
||||||
|
break
|
||||||
|
case ActionEvents.showConfirmDelete:
|
||||||
|
isRecordConfirmationOpen.value = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
onMounted(async () => {
|
||||||
|
await getMedicineGroupList()
|
||||||
|
await getMedicineMethodList()
|
||||||
|
await getUomList()
|
||||||
|
await getMedicineList()
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<Header :prep="{ ...hreaderPrep }" :ref-search-nav="refSearchNav" />
|
<Header
|
||||||
<div class="my-4 flex flex-1 flex-col gap-4 md:gap-8">
|
v-model="searchInput"
|
||||||
<AppMedicineList :data="data" />
|
:prep="headerPrep"
|
||||||
</div>
|
@search="handleSearch"
|
||||||
|
:ref-search-nav="headerPrep.refSearchNav"
|
||||||
|
class="mb-4 xl:mb-5"
|
||||||
|
/>
|
||||||
|
<AppMedicineList :data="data" :pagination-meta="paginationMeta" @page-change="handlePageChange" />
|
||||||
|
|
||||||
|
<Dialog v-model:open="isFormEntryDialogOpen" :title="!!recItem ? title : 'Tambah Obat'" size="lg" prevent-outside>
|
||||||
|
<AppMedicineEntryForm
|
||||||
|
:schema="MedicineSchema"
|
||||||
|
:values="recItem"
|
||||||
|
:medicineGroups="medicineGroups"
|
||||||
|
:medicineMethods="medicineMethods"
|
||||||
|
:uoms="uoms"
|
||||||
|
:is-loading="isProcessing"
|
||||||
|
:is-readonly="isReadonly"
|
||||||
|
@submit="
|
||||||
|
(values: MedicineFormData | Record<string, any>, resetForm: () => void) => {
|
||||||
|
if (recId > 0) {
|
||||||
|
handleActionEdit(recId, values, getMedicineList, resetForm, toast)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
handleActionSave(values, getMedicineList, resetForm, toast)
|
||||||
|
}
|
||||||
|
"
|
||||||
|
@cancel="handleCancelForm"
|
||||||
|
/>
|
||||||
|
</Dialog>
|
||||||
|
|
||||||
|
<!-- Record Confirmation Modal -->
|
||||||
|
<RecordConfirmation
|
||||||
|
v-model:open="isRecordConfirmationOpen"
|
||||||
|
action="delete"
|
||||||
|
:record="recItem"
|
||||||
|
@confirm="() => handleActionRemove(recId, getMedicineList, toast)"
|
||||||
|
@cancel=""
|
||||||
|
>
|
||||||
|
<template #default="{ record }">
|
||||||
|
<div class="text-sm">
|
||||||
|
<p><strong>ID:</strong> {{ record?.id }}</p>
|
||||||
|
<p v-if="record?.name"><strong>Nama:</strong> {{ record.name }}</p>
|
||||||
|
<p v-if="record?.code"><strong>Kode:</strong> {{ record.code }}</p>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</RecordConfirmation>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -0,0 +1,240 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import type { HeaderPrep } from '~/components/pub/custom-ui/data/types'
|
||||||
|
import AppSpecialistEntryForm from '~/components/app/specialist/entry-form.vue'
|
||||||
|
import Dialog from '~/components/pub/base/modal/dialog.vue'
|
||||||
|
import RecordConfirmation from '~/components/pub/custom-ui/confirmation/record-confirmation.vue'
|
||||||
|
import { ActionEvents } from '~/components/pub/custom-ui/data/types'
|
||||||
|
import Header from '~/components/pub/custom-ui/nav-header/header.vue'
|
||||||
|
import { usePaginatedList } from '~/composables/usePaginatedList'
|
||||||
|
import { createFilteredUnitConf, installationConf, schemaConf, unitConf } from './entry'
|
||||||
|
|
||||||
|
// #region State & Computed
|
||||||
|
// Dialog state
|
||||||
|
const isFormEntryDialogOpen = ref(false)
|
||||||
|
const isRecordConfirmationOpen = ref(false)
|
||||||
|
|
||||||
|
// Table action rowId provider
|
||||||
|
const recId = ref<number>(0)
|
||||||
|
const recAction = ref<string>('')
|
||||||
|
const recItem = ref<any>(null)
|
||||||
|
|
||||||
|
// State untuk tracking installation yang dipilih di form
|
||||||
|
const selectedInstallationId = ref<string>('')
|
||||||
|
|
||||||
|
// Computed untuk filtered unit berdasarkan installation yang dipilih
|
||||||
|
const filteredUnitConf = computed(() => {
|
||||||
|
if (!selectedInstallationId.value) {
|
||||||
|
return { ...unitConf, items: [] }
|
||||||
|
}
|
||||||
|
return createFilteredUnitConf(selectedInstallationId.value)
|
||||||
|
})
|
||||||
|
|
||||||
|
const {
|
||||||
|
data,
|
||||||
|
isLoading,
|
||||||
|
paginationMeta,
|
||||||
|
searchInput,
|
||||||
|
handlePageChange,
|
||||||
|
handleSearch,
|
||||||
|
fetchData: getSpecialistList,
|
||||||
|
} = usePaginatedList({
|
||||||
|
fetchFn: fetchSpecialistData,
|
||||||
|
entityName: 'specialist',
|
||||||
|
})
|
||||||
|
|
||||||
|
const headerPrep: HeaderPrep = {
|
||||||
|
title: 'Specialist',
|
||||||
|
icon: 'i-lucide-box',
|
||||||
|
refSearchNav: {
|
||||||
|
placeholder: 'Cari (min. 3 karakter)...',
|
||||||
|
minLength: 3,
|
||||||
|
debounceMs: 500,
|
||||||
|
showValidationFeedback: true,
|
||||||
|
onInput: (_val: string) => {
|
||||||
|
// Handle search input - this will be triggered by the header component
|
||||||
|
},
|
||||||
|
onClick: () => {
|
||||||
|
// Handle search button click if needed
|
||||||
|
},
|
||||||
|
onClear: () => {
|
||||||
|
// Handle search clear
|
||||||
|
},
|
||||||
|
},
|
||||||
|
addNav: {
|
||||||
|
label: 'Tambah Specialist',
|
||||||
|
icon: 'i-lucide-send',
|
||||||
|
onClick: () => {
|
||||||
|
isFormEntryDialogOpen.value = true
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
provide('rec_id', recId)
|
||||||
|
provide('rec_action', recAction)
|
||||||
|
provide('rec_item', recItem)
|
||||||
|
provide('table_data_loader', isLoading)
|
||||||
|
// #endregion
|
||||||
|
|
||||||
|
// #region Functions
|
||||||
|
async function fetchSpecialistData(params: any) {
|
||||||
|
const endpoint = transform('/api/v1/patient', params)
|
||||||
|
return await xfetch(endpoint)
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleDeleteRow(record: any) {
|
||||||
|
try {
|
||||||
|
// TODO : hit backend request untuk delete
|
||||||
|
console.log('Deleting record:', record)
|
||||||
|
|
||||||
|
// Simulate API call
|
||||||
|
// const response = await xfetch(`/api/v1/Installation/${record.id}`, {
|
||||||
|
// method: 'DELETE'
|
||||||
|
// })
|
||||||
|
|
||||||
|
// Refresh data setelah berhasil delete
|
||||||
|
await getSpecialistList()
|
||||||
|
|
||||||
|
// TODO: Show success message
|
||||||
|
console.log('Record deleted successfully')
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error deleting record:', error)
|
||||||
|
// TODO: Show error message
|
||||||
|
} finally {
|
||||||
|
// Reset record state
|
||||||
|
recId.value = 0
|
||||||
|
recAction.value = ''
|
||||||
|
recItem.value = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle confirmation result
|
||||||
|
function handleConfirmDelete(record: any, action: string) {
|
||||||
|
console.log('Confirmed action:', action, 'for record:', record)
|
||||||
|
handleDeleteRow(record)
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleCancelConfirmation() {
|
||||||
|
// Reset record state when cancelled
|
||||||
|
recId.value = 0
|
||||||
|
recAction.value = ''
|
||||||
|
recItem.value = null
|
||||||
|
}
|
||||||
|
|
||||||
|
// #endregion region
|
||||||
|
|
||||||
|
// #region Form event handlers
|
||||||
|
|
||||||
|
function onCancelForm(resetForm: () => void) {
|
||||||
|
isFormEntryDialogOpen.value = false
|
||||||
|
// Reset installation selection ketika form dibatal
|
||||||
|
selectedInstallationId.value = ''
|
||||||
|
setTimeout(() => {
|
||||||
|
resetForm()
|
||||||
|
}, 500)
|
||||||
|
}
|
||||||
|
|
||||||
|
function onInstallationChanged(installationId: string) {
|
||||||
|
// Update local state untuk trigger re-render filtered units
|
||||||
|
selectedInstallationId.value = installationId
|
||||||
|
|
||||||
|
// The filteredUnitConf computed will automatically update
|
||||||
|
// based on the new selectedInstallationId value
|
||||||
|
}
|
||||||
|
|
||||||
|
async function onSubmitForm(values: any, resetForm: () => void) {
|
||||||
|
let isSuccess = false
|
||||||
|
try {
|
||||||
|
// TODO: Implement form submission logic
|
||||||
|
console.log('Form submitted:', values)
|
||||||
|
|
||||||
|
// Simulate API call
|
||||||
|
// const response = await xfetch('/api/v1/Installation', {
|
||||||
|
// method: 'POST',
|
||||||
|
// body: JSON.stringify(values)
|
||||||
|
// })
|
||||||
|
|
||||||
|
// If successful, mark as success and close dialog
|
||||||
|
isFormEntryDialogOpen.value = false
|
||||||
|
isSuccess = true
|
||||||
|
|
||||||
|
// Reset installation selection ketika form berhasil submit
|
||||||
|
selectedInstallationId.value = ''
|
||||||
|
|
||||||
|
// Refresh data after successful submission
|
||||||
|
await getSpecialistList()
|
||||||
|
|
||||||
|
// TODO: Show success message
|
||||||
|
console.log('Installation created successfully')
|
||||||
|
} catch (error: unknown) {
|
||||||
|
console.warn('Error submitting form:', error)
|
||||||
|
isSuccess = false
|
||||||
|
// Don't close dialog or reset form on error
|
||||||
|
// TODO: Show error message to user
|
||||||
|
} finally {
|
||||||
|
if (isSuccess) {
|
||||||
|
setTimeout(() => {
|
||||||
|
resetForm()
|
||||||
|
}, 500)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// #endregion
|
||||||
|
|
||||||
|
// #region Watchers
|
||||||
|
|
||||||
|
// Watch for row actions
|
||||||
|
watch(recId, () => {
|
||||||
|
switch (recAction.value) {
|
||||||
|
case ActionEvents.showEdit:
|
||||||
|
// TODO: Handle edit action
|
||||||
|
// isFormEntryDialogOpen.value = true
|
||||||
|
break
|
||||||
|
case ActionEvents.showConfirmDelete:
|
||||||
|
// Trigger confirmation modal open
|
||||||
|
isRecordConfirmationOpen.value = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// Note: Installation change logic is now handled by the entry-form component
|
||||||
|
// through the onInstallationChanged event handler
|
||||||
|
// #endregion
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<Header v-model="searchInput" :prep="headerPrep" @search="handleSearch" />
|
||||||
|
<AppSpecialistList :data="data" :pagination-meta="paginationMeta" @page-change="handlePageChange" />
|
||||||
|
|
||||||
|
<Dialog v-model:open="isFormEntryDialogOpen" :title="headerPrep.addNav?.label!" size="lg" prevent-outside>
|
||||||
|
<AppSpecialistEntryFormPrev
|
||||||
|
:installation="installationConf"
|
||||||
|
:unit="filteredUnitConf"
|
||||||
|
:schema="schemaConf"
|
||||||
|
:disabled-unit="!selectedInstallationId"
|
||||||
|
:initial-values="{ name: '', code: '', installationId: '', unitId: '' }"
|
||||||
|
@submit="onSubmitForm"
|
||||||
|
@cancel="onCancelForm"
|
||||||
|
@installation-changed="onInstallationChanged"
|
||||||
|
/>
|
||||||
|
</Dialog>
|
||||||
|
|
||||||
|
<RecordConfirmation
|
||||||
|
v-model:open="isRecordConfirmationOpen"
|
||||||
|
action="delete"
|
||||||
|
:record="recItem"
|
||||||
|
@confirm="handleConfirmDelete"
|
||||||
|
@cancel="handleCancelConfirmation"
|
||||||
|
>
|
||||||
|
<template #default="{ record }">
|
||||||
|
<div class="text-sm">
|
||||||
|
<p><strong>ID:</strong> {{ record?.id }}</p>
|
||||||
|
<p v-if="record?.firstName"><strong>Nama:</strong> {{ record.firstName }}</p>
|
||||||
|
<p v-if="record?.code"><strong>Kode:</strong> {{ record.cellphone }}</p>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</RecordConfirmation>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
/* component style */
|
||||||
|
</style>
|
||||||
@@ -1,33 +1,41 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import type { HeaderPrep } from '~/components/pub/custom-ui/data/types'
|
// Components
|
||||||
import AppSpecialistEntryForm from '~/components/app/specialist/entry-form.vue'
|
|
||||||
import Dialog from '~/components/pub/base/modal/dialog.vue'
|
import Dialog from '~/components/pub/base/modal/dialog.vue'
|
||||||
|
import Header from '~/components/pub/custom-ui/nav-header/prep.vue'
|
||||||
import RecordConfirmation from '~/components/pub/custom-ui/confirmation/record-confirmation.vue'
|
import RecordConfirmation from '~/components/pub/custom-ui/confirmation/record-confirmation.vue'
|
||||||
import { ActionEvents } from '~/components/pub/custom-ui/data/types'
|
import AppSpecialistList from '~/components/app/specialist/list.vue'
|
||||||
import Header from '~/components/pub/custom-ui/nav-header/header.vue'
|
import AppSpecialistEntryForm from '~/components/app/specialist/entry-form.vue'
|
||||||
|
|
||||||
|
// Helpers
|
||||||
import { usePaginatedList } from '~/composables/usePaginatedList'
|
import { usePaginatedList } from '~/composables/usePaginatedList'
|
||||||
import { createFilteredUnitConf, installationConf, schemaConf, unitConf } from './entry'
|
import { toast } from '~/components/pub/ui/toast'
|
||||||
|
|
||||||
// #region State & Computed
|
// Types
|
||||||
// Dialog state
|
import { ActionEvents, type HeaderPrep } from '~/components/pub/custom-ui/data/types'
|
||||||
const isFormEntryDialogOpen = ref(false)
|
import { SpecialistSchema, type SpecialistFormData } from '~/schemas/specialist.schema'
|
||||||
const isRecordConfirmationOpen = ref(false)
|
import type { Unit } from '~/models/unit'
|
||||||
|
|
||||||
// Table action rowId provider
|
// Handlers
|
||||||
const recId = ref<number>(0)
|
import {
|
||||||
const recAction = ref<string>('')
|
recId,
|
||||||
const recItem = ref<any>(null)
|
recAction,
|
||||||
|
recItem,
|
||||||
|
isReadonly,
|
||||||
|
isProcessing,
|
||||||
|
isFormEntryDialogOpen,
|
||||||
|
isRecordConfirmationOpen,
|
||||||
|
handleActionSave,
|
||||||
|
handleActionEdit,
|
||||||
|
handleActionRemove,
|
||||||
|
handleCancelForm,
|
||||||
|
} from '~/handlers/specialist.handler'
|
||||||
|
|
||||||
// State untuk tracking installation yang dipilih di form
|
// Services
|
||||||
const selectedInstallationId = ref<string>('')
|
import { getSpecialists, getSpecialistDetail } from '~/services/specialist.service'
|
||||||
|
import { getUnits } from '~/services/unit.service'
|
||||||
|
|
||||||
// Computed untuk filtered unit berdasarkan installation yang dipilih
|
const units = ref<{ value: string; label: string }[]>([])
|
||||||
const filteredUnitConf = computed(() => {
|
const title = ref('')
|
||||||
if (!selectedInstallationId.value) {
|
|
||||||
return { ...unitConf, items: [] }
|
|
||||||
}
|
|
||||||
return createFilteredUnitConf(selectedInstallationId.value)
|
|
||||||
})
|
|
||||||
|
|
||||||
const {
|
const {
|
||||||
data,
|
data,
|
||||||
@@ -38,7 +46,10 @@ const {
|
|||||||
handleSearch,
|
handleSearch,
|
||||||
fetchData: getSpecialistList,
|
fetchData: getSpecialistList,
|
||||||
} = usePaginatedList({
|
} = usePaginatedList({
|
||||||
fetchFn: fetchSpecialistData,
|
fetchFn: async ({ page, search }) => {
|
||||||
|
const result = await getSpecialists({ search, page })
|
||||||
|
return { success: result.success || false, body: result.body || {} }
|
||||||
|
},
|
||||||
entityName: 'specialist',
|
entityName: 'specialist',
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -50,21 +61,20 @@ const headerPrep: HeaderPrep = {
|
|||||||
minLength: 3,
|
minLength: 3,
|
||||||
debounceMs: 500,
|
debounceMs: 500,
|
||||||
showValidationFeedback: true,
|
showValidationFeedback: true,
|
||||||
onInput: (_val: string) => {
|
onInput: (value: string) => {
|
||||||
// Handle search input - this will be triggered by the header component
|
searchInput.value = value
|
||||||
},
|
|
||||||
onClick: () => {
|
|
||||||
// Handle search button click if needed
|
|
||||||
},
|
|
||||||
onClear: () => {
|
|
||||||
// Handle search clear
|
|
||||||
},
|
},
|
||||||
|
onClick: () => {},
|
||||||
|
onClear: () => {},
|
||||||
},
|
},
|
||||||
addNav: {
|
addNav: {
|
||||||
label: 'Tambah Specialist',
|
label: 'Tambah',
|
||||||
icon: 'i-lucide-send',
|
icon: 'i-lucide-plus',
|
||||||
onClick: () => {
|
onClick: () => {
|
||||||
|
recItem.value = null
|
||||||
|
recId.value = 0
|
||||||
isFormEntryDialogOpen.value = true
|
isFormEntryDialogOpen.value = true
|
||||||
|
isReadonly.value = false
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@@ -73,168 +83,98 @@ provide('rec_id', recId)
|
|||||||
provide('rec_action', recAction)
|
provide('rec_action', recAction)
|
||||||
provide('rec_item', recItem)
|
provide('rec_item', recItem)
|
||||||
provide('table_data_loader', isLoading)
|
provide('table_data_loader', isLoading)
|
||||||
// #endregion
|
|
||||||
|
|
||||||
// #region Functions
|
const getCurrentSpecialistDetail = async (id: number | string) => {
|
||||||
async function fetchSpecialistData(params: any) {
|
const result = await getSpecialistDetail(id)
|
||||||
const endpoint = transform('/api/v1/patient', params)
|
if (result.success) {
|
||||||
return await xfetch(endpoint)
|
const currentValue = result.body?.data || {}
|
||||||
}
|
recItem.value = currentValue
|
||||||
|
isFormEntryDialogOpen.value = true
|
||||||
async function handleDeleteRow(record: any) {
|
|
||||||
try {
|
|
||||||
// TODO : hit backend request untuk delete
|
|
||||||
console.log('Deleting record:', record)
|
|
||||||
|
|
||||||
// Simulate API call
|
|
||||||
// const response = await xfetch(`/api/v1/Installation/${record.id}`, {
|
|
||||||
// method: 'DELETE'
|
|
||||||
// })
|
|
||||||
|
|
||||||
// Refresh data setelah berhasil delete
|
|
||||||
await getSpecialistList()
|
|
||||||
|
|
||||||
// TODO: Show success message
|
|
||||||
console.log('Record deleted successfully')
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error deleting record:', error)
|
|
||||||
// TODO: Show error message
|
|
||||||
} finally {
|
|
||||||
// Reset record state
|
|
||||||
recId.value = 0
|
|
||||||
recAction.value = ''
|
|
||||||
recItem.value = null
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle confirmation result
|
const getUnitList = async () => {
|
||||||
function handleConfirmDelete(record: any, action: string) {
|
const result = await getUnits()
|
||||||
console.log('Confirmed action:', action, 'for record:', record)
|
if (result.success) {
|
||||||
handleDeleteRow(record)
|
const currentUnits = result.body?.data || []
|
||||||
}
|
units.value = currentUnits.map((item: Unit) => ({ value: item.id ? Number(item.id) : item.code, label: item.name }))
|
||||||
|
|
||||||
function handleCancelConfirmation() {
|
|
||||||
// Reset record state when cancelled
|
|
||||||
recId.value = 0
|
|
||||||
recAction.value = ''
|
|
||||||
recItem.value = null
|
|
||||||
}
|
|
||||||
|
|
||||||
// #endregion region
|
|
||||||
|
|
||||||
// #region Form event handlers
|
|
||||||
|
|
||||||
function onCancelForm(resetForm: () => void) {
|
|
||||||
isFormEntryDialogOpen.value = false
|
|
||||||
// Reset installation selection ketika form dibatal
|
|
||||||
selectedInstallationId.value = ''
|
|
||||||
setTimeout(() => {
|
|
||||||
resetForm()
|
|
||||||
}, 500)
|
|
||||||
}
|
|
||||||
|
|
||||||
function onInstallationChanged(installationId: string) {
|
|
||||||
// Update local state untuk trigger re-render filtered units
|
|
||||||
selectedInstallationId.value = installationId
|
|
||||||
|
|
||||||
// The filteredUnitConf computed will automatically update
|
|
||||||
// based on the new selectedInstallationId value
|
|
||||||
}
|
|
||||||
|
|
||||||
async function onSubmitForm(values: any, resetForm: () => void) {
|
|
||||||
let isSuccess = false
|
|
||||||
try {
|
|
||||||
// TODO: Implement form submission logic
|
|
||||||
console.log('Form submitted:', values)
|
|
||||||
|
|
||||||
// Simulate API call
|
|
||||||
// const response = await xfetch('/api/v1/Installation', {
|
|
||||||
// method: 'POST',
|
|
||||||
// body: JSON.stringify(values)
|
|
||||||
// })
|
|
||||||
|
|
||||||
// If successful, mark as success and close dialog
|
|
||||||
isFormEntryDialogOpen.value = false
|
|
||||||
isSuccess = true
|
|
||||||
|
|
||||||
// Reset installation selection ketika form berhasil submit
|
|
||||||
selectedInstallationId.value = ''
|
|
||||||
|
|
||||||
// Refresh data after successful submission
|
|
||||||
await getSpecialistList()
|
|
||||||
|
|
||||||
// TODO: Show success message
|
|
||||||
console.log('Installation created successfully')
|
|
||||||
} catch (error: unknown) {
|
|
||||||
console.warn('Error submitting form:', error)
|
|
||||||
isSuccess = false
|
|
||||||
// Don't close dialog or reset form on error
|
|
||||||
// TODO: Show error message to user
|
|
||||||
} finally {
|
|
||||||
if (isSuccess) {
|
|
||||||
setTimeout(() => {
|
|
||||||
resetForm()
|
|
||||||
}, 500)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// #endregion
|
|
||||||
|
|
||||||
// #region Watchers
|
// Watch for row actions when recId or recAction changes
|
||||||
|
watch([recId, recAction], () => {
|
||||||
// Watch for row actions
|
|
||||||
watch(recId, () => {
|
|
||||||
switch (recAction.value) {
|
switch (recAction.value) {
|
||||||
|
case ActionEvents.showDetail:
|
||||||
|
getCurrentSpecialistDetail(recId.value)
|
||||||
|
title.value = 'Detail Spesialis'
|
||||||
|
isReadonly.value = true
|
||||||
|
break
|
||||||
case ActionEvents.showEdit:
|
case ActionEvents.showEdit:
|
||||||
// TODO: Handle edit action
|
getCurrentSpecialistDetail(recId.value)
|
||||||
// isFormEntryDialogOpen.value = true
|
title.value = 'Edit Spesialis'
|
||||||
|
isReadonly.value = false
|
||||||
break
|
break
|
||||||
case ActionEvents.showConfirmDelete:
|
case ActionEvents.showConfirmDelete:
|
||||||
// Trigger confirmation modal open
|
|
||||||
isRecordConfirmationOpen.value = true
|
isRecordConfirmationOpen.value = true
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
// Note: Installation change logic is now handled by the entry-form component
|
onMounted(async () => {
|
||||||
// through the onInstallationChanged event handler
|
await getUnitList()
|
||||||
// #endregion
|
await getSpecialistList()
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<Header v-model="searchInput" :prep="headerPrep" @search="handleSearch" />
|
<Header
|
||||||
|
v-model="searchInput"
|
||||||
|
:prep="headerPrep"
|
||||||
|
:ref-search-nav="headerPrep.refSearchNav"
|
||||||
|
@search="handleSearch"
|
||||||
|
class="mb-4 xl:mb-5"
|
||||||
|
/>
|
||||||
<AppSpecialistList :data="data" :pagination-meta="paginationMeta" @page-change="handlePageChange" />
|
<AppSpecialistList :data="data" :pagination-meta="paginationMeta" @page-change="handlePageChange" />
|
||||||
|
|
||||||
<Dialog v-model:open="isFormEntryDialogOpen" :title="headerPrep.addNav?.label!" size="lg" prevent-outside>
|
<Dialog
|
||||||
|
v-model:open="isFormEntryDialogOpen"
|
||||||
|
:title="!!recItem ? title : 'Tambah Spesialis'"
|
||||||
|
size="lg"
|
||||||
|
prevent-outside
|
||||||
|
>
|
||||||
<AppSpecialistEntryForm
|
<AppSpecialistEntryForm
|
||||||
:installation="installationConf"
|
:schema="SpecialistSchema"
|
||||||
:unit="filteredUnitConf"
|
:units="units"
|
||||||
:schema="schemaConf"
|
:values="recItem"
|
||||||
:disabled-unit="!selectedInstallationId"
|
:is-loading="isProcessing"
|
||||||
:initial-values="{ name: '', code: '', installationId: '', unitId: '' }"
|
:is-readonly="isReadonly"
|
||||||
@submit="onSubmitForm"
|
@submit="
|
||||||
@cancel="onCancelForm"
|
(values: SpecialistFormData | Record<string, any>, resetForm: () => void) => {
|
||||||
@installation-changed="onInstallationChanged"
|
if (recId > 0) {
|
||||||
|
handleActionEdit(recId, values, getSpecialistList, resetForm, toast)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
handleActionSave(values, getSpecialistList, resetForm, toast)
|
||||||
|
}
|
||||||
|
"
|
||||||
|
@cancel="handleCancelForm"
|
||||||
/>
|
/>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
|
|
||||||
|
<!-- Record Confirmation Modal -->
|
||||||
<RecordConfirmation
|
<RecordConfirmation
|
||||||
v-model:open="isRecordConfirmationOpen"
|
v-model:open="isRecordConfirmationOpen"
|
||||||
action="delete"
|
action="delete"
|
||||||
:record="recItem"
|
:record="recItem"
|
||||||
@confirm="handleConfirmDelete"
|
@confirm="() => handleActionRemove(recId, getSpecialistList, toast)"
|
||||||
@cancel="handleCancelConfirmation"
|
@cancel=""
|
||||||
>
|
>
|
||||||
<template #default="{ record }">
|
<template #default="{ record }">
|
||||||
<div class="text-sm">
|
<div class="text-sm">
|
||||||
<p><strong>ID:</strong> {{ record?.id }}</p>
|
<p><strong>ID:</strong> {{ record?.id }}</p>
|
||||||
<p v-if="record?.firstName"><strong>Nama:</strong> {{ record.firstName }}</p>
|
<p v-if="record?.name"><strong>Nama:</strong> {{ record.name }}</p>
|
||||||
<p v-if="record?.code"><strong>Kode:</strong> {{ record.cellphone }}</p>
|
<p v-if="record?.code"><strong>Kode:</strong> {{ record.code }}</p>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</RecordConfirmation>
|
</RecordConfirmation>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
/* component style */
|
|
||||||
</style>
|
|
||||||
|
|||||||
@@ -0,0 +1,239 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import type { HeaderPrep } from '~/components/pub/custom-ui/data/types'
|
||||||
|
import AppSubspecialistEntryForm from '~/components/app/subspecialist/entry-form.vue'
|
||||||
|
import Dialog from '~/components/pub/base/modal/dialog.vue'
|
||||||
|
import RecordConfirmation from '~/components/pub/custom-ui/confirmation/record-confirmation.vue'
|
||||||
|
import { ActionEvents } from '~/components/pub/custom-ui/data/types'
|
||||||
|
import Header from '~/components/pub/custom-ui/nav-header/header.vue'
|
||||||
|
import { usePaginatedList } from '~/composables/usePaginatedList'
|
||||||
|
import { createFilteredUnitConf, installationConf, schemaConf, specialistConf, unitConf } from './entry'
|
||||||
|
|
||||||
|
// #region State & Computed
|
||||||
|
// Dialog state
|
||||||
|
const isFormEntryDialogOpen = ref(false)
|
||||||
|
const isRecordConfirmationOpen = ref(false)
|
||||||
|
|
||||||
|
// Table action rowId provider
|
||||||
|
const recId = ref<number>(0)
|
||||||
|
const recAction = ref<string>('')
|
||||||
|
const recItem = ref<any>(null)
|
||||||
|
|
||||||
|
// State untuk tracking installation, unit, dan specialist yang dipilih di form
|
||||||
|
const selectedInstallationId = ref<string>('')
|
||||||
|
const selectedUnitId = ref<string>('')
|
||||||
|
|
||||||
|
// Computed untuk filtered unit berdasarkan installation yang dipilih
|
||||||
|
const filteredUnitConf = computed(() => {
|
||||||
|
if (!selectedInstallationId.value) {
|
||||||
|
return { ...unitConf, items: [] }
|
||||||
|
}
|
||||||
|
return createFilteredUnitConf(selectedInstallationId.value)
|
||||||
|
})
|
||||||
|
|
||||||
|
const {
|
||||||
|
data,
|
||||||
|
isLoading,
|
||||||
|
paginationMeta,
|
||||||
|
searchInput,
|
||||||
|
handlePageChange,
|
||||||
|
handleSearch,
|
||||||
|
fetchData: getSubSpecialistList,
|
||||||
|
} = usePaginatedList({
|
||||||
|
fetchFn: fetchSubSpecialistData,
|
||||||
|
entityName: 'subspecialist',
|
||||||
|
})
|
||||||
|
|
||||||
|
const headerPrep: HeaderPrep = {
|
||||||
|
title: 'Sub Specialist',
|
||||||
|
icon: 'i-lucide-box',
|
||||||
|
refSearchNav: {
|
||||||
|
placeholder: 'Cari (min. 3 karakter)...',
|
||||||
|
minLength: 3,
|
||||||
|
debounceMs: 500,
|
||||||
|
showValidationFeedback: true,
|
||||||
|
onInput: (_val: string) => {
|
||||||
|
// Handle search input - this will be triggered by the header component
|
||||||
|
},
|
||||||
|
onClick: () => {
|
||||||
|
// Handle search button click if needed
|
||||||
|
},
|
||||||
|
onClear: () => {
|
||||||
|
// Handle search clear
|
||||||
|
},
|
||||||
|
},
|
||||||
|
addNav: {
|
||||||
|
label: 'Tambah Sub Specialist',
|
||||||
|
icon: 'i-lucide-send',
|
||||||
|
onClick: () => {
|
||||||
|
isFormEntryDialogOpen.value = true
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
provide('rec_id', recId)
|
||||||
|
provide('rec_action', recAction)
|
||||||
|
provide('rec_item', recItem)
|
||||||
|
provide('table_data_loader', isLoading)
|
||||||
|
// #endregion
|
||||||
|
|
||||||
|
// #region Functions
|
||||||
|
async function fetchSubSpecialistData(params: any) {
|
||||||
|
const endpoint = transform('/api/v1/patient', params)
|
||||||
|
return await xfetch(endpoint)
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleDeleteRow(record: any) {
|
||||||
|
try {
|
||||||
|
// TODO : hit backend request untuk delete
|
||||||
|
console.log('Deleting record:', record)
|
||||||
|
|
||||||
|
// Simulate API call
|
||||||
|
// const response = await xfetch(`/api/v1/subspecialist/${record.id}`, {
|
||||||
|
// method: 'DELETE'
|
||||||
|
// })
|
||||||
|
|
||||||
|
// Refresh data setelah berhasil delete
|
||||||
|
await getSubSpecialistList()
|
||||||
|
|
||||||
|
// TODO: Show success message
|
||||||
|
console.log('Record deleted successfully')
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error deleting record:', error)
|
||||||
|
// TODO: Show error message
|
||||||
|
} finally {
|
||||||
|
// Reset record state
|
||||||
|
recId.value = 0
|
||||||
|
recAction.value = ''
|
||||||
|
recItem.value = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle confirmation result
|
||||||
|
function handleConfirmDelete(record: any, action: string) {
|
||||||
|
console.log('Confirmed action:', action, 'for record:', record)
|
||||||
|
handleDeleteRow(record)
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleCancelConfirmation() {
|
||||||
|
// Reset record state when cancelled
|
||||||
|
recId.value = 0
|
||||||
|
recAction.value = ''
|
||||||
|
recItem.value = null
|
||||||
|
}
|
||||||
|
|
||||||
|
// #endregion region
|
||||||
|
|
||||||
|
// #region Form event handlers
|
||||||
|
|
||||||
|
function onCancelForm(resetForm: () => void) {
|
||||||
|
isFormEntryDialogOpen.value = false
|
||||||
|
selectedInstallationId.value = ''
|
||||||
|
selectedUnitId.value = ''
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
resetForm()
|
||||||
|
}, 500)
|
||||||
|
}
|
||||||
|
|
||||||
|
function onInstallationChanged(installationId: string) {
|
||||||
|
selectedInstallationId.value = installationId
|
||||||
|
}
|
||||||
|
|
||||||
|
function onUnitChanged(unitId: string) {
|
||||||
|
selectedUnitId.value = unitId
|
||||||
|
}
|
||||||
|
|
||||||
|
async function onSubmitForm(values: any, resetForm: () => void) {
|
||||||
|
let isSuccess = false
|
||||||
|
try {
|
||||||
|
// TODO: Implement form submission logic
|
||||||
|
console.log('Form submitted:', values)
|
||||||
|
|
||||||
|
// Simulate API call
|
||||||
|
// const response = await xfetch('/api/v1/subspecialist', {
|
||||||
|
// method: 'POST',
|
||||||
|
// body: JSON.stringify(values)
|
||||||
|
// })
|
||||||
|
|
||||||
|
// If successful, mark as success and close dialog
|
||||||
|
isFormEntryDialogOpen.value = false
|
||||||
|
isSuccess = true
|
||||||
|
|
||||||
|
// Reset selection ketika form berhasil submit
|
||||||
|
selectedInstallationId.value = ''
|
||||||
|
selectedUnitId.value = ''
|
||||||
|
|
||||||
|
// Refresh data after successful submission
|
||||||
|
await getSubSpecialistList()
|
||||||
|
|
||||||
|
// TODO: Show success message
|
||||||
|
console.log('Sub Specialist created successfully')
|
||||||
|
} catch (error: unknown) {
|
||||||
|
console.warn('Error submitting form:', error)
|
||||||
|
isSuccess = false
|
||||||
|
// Don't close dialog or reset form on error
|
||||||
|
// TODO: Show error message to user
|
||||||
|
} finally {
|
||||||
|
if (isSuccess) {
|
||||||
|
setTimeout(() => {
|
||||||
|
resetForm()
|
||||||
|
}, 500)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// #endregion
|
||||||
|
|
||||||
|
// #region Watchers
|
||||||
|
|
||||||
|
// Watch for row actions
|
||||||
|
watch(recId, () => {
|
||||||
|
switch (recAction.value) {
|
||||||
|
case ActionEvents.showEdit:
|
||||||
|
// TODO: Handle edit action
|
||||||
|
// isFormEntryDialogOpen.value = true
|
||||||
|
break
|
||||||
|
case ActionEvents.showConfirmDelete:
|
||||||
|
// Trigger confirmation modal open
|
||||||
|
isRecordConfirmationOpen.value = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// #endregion
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<Header v-model="searchInput" :prep="headerPrep" @search="handleSearch" />
|
||||||
|
<AppSubspecialistList :data="data" :pagination-meta="paginationMeta" @page-change="handlePageChange" />
|
||||||
|
|
||||||
|
<Dialog v-model:open="isFormEntryDialogOpen" :title="headerPrep.addNav?.label!" size="lg" prevent-outside>
|
||||||
|
<AppSubspecialistEntryFormPrev
|
||||||
|
:installation="installationConf"
|
||||||
|
:unit="filteredUnitConf"
|
||||||
|
:specialist="specialistConf"
|
||||||
|
:schema="schemaConf"
|
||||||
|
:initial-values="{ name: '', code: '', installationId: '', unitId: '', specialistId: '' }"
|
||||||
|
@submit="onSubmitForm"
|
||||||
|
@cancel="onCancelForm"
|
||||||
|
@installation-changed="onInstallationChanged"
|
||||||
|
@unit-changed="onUnitChanged"
|
||||||
|
/>
|
||||||
|
</Dialog>
|
||||||
|
|
||||||
|
<RecordConfirmation
|
||||||
|
v-model:open="isRecordConfirmationOpen"
|
||||||
|
action="delete"
|
||||||
|
:record="recItem"
|
||||||
|
@confirm="handleConfirmDelete"
|
||||||
|
@cancel="handleCancelConfirmation"
|
||||||
|
>
|
||||||
|
<template #default="{ record }">
|
||||||
|
<div class="text-sm">
|
||||||
|
<p><strong>ID:</strong> {{ record?.id }}</p>
|
||||||
|
<p v-if="record?.name"><strong>Nama:</strong> {{ record.name }}</p>
|
||||||
|
<p v-if="record?.code"><strong>Kode:</strong> {{ record.code }}</p>
|
||||||
|
<p v-if="record?.specialist"><strong>Specialist:</strong> {{ record.specialist?.name }}</p>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</RecordConfirmation>
|
||||||
|
</template>
|
||||||
@@ -1,34 +1,41 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import type { HeaderPrep } from '~/components/pub/custom-ui/data/types'
|
// Components
|
||||||
import AppSubspecialistEntryForm from '~/components/app/subspecialist/entry-form.vue'
|
|
||||||
import Dialog from '~/components/pub/base/modal/dialog.vue'
|
import Dialog from '~/components/pub/base/modal/dialog.vue'
|
||||||
|
import Header from '~/components/pub/custom-ui/nav-header/prep.vue'
|
||||||
import RecordConfirmation from '~/components/pub/custom-ui/confirmation/record-confirmation.vue'
|
import RecordConfirmation from '~/components/pub/custom-ui/confirmation/record-confirmation.vue'
|
||||||
import { ActionEvents } from '~/components/pub/custom-ui/data/types'
|
import AppSubSpecialistList from '~/components/app/subspecialist/list.vue'
|
||||||
import Header from '~/components/pub/custom-ui/nav-header/header.vue'
|
import AppSubSpecialistEntryForm from '~/components/app/subspecialist/entry-form.vue'
|
||||||
|
|
||||||
|
// Helpers
|
||||||
import { usePaginatedList } from '~/composables/usePaginatedList'
|
import { usePaginatedList } from '~/composables/usePaginatedList'
|
||||||
import { createFilteredUnitConf, installationConf, schemaConf, specialistConf, unitConf } from './entry'
|
import { toast } from '~/components/pub/ui/toast'
|
||||||
|
|
||||||
// #region State & Computed
|
// Types
|
||||||
// Dialog state
|
import { ActionEvents, type HeaderPrep } from '~/components/pub/custom-ui/data/types'
|
||||||
const isFormEntryDialogOpen = ref(false)
|
import { SubspecialistSchema, type SubspecialistFormData } from '~/schemas/subspecialist.schema'
|
||||||
const isRecordConfirmationOpen = ref(false)
|
import type { Specialist } from '~/models/specialist'
|
||||||
|
|
||||||
// Table action rowId provider
|
// Handlers
|
||||||
const recId = ref<number>(0)
|
import {
|
||||||
const recAction = ref<string>('')
|
recId,
|
||||||
const recItem = ref<any>(null)
|
recAction,
|
||||||
|
recItem,
|
||||||
|
isReadonly,
|
||||||
|
isProcessing,
|
||||||
|
isFormEntryDialogOpen,
|
||||||
|
isRecordConfirmationOpen,
|
||||||
|
handleActionSave,
|
||||||
|
handleActionEdit,
|
||||||
|
handleActionRemove,
|
||||||
|
handleCancelForm,
|
||||||
|
} from '~/handlers/subspecialist.handler'
|
||||||
|
|
||||||
// State untuk tracking installation, unit, dan specialist yang dipilih di form
|
// Services
|
||||||
const selectedInstallationId = ref<string>('')
|
import { getSubspecialists, getSubspecialistDetail } from '~/services/subspecialist.service'
|
||||||
const selectedUnitId = ref<string>('')
|
import { getSpecialists } from '~/services/specialist.service'
|
||||||
|
|
||||||
// Computed untuk filtered unit berdasarkan installation yang dipilih
|
const specialists = ref<{ value: string; label: string }[]>([])
|
||||||
const filteredUnitConf = computed(() => {
|
const title = ref('')
|
||||||
if (!selectedInstallationId.value) {
|
|
||||||
return { ...unitConf, items: [] }
|
|
||||||
}
|
|
||||||
return createFilteredUnitConf(selectedInstallationId.value)
|
|
||||||
})
|
|
||||||
|
|
||||||
const {
|
const {
|
||||||
data,
|
data,
|
||||||
@@ -39,33 +46,35 @@ const {
|
|||||||
handleSearch,
|
handleSearch,
|
||||||
fetchData: getSubSpecialistList,
|
fetchData: getSubSpecialistList,
|
||||||
} = usePaginatedList({
|
} = usePaginatedList({
|
||||||
fetchFn: fetchSubSpecialistData,
|
fetchFn: async ({ page, search }) => {
|
||||||
|
const result = await getSubspecialists({ search, page })
|
||||||
|
return { success: result.success || false, body: result.body || {} }
|
||||||
|
},
|
||||||
entityName: 'subspecialist',
|
entityName: 'subspecialist',
|
||||||
})
|
})
|
||||||
|
|
||||||
const headerPrep: HeaderPrep = {
|
const headerPrep: HeaderPrep = {
|
||||||
title: 'Sub Specialist',
|
title: 'SubSpecialist',
|
||||||
icon: 'i-lucide-box',
|
icon: 'i-lucide-box',
|
||||||
refSearchNav: {
|
refSearchNav: {
|
||||||
placeholder: 'Cari (min. 3 karakter)...',
|
placeholder: 'Cari (min. 3 karakter)...',
|
||||||
minLength: 3,
|
minLength: 3,
|
||||||
debounceMs: 500,
|
debounceMs: 500,
|
||||||
showValidationFeedback: true,
|
showValidationFeedback: true,
|
||||||
onInput: (_val: string) => {
|
onInput: (value: string) => {
|
||||||
// Handle search input - this will be triggered by the header component
|
searchInput.value = value
|
||||||
},
|
|
||||||
onClick: () => {
|
|
||||||
// Handle search button click if needed
|
|
||||||
},
|
|
||||||
onClear: () => {
|
|
||||||
// Handle search clear
|
|
||||||
},
|
},
|
||||||
|
onClick: () => {},
|
||||||
|
onClear: () => {},
|
||||||
},
|
},
|
||||||
addNav: {
|
addNav: {
|
||||||
label: 'Tambah Sub Specialist',
|
label: 'Tambah',
|
||||||
icon: 'i-lucide-send',
|
icon: 'i-lucide-plus',
|
||||||
onClick: () => {
|
onClick: () => {
|
||||||
|
recItem.value = null
|
||||||
|
recId.value = 0
|
||||||
isFormEntryDialogOpen.value = true
|
isFormEntryDialogOpen.value = true
|
||||||
|
isReadonly.value = false
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@@ -74,165 +83,100 @@ provide('rec_id', recId)
|
|||||||
provide('rec_action', recAction)
|
provide('rec_action', recAction)
|
||||||
provide('rec_item', recItem)
|
provide('rec_item', recItem)
|
||||||
provide('table_data_loader', isLoading)
|
provide('table_data_loader', isLoading)
|
||||||
// #endregion
|
|
||||||
|
|
||||||
// #region Functions
|
const getCurrentSubSpecialistDetail = async (id: number | string) => {
|
||||||
async function fetchSubSpecialistData(params: any) {
|
const result = await getSubspecialistDetail(id)
|
||||||
const endpoint = transform('/api/v1/patient', params)
|
if (result.success) {
|
||||||
return await xfetch(endpoint)
|
const currentValue = result.body?.data || {}
|
||||||
}
|
recItem.value = currentValue
|
||||||
|
isFormEntryDialogOpen.value = true
|
||||||
async function handleDeleteRow(record: any) {
|
|
||||||
try {
|
|
||||||
// TODO : hit backend request untuk delete
|
|
||||||
console.log('Deleting record:', record)
|
|
||||||
|
|
||||||
// Simulate API call
|
|
||||||
// const response = await xfetch(`/api/v1/subspecialist/${record.id}`, {
|
|
||||||
// method: 'DELETE'
|
|
||||||
// })
|
|
||||||
|
|
||||||
// Refresh data setelah berhasil delete
|
|
||||||
await getSubSpecialistList()
|
|
||||||
|
|
||||||
// TODO: Show success message
|
|
||||||
console.log('Record deleted successfully')
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error deleting record:', error)
|
|
||||||
// TODO: Show error message
|
|
||||||
} finally {
|
|
||||||
// Reset record state
|
|
||||||
recId.value = 0
|
|
||||||
recAction.value = ''
|
|
||||||
recItem.value = null
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle confirmation result
|
const getSpecialistsList = async () => {
|
||||||
function handleConfirmDelete(record: any, action: string) {
|
const result = await getSpecialists()
|
||||||
console.log('Confirmed action:', action, 'for record:', record)
|
if (result.success) {
|
||||||
handleDeleteRow(record)
|
const currentSpecialists = result.body?.data || []
|
||||||
}
|
specialists.value = currentSpecialists.map((item: Specialist) => ({
|
||||||
|
value: item.id ? Number(item.id) : item.code,
|
||||||
function handleCancelConfirmation() {
|
label: item.name,
|
||||||
// Reset record state when cancelled
|
}))
|
||||||
recId.value = 0
|
|
||||||
recAction.value = ''
|
|
||||||
recItem.value = null
|
|
||||||
}
|
|
||||||
|
|
||||||
// #endregion region
|
|
||||||
|
|
||||||
// #region Form event handlers
|
|
||||||
|
|
||||||
function onCancelForm(resetForm: () => void) {
|
|
||||||
isFormEntryDialogOpen.value = false
|
|
||||||
selectedInstallationId.value = ''
|
|
||||||
selectedUnitId.value = ''
|
|
||||||
|
|
||||||
setTimeout(() => {
|
|
||||||
resetForm()
|
|
||||||
}, 500)
|
|
||||||
}
|
|
||||||
|
|
||||||
function onInstallationChanged(installationId: string) {
|
|
||||||
selectedInstallationId.value = installationId
|
|
||||||
}
|
|
||||||
|
|
||||||
function onUnitChanged(unitId: string) {
|
|
||||||
selectedUnitId.value = unitId
|
|
||||||
}
|
|
||||||
|
|
||||||
async function onSubmitForm(values: any, resetForm: () => void) {
|
|
||||||
let isSuccess = false
|
|
||||||
try {
|
|
||||||
// TODO: Implement form submission logic
|
|
||||||
console.log('Form submitted:', values)
|
|
||||||
|
|
||||||
// Simulate API call
|
|
||||||
// const response = await xfetch('/api/v1/subspecialist', {
|
|
||||||
// method: 'POST',
|
|
||||||
// body: JSON.stringify(values)
|
|
||||||
// })
|
|
||||||
|
|
||||||
// If successful, mark as success and close dialog
|
|
||||||
isFormEntryDialogOpen.value = false
|
|
||||||
isSuccess = true
|
|
||||||
|
|
||||||
// Reset selection ketika form berhasil submit
|
|
||||||
selectedInstallationId.value = ''
|
|
||||||
selectedUnitId.value = ''
|
|
||||||
|
|
||||||
// Refresh data after successful submission
|
|
||||||
await getSubSpecialistList()
|
|
||||||
|
|
||||||
// TODO: Show success message
|
|
||||||
console.log('Sub Specialist created successfully')
|
|
||||||
} catch (error: unknown) {
|
|
||||||
console.warn('Error submitting form:', error)
|
|
||||||
isSuccess = false
|
|
||||||
// Don't close dialog or reset form on error
|
|
||||||
// TODO: Show error message to user
|
|
||||||
} finally {
|
|
||||||
if (isSuccess) {
|
|
||||||
setTimeout(() => {
|
|
||||||
resetForm()
|
|
||||||
}, 500)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// #endregion
|
|
||||||
|
|
||||||
// #region Watchers
|
// Watch for row actions when recId or recAction changes
|
||||||
|
watch([recId, recAction], () => {
|
||||||
// Watch for row actions
|
|
||||||
watch(recId, () => {
|
|
||||||
switch (recAction.value) {
|
switch (recAction.value) {
|
||||||
|
case ActionEvents.showDetail:
|
||||||
|
getCurrentSubSpecialistDetail(recId.value)
|
||||||
|
title.value = 'Detail Sub Spesialis'
|
||||||
|
isReadonly.value = true
|
||||||
|
break
|
||||||
case ActionEvents.showEdit:
|
case ActionEvents.showEdit:
|
||||||
// TODO: Handle edit action
|
getCurrentSubSpecialistDetail(recId.value)
|
||||||
// isFormEntryDialogOpen.value = true
|
title.value = 'Edit Sub Spesialis'
|
||||||
|
isReadonly.value = false
|
||||||
break
|
break
|
||||||
case ActionEvents.showConfirmDelete:
|
case ActionEvents.showConfirmDelete:
|
||||||
// Trigger confirmation modal open
|
|
||||||
isRecordConfirmationOpen.value = true
|
isRecordConfirmationOpen.value = true
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
// #endregion
|
onMounted(async () => {
|
||||||
|
await getSpecialistsList()
|
||||||
|
await getSubSpecialistList()
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<Header v-model="searchInput" :prep="headerPrep" @search="handleSearch" />
|
<Header
|
||||||
<AppSubspecialistList :data="data" :pagination-meta="paginationMeta" @page-change="handlePageChange" />
|
v-model="searchInput"
|
||||||
|
:prep="headerPrep"
|
||||||
|
:ref-search-nav="headerPrep.refSearchNav"
|
||||||
|
@search="handleSearch"
|
||||||
|
class="mb-4 xl:mb-5"
|
||||||
|
/>
|
||||||
|
<AppSubSpecialistList :data="data" :pagination-meta="paginationMeta" @page-change="handlePageChange" />
|
||||||
|
|
||||||
<Dialog v-model:open="isFormEntryDialogOpen" :title="headerPrep.addNav?.label!" size="lg" prevent-outside>
|
<Dialog
|
||||||
<AppSubspecialistEntryForm
|
v-model:open="isFormEntryDialogOpen"
|
||||||
:installation="installationConf"
|
:title="!!recItem ? title : 'Tambah Sub Spesialis'"
|
||||||
:unit="filteredUnitConf"
|
size="lg"
|
||||||
:specialist="specialistConf"
|
prevent-outside
|
||||||
:schema="schemaConf"
|
>
|
||||||
:initial-values="{ name: '', code: '', installationId: '', unitId: '', specialistId: '' }"
|
<AppSubSpecialistEntryForm
|
||||||
@submit="onSubmitForm"
|
:schema="SubspecialistSchema"
|
||||||
@cancel="onCancelForm"
|
:specialists="specialists"
|
||||||
@installation-changed="onInstallationChanged"
|
:values="recItem"
|
||||||
@unit-changed="onUnitChanged"
|
:is-loading="isProcessing"
|
||||||
|
:is-readonly="isReadonly"
|
||||||
|
@submit="
|
||||||
|
(values: SubspecialistFormData | Record<string, any>, resetForm: () => void) => {
|
||||||
|
if (recId > 0) {
|
||||||
|
handleActionEdit(recId, values, getSubSpecialistList, resetForm, toast)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
handleActionSave(values, getSubSpecialistList, resetForm, toast)
|
||||||
|
}
|
||||||
|
"
|
||||||
|
@cancel="handleCancelForm"
|
||||||
/>
|
/>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
|
|
||||||
|
<!-- Record Confirmation Modal -->
|
||||||
<RecordConfirmation
|
<RecordConfirmation
|
||||||
v-model:open="isRecordConfirmationOpen"
|
v-model:open="isRecordConfirmationOpen"
|
||||||
action="delete"
|
action="delete"
|
||||||
:record="recItem"
|
:record="recItem"
|
||||||
@confirm="handleConfirmDelete"
|
@confirm="() => handleActionRemove(recId, getSubSpecialistList, toast)"
|
||||||
@cancel="handleCancelConfirmation"
|
@cancel=""
|
||||||
>
|
>
|
||||||
<template #default="{ record }">
|
<template #default="{ record }">
|
||||||
<div class="text-sm">
|
<div class="text-sm">
|
||||||
<p><strong>ID:</strong> {{ record?.id }}</p>
|
<p><strong>ID:</strong> {{ record?.id }}</p>
|
||||||
<p v-if="record?.name"><strong>Nama:</strong> {{ record.name }}</p>
|
<p v-if="record?.name"><strong>Nama:</strong> {{ record.name }}</p>
|
||||||
<p v-if="record?.code"><strong>Kode:</strong> {{ record.code }}</p>
|
<p v-if="record?.code"><strong>Kode:</strong> {{ record.code }}</p>
|
||||||
<p v-if="record?.specialist"><strong>Specialist:</strong> {{ record.specialist?.name }}</p>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</RecordConfirmation>
|
</RecordConfirmation>
|
||||||
|
|||||||
@@ -2,8 +2,9 @@
|
|||||||
// Components
|
// Components
|
||||||
import Dialog from '~/components/pub/base/modal/dialog.vue'
|
import Dialog from '~/components/pub/base/modal/dialog.vue'
|
||||||
import Header from '~/components/pub/custom-ui/nav-header/prep.vue'
|
import Header from '~/components/pub/custom-ui/nav-header/prep.vue'
|
||||||
import AppToolsEntryForm from '~/components/app/tools/entry-form.vue'
|
|
||||||
import RecordConfirmation from '~/components/pub/custom-ui/confirmation/record-confirmation.vue'
|
import RecordConfirmation from '~/components/pub/custom-ui/confirmation/record-confirmation.vue'
|
||||||
|
import AppToolsList from '~/components/app/tools/list.vue'
|
||||||
|
import AppToolsEntryForm from '~/components/app/tools/entry-form.vue'
|
||||||
|
|
||||||
// Helpers
|
// Helpers
|
||||||
import { usePaginatedList } from '~/composables/usePaginatedList'
|
import { usePaginatedList } from '~/composables/usePaginatedList'
|
||||||
@@ -60,8 +61,8 @@ const headerPrep: HeaderPrep = {
|
|||||||
minLength: 3,
|
minLength: 3,
|
||||||
debounceMs: 500,
|
debounceMs: 500,
|
||||||
showValidationFeedback: true,
|
showValidationFeedback: true,
|
||||||
onInput: (_val: string) => {
|
onInput: (value: string) => {
|
||||||
// Handle search input - this will be triggered by the header component
|
searchInput.value = value
|
||||||
},
|
},
|
||||||
onClick: () => {
|
onClick: () => {
|
||||||
// Handle search button click if needed
|
// Handle search button click if needed
|
||||||
@@ -131,7 +132,13 @@ onMounted(async () => {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<Header v-model="searchInput" :prep="headerPrep" @search="handleSearch" class="mb-4 xl:mb-5" />
|
<Header
|
||||||
|
v-model="searchInput"
|
||||||
|
:prep="headerPrep"
|
||||||
|
@search="handleSearch"
|
||||||
|
:ref-search-nav="headerPrep.refSearchNav"
|
||||||
|
class="mb-4 xl:mb-5"
|
||||||
|
/>
|
||||||
<AppToolsList :data="data" :pagination-meta="paginationMeta" @page-change="handlePageChange" />
|
<AppToolsList :data="data" :pagination-meta="paginationMeta" @page-change="handlePageChange" />
|
||||||
|
|
||||||
<Dialog
|
<Dialog
|
||||||
|
|||||||
@@ -0,0 +1,207 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import type { HeaderPrep } from '~/components/pub/custom-ui/data/types'
|
||||||
|
import AppUnitEntryForm from '~/components/app/unit/entry-form.vue'
|
||||||
|
import Dialog from '~/components/pub/base/modal/dialog.vue'
|
||||||
|
import RecordConfirmation from '~/components/pub/custom-ui/confirmation/record-confirmation.vue'
|
||||||
|
import { ActionEvents } from '~/components/pub/custom-ui/data/types'
|
||||||
|
import Header from '~/components/pub/custom-ui/nav-header/header.vue'
|
||||||
|
import { usePaginatedList } from '~/composables/usePaginatedList'
|
||||||
|
import { schemaConf, unitConf } from './entry'
|
||||||
|
|
||||||
|
// #region State & Computed
|
||||||
|
// Dialog state
|
||||||
|
const isFormEntryDialogOpen = ref(false)
|
||||||
|
const isRecordConfirmationOpen = ref(false)
|
||||||
|
|
||||||
|
// Table action rowId provider
|
||||||
|
const recId = ref<number>(0)
|
||||||
|
const recAction = ref<string>('')
|
||||||
|
const recItem = ref<any>(null)
|
||||||
|
|
||||||
|
async function fetchUnitData(params: any) {
|
||||||
|
const endpoint = transform('/api/v1/patient', params)
|
||||||
|
return await xfetch(endpoint)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Menggunakan composable untuk pagination
|
||||||
|
const {
|
||||||
|
data,
|
||||||
|
isLoading,
|
||||||
|
paginationMeta,
|
||||||
|
searchInput,
|
||||||
|
handlePageChange,
|
||||||
|
handleSearch,
|
||||||
|
fetchData: getUnitList,
|
||||||
|
} = usePaginatedList({
|
||||||
|
fetchFn: fetchUnitData,
|
||||||
|
entityName: 'unit',
|
||||||
|
})
|
||||||
|
|
||||||
|
const headerPrep: HeaderPrep = {
|
||||||
|
title: 'Unit',
|
||||||
|
icon: 'i-lucide-box',
|
||||||
|
refSearchNav: {
|
||||||
|
placeholder: 'Cari (min. 3 karakter)...',
|
||||||
|
minLength: 3,
|
||||||
|
debounceMs: 500,
|
||||||
|
showValidationFeedback: true,
|
||||||
|
onInput: (_val: string) => {
|
||||||
|
// Handle search input - this will be triggered by the header component
|
||||||
|
},
|
||||||
|
onClick: () => {
|
||||||
|
// Handle search button click if needed
|
||||||
|
},
|
||||||
|
onClear: () => {
|
||||||
|
// Handle search clear
|
||||||
|
},
|
||||||
|
},
|
||||||
|
addNav: {
|
||||||
|
label: 'Tambah Unit',
|
||||||
|
icon: 'i-lucide-send',
|
||||||
|
onClick: () => {
|
||||||
|
isFormEntryDialogOpen.value = true
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
provide('rec_id', recId)
|
||||||
|
provide('rec_action', recAction)
|
||||||
|
provide('rec_item', recItem)
|
||||||
|
provide('table_data_loader', isLoading)
|
||||||
|
// #endregion
|
||||||
|
|
||||||
|
// #region Functions
|
||||||
|
|
||||||
|
async function handleDeleteRow(record: any) {
|
||||||
|
try {
|
||||||
|
// TODO : hit backend request untuk delete
|
||||||
|
console.log('Deleting record:', record)
|
||||||
|
|
||||||
|
// Simulate API call
|
||||||
|
// const response = await xfetch(`/api/v1/unit/${record.id}`, {
|
||||||
|
// method: 'DELETE'
|
||||||
|
// })
|
||||||
|
|
||||||
|
// Refresh data setelah berhasil delete
|
||||||
|
await getUnitList()
|
||||||
|
|
||||||
|
// TODO: Show success message
|
||||||
|
console.log('Record deleted successfully')
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error deleting record:', error)
|
||||||
|
// TODO: Show error message
|
||||||
|
} finally {
|
||||||
|
// Reset record state
|
||||||
|
recId.value = 0
|
||||||
|
recAction.value = ''
|
||||||
|
recItem.value = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// #endregion region
|
||||||
|
|
||||||
|
// #region Form event handlers
|
||||||
|
|
||||||
|
function onCancelForm(resetForm: () => void) {
|
||||||
|
isFormEntryDialogOpen.value = false
|
||||||
|
setTimeout(() => {
|
||||||
|
resetForm()
|
||||||
|
}, 500)
|
||||||
|
}
|
||||||
|
|
||||||
|
async function onSubmitForm(values: any, resetForm: () => void) {
|
||||||
|
let isSuccess = false
|
||||||
|
try {
|
||||||
|
// TODO: Implement form submission logic
|
||||||
|
console.log('Form submitted:', values)
|
||||||
|
|
||||||
|
// Simulate API call
|
||||||
|
// const response = await xfetch('/api/v1/unit', {
|
||||||
|
// method: 'POST',
|
||||||
|
// body: JSON.stringify(values)
|
||||||
|
// })
|
||||||
|
|
||||||
|
// If successful, mark as success and close dialog
|
||||||
|
isFormEntryDialogOpen.value = false
|
||||||
|
isSuccess = true
|
||||||
|
|
||||||
|
// Refresh data after successful submission
|
||||||
|
await getUnitList()
|
||||||
|
|
||||||
|
// TODO: Show success message
|
||||||
|
console.log('Unit created successfully')
|
||||||
|
} catch (error: unknown) {
|
||||||
|
console.warn('Error submitting form:', error)
|
||||||
|
isSuccess = false
|
||||||
|
// Don't close dialog or reset form on error
|
||||||
|
// TODO: Show error message to user
|
||||||
|
} finally {
|
||||||
|
if (isSuccess) {
|
||||||
|
setTimeout(() => {
|
||||||
|
resetForm()
|
||||||
|
}, 500)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// #endregion
|
||||||
|
|
||||||
|
// #region Watchers
|
||||||
|
|
||||||
|
// Watch for row actions
|
||||||
|
watch(recId, () => {
|
||||||
|
switch (recAction.value) {
|
||||||
|
case ActionEvents.showEdit:
|
||||||
|
// TODO: Handle edit action
|
||||||
|
// isFormEntryDialogOpen.value = true
|
||||||
|
break
|
||||||
|
case ActionEvents.showConfirmDelete:
|
||||||
|
// Trigger confirmation modal open
|
||||||
|
isRecordConfirmationOpen.value = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// Handle confirmation result
|
||||||
|
function handleConfirmDelete(record: any, action: string) {
|
||||||
|
console.log('Confirmed action:', action, 'for record:', record)
|
||||||
|
handleDeleteRow(record)
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleCancelConfirmation() {
|
||||||
|
// Reset record state when cancelled
|
||||||
|
recId.value = 0
|
||||||
|
recAction.value = ''
|
||||||
|
recItem.value = null
|
||||||
|
}
|
||||||
|
// #endregion
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<Header v-model="searchInput" :prep="headerPrep" @search="handleSearch" />
|
||||||
|
<AppUnitList :data="data" :pagination-meta="paginationMeta" @page-change="handlePageChange" />
|
||||||
|
|
||||||
|
<Dialog v-model:open="isFormEntryDialogOpen" title="Tambah Unit" size="lg" prevent-outside>
|
||||||
|
<AppUnitEntryFormPrev
|
||||||
|
:unit="unitConf" :schema="schemaConf" :initial-values="{ name: '', code: '', parentId: '' }"
|
||||||
|
@submit="onSubmitForm" @cancel="onCancelForm"
|
||||||
|
/>
|
||||||
|
</Dialog>
|
||||||
|
|
||||||
|
<!-- Record Confirmation Modal -->
|
||||||
|
<RecordConfirmation
|
||||||
|
v-model:open="isRecordConfirmationOpen" action="delete" :record="recItem"
|
||||||
|
@confirm="handleConfirmDelete" @cancel="handleCancelConfirmation"
|
||||||
|
>
|
||||||
|
<template #default="{ record }">
|
||||||
|
<div class="text-sm">
|
||||||
|
<p><strong>ID:</strong> {{ record?.id }}</p>
|
||||||
|
<p v-if="record?.firstName"><strong>Nama:</strong> {{ record.firstName }}</p>
|
||||||
|
<p v-if="record?.code"><strong>Kode:</strong> {{ record.cellphone }}</p>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</RecordConfirmation>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
/* component style */
|
||||||
|
</style>
|
||||||
@@ -1,29 +1,39 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import type { HeaderPrep } from '~/components/pub/custom-ui/data/types'
|
// Components
|
||||||
import AppUnitEntryForm from '~/components/app/unit/entry-form.vue'
|
|
||||||
import Dialog from '~/components/pub/base/modal/dialog.vue'
|
import Dialog from '~/components/pub/base/modal/dialog.vue'
|
||||||
|
import Header from '~/components/pub/custom-ui/nav-header/prep.vue'
|
||||||
import RecordConfirmation from '~/components/pub/custom-ui/confirmation/record-confirmation.vue'
|
import RecordConfirmation from '~/components/pub/custom-ui/confirmation/record-confirmation.vue'
|
||||||
import { ActionEvents } from '~/components/pub/custom-ui/data/types'
|
import AppUnitList from '~/components/app/unit/list.vue'
|
||||||
import Header from '~/components/pub/custom-ui/nav-header/header.vue'
|
import AppUnitEntryForm from '~/components/app/unit/entry-form.vue'
|
||||||
|
|
||||||
|
// Helpers
|
||||||
import { usePaginatedList } from '~/composables/usePaginatedList'
|
import { usePaginatedList } from '~/composables/usePaginatedList'
|
||||||
import { schemaConf, unitConf } from './entry'
|
import { toast } from '~/components/pub/ui/toast'
|
||||||
|
|
||||||
// #region State & Computed
|
// Types
|
||||||
// Dialog state
|
import { ActionEvents, type HeaderPrep } from '~/components/pub/custom-ui/data/types'
|
||||||
const isFormEntryDialogOpen = ref(false)
|
import { UnitSchema, type UnitFormData } from '~/schemas/unit.schema'
|
||||||
const isRecordConfirmationOpen = ref(false)
|
|
||||||
|
|
||||||
// Table action rowId provider
|
// Handlers
|
||||||
const recId = ref<number>(0)
|
import {
|
||||||
const recAction = ref<string>('')
|
recId,
|
||||||
const recItem = ref<any>(null)
|
recAction,
|
||||||
|
recItem,
|
||||||
|
isReadonly,
|
||||||
|
isProcessing,
|
||||||
|
isFormEntryDialogOpen,
|
||||||
|
isRecordConfirmationOpen,
|
||||||
|
handleActionSave,
|
||||||
|
handleActionEdit,
|
||||||
|
handleActionRemove,
|
||||||
|
handleCancelForm,
|
||||||
|
} from '~/handlers/unit.handler'
|
||||||
|
|
||||||
async function fetchUnitData(params: any) {
|
// Services
|
||||||
const endpoint = transform('/api/v1/patient', params)
|
import { getUnits, getUnitDetail } from '~/services/unit.service'
|
||||||
return await xfetch(endpoint)
|
|
||||||
}
|
const title = ref('')
|
||||||
|
|
||||||
// Menggunakan composable untuk pagination
|
|
||||||
const {
|
const {
|
||||||
data,
|
data,
|
||||||
isLoading,
|
isLoading,
|
||||||
@@ -33,7 +43,10 @@ const {
|
|||||||
handleSearch,
|
handleSearch,
|
||||||
fetchData: getUnitList,
|
fetchData: getUnitList,
|
||||||
} = usePaginatedList({
|
} = usePaginatedList({
|
||||||
fetchFn: fetchUnitData,
|
fetchFn: async ({ page, search }) => {
|
||||||
|
const result = await getUnits({ search, page })
|
||||||
|
return { success: result.success || false, body: result.body || {} }
|
||||||
|
},
|
||||||
entityName: 'unit',
|
entityName: 'unit',
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -45,21 +58,20 @@ const headerPrep: HeaderPrep = {
|
|||||||
minLength: 3,
|
minLength: 3,
|
||||||
debounceMs: 500,
|
debounceMs: 500,
|
||||||
showValidationFeedback: true,
|
showValidationFeedback: true,
|
||||||
onInput: (_val: string) => {
|
onInput: (value: string) => {
|
||||||
// Handle search input - this will be triggered by the header component
|
searchInput.value = value
|
||||||
},
|
|
||||||
onClick: () => {
|
|
||||||
// Handle search button click if needed
|
|
||||||
},
|
|
||||||
onClear: () => {
|
|
||||||
// Handle search clear
|
|
||||||
},
|
},
|
||||||
|
onClick: () => {},
|
||||||
|
onClear: () => {},
|
||||||
},
|
},
|
||||||
addNav: {
|
addNav: {
|
||||||
label: 'Tambah Unit',
|
label: 'Tambah',
|
||||||
icon: 'i-lucide-send',
|
icon: 'i-lucide-plus',
|
||||||
onClick: () => {
|
onClick: () => {
|
||||||
|
recItem.value = null
|
||||||
|
recId.value = 0
|
||||||
isFormEntryDialogOpen.value = true
|
isFormEntryDialogOpen.value = true
|
||||||
|
isReadonly.value = false
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@@ -68,140 +80,83 @@ provide('rec_id', recId)
|
|||||||
provide('rec_action', recAction)
|
provide('rec_action', recAction)
|
||||||
provide('rec_item', recItem)
|
provide('rec_item', recItem)
|
||||||
provide('table_data_loader', isLoading)
|
provide('table_data_loader', isLoading)
|
||||||
// #endregion
|
|
||||||
|
|
||||||
// #region Functions
|
const getCurrentUnitDetail = async (id: number | string) => {
|
||||||
|
const result = await getUnitDetail(id)
|
||||||
async function handleDeleteRow(record: any) {
|
if (result.success) {
|
||||||
try {
|
const currentValue = result.body?.data || {}
|
||||||
// TODO : hit backend request untuk delete
|
recItem.value = currentValue
|
||||||
console.log('Deleting record:', record)
|
isFormEntryDialogOpen.value = true
|
||||||
|
|
||||||
// Simulate API call
|
|
||||||
// const response = await xfetch(`/api/v1/unit/${record.id}`, {
|
|
||||||
// method: 'DELETE'
|
|
||||||
// })
|
|
||||||
|
|
||||||
// Refresh data setelah berhasil delete
|
|
||||||
await getUnitList()
|
|
||||||
|
|
||||||
// TODO: Show success message
|
|
||||||
console.log('Record deleted successfully')
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error deleting record:', error)
|
|
||||||
// TODO: Show error message
|
|
||||||
} finally {
|
|
||||||
// Reset record state
|
|
||||||
recId.value = 0
|
|
||||||
recAction.value = ''
|
|
||||||
recItem.value = null
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// #endregion region
|
// Watch for row actions when recId or recAction changes
|
||||||
|
watch([recId, recAction], () => {
|
||||||
// #region Form event handlers
|
|
||||||
|
|
||||||
function onCancelForm(resetForm: () => void) {
|
|
||||||
isFormEntryDialogOpen.value = false
|
|
||||||
setTimeout(() => {
|
|
||||||
resetForm()
|
|
||||||
}, 500)
|
|
||||||
}
|
|
||||||
|
|
||||||
async function onSubmitForm(values: any, resetForm: () => void) {
|
|
||||||
let isSuccess = false
|
|
||||||
try {
|
|
||||||
// TODO: Implement form submission logic
|
|
||||||
console.log('Form submitted:', values)
|
|
||||||
|
|
||||||
// Simulate API call
|
|
||||||
// const response = await xfetch('/api/v1/unit', {
|
|
||||||
// method: 'POST',
|
|
||||||
// body: JSON.stringify(values)
|
|
||||||
// })
|
|
||||||
|
|
||||||
// If successful, mark as success and close dialog
|
|
||||||
isFormEntryDialogOpen.value = false
|
|
||||||
isSuccess = true
|
|
||||||
|
|
||||||
// Refresh data after successful submission
|
|
||||||
await getUnitList()
|
|
||||||
|
|
||||||
// TODO: Show success message
|
|
||||||
console.log('Unit created successfully')
|
|
||||||
} catch (error: unknown) {
|
|
||||||
console.warn('Error submitting form:', error)
|
|
||||||
isSuccess = false
|
|
||||||
// Don't close dialog or reset form on error
|
|
||||||
// TODO: Show error message to user
|
|
||||||
} finally {
|
|
||||||
if (isSuccess) {
|
|
||||||
setTimeout(() => {
|
|
||||||
resetForm()
|
|
||||||
}, 500)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// #endregion
|
|
||||||
|
|
||||||
// #region Watchers
|
|
||||||
|
|
||||||
// Watch for row actions
|
|
||||||
watch(recId, () => {
|
|
||||||
switch (recAction.value) {
|
switch (recAction.value) {
|
||||||
|
case ActionEvents.showDetail:
|
||||||
|
getCurrentUnitDetail(recId.value)
|
||||||
|
title.value = 'Detail Unit'
|
||||||
|
isReadonly.value = true
|
||||||
|
break
|
||||||
case ActionEvents.showEdit:
|
case ActionEvents.showEdit:
|
||||||
// TODO: Handle edit action
|
getCurrentUnitDetail(recId.value)
|
||||||
// isFormEntryDialogOpen.value = true
|
title.value = 'Edit Unit'
|
||||||
|
isReadonly.value = false
|
||||||
break
|
break
|
||||||
case ActionEvents.showConfirmDelete:
|
case ActionEvents.showConfirmDelete:
|
||||||
// Trigger confirmation modal open
|
|
||||||
isRecordConfirmationOpen.value = true
|
isRecordConfirmationOpen.value = true
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
// Handle confirmation result
|
onMounted(async () => {
|
||||||
function handleConfirmDelete(record: any, action: string) {
|
await getUnitList()
|
||||||
console.log('Confirmed action:', action, 'for record:', record)
|
})
|
||||||
handleDeleteRow(record)
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleCancelConfirmation() {
|
|
||||||
// Reset record state when cancelled
|
|
||||||
recId.value = 0
|
|
||||||
recAction.value = ''
|
|
||||||
recItem.value = null
|
|
||||||
}
|
|
||||||
// #endregion
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<Header v-model="searchInput" :prep="headerPrep" @search="handleSearch" />
|
<Header
|
||||||
|
v-model="searchInput"
|
||||||
|
:prep="headerPrep"
|
||||||
|
:ref-search-nav="headerPrep.refSearchNav"
|
||||||
|
@search="handleSearch"
|
||||||
|
class="mb-4 xl:mb-5"
|
||||||
|
/>
|
||||||
<AppUnitList :data="data" :pagination-meta="paginationMeta" @page-change="handlePageChange" />
|
<AppUnitList :data="data" :pagination-meta="paginationMeta" @page-change="handlePageChange" />
|
||||||
|
|
||||||
<Dialog v-model:open="isFormEntryDialogOpen" title="Tambah Unit" size="lg" prevent-outside>
|
<Dialog v-model:open="isFormEntryDialogOpen" :title="!!recItem ? title : 'Tambah Unit'" size="lg" prevent-outside>
|
||||||
<AppUnitEntryForm
|
<AppUnitEntryForm
|
||||||
:unit="unitConf" :schema="schemaConf" :initial-values="{ name: '', code: '', parentId: '' }"
|
:schema="UnitSchema"
|
||||||
@submit="onSubmitForm" @cancel="onCancelForm"
|
:values="recItem"
|
||||||
/>
|
:is-loading="isProcessing"
|
||||||
|
:is-readonly="isReadonly"
|
||||||
|
@submit="
|
||||||
|
(values: UnitFormData | Record<string, any>, resetForm: () => void) => {
|
||||||
|
if (recId > 0) {
|
||||||
|
handleActionEdit(recId, values, getUnitList, resetForm, toast)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
handleActionSave(values, getUnitList, resetForm, toast)
|
||||||
|
}
|
||||||
|
"
|
||||||
|
@cancel="handleCancelForm"
|
||||||
|
/>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
|
|
||||||
<!-- Record Confirmation Modal -->
|
<!-- Record Confirmation Modal -->
|
||||||
<RecordConfirmation
|
<RecordConfirmation
|
||||||
v-model:open="isRecordConfirmationOpen" action="delete" :record="recItem"
|
v-model:open="isRecordConfirmationOpen"
|
||||||
@confirm="handleConfirmDelete" @cancel="handleCancelConfirmation"
|
action="delete"
|
||||||
>
|
:record="recItem"
|
||||||
|
@confirm="() => handleActionRemove(recId, getUnitList, toast)"
|
||||||
|
@cancel=""
|
||||||
|
>
|
||||||
<template #default="{ record }">
|
<template #default="{ record }">
|
||||||
<div class="text-sm">
|
<div class="text-sm">
|
||||||
<p><strong>ID:</strong> {{ record?.id }}</p>
|
<p><strong>ID:</strong> {{ record?.id }}</p>
|
||||||
<p v-if="record?.firstName"><strong>Nama:</strong> {{ record.firstName }}</p>
|
<p v-if="record?.name"><strong>Nama:</strong> {{ record.name }}</p>
|
||||||
<p v-if="record?.code"><strong>Kode:</strong> {{ record.cellphone }}</p>
|
<p v-if="record?.code"><strong>Kode:</strong> {{ record.code }}</p>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</RecordConfirmation>
|
</RecordConfirmation>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
/* component style */
|
|
||||||
</style>
|
|
||||||
|
|||||||
@@ -2,8 +2,8 @@
|
|||||||
// Components
|
// Components
|
||||||
import Dialog from '~/components/pub/base/modal/dialog.vue'
|
import Dialog from '~/components/pub/base/modal/dialog.vue'
|
||||||
import Header from '~/components/pub/custom-ui/nav-header/prep.vue'
|
import Header from '~/components/pub/custom-ui/nav-header/prep.vue'
|
||||||
import AppUomEntryForm from '~/components/app/uom/entry-form.vue'
|
|
||||||
import RecordConfirmation from '~/components/pub/custom-ui/confirmation/record-confirmation.vue'
|
import RecordConfirmation from '~/components/pub/custom-ui/confirmation/record-confirmation.vue'
|
||||||
|
import AppUomEntryForm from '~/components/app/uom/entry-form.vue'
|
||||||
|
|
||||||
// Helpers
|
// Helpers
|
||||||
import { usePaginatedList } from '~/composables/usePaginatedList'
|
import { usePaginatedList } from '~/composables/usePaginatedList'
|
||||||
@@ -57,7 +57,9 @@ const headerPrep: HeaderPrep = {
|
|||||||
minLength: 3,
|
minLength: 3,
|
||||||
debounceMs: 500,
|
debounceMs: 500,
|
||||||
showValidationFeedback: true,
|
showValidationFeedback: true,
|
||||||
onInput: (_val: string) => {},
|
onInput: (value: string) => {
|
||||||
|
searchInput.value = value
|
||||||
|
},
|
||||||
onClick: () => {},
|
onClick: () => {},
|
||||||
onClear: () => {},
|
onClear: () => {},
|
||||||
},
|
},
|
||||||
@@ -113,17 +115,18 @@ onMounted(async () => {
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="p-4">
|
<div class="p-4">
|
||||||
<Header v-model="searchInput" :prep="headerPrep" @search="handleSearch" />
|
<Header
|
||||||
|
v-model="searchInput"
|
||||||
|
:prep="headerPrep"
|
||||||
|
:ref-search-nav="headerPrep.refSearchNav"
|
||||||
|
@search="handleSearch"
|
||||||
|
class="mb-4 xl:mb-5"
|
||||||
|
/>
|
||||||
<div class="rounded-md border p-4">
|
<div class="rounded-md border p-4">
|
||||||
<AppUomList :data="data" :pagination-meta="paginationMeta" @page-change="handlePageChange" />
|
<AppUomList :data="data" :pagination-meta="paginationMeta" @page-change="handlePageChange" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Dialog
|
<Dialog v-model:open="isFormEntryDialogOpen" :title="!!recItem ? title : 'Tambah Uom'" size="lg" prevent-outside>
|
||||||
v-model:open="isFormEntryDialogOpen"
|
|
||||||
:title="!!recItem ? title : 'Tambah Uom'"
|
|
||||||
size="lg"
|
|
||||||
prevent-outside
|
|
||||||
>
|
|
||||||
<AppUomEntryForm
|
<AppUomEntryForm
|
||||||
:schema="UomSchema"
|
:schema="UomSchema"
|
||||||
:values="recItem"
|
:values="recItem"
|
||||||
|
|||||||
@@ -1,3 +1,109 @@
|
|||||||
|
import { ref } from 'vue'
|
||||||
|
|
||||||
|
// Factory for CRUD handler state and actions
|
||||||
|
export function createCrudHandler<T = any>(crud: {
|
||||||
|
post: (...args: any[]) => Promise<any>
|
||||||
|
patch: (...args: any[]) => Promise<any>
|
||||||
|
remove: (...args: any[]) => Promise<any>
|
||||||
|
}) {
|
||||||
|
const recId = ref<number>(0)
|
||||||
|
const recAction = ref<string>('')
|
||||||
|
const recItem = ref<T | null>(null)
|
||||||
|
const isReadonly = ref(false)
|
||||||
|
const isProcessing = ref(false)
|
||||||
|
const isFormEntryDialogOpen = ref(false)
|
||||||
|
const isRecordConfirmationOpen = ref(false)
|
||||||
|
|
||||||
|
function onResetState() {
|
||||||
|
recId.value = 0
|
||||||
|
recAction.value = ''
|
||||||
|
recItem.value = null
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleActionSave(values: any, refresh: () => void, reset: () => void, toast: ToastFn) {
|
||||||
|
isProcessing.value = true
|
||||||
|
await handleAsyncAction<[any], any>({
|
||||||
|
action: crud.post,
|
||||||
|
args: [values],
|
||||||
|
toast,
|
||||||
|
successMessage: 'Data berhasil disimpan',
|
||||||
|
errorMessage: 'Gagal menyimpan data',
|
||||||
|
onSuccess: () => {
|
||||||
|
isFormEntryDialogOpen.value = false
|
||||||
|
if (refresh) refresh()
|
||||||
|
},
|
||||||
|
onFinally: (isSuccess: boolean) => {
|
||||||
|
if (isSuccess) setTimeout(reset, 500)
|
||||||
|
isProcessing.value = false
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleActionEdit(
|
||||||
|
id: number | string,
|
||||||
|
values: any,
|
||||||
|
refresh: () => void,
|
||||||
|
reset: () => void,
|
||||||
|
toast: ToastFn,
|
||||||
|
) {
|
||||||
|
isProcessing.value = true
|
||||||
|
await handleAsyncAction<[number | string, any], any>({
|
||||||
|
action: crud.patch,
|
||||||
|
args: [id, values],
|
||||||
|
toast,
|
||||||
|
successMessage: 'Data berhasil diubah',
|
||||||
|
errorMessage: 'Gagal mengubah data',
|
||||||
|
onSuccess: () => {
|
||||||
|
isFormEntryDialogOpen.value = false
|
||||||
|
if (refresh) refresh()
|
||||||
|
},
|
||||||
|
onFinally: (isSuccess: boolean) => {
|
||||||
|
if (isSuccess) setTimeout(reset, 500)
|
||||||
|
isProcessing.value = false
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleActionRemove(id: number | string, refresh: () => void, toast: ToastFn) {
|
||||||
|
isProcessing.value = true
|
||||||
|
await handleAsyncAction<[number | string], any>({
|
||||||
|
action: crud.remove,
|
||||||
|
args: [id],
|
||||||
|
toast,
|
||||||
|
successMessage: 'Data berhasil dihapus',
|
||||||
|
errorMessage: 'Gagal menghapus data',
|
||||||
|
onSuccess: () => {
|
||||||
|
isRecordConfirmationOpen.value = false
|
||||||
|
if (refresh) refresh()
|
||||||
|
},
|
||||||
|
onFinally: () => {
|
||||||
|
isProcessing.value = false
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleCancelForm(reset: () => void) {
|
||||||
|
isFormEntryDialogOpen.value = false
|
||||||
|
isReadonly.value = false
|
||||||
|
setTimeout(reset, 300)
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
recId,
|
||||||
|
recAction,
|
||||||
|
recItem,
|
||||||
|
isReadonly,
|
||||||
|
isProcessing,
|
||||||
|
isFormEntryDialogOpen,
|
||||||
|
isRecordConfirmationOpen,
|
||||||
|
onResetState,
|
||||||
|
handleActionSave,
|
||||||
|
handleActionEdit,
|
||||||
|
handleActionRemove,
|
||||||
|
handleCancelForm,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Reusable async handler for CRUD actions with toast and state management
|
// Reusable async handler for CRUD actions with toast and state management
|
||||||
export type ToastFn = (params: { title: string; description: string; variant: 'default' | 'destructive' }) => void
|
export type ToastFn = (params: { title: string; description: string; variant: 'default' | 'destructive' }) => void
|
||||||
|
|
||||||
|
|||||||
@@ -1,101 +1,21 @@
|
|||||||
import { ref } from 'vue'
|
import { createCrudHandler } from '~/handlers/_handler'
|
||||||
|
|
||||||
// Handlers
|
|
||||||
import { type ToastFn, handleAsyncAction } from '~/handlers/_handler'
|
|
||||||
|
|
||||||
// Services
|
|
||||||
import { postDevice, patchDevice, removeDevice } from '~/services/device.service'
|
import { postDevice, patchDevice, removeDevice } from '~/services/device.service'
|
||||||
|
|
||||||
const recId = ref<number>(0)
|
export const {
|
||||||
const recAction = ref<string>('')
|
recId,
|
||||||
const recItem = ref<any>(null)
|
recAction,
|
||||||
const isReadonly = ref(false)
|
recItem,
|
||||||
const isProcessing = ref(false)
|
isReadonly,
|
||||||
const isFormEntryDialogOpen = ref(false)
|
isProcessing,
|
||||||
const isRecordConfirmationOpen = ref(false)
|
isFormEntryDialogOpen,
|
||||||
|
isRecordConfirmationOpen,
|
||||||
function onResetState() {
|
onResetState,
|
||||||
recId.value = 0
|
handleActionSave,
|
||||||
recAction.value = ''
|
handleActionEdit,
|
||||||
recItem.value = null
|
handleActionRemove,
|
||||||
}
|
handleCancelForm,
|
||||||
|
} = createCrudHandler({
|
||||||
export async function handleActionSave(
|
post: postDevice,
|
||||||
values: any,
|
patch: patchDevice,
|
||||||
refresh: () => void,
|
remove: removeDevice,
|
||||||
reset: () => void,
|
})
|
||||||
toast: ToastFn
|
|
||||||
) {
|
|
||||||
isProcessing.value = true;
|
|
||||||
await handleAsyncAction<[any], any>({
|
|
||||||
action: postDevice,
|
|
||||||
args: [values],
|
|
||||||
toast,
|
|
||||||
successMessage: 'Data berhasil disimpan',
|
|
||||||
errorMessage: 'Gagal menyimpan data',
|
|
||||||
onSuccess: () => {
|
|
||||||
isFormEntryDialogOpen.value = false;
|
|
||||||
if (refresh) refresh();
|
|
||||||
},
|
|
||||||
onFinally: (isSuccess: boolean) => {
|
|
||||||
if (isSuccess) setTimeout(reset, 500);
|
|
||||||
isProcessing.value = false;
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function handleActionEdit(
|
|
||||||
id: number | string,
|
|
||||||
values: any,
|
|
||||||
refresh: () => void,
|
|
||||||
reset: () => void,
|
|
||||||
toast: ToastFn
|
|
||||||
) {
|
|
||||||
isProcessing.value = true;
|
|
||||||
await handleAsyncAction<[number | string, any], any>({
|
|
||||||
action: patchDevice,
|
|
||||||
args: [id, values],
|
|
||||||
toast,
|
|
||||||
successMessage: 'Data berhasil diubah',
|
|
||||||
errorMessage: 'Gagal mengubah data',
|
|
||||||
onSuccess: () => {
|
|
||||||
isFormEntryDialogOpen.value = false;
|
|
||||||
if (refresh) refresh();
|
|
||||||
},
|
|
||||||
onFinally: (isSuccess: boolean) => {
|
|
||||||
if (isSuccess) setTimeout(reset, 500);
|
|
||||||
isProcessing.value = false;
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function handleActionRemove(
|
|
||||||
id: number | string,
|
|
||||||
refresh: () => void,
|
|
||||||
toast: ToastFn
|
|
||||||
) {
|
|
||||||
isProcessing.value = true;
|
|
||||||
await handleAsyncAction<[number | string], any>({
|
|
||||||
action: removeDevice,
|
|
||||||
args: [id],
|
|
||||||
toast,
|
|
||||||
successMessage: 'Data berhasil dihapus',
|
|
||||||
errorMessage: 'Gagal menghapus data',
|
|
||||||
onSuccess: () => {
|
|
||||||
if (refresh) refresh();
|
|
||||||
},
|
|
||||||
onFinally: () => {
|
|
||||||
onResetState();
|
|
||||||
isProcessing.value = false;
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export function handleCancelForm(reset: () => void) {
|
|
||||||
isFormEntryDialogOpen.value = false
|
|
||||||
setTimeout(() => {
|
|
||||||
reset()
|
|
||||||
}, 500)
|
|
||||||
}
|
|
||||||
|
|
||||||
export { recId, recAction, recItem, isReadonly, isProcessing, isFormEntryDialogOpen, isRecordConfirmationOpen }
|
|
||||||
|
|||||||
@@ -0,0 +1,21 @@
|
|||||||
|
import { createCrudHandler } from '~/handlers/_handler'
|
||||||
|
import { postDivisionPosition, patchDivisionPosition, removeDivisionPosition } from '~/services/division-position.service'
|
||||||
|
|
||||||
|
export const {
|
||||||
|
recId,
|
||||||
|
recAction,
|
||||||
|
recItem,
|
||||||
|
isReadonly,
|
||||||
|
isProcessing,
|
||||||
|
isFormEntryDialogOpen,
|
||||||
|
isRecordConfirmationOpen,
|
||||||
|
onResetState,
|
||||||
|
handleActionSave,
|
||||||
|
handleActionEdit,
|
||||||
|
handleActionRemove,
|
||||||
|
handleCancelForm,
|
||||||
|
} = createCrudHandler({
|
||||||
|
post: postDivisionPosition,
|
||||||
|
patch: patchDivisionPosition,
|
||||||
|
remove: removeDivisionPosition,
|
||||||
|
})
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
import { createCrudHandler } from '~/handlers/_handler'
|
||||||
|
import { postDivision, patchDivision, removeDivision } from '~/services/division.service'
|
||||||
|
|
||||||
|
export const {
|
||||||
|
recId,
|
||||||
|
recAction,
|
||||||
|
recItem,
|
||||||
|
isReadonly,
|
||||||
|
isProcessing,
|
||||||
|
isFormEntryDialogOpen,
|
||||||
|
isRecordConfirmationOpen,
|
||||||
|
onResetState,
|
||||||
|
handleActionSave,
|
||||||
|
handleActionEdit,
|
||||||
|
handleActionRemove,
|
||||||
|
handleCancelForm,
|
||||||
|
} = createCrudHandler({
|
||||||
|
post: postDivision,
|
||||||
|
patch: patchDivision,
|
||||||
|
remove: removeDivision,
|
||||||
|
})
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
import { createCrudHandler } from '~/handlers/_handler'
|
||||||
|
import { postInstallation, patchInstallation, removeInstallation } from '~/services/installation.service'
|
||||||
|
|
||||||
|
export const {
|
||||||
|
recId,
|
||||||
|
recAction,
|
||||||
|
recItem,
|
||||||
|
isReadonly,
|
||||||
|
isProcessing,
|
||||||
|
isFormEntryDialogOpen,
|
||||||
|
isRecordConfirmationOpen,
|
||||||
|
onResetState,
|
||||||
|
handleActionSave,
|
||||||
|
handleActionEdit,
|
||||||
|
handleActionRemove,
|
||||||
|
handleCancelForm,
|
||||||
|
} = createCrudHandler({
|
||||||
|
post: postInstallation,
|
||||||
|
patch: patchInstallation,
|
||||||
|
remove: removeInstallation,
|
||||||
|
})
|
||||||
@@ -1,92 +1,21 @@
|
|||||||
import { ref } from 'vue'
|
import { createCrudHandler } from '~/handlers/_handler'
|
||||||
|
|
||||||
// Handlers
|
|
||||||
import { type ToastFn, handleAsyncAction } from '~/handlers/_handler'
|
|
||||||
|
|
||||||
// Services
|
|
||||||
import { postMaterial, patchMaterial, removeMaterial } from '~/services/material.service'
|
import { postMaterial, patchMaterial, removeMaterial } from '~/services/material.service'
|
||||||
|
|
||||||
const recId = ref<number>(0)
|
export const {
|
||||||
const recAction = ref<string>('')
|
recId,
|
||||||
const recItem = ref<any>(null)
|
recAction,
|
||||||
const isReadonly = ref(false)
|
recItem,
|
||||||
const isProcessing = ref(false)
|
isReadonly,
|
||||||
const isFormEntryDialogOpen = ref(false)
|
isProcessing,
|
||||||
const isRecordConfirmationOpen = ref(false)
|
isFormEntryDialogOpen,
|
||||||
|
isRecordConfirmationOpen,
|
||||||
function onResetState() {
|
onResetState,
|
||||||
recId.value = 0
|
handleActionSave,
|
||||||
recAction.value = ''
|
handleActionEdit,
|
||||||
recItem.value = null
|
handleActionRemove,
|
||||||
}
|
handleCancelForm,
|
||||||
|
} = createCrudHandler({
|
||||||
export async function handleActionSave(values: any, refresh: () => void, reset: () => void, toast: ToastFn) {
|
post: postMaterial,
|
||||||
isProcessing.value = true
|
patch: patchMaterial,
|
||||||
await handleAsyncAction<[any], any>({
|
remove: removeMaterial,
|
||||||
action: postMaterial,
|
})
|
||||||
args: [values],
|
|
||||||
toast,
|
|
||||||
successMessage: 'Data berhasil disimpan',
|
|
||||||
errorMessage: 'Gagal menyimpan data',
|
|
||||||
onSuccess: () => {
|
|
||||||
isFormEntryDialogOpen.value = false
|
|
||||||
if (refresh) refresh()
|
|
||||||
},
|
|
||||||
onFinally: (isSuccess: boolean) => {
|
|
||||||
if (isSuccess) setTimeout(reset, 500)
|
|
||||||
isProcessing.value = false
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function handleActionEdit(
|
|
||||||
id: number | string,
|
|
||||||
values: any,
|
|
||||||
refresh: () => void,
|
|
||||||
reset: () => void,
|
|
||||||
toast: ToastFn,
|
|
||||||
) {
|
|
||||||
isProcessing.value = true
|
|
||||||
await handleAsyncAction<[number | string, any], any>({
|
|
||||||
action: patchMaterial,
|
|
||||||
args: [id, values],
|
|
||||||
toast,
|
|
||||||
successMessage: 'Data berhasil diubah',
|
|
||||||
errorMessage: 'Gagal mengubah data',
|
|
||||||
onSuccess: () => {
|
|
||||||
isFormEntryDialogOpen.value = false
|
|
||||||
if (refresh) refresh()
|
|
||||||
},
|
|
||||||
onFinally: (isSuccess: boolean) => {
|
|
||||||
if (isSuccess) setTimeout(reset, 500)
|
|
||||||
isProcessing.value = false
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function handleActionRemove(id: number | string, refresh: () => void, toast: ToastFn) {
|
|
||||||
isProcessing.value = true
|
|
||||||
await handleAsyncAction<[number | string], any>({
|
|
||||||
action: removeMaterial,
|
|
||||||
args: [id],
|
|
||||||
toast,
|
|
||||||
successMessage: 'Data berhasil dihapus',
|
|
||||||
errorMessage: 'Gagal menghapus data',
|
|
||||||
onSuccess: () => {
|
|
||||||
if (refresh) refresh()
|
|
||||||
},
|
|
||||||
onFinally: () => {
|
|
||||||
onResetState()
|
|
||||||
isProcessing.value = false
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
export function handleCancelForm(reset: () => void) {
|
|
||||||
isFormEntryDialogOpen.value = false
|
|
||||||
setTimeout(() => {
|
|
||||||
reset()
|
|
||||||
}, 500)
|
|
||||||
}
|
|
||||||
|
|
||||||
export { recId, recAction, recItem, isReadonly, isProcessing, isFormEntryDialogOpen, isRecordConfirmationOpen }
|
|
||||||
|
|||||||
@@ -1,101 +1,21 @@
|
|||||||
import { ref } from 'vue'
|
import { createCrudHandler } from '~/handlers/_handler'
|
||||||
|
|
||||||
// Handlers
|
|
||||||
import { type ToastFn, handleAsyncAction } from '~/handlers/_handler'
|
|
||||||
|
|
||||||
// Services
|
|
||||||
import { postMedicineGroup, patchMedicineGroup, removeMedicineGroup } from '~/services/medicine-group.service'
|
import { postMedicineGroup, patchMedicineGroup, removeMedicineGroup } from '~/services/medicine-group.service'
|
||||||
|
|
||||||
const recId = ref<number>(0)
|
export const {
|
||||||
const recAction = ref<string>('')
|
recId,
|
||||||
const recItem = ref<any>(null)
|
recAction,
|
||||||
const isReadonly = ref(false)
|
recItem,
|
||||||
const isProcessing = ref(false)
|
isReadonly,
|
||||||
const isFormEntryDialogOpen = ref(false)
|
isProcessing,
|
||||||
const isRecordConfirmationOpen = ref(false)
|
isFormEntryDialogOpen,
|
||||||
|
isRecordConfirmationOpen,
|
||||||
function onResetState() {
|
onResetState,
|
||||||
recId.value = 0
|
handleActionSave,
|
||||||
recAction.value = ''
|
handleActionEdit,
|
||||||
recItem.value = null
|
handleActionRemove,
|
||||||
}
|
handleCancelForm,
|
||||||
|
} = createCrudHandler({
|
||||||
export async function handleActionSave(
|
post: postMedicineGroup,
|
||||||
values: any,
|
patch: patchMedicineGroup,
|
||||||
refresh: () => void,
|
remove: removeMedicineGroup,
|
||||||
reset: () => void,
|
})
|
||||||
toast: ToastFn
|
|
||||||
) {
|
|
||||||
isProcessing.value = true;
|
|
||||||
await handleAsyncAction<[any], any>({
|
|
||||||
action: postMedicineGroup,
|
|
||||||
args: [values],
|
|
||||||
toast,
|
|
||||||
successMessage: 'Data berhasil disimpan',
|
|
||||||
errorMessage: 'Gagal menyimpan data',
|
|
||||||
onSuccess: () => {
|
|
||||||
isFormEntryDialogOpen.value = false;
|
|
||||||
if (refresh) refresh();
|
|
||||||
},
|
|
||||||
onFinally: (isSuccess: boolean) => {
|
|
||||||
if (isSuccess) setTimeout(reset, 500);
|
|
||||||
isProcessing.value = false;
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function handleActionEdit(
|
|
||||||
id: number | string,
|
|
||||||
values: any,
|
|
||||||
refresh: () => void,
|
|
||||||
reset: () => void,
|
|
||||||
toast: ToastFn
|
|
||||||
) {
|
|
||||||
isProcessing.value = true;
|
|
||||||
await handleAsyncAction<[number | string, any], any>({
|
|
||||||
action: patchMedicineGroup,
|
|
||||||
args: [id, values],
|
|
||||||
toast,
|
|
||||||
successMessage: 'Data berhasil diubah',
|
|
||||||
errorMessage: 'Gagal mengubah data',
|
|
||||||
onSuccess: () => {
|
|
||||||
isFormEntryDialogOpen.value = false;
|
|
||||||
if (refresh) refresh();
|
|
||||||
},
|
|
||||||
onFinally: (isSuccess: boolean) => {
|
|
||||||
if (isSuccess) setTimeout(reset, 500);
|
|
||||||
isProcessing.value = false;
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function handleActionRemove(
|
|
||||||
id: number | string,
|
|
||||||
refresh: () => void,
|
|
||||||
toast: ToastFn
|
|
||||||
) {
|
|
||||||
isProcessing.value = true;
|
|
||||||
await handleAsyncAction<[number | string], any>({
|
|
||||||
action: removeMedicineGroup,
|
|
||||||
args: [id],
|
|
||||||
toast,
|
|
||||||
successMessage: 'Data berhasil dihapus',
|
|
||||||
errorMessage: 'Gagal menghapus data',
|
|
||||||
onSuccess: () => {
|
|
||||||
if (refresh) refresh();
|
|
||||||
},
|
|
||||||
onFinally: () => {
|
|
||||||
onResetState();
|
|
||||||
isProcessing.value = false;
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export function handleCancelForm(reset: () => void) {
|
|
||||||
isFormEntryDialogOpen.value = false
|
|
||||||
setTimeout(() => {
|
|
||||||
reset()
|
|
||||||
}, 500)
|
|
||||||
}
|
|
||||||
|
|
||||||
export { recId, recAction, recItem, isReadonly, isProcessing, isFormEntryDialogOpen, isRecordConfirmationOpen }
|
|
||||||
|
|||||||
@@ -1,101 +1,21 @@
|
|||||||
import { ref } from 'vue'
|
import { createCrudHandler } from '~/handlers/_handler'
|
||||||
|
|
||||||
// Handlers
|
|
||||||
import { type ToastFn, handleAsyncAction } from '~/handlers/_handler'
|
|
||||||
|
|
||||||
// Services
|
|
||||||
import { postMedicineMethod, patchMedicineMethod, removeMedicineMethod } from '~/services/medicine-method.service'
|
import { postMedicineMethod, patchMedicineMethod, removeMedicineMethod } from '~/services/medicine-method.service'
|
||||||
|
|
||||||
const recId = ref<number>(0)
|
export const {
|
||||||
const recAction = ref<string>('')
|
recId,
|
||||||
const recItem = ref<any>(null)
|
recAction,
|
||||||
const isReadonly = ref(false)
|
recItem,
|
||||||
const isProcessing = ref(false)
|
isReadonly,
|
||||||
const isFormEntryDialogOpen = ref(false)
|
isProcessing,
|
||||||
const isRecordConfirmationOpen = ref(false)
|
isFormEntryDialogOpen,
|
||||||
|
isRecordConfirmationOpen,
|
||||||
function onResetState() {
|
onResetState,
|
||||||
recId.value = 0
|
handleActionSave,
|
||||||
recAction.value = ''
|
handleActionEdit,
|
||||||
recItem.value = null
|
handleActionRemove,
|
||||||
}
|
handleCancelForm,
|
||||||
|
} = createCrudHandler({
|
||||||
export async function handleActionSave(
|
post: postMedicineMethod,
|
||||||
values: any,
|
patch: patchMedicineMethod,
|
||||||
refresh: () => void,
|
remove: removeMedicineMethod,
|
||||||
reset: () => void,
|
})
|
||||||
toast: ToastFn
|
|
||||||
) {
|
|
||||||
isProcessing.value = true;
|
|
||||||
await handleAsyncAction<[any], any>({
|
|
||||||
action: postMedicineMethod,
|
|
||||||
args: [values],
|
|
||||||
toast,
|
|
||||||
successMessage: 'Data berhasil disimpan',
|
|
||||||
errorMessage: 'Gagal menyimpan data',
|
|
||||||
onSuccess: () => {
|
|
||||||
isFormEntryDialogOpen.value = false;
|
|
||||||
if (refresh) refresh();
|
|
||||||
},
|
|
||||||
onFinally: (isSuccess: boolean) => {
|
|
||||||
if (isSuccess) setTimeout(reset, 500);
|
|
||||||
isProcessing.value = false;
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function handleActionEdit(
|
|
||||||
id: number | string,
|
|
||||||
values: any,
|
|
||||||
refresh: () => void,
|
|
||||||
reset: () => void,
|
|
||||||
toast: ToastFn
|
|
||||||
) {
|
|
||||||
isProcessing.value = true;
|
|
||||||
await handleAsyncAction<[number | string, any], any>({
|
|
||||||
action: patchMedicineMethod,
|
|
||||||
args: [id, values],
|
|
||||||
toast,
|
|
||||||
successMessage: 'Data berhasil diubah',
|
|
||||||
errorMessage: 'Gagal mengubah data',
|
|
||||||
onSuccess: () => {
|
|
||||||
isFormEntryDialogOpen.value = false;
|
|
||||||
if (refresh) refresh();
|
|
||||||
},
|
|
||||||
onFinally: (isSuccess: boolean) => {
|
|
||||||
if (isSuccess) setTimeout(reset, 500);
|
|
||||||
isProcessing.value = false;
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function handleActionRemove(
|
|
||||||
id: number | string,
|
|
||||||
refresh: () => void,
|
|
||||||
toast: ToastFn
|
|
||||||
) {
|
|
||||||
isProcessing.value = true;
|
|
||||||
await handleAsyncAction<[number | string], any>({
|
|
||||||
action: removeMedicineMethod,
|
|
||||||
args: [id],
|
|
||||||
toast,
|
|
||||||
successMessage: 'Data berhasil dihapus',
|
|
||||||
errorMessage: 'Gagal menghapus data',
|
|
||||||
onSuccess: () => {
|
|
||||||
if (refresh) refresh();
|
|
||||||
},
|
|
||||||
onFinally: () => {
|
|
||||||
onResetState();
|
|
||||||
isProcessing.value = false;
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export function handleCancelForm(reset: () => void) {
|
|
||||||
isFormEntryDialogOpen.value = false
|
|
||||||
setTimeout(() => {
|
|
||||||
reset()
|
|
||||||
}, 500)
|
|
||||||
}
|
|
||||||
|
|
||||||
export { recId, recAction, recItem, isReadonly, isProcessing, isFormEntryDialogOpen, isRecordConfirmationOpen }
|
|
||||||
|
|||||||
@@ -0,0 +1,21 @@
|
|||||||
|
import { createCrudHandler } from '~/handlers/_handler'
|
||||||
|
import { postMedicine, patchMedicine, removeMedicine } from '~/services/medicine.service'
|
||||||
|
|
||||||
|
export const {
|
||||||
|
recId,
|
||||||
|
recAction,
|
||||||
|
recItem,
|
||||||
|
isReadonly,
|
||||||
|
isProcessing,
|
||||||
|
isFormEntryDialogOpen,
|
||||||
|
isRecordConfirmationOpen,
|
||||||
|
onResetState,
|
||||||
|
handleActionSave,
|
||||||
|
handleActionEdit,
|
||||||
|
handleActionRemove,
|
||||||
|
handleCancelForm,
|
||||||
|
} = createCrudHandler({
|
||||||
|
post: postMedicine,
|
||||||
|
patch: patchMedicine,
|
||||||
|
remove: removeMedicine,
|
||||||
|
})
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
import { createCrudHandler } from '~/handlers/_handler'
|
||||||
|
import { postSpecialist, patchSpecialist, removeSpecialist } from '~/services/specialist.service'
|
||||||
|
|
||||||
|
export const {
|
||||||
|
recId,
|
||||||
|
recAction,
|
||||||
|
recItem,
|
||||||
|
isReadonly,
|
||||||
|
isProcessing,
|
||||||
|
isFormEntryDialogOpen,
|
||||||
|
isRecordConfirmationOpen,
|
||||||
|
onResetState,
|
||||||
|
handleActionSave,
|
||||||
|
handleActionEdit,
|
||||||
|
handleActionRemove,
|
||||||
|
handleCancelForm,
|
||||||
|
} = createCrudHandler({
|
||||||
|
post: postSpecialist,
|
||||||
|
patch: patchSpecialist,
|
||||||
|
remove: removeSpecialist,
|
||||||
|
})
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
import { createCrudHandler } from '~/handlers/_handler'
|
||||||
|
import { postSubspecialist, patchSubspecialist, removeSubspecialist } from '~/services/subspecialist.service'
|
||||||
|
|
||||||
|
export const {
|
||||||
|
recId,
|
||||||
|
recAction,
|
||||||
|
recItem,
|
||||||
|
isReadonly,
|
||||||
|
isProcessing,
|
||||||
|
isFormEntryDialogOpen,
|
||||||
|
isRecordConfirmationOpen,
|
||||||
|
onResetState,
|
||||||
|
handleActionSave,
|
||||||
|
handleActionEdit,
|
||||||
|
handleActionRemove,
|
||||||
|
handleCancelForm,
|
||||||
|
} = createCrudHandler({
|
||||||
|
post: postSubspecialist,
|
||||||
|
patch: patchSubspecialist,
|
||||||
|
remove: removeSubspecialist,
|
||||||
|
})
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
import { createCrudHandler } from '~/handlers/_handler'
|
||||||
|
import { postUnit, patchUnit, removeUnit } from '~/services/unit.service'
|
||||||
|
|
||||||
|
export const {
|
||||||
|
recId,
|
||||||
|
recAction,
|
||||||
|
recItem,
|
||||||
|
isReadonly,
|
||||||
|
isProcessing,
|
||||||
|
isFormEntryDialogOpen,
|
||||||
|
isRecordConfirmationOpen,
|
||||||
|
onResetState,
|
||||||
|
handleActionSave,
|
||||||
|
handleActionEdit,
|
||||||
|
handleActionRemove,
|
||||||
|
handleCancelForm,
|
||||||
|
} = createCrudHandler({
|
||||||
|
post: postUnit,
|
||||||
|
patch: patchUnit,
|
||||||
|
remove: removeUnit,
|
||||||
|
})
|
||||||
+19
-90
@@ -1,92 +1,21 @@
|
|||||||
import { ref } from 'vue'
|
import { createCrudHandler } from '~/handlers/_handler'
|
||||||
|
|
||||||
// Handlers
|
|
||||||
import { type ToastFn, handleAsyncAction } from '~/handlers/_handler'
|
|
||||||
|
|
||||||
// Services
|
|
||||||
import { postUom, patchUom, removeUom } from '~/services/uom.service'
|
import { postUom, patchUom, removeUom } from '~/services/uom.service'
|
||||||
|
|
||||||
const recId = ref<number>(0)
|
export const {
|
||||||
const recAction = ref<string>('')
|
recId,
|
||||||
const recItem = ref<any>(null)
|
recAction,
|
||||||
const isReadonly = ref(false)
|
recItem,
|
||||||
const isProcessing = ref(false)
|
isReadonly,
|
||||||
const isFormEntryDialogOpen = ref(false)
|
isProcessing,
|
||||||
const isRecordConfirmationOpen = ref(false)
|
isFormEntryDialogOpen,
|
||||||
|
isRecordConfirmationOpen,
|
||||||
function onResetState() {
|
onResetState,
|
||||||
recId.value = 0
|
handleActionSave,
|
||||||
recAction.value = ''
|
handleActionEdit,
|
||||||
recItem.value = null
|
handleActionRemove,
|
||||||
}
|
handleCancelForm,
|
||||||
|
} = createCrudHandler({
|
||||||
export async function handleActionSave(values: any, refresh: () => void, reset: () => void, toast: ToastFn) {
|
post: postUom,
|
||||||
isProcessing.value = true
|
patch: patchUom,
|
||||||
await handleAsyncAction<[any], any>({
|
remove: removeUom,
|
||||||
action: postUom,
|
})
|
||||||
args: [values],
|
|
||||||
toast,
|
|
||||||
successMessage: 'Data berhasil disimpan',
|
|
||||||
errorMessage: 'Gagal menyimpan data',
|
|
||||||
onSuccess: () => {
|
|
||||||
isFormEntryDialogOpen.value = false
|
|
||||||
if (refresh) refresh()
|
|
||||||
},
|
|
||||||
onFinally: (isSuccess: boolean) => {
|
|
||||||
if (isSuccess) setTimeout(reset, 500)
|
|
||||||
isProcessing.value = false
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function handleActionEdit(
|
|
||||||
id: number | string,
|
|
||||||
values: any,
|
|
||||||
refresh: () => void,
|
|
||||||
reset: () => void,
|
|
||||||
toast: ToastFn,
|
|
||||||
) {
|
|
||||||
isProcessing.value = true
|
|
||||||
await handleAsyncAction<[number | string, any], any>({
|
|
||||||
action: patchUom,
|
|
||||||
args: [id, values],
|
|
||||||
toast,
|
|
||||||
successMessage: 'Data berhasil diubah',
|
|
||||||
errorMessage: 'Gagal mengubah data',
|
|
||||||
onSuccess: () => {
|
|
||||||
isFormEntryDialogOpen.value = false
|
|
||||||
if (refresh) refresh()
|
|
||||||
},
|
|
||||||
onFinally: (isSuccess: boolean) => {
|
|
||||||
if (isSuccess) setTimeout(reset, 500)
|
|
||||||
isProcessing.value = false
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function handleActionRemove(id: number | string, refresh: () => void, toast: ToastFn) {
|
|
||||||
isProcessing.value = true
|
|
||||||
await handleAsyncAction<[number | string], any>({
|
|
||||||
action: removeUom,
|
|
||||||
args: [id],
|
|
||||||
toast,
|
|
||||||
successMessage: 'Data berhasil dihapus',
|
|
||||||
errorMessage: 'Gagal menghapus data',
|
|
||||||
onSuccess: () => {
|
|
||||||
if (refresh) refresh()
|
|
||||||
},
|
|
||||||
onFinally: () => {
|
|
||||||
onResetState()
|
|
||||||
isProcessing.value = false
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
export function handleCancelForm(reset: () => void) {
|
|
||||||
isFormEntryDialogOpen.value = false
|
|
||||||
setTimeout(() => {
|
|
||||||
reset()
|
|
||||||
}, 500)
|
|
||||||
}
|
|
||||||
|
|
||||||
export { recId, recAction, recItem, isReadonly, isProcessing, isFormEntryDialogOpen, isRecordConfirmationOpen }
|
|
||||||
|
|||||||
@@ -0,0 +1,10 @@
|
|||||||
|
export interface Division {
|
||||||
|
code: string
|
||||||
|
name: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface DivisionPosition {
|
||||||
|
code: string
|
||||||
|
name: string
|
||||||
|
division_id: number
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
export interface Installation {
|
||||||
|
code: string
|
||||||
|
name: string
|
||||||
|
encounterClass_code: string
|
||||||
|
}
|
||||||
+13
-25
@@ -4,30 +4,21 @@ export interface MedicineBase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface Medicine {
|
export interface Medicine {
|
||||||
id: string
|
|
||||||
name: string
|
|
||||||
code: string
|
code: string
|
||||||
|
name: string
|
||||||
medicineGroup_code: string
|
medicineGroup_code: string
|
||||||
medicineMethod_code: string
|
medicineMethod_code: string
|
||||||
uom_code: string
|
uom_code: string
|
||||||
type: string
|
infra_id?: string | null
|
||||||
dose: string
|
stock: number
|
||||||
infra_id: string
|
|
||||||
stock: string
|
|
||||||
status: string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CreateDto {
|
export interface CreateMedicineDto extends Medicine {
|
||||||
name: string
|
|
||||||
code: string
|
}
|
||||||
medicineGroup_code: string
|
|
||||||
medicineMethod_code: string
|
export interface UpdateMedicineDto extends CreateMedicineDto {
|
||||||
uom_code: string
|
id: string | number
|
||||||
type: string
|
|
||||||
dose: string
|
|
||||||
infra_id: string
|
|
||||||
stock: string
|
|
||||||
status: string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface GetListDto {
|
export interface GetListDto {
|
||||||
@@ -49,7 +40,7 @@ export interface GetDetailDto {
|
|||||||
id?: string
|
id?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface UpdateDto extends CreateDto {
|
export interface UpdateDto extends CreateMedicineDto {
|
||||||
id?: number
|
id?: number
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -57,17 +48,14 @@ export interface DeleteDto {
|
|||||||
id?: string
|
id?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export function genMedicine(): CreateDto {
|
export function genMedicine(): CreateMedicineDto {
|
||||||
return {
|
return {
|
||||||
name: 'name',
|
name: 'name',
|
||||||
code: 'code',
|
code: 'code',
|
||||||
medicineGroup_code: 'medicineGroup_code',
|
medicineGroup_code: 'medicineGroup_code',
|
||||||
medicineMethod_code: 'medicineMethod_code',
|
medicineMethod_code: 'medicineMethod_code',
|
||||||
uom_code: 'uom_code',
|
uom_code: 'uom_code',
|
||||||
type: 'type',
|
infra_id: null,
|
||||||
dose: 'dose',
|
stock: 0
|
||||||
infra_id: 'infra_id',
|
|
||||||
stock: 'stock',
|
|
||||||
status: 'status',
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,6 @@
|
|||||||
|
export interface Specialist {
|
||||||
|
id?: number
|
||||||
|
code: string
|
||||||
|
name: string
|
||||||
|
unit_id: number | string
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
export interface Subspecialist {
|
||||||
|
code: string
|
||||||
|
name: string
|
||||||
|
specialist_id: number | string
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
export interface Unit {
|
||||||
|
id?: number
|
||||||
|
code: string
|
||||||
|
name: string
|
||||||
|
installation?: string | number
|
||||||
|
}
|
||||||
@@ -7,7 +7,7 @@ definePageMeta({
|
|||||||
// middleware: ['rbac'],
|
// middleware: ['rbac'],
|
||||||
roles: ['doctor', 'nurse', 'admisi', 'pharmacy', 'billing', 'management'],
|
roles: ['doctor', 'nurse', 'admisi', 'pharmacy', 'billing', 'management'],
|
||||||
title: 'Daftar Divisi',
|
title: 'Daftar Divisi',
|
||||||
contentFrame: 'cf-full-width',
|
contentFrame: 'cf-container-lg',
|
||||||
})
|
})
|
||||||
|
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
|
|||||||
@@ -6,8 +6,8 @@ import Error from '~/components/pub/base/error/error.vue'
|
|||||||
definePageMeta({
|
definePageMeta({
|
||||||
// middleware: ['rbac'],
|
// middleware: ['rbac'],
|
||||||
roles: ['doctor', 'nurse', 'admisi', 'pharmacy', 'billing', 'management'],
|
roles: ['doctor', 'nurse', 'admisi', 'pharmacy', 'billing', 'management'],
|
||||||
title: 'List Specialist',
|
title: 'Daftar Specialist',
|
||||||
contentFrame: 'cf-full-width',
|
contentFrame: 'cf-container-lg',
|
||||||
})
|
})
|
||||||
|
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
|
|||||||
@@ -6,8 +6,8 @@ import Error from '~/components/pub/base/error/error.vue'
|
|||||||
definePageMeta({
|
definePageMeta({
|
||||||
// middleware: ['rbac'],
|
// middleware: ['rbac'],
|
||||||
roles: ['doctor', 'nurse', 'admisi', 'pharmacy', 'billing', 'management'],
|
roles: ['doctor', 'nurse', 'admisi', 'pharmacy', 'billing', 'management'],
|
||||||
title: 'List Specialist',
|
title: 'Daftar Subspecialist',
|
||||||
contentFrame: 'cf-full-width',
|
contentFrame: 'cf-container-lg',
|
||||||
})
|
})
|
||||||
|
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
|
|||||||
@@ -6,8 +6,8 @@ import Error from '~/components/pub/base/error/error.vue'
|
|||||||
definePageMeta({
|
definePageMeta({
|
||||||
// middleware: ['rbac'],
|
// middleware: ['rbac'],
|
||||||
roles: ['doctor', 'nurse', 'admisi', 'pharmacy', 'billing', 'management'],
|
roles: ['doctor', 'nurse', 'admisi', 'pharmacy', 'billing', 'management'],
|
||||||
title: 'List Unit',
|
title: 'Daftar Unit',
|
||||||
contentFrame: 'cf-full-width',
|
contentFrame: 'cf-container-lg',
|
||||||
})
|
})
|
||||||
|
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
|
|||||||
@@ -0,0 +1,12 @@
|
|||||||
|
import { z } from 'zod'
|
||||||
|
import type { Division, DivisionPosition } from '~/models/division'
|
||||||
|
|
||||||
|
const DivisionSchema = z.object({
|
||||||
|
code: z.string({ required_error: 'Kode harus diisi' }).min(1, 'Kode minimum 1 karakter'),
|
||||||
|
name: z.string({ required_error: 'Nama harus diisi' }).min(1, 'Nama minimum 1 karakter'),
|
||||||
|
})
|
||||||
|
|
||||||
|
type DivisionFormData = z.infer<typeof DivisionSchema> & (Division | DivisionPosition)
|
||||||
|
|
||||||
|
export { DivisionSchema }
|
||||||
|
export type { DivisionFormData }
|
||||||
@@ -1,12 +1,13 @@
|
|||||||
import { z } from 'zod'
|
import { z } from 'zod'
|
||||||
import type { MedicineBase } from '~/models/medicine'
|
|
||||||
|
|
||||||
const MedicineBaseSchema = z.object({
|
export const MedicineSchema = z.object({
|
||||||
code: z.string({ required_error: 'Kode harus diisi' }).min(1, 'Kode minimum 1 karakter'),
|
code: z.string({ required_error: 'Kode harus diisi' }).min(1, 'Kode minimal 1 karakter'),
|
||||||
name: z.string({ required_error: 'Nama harus diisi' }).min(1, 'Nama minimum 1 karakter')
|
name: z.string({ required_error: 'Nama harus diisi' }).min(1, 'Nama minimal 1 karakter'),
|
||||||
|
medicineGroup_code: z.string({ required_error: 'Kelompok obat harus diisi' }).min(1, 'Kelompok obat harus diisi'),
|
||||||
|
medicineMethod_code: z.string({ required_error: 'Metode pemberian harus diisi' }).min(1, 'Metode pemberian harus diisi'),
|
||||||
|
uom_code: z.string({ required_error: 'Satuan harus diisi' }).min(1, 'Satuan harus diisi'),
|
||||||
|
infra_id: z.number().nullable().optional(),
|
||||||
|
stock: z.preprocess((val) => Number(val), z.number({ invalid_type_error: 'Stok harus berupa angka' }).min(1, 'Stok harus lebih besar dari 0')),
|
||||||
})
|
})
|
||||||
|
|
||||||
type MedicineBaseFormData = z.infer<typeof MedicineBaseSchema> & MedicineBase
|
export type MedicineFormData = z.infer<typeof MedicineSchema>
|
||||||
|
|
||||||
export { MedicineBaseSchema }
|
|
||||||
export type { MedicineBaseFormData }
|
|
||||||
|
|||||||
@@ -0,0 +1,13 @@
|
|||||||
|
import { z } from 'zod'
|
||||||
|
import type { Specialist } from '~/models/specialist'
|
||||||
|
|
||||||
|
const SpecialistSchema = z.object({
|
||||||
|
code: z.string({ required_error: 'Kode harus diisi' }).min(1, 'Kode minimum 1 karakter'),
|
||||||
|
name: z.string({ required_error: 'Nama harus diisi' }).min(1, 'Nama minimum 1 karakter'),
|
||||||
|
unit_id: z.number().positive('Unit harus diisi'),
|
||||||
|
})
|
||||||
|
|
||||||
|
type SpecialistFormData = z.infer<typeof SpecialistSchema> & Specialist
|
||||||
|
|
||||||
|
export { SpecialistSchema }
|
||||||
|
export type { SpecialistFormData }
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
import { z } from 'zod'
|
||||||
|
import type { Subspecialist } from '~/models/subspecialist'
|
||||||
|
|
||||||
|
const SubspecialistSchema = z.object({
|
||||||
|
code: z.string({ required_error: 'Kode harus diisi' }).min(1, 'Kode minimum 1 karakter'),
|
||||||
|
name: z.string({ required_error: 'Nama harus diisi' }).min(1, 'Nama minimum 1 karakter'),
|
||||||
|
specialist_id: z.number().positive('Spesialis harus diisi'),
|
||||||
|
})
|
||||||
|
|
||||||
|
type SubspecialistFormData = z.infer<typeof SubspecialistSchema> & Subspecialist
|
||||||
|
|
||||||
|
export { SubspecialistSchema }
|
||||||
|
export type { SubspecialistFormData }
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
import { z } from 'zod'
|
||||||
|
import type { Unit } from '~/models/unit'
|
||||||
|
|
||||||
|
const UnitSchema = z.object({
|
||||||
|
code: z.string({ required_error: 'Kode harus diisi' }).min(1, 'Kode minimum 1 karakter'),
|
||||||
|
name: z.string({ required_error: 'Nama harus diisi' }).min(1, 'Nama minimum 1 karakter'),
|
||||||
|
})
|
||||||
|
|
||||||
|
type UnitFormData = z.infer<typeof UnitSchema> & Unit
|
||||||
|
|
||||||
|
export { UnitSchema }
|
||||||
|
export type { UnitFormData }
|
||||||
@@ -0,0 +1,79 @@
|
|||||||
|
import { xfetch } from '~/composables/useXfetch'
|
||||||
|
|
||||||
|
const mainUrl = '/api/v1/division-position'
|
||||||
|
|
||||||
|
export async function getDivisionPositions(params: any = null) {
|
||||||
|
try {
|
||||||
|
let url = mainUrl
|
||||||
|
if (params && typeof params === 'object' && Object.keys(params).length > 0) {
|
||||||
|
const searchParams = new URLSearchParams()
|
||||||
|
for (const key in params) {
|
||||||
|
if (params[key] !== null && params[key] !== undefined && params[key] !== '') {
|
||||||
|
searchParams.append(key, params[key])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const queryString = searchParams.toString()
|
||||||
|
if (queryString) url += `?${queryString}`
|
||||||
|
}
|
||||||
|
const resp = await xfetch(mainUrl, 'GET')
|
||||||
|
const result: any = {}
|
||||||
|
result.success = resp.success
|
||||||
|
result.body = (resp.body as Record<string, any>) || {}
|
||||||
|
return result
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error fetching division-positions:', error)
|
||||||
|
throw new Error('Failed to fetch division-positions')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getDivisionPositionDetail(id: number | string) {
|
||||||
|
try {
|
||||||
|
const resp = await xfetch(`${mainUrl}/${id}`, 'GET')
|
||||||
|
const result: any = {}
|
||||||
|
result.success = resp.success
|
||||||
|
result.body = (resp.body as Record<string, any>) || {}
|
||||||
|
return result
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error fetching division-position detail:', error)
|
||||||
|
throw new Error('Failed to get division-position detail')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function postDivisionPosition(record: any) {
|
||||||
|
try {
|
||||||
|
const resp = await xfetch(mainUrl, 'POST', record)
|
||||||
|
const result: any = {}
|
||||||
|
result.success = resp.success
|
||||||
|
result.body = (resp.body as Record<string, any>) || {}
|
||||||
|
return result
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error posting division-position:', error)
|
||||||
|
throw new Error('Failed to post division-position')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function patchDivisionPosition(id: number | string, record: any) {
|
||||||
|
try {
|
||||||
|
const resp = await xfetch(`${mainUrl}/${id}`, 'PATCH', record)
|
||||||
|
const result: any = {}
|
||||||
|
result.success = resp.success
|
||||||
|
result.body = (resp.body as Record<string, any>) || {}
|
||||||
|
return result
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error putting division-position:', error)
|
||||||
|
throw new Error('Failed to put division-position')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function removeDivisionPosition(id: number | string) {
|
||||||
|
try {
|
||||||
|
const resp = await xfetch(`${mainUrl}/${id}`, 'DELETE')
|
||||||
|
const result: any = {}
|
||||||
|
result.success = resp.success
|
||||||
|
result.body = (resp.body as Record<string, any>) || {}
|
||||||
|
return result
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error deleting record:', error)
|
||||||
|
throw new Error('Failed to delete division-position')
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,79 @@
|
|||||||
|
import { xfetch } from '~/composables/useXfetch'
|
||||||
|
|
||||||
|
const mainUrl = '/api/v1/division'
|
||||||
|
|
||||||
|
export async function getDivisions(params: any = null) {
|
||||||
|
try {
|
||||||
|
let url = mainUrl
|
||||||
|
if (params && typeof params === 'object' && Object.keys(params).length > 0) {
|
||||||
|
const searchParams = new URLSearchParams()
|
||||||
|
for (const key in params) {
|
||||||
|
if (params[key] !== null && params[key] !== undefined && params[key] !== '') {
|
||||||
|
searchParams.append(key, params[key])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const queryString = searchParams.toString()
|
||||||
|
if (queryString) url += `?${queryString}`
|
||||||
|
}
|
||||||
|
const resp = await xfetch(mainUrl, 'GET')
|
||||||
|
const result: any = {}
|
||||||
|
result.success = resp.success
|
||||||
|
result.body = (resp.body as Record<string, any>) || {}
|
||||||
|
return result
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error fetching divisions:', error)
|
||||||
|
throw new Error('Failed to fetch divisions')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getDivisionDetail(id: number | string) {
|
||||||
|
try {
|
||||||
|
const resp = await xfetch(`${mainUrl}/${id}`, 'GET')
|
||||||
|
const result: any = {}
|
||||||
|
result.success = resp.success
|
||||||
|
result.body = (resp.body as Record<string, any>) || {}
|
||||||
|
return result
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error fetching division detail:', error)
|
||||||
|
throw new Error('Failed to get division detail')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function postDivision(record: any) {
|
||||||
|
try {
|
||||||
|
const resp = await xfetch(mainUrl, 'POST', record)
|
||||||
|
const result: any = {}
|
||||||
|
result.success = resp.success
|
||||||
|
result.body = (resp.body as Record<string, any>) || {}
|
||||||
|
return result
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error posting division:', error)
|
||||||
|
throw new Error('Failed to post division')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function patchDivision(id: number | string, record: any) {
|
||||||
|
try {
|
||||||
|
const resp = await xfetch(`${mainUrl}/${id}`, 'PATCH', record)
|
||||||
|
const result: any = {}
|
||||||
|
result.success = resp.success
|
||||||
|
result.body = (resp.body as Record<string, any>) || {}
|
||||||
|
return result
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error putting division:', error)
|
||||||
|
throw new Error('Failed to put division')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function removeDivision(id: number | string) {
|
||||||
|
try {
|
||||||
|
const resp = await xfetch(`${mainUrl}/${id}`, 'DELETE')
|
||||||
|
const result: any = {}
|
||||||
|
result.success = resp.success
|
||||||
|
result.body = (resp.body as Record<string, any>) || {}
|
||||||
|
return result
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error deleting record:', error)
|
||||||
|
throw new Error('Failed to delete division')
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,79 @@
|
|||||||
|
import { xfetch } from '~/composables/useXfetch'
|
||||||
|
|
||||||
|
const mainUrl = '/api/v1/installation'
|
||||||
|
|
||||||
|
export async function getInstallations(params: any = null) {
|
||||||
|
try {
|
||||||
|
let url = mainUrl
|
||||||
|
if (params && typeof params === 'object' && Object.keys(params).length > 0) {
|
||||||
|
const searchParams = new URLSearchParams()
|
||||||
|
for (const key in params) {
|
||||||
|
if (params[key] !== null && params[key] !== undefined && params[key] !== '') {
|
||||||
|
searchParams.append(key, params[key])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const queryString = searchParams.toString()
|
||||||
|
if (queryString) url += `?${queryString}`
|
||||||
|
}
|
||||||
|
const resp = await xfetch(mainUrl, 'GET')
|
||||||
|
const result: any = {}
|
||||||
|
result.success = resp.success
|
||||||
|
result.body = (resp.body as Record<string, any>) || {}
|
||||||
|
return result
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error fetching installations:', error)
|
||||||
|
throw new Error('Failed to fetch installations')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getInstallationDetail(id: number | string) {
|
||||||
|
try {
|
||||||
|
const resp = await xfetch(`${mainUrl}/${id}`, 'GET')
|
||||||
|
const result: any = {}
|
||||||
|
result.success = resp.success
|
||||||
|
result.body = (resp.body as Record<string, any>) || {}
|
||||||
|
return result
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error fetching installation detail:', error)
|
||||||
|
throw new Error('Failed to get installation detail')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function postInstallation(record: any) {
|
||||||
|
try {
|
||||||
|
const resp = await xfetch(mainUrl, 'POST', record)
|
||||||
|
const result: any = {}
|
||||||
|
result.success = resp.success
|
||||||
|
result.body = (resp.body as Record<string, any>) || {}
|
||||||
|
return result
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error posting installation:', error)
|
||||||
|
throw new Error('Failed to post installation')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function patchInstallation(id: number | string, record: any) {
|
||||||
|
try {
|
||||||
|
const resp = await xfetch(`${mainUrl}/${id}`, 'PATCH', record)
|
||||||
|
const result: any = {}
|
||||||
|
result.success = resp.success
|
||||||
|
result.body = (resp.body as Record<string, any>) || {}
|
||||||
|
return result
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error putting installation:', error)
|
||||||
|
throw new Error('Failed to put installation')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function removeInstallation(id: number | string) {
|
||||||
|
try {
|
||||||
|
const resp = await xfetch(`${mainUrl}/${id}`, 'DELETE')
|
||||||
|
const result: any = {}
|
||||||
|
result.success = resp.success
|
||||||
|
result.body = (resp.body as Record<string, any>) || {}
|
||||||
|
return result
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error deleting record:', error)
|
||||||
|
throw new Error('Failed to delete installation')
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,79 @@
|
|||||||
|
import { xfetch } from '~/composables/useXfetch'
|
||||||
|
|
||||||
|
const mainUrl = '/api/v1/medicine'
|
||||||
|
|
||||||
|
export async function getMedicines(params: any = null) {
|
||||||
|
try {
|
||||||
|
let url = mainUrl
|
||||||
|
if (params && typeof params === 'object' && Object.keys(params).length > 0) {
|
||||||
|
const searchParams = new URLSearchParams()
|
||||||
|
for (const key in params) {
|
||||||
|
if (params[key] !== null && params[key] !== undefined && params[key] !== '') {
|
||||||
|
searchParams.append(key, params[key])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const queryString = searchParams.toString()
|
||||||
|
if (queryString) url += `?${queryString}`
|
||||||
|
}
|
||||||
|
const resp = await xfetch(url, 'GET')
|
||||||
|
const result: any = {}
|
||||||
|
result.success = resp.success
|
||||||
|
result.body = (resp.body as Record<string, any>) || {}
|
||||||
|
return result
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error fetching medicines:', error)
|
||||||
|
throw new Error('Failed to fetch medicines')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getMedicineDetail(id: number | string) {
|
||||||
|
try {
|
||||||
|
const resp = await xfetch(`${mainUrl}/${id}`, 'GET')
|
||||||
|
const result: any = {}
|
||||||
|
result.success = resp.success
|
||||||
|
result.body = (resp.body as Record<string, any>) || {}
|
||||||
|
return result
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error fetching medicine detail:', error)
|
||||||
|
throw new Error('Failed to get medicine detail')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function postMedicine(record: any) {
|
||||||
|
try {
|
||||||
|
const resp = await xfetch(mainUrl, 'POST', record)
|
||||||
|
const result: any = {}
|
||||||
|
result.success = resp.success
|
||||||
|
result.body = (resp.body as Record<string, any>) || {}
|
||||||
|
return result
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error posting medicine:', error)
|
||||||
|
throw new Error('Failed to post medicine')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function patchMedicine(id: number | string, record: any) {
|
||||||
|
try {
|
||||||
|
const resp = await xfetch(`${mainUrl}/${id}`, 'PATCH', record)
|
||||||
|
const result: any = {}
|
||||||
|
result.success = resp.success
|
||||||
|
result.body = (resp.body as Record<string, any>) || {}
|
||||||
|
return result
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error patching medicine:', error)
|
||||||
|
throw new Error('Failed to patch medicine')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function removeMedicine(id: number | string) {
|
||||||
|
try {
|
||||||
|
const resp = await xfetch(`${mainUrl}/${id}`, 'DELETE')
|
||||||
|
const result: any = {}
|
||||||
|
result.success = resp.success
|
||||||
|
result.body = (resp.body as Record<string, any>) || {}
|
||||||
|
return result
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error deleting medicine:', error)
|
||||||
|
throw new Error('Failed to delete medicine')
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,79 @@
|
|||||||
|
import { xfetch } from '~/composables/useXfetch'
|
||||||
|
|
||||||
|
const mainUrl = '/api/v1/specialist'
|
||||||
|
|
||||||
|
export async function getSpecialists(params: any = null) {
|
||||||
|
try {
|
||||||
|
let url = mainUrl
|
||||||
|
if (params && typeof params === 'object' && Object.keys(params).length > 0) {
|
||||||
|
const searchParams = new URLSearchParams()
|
||||||
|
for (const key in params) {
|
||||||
|
if (params[key] !== null && params[key] !== undefined && params[key] !== '') {
|
||||||
|
searchParams.append(key, params[key])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const queryString = searchParams.toString()
|
||||||
|
if (queryString) url += `?${queryString}`
|
||||||
|
}
|
||||||
|
const resp = await xfetch(mainUrl, 'GET')
|
||||||
|
const result: any = {}
|
||||||
|
result.success = resp.success
|
||||||
|
result.body = (resp.body as Record<string, any>) || {}
|
||||||
|
return result
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error fetching specialists:', error)
|
||||||
|
throw new Error('Failed to fetch specialists')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getSpecialistDetail(id: number | string) {
|
||||||
|
try {
|
||||||
|
const resp = await xfetch(`${mainUrl}/${id}`, 'GET')
|
||||||
|
const result: any = {}
|
||||||
|
result.success = resp.success
|
||||||
|
result.body = (resp.body as Record<string, any>) || {}
|
||||||
|
return result
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error fetching specialist detail:', error)
|
||||||
|
throw new Error('Failed to get specialist detail')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function postSpecialist(record: any) {
|
||||||
|
try {
|
||||||
|
const resp = await xfetch(mainUrl, 'POST', record)
|
||||||
|
const result: any = {}
|
||||||
|
result.success = resp.success
|
||||||
|
result.body = (resp.body as Record<string, any>) || {}
|
||||||
|
return result
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error posting specialist:', error)
|
||||||
|
throw new Error('Failed to post specialist')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function patchSpecialist(id: number | string, record: any) {
|
||||||
|
try {
|
||||||
|
const resp = await xfetch(`${mainUrl}/${id}`, 'PATCH', record)
|
||||||
|
const result: any = {}
|
||||||
|
result.success = resp.success
|
||||||
|
result.body = (resp.body as Record<string, any>) || {}
|
||||||
|
return result
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error putting specialist:', error)
|
||||||
|
throw new Error('Failed to put specialist')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function removeSpecialist(id: number | string) {
|
||||||
|
try {
|
||||||
|
const resp = await xfetch(`${mainUrl}/${id}`, 'DELETE')
|
||||||
|
const result: any = {}
|
||||||
|
result.success = resp.success
|
||||||
|
result.body = (resp.body as Record<string, any>) || {}
|
||||||
|
return result
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error deleting record:', error)
|
||||||
|
throw new Error('Failed to delete specialist')
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,79 @@
|
|||||||
|
import { xfetch } from '~/composables/useXfetch'
|
||||||
|
|
||||||
|
const mainUrl = '/api/v1/subspecialist'
|
||||||
|
|
||||||
|
export async function getSubspecialists(params: any = null) {
|
||||||
|
try {
|
||||||
|
let url = mainUrl
|
||||||
|
if (params && typeof params === 'object' && Object.keys(params).length > 0) {
|
||||||
|
const searchParams = new URLSearchParams()
|
||||||
|
for (const key in params) {
|
||||||
|
if (params[key] !== null && params[key] !== undefined && params[key] !== '') {
|
||||||
|
searchParams.append(key, params[key])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const queryString = searchParams.toString()
|
||||||
|
if (queryString) url += `?${queryString}`
|
||||||
|
}
|
||||||
|
const resp = await xfetch(mainUrl, 'GET')
|
||||||
|
const result: any = {}
|
||||||
|
result.success = resp.success
|
||||||
|
result.body = (resp.body as Record<string, any>) || {}
|
||||||
|
return result
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error fetching subspecialists:', error)
|
||||||
|
throw new Error('Failed to fetch subspecialists')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getSubspecialistDetail(id: number | string) {
|
||||||
|
try {
|
||||||
|
const resp = await xfetch(`${mainUrl}/${id}`, 'GET')
|
||||||
|
const result: any = {}
|
||||||
|
result.success = resp.success
|
||||||
|
result.body = (resp.body as Record<string, any>) || {}
|
||||||
|
return result
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error fetching subspecialist detail:', error)
|
||||||
|
throw new Error('Failed to get subspecialist detail')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function postSubspecialist(record: any) {
|
||||||
|
try {
|
||||||
|
const resp = await xfetch(mainUrl, 'POST', record)
|
||||||
|
const result: any = {}
|
||||||
|
result.success = resp.success
|
||||||
|
result.body = (resp.body as Record<string, any>) || {}
|
||||||
|
return result
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error posting subspecialist:', error)
|
||||||
|
throw new Error('Failed to post subspecialist')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function patchSubspecialist(id: number | string, record: any) {
|
||||||
|
try {
|
||||||
|
const resp = await xfetch(`${mainUrl}/${id}`, 'PATCH', record)
|
||||||
|
const result: any = {}
|
||||||
|
result.success = resp.success
|
||||||
|
result.body = (resp.body as Record<string, any>) || {}
|
||||||
|
return result
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error putting subspecialist:', error)
|
||||||
|
throw new Error('Failed to put subspecialist')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function removeSubspecialist(id: number | string) {
|
||||||
|
try {
|
||||||
|
const resp = await xfetch(`${mainUrl}/${id}`, 'DELETE')
|
||||||
|
const result: any = {}
|
||||||
|
result.success = resp.success
|
||||||
|
result.body = (resp.body as Record<string, any>) || {}
|
||||||
|
return result
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error deleting record:', error)
|
||||||
|
throw new Error('Failed to delete subspecialist')
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,79 @@
|
|||||||
|
import { xfetch } from '~/composables/useXfetch'
|
||||||
|
|
||||||
|
const mainUrl = '/api/v1/unit'
|
||||||
|
|
||||||
|
export async function getUnits(params: any = null) {
|
||||||
|
try {
|
||||||
|
let url = mainUrl
|
||||||
|
if (params && typeof params === 'object' && Object.keys(params).length > 0) {
|
||||||
|
const searchParams = new URLSearchParams()
|
||||||
|
for (const key in params) {
|
||||||
|
if (params[key] !== null && params[key] !== undefined && params[key] !== '') {
|
||||||
|
searchParams.append(key, params[key])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const queryString = searchParams.toString()
|
||||||
|
if (queryString) url += `?${queryString}`
|
||||||
|
}
|
||||||
|
const resp = await xfetch(mainUrl, 'GET')
|
||||||
|
const result: any = {}
|
||||||
|
result.success = resp.success
|
||||||
|
result.body = (resp.body as Record<string, any>) || {}
|
||||||
|
return result
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error fetching units:', error)
|
||||||
|
throw new Error('Failed to fetch units')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getUnitDetail(id: number | string) {
|
||||||
|
try {
|
||||||
|
const resp = await xfetch(`${mainUrl}/${id}`, 'GET')
|
||||||
|
const result: any = {}
|
||||||
|
result.success = resp.success
|
||||||
|
result.body = (resp.body as Record<string, any>) || {}
|
||||||
|
return result
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error fetching unit detail:', error)
|
||||||
|
throw new Error('Failed to get unit detail')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function postUnit(record: any) {
|
||||||
|
try {
|
||||||
|
const resp = await xfetch(mainUrl, 'POST', record)
|
||||||
|
const result: any = {}
|
||||||
|
result.success = resp.success
|
||||||
|
result.body = (resp.body as Record<string, any>) || {}
|
||||||
|
return result
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error posting unit:', error)
|
||||||
|
throw new Error('Failed to post unit')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function patchUnit(id: number | string, record: any) {
|
||||||
|
try {
|
||||||
|
const resp = await xfetch(`${mainUrl}/${id}`, 'PATCH', record)
|
||||||
|
const result: any = {}
|
||||||
|
result.success = resp.success
|
||||||
|
result.body = (resp.body as Record<string, any>) || {}
|
||||||
|
return result
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error putting unit:', error)
|
||||||
|
throw new Error('Failed to put unit')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function removeUnit(id: number | string) {
|
||||||
|
try {
|
||||||
|
const resp = await xfetch(`${mainUrl}/${id}`, 'DELETE')
|
||||||
|
const result: any = {}
|
||||||
|
result.success = resp.success
|
||||||
|
result.body = (resp.body as Record<string, any>) || {}
|
||||||
|
return result
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error deleting record:', error)
|
||||||
|
throw new Error('Failed to delete unit')
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user