Bundles the prior autonomous-session output that was sitting unstaged: - Em-dash sweep across src/ + tests/ (en-dash/em-dash to hyphen, ~2280 instances) - country-flag-icons rollout (CountryFlag component, replaces emoji glyphs that never rendered on Windows; lazy-loads the 3x2 SVG index as a single chunk after the per-subpath dynamic-import approach silently failed in webpack) - Admin IA Phase 1+2: 7-domain regroup, 41 to 38 pages, /admin/berths index, redirects (ocr to ai, reports to dashboard, invitations to users), docs/admin-ia-proposal.md - Per-template email tester (registry + endpoint + UI on Email admin page) - Cancel-document mode picker (delete-from-Documenso vs keep-for-audit) - Dashboard PDF report: 25 widgets, SVG charts, date-range picker, 11 resolvers - Customize-widgets per-region sortables at xl+ (charts/rails/feed); single flat sortable below xl when the layout stacks; per-viewport saved orders - Audit doc updates capturing each shipped item - Lint fixes: react-compiler immutability in DonutChart (reduce instead of let-reassign), set-state-in-effect disables in CountryFlag and UploadForSigning preview-bytes effect, unused 'confirm' destructures in interest contract + reservation tabs, unescaped apostrophe in test-template card copy
164 lines
4.6 KiB
TypeScript
164 lines
4.6 KiB
TypeScript
'use client';
|
|
|
|
import Link from 'next/link';
|
|
import { type ColumnDef } from '@tanstack/react-table';
|
|
import { MoreHorizontal, Archive, Pencil } from 'lucide-react';
|
|
|
|
import { Button } from '@/components/ui/button';
|
|
import {
|
|
DropdownMenu,
|
|
DropdownMenuContent,
|
|
DropdownMenuItem,
|
|
DropdownMenuTrigger,
|
|
} from '@/components/ui/dropdown-menu';
|
|
import { Badge } from '@/components/ui/badge';
|
|
import { RESIDENTIAL_STAGE_LABELS } from './residential-interest-filters';
|
|
|
|
export interface ResidentialInterestRow {
|
|
id: string;
|
|
residentialClientId: string;
|
|
pipelineStage: string;
|
|
source: string | null;
|
|
notes: string | null;
|
|
preferences: string | null;
|
|
assignedTo: string | null;
|
|
archivedAt: string | null;
|
|
updatedAt: string;
|
|
/** Optional client snapshot - server may join the residential client row
|
|
* so the table can show the client name in column 1 without a second
|
|
* fetch per row. */
|
|
clientName?: string | null;
|
|
}
|
|
|
|
export const RESIDENTIAL_INTEREST_COLUMN_OPTIONS: Array<{ id: string; label: string }> = [
|
|
{ id: 'clientName', label: 'Client' },
|
|
{ id: 'pipelineStage', label: 'Stage' },
|
|
{ id: 'source', label: 'Source' },
|
|
{ id: 'preferences', label: 'Preferences' },
|
|
{ id: 'notes', label: 'Notes' },
|
|
{ id: 'updatedAt', label: 'Updated' },
|
|
];
|
|
|
|
export const RESIDENTIAL_INTEREST_DEFAULT_HIDDEN: string[] = [];
|
|
|
|
interface GetColumnsOptions {
|
|
portSlug: string;
|
|
onEdit?: (interest: ResidentialInterestRow) => void;
|
|
onArchive?: (interest: ResidentialInterestRow) => void;
|
|
}
|
|
|
|
export function getResidentialInterestColumns({
|
|
portSlug,
|
|
onEdit,
|
|
onArchive,
|
|
}: GetColumnsOptions): ColumnDef<ResidentialInterestRow, unknown>[] {
|
|
return [
|
|
{
|
|
id: 'clientName',
|
|
header: 'Client',
|
|
cell: ({ row }) => {
|
|
const r = row.original;
|
|
const name = r.clientName ?? '-';
|
|
return (
|
|
<Link
|
|
/* eslint-disable-next-line @typescript-eslint/no-explicit-any */
|
|
href={`/${portSlug}/residential/interests/${r.id}` as any}
|
|
className="font-medium text-foreground hover:underline"
|
|
onClick={(e) => e.stopPropagation()}
|
|
>
|
|
{name}
|
|
</Link>
|
|
);
|
|
},
|
|
},
|
|
{
|
|
id: 'pipelineStage',
|
|
accessorKey: 'pipelineStage',
|
|
header: 'Stage',
|
|
cell: ({ row }) => {
|
|
const s = row.original.pipelineStage;
|
|
return (
|
|
<Badge variant="secondary" className="text-xs">
|
|
{RESIDENTIAL_STAGE_LABELS[s] ?? s}
|
|
</Badge>
|
|
);
|
|
},
|
|
},
|
|
{
|
|
id: 'source',
|
|
accessorKey: 'source',
|
|
header: 'Source',
|
|
cell: ({ row }) => (
|
|
<span className="capitalize text-muted-foreground">{row.original.source ?? '-'}</span>
|
|
),
|
|
},
|
|
{
|
|
id: 'preferences',
|
|
accessorKey: 'preferences',
|
|
header: 'Preferences',
|
|
enableSorting: false,
|
|
cell: ({ row }) => (
|
|
<span className="line-clamp-1 max-w-xs text-muted-foreground">
|
|
{row.original.preferences ?? '-'}
|
|
</span>
|
|
),
|
|
},
|
|
{
|
|
id: 'notes',
|
|
accessorKey: 'notes',
|
|
header: 'Notes',
|
|
enableSorting: false,
|
|
cell: ({ row }) => (
|
|
<span className="line-clamp-1 max-w-xs text-muted-foreground">
|
|
{row.original.notes ?? '-'}
|
|
</span>
|
|
),
|
|
},
|
|
{
|
|
id: 'updatedAt',
|
|
accessorKey: 'updatedAt',
|
|
header: 'Updated',
|
|
cell: ({ row }) => (
|
|
<span className="text-xs text-muted-foreground">
|
|
{new Date(row.original.updatedAt).toLocaleDateString()}
|
|
</span>
|
|
),
|
|
},
|
|
{
|
|
id: 'actions',
|
|
header: '',
|
|
enableSorting: false,
|
|
size: 48,
|
|
cell: ({ row }) => (
|
|
<DropdownMenu>
|
|
<DropdownMenuTrigger asChild>
|
|
<Button
|
|
variant="ghost"
|
|
size="icon"
|
|
className="h-8 w-8"
|
|
onClick={(e) => e.stopPropagation()}
|
|
>
|
|
<MoreHorizontal className="h-4 w-4" />
|
|
</Button>
|
|
</DropdownMenuTrigger>
|
|
<DropdownMenuContent align="end">
|
|
{onEdit ? (
|
|
<DropdownMenuItem onClick={() => onEdit(row.original)}>
|
|
<Pencil className="mr-2 h-4 w-4" /> Edit
|
|
</DropdownMenuItem>
|
|
) : null}
|
|
{onArchive ? (
|
|
<DropdownMenuItem
|
|
onClick={() => onArchive(row.original)}
|
|
className="text-destructive focus:text-destructive"
|
|
>
|
|
<Archive className="mr-2 h-4 w-4" /> Archive
|
|
</DropdownMenuItem>
|
|
) : null}
|
|
</DropdownMenuContent>
|
|
</DropdownMenu>
|
|
),
|
|
},
|
|
];
|
|
}
|