fix(F17 ext): apply DetailNotFound to clients/yachts/companies/berths
All checks were successful
Build & Push Docker Images / lint (push) Successful in 2m12s
Build & Push Docker Images / build-and-push (push) Successful in 4m44s

Refactored the interest-detail 404 pattern into a reusable
`<DetailNotFound>` component and applied it to the four other entity
detail pages. Pre-fix, navigating to a wrong-port or stale entity URL
silently rendered the layout shell with empty tabs on:

  - /[portSlug]/clients/[id]
  - /[portSlug]/yachts/[id]
  - /[portSlug]/companies/[id]
  - /[portSlug]/berths/[id]

All four now route a 404/403 response into an explicit "<Entity> not
found" / "No access" EmptyState with a back-to-list CTA, and the
TanStack Query retry policy short-circuits 404/403s so the empty state
appears immediately.

1373/1373 vitest pass.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-14 23:06:36 +02:00
parent 608641c23b
commit 3e78c2d4ab
6 changed files with 142 additions and 27 deletions

View File

@@ -1,10 +1,11 @@
'use client';
import { useEffect, useState } from 'react';
import { useSearchParams, useRouter } from 'next/navigation';
import { useSearchParams, useRouter, useParams } from 'next/navigation';
import { useQuery } from '@tanstack/react-query';
import { DetailLayout } from '@/components/shared/detail-layout';
import { DetailNotFound } from '@/components/shared/detail-not-found';
import { useMobileChrome } from '@/components/layout/mobile/mobile-layout-provider';
import { apiFetch } from '@/lib/api/client';
import { useRealtimeInvalidation } from '@/hooks/use-realtime-invalidation';
@@ -17,10 +18,18 @@ interface BerthDetailProps {
}
export function BerthDetail({ berthId }: BerthDetailProps) {
const { data, isLoading } = useQuery<BerthDetailData>({
const params = useParams<{ portSlug: string }>();
const portSlug = params?.portSlug ?? '';
const { data, isLoading, error } = useQuery<BerthDetailData>({
queryKey: ['berth', berthId],
queryFn: () =>
apiFetch<{ data: BerthDetailData }>(`/api/v1/berths/${berthId}`).then((r) => r.data),
retry: (failureCount, err) => {
const status = (err as { status?: number } | null | undefined)?.status;
if (status === 404 || status === 403) return false;
return failureCount < 2;
},
});
useRealtimeInvalidation({
@@ -56,6 +65,18 @@ export function BerthDetail({ berthId }: BerthDetailProps) {
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [searchParams]);
if (error && !isLoading) {
const status = (error as { status?: number } | null | undefined)?.status;
return (
<DetailNotFound
entity="berth"
backHref={`/${portSlug}/berths`}
backLabel="Back to berths"
status={status}
/>
);
}
const berth = data;
return (