various fixes to the site (socios, ingresos, gastos, balance, solicitudes) have been made

This commit is contained in:
Jose
2026-01-26 14:15:38 +01:00
parent 59efb7d81f
commit 4aada8a151
10 changed files with 111 additions and 84 deletions

View File

@@ -28,7 +28,7 @@
}, },
"incomes": { "incomes": {
"all": "/incomes", "all": "/incomes",
"withNames": "/incomes/with-names", "withInfo": "/incomes/with-info",
"mine": "/incomes/mine", "mine": "/incomes/mine",
"byId": "/incomes/:incomeId" "byId": "/incomes/:incomeId"
}, },
@@ -37,7 +37,8 @@
"byId": "/expenses/:expenseId" "byId": "/expenses/:expenseId"
}, },
"balance": { "balance": {
"all": "/balance" "all": "/balance",
"withTotals": "/balance/with-totals"
}, },
"announcements": { "announcements": {
"all": "/announcements", "all": "/announcements",

View File

@@ -6,7 +6,8 @@
"auth": { "auth": {
"login": "/auth/login", "login": "/auth/login",
"refreshToken": "/auth/refresh", "refreshToken": "/auth/refresh",
"changePassword": "/auth/change-password" "changePassword": "/auth/change-password",
"validateToken": "/auth/validate"
}, },
"users": { "users": {
"all": "/users", "all": "/users",
@@ -27,7 +28,7 @@
}, },
"incomes": { "incomes": {
"all": "/incomes", "all": "/incomes",
"withNames": "/incomes/with-names", "withInfo": "/incomes/with-info",
"mine": "/incomes/mine", "mine": "/incomes/mine",
"byId": "/incomes/:incomeId" "byId": "/incomes/:incomeId"
}, },
@@ -36,7 +37,8 @@
"byId": "/expenses/:expenseId" "byId": "/expenses/:expenseId"
}, },
"balance": { "balance": {
"all": "/balance" "all": "/balance",
"withTotals": "/balance/with-totals"
}, },
"announcements": { "announcements": {
"all": "/announcements", "all": "/announcements",

View File

@@ -64,6 +64,8 @@ const IngresoCard = ({
type: income.type ?? CONSTANTS.PAYMENT_TYPE_CASH, type: income.type ?? CONSTANTS.PAYMENT_TYPE_CASH,
frequency: income.frequency ?? CONSTANTS.PAYMENT_FREQUENCY_YEARLY, frequency: income.frequency ?? CONSTANTS.PAYMENT_FREQUENCY_YEARLY,
memberNumber: income.memberNumber, memberNumber: income.memberNumber,
userId: income.userId,
displayName: income.displayName || '',
createdAt: income.createdAt?.slice(0, 16) || (isNew ? getNowAsLocalDatetime() : ''), createdAt: income.createdAt?.slice(0, 16) || (isNew ? getNowAsLocalDatetime() : ''),
}); });
@@ -74,14 +76,29 @@ const IngresoCard = ({
amount: income.amount || 0, amount: income.amount || 0,
type: income.type ?? CONSTANTS.PAYMENT_TYPE_CASH, type: income.type ?? CONSTANTS.PAYMENT_TYPE_CASH,
frequency: income.frequency ?? CONSTANTS.PAYMENT_FREQUENCY_YEARLY, frequency: income.frequency ?? CONSTANTS.PAYMENT_FREQUENCY_YEARLY,
displayName: income.displayName, userId: income.userId,
memberNumber: income.memberNumber, memberNumber: income.memberNumber,
displayName: income.displayName || '',
createdAt: income.createdAt?.slice(0, 16) || (isNew ? getNowAsLocalDatetime() : ''), createdAt: income.createdAt?.slice(0, 16) || (isNew ? getNowAsLocalDatetime() : ''),
}); });
} }
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
}, [income, editMode]); }, [income, editMode]);
useEffect(() => {
if (formData.memberNumber && !formData.userId) {
const member = members.find(m => m.memberNumber === formData.memberNumber);
if (member) {
setFormData(prev => ({
...prev,
userId: member.userId,
displayName: member.displayName
}));
}
}
}, [formData.memberNumber, formData.userId, members]);
const handleChange = (field, value) => const handleChange = (field, value) =>
setFormData(prev => ({ ...prev, [field]: value })); setFormData(prev => ({ ...prev, [field]: value }));
@@ -167,13 +184,20 @@ const IngresoCard = ({
<Form.Select <Form.Select
className="themed-input" className="themed-input"
size="sm" size="sm"
value={formData.memberNumber} value={formData.memberNumber ?? ""}
onChange={(e) => handleChange('memberNumber', parseInt(e.target.value))} onChange={(e) => {
style={{ maxWidth: '300px', display: 'inline-block' }} const memberNumber = parseInt(e.target.value);
const member = members.find(m => m.memberNumber === memberNumber);
handleChange('memberNumber', memberNumber);
handleChange('userId', member?.userId ?? null);
handleChange('displayName', member?.displayName ?? "");
}}
> >
{uniqueMembers.map((m) => ( <option value="" disabled>Selecciona socio</option>
<option key={m.memberNumber} value={m.memberNumber}> {uniqueMembers.map((i) => (
{`${m.displayName} (${m.memberNumber})`} <option key={i.memberNumber} value={i.memberNumber}>
{`${i.displayName} (${i.memberNumber})`}
</option> </option>
))} ))}
</Form.Select> </Form.Select>

View File

@@ -128,7 +128,7 @@ const SocioCard = ({ identity, isNew = false, onCreate, onUpdate, onDelete, onCa
plotNumber: identity.metadata.plotNumber, plotNumber: identity.metadata.plotNumber,
notes: identity.metadata.notes || '', notes: identity.metadata.notes || '',
status: identity.account.status, status: identity.account.status,
type: identity.metadat.type, type: identity.metadata.type,
createdAt: identity.metadata.createdAt?.slice(0, 16) || (isNew ? getNowAsLocalDatetime() : ''), createdAt: identity.metadata.createdAt?.slice(0, 16) || (isNew ? getNowAsLocalDatetime() : ''),
assignedAt: identity.metadata.assignedAt?.slice(0, 16) || undefined, assignedAt: identity.metadata.assignedAt?.slice(0, 16) || undefined,
deactivatedAt: identity.metadata.deactivatedAt?.slice(0, 16) || undefined, deactivatedAt: identity.metadata.deactivatedAt?.slice(0, 16) || undefined,

View File

@@ -97,7 +97,7 @@ export const SociosPDF = ({ socios }) => (
<Text style={[styles.headerCell, { flex: 1 }]}>Tipo</Text> <Text style={[styles.headerCell, { flex: 1 }]}>Tipo</Text>
</View> </View>
{socios.map((socio, idx) => ( {socios.map((identity, idx) => (
<View <View
key={idx} key={idx}
style={[ style={[
@@ -107,16 +107,16 @@ export const SociosPDF = ({ socios }) => (
{ borderBottomRightRadius: idx === socios.length - 1 ? 10 : 0 }, { borderBottomRightRadius: idx === socios.length - 1 ? 10 : 0 },
]} ]}
> >
<Text style={[styles.cell, { flex: 0.2 }]}>{socio?.memberNumber}</Text> <Text style={[styles.cell, { flex: 0.2 }]}>{identity?.metadata?.memberNumber}</Text>
<Text style={[styles.cell, { flex: 0.2 }]}>{socio?.plotNumber}</Text> <Text style={[styles.cell, { flex: 0.2 }]}>{identity?.metadata?.plotNumber}</Text>
<Text style={[styles.cell, { flex: 3 }]}>{socio?.displayName}</Text> <Text style={[styles.cell, { flex: 3 }]}>{identity?.user?.displayName}</Text>
<Text style={[styles.cell, { flex: 1 }]}>{socio?.dni}</Text> <Text style={[styles.cell, { flex: 1 }]}>{identity?.metadata?.dni}</Text>
<Text style={[styles.cell, { flex: 1 }]}>{socio?.phone}</Text> <Text style={[styles.cell, { flex: 1 }]}>{identity?.metadata?.phone}</Text>
<Text style={[styles.cell, { flex: 3 }]}>{socio?.email || ''}</Text> <Text style={[styles.cell, { flex: 3 }]}>{identity?.account?.email || ''}</Text>
<Text style={[styles.cell, { flex: 1 }]}>{parseDate(socio?.createdAt?.split('T')[0] || '')}</Text> <Text style={[styles.cell, { flex: 1 }]}>{parseDate(identity?.metadata?.createdAt?.split('T')[0] || '')}</Text>
<Text style={[styles.cell, { flex: 1 }]}> <Text style={[styles.cell, { flex: 1 }]}>
{(() => { {(() => {
switch (socio?.type) { switch (identity?.metadata?.type) {
case 0: return 'L. Espera'; case 0: return 'L. Espera';
case 1: return 'Hortelano'; case 1: return 'Hortelano';
case 2: return 'Invernadero'; case 2: return 'Invernadero';

View File

@@ -35,15 +35,15 @@ const getPFP = (tipo) => {
}; };
const renderDescripcionSolicitud = (data, onProfile) => { const renderDescripcionSolicitud = (data, onProfile) => {
const { type, status, requestedByName, preDisplayName } = data; console.log(data);
switch (type) { switch (data.requestType) {
case 0: case 0:
if (requestedByName) { if (data.requestedByName) {
return `${requestedByName} quiere darse de alta.`; return `${data.requestedByName} quiere darse de alta.`;
} else if (status !== 1 && preDisplayName) { } else if (data.requestStatus !== 1 && data.preDisplayName) {
return `${preDisplayName} quiere darse de alta.`; return `${data.preDisplayName} quiere darse de alta.`;
} else if (status !== 1) { } else if (data.requestStatus !== 1) {
return `Alguien quiere darse de alta.`; return `Alguien quiere darse de alta.`;
} else { } else {
return `Se ha aceptado esta solicitud de alta.`; return `Se ha aceptado esta solicitud de alta.`;
@@ -52,30 +52,30 @@ const renderDescripcionSolicitud = (data, onProfile) => {
case 1: case 1:
return onProfile return onProfile
? "Has solicitado darte de baja." ? "Has solicitado darte de baja."
: requestedByName : data.requestedByName
? `${requestedByName} quiere darse de baja.` ? `${data.requestedByName} quiere darse de baja.`
: status !== 1 : data.requestStatus !== 1
? `Alguien quiere darse de baja.` ? `Alguien quiere darse de baja.`
: `Se ha aceptado esta solicitud de baja.`; : `Se ha aceptado esta solicitud de baja.`;
case 2: case 2:
if (onProfile) { if (onProfile) {
switch (status) { switch (data.requestStatus) {
case 0: return "Has solicitado añadir un colaborador."; case 0: return "Has solicitado añadir un colaborador.";
case 1: return "Tu solicitud de colaborador ha sido aceptada."; case 1: return "Tu solicitud de colaborador ha sido aceptada.";
case 2: return "Tu solicitud de colaborador ha sido rechazada."; case 2: return "Tu solicitud de colaborador ha sido rechazada.";
default: return "Solicitud de colaborador desconocida."; default: return "Solicitud de colaborador desconocida.";
} }
} else { } else {
switch (status) { switch (data.requestStatus) {
case 0: case 0:
return requestedByName return data.requestedByName
? `${requestedByName} quiere añadir a ${preDisplayName || "un colaborador"} como colaborador.` ? `${data.requestedByName} quiere añadir a ${data.preDisplayName || "un colaborador"} como colaborador.`
: `Alguien quiere añadir a ${preDisplayName || "un colaborador"} como colaborador.`; : `Alguien quiere añadir a ${data.preDisplayName || "un colaborador"} como colaborador.`;
case 1: case 1:
return `La solicitud de colaborador de ${requestedByName || "alguien"} ha sido aceptada.`; return `La solicitud de colaborador de ${data.requestedByName || "alguien"} ha sido aceptada.`;
case 2: case 2:
return `La solicitud de colaborador de ${requestedByName || "alguien"} ha sido rechazada.`; return `La solicitud de colaborador de ${data.requestedByName || "alguien"} ha sido rechazada.`;
default: default:
return "Solicitud de colaborador desconocida."; return "Solicitud de colaborador desconocida.";
} }
@@ -84,27 +84,27 @@ const renderDescripcionSolicitud = (data, onProfile) => {
case 3: case 3:
return onProfile return onProfile
? "Has solicitado quitar tu colaborador." ? "Has solicitado quitar tu colaborador."
: requestedByName : data.requestedByName
? `${requestedByName} quiere quitar su colaborador.` ? `${data.requestedByName} quiere quitar su colaborador.`
: status !== 1 : data.requestStatus !== 1
? `Alguien quiere quitar su colaborador.` ? `Alguien quiere quitar su colaborador.`
: `Se ha aceptado esta solicitud de baja de colaborador.`; : `Se ha aceptado esta solicitud de baja de colaborador.`;
case 4: case 4:
return onProfile return onProfile
? "Has solicitado una parcela en el invernadero." ? "Has solicitado una parcela en el invernadero."
: requestedByName : data.requestedByName
? `${requestedByName} quiere una parcela en el invernadero.` ? `${data.requestedByName} quiere una parcela en el invernadero.`
: status !== 1 : data.requestStatus !== 1
? `Alguien quiere una parcela en el invernadero.` ? `Alguien quiere una parcela en el invernadero.`
: `Se ha aceptado esta solicitud de parcela en el invernadero.`; : `Se ha aceptado esta solicitud de parcela en el invernadero.`;
case 5: case 5:
return onProfile return onProfile
? "Has solicitado dejar tu parcela del invernadero." ? "Has solicitado dejar tu parcela del invernadero."
: requestedByName : data.requestedByName
? `${requestedByName} quiere dejar su parcela del invernadero.` ? `${data.requestedByName} quiere dejar su parcela del invernadero.`
: status !== 1 : data.requestStatus !== 1
? `Alguien quiere dejar su parcela del invernadero.` ? `Alguien quiere dejar su parcela del invernadero.`
: `Se ha aceptado esta solicitud de salida del invernadero.`; : `Se ha aceptado esta solicitud de salida del invernadero.`;
@@ -123,9 +123,9 @@ const SolicitudCard = ({ data, onAccept, onReject, onDelete, editable = true, on
<img src={getPFP(data.preType)} width="36" className="rounded me-3" alt="PFP" /> <img src={getPFP(data.preType)} width="36" className="rounded me-3" alt="PFP" />
<div> <div>
<Card.Title className="mb-0"> <Card.Title className="mb-0">
Solicitud #{data.requestId} - {getTipoSolicitud(data.type)} Solicitud #{data.idx} - {getTipoSolicitud(data.requestType)}
</Card.Title> </Card.Title>
<small className='state-small'>Estado: <strong>{getEstadoSolicitud(data.status)}</strong></small> <small className='state-small'>Estado: <strong>{getEstadoSolicitud(data.requestStatus)}</strong></small>
</div> </div>
</div> </div>
@@ -147,7 +147,7 @@ const SolicitudCard = ({ data, onAccept, onReject, onDelete, editable = true, on
<ListGroup variant="flush" className="border rounded-3 mb-3"> <ListGroup variant="flush" className="border rounded-3 mb-3">
<ListGroup.Item> <ListGroup.Item>
<FontAwesomeIcon icon={faCalendar} className="me-2" /> <FontAwesomeIcon icon={faCalendar} className="me-2" />
Fecha de solicitud: <strong>{parseDate(data.request_createdAt)}</strong> Fecha de solicitud: <strong>{parseDate(data.createdAt)}</strong>
</ListGroup.Item> </ListGroup.Item>
</ListGroup> </ListGroup>
@@ -174,7 +174,7 @@ const SolicitudCard = ({ data, onAccept, onReject, onDelete, editable = true, on
</> </>
)} )}
{editable && data.status === 0 && ( {editable && data.requestStatus === 0 && (
<div className="d-flex justify-content-end gap-2 mt-3"> <div className="d-flex justify-content-end gap-2 mt-3">
<Button variant="danger" size="sm" onClick={() => onReject?.(data)}>Rechazar</Button> <Button variant="danger" size="sm" onClick={() => onReject?.(data)}>Rechazar</Button>
<Button variant="success" size="sm" onClick={() => onAccept?.(data)}>Aceptar</Button> <Button variant="success" size="sm" onClick={() => onAccept?.(data)}>Aceptar</Button>

View File

@@ -13,7 +13,7 @@ const Balance = () => {
if (configLoading || !config) return <p className="text-center my-5"><LoadingIcon /></p>; if (configLoading || !config) return <p className="text-center my-5"><LoadingIcon /></p>;
const reqConfig = { const reqConfig = {
baseUrl: config.apiConfig.baseUrl + "/v1/balance/with-totals" baseUrl: config.apiConfig.baseUrl + config.apiConfig.endpoints.balance.withTotals
}; };
return ( return (

View File

@@ -1,4 +1,4 @@
import { useEffect, useState } from 'react'; import { useState } from 'react';
import { useConfig } from '../hooks/useConfig'; import { useConfig } from '../hooks/useConfig';
import { DataProvider } from '../context/DataContext'; import { DataProvider } from '../context/DataContext';
import { useDataContext } from '../hooks/useDataContext'; import { useDataContext } from '../hooks/useDataContext';
@@ -29,9 +29,9 @@ const Ingresos = () => {
if (configLoading) return <p><LoadingIcon /></p>; if (configLoading) return <p><LoadingIcon /></p>;
const reqConfig = { const reqConfig = {
baseUrl: config.apiConfig.baseUrl + config.apiConfig.endpoints.incomes.allWithNames, baseUrl: config.apiConfig.baseUrl + config.apiConfig.endpoints.incomes.withInfo,
rawUrl: config.apiConfig.baseUrl + config.apiConfig.endpoints.incomes.all, rawUrl: config.apiConfig.baseUrl + config.apiConfig.endpoints.incomes.all,
membersUrl: config.apiConfig.baseUrl + config.apiConfig.endpoints.members.all, usersUrl: config.apiConfig.baseUrl + config.apiConfig.endpoints.users.all,
params: { params: {
_sort: 'createdAt', _sort: 'createdAt',
_order: 'desc' _order: 'desc'
@@ -46,26 +46,24 @@ const Ingresos = () => {
}; };
const IngresosContent = ({ reqConfig }) => { const IngresosContent = ({ reqConfig }) => {
const { data, dataLoading, dataError, getData, postData, putData, deleteData } = useDataContext(); const { data, dataLoading, dataError, postData, putData, deleteData } = useDataContext();
const [showPDFModal, setShowPDFModal] = useState(false); const [showPDFModal, setShowPDFModal] = useState(false);
const [creatingIngreso, setCreatingIngreso] = useState(false); const [creatingIngreso, setCreatingIngreso] = useState(false);
const [tempIngreso, setTempIngreso] = useState(null); const [tempIngreso, setTempIngreso] = useState(null);
const [error, setError] = useState(null); const [error, setError] = useState(null);
const [deleteTargetId, setDeleteTargetId] = useState(null); const [deleteTargetId, setDeleteTargetId] = useState(null);
const [members, setMembers] = useState([]);
useEffect(() => { const members = data
const fetchMembers = async () => { ? Array.from(
try { new Map(
const membersData = await getData(reqConfig.membersUrl, { params: { _sort: 'name', _order: 'asc' } }); data.map(i => [i.memberNumber, {
setMembers(membersData.data); memberNumber: i.memberNumber,
} catch (err) { displayName: i.displayName,
setError(errorParser(err)); userId: i.userId
} }])
}; ).values()
).sort((a, b) => a.memberNumber - b.memberNumber)
fetchMembers(); : [];
}, [reqConfig.membersUrl, getData]);
const { const {
filtered, filtered,
@@ -104,16 +102,18 @@ const IngresosContent = ({ reqConfig }) => {
}); });
const handleCreate = () => { const handleCreate = () => {
const firstMember = members[0];
setCreatingIngreso(true); setCreatingIngreso(true);
setTempIngreso({ setTempIngreso({
incomeId: null, incomeId: null,
memberNumber: 0, memberNumber: firstMember?.memberNumber ?? null,
userId: firstMember?.userId ?? null,
concept: '', concept: '',
amount: 0.0, amount: 0.0,
frequency: CONSTANTS.PAYMENT_FREQUENCY_YEARLY, frequency: CONSTANTS.PAYMENT_FREQUENCY_YEARLY,
type: CONSTANTS.PAYMENT_TYPE_BANK type: CONSTANTS.PAYMENT_TYPE_BANK
}); });
document.querySelector('.cards-grid')?.scrollTo({ top: 0, behavior: 'smooth' });
}; };
const handleCancelCreate = () => { const handleCancelCreate = () => {
@@ -150,7 +150,7 @@ const IngresosContent = ({ reqConfig }) => {
if (dataLoading) return <p className="text-center my-5"><LoadingIcon /></p>; if (dataLoading) return <p className="text-center my-5"><LoadingIcon /></p>;
if (dataError) return <p className="text-danger text-center my-5">{dataError}</p>; if (dataError) return <p className="text-danger text-center my-5">{dataError}</p>;
return ( return (
<CustomContainer> <CustomContainer>
<ContentWrapper> <ContentWrapper>

View File

@@ -29,7 +29,7 @@ const Socios = () => {
const reqConfig = { const reqConfig = {
baseUrl: `${config.apiConfig.baseUrl}${config.apiConfig.endpoints.users.all}`, baseUrl: `${config.apiConfig.baseUrl}${config.apiConfig.endpoints.users.all}`,
incomesUrl: `${config.apiConfig.baseUrl}${config.apiConfig.endpoints.users.payments}`, incomesUrl: `${config.apiConfig.baseUrl}${config.apiConfig.endpoints.users.incomesPreview}`,
rawIncomesUrl: `${config.apiConfig.baseUrl}${config.apiConfig.endpoints.incomes.all}`, rawIncomesUrl: `${config.apiConfig.baseUrl}${config.apiConfig.endpoints.incomes.all}`,
params: { params: {
_sort: "memberNumber", _sort: "memberNumber",
@@ -164,7 +164,7 @@ const SociosContent = ({ reqConfig }) => {
try { try {
const url = reqConfig.incomesUrl.replace(":memberNumber", memberNumber); const url = reqConfig.incomesUrl.replace(":memberNumber", memberNumber);
const res = await getData(url); const res = await getData(url);
setIncomes(res.data); setIncomes(res);
} catch (err) { } catch (err) {
setIncomesError(err.message); setIncomesError(err.message);
} finally { } finally {
@@ -217,19 +217,19 @@ const SociosContent = ({ reqConfig }) => {
onClearError={() => setError(null)} onClearError={() => setError(null)}
/> />
)} )}
renderCard={(socio) => { renderCard={(identity) => {
const position = socio.type === 0 const position = identity.metadata.type === 0
? listaEsperaOrdenada.findIndex(s => s.userId === socio.userId) + 1 ? listaEsperaOrdenada.findIndex(i => i.user.userId === identity.user.userId) + 1
: null; : null;
return ( return (
<SocioCard <SocioCard
key={socio.userId} key={identity.user.userId}
socio={socio} identity={identity}
onUpdate={handleEditSubmit} onUpdate={handleEditSubmit}
onDelete={handleDelete} onDelete={handleDelete}
onCancel={handleCancelCreate} onCancel={handleCancelCreate}
onViewIncomes={() => handleViewIncomes(socio.memberNumber)} onViewIncomes={() => handleViewIncomes(identity.metadata.memberNumber)}
error={error} error={error}
onClearError={() => setError(null)} onClearError={() => setError(null)}
positionIfWaitlist={position} positionIfWaitlist={position}

View File

@@ -21,7 +21,7 @@ const Solicitudes = () => {
if (configLoading || !config) return <p className="text-center my-5"><LoadingIcon /></p>; if (configLoading || !config) return <p className="text-center my-5"><LoadingIcon /></p>;
const reqConfig = { const reqConfig = {
baseUrl: config.apiConfig.baseUrl + config.apiConfig.endpoints.requests.allWithPreUsers, baseUrl: config.apiConfig.baseUrl + config.apiConfig.endpoints.requests.full,
rawUrl: config.apiConfig.baseUrl + config.apiConfig.endpoints.requests.all, rawUrl: config.apiConfig.baseUrl + config.apiConfig.endpoints.requests.all,
acceptUrl: config.apiConfig.baseUrl + config.apiConfig.endpoints.requests.accept, acceptUrl: config.apiConfig.baseUrl + config.apiConfig.endpoints.requests.accept,
rejectUrl: config.apiConfig.baseUrl + config.apiConfig.endpoints.requests.reject, rejectUrl: config.apiConfig.baseUrl + config.apiConfig.endpoints.requests.reject,
@@ -103,10 +103,10 @@ const SolicitudesContent = ({ reqConfig }) => {
<PaginatedCardGrid <PaginatedCardGrid
items={filtered} items={filtered}
renderCard={(entry) => ( renderCard={(entry, idx) => (
<SolicitudCard <SolicitudCard
key={entry.requestId} key={entry.requestId}
data={entry} data={{...entry, idx}}
onAccept={() => handleAccept(entry)} onAccept={() => handleAccept(entry)}
onReject={() => handleReject(entry)} onReject={() => handleReject(entry)}
onDelete={handleDelete} onDelete={handleDelete}