Files
iag-dev-evaluator/src/components/AdminClient.tsx

175 lines
7.1 KiB
TypeScript

"use client";
import { useState } from "react";
import { format } from "date-fns";
import { setUserRole, deleteUser } from "@/actions/admin";
import { useSession } from "next-auth/react";
import { ConfirmModal } from "@/components/ConfirmModal";
interface Template {
id: string;
name: string;
dimensions: { id: string; title: string; orderIndex: number }[];
}
interface User {
id: string;
email: string;
name: string | null;
role: string;
createdAt: Date | string;
}
interface AdminClientProps {
templates: Template[];
users: User[];
}
export function AdminClient({ templates, users: initialUsers }: AdminClientProps) {
const { data: session } = useSession();
const [users, setUsers] = useState(initialUsers);
const [updatingId, setUpdatingId] = useState<string | null>(null);
const [deleteTarget, setDeleteTarget] = useState<User | null>(null);
async function handleSetRole(userId: string, role: "admin" | "evaluator") {
setUpdatingId(userId);
try {
const result = await setUserRole(userId, role);
if (result.success) {
setUsers((prev) => prev.map((u) => (u.id === userId ? { ...u, role } : u)));
} else {
alert(result.error);
}
} finally {
setUpdatingId(null);
}
}
return (
<div>
<h1 className="mb-6 font-mono text-lg font-medium text-zinc-800 dark:text-zinc-200">Admin</h1>
<section>
<h2 className="mb-4 font-mono text-xs text-zinc-600 dark:text-zinc-500">Users</h2>
<div className="overflow-hidden rounded-lg border border-zinc-200 dark:border-zinc-600 bg-white dark:bg-zinc-800 shadow-sm dark:shadow-none">
<table className="min-w-full">
<thead>
<tr className="border-b border-zinc-200 dark:border-zinc-600 bg-zinc-50 dark:bg-zinc-700/80">
<th className="px-4 py-2.5 text-left font-mono text-xs text-zinc-600 dark:text-zinc-400">Email</th>
<th className="px-4 py-2.5 text-left font-mono text-xs text-zinc-600 dark:text-zinc-400">Nom</th>
<th className="px-4 py-2.5 text-left font-mono text-xs text-zinc-600 dark:text-zinc-400">Rôle</th>
<th className="px-4 py-2.5 text-left font-mono text-xs text-zinc-600 dark:text-zinc-400">Créé le</th>
<th className="px-4 py-2.5 text-right font-mono text-xs text-zinc-600 dark:text-zinc-400"></th>
</tr>
</thead>
<tbody>
{users.map((u) => (
<tr key={u.id} className="border-b border-zinc-200 dark:border-zinc-600/50 last:border-0">
<td className="px-4 py-2.5 text-sm text-zinc-800 dark:text-zinc-200">{u.email}</td>
<td className="px-4 py-2.5 text-sm text-zinc-600 dark:text-zinc-400">{u.name ?? "—"}</td>
<td className="px-4 py-2.5">
<span
className={`font-mono text-xs px-1.5 py-0.5 rounded ${
u.role === "admin" ? "bg-cyan-500/20 text-cyan-600 dark:text-cyan-400" : "bg-zinc-200 dark:bg-zinc-700 text-zinc-600 dark:text-zinc-400"
}`}
>
{u.role}
</span>
</td>
<td className="px-4 py-2.5 font-mono text-xs text-zinc-500 dark:text-zinc-400">
{format(new Date(u.createdAt), "yyyy-MM-dd HH:mm")}
</td>
<td className="px-4 py-2.5 text-right">
<span className="inline-flex items-center gap-2">
{u.role === "admin" ? (
<button
type="button"
onClick={() => handleSetRole(u.id, "evaluator")}
disabled={updatingId === u.id}
className="font-mono text-xs text-zinc-500 hover:text-zinc-700 dark:hover:text-zinc-300 disabled:opacity-50"
title="Rétrograder en évaluateur"
>
{updatingId === u.id ? "..." : "rétrograder"}
</button>
) : (
<button
type="button"
onClick={() => handleSetRole(u.id, "admin")}
disabled={updatingId === u.id}
className="font-mono text-xs text-cyan-600 dark:text-cyan-400 hover:text-cyan-500 disabled:opacity-50"
title="Promouvoir admin"
>
{updatingId === u.id ? "..." : "promouvoir admin"}
</button>
)}
{u.id !== session?.user?.id && (
<button
type="button"
onClick={() => setDeleteTarget(u)}
className="font-mono text-xs text-red-500 hover:text-red-400"
title="Supprimer"
>
supprimer
</button>
)}
</span>
</td>
</tr>
))}
</tbody>
</table>
</div>
</section>
<ConfirmModal
isOpen={!!deleteTarget}
title="Supprimer l'utilisateur"
message={
deleteTarget
? `Supprimer ${deleteTarget.name || deleteTarget.email} ? Les évaluations créées par cet utilisateur resteront (évaluateur mis à null).`
: ""
}
confirmLabel="Supprimer"
cancelLabel="Annuler"
variant="danger"
onConfirm={async () => {
if (!deleteTarget) return;
const result = await deleteUser(deleteTarget.id);
if (result.success) {
setUsers((prev) => prev.filter((u) => u.id !== deleteTarget.id));
setDeleteTarget(null);
} else {
alert(result.error);
}
}}
onCancel={() => setDeleteTarget(null)}
/>
<section className="mt-8">
<h2 className="mb-4 font-mono text-xs text-zinc-600 dark:text-zinc-500">Modèles</h2>
<div className="space-y-3">
{templates.map((t) => (
<div
key={t.id}
className="rounded-lg border border-zinc-200 dark:border-zinc-600 bg-white dark:bg-zinc-800 p-4 shadow-sm dark:shadow-none"
>
<h3 className="font-medium text-zinc-800 dark:text-zinc-200">{t.name}</h3>
<p className="mt-1 font-mono text-xs text-zinc-600 dark:text-zinc-500">
{t.dimensions.length} dim.
</p>
<ul className="mt-2 space-y-0.5 font-mono text-xs text-zinc-600 dark:text-zinc-400">
{t.dimensions.slice(0, 5).map((d) => (
<li key={d.id}> {d.title}</li>
))}
{t.dimensions.length > 5 && (
<li className="text-zinc-600 dark:text-zinc-500">+{t.dimensions.length - 5}</li>
)}
</ul>
</div>
))}
</div>
</section>
</div>
);
}