Server actions (src/app/admin/carbets/actions.ts) avec validation Zod : - createCarbetAction → INSERT + audit + redirect /admin/carbets/[id] - updateCarbetAction → UPDATE + revalidate page publique - updateCarbetStatusAction → DRAFT/PUBLISHED/ARCHIVED - deleteCarbetAction → soft archive (bookings/reviews FK Restrict) - addMediaAction(carbetId, fd) → INSERT Media + sortOrder - removeMediaAction, reorderMediaAction (transactionnel up/down) Helpers (src/lib/admin/carbets.ts) : - listCarbetsAdmin avec filtres (q/river/status/accessType) - listDistinctRivers, listOwners, listPirogueProviders - getCarbetForEdit (include owner, provider, media, _count bookings/reviews) - Options enum pour les selects (ACCESS_TYPE, TRANSPORT_MODE, STATUS) Pages : - /admin/carbets : liste tableau dense avec recherche/filtres GET, status badge, liens vers édition, count médias/résas - /admin/carbets/new : page création avec CarbetForm - /admin/carbets/[id] : header titre+badge+actions, MediaManager, CarbetForm d'édition. Lien public si PUBLISHED. Composants admin réutilisables : - StatusBadge (DRAFT/PUBLISHED/ARCHIVED + statuts Booking) - FormField + inputCls/selectCls/textareaCls - CarbetForm (client, 5 sections : identité, localisation, accès, séjour, publication) avec useTransition + erreur + succès inline - MediaManager (client, liste + reorder ↑↓ + suppression + ajout par URL) - StatusActions (client, publier/dépublier/archiver/réactiver avec confirm) API : - GET /api/admin/carbets/[id]/media pour refresh client après mutation Audit léger en log console (JSON structuré) — Sprint 5 ajoutera la table.
31 lines
1 KiB
TypeScript
31 lines
1 KiB
TypeScript
const TONES = {
|
|
draft: "bg-zinc-100 text-zinc-700 ring-zinc-300",
|
|
published: "bg-emerald-100 text-emerald-800 ring-emerald-300",
|
|
archived: "bg-amber-100 text-amber-800 ring-amber-300",
|
|
pending: "bg-sky-100 text-sky-800 ring-sky-300",
|
|
confirmed: "bg-emerald-100 text-emerald-800 ring-emerald-300",
|
|
cancelled: "bg-rose-100 text-rose-700 ring-rose-300",
|
|
completed: "bg-zinc-100 text-zinc-700 ring-zinc-300",
|
|
} as const;
|
|
|
|
const LABELS: Record<string, string> = {
|
|
DRAFT: "Brouillon",
|
|
PUBLISHED: "Publié",
|
|
ARCHIVED: "Archivé",
|
|
PENDING: "En attente",
|
|
CONFIRMED: "Confirmé",
|
|
CANCELLED: "Annulé",
|
|
COMPLETED: "Terminé",
|
|
};
|
|
|
|
export function StatusBadge({ status }: { status: string }) {
|
|
const key = status.toLowerCase() as keyof typeof TONES;
|
|
const tone = TONES[key] ?? TONES.draft;
|
|
return (
|
|
<span
|
|
className={`inline-flex items-center gap-1 rounded-full px-2 py-0.5 text-[11px] font-semibold uppercase tracking-wider ring-1 ring-inset ${tone}`}
|
|
>
|
|
{LABELS[status] ?? status}
|
|
</span>
|
|
);
|
|
}
|