next-recomponents 2.0.39 → 2.0.41
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 +35 -5
- package/dist/index.d.ts +35 -5
- package/dist/index.js +472 -420
- package/dist/index.mjs +497 -445
- package/package.json +1 -1
- package/src/input/index.tsx +75 -73
- package/src/pop/overlay.tsx +6 -6
- package/src/select/index.tsx +9 -12
- package/src/text-area/index.tsx +26 -31
- package/src/use-resources/index.ts +424 -397
|
@@ -1,40 +1,9 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { useEffect, useMemo, useState } from "react";
|
|
2
2
|
import { EnhancedEndpoints, ItemsRecord, Props, ShowOptions } from "./types";
|
|
3
3
|
import useToken from "./get.token";
|
|
4
4
|
import axios from "axios";
|
|
5
5
|
import httpStatusCodes from "./http.codes";
|
|
6
6
|
|
|
7
|
-
// ---------------------------------------------------------------------------
|
|
8
|
-
// Helpers
|
|
9
|
-
// ---------------------------------------------------------------------------
|
|
10
|
-
|
|
11
|
-
function getErrorMeaning(error: any): string | undefined {
|
|
12
|
-
return httpStatusCodes.find((s) => s.code === error?.status)?.meaning;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
function mergeDataArray(existing: any[], incoming: any, matchId?: any): any[] {
|
|
16
|
-
const idx = existing.findIndex((d) => d?.id == matchId);
|
|
17
|
-
if (idx >= 0) {
|
|
18
|
-
return existing.map((d, i) => (i === idx ? incoming : d));
|
|
19
|
-
}
|
|
20
|
-
return [incoming, ...existing];
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
// Copia profunda de un solo key para evitar mutación de estado
|
|
24
|
-
function cloneKey<T extends Record<string, any>>(state: T, key: string): T {
|
|
25
|
-
return {
|
|
26
|
-
...state,
|
|
27
|
-
[key]: {
|
|
28
|
-
...state[key],
|
|
29
|
-
data: state[key]?.data ? [...state[key].data] : [],
|
|
30
|
-
},
|
|
31
|
-
};
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
// ---------------------------------------------------------------------------
|
|
35
|
-
// Hook
|
|
36
|
-
// ---------------------------------------------------------------------------
|
|
37
|
-
|
|
38
7
|
export default function useResources<T extends Record<string, ItemsRecord>>({
|
|
39
8
|
baseURI,
|
|
40
9
|
endpoints,
|
|
@@ -42,389 +11,447 @@ export default function useResources<T extends Record<string, ItemsRecord>>({
|
|
|
42
11
|
}: Props<T>) {
|
|
43
12
|
const token = useToken();
|
|
44
13
|
|
|
45
|
-
const [info, setInfo] = useState<EnhancedEndpoints<T>>(
|
|
14
|
+
const [info, setInfo] = useState<EnhancedEndpoints<T>>(
|
|
46
15
|
Object.keys(endpoints).reduce((acc: any, key) => {
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
id: endpoints[key]?.id,
|
|
50
|
-
defaultParams: endpoints[key]?.defaultParams,
|
|
51
|
-
data: [],
|
|
52
|
-
selectedItem: {},
|
|
53
|
-
state: "success",
|
|
54
|
-
errorMessage: "",
|
|
55
|
-
};
|
|
56
|
-
return acc;
|
|
57
|
-
}, {} as any),
|
|
58
|
-
);
|
|
59
|
-
|
|
60
|
-
// Ref siempre actualizado → las callbacks leen estado fresco sin re-crearse
|
|
61
|
-
const infoRef = useRef(info);
|
|
62
|
-
useEffect(() => {
|
|
63
|
-
infoRef.current = info;
|
|
64
|
-
}, [info]);
|
|
65
|
-
|
|
66
|
-
// ------------------------------------------------------------------
|
|
67
|
-
// Funciones de estado auxiliares (no dependen de key en tiempo de cierre)
|
|
68
|
-
// ------------------------------------------------------------------
|
|
69
|
-
|
|
70
|
-
const setKeyLoading = useCallback((key: string) => {
|
|
71
|
-
setInfo((prev) => ({
|
|
72
|
-
...cloneKey(prev, key),
|
|
73
|
-
[key]: {
|
|
74
|
-
...cloneKey(prev, key)[key],
|
|
75
|
-
state: "loading",
|
|
76
|
-
errorMessage: "",
|
|
77
|
-
},
|
|
78
|
-
}));
|
|
79
|
-
}, []);
|
|
80
|
-
|
|
81
|
-
const setKeyError = useCallback(
|
|
82
|
-
(key: string, error: any) => {
|
|
83
|
-
const meaning = getErrorMeaning(error);
|
|
84
|
-
if (error?.status === 403) onError?.({ error, errorMessage: meaning });
|
|
85
|
-
setInfo((prev) => ({
|
|
86
|
-
...cloneKey(prev, key),
|
|
16
|
+
const newAcc: any = {
|
|
17
|
+
...acc,
|
|
87
18
|
[key]: {
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
19
|
+
loaded: false,
|
|
20
|
+
id: endpoints[key]?.id,
|
|
21
|
+
defaultParams: endpoints[key]?.defaultParams,
|
|
22
|
+
data: [],
|
|
23
|
+
selectedItem: {},
|
|
91
24
|
},
|
|
92
|
-
}
|
|
93
|
-
},
|
|
94
|
-
[onError],
|
|
95
|
-
);
|
|
25
|
+
};
|
|
96
26
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
const show = useCallback(
|
|
102
|
-
async (
|
|
103
|
-
key: string,
|
|
104
|
-
{ limit = 10, page = 1, merge = true, ...query }: ShowOptions,
|
|
105
|
-
) => {
|
|
106
|
-
setInfo((prev) => ({
|
|
107
|
-
...cloneKey(prev, key),
|
|
108
|
-
[key]: {
|
|
109
|
-
...cloneKey(prev, key)[key],
|
|
110
|
-
state: "loading",
|
|
111
|
-
errorMessage: "",
|
|
112
|
-
params: query,
|
|
113
|
-
loaded: true,
|
|
114
|
-
},
|
|
115
|
-
}));
|
|
27
|
+
return newAcc;
|
|
28
|
+
}, {} as any),
|
|
29
|
+
);
|
|
116
30
|
|
|
117
|
-
|
|
118
|
-
|
|
31
|
+
const results: EnhancedEndpoints<T> = Object.keys(endpoints).reduce(
|
|
32
|
+
(acc: any, key) => {
|
|
33
|
+
const endpoint = endpoints[key];
|
|
34
|
+
const showFunc = async (
|
|
35
|
+
{ limit = 10, page = 1, merge = true, ...query }: ShowOptions,
|
|
36
|
+
autoLoad: boolean = false,
|
|
37
|
+
) => {
|
|
38
|
+
const options = {
|
|
39
|
+
method: "GET",
|
|
40
|
+
url: `${baseURI}/${key}`,
|
|
119
41
|
params: { limit, page, ...query },
|
|
120
42
|
headers: { Authorization: token },
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
const newInfo = { ...info };
|
|
46
|
+
// newInfo[key] = newInfo[key] || {};
|
|
47
|
+
newInfo[key].state = "loading";
|
|
48
|
+
newInfo[key].errorMessage = "";
|
|
49
|
+
newInfo[key].params = query;
|
|
50
|
+
newInfo[key].loaded = true;
|
|
51
|
+
|
|
52
|
+
setInfo(newInfo);
|
|
53
|
+
|
|
54
|
+
try {
|
|
55
|
+
const consulta = await axios.request(options);
|
|
56
|
+
const d = consulta.data;
|
|
57
|
+
newInfo[key].state = "success";
|
|
58
|
+
newInfo[key].errorMessage = "";
|
|
59
|
+
|
|
60
|
+
newInfo[key].data = merge
|
|
61
|
+
? page == 1
|
|
62
|
+
? d.data
|
|
63
|
+
: [...d.data, ...(newInfo[key].data || [])]
|
|
64
|
+
: d.data;
|
|
65
|
+
newInfo[key].totalItems = d.totalItems;
|
|
66
|
+
newInfo[key].totalPages = d.totalPages;
|
|
67
|
+
newInfo[key].currentPage = d.currentPage;
|
|
68
|
+
|
|
69
|
+
setInfo({ ...newInfo });
|
|
70
|
+
return d.data;
|
|
71
|
+
} catch (error: any) {
|
|
72
|
+
const item = httpStatusCodes.find((s) => s.code == error.status);
|
|
73
|
+
|
|
74
|
+
newInfo[key].state = "error";
|
|
75
|
+
newInfo[key].errorMessage = item?.meaning;
|
|
76
|
+
if (error.status == 403) {
|
|
77
|
+
onError?.({ error, ...{ errorMessage: item?.meaning } });
|
|
78
|
+
}
|
|
79
|
+
setInfo({ ...newInfo });
|
|
80
|
+
return error;
|
|
81
|
+
}
|
|
82
|
+
};
|
|
83
|
+
const findFunc = async (id: number, query: any) => {
|
|
84
|
+
const options = {
|
|
85
|
+
method: "GET",
|
|
86
|
+
url: `${baseURI}/${key}/${id}`,
|
|
87
|
+
params: { ...query },
|
|
88
|
+
headers: { Authorization: token },
|
|
89
|
+
};
|
|
90
|
+
const newInfo = { ...info };
|
|
91
|
+
// newInfo[key] = newInfo[key] || {};
|
|
92
|
+
newInfo[key].state = "loading";
|
|
93
|
+
newInfo[key].errorMessage = "";
|
|
94
|
+
newInfo[key].params = query;
|
|
95
|
+
newInfo[key].loaded = true;
|
|
96
|
+
|
|
97
|
+
setInfo(newInfo);
|
|
98
|
+
|
|
99
|
+
try {
|
|
100
|
+
const consulta = await axios.request(options);
|
|
101
|
+
const d = consulta.data;
|
|
102
|
+
newInfo[key].state = "success";
|
|
103
|
+
newInfo[key].errorMessage = "";
|
|
104
|
+
|
|
105
|
+
newInfo[key].selectedItem = d;
|
|
106
|
+
const index = newInfo[key]?.data?.findIndex((d: any) => d?.id == id);
|
|
107
|
+
|
|
108
|
+
if (index >= 0) {
|
|
109
|
+
newInfo[key].data[index] = d;
|
|
110
|
+
} else {
|
|
111
|
+
if (newInfo[key]?.data) {
|
|
112
|
+
newInfo[key].data.unshift(d);
|
|
113
|
+
} else {
|
|
114
|
+
newInfo[key].data = [d];
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
setInfo({ ...newInfo });
|
|
119
|
+
return d;
|
|
120
|
+
} catch (error: any) {
|
|
121
|
+
const item = httpStatusCodes.find((s) => s.code == error.status);
|
|
122
|
+
|
|
123
|
+
newInfo[key].state = "error";
|
|
124
|
+
newInfo[key].errorMessage = item?.meaning || error.message;
|
|
125
|
+
if (error.status == 403) {
|
|
126
|
+
onError?.({ error, ...{ errorMessage: item?.meaning } });
|
|
127
|
+
}
|
|
128
|
+
setInfo({ ...newInfo });
|
|
129
|
+
return error;
|
|
130
|
+
}
|
|
131
|
+
};
|
|
132
|
+
const bodyCreateFunc = async (
|
|
133
|
+
data: Record<string, any> | Record<string, any>[],
|
|
134
|
+
) => {
|
|
135
|
+
const options = {
|
|
136
|
+
method: "POST",
|
|
137
|
+
url: `${baseURI}/${key}`,
|
|
138
|
+
data: Array.isArray(data) ? [...data] : { ...data },
|
|
139
|
+
headers: { Authorization: token },
|
|
140
|
+
};
|
|
141
|
+
const newInfo = { ...info };
|
|
142
|
+
// newInfo[key] = newInfo[key] || {};
|
|
143
|
+
newInfo[key].state = "loading";
|
|
144
|
+
newInfo[key].errorMessage = "";
|
|
145
|
+
|
|
146
|
+
setInfo(newInfo);
|
|
147
|
+
|
|
148
|
+
try {
|
|
149
|
+
const consulta = await axios.request(options);
|
|
150
|
+
const d = consulta.data;
|
|
151
|
+
newInfo[key].state = "success";
|
|
152
|
+
newInfo[key].errorMessage = "";
|
|
153
|
+
if (Array.isArray(data)) {
|
|
154
|
+
for (let datum of data) {
|
|
155
|
+
const index = newInfo[key]?.data?.findIndex(
|
|
156
|
+
(d: any) => d?.id == datum?.id,
|
|
157
|
+
);
|
|
158
|
+
if (index >= 0) {
|
|
159
|
+
newInfo[key].data[index] = d;
|
|
160
|
+
} else {
|
|
161
|
+
if (newInfo[key]?.data) {
|
|
162
|
+
newInfo[key].data.unshift(d);
|
|
163
|
+
} else {
|
|
164
|
+
newInfo[key].data = [d];
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
newInfo[key].data = newInfo[key].data.flat();
|
|
169
|
+
} else {
|
|
170
|
+
newInfo[key].selectedItem = d;
|
|
171
|
+
const index = newInfo[key]?.data?.findIndex(
|
|
172
|
+
(d: any) => d?.id == data?.id,
|
|
173
|
+
);
|
|
174
|
+
if (index >= 0) {
|
|
175
|
+
newInfo[key].data[index] = d;
|
|
176
|
+
} else {
|
|
177
|
+
if (newInfo[key]?.data) {
|
|
178
|
+
newInfo[key].data.unshift(d);
|
|
179
|
+
} else {
|
|
180
|
+
newInfo[key].data = [d];
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
setInfo({ ...newInfo });
|
|
185
|
+
return d;
|
|
186
|
+
} catch (error: any) {
|
|
187
|
+
const item = httpStatusCodes.find((s) => s.code == error.status);
|
|
188
|
+
|
|
189
|
+
newInfo[key].state = "error";
|
|
190
|
+
newInfo[key].errorMessage = item?.meaning || error.message;
|
|
191
|
+
if (error.status == 403) {
|
|
192
|
+
onError?.({ error, ...{ errorMessage: item?.meaning } });
|
|
193
|
+
}
|
|
194
|
+
setInfo({ ...newInfo });
|
|
195
|
+
return error;
|
|
196
|
+
}
|
|
197
|
+
};
|
|
198
|
+
const formCreateFunc = async (
|
|
199
|
+
data: Record<string, any> | Record<string, any>[],
|
|
200
|
+
) => {
|
|
201
|
+
const newInfo = { ...info };
|
|
202
|
+
newInfo[key].state = "loading";
|
|
203
|
+
newInfo[key].errorMessage = "";
|
|
204
|
+
setInfo(newInfo);
|
|
205
|
+
|
|
206
|
+
try {
|
|
207
|
+
// Convertir datos a FormData
|
|
208
|
+
const formData = new FormData();
|
|
209
|
+
|
|
210
|
+
if (Array.isArray(data)) {
|
|
211
|
+
// Suponiendo que la API acepta múltiples entradas con la misma clave
|
|
212
|
+
data.forEach((item, i) => {
|
|
213
|
+
for (const [k, v] of Object.entries(item)) {
|
|
214
|
+
formData.append(`${k}[${i}]`, v as any);
|
|
215
|
+
}
|
|
216
|
+
});
|
|
217
|
+
} else {
|
|
218
|
+
for (const [k, v] of Object.entries(data)) {
|
|
219
|
+
formData.append(k, v);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
const options = {
|
|
224
|
+
method: "POST",
|
|
225
|
+
url: `${baseURI}/${key}`,
|
|
226
|
+
data: formData,
|
|
227
|
+
headers: {
|
|
228
|
+
Authorization: token,
|
|
229
|
+
"Content-Type": "multipart/form-data",
|
|
140
230
|
},
|
|
141
231
|
};
|
|
142
|
-
});
|
|
143
232
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
233
|
+
const consulta = await axios.request(options);
|
|
234
|
+
const d = consulta.data;
|
|
235
|
+
|
|
236
|
+
newInfo[key].state = "success";
|
|
237
|
+
newInfo[key].errorMessage = "";
|
|
238
|
+
|
|
239
|
+
if (Array.isArray(data)) {
|
|
240
|
+
for (let datum of data) {
|
|
241
|
+
const index = newInfo[key]?.data?.findIndex(
|
|
242
|
+
(d: any) => d?.id == datum?.id,
|
|
243
|
+
);
|
|
244
|
+
if (index >= 0) {
|
|
245
|
+
newInfo[key].data[index] = d;
|
|
246
|
+
} else {
|
|
247
|
+
if (newInfo[key]?.data) {
|
|
248
|
+
newInfo[key].data.unshift(d);
|
|
249
|
+
} else {
|
|
250
|
+
newInfo[key].data = [d];
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
newInfo[key].data = newInfo[key].data.flat();
|
|
255
|
+
} else {
|
|
256
|
+
newInfo[key].selectedItem = d;
|
|
257
|
+
const index = newInfo[key]?.data?.findIndex(
|
|
258
|
+
(d: any) => d?.id == data?.id,
|
|
259
|
+
);
|
|
260
|
+
if (index >= 0) {
|
|
261
|
+
newInfo[key].data[index] = d;
|
|
262
|
+
} else {
|
|
263
|
+
if (newInfo[key]?.data) {
|
|
264
|
+
newInfo[key].data.unshift(d);
|
|
265
|
+
} else {
|
|
266
|
+
newInfo[key].data = [d];
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
setInfo({ ...newInfo });
|
|
272
|
+
return d;
|
|
273
|
+
} catch (error: any) {
|
|
274
|
+
const item = httpStatusCodes.find((s) => s.code == error.status);
|
|
275
|
+
newInfo[key].state = "error";
|
|
276
|
+
newInfo[key].errorMessage = item?.meaning || error.message;
|
|
277
|
+
if (error.status == 403) {
|
|
278
|
+
onError?.({ error, ...{ errorMessage: item?.meaning } });
|
|
279
|
+
}
|
|
280
|
+
setInfo({ ...newInfo });
|
|
281
|
+
return error;
|
|
282
|
+
}
|
|
283
|
+
};
|
|
284
|
+
acc[key] = {
|
|
285
|
+
...endpoint,
|
|
286
|
+
loaded: false,
|
|
287
|
+
show: showFunc,
|
|
288
|
+
find: findFunc,
|
|
289
|
+
create: async (data: Record<string, any> | Record<string, any>[]) => {
|
|
290
|
+
const hasFile = Array.isArray(data)
|
|
291
|
+
? data.some((item) =>
|
|
292
|
+
Object.values(item).some((value) => value instanceof File),
|
|
293
|
+
)
|
|
294
|
+
: Object.values(data).some((value) => value instanceof File);
|
|
295
|
+
|
|
296
|
+
if (hasFile) {
|
|
297
|
+
return await formCreateFunc(data);
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
return await bodyCreateFunc(data);
|
|
167
301
|
},
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
});
|
|
175
|
-
|
|
176
|
-
setInfo((prev) => ({
|
|
177
|
-
...prev,
|
|
178
|
-
[key]: {
|
|
179
|
-
...prev[key],
|
|
180
|
-
state: "success",
|
|
181
|
-
errorMessage: "",
|
|
182
|
-
selectedItem: d,
|
|
183
|
-
data: mergeDataArray(prev[key]?.data ?? [], d, id),
|
|
184
|
-
},
|
|
185
|
-
}));
|
|
186
|
-
|
|
187
|
-
return d;
|
|
188
|
-
} catch (error: any) {
|
|
189
|
-
setKeyError(key, error);
|
|
190
|
-
return error;
|
|
191
|
-
}
|
|
192
|
-
},
|
|
193
|
-
[baseURI, token, setKeyError],
|
|
194
|
-
);
|
|
195
|
-
|
|
196
|
-
// ------------------------------------------------------------------
|
|
197
|
-
// create (body o multipart según contenido)
|
|
198
|
-
// ------------------------------------------------------------------
|
|
199
|
-
|
|
200
|
-
const buildFormData = (
|
|
201
|
-
data: Record<string, any> | Record<string, any>[],
|
|
202
|
-
): FormData => {
|
|
203
|
-
const fd = new FormData();
|
|
204
|
-
if (Array.isArray(data)) {
|
|
205
|
-
data.forEach((item, i) => {
|
|
206
|
-
Object.entries(item).forEach(([k, v]) =>
|
|
207
|
-
fd.append(`${k}[${i}]`, v as any),
|
|
208
|
-
);
|
|
209
|
-
});
|
|
210
|
-
} else {
|
|
211
|
-
Object.entries(data).forEach(([k, v]) => fd.append(k, v));
|
|
212
|
-
}
|
|
213
|
-
return fd;
|
|
214
|
-
};
|
|
215
|
-
|
|
216
|
-
const hasFiles = (
|
|
217
|
-
data: Record<string, any> | Record<string, any>[],
|
|
218
|
-
): boolean =>
|
|
219
|
-
Array.isArray(data)
|
|
220
|
-
? data.some((item) => Object.values(item).some((v) => v instanceof File))
|
|
221
|
-
: Object.values(data).some((v) => v instanceof File);
|
|
222
|
-
|
|
223
|
-
const create = useCallback(
|
|
224
|
-
async (key: string, data: Record<string, any> | Record<string, any>[]) => {
|
|
225
|
-
setKeyLoading(key);
|
|
226
|
-
|
|
227
|
-
const isForm = hasFiles(data);
|
|
228
|
-
const payload = isForm
|
|
229
|
-
? buildFormData(data)
|
|
230
|
-
: Array.isArray(data)
|
|
231
|
-
? [...data]
|
|
232
|
-
: { ...data };
|
|
233
|
-
|
|
234
|
-
const headers: Record<string, string> = { Authorization: token };
|
|
235
|
-
if (isForm) headers["Content-Type"] = "multipart/form-data";
|
|
236
|
-
|
|
237
|
-
try {
|
|
238
|
-
const { data: d } = await axios.post(`${baseURI}/${key}`, payload, {
|
|
239
|
-
headers,
|
|
240
|
-
});
|
|
241
|
-
|
|
242
|
-
setInfo((prev) => {
|
|
243
|
-
const prevData = prev[key]?.data ?? [];
|
|
244
|
-
// Para arrays el servidor debería devolver el array creado;
|
|
245
|
-
// si devuelve un solo objeto, lo envolvemos.
|
|
246
|
-
const incoming: any[] = Array.isArray(d) ? d : [d];
|
|
247
|
-
const updatedData = incoming.reduce(
|
|
248
|
-
(acc, item) => mergeDataArray(acc, item, item?.id),
|
|
249
|
-
[...prevData],
|
|
250
|
-
);
|
|
251
|
-
|
|
252
|
-
return {
|
|
253
|
-
...prev,
|
|
254
|
-
[key]: {
|
|
255
|
-
...prev[key],
|
|
256
|
-
state: "success",
|
|
257
|
-
errorMessage: "",
|
|
258
|
-
selectedItem: Array.isArray(data) ? prev[key].selectedItem : d,
|
|
259
|
-
data: updatedData,
|
|
260
|
-
},
|
|
302
|
+
update: async (id: number, data: any) => {
|
|
303
|
+
const options = {
|
|
304
|
+
method: "PATCH",
|
|
305
|
+
url: `${baseURI}/${key}/${id}`,
|
|
306
|
+
data: Array.isArray(data) ? [...data] : { ...data },
|
|
307
|
+
headers: { Authorization: token },
|
|
261
308
|
};
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
309
|
+
const newInfo = { ...info };
|
|
310
|
+
// newInfo[key] = newInfo[key] || {};
|
|
311
|
+
newInfo[key].state = "loading";
|
|
312
|
+
newInfo[key].errorMessage = "";
|
|
313
|
+
|
|
314
|
+
setInfo(newInfo);
|
|
315
|
+
|
|
316
|
+
try {
|
|
317
|
+
const consulta = await axios.request(options);
|
|
318
|
+
const d = consulta.data;
|
|
319
|
+
newInfo[key].state = "success";
|
|
320
|
+
newInfo[key].errorMessage = "";
|
|
321
|
+
newInfo[key].selectedItem = { ...newInfo[key].selectedItem, ...d };
|
|
322
|
+
const index = newInfo[key]?.data?.findIndex(
|
|
323
|
+
(d: any) => d?.id == id,
|
|
324
|
+
);
|
|
325
|
+
|
|
326
|
+
if (index >= 0) {
|
|
327
|
+
newInfo[key].data[index] = { ...newInfo[key].data[index], ...d };
|
|
328
|
+
} else {
|
|
329
|
+
if (newInfo[key]?.data) {
|
|
330
|
+
newInfo[key].data.unshift(d);
|
|
331
|
+
} else {
|
|
332
|
+
newInfo[key].data = [d];
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
setInfo({ ...newInfo });
|
|
336
|
+
return d;
|
|
337
|
+
} catch (error: any) {
|
|
338
|
+
const item = httpStatusCodes.find((s) => s.code == error.status);
|
|
339
|
+
|
|
340
|
+
newInfo[key].state = "error";
|
|
341
|
+
newInfo[key].errorMessage = item?.meaning || error.message;
|
|
342
|
+
if (error.status == 403) {
|
|
343
|
+
onError?.({ error, ...{ errorMessage: item?.meaning } });
|
|
344
|
+
}
|
|
345
|
+
setInfo({ ...newInfo });
|
|
346
|
+
return error;
|
|
347
|
+
}
|
|
348
|
+
},
|
|
349
|
+
remove: async (id: number) => {
|
|
350
|
+
const options = {
|
|
351
|
+
method: "DELETE",
|
|
352
|
+
url: `${baseURI}/${key}/${id}`,
|
|
353
|
+
headers: { Authorization: token },
|
|
354
|
+
};
|
|
355
|
+
const newInfo = { ...info };
|
|
356
|
+
// newInfo[key] = newInfo[key] || {};
|
|
357
|
+
newInfo[key].state = "loading";
|
|
358
|
+
newInfo[key].errorMessage = "";
|
|
359
|
+
|
|
360
|
+
setInfo(newInfo);
|
|
361
|
+
|
|
362
|
+
try {
|
|
363
|
+
const consulta = await axios.request(options);
|
|
364
|
+
const d = consulta.data;
|
|
365
|
+
newInfo[key].state = "success";
|
|
366
|
+
newInfo[key].errorMessage = "";
|
|
367
|
+
newInfo[key].selectedItem = d;
|
|
368
|
+
const index = newInfo[key]?.data?.findIndex(
|
|
369
|
+
(d: any) => d?.id == id,
|
|
370
|
+
);
|
|
371
|
+
if (index >= 0) {
|
|
372
|
+
newInfo[key].data.splice(index, 1);
|
|
373
|
+
}
|
|
374
|
+
setInfo({ ...newInfo });
|
|
375
|
+
return d.data;
|
|
376
|
+
} catch (error: any) {
|
|
377
|
+
const item = httpStatusCodes.find((s) => s.code == error.status);
|
|
378
|
+
|
|
379
|
+
newInfo[key].state = "error";
|
|
380
|
+
newInfo[key].errorMessage = item?.meaning || error.message;
|
|
381
|
+
if (error.status == 403) {
|
|
382
|
+
onError?.({ error, ...{ errorMessage: item?.meaning } });
|
|
383
|
+
}
|
|
384
|
+
setInfo({ ...newInfo });
|
|
385
|
+
return error;
|
|
386
|
+
}
|
|
387
|
+
},
|
|
388
|
+
totalPages: info[key]?.totalPages,
|
|
389
|
+
currentPage: info[key]?.currentPage,
|
|
390
|
+
state: info[key]?.state || "success",
|
|
391
|
+
errorMessage: info[key]?.errorMessage,
|
|
392
|
+
params: info[key]?.params,
|
|
393
|
+
setLoaded: () => {
|
|
394
|
+
const newInfo = { ...info };
|
|
395
|
+
newInfo[key].loaded = true;
|
|
396
|
+
setInfo(newInfo);
|
|
397
|
+
},
|
|
398
|
+
getAllPages: async (limit = 100) => {
|
|
399
|
+
const allData: any[] = [];
|
|
400
|
+
let currentPage = 1;
|
|
401
|
+
let totalPages = 1;
|
|
402
|
+
|
|
403
|
+
while (currentPage <= totalPages) {
|
|
404
|
+
const response = await results[key].show({
|
|
405
|
+
limit,
|
|
406
|
+
page: currentPage,
|
|
407
|
+
});
|
|
408
|
+
|
|
409
|
+
// Importante: `show` ya actualiza info[key], así que podemos leer desde ahí
|
|
410
|
+
const currentInfo = info[key];
|
|
411
|
+
|
|
412
|
+
if (Array.isArray(response)) {
|
|
413
|
+
allData.push(...response);
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
currentPage = currentInfo.currentPage || currentPage + 1;
|
|
417
|
+
totalPages = currentInfo.totalPages || currentPage;
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
const newInfo = { ...info };
|
|
421
|
+
newInfo[key].data = allData;
|
|
422
|
+
newInfo[key].currentPage = currentPage;
|
|
423
|
+
newInfo[key].totalPages = totalPages;
|
|
424
|
+
setInfo(newInfo);
|
|
425
|
+
|
|
426
|
+
return allData;
|
|
427
|
+
},
|
|
428
|
+
data: info[key]?.data || [],
|
|
429
|
+
selectedItem: info[key]?.selectedItem || {},
|
|
430
|
+
};
|
|
272
431
|
|
|
273
|
-
|
|
274
|
-
// update
|
|
275
|
-
// ------------------------------------------------------------------
|
|
276
|
-
|
|
277
|
-
const update = useCallback(
|
|
278
|
-
async (key: string, id: number, data: any) => {
|
|
279
|
-
setKeyLoading(key);
|
|
280
|
-
|
|
281
|
-
try {
|
|
282
|
-
const { data: d } = await axios.patch(
|
|
283
|
-
`${baseURI}/${key}/${id}`,
|
|
284
|
-
Array.isArray(data) ? [...data] : { ...data },
|
|
285
|
-
{ headers: { Authorization: token } },
|
|
286
|
-
);
|
|
287
|
-
|
|
288
|
-
setInfo((prev) => ({
|
|
289
|
-
...prev,
|
|
290
|
-
[key]: {
|
|
291
|
-
...prev[key],
|
|
292
|
-
state: "success",
|
|
293
|
-
errorMessage: "",
|
|
294
|
-
selectedItem: { ...prev[key].selectedItem, ...d },
|
|
295
|
-
data: mergeDataArray(
|
|
296
|
-
prev[key]?.data ?? [],
|
|
297
|
-
{ ...prev[key]?.data?.find((i: any) => i?.id == id), ...d },
|
|
298
|
-
id,
|
|
299
|
-
),
|
|
300
|
-
},
|
|
301
|
-
}));
|
|
302
|
-
|
|
303
|
-
return d;
|
|
304
|
-
} catch (error: any) {
|
|
305
|
-
setKeyError(key, error);
|
|
306
|
-
return error;
|
|
307
|
-
}
|
|
432
|
+
return acc;
|
|
308
433
|
},
|
|
309
|
-
|
|
434
|
+
{},
|
|
310
435
|
);
|
|
311
436
|
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
setInfo((prev) => ({
|
|
326
|
-
...prev,
|
|
327
|
-
[key]: {
|
|
328
|
-
...prev[key],
|
|
329
|
-
state: "success",
|
|
330
|
-
errorMessage: "",
|
|
331
|
-
selectedItem: d,
|
|
332
|
-
data: (prev[key]?.data ?? []).filter((item: any) => item?.id != id),
|
|
333
|
-
},
|
|
334
|
-
}));
|
|
335
|
-
|
|
336
|
-
return d;
|
|
337
|
-
} catch (error: any) {
|
|
338
|
-
setKeyError(key, error);
|
|
339
|
-
return error;
|
|
437
|
+
async function doSome() {
|
|
438
|
+
const key = Object.keys(info).find((k) => {
|
|
439
|
+
return info[k].loaded === false;
|
|
440
|
+
});
|
|
441
|
+
|
|
442
|
+
if (key) {
|
|
443
|
+
if (info[key]?.id) {
|
|
444
|
+
await results[key].find(info[key]?.id, info[key]?.defaultParams);
|
|
445
|
+
} else if (info[key]?.defaultParams) {
|
|
446
|
+
await results[key].show(info[key]?.defaultParams);
|
|
447
|
+
} else {
|
|
448
|
+
results[key].setLoaded();
|
|
340
449
|
}
|
|
341
|
-
},
|
|
342
|
-
[baseURI, token, setKeyLoading, setKeyError],
|
|
343
|
-
);
|
|
344
|
-
|
|
345
|
-
// ------------------------------------------------------------------
|
|
346
|
-
// getAllPages — ya no usa referencia circular a results
|
|
347
|
-
// ------------------------------------------------------------------
|
|
348
|
-
|
|
349
|
-
const getAllPages = useCallback(
|
|
350
|
-
async (key: string, limit = 100) => {
|
|
351
|
-
const allData: any[] = [];
|
|
352
|
-
let page = 1;
|
|
353
|
-
let totalPages = 1;
|
|
354
|
-
|
|
355
|
-
do {
|
|
356
|
-
const pageData = await show(key, { limit, page, merge: false });
|
|
357
|
-
if (Array.isArray(pageData)) allData.push(...pageData);
|
|
358
|
-
// Leer totalPages desde infoRef (siempre fresco)
|
|
359
|
-
totalPages = infoRef.current[key]?.totalPages ?? page;
|
|
360
|
-
page++;
|
|
361
|
-
} while (page <= totalPages);
|
|
362
|
-
|
|
363
|
-
setInfo((prev) => ({
|
|
364
|
-
...prev,
|
|
365
|
-
[key]: { ...prev[key], data: allData },
|
|
366
|
-
}));
|
|
367
|
-
|
|
368
|
-
return allData;
|
|
369
|
-
},
|
|
370
|
-
[show],
|
|
371
|
-
);
|
|
372
|
-
|
|
373
|
-
// ------------------------------------------------------------------
|
|
374
|
-
// Auto-load al montar
|
|
375
|
-
// ------------------------------------------------------------------
|
|
376
|
-
|
|
377
|
-
useEffect(() => {
|
|
378
|
-
const key = Object.keys(info).find((k) => info[k].loaded === false);
|
|
379
|
-
if (!key) return;
|
|
380
|
-
|
|
381
|
-
if (info[key]?.id) {
|
|
382
|
-
find(key, info[key].id as any, info[key].defaultParams);
|
|
383
|
-
} else if (info[key]?.defaultParams) {
|
|
384
|
-
show(key, info[key].defaultParams);
|
|
385
|
-
} else {
|
|
386
|
-
setInfo((prev) => ({
|
|
387
|
-
...prev,
|
|
388
|
-
[key]: { ...prev[key], loaded: true },
|
|
389
|
-
}));
|
|
390
450
|
}
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
.map((k) => info[k].loaded)
|
|
396
|
-
.join(","),
|
|
397
|
-
]);
|
|
398
|
-
|
|
399
|
-
// ------------------------------------------------------------------
|
|
400
|
-
// Construir el objeto results que se expone al consumidor
|
|
401
|
-
// ------------------------------------------------------------------
|
|
402
|
-
|
|
403
|
-
const results = Object.keys(endpoints).reduce((acc: any, key) => {
|
|
404
|
-
const s = info[key];
|
|
405
|
-
acc[key] = {
|
|
406
|
-
...endpoints[key],
|
|
407
|
-
data: s?.data ?? [],
|
|
408
|
-
selectedItem: s?.selectedItem ?? {},
|
|
409
|
-
state: s?.state ?? "success",
|
|
410
|
-
errorMessage: s?.errorMessage,
|
|
411
|
-
params: s?.params,
|
|
412
|
-
totalPages: s?.totalPages,
|
|
413
|
-
currentPage: s?.currentPage,
|
|
414
|
-
totalItems: s?.totalItems,
|
|
415
|
-
loaded: s?.loaded ?? false,
|
|
416
|
-
show: (opts: ShowOptions) => show(key, opts),
|
|
417
|
-
find: (id: number, query?: any) => find(key, id, query),
|
|
418
|
-
create: (data: Record<string, any> | Record<string, any>[]) =>
|
|
419
|
-
create(key, data),
|
|
420
|
-
update: (id: number, data: any) => update(key, id, data),
|
|
421
|
-
remove: (id: number) => remove(key, id),
|
|
422
|
-
getAllPages: (limit?: number) => getAllPages(key, limit),
|
|
423
|
-
setLoaded: () =>
|
|
424
|
-
setInfo((prev) => ({ ...prev, [key]: { ...prev[key], loaded: true } })),
|
|
425
|
-
};
|
|
426
|
-
return acc;
|
|
427
|
-
}, {} as EnhancedEndpoints<T>);
|
|
451
|
+
}
|
|
452
|
+
useEffect(() => {
|
|
453
|
+
doSome();
|
|
454
|
+
}, [JSON.stringify(Object.values(info).map((v) => v.loaded))]);
|
|
428
455
|
|
|
429
456
|
return results;
|
|
430
457
|
}
|