From f9373bcd1f52414f550d676fd984f6a7c42ea1cf Mon Sep 17 00:00:00 2001 From: hfrancis31 Date: Thu, 26 Feb 2026 08:53:41 -0600 Subject: [PATCH] chore(apollo-vertex): glass light theme --- apps/apollo-vertex/app/_meta.ts | 1 + .../app/haidy-theme-option/page.mdx | 53 ++ apps/apollo-vertex/next-env.d.ts | 2 +- .../templates/FullscreenWrapper.tsx | 45 ++ .../templates/HaidyThemeDashboard.tsx | 465 +++++++++++ .../HaidyThemeDashboardAccessible.tsx | 490 ++++++++++++ .../templates/HaidyThemeLoanQC.tsx | 752 ++++++++++++++++++ 7 files changed, 1807 insertions(+), 1 deletion(-) create mode 100644 apps/apollo-vertex/app/haidy-theme-option/page.mdx create mode 100644 apps/apollo-vertex/templates/FullscreenWrapper.tsx create mode 100644 apps/apollo-vertex/templates/HaidyThemeDashboard.tsx create mode 100644 apps/apollo-vertex/templates/HaidyThemeDashboardAccessible.tsx create mode 100644 apps/apollo-vertex/templates/HaidyThemeLoanQC.tsx diff --git a/apps/apollo-vertex/app/_meta.ts b/apps/apollo-vertex/app/_meta.ts index 75ea5fb76..666004f63 100644 --- a/apps/apollo-vertex/app/_meta.ts +++ b/apps/apollo-vertex/app/_meta.ts @@ -4,5 +4,6 @@ export default { "shadcn-components": "Shadcn Components", "vertex-components": "Vertex Components", themes: "Themes", + "haidy-theme-option": "Haidy Theme Option", localization: "Localization", }; diff --git a/apps/apollo-vertex/app/haidy-theme-option/page.mdx b/apps/apollo-vertex/app/haidy-theme-option/page.mdx new file mode 100644 index 000000000..99233a9af --- /dev/null +++ b/apps/apollo-vertex/app/haidy-theme-option/page.mdx @@ -0,0 +1,53 @@ +import { HaidyThemeDashboard } from '@/templates/HaidyThemeDashboard'; +import { HaidyThemeDashboardAccessible } from '@/templates/HaidyThemeDashboardAccessible'; +import { HaidyThemeLoanQC } from '@/templates/HaidyThemeLoanQC'; +import { FullscreenWrapper } from '@/templates/FullscreenWrapper'; + +# Haidy Theme Option + +A frosted glass UI aesthetic applied to the Invoice Processing dashboard. Soft translucent panels, gentle glows, and minimal contrast. + +## Original + +
+
+ + + +
+
+ +## Accessible Version (WCAG AA) + +All text meets WCAG 2.1 AA contrast requirements (4.5:1 for normal text, 3:1 for large text and non-text elements). Hierarchy is preserved through font weight and size instead of low-contrast colors. + +| Change | Before | After | Contrast | +|---|---|---|---| +| Dates, subtitles | `slate-400` (2.29:1) | `slate-600` | 5.92:1 | +| Labels, headers | `slate-500` (4.26:1) | `slate-600` | 5.92:1 | +| Active nav | `primary` (~2.90:1) | `#0e7490` dark teal | 4.81:1 | +| Failed badge | `rose-600` (4.05:1) | `rose-700` | ~5.4:1 | +| Positive change | `emerald-600/80` (2.64:1) | `emerald-700` | 4.90:1 | +| Negative change | `rose-500/80` (2.74:1) | `rose-600` | 4.76:1 | +| Chart axis | `#94a3b8` (2.29:1) | `#475569` | 5.92:1 | +| Icons | `slate-400` (2.29:1) | `slate-500` | 4.26:1 (3:1 req for non-text) | + +
+
+ + + +
+
+ +## Loan Processing QC — Frosted Glass Theme + +The same accessible frosted glass aesthetic applied to the Loan Processing QC screen. Three-panel layout with compliance rules sidebar, dual document viewers, and glassmorphism buttons with hover states. + +
+
+ + + +
+
diff --git a/apps/apollo-vertex/next-env.d.ts b/apps/apollo-vertex/next-env.d.ts index 9edff1c7c..c4b7818fb 100644 --- a/apps/apollo-vertex/next-env.d.ts +++ b/apps/apollo-vertex/next-env.d.ts @@ -1,6 +1,6 @@ /// /// -import "./.next/types/routes.d.ts"; +import "./.next/dev/types/routes.d.ts"; // NOTE: This file should not be edited // see https://nextjs.org/docs/app/api-reference/config/typescript for more information. diff --git a/apps/apollo-vertex/templates/FullscreenWrapper.tsx b/apps/apollo-vertex/templates/FullscreenWrapper.tsx new file mode 100644 index 000000000..a38b3700c --- /dev/null +++ b/apps/apollo-vertex/templates/FullscreenWrapper.tsx @@ -0,0 +1,45 @@ +"use client"; + +import { Maximize2, Minimize2 } from "lucide-react"; +import { useCallback, useEffect, useRef, useState, type PropsWithChildren } from "react"; + +export function FullscreenWrapper({ children }: PropsWithChildren) { + const containerRef = useRef(null); + const [isFullscreen, setIsFullscreen] = useState(false); + + const toggleFullscreen = useCallback(() => { + if (!containerRef.current) return; + + if (!document.fullscreenElement) { + containerRef.current.requestFullscreen(); + } else { + document.exitFullscreen(); + } + }, []); + + useEffect(() => { + const onChange = () => { + setIsFullscreen(!!document.fullscreenElement); + }; + document.addEventListener("fullscreenchange", onChange); + return () => document.removeEventListener("fullscreenchange", onChange); + }, []); + + return ( +
+ {children} + +
+ ); +} diff --git a/apps/apollo-vertex/templates/HaidyThemeDashboard.tsx b/apps/apollo-vertex/templates/HaidyThemeDashboard.tsx new file mode 100644 index 000000000..04bbf70c3 --- /dev/null +++ b/apps/apollo-vertex/templates/HaidyThemeDashboard.tsx @@ -0,0 +1,465 @@ +"use client"; + +import { + BarChart3, + CheckCircle2, + Clock, + Eye, + FileText, + FolderOpen, + Home, + Settings, + TrendingUp, + Users, +} from "lucide-react"; +import { Bar, BarChart, CartesianGrid, XAxis, YAxis } from "recharts"; + +import { Badge } from "@/components/ui/badge"; +import { + ChartContainer, + ChartTooltip, + ChartTooltipContent, + type ChartConfig, +} from "@/components/ui/chart"; + +// --------------------------------------------------------------------------- +// Data +// --------------------------------------------------------------------------- + +const invoices = [ + { + id: "INV-4021", + vendor: "Acme Corp", + amount: "$12,450.00", + status: "Processed", + date: "Feb 25, 2026", + }, + { + id: "INV-4020", + vendor: "Global Supplies Ltd", + amount: "$3,280.50", + status: "Pending", + date: "Feb 25, 2026", + }, + { + id: "INV-4019", + vendor: "TechParts Inc", + amount: "$8,920.00", + status: "In Review", + date: "Feb 24, 2026", + }, + { + id: "INV-4018", + vendor: "Office Depot", + amount: "$1,150.75", + status: "Processed", + date: "Feb 24, 2026", + }, + { + id: "INV-4017", + vendor: "CloudServ Solutions", + amount: "$24,000.00", + status: "Failed", + date: "Feb 23, 2026", + }, + { + id: "INV-4016", + vendor: "Metro Logistics", + amount: "$6,780.00", + status: "Processed", + date: "Feb 23, 2026", + }, +]; + +const chartData = [ + { day: "Mon", processed: 32 }, + { day: "Tue", processed: 28 }, + { day: "Wed", processed: 45 }, + { day: "Thu", processed: 38 }, + { day: "Fri", processed: 52 }, + { day: "Sat", processed: 18 }, + { day: "Sun", processed: 12 }, +]; + +const chartConfig = { + processed: { + label: "Processed", + color: "var(--color-primary)", + }, +} satisfies ChartConfig; + +const activities = [ + { text: "INV-4021 processed successfully", time: "2 min ago" }, + { text: "INV-4020 submitted for review", time: "15 min ago" }, + { text: "Batch processing completed (42 invoices)", time: "1 hr ago" }, + { + text: "INV-4017 failed \u2014 missing PO number", + time: "3 hrs ago", + }, +]; + +const pipeline = [ + { label: "OCR Extraction", value: 96, color: "bg-primary" }, + { label: "Field Validation", value: 88, color: "bg-amber-400" }, + { label: "Approval Routing", value: 72, color: "bg-violet-500" }, +]; + +// --------------------------------------------------------------------------- +// Shared glass card styles +// --------------------------------------------------------------------------- + +const glassCard = [ + "rounded-2xl", + "bg-white/50", + "backdrop-blur-[20px]", + "border border-white/70", + "shadow-[0_0_0_1px_rgba(255,255,255,0.7),0_0_48px_-2px_rgba(185,205,230,0.45),0_0_28px_0_rgba(220,232,245,0.30),0_0_12px_0_rgba(255,255,255,0.25),inset_0_2px_20px_0_rgba(255,255,255,0.60),inset_0_-1px_6px_0_rgba(200,215,235,0.12)]", +].join(" "); + +// --------------------------------------------------------------------------- +// Status badge helper +// --------------------------------------------------------------------------- + +function StatusBadge({ status }: { status: string }) { + const map: Record = { + Processed: { + className: + "bg-emerald-100/60 text-emerald-700 border-emerald-200/40 shadow-[0_0_10px_-2px_rgba(16,185,129,0.12),inset_0_1px_4px_0_rgba(255,255,255,0.4)]", + }, + Pending: { + className: + "bg-white/50 text-slate-600 border-slate-200/40 shadow-[0_0_10px_-2px_rgba(185,205,230,0.15),inset_0_1px_4px_0_rgba(255,255,255,0.35)]", + }, + "In Review": { + className: + "bg-white/50 text-slate-600 border-slate-200/40 shadow-[0_0_10px_-2px_rgba(185,205,230,0.15),inset_0_1px_4px_0_rgba(255,255,255,0.35)]", + }, + Failed: { + className: + "bg-rose-100/50 text-rose-600 border-rose-200/40 shadow-[0_0_10px_-2px_rgba(244,63,94,0.10),inset_0_1px_4px_0_rgba(255,255,255,0.4)]", + }, + }; + + const style = map[status] ?? map.Pending; + + return ( + + {status} + + ); +} + +// --------------------------------------------------------------------------- +// Stat Card +// --------------------------------------------------------------------------- + +function StatCard({ + icon: Icon, + label, + value, + change, +}: { + icon: React.ComponentType<{ className?: string }>; + label: string; + value: string; + change: string; +}) { + const isPositive = change.startsWith("+"); + return ( +
+
+ {label} + +
+
+ {value} +
+ + {change} from last week + +
+ ); +} + +// --------------------------------------------------------------------------- +// Sidebar +// --------------------------------------------------------------------------- + +const navItems = [ + { icon: Home, label: "Dashboard", active: true }, + { icon: FolderOpen, label: "Projects" }, + { icon: BarChart3, label: "Analytics" }, + { icon: Users, label: "Team" }, + { icon: Settings, label: "Settings" }, + { icon: Eye, label: "Toggle Content" }, +]; + +const glassNavActive = "text-primary font-semibold"; + +function GlassSidebar() { + return ( + + ); +} + +// --------------------------------------------------------------------------- +// Main Dashboard +// --------------------------------------------------------------------------- + +export function HaidyThemeDashboard() { + return ( +
+ {/* Sidebar */} + + + {/* Main content */} +
+ {/* Header */} +
+

