- SchemaGuard: gate all activity bodies on form-screens (403 → permission card, not live form); dedup ActivityForm/RecommendBody - ActivityForm: generic file-field upload (Onboard policy doc etc) - UnderwritingBody: AI eligibility-notes parser (+raw fallback) in navy card, drop placeholder assess() panel - DocumentUploadCard: equal-height cards + Replace/Remove - Worklist + leads: sort latest-first by updated_at (instance_id tiebreak) - Modal: cap 85vh, scroll body; slim themed scrollbars (schedule/rail/modal/kanban) - Schedule agenda + Kanban columns: cap height, scroll internally; minimal kanban card - Topbar: back button each screen; Avatar lucide type fix Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
93 lines
3.6 KiB
TypeScript
93 lines
3.6 KiB
TypeScript
import { useState } from 'react';
|
|
import { Sparkles, ChevronDown, ChevronUp, Check, AlertTriangle } from 'lucide-react';
|
|
import { cn } from '../../lib/cn';
|
|
import { Avatar } from '../core/Avatar';
|
|
import { AIDecisionCard } from './AIDecisionCard';
|
|
import type { AIDecisionCardProps } from './AIDecisionCard';
|
|
|
|
export interface AiSuggestionPanelProps extends AIDecisionCardProps {
|
|
/** Eyebrow heading above the card. @default "AI suggestion" */
|
|
label?: string;
|
|
/** Start as a one-line strip, expandable to the full card. @default true */
|
|
collapsible?: boolean;
|
|
className?: string;
|
|
}
|
|
|
|
/**
|
|
* Sidebar framing for an AI employee's decision inside an activity form.
|
|
* Defaults to a compact one-line strip (avatar · name · confidence · summary)
|
|
* so it sits comfortably beside small forms, and expands to the full
|
|
* AIDecisionCard on click. Pass `collapsible={false}` to always show the card.
|
|
*/
|
|
export function AiSuggestionPanel({
|
|
label = 'AI suggestion',
|
|
collapsible = true,
|
|
className,
|
|
...card
|
|
}: AiSuggestionPanelProps) {
|
|
const [open, setOpen] = useState(!collapsible);
|
|
|
|
const {
|
|
employee = 'Aria',
|
|
confidence = 0.85,
|
|
threshold = 0.7,
|
|
summary,
|
|
} = card;
|
|
const autonomous = confidence >= threshold;
|
|
const accent = autonomous ? 'var(--status-autonomous)' : 'var(--status-escalated)';
|
|
const accentSoft = autonomous ? 'var(--status-autonomous-soft)' : 'var(--status-escalated-soft)';
|
|
const pct = Math.round(Math.max(0, Math.min(1, confidence)) * 100);
|
|
|
|
return (
|
|
<div className={cn('flex flex-col gap-2.5', className)}>
|
|
<div className="flex items-center justify-between gap-2">
|
|
<div className="flex items-center gap-1.5 text-2xs font-bold uppercase tracking-[0.06em] text-faint">
|
|
<Sparkles size={12} className="text-sunrise-500" />
|
|
{label}
|
|
</div>
|
|
{collapsible && open && (
|
|
<button
|
|
type="button"
|
|
onClick={() => setOpen(false)}
|
|
className="inline-flex items-center gap-1 bg-none border-none p-0 cursor-pointer font-sans text-2xs font-semibold text-link"
|
|
>
|
|
<ChevronUp size={12} />
|
|
Collapse
|
|
</button>
|
|
)}
|
|
</div>
|
|
|
|
{open ? (
|
|
<AIDecisionCard {...card} />
|
|
) : (
|
|
<button
|
|
type="button"
|
|
onClick={() => setOpen(true)}
|
|
className="group relative w-full text-left bg-card rounded-lg border border-border-subtle shadow-sm hover:shadow-md transition-shadow overflow-hidden font-sans flex items-center gap-3 pl-[14px] pr-3 py-2.5"
|
|
>
|
|
{/* accent rail */}
|
|
<span className="absolute top-0 left-0 bottom-0 w-1" style={{ background: accent }} />
|
|
<Avatar name={employee} ai size={30} />
|
|
<div className="min-w-0 flex-1">
|
|
<div className="flex items-center gap-2">
|
|
<span className="text-sm font-bold text-strong leading-tight truncate">{employee}</span>
|
|
<span
|
|
className="inline-flex items-center gap-1 text-2xs font-bold tracking-[0.03em] px-1.5 py-0.5 rounded-pill shrink-0"
|
|
style={{ background: accentSoft, color: accent }}
|
|
>
|
|
{autonomous ? <Check size={10} strokeWidth={3} /> : <AlertTriangle size={10} strokeWidth={3} />}
|
|
{pct}%
|
|
</span>
|
|
</div>
|
|
{summary && <div className="text-xs text-muted mt-0.5 truncate">{summary}</div>}
|
|
</div>
|
|
<ChevronDown
|
|
size={16}
|
|
className="shrink-0 text-faint transition-transform group-hover:translate-y-0.5"
|
|
/>
|
|
</button>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|