Ongoing adaptation to new backend structure

This commit is contained in:
Jose
2026-01-25 23:43:25 +01:00
parent 5f5d54d642
commit 59efb7d81f
31 changed files with 505 additions and 514 deletions

View File

@@ -1,77 +1,72 @@
{ {
"apiConfig": { "apiConfig": {
"baseUrl": "http://api.huertos.local", "baseUrl": "http://localhost:8081/v2/huertos",
"coreUrl": "http://api.miarma.local", "coreUrl": "http://localhost:8080/v2/core",
"endpoints": { "endpoints": {
"auth": { "auth": {
"login": "/v1/login", "login": "/auth/login",
"validateToken": "/auth/v1/validate-token", "refreshToken": "/auth/refresh",
"refreshToken": "/auth/v1/refresh-token", "changePassword": "/auth/change-password",
"changePassword": "/auth/v1/change-password", "validateToken": "/auth/validate"
"loginValidate": "/auth/v1/login/validate"
}, },
"members": { "users": {
"all": "/raw/v1/members", "all": "/users",
"byId": "/raw/v1/members/:user_id", "byId": "/users/:userId",
"profile": "/v1/members/profile", "me": "/users/me",
"byMemberNumber": "/v1/members/number/:member_number", "latestNumber": "/users/latest-number",
"byPlotNumber": "/v1/members/plot/:plot_number", "waitlist": "/users/waitlist",
"byDni": "/v1/members/dni/:dni", "waitlistLimited": "/users/waitlist/limited",
"payments": "/v1/members/number/:member_number/incomes", "byMemberNumber": "/users/number/:memberNumber",
"hasPaid": "/v1/members/number/:member_number/has-paid", "incomesPreview": "/users/number/:memberNumber/incomes",
"waitlist": "/v1/members/waitlist", "hasPaid": "/users/number/:memberNumber/has-paid",
"limitedWaitlist": "/v1/members/waitlist/limited", "hasCollaborator": "/users/number/:memberNumber/has-collaborator",
"lastMemberNumber": "/v1/members/latest-number", "hasGreenhouse": "/users/number/:memberNumber/has-greenhouse",
"hasCollaborator": "/v1/members/number/:member_number/has-collaborator", "hasCollaboratorRequest": "/users/number/:memberNumber/has-collaborator-request",
"hasCollaboratorRequest": "/v1/members/number/:member_number/has-collaborator-request", "hasGreenhouseRequest": "/users/number/:memberNumber/has-greenhouse-request",
"hasGreenHouse": "/v1/members/number/:member_number/has-greenhouse", "byPlotNumber": "/users/plot/:plotNumber",
"hasGreenHouseRequest": "/v1/members/number/:member_number/has-greenhouse-request", "byDni": "/users/dni/:dni"
"changeType": "/v1/members/number/:user_id/type",
"changeStatus": "/v1/members/number/:user_id/status"
}, },
"incomes": { "incomes": {
"all": "/raw/v1/incomes", "all": "/incomes",
"allWithNames": "/raw/v1/incomes-with-names", "withNames": "/incomes/with-names",
"byId": "/raw/v1/incomes/:income_id", "mine": "/incomes/mine",
"myIncomes": "/v1/incomes/my-incomes" "byId": "/incomes/:incomeId"
}, },
"expenses": { "expenses": {
"all": "/raw/v1/expenses", "all": "/expenses",
"byId": "/raw/v1/expenses/:expense_id" "byId": "/expenses/:expenseId"
}, },
"balance": { "balance": {
"all": "/raw/v1/balance" "all": "/balance"
}, },
"announces": { "announcements": {
"all": "/raw/v1/announces", "all": "/announcements",
"byId": "/raw/v1/announces/:announce_id" "byId": "/announcements/:announceId"
}, },
"requests": { "requests": {
"all": "/raw/v1/requests", "all": "/requests",
"byId": "/raw/v1/requests/:request_id", "waitlist": "/requests/waitlist",
"allWithPreUsers": "/v1/requests-full", "byId": "/requests/:requestId",
"byIdWithPreUser": "/v1/requests-full/:request_id", "count": "/requests/count",
"countPending": "/v1/requests/count", "mine": "/requests/mine",
"myRequests": "/v1/requests/my-requests", "full": "/requests/full",
"accept": "/v1/requests/:request_id/accept", "fullById": "/requests/full/:requestId",
"reject": "/v1/requests/:request_id/reject" "accept": "/requests/:requestId/accept",
"reject": "/requests/:requestId/reject"
}, },
"pre_users": { "preUsers": {
"all": "/raw/v1/pre_users", "all": "/pre-users",
"byId": "/raw/v1/pre_users/:pre_user_id", "byId": "/pre-users/:preUserId",
"validation": "/v1/pre_users/validate" "validate": "/pre-users/validate"
}, },
"files": { "files": {
"all": "/raw/v1/files", "all": "/files",
"byId": "/raw/v1/files/:file_id", "byId": "/files/:fileId"
"upload": "/raw/v1/files/upload",
"download": "/raw/v1/files/download/:file_id",
"userFiles": "/raw/v1/files/myfiles"
}, },
"mail": { "mail": {
"all": "/v1/mails", "all": "/mails",
"byIndex": "/v1/mails/:index", "byIndex": "/mails/:index",
"send": "/v1/mails/send" "send": "/mails/send"
} }
} }
} }

View File

@@ -1,77 +1,71 @@
{ {
"apiConfig": { "apiConfig": {
"baseUrl": "https://api.huertosbellavista.es", "baseUrl": "https://api.miarma.net/v2/huertos",
"coreUrl": "https://api.miarma.net", "coreUrl": "https://api.miarma.net/v2/core",
"endpoints": { "endpoints": {
"auth": { "auth": {
"login": "/v1/login", "login": "/auth/login",
"validateToken": "/auth/v1/validate-token", "refreshToken": "/auth/refresh",
"refreshToken": "/auth/v1/refresh-token", "changePassword": "/auth/change-password"
"changePassword": "/auth/v1/change-password",
"loginValidate": "/auth/v1/login/validate"
}, },
"members": { "users": {
"all": "/raw/v1/members", "all": "/users",
"byId": "/raw/v1/members/:user_id", "byId": "/users/:userId",
"profile": "/v1/members/profile", "me": "/users/me",
"byMemberNumber": "/v1/members/number/:member_number", "latestNumber": "/users/latest-number",
"byPlotNumber": "/v1/members/plot/:plot_number", "waitlist": "/users/waitlist",
"byDni": "/v1/members/dni/:dni", "waitlistLimited": "/users/waitlist/limited",
"payments": "/v1/members/number/:member_number/incomes", "byMemberNumber": "/users/number/:memberNumber",
"hasPaid": "/v1/members/number/:member_number/has-paid", "incomesPreview": "/users/number/:memberNumber/incomes",
"waitlist": "/v1/members/waitlist", "hasPaid": "/users/number/:memberNumber/has-paid",
"limitedWaitlist": "/v1/members/waitlist/limited", "hasCollaborator": "/users/number/:memberNumber/has-collaborator",
"lastMemberNumber": "/v1/members/latest-number", "hasGreenhouse": "/users/number/:memberNumber/has-greenhouse",
"hasCollaborator": "/v1/members/number/:member_number/has-collaborator", "hasCollaboratorRequest": "/users/number/:memberNumber/has-collaborator-request",
"hasCollaboratorRequest": "/v1/members/number/:member_number/has-collaborator-request", "hasGreenhouseRequest": "/users/number/:memberNumber/has-greenhouse-request",
"hasGreenHouse": "/v1/members/number/:member_number/has-greenhouse", "byPlotNumber": "/users/plot/:plotNumber",
"hasGreenHouseRequest": "/v1/members/number/:member_number/has-greenhouse-request", "byDni": "/users/dni/:dni"
"changeType": "/v1/members/number/:user_id/type",
"changeStatus": "/v1/members/number/:user_id/status"
}, },
"incomes": { "incomes": {
"all": "/raw/v1/incomes", "all": "/incomes",
"allWithNames": "/raw/v1/incomes-with-names", "withNames": "/incomes/with-names",
"byId": "/raw/v1/incomes/:income_id", "mine": "/incomes/mine",
"myIncomes": "/v1/incomes/my-incomes" "byId": "/incomes/:incomeId"
}, },
"expenses": { "expenses": {
"all": "/raw/v1/expenses", "all": "/expenses",
"byId": "/raw/v1/expenses/:expense_id" "byId": "/expenses/:expenseId"
}, },
"balance": { "balance": {
"all": "/raw/v1/balance" "all": "/balance"
}, },
"announces": { "announcements": {
"all": "/raw/v1/announces", "all": "/announcements",
"byId": "/raw/v1/announces/:announce_id" "byId": "/announcements/:announceId"
}, },
"requests": { "requests": {
"all": "/raw/v1/requests", "all": "/requests",
"byId": "/raw/v1/requests/:request_id", "waitlist": "/requests/waitlist",
"allWithPreUsers": "/v1/requests-full", "byId": "/requests/:requestId",
"byIdWithPreUser": "/v1/requests-full/:request_id", "count": "/requests/count",
"countPending": "/v1/requests/count", "mine": "/requests/mine",
"myRequests": "/v1/requests/my-requests", "full": "/requests/full",
"accept": "/v1/requests/:request_id/accept", "fullById": "/requests/full/:requestId",
"reject": "/v1/requests/:request_id/reject" "accept": "/requests/:requestId/accept",
"reject": "/requests/:requestId/reject"
}, },
"pre_users": { "preUsers": {
"all": "/raw/v1/pre_users", "all": "/pre-users",
"byId": "/raw/v1/pre_users/:pre_user_id", "byId": "/pre-users/:preUserId",
"validation": "/v1/pre_users/validate" "validate": "/pre-users/validate"
}, },
"files": { "files": {
"all": "/raw/v1/files", "all": "/files",
"byId": "/raw/v1/files/:file_id", "byId": "/files/:fileId"
"upload": "/raw/v1/files/upload",
"download": "/raw/v1/files/download/:file_id",
"userFiles": "/raw/v1/files/myfiles"
}, },
"mail": { "mail": {
"all": "/v1/mails", "all": "/mails",
"byIndex": "/v1/mails/:index", "byIndex": "/mails/:index",
"send": "/v1/mails/send" "send": "/mails/send"
} }
} }
} }

View File

@@ -45,7 +45,7 @@ const AnuncioCard = ({ anuncio, isNew = false, onCreate, onUpdate, onDelete, onC
const [formData, setFormData] = useState({ const [formData, setFormData] = useState({
body: anuncio.body || '', body: anuncio.body || '',
priority: anuncio.priority ?? 1, priority: anuncio.priority ?? 1,
published_by: JSON.parse(localStorage.getItem('user'))?.user_id, publishedBy: JSON.parse(localStorage.getItem('identity'))?.user?.userId,
}); });
useEffect(() => { useEffect(() => {
@@ -53,7 +53,7 @@ const AnuncioCard = ({ anuncio, isNew = false, onCreate, onUpdate, onDelete, onC
setFormData({ setFormData({
body: anuncio.body || '', body: anuncio.body || '',
priority: anuncio.priority ?? 1, priority: anuncio.priority ?? 1,
published_by: JSON.parse(localStorage.getItem('user'))?.user_id, publishedBy: JSON.parse(localStorage.getItem('identity'))?.user?.userId,
}); });
} }
}, [anuncio, editMode]); }, [anuncio, editMode]);
@@ -63,7 +63,7 @@ const AnuncioCard = ({ anuncio, isNew = false, onCreate, onUpdate, onDelete, onC
setEditMode(true); setEditMode(true);
}; };
const handleDelete = () => typeof onDelete === 'function' && onDelete(anuncio.announce_id); const handleDelete = () => typeof onDelete === 'function' && onDelete(anuncio.announceId);
const handleCancel = () => { const handleCancel = () => {
if (onClearError) onClearError(); if (onClearError) onClearError();
@@ -77,12 +77,12 @@ const AnuncioCard = ({ anuncio, isNew = false, onCreate, onUpdate, onDelete, onC
formData.body = sanitizedBody; formData.body = sanitizedBody;
const updated = { ...anuncio, ...formData }; const updated = { ...anuncio, ...formData };
if (createMode && typeof onCreate === 'function') return onCreate(updated); if (createMode && typeof onCreate === 'function') return onCreate(updated);
if (typeof onUpdate === 'function') return onUpdate(updated, anuncio.announce_id); if (typeof onUpdate === 'function') return onUpdate(updated, anuncio.announceId);
}; };
const handleChange = (field, value) => setFormData((prev) => ({ ...prev, [field]: value })); const handleChange = (field, value) => setFormData((prev) => ({ ...prev, [field]: value }));
const { date, time } = formatDateTime(anuncio.created_at); const { date, time } = formatDateTime(anuncio.createdAt);
const priorityInfo = PRIORITY_CONFIG[formData.priority] || PRIORITY_CONFIG[1]; const priorityInfo = PRIORITY_CONFIG[formData.priority] || PRIORITY_CONFIG[1];
const isLongBody = formData.body.length > 300; const isLongBody = formData.body.length > 300;
const displayBody = isLongBody && !showFullBody const displayBody = isLongBody && !showFullBody
@@ -101,11 +101,15 @@ const AnuncioCard = ({ anuncio, isNew = false, onCreate, onUpdate, onDelete, onC
<Card className="anuncio-card rounded-4 border-0 shadow-sm mb-4"> <Card className="anuncio-card rounded-4 border-0 shadow-sm mb-4">
<Card.Header className="d-flex justify-content-between align-items-center rounded-top-4 px-3 py-2"> <Card.Header className="d-flex justify-content-between align-items-center rounded-top-4 px-3 py-2">
<div className="d-flex flex-column"> <div className="d-flex flex-column">
<span className="fw-bold">📢&emsp;Anuncio #{anuncio.announce_id}</span> <span className="fw-bold">📢&emsp;Anuncio {!createMode ? ("#"+anuncio.idx) : ("")}</span>
<small className="muted"> {!createMode ? (
Publicado el {date} a las {time} por{' '} <small className="muted">
<span className="fw-semibold">#{anuncio.published_by}</span> Publicado el {date} a las {time} por{' '}
</small> <span className="fw-semibold">{anuncio.publishedByName}</span>
</small>
) : (
<></>
)}
</div> </div>
{!createMode && !editMode && ( {!createMode && !editMode && (
<AnimatedDropdown <AnimatedDropdown

View File

@@ -32,7 +32,6 @@ function App() {
<NavBar /> <NavBar />
<Routes> <Routes>
<Route path="/" element={<Home />} /> <Route path="/" element={<Home />} />
{/*
<Route path="/lista-espera" element={<ListaEspera />} /> <Route path="/lista-espera" element={<ListaEspera />} />
<Route path="/login" element={<Login />} /> <Route path="/login" element={<Login />} />
<Route path="/gestion/socios" element={ <Route path="/gestion/socios" element={
@@ -80,7 +79,6 @@ function App() {
<Correo /> <Correo />
</ProtectedRoute> </ProtectedRoute>
} /> } />
*/}
<Route path="/*" element={<Maintenance />} /> <Route path="/*" element={<Maintenance />} />
</Routes> </Routes>
{routesWithFooter.includes(useLocation().pathname) ? <Footer /> : null} {routesWithFooter.includes(useLocation().pathname) ? <Footer /> : null}

View File

@@ -1,11 +1,11 @@
import { useAuth } from "../../hooks/useAuth.js"; import { useAuth } from "../../hooks/useAuth.js";
const IfRole = ({ roles, children }) => { const IfRole = ({ roles, children }) => {
const { user, authStatus } = useAuth(); const { identity, authStatus } = useAuth();
if (authStatus !== "authenticated") return null; if (authStatus !== "authenticated") return null;
const userRole = user?.role; const userRole = identity?.metadata?.role;
return roles.includes(userRole) ? children : null; return roles.includes(userRole) ? children : null;
}; };

View File

@@ -11,15 +11,15 @@ import CustomContainer from '../CustomContainer.jsx';
import ContentWrapper from '../ContentWrapper.jsx'; import ContentWrapper from '../ContentWrapper.jsx';
import '../../css/LoginForm.css'; import '../../css/LoginForm.css';
import { CONSTANTS } from '../../util/constants.js';
const LoginForm = () => { const LoginForm = () => {
const { login, error } = useContext(AuthContext); const { login, error } = useContext(AuthContext);
const navigate = useNavigate(); const navigate = useNavigate();
const [formState, setFormState] = useState({ const [formState, setFormState] = useState({
emailOrUserName: "", username: "",
password: "", password: ""
keepLoggedIn: false
}); });
const handleChange = (e) => { const handleChange = (e) => {
@@ -30,19 +30,12 @@ const LoginForm = () => {
const handleSubmit = async (e) => { const handleSubmit = async (e) => {
e.preventDefault(); e.preventDefault();
const isEmail = /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(formState.emailOrUserName);
const loginBody = { const loginBody = {
username: formState.username,
password: formState.password, password: formState.password,
keepLoggedIn: Boolean(formState.keepLoggedIn), serviceId: CONSTANTS.SERVICE_ID
}; };
if (isEmail) {
loginBody.email = formState.emailOrUserName;
} else {
loginBody.userName = formState.emailOrUserName;
}
try { try {
await login(loginBody); await login(loginBody);
navigate("/"); navigate("/");
@@ -63,15 +56,15 @@ const LoginForm = () => {
label={ label={
<> <>
<FontAwesomeIcon icon={faUser} className="me-2" /> <FontAwesomeIcon icon={faUser} className="me-2" />
Usuario o Email Usuario
</> </>
} }
> >
<Form.Control <Form.Control
type="text" type="text"
placeholder="" placeholder=""
name="emailOrUserName" name="username"
value={formState.emailOrUserName} value={formState.username}
onChange={handleChange} onChange={handleChange}
className="rounded-4" className="rounded-4"
/> />
@@ -83,7 +76,7 @@ const LoginForm = () => {
name="password" name="password"
/> />
<div className="d-flex flex-column flex-sm-row justify-content-between align-items-center gap-2"> {/*<div className="d-flex flex-column flex-sm-row justify-content-between align-items-center gap-2">
<Form.Check <Form.Check
type="checkbox" type="checkbox"
name="keepLoggedIn" name="keepLoggedIn"
@@ -92,10 +85,10 @@ const LoginForm = () => {
value={formState.keepLoggedIn} value={formState.keepLoggedIn}
onChange={(e) => { formState.keepLoggedIn = e.target.checked; setFormState({ ...formState }) }} onChange={(e) => { formState.keepLoggedIn = e.target.checked; setFormState({ ...formState }) }}
/> />
{/*<Link disabled to="#" className="muted"> <Link disabled to="#" className="muted">
Olvidé mi contraseña Olvidé mi contraseña
</Link>*/} </Link>
</div> </div>*/}
</div> </div>
{error && ( {error && (

View File

@@ -6,10 +6,10 @@ import { faSpinner } from "@fortawesome/free-solid-svg-icons";
const ProtectedRoute = ({ minimumRoles, children }) => { const ProtectedRoute = ({ minimumRoles, children }) => {
const { authStatus } = useAuth(); const { authStatus } = useAuth();
if (authStatus === "checking") return <FontAwesomeIcon icon={faSpinner} />; // o un loader si quieres if (authStatus === "checking") return <FontAwesomeIcon icon={faSpinner} />;
if (authStatus === "unauthenticated") return <Navigate to="/login" replace />; if (authStatus === "unauthenticated") return <Navigate to="/login" replace />;
if (authStatus === "authenticated" && minimumRoles) { if (authStatus === "authenticated" && minimumRoles) {
const userRole = JSON.parse(localStorage.getItem("user"))?.role; const userRole = JSON.parse(localStorage.getItem("identity"))?.metadata?.role;
if (!minimumRoles.includes(userRole)) return <Navigate to="/" replace />; if (!minimumRoles.includes(userRole)) return <Navigate to="/" replace />;
} }
return children; return children;

View File

@@ -67,17 +67,17 @@ const formatCurrency = (value) =>
export const BalancePDF = ({ balance }) => { export const BalancePDF = ({ balance }) => {
const { const {
initial_bank, initialBank,
initial_cash, initialCash,
total_bank_expenses, totalBankExpenses,
total_cash_expenses, totalCashExpenses,
total_bank_incomes, totalBankIncomes,
total_cash_incomes, totalCashIncomes,
created_at createdAt
} = balance; } = balance;
const final_bank = initial_bank + total_bank_incomes - total_bank_expenses; const finalBank = initialBank + totalBankIncomes - totalBankExpenses;
const final_cash = initial_cash + total_cash_incomes - total_cash_expenses; const finalCash = initialCash + totalCashIncomes - totalCashExpenses;
return ( return (
<Document> <Document>
@@ -91,22 +91,22 @@ export const BalancePDF = ({ balance }) => {
</View> </View>
<Text style={styles.sectionTitle}>Banco</Text> <Text style={styles.sectionTitle}>Banco</Text>
<View style={styles.row}><Text style={styles.label}>Saldo inicial</Text><Text style={styles.value}>{formatCurrency(initial_bank)}</Text></View> <View style={styles.row}><Text style={styles.label}>Saldo inicial</Text><Text style={styles.value}>{formatCurrency(initialBank)}</Text></View>
<View style={styles.row}><Text style={styles.label}>Ingresos</Text><Text style={styles.value}>{formatCurrency(total_bank_incomes)}</Text></View> <View style={styles.row}><Text style={styles.label}>Ingresos</Text><Text style={styles.value}>{formatCurrency(totalBankIncomes)}</Text></View>
<View style={styles.row}><Text style={styles.label}>Gastos</Text><Text style={styles.value}>{formatCurrency(total_bank_expenses)}</Text></View> <View style={styles.row}><Text style={styles.label}>Gastos</Text><Text style={styles.value}>{formatCurrency(totalBankExpenses)}</Text></View>
<Text style={styles.sectionTitle}>Caja</Text> <Text style={styles.sectionTitle}>Caja</Text>
<View style={styles.row}><Text style={styles.label}>Saldo inicial</Text><Text style={styles.value}>{formatCurrency(initial_cash)}</Text></View> <View style={styles.row}><Text style={styles.label}>Saldo inicial</Text><Text style={styles.value}>{formatCurrency(initialCash)}</Text></View>
<View style={styles.row}><Text style={styles.label}>Ingresos</Text><Text style={styles.value}>{formatCurrency(total_cash_incomes)}</Text></View> <View style={styles.row}><Text style={styles.label}>Ingresos</Text><Text style={styles.value}>{formatCurrency(totalCashIncomes)}</Text></View>
<View style={styles.row}><Text style={styles.label}>Gastos</Text><Text style={styles.value}>{formatCurrency(total_cash_expenses)}</Text></View> <View style={styles.row}><Text style={styles.label}>Gastos</Text><Text style={styles.value}>{formatCurrency(totalCashExpenses)}</Text></View>
<Text style={styles.sectionTitle}>Total</Text> <Text style={styles.sectionTitle}>Total</Text>
<View style={styles.row}><Text style={styles.label}>Banco</Text><Text style={styles.value}>{formatCurrency(final_bank)}</Text></View> <View style={styles.row}><Text style={styles.label}>Banco</Text><Text style={styles.value}>{formatCurrency(finalBank)}</Text></View>
<View style={styles.row}><Text style={styles.label}>Caja</Text><Text style={styles.value}>{formatCurrency(final_cash)}</Text></View> <View style={styles.row}><Text style={styles.label}>Caja</Text><Text style={styles.value}>{formatCurrency(finalCash)}</Text></View>
<View style={styles.row}><Text style={styles.label}>Total</Text><Text style={styles.value}>{formatCurrency(final_bank + final_cash)}</Text></View> <View style={styles.row}><Text style={styles.label}>Total</Text><Text style={styles.value}>{formatCurrency(finalBank + finalCash)}</Text></View>
<Text style={[styles.label, { marginTop: 20 }]}> <Text style={[styles.label, { marginTop: 20 }]}>
Última actualización: {format(new Date(created_at), 'dd/MM/yyyy HH:mm')} Última actualización: {format(new Date(createdAt), 'dd/MM/yyyy HH:mm')}
</Text> </Text>
</Page> </Page>
</Document> </Document>

View File

@@ -24,17 +24,17 @@ const BalanceReport = ({ balance }) => {
const closePDFModal = () => setShowPDF(false); const closePDFModal = () => setShowPDF(false);
const { const {
initial_bank, initialBank,
initial_cash, initialCash,
total_bank_expenses, totalBankExpenses,
total_cash_expenses, totalCashExpenses,
total_bank_incomes, totalBankIncomes,
total_cash_incomes, totalCashIncomes,
created_at createdAt
} = balance; } = balance;
const final_bank = initial_bank + total_bank_incomes - total_bank_expenses; const finalBank = initialBank + totalBankIncomes - totalBankExpenses;
const final_cash = initial_cash + total_cash_incomes - total_cash_expenses; const finalCash = initialCash + totalCashIncomes - totalCashExpenses;
return ( return (
<> <>
@@ -56,20 +56,20 @@ const BalanceReport = ({ balance }) => {
<Col md={6}> <Col md={6}>
<div className="balance-box"> <div className="balance-box">
<h4><FontAwesomeIcon icon={faPiggyBank} className="me-2" />Banco</h4> <h4><FontAwesomeIcon icon={faPiggyBank} className="me-2" />Banco</h4>
<p>Saldo inicial: <span className="balance-value">{formatCurrency(initial_bank)}</span></p> <p>Saldo inicial: <span className="balance-value">{formatCurrency(initialBank)}</span></p>
<p><FontAwesomeIcon icon={faArrowUp} className="me-1 text-success" />Ingresos: <span className="balance-value">{formatCurrency(total_bank_incomes)}</span></p> <p><FontAwesomeIcon icon={faArrowUp} className="me-1 text-success" />Ingresos: <span className="balance-value">{formatCurrency(totalBankIncomes)}</span></p>
<p><FontAwesomeIcon icon={faArrowDown} className="me-1 text-danger" />Gastos: <span className="balance-value">{formatCurrency(total_bank_expenses)}</span></p> <p><FontAwesomeIcon icon={faArrowDown} className="me-1 text-danger" />Gastos: <span className="balance-value">{formatCurrency(totalBankExpenses)}</span></p>
<p className="fw-bold mt-3">💰 Saldo final: {formatCurrency(final_bank)}</p> <p className="fw-bold mt-3">💰 Saldo final: {formatCurrency(finalBank)}</p>
</div> </div>
</Col> </Col>
<Col md={6}> <Col md={6}>
<div className="balance-box"> <div className="balance-box">
<h4><FontAwesomeIcon icon={faCoins} className="me-2" />Caja</h4> <h4><FontAwesomeIcon icon={faCoins} className="me-2" />Caja</h4>
<p>Saldo inicial: <span className="balance-value">{formatCurrency(initial_cash)}</span></p> <p>Saldo inicial: <span className="balance-value">{formatCurrency(initialCash)}</span></p>
<p><FontAwesomeIcon icon={faArrowUp} className="me-1 text-success" />Ingresos: <span className="balance-value">{formatCurrency(total_cash_incomes)}</span></p> <p><FontAwesomeIcon icon={faArrowUp} className="me-1 text-success" />Ingresos: <span className="balance-value">{formatCurrency(totalCashIncomes)}</span></p>
<p><FontAwesomeIcon icon={faArrowDown} className="me-1 text-danger" />Gastos: <span className="balance-value">{formatCurrency(total_cash_expenses)}</span></p> <p><FontAwesomeIcon icon={faArrowDown} className="me-1 text-danger" />Gastos: <span className="balance-value">{formatCurrency(totalCashExpenses)}</span></p>
<p className="fw-bold mt-3">💵 Saldo final: {formatCurrency(final_cash)}</p> <p className="fw-bold mt-3">💵 Saldo final: {formatCurrency(finalCash)}</p>
</div> </div>
</Col> </Col>
</Row> </Row>
@@ -77,7 +77,7 @@ const BalanceReport = ({ balance }) => {
<Row className="mt-4"> <Row className="mt-4">
<Col className="text-end balance-timestamp"> <Col className="text-end balance-timestamp">
<FontAwesomeIcon icon={faClock} className="me-2" /> <FontAwesomeIcon icon={faClock} className="me-2" />
Última actualización: {format(new Date(created_at), 'dd/MM/yyyy HH:mm')} Última actualización: {format(new Date(createdAt), 'dd/MM/yyyy HH:mm')}
</Col> </Col>
</Row> </Row>
</Card> </Card>

View File

@@ -25,19 +25,19 @@ const File = ({ file, onDelete }) => {
return ( return (
<Card <Card
className="file-card col-sm-3 col-lg-2 col-xxl-1 m-0 p-0 position-relative text-decoration-none bg-transparent" className="file-card col-sm-3 col-lg-2 col-xxl-1 m-0 p-0 position-relative text-decoration-none bg-transparent"
onClick={() => window.open(`https://miarma.net/files/huertos/${file.file_name}`, "_blank")} onClick={() => window.open(`https://miarma.net/files/huertos/${file.fileName}`, "_blank")}
> >
<Card.Body className="text-center"> <Card.Body className="text-center">
<img <img
src={getIcon(file.mime_type)} src={getIcon(file.mimeType)}
alt={file.file_name} alt={file.fileName}
className="img-fluid mb-2" className="img-fluid mb-2"
/> />
<OverlayTrigger <OverlayTrigger
placement="bottom" placement="bottom"
overlay={<Tooltip>{file.file_name}</Tooltip>} overlay={<Tooltip>{file.fileName}</Tooltip>}
> >
<p className="m-0 p-0 text-truncate">{file.file_name}</p> <p className="m-0 p-0 text-truncate">{file.fileName}</p>
</OverlayTrigger> </OverlayTrigger>
</Card.Body> </Card.Body>

View File

@@ -49,7 +49,7 @@ const GastoCard = ({ gasto, isNew = false, onCreate, onUpdate, onDelete, onCance
supplier: gasto.supplier || '', supplier: gasto.supplier || '',
invoice: gasto.invoice || '', invoice: gasto.invoice || '',
type: gasto.type ?? 0, type: gasto.type ?? 0,
created_at: gasto.created_at?.slice(0, 16) || (isNew ? getNowAsLocalDatetime() : ''), createdAt: gasto.createdAt?.slice(0, 16) || (isNew ? getNowAsLocalDatetime() : ''),
}); });
useEffect(() => { useEffect(() => {
@@ -60,7 +60,7 @@ const GastoCard = ({ gasto, isNew = false, onCreate, onUpdate, onDelete, onCance
supplier: gasto.supplier || '', supplier: gasto.supplier || '',
invoice: gasto.invoice || '', invoice: gasto.invoice || '',
type: gasto.type ?? 0, type: gasto.type ?? 0,
created_at: gasto.created_at?.slice(0, 16) || (isNew ? getNowAsLocalDatetime() : ''), createdAt: gasto.createdAt?.slice(0, 16) || (isNew ? getNowAsLocalDatetime() : ''),
}); });
} }
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
@@ -68,7 +68,7 @@ const GastoCard = ({ gasto, isNew = false, onCreate, onUpdate, onDelete, onCance
const handleChange = (field, value) => setFormData(prev => ({ ...prev, [field]: value })); const handleChange = (field, value) => setFormData(prev => ({ ...prev, [field]: value }));
const handleDelete = () => typeof onDelete === 'function' && onDelete(gasto.expense_id); const handleDelete = () => typeof onDelete === 'function' && onDelete(gasto.expenseId);
const handleCancel = () => { const handleCancel = () => {
if (onClearError) onClearError(); if (onClearError) onClearError();
@@ -80,7 +80,7 @@ const GastoCard = ({ gasto, isNew = false, onCreate, onUpdate, onDelete, onCance
if (onClearError) onClearError(); 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.expense_id); if (typeof onUpdate === 'function') return onUpdate(newExpense, gasto.expenseId);
}; };
return ( return (
@@ -102,13 +102,13 @@ const GastoCard = ({ gasto, isNew = false, onCreate, onUpdate, onDelete, onCance
<small> <small>
{editMode ? ( {editMode ? (
<SpanishDateTimePicker <SpanishDateTimePicker
selected={new Date(formData.created_at)} selected={new Date(formData.createdAt)}
onChange={(date) => onChange={(date) =>
handleChange('created_at', date.toISOString().slice(0, 16)) handleChange('createdAt', date.toISOString().slice(0, 16))
} }
/> />
) : ( ) : (
DateParser.isoToStringWithTime(formData.created_at) DateParser.isoToStringWithTime(formData.createdAt)
)} )}
</small> </small>
</div> </div>

View File

@@ -104,7 +104,7 @@ export const GastosPDF = ({ gastos }) => (
{ borderBottomRightRadius: idx === gastos.length - 1 ? 10 : 0 }, { borderBottomRightRadius: idx === gastos.length - 1 ? 10 : 0 },
]} ]}
> >
<Text style={[styles.cell, { flex: 2 }]}>{parseDate(gasto.created_at)}</Text> <Text style={[styles.cell, { flex: 2 }]}>{parseDate(gasto.createdAt)}</Text>
<Text style={[styles.cell, { flex: 4 }]}>{gasto.concept}</Text> <Text style={[styles.cell, { flex: 4 }]}>{gasto.concept}</Text>
<Text style={[styles.cell, { flex: 2 }]}>{gasto.amount.toFixed(2)} </Text> <Text style={[styles.cell, { flex: 2 }]}>{gasto.amount.toFixed(2)} </Text>
<Text style={[styles.cell, { flex: 3 }]}>{gasto.supplier}</Text> <Text style={[styles.cell, { flex: 3 }]}>{gasto.supplier}</Text>

View File

@@ -63,8 +63,8 @@ const IngresoCard = ({
amount: income.amount || 0, amount: income.amount || 0,
type: income.type ?? CONSTANTS.PAYMENT_TYPE_CASH, type: income.type ?? CONSTANTS.PAYMENT_TYPE_CASH,
frequency: income.frequency ?? CONSTANTS.PAYMENT_FREQUENCY_YEARLY, frequency: income.frequency ?? CONSTANTS.PAYMENT_FREQUENCY_YEARLY,
member_number: income.member_number, memberNumber: income.memberNumber,
created_at: income.created_at?.slice(0, 16) || (isNew ? getNowAsLocalDatetime() : ''), createdAt: income.createdAt?.slice(0, 16) || (isNew ? getNowAsLocalDatetime() : ''),
}); });
useEffect(() => { useEffect(() => {
@@ -74,9 +74,9 @@ const IngresoCard = ({
amount: income.amount || 0, amount: income.amount || 0,
type: income.type ?? CONSTANTS.PAYMENT_TYPE_CASH, type: income.type ?? CONSTANTS.PAYMENT_TYPE_CASH,
frequency: income.frequency ?? CONSTANTS.PAYMENT_FREQUENCY_YEARLY, frequency: income.frequency ?? CONSTANTS.PAYMENT_FREQUENCY_YEARLY,
display_name: income.display_name, displayName: income.displayName,
member_number: income.member_number, memberNumber: income.memberNumber,
created_at: income.created_at?.slice(0, 16) || (isNew ? getNowAsLocalDatetime() : ''), createdAt: income.createdAt?.slice(0, 16) || (isNew ? getNowAsLocalDatetime() : ''),
}); });
} }
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
@@ -95,14 +95,14 @@ const IngresoCard = ({
if (onClearError) onClearError(); 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.income_id); if (typeof onUpdate === 'function') return onUpdate(newIncome, income.incomeId);
}; };
const handleDelete = () => typeof onDelete === 'function' && onDelete(income.income_id); const handleDelete = () => typeof onDelete === 'function' && onDelete(income.incomeId);
const uniqueMembers = Array.from( const uniqueMembers = Array.from(
new Map(members.map(item => [item.member_number, item])).values() new Map(members.map(item => [item.memberNumber, item])).values()
).sort((a, b) => a.member_number - b.member_number); ).sort((a, b) => a.memberNumber - b.memberNumber);
return ( return (
<MotionCard className={`ingreso-card shadow-sm rounded-4 border-0 h-100 ${className}`}> <MotionCard className={`ingreso-card shadow-sm rounded-4 border-0 h-100 ${className}`}>
@@ -125,13 +125,13 @@ const IngresoCard = ({
<small> <small>
{editMode ? ( {editMode ? (
<SpanishDateTimePicker <SpanishDateTimePicker
selected={new Date(formData.created_at)} selected={new Date(formData.createdAt)}
onChange={(date) => onChange={(date) =>
handleChange('created_at', date.toISOString().slice(0, 16)) handleChange('createdAt', date.toISOString().slice(0, 16))
} }
/> />
) : ( ) : (
DateParser.isoToStringWithTime(formData.created_at) DateParser.isoToStringWithTime(formData.createdAt)
)} )}
</small> </small>
</div> </div>
@@ -167,13 +167,13 @@ const IngresoCard = ({
<Form.Select <Form.Select
className="themed-input" className="themed-input"
size="sm" size="sm"
value={formData.member_number} value={formData.memberNumber}
onChange={(e) => handleChange('member_number', parseInt(e.target.value))} onChange={(e) => handleChange('memberNumber', parseInt(e.target.value))}
style={{ maxWidth: '300px', display: 'inline-block' }} style={{ maxWidth: '300px', display: 'inline-block' }}
> >
{uniqueMembers.map((m) => ( {uniqueMembers.map((m) => (
<option key={m.member_number} value={m.member_number}> <option key={m.memberNumber} value={m.memberNumber}>
{`${m.display_name} (${m.member_number})`} {`${m.displayName} (${m.memberNumber})`}
</option> </option>
))} ))}
</Form.Select> </Form.Select>
@@ -187,24 +187,24 @@ const IngresoCard = ({
disabled disabled
size="sm" size="sm"
type="text" type="text"
value={`${formData.display_name || 'Socio'} (${formData.member_number})`} value={`${formData.displayName || 'Socio'} (${formData.memberNumber})`}
style={{ maxWidth: '300px', display: 'inline-block' }} style={{ maxWidth: '300px', display: 'inline-block' }}
/> />
</OverlayTrigger> </OverlayTrigger>
) : ( ) : (
formData.display_name ? ( formData.displayName ? (
<> <>
<OverlayTrigger <OverlayTrigger
placement="top" placement="top"
overlay={<Tooltip>{formData.display_name}</Tooltip>} overlay={<Tooltip>{formData.displayName}</Tooltip>}
> >
<span className="text-truncate d-inline-block" style={{ maxWidth: '200px', verticalAlign: 'middle' }}> <span className="text-truncate d-inline-block" style={{ maxWidth: '200px', verticalAlign: 'middle' }}>
{formData.display_name} {formData.displayName}
</span> </span>
</OverlayTrigger> </OverlayTrigger>
&nbsp;({formData.member_number}) &nbsp;({formData.memberNumber})
</> </>
) : formData.member_number ) : formData.memberNumber
)} )}
</Card.Text> </Card.Text>

View File

@@ -105,12 +105,12 @@ export const IngresosPDF = ({ ingresos }) => (
{ borderBottomRightRadius: idx === ingresos.length - 1 ? 10 : 0 }, { borderBottomRightRadius: idx === ingresos.length - 1 ? 10 : 0 },
]} ]}
> >
<Text style={[styles.cell, { flex: 1 }]}>{ing.member_number}</Text> <Text style={[styles.cell, { flex: 1 }]}>{ing.memberNumber}</Text>
<Text style={[styles.cell, { flex: 3 }]}>{ing.concept}</Text> <Text style={[styles.cell, { flex: 3 }]}>{ing.concept}</Text>
<Text style={[styles.cell, { flex: 1 }]}>{ing.amount.toFixed(2)} </Text> <Text style={[styles.cell, { flex: 1 }]}>{ing.amount.toFixed(2)} </Text>
<Text style={[styles.cell, { flex: 1 }]}>{getTypeLabel(ing.type)}</Text> <Text style={[styles.cell, { flex: 1 }]}>{getTypeLabel(ing.type)}</Text>
<Text style={[styles.cell, { flex: 1 }]}>{getFreqLabel(ing.frequency)}</Text> <Text style={[styles.cell, { flex: 1 }]}>{getFreqLabel(ing.frequency)}</Text>
<Text style={[styles.cell, { flex: 2 }]}>{parseDate(ing.created_at)}</Text> <Text style={[styles.cell, { flex: 2 }]}>{parseDate(ing.createdAt)}</Text>
</View> </View>
))} ))}
</Page> </Page>

View File

@@ -27,7 +27,7 @@ import AnimatedDropdown from '../AnimatedDropdown.jsx';
import { CONSTANTS } from '../../util/constants.js'; import { CONSTANTS } from '../../util/constants.js';
const NavBar = () => { const NavBar = () => {
const { user, logout } = useAuth(); const { identity, logout } = useAuth();
const [showingUserDropdown, setShowingUserDropdown] = useState(false); const [showingUserDropdown, setShowingUserDropdown] = useState(false);
const [expanded, setExpanded] = useState(false); const [expanded, setExpanded] = useState(false);
const [isLg, setIsLg] = useState(window.innerWidth >= 992); const [isLg, setIsLg] = useState(window.innerWidth >= 992);
@@ -138,7 +138,7 @@ const NavBar = () => {
onToggle={(isOpen) => setShowingUserDropdown(isOpen)} onToggle={(isOpen) => setShowingUserDropdown(isOpen)}
trigger={ trigger={
<Link className="nav-link dropdown-toggle fw-bold"> <Link className="nav-link dropdown-toggle fw-bold">
@{user?.user_name} @{identity?.account?.username}
</Link> </Link>
} }
> >

View File

@@ -45,16 +45,16 @@ const renderDateField = (label, icon, dateValue, editMode, fieldKey, handleChang
}; };
const getFechas = (formData, editMode, handleChange) => { const getFechas = (formData, editMode, handleChange) => {
const { created_at, assigned_at, deactivated_at } = formData; const { createdAt, assignedAt, deactivatedAt } = formData;
// Si no hay fechas y no está en modo edición, no muestres nada // Si no hay fechas y no está en modo edición, no muestres nada
if (!editMode && !created_at && !assigned_at && !deactivated_at) return null; if (!editMode && !createdAt && !assignedAt && !deactivatedAt) return null;
return ( return (
<ListGroup className="mt-2 border-1 rounded-3 shadow-sm"> <ListGroup className="mt-2 border-1 rounded-3 shadow-sm">
{renderDateField("ALTA", faCalendar, created_at, editMode, "created_at", handleChange)} {renderDateField("ALTA", faCalendar, createdAt, editMode, "createdAt", handleChange)}
{renderDateField("ENTREGA", faCalendar, assigned_at, editMode, "assigned_at", handleChange)} {renderDateField("ENTREGA", faCalendar, assignedAt, editMode, "assignedAt", handleChange)}
{renderDateField("BAJA", faCalendar, deactivated_at, editMode, "deactivated_at", handleChange)} {renderDateField("BAJA", faCalendar, deactivatedAt, editMode, "deactivatedAt", handleChange)}
</ListGroup> </ListGroup>
); );
}; };
@@ -91,7 +91,7 @@ const getPFP = (tipo) => {
const MotionCard = _motion.create(Card); const MotionCard = _motion.create(Card);
const SocioCard = ({ socio, isNew = false, onCreate, onUpdate, onDelete, onCancel, onViewIncomes, error, onClearError, positionIfWaitlist }) => { const SocioCard = ({ identity, isNew = false, onCreate, onUpdate, onDelete, onCancel, onViewIncomes, error, onClearError, positionIfWaitlist }) => {
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);
@@ -99,60 +99,59 @@ const SocioCard = ({ socio, isNew = false, onCreate, onUpdate, onDelete, onCance
const { getData } = useDataContext(); const { getData } = useDataContext();
const [formData, setFormData] = useState({ const [formData, setFormData] = useState({
display_name: socio.display_name, displayName: identity?.user.displayName,
user_name: socio.user_name, userName: identity?.account.username,
email: socio.email || '', email: identity?.account.email || '',
dni: socio.dni, dni: identity?.metadata.dni,
phone: socio.phone, phone: identity?.metadata.phone,
member_number: socio.member_number || latestNumber, memberNumber: identity?.metadata.memberNumber || latestNumber,
plot_number: socio.plot_number, plotNumber: identity?.metadata.plotNumber,
notes: socio.notes || '', notes: identity?.metadata.notes || '',
status: socio.status, status: identity?.account.status,
type: socio.type, type: identity?.metadata.type,
created_at: socio.created_at?.slice(0, 16) || (isNew ? getNowAsLocalDatetime() : ''), createdAt: identity?.metadata.createdAt?.slice(0, 16) || (isNew ? getNowAsLocalDatetime() : ''),
assigned_at: socio.assigned_at?.slice(0, 16) || undefined, assignedAt: identity?.metadata.assignedAt?.slice(0, 16) || undefined,
deactivated_at: socio.deactivated_at?.slice(0, 16) || undefined, deactivatedAt: identity?.metadata.deactivatedAt?.slice(0, 16) || undefined,
global_role: 0, globalRole: 0,
password: createMode && !editMode ? generateSecurePassword() : null, password: createMode && !editMode ? generateSecurePassword() : null,
}); });
useEffect(() => { useEffect(() => {
if (!editMode) { if (!editMode) {
setFormData({ setFormData({
display_name: socio.display_name, displayName: identity.user.displayName,
user_name: socio.user_name, userName: identity.account.username,
email: socio.email || '', email: identity.account.email || '',
dni: socio.dni, dni: identity.metadata.dni,
phone: socio.phone, phone: identity.metadata.phone,
member_number: socio.member_number, memberNumber: identity.metadata.memberNumber,
plot_number: socio.plot_number, plotNumber: identity.metadata.plotNumber,
notes: socio.notes || '', notes: identity.metadata.notes || '',
status: socio.status, status: identity.account.status,
type: socio.type, type: identity.metadat.type,
created_at: socio.created_at?.slice(0, 16) || (isNew ? getNowAsLocalDatetime() : ''), createdAt: identity.metadata.createdAt?.slice(0, 16) || (isNew ? getNowAsLocalDatetime() : ''),
assigned_at: socio.assigned_at?.slice(0, 16) || undefined, assignedAt: identity.metadata.assignedAt?.slice(0, 16) || undefined,
deactivated_at: socio.deactivated_at?.slice(0, 16) || undefined, deactivatedAt: identity.metadata.deactivatedAt?.slice(0, 16) || undefined,
global_role: 0, globalRole: 0,
password: createMode ? generateSecurePassword() : '' password: createMode ? generateSecurePassword() : ''
}); });
} }
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
}, [socio, editMode]); }, [identity, editMode]);
useEffect(() => { useEffect(() => {
const fetchLastNumber = async () => { const fetchLastNumber = async () => {
try { try {
if (!(createMode || editMode)) return; if (!(createMode || editMode)) return;
const { data, error } = await getData("https://api.huertosbellavista.es/v1/members/latest-number"); const latestNumber = await getData("http://localhost:8081/v2/huertos/users/latest-number");
if (error) throw new Error(error);
const nuevoNumero = data.lastMemberNumber + 1; const nuevoNumero = latestNumber + 1;
setLatestNumber(nuevoNumero); setLatestNumber(nuevoNumero);
setFormData(prev => ({ setFormData(prev => ({
...prev, ...prev,
member_number: prev.member_number || nuevoNumero memberNumber: prev.memberNumber || nuevoNumero
})); }));
} catch (err) { } catch (err) {
console.error("Error al obtener el número de socio:", err); console.error("Error al obtener el número de socio:", err);
@@ -167,7 +166,7 @@ const SocioCard = ({ socio, isNew = false, onCreate, onUpdate, onDelete, onCance
setEditMode(true); setEditMode(true);
}; };
const handleDelete = () => typeof onDelete === "function" && onDelete(socio.user_id); const handleDelete = () => typeof onDelete === "function" && onDelete(identity.user.userId);
const handleCancel = () => { const handleCancel = () => {
if (onClearError) onClearError(); if (onClearError) onClearError();
@@ -177,16 +176,16 @@ const SocioCard = ({ socio, isNew = false, onCreate, onUpdate, onDelete, onCance
const handleSave = () => { const handleSave = () => {
if (onClearError) onClearError(); if (onClearError) onClearError();
const newSocio = { ...socio, ...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, socio.user_id); if (typeof onUpdate === 'function') return onUpdate(newSocio, identity.user.userId);
}; };
const handleChange = (field, value) => { const handleChange = (field, value) => {
if (["member_number"].includes(field)) { if (["memberNumber"].includes(field)) {
value = value === "" ? latestNumber : parseInt(value); value = value === "" ? latestNumber : parseInt(value);
} }
if (field === "display_name") { if (field === "displayName") {
value = value.toUpperCase(); value = value.toUpperCase();
} }
if (field === "dni") { if (field === "dni") {
@@ -196,7 +195,7 @@ const SocioCard = ({ socio, isNew = false, onCreate, onUpdate, onDelete, onCance
}; };
const handleViewIncomes = () => { const handleViewIncomes = () => {
onViewIncomes(socio.user_id); onViewIncomes(identity.user.userId);
} }
return ( return (
@@ -206,7 +205,7 @@ const SocioCard = ({ socio, isNew = false, onCreate, onUpdate, onDelete, onCance
{editMode ? ( {editMode ? (
<TipoSocioDropdown value={formData.type} onChange={(val) => handleChange('type', val)} /> <TipoSocioDropdown value={formData.type} onChange={(val) => handleChange('type', val)} />
) : ( ) : (
positionIfWaitlist && socio.type === 0 ? ( positionIfWaitlist && identity.metadata.type === 0 ? (
<OverlayTrigger <OverlayTrigger
placement="top" placement="top"
overlay={ overlay={
@@ -225,8 +224,8 @@ const SocioCard = ({ socio, isNew = false, onCreate, onUpdate, onDelete, onCance
<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.display_name} onChange={(e) => handleChange('display_name', e.target.value)} style={{ maxWidth: '220px' }} /> <Form.Control className="themed-input" size="sm" value={formData.displayName} onChange={(e) => handleChange('displayName', e.target.value)} style={{ maxWidth: '220px' }} />
) : formData.display_name} ) : formData.displayName}
</Card.Title> </Card.Title>
{editMode ? ( {editMode ? (
<Form.Select className="themed-input" size="sm" value={formData.status} onChange={(e) => handleChange('status', parseInt(e.target.value))} style={{ maxWidth: '8rem' }}> <Form.Select className="themed-input" size="sm" value={formData.status} onChange={(e) => handleChange('status', parseInt(e.target.value))} style={{ maxWidth: '8rem' }}>
@@ -269,9 +268,9 @@ const SocioCard = ({ socio, isNew = false, onCreate, onUpdate, onDelete, onCance
{[{ {[{
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'
}, { }, {
label: 'SOCIO Nº', clazz: '', icon: faUser, value: formData.member_number || latestNumber, field: 'member_number', type: 'number', maxWidth: '100px' label: 'SOCIO Nº', clazz: '', icon: faUser, value: formData.memberNumber || latestNumber, field: 'memberNumber', type: 'number', maxWidth: '100px'
}, { }, {
label: 'HUERTO Nº', clazz: '', icon: faSunPlantWilt, value: formData.plot_number, field: 'plot_number', type: 'number', maxWidth: '100px' label: 'HUERTO Nº', clazz: '', icon: faSunPlantWilt, value: formData.plotNumber, field: 'plotNumber', type: 'number', maxWidth: '100px'
}, { }, {
label: 'TLF.', clazz: '', icon: faPhone, value: formData.phone, field: 'phone', type: 'number', maxWidth: '200px' label: 'TLF.', clazz: '', icon: faPhone, value: formData.phone, field: 'phone', type: 'number', maxWidth: '200px'
}, { }, {

View File

@@ -107,13 +107,13 @@ export const SociosPDF = ({ socios }) => (
{ borderBottomRightRadius: idx === socios.length - 1 ? 10 : 0 }, { borderBottomRightRadius: idx === socios.length - 1 ? 10 : 0 },
]} ]}
> >
<Text style={[styles.cell, { flex: 0.2 }]}>{socio?.member_number}</Text> <Text style={[styles.cell, { flex: 0.2 }]}>{socio?.memberNumber}</Text>
<Text style={[styles.cell, { flex: 0.2 }]}>{socio?.plot_number}</Text> <Text style={[styles.cell, { flex: 0.2 }]}>{socio?.plotNumber}</Text>
<Text style={[styles.cell, { flex: 3 }]}>{socio?.display_name}</Text> <Text style={[styles.cell, { flex: 3 }]}>{socio?.displayName}</Text>
<Text style={[styles.cell, { flex: 1 }]}>{socio?.dni}</Text> <Text style={[styles.cell, { flex: 1 }]}>{socio?.dni}</Text>
<Text style={[styles.cell, { flex: 1 }]}>{socio?.phone}</Text> <Text style={[styles.cell, { flex: 1 }]}>{socio?.phone}</Text>
<Text style={[styles.cell, { flex: 3 }]}>{socio?.email || ''}</Text> <Text style={[styles.cell, { flex: 3 }]}>{socio?.email || ''}</Text>
<Text style={[styles.cell, { flex: 1 }]}>{parseDate(socio?.created_at?.split('T')[0] || '')}</Text> <Text style={[styles.cell, { flex: 1 }]}>{parseDate(socio?.createdAt?.split('T')[0] || '')}</Text>
<Text style={[styles.cell, { flex: 1 }]}> <Text style={[styles.cell, { flex: 1 }]}>
{(() => { {(() => {
switch (socio?.type) { switch (socio?.type) {

View File

@@ -3,22 +3,25 @@ import { Form, Row, Col, Button } from 'react-bootstrap';
import { useDataContext } from '../../hooks/useDataContext'; import { useDataContext } from '../../hooks/useDataContext';
import { Alert } from 'react-bootstrap'; import { Alert } from 'react-bootstrap';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { generateSecurePassword } from '../../util/passwordGenerator';
const PreUserForm = ({ onSubmit, userType, plotNumber, errors = {} }) => { const PreUserForm = ({ onSubmit, userType, plotNumber, errors = {} }) => {
const { getData } = useDataContext(); const { getData } = useDataContext();
const fetchedOnce = useRef(false); const fetchedOnce = useRef(false);
const [form, setForm] = useState({ const [form, setForm] = useState({
user_name: '', userName: '',
display_name: '', password: generateSecurePassword(8),
displayName: '',
dni: '', dni: '',
phone: '', phone: '',
email: '', email: '',
address: '', address: '',
zip_code: '', zipCode: '',
city: '', city: '',
member_number: '', memberNumber: '',
plot_number: plotNumber, plotNumber: plotNumber,
type: userType, type: userType,
status: 1, status: 1,
role: 0 role: 0
@@ -30,12 +33,10 @@ const PreUserForm = ({ onSubmit, userType, plotNumber, errors = {} }) => {
fetchedOnce.current = true; fetchedOnce.current = true;
try { try {
const { data, error } = await getData("https://api.huertosbellavista.es/v1/members/latest-number"); const latestNumber = await getData("http://localhost:8081/v2/huertos/users/latest-number");
if (error) throw new Error(error);
setForm((prev) => ({ setForm((prev) => ({
...prev, ...prev,
member_number: data.lastMemberNumber + 1 memberNumber: latestNumber + 1
})); }));
} catch (err) { } catch (err) {
console.error("Error al obtener el número de socio:", err); console.error("Error al obtener el número de socio:", err);
@@ -47,21 +48,21 @@ const PreUserForm = ({ onSubmit, userType, plotNumber, errors = {} }) => {
}, []); }, []);
useEffect(() => { useEffect(() => {
const trimmedName = form.display_name?.trim() ?? ""; const trimmedName = form.displayName?.trim() ?? "";
const nuevoUsername = trimmedName const nuevoUsername = trimmedName
? trimmedName.split(' ')[0].toLowerCase() : ""; ? trimmedName.split(' ')[0].toLowerCase() : "";
if (form.user_name !== nuevoUsername) { if (form.userName !== nuevoUsername) {
setForm(prev => ({ ...prev, user_name: nuevoUsername })); setForm(prev => ({ ...prev, userName: nuevoUsername }));
} }
}, [form.member_number, form.display_name, form.user_name]); }, [form.memberNumber, form.displayName, form.userName]);
const handleChange = (e) => { const handleChange = (e) => {
const { name, value, type } = e.target; const { name, value, type } = e.target;
let updatedValue = value; let updatedValue = value;
if (name === 'display_name' || name === 'dni') { if (name === 'displayName' || name === 'dni') {
updatedValue = value.toUpperCase(); updatedValue = value.toUpperCase();
} }
@@ -84,13 +85,13 @@ const PreUserForm = ({ onSubmit, userType, plotNumber, errors = {} }) => {
<Row className="gy-3"> <Row className="gy-3">
{[ {[
{ label: 'Nombre completo', name: 'display_name', type: 'text', required: true }, { label: 'Nombre completo', name: 'displayName', type: 'text', required: true },
{ label: 'Nombre de usuario', name: 'user_name', type: 'text', required: true }, { label: 'Nombre de usuario', name: 'userName', type: 'text', required: true },
{ label: 'DNI', name: 'dni', type: 'text', required: true, maxLength: 9 }, { label: 'DNI', name: 'dni', type: 'text', required: true, maxLength: 9 },
{ label: 'Teléfono', name: 'phone', type: 'tel', required: true }, { label: 'Teléfono', name: 'phone', type: 'tel', required: true },
{ label: 'Correo electrónico', name: 'email', type: 'email', required: true }, { label: 'Correo electrónico', name: 'email', type: 'email', required: true },
{ label: 'Domicilio', name: 'address', type: 'text' }, { label: 'Domicilio', name: 'address', type: 'text' },
{ label: 'Código Postal', name: 'zip_code', type: 'text' }, { label: 'Código Postal', name: 'zipCode', type: 'text' },
{ label: 'Ciudad', name: 'city', type: 'text' } { label: 'Ciudad', name: 'city', type: 'text' }
].map(({ label, name, type, required, maxLength }) => ( ].map(({ label, name, type, required, maxLength }) => (
<Col md={4} key={name}> <Col md={4} key={name}>
@@ -120,8 +121,8 @@ const PreUserForm = ({ onSubmit, userType, plotNumber, errors = {} }) => {
className="shadow-sm" className="shadow-sm"
disabled disabled
type="number" type="number"
name="member_number" name="memberNumber"
value={form.member_number} value={form.memberNumber}
onChange={handleChange} onChange={handleChange}
/> />
</Form.Group> </Form.Group>

View File

@@ -35,15 +35,15 @@ const getPFP = (tipo) => {
}; };
const renderDescripcionSolicitud = (data, onProfile) => { const renderDescripcionSolicitud = (data, onProfile) => {
const { request_type, request_status, requested_by_name, pre_display_name } = data; const { type, status, requestedByName, preDisplayName } = data;
switch (request_type) { switch (type) {
case 0: case 0:
if (requested_by_name) { if (requestedByName) {
return `${requested_by_name} quiere darse de alta.`; return `${requestedByName} quiere darse de alta.`;
} else if (request_status !== 1 && pre_display_name) { } else if (status !== 1 && preDisplayName) {
return `${pre_display_name} quiere darse de alta.`; return `${preDisplayName} quiere darse de alta.`;
} else if (request_status !== 1) { } else if (status !== 1) {
return `Alguien quiere darse de alta.`; return `Alguien quiere darse de alta.`;
} else { } else {
return `Se ha aceptado esta solicitud de alta.`; return `Se ha aceptado esta solicitud de alta.`;
@@ -52,30 +52,30 @@ const renderDescripcionSolicitud = (data, onProfile) => {
case 1: case 1:
return onProfile return onProfile
? "Has solicitado darte de baja." ? "Has solicitado darte de baja."
: requested_by_name : requestedByName
? `${requested_by_name} quiere darse de baja.` ? `${requestedByName} quiere darse de baja.`
: request_status !== 1 : status !== 1
? `Alguien quiere darse de baja.` ? `Alguien quiere darse de baja.`
: `Se ha aceptado esta solicitud de baja.`; : `Se ha aceptado esta solicitud de baja.`;
case 2: case 2:
if (onProfile) { if (onProfile) {
switch (request_status) { switch (status) {
case 0: return "Has solicitado añadir un colaborador."; case 0: return "Has solicitado añadir un colaborador.";
case 1: return "Tu solicitud de colaborador ha sido aceptada."; case 1: return "Tu solicitud de colaborador ha sido aceptada.";
case 2: return "Tu solicitud de colaborador ha sido rechazada."; case 2: return "Tu solicitud de colaborador ha sido rechazada.";
default: return "Solicitud de colaborador desconocida."; default: return "Solicitud de colaborador desconocida.";
} }
} else { } else {
switch (request_status) { switch (status) {
case 0: case 0:
return requested_by_name return requestedByName
? `${requested_by_name} quiere añadir a ${pre_display_name || "un colaborador"} como colaborador.` ? `${requestedByName} quiere añadir a ${preDisplayName || "un colaborador"} como colaborador.`
: `Alguien quiere añadir a ${pre_display_name || "un colaborador"} como colaborador.`; : `Alguien quiere añadir a ${preDisplayName || "un colaborador"} como colaborador.`;
case 1: case 1:
return `La solicitud de colaborador de ${requested_by_name || "alguien"} ha sido aceptada.`; return `La solicitud de colaborador de ${requestedByName || "alguien"} ha sido aceptada.`;
case 2: case 2:
return `La solicitud de colaborador de ${requested_by_name || "alguien"} ha sido rechazada.`; return `La solicitud de colaborador de ${requestedByName || "alguien"} ha sido rechazada.`;
default: default:
return "Solicitud de colaborador desconocida."; return "Solicitud de colaborador desconocida.";
} }
@@ -84,27 +84,27 @@ const renderDescripcionSolicitud = (data, onProfile) => {
case 3: case 3:
return onProfile return onProfile
? "Has solicitado quitar tu colaborador." ? "Has solicitado quitar tu colaborador."
: requested_by_name : requestedByName
? `${requested_by_name} quiere quitar su colaborador.` ? `${requestedByName} quiere quitar su colaborador.`
: request_status !== 1 : status !== 1
? `Alguien quiere quitar su colaborador.` ? `Alguien quiere quitar su colaborador.`
: `Se ha aceptado esta solicitud de baja de colaborador.`; : `Se ha aceptado esta solicitud de baja de colaborador.`;
case 4: case 4:
return onProfile return onProfile
? "Has solicitado una parcela en el invernadero." ? "Has solicitado una parcela en el invernadero."
: requested_by_name : requestedByName
? `${requested_by_name} quiere una parcela en el invernadero.` ? `${requestedByName} quiere una parcela en el invernadero.`
: request_status !== 1 : status !== 1
? `Alguien quiere una parcela en el invernadero.` ? `Alguien quiere una parcela en el invernadero.`
: `Se ha aceptado esta solicitud de parcela en el invernadero.`; : `Se ha aceptado esta solicitud de parcela en el invernadero.`;
case 5: case 5:
return onProfile return onProfile
? "Has solicitado dejar tu parcela del invernadero." ? "Has solicitado dejar tu parcela del invernadero."
: requested_by_name : requestedByName
? `${requested_by_name} quiere dejar su parcela del invernadero.` ? `${requestedByName} quiere dejar su parcela del invernadero.`
: request_status !== 1 : status !== 1
? `Alguien quiere dejar su parcela del invernadero.` ? `Alguien quiere dejar su parcela del invernadero.`
: `Se ha aceptado esta solicitud de salida del invernadero.`; : `Se ha aceptado esta solicitud de salida del invernadero.`;
@@ -114,18 +114,18 @@ const renderDescripcionSolicitud = (data, onProfile) => {
}; };
const SolicitudCard = ({ data, onAccept, onReject, onDelete, editable = true, onProfile = false }) => { const SolicitudCard = ({ data, onAccept, onReject, onDelete, editable = true, onProfile = false }) => {
const handleDelete = () => typeof onDelete === "function" && onDelete(data.request_id); const handleDelete = () => typeof onDelete === "function" && onDelete(data.requestId);
return ( return (
<MotionCard className="solicitud-card shadow-sm rounded-4 h-100"> <MotionCard className="solicitud-card shadow-sm rounded-4 h-100">
<Card.Header className="rounded-top-4 d-flex justify-content-between align-items-center"> <Card.Header className="rounded-top-4 d-flex justify-content-between align-items-center">
<div className="d-flex align-items-center"> <div className="d-flex align-items-center">
<img src={getPFP(data.pre_type)} width="36" className="rounded me-3" alt="PFP" /> <img src={getPFP(data.preType)} width="36" className="rounded me-3" alt="PFP" />
<div> <div>
<Card.Title className="mb-0"> <Card.Title className="mb-0">
Solicitud #{data.request_id} - {getTipoSolicitud(data.request_type)} Solicitud #{data.requestId} - {getTipoSolicitud(data.type)}
</Card.Title> </Card.Title>
<small className='state-small'>Estado: <strong>{getEstadoSolicitud(data.request_status)}</strong></small> <small className='state-small'>Estado: <strong>{getEstadoSolicitud(data.status)}</strong></small>
</div> </div>
</div> </div>
@@ -147,7 +147,7 @@ const SolicitudCard = ({ data, onAccept, onReject, onDelete, editable = true, on
<ListGroup variant="flush" className="border rounded-3 mb-3"> <ListGroup variant="flush" className="border rounded-3 mb-3">
<ListGroup.Item> <ListGroup.Item>
<FontAwesomeIcon icon={faCalendar} className="me-2" /> <FontAwesomeIcon icon={faCalendar} className="me-2" />
Fecha de solicitud: <strong>{parseDate(data.request_created_at)}</strong> Fecha de solicitud: <strong>{parseDate(data.request_createdAt)}</strong>
</ListGroup.Item> </ListGroup.Item>
</ListGroup> </ListGroup>
@@ -157,24 +157,24 @@ const SolicitudCard = ({ data, onAccept, onReject, onDelete, editable = true, on
</ListGroup.Item> </ListGroup.Item>
</ListGroup> </ListGroup>
{data.pre_display_name && ( {data.preDisplayName && (
<> <>
<Card.Subtitle className="card-subtitle mt-3 mb-2">Datos del futuro socio</Card.Subtitle> <Card.Subtitle className="card-subtitle mt-3 mb-2">Datos del futuro socio</Card.Subtitle>
<ListGroup variant="flush" className="border rounded-3"> <ListGroup variant="flush" className="border rounded-3">
<ListGroup.Item><FontAwesomeIcon icon={faUser} className="me-2" />Nombre: <strong>{data.pre_display_name}</strong></ListGroup.Item> <ListGroup.Item><FontAwesomeIcon icon={faUser} className="me-2" />Nombre: <strong>{data.preDisplayName}</strong></ListGroup.Item>
<ListGroup.Item><FontAwesomeIcon icon={faIdCard} className="me-2" />DNI: <strong>{data.pre_dni}</strong></ListGroup.Item> <ListGroup.Item><FontAwesomeIcon icon={faIdCard} className="me-2" />DNI: <strong>{data.preDni}</strong></ListGroup.Item>
<ListGroup.Item><FontAwesomeIcon icon={faPhone} className="me-2" />Teléfono: <strong>{data.pre_phone}</strong></ListGroup.Item> <ListGroup.Item><FontAwesomeIcon icon={faPhone} className="me-2" />Teléfono: <strong>{data.prePhone}</strong></ListGroup.Item>
<ListGroup.Item><FontAwesomeIcon icon={faEnvelope} className="me-2" />Email: <strong>{data.pre_email}</strong></ListGroup.Item> <ListGroup.Item><FontAwesomeIcon icon={faEnvelope} className="me-2" />Email: <strong>{data.preEmail}</strong></ListGroup.Item>
<ListGroup.Item><FontAwesomeIcon icon={faHome} className="me-2" />Dirección: <strong>{data.pre_address ?? 'NO'}</strong></ListGroup.Item> <ListGroup.Item><FontAwesomeIcon icon={faHome} className="me-2" />Dirección: <strong>{data.preAddress ?? 'NO'}</strong></ListGroup.Item>
<ListGroup.Item><FontAwesomeIcon icon={faMapMarkerAlt} className="me-2" />Ciudad: <strong>{data.pre_city ?? 'NO'} ({data.pre_zip_code ?? 'NO'})</strong></ListGroup.Item> <ListGroup.Item><FontAwesomeIcon icon={faMapMarkerAlt} className="me-2" />Ciudad: <strong>{data.preCity ?? 'NO'} ({data.preZipCode ?? 'NO'})</strong></ListGroup.Item>
<ListGroup.Item><FontAwesomeIcon icon={faHashtag} className="me-2" /> socio: <strong>{data.pre_member_number ?? 'NO'}</strong> | huerto: <strong>{data.pre_plot_number ?? 'NO'}</strong></ListGroup.Item> <ListGroup.Item><FontAwesomeIcon icon={faHashtag} className="me-2" /> socio: <strong>{data.preMemberNumber ?? 'NO'}</strong> | huerto: <strong>{data.prePlotNumber ?? 'NO'}</strong></ListGroup.Item>
<ListGroup.Item><FontAwesomeIcon icon={faSeedling} className="me-2" />Tipo: <strong>{['Lista de Espera', 'Hortelano', 'Hortelano + Invernadero', 'Colaborador'][data.pre_type]}</strong></ListGroup.Item> <ListGroup.Item><FontAwesomeIcon icon={faSeedling} className="me-2" />Tipo: <strong>{['Lista de Espera', 'Hortelano', 'Hortelano + Invernadero', 'Colaborador'][data.preType]}</strong></ListGroup.Item>
<ListGroup.Item><FontAwesomeIcon icon={faUserShield} className="me-2" />Rol: <strong>{['Usuario', 'Admin', 'Desarrollador'][data.pre_role]}</strong></ListGroup.Item> <ListGroup.Item><FontAwesomeIcon icon={faUserShield} className="me-2" />Rol: <strong>{['Usuario', 'Admin', 'Desarrollador'][data.preRole]}</strong></ListGroup.Item>
</ListGroup> </ListGroup>
</> </>
)} )}
{editable && data.request_status === 0 && ( {editable && data.status === 0 && (
<div className="d-flex justify-content-end gap-2 mt-3"> <div className="d-flex justify-content-end gap-2 mt-3">
<Button variant="danger" size="sm" onClick={() => onReject?.(data)}>Rechazar</Button> <Button variant="danger" size="sm" onClick={() => onReject?.(data)}>Rechazar</Button>
<Button variant="success" size="sm" onClick={() => onAccept?.(data)}>Aceptar</Button> <Button variant="success" size="sm" onClick={() => onAccept?.(data)}>Aceptar</Button>

View File

@@ -8,8 +8,12 @@ export const AuthProvider = ({ children }) => {
const axios = createAxiosInstance(); const axios = createAxiosInstance();
const { config } = useConfig(); const { config } = useConfig();
const [user, setUser] = useState(() => JSON.parse(localStorage.getItem("user")) || null);
const [token, setToken] = useState(() => localStorage.getItem("token")); const [token, setToken] = useState(() => localStorage.getItem("token"));
const [identity, setIdentity] = useState(() => {
const stored = localStorage.getItem("identity");
return stored ? JSON.parse(stored) : null;
});
const [authStatus, setAuthStatus] = useState("checking"); const [authStatus, setAuthStatus] = useState("checking");
const [error, setError] = useState(null); const [error, setError] = useState(null);
@@ -21,7 +25,7 @@ export const AuthProvider = ({ children }) => {
return; return;
} }
const BASE_URL = config.apiConfig.baseUrl; const BASE_URL = config.apiConfig.coreUrl;
const VALIDATE_URL = `${BASE_URL}${config.apiConfig.endpoints.auth.validateToken}`; const VALIDATE_URL = `${BASE_URL}${config.apiConfig.endpoints.auth.validateToken}`;
const checkAuth = async () => { const checkAuth = async () => {
@@ -29,6 +33,7 @@ export const AuthProvider = ({ children }) => {
const res = await axios.get(VALIDATE_URL, { const res = await axios.get(VALIDATE_URL, {
headers: { Authorization: `Bearer ${token}` }, headers: { Authorization: `Bearer ${token}` },
}); });
if (res.status === 200) { if (res.status === 200) {
setAuthStatus("authenticated"); setAuthStatus("authenticated");
} else { } else {
@@ -45,53 +50,70 @@ export const AuthProvider = ({ children }) => {
const login = async (formData) => { const login = async (formData) => {
setError(null); setError(null);
const BASE_URL = config.apiConfig.baseUrl; const BASE_URL = config.apiConfig.baseUrl;
const LOGIN_URL = `${BASE_URL}${config.apiConfig.endpoints.auth.login}`; const LOGIN_URL = `${BASE_URL}${config.apiConfig.endpoints.auth.login}`;
try { try {
const res = await axios.post(LOGIN_URL, formData); const res = await axios.post(LOGIN_URL, formData);
const { token, member, tokenTime } = res.data.data;
const { token, user, account, metadata } = res.data;
const identity = {
user,
account,
metadata,
};
localStorage.setItem("token", token); localStorage.setItem("token", token);
localStorage.setItem("user", JSON.stringify(member)); localStorage.setItem("identity", JSON.stringify(identity));
localStorage.setItem("tokenTime", tokenTime);
setToken(token); setToken(token);
setUser(member); setIdentity(identity);
setAuthStatus("authenticated"); setAuthStatus("authenticated");
} catch (err) { } catch (err) {
console.error("Error al iniciar sesión:", err); console.error("Error al iniciar sesión:", err);
let message = "Ha ocurrido un error inesperado."; let message = "Ha ocurrido un error inesperado.";
if (err.response) { if (err.response) {
const { status, data } = err.response; const { status, data } = err.response;
if (status === 400) { if (status === 400) {
message = "Usuario o contraseña incorrectos."; message = "Usuario o contraseña incorrectos.";
} else if (status === 403) { } else if (status === 403) {
message = "Tu cuenta está inactiva o ha sido suspendida."; message = "Tu cuenta está inactiva o suspendida.";
} else if (status === 404) { } else if (status === 404) {
message = "Usuario no encontrado."; message = "Usuario no encontrado.";
} else if (data?.message) { } else if (data?.message) {
message = data.message; message = data.message;
} }
} }
setError(message); setError(message);
throw new Error(message); throw new Error(message);
} }
}; };
const logout = () => { const logout = () => {
localStorage.clear(); localStorage.removeItem("token");
setUser(null); localStorage.removeItem("identity");
setIdentity(null);
setToken(null); setToken(null);
setAuthStatus("unauthenticated"); setAuthStatus("unauthenticated");
}; };
return ( return (
<AuthContext.Provider value={{ user, token, authStatus, login, logout, error }}> <AuthContext.Provider
value={{
identity, // { user, account, metadata }
token,
authStatus,
login,
logout,
error,
}}
>
{children} {children}
</AuthContext.Provider> </AuthContext.Provider>
); );

View File

@@ -30,9 +30,9 @@ export const useData = (config) => {
headers: getAuthHeaders(), headers: getAuthHeaders(),
params: current.params, params: current.params,
}); });
setData(response.data.data); setData(response.data);
} catch (err) { } catch (err) {
setError(err.response?.data?.message || err.message); setError(err.response?.data);
} finally { } finally {
setLoading(false); setLoading(false);
} }
@@ -45,18 +45,11 @@ export const useData = (config) => {
}, [config, fetchData]); }, [config, fetchData]);
const getData = async (url, params = {}) => { const getData = async (url, params = {}) => {
try { const response = await axios.get(url, {
const response = await axios.get(url, { headers: getAuthHeaders(),
headers: getAuthHeaders(), params,
params, });
}); return response.data;
return { data: response.data.data, error: null };
} catch (err) {
return {
data: null,
error: err.response?.data?.message || err.message,
};
}
}; };
const postData = async (endpoint, payload) => { const postData = async (endpoint, payload) => {
@@ -66,37 +59,33 @@ export const useData = (config) => {
}; };
const response = await axios.post(endpoint, payload, { headers }); const response = await axios.post(endpoint, payload, { headers });
await fetchData(); await fetchData();
return response.data.data; return response.data;
}; };
const postDataValidated = async (endpoint, payload) => { const postDataValidated = async (endpoint, payload) => {
try { try {
const headers = { const response = await axios.post(endpoint, payload, {
Authorization: `Bearer ${localStorage.getItem("token")}`, headers: {
...(payload instanceof FormData ? {} : { "Content-Type": "application/json" }), Authorization: `Bearer ${localStorage.getItem("token")}`,
}; "Content-Type": "application/json",
const response = await axios.post(endpoint, payload, { headers }); },
return { data: response.data.data, errors: null }; });
return { ok: true };
} catch (err) { } catch (err) {
const raw = err.response?.data?.message; return {
let parsed = {}; ok: false,
errors: err.response?.data?.errors || {}
try { };
parsed = JSON.parse(raw);
} catch {
return { data: null, errors: { general: raw || err.message } };
}
return { data: null, errors: parsed };
} }
}; };
const putData = async (endpoint, payload) => { const putData = async (endpoint, payload) => {
const response = await axios.put(endpoint, payload, { const response = await axios.put(endpoint, payload, {
headers: getAuthHeaders(), headers: getAuthHeaders(),
}); });
await fetchData(); await fetchData();
return response.data.data; return response.data;
}; };
const deleteData = async (endpoint) => { const deleteData = async (endpoint) => {
@@ -104,7 +93,7 @@ export const useData = (config) => {
headers: getAuthHeaders(), headers: getAuthHeaders(),
}); });
await fetchData(); await fetchData();
return response.data.data; return response.data;
}; };
const deleteDataWithBody = async (endpoint, payload) => { const deleteDataWithBody = async (endpoint, payload) => {
@@ -113,7 +102,7 @@ export const useData = (config) => {
data: payload, data: payload,
}); });
await fetchData(); await fetchData();
return response.data.data; return response.data;
}; };
return { return {

View File

@@ -12,7 +12,7 @@ const useRequestCount = () => {
const fetchCount = async () => { const fetchCount = async () => {
try { try {
const res = await axios.get( const res = await axios.get(
config.apiConfig.baseUrl + config.apiConfig.endpoints.requests.countPending, config.apiConfig.baseUrl + config.apiConfig.endpoints.requests.count,
{ {
headers: { headers: {
Authorization: `Bearer ${localStorage.getItem('token')}`, Authorization: `Bearer ${localStorage.getItem('token')}`,
@@ -20,7 +20,7 @@ const useRequestCount = () => {
}, },
} }
); );
setCount(res.data.data.count); setCount(res.data.count);
} catch (err) { } catch (err) {
console.error('❌ Error al obtener el número de solicitudes:', err.message); console.error('❌ Error al obtener el número de solicitudes:', err.message);
} }

View File

@@ -25,9 +25,9 @@ const Anuncios = () => {
if (configLoading) return <p><LoadingIcon /></p>; if (configLoading) return <p><LoadingIcon /></p>;
const reqConfig = { const reqConfig = {
baseUrl: `${config.apiConfig.baseUrl}${config.apiConfig.endpoints.announces.all}`, baseUrl: `${config.apiConfig.baseUrl}${config.apiConfig.endpoints.announcements.all}`,
params: { params: {
_sort: 'created_at', _sort: 'createdAt',
_order: 'desc', _order: 'desc',
}, },
}; };
@@ -61,7 +61,7 @@ const AnunciosContent = ({ reqConfig }) => {
(filters.baja && anuncio.priority === 0) || (filters.baja && anuncio.priority === 0) ||
(filters.media && anuncio.priority === 1) || (filters.media && anuncio.priority === 1) ||
(filters.alta && anuncio.priority === 2); (filters.alta && anuncio.priority === 2);
const createdAt = new Date(anuncio.created_at); const createdAt = new Date(anuncio.createdAt);
const now = new Date(); const now = new Date();
const matchesFecha = const matchesFecha =
(filters.ultimos7 && (now - createdAt) / (1000 * 60 * 60 * 24) <= 7) || (filters.ultimos7 && (now - createdAt) / (1000 * 60 * 60 * 24) <= 7) ||
@@ -74,7 +74,7 @@ const AnunciosContent = ({ reqConfig }) => {
const normalized = term.toLowerCase(); const normalized = term.toLowerCase();
return ( return (
anuncio.body?.toLowerCase().includes(normalized) || anuncio.body?.toLowerCase().includes(normalized) ||
anuncio.published_by_name?.toLowerCase().includes(normalized) anuncio.publishedByName?.toLowerCase().includes(normalized)
); );
}, },
initialFilters: { initialFilters: {
@@ -90,10 +90,10 @@ const AnunciosContent = ({ reqConfig }) => {
const handleCreate = () => { const handleCreate = () => {
setCreatingAnuncio(true); setCreatingAnuncio(true);
setTempAnuncio({ setTempAnuncio({
announce_id: null,
body: 'Nuevo anuncio', body: 'Nuevo anuncio',
priority: 1, priority: 1,
published_by_name: 'Admin', publishedBy: JSON.parse(localStorage.getItem("identity"))?.user?.displayName,
createdAt: Date.now()
}); });
document.querySelector('.cards-grid')?.scrollTo({ top: 0, behavior: 'smooth' }); document.querySelector('.cards-grid')?.scrollTo({ top: 0, behavior: 'smooth' });
}; };
@@ -162,12 +162,12 @@ const AnunciosContent = ({ reqConfig }) => {
/> />
</EditorProvider> </EditorProvider>
)} )}
renderCard={(anuncio) => ( renderCard={(anuncio, idx) => (
<AnuncioCard <AnuncioCard
key={anuncio.announce_id} key={anuncio.announceId}
anuncio={anuncio} anuncio={{...anuncio, idx: idx}}
onUpdate={(a, id) => handleEditSubmit(a, id)} onUpdate={(a, id) => handleEditSubmit(a, id)}
onDelete={() => handleDelete(anuncio.announce_id)} onDelete={() => handleDelete(anuncio.announceId)}
error={error} error={error}
onClearError={() => setError(null)} onClearError={() => setError(null)}
/> />

View File

@@ -19,9 +19,8 @@ const Documentacion = () => {
const reqConfig = { const reqConfig = {
baseUrl: config.apiConfig.coreUrl + config.apiConfig.endpoints.files.all, baseUrl: config.apiConfig.coreUrl + config.apiConfig.endpoints.files.all,
uploadUrl: config.apiConfig.coreUrl + config.apiConfig.endpoints.files.upload,
params: { params: {
_sort: 'uploaded_at', _sort: 'uploadedAt',
_order: 'desc' _order: 'desc'
} }
}; };
@@ -40,22 +39,22 @@ const DocumentacionContent = ({ reqConfig }) => {
const handleSelectFiles = async (files) => { const handleSelectFiles = async (files) => {
const file = files[0]; const file = files[0];
if (!file || !reqConfig?.uploadUrl) return; if (!file || !reqConfig?.baseUrl) return;
const file_name = file.name; const fileName = file.name;
const mime_type = file.type || "application/octet-stream"; const mimeType = file.type || "application/octet-stream";
const uploaded_by = JSON.parse(localStorage.getItem("user"))?.user_id; const uploadedBy = JSON.parse(localStorage.getItem("identity"))?.user?.userId;
const context = 1; const context = 1;
const formData = new FormData(); const formData = new FormData();
formData.append("file", file); formData.append("file", file);
formData.append("file_name", file_name); formData.append("fileName", fileName);
formData.append("mime_type", mime_type); formData.append("mimeType", mimeType);
formData.append("uploaded_by", uploaded_by); formData.append("uploadedBy", uploadedBy);
formData.append("context", context); formData.append("context", context);
try { try {
await postData(reqConfig.uploadUrl, formData); await postData(reqConfig.baseUrl, formData);
fileUploadRef.current?.resetSelectedFiles(); fileUploadRef.current?.resetSelectedFiles();
} catch (err) { } catch (err) {
console.error("Error al subir archivo:", err); console.error("Error al subir archivo:", err);
@@ -100,8 +99,8 @@ const DocumentacionContent = ({ reqConfig }) => {
variant="danger" variant="danger"
onClick={async () => { onClick={async () => {
try { try {
await deleteDataWithBody(`${reqConfig.baseUrl}/${deleteTarget.file_id}`, { await deleteDataWithBody(`${reqConfig.baseUrl}/${deleteTarget.fileId}`, {
file_path: deleteTarget.file_path filePath: deleteTarget.filePath
}); });
setDeleteTarget(null); setDeleteTarget(null);
} catch (err) { } catch (err) {

View File

@@ -30,7 +30,7 @@ const Gastos = () => {
const reqConfig = { const reqConfig = {
baseUrl: `${config.apiConfig.baseUrl}${config.apiConfig.endpoints.expenses.all}`, baseUrl: `${config.apiConfig.baseUrl}${config.apiConfig.endpoints.expenses.all}`,
params: { params: {
_sort: 'created_at', _sort: 'createdAt',
_order: 'desc', _order: 'desc',
}, },
}; };
@@ -84,7 +84,7 @@ const GastosContent = ({ reqConfig }) => {
const handleCreate = () => { const handleCreate = () => {
setCreatingGasto(true); setCreatingGasto(true);
setTempGasto({ setTempGasto({
expense_id: null, expenseId: null,
concept: '', concept: '',
amount: 0.0, amount: 0.0,
supplier: '', supplier: '',
@@ -160,7 +160,7 @@ const GastosContent = ({ reqConfig }) => {
)} )}
renderCard={(gasto) => ( renderCard={(gasto) => (
<GastoCard <GastoCard
key={gasto.expense_id} key={gasto.expenseId}
gasto={gasto} gasto={gasto}
onUpdate={handleEditSubmit} onUpdate={handleEditSubmit}
onDelete={handleDelete} onDelete={handleDelete}

View File

@@ -33,7 +33,7 @@ const Ingresos = () => {
rawUrl: config.apiConfig.baseUrl + config.apiConfig.endpoints.incomes.all, rawUrl: config.apiConfig.baseUrl + config.apiConfig.endpoints.incomes.all,
membersUrl: config.apiConfig.baseUrl + config.apiConfig.endpoints.members.all, membersUrl: config.apiConfig.baseUrl + config.apiConfig.endpoints.members.all,
params: { params: {
_sort: 'created_at', _sort: 'createdAt',
_order: 'desc' _order: 'desc'
} }
}; };
@@ -98,16 +98,16 @@ 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.member_number).includes(normalized) || String(ingreso.memberNumber).includes(normalized) ||
ingreso.display_name?.toLowerCase().includes(normalized); ingreso.displayName?.toLowerCase().includes(normalized);
} }
}); });
const handleCreate = () => { const handleCreate = () => {
setCreatingIngreso(true); setCreatingIngreso(true);
setTempIngreso({ setTempIngreso({
income_id: null, incomeId: null,
member_number: 0, memberNumber: 0,
concept: '', concept: '',
amount: 0.0, amount: 0.0,
frequency: CONSTANTS.PAYMENT_FREQUENCY_YEARLY, frequency: CONSTANTS.PAYMENT_FREQUENCY_YEARLY,
@@ -183,10 +183,10 @@ const IngresosContent = ({ reqConfig }) => {
)} )}
renderCard={(income) => ( renderCard={(income) => (
<IngresoCard <IngresoCard
key={income.income_id} key={income.incomeId}
income={income} income={income}
onUpdate={(data, id) => handleEditSubmit(data, id)} onUpdate={(data, id) => handleEditSubmit(data, id)}
onDelete={() => handleDelete(income.income_id)} onDelete={() => handleDelete(income.incomeId)}
error={error} error={error}
onClearError={() => setError(null)} onClearError={() => setError(null)}
/> />

View File

@@ -23,10 +23,10 @@ const ListaEspera = () => {
if (configLoading) return <p><LoadingIcon /></p>; if (configLoading) return <p><LoadingIcon /></p>;
const reqConfig = { const reqConfig = {
baseUrl: config.apiConfig.baseUrl + config.apiConfig.endpoints.members.limitedWaitlist, baseUrl: config.apiConfig.baseUrl + config.apiConfig.endpoints.users.waitlistLimited,
requestUrl: config.apiConfig.baseUrl + config.apiConfig.endpoints.requests.all, requestUrl: config.apiConfig.baseUrl + config.apiConfig.endpoints.requests.all,
preUsersUrl: config.apiConfig.baseUrl + config.apiConfig.endpoints.pre_users.all, preUsersUrl: config.apiConfig.baseUrl + config.apiConfig.endpoints.preUsers.all,
preUserValidationUrl: config.apiConfig.baseUrl + config.apiConfig.endpoints.pre_users.validation, preUserValidationUrl: config.apiConfig.baseUrl + config.apiConfig.endpoints.preUsers.validate,
params: {} params: {}
}; };
@@ -39,7 +39,7 @@ const ListaEspera = () => {
const ListaEsperaContent = ({ reqConfig }) => { const ListaEsperaContent = ({ reqConfig }) => {
const { authStatus } = useAuth(); const { authStatus } = useAuth();
const { data, dataLoading, dataError, postDataValidated, postData } = useDataContext(); const { data, dataLoading, dataError, postData, postDataValidated } = useDataContext();
const [showWelcomeModal, setShowWelcomeModal] = useState(false); const [showWelcomeModal, setShowWelcomeModal] = useState(false);
const [showPreUserFormModal, setShowPreUserFormModal] = useState(false); const [showPreUserFormModal, setShowPreUserFormModal] = useState(false);
@@ -64,27 +64,30 @@ const ListaEsperaContent = ({ reqConfig }) => {
const handleRegisterSubmit = async (formData) => { const handleRegisterSubmit = async (formData) => {
setValidationErrors({}); setValidationErrors({});
const { _, errors } = await postDataValidated(reqConfig.preUserValidationUrl, formData); const validation = await postDataValidated(
reqConfig.preUserValidationUrl,
formData
);
if (errors) { if (!validation.ok) {
setValidationErrors(errors); setValidationErrors(validation.errors);
return; return;
} }
try { try {
const request = await postData(reqConfig.requestUrl, { type: 0, status: 0 }); const request = await postData(reqConfig.requestUrl, { type: 0, status: 0 });
const requestId = request?.request_id; const requestId = request?.requestId;
if (!requestId) throw new Error("No se pudo registrar la solicitud."); if (!requestId) throw new Error("No se pudo registrar la solicitud.");
await postData(reqConfig.preUsersUrl, { await postData(reqConfig.preUsersUrl, {
...formData, ...formData,
request_id: requestId requestId
}); });
setShowPreUserFormModal(false); setShowPreUserFormModal(false);
setShowConfirmationModal(true); setShowConfirmationModal(true);
} catch (err) { } catch (err) {
setValidationErrors({ general: err.message }); setValidationErrors({ general: "Error inesperado al enviar la solicitud" });
} }
}; };
@@ -94,13 +97,6 @@ const ListaEsperaContent = ({ reqConfig }) => {
setShowPreUserFormModal(true); setShowPreUserFormModal(true);
}; };
const mapped = [...(data ?? [])]
.sort((a, b) => new Date(a.created_at) - new Date(b.created_at))
.map((item) => ({
...item,
created_at: DateParser.timestampToString(item.created_at)
}));
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 (dataError) return <p className="text-danger text-center my-5">{dataError}</p>;
@@ -117,7 +113,7 @@ const ListaEsperaContent = ({ reqConfig }) => {
</IfNotAuthenticated> </IfNotAuthenticated>
</div> </div>
<hr className="section-divider" /> <hr className="section-divider" />
<List datos={mapped} config={{ title: 'display_name', subtitle: 'created_at', showIndex: true }} /> <List datos={data} config={{ title: 'name', subtitle: '', showIndex: true }} />
{authStatus === 'unauthenticated' && ( {authStatus === 'unauthenticated' && (
<Modal show={showWelcomeModal} onHide={() => setShowWelcomeModal(false)}> <Modal show={showWelcomeModal} onHide={() => setShowWelcomeModal(false)}>

View File

@@ -66,12 +66,12 @@ const Perfil = () => {
}; };
const reqConfig = { const reqConfig = {
baseUrl: `${config.apiConfig.baseUrl}${config.apiConfig.endpoints.members.profile}`, baseUrl: `${config.apiConfig.baseUrl}${config.apiConfig.endpoints.users.me}`,
myIncomesUrl: buildUrl(config.apiConfig.baseUrl, config.apiConfig.endpoints.incomes.myIncomes), myIncomesUrl: buildUrl(config.apiConfig.baseUrl, config.apiConfig.endpoints.incomes.mine),
requestUrl: buildUrl(config.apiConfig.baseUrl, config.apiConfig.endpoints.requests.all), requestUrl: buildUrl(config.apiConfig.baseUrl, config.apiConfig.endpoints.requests.all),
preUsersUrl: buildUrl(config.apiConfig.baseUrl, config.apiConfig.endpoints.pre_users.all), preUsersUrl: buildUrl(config.apiConfig.baseUrl, config.apiConfig.endpoints.preUsers.all),
preUserValidationUrl: buildUrl(config.apiConfig.baseUrl, config.apiConfig.endpoints.pre_users.validation), preUserValidationUrl: buildUrl(config.apiConfig.baseUrl, config.apiConfig.endpoints.preUsers.validate),
myRequestsUrl: buildUrl(config.apiConfig.baseUrl, config.apiConfig.endpoints.requests.myRequests), myRequestsUrl: buildUrl(config.apiConfig.baseUrl, config.apiConfig.endpoints.requests.mine),
changePasswordUrl: buildUrl(config.apiConfig.coreUrl, config.apiConfig.endpoints.auth.changePassword), changePasswordUrl: buildUrl(config.apiConfig.coreUrl, config.apiConfig.endpoints.auth.changePassword),
loginValidateUrl: buildUrl(config.apiConfig.coreUrl, config.apiConfig.endpoints.auth.loginValidate), loginValidateUrl: buildUrl(config.apiConfig.coreUrl, config.apiConfig.endpoints.auth.loginValidate),
}; };
@@ -87,13 +87,13 @@ const PerfilContent = ({ config }) => {
const { data, dataLoading, dataError, postData, postDataValidated } = useDataContext(); const { data, dataLoading, dataError, postData, postDataValidated } = useDataContext();
const { logout } = useAuth(); const { logout } = useAuth();
const usuario = data?.member; const identity = JSON.parse(localStorage.getItem("identity"));
const myRequests = data?.requests ?? []; const myRequests = data?.requests ?? [];
const incomes = data?.payments ?? []; const incomes = data?.payments ?? [];
const hasCollaborator = data?.hasCollaborator ?? false; const hasCollaborator = data?.hasCollaborator ?? false;
const hasCollaboratorRequest = data?.hasCollaboratorRequest ?? false; const hasCollaboratorRequest = data?.hasCollaboratorRequest ?? false;
const hasGreenHouse = data?.hasGreenHouse ?? false; const hasGreenHouse = data?.hasGreenhouse ?? false;
const hasGreenHouseRequest = data?.hasGreenHouseRequest ?? false; const hasGreenHouseRequest = data?.hasGreenhouseRequest ?? false;
const [showAddCollaboratorModal, setShowAddCollaboratorModal] = useState(false); const [showAddCollaboratorModal, setShowAddCollaboratorModal] = useState(false);
const [showRemoveCollaboratorModal, setShowRemoveCollaboratorModal] = useState(false); const [showRemoveCollaboratorModal, setShowRemoveCollaboratorModal] = useState(false);
@@ -117,7 +117,7 @@ const PerfilContent = ({ config }) => {
await postData(config.requestUrl, { await postData(config.requestUrl, {
type: CONSTANTS.REQUEST_TYPE_UNREGISTER, type: CONSTANTS.REQUEST_TYPE_UNREGISTER,
status: CONSTANTS.REQUEST_PENDING, status: CONSTANTS.REQUEST_PENDING,
requested_by: usuario.user_id requestedBy: identity.user.userId
}); });
setFeedbackModal({ setFeedbackModal({
title: 'Solicitud enviada', title: 'Solicitud enviada',
@@ -140,7 +140,7 @@ const PerfilContent = ({ config }) => {
await postData(config.requestUrl, { await postData(config.requestUrl, {
type: CONSTANTS.REQUEST_TYPE_ADD_GREENHOUSE, type: CONSTANTS.REQUEST_TYPE_ADD_GREENHOUSE,
status: CONSTANTS.REQUEST_PENDING, status: CONSTANTS.REQUEST_PENDING,
requested_by: usuario.user_id requestedBy: identity.user.userId
}); });
setFeedbackModal({ setFeedbackModal({
title: 'Solicitud enviada', title: 'Solicitud enviada',
@@ -163,7 +163,7 @@ const PerfilContent = ({ config }) => {
await postData(config.requestUrl, { await postData(config.requestUrl, {
type: CONSTANTS.REQUEST_TYPE_REMOVE_GREENHOUSE, type: CONSTANTS.REQUEST_TYPE_REMOVE_GREENHOUSE,
status: CONSTANTS.REQUEST_PENDING, status: CONSTANTS.REQUEST_PENDING,
requested_by: usuario.user_id requestedBy: identity.user.userId
}); });
setFeedbackModal({ setFeedbackModal({
title: 'Solicitud enviada', title: 'Solicitud enviada',
@@ -191,7 +191,7 @@ const PerfilContent = ({ config }) => {
const handleChangePassword = async () => { const handleChangePassword = async () => {
try { try {
const validOldPassword = await postData(config.loginValidateUrl, { const validOldPassword = await postData(config.loginValidateUrl, {
userId: usuario.user_id, userId: identity.user.userId,
password: newPasswordData.currentPassword password: newPasswordData.currentPassword
}); });
if (!validOldPassword.valid) throw new Error("La contraseña actual es incorrecta."); if (!validOldPassword.valid) throw new Error("La contraseña actual es incorrecta.");
@@ -199,7 +199,7 @@ const PerfilContent = ({ config }) => {
if (newPasswordData.newPassword.length < 8) throw new Error("La nueva contraseña debe tener al menos 8 caracteres."); if (newPasswordData.newPassword.length < 8) throw new Error("La nueva contraseña debe tener al menos 8 caracteres.");
const response = await postData(config.changePasswordUrl, { const response = await postData(config.changePasswordUrl, {
userId: usuario.user_id, userId: identity.user.userId,
newPassword: newPasswordData.newPassword newPassword: newPasswordData.newPassword
}); });
@@ -231,9 +231,9 @@ const PerfilContent = ({ config }) => {
const mappedRequests = myRequests.map(r => ({ const mappedRequests = myRequests.map(r => ({
...r, ...r,
request_type: r.request_type ?? r.type, type: r.type ?? r.type,
request_status: r.request_status ?? r.status, status: r.status ?? r.status,
request_created_at: r.request_created_at ?? r.created_at request_createdAt: r.request_createdAt ?? r.createdAt
})); }));
if (dataLoading) return <p className="text-center my-5"><LoadingIcon /></p>; if (dataLoading) return <p className="text-center my-5"><LoadingIcon /></p>;
@@ -247,10 +247,10 @@ const PerfilContent = ({ config }) => {
<Card className="shadow-sm rounded-4 perfil-card"> <Card className="shadow-sm rounded-4 perfil-card">
<Card.Header className="bg-secondary text-white rounded-top-4 d-flex align-items-center justify-content-between"> <Card.Header className="bg-secondary text-white rounded-top-4 d-flex align-items-center justify-content-between">
<div className="d-flex align-items-center"> <div className="d-flex align-items-center">
<img src={getPFP(usuario.type)} alt="PFP" width={36} className="me-3" /> <img src={getPFP(identity.metadata.type)} alt="PFP" width={36} className="me-3" />
<div className="m-0 p-0"> <div className="m-0 p-0">
<Card.Title className="mb-0">{`@${usuario.user_name}`}</Card.Title> <Card.Title className="mb-0">{`@${identity.account.username}`}</Card.Title>
<small>Te uniste el {parseDate(usuario.created_at)}</small> <small>Te uniste el {parseDate(identity.metadata.createdAt)}</small>
</div> </div>
</div> </div>
@@ -293,21 +293,21 @@ const PerfilContent = ({ config }) => {
<Card.Body> <Card.Body>
<ListGroup variant="flush" className="border rounded-3"> <ListGroup variant="flush" className="border rounded-3">
<ListGroup.Item><FontAwesomeIcon icon={faUser} className="me-2" />Nombre: <strong>{usuario.display_name}</strong></ListGroup.Item> <ListGroup.Item><FontAwesomeIcon icon={faUser} className="me-2" />Nombre: <strong>{identity.user.displayName}</strong></ListGroup.Item>
<ListGroup.Item><FontAwesomeIcon icon={faIdCard} className="me-2" />DNI: <strong>{usuario.dni}</strong></ListGroup.Item> <ListGroup.Item><FontAwesomeIcon icon={faIdCard} className="me-2" />DNI: <strong>{identity.metadata.dni}</strong></ListGroup.Item>
<ListGroup.Item><FontAwesomeIcon icon={faEnvelope} className="me-2" />Email: <strong>{usuario.email}</strong></ListGroup.Item> <ListGroup.Item><FontAwesomeIcon icon={faEnvelope} className="me-2" />Email: <strong>{identity.account.email}</strong></ListGroup.Item>
<ListGroup.Item><FontAwesomeIcon icon={faPhone} className="me-2" />Teléfono: <strong>{usuario.phone}</strong></ListGroup.Item> <ListGroup.Item><FontAwesomeIcon icon={faPhone} className="me-2" />Teléfono: <strong>{identity.metadata.phone}</strong></ListGroup.Item>
<ListGroup.Item> <ListGroup.Item>
<FontAwesomeIcon icon={faHashtag} className="me-2" />Socio : <strong>{usuario.member_number}</strong> | Huerto : <strong>{usuario.plot_number}</strong> <FontAwesomeIcon icon={faHashtag} className="me-2" />Socio : <strong>{identity.metadata.memberNumber}</strong> | Huerto : <strong>{identity.metadata.plotNumber}</strong>
</ListGroup.Item> </ListGroup.Item>
<ListGroup.Item> <ListGroup.Item>
<FontAwesomeIcon icon={faSeedling} className="me-2" />Tipo de socio: <strong>{['LISTA DE ESPERA', 'HORTELANO', 'HORTELANO + INVERNADERO', 'COLABORADOR', 'SUBVENCION', 'DESARROLLADOR'][usuario.type]}</strong> <FontAwesomeIcon icon={faSeedling} className="me-2" />Tipo de socio: <strong>{['LISTA DE ESPERA', 'HORTELANO', 'HORTELANO + INVERNADERO', 'COLABORADOR', 'SUBVENCION', 'DESARROLLADOR'][identity.metadata.type]}</strong>
</ListGroup.Item> </ListGroup.Item>
<ListGroup.Item> <ListGroup.Item>
<FontAwesomeIcon icon={faUserShield} className="me-2" />Rol en huertos: <strong>{['USUARIO', 'ADMIN', 'DESARROLLADOR'][usuario.role]}</strong> <FontAwesomeIcon icon={faUserShield} className="me-2" />Rol en huertos: <strong>{['USUARIO', 'ADMIN', 'DESARROLLADOR'][identity.metadata.role]}</strong>
</ListGroup.Item> </ListGroup.Item>
<ListGroup.Item> <ListGroup.Item>
<FontAwesomeIcon icon={faCalendar} className="me-2" />Estado: <strong>{usuario.status === 1 ? 'ACTIVO' : 'INACTIVO'}</strong> <FontAwesomeIcon icon={faCalendar} className="me-2" />Estado: <strong>{identity.account.status === 1 ? 'ACTIVO' : 'INACTIVO'}</strong>
</ListGroup.Item> </ListGroup.Item>
</ListGroup> </ListGroup>
</Card.Body> </Card.Body>
@@ -321,7 +321,7 @@ const PerfilContent = ({ config }) => {
{incomes.length === 0 && <p className="text-center">No hay pagos registrados.</p>} {incomes.length === 0 && <p className="text-center">No hay pagos registrados.</p>}
<div className="d-flex flex-wrap gap-3 mb-4"> <div className="d-flex flex-wrap gap-3 mb-4">
{incomes.map(income => ( {incomes.map(income => (
<IngresoCard key={income.income_id} income={income} editable={false} /> <IngresoCard key={income.incomeId} income={income} editable={false} />
))} ))}
</div> </div>
@@ -331,7 +331,7 @@ const PerfilContent = ({ config }) => {
<div className="d-flex flex-wrap gap-3 mb-4"> <div className="d-flex flex-wrap gap-3 mb-4">
{mappedRequests.map(request => ( {mappedRequests.map(request => (
<SolicitudCard key={request.request_id} data={request} editable={false} onProfile={true} /> <SolicitudCard key={request.requestId} data={request} editable={false} onProfile={true} />
))} ))}
</div> </div>
@@ -432,7 +432,7 @@ const PerfilContent = ({ config }) => {
> >
<PreUserForm <PreUserForm
userType={3} userType={3}
plotNumber={usuario.plot_number} plotNumber={identity.metadata.plotNumber}
errors={validationErrors} errors={validationErrors}
onSubmit={async (formData) => { onSubmit={async (formData) => {
setValidationErrors({}); setValidationErrors({});
@@ -447,15 +447,15 @@ const PerfilContent = ({ config }) => {
const request = await postData(config.requestUrl, { const request = await postData(config.requestUrl, {
type: CONSTANTS.REQUEST_TYPE_ADD_COLLABORATOR, type: CONSTANTS.REQUEST_TYPE_ADD_COLLABORATOR,
status: CONSTANTS.REQUEST_PENDING, status: CONSTANTS.REQUEST_PENDING,
requested_by: usuario.user_id requestedBy: identity.user.userId
}); });
const requestId = request?.request_id; const requestId = request?.requestId;
if (!requestId) throw new Error("No se pudo crear la solicitud."); if (!requestId) throw new Error("No se pudo crear la solicitud.");
await postData(config.preUsersUrl, { await postData(config.preUsersUrl, {
...formData, ...formData,
request_id: requestId requestId: requestId
}); });
setValidationErrors({}); setValidationErrors({});
@@ -495,7 +495,7 @@ const PerfilContent = ({ config }) => {
await postData(config.requestUrl, { await postData(config.requestUrl, {
type: CONSTANTS.REQUEST_TYPE_REMOVE_COLLABORATOR, type: CONSTANTS.REQUEST_TYPE_REMOVE_COLLABORATOR,
status: CONSTANTS.REQUEST_PENDING, status: CONSTANTS.REQUEST_PENDING,
requested_by: usuario.user_id requestedBy: identity.user.userId
}); });
setFeedbackModal({ setFeedbackModal({

View File

@@ -28,11 +28,11 @@ const Socios = () => {
if (configLoading || !config) return <p className="text-center my-5"><LoadingIcon /></p>; if (configLoading || !config) return <p className="text-center my-5"><LoadingIcon /></p>;
const reqConfig = { const reqConfig = {
baseUrl: `${config.apiConfig.baseUrl}${config.apiConfig.endpoints.members.all}`, baseUrl: `${config.apiConfig.baseUrl}${config.apiConfig.endpoints.users.all}`,
incomesUrl: `${config.apiConfig.baseUrl}${config.apiConfig.endpoints.members.payments}`, incomesUrl: `${config.apiConfig.baseUrl}${config.apiConfig.endpoints.users.payments}`,
rawIncomesUrl: `${config.apiConfig.baseUrl}${config.apiConfig.endpoints.incomes.all}`, rawIncomesUrl: `${config.apiConfig.baseUrl}${config.apiConfig.endpoints.incomes.all}`,
params: { params: {
_sort: "member_number", _sort: "memberNumber",
_order: "asc" _order: "asc"
} }
}; };
@@ -67,24 +67,24 @@ const SociosContent = ({ reqConfig }) => {
} = usePaginatedList({ } = usePaginatedList({
data, data,
pageSize: PAGE_SIZE, pageSize: PAGE_SIZE,
filterFn: (socio, filters) => { filterFn: (identity, filters) => {
if (filters.todos) return true; if (filters.todos) return true;
if (!filters.inactivos && socio.status === 0) return false; if (!filters.inactivos && identity.account.status === 0) return false;
return ( return (
(filters.listaEspera && socio.type === 0) || (filters.listaEspera && identity.metadata.type === 0) ||
(filters.hortelanos && socio.type === 1) || (filters.hortelanos && identity.metadata.type === 1) ||
(filters.invernadero && socio.type === 2) || (filters.invernadero && identity.metadata.type === 2) ||
(filters.colaboradores && socio.type === 3) || (filters.colaboradores && identity.metadata.type === 3) ||
(filters.inactivos && socio.status === 0) (filters.inactivos && identity.account.status === 0)
); );
}, },
searchFn: (socio, term) => { searchFn: (identity, term) => {
const normalized = term.toLowerCase(); const normalized = term.toLowerCase();
return ( return (
socio.display_name?.toLowerCase().includes(normalized) || identity.user.displayName?.toLowerCase().includes(normalized) ||
socio.dni?.toLowerCase().includes(normalized) || identity.metadata.dni?.toLowerCase().includes(normalized) ||
String(socio.member_number).includes(normalized) || String(identity.metadata.memberNumber).includes(normalized) ||
String(socio.plot_number).includes(normalized) String(identity.metadata.plotNumber).includes(normalized)
); );
}, },
initialFilters: { initialFilters: {
@@ -98,20 +98,20 @@ const SociosContent = ({ reqConfig }) => {
}); });
const listaEsperaOrdenada = data ? data const listaEsperaOrdenada = data ? data
.filter(s => s.type === 0 && s.status !== 0) .filter(identity => identity.metadata.type === 0 && identity.account.status !== 0)
.sort((a, b) => new Date(a.created_at) - new Date(b.created_at)) : []; .sort((a, b) => new Date(a.metadata.createdAt) - new Date(b.metadata.createdAt)) : [];
const handleCreate = () => { const handleCreate = () => {
setCreatingSocio(true); setCreatingSocio(true);
const socio = { const socio = {
user_id: null, userId: null,
user_name: "nuevo" + Date.now(), userName: "nuevo" + Date.now(),
email: "", email: "",
display_name: "Nuevo Socio", displayName: "Nuevo Socio",
role: 0, role: 0,
global_status: 1, globalStatus: 1,
member_number: "", memberNumber: "",
plot_number: "", plotNumber: "",
dni: "", dni: "",
phone: "", phone: "",
notes: "", notes: "",
@@ -130,7 +130,7 @@ const SociosContent = ({ reqConfig }) => {
const handleCreateSubmit = async (newSocio) => { const handleCreateSubmit = async (newSocio) => {
try { try {
newSocio.user_name = newSocio.display_name.split(" ")[0].toLowerCase() + newSocio.member_number; newSocio.userName = newSocio.displayName.split(" ")[0].toLowerCase() + newSocio.memberNumber;
await postData(reqConfig.baseUrl, newSocio); await postData(reqConfig.baseUrl, newSocio);
setError(null); setError(null);
setCreatingSocio(false); setCreatingSocio(false);
@@ -162,7 +162,7 @@ const SociosContent = ({ reqConfig }) => {
setIncomesError(null); setIncomesError(null);
try { try {
const url = reqConfig.incomesUrl.replace(":member_number", memberNumber); const url = reqConfig.incomesUrl.replace(":memberNumber", memberNumber);
const res = await getData(url); const res = await getData(url);
setIncomes(res.data); setIncomes(res.data);
} catch (err) { } catch (err) {
@@ -174,7 +174,7 @@ const SociosContent = ({ reqConfig }) => {
const handleIncomeUpdate = async (editado) => { const handleIncomeUpdate = async (editado) => {
try { try {
await putData(`${reqConfig.rawIncomesUrl}/${editado.income_id}`, editado); await putData(`${reqConfig.rawIncomesUrl}/${editado.incomeId}`, editado);
await handleViewIncomes(selectedMemberNumber); await handleViewIncomes(selectedMemberNumber);
} catch (err) { } catch (err) {
console.error("Error actualizando ingreso:", err); console.error("Error actualizando ingreso:", err);
@@ -219,17 +219,17 @@ const SociosContent = ({ reqConfig }) => {
)} )}
renderCard={(socio) => { renderCard={(socio) => {
const position = socio.type === 0 const position = socio.type === 0
? listaEsperaOrdenada.findIndex(s => s.user_id === socio.user_id) + 1 ? listaEsperaOrdenada.findIndex(s => s.userId === socio.userId) + 1
: null; : null;
return ( return (
<SocioCard <SocioCard
key={socio.user_id} key={socio.userId}
socio={socio} socio={socio}
onUpdate={handleEditSubmit} onUpdate={handleEditSubmit}
onDelete={handleDelete} onDelete={handleDelete}
onCancel={handleCancelCreate} onCancel={handleCancelCreate}
onViewIncomes={() => handleViewIncomes(socio.member_number)} onViewIncomes={() => handleViewIncomes(socio.memberNumber)}
error={error} error={error}
onClearError={() => setError(null)} onClearError={() => setError(null)}
positionIfWaitlist={position} positionIfWaitlist={position}
@@ -256,7 +256,7 @@ const SociosContent = ({ reqConfig }) => {
)} )}
<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.income_id} income={income} <IngresoCard key={income.incomeId} income={income}
onUpdate={handleIncomeUpdate} className='from-members' /> onUpdate={handleIncomeUpdate} className='from-members' />
))} ))}
</div> </div>

View File

@@ -49,30 +49,28 @@ const SolicitudesContent = ({ reqConfig }) => {
searchFn: (entry, term) => { searchFn: (entry, term) => {
const normalized = term.toLowerCase(); const normalized = term.toLowerCase();
return ( return (
entry.pre_display_name?.toLowerCase().includes(normalized) || entry.preDisplayName?.toLowerCase().includes(normalized) ||
entry.pre_dni?.toLowerCase().includes(normalized) || entry.preDni?.toLowerCase().includes(normalized) ||
entry.pre_email?.toLowerCase().includes(normalized) || entry.preEmail?.toLowerCase().includes(normalized) ||
String(entry.pre_phone).includes(normalized) String(entry.prePhone).includes(normalized)
); );
}, },
sortFn: (a, b) => a.request_status - b.request_status sortFn: (a, b) => a.status - b.status
}); });
const handleAccept = async (entry) => { const handleAccept = async (entry) => {
const url = reqConfig.acceptUrl.replace(":request_id", entry.request_id); const url = reqConfig.acceptUrl.replace(":requestId", entry.requestId);
try { try {
await putData(url, {}); await putData(url, {});
console.log("✅ Solicitud aceptada:", entry.request_id);
} catch (err) { } catch (err) {
console.error("❌ Error al aceptar solicitud:", err.message); console.error("❌ Error al aceptar solicitud:", err.message);
} }
}; };
const handleReject = async (entry) => { const handleReject = async (entry) => {
const url = reqConfig.rejectUrl.replace(":request_id", entry.request_id); const url = reqConfig.rejectUrl.replace(":requestId", entry.requestId);
try { try {
await putData(url, {}); await putData(url, {});
console.log("🛑 Solicitud rechazada:", entry.request_id);
} catch (err) { } catch (err) {
console.error("❌ Error al rechazar solicitud:", err.message); console.error("❌ Error al rechazar solicitud:", err.message);
} }
@@ -107,7 +105,7 @@ const SolicitudesContent = ({ reqConfig }) => {
items={filtered} items={filtered}
renderCard={(entry) => ( renderCard={(entry) => (
<SolicitudCard <SolicitudCard
key={entry.request_id} key={entry.requestId}
data={entry} data={entry}
onAccept={() => handleAccept(entry)} onAccept={() => handleAccept(entry)}
onReject={() => handleReject(entry)} onReject={() => handleReject(entry)}

View File

@@ -1,6 +1,9 @@
'use strict'; 'use strict';
const CONSTANTS = { const CONSTANTS = {
// Service
SERVICE_ID: 1,
// Roles // Roles
ROLE_USER: 0, ROLE_USER: 0,
ROLE_ADMIN: 1, ROLE_ADMIN: 1,