+ Invoice Processing +

+

+ Monitor and manage invoice automation workflows +

+
+ + {/* Stat cards */} +
+ + + + +
+ + {/* Invoices table */} +
+
+

+ Recent Invoices +

+
+
+ + + + + + + + + + + + {invoices.map((inv) => ( + + + + + + + + ))} + +
+ Invoice + + Vendor + + Amount + + Status + + Date +
+ {inv.id} + {inv.vendor} + {inv.amount} + + + {inv.date}
+
+
+ + {/* Chart + Activity row */} +
+ {/* Processing Activity chart */} +
+

+ Processing Activity +

+ + + + + + + + + + + + + + + + + + + } + /> + + + +
+ + {/* Recent Activity */} +
+

+ Recent Activity +

+
+ {activities.map((a) => ( +
+
+
+

+ {a.text} +

+

{a.time}

+
+
+ ))} +
+
+
+ + {/* Processing Pipeline */} +
+

+ Processing Pipeline +

+
+ {pipeline.map((p) => ( +
+
+ {p.label} + {p.value}% +
+
+
+
+
+ ))} +
+
+
+
+ ); +} diff --git a/apps/apollo-vertex/templates/HaidyThemeDashboardAccessible.tsx b/apps/apollo-vertex/templates/HaidyThemeDashboardAccessible.tsx new file mode 100644 index 000000000..f1785dc6f --- /dev/null +++ b/apps/apollo-vertex/templates/HaidyThemeDashboardAccessible.tsx @@ -0,0 +1,490 @@ +"use client"; + +import { + BarChart3, + CheckCircle2, + Clock, + Eye, + FileText, + FolderOpen, + Home, + Settings, + TrendingUp, + Users, +} from "lucide-react"; +import { Bar, BarChart, CartesianGrid, XAxis, YAxis } from "recharts"; + +import { Badge } from "@/components/ui/badge"; +import { + ChartContainer, + ChartTooltip, + ChartTooltipContent, + type ChartConfig, +} from "@/components/ui/chart"; + +// --------------------------------------------------------------------------- +// Data +// --------------------------------------------------------------------------- + +const invoices = [ + { + id: "INV-4021", + vendor: "Acme Corp", + amount: "$12,450.00", + date: "Feb 25, 2026", + status: "Processed", + }, + { + id: "INV-4020", + vendor: "Global Supplies Ltd", + amount: "$3,280.50", + date: "Feb 25, 2026", + status: "Pending", + }, + { + id: "INV-4019", + vendor: "TechParts Inc", + amount: "$8,920.00", + date: "Feb 24, 2026", + status: "In Review", + }, + { + id: "INV-4018", + vendor: "Office Depot", + amount: "$1,150.75", + date: "Feb 24, 2026", + status: "Processed", + }, + { + id: "INV-4017", + vendor: "CloudServ Solutions", + amount: "$24,000.00", + date: "Feb 23, 2026", + status: "Failed", + }, + { + id: "INV-4016", + vendor: "Metro Logistics", + amount: "$6,780.00", + date: "Feb 23, 2026", + status: "Processed", + }, +]; + +const chartData = [ + { day: "Mon", processed: 32 }, + { day: "Tue", processed: 28 }, + { day: "Wed", processed: 45 }, + { day: "Thu", processed: 38 }, + { day: "Fri", processed: 52 }, + { day: "Sat", processed: 18 }, + { day: "Sun", processed: 12 }, +]; + +const chartConfig = { + processed: { + label: "Processed", + color: "var(--color-primary)", + }, +} satisfies ChartConfig; + +const activities = [ + { text: "INV-4021 processed successfully", time: "2 min ago" }, + { text: "INV-4020 submitted for review", time: "15 min ago" }, + { text: "Batch processing completed (42 invoices)", time: "1 hr ago" }, + { + text: "INV-4017 failed \u2014 missing PO number", + time: "3 hrs ago", + }, +]; + +const pipeline = [ + { label: "OCR Extraction", value: 96, color: "bg-primary" }, + { label: "Field Validation", value: 88, color: "bg-amber-400" }, + { label: "Approval Routing", value: 72, color: "bg-violet-500" }, +]; + +// --------------------------------------------------------------------------- +// Shared glass card styles +// --------------------------------------------------------------------------- + +const glassCard = [ + "rounded-2xl", + "bg-white/50", + "backdrop-blur-[20px]", + "border border-white/70", + "shadow-[0_0_0_1px_rgba(255,255,255,0.7),0_0_48px_-2px_rgba(185,205,230,0.45),0_0_28px_0_rgba(220,232,245,0.30),0_0_12px_0_rgba(255,255,255,0.25),inset_0_2px_20px_0_rgba(255,255,255,0.60),inset_0_-1px_6px_0_rgba(200,215,235,0.12)]", +].join(" "); + +// --------------------------------------------------------------------------- +// Accessible active nav color — darker teal (#0e7490) at 4.81:1 contrast +// --------------------------------------------------------------------------- + +const glassNavActive = "text-[#0e7490] font-semibold"; + +// --------------------------------------------------------------------------- +// Status badge helper — accessible text colors +// --------------------------------------------------------------------------- + +function StatusBadge({ status }: { status: string }) { + const map: Record = { + Processed: { + className: + "bg-emerald-100/60 text-emerald-700 border-emerald-200/40 shadow-[0_0_10px_-2px_rgba(16,185,129,0.12),inset_0_1px_4px_0_rgba(255,255,255,0.4)]", + }, + Pending: { + className: + "bg-white/50 text-slate-700 border-slate-200/40 shadow-[0_0_10px_-2px_rgba(185,205,230,0.15),inset_0_1px_4px_0_rgba(255,255,255,0.35)]", + }, + "In Review": { + className: + "bg-white/50 text-slate-700 border-slate-200/40 shadow-[0_0_10px_-2px_rgba(185,205,230,0.15),inset_0_1px_4px_0_rgba(255,255,255,0.35)]", + }, + Failed: { + className: + "bg-rose-100/50 text-rose-700 border-rose-200/40 shadow-[0_0_10px_-2px_rgba(244,63,94,0.10),inset_0_1px_4px_0_rgba(255,255,255,0.4)]", + }, + }; + + const style = map[status] ?? map.Pending; + + return ( + + {status} + + ); +} + +// --------------------------------------------------------------------------- +// Stat Card — accessible version +// --------------------------------------------------------------------------- + +function StatCard({ + icon: Icon, + label, + value, + change, +}: { + icon: React.ComponentType<{ className?: string }>; + label: string; + value: string; + change: string; +}) { + const isPositive = change.startsWith("+"); + return ( +
+
+ {label} + +
+
+ {value} +
+ + {change} from last week + +
+ ); +} + +// --------------------------------------------------------------------------- +// Sidebar — accessible version +// --------------------------------------------------------------------------- + +const navItems = [ + { icon: Home, label: "Dashboard", active: true }, + { icon: FolderOpen, label: "Projects" }, + { icon: BarChart3, label: "Analytics" }, + { icon: Users, label: "Team" }, + { icon: Settings, label: "Settings" }, + { icon: Eye, label: "Toggle Content" }, +]; + +function GlassSidebar() { + return ( + + ); +} + +// --------------------------------------------------------------------------- +// Main Dashboard — Accessible Version +// --------------------------------------------------------------------------- + +export function HaidyThemeDashboardAccessible() { + return ( +
+ {/* Sidebar */} + + + {/* Main content */} +
+ {/* Header */} +
+

