feat(FE) : start integrate api
This commit is contained in:
No files matched your search
@@ -1,7 +1,7 @@
|
||||
<script setup lang="ts">
|
||||
interface Props {
|
||||
readonly?: boolean;
|
||||
}
|
||||
import { apiGomed } from '@/services/api';
|
||||
import type { Props } from '~/types/common';
|
||||
import type { PatientData } from '~/types/pendaftaran';
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
readonly: false
|
||||
@@ -22,6 +22,69 @@ const rules = {
|
||||
required: (value: string) => !!value || 'Field ini wajib diisi'
|
||||
};
|
||||
|
||||
// Autocomplete state
|
||||
const patientItems = ref<PatientData[]>([]);
|
||||
const loadingPatients = ref(false);
|
||||
const searchTimeout = ref<NodeJS.Timeout | null>(null);
|
||||
const selectedPatient = ref<PatientData | null>(null);
|
||||
|
||||
// Search patients by RM number or name
|
||||
const searchPatients = async (search: string) => {
|
||||
if (!search || search.length < 3) {
|
||||
patientItems.value = [];
|
||||
return;
|
||||
}
|
||||
|
||||
loadingPatients.value = true;
|
||||
|
||||
try {
|
||||
const response = await apiGomed.get('/account/caripasienbynomorrm', {
|
||||
params: {
|
||||
limit: 10,
|
||||
search: search
|
||||
}
|
||||
});
|
||||
|
||||
if (response.data?.response) {
|
||||
patientItems.value = response.data.response;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error fetching patients:', error);
|
||||
patientItems.value = [];
|
||||
} finally {
|
||||
loadingPatients.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
// Handle search input with debounce
|
||||
const handleSearchInput = (value: string) => {
|
||||
if(value === formData.value.noRekamMedis) {
|
||||
patientItems.value = selectedPatient.value ? [selectedPatient.value] : [];
|
||||
return;
|
||||
}
|
||||
if (searchTimeout.value) {
|
||||
clearTimeout(searchTimeout.value);
|
||||
}
|
||||
|
||||
searchTimeout.value = setTimeout(() => {
|
||||
searchPatients(value);
|
||||
}, 500);
|
||||
};
|
||||
|
||||
// Handle patient selection
|
||||
const handlePatientSelect = (patient: PatientData | null) => {
|
||||
if (patient) {
|
||||
selectedPatient.value = patient;
|
||||
formData.value.noRekamMedis = patient.nomr;
|
||||
formData.value.noKtp = patient.nik;
|
||||
formData.value.namaPasien = patient.nama;
|
||||
formData.value.jenisKelamin = patient.jeniskelamin;
|
||||
formData.value.tanggalLahir = patient.tgllahir;
|
||||
formData.value.umur = patient.dataumur.label;
|
||||
formData.value.alamat = patient.alamat;
|
||||
}
|
||||
};
|
||||
|
||||
// Phone validation rules
|
||||
const phoneLength = (value: string) => {
|
||||
if (!value) return true;
|
||||
@@ -111,16 +174,6 @@ const savePhoneNumber = () => {
|
||||
const deletePhoneNumber = (index: number) => {
|
||||
formData.value.nomorTelepon.splice(index, 1);
|
||||
};
|
||||
|
||||
// Handle noRekamMedis input - only allow numbers
|
||||
const handleRmInput = (event: Event) => {
|
||||
const input = event.target as HTMLInputElement;
|
||||
const value = input.value;
|
||||
|
||||
// Only allow numbers
|
||||
const numericValue = value.replace(/\D/g, '');
|
||||
formData.value.noRekamMedis = numericValue;
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -135,39 +188,57 @@ const handleRmInput = (event: Event) => {
|
||||
<v-col cols="12" md="6">
|
||||
<v-label class="mb-2 font-weight-medium">No Rekam Medis <span
|
||||
class="text-error">*</span></v-label>
|
||||
<v-text-field
|
||||
v-model="formData.noRekamMedis"
|
||||
<v-autocomplete
|
||||
v-model="formData.noRekamMedis"
|
||||
:items="patientItems"
|
||||
item-title="select"
|
||||
item-value="nomr"
|
||||
placeholder="Masukan Nomor RM / Nama Pasien"
|
||||
variant="outlined"
|
||||
maxlength="8"
|
||||
variant="outlined"
|
||||
density="comfortable"
|
||||
type="tel"
|
||||
:rules="[rules.required]"
|
||||
hide-details="auto"
|
||||
max="8"
|
||||
:readonly="readonly"
|
||||
:disabled="readonly"
|
||||
:bg-color="readonly ? 'grey-lighten-3' : undefined"
|
||||
@input="handleRmInput"
|
||||
></v-text-field>
|
||||
:loading="loadingPatients"
|
||||
no-filter
|
||||
clearable
|
||||
@update:search="handleSearchInput"
|
||||
@update:model-value="(value: string) => {
|
||||
const patient = patientItems.find(p => p.nomr === value);
|
||||
handlePatientSelect(patient || null);
|
||||
}"
|
||||
>
|
||||
<template #no-data>
|
||||
<v-list-item>
|
||||
<v-list-item-title>
|
||||
Ketik minimal 3 karakter untuk mencari pasien
|
||||
</v-list-item-title>
|
||||
</v-list-item>
|
||||
</template>
|
||||
</v-autocomplete>
|
||||
</v-col>
|
||||
|
||||
<!-- No KTP -->
|
||||
<v-col cols="12" md="6">
|
||||
<v-label class="mb-2 font-weight-medium">No Ktp</v-label>
|
||||
<v-text-field v-model="formData.noKtp" variant="outlined" density="comfortable"
|
||||
hide-details="auto" readonly bg-color="grey-lighten-3"></v-text-field>
|
||||
hide-details="auto" readonly style="background-color: #f5f5f5; border-radius: 4px;"></v-text-field>
|
||||
</v-col>
|
||||
|
||||
<!-- Nama Pasien -->
|
||||
<v-col cols="12" md="6">
|
||||
<v-label class="mb-2 font-weight-medium">Nama Pasien</v-label>
|
||||
<v-text-field v-model="formData.namaPasien" variant="outlined" density="comfortable"
|
||||
hide-details="auto" readonly bg-color="grey-lighten-3"></v-text-field>
|
||||
hide-details="auto" readonly style="background-color: #f5f5f5; border-radius: 4px;"></v-text-field>
|
||||
</v-col>
|
||||
|
||||
<!-- Jenis Kelamin -->
|
||||
<v-col cols="12" md="6">
|
||||
<v-label class="mb-2 font-weight-medium">Jenis Kelamin</v-label>
|
||||
<v-radio-group v-model="formData.jenisKelamin" inline hide-details="auto" :readonly="readonly" :disabled="readonly">
|
||||
<v-radio-group readonly v-model="formData.jenisKelamin" inline hide-details="auto" :disabled="readonly">
|
||||
<v-radio label="Laki-Laki" value="L" color="primary"></v-radio>
|
||||
<v-radio label="Perempuan" value="P" color="primary"></v-radio>
|
||||
</v-radio-group>
|
||||
@@ -178,21 +249,21 @@ const handleRmInput = (event: Event) => {
|
||||
<v-label class="mb-2 font-weight-medium">Tanggal Lahir</v-label>
|
||||
<v-text-field v-model="formData.tanggalLahir" type="date" variant="outlined"
|
||||
density="comfortable" hide-details="auto" readonly
|
||||
bg-color="grey-lighten-3"></v-text-field>
|
||||
style="background-color: #f5f5f5; border-radius: 4px;"></v-text-field>
|
||||
</v-col>
|
||||
|
||||
<!-- Umur -->
|
||||
<v-col cols="12" md="6">
|
||||
<v-label class="mb-2 font-weight-medium">Umur</v-label>
|
||||
<v-text-field v-model="formData.umur" variant="outlined" density="comfortable"
|
||||
hide-details="auto" readonly bg-color="grey-lighten-3"></v-text-field>
|
||||
hide-details="auto" readonly style="background-color: #f5f5f5; border-radius: 4px;"></v-text-field>
|
||||
</v-col>
|
||||
|
||||
<!-- Alamat -->
|
||||
<v-col cols="12">
|
||||
<v-label class="mb-2 font-weight-medium">Alamat</v-label>
|
||||
<v-textarea v-model="formData.alamat" variant="outlined" rows="3" hide-details="auto"
|
||||
readonly bg-color="grey-lighten-3"></v-textarea>
|
||||
readonly style="background-color: #f5f5f5; border-radius: 4px;"></v-textarea>
|
||||
</v-col>
|
||||
|
||||
<!-- Nomor Telepon -->
|
||||
|
||||
@@ -1,22 +1,7 @@
|
||||
<script setup lang="ts">
|
||||
import diagnosisData from '~/data/mock/diagnosa.json';
|
||||
import tindakanData from '~/data/mock/tindakan.json';
|
||||
|
||||
interface DiagnosisItem {
|
||||
kodeDiagnosa: string;
|
||||
diagnosa: string;
|
||||
jenisDiagnosa: string;
|
||||
}
|
||||
|
||||
interface TindakanItem {
|
||||
kodeTindakan: string;
|
||||
tindakan: string;
|
||||
tindakanTambahan?: string;
|
||||
}
|
||||
|
||||
interface Props {
|
||||
readonly?: boolean;
|
||||
}
|
||||
import { apiGomed } from '@/services/api';
|
||||
import type { Props } from '~/types/common';
|
||||
import type { DiagnosisItem, TindakanItem, DiagnosisApiData, TindakanApiData } from '~/types/pendaftaran';
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
readonly: false
|
||||
@@ -33,15 +18,60 @@ const diagnosisForm = ref<DiagnosisItem>({
|
||||
jenisDiagnosa: 'utama'
|
||||
});
|
||||
const editingDiagnosisIndex = ref<number | null>(null);
|
||||
const diagnosisList = ref(diagnosisData);
|
||||
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 apiGomed.get('/account/caridiagnosa', {
|
||||
params: {
|
||||
limit: 100,
|
||||
search: search
|
||||
}
|
||||
});
|
||||
|
||||
if (response.data?.response) {
|
||||
diagnosisList.value = response.data.response;
|
||||
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.kode} - ${d.diagnosa}`,
|
||||
value: d.kode,
|
||||
diagnosa: d.diagnosa,
|
||||
jenisDiagnosa: d.jenis_diagnosa
|
||||
title: d.select,
|
||||
value: d.kodeicd,
|
||||
diagnosa: d.keterangan
|
||||
}));
|
||||
});
|
||||
|
||||
@@ -53,15 +83,60 @@ const tindakanForm = ref<TindakanItem>({
|
||||
tindakanTambahan: ''
|
||||
});
|
||||
const editingTindakanIndex = ref<number | null>(null);
|
||||
const tindakanList = ref(tindakanData);
|
||||
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 apiGomed.get('/account/cariprosedur', {
|
||||
params: {
|
||||
limit: 100,
|
||||
search: search
|
||||
}
|
||||
});
|
||||
|
||||
if (response.data?.response) {
|
||||
tindakanList.value = response.data.response;
|
||||
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.kode} - ${t.tindakan}`,
|
||||
title: t.select,
|
||||
value: t.kode,
|
||||
tindakan: t.tindakan,
|
||||
tindakanTambahan: t.tindakan_tambahan
|
||||
tindakan: t.keterangan
|
||||
}));
|
||||
});
|
||||
|
||||
@@ -113,10 +188,10 @@ const deleteDiagnosis = (index: number) => {
|
||||
|
||||
// Auto-fill diagnosis when code is selected
|
||||
const handleKodeDiagnosaChange = (kode: string) => {
|
||||
const found = diagnosisList.value.find(d => d.kode === kode);
|
||||
const found = diagnosisList.value.find(d => d.kodeicd === kode);
|
||||
if (found) {
|
||||
diagnosisForm.value.diagnosa = found.diagnosa;
|
||||
diagnosisForm.value.jenisDiagnosa = found.jenis_diagnosa;
|
||||
diagnosisForm.value.diagnosa = found.keterangan;
|
||||
// Keep jenisDiagnosa as is or default to current value
|
||||
}
|
||||
};
|
||||
|
||||
@@ -162,8 +237,8 @@ const deleteTindakan = (index: number) => {
|
||||
const handleKodeTindakanChange = (kode: string) => {
|
||||
const found = tindakanList.value.find(t => t.kode === kode);
|
||||
if (found) {
|
||||
tindakanForm.value.tindakan = found.tindakan;
|
||||
tindakanForm.value.tindakanTambahan = found.tindakan_tambahan || '';
|
||||
tindakanForm.value.tindakan = found.keterangan;
|
||||
// Keep tindakanTambahan as is or user can input manually
|
||||
}
|
||||
};
|
||||
</script>
|
||||
@@ -304,12 +379,26 @@ const handleKodeTindakanChange = (kode: string) => {
|
||||
<v-autocomplete
|
||||
v-model="diagnosisForm.kodeDiagnosa"
|
||||
:items="diagnosisOptions"
|
||||
placeholder="Kode ICD 10"
|
||||
placeholder="Ketik minimal 3 karakter untuk mencari diagnosa"
|
||||
variant="outlined"
|
||||
density="comfortable"
|
||||
hide-details="auto"
|
||||
:loading="loadingDiagnosis"
|
||||
no-filter
|
||||
clearable
|
||||
@update:search="handleDiagnosisSearchInput"
|
||||
@update:model-value="handleKodeDiagnosaChange"
|
||||
></v-autocomplete>
|
||||
>
|
||||
<template #no-data>
|
||||
<v-list-item>
|
||||
<v-list-item-title>
|
||||
{{ diagnosisSearchPerformed && diagnosisList.length === 0
|
||||
? 'Data tidak ditemukan'
|
||||
: 'Ketik minimal 3 karakter untuk mencari diagnosa' }}
|
||||
</v-list-item-title>
|
||||
</v-list-item>
|
||||
</template>
|
||||
</v-autocomplete>
|
||||
</v-col>
|
||||
|
||||
<!-- Diagnosa -->
|
||||
@@ -328,7 +417,7 @@ const handleKodeTindakanChange = (kode: string) => {
|
||||
<!-- Jenis Diagnosa -->
|
||||
<v-col cols="12">
|
||||
<v-label class="mb-2 font-weight-medium">Jenis Diagnosa</v-label>
|
||||
<v-radio-group v-model="diagnosisForm.jenisDiagnosa" inline hide-details="auto" readonly>
|
||||
<v-radio-group v-model="diagnosisForm.jenisDiagnosa" inline hide-details="auto">
|
||||
<v-radio label="Diagnosa Utama" value="utama" color="primary"></v-radio>
|
||||
<v-radio label="Diagnosa Tambahan" value="tambahan" color="primary"></v-radio>
|
||||
</v-radio-group>
|
||||
@@ -379,12 +468,26 @@ const handleKodeTindakanChange = (kode: string) => {
|
||||
<v-autocomplete
|
||||
v-model="tindakanForm.kodeTindakan"
|
||||
:items="tindakanOptions"
|
||||
placeholder="Kode ICD 9"
|
||||
placeholder="Ketik minimal 3 karakter untuk mencari tindakan"
|
||||
variant="outlined"
|
||||
density="comfortable"
|
||||
hide-details="auto"
|
||||
:loading="loadingTindakan"
|
||||
no-filter
|
||||
clearable
|
||||
@update:search="handleTindakanSearchInput"
|
||||
@update:model-value="handleKodeTindakanChange"
|
||||
></v-autocomplete>
|
||||
>
|
||||
<template #no-data>
|
||||
<v-list-item>
|
||||
<v-list-item-title>
|
||||
{{ tindakanSearchPerformed && tindakanList.length === 0
|
||||
? 'Data tidak ditemukan'
|
||||
: 'Ketik minimal 3 karakter untuk mencari tindakan' }}
|
||||
</v-list-item-title>
|
||||
</v-list-item>
|
||||
</template>
|
||||
</v-autocomplete>
|
||||
</v-col>
|
||||
|
||||
<!-- Tindakan -->
|
||||
|
||||
@@ -1,19 +1,9 @@
|
||||
<script setup lang="ts">
|
||||
import dokterData from '~/data/mock/dokter.json';
|
||||
import spesialisData from '~/data/mock/spesialis.json';
|
||||
import subSpesialisData from '~/data/mock/subSpesialis.json';
|
||||
import kategoriOperasiData from '~/data/mock/kategoriOperasi.json';
|
||||
|
||||
interface Dokter {
|
||||
id: number;
|
||||
nip: string;
|
||||
nama: string;
|
||||
satuan_kerja: string;
|
||||
}
|
||||
|
||||
interface Props {
|
||||
readonly?: boolean;
|
||||
}
|
||||
import { onMounted } from 'vue';
|
||||
import api from '~/services/api';
|
||||
import type { Props } from '~/types/common';
|
||||
import type { Dokter } from '~/types/pendaftaran';
|
||||
import type { KategoriOperasi, Spesialis, SubSpesialis } from '~/types/antrean';
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
readonly: false
|
||||
@@ -35,9 +25,12 @@ const rules = {
|
||||
};
|
||||
|
||||
// Spesialis, Sub-spesialis, dan Kategori Operasi data
|
||||
const spesialisList = ref(spesialisData);
|
||||
const subSpesialisList = ref(subSpesialisData);
|
||||
const kategoriOperasiList = ref(kategoriOperasiData);
|
||||
const spesialisList = ref<Spesialis[]>([]);
|
||||
const subSpesialisList = ref<SubSpesialis[]>([]);
|
||||
const kategoriOperasiList = ref<KategoriOperasi[]>([]);
|
||||
const isLoadingSpesialis = ref(false);
|
||||
const isLoadingSubSpesialis = ref(false);
|
||||
const isLoadingKategori = ref(false);
|
||||
|
||||
// Autocomplete options
|
||||
const spesialisOptions = computed(() => {
|
||||
@@ -48,44 +41,125 @@ const spesialisOptions = computed(() => {
|
||||
});
|
||||
|
||||
const subSpesialisOptions = computed(() => {
|
||||
if (!rencanaOperasiData.value.spesialis) return [];
|
||||
|
||||
return subSpesialisList.value
|
||||
.filter(ss => ss.id_spesialis === rencanaOperasiData.value.spesialis)
|
||||
.map(ss => ({
|
||||
title: ss.subspesialis,
|
||||
value: ss.id
|
||||
}));
|
||||
return subSpesialisList.value.map(ss => ({
|
||||
title: ss.sub_spesialis,
|
||||
value: ss.id
|
||||
}));
|
||||
});
|
||||
|
||||
const kategoriOperasiOptions = computed(() => {
|
||||
return kategoriOperasiList.value.map(k => ({
|
||||
return kategoriOperasiList.value.map((k: KategoriOperasi) => ({
|
||||
title: k.kategori,
|
||||
value: k.id
|
||||
}));
|
||||
});
|
||||
|
||||
// Watch spesialis change to reset sub-spesialis
|
||||
watch(() => rencanaOperasiData.value.spesialis, () => {
|
||||
// Fetch sub-spesialis from API
|
||||
const fetchSubSpesialis = async (idSpesialis: number) => {
|
||||
try {
|
||||
isLoadingSubSpesialis.value = true;
|
||||
const response = await api.get(`/sub-spesialis?id_spesialis=${idSpesialis}`);
|
||||
|
||||
if (response.data.success && response.data.data) {
|
||||
subSpesialisList.value = response.data.data;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error fetching sub-spesialis:', error);
|
||||
subSpesialisList.value = [];
|
||||
} finally {
|
||||
isLoadingSubSpesialis.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
// Watch spesialis change to fetch sub-spesialis and reset sub-spesialis selection
|
||||
watch(() => rencanaOperasiData.value.spesialis, (newSpesialis) => {
|
||||
rencanaOperasiData.value.subSpesialis = null;
|
||||
subSpesialisList.value = [];
|
||||
|
||||
if (newSpesialis) {
|
||||
fetchSubSpesialis(newSpesialis);
|
||||
}
|
||||
});
|
||||
|
||||
// Fetch spesialis from API
|
||||
const fetchSpesialis = async () => {
|
||||
try {
|
||||
isLoadingSpesialis.value = true;
|
||||
const response = await api.get('/spesialis');
|
||||
|
||||
if (response.data.success && response.data.data) {
|
||||
spesialisList.value = response.data.data;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error fetching spesialis:', error);
|
||||
// You can add error notification here if needed
|
||||
} finally {
|
||||
isLoadingSpesialis.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
// Fetch kategori operasi from API
|
||||
const fetchKategoriOperasi = async () => {
|
||||
try {
|
||||
isLoadingKategori.value = true;
|
||||
const response = await api.get('/kategori');
|
||||
|
||||
if (response.data.success && response.data.data) {
|
||||
kategoriOperasiList.value = response.data.data;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error fetching kategori operasi:', error);
|
||||
// You can add error notification here if needed
|
||||
} finally {
|
||||
isLoadingKategori.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
// Load data on mount
|
||||
onMounted(() => {
|
||||
fetchSpesialis();
|
||||
fetchKategoriOperasi();
|
||||
});
|
||||
|
||||
// Modal state for dokter
|
||||
const showDokterModal = ref(false);
|
||||
const searchDokter = ref('');
|
||||
const selectedDokters = ref<number[]>([]);
|
||||
const dokterList = ref<Dokter[]>(dokterData);
|
||||
const selectedDokters = ref<string[]>([]);
|
||||
const dokterList = ref<Dokter[]>([]);
|
||||
const isLoadingDokter = ref(false);
|
||||
const dokterLimit = ref(10);
|
||||
const dokterOffset = ref(0);
|
||||
const dokterTotalCount = ref(0);
|
||||
|
||||
// Computed filtered dokter list
|
||||
const filteredDokterList = computed(() => {
|
||||
if (!searchDokter.value) return dokterList.value;
|
||||
|
||||
const search = searchDokter.value.toLowerCase();
|
||||
return dokterList.value.filter(dokter =>
|
||||
dokter.nama.toLowerCase().includes(search) ||
|
||||
dokter.nip.includes(search) ||
|
||||
dokter.satuan_kerja.toLowerCase().includes(search)
|
||||
);
|
||||
// Fetch dokter from API
|
||||
const fetchDokter = async () => {
|
||||
try {
|
||||
isLoadingDokter.value = true;
|
||||
const params = new URLSearchParams({
|
||||
search: searchDokter.value,
|
||||
limit: dokterLimit.value.toString(),
|
||||
offset: dokterOffset.value.toString()
|
||||
});
|
||||
|
||||
const response = await api.get(`/dokter?${params}`);
|
||||
|
||||
if (response.data.success && response.data.data) {
|
||||
dokterList.value = response.data.data;
|
||||
// TODO: Update when backend implements pagination
|
||||
// dokterTotalCount.value = response.data.total || response.data.data.length;
|
||||
dokterTotalCount.value = response.data.data.length;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error fetching dokter:', error);
|
||||
dokterList.value = [];
|
||||
} finally {
|
||||
isLoadingDokter.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
// Watch search with debounce
|
||||
watch(searchDokter, () => {
|
||||
fetchDokter();
|
||||
});
|
||||
|
||||
// Initialize dokterPelaksanaItems as array if not already
|
||||
@@ -98,6 +172,7 @@ const openDokterModal = () => {
|
||||
selectedDokters.value = dokterPelaksanaItems.value.map(d => d.id);
|
||||
searchDokter.value = '';
|
||||
showDokterModal.value = true;
|
||||
fetchDokter();
|
||||
};
|
||||
|
||||
const saveDokterSelection = () => {
|
||||
@@ -113,19 +188,19 @@ const deleteDokter = (index: number) => {
|
||||
|
||||
// Toggle all selection
|
||||
const toggleAllDokters = () => {
|
||||
if (selectedDokters.value.length === filteredDokterList.value.length) {
|
||||
if (selectedDokters.value.length === dokterList.value.length) {
|
||||
selectedDokters.value = [];
|
||||
} else {
|
||||
selectedDokters.value = filteredDokterList.value.map(d => d.id);
|
||||
selectedDokters.value = dokterList.value.map(d => d.id);
|
||||
}
|
||||
};
|
||||
|
||||
const isAllSelected = computed(() => {
|
||||
return filteredDokterList.value.length > 0 &&
|
||||
selectedDokters.value.length === filteredDokterList.value.length;
|
||||
return dokterList.value.length > 0 &&
|
||||
selectedDokters.value.length === dokterList.value.length;
|
||||
});
|
||||
|
||||
const toggleDokterSelection = (dokterId: number) => {
|
||||
const toggleDokterSelection = (dokterId: string) => {
|
||||
const index = selectedDokters.value.indexOf(dokterId);
|
||||
if (index > -1) {
|
||||
selectedDokters.value.splice(index, 1);
|
||||
@@ -155,7 +230,8 @@ const toggleDokterSelection = (dokterId: number) => {
|
||||
:rules="[rules.required]"
|
||||
hide-details="auto"
|
||||
:readonly="readonly"
|
||||
:disabled="readonly"
|
||||
:disabled="readonly || isLoadingSpesialis"
|
||||
:loading="isLoadingSpesialis"
|
||||
:bg-color="readonly ? 'grey-lighten-3' : undefined"
|
||||
></v-autocomplete>
|
||||
</v-col>
|
||||
@@ -171,7 +247,8 @@ const toggleDokterSelection = (dokterId: number) => {
|
||||
density="comfortable"
|
||||
:rules="[rules.required]"
|
||||
hide-details="auto"
|
||||
:disabled="readonly || !rencanaOperasiData.spesialis"
|
||||
:disabled="readonly || !rencanaOperasiData.spesialis || isLoadingSubSpesialis"
|
||||
:loading="isLoadingSubSpesialis"
|
||||
:readonly="readonly"
|
||||
:bg-color="readonly ? 'grey-lighten-3' : undefined"
|
||||
></v-autocomplete>
|
||||
@@ -202,9 +279,9 @@ const toggleDokterSelection = (dokterId: number) => {
|
||||
<template #default>
|
||||
<div class="d-flex align-center justify-space-between w-100">
|
||||
<div>
|
||||
<div class="font-weight-medium">{{ item.nama }}</div>
|
||||
<div class="font-weight-medium">{{ item.nama_lengkap }}</div>
|
||||
<div class="text-caption text-medium-emphasis">
|
||||
{{ item.nip }} - {{ item.satuan_kerja }}
|
||||
{{ item.nip }} - {{ item.nama_ksm }}
|
||||
</div>
|
||||
</div>
|
||||
<v-btn
|
||||
@@ -254,7 +331,8 @@ const toggleDokterSelection = (dokterId: number) => {
|
||||
:rules="[rules.required]"
|
||||
hide-details="auto"
|
||||
:readonly="readonly"
|
||||
:disabled="readonly"
|
||||
:disabled="readonly || isLoadingKategori"
|
||||
:loading="isLoadingKategori"
|
||||
:bg-color="readonly ? 'grey-lighten-3' : undefined"
|
||||
></v-autocomplete>
|
||||
</v-col>
|
||||
@@ -316,12 +394,13 @@ const toggleDokterSelection = (dokterId: number) => {
|
||||
density="comfortable"
|
||||
hide-details
|
||||
prepend-inner-icon="mdi-magnify"
|
||||
:loading="isLoadingDokter"
|
||||
class="mb-4"
|
||||
></v-text-field>
|
||||
|
||||
<!-- Info Badge -->
|
||||
<div class="text-caption text-medium-emphasis mb-3">
|
||||
1-{{ filteredDokterList.length }} of {{ dokterList.length }} Items
|
||||
1-{{ dokterList.length }} of {{ dokterTotalCount }} Items
|
||||
</div>
|
||||
|
||||
<!-- Data Table -->
|
||||
@@ -334,16 +413,17 @@ const toggleDokterSelection = (dokterId: number) => {
|
||||
@click="toggleAllDokters"
|
||||
hide-details
|
||||
density="compact"
|
||||
:disabled="isLoadingDokter"
|
||||
></v-checkbox>
|
||||
</th>
|
||||
<th class="text-left">Display</th>
|
||||
<th class="text-left">Nip</th>
|
||||
<th class="text-left">NIP</th>
|
||||
<th class="text-left">Nama Lengkap</th>
|
||||
<th class="text-left">KSM</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr
|
||||
v-for="dokter in filteredDokterList"
|
||||
v-for="dokter in dokterList"
|
||||
:key="dokter.id"
|
||||
@click="toggleDokterSelection(dokter.id)"
|
||||
class="cursor-pointer"
|
||||
@@ -357,15 +437,21 @@ const toggleDokterSelection = (dokterId: number) => {
|
||||
density="compact"
|
||||
></v-checkbox>
|
||||
</td>
|
||||
<td>Doctor</td>
|
||||
<td>{{ dokter.nip }}</td>
|
||||
<td>{{ dokter.nama }}</td>
|
||||
<td>{{ dokter.nama_lengkap }}</td>
|
||||
<td>{{ dokter.nama_ksm }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</v-table>
|
||||
|
||||
<!-- Loading State -->
|
||||
<div v-if="isLoadingDokter" class="text-center py-8">
|
||||
<v-progress-circular indeterminate color="primary"></v-progress-circular>
|
||||
<p class="text-medium-emphasis mt-2">Loading...</p>
|
||||
</div>
|
||||
|
||||
<!-- Empty State -->
|
||||
<div v-if="filteredDokterList.length === 0" class="text-center py-8">
|
||||
<div v-else-if="dokterList.length === 0" class="text-center py-8">
|
||||
<v-icon size="large" class="mb-2 text-medium-emphasis">mdi-information-outline</v-icon>
|
||||
<p class="text-medium-emphasis">No results found</p>
|
||||
</div>
|
||||
|
||||
@@ -1,9 +1,13 @@
|
||||
<script setup lang="ts">
|
||||
interface Props {
|
||||
readonly?: boolean;
|
||||
import type { Props } from '~/types/common';
|
||||
|
||||
interface StatusProps extends Props {
|
||||
rencanaOperasiData?: {
|
||||
tanggalDaftar: string;
|
||||
};
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
const props = withDefaults(defineProps<StatusProps>(), {
|
||||
readonly: false
|
||||
});
|
||||
|
||||
@@ -12,6 +16,27 @@ const statusPasienData = defineModel<{
|
||||
statusOperasi: string;
|
||||
keteranganStatus: string;
|
||||
}>({ required: true });
|
||||
|
||||
// Computed min date from tanggal daftar
|
||||
const minTanggalSelesai = computed(() => {
|
||||
return props.rencanaOperasiData?.tanggalDaftar || '';
|
||||
});
|
||||
|
||||
// Validation rules
|
||||
const rules = {
|
||||
requiredIfSelesai: (value: any) => {
|
||||
if (statusPasienData.value.statusOperasi === 'Selesai' && !value) {
|
||||
return 'Tanggal selesai operasi wajib diisi jika status Selesai';
|
||||
}
|
||||
return true;
|
||||
},
|
||||
minDate: (value: any) => {
|
||||
if (value && minTanggalSelesai.value && value < minTanggalSelesai.value) {
|
||||
return 'Tanggal selesai tidak boleh kurang dari tanggal daftar';
|
||||
}
|
||||
return true;
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -83,15 +108,20 @@ const statusPasienData = defineModel<{
|
||||
|
||||
<!-- Tanggal Selesai Operasi -->
|
||||
<v-col cols="6">
|
||||
<v-label class="mb-2 font-weight-medium">Tanggal Selesai Operasi</v-label>
|
||||
<v-label class="mb-2 font-weight-medium">
|
||||
Tanggal Selesai Operasi
|
||||
<span v-if="statusPasienData.statusOperasi === 'Selesai'" class="text-error">*</span>
|
||||
</v-label>
|
||||
<v-text-field
|
||||
v-model="statusPasienData.tanggalSelesai"
|
||||
type="date"
|
||||
type="datetime-local"
|
||||
variant="outlined"
|
||||
density="comfortable"
|
||||
hide-details="auto"
|
||||
:readonly="readonly"
|
||||
:bg-color="readonly ? 'grey-lighten-3' : undefined"
|
||||
:min="minTanggalSelesai"
|
||||
:rules="[rules.requiredIfSelesai, rules.minDate]"
|
||||
></v-text-field>
|
||||
</v-col>
|
||||
|
||||
|
||||
@@ -1,18 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
interface Header {
|
||||
title: string;
|
||||
key: string;
|
||||
sortable?: boolean;
|
||||
width?: string;
|
||||
align?: 'start' | 'center' | 'end';
|
||||
}
|
||||
|
||||
interface Action {
|
||||
icon: string;
|
||||
color?: string;
|
||||
tooltip?: string;
|
||||
event: string;
|
||||
}
|
||||
import type { Header, Action } from '~/types/common';
|
||||
|
||||
interface Props {
|
||||
headers: Header[];
|
||||
@@ -62,7 +49,7 @@ const itemsPerPageLocal = ref(props.itemsPerPage);
|
||||
<template #item.jenisKelamin="{ item }">
|
||||
<slot name="item.jenisKelamin" :item="item">
|
||||
<v-chip
|
||||
:color="item.jenisKelamin === 'L' ? 'primary' : 'pink'"
|
||||
:color="item.jenisKelamin === 'L' ? 'primary' : 'error'"
|
||||
size="small"
|
||||
>
|
||||
{{ item.jenisKelamin }}
|
||||
|
||||
Reference in New Issue
Block a user