only change password, docs and mail left
This commit is contained in:
@@ -4,7 +4,6 @@ import AnimatedDropdown from '../../components/AnimatedDropdown';
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
import { faEdit, faTrash, faEllipsisVertical } from '@fortawesome/free-solid-svg-icons';
|
||||
import '../../css/AnuncioCard.css';
|
||||
import { renderErrorAlert } from '../../util/alertHelpers';
|
||||
import {
|
||||
EditorProvider,
|
||||
Editor,
|
||||
@@ -37,7 +36,7 @@ const formatDateTime = (iso) => {
|
||||
};
|
||||
};
|
||||
|
||||
const AnuncioCard = ({ anuncio, isNew = false, onCreate, onUpdate, onDelete, onCancel, error, onClearError }) => {
|
||||
const AnuncioCard = ({ anuncio, isNew = false, onCreate, onUpdate, onDelete, onCancel }) => {
|
||||
const createMode = isNew;
|
||||
const [editMode, setEditMode] = useState(createMode);
|
||||
const [showFullBody, setShowFullBody] = useState(false);
|
||||
@@ -59,20 +58,17 @@ const AnuncioCard = ({ anuncio, isNew = false, onCreate, onUpdate, onDelete, onC
|
||||
}, [anuncio, editMode]);
|
||||
|
||||
const handleEdit = () => {
|
||||
if (onClearError) onClearError();
|
||||
setEditMode(true);
|
||||
};
|
||||
|
||||
const handleDelete = () => typeof onDelete === 'function' && onDelete(anuncio.announceId);
|
||||
|
||||
const handleCancel = () => {
|
||||
if (onClearError) onClearError();
|
||||
if (createMode && onCancel) return onCancel();
|
||||
setEditMode(false);
|
||||
};
|
||||
|
||||
const handleSave = () => {
|
||||
if (onClearError) onClearError();
|
||||
const sanitizedBody = DOMPurify.sanitize(formData.body);
|
||||
formData.body = sanitizedBody;
|
||||
const updated = { ...anuncio, ...formData };
|
||||
@@ -133,8 +129,6 @@ const AnuncioCard = ({ anuncio, isNew = false, onCreate, onUpdate, onDelete, onC
|
||||
</Card.Header>
|
||||
|
||||
<Card.Body className="py-3">
|
||||
{(editMode || createMode) && renderErrorAlert(error)}
|
||||
|
||||
{editMode || createMode ? (
|
||||
<EditorProvider>
|
||||
<Editor
|
||||
|
||||
@@ -19,7 +19,6 @@ import { useTheme } from '../../hooks/useTheme';
|
||||
import '../../css/IngresoCard.css';
|
||||
import { CONSTANTS } from '../../util/constants';
|
||||
import { DateParser } from '../../util/parsers/dateParser';
|
||||
import { renderErrorAlert } from '../../util/alertHelpers';
|
||||
import { getNowAsLocalDatetime } from '../../util/date';
|
||||
import SpanishDateTimePicker from '../SpanishDateTimePicker';
|
||||
|
||||
@@ -38,7 +37,7 @@ const getPFP = (tipo) => {
|
||||
return base + (map[tipo] || 'farmer.svg');
|
||||
};
|
||||
|
||||
const GastoCard = ({ gasto, isNew = false, onCreate, onUpdate, onDelete, onCancel, error, onClearError }) => {
|
||||
const GastoCard = ({ gasto, isNew = false, onCreate, onUpdate, onDelete, onCancel, fieldErrors }) => {
|
||||
const createMode = isNew;
|
||||
const [editMode, setEditMode] = useState(createMode);
|
||||
const { theme } = useTheme();
|
||||
@@ -52,6 +51,8 @@ const GastoCard = ({ gasto, isNew = false, onCreate, onUpdate, onDelete, onCance
|
||||
createdAt: gasto.createdAt?.slice(0, 16) || (isNew ? getNowAsLocalDatetime() : ''),
|
||||
});
|
||||
|
||||
const getFieldError = (field) => fieldErrors?.[field] ?? null;
|
||||
|
||||
useEffect(() => {
|
||||
if (!editMode) {
|
||||
setFormData({
|
||||
@@ -71,13 +72,11 @@ const GastoCard = ({ gasto, isNew = false, onCreate, onUpdate, onDelete, onCance
|
||||
const handleDelete = () => typeof onDelete === 'function' && onDelete(gasto.expenseId);
|
||||
|
||||
const handleCancel = () => {
|
||||
if (onClearError) onClearError();
|
||||
if (isNew && typeof onCancel === 'function') return onCancel();
|
||||
setEditMode(false);
|
||||
};
|
||||
|
||||
const handleSave = () => {
|
||||
if (onClearError) onClearError();
|
||||
const newExpense = { ...gasto, ...formData };
|
||||
if (createMode && typeof onCreate === 'function') return onCreate(newExpense);
|
||||
if (typeof onUpdate === 'function') return onUpdate(newExpense, gasto.expenseId);
|
||||
@@ -91,12 +90,18 @@ const GastoCard = ({ gasto, isNew = false, onCreate, onUpdate, onDelete, onCance
|
||||
<div className="d-flex flex-column">
|
||||
<span className="fw-bold">
|
||||
{editMode ? (
|
||||
<>
|
||||
<Form.Control
|
||||
className="themed-input"
|
||||
size="sm"
|
||||
isInvalid={!!fieldErrors?.concept}
|
||||
value={formData.concept}
|
||||
onChange={(e) => handleChange('concept', e.target.value.toUpperCase())}
|
||||
/>
|
||||
<Form.Control.Feedback type="invalid" as="span">
|
||||
{getFieldError("concept")}
|
||||
</Form.Control.Feedback>
|
||||
</>
|
||||
) : formData.concept}
|
||||
</span>
|
||||
<small>
|
||||
@@ -132,13 +137,17 @@ const GastoCard = ({ gasto, isNew = false, onCreate, onUpdate, onDelete, onCance
|
||||
|
||||
|
||||
<Card.Body>
|
||||
{(editMode || createMode) && renderErrorAlert(error)}
|
||||
|
||||
<Card.Text className="mb-2">
|
||||
<FontAwesomeIcon icon={faMoneyBillWave} className="me-2" />
|
||||
<strong>Importe:</strong>{' '}
|
||||
{editMode ? (
|
||||
<Form.Control className="themed-input" size="sm" type="number" step="0.01" value={formData.amount} onChange={(e) => handleChange('amount', parseFloat(e.target.value))} style={{ maxWidth: '150px', display: 'inline-block' }} />
|
||||
<>
|
||||
<Form.Control className="themed-input" size="sm" type="number" step="0.01" isInvalid={!!fieldErrors?.amount} value={formData.amount}
|
||||
onChange={(e) => handleChange('amount', parseFloat(e.target.value))} style={{ maxWidth: '150px', display: 'inline-block' }} />
|
||||
<Form.Control.Feedback type="invalid" as="span">
|
||||
{getFieldError("amount")}
|
||||
</Form.Control.Feedback>
|
||||
</>
|
||||
) : `${formData.amount.toFixed(2)} €`}
|
||||
</Card.Text>
|
||||
|
||||
@@ -146,7 +155,13 @@ const GastoCard = ({ gasto, isNew = false, onCreate, onUpdate, onDelete, onCance
|
||||
<FontAwesomeIcon icon={faTruck} className="me-2" />
|
||||
<strong>Proveedor:</strong>{' '}
|
||||
{editMode ? (
|
||||
<Form.Control className="themed-input" size="sm" type="text" value={formData.supplier} onChange={(e) => handleChange('supplier', e.target.value)} />
|
||||
<>
|
||||
<Form.Control className="themed-input" size="sm" type="text" isInvalid={!!fieldErrors?.supplier}
|
||||
alue={formData.supplier} onChange={(e) => handleChange('supplier', e.target.value)} />
|
||||
<Form.Control.Feedback type="invalid" as="span">
|
||||
{getFieldError("supplier")}
|
||||
</Form.Control.Feedback>
|
||||
</>
|
||||
) : formData.supplier}
|
||||
</Card.Text>
|
||||
|
||||
@@ -154,7 +169,13 @@ const GastoCard = ({ gasto, isNew = false, onCreate, onUpdate, onDelete, onCance
|
||||
<FontAwesomeIcon icon={faReceipt} className="me-2" />
|
||||
<strong>Factura:</strong>{' '}
|
||||
{editMode ? (
|
||||
<Form.Control className="themed-input" size="sm" type="text" value={formData.invoice} onChange={(e) => handleChange('invoice', e.target.value)} />
|
||||
<>
|
||||
<Form.Control className="themed-input" size="sm" type="text" isInvalid={!!fieldErrors?.invoice}
|
||||
value={formData.invoice} onChange={(e) => handleChange('invoice', e.target.value)} />
|
||||
<Form.Control.Feedback type="invalid" as="span">
|
||||
{getFieldError("invoice")}
|
||||
</Form.Control.Feedback>
|
||||
</>
|
||||
) : formData.invoice}
|
||||
</Card.Text>
|
||||
|
||||
@@ -190,7 +211,8 @@ GastoCard.propTypes = {
|
||||
onCreate: PropTypes.func,
|
||||
onUpdate: PropTypes.func,
|
||||
onDelete: PropTypes.func,
|
||||
onCancel: PropTypes.func
|
||||
onCancel: PropTypes.func,
|
||||
fieldErrors: PropTypes.object
|
||||
};
|
||||
|
||||
export default GastoCard;
|
||||
|
||||
@@ -18,7 +18,6 @@ import { CONSTANTS } from '../../util/constants';
|
||||
import '../../css/IngresoCard.css';
|
||||
import { useTheme } from '../../hooks/useTheme';
|
||||
import { DateParser } from '../../util/parsers/dateParser';
|
||||
import { renderErrorAlert } from '../../util/alertHelpers';
|
||||
import { getNowAsLocalDatetime } from '../../util/date';
|
||||
import SpanishDateTimePicker from '../SpanishDateTimePicker';
|
||||
|
||||
@@ -50,9 +49,8 @@ const IngresoCard = ({
|
||||
onCancel,
|
||||
className = '',
|
||||
editable = true,
|
||||
error,
|
||||
onClearError,
|
||||
members = []
|
||||
members = [],
|
||||
fieldErrors
|
||||
}) => {
|
||||
const createMode = isNew;
|
||||
const [editMode, setEditMode] = useState(createMode);
|
||||
@@ -69,6 +67,8 @@ const IngresoCard = ({
|
||||
createdAt: income.createdAt?.slice(0, 16) || (isNew ? getNowAsLocalDatetime() : ''),
|
||||
});
|
||||
|
||||
const getFieldError = (field) => fieldErrors?.[field] ?? null;
|
||||
|
||||
useEffect(() => {
|
||||
if (!editMode) {
|
||||
setFormData({
|
||||
@@ -103,13 +103,11 @@ const IngresoCard = ({
|
||||
setFormData(prev => ({ ...prev, [field]: value }));
|
||||
|
||||
const handleCancel = () => {
|
||||
if (onClearError) onClearError();
|
||||
if (isNew && typeof onCancel === 'function') return onCancel();
|
||||
setEditMode(false);
|
||||
};
|
||||
|
||||
const handleSave = () => {
|
||||
if (onClearError) onClearError();
|
||||
const newIncome = { ...income, ...formData };
|
||||
if (createMode && typeof onCreate === 'function') return onCreate(newIncome);
|
||||
if (typeof onUpdate === 'function') return onUpdate(newIncome, income.incomeId);
|
||||
@@ -130,12 +128,18 @@ const IngresoCard = ({
|
||||
<div className="d-flex flex-column">
|
||||
<span className="fw-bold">
|
||||
{editMode ? (
|
||||
<>
|
||||
<Form.Control
|
||||
className="themed-input"
|
||||
size="sm"
|
||||
value={formData.concept}
|
||||
isInvalid={!!fieldErrors?.concept}
|
||||
onChange={(e) => handleChange('concept', e.target.value.toUpperCase())}
|
||||
/>
|
||||
<Form.Control.Feedback type="invalid" as="span">
|
||||
{getFieldError("concept")}
|
||||
</Form.Control.Feedback>
|
||||
</>
|
||||
) : formData.concept}
|
||||
</span>
|
||||
|
||||
@@ -161,7 +165,7 @@ const IngresoCard = ({
|
||||
>
|
||||
{({ closeDropdown }) => (
|
||||
<>
|
||||
<div className="dropdown-item d-flex align-items-center" onClick={() => { setEditMode(true); onClearError && onClearError(); closeDropdown(); }}>
|
||||
<div className="dropdown-item d-flex align-items-center" onClick={() => { setEditMode(true); closeDropdown(); }}>
|
||||
<FontAwesomeIcon icon={faEdit} className="me-2" />Editar
|
||||
</div>
|
||||
<div className="dropdown-item d-flex align-items-center text-danger" onClick={() => { handleDelete(); closeDropdown(); }}>
|
||||
@@ -175,8 +179,6 @@ const IngresoCard = ({
|
||||
</Card.Header>
|
||||
|
||||
<Card.Body>
|
||||
{(editMode || createMode) && renderErrorAlert(error)}
|
||||
|
||||
<Card.Text className="mb-2">
|
||||
<FontAwesomeIcon icon={faUser} className="me-2" />
|
||||
<strong>Socio:</strong>{' '}
|
||||
@@ -236,15 +238,21 @@ const IngresoCard = ({
|
||||
<FontAwesomeIcon icon={faMoneyBillWave} className="me-2" />
|
||||
<strong>Importe:</strong>{' '}
|
||||
{editMode ? (
|
||||
<>
|
||||
<Form.Control
|
||||
className="themed-input"
|
||||
size="sm"
|
||||
type="number"
|
||||
step="0.01"
|
||||
isInvalid={!!fieldErrors?.amount}
|
||||
value={formData.amount}
|
||||
onChange={(e) => handleChange('amount', parseFloat(e.target.value))}
|
||||
style={{ maxWidth: '150px', display: 'inline-block' }}
|
||||
/>
|
||||
<Form.Control.Feedback type="invalid" as="span">
|
||||
{getFieldError("amount")}
|
||||
</Form.Control.Feedback>
|
||||
</>
|
||||
) : `${formData.amount.toFixed(2)} €`}
|
||||
</Card.Text>
|
||||
|
||||
@@ -299,9 +307,8 @@ IngresoCard.propTypes = {
|
||||
onCancel: PropTypes.func,
|
||||
className: PropTypes.string,
|
||||
editable: PropTypes.bool,
|
||||
error: PropTypes.string,
|
||||
onClearError: PropTypes.func,
|
||||
members: PropTypes.array
|
||||
members: PropTypes.array,
|
||||
fieldErrors: PropTypes.object
|
||||
};
|
||||
|
||||
export default IngresoCard;
|
||||
|
||||
@@ -20,7 +20,6 @@ import TipoSocioDropdown from './TipoSocioDropdown';
|
||||
import { getNowAsLocalDatetime } from '../../util/date';
|
||||
import { generateSecurePassword } from '../../util/passwordGenerator';
|
||||
import { DateParser } from '../../util/parsers/dateParser';
|
||||
import { renderErrorAlert } from '../../util/alertHelpers';
|
||||
import { useDataContext } from "../../hooks/useDataContext";
|
||||
import SpanishDateTimePicker from '../SpanishDateTimePicker';
|
||||
|
||||
@@ -91,7 +90,7 @@ const getPFP = (tipo) => {
|
||||
|
||||
const MotionCard = _motion.create(Card);
|
||||
|
||||
const SocioCard = ({ identity, isNew = false, onCreate, onUpdate, onDelete, onCancel, onViewIncomes, error, onClearError, positionIfWaitlist }) => {
|
||||
const SocioCard = ({ identity, isNew = false, onCreate, onUpdate, onDelete, onCancel, onViewIncomes, positionIfWaitlist, fieldErrors }) => {
|
||||
const createMode = isNew;
|
||||
const [editMode, setEditMode] = useState(isNew);
|
||||
const [showPassword, setShowPassword] = useState(false);
|
||||
@@ -116,6 +115,8 @@ const SocioCard = ({ identity, isNew = false, onCreate, onUpdate, onDelete, onCa
|
||||
password: createMode && !editMode ? generateSecurePassword() : null,
|
||||
});
|
||||
|
||||
const getFieldError = (field) => fieldErrors?.[field] ?? null;
|
||||
|
||||
useEffect(() => {
|
||||
if (!editMode) {
|
||||
setFormData({
|
||||
@@ -144,7 +145,7 @@ const SocioCard = ({ identity, isNew = false, onCreate, onUpdate, onDelete, onCa
|
||||
try {
|
||||
if (!(createMode || editMode)) return;
|
||||
|
||||
const latestNumber = await getData("http://localhost:8081/v2/huertos/users/latest-number");
|
||||
const latestNumber = await getData("http://localhost:8081/v2/huertos/users/latest-number", {}, false);
|
||||
|
||||
const nuevoNumero = latestNumber + 1;
|
||||
setLatestNumber(nuevoNumero);
|
||||
@@ -162,20 +163,17 @@ const SocioCard = ({ identity, isNew = false, onCreate, onUpdate, onDelete, onCa
|
||||
}, [createMode, editMode, getData]);
|
||||
|
||||
const handleEdit = () => {
|
||||
if (onClearError) onClearError();
|
||||
setEditMode(true);
|
||||
};
|
||||
|
||||
const handleDelete = () => typeof onDelete === "function" && onDelete(identity.user.userId);
|
||||
|
||||
const handleCancel = () => {
|
||||
if (onClearError) onClearError();
|
||||
if (isNew && typeof onCancel === 'function') return onCancel();
|
||||
setEditMode(false);
|
||||
};
|
||||
|
||||
const handleSave = () => {
|
||||
if (onClearError) onClearError();
|
||||
const newSocio = { ...identity, ...formData };
|
||||
if (createMode && typeof onCreate === 'function') return onCreate(newSocio);
|
||||
if (typeof onUpdate === 'function') return onUpdate(newSocio, identity.user.userId);
|
||||
@@ -224,7 +222,13 @@ const SocioCard = ({ identity, isNew = false, onCreate, onUpdate, onDelete, onCa
|
||||
<div className='d-flex flex-column gap-1'>
|
||||
<Card.Title className="m-0">
|
||||
{editMode ? (
|
||||
<Form.Control className="themed-input" size="sm" value={formData.displayName} onChange={(e) => handleChange('displayName', e.target.value)} style={{ maxWidth: '220px' }} />
|
||||
<>
|
||||
<Form.Control className="themed-input" size="sm" isInvalid={!!fieldErrors?.displayName}
|
||||
value={formData.displayName} onChange={(e) => handleChange('displayName', e.target.value)} style={{ maxWidth: '220px' }} />
|
||||
<Form.Control.Feedback type="invalid" as="span">
|
||||
{getFieldError("displayName")}
|
||||
</Form.Control.Feedback>
|
||||
</>
|
||||
) : formData.displayName}
|
||||
</Card.Title>
|
||||
{editMode ? (
|
||||
@@ -262,8 +266,6 @@ const SocioCard = ({ identity, isNew = false, onCreate, onUpdate, onDelete, onCa
|
||||
</Card.Header>
|
||||
|
||||
<Card.Body>
|
||||
{(editMode || createMode) && renderErrorAlert(error)}
|
||||
|
||||
<ListGroup className="mt-2 border-1 rounded-3 shadow-sm">
|
||||
{[{
|
||||
label: 'DNI', clazz: '', icon: faIdCard, value: formData.dni, field: 'dni', type: 'text', maxWidth: '180px'
|
||||
@@ -279,7 +281,13 @@ const SocioCard = ({ identity, isNew = false, onCreate, onUpdate, onDelete, onCa
|
||||
<ListGroup.Item key={field} className="d-flex justify-content-between align-items-center">
|
||||
<span><FontAwesomeIcon icon={icon} className="me-2" />{label}</span>
|
||||
{editMode ? (
|
||||
<Form.Control className="themed-input" size="sm" type={type} value={value} onChange={(e) => handleChange(field, e.target.value)} style={{ maxWidth }} />
|
||||
<>
|
||||
<Form.Control className="themed-input" size="sm" type={type} value={value} isInvalid={!!fieldErrors?.[field]}
|
||||
onChange={(e) => handleChange(field, e.target.value)} style={{ maxWidth }} />
|
||||
<Form.Control.Feedback type="invalid" as="span">
|
||||
{getFieldError(`${field}`)}
|
||||
</Form.Control.Feedback>
|
||||
</>
|
||||
) : (
|
||||
<strong className={clazz}>{parseNull(value)}</strong>
|
||||
)}
|
||||
@@ -289,6 +297,7 @@ const SocioCard = ({ identity, isNew = false, onCreate, onUpdate, onDelete, onCa
|
||||
<ListGroup.Item className="d-flex justify-content-between align-items-center">
|
||||
<span><FontAwesomeIcon icon={faKey} className="me-2" />CONTRASEÑA</span>
|
||||
<div className="d-flex align-items-center gap-2" style={{ maxWidth: 'fit-content' }}>
|
||||
<>
|
||||
<Form.Control
|
||||
className="themed-input"
|
||||
size="sm"
|
||||
@@ -296,7 +305,12 @@ const SocioCard = ({ identity, isNew = false, onCreate, onUpdate, onDelete, onCa
|
||||
value={formData.password}
|
||||
onChange={(e) => handleChange('password', e.target.value)}
|
||||
style={{ maxWidth: '200px' }}
|
||||
isInvalid={!!fieldErrors?.password}
|
||||
/>
|
||||
<Form.Control.Feedback type="invalid" as="span">
|
||||
{getFieldError("password")}
|
||||
</Form.Control.Feedback>
|
||||
</>
|
||||
<Button
|
||||
size="sm"
|
||||
variant="outline-secondary"
|
||||
@@ -329,7 +343,13 @@ const SocioCard = ({ identity, isNew = false, onCreate, onUpdate, onDelete, onCa
|
||||
)}
|
||||
</Card.Subtitle>
|
||||
{editMode ? (
|
||||
<Form.Control className="themed-input" as="textarea" rows={3} value={formData.notes} onChange={(e) => handleChange('notes', e.target.value)} />
|
||||
<>
|
||||
<Form.Control className="themed-input" as="textarea" rows={3} value={formData.notes} isInvalid={!!fieldErrors?.notes}
|
||||
onChange={(e) => handleChange('notes', e.target.value)} />
|
||||
<Form.Control.Feedback type="invalid" as="span">
|
||||
{getFieldError("notes")}
|
||||
</Form.Control.Feedback>
|
||||
</>
|
||||
) : (
|
||||
<Card.Text>{parseNull(formData.notes)}</Card.Text>
|
||||
)}
|
||||
@@ -355,9 +375,9 @@ SocioCard.propTypes = {
|
||||
onUpdate: PropTypes.func,
|
||||
onDelete: PropTypes.func,
|
||||
onViewIncomes: PropTypes.func,
|
||||
error: PropTypes.string,
|
||||
onClearError: PropTypes.func,
|
||||
positionIfWaitlist: PropTypes.number
|
||||
positionIfWaitlist: PropTypes.number,
|
||||
fieldErrors: PropTypes.object
|
||||
};
|
||||
|
||||
export default SocioCard;
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
import { useState, useEffect, useRef } from 'react';
|
||||
import { Form, Row, Col, Button } from 'react-bootstrap';
|
||||
import { useDataContext } from '../../hooks/useDataContext';
|
||||
import { Alert } from 'react-bootstrap';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
const NewUserForm = ({ onSubmit, userType, plotNumber, errors = {} }) => {
|
||||
const NewUserForm = ({ onSubmit, userType, plotNumber, fieldErrors }) => {
|
||||
const { getData } = useDataContext();
|
||||
const fetchedOnce = useRef(false);
|
||||
|
||||
@@ -28,7 +27,7 @@ const NewUserForm = ({ onSubmit, userType, plotNumber, errors = {} }) => {
|
||||
fetchedOnce.current = true;
|
||||
|
||||
try {
|
||||
const latestNumber = await getData("http://localhost:8081/v2/huertos/users/latest-number");
|
||||
const latestNumber = await getData("http://localhost:8081/v2/huertos/users/latest-number", {}, false);
|
||||
setForm((prev) => ({
|
||||
...prev,
|
||||
memberNumber: latestNumber + 1
|
||||
@@ -46,12 +45,13 @@ const NewUserForm = ({ onSubmit, userType, plotNumber, errors = {} }) => {
|
||||
const trimmedName = form.displayName?.trim() ?? "";
|
||||
|
||||
const nuevoUsername = trimmedName
|
||||
? trimmedName.split(' ')[0].toLowerCase() : "";
|
||||
? trimmedName.split(' ')[0].toLowerCase() + String(form.memberNumber) : "";
|
||||
|
||||
if (form.username !== nuevoUsername) {
|
||||
setForm(prev => ({ ...prev, username: nuevoUsername }));
|
||||
}
|
||||
}, [form.memberNumber, form.displayName, form.username]);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [form.displayName]);
|
||||
|
||||
const handleChange = (e) => {
|
||||
const { name, value, type } = e.target;
|
||||
@@ -72,10 +72,10 @@ const NewUserForm = ({ onSubmit, userType, plotNumber, errors = {} }) => {
|
||||
if (onSubmit) onSubmit(form);
|
||||
};
|
||||
|
||||
const getFieldError = (field) => fieldErrors?.[field] ?? null;
|
||||
|
||||
return (
|
||||
<>
|
||||
{errors.general && <Alert variant="danger" className="my-2">{errors.general}</Alert>}
|
||||
|
||||
<Form onSubmit={handleSubmit} className="p-3 px-md-4">
|
||||
<Row className="gy-3">
|
||||
|
||||
@@ -100,10 +100,10 @@ const NewUserForm = ({ onSubmit, userType, plotNumber, errors = {} }) => {
|
||||
onChange={handleChange}
|
||||
required={required}
|
||||
maxLength={maxLength}
|
||||
isInvalid={!!errors[name]}
|
||||
isInvalid={!!getFieldError(name)}
|
||||
/>
|
||||
<Form.Control.Feedback type="invalid">
|
||||
{errors[name]}
|
||||
{getFieldError(name)}
|
||||
</Form.Control.Feedback>
|
||||
</Form.Group>
|
||||
</Col>
|
||||
|
||||
@@ -40,43 +40,94 @@ const getPFP = (tipo) => {
|
||||
const renderDescripcionSolicitud = (data, onProfile) => {
|
||||
const m = data.metadata;
|
||||
|
||||
if (onProfile) {
|
||||
switch (data.type) {
|
||||
case 0: // Alta
|
||||
return data.status === 1
|
||||
? 'Te han dado de alta correctamente.'
|
||||
: 'Has solicitado darte de alta.';
|
||||
|
||||
case 1: // Baja
|
||||
return data.status === 1
|
||||
? 'Tu baja ha sido procesada.'
|
||||
: 'Has solicitado darte de baja.';
|
||||
|
||||
case 2: // Añadir colaborador
|
||||
return data.status === 1
|
||||
? 'Tu solicitud de añadir colaborador ha sido aceptada.'
|
||||
: data.status === 2
|
||||
? 'Tu solicitud de añadir colaborador ha sido rechazada.'
|
||||
: 'Has solicitado añadir un colaborador.';
|
||||
|
||||
case 3: // Quitar colaborador
|
||||
return data.status === 1
|
||||
? 'Tu solicitud de quitar colaborador ha sido aceptada.'
|
||||
: data.status === 2
|
||||
? 'Tu solicitud de quitar colaborador ha sido rechazada.'
|
||||
: 'Has solicitado quitar tu colaborador.';
|
||||
|
||||
case 4: // Añadir parcela invernadero
|
||||
return data.status === 1
|
||||
? 'Tu solicitud de añadir parcela en invernadero ha sido aceptada.'
|
||||
: data.status === 2
|
||||
? 'Tu solicitud de añadir parcela en invernadero ha sido rechazada.'
|
||||
: 'Has solicitado añadir una parcela en invernadero.';
|
||||
|
||||
case 5: // Dejar parcela invernadero
|
||||
return data.status === 1
|
||||
? 'Tu solicitud de dejar la parcela en invernadero ha sido aceptada.'
|
||||
: data.status === 2
|
||||
? 'Tu solicitud de dejar la parcela en invernadero ha sido rechazada.'
|
||||
: 'Has solicitado dejar tu parcela en invernadero.';
|
||||
|
||||
default:
|
||||
return 'Solicitud desconocida.';
|
||||
}
|
||||
} else {
|
||||
// Para administradores o vista general
|
||||
switch (data.type) {
|
||||
case 0:
|
||||
return data.status === 1
|
||||
? 'Se ha aceptado esta solicitud de alta.'
|
||||
: `${m?.displayName ?? 'Alguien'} quiere darse de alta.`;
|
||||
? `Se ha aceptado la solicitud de alta de ${m?.displayName ?? data.name}.`
|
||||
: `${m?.displayName ?? data.name} quiere darse de alta.`;
|
||||
|
||||
case 1:
|
||||
return onProfile
|
||||
? 'Has solicitado darte de baja.'
|
||||
: `${data.name ?? 'Alguien'} quiere darse de baja.`;
|
||||
return data.status === 1
|
||||
? `Se ha procesado la baja de ${m?.displayName ?? data.name}.`
|
||||
: `${m?.displayName ?? data.name} quiere darse de baja.`;
|
||||
|
||||
case 2:
|
||||
if (onProfile) {
|
||||
return [
|
||||
'Has solicitado añadir un colaborador.',
|
||||
'Tu solicitud de colaborador ha sido aceptada.',
|
||||
'Tu solicitud de colaborador ha sido rechazada.'
|
||||
][data.status] ?? 'Solicitud de colaborador.';
|
||||
}
|
||||
return data.status === 0
|
||||
? `${data.name ?? 'Alguien'} quiere añadir un colaborador.`
|
||||
: `La solicitud de colaborador ha sido ${
|
||||
data.status === 1 ? 'aceptada' : 'rechazada'
|
||||
}.`;
|
||||
return data.status === 1
|
||||
? `La solicitud de añadir colaborador de ${m?.displayName ?? data.name} ha sido aceptada.`
|
||||
: data.status === 2
|
||||
? `La solicitud de añadir colaborador de ${m?.displayName ?? data.name} ha sido rechazada.`
|
||||
: `${m?.displayName ?? data.name} quiere añadir un colaborador.`;
|
||||
|
||||
case 3:
|
||||
return `${data.name ?? 'Alguien'} quiere quitar su colaborador.`;
|
||||
return data.status === 1
|
||||
? `La solicitud de quitar colaborador de ${m?.displayName ?? data.name} ha sido aceptada.`
|
||||
: data.status === 2
|
||||
? `La solicitud de quitar colaborador de ${m?.displayName ?? data.name} ha sido rechazada.`
|
||||
: `${m?.displayName ?? data.name} quiere quitar su colaborador.`;
|
||||
|
||||
case 4:
|
||||
return `${data.name ?? 'Alguien'} quiere una parcela en el invernadero.`;
|
||||
return data.status === 1
|
||||
? `La solicitud de añadir parcela de ${m?.displayName ?? data.name} ha sido aceptada.`
|
||||
: data.status === 2
|
||||
? `La solicitud de añadir parcela de ${m?.displayName ?? data.name} ha sido rechazada.`
|
||||
: `${m?.displayName ?? data.name} quiere una parcela en invernadero.`;
|
||||
|
||||
case 5:
|
||||
return `${data.name ?? 'Alguien'} quiere dejar su parcela del invernadero.`;
|
||||
return data.status === 1
|
||||
? `La solicitud de dejar parcela de ${m?.displayName ?? data.name} ha sido aceptada.`
|
||||
: data.status === 2
|
||||
? `La solicitud de dejar parcela de ${m?.displayName ?? data.name} ha sido rechazada.`
|
||||
: `${m?.displayName ?? data.name} quiere dejar su parcela del invernadero.`;
|
||||
|
||||
default:
|
||||
return 'Tipo de solicitud desconocido.';
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const SolicitudCard = ({
|
||||
@@ -148,7 +199,7 @@ const SolicitudCard = ({
|
||||
</ListGroup.Item>
|
||||
</ListGroup>
|
||||
|
||||
{m && (
|
||||
{m && !onProfile && (
|
||||
<>
|
||||
<Card.Subtitle className="card-subtitle mt-3 mb-2">
|
||||
Datos asociados a la solicitud
|
||||
|
||||
@@ -4,8 +4,8 @@ import { useData } from "../hooks/useData";
|
||||
|
||||
export const DataContext = createContext();
|
||||
|
||||
export const DataProvider = ({ config, children }) => {
|
||||
const data = useData(config);
|
||||
export const DataProvider = ({ config, onError, children }) => {
|
||||
const data = useData(config, onError);
|
||||
|
||||
return (
|
||||
<DataContext.Provider value={data}>
|
||||
|
||||
36
src/context/ErrorContext.jsx
Normal file
36
src/context/ErrorContext.jsx
Normal file
@@ -0,0 +1,36 @@
|
||||
import { createContext, useState, useContext } from 'react';
|
||||
import NotificationModal from '../components/NotificationModal';
|
||||
|
||||
const ErrorContext = createContext();
|
||||
|
||||
export const ErrorProvider = ({ children }) => {
|
||||
const [error, setError] = useState(null);
|
||||
|
||||
const showError = (err) => {
|
||||
setError({
|
||||
title: err.status ? `Error ${err.status}` : "Error",
|
||||
message: err.message,
|
||||
variant: 'danger'
|
||||
});
|
||||
};
|
||||
|
||||
const closeError = () => setError(null);
|
||||
|
||||
return (
|
||||
<ErrorContext.Provider value={{ showError }}>
|
||||
{children}
|
||||
{error && (
|
||||
<NotificationModal
|
||||
show={true}
|
||||
onClose={closeError}
|
||||
title={error.title}
|
||||
message={error.message}
|
||||
variant='danger'
|
||||
buttons={[{ label: "Aceptar", variant: "danger", onClick: closeError }]}
|
||||
/>
|
||||
)}
|
||||
</ErrorContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
export const useError = () => useContext(ErrorContext);
|
||||
@@ -1,7 +1,7 @@
|
||||
import { useState, useEffect, useCallback, useRef } from "react";
|
||||
import axios from "axios";
|
||||
|
||||
export const useData = (config) => {
|
||||
export const useData = (config, onError) => {
|
||||
const [data, setData] = useState(null);
|
||||
const [dataLoading, setLoading] = useState(true);
|
||||
const [dataError, setError] = useState(null);
|
||||
@@ -13,10 +13,55 @@ export const useData = (config) => {
|
||||
}
|
||||
}, [config]);
|
||||
|
||||
const getAuthHeaders = () => ({
|
||||
const getAuthHeaders = () => {
|
||||
const token = localStorage.getItem("token");
|
||||
if (!token) return { "Content-Type": "application/json" };
|
||||
return {
|
||||
"Content-Type": "application/json",
|
||||
"Authorization": `Bearer ${localStorage.getItem("token")}`,
|
||||
});
|
||||
"Authorization": `Bearer ${token}`,
|
||||
};
|
||||
};
|
||||
|
||||
const handleAxiosError = (err) => {
|
||||
if (err.response && err.response.data) {
|
||||
const data = err.response.data;
|
||||
|
||||
if (data.status === 422 && data.errors) {
|
||||
return {
|
||||
status: 422,
|
||||
errors: data.errors,
|
||||
path: data.path ?? null,
|
||||
timestamp: data.timestamp ?? null,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
status: data.status ?? err.response.status,
|
||||
error: data.error ?? null,
|
||||
message: data.message ?? err.response.statusText ?? "Error desconocido",
|
||||
path: data.path ?? null,
|
||||
timestamp: data.timestamp ?? null,
|
||||
};
|
||||
}
|
||||
|
||||
if (err.request) {
|
||||
return {
|
||||
status: null,
|
||||
error: "Network Error",
|
||||
message: "No se pudo conectar al servidor",
|
||||
path: null,
|
||||
timestamp: new Date().toISOString(),
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
status: null,
|
||||
error: "Client Error",
|
||||
message: err.message || "Error desconocido",
|
||||
path: null,
|
||||
timestamp: new Date().toISOString(),
|
||||
};
|
||||
};
|
||||
|
||||
const fetchData = useCallback(async () => {
|
||||
const current = configRef.current;
|
||||
@@ -32,69 +77,59 @@ export const useData = (config) => {
|
||||
});
|
||||
setData(response.data);
|
||||
} catch (err) {
|
||||
setError(err.response?.data);
|
||||
const error = handleAxiosError(err);
|
||||
setError(error);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (config?.baseUrl) {
|
||||
fetchData();
|
||||
}
|
||||
if (config?.baseUrl) fetchData();
|
||||
}, [config, fetchData]);
|
||||
|
||||
const getData = async (url, params = {}) => {
|
||||
const response = await axios.get(url, {
|
||||
headers: getAuthHeaders(),
|
||||
params,
|
||||
});
|
||||
const requestWrapper = async (method, endpoint, payload = null, refresh = false) => {
|
||||
try {
|
||||
const headers = getAuthHeaders();
|
||||
const cfg = { headers };
|
||||
let response;
|
||||
|
||||
if (method === "get") {
|
||||
if (payload) cfg.params = payload;
|
||||
response = await axios.get(endpoint, cfg);
|
||||
} else if (method === "delete") {
|
||||
if (payload) cfg.data = payload;
|
||||
response = await axios.delete(endpoint, cfg);
|
||||
} else {
|
||||
response = await axios[method](endpoint, payload, cfg);
|
||||
}
|
||||
|
||||
if (refresh) await fetchData();
|
||||
return response.data;
|
||||
|
||||
} catch (err) {
|
||||
const error = handleAxiosError(err);
|
||||
|
||||
if (error.status !== 422 && onError) {
|
||||
onError(error);
|
||||
}
|
||||
|
||||
setError(error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
const postData = async (endpoint, payload) => {
|
||||
const headers = {
|
||||
Authorization: `Bearer ${localStorage.getItem("token")}`,
|
||||
...(payload instanceof FormData ? {} : { "Content-Type": "application/json" }),
|
||||
};
|
||||
const response = await axios.post(endpoint, payload, { headers });
|
||||
await fetchData();
|
||||
return response.data;
|
||||
};
|
||||
|
||||
const putData = async (endpoint, payload) => {
|
||||
const response = await axios.put(endpoint, payload, {
|
||||
headers: getAuthHeaders(),
|
||||
});
|
||||
await fetchData();
|
||||
return response.data;
|
||||
};
|
||||
|
||||
const deleteData = async (endpoint) => {
|
||||
const response = await axios.delete(endpoint, {
|
||||
headers: getAuthHeaders(),
|
||||
});
|
||||
await fetchData();
|
||||
return response.data;
|
||||
};
|
||||
|
||||
const deleteDataWithBody = async (endpoint, payload) => {
|
||||
const response = await axios.delete(endpoint, {
|
||||
headers: getAuthHeaders(),
|
||||
data: payload,
|
||||
});
|
||||
await fetchData();
|
||||
return response.data;
|
||||
};
|
||||
const clearError = () => setError(null);
|
||||
|
||||
return {
|
||||
data,
|
||||
dataLoading,
|
||||
dataError,
|
||||
getData,
|
||||
postData,
|
||||
putData,
|
||||
deleteData,
|
||||
deleteDataWithBody,
|
||||
clearError,
|
||||
getData: (url, params, refresh = true) => requestWrapper("get", url, params, refresh),
|
||||
postData: (url, body, refresh = true) => requestWrapper("post", url, body, refresh),
|
||||
putData: (url, body, refresh = true) => requestWrapper("put", url, body, refresh),
|
||||
deleteData: (url, refresh = true) => requestWrapper("delete", url, null, refresh),
|
||||
deleteDataWithBody: (url, body, refresh = true) => requestWrapper("delete", url, body, refresh)
|
||||
};
|
||||
};
|
||||
|
||||
@@ -14,16 +14,19 @@ import 'bootstrap/dist/js/bootstrap.bundle.min.js'
|
||||
import "slick-carousel/slick/slick.css";
|
||||
import "slick-carousel/slick/slick-theme.css";
|
||||
import './css/index.css'
|
||||
import { ErrorProvider } from './context/ErrorContext.jsx'
|
||||
|
||||
createRoot(document.getElementById('root')).render(
|
||||
<StrictMode>
|
||||
<ConfigProvider>
|
||||
<ThemeProvider>
|
||||
<ErrorProvider>
|
||||
<AuthProvider>
|
||||
<BrowserRouter>
|
||||
<App />
|
||||
</BrowserRouter>
|
||||
</AuthProvider>
|
||||
</ErrorProvider>
|
||||
</ThemeProvider>
|
||||
</ConfigProvider>
|
||||
</StrictMode>
|
||||
|
||||
@@ -12,15 +12,16 @@ import PaginatedCardGrid from '../components/PaginatedCardGrid';
|
||||
import AnuncioCard from '../components/Anuncios/AnuncioCard';
|
||||
import AnunciosFilter from '../components/Anuncios/AnunciosFilter';
|
||||
|
||||
import { errorParser } from '../util/parsers/errorParser';
|
||||
import CustomModal from '../components/CustomModal';
|
||||
import { Button } from 'react-bootstrap';
|
||||
import { EditorProvider } from 'react-simple-wysiwyg';
|
||||
import { useError } from '../context/ErrorContext';
|
||||
|
||||
const PAGE_SIZE = 10;
|
||||
|
||||
const Anuncios = () => {
|
||||
const { config, configLoading } = useConfig();
|
||||
const { showError } = useError();
|
||||
|
||||
if (configLoading) return <p><LoadingIcon /></p>;
|
||||
|
||||
@@ -30,17 +31,16 @@ const Anuncios = () => {
|
||||
};
|
||||
|
||||
return (
|
||||
<DataProvider config={reqConfig}>
|
||||
<DataProvider config={reqConfig} onError={showError} >
|
||||
<AnunciosContent reqConfig={reqConfig} />
|
||||
</DataProvider>
|
||||
);
|
||||
};
|
||||
|
||||
const AnunciosContent = ({ reqConfig }) => {
|
||||
const { data, dataLoading, dataError, postData, putData, deleteData } = useDataContext();
|
||||
const { data, dataLoading, postData, putData, deleteData } = useDataContext();
|
||||
const [creatingAnuncio, setCreatingAnuncio] = useState(false);
|
||||
const [tempAnuncio, setTempAnuncio] = useState(null);
|
||||
const [error, setError] = useState(null);
|
||||
const [deleteTargetId, setDeleteTargetId] = useState(null);
|
||||
|
||||
const {
|
||||
@@ -103,22 +103,16 @@ const AnunciosContent = ({ reqConfig }) => {
|
||||
const handleCreateSubmit = async (nuevo) => {
|
||||
try {
|
||||
await postData(reqConfig.baseUrl, nuevo);
|
||||
setError(null);
|
||||
setCreatingAnuncio(false);
|
||||
setTempAnuncio(null);
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
} catch (err) {
|
||||
setTempAnuncio({ ...nuevo });
|
||||
setError(errorParser(err));
|
||||
}
|
||||
};
|
||||
|
||||
const handleEditSubmit = async (editado, id) => {
|
||||
try {
|
||||
await putData(`${reqConfig.baseUrl}/${id}`, editado);
|
||||
setError(null);
|
||||
} catch (err) {
|
||||
setError(errorParser(err));
|
||||
}
|
||||
};
|
||||
|
||||
const handleDelete = async (id) => {
|
||||
@@ -126,7 +120,6 @@ const AnunciosContent = ({ reqConfig }) => {
|
||||
};
|
||||
|
||||
if (dataLoading) return <p className="text-center my-5"><LoadingIcon /></p>;
|
||||
if (dataError) return <p className="text-danger text-center my-5">{dataError}</p>;
|
||||
|
||||
return (
|
||||
<CustomContainer>
|
||||
@@ -154,8 +147,6 @@ const AnunciosContent = ({ reqConfig }) => {
|
||||
isNew
|
||||
onCreate={handleCreateSubmit}
|
||||
onCancel={handleCancelCreate}
|
||||
error={error}
|
||||
onClearError={() => setError(null)}
|
||||
/>
|
||||
</EditorProvider>
|
||||
)}
|
||||
@@ -165,8 +156,6 @@ const AnunciosContent = ({ reqConfig }) => {
|
||||
anuncio={{...anuncio, idx: idx}}
|
||||
onUpdate={(a, id) => handleEditSubmit(a, id)}
|
||||
onDelete={() => handleDelete(anuncio.announceId)}
|
||||
error={error}
|
||||
onClearError={() => setError(null)}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
@@ -187,7 +176,7 @@ const AnunciosContent = ({ reqConfig }) => {
|
||||
setSearchTerm("");
|
||||
setDeleteTargetId(null);
|
||||
} catch (err) {
|
||||
setError(errorParser(err));
|
||||
console.error(err);
|
||||
}
|
||||
}}
|
||||
>
|
||||
|
||||
@@ -6,9 +6,11 @@ import CustomContainer from '../components/CustomContainer';
|
||||
import ContentWrapper from '../components/ContentWrapper';
|
||||
import LoadingIcon from '../components/LoadingIcon';
|
||||
import BalanceReport from '../components/Balance/BalanceReport';
|
||||
import { useError } from '../context/ErrorContext';
|
||||
|
||||
const Balance = () => {
|
||||
const { config, configLoading } = useConfig();
|
||||
const { showError } = useError();
|
||||
|
||||
if (configLoading || !config) return <p className="text-center my-5"><LoadingIcon /></p>;
|
||||
|
||||
@@ -17,17 +19,16 @@ const Balance = () => {
|
||||
};
|
||||
|
||||
return (
|
||||
<DataProvider config={reqConfig}>
|
||||
<DataProvider config={reqConfig} onError={showError}>
|
||||
<BalanceContent />
|
||||
</DataProvider>
|
||||
);
|
||||
};
|
||||
|
||||
const BalanceContent = () => {
|
||||
const { data, dataLoading, dataError } = useDataContext();
|
||||
const { data, dataLoading } = useDataContext();
|
||||
|
||||
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 (!data || !data.id) return <p className="text-center my-5">No se encontró el balance.</p>;
|
||||
|
||||
return (
|
||||
|
||||
@@ -11,9 +11,11 @@ import IfRole from '../components/Auth/IfRole.jsx';
|
||||
import { CONSTANTS } from '../util/constants.js';
|
||||
import CustomModal from '../components/CustomModal.jsx';
|
||||
import { Button } from 'react-bootstrap';
|
||||
import { useError } from '../context/ErrorContext';
|
||||
|
||||
const Documentacion = () => {
|
||||
const { config, configLoading } = useConfig();
|
||||
const { showError } = useError();
|
||||
|
||||
if (configLoading) return <p><LoadingIcon /></p>;
|
||||
|
||||
@@ -23,14 +25,14 @@ const Documentacion = () => {
|
||||
};
|
||||
|
||||
return (
|
||||
<DataProvider config={reqConfig}>
|
||||
<DataProvider config={reqConfig} onError={showError}>
|
||||
<DocumentacionContent reqConfig={reqConfig} />
|
||||
</DataProvider>
|
||||
);
|
||||
};
|
||||
|
||||
const DocumentacionContent = ({ reqConfig }) => {
|
||||
const { data, dataLoading, dataError, postData, deleteDataWithBody } = useDataContext();
|
||||
const { data, dataLoading, postData, deleteDataWithBody } = useDataContext();
|
||||
const [deleteTarget, setDeleteTarget] = useState(null);
|
||||
const fileUploadRef = useRef();
|
||||
|
||||
@@ -74,7 +76,6 @@ const DocumentacionContent = ({ reqConfig }) => {
|
||||
|
||||
{dataLoading ? (<LoadingIcon />) : (
|
||||
<div className="mt-4 d-flex flex-wrap gap-3 justify-content-start">
|
||||
{dataError && <p className="text-danger">Error al cargar los archivos.</p>}
|
||||
{data?.length === 0 && <p>No hay documentos todavía.</p>}
|
||||
{data?.filter(file => file.context === CONSTANTS.CONTEXT_HUERTOS)
|
||||
.map((file, idx) => (
|
||||
|
||||
@@ -16,14 +16,15 @@ import { GastosPDF } from '../components/Gastos/GastosPDF';
|
||||
|
||||
import '../css/Ingresos.css';
|
||||
import { CONSTANTS } from '../util/constants';
|
||||
import { errorParser } from '../util/parsers/errorParser';
|
||||
import CustomModal from '../components/CustomModal';
|
||||
import { Button } from 'react-bootstrap';
|
||||
import { useError } from '../context/ErrorContext';
|
||||
|
||||
const PAGE_SIZE = 10;
|
||||
|
||||
const Gastos = () => {
|
||||
const { config, configLoading } = useConfig();
|
||||
const { showError } = useError();
|
||||
|
||||
if (configLoading) return <p><LoadingIcon /></p>;
|
||||
|
||||
@@ -33,19 +34,19 @@ const Gastos = () => {
|
||||
};
|
||||
|
||||
return (
|
||||
<DataProvider config={reqConfig}>
|
||||
<DataProvider config={reqConfig} onError={showError}>
|
||||
<GastosContent reqConfig={reqConfig}/>
|
||||
</DataProvider>
|
||||
);
|
||||
};
|
||||
|
||||
const GastosContent = ({ reqConfig }) => {
|
||||
const { data, dataLoading, dataError, postData, putData, deleteData } = useDataContext();
|
||||
const { data, dataLoading, postData, putData, deleteData } = useDataContext();
|
||||
const [showPDFModal, setShowPDFModal] = useState(false);
|
||||
const [creatingGasto, setCreatingGasto] = useState(false);
|
||||
const [tempGasto, setTempGasto] = useState(null);
|
||||
const [error, setError] = useState(null);
|
||||
const [deleteTargetId, setDeleteTargetId] = useState(null);
|
||||
const [fieldErrors, setFieldErrors] = useState(null);
|
||||
|
||||
const {
|
||||
filtered,
|
||||
@@ -94,21 +95,24 @@ const GastosContent = ({ reqConfig }) => {
|
||||
const handleCreateSubmit = async (nuevo) => {
|
||||
try {
|
||||
await postData(reqConfig.baseUrl, nuevo);
|
||||
setError(null);
|
||||
setCreatingGasto(false);
|
||||
setTempGasto(null);
|
||||
setFieldErrors(null);
|
||||
} catch (err) {
|
||||
setTempGasto({ ...nuevo });
|
||||
setError(errorParser(err));
|
||||
if (err?.status === 422 && err?.errors) {
|
||||
setFieldErrors(err.errors);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const handleEditSubmit = async (editado, id) => {
|
||||
try {
|
||||
await putData(`${reqConfig.baseUrl}/${id}`, editado);
|
||||
setError(null);
|
||||
} catch (err) {
|
||||
setError(errorParser(err));
|
||||
if (err?.status === 422 && err?.errors) {
|
||||
setFieldErrors(err.errors);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -119,11 +123,10 @@ const GastosContent = ({ reqConfig }) => {
|
||||
const handleCancelCreate = () => {
|
||||
setCreatingGasto(false);
|
||||
setTempGasto(null);
|
||||
setError(null);
|
||||
setFieldErrors(null);
|
||||
};
|
||||
|
||||
if (dataLoading) return <p className="text-center my-5"><LoadingIcon /></p>;
|
||||
if (dataError) return <p className="text-danger text-center my-5">{dataError}</p>;
|
||||
|
||||
return (
|
||||
<CustomContainer>
|
||||
@@ -151,8 +154,7 @@ const GastosContent = ({ reqConfig }) => {
|
||||
isNew
|
||||
onCreate={handleCreateSubmit}
|
||||
onCancel={handleCancelCreate}
|
||||
error={error}
|
||||
onClearError={() => setError(null)}
|
||||
fieldErrors={fieldErrors}
|
||||
/>
|
||||
)}
|
||||
renderCard={(gasto) => (
|
||||
@@ -161,8 +163,7 @@ const GastosContent = ({ reqConfig }) => {
|
||||
gasto={gasto}
|
||||
onUpdate={handleEditSubmit}
|
||||
onDelete={handleDelete}
|
||||
error={error}
|
||||
onClearError={() => setError(null)}
|
||||
fieldErrors={fieldErrors}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
@@ -187,7 +188,7 @@ const GastosContent = ({ reqConfig }) => {
|
||||
setSearchTerm("");
|
||||
setDeleteTargetId(null);
|
||||
} catch (err) {
|
||||
setError(errorParser(err));
|
||||
console.error(err);
|
||||
}
|
||||
}}
|
||||
>
|
||||
|
||||
@@ -15,16 +15,17 @@ import IngresoCard from '../components/Ingresos/IngresoCard';
|
||||
import IngresosFilter from '../components/Ingresos/IngresosFilter';
|
||||
import { IngresosPDF } from '../components/Ingresos/IngresosPDF';
|
||||
import { CONSTANTS } from '../util/constants';
|
||||
import { errorParser } from '../util/parsers/errorParser';
|
||||
|
||||
import '../css/Ingresos.css';
|
||||
import CustomModal from '../components/CustomModal';
|
||||
import { Button } from 'react-bootstrap';
|
||||
import { useError } from '../context/ErrorContext';
|
||||
|
||||
const PAGE_SIZE = 10;
|
||||
|
||||
const Ingresos = () => {
|
||||
const { config, configLoading } = useConfig();
|
||||
const { showError } = useError();
|
||||
|
||||
if (configLoading) return <p><LoadingIcon /></p>;
|
||||
|
||||
@@ -36,19 +37,19 @@ const Ingresos = () => {
|
||||
};
|
||||
|
||||
return (
|
||||
<DataProvider config={reqConfig}>
|
||||
<DataProvider config={reqConfig} onError={showError}>
|
||||
<IngresosContent reqConfig={reqConfig} />
|
||||
</DataProvider>
|
||||
);
|
||||
};
|
||||
|
||||
const IngresosContent = ({ reqConfig }) => {
|
||||
const { data, dataLoading, dataError, postData, putData, deleteData } = useDataContext();
|
||||
const { data, dataLoading, postData, putData, deleteData } = useDataContext();
|
||||
const [showPDFModal, setShowPDFModal] = useState(false);
|
||||
const [creatingIngreso, setCreatingIngreso] = useState(false);
|
||||
const [tempIngreso, setTempIngreso] = useState(null);
|
||||
const [error, setError] = useState(null);
|
||||
const [deleteTargetId, setDeleteTargetId] = useState(null);
|
||||
const [fieldErrors, setFieldErrors] = useState(null);
|
||||
|
||||
const members = data
|
||||
? Array.from(
|
||||
@@ -116,37 +117,38 @@ const IngresosContent = ({ reqConfig }) => {
|
||||
const handleCancelCreate = () => {
|
||||
setCreatingIngreso(false);
|
||||
setTempIngreso(null);
|
||||
setError(null);
|
||||
setFieldErrors(null);
|
||||
};
|
||||
|
||||
const handleCreateSubmit = async (nuevo) => {
|
||||
try {
|
||||
await postData(reqConfig.rawUrl, nuevo);
|
||||
setError(null);
|
||||
setCreatingIngreso(false);
|
||||
setTempIngreso(null);
|
||||
setFieldErrors(null);
|
||||
} catch (err) {
|
||||
setTempIngreso({ ...nuevo });
|
||||
setError(errorParser(err));
|
||||
if (err?.status === 422 && err?.errors) {
|
||||
setFieldErrors(err.errors);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const handleEditSubmit = async (editado, id) => {
|
||||
try {
|
||||
await putData(`${reqConfig.rawUrl}/${id}`, editado);
|
||||
setError(null);
|
||||
} catch (err) {
|
||||
setError(errorParser(err));
|
||||
if (err?.status === 422 && err?.errors) {
|
||||
setFieldErrors(err.errors);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
const handleDelete = async (id) => {
|
||||
setDeleteTargetId(id);
|
||||
};
|
||||
|
||||
if (dataLoading) return <p className="text-center my-5"><LoadingIcon /></p>;
|
||||
if (dataError) return <p className="text-danger text-center my-5">{dataError}</p>;
|
||||
|
||||
return (
|
||||
<CustomContainer>
|
||||
@@ -173,9 +175,8 @@ const IngresosContent = ({ reqConfig }) => {
|
||||
isNew
|
||||
onCreate={handleCreateSubmit}
|
||||
onCancel={handleCancelCreate}
|
||||
error={error}
|
||||
onClearError={() => setError(null)}
|
||||
members={members}
|
||||
fieldErrors={fieldErrors}
|
||||
/>
|
||||
)}
|
||||
renderCard={(income) => (
|
||||
@@ -184,8 +185,7 @@ const IngresosContent = ({ reqConfig }) => {
|
||||
income={income}
|
||||
onUpdate={(data, id) => handleEditSubmit(data, id)}
|
||||
onDelete={() => handleDelete(income.incomeId)}
|
||||
error={error}
|
||||
onClearError={() => setError(null)}
|
||||
fieldErrors={fieldErrors}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
@@ -210,7 +210,7 @@ const IngresosContent = ({ reqConfig }) => {
|
||||
setSearchTerm("");
|
||||
setDeleteTargetId(null);
|
||||
} catch (err) {
|
||||
setError(errorParser(err));
|
||||
console.log(err);
|
||||
}
|
||||
}}
|
||||
>
|
||||
|
||||
@@ -16,9 +16,11 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
import { faPencil } from '@fortawesome/free-solid-svg-icons';
|
||||
import IfNotAuthenticated from '../components/Auth/IfNotAuthenticated';
|
||||
import NotificationModal from '../components/NotificationModal';
|
||||
import { useError } from '../context/ErrorContext';
|
||||
|
||||
const ListaEspera = () => {
|
||||
const { config, configLoading } = useConfig();
|
||||
const { showError } = useError();
|
||||
|
||||
if (configLoading) return <p><LoadingIcon /></p>;
|
||||
|
||||
@@ -29,7 +31,7 @@ const ListaEspera = () => {
|
||||
};
|
||||
|
||||
return (
|
||||
<DataProvider config={reqConfig}>
|
||||
<DataProvider config={reqConfig} onError={showError}>
|
||||
<ListaEsperaContent reqConfig={reqConfig} />
|
||||
</DataProvider>
|
||||
);
|
||||
@@ -37,7 +39,7 @@ const ListaEspera = () => {
|
||||
|
||||
const ListaEsperaContent = ({ reqConfig }) => {
|
||||
const { authStatus } = useAuth();
|
||||
const { data, dataLoading, dataError, postData } = useDataContext();
|
||||
const { data, dataLoading, postData } = useDataContext();
|
||||
|
||||
const [showWelcomeModal, setShowWelcomeModal] = useState(false);
|
||||
const [showNewUserFormModal, setShowNewUserFormModal] = useState(false);
|
||||
@@ -101,7 +103,6 @@ const ListaEsperaContent = ({ reqConfig }) => {
|
||||
};
|
||||
|
||||
if (dataLoading) return <p className="text-center my-5"><LoadingIcon /></p>;
|
||||
if (dataError) return <p className="text-danger text-center my-5">{dataError}</p>;
|
||||
|
||||
return (
|
||||
<CustomContainer>
|
||||
|
||||
@@ -31,6 +31,7 @@ import { Button, Col, Row } from 'react-bootstrap';
|
||||
import AnimatedDropdown from '../components/AnimatedDropdown';
|
||||
import { useAuth } from '../hooks/useAuth';
|
||||
import { CONSTANTS } from '../util/constants';
|
||||
import { useError } from '../context/ErrorContext';
|
||||
|
||||
const parseDate = (date) => {
|
||||
if (!date) return 'NO';
|
||||
@@ -53,6 +54,7 @@ const getPFP = (tipo) => {
|
||||
|
||||
const Perfil = () => {
|
||||
const { config, configLoading } = useConfig();
|
||||
const { showError } = useError();
|
||||
|
||||
if (configLoading || !config) return <p className="text-center my-5"><LoadingIcon /></p>;
|
||||
|
||||
@@ -75,14 +77,14 @@ const Perfil = () => {
|
||||
};
|
||||
|
||||
return (
|
||||
<DataProvider config={reqConfig}>
|
||||
<DataProvider config={reqConfig} onError={showError}>
|
||||
<PerfilContent config={reqConfig} />
|
||||
</DataProvider>
|
||||
);
|
||||
};
|
||||
|
||||
const PerfilContent = ({ config }) => {
|
||||
const { data, dataLoading, dataError, postData } = useDataContext();
|
||||
const { data, dataLoading, postData } = useDataContext();
|
||||
const { logout } = useAuth();
|
||||
|
||||
const identity = JSON.parse(localStorage.getItem("identity"));
|
||||
@@ -99,6 +101,7 @@ const PerfilContent = ({ config }) => {
|
||||
const [showRemoveCollaboratorModal, setShowRemoveCollaboratorModal] = useState(false);
|
||||
const [feedbackModal, setFeedbackModal] = useState(null);
|
||||
const closeFeedback = () => setFeedbackModal(null);
|
||||
const [fieldErrors, setFieldErrors] = useState(null);
|
||||
|
||||
const baseMetadata = {
|
||||
displayName: identity.user.displayName,
|
||||
@@ -113,6 +116,7 @@ const PerfilContent = ({ config }) => {
|
||||
};
|
||||
|
||||
const sendSimpleRequest = async (type) => {
|
||||
setFieldErrors(null);
|
||||
const requestOf = type == 1 ? "baja" : type == 2 ? "adición de colaborador" :
|
||||
type == 3 ? "eliminación de colaborador" : type == 4 ? "adición de invernadero" :
|
||||
type == 5 ? "eliminación de invernadero" : "desconocido";
|
||||
@@ -131,21 +135,16 @@ const PerfilContent = ({ config }) => {
|
||||
onClick: closeFeedback
|
||||
});
|
||||
} catch (err) {
|
||||
setFeedbackModal({
|
||||
title: 'Error',
|
||||
message: err.message,
|
||||
variant: 'danger',
|
||||
onClick: closeFeedback
|
||||
});
|
||||
if (err?.status === 422 && err?.errors) {
|
||||
setFieldErrors(err.errors);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const [validationErrors, setValidationErrors] = useState({});
|
||||
|
||||
const [newPasswordData, setNewPasswordData] = useState({
|
||||
currentPassword: "",
|
||||
oldPassword: "",
|
||||
newPassword: "",
|
||||
confirmNewPassword: ""
|
||||
serviceId: identity.account.serviceId
|
||||
});
|
||||
|
||||
const [showOld, setShowOld] = useState(false);
|
||||
@@ -157,28 +156,21 @@ const PerfilContent = ({ config }) => {
|
||||
...newPasswordData,
|
||||
[e.target.name]: e.target.value
|
||||
});
|
||||
setFieldErrors(null);
|
||||
}
|
||||
|
||||
const handleChangePassword = async () => {
|
||||
try {
|
||||
const validOldPassword = await postData(config.loginValidateUrl, {
|
||||
userId: identity.user.userId,
|
||||
password: newPasswordData.currentPassword
|
||||
});
|
||||
if (!validOldPassword.valid) throw new Error("La contraseña actual es incorrecta.");
|
||||
if (newPasswordData.newPassword !== newPasswordData.confirmNewPassword) throw new Error("Las contraseñas no coinciden.");
|
||||
if (newPasswordData.newPassword.length < 8) throw new Error("La nueva contraseña debe tener al menos 8 caracteres.");
|
||||
|
||||
const response = await postData(config.changePasswordUrl, {
|
||||
userId: identity.user.userId,
|
||||
newPassword: newPasswordData.newPassword
|
||||
await postData(config.changePasswordUrl, {
|
||||
oldPassword: newPasswordData.oldPassword,
|
||||
newPassword: newPasswordData.newPassword,
|
||||
serviceId: identity.user.account.serviceId
|
||||
});
|
||||
|
||||
if (!response) throw new Error("Error al cambiar la contraseña.");
|
||||
setNewPasswordData({
|
||||
currentPassword: "",
|
||||
oldPassword: "",
|
||||
newPassword: "",
|
||||
confirmNewPassword: ""
|
||||
serviceId: identity.user.account.serviceId
|
||||
});
|
||||
|
||||
setFeedbackModal({
|
||||
@@ -191,14 +183,13 @@ const PerfilContent = ({ config }) => {
|
||||
}
|
||||
});
|
||||
} catch (err) {
|
||||
setFeedbackModal({
|
||||
title: 'Error',
|
||||
message: err.message,
|
||||
variant: 'danger',
|
||||
onClick: closeFeedback
|
||||
});
|
||||
if (err?.status === 422 && err?.errors) {
|
||||
setFieldErrors(err.errors);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const getFieldError = (field) => fieldErrors?.[field] ?? null;
|
||||
|
||||
const mappedRequests = myRequests.map(r => ({
|
||||
...r,
|
||||
@@ -208,7 +199,6 @@ const PerfilContent = ({ config }) => {
|
||||
}));
|
||||
|
||||
if (dataLoading) return <p className="text-center my-5"><LoadingIcon /></p>;
|
||||
if (dataError) return <p className="text-danger text-center my-5">{dataError}</p>;
|
||||
|
||||
return (
|
||||
<CustomContainer>
|
||||
@@ -243,6 +233,7 @@ const PerfilContent = ({ config }) => {
|
||||
{!hasCollaborator && !hasCollaboratorRequest && (
|
||||
<div className="dropdown-item d-flex align-items-center" onClick={() => {
|
||||
setShowAddCollaboratorModal(true);
|
||||
setFieldErrors(null);
|
||||
closeDropdown();
|
||||
}}>
|
||||
<FontAwesomeIcon icon={faUserPlus} className="me-2" />Añadir un colaborador
|
||||
@@ -334,7 +325,11 @@ const PerfilContent = ({ config }) => {
|
||||
placeholder=""
|
||||
name="currentPassword"
|
||||
className="rounded-4"
|
||||
isInvalid={!!fieldErrors?.oldPassword}
|
||||
/>
|
||||
<Form.Control.Feedback type="invalid">
|
||||
{getFieldError("oldPassword")}
|
||||
</Form.Control.Feedback>
|
||||
<Button
|
||||
variant="link"
|
||||
className="show-button position-absolute end-0 top-50 translate-middle-y me-2"
|
||||
@@ -356,7 +351,11 @@ const PerfilContent = ({ config }) => {
|
||||
placeholder=""
|
||||
name="newPassword"
|
||||
className="rounded-4"
|
||||
isInvalid={!!fieldErrors?.newPassword}
|
||||
/>
|
||||
<Form.Control.Feedback type="invalid" as="span">
|
||||
{getFieldError("newPassword")}
|
||||
</Form.Control.Feedback>
|
||||
<Button
|
||||
variant="link"
|
||||
className="show-button position-absolute end-0 top-50 translate-middle-y me-2"
|
||||
@@ -396,7 +395,6 @@ const PerfilContent = ({ config }) => {
|
||||
newPasswordData.newPassword === '' || newPasswordData.confirmNewPassword === '' ||
|
||||
newPasswordData.currentPassword === ''
|
||||
}
|
||||
onClick={(e) => { e.preventDefault(); handleChangePassword(); }}
|
||||
type='submit'
|
||||
variant="warning"
|
||||
style={{ width: 'fit-content' }}
|
||||
@@ -413,17 +411,16 @@ const PerfilContent = ({ config }) => {
|
||||
show={showAddCollaboratorModal}
|
||||
onClose={() => {
|
||||
setShowAddCollaboratorModal(false);
|
||||
setValidationErrors({});
|
||||
setFieldErrors(null);
|
||||
}}
|
||||
>
|
||||
<NewUserForm
|
||||
userType={3}
|
||||
plotNumber={identity.metadata.plotNumber}
|
||||
errors={validationErrors}
|
||||
fieldErrors={fieldErrors}
|
||||
onSubmit={async (formData) => {
|
||||
console.log("🚀 Enviando al backend...", formData); // Debug
|
||||
try {
|
||||
setValidationErrors({});
|
||||
setFieldErrors(null);
|
||||
|
||||
await postData(config.requestUrl, {
|
||||
type: CONSTANTS.REQUEST_TYPE_ADD_COLLABORATOR,
|
||||
@@ -449,9 +446,10 @@ const PerfilContent = ({ config }) => {
|
||||
onClick: closeFeedback
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error("💥 Error al añadir:", error);
|
||||
setValidationErrors({ general: error.message || "Ha ocurrido un error al procesar la solicitud." });
|
||||
} catch (err) {
|
||||
if (err?.status === 422 && err?.errors) {
|
||||
setFieldErrors(err.errors);
|
||||
}
|
||||
}
|
||||
}}
|
||||
/>
|
||||
@@ -483,13 +481,8 @@ const PerfilContent = ({ config }) => {
|
||||
onClick: closeFeedback
|
||||
});
|
||||
setShowRemoveCollaboratorModal(false);
|
||||
} catch (err) {
|
||||
setFeedbackModal({
|
||||
title: "Error",
|
||||
message: err.message,
|
||||
variant: "danger",
|
||||
onClick: closeFeedback
|
||||
});
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
}}
|
||||
>
|
||||
|
||||
@@ -15,15 +15,16 @@ import { SociosPDF } from '../components/Socios/SociosPDF';
|
||||
import PaginatedCardGrid from '../components/PaginatedCardGrid';
|
||||
import CustomModal from '../components/CustomModal';
|
||||
import IngresoCard from '../components/Ingresos/IngresoCard';
|
||||
import { errorParser } from '../util/parsers/errorParser';
|
||||
|
||||
import '../css/Socios.css';
|
||||
import { Button } from 'react-bootstrap';
|
||||
import { useError } from '../context/ErrorContext';
|
||||
|
||||
const PAGE_SIZE = 10;
|
||||
|
||||
const Socios = () => {
|
||||
const { config, configLoading } = useConfig();
|
||||
const { showError } = useError();
|
||||
|
||||
if (configLoading || !config) return <p className="text-center my-5"><LoadingIcon /></p>;
|
||||
|
||||
@@ -35,14 +36,14 @@ const Socios = () => {
|
||||
};
|
||||
|
||||
return (
|
||||
<DataProvider config={reqConfig}>
|
||||
<DataProvider config={reqConfig} onError={showError}>
|
||||
<SociosContent reqConfig={reqConfig} />
|
||||
</DataProvider>
|
||||
);
|
||||
};
|
||||
|
||||
const SociosContent = ({ reqConfig }) => {
|
||||
const { data, dataLoading, dataError, getData, postData, putData, deleteData } = useDataContext();
|
||||
const { data, dataLoading, getData, postData, putData, deleteData } = useDataContext();
|
||||
|
||||
const [showPDFModal, setShowPDFModal] = useState(false);
|
||||
const [creatingSocio, setCreatingSocio] = useState(false);
|
||||
@@ -51,9 +52,9 @@ const SociosContent = ({ reqConfig }) => {
|
||||
const [selectedMemberNumber, setSelectedMemberNumber] = useState(null);
|
||||
const [incomes, setIncomes] = useState([]);
|
||||
const [incomesLoading, setIncomesLoading] = useState(false);
|
||||
const [incomesError, setIncomesError] = useState(null);
|
||||
const [error, setError] = useState(null);
|
||||
const [deleteTargetId, setDeleteTargetId] = useState(null);
|
||||
const [fieldErrors, setFieldErrors] = useState(null);
|
||||
const [incomeFieldErrors, setIncomeFieldErrors] = useState(null);
|
||||
|
||||
const {
|
||||
filtered,
|
||||
@@ -122,28 +123,31 @@ const SociosContent = ({ reqConfig }) => {
|
||||
const handleCancelCreate = () => {
|
||||
setCreatingSocio(false);
|
||||
setTempSocio(null);
|
||||
setError(null);
|
||||
setFieldErrors(null);
|
||||
};
|
||||
|
||||
const handleCreateSubmit = async (newSocio) => {
|
||||
try {
|
||||
newSocio.userName = newSocio.displayName.split(" ")[0].toLowerCase() + newSocio.memberNumber;
|
||||
await postData(reqConfig.baseUrl, newSocio);
|
||||
setError(null);
|
||||
setCreatingSocio(false);
|
||||
setTempSocio(null);
|
||||
setFieldErrors(null);
|
||||
} catch (err) {
|
||||
setTempSocio({ ...newSocio });
|
||||
setError(errorParser(err));
|
||||
if (err?.status === 422 && err?.errors) {
|
||||
setFieldErrors(err.errors);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const handleEditSubmit = async (updatedSocio, userId) => {
|
||||
try {
|
||||
await putData(`${reqConfig.baseUrl}/${userId}`, updatedSocio);
|
||||
setError(null);
|
||||
} catch (err) {
|
||||
setError(errorParser(err));
|
||||
if (err?.status === 422 && err?.errors) {
|
||||
setFieldErrors(err.errors);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -156,14 +160,14 @@ const SociosContent = ({ reqConfig }) => {
|
||||
setShowIncomesModal(true);
|
||||
setIncomes([]);
|
||||
setIncomesLoading(true);
|
||||
setIncomesError(null);
|
||||
setIncomeFieldErrors(null);
|
||||
|
||||
try {
|
||||
const url = reqConfig.incomesUrl.replace(":memberNumber", memberNumber);
|
||||
const res = await getData(url);
|
||||
setIncomes(res);
|
||||
} catch (err) {
|
||||
setIncomesError(err.message);
|
||||
console.error(err);
|
||||
} finally {
|
||||
setIncomesLoading(false);
|
||||
}
|
||||
@@ -174,7 +178,9 @@ const SociosContent = ({ reqConfig }) => {
|
||||
await putData(`${reqConfig.rawIncomesUrl}/${editado.incomeId}`, editado);
|
||||
await handleViewIncomes(selectedMemberNumber);
|
||||
} catch (err) {
|
||||
console.error("Error actualizando ingreso:", err);
|
||||
if (err?.status === 422 && err?.errors) {
|
||||
setIncomeFieldErrors(err.errors);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -182,7 +188,6 @@ const SociosContent = ({ reqConfig }) => {
|
||||
const closePDFPopup = () => setShowPDFModal(false);
|
||||
|
||||
if (dataLoading) return <p className="text-center my-5"><LoadingIcon /></p>;
|
||||
if (dataError) return <p className="text-danger text-center my-5">{dataError}</p>;
|
||||
|
||||
return (
|
||||
<CustomContainer>
|
||||
@@ -197,7 +202,7 @@ const SociosContent = ({ reqConfig }) => {
|
||||
searchTerm={searchTerm}
|
||||
onSearchChange={setSearchTerm}
|
||||
filtersComponent={<SociosFilter filters={filters} onChange={setFilters} />}
|
||||
onCreate={handleCreate}
|
||||
//onCreate={handleCreate}
|
||||
onPDF={showPDFPopup}
|
||||
/>
|
||||
|
||||
@@ -210,8 +215,7 @@ const SociosContent = ({ reqConfig }) => {
|
||||
isNew
|
||||
onCreate={handleCreateSubmit}
|
||||
onCancel={handleCancelCreate}
|
||||
error={error}
|
||||
onClearError={() => setError(null)}
|
||||
fieldErrors={fieldErrors}
|
||||
/>
|
||||
)}
|
||||
renderCard={(identity) => {
|
||||
@@ -227,9 +231,8 @@ const SociosContent = ({ reqConfig }) => {
|
||||
onDelete={handleDelete}
|
||||
onCancel={handleCancelCreate}
|
||||
onViewIncomes={() => handleViewIncomes(identity.metadata.memberNumber)}
|
||||
error={error}
|
||||
onClearError={() => setError(null)}
|
||||
positionIfWaitlist={position}
|
||||
fieldErrors={fieldErrors}
|
||||
/>
|
||||
);
|
||||
}}
|
||||
@@ -247,14 +250,13 @@ const SociosContent = ({ reqConfig }) => {
|
||||
title={`Ingresos del socio nº ${selectedMemberNumber}`}
|
||||
>
|
||||
{incomesLoading && <p className="text-center my-3"><LoadingIcon /></p>}
|
||||
{incomesError && <p className="text-danger text-center my-3">{incomesError}</p>}
|
||||
{!incomesLoading && !incomesError && incomes.length === 0 && (
|
||||
{!incomesLoading && incomes.length === 0 && (
|
||||
<p className="text-center my-3">Este socio no tiene ingresos registrados.</p>
|
||||
)}
|
||||
<div className="d-flex flex-wrap gap-3 p-3 justify-content-start">
|
||||
{incomes.map((income) => (
|
||||
<IngresoCard key={income.incomeId} income={income}
|
||||
onUpdate={handleIncomeUpdate} className='from-members' />
|
||||
onUpdate={handleIncomeUpdate} className='from-members' fieldErrors={incomeFieldErrors} />
|
||||
))}
|
||||
</div>
|
||||
</CustomModal>
|
||||
@@ -275,7 +277,7 @@ const SociosContent = ({ reqConfig }) => {
|
||||
setSearchTerm("");
|
||||
setDeleteTargetId(null);
|
||||
} catch (err) {
|
||||
setError(errorParser(err));
|
||||
console.error(err);
|
||||
}
|
||||
}}
|
||||
>
|
||||
|
||||
@@ -12,11 +12,13 @@ import PaginatedCardGrid from '../components/PaginatedCardGrid';
|
||||
import SolicitudCard from '../components/Solicitudes/SolicitudCard';
|
||||
import { Button } from 'react-bootstrap';
|
||||
import CustomModal from '../components/CustomModal';
|
||||
import { useError } from '../context/ErrorContext';
|
||||
|
||||
const PAGE_SIZE = 10;
|
||||
|
||||
const Solicitudes = () => {
|
||||
const { config, configLoading } = useConfig();
|
||||
const { showError } = useError();
|
||||
|
||||
if (configLoading || !config) return <p className="text-center my-5"><LoadingIcon /></p>;
|
||||
|
||||
@@ -29,14 +31,14 @@ const Solicitudes = () => {
|
||||
};
|
||||
|
||||
return (
|
||||
<DataProvider config={reqConfig}>
|
||||
<DataProvider config={reqConfig} onError={showError}>
|
||||
<SolicitudesContent reqConfig={reqConfig} />
|
||||
</DataProvider>
|
||||
);
|
||||
};
|
||||
|
||||
const SolicitudesContent = ({ reqConfig }) => {
|
||||
const { data, dataLoading, dataError, putData, deleteData } = useDataContext();
|
||||
const { data, dataLoading, putData, deleteData } = useDataContext();
|
||||
const [deleteTargetId, setDeleteTargetId] = useState(null);
|
||||
|
||||
const {
|
||||
@@ -84,7 +86,6 @@ const SolicitudesContent = ({ reqConfig }) => {
|
||||
}
|
||||
|
||||
if (dataLoading) return <p className="text-center my-5"><LoadingIcon /></p>;
|
||||
if (dataError) return <p className="text-danger text-center my-5">{dataError}</p>;
|
||||
|
||||
return (
|
||||
<CustomContainer>
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
export const renderErrorAlert = (error, options = {}) => {
|
||||
const { className = 'alert alert-danger py-1 px-2 small', role = 'alert' } = options;
|
||||
|
||||
if (!error) return null;
|
||||
|
||||
return (
|
||||
<div className={className} role={role}>
|
||||
{typeof error === 'string' ? error : 'An unexpected error occurred.'}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export const resetErrorIfEditEnds = (editMode, setError) => {
|
||||
if (!editMode) setError(null);
|
||||
};
|
||||
@@ -1,10 +0,0 @@
|
||||
export const errorParser = (err) => {
|
||||
const message = err.response?.data?.message;
|
||||
try {
|
||||
const parsed = JSON.parse(message);
|
||||
return Object.values(parsed)[0];
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
} catch (e) {
|
||||
return message || err.message || "Unknown error";
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user