diff --git a/apps/apollo-vertex/app/(preview)/layout.tsx b/apps/apollo-vertex/app/(preview)/layout.tsx new file mode 100644 index 000000000..02b220198 --- /dev/null +++ b/apps/apollo-vertex/app/(preview)/layout.tsx @@ -0,0 +1,9 @@ +import type { ReactNode } from "react"; + +export default function PreviewLayout({ children }: { children: ReactNode }) { + return ( +
+ {children} +
+ ); +} diff --git a/apps/apollo-vertex/app/(preview)/preview/shell-minimal/analytics-page.tsx b/apps/apollo-vertex/app/(preview)/preview/shell-minimal/analytics-page.tsx new file mode 100644 index 000000000..f37fcd86b --- /dev/null +++ b/apps/apollo-vertex/app/(preview)/preview/shell-minimal/analytics-page.tsx @@ -0,0 +1,693 @@ +"use client"; + +import { + AlertTriangle, + ArrowLeft, + ArrowUp, + CheckCircle, + CircleAlert, + Grid2x2, + Loader2, + Sparkles, +} from "lucide-react"; +import { AnimatePresence, motion } from "framer-motion"; +import { useCallback, useEffect, useState } from "react"; +import { + AlertDialog, + AlertDialogAction, + AlertDialogCancel, + AlertDialogContent, + AlertDialogDescription, + AlertDialogFooter, + AlertDialogHeader, + AlertDialogTitle, + AlertDialogTrigger, +} from "@/components/ui/alert-dialog"; +import { Button } from "@/components/ui/button"; +import { Skeleton } from "@/components/ui/skeleton"; +import { + Table, + TableBody, + TableCell, + TableHead, + TableHeader, + TableRow, +} from "@/components/ui/table"; +import { ToggleGroup, ToggleGroupItem } from "@/components/ui/toggle-group"; +import { cn } from "@/lib/utils"; +import { useShellNavigation } from "@/registry/shell/shell-navigation-context"; + +const findings = [ + { + icon: CircleAlert, + label: "Duplicate line item detected", + time: "5m ago", + severity: "medium" as const, + description: + "Line item #3 (Industrial Bearings, $2,400) appears to match a charge on INV-3987 from Jan 15...", + detail: { + headline: "Potential duplicate charge of $2,400 for Industrial Bearings", + summary: + "Line item #3 on this invoice (120× SKF-6205-2RS at $20.00 each, totaling $2,400.00) matches an identical charge on INV-3987, submitted by Acme Corp on January 15 and already approved for payment on January 22. Same vendor, same part number, same quantity and unit price. This could be a legitimate reorder for the Detroit facility, but the 41-day gap and identical amounts suggest it may be an accidental duplicate submission.", + actions: ["Mark as Duplicate", "Approve Anyway"], + }, + }, + { + icon: AlertTriangle, + label: "Payment terms mismatch", + time: "12m ago", + severity: "low" as const, + description: + "Invoice specifies Net 45, but vendor master record shows agreed terms of Net 30.", + detail: { + headline: "Payment terms discrepancy — Net 45 vs. agreed Net 30", + summary: + "This invoice from Acme Corp specifies payment terms of Net 45, but the vendor master record (VMR-AC-0042) shows mutually agreed terms of Net 30, last updated on November 8, 2025. Paying on Net 45 would delay payment by 15 days beyond the contractual obligation, which could trigger late payment penalties or affect the vendor relationship. This may be a clerical error on the invoice, or Acme Corp may have recently requested extended terms that haven't been reflected in the master record yet.", + actions: ["Use Agreed Net 30", "Accept Net 45", "Contact Vendor"], + }, + }, +]; + +const initialTimeline = [ + { + text: "Three-way match completed — invoice, PO, and goods receipt verified.", + time: "2m ago", + }, + { + text: "Goods receipt GRN-8842 confirmed for PO-7710.", + time: "18m ago", + }, + { + text: "Invoice matched to purchase order PO-7710.", + time: "22m ago", + }, + { + text: "Invoice received via EDI and entered into processing queue.", + time: "1h ago", + }, +]; + +const lineItems = [ + { line: 1, description: "Industrial Bearings (SKF 6205)", partNo: "SKF-6205-2RS", qty: 120, unitPrice: 20.00, total: 2400.00 }, + { line: 2, description: "Hydraulic Cylinder Seals", partNo: "HCS-4450-KIT", qty: 50, unitPrice: 38.50, total: 1925.00 }, + { line: 3, description: "Precision Machined Shafts", partNo: "PMS-1020-SS", qty: 25, unitPrice: 185.00, total: 4625.00 }, + { line: 4, description: "Linear Guide Rails", partNo: "LGR-20-800", qty: 10, unitPrice: 245.00, total: 2450.00 }, + { line: 5, description: "Coupling Assemblies", partNo: "CA-30-FL", qty: 15, unitPrice: 70.00, total: 1050.00 }, +]; + +const tabs = ["All", "Findings", "Activity"] as const; +type Tab = (typeof tabs)[number]; + +const fmt = (n: number) => + n.toLocaleString("en-US", { style: "currency", currency: "USD" }); + +export function AnalyticsPage({ visible }: { visible: boolean }) { + const [loading, setLoading] = useState(true); + const [activeTab, setActiveTab] = useState("All"); + const [view, setView] = useState<"summary" | "table">("summary"); + const [selectedFinding, setSelectedFinding] = useState(null); + const [chatMessages, setChatMessages] = useState<{ text: string; status: "loading" | "done" }[]>([]); + const [duplicateMarked, setDuplicateMarked] = useState(false); + const [net45Accepted, setNet45Accepted] = useState(false); + const [timelineItems, setTimelineItems] = useState(initialTimeline); + const nav = useShellNavigation(); + + useEffect(() => { + const timer = setTimeout(() => setLoading(false), 2000); + return () => clearTimeout(timer); + }, []); + + const handleMarkDuplicate = useCallback(() => { + setDuplicateMarked(true); + setChatMessages((prev) => [...prev, { text: "Marking line item #3 as duplicate across INV-4021 and INV-3987...", status: "loading" }]); + setTimeout(() => { + setChatMessages((prev) => + prev.map((m, i) => (i === prev.length - 1 ? { ...m, status: "done" as const } : m)) + ); + setTimeout(() => { + setChatMessages((prev) => [ + ...prev, + { text: "Done — line item #3 (Industrial Bearings, $2,400.00) has been flagged as a duplicate. The charge has been excluded from the payment total. Updated invoice amount: $10,050.00.", status: "done" }, + ]); + setTimelineItems((prev) => [ + { text: "Line item #3 marked as duplicate — charge excluded from payment total.", time: "Just now" }, + ...prev, + ]); + }, 800); + }, 1500); + }, []); + + const handleAcceptNet45 = useCallback(() => { + setNet45Accepted(true); + setChatMessages((prev) => [...prev, { text: "Updating payment terms to Net 45 for INV-4021...", status: "loading" }]); + setTimeout(() => { + setChatMessages((prev) => + prev.map((m, i) => (i === prev.length - 1 ? { ...m, status: "done" as const } : m)) + ); + setTimeout(() => { + setChatMessages((prev) => [ + ...prev, + { text: "Done — payment terms updated to Net 45. Due date adjusted to April 11, 2026. Vendor master record VMR-AC-0042 has been flagged for review to align with the updated terms.", status: "done" }, + ]); + setTimelineItems((prev) => [ + { text: "Payment terms updated to Net 45 — due date adjusted to Apr 11, 2026.", time: "Just now" }, + ...prev, + ]); + }, 800); + }, 1500); + }, []); + + if (!visible) return null; + + return ( +
+ {/* Top Header Bar */} +
+
+ +
+

Acme Corp

+

INV-4021

+
+
+ +
+
+

Amount

+

$12,450.00

+
+
+

Received

+

Feb 25, 2026

+
+
+

Status

+
+ +

Processed

+
+
+
+

Assignee

+

Sarah Mitchell

+
+
+

Match Score

+

94%

+
+
+ +
+ v && setView(v as "summary" | "table")} + variant="outline" + > + + + + + + + + + + + + + + Approve payment for INV-4021? + + This will authorize a payment of $12,450.00 to Acme Corp under purchase order PO-7710. Once confirmed, the payment will be queued for processing and cannot be reversed. + + + + Cancel + nav?.onNavigate("dashboard")}> + Confirm + + + + +
+
+ + {/* Content Area */} + + {loading ? ( + + {/* Left Panel Skeleton */} +
+
+ + +
+
+ + + +
+
+ + + +
+
+ +
+ +
+ + +
+
+
+ +
+ + +
+
+
+ +
+ + +
+
+
+
+ + {/* Center Panel Skeleton */} +
+
+ + +
+
+ + +
+
+ + + +
+
+ + +
+
+ + {/* Right Panel Skeleton */} +
+ +
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + + +
+
+
+ ) : view === "summary" ? ( + + {/* Left Panel — Notifications */} +
+
+
+

Notifications

+
+ Recent +
+
+ + {/* Tabs */} +
+ {tabs.map((tab) => ( + + ))} +
+ + {/* Findings */} + {(activeTab === "All" || activeTab === "Findings") && ( + <> +

+ Findings +

+
+ {findings.map((finding, i) => ( +
{ setSelectedFinding(prev => prev === i ? null : i); setChatMessages([]); setDuplicateMarked(false); setNet45Accepted(false); } : undefined} + className={cn( + "p-3 rounded-lg border transition-colors", + finding.detail && "cursor-pointer", + selectedFinding === i + ? "border-primary bg-primary/5" + : "border-border/50 bg-card/50", + )} + > +
+
+ + {finding.label} +
+
+ + {finding.time} + +
+
+
+ {finding.description && ( +

+ {finding.description} +

+ )} +
+ ))} +
+ + )} + + {/* Activity Timeline */} + {(activeTab === "All" || activeTab === "Activity") && ( + <> +

+ Activity timeline +

+
+ {timelineItems.map((item, i) => ( +
+
+
+ {i < timelineItems.length - 1 && ( +
+ )} +
+
+

{item.text}

+

+ {item.time} +

+
+
+ ))} +
+ + )} +
+
+
+ + {/* Center Panel — AI Summary */} +
+
+ {/* Agent Message */} +
+
+

AI Summary

+ + 2m ago + +
+ + {selectedFinding !== null && findings[selectedFinding]?.detail ? ( + +

+ {findings[selectedFinding].detail.headline} +

+

+ {findings[selectedFinding].detail.summary} +

+
+ {findings[selectedFinding].detail.actions.map((action) => { + const isActive = + (action === "Mark as Duplicate" && duplicateMarked) || + (action === "Accept Net 45" && net45Accepted); + return ( + + ); + })} +
+
+ ) : ( + +

+ INV-4021 from Acme Corp has been fully processed and is + ready for payment approval. +

+

+ The three-way match between the invoice, purchase order + PO-7710, and goods receipt GRN-8842 was completed + successfully. Two findings were flagged: a potential + duplicate line item and a payment terms discrepancy. + Neither is blocking, but both may warrant review before + final approval. +

+
+ + +
+
+ )} +
+
+ + {/* Chat Messages */} + {chatMessages.length > 0 && ( +
+ {chatMessages.map((msg, i) => ( + +
+ {msg.status === "loading" ? ( + + ) : ( + + )} +
+

{msg.text}

+
+ ))} +
+ )} +
+ + {/* Chat Input */} +
+
+