diff --git a/.meteor/versions b/.meteor/versions index 4a9dc98..1ca3900 100644 --- a/.meteor/versions +++ b/.meteor/versions @@ -57,7 +57,7 @@ random@1.2.2 rate-limit@1.1.2 react-fast-refresh@0.2.9 react-meteor-accounts@1.0.3 -react-meteor-data@3.0.2 +react-meteor-data@3.0.3 reactive-var@1.0.13 reload@1.3.2 retry@1.1.1 diff --git a/client/main.jsx b/client/main.jsx index d2e380f..a9f7af3 100644 --- a/client/main.jsx +++ b/client/main.jsx @@ -1,4 +1,4 @@ -import React from 'react'; +import React, {StrictMode} from 'react'; import { createRoot } from 'react-dom/client'; import { Meteor } from 'meteor/meteor'; import { App } from '/imports/ui/App'; @@ -6,5 +6,7 @@ import { App } from '/imports/ui/App'; Meteor.startup(() => { const container = document.getElementById('react-target'); const root = createRoot(container); - root.render(); + root.render( + + ); }); diff --git a/eslint.config.mjs b/eslint.config.mjs new file mode 100644 index 0000000..bb72d11 --- /dev/null +++ b/eslint.config.mjs @@ -0,0 +1,12 @@ +import globals from "globals"; +import pluginJs from "@eslint/js"; +import pluginReact from "eslint-plugin-react"; + + +/** @type {import('eslint').Linter.Config[]} */ +export default [ + {files: ["**/*.{js,mjs,cjs,jsx}"]}, + {languageOptions: { globals: globals.browser }}, + pluginJs.configs.recommended, + pluginReact.configs.flat.recommended, +]; \ No newline at end of file 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 f8101ce..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()); @@ -45,11 +48,12 @@ export const App = () => { Carregant...>}>{user ? : } } /> - } /> - } /> - } /> - } /> - } /> + } /> + } /> + } /> + } /> + } /> + } /> }; diff --git a/imports/ui/BarraNav/BarraNav.jsx b/imports/ui/BarraNav/BarraNav.jsx index 6e7dc86..ed46184 100644 --- a/imports/ui/BarraNav/BarraNav.jsx +++ b/imports/ui/BarraNav/BarraNav.jsx @@ -19,6 +19,10 @@ const SeccióUsuaris = lazy(async () => { const module = await import('/imports/ui/BarraNav/PanellSeccions/PanellSeccions.jsx'); return ({ default: module.SeccióUsuaris }); }); +const SeccióCodis = lazy(async () => { + const module = await import('/imports/ui/BarraNav/PanellSeccions/PanellSeccions.jsx'); + return ({ default: module.SeccióCodis }); +}); const BarraNav = ({esAdministrador, setEsAdministrador}) => { const userId = Meteor.userId(); @@ -38,6 +42,7 @@ const BarraNav = ({esAdministrador, setEsAdministrador}) => { + { esAdministrador && } ; }; diff --git a/imports/ui/BarraNav/PanellSeccions/PanellSeccions.jsx b/imports/ui/BarraNav/PanellSeccions/PanellSeccions.jsx index 2a73670..8092e6a 100644 --- a/imports/ui/BarraNav/PanellSeccions/PanellSeccions.jsx +++ b/imports/ui/BarraNav/PanellSeccions/PanellSeccions.jsx @@ -16,22 +16,27 @@ export const BotóSecció = ({titol, linkto}) => { export const SeccióPobles = () => ; export const SeccióNecessitats = () => ; export const SeccióTipus = () => ; export const SeccióUsuaris = () => ; + +export const SeccióCodis = () => ; export const PanellSeccions = ({children}) => { @@ -40,9 +45,10 @@ export const PanellSeccions = ({children}) => { border: `1px solid #cccc`, padding: `.5rem`, borderRadius: `.5rem`, - backgroundColor: `lightslategray` + backgroundColor: `lightslategray`, + flexWrap: `wrap`, + justifyContent: `space-around` }}>{children} >; }; - diff --git a/imports/ui/Codis.jsx b/imports/ui/Codis.jsx new file mode 100644 index 0000000..b22ce5f --- /dev/null +++ b/imports/ui/Codis.jsx @@ -0,0 +1,250 @@ +import React, { Suspense, useEffect, useState, useRef, lazy } from 'react'; +import { Meteor } from 'meteor/meteor';; +import { NecessitatsCollection } from '/imports/api/necessitats.js'; +import { PoblesCollection } from '../api/pobles'; +import { TipusCollection } from '../api/tipus'; +import { useSubscribe, useTracker, useFind } from 'meteor/react-meteor-data/suspense'; +import { Roles } from 'meteor/roles'; +// import { useUserId } from 'meteor/react-meteor-accounts'; +import Select from 'react-select'; + +import CreatableSelect from 'react-select/creatable'; +import AsyncCreatableSelect from 'react-select/async-creatable'; +import { BarraNav } from "./BarraNav/BarraNav"; + + + + +export const Codis = () => { + + // const [permes, setPermes] = useState(false); + const [esEditor, setEsEditor] = useState(false); + const userId = Meteor.userId(); + + useEffect(() => { + (async () => { + const comprovaAdmin = await Roles.userIsInRoleAsync(userId, ["admin"]); + setEsEditor(comprovaAdmin); + })(); + }, []); + + // console.log("isAdmin: ", isAdmin) ; + + let ambitSeleccionat = null; + + useSubscribe('necessitats'); + const necessitats = useTracker("necessitats", () => NecessitatsCollection.find().fetchAsync()); + + useSubscribe('pobles'); + const pobles = useTracker("pobles", () => PoblesCollection.find().fetchAsync()); + const ambits = pobles?.map(p => p?.ambitAssociat); + + // console.log("ambits: ", ambits); + + // useSubscribe('tipus'); + // const ambits = useTracker("ambits", () => Collection.find().fetchAsync()); + + // console.log("tipus: ", tipus); + // console.log("necessitats: ", necessitats); + + // console.log("tipusSeleccionat: ", tipusSeleccionat); + + + // const filterAmbit = (inputValue) => { + // return ambits.filter((i) => + // i.toLowerCase().includes(inputValue.toLowerCase()) + // ); + // }; + + const SelectorDeRol = ({ambitSeleccionat}) => { + return <> + Rol: + necessitats.find(nec => nec.tipus === tipusSeleccionat._id)} + options={ + (ambitSeleccionat == "GENERAL") + ? [ + { + value: "admin", + label: "administrador" + }, + { + value: "usuari", + label: "usuari" + } + ] + + : [ + { + value: "monitor_de_poble", + label: "monitor_de_poble" + }, + { + value: "encarregat_de_tasques", + label: "encarregat_de_tasques" + }, + { + value: "voluntari_de_poble", + label: "voluntari_de_poble" + } + ] + } + /> + >; + } + + const QuadreInfo_Tipus = () => { + + return + + {ambitSeleccionat && {ambitSeleccionat}} + Codis + + Tria el rol que adquirirà qui utilitze el codi. + + { + // if (d.get('selTipus')) + try { + Meteor.callAsync('editaOAfigNecessitat', { + ...tipusSeleccionat || [], + titol: d.get('titol'), + tipus: d.get('selTipus') || "", + poble: d.get('selPoble') + }) + .then(() => setAmbitSeleccionat(null)) + .catch(err => console.error(err)) + ; + } catch (err) { + alert(err); + console.error(err); + } + }} + > + Àmbit: + {/* Si + l'usuari és ADMINISTRADOR o té permisos de responsabilitat, podrà crear nous Tipus al CREAR o a l'EDITAR + si no + tansols podrà assignar a un tipus predefinit o al de ALTRES + */} + + "Crear nou tipus..."} + // filterOption={filterAmbit} + options={ + [... + ambits + .map((v,i) => ({ + value: v, + label: v + })) + .sort((a,b) => a.label.toLowerCase() > b.label.toLowerCase()), + { + value: "GENERAL", + label: "GENERAL" + } + ] + } + // onCreateOption={(inputValue) => Meteor.callAsync('afigT', {titol: inputValue})} + isSearchable + // loadOptions={tipus.map((v,i) => ({value: v, label: v.titol}))} + onChange={ev => { + ambitSeleccionat = ev.value; + console.log("ambitSeleccionat: ", ambitSeleccionat); + + }} + /> + + {/* + Moble + Maquinari + Menjar i beguda + Infantils + Higiene + */} + + + + + + + + Títol: + + + + + Poble: + + ({value: v._id, label: v.nomPoble})) } /> + {/* + { + pobles.map((v,i) => {v.nomPoble}) + } + */} + + + + + + {ambitSeleccionat && esEditor && { + ev.preventDefault(); + if (confirm(`Vas a eliminar el poble "${pobleSeleccionat.nomPoble}". És una operació irreversible. Procedir?`)) { + Meteor.callAsync('eliminaPoble', pobleSeleccionat._id).catch(err => console.error(err)); + setNecessitatSeleccionada(null); + } + }}>Elimina} + + ; + }; + + + return Carregant...>} > + + + + + + {/* ({value: v, label: v.titol})) } + loadOptions={tipus.map((v,i) => ({value: v, label: v.titol}))} + /> */} + + {/* */} + + + { + ambits + .sort((a,b) => a.toLowerCase() > b.toLowerCase()) + .map(amb => + {amb}{esEditor && {setTipusSeleccionat(titol)}}>Edita} + ) + } + ; +}; \ No newline at end of file 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/Necessitats.jsx b/imports/ui/Necessitats.jsx index ad5b45f..deae70f 100644 --- a/imports/ui/Necessitats.jsx +++ b/imports/ui/Necessitats.jsx @@ -59,11 +59,20 @@ export const Necessitats = () => { action={d => { if (d.get('selTipus')) try { + const tipusSeleccionat = tipus.find(t => t._id === d.get('selTipus')) || ""; + const pobleSeleccionat = pobles.find(p => p._id === d.get('selPoble')) || ""; Meteor.callAsync('afigNecessitat', { ...necessitatSeleccionada || [], - titol: d.get('titol'), - tipus: d.get('selTipus') || "", - poble: d.get('selPoble') + titol: d.get('taTitol'), + tipus: tipusSeleccionat, + poble: pobleSeleccionat, + descrip: d.get('taDescripcio'), + contacte: { + nom: d.get('inContacte'), + tel: d.get('inTelefon'), + email: d.get('inEMail'), + adr: d.get('inAdreça') + } }) .then(() => setNecessitatSeleccionada(null)) .catch(err => console.error(err)) @@ -76,7 +85,7 @@ export const Necessitats = () => { > Títol - + @@ -91,6 +100,7 @@ export const Necessitats = () => { formatCreateLabel={(inputValue) => "Crear nou tipus..."} defaultOptions={tipus.map((v,i) => ({value: v._id, label: v.titol})).sort((a,b) => a.label.toLowerCase() > b.label.toLowerCase()) } onCreateOption={(inputValue) => Meteor.callAsync('afigTipus', {titol: inputValue})} + defaultValue={ necessitatSeleccionada ? { value: necessitatSeleccionada.tipus._id, label: necessitatSeleccionada.tipus.titol} : ""} // loadOptions={tipus.map((v,i) => ({value: v, label: v.titol}))} /> @@ -106,7 +116,9 @@ export const Necessitats = () => { Poble - ({value: v._id, label: v.nomPoble})) } /> + ({value: v._id, label: v.nomPoble})) } + defaultValue={ necessitatSeleccionada ? { value: necessitatSeleccionada.poble._id, label: necessitatSeleccionada.poble.nomPoble} : ""} + /> {/* { pobles.map((v,i) => {v.nomPoble}) @@ -116,7 +128,9 @@ export const Necessitats = () => { Descripció: - + @@ -125,10 +139,18 @@ export const Necessitats = () => { border: `1px solid #6666` }}> Contacte - - - - + + + + @@ -173,7 +195,7 @@ export const Necessitats = () => { backgroundColor: `${'lightgreen' || 'lightcoral'}` }} > - {nec.titol}{esEditor && {setNecessitatSeleccionada(nec)}}>Edita}) + {nec.titol} {esEditor && {setNecessitatSeleccionada(nec)}}>Edita}) } ; }; \ No newline at end of file diff --git a/imports/ui/Poble.jsx b/imports/ui/Poble.jsx new file mode 100644 index 0000000..4b06e52 --- /dev/null +++ b/imports/ui/Poble.jsx @@ -0,0 +1,228 @@ +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"; +import { NecessitatsCollection } from '../api/necessitats'; + + + + +export const Poble = () => { + + let { ambitPoble } = useParams(); + + // const [pobleSeleccionat, setPobleSeleccionat] = useState(null); + // const [creantPoble, setCreantPoble] = useState(false); + useSubscribe('pobles'); + useSubscribe('usuaris'); + useSubscribe('necessitats', ambitPoble); + // const pobles = useTracker("pobles", () => PoblesCollection.find().fetchAsync()); + const pobles = useFind(PoblesCollection, [{}, {sort: {nomPoble: 1}}]); + const necessitats = useFind(NecessitatsCollection, [{}, {sort: {titol: 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); + // } + // }} + // > + // Població: + // refInAmbitAssignat.current.value = ev.target.value.replace(/ +/g, '-').toLowerCase()} + // /> + // Comarca: + // Codi Postal: + + // { esEditor && <> + // + // Àmbit associat: + + // + // {/* // nomPoble.replace(/ +/g, '-').toLowerCase()}/> */} + // + // >} + + // + // { + // ev.preventDefault(); + // if (confirm(`Vas a eliminar el poble "${pobleSeleccionat.nomPoble}". És una operació irreversible. Procedir?`)) { + // Meteor.callAsync('eliminaPoble', pobleSeleccionat._id).catch(err => console.error(err)); + // setPobleSeleccionat(null); + // } + // }}>Elimina + // + // ; + // }; + + return Carregant...>} > + + {pob?.nomPoble} + + Monitores: + + { + (async () => { + const monitors = await Roles.getUsersInRoleAsync("monitor_de_poble", pob?.ambitAssociat); + // console.log("monitors: ", monitors); + return monitors?.map( (u) => {u.username} ); + })() + } + + + Encarregades de tasques: + + { + (async () => { + const encarregats = await Roles.getUsersInRoleAsync("encarregat_de_tasques", pob?.ambitAssociat); + // console.log("encarregats: ", encarregats); + return encarregats?.map( (u) => {u.username} ); + })() + } + + + Voluntàries de poble: + + { + (async () => { + const voluntaris = await Roles.getUsersInRoleAsync("voluntari_de_poble", pob?.ambitAssociat); + // console.log("voluntaris: ", voluntaris); + return voluntaris?.map( (u) => {u.username} ); + })() + } + + + Necessitats: + + { + necessitats + .sort((a,b) => a.titol?.toLowerCase() > b.titol?.toLowerCase()) + .map(nec => + {nec.titol} {esEditor && {setNecessitatSeleccionada(nec)}}>Edita}) + } + + {/* { esEditor && (pobleSeleccionat || creantPoble) && } + + { + pobles + .sort((a,b) => a.nomPoble?.toLowerCase() > b.nomPoble?.toLowerCase()) + .map(pob => + {pob.nomPoble}{esEditor && {setPobleSeleccionat(pob)}}>Edita}) + } + {esEditor && { + setCreantPoble(!creantPoble); + }} + >+} */} + ; +}; \ No newline at end of file diff --git a/imports/ui/Pobles.jsx b/imports/ui/Pobles.jsx index 3eb370c..bacdebb 100644 --- a/imports/ui/Pobles.jsx +++ b/imports/ui/Pobles.jsx @@ -6,14 +6,215 @@ 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'; +import { NecessitatsCollection } from '../api/necessitats'; +const QuadreInfo_Poble = ({esEditor, pobleSeleccionat, setPobleSeleccionat}) => { + + 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); + } + }} + > + Població: + refInAmbitAssignat.current.value = ev.target.value.replace(/ +/g, '-').toLowerCase()} + /> + Comarca: + Codi Postal: + + { esEditor && <> + + Àmbit associat: + + + {/* // nomPoble.replace(/ +/g, '-').toLowerCase()}/> */} + + >} + + + { + ev.preventDefault(); + if (confirm(`Vas a eliminar el poble "${pobleSeleccionat.nomPoble}". És una operació irreversible. Procedir?`)) { + Meteor.callAsync('eliminaPoble', pobleSeleccionat._id).catch(err => console.error(err)); + setPobleSeleccionat(null); + } + }}>Elimina + + ; +}; + +const BotoPobleAmbIndicadors = ({pob, pobleSeleccionat, setPobleSeleccionat, esEditor}) => { + // let pob = pobleSeleccionat; + useSubscribe('necessitats', pob.ambitAssociat); + const necessitats = useFind(NecessitatsCollection, [{"poble.ambitAssociat": pob.ambitAssociat}]); + const [monitors, setMonitors] = useState(false); + const [encarregats, setEncarregats] = useState(false); + const [voluntaris, setVoluntaris] = useState(false); + + // let mon, mons, enc, encs, vol, vols; + + (async () => { + const mons = await Roles.getUsersInRoleAsync("monitor_de_poble", pob?.ambitAssociat); + // console.log("monitors: ", monitors); + setMonitors(mons?.fetch().length || false); + + const encs = await Roles.getUsersInRoleAsync("encarregat_de_tasques", pob?.ambitAssociat); + // console.log("monitors: ", monitors); + setEncarregats(encs?.fetch().length || false); + + const vols = await Roles.getUsersInRoleAsync("voluntari_de_poble", pob?.ambitAssociat); + // console.log("monitors: ", monitors); + setVoluntaris(vols?.fetch().length || false); + })(); + + return + {// Indicadors de Responsables + + { + monitors + ? {monitors} + : false + } + { encarregats + ? {encarregats} + : false + } + { voluntaris + ? {voluntaris} + : false + } + + } + + {// Indicadors de Necessitats + + { + necessitats.length + ? + {necessitats.length } + : false + } + + } + + {// Botó de creació de nou poble + + {pob.nomPoble} {esEditor && { + ev.preventDefault(); + ev.stopPropagation(); + + setPobleSeleccionat(pob); + } + }>Edita} + + } + ; +} export const Pobles = () => { const [pobleSeleccionat, setPobleSeleccionat] = useState(null); + const [creantPoble, setCreantPoble] = useState(false); useSubscribe('pobles'); + useSubscribe('usuaris'); + // const pobles = useTracker("pobles", () => PoblesCollection.find().fetchAsync()); const pobles = useFind(PoblesCollection, [{}, {sort: {nomPoble: 1}}]); @@ -31,54 +232,20 @@ export const Pobles = () => { }, []); // })(); - const QuadreInfo_Poble = () => { + + + + + + // const Indicador = ({valor, ambit, }) => { + + // }; + + return <> - return - - {pobleSeleccionat && {pobleSeleccionat._id}} - Pobles + Pobles - { - try { - Meteor.callAsync('editaOAfigPoble', - { - ...pobleSeleccionat || [], - nomPoble: d.get('nomPoble'), - cp: d.get('cp') || "", - comarca: d.get('comarca') || "", - }).then(() => setPobleSeleccionat(null)) - .catch(err => console.error(err)); - } catch (err) { - alert(err); - console.error(err); - } - }} - > - Població: - Comarca: - Codi Postal: - - { - ev.preventDefault(); - if (confirm(`Vas a eliminar el poble "${pobleSeleccionat.nomPoble}". És una operació irreversible. Procedir?`)) { - Meteor.callAsync('eliminaPoble', pobleSeleccionat._id).catch(err => console.error(err)); - setPobleSeleccionat(null); - } - }}>Elimina - - ; - }; - - return Carregant...>} > - - { esEditor && } + { esEditor && (pobleSeleccionat || creantPoble) && } { }}>{ pobles .sort((a,b) => a.nomPoble?.toLowerCase() > b.nomPoble?.toLowerCase()) - .map(pob => - {pob.nomPoble}{esEditor && {setPobleSeleccionat(pob)}}>Edita}) + .map(pob => { + return + }) } - ; + + {esEditor && { + setCreantPoble(!creantPoble); + }} + >+} + >; }; \ No newline at end of file diff --git a/imports/ui/Usuaris.jsx b/imports/ui/Usuaris.jsx index d0a96cd..a1f0955 100644 --- a/imports/ui/Usuaris.jsx +++ b/imports/ui/Usuaris.jsx @@ -7,6 +7,438 @@ import { Roles } from 'meteor/roles'; // import { useUserId } from 'meteor/react-meteor-accounts'; +// import { Meteor } from 'meteor/meteor'; +// import React, {useState, useEffect, Suspense} from 'react'; +// import { useTracker, useFind } from 'meteor/react-meteor-data/suspense'; + +// import { FilesCol } from '/imports/api/files.js'; +import { useNavigate, Link } from 'react-router-dom'; + +// import { Roles } from 'meteor/roles'; +// import { useUserContext } from '/imports/api/contexts/AppContext'; +// import { groupBy } from 'lodash'; + +//import Radium from 'radium'; + + +// const IndicadorMissatges = ({notif}) => { +// return +// {notif && } +// +// ; +// }; + +const U = ({u, esAdministrador, setUsrSeleccionat}) => { + + const [adm, setAdm] = useState(false); + const [mon, setMon] = useState(false); + const [enc, setEnc] = useState(false); + const [vol, setVol] = useState(false); + + (async () => { + const esAdmin = await Roles.userIsInRoleAsync(u._id, ["admin"]); + setAdm(esAdmin); + const esMonit = await Roles.userIsInRoleAsync(u._id, ["monitor_de_poble"], {anyScope: true}); + setMon(esMonit); + const esEncar = await Roles.userIsInRoleAsync(u._id, ["encarregat_de_tasques"], {anyScope: true}); + setEnc(esEncar); + const esVolun = await Roles.userIsInRoleAsync(u._id, ["voluntari_de_poble"], {anyScope: true}); + setVol(esVolun); + // setEsAdministrador(comprovaAdmin); + })(); + + + // console.log("adm: ", adm); + // console.log("mon: ", mon); + // console.log("enc: ", enc); + // console.log("vol: ", vol); + + + // const [esAdministrador, setEsAdministrador] = useState(false); + +// const u = useTracker("user", async () => await Meteor.userAsync()); +// const userId = Meteor.userId(); + + + // useEffect(() => { + // (async () => { + // const comprovaAdmin = await Roles.userIsInRoleAsync(userId, ["admin"]); + // setEsAdministrador(comprovaAdmin); + // })(); + // }, [Meteor.userId()]); + + // const u = useUserContext(); + + + // console.log("UUU: ", u); + + const navigate = useNavigate(); + // const files = useTracker(() => { + // const filesHandle = Meteor.subscribe('files.avatar'); + // // const docsReadyYet = filesHandle.ready(); + // const files = FilesCol?.find({"meta.userId": Meteor.userId()}, {sort: {name: 1}}).fetch(); // Meteor.userId() ?? "nop" + + // return files; + // }); + +// const uname = useTracker(() => Meteor.user({ fields: { 'username': 1 } })?.username ); + +// const [mostraMenu, setMostraMenu] = useState(false); + + //const avatarLink = u.avatarLink; + + // alert("avLnk: ", u.profile.avatarLink); + + return + { + // setMostraMenu(true); + // }} + + // title="Logout" + + onClick={ev => { + ev.stopPropagation(); + ev.preventDefault(); + + // console.log("u: ", u); + + navigate(`/${u.username}`); + + }} + > + + { (u && adm) && ADMIN } + { (u && mon) && mon } + { (u && enc) && enc } + { (u && vol) && vol } + + + + {u?.username} + + { + ev.preventDefault(); + ev.stopPropagation(); + + // alert("Click en missatges"); + }} + > + { {setUsrSeleccionat(u)}}>Edita} + {/* */} + + + { + // mostraMenu && + // { + // // setMostraMenu(false);p + // // }} + // > + // {/* */} + // { + // //setEditaPerfil(true); + // ev.stopPropagation(); + // ev.preventDefault(); + + // navigate(`/c/config`); + + // }} + // >Configuración + // {/* */} + + // { + // Meteor.logout(err => { + // err && alert(`Problema: ${err}`); + // }); + // navigate(`/`); + // }} + // >Cierra la sesión + // + } + + ; +}; + + + +const AssignadorDeRols = ({pobles, esEditor, rols, usrSeleccionat, ambitsUSel}) => { + const [creantRol, setCreantRol] = useState(false); + const [ambitGeneral, setAmbitGeneral] = useState(false); + // const [ambitsUSel, setAmbitsUSel] = useState([]); + // let ambitsUSel; + // let ambitsUSel = []; + // useEffect(() => { + // (async () => { + // const ambits = await Roles.getScopesForUserAsync(usrSeleccionat?._id); + // // console.log("ambitsUSel: ", ambitsUSel); + // // ambitsUSel = ambits; + // // setAmbitsUSel(ambits); + // ambitsUSel = ambits; + // // console.log("ambitsUSel: ", ambitsUSel); + // })(); + // }, []); + + return + Rols + + Àmbits + General: + { + rols?.map(uRol => + {uRol} + {esEditor && { + ev.preventDefault(); + ev.stopPropagation(); + + confirm(`Vas a retirar el Rol ${uRol} a l'usuari ${usrSeleccionat.username}. Procedir?`) && Meteor.callAsync('retiraRols', uRol, usrSeleccionat._id) + }}>×} + ) + } + + {/* */} + { + ambitsUSel.map(async aus => { + + console.log("usrSeleccionat: ", usrSeleccionat); + console.log("ambitsUSel: ", ambitsUSel); + const uRol = await Roles.getRolesForUserAsync(usrSeleccionat._id, {scope: aus, onlyScoped: true}); + return <> + {aus}: + { + {uRol} + {esEditor && { + ev.preventDefault(); + ev.stopPropagation(); + + confirm(`Vas a retirar el Rol ${uRol} a l'usuari ${usrSeleccionat.username}. Procedir?`) && Meteor.callAsync('retiraRols', uRol, usrSeleccionat._id, aus) + }}>×} + + } + >; + }) + } + {/* */} + + {esEditor && creantRol && + { + if (ambitGeneral) { + Meteor.callAsync('assignaRol', usrSeleccionat._id, d.get('selRol')); + } else { + Meteor.callAsync('assignaRol', usrSeleccionat._id, d.get('selRol'), d.get('selAmbit')); + } + }}> + + Àmbit: setAmbitGeneral(ev.target.value === "GENERAL")} + > + { + pobles.sort((a,b) => a.ambitAssociat > b.ambitAssociat).map((pob,i) => {pob.ambitAssociat}) + } + General + + + Rol: + { + ambitGeneral + ? Administrador general + : <> + Monitor de poble + Encarregat de tasques + Voluntairi de poble + > + } + + + + + + } + + {esEditor && usrSeleccionat && { + ev.preventDefault(); + ev.stopPropagation(); + return false; + }} + onPointerUp={ev => { + ev.preventDefault(); + ev.stopPropagation(); + setCreantRol(!creantRol); + return false; + }} + >+} + ; +}; + +const QuadreInfo_Usuari = ({usrSeleccionat, esEditor, pobles, rols, ambitsUSel}) => { + + return + + {usrSeleccionat && {usrSeleccionat._id}} + + { + try { + Meteor.callAsync('editaOAfigPoble', + { + ...usrSeleccionat || [], + nomPoble: d.get('nomPoble'), + cp: d.get('cp') || "", + comarca: d.get('comarca') || "", + }).then(() => setUsrSeleccionat(null)) + .catch(err => console.error(err)); + } catch (err) { + alert(err); + console.error(err); + } + }} + > + Nom d'usuari: + Telèfon: + Correu Electrònic: + + { + ev.preventDefault(); + if (confirm(`Vas a eliminar el poble "${usrSeleccionat.nomPoble}". És una operació irreversible. Procedir?`)) { + Meteor.callAsync('eliminaPoble', usrSeleccionat._id).catch(err => console.error(err)); + setUsrSeleccionat(null); + } + }}>Elimina + + + { esEditor && } + ; +}; + + export const Usuaris = () => { const [usrSeleccionat, setUsrSeleccionat] = useState(null); @@ -18,90 +450,103 @@ 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()); }); // console.log("isAdmin: ", isAdmin) ; console.log("usuaris: ", usuaris); + + // const [ambitsUSel, setAmbitsUSel] = useState([]); // (async () => { - useEffect(() => { - (async () => { - const comprovaAdmin = await Roles.userIsInRoleAsync(userId, ["admin"]); - setEsEditor(comprovaAdmin); - })(); - - }, []); + + // let ambitsUSel = []; + + useEffect(() => { + (async () => { + const comprovaAdmin = await Roles.userIsInRoleAsync(userId, ["admin"]); + setEsEditor(comprovaAdmin); + + // console.log("ambitsUSel: ", ambitsUSel); + })(); + + }, []); + + const ambitsUSel = Roles.getScopesForUser(usrSeleccionat?._id); + // setAmbitsUSel(ambits); + + + const rols = useTracker("rols", async () => { + return await Roles.getRolesForUserAsync(usrSeleccionat?._id); + }); // })(); - const QuadreInfo_Usuari = () => { + // const [monitors, setMonitors] = useState(false); + // const [encarregats, setEncarregats] = useState(false); + // const [voluntaris, setVoluntaris] = useState(false); + // const [administradors, setAdministradors] = useState(false); + + // let mon, mons, enc, encs, vol, vols; + // let + // administradors, + // monitors, + // encarregats, + // voluntaris + // ; - return - - {usrSeleccionat && {usrSeleccionat._id}} - - { - try { - Meteor.callAsync('editaOAfigPoble', - { - ...usrSeleccionat || [], - nomPoble: d.get('nomPoble'), - cp: d.get('cp') || "", - comarca: d.get('comarca') || "", - }).then(() => setUsrSeleccionat(null)) - .catch(err => console.error(err)); - } catch (err) { - alert(err); - console.error(err); - } - }} - > - Nom d'usuari: - Telèfon: - Correu Electrònic: - - { - ev.preventDefault(); - if (confirm(`Vas a eliminar el poble "${usrSeleccionat.nomPoble}". És una operació irreversible. Procedir?`)) { - Meteor.callAsync('eliminaPoble', usrSeleccionat._id).catch(err => console.error(err)); - setUsrSeleccionat(null); - } - }}>Elimina - - ; - }; + + // (async () => { + // const admins = await Roles.getUsersInRoleAsync("administrador"); + // // console.log("admins: ", admins); + // administradors = await admins?.fetchAsync(); + // console.log("administradors: ", administradors); + + // const mons = await Roles.getUsersInRoleAsync("monitor_de_poble"); + // // console.log("mons: ", mons); + // monitors = await mons?.fetchAsync(); + // console.log("monitors: ", monitors); + + // const encs = await Roles.getUsersInRoleAsync("encarregat_de_tasques"); + // // console.log("encs: ", encs); + // encarregats = await encs?.fetchAsync(); + // console.log("encarregats: ", encarregats); + + // const vols = await Roles.getUsersInRoleAsync("voluntari_de_poble"); + // // console.log("vols: ", vols); + // voluntaris = await vols?.fetchAsync(); + // console.log("voluntaris: ", voluntaris); + // })(); + + + return Carregant...>} > Usuaris - { esEditor && } + { esEditor && } { usuaris .sort((a,b) => a.username?.toLowerCase() > b.username?.toLowerCase()) .map(usr => - {usr.username}{esEditor && {setUsrSeleccionat(usr)}}>Edita}) + {/* {usr.username}{esEditor && {setUsrSeleccionat(usr)}}>Edita} */} + + ) } ; }; \ No newline at end of file diff --git a/package.json b/package.json index 279ce12..505f5b4 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" @@ -22,5 +23,9 @@ "server": "server/main.js" }, "testModule": "tests/main.js" + }, + "devDependencies": { + "babel-plugin-react-compiler": "^19.0.0-beta-55955c9-20241229", + "eslint-plugin-react-compiler": "^19.0.0-beta-55955c9-20241229" } } diff --git a/server/main.js b/server/main.js index 1d38f67..98febb9 100644 --- a/server/main.js +++ b/server/main.js @@ -184,7 +184,16 @@ Meteor.startup(async () => { return PoblesCollection.find(); }); - Meteor.publish("necessitats", function () { + Meteor.publish("necessitats", function (ambits) { + + if (Array.isArray(ambits)) { + return; + } + + if (ambits) { + return NecessitatsCollection.find({"poble.ambitAssociat": ambits}); + } + return NecessitatsCollection.find(); }); @@ -194,13 +203,20 @@ Meteor.startup(async () => { Meteor.publish('usuaris', async function (uid) { - const esAdmin = await Roles.userIsInRoleAsync(Meteor.userId(), "admin"); - const userRoles = await Roles.getRolesForUserAsync(Meteor.userId()); + const esAdmin = await Roles.userIsInRoleAsync(this.userId, "admin", null); + const userRoles = await Roles.getRolesForUserAsync(this.userId); console.log("userRoles: ", userRoles); - if (uid) { - return Meteor.users.find({_id: uid},{fields: {username: 1, avatarId: 1, avatarLink: 1}}); + console.log("esAdmin: ", esAdmin); + + if (esAdmin) { + return Meteor.users.find({}); } + + if (uid) { + return Meteor.users.find({_id: uid}, {fields: {username: 1, avatarId: 1, avatarLink: 1}}); + } + return Meteor.users.find({},{fields: {username: 1, avatarId: 1, avatarLink: 1}}); }); //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// @@ -213,33 +229,53 @@ 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 () { - if (this.userId) { - return Meteor.roleAssignment.find({ "user._id": this.userId }); - } - this.ready(); +Meteor.publish(null, async function () { + // if (await Roles.userIsInRoleAsync(this.userId, "admin")) { + return Meteor.roleAssignment.find(); + // } + + // if (this.userId) { + // return Meteor.roleAssignment.find({ "user._id": this.userId }); + // } + // this.ready(); }); // Publish roles for specific scope // 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 +341,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 {
Tria el rol que adquirirà qui utilitze el codi.