Cash Flow Treasury Dashboard
blockfinanceFluxo and Mercury style corporate cash flow treasury dashboard with 30-day runway projection, liquidity buffer monitor, inflow/outflow breakdown, and connected bank accounts table.
Installation
$pnpm dlx uipkge-ng@latest add cash-flow-treasury-dashboard$npx uipkge-ng@latest add cash-flow-treasury-dashboard$yarn dlx uipkge-ng@latest add cash-flow-treasury-dashboard$bunx uipkge-ng@latest add cash-flow-treasury-dashboardManifest:
https://uipkge.dev/r/angular/cash-flow-treasury-dashboard.jsonInstalls to:src/app/components/blocks/cash-flow-treasury-dashboard/Variants
Loading interactive previews…
Loading interactive previews…
Schema
Type aliases exported from this item's source. Use these to shape the data you pass in.
HealthCardinterface HealthCard {
title: string
value: string
highlight?: string
badgeText: string
badgeVariant: 'default' | 'secondary' | 'outline' | 'destructive'
badgeClass?: string
description: string
metricFootnote: string
progressValue: number
// Carried for parity with the React/Vue data; the card chrome renders no icon.
icon: unknown
}FlowIteminterface FlowItem {
id: string
name: string
amount: string
rawAmount: number
percentage: number
category: string
detail: string
color: string
}BankAccountinterface BankAccount {
id: string
name: string
institution: string
accountType: string
accountMask: string
routingMask: string
purpose: string
apy: string
balance: string
balanceRaw: number
status: 'Active' | 'Yield Sweeping' | 'Ringfenced' | 'Reserve Locked'
statusType: 'success' | 'info' | 'warning'
isSweepAccount?: boolean
}Files installed (2)
src/app/components/blocks/cash-flow-treasury-dashboard/cash-flow-treasury-dashboard.component.ts41.7 kB
// Fluxo and Mercury style corporate cash flow treasury dashboard: 30-day runway // projection with a confidence corridor, liquidity buffer monitor, inflow/outflow // breakdown, and a connected bank accounts table. Port of the React block 1:1. import { ChangeDetectionStrategy, Component, DestroyRef, EventEmitter, Input, Output, inject, signal, } from '@angular/core' import { ArrowDownLeft, ArrowUpRight, Building2, CheckCircle2, Flame, Landmark, Lock, LucideAngularModule, RefreshCw, Send, Shield, ShieldCheck, TrendingUp, Zap, } from 'lucide-angular' import { cn } from '@/lib/utils' import { UiBadgeComponent } from '@/ui/badge/badge.component' import { UiButtonComponent } from '@/ui/button/button.component' import { UiCardComponent, UiCardContentComponent, UiCardDescriptionComponent, UiCardHeaderComponent, UiCardTitleComponent, } from '@/ui/card/card.component' import { UiProgressComponent } from '@/ui/progress/progress.component' import { UiSeparatorComponent } from '@/ui/separator/separator.component' import { UiTableBodyComponent, UiTableCellComponent, UiTableComponent, UiTableHeadComponent, UiTableHeaderComponent, UiTableRowComponent, } from '@/ui/table/table.component' export type TreasuryTab = '30d' | '60d' | '90d' interface HealthCard { title: string value: string highlight?: string badgeText: string badgeVariant: 'default' | 'secondary' | 'outline' | 'destructive' badgeClass?: string description: string metricFootnote: string progressValue: number // Carried for parity with the React/Vue data; the card chrome renders no icon. icon: unknown } const healthCards: HealthCard[] = [ { title: 'Total Cash Runway', value: '28.4 Months Runway', badgeText: 'Optimal Runway', badgeVariant: 'outline', badgeClass: 'bg-success/10 text-success border-success/20', description: 'At current net burn rate ($170.8k/mo)', metricFootnote: 'Zero dilution horizon through Q4 2028', progressValue: 94, icon: ShieldCheck, }, { title: 'Net Monthly Burn', value: '-$170,800.00 / mo', badgeText: '-12% vs budget', badgeVariant: 'outline', badgeClass: 'bg-success/10 text-success border-success/20', description: 'Gross outflow $415.8k offset by $245.0k inflow', metricFootnote: '$23,200 under monthly board approved cap', progressValue: 74, icon: Flame, }, { title: 'Projected 30-Day Inflow', value: '+$245,000.00', badgeText: '+18.4% YoY', badgeVariant: 'outline', badgeClass: 'bg-success/10 text-success border-success/20', description: 'From customer subscriptions & ARR renewals', metricFootnote: '96.2% contracted recurring enterprise revenue', progressValue: 88, icon: TrendingUp, }, { title: 'Yield on Treasury Funds', value: '5.14% APY', highlight: '$20,800/mo interest earned', badgeText: 'Yield Active', badgeVariant: 'outline', badgeClass: 'bg-success/10 text-success border-success/20', description: '$2.85M deployed in US Treasury bills & sweeps', metricFootnote: '$249,600 annualized passive treasury income', progressValue: 98, icon: TrendingUp, }, ] interface FlowItem { id: string name: string amount: string rawAmount: number percentage: number category: string detail: string color: string } const inflowBreakdown: FlowItem[] = [ { id: 'in-arr', name: 'Customer ARR', amount: '+$210,000.00', rawAmount: 210000, percentage: 85.7, category: 'Subscription ARR', detail: 'Enterprise multi-seat renewals and monthly cloud subscriptions', color: 'bg-success', }, { id: 'in-yield', name: 'Treasury Yield', amount: '+$20,800.00', rawAmount: 20800, percentage: 8.5, category: 'Money Market Sweep', detail: 'Accrued interest from 5.14% APY government sweeps & T-Bills', color: 'bg-teal-500', }, { id: 'in-rebates', name: 'Partner Rebates', amount: '+$14,200.00', rawAmount: 14200, percentage: 5.8, category: 'Interchange & Cashback', detail: 'Corporate card cashback and cloud partner referral volume', color: 'bg-cyan-500', }, ] const outflowBreakdown: FlowItem[] = [ { id: 'out-payroll', name: 'Payroll & Benefits', amount: '-$138,400.00', rawAmount: 138400, percentage: 81.0, category: 'Compensation', detail: '42 full-time employees, health insurance, and 401(k) matching', color: 'bg-destructive', }, { id: 'out-infra', name: 'Cloud Infrastructure', amount: '-$24,800.00', rawAmount: 24800, percentage: 14.5, category: 'Hosting & AI APIs', detail: 'AWS compute clusters, Vercel edge deployment, & OpenAI inference', color: 'bg-warning', }, { id: 'out-office', name: 'Office & Taxes', amount: '-$7,600.00', rawAmount: 7600, percentage: 4.5, category: 'G&A and Compliance', detail: 'San Francisco hub lease, state registrations & tax prep reserves', color: 'bg-slate-500 dark:bg-slate-400', }, ] interface BankAccount { id: string name: string institution: string accountType: string accountMask: string routingMask: string purpose: string apy: string balance: string balanceRaw: number status: 'Active' | 'Yield Sweeping' | 'Ringfenced' | 'Reserve Locked' statusType: 'success' | 'info' | 'warning' isSweepAccount?: boolean } const bankAccounts: BankAccount[] = [ { id: 'acc-operating', name: 'Chase Corporate Operating', institution: 'JPMorgan Chase Bank', accountType: 'Operating Checking', accountMask: '•••• 4892', routingMask: '•••• 0210', purpose: 'Primary AP, vendor payouts & incoming customer wires', apy: '0.05% APY', balance: '$1,420,500.00', balanceRaw: 1420500, status: 'Active', statusType: 'success', }, { id: 'acc-sweep', name: 'Morgan Stanley Treasury Sweep', institution: 'Morgan Stanley Private Bank', accountType: 'High-Yield Treasury Sweep', accountMask: '•••• 9210', routingMask: '•••• 0119', purpose: 'Automated daily sweep of cash over $1.0M into 4-week US T-Bills', apy: '5.14% APY', balance: '$2,850,000.00', balanceRaw: 2850000, status: 'Yield Sweeping', statusType: 'success', isSweepAccount: true, }, { id: 'acc-payroll', name: 'SVB Payroll Escrow', institution: 'Silicon Valley Bank', accountType: 'Payroll Escrow', accountMask: '•••• 1049', routingMask: '•••• 1211', purpose: 'Ringfenced 2-cycle buffer for bi-weekly employee compensation', apy: '1.20% APY', balance: '$380,240.00', balanceRaw: 380240, status: 'Ringfenced', statusType: 'info', }, { id: 'acc-tax', name: 'Citi Corporate Tax Reserve', institution: 'Citibank N.A.', accountType: 'Tax Reserve', accountMask: '•••• 8832', routingMask: '•••• 0008', purpose: 'Q3 estimated federal/state taxes & corporate franchise reserves', apy: '2.10% APY', balance: '$199,500.00', balanceRaw: 199500, status: 'Reserve Locked', statusType: 'warning', }, ] const trajectoryMilestones = [ { day: 'Day 1', date: 'Aug 21', title: 'Starting Liquidity', amount: '$4,850,240.00', note: 'Base cash balance', cx: 50, cy: 130, }, { day: 'Day 7', date: 'Aug 28', title: 'Weekly ARR Inflow', amount: '+$52,500.00', note: 'SaaS recurring billing', cx: 180, cy: 123, }, { day: 'Day 15', date: 'Sep 05', title: 'Mid-Month Payroll Run', amount: '-$69,200.00', note: 'Bi-weekly payroll disbursal', cx: 330, cy: 144, }, { day: 'Day 22', date: 'Sep 12', title: 'Enterprise ARR Renewals', amount: '+$185,000.00', note: 'Annual contract renewals', cx: 490, cy: 89, }, { day: 'Day 30', date: 'Sep 20', title: 'Projected Ending Balance', amount: '$4,924,440.00', note: '+$74,200 net increase', cx: 650, cy: 108, }, ] @Component({ changeDetection: ChangeDetectionStrategy.Eager, selector: 'ui-cash-flow-treasury-dashboard, [ui-cash-flow-treasury-dashboard]', standalone: true, // React renders the root <div> itself: the host stays out of layout and `class` goes to the root. host: { '[attr.class]': '"contents"' }, imports: [ LucideAngularModule, UiBadgeComponent, UiButtonComponent, UiCardComponent, UiCardContentComponent, UiCardDescriptionComponent, UiCardHeaderComponent, UiCardTitleComponent, UiProgressComponent, UiSeparatorComponent, UiTableBodyComponent, UiTableCellComponent, UiTableComponent, UiTableHeadComponent, UiTableHeaderComponent, UiTableRowComponent, ], template: ` <div data-slot="cash-flow-treasury-dashboard" [class]="rootClass"> <!-- Top Action Feedback Banner --> @if (actionFeedback()) { <div class="bg-primary/10 border-primary/20 text-foreground flex flex-wrap items-center justify-between gap-x-2 rounded-lg border px-4 py-3 text-xs shadow-xs" > <div class="flex items-center gap-2.5"> <lucide-icon [img]="CheckCircle2" class="text-primary size-4 shrink-0" aria-hidden="true" /> <span class="font-medium">{{ actionFeedback() }}</span> </div> <button type="button" class="text-muted-foreground hover:text-foreground inline-flex size-5 items-center justify-center rounded-sm" (click)="dismissFeedback()" > <span class="sr-only">Close message</span> × </button> </div> } <!-- Header Section --> <div class="bg-card border-border relative overflow-hidden rounded-xl border p-6 shadow-xs"> <div class="flex flex-col gap-6 lg:flex-row lg:items-center lg:justify-between"> <!-- Company & Title --> <div class="space-y-2"> <div class="flex flex-wrap items-center gap-2.5"> <span class="text-muted-foreground font-mono text-xs font-semibold tracking-wider uppercase"> UIPKGE Technologies Inc. </span> <span ui-badge variant="outline" class="border-success/20 bg-success/10 text-success gap-1.5 text-xs font-medium" > <span class="bg-success size-1.5 animate-pulse rounded-full" aria-hidden="true"></span> Live FDIC Insured Sweeps ($5M Aggregate) </span> </div> <div> <h1 class="text-foreground text-2xl font-semibold tracking-tight sm:text-3xl"> Treasury & Cash Flow Management </h1> <p class="text-muted-foreground mt-0.5 text-xs sm:text-sm"> Real-time liquidity monitoring, 30-day runway projection, and automated 5.14% APY yield sweep desk. </p> </div> </div> <!-- Header Action Controls --> <div class="flex flex-wrap items-center gap-2.5"> <button ui-button variant="outline" class="gap-2 text-xs font-medium sm:text-sm" (click)="handleDeposit()"> <lucide-icon [img]="ArrowDownLeft" class="size-4" aria-hidden="true" /> Deposit Funds </button> <button ui-button class="gap-2 text-xs font-medium sm:text-sm" (click)="handleTransfer()"> <lucide-icon [img]="ArrowUpRight" class="size-4" aria-hidden="true" /> Initiate Wire / Transfer </button> </div> </div> <ui-separator class="my-5" /> <!-- Net Total Liquidity Hero Metric Row --> <div class="grid grid-cols-1 gap-6 md:grid-cols-12 md:items-end"> <div class="space-y-1.5 md:col-span-6 lg:col-span-5"> <div class="text-muted-foreground text-xs font-medium">Net Total Liquidity</div> <div class="flex flex-wrap items-baseline gap-3"> <span class="text-foreground text-3xl font-semibold tracking-tight tabular-nums sm:text-4xl"> $4,850,240.00 </span> <span ui-badge variant="outline" class="border-success/20 bg-success/10 text-success gap-1 px-2 py-0.5 text-xs font-semibold" > <lucide-icon [img]="TrendingUp" class="size-3" aria-hidden="true" /> +$74,200.00 (+1.55%) this month </span> </div> <p class="text-muted-foreground text-xs">Aggregated across 4 corporate custody & sweep accounts</p> </div> <!-- Liquidity Allocation Mini Pills --> <div class="grid grid-cols-1 gap-2.5 sm:grid-cols-3 md:col-span-6 lg:col-span-7"> <div class="border-border bg-muted/40 rounded-lg border p-2.5"> <span class="text-muted-foreground block text-xs">Available Cash</span> <span class="text-foreground font-mono text-sm font-semibold tabular-nums">$1,420,500.00</span> <span class="text-muted-foreground mt-0.5 block text-xs">29.3% in Operating</span> </div> <div class="border-border bg-muted/40 rounded-lg border p-2.5"> <div class="flex flex-wrap items-center justify-between gap-x-2"> <span class="text-muted-foreground block text-xs">Yield Sweep</span> <span ui-badge variant="outline" class="border-success/30 text-success px-1 py-0 text-xs font-normal"> 5.14% APY </span> </div> <span class="text-foreground font-mono text-sm font-semibold tabular-nums">$2,850,000.00</span> <span class="text-muted-foreground mt-0.5 block text-xs">58.8% in T-Bills</span> </div> <div class="border-border bg-muted/40 rounded-lg border p-2.5"> <span class="text-muted-foreground block text-xs">Escrow & Reserves</span> <span class="text-foreground font-mono text-sm font-semibold tabular-nums">$579,740.00</span> <span class="text-muted-foreground mt-0.5 block text-xs">11.9% Ringfenced</span> </div> </div> </div> </div> <!-- 4 Treasury Health Metric Cards --> <div class="grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-4"> @for (card of healthCards; track card.title) { <div ui-card class="border-border relative flex flex-col justify-between overflow-hidden shadow-xs"> <div ui-card-header class="flex flex-row items-center justify-between space-y-0 pb-2"> <div ui-card-title class="text-muted-foreground text-xs font-medium tracking-tight">{{ card.title }}</div> <span ui-badge [variant]="card.badgeVariant" [class]="'text-xs font-medium ' + (card.badgeClass ?? '')"> {{ card.badgeText }} </span> </div> <div ui-card-content class="space-y-3"> <div> <div [class]="healthValueClass(card.title)">{{ card.value }}</div> @if (card.highlight) { <p class="text-success mt-0.5 text-xs font-semibold">{{ card.highlight }}</p> } <p class="text-muted-foreground mt-1 text-xs leading-normal">{{ card.description }}</p> </div> <div class="space-y-1.5 border-t pt-2.5"> <ui-progress [value]="card.progressValue" class="h-1.5" /> <div class="text-muted-foreground flex flex-wrap items-center justify-between gap-x-2 text-xs"> <span>{{ card.metricFootnote }}</span> </div> </div> </div> </div> } </div> <!-- 30-Day Cash Flow Projection & Inflow/Outflow Breakdown Section --> <div class="grid grid-cols-1 gap-6 lg:grid-cols-12"> <!-- 30-Day Cash Flow Projection Chart (7 Cols on LG) --> <div ui-card class="border-border flex flex-col justify-between shadow-xs lg:col-span-7"> <div ui-card-header class="border-b pb-4"> <div class="flex flex-col gap-2 sm:flex-row sm:items-center sm:justify-between"> <div class="space-y-0.5"> <div class="flex items-center gap-2"> <div ui-card-title class="text-base font-semibold tracking-tight">30-Day Cash Flow Projection</div> <span ui-badge variant="outline" class="border-success/20 bg-success/10 text-success text-xs"> 95% Confidence Band </span> </div> <div ui-card-description class="text-xs"> Trajectory from <strong class="text-foreground">$4,850,240.00</strong> to projected <strong class="text-foreground">$4,924,440.00</strong> balance. </div> </div> <!-- Timeframe selector --> <div class="flex items-center rounded-lg border p-0.5 text-xs"> @for (tab of timeframeTabs; track tab.value) { <button type="button" [class]="timeframeTabClass(tab.value)" (click)="setActiveTab(tab.value)"> {{ tab.label }} </button> } </div> </div> </div> <div ui-card-content class="space-y-4 pt-5"> <!-- Responsive Projection SVG Curve with Confidence Corridor --> <div class="bg-muted/10 relative w-full overflow-x-auto rounded-lg border p-2"> <svg viewBox="0 0 700 220" class="h-56 w-full max-w-[560px] min-w-full overflow-visible" preserveAspectRatio="none" aria-label="30-day corporate cash projection graph" > <defs> <linearGradient id="cfBandGradientNg" x1="0" y1="0" x2="0" y2="1"> <stop offset="0%" stop-color="#10B981" stop-opacity="0.22" /> <stop offset="100%" stop-color="#10B981" stop-opacity="0.02" /> </linearGradient> <linearGradient id="cfLineGradientNg" x1="0" y1="0" x2="1" y2="0"> <stop offset="0%" stop-color="#10B981" /> <stop offset="45%" stop-color="#06B6D4" /> <stop offset="100%" stop-color="#10B981" /> </linearGradient> </defs> <!-- Horizontal Grid Guidelines --> <g stroke="currentColor" class="text-border/60" stroke-width="1" stroke-dasharray="3 3"> <line x1="40" y1="55" x2="670" y2="55" /> <line x1="40" y1="115" x2="670" y2="115" /> <line x1="40" y1="175" x2="670" y2="175" /> </g> <!-- Guideline Labels --> <g class="fill-muted-foreground font-mono text-xs" font-size="12"> <text x="4" y="58">$5.10M</text> <text x="4" y="118">$4.85M</text> <text x="4" y="178">$4.60M</text> </g> <!-- 95% Confidence Band Polygon Area --> <path d="M 50,120 C 110,110 140,95 180,90 C 240,85 280,105 330,105 C 390,105 430,55 490,55 C 550,55 590,75 650,75 L 650,140 C 590,140 550,125 490,125 C 430,125 390,175 330,175 C 280,175 240,155 180,155 C 140,155 110,140 50,140 Z" fill="url(#cfBandGradientNg)" /> <!-- Upper Confidence Bound Line (Dashed) --> <path d="M 50,120 C 110,110 140,95 180,90 C 240,85 280,105 330,105 C 390,105 430,55 490,55 C 550,55 590,75 650,75" fill="none" stroke="#10B981" stroke-width="1.5" stroke-dasharray="4 4" opacity="0.6" /> <!-- Lower Confidence Bound Line (Dashed) --> <path d="M 50,140 C 110,140 140,155 180,155 C 240,155 280,175 330,175 C 390,175 430,125 490,125 C 550,125 590,140 650,140" fill="none" stroke="#10B981" stroke-width="1.5" stroke-dasharray="4 4" opacity="0.4" /> <!-- Median Expected Trajectory Main Curve --> <path d="M 50,130 C 110,128 140,123 180,123 C 240,123 280,144 330,144 C 390,144 430,89 490,89 C 550,89 590,108 650,108" fill="none" stroke="url(#cfLineGradientNg)" stroke-width="3" stroke-linecap="round" /> <!-- Milestone Markers on Curve --> @for (m of milestones; track m.day; let idx = $index) { <g class="cursor-pointer" (mouseenter)="setActiveMilestone(idx)"> <circle [attr.cx]="m.cx" [attr.cy]="m.cy" r="6" class="fill-background stroke-emerald-500" stroke-width="3" /> @if (activeMilestone() === idx) { <circle [attr.cx]="m.cx" [attr.cy]="m.cy" r="11" class="fill-success/20 stroke-emerald-400" stroke-width="1" /> } </g> } </svg> <!-- Bottom X-Axis Milestone Labels --> <div class="mt-2 grid max-w-[560px] min-w-full grid-cols-5 gap-1 border-t pt-2 text-center"> @for (m of milestones; track m.day; let idx = $index) { <div [class]="milestoneCellClass(idx)" (mouseenter)="setActiveMilestone(idx)"> <span class="text-foreground block text-xs font-semibold">{{ m.day }}</span> <span class="text-muted-foreground block text-xs">{{ m.date }}</span> <span [class]="milestoneAmountClass(m.amount)">{{ m.amount }}</span> </div> } </div> </div> <!-- Chart Key Legend & Milestone Detail Bar --> <div class="flex flex-wrap items-center justify-between gap-3 text-xs"> <div class="flex flex-wrap items-center gap-4"> <div class="flex items-center gap-1.5"> <span class="bg-success size-2.5 rounded-full"></span> <span class="text-foreground font-medium">Expected Trajectory</span> </div> <div class="flex items-center gap-1.5"> <span class="border-success bg-success/20 h-2 w-4 rounded-xs border border-dashed"></span> <span class="text-muted-foreground">95% Confidence Corridor</span> </div> <div class="flex items-center gap-1.5"> <span class="bg-background border-success size-2 rounded-full border-2"></span> <span class="text-muted-foreground">Cash Events</span> </div> </div> <div class="text-muted-foreground font-mono"> Net 30-Day Delta: <strong class="text-success">+$74,200.00</strong> </div> </div> </div> </div> <!-- Inflow & Outflow Breakdown Side Column (5 Cols on LG) --> <div class="space-y-4 lg:col-span-5"> <!-- Inflow Breakdown Card --> <div ui-card class="border-border shadow-xs"> <div ui-card-header class="pb-3"> <div class="flex flex-wrap items-center justify-between gap-x-2"> <div class="flex items-center gap-2"> <div class="bg-success/10 text-success flex size-7 items-center justify-center rounded-md"> <lucide-icon [img]="ArrowDownLeft" class="size-4" aria-hidden="true" /> </div> <div> <div ui-card-title class="text-sm font-semibold tracking-tight">Projected 30D Inflow</div> <div ui-card-description class="text-xs">Total incoming cash: +$245,000.00</div> </div> </div> <span ui-badge variant="outline" class="border-success/20 bg-success/10 text-success text-xs font-semibold" > 100% </span> </div> </div> <div ui-card-content class="space-y-3"> @for (item of inflow; track item.id) { <div class="space-y-1.5"> <div class="flex flex-wrap items-center justify-between gap-x-2 text-xs"> <div class="flex items-center gap-2"> <span class="text-foreground font-medium">{{ item.name }}</span> <span class="text-muted-foreground font-mono text-xs">({{ item.percentage }}%)</span> </div> <span class="text-success font-mono font-semibold tabular-nums">{{ item.amount }}</span> </div> <ui-progress [value]="item.percentage" class="h-2" /> <p class="text-muted-foreground text-xs">{{ item.detail }}</p> </div> } </div> </div> <!-- Outflow Breakdown Card --> <div ui-card class="border-border shadow-xs"> <div ui-card-header class="pb-3"> <div class="flex flex-wrap items-center justify-between gap-x-2"> <div class="flex items-center gap-2"> <div class="bg-destructive/10 text-destructive flex size-7 items-center justify-center rounded-md"> <lucide-icon [img]="ArrowUpRight" class="size-4" aria-hidden="true" /> </div> <div> <div ui-card-title class="text-sm font-semibold tracking-tight">Projected 30D Outflow</div> <div ui-card-description class="text-xs">Total operational burn: -$170,800.00</div> </div> </div> <span ui-badge variant="outline" class="border-destructive/20 bg-destructive/10 text-destructive text-xs font-semibold" > 100% </span> </div> </div> <div ui-card-content class="space-y-3"> @for (item of outflow; track item.id) { <div class="space-y-1.5"> <div class="flex flex-wrap items-center justify-between gap-x-2 text-xs"> <div class="flex items-center gap-2"> <span class="text-foreground font-medium">{{ item.name }}</span> <span class="text-muted-foreground font-mono text-xs">({{ item.percentage }}%)</span> </div> <span class="text-destructive font-mono font-semibold tabular-nums">{{ item.amount }}</span> </div> <ui-progress [value]="item.percentage" class="h-2" /> <p class="text-muted-foreground text-xs">{{ item.detail }}</p> </div> } </div> </div> </div> </div> <!-- Connected Corporate Bank Accounts Table --> <div class="bg-card border-border overflow-hidden rounded-xl border shadow-xs"> <div class="flex flex-col gap-3 border-b p-5 sm:flex-row sm:items-center sm:justify-between"> <div class="space-y-1"> <div class="flex items-center gap-2.5"> <lucide-icon [img]="Landmark" class="text-muted-foreground size-5" aria-hidden="true" /> <h3 class="text-foreground text-base font-semibold tracking-tight sm:text-lg"> Connected Corporate Bank & Treasury Accounts </h3> </div> <p class="text-muted-foreground text-xs"> 4 institutional accounts connected via real-time Open Banking / Plaid Exchange </p> </div> <div class="flex flex-wrap items-center gap-2.5"> <button ui-button size="sm" variant="outline" [class]="sweepButtonClass" (click)="handleManageSweep()"> <lucide-icon [img]="Zap" class="size-3.5" aria-hidden="true" /> <span>Auto-Sweep Rule: {{ isAutoSweepActive() ? 'Active (5.14% APY)' : 'Paused' }}</span> </button> <button ui-button size="sm" variant="outline" class="gap-1.5 text-xs" (click)="refreshBalances()"> <lucide-icon [img]="RefreshCw" class="size-3.5" aria-hidden="true" /> Refresh Balances </button> </div> </div> <div class="overflow-x-auto"> <table ui-table> <thead ui-table-header> <tr ui-table-row class="bg-muted/40 hover:bg-muted/40"> <th ui-table-head class="min-w-[240px]">Institution & Account</th> <th ui-table-head class="min-w-[220px]">Purpose & Routing</th> <th ui-table-head class="text-center">APY / Yield</th> <th ui-table-head class="text-right">Available Balance</th> <th ui-table-head class="text-center">Status</th> <th ui-table-head class="text-right">Actions</th> </tr> </thead> <tbody ui-table-body> @for (account of accounts; track account.id) { <tr ui-table-row class="hover:bg-muted/30"> <!-- Account & Institution --> <td ui-table-cell> <div class="flex items-center gap-3"> <div class="border-border/80 bg-background flex size-10 shrink-0 items-center justify-center rounded-lg border shadow-xs" > @switch (account.id) { @case ('acc-operating') { <svg viewBox="0 0 24 24" class="size-5 shrink-0 text-[#117ACA]" fill="currentColor"> <path d="M12 2.5L7.5 7h4.8L16.2 3.1 12 2.5zm9.5 9.5L17 7.5v4.8l3.9 3.9.6-4.2zm-9.5 9.5L16.5 17h-4.8L7.8 20.9l4.2.6zm-9.5-9.5L7 16.5v-4.8L3.1 7.8 2.5 12z" /> </svg> } @case ('acc-sweep') { <svg viewBox="0 0 24 24" class="size-5 shrink-0" fill="none"> <rect width="24" height="24" rx="4" fill="#002D62" /> <path d="M4 17V7l5 5-5 5zm8 0V7l5 5-5 5z" fill="#00A3E0" /> <circle cx="18" cy="12" r="2" fill="#FFFFFF" /> </svg> } @case ('acc-payroll') { <svg viewBox="0 0 24 24" class="size-5 shrink-0" fill="none"> <rect width="24" height="24" rx="4" fill="#005587" /> <path d="M6 15l6-9 6 9-3 1.5-3-4.5-3 4.5L6 15z" fill="#FFFFFF" /> <path d="M9 16.5l3-4.5 3 4.5-3 1.5-3-1.5z" fill="#00A3E0" /> </svg> } @case ('acc-tax') { <svg viewBox="0 0 24 24" class="size-5 shrink-0" fill="none"> <path d="M12 4.5C8 4.5 4.5 6.8 4 9.5h2.5c.5-1.5 3-2.5 5.5-2.5s5 1 5.5 2.5H20c-.5-2.7-4-5-8-5z" fill="#EC111A" /> <rect x="4" y="11" width="3" height="8" rx="0.5" fill="#003B70" /> <circle cx="12" cy="15" r="4" fill="#003B70" /> <rect x="17" y="11" width="3" height="8" rx="0.5" fill="#003B70" /> </svg> } @default { <lucide-icon [img]="Building2" class="text-muted-foreground size-5" /> } } </div> <div class="min-w-0 space-y-0.5"> <div class="flex items-center gap-2"> <span class="text-foreground truncate text-sm font-semibold">{{ account.name }}</span> </div> <div class="flex items-center gap-2"> <span class="text-muted-foreground truncate text-xs">{{ account.institution }}</span> <span class="text-muted-foreground font-mono text-xs">{{ account.accountMask }}</span> </div> </div> </div> </td> <!-- Purpose --> <td ui-table-cell> <div class="space-y-0.5"> <span class="text-foreground block text-xs font-medium">{{ account.accountType }}</span> <span class="text-muted-foreground block text-xs leading-tight">{{ account.purpose }}</span> </div> </td> <!-- APY Rate --> <td ui-table-cell class="text-center"> @if (account.isSweepAccount) { <span ui-badge variant="outline" class="border-success/30 bg-success/10 text-success font-mono text-xs font-semibold" > {{ account.apy }} </span> } @else { <span class="text-muted-foreground font-mono text-xs">{{ account.apy }}</span> } </td> <!-- Balance --> <td ui-table-cell class="text-right"> <span class="text-foreground font-mono text-sm font-semibold tabular-nums">{{ account.balance }}</span> </td> <!-- Status --> <td ui-table-cell class="text-center"> @if (account.statusType === 'success') { <span ui-badge variant="outline" class="border-success/20 bg-success/10 text-success gap-1 text-xs font-medium" > <span class="bg-success size-1.5 rounded-full" aria-hidden="true"></span> {{ account.status }} </span> } @else if (account.statusType === 'info') { <span ui-badge variant="outline" class="border-info/20 bg-info/10 text-info gap-1 text-xs font-medium" > <span class="bg-info size-1.5 rounded-full" aria-hidden="true"></span> {{ account.status }} </span> } @else { <span ui-badge variant="outline" class="border-warning/20 bg-warning/10 text-warning gap-1 text-xs font-medium" > <lucide-icon [img]="Lock" class="size-3" aria-hidden="true" /> {{ account.status }} </span> } </td> <!-- Actions --> <td ui-table-cell class="text-right"> <div class="flex items-center justify-end gap-1.5"> @if (account.isSweepAccount) { <button ui-button size="sm" variant="outline" class="h-8 gap-1 text-xs" (click)="handleManageSweep()" > <lucide-icon [img]="Zap" class="text-success size-3" aria-hidden="true" /> Configure Sweep </button> } @else { <button ui-button size="sm" variant="outline" class="h-8 text-xs" (click)="handleTransfer(account.name)" > Transfer </button> } </div> </td> </tr> } </tbody> </table> </div> <!-- Table Summary Footer --> <div class="bg-muted/20 flex flex-col items-center justify-between gap-3 border-t p-4 sm:flex-row"> <div class="flex items-center gap-2"> <lucide-icon [img]="Shield" class="text-muted-foreground size-4" aria-hidden="true" /> <p class="text-muted-foreground text-xs"> Custody verified through Member FINRA / SIPC & FDIC depository institutions. </p> </div> <div class="flex items-center gap-3"> <span class="text-muted-foreground font-mono text-xs"> Net Liquidity: <strong class="text-foreground">$4,850,240.00</strong> </span> <button ui-button size="sm" class="h-8 text-xs font-medium" (click)="handleTransfer()"> <lucide-icon [img]="Send" class="mr-1.5 size-3.5" aria-hidden="true" /> Transfer Between Accounts </button> </div> </div> </div> </div> `, }) export class UiCashFlowTreasuryDashboardComponent { protected readonly ArrowDownLeft = ArrowDownLeft protected readonly ArrowUpRight = ArrowUpRight protected readonly Building2 = Building2 protected readonly CheckCircle2 = CheckCircle2 protected readonly Landmark = Landmark protected readonly Lock = Lock protected readonly RefreshCw = RefreshCw protected readonly Send = Send protected readonly Shield = Shield protected readonly TrendingUp = TrendingUp protected readonly Zap = Zap protected readonly healthCards = healthCards protected readonly inflow = inflowBreakdown protected readonly outflow = outflowBreakdown protected readonly accounts = bankAccounts protected readonly milestones = trajectoryMilestones protected readonly timeframeTabs: { value: TreasuryTab; label: string }[] = [ { value: '30d', label: '30D Projection' }, { value: '60d', label: '60D' }, { value: '90d', label: '90D' }, ] @Input('class') className?: string @Output() readonly deposit = new EventEmitter<void>() @Output() readonly transfer = new EventEmitter<string | undefined>() @Output() readonly manageSweep = new EventEmitter<void>() readonly activeTab = signal<TreasuryTab>('30d') readonly activeMilestone = signal<number | null>(null) readonly actionFeedback = signal<string | null>(null) readonly isAutoSweepActive = signal(true) private feedbackTimer: ReturnType<typeof setTimeout> | undefined constructor() { inject(DestroyRef).onDestroy(() => clearTimeout(this.feedbackTimer)) } get rootClass(): string { return cn('w-full space-y-6', this.className) } get sweepButtonClass(): string { return cn( 'gap-1.5 text-xs', this.isAutoSweepActive() ? 'border-success/30 bg-success/10 text-success' : 'text-muted-foreground', ) } timeframeTabClass(tab: TreasuryTab): string { return cn( 'rounded-md px-2.5 py-1 font-medium transition-colors', this.activeTab() === tab ? 'bg-primary text-primary-foreground shadow-xs' : 'text-muted-foreground hover:text-foreground', ) } healthValueClass(title: string): string { return cn( 'text-foreground text-2xl font-semibold tracking-tight tabular-nums', title === 'Yield on Treasury Funds' && 'text-success', ) } milestoneCellClass(idx: number): string { return cn( 'rounded-md p-1 transition-colors', this.activeMilestone() === idx ? 'bg-muted ring-border/80 ring-1' : 'hover:bg-muted/40', ) } milestoneAmountClass(amount: string): string { return cn( 'font-mono text-xs font-medium tabular-nums', amount.startsWith('+') ? 'text-success' : amount.startsWith('-') ? 'text-destructive' : 'text-foreground', ) } setActiveTab(tab: TreasuryTab): void { this.activeTab.set(tab) } setActiveMilestone(idx: number | null): void { this.activeMilestone.set(idx) } dismissFeedback(): void { this.actionFeedback.set(null) } private triggerNotification(msg: string): void { this.actionFeedback.set(msg) clearTimeout(this.feedbackTimer) this.feedbackTimer = setTimeout(() => { this.actionFeedback.update((prev) => (prev === msg ? null : prev)) }, 4000) } handleDeposit(): void { this.deposit.emit() this.triggerNotification('Deposit modal opened. Direct ACH & Wire instructions ready for UIPKGE Technologies Inc.') } handleTransfer(accountName?: string): void { this.transfer.emit(accountName) this.triggerNotification( accountName ? `Transfer interface initiated from ${accountName}. Real-time settlement active.` : 'Initiating wire & multi-account transfer portal.', ) } handleManageSweep(): void { const next = !this.isAutoSweepActive() this.isAutoSweepActive.set(next) this.manageSweep.emit() this.triggerNotification( next ? 'Auto-Sweep rule ACTIVE: Daily excess over $1.0M swept into 5.14% APY Treasury Bills.' : 'Auto-Sweep rule PAUSED. Funds will remain in standard operating checking.', ) } refreshBalances(): void { this.triggerNotification('Bank balances refreshed from Federal Reserve Fedwire & NACHA feeds.') } }src/app/components/blocks/cash-flow-treasury-dashboard/index.ts0.1 kB
export { UiCashFlowTreasuryDashboardComponent, type TreasuryTab } from './cash-flow-treasury-dashboard.component'
Raw manifest:https://uipkge.dev/r/angular/cash-flow-treasury-dashboard.json