@flowselections/admin-installatietechniek 1.0.3 → 1.0.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. package/dist-lib/components/AdminForInstallatieTechniek.d.ts +4 -0
  2. package/dist-lib/components/AdminForInstallatieTechniek.d.ts.map +1 -1
  3. package/dist-lib/components/AdminForInstallatieTechniek.js +223 -197
  4. package/dist-lib/components/ModuleAccessGuard.d.ts +15 -0
  5. package/dist-lib/components/ModuleAccessGuard.d.ts.map +1 -0
  6. package/dist-lib/components/ModuleAccessGuard.js +40 -0
  7. package/dist-lib/hooks/useModulePermission.d.ts +11 -4
  8. package/dist-lib/hooks/useModulePermission.d.ts.map +1 -1
  9. package/dist-lib/hooks/useModulePermission.js +33 -26
  10. package/dist-lib/index.d.ts +3 -3
  11. package/dist-lib/index.d.ts.map +1 -1
  12. package/dist-lib/index.js +2 -1
  13. package/dist-lib/integrations/supabase/auth-attacher.d.ts.map +1 -1
  14. package/dist-lib/integrations/supabase/auth-attacher.js +5 -1
  15. package/dist-lib/integrations/supabase/auth-middleware.d.ts +978 -133
  16. package/dist-lib/integrations/supabase/auth-middleware.d.ts.map +1 -1
  17. package/dist-lib/integrations/supabase/auth-middleware.js +21 -7
  18. package/dist-lib/integrations/supabase/client.d.ts +978 -133
  19. package/dist-lib/integrations/supabase/client.d.ts.map +1 -1
  20. package/dist-lib/integrations/supabase/client.server.d.ts +978 -133
  21. package/dist-lib/integrations/supabase/client.server.d.ts.map +1 -1
  22. package/dist-lib/integrations/supabase/types.d.ts +987 -137
  23. package/dist-lib/integrations/supabase/types.d.ts.map +1 -1
  24. package/dist-lib/lib/admin.functions.d.ts +78644 -2655
  25. package/dist-lib/lib/admin.functions.d.ts.map +1 -1
  26. package/dist-lib/lib/admin.functions.js +351 -174
  27. package/dist-lib/lib/moduleDiscovery.d.ts +7 -2
  28. package/dist-lib/lib/moduleDiscovery.d.ts.map +1 -1
  29. package/dist-lib/lib/moduleDiscovery.js +26 -11
  30. package/dist-lib/lib/permissions.server.d.ts +14 -0
  31. package/dist-lib/lib/permissions.server.d.ts.map +1 -0
  32. package/dist-lib/lib/permissions.server.js +26 -0
  33. package/package.json +1 -1
  34. package/dist-lib/hooks/useModuleOrder.d.ts +0 -30
  35. package/dist-lib/hooks/useModuleOrder.d.ts.map +0 -1
  36. package/dist-lib/hooks/useModuleOrder.js +0 -99
@@ -1,22 +1,17 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { useCallback, useEffect, useMemo, useRef, useState } from "react";
3
- import { useAuth, AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, Button, Card, CardContent, CardHeader, CardTitle, Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, Input, Label, Select, SelectContent, SelectItem, SelectTrigger, SelectValue, Table, TableBody, TableCell, TableHead, TableHeader, TableRow, Tabs, TabsContent, TabsList, TabsTrigger, Badge, toast, } from "@flowselections/core";
4
- import { ArrowDown, ArrowUp, Copy, FlaskConical, Shield, Trash2, UserPlus, UserX } from "lucide-react";
5
- import { assignRole, createTestUser, deleteUser, getModuleOrder, getModulePermissions, getUserModulePermissions, getUsersWithRoles, inviteUser, removeRole, setModuleOrder, setModulePermission, setUserModulePermission, syncModuleRegistry, } from "../lib/admin.functions";
6
- import { notifyModuleOrderChanged } from "../hooks/useModuleOrder";
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { useEffect, useMemo, useState } from "react";
3
+ import { useAuth, AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, Badge, Button, Card, CardContent, CardHeader, CardTitle, Checkbox, Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuSeparator, DropdownMenuSub, DropdownMenuSubContent, DropdownMenuSubTrigger, DropdownMenuTrigger, Input, Label, Select, SelectContent, SelectItem, SelectTrigger, SelectValue, Switch, Table, TableBody, TableCell, TableHead, TableHeader, TableRow, Tabs, TabsContent, TabsList, TabsTrigger, toast, } from "@flowselections/core";
4
+ import { ChevronDown, ChevronRight, Copy, FlaskConical, MoreVertical, Plus, Pencil, Shield, UserPlus, UserX, } from "lucide-react";
5
+ import { assignRole, createRole, createTestUser, deleteRole, deleteUser, duplicateRole, getRoles, getUserModulePermissions, getUsersWithRoles, inviteUser, removeRole, setModuleEnforce, setUserModulePermission, syncModuleRegistry, updateRole, updateUserName, } from "../lib/admin.functions";
7
6
  import { useDiscoveredModules } from "../lib/moduleDiscovery";
8
7
  import { supabase } from "../integrations/supabase/client";
9
- const PERMISSION_LABELS = {
10
- none: "Geen toegang",
11
- view: "Bekijken",
12
- edit: "Aanpassen",
13
- };
14
8
  const USER_PERMISSION_LABELS = {
15
9
  inherit: "Volg rol",
16
10
  none: "Geen toegang",
17
11
  view: "Bekijken",
18
12
  edit: "Aanpassen",
19
13
  };
