Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 | 10x 10x 10x 10x 10x 3x 16x | import { Link } from "@tanstack/react-router";
import { Store } from "lucide-react";
import { AdminMetric, AdminMetrics } from "../../components/admin/AdminMetric";
import { AdminBand, AdminPageHead } from "../../components/admin/AdminPageHead";
import { Card } from "../../components/ui/card";
import { EmptyState } from "../../components/ui/empty-state";
import { Skeleton } from "../../components/ui/skeleton";
import { StatusBadge } from "../../components/ui/status-badge";
import { useAdminDashboard } from "../../hooks/queries/useAdminQueries";
/**
* `/admin` — the operator's first screen, in the Job Book idiom.
*
* WHAT CHANGED
* Four stat *cards* became four `.metric` figures: a label, a 2px rule and a
* tabular number. Boxes are the one thing the Job Book forbids, and a figure
* buried in the third element of a shadowed card is not a figure leading.
*
* The pending-publish tile used to turn warning-coloured — border, ring, icon
* and number — when it was non-zero. That is colour travelling alone, and the
* number it coloured is not a state. It now prints a `Waiting` code beside its
* label, which says the same thing in a word.
*
* Recent pros became ruled rows that tint on hover, with the pro's status as a
* printed code on the right, rather than a card with a 1.125rem title.
*/
export function AdminDashboardPage() {
const { data, isLoading } = useAdminDashboard();
const stats = data?.stats ?? null;
const recentPros = data?.recentPros ?? [];
const pendingPros = stats?.pendingPros ?? 0;
return (
<div className="space-y-8">
<AdminPageHead
title="Admin Dashboard"
description="Overview of all pros and projects."
/>
{/* Figures lead. `.metrics` — a label, a rule and a number each. */}
<AdminMetrics>
<AdminMetric
label="Total Pros"
value={stats?.totalPros ?? "-"}
note={`${stats?.publishedPros ?? 0} published`}
href="/admin/pros"
isLoading={isLoading}
/>
<AdminMetric
label="Pros pending publish"
value={pendingPros}
note={
pendingPros === 0
? "No pros waiting for approval"
: "Set status to Published to approve"
}
// State is a printed code, never a tinted number.
code={
pendingPros > 0 ? (
<StatusBadge status="Waiting" variant="warning" />
) : undefined
}
href="/admin/pros?status=draft"
isLoading={isLoading}
/>
<AdminMetric
label="Total Projects"
value={stats?.totalProjects ?? "-"}
note={`${stats?.publishedProjects ?? 0} published`}
href="/admin/projects"
isLoading={isLoading}
/>
<AdminMetric
label="Conversion Rate"
value="-"
note="Coming soon"
isLoading={isLoading}
/>
</AdminMetrics>
<section className="space-y-2">
<AdminBand
title="Recent Pros"
count={isLoading ? undefined : recentPros.length}
quiet
/>
{isLoading ? (
<Card className="divide-y divide-border-default overflow-hidden">
{[1, 2, 3].map((i) => (
<div key={i} className="space-y-2 px-3.5 py-2.5">
<Skeleton className="h-4 w-32" />
<Skeleton className="h-3 w-24" />
</div>
))}
</Card>
) : recentPros.length === 0 ? (
<EmptyState icon={Store} title="No pros yet" className="py-8" />
) : (
<Card className="overflow-hidden">
<ul>
{recentPros.map((pro) => (
<li
key={pro.id}
className="[&+li]:border-t [&+li]:border-border-default"
>
{/* The whole row is the target: one tab stop, one hit
area. Rows tint on hover; they never lift. */}
<Link
to={`/admin/pros/${pro.id}`}
className="flex items-center gap-3 px-3.5 py-2.5 transition-colors hover:bg-foreground-default/7 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary-500"
>
<span className="min-w-0 grow">
<span className="block truncate text-sm font-medium text-foreground-default">
{pro.businessName}
</span>
<span className="block truncate text-xs text-foreground-muted">
{pro.primaryLocality
? `${pro.primaryLocality.name}, ${pro.primaryLocality.city}`
: "No location set"}
</span>
</span>
<StatusBadge status={pro.status} />
</Link>
</li>
))}
</ul>
<div className="border-t border-border-strong px-3.5 py-2">
<Link
to="/admin/pros"
className="text-xs font-semibold uppercase tracking-wider text-primary-600 hover:text-primary-700"
>
View all pros →
</Link>
</div>
</Card>
)}
</section>
</div>
);
}
|