@zauru-sdk/components 6.2.28 → 6.3.1

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 (98) hide show
  1. package/.ladle/components.tsx +30 -0
  2. package/.ladle/config.mjs +9 -0
  3. package/.ladle/vite.config.ts +23 -0
  4. package/CHANGELOG.md +22 -0
  5. package/PLAYGROUND.md +59 -0
  6. package/dist/Alerts/StaticAlert.d.ts +1 -1
  7. package/dist/Buttons/ActionList.d.ts +17 -0
  8. package/dist/Buttons/Button.d.ts +1 -1
  9. package/dist/Buttons/index.d.ts +1 -0
  10. package/dist/Containers/ButtonSectionContainer.d.ts +2 -0
  11. package/dist/Containers/Container.d.ts +3 -1
  12. package/dist/DynamicTable/GenericDynamicTable.d.ts +2 -0
  13. package/dist/Form/FileUpload/index.d.ts +1 -0
  14. package/dist/Form/FormLayout/index.d.ts +11 -0
  15. package/dist/Form/RadioButtonGroup/index.d.ts +1 -0
  16. package/dist/Form/SelectField/index.d.ts +4 -1
  17. package/dist/Form/index.d.ts +2 -0
  18. package/dist/Labels/SearchFiltersSummary.d.ts +12 -0
  19. package/dist/Labels/index.d.ts +1 -0
  20. package/dist/Layouts/errorLayout/index.d.ts +4 -1
  21. package/dist/NavBar/NavBar.d.ts +1 -1
  22. package/dist/NavBar/NavBar.types.d.ts +1 -0
  23. package/dist/Table/ZauruTable.d.ts +5 -0
  24. package/dist/Tooltip/Tooltip.d.ts +7 -0
  25. package/dist/Tooltip/index.d.ts +1 -0
  26. package/dist/WithTooltip/WithTooltip.d.ts +7 -0
  27. package/dist/WithTooltip/index.d.ts +1 -0
  28. package/dist/esm/Alerts/StaticAlert.js +6 -0
  29. package/dist/esm/BlockUI/BlockUI.js +1 -1
  30. package/dist/esm/Buttons/ActionList.js +8 -0
  31. package/dist/esm/Buttons/Button.js +5 -5
  32. package/dist/esm/Buttons/index.js +1 -0
  33. package/dist/esm/Containers/ButtonSectionContainer.js +2 -2
  34. package/dist/esm/Containers/Container.js +7 -2
  35. package/dist/esm/Containers/OutletContainer.js +1 -1
  36. package/dist/esm/DynamicTable/GenericDynamicTable.js +6 -3
  37. package/dist/esm/Footer/Footer.js +1 -1
  38. package/dist/esm/Form/FileUpload/index.js +2 -2
  39. package/dist/esm/Form/FormLayout/index.js +7 -0
  40. package/dist/esm/Form/RadioButtonGroup/index.js +1 -0
  41. package/dist/esm/Form/SelectField/index.js +16 -6
  42. package/dist/esm/Form/TextField/index.js +13 -3
  43. package/dist/esm/Form/index.js +2 -0
  44. package/dist/esm/Labels/SearchFiltersSummary.js +7 -0
  45. package/dist/esm/Labels/index.js +1 -0
  46. package/dist/esm/Layouts/errorLayout/index.js +9 -3
  47. package/dist/esm/Modal/ItemModal.js +7 -4
  48. package/dist/esm/NavBar/NavBar.js +8 -3
  49. package/dist/esm/Table/ZauruTable.js +67 -8
  50. package/dist/esm/Tooltip/Tooltip.js +14 -0
  51. package/dist/esm/Tooltip/index.js +1 -0
  52. package/dist/esm/WithTooltip/WithTooltip.js +5 -0
  53. package/dist/esm/WithTooltip/index.js +1 -0
  54. package/dist/esm/index.js +2 -0
  55. package/dist/index.d.ts +2 -0
  56. package/package.json +11 -4
  57. package/postcss.config.cjs +6 -0
  58. package/src/Alerts/StaticAlert.tsx +11 -1
  59. package/src/BlockUI/BlockUI.tsx +1 -1
  60. package/src/Buttons/ActionList.tsx +56 -0
  61. package/src/Buttons/Button.tsx +15 -9
  62. package/src/Buttons/index.ts +1 -0
  63. package/src/Containers/ButtonSectionContainer.tsx +4 -2
  64. package/src/Containers/Container.tsx +43 -13
  65. package/src/Containers/OutletContainer.tsx +1 -1
  66. package/src/DynamicTable/GenericDynamicTable.tsx +26 -9
  67. package/src/Footer/Footer.tsx +2 -1
  68. package/src/Form/FileUpload/index.tsx +3 -0
  69. package/src/Form/FormLayout/index.tsx +35 -0
  70. package/src/Form/RadioButtonGroup/index.tsx +4 -0
  71. package/src/Form/SelectField/index.tsx +44 -23
  72. package/src/Form/TextField/index.tsx +14 -1
  73. package/src/Form/index.ts +2 -0
  74. package/src/Labels/SearchFiltersSummary.tsx +34 -0
  75. package/src/Labels/index.tsx +1 -0
  76. package/src/Layouts/errorLayout/index.tsx +11 -2
  77. package/src/Modal/ItemModal.tsx +18 -5
  78. package/src/NavBar/NavBar.tsx +20 -2
  79. package/src/NavBar/NavBar.types.ts +1 -0
  80. package/src/Table/ZauruTable.tsx +83 -7
  81. package/src/Tooltip/Tooltip.tsx +42 -0
  82. package/src/Tooltip/index.ts +1 -0
  83. package/src/WithTooltip/WithTooltip.tsx +22 -0
  84. package/src/WithTooltip/index.tsx +1 -0
  85. package/src/index.ts +2 -0
  86. package/stories/Alerts.stories.tsx +17 -0
  87. package/stories/Button.stories.tsx +52 -0
  88. package/stories/Card.stories.tsx +7 -0
  89. package/stories/Container.stories.tsx +24 -0
  90. package/stories/Form.stories.tsx +58 -0
  91. package/stories/Labels.stories.tsx +17 -0
  92. package/stories/Modal.stories.tsx +21 -0
  93. package/stories/Tab.stories.tsx +10 -0
  94. package/stories/Table.stories.tsx +37 -0
  95. package/stories/Titles.stories.tsx +11 -0
  96. package/stories/Tooltip.stories.tsx +15 -0
  97. package/tailwind.config.cjs +12 -0
  98. package/tsconfig.json +1 -0
