630 lines
26 KiB
Vue
630 lines
26 KiB
Vue
<script setup lang="ts">
|
|
import api from '@/services/api';
|
|
import type { Props } from '~/types/common';
|
|
import type { DiagnosisItem, TindakanItem, DiagnosisApiData, TindakanApiData } from '~/types/pendaftaran';
|
|
import { Icon } from '@iconify/vue';
|
|
|
|
const props = withDefaults(defineProps<Props>(), {
|
|
readonly: false
|
|
});
|
|
|
|
const diagnosisItems = defineModel<DiagnosisItem[]>('diagnosisItems', { required: true });
|
|
const tindakanItems = defineModel<TindakanItem[]>('tindakanItems', { required: true });
|
|
|
|
// Form state for diagnosis
|
|
const diagnosisForm = ref<DiagnosisItem>({
|
|
kodeDiagnosa: '',
|
|
diagnosa: '',
|
|
jenisDiagnosa: 'utama'
|
|
});
|
|
const editingDiagnosisIndex = ref<number | null>(null);
|
|
const diagnosisFormExpanded = ref(true); // Always show form
|
|
const diagnosisList = ref<DiagnosisApiData[]>([]);
|
|
const loadingDiagnosis = ref(false);
|
|
const diagnosisSearchTimeout = ref<NodeJS.Timeout | null>(null);
|
|
const diagnosisSearchPerformed = ref(false);
|
|
|
|
// Search diagnosis from API
|
|
const searchDiagnosis = async (search: string) => {
|
|
if (!search || search.length < 3) {
|
|
diagnosisList.value = [];
|
|
diagnosisSearchPerformed.value = false;
|
|
return;
|
|
}
|
|
|
|
loadingDiagnosis.value = true;
|
|
diagnosisSearchPerformed.value = false;
|
|
|
|
try {
|
|
const response = await api.get('/reference/diagnosa', {
|
|
params: {
|
|
limit: 100,
|
|
search: search
|
|
}
|
|
});
|
|
|
|
if (response.data?.data) {
|
|
diagnosisList.value = response.data.data;
|
|
diagnosisSearchPerformed.value = true;
|
|
}
|
|
} catch (error) {
|
|
console.error('Error fetching diagnosis:', error);
|
|
diagnosisList.value = [];
|
|
diagnosisSearchPerformed.value = true;
|
|
} finally {
|
|
loadingDiagnosis.value = false;
|
|
}
|
|
};
|
|
|
|
// Handle diagnosis search input with debounce
|
|
const handleDiagnosisSearchInput = (value: string) => {
|
|
if (diagnosisSearchTimeout.value) {
|
|
clearTimeout(diagnosisSearchTimeout.value);
|
|
}
|
|
|
|
diagnosisSearchTimeout.value = setTimeout(() => {
|
|
searchDiagnosis(value);
|
|
}, 500);
|
|
};
|
|
|
|
// Autocomplete options for diagnosis
|
|
const diagnosisOptions = computed(() => {
|
|
return diagnosisList.value.map(d => ({
|
|
title: d.select,
|
|
value: d.kodeicd,
|
|
diagnosa: d.keterangan
|
|
}));
|
|
});
|
|
|
|
// Form state for tindakan
|
|
const tindakanForm = ref<TindakanItem>({
|
|
kodeTindakan: '',
|
|
tindakan: '',
|
|
tindakanTambahan: ''
|
|
});
|
|
const editingTindakanIndex = ref<number | null>(null);
|
|
const tindakanFormExpanded = ref(true); // Always show form
|
|
const tindakanList = ref<TindakanApiData[]>([]);
|
|
const loadingTindakan = ref(false);
|
|
const tindakanSearchTimeout = ref<NodeJS.Timeout | null>(null);
|
|
const tindakanSearchPerformed = ref(false);
|
|
|
|
// Search tindakan from API
|
|
const searchTindakan = async (search: string) => {
|
|
if (!search || search.length < 3) {
|
|
tindakanList.value = [];
|
|
tindakanSearchPerformed.value = false;
|
|
return;
|
|
}
|
|
|
|
loadingTindakan.value = true;
|
|
tindakanSearchPerformed.value = false;
|
|
|
|
try {
|
|
const response = await api.get('/reference/tindakan', {
|
|
params: {
|
|
limit: 100,
|
|
search: search
|
|
}
|
|
});
|
|
|
|
if (response.data?.data) {
|
|
tindakanList.value = response.data.data;
|
|
tindakanSearchPerformed.value = true;
|
|
}
|
|
} catch (error) {
|
|
console.error('Error fetching tindakan:', error);
|
|
tindakanList.value = [];
|
|
tindakanSearchPerformed.value = true;
|
|
} finally {
|
|
loadingTindakan.value = false;
|
|
}
|
|
};
|
|
|
|
// Handle tindakan search input with debounce
|
|
const handleTindakanSearchInput = (value: string) => {
|
|
if (tindakanSearchTimeout.value) {
|
|
clearTimeout(tindakanSearchTimeout.value);
|
|
}
|
|
|
|
tindakanSearchTimeout.value = setTimeout(() => {
|
|
searchTindakan(value);
|
|
}, 500);
|
|
};
|
|
|
|
// Autocomplete options for tindakan
|
|
const tindakanOptions = computed(() => {
|
|
return tindakanList.value.map(t => ({
|
|
title: t.select,
|
|
value: t.kode,
|
|
tindakan: t.keterangan
|
|
}));
|
|
});
|
|
|
|
// Initialize arrays if not already
|
|
if (!Array.isArray(diagnosisItems.value)) {
|
|
diagnosisItems.value = [];
|
|
}
|
|
if (!Array.isArray(tindakanItems.value)) {
|
|
tindakanItems.value = [];
|
|
}
|
|
|
|
const resetDiagnosisForm = () => {
|
|
diagnosisForm.value = {
|
|
kodeDiagnosa: '',
|
|
diagnosa: '',
|
|
jenisDiagnosa: 'utama'
|
|
};
|
|
editingDiagnosisIndex.value = null;
|
|
};
|
|
|
|
const openDiagnosisModal = () => {
|
|
resetDiagnosisForm();
|
|
};
|
|
|
|
const editDiagnosis = (index: number) => {
|
|
diagnosisForm.value = { ...diagnosisItems.value[index] };
|
|
editingDiagnosisIndex.value = index;
|
|
};
|
|
|
|
const saveDiagnosis = () => {
|
|
if (!diagnosisForm.value.kodeDiagnosa.trim() || !diagnosisForm.value.diagnosa.trim()) return;
|
|
|
|
if (editingDiagnosisIndex.value !== null) {
|
|
diagnosisItems.value[editingDiagnosisIndex.value] = { ...diagnosisForm.value };
|
|
} else {
|
|
diagnosisItems.value.push({ ...diagnosisForm.value });
|
|
}
|
|
|
|
resetDiagnosisForm();
|
|
};
|
|
|
|
const cancelEditDiagnosis = () => {
|
|
resetDiagnosisForm();
|
|
};
|
|
|
|
const deleteDiagnosis = (index: number) => {
|
|
diagnosisItems.value.splice(index, 1);
|
|
};
|
|
|
|
// Auto-fill diagnosis when code is selected
|
|
const handleKodeDiagnosaChange = (kode: string) => {
|
|
const found = diagnosisList.value.find(d => d.kodeicd === kode);
|
|
if (found) {
|
|
diagnosisForm.value.diagnosa = found.keterangan;
|
|
}
|
|
};
|
|
|
|
const resetTindakanForm = () => {
|
|
tindakanForm.value = {
|
|
kodeTindakan: '',
|
|
tindakan: '',
|
|
tindakanTambahan: ''
|
|
};
|
|
editingTindakanIndex.value = null;
|
|
};
|
|
|
|
const openTindakanModal = () => {
|
|
resetTindakanForm();
|
|
};
|
|
|
|
const editTindakan = (index: number) => {
|
|
tindakanForm.value = { ...tindakanItems.value[index] };
|
|
editingTindakanIndex.value = index;
|
|
};
|
|
|
|
const saveTindakan = () => {
|
|
if (!tindakanForm.value.kodeTindakan.trim() || !tindakanForm.value.tindakan.trim()) return;
|
|
|
|
if (editingTindakanIndex.value !== null) {
|
|
tindakanItems.value[editingTindakanIndex.value] = { ...tindakanForm.value };
|
|
} else {
|
|
tindakanItems.value.push({ ...tindakanForm.value });
|
|
}
|
|
|
|
resetTindakanForm();
|
|
};
|
|
|
|
const cancelEditTindakan = () => {
|
|
resetTindakanForm();
|
|
};
|
|
|
|
const deleteTindakan = (index: number) => {
|
|
tindakanItems.value.splice(index, 1);
|
|
};
|
|
|
|
// Auto-fill tindakan when code is selected
|
|
const handleKodeTindakanChange = (kode: string) => {
|
|
const found = tindakanList.value.find(t => t.kode === kode);
|
|
if (found) {
|
|
tindakanForm.value.tindakan = found.keterangan;
|
|
}
|
|
};
|
|
|
|
// Refs for focus functionality
|
|
const diagnosisSection = ref();
|
|
const tindakanSection = ref();
|
|
const diagnosisInput = ref();
|
|
const tindakanInput = ref();
|
|
|
|
// Expose methods to focus on fields
|
|
const focusDiagnosis = () => {
|
|
if (diagnosisSection.value) {
|
|
diagnosisSection.value.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
|
setTimeout(() => {
|
|
if (diagnosisInput.value) {
|
|
diagnosisInput.value.focus();
|
|
}
|
|
}, 300);
|
|
}
|
|
};
|
|
|
|
const focusTindakan = () => {
|
|
if (tindakanSection.value) {
|
|
tindakanSection.value.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
|
setTimeout(() => {
|
|
if (tindakanInput.value) {
|
|
tindakanInput.value.focus();
|
|
}
|
|
}, 300);
|
|
}
|
|
};
|
|
|
|
defineExpose({
|
|
focusDiagnosis,
|
|
focusTindakan
|
|
});
|
|
</script>
|
|
|
|
<template>
|
|
<v-card elevation="10">
|
|
<v-card-text class="pa-5">
|
|
<div class="d-flex align-center">
|
|
<!-- <Icon icon="solar:medical-kit-bold-duotone" height="24" class="mr-2" /> -->
|
|
<span class="text-h5">Data Medis Pasien</span>
|
|
</div>
|
|
</v-card-text>
|
|
<v-divider> </v-divider>
|
|
<v-card-text>
|
|
<!-- Diagnosa -->
|
|
<div ref="diagnosisSection" class="mb-6">
|
|
|
|
<!-- Form Input Diagnosa -->
|
|
<v-card v-if="!readonly" variant="outlined" class="mb-4 pa-4" color="blue-lighten-5">
|
|
<h6 class="text-h6 font-weight-bold mb-6">Diagnosa</h6>
|
|
<v-row>
|
|
<!-- Jenis Diagnosa -->
|
|
<v-col cols="12" md="6">
|
|
<v-label class="mb-2 font-weight-medium text-caption">
|
|
Jenis Diagnosa <span class="text-error">*</span>
|
|
</v-label>
|
|
<div class="d-flex ga-2">
|
|
<v-btn
|
|
:color="diagnosisForm.jenisDiagnosa === 'utama' ? 'primary' : 'default'"
|
|
:variant="diagnosisForm.jenisDiagnosa === 'utama' ? 'flat' : 'outlined'"
|
|
size="large"
|
|
class="flex-grow-1 text-none"
|
|
@click="diagnosisForm.jenisDiagnosa = 'utama'"
|
|
:prepend-icon="diagnosisForm.jenisDiagnosa === 'utama' ? 'mdi-radiobox-marked' : 'mdi-radiobox-blank'"
|
|
>
|
|
Diagnosa utama
|
|
</v-btn>
|
|
<v-btn
|
|
:color="diagnosisForm.jenisDiagnosa === 'tambahan' ? 'primary' : 'default'"
|
|
:variant="diagnosisForm.jenisDiagnosa === 'tambahan' ? 'flat' : 'outlined'"
|
|
size="large"
|
|
class="flex-grow-1 text-none"
|
|
@click="diagnosisForm.jenisDiagnosa = 'tambahan'"
|
|
:prepend-icon="diagnosisForm.jenisDiagnosa === 'tambahan' ? 'mdi-radiobox-marked' : 'mdi-radiobox-blank'"
|
|
>
|
|
Diagnosa tambahan
|
|
</v-btn>
|
|
</div>
|
|
</v-col>
|
|
|
|
<!-- Kode Diagnosa -->
|
|
<v-col cols="12" md="6">
|
|
<v-label class="mb-2 font-weight-medium text-caption">
|
|
Kode Diagnosa <span class="text-error">*</span>
|
|
</v-label>
|
|
<v-autocomplete
|
|
ref="diagnosisInput"
|
|
v-model="diagnosisForm.kodeDiagnosa"
|
|
:items="diagnosisOptions"
|
|
placeholder="Ketik min. 3 karakter..."
|
|
variant="outlined"
|
|
density="compact"
|
|
hide-details="auto"
|
|
:loading="loadingDiagnosis"
|
|
no-filter
|
|
clearable
|
|
@update:search="handleDiagnosisSearchInput"
|
|
@update:model-value="handleKodeDiagnosaChange"
|
|
>
|
|
<template #no-data>
|
|
<v-list-item>
|
|
<v-list-item-title class="text-caption">
|
|
{{ diagnosisSearchPerformed && diagnosisList.length === 0
|
|
? 'Data tidak ditemukan' : 'Ketik untuk mencari...' }}
|
|
</v-list-item-title>
|
|
</v-list-item>
|
|
</template>
|
|
</v-autocomplete>
|
|
</v-col>
|
|
|
|
<!-- Diagnosa -->
|
|
<v-col cols="12" md="12">
|
|
<v-label class="mb-2 font-weight-medium text-caption">
|
|
Diagnosa <span class="text-error">*</span>
|
|
</v-label>
|
|
<v-text-field
|
|
v-model="diagnosisForm.diagnosa"
|
|
variant="outlined"
|
|
density="compact"
|
|
readonly
|
|
bg-color="grey-lighten-3"
|
|
hide-details="auto"
|
|
></v-text-field>
|
|
</v-col>
|
|
|
|
<!-- Action Buttons -->
|
|
<v-col cols="12">
|
|
<div class="d-flex ga-2 justify-end">
|
|
<v-btn
|
|
v-if="editingDiagnosisIndex !== null"
|
|
color="default"
|
|
variant="outlined"
|
|
size="small"
|
|
@click="cancelEditDiagnosis"
|
|
>
|
|
<v-icon start size="small">mdi-close</v-icon>
|
|
Batal
|
|
</v-btn>
|
|
<v-btn
|
|
color="primary"
|
|
variant="flat"
|
|
size="small"
|
|
@click="saveDiagnosis"
|
|
:disabled="!diagnosisForm.kodeDiagnosa || !diagnosisForm.diagnosa"
|
|
>
|
|
<v-icon start size="small">
|
|
{{ editingDiagnosisIndex !== null ? 'mdi-check' : 'mdi-plus' }}
|
|
</v-icon>
|
|
{{ editingDiagnosisIndex !== null ? 'Update' : 'Tambah' }}
|
|
</v-btn>
|
|
</div>
|
|
</v-col>
|
|
</v-row>
|
|
</v-card>
|
|
|
|
<!-- List Diagnosa -->
|
|
<v-alert
|
|
v-if="diagnosisItems.length === 0"
|
|
type="info"
|
|
variant="tonal"
|
|
density="compact"
|
|
class="ma-0"
|
|
>
|
|
<div class="d-flex align-center">
|
|
<span>Tidak ada data diagnosa</span>
|
|
</div>
|
|
</v-alert>
|
|
|
|
<v-card v-else variant="outlined">
|
|
<v-card-text class="pa-1">
|
|
<v-list density="compact" class="pa-0">
|
|
<v-list-item
|
|
v-for="(item, index) in diagnosisItems"
|
|
:key="index"
|
|
class="px-4"
|
|
:class="{ 'bg-blue-lighten-5': editingDiagnosisIndex === index }"
|
|
>
|
|
<template #prepend>
|
|
<v-avatar
|
|
:color="item.jenisDiagnosa === 'utama' ? 'primary' : 'secondary'"
|
|
size="32"
|
|
class="mr-3"
|
|
>
|
|
<v-icon size="small">
|
|
{{ item.jenisDiagnosa === 'utama' ? 'mdi-stethoscope' : 'mdi-stethoscope' }}
|
|
</v-icon>
|
|
</v-avatar>
|
|
</template>
|
|
|
|
<v-list-item-title class="font-weight-medium">
|
|
{{ item.kodeDiagnosa }} - {{ item.diagnosa }}
|
|
</v-list-item-title>
|
|
<v-list-item-subtitle class="text-caption">
|
|
{{ item.jenisDiagnosa === 'utama' ? 'Diagnosa Utama' : 'Diagnosa Tambahan' }}
|
|
</v-list-item-subtitle>
|
|
|
|
<template v-if="!readonly" #append>
|
|
<div class="d-flex ga-1">
|
|
<v-btn
|
|
icon
|
|
size="small"
|
|
variant="text"
|
|
color="primary"
|
|
@click="editDiagnosis(index)"
|
|
>
|
|
<v-icon size="small">mdi-pencil</v-icon>
|
|
</v-btn>
|
|
<v-btn
|
|
icon
|
|
size="small"
|
|
variant="text"
|
|
color="error"
|
|
@click="deleteDiagnosis(index)"
|
|
>
|
|
<v-icon size="small">mdi-delete</v-icon>
|
|
</v-btn>
|
|
</div>
|
|
</template>
|
|
</v-list-item>
|
|
</v-list>
|
|
</v-card-text>
|
|
</v-card>
|
|
</div>
|
|
|
|
<!-- Tindakan -->
|
|
<div ref="tindakanSection">
|
|
|
|
<!-- Form Input Tindakan -->
|
|
<v-card v-if="!readonly" variant="outlined" class="mb-4 pa-4" color="purple-lighten-5">
|
|
<h6 class="text-h6 font-weight-bold mb-6">Tindakan</h6>
|
|
<v-row>
|
|
<!-- Kode Tindakan -->
|
|
<v-col cols="12" md="6">
|
|
<v-label class="mb-2 font-weight-medium text-caption">
|
|
Kode Tindakan <span class="text-error">*</span>
|
|
</v-label>
|
|
<v-autocomplete
|
|
ref="tindakanInput"
|
|
v-model="tindakanForm.kodeTindakan"
|
|
:items="tindakanOptions"
|
|
placeholder="Ketik min. 3 karakter..."
|
|
variant="outlined"
|
|
density="compact"
|
|
hide-details="auto"
|
|
:loading="loadingTindakan"
|
|
no-filter
|
|
clearable
|
|
@update:search="handleTindakanSearchInput"
|
|
@update:model-value="handleKodeTindakanChange"
|
|
>
|
|
<template #no-data>
|
|
<v-list-item>
|
|
<v-list-item-title class="text-caption">
|
|
{{ tindakanSearchPerformed && tindakanList.length === 0
|
|
? 'Data tidak ditemukan' : 'Ketik untuk mencari...' }}
|
|
</v-list-item-title>
|
|
</v-list-item>
|
|
</template>
|
|
</v-autocomplete>
|
|
</v-col>
|
|
|
|
<!-- Tindakan -->
|
|
<v-col cols="12" md="6">
|
|
<v-label class="mb-2 font-weight-medium text-caption">
|
|
Tindakan <span class="text-error">*</span>
|
|
</v-label>
|
|
<v-text-field
|
|
v-model="tindakanForm.tindakan"
|
|
variant="outlined"
|
|
density="compact"
|
|
readonly
|
|
bg-color="grey-lighten-3"
|
|
hide-details="auto"
|
|
></v-text-field>
|
|
</v-col>
|
|
|
|
<!-- Tindakan Tambahan -->
|
|
<v-col cols="12" md="12">
|
|
<v-label class="mb-2 font-weight-medium text-caption">
|
|
Tindakan Tambahan
|
|
</v-label>
|
|
<v-textarea
|
|
v-model="tindakanForm.tindakanTambahan"
|
|
placeholder="Prosedur tambahan..."
|
|
variant="outlined"
|
|
density="compact"
|
|
rows="2"
|
|
hide-details="auto"
|
|
></v-textarea>
|
|
</v-col>
|
|
|
|
<!-- Action Buttons -->
|
|
<v-col cols="12">
|
|
<div class="d-flex ga-2 justify-end">
|
|
<v-btn
|
|
v-if="editingTindakanIndex !== null"
|
|
color="default"
|
|
variant="outlined"
|
|
size="small"
|
|
@click="cancelEditTindakan"
|
|
>
|
|
<v-icon start size="small">mdi-close</v-icon>
|
|
Batal
|
|
</v-btn>
|
|
<v-btn
|
|
color="primary"
|
|
variant="flat"
|
|
size="small"
|
|
@click="saveTindakan"
|
|
:disabled="!tindakanForm.kodeTindakan || !tindakanForm.tindakan"
|
|
>
|
|
<v-icon start size="small">
|
|
{{ editingTindakanIndex !== null ? 'mdi-check' : 'mdi-plus' }}
|
|
</v-icon>
|
|
{{ editingTindakanIndex !== null ? 'Update' : 'Tambah' }}
|
|
</v-btn>
|
|
</div>
|
|
</v-col>
|
|
</v-row>
|
|
</v-card>
|
|
|
|
<!-- List Tindakan -->
|
|
<v-alert
|
|
v-if="tindakanItems.length === 0"
|
|
type="info"
|
|
variant="tonal"
|
|
density="compact"
|
|
class="ma-0"
|
|
>
|
|
<div class="d-flex align-center">
|
|
<span>Tidak ada data tindakan</span>
|
|
</div>
|
|
</v-alert>
|
|
<v-card v-else variant="outlined">
|
|
<v-card-text class="pa-1">
|
|
<v-list density="compact" class="pa-0">
|
|
<v-list-item
|
|
v-for="(item, index) in tindakanItems"
|
|
:key="index"
|
|
class="px-4 mb-1"
|
|
:class="{ 'bg-purple-lighten-5': editingTindakanIndex === index }"
|
|
>
|
|
<template #prepend>
|
|
<v-avatar color="primary" size="32" class="mr-3">
|
|
<v-icon size="small">mdi-medical-bag</v-icon>
|
|
</v-avatar>
|
|
</template>
|
|
|
|
<v-list-item-title class="font-weight-medium">
|
|
{{ item.kodeTindakan }} - {{ item.tindakan }}
|
|
</v-list-item-title>
|
|
<v-list-item-subtitle v-if="item.tindakanTambahan" class="text-caption">
|
|
{{ item.tindakanTambahan }}
|
|
</v-list-item-subtitle>
|
|
|
|
<template v-if="!readonly" #append>
|
|
<div class="d-flex ga-1">
|
|
<v-btn
|
|
icon
|
|
size="small"
|
|
variant="text"
|
|
color="primary"
|
|
@click="editTindakan(index)"
|
|
>
|
|
<v-icon size="small">mdi-pencil</v-icon>
|
|
</v-btn>
|
|
<v-btn
|
|
icon
|
|
size="small"
|
|
variant="text"
|
|
color="error"
|
|
@click="deleteTindakan(index)"
|
|
>
|
|
<v-icon size="small">mdi-delete</v-icon>
|
|
</v-btn>
|
|
</div>
|
|
</template>
|
|
</v-list-item>
|
|
</v-list>
|
|
</v-card-text>
|
|
</v-card>
|
|
</div>
|
|
</v-card-text>
|
|
</v-card>
|
|
</template>
|