'use client'; import { useState, useEffect, useRef } from 'react'; import type { AlertConfig } from '@/lib/types'; import { fetchAlerts, createAlert, updateAlert, deleteAlert } from '@/lib/api'; import { useApp } from '@/lib/context'; import { resolveName, resolveKey } from '@/lib/localization'; interface Props { open: boolean; onClose: () => void; } const inputCls = 'w-full bg-gray-800 border border-gray-600 rounded px-2 py-1 text-sm text-white focus:outline-none focus:border-indigo-500'; const selectCls = 'flex-1 bg-gray-800 border border-gray-600 rounded px-2 py-1 text-sm text-white focus:outline-none'; interface AlertFormState { itemKey: string; itemKeyIsRegex: boolean; combinator: string; signalType: 'green' | 'red'; condition: 'above' | 'below'; threshold: string; } function emptyForm(): AlertFormState { return { itemKey: '', itemKeyIsRegex: false, combinator: '', signalType: 'green', condition: 'below', threshold: '0', }; } function alertToForm(a: AlertConfig): AlertFormState { return { itemKey: a.item_key, itemKeyIsRegex: a.item_key_is_regex, combinator: a.combinator ?? '', signalType: a.signal_type, condition: a.condition, threshold: String(a.threshold), }; } function Tooltip({ text }: { text: string }) { return ( {text} ); } function AlertForm({ value, onChange, onSubmit, onCancel, submitLabel, }: { value: AlertFormState; onChange: (s: AlertFormState) => void; onSubmit: () => void; onCancel?: () => void; submitLabel: string; }) { return (
onChange({ ...value, itemKey: e.target.value })} placeholder={value.itemKeyIsRegex ? 'iron-.*|Iron Plate' : 'Iron Plate or item-key'} className={inputCls} /> onChange({ ...value, combinator: e.target.value })} placeholder="combinator (empty = all)" className={inputCls} />
onChange({ ...value, threshold: e.target.value })} placeholder="threshold" className={inputCls} />
{onCancel && ( )}
); } export default function AlertPanel({ open, onClose }: Props) { const { triggeredAlerts, refreshAlerts, localeMap, reverseMap } = useApp(); const [alerts, setAlerts] = useState([]); const [newForm, setNewForm] = useState(emptyForm()); const [editingId, setEditingId] = useState(null); const [editForm, setEditForm] = useState(emptyForm()); const prevTriggeredCount = useRef(0); useEffect(() => { if (open) fetchAlerts().then(setAlerts); }, [open]); useEffect(() => { if (triggeredAlerts.length > prevTriggeredCount.current) { try { const ctx = new AudioContext(); const osc = ctx.createOscillator(); const gain = ctx.createGain(); osc.connect(gain); gain.connect(ctx.destination); osc.frequency.value = 880; gain.gain.setValueAtTime(0.3, ctx.currentTime); gain.gain.exponentialRampToValueAtTime(0.001, ctx.currentTime + 0.4); osc.start(); osc.stop(ctx.currentTime + 0.4); } catch { /* AudioContext blocked */ } } prevTriggeredCount.current = triggeredAlerts.length; }, [triggeredAlerts.length]); /** Resolves item key input — skips resolution for regex mode. */ function normalizeItemKey(form: AlertFormState): string { if (form.itemKeyIsRegex) return form.itemKey.trim(); return resolveKey(form.itemKey.trim(), reverseMap); } async function handleCreate() { if (!newForm.itemKey.trim()) return; const created = await createAlert({ item_key: normalizeItemKey(newForm), item_key_is_regex: newForm.itemKeyIsRegex, combinator: newForm.combinator.trim() || null, signal_type: newForm.signalType, condition: newForm.condition, threshold: parseInt(newForm.threshold, 10), }); setAlerts((a) => [created, ...a]); setNewForm(emptyForm()); await refreshAlerts(); } async function handleEdit(id: string) { if (!editForm.itemKey.trim()) return; const updated = await updateAlert(id, { item_key: normalizeItemKey(editForm), item_key_is_regex: editForm.itemKeyIsRegex, combinator: editForm.combinator.trim() || null, signal_type: editForm.signalType, condition: editForm.condition, threshold: parseInt(editForm.threshold, 10), }); setAlerts((a) => a.map((x) => (x.id === id ? updated : x))); setEditingId(null); await refreshAlerts(); } async function handleDelete(id: string) { await deleteAlert(id); setAlerts((a) => a.filter((x) => x.id !== id)); await refreshAlerts(); } function startEdit(alert: AlertConfig) { setEditingId(alert.id); setEditForm(alertToForm(alert)); } return (
Alerts
{triggeredAlerts.length > 0 && (

🔴 TRIGGERED ({triggeredAlerts.length})

{triggeredAlerts.map((a, i) => (
{resolveName(a.matched_item_key, localeMap)} ({a.combinator_match}) [{a.signal_type}] = {a.current_value} {a.condition} {a.threshold} {a.item_key_is_regex && a.matched_item_key !== a.item_key && ( )}
))}
)}

New Alert

{alerts.length === 0 &&

No alerts configured.

} {alerts.map((a) => (
{editingId === a.id ? ( handleEdit(a.id)} onCancel={() => setEditingId(null)} submitLabel="Save" /> ) : (
{resolveName(a.item_key, localeMap)} {a.item_key_is_regex && } {a.combinator && @ {a.combinator}}
[{a.signal_type}] {' '} {a.condition} {a.threshold}
)}
))}
); }