fix(sep): integrate search + pagination on patient
This commit is contained in:
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user