diff --git a/app/api/register/search/route.ts b/app/api/register/search/route.ts index a0fe9d4..5a8b065 100644 --- a/app/api/register/search/route.ts +++ b/app/api/register/search/route.ts @@ -18,12 +18,15 @@ export async function GET(req: NextRequest) { try { const { searchParams } = new URL(req.url); const q = searchParams.get("q") || ""; - const fields = searchParams.get("fields") || "id,reference_name,status,proposed_name_1"; + const fields = searchParams.get("fields") || "id,reference_name,status,proposed_name_1,trading_address,created"; + const perPage = Math.min(Number(searchParams.get("perPage")) || 500, 500); + const sort = searchParams.get("sort") || "-created"; const data = await fetchCollection("ysterbaard_company_registrations", { page: 1, - perPage: 20, + perPage, fields, + sort, }); let items = data.items; @@ -38,7 +41,7 @@ export async function GET(req: NextRequest) { ); } - // Sort newest first + // Default sort: newest first (nanoid is time-sortable) items.sort((a, b) => b.id.localeCompare(a.id)); // Return lightweight response diff --git a/app/registrations/page.tsx b/app/registrations/page.tsx index 9392a5e..09ac836 100644 --- a/app/registrations/page.tsx +++ b/app/registrations/page.tsx @@ -1,38 +1,84 @@ // app/registrations/page.tsx -// Server component. Lists submitted company registrations. +// Client component. Lists submitted company registrations. +// Uses /api/register/search for data (client-safe). -import { fetchCollection } from "@/lib/pb"; -import type { PBList, PBRecord } from "@/lib/pb"; +"use client"; + +import { useState, useEffect } from "react"; import PageHeader from "@/components/PageHeader"; import Link from "next/link"; -export const dynamic = "force-dynamic"; - -interface RegItem extends PBRecord { +interface RegItem { id: string; reference_name: string; status: string; proposed_name_1: string; trading_address: string; + created: string; } -export default async function RegistrationsPage() { - const data = await fetchCollection("ysterbaard_company_registrations", { - page: 1, - perPage: 200, - fields: "id,reference_name,status,proposed_name_1,trading_address", +function formatDate(iso: string | null): string { + if (!iso) return "—"; + return new Date(iso).toLocaleDateString("en-ZA", { + day: "2-digit", month: "short", year: "numeric", }); +} - const items = data.items.sort((a, b) => { - // Use registration ID as proxy for recency (nanoid is time-sortable) - return b.id.localeCompare(a.id); - }); +const statusLabel: Record = { + pending: "Pending", + submitted: "Submitted", + approved: "Approved", + rejected: "Rejected", + draft: "Draft", +}; - const statusLabel: Record = { - pending: "Pending", - submitted: "Submitted", - approved: "Approved", - rejected: "Rejected", +const statusStyle: Record = { + pending: "bg-surface border border-border text-muted", + draft: "bg-surface border border-border text-muted", + submitted: "bg-accent/10 text-accent border border-accent/20", + approved: "bg-success/10 text-success border border-success/20", + rejected: "bg-danger/10 text-danger border border-danger/20", +}; + +export default function RegistrationsPage() { + const [items, setItems] = useState([]); + const [loading, setLoading] = useState(true); + const [copiedId, setCopiedId] = useState(null); + + useEffect(() => { + fetch("/api/register/search?q=&fields=id,reference_name,status,proposed_name_1,trading_address,created&sort=-created") + .then((r) => r.json()) + .then((d) => { + setItems(d.items || []); + setLoading(false); + }) + .catch(() => setLoading(false)); + }, []); + + const handleCopyLink = (id: string) => { + const url = `${window.location.origin}/register/${id}`; + const el = document.createElement("textarea"); + el.value = url; + el.style.position = "fixed"; + el.style.opacity = "0"; + document.body.appendChild(el); + el.focus(); + el.select(); + document.execCommand("copy"); + document.body.removeChild(el); + setCopiedId(id); + setTimeout(() => setCopiedId(null), 2000); + }; + + const canShare = typeof navigator !== "undefined" && !!navigator.share; + + const handleShare = (item: RegItem) => { + const url = `${window.location.origin}/register/${item.id}`; + navigator.share({ + title: "Company Registration", + text: `Registration: ${item.reference_name || item.id}`, + url, + }).catch(() => {}); }; return ( @@ -43,7 +89,9 @@ export default async function RegistrationsPage() { subtitle="Client submissions from the registration wizard." />
- {items.length === 0 ? ( + {loading ? ( +
Loading...
+ ) : items.length === 0 ? (

No registrations yet.

@@ -56,20 +104,15 @@ export default async function RegistrationsPage() { Reference - Company name - Address + Company + Date Status + Actions {items.map((r) => { - const status = r.status || "pending"; - const statusStyle: Record = { - pending: "bg-surface border border-border text-muted", - submitted: "bg-accent/10 text-accent border border-accent/20", - approved: "bg-success/10 text-success border border-success/20", - rejected: "bg-danger/10 text-danger border border-danger/20", - }; + const status = r.status || "draft"; return ( @@ -80,13 +123,43 @@ export default async function RegistrationsPage() { {r.reference_name || r.id} - {r.proposed_name_1 || "—"} - {r.trading_address || "—"} + + {r.proposed_name_1 || "—"} + + + {formatDate(r.created)} + - + {statusLabel[status] || status} + +

+ + {canShare && ( + + )} + + View → + +
+ ); })}