import { supabase } from './supabase'; // Types export interface HistoryRow { id?: number; created_at?: string; v_batt: number; battery_pct: number; v_in?: number; v_out?: number; line_state: number; power_on: boolean; } export interface EventRow { id?: number; created_at?: string; message: string; severity: 'info' | 'success' | 'warning' | 'error'; } let dbUnavailable = false; let warnedUnavailable = false; let retryTimeoutId: ReturnType | null = null; function isNetworkError(error: unknown) { const message = error instanceof Error ? error.message : String(error ?? ''); return /failed to fetch|fetch failed|networkerror|load failed/i.test(message); } function logDbIssue(operation: string, error: unknown) { const message = error instanceof Error ? error.message : String(error ?? 'Unknown error'); if (isNetworkError(error)) { dbUnavailable = true; if (!warnedUnavailable) { warnedUnavailable = true; console.warn( `[DB] Supabase tidak bisa dijangkau (${message}). ` + 'Dashboard tetap berjalan tanpa sinkronisasi database. ' + 'Periksa NEXT_PUBLIC_SUPABASE_URL dan koneksi jaringan.' ); } // Auto-retry after 60 seconds if (!retryTimeoutId) { retryTimeoutId = setTimeout(() => { dbUnavailable = false; warnedUnavailable = false; retryTimeoutId = null; console.info('[DB] Retrying Supabase connection...'); }, 60000); } return; } console.warn(`[DB] ${operation} error:`, message); } function shouldSkipDb() { return dbUnavailable; } // History /** * Simpan satu titik data history ke Supabase. */ export async function insertHistoryPoint(row: HistoryRow) { if (shouldSkipDb()) return; try { const { error } = await supabase.from('ups_history').insert([row]); if (error) logDbIssue('insertHistoryPoint', error); } catch (error) { logDbIssue('insertHistoryPoint', error); } } /** * Ambil N data history terakhir dari Supabase, diurutkan dari terlama ke terbaru. */ export async function fetchHistory(limit = 100): Promise { if (shouldSkipDb()) return []; try { const { data, error } = await supabase .from('ups_history') .select('*') .order('created_at', { ascending: false }) .limit(limit); if (error) { logDbIssue('fetchHistory', error); return []; } return (data as HistoryRow[]).reverse(); } catch (error) { logDbIssue('fetchHistory', error); return []; } } /** * Ambil data history dari Supabase dalam rentang tanggal tertentu. * @param from ISO string (misal "2024-05-01T00:00:00.000Z") * @param to ISO string (misal "2024-05-07T23:59:59.999Z") * @param limit maksimum jumlah baris yang diambil */ export async function fetchHistoryByRange(from: string, to: string, limit = 2000): Promise { if (shouldSkipDb()) return []; try { const { data, error } = await supabase .from('ups_history') .select('*') .gte('created_at', from) .lte('created_at', to) .order('created_at', { ascending: true }) .limit(limit); if (error) { logDbIssue('fetchHistoryByRange', error); return []; } return data as HistoryRow[]; } catch (error) { logDbIssue('fetchHistoryByRange', error); return []; } } // Events /** * Simpan satu event log ke Supabase. */ export async function insertEvent(row: EventRow) { if (shouldSkipDb()) return; try { const { error } = await supabase.from('ups_events').insert([row]); if (error) logDbIssue('insertEvent', error); } catch (error) { logDbIssue('insertEvent', error); } } /** * Ambil N event log terakhir dari Supabase, diurutkan dari terbaru ke terlama. */ export async function fetchEvents(limit = 200): Promise { if (shouldSkipDb()) return []; try { const { data, error } = await supabase .from('ups_events') .select('*') .order('created_at', { ascending: false }) .limit(limit); if (error) { logDbIssue('fetchEvents', error); return []; } return data as EventRow[]; } catch (error) { logDbIssue('fetchEvents', error); return []; } } /** * Hapus semua event log dari Supabase. */ export async function clearAllEvents() { if (shouldSkipDb()) return; try { const { error } = await supabase .from('ups_events') .delete() .gte('id', 0); // delete all rows if (error) logDbIssue('clearAllEvents', error); } catch (error) { logDbIssue('clearAllEvents', error); } }