Skip to content

Commit e389233

Browse files
petervachonclaude
authored andcommitted
feat(apollo-vertex): dashboard config layer — glow-config, DashboardCards, AutopilotInsight
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent be38f96 commit e389233

3 files changed

Lines changed: 658 additions & 0 deletions

File tree

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
"use client";
2+
3+
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
4+
5+
interface AutopilotInsightProps {
6+
onClose: () => void;
7+
sourceCardTitle: string;
8+
}
9+
10+
export function AutopilotInsight({
11+
onClose,
12+
sourceCardTitle,
13+
}: AutopilotInsightProps) {
14+
return (
15+
<Card
16+
variant="glass"
17+
className="!bg-white/90 dark:!bg-card h-full overflow-hidden relative"
18+
>
19+
{/* Close button */}
20+
<button
21+
type="button"
22+
onClick={onClose}
23+
className="absolute top-5 right-5 z-20 size-7 rounded-md flex items-center justify-center text-muted-foreground hover:text-foreground hover:bg-muted/50 transition-all"
24+
>
25+
<svg
26+
xmlns="http://www.w3.org/2000/svg"
27+
viewBox="0 0 24 24"
28+
fill="none"
29+
stroke="currentColor"
30+
strokeWidth="2"
31+
strokeLinecap="round"
32+
strokeLinejoin="round"
33+
className="size-4"
34+
>
35+
<path d="M18 6 6 18" />
36+
<path d="m6 6 12 12" />
37+
</svg>
38+
</button>
39+
40+
<CardHeader className="!gap-2">
41+
<div className="flex items-center gap-2">
42+
<img
43+
src="/Autopilot_dark.svg"
44+
alt="Autopilot"
45+
className="size-5 block dark:hidden"
46+
/>
47+
<img
48+
src="/Autopilot_light.svg"
49+
alt="Autopilot"
50+
className="size-5 hidden dark:block"
51+
/>
52+
<CardTitle className="text-sm font-bold tracking-tight">
53+
Autopilot Insight
54+
</CardTitle>
55+
</div>
56+
<p className="text-xs text-muted-foreground">
57+
Analyzing {sourceCardTitle}
58+
</p>
59+
</CardHeader>
60+
61+
<CardContent className="flex-1 flex flex-col">
62+
{/* Placeholder for future chat UX responses */}
63+
<div className="flex-1 border border-dashed border-muted-foreground/15 bg-muted/30 rounded-lg flex items-center justify-center">
64+
<div className="text-center">
65+
<p className="text-sm text-muted-foreground/60">
66+
Autopilot response area
67+
</p>
68+
<p className="text-xs text-muted-foreground/40 mt-1">
69+
Chat UX content will appear here
70+
</p>
71+
</div>
72+
</div>
73+
</CardContent>
74+
</Card>
75+
);
76+
}
Lines changed: 288 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,288 @@
1+
import { AlertTriangle, CheckCircle, Clock, XCircle } from "lucide-react";
2+
import type { LucideIcon } from "lucide-react";
3+
import { Badge } from "@/components/ui/badge";
4+
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
5+
import { Progress } from "@/components/ui/progress";
6+
import {
7+
Table,
8+
TableBody,
9+
TableCell,
10+
TableHead,
11+
TableHeader,
12+
TableRow,
13+
} from "@/components/ui/table";
14+
15+
// --- Types ---
16+
17+
export interface KpiItem {
18+
label: string;
19+
value: string;
20+
icon: LucideIcon;
21+
change: string;
22+
}
23+
24+
// --- Sample data ---
25+
26+
const invoices = [
27+
{
28+
id: "INV-4021",
29+
vendor: "Acme Corp",
30+
amount: "$12,450.00",
31+
status: "Processed" as const,
32+
date: "Mar 18, 2026",
33+
},
34+
{
35+
id: "INV-4020",
36+
vendor: "Global Supplies Ltd",
37+
amount: "$3,280.50",
38+
status: "Pending" as const,
39+
date: "Mar 18, 2026",
40+
},
41+
{
42+
id: "INV-4019",
43+
vendor: "TechParts Inc",
44+
amount: "$8,920.00",
45+
status: "In Review" as const,
46+
date: "Mar 17, 2026",
47+
},
48+
{
49+
id: "INV-4018",
50+
vendor: "Office Depot",
51+
amount: "$1,150.75",
52+
status: "Processed" as const,
53+
date: "Mar 17, 2026",
54+
},
55+
{
56+
id: "INV-4017",
57+
vendor: "CloudServ Solutions",
58+
amount: "$24,000.00",
59+
status: "Failed" as const,
60+
date: "Mar 16, 2026",
61+
},
62+
{
63+
id: "INV-4016",
64+
vendor: "Metro Logistics",
65+
amount: "$6,780.00",
66+
status: "Processed" as const,
67+
date: "Mar 16, 2026",
68+
},
69+
];
70+
71+
const statusVariant: Record<
72+
string,
73+
"default" | "secondary" | "destructive" | "outline"
74+
> = {
75+
Processed: "default",
76+
Pending: "secondary",
77+
Failed: "destructive",
78+
"In Review": "outline",
79+
};
80+
81+
const statusIcon: Record<string, typeof CheckCircle> = {
82+
Processed: CheckCircle,
83+
Pending: Clock,
84+
Failed: XCircle,
85+
"In Review": AlertTriangle,
86+
};
87+
88+
const activityBars = [
89+
{ label: "Mon", height: 60 },
90+
{ label: "Tue", height: 85 },
91+
{ label: "Wed", height: 45 },
92+
{ label: "Thu", height: 92 },
93+
{ label: "Fri", height: 78 },
94+
{ label: "Sat", height: 30 },
95+
{ label: "Sun", height: 15 },
96+
];
97+
98+
const recentActivity = [
99+
{ text: "INV-4021 processed successfully", time: "2 min ago" },
100+
{ text: "INV-4020 submitted for review", time: "15 min ago" },
101+
{ text: "Batch processing completed (42 invoices)", time: "1 hr ago" },
102+
{ text: "INV-4017 failed — missing PO number", time: "3 hrs ago" },
103+
];
104+
105+
const pipelineStages = [
106+
{ label: "OCR Extraction", value: 96 },
107+
{ label: "Field Validation", value: 88 },
108+
{ label: "Approval Routing", value: 72 },
109+
{ label: "Final Review", value: 64 },
110+
];
111+
112+
const complianceChecks = [
113+
{ label: "Income Verification", pass: 98 },
114+
{ label: "Credit Score Threshold", pass: 96 },
115+
{ label: "Debt-to-Income Ratio", pass: 91 },
116+
{ label: "Collateral Appraisal", pass: 87 },
117+
{ label: "Document Completeness", pass: 94 },
118+
];
119+
120+
// --- Card components ---
121+
122+
export function KpiCards({ kpis }: { kpis: KpiItem[] }) {
123+
return (
124+
<>
125+
{kpis.map((kpi) => (
126+
<Card key={kpi.label} variant="glass">
127+
<CardHeader className="pb-2">
128+
<div className="flex items-center justify-between">
129+
<CardTitle className="text-sm font-medium text-muted-foreground">
130+
{kpi.label}
131+
</CardTitle>
132+
<kpi.icon className="w-4 h-4 text-muted-foreground" />
133+
</div>
134+
</CardHeader>
135+
<CardContent>
136+
<div className="text-2xl font-bold">{kpi.value}</div>
137+
<p className="text-xs text-muted-foreground mt-1">
138+
<span className="text-emerald-500">{kpi.change}</span> from last
139+
week
140+
</p>
141+
</CardContent>
142+
</Card>
143+
))}
144+
</>
145+
);
146+
}
147+
148+
export function InvoiceTable() {
149+
return (
150+
<Card variant="glass">
151+
<CardHeader>
152+
<CardTitle>Recent Invoices</CardTitle>
153+
</CardHeader>
154+
<CardContent>
155+
<Table>
156+
<TableHeader>
157+
<TableRow>
158+
<TableHead>Invoice</TableHead>
159+
<TableHead>Vendor</TableHead>
160+
<TableHead>Amount</TableHead>
161+
<TableHead>Status</TableHead>
162+
<TableHead>Date</TableHead>
163+
</TableRow>
164+
</TableHeader>
165+
<TableBody>
166+
{invoices.map((inv) => {
167+
const StatusIcon = statusIcon[inv.status];
168+
return (
169+
<TableRow key={inv.id}>
170+
<TableCell className="font-medium">{inv.id}</TableCell>
171+
<TableCell>{inv.vendor}</TableCell>
172+
<TableCell>{inv.amount}</TableCell>
173+
<TableCell>
174+
<Badge
175+
variant={statusVariant[inv.status]}
176+
className="gap-1"
177+
>
178+
<StatusIcon className="w-3 h-3" />
179+
{inv.status}
180+
</Badge>
181+
</TableCell>
182+
<TableCell className="text-muted-foreground">
183+
{inv.date}
184+
</TableCell>
185+
</TableRow>
186+
);
187+
})}
188+
</TableBody>
189+
</Table>
190+
</CardContent>
191+
</Card>
192+
);
193+
}
194+
195+
export function ActivityBarChart() {
196+
return (
197+
<Card variant="glass">
198+
<CardHeader>
199+
<CardTitle>Processing Activity</CardTitle>
200+
</CardHeader>
201+
<CardContent>
202+
<div className="flex items-end gap-3 h-32">
203+
{activityBars.map((bar) => (
204+
<div
205+
key={bar.label}
206+
className="flex-1 flex flex-col items-center gap-1"
207+
>
208+
<div
209+
className="w-full bg-primary/80 rounded-t-sm"
210+
style={{ height: `${bar.height}%` }}
211+
/>
212+
<span className="text-xs text-muted-foreground">{bar.label}</span>
213+
</div>
214+
))}
215+
</div>
216+
</CardContent>
217+
</Card>
218+
);
219+
}
220+
221+
export function ActivityFeed() {
222+
return (
223+
<Card variant="glass">
224+
<CardHeader>
225+
<CardTitle>Recent Activity</CardTitle>
226+
</CardHeader>
227+
<CardContent>
228+
<div className="space-y-4">
229+
{recentActivity.map((event) => (
230+
<div key={event.text} className="flex items-start gap-3">
231+
<div className="mt-1.5 w-2 h-2 rounded-full bg-primary shrink-0" />
232+
<div className="flex-1 min-w-0">
233+
<p className="text-sm">{event.text}</p>
234+
<p className="text-xs text-muted-foreground">{event.time}</p>
235+
</div>
236+
</div>
237+
))}
238+
</div>
239+
</CardContent>
240+
</Card>
241+
);
242+
}
243+
244+
export function PipelineProgress() {
245+
return (
246+
<Card variant="glass">
247+
<CardHeader>
248+
<CardTitle>Processing Pipeline</CardTitle>
249+
</CardHeader>
250+
<CardContent>
251+
<div className="space-y-4">
252+
{pipelineStages.map((stage) => (
253+
<div key={stage.label}>
254+
<div className="flex items-center justify-between text-sm mb-1.5">
255+
<span>{stage.label}</span>
256+
<span className="text-muted-foreground">{stage.value}%</span>
257+
</div>
258+
<Progress value={stage.value} />
259+
</div>
260+
))}
261+
</div>
262+
</CardContent>
263+
</Card>
264+
);
265+
}
266+
267+
export function ComplianceProgress() {
268+
return (
269+
<Card variant="glass">
270+
<CardHeader>
271+
<CardTitle>Compliance Pass Rates</CardTitle>
272+
</CardHeader>
273+
<CardContent>
274+
<div className="space-y-4">
275+
{complianceChecks.map((check) => (
276+
<div key={check.label}>
277+
<div className="flex items-center justify-between text-sm mb-1.5">
278+
<span>{check.label}</span>
279+
<span className="text-muted-foreground">{check.pass}%</span>
280+
</div>
281+
<Progress value={check.pass} />
282+
</div>
283+
))}
284+
</div>
285+
</CardContent>
286+
</Card>
287+
);
288+
}

0 commit comments

Comments
 (0)