Compare commits

..

12 Commits

Author SHA1 Message Date
69ef122f9c WIP push 2025-01-10 10:44:13 +01:00
52c381b592 pre codis 2025-01-09 18:27:36 +01:00
f8072fd2d7 Versió async de 'ambits' en assignació de rols 2025-01-08 03:39:28 +01:00
f3b9214019 Eliminat package-lock.json 2025-01-08 03:18:48 +01:00
7c21d374ba Després d'un clot 2025-01-08 03:12:15 +01:00
8c67900952 Assignació de rol admin 2025-01-08 03:05:30 +01:00
6efa6956d7 Resolt problema amb versió síncrona 2025-01-08 01:32:40 +01:00
182c1025ac Unmessing. Abans de provar les solucions de kapa ai 2025-01-08 00:55:39 +01:00
945cfa46e3 codis_1 2025-01-07 21:58:03 +01:00
d6e1329d79 codis_0 2025-01-07 12:08:38 +01:00
1ca37ff79b Abans del RRv7 2024-12-15 16:13:37 +01:00
2b1dd38b95 Abans d'actualitzar a Router v7 2024-12-15 15:29:55 +01:00
8 changed files with 560 additions and 3862 deletions

View File

@ -12,6 +12,7 @@ import { BarraNav } from './BarraNav/BarraNav';
import { UserStat } from './BarraNav/UserStat';
import { Usuaris } from './Usuaris';
import { Codis } from './Codis';
const Loguejat = lazy(async () => await import('/imports/ui/Loguejat.jsx'));
@ -54,6 +55,7 @@ export const App = () => {
<Route path="necessitats" element={ <Necessitats /> } />
<Route path="tipus" element={ <Tipus /> } />
<Route path="login" element={ <Login /> } />
{esAdministrador && <Route path="codis" element={ <Codis /> } />}
</Routes>
</BrowserRouter>
};

View File

@ -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}) => {
<SeccióPobles />
<SeccióNecessitats />
<SeccióTipus />
{ esAdministrador && <SeccióCodis />}
</PanellSeccions>;
};

View File

@ -34,15 +34,21 @@ export const SeccióUsuaris = () => <BotóSecció
linkto="usuaris"
/>;
export const SeccióCodis = () => <BotóSecció
titol="Codis"
linkto="codis"
/>;
export const PanellSeccions = ({children}) => {
return <><div style={{
display: `flex`,
border: `1px solid #cccc`,
padding: `.5rem`,
borderRadius: `.5rem`,
backgroundColor: `lightslategray`
backgroundColor: `lightslategray`,
flexWrap: `wrap`,
justifyContent: `space-around`
}}>{children}</div>
<br />
</>;
};

View File

