@zauru-sdk/components 6.9.0 → 6.11.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 CHANGED
@@ -3,6 +3,36 @@
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.11.0](https://github.com/intuitiva/zauru-typescript-sdk/compare/@zauru-sdk/components@6.10.0...@zauru-sdk/components@6.11.0) (2026-10-05)
7
+
8
+
9
+ ### Bug Fixes
10
+
11
+ * asociar etiquetas del selector con su campo ([1626bbd](https://github.com/intuitiva/zauru-typescript-sdk/commit/1626bbdd58971c353bf03b07533a171a05ec8571))
12
+ * identificar controles de la matriz de permisos ([472c956](https://github.com/intuitiva/zauru-typescript-sdk/commit/472c9565f2dc167e8566706f58c27ca55c6c9d09))
13
+ * limitar altura de selecciones múltiples ([b0e578d](https://github.com/intuitiva/zauru-typescript-sdk/commit/b0e578df1d7ec2198abfd57515b6dc8409227c4d))
14
+
15
+
16
+ ### Features
17
+
18
+ * exportar gestión de acceso de empleados ([df97155](https://github.com/intuitiva/zauru-typescript-sdk/commit/df97155d1370cdb624cc3ceb3905111a42edf65f))
19
+ * exportar gestión de roles ([f0ee633](https://github.com/intuitiva/zauru-typescript-sdk/commit/f0ee63301583591b72faba93b8eab2d7ec8e735f))
20
+
21
+
22
+
23
+
24
+
25
+ # [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)
26
+
27
+
28
+ ### Features
29
+
30
+ * menú Configuración genérico y extras de asignación fuera del SDK ([2691957](https://github.com/intuitiva/zauru-typescript-sdk/commit/2691957c9dba23226a4b8d371fee26b221e126f0))
31
+
32
+
33
+
34
+
35
+
6
36
  # [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)
7
37
 
8
38
 
@@ -0,0 +1,36 @@
1
+ import type { EmployeeGraphQL, WebAppRowGraphQL, WebappEmployeeRole, WebappRole } from "@zauru-sdk/types";
2
+ import { type ReactNode } from "react";
3
+ export type WebappEmployeeAssignmentChange<TAssignment extends WebappEmployeeRole> = {
4
+ employee: EmployeeGraphQL;
5
+ assignment?: WebAppRowGraphQL<TAssignment>;
6
+ roleId: number | null;
7
+ };
8
+ export type WebappEmployeeAssignmentDetails<TAssignment extends WebappEmployeeRole> = {
9
+ employee: EmployeeGraphQL;
10
+ assignment?: WebAppRowGraphQL<TAssignment>;
11
+ disabled: boolean;
12
+ };
13
+ type Props<TAssignment extends WebappEmployeeRole> = {
14
+ employees: EmployeeGraphQL[];
15
+ roles: WebAppRowGraphQL<WebappRole>[];
16
+ assignments: WebAppRowGraphQL<TAssignment>[];
17
+ canAssign?: boolean;
18
+ loading?: boolean;
19
+ description?: string;
20
+ superAdminLabel?: string;
21
+ addEmployeeTitle?: string;
22
+ addEmployeeDescription?: string;
23
+ noEmployeesToAddText?: string;
24
+ noRolesText?: string;
25
+ assignmentDetailsColumn?: {
26
+ name: string;
27
+ minWidth?: string;
28
+ grow?: number;
29
+ };
30
+ renderAssignmentDetails?: (details: WebappEmployeeAssignmentDetails<TAssignment>) => ReactNode;
31
+ superAdminEmailSuffixes?: readonly string[];
32
+ onAssignRole: (change: WebappEmployeeAssignmentChange<TAssignment>) => void;
33
+ onAddEmployee: (employeeId: number, roleId: number) => void;
34
+ };
35
+ export declare function WebappEmployeeAccess<TAssignment extends WebappEmployeeRole = WebappEmployeeRole>({ employees, roles, assignments, canAssign, loading, description, superAdminLabel, addEmployeeTitle, addEmployeeDescription, noEmployeesToAddText, noRolesText, assignmentDetailsColumn, renderAssignmentDetails, superAdminEmailSuffixes, onAssignRole, onAddEmployee, }: Props<TAssignment>): import("react/jsx-runtime").JSX.Element;
36
+ export {};
@@ -0,0 +1,22 @@
1
+ import type { WebAppRowGraphQL, WebappRole } from "@zauru-sdk/types";
2
+ export type WebappRoleValues = {
3
+ name: string;
4
+ description: string;
5
+ };
6
+ type Props = {
7
+ roles: WebAppRowGraphQL<WebappRole>[];
8
+ canCreate?: boolean;
9
+ canEdit?: boolean;
10
+ canDelete?: boolean;
11
+ loading?: boolean;
12
+ createTitle?: string;
13
+ createButtonLabel?: string;
14
+ editTitle?: string;
15
+ deleteTitle?: string;
16
+ deleteDescription?: string;
17
+ onCreate: (values: WebappRoleValues) => void;
18
+ onUpdate: (roleId: number, values: WebappRoleValues) => void;
19
+ onDelete: (roleId: number) => void;
20
+ };
21
+ export declare function WebappRoleManagement({ roles, canCreate, canEdit, canDelete, loading, createTitle, createButtonLabel, editTitle, deleteTitle, deleteDescription, onCreate, onUpdate, onDelete, }: Props): import("react/jsx-runtime").JSX.Element | null;
22
+ export {};
@@ -0,0 +1,2 @@
1
+ export * from "./WebappEmployeeAccess.js";
2
+ export * from "./WebappRoleManagement.js";
@@ -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;
@@ -17,6 +17,7 @@ export type NavBarProps = {
17
17
  LinkComponent?: any;
18
18
  version?: string;
19
19
  reloadCatalogsOption?: boolean;
20
+ settingsOption?: boolean;
20
21
  showDarkModeToggle?: boolean;
21
22
  };
22
23
  export type NavBarStateProps = {
@@ -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 {};
@@ -1,3 +1,4 @@
1
1
  export * from "./Tab.js";
2
2
  export * from "./RouteTabNav.js";
3
3
  export * from "./PermissionMatrix.js";
4
+ export * from "./WebappSettingsChrome.js";
@@ -0,0 +1,103 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { isSuperAdminEmail } from "@zauru-sdk/utils";
3
+ import { useMemo } from "react";
4
+ import { z } from "zod";
5
+ import { Button } from "../Buttons/Button.js";
6
+ import { ButtonSectionContainer } from "../Containers/ButtonSectionContainer.js";
7
+ import { DoubleFieldContainer } from "../Form/FieldContainer/DoubleFieldContainer.js";
8
+ import { ReactZodForm } from "../Form/ReactZodForm/index.js";
9
+ import { SelectField } from "../Form/SelectField/index.js";
10
+ import { ZauruTable } from "../Table/ZauruTable.js";
11
+ const addEmployeeSchema = z.object({
12
+ employee_id: z.coerce.number().min(1, "El empleado es requerido"),
13
+ role_id: z.coerce.number().min(1, "El rol es requerido"),
14
+ });
15
+ export function WebappEmployeeAccess({ employees, roles, assignments, canAssign = false, loading = false, description = "Solo aparecen quienes ya tienen acceso: rol asignado o super admin. Quitar el rol los saca de la lista.", superAdminLabel = "Super admin (todos los permisos)", addEmployeeTitle = "Agregar empleado", addEmployeeDescription = "Busca entre quienes aún no están en la lista y asígnales un rol.", noEmployeesToAddText = "No quedan empleados activos por agregar.", noRolesText = "Crea al menos un rol antes de agregar empleados.", assignmentDetailsColumn, renderAssignmentDetails, superAdminEmailSuffixes, onAssignRole, onAddEmployee, }) {
16
+ const roleOptions = useMemo(() => [
17
+ { value: "", label: "Sin rol" },
18
+ ...roles.map((role) => ({
19
+ value: String(role.id),
20
+ label: role.data.name,
21
+ })),
22
+ ], [roles]);
23
+ const addRoleOptions = useMemo(() => roles.map((role) => ({
24
+ value: String(role.id),
25
+ label: role.data.name,
26
+ })), [roles]);
27
+ const assignmentByEmployee = useMemo(() => {
28
+ const map = new Map();
29
+ for (const row of assignments) {
30
+ map.set(Number(row.data.employee_id), row);
31
+ }
32
+ return map;
33
+ }, [assignments]);
34
+ const listedEmployees = useMemo(() => employees
35
+ .filter((employee) => isSuperAdminEmail(employee.email, superAdminEmailSuffixes) ||
36
+ assignmentByEmployee.has(employee.id))
37
+ .sort((a, b) => a.name.localeCompare(b.name)), [assignmentByEmployee, employees, superAdminEmailSuffixes]);
38
+ const addEmployeeOptions = useMemo(() => {
39
+ const listedIds = new Set(listedEmployees.map((employee) => employee.id));
40
+ return employees
41
+ .filter((employee) => !listedIds.has(employee.id))
42
+ .sort((a, b) => a.name.localeCompare(b.name))
43
+ .map((employee) => ({
44
+ value: employee.id,
45
+ label: employee.email
46
+ ? `${employee.name} (${employee.email})`
47
+ : employee.name,
48
+ }));
49
+ }, [employees, listedEmployees]);
50
+ const columns = [
51
+ {
52
+ name: "Empleado",
53
+ sortable: true,
54
+ grow: 1,
55
+ minWidth: "12rem",
56
+ selector: (employee) => employee.name,
57
+ },
58
+ {
59
+ name: "Email",
60
+ sortable: true,
61
+ grow: 1,
62
+ minWidth: "14rem",
63
+ selector: (employee) => employee.email || "—",
64
+ },
65
+ {
66
+ name: "Rol",
67
+ minWidth: "14rem",
68
+ cell: (employee) => {
69
+ const assignment = assignmentByEmployee.get(employee.id);
70
+ const superAdmin = isSuperAdminEmail(employee.email, superAdminEmailSuffixes);
71
+ const currentRole = assignment
72
+ ? String(assignment.data.role_id)
73
+ : "";
74
+ return (_jsx("div", { className: "w-56 py-2", children: superAdmin ? (_jsx("p", { className: "text-sm text-gray-600", children: superAdminLabel })) : (_jsx(SelectField, { name: `role_${employee.id}`, options: roleOptions, defaultValue: roleOptions.find((option) => option.value === currentRole) ?? roleOptions[0], disabled: !canAssign || loading, onChange: (option) => {
75
+ const value = option?.value?.toString() ?? "";
76
+ onAssignRole({
77
+ employee,
78
+ assignment,
79
+ roleId: value ? Number(value) : null,
80
+ });
81
+ } })) }));
82
+ },
83
+ },
84
+ ...(assignmentDetailsColumn && renderAssignmentDetails
85
+ ? [
86
+ {
87
+ name: assignmentDetailsColumn.name,
88
+ minWidth: assignmentDetailsColumn.minWidth ?? "16rem",
89
+ grow: assignmentDetailsColumn.grow ?? 1,
90
+ cell: (employee) => {
91
+ const assignment = assignmentByEmployee.get(employee.id);
92
+ return renderAssignmentDetails({
93
+ employee,
94
+ assignment,
95
+ disabled: !canAssign || !assignment || loading,
96
+ });
97
+ },
98
+ },
99
+ ]
100
+ : []),
101
+ ];
102
+ return (_jsxs("div", { className: "mt-4 space-y-6", children: [_jsx("p", { className: "text-sm text-gray-600", children: description }), _jsx(ZauruTable, { columns: columns, data: listedEmployees, loading: loading, offlineSearch: ["name", "email"] }), canAssign ? (roles.length === 0 ? (_jsx("p", { className: "text-sm text-gray-600", role: "status", children: noRolesText })) : addEmployeeOptions.length > 0 ? (_jsx("div", { className: "rounded-md border border-gray-200 bg-white p-4", children: _jsxs(ReactZodForm, { schema: addEmployeeSchema, onSubmit: (values) => onAddEmployee(Number(values.employee_id), Number(values.role_id)), children: [_jsx("h2", { className: "text-base font-semibold text-gray-900", children: addEmployeeTitle }), _jsx("p", { className: "mb-4 mt-1 text-sm text-gray-600", children: addEmployeeDescription }), _jsxs(DoubleFieldContainer, { alignCenter: false, children: [_jsx(SelectField, { name: "employee_id", title: "Empleado", hint: "Escribe para filtrar por nombre o correo", options: addEmployeeOptions, required: true }), _jsx(SelectField, { name: "role_id", title: "Rol", options: addRoleOptions, required: true })] }), _jsx(ButtonSectionContainer, { children: _jsx(Button, { loading: loading, title: "Agregar" }) })] }) })) : (_jsx("p", { className: "text-sm text-gray-600", role: "status", children: noEmployeesToAddText }))) : null] }));
103
+ }
@@ -0,0 +1,77 @@
1
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { PencilSvg, TrashSvg } from "@zauru-sdk/icons";
3
+ import { useSyncExternalStore } from "react";
4
+ import { z } from "zod";
5
+ import { Button } from "../Buttons/Button.js";
6
+ import { ButtonSectionContainer } from "../Containers/ButtonSectionContainer.js";
7
+ import { ReactZodForm } from "../Form/ReactZodForm/index.js";
8
+ import { TextField } from "../Form/TextField/index.js";
9
+ import { createModal } from "../Modal/Modal.js";
10
+ import { ZauruTable } from "../Table/ZauruTable.js";
11
+ const roleSchema = z.object({
12
+ name: z.string().min(1, "El nombre es requerido"),
13
+ description: z.string().optional(),
14
+ });
15
+ const subscribeToBrowser = () => () => undefined;
16
+ const getBrowserSnapshot = () => true;
17
+ const getServerSnapshot = () => false;
18
+ export function WebappRoleManagement({ roles, canCreate = false, canEdit = false, canDelete = false, loading = false, createTitle = "Crear rol", createButtonLabel = "Crear rol", editTitle = "Editar rol", deleteTitle = "¿Eliminar este rol?", deleteDescription = "No se puede eliminar si hay empleados asignados.", onCreate, onUpdate, onDelete, }) {
19
+ const isClient = useSyncExternalStore(subscribeToBrowser, getBrowserSnapshot, getServerSnapshot);
20
+ const editRole = async (role) => {
21
+ let name = role.data.name;
22
+ let description = role.data.description ?? "";
23
+ const response = await createModal({
24
+ title: editTitle,
25
+ okButtonText: "Guardar",
26
+ description: (_jsxs(_Fragment, { children: [_jsx(TextField, { title: "Nombre", name: "name", defaultValue: name, onChange: (value) => {
27
+ name = value;
28
+ } }), _jsx(TextField, { className: "mt-4", title: "Descripci\u00F3n", name: "description", defaultValue: description, onChange: (value) => {
29
+ description = value;
30
+ } })] })),
31
+ });
32
+ if (response === "OK") {
33
+ onUpdate(role.id, { name, description });
34
+ }
35
+ };
36
+ const confirmDelete = async (roleId) => {
37
+ const response = await createModal({
38
+ title: deleteTitle,
39
+ description: deleteDescription,
40
+ });
41
+ if (response === "OK") {
42
+ onDelete(roleId);
43
+ }
44
+ };
45
+ const columns = [
46
+ {
47
+ name: "Nombre",
48
+ sortable: true,
49
+ grow: 1,
50
+ selector: (role) => role.data.name,
51
+ },
52
+ {
53
+ name: "Descripción",
54
+ grow: 2,
55
+ selector: (role) => role.data.description || "—",
56
+ },
57
+ {
58
+ name: "Permisos",
59
+ width: "8%",
60
+ selector: (role) => role.data.permission_keys?.length ?? 0,
61
+ },
62
+ {
63
+ name: "",
64
+ grow: 0,
65
+ minWidth: "90px",
66
+ maxWidth: "90px",
67
+ cell: (role) => (_jsxs("div", { className: "flex gap-2", children: [canDelete ? (_jsx("button", { type: "button", title: "Eliminar", "aria-label": `Eliminar rol ${role.data.name}`, disabled: loading, className: "cursor-pointer rounded focus:outline-none focus-visible:ring-2 focus-visible:ring-indigo-500 focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50", onClick: () => void confirmDelete(role.id), children: _jsx(TrashSvg, {}) })) : null, canEdit ? (_jsx("button", { type: "button", title: "Editar", "aria-label": `Editar rol ${role.data.name}`, disabled: loading, className: "cursor-pointer rounded focus:outline-none focus-visible:ring-2 focus-visible:ring-indigo-500 focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50", onClick: () => void editRole(role), children: _jsx(PencilSvg, {}) })) : null] })),
68
+ },
69
+ ];
70
+ if (!isClient) {
71
+ return null;
72
+ }
73
+ return (_jsxs("div", { className: "mt-4 space-y-6", children: [_jsx(ZauruTable, { columns: columns, data: [...roles].sort((a, b) => a.data.name.localeCompare(b.data.name)), loading: loading, offlineSearch: ["data.name", "data.description"] }), canCreate ? (_jsx("div", { className: "rounded-md border border-gray-200 bg-white p-4", children: _jsxs(ReactZodForm, { schema: roleSchema, onSubmit: (values) => onCreate({
74
+ name: String(values.name ?? ""),
75
+ description: String(values.description ?? ""),
76
+ }), children: [_jsx("h2", { className: "mb-4 text-base font-semibold text-gray-900", children: createTitle }), _jsx(TextField, { name: "name", title: "Nombre del rol", required: true }), _jsx(TextField, { className: "mt-4", name: "description", title: "Descripci\u00F3n" }), _jsx(ButtonSectionContainer, { children: _jsx(Button, { loading: loading, title: createButtonLabel }) })] }) })) : null] }));
77
+ }
@@ -0,0 +1,2 @@
1
+ export * from "./WebappEmployeeAccess.js";
2
+ export * from "./WebappRoleManagement.js";
@@ -1,6 +1,6 @@
1
1
  import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { IdeaIconSVG } from "@zauru-sdk/icons";
3
- import { useEffect, useState, useRef, useCallback, } from "react";
3
+ import { useEffect, useState, useRef, useId, useCallback, } from "react";
4
4
  import { LoadingInputSkeleton } from "../../Skeletons/index.js";
5
5
  import { useFormContext } from "react-hook-form";
6
6
  // Simple comparison helper to check if two SelectFieldOption arrays contain the
@@ -30,6 +30,10 @@ export const SelectField = (props) => {
30
30
  const lastSyncedDefaultValues = useRef(defaultValueMulti.map((option) => option.value));
31
31
  const [isTabPressed, setIsTabPressed] = useState(false);
32
32
  const [isSearching, setIsSearching] = useState(false);
33
+ const generatedInputId = useId();
34
+ const inputId = id ?? (name ? `${name}-input` : generatedInputId);
35
+ const errorId = `${inputId}-error`;
36
+ const hintId = `${inputId}-hint`;
33
37
  // Use refs to store stable references to callbacks
34
38
  const onChangeRef = useRef(onChange);
35
39
  const onChangeMultiRef = useRef(onChangeMulti);
@@ -275,11 +279,13 @@ export const SelectField = (props) => {
275
279
  options,
276
280
  ]);
277
281
  if (loading) {
278
- return (_jsxs(_Fragment, { children: [title && (_jsx("label", { htmlFor: error ? `${name}-error` : `${name}-success`, className: `block text-sm font-medium text-${color}-700 dark:text-${color}-500`, children: title })), _jsx(LoadingInputSkeleton, {}), helpText && (_jsx("p", { className: `mt-2 italic text-sm text-${color}-500 dark:text-${color}-400`, children: helpText }))] }));
282
+ return (_jsxs(_Fragment, { children: [title && (_jsx("span", { className: `block text-sm font-medium text-${color}-700 dark:text-${color}-500`, children: title })), _jsx(LoadingInputSkeleton, {}), helpText && (_jsx("p", { className: `mt-2 italic text-sm text-${color}-500 dark:text-${color}-400`, children: helpText }))] }));
279
283
  }
280
- return (_jsxs("div", { className: `col-span-6 sm:col-span-3 ${className}`, ref: selectRef, children: [title && (_jsxs("label", { htmlFor: error ? `${name}-error` : `${name}-success`, className: `block text-sm font-medium ${color === "red"
284
+ return (_jsxs("div", { className: `col-span-6 sm:col-span-3 ${className}`, ref: selectRef, children: [title && (_jsxs("label", { htmlFor: inputId, className: `block text-sm font-medium ${color === "red"
281
285
  ? "text-red-700 dark:text-red-500"
282
- : "text-gray-700 dark:text-gray-500"}`, children: [title, required && _jsx("span", { className: "text-red-500", children: "*" })] })), _jsxs("div", { className: "relative", children: [_jsxs("div", { className: "flex items-center", children: [_jsx("input", { type: "text", id: id, value: inputValue, onFocus: () => setIsOpen(true), onKeyDown: handleKeyDown, readOnly: isReadOnly, disabled: disabled, className: `block w-full rounded-md ${bgColor} ${borderColor} ${textColor} shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm pr-10`, placeholder: isMulti ? "Select options..." : "Select an option...", autoComplete: "off", onChange: handleInputChange, onBlur: handleBlur, required: required }), isClearable && (value || valueMulti.length > 0) && (_jsx("button", { type: "button", onClick: handleClear, className: "absolute inset-y-0 right-0 pr-3 flex items-center", children: "\u00D7" })), helpText && (_jsx("div", { className: "flex items-center relative ml-3", children: _jsxs("div", { className: "relative cursor-pointer", onMouseEnter: () => setShowTooltip(true), onMouseLeave: () => setShowTooltip(false), children: [_jsx(IdeaIconSVG, {}), showTooltip && (_jsx("div", { className: "absolute -left-48 top-0 mt-8 p-2 bg-white border rounded shadow text-black z-50", children: helpText }))] }) }))] }), _jsx("input", { type: "hidden", ...(register ?? {}), name: name, value: isMulti
286
+ : "text-gray-700 dark:text-gray-500"}`, children: [title, required && _jsx("span", { className: "text-red-500", children: "*" })] })), _jsxs("div", { className: "relative", children: [_jsxs("div", { className: "flex items-center", children: [_jsx("input", { type: "text", id: inputId, value: inputValue, onFocus: () => setIsOpen(true), onKeyDown: handleKeyDown, readOnly: isReadOnly, disabled: disabled, className: `block w-full rounded-md ${bgColor} ${borderColor} ${textColor} shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm pr-10`, placeholder: isMulti ? "Select options..." : "Select an option...", autoComplete: "off", onChange: handleInputChange, onBlur: handleBlur, required: required, "aria-invalid": Boolean(error), "aria-describedby": error ? errorId : hint ? hintId : undefined }), isClearable && (value || valueMulti.length > 0) && (_jsx("button", { type: "button", onClick: handleClear, "aria-label": isMulti
287
+ ? "Limpiar todas las opciones seleccionadas"
288
+ : "Limpiar la opción seleccionada", className: "absolute inset-y-0 right-0 flex cursor-pointer items-center pr-3 text-gray-600 hover:text-gray-900 focus:outline-none focus-visible:ring-2 focus-visible:ring-indigo-500 focus-visible:ring-offset-2", children: _jsx("span", { "aria-hidden": "true", children: "\u00D7" }) })), helpText && (_jsx("div", { className: "flex items-center relative ml-3", children: _jsxs("div", { className: "relative cursor-pointer", onMouseEnter: () => setShowTooltip(true), onMouseLeave: () => setShowTooltip(false), children: [_jsx(IdeaIconSVG, {}), showTooltip && (_jsx("div", { className: "absolute -left-48 top-0 mt-8 p-2 bg-white border rounded shadow text-black z-50", children: helpText }))] }) }))] }), _jsx("input", { type: "hidden", ...(register ?? {}), name: name, value: isMulti
283
289
  ? valueMulti.map((v) => v.value).join(",")
284
290
  : value?.value || "" }), isOpen && !isReadOnly && (_jsx("ul", { ref: optionsRef, className: "absolute z-10 mt-1 w-full bg-white shadow-lg max-h-60 rounded-md py-1 text-base ring-1 ring-black ring-opacity-5 overflow-auto focus:outline-none sm:text-sm", children: filteredOptions.map((option, index) => {
285
291
  const isDisabled = Boolean(option.disabled);
@@ -294,5 +300,5 @@ export const SelectField = (props) => {
294
300
  : index === highlightedIndex
295
301
  ? "cursor-pointer text-black bg-sky-200"
296
302
  : "cursor-pointer text-gray-900"}`, onClick: () => !isDisabled && handleOptionClick(option), onMouseEnter: () => !isDisabled && setHighlightedIndex(index), onMouseLeave: () => setHighlightedIndex(-1), children: [option.label, isDisabled && disabledLabel && (_jsxs("span", { className: "ml-2 text-xs text-gray-400", children: ["(", disabledLabel, ")"] }))] }, `${option.value}-${index}`));
297
- }) }))] }), isMulti && valueMulti.length > 0 && (_jsx("div", { className: "mt-2 flex flex-wrap gap-2", children: valueMulti.map((option, index) => (_jsxs("span", { className: "bg-blue-100 text-blue-800 text-xs font-semibold mr-2 px-2.5 py-0.5 rounded", children: [option.label, _jsx("button", { type: "button", onClick: () => handleRemoveMultiValue(option), className: "ml-1 text-blue-600 hover:text-blue-800", children: "\u00D7" })] }, `${option.value}-${index}`))) })), error && (_jsxs("p", { className: `mt-2 text-sm text-${color}-600 dark:text-${color}-500`, children: [_jsx("span", { className: "font-medium", children: "Oops!" }), " ", error?.message?.toString() || "Error desconocido"] })), !error && hint && (_jsx("p", { className: `mt-2 italic text-sm text-${color}-500 dark:text-${color}-400`, children: hint }))] }));
303
+ }) }))] }), isMulti && valueMulti.length > 0 && (_jsx("ul", { className: "mt-2 flex max-h-20 flex-wrap gap-1.5 overflow-auto overscroll-contain rounded-md border border-blue-100 bg-blue-50/40 p-1.5", "aria-label": "Opciones seleccionadas", children: valueMulti.map((option, index) => (_jsxs("li", { className: "inline-flex max-w-full shrink-0 items-center rounded bg-blue-100 py-0.5 pl-2.5 pr-0.5 text-xs font-semibold text-blue-800", children: [_jsx("span", { className: "whitespace-nowrap", children: option.label }), _jsx("button", { type: "button", onClick: () => handleRemoveMultiValue(option), "aria-label": `Quitar ${option.label}`, className: "ml-1 inline-flex h-6 w-6 shrink-0 cursor-pointer items-center justify-center rounded text-blue-700 hover:bg-blue-200 hover:text-blue-900 focus:outline-none focus-visible:ring-2 focus-visible:ring-indigo-500 focus-visible:ring-offset-1", children: _jsx("span", { "aria-hidden": "true", children: "\u00D7" }) })] }, `${option.value}-${index}`))) })), error && (_jsxs("p", { id: errorId, className: `mt-2 text-sm text-${color}-600 dark:text-${color}-500`, children: [_jsx("span", { className: "font-medium", children: "Oops!" }), " ", error?.message?.toString() || "Error desconocido"] })), !error && hint && (_jsx("p", { id: hintId, className: `mt-2 italic text-sm text-${color}-500 dark:text-${color}-400`, children: hint }))] }));
298
304
  };
@@ -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-opacity-40 focus:outline-none`, 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))) })] }) }) }));
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
  };
@@ -22,7 +22,7 @@ function GroupRows({ group, roles, sectionKeys, disabled, pendingCells, onToggle
22
22
  const granted = role.permissionKeys.includes(action.key);
23
23
  const cellKey = permissionMatrixCellKey(role.id, action.key);
24
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));
25
+ return (_jsx("td", { className: "border-b border-l border-gray-100 px-3 py-2 text-center", children: _jsx("input", { type: "checkbox", id: `permission-${role.id}-${action.key}`, name: `permission-${role.id}-${action.key}`, className: checkboxClass, checked: granted, disabled: disabled || pending, "aria-label": `${role.name}: ${action.label}`, onChange: () => onToggle(role.id, action.key, !granted) }) }, role.id));
26
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
27
  const sectionGranted = sectionKeys.every((key) => role.permissionKeys.includes(key));
28
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));
@@ -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
+ };
@@ -1,3 +1,4 @@
1
1
  export * from "./Tab.js";
2
2
  export * from "./RouteTabNav.js";
3
3
  export * from "./PermissionMatrix.js";
4
+ export * from "./WebappSettingsChrome.js";
package/dist/esm/index.js CHANGED
@@ -1,4 +1,5 @@
1
1
  export * from "./Alerts/index.js";
2
+ export * from "./AccessControl/index.js";
2
3
  export * from "./BlockUI/index.js";
3
4
  export * from "./Buttons/index.js";
4
5
  export * from "./Card/index.js";
package/dist/index.d.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  export * from "./Alerts/index.js";
2
+ export * from "./AccessControl/index.js";
2
3
  export * from "./BlockUI/index.js";
3
4
  export * from "./Buttons/index.js";
4
5
  export * from "./Card/index.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zauru-sdk/components",
3
- "version": "6.9.0",
3
+ "version": "6.11.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.4",
48
- "@zauru-sdk/hooks": "^6.3.17",
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.15",
51
- "@zauru-sdk/types": "^6.22.0",
52
- "@zauru-sdk/utils": "^6.21.0",
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": "800600a8cdb22f1c49293fbc3a48d26aa03771c9"
65
+ "gitHead": "885c1d44afb4c5717ae823664b37ac48b30283db"
66
66
  }