@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.
- package/.ladle/components.tsx +30 -0
- package/.ladle/config.mjs +9 -0
- package/.ladle/vite.config.ts +23 -0
- package/CHANGELOG.md +22 -0
- package/PLAYGROUND.md +59 -0
- package/dist/Alerts/StaticAlert.d.ts +1 -1
- package/dist/Buttons/ActionList.d.ts +17 -0
- package/dist/Buttons/Button.d.ts +1 -1
- package/dist/Buttons/index.d.ts +1 -0
- package/dist/Containers/ButtonSectionContainer.d.ts +2 -0
- package/dist/Containers/Container.d.ts +3 -1
- package/dist/DynamicTable/GenericDynamicTable.d.ts +2 -0
- package/dist/Form/FileUpload/index.d.ts +1 -0
- package/dist/Form/FormLayout/index.d.ts +11 -0
- package/dist/Form/RadioButtonGroup/index.d.ts +1 -0
- package/dist/Form/SelectField/index.d.ts +4 -1
- package/dist/Form/index.d.ts +2 -0
- package/dist/Labels/SearchFiltersSummary.d.ts +12 -0
- package/dist/Labels/index.d.ts +1 -0
- package/dist/Layouts/errorLayout/index.d.ts +4 -1
- package/dist/NavBar/NavBar.d.ts +1 -1
- package/dist/NavBar/NavBar.types.d.ts +1 -0
- package/dist/Table/ZauruTable.d.ts +5 -0
- package/dist/Tooltip/Tooltip.d.ts +7 -0
- package/dist/Tooltip/index.d.ts +1 -0
- package/dist/WithTooltip/WithTooltip.d.ts +7 -0
- package/dist/WithTooltip/index.d.ts +1 -0
- package/dist/esm/Alerts/StaticAlert.js +6 -0
- package/dist/esm/BlockUI/BlockUI.js +1 -1
- package/dist/esm/Buttons/ActionList.js +8 -0
- package/dist/esm/Buttons/Button.js +5 -5
- package/dist/esm/Buttons/index.js +1 -0
- package/dist/esm/Containers/ButtonSectionContainer.js +2 -2
- package/dist/esm/Containers/Container.js +7 -2
- package/dist/esm/Containers/OutletContainer.js +1 -1
- package/dist/esm/DynamicTable/GenericDynamicTable.js +6 -3
- package/dist/esm/Footer/Footer.js +1 -1
- package/dist/esm/Form/FileUpload/index.js +2 -2
- package/dist/esm/Form/FormLayout/index.js +7 -0
- package/dist/esm/Form/RadioButtonGroup/index.js +1 -0
- package/dist/esm/Form/SelectField/index.js +16 -6
- package/dist/esm/Form/TextField/index.js +13 -3
- package/dist/esm/Form/index.js +2 -0
- package/dist/esm/Labels/SearchFiltersSummary.js +7 -0
- package/dist/esm/Labels/index.js +1 -0
- package/dist/esm/Layouts/errorLayout/index.js +9 -3
- package/dist/esm/Modal/ItemModal.js +7 -4
- package/dist/esm/NavBar/NavBar.js +8 -3
- package/dist/esm/Table/ZauruTable.js +67 -8
- package/dist/esm/Tooltip/Tooltip.js +14 -0
- package/dist/esm/Tooltip/index.js +1 -0
- package/dist/esm/WithTooltip/WithTooltip.js +5 -0
- package/dist/esm/WithTooltip/index.js +1 -0
- package/dist/esm/index.js +2 -0
- package/dist/index.d.ts +2 -0
- package/package.json +11 -4
- package/postcss.config.cjs +6 -0
- package/src/Alerts/StaticAlert.tsx +11 -1
- package/src/BlockUI/BlockUI.tsx +1 -1
- package/src/Buttons/ActionList.tsx +56 -0
- package/src/Buttons/Button.tsx +15 -9
- package/src/Buttons/index.ts +1 -0
- package/src/Containers/ButtonSectionContainer.tsx +4 -2
- package/src/Containers/Container.tsx +43 -13
- package/src/Containers/OutletContainer.tsx +1 -1
- package/src/DynamicTable/GenericDynamicTable.tsx +26 -9
- package/src/Footer/Footer.tsx +2 -1
- package/src/Form/FileUpload/index.tsx +3 -0
- package/src/Form/FormLayout/index.tsx +35 -0
- package/src/Form/RadioButtonGroup/index.tsx +4 -0
- package/src/Form/SelectField/index.tsx +44 -23
- package/src/Form/TextField/index.tsx +14 -1
- package/src/Form/index.ts +2 -0
- package/src/Labels/SearchFiltersSummary.tsx +34 -0
- package/src/Labels/index.tsx +1 -0
- package/src/Layouts/errorLayout/index.tsx +11 -2
- package/src/Modal/ItemModal.tsx +18 -5
- package/src/NavBar/NavBar.tsx +20 -2
- package/src/NavBar/NavBar.types.ts +1 -0
- package/src/Table/ZauruTable.tsx +83 -7
- package/src/Tooltip/Tooltip.tsx +42 -0
- package/src/Tooltip/index.ts +1 -0
- package/src/WithTooltip/WithTooltip.tsx +22 -0
- package/src/WithTooltip/index.tsx +1 -0
- package/src/index.ts +2 -0
- package/stories/Alerts.stories.tsx +17 -0
- package/stories/Button.stories.tsx +52 -0
- package/stories/Card.stories.tsx +7 -0
- package/stories/Container.stories.tsx +24 -0
- package/stories/Form.stories.tsx +58 -0
- package/stories/Labels.stories.tsx +17 -0
- package/stories/Modal.stories.tsx +21 -0
- package/stories/Tab.stories.tsx +10 -0
- package/stories/Table.stories.tsx +37 -0
- package/stories/Titles.stories.tsx +11 -0
- package/stories/Tooltip.stories.tsx +15 -0
- package/tailwind.config.cjs +12 -0
- package/tsconfig.json +1 -0
|
@@ -1,15 +1,25 @@
|
|
|
1
1
|
import React from "react";
|
|
2
2
|
|
|
3
3
|
type Props = {
|
|
4
|
-
title?:
|
|
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 {
|
|
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
|
-
|
|
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
|
-
{
|
|
29
|
-
<
|
|
30
|
-
<
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
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
|
-
<
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
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
|
package/src/Footer/Footer.tsx
CHANGED
|
@@ -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>
|
|
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
|
+
};
|
|
@@ -17,7 +17,9 @@ type Props = {
|
|
|
17
17
|
defaultValue?: SelectFieldOption;
|
|
18
18
|
defaultValueMulti?: SelectFieldOption[];
|
|
19
19
|
helpText?: string;
|
|
20
|
-
options: Array<
|
|
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
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
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
|
+
};
|
package/src/Labels/index.tsx
CHANGED
|
@@ -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>
|
package/src/Modal/ItemModal.tsx
CHANGED
|
@@ -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(
|
|
97
|
-
item
|
|
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
|
|
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
|
×
|
|
@@ -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">
|
package/src/NavBar/NavBar.tsx
CHANGED
|
@@ -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}`}>
|