@flowselections/admin-installatietechniek 1.0.8 → 1.0.10

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.
@@ -1,13 +1,12 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
2
  import { useEffect, useMemo, useRef, useState } from "react";
3
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 { Building2, ChevronDown, ChevronRight, Copy, FlaskConical, MoreVertical, Plus, Pencil, Shield, UserPlus, UserX, ArrowLeft, } from "lucide-react";
5
- import { assignRole, createRole, createProfile, createTestUser, deleteProfile, deleteRole, deleteUser, duplicateProfile, duplicateRole, getProfiles, getRoles, getUserModulePermissions, getUsersWithRoles, inviteUser, removeRole, setActiveProfile, setModuleEnforce, setUserModulePermission, syncModuleRegistry, updateProfile, updateRole, updateUserName, } from "../lib/admin.functions";
4
+ import { ChevronDown, ChevronRight, Copy, FlaskConical, MoreVertical, Plus, Pencil, Shield, UserPlus, UserX, AlertTriangle, } from "lucide-react";
5
+ import { assignRole, createRole, createTestUser, deleteRole, deleteUser, duplicateRole, getRoles, getUserModulePermissions, getUsersWithRoles, inviteUser, removeRole, setModuleEnforce, setUserModulePermission, syncModuleRegistry, updateRole, updateUserName, updateUserContact, } from "../lib/admin.functions";
6
6
  import { useDiscoveredModules } from "../lib/moduleDiscovery";
7
7
  import { supabase } from "../integrations/supabase/client";
