feat(tenancies-p6-followup): generic create dialog + edit dialog + self-FKs
- Migration 0086: berth_tenancies.previous_tenancy_id + transferred_from_tenancy_id self-FKs + partial indexes. Per docs/tenancies-design.md these chain renewal / transfer successors to predecessors for fixed-term and seasonal lineage. Schema mirrored in tenancies.ts with AnyPgColumn typed-import. - POST /api/v1/tenancies (generic create): accepts berthId in the body so client + yacht tab entry points don't have to bounce through /api/v1/berths/[id]/tenancies. Same createPending service helper. - TenancyCreateDialog: <TenancyCreateDialog open clientId? yachtId? berthId? /> with all three pickers; pre-fills the carrier from the parent entity. POSTs to /api/v1/tenancies; "Create" and "Create and activate" CTAs both wire to the new endpoint. - Mounted on ClientTenanciesTab + YachtTenanciesTab behind <PermissionGate resource="tenancies" action="manage"> so reps can mint tenancies directly from those tabs without bouncing through the berth page. - TenancyEditDialog: edit metadata only (start/end dates, tenure type, notes) via the new action='update' branch on the [id] PATCH route. Status transitions stay on activate/end/cancel. Wired into the tenancy detail page header. Outer wrapper unmounts on close so the form re-initialises from current row data without setState-in-effect. - updateTenancy service helper + PATCH action='update' branch added. Audit-logged + emits berth_tenancy:activated to invalidate detail query caches. Renew + Transfer dialogs deferred — both need lineage UX decisions (tenure-aware mutate-in-place vs new-row spawn; client/yacht swap semantics) and the self-FK columns this commit lands are the underpinning. Next sub-task. Verified: tsc clean, 1493/1493 vitest, migration applied. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,9 +1,14 @@
|
||||
'use client';
|
||||
|
||||
import { useState } from 'react';
|
||||
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
|
||||
import { useParams } from 'next/navigation';
|
||||
import { Plus } from 'lucide-react';
|
||||
|
||||
import type { DetailTab } from '@/components/shared/detail-layout';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { PermissionGate } from '@/components/shared/permission-gate';
|
||||
import { TenancyCreateDialog } from '@/components/tenancies/tenancy-create-dialog';
|
||||
import { InlineEditableField } from '@/components/shared/inline-editable-field';
|
||||
import { FieldHistoryProvider, FieldHistoryIcon } from '@/components/shared/field-history';
|
||||
import { InlineTagEditor } from '@/components/shared/inline-tag-editor';
|
||||
@@ -337,6 +342,7 @@ function YachtInterestsTab({ yachtId }: { yachtId: string }) {
|
||||
function YachtTenanciesTab({ yachtId }: { yachtId: string }) {
|
||||
const routeParams = useParams<{ portSlug: string }>();
|
||||
const portSlug = routeParams?.portSlug ?? '';
|
||||
const [createOpen, setCreateOpen] = useState(false);
|
||||
|
||||
const { data, isLoading } = useQuery<{ data: TenancyRow[] }>({
|
||||
queryKey: ['tenancies', 'by-yacht', yachtId],
|
||||
@@ -346,12 +352,23 @@ function YachtTenanciesTab({ yachtId }: { yachtId: string }) {
|
||||
if (isLoading) return <p className="text-sm text-muted-foreground">Loading…</p>;
|
||||
|
||||
return (
|
||||
<TenancyList
|
||||
tenancies={data?.data ?? []}
|
||||
showBerth
|
||||
portSlug={portSlug}
|
||||
emptyMessage="No tenancies for this yacht."
|
||||
/>
|
||||
<div className="space-y-4">
|
||||
<div className="flex items-center justify-end">
|
||||
<PermissionGate resource="tenancies" action="manage">
|
||||
<Button size="sm" onClick={() => setCreateOpen(true)}>
|
||||
<Plus className="mr-1.5 h-4 w-4" aria-hidden />
|
||||
Create tenancy
|
||||
</Button>
|
||||
</PermissionGate>
|
||||
</div>
|
||||
<TenancyList
|
||||
tenancies={data?.data ?? []}
|
||||
showBerth
|
||||
portSlug={portSlug}
|
||||
emptyMessage="No tenancies for this yacht."
|
||||
/>
|
||||
<TenancyCreateDialog open={createOpen} onOpenChange={setCreateOpen} yachtId={yachtId} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user