Ongoing adaptation to new backend structure
This commit is contained in:
@@ -1,77 +1,72 @@
|
||||
{
|
||||
"apiConfig": {
|
||||
"baseUrl": "http://api.huertos.local",
|
||||
"coreUrl": "http://api.miarma.local",
|
||||
"baseUrl": "http://localhost:8081/v2/huertos",
|
||||
"coreUrl": "http://localhost:8080/v2/core",
|
||||
"endpoints": {
|
||||
"auth": {
|
||||
"login": "/v1/login",
|
||||
"validateToken": "/auth/v1/validate-token",
|
||||
"refreshToken": "/auth/v1/refresh-token",
|
||||
"changePassword": "/auth/v1/change-password",
|
||||
"loginValidate": "/auth/v1/login/validate"
|
||||
"login": "/auth/login",
|
||||
"refreshToken": "/auth/refresh",
|
||||
"changePassword": "/auth/change-password",
|
||||
"validateToken": "/auth/validate"
|
||||
},
|
||||
"members": {
|
||||
"all": "/raw/v1/members",
|
||||
"byId": "/raw/v1/members/:user_id",
|
||||
"profile": "/v1/members/profile",
|
||||
"byMemberNumber": "/v1/members/number/:member_number",
|
||||
"byPlotNumber": "/v1/members/plot/:plot_number",
|
||||
"byDni": "/v1/members/dni/:dni",
|
||||
"payments": "/v1/members/number/:member_number/incomes",
|
||||
"hasPaid": "/v1/members/number/:member_number/has-paid",
|
||||
"waitlist": "/v1/members/waitlist",
|
||||
"limitedWaitlist": "/v1/members/waitlist/limited",
|
||||
"lastMemberNumber": "/v1/members/latest-number",
|
||||
"hasCollaborator": "/v1/members/number/:member_number/has-collaborator",
|
||||
"hasCollaboratorRequest": "/v1/members/number/:member_number/has-collaborator-request",
|
||||
"hasGreenHouse": "/v1/members/number/:member_number/has-greenhouse",
|
||||
"hasGreenHouseRequest": "/v1/members/number/:member_number/has-greenhouse-request",
|
||||
"changeType": "/v1/members/number/:user_id/type",
|
||||
"changeStatus": "/v1/members/number/:user_id/status"
|
||||
"users": {
|
||||
"all": "/users",
|
||||
"byId": "/users/:userId",
|
||||
"me": "/users/me",
|
||||
"latestNumber": "/users/latest-number",
|
||||
"waitlist": "/users/waitlist",
|
||||
"waitlistLimited": "/users/waitlist/limited",
|
||||
"byMemberNumber": "/users/number/:memberNumber",
|
||||
"incomesPreview": "/users/number/:memberNumber/incomes",
|
||||
"hasPaid": "/users/number/:memberNumber/has-paid",
|
||||
"hasCollaborator": "/users/number/:memberNumber/has-collaborator",
|
||||
"hasGreenhouse": "/users/number/:memberNumber/has-greenhouse",
|
||||
"hasCollaboratorRequest": "/users/number/:memberNumber/has-collaborator-request",
|
||||
"hasGreenhouseRequest": "/users/number/:memberNumber/has-greenhouse-request",
|
||||
"byPlotNumber": "/users/plot/:plotNumber",
|
||||
"byDni": "/users/dni/:dni"
|
||||
},
|
||||
"incomes": {
|
||||
"all": "/raw/v1/incomes",
|
||||
"allWithNames": "/raw/v1/incomes-with-names",
|
||||
"byId": "/raw/v1/incomes/:income_id",
|
||||
"myIncomes": "/v1/incomes/my-incomes"
|
||||
"all": "/incomes",
|
||||
"withNames": "/incomes/with-names",
|
||||
"mine": "/incomes/mine",
|
||||
"byId": "/incomes/:incomeId"
|
||||
},
|
||||
"expenses": {
|
||||
"all": "/raw/v1/expenses",
|
||||
"byId": "/raw/v1/expenses/:expense_id"
|
||||
"all": "/expenses",
|
||||
"byId": "/expenses/:expenseId"
|
||||
},
|
||||
"balance": {
|
||||
"all": "/raw/v1/balance"
|
||||
"all": "/balance"
|
||||
},
|
||||
"announces": {
|
||||
"all": "/raw/v1/announces",
|
||||
"byId": "/raw/v1/announces/:announce_id"
|
||||
"announcements": {
|
||||
"all": "/announcements",
|
||||
"byId": "/announcements/:announceId"
|
||||
},
|
||||
"requests": {
|
||||
"all": "/raw/v1/requests",
|
||||
"byId": "/raw/v1/requests/:request_id",
|
||||
"allWithPreUsers": "/v1/requests-full",
|
||||
"byIdWithPreUser": "/v1/requests-full/:request_id",
|
||||
"countPending": "/v1/requests/count",
|
||||
"myRequests": "/v1/requests/my-requests",
|
||||
"accept": "/v1/requests/:request_id/accept",
|
||||
"reject": "/v1/requests/:request_id/reject"
|
||||
"all": "/requests",
|
||||
"waitlist": "/requests/waitlist",
|
||||
"byId": "/requests/:requestId",
|
||||
"count": "/requests/count",
|
||||
"mine": "/requests/mine",
|
||||
"full": "/requests/full",
|
||||
"fullById": "/requests/full/:requestId",
|
||||
"accept": "/requests/:requestId/accept",
|
||||
"reject": "/requests/:requestId/reject"
|
||||
},
|
||||
"pre_users": {
|
||||
"all": "/raw/v1/pre_users",
|
||||
"byId": "/raw/v1/pre_users/:pre_user_id",
|
||||
"validation": "/v1/pre_users/validate"
|
||||
"preUsers": {
|
||||
"all": "/pre-users",
|
||||
"byId": "/pre-users/:preUserId",
|
||||
"validate": "/pre-users/validate"
|
||||
},
|
||||
"files": {
|
||||
"all": "/raw/v1/files",
|
||||
"byId": "/raw/v1/files/:file_id",
|
||||
"upload": "/raw/v1/files/upload",
|
||||
"download": "/raw/v1/files/download/:file_id",
|
||||
"userFiles": "/raw/v1/files/myfiles"
|
||||
"all": "/files",
|
||||
"byId": "/files/:fileId"
|
||||
},
|
||||
"mail": {
|
||||
"all": "/v1/mails",
|
||||
"byIndex": "/v1/mails/:index",
|
||||
"send": "/v1/mails/send"
|
||||
"all": "/mails",
|
||||
"byIndex": "/mails/:index",
|
||||
"send": "/mails/send"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,77 +1,71 @@
|
||||
{
|
||||
"apiConfig": {
|
||||
"baseUrl": "https://api.huertosbellavista.es",
|
||||
"coreUrl": "https://api.miarma.net",
|
||||
"baseUrl": "https://api.miarma.net/v2/huertos",
|
||||
"coreUrl": "https://api.miarma.net/v2/core",
|
||||
"endpoints": {
|
||||
"auth": {
|
||||
"login": "/v1/login",
|
||||
"validateToken": "/auth/v1/validate-token",
|
||||
"refreshToken": "/auth/v1/refresh-token",
|
||||
"changePassword": "/auth/v1/change-password",
|
||||
"loginValidate": "/auth/v1/login/validate"
|
||||
"login": "/auth/login",
|
||||
"refreshToken": "/auth/refresh",
|
||||
"changePassword": "/auth/change-password"
|
||||
},
|
||||
"members": {
|
||||
"all": "/raw/v1/members",
|
||||
"byId": "/raw/v1/members/:user_id",
|
||||
"profile": "/v1/members/profile",
|
||||
"byMemberNumber": "/v1/members/number/:member_number",
|
||||
"byPlotNumber": "/v1/members/plot/:plot_number",
|
||||
"byDni": "/v1/members/dni/:dni",
|
||||
"payments": "/v1/members/number/:member_number/incomes",
|
||||
"hasPaid": "/v1/members/number/:member_number/has-paid",
|
||||
"waitlist": "/v1/members/waitlist",
|
||||
"limitedWaitlist": "/v1/members/waitlist/limited",
|
||||
"lastMemberNumber": "/v1/members/latest-number",
|
||||
"hasCollaborator": "/v1/members/number/:member_number/has-collaborator",
|
||||
"hasCollaboratorRequest": "/v1/members/number/:member_number/has-collaborator-request",
|
||||
"hasGreenHouse": "/v1/members/number/:member_number/has-greenhouse",
|
||||
"hasGreenHouseRequest": "/v1/members/number/:member_number/has-greenhouse-request",
|
||||
"changeType": "/v1/members/number/:user_id/type",
|
||||
"changeStatus": "/v1/members/number/:user_id/status"
|
||||
"users": {
|
||||
"all": "/users",
|
||||
"byId": "/users/:userId",
|
||||
"me": "/users/me",
|
||||
"latestNumber": "/users/latest-number",
|
||||
"waitlist": "/users/waitlist",
|
||||
"waitlistLimited": "/users/waitlist/limited",
|
||||
"byMemberNumber": "/users/number/:memberNumber",
|
||||
"incomesPreview": "/users/number/:memberNumber/incomes",
|
||||
"hasPaid": "/users/number/:memberNumber/has-paid",
|
||||
"hasCollaborator": "/users/number/:memberNumber/has-collaborator",
|
||||
"hasGreenhouse": "/users/number/:memberNumber/has-greenhouse",
|
||||
"hasCollaboratorRequest": "/users/number/:memberNumber/has-collaborator-request",
|
||||
"hasGreenhouseRequest": "/users/number/:memberNumber/has-greenhouse-request",
|
||||
"byPlotNumber": "/users/plot/:plotNumber",
|
||||
"byDni": "/users/dni/:dni"
|
||||
},
|
||||
"incomes": {
|
||||
"all": "/raw/v1/incomes",
|
||||
"allWithNames": "/raw/v1/incomes-with-names",
|
||||
"byId": "/raw/v1/incomes/:income_id",
|
||||
"myIncomes": "/v1/incomes/my-incomes"
|
||||
"all": "/incomes",
|
||||
"withNames": "/incomes/with-names",
|
||||
"mine": "/incomes/mine",
|
||||
"byId": "/incomes/:incomeId"
|
||||
},
|
||||
"expenses": {
|
||||
"all": "/raw/v1/expenses",
|
||||
"byId": "/raw/v1/expenses/:expense_id"
|
||||
"all": "/expenses",
|
||||
"byId": "/expenses/:expenseId"
|
||||
},
|
||||
"balance": {
|
||||
"all": "/raw/v1/balance"
|
||||
"all": "/balance"
|
||||
},
|
||||
"announces": {
|
||||
"all": "/raw/v1/announces",
|
||||
"byId": "/raw/v1/announces/:announce_id"
|
||||
"announcements": {
|
||||
"all": "/announcements",
|
||||
"byId": "/announcements/:announceId"
|
||||
},
|
||||
"requests": {
|
||||
"all": "/raw/v1/requests",
|
||||
"byId": "/raw/v1/requests/:request_id",
|
||||
"allWithPreUsers": "/v1/requests-full",
|
||||
"byIdWithPreUser": "/v1/requests-full/:request_id",
|
||||
"countPending": "/v1/requests/count",
|
||||
"myRequests": "/v1/requests/my-requests",
|
||||
"accept": "/v1/requests/:request_id/accept",
|
||||
"reject": "/v1/requests/:request_id/reject"
|
||||
"all": "/requests",
|
||||
"waitlist": "/requests/waitlist",
|
||||
"byId": "/requests/:requestId",
|
||||
"count": "/requests/count",
|
||||
"mine": "/requests/mine",
|
||||
"full": "/requests/full",
|
||||
"fullById": "/requests/full/:requestId",
|
||||
"accept": "/requests/:requestId/accept",
|
||||
"reject": "/requests/:requestId/reject"
|
||||
},
|
||||
"pre_users": {
|
||||
"all": "/raw/v1/pre_users",
|
||||
"byId": "/raw/v1/pre_users/:pre_user_id",
|
||||
"validation": "/v1/pre_users/validate"
|
||||
"preUsers": {
|
||||
"all": "/pre-users",
|
||||
"byId": "/pre-users/:preUserId",
|
||||
"validate": "/pre-users/validate"
|
||||
},
|
||||
"files": {
|
||||
"all": "/raw/v1/files",
|
||||
"byId": "/raw/v1/files/:file_id",
|
||||
"upload": "/raw/v1/files/upload",
|
||||
"download": "/raw/v1/files/download/:file_id",
|
||||
"userFiles": "/raw/v1/files/myfiles"
|
||||
"all": "/files",
|
||||
"byId": "/files/:fileId"
|
||||
},
|
||||
"mail": {
|
||||
"all": "/v1/mails",
|
||||
"byIndex": "/v1/mails/:index",
|
||||
"send": "/v1/mails/send"
|
||||
"all": "/mails",
|
||||
"byIndex": "/mails/:index",
|
||||
"send": "/mails/send"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,7 +45,7 @@ const AnuncioCard = ({ anuncio, isNew = false, onCreate, onUpdate, onDelete, onC
|
||||
const [formData, setFormData] = useState({
|
||||
body: anuncio.body || '',
|
||||
priority: anuncio.priority ?? 1,
|
||||
published_by: JSON.parse(localStorage.getItem('user'))?.user_id,
|
||||
publishedBy: JSON.parse(localStorage.getItem('identity'))?.user?.userId,
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
@@ -53,7 +53,7 @@ const AnuncioCard = ({ anuncio, isNew = false, onCreate, onUpdate, onDelete, onC
|
||||
setFormData({
|
||||
body: anuncio.body || '',
|
||||
priority: anuncio.priority ?? 1,
|
||||
published_by: JSON.parse(localStorage.getItem('user'))?.user_id,
|
||||
publishedBy: JSON.parse(localStorage.getItem('identity'))?.user?.userId,
|
||||
});
|
||||
}
|
||||
}, [anuncio, editMode]);
|
||||
@@ -63,7 +63,7 @@ const AnuncioCard = ({ anuncio, isNew = false, onCreate, onUpdate, onDelete, onC
|
||||
setEditMode(true);
|
||||
};
|
||||
|
||||
const handleDelete = () => typeof onDelete === 'function' && onDelete(anuncio.announce_id);
|
||||
const handleDelete = () => typeof onDelete === 'function' && onDelete(anuncio.announceId);
|
||||
|
||||
const handleCancel = () => {
|
||||
if (onClearError) onClearError();
|
||||
@@ -77,12 +77,12 @@ const AnuncioCard = ({ anuncio, isNew = false, onCreate, onUpdate, onDelete, onC
|
||||
formData.body = sanitizedBody;
|
||||
const updated = { ...anuncio, ...formData };
|
||||
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 { date, time } = formatDateTime(anuncio.created_at);
|
||||
const { date, time } = formatDateTime(anuncio.createdAt);
|
||||
const priorityInfo = PRIORITY_CONFIG[formData.priority] || PRIORITY_CONFIG[1];
|
||||
const isLongBody = formData.body.length > 300;
|
||||
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.Header className="d-flex justify-content-between align-items-center rounded-top-4 px-3 py-2">
|
||||
<div className="d-flex flex-column">
|
||||
<span className="fw-bold">📢 Anuncio #{anuncio.announce_id}</span>
|
||||
<span className="fw-bold">📢 Anuncio {!createMode ? ("#"+anuncio.idx) : ("")}</span>
|
||||
{!createMode ? (
|
||||
<small className="muted">
|
||||
Publicado el {date} a las {time} por{' '}
|
||||
<span className="fw-semibold">#{anuncio.published_by}</span>
|
||||
<span className="fw-semibold">{anuncio.publishedByName}</span>
|
||||
</small>
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
</div>
|
||||
{!createMode && !editMode && (
|
||||
<AnimatedDropdown
|
||||
|
||||
@@ -32,7 +32,6 @@ function App() {
|
||||
<NavBar />
|
||||
<Routes>
|
||||
<Route path="/" element={<Home />} />
|
||||
{/*
|
||||
<Route path="/lista-espera" element={<ListaEspera />} />
|
||||
<Route path="/login" element={<Login />} />
|
||||
<Route path="/gestion/socios" element={
|
||||
@@ -80,7 +79,6 @@ function App() {
|
||||
<Correo />
|
||||
</ProtectedRoute>
|
||||
} />
|
||||
*/}
|
||||
<Route path="/*" element={<Maintenance />} />
|
||||
</Routes>
|
||||
{routesWithFooter.includes(useLocation().pathname) ? <Footer /> : null}
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import { useAuth } from "../../hooks/useAuth.js";
|
||||
|
||||
const IfRole = ({ roles, children }) => {
|
||||
const { user, authStatus } = useAuth();
|
||||
const { identity, authStatus } = useAuth();
|
||||
|
||||
if (authStatus !== "authenticated") return null;
|
||||
|
||||
const userRole = user?.role;
|
||||
const userRole = identity?.metadata?.role;
|
||||
|
||||
return roles.includes(userRole) ? children : null;
|
||||
};
|
||||
|
||||
@@ -11,15 +11,15 @@ import CustomContainer from '../CustomContainer.jsx';
|
||||
import ContentWrapper from '../ContentWrapper.jsx';
|
||||
|
||||
import '../../css/LoginForm.css';
|
||||
import { CONSTANTS } from '../../util/constants.js';
|
||||
|
||||
const LoginForm = () => {
|
||||
const { login, error } = useContext(AuthContext);
|
||||
const navigate = useNavigate();
|
||||
|
||||
const [formState, setFormState] = useState({
|
||||
emailOrUserName: "",
|
||||
password: "",
|
||||
keepLoggedIn: false
|
||||
username: "",
|
||||
password: ""
|
||||
});
|
||||
|
||||
const handleChange = (e) => {
|
||||
@@ -30,19 +30,12 @@ const LoginForm = () => {
|
||||
const handleSubmit = async (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
const isEmail = /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(formState.emailOrUserName);
|
||||
|
||||
const loginBody = {
|
||||
username: formState.username,
|
||||
password: formState.password,
|
||||
keepLoggedIn: Boolean(formState.keepLoggedIn),
|
||||
serviceId: CONSTANTS.SERVICE_ID
|
||||
};
|
||||
|
||||
if (isEmail) {
|
||||
loginBody.email = formState.emailOrUserName;
|
||||
} else {
|
||||
loginBody.userName = formState.emailOrUserName;
|
||||
}
|
||||
|
||||
try {
|
||||
await login(loginBody);
|
||||
navigate("/");
|
||||
@@ -63,15 +56,15 @@ const LoginForm = () => {
|
||||
label={
|
||||
<>
|
||||
<FontAwesomeIcon icon={faUser} className="me-2" />
|
||||
Usuario o Email
|
||||
Usuario
|
||||
</>
|
||||
}
|
||||
>
|
||||
<Form.Control
|
||||
type="text"
|
||||
placeholder=""
|
||||
name="emailOrUserName"
|
||||
value={formState.emailOrUserName}
|
||||
name="username"
|
||||
value={formState.username}
|
||||
onChange={handleChange}
|
||||
className="rounded-4"
|
||||
/>
|
||||
@@ -83,7 +76,7 @@ const LoginForm = () => {
|
||||
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
|
||||
type="checkbox"
|
||||
name="keepLoggedIn"
|
||||
@@ -92,10 +85,10 @@ const LoginForm = () => {
|
||||
value={formState.keepLoggedIn}
|
||||
onChange={(e) => { formState.keepLoggedIn = e.target.checked; setFormState({ ...formState }) }}
|
||||
/>
|
||||
{/*<Link disabled to="#" className="muted">
|
||||
<Link disabled to="#" className="muted">
|
||||
Olvidé mi contraseña
|
||||
</Link>*/}
|
||||
</div>
|
||||
</Link>
|
||||
</div>*/}
|
||||
</div>
|
||||
|
||||
{error && (
|
||||
|
||||
@@ -6,10 +6,10 @@ import { faSpinner } from "@fortawesome/free-solid-svg-icons";
|
||||
const ProtectedRoute = ({ minimumRoles, children }) => {
|
||||
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 === "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 />;
|
||||
}
|
||||
return children;
|
||||
|
||||
@@ -67,17 +67,17 @@ const formatCurrency = (value) =>
|
||||
|
||||
export const BalancePDF = ({ balance }) => {
|
||||
const {
|
||||
initial_bank,
|
||||
initial_cash,
|
||||
total_bank_expenses,
|
||||
total_cash_expenses,
|
||||
total_bank_incomes,
|
||||
total_cash_incomes,
|
||||
created_at
|
||||
initialBank,
|
||||
initialCash,
|
||||
totalBankExpenses,
|
||||
totalCashExpenses,
|
||||
totalBankIncomes,
|
||||
totalCashIncomes,
|
||||
createdAt
|
||||
} = balance;
|
||||
|
||||
const final_bank = initial_bank + total_bank_incomes - total_bank_expenses;
|
||||
const final_cash = initial_cash + total_cash_incomes - total_cash_expenses;
|
||||
const finalBank = initialBank + totalBankIncomes - totalBankExpenses;
|
||||
const finalCash = initialCash + totalCashIncomes - totalCashExpenses;
|
||||
|
||||
return (
|
||||
<Document>
|
||||
@@ -91,22 +91,22 @@ export const BalancePDF = ({ balance }) => {
|
||||
</View>
|
||||
|
||||
<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}>Ingresos</Text><Text style={styles.value}>{formatCurrency(total_bank_incomes)}</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}>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(totalBankIncomes)}</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>
|
||||
<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}>Ingresos</Text><Text style={styles.value}>{formatCurrency(total_cash_incomes)}</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}>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(totalCashIncomes)}</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>
|
||||
<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}>Caja</Text><Text style={styles.value}>{formatCurrency(final_cash)}</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}>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(finalCash)}</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 }]}>
|
||||
Ú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>
|
||||
</Page>
|
||||
</Document>
|
||||
|
||||
@@ -24,17 +24,17 @@ const BalanceReport = ({ balance }) => {
|
||||
const closePDFModal = () => setShowPDF(false);
|
||||
|
||||
const {
|
||||
initial_bank,
|
||||
initial_cash,
|
||||
total_bank_expenses,
|
||||
total_cash_expenses,
|
||||
total_bank_incomes,
|
||||
total_cash_incomes,
|
||||
created_at
|
||||
initialBank,
|
||||
initialCash,
|
||||
totalBankExpenses,
|
||||
totalCashExpenses,
|
||||
totalBankIncomes,
|
||||
totalCashIncomes,
|
||||
createdAt
|
||||
} = balance;
|
||||
|
||||
const final_bank = initial_bank + total_bank_incomes - total_bank_expenses;
|
||||
const final_cash = initial_cash + total_cash_incomes - total_cash_expenses;
|
||||
const finalBank = initialBank + totalBankIncomes - totalBankExpenses;
|
||||
const finalCash = initialCash + totalCashIncomes - totalCashExpenses;
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -56,20 +56,20 @@ const BalanceReport = ({ balance }) => {
|
||||
<Col md={6}>
|
||||
<div className="balance-box">
|
||||
<h4><FontAwesomeIcon icon={faPiggyBank} className="me-2" />Banco</h4>
|
||||
<p>Saldo inicial: <span className="balance-value">{formatCurrency(initial_bank)}</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={faArrowDown} className="me-1 text-danger" />Gastos: <span className="balance-value">{formatCurrency(total_bank_expenses)}</span></p>
|
||||
<p className="fw-bold mt-3">💰 Saldo final: {formatCurrency(final_bank)}</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(totalBankIncomes)}</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(finalBank)}</p>
|
||||
</div>
|
||||
</Col>
|
||||
|
||||
<Col md={6}>
|
||||
<div className="balance-box">
|
||||
<h4><FontAwesomeIcon icon={faCoins} className="me-2" />Caja</h4>
|
||||
<p>Saldo inicial: <span className="balance-value">{formatCurrency(initial_cash)}</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={faArrowDown} className="me-1 text-danger" />Gastos: <span className="balance-value">{formatCurrency(total_cash_expenses)}</span></p>
|
||||
<p className="fw-bold mt-3">💵 Saldo final: {formatCurrency(final_cash)}</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(totalCashIncomes)}</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(finalCash)}</p>
|
||||
</div>
|
||||
</Col>
|
||||
</Row>
|
||||
@@ -77,7 +77,7 @@ const BalanceReport = ({ balance }) => {
|
||||
<Row className="mt-4">
|
||||
<Col className="text-end balance-timestamp">
|
||||
<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>
|
||||
</Row>
|
||||
</Card>
|
||||
|
||||
@@ -25,19 +25,19 @@ const File = ({ file, onDelete }) => {
|
||||
return (
|
||||
<Card
|
||||
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">
|
||||
<img
|
||||
src={getIcon(file.mime_type)}
|
||||
alt={file.file_name}
|
||||
src={getIcon(file.mimeType)}
|
||||
alt={file.fileName}
|
||||
className="img-fluid mb-2"
|
||||
/>
|
||||
<OverlayTrigger
|
||||
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>
|
||||
</Card.Body>
|
||||
|
||||
|
||||
@@ -49,7 +49,7 @@ const GastoCard = ({ gasto, isNew = false, onCreate, onUpdate, onDelete, onCance
|
||||
supplier: gasto.supplier || '',
|
||||
invoice: gasto.invoice || '',
|
||||
type: gasto.type ?? 0,
|
||||
created_at: gasto.created_at?.slice(0, 16) || (isNew ? getNowAsLocalDatetime() : ''),
|
||||
createdAt: gasto.createdAt?.slice(0, 16) || (isNew ? getNowAsLocalDatetime() : ''),
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
@@ -60,7 +60,7 @@ const GastoCard = ({ gasto, isNew = false, onCreate, onUpdate, onDelete, onCance
|
||||
supplier: gasto.supplier || '',
|
||||
invoice: gasto.invoice || '',
|
||||
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
|
||||
@@ -68,7 +68,7 @@ const GastoCard = ({ gasto, isNew = false, onCreate, onUpdate, onDelete, onCance
|
||||
|
||||
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 = () => {
|
||||
if (onClearError) onClearError();
|
||||
@@ -80,7 +80,7 @@ const GastoCard = ({ gasto, isNew = false, onCreate, onUpdate, onDelete, onCance
|
||||
if (onClearError) onClearError();
|
||||
const newExpense = { ...gasto, ...formData };
|
||||
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 (
|
||||
@@ -102,13 +102,13 @@ const GastoCard = ({ gasto, isNew = false, onCreate, onUpdate, onDelete, onCance
|
||||
<small>
|
||||
{editMode ? (
|
||||
<SpanishDateTimePicker
|
||||
selected={new Date(formData.created_at)}
|
||||
selected={new Date(formData.createdAt)}
|
||||
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>
|
||||
</div>
|
||||
|
||||
@@ -104,7 +104,7 @@ export const GastosPDF = ({ gastos }) => (
|
||||
{ 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: 2 }]}>{gasto.amount.toFixed(2)} €</Text>
|
||||
<Text style={[styles.cell, { flex: 3 }]}>{gasto.supplier}</Text>
|
||||
|
||||
@@ -63,8 +63,8 @@ const IngresoCard = ({
|
||||
amount: income.amount || 0,
|
||||
type: income.type ?? CONSTANTS.PAYMENT_TYPE_CASH,
|
||||
frequency: income.frequency ?? CONSTANTS.PAYMENT_FREQUENCY_YEARLY,
|
||||
member_number: income.member_number,
|
||||
created_at: income.created_at?.slice(0, 16) || (isNew ? getNowAsLocalDatetime() : ''),
|
||||
memberNumber: income.memberNumber,
|
||||
createdAt: income.createdAt?.slice(0, 16) || (isNew ? getNowAsLocalDatetime() : ''),
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
@@ -74,9 +74,9 @@ const IngresoCard = ({
|
||||
amount: income.amount || 0,
|
||||
type: income.type ?? CONSTANTS.PAYMENT_TYPE_CASH,
|
||||
frequency: income.frequency ?? CONSTANTS.PAYMENT_FREQUENCY_YEARLY,
|
||||
display_name: income.display_name,
|
||||
member_number: income.member_number,
|
||||
created_at: income.created_at?.slice(0, 16) || (isNew ? getNowAsLocalDatetime() : ''),
|
||||
displayName: income.displayName,
|
||||
memberNumber: income.memberNumber,
|
||||
createdAt: income.createdAt?.slice(0, 16) || (isNew ? getNowAsLocalDatetime() : ''),
|
||||
});
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
@@ -95,14 +95,14 @@ const IngresoCard = ({
|
||||
if (onClearError) onClearError();
|
||||
const newIncome = { ...income, ...formData };
|
||||
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(
|
||||
new Map(members.map(item => [item.member_number, item])).values()
|
||||
).sort((a, b) => a.member_number - b.member_number);
|
||||
new Map(members.map(item => [item.memberNumber, item])).values()
|
||||
).sort((a, b) => a.memberNumber - b.memberNumber);
|
||||
|
||||
return (
|
||||
<MotionCard className={`ingreso-card shadow-sm rounded-4 border-0 h-100 ${className}`}>
|
||||
@@ -125,13 +125,13 @@ const IngresoCard = ({
|
||||
<small>
|
||||
{editMode ? (
|
||||
<SpanishDateTimePicker
|
||||
selected={new Date(formData.created_at)}
|
||||
selected={new Date(formData.createdAt)}
|
||||
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>
|
||||
</div>
|
||||
@@ -167,13 +167,13 @@ const IngresoCard = ({
|
||||
<Form.Select
|
||||
className="themed-input"
|
||||
size="sm"
|
||||
value={formData.member_number}
|
||||
onChange={(e) => handleChange('member_number', parseInt(e.target.value))}
|
||||
value={formData.memberNumber}
|
||||
onChange={(e) => handleChange('memberNumber', parseInt(e.target.value))}
|
||||
style={{ maxWidth: '300px', display: 'inline-block' }}
|
||||
>
|
||||
{uniqueMembers.map((m) => (
|
||||
<option key={m.member_number} value={m.member_number}>
|
||||
{`${m.display_name} (${m.member_number})`}
|
||||
<option key={m.memberNumber} value={m.memberNumber}>
|
||||
{`${m.displayName} (${m.memberNumber})`}
|
||||
</option>
|
||||
))}
|
||||
</Form.Select>
|
||||
@@ -187,24 +187,24 @@ const IngresoCard = ({
|
||||
disabled
|
||||
size="sm"
|
||||
type="text"
|
||||
value={`${formData.display_name || 'Socio'} (${formData.member_number})`}
|
||||
value={`${formData.displayName || 'Socio'} (${formData.memberNumber})`}
|
||||
style={{ maxWidth: '300px', display: 'inline-block' }}
|
||||
/>
|
||||
</OverlayTrigger>
|
||||
) : (
|
||||
formData.display_name ? (
|
||||
formData.displayName ? (
|
||||
<>
|
||||
<OverlayTrigger
|
||||
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' }}>
|
||||
{formData.display_name}
|
||||
{formData.displayName}
|
||||
</span>
|
||||
</OverlayTrigger>
|
||||
({formData.member_number})
|
||||
({formData.memberNumber})
|
||||
</>
|
||||
) : formData.member_number
|
||||
) : formData.memberNumber
|
||||
)}
|
||||
</Card.Text>
|
||||
|
||||
|
||||
@@ -105,12 +105,12 @@ export const IngresosPDF = ({ ingresos }) => (
|
||||
{ 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: 1 }]}>{ing.amount.toFixed(2)} €</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: 2 }]}>{parseDate(ing.created_at)}</Text>
|
||||
<Text style={[styles.cell, { flex: 2 }]}>{parseDate(ing.createdAt)}</Text>
|
||||
</View>
|
||||
))}
|
||||
</Page>
|
||||
|
||||
@@ -27,7 +27,7 @@ import AnimatedDropdown from '../AnimatedDropdown.jsx';
|
||||
import { CONSTANTS } from '../../util/constants.js';
|
||||
|
||||
const NavBar = () => {
|
||||
const { user, logout } = useAuth();
|
||||
const { identity, logout } = useAuth();
|
||||
const [showingUserDropdown, setShowingUserDropdown] = useState(false);
|
||||
const [expanded, setExpanded] = useState(false);
|
||||
const [isLg, setIsLg] = useState(window.innerWidth >= 992);
|
||||
@@ -138,7 +138,7 @@ const NavBar = () => {
|
||||
onToggle={(isOpen) => setShowingUserDropdown(isOpen)}
|
||||
trigger={
|
||||
<Link className="nav-link dropdown-toggle fw-bold">
|
||||
@{user?.user_name}
|
||||
@{identity?.account?.username}
|
||||
</Link>
|
||||
}
|
||||
>
|
||||
|
||||
@@ -45,16 +45,16 @@ const renderDateField = (label, icon, dateValue, editMode, fieldKey, handleChang
|
||||
};
|
||||
|
||||
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
|
||||
if (!editMode && !created_at && !assigned_at && !deactivated_at) return null;
|
||||
if (!editMode && !createdAt && !assignedAt && !deactivatedAt) return null;
|
||||
|
||||
return (
|
||||
<ListGroup className="mt-2 border-1 rounded-3 shadow-sm">
|
||||
{renderDateField("ALTA", faCalendar, created_at, editMode, "created_at", handleChange)}
|
||||
{renderDateField("ENTREGA", faCalendar, assigned_at, editMode, "assigned_at", handleChange)}
|
||||
{renderDateField("BAJA", faCalendar, deactivated_at, editMode, "deactivated_at", handleChange)}
|
||||
{renderDateField("ALTA", faCalendar, createdAt, editMode, "createdAt", handleChange)}
|
||||
{renderDateField("ENTREGA", faCalendar, assignedAt, editMode, "assignedAt", handleChange)}
|
||||
{renderDateField("BAJA", faCalendar, deactivatedAt, editMode, "deactivatedAt", handleChange)}
|
||||
</ListGroup>
|
||||
);
|
||||
};
|
||||
@@ -91,7 +91,7 @@ const getPFP = (tipo) => {
|
||||
|
||||
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 [editMode, setEditMode] = useState(isNew);
|
||||
const [showPassword, setShowPassword] = useState(false);
|
||||
@@ -99,60 +99,59 @@ const SocioCard = ({ socio, isNew = false, onCreate, onUpdate, onDelete, onCance
|
||||
const { getData } = useDataContext();
|
||||
|
||||
const [formData, setFormData] = useState({
|
||||
display_name: socio.display_name,
|
||||
user_name: socio.user_name,
|
||||
email: socio.email || '',
|
||||
dni: socio.dni,
|
||||
phone: socio.phone,
|
||||
member_number: socio.member_number || latestNumber,
|
||||
plot_number: socio.plot_number,
|
||||
notes: socio.notes || '',
|
||||
status: socio.status,
|
||||
type: socio.type,
|
||||
created_at: socio.created_at?.slice(0, 16) || (isNew ? getNowAsLocalDatetime() : ''),
|
||||
assigned_at: socio.assigned_at?.slice(0, 16) || undefined,
|
||||
deactivated_at: socio.deactivated_at?.slice(0, 16) || undefined,
|
||||
global_role: 0,
|
||||
displayName: identity?.user.displayName,
|
||||
userName: identity?.account.username,
|
||||
email: identity?.account.email || '',
|
||||
dni: identity?.metadata.dni,
|
||||
phone: identity?.metadata.phone,
|
||||
memberNumber: identity?.metadata.memberNumber || latestNumber,
|
||||
plotNumber: identity?.metadata.plotNumber,
|
||||
notes: identity?.metadata.notes || '',
|
||||
status: identity?.account.status,
|
||||
type: identity?.metadata.type,
|
||||
createdAt: identity?.metadata.createdAt?.slice(0, 16) || (isNew ? getNowAsLocalDatetime() : ''),
|
||||
assignedAt: identity?.metadata.assignedAt?.slice(0, 16) || undefined,
|
||||
deactivatedAt: identity?.metadata.deactivatedAt?.slice(0, 16) || undefined,
|
||||
globalRole: 0,
|
||||
password: createMode && !editMode ? generateSecurePassword() : null,
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (!editMode) {
|
||||
setFormData({
|
||||
display_name: socio.display_name,
|
||||
user_name: socio.user_name,
|
||||
email: socio.email || '',
|
||||
dni: socio.dni,
|
||||
phone: socio.phone,
|
||||
member_number: socio.member_number,
|
||||
plot_number: socio.plot_number,
|
||||
notes: socio.notes || '',
|
||||
status: socio.status,
|
||||
type: socio.type,
|
||||
created_at: socio.created_at?.slice(0, 16) || (isNew ? getNowAsLocalDatetime() : ''),
|
||||
assigned_at: socio.assigned_at?.slice(0, 16) || undefined,
|
||||
deactivated_at: socio.deactivated_at?.slice(0, 16) || undefined,
|
||||
global_role: 0,
|
||||
displayName: identity.user.displayName,
|
||||
userName: identity.account.username,
|
||||
email: identity.account.email || '',
|
||||
dni: identity.metadata.dni,
|
||||
phone: identity.metadata.phone,
|
||||
memberNumber: identity.metadata.memberNumber,
|
||||
plotNumber: identity.metadata.plotNumber,
|
||||
notes: identity.metadata.notes || '',
|
||||
status: identity.account.status,
|
||||
type: identity.metadat.type,
|
||||
createdAt: identity.metadata.createdAt?.slice(0, 16) || (isNew ? getNowAsLocalDatetime() : ''),
|
||||
assignedAt: identity.metadata.assignedAt?.slice(0, 16) || undefined,
|
||||
deactivatedAt: identity.metadata.deactivatedAt?.slice(0, 16) || undefined,
|
||||
globalRole: 0,
|
||||
password: createMode ? generateSecurePassword() : ''
|
||||
});
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [socio, editMode]);
|
||||
}, [identity, editMode]);
|
||||
|
||||
useEffect(() => {
|
||||
const fetchLastNumber = async () => {
|
||||
try {
|
||||
if (!(createMode || editMode)) return;
|
||||
|
||||
const { data, error } = await getData("https://api.huertosbellavista.es/v1/members/latest-number");
|
||||
if (error) throw new Error(error);
|
||||
const latestNumber = await getData("http://localhost:8081/v2/huertos/users/latest-number");
|
||||
|
||||
const nuevoNumero = data.lastMemberNumber + 1;
|
||||
const nuevoNumero = latestNumber + 1;
|
||||
setLatestNumber(nuevoNumero);
|
||||
|
||||
setFormData(prev => ({
|
||||
...prev,
|
||||
member_number: prev.member_number || nuevoNumero
|
||||
memberNumber: prev.memberNumber || nuevoNumero
|
||||
}));
|
||||
} catch (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);
|
||||
};
|
||||
|
||||
const handleDelete = () => typeof onDelete === "function" && onDelete(socio.user_id);
|
||||
const handleDelete = () => typeof onDelete === "function" && onDelete(identity.user.userId);
|
||||
|
||||
const handleCancel = () => {
|
||||
if (onClearError) onClearError();
|
||||
@@ -177,16 +176,16 @@ const SocioCard = ({ socio, isNew = false, onCreate, onUpdate, onDelete, onCance
|
||||
|
||||
const handleSave = () => {
|
||||
if (onClearError) onClearError();
|
||||
const newSocio = { ...socio, ...formData };
|
||||
const newSocio = { ...identity, ...formData };
|
||||
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) => {
|
||||
if (["member_number"].includes(field)) {
|
||||
if (["memberNumber"].includes(field)) {
|
||||
value = value === "" ? latestNumber : parseInt(value);
|
||||
}
|
||||
if (field === "display_name") {
|
||||
if (field === "displayName") {
|
||||
value = value.toUpperCase();
|
||||
}
|
||||
if (field === "dni") {
|
||||
@@ -196,7 +195,7 @@ const SocioCard = ({ socio, isNew = false, onCreate, onUpdate, onDelete, onCance
|
||||
};
|
||||
|
||||
const handleViewIncomes = () => {
|
||||
onViewIncomes(socio.user_id);
|
||||
onViewIncomes(identity.user.userId);
|
||||
}
|
||||
|
||||
return (
|
||||
@@ -206,7 +205,7 @@ const SocioCard = ({ socio, isNew = false, onCreate, onUpdate, onDelete, onCance
|
||||
{editMode ? (
|
||||
<TipoSocioDropdown value={formData.type} onChange={(val) => handleChange('type', val)} />
|
||||
) : (
|
||||
positionIfWaitlist && socio.type === 0 ? (
|
||||
positionIfWaitlist && identity.metadata.type === 0 ? (
|
||||
<OverlayTrigger
|
||||
placement="top"
|
||||
overlay={
|
||||
@@ -225,8 +224,8 @@ const SocioCard = ({ socio, isNew = false, onCreate, onUpdate, onDelete, onCance
|
||||
<div className='d-flex flex-column gap-1'>
|
||||
<Card.Title className="m-0">
|
||||
{editMode ? (
|
||||
<Form.Control className="themed-input" size="sm" value={formData.display_name} onChange={(e) => handleChange('display_name', e.target.value)} style={{ maxWidth: '220px' }} />
|
||||
) : formData.display_name}
|
||||
<Form.Control className="themed-input" size="sm" value={formData.displayName} onChange={(e) => handleChange('displayName', e.target.value)} style={{ maxWidth: '220px' }} />
|
||||
) : formData.displayName}
|
||||
</Card.Title>
|
||||
{editMode ? (
|
||||
<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: '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'
|
||||
}, {
|
||||
|
||||
@@ -107,13 +107,13 @@ export const SociosPDF = ({ socios }) => (
|
||||
{ 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?.plot_number}</Text>
|
||||
<Text style={[styles.cell, { flex: 3 }]}>{socio?.display_name}</Text>
|
||||
<Text style={[styles.cell, { flex: 0.2 }]}>{socio?.memberNumber}</Text>
|
||||
<Text style={[styles.cell, { flex: 0.2 }]}>{socio?.plotNumber}</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?.phone}</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 }]}>
|
||||
{(() => {
|
||||
switch (socio?.type) {
|
||||
|
||||
@@ -3,22 +3,25 @@ import { Form, Row, Col, Button } from 'react-bootstrap';
|
||||
import { useDataContext } from '../../hooks/useDataContext';
|
||||
import { Alert } from 'react-bootstrap';
|
||||
import PropTypes from 'prop-types';
|
||||
import { generateSecurePassword } from '../../util/passwordGenerator';
|
||||
|
||||
|
||||
const PreUserForm = ({ onSubmit, userType, plotNumber, errors = {} }) => {
|
||||
const { getData } = useDataContext();
|
||||
const fetchedOnce = useRef(false);
|
||||
|
||||
const [form, setForm] = useState({
|
||||
user_name: '',
|
||||
display_name: '',
|
||||
userName: '',
|
||||
password: generateSecurePassword(8),
|
||||
displayName: '',
|
||||
dni: '',
|
||||
phone: '',
|
||||
email: '',
|
||||
address: '',
|
||||
zip_code: '',
|
||||
zipCode: '',
|
||||
city: '',
|
||||
member_number: '',
|
||||
plot_number: plotNumber,
|
||||
memberNumber: '',
|
||||
plotNumber: plotNumber,
|
||||
type: userType,
|
||||
status: 1,
|
||||
role: 0
|
||||
@@ -30,12 +33,10 @@ const PreUserForm = ({ onSubmit, userType, plotNumber, errors = {} }) => {
|
||||
fetchedOnce.current = true;
|
||||
|
||||
try {
|
||||
const { data, error } = await getData("https://api.huertosbellavista.es/v1/members/latest-number");
|
||||
if (error) throw new Error(error);
|
||||
|
||||
const latestNumber = await getData("http://localhost:8081/v2/huertos/users/latest-number");
|
||||
setForm((prev) => ({
|
||||
...prev,
|
||||
member_number: data.lastMemberNumber + 1
|
||||
memberNumber: latestNumber + 1
|
||||
}));
|
||||
} catch (err) {
|
||||
console.error("Error al obtener el número de socio:", err);
|
||||
@@ -47,21 +48,21 @@ const PreUserForm = ({ onSubmit, userType, plotNumber, errors = {} }) => {
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const trimmedName = form.display_name?.trim() ?? "";
|
||||
const trimmedName = form.displayName?.trim() ?? "";
|
||||
|
||||
const nuevoUsername = trimmedName
|
||||
? trimmedName.split(' ')[0].toLowerCase() : "";
|
||||
|
||||
if (form.user_name !== nuevoUsername) {
|
||||
setForm(prev => ({ ...prev, user_name: nuevoUsername }));
|
||||
if (form.userName !== 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 { name, value, type } = e.target;
|
||||
let updatedValue = value;
|
||||
|
||||
if (name === 'display_name' || name === 'dni') {
|
||||
if (name === 'displayName' || name === 'dni') {
|
||||
updatedValue = value.toUpperCase();
|
||||
}
|
||||
|
||||
@@ -84,13 +85,13 @@ const PreUserForm = ({ onSubmit, userType, plotNumber, errors = {} }) => {
|
||||
<Row className="gy-3">
|
||||
|
||||
{[
|
||||
{ label: 'Nombre completo', name: 'display_name', type: 'text', required: true },
|
||||
{ label: 'Nombre de usuario', name: 'user_name', type: 'text', required: true },
|
||||
{ label: 'Nombre completo', name: 'displayName', 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: 'Teléfono', name: 'phone', type: 'tel', required: true },
|
||||
{ label: 'Correo electrónico', name: 'email', type: 'email', required: true },
|
||||
{ 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' }
|
||||
].map(({ label, name, type, required, maxLength }) => (
|
||||
<Col md={4} key={name}>
|
||||
@@ -120,8 +121,8 @@ const PreUserForm = ({ onSubmit, userType, plotNumber, errors = {} }) => {
|
||||
className="shadow-sm"
|
||||
disabled
|
||||
type="number"
|
||||
name="member_number"
|
||||
value={form.member_number}
|
||||
name="memberNumber"
|
||||
value={form.memberNumber}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
</Form.Group>
|
||||
|
||||
@@ -35,15 +35,15 @@ const getPFP = (tipo) => {
|
||||
};
|
||||
|
||||
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:
|
||||
if (requested_by_name) {
|
||||
return `${requested_by_name} quiere darse de alta.`;
|
||||
} else if (request_status !== 1 && pre_display_name) {
|
||||
return `${pre_display_name} quiere darse de alta.`;
|
||||
} else if (request_status !== 1) {
|
||||
if (requestedByName) {
|
||||
return `${requestedByName} quiere darse de alta.`;
|
||||
} else if (status !== 1 && preDisplayName) {
|
||||
return `${preDisplayName} quiere darse de alta.`;
|
||||
} else if (status !== 1) {
|
||||
return `Alguien quiere darse de alta.`;
|
||||
} else {
|
||||
return `Se ha aceptado esta solicitud de alta.`;
|
||||
@@ -52,30 +52,30 @@ const renderDescripcionSolicitud = (data, onProfile) => {
|
||||
case 1:
|
||||
return onProfile
|
||||
? "Has solicitado darte de baja."
|
||||
: requested_by_name
|
||||
? `${requested_by_name} quiere darse de baja.`
|
||||
: request_status !== 1
|
||||
: requestedByName
|
||||
? `${requestedByName} quiere darse de baja.`
|
||||
: status !== 1
|
||||
? `Alguien quiere darse de baja.`
|
||||
: `Se ha aceptado esta solicitud de baja.`;
|
||||
|
||||
case 2:
|
||||
if (onProfile) {
|
||||
switch (request_status) {
|
||||
switch (status) {
|
||||
case 0: return "Has solicitado añadir un colaborador.";
|
||||
case 1: return "Tu solicitud de colaborador ha sido aceptada.";
|
||||
case 2: return "Tu solicitud de colaborador ha sido rechazada.";
|
||||
default: return "Solicitud de colaborador desconocida.";
|
||||
}
|
||||
} else {
|
||||
switch (request_status) {
|
||||
switch (status) {
|
||||
case 0:
|
||||
return requested_by_name
|
||||
? `${requested_by_name} quiere añadir a ${pre_display_name || "un colaborador"} como colaborador.`
|
||||
: `Alguien quiere añadir a ${pre_display_name || "un colaborador"} como colaborador.`;
|
||||
return requestedByName
|
||||
? `${requestedByName} quiere añadir a ${preDisplayName || "un colaborador"} como colaborador.`
|
||||
: `Alguien quiere añadir a ${preDisplayName || "un colaborador"} como colaborador.`;
|
||||
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:
|
||||
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:
|
||||
return "Solicitud de colaborador desconocida.";
|
||||
}
|
||||
@@ -84,27 +84,27 @@ const renderDescripcionSolicitud = (data, onProfile) => {
|
||||
case 3:
|
||||
return onProfile
|
||||
? "Has solicitado quitar tu colaborador."
|
||||
: requested_by_name
|
||||
? `${requested_by_name} quiere quitar su colaborador.`
|
||||
: request_status !== 1
|
||||
: requestedByName
|
||||
? `${requestedByName} quiere quitar su colaborador.`
|
||||
: status !== 1
|
||||
? `Alguien quiere quitar su colaborador.`
|
||||
: `Se ha aceptado esta solicitud de baja de colaborador.`;
|
||||
|
||||
case 4:
|
||||
return onProfile
|
||||
? "Has solicitado una parcela en el invernadero."
|
||||
: requested_by_name
|
||||
? `${requested_by_name} quiere una parcela en el invernadero.`
|
||||
: request_status !== 1
|
||||
: requestedByName
|
||||
? `${requestedByName} quiere una parcela en el invernadero.`
|
||||
: status !== 1
|
||||
? `Alguien quiere una parcela en el invernadero.`
|
||||
: `Se ha aceptado esta solicitud de parcela en el invernadero.`;
|
||||
|
||||
case 5:
|
||||
return onProfile
|
||||
? "Has solicitado dejar tu parcela del invernadero."
|
||||
: requested_by_name
|
||||
? `${requested_by_name} quiere dejar su parcela del invernadero.`
|
||||
: request_status !== 1
|
||||
: requestedByName
|
||||
? `${requestedByName} quiere dejar su parcela del invernadero.`
|
||||
: status !== 1
|
||||
? `Alguien quiere dejar su parcela 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 handleDelete = () => typeof onDelete === "function" && onDelete(data.request_id);
|
||||
const handleDelete = () => typeof onDelete === "function" && onDelete(data.requestId);
|
||||
|
||||
return (
|
||||
<MotionCard className="solicitud-card shadow-sm rounded-4 h-100">
|
||||
<Card.Header className="rounded-top-4 d-flex justify-content-between 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>
|
||||
<Card.Title className="mb-0">
|
||||
Solicitud #{data.request_id} - {getTipoSolicitud(data.request_type)}
|
||||
Solicitud #{data.requestId} - {getTipoSolicitud(data.type)}
|
||||
</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>
|
||||
|
||||
@@ -147,7 +147,7 @@ const SolicitudCard = ({ data, onAccept, onReject, onDelete, editable = true, on
|
||||
<ListGroup variant="flush" className="border rounded-3 mb-3">
|
||||
<ListGroup.Item>
|
||||
<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>
|
||||
|
||||
@@ -157,24 +157,24 @@ const SolicitudCard = ({ data, onAccept, onReject, onDelete, editable = true, on
|
||||
</ListGroup.Item>
|
||||
</ListGroup>
|
||||
|
||||
{data.pre_display_name && (
|
||||
{data.preDisplayName && (
|
||||
<>
|
||||
<Card.Subtitle className="card-subtitle mt-3 mb-2">Datos del futuro socio</Card.Subtitle>
|
||||
<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={faIdCard} className="me-2" />DNI: <strong>{data.pre_dni}</strong></ListGroup.Item>
|
||||
<ListGroup.Item><FontAwesomeIcon icon={faPhone} className="me-2" />Teléfono: <strong>{data.pre_phone}</strong></ListGroup.Item>
|
||||
<ListGroup.Item><FontAwesomeIcon icon={faEnvelope} className="me-2" />Email: <strong>{data.pre_email}</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={faMapMarkerAlt} className="me-2" />Ciudad: <strong>{data.pre_city ?? 'NO'} ({data.pre_zip_code ?? 'NO'})</strong></ListGroup.Item>
|
||||
<ListGroup.Item><FontAwesomeIcon icon={faHashtag} className="me-2" />Nº socio: <strong>{data.pre_member_number ?? 'NO'}</strong> | Nº huerto: <strong>{data.pre_plot_number ?? '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={faUserShield} className="me-2" />Rol: <strong>{['Usuario', 'Admin', 'Desarrollador'][data.pre_role]}</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.preDni}</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.preEmail}</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.preCity ?? 'NO'} ({data.preZipCode ?? 'NO'})</strong></ListGroup.Item>
|
||||
<ListGroup.Item><FontAwesomeIcon icon={faHashtag} className="me-2" />Nº socio: <strong>{data.preMemberNumber ?? 'NO'}</strong> | Nº 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.preType]}</strong></ListGroup.Item>
|
||||
<ListGroup.Item><FontAwesomeIcon icon={faUserShield} className="me-2" />Rol: <strong>{['Usuario', 'Admin', 'Desarrollador'][data.preRole]}</strong></ListGroup.Item>
|
||||
</ListGroup>
|
||||
</>
|
||||
)}
|
||||
|
||||
{editable && data.request_status === 0 && (
|
||||
{editable && data.status === 0 && (
|
||||
<div className="d-flex justify-content-end gap-2 mt-3">
|
||||
<Button variant="danger" size="sm" onClick={() => onReject?.(data)}>Rechazar</Button>
|
||||
<Button variant="success" size="sm" onClick={() => onAccept?.(data)}>Aceptar</Button>
|
||||
|
||||
@@ -8,8 +8,12 @@ export const AuthProvider = ({ children }) => {
|
||||
const axios = createAxiosInstance();
|
||||
const { config } = useConfig();
|
||||
|
||||
const [user, setUser] = useState(() => JSON.parse(localStorage.getItem("user")) || null);
|
||||
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 [error, setError] = useState(null);
|
||||
|
||||
@@ -21,7 +25,7 @@ export const AuthProvider = ({ children }) => {
|
||||
return;
|
||||
}
|
||||
|
||||
const BASE_URL = config.apiConfig.baseUrl;
|
||||
const BASE_URL = config.apiConfig.coreUrl;
|
||||
const VALIDATE_URL = `${BASE_URL}${config.apiConfig.endpoints.auth.validateToken}`;
|
||||
|
||||
const checkAuth = async () => {
|
||||
@@ -29,6 +33,7 @@ export const AuthProvider = ({ children }) => {
|
||||
const res = await axios.get(VALIDATE_URL, {
|
||||
headers: { Authorization: `Bearer ${token}` },
|
||||
});
|
||||
|
||||
if (res.status === 200) {
|
||||
setAuthStatus("authenticated");
|
||||
} else {
|
||||
@@ -45,19 +50,26 @@ export const AuthProvider = ({ children }) => {
|
||||
|
||||
const login = async (formData) => {
|
||||
setError(null);
|
||||
|
||||
const BASE_URL = config.apiConfig.baseUrl;
|
||||
const LOGIN_URL = `${BASE_URL}${config.apiConfig.endpoints.auth.login}`;
|
||||
|
||||
try {
|
||||
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("user", JSON.stringify(member));
|
||||
localStorage.setItem("tokenTime", tokenTime);
|
||||
localStorage.setItem("identity", JSON.stringify(identity));
|
||||
|
||||
setToken(token);
|
||||
setUser(member);
|
||||
setIdentity(identity);
|
||||
setAuthStatus("authenticated");
|
||||
} catch (err) {
|
||||
console.error("Error al iniciar sesión:", err);
|
||||
@@ -70,7 +82,7 @@ export const AuthProvider = ({ children }) => {
|
||||
if (status === 400) {
|
||||
message = "Usuario o contraseña incorrectos.";
|
||||
} else if (status === 403) {
|
||||
message = "Tu cuenta está inactiva o ha sido suspendida.";
|
||||
message = "Tu cuenta está inactiva o suspendida.";
|
||||
} else if (status === 404) {
|
||||
message = "Usuario no encontrado.";
|
||||
} else if (data?.message) {
|
||||
@@ -84,14 +96,24 @@ export const AuthProvider = ({ children }) => {
|
||||
};
|
||||
|
||||
const logout = () => {
|
||||
localStorage.clear();
|
||||
setUser(null);
|
||||
localStorage.removeItem("token");
|
||||
localStorage.removeItem("identity");
|
||||
setIdentity(null);
|
||||
setToken(null);
|
||||
setAuthStatus("unauthenticated");
|
||||
};
|
||||
|
||||
return (
|
||||
<AuthContext.Provider value={{ user, token, authStatus, login, logout, error }}>
|
||||
<AuthContext.Provider
|
||||
value={{
|
||||
identity, // { user, account, metadata }
|
||||
token,
|
||||
authStatus,
|
||||
login,
|
||||
logout,
|
||||
error,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</AuthContext.Provider>
|
||||
);
|
||||
|
||||
@@ -30,9 +30,9 @@ export const useData = (config) => {
|
||||
headers: getAuthHeaders(),
|
||||
params: current.params,
|
||||
});
|
||||
setData(response.data.data);
|
||||
setData(response.data);
|
||||
} catch (err) {
|
||||
setError(err.response?.data?.message || err.message);
|
||||
setError(err.response?.data);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
@@ -45,18 +45,11 @@ export const useData = (config) => {
|
||||
}, [config, fetchData]);
|
||||
|
||||
const getData = async (url, params = {}) => {
|
||||
try {
|
||||
const response = await axios.get(url, {
|
||||
headers: getAuthHeaders(),
|
||||
params,
|
||||
});
|
||||
return { data: response.data.data, error: null };
|
||||
} catch (err) {
|
||||
return {
|
||||
data: null,
|
||||
error: err.response?.data?.message || err.message,
|
||||
};
|
||||
}
|
||||
return response.data;
|
||||
};
|
||||
|
||||
const postData = async (endpoint, payload) => {
|
||||
@@ -66,28 +59,24 @@ export const useData = (config) => {
|
||||
};
|
||||
const response = await axios.post(endpoint, payload, { headers });
|
||||
await fetchData();
|
||||
return response.data.data;
|
||||
return response.data;
|
||||
};
|
||||
|
||||
const postDataValidated = async (endpoint, payload) => {
|
||||
try {
|
||||
const headers = {
|
||||
const response = await axios.post(endpoint, payload, {
|
||||
headers: {
|
||||
Authorization: `Bearer ${localStorage.getItem("token")}`,
|
||||
...(payload instanceof FormData ? {} : { "Content-Type": "application/json" }),
|
||||
};
|
||||
const response = await axios.post(endpoint, payload, { headers });
|
||||
return { data: response.data.data, errors: null };
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
});
|
||||
|
||||
return { ok: true };
|
||||
} catch (err) {
|
||||
const raw = err.response?.data?.message;
|
||||
let parsed = {};
|
||||
|
||||
try {
|
||||
parsed = JSON.parse(raw);
|
||||
} catch {
|
||||
return { data: null, errors: { general: raw || err.message } };
|
||||
}
|
||||
|
||||
return { data: null, errors: parsed };
|
||||
return {
|
||||
ok: false,
|
||||
errors: err.response?.data?.errors || {}
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
@@ -96,7 +85,7 @@ export const useData = (config) => {
|
||||
headers: getAuthHeaders(),
|
||||
});
|
||||
await fetchData();
|
||||
return response.data.data;
|
||||
return response.data;
|
||||
};
|
||||
|
||||
const deleteData = async (endpoint) => {
|
||||
@@ -104,7 +93,7 @@ export const useData = (config) => {
|
||||
headers: getAuthHeaders(),
|
||||
});
|
||||
await fetchData();
|
||||
return response.data.data;
|
||||
return response.data;
|
||||
};
|
||||
|
||||
const deleteDataWithBody = async (endpoint, payload) => {
|
||||
@@ -113,7 +102,7 @@ export const useData = (config) => {
|
||||
data: payload,
|
||||
});
|
||||
await fetchData();
|
||||
return response.data.data;
|
||||
return response.data;
|
||||
};
|
||||
|
||||
return {
|
||||
|
||||
@@ -12,7 +12,7 @@ const useRequestCount = () => {
|
||||
const fetchCount = async () => {
|
||||
try {
|
||||
const res = await axios.get(
|
||||
config.apiConfig.baseUrl + config.apiConfig.endpoints.requests.countPending,
|
||||
config.apiConfig.baseUrl + config.apiConfig.endpoints.requests.count,
|
||||
{
|
||||
headers: {
|
||||
Authorization: `Bearer ${localStorage.getItem('token')}`,
|
||||
@@ -20,7 +20,7 @@ const useRequestCount = () => {
|
||||
},
|
||||
}
|
||||
);
|
||||
setCount(res.data.data.count);
|
||||
setCount(res.data.count);
|
||||
} catch (err) {
|
||||
console.error('❌ Error al obtener el número de solicitudes:', err.message);
|
||||
}
|
||||
|
||||
@@ -25,9 +25,9 @@ const Anuncios = () => {
|
||||
if (configLoading) return <p><LoadingIcon /></p>;
|
||||
|
||||
const reqConfig = {
|
||||
baseUrl: `${config.apiConfig.baseUrl}${config.apiConfig.endpoints.announces.all}`,
|
||||
baseUrl: `${config.apiConfig.baseUrl}${config.apiConfig.endpoints.announcements.all}`,
|
||||
params: {
|
||||
_sort: 'created_at',
|
||||
_sort: 'createdAt',
|
||||
_order: 'desc',
|
||||
},
|
||||
};
|
||||
@@ -61,7 +61,7 @@ const AnunciosContent = ({ reqConfig }) => {
|
||||
(filters.baja && anuncio.priority === 0) ||
|
||||
(filters.media && anuncio.priority === 1) ||
|
||||
(filters.alta && anuncio.priority === 2);
|
||||
const createdAt = new Date(anuncio.created_at);
|
||||
const createdAt = new Date(anuncio.createdAt);
|
||||
const now = new Date();
|
||||
const matchesFecha =
|
||||
(filters.ultimos7 && (now - createdAt) / (1000 * 60 * 60 * 24) <= 7) ||
|
||||
@@ -74,7 +74,7 @@ const AnunciosContent = ({ reqConfig }) => {
|
||||
const normalized = term.toLowerCase();
|
||||
return (
|
||||
anuncio.body?.toLowerCase().includes(normalized) ||
|
||||
anuncio.published_by_name?.toLowerCase().includes(normalized)
|
||||
anuncio.publishedByName?.toLowerCase().includes(normalized)
|
||||
);
|
||||
},
|
||||
initialFilters: {
|
||||
@@ -90,10 +90,10 @@ const AnunciosContent = ({ reqConfig }) => {
|
||||
const handleCreate = () => {
|
||||
setCreatingAnuncio(true);
|
||||
setTempAnuncio({
|
||||
announce_id: null,
|
||||
body: 'Nuevo anuncio',
|
||||
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' });
|
||||
};
|
||||
@@ -162,12 +162,12 @@ const AnunciosContent = ({ reqConfig }) => {
|
||||
/>
|
||||
</EditorProvider>
|
||||
)}
|
||||
renderCard={(anuncio) => (
|
||||
renderCard={(anuncio, idx) => (
|
||||
<AnuncioCard
|
||||
key={anuncio.announce_id}
|
||||
anuncio={anuncio}
|
||||
key={anuncio.announceId}
|
||||
anuncio={{...anuncio, idx: idx}}
|
||||
onUpdate={(a, id) => handleEditSubmit(a, id)}
|
||||
onDelete={() => handleDelete(anuncio.announce_id)}
|
||||
onDelete={() => handleDelete(anuncio.announceId)}
|
||||
error={error}
|
||||
onClearError={() => setError(null)}
|
||||
/>
|
||||
|
||||
@@ -19,9 +19,8 @@ const Documentacion = () => {
|
||||
|
||||
const reqConfig = {
|
||||
baseUrl: config.apiConfig.coreUrl + config.apiConfig.endpoints.files.all,
|
||||
uploadUrl: config.apiConfig.coreUrl + config.apiConfig.endpoints.files.upload,
|
||||
params: {
|
||||
_sort: 'uploaded_at',
|
||||
_sort: 'uploadedAt',
|
||||
_order: 'desc'
|
||||
}
|
||||
};
|
||||
@@ -40,22 +39,22 @@ const DocumentacionContent = ({ reqConfig }) => {
|
||||
|
||||
const handleSelectFiles = async (files) => {
|
||||
const file = files[0];
|
||||
if (!file || !reqConfig?.uploadUrl) return;
|
||||
if (!file || !reqConfig?.baseUrl) return;
|
||||
|
||||
const file_name = file.name;
|
||||
const mime_type = file.type || "application/octet-stream";
|
||||
const uploaded_by = JSON.parse(localStorage.getItem("user"))?.user_id;
|
||||
const fileName = file.name;
|
||||
const mimeType = file.type || "application/octet-stream";
|
||||
const uploadedBy = JSON.parse(localStorage.getItem("identity"))?.user?.userId;
|
||||
const context = 1;
|
||||
|
||||
const formData = new FormData();
|
||||
formData.append("file", file);
|
||||
formData.append("file_name", file_name);
|
||||
formData.append("mime_type", mime_type);
|
||||
formData.append("uploaded_by", uploaded_by);
|
||||
formData.append("fileName", fileName);
|
||||
formData.append("mimeType", mimeType);
|
||||
formData.append("uploadedBy", uploadedBy);
|
||||
formData.append("context", context);
|
||||
|
||||
try {
|
||||
await postData(reqConfig.uploadUrl, formData);
|
||||
await postData(reqConfig.baseUrl, formData);
|
||||
fileUploadRef.current?.resetSelectedFiles();
|
||||
} catch (err) {
|
||||
console.error("Error al subir archivo:", err);
|
||||
@@ -100,8 +99,8 @@ const DocumentacionContent = ({ reqConfig }) => {
|
||||
variant="danger"
|
||||
onClick={async () => {
|
||||
try {
|
||||
await deleteDataWithBody(`${reqConfig.baseUrl}/${deleteTarget.file_id}`, {
|
||||
file_path: deleteTarget.file_path
|
||||
await deleteDataWithBody(`${reqConfig.baseUrl}/${deleteTarget.fileId}`, {
|
||||
filePath: deleteTarget.filePath
|
||||
});
|
||||
setDeleteTarget(null);
|
||||
} catch (err) {
|
||||
|
||||
@@ -30,7 +30,7 @@ const Gastos = () => {
|
||||
const reqConfig = {
|
||||
baseUrl: `${config.apiConfig.baseUrl}${config.apiConfig.endpoints.expenses.all}`,
|
||||
params: {
|
||||
_sort: 'created_at',
|
||||
_sort: 'createdAt',
|
||||
_order: 'desc',
|
||||
},
|
||||
};
|
||||
@@ -84,7 +84,7 @@ const GastosContent = ({ reqConfig }) => {
|
||||
const handleCreate = () => {
|
||||
setCreatingGasto(true);
|
||||
setTempGasto({
|
||||
expense_id: null,
|
||||
expenseId: null,
|
||||
concept: '',
|
||||
amount: 0.0,
|
||||
supplier: '',
|
||||
@@ -160,7 +160,7 @@ const GastosContent = ({ reqConfig }) => {
|
||||
)}
|
||||
renderCard={(gasto) => (
|
||||
<GastoCard
|
||||
key={gasto.expense_id}
|
||||
key={gasto.expenseId}
|
||||
gasto={gasto}
|
||||
onUpdate={handleEditSubmit}
|
||||
onDelete={handleDelete}
|
||||
|
||||
@@ -33,7 +33,7 @@ const Ingresos = () => {
|
||||
rawUrl: config.apiConfig.baseUrl + config.apiConfig.endpoints.incomes.all,
|
||||
membersUrl: config.apiConfig.baseUrl + config.apiConfig.endpoints.members.all,
|
||||
params: {
|
||||
_sort: 'created_at',
|
||||
_sort: 'createdAt',
|
||||
_order: 'desc'
|
||||
}
|
||||
};
|
||||
@@ -98,16 +98,16 @@ const IngresosContent = ({ reqConfig }) => {
|
||||
searchFn: (ingreso, term) => {
|
||||
const normalized = term.toLowerCase();
|
||||
return ingreso.concept?.toLowerCase().includes(normalized) ||
|
||||
String(ingreso.member_number).includes(normalized) ||
|
||||
ingreso.display_name?.toLowerCase().includes(normalized);
|
||||
String(ingreso.memberNumber).includes(normalized) ||
|
||||
ingreso.displayName?.toLowerCase().includes(normalized);
|
||||
}
|
||||
});
|
||||
|
||||
const handleCreate = () => {
|
||||
setCreatingIngreso(true);
|
||||
setTempIngreso({
|
||||
income_id: null,
|
||||
member_number: 0,
|
||||
incomeId: null,
|
||||
memberNumber: 0,
|
||||
concept: '',
|
||||
amount: 0.0,
|
||||
frequency: CONSTANTS.PAYMENT_FREQUENCY_YEARLY,
|
||||
@@ -183,10 +183,10 @@ const IngresosContent = ({ reqConfig }) => {
|
||||
)}
|
||||
renderCard={(income) => (
|
||||
<IngresoCard
|
||||
key={income.income_id}
|
||||
key={income.incomeId}
|
||||
income={income}
|
||||
onUpdate={(data, id) => handleEditSubmit(data, id)}
|
||||
onDelete={() => handleDelete(income.income_id)}
|
||||
onDelete={() => handleDelete(income.incomeId)}
|
||||
error={error}
|
||||
onClearError={() => setError(null)}
|
||||
/>
|
||||
|
||||
@@ -23,10 +23,10 @@ const ListaEspera = () => {
|
||||
if (configLoading) return <p><LoadingIcon /></p>;
|
||||
|
||||
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,
|
||||
preUsersUrl: config.apiConfig.baseUrl + config.apiConfig.endpoints.pre_users.all,
|
||||
preUserValidationUrl: config.apiConfig.baseUrl + config.apiConfig.endpoints.pre_users.validation,
|
||||
preUsersUrl: config.apiConfig.baseUrl + config.apiConfig.endpoints.preUsers.all,
|
||||
preUserValidationUrl: config.apiConfig.baseUrl + config.apiConfig.endpoints.preUsers.validate,
|
||||
params: {}
|
||||
};
|
||||
|
||||
@@ -39,7 +39,7 @@ const ListaEspera = () => {
|
||||
|
||||
const ListaEsperaContent = ({ reqConfig }) => {
|
||||
const { authStatus } = useAuth();
|
||||
const { data, dataLoading, dataError, postDataValidated, postData } = useDataContext();
|
||||
const { data, dataLoading, dataError, postData, postDataValidated } = useDataContext();
|
||||
|
||||
const [showWelcomeModal, setShowWelcomeModal] = useState(false);
|
||||
const [showPreUserFormModal, setShowPreUserFormModal] = useState(false);
|
||||
@@ -64,27 +64,30 @@ const ListaEsperaContent = ({ reqConfig }) => {
|
||||
const handleRegisterSubmit = async (formData) => {
|
||||
setValidationErrors({});
|
||||
|
||||
const { _, errors } = await postDataValidated(reqConfig.preUserValidationUrl, formData);
|
||||
const validation = await postDataValidated(
|
||||
reqConfig.preUserValidationUrl,
|
||||
formData
|
||||
);
|
||||
|
||||
if (errors) {
|
||||
setValidationErrors(errors);
|
||||
if (!validation.ok) {
|
||||
setValidationErrors(validation.errors);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
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.");
|
||||
|
||||
await postData(reqConfig.preUsersUrl, {
|
||||
...formData,
|
||||
request_id: requestId
|
||||
requestId
|
||||
});
|
||||
|
||||
setShowPreUserFormModal(false);
|
||||
setShowConfirmationModal(true);
|
||||
} catch (err) {
|
||||
setValidationErrors({ general: err.message });
|
||||
setValidationErrors({ general: "Error inesperado al enviar la solicitud" });
|
||||
}
|
||||
};
|
||||
|
||||
@@ -94,13 +97,6 @@ const ListaEsperaContent = ({ reqConfig }) => {
|
||||
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 (dataError) return <p className="text-danger text-center my-5">{dataError}</p>;
|
||||
|
||||
@@ -117,7 +113,7 @@ const ListaEsperaContent = ({ reqConfig }) => {
|
||||
</IfNotAuthenticated>
|
||||
</div>
|
||||
<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' && (
|
||||
<Modal show={showWelcomeModal} onHide={() => setShowWelcomeModal(false)}>
|
||||
|
||||
@@ -66,12 +66,12 @@ const Perfil = () => {
|
||||
};
|
||||
|
||||
const reqConfig = {
|
||||
baseUrl: `${config.apiConfig.baseUrl}${config.apiConfig.endpoints.members.profile}`,
|
||||
myIncomesUrl: buildUrl(config.apiConfig.baseUrl, config.apiConfig.endpoints.incomes.myIncomes),
|
||||
baseUrl: `${config.apiConfig.baseUrl}${config.apiConfig.endpoints.users.me}`,
|
||||
myIncomesUrl: buildUrl(config.apiConfig.baseUrl, config.apiConfig.endpoints.incomes.mine),
|
||||
requestUrl: buildUrl(config.apiConfig.baseUrl, config.apiConfig.endpoints.requests.all),
|
||||
preUsersUrl: buildUrl(config.apiConfig.baseUrl, config.apiConfig.endpoints.pre_users.all),
|
||||
preUserValidationUrl: buildUrl(config.apiConfig.baseUrl, config.apiConfig.endpoints.pre_users.validation),
|
||||
myRequestsUrl: buildUrl(config.apiConfig.baseUrl, config.apiConfig.endpoints.requests.myRequests),
|
||||
preUsersUrl: buildUrl(config.apiConfig.baseUrl, config.apiConfig.endpoints.preUsers.all),
|
||||
preUserValidationUrl: buildUrl(config.apiConfig.baseUrl, config.apiConfig.endpoints.preUsers.validate),
|
||||
myRequestsUrl: buildUrl(config.apiConfig.baseUrl, config.apiConfig.endpoints.requests.mine),
|
||||
changePasswordUrl: buildUrl(config.apiConfig.coreUrl, config.apiConfig.endpoints.auth.changePassword),
|
||||
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 { logout } = useAuth();
|
||||
|
||||
const usuario = data?.member;
|
||||
const identity = JSON.parse(localStorage.getItem("identity"));
|
||||
const myRequests = data?.requests ?? [];
|
||||
const incomes = data?.payments ?? [];
|
||||
const hasCollaborator = data?.hasCollaborator ?? false;
|
||||
const hasCollaboratorRequest = data?.hasCollaboratorRequest ?? false;
|
||||
const hasGreenHouse = data?.hasGreenHouse ?? false;
|
||||
const hasGreenHouseRequest = data?.hasGreenHouseRequest ?? false;
|
||||
const hasGreenHouse = data?.hasGreenhouse ?? false;
|
||||
const hasGreenHouseRequest = data?.hasGreenhouseRequest ?? false;
|
||||
|
||||
const [showAddCollaboratorModal, setShowAddCollaboratorModal] = useState(false);
|
||||
const [showRemoveCollaboratorModal, setShowRemoveCollaboratorModal] = useState(false);
|
||||
@@ -117,7 +117,7 @@ const PerfilContent = ({ config }) => {
|
||||
await postData(config.requestUrl, {
|
||||
type: CONSTANTS.REQUEST_TYPE_UNREGISTER,
|
||||
status: CONSTANTS.REQUEST_PENDING,
|
||||
requested_by: usuario.user_id
|
||||
requestedBy: identity.user.userId
|
||||
});
|
||||
setFeedbackModal({
|
||||
title: 'Solicitud enviada',
|
||||
@@ -140,7 +140,7 @@ const PerfilContent = ({ config }) => {
|
||||
await postData(config.requestUrl, {
|
||||
type: CONSTANTS.REQUEST_TYPE_ADD_GREENHOUSE,
|
||||
status: CONSTANTS.REQUEST_PENDING,
|
||||
requested_by: usuario.user_id
|
||||
requestedBy: identity.user.userId
|
||||
});
|
||||
setFeedbackModal({
|
||||
title: 'Solicitud enviada',
|
||||
@@ -163,7 +163,7 @@ const PerfilContent = ({ config }) => {
|
||||
await postData(config.requestUrl, {
|
||||
type: CONSTANTS.REQUEST_TYPE_REMOVE_GREENHOUSE,
|
||||
status: CONSTANTS.REQUEST_PENDING,
|
||||
requested_by: usuario.user_id
|
||||
requestedBy: identity.user.userId
|
||||
});
|
||||
setFeedbackModal({
|
||||
title: 'Solicitud enviada',
|
||||
@@ -191,7 +191,7 @@ const PerfilContent = ({ config }) => {
|
||||
const handleChangePassword = async () => {
|
||||
try {
|
||||
const validOldPassword = await postData(config.loginValidateUrl, {
|
||||
userId: usuario.user_id,
|
||||
userId: identity.user.userId,
|
||||
password: newPasswordData.currentPassword
|
||||
});
|
||||
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.");
|
||||
|
||||
const response = await postData(config.changePasswordUrl, {
|
||||
userId: usuario.user_id,
|
||||
userId: identity.user.userId,
|
||||
newPassword: newPasswordData.newPassword
|
||||
});
|
||||
|
||||
@@ -231,9 +231,9 @@ const PerfilContent = ({ config }) => {
|
||||
|
||||
const mappedRequests = myRequests.map(r => ({
|
||||
...r,
|
||||
request_type: r.request_type ?? r.type,
|
||||
request_status: r.request_status ?? r.status,
|
||||
request_created_at: r.request_created_at ?? r.created_at
|
||||
type: r.type ?? r.type,
|
||||
status: r.status ?? r.status,
|
||||
request_createdAt: r.request_createdAt ?? r.createdAt
|
||||
}));
|
||||
|
||||
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.Header className="bg-secondary text-white rounded-top-4 d-flex align-items-center justify-content-between">
|
||||
<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">
|
||||
<Card.Title className="mb-0">{`@${usuario.user_name}`}</Card.Title>
|
||||
<small>Te uniste el {parseDate(usuario.created_at)}</small>
|
||||
<Card.Title className="mb-0">{`@${identity.account.username}`}</Card.Title>
|
||||
<small>Te uniste el {parseDate(identity.metadata.createdAt)}</small>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -293,21 +293,21 @@ const PerfilContent = ({ config }) => {
|
||||
|
||||
<Card.Body>
|
||||
<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={faIdCard} className="me-2" />DNI: <strong>{usuario.dni}</strong></ListGroup.Item>
|
||||
<ListGroup.Item><FontAwesomeIcon icon={faEnvelope} className="me-2" />Email: <strong>{usuario.email}</strong></ListGroup.Item>
|
||||
<ListGroup.Item><FontAwesomeIcon icon={faPhone} className="me-2" />Teléfono: <strong>{usuario.phone}</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>{identity.metadata.dni}</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>{identity.metadata.phone}</strong></ListGroup.Item>
|
||||
<ListGroup.Item>
|
||||
<FontAwesomeIcon icon={faHashtag} className="me-2" />Socio Nº: <strong>{usuario.member_number}</strong> | Huerto Nº: <strong>{usuario.plot_number}</strong>
|
||||
<FontAwesomeIcon icon={faHashtag} className="me-2" />Socio Nº: <strong>{identity.metadata.memberNumber}</strong> | Huerto Nº: <strong>{identity.metadata.plotNumber}</strong>
|
||||
</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>
|
||||
<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>
|
||||
<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>
|
||||
</Card.Body>
|
||||
@@ -321,7 +321,7 @@ const PerfilContent = ({ config }) => {
|
||||
{incomes.length === 0 && <p className="text-center">No hay pagos registrados.</p>}
|
||||
<div className="d-flex flex-wrap gap-3 mb-4">
|
||||
{incomes.map(income => (
|
||||
<IngresoCard key={income.income_id} income={income} editable={false} />
|
||||
<IngresoCard key={income.incomeId} income={income} editable={false} />
|
||||
))}
|
||||
</div>
|
||||
|
||||
@@ -331,7 +331,7 @@ const PerfilContent = ({ config }) => {
|
||||
|
||||
<div className="d-flex flex-wrap gap-3 mb-4">
|
||||
{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>
|
||||
|
||||
@@ -432,7 +432,7 @@ const PerfilContent = ({ config }) => {
|
||||
>
|
||||
<PreUserForm
|
||||
userType={3}
|
||||
plotNumber={usuario.plot_number}
|
||||
plotNumber={identity.metadata.plotNumber}
|
||||
errors={validationErrors}
|
||||
onSubmit={async (formData) => {
|
||||
setValidationErrors({});
|
||||
@@ -447,15 +447,15 @@ const PerfilContent = ({ config }) => {
|
||||
const request = await postData(config.requestUrl, {
|
||||
type: CONSTANTS.REQUEST_TYPE_ADD_COLLABORATOR,
|
||||
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.");
|
||||
|
||||
await postData(config.preUsersUrl, {
|
||||
...formData,
|
||||
request_id: requestId
|
||||
requestId: requestId
|
||||
});
|
||||
|
||||
setValidationErrors({});
|
||||
@@ -495,7 +495,7 @@ const PerfilContent = ({ config }) => {
|
||||
await postData(config.requestUrl, {
|
||||
type: CONSTANTS.REQUEST_TYPE_REMOVE_COLLABORATOR,
|
||||
status: CONSTANTS.REQUEST_PENDING,
|
||||
requested_by: usuario.user_id
|
||||
requestedBy: identity.user.userId
|
||||
});
|
||||
|
||||
setFeedbackModal({
|
||||
|
||||
@@ -28,11 +28,11 @@ const Socios = () => {
|
||||
if (configLoading || !config) return <p className="text-center my-5"><LoadingIcon /></p>;
|
||||
|
||||
const reqConfig = {
|
||||
baseUrl: `${config.apiConfig.baseUrl}${config.apiConfig.endpoints.members.all}`,
|
||||
incomesUrl: `${config.apiConfig.baseUrl}${config.apiConfig.endpoints.members.payments}`,
|
||||
baseUrl: `${config.apiConfig.baseUrl}${config.apiConfig.endpoints.users.all}`,
|
||||
incomesUrl: `${config.apiConfig.baseUrl}${config.apiConfig.endpoints.users.payments}`,
|
||||
rawIncomesUrl: `${config.apiConfig.baseUrl}${config.apiConfig.endpoints.incomes.all}`,
|
||||
params: {
|
||||
_sort: "member_number",
|
||||
_sort: "memberNumber",
|
||||
_order: "asc"
|
||||
}
|
||||
};
|
||||
@@ -67,24 +67,24 @@ const SociosContent = ({ reqConfig }) => {
|
||||
} = usePaginatedList({
|
||||
data,
|
||||
pageSize: PAGE_SIZE,
|
||||
filterFn: (socio, filters) => {
|
||||
filterFn: (identity, filters) => {
|
||||
if (filters.todos) return true;
|
||||
if (!filters.inactivos && socio.status === 0) return false;
|
||||
if (!filters.inactivos && identity.account.status === 0) return false;
|
||||
return (
|
||||
(filters.listaEspera && socio.type === 0) ||
|
||||
(filters.hortelanos && socio.type === 1) ||
|
||||
(filters.invernadero && socio.type === 2) ||
|
||||
(filters.colaboradores && socio.type === 3) ||
|
||||
(filters.inactivos && socio.status === 0)
|
||||
(filters.listaEspera && identity.metadata.type === 0) ||
|
||||
(filters.hortelanos && identity.metadata.type === 1) ||
|
||||
(filters.invernadero && identity.metadata.type === 2) ||
|
||||
(filters.colaboradores && identity.metadata.type === 3) ||
|
||||
(filters.inactivos && identity.account.status === 0)
|
||||
);
|
||||
},
|
||||
searchFn: (socio, term) => {
|
||||
searchFn: (identity, term) => {
|
||||
const normalized = term.toLowerCase();
|
||||
return (
|
||||
socio.display_name?.toLowerCase().includes(normalized) ||
|
||||
socio.dni?.toLowerCase().includes(normalized) ||
|
||||
String(socio.member_number).includes(normalized) ||
|
||||
String(socio.plot_number).includes(normalized)
|
||||
identity.user.displayName?.toLowerCase().includes(normalized) ||
|
||||
identity.metadata.dni?.toLowerCase().includes(normalized) ||
|
||||
String(identity.metadata.memberNumber).includes(normalized) ||
|
||||
String(identity.metadata.plotNumber).includes(normalized)
|
||||
);
|
||||
},
|
||||
initialFilters: {
|
||||
@@ -98,20 +98,20 @@ const SociosContent = ({ reqConfig }) => {
|
||||
});
|
||||
|
||||
const listaEsperaOrdenada = data ? data
|
||||
.filter(s => s.type === 0 && s.status !== 0)
|
||||
.sort((a, b) => new Date(a.created_at) - new Date(b.created_at)) : [];
|
||||
.filter(identity => identity.metadata.type === 0 && identity.account.status !== 0)
|
||||
.sort((a, b) => new Date(a.metadata.createdAt) - new Date(b.metadata.createdAt)) : [];
|
||||
|
||||
const handleCreate = () => {
|
||||
setCreatingSocio(true);
|
||||
const socio = {
|
||||
user_id: null,
|
||||
user_name: "nuevo" + Date.now(),
|
||||
userId: null,
|
||||
userName: "nuevo" + Date.now(),
|
||||
email: "",
|
||||
display_name: "Nuevo Socio",
|
||||
displayName: "Nuevo Socio",
|
||||
role: 0,
|
||||
global_status: 1,
|
||||
member_number: "",
|
||||
plot_number: "",
|
||||
globalStatus: 1,
|
||||
memberNumber: "",
|
||||
plotNumber: "",
|
||||
dni: "",
|
||||
phone: "",
|
||||
notes: "",
|
||||
@@ -130,7 +130,7 @@ const SociosContent = ({ reqConfig }) => {
|
||||
|
||||
const handleCreateSubmit = async (newSocio) => {
|
||||
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);
|
||||
setError(null);
|
||||
setCreatingSocio(false);
|
||||
@@ -162,7 +162,7 @@ const SociosContent = ({ reqConfig }) => {
|
||||
setIncomesError(null);
|
||||
|
||||
try {
|
||||
const url = reqConfig.incomesUrl.replace(":member_number", memberNumber);
|
||||
const url = reqConfig.incomesUrl.replace(":memberNumber", memberNumber);
|
||||
const res = await getData(url);
|
||||
setIncomes(res.data);
|
||||
} catch (err) {
|
||||
@@ -174,7 +174,7 @@ const SociosContent = ({ reqConfig }) => {
|
||||
|
||||
const handleIncomeUpdate = async (editado) => {
|
||||
try {
|
||||
await putData(`${reqConfig.rawIncomesUrl}/${editado.income_id}`, editado);
|
||||
await putData(`${reqConfig.rawIncomesUrl}/${editado.incomeId}`, editado);
|
||||
await handleViewIncomes(selectedMemberNumber);
|
||||
} catch (err) {
|
||||
console.error("Error actualizando ingreso:", err);
|
||||
@@ -219,17 +219,17 @@ const SociosContent = ({ reqConfig }) => {
|
||||
)}
|
||||
renderCard={(socio) => {
|
||||
const position = socio.type === 0
|
||||
? listaEsperaOrdenada.findIndex(s => s.user_id === socio.user_id) + 1
|
||||
? listaEsperaOrdenada.findIndex(s => s.userId === socio.userId) + 1
|
||||
: null;
|
||||
|
||||
return (
|
||||
<SocioCard
|
||||
key={socio.user_id}
|
||||
key={socio.userId}
|
||||
socio={socio}
|
||||
onUpdate={handleEditSubmit}
|
||||
onDelete={handleDelete}
|
||||
onCancel={handleCancelCreate}
|
||||
onViewIncomes={() => handleViewIncomes(socio.member_number)}
|
||||
onViewIncomes={() => handleViewIncomes(socio.memberNumber)}
|
||||
error={error}
|
||||
onClearError={() => setError(null)}
|
||||
positionIfWaitlist={position}
|
||||
@@ -256,7 +256,7 @@ const SociosContent = ({ reqConfig }) => {
|
||||
)}
|
||||
<div className="d-flex flex-wrap gap-3 p-3 justify-content-start">
|
||||
{incomes.map((income) => (
|
||||
<IngresoCard key={income.income_id} income={income}
|
||||
<IngresoCard key={income.incomeId} income={income}
|
||||
onUpdate={handleIncomeUpdate} className='from-members' />
|
||||
))}
|
||||
</div>
|
||||
|
||||
@@ -49,30 +49,28 @@ const SolicitudesContent = ({ reqConfig }) => {
|
||||
searchFn: (entry, term) => {
|
||||
const normalized = term.toLowerCase();
|
||||
return (
|
||||
entry.pre_display_name?.toLowerCase().includes(normalized) ||
|
||||
entry.pre_dni?.toLowerCase().includes(normalized) ||
|
||||
entry.pre_email?.toLowerCase().includes(normalized) ||
|
||||
String(entry.pre_phone).includes(normalized)
|
||||
entry.preDisplayName?.toLowerCase().includes(normalized) ||
|
||||
entry.preDni?.toLowerCase().includes(normalized) ||
|
||||
entry.preEmail?.toLowerCase().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 url = reqConfig.acceptUrl.replace(":request_id", entry.request_id);
|
||||
const url = reqConfig.acceptUrl.replace(":requestId", entry.requestId);
|
||||
try {
|
||||
await putData(url, {});
|
||||
console.log("✅ Solicitud aceptada:", entry.request_id);
|
||||
} catch (err) {
|
||||
console.error("❌ Error al aceptar solicitud:", err.message);
|
||||
}
|
||||
};
|
||||
|
||||
const handleReject = async (entry) => {
|
||||
const url = reqConfig.rejectUrl.replace(":request_id", entry.request_id);
|
||||
const url = reqConfig.rejectUrl.replace(":requestId", entry.requestId);
|
||||
try {
|
||||
await putData(url, {});
|
||||
console.log("🛑 Solicitud rechazada:", entry.request_id);
|
||||
} catch (err) {
|
||||
console.error("❌ Error al rechazar solicitud:", err.message);
|
||||
}
|
||||
@@ -107,7 +105,7 @@ const SolicitudesContent = ({ reqConfig }) => {
|
||||
items={filtered}
|
||||
renderCard={(entry) => (
|
||||
<SolicitudCard
|
||||
key={entry.request_id}
|
||||
key={entry.requestId}
|
||||
data={entry}
|
||||
onAccept={() => handleAccept(entry)}
|
||||
onReject={() => handleReject(entry)}
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
'use strict';
|
||||
|
||||
const CONSTANTS = {
|
||||
// Service
|
||||
SERVICE_ID: 1,
|
||||
|
||||
// Roles
|
||||
ROLE_USER: 0,
|
||||
ROLE_ADMIN: 1,
|
||||
|
||||
Reference in New Issue
Block a user