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">
|
||||
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
|
||||
}
|
||||
// Components
|
||||
import * as DE from '~/components/pub/my-ui/doc-entry'
|
||||
|
||||
// Props
|
||||
const props = defineProps<{
|
||||
installation: {
|
||||
msg: {
|
||||
placeholder: string
|
||||
}
|
||||
items: {
|
||||
value: string
|
||||
label: string
|
||||
code: string
|
||||
}[]
|
||||
}
|
||||
schema: any
|
||||
initialValues?: Partial<InstallationFormData>
|
||||
errors?: FormErrors
|
||||
payments: any[]
|
||||
units: any[]
|
||||
visits: any[]
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
submit: [values: InstallationFormData, resetForm: () => void]
|
||||
cancel: [resetForm: () => void]
|
||||
(e: 'search', values: any): 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 onSubmitForm(values: any, { resetForm }: { resetForm: () => void }) {
|
||||
const formData: InstallationFormData = {
|
||||
name: values.name || '',
|
||||
code: values.code || '',
|
||||
encounterClassCode: values.encounterClassCode || '',
|
||||
}
|
||||
emit('submit', formData, resetForm)
|
||||
function handleReset() {
|
||||
paymentItem.value = null
|
||||
unitItem.value = null
|
||||
visitItem.value = null
|
||||
}
|
||||
|
||||
// Form cancel handler
|
||||
function onCancelForm({ resetForm }: { resetForm: () => void }) {
|
||||
emit('cancel', resetForm)
|
||||
function handleSearch() {
|
||||
emit('search', {
|
||||
paymentMethodCode: paymentItem.value,
|
||||
unit: unitItem.value,
|
||||
visit: visitItem.value,
|
||||
})
|
||||
}
|
||||
|
||||
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>
|
||||
<div class="mx-auto w-full">
|
||||
<DE.Block
|
||||
labelSize="thin"
|
||||
class="!pt-0"
|
||||
:colCount="1"
|
||||
:cellFlex="false"
|
||||
>
|
||||
<DE.Cell>
|
||||
<DE.Label height="compact">Cara Bayar</DE.Label>
|
||||
<DE.Field>
|
||||
<Select
|
||||
id="paymentMethodCode"
|
||||
v-model="paymentItem"
|
||||
:items="payments || []"
|
||||
placeholder="Pilih Jenis Pembayaran"
|
||||
/>
|
||||
</DE.Field>
|
||||
</DE.Cell>
|
||||
|
||||
<DE.Cell>
|
||||
<DE.Label height="compact">Poliklinik</DE.Label>
|
||||
<DE.Field>
|
||||
<Select
|
||||
id="unit"
|
||||
v-model="unitItem"
|
||||
:items="units || []"
|
||||
placeholder="Pilih Poliklinik"
|
||||
/>
|
||||
</DE.Field>
|
||||
</DE.Cell>
|
||||
|
||||
<DE.Cell>
|
||||
<DE.Label height="compact">Kunjungan</DE.Label>
|
||||
<DE.Field>
|
||||
<Select
|
||||
id="visit"
|
||||
v-model="visitItem"
|
||||
:items="visits || []"
|
||||
placeholder="Pilih Kunjungan"
|
||||
/>
|
||||
</DE.Field>
|
||||
</DE.Cell>
|
||||
</DE.Block>
|
||||
|
||||
<div class="flex w-full justify-end gap-2">
|
||||
<Button
|
||||
variant="outline"
|
||||
type="button"
|
||||
class="h-[40px] rounded-md border-orange-400 text-orange-400 hover:bg-green-50"
|
||||
@click="handleReset"
|
||||
>
|
||||
<Icon
|
||||
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>
|
||||
|
||||
@@ -20,11 +20,13 @@ const props = defineProps<{
|
||||
|
||||
const emit = defineEmits<{
|
||||
apply: [filters: { personName: string; startDate: string; endDate: string }]
|
||||
click: []
|
||||
}>()
|
||||
|
||||
const searchQuery = ref('')
|
||||
const isRoleRegistration = props.activePositon === 'registration'
|
||||
const isRoleMedical = props.activePositon === 'medical'
|
||||
const debouncedSearchQuery = refDebounced(searchQuery, 500)
|
||||
|
||||
const df = new DateFormatter('en-US', {
|
||||
dateStyle: 'medium',
|
||||
@@ -43,14 +45,18 @@ const oneMonthAgoCalendar = new CalendarDate(
|
||||
oneMonthAgo.getDate(),
|
||||
)
|
||||
|
||||
const value = ref({
|
||||
const dateRange = ref({
|
||||
start: oneMonthAgoCalendar,
|
||||
end: todayCalendar,
|
||||
}) as Ref<DateRange>
|
||||
|
||||
function onFilterClick() {
|
||||
const startDate = value.value.start ? value.value.start.toString() : ''
|
||||
const endDate = value.value.end ? value.value.end.toString() : startDate
|
||||
emit('click')
|
||||
}
|
||||
|
||||
function onFilterApply() {
|
||||
const startDate = dateRange.value.start ? dateRange.value.start.toString() : ''
|
||||
const endDate = dateRange.value.end ? dateRange.value.end.toString() : startDate
|
||||
|
||||
emit('apply', {
|
||||
personName: searchQuery.value,
|
||||
@@ -58,6 +64,18 @@ function onFilterClick() {
|
||||
endDate,
|
||||
})
|
||||
}
|
||||
|
||||
watch(debouncedSearchQuery, () => {
|
||||
onFilterApply()
|
||||
})
|
||||
|
||||
watch(
|
||||
dateRange,
|
||||
() => {
|
||||
onFilterApply()
|
||||
},
|
||||
{ deep: true },
|
||||
)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -76,18 +94,18 @@ function onFilterClick() {
|
||||
<Button
|
||||
variant="outline"
|
||||
: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" />
|
||||
<template v-if="value.start">
|
||||
<template v-if="value.end">
|
||||
{{ df.format(value.start.toDate(getLocalTimeZone())) }} -
|
||||
{{ df.format(value.end.toDate(getLocalTimeZone())) }}
|
||||
<template v-if="dateRange.start">
|
||||
<template v-if="dateRange.end">
|
||||
{{ df.format(dateRange.start.toDate(getLocalTimeZone())) }} -
|
||||
{{ df.format(dateRange.end.toDate(getLocalTimeZone())) }}
|
||||
</template>
|
||||
|
||||
<template v-else>
|
||||
{{ df.format(value.start.toDate(getLocalTimeZone())) }}
|
||||
{{ df.format(dateRange.start.toDate(getLocalTimeZone())) }}
|
||||
</template>
|
||||
</template>
|
||||
<template v-else>Pick a date</template>
|
||||
@@ -95,10 +113,10 @@ function onFilterClick() {
|
||||
</PopoverTrigger>
|
||||
<PopoverContent class="w-auto p-0">
|
||||
<RangeCalendar
|
||||
v-model="value"
|
||||
v-model="dateRange"
|
||||
initial-focus
|
||||
:number-of-months="2"
|
||||
@update:start-value="(startDate) => (value.start = startDate)"
|
||||
@update:start-value="(startDate) => (dateRange.start = startDate)"
|
||||
/>
|
||||
</PopoverContent>
|
||||
</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 { useSidebar } from '~/components/pub/ui/sidebar/utils'
|
||||
|
||||
// Constants
|
||||
import { paymentTypes } from '~/lib/constants.vclaim'
|
||||
|
||||
// App libs
|
||||
import { getServicePosition } from '~/lib/roles' // previously getPositionAs
|
||||
|
||||
@@ -19,6 +22,7 @@ import {
|
||||
remove as removeEncounter,
|
||||
cancel as cancelEncounter,
|
||||
} from '~/services/encounter.service'
|
||||
import { getValueLabelList as getUnits } from '~/services/unit.service'
|
||||
|
||||
// Handlers
|
||||
import { uploadAttachmentCustom } from '~/handlers/supporting-document.handler'
|
||||
@@ -64,6 +68,12 @@ const isFilterFormDialogOpen = ref(false)
|
||||
const isRecordConfirmationOpen = ref(false)
|
||||
const isRecordCancelOpen = ref(false)
|
||||
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
|
||||
const hreaderPrep: CH.Config = {
|
||||
@@ -80,31 +90,6 @@ if (!props.canCreate) {
|
||||
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
|
||||
provide('activeServicePosition', activeServicePosition)
|
||||
provide('rec_id', recId)
|
||||
@@ -153,8 +138,13 @@ watch([recSepId, recSepMenu, recSepSubMenu], (value) => {
|
||||
}
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
onMounted(async () => {
|
||||
getPatientList()
|
||||
units.value = await getUnits({}, true)
|
||||
payments.value = Object.keys(paymentTypes).map((item) => ({
|
||||
value: item.toString(),
|
||||
label: paymentTypes[item],
|
||||
})) as any
|
||||
})
|
||||
|
||||
/////// Functions
|
||||
@@ -350,6 +340,7 @@ function handleRemoveConfirmation() {
|
||||
<FilterNav
|
||||
:active-positon="activeServicePosition"
|
||||
@apply="handleFilterApply"
|
||||
@click="() => (isFilterFormDialogOpen = true)"
|
||||
@onExportPdf="() => {}"
|
||||
@onExportExcel="() => {}"
|
||||
@nExportCsv="() => {}"
|
||||
@@ -364,11 +355,15 @@ function handleRemoveConfirmation() {
|
||||
<!-- Filter -->
|
||||
<Dialog
|
||||
v-model:open="isFilterFormDialogOpen"
|
||||
title="Filter"
|
||||
title="Filter Data"
|
||||
size="lg"
|
||||
prevent-outside
|
||||
>
|
||||
<FilterForm v-bind="filter" />
|
||||
<FilterForm
|
||||
:payments="payments"
|
||||
:units="units"
|
||||
:visits="visits"
|
||||
/>
|
||||
</Dialog>
|
||||
|
||||
<!-- Batal -->
|
||||
|
||||
Reference in New Issue
Block a user