next-recomponents 2.0.38 → 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.
- package/dist/index.d.mts +1 -31
- package/dist/index.d.ts +1 -31
- package/dist/index.js +291 -395
- package/dist/index.mjs +316 -420
- package/package.json +1 -1
- package/src/use-resources/index.ts +378 -475
|
@@ -1,9 +1,40 @@
|
|
|
1
|
-
import { useEffect,
|
|
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,517 +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
|
-
|
|
17
|
-
|
|
18
|
-
[key]
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
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
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
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
|
-
// Helper para actualizar el array sin mutar
|
|
133
|
-
const mergeDataArray = (
|
|
134
|
-
existingData: any[] | undefined,
|
|
135
|
-
newItem: any,
|
|
136
|
-
matchId?: any,
|
|
137
|
-
): any[] => {
|
|
138
|
-
if (!existingData) return [newItem];
|
|
139
|
-
const index = existingData.findIndex((d: any) => d?.id == matchId);
|
|
140
|
-
if (index >= 0) {
|
|
141
|
-
return existingData.map((d: any, i: number) =>
|
|
142
|
-
i === index ? newItem : d,
|
|
143
|
-
);
|
|
144
|
-
}
|
|
145
|
-
return [newItem, ...existingData];
|
|
146
|
-
};
|
|
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
|
+
);
|
|
147
96
|
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
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
|
+
}));
|
|
157
116
|
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
let updatedData: any[];
|
|
175
|
-
|
|
176
|
-
if (Array.isArray(data)) {
|
|
177
|
-
updatedData = [...(newInfo[key]?.data ?? [])];
|
|
178
|
-
for (const datum of data) {
|
|
179
|
-
updatedData = mergeDataArray(updatedData, d, datum?.id);
|
|
180
|
-
}
|
|
181
|
-
updatedData = updatedData.flat();
|
|
182
|
-
} else {
|
|
183
|
-
updatedData = mergeDataArray(
|
|
184
|
-
newInfo[key]?.data,
|
|
185
|
-
d,
|
|
186
|
-
(data as any)?.id,
|
|
187
|
-
);
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
setInfo({
|
|
191
|
-
...newInfo,
|
|
117
|
+
try {
|
|
118
|
+
const { data: res } = await axios.get(`${baseURI}/${key}`, {
|
|
119
|
+
params: { limit, page, ...query },
|
|
120
|
+
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,
|
|
192
132
|
[key]: {
|
|
193
|
-
...
|
|
133
|
+
...prev[key],
|
|
194
134
|
state: "success",
|
|
195
135
|
errorMessage: "",
|
|
196
|
-
|
|
197
|
-
|
|
136
|
+
data: merged,
|
|
137
|
+
totalItems: res.totalItems,
|
|
138
|
+
totalPages: res.totalPages,
|
|
139
|
+
currentPage: res.currentPage,
|
|
198
140
|
},
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
return d;
|
|
202
|
-
} catch (error: any) {
|
|
203
|
-
const item = httpStatusCodes.find((s) => s.code == error.status);
|
|
141
|
+
};
|
|
142
|
+
});
|
|
204
143
|
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
144
|
+
return res.data as any[];
|
|
145
|
+
} catch (error: any) {
|
|
146
|
+
setKeyError(key, error);
|
|
147
|
+
return error;
|
|
148
|
+
}
|
|
149
|
+
},
|
|
150
|
+
[baseURI, token, setKeyError],
|
|
151
|
+
);
|
|
208
152
|
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
...newInfo[key],
|
|
213
|
-
state: "error",
|
|
214
|
-
errorMessage: item?.meaning || error.message,
|
|
215
|
-
},
|
|
216
|
-
});
|
|
153
|
+
// ------------------------------------------------------------------
|
|
154
|
+
// find
|
|
155
|
+
// ------------------------------------------------------------------
|
|
217
156
|
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
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,
|
|
167
|
+
},
|
|
168
|
+
}));
|
|
221
169
|
|
|
222
|
-
|
|
223
|
-
data:
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
if (Array.isArray(data)) {
|
|
228
|
-
data.forEach((item, i) => {
|
|
229
|
-
for (const [k, v] of Object.entries(item)) {
|
|
230
|
-
formData.append(`${k}[${i}]`, v as any);
|
|
231
|
-
}
|
|
232
|
-
});
|
|
233
|
-
} else {
|
|
234
|
-
for (const [k, v] of Object.entries(data)) {
|
|
235
|
-
formData.append(k, v);
|
|
236
|
-
}
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
const options = {
|
|
240
|
-
method: "POST",
|
|
241
|
-
url: `${baseURI}/${key}`,
|
|
242
|
-
data: formData,
|
|
243
|
-
headers: {
|
|
244
|
-
Authorization: token,
|
|
245
|
-
"Content-Type": "multipart/form-data",
|
|
246
|
-
},
|
|
247
|
-
};
|
|
170
|
+
try {
|
|
171
|
+
const { data: d } = await axios.get(`${baseURI}/${key}/${id}`, {
|
|
172
|
+
params: query,
|
|
173
|
+
headers: { Authorization: token },
|
|
174
|
+
});
|
|
248
175
|
|
|
249
|
-
|
|
250
|
-
...
|
|
176
|
+
setInfo((prev) => ({
|
|
177
|
+
...prev,
|
|
251
178
|
[key]: {
|
|
252
|
-
...
|
|
253
|
-
|
|
254
|
-
state: "loading",
|
|
179
|
+
...prev[key],
|
|
180
|
+
state: "success",
|
|
255
181
|
errorMessage: "",
|
|
182
|
+
selectedItem: d,
|
|
183
|
+
data: mergeDataArray(prev[key]?.data ?? [], d, id),
|
|
256
184
|
},
|
|
257
|
-
};
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
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,
|
|
283
254
|
[key]: {
|
|
284
|
-
...
|
|
255
|
+
...prev[key],
|
|
285
256
|
state: "success",
|
|
286
257
|
errorMessage: "",
|
|
287
|
-
selectedItem: Array.isArray(data) ?
|
|
258
|
+
selectedItem: Array.isArray(data) ? prev[key].selectedItem : d,
|
|
288
259
|
data: updatedData,
|
|
289
260
|
},
|
|
290
|
-
}
|
|
261
|
+
};
|
|
262
|
+
});
|
|
291
263
|
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
264
|
+
return d;
|
|
265
|
+
} catch (error: any) {
|
|
266
|
+
setKeyError(key, error);
|
|
267
|
+
return error;
|
|
268
|
+
}
|
|
269
|
+
},
|
|
270
|
+
[baseURI, token, setKeyLoading, setKeyError],
|
|
271
|
+
);
|
|
295
272
|
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
273
|
+
// ------------------------------------------------------------------
|
|
274
|
+
// update
|
|
275
|
+
// ------------------------------------------------------------------
|
|
299
276
|
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
...newInfo[key],
|
|
304
|
-
state: "error",
|
|
305
|
-
errorMessage: item?.meaning || error.message,
|
|
306
|
-
},
|
|
307
|
-
});
|
|
277
|
+
const update = useCallback(
|
|
278
|
+
async (key: string, id: number, data: any) => {
|
|
279
|
+
setKeyLoading(key);
|
|
308
280
|
|
|
309
|
-
|
|
310
|
-
}
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
show: showFunc,
|
|
316
|
-
find: findFunc,
|
|
317
|
-
create: async (data: Record<string, any> | Record<string, any>[]) => {
|
|
318
|
-
const hasFile = Array.isArray(data)
|
|
319
|
-
? data.some((item) =>
|
|
320
|
-
Object.values(item).some((value) => value instanceof File),
|
|
321
|
-
)
|
|
322
|
-
: Object.values(data).some((value) => value instanceof File);
|
|
323
|
-
|
|
324
|
-
if (hasFile) {
|
|
325
|
-
return await formCreateFunc(data);
|
|
326
|
-
}
|
|
327
|
-
|
|
328
|
-
return await bodyCreateFunc(data);
|
|
329
|
-
},
|
|
330
|
-
update: async (id: number, data: any) => {
|
|
331
|
-
const options = {
|
|
332
|
-
method: "PATCH",
|
|
333
|
-
url: `${baseURI}/${key}/${id}`,
|
|
334
|
-
data: Array.isArray(data) ? [...data] : { ...data },
|
|
335
|
-
headers: { Authorization: token },
|
|
336
|
-
};
|
|
281
|
+
try {
|
|
282
|
+
const { data: d } = await axios.patch(
|
|
283
|
+
`${baseURI}/${key}/${id}`,
|
|
284
|
+
Array.isArray(data) ? [...data] : { ...data },
|
|
285
|
+
{ headers: { Authorization: token } },
|
|
286
|
+
);
|
|
337
287
|
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
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
|
+
}));
|
|
347
302
|
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
);
|
|
357
|
-
|
|
358
|
-
const updatedData =
|
|
359
|
-
index >= 0
|
|
360
|
-
? newInfo[key].data.map(
|
|
361
|
-
(
|
|
362
|
-
item: any,
|
|
363
|
-
i: number, // ✅ map en lugar de mutación
|
|
364
|
-
) => (i === index ? { ...item, ...d } : item),
|
|
365
|
-
)
|
|
366
|
-
: newInfo[key]?.data
|
|
367
|
-
? [d, ...newInfo[key].data] // ✅ nuevo array en lugar de unshift
|
|
368
|
-
: [d];
|
|
369
|
-
|
|
370
|
-
const updatedInfo = {
|
|
371
|
-
...newInfo,
|
|
372
|
-
[key]: {
|
|
373
|
-
...newInfo[key],
|
|
374
|
-
state: "success",
|
|
375
|
-
errorMessage: "",
|
|
376
|
-
selectedItem: { ...newInfo[key].selectedItem, ...d },
|
|
377
|
-
data: updatedData,
|
|
378
|
-
},
|
|
379
|
-
};
|
|
380
|
-
|
|
381
|
-
setInfo(updatedInfo);
|
|
382
|
-
return d;
|
|
383
|
-
} catch (error: any) {
|
|
384
|
-
const item = httpStatusCodes.find((s) => s.code == error.status);
|
|
385
|
-
|
|
386
|
-
setInfo({
|
|
387
|
-
...newInfo,
|
|
388
|
-
[key]: {
|
|
389
|
-
...newInfo[key],
|
|
390
|
-
state: "error",
|
|
391
|
-
errorMessage: item?.meaning || error.message,
|
|
392
|
-
},
|
|
393
|
-
});
|
|
394
|
-
|
|
395
|
-
if (error.status == 403) {
|
|
396
|
-
onError?.({ error, ...{ errorMessage: item?.meaning } });
|
|
397
|
-
}
|
|
398
|
-
|
|
399
|
-
return error;
|
|
400
|
-
}
|
|
401
|
-
},
|
|
402
|
-
remove: async (id: number) => {
|
|
403
|
-
const options = {
|
|
404
|
-
method: "DELETE",
|
|
405
|
-
url: `${baseURI}/${key}/${id}`,
|
|
406
|
-
headers: { Authorization: token },
|
|
407
|
-
};
|
|
303
|
+
return d;
|
|
304
|
+
} catch (error: any) {
|
|
305
|
+
setKeyError(key, error);
|
|
306
|
+
return error;
|
|
307
|
+
}
|
|
308
|
+
},
|
|
309
|
+
[baseURI, token, setKeyLoading, setKeyError],
|
|
310
|
+
);
|
|
408
311
|
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
...info[key],
|
|
413
|
-
data: [...(info[key]?.data ?? [])],
|
|
414
|
-
state: "loading",
|
|
415
|
-
errorMessage: "",
|
|
416
|
-
},
|
|
417
|
-
};
|
|
312
|
+
// ------------------------------------------------------------------
|
|
313
|
+
// remove
|
|
314
|
+
// ------------------------------------------------------------------
|
|
418
315
|
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
const consulta = await axios.request(options);
|
|
423
|
-
const d = consulta.data;
|
|
424
|
-
|
|
425
|
-
setInfo({
|
|
426
|
-
...newInfo,
|
|
427
|
-
[key]: {
|
|
428
|
-
...newInfo[key],
|
|
429
|
-
state: "success",
|
|
430
|
-
errorMessage: "",
|
|
431
|
-
selectedItem: d,
|
|
432
|
-
data:
|
|
433
|
-
newInfo[key]?.data?.filter((item: any) => item?.id != id) ??
|
|
434
|
-
[], // ✅ filter en lugar de splice
|
|
435
|
-
},
|
|
436
|
-
});
|
|
437
|
-
|
|
438
|
-
return d.data;
|
|
439
|
-
} catch (error: any) {
|
|
440
|
-
const item = httpStatusCodes.find((s) => s.code == error.status);
|
|
441
|
-
|
|
442
|
-
if (error.status == 403) {
|
|
443
|
-
onError?.({ error, errorMessage: item?.meaning });
|
|
444
|
-
}
|
|
445
|
-
|
|
446
|
-
setInfo({
|
|
447
|
-
...newInfo,
|
|
448
|
-
[key]: {
|
|
449
|
-
...newInfo[key],
|
|
450
|
-
state: "error",
|
|
451
|
-
errorMessage: item?.meaning || error.message,
|
|
452
|
-
},
|
|
453
|
-
});
|
|
454
|
-
|
|
455
|
-
return error;
|
|
456
|
-
}
|
|
457
|
-
},
|
|
458
|
-
totalPages: info[key]?.totalPages,
|
|
459
|
-
currentPage: info[key]?.currentPage,
|
|
460
|
-
state: info[key]?.state || "success",
|
|
461
|
-
errorMessage: info[key]?.errorMessage,
|
|
462
|
-
params: info[key]?.params,
|
|
463
|
-
setLoaded: () => {
|
|
464
|
-
const newInfo = { ...info };
|
|
465
|
-
newInfo[key].loaded = true;
|
|
466
|
-
setInfo(newInfo);
|
|
467
|
-
},
|
|
468
|
-
getAllPages: async (limit = 100) => {
|
|
469
|
-
const allData: any[] = [];
|
|
470
|
-
let currentPage = 1;
|
|
471
|
-
let totalPages = 1;
|
|
472
|
-
|
|
473
|
-
while (currentPage <= totalPages) {
|
|
474
|
-
const response = await results[key].show({
|
|
475
|
-
limit,
|
|
476
|
-
page: currentPage,
|
|
477
|
-
});
|
|
478
|
-
|
|
479
|
-
// Importante: `show` ya actualiza info[key], así que podemos leer desde ahí
|
|
480
|
-
const currentInfo = info[key];
|
|
481
|
-
|
|
482
|
-
if (Array.isArray(response)) {
|
|
483
|
-
allData.push(...response);
|
|
484
|
-
}
|
|
485
|
-
|
|
486
|
-
currentPage = currentInfo.currentPage || currentPage + 1;
|
|
487
|
-
totalPages = currentInfo.totalPages || currentPage;
|
|
488
|
-
}
|
|
489
|
-
|
|
490
|
-
const newInfo = { ...info };
|
|
491
|
-
newInfo[key].data = allData;
|
|
492
|
-
newInfo[key].currentPage = currentPage;
|
|
493
|
-
newInfo[key].totalPages = totalPages;
|
|
494
|
-
setInfo(newInfo);
|
|
495
|
-
|
|
496
|
-
return allData;
|
|
497
|
-
},
|
|
498
|
-
data: info[key]?.data || [],
|
|
499
|
-
selectedItem: info[key]?.selectedItem || {},
|
|
500
|
-
};
|
|
316
|
+
const remove = useCallback(
|
|
317
|
+
async (key: string, id: number) => {
|
|
318
|
+
setKeyLoading(key);
|
|
501
319
|
|
|
502
|
-
|
|
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
|
+
}
|
|
503
341
|
},
|
|
504
|
-
|
|
342
|
+
[baseURI, token, setKeyLoading, setKeyError],
|
|
505
343
|
);
|
|
506
344
|
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
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
|
+
|
|
522
377
|
useEffect(() => {
|
|
523
|
-
|
|
524
|
-
|
|
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>);
|
|
525
428
|
|
|
526
429
|
return results;
|
|
527
430
|
}
|