Fix: custom dates on socios, ingresos and gastos. Add: new endpoint for getting socios dropdown on ingresos.
This commit is contained in:
@@ -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",
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -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())
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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() : ''
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -91,8 +91,12 @@ 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>
|
||||||
|
|
||||||
|
|||||||
@@ -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',
|
||||||
|
|||||||
Reference in New Issue
Block a user