feat(mobile): mobile cards for yachts, companies, berths, invoices, expenses
Five new <EntityCard> files using the shared <ListCard> shell, wired
into each list page's <DataTable> via cardRender. Desktop view
(lg+) is unchanged.
- YachtCard: Ship icon, owner subtitle (User/Building2 icon by
ownerType), dimensions in meters preferred, hull #,
status pill. No accent bar (status is free-text).
- CompanyCard: Building2 icon, legalName subtitle, country (MapPin)
+ tax id (Hash) meta, member/yacht count line.
- BerthCard: Anchor icon, area subtitle (MapPin), dimensions
meta, status pill. Status-encoded accent bar
(emerald=available, amber=under_offer, slate=sold).
- InvoiceCard: FileText icon, client subtitle, due date (Calendar)
meta, prominent currency-formatted amount. Status
accent bar (emerald=paid, orange=overdue, ...).
- ExpenseCard: Receipt icon, category subtitle, expense date meta,
prominent amount, payment-status pill, "Possible
duplicate" pill when duplicateOf is set. Accent bar
by paymentStatus, overridden to amber when flagged
as duplicate.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-01 15:34:04 +02:00
|
|
|
|
'use client';
|
|
|
|
|
|
|
2026-05-12 14:50:58 +02:00
|
|
|
|
import { Activity, MoreHorizontal, Pencil } from 'lucide-react';
|
feat(mobile): mobile cards for yachts, companies, berths, invoices, expenses
Five new <EntityCard> files using the shared <ListCard> shell, wired
into each list page's <DataTable> via cardRender. Desktop view
(lg+) is unchanged.
- YachtCard: Ship icon, owner subtitle (User/Building2 icon by
ownerType), dimensions in meters preferred, hull #,
status pill. No accent bar (status is free-text).
- CompanyCard: Building2 icon, legalName subtitle, country (MapPin)
+ tax id (Hash) meta, member/yacht count line.
- BerthCard: Anchor icon, area subtitle (MapPin), dimensions
meta, status pill. Status-encoded accent bar
(emerald=available, amber=under_offer, slate=sold).
- InvoiceCard: FileText icon, client subtitle, due date (Calendar)
meta, prominent currency-formatted amount. Status
accent bar (emerald=paid, orange=overdue, ...).
- ExpenseCard: Receipt icon, category subtitle, expense date meta,
prominent amount, payment-status pill, "Possible
duplicate" pill when duplicateOf is set. Accent bar
by paymentStatus, overridden to amber when flagged
as duplicate.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-01 15:34:04 +02:00
|
|
|
|
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';
|
|
|
|
|
|
import { ListCard, ListCardAvatar, ListCardMeta } from '@/components/shared/list-card';
|
2026-05-13 11:54:13 +02:00
|
|
|
|
import { StatusPill, type StatusPillStatus } from '@/components/ui/status-pill';
|
2026-06-03 16:01:40 +02:00
|
|
|
|
import { BerthStatusQuickEdit } from './berth-status-quick-edit';
|
2026-05-09 04:24:46 +02:00
|
|
|
|
import { formatCurrency } from '@/lib/utils/currency';
|
feat(mobile): mobile cards for yachts, companies, berths, invoices, expenses
Five new <EntityCard> files using the shared <ListCard> shell, wired
into each list page's <DataTable> via cardRender. Desktop view
(lg+) is unchanged.
- YachtCard: Ship icon, owner subtitle (User/Building2 icon by
ownerType), dimensions in meters preferred, hull #,
status pill. No accent bar (status is free-text).
- CompanyCard: Building2 icon, legalName subtitle, country (MapPin)
+ tax id (Hash) meta, member/yacht count line.
- BerthCard: Anchor icon, area subtitle (MapPin), dimensions
meta, status pill. Status-encoded accent bar
(emerald=available, amber=under_offer, slate=sold).
- InvoiceCard: FileText icon, client subtitle, due date (Calendar)
meta, prominent currency-formatted amount. Status
accent bar (emerald=paid, orange=overdue, ...).
- ExpenseCard: Receipt icon, category subtitle, expense date meta,
prominent amount, payment-status pill, "Possible
duplicate" pill when duplicateOf is set. Accent bar
by paymentStatus, overridden to amber when flagged
as duplicate.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-01 15:34:04 +02:00
|
|
|
|
import type { BerthRow } from './berth-columns';
|
feat(berths): NocoDB-aligned dropdown enums + dual-unit auto-fill
Pull verbatim SingleSelect choices from NocoDB Berths via MCP and lock
them into BERTH_*_OPTIONS / _TYPES in lib/constants.ts: Side Pontoon
(10 values), Mooring Type (5), Cleat Type (2), Cleat Capacity (2),
Bollard Type (2), Bollard Capacity (2), Access (5), Area (A–E), Bow
Facing (4-value UX-only constraint over a SingleLineText). Power
Capacity / Voltage stay numeric inputs (NocoDB stores Number).
Add `toSelectOptions()` mapper for shadcn `<Select>` `{value,label}`
pairs.
Wire every berth dropdown — both the modal form and the inline-edit
detail tabs — to `<Select>`. Inline `EditableSpec` gains
`selectOptions` for the variant and `linkedUnit { field, multiplier }`
to auto-patch the metric column on save (× 0.3048 for ft→m on length,
width, draft, nominal boat size, water depth).
Promote nominal boat size + tenure type from read-only `<SpecRow>` to
`<EditableSpec>` so reps can edit them. Tenure type currently uses the
validator's `'permanent' | 'fixed_term'` set; will swap to per-port
configurable list once Vocabularies admin lands (Wave 5).
Mobile berth cards: replace status-coloured stripe with
`mooringLetterDot()` so it groups by dock letter; status conveyed by
the existing pill below. Berth detail header: "{Letter} Dock" chip
instead of bare "A" / "B" text. Berth area filter: `<Select>` over
A/B/C/D/E (renamed to "Dock"). Documents tab gets a one-paragraph
explainer disambiguating the spec PDF from deal documents (Interests
tab).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 04:10:24 +02:00
|
|
|
|
import { mooringLetterDot } from './mooring-letter-tone';
|
feat(mobile): mobile cards for yachts, companies, berths, invoices, expenses
Five new <EntityCard> files using the shared <ListCard> shell, wired
into each list page's <DataTable> via cardRender. Desktop view
(lg+) is unchanged.
- YachtCard: Ship icon, owner subtitle (User/Building2 icon by
ownerType), dimensions in meters preferred, hull #,
status pill. No accent bar (status is free-text).
- CompanyCard: Building2 icon, legalName subtitle, country (MapPin)
+ tax id (Hash) meta, member/yacht count line.
- BerthCard: Anchor icon, area subtitle (MapPin), dimensions
meta, status pill. Status-encoded accent bar
(emerald=available, amber=under_offer, slate=sold).
- InvoiceCard: FileText icon, client subtitle, due date (Calendar)
meta, prominent currency-formatted amount. Status
accent bar (emerald=paid, orange=overdue, ...).
- ExpenseCard: Receipt icon, category subtitle, expense date meta,
prominent amount, payment-status pill, "Possible
duplicate" pill when duplicateOf is set. Accent bar
by paymentStatus, overridden to amber when flagged
as duplicate.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-01 15:34:04 +02:00
|
|
|
|
|
|
|
|
|
|
const STATUS_LABELS: Record<string, string> = {
|
|
|
|
|
|
available: 'Available',
|
|
|
|
|
|
under_offer: 'Under Offer',
|
|
|
|
|
|
sold: 'Sold',
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2026-05-13 11:54:13 +02:00
|
|
|
|
const BERTH_STATUS_PILL: Record<string, StatusPillStatus> = {
|
|
|
|
|
|
available: 'available',
|
|
|
|
|
|
under_offer: 'under_offer',
|
|
|
|
|
|
sold: 'sold',
|
|
|
|
|
|
};
|
|
|
|
|
|
|
feat(mobile): mobile cards for yachts, companies, berths, invoices, expenses
Five new <EntityCard> files using the shared <ListCard> shell, wired
into each list page's <DataTable> via cardRender. Desktop view
(lg+) is unchanged.
- YachtCard: Ship icon, owner subtitle (User/Building2 icon by
ownerType), dimensions in meters preferred, hull #,
status pill. No accent bar (status is free-text).
- CompanyCard: Building2 icon, legalName subtitle, country (MapPin)
+ tax id (Hash) meta, member/yacht count line.
- BerthCard: Anchor icon, area subtitle (MapPin), dimensions
meta, status pill. Status-encoded accent bar
(emerald=available, amber=under_offer, slate=sold).
- InvoiceCard: FileText icon, client subtitle, due date (Calendar)
meta, prominent currency-formatted amount. Status
accent bar (emerald=paid, orange=overdue, ...).
- ExpenseCard: Receipt icon, category subtitle, expense date meta,
prominent amount, payment-status pill, "Possible
duplicate" pill when duplicateOf is set. Accent bar
by paymentStatus, overridden to amber when flagged
as duplicate.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-01 15:34:04 +02:00
|
|
|
|
interface BerthCardProps {
|
|
|
|
|
|
berth: BerthRow;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export function BerthCard({ berth }: BerthCardProps) {
|
|
|
|
|
|
const router = useRouter();
|
|
|
|
|
|
const params = useParams<{ portSlug: string }>();
|
|
|
|
|
|
const portSlug = params?.portSlug ?? '';
|
|
|
|
|
|
|
|
|
|
|
|
const statusLabel = STATUS_LABELS[berth.status] ?? berth.status;
|
2026-05-13 11:54:13 +02:00
|
|
|
|
const statusPill = BERTH_STATUS_PILL[berth.status] ?? 'pending';
|
feat(berths): NocoDB-aligned dropdown enums + dual-unit auto-fill
Pull verbatim SingleSelect choices from NocoDB Berths via MCP and lock
them into BERTH_*_OPTIONS / _TYPES in lib/constants.ts: Side Pontoon
(10 values), Mooring Type (5), Cleat Type (2), Cleat Capacity (2),
Bollard Type (2), Bollard Capacity (2), Access (5), Area (A–E), Bow
Facing (4-value UX-only constraint over a SingleLineText). Power
Capacity / Voltage stay numeric inputs (NocoDB stores Number).
Add `toSelectOptions()` mapper for shadcn `<Select>` `{value,label}`
pairs.
Wire every berth dropdown — both the modal form and the inline-edit
detail tabs — to `<Select>`. Inline `EditableSpec` gains
`selectOptions` for the variant and `linkedUnit { field, multiplier }`
to auto-patch the metric column on save (× 0.3048 for ft→m on length,
width, draft, nominal boat size, water depth).
Promote nominal boat size + tenure type from read-only `<SpecRow>` to
`<EditableSpec>` so reps can edit them. Tenure type currently uses the
validator's `'permanent' | 'fixed_term'` set; will swap to per-port
configurable list once Vocabularies admin lands (Wave 5).
Mobile berth cards: replace status-coloured stripe with
`mooringLetterDot()` so it groups by dock letter; status conveyed by
the existing pill below. Berth detail header: "{Letter} Dock" chip
instead of bare "A" / "B" text. Berth area filter: `<Select>` over
A/B/C/D/E (renamed to "Dock"). Documents tab gets a one-paragraph
explainer disambiguating the spec PDF from deal documents (Interests
tab).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 04:10:24 +02:00
|
|
|
|
// Accent stripe groups visually by dock (A-row, B-row, ...). Status is
|
|
|
|
|
|
// already conveyed by the pill below, so the stripe is dock-keyed.
|
|
|
|
|
|
const accentClass = mooringLetterDot(berth.mooringNumber) ?? 'bg-slate-300';
|
feat(mobile): mobile cards for yachts, companies, berths, invoices, expenses
Five new <EntityCard> files using the shared <ListCard> shell, wired
into each list page's <DataTable> via cardRender. Desktop view
(lg+) is unchanged.
- YachtCard: Ship icon, owner subtitle (User/Building2 icon by
ownerType), dimensions in meters preferred, hull #,
status pill. No accent bar (status is free-text).
- CompanyCard: Building2 icon, legalName subtitle, country (MapPin)
+ tax id (Hash) meta, member/yacht count line.
- BerthCard: Anchor icon, area subtitle (MapPin), dimensions
meta, status pill. Status-encoded accent bar
(emerald=available, amber=under_offer, slate=sold).
- InvoiceCard: FileText icon, client subtitle, due date (Calendar)
meta, prominent currency-formatted amount. Status
accent bar (emerald=paid, orange=overdue, ...).
- ExpenseCard: Receipt icon, category subtitle, expense date meta,
prominent amount, payment-status pill, "Possible
duplicate" pill when duplicateOf is set. Accent bar
by paymentStatus, overridden to amber when flagged
as duplicate.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-01 15:34:04 +02:00
|
|
|
|
|
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
|
|
|
|
// Dimensions string - Length × Width × Draft (each segment is optional).
|
2026-05-12 14:50:58 +02:00
|
|
|
|
// The avatar already conveys the mooring number, so this becomes the
|
|
|
|
|
|
// primary "what is this berth" line.
|
|
|
|
|
|
const dimParts: string[] = [];
|
|
|
|
|
|
if (berth.lengthM) dimParts.push(`${berth.lengthM}m`);
|
|
|
|
|
|
if (berth.widthM) dimParts.push(`${berth.widthM}m`);
|
|
|
|
|
|
if (berth.draftM) dimParts.push(`${berth.draftM}m draft`);
|
|
|
|
|
|
const dimText = dimParts.length > 0 ? dimParts.join(' × ') : null;
|
|
|
|
|
|
|
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
|
|
|
|
// Recommended boat size - the most rep-actionable signal in a glance
|
2026-05-12 14:50:58 +02:00
|
|
|
|
// ("can my client's yacht park here?"). Tenure was previously here but
|
|
|
|
|
|
// dropped: tenure is set per EOI/contract, not per berth, so showing
|
|
|
|
|
|
// it as a berth property was misleading.
|
|
|
|
|
|
let boatCapacityText: string | null = null;
|
|
|
|
|
|
if (berth.nominalBoatSizeM) {
|
|
|
|
|
|
boatCapacityText = `Fits up to ${berth.nominalBoatSizeM}m`;
|
|
|
|
|
|
} else if (berth.nominalBoatSize) {
|
|
|
|
|
|
boatCapacityText = `Fits up to ${berth.nominalBoatSize}ft`;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
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
|
|
|
|
// Water depth - operational; matters for deep-keel yachts.
|
2026-05-12 14:50:58 +02:00
|
|
|
|
let waterDepthText: string | null = null;
|
|
|
|
|
|
if (berth.waterDepthM) {
|
|
|
|
|
|
const prefix = berth.waterDepthIsMinimum ? '≥ ' : '';
|
|
|
|
|
|
waterDepthText = `${prefix}${berth.waterDepthM}m deep`;
|
feat(mobile): mobile cards for yachts, companies, berths, invoices, expenses
Five new <EntityCard> files using the shared <ListCard> shell, wired
into each list page's <DataTable> via cardRender. Desktop view
(lg+) is unchanged.
- YachtCard: Ship icon, owner subtitle (User/Building2 icon by
ownerType), dimensions in meters preferred, hull #,
status pill. No accent bar (status is free-text).
- CompanyCard: Building2 icon, legalName subtitle, country (MapPin)
+ tax id (Hash) meta, member/yacht count line.
- BerthCard: Anchor icon, area subtitle (MapPin), dimensions
meta, status pill. Status-encoded accent bar
(emerald=available, amber=under_offer, slate=sold).
- InvoiceCard: FileText icon, client subtitle, due date (Calendar)
meta, prominent currency-formatted amount. Status
accent bar (emerald=paid, orange=overdue, ...).
- ExpenseCard: Receipt icon, category subtitle, expense date meta,
prominent amount, payment-status pill, "Possible
duplicate" pill when duplicateOf is set. Accent bar
by paymentStatus, overridden to amber when flagged
as duplicate.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-01 15:34:04 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-12 14:50:58 +02:00
|
|
|
|
// Power label: combine capacity + voltage when both present.
|
|
|
|
|
|
let powerText: string | null = null;
|
|
|
|
|
|
if (berth.powerCapacity && berth.voltage) {
|
|
|
|
|
|
powerText = `${berth.powerCapacity}A / ${berth.voltage}V`;
|
|
|
|
|
|
} else if (berth.powerCapacity) {
|
|
|
|
|
|
powerText = `${berth.powerCapacity}A`;
|
|
|
|
|
|
} else if (berth.voltage) {
|
|
|
|
|
|
powerText = `${berth.voltage}V`;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Secondary meta: boat-capacity · water-depth · price · power. All
|
|
|
|
|
|
// optional; order favours the highest-utility scan signals first.
|
feat(mobile): mobile cards for yachts, companies, berths, invoices, expenses
Five new <EntityCard> files using the shared <ListCard> shell, wired
into each list page's <DataTable> via cardRender. Desktop view
(lg+) is unchanged.
- YachtCard: Ship icon, owner subtitle (User/Building2 icon by
ownerType), dimensions in meters preferred, hull #,
status pill. No accent bar (status is free-text).
- CompanyCard: Building2 icon, legalName subtitle, country (MapPin)
+ tax id (Hash) meta, member/yacht count line.
- BerthCard: Anchor icon, area subtitle (MapPin), dimensions
meta, status pill. Status-encoded accent bar
(emerald=available, amber=under_offer, slate=sold).
- InvoiceCard: FileText icon, client subtitle, due date (Calendar)
meta, prominent currency-formatted amount. Status
accent bar (emerald=paid, orange=overdue, ...).
- ExpenseCard: Receipt icon, category subtitle, expense date meta,
prominent amount, payment-status pill, "Possible
duplicate" pill when duplicateOf is set. Accent bar
by paymentStatus, overridden to amber when flagged
as duplicate.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-01 15:34:04 +02:00
|
|
|
|
const metaParts: string[] = [];
|
2026-05-12 14:50:58 +02:00
|
|
|
|
if (boatCapacityText) metaParts.push(boatCapacityText);
|
|
|
|
|
|
if (waterDepthText) metaParts.push(waterDepthText);
|
2026-05-09 04:24:46 +02:00
|
|
|
|
if (berth.price)
|
2026-05-09 18:35:34 +02:00
|
|
|
|
metaParts.push(formatCurrency(berth.price, berth.priceCurrency, { maxFractionDigits: 0 }));
|
2026-05-12 14:50:58 +02:00
|
|
|
|
if (powerText) metaParts.push(powerText);
|
feat(mobile): mobile cards for yachts, companies, berths, invoices, expenses
Five new <EntityCard> files using the shared <ListCard> shell, wired
into each list page's <DataTable> via cardRender. Desktop view
(lg+) is unchanged.
- YachtCard: Ship icon, owner subtitle (User/Building2 icon by
ownerType), dimensions in meters preferred, hull #,
status pill. No accent bar (status is free-text).
- CompanyCard: Building2 icon, legalName subtitle, country (MapPin)
+ tax id (Hash) meta, member/yacht count line.
- BerthCard: Anchor icon, area subtitle (MapPin), dimensions
meta, status pill. Status-encoded accent bar
(emerald=available, amber=under_offer, slate=sold).
- InvoiceCard: FileText icon, client subtitle, due date (Calendar)
meta, prominent currency-formatted amount. Status
accent bar (emerald=paid, orange=overdue, ...).
- ExpenseCard: Receipt icon, category subtitle, expense date meta,
prominent amount, payment-status pill, "Possible
duplicate" pill when duplicateOf is set. Accent bar
by paymentStatus, overridden to amber when flagged
as duplicate.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-01 15:34:04 +02:00
|
|
|
|
|
|
|
|
|
|
const tags = berth.tags ?? [];
|
|
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
|
<ListCard
|
|
|
|
|
|
href={`/${portSlug}/berths/${berth.id}`}
|
|
|
|
|
|
ariaLabel={`Berth ${berth.mooringNumber}`}
|
|
|
|
|
|
accentClassName={accentClass}
|
|
|
|
|
|
actions={
|
|
|
|
|
|
<DropdownMenu>
|
|
|
|
|
|
<DropdownMenuTrigger asChild>
|
|
|
|
|
|
<Button
|
|
|
|
|
|
variant="ghost"
|
|
|
|
|
|
size="icon"
|
|
|
|
|
|
className="h-9 w-9"
|
|
|
|
|
|
onClick={(e) => e.stopPropagation()}
|
|
|
|
|
|
aria-label={`Actions for berth ${berth.mooringNumber}`}
|
|
|
|
|
|
>
|
fix(audit-wave-10): aria-hidden sweep on decorative Lucide icons (#69)
Mechanical codemod added \`aria-hidden\` to 444 self-closing single-line
Lucide icon JSX elements across 267 .tsx files in:
- shared/, layout/, dashboard/
- admin/ (all sections)
- clients/, berths/, yachts/, companies/, interests/, documents/
- reminders/, reservations/, residential/, expenses/, email/
The regex targeted only the safe pattern \`<IconName className="..." />\`
(no other props, self-closing, capitalized component name). Every match
inspected is a decorative companion to visible text or sits inside a
button whose accessible name comes from \`aria-label\` / sr-only text
— the icon itself should not be announced.
Screen readers no longer double-read the icon + the adjacent label
text (e.g. "Pencil Pencil Edit" → just "Edit"). The existing
@axe-core/playwright smoke test (\`20-accessibility.spec.ts\`) continues
to pass.
Test suite stays at 1315/1315 vitest. typescript clean.
Closes task #69 (aria-hidden sweep) from the AUDIT-2026-05-12 follow-ups
backlog.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-13 12:37:22 +02:00
|
|
|
|
<MoreHorizontal className="h-4 w-4" aria-hidden />
|
feat(mobile): mobile cards for yachts, companies, berths, invoices, expenses
Five new <EntityCard> files using the shared <ListCard> shell, wired
into each list page's <DataTable> via cardRender. Desktop view
(lg+) is unchanged.
- YachtCard: Ship icon, owner subtitle (User/Building2 icon by
ownerType), dimensions in meters preferred, hull #,
status pill. No accent bar (status is free-text).
- CompanyCard: Building2 icon, legalName subtitle, country (MapPin)
+ tax id (Hash) meta, member/yacht count line.
- BerthCard: Anchor icon, area subtitle (MapPin), dimensions
meta, status pill. Status-encoded accent bar
(emerald=available, amber=under_offer, slate=sold).
- InvoiceCard: FileText icon, client subtitle, due date (Calendar)
meta, prominent currency-formatted amount. Status
accent bar (emerald=paid, orange=overdue, ...).
- ExpenseCard: Receipt icon, category subtitle, expense date meta,
prominent amount, payment-status pill, "Possible
duplicate" pill when duplicateOf is set. Accent bar
by paymentStatus, overridden to amber when flagged
as duplicate.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-01 15:34:04 +02:00
|
|
|
|
</Button>
|
|
|
|
|
|
</DropdownMenuTrigger>
|
|
|
|
|
|
<DropdownMenuContent align="end">
|
|
|
|
|
|
<DropdownMenuItem
|
|
|
|
|
|
onClick={(e) => {
|
|
|
|
|
|
e.stopPropagation();
|
|
|
|
|
|
router.push(`/${portSlug}/berths/${berth.id}`);
|
|
|
|
|
|
}}
|
|
|
|
|
|
>
|
fix(audit-wave-10): aria-hidden sweep on decorative Lucide icons (#69)
Mechanical codemod added \`aria-hidden\` to 444 self-closing single-line
Lucide icon JSX elements across 267 .tsx files in:
- shared/, layout/, dashboard/
- admin/ (all sections)
- clients/, berths/, yachts/, companies/, interests/, documents/
- reminders/, reservations/, residential/, expenses/, email/
The regex targeted only the safe pattern \`<IconName className="..." />\`
(no other props, self-closing, capitalized component name). Every match
inspected is a decorative companion to visible text or sits inside a
button whose accessible name comes from \`aria-label\` / sr-only text
— the icon itself should not be announced.
Screen readers no longer double-read the icon + the adjacent label
text (e.g. "Pencil Pencil Edit" → just "Edit"). The existing
@axe-core/playwright smoke test (\`20-accessibility.spec.ts\`) continues
to pass.
Test suite stays at 1315/1315 vitest. typescript clean.
Closes task #69 (aria-hidden sweep) from the AUDIT-2026-05-12 follow-ups
backlog.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-13 12:37:22 +02:00
|
|
|
|
<Activity className="mr-2 h-3.5 w-3.5" aria-hidden />
|
feat(mobile): mobile cards for yachts, companies, berths, invoices, expenses
Five new <EntityCard> files using the shared <ListCard> shell, wired
into each list page's <DataTable> via cardRender. Desktop view
(lg+) is unchanged.
- YachtCard: Ship icon, owner subtitle (User/Building2 icon by
ownerType), dimensions in meters preferred, hull #,
status pill. No accent bar (status is free-text).
- CompanyCard: Building2 icon, legalName subtitle, country (MapPin)
+ tax id (Hash) meta, member/yacht count line.
- BerthCard: Anchor icon, area subtitle (MapPin), dimensions
meta, status pill. Status-encoded accent bar
(emerald=available, amber=under_offer, slate=sold).
- InvoiceCard: FileText icon, client subtitle, due date (Calendar)
meta, prominent currency-formatted amount. Status
accent bar (emerald=paid, orange=overdue, ...).
- ExpenseCard: Receipt icon, category subtitle, expense date meta,
prominent amount, payment-status pill, "Possible
duplicate" pill when duplicateOf is set. Accent bar
by paymentStatus, overridden to amber when flagged
as duplicate.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-01 15:34:04 +02:00
|
|
|
|
View details
|
|
|
|
|
|
</DropdownMenuItem>
|
|
|
|
|
|
<DropdownMenuItem
|
|
|
|
|
|
onClick={(e) => {
|
|
|
|
|
|
e.stopPropagation();
|
|
|
|
|
|
router.push(`/${portSlug}/berths/${berth.id}?edit=true`);
|
|
|
|
|
|
}}
|
|
|
|
|
|
>
|
fix(audit-wave-10): aria-hidden sweep on decorative Lucide icons (#69)
Mechanical codemod added \`aria-hidden\` to 444 self-closing single-line
Lucide icon JSX elements across 267 .tsx files in:
- shared/, layout/, dashboard/
- admin/ (all sections)
- clients/, berths/, yachts/, companies/, interests/, documents/
- reminders/, reservations/, residential/, expenses/, email/
The regex targeted only the safe pattern \`<IconName className="..." />\`
(no other props, self-closing, capitalized component name). Every match
inspected is a decorative companion to visible text or sits inside a
button whose accessible name comes from \`aria-label\` / sr-only text
— the icon itself should not be announced.
Screen readers no longer double-read the icon + the adjacent label
text (e.g. "Pencil Pencil Edit" → just "Edit"). The existing
@axe-core/playwright smoke test (\`20-accessibility.spec.ts\`) continues
to pass.
Test suite stays at 1315/1315 vitest. typescript clean.
Closes task #69 (aria-hidden sweep) from the AUDIT-2026-05-12 follow-ups
backlog.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-13 12:37:22 +02:00
|
|
|
|
<Pencil className="mr-2 h-3.5 w-3.5" aria-hidden />
|
feat(mobile): mobile cards for yachts, companies, berths, invoices, expenses
Five new <EntityCard> files using the shared <ListCard> shell, wired
into each list page's <DataTable> via cardRender. Desktop view
(lg+) is unchanged.
- YachtCard: Ship icon, owner subtitle (User/Building2 icon by
ownerType), dimensions in meters preferred, hull #,
status pill. No accent bar (status is free-text).
- CompanyCard: Building2 icon, legalName subtitle, country (MapPin)
+ tax id (Hash) meta, member/yacht count line.
- BerthCard: Anchor icon, area subtitle (MapPin), dimensions
meta, status pill. Status-encoded accent bar
(emerald=available, amber=under_offer, slate=sold).
- InvoiceCard: FileText icon, client subtitle, due date (Calendar)
meta, prominent currency-formatted amount. Status
accent bar (emerald=paid, orange=overdue, ...).
- ExpenseCard: Receipt icon, category subtitle, expense date meta,
prominent amount, payment-status pill, "Possible
duplicate" pill when duplicateOf is set. Accent bar
by paymentStatus, overridden to amber when flagged
as duplicate.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-01 15:34:04 +02:00
|
|
|
|
Edit
|
|
|
|
|
|
</DropdownMenuItem>
|
|
|
|
|
|
</DropdownMenuContent>
|
|
|
|
|
|
</DropdownMenu>
|
|
|
|
|
|
}
|
|
|
|
|
|
>
|
2026-05-12 14:50:58 +02:00
|
|
|
|
<div className="flex items-center gap-3">
|
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
|
|
|
|
{/* The mooring number IS the avatar - recognisable at a glance
|
2026-05-12 14:50:58 +02:00
|
|
|
|
(A1, B12, …) and eliminates the duplicate berth-number heading
|
|
|
|
|
|
that previously sat to the right of an anchor icon. */}
|
|
|
|
|
|
<ListCardAvatar
|
|
|
|
|
|
initials={berth.mooringNumber}
|
|
|
|
|
|
className="text-base font-bold tracking-tight"
|
|
|
|
|
|
/>
|
feat(mobile): mobile cards for yachts, companies, berths, invoices, expenses
Five new <EntityCard> files using the shared <ListCard> shell, wired
into each list page's <DataTable> via cardRender. Desktop view
(lg+) is unchanged.
- YachtCard: Ship icon, owner subtitle (User/Building2 icon by
ownerType), dimensions in meters preferred, hull #,
status pill. No accent bar (status is free-text).
- CompanyCard: Building2 icon, legalName subtitle, country (MapPin)
+ tax id (Hash) meta, member/yacht count line.
- BerthCard: Anchor icon, area subtitle (MapPin), dimensions
meta, status pill. Status-encoded accent bar
(emerald=available, amber=under_offer, slate=sold).
- InvoiceCard: FileText icon, client subtitle, due date (Calendar)
meta, prominent currency-formatted amount. Status
accent bar (emerald=paid, orange=overdue, ...).
- ExpenseCard: Receipt icon, category subtitle, expense date meta,
prominent amount, payment-status pill, "Possible
duplicate" pill when duplicateOf is set. Accent bar
by paymentStatus, overridden to amber when flagged
as duplicate.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-01 15:34:04 +02:00
|
|
|
|
<div className="min-w-0 flex-1">
|
2026-05-12 14:50:58 +02:00
|
|
|
|
{/* Primary line: dimensions (L × W × Draft). The avatar
|
|
|
|
|
|
already carries the area letter, so this slot becomes the
|
|
|
|
|
|
"what fits here" answer. Falls back gracefully when
|
|
|
|
|
|
dimensions aren't recorded yet. */}
|
|
|
|
|
|
<div className="flex items-center justify-between gap-2">
|
|
|
|
|
|
<p className="min-w-0 truncate text-sm font-semibold text-foreground">
|
|
|
|
|
|
{dimText ?? <span className="font-normal text-muted-foreground">No dimensions</span>}
|
|
|
|
|
|
</p>
|
feat(mobile): mobile cards for yachts, companies, berths, invoices, expenses
Five new <EntityCard> files using the shared <ListCard> shell, wired
into each list page's <DataTable> via cardRender. Desktop view
(lg+) is unchanged.
- YachtCard: Ship icon, owner subtitle (User/Building2 icon by
ownerType), dimensions in meters preferred, hull #,
status pill. No accent bar (status is free-text).
- CompanyCard: Building2 icon, legalName subtitle, country (MapPin)
+ tax id (Hash) meta, member/yacht count line.
- BerthCard: Anchor icon, area subtitle (MapPin), dimensions
meta, status pill. Status-encoded accent bar
(emerald=available, amber=under_offer, slate=sold).
- InvoiceCard: FileText icon, client subtitle, due date (Calendar)
meta, prominent currency-formatted amount. Status
accent bar (emerald=paid, orange=overdue, ...).
- ExpenseCard: Receipt icon, category subtitle, expense date meta,
prominent amount, payment-status pill, "Possible
duplicate" pill when duplicateOf is set. Accent bar
by paymentStatus, overridden to amber when flagged
as duplicate.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-01 15:34:04 +02:00
|
|
|
|
<span aria-hidden className="block h-9 w-9 shrink-0" />
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
2026-05-12 14:50:58 +02:00
|
|
|
|
{/* Meta line: tenure · price · power. All optional. */}
|
feat(mobile): mobile cards for yachts, companies, berths, invoices, expenses
Five new <EntityCard> files using the shared <ListCard> shell, wired
into each list page's <DataTable> via cardRender. Desktop view
(lg+) is unchanged.
- YachtCard: Ship icon, owner subtitle (User/Building2 icon by
ownerType), dimensions in meters preferred, hull #,
status pill. No accent bar (status is free-text).
- CompanyCard: Building2 icon, legalName subtitle, country (MapPin)
+ tax id (Hash) meta, member/yacht count line.
- BerthCard: Anchor icon, area subtitle (MapPin), dimensions
meta, status pill. Status-encoded accent bar
(emerald=available, amber=under_offer, slate=sold).
- InvoiceCard: FileText icon, client subtitle, due date (Calendar)
meta, prominent currency-formatted amount. Status
accent bar (emerald=paid, orange=overdue, ...).
- ExpenseCard: Receipt icon, category subtitle, expense date meta,
prominent amount, payment-status pill, "Possible
duplicate" pill when duplicateOf is set. Accent bar
by paymentStatus, overridden to amber when flagged
as duplicate.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-01 15:34:04 +02:00
|
|
|
|
{metaParts.length > 0 ? (
|
|
|
|
|
|
<div className="mt-0.5 flex flex-wrap items-center gap-x-1.5 text-xs text-muted-foreground">
|
|
|
|
|
|
{metaParts.map((part, i) => (
|
|
|
|
|
|
<span key={part} className="inline-flex items-center gap-1">
|
|
|
|
|
|
{i > 0 ? <span aria-hidden>·</span> : null}
|
|
|
|
|
|
<ListCardMeta>{part}</ListCardMeta>
|
|
|
|
|
|
</span>
|
|
|
|
|
|
))}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
) : null}
|
|
|
|
|
|
|
2026-05-12 14:50:58 +02:00
|
|
|
|
{/* Status pill + tags */}
|
|
|
|
|
|
<div className="mt-1.5 flex flex-wrap items-center gap-1.5">
|
2026-06-03 16:01:40 +02:00
|
|
|
|
<BerthStatusQuickEdit berthId={berth.id} currentStatus={berth.status}>
|
|
|
|
|
|
<StatusPill status={statusPill}>{statusLabel}</StatusPill>
|
|
|
|
|
|
</BerthStatusQuickEdit>
|
2026-05-12 14:50:58 +02:00
|
|
|
|
{tags.slice(0, 2).map((tag) => (
|
|
|
|
|
|
<TagBadge key={tag.id} name={tag.name} color={tag.color} />
|
|
|
|
|
|
))}
|
|
|
|
|
|
{tags.length > 2 ? (
|
|
|
|
|
|
<span className="inline-flex items-center rounded-full bg-secondary px-2 py-0.5 text-xs text-secondary-foreground">
|
|
|
|
|
|
+{tags.length - 2}
|
|
|
|
|
|
</span>
|
|
|
|
|
|
) : null}
|
feat(mobile): mobile cards for yachts, companies, berths, invoices, expenses
Five new <EntityCard> files using the shared <ListCard> shell, wired
into each list page's <DataTable> via cardRender. Desktop view
(lg+) is unchanged.
- YachtCard: Ship icon, owner subtitle (User/Building2 icon by
ownerType), dimensions in meters preferred, hull #,
status pill. No accent bar (status is free-text).
- CompanyCard: Building2 icon, legalName subtitle, country (MapPin)
+ tax id (Hash) meta, member/yacht count line.
- BerthCard: Anchor icon, area subtitle (MapPin), dimensions
meta, status pill. Status-encoded accent bar
(emerald=available, amber=under_offer, slate=sold).
- InvoiceCard: FileText icon, client subtitle, due date (Calendar)
meta, prominent currency-formatted amount. Status
accent bar (emerald=paid, orange=overdue, ...).
- ExpenseCard: Receipt icon, category subtitle, expense date meta,
prominent amount, payment-status pill, "Possible
duplicate" pill when duplicateOf is set. Accent bar
by paymentStatus, overridden to amber when flagged
as duplicate.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-01 15:34:04 +02:00
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</ListCard>
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|