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