# Content Overview — Design

- **Date**: 2026-06-06
- **Status**: approved (design)
- **Section**: contenu / overview
- **Depends on**: content planning (`content_items` + `ContentItem`/`getContentItemsForSite`) on `dev`.

## Goal

A per-site **content dashboard** mirroring the keyword Overview, aggregating the `content_items` produced on the Planning board: how many articles, where they are in the pipeline, what's overdue, and what's due next. Real data only.

## Core principles

- **Active site** (client `useActiveSite`).
- Pure, tested aggregator `buildContentOverview(items, nowIso)`.
- **Reuse** the existing `StatCard` + `BarList` primitives (`src/components/keywords/overview/`) — they're generic presentational units.
- Project design system (shadcn), consistent with the keyword Overview.

## Layout

```
[Total articles][Publiés][En cours][En retard][Avec brief]   ← 5 KPI cards
┌ Pipeline (par statut) ─────────────┐ ┌ Par assigné ───────┐
│ Idée ▓▓▓ Brief ▓ … Publié ▓        │ │ @marie ▓▓ …         │
└────────────────────────────────────┘ └────────────────────┘
┌ Prochaines échéances ───────────────────────────────────────┐
│ titre · statut · échéance (rouge si en retard)               │
└──────────────────────────────────────────────────────────────┘
```

## Data model — the aggregate

`src/lib/content/overview.ts`:
```ts
import type { ContentStatus } from "@/lib/db/domain-schema";
import type { ContentItem } from "./types";

export interface CountBy<K> { key: K; count: number }
export interface UpcomingRow {
  id: string; title: string; status: ContentStatus;
  dueDate: string; overdue: boolean;
}
export interface ContentOverview {
  total: number;
  byStatus: CountBy<ContentStatus>[]; // all 5 in CONTENT_STATUSES order
  published: number;                  // status === "published"
  inProgress: number;                 // writing + review
  overdue: number;                    // isOverdue(dueDate) && status !== published
  withBrief: number;                  // hasBrief
  unlinked: number;                   // primaryKeywordId === null
  byAssignee: CountBy<string>[];      // "Non assigné" for null, desc by count, top 6
  upcoming: UpcomingRow[];            // items with a dueDate, soonest first, top 8
}
```

`buildContentOverview(items: ContentItem[], nowIso: string): ContentOverview` — pure. Reuses `isOverdue` from `./board` and `CONTENT_STATUSES` from `./types`.

## Flow

`getContentOverviewAction(siteId)` (server action, in `src/lib/content/actions.ts`): `getContentItemsForSite(siteId)` → `buildContentOverview(items, new Date().toISOString())`.

`ContentOverviewContainer` (client): `useActiveSite` → load the action on `activeSiteId` → render modules; loading skeleton; empty-state when `total === 0` (CTA to the Planning board).

## Components / files

```
src/lib/content/overview.ts          — ContentOverview + buildContentOverview (pure)
src/lib/content/overview.test.ts     — unit tests
src/lib/content/actions.ts           — add getContentOverviewAction
src/components/content/overview/content-overview-container.tsx — client loader + grid
src/components/content/overview/content-overview-kpis.tsx       — 5 StatCards
src/components/content/overview/content-status-distribution.tsx — BarList by status
src/components/content/overview/content-by-assignee.tsx         — BarList by assignee
src/components/content/overview/content-upcoming.tsx            — upcoming-deadlines list
src/app/(app)/content/overview/page.tsx                         — header + container
src/components/app-sidebar.tsx                                  — add "Overview" to the Contenu section (before Planning)
```

`StatCard` and `BarList` are imported from `@/components/keywords/overview/...` (generic primitives — reused, not duplicated).

## Error / empty states

- No content items → empty-state with a CTA linking to `/content/planning`.
- Action failure → non-blocking (fire-and-forget); skeleton clears.

## Testing

- **Unit** (`overview.test.ts`, pure): `buildContentOverview` over a fixture — total, byStatus (5 keys in order), published/inProgress/overdue (status + due interplay), withBrief, unlinked, byAssignee ("Non assigné" bucket + ordering), upcoming (only dated, soonest-first, overdue flag).
- **Manual (d3k)**: a site with content items → KPIs/pipeline/assignee/upcoming render and match; empty-state on a site with no content; `d3k errors` clean; the Contenu nav has Overview + Planning.

## Non-goals (YAGNI)

- Time-series / trends (no history).
- The other contenu sub-pages (online / stats / import-export).
- Charts beyond CSS/flex bars.