@ -10,6 +10,7 @@ import { Roles } from 'meteor/roles';
// import { groupBy } from 'lodash';
//import Radium from 'radium';
import { useLongTap } from '../hooks';
const IndicadorMissatges = ({notif}) => {
@ -31,6 +32,10 @@ const UserStat = ({esAdministrador, setEsAdministrador}) => {
// const [esAdministrador, setEsAdministrador] = useState(false);
const { onPointerDown, onPointerUp, onPointerCancel, onPointerOut } = useLongTap(() => {
alert('Long tap detected!');
});
const u = useTracker("user", async () => await Meteor.userAsync());
const userId = Meteor.userId();
@ -80,21 +85,26 @@ const UserStat = ({esAdministrador, setEsAdministrador}) => {
fontWeight: `bold`,
display: `inline-block`
}}
onMouseEnter={ev => {
setMostraMenu(true);
}}
// onMouseEnter={ev => {
// setMostraMenu(true);
// }}
// title="Logout"
// // title="Logout"
onClick={ev => {
ev.stopPropagation();
ev.preventDefault();
// onClick={ev => {
// ev.stopPropagation();
// ev.preventDefault();
// console.log("u: ", u);
// // console.log("u: ", u);
navigate(`/${u.username}`);
// navigate(`/${u.username}`);
}}
// }}
onPointerDown={onPointerDown}
onPointerUp={onPointerUp}
onPointerCancel={onPointerCancel}
onPointerOut={onPointerOut}
>
{ (u && esAdministrador) && <div style={{
color: `red`,

252
imports/ui/Codis.jsx Normal file
View File

@ -0,0 +1,252 @@
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) ;
const [ambitSeleccionat, setAmbitSeleccionat] = useState(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 [ambitGeneral, setAmbitGeneral] = useState(ambitSeleccionat === "GENERAL");
const SelectorDeRol = () => {
return <>
<label htmlFor="selRol">Rol: </label>
<Select
key={Math.random()}
name="selRol"
// filterOption={(opts) => necessitats.find(nec => nec.tipus === tipusSeleccionat._id)}
options={
(ambitSeleccionat?.value === "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_Codis = () => {
return <div style={{
display: `inline-block`,
border: `1px solid #6666`,
padding: `.5rem`,
borderRadius: `.3em`,
backgroundColor: `lightcyan`
}}>
{ambitSeleccionat && <h1>{ambitSeleccionat?.value}</h1>}
<h1>Codis</h1>
<p>Tria el rol que adquirirà qui utilitze el codi.</p>
<form
action={d => {
// 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);
}
}}
>
<label htmlFor="selAmbit">Àmbit: </label>
{/* 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
*/}
<Select
name="selAmbit"
// formatCreateLabel={(inputValue) => "Crear nou tipus..."}
// filterOption={filterAmbit}
defaultValue={ambitSeleccionat}
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={amb => {
setAmbitSeleccionat(amb);
console.log("amb: ", amb);
}}
/>
{/* <select name="selTipus">
<option value="moble">Moble</option>
<option value="maquinari">Maquinari</option>
<option value="menjar">Menjar i beguda</option>
<option value="infantil">Infantils</option>
<option value="higiene">Higiene</option>
</select> */}
<br />
<SelectorDeRol {... {ambitSeleccionat}} />
<br />
<label htmlFor="actiu">Actiu? </label>
<input type="checkbox" name='actiu' />
<br /><br />
<label htmlFor="periode_validesa">Periode de validesa: </label>
<input type="date" name="periode_validesa" />
{/* <select name="selPoble">
{
pobles.map((v,i) => <option key={`optPob${i}`}>{v.nomPoble}</option>)
}
</select> */}
<br />
<input type='submit' value="Enviar" />
{ambitSeleccionat && esEditor && <button onClick={ev => {
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</button>}
</form>
</div>;
};
return <Suspense fallback={<>Carregant...</>} >
<QuadreInfo_Codis />
<br /><br />
{/* <AsyncCreatableSelect
cacheOptions
defaultOptions={tipus.map((v,i) => ({value: v, label: v.titol})) }
loadOptions={tipus.map((v,i) => ({value: v, label: v.titol}))}
/> */}
{/* <SelectSearch options={options} value="sv" name="language" placeholder="Choose your language" /> */}
<ul style={{
display: `flex`,
flexWrap: `wrap`,
justifyContent: `space-evenly`,
rowGap: `.3em`,
alignContent: `space-around`
}}>{
ambits
.sort((a,b) => a.toLowerCase() > b.toLowerCase())
.map(amb => <li
key={amb}
style={{
display: `block`,
border: `1px solid #6666`,
borderRadius: `.4em`,
padding: `4px`,
listStyle: `none`,
backgroundColor: `${'lightgreen' || 'lightcoral'}`
}}
>
{amb}{esEditor && <button onClick={() => {setAmbitSeleccionat({value: amb, label: amb})}}>Edita</button>}
</li>)
}</ul>
</Suspense>;
};

View File

@ -36,7 +36,7 @@ import { useNavigate, Link } from 'react-router-dom';
// </span>;
// };
const U = ({u, esAdministrador}) => {
const U = ({u, esAdministrador, setUsrSeleccionat}) => {
const [adm, setAdm] = useState(false);
const [mon, setMon] = useState(false);
@ -99,6 +99,7 @@ const U = ({u, esAdministrador}) => {
return <Suspense fallback="Carregant...">
<div
key={`u_${u._id}`}
style={{
// color: `lightblue`,
@ -190,9 +191,10 @@ const U = ({u, esAdministrador}) => {
ev.preventDefault();
ev.stopPropagation();
alert("Click en missatges");
// alert("Click en missatges");
}}
>
{<button onClick={() => {setUsrSeleccionat(u)}}>Edita</button>}
{/* <IndicadorMissatges notif={false} /> */}
</span>
</div>
@ -210,7 +212,7 @@ const U = ({u, esAdministrador}) => {
// zIndex: `200`
// }}
// // onMouseLeave={ev => {
// // setMostraMenu(false);
// // setMostraMenu(false);p
// // }}
// >
// {/* <Link to="/c/config"> */}
@ -245,69 +247,152 @@ const U = ({u, esAdministrador}) => {
};
export const Usuaris = () => {
const [usrSeleccionat, setUsrSeleccionat] = useState(null);
useSubscribe('pobles');
const pobles = useTracker("pobles", () => PoblesCollection.find().fetchAsync());
const [esEditor, setEsEditor] = useState(false);
const userId = Meteor.userId();
const usuaris = useTracker("usuaris", () => {
Meteor.subscribe('usuaris');
return Meteor.users.find().fetch();//.filter(u => u._id !== Meteor.userId());
});
// console.log("isAdmin: ", isAdmin) ;
console.log("usuaris: ", usuaris);
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 () => {
useEffect(() => {
(async () => {
const comprovaAdmin = await Roles.userIsInRoleAsync(userId, ["admin"]);
setEsEditor(comprovaAdmin);
})();
}, []);
// const ambits = await Roles.getScopesForUserAsync(usrSeleccionat?._id);
// // console.log("ambitsUSel: ", ambitsUSel);
// // ambitsUSel = ambits;
// // setAmbitsUSel(ambits);
// ambitsUSel = ambits;
// // console.log("ambitsUSel: ", ambitsUSel);
// })();
// }, []);
// const [monitors, setMonitors] = useState(false);
// const [encarregats, setEncarregats] = useState(false);
// const [voluntaris, setVoluntaris] = useState(false);
// const [administradors, setAdministradors] = useState(false);
return <div
style={{
display: `block`,
border: `1px solid #6666`,
padding: `.5rem`,
borderRadius: `.3em`,
margin: `1em`,
backgroundColor: `#fffa`
}}
>
<h3>Rols</h3>
// let mon, mons, enc, encs, vol, vols;
// let
// administradors,
// monitors,
// encarregats,
// voluntaris
// ;
<h4>Àmbits</h4>
<h5>General:</h5>
<ul>{
rols?.map(uRol => <li
key={`rol_${uRol}`}
style={{
display: `block`,
border: `1px solid #6666`,
borderRadius: `.4em`,
padding: `4px`,
listStyle: `none`,
backgroundColor: `${'orange'}`,
textAlign: `center`
}}
>
{uRol}
{esEditor && <button onClick={ev => {
ev.preventDefault();
ev.stopPropagation();
confirm(`Vas a retirar el Rol ${uRol} a l'usuari ${usrSeleccionat.username}. Procedir?`) && Meteor.callAsync('retiraRols', uRol, usrSeleccionat._id)
}}>&times;</button>}
</li>)
}</ul>
// (async () => {
// const admins = await Roles.getUsersInRoleAsync("administrador");
// // console.log("admins: ", admins);
// administradors = await admins?.fetchAsync();
// console.log("administradors: ", administradors);
{/* <ul> */}
{
ambitsUSel.map(async aus => {
// const mons = await Roles.getUsersInRoleAsync("monitor_de_poble");
// // console.log("mons: ", mons);
// monitors = await mons?.fetchAsync();
// console.log("monitors: ", monitors);
console.log("usrSeleccionat: ", usrSeleccionat);
console.log("ambitsUSel: ", ambitsUSel);
const uRol = await Roles.getRolesForUserAsync(usrSeleccionat._id, {scope: aus, onlyScoped: true});
return <>
<h5>{aus}:</h5>
{
<span style={{
display: `block`,
border: `1px solid #6666`,
borderRadius: `.4em`,
padding: `4px`,
listStyle: `none`,
backgroundColor: `${'orange'}`,
textAlign: `center`
}}>{uRol}
{esEditor && <button onClick={ev => {
ev.preventDefault();
ev.stopPropagation();
// const encs = await Roles.getUsersInRoleAsync("encarregat_de_tasques");
// // console.log("encs: ", encs);
// encarregats = await encs?.fetchAsync();
// console.log("encarregats: ", encarregats);
confirm(`Vas a retirar el Rol ${uRol} a l'usuari ${usrSeleccionat.username}. Procedir?`) && Meteor.callAsync('retiraRols', uRol, usrSeleccionat._id, aus)
}}>&times;</button>}
</span>
} <br />
</>;
})
}
{/* </ul> */}
// const vols = await Roles.getUsersInRoleAsync("voluntari_de_poble");
// // console.log("vols: ", vols);
// voluntaris = await vols?.fetchAsync();
// console.log("voluntaris: ", voluntaris);
// })();
{esEditor && creantRol && <div style={{
display: `block`,
border: `1px solid #6666`,
padding: `.5rem`,
borderRadius: `.3em`,
margin: `1em`,
backgroundColor: `#fffa`
}}>
<form action={d => {
if (ambitGeneral) {
Meteor.callAsync('assignaRol', usrSeleccionat._id, d.get('selRol'));
} else {
Meteor.callAsync('assignaRol', usrSeleccionat._id, d.get('selRol'), d.get('selAmbit'));
}
}}>
Àmbit: <select name="selAmbit" id="selAmbit"
onChange={ev => setAmbitGeneral(ev.target.value === "GENERAL")}
>
{
pobles.sort((a,b) => a.ambitAssociat > b.ambitAssociat).map((pob,i) => <option key={`optAmb_${i}`} value={pob.ambitAssociat}>{pob.ambitAssociat}</option>)
}
<option value={"GENERAL"}>General</option>
</select> <br />
const QuadreInfo_Usuari = () => {
Rol: <select name="selRol" id="selRol">
{
ambitGeneral
? <option value={"admin"}>Administrador general</option>
: <>
<option value="monitor_de_poble">Monitor de poble</option>
<option value="encarregat_de_tasques">Encarregat de tasques</option>
<option value="voluntari_de_poble">Voluntairi de poble</option>
</>
}
</select> <br /> <br />
<input type="submit" value="Estableix nou rol" />
</form>
</div>}
{esEditor && usrSeleccionat && <button
onClick={ev => {
ev.preventDefault();
ev.stopPropagation();
return false;
}}
onPointerUp={ev => {
ev.preventDefault();
ev.stopPropagation();
setCreantRol(!creantRol);
return false;
}}
>+</button>}
</div>;
};
const QuadreInfo_Usuari = ({usrSeleccionat, esEditor, pobles, rols, ambitsUSel}) => {
return <div style={{
display: `inline-block`,
@ -348,13 +433,100 @@ export const Usuaris = () => {
}
}}>Elimina</button>
</form>
{ esEditor && <Suspense>
<AssignadorDeRols {... {esEditor, usrSeleccionat, rols, pobles, ambitsUSel}} />
</Suspense>
}
</div>;
};
};
export const Usuaris = () => {
const [usrSeleccionat, setUsrSeleccionat] = useState(null);
useSubscribe('pobles');
const pobles = useTracker("pobles", () => PoblesCollection.find().fetchAsync());
const [esEditor, setEsEditor] = useState(false);
const userId = Meteor.userId();
const usuaris = useTracker("usuaris", () => {
Meteor.subscribe('usuaris');
return Meteor.users.find().fetch();//.filter(u => u._id !== Meteor.userId());
});
// console.log("isAdmin: ", isAdmin) ;
console.log("usuaris: ", usuaris);
// const [ambitsUSel, setAmbitsUSel] = useState([]);
// (async () => {
// let ambitsUSel = [];
useEffect(() => {
(async () => {
const comprovaAdmin = await Roles.userIsInRoleAsync(userId, ["admin"]);
setEsEditor(comprovaAdmin);
// console.log("ambitsUSel: ", ambitsUSel);
})();
}, []);
const ambitsUSel = useTracker("ambits", async () => {
return await Roles.getScopesForUserAsync(usrSeleccionat?._id);
});
// setAmbitsUSel(ambits);
const rols = useTracker("rols", async () => {
return await Roles.getRolesForUserAsync(usrSeleccionat?._id);
});
// })();
// 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
// ;
// (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 <Suspense fallback={<>Carregant...</>} >
<h1>Usuaris</h1>
{ esEditor && <QuadreInfo_Usuari /> }
{ esEditor && <QuadreInfo_Usuari {... {usrSeleccionat, esEditor, pobles, rols, ambitsUSel}} /> }
<ul style={{
display: `flex`,
@ -378,7 +550,7 @@ export const Usuaris = () => {
}}
>
{/* {usr.username}{esEditor && <button onClick={() => {setUsrSeleccionat(usr)}}>Edita</button>} */}
<U u={usr} esAdministrador={usr._id === Meteor.userId() && esEditor} />
<U u={usr} esAdministrador={usr._id === Meteor.userId() && esEditor} setUsrSeleccionat={setUsrSeleccionat} />
</li>)
}</ul>
</Suspense>;

46
imports/ui/hooks.jsx Normal file
View File

@ -0,0 +1,46 @@
import { useState, useEffect } from 'react';
const useLongTap = (onLongTap, duration = 500) => {
const [isPressing, setIsPressing] = useState(false);
const [startTime, setStartTime] = useState(null);
useEffect(() => {
if (isPressing && startTime) {
const elapsedTime = performance.now() - startTime;
if (elapsedTime >= duration) {
onLongTap();
setIsPressing(false);
setStartTime(null);
}
}
}, [isPressing, startTime, onLongTap, duration]);
const handlePointerDown = (event) => {
setIsPressing(true);
setStartTime(performance.now());
};
const handlePointerUp = () => {
setIsPressing(false);
setStartTime(null);
};
const handlePointerCancel = () => {
setIsPressing(false);
setStartTime(null);
};
const handlePointerOut = () => {
setIsPressing(false);
setStartTime(null);
};
return {
onPointerDown: handlePointerDown,
onPointerUp: handlePointerUp,
onPointerCancel: handlePointerCancel,
onPointerOut: handlePointerOut,
};
};
export { useLongTap };

3795
package-lock.json generated

File diff suppressed because it is too large Load Diff