s))("Online")} ${x.on}m`} style={{ width: w(x.on), background: 'var(--ok, #16a34a)' }} />
s))("Away")} ${x.aw}m`} style={{ width: w(x.aw), background: 'var(--warn, #d97706)' }} />
s))("Offline")} ${x.off}m`} style={{ width: w(x.off), background: 'var(--text-3, #94a3b8)' }} />
{x.on}m
);
})}
);
}
/* signin-histogram — hours-of-day distribution from today's /api/attendance.
(No 14-day historical endpoint is exposed publicly — we render today's
distribution and label that clearly so we're honest about the source.) */
function _HudSigninHistogram() {
const [rows, setRows] = _hudUseState(null);
const [err, setErr] = _hudUseState(null);
_hudUseEffect(() => {
let cancelled = false;
fetch('/api/attendance', { credentials: 'same-origin' })
.then(r => r.ok ? r.json() : Promise.reject(r.status))
.then(d => { if (!cancelled) setRows(Array.isArray(d) ? d : []); })
.catch(e => { if (!cancelled) setErr(String(e)); });
return () => { cancelled = true; };
}, []);
if (err) return
{(window.appT||(s=>s))("Error")}: {err}
;
if (rows === null) return
{(window.appT||(s=>s))("Loading…")}
;
const bins = new Array(24).fill(0);
rows.forEach(r => {
if (!r.sign_in_at) return;
const d = new Date(r.sign_in_at);
if (isNaN(d)) return;
bins[d.getHours()] += 1;
});
const data = bins.map((v, h) => {
const am = h === 0 ? '12a' : h < 12 ? `${h}a` : h === 12 ? '12p' : `${h - 12}p`;
return { label: am, value: v };
});
const nonzero = data.some(x => x.value > 0);
if (!nonzero) return
{(window.appT||(s=>s))("No sign-ins yet today.")}
;
const BC = window.BarChart;
if (!BC) return
{(window.appT||(s=>s))("BarChart not loaded.")}
;
return (
{(window.appT||(s=>s))("Today's sign-ins by hour (12-hour)")}
);
}
/* audit-recent — last 20 audit events */
function _HudAuditRecent() {
const [rows, setRows] = _hudUseState(null);
const [err, setErr] = _hudUseState(null);
_hudUseEffect(() => {
let cancelled = false;
fetch('/api/audit?limit=20', { credentials: 'same-origin' })
.then(r => r.ok ? r.json() : Promise.reject(r.status))
.then(d => { if (!cancelled) setRows(Array.isArray(d) ? d : []); })
.catch(e => { if (!cancelled) setErr(String(e)); });
return () => { cancelled = true; };
}, []);
if (err) return
{(window.appT||(s=>s))("Error")}: {err}
;
if (rows === null) return
{(window.appT||(s=>s))("Loading audit…")}
;
if (!rows.length) return
{(window.appT||(s=>s))("No audit events.")}
;
return (
{(window.appT||(s=>s))("When")}
{/* #6/#10 extension: Actor = who DID this (admin name, bot label
if bot-initiated, derived from API's `actor` field). Subject
= the employee it concerns (kept as a sub-line). */}
{(window.appT||(s=>s))("Actor · Subject")}
{(window.appT||(s=>s))("Action")}
{rows.map((r, i) => {
const actor = r.actor || null;
const subject = r.user_display_name || null;
return (
{_hudFmt12Date(r.occurred_at)}
{actor || subject || '—'}
{actor && subject && actor !== subject && (
→ {subject}
)}
{/* Issue #32: humanized label (API provides action_label;
local table fallback via window.humanizeActionType). */}
{r.action_label || (window.humanizeActionType ? window.humanizeActionType(r.action_type) : r.action_type) || '—'}
);
})}
);
}
/* pto-calendar — current month, cells colored by PTO count */
function _HudPtoCalendar() {
const [rows, setRows] = _hudUseState(null);
const [err, setErr] = _hudUseState(null);
const now = new Date();
const year = now.getFullYear(), month = now.getMonth();
_hudUseEffect(() => {
let cancelled = false;
const first = new Date(year, month, 1);
const last = new Date(year, month + 1, 0);
const iso = (d) => d.toISOString().slice(0, 10);
const url = `/api/pto?from_date=${iso(first)}&to_date=${iso(last)}`;
fetch(url, { credentials: 'same-origin' })
.then(r => r.ok ? r.json() : Promise.reject(r.status))
.then(d => { if (!cancelled) setRows(Array.isArray(d) ? d : []); })
.catch(e => { if (!cancelled) setErr(String(e)); });
return () => { cancelled = true; };
}, [year, month]);
if (err) return
{(window.appT||(s=>s))("Error")}: {err}
;
if (rows === null) return
{(window.appT||(s=>s))("Loading PTO…")}
;
// Count PTO bodies per day.
const counts = {};
rows.forEach(p => {
const s = new Date(p.start_date), e = new Date(p.end_date);
for (let d = new Date(s); d <= e; d.setDate(d.getDate() + 1)) {
if (d.getFullYear() === year && d.getMonth() === month) {
const k = d.getDate();
counts[k] = (counts[k] || 0) + 1;
}
}
});
const daysInMonth = new Date(year, month + 1, 0).getDate();
const firstDow = new Date(year, month, 1).getDay(); // 0=Sun
const cells = [];
for (let i = 0; i < firstDow; i++) cells.push(null);
for (let d = 1; d <= daysInMonth; d++) cells.push(d);
while (cells.length % 7 !== 0) cells.push(null);
const max = Math.max(1, ...Object.values(counts));
const colorFor = (n) => {
if (!n) return 'rgba(255,255,255,0.04)';
const t = Math.min(1, n / max);
// Blend brand-blue → warn for heat
const r = Math.round(74 + (217 - 74) * t);
const g = Math.round(170 + (119 - 170) * t);
const b = Math.round(255 + (6 - 255) * t);
return `rgb(${r},${g},${b})`;
};
const todayDay = (now.getMonth() === month && now.getFullYear() === year) ? now.getDate() : -1;
const monthName = now.toLocaleString([], { month: 'long', year: 'numeric' });
const dows = [
(window.appT||(s=>s))('Su'),
(window.appT||(s=>s))('Mo'),
(window.appT||(s=>s))('Tu'),
(window.appT||(s=>s))('We'),
(window.appT||(s=>s))('Th'),
(window.appT||(s=>s))('Fr'),
(window.appT||(s=>s))('Sa'),
];
return (
{monthName} — {rows.length} {(window.appT||(s=>s))("PTO blocks")}
{dows.map((d, i) => (
{d}
))}
{cells.map((d, i) => d == null ? (
) : (
s))("on PTO")}`}
style={{
background: colorFor(counts[d] || 0),
borderRadius: 3,
padding: '6px 4px',
textAlign: 'center',
fontFamily: 'var(--font-mono)',
fontSize: 11,
color: counts[d] ? '#0b0f14' : 'var(--text-2)',
outline: d === todayDay ? '1px solid var(--brand, #4af)' : 'none',
fontWeight: counts[d] ? 700 : 400,
}}>
{d}{counts[d] ?
×{counts[d]}
: null}
))}
);
}
/* schema-browser — DB table/column introspection via /api/ai/hud-schema */
function _HudSchemaBrowser() {
const [tables, setTables] = _hudUseState(null);
const [err, setErr] = _hudUseState(null);
const [selected, setSelected] = _hudUseState(null);
_hudUseEffect(() => {
let cancelled = false;
fetch('/api/ai/hud-schema', { credentials: 'same-origin' })
.then(r => r.ok ? r.json() : Promise.reject(r.status))
.then(d => {
if (!cancelled) {
const arr = Array.isArray(d) ? d : [];
setTables(arr);
if (arr.length) setSelected(arr[0].name);
}
})
.catch(e => { if (!cancelled) setErr(String(e)); });
return () => { cancelled = true; };
}, []);
if (err) return
{(window.appT||(s=>s))("Error")}: {err}
;
if (tables === null) return
{(window.appT||(s=>s))("Loading schema…")}
;
if (!tables.length) return
{(window.appT||(s=>s))("No tables found.")}
;
const tbl = tables.find(t => t.name === selected) || tables[0];
return (
{tables.map(t => (
setSelected(t.name)}
style={{
padding: '3px 6px',
borderRadius: 3,
cursor: 'pointer',
fontSize: 11,
fontFamily: 'var(--font-mono)',
color: t.name === selected ? 'var(--brand, #4af)' : 'var(--text-2)',
background: t.name === selected ? 'rgba(74,170,255,0.12)' : 'transparent',
marginBottom: 2,
}}>
{t.name}
))}
{tbl.doc &&
{tbl.doc}
}
{(window.appT||(s=>s))("Name")}
{(window.appT||(s=>s))("Type")}
{(window.appT||(s=>s))("Nullable")}
{(window.appT||(s=>s))("Default")}
{(window.appT||(s=>s))("PK")}
{(tbl.columns || []).map((c, i) => (
{c.name}
{c.type}
{c.nullable ? (window.appT||(s=>s))('yes') : (window.appT||(s=>s))('no')}
{c.default || '—'}
{c.pk ? 'PK' : ''}
))}
);
}
/* code-search — debounced grep of app/*.py via /api/ai/hud-codebase */
function _HudCodeSearch() {
const [query, setQuery] = _hudUseState('');
const [results, setResults] = _hudUseState(null);
const [err, setErr] = _hudUseState(null);
const [loading, setLoading] = _hudUseState(false);
const timerRef = React.useRef(null);
_hudUseEffect(() => {
const q = query.trim();
if (!q) { setResults(null); setErr(null); return; }
clearTimeout(timerRef.current);
timerRef.current = setTimeout(() => {
setLoading(true);
setErr(null);
fetch(`/api/ai/hud-codebase?q=${encodeURIComponent(q)}`, { credentials: 'same-origin' })
.then(r => r.ok ? r.json() : Promise.reject(r.status))
.then(d => { setResults(Array.isArray(d) ? d : []); setLoading(false); })
.catch(e => { setErr(String(e)); setLoading(false); });
}, 300);
return () => clearTimeout(timerRef.current);
}, [query]);
return (
setQuery(e.target.value)}
placeholder={(window.appT||(s=>s))("Type a search term…")}
style={{
background: 'rgba(255,255,255,0.06)',
border: '1px solid rgba(74,170,255,0.35)',
borderRadius: 4,
color: 'var(--text-1)',
fontFamily: 'var(--font-mono)',
fontSize: 12,
padding: '6px 10px',
outline: 'none',
width: '100%',
boxSizing: 'border-box',
}}
/>
{loading &&
{(window.appT||(s=>s))("Searching…")}
}
{err &&
{(window.appT||(s=>s))("Error")}: {err}
}
{results !== null && !loading && (
results.length === 0
?
{(window.appT||(s=>s))("No matches.")}
:
{results.map((m, i) => (
{m.path}
:{m.line}
{m.snippet}
))}
)}
{results === null && !loading && !err && (
{(window.appT||(s=>s))("Type a search term to grep app/*.py")}
)}
);
}
const HUD_WIDGETS = {
'today-roster': { id: 'today-roster', title: (window.appT||(s=>s))('Today — Roster'), render: () => <_HudBoundary><_HudTodayRoster /> },
'hours-bar': { id: 'hours-bar', title: (window.appT||(s=>s))('Hours Worked Today'), render: () => <_HudBoundary><_HudHoursBar /> },
'attendance-pie': { id: 'attendance-pie', title: (window.appT||(s=>s))('Attendance Breakdown'), render: () => <_HudBoundary><_HudAttendancePie /> },
'compliance-table': { id: 'compliance-table', title: (window.appT||(s=>s))('Compliance — strikes per user'), render: () => <_HudBoundary><_HudCompliance /> },
'monthly-hours-grid': { id: 'monthly-hours-grid', title: (window.appT||(s=>s))('Hours — week trend'), render: () => <_HudBoundary><_HudMonthlyHours /> },
'presence-now': { id: 'presence-now', title: (window.appT||(s=>s))('Live K9 presence'), render: () => <_HudBoundary><_HudPresence /> },
'signin-histogram': { id: 'signin-histogram', title: (window.appT||(s=>s))('Sign-in time distribution'), render: () => <_HudBoundary><_HudSigninHistogram /> },
'audit-recent': { id: 'audit-recent', title: (window.appT||(s=>s))('Recent audit events'), render: () => <_HudBoundary><_HudAuditRecent /> },
'pto-calendar': { id: 'pto-calendar', title: (window.appT||(s=>s))('PTO calendar — this month'), render: () => <_HudBoundary><_HudPtoCalendar /> },
'schema-browser': { id: 'schema-browser', title: (window.appT||(s=>s))('DB Schema Browser'), render: () => <_HudBoundary><_HudSchemaBrowser /> },
'code-search': { id: 'code-search', title: (window.appT||(s=>s))('Codebase Search'), render: () => <_HudBoundary><_HudCodeSearch /> },
};
const DEFAULT_WIDGETS = [HUD_WIDGETS['today-roster'], HUD_WIDGETS['hours-bar'], HUD_WIDGETS['attendance-pie']];
/* ---------- main HUD component ---------- */
// Phase 6 — dedupe set persists for the full page session (module scope).
const _hudSeenEventIds = new Set();
function HUD() {
const { open, widgets, title, openHUD, closeHUD } = useHUD();
const [proactiveToast, setProactiveToast] = _hudUseState(null); // {phrase, id}
const [mutedAutoOpen, setMutedAutoOpen] = _hudUseState(
() => localStorage.getItem('hud_proactive_off') === '1'
);
// Phase 6 — SSE connection for proactive HUD events.
_hudUseEffect(() => {
let es;
try {
es = new EventSource('/api/hud/stream', { withCredentials: true });
} catch (_) { return; }
es.addEventListener('hud_proactive', (e) => {
try {
const ev = JSON.parse(e.data);
const evId = ev.id || (ev.kind + ':' + ev.created_at);
// Dedupe: skip already-seen event ids.
if (_hudSeenEventIds.has(evId)) return;
_hudSeenEventIds.add(evId);
// Cap set size to avoid unbounded growth on long-lived tabs.
if (_hudSeenEventIds.size > 200) {
const first = _hudSeenEventIds.values().next().value;
_hudSeenEventIds.delete(first);
}
const widgetList = (ev.widgets || []).map(id => HUD_WIDGETS[id]).filter(Boolean);
const phrase = ev.phrase || (window.appT||(s=>s))('Proactive update');
// Read mute preference at receipt time (not just mount time).
const muted = localStorage.getItem('hud_proactive_off') === '1';
if (window.__hudState__.open) {
// HUD already open: surface non-blocking toast.
setProactiveToast({ phrase, id: evId });
setTimeout(() => setProactiveToast(t => t && t.id === evId ? null : t), 8000);
} else if (!muted && widgetList.length) {
openHUD(widgetList, (window.appT||(s=>s))('Auto: ') + phrase.slice(0, 60));
}
} catch (_) { /* parse error — ignore */ }
});
return () => { try { es.close(); } catch (_) {} };
}, [openHUD]);
// Sync mute toggle to localStorage.
const toggleMute = _hudUseCallback(() => {
setMutedAutoOpen(prev => {
const next = !prev;
if (next) localStorage.setItem('hud_proactive_off', '1');
else localStorage.removeItem('hud_proactive_off');
return next;
});
}, []);
// Global keybind: Cmd+H / Ctrl+H opens with defaults
_hudUseEffect(() => {
const onKey = (e) => {
const isMod = e.metaKey || e.ctrlKey;
if (isMod && (e.key === 'h' || e.key === 'H')) {
// Avoid hijacking browser's Hide-Window (macOS Cmd+H is OS-level on
// some apps but Chrome forwards it). Only open when not already open.
e.preventDefault();
if (!window.__hudState__.open) {
openHUD(DEFAULT_WIDGETS, (window.appT||(s=>s))('Tactical Overview'));
} else {
closeHUD();
}
}
if (e.key === 'Escape' && window.__hudState__.open) {
e.preventDefault();
closeHUD();
}
};
window.addEventListener('keydown', onKey);
return () => window.removeEventListener('keydown', onKey);
}, [openHUD, closeHUD]);
// Phase 3 — deep-link `?hud=id1,id2&q=…` opens the HUD on page load with
// the named widgets. Used by the bot's "Open in new tab" affordance and by
// shareable HUD URLs. Runs once on mount.
_hudUseEffect(() => {
try {
const params = new URLSearchParams(window.location.search);
const ids = (params.get('hud') || '').split(',').map(s => s.trim()).filter(Boolean);
if (!ids.length) return;
const list = ids.map(i => HUD_WIDGETS[i]).filter(Boolean);
if (!list.length) return;
const q = params.get('q') || '';
openHUD(list, q ? `From: "${q.slice(0, 60)}"` : (window.appT||(s=>s))('Deep-linked'));
// Strip the params from the URL so a refresh doesn't keep re-opening.
const url = new URL(window.location.href);
url.searchParams.delete('hud');
url.searchParams.delete('q');
window.history.replaceState({}, '', url.toString());
} catch (_) { /* no-op */ }
}, [openHUD]);
// Phase 6: proactive toast rendered even when HUD is closed
// (so we can show it as a floating notification without opening the overlay).
const toastEl = proactiveToast ? (
{(window.appT||(s=>s))("Proactive")}:
{proactiveToast.phrase}
setProactiveToast(null)} style={{
background: 'transparent', border: 'none', color: '#9aa6b6',
cursor: 'pointer', fontSize: 16, lineHeight: 1, padding: 0,
}}>x
) : null;
if (!open) return toastEl;
// Single declaration only — a prior edit accidentally duplicated this
// pair, which crashed babel-standalone with "Identifier 'list' has
// already been declared" and left the whole React dashboard blank.
const list = (widgets && widgets.length) ? widgets : DEFAULT_WIDGETS;
const subtitle = title || (window.appT||(s=>s))('Tactical Overview');
const overlay = {
position: 'fixed', inset: 0, zIndex: 9999,
background: 'rgba(0,0,0,0.92)',
backdropFilter: 'blur(8px)',
WebkitBackdropFilter: 'blur(8px)',
color: '#e8eef6',
display: 'flex', flexDirection: 'column',
fontFamily: 'inherit',
};
const header = {
display: 'flex', alignItems: 'center', justifyContent: 'space-between',
padding: '14px 22px',
borderBottom: '1px solid var(--brand, #4af)',
boxShadow: '0 0 24px -6px var(--brand, #4af)',
background: 'linear-gradient(180deg, rgba(0,0,0,0.6), rgba(0,0,0,0.2))',
};
const titleStyle = {
display: 'flex', alignItems: 'baseline', gap: 14,
};
const titleMain = {
fontFamily: 'var(--font-mono, ui-monospace, Menlo, monospace)',
fontWeight: 700, letterSpacing: '0.18em', fontSize: 16,
color: 'var(--brand, #4af)', textShadow: '0 0 12px var(--brand, #4af)',
};
const titleSub = {
fontFamily: 'var(--font-mono, ui-monospace, Menlo, monospace)',
fontSize: 11, letterSpacing: '0.14em', textTransform: 'uppercase',
color: '#9aa6b6',
};
const closeBtn = {
background: 'transparent', border: '1px solid var(--brand, #4af)',
color: 'var(--brand, #4af)', borderRadius: 4,
width: 32, height: 32, cursor: 'pointer', fontSize: 18, lineHeight: '28px',
display:'flex', alignItems:'center', justifyContent:'center',
};
const tabBtn = { ...closeBtn, width: 'auto', padding: '0 10px', fontSize: 11,
fontFamily: 'var(--font-mono, ui-monospace, Menlo, monospace)',
letterSpacing: '0.12em', textTransform: 'uppercase' };
// Build a deep-link to re-open this HUD in a new tab (preserves widget set
// + question subtitle so the bot's voice flow can pop the HUD elsewhere).
const openNewTab = () => {
const ids = list.map(w => w.id).join(',');
const q = (title && title.startsWith('From: '))
? title.replace(/^From: \"?/, '').replace(/\"?$/, '')
: '';
const url = `${window.location.origin}/?hud=${encodeURIComponent(ids)}` +
(q ? `&q=${encodeURIComponent(q)}` : '');
window.open(url, '_blank', 'noopener');
};
const grid = {
flex: 1, overflowY: 'auto',
display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(380px, 1fr))',
gap: 16, padding: 22,
};
const card = {
background: 'rgba(8,12,18,0.85)',
border: '1px solid rgba(74,170,255,0.35)',
borderRadius: 6,
padding: 14,
boxShadow: 'inset 0 0 0 1px rgba(255,255,255,0.02), 0 0 18px -8px var(--brand, #4af)',
display: 'flex', flexDirection: 'column', gap: 10,
minHeight: 160,
};
const cardTitle = {
fontFamily: 'var(--font-mono, ui-monospace, Menlo, monospace)',
fontSize: 11, letterSpacing: '0.14em', textTransform: 'uppercase',
color: 'var(--brand, #4af)',
borderBottom: '1px dashed rgba(74,170,255,0.3)',
paddingBottom: 6,
};
return (
{ if (e.target === e.currentTarget) closeHUD(); }}>
{(window.appT||(s=>s))("HUD")} ⛶
{subtitle}
s))("Open this HUD in a new browser tab")}>
↗ {(window.appT||(s=>s))("New Tab")}
s))('Auto-open is muted — click to unmute') : (window.appT||(s=>s))('Mute auto-open (proactive HUD)')}
>
{mutedAutoOpen ? '🔇 ' + (window.appT||(s=>s))('Unmute') : '🔔 ' + (window.appT||(s=>s))('Mute')}
s))("Close (Esc)")}>×
{toastEl}
);
}
window.HUD = HUD;
window.useHUD = useHUD;
window.HUD_WIDGETS = HUD_WIDGETS;
window.HUD_DEFAULT_WIDGETS = DEFAULT_WIDGETS;