feat(03-03): add Dashboard page with filterable documents table
- Async server component queries all documents with LEFT JOIN to clients - Filter state lives in URL (?status=Draft|Sent|Viewed|Signed) via DashboardFilters client component - Rows filtered in JavaScript after fetch (tiny dataset in Phase 3) - DashboardFilters extracted to _components/DashboardFilters.tsx (use client + useRouter) - Displays agent first name extracted from session email
This commit is contained in:
@@ -0,0 +1,55 @@
|
||||
import { auth } from "@/lib/auth";
|
||||
import { db } from "@/lib/db";
|
||||
import { documents, clients } from "@/lib/db/schema";
|
||||
import { eq, desc } from "drizzle-orm";
|
||||
import { DocumentsTable } from "../../_components/DocumentsTable";
|
||||
import { DashboardFilters } from "../../_components/DashboardFilters";
|
||||
|
||||
const VALID_STATUSES = ["Draft", "Sent", "Viewed", "Signed"] as const;
|
||||
type ValidStatus = (typeof VALID_STATUSES)[number];
|
||||
|
||||
export default async function DashboardPage({
|
||||
searchParams,
|
||||
}: {
|
||||
searchParams: Promise<{ status?: string }>;
|
||||
}) {
|
||||
const session = await auth();
|
||||
const { status } = await searchParams;
|
||||
|
||||
const allRows = await db
|
||||
.select({
|
||||
id: documents.id,
|
||||
name: documents.name,
|
||||
status: documents.status,
|
||||
sentAt: documents.sentAt,
|
||||
clientName: clients.name,
|
||||
clientId: documents.clientId,
|
||||
})
|
||||
.from(documents)
|
||||
.leftJoin(clients, eq(documents.clientId, clients.id))
|
||||
.orderBy(desc(documents.createdAt));
|
||||
|
||||
const filteredRows =
|
||||
status && (VALID_STATUSES as readonly string[]).includes(status)
|
||||
? allRows.filter((r) => r.status === (status as ValidStatus))
|
||||
: allRows;
|
||||
|
||||
const firstName = session?.user?.email?.split("@")[0] ?? "Agent";
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className="flex items-center justify-between mb-6">
|
||||
<h1 className="text-[var(--navy)] text-2xl font-semibold">
|
||||
Welcome back, {firstName}
|
||||
</h1>
|
||||
</div>
|
||||
<div className="flex items-center gap-4 mb-6">
|
||||
<span className="text-sm text-gray-600">Filter by status:</span>
|
||||
<DashboardFilters currentStatus={status} />
|
||||
</div>
|
||||
<div className="bg-white rounded-xl shadow-sm overflow-hidden">
|
||||
<DocumentsTable rows={filteredRows} showClientColumn={true} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
"use client";
|
||||
|
||||
import { useRouter } from "next/navigation";
|
||||
|
||||
export function DashboardFilters({ currentStatus }: { currentStatus?: string }) {
|
||||
const router = useRouter();
|
||||
return (
|
||||
<select
|
||||
value={currentStatus ?? ""}
|
||||
onChange={(e) =>
|
||||
router.push(
|
||||
e.target.value
|
||||
? `?status=${e.target.value}`
|
||||
: "/portal/dashboard"
|
||||
)
|
||||
}
|
||||
className="border border-gray-200 rounded-lg px-3 py-1.5 text-sm"
|
||||
>
|
||||
<option value="">All statuses</option>
|
||||
<option value="Draft">Draft</option>
|
||||
<option value="Sent">Sent</option>
|
||||
<option value="Viewed">Viewed</option>
|
||||
<option value="Signed">Signed</option>
|
||||
</select>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user