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.
@@ -1,40 +1,9 @@
1
- import { useCallback, useEffect, useRef, useState } from "react";
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
- acc[key] = {
48
- loaded: false,
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
- ...cloneKey(prev, key)[key],
89
- state: "error",
90
- errorMessage: meaning || error?.message,
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
- // 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
- }));
27
+ return newAcc;
28
+ }, {} as any),
29
+ );
116
30
 
117
- try {
118
- const { data: res } = await axios.get(`${baseURI}/${key}`, {
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
- 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,
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
- 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,
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
- 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
- },
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
- return d;
265
- } catch (error: any) {
266
- setKeyError(key, error);
267
- return error;
268
- }
269
- },
270
- [baseURI, token, setKeyLoading, setKeyError],
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
- [baseURI, token, setKeyLoading, setKeyError],
434
+ {},
310
435
  );
311
436
 
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;
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
- // 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>);
451
+ }
452
+ useEffect(() => {
453
+ doSome();
454
+ }, [JSON.stringify(Object.values(info).map((v) => v.loaded))]);
428
455
 
429
456
  return results;
430
457
  }