Initial commit: Retribusi frontend dengan dashboard, event logs, dan settings
This commit is contained in:
160
public/dashboard/js/realtime.js
Normal file
160
public/dashboard/js/realtime.js
Normal file
@@ -0,0 +1,160 @@
|
||||
// public/dashboard/js/realtime.js
|
||||
// Realtime dashboard (SSE + fallback snapshot)
|
||||
|
||||
import { apiGetRealtimeSnapshot } from './api.js';
|
||||
import { API_CONFIG } from './config.js';
|
||||
|
||||
const REALTIME_STREAM_URL = `${API_CONFIG.BASE_URL}/retribusi/v1/realtime/stream`;
|
||||
|
||||
class RealtimeManager {
|
||||
constructor() {
|
||||
this.eventSource = null;
|
||||
this.snapshotTimer = null;
|
||||
this.isConnected = false;
|
||||
}
|
||||
|
||||
init() {
|
||||
// Mulai SSE, kalau gagal pakai fallback polling snapshot
|
||||
this.startSSE();
|
||||
this.startSnapshotFallback();
|
||||
}
|
||||
|
||||
startSSE() {
|
||||
try {
|
||||
const token = localStorage.getItem('token') || '';
|
||||
// SSE tidak support custom headers, jadi token harus di query string
|
||||
// TAPI sesuai spec, parameter yang benar adalah 'last_id', bukan 'token'
|
||||
// Token tetap dikirim via Authorization header jika backend support
|
||||
// Untuk SSE, kita pakai query string karena EventSource tidak support custom headers
|
||||
const params = new URLSearchParams();
|
||||
// Jika ada last_id dari state, tambahkan
|
||||
// params.append('last_id', this.lastEventId || '');
|
||||
// Token tetap di query string untuk SSE (limitation EventSource)
|
||||
if (token) {
|
||||
// Backend harus handle token dari query string atau implement custom SSE handler
|
||||
// Untuk sekarang, kita tetap pakai query string karena EventSource limitation
|
||||
params.append('token', token);
|
||||
}
|
||||
const url = `${REALTIME_STREAM_URL}?${params.toString()}`;
|
||||
|
||||
console.log('[Realtime] Connect SSE:', url);
|
||||
|
||||
// EventSource tidak support custom headers, jadi token harus di query string
|
||||
// Backend harus handle ini atau implement custom SSE handler dengan fetch + stream
|
||||
this.eventSource = new EventSource(url);
|
||||
|
||||
this.eventSource.onopen = () => {
|
||||
console.log('[Realtime] SSE opened');
|
||||
this.isConnected = true;
|
||||
};
|
||||
|
||||
this.eventSource.onmessage = (event) => {
|
||||
try {
|
||||
const data = JSON.parse(event.data);
|
||||
console.log('[Realtime] SSE event:', data);
|
||||
|
||||
// Backend kirim realtime_events, bisa berupa:
|
||||
// - event baru (entry baru)
|
||||
// - snapshot agregat (total_count_today, by_category, dll)
|
||||
// Sesuaikan dengan struktur yang backend kirim via SSE
|
||||
|
||||
// Jika backend kirim snapshot via SSE, parse sama seperti fetchSnapshot()
|
||||
if (data.total_count_today !== undefined || data.by_category) {
|
||||
const personCat = (data.by_category || []).find(c => c.category === 'person_walk') || { total_count: 0 };
|
||||
const motorCat = (data.by_category || []).find(c => c.category === 'motor') || { total_count: 0 };
|
||||
const carCat = (data.by_category || []).find(c => c.category === 'car') || { total_count: 0 };
|
||||
|
||||
const kpiData = {
|
||||
totalPeople: personCat.total_count || 0,
|
||||
totalVehicles: (motorCat.total_count || 0) + (carCat.total_count || 0),
|
||||
totalCount: data.total_count_today || 0,
|
||||
totalAmount: data.total_amount_today || 0
|
||||
};
|
||||
|
||||
window.dispatchEvent(new CustomEvent('realtime:snapshot', { detail: kpiData }));
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn('[Realtime] gagal parse event data', e, event.data);
|
||||
}
|
||||
};
|
||||
|
||||
this.eventSource.onerror = (err) => {
|
||||
console.error('[Realtime] SSE error', err);
|
||||
this.isConnected = false;
|
||||
// Biarkan browser auto-reconnect, kalau tetap gagal nanti fallback snapshot yang jalan
|
||||
};
|
||||
} catch (e) {
|
||||
console.error('[Realtime] tidak bisa inisialisasi SSE', e);
|
||||
this.isConnected = false;
|
||||
}
|
||||
}
|
||||
|
||||
async fetchSnapshot() {
|
||||
try {
|
||||
// Struktur response setelah di-unwrap: { total_count_today, total_amount_today, by_gate, by_category }
|
||||
const snapshot = await apiGetRealtimeSnapshot({
|
||||
date: new Date().toISOString().split('T')[0],
|
||||
location_code: '' // bisa diambil dari state dashboard jika perlu
|
||||
});
|
||||
|
||||
console.log('[Realtime] snapshot:', snapshot);
|
||||
|
||||
// Parse data snapshot sesuai struktur resmi
|
||||
const parsed = {
|
||||
totalCount: snapshot.total_count_today || 0,
|
||||
totalAmount: snapshot.total_amount_today || 0,
|
||||
byGate: Array.isArray(snapshot.by_gate) ? snapshot.by_gate : [],
|
||||
byCategory: Array.isArray(snapshot.by_category) ? snapshot.by_category : []
|
||||
};
|
||||
|
||||
// Hitung total orang & kendaraan dari by_category
|
||||
const personCat = parsed.byCategory.find(c => c.category === 'person_walk') || { total_count: 0 };
|
||||
const motorCat = parsed.byCategory.find(c => c.category === 'motor') || { total_count: 0 };
|
||||
const carCat = parsed.byCategory.find(c => c.category === 'car') || { total_count: 0 };
|
||||
|
||||
const kpiData = {
|
||||
totalPeople: personCat.total_count || 0,
|
||||
totalVehicles: (motorCat.total_count || 0) + (carCat.total_count || 0),
|
||||
totalCount: parsed.totalCount,
|
||||
totalAmount: parsed.totalAmount
|
||||
};
|
||||
|
||||
// Dispatch event untuk update dashboard real-time
|
||||
window.dispatchEvent(new CustomEvent('realtime:snapshot', { detail: kpiData }));
|
||||
|
||||
} catch (e) {
|
||||
console.error('[Realtime] gagal ambil snapshot', e);
|
||||
}
|
||||
}
|
||||
|
||||
startSnapshotFallback() {
|
||||
// Polling ringan tiap 5 detik, hanya kalau SSE belum stable
|
||||
if (this.snapshotTimer) clearInterval(this.snapshotTimer);
|
||||
this.snapshotTimer = setInterval(() => {
|
||||
if (!this.isConnected) {
|
||||
this.fetchSnapshot();
|
||||
}
|
||||
}, 5000);
|
||||
}
|
||||
|
||||
stop() {
|
||||
if (this.eventSource) {
|
||||
this.eventSource.close();
|
||||
this.eventSource = null;
|
||||
}
|
||||
if (this.snapshotTimer) {
|
||||
clearInterval(this.snapshotTimer);
|
||||
this.snapshotTimer = null;
|
||||
}
|
||||
this.isConnected = false;
|
||||
}
|
||||
}
|
||||
|
||||
export const Realtime = new RealtimeManager();
|
||||
|
||||
// Auto-init saat dashboard dibuka
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
Realtime.init();
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user