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", "all": "/users",
"byId": "/users/:userId", "byId": "/users/:userId",
"me": "/users/me", "me": "/users/me",
"dropdown": "/users/dropdown",
"latestNumber": "/users/latest-number", "latestNumber": "/users/latest-number",
"waitlist": "/users/waitlist", "waitlist": "/users/waitlist",
"waitlistLimited": "/users/waitlist/limited", "waitlistLimited": "/users/waitlist/limited",

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -15,7 +15,7 @@ export const DateParser = {
if (!isoString) return '—'; if (!isoString) return '—';
const date = new Date(isoString); 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', { return new Intl.DateTimeFormat('es-ES', {
day: '2-digit', day: '2-digit',