From f54b2640adb822c55f0d95e8218ddc2f42c0c835 Mon Sep 17 00:00:00 2001 From: Hyko Date: Wed, 22 Apr 2026 16:15:43 -0400 Subject: [PATCH] refactor(admin): replace parameterized routes with modal-based editing for users and roles --- src/features/admin/AdminPage.client.js | 15 - .../admin/components/RoleEditModal.client.js | 180 ++++++++++ .../admin/components/UserEditModal.client.js | 194 +++++++++++ src/features/admin/components/index.js | 2 + .../admin/pages/RoleEditPage.client.js | 200 ----------- src/features/admin/pages/RolesPage.client.js | 317 ++++++++++-------- .../admin/pages/UserEditPage.client.js | 244 -------------- src/features/admin/pages/UsersPage.client.js | 89 ++--- src/features/admin/pages/index.client.js | 15 +- src/shared/components/Modal.js | 67 +++- 10 files changed, 643 insertions(+), 680 deletions(-) create mode 100644 src/features/admin/components/RoleEditModal.client.js create mode 100644 src/features/admin/components/UserEditModal.client.js delete mode 100644 src/features/admin/pages/RoleEditPage.client.js delete mode 100644 src/features/admin/pages/UserEditPage.client.js diff --git a/src/features/admin/AdminPage.client.js b/src/features/admin/AdminPage.client.js index ac7decf..c38d07d 100644 --- a/src/features/admin/AdminPage.client.js +++ b/src/features/admin/AdminPage.client.js @@ -8,21 +8,6 @@ export default function AdminPageClient({ params, user, widgetData }) { const parts = params?.admin || []; const [first, second, third] = parts; - // Routes paramétrées — le registre stocke le composant sous un slug - // "namespace:form", le client y attache les bons props. - if (first === 'users' && second === 'edit' && third) { - const page = getPage('users:edit'); - if (page) return ; - } - if (first === 'roles' && second === 'edit' && third) { - const page = getPage('roles:edit'); - if (page) return ; - } - if (first === 'roles' && second === 'new') { - const page = getPage('roles:edit'); - if (page) return ; - } - const slug = first || 'dashboard'; const page = getPage(slug) || getPage('dashboard'); if (!page) return null; diff --git a/src/features/admin/components/RoleEditModal.client.js b/src/features/admin/components/RoleEditModal.client.js new file mode 100644 index 0000000..a38043b --- /dev/null +++ b/src/features/admin/components/RoleEditModal.client.js @@ -0,0 +1,180 @@ +'use client'; + +import { useState, useEffect } from 'react'; +import { Input, Textarea, Switch, Modal } from '@zen/core/shared/components'; +import { useToast } from '@zen/core/toast'; +import { getPermissionGroups } from '@zen/core/users/constants'; + +const PERMISSION_GROUPS = getPermissionGroups(); + +const RoleEditModal = ({ roleId, isOpen, onClose, onSaved }) => { + const toast = useToast(); + const isNew = !roleId || roleId === 'new'; + + const [loading, setLoading] = useState(false); + const [saving, setSaving] = useState(false); + const [isSystem, setIsSystem] = useState(false); + + const [name, setName] = useState(''); + const [description, setDescription] = useState(''); + const [color, setColor] = useState('#6b7280'); + const [selectedPerms, setSelectedPerms] = useState([]); + + useEffect(() => { + if (!isOpen) return; + if (isNew) { + setName(''); + setDescription(''); + setColor('#6b7280'); + setSelectedPerms([]); + setIsSystem(false); + return; + } + fetchRole(); + }, [isOpen, roleId]); + + const fetchRole = async () => { + try { + setLoading(true); + const response = await fetch(`/zen/api/roles/${roleId}`, { credentials: 'include' }); + if (!response.ok) { + toast.error('Rôle introuvable'); + onClose(); + return; + } + const data = await response.json(); + const role = data.role; + setName(role.name || ''); + setDescription(role.description || ''); + setColor(role.color || '#6b7280'); + setSelectedPerms(role.permission_keys || []); + setIsSystem(role.is_system || false); + } catch { + toast.error('Impossible de charger ce rôle'); + onClose(); + } finally { + setLoading(false); + } + }; + + const togglePerm = (key) => { + setSelectedPerms(prev => + prev.includes(key) ? prev.filter(k => k !== key) : [...prev, key] + ); + }; + + const handleSubmit = async () => { + if (!name.trim()) { + toast.error('Le nom du rôle est requis'); + return; + } + try { + setSaving(true); + const url = isNew ? '/zen/api/roles' : `/zen/api/roles/${roleId}`; + const method = isNew ? 'POST' : 'PUT'; + + const response = await fetch(url, { + method, + credentials: 'include', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + name: name.trim(), + description: description.trim() || null, + color, + permissionKeys: selectedPerms, + }), + }); + const data = await response.json(); + if (!response.ok) { + toast.error(data.message || 'Impossible de sauvegarder ce rôle'); + return; + } + toast.success(isNew ? 'Rôle créé' : 'Rôle mis à jour'); + onSaved?.(); + onClose(); + } catch { + toast.error('Impossible de sauvegarder ce rôle'); + } finally { + setSaving(false); + } + }; + + const title = isNew ? 'Nouveau rôle' : `Modifier "${name}"`; + + return ( + + {loading ? ( +
+
+
+
+
+ ) : ( +
+
+ +