cemtrik-dependencies 1.0.4

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 (92) hide show
  1. package/README copy.md +54 -0
  2. package/README.md +29 -0
  3. package/components.js +47 -0
  4. package/hooks.js +4 -0
  5. package/package.json +58 -0
  6. package/postcss.config.js +6 -0
  7. package/src/assets/cemtrik-icons-v1.0/Read Me.txt +7 -0
  8. package/src/assets/cemtrik-icons-v1.0/demo-files/demo.css +152 -0
  9. package/src/assets/cemtrik-icons-v1.0/demo-files/demo.js +30 -0
  10. package/src/assets/cemtrik-icons-v1.0/demo.html +318 -0
  11. package/src/assets/cemtrik-icons-v1.0/fonts/cemtrik-icons.eot +0 -0
  12. package/src/assets/cemtrik-icons-v1.0/fonts/cemtrik-icons.svg +31 -0
  13. package/src/assets/cemtrik-icons-v1.0/fonts/cemtrik-icons.ttf +0 -0
  14. package/src/assets/cemtrik-icons-v1.0/fonts/cemtrik-icons.woff +0 -0
  15. package/src/assets/cemtrik-icons-v1.0/selection.json +1 -0
  16. package/src/assets/cemtrik-icons-v1.0/style.css +96 -0
  17. package/src/assets/icons/actions.svg +4 -0
  18. package/src/assets/icons/attributes.svg +5 -0
  19. package/src/assets/icons/bars.svg +3 -0
  20. package/src/assets/icons/bell.svg +4 -0
  21. package/src/assets/icons/cemtrik.svg +9 -0
  22. package/src/assets/icons/clipboardCheck.svg +4 -0
  23. package/src/assets/icons/clipboardList.svg +5 -0
  24. package/src/assets/icons/clock.svg +3 -0
  25. package/src/assets/icons/flag.svg +3 -0
  26. package/src/assets/icons/folder.svg +3 -0
  27. package/src/assets/icons/graph_indicators.svg +3 -0
  28. package/src/assets/icons/group.svg +3 -0
  29. package/src/assets/icons/letter.svg +3 -0
  30. package/src/assets/icons/measuring_points.svg +3 -0
  31. package/src/assets/icons/reports.svg +4 -0
  32. package/src/assets/icons/roboot.svg +3 -0
  33. package/src/assets/icons/user.svg +4 -0
  34. package/src/assets/icons/user_point.svg +10 -0
  35. package/src/assets/icons/vector.svg +3 -0
  36. package/src/assets/icons/vector2.svg +3 -0
  37. package/src/components/atoms/Alert/index.js +104 -0
  38. package/src/components/atoms/Alert/index.test.js +36 -0
  39. package/src/components/atoms/Avatar/index.js +63 -0
  40. package/src/components/atoms/Avatar/index.test.js +30 -0
  41. package/src/components/atoms/Bullets/Bullets.test.js +22 -0
  42. package/src/components/atoms/Bullets/index.js +51 -0
  43. package/src/components/atoms/ButtonOutline/index.js +43 -0
  44. package/src/components/atoms/ButtonOutline/index.test.js +36 -0
  45. package/src/components/atoms/ButtonPagination/index.js +37 -0
  46. package/src/components/atoms/ButtonPagination/index.test.js +36 -0
  47. package/src/components/atoms/ButtonSolid/index.js +74 -0
  48. package/src/components/atoms/ButtonSolid/index.test.js +59 -0
  49. package/src/components/atoms/Checkbox/index.js +52 -0
  50. package/src/components/atoms/Checkbox/index.test.js +15 -0
  51. package/src/components/atoms/ConfirmationAbandoningCreation/index.js +70 -0
  52. package/src/components/atoms/ConfirmationAbandoningCreation/index.test.js +70 -0
  53. package/src/components/atoms/Divider/index.js +10 -0
  54. package/src/components/atoms/Divider/index.test.js +12 -0
  55. package/src/components/atoms/GoBack/index.js +30 -0
  56. package/src/components/atoms/GoBack/index.test.js +24 -0
  57. package/src/components/atoms/Input/index.js +107 -0
  58. package/src/components/atoms/Input/index.test.js +63 -0
  59. package/src/components/atoms/InputDropdown/index.js +111 -0
  60. package/src/components/atoms/InputDropdown/index.test.js +86 -0
  61. package/src/components/atoms/Select/index.js +199 -0
  62. package/src/components/atoms/Select/index.test.js +86 -0
  63. package/src/components/atoms/Spinner/index.js +49 -0
  64. package/src/components/atoms/Spinner/index.test.js +9 -0
  65. package/src/components/atoms/Switch/index.js +46 -0
  66. package/src/components/atoms/Switch/index.test.js +18 -0
  67. package/src/components/atoms/Textarea/index.js +136 -0
  68. package/src/components/atoms/Textarea/index.spec.js +51 -0
  69. package/src/components/atoms/Tooltip/index.js +64 -0
  70. package/src/components/atoms/Tooltip/index.test.js +31 -0
  71. package/src/components/atoms/UploadImage/index.js +55 -0
  72. package/src/components/atoms/UploadImage/index.test.js +36 -0
  73. package/src/components/molecules/Dropdown/index.js +315 -0
  74. package/src/components/molecules/Dropdown/index.test.js +190 -0
  75. package/src/components/molecules/Modal/index.js +103 -0
  76. package/src/components/molecules/Modal/index.test.js +42 -0
  77. package/src/components/molecules/Pagination/index.js +126 -0
  78. package/src/components/molecules/Pagination/index.test.js +57 -0
  79. package/src/components/templates/Accordion/index.js +174 -0
  80. package/src/components/templates/Accordion/index.test.js +130 -0
  81. package/src/hooks/useCloseModal/index.js +27 -0
  82. package/src/hooks/useCloseModal/index.test.js +26 -0
  83. package/src/hooks/useForm/index.js +70 -0
  84. package/src/hooks/useForm/useForm.test.js +104 -0
  85. package/src/hooks/useMediaQuery/index.js +53 -0
  86. package/src/hooks/useMediaQuery/useMediaQuery.test.js +46 -0
  87. package/src/hooks/useWindowDimensions/index.js +34 -0
  88. package/src/hooks/useWindowDimensions/useWindowDimensions.test.js +19 -0
  89. package/src/utils/index.js +32 -0
  90. package/src/utils/index.test.js +56 -0
  91. package/tailwind.config.js +117 -0
  92. package/utils.js +8 -0
