Files
pn-new-crm/src/components/berths/berth-columns.tsx
Matt Ciaccio 8699f81879
Some checks failed
Build & Push Docker Images / lint (push) Failing after 1m18s
Build & Push Docker Images / build-and-push (push) Has been skipped
chore(style): codebase em-dash sweep + minor layout polish
Replaces every em-dash and en-dash with regular ASCII hyphens
across comments, JSX strings, and dev-facing logs. Mostly cosmetic
but stops the inconsistent mix that crept in over the last few
months (some files used em-dashes in comments, others didn't,
some used both).

Bundles two small dashboard-layout tweaks that touch a couple of
already-modified files:
- (dashboard)/layout.tsx main padding goes from p-6 to pt-3 px-6
  pb-6 so page content sits closer to the topbar.
- Sidebar now receives the ports list it needs for the footer
  port switcher.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-04 22:57:01 +02:00

164 lines
4.4 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
'use client';
import { type ColumnDef } from '@tanstack/react-table';
import { MoreHorizontal, Pencil, Activity } from 'lucide-react';
import { useRouter, useParams } from 'next/navigation';
import { Button } from '@/components/ui/button';
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from '@/components/ui/dropdown-menu';
import { TagBadge } from '@/components/shared/tag-badge';
export type BerthRow = {
id: string;
mooringNumber: string;
area: string | null;
status: string;
lengthM: string | null;
widthM: string | null;
price: string | null;
priceCurrency: string;
tenureType: string;
tags: Array<{ id: string; name: string; color: string }>;
};
function StatusBadge({ status }: { status: string }) {
const variants: Record<string, string> = {
available: 'bg-green-100 text-green-800 border-green-200',
under_offer: 'bg-yellow-100 text-yellow-800 border-yellow-200',
sold: 'bg-red-100 text-red-800 border-red-200',
};
const labels: Record<string, string> = {
available: 'Available',
under_offer: 'Under Offer',
sold: 'Sold',
};
return (
<span
className={`inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-medium ${variants[status] ?? 'bg-muted text-muted-foreground'}`}
>
{labels[status] ?? status}
</span>
);
}
function ActionsCell({ row }: { row: { original: BerthRow } }) {
const router = useRouter();
const params = useParams<{ portSlug: string }>();
const berth = row.original;
return (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button
variant="ghost"
size="icon"
className="h-8 w-8"
onClick={(e) => e.stopPropagation()}
>
<MoreHorizontal className="h-4 w-4" />
<span className="sr-only">Open menu</span>
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuItem
onClick={(e) => {
e.stopPropagation();
router.push(`/${params.portSlug}/berths/${berth.id}`);
}}
>
<Activity className="mr-2 h-4 w-4" />
View details
</DropdownMenuItem>
<DropdownMenuItem
onClick={(e) => {
e.stopPropagation();
router.push(`/${params.portSlug}/berths/${berth.id}?edit=true`);
}}
>
<Pencil className="mr-2 h-4 w-4" />
Edit
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
);
}
export const berthColumns: ColumnDef<BerthRow, unknown>[] = [
{
accessorKey: 'mooringNumber',
header: 'Mooring #',
cell: ({ row }) => <span className="font-medium">{row.original.mooringNumber}</span>,
},
{
accessorKey: 'area',
header: 'Area',
cell: ({ row }) => row.original.area ?? '-',
},
{
accessorKey: 'status',
header: 'Status',
cell: ({ row }) => <StatusBadge status={row.original.status} />,
},
{
id: 'dimensions',
header: 'Dimensions',
enableSorting: false,
cell: ({ row }) => {
const { lengthM, widthM } = row.original;
if (!lengthM && !widthM) return '-';
return `${lengthM ?? '?'}m × ${widthM ?? '?'}m`;
},
},
{
accessorKey: 'price',
header: 'Price',
cell: ({ row }) => {
const { price, priceCurrency } = row.original;
if (!price) return '-';
return new Intl.NumberFormat('en-US', {
style: 'currency',
currency: priceCurrency || 'USD',
maximumFractionDigits: 0,
}).format(Number(price));
},
},
{
accessorKey: 'tenureType',
header: 'Tenure',
cell: ({ row }) => (row.original.tenureType === 'permanent' ? 'Permanent' : 'Fixed Term'),
},
{
id: 'tags',
header: 'Tags',
enableSorting: false,
cell: ({ row }) => {
const { tags } = row.original;
if (!tags || tags.length === 0) return null;
return (
<div className="flex flex-wrap gap-1">
{tags.slice(0, 3).map((tag) => (
<TagBadge key={tag.id} name={tag.name} color={tag.color} />
))}
{tags.length > 3 && (
<span className="text-xs text-muted-foreground">+{tags.length - 3}</span>
)}
</div>
);
},
},
{
id: 'actions',
header: '',
enableSorting: false,
size: 48,
cell: ({ row }) => <ActionsCell row={row} />,
},
];