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.
@@ -0,0 +1,55 @@
1
+ import { ReactNode, useState } from "react";
2
+ import HTable from "./h";
3
+ import useExport from "./export";
4
+ import { ExcelIcon } from "./filters";
5
+
6
+ export interface TableProps
7
+ extends React.DetailedHTMLProps<
8
+ React.TableHTMLAttributes<HTMLTableElement>,
9
+ HTMLTableElement
10
+ > {
11
+ data: Record<string, any> | Array<Record<string, any>>;
12
+ dataTypes?: Record<string, any>;
13
+ mapedData?: any;
14
+ setMapedData?: any;
15
+ totals?: Array<string>;
16
+ symbols?: Record<string, ReactNode>;
17
+ exportName?: string;
18
+ }
19
+
20
+ export default function Table(props: TableProps) {
21
+ const [mapedData, setMapedData] = useState([]);
22
+ const isArray = Array.isArray(props.data);
23
+ const exported = useExport();
24
+ return (
25
+ <div className="bg-gray-200 border rounded shadow p-1">
26
+ {props?.exportName && (
27
+ <button
28
+ className="p-2 border rounded shadow bg-green-800 text-white flex gap-1"
29
+ onClick={(e) =>
30
+ exported.export(
31
+ mapedData.map((md: any) => {
32
+ for (let i of Object.keys(md)) {
33
+ md[i] = md[i].content;
34
+ }
35
+ return md;
36
+ }),
37
+ props.exportName
38
+ )
39
+ }
40
+ >
41
+ <ExcelIcon /> Exportar
42
+ </button>
43
+ )}
44
+ {isArray && (
45
+ <HTable
46
+ {...{
47
+ ...props,
48
+ mapedData,
49
+ setMapedData,
50
+ }}
51
+ />
52
+ )}
53
+ </div>
54
+ );
55
+ }
@@ -0,0 +1,75 @@
1
+ import React, { useMemo, useState } from "react";
2
+ import { EditIcon } from "./filters";
3
+
4
+ interface TDProps
5
+ extends React.DetailedHTMLProps<
6
+ React.TdHTMLAttributes<HTMLTableCellElement>,
7
+ HTMLTableCellElement
8
+ > {
9
+ index: number;
10
+ item: { cellTypeOf: string; content: string; title: string; name: string };
11
+ color: string;
12
+ symbols: Record<string, any> | undefined;
13
+ }
14
+ export default function TD({
15
+ className,
16
+ item,
17
+ index,
18
+ color,
19
+ symbols,
20
+
21
+ ...props
22
+ }: TDProps) {
23
+ const [isHidded, setIsHidded] = useState(false);
24
+ const isNode = useMemo(() => {
25
+ return (
26
+ symbols &&
27
+ typeof symbols[item.name] == "object" &&
28
+ symbols[item.name]?.props
29
+ );
30
+ }, [symbols]);
31
+ const newProps =
32
+ symbols &&
33
+ isNode &&
34
+ Object.keys(symbols[item.name].props).reduce(
35
+ (acc: any, i) => {
36
+ try {
37
+ const newAcc = { ...acc };
38
+ const hasEvent = `${i}`.startsWith("on");
39
+ if (hasEvent) {
40
+ newAcc[i] = (e: any) => {
41
+ e.item = item;
42
+ symbols[item.name].props[i]?.(e);
43
+ };
44
+ }
45
+ return newAcc;
46
+ } catch (error) {}
47
+ },
48
+ { defaultValue: item.content }
49
+ );
50
+ return (
51
+ <td
52
+ onDoubleClick={(e) => setIsHidded(!isHidded)}
53
+ title={item.title}
54
+ className={[
55
+ isHidded && color,
56
+ !isHidded && "whitespace-nowrap overflow-hidden text-ellipsis ",
57
+ "border-b max-w-[200px] p-5 ",
58
+ ["number", "money"].includes(item.cellTypeOf) && "text-right",
59
+ ].join(" ")}
60
+ >
61
+ <div className={symbols && symbols[item.name] && "flex justify-between"}>
62
+ <div>
63
+ {symbols &&
64
+ symbols[item.name] &&
65
+ (isNode
66
+ ? React.cloneElement(symbols[item.name], {
67
+ ...newProps,
68
+ })
69
+ : symbols[item.name])}
70
+ </div>
71
+ <div>{item.content}</div>
72
+ </div>
73
+ </td>
74
+ );
75
+ }
@@ -0,0 +1,52 @@
1
+ import React, {
2
+ DetailedHTMLProps,
3
+ TextareaHTMLAttributes,
4
+ useState,
5
+ } from "react";
6
+
7
+ interface Props
8
+ extends DetailedHTMLProps<
9
+ TextareaHTMLAttributes<HTMLTextAreaElement>,
10
+ HTMLTextAreaElement
11
+ > {
12
+ label?: React.ReactNode;
13
+ maxLength?: number;
14
+ }
15
+ export default function TextArea({
16
+ label,
17
+ className,
18
+ maxLength,
19
+ onChange,
20
+ children = "",
21
+ ...props
22
+ }: Props) {
23
+ const [value, setValue] = useState<string>(children as string);
24
+ return (
25
+ <div className="w-full">
26
+ <label className="flex flex-col gap-1">
27
+ <div className="font-bold ">{label}</div>
28
+ <div>
29
+ <textarea
30
+ {...props}
31
+ className={["p-1 w-full rounded border shadow", className].join(
32
+ " "
33
+ )}
34
+ onChange={(e) => {
35
+ if (maxLength) {
36
+ e.target.value = e.target.value.slice(0, maxLength);
37
+ }
38
+ setValue(e.target.value);
39
+ onChange?.(e);
40
+ }}
41
+ value={value}
42
+ ></textarea>
43
+ {maxLength && (
44
+ <div className=" text-xs text-gray text-right">
45
+ {value.length} / {maxLength}
46
+ </div>
47
+ )}
48
+ </div>
49
+ </label>
50
+ </div>
51
+ );
52
+ }
@@ -0,0 +1,25 @@
1
+ "use client";
2
+ import CryptoJS from "crypto-js";
3
+ import useToken from "./get.token";
4
+ // import { config } from "dotenv";
5
+ // config();
6
+
7
+ export const encryptData = (data: object, secretKey: string): string => {
8
+ const stringData = JSON.stringify(data);
9
+ const encriptedText = CryptoJS.AES.encrypt(stringData, secretKey).toString();
10
+ window.localStorage.setItem("resources", encriptedText);
11
+ return encriptedText;
12
+ };
13
+
14
+ export const decryptData = (secretKey: string): any => {
15
+ const ciphertext = window.localStorage.getItem("resources");
16
+ if (!ciphertext) return null;
17
+
18
+ try {
19
+ const bytes = CryptoJS.AES.decrypt(ciphertext, secretKey);
20
+ const decrypted = bytes.toString(CryptoJS.enc.Utf8);
21
+ return JSON.parse(decrypted);
22
+ } catch (error) {
23
+ return null;
24
+ }
25
+ };
File without changes
@@ -0,0 +1,15 @@
1
+ import { useMemo } from "react";
2
+
3
+ export default function useToken(): string {
4
+ const token = useMemo(() => {
5
+ if (typeof window != "undefined") {
6
+ const t = window.localStorage.getItem("token");
7
+ if (!t) throw Error("Token undefined");
8
+
9
+ return t;
10
+ }
11
+ return "";
12
+ }, [typeof window]);
13
+
14
+ return token;
15
+ }
@@ -0,0 +1,77 @@
1
+ const httpStatusCodes = [
2
+ {
3
+ code: 100,
4
+ meaning:
5
+ "Continue: El servidor ha recibido los encabezados y el cliente debe continuar con la solicitud.",
6
+ },
7
+ {
8
+ code: 101,
9
+ meaning: "Switching Protocols: El servidor acepta cambiar de protocolo.",
10
+ },
11
+ { code: 200, meaning: "OK: La solicitud fue exitosa." },
12
+ { code: 201, meaning: "Created: Recurso creado exitosamente." },
13
+ {
14
+ code: 202,
15
+ meaning: "Accepted: La solicitud ha sido aceptada pero aún no procesada.",
16
+ },
17
+ {
18
+ code: 204,
19
+ meaning: "No Content: Éxito, pero sin contenido para devolver.",
20
+ },
21
+ {
22
+ code: 301,
23
+ meaning: "Moved Permanently: El recurso se ha movido de forma permanente.",
24
+ },
25
+ { code: 302, meaning: "Found: El recurso se ha movido temporalmente." },
26
+ {
27
+ code: 304,
28
+ meaning:
29
+ "Not Modified: El recurso no ha cambiado desde la última solicitud.",
30
+ },
31
+ { code: 400, meaning: "Bad Request: La solicitud es inválida." },
32
+ {
33
+ code: 401,
34
+ meaning: "Unauthorized: No autorizado. Puede requerir autenticación.",
35
+ },
36
+ {
37
+ code: 403,
38
+ meaning: "Forbidden: Acceso denegado, aunque estés autenticado.",
39
+ },
40
+ { code: 404, meaning: "Not Found: Recurso no encontrado." },
41
+ {
42
+ code: 405,
43
+ meaning: "Method Not Allowed: Método HTTP no permitido para este recurso.",
44
+ },
45
+ {
46
+ code: 409,
47
+ meaning: "Conflict: Conflicto con el estado actual del recurso.",
48
+ },
49
+ {
50
+ code: 422,
51
+ meaning:
52
+ "Unprocessable Entity: La solicitud está bien formada pero tiene errores semánticos.",
53
+ },
54
+ { code: 429, meaning: "Too Many Requests: Límite de peticiones excedido." },
55
+ { code: 500, meaning: "Internal Server Error: Error en el servidor." },
56
+ {
57
+ code: 501,
58
+ meaning: "Not Implemented: El servidor no reconoce el método solicitado.",
59
+ },
60
+ {
61
+ code: 502,
62
+ meaning:
63
+ "Bad Gateway: El servidor recibió una respuesta inválida del servidor upstream.",
64
+ },
65
+ {
66
+ code: 503,
67
+ meaning:
68
+ "Service Unavailable: El servidor no está disponible temporalmente.",
69
+ },
70
+ {
71
+ code: 504,
72
+ meaning:
73
+ "Gateway Timeout: Tiempo de espera agotado desde el servidor upstream.",
74
+ },
75
+ ];
76
+
77
+ export default httpStatusCodes;
@@ -0,0 +1,189 @@
1
+ import { useEffect, useMemo, useState } from "react";
2
+ import { EnhancedEndpoints, ItemsRecord, Props, ShowOptions } from "./types";
3
+ import useToken from "./get.token";
4
+ import axios from "axios";
5
+ import httpStatusCodes from "./http.codes";
6
+ import { useRouter } from "next/navigation";
7
+
8
+ export default function useResources<T extends Record<string, ItemsRecord>>({
9
+ baseURI,
10
+ authURI = "/auth/login",
11
+ endpoints,
12
+ }: Props) {
13
+ const token = useToken();
14
+ const router = useRouter();
15
+ const [info, setInfo] = useState<Record<string, any>>({});
16
+
17
+ const result = useMemo(() => {
18
+ const r = {} as EnhancedEndpoints<T>;
19
+
20
+ for (const key in endpoints) {
21
+ const endpoint = endpoints[key];
22
+ const showFunc = async ({
23
+ limit = 10,
24
+ page = 1,
25
+ merge = true,
26
+ ...query
27
+ }: ShowOptions) => {
28
+ const options = {
29
+ method: "GET",
30
+ url: `${baseURI}/${key}`,
31
+ params: { limit, page, ...query },
32
+ headers: { Authorization: token },
33
+ };
34
+
35
+ const newInfo = { ...info };
36
+ newInfo[key] = newInfo[key] || {};
37
+ newInfo[key].state = "loading";
38
+ newInfo[key].errorMessage = "";
39
+ newInfo[key].params = query;
40
+
41
+ setInfo(newInfo);
42
+
43
+ try {
44
+ const consulta = await axios.request(options);
45
+ const d = consulta.data;
46
+ newInfo[key].state = "success";
47
+ newInfo[key].errorMessage = "";
48
+ newInfo[key].data = merge
49
+ ? page == 1
50
+ ? d.data
51
+ : [...d.data, ...(newInfo[key].data || [])]
52
+ : d.data;
53
+ newInfo[key].totalItems = d.totalItems;
54
+ newInfo[key].totalPages = d.totalPages;
55
+ newInfo[key].currentPage = d.currentPage;
56
+ setInfo({ ...newInfo });
57
+ return d.data;
58
+ } catch (error: any) {
59
+ const item = httpStatusCodes.find((s) => s.code == error.status);
60
+
61
+ newInfo[key].state = "error";
62
+ newInfo[key].errorMessage = item?.meaning;
63
+ if (error.status == 403) {
64
+ router.push(authURI);
65
+ }
66
+ setInfo({ ...newInfo });
67
+ return error;
68
+ }
69
+ };
70
+ (r as any)[key] = {
71
+ ...endpoint,
72
+ show: showFunc,
73
+ find: async (id: number, query: any) => {
74
+ const options = {
75
+ method: "GET",
76
+ url: `${baseURI}/${key}/${id}`,
77
+ params: { ...query },
78
+ headers: { Authorization: token },
79
+ };
80
+ const newInfo = { ...info };
81
+ newInfo[key] = newInfo[key] || {};
82
+ newInfo[key].state = "loading";
83
+ newInfo[key].errorMessage = "";
84
+ newInfo[key].params = query;
85
+
86
+ setInfo(newInfo);
87
+
88
+ try {
89
+ const consulta = await axios.request(options);
90
+ const d = consulta.data;
91
+ newInfo[key].state = "success";
92
+ newInfo[key].errorMessage = "";
93
+ newInfo[key].selectedItem = d;
94
+ const index = newInfo[key]?.data?.findIndex(
95
+ (d: any) => d?.id == d?.id
96
+ );
97
+ if (index >= 0) {
98
+ newInfo[key].data[index] = d;
99
+ } else {
100
+ if (newInfo[key]?.data) {
101
+ newInfo[key].data.unshift(d);
102
+ } else {
103
+ newInfo[key].data = [d];
104
+ }
105
+ }
106
+ setInfo({ ...newInfo });
107
+ return d.data;
108
+ } catch (error: any) {
109
+ const item = httpStatusCodes.find((s) => s.code == error.status);
110
+
111
+ newInfo[key].state = "error";
112
+ newInfo[key].errorMessage = item?.meaning || error.message;
113
+ if (error.status == 403) {
114
+ router.push(authURI);
115
+ }
116
+ setInfo({ ...newInfo });
117
+ return error;
118
+ }
119
+ },
120
+ create: async (data: any) => {
121
+ const options = {
122
+ method: "POST",
123
+ url: `${baseURI}/${key}`,
124
+ data: { ...data },
125
+ headers: { Authorization: token },
126
+ };
127
+ const newInfo = { ...info };
128
+ newInfo[key] = newInfo[key] || {};
129
+ newInfo[key].state = "loading";
130
+ newInfo[key].errorMessage = "";
131
+
132
+ setInfo(newInfo);
133
+
134
+ try {
135
+ const consulta = await axios.request(options);
136
+ const d = consulta.data;
137
+ newInfo[key].state = "success";
138
+ newInfo[key].errorMessage = "";
139
+ newInfo[key].selectedItem = d;
140
+ const index = newInfo[key]?.data?.findIndex(
141
+ (d: any) => d?.id == d?.id
142
+ );
143
+ if (index >= 0) {
144
+ newInfo[key].data[index] = d;
145
+ } else {
146
+ if (newInfo[key]?.data) {
147
+ newInfo[key].data.unshift(d);
148
+ } else {
149
+ newInfo[key].data = [d];
150
+ }
151
+ }
152
+ setInfo({ ...newInfo });
153
+ return d.data;
154
+ } catch (error: any) {
155
+ const item = httpStatusCodes.find((s) => s.code == error.status);
156
+
157
+ newInfo[key].state = "error";
158
+ newInfo[key].errorMessage = item?.meaning || error.message;
159
+ if (error.status == 403) {
160
+ router.push(authURI);
161
+ }
162
+ setInfo({ ...newInfo });
163
+ return error;
164
+ }
165
+ },
166
+ update: async (id: number, data: any) => {
167
+ console.log("update", id, data);
168
+ },
169
+ remove: async (id: number) => {
170
+ console.log("remove", id);
171
+ },
172
+ totalPages: info[key]?.totalPages,
173
+ currentPage: info[key]?.currentPage,
174
+ state: info[key]?.state || "success",
175
+ errorMessage: info[key]?.errorMessage,
176
+ params: info[key]?.params,
177
+ getAllPages: async (limit = 10) => {
178
+ console.log("Not aviable");
179
+ },
180
+ data: info[key]?.data || [],
181
+ selectedItem: info[key]?.selectedItem || {},
182
+ };
183
+ }
184
+
185
+ return r;
186
+ }, [info, token, endpoints]);
187
+
188
+ return result;
189
+ }
@@ -0,0 +1,41 @@
1
+ export interface ItemsRecord {
2
+ typeof: Object;
3
+ }
4
+
5
+ export interface Props {
6
+ baseURI: string;
7
+ authURI?: string;
8
+ endpoints: Record<string, Partial<ItemsRecord>>;
9
+ }
10
+
11
+ export interface ShowOptions {
12
+ limit?: number;
13
+ merge?: boolean;
14
+ page?: number;
15
+ [key: string | number]: string | number | undefined | null | boolean;
16
+ }
17
+ export type EnhancedEndpoints<T extends Record<string, ItemsRecord>> = {
18
+ [K in keyof T]: {
19
+ typeof: T[K]["typeof"];
20
+ state: "success" | "loading" | "error";
21
+ show: (props: ShowOptions) => Promise<Array<T[K]["typeof"]>>;
22
+ create: (data: Partial<T[K]["typeof"]>) => Promise<T[K]["typeof"]>;
23
+ update: (
24
+ id: number | string,
25
+ data: Partial<T[K]["typeof"]>
26
+ ) => Promise<T[K]["typeof"]>;
27
+ remove: (id: number | string) => Promise<T[K]["typeof"]>;
28
+ find: (
29
+ id: number | string,
30
+ props?: Record<string, string>
31
+ ) => Promise<T[K]["typeof"]>;
32
+ getAllPages: (limit: number) => Promise<Array<T[K]["typeof"]>>;
33
+ data: T[K]["typeof"][];
34
+ selectedItem: T[K]["typeof"];
35
+ params: Record<string, any>;
36
+ currentPage: number;
37
+ totalItems: number;
38
+ totalPages: number;
39
+ errorMessage: string;
40
+ };
41
+ };
package/tsconfig.json ADDED
@@ -0,0 +1,15 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES6",
4
+ "module": "ESNext",
5
+ "jsx": "react-jsx",
6
+ "declaration": true,
7
+ "outDir": "dist",
8
+ "esModuleInterop": true,
9
+ "moduleResolution": "node",
10
+ "skipLibCheck": true,
11
+ "strict": true,
12
+ "baseUrl": "."
13
+ },
14
+ "include": ["src"]
15
+ }