import { ActionIcon, Badge, Box, Card, Group, SegmentedControl, Stack, Text, TextInput, Tooltip, } from "@mantine/core"; import { useDebouncedValue } from "@mantine/hooks"; import { useQuery } from "@tanstack/react-query"; import { Building2, CheckCircle2, Clock, Hourglass, Mail, Phone, RefreshCw, Search, ShieldOff, Users, X, } from "lucide-react"; import { useMemo, useState } from "react"; import { useNavigate } from "react-router-dom"; import { CompanyStatusBadge, CompanyTypeBadge, ProfileChips, formatDate, } from "@/components/customers"; import { KpiStrip, PageContainer, PageHeader } from "@/components/page"; import { api } from "@/services/api"; import type { Company, CompanyStatus } from "@/types/customer"; import { isOnboardingDraft } from "@/types/customer"; import { DataTable, DataTableFooter, usePagination, type ColumnDef, } from "@edr/ui-common"; /** * The list's segmented views. "Pending approval" means submitted-and-awaiting- * review, so it excludes drafts — a company row exists from the onboarding * wizard's first click and would otherwise pad the review queue. Those drafts * get their own view instead of disappearing, so staff can still chase them. */ type CustomerView = "all" | "pending" | "onboarding" | "active"; const VIEW_FILTERS: Record< CustomerView, { status?: CompanyStatus; onboardingCompleted?: boolean } > = { all: {}, pending: { status: "pending", onboardingCompleted: true }, onboarding: { onboardingCompleted: false }, active: { status: "active" }, }; export default function CustomersPage() { const navigate = useNavigate(); const { pagination, setPagination } = usePagination({ pageSize: 10 }); const [query, setQuery] = useState(""); const [debouncedQuery] = useDebouncedValue(query, 300); const [view, setView] = useState("all"); const filter = useMemo( () => ({ page: pagination.pageIndex + 1, pageSize: pagination.pageSize, search: debouncedQuery, ...VIEW_FILTERS[view], }), [pagination.pageIndex, pagination.pageSize, debouncedQuery, view], ); const { data: stats } = useQuery(api.customers.stats.queryOptions({ input: {} })); const { data, isLoading, isError, refetch, isFetching } = useQuery( api.customers.list.queryOptions({ input: { filter } }), ); const rows = data?.items ?? []; const total = data?.total ?? 0; const pageCount = Math.max(1, Math.ceil(total / pagination.pageSize)); const columns: ColumnDef[] = useMemo( () => [ { id: "company", header: "Company", cell: ({ row }) => { const c = row.original; return (
{c.name} TIN {c.tin} {c.country ? ` · ${c.country}` : ""}
); }, }, { id: "profiles", header: "Profiles", cell: ({ row }) => , }, { id: "status", header: "Status", cell: ({ row }) => { // A draft's profiles are all `pending` by construction, so the // "N pending" review hint would be a lie until they submit. if (isOnboardingDraft(row.original)) { return ( Onboarding ); } const pending = (row.original.companyProfiles ?? []).filter( (p) => p.status === "pending", ).length; return ( {pending > 0 ? ( 1 ? "s" : ""} awaiting approval`} > {pending} pending ) : null} ); }, }, { id: "contact", header: "Contact", cell: ({ row }) => { const c = row.original; return ( {c.contactPersonName ? ( {c.contactPersonName} ) : null} {c.phone ? ( {c.phone} ) : null} {c.email ? ( {c.email} ) : null} ); }, }, { id: "created", header: "Registered", meta: { headerClassName: "text-right", cellClassName: "text-right" }, cell: ({ row }) => ( {formatDate(row.original.createdAt)} ), }, ], [], ); return ( void refetch()} > } /> } value={query} onChange={(e) => setQuery(e.target.value)} rightSection={ query ? ( setQuery("")} > ) : null } style={{ flex: 1, minWidth: "240px" }} radius="lg" /> { setView(v as CustomerView); setPagination((prev) => ({ ...prev, pageIndex: 0 })); }} data={[ { label: "All", value: "all" }, { label: "Pending approval", value: "pending" }, { label: "Onboarding", value: "onboarding" }, { label: "Active", value: "active" }, ]} /> {total} record{total !== 1 ? "s" : ""} navigate(`/dashboard/customers/${row.id}`)} emptyMessage={ debouncedQuery ? "No companies match your search." : "No companies yet." } error={ isError ? { message: "Failed to load customers.", onRetry: () => void refetch(), } : undefined } pagination={{ pageIndex: pagination.pageIndex, pageSize: pagination.pageSize, pageCount, totalCount: total, }} tableOptions={{ state: { pagination }, onPaginationChange: setPagination, manualPagination: true, pageCount, }} containerClassName="border-0 shadow-none bg-transparent" footer={DataTableFooter} /> ); }