@flowselections/admin-installatietechniek 1.0.5 → 1.0.7
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.map +1 -1
- package/dist-lib/components/AdminForInstallatieTechniek.js +265 -21
- package/dist-lib/hooks/useModulePermission.d.ts +2 -1
- package/dist-lib/hooks/useModulePermission.d.ts.map +1 -1
- package/dist-lib/index.d.ts +5 -4
- package/dist-lib/index.d.ts.map +1 -1
- package/dist-lib/index.js +3 -3
- package/dist-lib/integrations/supabase/auth-attacher.d.ts.map +1 -1
- package/dist-lib/integrations/supabase/auth-attacher.js +11 -1
- package/dist-lib/integrations/supabase/auth-middleware.d.ts +841 -155
- package/dist-lib/integrations/supabase/auth-middleware.d.ts.map +1 -1
- package/dist-lib/integrations/supabase/client.d.ts +841 -155
- package/dist-lib/integrations/supabase/client.d.ts.map +1 -1
- package/dist-lib/integrations/supabase/client.server.d.ts +841 -155
- package/dist-lib/integrations/supabase/client.server.d.ts.map +1 -1
- package/dist-lib/integrations/supabase/types.d.ts +870 -157
- package/dist-lib/integrations/supabase/types.d.ts.map +1 -1
- package/dist-lib/integrations/supabase/types.js +3 -0
- package/dist-lib/lib/admin.functions.d.ts +146927 -1852
- package/dist-lib/lib/admin.functions.d.ts.map +1 -1
- package/dist-lib/lib/admin.functions.js +446 -25
- package/dist-lib/lib/permission-types.d.ts +7 -0
- package/dist-lib/lib/permission-types.d.ts.map +1 -0
- package/dist-lib/lib/permission-types.js +1 -0
- package/dist-lib/lib/permissions.server.d.ts +1 -1
- package/dist-lib/lib/permissions.server.d.ts.map +1 -1
- package/package.json +3 -3
|
@@ -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":"AAiGA,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+GD,wBAAgB,2BAA2B,CAAC,EAAE,OAAO,EAAE,OAAO,EAAE,GAAE,gCAAqC,+BA8vDtG"}
|
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
-
import { useEffect, useMemo, useState } from "react";
|
|
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 { ChevronDown, ChevronRight, Copy, FlaskConical, MoreVertical, Plus, Pencil, Shield, UserPlus, UserX, } from "lucide-react";
|
|
5
|
-
import { assignRole, createRole, createTestUser, deleteRole, deleteUser, duplicateRole, getRoles, getUserModulePermissions, getUsersWithRoles, inviteUser, removeRole, setModuleEnforce, setUserModulePermission, syncModuleRegistry, updateRole, updateUserName, } from "../lib/admin.functions";
|
|
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";
|
|
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
11
|
const USER_PERMISSION_LABELS = {
|
|
9
12
|
inherit: "Volg rol",
|
|
10
13
|
none: "Geen toegang",
|
|
@@ -12,8 +15,17 @@ const USER_PERMISSION_LABELS = {
|
|
|
12
15
|
edit: "Aanpassen",
|
|
13
16
|
};
|
|
14
17
|
const permCellKey = (moduleId, featureId) => `${moduleId}::${featureId}`;
|
|
18
|
+
const SESSION_EXPIRED_MESSAGE = "Je sessie is verlopen of niet meer geldig. Log opnieuw in en probeer het nogmaals.";
|
|
15
19
|
const getErrorMessage = (error, fallback) => {
|
|
20
|
+
// De auth-middleware gooit een ruwe Response bij een ongeldig/verlopen token.
|
|
21
|
+
// Die komt over de RPC-grens binnen als "[object Response]" — vertaal dat.
|
|
22
|
+
if (typeof Response !== "undefined" && error instanceof Response) {
|
|
23
|
+
return error.status === 401 ? SESSION_EXPIRED_MESSAGE : `Serverfout (${error.status}).`;
|
|
24
|
+
}
|
|
16
25
|
if (error instanceof Error && error.message) {
|
|
26
|
+
if (/\[object Response\]|unauthorized|auth session missing/i.test(error.message)) {
|
|
27
|
+
return SESSION_EXPIRED_MESSAGE;
|
|
28
|
+
}
|
|
17
29
|
if (/permission denied|row-level security|rls|forbidden|not authorized/i.test(error.message)) {
|
|
18
30
|
return "Geen rechten voor deze actie.";
|
|
19
31
|
}
|
|
@@ -39,6 +51,10 @@ export function AdminForInstallatieTechniek({ modules, shellId } = {}) {
|
|
|
39
51
|
const [users, setUsers] = useState([]);
|
|
40
52
|
const [availableRoles, setAvailableRoles] = useState([]);
|
|
41
53
|
const [loading, setLoading] = useState(true);
|
|
54
|
+
const [usersError, setUsersError] = useState(null);
|
|
55
|
+
const [rolesError, setRolesError] = useState(null);
|
|
56
|
+
const usersRequestRef = useRef(0);
|
|
57
|
+
const rolesRequestRef = useRef(0);
|
|
42
58
|
const [submitting, setSubmitting] = useState(false);
|
|
43
59
|
const [renameTarget, setRenameTarget] = useState(null);
|
|
44
60
|
const [renameValue, setRenameValue] = useState("");
|
|
@@ -60,12 +76,27 @@ export function AdminForInstallatieTechniek({ modules, shellId } = {}) {
|
|
|
60
76
|
const [deleting, setDeleting] = useState(false);
|
|
61
77
|
const [testOpen, setTestOpen] = useState(false);
|
|
62
78
|
const [testName, setTestName] = useState("");
|
|
79
|
+
const [testNameTouched, setTestNameTouched] = useState(false);
|
|
63
80
|
const [testRole, setTestRole] = useState("");
|
|
64
81
|
const [testCreating, setTestCreating] = useState(false);
|
|
82
|
+
const [profileLocked, setProfileLocked] = useState(false);
|
|
83
|
+
const [lockedProfile, setLockedProfile] = useState(null);
|
|
65
84
|
const [testCredentials, setTestCredentials] = useState(null);
|
|
66
85
|
const [searchQuery, setSearchQuery] = useState("");
|
|
67
86
|
const [roleFilter, setRoleFilter] = useState("all");
|
|
68
87
|
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);
|
|
69
100
|
const [permUserId, setPermUserId] = useState("");
|
|
70
101
|
const [userPermModules, setUserPermModules] = useState([]);
|
|
71
102
|
const [userPermMap, setUserPermMap] = useState({});
|
|
@@ -79,11 +110,21 @@ export function AdminForInstallatieTechniek({ modules, shellId } = {}) {
|
|
|
79
110
|
const shellModulesKey = shellModules
|
|
80
111
|
.map((m) => `${m.id}:${m.name}:${(m.features ?? []).map((f) => `${f.id}=${f.name}`).join(",")}`)
|
|
81
112
|
.join("|");
|
|
82
|
-
const loadUsers = async () => {
|
|
113
|
+
const loadUsers = async (profileId = activeProfileId) => {
|
|
114
|
+
const requestId = ++usersRequestRef.current;
|
|
83
115
|
setLoading(true);
|
|
116
|
+
setUsersError(null);
|
|
84
117
|
try {
|
|
85
|
-
const result = await getUsersWithRoles(
|
|
118
|
+
const result = await getUsersWithRoles({
|
|
119
|
+
data: { profileId: profileId || undefined },
|
|
120
|
+
});
|
|
121
|
+
if (requestId !== usersRequestRef.current)
|
|
122
|
+
return;
|
|
86
123
|
setUsers(Array.isArray(result?.users) ? result.users : []);
|
|
124
|
+
setProfileLocked(Boolean(result?.profileLocked));
|
|
125
|
+
setLockedProfile(result?.profileLocked
|
|
126
|
+
? result?.activeProfile ?? null
|
|
127
|
+
: null);
|
|
87
128
|
const roleList = Array.isArray(result?.availableRoles) ? result.availableRoles : [];
|
|
88
129
|
setAvailableRoles(roleList);
|
|
89
130
|
const fallback = roleList[0]?.id ?? "";
|
|
@@ -91,25 +132,60 @@ export function AdminForInstallatieTechniek({ modules, shellId } = {}) {
|
|
|
91
132
|
setTestRole((current) => current || fallback);
|
|
92
133
|
}
|
|
93
134
|
catch (error) {
|
|
94
|
-
|
|
135
|
+
if (requestId !== usersRequestRef.current)
|
|
136
|
+
return;
|
|
137
|
+
const message = getErrorMessage(error, "Fout bij het laden van gebruikers en rollen");
|
|
138
|
+
setUsersError(message);
|
|
139
|
+
toast.error(message);
|
|
95
140
|
}
|
|
96
141
|
finally {
|
|
97
|
-
|
|
142
|
+
if (requestId === usersRequestRef.current)
|
|
143
|
+
setLoading(false);
|
|
98
144
|
}
|
|
99
145
|
};
|
|
100
|
-
const loadRoles = async () => {
|
|
146
|
+
const loadRoles = async (profileId = activeProfileId) => {
|
|
147
|
+
const requestId = ++rolesRequestRef.current;
|
|
101
148
|
setRolesLoading(true);
|
|
149
|
+
setRolesError(null);
|
|
102
150
|
try {
|
|
103
|
-
const result = await getRoles(
|
|
151
|
+
const result = await getRoles({
|
|
152
|
+
data: { profileId: profileId || undefined },
|
|
153
|
+
});
|
|
154
|
+
if (requestId !== rolesRequestRef.current)
|
|
155
|
+
return;
|
|
104
156
|
setRoles(Array.isArray(result?.roles) ? result.roles : []);
|
|
105
157
|
setRoleModules(Array.isArray(result?.modules) ? result.modules : []);
|
|
106
158
|
setRolePermissions(Array.isArray(result?.permissions) ? result.permissions : []);
|
|
107
159
|
}
|
|
108
160
|
catch (error) {
|
|
109
|
-
|
|
161
|
+
if (requestId !== rolesRequestRef.current)
|
|
162
|
+
return;
|
|
163
|
+
const message = getErrorMessage(error, "Fout bij het laden van rollen");
|
|
164
|
+
setRolesError(message);
|
|
165
|
+
toast.error(message);
|
|
166
|
+
}
|
|
167
|
+
finally {
|
|
168
|
+
if (requestId === rolesRequestRef.current)
|
|
169
|
+
setRolesLoading(false);
|
|
170
|
+
}
|
|
171
|
+
};
|
|
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"));
|
|
110
186
|
}
|
|
111
187
|
finally {
|
|
112
|
-
|
|
188
|
+
setProfilesLoading(false);
|
|
113
189
|
}
|
|
114
190
|
};
|
|
115
191
|
const loadUserPermissions = async (userId) => {
|
|
@@ -142,6 +218,7 @@ export function AdminForInstallatieTechniek({ modules, shellId } = {}) {
|
|
|
142
218
|
setUserPermLoading(false);
|
|
143
219
|
}
|
|
144
220
|
};
|
|
221
|
+
// Registratie van shell-modules + profielen: los van de profielcontext.
|
|
145
222
|
useEffect(() => {
|
|
146
223
|
if (!canAccess)
|
|
147
224
|
return;
|
|
@@ -157,14 +234,39 @@ export function AdminForInstallatieTechniek({ modules, shellId } = {}) {
|
|
|
157
234
|
}
|
|
158
235
|
if (cancelled)
|
|
159
236
|
return;
|
|
160
|
-
|
|
161
|
-
loadRoles();
|
|
237
|
+
loadProfiles();
|
|
162
238
|
})();
|
|
163
239
|
return () => {
|
|
164
240
|
cancelled = true;
|
|
165
241
|
};
|
|
166
242
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
167
243
|
}, [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
|
+
useEffect(() => {
|
|
255
|
+
if (!canAccess)
|
|
256
|
+
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);
|
|
268
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
269
|
+
}, [activeProfileId, canAccess]);
|
|
168
270
|
useEffect(() => {
|
|
169
271
|
if (!canAccess)
|
|
170
272
|
return;
|
|
@@ -202,7 +304,9 @@ export function AdminForInstallatieTechniek({ modules, shellId } = {}) {
|
|
|
202
304
|
const handleAssign = async (userId, roleId) => {
|
|
203
305
|
setSubmitting(true);
|
|
204
306
|
try {
|
|
205
|
-
await assignRole({
|
|
307
|
+
await assignRole({
|
|
308
|
+
data: { userId, roleId, ...(activeProfileId ? { profileId: activeProfileId } : {}) },
|
|
309
|
+
});
|
|
206
310
|
await refreshOwnSessionIfSelf(userId, currentUserId);
|
|
207
311
|
toast.success("Rol toegekend");
|
|
208
312
|
await loadUsers();
|
|
@@ -319,7 +423,12 @@ export function AdminForInstallatieTechniek({ modules, shellId } = {}) {
|
|
|
319
423
|
setInviting(true);
|
|
320
424
|
try {
|
|
321
425
|
await inviteUser({
|
|
322
|
-
data: {
|
|
426
|
+
data: {
|
|
427
|
+
email: inviteEmail.trim(),
|
|
428
|
+
displayName: inviteName.trim(),
|
|
429
|
+
roleId: inviteRole,
|
|
430
|
+
...(activeProfileId ? { profileId: activeProfileId } : {}),
|
|
431
|
+
},
|
|
323
432
|
});
|
|
324
433
|
toast.success(`Uitnodiging verstuurd naar ${inviteEmail.trim()}`);
|
|
325
434
|
setInviteOpen(false);
|
|
@@ -378,7 +487,11 @@ export function AdminForInstallatieTechniek({ modules, shellId } = {}) {
|
|
|
378
487
|
setTestCreating(true);
|
|
379
488
|
try {
|
|
380
489
|
const result = await createTestUser({
|
|
381
|
-
data: {
|
|
490
|
+
data: {
|
|
491
|
+
displayName: testName.trim(),
|
|
492
|
+
roleId: testRole,
|
|
493
|
+
...(activeProfileId ? { profileId: activeProfileId } : {}),
|
|
494
|
+
},
|
|
382
495
|
});
|
|
383
496
|
setTestCredentials({ email: result.email, password: result.password });
|
|
384
497
|
setTestOpen(false);
|
|
@@ -402,6 +515,99 @@ export function AdminForInstallatieTechniek({ modules, shellId } = {}) {
|
|
|
402
515
|
toast.error("Kopiëren mislukt");
|
|
403
516
|
}
|
|
404
517
|
};
|
|
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;
|
|
405
611
|
const filteredUsers = useMemo(() => {
|
|
406
612
|
const query = searchQuery.trim().toLowerCase();
|
|
407
613
|
const list = users.filter((u) => {
|
|
@@ -429,17 +635,26 @@ export function AdminForInstallatieTechniek({ modules, shellId } = {}) {
|
|
|
429
635
|
}, [users, searchQuery, roleFilter, sortBy]);
|
|
430
636
|
const hasActiveFilters = searchQuery.trim() !== "" || roleFilter !== "all";
|
|
431
637
|
const assignableRoles = availableRoles;
|
|
638
|
+
const suggestedTestName = (() => {
|
|
639
|
+
const roleName = assignableRoles.find((r) => r.id === testRole)?.name;
|
|
640
|
+
return roleName ? `Test ${roleName}` : "Test gebruiker";
|
|
641
|
+
})();
|
|
642
|
+
const openTestDialog = () => {
|
|
643
|
+
setTestNameTouched(false);
|
|
644
|
+
setTestName(suggestedTestName);
|
|
645
|
+
setTestOpen(true);
|
|
646
|
+
};
|
|
432
647
|
const canManageUser = (target) => isAdmin || !target.roles.some((r) => r.key === "admin");
|
|
433
648
|
if (!canAccess) {
|
|
434
649
|
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." }) }) }) }));
|
|
435
650
|
}
|
|
436
|
-
return (_jsxs("main", { className: "p-6 space-y-6", children: [_jsxs(Card, { children: [
|
|
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
|
|
437
652
|
? "Geen gebruikers gevonden met de huidige filters."
|
|
438
653
|
: "Geen gebruikers gevonden." })) : (_jsxs(Table, { children: [_jsx(TableHeader, { children: _jsxs(TableRow, { children: [_jsx(TableHead, { children: "Gebruiker" }), _jsx(TableHead, { children: "Rollen" }), _jsx(TableHead, { className: "w-16" })] }) }), _jsx(TableBody, { children: filteredUsers.map((u) => {
|
|
439
654
|
const manageable = canManageUser(u);
|
|
440
655
|
const unassigned = assignableRoles.filter((r) => !u.roles.some((ur) => ur.id === r.id));
|
|
441
|
-
return (_jsxs(TableRow, { children: [_jsxs(TableCell, { children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx("span", { className: "font-medium", children: u.displayName }), !u.emailConfirmed && (_jsx(Badge, { variant: "outline", className: "text-xs", children: "Wacht op verificatie" })), u.isTestAccount && (_jsx(Badge, { variant: "secondary", className: "text-xs", children: "Test" }))] }), _jsx("div", { className: "text-xs text-muted-foreground", children: u.email })] }), _jsx(TableCell, { children: _jsx("div", { className: "flex flex-wrap gap-2", children: u.roles.length === 0 ? (_jsx("span", { className: "text-sm text-muted-foreground", children: "Geen rollen" })) : (u.roles.map((role) => (_jsx(Badge, { variant: "secondary", children: role.name }, `${u.id}-${role.id}`)))) }) }), _jsx(TableCell, { className: "text-right", children: _jsxs(DropdownMenu, { children: [_jsx(DropdownMenuTrigger, { asChild: true, children: _jsx(Button, { variant: "ghost", size: "sm", "aria-label": `Acties voor ${u.email}`, disabled: submitting, children: _jsx(MoreVertical, { className: "h-4 w-4" }) }) }), _jsxs(DropdownMenuContent, { align: "end", className: "w-52", children: [_jsxs(DropdownMenuSub, { children: [_jsx(DropdownMenuSubTrigger, { disabled: !manageable || unassigned.length === 0, children: "Rol toekennen" }), _jsx(DropdownMenuSubContent, { children: unassigned.length === 0 ? (_jsx(DropdownMenuItem, { disabled: true, children: "Geen rollen beschikbaar" })) : (unassigned.map((role) => (_jsx(DropdownMenuItem, { onSelect: () => handleAssign(u.id, role.id), children: role.name }, `assign-${u.id}-${role.id}`)))) })] }), _jsxs(DropdownMenuSub, { children: [_jsx(DropdownMenuSubTrigger, { disabled: u.roles.length === 0, children: "Rol verwijderen" }), _jsx(DropdownMenuSubContent, { children: u.roles.map((role) => (_jsx(DropdownMenuItem, { disabled: role.key === "admin" && !isAdmin, onSelect: () => handleRemove(u.id, role.id), children: role.name }, `remove-${u.id}-${role.id}`))) })] }), _jsx(DropdownMenuSeparator, {}), _jsxs(DropdownMenuItem, { disabled: !manageable, onSelect: () => openRename(u), children: [_jsx(Pencil, { className: "h-4 w-4 mr-2" }), "Naam wijzigen"] }), _jsxs(DropdownMenuItem, { className: "text-destructive focus:text-destructive", disabled: u.id === currentUserId || !manageable, onSelect: () => setDeleteTarget(u), children: [_jsx(UserX, { className: "h-4 w-4 mr-2" }), "Account verwijderen"] })] })] }) })] }, u.id));
|
|
442
|
-
}) })] }))] }), _jsx(TabsContent, { value: "roles", className: "mt-4", children: rolesLoading ? (_jsx("p", { className: "text-sm text-muted-foreground", children: "Laden..." })) : roles.length === 0 ? (_jsx("p", { className: "text-sm text-muted-foreground", children: "Er zijn nog geen rollen aangemaakt." })) : (_jsxs(Table, { children: [_jsx(TableHeader, { children: _jsxs(TableRow, { children: [_jsx(TableHead, { children: "Rol" }), _jsx(TableHead, { children: "Toegang" }), _jsx(TableHead, { className: "w-16" })] }) }), _jsx(TableBody, { children: roles.map((role) => {
|
|
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) => {
|
|
443
658
|
const count = rolePermissions.filter((p) => p.roleId === role.id && p.featureId === "" && (p.canView || p.canEdit)).length;
|
|
444
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));
|
|
445
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) => {
|
|
@@ -450,7 +665,27 @@ export function AdminForInstallatieTechniek({ modules, shellId } = {}) {
|
|
|
450
665
|
rows.push(_jsxs(TableRow, { children: [_jsx(TableCell, { className: "pl-8 text-sm text-muted-foreground", children: f.name }), _jsx(TableCell, { children: _jsxs(Select, { value: userPermMap[permCellKey(mod.id, f.id)] ?? "inherit", onValueChange: (v) => handleUserPermChange(mod.id, f.id, v), children: [_jsx(SelectTrigger, { className: "w-48", children: _jsx(SelectValue, {}) }), _jsx(SelectContent, { children: Object.keys(USER_PERMISSION_LABELS).map((p) => (_jsx(SelectItem, { value: p, children: USER_PERMISSION_LABELS[p] }, p))) })] }) })] }, `${mod.id}-${f.id}`));
|
|
451
666
|
}
|
|
452
667
|
return rows;
|
|
453
|
-
}) })] }))] }), _jsxs(TabsContent, { value: "modules", className: "mt-4 space-y-6", children: [_jsx("p", { className: "text-sm text-muted-foreground", children: "Schakel \"Toegang afdwingen\" in voor een module om de rollen en gebruikersrechten actief te laten controleren. Modules zonder deze schakelaar blijven voor iedereen toegankelijk (default-allow)." }), roleModules.length === 0 ? (_jsx("p", { className: "text-sm text-muted-foreground", children: "Er zijn nog geen modules geregistreerd." })) : (_jsxs(Table, { children: [_jsx(TableHeader, { children: _jsxs(TableRow, { children: [_jsx(TableHead, { children: "Module" }), _jsx(TableHead, { className: "w-48", children: "Toegang afdwingen" })] }) }), _jsx(TableBody, { children: roleModules.map((mod) => (_jsxs(TableRow, { children: [_jsx(TableCell, { className: "font-medium", children: mod.name }),
|
|
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) => {
|
|
454
689
|
const cell = editor?.cells[permCellKey(mod.id, "")] ?? {
|
|
455
690
|
canView: false,
|
|
456
691
|
canEdit: false,
|
|
@@ -465,7 +700,16 @@ export function AdminForInstallatieTechniek({ modules, shellId } = {}) {
|
|
|
465
700
|
};
|
|
466
701
|
return (_jsxs("div", { className: "flex items-center bg-muted/40 px-4 py-2 pl-10 text-sm", children: [_jsx("span", { className: "flex-1", children: f.name }), _jsxs("div", { className: "flex items-center gap-8", children: [_jsx("span", { className: "flex w-16 justify-center", children: _jsx(Checkbox, { checked: fcell.canView, onCheckedChange: (v) => setCell(mod.id, f.id, "canView", Boolean(v)), "aria-label": `Lezen ${mod.name} ${f.name}` }) }), _jsx("span", { className: "flex w-16 justify-center", children: _jsx(Checkbox, { checked: fcell.canEdit, onCheckedChange: (v) => setCell(mod.id, f.id, "canEdit", Boolean(v)), "aria-label": `Bewerken ${mod.name} ${f.name}` }) })] })] }, f.id));
|
|
467
702
|
})] }, mod.id));
|
|
468
|
-
}))] })] }), _jsxs(DialogFooter, { children: [_jsx(Button, { variant: "outline", onClick: () => setEditor(null), disabled: editorSaving, children: "Annuleren" }), _jsx(Button, { onClick: handleSaveRole, disabled: editorSaving || !editor?.name.trim(), children: editorSaving ? "Opslaan..." : "Opslaan" })] })] }) }), _jsx(Dialog, { open: inviteOpen, onOpenChange: setInviteOpen, children: _jsxs(DialogContent, { children: [_jsxs(DialogHeader, { children: [_jsx(DialogTitle, { children: "Nieuw account aanmaken" }), _jsx(DialogDescription, { children: "De gebruiker ontvangt een uitnodigingsmail en kiest zelf een wachtwoord." })] }), _jsxs("div", { className: "space-y-4", children: [_jsxs("div", { className: "space-y-2", children: [_jsx(Label, { htmlFor: "invite-name", children: "Naam" }), _jsx(Input, { id: "invite-name", value: inviteName, onChange: (e) => setInviteName(e.target.value), placeholder: "Jan Jansen", disabled: inviting })] }), _jsxs("div", { className: "space-y-2", children: [_jsx(Label, { htmlFor: "invite-email", children: "E-mail" }), _jsx(Input, { id: "invite-email", type: "email", value: inviteEmail, onChange: (e) => setInviteEmail(e.target.value), placeholder: "jan@voorbeeld.nl", disabled: inviting })] }), _jsxs("div", { className: "space-y-2", children: [_jsx(Label, { children: "Rol" }), _jsxs(Select, { value: inviteRole, onValueChange: setInviteRole, children: [_jsx(SelectTrigger, { children: _jsx(SelectValue, { placeholder: "Kies rol" }) }), _jsx(SelectContent, { children: assignableRoles.map((r) => (_jsx(SelectItem, { value: r.id, children: r.name }, r.id))) })] })] })] }), _jsxs(DialogFooter, { children: [_jsx(Button, { variant: "outline", onClick: () => setInviteOpen(false), disabled: inviting, children: "Annuleren" }), _jsx(Button, { onClick: handleInvite, disabled: inviting || !inviteEmail.trim() || !inviteName.trim() || !inviteRole, children: inviting ? "Versturen..." : "Uitnodiging versturen" })] })] }) }), _jsx(Dialog, { open: testOpen, onOpenChange: (open) => !testCreating && setTestOpen(open), children: _jsxs(DialogContent, { children: [_jsxs(DialogHeader, { children: [_jsx(DialogTitle, { children: "Testaccount aanmaken" }), _jsx(DialogDescription, { children: "Maakt direct een geverifieerd account met een automatisch wachtwoord \u2014 geen e-mail nodig." })] }), _jsxs("div", { className: "space-y-4", children: [_jsxs("div", { className: "space-y-2", children: [_jsx(Label, { htmlFor: "test-name", children: "Naam" }), _jsx(Input, { id: "test-name", value: testName, onChange: (e) =>
|
|
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) => {
|
|
704
|
+
setTestNameTouched(true);
|
|
705
|
+
setTestName(e.target.value);
|
|
706
|
+
}, placeholder: suggestedTestName, disabled: testCreating })] }), _jsxs("div", { className: "space-y-2", children: [_jsx(Label, { children: "Rol" }), _jsxs(Select, { value: testRole, onValueChange: (value) => {
|
|
707
|
+
setTestRole(value);
|
|
708
|
+
if (!testNameTouched) {
|
|
709
|
+
const roleName = assignableRoles.find((r) => r.id === value)?.name;
|
|
710
|
+
setTestName(roleName ? `Test ${roleName}` : "");
|
|
711
|
+
}
|
|
712
|
+
}, children: [_jsx(SelectTrigger, { children: _jsx(SelectValue, { placeholder: "Kies rol" }) }), _jsx(SelectContent, { children: assignableRoles.map((r) => (_jsx(SelectItem, { value: r.id, children: r.name }, r.id))) })] })] })] }), _jsxs(DialogFooter, { children: [_jsx(Button, { variant: "outline", onClick: () => setTestOpen(false), disabled: testCreating, children: "Annuleren" }), _jsx(Button, { onClick: handleCreateTestUser, disabled: testCreating || !testName.trim() || !testRole, children: testCreating ? "Aanmaken..." : "Aanmaken" })] })] }) }), _jsx(Dialog, { open: testCredentials !== null, onOpenChange: (open) => !open && setTestCredentials(null), children: _jsxs(DialogContent, { children: [_jsxs(DialogHeader, { children: [_jsx(DialogTitle, { children: "Testaccount aangemaakt" }), _jsx(DialogDescription, { children: "Kopieer de inloggegevens nu \u2014 het wachtwoord wordt maar \u00E9\u00E9n keer getoond." })] }), _jsxs("div", { className: "space-y-3", children: [_jsxs("div", { className: "space-y-1", children: [_jsx(Label, { children: "E-mail" }), _jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Input, { readOnly: true, value: testCredentials?.email ?? "" }), _jsx(Button, { variant: "outline", size: "icon", onClick: () => testCredentials && copyToClipboard(testCredentials.email, "E-mail"), "aria-label": "Kopieer e-mail", children: _jsx(Copy, { className: "h-4 w-4" }) })] })] }), _jsxs("div", { className: "space-y-1", children: [_jsx(Label, { children: "Wachtwoord" }), _jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Input, { readOnly: true, value: testCredentials?.password ?? "", className: "font-mono" }), _jsx(Button, { variant: "outline", size: "icon", onClick: () => testCredentials && copyToClipboard(testCredentials.password, "Wachtwoord"), "aria-label": "Kopieer wachtwoord", children: _jsx(Copy, { className: "h-4 w-4" }) })] })] })] }), _jsx(DialogFooter, { children: _jsx(Button, { onClick: () => setTestCredentials(null), children: "Sluiten" }) })] }) }), _jsx(AlertDialog, { open: roleDeleteTarget !== null, onOpenChange: (open) => !open && setRoleDeleteTarget(null), children: _jsxs(AlertDialogContent, { children: [_jsxs(AlertDialogHeader, { children: [_jsx(AlertDialogTitle, { children: "Rol verwijderen?" }), _jsxs(AlertDialogDescription, { children: ["Weet je zeker dat je de rol ", _jsx("strong", { children: roleDeleteTarget?.name }), " wilt verwijderen? Gebruikers met deze rol verliezen de bijbehorende rechten."] })] }), _jsxs(AlertDialogFooter, { children: [_jsx(AlertDialogCancel, { children: "Annuleren" }), _jsx(AlertDialogAction, { onClick: (e) => {
|
|
469
713
|
e.preventDefault();
|
|
470
714
|
handleDeleteRole();
|
|
471
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) => {
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
|
|
1
|
+
import type { ModulePermission } from "../lib/permission-types";
|
|
2
|
+
export type { ModulePermission };
|
|
2
3
|
/**
|
|
3
4
|
* Haalt de effectieve rechten van de ingelogde gebruiker op voor alle
|
|
4
5
|
* geregistreerde modules en onderdelen. Persoonlijke uitzonderingen winnen
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useModulePermission.d.ts","sourceRoot":"","sources":["../../src/hooks/useModulePermission.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"useModulePermission.d.ts","sourceRoot":"","sources":["../../src/hooks/useModulePermission.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAE/D,YAAY,EAAE,gBAAgB,EAAE,CAAC;AAMjC;;;;GAIG;AACH,wBAAgB,sBAAsB;;;EAmBrC;AAED,8EAA8E;AAC9E,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,MAAM,EAAE,SAAS,SAAK;;;;;EAgBnE;AAED,kDAAkD;AAClD,wBAAgB,2BAA2B,CAAC,WAAW,EAAE;IACvD,iBAAiB,EAAE,CAAC,OAAO,EAAE;QAAE,QAAQ,EAAE,MAAM,EAAE,CAAA;KAAE,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CACvE,iBAEA"}
|
package/dist-lib/index.d.ts
CHANGED
|
@@ -2,11 +2,12 @@ import type { FlowModule } from "@flowselections/core";
|
|
|
2
2
|
export * from './_core-safelist';
|
|
3
3
|
export { AdminForInstallatieTechniek } from './components/AdminForInstallatieTechniek';
|
|
4
4
|
export type { AdminForInstallatieTechniekProps, AdminModuleInput } from './components/AdminForInstallatieTechniek';
|
|
5
|
-
export { useModulePermission, useMyModulePermissions } from '
|
|
6
|
-
export type {
|
|
5
|
+
export { useModulePermission, useMyModulePermissions, useModulePermissionTransform, ModuleAccessGuard, } from '@flowselections/core';
|
|
6
|
+
export type { PermissionLevel, ModuleAccessGuardProps } from '@flowselections/core';
|
|
7
|
+
/** @deprecated gebruik PermissionLevel uit @flowselections/core */
|
|
8
|
+
export type ModulePermission = import('@flowselections/core').PermissionLevel;
|
|
7
9
|
export { useDiscoveredModules } from './lib/moduleDiscovery';
|
|
8
10
|
export type { DiscoveredModule, DiscoveredFeature } from './lib/moduleDiscovery';
|
|
9
|
-
export {
|
|
10
|
-
export { requireModulePermission } from './lib/permissions.server';
|
|
11
|
+
export { requireModulePermission } from '@flowselections/core/server';
|
|
11
12
|
export declare const adminInstallatietechniekModule: FlowModule;
|
|
12
13
|
//# sourceMappingURL=index.d.ts.map
|
package/dist-lib/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AAEvD,cAAc,kBAAkB,CAAC;AACjC,OAAO,EAAE,2BAA2B,EAAE,MAAM,0CAA0C,CAAC;AACvF,YAAY,EAAE,gCAAgC,EAAE,gBAAgB,EAAE,MAAM,0CAA0C,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AAEvD,cAAc,kBAAkB,CAAC;AACjC,OAAO,EAAE,2BAA2B,EAAE,MAAM,0CAA0C,CAAC;AACvF,YAAY,EAAE,gCAAgC,EAAE,gBAAgB,EAAE,MAAM,0CAA0C,CAAC;AAEnH,OAAO,EACL,mBAAmB,EACnB,sBAAsB,EACtB,4BAA4B,EAC5B,iBAAiB,GAClB,MAAM,sBAAsB,CAAC;AAC9B,YAAY,EAAE,eAAe,EAAE,sBAAsB,EAAE,MAAM,sBAAsB,CAAC;AACpF,mEAAmE;AACnE,MAAM,MAAM,gBAAgB,GAAG,OAAO,sBAAsB,EAAE,eAAe,CAAC;AAC9E,OAAO,EAAE,oBAAoB,EAAE,MAAM,uBAAuB,CAAC;AAC7D,YAAY,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAC;AACjF,OAAO,EAAE,uBAAuB,EAAE,MAAM,6BAA6B,CAAC;AAItE,eAAO,MAAM,8BAA8B,EAAE,UAY5C,CAAC"}
|
package/dist-lib/index.js
CHANGED
|
@@ -4,10 +4,10 @@
|
|
|
4
4
|
import { Shield } from "lucide-react";
|
|
5
5
|
export * from './_core-safelist';
|
|
6
6
|
export { AdminForInstallatieTechniek } from './components/AdminForInstallatieTechniek';
|
|
7
|
-
|
|
7
|
+
// Check-laag komt uit core; deze module blijft alleen het beheer leveren.
|
|
8
|
+
export { useModulePermission, useMyModulePermissions, useModulePermissionTransform, ModuleAccessGuard, } from '@flowselections/core';
|
|
8
9
|
export { useDiscoveredModules } from './lib/moduleDiscovery';
|
|
9
|
-
export {
|
|
10
|
-
export { requireModulePermission } from './lib/permissions.server';
|
|
10
|
+
export { requireModulePermission } from '@flowselections/core/server';
|
|
11
11
|
export const adminInstallatietechniekModule = {
|
|
12
12
|
id: "admin-installatietechniek",
|
|
13
13
|
name: "Admin Installatietechniek",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"auth-attacher.d.ts","sourceRoot":"","sources":["../../../src/integrations/supabase/auth-attacher.ts"],"names":[],"mappings":"AAMA,eAAO,MAAM,kBAAkB,
|
|
1
|
+
{"version":3,"file":"auth-attacher.d.ts","sourceRoot":"","sources":["../../../src/integrations/supabase/auth-attacher.ts"],"names":[],"mappings":"AAMA,eAAO,MAAM,kBAAkB,mHAwB9B,CAAA"}
|
|
@@ -5,7 +5,17 @@ import { supabase } from './client';
|
|
|
5
5
|
// the browser never attaches the bearer token to serverFn RPCs.
|
|
6
6
|
export const attachSupabaseAuth = createMiddleware({ type: 'function' }).client(async ({ next }) => {
|
|
7
7
|
const { data } = await supabase.auth.getSession();
|
|
8
|
-
|
|
8
|
+
let session = data.session;
|
|
9
|
+
// Een (bijna) verlopen access-token wordt door de server geweigerd met
|
|
10
|
+
// "Auth session missing!". Ververs proactief zodat serverFn-aanroepen niet
|
|
11
|
+
// willekeurig met 401 falen.
|
|
12
|
+
const expiresAt = session?.expires_at ?? 0;
|
|
13
|
+
const nowSeconds = Math.floor(Date.now() / 1000);
|
|
14
|
+
if (session && expiresAt && expiresAt - nowSeconds < 60) {
|
|
15
|
+
const refreshed = await supabase.auth.refreshSession();
|
|
16
|
+
session = refreshed.data.session ?? session;
|
|
17
|
+
}
|
|
18
|
+
const token = session?.access_token;
|
|
9
19
|
return next({
|
|
10
20
|
// `x-sb-auth` is een fallback: sommige preview-proxies overschrijven de
|
|
11
21
|
// Authorization-header, waardoor de server een ongeldig token zag.
|