espace-paie-odentas/app/(app)/staff/utilisateurs/page-old.tsx
2025-10-12 17:05:46 +02:00

231 lines
No EOL
10 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// app/(app)/staff/utilisateurs/page.tsx
import { cookies } from "next/headers";
import Link from "next/link";
import ConfirmableForm from "@/components/ConfirmableForm";
import { createSbServer } from "@/lib/supabaseServer";
export const dynamic = "force-dynamic";
export default async function StaffUsersListPage() {
const sb = createSbServer();
const { data: { user } } = await sb.auth.getUser();
// 1) Refus si pas connecté
if (!user) {
return (
<main className="p-6">
<h1 className="text-lg font-semibold">Accès refusé</h1>
<p className="text-sm text-slate-600">Vous devez être connecté.</p>
</main>
);
}
// 2) Vérifier Staff
const { data: me } = await sb
.from("staff_users")
.select("is_staff")
.eq("user_id", user.id)
.maybeSingle();
const isStaff = !!me?.is_staff;
if (!isStaff) {
return (
<main className="p-6">
<h1 className="text-lg font-semibold">Accès refusé</h1>
<p className="text-sm text-slate-600">Cette page est réservée au Staff.</p>
</main>
);
}
// 3) Récupérer lorg active depuis le cookie posé par le switcher
const c = cookies();
const activeOrgId = c.get("active_org_id")?.value;
if (!activeOrgId) {
return (
<main className="p-6 space-y-2">
<h1 className="text-lg font-semibold">Utilisateurs de la structure</h1>
<p className="text-sm text-slate-600">
Aucune structure active. Sélectionnez un client via le sélecteur en haut à droite.
</p>
</main>
);
}
// 4) Appel RPC sécurisé : liste des membres
const { data: members, error } = await sb.rpc("get_org_members_secure", { p_org: activeOrgId });
if (error) {
return (
<main className="p-6">
<h1 className="text-lg font-semibold">Utilisateurs de la structure</h1>
<p className="text-sm text-red-600">Erreur de chargement : {error.message}</p>
</main>
);
}
return (
<main className="p-6 space-y-4">
<div className="flex items-center justify-between">
<h1 className="text-lg font-semibold">Utilisateurs de la structure</h1>
<Link
href="/staff/utilisateurs/nouveau"
className="inline-flex items-center gap-2 px-3 py-2 rounded-lg text-sm bg-emerald-600 text-white hover:bg-emerald-700"
>
+ Créer un utilisateur
</Link>
</div>
<section className="rounded-2xl border bg-white p-4">
<h2 className="text-sm font-semibold mb-2">Niveaux d'habilitation</h2>
<ul className="text-sm leading-6 text-slate-700 list-disc pl-5 space-y-1">
<li>
<span className="font-medium">Super Admin</span> — accès total : gestion des utilisateurs (création, modification de niveau, révocation),
toutes les données (contrats, paies, salarié·es, facturation). Ne peut être modifié que par le support Odentas.
</li>
<li>
<span className="font-medium">Admin</span> — accès total : gestion des utilisateurs (création, modification de niveau, révocation) sauf le Super Admin,
toutes les données (contrats, paies, salarié·es, facturation).
</li>
<li>
<span className="font-medium">Agent</span> — accès opérationnel courant : création et suivi des contrats/paies et consultation salariés.
Pas d'accès à la facturation ni à la gestion des utilisateurs.
</li>
<li>
<span className="font-medium">Compta</span> accès en lecture aux éléments financiers (paies, cotisations, facturation) et documents associés.
Pas de création/modification de contrats ni gestion des utilisateurs.
</li>
</ul>
</section>
<div className="rounded-2xl border bg-white overflow-hidden">
<table className="w-full text-sm">
<thead className="bg-slate-50 text-slate-600">
<tr>
<th className="text-left px-4 py-3">Prénom</th>
<th className="text-left px-4 py-3">Email</th>
<th className="text-left px-4 py-3">Niveau</th>
<th className="text-left px-4 py-3">Créé le</th>
<th className="text-left px-4 py-3">Statut</th>
<th className="text-left px-4 py-3">Actions</th>
</tr>
</thead>
<tbody>
{(members ?? []).length === 0 ? (
<tr>
<td colSpan={6} className="px-4 py-6 text-slate-500">
Aucun utilisateur pour cette structure.
</td>
</tr>
) : (
(members as any[]).map((m) => {
const created = m.created_at ? new Date(m.created_at as string) : null;
const createdFmt = created
? created.toLocaleString("fr-FR", {
year: "numeric",
month: "2-digit",
day: "2-digit",
hour: "2-digit",
minute: "2-digit",
})
: "—";
const status = m.revoked ? "Révoqué" : "Actif";
const disabled = !!m.revoked;
return (
<tr key={m.user_id} className="border-t align-top">
<td className="px-4 py-3 whitespace-nowrap">{m.first_name || "—"}</td>
<td className="px-4 py-3">{m.email}</td>
<td className="px-4 py-3 uppercase tracking-wide text-xs">{m.role || "—"}</td>
<td className="px-4 py-3 whitespace-nowrap">{createdFmt}</td>
<td className="px-4 py-3">{status}</td>
<td className="px-4 py-2">
<div className="flex flex-col gap-2">
<ConfirmableForm
method="post"
action="/api/staff/users/update-role"
className="flex items-center gap-2"
disabled={disabled}
confirmTitle="Confirmer la modification du niveau"
confirmMessage={`Voulez-vous vraiment appliquer ce niveau à ${m.first_name || m.email} ?`}
>
<input type="hidden" name="org_id" value={activeOrgId} />
<input type="hidden" name="user_id" value={m.user_id} />
<select
name="role"
defaultValue={m.role || "ADMIN"}
disabled={disabled}
className="px-2 py-1 rounded border"
>
<option value="SUPER_ADMIN">Super Admin</option>
<option value="ADMIN">Admin</option>
<option value="AGENT">Agent</option>
<option value="COMPTA">Compta</option>
</select>
<button
type="submit"
disabled={disabled}
className="px-3 py-1 rounded bg-slate-900 text-white text-xs hover:bg-slate-700 disabled:opacity-50"
>
Modifier
</button>
</ConfirmableForm>
{!disabled ? (
<ConfirmableForm
method="post"
action="/api/staff/users/revoke"
className="flex items-center gap-2"
disabled={disabled}
confirmTitle="Confirmer la révocation"
confirmMessage={`Voulez-vous révoquer laccès de ${m.first_name || m.email} pour cette structure ?`}
confirmCta="Révoquer"
>
<input type="hidden" name="org_id" value={activeOrgId} />
<input type="hidden" name="user_id" value={m.user_id} />
<button
type="submit"
disabled={disabled}
className="px-3 py-1 rounded text-xs bg-red-600 text-white hover:bg-red-700 disabled:opacity-50"
>
Supprimer
</button>
</ConfirmableForm>
) : (
<ConfirmableForm
method="post"
action="/api/staff/users/unrevoke"
className="flex items-center gap-2"
disabled={false}
confirmTitle="Confirmer la réintégration"
confirmMessage={`Réintégrer ${m.first_name || m.email} et lui redonner laccès à cette structure ?`}
confirmCta="Réintégrer"
>
<input type="hidden" name="org_id" value={activeOrgId} />
<input type="hidden" name="user_id" value={m.user_id} />
<button
type="submit"
className="px-3 py-1 rounded text-xs bg-emerald-600 text-white hover:bg-emerald-700"
>
Réintégrer
</button>
</ConfirmableForm>
)}
{disabled && (
<div className="text-xs text-slate-500">Utilisateur révoqué accès désactivé</div>
)}
</div>
</td>
</tr>
);
})
)}
</tbody>
</table>
</div>
<div className="text-xs text-slate-500">
* La suppression est une révocation : lutilisateur reste historisé dans la base, mais ne peut plus se connecter.
</div>
</main>
);
}