2025-12-18 11:21:40 +07:00
|
|
|
// public/dashboard/js/api.js
|
|
|
|
|
// Centralized REST API client for Btekno Retribusi Admin Dashboard
|
|
|
|
|
|
|
|
|
|
import { API_CONFIG } from './config.js';
|
|
|
|
|
|
|
|
|
|
// Export API_CONFIG untuk digunakan di file lain
|
|
|
|
|
export { API_CONFIG };
|
|
|
|
|
|
|
|
|
|
const API_BASE_URL = API_CONFIG.BASE_URL;
|
|
|
|
|
|
|
|
|
|
function getToken() {
|
|
|
|
|
return localStorage.getItem('token') || '';
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function apiRequest(path, options = {}) {
|
|
|
|
|
const url = path.startsWith('http') ? path : `${API_BASE_URL}${path}`;
|
|
|
|
|
|
|
|
|
|
const headers = {
|
|
|
|
|
'Content-Type': 'application/json',
|
|
|
|
|
// X-API-KEY dari konfigurasi backend (RETRIBUSI_API_KEY)
|
|
|
|
|
'X-API-KEY': API_CONFIG.API_KEY,
|
|
|
|
|
...(options.headers || {})
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const token = getToken();
|
|
|
|
|
if (token) {
|
|
|
|
|
headers['Authorization'] = `Bearer ${token}`;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const config = {
|
|
|
|
|
method: options.method || 'GET',
|
|
|
|
|
headers,
|
|
|
|
|
body: options.body ? JSON.stringify(options.body) : null
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
const res = await fetch(url, config);
|
|
|
|
|
|
|
|
|
|
if (res.status === 401) {
|
|
|
|
|
// Unauthorized → clear token & redirect to login
|
|
|
|
|
localStorage.removeItem('token');
|
|
|
|
|
localStorage.removeItem('user');
|
2025-12-18 11:37:54 +07:00
|
|
|
sessionStorage.removeItem('auth_redirect_done');
|
2025-12-18 11:34:20 +07:00
|
|
|
// Cek apakah sudah di login page untuk menghindari redirect loop
|
|
|
|
|
const currentPath = window.location.pathname;
|
2025-12-18 11:37:54 +07:00
|
|
|
const isLoginPage = currentPath.includes('index.php');
|
|
|
|
|
if (!isLoginPage) {
|
2025-12-18 11:34:20 +07:00
|
|
|
window.location.href = '../index.php';
|
|
|
|
|
}
|
2025-12-18 11:21:40 +07:00
|
|
|
throw new Error('Unauthorized');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const text = await res.text();
|
|
|
|
|
let json;
|
|
|
|
|
try {
|
|
|
|
|
json = text ? JSON.parse(text) : {};
|
|
|
|
|
} catch (e) {
|
|
|
|
|
json = { raw: text };
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!res.ok) {
|
|
|
|
|
const msg = json.message || json.error || `HTTP ${res.status}`;
|
|
|
|
|
throw new Error(msg);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Some endpoints might wrap data as { success, data, ... }
|
|
|
|
|
if (json && Object.prototype.hasOwnProperty.call(json, 'success') &&
|
|
|
|
|
Object.prototype.hasOwnProperty.call(json, 'data')) {
|
|
|
|
|
return json.data;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return json;
|
|
|
|
|
} catch (err) {
|
|
|
|
|
console.error('API error', { url, error: err });
|
|
|
|
|
throw err;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Helper untuk build query string dari object params
|
|
|
|
|
function buildQuery(params = {}) {
|
|
|
|
|
const search = new URLSearchParams();
|
|
|
|
|
Object.entries(params).forEach(([key, value]) => {
|
|
|
|
|
if (value !== undefined && value !== null && value !== '') {
|
|
|
|
|
search.append(key, value);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
const qs = search.toString();
|
|
|
|
|
return qs ? `?${qs}` : '';
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Typed helpers
|
|
|
|
|
|
|
|
|
|
export async function apiLogin(username, password) {
|
|
|
|
|
return apiRequest('/auth/v1/login', {
|
|
|
|
|
method: 'POST',
|
|
|
|
|
body: { username, password }
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export async function apiGetLocations(params = {}) {
|
|
|
|
|
// Handle pagination: { page, limit }
|
|
|
|
|
const qs = buildQuery(params);
|
|
|
|
|
return apiRequest(`/retribusi/v1/frontend/locations${qs}`);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export async function apiGetGates(locationCode, params = {}) {
|
|
|
|
|
// Handle pagination: { page, limit, location_code }
|
|
|
|
|
const queryParams = { ...params };
|
|
|
|
|
if (locationCode) queryParams.location_code = locationCode;
|
|
|
|
|
const qs = buildQuery(queryParams);
|
|
|
|
|
return apiRequest(`/retribusi/v1/frontend/gates${qs}`);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export async function apiGetSummary({ date, locationCode, gateCode }) {
|
|
|
|
|
const qs = buildQuery({
|
|
|
|
|
date,
|
|
|
|
|
location_code: locationCode,
|
|
|
|
|
gate_code: gateCode
|
|
|
|
|
});
|
|
|
|
|
return apiRequest(`/retribusi/v1/dashboard/summary${qs}`);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export async function apiGetDaily({ startDate, endDate, locationCode }) {
|
|
|
|
|
const qs = buildQuery({
|
|
|
|
|
start_date: startDate,
|
|
|
|
|
end_date: endDate,
|
|
|
|
|
location_code: locationCode
|
|
|
|
|
});
|
|
|
|
|
return apiRequest(`/retribusi/v1/dashboard/daily${qs}`);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export async function apiGetByCategory({ date, locationCode, gateCode }) {
|
|
|
|
|
const qs = buildQuery({
|
|
|
|
|
date,
|
|
|
|
|
location_code: locationCode,
|
|
|
|
|
gate_code: gateCode
|
|
|
|
|
});
|
|
|
|
|
return apiRequest(`/retribusi/v1/dashboard/by-category${qs}`);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Ringkasan global harian (daily_summary)
|
|
|
|
|
export async function apiGetSummaryDaily(params = {}) {
|
|
|
|
|
const qs = buildQuery(params);
|
|
|
|
|
return apiRequest(`/retribusi/v1/summary/daily${qs}`);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Ringkasan per jam (hourly_summary)
|
|
|
|
|
export async function apiGetSummaryHourly(params = {}) {
|
|
|
|
|
const qs = buildQuery(params);
|
|
|
|
|
return apiRequest(`/retribusi/v1/summary/hourly${qs}`);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Snapshot realtime (untuk panel live / TV wall)
|
|
|
|
|
export async function apiGetRealtimeSnapshot(params = {}) {
|
|
|
|
|
const qs = buildQuery(params);
|
|
|
|
|
return apiRequest(`/retribusi/v1/realtime/snapshot${qs}`);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Entry events list (raw events dari mesin YOLO)
|
|
|
|
|
// GET /retribusi/v1/frontend/entry-events
|
|
|
|
|
// Parameters: page, limit, location_code, gate_code, category, start_date, end_date
|
|
|
|
|
export async function apiGetEntryEvents(params = {}) {
|
|
|
|
|
const qs = buildQuery(params);
|
|
|
|
|
return apiRequest(`/retribusi/v1/frontend/entry-events${qs}`);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Realtime events list (history untuk SSE)
|
|
|
|
|
// GET /retribusi/v1/realtime/events
|
|
|
|
|
// Parameters: page, limit, location_code, gate_code, category, start_date, end_date
|
|
|
|
|
export async function apiGetRealtimeEvents(params = {}) {
|
|
|
|
|
const qs = buildQuery(params);
|
|
|
|
|
return apiRequest(`/retribusi/v1/realtime/events${qs}`);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Alias untuk backward compatibility
|
|
|
|
|
export async function apiGetEvents(params = {}) {
|
|
|
|
|
return apiGetEntryEvents(params);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Catatan: realtime SSE /retribusi/v1/realtime/stream akan diakses langsung via EventSource,
|
|
|
|
|
// bukan lewat fetch/apiRequest karena menggunakan Server-Sent Events (SSE).
|
|
|
|
|
|