← back to Dw Boardroom Governance
frontend/src/components/Ledger.tsx
80 lines
import { useState } from 'react';
import { api } from '../api';
import { useApi } from '../hooks/useApi';
import type { Decision } from '../types';
const STATUS_COLORS: Record<string, string> = {
pending: '#f59e0b', approved: '#22c55e', rejected: '#ef4444',
modified: '#3b82f6', deferred: '#7a7f96', auto_approved: '#06b6d4',
};
export default function Ledger() {
const [filter, setFilter] = useState('');
const { data: decisions, refresh } = useApi(() => api.getDecisions(), []);
const filtered = (decisions || []).filter((d: Decision) =>
!filter || d.status === filter
);
return (
<div style={styles.card}>
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 12 }}>
<h3 style={styles.cardTitle}>Decision Ledger</h3>
<div style={{ display: 'flex', gap: 4 }}>
<button style={{ ...styles.filterBtn, ...(filter === '' ? styles.activeFilter : {}) }} onClick={() => setFilter('')}>All</button>
{Object.keys(STATUS_COLORS).map(s => (
<button key={s} style={{ ...styles.filterBtn, ...(filter === s ? styles.activeFilter : {}) }} onClick={() => setFilter(s)}>
{s.replace('_', ' ')}
</button>
))}
</div>
</div>
<div style={styles.table}>
<div style={styles.tableHeader}>
<span style={{ width: 140 }}>Date</span>
<span style={{ flex: 1 }}>Topic</span>
<span style={{ width: 30 }}>Type</span>
<span style={{ width: 40 }}>Score</span>
<span style={{ width: 90 }}>Status</span>
<span style={{ width: 80 }}>Owner</span>
</div>
{filtered.length === 0 && <div style={styles.empty}>No decisions found.</div>}
{filtered.map((d: Decision) => (
<div key={d.id} style={styles.row}>
<span style={{ width: 140, fontSize: 10, color: '#7a7f96' }}>
{new Date(d.created_at).toLocaleString('en-US', { timeZone: 'America/Los_Angeles', month: 'short', day: 'numeric', hour: '2-digit', minute: '2-digit' })}
</span>
<span style={{ flex: 1, fontSize: 12, fontWeight: 500 }}>{d.topic}</span>
<span style={{ width: 30, fontSize: 11, fontWeight: 700, color: '#8b5cf6' }}>{d.decision_type}</span>
<span style={{ width: 40, fontSize: 11, fontWeight: 700 }}>{d.score ?? '—'}</span>
<span style={{ width: 90 }}>
<span style={{ ...styles.statusBadge, background: (STATUS_COLORS[d.status] || '#7a7f96') + '22', color: STATUS_COLORS[d.status] }}>
{d.status.replace('_', ' ')}
</span>
</span>
<span style={{ width: 80, fontSize: 11, color: '#7a7f96' }}>{d.owner}</span>
</div>
))}
</div>
<div style={{ marginTop: 8, fontSize: 11, color: '#7a7f96' }}>
{filtered.length} decision(s) {filter && `(${filter})`}
<button style={{ ...styles.filterBtn, marginLeft: 8 }} onClick={refresh}>Refresh</button>
</div>
</div>
);
}
const styles: Record<string, React.CSSProperties> = {
card: { background: '#1a1a2e', borderRadius: 12, padding: 20, border: '1px solid #252540' },
cardTitle: { margin: 0, fontSize: 15, fontWeight: 700 },
filterBtn: { padding: '3px 8px', borderRadius: 4, border: '1px solid #252540', background: 'transparent', color: '#7a7f96', fontSize: 10, cursor: 'pointer', fontFamily: 'inherit', textTransform: 'capitalize' as const },
activeFilter: { background: '#8b5cf633', borderColor: '#8b5cf6', color: '#e8eaf0' },
table: { display: 'flex', flexDirection: 'column', gap: 2 },
tableHeader: { display: 'flex', gap: 8, padding: '8px 10px', fontSize: 10, fontWeight: 700, color: '#7a7f96', textTransform: 'uppercase' as const, borderBottom: '1px solid #252540' },
row: { display: 'flex', gap: 8, padding: '8px 10px', alignItems: 'center', borderBottom: '1px solid #1a1a2e', background: '#12121f', borderRadius: 4 },
statusBadge: { padding: '2px 8px', borderRadius: 4, fontSize: 10, fontWeight: 600 },
empty: { color: '#7a7f96', fontSize: 12, padding: 20, textAlign: 'center' as const },
};