@@ -1,15 +1,25 @@
1
1
  import React from "react";
2
2
 
3
3
  type Props = {
4
- title?: string;
4
+ title?: React.ReactNode;
5
5
  description?: string;
6
6
  children: React.ReactNode;
7
7
  className?: string;
8
8
  rightContent?: React.ReactNode;
9
+ collapsible?: boolean;
10
+ defaultOpen?: boolean;
9
11
  };
10
12
 
11
13
  export const Container = (props: Props) => {
12
- const { title, description, children, className = "", rightContent } = props;
14
+ const {
15
+ title,
16
+ description,
17
+ children,
18
+ className = "",
19
+ rightContent,
20
+ collapsible = false,
21
+ defaultOpen = true,
22
+ } = props;
13
23
 
14
24
  const titleInfo = (
15
25
  <>
@@ -22,18 +32,38 @@ export const Container = (props: Props) => {
22
32
  </>
23
33
  );
24
34
 
25
- return (
26
- <>
35
+ const header = rightContent ? (
36
+ <div className="flex flex-col sm:flex-row sm:justify-between sm:items-start gap-4">
37
+ <div className="max-w-3xl">{titleInfo}</div>
38
+ <div className="flex-shrink-0">{rightContent}</div>
39
+ </div>
40
+ ) : (
41
+ titleInfo
42
+ );
43
+
44
+ const body = <div className="mt-5 space-y-5">{children}</div>;
45
+
46
+ if (collapsible) {
47
+ return (
27
48
  <div className={`mx-2 ${className}`}>
28
- {rightContent && (
29
- <div className="flex flex-col sm:flex-row sm:justify-between sm:items-start gap-4">
30
- <div className="max-w-3xl">{titleInfo}</div>
31
- <div className="flex-shrink-0">{rightContent}</div>
32
- </div>
33
- )}
34
- {!rightContent && titleInfo}
35
- <div className="mt-5 space-y-5">{children}</div>
49
+ <details className="group" {...(defaultOpen ? { open: true } : {})}>
50
+ <summary className="flex cursor-pointer list-none items-start gap-3 rounded-md py-1 focus:outline-none focus-visible:ring-2 focus-visible:ring-gray-400 focus-visible:ring-offset-2 [&::-webkit-details-marker]:hidden">
51
+ <span
52
+ aria-hidden="true"
53
+ className="mt-2 inline-block h-0 w-0 shrink-0 border-y-[5px] border-l-[7px] border-y-transparent border-l-gray-700 transition-transform group-open:rotate-90"
54
+ />
55
+ <div className="min-w-0 flex-1">{header}</div>
56
+ </summary>
57
+ {body}
58
+ </details>
36
59
  </div>
37
- </>
60
+ );
61
+ }
62
+
63
+ return (
64
+ <div className={`mx-2 ${className}`}>
65
+ {header}
66
+ {body}
67
+ </div>
38
68
  );
39
69
  };
@@ -7,7 +7,7 @@ type Props = {
7
7
  export const OutletContainer = (props: Props) => {
8
8
  const { children } = props;
9
9
  return (
10
- <div className="flex-1 overflow-x-auto" suppressHydrationWarning>
10
+ <div className="min-w-0 flex-1 overflow-x-auto" suppressHydrationWarning>
11
11
  {children}
12
12
  </div>
13
13
  );
@@ -59,6 +59,8 @@ type Props = {
59
59
  * es decir, sin permitir ningún tipo de interacción de edición o eliminación.
60
60
  */
61
61
  readOnly?: boolean;
62
+ /** Contenido extra debajo de cada fila (p. ej. un resumen). */
63
+ rowSummary?: (rowData: RowDataType) => React.ReactNode;
62
64
  };
63
65
 
64
66
  const GenericDynamicTableErrorComponent = ({ name }: { name: string }) => {
@@ -178,6 +180,7 @@ export const GenericDynamicTable = (props: Props) => {
178
180
  maxRows,
179
181
  confirmDelete = true,
180
182
  addRowButtonHandler,
183
+ rowSummary,
181
184
  } = props;
182
185
 
183
186
  /**
@@ -300,16 +303,30 @@ export const GenericDynamicTable = (props: Props) => {
300
303
  );
301
304
 
302
305
  if (orientation === "horizontal") {
306
+ const rowClassName =
307
+ index % 2 === 0 ? `${withoutBg ? "" : "bg-gray-200"}` : "";
308
+ const summaryColSpan =
309
+ rendereableColumns.length + (isEditable ? 1 : 0);
310
+ const summaryContent = rowSummary?.(rowData);
311
+
303
312
  return (
304
- <tr
305
- key={rowData.id}
306
- className={
307
- index % 2 === 0 ? `${withoutBg ? "" : "bg-gray-200"}` : ""
308
- }
309
- >
310
- {rendereableColumns.map((column) => renderCell(rowData, column))}
311
- {isEditable && renderDeleteButton(rowData)}
312
- </tr>
313
+ <React.Fragment key={rowData.id}>
314
+ <tr className={rowClassName}>
315
+ {rendereableColumns.map((column) => renderCell(rowData, column))}
316
+ {isEditable && renderDeleteButton(rowData)}
317
+ </tr>
318
+ {summaryContent ? (
319
+ <tr className={rowClassName}>
320
+ <td
321
+ colSpan={summaryColSpan}
322
+ className="px-2 pb-3 pt-0 text-sm text-gray-700 border-b border-gray-300"
323
+ >
324
+ <span className="font-medium text-gray-500">Resumen: </span>
325
+ {summaryContent}
326
+ </td>
327
+ </tr>
328
+ ) : null}
329
+ </React.Fragment>
313
330
  );
314
331
  } else {
315
332
  // Orientación vertical
@@ -40,7 +40,8 @@ export const Footer = ({
40
40
  <footer className={`inset-x-0 bottom-0 px-2 py-[20px] ${color}`}>
41
41
  <div className="px-4 mx-auto flex flex-wrap items-center justify-center">
42
42
  <p className="text-white text-[1.2rem]">
43
- {`Creado en `} <a href={href}>Zauru</a> {`con ❤️ v.${version}`}
43
+ {`Creado en `} <a href={href}>Zauru</a>{" "}
44
+ {`con ❤️ ${new Date().getFullYear()} v.${version}`}
44
45
  </p>
45
46
  {showConnection && (
46
47
  <div className="ml-5">
@@ -22,6 +22,7 @@ type Props = {
22
22
  zauruBaseURL?: string;
23
23
  setProcessing?: (processing: boolean) => void;
24
24
  signature?: boolean;
25
+ capture?: "user" | "environment";
25
26
  };
26
27
 
27
28
  export const FileUploadField = (props: Props) => {
@@ -43,6 +44,7 @@ export const FileUploadField = (props: Props) => {
43
44
  zauruBaseURL = "https://zauru.herokuapp.com",
44
45
  setProcessing,
45
46
  signature = false,
47
+ capture,
46
48
  } = props;
47
49
 
48
50
  const {
@@ -572,6 +574,7 @@ export const FileUploadField = (props: Props) => {
572
574
  type="file"
573
575
  id={id ?? name}
574
576
  accept={fileTypes.map((ft) => `.${ft}`).join(", ")}
577
+ capture={capture}
575
578
  className={`block w-full rounded-md ${bgColor} ${borderColor} ${textColor} shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm`}
576
579
  {...(register ?? {})}
577
580
  ref={fileInputRef}
@@ -0,0 +1,35 @@
1
+ import { Form, type FormMethod } from "@remix-run/react";
2
+ import { type ReactNode } from "react";
3
+ import { ButtonSectionContainer } from "../../Containers/index.js";
4
+
5
+ type Props = {
6
+ formId: string;
7
+ title?: string;
8
+ children: ReactNode;
9
+ buttons?: ReactNode;
10
+ method?: FormMethod;
11
+ };
12
+
13
+ export const FormLayout = (props: Props) => {
14
+ const { title, children, buttons, method, formId } = props;
15
+
16
+ return (
17
+ <Form id={formId} name={formId} method={method ?? "post"} key={formId}>
18
+ {title && (
19
+ <label className="block text-sm font-medium text-gray-700 mb-3">
20
+ {title}
21
+ </label>
22
+ )}
23
+ <div className="shadow sm:overflow-hidden sm:rounded-md">
24
+ <div className="space-y-6 bg-white px-4 py-5 sm:p-6">
25
+ {children}
26
+ </div>
27
+ {buttons && (
28
+ <ButtonSectionContainer whitBg>
29
+ {buttons}
30
+ </ButtonSectionContainer>
31
+ )}
32
+ </div>
33
+ </Form>
34
+ );
35
+ };
@@ -0,0 +1,4 @@
1
+ export {
2
+ RadioButton as RadioButtonGroup,
3
+ RadioButton as RadioButtonGroupWithoutValidation,
4
+ } from "../RadioButton/index.js";
@@ -17,7 +17,9 @@ type Props = {
17
17
  defaultValue?: SelectFieldOption;
18
18
  defaultValueMulti?: SelectFieldOption[];
19
19
  helpText?: string;
20
- options: Array<SelectFieldOption>;
20
+ options: Array<
21
+ SelectFieldOption & { disabled?: boolean; disabledLabel?: string }
22
+ >;
21
23
  onChange?: (value: SelectFieldOption | null) => void;
22
24
  onChangeMulti?: (value: SelectFieldOption[]) => void;
23
25
  onInputChange?: (newValue: string) => void;
@@ -236,7 +238,10 @@ export const SelectField = (props: Props) => {
236
238
  );
237
239
 
238
240
  const handleOptionClick = useCallback(
239
- (option: SelectFieldOption) => {
241
+ (option: SelectFieldOption & { disabled?: boolean }) => {
242
+ if (option.disabled) {
243
+ return;
244
+ }
240
245
  if (isMulti) {
241
246
  handleAddMultiValue(option);
242
247
  } else {
@@ -441,27 +446,43 @@ export const SelectField = (props: Props) => {
441
446
  ref={optionsRef}
442
447
  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"
443
448
  >
444
- {filteredOptions.map((option, index) => (
445
- <li
446
- key={`${option.value}-${index}`}
447
- className={`cursor-pointer select-none relative py-2 pl-3 pr-9 ${
448
- (
449
- isMulti
450
- ? valueMulti.some((v) => v.value === option.value)
451
- : value?.value === option.value
452
- )
453
- ? "text-white bg-indigo-600"
454
- : index === highlightedIndex
455
- ? "text-black bg-sky-200"
456
- : "text-gray-900"
457
- }`}
458
- onClick={() => handleOptionClick(option)}
459
- onMouseEnter={() => setHighlightedIndex(index)}
460
- onMouseLeave={() => setHighlightedIndex(-1)}
461
- >
462
- {option.label}
463
- </li>
464
- ))}
449
+ {filteredOptions.map((option, index) => {
450
+ const isDisabled = Boolean(
451
+ (option as SelectFieldOption & { disabled?: boolean }).disabled
452
+ );
453
+ const disabledLabel = (
454
+ option as SelectFieldOption & { disabledLabel?: string }
455
+ ).disabledLabel;
456
+ const isSelected = isMulti
457
+ ? valueMulti.some((v) => v.value === option.value)
458
+ : value?.value === option.value;
459
+ return (
460
+ <li
461
+ key={`${option.value}-${index}`}
462
+ className={`select-none relative py-2 pl-3 pr-9 ${
463
+ isDisabled
464
+ ? "cursor-not-allowed text-gray-400 bg-gray-50"
465
+ : isSelected
466
+ ? "cursor-pointer text-white bg-indigo-600"
467
+ : index === highlightedIndex
468
+ ? "cursor-pointer text-black bg-sky-200"
469
+ : "cursor-pointer text-gray-900"
470
+ }`}
471
+ onClick={() => !isDisabled && handleOptionClick(option)}
472
+ onMouseEnter={() =>
473
+ !isDisabled && setHighlightedIndex(index)
474
+ }
475
+ onMouseLeave={() => setHighlightedIndex(-1)}
476
+ >
477
+ {option.label}
478
+ {isDisabled && disabledLabel && (
479
+ <span className="ml-2 text-xs text-gray-400">
480
+ ({disabledLabel})
481
+ </span>
482
+ )}
483
+ </li>
484
+ );
485
+ })}
465
486
  </ul>
466
487
  )}
467
488
  </div>
@@ -1,5 +1,5 @@
1
1
  import { IdeaIconSVG } from "@zauru-sdk/icons";
2
- import React, { useEffect, useState } from "react";
2
+ import React, { useEffect, useRef, useState } from "react";
3
3
  import { useFormContext } from "react-hook-form";
4
4
 
5
5
  type Props = {
@@ -52,6 +52,7 @@ export const TextField = (props: Props) => {
52
52
 
53
53
  const [showTooltip, setShowTooltip] = useState<boolean>(false);
54
54
  const [value, setValue] = useState(defaultValue);
55
+ const inputRef = useRef<HTMLInputElement | null>(null);
55
56
  const {
56
57
  register: tempRegister,
57
58
  formState: { errors },
@@ -65,6 +66,13 @@ export const TextField = (props: Props) => {
65
66
  })
66
67
  : undefined; // Solo usar register si está disponible
67
68
 
69
+ const assignRef = (element: HTMLInputElement | null) => {
70
+ inputRef.current = element;
71
+ if (typeof register?.ref === "function") {
72
+ register.ref(element);
73
+ }
74
+ };
75
+
68
76
  const color = error ? "red" : "gray";
69
77
 
70
78
  const isReadOnly = disabled || readOnly;
@@ -73,6 +81,9 @@ export const TextField = (props: Props) => {
73
81
  const borderColor = isReadOnly ? "border-gray-300" : `border-${color}-200`;
74
82
 
75
83
  useEffect(() => {
84
+ if (inputRef.current && document.activeElement === inputRef.current) {
85
+ return;
86
+ }
76
87
  if (setOnFormValue) {
77
88
  setOnFormValue(name ?? "-1", defaultValue);
78
89
  }
@@ -119,6 +130,7 @@ export const TextField = (props: Props) => {
119
130
  value={value}
120
131
  hidden
121
132
  {...(register ?? {})}
133
+ ref={assignRef}
122
134
  name={name}
123
135
  onChange={handleInputChange}
124
136
  />
@@ -144,6 +156,7 @@ export const TextField = (props: Props) => {
144
156
  style={style}
145
157
  className={`block w-full rounded-md ${bgColor} ${borderColor} ${textColor} shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm`}
146
158
  {...(register ?? {})}
159
+ ref={assignRef}
147
160
  name={name}
148
161
  autoComplete={autoComplete}
149
162
  onChange={handleInputChange}
package/src/Form/index.ts CHANGED
@@ -5,6 +5,7 @@ export * from "./DynamicBaculoForm/index.js";
5
5
  export * from "./FieldContainer/index.js";
6
6
  export * from "./FileUpload/index.js";
7
7
  export * from "./FormButtons/index.js";
8
+ export * from "./FormLayout/index.js";
8
9
  export * from "./SelectField/index.js";
9
10
  export * from "./TextArea/index.js";
10
11
  export * from "./TextField/index.js";
@@ -12,3 +13,4 @@ export * from "./TimePicker/index.js";
12
13
  export * from "./YesNo/index.js";
13
14
  export * from "./ReactZodForm/index.js";
14
15
  export * from "./RadioButton/index.js";
16
+ export * from "./RadioButtonGroup/index.js";
@@ -0,0 +1,34 @@
1
+ export type SearchFilterItem = {
2
+ key: string;
3
+ label: string;
4
+ value: string;
5
+ };
6
+
7
+ type Props = {
8
+ items?: SearchFilterItem[];
9
+ emptyText?: string;
10
+ className?: string;
11
+ };
12
+
13
+ export const SearchFiltersSummary = ({
14
+ items,
15
+ emptyText = "Sin filtros registrados",
16
+ className = "",
17
+ }: Props) => {
18
+ if (!items?.length) {
19
+ return (
20
+ <p className={`text-xs text-gray-400 ${className}`}>{emptyText}</p>
21
+ );
22
+ }
23
+
24
+ return (
25
+ <dl className={`flex flex-col gap-1 text-xs text-gray-700 ${className}`}>
26
+ {items.map((item) => (
27
+ <div key={item.key} className="flex flex-wrap items-baseline gap-x-1">
28
+ <dt className="font-semibold text-gray-500">{item.label}:</dt>
29
+ <dd className="text-gray-800">{item.value}</dd>
30
+ </div>
31
+ ))}
32
+ </dl>
33
+ );
34
+ };
@@ -1 +1,2 @@
1
1
  export * from "./InfoLabel/index.js";
2
+ export * from "./SearchFiltersSummary.js";
@@ -6,21 +6,30 @@ import {
6
6
  useRouteError,
7
7
  Link,
8
8
  } from "@remix-run/react";
9
- import { useState } from "react";
9
+ import { useEffect, useState } from "react";
10
10
 
11
11
  export const ErrorLayout = ({
12
12
  from,
13
13
  isRootLevel = true,
14
14
  error: parentError,
15
+ onError,
15
16
  }: {
16
17
  from?: string;
17
18
  isRootLevel?: boolean;
18
19
  error?: Error;
20
+ onError?: (error: Error, meta: { from?: string }) => void;
19
21
  }) => {
20
22
  try {
21
23
  const error = useRouteError();
22
24
  const [showDetails, setShowDetails] = useState(!!parentError);
23
25
 
26
+ useEffect(() => {
27
+ const reportable = error instanceof Error ? error : parentError;
28
+ if (reportable instanceof Error) {
29
+ onError?.(reportable, { from });
30
+ }
31
+ }, [error, parentError, from, onError]);
32
+
24
33
  const baseError = (
25
34
  <div className="min-h-screen flex flex-col items-center justify-center p-4">
26
35
  <img src="/logo.png" alt="Zauru Logo" className="mb-8 h-20" />
@@ -49,7 +58,7 @@ export const ErrorLayout = ({
49
58
  <div className="mb-4 text-center">
50
59
  <button
51
60
  onClick={() => setShowDetails(!showDetails)}
52
- className="text-blue-400 hover:text-blue-300 transition duration-300"
61
+ className="text-blue-400 hover:text-blue-300 transition duration-300 cursor-pointer"
53
62
  >
54
63
  {showDetails ? "Ocultar detalles" : "Ver más detalles"}
55
64
  </button>
@@ -93,8 +93,10 @@ const ItemSelectionModal: React.FC<ItemModalProps> = ({
93
93
  const filteredList = itemList
94
94
  .map((category) => ({
95
95
  ...category,
96
- items: category.items.filter((item) =>
97
- item.name.toLowerCase().includes(searchTerm.toLowerCase())
96
+ items: category.items.filter(
97
+ (item) =>
98
+ item.name.toLowerCase().includes(searchTerm.toLowerCase()) ||
99
+ item.code.toLowerCase().includes(searchTerm.toLowerCase())
98
100
  ),
99
101
  }))
100
102
  .filter(
@@ -163,10 +165,15 @@ const ItemSelectionModal: React.FC<ItemModalProps> = ({
163
165
  {/* Header */}
164
166
  <div className="flex justify-between items-center mb-4">
165
167
  <h2 className="text-2xl font-bold">
166
- {selectedItem ? "Confirmar selección" : "Seleccionar un Ítem"}
168
+ {selectedItem
169
+ ? "Confirmar selección"
170
+ : `Seleccionar un Ítem - (${itemList.reduce(
171
+ (acc, category) => acc + category.items.length,
172
+ 0
173
+ )} ítems disponibles)`}
167
174
  </h2>
168
175
  <button
169
- className="text-gray-500 hover:text-gray-800 text-3xl"
176
+ className="text-gray-500 hover:text-gray-800 text-3xl cursor-pointer"
170
177
  onClick={handleCloseModal}
171
178
  >
172
179
  &times;
@@ -187,7 +194,7 @@ const ItemSelectionModal: React.FC<ItemModalProps> = ({
187
194
  <div className="relative mb-4">
188
195
  <input
189
196
  type="text"
190
- placeholder="Buscar por nombre o categoría..."
197
+ placeholder="Buscar por nombre, código o categoría..."
191
198
  value={searchTerm}
192
199
  onChange={(e) => setSearchTerm(e.target.value)}
193
200
  className="p-2 border rounded-lg w-full"
@@ -323,6 +330,12 @@ const ItemSelectionModal: React.FC<ItemModalProps> = ({
323
330
  className="w-40 h-40 object-cover rounded mb-4 shadow-md"
324
331
  />
325
332
  <p className="mb-2 text-xl font-bold">{selectedItem.name}</p>
333
+ <p className="mb-2 text-base font-semibold text-gray-700">
334
+ Código:
335
+ <span className="ml-1 font-normal text-gray-800">
336
+ {selectedItem.code}
337
+ </span>
338
+ </p>
326
339
  <p className="mb-2 text-base font-semibold text-gray-700">
327
340
  Stock disponible:
328
341
  <span className="ml-1 font-normal text-gray-800">
@@ -24,7 +24,7 @@ const OptionsDropDownButton = ({ color, options, name }: EntityProps) => {
24
24
  <div className="relative inline-block">
25
25
  <button
26
26
  onClick={() => setShowOptionsMenu(!showOptionsMenu)}
27
- className={`relative flex 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`}
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-opacity-40 focus:outline-none`}
28
28
  >
29
29
  {name ?? <OpcionButtonSvgIcon />}
30
30
  <DropDownArrowSvgIcon />
@@ -128,12 +128,18 @@ export const NavBar = ({
128
128
  selectedColor,
129
129
  version,
130
130
  reloadCatalogsOption,
131
+ showDarkModeToggle = false,
131
132
  }: NavBarProps) => {
132
133
  const color: ColorInterface = COLORS[selectedColor];
133
134
  const [NavBarOpen, setNavBarOpen] = useState(false);
134
135
  const [currentVersion, setCurrentVersion] = useState(version);
136
+ const [isDarkMode, setIsDarkMode] = useState(false);
135
137
  const navigate = useNavigate();
136
138
 
139
+ useEffect(() => {
140
+ document.documentElement.classList.toggle("dark", isDarkMode);
141
+ }, [isDarkMode]);
142
+
137
143
  useEffect(() => {
138
144
  if (version !== currentVersion) {
139
145
  setCurrentVersion(version);
@@ -213,6 +219,18 @@ export const NavBar = ({
213
219
  </div>
214
220
  </Link>
215
221
  ),
222
+ showDarkModeToggle && (
223
+ <button
224
+ key="dark-mode-toggle"
225
+ type="button"
226
+ 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"
227
+ onClick={() => setIsDarkMode((value) => !value)}
228
+ >
229
+ <div className="mx-auto pt-2">
230
+ <span>{isDarkMode ? "Modo claro" : "Modo oscuro"}</span>
231
+ </div>
232
+ </button>
233
+ ),
216
234
  <Link
217
235
  key="cerrar-sesion"
218
236
  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`}
@@ -229,7 +247,7 @@ export const NavBar = ({
229
247
  </ul>
230
248
  </>
231
249
  );
232
- }, [items, loggedIn, hiddenItemsChange]);
250
+ }, [items, loggedIn, hiddenItemsChange, showDarkModeToggle, isDarkMode]);
233
251
 
234
252
  return (
235
253
  <nav className={`py-3 ${color.bg600}`}>
@@ -32,6 +32,7 @@ export type NavBarProps = {
32
32
  LinkComponent?: any;
33
33
  version?: string;
34
34
  reloadCatalogsOption?: boolean;
35
+ showDarkModeToggle?: boolean;
35
36
  };
36
37
 
37
38
  export type NavBarStateProps = {