hazo_auth 3.0.4 → 4.0.0
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/README.md +146 -0
- package/SETUP_CHECKLIST.md +369 -0
- package/dist/components/layouts/my_settings/components/profile_picture_library_tab.d.ts.map +1 -1
- package/dist/components/layouts/my_settings/components/profile_picture_library_tab.js +2 -2
- package/dist/components/layouts/rbac_test/index.d.ts +15 -0
- package/dist/components/layouts/rbac_test/index.d.ts.map +1 -0
- package/dist/components/layouts/rbac_test/index.js +378 -0
- package/dist/components/layouts/shared/components/password_field.js +1 -1
- package/dist/components/layouts/shared/components/sidebar_layout_wrapper.d.ts.map +1 -1
- package/dist/components/layouts/shared/components/sidebar_layout_wrapper.js +2 -2
- package/dist/components/layouts/shared/components/two_column_auth_layout.js +1 -1
- package/dist/components/layouts/user_management/components/roles_matrix.d.ts +2 -3
- package/dist/components/layouts/user_management/components/roles_matrix.d.ts.map +1 -1
- package/dist/components/layouts/user_management/components/roles_matrix.js +133 -8
- package/dist/components/layouts/user_management/components/scope_hierarchy_tab.d.ts +12 -0
- package/dist/components/layouts/user_management/components/scope_hierarchy_tab.d.ts.map +1 -0
- package/dist/components/layouts/user_management/components/scope_hierarchy_tab.js +291 -0
- package/dist/components/layouts/user_management/components/scope_labels_tab.d.ts +13 -0
- package/dist/components/layouts/user_management/components/scope_labels_tab.d.ts.map +1 -0
- package/dist/components/layouts/user_management/components/scope_labels_tab.js +158 -0
- package/dist/components/layouts/user_management/components/user_scopes_tab.d.ts +11 -0
- package/dist/components/layouts/user_management/components/user_scopes_tab.d.ts.map +1 -0
- package/dist/components/layouts/user_management/components/user_scopes_tab.js +267 -0
- package/dist/components/layouts/user_management/index.d.ts +9 -2
- package/dist/components/layouts/user_management/index.d.ts.map +1 -1
- package/dist/components/layouts/user_management/index.js +22 -6
- package/dist/components/ui/select.d.ts +14 -0
- package/dist/components/ui/select.d.ts.map +1 -0
- package/dist/components/ui/select.js +59 -0
- package/dist/components/ui/tree-view.d.ts +108 -0
- package/dist/components/ui/tree-view.d.ts.map +1 -0
- package/dist/components/ui/tree-view.js +194 -0
- package/dist/lib/auth/auth_types.d.ts +45 -0
- package/dist/lib/auth/auth_types.d.ts.map +1 -1
- package/dist/lib/auth/auth_types.js +13 -0
- package/dist/lib/auth/hazo_get_auth.server.d.ts +4 -2
- package/dist/lib/auth/hazo_get_auth.server.d.ts.map +1 -1
- package/dist/lib/auth/hazo_get_auth.server.js +107 -3
- package/dist/lib/auth/scope_cache.d.ts +92 -0
- package/dist/lib/auth/scope_cache.d.ts.map +1 -0
- package/dist/lib/auth/scope_cache.js +171 -0
- package/dist/lib/scope_hierarchy_config.server.d.ts +39 -0
- package/dist/lib/scope_hierarchy_config.server.d.ts.map +1 -0
- package/dist/lib/scope_hierarchy_config.server.js +96 -0
- package/dist/lib/services/email_service.d.ts.map +1 -1
- package/dist/lib/services/email_service.js +7 -2
- package/dist/lib/services/profile_picture_service.d.ts +1 -7
- package/dist/lib/services/profile_picture_service.d.ts.map +1 -1
- package/dist/lib/services/profile_picture_service.js +77 -32
- package/dist/lib/services/registration_service.js +1 -1
- package/dist/lib/services/scope_labels_service.d.ts +48 -0
- package/dist/lib/services/scope_labels_service.d.ts.map +1 -0
- package/dist/lib/services/scope_labels_service.js +277 -0
- package/dist/lib/services/scope_service.d.ts +114 -0
- package/dist/lib/services/scope_service.d.ts.map +1 -0
- package/dist/lib/services/scope_service.js +582 -0
- package/dist/lib/services/user_scope_service.d.ts +74 -0
- package/dist/lib/services/user_scope_service.d.ts.map +1 -0
- package/dist/lib/services/user_scope_service.js +415 -0
- package/hazo_auth_config.example.ini +1 -1
- package/package.json +3 -1
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// file_description: internal reusable component for roles-permissions
|
|
1
|
+
// file_description: internal reusable component for roles-permissions management with tag-based UI
|
|
2
2
|
// section: client_directive
|
|
3
3
|
"use client";
|
|
4
4
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
@@ -10,30 +10,36 @@ import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from "
|
|
|
10
10
|
import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, } from "../../../ui/dialog";
|
|
11
11
|
import { Input } from "../../../ui/input";
|
|
12
12
|
import { Label } from "../../../ui/label";
|
|
13
|
-
import { Plus, Loader2, CircleCheck, CircleX } from "lucide-react";
|
|
13
|
+
import { Plus, Loader2, CircleCheck, CircleX, Pencil } from "lucide-react";
|
|
14
14
|
import { toast } from "sonner";
|
|
15
15
|
import { Avatar, AvatarImage, AvatarFallback } from "../../../ui/avatar";
|
|
16
16
|
import { useHazoAuthConfig } from "../../../../contexts/hazo_auth_provider";
|
|
17
17
|
// section: component
|
|
18
18
|
/**
|
|
19
|
-
* Roles matrix component - reusable internal component for roles-permissions
|
|
20
|
-
* Shows
|
|
21
|
-
* Checkboxes in cells indicate role-permission mappings
|
|
19
|
+
* Roles matrix component - reusable internal component for roles-permissions management
|
|
20
|
+
* Shows roles with permission tags and an edit button to modify permissions via dialog
|
|
22
21
|
* Changes are stored locally and only saved when Save button is pressed
|
|
23
22
|
* @param props - Component props including button enable flags and save callback
|
|
24
23
|
* @returns Roles matrix component
|
|
25
24
|
*/
|
|
26
25
|
export function RolesMatrix({ add_button_enabled = true, role_name_selection_enabled = true, permissions_read_only = false, show_save_cancel = true, user_id, onSave, onCancel, onRoleSelection, className, }) {
|
|
26
|
+
var _a;
|
|
27
27
|
const { apiBasePath } = useHazoAuthConfig();
|
|
28
28
|
const [roles, setRoles] = useState([]);
|
|
29
29
|
const [originalRoles, setOriginalRoles] = useState([]);
|
|
30
30
|
const [permissions, setPermissions] = useState([]);
|
|
31
|
+
const [permissionsWithDescriptions, setPermissionsWithDescriptions] = useState([]);
|
|
31
32
|
const [loading, setLoading] = useState(true);
|
|
32
33
|
const [saving, setSaving] = useState(false);
|
|
33
34
|
const [isAddDialogOpen, setIsAddDialogOpen] = useState(false);
|
|
34
35
|
const [newRoleName, setNewRoleName] = useState("");
|
|
35
36
|
const [userInfo, setUserInfo] = useState(null);
|
|
36
37
|
const [userRoleIds, setUserRoleIds] = useState([]);
|
|
38
|
+
// Edit permissions dialog state
|
|
39
|
+
const [editPermissionsDialogOpen, setEditPermissionsDialogOpen] = useState(false);
|
|
40
|
+
const [editingRoleIndex, setEditingRoleIndex] = useState(null);
|
|
41
|
+
// Track which roles have expanded permission tags (for read-only mode)
|
|
42
|
+
const [expandedRoles, setExpandedRoles] = useState(new Set());
|
|
37
43
|
// Load roles and permissions on mount
|
|
38
44
|
useEffect(() => {
|
|
39
45
|
const loadData = async () => {
|
|
@@ -48,6 +54,44 @@ export function RolesMatrix({ add_button_enabled = true, role_name_selection_ena
|
|
|
48
54
|
return;
|
|
49
55
|
}
|
|
50
56
|
setPermissions(roles_data.permissions.map((p) => p.permission_name));
|
|
57
|
+
// Also fetch permission descriptions from the permissions endpoint
|
|
58
|
+
try {
|
|
59
|
+
const perms_response = await fetch(`${apiBasePath}/user_management/permissions`);
|
|
60
|
+
const perms_data = await perms_response.json();
|
|
61
|
+
if (perms_data.success) {
|
|
62
|
+
// Combine DB permissions and config permissions with descriptions
|
|
63
|
+
const all_perms_with_desc = [];
|
|
64
|
+
// DB permissions have descriptions
|
|
65
|
+
if (Array.isArray(perms_data.db_permissions)) {
|
|
66
|
+
perms_data.db_permissions.forEach((p) => {
|
|
67
|
+
all_perms_with_desc.push({
|
|
68
|
+
permission_name: p.permission_name,
|
|
69
|
+
description: p.description || "",
|
|
70
|
+
});
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
// Config permissions don't have descriptions in the API
|
|
74
|
+
if (Array.isArray(perms_data.config_permissions)) {
|
|
75
|
+
perms_data.config_permissions.forEach((name) => {
|
|
76
|
+
// Only add if not already in db_permissions
|
|
77
|
+
if (!all_perms_with_desc.some(p => p.permission_name === name)) {
|
|
78
|
+
all_perms_with_desc.push({
|
|
79
|
+
permission_name: name,
|
|
80
|
+
description: "",
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
setPermissionsWithDescriptions(all_perms_with_desc);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
catch (_a) {
|
|
89
|
+
// If we can't get descriptions, use permissions without descriptions
|
|
90
|
+
setPermissionsWithDescriptions(roles_data.permissions.map((p) => ({
|
|
91
|
+
permission_name: p.permission_name,
|
|
92
|
+
description: "",
|
|
93
|
+
})));
|
|
94
|
+
}
|
|
51
95
|
// Initialize roles with permissions as Sets
|
|
52
96
|
const roles_with_permissions = roles_data.roles.map((role) => ({
|
|
53
97
|
role_id: role.role_id,
|
|
@@ -105,7 +149,7 @@ export function RolesMatrix({ add_button_enabled = true, role_name_selection_ena
|
|
|
105
149
|
}
|
|
106
150
|
};
|
|
107
151
|
void loadData();
|
|
108
|
-
}, [user_id]);
|
|
152
|
+
}, [user_id, apiBasePath]);
|
|
109
153
|
// Handle checkbox change for role-permission mapping
|
|
110
154
|
const handlePermissionToggle = (role_index, permission_name) => {
|
|
111
155
|
setRoles((prev) => {
|
|
@@ -264,6 +308,53 @@ export function RolesMatrix({ add_button_enabled = true, role_name_selection_ena
|
|
|
264
308
|
setSaving(false);
|
|
265
309
|
}
|
|
266
310
|
};
|
|
311
|
+
// Handle opening the edit permissions dialog
|
|
312
|
+
const handleOpenEditPermissions = (role_index) => {
|
|
313
|
+
setEditingRoleIndex(role_index);
|
|
314
|
+
setEditPermissionsDialogOpen(true);
|
|
315
|
+
};
|
|
316
|
+
// Handle toggling expanded state for a role's permission tags
|
|
317
|
+
const handleToggleExpandedPermissions = (role_index) => {
|
|
318
|
+
setExpandedRoles((prev) => {
|
|
319
|
+
const updated = new Set(prev);
|
|
320
|
+
if (updated.has(role_index)) {
|
|
321
|
+
updated.delete(role_index);
|
|
322
|
+
}
|
|
323
|
+
else {
|
|
324
|
+
updated.add(role_index);
|
|
325
|
+
}
|
|
326
|
+
return updated;
|
|
327
|
+
});
|
|
328
|
+
};
|
|
329
|
+
// Handle select all permissions for the editing role
|
|
330
|
+
const handleSelectAllPermissions = () => {
|
|
331
|
+
if (editingRoleIndex === null)
|
|
332
|
+
return;
|
|
333
|
+
setRoles((prev) => {
|
|
334
|
+
const updated = [...prev];
|
|
335
|
+
const role = Object.assign({}, updated[editingRoleIndex]);
|
|
336
|
+
role.permissions = new Set(permissions);
|
|
337
|
+
updated[editingRoleIndex] = role;
|
|
338
|
+
return updated;
|
|
339
|
+
});
|
|
340
|
+
};
|
|
341
|
+
// Handle unselect all permissions for the editing role
|
|
342
|
+
const handleUnselectAllPermissions = () => {
|
|
343
|
+
if (editingRoleIndex === null)
|
|
344
|
+
return;
|
|
345
|
+
setRoles((prev) => {
|
|
346
|
+
const updated = [...prev];
|
|
347
|
+
const role = Object.assign({}, updated[editingRoleIndex]);
|
|
348
|
+
role.permissions = new Set();
|
|
349
|
+
updated[editingRoleIndex] = role;
|
|
350
|
+
return updated;
|
|
351
|
+
});
|
|
352
|
+
};
|
|
353
|
+
// Get description for a permission
|
|
354
|
+
const getPermissionDescription = (permission_name) => {
|
|
355
|
+
const perm = permissionsWithDescriptions.find(p => p.permission_name === permission_name);
|
|
356
|
+
return (perm === null || perm === void 0 ? void 0 : perm.description) || "";
|
|
357
|
+
};
|
|
267
358
|
if (loading) {
|
|
268
359
|
return (_jsx("div", { className: `cls_roles_matrix flex items-center justify-center p-8 ${className || ""}`, children: _jsx(Loader2, { className: "h-6 w-6 animate-spin text-slate-400" }) }));
|
|
269
360
|
}
|
|
@@ -278,12 +369,46 @@ export function RolesMatrix({ add_button_enabled = true, role_name_selection_ena
|
|
|
278
369
|
}
|
|
279
370
|
return email.substring(0, 2).toUpperCase();
|
|
280
371
|
};
|
|
281
|
-
|
|
372
|
+
// Number of permission tags to show before truncating
|
|
373
|
+
const MAX_VISIBLE_TAGS = 4;
|
|
374
|
+
return (_jsxs("div", { className: `cls_roles_matrix flex flex-col gap-4 w-full ${className || ""}`, children: [user_id && userInfo && (_jsxs("div", { className: "cls_roles_matrix_user_info flex items-center gap-4 p-4 border rounded-lg bg-muted/50", children: [_jsxs(Avatar, { className: "cls_roles_matrix_user_avatar h-12 w-12", children: [_jsx(AvatarImage, { src: userInfo.profile_picture_url || undefined, alt: userInfo.name ? `Profile picture of ${userInfo.name}` : "Profile picture", className: "cls_roles_matrix_user_avatar_image" }), _jsx(AvatarFallback, { className: "cls_roles_matrix_user_avatar_fallback bg-slate-200 text-slate-600", children: getUserInitials(userInfo.name, userInfo.email_address) })] }), _jsxs("div", { className: "cls_roles_matrix_user_info_details flex flex-col", children: [_jsx("span", { className: "cls_roles_matrix_user_name font-semibold text-lg", children: userInfo.name || userInfo.email_address }), userInfo.name && (_jsx("span", { className: "cls_roles_matrix_user_email text-sm text-muted-foreground", children: userInfo.email_address }))] })] })), _jsx("div", { className: "cls_roles_matrix_header flex items-center justify-between", children: _jsx("div", { className: "cls_roles_matrix_header_left", children: add_button_enabled && (_jsxs(Button, { onClick: () => setIsAddDialogOpen(true), variant: "default", size: "sm", className: "cls_roles_matrix_add_button", children: [_jsx(Plus, { className: "h-4 w-4 mr-2" }), "Add Role"] })) }) }), _jsx("div", { className: "cls_roles_matrix_table_container border rounded-lg overflow-auto w-full", children: _jsxs(Table, { className: "cls_roles_matrix_table w-full", children: [_jsx(TableHeader, { className: "cls_roles_matrix_table_header", children: _jsxs(TableRow, { className: "cls_roles_matrix_table_header_row", children: [role_name_selection_enabled && (_jsx(TableHead, { className: "cls_roles_matrix_table_header_role_checkbox w-12" })), _jsx(TableHead, { className: "cls_roles_matrix_table_header_role_name w-48", children: "Role Name" }), _jsx(TableHead, { className: "cls_roles_matrix_table_header_permissions", children: "Permissions" }), !permissions_read_only && (_jsx(TableHead, { className: "cls_roles_matrix_table_header_actions w-24 text-center", children: "Actions" }))] }) }), _jsx(TableBody, { className: "cls_roles_matrix_table_body", children: roles.length === 0 ? (_jsx(TableRow, { className: "cls_roles_matrix_table_row_empty", children: _jsx(TableCell, { colSpan: role_name_selection_enabled ? 4 : 3, className: "text-center text-muted-foreground py-8", children: "No roles found. Add a role to get started." }) })) : (roles.map((role, role_index) => {
|
|
375
|
+
const permission_array = Array.from(role.permissions);
|
|
376
|
+
const is_expanded = expandedRoles.has(role_index);
|
|
377
|
+
const visible_permissions = is_expanded
|
|
378
|
+
? permission_array
|
|
379
|
+
: permission_array.slice(0, MAX_VISIBLE_TAGS);
|
|
380
|
+
const remaining_count = permission_array.length - MAX_VISIBLE_TAGS;
|
|
381
|
+
return (_jsxs(TableRow, { className: "cls_roles_matrix_table_row", children: [role_name_selection_enabled && (_jsx(TableCell, { className: "cls_roles_matrix_table_cell_role_checkbox text-center", children: _jsx("div", { className: "cls_roles_matrix_role_checkbox_wrapper flex items-center justify-center", children: _jsx(Checkbox, { checked: role.selected, onCheckedChange: () => handleRoleSelectionToggle(role_index), className: "cls_roles_matrix_role_checkbox" }) }) })), _jsx(TableCell, { className: "cls_roles_matrix_table_cell_role_name font-medium", children: role.role_name }), _jsx(TableCell, { className: "cls_roles_matrix_table_cell_permissions", children: _jsx("div", { className: "cls_roles_matrix_permission_tags flex flex-wrap items-center gap-1.5", children: permission_array.length === 0 ? (_jsx("span", { className: "text-muted-foreground text-sm italic", children: "No permissions assigned" })) : (_jsxs(_Fragment, { children: [visible_permissions.map((perm_name) => (_jsx("span", { className: "cls_roles_matrix_permission_tag bg-blue-100 text-blue-700 px-2 py-0.5 rounded text-xs font-medium", title: getPermissionDescription(perm_name) || perm_name, children: perm_name }, perm_name))), remaining_count > 0 && !is_expanded && (_jsxs("span", { className: "cls_roles_matrix_permission_tag_more bg-slate-100 text-slate-600 px-2 py-0.5 rounded text-xs font-medium cursor-pointer hover:bg-slate-200", onClick: () => {
|
|
382
|
+
if (permissions_read_only) {
|
|
383
|
+
handleToggleExpandedPermissions(role_index);
|
|
384
|
+
}
|
|
385
|
+
else {
|
|
386
|
+
handleOpenEditPermissions(role_index);
|
|
387
|
+
}
|
|
388
|
+
}, title: permissions_read_only ? `Click to expand all ${permission_array.length} permissions` : `Click to edit all ${permission_array.length} permissions`, children: ["+", remaining_count, " more"] })), is_expanded && remaining_count > 0 && (_jsx("span", { className: "cls_roles_matrix_permission_tag_collapse bg-slate-100 text-slate-600 px-2 py-0.5 rounded text-xs font-medium cursor-pointer hover:bg-slate-200", onClick: () => handleToggleExpandedPermissions(role_index), title: "Click to collapse", children: "Show less" }))] })) }) }), !permissions_read_only && (_jsx(TableCell, { className: "cls_roles_matrix_table_cell_actions text-center", children: _jsx(Button, { onClick: () => handleOpenEditPermissions(role_index), variant: "ghost", size: "sm", className: "cls_roles_matrix_edit_button h-8 w-8 p-0", title: "Edit permissions", children: _jsx(Pencil, { className: "h-4 w-4" }) }) }))] }, role_index));
|
|
389
|
+
})) })] }) }), show_save_cancel && (_jsxs("div", { className: "cls_roles_matrix_footer flex items-center justify-end gap-2", children: [_jsx(Button, { onClick: handleSave, disabled: saving, variant: "default", size: "sm", className: "cls_roles_matrix_save_button", children: saving ? (_jsxs(_Fragment, { children: [_jsx(Loader2, { className: "h-4 w-4 mr-2 animate-spin" }), "Saving..."] })) : (_jsxs(_Fragment, { children: [_jsx(CircleCheck, { className: "h-4 w-4 mr-2" }), "Save"] })) }), _jsxs(Button, { onClick: handleCancel, variant: "outline", size: "sm", className: "cls_roles_matrix_cancel_button", children: [_jsx(CircleX, { className: "h-4 w-4 mr-2" }), "Cancel"] })] })), _jsx(Dialog, { open: isAddDialogOpen, onOpenChange: setIsAddDialogOpen, children: _jsxs(DialogContent, { className: "cls_roles_matrix_add_dialog", children: [_jsxs(DialogHeader, { children: [_jsx(DialogTitle, { children: "Add New Role" }), _jsx(DialogDescription, { children: "Enter a name for the new role. You can assign permissions after creating the role." })] }), _jsx("div", { className: "cls_roles_matrix_add_dialog_content flex flex-col gap-4 py-4", children: _jsxs("div", { className: "cls_roles_matrix_add_dialog_field flex flex-col gap-2", children: [_jsx(Label, { htmlFor: "role_name", className: "cls_roles_matrix_add_dialog_label", children: "Role Name" }), _jsx(Input, { id: "role_name", value: newRoleName, onChange: (e) => setNewRoleName(e.target.value), placeholder: "Enter role name", className: "cls_roles_matrix_add_dialog_input", onKeyDown: (e) => {
|
|
282
390
|
if (e.key === "Enter") {
|
|
283
391
|
handleAddRole();
|
|
284
392
|
}
|
|
285
393
|
} })] }) }), _jsxs(DialogFooter, { className: "cls_roles_matrix_add_dialog_footer", children: [_jsx(Button, { onClick: handleAddRole, variant: "default", className: "cls_roles_matrix_add_dialog_save", children: "Add Role" }), _jsx(Button, { onClick: () => {
|
|
286
394
|
setIsAddDialogOpen(false);
|
|
287
395
|
setNewRoleName("");
|
|
288
|
-
}, variant: "outline", className: "cls_roles_matrix_add_dialog_cancel", children: "Cancel" })] })] }) })] }))
|
|
396
|
+
}, variant: "outline", className: "cls_roles_matrix_add_dialog_cancel", children: "Cancel" })] })] }) }), _jsx(Dialog, { open: editPermissionsDialogOpen, onOpenChange: setEditPermissionsDialogOpen, children: _jsxs(DialogContent, { className: "cls_roles_matrix_edit_permissions_dialog max-w-lg max-h-[80vh] flex flex-col", children: [_jsxs(DialogHeader, { children: [_jsxs(DialogTitle, { children: ["Edit Permissions for: ", editingRoleIndex !== null ? (_a = roles[editingRoleIndex]) === null || _a === void 0 ? void 0 : _a.role_name : ""] }), _jsx(DialogDescription, { children: "Select which permissions to assign to this role." })] }), _jsxs("div", { className: "cls_roles_matrix_edit_permissions_actions flex items-center gap-2 py-2 border-b", children: [_jsx(Button, { onClick: handleSelectAllPermissions, variant: "outline", size: "sm", className: "cls_roles_matrix_select_all_button", children: "Select All" }), _jsx(Button, { onClick: handleUnselectAllPermissions, variant: "outline", size: "sm", className: "cls_roles_matrix_unselect_all_button", children: "Unselect All" })] }), _jsx("div", { className: "cls_roles_matrix_edit_permissions_list flex-1 overflow-y-auto py-2 min-h-0", children: _jsx("div", { className: "flex flex-col gap-3", children: permissionsWithDescriptions.map((perm) => {
|
|
397
|
+
var _a;
|
|
398
|
+
const is_checked = editingRoleIndex !== null && ((_a = roles[editingRoleIndex]) === null || _a === void 0 ? void 0 : _a.permissions.has(perm.permission_name));
|
|
399
|
+
return (_jsxs("div", { className: "cls_roles_matrix_permission_item flex items-start gap-3 p-2 rounded hover:bg-muted/50 cursor-pointer", onClick: () => editingRoleIndex !== null && handlePermissionToggle(editingRoleIndex, perm.permission_name), children: [_jsx(Checkbox, { checked: is_checked, onCheckedChange: () => editingRoleIndex !== null && handlePermissionToggle(editingRoleIndex, perm.permission_name), className: "cls_roles_matrix_permission_checkbox mt-0.5" }), _jsxs("div", { className: "cls_roles_matrix_permission_info flex flex-col gap-0.5", children: [_jsx("span", { className: "cls_roles_matrix_permission_name font-medium text-sm", children: perm.permission_name }), perm.description && (_jsx("span", { className: "cls_roles_matrix_permission_description text-xs text-muted-foreground italic", children: perm.description }))] })] }, perm.permission_name));
|
|
400
|
+
}) }) }), _jsxs(DialogFooter, { className: "cls_roles_matrix_edit_permissions_footer border-t pt-4", children: [_jsxs(Button, { onClick: () => setEditPermissionsDialogOpen(false), variant: "default", className: "cls_roles_matrix_edit_permissions_done", children: [_jsx(CircleCheck, { className: "h-4 w-4 mr-2" }), "Done"] }), _jsxs(Button, { onClick: () => {
|
|
401
|
+
// Reset this role's permissions to original state
|
|
402
|
+
if (editingRoleIndex !== null) {
|
|
403
|
+
const original_role = originalRoles.find(r => r.role_name === roles[editingRoleIndex].role_name);
|
|
404
|
+
if (original_role) {
|
|
405
|
+
setRoles((prev) => {
|
|
406
|
+
const updated = [...prev];
|
|
407
|
+
updated[editingRoleIndex] = Object.assign(Object.assign({}, updated[editingRoleIndex]), { permissions: new Set(original_role.permissions) });
|
|
408
|
+
return updated;
|
|
409
|
+
});
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
setEditPermissionsDialogOpen(false);
|
|
413
|
+
}, variant: "outline", className: "cls_roles_matrix_edit_permissions_cancel", children: [_jsx(CircleX, { className: "h-4 w-4 mr-2" }), "Cancel"] })] })] }) })] }));
|
|
289
414
|
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export type ScopeHierarchyTabProps = {
|
|
2
|
+
className?: string;
|
|
3
|
+
defaultOrg?: string;
|
|
4
|
+
};
|
|
5
|
+
/**
|
|
6
|
+
* Scope Hierarchy tab component for managing HRBAC scopes
|
|
7
|
+
* Displays scopes in a tree view for intuitive hierarchy configuration
|
|
8
|
+
* @param props - Component props
|
|
9
|
+
* @returns Scope Hierarchy tab component
|
|
10
|
+
*/
|
|
11
|
+
export declare function ScopeHierarchyTab({ className, defaultOrg, }: ScopeHierarchyTabProps): import("react/jsx-runtime").JSX.Element;
|
|
12
|
+
//# sourceMappingURL=scope_hierarchy_tab.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scope_hierarchy_tab.d.ts","sourceRoot":"","sources":["../../../../../src/components/layouts/user_management/components/scope_hierarchy_tab.tsx"],"names":[],"mappings":"AA2CA,MAAM,MAAM,sBAAsB,GAAG;IACnC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB,CAAC;AA8HF;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,EAChC,SAAS,EACT,UAAe,GAChB,EAAE,sBAAsB,2CA2fxB"}
|
|
@@ -0,0 +1,291 @@
|
|
|
1
|
+
// file_description: Scope Hierarchy tab component for managing HRBAC scopes (L1-L7) using tree view
|
|
2
|
+
// section: client_directive
|
|
3
|
+
"use client";
|
|
4
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
5
|
+
// section: imports
|
|
6
|
+
import { useState, useEffect, useCallback, useMemo } from "react";
|
|
7
|
+
import { TreeView } from "../../../ui/tree-view";
|
|
8
|
+
import { Button } from "../../../ui/button";
|
|
9
|
+
import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, } from "../../../ui/dialog";
|
|
10
|
+
import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, } from "../../../ui/alert-dialog";
|
|
11
|
+
import { Input } from "../../../ui/input";
|
|
12
|
+
import { Label } from "../../../ui/label";
|
|
13
|
+
import { Loader2, Plus, Edit, Trash2, CircleCheck, CircleX, Building2, FolderTree, RefreshCw, } from "lucide-react";
|
|
14
|
+
import { toast } from "sonner";
|
|
15
|
+
import { useHazoAuthConfig } from "../../../../contexts/hazo_auth_provider";
|
|
16
|
+
const SCOPE_LEVEL_LABELS = {
|
|
17
|
+
hazo_scopes_l1: "Level 1",
|
|
18
|
+
hazo_scopes_l2: "Level 2",
|
|
19
|
+
hazo_scopes_l3: "Level 3",
|
|
20
|
+
hazo_scopes_l4: "Level 4",
|
|
21
|
+
hazo_scopes_l5: "Level 5",
|
|
22
|
+
hazo_scopes_l6: "Level 6",
|
|
23
|
+
hazo_scopes_l7: "Level 7",
|
|
24
|
+
};
|
|
25
|
+
// section: helpers
|
|
26
|
+
function getLevelNumber(level) {
|
|
27
|
+
return parseInt(level.replace("hazo_scopes_l", ""));
|
|
28
|
+
}
|
|
29
|
+
function getChildLevel(level) {
|
|
30
|
+
const num = getLevelNumber(level);
|
|
31
|
+
if (num >= 7)
|
|
32
|
+
return null;
|
|
33
|
+
return `hazo_scopes_l${num + 1}`;
|
|
34
|
+
}
|
|
35
|
+
// Convert ScopeTreeNode to TreeDataItem format
|
|
36
|
+
function convertToTreeData(nodes, onEdit, onDelete, onAddChild) {
|
|
37
|
+
return nodes.map((node) => {
|
|
38
|
+
const levelNum = getLevelNumber(node.level);
|
|
39
|
+
const hasChildren = node.children && node.children.length > 0;
|
|
40
|
+
const canHaveChildren = levelNum < 7;
|
|
41
|
+
const item = {
|
|
42
|
+
id: node.id,
|
|
43
|
+
name: `${node.name} (${node.seq})`,
|
|
44
|
+
icon: Building2,
|
|
45
|
+
scopeData: node,
|
|
46
|
+
actions: (_jsxs("div", { className: "flex items-center gap-1", children: [canHaveChildren && (_jsx(Button, { variant: "ghost", size: "sm", className: "h-6 w-6 p-0", onClick: (e) => {
|
|
47
|
+
e.stopPropagation();
|
|
48
|
+
onAddChild(node);
|
|
49
|
+
}, title: "Add child scope", children: _jsx(Plus, { className: "h-3 w-3" }) })), _jsx(Button, { variant: "ghost", size: "sm", className: "h-6 w-6 p-0", onClick: (e) => {
|
|
50
|
+
e.stopPropagation();
|
|
51
|
+
onEdit(node);
|
|
52
|
+
}, title: "Edit scope", children: _jsx(Edit, { className: "h-3 w-3" }) }), _jsx(Button, { variant: "ghost", size: "sm", className: "h-6 w-6 p-0 text-destructive hover:text-destructive", onClick: (e) => {
|
|
53
|
+
e.stopPropagation();
|
|
54
|
+
onDelete(node);
|
|
55
|
+
}, title: "Delete scope", children: _jsx(Trash2, { className: "h-3 w-3" }) })] })),
|
|
56
|
+
};
|
|
57
|
+
if (hasChildren) {
|
|
58
|
+
item.children = convertToTreeData(node.children, onEdit, onDelete, onAddChild);
|
|
59
|
+
}
|
|
60
|
+
return item;
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
// section: component
|
|
64
|
+
/**
|
|
65
|
+
* Scope Hierarchy tab component for managing HRBAC scopes
|
|
66
|
+
* Displays scopes in a tree view for intuitive hierarchy configuration
|
|
67
|
+
* @param props - Component props
|
|
68
|
+
* @returns Scope Hierarchy tab component
|
|
69
|
+
*/
|
|
70
|
+
export function ScopeHierarchyTab({ className, defaultOrg = "", }) {
|
|
71
|
+
const { apiBasePath } = useHazoAuthConfig();
|
|
72
|
+
// State
|
|
73
|
+
const [tree, setTree] = useState([]);
|
|
74
|
+
const [loading, setLoading] = useState(true);
|
|
75
|
+
const [actionLoading, setActionLoading] = useState(false);
|
|
76
|
+
const [org, setOrg] = useState(defaultOrg);
|
|
77
|
+
const [selectedItem, setSelectedItem] = useState();
|
|
78
|
+
// Dialog state
|
|
79
|
+
const [addDialogOpen, setAddDialogOpen] = useState(false);
|
|
80
|
+
const [editDialogOpen, setEditDialogOpen] = useState(false);
|
|
81
|
+
const [deleteDialogOpen, setDeleteDialogOpen] = useState(false);
|
|
82
|
+
const [selectedScope, setSelectedScope] = useState(null);
|
|
83
|
+
const [addParentScope, setAddParentScope] = useState(null);
|
|
84
|
+
// Form state
|
|
85
|
+
const [newName, setNewName] = useState("");
|
|
86
|
+
const [newOrg, setNewOrg] = useState(defaultOrg);
|
|
87
|
+
const [editName, setEditName] = useState("");
|
|
88
|
+
// Load tree data
|
|
89
|
+
const loadTree = useCallback(async () => {
|
|
90
|
+
if (!org) {
|
|
91
|
+
setTree([]);
|
|
92
|
+
setLoading(false);
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
setLoading(true);
|
|
96
|
+
try {
|
|
97
|
+
const params = new URLSearchParams({ action: "tree", org });
|
|
98
|
+
const response = await fetch(`${apiBasePath}/scope_management/scopes?${params}`);
|
|
99
|
+
const data = await response.json();
|
|
100
|
+
if (data.success) {
|
|
101
|
+
setTree(data.tree || []);
|
|
102
|
+
}
|
|
103
|
+
else {
|
|
104
|
+
toast.error(data.error || "Failed to load scope hierarchy");
|
|
105
|
+
setTree([]);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
catch (error) {
|
|
109
|
+
toast.error("Failed to load scope hierarchy");
|
|
110
|
+
setTree([]);
|
|
111
|
+
}
|
|
112
|
+
finally {
|
|
113
|
+
setLoading(false);
|
|
114
|
+
}
|
|
115
|
+
}, [apiBasePath, org]);
|
|
116
|
+
// Load data when org changes
|
|
117
|
+
useEffect(() => {
|
|
118
|
+
void loadTree();
|
|
119
|
+
}, [loadTree]);
|
|
120
|
+
// Handle add scope (root level)
|
|
121
|
+
const handleAddRootScope = () => {
|
|
122
|
+
setAddParentScope(null);
|
|
123
|
+
setNewOrg(org || defaultOrg);
|
|
124
|
+
setNewName("");
|
|
125
|
+
setAddDialogOpen(true);
|
|
126
|
+
};
|
|
127
|
+
// Handle add child scope
|
|
128
|
+
const handleAddChildScope = (parent) => {
|
|
129
|
+
setAddParentScope(parent);
|
|
130
|
+
setNewOrg(parent.org);
|
|
131
|
+
setNewName("");
|
|
132
|
+
setAddDialogOpen(true);
|
|
133
|
+
};
|
|
134
|
+
// Handle edit scope
|
|
135
|
+
const openEditDialog = (scope) => {
|
|
136
|
+
setSelectedScope(scope);
|
|
137
|
+
setEditName(scope.name);
|
|
138
|
+
setEditDialogOpen(true);
|
|
139
|
+
};
|
|
140
|
+
// Handle delete scope
|
|
141
|
+
const openDeleteDialog = (scope) => {
|
|
142
|
+
setSelectedScope(scope);
|
|
143
|
+
setDeleteDialogOpen(true);
|
|
144
|
+
};
|
|
145
|
+
// Create scope
|
|
146
|
+
const handleCreateScope = async () => {
|
|
147
|
+
if (!newName.trim()) {
|
|
148
|
+
toast.error("Name is required");
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
if (!newOrg.trim()) {
|
|
152
|
+
toast.error("Organization is required");
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
setActionLoading(true);
|
|
156
|
+
try {
|
|
157
|
+
const level = addParentScope
|
|
158
|
+
? getChildLevel(addParentScope.level)
|
|
159
|
+
: "hazo_scopes_l1";
|
|
160
|
+
if (!level) {
|
|
161
|
+
toast.error("Cannot add children to Level 7 scopes");
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
const body = {
|
|
165
|
+
level,
|
|
166
|
+
org: newOrg.trim(),
|
|
167
|
+
name: newName.trim(),
|
|
168
|
+
};
|
|
169
|
+
if (addParentScope) {
|
|
170
|
+
body.parent_scope_id = addParentScope.id;
|
|
171
|
+
}
|
|
172
|
+
const response = await fetch(`${apiBasePath}/scope_management/scopes`, {
|
|
173
|
+
method: "POST",
|
|
174
|
+
headers: { "Content-Type": "application/json" },
|
|
175
|
+
body: JSON.stringify(body),
|
|
176
|
+
});
|
|
177
|
+
const data = await response.json();
|
|
178
|
+
if (data.success) {
|
|
179
|
+
toast.success("Scope created successfully");
|
|
180
|
+
setAddDialogOpen(false);
|
|
181
|
+
setNewName("");
|
|
182
|
+
setAddParentScope(null);
|
|
183
|
+
await loadTree();
|
|
184
|
+
}
|
|
185
|
+
else {
|
|
186
|
+
toast.error(data.error || "Failed to create scope");
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
catch (error) {
|
|
190
|
+
toast.error("Failed to create scope");
|
|
191
|
+
}
|
|
192
|
+
finally {
|
|
193
|
+
setActionLoading(false);
|
|
194
|
+
}
|
|
195
|
+
};
|
|
196
|
+
// Update scope
|
|
197
|
+
const handleUpdateScope = async () => {
|
|
198
|
+
if (!selectedScope)
|
|
199
|
+
return;
|
|
200
|
+
if (!editName.trim()) {
|
|
201
|
+
toast.error("Name is required");
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
204
|
+
setActionLoading(true);
|
|
205
|
+
try {
|
|
206
|
+
const body = {
|
|
207
|
+
level: selectedScope.level,
|
|
208
|
+
scope_id: selectedScope.id,
|
|
209
|
+
name: editName.trim(),
|
|
210
|
+
};
|
|
211
|
+
const response = await fetch(`${apiBasePath}/scope_management/scopes`, {
|
|
212
|
+
method: "PATCH",
|
|
213
|
+
headers: { "Content-Type": "application/json" },
|
|
214
|
+
body: JSON.stringify(body),
|
|
215
|
+
});
|
|
216
|
+
const data = await response.json();
|
|
217
|
+
if (data.success) {
|
|
218
|
+
toast.success("Scope updated successfully");
|
|
219
|
+
setEditDialogOpen(false);
|
|
220
|
+
setSelectedScope(null);
|
|
221
|
+
setEditName("");
|
|
222
|
+
await loadTree();
|
|
223
|
+
}
|
|
224
|
+
else {
|
|
225
|
+
toast.error(data.error || "Failed to update scope");
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
catch (error) {
|
|
229
|
+
toast.error("Failed to update scope");
|
|
230
|
+
}
|
|
231
|
+
finally {
|
|
232
|
+
setActionLoading(false);
|
|
233
|
+
}
|
|
234
|
+
};
|
|
235
|
+
// Delete scope
|
|
236
|
+
const handleDeleteScope = async () => {
|
|
237
|
+
if (!selectedScope)
|
|
238
|
+
return;
|
|
239
|
+
setActionLoading(true);
|
|
240
|
+
try {
|
|
241
|
+
const params = new URLSearchParams({
|
|
242
|
+
level: selectedScope.level,
|
|
243
|
+
scope_id: selectedScope.id,
|
|
244
|
+
});
|
|
245
|
+
const response = await fetch(`${apiBasePath}/scope_management/scopes?${params}`, {
|
|
246
|
+
method: "DELETE",
|
|
247
|
+
});
|
|
248
|
+
const data = await response.json();
|
|
249
|
+
if (data.success) {
|
|
250
|
+
toast.success("Scope deleted successfully");
|
|
251
|
+
setDeleteDialogOpen(false);
|
|
252
|
+
setSelectedScope(null);
|
|
253
|
+
await loadTree();
|
|
254
|
+
}
|
|
255
|
+
else {
|
|
256
|
+
toast.error(data.error || "Failed to delete scope");
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
catch (error) {
|
|
260
|
+
toast.error("Failed to delete scope");
|
|
261
|
+
}
|
|
262
|
+
finally {
|
|
263
|
+
setActionLoading(false);
|
|
264
|
+
}
|
|
265
|
+
};
|
|
266
|
+
// Convert tree to TreeDataItem format
|
|
267
|
+
const treeData = useMemo(() => {
|
|
268
|
+
return convertToTreeData(tree, openEditDialog, openDeleteDialog, handleAddChildScope);
|
|
269
|
+
}, [tree]);
|
|
270
|
+
// Handle tree item selection
|
|
271
|
+
const handleSelectChange = (item) => {
|
|
272
|
+
setSelectedItem(item);
|
|
273
|
+
};
|
|
274
|
+
// Get level label for dialog
|
|
275
|
+
const getAddDialogLevelLabel = () => {
|
|
276
|
+
if (!addParentScope)
|
|
277
|
+
return "Level 1";
|
|
278
|
+
const childLevel = getChildLevel(addParentScope.level);
|
|
279
|
+
return childLevel ? SCOPE_LEVEL_LABELS[childLevel] : "Unknown";
|
|
280
|
+
};
|
|
281
|
+
return (_jsxs("div", { className: `cls_scope_hierarchy_tab flex flex-col gap-4 w-full ${className || ""}`, children: [_jsxs("div", { className: "cls_scope_hierarchy_header flex items-center justify-between gap-4 flex-wrap", children: [_jsxs("div", { className: "cls_scope_hierarchy_header_left flex items-center gap-4", children: [_jsxs("div", { className: "cls_scope_hierarchy_org_filter flex items-center gap-2", children: [_jsx(Label, { htmlFor: "scope_org", className: "text-sm font-medium", children: "Organization:" }), _jsx(Input, { id: "scope_org", value: org, onChange: (e) => setOrg(e.target.value), placeholder: "Enter organization", className: "w-[200px]" })] }), _jsxs(Button, { variant: "outline", size: "sm", onClick: () => void loadTree(), disabled: loading || !org, children: [_jsx(RefreshCw, { className: `h-4 w-4 mr-2 ${loading ? "animate-spin" : ""}` }), "Refresh"] })] }), _jsx("div", { className: "cls_scope_hierarchy_header_right", children: _jsxs(Button, { onClick: handleAddRootScope, variant: "default", size: "sm", disabled: !org, children: [_jsx(Plus, { className: "h-4 w-4 mr-2" }), "Add Root Scope"] }) })] }), loading ? (_jsx("div", { className: "cls_scope_hierarchy_loading flex items-center justify-center p-8", children: _jsx(Loader2, { className: "h-6 w-6 animate-spin text-slate-400" }) })) : !org ? (_jsxs("div", { className: "cls_scope_hierarchy_empty flex flex-col items-center justify-center p-8 border rounded-lg border-dashed", children: [_jsx(Building2, { className: "h-12 w-12 text-muted-foreground mb-4" }), _jsx("p", { className: "text-muted-foreground text-center", children: "Enter an organization name to view scope hierarchy" })] })) : tree.length === 0 ? (_jsxs("div", { className: "cls_scope_hierarchy_empty flex flex-col items-center justify-center p-8 border rounded-lg border-dashed", children: [_jsx(FolderTree, { className: "h-12 w-12 text-muted-foreground mb-4" }), _jsxs("p", { className: "text-muted-foreground text-center mb-4", children: ["No scopes found for organization \"", org, "\""] }), _jsxs(Button, { onClick: handleAddRootScope, variant: "outline", size: "sm", children: [_jsx(Plus, { className: "h-4 w-4 mr-2" }), "Create First Scope"] })] })) : (_jsx("div", { className: "cls_scope_hierarchy_tree_container border rounded-lg overflow-auto w-full min-h-[300px]", children: _jsx(TreeView, { data: treeData, expandAll: true, defaultNodeIcon: Building2, defaultLeafIcon: Building2, onSelectChange: handleSelectChange, className: "w-full" }) })), (selectedItem === null || selectedItem === void 0 ? void 0 : selectedItem.scopeData) && (_jsxs("div", { className: "cls_scope_hierarchy_selected_info p-4 border rounded-lg bg-muted/50", children: [_jsx("h4", { className: "font-medium mb-2", children: "Selected Scope" }), _jsxs("div", { className: "grid grid-cols-2 gap-2 text-sm", children: [_jsxs("div", { children: [_jsx("span", { className: "text-muted-foreground", children: "Name:" }), " ", selectedItem.scopeData.name] }), _jsxs("div", { children: [_jsx("span", { className: "text-muted-foreground", children: "Seq:" }), " ", selectedItem.scopeData.seq] }), _jsxs("div", { children: [_jsx("span", { className: "text-muted-foreground", children: "Level:" }), " ", SCOPE_LEVEL_LABELS[selectedItem.scopeData.level]] }), _jsxs("div", { children: [_jsx("span", { className: "text-muted-foreground", children: "Org:" }), " ", selectedItem.scopeData.org] })] })] })), _jsx(Dialog, { open: addDialogOpen, onOpenChange: setAddDialogOpen, children: _jsxs(DialogContent, { className: "cls_scope_hierarchy_add_dialog", children: [_jsxs(DialogHeader, { children: [_jsx(DialogTitle, { children: addParentScope
|
|
282
|
+
? `Add Child Scope to "${addParentScope.name}"`
|
|
283
|
+
: "Add Root Scope" }), _jsxs(DialogDescription, { children: ["Create a new scope at ", getAddDialogLevelLabel(), ".", addParentScope &&
|
|
284
|
+
` This will be a child of "${addParentScope.name}".`] })] }), _jsxs("div", { className: "flex flex-col gap-4 py-4", children: [_jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { htmlFor: "new_scope_name", children: "Name *" }), _jsx(Input, { id: "new_scope_name", value: newName, onChange: (e) => setNewName(e.target.value), placeholder: "Enter scope name" })] }), !addParentScope && (_jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { htmlFor: "new_scope_org", children: "Organization *" }), _jsx(Input, { id: "new_scope_org", value: newOrg, onChange: (e) => setNewOrg(e.target.value), placeholder: "Enter organization" })] }))] }), _jsxs(DialogFooter, { children: [_jsx(Button, { onClick: handleCreateScope, disabled: actionLoading, variant: "default", children: actionLoading ? (_jsxs(_Fragment, { children: [_jsx(Loader2, { className: "h-4 w-4 mr-2 animate-spin" }), "Creating..."] })) : (_jsxs(_Fragment, { children: [_jsx(CircleCheck, { className: "h-4 w-4 mr-2" }), "Create"] })) }), _jsxs(Button, { onClick: () => setAddDialogOpen(false), variant: "outline", children: [_jsx(CircleX, { className: "h-4 w-4 mr-2" }), "Cancel"] })] })] }) }), _jsx(Dialog, { open: editDialogOpen, onOpenChange: setEditDialogOpen, children: _jsxs(DialogContent, { className: "cls_scope_hierarchy_edit_dialog", children: [_jsxs(DialogHeader, { children: [_jsx(DialogTitle, { children: "Edit Scope" }), _jsxs(DialogDescription, { children: ["Update scope: ", selectedScope === null || selectedScope === void 0 ? void 0 : selectedScope.name, " (", selectedScope === null || selectedScope === void 0 ? void 0 : selectedScope.seq, ")"] })] }), _jsx("div", { className: "flex flex-col gap-4 py-4", children: _jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { htmlFor: "edit_scope_name", children: "Name *" }), _jsx(Input, { id: "edit_scope_name", value: editName, onChange: (e) => setEditName(e.target.value), placeholder: "Enter scope name" })] }) }), _jsxs(DialogFooter, { children: [_jsx(Button, { onClick: handleUpdateScope, disabled: actionLoading, variant: "default", children: actionLoading ? (_jsxs(_Fragment, { children: [_jsx(Loader2, { className: "h-4 w-4 mr-2 animate-spin" }), "Saving..."] })) : (_jsxs(_Fragment, { children: [_jsx(CircleCheck, { className: "h-4 w-4 mr-2" }), "Save"] })) }), _jsxs(Button, { onClick: () => {
|
|
285
|
+
setEditDialogOpen(false);
|
|
286
|
+
setSelectedScope(null);
|
|
287
|
+
}, variant: "outline", children: [_jsx(CircleX, { className: "h-4 w-4 mr-2" }), "Cancel"] })] })] }) }), _jsx(AlertDialog, { open: deleteDialogOpen, onOpenChange: setDeleteDialogOpen, children: _jsxs(AlertDialogContent, { children: [_jsxs(AlertDialogHeader, { children: [_jsx(AlertDialogTitle, { children: "Delete Scope" }), _jsxs(AlertDialogDescription, { children: ["Are you sure you want to delete \"", selectedScope === null || selectedScope === void 0 ? void 0 : selectedScope.name, "\" (", selectedScope === null || selectedScope === void 0 ? void 0 : selectedScope.seq, ")? This action cannot be undone and will also delete all child scopes."] })] }), _jsxs(AlertDialogFooter, { children: [_jsx(AlertDialogAction, { onClick: handleDeleteScope, disabled: actionLoading, children: actionLoading ? (_jsxs(_Fragment, { children: [_jsx(Loader2, { className: "h-4 w-4 mr-2 animate-spin" }), "Deleting..."] })) : ("Delete") }), _jsx(AlertDialogCancel, { onClick: () => {
|
|
288
|
+
setDeleteDialogOpen(false);
|
|
289
|
+
setSelectedScope(null);
|
|
290
|
+
}, children: "Cancel" })] })] }) })] }));
|
|
291
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export type ScopeLabelsTabProps = {
|
|
2
|
+
className?: string;
|
|
3
|
+
defaultOrg?: string;
|
|
4
|
+
};
|
|
5
|
+
/**
|
|
6
|
+
* Scope Labels tab component for configuring friendly names for scope levels
|
|
7
|
+
* Shows all 7 scope levels with their current labels from database
|
|
8
|
+
* Empty inputs for levels without labels - no placeholders
|
|
9
|
+
* @param props - Component props
|
|
10
|
+
* @returns Scope Labels tab component
|
|
11
|
+
*/
|
|
12
|
+
export declare function ScopeLabelsTab({ className, defaultOrg }: ScopeLabelsTabProps): import("react/jsx-runtime").JSX.Element;
|
|
13
|
+
//# sourceMappingURL=scope_labels_tab.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scope_labels_tab.d.ts","sourceRoot":"","sources":["../../../../../src/components/layouts/user_management/components/scope_labels_tab.tsx"],"names":[],"mappings":"AAsBA,MAAM,MAAM,mBAAmB,GAAG;IAChC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB,CAAC;AAeF;;;;;;GAMG;AACH,wBAAgB,cAAc,CAAC,EAAE,SAAS,EAAE,UAAe,EAAE,EAAE,mBAAmB,2CA4NjF"}
|