feat(FE) : start integrate api

This commit is contained in:
Yusron alamsyah
2026-02-02 08:13:15 +07:00
parent d826b43887
commit eeda789acd
59 changed files with 2756 additions and 1917 deletions

No files matched your search

+97 -26
View File
@@ -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 -->
+140 -37
View File
@@ -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 -->
+144 -58
View File
@@ -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>
+35 -5
View File
@@ -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>
+2 -15
View File
@@ -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 }}