From 99f3bbdc7135f5296a03ebe8468adfb026cb56fc Mon Sep 17 00:00:00 2001 From: Claude Integration Date: Sun, 31 May 2026 21:36:22 +0000 Subject: [PATCH] =?UTF-8?q?feat(admin):=20Sprint=204=20=E2=80=94=20Organis?= =?UTF-8?q?ations=20CE=20+=20Prestataires=20pirogue=20(CRUD)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../[id]/_components/DeleteOrgButton.tsx | 71 ++++++++++ src/app/admin/organizations/[id]/page.tsx | 90 +++++++++++++ .../organizations/_components/OrgForm.tsx | 77 +++++++++++ src/app/admin/organizations/actions.ts | 88 +++++++++++++ src/app/admin/organizations/new/page.tsx | 21 +++ src/app/admin/organizations/page.tsx | 89 +++++++++++++ .../_components/ProviderInlineActions.tsx | 98 ++++++++++++++ src/app/admin/pirogue-providers/[id]/page.tsx | 105 +++++++++++++++ .../_components/ProviderForm.tsx | 119 +++++++++++++++++ src/app/admin/pirogue-providers/actions.ts | 94 +++++++++++++ src/app/admin/pirogue-providers/new/page.tsx | 21 +++ src/app/admin/pirogue-providers/page.tsx | 124 ++++++++++++++++++ src/lib/admin/organizations.ts | 60 +++++++++ src/lib/admin/pirogue-providers.ts | 84 ++++++++++++ 14 files changed, 1141 insertions(+) create mode 100644 src/app/admin/organizations/[id]/_components/DeleteOrgButton.tsx create mode 100644 src/app/admin/organizations/[id]/page.tsx create mode 100644 src/app/admin/organizations/_components/OrgForm.tsx create mode 100644 src/app/admin/organizations/actions.ts create mode 100644 src/app/admin/organizations/new/page.tsx create mode 100644 src/app/admin/organizations/page.tsx create mode 100644 src/app/admin/pirogue-providers/[id]/_components/ProviderInlineActions.tsx create mode 100644 src/app/admin/pirogue-providers/[id]/page.tsx create mode 100644 src/app/admin/pirogue-providers/_components/ProviderForm.tsx create mode 100644 src/app/admin/pirogue-providers/actions.ts create mode 100644 src/app/admin/pirogue-providers/new/page.tsx create mode 100644 src/app/admin/pirogue-providers/page.tsx create mode 100644 src/lib/admin/organizations.ts create mode 100644 src/lib/admin/pirogue-providers.ts diff --git a/src/app/admin/organizations/[id]/_components/DeleteOrgButton.tsx b/src/app/admin/organizations/[id]/_components/DeleteOrgButton.tsx new file mode 100644 index 0000000..4b54f5b --- /dev/null +++ b/src/app/admin/organizations/[id]/_components/DeleteOrgButton.tsx @@ -0,0 +1,71 @@ +"use client"; + +import { useState, useTransition } from "react"; + +type Props = { + action: () => Promise<{ ok: false; error: string } | { ok: true } | undefined | void>; + memberCount: number; +}; + +export function DeleteOrgButton({ action, memberCount }: Props) { + const [pending, startTransition] = useTransition(); + const [confirm, setConfirm] = useState(false); + const [error, setError] = useState(null); + + function run() { + setError(null); + startTransition(async () => { + const res = await action(); + if (res && (res as { ok?: boolean }).ok === false) { + setError((res as { error: string }).error); + setConfirm(false); + } + }); + } + + if (memberCount > 0) { + return ( + + Suppression impossible — {memberCount} membre{memberCount > 1 ? "s" : ""} rattaché{memberCount > 1 ? "s" : ""} + + ); + } + + return ( +
+ {confirm ? ( +
+ Supprimer définitivement ? + + +
+ ) : ( + + )} + {error ? ( +
{error}
+ ) : null} +
+ ); +} diff --git a/src/app/admin/organizations/[id]/page.tsx b/src/app/admin/organizations/[id]/page.tsx new file mode 100644 index 0000000..90c91b6 --- /dev/null +++ b/src/app/admin/organizations/[id]/page.tsx @@ -0,0 +1,90 @@ +import { notFound } from "next/navigation"; +import Link from "next/link"; +import { getOrganizationForAdmin } from "@/lib/admin/organizations"; +import { OrgForm } from "../_components/OrgForm"; +import { StatusBadge } from "@/components/admin/StatusBadge"; +import { deleteOrganizationAction, updateOrganizationAction } from "../actions"; +import { DeleteOrgButton } from "./_components/DeleteOrgButton"; + +export const dynamic = "force-dynamic"; + +const ROLE_LABEL: Record = { + OWNER: "Propriétaire", + CE_MANAGER: "CE — Manager", + CE_MEMBER: "CE — Membre", + TOURIST: "Touriste", + ADMIN: "Admin", +}; + +type PageProps = { params: Promise<{ id: string }> }; + +export default async function EditOrgPage({ params }: PageProps) { + const { id } = await params; + const org = await getOrganizationForAdmin(id); + if (!org) notFound(); + + const updateThis = async (fd: FormData) => { + "use server"; + return await updateOrganizationAction(id, fd); + }; + const deleteThis = async () => { + "use server"; + return await deleteOrganizationAction(id); + }; + + return ( +
+
+
+ + ← Toutes les organisations + +

{org.name}

+

+ /{org.slug} · {org.members.length} membre{org.members.length > 1 ? "s" : ""} +

+
+ +
+ +
+

Identité

+ +
+ +
+

+ Membres ({org.members.length}) +

+ {org.members.length === 0 ? ( +

+ Aucun membre. Rattachez un utilisateur via{" "} + + la page Utilisateurs + + . +

+ ) : ( +
    + {org.members.map((m) => ( +
  • + + {m.firstName} {m.lastName} + {m.email} + + + {ROLE_LABEL[m.role] ?? m.role} + + +
  • + ))} +
+ )} +
+
+ ); +} diff --git a/src/app/admin/organizations/_components/OrgForm.tsx b/src/app/admin/organizations/_components/OrgForm.tsx new file mode 100644 index 0000000..be0f724 --- /dev/null +++ b/src/app/admin/organizations/_components/OrgForm.tsx @@ -0,0 +1,77 @@ +"use client"; + +import { useState, useTransition } from "react"; +import { FormField, inputCls, textareaCls } from "@/components/admin/FormField"; + +type Props = { + initial?: { + name?: string; + slug?: string; + description?: string | null; + }; + action: (fd: FormData) => Promise<{ ok: false; error: string } | { ok: true } | undefined>; + submitLabel?: string; +}; + +export function OrgForm({ initial = {}, action, submitLabel = "Enregistrer" }: Props) { + const [pending, startTransition] = useTransition(); + const [error, setError] = useState(null); + const [success, setSuccess] = useState(null); + + function onSubmit(formData: FormData) { + setError(null); + setSuccess(null); + startTransition(async () => { + const res = await action(formData); + if (res && res.ok === false) setError(res.error); + else if (res && res.ok === true) setSuccess("Organisation enregistrée."); + }); + } + + return ( +
+
+
+ + + + + + +
+ +