@@ -0,0 +1,126 @@
1
+ // 1.- libraries
2
+ import { useState, useEffect } from "react";
3
+
4
+ // 2.- components
5
+ import ButtonPagination from "../../atoms/ButtonPagination";
6
+
7
+ // 3.- icons
8
+ import { ArrowLongLeftIcon, ArrowLongRightIcon } from "@heroicons/react/20/solid";
9
+
10
+ // 4.- utils
11
+ import { classNames } from "../../../utils";
12
+
13
+ export const rowsPerPage = 50;
14
+
15
+ const Pagination = ({ total, handleNext, handlePrev, handleSelectPage, page = null }) => {
16
+ const [currentPage, setCurrentPage] = useState(1);
17
+ const [arrOfCurrButtons, setArrOfCurrButtons] = useState([]);
18
+ const [numOfButtons, setNumOfButtons] = useState([]);
19
+
20
+ const numOfPages = Math.ceil(total / rowsPerPage);
21
+
22
+ useEffect(() => {
23
+ const arr = [];
24
+ for (let i = 1; i <= numOfPages; i++) arr.push(i);
25
+ setNumOfButtons(arr);
26
+ }, [numOfPages]);
27
+
28
+ useEffect(() => {
29
+ let tempNumberOfButtons = [];
30
+ const dotsInitial = "...";
31
+ const dotsLeft = "... ";
32
+ const dotsRight = " ...";
33
+ if (numOfButtons.length < 6) {
34
+ tempNumberOfButtons = numOfButtons;
35
+ } else if (currentPage >= 1 && currentPage <= 3) {
36
+ tempNumberOfButtons = [1, 2, 3, 4, dotsInitial, numOfButtons.length];
37
+ } else if (currentPage === 4) {
38
+ const sliced = numOfButtons.slice(0, 5);
39
+ tempNumberOfButtons = [...sliced, dotsInitial, numOfButtons.length];
40
+ } else if (currentPage > 4 && currentPage < numOfButtons.length - 2) {
41
+ const sliced1 = numOfButtons.slice(currentPage - 2, currentPage);
42
+ const sliced2 = numOfButtons.slice(currentPage, currentPage + 1);
43
+ tempNumberOfButtons = [
44
+ 1,
45
+ dotsLeft,
46
+ ...sliced1,
47
+ ...sliced2,
48
+ dotsRight,
49
+ numOfButtons.length,
50
+ ];
51
+ } else if (currentPage > numOfButtons.length - 3) {
52
+ const sliced = numOfButtons.slice(numOfButtons.length - 4);
53
+ tempNumberOfButtons = [1, dotsLeft, ...sliced];
54
+ }
55
+ setArrOfCurrButtons(tempNumberOfButtons);
56
+ }, [currentPage, numOfButtons]);
57
+
58
+ useEffect(() => {
59
+ if (page === null) return;
60
+ page !== currentPage && setCurrentPage(page);
61
+ }, [page, currentPage]);
62
+
63
+ const selectPage = (data) => {
64
+ setCurrentPage(data);
65
+ handleSelectPage(data);
66
+ };
67
+
68
+ const prev = () => {
69
+ currentPage === 1 ? setCurrentPage(currentPage) : setCurrentPage(currentPage - 1);
70
+ currentPage > 1 && handlePrev(currentPage - 1);
71
+ };
72
+
73
+ const next = () => {
74
+ currentPage === numOfButtons.length
75
+ ? setCurrentPage(currentPage)
76
+ : setCurrentPage(currentPage + 1);
77
+ currentPage < numOfButtons.length && handleNext(currentPage + 1);
78
+ };
79
+
80
+ return (
81
+ <nav className="flex items-center justify-between border-t border-gray-200 px-4 sm:px-0">
82
+ <div className="-mt-px flex w-0 flex-1">
83
+ <span
84
+ className="inline-flex items-center border-t-2 border-transparent pr-1 pt-4 text-sm font-medium text-gray-500 hover:border-gray-300 hover:text-gray-700 cursor-pointer"
85
+ onClick={prev}
86
+ data-testid="prev"
87
+ >
88
+ <ArrowLongLeftIcon className="mr-3 h-5 w-5 text-gray-400" aria-hidden="true" />
89
+ Anterior
90
+ </span>
91
+ </div>
92
+ <div className="hidden md:-mt-px md:flex">
93
+ {arrOfCurrButtons.map((data, index) => (
94
+ <ButtonPagination
95
+ key={index}
96
+ className={classNames(
97
+ "inline-flex items-center border-t-2 px-4 pt-4 text-sm font-medium",
98
+ currentPage === data
99
+ ? "border-indigo-500 text-indigo-600"
100
+ : "border-transparent text-gray-500 hover:border-gray-300 hover:text-gray-700",
101
+ )}
102
+ aria-current="page"
103
+ onClick={() =>
104
+ data !== "..." && data !== " ..." && data !== "... " && selectPage(data)
105
+ }
106
+ id={`page-${index + 1}`}
107
+ >
108
+ {data}
109
+ </ButtonPagination>
110
+ ))}
111
+ </div>
112
+ <div className="-mt-px flex w-0 flex-1 justify-end">
113
+ <span
114
+ className="inline-flex items-center border-t-2 border-transparent pl-1 pt-4 text-sm font-medium text-gray-500 hover:border-gray-300 hover:text-gray-700 cursor-pointer"
115
+ onClick={next}
116
+ data-testid="next"
117
+ >
118
+ Siguiente
119
+ <ArrowLongRightIcon className="ml-3 h-5 w-5 text-gray-400" aria-hidden="true" />
120
+ </span>
121
+ </div>
122
+ </nav>
123
+ );
124
+ };
125
+
126
+ export default Pagination;
@@ -0,0 +1,57 @@
1
+ import { render, fireEvent } from "@testing-library/react";
2
+ import Pagination, { rowsPerPage } from "./";
3
+
4
+ describe("Pagination", () => {
5
+ afterEach(() => {
6
+ jest.resetAllMocks();
7
+ });
8
+
9
+ it("selectPage", () => {
10
+ const handleSelectPage = jest.fn();
11
+ const component = render(
12
+ <Pagination
13
+ total={rowsPerPage}
14
+ handleNext={() => {}}
15
+ handlePrev={() => {}}
16
+ handleSelectPage={handleSelectPage}
17
+ page={2}
18
+ />,
19
+ );
20
+ const page = component.getByTestId("button-page-1");
21
+ fireEvent.click(page);
22
+ expect(handleSelectPage).toHaveBeenCalledWith(1);
23
+ });
24
+
25
+ it("prev", () => {
26
+ const handlePrev = jest.fn();
27
+ const component = render(
28
+ <Pagination total={1150} handlePrev={handlePrev} handleSelectPage={() => {}} />,
29
+ );
30
+ const page3 = component.getByTestId("button-page-3");
31
+ const prev = component.getByTestId("prev");
32
+
33
+ fireEvent.click(prev);
34
+ fireEvent.click(page3);
35
+ fireEvent.click(prev);
36
+ expect(handlePrev).toHaveBeenCalledWith(2);
37
+ });
38
+
39
+ it("next", () => {
40
+ const handleNext = jest.fn();
41
+ const component = render(
42
+ <Pagination total={450} handleNext={handleNext} handleSelectPage={() => {}} />,
43
+ );
44
+ const page4 = component.getByTestId("button-page-4");
45
+ const page6 = component.getByTestId("button-page-6");
46
+ const next = component.getByTestId("next");
47
+ fireEvent.click(page4);
48
+ fireEvent.click(next);
49
+ fireEvent.click(next);
50
+ fireEvent.click(next);
51
+ fireEvent.click(page6);
52
+ fireEvent.click(next);
53
+ expect(handleNext).toHaveBeenNthCalledWith(1, 5);
54
+ expect(handleNext).toHaveBeenNthCalledWith(2, 6);
55
+ expect(handleNext).toHaveBeenNthCalledWith(3, 7);
56
+ });
57
+ });
@@ -0,0 +1,174 @@
1
+ // 1.- libraries
2
+ import { useState, useEffect, Fragment } from "react";
3
+ import { Transition } from "@headlessui/react";
4
+
5
+ // 2.- components
6
+ import Input from "../../atoms/Input";
7
+
8
+ // 3.- icons
9
+ import {
10
+ TrashIcon,
11
+ ChevronDownIcon,
12
+ ChevronUpIcon,
13
+ MagnifyingGlassIcon,
14
+ } from "@heroicons/react/24/outline";
15
+
16
+ // 4.- utils
17
+ import { copyOptions, classNames } from "../../../utils";
18
+
19
+ const Accordion = ({
20
+ openDefault = false,
21
+ setForm,
22
+ setValuesDefault,
23
+ title,
24
+ type,
25
+ data,
26
+ setData,
27
+ Component,
28
+ id,
29
+ defaultData,
30
+ dispatch,
31
+ isAdd = true,
32
+ search = null,
33
+ isPadding = true,
34
+ }) => {
35
+ const [open, setOpen] = useState(false);
36
+
37
+ useEffect(() => {
38
+ setOpen(openDefault);
39
+ }, [openDefault]);
40
+
41
+ const handleChange = ({ e, index }) => {
42
+ const copyData = copyOptions(data);
43
+ const { name, value } = e.target;
44
+ copyData[index][name] = value;
45
+ setData(copyData);
46
+ setValuesDefault(id, copyData);
47
+ setForm((state) => ({ ...state, [id]: copyData }));
48
+ };
49
+
50
+ const add = () => {
51
+ const isEmpty = data.some((item) => {
52
+ const items = Object.values(item);
53
+ const isEmptyValues = items.some((v) => v.value === -1);
54
+ return isEmptyValues;
55
+ });
56
+ if (isEmpty || data.length === 0)
57
+ return dispatch({
58
+ type: "ALERT",
59
+ payload: {
60
+ title: "",
61
+ message: "Todos los campos son requeridos",
62
+ typeAlert: "warning",
63
+ active: true,
64
+ },
65
+ });
66
+ setForm((state) => ({ ...state, data }));
67
+ setValuesDefault(id, data);
68
+ setData([...data, defaultData]);
69
+ };
70
+
71
+ const deleteData = (index) => {
72
+ if (index === data.length - 1) return;
73
+ const copyData = copyOptions(data);
74
+ copyData.splice(index, 1);
75
+ setForm((state) => ({ ...state, copyData }));
76
+ setValuesDefault(id, copyData);
77
+ setData(copyData);
78
+ };
79
+
80
+ return (
81
+ <div className="divide-y divide-gray-200 rounded-lg bg-white shadow mt-7">
82
+ <div
83
+ className="px-4 py-3.5 sm:px-6 flex justify-between items-center cursor-pointer"
84
+ onClick={() => setOpen(!open)}
85
+ data-testid="open-accordion"
86
+ >
87
+ <p className="text-gray-500 uppercase text-xs font-bold">{title}</p>
88
+ <div className="flex items-center">
89
+ {search && (
90
+ <Input
91
+ htmlFor="search"
92
+ id="search"
93
+ name="search"
94
+ type="text"
95
+ autoComplete="search"
96
+ placeholder="Buscar..."
97
+ handleChange={search}
98
+ icon={<MagnifyingGlassIcon className="w-6 h-6 text-indigo-300" />}
99
+ className="h-9"
100
+ classNameContainerInput="w-52 2xs:w-64 max-w-64 mr-8 hidden sm:block"
101
+ value=""
102
+ onClick={(e) => e.stopPropagation()}
103
+ onFocus={() => setOpen(true)}
104
+ />
105
+ )}
106
+ {open ? (
107
+ <ChevronUpIcon className="w-5 h-5 text-default-5" />
108
+ ) : (
109
+ <ChevronDownIcon className="w-5 h-5 text-default-5" />
110
+ )}
111
+ </div>
112
+ </div>
113
+ <Transition
114
+ as={Fragment}
115
+ show={open}
116
+ enter="transition duration-[400ms]"
117
+ enterFrom="opacity-0"
118
+ enterTo="opacity-100"
119
+ leave="duration-200 transition ease-in-out"
120
+ leaveFrom="opacity-100"
121
+ leaveTo="opacity-0"
122
+ >
123
+ <div className={classNames("sm:mt-2.5 py-3.5", isPadding && "px-4 sm:p-6")}>
124
+ {data.map((item, index) => (
125
+ <div
126
+ key={index}
127
+ className={classNames(
128
+ "flex flex-col 3xs:flex-row mb-4",
129
+ isPadding && "ml-0 3xs:ml-3",
130
+ )}
131
+ >
132
+ <Component item={item} index={index} handleChange={handleChange} />
133
+ {isAdd && (
134
+ <div
135
+ style={{
136
+ backgroundColor: "#B0B8BF",
137
+ width: "25px",
138
+ height: "25px",
139
+ }}
140
+ className={classNames(
141
+ "rounded-full flex justify-center items-center mt-4 3xs:mt-7 ml-2 3xs:ml-5 self-start 3xs:self-center",
142
+ index === data.length - 1 && "3xs:opacity-0",
143
+ )}
144
+ >
145
+ <TrashIcon
146
+ className={classNames(
147
+ "w-6 h-4",
148
+ index !== data.length - 1 && "cursor-pointer",
149
+ )}
150
+ style={{ color: "#637381" }}
151
+ strokeWidth={2.5}
152
+ onClick={() => deleteData(index)}
153
+ data-testid={`delete-data-${index}`}
154
+ />
155
+ </div>
156
+ )}
157
+ </div>
158
+ ))}
159
+ {isAdd && (
160
+ <p
161
+ className="text-secondary-2 text-sm font-medium ml-3 cursor-pointer"
162
+ onClick={add}
163
+ data-testid="add"
164
+ >
165
+ {`+ Añadir ${type}`}
166
+ </p>
167
+ )}
168
+ </div>
169
+ </Transition>
170
+ </div>
171
+ );
172
+ };
173
+
174
+ export default Accordion;
@@ -0,0 +1,130 @@
1
+ import { render, fireEvent, act } from "@testing-library/react";
2
+ import Accordion from "./";
3
+
4
+ describe("Accordion", () => {
5
+ afterEach(() => {
6
+ jest.resetAllMocks();
7
+ });
8
+
9
+ it("renders Accordion component without errors", async () => {
10
+ let component;
11
+ await act(async () => {
12
+ component = render(
13
+ <Accordion title="title" data={[]} openDefault id={1} dispatch={() => {}} />,
14
+ );
15
+ });
16
+ const add = component.getByTestId("add");
17
+ fireEvent.click(add);
18
+ });
19
+
20
+ it("search", async () => {
21
+ const handleSearch = jest.fn();
22
+ const component = render(<Accordion title="title" search={handleSearch} data={[]} />);
23
+ const open = component.getByTestId("open-accordion");
24
+ const input = component.getByTestId("input-search");
25
+ await act(async () => fireEvent.click(open));
26
+ fireEvent.change(input, { target: { value: "search..." } });
27
+ fireEvent.click(input);
28
+ fireEvent.focus(input);
29
+ expect(handleSearch).toHaveBeenCalled();
30
+ });
31
+
32
+ it("handleChange Component", async () => {
33
+ const MockComponent = ({ item, index, handleChange }) => {
34
+ return (
35
+ <button
36
+ onClick={() =>
37
+ handleChange({
38
+ e: { target: { name: "scope", value: { label: "label-2", id: 2 } } },
39
+ index,
40
+ })
41
+ }
42
+ data-testid="component-mock"
43
+ >
44
+ Click me
45
+ </button>
46
+ );
47
+ };
48
+ const mockSetValuesDefault = jest.fn();
49
+ const mockSetData = jest.fn();
50
+ let component;
51
+ await act(async () => {
52
+ component = render(
53
+ <Accordion
54
+ id={3}
55
+ title="title"
56
+ isPadding
57
+ openDefault
58
+ data={[{ scope: { label: "label", id: 1 } }]}
59
+ Component={MockComponent}
60
+ setValuesDefault={mockSetValuesDefault}
61
+ setForm={(fn) => fn()}
62
+ setData={mockSetData}
63
+ />,
64
+ );
65
+ });
66
+ const button = component.getByTestId("component-mock");
67
+ fireEvent.click(button);
68
+ expect(mockSetValuesDefault).toHaveBeenCalledWith(3, [
69
+ { scope: { id: 2, label: "label-2" } },
70
+ ]);
71
+ expect(mockSetData).toHaveBeenCalledWith([{ scope: { id: 2, label: "label-2" } }]);
72
+ });
73
+
74
+ it("delete data", async () => {
75
+ const mockSetData = jest.fn();
76
+ const mockSetValuesDefault = jest.fn();
77
+ let component;
78
+ await act(async () => {
79
+ component = render(
80
+ <Accordion
81
+ id={4}
82
+ title="title"
83
+ openDefault
84
+ data={[
85
+ { scope: { label: "label", id: 1 } },
86
+ { scope: { label: "label", id: 2 } },
87
+ ]}
88
+ Component={() => {}}
89
+ isAdd
90
+ setValuesDefault={mockSetValuesDefault}
91
+ setForm={(fn) => fn()}
92
+ setData={mockSetData}
93
+ />,
94
+ );
95
+ });
96
+ const buttonDelete = component.getByTestId("delete-data-0");
97
+ const buttonDelete2 = component.getByTestId("delete-data-1");
98
+ fireEvent.click(buttonDelete);
99
+ fireEvent.click(buttonDelete2);
100
+ expect(mockSetValuesDefault).toHaveBeenCalledWith(4, [
101
+ { scope: { id: 2, label: "label" } },
102
+ ]);
103
+ expect(mockSetData).toHaveBeenCalledWith([{ scope: { id: 2, label: "label" } }]);
104
+ });
105
+
106
+ it("add data", async () => {
107
+ const mockSetData = jest.fn();
108
+ const mockSetValuesDefault = jest.fn();
109
+ let component;
110
+ await act(async () => {
111
+ component = render(
112
+ <Accordion
113
+ id={5}
114
+ title="title"
115
+ data={[{ item: { value: 1 } }]}
116
+ type="rol"
117
+ openDefault
118
+ Component={() => {}}
119
+ setValuesDefault={mockSetValuesDefault}
120
+ setForm={(fn) => fn()}
121
+ setData={mockSetData}
122
+ />,
123
+ );
124
+ });
125
+ const add = component.getByTestId("add");
126
+ fireEvent.click(add);
127
+ expect(mockSetValuesDefault).toHaveBeenCalledWith(5, [{ item: { value: 1 } }]);
128
+ expect(mockSetData).toHaveBeenCalledWith([{ item: { value: 1 } }, undefined]);
129
+ });
130
+ });
@@ -0,0 +1,27 @@
1
+ import { useEffect, useCallback } from "react";
2
+
3
+ export const useCloseModal = ({ id, close, isPathsTest = false }) => {
4
+ const closeWindow = useCallback(
5
+ (paths) => {
6
+ const isClose = paths.some((v) => {
7
+ return v.getAttribute === undefined ? false : v.getAttribute("id") === id;
8
+ });
9
+ if (!isClose) close();
10
+ },
11
+ [close, id],
12
+ );
13
+
14
+ useEffect(() => {
15
+ const closeModal = (e) => {
16
+ const paths = e.composedPath();
17
+ if (paths === undefined || isPathsTest) return;
18
+ closeWindow(paths);
19
+ };
20
+
21
+ window.addEventListener("click", closeModal);
22
+
23
+ return () => {
24
+ window.removeEventListener("click", closeModal);
25
+ };
26
+ }, [closeWindow, isPathsTest]);
27
+ };
@@ -0,0 +1,26 @@
1
+ import { renderHook, act } from "@testing-library/react";
2
+ import { useCloseModal } from "./";
3
+
4
+ describe("useCloseModal", () => {
5
+ it("close", () => {
6
+ const closeMock = jest.fn();
7
+ renderHook(useCloseModal, {
8
+ initialProps: { id: "", close: closeMock },
9
+ });
10
+ act(() => {
11
+ window.dispatchEvent(new MouseEvent("click", { composedPath: undefined }));
12
+ });
13
+ expect(closeMock.mock.calls.length).toBe(1);
14
+ });
15
+
16
+ it("paths undefined", () => {
17
+ const closeMock = jest.fn();
18
+ renderHook(useCloseModal, {
19
+ initialProps: { id: "", close: closeMock, isPathsTest: true },
20
+ });
21
+ act(() => {
22
+ window.dispatchEvent(new Event("click"));
23
+ });
24
+ expect(closeMock.mock.calls.length).toBe(0);
25
+ });
26
+ });
@@ -0,0 +1,70 @@
1
+ import { useState, useCallback } from "react";
2
+
3
+ export const useForm = () => {
4
+ const [model, setModel] = useState({});
5
+ const [messageError, setMessageError] = useState({});
6
+ const [errors, setErrors] = useState([]);
7
+
8
+ const handleSubmit = (onSubmit) => (e) => {
9
+ e.preventDefault();
10
+ e.stopPropagation();
11
+ onSubmit(model);
12
+ };
13
+
14
+ const handleChange = (e, setForm, form) => {
15
+ const { checked, value, name, type, id } = e.target;
16
+ const dict = {
17
+ radio: id,
18
+ checkbox: checked,
19
+ };
20
+ setModel({
21
+ ...model,
22
+ [name]: dict[type] ?? value,
23
+ });
24
+ setForm({
25
+ ...form,
26
+ [name]: dict[type] ?? value,
27
+ });
28
+ };
29
+
30
+ const setValuesDefault = useCallback((name, value) => {
31
+ setModel((obj) => ({ ...obj, [name]: value }));
32
+ }, []);
33
+
34
+ const validateFields = useCallback((obj, fieldRequerids) => {
35
+ const fields = fieldRequerids.filter(
36
+ (field) => obj[field] === "" || obj[field] === undefined,
37
+ );
38
+ fields.forEach((v) =>
39
+ setMessageError((state) => ({
40
+ ...state,
41
+ [v]: "Este campo es requerido",
42
+ })),
43
+ );
44
+
45
+ setErrors(fields);
46
+ return fields.length > 0;
47
+ }, []);
48
+
49
+ const handleError = (message, fields) => {
50
+ fields.forEach((v) =>
51
+ setMessageError((state) => ({
52
+ ...state,
53
+ [v]: message,
54
+ })),
55
+ );
56
+ setErrors(fields);
57
+ };
58
+
59
+ return {
60
+ handleSubmit,
61
+ handleChange,
62
+ setValuesDefault,
63
+ validateFields,
64
+ errors,
65
+ model,
66
+ handleError,
67
+ messageError,
68
+ setModel,
69
+ };
70
+ };