← back to Dw Boardroom Governance
frontend/src/components/Initiatives.tsx
127 lines
import { useState } from 'react';
import { api } from '../api';
import { useApi } from '../hooks/useApi';
import type { Initiative, RiskLevel } from '../types';
const RISK_COLORS: Record<string, string> = { low: '#22c55e', medium: '#f59e0b', high: '#ef4444', critical: '#dc2626' };
const STATUS_COLORS: Record<string, string> = { proposed: '#3b82f6', active: '#22c55e', paused: '#f59e0b', completed: '#7a7f96', cancelled: '#ef4444' };
export default function Initiatives() {
const { data: initiatives, refresh } = useApi(() => api.getInitiatives(), []);
const [title, setTitle] = useState('');
const [driver, setDriver] = useState('');
const [kpi, setKpi] = useState('');
const [risk, setRisk] = useState<RiskLevel>('low');
const [showForm, setShowForm] = useState(false);
async function create() {
if (!title.trim()) return;
try {
await api.createInitiative({ title, driverAgent: driver || 'unassigned', kpi, riskLevel: risk });
setTitle(''); setDriver(''); setKpi('');
setShowForm(false);
refresh();
} catch (err: any) {
console.error('[Initiatives] create failed:', err.message);
}
}
async function updateStatus(id: string, status: string) {
try {
await api.updateInitiative(id, { status });
refresh();
} catch (err: any) {
console.error('[Initiatives] updateStatus failed:', err.message);
}
}
async function updateProgress(id: string, progress: number) {
try {
await api.updateInitiative(id, { progress });
refresh();
} catch (err: any) {
console.error('[Initiatives] updateProgress failed:', err.message);
}
}
return (
<div style={styles.wrapper}>
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 16 }}>
<h3 style={{ margin: 0, fontSize: 18, fontWeight: 700 }}>Initiative Registry</h3>
<button style={styles.addBtn} onClick={() => setShowForm(!showForm)}>
{showForm ? 'Cancel' : '+ New Initiative'}
</button>
</div>
{showForm && (
<div style={styles.form}>
<input style={styles.input} placeholder="Initiative title" value={title} onChange={e => setTitle(e.target.value)} />
<input style={styles.input} placeholder="Driver agent" value={driver} onChange={e => setDriver(e.target.value)} />
<input style={styles.input} placeholder="KPI / Success metric" value={kpi} onChange={e => setKpi(e.target.value)} />
<select style={styles.select} value={risk} onChange={e => setRisk(e.target.value as RiskLevel)}>
<option value="low">Low Risk</option><option value="medium">Medium</option><option value="high">High</option><option value="critical">Critical</option>
</select>
<button style={styles.createBtn} onClick={create}>Create</button>
</div>
)}
<div style={styles.grid}>
{(!initiatives || initiatives.length === 0) && <div style={styles.empty}>No initiatives. Create one to start tracking.</div>}
{initiatives?.map((init: Initiative) => (
<div key={init.id} style={styles.card}>
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 8 }}>
<span style={{ ...styles.riskBadge, background: RISK_COLORS[init.risk_level] + '22', color: RISK_COLORS[init.risk_level] }}>
{init.risk_level}
</span>
<span style={{ ...styles.statusBadge, background: STATUS_COLORS[init.status] + '22', color: STATUS_COLORS[init.status] }}>
{init.status}
</span>
</div>
<h4 style={{ margin: '0 0 4px', fontSize: 14, fontWeight: 700 }}>{init.title}</h4>
<div style={{ fontSize: 11, color: '#7a7f96', marginBottom: 8 }}>
Driver: {init.driver_agent} {init.kpi && `| KPI: ${init.kpi}`}
</div>
{/* Progress bar */}
<div style={styles.progressTrack}>
<div style={{ ...styles.progressFill, width: `${init.progress}%` }} />
</div>
<div style={{ fontSize: 10, color: '#7a7f96', marginTop: 4, display: 'flex', justifyContent: 'space-between' }}>
<span>{init.progress}%</span>
<input type="range" min={0} max={100} value={init.progress} style={{ width: 100 }}
onChange={e => updateProgress(init.id, Number(e.target.value))} />
</div>
{/* Status actions */}
<div style={{ display: 'flex', gap: 4, marginTop: 8, flexWrap: 'wrap' }}>
{['proposed', 'active', 'paused', 'completed'].map(s => (
<button key={s} onClick={() => updateStatus(init.id, s)}
style={{ ...styles.miniBtn, opacity: init.status === s ? 1 : 0.5 }}>
{s}
</button>
))}
</div>
</div>
))}
</div>
</div>
);
}
const styles: Record<string, React.CSSProperties> = {
wrapper: {},
addBtn: { padding: '8px 16px', borderRadius: 8, border: 'none', background: '#8b5cf6', color: '#fff', fontWeight: 600, fontSize: 12, cursor: 'pointer', fontFamily: 'inherit' },
form: { display: 'flex', gap: 8, marginBottom: 16, flexWrap: 'wrap' },
input: { padding: '8px 10px', borderRadius: 6, border: '1px solid #252540', background: '#0a0a12', color: '#e8eaf0', fontSize: 12, fontFamily: 'inherit', flex: 1, minWidth: 150 },
select: { padding: '8px', borderRadius: 6, border: '1px solid #252540', background: '#0a0a12', color: '#e8eaf0', fontSize: 12, fontFamily: 'inherit' },
createBtn: { padding: '8px 16px', borderRadius: 6, border: 'none', background: '#22c55e', color: '#fff', fontWeight: 600, fontSize: 12, cursor: 'pointer', fontFamily: 'inherit' },
grid: { display: 'grid', gridTemplateColumns: 'repeat(auto-fill, minmax(300px, 1fr))', gap: 16 },
card: { background: '#1a1a2e', borderRadius: 12, padding: 16, border: '1px solid #252540' },
riskBadge: { padding: '2px 8px', borderRadius: 4, fontSize: 10, fontWeight: 700, textTransform: 'uppercase' as const },
statusBadge: { padding: '2px 8px', borderRadius: 4, fontSize: 10, fontWeight: 600 },
progressTrack: { height: 6, background: '#252540', borderRadius: 3, overflow: 'hidden' },
progressFill: { height: '100%', background: 'linear-gradient(90deg, #8b5cf6, #22c55e)', borderRadius: 3, transition: 'width 0.3s' },
miniBtn: { padding: '3px 8px', borderRadius: 4, border: '1px solid #252540', background: 'transparent', color: '#7a7f96', fontSize: 10, cursor: 'pointer', fontFamily: 'inherit', textTransform: 'capitalize' as const },
empty: { color: '#7a7f96', fontSize: 13, textAlign: 'center' as const, padding: 40, gridColumn: '1 / -1' },
};