← back to Dw Boardroom Governance

frontend/src/components/DelegationForm.tsx

133 lines

import { useState } from 'react';
import { api } from '../api';
import { useApi } from '../hooks/useApi';
import type { DelegationContract } from '../types';

export default function DelegationForm() {
  const { data: delegations, refresh } = useApi(() => api.getDelegations(), []);
  const [owner, setOwner] = useState('');
  const [delegate, setDelegate] = useState('');
  const [autonomy, setAutonomy] = useState(2);
  const [metric, setMetric] = useState('');
  const [suggestion, setSuggestion] = useState<any>(null);
  const [searchText, setSearchText] = useState('');

  async function create() {
    if (!owner || !delegate) return;
    try {
      await api.createDelegation({ owner, delegate, autonomyLevel: autonomy, metric: metric || undefined });
      setOwner(''); setDelegate(''); setMetric('');
      refresh();
    } catch (err: any) {
      alert(err.message);
    }
  }

  async function suggest() {
    if (!searchText) return;
    try {
      const result = await api.suggestDelegate(searchText);
      setSuggestion(result);
      if (result.suggestedDelegate) setDelegate(result.suggestedDelegate);
    } catch (err: any) {
      console.error('[DelegationForm] suggest failed:', err.message);
    }
  }

  async function revoke(id: string) {
    try {
      await api.revokeDelegation(id);
      refresh();
    } catch (err: any) {
      console.error('[DelegationForm] revoke failed:', err.message);
    }
  }

  return (
    <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 20 }}>
      {/* Create Form */}
      <div style={styles.card}>
        <h3 style={styles.cardTitle}>Create Delegation</h3>

        {/* Keyword routing preview */}
        <div style={{ marginBottom: 16 }}>
          <div style={{ display: 'flex', gap: 6 }}>
            <input style={styles.input} placeholder="Describe the task for auto-routing..." value={searchText} onChange={e => setSearchText(e.target.value)} />
            <button style={styles.suggestBtn} onClick={suggest}>Suggest</button>
          </div>
          {suggestion && (
            <div style={{ marginTop: 8, padding: 10, background: '#12121f', borderRadius: 6, fontSize: 12, border: '1px solid #252540' }}>
              <div>Suggested: <strong style={{ color: '#8b5cf6' }}>{suggestion.suggestedDelegate || 'None'}</strong></div>
              <div style={{ color: '#7a7f96', fontSize: 11 }}>{suggestion.reasoning}</div>
              {suggestion.hierarchyPath && <div style={{ color: '#7a7f96', fontSize: 11 }}>Path: {suggestion.hierarchyPath}</div>}
            </div>
          )}
        </div>

        <div style={styles.formGrid}>
          <label style={styles.label}>
            Owner
            <input style={styles.input} value={owner} onChange={e => setOwner(e.target.value)} placeholder="e.g. ceo, cfo" />
          </label>
          <label style={styles.label}>
            Delegate
            <input style={styles.input} value={delegate} onChange={e => setDelegate(e.target.value)} placeholder="e.g. frank, max" />
          </label>
          <label style={styles.label}>
            Autonomy Level: {autonomy}
            <input type="range" min={1} max={5} value={autonomy} onChange={e => setAutonomy(Number(e.target.value))} style={{ width: '100%' }} />
            <div style={{ display: 'flex', justifyContent: 'space-between', fontSize: 10, color: '#7a7f96' }}>
              <span>1 Supervised</span><span>5 Autonomous</span>
            </div>
          </label>
          <label style={styles.label}>
            Success Metric
            <input style={styles.input} value={metric} onChange={e => setMetric(e.target.value)} placeholder="KPI or completion criteria" />
          </label>
        </div>

        <button style={styles.createBtn} onClick={create}>Create Delegation Contract</button>
      </div>

      {/* Active Contracts */}
      <div style={styles.card}>
        <h3 style={styles.cardTitle}>Active Contracts</h3>
        <div style={styles.list}>
          {(!delegations || delegations.length === 0) && <div style={styles.empty}>No delegations.</div>}
          {delegations?.map((d: DelegationContract) => (
            <div key={d.id} style={styles.contract}>
              <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
                <span style={{ fontWeight: 600, fontSize: 13 }}>{d.owner} → {d.delegate}</span>
                <span style={{ ...styles.statusBadge, background: d.status === 'active' ? '#22c55e22' : '#7a7f9622', color: d.status === 'active' ? '#22c55e' : '#7a7f96' }}>{d.status}</span>
              </div>
              <div style={{ fontSize: 11, color: '#7a7f96', marginTop: 4 }}>
                Autonomy: {'★'.repeat(d.autonomy_level)}{'☆'.repeat(5 - d.autonomy_level)}
                {d.hierarchy_path && ` | ${d.hierarchy_path}`}
              </div>
              {d.metric && <div style={{ fontSize: 11, color: '#b0b3c0', marginTop: 2 }}>Metric: {d.metric}</div>}
              {d.status === 'active' && (
                <button style={styles.revokeBtn} onClick={() => revoke(d.id)}>Revoke</button>
              )}
            </div>
          ))}
        </div>
      </div>
    </div>
  );
}

const styles: Record<string, React.CSSProperties> = {
  card: { background: '#1a1a2e', borderRadius: 12, padding: 20, border: '1px solid #252540' },
  cardTitle: { margin: '0 0 16px', fontSize: 15, fontWeight: 700 },
  formGrid: { display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 12, marginBottom: 16 },
  label: { display: 'flex', flexDirection: 'column', gap: 4, fontSize: 11, fontWeight: 600, color: '#7a7f96' },
  input: { padding: '8px 10px', borderRadius: 6, border: '1px solid #252540', background: '#0a0a12', color: '#e8eaf0', fontSize: 12, fontFamily: 'inherit', flex: 1 },
  suggestBtn: { padding: '8px 14px', borderRadius: 6, border: 'none', background: '#6366f1', color: '#fff', fontWeight: 600, fontSize: 12, cursor: 'pointer', fontFamily: 'inherit', whiteSpace: 'nowrap' },
  createBtn: { padding: '10px 20px', borderRadius: 8, border: 'none', background: 'linear-gradient(135deg, #8b5cf6, #6366f1)', color: '#fff', fontWeight: 700, fontSize: 13, cursor: 'pointer', width: '100%', fontFamily: 'inherit' },
  list: { display: 'flex', flexDirection: 'column', gap: 8 },
  empty: { color: '#7a7f96', fontSize: 13, textAlign: 'center' as const, padding: 20 },
  contract: { background: '#12121f', borderRadius: 8, padding: 12, border: '1px solid #252540' },
  statusBadge: { padding: '2px 8px', borderRadius: 4, fontSize: 10, fontWeight: 600 },
  revokeBtn: { marginTop: 8, padding: '4px 12px', borderRadius: 4, border: '1px solid #ef4444', background: '#ef444422', color: '#ef4444', fontSize: 11, fontWeight: 600, cursor: 'pointer', fontFamily: 'inherit' },
};