+ Invoice Processing +

+

+ Monitor and manage invoice automation workflows +

+
+ + {/* Stat cards */} +
+ + + + +
+ + {/* Invoices table */} +
+
+

+ Recent Invoices +

+
+
+ + + + + + + + + + + + {invoices.map((inv) => ( + + + + + + + + ))} + +
+ Invoice + + Vendor + + Amount + + Status + + Date +
+ {inv.id} + {inv.vendor} + {inv.amount} + + + {inv.date}
+
+
+ + {/* Chart + Activity row */} +
+ {/* Processing Activity chart */} +
+

+ Processing Activity +

+ + + + + + + + + + + + + + + + + + + } + /> + + + +
+ + {/* Recent Activity */} +
+

+ Recent Activity +

+
+ {activities.map((a) => ( +
+
+
+

+ {a.text} +

+

{a.time}

+
+
+ ))} +
+
+
+ + {/* Processing Pipeline */} +
+

+ Processing Pipeline +

+
+ {pipeline.map((p) => ( +
+
+ {p.label} + + {p.value}% + +
+
+
+
+
+ ))} +
+
+
+
+ ); +} diff --git a/apps/apollo-vertex/templates/HaidyThemeLoanQC.tsx b/apps/apollo-vertex/templates/HaidyThemeLoanQC.tsx new file mode 100644 index 000000000..7962af50d --- /dev/null +++ b/apps/apollo-vertex/templates/HaidyThemeLoanQC.tsx @@ -0,0 +1,752 @@ +"use client"; + +import { + ArrowLeft, + Bell, + ChevronLeft, + ChevronRight, + MessageSquare, + Minus, + Settings, + User, + ZoomIn, + ZoomOut, +} from "lucide-react"; +import { useState } from "react"; + +// --------------------------------------------------------------------------- +// Shared glass styles (accessible — all text ≥ 4.5:1 on ~#eff3f7) +// --------------------------------------------------------------------------- + +/** Standard glass card — fully rounded */ +const glassCard = [ + "rounded-2xl", + "bg-white/50", + "backdrop-blur-[20px]", + "border border-white/70", + "shadow-[0_0_0_1px_rgba(255,255,255,0.7),0_0_48px_-2px_rgba(185,205,230,0.45),0_0_28px_0_rgba(220,232,245,0.30),0_0_12px_0_rgba(255,255,255,0.25),inset_0_2px_20px_0_rgba(255,255,255,0.60),inset_0_-1px_6px_0_rgba(200,215,235,0.12)]", +].join(" "); + +/** Left sidebar — no rounding, flush to left edge, right border acts as divider */ +const glassSidebar = [ + "rounded-none", + "bg-white/50", + "backdrop-blur-[20px]", + "border-r border-white/60", +].join(" "); + +// --------------------------------------------------------------------------- +// Data — Credit Approval Memo +// --------------------------------------------------------------------------- + +const memoProfile = { + relationship: "TILBRAE LOGISTICS GROUP LLC", + loanOfficer: "NELA MORVINE", + date: "04/07/2025", + creditAnalyst: "MARISSA DRENLOW", + borrowerMember: "TILBRAE LOGISTICS GROUP LLC", + memberNumber: "7654321-00", + memberSince: "06/06/2025", + streetAddress: "1914 East Varella Parkway", + cityStateZip: "Stonefield, WY 82932", + legalStructure: "LLC", + dateEstablished: "18/03/2025", + tinSsn: "71-8493026", + naicsCode: "484121 — General Freight", + naicsDesc: "Trucking, Long-Distance;", + currentTotalExposure: "$31,000", + proposedChanges: "$50,900", + plusOutstandingApprovals: "$0", + proposedTotalExposure: "$81,900", + averageYtdDeposits: "$0", + totalCurrentDeposits: "$4,999", +}; + +const memoNotes = `The member established since 06/06/2025: registered with Sumcompany on 01/01/2025. Both business and personal deposits $4,999 with ABC, credit pulled 03/17/2025 (700) and SMTHSmth on 03/24/2025. Bus SMTHSmth Free of any judgments, liens, foreclosures. Personal SMTHSmth Judgment $1000, liens, foreclosures. John Free of any judgments, liens, foreclosures. The members are 50/50 of TILBRAE LOGISTICS GROUP LLC MICRO Equipment Loan ***`; + +const memoNotes2 = `The member established since 03/04/2025: registered with Sumcompany on 01/01/2025. The members are 50/50 owners of TILBRAE LOGISTICS GROUP LLC *** (Micro Equipment Loan) MICRO Equipment loan to purchase a Commercial Truck (VIN 7FZKBYA22MN014983), Projected Annual Gross Revenue - $200,000 based on the application provided. No existing business liabilities. certificate of origin, and course certificate has been saved to the Z drive.`; + +// --------------------------------------------------------------------------- +// Compliance rules +// --------------------------------------------------------------------------- + +type RuleEvaluation = + | "yes" + | "no" + | "na" + | "inconclusive" + | "needs_review" + | null; + +interface ComplianceRule { + label: string; + agentEval: string | null; + agentReasoning: string | null; + humanEval: RuleEvaluation; + showHumanButtons: + | "yes_no_na" + | "yes_person_minus" + | "no_person_minus" + | "inconclusive_needs_review"; +} + +const complianceRules: ComplianceRule[] = [ + { + label: "Rule name-", + agentEval: "No", + agentReasoning: "[Agent reasoning goes here...]", + humanEval: null, + showHumanButtons: "yes_no_na", + }, + { + label: "Loan amount does not exceed approved amount", + agentEval: null, + agentReasoning: null, + humanEval: "yes", + showHumanButtons: "yes_person_minus", + }, + { + label: "Lender's address matches Credit Memo", + agentEval: null, + agentReasoning: null, + humanEval: "no", + showHumanButtons: "no_person_minus", + }, + { + label: "Borrower's name for signature matches ID", + agentEval: null, + agentReasoning: null, + humanEval: "inconclusive", + showHumanButtons: "inconclusive_needs_review", + }, +]; + +// --------------------------------------------------------------------------- +// Small reusable components +// --------------------------------------------------------------------------- + +function GlassButton({ + children, + variant = "default", + active = false, + className = "", +}: { + children: React.ReactNode; + variant?: "default" | "yes" | "no" | "inconclusive" | "needs_review"; + active?: boolean; + className?: string; +}) { + const base = + "px-3 py-1.5 rounded-xl text-xs font-medium transition-all cursor-pointer backdrop-blur-[6px] border"; + + const variants: Record = { + default: active + ? "bg-white/60 text-slate-700 border-slate-200/40 shadow-[0_0_10px_-2px_rgba(185,205,230,0.15),inset_0_1px_4px_0_rgba(255,255,255,0.35)]" + : "bg-white/30 text-slate-600 border-white/40 hover:bg-white/50 hover:shadow-[0_0_12px_-2px_rgba(185,205,230,0.25)]", + yes: active + ? "bg-[#0e7490]/10 text-[#0e7490] border-[#0e7490]/20 shadow-[0_0_10px_-2px_rgba(14,116,144,0.15),inset_0_1px_4px_0_rgba(255,255,255,0.35)]" + : "bg-white/30 text-slate-600 border-white/40 hover:bg-[#0e7490]/5 hover:text-[#0e7490] hover:border-[#0e7490]/15 hover:shadow-[0_0_12px_-2px_rgba(14,116,144,0.15)]", + no: active + ? "bg-rose-100/50 text-rose-700 border-rose-200/40 shadow-[0_0_10px_-2px_rgba(244,63,94,0.10),inset_0_1px_4px_0_rgba(255,255,255,0.4)]" + : "bg-white/30 text-slate-600 border-white/40 hover:bg-rose-50/40 hover:text-rose-700 hover:border-rose-200/30 hover:shadow-[0_0_12px_-2px_rgba(244,63,94,0.10)]", + inconclusive: active + ? "bg-white/60 text-slate-700 border-slate-300/40 shadow-[0_0_10px_-2px_rgba(185,205,230,0.15),inset_0_1px_4px_0_rgba(255,255,255,0.35)]" + : "bg-white/30 text-slate-600 border-white/40 hover:bg-white/50 hover:shadow-[0_0_12px_-2px_rgba(185,205,230,0.2)]", + needs_review: active + ? "bg-amber-100/50 text-amber-800 border-amber-200/40 shadow-[0_0_10px_-2px_rgba(245,158,11,0.12),inset_0_1px_4px_0_rgba(255,255,255,0.4)]" + : "bg-white/30 text-slate-600 border-white/40 hover:bg-amber-50/40 hover:text-amber-800 hover:border-amber-200/30 hover:shadow-[0_0_12px_-2px_rgba(245,158,11,0.10)]", + }; + + return ( + + ); +} + +function IconButton({ + children, + className = "", +}: { + children: React.ReactNode; + className?: string; +}) { + return ( + + ); +} + +// --------------------------------------------------------------------------- +// Zoom controls +// --------------------------------------------------------------------------- + +function ZoomControls({ + zoom, + onZoomIn, + onZoomOut, +}: { + zoom: number; + onZoomIn: () => void; + onZoomOut: () => void; +}) { + return ( +
+ + + {Math.round(zoom * 100)}% + + +
+ ); +} + +// --------------------------------------------------------------------------- +// Credit Approval Memo Document +// --------------------------------------------------------------------------- + +function CreditMemoDocument({ zoom }: { zoom: number }) { + return ( +
+

