@flowselections/admin-installatietechniek 1.0.2 → 1.0.4
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.
- package/dist-lib/components/AdminForInstallatieTechniek.d.ts +4 -0
- package/dist-lib/components/AdminForInstallatieTechniek.d.ts.map +1 -1
- package/dist-lib/components/AdminForInstallatieTechniek.js +211 -106
- package/dist-lib/hooks/useModulePermission.d.ts +4 -3
- package/dist-lib/hooks/useModulePermission.d.ts.map +1 -1
- package/dist-lib/hooks/useModulePermission.js +10 -6
- package/dist-lib/index.d.ts +1 -1
- package/dist-lib/index.d.ts.map +1 -1
- package/dist-lib/integrations/supabase/auth-middleware.d.ts +410 -0
- package/dist-lib/integrations/supabase/auth-middleware.d.ts.map +1 -1
- package/dist-lib/integrations/supabase/client.d.ts +410 -0
- package/dist-lib/integrations/supabase/client.d.ts.map +1 -1
- package/dist-lib/integrations/supabase/client.server.d.ts +410 -0
- package/dist-lib/integrations/supabase/client.server.d.ts.map +1 -1
- package/dist-lib/integrations/supabase/types.d.ts +414 -0
- package/dist-lib/integrations/supabase/types.d.ts.map +1 -1
- package/dist-lib/lib/admin.functions.d.ts +90380 -956
- package/dist-lib/lib/admin.functions.d.ts.map +1 -1
- package/dist-lib/lib/admin.functions.js +340 -131
- package/dist-lib/lib/moduleDiscovery.d.ts +7 -2
- package/dist-lib/lib/moduleDiscovery.d.ts.map +1 -1
- package/dist-lib/lib/moduleDiscovery.js +25 -10
- package/package.json +87 -87
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AdminForInstallatieTechniek.d.ts","sourceRoot":"","sources":["../../src/components/AdminForInstallatieTechniek.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"AdminForInstallatieTechniek.d.ts","sourceRoot":"","sources":["../../src/components/AdminForInstallatieTechniek.tsx"],"names":[],"mappings":"AAsFA,MAAM,WAAW,gBAAgB;IAC/B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,CAAC,EAAE;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;CAC3C;AAED,MAAM,WAAW,gCAAgC;IAC/C,8EAA8E;IAC9E,OAAO,CAAC,EAAE,gBAAgB,EAAE,CAAC;IAC7B,uEAAuE;IACvE,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AA+DD,wBAAgB,2BAA2B,CAAC,EAAE,OAAO,EAAE,OAAO,EAAE,GAAE,gCAAqC,+BA2oCtG"}
|
|
@@ -1,21 +1,17 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
2
|
import { useEffect, useMemo, 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,
|
|
4
|
-
import { Copy, FlaskConical,
|
|
5
|
-
import { assignRole, createTestUser, deleteUser,
|
|
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, 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, setUserModulePermission, syncModuleRegistry, updateRole, updateUserName, } from "../lib/admin.functions";
|
|
6
6
|
import { useDiscoveredModules } from "../lib/moduleDiscovery";
|
|
7
7
|
import { supabase } from "../integrations/supabase/client";
|
|
8
|
-
const PERMISSION_LABELS = {
|
|
9
|
-
none: "Geen toegang",
|
|
10
|
-
view: "Bekijken",
|
|
11
|
-
edit: "Aanpassen",
|
|
12
|
-
};
|
|
13
8
|
const USER_PERMISSION_LABELS = {
|
|
14
9
|
inherit: "Volg rol",
|
|
15
10
|
none: "Geen toegang",
|
|
16
11
|
view: "Bekijken",
|
|
17
12
|
edit: "Aanpassen",
|
|
18
13
|
};
|
|
14
|
+
const permCellKey = (moduleId, featureId) => `${moduleId}::${featureId}`;
|
|
19
15
|
const getErrorMessage = (error, fallback) => {
|
|
20
16
|
if (error instanceof Error && error.message) {
|
|
21
17
|
if (/permission denied|row-level security|rls|forbidden|not authorized/i.test(error.message)) {
|
|
@@ -27,7 +23,6 @@ const getErrorMessage = (error, fallback) => {
|
|
|
27
23
|
};
|
|
28
24
|
async function refreshOwnSessionIfSelf(targetUserId, currentUserId) {
|
|
29
25
|
if (currentUserId && targetUserId === currentUserId) {
|
|
30
|
-
// Nieuwe access-token ophalen zodat de user_roles JWT-claim direct actueel is.
|
|
31
26
|
await supabase.auth.refreshSession();
|
|
32
27
|
}
|
|
33
28
|
}
|
|
@@ -40,26 +35,32 @@ export function AdminForInstallatieTechniek({ modules, shellId } = {}) {
|
|
|
40
35
|
const isAdministratie = authRoles.includes("administratie");
|
|
41
36
|
const canAccess = isAdmin || isAdministratie;
|
|
42
37
|
const currentUserId = user?.id;
|
|
38
|
+
const [activeTab, setActiveTab] = useState("users");
|
|
43
39
|
const [users, setUsers] = useState([]);
|
|
44
40
|
const [availableRoles, setAvailableRoles] = useState([]);
|
|
45
41
|
const [loading, setLoading] = useState(true);
|
|
46
|
-
const [selectedUser, setSelectedUser] = useState("");
|
|
47
|
-
const [selectedRole, setSelectedRole] = useState("planner");
|
|
48
42
|
const [submitting, setSubmitting] = useState(false);
|
|
49
|
-
const [
|
|
50
|
-
const [
|
|
51
|
-
const [
|
|
52
|
-
const [
|
|
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);
|
|
53
54
|
const [inviteOpen, setInviteOpen] = useState(false);
|
|
54
55
|
const [inviteName, setInviteName] = useState("");
|
|
55
56
|
const [inviteEmail, setInviteEmail] = useState("");
|
|
56
|
-
const [inviteRole, setInviteRole] = useState("
|
|
57
|
+
const [inviteRole, setInviteRole] = useState("");
|
|
57
58
|
const [inviting, setInviting] = useState(false);
|
|
58
59
|
const [deleteTarget, setDeleteTarget] = useState(null);
|
|
59
60
|
const [deleting, setDeleting] = useState(false);
|
|
60
61
|
const [testOpen, setTestOpen] = useState(false);
|
|
61
62
|
const [testName, setTestName] = useState("");
|
|
62
|
-
const [testRole, setTestRole] = useState("
|
|
63
|
+
const [testRole, setTestRole] = useState("");
|
|
63
64
|
const [testCreating, setTestCreating] = useState(false);
|
|
64
65
|
const [testCredentials, setTestCredentials] = useState(null);
|
|
65
66
|
const [searchQuery, setSearchQuery] = useState("");
|
|
@@ -69,20 +70,25 @@ export function AdminForInstallatieTechniek({ modules, shellId } = {}) {
|
|
|
69
70
|
const [userPermModules, setUserPermModules] = useState([]);
|
|
70
71
|
const [userPermMap, setUserPermMap] = useState({});
|
|
71
72
|
const [userPermLoading, setUserPermLoading] = useState(false);
|
|
72
|
-
const permKey = (role, moduleId) => `${role}::${moduleId}`;
|
|
73
73
|
const discoveredModules = useDiscoveredModules();
|
|
74
74
|
const shellModules = useMemo(() => {
|
|
75
75
|
const source = Array.isArray(modules) && modules.length > 0 ? modules : discoveredModules;
|
|
76
76
|
return source.filter((m) => Boolean(m?.id) && Boolean(m?.name));
|
|
77
77
|
}, [modules, discoveredModules]);
|
|
78
78
|
const effectiveShellId = shellId ?? (typeof window !== "undefined" ? window.location.host : "unknown-shell");
|
|
79
|
-
const shellModulesKey = shellModules
|
|
79
|
+
const shellModulesKey = shellModules
|
|
80
|
+
.map((m) => `${m.id}:${m.name}:${(m.features ?? []).map((f) => `${f.id}=${f.name}`).join(",")}`)
|
|
81
|
+
.join("|");
|
|
80
82
|
const loadUsers = async () => {
|
|
81
83
|
setLoading(true);
|
|
82
84
|
try {
|
|
83
85
|
const result = await getUsersWithRoles();
|
|
84
86
|
setUsers(Array.isArray(result?.users) ? result.users : []);
|
|
85
|
-
|
|
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);
|
|
86
92
|
}
|
|
87
93
|
catch (error) {
|
|
88
94
|
toast.error(getErrorMessage(error, "Fout bij het laden van gebruikers en rollen"));
|
|
@@ -91,31 +97,19 @@ export function AdminForInstallatieTechniek({ modules, shellId } = {}) {
|
|
|
91
97
|
setLoading(false);
|
|
92
98
|
}
|
|
93
99
|
};
|
|
94
|
-
const
|
|
95
|
-
|
|
100
|
+
const loadRoles = async () => {
|
|
101
|
+
setRolesLoading(true);
|
|
96
102
|
try {
|
|
97
|
-
const result = await
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
setPermRoles(roles);
|
|
102
|
-
setPermModules(mods);
|
|
103
|
-
const matrix = {};
|
|
104
|
-
for (const r of roles) {
|
|
105
|
-
for (const m of mods) {
|
|
106
|
-
matrix[permKey(r, m.id)] = "none";
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
for (const p of perms) {
|
|
110
|
-
matrix[permKey(p.role, p.moduleId)] = p.permission;
|
|
111
|
-
}
|
|
112
|
-
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 : []);
|
|
113
107
|
}
|
|
114
108
|
catch (error) {
|
|
115
|
-
toast.error(getErrorMessage(error, "Fout bij het laden van
|
|
109
|
+
toast.error(getErrorMessage(error, "Fout bij het laden van rollen"));
|
|
116
110
|
}
|
|
117
111
|
finally {
|
|
118
|
-
|
|
112
|
+
setRolesLoading(false);
|
|
119
113
|
}
|
|
120
114
|
};
|
|
121
115
|
const loadUserPermissions = async (userId) => {
|
|
@@ -131,10 +125,14 @@ export function AdminForInstallatieTechniek({ modules, shellId } = {}) {
|
|
|
131
125
|
const overrides = Array.isArray(result?.overrides) ? result.overrides : [];
|
|
132
126
|
setUserPermModules(mods);
|
|
133
127
|
const map = {};
|
|
134
|
-
for (const m of mods)
|
|
135
|
-
map[m.id] = "inherit";
|
|
136
|
-
|
|
137
|
-
|
|
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
|
+
}
|
|
138
136
|
setUserPermMap(map);
|
|
139
137
|
}
|
|
140
138
|
catch (error) {
|
|
@@ -149,7 +147,6 @@ export function AdminForInstallatieTechniek({ modules, shellId } = {}) {
|
|
|
149
147
|
return;
|
|
150
148
|
let cancelled = false;
|
|
151
149
|
(async () => {
|
|
152
|
-
// Modules die deze shell heeft geladen automatisch registreren.
|
|
153
150
|
if (isAdmin && shellModules.length > 0) {
|
|
154
151
|
try {
|
|
155
152
|
await syncModuleRegistry({ data: { modules: shellModules, shellId: effectiveShellId } });
|
|
@@ -161,13 +158,7 @@ export function AdminForInstallatieTechniek({ modules, shellId } = {}) {
|
|
|
161
158
|
if (cancelled)
|
|
162
159
|
return;
|
|
163
160
|
loadUsers();
|
|
164
|
-
|
|
165
|
-
if (isAdmin) {
|
|
166
|
-
loadPermissions();
|
|
167
|
-
}
|
|
168
|
-
else {
|
|
169
|
-
setPermLoading(false);
|
|
170
|
-
}
|
|
161
|
+
loadRoles();
|
|
171
162
|
})();
|
|
172
163
|
return () => {
|
|
173
164
|
cancelled = true;
|
|
@@ -180,27 +171,28 @@ export function AdminForInstallatieTechniek({ modules, shellId } = {}) {
|
|
|
180
171
|
loadUserPermissions(permUserId);
|
|
181
172
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
182
173
|
}, [permUserId, canAccess]);
|
|
183
|
-
const handleUserPermChange = async (moduleId, permission) => {
|
|
174
|
+
const handleUserPermChange = async (moduleId, featureId, permission) => {
|
|
184
175
|
if (!permUserId)
|
|
185
176
|
return;
|
|
186
|
-
const
|
|
187
|
-
|
|
177
|
+
const key = permCellKey(moduleId, featureId);
|
|
178
|
+
const prev = userPermMap[key] ?? "inherit";
|
|
179
|
+
setUserPermMap((m) => ({ ...m, [key]: permission }));
|
|
188
180
|
try {
|
|
189
|
-
await setUserModulePermission({
|
|
181
|
+
await setUserModulePermission({
|
|
182
|
+
data: { userId: permUserId, moduleId, featureId, permission },
|
|
183
|
+
});
|
|
190
184
|
toast.success("Persoonlijk recht opgeslagen");
|
|
191
185
|
}
|
|
192
186
|
catch (error) {
|
|
193
|
-
setUserPermMap((m) => ({ ...m, [
|
|
187
|
+
setUserPermMap((m) => ({ ...m, [key]: prev }));
|
|
194
188
|
toast.error(getErrorMessage(error, "Opslaan mislukt"));
|
|
195
189
|
}
|
|
196
190
|
};
|
|
197
|
-
const handleAssign = async () => {
|
|
198
|
-
if (!selectedUser || !selectedRole)
|
|
199
|
-
return;
|
|
191
|
+
const handleAssign = async (userId, roleId) => {
|
|
200
192
|
setSubmitting(true);
|
|
201
193
|
try {
|
|
202
|
-
await assignRole({ data: { userId
|
|
203
|
-
await refreshOwnSessionIfSelf(
|
|
194
|
+
await assignRole({ data: { userId, roleId } });
|
|
195
|
+
await refreshOwnSessionIfSelf(userId, currentUserId);
|
|
204
196
|
toast.success("Rol toegekend");
|
|
205
197
|
await loadUsers();
|
|
206
198
|
}
|
|
@@ -211,9 +203,9 @@ export function AdminForInstallatieTechniek({ modules, shellId } = {}) {
|
|
|
211
203
|
setSubmitting(false);
|
|
212
204
|
}
|
|
213
205
|
};
|
|
214
|
-
const handleRemove = async (userId,
|
|
206
|
+
const handleRemove = async (userId, roleId) => {
|
|
215
207
|
try {
|
|
216
|
-
await removeRole({ data: { userId,
|
|
208
|
+
await removeRole({ data: { userId, roleId } });
|
|
217
209
|
await refreshOwnSessionIfSelf(userId, currentUserId);
|
|
218
210
|
toast.success("Rol verwijderd");
|
|
219
211
|
await loadUsers();
|
|
@@ -222,31 +214,106 @@ export function AdminForInstallatieTechniek({ modules, shellId } = {}) {
|
|
|
222
214
|
toast.error(getErrorMessage(error, "Fout bij het verwijderen van rol"));
|
|
223
215
|
}
|
|
224
216
|
};
|
|
225
|
-
const
|
|
226
|
-
const
|
|
227
|
-
|
|
217
|
+
const openRoleEditor = (role) => {
|
|
218
|
+
const cells = {};
|
|
219
|
+
for (const m of roleModules) {
|
|
220
|
+
cells[permCellKey(m.id, "")] = { canView: false, canEdit: false };
|
|
221
|
+
for (const f of m.features ?? []) {
|
|
222
|
+
cells[permCellKey(m.id, f.id)] = { canView: false, canEdit: false };
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
if (role) {
|
|
226
|
+
for (const p of rolePermissions.filter((p) => p.roleId === role.id)) {
|
|
227
|
+
cells[permCellKey(p.moduleId, p.featureId)] = { canView: p.canView, canEdit: p.canEdit };
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
setEditor({ roleId: role?.id ?? null, name: role?.name ?? "", cells });
|
|
231
|
+
};
|
|
232
|
+
const setCell = (moduleId, featureId, field, value) => {
|
|
233
|
+
setEditor((state) => {
|
|
234
|
+
if (!state)
|
|
235
|
+
return state;
|
|
236
|
+
const cells = { ...state.cells };
|
|
237
|
+
const key = permCellKey(moduleId, featureId);
|
|
238
|
+
const current = cells[key] ?? { canView: false, canEdit: false };
|
|
239
|
+
const next = { ...current, [field]: value };
|
|
240
|
+
if (field === "canEdit" && value)
|
|
241
|
+
next.canView = true;
|
|
242
|
+
if (field === "canView" && !value)
|
|
243
|
+
next.canEdit = false;
|
|
244
|
+
cells[key] = next;
|
|
245
|
+
// Vinkje op moduleniveau zet alle onderdelen mee.
|
|
246
|
+
if (featureId === "") {
|
|
247
|
+
const mod = roleModules.find((m) => m.id === moduleId);
|
|
248
|
+
for (const f of mod?.features ?? []) {
|
|
249
|
+
cells[permCellKey(moduleId, f.id)] = { ...next };
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
return { ...state, cells };
|
|
253
|
+
});
|
|
254
|
+
};
|
|
255
|
+
const handleSaveRole = async () => {
|
|
256
|
+
if (!editor || !editor.name.trim())
|
|
257
|
+
return;
|
|
258
|
+
setEditorSaving(true);
|
|
228
259
|
try {
|
|
229
|
-
|
|
230
|
-
|
|
260
|
+
const permissions = Object.entries(editor.cells).map(([key, value]) => {
|
|
261
|
+
const [moduleId, featureId] = key.split("::");
|
|
262
|
+
return { moduleId, featureId: featureId ?? "", canView: value.canView, canEdit: value.canEdit };
|
|
263
|
+
});
|
|
264
|
+
if (editor.roleId) {
|
|
265
|
+
await updateRole({ data: { roleId: editor.roleId, name: editor.name.trim(), permissions } });
|
|
266
|
+
toast.success("Rol bijgewerkt");
|
|
267
|
+
}
|
|
268
|
+
else {
|
|
269
|
+
await createRole({ data: { name: editor.name.trim(), permissions } });
|
|
270
|
+
toast.success("Rol aangemaakt");
|
|
271
|
+
}
|
|
272
|
+
setEditor(null);
|
|
273
|
+
await Promise.all([loadRoles(), loadUsers()]);
|
|
231
274
|
}
|
|
232
275
|
catch (error) {
|
|
233
|
-
|
|
234
|
-
|
|
276
|
+
toast.error(getErrorMessage(error, "Opslaan mislukt"));
|
|
277
|
+
}
|
|
278
|
+
finally {
|
|
279
|
+
setEditorSaving(false);
|
|
280
|
+
}
|
|
281
|
+
};
|
|
282
|
+
const handleDuplicateRole = async (role) => {
|
|
283
|
+
try {
|
|
284
|
+
await duplicateRole({ data: { roleId: role.id } });
|
|
285
|
+
toast.success("Rol gedupliceerd");
|
|
286
|
+
await Promise.all([loadRoles(), loadUsers()]);
|
|
287
|
+
}
|
|
288
|
+
catch (error) {
|
|
289
|
+
toast.error(getErrorMessage(error, "Dupliceren mislukt"));
|
|
290
|
+
}
|
|
291
|
+
};
|
|
292
|
+
const handleDeleteRole = async () => {
|
|
293
|
+
if (!roleDeleteTarget)
|
|
294
|
+
return;
|
|
295
|
+
try {
|
|
296
|
+
await deleteRole({ data: { roleId: roleDeleteTarget.id } });
|
|
297
|
+
toast.success("Rol verwijderd");
|
|
298
|
+
setRoleDeleteTarget(null);
|
|
299
|
+
await Promise.all([loadRoles(), loadUsers()]);
|
|
300
|
+
}
|
|
301
|
+
catch (error) {
|
|
302
|
+
toast.error(getErrorMessage(error, "Verwijderen mislukt"));
|
|
235
303
|
}
|
|
236
304
|
};
|
|
237
305
|
const handleInvite = async () => {
|
|
238
|
-
if (!inviteEmail.trim() || !inviteName.trim())
|
|
306
|
+
if (!inviteEmail.trim() || !inviteName.trim() || !inviteRole)
|
|
239
307
|
return;
|
|
240
308
|
setInviting(true);
|
|
241
309
|
try {
|
|
242
310
|
await inviteUser({
|
|
243
|
-
data: { email: inviteEmail.trim(), displayName: inviteName.trim(),
|
|
311
|
+
data: { email: inviteEmail.trim(), displayName: inviteName.trim(), roleId: inviteRole },
|
|
244
312
|
});
|
|
245
313
|
toast.success(`Uitnodiging verstuurd naar ${inviteEmail.trim()}`);
|
|
246
314
|
setInviteOpen(false);
|
|
247
315
|
setInviteName("");
|
|
248
316
|
setInviteEmail("");
|
|
249
|
-
setInviteRole("monteur");
|
|
250
317
|
await loadUsers();
|
|
251
318
|
}
|
|
252
319
|
catch (error) {
|
|
@@ -273,18 +340,38 @@ export function AdminForInstallatieTechniek({ modules, shellId } = {}) {
|
|
|
273
340
|
setDeleting(false);
|
|
274
341
|
}
|
|
275
342
|
};
|
|
343
|
+
const openRename = (target) => {
|
|
344
|
+
setRenameTarget(target);
|
|
345
|
+
setRenameValue(target.displayName === "-" ? "" : target.displayName);
|
|
346
|
+
};
|
|
347
|
+
const handleRename = async () => {
|
|
348
|
+
if (!renameTarget || !renameValue.trim())
|
|
349
|
+
return;
|
|
350
|
+
setRenaming(true);
|
|
351
|
+
try {
|
|
352
|
+
await updateUserName({ data: { userId: renameTarget.id, displayName: renameValue.trim() } });
|
|
353
|
+
toast.success("Naam bijgewerkt");
|
|
354
|
+
setRenameTarget(null);
|
|
355
|
+
await loadUsers();
|
|
356
|
+
}
|
|
357
|
+
catch (error) {
|
|
358
|
+
toast.error(getErrorMessage(error, "Naam wijzigen mislukt"));
|
|
359
|
+
}
|
|
360
|
+
finally {
|
|
361
|
+
setRenaming(false);
|
|
362
|
+
}
|
|
363
|
+
};
|
|
276
364
|
const handleCreateTestUser = async () => {
|
|
277
|
-
if (!testName.trim())
|
|
365
|
+
if (!testName.trim() || !testRole)
|
|
278
366
|
return;
|
|
279
367
|
setTestCreating(true);
|
|
280
368
|
try {
|
|
281
369
|
const result = await createTestUser({
|
|
282
|
-
data: { displayName: testName.trim(),
|
|
370
|
+
data: { displayName: testName.trim(), roleId: testRole },
|
|
283
371
|
});
|
|
284
372
|
setTestCredentials({ email: result.email, password: result.password });
|
|
285
373
|
setTestOpen(false);
|
|
286
374
|
setTestName("");
|
|
287
|
-
setTestRole("monteur");
|
|
288
375
|
toast.success("Testaccount aangemaakt");
|
|
289
376
|
await loadUsers();
|
|
290
377
|
}
|
|
@@ -304,55 +391,73 @@ export function AdminForInstallatieTechniek({ modules, shellId } = {}) {
|
|
|
304
391
|
toast.error("Kopiëren mislukt");
|
|
305
392
|
}
|
|
306
393
|
};
|
|
307
|
-
const selectedUserHasRole = users.some((user) => user.id === selectedUser && user.roles.includes(selectedRole));
|
|
308
394
|
const filteredUsers = useMemo(() => {
|
|
309
395
|
const query = searchQuery.trim().toLowerCase();
|
|
310
|
-
|
|
311
|
-
const matchesQuery = !query ||
|
|
312
|
-
user.displayName.toLowerCase().includes(query) ||
|
|
313
|
-
user.email.toLowerCase().includes(query);
|
|
396
|
+
const list = users.filter((u) => {
|
|
397
|
+
const matchesQuery = !query || u.displayName.toLowerCase().includes(query) || u.email.toLowerCase().includes(query);
|
|
314
398
|
const matchesRole = roleFilter === "all" ||
|
|
315
|
-
(roleFilter === "none" ?
|
|
399
|
+
(roleFilter === "none" ? u.roles.length === 0 : u.roles.some((r) => r.id === roleFilter));
|
|
316
400
|
return matchesQuery && matchesRole;
|
|
317
401
|
});
|
|
318
|
-
|
|
402
|
+
return [...list].sort((a, b) => {
|
|
403
|
+
const roleA = a.roles[0]?.name ?? "";
|
|
404
|
+
const roleB = b.roles[0]?.name ?? "";
|
|
319
405
|
switch (sortBy) {
|
|
320
406
|
case "name-asc":
|
|
321
407
|
return a.displayName.localeCompare(b.displayName);
|
|
322
408
|
case "name-desc":
|
|
323
409
|
return b.displayName.localeCompare(a.displayName);
|
|
324
|
-
case "role-asc":
|
|
325
|
-
const roleA = a.roles[0] ?? "";
|
|
326
|
-
const roleB = b.roles[0] ?? "";
|
|
410
|
+
case "role-asc":
|
|
327
411
|
return roleA.localeCompare(roleB);
|
|
328
|
-
|
|
329
|
-
case "role-desc": {
|
|
330
|
-
const roleA = a.roles[0] ?? "";
|
|
331
|
-
const roleB = b.roles[0] ?? "";
|
|
412
|
+
case "role-desc":
|
|
332
413
|
return roleB.localeCompare(roleA);
|
|
333
|
-
}
|
|
334
414
|
default:
|
|
335
415
|
return 0;
|
|
336
416
|
}
|
|
337
417
|
});
|
|
338
|
-
return list;
|
|
339
418
|
}, [users, searchQuery, roleFilter, sortBy]);
|
|
340
419
|
const hasActiveFilters = searchQuery.trim() !== "" || roleFilter !== "all";
|
|
341
|
-
|
|
342
|
-
const
|
|
343
|
-
const assignableRoles = isAdmin ? baseRoles : baseRoles.filter((r) => r !== "admin");
|
|
344
|
-
const canManageUser = (target) => isAdmin || !target.roles.includes("admin");
|
|
420
|
+
const assignableRoles = availableRoles;
|
|
421
|
+
const canManageUser = (target) => isAdmin || !target.roles.some((r) => r.key === "admin");
|
|
345
422
|
if (!canAccess) {
|
|
346
423
|
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." }) }) }) }));
|
|
347
424
|
}
|
|
348
|
-
return (_jsxs("main", { className: "p-6 space-y-6", children: [_jsxs(
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
425
|
+
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" })] }), _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
|
|
426
|
+
? "Geen gebruikers gevonden met de huidige filters."
|
|
427
|
+
: "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) => {
|
|
428
|
+
const manageable = canManageUser(u);
|
|
429
|
+
const unassigned = assignableRoles.filter((r) => !u.roles.some((ur) => ur.id === r.id));
|
|
430
|
+
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));
|
|
431
|
+
}) })] }))] }), _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) => {
|
|
432
|
+
const count = rolePermissions.filter((p) => p.roleId === role.id && p.featureId === "" && (p.canView || p.canEdit)).length;
|
|
433
|
+
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));
|
|
434
|
+
}) })] })) }), _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) => {
|
|
435
|
+
const rows = [
|
|
436
|
+
_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),
|
|
437
|
+
];
|
|
438
|
+
for (const f of mod.features ?? []) {
|
|
439
|
+
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}`));
|
|
440
|
+
}
|
|
441
|
+
return rows;
|
|
442
|
+
}) })] }))] })] }) })] }), _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) => {
|
|
443
|
+
const cell = editor?.cells[permCellKey(mod.id, "")] ?? {
|
|
444
|
+
canView: false,
|
|
445
|
+
canEdit: false,
|
|
446
|
+
};
|
|
447
|
+
const expanded = Boolean(expandedModules[mod.id]);
|
|
448
|
+
const hasFeatures = (mod.features ?? []).length > 0;
|
|
449
|
+
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 &&
|
|
450
|
+
(mod.features ?? []).map((f) => {
|
|
451
|
+
const fcell = editor?.cells[permCellKey(mod.id, f.id)] ?? {
|
|
452
|
+
canView: false,
|
|
453
|
+
canEdit: false,
|
|
454
|
+
};
|
|
455
|
+
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));
|
|
456
|
+
})] }, mod.id));
|
|
457
|
+
}))] })] }), _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) => {
|
|
458
|
+
e.preventDefault();
|
|
459
|
+
handleDeleteRole();
|
|
460
|
+
}, 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) => {
|
|
356
461
|
e.preventDefault();
|
|
357
462
|
handleDelete();
|
|
358
463
|
}, disabled: deleting, className: "bg-destructive text-destructive-foreground hover:bg-destructive/90", children: deleting ? "Verwijderen..." : "Verwijderen" })] })] }) })] }));
|
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
export type ModulePermission = "none" | "view" | "edit";
|
|
2
2
|
/**
|
|
3
3
|
* Haalt de effectieve rechten van de ingelogde gebruiker op voor alle
|
|
4
|
-
* geregistreerde modules. Persoonlijke uitzonderingen winnen
|
|
4
|
+
* geregistreerde modules en onderdelen. Persoonlijke uitzonderingen winnen
|
|
5
|
+
* van rolrechten.
|
|
5
6
|
*/
|
|
6
7
|
export declare function useMyModulePermissions(): {
|
|
7
8
|
permissions: Record<string, ModulePermission>;
|
|
8
9
|
loading: boolean;
|
|
9
10
|
};
|
|
10
|
-
/** Effectief recht voor één module: "none" | "view" | "edit". */
|
|
11
|
-
export declare function useModulePermission(moduleId: string): {
|
|
11
|
+
/** Effectief recht voor één module of onderdeel: "none" | "view" | "edit". */
|
|
12
|
+
export declare function useModulePermission(moduleId: string, featureId?: string): {
|
|
12
13
|
permission: ModulePermission;
|
|
13
14
|
canView: boolean;
|
|
14
15
|
canEdit: boolean;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useModulePermission.d.ts","sourceRoot":"","sources":["../../src/hooks/useModulePermission.ts"],"names":[],"mappings":"AAGA,MAAM,MAAM,gBAAgB,GAAG,MAAM,GAAG,MAAM,GAAG,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"useModulePermission.d.ts","sourceRoot":"","sources":["../../src/hooks/useModulePermission.ts"],"names":[],"mappings":"AAGA,MAAM,MAAM,gBAAgB,GAAG,MAAM,GAAG,MAAM,GAAG,MAAM,CAAC;AAIxD;;;;GAIG;AACH,wBAAgB,sBAAsB;;;EA2BrC;AAED,8EAA8E;AAC9E,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,MAAM,EAAE,SAAS,SAAK;;;;;EAUnE"}
|