next-recomponents 2.0.36 → 2.0.39

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.
@@ -1,9 +1,40 @@
1
- import { useEffect, useMemo, useState } from "react";
1
+ import { useCallback, useEffect, useRef, 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
+
7
38
  export default function useResources<T extends Record<string, ItemsRecord>>({
8
39
  baseURI,
9
40
  endpoints,
@@ -11,447 +42,389 @@ export default function useResources<T extends Record<string, ItemsRecord>>({
11
42
  }: Props<T>) {
12
43
  const token = useToken();
13
44
 
14
- const [info, setInfo] = useState<EnhancedEndpoints<T>>(
45
+ const [info, setInfo] = useState<EnhancedEndpoints<T>>(() =>
15
46
  Object.keys(endpoints).reduce((acc: any, key) => {
16
- const newAcc: any = {
17
- ...acc,
18
- [key]: {
19
- loaded: false,
20
- id: endpoints[key]?.id,
21
- defaultParams: endpoints[key]?.defaultParams,
22
- data: [],
23
- selectedItem: {},
24
- },
47
+ acc[key] = {
48
+ loaded: false,
49
+ id: endpoints[key]?.id,
50
+ defaultParams: endpoints[key]?.defaultParams,
51
+ data: [],
52
+ selectedItem: {},
53
+ state: "success",
54
+ errorMessage: "",
25
55
  };
26
-
27
- return newAcc;
56
+ return acc;
28
57
  }, {} as any),
29
58
  );
30
59
 
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}`,
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),
87
+ [key]: {
88
+ ...cloneKey(prev, key)[key],
89
+ state: "error",
90
+ errorMessage: meaning || error?.message,
91
+ },
92
+ }));
93
+ },
94
+ [onError],
95
+ );
96
+
97
+ // ------------------------------------------------------------------
98
+ // show
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
+ }));
116
+
117
+ try {
118
+ const { data: res } = await axios.get(`${baseURI}/${key}`, {
41
119
  params: { limit, page, ...query },
42
120
  headers: { Authorization: token },
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",
121
+ });
122
+
123
+ setInfo((prev) => {
124
+ const prevData = prev[key]?.data ?? [];
125
+ const merged = merge
126
+ ? page === 1
127
+ ? res.data
128
+ : [...prevData, ...res.data]
129
+ : res.data;
130
+ return {
131
+ ...prev,
132
+ [key]: {
133
+ ...prev[key],
134
+ state: "success",
135
+ errorMessage: "",
136
+ data: merged,
137
+ totalItems: res.totalItems,
138
+ totalPages: res.totalPages,
139
+ currentPage: res.currentPage,
230
140
  },
231
141
  };
142
+ });
232
143
 
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);
301
- },
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 },
308
- };
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
- }
144
+ return res.data as any[];
145
+ } catch (error: any) {
146
+ setKeyError(key, error);
147
+ return error;
148
+ }
149
+ },
150
+ [baseURI, token, setKeyError],
151
+ );
152
+
153
+ // ------------------------------------------------------------------
154
+ // find
155
+ // ------------------------------------------------------------------
156
+
157
+ const find = useCallback(
158
+ async (key: string, id: number, query?: any) => {
159
+ setInfo((prev) => ({
160
+ ...cloneKey(prev, key),
161
+ [key]: {
162
+ ...cloneKey(prev, key)[key],
163
+ state: "loading",
164
+ errorMessage: "",
165
+ params: query,
166
+ loaded: true,
348
167
  },
349
- remove: async (id: number) => {
350
- const options = {
351
- method: "DELETE",
352
- url: `${baseURI}/${key}/${id}`,
353
- headers: { Authorization: token },
168
+ }));
169
+
170
+ try {
171
+ const { data: d } = await axios.get(`${baseURI}/${key}/${id}`, {
172
+ params: query,
173
+ headers: { Authorization: token },
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
+ },
354
261
  };
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
- };
262
+ });
431
263
 
432
- return acc;
264
+ return d;
265
+ } catch (error: any) {
266
+ setKeyError(key, error);
267
+ return error;
268
+ }
433
269
  },
434
- {},
270
+ [baseURI, token, setKeyLoading, setKeyError],
435
271
  );
436
272
 
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();
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;
449
307
  }
450
- }
451
- }
308
+ },
309
+ [baseURI, token, setKeyLoading, setKeyError],
310
+ );
311
+
312
+ // ------------------------------------------------------------------
313
+ // remove
314
+ // ------------------------------------------------------------------
315
+
316
+ const remove = useCallback(
317
+ async (key: string, id: number) => {
318
+ setKeyLoading(key);
319
+
320
+ try {
321
+ const { data: d } = await axios.delete(`${baseURI}/${key}/${id}`, {
322
+ headers: { Authorization: token },
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;
340
+ }
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
+
452
377
  useEffect(() => {
453
- doSome();
454
- }, [JSON.stringify(Object.values(info).map((v) => v.loaded))]);
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
+ }
391
+ // Solo se dispara cuando cambia cuáles keys están cargados
392
+ // eslint-disable-next-line react-hooks/exhaustive-deps
393
+ }, [
394
+ Object.keys(info)
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>);
455
428
 
456
429
  return results;
457
430
  }