+ Credit Approval Memo +

+ + {/* Header fields */} +
+
+ + Relationship: + + {memoProfile.relationship} +
+
+ + Date: + + {memoProfile.date} +
+
+ + Loan Officer: + + {memoProfile.loanOfficer} +
+
+ + Credit Analyst: + + {memoProfile.creditAnalyst} +
+
+ + {/* PROFILE section */} +
+

+ PROFILE +

+
+ {/* Left column */} +
+ + + + + + + +
{memoProfile.naicsDesc}
+
+ {/* Right column */} +
+ + + + + + + + +
+
+
+ + {/* Loan Request Purpose */} +
+

Loan Request Purpose:

+

+ Micro equipment loan to purchase a Commercial Truck (VIN + 7FZKBYA22MN014983) +

+
+ + {/* Notes */} +
+

Notes:

+

{memoNotes}

+
+ + {/* Concentration */} +
+

Concentration: 26

+

Annual Revenue of $ 200K

+

Agg Exposure $81,900

+
+ + {/* Second notes block */} +

{memoNotes2}

+ + {/* Request header row */} +
+ 12345-6 + Request 5556677 + + New Money- +
+ 5556677 +
+
+ + {/* Loan type grid */} +
+
+ HOMA{" "} + NO +
+
+ CRA{" "} + NO +
+
+ CD{" "} + NO +
+
+ REG-0{" "} + NO +
+
+ TDR{" "} + NO +
+
+ HVCRE{" "} + NO +
+
+ Gov. Guarantee{" "} + NO +
+
+ Participation:{" "} + NO +
+
+ + Policy Exception: + {" "} + NO +
+
+ FLOOD{" "} + NO +
+
+ + {/* Borrower/Co-Borrower table */} +
+

