Hoists hooks above early return (render-time Navigate); ports finance's ConfirmDialog/PageHeader/loading/states + APC error-copy mapper; removes window.confirm, dead appbar search, and seeded fake notifications; wires toasts. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
99 lines
3.5 KiB
TypeScript
99 lines
3.5 KiB
TypeScript
import { Skeleton } from "~/components/ui/skeleton"
|
|
import { Card, CardContent } from "~/components/ui/card"
|
|
|
|
/** Layout-preserving placeholder for a list/table surface. Renders a
|
|
* header bar + `rows` row skeletons. Looks far less alarming than a
|
|
* single spinner during slow loads and keeps the layout from jumping
|
|
* when data lands. */
|
|
export function ListSkeleton({ rows = 8 }: { rows?: number }) {
|
|
return (
|
|
<div className="space-y-2">
|
|
<div className="flex items-center justify-between">
|
|
<Skeleton className="h-9 w-72" />
|
|
<Skeleton className="h-8 w-24" />
|
|
</div>
|
|
<div className="rounded-md border">
|
|
<div className="flex items-center gap-4 border-b bg-muted/30 px-4 py-2">
|
|
<Skeleton className="h-3 w-16" />
|
|
<Skeleton className="h-3 w-24" />
|
|
<Skeleton className="h-3 w-20" />
|
|
<Skeleton className="ml-auto h-3 w-16" />
|
|
</div>
|
|
{Array.from({ length: rows }).map((_, i) => (
|
|
<div
|
|
key={i}
|
|
className="flex items-center gap-4 border-b px-4 py-3 last:border-b-0"
|
|
>
|
|
<Skeleton className="h-4 w-20" />
|
|
<Skeleton className="h-4 w-32" />
|
|
<Skeleton className="h-4 w-16" />
|
|
<Skeleton className="ml-auto h-4 w-24" />
|
|
</div>
|
|
))}
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
/** Layout-preserving placeholder for a dashboard/overview surface. Mirrors
|
|
* a hero KPI + secondary KPIs, a trend + categories row, a forecast strip
|
|
* and a list card so there's no visible reshuffle when data lands. Shape
|
|
* it to match whatever your overview renders. */
|
|
export function DashboardSkeleton() {
|
|
return (
|
|
<div className="space-y-6">
|
|
{/* Hero KPI row */}
|
|
<section className="grid gap-6 lg:grid-cols-3 lg:items-end">
|
|
<div className="lg:col-span-2 space-y-3">
|
|
<Skeleton className="h-3 w-32" />
|
|
<Skeleton className="h-14 w-72" />
|
|
<Skeleton className="h-8 w-full max-w-md" />
|
|
</div>
|
|
<div className="space-y-4">
|
|
<Skeleton className="h-7 w-full" />
|
|
<Skeleton className="h-7 w-full" />
|
|
<Skeleton className="h-3 w-32" />
|
|
</div>
|
|
</section>
|
|
|
|
{/* Trend + categories */}
|
|
<div className="grid gap-6 lg:grid-cols-3">
|
|
<Card className="lg:col-span-2">
|
|
<CardContent className="space-y-3 p-5 md:p-7">
|
|
<Skeleton className="h-4 w-40" />
|
|
<Skeleton className="h-3 w-64" />
|
|
<Skeleton className="h-56 w-full" />
|
|
</CardContent>
|
|
</Card>
|
|
<Card>
|
|
<CardContent className="space-y-4 p-5 md:p-7">
|
|
<Skeleton className="h-4 w-40" />
|
|
<Skeleton className="h-8 w-40" />
|
|
<Skeleton className="h-2.5 w-full rounded-full" />
|
|
<div className="space-y-2">
|
|
{Array.from({ length: 4 }).map((_, i) => (
|
|
<Skeleton key={i} className="h-4 w-full" />
|
|
))}
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
</div>
|
|
|
|
{/* Forecast */}
|
|
<Card>
|
|
<CardContent className="space-y-4 p-5 md:p-7">
|
|
<Skeleton className="h-4 w-32" />
|
|
<div className="grid gap-4 sm:grid-cols-3">
|
|
{Array.from({ length: 3 }).map((_, i) => (
|
|
<div key={i} className="space-y-2">
|
|
<Skeleton className="h-3 w-24" />
|
|
<Skeleton className="h-7 w-32" />
|
|
</div>
|
|
))}
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
</div>
|
|
)
|
|
}
|