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.
Files changed (61) hide show
  1. package/README.md +146 -0
  2. package/SETUP_CHECKLIST.md +369 -0
  3. package/dist/components/layouts/my_settings/components/profile_picture_library_tab.d.ts.map +1 -1
  4. package/dist/components/layouts/my_settings/components/profile_picture_library_tab.js +2 -2
  5. package/dist/components/layouts/rbac_test/index.d.ts +15 -0
  6. package/dist/components/layouts/rbac_test/index.d.ts.map +1 -0
  7. package/dist/components/layouts/rbac_test/index.js +378 -0
  8. package/dist/components/layouts/shared/components/password_field.js +1 -1
  9. package/dist/components/layouts/shared/components/sidebar_layout_wrapper.d.ts.map +1 -1
  10. package/dist/components/layouts/shared/components/sidebar_layout_wrapper.js +2 -2
  11. package/dist/components/layouts/shared/components/two_column_auth_layout.js +1 -1
  12. package/dist/components/layouts/user_management/components/roles_matrix.d.ts +2 -3
  13. package/dist/components/layouts/user_management/components/roles_matrix.d.ts.map +1 -1
  14. package/dist/components/layouts/user_management/components/roles_matrix.js +133 -8
  15. package/dist/components/layouts/user_management/components/scope_hierarchy_tab.d.ts +12 -0
  16. package/dist/components/layouts/user_management/components/scope_hierarchy_tab.d.ts.map +1 -0
  17. package/dist/components/layouts/user_management/components/scope_hierarchy_tab.js +291 -0
  18. package/dist/components/layouts/user_management/components/scope_labels_tab.d.ts +13 -0
  19. package/dist/components/layouts/user_management/components/scope_labels_tab.d.ts.map +1 -0
  20. package/dist/components/layouts/user_management/components/scope_labels_tab.js +158 -0
  21. package/dist/components/layouts/user_management/components/user_scopes_tab.d.ts +11 -0
  22. package/dist/components/layouts/user_management/components/user_scopes_tab.d.ts.map +1 -0
  23. package/dist/components/layouts/user_management/components/user_scopes_tab.js +267 -0
  24. package/dist/components/layouts/user_management/index.d.ts +9 -2
  25. package/dist/components/layouts/user_management/index.d.ts.map +1 -1
  26. package/dist/components/layouts/user_management/index.js +22 -6
  27. package/dist/components/ui/select.d.ts +14 -0
  28. package/dist/components/ui/select.d.ts.map +1 -0
  29. package/dist/components/ui/select.js +59 -0
  30. package/dist/components/ui/tree-view.d.ts +108 -0
  31. package/dist/components/ui/tree-view.d.ts.map +1 -0
  32. package/dist/components/ui/tree-view.js +194 -0
  33. package/dist/lib/auth/auth_types.d.ts +45 -0
  34. package/dist/lib/auth/auth_types.d.ts.map +1 -1
  35. package/dist/lib/auth/auth_types.js +13 -0
  36. package/dist/lib/auth/hazo_get_auth.server.d.ts +4 -2
  37. package/dist/lib/auth/hazo_get_auth.server.d.ts.map +1 -1
  38. package/dist/lib/auth/hazo_get_auth.server.js +107 -3
  39. package/dist/lib/auth/scope_cache.d.ts +92 -0
  40. package/dist/lib/auth/scope_cache.d.ts.map +1 -0
  41. package/dist/lib/auth/scope_cache.js +171 -0
  42. package/dist/lib/scope_hierarchy_config.server.d.ts +39 -0
  43. package/dist/lib/scope_hierarchy_config.server.d.ts.map +1 -0
  44. package/dist/lib/scope_hierarchy_config.server.js +96 -0
  45. package/dist/lib/services/email_service.d.ts.map +1 -1
  46. package/dist/lib/services/email_service.js +7 -2
  47. package/dist/lib/services/profile_picture_service.d.ts +1 -7
  48. package/dist/lib/services/profile_picture_service.d.ts.map +1 -1
  49. package/dist/lib/services/profile_picture_service.js +77 -32
  50. package/dist/lib/services/registration_service.js +1 -1
  51. package/dist/lib/services/scope_labels_service.d.ts +48 -0
  52. package/dist/lib/services/scope_labels_service.d.ts.map +1 -0
  53. package/dist/lib/services/scope_labels_service.js +277 -0
  54. package/dist/lib/services/scope_service.d.ts +114 -0
  55. package/dist/lib/services/scope_service.d.ts.map +1 -0
  56. package/dist/lib/services/scope_service.js +582 -0
  57. package/dist/lib/services/user_scope_service.d.ts +74 -0
  58. package/dist/lib/services/user_scope_service.d.ts.map +1 -0
  59. package/dist/lib/services/user_scope_service.js +415 -0
  60. package/hazo_auth_config.example.ini +1 -1
  61. package/package.json +3 -1
@@ -1,4 +1,4 @@
1
- // file_description: internal reusable component for roles-permissions matrix with data table
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 matrix
20
- * Shows data table with permissions as columns and roles as rows
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
- 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", children: "Role Name" }), permissions.map((permission_name) => (_jsx(TableHead, { className: "cls_roles_matrix_table_header_permission text-center", children: permission_name }, permission_name)))] }) }), _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: permissions.length + (role_name_selection_enabled ? 2 : 1), className: "text-center text-muted-foreground py-8", children: "No roles found. Add a role to get started." }) })) : (roles.map((role, role_index) => (_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 }), permissions.map((permission_name) => (_jsx(TableCell, { className: "cls_roles_matrix_table_cell_permission text-center", children: _jsx("div", { className: "cls_roles_matrix_permission_checkbox_wrapper flex items-center justify-center", children: _jsx(Checkbox, { checked: role.permissions.has(permission_name), onCheckedChange: () => handlePermissionToggle(role_index, permission_name), disabled: permissions_read_only, className: "cls_roles_matrix_permission_checkbox" }) }) }, permission_name)))] }, role_index)))) })] }) }), 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) => {
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"}