diff --git a/app/(dashboard)/e/[sector]/[slug]/loading.tsx b/app/(dashboard)/e/[sector]/[slug]/loading.tsx index 929bde24..957208c6 100644 --- a/app/(dashboard)/e/[sector]/[slug]/loading.tsx +++ b/app/(dashboard)/e/[sector]/[slug]/loading.tsx @@ -1,25 +1,29 @@ -import { headers } from 'next/headers' +'use client' + +import { usePathname } from 'next/navigation' import { Skeleton } from '@/components/ui/skeleton' import { Card, CardHeader, CardContent } from '@/components/ui/card' import { PageHeader } from '@/components/ui/page-header' import { getExtensionDefinition } from '@/lib/extensions/sectors' +import InvoiceInboxSkeleton from '@/components/extensions/general/InvoiceInboxSkeleton' // Mirror of FULLSCREEN_WORKSPACES in ExtensionWorkspaceLoader. loading.tsx -// can't read route params, so we inspect the forwarded x-pathname header to -// branch the skeleton shape: the parent dashboard loading.tsx renders a -// metrics dashboard shape that has nothing to do with extension workspaces. +// can't read route params, so we branch on the client pathname: the parent +// dashboard loading.tsx renders a metrics dashboard shape that has nothing +// to do with extension workspaces. (This must stay a client component: the +// x-pathname header middleware sets goes on the response, so headers() in a +// server loading file never sees it and the fullscreen branch went dead.) const FULLSCREEN_WORKSPACES = new Set(['general/invoice-inbox']) -export default async function ExtensionWorkspaceLoading() { - const h = await headers() - const pathname = h.get('x-pathname') ?? '' +export default function ExtensionWorkspaceLoading() { + const pathname = usePathname() const match = pathname.match(/^\/e\/([^/]+)\/([^/]+)/) const sector = match?.[1] ?? '' const slug = match?.[2] ?? '' const key = `${sector}/${slug}` if (FULLSCREEN_WORKSPACES.has(key)) { - return + return } const definition = sector && slug ? getExtensionDefinition(sector, slug) : undefined @@ -101,49 +105,3 @@ function TicSkeleton() { ) } - -function FullScreenWorkspaceSkeleton() { - return ( -
-
-
-
- - - -
- -
-
- -
-
-
-
- ) -} diff --git a/components/extensions/general/InvoiceInboxSkeleton.tsx b/components/extensions/general/InvoiceInboxSkeleton.tsx new file mode 100644 index 00000000..3ce9a5a3 --- /dev/null +++ b/components/extensions/general/InvoiceInboxSkeleton.tsx @@ -0,0 +1,48 @@ +import { Skeleton } from '@/components/ui/skeleton' + +// Single source of truth for the Dokumentinkorg loading silhouette, rendered +// by both the route-level loading.tsx and the workspace's own client-fetch +// state so the route fallback, the fetch shell, and the loaded UI are one +// shape with no reflow. Mirrors the live layout: full-bleed top bar + +// 3-pane grid, list pane with search + filter-dropdown trigger (PR #1524 +// replaced the old filter-pill row with a single full-width dropdown). +export default function InvoiceInboxSkeleton() { + return ( +
+
+
+
+ + + +
+ +
+
+ +
+
+
+
+ ) +} diff --git a/components/extensions/general/InvoiceInboxWorkspace.tsx b/components/extensions/general/InvoiceInboxWorkspace.tsx index 5e084901..9feed7d8 100644 --- a/components/extensions/general/InvoiceInboxWorkspace.tsx +++ b/components/extensions/general/InvoiceInboxWorkspace.tsx @@ -52,6 +52,7 @@ import { cn, formatCurrency, formatDate, formatDateLong } from '@/lib/utils' import { QUIET_LINK_CLASS } from '@/components/ui/dry-table' import { GoogleMark, MicrosoftMark } from '@/components/ui/provider-marks' import EditKonteringDialog from '@/components/extensions/general/EditKonteringDialog' +import InvoiceInboxSkeleton from '@/components/extensions/general/InvoiceInboxSkeleton' import { WhatsAppMark } from '@/components/extensions/general/WhatsAppMark' import { useReceiptHunt } from '@/components/extensions/general/use-receipt-hunt' import { createClient } from '@/lib/supabase/client' @@ -302,55 +303,10 @@ function deriveInboxStatus(item: InboxItem): InboxStatus { } // ── Skeleton ───────────────────────────────────────────────── -// Mirrors the live layout (top bar + 3-pane card) so the transition from -// the route-level loading.tsx to data-loaded content has no visible reflow. -// Keep in sync with app/(dashboard)/e/[sector]/[slug]/loading.tsx. +// Shared with app/(dashboard)/e/[sector]/[slug]/loading.tsx so the route +// fallback and this client-fetch shell are one silhouette with no reflow. -function WorkspaceSkeleton() { - return ( -
-
-
-
- - - -
- -
-
- -
-
-
-
- ) -} +const WorkspaceSkeleton = InvoiceInboxSkeleton // ── Main component ───────────────────────────────────────────