← 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' },
};