feat(uat-batch-14): InterestDocumentsTab rename, custom-field tooltip, yacht Transfer surface
- InterestDocumentsTab section "Legal documents" renamed to
"Signature documents" so its scope is unambiguous. The section
holds Documenso envelopes (EOI / Reservation / Contract); generic
legal uploads belong in Attachments below.
- Custom-field admin form's "Sort Order" label now uses the
FieldLabel primitive with an explainer tooltip ("Lower numbers
render first... use to pin frequently-edited fields to the top").
First adoption of the FieldLabel primitive shipped in PR4.2.
- Yacht Ownership History tab gains a "Transfer ownership" button:
in the populated state as a header CTA (perm-gated by yachts.edit),
in the empty state as the EmptyState action. Reuses the existing
YachtTransferDialog from the header. Closes the "no way to enter/
change" UX gap without duplicating the transfer logic.
- Verified the existing row-owner rendering already uses OwnerLink,
so the row-click affordance was already in place.
tsc clean. 1419/1419 vitest pass.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -7,6 +7,7 @@ import { Plus, X } from 'lucide-react';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { Label } from '@/components/ui/label';
|
||||
import { FieldLabel } from '@/components/ui/field-label';
|
||||
import { Switch } from '@/components/ui/switch';
|
||||
import {
|
||||
Dialog,
|
||||
@@ -297,7 +298,12 @@ function CustomFieldFormBody({ open, onOpenChange, field, onSuccess }: CustomFie
|
||||
|
||||
{/* Sort Order */}
|
||||
<div className="space-y-1.5">
|
||||
<Label htmlFor="cf-sort-order">Sort Order</Label>
|
||||
<FieldLabel
|
||||
htmlFor="cf-sort-order"
|
||||
tooltip="Lower numbers render first when the field set is shown on entity detail pages. Use to pin frequently-edited fields to the top."
|
||||
>
|
||||
Sort Order
|
||||
</FieldLabel>
|
||||
<Input
|
||||
id="cf-sort-order"
|
||||
type="number"
|
||||
|
||||
@@ -97,7 +97,7 @@ export function InterestDocumentsTab({ interestId }: InterestDocumentsTabProps)
|
||||
<div className="space-y-8">
|
||||
<section className="space-y-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<h3 className="text-sm font-medium text-muted-foreground">Legal documents</h3>
|
||||
<h3 className="text-sm font-medium text-muted-foreground">Signature documents</h3>
|
||||
<Button size="sm" variant="outline" onClick={() => setEoiDialogOpen(true)}>
|
||||
Generate EOI
|
||||
</Button>
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
'use client';
|
||||
|
||||
import { useState } from 'react';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { useParams } from 'next/navigation';
|
||||
import { Loader2 } from 'lucide-react';
|
||||
import { Loader2, ArrowLeftRight } from 'lucide-react';
|
||||
|
||||
import { Button } from '@/components/ui/button';
|
||||
import {
|
||||
Table,
|
||||
TableBody,
|
||||
@@ -15,6 +17,8 @@ import {
|
||||
import { Badge } from '@/components/ui/badge';
|
||||
import { EmptyState } from '@/components/shared/empty-state';
|
||||
import { OwnerLink } from '@/components/yachts/yacht-detail-header';
|
||||
import { YachtTransferDialog } from '@/components/yachts/yacht-transfer-dialog';
|
||||
import { PermissionGate } from '@/components/shared/permission-gate';
|
||||
import { apiFetch } from '@/lib/api/client';
|
||||
|
||||
interface OwnershipHistoryRow {
|
||||
@@ -52,6 +56,7 @@ function formatDate(value: string | null): string {
|
||||
export function YachtOwnershipHistory({ yachtId }: YachtOwnershipHistoryProps) {
|
||||
const params = useParams<{ portSlug: string }>();
|
||||
const portSlug = params?.portSlug ?? '';
|
||||
const [transferOpen, setTransferOpen] = useState(false);
|
||||
|
||||
const { data, isLoading } = useQuery<OwnershipHistoryRow[]>({
|
||||
queryKey: ['yachts', yachtId, 'ownership-history'],
|
||||
@@ -61,6 +66,11 @@ export function YachtOwnershipHistory({ yachtId }: YachtOwnershipHistoryProps) {
|
||||
),
|
||||
});
|
||||
|
||||
const currentOwnerRow = data?.find((r) => r.endDate === null);
|
||||
const currentOwner = currentOwnerRow
|
||||
? { type: currentOwnerRow.ownerType, id: currentOwnerRow.ownerId }
|
||||
: undefined;
|
||||
|
||||
if (isLoading) {
|
||||
return (
|
||||
<div className="flex items-center justify-center py-12">
|
||||
@@ -71,53 +81,78 @@ export function YachtOwnershipHistory({ yachtId }: YachtOwnershipHistoryProps) {
|
||||
|
||||
if (!data || data.length === 0) {
|
||||
return (
|
||||
<EmptyState
|
||||
title="No ownership history"
|
||||
description="This yacht's ownership transfers will appear here."
|
||||
/>
|
||||
<>
|
||||
<EmptyState
|
||||
title="No ownership history"
|
||||
description="This yacht's ownership transfers will appear here."
|
||||
action={{ label: 'Transfer ownership', onClick: () => setTransferOpen(true) }}
|
||||
/>
|
||||
<YachtTransferDialog
|
||||
open={transferOpen}
|
||||
onOpenChange={setTransferOpen}
|
||||
yachtId={yachtId}
|
||||
currentOwner={currentOwner}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="rounded-md border">
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead>Start Date</TableHead>
|
||||
<TableHead>End Date</TableHead>
|
||||
<TableHead>Owner</TableHead>
|
||||
<TableHead>Reason</TableHead>
|
||||
<TableHead>Notes</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{data.map((row) => (
|
||||
<TableRow key={row.id}>
|
||||
<TableCell>{formatDate(row.startDate)}</TableCell>
|
||||
<TableCell>
|
||||
{row.endDate ? (
|
||||
formatDate(row.endDate)
|
||||
) : (
|
||||
<Badge variant="secondary" className="text-xs">
|
||||
Current
|
||||
</Badge>
|
||||
)}
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<OwnerLink portSlug={portSlug} type={row.ownerType} id={row.ownerId} />
|
||||
</TableCell>
|
||||
<TableCell className="text-sm text-muted-foreground">
|
||||
{row.transferReason
|
||||
? (REASON_LABELS[row.transferReason] ?? row.transferReason)
|
||||
: '-'}
|
||||
</TableCell>
|
||||
<TableCell className="text-sm text-muted-foreground max-w-[320px] truncate">
|
||||
{row.transferNotes ?? '-'}
|
||||
</TableCell>
|
||||
<div className="space-y-3">
|
||||
<div className="flex items-center justify-end">
|
||||
<PermissionGate resource="yachts" action="edit">
|
||||
<Button size="sm" onClick={() => setTransferOpen(true)} className="gap-1.5">
|
||||
<ArrowLeftRight className="size-4" aria-hidden />
|
||||
Transfer ownership
|
||||
</Button>
|
||||
</PermissionGate>
|
||||
</div>
|
||||
<div className="rounded-md border">
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead>Start Date</TableHead>
|
||||
<TableHead>End Date</TableHead>
|
||||
<TableHead>Owner</TableHead>
|
||||
<TableHead>Reason</TableHead>
|
||||
<TableHead>Notes</TableHead>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{data.map((row) => (
|
||||
<TableRow key={row.id}>
|
||||
<TableCell>{formatDate(row.startDate)}</TableCell>
|
||||
<TableCell>
|
||||
{row.endDate ? (
|
||||
formatDate(row.endDate)
|
||||
) : (
|
||||
<Badge variant="secondary" className="text-xs">
|
||||
Current
|
||||
</Badge>
|
||||
)}
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<OwnerLink portSlug={portSlug} type={row.ownerType} id={row.ownerId} />
|
||||
</TableCell>
|
||||
<TableCell className="text-sm text-muted-foreground">
|
||||
{row.transferReason
|
||||
? (REASON_LABELS[row.transferReason] ?? row.transferReason)
|
||||
: '-'}
|
||||
</TableCell>
|
||||
<TableCell className="text-sm text-muted-foreground max-w-[320px] truncate">
|
||||
{row.transferNotes ?? '-'}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</div>
|
||||
<YachtTransferDialog
|
||||
open={transferOpen}
|
||||
onOpenChange={setTransferOpen}
|
||||
yachtId={yachtId}
|
||||
currentOwner={currentOwner}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user