diff --git a/imports/roles.js b/imports/roles.js index 07eb4be..d24b4a2 100644 --- a/imports/roles.js +++ b/imports/roles.js @@ -13,4 +13,13 @@ export const ROLS_DE_POBLE = { }; // as const; // export type TeamRolesKeys = keyof typeof TEAM_ROLES; -// export type TeamRolesValues = (typeof TEAM_ROLES)[TeamRolesKeys]; \ No newline at end of file +// export type TeamRolesValues = (typeof TEAM_ROLES)[TeamRolesKeys]; + + + +///////////////////////////////////////////////////////////////////////////////////////////////////// + +// De menor a major privilegi: +// +// Usuari < Voluntari de Poble < Encarregat de Tasques < Monitor de Poble < Administrador Global + diff --git a/imports/ui/App.jsx b/imports/ui/App.jsx index 9418765..227d054 100644 --- a/imports/ui/App.jsx +++ b/imports/ui/App.jsx @@ -5,6 +5,7 @@ import { Login } from './Login'; import { useSubscribe, useTracker, useFind } from 'meteor/react-meteor-data/suspense'; import { Pobles } from './Pobles'; +import { Poble } from './Poble'; import { Necessitats } from './Necessitats'; import { Tipus } from './Tipus'; import { BarraNav } from './BarraNav/BarraNav'; @@ -22,6 +23,8 @@ export const App = () => { const [esAdministrador, setEsAdministrador] = useState(false); + // const [pobleSeleccionat, setPobleSeleccionat] = useState(null); + // const u = useTracker("user", async () => await Meteor.userAsync()); @@ -33,18 +36,24 @@ export const App = () => { }, [user]); // const userId = await Meteor.userAsync(); - return + return {Meteor.userId() && } Carregant...}>{user ? : } } /> - } /> - } /> - } /> - } /> - } /> + } /> + } /> + } /> + } /> + } /> + } /> }; diff --git a/imports/ui/BarraNav/PanellSeccions/PanellSeccions.jsx b/imports/ui/BarraNav/PanellSeccions/PanellSeccions.jsx index 2a73670..ead6caf 100644 --- a/imports/ui/BarraNav/PanellSeccions/PanellSeccions.jsx +++ b/imports/ui/BarraNav/PanellSeccions/PanellSeccions.jsx @@ -16,22 +16,22 @@ export const BotóSecció = ({titol, linkto}) => { export const SeccióPobles = () => ; export const SeccióNecessitats = () => ; export const SeccióTipus = () => ; export const SeccióUsuaris = () => ; export const PanellSeccions = ({children}) => { diff --git a/imports/ui/Login.tsx b/imports/ui/Login.tsx index 82de457..d4e8c2c 100644 --- a/imports/ui/Login.tsx +++ b/imports/ui/Login.tsx @@ -2,7 +2,8 @@ import React, { useState } from 'react'; import { Accounts } from 'meteor/accounts-base'; import { useNavigate } from 'react-router-dom'; import { Meteor } from 'meteor/meteor'; - +import { Roles } from 'meteor/roles'; +import { ROLS_GLOBALS } from '../roles'; export const Login = () => { const [isLogin, setIsLogin] = useState( { initialState: true } ); @@ -25,7 +26,7 @@ export const Login = () => { }); }; - const handleRegistration = (e) => { + const handleRegistration = async (e) => { e.preventDefault(); // console.dir(e); const username = e.target.elements.username.value; @@ -38,11 +39,16 @@ export const Login = () => { return null; } - Accounts.createUserAsync({ + const userId = await Accounts.createUserAsync({ username, email, password - }).then(() => navigate('/')) + }); + console.log("userId deL NOU USUARI: ", userId); + userId && await Roles.addUsersToRolesAsync(userId, [ROLS_GLOBALS.USUARI]); + navigate('/'); + + return userId; }; diff --git a/imports/ui/Poble.jsx b/imports/ui/Poble.jsx new file mode 100644 index 0000000..57ff162 --- /dev/null +++ b/imports/ui/Poble.jsx @@ -0,0 +1,144 @@ +import React, { Suspense, useEffect, useState, useRef, lazy } from 'react'; +import { Meteor } from 'meteor/meteor'; +import { PoblesCollection } from '/imports/api/pobles.js'; +import { useSubscribe, useTracker, useFind } from 'meteor/react-meteor-data/suspense'; + +import { Roles } from 'meteor/roles'; +// import { useUserId } from 'meteor/react-meteor-accounts'; +import { BarraNav } from "./BarraNav/BarraNav"; +import { useParams } from "react-router-dom"; + + + + +export const Poble = () => { + + let { ambitPoble } = useParams(); + + // const [pobleSeleccionat, setPobleSeleccionat] = useState(null); + // const [creantPoble, setCreantPoble] = useState(false); + useSubscribe('pobles'); + // const pobles = useTracker("pobles", () => PoblesCollection.find().fetchAsync()); + const pobles = useFind(PoblesCollection, [{}, {sort: {nomPoble: 1}}]); + + const [esEditor, setEsEditor] = useState(false); + const userId = Meteor.userId(); + let pob; + + console.log("ambitPoble: ", ambitPoble); + console.log("pobles: ", pobles); + pob = pobles.find(p => p.ambitAssociat === ambitPoble); + console.log("pob: ", pob); + + // console.log("isAdmin: ", isAdmin) ; + + // (async () => { + useEffect(() => { + (async () => { + const comprovaAdmin = await Roles.userIsInRoleAsync(userId, ["admin"]); + setEsEditor(comprovaAdmin); + + })(); + }, []); + // })(); + + + + // const QuadreInfo_Poble = () => { + + // const refInAmbitAssignat = useRef(); + + // return
+ + // {pobleSeleccionat &&

