2.9 KiB
2.9 KiB
F-074 — Architect: Audit shows real-time logs from application services
Current state
The admin audit page (/audit) fetches audit log entries once on mount via useEffect. There is no polling — new entries only appear after a page refresh.
Goal
Add polling to the audit page so new log entries appear automatically without refresh. Entries from application services (product edits, category changes, tax rate toggles, etc.) will show up in real-time on the audit page.
Scope IN
project/apps/admin/src/app/(dashboard)/audit/page.tsx:- Add a
useEffectwithsetInterval(5 second interval) that fetches the latest entries whenpage === 0(first page) - Prepend new entries to the list (deduplicate by
id) - Add a
liveIndicatorstate to show "● En vivo" when polling is active on page 0 - Stop polling when navigating to page > 0 or when filter is active
- Show count of new entries (e.g., "+3 nuevas" badge) when new entries arrive
- Clear new entry count when user views them
- Add a
Scope OUT
- Backend — no changes (existing
GET /admin/auditis sufficient for polling) - No SSE/WebSocket (simple polling is sufficient)
- No changes to the audit logger itself
Design
Polling strategy:
const [newCount, setNewCount] = useState(0);
const [pollingActive, setPollingActive] = useState(false);
useEffect(() => {
if (page !== 0 || debounced) { setPollingActive(false); return; }
setPollingActive(true);
const interval = setInterval(async () => {
try {
const data = await auditApi.list({ limit: PAGE_SIZE, offset: 0 });
const existingIds = new Set(items.map(i => i.id));
const newItems = (data.items ?? []).filter(i => !existingIds.has(i.id));
if (newItems.length > 0) {
setItems(prev => [...newItems, ...prev].slice(0, PAGE_SIZE));
setNewCount(prev => prev + newItems.length);
}
} catch { /* silent */ }
}, 5000);
return () => { clearInterval(interval); setPollingActive(false); };
}, [page, debounced, items]);
UI indicator:
// In header:
{pollingActive && (
<span className="inline-flex items-center gap-1.5 text-xs text-green-600 font-medium">
<span className="w-2 h-2 rounded-full bg-green-500 animate-pulse" />
En vivo
</span>
)}
{newCount > 0 && (
<button onClick={() => setNewCount(0)}
className="ml-2 px-2 py-0.5 bg-green-100 text-green-700 text-xs rounded-full">
+{newCount} nueva{newCount !== 1 ? 's' : ''}
</button>
)}
Risk
- Low risk: purely frontend polling, no backend or DB changes
- No performance concern: 5-second polling is lightweight
- Polling stops automatically when filter is active or on paginated views
Verification
npx tsc --noEmitadminnpx eslinton changed file./scripts/verify.shgreen- Manual: open audit page, perform an action (e.g. toggle a tax rate), observe new entry appears within 5 seconds