next-recomponents 1.7.63 → 1.8.2

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.
@@ -0,0 +1,56 @@
1
+ export default function TableFooter({
2
+ objectData,
3
+ headers,
4
+ footer,
5
+ selectItems,
6
+ modal,
7
+ }: {
8
+ objectData: any;
9
+ headers: string[];
10
+ footer?: Record<string, "count" | "sum" | "avg">;
11
+ selectItems?: any;
12
+ modal?: any;
13
+ }) {
14
+ function operacion(tipo: "count" | "sum" | "avg", header: string): any {
15
+ switch (tipo) {
16
+ case "count":
17
+ return Object.values(objectData).filter(
18
+ (data: any) => data._visible === true
19
+ ).length;
20
+ case "sum":
21
+ return Object.values(objectData)
22
+ .filter((data: any) => data._visible === true)
23
+ .reduce((acc, data: any) => acc + data?.[header], 0);
24
+ case "avg":
25
+ return (
26
+ Object.values(objectData)
27
+ .filter((data: any) => data._visible === true)
28
+ .reduce((acc: number, data: any) => acc + data?.[header], 0) /
29
+ Object.values(objectData).filter(
30
+ (data: any) => data._visible === true
31
+ ).length
32
+ );
33
+ default:
34
+ return null;
35
+ }
36
+ }
37
+ return (
38
+ footer && (
39
+ <tfoot>
40
+ <tr className="bg-blue-500 text-white">
41
+ {selectItems && <th></th>}
42
+ {modal && <th></th>}
43
+ {headers.map((header) => {
44
+ if (header.startsWith("_")) {
45
+ return null;
46
+ } else if (footer?.[header]) {
47
+ return <th key={header}>{operacion(footer[header], header)}</th>;
48
+ }
49
+
50
+ return <th key={header}></th>;
51
+ })}
52
+ </tr>
53
+ </tfoot>
54
+ )
55
+ );
56
+ }
@@ -0,0 +1,72 @@
1
+ import React from "react";
2
+ import Filter from "./filter";
3
+
4
+ export default function TableHead({
5
+ headers,
6
+ selectItems,
7
+ setObjectData,
8
+ objectData,
9
+ page,
10
+ setPage,
11
+ maxItems,
12
+ colSizes,
13
+ modal,
14
+ }: {
15
+ headers: string[];
16
+ selectItems?: boolean;
17
+ setObjectData: any;
18
+ objectData: any;
19
+ page: number;
20
+ setPage: any;
21
+ maxItems?: number;
22
+ colSizes?: Record<string, any>;
23
+ modal?: React.ReactNode;
24
+ }) {
25
+ return (
26
+ <thead>
27
+ <tr className="bg-blue-500 text-white font-bold">
28
+ {modal && <th>-</th>}
29
+ {selectItems && (
30
+ <th>
31
+ <input
32
+ className="m-2"
33
+ type="checkbox"
34
+ checked={Object.values(objectData).every(
35
+ (d: any) => d._selected === true
36
+ )}
37
+ onChange={(e) => {
38
+ const newVal = Object.values(objectData).map((d: any) => {
39
+ if (d?._id) {
40
+ return {
41
+ _id: d._id,
42
+ _selected: e.target.checked,
43
+ };
44
+ } else {
45
+ return {
46
+ id: d.id,
47
+ _selected: e.target.checked,
48
+ };
49
+ }
50
+ });
51
+ setObjectData(newVal);
52
+ }}
53
+ />
54
+ </th>
55
+ )}
56
+ {Object.values(headers).map((h) => {
57
+ if (h.startsWith("_")) return null;
58
+ return (
59
+ <Filter
60
+ key={h}
61
+ objectData={objectData}
62
+ h={h}
63
+ setObjectData={setObjectData}
64
+ setPage={setPage}
65
+ colSizes={colSizes}
66
+ />
67
+ );
68
+ })}
69
+ </tr>
70
+ </thead>
71
+ );
72
+ }
@@ -0,0 +1,190 @@
1
+ "use client";
2
+ import React, {
3
+ DetailedHTMLProps,
4
+ TableHTMLAttributes,
5
+ useEffect,
6
+ useMemo,
7
+ useReducer,
8
+ useRef,
9
+ useState,
10
+ } from "react";
11
+
12
+ import TableHead from "./head";
13
+ import TableBody from "./body";
14
+ import Panel from "./panel";
15
+ import TableFooter from "./footer";
16
+ import Dialog from "./dialog";
17
+
18
+ interface Table3Props
19
+ extends DetailedHTMLProps<
20
+ TableHTMLAttributes<HTMLTableElement>,
21
+ HTMLTableElement
22
+ > {
23
+ data: Record<string, any>[];
24
+ selectItems?: boolean;
25
+ maxItems?: number;
26
+ onSave?: (e: any) => void;
27
+ exportName?: string;
28
+ colSizes?: Record<string, number>;
29
+ modal?: React.ReactNode;
30
+ footer?: Record<string, "sum" | "count" | "avg">;
31
+ header?: React.ReactNode;
32
+ onChange?: (e: any) => any;
33
+ }
34
+
35
+ export default function Table3({
36
+ data,
37
+ selectItems,
38
+ maxItems,
39
+ onSave,
40
+ exportName,
41
+ colSizes,
42
+ modal,
43
+ header,
44
+ footer,
45
+ onChange,
46
+ ...props
47
+ }: Table3Props) {
48
+ const [handlers, setHandlers] = useState<Record<string, any>>({});
49
+ const [page, setPage] = useState(1);
50
+
51
+ function dataReducer(a: any, b: any) {
52
+ if (b == null) return {};
53
+ if (Array.isArray(b)) {
54
+ const obj = b.reduce((acc: any, bb: any) => {
55
+ const id = bb?.id || bb?._id;
56
+ if (!id) throw new Error("Se necesita el id o _id");
57
+
58
+ const cc = a?.[id]?._updated
59
+ ? {
60
+ ...bb,
61
+ ...a?.[id],
62
+ _selected: bb?._selected,
63
+ _visible: bb?._visible == false ? false : true,
64
+ }
65
+ : {
66
+ ...bb,
67
+ _selected: bb?._selected,
68
+ _visible: bb?._visible == false ? false : true,
69
+ };
70
+
71
+ const newHandlers = { ...handlers };
72
+
73
+ for (let item in cc) {
74
+ const isReactComponent = React.isValidElement(cc[item]);
75
+
76
+ if (isReactComponent) {
77
+ // guarda el componente original
78
+ newHandlers[item] = cc[item];
79
+
80
+ // guarda el valor "actual" en la data
81
+ const value =
82
+ (cc[item] as any)?.props?.value ||
83
+ (typeof (cc[item] as any)?.props?.children !== "object"
84
+ ? (cc[item] as any)?.props?.children
85
+ : "");
86
+ cc[item] = value || "";
87
+ }
88
+ }
89
+
90
+ setHandlers(newHandlers);
91
+
92
+ acc[id] = { ...bb, ...cc };
93
+
94
+ return acc;
95
+ }, {});
96
+
97
+ return obj;
98
+ } else {
99
+ const newA = { ...a };
100
+ const id = b?.id || b?._id;
101
+ if (!id) throw new Error("Se necesita el id o _id para actualizar");
102
+
103
+ newA[id] = { ...newA[id], ...b };
104
+
105
+ return newA;
106
+ }
107
+ }
108
+ const [objectData, setObjectData] = useReducer(dataReducer, null);
109
+
110
+ useEffect(() => {
111
+ setObjectData(data);
112
+ setPage(1);
113
+ }, [data]);
114
+
115
+ const headers = useMemo<string[]>(() => {
116
+ if (!objectData) return [];
117
+ return [
118
+ ...new Set<any>(
119
+ Object.values(objectData)
120
+ .map((o: any) => Object.keys(o))
121
+ .flat()
122
+ ),
123
+ ];
124
+ }, [objectData]);
125
+ const totalPages = useMemo(() => {
126
+ if (!objectData) return 0;
127
+ return maxItems ? Math.ceil(Object.keys(objectData).length / maxItems) : 1;
128
+ }, [objectData, maxItems]);
129
+
130
+ const modalRef = useRef<HTMLDialogElement>(null);
131
+ const [dialogRow, setDialogRow] = useState<any>({});
132
+ const context = {
133
+ objectData,
134
+ headers,
135
+ handlers,
136
+ setObjectData,
137
+ page,
138
+ setPage,
139
+ totalPages,
140
+ exportName,
141
+ onSave,
142
+ modalRef,
143
+ modal,
144
+ dialogRow,
145
+ setDialogRow,
146
+ selectItems,
147
+ maxItems,
148
+ colSizes,
149
+ header,
150
+ footer,
151
+ onChange,
152
+ ...props,
153
+ };
154
+
155
+ useEffect(() => {
156
+ if (dialogRow?.id || dialogRow?._id) {
157
+ const newDialogRow =
158
+ objectData[dialogRow?.id] || objectData[dialogRow?._id];
159
+ setDialogRow(newDialogRow);
160
+ }
161
+ }, [objectData]);
162
+
163
+ const style: any = props?.style
164
+ ? { ...props.style, tableLayout: "fixed" }
165
+ : { tableLayout: "fixed" };
166
+
167
+ if (!objectData) return null;
168
+ console.log({ objectData });
169
+ return (
170
+ <div className="border shadow rounded m-1 p-1 bg-white">
171
+ {modal && (
172
+ <Dialog
173
+ modalRef={modalRef}
174
+ dialogRow={dialogRow}
175
+ setDialogRow={setDialogRow}
176
+ setObjectData={setObjectData}
177
+ >
178
+ {modal}
179
+ </Dialog>
180
+ )}
181
+ <div className="font-bold text-2xl py-5 px-2 bg-blue-50">{header}</div>
182
+ <Panel {...context} />
183
+ <table {...props} style={style}>
184
+ <TableHead {...context} />
185
+ <TableBody {...context} />
186
+ <TableFooter {...context} />
187
+ </table>
188
+ </div>
189
+ );
190
+ }
@@ -0,0 +1,85 @@
1
+ import { ExcelIcon, SaveIcon } from "../../src/table/filters";
2
+ import useExcel from "../../src/use-excel";
3
+
4
+ export default function Panel({
5
+ page,
6
+ setPage,
7
+ onSave,
8
+ objectData,
9
+ exportName,
10
+ totalPages,
11
+ }: {
12
+ page: number;
13
+ setPage: any;
14
+ onSave?: any;
15
+ objectData: any;
16
+ exportName?: string;
17
+ totalPages: number;
18
+ }) {
19
+ const excel = useExcel();
20
+ return (
21
+ <div className="flex gap-2 bg-gray-100 items-center">
22
+ <div className="flex gap-1 ">
23
+ {onSave && (
24
+ <button
25
+ className="p-2 border shadow rounded bg-blue-500 text-white flex items-center gap-1 text-md"
26
+ onClick={(e) => {
27
+ onSave?.(Object.values(objectData).reverse());
28
+ }}
29
+ >
30
+ {" "}
31
+ <SaveIcon />
32
+ Guardar
33
+ </button>
34
+ )}
35
+ {exportName && (
36
+ <button
37
+ className="p-2 border shadow rounded bg-green-800 text-white flex items-center gap-1 text-md"
38
+ onClick={(e) => {
39
+ excel.export(
40
+ Object.values(objectData)
41
+ .reverse()
42
+ .map((d: any) => {
43
+ const { _visible, _id, _selected, ...data } = d;
44
+ return data;
45
+ }) as any[],
46
+ exportName + ".xlsx"
47
+ );
48
+ }}
49
+ >
50
+ <ExcelIcon />
51
+ Exportar
52
+ </button>
53
+ )}
54
+ </div>
55
+
56
+ <div className="flex gap-2 items-center text-2xl">
57
+ <button onClick={() => setPage(1)} disabled={page === 1}>
58
+
59
+ </button>
60
+
61
+ <button onClick={() => setPage(page - 1)} disabled={page === 1}>
62
+
63
+ </button>
64
+
65
+ <span className="text-sm">
66
+ Página {page} / {totalPages}
67
+ </span>
68
+
69
+ <button
70
+ onClick={() => setPage(page + 1)}
71
+ disabled={page === totalPages}
72
+ >
73
+
74
+ </button>
75
+
76
+ <button
77
+ onClick={() => setPage(totalPages)}
78
+ disabled={page === totalPages}
79
+ >
80
+
81
+ </button>
82
+ </div>
83
+ </div>
84
+ );
85
+ }
@@ -0,0 +1,148 @@
1
+ import React from "react";
2
+ import { EditIcon } from "../../src/table/filters";
3
+
4
+ export default function TR({
5
+ handlers,
6
+ setObjectData,
7
+ id,
8
+ row,
9
+ headers,
10
+ selectItems,
11
+ colSizes,
12
+ modal,
13
+ modalRef,
14
+ dialogRow,
15
+ setDialogRow,
16
+ index,
17
+ selected,
18
+ setSelected,
19
+ onChange,
20
+ }: {
21
+ handlers: any;
22
+ setObjectData: any;
23
+ headers: string[];
24
+ id: any;
25
+ row: any;
26
+ selectItems?: boolean;
27
+ colSizes?: Record<string, any>;
28
+ modal?: React.ReactNode;
29
+ modalRef: React.RefObject<HTMLDialogElement>;
30
+ dialogRow: any;
31
+ setDialogRow: any;
32
+ index: number;
33
+ selected: number;
34
+ setSelected: any;
35
+ onChange: any;
36
+ }) {
37
+ const color =
38
+ selected == index
39
+ ? "bg-blue-600 text-white hover:bg-blue-800"
40
+ : index % 2 == 0
41
+ ? "bg-white"
42
+ : "bg-blue-50";
43
+ return (
44
+ <tr
45
+ key={id}
46
+ className={` hover:bg-blue-100 ${color} cursor-pointer`}
47
+ onClick={(e) => {
48
+ setSelected(selected == index ? -1 : index);
49
+ }}
50
+ >
51
+ {modal && (
52
+ <th className="border">
53
+ <button
54
+ className="p-1 border shadow-rounded bg-blue-500 rounded text-white"
55
+ onClick={(e) => {
56
+ modalRef.current?.showModal();
57
+ setDialogRow(row);
58
+ }}
59
+ >
60
+ <EditIcon />
61
+ </button>
62
+ </th>
63
+ )}
64
+ {selectItems && (
65
+ <th className="border">
66
+ <input
67
+ type="checkbox"
68
+ checked={Boolean(row?._selected)}
69
+ onChange={(e) => {
70
+ if (row?._id) {
71
+ setObjectData({ _id: +id, _selected: e.target.checked });
72
+ } else {
73
+ setObjectData({ id: +id, _selected: e.target.checked });
74
+ }
75
+ }}
76
+ />
77
+ </th>
78
+ )}
79
+ {headers.map((h) => {
80
+ const colSize = colSizes?.[h];
81
+ if (h.startsWith("_")) {
82
+ return null;
83
+ } else if (handlers?.[h]) {
84
+ const original = handlers[h];
85
+
86
+ const cloned = React.cloneElement(original, {
87
+ // si es controlado por value → actualiza con row[h]
88
+ value: original.props?.value !== undefined ? row[h] : undefined,
89
+
90
+ // si usa children → actualiza con row[h] también
91
+ children: original.props?.children
92
+ ? typeof original.props.children == "object"
93
+ ? original.props?.children
94
+ : row?.[h]
95
+ : null,
96
+
97
+ ...Object.keys({
98
+ ...original.props,
99
+ onChange: original.props?.onChange,
100
+ })
101
+ .filter((action) => action.startsWith("on"))
102
+ .reduce((acc, action) => {
103
+ acc[action] = (e: any) => {
104
+ e.row = row;
105
+
106
+ const userResponse = original.props?.[action]?.(e) || {};
107
+ const newObject = row?._id
108
+ ? {
109
+ _id: row?._id,
110
+ [h]: e.target.value,
111
+ _updated: true,
112
+ _visible: true,
113
+ ...userResponse,
114
+ }
115
+ : {
116
+ id: row?.id || null,
117
+ [h]: e.target.value,
118
+ _updated: true,
119
+ _visible: true,
120
+ ...userResponse,
121
+ };
122
+
123
+ const finalResponse =
124
+ action == "onChange" && onChange
125
+ ? onChange({ ...e, row: { ...e.row, ...newObject } })
126
+ : {};
127
+
128
+ setObjectData({ ...newObject, ...finalResponse });
129
+ };
130
+ return acc;
131
+ }, {} as any),
132
+ });
133
+
134
+ return (
135
+ <td key={h} className={`text-black `}>
136
+ {cloned}
137
+ </td>
138
+ );
139
+ }
140
+ return (
141
+ <td key={h} className={`text-center border max-w-[${colSize}px]`}>
142
+ {row[h]}
143
+ </td>
144
+ );
145
+ })}
146
+ </tr>
147
+ );
148
+ }
package/tsconfig.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "compilerOptions": {
3
3
  "target": "ES6",
4
- "module": "node16",
5
- "moduleResolution": "node16",
4
+ "module": "ESNext",
6
5
  "jsx": "react-jsx",
7
6
  "declaration": true,
8
7
  "outDir": "dist",
9
8
  "esModuleInterop": true,
9
+ "moduleResolution": "node",
10
10
  "skipLibCheck": true,
11
11
  "strict": true,
12
12
  "baseUrl": "."
@@ -1,141 +0,0 @@
1
- import { useEffect, useMemo, useState } from "react";
2
- import useExcel from "src/use-excel";
3
-
4
- export default function useTableContext({
5
- data,
6
- maxItems,
7
- symbols,
8
- handlers,
9
- onSave,
10
- exportName,
11
- selectItems,
12
- colSizes,
13
- }: {
14
- data: Record<string, any>[];
15
- maxItems: number;
16
- symbols: Record<string, React.ReactNode>;
17
- handlers: Record<string, React.ReactNode>;
18
- onSave?: (data: Record<string, any>[]) => void;
19
- exportName?: string;
20
- selectItems?: boolean;
21
- colSizes?: Record<string, number>;
22
- }) {
23
- function dataReduce(datums: Record<string, any>[]) {
24
- return datums
25
- .map((row: any) => {
26
- for (let key in row) {
27
- if (typeof row[key] === "object") {
28
- row[key] = JSON.stringify(row[key]);
29
- }
30
- }
31
- return row;
32
- })
33
- .reduce((acc, d) => {
34
- const id = d?._id || d?.id;
35
- if (!id) throw new Error("Data must have an '_id' or 'id' field");
36
- acc[id] = d;
37
- acc[id]._checkedItem = false;
38
- return acc;
39
- }, {});
40
- }
41
- const excel = useExcel();
42
-
43
- const [defaultData, setDefaultData] = useState(dataReduce(data));
44
-
45
- const [page, setPage] = useState(1);
46
-
47
- const headers = useMemo(() => {
48
- return [...new Set(data.flatMap((d) => Object.keys(d)))];
49
- }, [data]);
50
-
51
- const list = useMemo(() => {
52
- return headers.reduce((acc, h) => {
53
- acc[h] = [
54
- ...new Set(
55
- Object.values(defaultData).map((row: any) => {
56
- const value =
57
- typeof row[h] === "object" ? JSON.stringify(row[h]) : row[h];
58
- return value;
59
- })
60
- ),
61
- ];
62
- return acc;
63
- }, {} as any);
64
- }, [headers, defaultData]);
65
-
66
- const [filters, setFilters] = useState<any>(
67
- headers.reduce((acc, h) => {
68
- acc[h] = list[h].reduce((ac: any, l: any) => {
69
- ac[l] = true;
70
- return ac;
71
- }, {});
72
- return acc;
73
- }, {} as any)
74
- );
75
-
76
- const bodyKeys = useMemo(() => Object.keys(defaultData), [defaultData]);
77
- const body = useMemo(() => defaultData, [defaultData]);
78
-
79
- const filteredBody = useMemo(() => {
80
- return bodyKeys.filter((key) => {
81
- return headers.every((header) => {
82
- return (
83
- filters?.[`${header}`]?.[body?.[`${key}`]?.[`${header}`]] !== false
84
- );
85
- });
86
- });
87
- }, [filters, headers, defaultData]);
88
-
89
- const paginatedBody = useMemo(() => {
90
- return filteredBody.filter((k, index) => {
91
- if (maxItems === Infinity) return true;
92
-
93
- const desde = (page - 1) * maxItems;
94
- const hasta = desde + maxItems;
95
- return index >= desde && index < hasta;
96
- });
97
- }, [filteredBody, page]);
98
-
99
- const totalPages = useMemo(
100
- () =>
101
- maxItems === Infinity ? 1 : Math.ceil(filteredBody.length / maxItems),
102
- [filteredBody, maxItems]
103
- );
104
-
105
- useEffect(() => {
106
- const original = dataReduce(data);
107
- const originalKeys = Object.keys(original);
108
- setDefaultData((prevDefaultData: any) => {
109
- const newDefault: any = {};
110
- for (let key of originalKeys) {
111
- // si ya existía en el estado anterior, lo conservamos
112
- newDefault[key] = prevDefaultData?.[key] ?? original[key];
113
- }
114
- return newDefault;
115
- });
116
- }, [data]);
117
-
118
- return {
119
- page,
120
- setPage,
121
- defaultData,
122
- setDefaultData,
123
- totalPages,
124
- headers,
125
- filters,
126
- setFilters,
127
- list,
128
- filteredBody,
129
- maxItems,
130
- paginatedBody,
131
- handlers,
132
- symbols,
133
- onSave,
134
- exportName,
135
- excel,
136
- selectItems,
137
- colSizes,
138
- };
139
- }
140
-
141
- export type TableContextType = ReturnType<typeof useTableContext>;
@@ -1,5 +0,0 @@
1
- import { TableContextType } from "./context";
2
-
3
- export default function HTable({ context }: { context: TableContextType }) {
4
- return <></>;
5
- }