diff --git a/pages/AdminLoket.vue b/pages/AdminLoket/[id].vue
similarity index 56%
rename from pages/AdminLoket.vue
rename to pages/AdminLoket/[id].vue
index 16acf70..f66ace7 100644
--- a/pages/AdminLoket.vue
+++ b/pages/AdminLoket/[id].vue
@@ -2,209 +2,222 @@
+ >
+
+
+ Kembali
+
+
+
-
-
-
-
-
+
+
+
+
+
-
-
-
-
+
+
+
+
-
-
-
-
-
-
+
+
diff --git a/pages/Anjungan/AntrianLoket/[id].vue b/pages/Anjungan/AntrianLoket/[id].vue
index ba7e6f7..bd67211 100644
--- a/pages/Anjungan/AntrianLoket/[id].vue
+++ b/pages/Anjungan/AntrianLoket/[id].vue
@@ -201,6 +201,8 @@ const clinicStore = useClinicStore()
const currentTime = ref('')
const currentDate = ref('')
let timeInterval = null
+let broadcastChannel = null
+const broadcastedPatient = ref(null)
// Get loket ID from route
const loketId = computed(() => {
@@ -665,6 +667,11 @@ const isInTTSWindow = (queue) => {
// Current called queue - tiket yang sedang diproses di admin loket
// Nomor antrian menjadi "dipanggil" jika diproses pada AdminLoket DAN sudah dipanggil oleh admin
const currentCalledQueue = computed(() => {
+ // Prioritas 0: Broadcasted patient (Real-time from BroadcastChannel)
+ if (broadcastedPatient.value) {
+ return broadcastedPatient.value
+ }
+
// Prioritas 1: Pasien yang sedang diproses di admin loket (currentProcessingPatient)
// Hanya tampilkan jika sudah dipanggil eksplisit oleh admin (calledByAdmin === true)
if (currentProcessingPatient.value) {
@@ -926,11 +933,71 @@ onMounted(() => {
// Langsung start timer tanpa menunggu fetch
updateTime()
+ // Init BroadcastChannel
+ try {
+ broadcastChannel = new BroadcastChannel('antrian-loket-channel')
+ broadcastChannel.onmessage = (event) => {
+ console.log('Anjungan received broadcast:', event.data)
+ if (event.data && event.data.type === 'CALL_PATIENT') {
+ const { patient, loketId: senderLoketId } = event.data
+
+ // Cek apakah event ini untuk loket ini
+ // Gunakan loose equality untuk handle string vs number
+ if (loketId.value) {
+ if (String(loketId.value) != String(senderLoketId)) {
+ console.log('Skipping broadcast for different loket:', senderLoketId)
+ return
+ }
+ }
+
+ // Update local state untuk prioritas tampilan hero
+ broadcastedPatient.value = patient
+
+ // PENTING: Update Store agar list antrian juga berubah
+ // 1. Update currentProcessingPatient
+ if (!queueStore.currentProcessingPatient) {
+ queueStore.currentProcessingPatient = {}
+ }
+ queueStore.currentProcessingPatient.loket = patient
+
+ // 2. Update status patient di allPatients list jika ada
+ if (queueStore.allPatients && Array.isArray(queueStore.allPatients)) {
+ const idx = queueStore.allPatients.findIndex(p =>
+ p.no === patient.no ||
+ p.noAntrian === patient.noAntrian
+ )
+
+ if (idx !== -1) {
+ // Update existing patient data
+ // Kita update status menjadi 'di-loket' dan lastCalledAt
+ const updatedPatient = {
+ ...queueStore.allPatients[idx],
+ ...patient,
+ status: 'di-loket', // Pastikan status sinkron
+ lastCalledAt: patient.lastCalledAt || new Date().toISOString()
+ }
+ queueStore.allPatients[idx] = updatedPatient
+ } else {
+ // Opsional: Jika pasien tidak ditemukan di list (misal data awal kosong), tambahkan?
+ // Sebaiknya jangan sembarang nambah, tapi untuk robustnes UI boleh saja
+ // queueStore.allPatients.push(patient)
+ }
+ }
+
+ console.log('Store updated from broadcast')
+ }
+ }
+ } catch (e) {
+ console.error('BroadcastChannel error:', e)
+ }
+
+ // Start TTS checker
timeInterval = setInterval(updateTime, 1000)
})
onUnmounted(() => {
if (timeInterval) clearInterval(timeInterval)
+ if (broadcastChannel) broadcastChannel.close()
})
diff --git a/stores/loketStore.js b/stores/loketStore.js
index 17ada66..0ef36b9 100644
--- a/stores/loketStore.js
+++ b/stores/loketStore.js
@@ -262,37 +262,31 @@ export const useLoketStore = defineStore('loket', () => {
/**
* Fetch loket data dari API backend
- * Mengikuti pattern dari clinicStore.fetchRegulerClinics()
- * Filter hanya loket dengan pembayaran JKN (REGULER)
- * Sort berdasarkan nama loket (urut dari 1)
+ * MENGGUNAKAN endpoint /api/v1/klinik/reguler untuk memetakan Loket -> Clinics
*/
const fetchLoketFromAPI = async (force = false, retryCount = 0) => {
- // Guard: singleton pattern - prevent duplicate fetch
if (activeFetchPromise && retryCount === 0) {
console.log('⏳ Loket fetch in progress, reusing existing request...');
return activeFetchPromise;
}
- // Guard: Skip if data already exists and not forced
- const hasData = loketData.value && loketData.value.length > 0;
+ const hasData = apiLoketData.value && apiLoketData.value.length > 0;
if (hasData && !force && retryCount === 0) {
return { success: true, message: 'Data sudah tersedia' };
}
- // Define the actual fetch operation
const performFetch = async () => {
isLoadingAPI.value = true;
apiError.value = null;
try {
- console.log(`🔄 Fetching loket from API (Try ${retryCount + 1})...`);
- const response = await fetch('http://10.10.150.131:8089/api/v1/klinik/loket');
+ console.log(`🔄 Fetching clinics for loket config (Try ${retryCount + 1})...`);
+ // Endpoint ini memberikan list klinik, setiap klinik punya list loket
+ const response = await fetch('http://10.10.150.131:8089/api/v1/klinik/reguler');
if (!response.ok) {
- // Handle Rate Limiting with exponential backoff
if (response.status === 429 && retryCount < 3) {
const delay = (retryCount + 1) * 1500;
- console.warn(`⚠️ Rate limit hit (429). Retrying in ${delay}ms...`);
await new Promise(resolve => setTimeout(resolve, delay));
activeFetchPromise = null;
return fetchLoketFromAPI(force, retryCount + 1);
@@ -301,81 +295,73 @@ export const useLoketStore = defineStore('loket', () => {
}
const rawData = await response.json();
- console.log('📦 Raw API Response received');
- console.log('📦 Raw data:', rawData);
+ const clinics = rawData.data || [];
- // Extract data array
- const data = rawData.data || rawData;
- if (!Array.isArray(data)) {
- throw new Error('Data format tidak valid. Expected array.');
- }
+ // AGGREGATE: Build Loket Map from Clinics
+ const loketMap = new Map();
- console.log('📊 Total items from API:', data.length);
+ clinics.forEach(clinic => {
+ if (clinic.loket && Array.isArray(clinic.loket)) {
+ clinic.loket.forEach(l => {
+ const id = parseInt(l.idloket);
+ if (!loketMap.has(id)) {
+ loketMap.set(id, {
+ id: id,
+ namaLoket: l.namaloket,
+ kodeLoket: l.kodeloket,
+ kuota: clinic.kuota || 100, // Fallback
+ pelayanan: [],
+ _spesialisDetail: [],
+ pembayaran: 'JKN',
+ source: 'api',
+ loketAktif: true
+ });
+ }
+
+ const loketObj = loketMap.get(id);
+ // Add clinic code to pelayanan if not already there
+ if (!loketObj.pelayanan.includes(clinic.code)) {
+ loketObj.pelayanan.push(clinic.code);
+ loketObj._spesialisDetail.push({
+ idklinik: clinic.idklinik,
+ namaklinik: clinic.namaklinik,
+ code: clinic.code
+ });
+ }
+ });
+ }
+ });
- // Filter hanya JKN (REGULER) dan transform ke format internal
- const lokets = data
- .filter(item => {
- const hasJKN = item.pembayaran?.some(p => p.pembayaran === 'JKN');
- if (!hasJKN) {
- console.warn('⚠️ Loket skipped (no JKN):', item.namaloket, 'pembayaran:', item.pembayaran);
- }
- return hasJKN;
- })
- .map((item) => ({
- id: parseInt(item.idloket),
- no: 0, // Will be set after sort
- namaLoket: item.namaloket,
- kodeLoket: item.kodeloket,
- kuota: parseInt(item.kuotaloket),
- pelayanan: item.spesialis?.map(s => s.idklinik) || [],
- pembayaran: 'JKN',
- keterangan: item.tipevisit?.map(t => t.tipevisit).join(', ') || 'ONLINE',
- statusPelayanan: item.tipeloket || 'REGULER',
- loketAktif: item.loketaktif,
- jenisLoket: item.jenisloket,
-
- // Simpan detail lengkap spesialis untuk display nama klinik
- _spesialisDetail: item.spesialis || [],
- }));
+ const lokets = Array.from(loketMap.values());
- // Sort by nama loket (extract number and sort: LOKET 1, 2, 3,... 14)
+ // Sort by nama loket (LOKET 1, 2, ...)
lokets.sort((a, b) => {
const numA = parseInt(a.namaLoket.match(/\d+/)?.[0] || 0);
const numB = parseInt(b.namaLoket.match(/\d+/)?.[0] || 0);
return numA - numB;
});
- // Update `no` field to sequential after sort
+ // Set No sequential
lokets.forEach((loket, idx) => {
loket.no = idx + 1;
- loket.source = 'api'; // Mark as API data
});
- console.log(`✅ ${lokets.length} loket JKN berhasil di-filter dan dimuat dari API`);
- console.log('✅ Loket data:', lokets.map(l => ({ id: l.id, namaLoket: l.namaLoket, pembayaran: l.pembayaran })));
-
- // Update API store - TIDAK di-persist
apiLoketData.value = lokets;
lastSyncTimestamp.value = new Date().toISOString();
- console.log(`✅ ${lokets.length} loket JKN berhasil dimuat dari API`);
return {
success: true,
- message: `${lokets.length} loket berhasil dimuat`,
+ message: `${lokets.length} loket berhasil dipetakan dari klinik`,
data: lokets
};
} catch (error) {
- console.error('❌ Error fetching loket:', error);
+ console.error('❌ Error fetching loket config:', error);
apiError.value = error.message;
- return {
- success: false,
- message: `Gagal memuat data: ${error.message}`
- };
+ return { success: false, message: `Gagal memuat: ${error.message}` };
} finally {
isLoadingAPI.value = false;
activeFetchPromise = null;
- console.log('✅ API fetch flow completed');
}
};