{pobleSeleccionat._id}

} + + //
{ + // try { + // Meteor.callAsync('editaOAfigPoble', + // { + // ...pobleSeleccionat || [], + // nomPoble: d.get('nomPoble'), + // cp: d.get('cp') || "", + // comarca: d.get('comarca') || "", + // ambitAssociat: d.get('ambit-associat') + // }).then(() => setPobleSeleccionat(null)) + // .catch(err => console.error(err)); + // } catch (err) { + // alert(err); + // console.error(err); + // } + // }} + // > + // + // refInAmbitAssignat.current.value = ev.target.value.replace(/ +/g, '-').toLowerCase()} + // />
+ //
+ //
+ + // { esEditor && <> + //

+ // Àmbit associat: + + // + // {/* // nomPoble.replace(/ +/g, '-').toLowerCase()}/> */} + //

+ // } + + // + // + //
+ //
; + // }; + + return Carregant...} > + +

{pob?.nomPoble}

+ + {/* { esEditor && (pobleSeleccionat || creantPoble) && } + +
    { + pobles + .sort((a,b) => a.nomPoble?.toLowerCase() > b.nomPoble?.toLowerCase()) + .map(pob =>
  • + {pob.nomPoble}{esEditor && }
  • ) + }
+ {esEditor && } */} +
; +}; \ No newline at end of file diff --git a/imports/ui/Pobles.jsx b/imports/ui/Pobles.jsx index 3eb370c..460eea5 100644 --- a/imports/ui/Pobles.jsx +++ b/imports/ui/Pobles.jsx @@ -6,6 +6,7 @@ import { useSubscribe, useTracker, useFind } from 'meteor/react-meteor-data/susp import { Roles } from 'meteor/roles'; // import { useUserId } from 'meteor/react-meteor-accounts'; import { BarraNav } from "./BarraNav/BarraNav"; +import { Link } from 'react-router-dom'; @@ -13,6 +14,7 @@ import { BarraNav } from "./BarraNav/BarraNav"; export const Pobles = () => { const [pobleSeleccionat, setPobleSeleccionat] = useState(null); + const [creantPoble, setCreantPoble] = useState(false); useSubscribe('pobles'); // const pobles = useTracker("pobles", () => PoblesCollection.find().fetchAsync()); const pobles = useFind(PoblesCollection, [{}, {sort: {nomPoble: 1}}]); @@ -31,8 +33,12 @@ export const Pobles = () => { }, []); // })(); + + const QuadreInfo_Poble = () => { + const refInAmbitAssignat = useRef(); + return
{ }}> {pobleSeleccionat &&

{pobleSeleccionat._id}

} -