+ BORROWER/CO-BORROWER INFORMATION +

+ + + + + + + + + + + + + + + + + + + + + + + + + +
+ Name + + Global DSCR +
+ Current +
+ Proj + + Liquidity + + Adj. +
+ Net Worth +
+ Net Worth + + DTI + + DSCR +
+ Before/After +
+ Stmt Date + + Score +
+ TILBRAE LOGISTICS GROUP -
+ Borrower +
0.000.00 + + + N/A0.00/0.00 + N/A
+
+
+ ); +} + +function ProfileRow({ label, value }: { label: string; value: string }) { + return ( +
+ + {label} + + {value} +
+ ); +} + +// --------------------------------------------------------------------------- +// Compliance Rule Card +// --------------------------------------------------------------------------- + +function RuleCard({ rule }: { rule: ComplianceRule }) { + return ( +
+ {/* Rule label with dot */} +
+
+ + {rule.label} + +
+ + {/* Agent evaluation */} + {rule.agentEval !== null && ( +
+

+ Agent evaluation:{" "} + {rule.agentEval} +

+ {rule.agentReasoning && ( +

{rule.agentReasoning}

+ )} +
+ )} + + {/* Human evaluation */} +
+ {rule.agentEval !== null && ( +

+ Human evaluation +

+ )} + +
+ {rule.showHumanButtons === "yes_no_na" && ( + <> + Yes + No + N/A + + + + + )} + + {rule.showHumanButtons === "yes_person_minus" && ( + <> + + Yes + + + + + + + + + )} + + {rule.showHumanButtons === "no_person_minus" && ( + <> + + No + + + + + + + + + )} + + {rule.showHumanButtons === "inconclusive_needs_review" && ( + <> + + Inconclusive + + + + + Needs review + + + + )} +
+
+
+ ); +} + +// --------------------------------------------------------------------------- +// Main Export +// --------------------------------------------------------------------------- + +export function HaidyThemeLoanQC() { + const [currentCategory] = useState(1); + const totalCategories = 6; + + // Independent zoom state for each document pane + const [leftZoom, setLeftZoom] = useState(1); + const [rightZoom, setRightZoom] = useState(1); + + const ZOOM_STEP = 0.1; + const ZOOM_MIN = 0.5; + const ZOOM_MAX = 2; + + const clampZoom = (z: number) => Math.min(ZOOM_MAX, Math.max(ZOOM_MIN, z)); + + return ( +
+ {/* ── Top header bar — same bg as left pane, extends behind ── */} +
+
+
+ +
+ + Loan processing - QC + +
+
+ + + +
+ +
+
+
+ + {/* ── Section header — same bg, full-width horizontal line below ── */} +
+ +
+

+ Section name +

+

CRE #00000000000-00

+
+
+ + {/* ── Main content: 12-col grid, 16px gutters ── */} +
+ {/* Left sidebar — spans 3 cols, no rounding, flush to left edge */} + + + {/* Document area — spans 9 cols, no container, sits on background */} +
+ {/* Left document pane */} +
+
+
+ +
+
+ + setLeftZoom((z) => clampZoom(z + ZOOM_STEP)) + } + onZoomOut={() => + setLeftZoom((z) => clampZoom(z - ZOOM_STEP)) + } + /> +
+ + {/* Vertical divider */} +
+ + {/* Right document pane */} +
+
+
+ +
+
+ + setRightZoom((z) => clampZoom(z + ZOOM_STEP)) + } + onZoomOut={() => + setRightZoom((z) => clampZoom(z - ZOOM_STEP)) + } + /> +
+
+
+
+ ); +}