feat: Implement encounter list with filtering and navigation components.
This commit is contained in:
@@ -0,0 +1,114 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import type { FormErrors } from '~/types/error'
|
||||||
|
import { toTypedSchema } from '@vee-validate/zod'
|
||||||
|
import FieldGroup from '~/components/pub/my-ui/form/field-group.vue'
|
||||||
|
import Field from '~/components/pub/my-ui/form/field.vue'
|
||||||
|
import Label from '~/components/pub/my-ui/form/label.vue'
|
||||||
|
import Select from '~/components/pub/my-ui/form/select.vue'
|
||||||
|
import { Form } from '~/components/pub/ui/form'
|
||||||
|
|
||||||
|
interface InstallationFormData {
|
||||||
|
name: string
|
||||||
|
code: string
|
||||||
|
encounterClassCode: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
installation: {
|
||||||
|
msg: {
|
||||||
|
placeholder: string
|
||||||
|
}
|
||||||
|
items: {
|
||||||
|
value: string
|
||||||
|
label: string
|
||||||
|
code: string
|
||||||
|
}[]
|
||||||
|
}
|
||||||
|
schema: any
|
||||||
|
initialValues?: Partial<InstallationFormData>
|
||||||
|
errors?: FormErrors
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
submit: [values: InstallationFormData, resetForm: () => void]
|
||||||
|
cancel: [resetForm: () => void]
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const formSchema = toTypedSchema(props.schema)
|
||||||
|
|
||||||
|
// Form submission handler
|
||||||
|
function onSubmitForm(values: any, { resetForm }: { resetForm: () => void }) {
|
||||||
|
const formData: InstallationFormData = {
|
||||||
|
name: values.name || '',
|
||||||
|
code: values.code || '',
|
||||||
|
encounterClassCode: values.encounterClassCode || '',
|
||||||
|
}
|
||||||
|
emit('submit', formData, resetForm)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Form cancel handler
|
||||||
|
function onCancelForm({ resetForm }: { resetForm: () => void }) {
|
||||||
|
emit('cancel', resetForm)
|
||||||
|
}
|
||||||
|
|
||||||
|
const items = ref([
|
||||||
|
{ label: 'Rujukan Internal', value: 'ri' },
|
||||||
|
{ label: 'SEP Rujukan', value: 'sr' },
|
||||||
|
])
|
||||||
|
</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="parentId">Cara Bayar</Label>
|
||||||
|
<Field id="encounterClassCode" :errors="errors">
|
||||||
|
<FormField v-slot="{ componentField }" name="encounterClassCode">
|
||||||
|
<FormItem>
|
||||||
|
<FormControl>
|
||||||
|
<Select v-bind="componentField" :items="items" />
|
||||||
|
</FormControl>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
</FormField>
|
||||||
|
</Field>
|
||||||
|
</FieldGroup>
|
||||||
|
<FieldGroup>
|
||||||
|
<Label label-for="parentId">Poliklinik</Label>
|
||||||
|
<Field id="encounterClassCode" :errors="errors">
|
||||||
|
<FormField v-slot="{ componentField }" name="encounterClassCode">
|
||||||
|
<FormItem>
|
||||||
|
<FormControl>
|
||||||
|
<Select v-bind="componentField" :items="items" />
|
||||||
|
</FormControl>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
</FormField>
|
||||||
|
</Field>
|
||||||
|
</FieldGroup>
|
||||||
|
<FieldGroup>
|
||||||
|
<Label label-for="parentId">Kunjungan</Label>
|
||||||
|
<Field id="encounterClassCode" :errors="errors">
|
||||||
|
<FormField v-slot="{ componentField }" name="encounterClassCode">
|
||||||
|
<FormItem>
|
||||||
|
<FormControl>
|
||||||
|
<Select v-bind="componentField" :items="items" />
|
||||||
|
</FormControl>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
</FormField>
|
||||||
|
</Field>
|
||||||
|
</FieldGroup>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</Form>
|
||||||
|
</template>
|
||||||
@@ -1,114 +1,107 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import type { FormErrors } from '~/types/error'
|
// Components
|
||||||
import { toTypedSchema } from '@vee-validate/zod'
|
import * as DE from '~/components/pub/my-ui/doc-entry'
|
||||||
import FieldGroup from '~/components/pub/my-ui/form/field-group.vue'
|
|
||||||
import Field from '~/components/pub/my-ui/form/field.vue'
|
|
||||||
import Label from '~/components/pub/my-ui/form/label.vue'
|
|
||||||
import Select from '~/components/pub/my-ui/form/select.vue'
|
|
||||||
import { Form } from '~/components/pub/ui/form'
|
|
||||||
|
|
||||||
interface InstallationFormData {
|
|
||||||
name: string
|
|
||||||
code: string
|
|
||||||
encounterClassCode: string
|
|
||||||
}
|
|
||||||
|
|
||||||
|
// Props
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
installation: {
|
payments: any[]
|
||||||
msg: {
|
units: any[]
|
||||||
placeholder: string
|
visits: any[]
|
||||||
}
|
|
||||||
items: {
|
|
||||||
value: string
|
|
||||||
label: string
|
|
||||||
code: string
|
|
||||||
}[]
|
|
||||||
}
|
|
||||||
schema: any
|
|
||||||
initialValues?: Partial<InstallationFormData>
|
|
||||||
errors?: FormErrors
|
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
submit: [values: InstallationFormData, resetForm: () => void]
|
(e: 'search', values: any): void
|
||||||
cancel: [resetForm: () => void]
|
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
const formSchema = toTypedSchema(props.schema)
|
const paymentItem = ref<any>(null)
|
||||||
|
const unitItem = ref<any>(null)
|
||||||
|
const visitItem = ref<any>(null)
|
||||||
|
|
||||||
// Form submission handler
|
function handleReset() {
|
||||||
function onSubmitForm(values: any, { resetForm }: { resetForm: () => void }) {
|
paymentItem.value = null
|
||||||
const formData: InstallationFormData = {
|
unitItem.value = null
|
||||||
name: values.name || '',
|
visitItem.value = null
|
||||||
code: values.code || '',
|
|
||||||
encounterClassCode: values.encounterClassCode || '',
|
|
||||||
}
|
|
||||||
emit('submit', formData, resetForm)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Form cancel handler
|
function handleSearch() {
|
||||||
function onCancelForm({ resetForm }: { resetForm: () => void }) {
|
emit('search', {
|
||||||
emit('cancel', resetForm)
|
paymentMethodCode: paymentItem.value,
|
||||||
|
unit: unitItem.value,
|
||||||
|
visit: visitItem.value,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const items = ref([
|
|
||||||
{ label: 'Rujukan Internal', value: 'ri' },
|
|
||||||
{ label: 'SEP Rujukan', value: 'sr' },
|
|
||||||
])
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<Form
|
<div class="mx-auto w-full">
|
||||||
v-slot="{ handleSubmit, resetForm }"
|
<DE.Block
|
||||||
as=""
|
labelSize="thin"
|
||||||
keep-values
|
class="!pt-0"
|
||||||
:validation-schema="formSchema"
|
:colCount="1"
|
||||||
:initial-values="initialValues"
|
:cellFlex="false"
|
||||||
>
|
>
|
||||||
<form id="entry-form" @submit="handleSubmit($event, (values) => onSubmitForm(values, { resetForm }))">
|
<DE.Cell>
|
||||||
<div class="mb-5 border-b border-b-slate-300 pb-3 text-lg xl:text-xl">
|
<DE.Label height="compact">Cara Bayar</DE.Label>
|
||||||
<div class="flex flex-col justify-between">
|
<DE.Field>
|
||||||
<FieldGroup>
|
<Select
|
||||||
<Label label-for="parentId">Cara Bayar</Label>
|
id="paymentMethodCode"
|
||||||
<Field id="encounterClassCode" :errors="errors">
|
v-model="paymentItem"
|
||||||
<FormField v-slot="{ componentField }" name="encounterClassCode">
|
:items="payments || []"
|
||||||
<FormItem>
|
placeholder="Pilih Jenis Pembayaran"
|
||||||
<FormControl>
|
/>
|
||||||
<Select v-bind="componentField" :items="items" />
|
</DE.Field>
|
||||||
</FormControl>
|
</DE.Cell>
|
||||||
<FormMessage />
|
|
||||||
</FormItem>
|
<DE.Cell>
|
||||||
</FormField>
|
<DE.Label height="compact">Poliklinik</DE.Label>
|
||||||
</Field>
|
<DE.Field>
|
||||||
</FieldGroup>
|
<Select
|
||||||
<FieldGroup>
|
id="unit"
|
||||||
<Label label-for="parentId">Poliklinik</Label>
|
v-model="unitItem"
|
||||||
<Field id="encounterClassCode" :errors="errors">
|
:items="units || []"
|
||||||
<FormField v-slot="{ componentField }" name="encounterClassCode">
|
placeholder="Pilih Poliklinik"
|
||||||
<FormItem>
|
/>
|
||||||
<FormControl>
|
</DE.Field>
|
||||||
<Select v-bind="componentField" :items="items" />
|
</DE.Cell>
|
||||||
</FormControl>
|
|
||||||
<FormMessage />
|
<DE.Cell>
|
||||||
</FormItem>
|
<DE.Label height="compact">Kunjungan</DE.Label>
|
||||||
</FormField>
|
<DE.Field>
|
||||||
</Field>
|
<Select
|
||||||
</FieldGroup>
|
id="visit"
|
||||||
<FieldGroup>
|
v-model="visitItem"
|
||||||
<Label label-for="parentId">Kunjungan</Label>
|
:items="visits || []"
|
||||||
<Field id="encounterClassCode" :errors="errors">
|
placeholder="Pilih Kunjungan"
|
||||||
<FormField v-slot="{ componentField }" name="encounterClassCode">
|
/>
|
||||||
<FormItem>
|
</DE.Field>
|
||||||
<FormControl>
|
</DE.Cell>
|
||||||
<Select v-bind="componentField" :items="items" />
|
</DE.Block>
|
||||||
</FormControl>
|
|
||||||
<FormMessage />
|
<div class="flex w-full justify-end gap-2">
|
||||||
</FormItem>
|
<Button
|
||||||
</FormField>
|
variant="outline"
|
||||||
</Field>
|
type="button"
|
||||||
</FieldGroup>
|
class="h-[40px] rounded-md border-orange-400 text-orange-400 hover:bg-green-50"
|
||||||
</div>
|
@click="handleReset"
|
||||||
</div>
|
>
|
||||||
</form>
|
<Icon
|
||||||
</Form>
|
name="i-lucide-rotate-ccw"
|
||||||
|
class="h-5 w-5"
|
||||||
|
/>
|
||||||
|
Reset
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
type="button"
|
||||||
|
class="h-[40px] rounded-md border-orange-400 text-orange-400 hover:bg-green-50"
|
||||||
|
@click="handleSearch"
|
||||||
|
>
|
||||||
|
<Icon
|
||||||
|
name="i-lucide-check"
|
||||||
|
class="h-5 w-5"
|
||||||
|
/>
|
||||||
|
Terapkan
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -20,11 +20,13 @@ const props = defineProps<{
|
|||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
apply: [filters: { personName: string; startDate: string; endDate: string }]
|
apply: [filters: { personName: string; startDate: string; endDate: string }]
|
||||||
|
click: []
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
const searchQuery = ref('')
|
const searchQuery = ref('')
|
||||||
const isRoleRegistration = props.activePositon === 'registration'
|
const isRoleRegistration = props.activePositon === 'registration'
|
||||||
const isRoleMedical = props.activePositon === 'medical'
|
const isRoleMedical = props.activePositon === 'medical'
|
||||||
|
const debouncedSearchQuery = refDebounced(searchQuery, 500)
|
||||||
|
|
||||||
const df = new DateFormatter('en-US', {
|
const df = new DateFormatter('en-US', {
|
||||||
dateStyle: 'medium',
|
dateStyle: 'medium',
|
||||||
@@ -43,14 +45,18 @@ const oneMonthAgoCalendar = new CalendarDate(
|
|||||||
oneMonthAgo.getDate(),
|
oneMonthAgo.getDate(),
|
||||||
)
|
)
|
||||||
|
|
||||||
const value = ref({
|
const dateRange = ref({
|
||||||
start: oneMonthAgoCalendar,
|
start: oneMonthAgoCalendar,
|
||||||
end: todayCalendar,
|
end: todayCalendar,
|
||||||
}) as Ref<DateRange>
|
}) as Ref<DateRange>
|
||||||
|
|
||||||
function onFilterClick() {
|
function onFilterClick() {
|
||||||
const startDate = value.value.start ? value.value.start.toString() : ''
|
emit('click')
|
||||||
const endDate = value.value.end ? value.value.end.toString() : startDate
|
}
|
||||||
|
|
||||||
|
function onFilterApply() {
|
||||||
|
const startDate = dateRange.value.start ? dateRange.value.start.toString() : ''
|
||||||
|
const endDate = dateRange.value.end ? dateRange.value.end.toString() : startDate
|
||||||
|
|
||||||
emit('apply', {
|
emit('apply', {
|
||||||
personName: searchQuery.value,
|
personName: searchQuery.value,
|
||||||
@@ -58,6 +64,18 @@ function onFilterClick() {
|
|||||||
endDate,
|
endDate,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
watch(debouncedSearchQuery, () => {
|
||||||
|
onFilterApply()
|
||||||
|
})
|
||||||
|
|
||||||
|
watch(
|
||||||
|
dateRange,
|
||||||
|
() => {
|
||||||
|
onFilterApply()
|
||||||
|
},
|
||||||
|
{ deep: true },
|
||||||
|
)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@@ -76,18 +94,18 @@ function onFilterClick() {
|
|||||||
<Button
|
<Button
|
||||||
variant="outline"
|
variant="outline"
|
||||||
:class="
|
:class="
|
||||||
cn('min-w-[240px] max-w-[320px] justify-start text-left font-normal', !value && 'text-muted-foreground')
|
cn('min-w-[240px] max-w-[320px] justify-start text-left font-normal', !dateRange && 'text-muted-foreground')
|
||||||
"
|
"
|
||||||
>
|
>
|
||||||
<CalendarIcon class="mr-2 h-4 w-4" />
|
<CalendarIcon class="mr-2 h-4 w-4" />
|
||||||
<template v-if="value.start">
|
<template v-if="dateRange.start">
|
||||||
<template v-if="value.end">
|
<template v-if="dateRange.end">
|
||||||
{{ df.format(value.start.toDate(getLocalTimeZone())) }} -
|
{{ df.format(dateRange.start.toDate(getLocalTimeZone())) }} -
|
||||||
{{ df.format(value.end.toDate(getLocalTimeZone())) }}
|
{{ df.format(dateRange.end.toDate(getLocalTimeZone())) }}
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template v-else>
|
<template v-else>
|
||||||
{{ df.format(value.start.toDate(getLocalTimeZone())) }}
|
{{ df.format(dateRange.start.toDate(getLocalTimeZone())) }}
|
||||||
</template>
|
</template>
|
||||||
</template>
|
</template>
|
||||||
<template v-else>Pick a date</template>
|
<template v-else>Pick a date</template>
|
||||||
@@ -95,10 +113,10 @@ function onFilterClick() {
|
|||||||
</PopoverTrigger>
|
</PopoverTrigger>
|
||||||
<PopoverContent class="w-auto p-0">
|
<PopoverContent class="w-auto p-0">
|
||||||
<RangeCalendar
|
<RangeCalendar
|
||||||
v-model="value"
|
v-model="dateRange"
|
||||||
initial-focus
|
initial-focus
|
||||||
:number-of-months="2"
|
:number-of-months="2"
|
||||||
@update:start-value="(startDate) => (value.start = startDate)"
|
@update:start-value="(startDate) => (dateRange.start = startDate)"
|
||||||
/>
|
/>
|
||||||
</PopoverContent>
|
</PopoverContent>
|
||||||
</Popover>
|
</Popover>
|
||||||
|
|||||||
@@ -10,6 +10,9 @@ import RecordConfirmation from '~/components/pub/my-ui/confirmation/record-confi
|
|||||||
import FileUpload from '~/components/pub/my-ui/form/file-field.vue'
|
import FileUpload from '~/components/pub/my-ui/form/file-field.vue'
|
||||||
import { useSidebar } from '~/components/pub/ui/sidebar/utils'
|
import { useSidebar } from '~/components/pub/ui/sidebar/utils'
|
||||||
|
|
||||||
|
// Constants
|
||||||
|
import { paymentTypes } from '~/lib/constants.vclaim'
|
||||||
|
|
||||||
// App libs
|
// App libs
|
||||||
import { getServicePosition } from '~/lib/roles' // previously getPositionAs
|
import { getServicePosition } from '~/lib/roles' // previously getPositionAs
|
||||||
|
|
||||||
@@ -19,6 +22,7 @@ import {
|
|||||||
remove as removeEncounter,
|
remove as removeEncounter,
|
||||||
cancel as cancelEncounter,
|
cancel as cancelEncounter,
|
||||||
} from '~/services/encounter.service'
|
} from '~/services/encounter.service'
|
||||||
|
import { getValueLabelList as getUnits } from '~/services/unit.service'
|
||||||
|
|
||||||
// Handlers
|
// Handlers
|
||||||
import { uploadAttachmentCustom } from '~/handlers/supporting-document.handler'
|
import { uploadAttachmentCustom } from '~/handlers/supporting-document.handler'
|
||||||
@@ -64,6 +68,12 @@ const isFilterFormDialogOpen = ref(false)
|
|||||||
const isRecordConfirmationOpen = ref(false)
|
const isRecordConfirmationOpen = ref(false)
|
||||||
const isRecordCancelOpen = ref(false)
|
const isRecordCancelOpen = ref(false)
|
||||||
const uploadFile = ref<any>(null)
|
const uploadFile = ref<any>(null)
|
||||||
|
const payments = ref<any>([])
|
||||||
|
const units = ref<any>([])
|
||||||
|
const visits = ref([
|
||||||
|
{ label: 'Rujukan Internal', value: 'ri' },
|
||||||
|
{ label: 'SEP Rujukan', value: 'sr' },
|
||||||
|
])
|
||||||
|
|
||||||
// Headers
|
// Headers
|
||||||
const hreaderPrep: CH.Config = {
|
const hreaderPrep: CH.Config = {
|
||||||
@@ -80,31 +90,6 @@ if (!props.canCreate) {
|
|||||||
delete hreaderPrep.addNav
|
delete hreaderPrep.addNav
|
||||||
}
|
}
|
||||||
|
|
||||||
// Filters
|
|
||||||
const filter = ref<{
|
|
||||||
installation: {
|
|
||||||
msg: {
|
|
||||||
placeholder: string
|
|
||||||
}
|
|
||||||
items: {
|
|
||||||
value: string
|
|
||||||
label: string
|
|
||||||
code: string
|
|
||||||
}[]
|
|
||||||
}
|
|
||||||
schema: any
|
|
||||||
initialValues?: Partial<any>
|
|
||||||
errors?: any
|
|
||||||
}>({
|
|
||||||
installation: {
|
|
||||||
msg: {
|
|
||||||
placeholder: 'Pilih',
|
|
||||||
},
|
|
||||||
items: [],
|
|
||||||
},
|
|
||||||
schema: {},
|
|
||||||
})
|
|
||||||
|
|
||||||
// Recrod reactivities
|
// Recrod reactivities
|
||||||
provide('activeServicePosition', activeServicePosition)
|
provide('activeServicePosition', activeServicePosition)
|
||||||
provide('rec_id', recId)
|
provide('rec_id', recId)
|
||||||
@@ -153,8 +138,13 @@ watch([recSepId, recSepMenu, recSepSubMenu], (value) => {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(async () => {
|
||||||
getPatientList()
|
getPatientList()
|
||||||
|
units.value = await getUnits({}, true)
|
||||||
|
payments.value = Object.keys(paymentTypes).map((item) => ({
|
||||||
|
value: item.toString(),
|
||||||
|
label: paymentTypes[item],
|
||||||
|
})) as any
|
||||||
})
|
})
|
||||||
|
|
||||||
/////// Functions
|
/////// Functions
|
||||||
@@ -350,6 +340,7 @@ function handleRemoveConfirmation() {
|
|||||||
<FilterNav
|
<FilterNav
|
||||||
:active-positon="activeServicePosition"
|
:active-positon="activeServicePosition"
|
||||||
@apply="handleFilterApply"
|
@apply="handleFilterApply"
|
||||||
|
@click="() => (isFilterFormDialogOpen = true)"
|
||||||
@onExportPdf="() => {}"
|
@onExportPdf="() => {}"
|
||||||
@onExportExcel="() => {}"
|
@onExportExcel="() => {}"
|
||||||
@nExportCsv="() => {}"
|
@nExportCsv="() => {}"
|
||||||
@@ -364,11 +355,15 @@ function handleRemoveConfirmation() {
|
|||||||
<!-- Filter -->
|
<!-- Filter -->
|
||||||
<Dialog
|
<Dialog
|
||||||
v-model:open="isFilterFormDialogOpen"
|
v-model:open="isFilterFormDialogOpen"
|
||||||
title="Filter"
|
title="Filter Data"
|
||||||
size="lg"
|
size="lg"
|
||||||
prevent-outside
|
prevent-outside
|
||||||
>
|
>
|
||||||
<FilterForm v-bind="filter" />
|
<FilterForm
|
||||||
|
:payments="payments"
|
||||||
|
:units="units"
|
||||||
|
:visits="visits"
|
||||||
|
/>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
|
|
||||||
<!-- Batal -->
|
<!-- Batal -->
|
||||||
|
|||||||
Reference in New Issue
Block a user