# Pattern — `{Entity}DetailPage.tsx`

## When to use

The page that renders a single entity's full state (read view). URL contains
a `:id` param. Has an "Edit" CTA that navigates to the Form page. May expose
a sidebar slot for related data (activity timeline, relations tab, etc.).

## Anatomy

```
PermissionGuard (read)
  PageTemplate (title = entity label, icon, actions = Edit CTA)
    Slot             — {entity}.detail.header (context={data})
    card section     — dl grid with every field (label + value)
    Slot             — {entity}.detail.sidebar (context={data})
```

## Canonical example (compliant)

```tsx
import { useParams, useNavigate, Navigate } from 'react-router-dom';
import { useTranslation } from 'react-i18next';
import { Loader2, Pencil, FileText } from 'lucide-react';
import { PermissionGuard, Slot } from '@atlashub/smartstack';
import { PageTemplate } from '@/components/ui/PageTemplate';
import { useContact } from '../hooks/useContact';

export function ContactDetailPage() {
  const { id } = useParams<{ id: string }>();
  const navigate = useNavigate();
  const { t } = useTranslation('contact');

  if (!id) return <Navigate to=".." replace />;  // R9 — null-check

  const { data, isLoading } = useContact(id);

  if (isLoading) {
    return (
      <div className="flex items-center justify-center py-12 text-[var(--text-muted)]">
        <Loader2 className="w-5 h-5 animate-spin mr-2" />
        {t('detail.loading')}
      </div>
    );
  }
  if (!data) {
    return (
      <div className="p-4 rounded-[var(--radius-card)] bg-[var(--warning-bg)] border border-[var(--warning-border)] text-[var(--warning-text)]">
        {t('detail.notFound')}
      </div>
    );
  }

  const headerTitle = `${data.firstName ?? ''} ${data.lastName}`.trim();

  return (
    <PermissionGuard permission="crm.contacts.read">
      <PageTemplate
        title={headerTitle}
        icon={<FileText className="w-6 h-6" />}
        actions={
          <PermissionGuard permission="crm.contacts.update">
            <button
              type="button"
              onClick={() => navigate('./edit')}
              className="flex items-center gap-1.5 px-3 py-2 rounded-[var(--radius-button)] bg-[var(--color-accent-500)] text-white text-sm font-medium hover:bg-[var(--color-accent-600)] transition-colors"
            >
              <Pencil className="w-4 h-4" />
              {t('detail.edit')}
            </button>
          </PermissionGuard>
        }
      >
        <Slot name="contact.detail.header" context={{ data }} />

        <div className="p-6 rounded-[var(--radius-card)] border border-[var(--border-color)] bg-[var(--bg-card)]">
          <dl className="grid grid-cols-1 md:grid-cols-2 gap-x-6 gap-y-4">
            <div>
              <dt className="text-xs font-semibold uppercase tracking-wider text-[var(--text-muted)] mb-1">
                {t('detail.fields.lastName')}
              </dt>
              <dd className="text-sm text-[var(--text-primary)]">{data.lastName}</dd>
            </div>
            <div>
              <dt className="text-xs font-semibold uppercase tracking-wider text-[var(--text-muted)] mb-1">
                {t('detail.fields.email')}
              </dt>
              <dd className="text-sm text-[var(--text-primary)]">{data.email ?? '—'}</dd>
            </div>
            {/* one <div> per field */}
          </dl>
        </div>

        <Slot name="contact.detail.sidebar" context={{ data }} />
      </PageTemplate>
    </PermissionGuard>
  );
}
```

## Rules this pattern enforces

- **R1** — `<PageTemplate>`
- **R3** — primary CTA Edit → `bg-[var(--color-accent-500)]`
- **R4** — `useTranslation('entity')` + `t('detail.fields.<name>')` for every field label
- **R5** — lucide-react icons only
- **R6** — `<Loader2 className="animate-spin" />` for loading
- **R9** — `useParams<{ id: string }>()` is null-checked (`if (!id) return <Navigate …>`)
- **R10** — permission key format `{module}.{section}.{action}`
- **R11** — two Slot extension points: `{entity}.detail.header` and `{entity}.detail.sidebar`
- **R16** — radius tokens (`rounded-[var(--radius-card|button)]`)
- **R19** — no palette / absolute color classes
- **R20** — border always paired with `border-[var(--border-color)]`

## Import contract

| Name | From |
|---|---|
| `PermissionGuard`, `Slot` | `@atlashub/smartstack` |
| `PageTemplate` | `@/components/ui/PageTemplate` |
| Icons | `lucide-react` |
| `useTranslation` | `react-i18next` |
| `useParams`, `useNavigate`, `Navigate` | `react-router-dom` |

## Do NOT

- Skip the `if (!id) return <Navigate …>` guard — breaks R9 + crashes when route
  lands on `/entity/` without id.
- Render fields via hardcoded labels — every field label must be `t('detail.fields.<key>')`.
- Put the Edit button outside `<PermissionGuard permission="*.update">`.
- Render `data.lastName` as a raw string unprotected — always fall back via
  `data.email ?? '—'` for nullable fields to avoid "undefined" in the UI.
- Use `bg-white` for the card → `bg-[var(--bg-card)]`.
