@zauru-sdk/components 6.8.3 → 6.10.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/CHANGELOG.md +22 -0
- package/dist/NavBar/NavBar.d.ts +1 -1
- package/dist/NavBar/NavBar.types.d.ts +1 -0
- package/dist/Tab/PermissionMatrix.d.ts +21 -0
- package/dist/Tab/RouteTabNav.d.ts +13 -0
- package/dist/Tab/WebappSettingsChrome.d.ts +12 -0
- package/dist/Tab/index.d.ts +3 -0
- package/dist/esm/NavBar/NavBar.js +6 -5
- package/dist/esm/Tab/PermissionMatrix.js +30 -0
- package/dist/esm/Tab/RouteTabNav.js +21 -0
- package/dist/esm/Tab/WebappSettingsChrome.js +24 -0
- package/dist/esm/Tab/index.js +3 -0
- package/package.json +7 -7
- package/src/NavBar/NavBar.tsx +43 -3
- package/src/NavBar/NavBar.types.ts +1 -0
- package/src/Tab/PermissionMatrix.tsx +223 -0
- package/src/Tab/RouteTabNav.tsx +62 -0
- package/src/Tab/WebappSettingsChrome.tsx +54 -0
- package/src/Tab/index.ts +3 -0
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,28 @@
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
|
5
5
|
|
|
6
|
+
# [6.10.0](https://github.com/intuitiva/zauru-typescript-sdk/compare/@zauru-sdk/components@6.9.0...@zauru-sdk/components@6.10.0) (2026-10-05)
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
### Features
|
|
10
|
+
|
|
11
|
+
* menú Configuración genérico y extras de asignación fuera del SDK ([2691957](https://github.com/intuitiva/zauru-typescript-sdk/commit/2691957c9dba23226a4b8d371fee26b221e126f0))
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
# [6.9.0](https://github.com/intuitiva/zauru-typescript-sdk/compare/@zauru-sdk/components@6.8.3...@zauru-sdk/components@6.9.0) (2026-10-05)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
### Features
|
|
21
|
+
|
|
22
|
+
* motor RBAC genérico para webapps Remix (roles, asignaciones y matriz) ([b48c635](https://github.com/intuitiva/zauru-typescript-sdk/commit/b48c635101814ebf0b362636839b626d23a13b57))
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
|
|
6
28
|
## [6.8.3](https://github.com/intuitiva/zauru-typescript-sdk/compare/@zauru-sdk/components@6.8.2...@zauru-sdk/components@6.8.3) (2026-10-05)
|
|
7
29
|
|
|
8
30
|
**Note:** Version bump only for package @zauru-sdk/components
|
package/dist/NavBar/NavBar.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
import type { NavBarProps } from "./NavBar.types.js";
|
|
2
|
-
export declare const NavBar: ({ title, loggedIn, items, selectedColor, version, reloadCatalogsOption, showDarkModeToggle, }: NavBarProps) => import("react/jsx-runtime").JSX.Element;
|
|
2
|
+
export declare const NavBar: ({ title, loggedIn, items, selectedColor, version, reloadCatalogsOption, settingsOption, showDarkModeToggle, }: NavBarProps) => import("react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { PermissionGroup } from "@zauru-sdk/types";
|
|
2
|
+
export type PermissionMatrixRole = {
|
|
3
|
+
id: number;
|
|
4
|
+
name: string;
|
|
5
|
+
permissionKeys: string[];
|
|
6
|
+
};
|
|
7
|
+
type Props = {
|
|
8
|
+
groups: PermissionGroup[];
|
|
9
|
+
roles: PermissionMatrixRole[];
|
|
10
|
+
disabled?: boolean;
|
|
11
|
+
pendingCells?: ReadonlySet<string>;
|
|
12
|
+
onToggle: (roleId: number, key: string, granted: boolean) => void;
|
|
13
|
+
onBulkRole?: (roleId: number, mode: "all" | "none") => void;
|
|
14
|
+
onBulkSection?: (roleId: number, groupId: string, mode: "add" | "remove") => void;
|
|
15
|
+
emptyText?: string;
|
|
16
|
+
markAllLabel?: string;
|
|
17
|
+
unmarkAllLabel?: string;
|
|
18
|
+
};
|
|
19
|
+
export declare function permissionMatrixCellKey(roleId: number, key: string): string;
|
|
20
|
+
export declare const PermissionMatrix: ({ groups, roles, disabled, pendingCells, onToggle, onBulkRole, onBulkSection, emptyText, markAllLabel, unmarkAllLabel, }: Props) => import("react/jsx-runtime").JSX.Element;
|
|
21
|
+
export {};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export type RouteTabNavItem = {
|
|
2
|
+
to: string;
|
|
3
|
+
label: string;
|
|
4
|
+
hide?: boolean;
|
|
5
|
+
end?: boolean;
|
|
6
|
+
};
|
|
7
|
+
type Props = {
|
|
8
|
+
items: RouteTabNavItem[];
|
|
9
|
+
ariaLabel?: string;
|
|
10
|
+
selectedColor?: "indigo" | "slate" | "blue" | "green";
|
|
11
|
+
};
|
|
12
|
+
export declare const RouteTabNav: ({ items, ariaLabel, selectedColor, }: Props) => import("react/jsx-runtime").JSX.Element | null;
|
|
13
|
+
export {};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { ReactNode } from "react";
|
|
2
|
+
type Props = {
|
|
3
|
+
children: ReactNode;
|
|
4
|
+
showEmployees?: boolean;
|
|
5
|
+
showRoles?: boolean;
|
|
6
|
+
showPermissions?: boolean;
|
|
7
|
+
basePath?: string;
|
|
8
|
+
title?: string;
|
|
9
|
+
description?: string;
|
|
10
|
+
};
|
|
11
|
+
export declare const WebappSettingsChrome: ({ children, showEmployees, showRoles, showPermissions, basePath, title, description, }: Props) => import("react/jsx-runtime").JSX.Element;
|
|
12
|
+
export {};
|
package/dist/Tab/index.d.ts
CHANGED
|
@@ -4,9 +4,9 @@ import { DropDownArrowSvgIcon, LogoutDropDownSvgIcon, MenuAlt4Svg, OpcionButtonS
|
|
|
4
4
|
import { COLORS } from "./NavBar.utils.js";
|
|
5
5
|
import { Link, useNavigate, useLocation } from "@remix-run/react";
|
|
6
6
|
import { useAppSelector } from "@zauru-sdk/redux";
|
|
7
|
-
const OptionsDropDownButton = ({ color, options, name }) => {
|
|
7
|
+
const OptionsDropDownButton = ({ color, options, name, ariaLabel, }) => {
|
|
8
8
|
const [showOptionsMenu, setShowOptionsMenu] = useState(true);
|
|
9
|
-
return (_jsx("div", { className: "nav-item ml-auto", children: _jsx("div", { className: "flex justify-center", children: _jsxs("div", { className: "relative inline-block", children: [_jsxs("button", { onClick: () => setShowOptionsMenu(!showOptionsMenu), className: `relative flex cursor-pointer items-center p-2 text-xs text-white ${color.bg700} active:${color.bg900} border border-transparent rounded-full uppercase focus:ring-
|
|
9
|
+
return (_jsx("div", { className: "nav-item ml-auto", children: _jsx("div", { className: "flex justify-center", children: _jsxs("div", { className: "relative inline-block", children: [_jsxs("button", { type: "button", "aria-label": ariaLabel ?? (typeof name === "string" ? name : "Menú"), "aria-haspopup": "menu", "aria-expanded": !showOptionsMenu, onClick: () => setShowOptionsMenu(!showOptionsMenu), className: `relative flex cursor-pointer items-center p-2 text-xs text-white ${color.bg700} active:${color.bg900} border border-transparent rounded-full uppercase focus:outline-none focus-visible:ring-2 focus-visible:ring-white focus-visible:ring-offset-2`, children: [name ?? _jsx(OpcionButtonSvgIcon, {}), _jsx(DropDownArrowSvgIcon, {})] }), _jsx("div", { className: "absolute right-0 z-20 w-56 py-2 mt-2 overflow-hidden bg-white rounded-md shadow-xl dark:bg-gray-800", hidden: showOptionsMenu, onMouseLeave: () => setShowOptionsMenu(true), children: options.map((option, index) => (_jsx(React.Fragment, { children: option }, index))) })] }) }) }));
|
|
10
10
|
};
|
|
11
11
|
const NavItem = ({ name, link, icon, selectedColor, childrens = [], reduxNotificationBadge, }) => {
|
|
12
12
|
const location = useLocation();
|
|
@@ -30,7 +30,7 @@ const NavItem = ({ name, link, icon, selectedColor, childrens = [], reduxNotific
|
|
|
30
30
|
// De lo contrario, usamos el color normal (bg700)
|
|
31
31
|
className: `${isActive ? specialColor.bg900 : specialColor.bg700} container text-white w-full sm:w-auto h-10 text-sm py-1 uppercase shadow hover:shadow-lg outline-none rounded-full focus:outline-none my-auto sm:my-0 sm:mr-1 mb-1 ease-linear transition-all duration-150`, children: _jsxs(Link, { className: "px-3 flex items-center text-xs leading-snug text-white uppercase hover:opacity-75 relative", to: link, children: [_jsxs("div", { className: "mx-auto pt-2", children: [icon, _jsx("span", { children: name })] }), notificationBadge !== undefined && (_jsx("span", { className: "absolute -top-2 -right-2 bg-red-500 text-white text-xs font-bold rounded-full flex items-center justify-center w-5 h-5", children: notificationBadge }))] }) }) }));
|
|
32
32
|
};
|
|
33
|
-
export const NavBar = ({ title, loggedIn, items, selectedColor, version, reloadCatalogsOption, showDarkModeToggle = false, }) => {
|
|
33
|
+
export const NavBar = ({ title, loggedIn, items, selectedColor, version, reloadCatalogsOption, settingsOption, showDarkModeToggle = false, }) => {
|
|
34
34
|
const color = COLORS[selectedColor];
|
|
35
35
|
const [NavBarOpen, setNavBarOpen] = useState(false);
|
|
36
36
|
const [currentVersion, setCurrentVersion] = useState(version);
|
|
@@ -56,11 +56,12 @@ export const NavBar = ({ title, loggedIn, items, selectedColor, version, reloadC
|
|
|
56
56
|
const options = useMemo(() => {
|
|
57
57
|
return (_jsxs(_Fragment, { children: [_jsx("ul", { className: "w-full lg:flex lg:items-center", children: renderNavItems(items
|
|
58
58
|
.filter((item) => item.loggedIn === loggedIn)
|
|
59
|
-
.filter((item) => item.hide !== true)) }), _jsx("ul", { className: "sm:flex sm:flex-col lg:flex-row ml-auto", children: loggedIn && (_jsx(OptionsDropDownButton, { color: color, options: [
|
|
59
|
+
.filter((item) => item.hide !== true)) }), _jsx("ul", { className: "sm:flex sm:flex-col lg:flex-row ml-auto", children: loggedIn && (_jsx(OptionsDropDownButton, { color: color, ariaLabel: "Men\u00FA de usuario", options: [
|
|
60
|
+
settingsOption && (_jsx(Link, { className: "block cursor-pointer px-4 py-3 text-sm text-gray-600 capitalize transition-colors duration-200 transform dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-700 dark:hover:text-white", to: "/configuracion", children: _jsxs("div", { className: "mx-auto pt-2", children: [_jsxs("svg", { className: "h-5 w-5 mr-1", width: "24", height: "24", fill: "none", stroke: "currentColor", strokeWidth: "1.5", viewBox: "0 0 24 24", strokeLinecap: "round", strokeLinejoin: "round", xmlns: "http://www.w3.org/2000/svg", "aria-hidden": "true", children: [_jsx("path", { d: "M12 15.5a3.5 3.5 0 1 0 0-7 3.5 3.5 0 0 0 0 7Z" }), _jsx("path", { d: "M19.4 13a7.7 7.7 0 0 0 .06-2l2.1-1.64-2-3.46-2.55 1a7.7 7.7 0 0 0-1.73-1L14.7 2H9.3l-.58 2.9a7.7 7.7 0 0 0-1.73 1l-2.55-1-2 3.46L4.54 11a7.7 7.7 0 0 0 0 2l-2.1 1.64 2 3.46 2.55-1a7.7 7.7 0 0 0 1.73 1L9.3 22h5.4l.58-2.9a7.7 7.7 0 0 0 1.73-1l2.55 1 2-3.46Z" })] }), _jsx("span", { children: "Configuraci\u00F3n" })] }) }, "configuracion")),
|
|
60
61
|
reloadCatalogsOption && (_jsx(Link, { className: `block px-4 py-3 text-sm text-gray-600 capitalize transition-colors duration-200 transform dark:text-gray-300 hover:bg-red-100 dark:hover:bg-gray-700 dark:hover:text-white`, to: "/reload-catalogs", children: _jsxs("div", { className: "mx-auto pt-2", children: [_jsxs("svg", { className: `h-5 w-5 mr-1`, width: "24", height: "24", fill: "none", stroke: "currentColor", strokeWidth: "1.5", viewBox: "0 0 24 24", strokeLinecap: "round", strokeLinejoin: "round", xmlns: "http://www.w3.org/2000/svg", children: [_jsx("path", { d: "M20.5 8c-1.392-3.179-4.823-5-8.522-5C7.299 3 3.453 6.552 3 11.1" }), _jsx("path", { d: "M16.489 8.4h3.97A.54.54 0 0 0 21 7.86V3.9M3.5 16c1.392 3.179 4.823 5 8.522 5 4.679 0 8.525-3.552 8.978-8.1" }), _jsx("path", { d: "M7.511 15.6h-3.97a.54.54 0 0 0-.541.54v3.96" })] }), _jsx("span", { children: "Recargar cat\u00E1logos" })] }) }, "recargar-catalogos")),
|
|
61
62
|
showDarkModeToggle && (_jsx("button", { type: "button", className: "block w-full cursor-pointer text-left px-4 py-3 text-sm text-gray-600 capitalize transition-colors duration-200 transform dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-700 dark:hover:text-white", onClick: () => setIsDarkMode((value) => !value), children: _jsx("div", { className: "mx-auto pt-2", children: _jsx("span", { children: isDarkMode ? "Modo claro" : "Modo oscuro" }) }) }, "dark-mode-toggle")),
|
|
62
63
|
_jsx(Link, { className: `block px-4 py-3 text-sm text-gray-600 capitalize transition-colors duration-200 transform dark:text-gray-300 hover:bg-red-100 dark:hover:bg-gray-700 dark:hover:text-white`, to: "/logout", children: _jsxs("div", { className: "mx-auto pt-2", children: [_jsx(LogoutDropDownSvgIcon, {}), _jsx("span", { children: "Cerrar sesi\u00F3n" })] }) }, "cerrar-sesion"),
|
|
63
64
|
] })) })] }));
|
|
64
|
-
}, [items, loggedIn, hiddenItemsChange, showDarkModeToggle, isDarkMode]);
|
|
65
|
+
}, [items, loggedIn, hiddenItemsChange, reloadCatalogsOption, settingsOption, showDarkModeToggle, isDarkMode]);
|
|
65
66
|
return (_jsx("nav", { className: `py-3 ${color.bg600}`, children: _jsxs("div", { className: "flex items-center justify-between ml-5 mr-5", children: [_jsxs("div", { className: "flex justify-between items-center w-full lg:w-auto", children: [_jsxs(Link, { className: "text-sm font-bold leading-relaxed inline-block mr-4 py-2 whitespace-nowrap uppercase text-white", to: "/home", children: [_jsx("div", { className: "inline-block mr-2 mb-2 align-middle", children: _jsx("img", { className: "w-auto h-7", src: "/logo.png", alt: "logo-zauru" }) }), title] }), version !== currentVersion && (_jsx("button", { className: `ml-2 px-2 py-1 text-xs text-white ${color.bg700} rounded-full hover:${color.bg900} transition-colors duration-200`, onClick: refreshPage, children: "\uD83D\uDD04 Actualizar versi\u00F3n" })), _jsx("button", { className: `rounded lg:hidden focus:outline-none focus:ring focus:${color.ring600} focus:ring-opacity-50`, "aria-label": "Toggle mobile menu", type: "button", onClick: () => setNavBarOpen(!NavBarOpen), children: _jsx(MenuAlt4Svg, { open: NavBarOpen }) })] }), _jsx("div", { className: `lg:hidden fixed top-0 left-0 z-50 w-64 h-full ${color.bg700} dark:bg-gray-900 shadow-lg transform ${NavBarOpen ? "translate-x-0" : "-translate-x-full"} transition-transform duration-300 ease-in-out overflow-y-auto`, children: _jsx("div", { className: "p-4", children: options }) }), _jsx("div", { className: "hidden lg:flex lg:items-center w-full lg:w-auto", children: options })] }) }));
|
|
66
67
|
};
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
export function permissionMatrixCellKey(roleId, key) {
|
|
3
|
+
return `${roleId}:${key}`;
|
|
4
|
+
}
|
|
5
|
+
const checkboxClass = "form-checkbox h-4 w-4 cursor-pointer rounded border-gray-300 text-indigo-600 focus:ring-indigo-500 disabled:cursor-not-allowed disabled:opacity-50";
|
|
6
|
+
export const PermissionMatrix = ({ groups, roles, disabled = false, pendingCells, onToggle, onBulkRole, onBulkSection, emptyText = "Crea un rol para editar la matriz de permisos.", markAllLabel = "Marcar todo", unmarkAllLabel = "Desmarcar todo", }) => {
|
|
7
|
+
const allKeys = groups.flatMap((group) => group.actions.map((action) => action.key));
|
|
8
|
+
if (roles.length === 0) {
|
|
9
|
+
return (_jsx("p", { className: "text-sm text-gray-600", role: "status", children: emptyText }));
|
|
10
|
+
}
|
|
11
|
+
const roleHasKeys = (role, keys) => keys.length > 0 && keys.every((key) => role.permissionKeys.includes(key));
|
|
12
|
+
return (_jsx("div", { className: "overflow-x-auto rounded-md border border-gray-200", children: _jsxs("table", { className: "min-w-full border-collapse text-sm", children: [_jsx("caption", { className: "sr-only", children: "Matriz de permisos por rol. Filas son acciones, columnas son roles." }), _jsx("thead", { children: _jsxs("tr", { className: "bg-gray-50", children: [_jsx("th", { scope: "col", className: "sticky left-0 z-10 min-w-[16rem] border-b border-gray-200 bg-gray-50 px-3 py-2 text-left font-semibold text-gray-800", children: "Permiso" }), roles.map((role) => {
|
|
13
|
+
const allGranted = roleHasKeys(role, allKeys);
|
|
14
|
+
return (_jsx("th", { scope: "col", className: "min-w-[9rem] border-b border-l border-gray-200 px-3 py-2 text-center font-semibold text-gray-800", children: _jsxs("div", { className: "flex flex-col items-center gap-1", children: [_jsx("span", { className: "break-words", children: role.name }), onBulkRole ? (_jsx("button", { type: "button", className: "cursor-pointer text-xs font-medium text-indigo-700 hover:text-indigo-900 focus:outline-none focus-visible:ring-2 focus-visible:ring-indigo-500 rounded", disabled: disabled, onClick: () => onBulkRole(role.id, allGranted ? "none" : "all"), children: allGranted ? unmarkAllLabel : markAllLabel })) : null] }) }, role.id));
|
|
15
|
+
})] }) }), _jsx("tbody", { children: groups.map((group) => {
|
|
16
|
+
const sectionKeys = group.actions.map((action) => action.key);
|
|
17
|
+
return (_jsx(GroupRows, { group: group, roles: roles, sectionKeys: sectionKeys, disabled: disabled, pendingCells: pendingCells, onToggle: onToggle, onBulkSection: onBulkSection, checkboxClass: checkboxClass }, group.id));
|
|
18
|
+
}) })] }) }));
|
|
19
|
+
};
|
|
20
|
+
function GroupRows({ group, roles, sectionKeys, disabled, pendingCells, onToggle, onBulkSection, checkboxClass, }) {
|
|
21
|
+
return (_jsxs(_Fragment, { children: [_jsx("tr", { className: "bg-slate-100", children: _jsx("th", { scope: "colgroup", colSpan: roles.length + 1, className: "sticky left-0 border-b border-gray-200 px-3 py-2 text-left text-xs font-semibold uppercase tracking-wide text-slate-700", children: group.label }) }), group.actions.map((action) => (_jsxs("tr", { className: "odd:bg-white even:bg-slate-50", children: [_jsx("th", { scope: "row", className: "sticky left-0 z-10 border-b border-gray-100 bg-inherit px-3 py-2 text-left font-normal text-gray-800", children: _jsxs("div", { className: "flex flex-col gap-0.5", children: [_jsx("span", { children: action.label }), _jsx("code", { className: "text-xs text-gray-500", children: action.key })] }) }), roles.map((role) => {
|
|
22
|
+
const granted = role.permissionKeys.includes(action.key);
|
|
23
|
+
const cellKey = permissionMatrixCellKey(role.id, action.key);
|
|
24
|
+
const pending = pendingCells?.has(cellKey) ?? false;
|
|
25
|
+
return (_jsx("td", { className: "border-b border-l border-gray-100 px-3 py-2 text-center", children: _jsx("input", { type: "checkbox", className: checkboxClass, checked: granted, disabled: disabled || pending, "aria-label": `${role.name}: ${action.label}`, onChange: () => onToggle(role.id, action.key, !granted) }) }, role.id));
|
|
26
|
+
})] }, action.key))), onBulkSection ? (_jsxs("tr", { children: [_jsx("td", { className: "sticky left-0 z-10 border-b border-gray-200 bg-white px-3 py-1 text-xs text-gray-500", children: "Secci\u00F3n" }), roles.map((role) => {
|
|
27
|
+
const sectionGranted = sectionKeys.every((key) => role.permissionKeys.includes(key));
|
|
28
|
+
return (_jsx("td", { className: "border-b border-l border-gray-200 px-3 py-1 text-center", children: _jsx("button", { type: "button", className: "cursor-pointer text-xs font-medium text-indigo-700 hover:text-indigo-900 focus:outline-none focus-visible:ring-2 focus-visible:ring-indigo-500 rounded disabled:cursor-not-allowed disabled:opacity-50", disabled: disabled, onClick: () => onBulkSection(role.id, group.id, sectionGranted ? "remove" : "add"), children: sectionGranted ? "Quitar sección" : "Marcar sección" }) }, role.id));
|
|
29
|
+
})] })) : null] }));
|
|
30
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { NavLink } from "@remix-run/react";
|
|
3
|
+
const ACTIVE = {
|
|
4
|
+
indigo: "border-indigo-600 text-indigo-700",
|
|
5
|
+
slate: "border-slate-700 text-slate-800",
|
|
6
|
+
blue: "border-blue-600 text-blue-700",
|
|
7
|
+
green: "border-green-600 text-green-700",
|
|
8
|
+
};
|
|
9
|
+
export const RouteTabNav = ({ items, ariaLabel = "Secciones", selectedColor = "indigo", }) => {
|
|
10
|
+
const visible = items.filter((item) => !item.hide);
|
|
11
|
+
if (visible.length === 0) {
|
|
12
|
+
return null;
|
|
13
|
+
}
|
|
14
|
+
return (_jsx("nav", { "aria-label": ariaLabel, className: "border-b border-gray-200", children: _jsx("ul", { className: "flex flex-wrap gap-1 -mb-px", children: visible.map((item) => (_jsx("li", { children: _jsx(NavLink, { to: item.to, end: item.end, className: ({ isActive }) => [
|
|
15
|
+
"inline-flex items-center cursor-pointer rounded-t-md border-b-2 px-3 py-2 text-sm font-medium transition-colors duration-150",
|
|
16
|
+
"focus:outline-none focus-visible:ring-2 focus-visible:ring-indigo-500 focus-visible:ring-offset-2",
|
|
17
|
+
isActive
|
|
18
|
+
? ACTIVE[selectedColor]
|
|
19
|
+
: "border-transparent text-gray-500 hover:border-gray-300 hover:text-gray-800",
|
|
20
|
+
].join(" "), children: item.label }) }, item.to))) }) }));
|
|
21
|
+
};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { MainContainer } from "../Containers/MainContainer.js";
|
|
3
|
+
import { SubContainer } from "../Containers/SubContainer.js";
|
|
4
|
+
import { RouteTabNav } from "./RouteTabNav.js";
|
|
5
|
+
export const WebappSettingsChrome = ({ children, showEmployees = true, showRoles = true, showPermissions = true, basePath = "/configuracion", title = "Configuración", description = "Roles, empleados y permisos de esta aplicación.", }) => {
|
|
6
|
+
const base = basePath.replace(/\/$/, "");
|
|
7
|
+
return (_jsx(MainContainer, { children: _jsxs(SubContainer, { title: title, description: description, children: [_jsx(RouteTabNav, { ariaLabel: "Secciones de configuraci\u00F3n de accesos", selectedColor: "indigo", items: [
|
|
8
|
+
{
|
|
9
|
+
to: `${base}/empleados`,
|
|
10
|
+
label: "Empleados",
|
|
11
|
+
hide: !showEmployees,
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
to: `${base}/roles`,
|
|
15
|
+
label: "Roles",
|
|
16
|
+
hide: !showRoles,
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
to: `${base}/permisos`,
|
|
20
|
+
label: "Permisos",
|
|
21
|
+
hide: !showPermissions,
|
|
22
|
+
},
|
|
23
|
+
] }), children] }) }));
|
|
24
|
+
};
|
package/dist/esm/Tab/index.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zauru-sdk/components",
|
|
3
|
-
"version": "6.
|
|
3
|
+
"version": "6.10.0",
|
|
4
4
|
"description": "Componentes reutilizables en las WebApps de Zauru.",
|
|
5
5
|
"main": "./dist/esm/index.js",
|
|
6
6
|
"module": "./dist/esm/index.js",
|
|
@@ -44,12 +44,12 @@
|
|
|
44
44
|
"@reduxjs/toolkit": "^2.2.1",
|
|
45
45
|
"@remix-run/node": "^2.8.1",
|
|
46
46
|
"@remix-run/react": "^2.8.1",
|
|
47
|
-
"@zauru-sdk/common": "^6.8.
|
|
48
|
-
"@zauru-sdk/hooks": "^6.3.
|
|
47
|
+
"@zauru-sdk/common": "^6.8.5",
|
|
48
|
+
"@zauru-sdk/hooks": "^6.3.18",
|
|
49
49
|
"@zauru-sdk/icons": "^6.1.0",
|
|
50
|
-
"@zauru-sdk/redux": "^6.5.
|
|
51
|
-
"@zauru-sdk/types": "^6.
|
|
52
|
-
"@zauru-sdk/utils": "^6.
|
|
50
|
+
"@zauru-sdk/redux": "^6.5.16",
|
|
51
|
+
"@zauru-sdk/types": "^6.23.0",
|
|
52
|
+
"@zauru-sdk/utils": "^6.22.0",
|
|
53
53
|
"browser-image-compression": "^2.0.2",
|
|
54
54
|
"framer-motion": "^12.0.0",
|
|
55
55
|
"jsonwebtoken": "^9.0.2",
|
|
@@ -62,5 +62,5 @@
|
|
|
62
62
|
"styled-components": "^5.3.5",
|
|
63
63
|
"zod": "^3.23.8"
|
|
64
64
|
},
|
|
65
|
-
"gitHead": "
|
|
65
|
+
"gitHead": "a3c1f2af46acb59cf9fcb78ecb18d904e33c61e5"
|
|
66
66
|
}
|
package/src/NavBar/NavBar.tsx
CHANGED
|
@@ -15,7 +15,12 @@ import type {
|
|
|
15
15
|
import { Link, useNavigate, useLocation } from "@remix-run/react";
|
|
16
16
|
import { useAppSelector } from "@zauru-sdk/redux";
|
|
17
17
|
|
|
18
|
-
const OptionsDropDownButton = ({
|
|
18
|
+
const OptionsDropDownButton = ({
|
|
19
|
+
color,
|
|
20
|
+
options,
|
|
21
|
+
name,
|
|
22
|
+
ariaLabel,
|
|
23
|
+
}: EntityProps & { ariaLabel?: string }) => {
|
|
19
24
|
const [showOptionsMenu, setShowOptionsMenu] = useState(true);
|
|
20
25
|
|
|
21
26
|
return (
|
|
@@ -23,8 +28,14 @@ const OptionsDropDownButton = ({ color, options, name }: EntityProps) => {
|
|
|
23
28
|
<div className="flex justify-center">
|
|
24
29
|
<div className="relative inline-block">
|
|
25
30
|
<button
|
|
31
|
+
type="button"
|
|
32
|
+
aria-label={
|
|
33
|
+
ariaLabel ?? (typeof name === "string" ? name : "Menú")
|
|
34
|
+
}
|
|
35
|
+
aria-haspopup="menu"
|
|
36
|
+
aria-expanded={!showOptionsMenu}
|
|
26
37
|
onClick={() => setShowOptionsMenu(!showOptionsMenu)}
|
|
27
|
-
className={`relative flex cursor-pointer items-center p-2 text-xs text-white ${color.bg700} active:${color.bg900} border border-transparent rounded-full uppercase focus:ring-
|
|
38
|
+
className={`relative flex cursor-pointer items-center p-2 text-xs text-white ${color.bg700} active:${color.bg900} border border-transparent rounded-full uppercase focus:outline-none focus-visible:ring-2 focus-visible:ring-white focus-visible:ring-offset-2`}
|
|
28
39
|
>
|
|
29
40
|
{name ?? <OpcionButtonSvgIcon />}
|
|
30
41
|
<DropDownArrowSvgIcon />
|
|
@@ -128,6 +139,7 @@ export const NavBar = ({
|
|
|
128
139
|
selectedColor,
|
|
129
140
|
version,
|
|
130
141
|
reloadCatalogsOption,
|
|
142
|
+
settingsOption,
|
|
131
143
|
showDarkModeToggle = false,
|
|
132
144
|
}: NavBarProps) => {
|
|
133
145
|
const color: ColorInterface = COLORS[selectedColor];
|
|
@@ -191,7 +203,35 @@ export const NavBar = ({
|
|
|
191
203
|
{loggedIn && (
|
|
192
204
|
<OptionsDropDownButton
|
|
193
205
|
color={color}
|
|
206
|
+
ariaLabel="Menú de usuario"
|
|
194
207
|
options={[
|
|
208
|
+
settingsOption && (
|
|
209
|
+
<Link
|
|
210
|
+
key="configuracion"
|
|
211
|
+
className="block cursor-pointer px-4 py-3 text-sm text-gray-600 capitalize transition-colors duration-200 transform dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-700 dark:hover:text-white"
|
|
212
|
+
to="/configuracion"
|
|
213
|
+
>
|
|
214
|
+
<div className="mx-auto pt-2">
|
|
215
|
+
<svg
|
|
216
|
+
className="h-5 w-5 mr-1"
|
|
217
|
+
width="24"
|
|
218
|
+
height="24"
|
|
219
|
+
fill="none"
|
|
220
|
+
stroke="currentColor"
|
|
221
|
+
strokeWidth="1.5"
|
|
222
|
+
viewBox="0 0 24 24"
|
|
223
|
+
strokeLinecap="round"
|
|
224
|
+
strokeLinejoin="round"
|
|
225
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
226
|
+
aria-hidden="true"
|
|
227
|
+
>
|
|
228
|
+
<path d="M12 15.5a3.5 3.5 0 1 0 0-7 3.5 3.5 0 0 0 0 7Z" />
|
|
229
|
+
<path d="M19.4 13a7.7 7.7 0 0 0 .06-2l2.1-1.64-2-3.46-2.55 1a7.7 7.7 0 0 0-1.73-1L14.7 2H9.3l-.58 2.9a7.7 7.7 0 0 0-1.73 1l-2.55-1-2 3.46L4.54 11a7.7 7.7 0 0 0 0 2l-2.1 1.64 2 3.46 2.55-1a7.7 7.7 0 0 0 1.73 1L9.3 22h5.4l.58-2.9a7.7 7.7 0 0 0 1.73-1l2.55 1 2-3.46Z" />
|
|
230
|
+
</svg>
|
|
231
|
+
<span>Configuración</span>
|
|
232
|
+
</div>
|
|
233
|
+
</Link>
|
|
234
|
+
),
|
|
195
235
|
reloadCatalogsOption && (
|
|
196
236
|
<Link
|
|
197
237
|
key="recargar-catalogos"
|
|
@@ -247,7 +287,7 @@ export const NavBar = ({
|
|
|
247
287
|
</ul>
|
|
248
288
|
</>
|
|
249
289
|
);
|
|
250
|
-
}, [items, loggedIn, hiddenItemsChange, showDarkModeToggle, isDarkMode]);
|
|
290
|
+
}, [items, loggedIn, hiddenItemsChange, reloadCatalogsOption, settingsOption, showDarkModeToggle, isDarkMode]);
|
|
251
291
|
|
|
252
292
|
return (
|
|
253
293
|
<nav className={`py-3 ${color.bg600}`}>
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
import type { PermissionGroup } from "@zauru-sdk/types";
|
|
2
|
+
|
|
3
|
+
export type PermissionMatrixRole = {
|
|
4
|
+
id: number;
|
|
5
|
+
name: string;
|
|
6
|
+
permissionKeys: string[];
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
type Props = {
|
|
10
|
+
groups: PermissionGroup[];
|
|
11
|
+
roles: PermissionMatrixRole[];
|
|
12
|
+
disabled?: boolean;
|
|
13
|
+
pendingCells?: ReadonlySet<string>;
|
|
14
|
+
onToggle: (roleId: number, key: string, granted: boolean) => void;
|
|
15
|
+
onBulkRole?: (roleId: number, mode: "all" | "none") => void;
|
|
16
|
+
onBulkSection?: (
|
|
17
|
+
roleId: number,
|
|
18
|
+
groupId: string,
|
|
19
|
+
mode: "add" | "remove",
|
|
20
|
+
) => void;
|
|
21
|
+
emptyText?: string;
|
|
22
|
+
markAllLabel?: string;
|
|
23
|
+
unmarkAllLabel?: string;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export function permissionMatrixCellKey(roleId: number, key: string): string {
|
|
27
|
+
return `${roleId}:${key}`;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const checkboxClass =
|
|
31
|
+
"form-checkbox h-4 w-4 cursor-pointer rounded border-gray-300 text-indigo-600 focus:ring-indigo-500 disabled:cursor-not-allowed disabled:opacity-50";
|
|
32
|
+
|
|
33
|
+
export const PermissionMatrix = ({
|
|
34
|
+
groups,
|
|
35
|
+
roles,
|
|
36
|
+
disabled = false,
|
|
37
|
+
pendingCells,
|
|
38
|
+
onToggle,
|
|
39
|
+
onBulkRole,
|
|
40
|
+
onBulkSection,
|
|
41
|
+
emptyText = "Crea un rol para editar la matriz de permisos.",
|
|
42
|
+
markAllLabel = "Marcar todo",
|
|
43
|
+
unmarkAllLabel = "Desmarcar todo",
|
|
44
|
+
}: Props) => {
|
|
45
|
+
const allKeys = groups.flatMap((group) =>
|
|
46
|
+
group.actions.map((action) => action.key),
|
|
47
|
+
);
|
|
48
|
+
|
|
49
|
+
if (roles.length === 0) {
|
|
50
|
+
return (
|
|
51
|
+
<p className="text-sm text-gray-600" role="status">
|
|
52
|
+
{emptyText}
|
|
53
|
+
</p>
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const roleHasKeys = (role: PermissionMatrixRole, keys: string[]) =>
|
|
58
|
+
keys.length > 0 && keys.every((key) => role.permissionKeys.includes(key));
|
|
59
|
+
|
|
60
|
+
return (
|
|
61
|
+
<div className="overflow-x-auto rounded-md border border-gray-200">
|
|
62
|
+
<table className="min-w-full border-collapse text-sm">
|
|
63
|
+
<caption className="sr-only">
|
|
64
|
+
Matriz de permisos por rol. Filas son acciones, columnas son roles.
|
|
65
|
+
</caption>
|
|
66
|
+
<thead>
|
|
67
|
+
<tr className="bg-gray-50">
|
|
68
|
+
<th
|
|
69
|
+
scope="col"
|
|
70
|
+
className="sticky left-0 z-10 min-w-[16rem] border-b border-gray-200 bg-gray-50 px-3 py-2 text-left font-semibold text-gray-800"
|
|
71
|
+
>
|
|
72
|
+
Permiso
|
|
73
|
+
</th>
|
|
74
|
+
{roles.map((role) => {
|
|
75
|
+
const allGranted = roleHasKeys(role, allKeys);
|
|
76
|
+
return (
|
|
77
|
+
<th
|
|
78
|
+
key={role.id}
|
|
79
|
+
scope="col"
|
|
80
|
+
className="min-w-[9rem] border-b border-l border-gray-200 px-3 py-2 text-center font-semibold text-gray-800"
|
|
81
|
+
>
|
|
82
|
+
<div className="flex flex-col items-center gap-1">
|
|
83
|
+
<span className="break-words">{role.name}</span>
|
|
84
|
+
{onBulkRole ? (
|
|
85
|
+
<button
|
|
86
|
+
type="button"
|
|
87
|
+
className="cursor-pointer text-xs font-medium text-indigo-700 hover:text-indigo-900 focus:outline-none focus-visible:ring-2 focus-visible:ring-indigo-500 rounded"
|
|
88
|
+
disabled={disabled}
|
|
89
|
+
onClick={() =>
|
|
90
|
+
onBulkRole(role.id, allGranted ? "none" : "all")
|
|
91
|
+
}
|
|
92
|
+
>
|
|
93
|
+
{allGranted ? unmarkAllLabel : markAllLabel}
|
|
94
|
+
</button>
|
|
95
|
+
) : null}
|
|
96
|
+
</div>
|
|
97
|
+
</th>
|
|
98
|
+
);
|
|
99
|
+
})}
|
|
100
|
+
</tr>
|
|
101
|
+
</thead>
|
|
102
|
+
<tbody>
|
|
103
|
+
{groups.map((group) => {
|
|
104
|
+
const sectionKeys = group.actions.map((action) => action.key);
|
|
105
|
+
return (
|
|
106
|
+
<GroupRows
|
|
107
|
+
key={group.id}
|
|
108
|
+
group={group}
|
|
109
|
+
roles={roles}
|
|
110
|
+
sectionKeys={sectionKeys}
|
|
111
|
+
disabled={disabled}
|
|
112
|
+
pendingCells={pendingCells}
|
|
113
|
+
onToggle={onToggle}
|
|
114
|
+
onBulkSection={onBulkSection}
|
|
115
|
+
checkboxClass={checkboxClass}
|
|
116
|
+
/>
|
|
117
|
+
);
|
|
118
|
+
})}
|
|
119
|
+
</tbody>
|
|
120
|
+
</table>
|
|
121
|
+
</div>
|
|
122
|
+
);
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
function GroupRows({
|
|
126
|
+
group,
|
|
127
|
+
roles,
|
|
128
|
+
sectionKeys,
|
|
129
|
+
disabled,
|
|
130
|
+
pendingCells,
|
|
131
|
+
onToggle,
|
|
132
|
+
onBulkSection,
|
|
133
|
+
checkboxClass,
|
|
134
|
+
}: {
|
|
135
|
+
group: PermissionGroup;
|
|
136
|
+
roles: PermissionMatrixRole[];
|
|
137
|
+
sectionKeys: string[];
|
|
138
|
+
disabled: boolean;
|
|
139
|
+
pendingCells?: ReadonlySet<string>;
|
|
140
|
+
onToggle: Props["onToggle"];
|
|
141
|
+
onBulkSection: Props["onBulkSection"];
|
|
142
|
+
checkboxClass: string;
|
|
143
|
+
}) {
|
|
144
|
+
return (
|
|
145
|
+
<>
|
|
146
|
+
<tr className="bg-slate-100">
|
|
147
|
+
<th
|
|
148
|
+
scope="colgroup"
|
|
149
|
+
colSpan={roles.length + 1}
|
|
150
|
+
className="sticky left-0 border-b border-gray-200 px-3 py-2 text-left text-xs font-semibold uppercase tracking-wide text-slate-700"
|
|
151
|
+
>
|
|
152
|
+
{group.label}
|
|
153
|
+
</th>
|
|
154
|
+
</tr>
|
|
155
|
+
{group.actions.map((action) => (
|
|
156
|
+
<tr key={action.key} className="odd:bg-white even:bg-slate-50">
|
|
157
|
+
<th
|
|
158
|
+
scope="row"
|
|
159
|
+
className="sticky left-0 z-10 border-b border-gray-100 bg-inherit px-3 py-2 text-left font-normal text-gray-800"
|
|
160
|
+
>
|
|
161
|
+
<div className="flex flex-col gap-0.5">
|
|
162
|
+
<span>{action.label}</span>
|
|
163
|
+
<code className="text-xs text-gray-500">{action.key}</code>
|
|
164
|
+
</div>
|
|
165
|
+
</th>
|
|
166
|
+
{roles.map((role) => {
|
|
167
|
+
const granted = role.permissionKeys.includes(action.key);
|
|
168
|
+
const cellKey = permissionMatrixCellKey(role.id, action.key);
|
|
169
|
+
const pending = pendingCells?.has(cellKey) ?? false;
|
|
170
|
+
return (
|
|
171
|
+
<td
|
|
172
|
+
key={role.id}
|
|
173
|
+
className="border-b border-l border-gray-100 px-3 py-2 text-center"
|
|
174
|
+
>
|
|
175
|
+
<input
|
|
176
|
+
type="checkbox"
|
|
177
|
+
className={checkboxClass}
|
|
178
|
+
checked={granted}
|
|
179
|
+
disabled={disabled || pending}
|
|
180
|
+
aria-label={`${role.name}: ${action.label}`}
|
|
181
|
+
onChange={() => onToggle(role.id, action.key, !granted)}
|
|
182
|
+
/>
|
|
183
|
+
</td>
|
|
184
|
+
);
|
|
185
|
+
})}
|
|
186
|
+
</tr>
|
|
187
|
+
))}
|
|
188
|
+
{onBulkSection ? (
|
|
189
|
+
<tr>
|
|
190
|
+
<td className="sticky left-0 z-10 border-b border-gray-200 bg-white px-3 py-1 text-xs text-gray-500">
|
|
191
|
+
Sección
|
|
192
|
+
</td>
|
|
193
|
+
{roles.map((role) => {
|
|
194
|
+
const sectionGranted = sectionKeys.every((key) =>
|
|
195
|
+
role.permissionKeys.includes(key),
|
|
196
|
+
);
|
|
197
|
+
return (
|
|
198
|
+
<td
|
|
199
|
+
key={role.id}
|
|
200
|
+
className="border-b border-l border-gray-200 px-3 py-1 text-center"
|
|
201
|
+
>
|
|
202
|
+
<button
|
|
203
|
+
type="button"
|
|
204
|
+
className="cursor-pointer text-xs font-medium text-indigo-700 hover:text-indigo-900 focus:outline-none focus-visible:ring-2 focus-visible:ring-indigo-500 rounded disabled:cursor-not-allowed disabled:opacity-50"
|
|
205
|
+
disabled={disabled}
|
|
206
|
+
onClick={() =>
|
|
207
|
+
onBulkSection(
|
|
208
|
+
role.id,
|
|
209
|
+
group.id,
|
|
210
|
+
sectionGranted ? "remove" : "add",
|
|
211
|
+
)
|
|
212
|
+
}
|
|
213
|
+
>
|
|
214
|
+
{sectionGranted ? "Quitar sección" : "Marcar sección"}
|
|
215
|
+
</button>
|
|
216
|
+
</td>
|
|
217
|
+
);
|
|
218
|
+
})}
|
|
219
|
+
</tr>
|
|
220
|
+
) : null}
|
|
221
|
+
</>
|
|
222
|
+
);
|
|
223
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { NavLink } from "@remix-run/react";
|
|
2
|
+
|
|
3
|
+
export type RouteTabNavItem = {
|
|
4
|
+
to: string;
|
|
5
|
+
label: string;
|
|
6
|
+
hide?: boolean;
|
|
7
|
+
end?: boolean;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
type Props = {
|
|
11
|
+
items: RouteTabNavItem[];
|
|
12
|
+
ariaLabel?: string;
|
|
13
|
+
selectedColor?: "indigo" | "slate" | "blue" | "green";
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
const ACTIVE: Record<NonNullable<Props["selectedColor"]>, string> = {
|
|
17
|
+
indigo: "border-indigo-600 text-indigo-700",
|
|
18
|
+
slate: "border-slate-700 text-slate-800",
|
|
19
|
+
blue: "border-blue-600 text-blue-700",
|
|
20
|
+
green: "border-green-600 text-green-700",
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export const RouteTabNav = ({
|
|
24
|
+
items,
|
|
25
|
+
ariaLabel = "Secciones",
|
|
26
|
+
selectedColor = "indigo",
|
|
27
|
+
}: Props) => {
|
|
28
|
+
const visible = items.filter((item) => !item.hide);
|
|
29
|
+
|
|
30
|
+
if (visible.length === 0) {
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
return (
|
|
35
|
+
<nav
|
|
36
|
+
aria-label={ariaLabel}
|
|
37
|
+
className="border-b border-gray-200"
|
|
38
|
+
>
|
|
39
|
+
<ul className="flex flex-wrap gap-1 -mb-px">
|
|
40
|
+
{visible.map((item) => (
|
|
41
|
+
<li key={item.to}>
|
|
42
|
+
<NavLink
|
|
43
|
+
to={item.to}
|
|
44
|
+
end={item.end}
|
|
45
|
+
className={({ isActive }) =>
|
|
46
|
+
[
|
|
47
|
+
"inline-flex items-center cursor-pointer rounded-t-md border-b-2 px-3 py-2 text-sm font-medium transition-colors duration-150",
|
|
48
|
+
"focus:outline-none focus-visible:ring-2 focus-visible:ring-indigo-500 focus-visible:ring-offset-2",
|
|
49
|
+
isActive
|
|
50
|
+
? ACTIVE[selectedColor]
|
|
51
|
+
: "border-transparent text-gray-500 hover:border-gray-300 hover:text-gray-800",
|
|
52
|
+
].join(" ")
|
|
53
|
+
}
|
|
54
|
+
>
|
|
55
|
+
{item.label}
|
|
56
|
+
</NavLink>
|
|
57
|
+
</li>
|
|
58
|
+
))}
|
|
59
|
+
</ul>
|
|
60
|
+
</nav>
|
|
61
|
+
);
|
|
62
|
+
};
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import type { ReactNode } from "react";
|
|
2
|
+
import { MainContainer } from "../Containers/MainContainer.js";
|
|
3
|
+
import { SubContainer } from "../Containers/SubContainer.js";
|
|
4
|
+
import { RouteTabNav } from "./RouteTabNav.js";
|
|
5
|
+
|
|
6
|
+
type Props = {
|
|
7
|
+
children: ReactNode;
|
|
8
|
+
showEmployees?: boolean;
|
|
9
|
+
showRoles?: boolean;
|
|
10
|
+
showPermissions?: boolean;
|
|
11
|
+
basePath?: string;
|
|
12
|
+
title?: string;
|
|
13
|
+
description?: string;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export const WebappSettingsChrome = ({
|
|
17
|
+
children,
|
|
18
|
+
showEmployees = true,
|
|
19
|
+
showRoles = true,
|
|
20
|
+
showPermissions = true,
|
|
21
|
+
basePath = "/configuracion",
|
|
22
|
+
title = "Configuración",
|
|
23
|
+
description = "Roles, empleados y permisos de esta aplicación.",
|
|
24
|
+
}: Props) => {
|
|
25
|
+
const base = basePath.replace(/\/$/, "");
|
|
26
|
+
return (
|
|
27
|
+
<MainContainer>
|
|
28
|
+
<SubContainer title={title} description={description}>
|
|
29
|
+
<RouteTabNav
|
|
30
|
+
ariaLabel="Secciones de configuración de accesos"
|
|
31
|
+
selectedColor="indigo"
|
|
32
|
+
items={[
|
|
33
|
+
{
|
|
34
|
+
to: `${base}/empleados`,
|
|
35
|
+
label: "Empleados",
|
|
36
|
+
hide: !showEmployees,
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
to: `${base}/roles`,
|
|
40
|
+
label: "Roles",
|
|
41
|
+
hide: !showRoles,
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
to: `${base}/permisos`,
|
|
45
|
+
label: "Permisos",
|
|
46
|
+
hide: !showPermissions,
|
|
47
|
+
},
|
|
48
|
+
]}
|
|
49
|
+
/>
|
|
50
|
+
{children}
|
|
51
|
+
</SubContainer>
|
|
52
|
+
</MainContainer>
|
|
53
|
+
);
|
|
54
|
+
};
|
package/src/Tab/index.ts
CHANGED