Pobles

{ @@ -53,6 +58,7 @@ export const Pobles = () => { nomPoble: d.get('nomPoble'), cp: d.get('cp') || "", comarca: d.get('comarca') || "", + ambitAssociat: d.get('ambit-associat') }).then(() => setPobleSeleccionat(null)) .catch(err => console.error(err)); } catch (err) { @@ -61,9 +67,25 @@ export const Pobles = () => { } }} > -
+ + refInAmbitAssignat.current.value = ev.target.value.replace(/ +/g, '-').toLowerCase()} + />


+ + { esEditor && <> +

+ Àmbit associat: + + + {/* // nomPoble.replace(/ +/g, '-').toLowerCase()}/> */} +

+ } +
; }; - + return Carregant...} > + +

Pobles

- { esEditor && } + { esEditor && (pobleSeleccionat || creantPoble) && }
    { backgroundColor: `${'lightgreen' || 'lightcoral'}` }} > - {pob.nomPoble}{esEditor && }) + + {pob.nomPoble}{esEditor && } + + ) + }
+ {esEditor && }
; }; \ No newline at end of file diff --git a/imports/ui/Usuaris.jsx b/imports/ui/Usuaris.jsx index 59eca25..6d81b31 100644 --- a/imports/ui/Usuaris.jsx +++ b/imports/ui/Usuaris.jsx @@ -7,12 +7,153 @@ import { Roles } from 'meteor/roles'; // import { useUserId } from 'meteor/react-meteor-accounts'; import { BarraNav } from "./BarraNav/BarraNav"; +const isClickInsideRectangle = (e, element) => { + const r = element?.getBoundingClientRect(); + return ( + e.clientX > r.left && + e.clientX < r.right && + e.clientY > r.top && + e.clientY < r.bottom + ); +}; +const AssignadorDeRols = ({pobles, ambitGeneral, esEditor, rols, usrSeleccionat}) => { + const [creantRol, setCreantRol] = useState(false); + const [ambitsUSel, setAmbitsUSel] = useState([]); + + useEffect(() => { + (async () => { + const ambits = await Roles.getScopesForUserAsync(usrSeleccionat?._id); + setAmbitsUSel(ambits); + })(); + }, []); + + return
+

Rols

+ +

Àmbits

+
General:
+
    { + rols?.map(uRol =>
  • + {uRol} + {esEditor && } +
  • ) + }
+ + {/*
    */} + { + ambitsUSel?.map(async aus => { + + const uRol = await Roles.getRolesForUserAsync(usrSeleccionat._id, {scope: aus, onlyScoped: true}); + return <> +
    {aus}:
    + { + {uRol} + {esEditor && } + + }
    + ; + }) + } + {/*
*/} + + {esEditor && creantRol &&
+
{ + Meteor.callAsync('assignaRol', usrSeleccionat._id, d.get('selRol'), d.get('selAmbit')); + }}> + + Àmbit:
+ + Rol:

+ + + +
+
} + + {esEditor && usrSeleccionat && } +
; +}; export const Usuaris = () => { - + const refQuadreSeleccionat = useRef(); const [usrSeleccionat, setUsrSeleccionat] = useState(null); + useSubscribe('pobles'); const pobles = useTracker("pobles", () => PoblesCollection.find().fetchAsync()); @@ -21,8 +162,15 @@ export const Usuaris = () => { const usuaris = useTracker("usuaris", () => { Meteor.subscribe('usuaris'); - return Meteor.users.find().fetch().filter(u => u._id !== Meteor.userId()); + return Meteor.users.find().fetch()//.filter(u => u._id !== Meteor.userId()); }); + + const rols = useTracker("rols", async () => { + return await Roles.getRolesForUserAsync(usrSeleccionat?._id); + }); + + console.log("rols: ", rols); + // console.log("isAdmin: ", isAdmin) ; console.log("usuaris: ", usuaris); // (async () => { @@ -35,15 +183,34 @@ export const Usuaris = () => { }, []); // })(); + const QuadreInfo_Usuari = () => { + + // const [rols, setRols] = useState([]); + + const [ambitGeneral, setAmbitGeneral] = useState(false); + + + let allScopes; + + // useEffect( async () => { + // allScopes = await Roles.getScopesForUserAsync(usrSeleccionat?._id); + // // Roles.getRolesForUser(usrSeleccionat?._id).map(uRol =>
  • {uRol}
  • ); + + // // setRols(rols); + // }); + + return
    + display: `inline-block`, + border: `1px solid #6666`, + padding: `.5rem`, + borderRadius: `.3em`, + backgroundColor: `lightcyan` + }} + ref={refQuadreSeleccionat} + > {usrSeleccionat &&

    {usrSeleccionat._id}

    } @@ -66,23 +233,30 @@ export const Usuaris = () => { >

    -
    - + + + + + { esEditor && } +
    ; }; - return Carregant...} > + return
    { + !isClickInsideRectangle(ev, refQuadreSeleccionat.current) && setUsrSeleccionat(null); + }}>Carregant...} > +

    Usuaris

    - { esEditor && } + { esEditor && usrSeleccionat && }
      { backgroundColor: `${'lightgreen' || 'lightcoral'}` }} > - {usr.username}{esEditor && }) + {usr.username}{esEditor && }) }
    -
    ; + + {esEditor && !usrSeleccionat && } + +
    ; }; \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 1d952f7..6f5f927 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,6 +10,7 @@ "meteor-node-stubs": "^1.2.5", "react": "19", "react-dom": "19", + "react-router": "^7.0.2", "react-router-dom": "^6.28.0", "react-select": "^5.8.3", "styled-components": "^6.1.13" @@ -356,6 +357,12 @@ "node": ">=14.0.0" } }, + "node_modules/@types/cookie": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.6.0.tgz", + "integrity": "sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==", + "license": "MIT" + }, "node_modules/@types/parse-json": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.2.tgz", @@ -432,6 +439,15 @@ "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", "license": "MIT" }, + "node_modules/cookie": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.0.2.tgz", + "integrity": "sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA==", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, "node_modules/cosmiconfig": { "version": "7.1.0", "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.1.0.tgz", @@ -1977,6 +1993,30 @@ "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", "license": "MIT" }, + "node_modules/react-router": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.0.2.tgz", + "integrity": "sha512-m5AcPfTRUcjwmhBzOJGEl6Y7+Crqyju0+TgTQxoS4SO+BkWbhOrcfZNq6wSWdl2BBbJbsAoBUb8ZacOFT+/JlA==", + "license": "MIT", + "dependencies": { + "@types/cookie": "^0.6.0", + "cookie": "^1.0.1", + "set-cookie-parser": "^2.6.0", + "turbo-stream": "2.4.0" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "react": ">=18", + "react-dom": ">=18" + }, + "peerDependenciesMeta": { + "react-dom": { + "optional": true + } + } + }, "node_modules/react-router-dom": { "version": "6.28.0", "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.28.0.tgz", @@ -2131,6 +2171,12 @@ "integrity": "sha512-xFVuu11jh+xcO7JOAGJNOXld8/TcEHK/4CituBUeUb5hqxJLj9YuemAEuvm9gQ/+pgXYfbQuqAkiYu+u7YEsNA==", "license": "MIT" }, + "node_modules/set-cookie-parser": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.1.tgz", + "integrity": "sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ==", + "license": "MIT" + }, "node_modules/shallowequal": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/shallowequal/-/shallowequal-1.1.0.tgz", @@ -2207,6 +2253,12 @@ "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==", "license": "0BSD" }, + "node_modules/turbo-stream": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/turbo-stream/-/turbo-stream-2.4.0.tgz", + "integrity": "sha512-FHncC10WpBd2eOmGwpmQsWLDoK4cqsA/UT/GqNoaKOQnT8uzhtCbg3EoUDMvqpOSAI0S26mr0rkjzbOO6S3v1g==", + "license": "ISC" + }, "node_modules/yaml": { "version": "1.10.2", "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", diff --git a/package.json b/package.json index 279ce12..6804200 100644 --- a/package.json +++ b/package.json @@ -12,6 +12,7 @@ "meteor-node-stubs": "^1.2.5", "react": "19", "react-dom": "19", + "react-router": "^7.0.2", "react-router-dom": "^6.28.0", "react-select": "^5.8.3", "styled-components": "^6.1.13" diff --git a/server/main.js b/server/main.js index 1d38f67..c154f0f 100644 --- a/server/main.js +++ b/server/main.js @@ -213,15 +213,19 @@ Meteor.startup(async () => { await Roles.createRoleAsync(ROLS_DE_POBLE.ENCARREGAT, { unlessExists: true }); await Roles.createRoleAsync(ROLS_DE_POBLE.VOLUNTARI, { unlessExists: true }); - await Roles.addRolesToParentAsync([ROLS_DE_POBLE.VOLUNTARI, ROLS_DE_POBLE.ENCARREGAT], ROLS_DE_POBLE.MONITOR); - await Roles.addRolesToParentAsync([ROLS_DE_POBLE.VOLUNTARI, ROLS_DE_POBLE.ENCARREGAT, ROLS_DE_POBLE.MONITOR, ROLS_GLOBALS.USUARI], ROLS_GLOBALS.ADMINISTRADOR); + // await Roles.addRolesToParentAsync([ROLS_DE_POBLE.VOLUNTARI, ROLS_DE_POBLE.ENCARREGAT], ROLS_DE_POBLE.MONITOR); + // await Roles.addRolesToParentAsync([ROLS_DE_POBLE.VOLUNTARI, ROLS_DE_POBLE.ENCARREGAT, ROLS_DE_POBLE.MONITOR, ROLS_GLOBALS.USUARI], ROLS_GLOBALS.ADMINISTRADOR); } catch (err) { console.error(err.message); } // Publish user's own roles -Meteor.publish(null, function () { +Meteor.publish(null, async function () { + if (await Roles.userIsInRoleAsync(Meteor.userId(), "admin")) { + return Meteor.roleAssignment.find(); + } + if (this.userId) { return Meteor.roleAssignment.find({ "user._id": this.userId }); } @@ -232,14 +236,30 @@ Meteor.publish(null, function () { // Meteor.publish("scopeRoles", function (scope) { // if (this.userId) { // return Meteor.roleAssignment.find({ scope: scope }); -// } +// }2024-12-19 21:40:26 // this.ready(); // }); +Accounts.onCreateUser(async (options, user) => { + await Roles.addUsersToRolesAsync(user._id, ROLS_GLOBALS.USUARI); + return user; +}); //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - Meteor.methods({ +Meteor.methods({ + 'assignaRol': async function (userId, rols, ambits) { + const esAdmin = await Roles.userIsInRoleAsync(Meteor.userId(), "admin"); + esAdmin && await Roles.addUsersToRolesAsync(userId, rols, ambits); + console.log(`Rol "${rols}" assignat a l'usuari "${userId}" delimitat amb l'àmbit "${ambits}"`); + }, + + 'retiraRols': async function (rols, userId, ambits) { + const esAdmin = await Roles.userIsInRoleAsync(Meteor.userId(), "admin"); + esAdmin && await Roles.removeUsersFromRolesAsync(userId, rols, ambits); + + console.log(`Retirats els rols "${rols}" de l'usuari "${userId}"`); + }, 'editaOAfigPoble': async function (poble) { const esAdmin = await Roles.userIsInRoleAsync(Meteor.userId(), "admin"); @@ -305,6 +325,20 @@ Meteor.publish(null, function () { } }, + 'eliminaUsuari': async function (usuariId) { + const esAdmin = await Roles.userIsInRoleAsync(Meteor.userId(), "admin"); + try { + console.log(`ELIMINACIÓ D'USUARI sol·licitada per a ${usuariId}. Comprovant si ${Meteor.userId()} és Admin: `, esAdmin); + if (esAdmin && usuariId) { + return await Meteor.users.removeAsync(usuariId); + } else { + throw new Error("El nom de l'usuari no és vàlid"); + } + } catch (e) { + console.error(e); + } + }, + 'afigTipus': async function (tipus) { const esAdmin = await Roles.userIsInRoleAsync(Meteor.userId(), "admin"); try {