8
- const ACTIVE_PROFILE_STORAGE_KEY = "flowselections.admin.activeProfileId";
9
- /** Sentinelwaarde in de profielkiezer voor "geen profiel actief". */
10
- const ALL_PROFILES_VALUE = "__all__";
8
+ const CONTACT_ROLE_KEYS = ["monteur", "planner", "administratie"];
9
+ const PHONE_RE = /^[0-9+()\-\s]{8,25}$/;
11
10
  const USER_PERMISSION_LABELS = {
12
11
  inherit: "Volg rol",
13
12
  none: "Geen toegang",
@@ -40,10 +39,12 @@ async function refreshOwnSessionIfSelf(targetUserId, currentUserId) {
40
39
  }
41
40
  export function AdminForInstallatieTechniek({ modules, shellId } = {}) {
42
41
  const auth = useAuth();
43
- const { isAdmin, user } = auth;
42
+ const { user } = auth;
44
43
  const authRoles = Array.isArray(auth.roles)
45
44
  ? auth.roles
46
45
  : [];
46
+ // Superadmin mag alles wat admin mag.
47
+ const isAdmin = Boolean(auth.isAdmin) || authRoles.includes("superadmin");
47
48
  const isAdministratie = authRoles.includes("administratie");
48
49
  const canAccess = isAdmin || isAdministratie;
49
50
  const currentUserId = user?.id;
@@ -56,9 +57,12 @@ export function AdminForInstallatieTechniek({ modules, shellId } = {}) {
56
57
  const usersRequestRef = useRef(0);
57
58
  const rolesRequestRef = useRef(0);
58
59
  const [submitting, setSubmitting] = useState(false);
59
- const [renameTarget, setRenameTarget] = useState(null);
60
+ const [editTarget, setEditTarget] = useState(null);
61
+ const [detailTarget, setDetailTarget] = useState(null);
62
+ const [editSaving, setEditSaving] = useState(false);
60
63
  const [renameValue, setRenameValue] = useState("");
61
- const [renaming, setRenaming] = useState(false);
64
+ const [contactAddress, setContactAddress] = useState("");
65
+ const [contactPhone, setContactPhone] = useState("");
62
66
  const [roles, setRoles] = useState([]);
63
67
  const [roleModules, setRoleModules] = useState([]);
64
68
  const [rolePermissions, setRolePermissions] = useState([]);
@@ -71,6 +75,8 @@ export function AdminForInstallatieTechniek({ modules, shellId } = {}) {
71
75
  const [inviteName, setInviteName] = useState("");
72
76
  const [inviteEmail, setInviteEmail] = useState("");
73
77
  const [inviteRole, setInviteRole] = useState("");
78
+ const [inviteAddress, setInviteAddress] = useState("");
79
+ const [invitePhone, setInvitePhone] = useState("");
74
80
  const [inviting, setInviting] = useState(false);
75
81
  const [deleteTarget, setDeleteTarget] = useState(null);
76
82
  const [deleting, setDeleting] = useState(false);
@@ -79,24 +85,10 @@ export function AdminForInstallatieTechniek({ modules, shellId } = {}) {
79
85
  const [testNameTouched, setTestNameTouched] = useState(false);
80
86
  const [testRole, setTestRole] = useState("");
81
87
  const [testCreating, setTestCreating] = useState(false);
82
- const [profileLocked, setProfileLocked] = useState(false);
83
- const [lockedProfile, setLockedProfile] = useState(null);
84
88
  const [testCredentials, setTestCredentials] = useState(null);
85
89
  const [searchQuery, setSearchQuery] = useState("");
86
90
  const [roleFilter, setRoleFilter] = useState("all");
87
91
  const [sortBy, setSortBy] = useState("name-asc");
88
- const [orgProfiles, setOrgProfiles] = useState([]);
89
- const [tenants, setTenants] = useState([]);
90
- const [profilesLoading, setProfilesLoading] = useState(false);
91
- const [activeProfileId, setActiveProfileId] = useState(() => {
92
- if (typeof window === "undefined")
93
- return "";
94
- return window.localStorage.getItem(ACTIVE_PROFILE_STORAGE_KEY) ?? "";
95
- });
96
- const [profileEditor, setProfileEditor] = useState(null);
97
- const [allProfileRoles, setAllProfileRoles] = useState([]);
98
- const [profileSaving, setProfileSaving] = useState(false);
99
- const [profileDeleteTarget, setProfileDeleteTarget] = useState(null);
100
92
  const [permUserId, setPermUserId] = useState("");
101
93
  const [userPermModules, setUserPermModules] = useState([]);
102
94
  const [userPermMap, setUserPermMap] = useState({});
@@ -110,21 +102,15 @@ export function AdminForInstallatieTechniek({ modules, shellId } = {}) {
110
102
  const shellModulesKey = shellModules
111
103
  .map((m) => `${m.id}:${m.name}:${(m.features ?? []).map((f) => `${f.id}=${f.name}`).join(",")}`)
112
104
  .join("|");
113
- const loadUsers = async (profileId = activeProfileId) => {
105
+ const loadUsers = async () => {
114
106
  const requestId = ++usersRequestRef.current;
115
107
  setLoading(true);
116
108
  setUsersError(null);
117
109
  try {
118
- const result = await getUsersWithRoles({
119
- data: { profileId: profileId || undefined },
120
- });
110
+ const result = await getUsersWithRoles();
121
111
  if (requestId !== usersRequestRef.current)
122
112
  return;
123
113
  setUsers(Array.isArray(result?.users) ? result.users : []);
124
- setProfileLocked(Boolean(result?.profileLocked));
125
- setLockedProfile(result?.profileLocked
126
- ? result?.activeProfile ?? null
127
- : null);
128
114
  const roleList = Array.isArray(result?.availableRoles) ? result.availableRoles : [];
129
115
  setAvailableRoles(roleList);
130
116
  const fallback = roleList[0]?.id ?? "";
@@ -143,14 +129,12 @@ export function AdminForInstallatieTechniek({ modules, shellId } = {}) {
143
129
  setLoading(false);
144
130
  }
145
131
  };
146
- const loadRoles = async (profileId = activeProfileId) => {
132
+ const loadRoles = async () => {
147
133
  const requestId = ++rolesRequestRef.current;
148
134
  setRolesLoading(true);
149
135
  setRolesError(null);
150
136
  try {
151
- const result = await getRoles({
152
- data: { profileId: profileId || undefined },
153
- });
137
+ const result = await getRoles();
154
138
  if (requestId !== rolesRequestRef.current)
155
139
  return;
156
140
  setRoles(Array.isArray(result?.roles) ? result.roles : []);
@@ -169,25 +153,6 @@ export function AdminForInstallatieTechniek({ modules, shellId } = {}) {
169
153
  setRolesLoading(false);
170
154
  }
171
155
  };
172
- const loadProfiles = async () => {
173
- if (!isAdmin)
174
- return;
175
- setProfilesLoading(true);
176
- try {
177
- const result = await getProfiles();
178
- const list = Array.isArray(result?.profiles) ? result.profiles : [];
179
- setOrgProfiles(list);
180
- setTenants(Array.isArray(result?.tenants) ? result.tenants : []);
181
- // Nooit automatisch een profiel kiezen: leeg = overzicht (alle bedrijven).
182
- setActiveProfileId((current) => current && list.some((p) => p.id === current) ? current : "");
183
- }
184
- catch (error) {
185
- toast.error(getErrorMessage(error, "Fout bij het laden van bedrijfsprofielen"));
186
- }
187
- finally {
188
- setProfilesLoading(false);
189
- }
190
- };
191
156
  const loadUserPermissions = async (userId) => {
192
157
  if (!userId) {
193
158
  setUserPermModules([]);
@@ -218,7 +183,7 @@ export function AdminForInstallatieTechniek({ modules, shellId } = {}) {
218
183
  setUserPermLoading(false);
219
184
  }
220
185
  };
221
- // Registratie van shell-modules + profielen: los van de profielcontext.
186
+ // Registratie van shell-modules.
222
187
  useEffect(() => {
223
188
  if (!canAccess)
224
189
  return;
@@ -232,41 +197,19 @@ export function AdminForInstallatieTechniek({ modules, shellId } = {}) {
232
197
  console.error("Module-registratie mislukt", error);
233
198
  }
234
199
  }
235
- if (cancelled)
236
- return;
237
- loadProfiles();
238
200
  })();
239
201
  return () => {
240
202
  cancelled = true;
241
203
  };
242
204
  // eslint-disable-next-line react-hooks/exhaustive-deps
243
205
  }, [canAccess, isAdmin, shellModulesKey, effectiveShellId]);
244
- useEffect(() => {
245
- if (typeof window === "undefined")
246
- return;
247
- if (activeProfileId)
248
- window.localStorage.setItem(ACTIVE_PROFILE_STORAGE_KEY, activeProfileId);
249
- else
250
- window.localStorage.removeItem(ACTIVE_PROFILE_STORAGE_KEY);
251
- }, [activeProfileId]);
252
- // Eén gecontroleerde laadactie per profielcontext (ook bij eerste render):
253
- // profielgebonden state resetten en opnieuw laden binnen de nieuwe context.
254
206
  useEffect(() => {
255
207
  if (!canAccess)
256
208
  return;
257
- setUsers([]);
258
- setRoles([]);
259
- setAvailableRoles([]);
260
- setRolePermissions([]);
261
- setSearchQuery("");
262
- setRoleFilter("all");
263
- setPermUserId("");
264
- setUserPermModules([]);
265
- setUserPermMap({});
266
- loadUsers(activeProfileId);
267
- loadRoles(activeProfileId);
209
+ loadUsers();
210
+ loadRoles();
268
211
  // eslint-disable-next-line react-hooks/exhaustive-deps
269
- }, [activeProfileId, canAccess]);
212
+ }, [canAccess]);
270
213
  useEffect(() => {
271
214
  if (!canAccess)
272
215
  return;
@@ -305,7 +248,7 @@ export function AdminForInstallatieTechniek({ modules, shellId } = {}) {
305
248
  setSubmitting(true);
306
249
  try {
307
250
  await assignRole({
308
- data: { userId, roleId, ...(activeProfileId ? { profileId: activeProfileId } : {}) },
251
+ data: { userId, roleId },
309
252
  });
310
253
  await refreshOwnSessionIfSelf(userId, currentUserId);
311
254
  toast.success("Rol toegekend");
@@ -367,6 +310,28 @@ export function AdminForInstallatieTechniek({ modules, shellId } = {}) {
367
310
  return { ...state, cells };
368
311
  });
369
312
  };
313
+ // Zet één kolom (Lezen of Bewerken) in één keer aan/uit voor alle modules
314
+ // en onderdelen. Handig bij lange modulelijsten.
315
+ const setAllCells = (field, value) => {
316
+ setEditor((state) => {
317
+ if (!state)
318
+ return state;
319
+ const cells = {};
320
+ for (const [key, current] of Object.entries(state.cells)) {
321
+ // Alleen de aangeklikte kolom aanpassen: geen cascade tussen
322
+ // Lezen en Bewerken, zodat "Niets" bij Lezen de Bewerken-vinkjes
323
+ // ongemoeid laat (en andersom).
324
+ cells[key] = { ...current, [field]: value };
325
+ }
326
+ return { ...state, cells };
327
+ });
328
+ };
329
+ const allCellsOn = (field) => {
330
+ if (!editor)
331
+ return false;
332
+ const values = Object.values(editor.cells);
333
+ return values.length > 0 && values.every((cell) => cell[field]);
334
+ };
370
335
  const handleSaveRole = async () => {
371
336
  if (!editor || !editor.name.trim())
372
337
  return;
@@ -417,9 +382,20 @@ export function AdminForInstallatieTechniek({ modules, shellId } = {}) {
417
382
  toast.error(getErrorMessage(error, "Verwijderen mislukt"));
418
383
  }
419
384
  };
385
+ const inviteRoleKey = roles.find((r) => r.id === inviteRole)?.key ?? "";
386
+ const inviteContactRequired = CONTACT_ROLE_KEYS.includes(inviteRoleKey);
387
+ const inviteContactOk = !inviteContactRequired || inviteAddress.trim().length >= 5;
420
388
  const handleInvite = async () => {
421
389
  if (!inviteEmail.trim() || !inviteName.trim() || !inviteRole)
422
390
  return;
391
+ if (!inviteContactOk) {
392
+ toast.error("Vul een adres in (minimaal 5 tekens).");
393
+ return;
394
+ }
395
+ if (invitePhone.trim() && !PHONE_RE.test(invitePhone.trim())) {
396
+ toast.error("Ongeldig telefoonnummer.");
397
+ return;
398
+ }
423
399
  setInviting(true);
424
400
  try {
425
401
  await inviteUser({
@@ -427,13 +403,16 @@ export function AdminForInstallatieTechniek({ modules, shellId } = {}) {
427
403
  email: inviteEmail.trim(),
428
404
  displayName: inviteName.trim(),
429
405
  roleId: inviteRole,
430
- ...(activeProfileId ? { profileId: activeProfileId } : {}),
406
+ address: inviteAddress.trim(),
407
+ phone: invitePhone.trim(),
431
408
  },
432
409
  });
433
410
  toast.success(`Uitnodiging verstuurd naar ${inviteEmail.trim()}`);
434
411
  setInviteOpen(false);
435
412
  setInviteName("");
436
413
  setInviteEmail("");
414
+ setInviteAddress("");
415
+ setInvitePhone("");
437
416
  await loadUsers();
438
417
  }
439
418
  catch (error) {
@@ -443,6 +422,7 @@ export function AdminForInstallatieTechniek({ modules, shellId } = {}) {
443
422
  setInviting(false);
444
423
  }
445
424
  };
425
+ const needsContact = (u) => u.roles.some((r) => CONTACT_ROLE_KEYS.includes(r.key));
446
426
  const handleDelete = async () => {
447
427
  if (!deleteTarget)
448
428
  return;
@@ -460,25 +440,47 @@ export function AdminForInstallatieTechniek({ modules, shellId } = {}) {
460
440
  setDeleting(false);
461
441
  }
462
442
  };
463
- const openRename = (target) => {
464
- setRenameTarget(target);
443
+ const openEdit = (target) => {
444
+ setEditTarget(target);
465
445
  setRenameValue(target.displayName === "-" ? "" : target.displayName);
446
+ setContactAddress(target.address ?? "");
447
+ setContactPhone(target.phone ?? "");
466
448
  };
467
- const handleRename = async () => {
468
- if (!renameTarget || !renameValue.trim())
449
+ const handleSaveEdit = async () => {
450
+ if (!editTarget)
451
+ return;
452
+ const name = renameValue.trim();
453
+ if (!name) {
454
+ toast.error("Vul een naam in");
455
+ return;
456
+ }
457
+ if (needsContact(editTarget) && contactAddress.trim().length < 5) {
458
+ toast.error("Vul een adres in (minimaal 5 tekens).");
459
+ return;
460
+ }
461
+ if (contactPhone.trim() && !PHONE_RE.test(contactPhone.trim())) {
462
+ toast.error("Ongeldig telefoonnummer.");
469
463
  return;
470
- setRenaming(true);
464
+ }
465
+ setEditSaving(true);
471
466
  try {
472
- await updateUserName({ data: { userId: renameTarget.id, displayName: renameValue.trim() } });
473
- toast.success("Naam bijgewerkt");
474
- setRenameTarget(null);
467
+ await updateUserName({ data: { userId: editTarget.id, displayName: name } });
468
+ await updateUserContact({
469
+ data: {
470
+ userId: editTarget.id,
471
+ address: contactAddress.trim(),
472
+ phone: contactPhone.trim(),
473
+ },
474
+ });
475
+ toast.success("Gegevens bijgewerkt");
476
+ setEditTarget(null);
475
477
  await loadUsers();
476
478
  }
477
479
  catch (error) {
478
- toast.error(getErrorMessage(error, "Naam wijzigen mislukt"));
480
+ toast.error(getErrorMessage(error, "Opslaan mislukt"));
479
481
  }
480
482
  finally {
481
- setRenaming(false);
483
+ setEditSaving(false);
482
484
  }
483
485
  };
484
486
  const handleCreateTestUser = async () => {
@@ -490,7 +492,6 @@ export function AdminForInstallatieTechniek({ modules, shellId } = {}) {
490
492
  data: {
491
493
  displayName: testName.trim(),
492
494
  roleId: testRole,
493
- ...(activeProfileId ? { profileId: activeProfileId } : {}),
494
495
  },
495
496
  });
496
497
  setTestCredentials({ email: result.email, password: result.password });
@@ -515,99 +516,6 @@ export function AdminForInstallatieTechniek({ modules, shellId } = {}) {
515
516
  toast.error("Kopiëren mislukt");
516
517
  }
517
518
  };
518
- const openProfileEditor = (profile) => {
519
- // Profielen mogen rollen buiten de huidige scope bevatten: haal de volledige lijst op.
520
- getRoles({ data: {} })
521
- .then((result) => setAllProfileRoles(Array.isArray(result?.roles) ? result.roles : []))
522
- .catch(() => setAllProfileRoles([]));
523
- setProfileEditor({
524
- profileId: profile?.id ?? null,
525
- name: profile?.name ?? "",
526
- tenantId: profile?.tenantId ?? "",
527
- tenantName: profile?.tenantName ?? profile?.name ?? "",
528
- tenantNameTouched: Boolean(profile),
529
- roleIds: profile?.roleIds ?? [],
530
- moduleIds: profile?.moduleIds ?? [],
531
- });
532
- };
533
- const toggleProfileValue = (field, value) => {
534
- setProfileEditor((state) => {
535
- if (!state)
536
- return state;
537
- const list = state[field];
538
- return {
539
- ...state,
540
- [field]: list.includes(value) ? list.filter((v) => v !== value) : [...list, value],
541
- };
542
- });
543
- };
544
- const handleSaveProfile = async () => {
545
- if (!profileEditor || !profileEditor.name.trim())
546
- return;
547
- setProfileSaving(true);
548
- try {
549
- const payload = {
550
- name: profileEditor.name.trim(),
551
- tenantId: profileEditor.tenantId ? profileEditor.tenantId : null,
552
- // Leeg gelaten? Dan gebruiken we de profielnaam als bedrijfsnaam.
553
- tenantName: (profileEditor.tenantName.trim() || profileEditor.name.trim()),
554
- roleIds: profileEditor.roleIds,
555
- moduleIds: profileEditor.moduleIds,
556
- };
557
- if (profileEditor.profileId) {
558
- await updateProfile({ data: { profileId: profileEditor.profileId, ...payload } });
559
- toast.success("Bedrijfsprofiel bijgewerkt");
560
- }
561
- else {
562
- await createProfile({ data: payload });
563
- toast.success("Bedrijfsprofiel aangemaakt");
564
- }
565
- setProfileEditor(null);
566
- await loadProfiles();
567
- await Promise.all([loadUsers(), loadRoles()]);
568
- }
569
- catch (error) {
570
- toast.error(getErrorMessage(error, "Opslaan mislukt"));
571
- }
572
- finally {
573
- setProfileSaving(false);
574
- }
575
- };
576
- const handleDuplicateProfile = async (profile) => {
577
- try {
578
- await duplicateProfile({ data: { profileId: profile.id } });
579
- toast.success("Bedrijfsprofiel gedupliceerd");
580
- await loadProfiles();
581
- }
582
- catch (error) {
583
- toast.error(getErrorMessage(error, "Dupliceren mislukt"));
584
- }
585
- };
586
- const handleDeleteProfile = async () => {
587
- if (!profileDeleteTarget)
588
- return;
589
- try {
590
- await deleteProfile({ data: { profileId: profileDeleteTarget.id } });
591
- toast.success("Bedrijfsprofiel verwijderd");
592
- setProfileDeleteTarget(null);
593
- await loadProfiles();
594
- }
595
- catch (error) {
596
- toast.error(getErrorMessage(error, "Verwijderen mislukt"));
597
- }
598
- };
599
- const handleSetActiveProfile = async (profile) => {
600
- try {
601
- await setActiveProfile({ data: { profileId: profile.id } });
602
- setActiveProfileId(profile.id);
603
- toast.success(`${profile.name} is nu het standaardprofiel`);
604
- await loadProfiles();
605
- }
606
- catch (error) {
607
- toast.error(getErrorMessage(error, "Instellen mislukt"));
608
- }
609
- };
610
- const allRolesForProfile = allProfileRoles.length > 0 ? allProfileRoles : roles;
611
519
  const filteredUsers = useMemo(() => {
612
520
  const query = searchQuery.trim().toLowerCase();
613
521
  const list = users.filter((u) => {
@@ -644,20 +552,23 @@ export function AdminForInstallatieTechniek({ modules, shellId } = {}) {
644
552
  setTestName(suggestedTestName);
645
553
  setTestOpen(true);
646
554
  };
647
- const canManageUser = (target) => isAdmin || !target.roles.some((r) => r.key === "admin");
555
+ const canManageUser = (target) => isAdmin || !target.roles.some((r) => r.key === "admin" || r.key === "superadmin");
648
556
  if (!canAccess) {
649
557
  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." }) }) }) }));
650
558
  }
651
- return (_jsxs("main", { className: "p-6 space-y-6", children: [_jsxs(Card, { children: [_jsxs(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 && orgProfiles.length > 0 && (_jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Building2, { className: "h-4 w-4 text-muted-foreground" }), _jsxs(Select, { value: activeProfileId || ALL_PROFILES_VALUE, onValueChange: (value) => setActiveProfileId(value === ALL_PROFILES_VALUE ? "" : value), children: [_jsx(SelectTrigger, { className: "w-56", children: _jsx(SelectValue, { placeholder: "Bedrijfsprofiel" }) }), _jsxs(SelectContent, { children: [_jsx(SelectItem, { value: ALL_PROFILES_VALUE, children: "Alle bedrijven (overzicht)" }), orgProfiles.map((profile) => (_jsx(SelectItem, { value: profile.id, children: profile.name }, profile.id)))] })] })] })), isAdmin && activeTab === "profiles" && (_jsxs(Button, { size: "sm", onClick: () => openProfileEditor(), children: [_jsx(Plus, { className: "h-4 w-4 mr-2" }), "Nieuw profiel"] })), 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: openTestDialog, 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"] })] }))] })] }), isAdmin && activeProfileId && (_jsxs("div", { className: "flex flex-wrap items-center gap-3 rounded-md border bg-muted/40 px-3 py-2", children: [_jsxs(Button, { variant: "ghost", size: "sm", className: "h-7 px-2", onClick: () => setActiveProfileId(""), children: [_jsx(ArrowLeft, { className: "mr-1 h-4 w-4" }), "Alle bedrijven"] }), _jsxs("span", { className: "text-sm text-muted-foreground", children: ["Je bekijkt bedrijfsprofiel", " ", _jsx("span", { className: "font-medium text-foreground", children: orgProfiles.find((p) => p.id === activeProfileId)?.name ?? "onbekend" }), ". Alleen gebruikers en rollen van dit bedrijf worden getoond."] })] })), profileLocked && (_jsxs("div", { className: "flex flex-wrap items-center gap-2 rounded-md border bg-muted/40 px-3 py-2", children: [_jsx(Building2, { className: "h-4 w-4 text-muted-foreground" }), lockedProfile ? (_jsxs("span", { className: "text-sm text-muted-foreground", children: ["Je ziet uitsluitend bedrijfsprofiel", " ", _jsx("span", { className: "font-medium text-foreground", children: lockedProfile.name }), "."] })) : (_jsx("span", { className: "text-sm text-muted-foreground", children: "Er is nog geen bedrijfsprofiel aan je account gekoppeld. Vraag een beheerder om je aan een profiel te koppelen." }))] }))] }), _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" }), isAdmin && _jsx(TabsTrigger, { value: "profiles", children: "Profielen" })] }), _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..." })) : usersError ? (_jsxs("div", { className: "rounded-md border border-destructive/40 bg-destructive/5 p-4", children: [_jsx("p", { className: "text-sm text-destructive", children: usersError }), _jsx(Button, { variant: "outline", size: "sm", className: "mt-3", onClick: () => loadUsers(activeProfileId), children: "Opnieuw proberen" })] })) : filteredUsers.length === 0 ? (_jsx("p", { className: "text-sm text-muted-foreground", children: hasActiveFilters
559
+ 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: openTestDialog, 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..." })) : usersError ? (_jsxs("div", { className: "rounded-md border border-destructive/40 bg-destructive/5 p-4", children: [_jsx("p", { className: "text-sm text-destructive", children: usersError }), _jsx(Button, { variant: "outline", size: "sm", className: "mt-3", onClick: () => loadUsers(), children: "Opnieuw proberen" })] })) : filteredUsers.length === 0 ? (_jsx("p", { className: "text-sm text-muted-foreground", children: hasActiveFilters
652
560
  ? "Geen gebruikers gevonden met de huidige filters."
653
561
  : "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) => {
654
562
  const manageable = canManageUser(u);
655
563
  const unassigned = assignableRoles.filter((r) => !u.roles.some((ur) => ur.id === r.id));
656
- 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" })), activeProfileId && u.matchedVia === "role" && (_jsx(Badge, { variant: "outline", className: "text-xs", children: "Via rol" }))] }), _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));
657
- }) })] }))] }), _jsx(TabsContent, { value: "roles", className: "mt-4", children: rolesLoading ? (_jsx("p", { className: "text-sm text-muted-foreground", children: "Laden..." })) : rolesError ? (_jsxs("div", { className: "rounded-md border border-destructive/40 bg-destructive/5 p-4", children: [_jsx("p", { className: "text-sm text-destructive", children: rolesError }), _jsx(Button, { variant: "outline", size: "sm", className: "mt-3", onClick: () => loadRoles(activeProfileId), children: "Opnieuw proberen" })] })) : 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) => {
658
- const count = rolePermissions.filter((p) => p.roleId === role.id && p.featureId === "" && (p.canView || p.canEdit)).length;
659
- 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));
660
- }) })] })) }), _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) => {
564
+ return (_jsxs(TableRow, { className: "cursor-pointer", onClick: () => setDetailTarget(u), children: [_jsxs(TableCell, { children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx("button", { type: "button", className: "font-medium text-left hover:underline", onClick: (e) => {
565
+ e.stopPropagation();
566
+ setDetailTarget(u);
567
+ }, 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", onClick: (e) => e.stopPropagation(), 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", onClick: (e) => e.stopPropagation(), children: [_jsxs(DropdownMenuSub, { children: [_jsx(DropdownMenuSubTrigger, { disabled: !manageable || unassigned.length === 0, children: "Rol toekennen" }), _jsx(DropdownMenuSubContent, { onClick: (e) => e.stopPropagation(), 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, { onClick: (e) => e.stopPropagation(), children: u.roles.map((role) => (_jsx(DropdownMenuItem, { disabled: (role.key === "admin" || role.key === "superadmin") && !isAdmin, onSelect: () => handleRemove(u.id, role.id), children: role.name }, `remove-${u.id}-${role.id}`))) })] }), _jsx(DropdownMenuSeparator, {}), _jsxs(DropdownMenuItem, { disabled: !manageable, onSelect: () => openEdit(u), children: [_jsx(Pencil, { className: "h-4 w-4 mr-2" }), "Gegevens 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));
568
+ }) })] }))] }), _jsx(TabsContent, { value: "roles", className: "mt-4", children: rolesLoading ? (_jsx("p", { className: "text-sm text-muted-foreground", children: "Laden..." })) : rolesError ? (_jsxs("div", { className: "rounded-md border border-destructive/40 bg-destructive/5 p-4", children: [_jsx("p", { className: "text-sm text-destructive", children: rolesError }), _jsx(Button, { variant: "outline", size: "sm", className: "mt-3", onClick: () => loadRoles(), children: "Opnieuw proberen" })] })) : roles.length === 0 ? (_jsx("p", { className: "text-sm text-muted-foreground", children: "Er zijn nog geen rollen aangemaakt." })) : (_jsxs(_Fragment, { children: [roles.some((r) => r.unmapped) && (_jsxs("div", { className: "mb-4 flex gap-3 rounded-md border border-destructive/40 bg-destructive/5 p-4", children: [_jsx(AlertTriangle, { className: "mt-0.5 h-4 w-4 shrink-0 text-destructive" }), _jsxs("div", { className: "text-sm", children: [_jsx("p", { className: "font-medium text-destructive", children: "Een of meer rollen werken niet" }), _jsxs("p", { className: "text-muted-foreground", children: ["De sleutel van deze rollen is onbekend in het systeem, waardoor een toekenning stil wordt overgeslagen: ", " ", roles.filter((r) => r.unmapped).map((r) => r.name).join(", "), ". Gebruikers met zo'n rol krijgen er geen enkele toegang door."] })] })] })), _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) => {
569
+ const count = rolePermissions.filter((p) => p.roleId === role.id && p.featureId === "" && (p.canView || p.canEdit)).length;
570
+ 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" })), role.unmapped && (_jsxs(Badge, { variant: "destructive", className: "gap-1 text-xs", title: "De sleutel van deze rol is onbekend in het systeem; toekenningen worden stil overgeslagen.", children: [_jsx(AlertTriangle, { className: "h-3 w-3" }), "Werkt niet"] }))] }) }), _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));
571
+ }) })] })] })) }), _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) => {
661
572
  const rows = [
662
573
  _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),
663
574
  ];
