fix : add filter dokter,kategori antrean

This commit is contained in:
Yusron alamsyah
2026-04-27 13:43:12 +07:00
parent f5db0d9066
commit 41cd9d2715
9 changed files with 505 additions and 55 deletions
+95 -13
View File
@@ -4,6 +4,8 @@ import type { Props } from '~/types/common';
import type { PatientData } from '~/types/pendaftaran';
import { Icon } from '@iconify/vue';
import { formatDate } from '~/utils/helpers';
import { getAntrianOperasi } from '~/services/antrean';
import { STATUS } from '~/types/antrean';
const props = withDefaults(defineProps<Props>(), {
readonly: false
@@ -21,15 +23,19 @@ const formData = defineModel<{
}>({ required: true });
const rules = {
required: (value: string) => !!value || 'Field ini wajib diisi'
required: (value: any) => !!value || 'Field ini wajib diisi'
};
// Autocomplete state
const patientItems = ref<PatientData[]>([]);
const loadingPatients = ref(false);
const searchTimeout = ref<NodeJS.Timeout | null>(null);
const searchTimeout = ref<ReturnType<typeof setTimeout> | null>(null);
const selectedPatient = ref<PatientData | null>(null);
// History antrean belum selesai
const loadingHistoryAntrean = ref(false);
const pendingAntreanOperasi = ref<any[]>([]);
// Search patients by RM number or name
const searchPatients = async (search: string) => {
if (!search || search.length < 3) {
@@ -60,8 +66,9 @@ const searchPatients = async (search: string) => {
// Handle search input with debounce
const handleSearchInput = (value: string) => {
if (value === formData.value.noRekamMedis) {
patientItems.value = selectedPatient.value ? [selectedPatient.value] : [];
// Avoid re-triggering search when the input reflects the selected value
if (selectedPatient.value && (value === selectedPatient.value.nomr || value === selectedPatient.value.select)) {
patientItems.value = [selectedPatient.value];
return;
}
if (searchTimeout.value) {
@@ -84,9 +91,54 @@ const handlePatientSelect = (patient: PatientData | null) => {
formData.value.tanggalLahir = patient.tgllahir;
formData.value.umur = patient.dataumur.label;
formData.value.alamat = patient.alamat;
fetchHistoryAntrean(patient.nomr);
return;
}
selectedPatient.value = null;
pendingAntreanOperasi.value = [];
formData.value.noRekamMedis = '';
formData.value.noKtp = '';
formData.value.namaPasien = '';
formData.value.jenisKelamin = '';
formData.value.tanggalLahir = '';
formData.value.umur = '';
formData.value.alamat = '';
};
const fetchHistoryAntrean = async (rm: string) => {
if (!rm) return;
loadingHistoryAntrean.value = true;
pendingAntreanOperasi.value = [];
try {
const response = await getAntrianOperasi({
type: 'all',
limit: 50,
offset: 0,
search: rm,
status : STATUS.BELUM
});
if (response?.success && Array.isArray(response.data)) {
pendingAntreanOperasi.value = response.data;
}
} catch (error) {
console.error('Error fetching history antrean:', error);
pendingAntreanOperasi.value = [];
} finally {
loadingHistoryAntrean.value = false;
}
};
const pendingAntreanDetailUrl = computed(() => {
const rm = selectedPatient.value?.nomr || formData.value.noRekamMedis;
if (!rm) return '/antrean/all';
return `/antrean/all?search=${encodeURIComponent(rm)}`;
});
// Fetch patient by RM number (for edit mode)
const fetchPatientByRm = async (rm: string) => {
if (!rm) return;
@@ -231,15 +283,27 @@ defineExpose({
<!-- No Rekam Medis -->
<v-col cols="12" md="12" class="">
<v-label class="mb-2">No Rekam Medis <span class="text-error">*</span></v-label>
<v-autocomplete ref="noRekamMedisInput" v-model="formData.noRekamMedis" :items="patientItems"
item-title="select" item-value="nomr" placeholder="Masukan Nomor RM / Nama Pasien"
variant="outlined" density="compact" :rules="[rules.required]" hide-details="auto" max="8"
:readonly="readonly" :disabled="readonly" :bg-color="readonly ? 'grey-lighten-3' : undefined"
:loading="loadingPatients" no-filter clearable @update:search="handleSearchInput"
@update:model-value="(value: string) => {
const patient = patientItems.find(p => p.nomr === value);
handlePatientSelect(patient || null);
}">
<v-autocomplete
ref="noRekamMedisInput"
v-model="selectedPatient"
:items="patientItems"
item-title="select"
item-value="nomr"
placeholder="Masukan Nomor RM / Nama Pasien"
variant="outlined"
density="compact"
:rules="[rules.required]"
hide-details="auto"
:readonly="readonly"
:disabled="readonly"
:bg-color="readonly ? 'grey-lighten-3' : undefined"
:loading="loadingPatients"
no-filter
clearable
return-object
@update:search="handleSearchInput"
@update:model-value="handlePatientSelect"
>
<template #no-data>
<v-list-item>
<v-list-item-title>
@@ -250,6 +314,24 @@ defineExpose({
</v-autocomplete>
</v-col>
<v-col v-if="selectedPatient && pendingAntreanOperasi.length > 0" cols="12">
<v-alert type="warning" variant="tonal" density="compact">
<div class="d-flex align-center justify-space-between flex-wrap ga-2">
<div>
Pasien ini memiliki {{ pendingAntreanOperasi.length }} antrean operasi yang belum selesai.
<v-btn
:href="pendingAntreanDetailUrl"
target="_blank"
variant="text"
class="text-decoration-underline pl-1"
>
Lihat lebih detail
</v-btn>
</div>
</div>
</v-alert>
</v-col>
<template v-if="formData.noRekamMedis && selectedPatient">
<v-col cols="12">
<v-card class="pa-4 bg-light" elevation="0">
@@ -3,6 +3,7 @@ import { ref, computed, onMounted, onUnmounted } from 'vue';
import { Icon } from '@iconify/vue';
import type { AntreanOperasi } from '~/types/antrean';
import { STATUS, statusLabels } from '~/types/antrean';
import { numberFormat } from '~/utils/helpers';
interface Props {
items: any[];
@@ -127,6 +128,11 @@ onUnmounted(() => {
</script>
<template>
<v-col cols="12" class="pa-1">
<p class="text-caption text-medium-emphasis mb-0">
Total Data: {{ numberFormat(totalItems || items.length) }}
</p>
</v-col>
<div ref="scrollContainer" class="card-list-container"
style="max-height: calc(100vh - 200px); overflow-y: auto; overflow-x: hidden;">
<!-- Loading state -->
+36 -1
View File
@@ -1,11 +1,12 @@
<script setup lang="ts">
interface FilterOption {
type: 'btn-group' | 'select';
type: 'btn-group' | 'select' | 'autocomplete' | 'switch';
label: string;
modelKey: string;
options: Array<{ label: string; value: any }>;
defaultValue?: any;
showAllOption?: boolean;
disabled?: boolean;
}
// Props
@@ -17,6 +18,7 @@ interface Props {
// Emits
interface Emits {
(e: 'update:modelValue', value: Record<string, any>): void;
(e: 'search', payload: { modelKey: string, query: string }): void;
}
const props = defineProps<Props>();
@@ -136,8 +138,41 @@ const resetFilters = () => {
variant="outlined"
density="compact"
hide-details
:disabled="filter.disabled"
bg-color="white"
></v-select>
<!-- Autocomplete Type -->
<v-autocomplete
v-else-if="filter.type === 'autocomplete'"
:model-value="localFilters[filter.modelKey]"
@update:model-value="updateFilter(filter.modelKey, $event)"
@update:search="query => emit('search', { modelKey: filter.modelKey, query })"
:items="filter.options"
item-title="label"
item-value="value"
variant="outlined"
density="compact"
hide-details
:disabled="filter.disabled"
bg-color="white"
clearable
return-object
no-filter
></v-autocomplete>
<!-- Switch Type -->
<v-switch
v-else-if="filter.type === 'switch'"
:model-value="localFilters[filter.modelKey]"
@update:model-value="updateFilter(filter.modelKey, $event)"
:label="filter.label"
density="compact"
inset
color="primary"
hide-details
:disabled="filter.disabled"
/>
</div>
</v-card-text>
</v-card>