push loket baru
This commit is contained in:
@@ -139,7 +139,7 @@
|
||||
/>
|
||||
|
||||
<!-- Dialog Klinik Ruang -->
|
||||
<v-dialog v-model="showKlinikRuangDialog" max-width="900px" persistent scrollable>
|
||||
<v-dialog v-model="showKlinikRuangDialog" max-width="900px" scrollable>
|
||||
<v-card class="dialog-card">
|
||||
<v-card-title class="dialog-header dialog-header-warning">
|
||||
<span class="headline-4">Pilih Klinik Ruang</span>
|
||||
@@ -232,7 +232,7 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed } from "vue";
|
||||
import { ref, computed, watch } from "vue";
|
||||
import { useQueue } from "@/composables/useQueue";
|
||||
import { useQueueStore } from "@/stores/queueStore";
|
||||
import { useMasterStore } from "@/stores/masterStore";
|
||||
@@ -296,6 +296,13 @@ const selectedFastTrack = ref(null);
|
||||
const showKlinikRuangDialog = ref(false);
|
||||
const klinikRuangSearch = ref("");
|
||||
|
||||
// Watch dialog state to reset search when closed
|
||||
watch(showKlinikRuangDialog, (newValue) => {
|
||||
if (!newValue) {
|
||||
klinikRuangSearch.value = "";
|
||||
}
|
||||
});
|
||||
|
||||
// Get klinik ruang data from masterStore
|
||||
const klinikRuangList = computed(() => {
|
||||
return masterStore.ruangData || [];
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -107,9 +107,13 @@ const numberToLetter = (num) => {
|
||||
return String.fromCharCode(Math.min(charCode, 90));
|
||||
};
|
||||
|
||||
// Get all lokets
|
||||
// Get all lokets from loketStore (master loket)
|
||||
const allLokets = computed(() => {
|
||||
return loketStore.loketData?.value || loketStore.loketData || [];
|
||||
// Akses langsung dari loketStore.loketData (ref)
|
||||
const lokets = loketStore.loketData
|
||||
if (!lokets) return []
|
||||
// Jika lokets adalah ref, gunakan .value, jika tidak langsung return
|
||||
return Array.isArray(lokets) ? lokets : (lokets.value || [])
|
||||
});
|
||||
|
||||
// Pagination
|
||||
|
||||
+111
-16
@@ -306,8 +306,13 @@
|
||||
</div>
|
||||
|
||||
<div v-if="recentHistory.length > 0" class="recent-history-list">
|
||||
<!-- Info jumlah data -->
|
||||
<div v-if="recentHistory.length > recentHistoryItemsPerPage" class="mb-2 text-caption text-grey-darken-1">
|
||||
Menampilkan {{ (recentHistoryPage - 1) * recentHistoryItemsPerPage + 1 }} - {{ Math.min(recentHistoryPage * recentHistoryItemsPerPage, recentHistory.length) }} dari {{ recentHistory.length }} riwayat
|
||||
</div>
|
||||
|
||||
<v-card
|
||||
v-for="(item, index) in recentHistory"
|
||||
v-for="(item, index) in paginatedRecentHistory"
|
||||
:key="index"
|
||||
variant="outlined"
|
||||
class="mb-2 history-item-compact"
|
||||
@@ -380,6 +385,19 @@
|
||||
</div>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
|
||||
<!-- Pagination untuk recent history -->
|
||||
<div v-if="totalRecentHistoryPages > 1" class="mt-3 d-flex justify-center">
|
||||
<v-pagination
|
||||
v-model="recentHistoryPage"
|
||||
:length="totalRecentHistoryPages"
|
||||
:total-visible="5"
|
||||
color="primary"
|
||||
size="small"
|
||||
density="compact"
|
||||
rounded="circle"
|
||||
></v-pagination>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-else class="text-center py-6">
|
||||
@@ -892,14 +910,20 @@
|
||||
</div>
|
||||
|
||||
<!-- History List -->
|
||||
<div v-if="filteredHistory.length > 0" class="history-list">
|
||||
<v-card
|
||||
v-for="(item, index) in filteredHistory"
|
||||
:key="index"
|
||||
variant="outlined"
|
||||
class="mb-3 history-item"
|
||||
:class="getStatusClass(item.status)"
|
||||
>
|
||||
<div v-if="filteredHistory.length > 0">
|
||||
<!-- Info jumlah data -->
|
||||
<div class="mb-3 text-body-2 text-grey-darken-1">
|
||||
Menampilkan {{ (historyPage - 1) * historyItemsPerPage + 1 }} - {{ Math.min(historyPage * historyItemsPerPage, filteredHistory.length) }} dari {{ filteredHistory.length }} riwayat
|
||||
</div>
|
||||
|
||||
<div class="history-list">
|
||||
<v-card
|
||||
v-for="(item, index) in paginatedHistory"
|
||||
:key="index"
|
||||
variant="outlined"
|
||||
class="mb-3 history-item"
|
||||
:class="getStatusClass(item.status)"
|
||||
>
|
||||
<v-card-text class="pa-4">
|
||||
<div class="d-flex justify-space-between align-start">
|
||||
<div class="flex-grow-1">
|
||||
@@ -988,6 +1012,18 @@
|
||||
</div>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
</div>
|
||||
|
||||
<!-- Pagination -->
|
||||
<div v-if="totalHistoryPages > 1" class="mt-4 d-flex justify-center">
|
||||
<v-pagination
|
||||
v-model="historyPage"
|
||||
:length="totalHistoryPages"
|
||||
:total-visible="7"
|
||||
color="primary"
|
||||
rounded="circle"
|
||||
></v-pagination>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Empty State -->
|
||||
@@ -1214,6 +1250,8 @@ if (typeof window !== 'undefined') {
|
||||
const historyDialog = ref(false);
|
||||
const historySearch = ref('');
|
||||
const historyStatusFilter = ref('');
|
||||
const historyPage = ref(1);
|
||||
const historyItemsPerPage = ref(10);
|
||||
const checkInHistory = ref<Array<{
|
||||
patientId: string;
|
||||
queueNumber?: string;
|
||||
@@ -2959,11 +2997,29 @@ const saveToHistory = (item: {
|
||||
};
|
||||
|
||||
const deleteHistoryItem = (index: number) => {
|
||||
checkInHistory.value.splice(index, 1);
|
||||
if (typeof window !== 'undefined') {
|
||||
localStorage.setItem(HISTORY_STORAGE_KEY, JSON.stringify(checkInHistory.value));
|
||||
// Index dari paginatedHistory, perlu dikonversi ke index di checkInHistory
|
||||
const paginatedItem = paginatedHistory.value[index];
|
||||
if (!paginatedItem) return;
|
||||
|
||||
// Cari index sebenarnya di checkInHistory
|
||||
const actualIndex = checkInHistory.value.findIndex(h =>
|
||||
h.patientId === paginatedItem.patientId &&
|
||||
h.checkInTime === paginatedItem.checkInTime &&
|
||||
h.method === paginatedItem.method
|
||||
);
|
||||
|
||||
if (actualIndex !== -1) {
|
||||
checkInHistory.value.splice(actualIndex, 1);
|
||||
if (typeof window !== 'undefined') {
|
||||
localStorage.setItem(HISTORY_STORAGE_KEY, JSON.stringify(checkInHistory.value));
|
||||
}
|
||||
showSnackbar('Berhasil', 'Riwayat berhasil dihapus', 'success', 'mdi-check');
|
||||
|
||||
// Reset ke halaman sebelumnya jika halaman saat ini kosong
|
||||
if (paginatedHistory.value.length === 0 && historyPage.value > 1) {
|
||||
historyPage.value = Math.max(1, historyPage.value - 1);
|
||||
}
|
||||
}
|
||||
showSnackbar('Berhasil', 'Riwayat berhasil dihapus', 'success', 'mdi-check');
|
||||
};
|
||||
|
||||
const clearHistory = () => {
|
||||
@@ -2977,9 +3033,10 @@ const clearHistory = () => {
|
||||
const openHistoryDialog = () => {
|
||||
// Pastikan history dimuat ulang saat dialog dibuka
|
||||
loadHistory();
|
||||
// Reset filter saat membuka dialog
|
||||
// Reset filter dan pagination saat membuka dialog
|
||||
historySearch.value = '';
|
||||
historyStatusFilter.value = '';
|
||||
historyPage.value = 1;
|
||||
historyDialog.value = true;
|
||||
};
|
||||
|
||||
@@ -3081,7 +3138,24 @@ const filteredHistory = computed(() => {
|
||||
return filtered;
|
||||
});
|
||||
|
||||
// Recent history untuk ditampilkan di sidebar (5 item terbaru)
|
||||
// Paginated history
|
||||
const paginatedHistory = computed(() => {
|
||||
const start = (historyPage.value - 1) * historyItemsPerPage.value;
|
||||
const end = start + historyItemsPerPage.value;
|
||||
return filteredHistory.value.slice(start, end);
|
||||
});
|
||||
|
||||
// Total pages untuk pagination
|
||||
const totalHistoryPages = computed(() => {
|
||||
return Math.ceil(filteredHistory.value.length / historyItemsPerPage.value);
|
||||
});
|
||||
|
||||
// Reset page saat filter berubah
|
||||
watch([historySearch, historyStatusFilter], () => {
|
||||
historyPage.value = 1;
|
||||
});
|
||||
|
||||
// Recent history untuk ditampilkan di sidebar
|
||||
// Hanya menampilkan riwayat dari hari ini setelah reset (setelah jam 10 malam)
|
||||
const recentHistory = computed(() => {
|
||||
const todayAfterReset = getTodayAfterReset();
|
||||
@@ -3093,7 +3167,28 @@ const recentHistory = computed(() => {
|
||||
return itemDateStr === todayAfterReset;
|
||||
});
|
||||
|
||||
return todayHistory.slice(0, 5);
|
||||
// Sort by checkInTime (terbaru di atas)
|
||||
return todayHistory.sort((a, b) => {
|
||||
const timeA = new Date(a.checkInTime || a.checkInDate || 0).getTime();
|
||||
const timeB = new Date(b.checkInTime || b.checkInDate || 0).getTime();
|
||||
return timeB - timeA; // Descending order (newest first)
|
||||
});
|
||||
});
|
||||
|
||||
// Pagination untuk recent history
|
||||
const recentHistoryPage = ref(1);
|
||||
const recentHistoryItemsPerPage = ref(5);
|
||||
|
||||
// Paginated recent history
|
||||
const paginatedRecentHistory = computed(() => {
|
||||
const start = (recentHistoryPage.value - 1) * recentHistoryItemsPerPage.value;
|
||||
const end = start + recentHistoryItemsPerPage.value;
|
||||
return recentHistory.value.slice(start, end);
|
||||
});
|
||||
|
||||
// Total pages untuk recent history pagination
|
||||
const totalRecentHistoryPages = computed(() => {
|
||||
return Math.ceil(recentHistory.value.length / recentHistoryItemsPerPage.value);
|
||||
});
|
||||
|
||||
const getStatusColor = (status: string) => {
|
||||
|
||||
+127
-48
@@ -2,6 +2,7 @@
|
||||
import { defineStore } from 'pinia';
|
||||
import { ref, computed } from 'vue';
|
||||
import { useClinicStore } from './clinicStore';
|
||||
import { useAnjunganStore } from './anjunganStore';
|
||||
|
||||
// Helper function: Convert nomor loket ke huruf (1 -> A, 2 -> B, dst)
|
||||
const numberToLetter = (num) => {
|
||||
@@ -11,61 +12,139 @@ const numberToLetter = (num) => {
|
||||
return String.fromCharCode(Math.min(charCode, 90)); // Maksimal Z (90)
|
||||
};
|
||||
|
||||
// Helper function: Get pelayanan contoh dari master anjungan
|
||||
// Mengambil data klinik dari anjungan pertama (Reguler) sebagai contoh
|
||||
const getPelayananContohDariAnjungan = (anjunganItems) => {
|
||||
// Ambil data klinik dari anjungan pertama (Reguler) jika ada
|
||||
let klinikAnjunganReguler = [];
|
||||
if (anjunganItems && anjunganItems.length > 0) {
|
||||
// Cari anjungan dengan jenisPasien 'Reguler' atau ambil yang pertama
|
||||
const anjunganReguler = anjunganItems.find(a => a.jenisPasien === 'Reguler') || anjunganItems[0];
|
||||
klinikAnjunganReguler = anjunganReguler.klinik || [];
|
||||
}
|
||||
|
||||
// Jika tidak ada data dari anjungan, gunakan data default dari master anjungan
|
||||
if (klinikAnjunganReguler.length === 0) {
|
||||
klinikAnjunganReguler = ['AN', 'AS', 'BD', 'GR', 'HO', 'GI', 'GZ', 'IP', 'JT', 'JW', 'KK', 'MT', 'OB', 'PR', 'RT', 'RM', 'SR'];
|
||||
}
|
||||
|
||||
// Distribusi pelayanan ke beberapa loket sebagai contoh
|
||||
// Menggunakan data klinik yang tersedia dari master anjungan
|
||||
return {
|
||||
loket1: klinikAnjunganReguler.filter(k => ['RT', 'RM', 'TD'].includes(k)).length > 0
|
||||
? klinikAnjunganReguler.filter(k => ['RT', 'RM', 'TD'].includes(k))
|
||||
: klinikAnjunganReguler.slice(0, 3), // Ambil 3 pertama jika tidak ada match
|
||||
loket2: klinikAnjunganReguler.filter(k => ['JW', 'SR'].includes(k)).length > 0
|
||||
? klinikAnjunganReguler.filter(k => ['JW', 'SR'].includes(k))
|
||||
: klinikAnjunganReguler.slice(3, 5), // Ambil 2 berikutnya
|
||||
loket3: klinikAnjunganReguler.filter(k => ['AS', 'JT'].includes(k)).length > 0
|
||||
? klinikAnjunganReguler.filter(k => ['AS', 'JT'].includes(k))
|
||||
: klinikAnjunganReguler.slice(5, 7), // Ambil 2 berikutnya
|
||||
loket4: klinikAnjunganReguler.filter(k => ['KK', 'PR'].includes(k)).length > 0
|
||||
? klinikAnjunganReguler.filter(k => ['KK', 'PR'].includes(k))
|
||||
: klinikAnjunganReguler.slice(7, 9), // Ambil 2 berikutnya
|
||||
};
|
||||
};
|
||||
|
||||
export const useLoketStore = defineStore('loket', () => {
|
||||
const clinicStore = useClinicStore();
|
||||
const anjunganStore = useAnjunganStore();
|
||||
|
||||
// Get pelayanan contoh dari master anjungan secara dinamis
|
||||
// Menggunakan computed untuk mendapatkan data terbaru dari anjunganStore
|
||||
const getPelayananContoh = () => {
|
||||
// getAllAnjungan adalah computed yang mengembalikan anjunganItems.value
|
||||
// Jadi langsung akses tanpa .value karena computed sudah handle itu
|
||||
try {
|
||||
const anjunganItems = anjunganStore.getAllAnjungan || [];
|
||||
return getPelayananContohDariAnjungan(anjunganItems);
|
||||
} catch (error) {
|
||||
// Fallback jika store belum terinisialisasi
|
||||
console.warn('AnjunganStore belum terinisialisasi, menggunakan data default');
|
||||
return getPelayananContohDariAnjungan([]);
|
||||
}
|
||||
};
|
||||
|
||||
// Inisialisasi data loket dengan pelayanan dari master anjungan
|
||||
// Data akan diupdate saat store diinisialisasi
|
||||
const initLoketData = () => {
|
||||
const pelayananContoh = getPelayananContoh();
|
||||
return [
|
||||
{
|
||||
id: 1,
|
||||
no: 1,
|
||||
namaLoket: "Loket A",
|
||||
kuota: 500,
|
||||
pelayanan: pelayananContoh.loket1, // Dari master anjungan
|
||||
pembayaran: "JKN",
|
||||
keterangan: "ONLINE",
|
||||
statusPelayanan: "RAWAT JALAN",
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
no: 2,
|
||||
namaLoket: "Loket B",
|
||||
kuota: 666,
|
||||
pelayanan: pelayananContoh.loket2, // Dari master anjungan
|
||||
pembayaran: "JKN",
|
||||
keterangan: "ONLINE",
|
||||
statusPelayanan: "RAWAT JALAN",
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
no: 3,
|
||||
namaLoket: "Loket C",
|
||||
kuota: 666,
|
||||
pelayanan: pelayananContoh.loket3, // Dari master anjungan
|
||||
pembayaran: "JKN",
|
||||
keterangan: "ONLINE",
|
||||
statusPelayanan: "RAWAT JALAN",
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
no: 4,
|
||||
namaLoket: "Loket D",
|
||||
kuota: 3676,
|
||||
pelayanan: pelayananContoh.loket4, // Dari master anjungan
|
||||
pembayaran: "JKN",
|
||||
keterangan: "ONLINE",
|
||||
statusPelayanan: "RAWAT JALAN",
|
||||
},
|
||||
];
|
||||
};
|
||||
|
||||
// State - Loket Data
|
||||
const loketData = ref([
|
||||
{
|
||||
id: 1,
|
||||
no: 1,
|
||||
namaLoket: "Loket A",
|
||||
kuota: 500,
|
||||
pelayanan: ["RT", "RM", "TD"],
|
||||
pembayaran: "JKN",
|
||||
keterangan: "ONLINE",
|
||||
statusPelayanan: "RAWAT JALAN",
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
no: 2,
|
||||
namaLoket: "Loket B",
|
||||
kuota: 666,
|
||||
pelayanan: ["JW", "SR"],
|
||||
pembayaran: "JKN",
|
||||
keterangan: "ONLINE",
|
||||
statusPelayanan: "RAWAT JALAN",
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
no: 3,
|
||||
namaLoket: "Loket C",
|
||||
kuota: 666,
|
||||
pelayanan: ["AS", "JT"],
|
||||
pembayaran: "JKN",
|
||||
keterangan: "ONLINE",
|
||||
statusPelayanan: "RAWAT JALAN",
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
no: 4,
|
||||
namaLoket: "Loket D",
|
||||
kuota: 3676,
|
||||
pelayanan: ["KK", "PR"],
|
||||
pembayaran: "JKN",
|
||||
keterangan: "ONLINE",
|
||||
statusPelayanan: "RAWAT JALAN",
|
||||
},
|
||||
]);
|
||||
// Data pelayanan diambil dari contoh master anjungan
|
||||
const loketData = ref(initLoketData());
|
||||
|
||||
// Computed - Available services (reference dari clinicStore)
|
||||
// Mengambil data dari clinicStore untuk dropdown pelayanan
|
||||
const availableServices = computed(() => {
|
||||
const clinics = clinicStore.getAllClinics.value || [];
|
||||
return clinics.map(c => ({
|
||||
id: c.kode,
|
||||
nama: c.name,
|
||||
kode: c.kode,
|
||||
}));
|
||||
try {
|
||||
// Akses getAllClinics computed dari clinicStore
|
||||
// Karena kita di dalam computed, Vue akan handle reactivity
|
||||
const clinicsComputed = clinicStore.getAllClinics;
|
||||
|
||||
// getAllClinics adalah computed, jadi kita perlu .value untuk mendapatkan array
|
||||
const clinicsArray = clinicsComputed?.value || [];
|
||||
|
||||
// Pastikan clinicsArray adalah array
|
||||
if (!Array.isArray(clinicsArray) || clinicsArray.length === 0) {
|
||||
console.warn('No clinics available from clinicStore');
|
||||
return [];
|
||||
}
|
||||
|
||||
// Map ke format yang dibutuhkan form: { id, nama, kode }
|
||||
// id menggunakan kode untuk kompatibilitas dengan form yang menggunakan item-value="id"
|
||||
return clinicsArray.map(c => ({
|
||||
id: c.kode, // Menggunakan kode sebagai id (sesuai dengan item-value="id" di form)
|
||||
nama: c.name,
|
||||
kode: c.kode,
|
||||
}));
|
||||
} catch (error) {
|
||||
console.error('Error getting available services from clinicStore:', error);
|
||||
return [];
|
||||
}
|
||||
});
|
||||
|
||||
// Actions - CRUD Operations
|
||||
|
||||
@@ -125,6 +125,8 @@ export const useMasterStore = defineStore('master', () => {
|
||||
// ============================================
|
||||
// Reference dari loketStore
|
||||
const loketData = computed(() => loketStore.loketData);
|
||||
// availableServices adalah computed dari loketStore
|
||||
// Pinia computed sudah handle reactivity dengan baik, jadi akses langsung
|
||||
const availableServices = computed(() => loketStore.availableServices);
|
||||
|
||||
// Actions - Loket (delegate to loketStore)
|
||||
|
||||
+14
-2
@@ -366,9 +366,15 @@ export const useQueueStore = defineStore('queue', () => {
|
||||
}
|
||||
|
||||
// Update status dari 'menunggu' menjadi 'waiting' (sudah dipanggil, bisa check-in)
|
||||
// Track waktu panggilan untuk multiple calls detection
|
||||
const callTimestamp = new Date().toISOString();
|
||||
const index = allPatients.value.findIndex(p => p.no === nextPatient.no);
|
||||
if (index !== -1) {
|
||||
allPatients.value[index] = { ...allPatients.value[index], status: "waiting" };
|
||||
allPatients.value[index] = {
|
||||
...allPatients.value[index],
|
||||
status: "waiting",
|
||||
lastCalledAt: callTimestamp // Track waktu panggilan untuk multiple calls
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
@@ -421,10 +427,16 @@ export const useQueueStore = defineStore('queue', () => {
|
||||
const patientsToCall = combinedList.slice(0, maxCallable);
|
||||
|
||||
// Update status dari 'menunggu' menjadi 'waiting' (sudah dipanggil, bisa check-in)
|
||||
// Track waktu panggilan untuk multiple calls detection - semua dipanggil dengan timestamp yang sama
|
||||
const callTimestamp = new Date().toISOString();
|
||||
patientsToCall.forEach((patient) => {
|
||||
const index = allPatients.value.findIndex(p => p.no === patient.no);
|
||||
if (index !== -1) {
|
||||
allPatients.value[index] = { ...allPatients.value[index], status: "waiting" };
|
||||
allPatients.value[index] = {
|
||||
...allPatients.value[index],
|
||||
status: "waiting",
|
||||
lastCalledAt: callTimestamp // Track waktu panggilan untuk multiple calls
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user