@@ -665,27 +576,7 @@ export function AdminForInstallatieTechniek({ modules, shellId } = {}) {
665
576
  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}`));
666
577
  }
667
578
  return rows;
668
- }) })] }))] }), _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 }), _jsxs(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" })] }), _jsx("p", { className: "mt-1 text-xs text-muted-foreground", children: "Beheerders houden altijd toegang, ook als afdwingen aanstaat." })] })] }, mod.id))) })] }))] }), isAdmin && (_jsxs(TabsContent, { value: "profiles", className: "mt-4 space-y-6", children: [_jsx("p", { className: "text-sm text-muted-foreground", children: "Een bedrijfsprofiel bundelt de rollen en modules van \u00E9\u00E9n organisatie. Het gekozen profiel bepaalt welke medewerkers en rollen je hierboven ziet." }), profilesLoading ? (_jsx("p", { className: "text-sm text-muted-foreground", children: "Laden..." })) : orgProfiles.length === 0 ? (_jsx("p", { className: "text-sm text-muted-foreground", children: "Er zijn nog geen bedrijfsprofielen." })) : (_jsxs(Table, { children: [_jsx(TableHeader, { children: _jsxs(TableRow, { children: [_jsx(TableHead, { children: "Profiel" }), _jsx(TableHead, { children: "Bedrijf" }), _jsx(TableHead, { className: "w-24", children: "Rollen" }), _jsx(TableHead, { className: "w-24", children: "Modules" }), _jsx(TableHead, { className: "w-28", children: "Gebruikers" }), _jsx(TableHead, { className: "w-16 text-right", children: "Acties" })] }) }), _jsx(TableBody, { children: orgProfiles.map((profile) => (_jsxs(TableRow, { children: [_jsx(TableCell, { className: "font-medium", children: _jsxs("div", { className: "flex items-center gap-2", children: [profile.name, profile.isDefault && _jsx(Badge, { variant: "secondary", children: "Standaard" })] }) }), _jsx(TableCell, { className: "text-muted-foreground", children: profile.tenantName ?? "-" }), _jsx(TableCell, { children: profile.roleIds.length }), _jsx(TableCell, { children: profile.moduleIds.length }), _jsx(TableCell, { children: profile.userCount }), _jsx(TableCell, { className: "text-right", children: _jsxs(DropdownMenu, { children: [_jsx(DropdownMenuTrigger, { asChild: true, children: _jsx(Button, { variant: "ghost", size: "icon", children: _jsx(MoreVertical, { className: "h-4 w-4" }) }) }), _jsxs(DropdownMenuContent, { align: "end", children: [_jsxs(DropdownMenuItem, { onClick: () => openProfileEditor(profile), children: [_jsx(Pencil, { className: "h-4 w-4 mr-2" }), "Bewerken"] }), _jsxs(DropdownMenuItem, { onClick: () => handleDuplicateProfile(profile), children: [_jsx(Copy, { className: "h-4 w-4 mr-2" }), "Dupliceren"] }), !profile.isDefault && (_jsxs(DropdownMenuItem, { onClick: () => handleSetActiveProfile(profile), children: [_jsx(Shield, { className: "h-4 w-4 mr-2" }), "Als standaard instellen"] })), _jsx(DropdownMenuSeparator, {}), _jsxs(DropdownMenuItem, { className: "text-destructive", disabled: profile.isDefault, onClick: () => setProfileDeleteTarget(profile), children: [_jsx(UserX, { className: "h-4 w-4 mr-2" }), "Verwijderen"] })] })] }) })] }, profile.id))) })] }))] }))] }) })] }), _jsx(Dialog, { open: profileEditor !== null, onOpenChange: (open) => !open && !profileSaving && setProfileEditor(null), children: _jsxs(DialogContent, { className: "max-w-2xl", children: [_jsxs(DialogHeader, { children: [_jsx(DialogTitle, { children: profileEditor?.profileId ? "Bedrijfsprofiel bewerken" : "Bedrijfsprofiel toevoegen" }), _jsx(DialogDescription, { children: "Koppel het profiel aan een bedrijf en kies welke rollen en modules erbij horen." })] }), _jsxs("div", { className: "space-y-4", children: [_jsxs("div", { className: "space-y-2", children: [_jsx(Label, { htmlFor: "profile-name", children: "Naam profiel" }), _jsx(Input, { id: "profile-name", value: profileEditor?.name ?? "", placeholder: "Bijvoorbeeld: Installatie Techniek BV", onChange: (e) => setProfileEditor((state) => state
669
- ? {
670
- ...state,
671
- name: e.target.value,
672
- // Zolang de bedrijfsnaam niet handmatig is aangepast, volgt hij de profielnaam.
673
- tenantName: state.tenantNameTouched ? state.tenantName : e.target.value,
674
- }
675
- : state), disabled: profileSaving })] }), _jsxs("div", { className: "space-y-2", children: [_jsx(Label, { children: "Bedrijf" }), _jsxs(Select, { value: profileEditor?.tenantId ? profileEditor.tenantId : "none", onValueChange: (value) => setProfileEditor((state) => {
676
- if (!state)
677
- return state;
678
- const tenantId = value === "none" ? "" : value;
679
- const picked = tenants.find((t) => t.id === tenantId);
680
- return {
681
- ...state,
682
- tenantId,
683
- tenantName: picked ? picked.name : state.name,
684
- tenantNameTouched: Boolean(picked),
685
- };
686
- }), children: [_jsx(SelectTrigger, { children: _jsx(SelectValue, { placeholder: "Kies een bedrijf" }) }), _jsxs(SelectContent, { children: [_jsx(SelectItem, { value: "none", children: "Nieuw bedrijf aanmaken" }), tenants.map((tenant) => (_jsx(SelectItem, { value: tenant.id, children: tenant.name }, tenant.id)))] })] })] }), _jsxs("div", { className: "space-y-2", children: [_jsx(Label, { htmlFor: "profile-tenant-name", children: "Bedrijfsnaam" }), _jsx(Input, { id: "profile-tenant-name", value: profileEditor?.tenantName ?? "", placeholder: "Zelfde als profielnaam", onChange: (e) => setProfileEditor((state) => state
687
- ? { ...state, tenantName: e.target.value, tenantNameTouched: true }
688
- : state), disabled: profileSaving }), _jsx("p", { className: "text-xs text-muted-foreground", children: "Wordt gebruikt als weergavenaam van het bedrijf. Leeg laten neemt de profielnaam over." })] }), _jsxs("div", { className: "grid gap-4 sm:grid-cols-2", children: [_jsxs("div", { className: "space-y-2", children: [_jsx(Label, { children: "Rollen in dit profiel" }), _jsx("div", { className: "max-h-56 space-y-2 overflow-y-auto rounded-md border p-3", children: allRolesForProfile.length === 0 ? (_jsx("p", { className: "text-sm text-muted-foreground", children: "Geen rollen beschikbaar." })) : (allRolesForProfile.map((role) => (_jsxs("label", { className: "flex items-center gap-2 text-sm", children: [_jsx(Checkbox, { checked: profileEditor?.roleIds.includes(role.id) ?? false, onCheckedChange: () => toggleProfileValue("roleIds", role.id), disabled: profileSaving }), role.name] }, role.id)))) })] }), _jsxs("div", { className: "space-y-2", children: [_jsx(Label, { children: "Modules in dit profiel" }), _jsx("div", { className: "max-h-56 space-y-2 overflow-y-auto rounded-md border p-3", children: roleModules.length === 0 ? (_jsx("p", { className: "text-sm text-muted-foreground", children: "Geen modules geregistreerd." })) : (roleModules.map((mod) => (_jsxs("label", { className: "flex items-center gap-2 text-sm", children: [_jsx(Checkbox, { checked: profileEditor?.moduleIds.includes(mod.id) ?? false, onCheckedChange: () => toggleProfileValue("moduleIds", mod.id), disabled: profileSaving }), mod.name] }, mod.id)))) })] })] })] }), _jsxs(DialogFooter, { children: [_jsx(Button, { variant: "outline", onClick: () => setProfileEditor(null), disabled: profileSaving, children: "Annuleren" }), _jsx(Button, { onClick: handleSaveProfile, disabled: profileSaving || !profileEditor?.name.trim(), children: profileSaving ? "Opslaan..." : "Opslaan" })] })] }) }), _jsx(AlertDialog, { open: profileDeleteTarget !== null, onOpenChange: (open) => !open && setProfileDeleteTarget(null), children: _jsxs(AlertDialogContent, { children: [_jsxs(AlertDialogHeader, { children: [_jsx(AlertDialogTitle, { children: "Bedrijfsprofiel verwijderen?" }), _jsxs(AlertDialogDescription, { children: ["Weet je zeker dat je \"", profileDeleteTarget?.name, "\" wilt verwijderen? Rollen en gebruikers blijven bestaan, alleen de bundeling verdwijnt."] })] }), _jsxs(AlertDialogFooter, { children: [_jsx(AlertDialogCancel, { children: "Annuleren" }), _jsx(AlertDialogAction, { onClick: handleDeleteProfile, children: "Verwijderen" })] })] }) }), _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) => {
579
+ }) })] }))] }), _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 }), _jsxs(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" })] }), _jsx("p", { className: "mt-1 text-xs text-muted-foreground", children: "Beheerders houden altijd toegang, ook als afdwingen aanstaat." })] })] }, 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: [_jsxs("div", { className: "flex w-16 flex-col items-center gap-1", children: [_jsx("span", { children: "Lezen" }), _jsx(Button, { type: "button", variant: "outline", size: "sm", className: "h-6 px-2 text-xs", disabled: editorSaving, onClick: () => setAllCells("canView", !allCellsOn("canView")), children: allCellsOn("canView") ? "Niets" : "Alles" })] }), _jsxs("div", { className: "flex w-16 flex-col items-center gap-1", children: [_jsx("span", { children: "Bewerken" }), _jsx(Button, { type: "button", variant: "outline", size: "sm", className: "h-6 px-2 text-xs", disabled: editorSaving, onClick: () => setAllCells("canEdit", !allCellsOn("canEdit")), children: allCellsOn("canEdit") ? "Niets" : "Alles" })] })] }), 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) => {
689
580
  const cell = editor?.cells[permCellKey(mod.id, "")] ?? {
690
581
  canView: false,
691
582
  canEdit: false,
@@ -700,7 +591,11 @@ export function AdminForInstallatieTechniek({ modules, shellId } = {}) {
700
591
  };
701
592
  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));
702
593
  })] }, mod.id));
703
- }))] })] }), _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) => {
594
+ }))] })] }), _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("div", { className: "space-y-2", children: [_jsxs(Label, { htmlFor: "invite-address", children: ["Adres ", inviteContactRequired && _jsx("span", { className: "text-destructive", children: "*" })] }), _jsx(Input, { id: "invite-address", value: inviteAddress, onChange: (e) => setInviteAddress(e.target.value), placeholder: "Dorpsstraat 1, 1234 AB Amsterdam", disabled: inviting })] }), _jsxs("div", { className: "space-y-2", children: [_jsx(Label, { htmlFor: "invite-phone", children: "Telefoonnummer (optioneel)" }), _jsx(Input, { id: "invite-phone", value: invitePhone, onChange: (e) => setInvitePhone(e.target.value), placeholder: "+31 6 12345678", disabled: inviting })] }), inviteContactRequired && !inviteContactOk && (_jsx("p", { className: "text-xs text-muted-foreground", children: "Voor deze rol is een adres verplicht. Het adres wordt gecontroleerd via Google Maps." }))] }), _jsxs(DialogFooter, { children: [_jsx(Button, { variant: "outline", onClick: () => setInviteOpen(false), disabled: inviting, children: "Annuleren" }), _jsx(Button, { onClick: handleInvite, disabled: inviting ||
595
+ !inviteEmail.trim() ||
596
+ !inviteName.trim() ||
597
+ !inviteRole ||
598
+ !inviteContactOk, 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) => {
704
599
  setTestNameTouched(true);
705
600
  setTestName(e.target.value);
706
601
  }, placeholder: suggestedTestName, disabled: testCreating })] }), _jsxs("div", { className: "space-y-2", children: [_jsx(Label, { children: "Rol" }), _jsxs(Select, { value: testRole, onValueChange: (value) => {
@@ -712,7 +607,14 @@ export function AdminForInstallatieTechniek({ modules, shellId } = {}) {
712
607
  }, 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) => {
713
608
  e.preventDefault();
714
609
  handleDeleteRole();
715
- }, 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) => {
610
+ }, className: "bg-destructive text-destructive-foreground hover:bg-destructive/90", children: "Verwijderen" })] })] }) }), _jsx(Dialog, { open: detailTarget !== null, onOpenChange: (open) => !open && setDetailTarget(null), children: _jsxs(DialogContent, { className: "max-w-lg", children: [_jsxs(DialogHeader, { children: [_jsxs(DialogTitle, { children: ["Gegevens van ", detailTarget?.displayName] }), _jsx(DialogDescription, { children: detailTarget?.email })] }), detailTarget && (() => {
611
+ return (_jsxs("div", { className: "space-y-5 max-h-[60vh] overflow-y-auto pr-1", children: [_jsxs("div", { className: "space-y-1", children: [_jsx("p", { className: "text-sm font-medium", children: "Rollen" }), _jsx("div", { className: "flex flex-wrap gap-2", children: detailTarget.roles.length === 0 ? (_jsx("span", { className: "text-sm text-muted-foreground", children: "Geen rollen" })) : (detailTarget.roles.map((role) => (_jsx(Badge, { variant: "secondary", children: role.name }, `detail-${detailTarget.id}-${role.id}`)))) })] }), _jsxs("div", { className: "space-y-1", children: [_jsx("p", { className: "text-sm font-medium", children: "Contactgegevens" }), _jsxs("p", { className: "text-sm", children: ["Adres:", " ", detailTarget.address?.trim() ? (detailTarget.address) : (_jsx("span", { className: "text-destructive", children: "Ontbreekt" }))] }), _jsxs("p", { className: "text-sm", children: ["Telefoon:", " ", detailTarget.phone?.trim() ? (detailTarget.phone) : (_jsx("span", { className: "text-muted-foreground", children: "Niet ingevuld" }))] })] })] }));
612
+ })(), _jsxs(DialogFooter, { children: [_jsx(Button, { variant: "outline", onClick: () => setDetailTarget(null), children: "Sluiten" }), _jsx(Button, { disabled: !detailTarget || !canManageUser(detailTarget), onClick: () => {
613
+ if (!detailTarget)
614
+ return;
615
+ openEdit(detailTarget);
616
+ setDetailTarget(null);
617
+ }, children: "Wijzigen" })] })] }) }), _jsx(Dialog, { open: editTarget !== null, onOpenChange: (open) => !open && !editSaving && setEditTarget(null), children: _jsxs(DialogContent, { className: "max-w-lg", children: [_jsxs(DialogHeader, { children: [_jsx(DialogTitle, { children: "Gegevens wijzigen" }), _jsxs(DialogDescription, { children: ["Pas naam en contactgegevens van ", editTarget?.email, " in \u00E9\u00E9n keer aan."] })] }), _jsxs("div", { className: "space-y-5 max-h-[60vh] overflow-y-auto pr-1", children: [_jsxs("div", { className: "space-y-2", children: [_jsx(Label, { htmlFor: "edit-name", children: "Naam" }), _jsx(Input, { id: "edit-name", value: renameValue, onChange: (e) => setRenameValue(e.target.value), placeholder: "Naam van de gebruiker", disabled: editSaving })] }), _jsxs("div", { className: "space-y-2", children: [_jsx(Label, { htmlFor: "edit-address", children: "Adres" }), _jsx(Input, { id: "edit-address", value: contactAddress, onChange: (e) => setContactAddress(e.target.value), placeholder: "Dorpsstraat 1, 1234 AB Amsterdam", disabled: editSaving })] }), _jsxs("div", { className: "space-y-2", children: [_jsx(Label, { htmlFor: "edit-phone", children: "Telefoonnummer (optioneel)" }), _jsx(Input, { id: "edit-phone", value: contactPhone, onChange: (e) => setContactPhone(e.target.value), placeholder: "+31 6 12345678", disabled: editSaving }), editTarget && needsContact(editTarget) && (_jsx("p", { className: "text-xs text-muted-foreground", children: "Voor deze rol is een adres verplicht. Het adres wordt gecontroleerd via Google Maps." }))] })] }), _jsxs(DialogFooter, { children: [_jsx(Button, { variant: "outline", onClick: () => setEditTarget(null), disabled: editSaving, children: "Annuleren" }), _jsx(Button, { onClick: handleSaveEdit, disabled: editSaving || !renameValue.trim(), children: editSaving ? "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) => {
716
618
  e.preventDefault();
717
619
  handleDelete();
718
620
  }, disabled: deleting, className: "bg-destructive text-destructive-foreground hover:bg-destructive/90", children: deleting ? "Verwijderen..." : "Verwijderen" })] })] }) })] }));