push loket baru

This commit is contained in:
Fanrouver
2026-01-12 14:45:29 +07:00
parent 5c142273aa
commit 42fe62cb41
7 changed files with 1138 additions and 247 deletions
+9 -2
View File
@@ -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
+6 -2
View File
@@ -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
View File
@@ -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
View File
@@ -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
+2
View File
@@ -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
View File
@@ -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
};
}
});