Fix: custom dates on socios, ingresos and gastos. Add: new endpoint for getting socios dropdown on ingresos.

This commit is contained in:
Jose
2026-02-01 14:42:22 +01:00
parent 3c7de7e71d
commit 6069486f20
9 changed files with 69 additions and 61 deletions

View File

@@ -13,6 +13,7 @@
"all": "/users",
"byId": "/users/:userId",
"me": "/users/me",
"dropdown": "/users/dropdown",
"latestNumber": "/users/latest-number",
"waitlist": "/users/waitlist",
"waitlistLimited": "/users/waitlist/limited",

View File

@@ -13,6 +13,7 @@
"all": "/users",
"byId": "/users/:userId",
"me": "/users/me",
"dropdown": "/users/dropdown",
"latestNumber": "/users/latest-number",
"waitlist": "/users/waitlist",
"waitlistLimited": "/users/waitlist/limited",

View File

@@ -48,7 +48,7 @@ const GastoCard = ({ gasto, isNew = false, onCreate, onUpdate, onDelete, onCance
supplier: gasto.supplier || '',
invoice: gasto.invoice || '',
type: gasto.type ?? 0,
createdAt: gasto.createdAt?.slice(0, 16) || (isNew ? getNowAsLocalDatetime() : ''),
createdAt: gasto.createdAt || (isNew ? getNowAsLocalDatetime() : ''),
});
const getFieldError = (field) => fieldErrors?.[field] ?? null;
@@ -61,7 +61,7 @@ const GastoCard = ({ gasto, isNew = false, onCreate, onUpdate, onDelete, onCance
supplier: gasto.supplier || '',
invoice: gasto.invoice || '',
type: gasto.type ?? 0,
createdAt: gasto.createdAt?.slice(0, 16) || (isNew ? getNowAsLocalDatetime() : ''),
createdAt: gasto.createdAt || (isNew ? getNowAsLocalDatetime() : ''),
});
}
// eslint-disable-next-line react-hooks/exhaustive-deps
@@ -109,7 +109,7 @@ const GastoCard = ({ gasto, isNew = false, onCreate, onUpdate, onDelete, onCance
<SpanishDateTimePicker
selected={new Date(formData.createdAt)}
onChange={(date) =>
handleChange('createdAt', date.toISOString().slice(0, 16))
handleChange('createdAt', date.toISOString())
}
/>
) : (

View File

@@ -49,7 +49,7 @@ const IngresoCard = ({
onCancel,
className = '',
editable = true,
members = [],
dropdown = new Map(),
fieldErrors
}) => {
const createMode = isNew;
@@ -64,7 +64,7 @@ const IngresoCard = ({
memberNumber: income.memberNumber,
userId: income.userId,
displayName: income.displayName || '',
createdAt: income.createdAt?.slice(0, 16) || (isNew ? getNowAsLocalDatetime() : ''),
createdAt: income.createdAt || (isNew ? getNowAsLocalDatetime() : ''),
});
const getFieldError = (field) => fieldErrors?.[field] ?? null;
@@ -79,26 +79,12 @@ const IngresoCard = ({
userId: income.userId,
memberNumber: income.memberNumber,
displayName: income.displayName || '',
createdAt: income.createdAt?.slice(0, 16) || (isNew ? getNowAsLocalDatetime() : ''),
createdAt: income.createdAt || (isNew ? getNowAsLocalDatetime() : ''),
});
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [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) =>
setFormData(prev => ({ ...prev, [field]: value }));
@@ -115,9 +101,12 @@ const IngresoCard = ({
const handleDelete = () => typeof onDelete === 'function' && onDelete(income.incomeId);
const uniqueMembers = Array.from(
new Map(members.map(item => [item.memberNumber, item])).values()
).sort((a, b) => a.memberNumber - b.memberNumber);
const uniqueMembers = Array.from(dropdown.entries())
.map(([memberNumber, member]) => ({
memberNumber,
...member
}))
.sort((a, b) => a.memberNumber - b.memberNumber);
return (
<MotionCard className={`ingreso-card shadow-sm rounded-4 border-0 h-100 ${className}`}>
@@ -148,7 +137,7 @@ const IngresoCard = ({
<SpanishDateTimePicker
selected={new Date(formData.createdAt)}
onChange={(date) =>
handleChange('createdAt', date.toISOString().slice(0, 16))
handleChange('createdAt', date.toISOString())
}
/>
) : (
@@ -188,8 +177,8 @@ const IngresoCard = ({
size="sm"
value={formData.memberNumber ?? ""}
onChange={(e) => {
const memberNumber = parseInt(e.target.value);
const member = members.find(m => m.memberNumber === memberNumber);
const memberNumber = parseInt(e.target.value, 10);
const member = dropdown.get(memberNumber);
handleChange('memberNumber', memberNumber);
handleChange('userId', member?.userId ?? null);

View File

@@ -33,7 +33,7 @@ const renderDateField = (label, icon, dateValue, editMode, fieldKey, handleChang
<SpanishDateTimePicker
selected={dateValue ? new Date(dateValue) : null}
onChange={(date) =>
date ? handleChange(fieldKey, date.toISOString().slice(0, 16)) : handleChange(fieldKey, null)
date ? handleChange(fieldKey, date.toISOString()) : handleChange(fieldKey, null)
}
/>
) : (
@@ -108,9 +108,9 @@ const SocioCard = ({ identity, isNew = false, onCreate, onUpdate, onDelete, onCa
notes: identity?.metadata.notes || '',
status: identity?.account.status,
type: identity?.metadata.type,
createdAt: identity?.metadata.createdAt?.slice(0, 16) || (isNew ? getNowAsLocalDatetime() : ''),
assignedAt: identity?.metadata.assignedAt?.slice(0, 16) || undefined,
deactivatedAt: identity?.metadata.deactivatedAt?.slice(0, 16) || undefined,
createdAt: identity?.metadata.createdAt || (isNew ? getNowAsLocalDatetime() : ''),
assignedAt: identity?.metadata.assignedAt || undefined,
deactivatedAt: identity?.metadata.deactivatedAt || undefined,
globalRole: 0,
password: createMode && !editMode ? generateSecurePassword() : null,
});
@@ -130,9 +130,9 @@ const SocioCard = ({ identity, isNew = false, onCreate, onUpdate, onDelete, onCa
notes: identity.metadata.notes || '',
status: identity.account.status,
type: identity.metadata.type,
createdAt: identity.metadata.createdAt?.slice(0, 16) || (isNew ? getNowAsLocalDatetime() : ''),
assignedAt: identity.metadata.assignedAt?.slice(0, 16) || undefined,
deactivatedAt: identity.metadata.deactivatedAt?.slice(0, 16) || undefined,
createdAt: identity.metadata.createdAt || (isNew ? getNowAsLocalDatetime() : ''),
assignedAt: identity.metadata.assignedAt || undefined,
deactivatedAt: identity.metadata.deactivatedAt || undefined,
globalRole: 0,
password: createMode ? generateSecurePassword() : ''
});

View File

@@ -140,7 +140,7 @@ NewUserForm.propTypes = {
userType: PropTypes.number.isRequired,
plotNumber: PropTypes.number.isRequired,
onSubmit: PropTypes.func.isRequired,
errors: PropTypes.object
fieldErrors: PropTypes.object
};
export default NewUserForm;

View File

@@ -1,4 +1,4 @@
import { useState } from 'react';
import { useEffect, useState } from 'react';
import { useConfig } from '../hooks/useConfig';
import { DataProvider } from '../context/DataContext';
import { useDataContext } from '../hooks/useDataContext';
@@ -32,7 +32,7 @@ const Ingresos = () => {
const reqConfig = {
baseUrl: config.apiConfig.baseUrl + config.apiConfig.endpoints.incomes.withInfo,
rawUrl: config.apiConfig.baseUrl + config.apiConfig.endpoints.incomes.all,
usersUrl: config.apiConfig.baseUrl + config.apiConfig.endpoints.users.all,
dropdownUrl: config.apiConfig.baseUrl + config.apiConfig.endpoints.users.dropdown,
params: {}
};
@@ -44,24 +44,35 @@ const Ingresos = () => {
};
const IngresosContent = ({ reqConfig }) => {
const { data, dataLoading, postData, putData, deleteData } = useDataContext();
const { data, dataLoading, getData, postData, putData, deleteData } = useDataContext();
const [showPDFModal, setShowPDFModal] = useState(false);
const [creatingIngreso, setCreatingIngreso] = useState(false);
const [tempIngreso, setTempIngreso] = useState(null);
const [deleteTargetId, setDeleteTargetId] = useState(null);
const [fieldErrors, setFieldErrors] = useState(null);
const [dropdown, setDropdown] = useState(new Map());
const members = data
? Array.from(
new Map(
data.map(i => [i.memberNumber, {
memberNumber: i.memberNumber,
displayName: i.displayName,
userId: i.userId
}])
).values()
).sort((a, b) => a.memberNumber - b.memberNumber)
: [];
useEffect(() => {
const fetchData = async () => {
try {
const response = await getData(reqConfig.dropdownUrl);
const map = new Map();
response
.sort((a, b) => a.memberNumber - b.memberNumber)
.forEach(item => {
map.set(item.memberNumber, {
userId: item.userId,
displayName: item.displayName
});
});
setDropdown(map);
} catch (e) {
console.error(e);
}
};
fetchData();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [reqConfig.dropdownUrl]);
const {
filtered,
@@ -100,13 +111,15 @@ const IngresosContent = ({ reqConfig }) => {
});
const handleCreate = () => {
const firstMember = members[0];
if (dropdown.size === 0) return;
const firstEntry = dropdown.entries().next().value;
const [memberNumber, member] = firstEntry ?? [];
setCreatingIngreso(true);
setTempIngreso({
incomeId: null,
memberNumber: firstMember?.memberNumber ?? null,
userId: firstMember?.userId ?? null,
memberNumber: memberNumber ?? null,
userId: member?.userId ?? null,
concept: '',
amount: 0.0,
frequency: CONSTANTS.PAYMENT_FREQUENCY_YEARLY,
@@ -162,7 +175,7 @@ const IngresosContent = ({ reqConfig }) => {
searchTerm={searchTerm}
onSearchChange={setSearchTerm}
filtersComponent={<IngresosFilter filters={filters} onChange={setFilters} />}
onCreate={handleCreate}
onCreate={dropdown.size > 0 ? handleCreate : null}
onPDF={() => setShowPDFModal(true)}
/>
@@ -175,7 +188,7 @@ const IngresosContent = ({ reqConfig }) => {
isNew
onCreate={handleCreateSubmit}
onCancel={handleCancelCreate}
members={members}
dropdown={dropdown}
fieldErrors={fieldErrors}
/>
)}

View File

@@ -90,9 +90,13 @@ const ListaEsperaContent = ({ reqConfig }) => {
setShowNewUserFormModal(false);
setShowConfirmationModal(true);
// eslint-disable-next-line no-unused-vars
} catch (_err) {
setValidationErrors({ general: "Error inesperado al enviar la solicitud" });
// eslint-disable-next-line no-unused-vars
} catch (err) {
if (err.status === 422 && err.errors) {
setValidationErrors(err.errors);
} else {
setShowNewUserFormModal(false);
}
}
};
@@ -152,7 +156,7 @@ const ListaEsperaContent = ({ reqConfig }) => {
userType={0}
plotNumber={0}
onSubmit={handleRegisterSubmit}
errors={validationErrors}
fieldErrors={validationErrors}
/>
</CustomModal>

View File

@@ -15,7 +15,7 @@ export const DateParser = {
if (!isoString) return '—';
const date = new Date(isoString);
if (isNaN(date)) return '—'; // Para proteger aún más por si llega basura
if (isNaN(date)) return '—';
return new Intl.DateTimeFormat('es-ES', {
day: '2-digit',