Files
pn-new-crm/src/components/residential/residential-interest-columns.tsx
Matt 221ae5784e chore(autonomous-session): consolidate uncommitted work from prior session
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
2026-05-23 00:52:59 +02:00

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>
),
},
];
}