next-recomponents 1.0.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/dist/index.d.mts +122 -0
- package/dist/index.d.ts +122 -0
- package/dist/index.js +50922 -0
- package/dist/index.mjs +50909 -0
- package/package.json +18 -0
- package/src/alert/index.tsx +24 -0
- package/src/button/colors.tsx +9 -0
- package/src/button/index.tsx +42 -0
- package/src/container/icons.tsx +65 -0
- package/src/container/index.tsx +175 -0
- package/src/form/index.tsx +95 -0
- package/src/index.tsx +10 -0
- package/src/input/index.tsx +43 -0
- package/src/regular-expresions/index.ts +11 -0
- package/src/select/close.tsx +15 -0
- package/src/select/icon.tsx +15 -0
- package/src/select/index.tsx +212 -0
- package/src/table/export.tsx +29 -0
- package/src/table/filter.menu.tsx +188 -0
- package/src/table/filters.tsx +77 -0
- package/src/table/h.tsx +185 -0
- package/src/table/index.tsx +55 -0
- package/src/table/td.tsx +75 -0
- package/src/text-area/index.tsx +52 -0
- package/src/use-resources/encode.decode.tsx +25 -0
- package/src/use-resources/functions.ts +0 -0
- package/src/use-resources/get.token.tsx +15 -0
- package/src/use-resources/http.codes.ts +77 -0
- package/src/use-resources/index.ts +189 -0
- package/src/use-resources/types.ts +41 -0
- package/tsconfig.json +15 -0
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
import React, {
|
|
2
|
+
ReactElement,
|
|
3
|
+
ReactNode,
|
|
4
|
+
useEffect,
|
|
5
|
+
useMemo,
|
|
6
|
+
useRef,
|
|
7
|
+
useState,
|
|
8
|
+
} from "react";
|
|
9
|
+
import SelectIcon from "./icon";
|
|
10
|
+
import CloseIcon from "./close";
|
|
11
|
+
|
|
12
|
+
interface CustomSelectProps
|
|
13
|
+
extends React.DetailedHTMLProps<
|
|
14
|
+
React.InputHTMLAttributes<HTMLInputElement>,
|
|
15
|
+
HTMLInputElement
|
|
16
|
+
> {
|
|
17
|
+
label?: string;
|
|
18
|
+
placeholder?: string;
|
|
19
|
+
children: ReactNode;
|
|
20
|
+
name: string;
|
|
21
|
+
value?: string;
|
|
22
|
+
onChange?: any;
|
|
23
|
+
className?: string;
|
|
24
|
+
strictMode?: boolean;
|
|
25
|
+
}
|
|
26
|
+
export default function Select({
|
|
27
|
+
label,
|
|
28
|
+
placeholder = "Selecciona...",
|
|
29
|
+
children,
|
|
30
|
+
strictMode = true,
|
|
31
|
+
...props
|
|
32
|
+
}: CustomSelectProps) {
|
|
33
|
+
const [isOpen, setIsOpen] = useState(false);
|
|
34
|
+
const [inputValue, setInputValue] = useState(props?.value || "");
|
|
35
|
+
const [options, setOptions] = useState<{ value: string; label: string }[]>(
|
|
36
|
+
[]
|
|
37
|
+
);
|
|
38
|
+
const [filtered, setFiltered] = useState<{ value: string; label: string }[]>(
|
|
39
|
+
[]
|
|
40
|
+
);
|
|
41
|
+
const [highlightedIndex, setHighlightedIndex] = useState(-1);
|
|
42
|
+
const inputRef = useRef<HTMLInputElement>(null);
|
|
43
|
+
const validOption = (value: string) => {
|
|
44
|
+
return options.find((opt) => opt.label == value);
|
|
45
|
+
};
|
|
46
|
+
useEffect(() => {
|
|
47
|
+
const parsedOptions = React.Children.toArray(children)
|
|
48
|
+
.filter((child): child is ReactElement => {
|
|
49
|
+
return (child as ReactElement).type === "option";
|
|
50
|
+
})
|
|
51
|
+
.map((child) => {
|
|
52
|
+
const el = child as ReactElement;
|
|
53
|
+
return {
|
|
54
|
+
value: el.props.value,
|
|
55
|
+
label: el.props.children,
|
|
56
|
+
};
|
|
57
|
+
});
|
|
58
|
+
setOptions(parsedOptions);
|
|
59
|
+
setFiltered(parsedOptions);
|
|
60
|
+
}, [children]);
|
|
61
|
+
|
|
62
|
+
useEffect(() => {
|
|
63
|
+
setFiltered(
|
|
64
|
+
options.filter((opt) =>
|
|
65
|
+
opt?.label?.toLowerCase()?.includes(inputValue?.toLowerCase())
|
|
66
|
+
)
|
|
67
|
+
);
|
|
68
|
+
setHighlightedIndex(-1);
|
|
69
|
+
}, [inputValue, options]);
|
|
70
|
+
|
|
71
|
+
const handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
|
|
72
|
+
if (e.key === "ArrowDown") {
|
|
73
|
+
e.preventDefault();
|
|
74
|
+
setHighlightedIndex((prev) => Math.min(prev + 1, filtered.length - 1));
|
|
75
|
+
} else if (e.key === "ArrowUp") {
|
|
76
|
+
e.preventDefault();
|
|
77
|
+
setHighlightedIndex((prev) => Math.max(prev - 1, 0));
|
|
78
|
+
} else if (e.key === "Enter" && highlightedIndex >= 0) {
|
|
79
|
+
e.preventDefault();
|
|
80
|
+
selectOption(filtered[highlightedIndex]);
|
|
81
|
+
} else if (e.key === "Escape") {
|
|
82
|
+
setIsOpen(false);
|
|
83
|
+
}
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
const selectOption = (opt: { value: string; label: string }) => {
|
|
87
|
+
setInputValue(opt.label);
|
|
88
|
+
setIsOpen(false);
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
const containerRef = useRef<HTMLDivElement>(null);
|
|
92
|
+
const [openUpwards, setOpenUpwards] = useState(false);
|
|
93
|
+
useEffect(() => {
|
|
94
|
+
if (isOpen && containerRef.current) {
|
|
95
|
+
const rect = containerRef.current.getBoundingClientRect();
|
|
96
|
+
const spaceBelow = window.innerHeight - rect.bottom;
|
|
97
|
+
const spaceAbove = rect.top;
|
|
98
|
+
|
|
99
|
+
// Si hay menos de 200px abajo y más espacio arriba, abrir hacia arriba
|
|
100
|
+
if (spaceBelow < 200 && spaceAbove > spaceBelow) {
|
|
101
|
+
setOpenUpwards(true);
|
|
102
|
+
} else {
|
|
103
|
+
setOpenUpwards(false);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}, [isOpen]);
|
|
107
|
+
return (
|
|
108
|
+
<div ref={containerRef} className="w-full">
|
|
109
|
+
{label && <label className="font-bold mb-1 block">{label}</label>}
|
|
110
|
+
<div className="relative">
|
|
111
|
+
<input
|
|
112
|
+
autoComplete="off"
|
|
113
|
+
ref={inputRef}
|
|
114
|
+
{...props}
|
|
115
|
+
className={[
|
|
116
|
+
"p-2 w-full rounded border shadow",
|
|
117
|
+
props?.className,
|
|
118
|
+
].join(" ")}
|
|
119
|
+
value={inputValue}
|
|
120
|
+
onBlur={(e) => {
|
|
121
|
+
const item = validOption(e.target.value);
|
|
122
|
+
|
|
123
|
+
if (!item) {
|
|
124
|
+
if (strictMode) {
|
|
125
|
+
e.target.value = "";
|
|
126
|
+
setInputValue("");
|
|
127
|
+
e.currentTarget.classList.remove("bg-green-200");
|
|
128
|
+
e.currentTarget.classList.add("bg-red-200");
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
} else {
|
|
132
|
+
e.target.value = item?.value || item.label;
|
|
133
|
+
e.currentTarget.classList.add("bg-green-200");
|
|
134
|
+
e.currentTarget.classList.remove("bg-red-200");
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
props.onChange(e);
|
|
138
|
+
}}
|
|
139
|
+
onChange={(e) => {
|
|
140
|
+
setInputValue(e.target.value);
|
|
141
|
+
props.onChange?.(e);
|
|
142
|
+
}}
|
|
143
|
+
placeholder={placeholder}
|
|
144
|
+
onFocus={() => setIsOpen(true)}
|
|
145
|
+
onKeyDown={handleKeyDown}
|
|
146
|
+
/>
|
|
147
|
+
{!isOpen && (
|
|
148
|
+
<div className="absolute top-0 right-0 flex flex-col justify-center items-center px-2 py-2 font-bold">
|
|
149
|
+
<SelectIcon />
|
|
150
|
+
</div>
|
|
151
|
+
)}
|
|
152
|
+
{isOpen && inputValue != "" && (
|
|
153
|
+
<button
|
|
154
|
+
onClick={(e) => {
|
|
155
|
+
setIsOpen(true);
|
|
156
|
+
setInputValue("");
|
|
157
|
+
if (inputRef?.current && strictMode) {
|
|
158
|
+
inputRef.current.classList.remove("bg-green-200");
|
|
159
|
+
inputRef.current.classList.add("bg-red-200");
|
|
160
|
+
}
|
|
161
|
+
}}
|
|
162
|
+
className="absolute top-0 right-0 flex flex-col justify-center items-center px-2 py-2 font-bold text-red-500"
|
|
163
|
+
>
|
|
164
|
+
<CloseIcon />
|
|
165
|
+
</button>
|
|
166
|
+
)}
|
|
167
|
+
{isOpen && filtered.length > 0 && (
|
|
168
|
+
<div
|
|
169
|
+
className={`absolute w-full border rounded shadow bg-white z-10 max-h-60 overflow-y-auto ${
|
|
170
|
+
openUpwards ? "bottom-full mb-1" : "mt-1"
|
|
171
|
+
}`}
|
|
172
|
+
>
|
|
173
|
+
{filtered.map((opt, index) => (
|
|
174
|
+
<div
|
|
175
|
+
key={index}
|
|
176
|
+
className={`p-2 cursor-pointer ${
|
|
177
|
+
index === highlightedIndex
|
|
178
|
+
? "bg-blue-100"
|
|
179
|
+
: "hover:bg-gray-100"
|
|
180
|
+
}`}
|
|
181
|
+
onMouseDown={() => {
|
|
182
|
+
props.onChange({
|
|
183
|
+
target: { value: opt?.value || opt.label },
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
const item = validOption(opt.label);
|
|
187
|
+
if (inputRef?.current) {
|
|
188
|
+
if (!item) {
|
|
189
|
+
if (strictMode) {
|
|
190
|
+
inputRef.current.value = "";
|
|
191
|
+
inputRef.current.classList.remove("bg-green-200");
|
|
192
|
+
inputRef.current.classList.add("bg-red-200");
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
} else {
|
|
196
|
+
inputRef.current.classList.add("bg-green-200");
|
|
197
|
+
inputRef.current.classList.remove("bg-red-200");
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
selectOption(opt);
|
|
202
|
+
}}
|
|
203
|
+
>
|
|
204
|
+
{opt.label}
|
|
205
|
+
</div>
|
|
206
|
+
))}
|
|
207
|
+
</div>
|
|
208
|
+
)}
|
|
209
|
+
</div>
|
|
210
|
+
</div>
|
|
211
|
+
);
|
|
212
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import * as XLSX from "xlsx";
|
|
2
|
+
import { saveAs } from "file-saver";
|
|
3
|
+
export default function useExportdata() {
|
|
4
|
+
return {
|
|
5
|
+
export: (data: any[], fileName: string = "archivo") => {
|
|
6
|
+
// utils/exportToExcel.ts
|
|
7
|
+
|
|
8
|
+
// Convertir el array a una hoja de cálculo
|
|
9
|
+
const worksheet = XLSX.utils.json_to_sheet(data);
|
|
10
|
+
|
|
11
|
+
// Crear un libro de trabajo (workbook)
|
|
12
|
+
const workbook = XLSX.utils.book_new();
|
|
13
|
+
XLSX.utils.book_append_sheet(workbook, worksheet, "Datos");
|
|
14
|
+
|
|
15
|
+
// Escribir el archivo en formato binario
|
|
16
|
+
const excelBuffer = XLSX.write(workbook, {
|
|
17
|
+
bookType: "xlsx",
|
|
18
|
+
type: "array",
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
// Crear un Blob y descargar el archivo
|
|
22
|
+
const blob = new Blob([excelBuffer], {
|
|
23
|
+
type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
saveAs(blob, `${fileName}.xlsx`);
|
|
27
|
+
},
|
|
28
|
+
};
|
|
29
|
+
}
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
import { Dispatch, useEffect, useState } from "react";
|
|
2
|
+
import Input from "../input";
|
|
3
|
+
import { FilterOffIcon } from "./filters";
|
|
4
|
+
|
|
5
|
+
export default function FilterMenu({
|
|
6
|
+
h,
|
|
7
|
+
mapedData,
|
|
8
|
+
selectedFilter,
|
|
9
|
+
index,
|
|
10
|
+
filters,
|
|
11
|
+
setFilters,
|
|
12
|
+
setSelectedFilter,
|
|
13
|
+
items,
|
|
14
|
+
}: {
|
|
15
|
+
h: string;
|
|
16
|
+
mapedData: any[];
|
|
17
|
+
selectedFilter: number | null;
|
|
18
|
+
index: number;
|
|
19
|
+
filters: any;
|
|
20
|
+
setFilters: Dispatch<any>;
|
|
21
|
+
setSelectedFilter: Dispatch<any>;
|
|
22
|
+
items: any[];
|
|
23
|
+
}) {
|
|
24
|
+
const [visibleFloat, setVisibleFloat] = useState(false);
|
|
25
|
+
const [text, setText] = useState("");
|
|
26
|
+
const [newMapedData, setNewMapedData] = useState<Array<any>>([]);
|
|
27
|
+
|
|
28
|
+
useEffect(() => {
|
|
29
|
+
setNewMapedData(
|
|
30
|
+
[...new Set<string>(mapedData.map((md: any) => md[h].content))].filter(
|
|
31
|
+
(item) => {
|
|
32
|
+
return item && `${item}`.toLowerCase().includes(text.toLowerCase());
|
|
33
|
+
}
|
|
34
|
+
)
|
|
35
|
+
);
|
|
36
|
+
}, [mapedData, text]);
|
|
37
|
+
useEffect(() => {
|
|
38
|
+
setText("");
|
|
39
|
+
}, [selectedFilter]);
|
|
40
|
+
|
|
41
|
+
return (
|
|
42
|
+
selectedFilter == index && (
|
|
43
|
+
<>
|
|
44
|
+
<div
|
|
45
|
+
className="absolute bg-white border rounded min-w-[200px] "
|
|
46
|
+
style={{ zIndex: 9999999 }}
|
|
47
|
+
>
|
|
48
|
+
<div className="flex flex-col items-start text-black">
|
|
49
|
+
{filters[h].length != items.length && (
|
|
50
|
+
<div
|
|
51
|
+
className="flex gap-1 w-full hover:bg-gray-100 p-3 cursor-pointer"
|
|
52
|
+
onClick={(e) => {
|
|
53
|
+
const newFilters = { ...filters };
|
|
54
|
+
newFilters[h] = [...items];
|
|
55
|
+
setFilters(newFilters);
|
|
56
|
+
}}
|
|
57
|
+
>
|
|
58
|
+
<div className="text-red-500">
|
|
59
|
+
<FilterOffIcon />
|
|
60
|
+
</div>{" "}
|
|
61
|
+
Limpiar filtro de "{h}"
|
|
62
|
+
</div>
|
|
63
|
+
)}
|
|
64
|
+
{/* <div
|
|
65
|
+
className="flex w-full items-center flex-row justify-between hover:bg-gray-100 p-3 cursor-pointer"
|
|
66
|
+
onPointerEnter={() => {
|
|
67
|
+
setVisibleFloat(true);
|
|
68
|
+
}}
|
|
69
|
+
onPointerLeave={(e) => {
|
|
70
|
+
setVisibleFloat(false);
|
|
71
|
+
}}
|
|
72
|
+
>
|
|
73
|
+
Filtros de Texto <ArrowIcon />
|
|
74
|
+
{visibleFloat && (
|
|
75
|
+
<div className="absolute -right-[200px] border shadow w-[200px] bg-white flex flex-col items-start">
|
|
76
|
+
<div className="hover:bg-gray-100 w-full text-left p-2">
|
|
77
|
+
Es igual
|
|
78
|
+
</div>
|
|
79
|
+
<div className="hover:bg-gray-100 w-full text-left p-2">
|
|
80
|
+
No es igual
|
|
81
|
+
</div>
|
|
82
|
+
<hr />
|
|
83
|
+
<div className="hover:bg-gray-100 w-full text-left p-2">
|
|
84
|
+
Empieza con
|
|
85
|
+
</div>
|
|
86
|
+
<div className="hover:bg-gray-100 w-full text-left p-2">
|
|
87
|
+
Termina con
|
|
88
|
+
</div>
|
|
89
|
+
<hr />
|
|
90
|
+
<div className="hover:bg-gray-100 w-full text-left p-2">
|
|
91
|
+
Contiene
|
|
92
|
+
</div>
|
|
93
|
+
<div className="hover:bg-gray-100 w-full text-left p-2">
|
|
94
|
+
No contiene
|
|
95
|
+
</div>
|
|
96
|
+
</div>
|
|
97
|
+
)}
|
|
98
|
+
</div> */}
|
|
99
|
+
<div className="flex w-full items-center flex-row justify-between hover:bg-gray-100 p-3 cursor-pointer">
|
|
100
|
+
<Input
|
|
101
|
+
label={null}
|
|
102
|
+
type="search"
|
|
103
|
+
placeholder="Buscar..."
|
|
104
|
+
value={text}
|
|
105
|
+
onChange={(e) => {
|
|
106
|
+
setFilters({ ...filters, [h]: [] });
|
|
107
|
+
setText(e.target.value);
|
|
108
|
+
}}
|
|
109
|
+
/>
|
|
110
|
+
</div>
|
|
111
|
+
<div className="px-2 w-full h-[150px]">
|
|
112
|
+
<div
|
|
113
|
+
style={{ overflow: "auto" }}
|
|
114
|
+
className="border h-full flex gap-2 flex-col p-1"
|
|
115
|
+
>
|
|
116
|
+
<div className="flex gap-2 items-center justify-start ">
|
|
117
|
+
<label>
|
|
118
|
+
<input
|
|
119
|
+
type="checkbox"
|
|
120
|
+
checked={filters[h].length == items.length}
|
|
121
|
+
onChange={(e) => {
|
|
122
|
+
const newFilters = { ...filters };
|
|
123
|
+
if (filters[h].length == items.length) {
|
|
124
|
+
newFilters[h] = [];
|
|
125
|
+
} else {
|
|
126
|
+
newFilters[h] = [...items];
|
|
127
|
+
}
|
|
128
|
+
setFilters(newFilters);
|
|
129
|
+
}}
|
|
130
|
+
/>{" "}
|
|
131
|
+
(Seleccionar todo)
|
|
132
|
+
</label>
|
|
133
|
+
</div>
|
|
134
|
+
{newMapedData.map((item: string, key: number) => {
|
|
135
|
+
const checked = filters[h]?.find((i: any) => i == item);
|
|
136
|
+
return (
|
|
137
|
+
<div
|
|
138
|
+
className="flex gap-2 items-center justify-start"
|
|
139
|
+
key={item}
|
|
140
|
+
>
|
|
141
|
+
<label>
|
|
142
|
+
<input
|
|
143
|
+
type="checkbox"
|
|
144
|
+
checked={!!checked}
|
|
145
|
+
onChange={(e) => {
|
|
146
|
+
const newFilters = { ...filters };
|
|
147
|
+
newFilters[h] = newFilters[h] || [];
|
|
148
|
+
const index = newFilters[h].findIndex(
|
|
149
|
+
(nf: any) => nf == item
|
|
150
|
+
);
|
|
151
|
+
if (index >= 0) {
|
|
152
|
+
newFilters[h].splice(index, 1);
|
|
153
|
+
} else {
|
|
154
|
+
newFilters[h].push(item);
|
|
155
|
+
}
|
|
156
|
+
setFilters(newFilters);
|
|
157
|
+
}}
|
|
158
|
+
/>{" "}
|
|
159
|
+
{item}
|
|
160
|
+
</label>
|
|
161
|
+
</div>
|
|
162
|
+
);
|
|
163
|
+
})}
|
|
164
|
+
</div>
|
|
165
|
+
</div>
|
|
166
|
+
<div className="w-full p-1">
|
|
167
|
+
<div className="flex flex-row gap-2 justify-end w-full ">
|
|
168
|
+
<button
|
|
169
|
+
onClick={(e) => setSelectedFilter(null)}
|
|
170
|
+
className="p-1 border rounded shadow hover:bg-gray-100"
|
|
171
|
+
>
|
|
172
|
+
Aceptar
|
|
173
|
+
</button>
|
|
174
|
+
</div>
|
|
175
|
+
</div>
|
|
176
|
+
</div>
|
|
177
|
+
</div>{" "}
|
|
178
|
+
<div
|
|
179
|
+
className="w-full h-screen fixed top-0 left-0"
|
|
180
|
+
style={{ zIndex: 9999997 }}
|
|
181
|
+
onClick={(e) => {
|
|
182
|
+
setSelectedFilter(null);
|
|
183
|
+
}}
|
|
184
|
+
></div>
|
|
185
|
+
</>
|
|
186
|
+
)
|
|
187
|
+
);
|
|
188
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
export function FilterOffIcon() {
|
|
2
|
+
return (
|
|
3
|
+
<svg
|
|
4
|
+
stroke="currentColor"
|
|
5
|
+
fill="currentColor"
|
|
6
|
+
strokeWidth="0"
|
|
7
|
+
viewBox="0 0 24 24"
|
|
8
|
+
height="20px"
|
|
9
|
+
width="20px"
|
|
10
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
11
|
+
>
|
|
12
|
+
<path d="M6.92893 0.514648L21.0711 14.6568L19.6569 16.071L15.834 12.2486L14 14.9999V21.9999H10V14.9999L4 5.99993H3V3.99993L7.585 3.99965L5.51472 1.92886L6.92893 0.514648ZM21 3.99993V5.99993H20L18.085 8.87193L13.213 3.99993H21Z"></path>
|
|
13
|
+
</svg>
|
|
14
|
+
);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function ExcelIcon() {
|
|
18
|
+
return (
|
|
19
|
+
<svg
|
|
20
|
+
stroke="currentColor"
|
|
21
|
+
fill="currentColor"
|
|
22
|
+
strokeWidth="0"
|
|
23
|
+
viewBox="0 0 24 24"
|
|
24
|
+
height="20px"
|
|
25
|
+
width="20px"
|
|
26
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
27
|
+
>
|
|
28
|
+
<path d="M2.85858 2.87732L15.4293 1.0815C15.7027 1.04245 15.9559 1.2324 15.995 1.50577C15.9983 1.52919 16 1.55282 16 1.57648V22.4235C16 22.6996 15.7761 22.9235 15.5 22.9235C15.4763 22.9235 15.4527 22.9218 15.4293 22.9184L2.85858 21.1226C2.36593 21.0522 2 20.6303 2 20.1327V3.86727C2 3.36962 2.36593 2.9477 2.85858 2.87732ZM17 2.99997H21C21.5523 2.99997 22 3.44769 22 3.99997V20C22 20.5523 21.5523 21 21 21H17V2.99997ZM10.2 12L13 7.99997H10.6L9 10.2857L7.39999 7.99997H5L7.8 12L5 16H7.39999L9 13.7143L10.6 16H13L10.2 12Z"></path>
|
|
29
|
+
</svg>
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
export function EditIcon() {
|
|
33
|
+
return (
|
|
34
|
+
<svg
|
|
35
|
+
stroke="currentColor"
|
|
36
|
+
fill="currentColor"
|
|
37
|
+
strokeWidth="0"
|
|
38
|
+
viewBox="0 0 576 512"
|
|
39
|
+
height="20px"
|
|
40
|
+
width="20px"
|
|
41
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
42
|
+
>
|
|
43
|
+
<path d="M402.6 83.2l90.2 90.2c3.8 3.8 3.8 10 0 13.8L274.4 405.6l-92.8 10.3c-12.4 1.4-22.9-9.1-21.5-21.5l10.3-92.8L388.8 83.2c3.8-3.8 10-3.8 13.8 0zm162-22.9l-48.8-48.8c-15.2-15.2-39.9-15.2-55.2 0l-35.4 35.4c-3.8 3.8-3.8 10 0 13.8l90.2 90.2c3.8 3.8 10 3.8 13.8 0l35.4-35.4c15.2-15.3 15.2-40 0-55.2zM384 346.2V448H64V128h229.8c3.2 0 6.2-1.3 8.5-3.5l40-40c7.6-7.6 2.2-20.5-8.5-20.5H48C21.5 64 0 85.5 0 112v352c0 26.5 21.5 48 48 48h352c26.5 0 48-21.5 48-48V306.2c0-10.7-12.9-16-20.5-8.5l-40 40c-2.2 2.3-3.5 5.3-3.5 8.5z"></path>
|
|
44
|
+
</svg>
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
export function ArrowIcon() {
|
|
48
|
+
return (
|
|
49
|
+
<svg
|
|
50
|
+
stroke="currentColor"
|
|
51
|
+
fill="currentColor"
|
|
52
|
+
strokeWidth="0"
|
|
53
|
+
viewBox="0 0 512 512"
|
|
54
|
+
height="20px"
|
|
55
|
+
width="20px"
|
|
56
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
57
|
+
>
|
|
58
|
+
<path d="M294.1 256L167 129c-9.4-9.4-9.4-24.6 0-33.9s24.6-9.3 34 0L345 239c9.1 9.1 9.3 23.7.7 33.1L201.1 417c-4.7 4.7-10.9 7-17 7s-12.3-2.3-17-7c-9.4-9.4-9.4-24.6 0-33.9l127-127.1z"></path>
|
|
59
|
+
</svg>
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export function FilterIcon() {
|
|
64
|
+
return (
|
|
65
|
+
<svg
|
|
66
|
+
stroke="currentColor"
|
|
67
|
+
fill="currentColor"
|
|
68
|
+
strokeWidth="0"
|
|
69
|
+
viewBox="0 0 24 24"
|
|
70
|
+
height="20px"
|
|
71
|
+
width="20px"
|
|
72
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
73
|
+
>
|
|
74
|
+
<path d="M21 4V6H20L15 13.5V22H9V13.5L4 6H3V4H21ZM6.4037 6L11 12.8944V20H13V12.8944L17.5963 6H6.4037Z"></path>
|
|
75
|
+
</svg>
|
|
76
|
+
);
|
|
77
|
+
}
|
package/src/table/h.tsx
ADDED
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
import { useEffect, useMemo, useRef, useState } from "react";
|
|
2
|
+
import { TableProps } from ".";
|
|
3
|
+
import TD from "./td";
|
|
4
|
+
import { FilterOffIcon } from "./filters";
|
|
5
|
+
import FilterMenu from "./filter.menu";
|
|
6
|
+
|
|
7
|
+
export default function HTable({
|
|
8
|
+
data,
|
|
9
|
+
mapedData,
|
|
10
|
+
setMapedData,
|
|
11
|
+
symbols,
|
|
12
|
+
totals,
|
|
13
|
+
exportName,
|
|
14
|
+
...props
|
|
15
|
+
}: TableProps) {
|
|
16
|
+
const [selected, setSelected] = useState<number | null>(null);
|
|
17
|
+
const [selectedFilter, setSelectedFilter] = useState<number | null>(null);
|
|
18
|
+
const modalRef = useRef<HTMLDialogElement>(null);
|
|
19
|
+
const [filters, setFilters] = useState<any>({});
|
|
20
|
+
const head = useMemo(() => {
|
|
21
|
+
return [...new Set<string>(data.map((d: any) => Object.keys(d)).flat())];
|
|
22
|
+
}, [data]);
|
|
23
|
+
|
|
24
|
+
const mapedTotals = useMemo(() => {
|
|
25
|
+
return mapedData.reduce((acc: any, md: any) => {
|
|
26
|
+
head.forEach((h) => {
|
|
27
|
+
const value = isNaN(+md[h].content) ? 0 : +md[h].content;
|
|
28
|
+
if (acc[h]) {
|
|
29
|
+
acc[h] += value;
|
|
30
|
+
} else {
|
|
31
|
+
acc[h] = value;
|
|
32
|
+
}
|
|
33
|
+
});
|
|
34
|
+
return acc;
|
|
35
|
+
}, {});
|
|
36
|
+
}, [mapedData]);
|
|
37
|
+
|
|
38
|
+
useEffect(() => {
|
|
39
|
+
setFilters({
|
|
40
|
+
...head.reduce((acc, hh) => {
|
|
41
|
+
const newAcc: any = { ...acc };
|
|
42
|
+
newAcc[hh] = [
|
|
43
|
+
...new Set<string>(mapedData.map((md: any) => md[hh].content)),
|
|
44
|
+
];
|
|
45
|
+
return newAcc;
|
|
46
|
+
}, {}),
|
|
47
|
+
});
|
|
48
|
+
}, [mapedData, head]);
|
|
49
|
+
|
|
50
|
+
useEffect(() => {
|
|
51
|
+
const newData = data.map((d: any, trkey: number) => {
|
|
52
|
+
const obj: any = {};
|
|
53
|
+
for (let key in d) {
|
|
54
|
+
const typeOf = typeof d[key];
|
|
55
|
+
const isObject = typeOf == "object";
|
|
56
|
+
const isDate = typeOf == "string" && d[key].includes("T");
|
|
57
|
+
const cellTypeOf = isDate ? "date" : isObject ? "object" : typeOf;
|
|
58
|
+
const content =
|
|
59
|
+
cellTypeOf == "date"
|
|
60
|
+
? d[key].split("T").join(" ").split(".")[0]
|
|
61
|
+
: cellTypeOf == "object"
|
|
62
|
+
? JSON.stringify(d[key])
|
|
63
|
+
: d[key];
|
|
64
|
+
obj[key] = {
|
|
65
|
+
originalData: content,
|
|
66
|
+
cellTypeOf: cellTypeOf,
|
|
67
|
+
title: content,
|
|
68
|
+
content,
|
|
69
|
+
name: key,
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
return obj;
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
setMapedData(newData);
|
|
76
|
+
}, [data]);
|
|
77
|
+
return (
|
|
78
|
+
<table {...props} className="w-full border-collapse table-auto">
|
|
79
|
+
<thead className="bg-gray-800 text-white">
|
|
80
|
+
<tr>
|
|
81
|
+
{head.map((h, key) => {
|
|
82
|
+
const items = [
|
|
83
|
+
...new Set<string>(mapedData.map((item: any) => item[h].content)),
|
|
84
|
+
];
|
|
85
|
+
return (
|
|
86
|
+
<th
|
|
87
|
+
key={key}
|
|
88
|
+
className="whitespace-nowrap overflow-hidden text-ellipsis max-w-[200px] border-b p-5"
|
|
89
|
+
>
|
|
90
|
+
<div
|
|
91
|
+
className="cursor-pointer flex "
|
|
92
|
+
onClick={(e) => {
|
|
93
|
+
setSelectedFilter(key == selectedFilter ? null : key);
|
|
94
|
+
}}
|
|
95
|
+
>
|
|
96
|
+
<div className="text-white w-full bg-black rounded p-1 flex justify-center">
|
|
97
|
+
{h}{" "}
|
|
98
|
+
{filters[h]?.length != items.length && (
|
|
99
|
+
<div className="text-red-300">
|
|
100
|
+
<FilterOffIcon />
|
|
101
|
+
</div>
|
|
102
|
+
)}
|
|
103
|
+
</div>
|
|
104
|
+
</div>
|
|
105
|
+
|
|
106
|
+
<FilterMenu
|
|
107
|
+
filters={filters}
|
|
108
|
+
setFilters={setFilters}
|
|
109
|
+
items={items}
|
|
110
|
+
h={h}
|
|
111
|
+
mapedData={mapedData}
|
|
112
|
+
index={key}
|
|
113
|
+
selectedFilter={selectedFilter}
|
|
114
|
+
setSelectedFilter={setSelectedFilter}
|
|
115
|
+
/>
|
|
116
|
+
</th>
|
|
117
|
+
);
|
|
118
|
+
})}
|
|
119
|
+
</tr>
|
|
120
|
+
</thead>
|
|
121
|
+
<tbody className="divide-y divide-gray-200">
|
|
122
|
+
{mapedData
|
|
123
|
+
.filter((md: any) => {
|
|
124
|
+
for (let datum of Object.keys(md)) {
|
|
125
|
+
if (
|
|
126
|
+
!filters[datum]
|
|
127
|
+
.map((d: any) => `${d}`.toLowerCase())
|
|
128
|
+
.includes(`${md[datum].content}`.toLowerCase())
|
|
129
|
+
) {
|
|
130
|
+
return false;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
return true;
|
|
134
|
+
})
|
|
135
|
+
.map((md: any, trKey: number) => {
|
|
136
|
+
const color = trKey % 2 == 0 ? "bg-white" : "bg-gray-100";
|
|
137
|
+
return (
|
|
138
|
+
<tr
|
|
139
|
+
onDoubleClick={(e) => {
|
|
140
|
+
modalRef.current?.showModal();
|
|
141
|
+
}}
|
|
142
|
+
key={trKey}
|
|
143
|
+
onClick={(e) => setSelected(trKey)}
|
|
144
|
+
className={[
|
|
145
|
+
"hover:bg-green-100 ",
|
|
146
|
+
color,
|
|
147
|
+
selected == trKey && "bg-green-200 hover:bg-green-300",
|
|
148
|
+
].join(" ")}
|
|
149
|
+
>
|
|
150
|
+
{head.map((h, tdKey: number) => {
|
|
151
|
+
return (
|
|
152
|
+
<TD
|
|
153
|
+
key={tdKey}
|
|
154
|
+
index={trKey}
|
|
155
|
+
symbols={symbols}
|
|
156
|
+
item={md[h]}
|
|
157
|
+
color={color}
|
|
158
|
+
/>
|
|
159
|
+
);
|
|
160
|
+
})}
|
|
161
|
+
</tr>
|
|
162
|
+
);
|
|
163
|
+
})}
|
|
164
|
+
</tbody>
|
|
165
|
+
<tfoot className="bg-gray-800 text-white">
|
|
166
|
+
<tr>
|
|
167
|
+
{head.map((h, fkey) => {
|
|
168
|
+
return (
|
|
169
|
+
<th key={fkey} className="text-right border-b max-w-[200px] p-5 ">
|
|
170
|
+
{totals && totals.includes(h) && (
|
|
171
|
+
<div className="flex justify-between text-white w-full bg-black rounded p-1">
|
|
172
|
+
<div className="p-1">
|
|
173
|
+
{symbols && symbols[h] && symbols[h]}
|
|
174
|
+
</div>
|
|
175
|
+
<div className="p-1">{mapedTotals[h]}</div>
|
|
176
|
+
</div>
|
|
177
|
+
)}
|
|
178
|
+
</th>
|
|
179
|
+
);
|
|
180
|
+
})}
|
|
181
|
+
</tr>
|
|
182
|
+
</tfoot>
|
|
183
|
+
</table>
|
|
184
|
+
);
|
|
185
|
+
}
|