fix : add filter dokter,kategori antrean
This commit is contained in:
@@ -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 -->
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user