fix(sep): integrate search + pagination on patient

This commit is contained in:
riefive
2025-10-15 13:49:42 +07:00
parent d9e49a7907
commit e5df79c2fa
2 changed files with 128 additions and 44 deletions
+65 -20
View File
@@ -1,5 +1,10 @@
<script setup lang="ts">
import { ref } from 'vue'
// Helpers
import { refDebounced } from '@vueuse/core'
// Components
import {
Dialog,
DialogContent,
@@ -11,44 +16,60 @@ import {
import { Button } from '~/components/pub/ui/button'
import { Input } from '~/components/pub/ui/input'
import { RadioGroup, RadioGroupItem } from '~/components/pub/ui/radio-group'
import PaginationView from '~/components/pub/my-ui/pagination/pagination-view.vue'
// Types
import type { PaginationMeta } from '~/components/pub/my-ui/pagination/pagination.type'
const props = defineProps<{
open: boolean
patients: Array<{
ktp: string
rm: string
identity: string
number: string
bpjs: string
nama: string
name: string
}>
selected: string
paginationMeta: PaginationMeta
}>()
const emit = defineEmits<{
(e: 'update:open', value: boolean): void
(e: 'update:selected', value: string): void
(e: 'fetch', value: any): void
(e: 'save'): void
}>()
const search = ref('')
const debouncedSearch = refDebounced(search, 500) // 500ms debounce
const filteredPatients = computed(() => {
const patients = props.patients || []
return patients.filter(
(p) =>
p.ktp.includes(search.value) ||
p.rm.includes(search.value) ||
p.bpjs.includes(search.value) ||
p.nama.toLowerCase().includes(search.value.toLowerCase()),
)
return patients
})
function saveSelection() {
emit('save')
emit('update:open', false)
}
function handlePageChange(page: number) {
emit('fetch', { page })
}
watch(debouncedSearch, (newValue) => {
// Only search if 3+ characters or empty (to clear search)
if (newValue.length === 0 || newValue.length >= 3) {
emit('fetch', { search: newValue })
}
})
</script>
<template>
<Dialog :open="props.open" @update:open="emit('update:open', $event)">
<Dialog
:open="props.open"
@update:open="emit('update:open', $event)"
>
<DialogTrigger as-child></DialogTrigger>
<DialogContent class="max-w-3xl">
<DialogHeader>
@@ -57,7 +78,10 @@ function saveSelection() {
<!-- Input Search -->
<div class="mb-2 max-w-[50%]">
<Input v-model="search" placeholder="Cari berdasarkan No. KTP / No. RM / Nomor Kartu" />
<Input
v-model="search"
placeholder="Cari berdasarkan No. KTP / No. RM / Nomor Kartu"
/>
</div>
<!-- Table -->
@@ -73,25 +97,46 @@ function saveSelection() {
</tr>
</thead>
<tbody class="font-normal">
<tr v-for="p in filteredPatients" :key="p.ktp" class="border-t hover:bg-gray-50">
<tr
v-for="p in filteredPatients"
:key="p.identity"
class="border-t hover:bg-gray-50"
>
<td class="p-2">
<RadioGroup :model-value="props.selected" @update:model-value="emit('update:selected', $event)">
<RadioGroupItem :id="p.ktp" :value="p.ktp" />
<RadioGroup
:model-value="props.selected"
@update:model-value="emit('update:selected', $event)"
>
<RadioGroupItem
:id="p.identity"
:value="p.identity"
/>
</RadioGroup>
</td>
<td class="p-2">{{ p.ktp }}</td>
<td class="p-2">{{ p.rm }}</td>
<td class="p-2">{{ p.identity }}</td>
<td class="p-2">{{ p.number }}</td>
<td class="p-2">{{ p.bpjs }}</td>
<td class="p-2">{{ p.nama }}</td>
<td class="p-2">{{ p.name }}</td>
</tr>
</tbody>
</table>
<PaginationView
:pagination-meta="paginationMeta"
@page-change="handlePageChange"
/>
</div>
<!-- Footer -->
<DialogFooter>
<Button variant="default" class="h-[40px] min-w-[120px] text-white" @click="saveSelection">
<Icon name="i-lucide-save" class="h-5 w-5" />
<Button
variant="default"
class="h-[40px] min-w-[120px] text-white"
@click="saveSelection"
>
<Icon
name="i-lucide-save"
class="h-5 w-5"
/>
Simpan
</Button>
</DialogFooter>
+63 -24
View File
@@ -1,34 +1,68 @@
<script setup lang="ts">
import { ref } from 'vue'
// Types
import type { PaginationMeta } from '~/components/pub/my-ui/pagination/pagination.type'
// Services
import { getPatients } from '~/services/patient.service'
const openPatient = ref(false)
const openLetter = ref(false)
const openHistory = ref(false)
const selectedPatient = ref('3456512345678880')
const selectedPatient = ref('')
const selectedLetter = ref('SK22334442')
// patients used by AppSepTableSearchPatient (will be filled from API)
const patients = ref<Array<{ ktp: string; rm: string; bpjs: string; nama: string }>>([
const patients = ref<Array<{ identity: string; number: string; bpjs: string; name: string }>>([
// fallback empty list until fetched
])
const isPatientsLoading = ref(false)
const paginationMeta = ref<PaginationMeta>({
recordCount: 0,
page: 1,
pageSize: 10,
totalPage: 5,
hasNext: false,
hasPrev: false,
})
function mapPatientToRow(p: any) {
function mapPatientToRow(patient: any) {
// Defensive mapping: try common field names that might be returned by the API
const ktp = p.ktp || p.noKtp || p.ktpNumber || p.person?.identityNo || ''
const rm = p.rm || p.noRm || p.medicalRecordNo || ''
const bpjs = p.bpjs || p.noBpjs || p.bpjsNo || ''
const nama = p.nama || p.name || p.fullName || p.person?.name || p.person?.fullName || p.person?.nama || ''
return { ktp, rm, bpjs, nama }
const identity = patient?.person?.residentIdentityNumber || '-'
const number = patient?.number || patient?.medicalRecordNo || '-'
const bpjs = patient?.person?.refNumber || '-'
const name = patient.name || patient?.person?.name || '-'
return { identity, number, bpjs, name }
}
function mapPaginationMetaToRow(meta: any) {
const recordCount = meta['record_totalCount'] ? Number(meta['record_totalCount']) : 0
const currentCount = meta['record_currentCount'] ? Number(meta['record_currentCount']) : 0
const page = meta['page_number'] ? Number(meta['page_number']) : 1
const pageSize = meta['page_size'] ? Number(meta['page_size']) : 10
const totalPage = Math.ceil(recordCount / pageSize)
return {
recordCount,
page,
pageSize,
totalPage,
hasNext: currentCount < recordCount && page < totalPage,
hasPrev: page > 1,
}
}
async function fetchPatients(params: any = { 'page-size': 100 }) {
try {
isPatientsLoading.value = true
patients.value = []
paginationMeta.value = {} as PaginationMeta
const result = await getPatients(params)
if (result && result.success && result.body && Array.isArray(result.body.data)) {
const meta = result.body.meta
patients.value = result.body.data.map(mapPatientToRow)
paginationMeta.value = mapPaginationMetaToRow(meta)
} else {
// fallback to empty array
patients.value = []
@@ -64,20 +98,20 @@ const letters = [
const histories = [
{
no_sep: "SP23311224",
tgl_sep: "12 Agustus 2025",
no_rujukan: "123444",
diagnosis: "C34.9 Karsinoma Paru",
pelayanan: "Rawat Jalan",
kelas: "Kelas II",
no_sep: 'SP23311224',
tgl_sep: '12 Agustus 2025',
no_rujukan: '123444',
diagnosis: 'C34.9 Karsinoma Paru',
pelayanan: 'Rawat Jalan',
kelas: 'Kelas II',
},
{
no_sep: "SP23455667",
tgl_sep: "11 Agustus 2025",
no_rujukan: "2331221",
diagnosis: "K35 Apendisitis akut",
pelayanan: "Rawat Jalan",
kelas: "Kelas II",
no_sep: 'SP23455667',
tgl_sep: '11 Agustus 2025',
no_rujukan: '2331221',
diagnosis: 'K35 Apendisitis akut',
pelayanan: 'Rawat Jalan',
kelas: 'Kelas II',
},
]
@@ -92,7 +126,7 @@ function handleSaveLetter() {
function handleEvent(value: string) {
if (value === 'search-patient') {
// fetch patients from API then open the dialog
fetchPatients().then(() => {
fetchPatients({ 'page-size': 10, includes: 'person' }).then(() => {
openPatient.value = true
})
return
@@ -113,14 +147,20 @@ function handleEvent(value: string) {
<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> SEP
<Icon
name="i-lucide-panel-bottom"
class="me-2"
/>
<span class="font-semibold">Tambah</span>
SEP
</div>
<AppSepEntryForm @event="handleEvent" />
<AppSepTableSearchPatient
v-model:open="openPatient"
v-model:selected="selectedPatient"
:patients="patients"
:pagination-meta="paginationMeta"
@fetch="(value) => fetchPatients(value)"
@save="handleSavePatient"
/>
<AppSepTableSearchLetter
@@ -133,5 +173,4 @@ function handleEvent(value: string) {
v-model:open="openHistory"
:histories="histories"
/>
</template>