14
+ const permCellKey = (moduleId, featureId) => `${moduleId}::${featureId}`;
20
15
  const getErrorMessage = (error, fallback) => {
21
16
  if (error instanceof Error && error.message) {
22
17
  if (/permission denied|row-level security|rls|forbidden|not authorized/i.test(error.message)) {
@@ -28,7 +23,6 @@ const getErrorMessage = (error, fallback) => {
28
23
  };
29
24
  async function refreshOwnSessionIfSelf(targetUserId, currentUserId) {
30
25
  if (currentUserId && targetUserId === currentUserId) {
31
- // Nieuwe access-token ophalen zodat de user_roles JWT-claim direct actueel is.
32
26
  await supabase.auth.refreshSession();
33
27
  }
34
28
  }
@@ -41,26 +35,32 @@ export function AdminForInstallatieTechniek({ modules, shellId } = {}) {
41
35
  const isAdministratie = authRoles.includes("administratie");
42
36
  const canAccess = isAdmin || isAdministratie;
43
37
  const currentUserId = user?.id;
38
+ const [activeTab, setActiveTab] = useState("users");
44
39
  const [users, setUsers] = useState([]);
45
40
  const [availableRoles, setAvailableRoles] = useState([]);
46
41
  const [loading, setLoading] = useState(true);
47
- const [selectedUser, setSelectedUser] = useState("");
48
- const [selectedRole, setSelectedRole] = useState("planner");
49
42
  const [submitting, setSubmitting] = useState(false);
50
- const [permRoles, setPermRoles] = useState([]);
51
- const [permModules, setPermModules] = useState([]);
52
- const [permMatrix, setPermMatrix] = useState({});
53
- const [permLoading, setPermLoading] = useState(true);
43
+ const [renameTarget, setRenameTarget] = useState(null);
44
+ const [renameValue, setRenameValue] = useState("");
45
+ const [renaming, setRenaming] = useState(false);
46
+ const [roles, setRoles] = useState([]);
47
+ const [roleModules, setRoleModules] = useState([]);
48
+ const [rolePermissions, setRolePermissions] = useState([]);
49
+ const [rolesLoading, setRolesLoading] = useState(true);
50
+ const [editor, setEditor] = useState(null);
51
+ const [editorSaving, setEditorSaving] = useState(false);
52
+ const [expandedModules, setExpandedModules] = useState({});
53
+ const [roleDeleteTarget, setRoleDeleteTarget] = useState(null);
54
54
  const [inviteOpen, setInviteOpen] = useState(false);
55
55
  const [inviteName, setInviteName] = useState("");
56
56
  const [inviteEmail, setInviteEmail] = useState("");
57
- const [inviteRole, setInviteRole] = useState("monteur");
57
+ const [inviteRole, setInviteRole] = useState("");
58
58
  const [inviting, setInviting] = useState(false);
59
59
  const [deleteTarget, setDeleteTarget] = useState(null);
60
60
  const [deleting, setDeleting] = useState(false);
61
61
  const [testOpen, setTestOpen] = useState(false);
62
62
  const [testName, setTestName] = useState("");
63
- const [testRole, setTestRole] = useState("monteur");
63
+ const [testRole, setTestRole] = useState("");
64
64
  const [testCreating, setTestCreating] = useState(false);
65
65
  const [testCredentials, setTestCredentials] = useState(null);
66
66
  const [searchQuery, setSearchQuery] = useState("");
@@ -70,25 +70,25 @@ export function AdminForInstallatieTechniek({ modules, shellId } = {}) {
70
70
  const [userPermModules, setUserPermModules] = useState([]);
71
71
  const [userPermMap, setUserPermMap] = useState({});
72
72
  const [userPermLoading, setUserPermLoading] = useState(false);
73
- const [orderModules, setOrderModules] = useState([]);
74
- const [orderLoading, setOrderLoading] = useState(false);
75
- const [orderSaving, setOrderSaving] = useState(false);
76
- const [orderSavedAt, setOrderSavedAt] = useState(null);
77
- const orderSaveTimer = useRef(null);
78
- const permKey = (role, moduleId) => `${role}::${moduleId}`;
79
73
  const discoveredModules = useDiscoveredModules();
80
74
  const shellModules = useMemo(() => {
81
75
  const source = Array.isArray(modules) && modules.length > 0 ? modules : discoveredModules;
82
76
  return source.filter((m) => Boolean(m?.id) && Boolean(m?.name));
83
77
  }, [modules, discoveredModules]);
84
78
  const effectiveShellId = shellId ?? (typeof window !== "undefined" ? window.location.host : "unknown-shell");
85
- const shellModulesKey = shellModules.map((m) => `${m.id}:${m.name}`).join("|");
79
+ const shellModulesKey = shellModules
80
+ .map((m) => `${m.id}:${m.name}:${(m.features ?? []).map((f) => `${f.id}=${f.name}`).join(",")}`)
81
+ .join("|");
86
82
  const loadUsers = async () => {
87
83
  setLoading(true);
88
84
  try {
89
85
  const result = await getUsersWithRoles();
90
86
  setUsers(Array.isArray(result?.users) ? result.users : []);
91
- setAvailableRoles(Array.isArray(result?.availableRoles) ? result.availableRoles : []);
87
+ const roleList = Array.isArray(result?.availableRoles) ? result.availableRoles : [];
88
+ setAvailableRoles(roleList);
89
+ const fallback = roleList[0]?.id ?? "";
90
+ setInviteRole((current) => current || fallback);
91
+ setTestRole((current) => current || fallback);
92
92
  }
93
93
  catch (error) {
94
94
  toast.error(getErrorMessage(error, "Fout bij het laden van gebruikers en rollen"));
@@ -97,31 +97,19 @@ export function AdminForInstallatieTechniek({ modules, shellId } = {}) {
97
97
  setLoading(false);
98
98
  }
99
99
  };
100
- const loadPermissions = async () => {
101
- setPermLoading(true);
100
+ const loadRoles = async () => {
101
+ setRolesLoading(true);
102
102
  try {
103
- const result = await getModulePermissions();
104
- const roles = Array.isArray(result?.roles) ? result.roles : [];
105
- const mods = Array.isArray(result?.modules) ? result.modules : [];
106
- const perms = Array.isArray(result?.permissions) ? result.permissions : [];
107
- setPermRoles(roles);
108
- setPermModules(mods);
109
- const matrix = {};
110
- for (const r of roles) {
111
- for (const m of mods) {
112
- matrix[permKey(r, m.id)] = "none";
113
- }
114
- }
115
- for (const p of perms) {
116
- matrix[permKey(p.role, p.moduleId)] = p.permission;
117
- }
118
- setPermMatrix(matrix);
103
+ const result = await getRoles();
104
+ setRoles(Array.isArray(result?.roles) ? result.roles : []);
105
+ setRoleModules(Array.isArray(result?.modules) ? result.modules : []);
106
+ setRolePermissions(Array.isArray(result?.permissions) ? result.permissions : []);
119
107
  }
120
108
  catch (error) {
121
- toast.error(getErrorMessage(error, "Fout bij het laden van module-machtigingen"));
109
+ toast.error(getErrorMessage(error, "Fout bij het laden van rollen"));
122
110
  }
123
111
  finally {
124
- setPermLoading(false);
112
+ setRolesLoading(false);
125
113
  }
126
114
  };
127
115
  const loadUserPermissions = async (userId) => {
@@ -137,10 +125,14 @@ export function AdminForInstallatieTechniek({ modules, shellId } = {}) {
137
125
  const overrides = Array.isArray(result?.overrides) ? result.overrides : [];
138
126
  setUserPermModules(mods);
139
127
  const map = {};
140
- for (const m of mods)
141
- map[m.id] = "inherit";
142
- for (const o of overrides)
143
- map[o.moduleId] = o.permission;
128
+ for (const m of mods) {
129
+ map[permCellKey(m.id, "")] = "inherit";
130
+ for (const f of m.features ?? [])
131
+ map[permCellKey(m.id, f.id)] = "inherit";
132
+ }
133
+ for (const o of overrides) {
134
+ map[permCellKey(o.moduleId, o.featureId ?? "")] = o.permission;
135
+ }
144
136
  setUserPermMap(map);
145
137
  }
146
138
  catch (error) {
@@ -150,95 +142,11 @@ export function AdminForInstallatieTechniek({ modules, shellId } = {}) {
150
142
  setUserPermLoading(false);
151
143
  }
152
144
  };
153
- const loadModuleOrder = async () => {
154
- setOrderLoading(true);
155
- try {
156
- const result = await getModuleOrder();
157
- const saved = Array.isArray(result?.modules) ? result.modules : [];
158
- const byId = new Map(saved.map((m) => [m.id, m]));
159
- // Standaardvolgorde = de volgorde waarin de shell zijn modules levert.
160
- // Opgeslagen posities krijgen voorrang; de rest houdt de bronvolgorde.
161
- const base = [];
162
- for (const m of shellModules) {
163
- const match = byId.get(m.id);
164
- base.push({ id: m.id, name: match?.name ?? m.name });
165
- byId.delete(m.id);
166
- }
167
- for (const m of byId.values())
168
- base.push({ id: m.id, name: m.name });
169
- const positions = new Map();
170
- for (const m of saved) {
171
- if (typeof m.sortOrder === "number")
172
- positions.set(m.id, m.sortOrder);
173
- }
174
- const ordered = base
175
- .map((mod, index) => ({ mod, index }))
176
- .sort((a, b) => {
177
- const ao = positions.get(a.mod.id);
178
- const bo = positions.get(b.mod.id);
179
- if (ao !== undefined && bo !== undefined)
180
- return ao - bo;
181
- if (ao !== undefined)
182
- return -1;
183
- if (bo !== undefined)
184
- return 1;
185
- return a.index - b.index;
186
- })
187
- .map((e) => e.mod);
188
- setOrderModules(ordered);
189
- }
190
- catch (error) {
191
- toast.error(getErrorMessage(error, "Fout bij het laden van de zijbalk-volgorde"));
192
- }
193
- finally {
194
- setOrderLoading(false);
195
- }
196
- };
197
- const persistOrder = useCallback(async (list, previous) => {
198
- setOrderSaving(true);
199
- try {
200
- await setModuleOrder({ data: { moduleIds: list.map((m) => m.id) } });
201
- setOrderSavedAt(Date.now());
202
- // Zijbalk van de shell direct laten herordenen.
203
- notifyModuleOrderChanged();
204
- }
205
- catch (error) {
206
- setOrderModules(previous);
207
- toast.error(getErrorMessage(error, "Opslaan van de volgorde mislukt"));
208
- }
209
- finally {
210
- setOrderSaving(false);
211
- }
212
- }, []);
213
- const moveModule = (index, direction) => {
214
- const previous = orderModules;
215
- const next = [...previous];
216
- const target = index + direction;
217
- if (target < 0 || target >= next.length)
218
- return;
219
- const [item] = next.splice(index, 1);
220
- next.splice(target, 0, item);
221
- setOrderModules(next);
222
- // Realtime opslaan: klikken worden kort gebundeld.
223
- if (orderSaveTimer.current)
224
- clearTimeout(orderSaveTimer.current);
225
- orderSaveTimer.current = setTimeout(() => {
226
- orderSaveTimer.current = null;
227
- void persistOrder(next, previous);
228
- }, 400);
229
- };
230
- useEffect(() => {
231
- return () => {
232
- if (orderSaveTimer.current)
233
- clearTimeout(orderSaveTimer.current);
234
- };
235
- }, []);
236
145
  useEffect(() => {
237
146
  if (!canAccess)
238
147
  return;
239
148
  let cancelled = false;
240
149
  (async () => {
241
- // Modules die deze shell heeft geladen automatisch registreren.
242
150
  if (isAdmin && shellModules.length > 0) {
243
151
  try {
244
152
  await syncModuleRegistry({ data: { modules: shellModules, shellId: effectiveShellId } });
@@ -250,14 +158,7 @@ export function AdminForInstallatieTechniek({ modules, shellId } = {}) {
250
158
  if (cancelled)
251
159
  return;
252
160
  loadUsers();
253
- // Module-machtigingen zijn strikt admin-only.
254
- if (isAdmin) {
255
- loadPermissions();
256
- loadModuleOrder();
257
- }
258
- else {
259
- setPermLoading(false);
260
- }
161
+ loadRoles();
261
162
  })();
262
163
  return () => {
263
164
  cancelled = true;
@@ -270,27 +171,39 @@ export function AdminForInstallatieTechniek({ modules, shellId } = {}) {
270
171
  loadUserPermissions(permUserId);
271
172
  // eslint-disable-next-line react-hooks/exhaustive-deps
272
173
  }, [permUserId, canAccess]);
273
- const handleUserPermChange = async (moduleId, permission) => {
174
+ const handleUserPermChange = async (moduleId, featureId, permission) => {
274
175
  if (!permUserId)
275
176
  return;
276
- const prev = userPermMap[moduleId] ?? "inherit";
277
- setUserPermMap((m) => ({ ...m, [moduleId]: permission }));
177
+ const key = permCellKey(moduleId, featureId);
178
+ const prev = userPermMap[key] ?? "inherit";
179
+ setUserPermMap((m) => ({ ...m, [key]: permission }));
278
180
  try {
279
- await setUserModulePermission({ data: { userId: permUserId, moduleId, permission } });
181
+ await setUserModulePermission({
182
+ data: { userId: permUserId, moduleId, featureId, permission },
183
+ });
280
184
  toast.success("Persoonlijk recht opgeslagen");
281
185
  }
282
186
  catch (error) {
283
- setUserPermMap((m) => ({ ...m, [moduleId]: prev }));
187
+ setUserPermMap((m) => ({ ...m, [key]: prev }));
284
188
  toast.error(getErrorMessage(error, "Opslaan mislukt"));
285
189
  }
286
190
  };
287
- const handleAssign = async () => {
288
- if (!selectedUser || !selectedRole)
289
- return;
191
+ const handleEnforceChange = async (moduleId, enforce) => {
192
+ setRoleModules((prev) => prev.map((m) => (m.id === moduleId ? { ...m, enforce } : m)));
193
+ try {
194
+ await setModuleEnforce({ data: { moduleId, enforce } });
195
+ toast.success(`Module ${enforce ? "afgedwongen" : "niet langer afgedwongen"}`);
196
+ }
197
+ catch (error) {
198
+ setRoleModules((prev) => prev.map((m) => (m.id === moduleId ? { ...m, enforce: !enforce } : m)));
199
+ toast.error(getErrorMessage(error, "Afdwingen wijzigen mislukt"));
200
+ }
201
+ };
202
+ const handleAssign = async (userId, roleId) => {
290
203
  setSubmitting(true);
291
204
  try {
292
- await assignRole({ data: { userId: selectedUser, role: selectedRole } });
293
- await refreshOwnSessionIfSelf(selectedUser, currentUserId);
205
+ await assignRole({ data: { userId, roleId } });
206
+ await refreshOwnSessionIfSelf(userId, currentUserId);
294
207
  toast.success("Rol toegekend");
295
208
  await loadUsers();
296
209
  }
@@ -301,9 +214,9 @@ export function AdminForInstallatieTechniek({ modules, shellId } = {}) {
301
214
  setSubmitting(false);
302
215
  }
303
216
  };
304
- const handleRemove = async (userId, role) => {
217
+ const handleRemove = async (userId, roleId) => {
305
218
  try {
306
- await removeRole({ data: { userId, role } });
219
+ await removeRole({ data: { userId, roleId } });
307
220
  await refreshOwnSessionIfSelf(userId, currentUserId);
308
221
  toast.success("Rol verwijderd");
309
222
  await loadUsers();
@@ -312,31 +225,106 @@ export function AdminForInstallatieTechniek({ modules, shellId } = {}) {
312
225
  toast.error(getErrorMessage(error, "Fout bij het verwijderen van rol"));
313
226
  }
314
227
  };
315
- const handlePermChange = async (role, moduleId, permission) => {
316
- const prev = permMatrix[permKey(role, moduleId)];
317
- setPermMatrix((m) => ({ ...m, [permKey(role, moduleId)]: permission }));
228
+ const openRoleEditor = (role) => {
229
+ const cells = {};
230
+ for (const m of roleModules) {
231
+ cells[permCellKey(m.id, "")] = { canView: false, canEdit: false };
232
+ for (const f of m.features ?? []) {
233
+ cells[permCellKey(m.id, f.id)] = { canView: false, canEdit: false };
234
+ }
235
+ }
236
+ if (role) {
237
+ for (const p of rolePermissions.filter((p) => p.roleId === role.id)) {
238
+ cells[permCellKey(p.moduleId, p.featureId)] = { canView: p.canView, canEdit: p.canEdit };
239
+ }
240
+ }
241
+ setEditor({ roleId: role?.id ?? null, name: role?.name ?? "", cells });
242
+ };
243
+ const setCell = (moduleId, featureId, field, value) => {
244
+ setEditor((state) => {
245
+ if (!state)
246
+ return state;
247
+ const cells = { ...state.cells };
248
+ const key = permCellKey(moduleId, featureId);
249
+ const current = cells[key] ?? { canView: false, canEdit: false };
250
+ const next = { ...current, [field]: value };
251
+ if (field === "canEdit" && value)
252
+ next.canView = true;
253
+ if (field === "canView" && !value)
254
+ next.canEdit = false;
255
+ cells[key] = next;
256
+ // Vinkje op moduleniveau zet alle onderdelen mee.
257
+ if (featureId === "") {
258
+ const mod = roleModules.find((m) => m.id === moduleId);
259
+ for (const f of mod?.features ?? []) {
260
+ cells[permCellKey(moduleId, f.id)] = { ...next };
261
+ }
262
+ }
263
+ return { ...state, cells };
264
+ });
265
+ };
266
+ const handleSaveRole = async () => {
267
+ if (!editor || !editor.name.trim())
268
+ return;
269
+ setEditorSaving(true);
318
270
  try {
319
- await setModulePermission({ data: { role, moduleId, permission } });
320
- toast.success("Machtiging opgeslagen");
271
+ const permissions = Object.entries(editor.cells).map(([key, value]) => {
272
+ const [moduleId, featureId] = key.split("::");
273
+ return { moduleId, featureId: featureId ?? "", canView: value.canView, canEdit: value.canEdit };
274
+ });
275
+ if (editor.roleId) {
276
+ await updateRole({ data: { roleId: editor.roleId, name: editor.name.trim(), permissions } });
277
+ toast.success("Rol bijgewerkt");
278
+ }
279
+ else {
280
+ await createRole({ data: { name: editor.name.trim(), permissions } });
281
+ toast.success("Rol aangemaakt");
282
+ }
283
+ setEditor(null);
284
+ await Promise.all([loadRoles(), loadUsers()]);
285
+ }
286
+ catch (error) {
287
+ toast.error(getErrorMessage(error, "Opslaan mislukt"));
288
+ }
289
+ finally {
290
+ setEditorSaving(false);
291
+ }
292
+ };
293
+ const handleDuplicateRole = async (role) => {
294
+ try {
295
+ await duplicateRole({ data: { roleId: role.id } });
296
+ toast.success("Rol gedupliceerd");
297
+ await Promise.all([loadRoles(), loadUsers()]);
321
298
  }
322
299
  catch (error) {
323
- setPermMatrix((m) => ({ ...m, [permKey(role, moduleId)]: prev ?? "none" }));
324
- toast.error(getErrorMessage(error, "Fout bij het opslaan van machtiging"));
300
+ toast.error(getErrorMessage(error, "Dupliceren mislukt"));
301
+ }
302
+ };
303
+ const handleDeleteRole = async () => {
304
+ if (!roleDeleteTarget)
305
+ return;
306
+ try {
307
+ await deleteRole({ data: { roleId: roleDeleteTarget.id } });
308
+ toast.success("Rol verwijderd");
309
+ setRoleDeleteTarget(null);
310
+ await Promise.all([loadRoles(), loadUsers()]);
311
+ }
312
+ catch (error) {
313
+ toast.error(getErrorMessage(error, "Verwijderen mislukt"));
325
314
  }
326
315
  };
327
316
  const handleInvite = async () => {
328
- if (!inviteEmail.trim() || !inviteName.trim())
317
+ if (!inviteEmail.trim() || !inviteName.trim() || !inviteRole)
329
318
  return;
330
319
  setInviting(true);
331
320
  try {
332
321
  await inviteUser({
333
- data: { email: inviteEmail.trim(), displayName: inviteName.trim(), role: inviteRole },
322
+ data: { email: inviteEmail.trim(), displayName: inviteName.trim(), roleId: inviteRole },
334
323
  });
335
324
  toast.success(`Uitnodiging verstuurd naar ${inviteEmail.trim()}`);
336
325
  setInviteOpen(false);
337
326
  setInviteName("");
338
327
  setInviteEmail("");
339
- setInviteRole("monteur");
340
328
  await loadUsers();
341
329
  }
342
330
  catch (error) {
@@ -363,18 +351,38 @@ export function AdminForInstallatieTechniek({ modules, shellId } = {}) {
363
351
  setDeleting(false);
364
352
  }
365
353
  };
354
+ const openRename = (target) => {
355
+ setRenameTarget(target);
356
+ setRenameValue(target.displayName === "-" ? "" : target.displayName);
357
+ };
358
+ const handleRename = async () => {
359
+ if (!renameTarget || !renameValue.trim())
360
+ return;
361
+ setRenaming(true);
362
+ try {
363
+ await updateUserName({ data: { userId: renameTarget.id, displayName: renameValue.trim() } });
364
+ toast.success("Naam bijgewerkt");
365
+ setRenameTarget(null);
366
+ await loadUsers();
367
+ }
368
+ catch (error) {
369
+ toast.error(getErrorMessage(error, "Naam wijzigen mislukt"));
370
+ }
371
+ finally {
372
+ setRenaming(false);
373
+ }
374
+ };
366
375
  const handleCreateTestUser = async () => {
367
- if (!testName.trim())
376
+ if (!testName.trim() || !testRole)
368
377
  return;
369
378
  setTestCreating(true);
370
379
  try {
371
380
  const result = await createTestUser({
372
- data: { displayName: testName.trim(), role: testRole },
381
+ data: { displayName: testName.trim(), roleId: testRole },
373
382
  });
374
383
  setTestCredentials({ email: result.email, password: result.password });
375
384
  setTestOpen(false);
376
385
  setTestName("");
377
- setTestRole("monteur");
378
386
  toast.success("Testaccount aangemaakt");
379
387
  await loadUsers();
380
388
  }
@@ -394,55 +402,73 @@ export function AdminForInstallatieTechniek({ modules, shellId } = {}) {
394
402
  toast.error("Kopiëren mislukt");
395
403
  }
396
404
  };
397
- const selectedUserHasRole = users.some((user) => user.id === selectedUser && user.roles.includes(selectedRole));
398
405
  const filteredUsers = useMemo(() => {
399
406
  const query = searchQuery.trim().toLowerCase();
400
- let list = users.filter((user) => {
401
- const matchesQuery = !query ||
402
- user.displayName.toLowerCase().includes(query) ||
403
- user.email.toLowerCase().includes(query);
407
+ const list = users.filter((u) => {
408
+ const matchesQuery = !query || u.displayName.toLowerCase().includes(query) || u.email.toLowerCase().includes(query);
404
409
  const matchesRole = roleFilter === "all" ||
405
- (roleFilter === "none" ? user.roles.length === 0 : user.roles.includes(roleFilter));
410
+ (roleFilter === "none" ? u.roles.length === 0 : u.roles.some((r) => r.id === roleFilter));
406
411
  return matchesQuery && matchesRole;
407
412
  });
408
- list = [...list].sort((a, b) => {
413
+ return [...list].sort((a, b) => {
414
+ const roleA = a.roles[0]?.name ?? "";
415
+ const roleB = b.roles[0]?.name ?? "";
409
416
  switch (sortBy) {
410
417
  case "name-asc":
411
418
  return a.displayName.localeCompare(b.displayName);
412
419
  case "name-desc":
413
420
  return b.displayName.localeCompare(a.displayName);
414
- case "role-asc": {
415
- const roleA = a.roles[0] ?? "";
416
- const roleB = b.roles[0] ?? "";
421
+ case "role-asc":
417
422
  return roleA.localeCompare(roleB);
418
- }
419
- case "role-desc": {
420
- const roleA = a.roles[0] ?? "";
421
- const roleB = b.roles[0] ?? "";
423
+ case "role-desc":
422
424
  return roleB.localeCompare(roleA);
423
- }
424
425
  default:
425
426
  return 0;
426
427
  }
427
428
  });
428
- return list;
429
429
  }, [users, searchQuery, roleFilter, sortBy]);
430
430
  const hasActiveFilters = searchQuery.trim() !== "" || roleFilter !== "all";
431
- // Administratie mag de adminrol niet toekennen of verwijderen.
432
- const baseRoles = availableRoles.length > 0 ? availableRoles : ["admin", "planner", "monteur", "administratie"];
433
- const assignableRoles = isAdmin ? baseRoles : baseRoles.filter((r) => r !== "admin");
434
- const canManageUser = (target) => isAdmin || !target.roles.includes("admin");
431
+ const assignableRoles = availableRoles;
432
+ const canManageUser = (target) => isAdmin || !target.roles.some((r) => r.key === "admin");
435
433
  if (!canAccess) {
436
434
  return (_jsx("main", { className: "p-6", children: _jsx(Card, { children: _jsx(CardContent, { className: "pt-6", children: _jsx("p", { className: "text-muted-foreground", children: "Je hebt geen toegang tot deze pagina." }) }) }) }));
437
435
  }
438
- return (_jsxs("main", { className: "p-6 space-y-6", children: [_jsxs(Tabs, { defaultValue: "users", children: [_jsxs(TabsList, { children: [_jsx(TabsTrigger, { value: "users", children: "Gebruikers & rollen" }), isAdmin && _jsx(TabsTrigger, { value: "permissions", children: "Module-machtigingen" }), _jsx(TabsTrigger, { value: "user-permissions", children: "Rechten per gebruiker" }), isAdmin && _jsx(TabsTrigger, { value: "order", children: "Zijbalk volgorde" })] }), _jsx(TabsContent, { value: "users", className: "mt-4", children: _jsxs(Card, { children: [_jsxs(CardHeader, { className: "flex flex-row items-center justify-between gap-4", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Shield, { className: "h-5 w-5 text-primary" }), _jsx(CardTitle, { children: "Gebruikersrollen beheren" })] }), _jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [_jsxs(Button, { variant: "outline", onClick: () => setTestOpen(true), size: "sm", children: [_jsx(FlaskConical, { className: "h-4 w-4 mr-2" }), "Testaccount aanmaken"] }), _jsxs(Button, { onClick: () => setInviteOpen(true), size: "sm", children: [_jsx(UserPlus, { className: "h-4 w-4 mr-2" }), "Account aanmaken"] })] })] }), _jsxs(CardContent, { className: "space-y-6", children: [_jsxs("div", { className: "flex flex-col gap-4 sm:flex-row sm:items-end", children: [_jsxs("div", { className: "flex-1 space-y-2", children: [_jsx("label", { className: "text-sm font-medium", children: "Gebruiker" }), _jsxs(Select, { value: selectedUser, onValueChange: setSelectedUser, children: [_jsx(SelectTrigger, { children: _jsx(SelectValue, { placeholder: "Selecteer een gebruiker" }) }), _jsx(SelectContent, { children: users.map((user) => (_jsxs(SelectItem, { value: user.id, children: [user.displayName, " (", user.email, ")"] }, user.id))) })] })] }), _jsxs("div", { className: "w-full sm:w-48 space-y-2", children: [_jsx("label", { className: "text-sm font-medium", children: "Rol" }), _jsxs(Select, { value: selectedRole, onValueChange: (value) => setSelectedRole(value), children: [_jsx(SelectTrigger, { children: _jsx(SelectValue, {}) }), _jsx(SelectContent, { children: assignableRoles.map((role) => (_jsx(SelectItem, { value: role, children: role }, role))) })] })] }), _jsx(Button, { onClick: handleAssign, disabled: !selectedUser || submitting || loading || selectedUserHasRole, children: submitting ? "Toekennen..." : selectedUserHasRole ? "Rol bestaat al" : "Rol toekennen" })] }), _jsxs("div", { className: "flex flex-col gap-4 sm:flex-row sm:items-end", children: [_jsxs("div", { className: "flex-1 space-y-2", children: [_jsx(Label, { className: "text-sm font-medium", children: "Zoeken" }), _jsx(Input, { placeholder: "Zoek op naam of e-mail", value: searchQuery, onChange: (e) => setSearchQuery(e.target.value) })] }), _jsxs("div", { className: "w-full sm:w-48 space-y-2", children: [_jsx(Label, { className: "text-sm font-medium", children: "Filter op rol" }), _jsxs(Select, { value: roleFilter, onValueChange: (value) => setRoleFilter(value), children: [_jsx(SelectTrigger, { children: _jsx(SelectValue, {}) }), _jsxs(SelectContent, { children: [_jsx(SelectItem, { value: "all", children: "Alle rollen" }), availableRoles.map((role) => (_jsx(SelectItem, { value: role, children: role }, role))), _jsx(SelectItem, { value: "none", children: "Geen rollen" })] })] })] }), _jsxs("div", { className: "w-full sm:w-48 space-y-2", children: [_jsx(Label, { className: "text-sm font-medium", children: "Sorteren" }), _jsxs(Select, { value: sortBy, onValueChange: (value) => setSortBy(value), children: [_jsx(SelectTrigger, { children: _jsx(SelectValue, {}) }), _jsxs(SelectContent, { children: [_jsx(SelectItem, { value: "name-asc", children: "Naam A-Z" }), _jsx(SelectItem, { value: "name-desc", children: "Naam Z-A" }), _jsx(SelectItem, { value: "role-asc", children: "Rol A-Z" }), _jsx(SelectItem, { value: "role-desc", children: "Rol Z-A" })] })] })] })] }), loading ? (_jsx("p", { className: "text-sm text-muted-foreground", children: "Laden..." })) : filteredUsers.length === 0 ? (_jsx("p", { className: "text-sm text-muted-foreground", children: hasActiveFilters ? "Geen gebruikers gevonden met de huidige filters." : "Geen gebruikers gevonden." })) : (_jsxs(Table, { children: [_jsx(TableHeader, { children: _jsxs(TableRow, { children: [_jsx(TableHead, { children: "Gebruiker" }), _jsx(TableHead, { children: "E-mail" }), _jsx(TableHead, { children: "Rollen" }), _jsx(TableHead, { className: "w-24" })] }) }), _jsx(TableBody, { children: filteredUsers.map((user) => (_jsxs(TableRow, { children: [_jsx(TableCell, { className: "font-medium", children: _jsxs("div", { className: "flex items-center gap-2", children: [_jsx("span", { children: user.displayName }), !user.emailConfirmed && (_jsx(Badge, { variant: "outline", className: "text-xs", children: "Wacht op verificatie" })), user.isTestAccount && (_jsx(Badge, { variant: "secondary", className: "text-xs", children: "Test" }))] }) }), _jsx(TableCell, { children: user.email }), _jsx(TableCell, { children: _jsx("div", { className: "flex flex-wrap gap-2", children: user.roles.length === 0 ? (_jsx("span", { className: "text-sm text-muted-foreground", children: "Geen rollen" })) : (user.roles.map((role) => (_jsxs(Badge, { variant: "secondary", className: "gap-1", children: [role, !(role === "admin" && !isAdmin) && (_jsx("button", { onClick: () => handleRemove(user.id, role), className: "ml-1 rounded-sm hover:text-destructive", "aria-label": `Verwijder rol ${role}`, children: _jsx(Trash2, { className: "h-3 w-3" }) }))] }, `${user.id}-${role}`)))) }) }), _jsx(TableCell, { className: "text-right", children: _jsx(Button, { variant: "ghost", size: "sm", className: "text-destructive hover:text-destructive hover:bg-destructive/10", disabled: user.id === currentUserId || !canManageUser(user), title: user.id === currentUserId
439
- ? "Je kunt je eigen account niet verwijderen."
440
- : !canManageUser(user)
441
- ? "Alleen een admin kan een adminaccount verwijderen."
442
- : "Verwijder account", onClick: () => setDeleteTarget(user), "aria-label": `Verwijder account ${user.email}`, children: _jsx(UserX, { className: "h-4 w-4" }) }) })] }, user.id))) })] }))] })] }) }), isAdmin && (_jsx(TabsContent, { value: "permissions", className: "mt-4", children: _jsxs(Card, { children: [_jsx(CardHeader, { children: _jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Shield, { className: "h-5 w-5 text-primary" }), _jsx(CardTitle, { children: "Module-machtigingen per rol" })] }) }), _jsx(CardContent, { children: permLoading ? (_jsx("p", { className: "text-sm text-muted-foreground", children: "Laden..." })) : (_jsxs(Table, { children: [_jsx(TableHeader, { children: _jsxs(TableRow, { children: [_jsx(TableHead, { children: "Module" }), permRoles.map((role) => (_jsx(TableHead, { className: "capitalize", children: role }, role)))] }) }), _jsx(TableBody, { children: permModules.map((mod) => (_jsxs(TableRow, { children: [_jsx(TableCell, { className: "font-medium", children: mod.name }), permRoles.map((role) => {
443
- const value = permMatrix[permKey(role, mod.id)] ?? "none";
444
- return (_jsx(TableCell, { children: _jsxs(Select, { value: value, onValueChange: (v) => handlePermChange(role, mod.id, v), children: [_jsx(SelectTrigger, { className: "w-40", children: _jsx(SelectValue, {}) }), _jsx(SelectContent, { children: Object.keys(PERMISSION_LABELS).map((p) => (_jsx(SelectItem, { value: p, children: PERMISSION_LABELS[p] }, p))) })] }) }, role));
445
- })] }, mod.id))) })] })) })] }) })), _jsx(TabsContent, { value: "user-permissions", className: "mt-4", children: _jsxs(Card, { children: [_jsx(CardHeader, { children: _jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Shield, { className: "h-5 w-5 text-primary" }), _jsx(CardTitle, { children: "Rechten per gebruiker" })] }) }), _jsxs(CardContent, { className: "space-y-6", children: [_jsxs("div", { className: "w-full sm:w-96 space-y-2", children: [_jsx(Label, { className: "text-sm font-medium", children: "Gebruiker" }), _jsxs(Select, { value: permUserId, onValueChange: setPermUserId, children: [_jsx(SelectTrigger, { children: _jsx(SelectValue, { placeholder: "Selecteer een gebruiker" }) }), _jsx(SelectContent, { children: users.map((u) => (_jsxs(SelectItem, { value: u.id, children: [u.displayName, " (", u.email, ")"] }, u.id))) })] })] }), !permUserId ? (_jsx("p", { className: "text-sm text-muted-foreground", children: "Kies een gebruiker om persoonlijke uitzonderingen in te stellen. \"Volg rol\" gebruikt het recht dat via de rollen van deze gebruiker geldt." })) : userPermLoading ? (_jsx("p", { className: "text-sm text-muted-foreground", children: "Laden..." })) : userPermModules.length === 0 ? (_jsx("p", { className: "text-sm text-muted-foreground", children: "Er zijn nog geen modules geregistreerd voor deze shell." })) : (_jsxs(Table, { children: [_jsx(TableHeader, { children: _jsxs(TableRow, { children: [_jsx(TableHead, { children: "Module" }), _jsx(TableHead, { className: "w-56", children: "Recht" })] }) }), _jsx(TableBody, { children: userPermModules.map((mod) => (_jsxs(TableRow, { children: [_jsx(TableCell, { className: "font-medium", children: mod.name }), _jsx(TableCell, { children: _jsxs(Select, { value: userPermMap[mod.id] ?? "inherit", onValueChange: (v) => handleUserPermChange(mod.id, v), children: [_jsx(SelectTrigger, { className: "w-48", children: _jsx(SelectValue, {}) }), _jsx(SelectContent, { children: Object.keys(USER_PERMISSION_LABELS).map((p) => (_jsx(SelectItem, { value: p, children: USER_PERMISSION_LABELS[p] }, p))) })] }) })] }, mod.id))) })] }))] })] }) }), isAdmin && (_jsx(TabsContent, { value: "order", className: "mt-4", children: _jsxs(Card, { children: [_jsxs(CardHeader, { className: "flex flex-row items-center justify-between gap-4", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Shield, { className: "h-5 w-5 text-primary" }), _jsx(CardTitle, { children: "Volgorde van modules in de zijbalk" })] }), _jsx("span", { className: "text-sm text-muted-foreground", children: orderSaving ? "Opslaan..." : orderSavedAt ? "Opgeslagen" : "" })] }), _jsxs(CardContent, { className: "space-y-4", children: [_jsx("p", { className: "text-sm text-muted-foreground", children: "Zet de modules in de gewenste volgorde. Wijzigingen worden direct opgeslagen en meteen toegepast in de zijbalk. De startvolgorde is de volgorde waarin deze shell zijn modules aanlevert; nieuwe modules komen daarin vanzelf op hun plek." }), orderLoading ? (_jsx("p", { className: "text-sm text-muted-foreground", children: "Laden..." })) : orderModules.length === 0 ? (_jsx("p", { className: "text-sm text-muted-foreground", children: "Er zijn nog geen modules geregistreerd voor deze shell." })) : (_jsx("ul", { className: "divide-y rounded-md border", children: orderModules.map((mod, index) => (_jsxs("li", { className: "flex items-center justify-between gap-4 px-4 py-3", children: [_jsxs("div", { className: "flex items-center gap-3", children: [_jsxs("span", { className: "w-6 text-sm text-muted-foreground", children: [index + 1, "."] }), _jsx("span", { className: "font-medium", children: mod.name })] }), _jsxs("div", { className: "flex items-center gap-1", children: [_jsx(Button, { variant: "outline", size: "icon", "aria-label": `${mod.name} omhoog`, disabled: index === 0, onClick: () => moveModule(index, -1), children: _jsx(ArrowUp, { className: "h-4 w-4" }) }), _jsx(Button, { variant: "outline", size: "icon", "aria-label": `${mod.name} omlaag`, disabled: index === orderModules.length - 1, onClick: () => moveModule(index, 1), children: _jsx(ArrowDown, { className: "h-4 w-4" }) })] })] }, mod.id))) }))] })] }) }))] }), _jsx(Dialog, { open: inviteOpen, onOpenChange: setInviteOpen, children: _jsxs(DialogContent, { children: [_jsxs(DialogHeader, { children: [_jsx(DialogTitle, { children: "Nieuw account aanmaken" }), _jsx(DialogDescription, { children: "De gebruiker ontvangt een uitnodigingsmail en kiest zelf een wachtwoord." })] }), _jsxs("div", { className: "space-y-4", children: [_jsxs("div", { className: "space-y-2", children: [_jsx(Label, { htmlFor: "invite-name", children: "Naam" }), _jsx(Input, { id: "invite-name", value: inviteName, onChange: (e) => setInviteName(e.target.value), placeholder: "Jan Jansen", disabled: inviting })] }), _jsxs("div", { className: "space-y-2", children: [_jsx(Label, { htmlFor: "invite-email", children: "E-mail" }), _jsx(Input, { id: "invite-email", type: "email", value: inviteEmail, onChange: (e) => setInviteEmail(e.target.value), placeholder: "jan@voorbeeld.nl", disabled: inviting })] }), _jsxs("div", { className: "space-y-2", children: [_jsx(Label, { children: "Rol" }), _jsxs(Select, { value: inviteRole, onValueChange: (v) => setInviteRole(v), children: [_jsx(SelectTrigger, { children: _jsx(SelectValue, {}) }), _jsx(SelectContent, { children: assignableRoles.map((r) => (_jsx(SelectItem, { value: r, children: r }, r))) })] })] })] }), _jsxs(DialogFooter, { children: [_jsx(Button, { variant: "outline", onClick: () => setInviteOpen(false), disabled: inviting, children: "Annuleren" }), _jsx(Button, { onClick: handleInvite, disabled: inviting || !inviteEmail.trim() || !inviteName.trim(), children: inviting ? "Versturen..." : "Uitnodiging versturen" })] })] }) }), _jsx(Dialog, { open: testOpen, onOpenChange: (open) => !testCreating && setTestOpen(open), children: _jsxs(DialogContent, { children: [_jsxs(DialogHeader, { children: [_jsx(DialogTitle, { children: "Testaccount aanmaken" }), _jsx(DialogDescription, { children: "Maakt direct een geverifieerd account met een automatisch wachtwoord \u2014 geen e-mail nodig." })] }), _jsxs("div", { className: "space-y-4", children: [_jsxs("div", { className: "space-y-2", children: [_jsx(Label, { htmlFor: "test-name", children: "Naam" }), _jsx(Input, { id: "test-name", value: testName, onChange: (e) => setTestName(e.target.value), placeholder: "Test Monteur", disabled: testCreating })] }), _jsxs("div", { className: "space-y-2", children: [_jsx(Label, { children: "Rol" }), _jsxs(Select, { value: testRole, onValueChange: (v) => setTestRole(v), children: [_jsx(SelectTrigger, { children: _jsx(SelectValue, {}) }), _jsx(SelectContent, { children: assignableRoles.map((r) => (_jsx(SelectItem, { value: r, children: r }, r))) })] })] })] }), _jsxs(DialogFooter, { children: [_jsx(Button, { variant: "outline", onClick: () => setTestOpen(false), disabled: testCreating, children: "Annuleren" }), _jsx(Button, { onClick: handleCreateTestUser, disabled: testCreating || !testName.trim(), children: testCreating ? "Aanmaken..." : "Aanmaken" })] })] }) }), _jsx(Dialog, { open: testCredentials !== null, onOpenChange: (open) => !open && setTestCredentials(null), children: _jsxs(DialogContent, { children: [_jsxs(DialogHeader, { children: [_jsx(DialogTitle, { children: "Testaccount aangemaakt" }), _jsx(DialogDescription, { children: "Kopieer de inloggegevens nu \u2014 het wachtwoord wordt maar \u00E9\u00E9n keer getoond." })] }), _jsxs("div", { className: "space-y-3", children: [_jsxs("div", { className: "space-y-1", children: [_jsx(Label, { children: "E-mail" }), _jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Input, { readOnly: true, value: testCredentials?.email ?? "" }), _jsx(Button, { variant: "outline", size: "icon", onClick: () => testCredentials && copyToClipboard(testCredentials.email, "E-mail"), "aria-label": "Kopieer e-mail", children: _jsx(Copy, { className: "h-4 w-4" }) })] })] }), _jsxs("div", { className: "space-y-1", children: [_jsx(Label, { children: "Wachtwoord" }), _jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Input, { readOnly: true, value: testCredentials?.password ?? "", className: "font-mono" }), _jsx(Button, { variant: "outline", size: "icon", onClick: () => testCredentials && copyToClipboard(testCredentials.password, "Wachtwoord"), "aria-label": "Kopieer wachtwoord", children: _jsx(Copy, { className: "h-4 w-4" }) })] })] })] }), _jsx(DialogFooter, { children: _jsx(Button, { onClick: () => setTestCredentials(null), children: "Sluiten" }) })] }) }), _jsx(AlertDialog, { open: deleteTarget !== null, onOpenChange: (open) => !open && !deleting && setDeleteTarget(null), children: _jsxs(AlertDialogContent, { children: [_jsxs(AlertDialogHeader, { children: [_jsx(AlertDialogTitle, { children: "Account verwijderen?" }), _jsxs(AlertDialogDescription, { children: ["Weet je zeker dat je", " ", _jsx("strong", { children: deleteTarget?.displayName || deleteTarget?.email }), " ", "wilt verwijderen? Deze actie kan niet ongedaan worden gemaakt."] })] }), _jsxs(AlertDialogFooter, { children: [_jsx(AlertDialogCancel, { disabled: deleting, children: "Annuleren" }), _jsx(AlertDialogAction, { onClick: (e) => {
436
+ return (_jsxs("main", { className: "p-6 space-y-6", children: [_jsxs(Card, { children: [_jsx(CardHeader, { className: "space-y-4", children: _jsxs("div", { className: "flex flex-row items-start justify-between gap-4", children: [_jsxs("div", { className: "space-y-1", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Shield, { className: "h-5 w-5 text-primary" }), _jsx(CardTitle, { children: "Gebruikers & rollen" })] }), _jsx("p", { className: "text-sm text-muted-foreground", children: "Hier vind je alle gebruikers die toegang hebben en zie je welke rol zij hebben. Ook beheer je hier alle beschikbare rollen." })] }), _jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [isAdmin && activeTab === "roles" && (_jsxs(Button, { size: "sm", onClick: () => openRoleEditor(), children: [_jsx(Plus, { className: "h-4 w-4 mr-2" }), "Nieuwe rol"] })), activeTab === "users" && (_jsxs(_Fragment, { children: [_jsxs(Button, { variant: "outline", size: "sm", onClick: () => setTestOpen(true), children: [_jsx(FlaskConical, { className: "h-4 w-4 mr-2" }), "Testaccount"] }), _jsxs(Button, { variant: "outline", size: "sm", onClick: () => setInviteOpen(true), children: [_jsx(UserPlus, { className: "h-4 w-4 mr-2" }), "Account aanmaken"] })] }))] })] }) }), _jsx(CardContent, { children: _jsxs(Tabs, { value: activeTab, onValueChange: setActiveTab, children: [_jsxs(TabsList, { children: [_jsx(TabsTrigger, { value: "users", children: "Gebruikers" }), _jsx(TabsTrigger, { value: "roles", children: "Rollen" }), _jsx(TabsTrigger, { value: "user-permissions", children: "Rechten per gebruiker" }), _jsx(TabsTrigger, { value: "modules", children: "Modules" })] }), _jsxs(TabsContent, { value: "users", className: "mt-4 space-y-6", children: [_jsxs("div", { className: "flex flex-col gap-4 sm:flex-row sm:items-end", children: [_jsxs("div", { className: "flex-1 space-y-2", children: [_jsx(Label, { className: "text-sm font-medium", children: "Zoeken" }), _jsx(Input, { placeholder: "Zoek op naam of e-mail", value: searchQuery, onChange: (e) => setSearchQuery(e.target.value) })] }), _jsxs("div", { className: "w-full sm:w-48 space-y-2", children: [_jsx(Label, { className: "text-sm font-medium", children: "Filter op rol" }), _jsxs(Select, { value: roleFilter, onValueChange: setRoleFilter, children: [_jsx(SelectTrigger, { children: _jsx(SelectValue, {}) }), _jsxs(SelectContent, { children: [_jsx(SelectItem, { value: "all", children: "Alle rollen" }), availableRoles.map((role) => (_jsx(SelectItem, { value: role.id, children: role.name }, role.id))), _jsx(SelectItem, { value: "none", children: "Geen rollen" })] })] })] }), _jsxs("div", { className: "w-full sm:w-48 space-y-2", children: [_jsx(Label, { className: "text-sm font-medium", children: "Sorteren" }), _jsxs(Select, { value: sortBy, onValueChange: (value) => setSortBy(value), children: [_jsx(SelectTrigger, { children: _jsx(SelectValue, {}) }), _jsxs(SelectContent, { children: [_jsx(SelectItem, { value: "name-asc", children: "Naam A-Z" }), _jsx(SelectItem, { value: "name-desc", children: "Naam Z-A" }), _jsx(SelectItem, { value: "role-asc", children: "Rol A-Z" }), _jsx(SelectItem, { value: "role-desc", children: "Rol Z-A" })] })] })] })] }), loading ? (_jsx("p", { className: "text-sm text-muted-foreground", children: "Laden..." })) : filteredUsers.length === 0 ? (_jsx("p", { className: "text-sm text-muted-foreground", children: hasActiveFilters
437
+ ? "Geen gebruikers gevonden met de huidige filters."
438
+ : "Geen gebruikers gevonden." })) : (_jsxs(Table, { children: [_jsx(TableHeader, { children: _jsxs(TableRow, { children: [_jsx(TableHead, { children: "Gebruiker" }), _jsx(TableHead, { children: "Rollen" }), _jsx(TableHead, { className: "w-16" })] }) }), _jsx(TableBody, { children: filteredUsers.map((u) => {
439
+ const manageable = canManageUser(u);
440
+ const unassigned = assignableRoles.filter((r) => !u.roles.some((ur) => ur.id === r.id));
441
+ return (_jsxs(TableRow, { children: [_jsxs(TableCell, { children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx("span", { className: "font-medium", children: u.displayName }), !u.emailConfirmed && (_jsx(Badge, { variant: "outline", className: "text-xs", children: "Wacht op verificatie" })), u.isTestAccount && (_jsx(Badge, { variant: "secondary", className: "text-xs", children: "Test" }))] }), _jsx("div", { className: "text-xs text-muted-foreground", children: u.email })] }), _jsx(TableCell, { children: _jsx("div", { className: "flex flex-wrap gap-2", children: u.roles.length === 0 ? (_jsx("span", { className: "text-sm text-muted-foreground", children: "Geen rollen" })) : (u.roles.map((role) => (_jsx(Badge, { variant: "secondary", children: role.name }, `${u.id}-${role.id}`)))) }) }), _jsx(TableCell, { className: "text-right", children: _jsxs(DropdownMenu, { children: [_jsx(DropdownMenuTrigger, { asChild: true, children: _jsx(Button, { variant: "ghost", size: "sm", "aria-label": `Acties voor ${u.email}`, disabled: submitting, children: _jsx(MoreVertical, { className: "h-4 w-4" }) }) }), _jsxs(DropdownMenuContent, { align: "end", className: "w-52", children: [_jsxs(DropdownMenuSub, { children: [_jsx(DropdownMenuSubTrigger, { disabled: !manageable || unassigned.length === 0, children: "Rol toekennen" }), _jsx(DropdownMenuSubContent, { children: unassigned.length === 0 ? (_jsx(DropdownMenuItem, { disabled: true, children: "Geen rollen beschikbaar" })) : (unassigned.map((role) => (_jsx(DropdownMenuItem, { onSelect: () => handleAssign(u.id, role.id), children: role.name }, `assign-${u.id}-${role.id}`)))) })] }), _jsxs(DropdownMenuSub, { children: [_jsx(DropdownMenuSubTrigger, { disabled: u.roles.length === 0, children: "Rol verwijderen" }), _jsx(DropdownMenuSubContent, { children: u.roles.map((role) => (_jsx(DropdownMenuItem, { disabled: role.key === "admin" && !isAdmin, onSelect: () => handleRemove(u.id, role.id), children: role.name }, `remove-${u.id}-${role.id}`))) })] }), _jsx(DropdownMenuSeparator, {}), _jsxs(DropdownMenuItem, { disabled: !manageable, onSelect: () => openRename(u), children: [_jsx(Pencil, { className: "h-4 w-4 mr-2" }), "Naam wijzigen"] }), _jsxs(DropdownMenuItem, { className: "text-destructive focus:text-destructive", disabled: u.id === currentUserId || !manageable, onSelect: () => setDeleteTarget(u), children: [_jsx(UserX, { className: "h-4 w-4 mr-2" }), "Account verwijderen"] })] })] }) })] }, u.id));
442
+ }) })] }))] }), _jsx(TabsContent, { value: "roles", className: "mt-4", children: rolesLoading ? (_jsx("p", { className: "text-sm text-muted-foreground", children: "Laden..." })) : roles.length === 0 ? (_jsx("p", { className: "text-sm text-muted-foreground", children: "Er zijn nog geen rollen aangemaakt." })) : (_jsxs(Table, { children: [_jsx(TableHeader, { children: _jsxs(TableRow, { children: [_jsx(TableHead, { children: "Rol" }), _jsx(TableHead, { children: "Toegang" }), _jsx(TableHead, { className: "w-16" })] }) }), _jsx(TableBody, { children: roles.map((role) => {
443
+ const count = rolePermissions.filter((p) => p.roleId === role.id && p.featureId === "" && (p.canView || p.canEdit)).length;
444
+ return (_jsxs(TableRow, { children: [_jsx(TableCell, { className: "font-medium", children: _jsxs("div", { className: "flex items-center gap-2", children: [role.name, role.isProtected && (_jsx(Badge, { variant: "outline", className: "text-xs", children: "Beschermd" }))] }) }), _jsx(TableCell, { className: "text-sm text-muted-foreground", children: count === 0 ? "Geen modules" : `${count} module(s)` }), _jsx(TableCell, { className: "text-right", children: isAdmin && (_jsxs(DropdownMenu, { children: [_jsx(DropdownMenuTrigger, { asChild: true, children: _jsx(Button, { variant: "ghost", size: "sm", "aria-label": `Acties voor ${role.name}`, children: _jsx(MoreVertical, { className: "h-4 w-4" }) }) }), _jsxs(DropdownMenuContent, { align: "end", children: [_jsx(DropdownMenuItem, { onClick: () => openRoleEditor(role), children: "Bewerken" }), _jsx(DropdownMenuItem, { onClick: () => handleDuplicateRole(role), children: "Dupliceren" }), _jsx(DropdownMenuItem, { disabled: role.isProtected, className: "text-destructive", onClick: () => setRoleDeleteTarget(role), children: "Verwijderen" })] })] })) })] }, role.id));
445
+ }) })] })) }), _jsxs(TabsContent, { value: "user-permissions", className: "mt-4 space-y-6", children: [_jsxs("div", { className: "w-full sm:w-96 space-y-2", children: [_jsx(Label, { className: "text-sm font-medium", children: "Gebruiker" }), _jsxs(Select, { value: permUserId, onValueChange: setPermUserId, children: [_jsx(SelectTrigger, { children: _jsx(SelectValue, { placeholder: "Selecteer een gebruiker" }) }), _jsx(SelectContent, { children: users.map((u) => (_jsxs(SelectItem, { value: u.id, children: [u.displayName, " (", u.email, ")"] }, u.id))) })] })] }), !permUserId ? (_jsx("p", { className: "text-sm text-muted-foreground", children: "Kies een gebruiker om persoonlijke uitzonderingen in te stellen. \"Volg rol\" gebruikt het recht dat via de rollen van deze gebruiker geldt." })) : userPermLoading ? (_jsx("p", { className: "text-sm text-muted-foreground", children: "Laden..." })) : userPermModules.length === 0 ? (_jsx("p", { className: "text-sm text-muted-foreground", children: "Er zijn nog geen modules geregistreerd voor deze shell." })) : (_jsxs(Table, { children: [_jsx(TableHeader, { children: _jsxs(TableRow, { children: [_jsx(TableHead, { children: "Module / onderdeel" }), _jsx(TableHead, { className: "w-56", children: "Recht" })] }) }), _jsx(TableBody, { children: userPermModules.flatMap((mod) => {
446
+ const rows = [
447
+ _jsxs(TableRow, { children: [_jsx(TableCell, { className: "font-medium", children: mod.name }), _jsx(TableCell, { children: _jsxs(Select, { value: userPermMap[permCellKey(mod.id, "")] ?? "inherit", onValueChange: (v) => handleUserPermChange(mod.id, "", v), children: [_jsx(SelectTrigger, { className: "w-48", children: _jsx(SelectValue, {}) }), _jsx(SelectContent, { children: Object.keys(USER_PERMISSION_LABELS).map((p) => (_jsx(SelectItem, { value: p, children: USER_PERMISSION_LABELS[p] }, p))) })] }) })] }, mod.id),
448
+ ];
449
+ for (const f of mod.features ?? []) {
450
+ rows.push(_jsxs(TableRow, { children: [_jsx(TableCell, { className: "pl-8 text-sm text-muted-foreground", children: f.name }), _jsx(TableCell, { children: _jsxs(Select, { value: userPermMap[permCellKey(mod.id, f.id)] ?? "inherit", onValueChange: (v) => handleUserPermChange(mod.id, f.id, v), children: [_jsx(SelectTrigger, { className: "w-48", children: _jsx(SelectValue, {}) }), _jsx(SelectContent, { children: Object.keys(USER_PERMISSION_LABELS).map((p) => (_jsx(SelectItem, { value: p, children: USER_PERMISSION_LABELS[p] }, p))) })] }) })] }, `${mod.id}-${f.id}`));
451
+ }
452
+ return rows;
453
+ }) })] }))] }), _jsxs(TabsContent, { value: "modules", className: "mt-4 space-y-6", children: [_jsx("p", { className: "text-sm text-muted-foreground", children: "Schakel \"Toegang afdwingen\" in voor een module om de rollen en gebruikersrechten actief te laten controleren. Modules zonder deze schakelaar blijven voor iedereen toegankelijk (default-allow)." }), roleModules.length === 0 ? (_jsx("p", { className: "text-sm text-muted-foreground", children: "Er zijn nog geen modules geregistreerd." })) : (_jsxs(Table, { children: [_jsx(TableHeader, { children: _jsxs(TableRow, { children: [_jsx(TableHead, { children: "Module" }), _jsx(TableHead, { className: "w-48", children: "Toegang afdwingen" })] }) }), _jsx(TableBody, { children: roleModules.map((mod) => (_jsxs(TableRow, { children: [_jsx(TableCell, { className: "font-medium", children: mod.name }), _jsx(TableCell, { children: _jsxs("div", { className: "flex items-center gap-3", children: [_jsx(Switch, { id: `enforce-${mod.id}`, checked: mod.enforce ?? false, onCheckedChange: (checked) => handleEnforceChange(mod.id, checked), disabled: submitting || !isAdmin }), _jsx(Label, { htmlFor: `enforce-${mod.id}`, className: "text-sm font-normal", children: (mod.enforce ?? false) ? "Ingeschakeld" : "Uitgeschakeld" })] }) })] }, mod.id))) })] }))] })] }) })] }), _jsx(Dialog, { open: editor !== null, onOpenChange: (open) => !open && !editorSaving && setEditor(null), children: _jsxs(DialogContent, { className: "max-w-3xl", children: [_jsxs(DialogHeader, { children: [_jsx(DialogTitle, { children: editor?.roleId ? "Rol bewerken" : "Rol toevoegen" }), _jsx(DialogDescription, { children: "Maak hier een rol aan en wijs de gewenste rechten toe. Vink je een onderdeel niet aan? Dan heeft de gebruiker geen toegang tot (een deel van) die pagina." })] }), _jsxs("div", { className: "space-y-4", children: [_jsxs("div", { className: "space-y-2", children: [_jsx(Label, { htmlFor: "role-name", children: "Naam rol" }), _jsx(Input, { id: "role-name", value: editor?.name ?? "", placeholder: "Vul een naam voor de rol in.", onChange: (e) => setEditor((state) => (state ? { ...state, name: e.target.value } : state)), disabled: editorSaving })] }), _jsxs("div", { className: "max-h-[45vh] overflow-y-auto rounded-md border", children: [_jsxs("div", { className: "flex items-center justify-end gap-8 border-b px-4 py-2 text-sm font-medium", children: [_jsx("span", { className: "w-16 text-center", children: "Lezen" }), _jsx("span", { className: "w-16 text-center", children: "Bewerken" })] }), roleModules.length === 0 ? (_jsx("p", { className: "p-4 text-sm text-muted-foreground", children: "Er zijn nog geen modules geregistreerd voor deze shell." })) : (roleModules.map((mod) => {
454
+ const cell = editor?.cells[permCellKey(mod.id, "")] ?? {
455
+ canView: false,
456
+ canEdit: false,
457
+ };
458
+ const expanded = Boolean(expandedModules[mod.id]);
459
+ const hasFeatures = (mod.features ?? []).length > 0;
460
+ return (_jsxs("div", { className: "border-b last:border-b-0", children: [_jsxs("div", { className: "flex items-center px-4 py-3", children: [_jsxs("button", { type: "button", className: "flex flex-1 items-center gap-2 text-left font-medium", onClick: () => setExpandedModules((s) => ({ ...s, [mod.id]: !s[mod.id] })), disabled: !hasFeatures, children: [hasFeatures ? (expanded ? (_jsx(ChevronDown, { className: "h-4 w-4" })) : (_jsx(ChevronRight, { className: "h-4 w-4" }))) : (_jsx("span", { className: "h-4 w-4" })), mod.name] }), _jsxs("div", { className: "flex items-center gap-8", children: [_jsx("span", { className: "flex w-16 justify-center", children: _jsx(Checkbox, { checked: cell.canView, onCheckedChange: (v) => setCell(mod.id, "", "canView", Boolean(v)), "aria-label": `Lezen ${mod.name}` }) }), _jsx("span", { className: "flex w-16 justify-center", children: _jsx(Checkbox, { checked: cell.canEdit, onCheckedChange: (v) => setCell(mod.id, "", "canEdit", Boolean(v)), "aria-label": `Bewerken ${mod.name}` }) })] })] }), expanded &&
461
+ (mod.features ?? []).map((f) => {
462
+ const fcell = editor?.cells[permCellKey(mod.id, f.id)] ?? {
463
+ canView: false,
464
+ canEdit: false,
465
+ };
466
+ return (_jsxs("div", { className: "flex items-center bg-muted/40 px-4 py-2 pl-10 text-sm", children: [_jsx("span", { className: "flex-1", children: f.name }), _jsxs("div", { className: "flex items-center gap-8", children: [_jsx("span", { className: "flex w-16 justify-center", children: _jsx(Checkbox, { checked: fcell.canView, onCheckedChange: (v) => setCell(mod.id, f.id, "canView", Boolean(v)), "aria-label": `Lezen ${mod.name} ${f.name}` }) }), _jsx("span", { className: "flex w-16 justify-center", children: _jsx(Checkbox, { checked: fcell.canEdit, onCheckedChange: (v) => setCell(mod.id, f.id, "canEdit", Boolean(v)), "aria-label": `Bewerken ${mod.name} ${f.name}` }) })] })] }, f.id));
467
+ })] }, mod.id));
468
+ }))] })] }), _jsxs(DialogFooter, { children: [_jsx(Button, { variant: "outline", onClick: () => setEditor(null), disabled: editorSaving, children: "Annuleren" }), _jsx(Button, { onClick: handleSaveRole, disabled: editorSaving || !editor?.name.trim(), children: editorSaving ? "Opslaan..." : "Opslaan" })] })] }) }), _jsx(Dialog, { open: inviteOpen, onOpenChange: setInviteOpen, children: _jsxs(DialogContent, { children: [_jsxs(DialogHeader, { children: [_jsx(DialogTitle, { children: "Nieuw account aanmaken" }), _jsx(DialogDescription, { children: "De gebruiker ontvangt een uitnodigingsmail en kiest zelf een wachtwoord." })] }), _jsxs("div", { className: "space-y-4", children: [_jsxs("div", { className: "space-y-2", children: [_jsx(Label, { htmlFor: "invite-name", children: "Naam" }), _jsx(Input, { id: "invite-name", value: inviteName, onChange: (e) => setInviteName(e.target.value), placeholder: "Jan Jansen", disabled: inviting })] }), _jsxs("div", { className: "space-y-2", children: [_jsx(Label, { htmlFor: "invite-email", children: "E-mail" }), _jsx(Input, { id: "invite-email", type: "email", value: inviteEmail, onChange: (e) => setInviteEmail(e.target.value), placeholder: "jan@voorbeeld.nl", disabled: inviting })] }), _jsxs("div", { className: "space-y-2", children: [_jsx(Label, { children: "Rol" }), _jsxs(Select, { value: inviteRole, onValueChange: setInviteRole, children: [_jsx(SelectTrigger, { children: _jsx(SelectValue, { placeholder: "Kies rol" }) }), _jsx(SelectContent, { children: assignableRoles.map((r) => (_jsx(SelectItem, { value: r.id, children: r.name }, r.id))) })] })] })] }), _jsxs(DialogFooter, { children: [_jsx(Button, { variant: "outline", onClick: () => setInviteOpen(false), disabled: inviting, children: "Annuleren" }), _jsx(Button, { onClick: handleInvite, disabled: inviting || !inviteEmail.trim() || !inviteName.trim() || !inviteRole, children: inviting ? "Versturen..." : "Uitnodiging versturen" })] })] }) }), _jsx(Dialog, { open: testOpen, onOpenChange: (open) => !testCreating && setTestOpen(open), children: _jsxs(DialogContent, { children: [_jsxs(DialogHeader, { children: [_jsx(DialogTitle, { children: "Testaccount aanmaken" }), _jsx(DialogDescription, { children: "Maakt direct een geverifieerd account met een automatisch wachtwoord \u2014 geen e-mail nodig." })] }), _jsxs("div", { className: "space-y-4", children: [_jsxs("div", { className: "space-y-2", children: [_jsx(Label, { htmlFor: "test-name", children: "Naam" }), _jsx(Input, { id: "test-name", value: testName, onChange: (e) => setTestName(e.target.value), placeholder: "Test Monteur", disabled: testCreating })] }), _jsxs("div", { className: "space-y-2", children: [_jsx(Label, { children: "Rol" }), _jsxs(Select, { value: testRole, onValueChange: setTestRole, children: [_jsx(SelectTrigger, { children: _jsx(SelectValue, { placeholder: "Kies rol" }) }), _jsx(SelectContent, { children: assignableRoles.map((r) => (_jsx(SelectItem, { value: r.id, children: r.name }, r.id))) })] })] })] }), _jsxs(DialogFooter, { children: [_jsx(Button, { variant: "outline", onClick: () => setTestOpen(false), disabled: testCreating, children: "Annuleren" }), _jsx(Button, { onClick: handleCreateTestUser, disabled: testCreating || !testName.trim() || !testRole, children: testCreating ? "Aanmaken..." : "Aanmaken" })] })] }) }), _jsx(Dialog, { open: testCredentials !== null, onOpenChange: (open) => !open && setTestCredentials(null), children: _jsxs(DialogContent, { children: [_jsxs(DialogHeader, { children: [_jsx(DialogTitle, { children: "Testaccount aangemaakt" }), _jsx(DialogDescription, { children: "Kopieer de inloggegevens nu \u2014 het wachtwoord wordt maar \u00E9\u00E9n keer getoond." })] }), _jsxs("div", { className: "space-y-3", children: [_jsxs("div", { className: "space-y-1", children: [_jsx(Label, { children: "E-mail" }), _jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Input, { readOnly: true, value: testCredentials?.email ?? "" }), _jsx(Button, { variant: "outline", size: "icon", onClick: () => testCredentials && copyToClipboard(testCredentials.email, "E-mail"), "aria-label": "Kopieer e-mail", children: _jsx(Copy, { className: "h-4 w-4" }) })] })] }), _jsxs("div", { className: "space-y-1", children: [_jsx(Label, { children: "Wachtwoord" }), _jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Input, { readOnly: true, value: testCredentials?.password ?? "", className: "font-mono" }), _jsx(Button, { variant: "outline", size: "icon", onClick: () => testCredentials && copyToClipboard(testCredentials.password, "Wachtwoord"), "aria-label": "Kopieer wachtwoord", children: _jsx(Copy, { className: "h-4 w-4" }) })] })] })] }), _jsx(DialogFooter, { children: _jsx(Button, { onClick: () => setTestCredentials(null), children: "Sluiten" }) })] }) }), _jsx(AlertDialog, { open: roleDeleteTarget !== null, onOpenChange: (open) => !open && setRoleDeleteTarget(null), children: _jsxs(AlertDialogContent, { children: [_jsxs(AlertDialogHeader, { children: [_jsx(AlertDialogTitle, { children: "Rol verwijderen?" }), _jsxs(AlertDialogDescription, { children: ["Weet je zeker dat je de rol ", _jsx("strong", { children: roleDeleteTarget?.name }), " wilt verwijderen? Gebruikers met deze rol verliezen de bijbehorende rechten."] })] }), _jsxs(AlertDialogFooter, { children: [_jsx(AlertDialogCancel, { children: "Annuleren" }), _jsx(AlertDialogAction, { onClick: (e) => {
469
+ e.preventDefault();
470
+ handleDeleteRole();
471
+ }, className: "bg-destructive text-destructive-foreground hover:bg-destructive/90", children: "Verwijderen" })] })] }) }), _jsx(Dialog, { open: renameTarget !== null, onOpenChange: (open) => !open && setRenameTarget(null), children: _jsxs(DialogContent, { children: [_jsxs(DialogHeader, { children: [_jsx(DialogTitle, { children: "Naam wijzigen" }), _jsxs(DialogDescription, { children: ["Pas de weergavenaam van ", renameTarget?.email, " aan."] })] }), _jsxs("div", { className: "space-y-2", children: [_jsx(Label, { htmlFor: "rename-input", children: "Naam" }), _jsx(Input, { id: "rename-input", value: renameValue, onChange: (e) => setRenameValue(e.target.value), placeholder: "Naam van de gebruiker" })] }), _jsxs(DialogFooter, { children: [_jsx(Button, { variant: "outline", onClick: () => setRenameTarget(null), disabled: renaming, children: "Annuleren" }), _jsx(Button, { onClick: handleRename, disabled: renaming || !renameValue.trim(), children: renaming ? "Opslaan..." : "Opslaan" })] })] }) }), _jsx(AlertDialog, { open: deleteTarget !== null, onOpenChange: (open) => !open && !deleting && setDeleteTarget(null), children: _jsxs(AlertDialogContent, { children: [_jsxs(AlertDialogHeader, { children: [_jsx(AlertDialogTitle, { children: "Account verwijderen?" }), _jsxs(AlertDialogDescription, { children: ["Weet je zeker dat je ", _jsx("strong", { children: deleteTarget?.displayName || deleteTarget?.email }), " ", "wilt verwijderen? Deze actie kan niet ongedaan worden gemaakt."] })] }), _jsxs(AlertDialogFooter, { children: [_jsx(AlertDialogCancel, { disabled: deleting, children: "Annuleren" }), _jsx(AlertDialogAction, { onClick: (e) => {
446
472
  e.preventDefault();
447
473
  handleDelete();
448
474
  }, disabled: deleting, className: "bg-destructive text-destructive-foreground hover:bg-destructive/90", children: deleting ? "Verwijderen..." : "Verwijderen" })] })] }) })] }));