wizards onder roostermaker/personen + snelmenu leerplein
This commit is contained in:
parent
f0c7b3644f
commit
31d037d8af
5 changed files with 28 additions and 21 deletions
|
|
@ -19,9 +19,9 @@ volgt.
|
||||||
T (deze week), Esc (paneel/sluiten); met hint.
|
T (deze week), Esc (paneel/sluiten); met hint.
|
||||||
- [x] **Rol van een begeleider zichtbaar** in de Roostermaker: afkorting (LK/PO/KO)
|
- [x] **Rol van een begeleider zichtbaar** in de Roostermaker: afkorting (LK/PO/KO)
|
||||||
bij de begeleider in een blok en de volledige rol bij het toewijzen.
|
bij de begeleider in een blok en de volledige rol bij het toewijzen.
|
||||||
- [x] **Vak-wizard** (module `blokwizard`, default uit): stap-voor-stap vak →
|
- [x] **Vak-wizard** (module `blokwizard`, default uit; verschijnt als tab in de Roostermaker): stap-voor-stap vak →
|
||||||
voor wie → wanneer/waar (+periode) → begeleiders → controle → inplannen.
|
voor wie → wanneer/waar (+periode) → begeleiders → controle → inplannen.
|
||||||
- [x] **Personen-wizard + import** (module `personenwizard`, default uit):
|
- [x] **Personen-wizard + import** (module `personenwizard`, default uit; tab onder Personen):
|
||||||
leerling/medewerker enkel toevoegen óf in bulk via plakken/CSV (komma,
|
leerling/medewerker enkel toevoegen óf in bulk via plakken/CSV (komma,
|
||||||
puntkomma of tab; koprij optioneel) met preview; endpoint /api/personen-import/.
|
puntkomma of tab; koprij optioneel) met preview; endpoint /api/personen-import/.
|
||||||
|
|
||||||
|
|
@ -30,7 +30,7 @@ volgt.
|
||||||
- [~] **Alle klassen verwerkbaar in het rooster**, overzichtelijk gemaakt — een
|
- [~] **Alle klassen verwerkbaar in het rooster**, overzichtelijk gemaakt — een
|
||||||
**scope-filter** (per klas/leerplein/leerling/personeel) is toegevoegd in
|
**scope-filter** (per klas/leerplein/leerling/personeel) is toegevoegd in
|
||||||
Rooster en Roostermaker; een vast **per-leerplein-overzicht** kan nog volgen.
|
Rooster en Roostermaker; een vast **per-leerplein-overzicht** kan nog volgen.
|
||||||
- [ ] **Snelmenu** om per klas of per leerplein direct het rooster te tonen.
|
- [x] **Snelmenu** per klas én per bovenliggende groep (chips in Rooster én Roostermaker) om direct dat rooster te tonen.
|
||||||
- [x] **Schooljaar uit het hoofdmenu** halen en **onder Rooster** plaatsen
|
- [x] **Schooljaar uit het hoofdmenu** halen en **onder Rooster** plaatsen
|
||||||
(tab "Schooljaar") met snelle invoer + kalenderbeheer.
|
(tab "Schooljaar") met snelle invoer + kalenderbeheer.
|
||||||
- [x] **Instellingenmenu** (subtiel onderaan de zijbalk) waaronder **Gebruikers**
|
- [x] **Instellingenmenu** (subtiel onderaan de zijbalk) waaronder **Gebruikers**
|
||||||
|
|
|
||||||
|
|
@ -22,8 +22,7 @@ class BlokwizardConfig(AppConfig):
|
||||||
category="Hulpmiddelen",
|
category="Hulpmiddelen",
|
||||||
core=False,
|
core=False,
|
||||||
default_enabled=False,
|
default_enabled=False,
|
||||||
menu_items=(
|
# Geen eigen menu-item: de wizard verschijnt als tab in de Roostermaker.
|
||||||
MenuItem("Vak-wizard", "/vak-wizard", icon="puzzle", order=40),
|
menu_items=(),
|
||||||
),
|
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -22,8 +22,7 @@ class PersonenwizardConfig(AppConfig):
|
||||||
category="Hulpmiddelen",
|
category="Hulpmiddelen",
|
||||||
core=False,
|
core=False,
|
||||||
default_enabled=False,
|
default_enabled=False,
|
||||||
menu_items=(
|
# Geen eigen menu-item: verschijnt als tab onder Personen.
|
||||||
MenuItem("Personen toevoegen", "/personen-wizard", icon="users", order=42),
|
menu_items=(),
|
||||||
),
|
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -344,6 +344,8 @@ export function PersonenPage() {
|
||||||
const [editId, setEditId] = useState(null);
|
const [editId, setEditId] = useState(null);
|
||||||
const [filter, setFilter] = useState("");
|
const [filter, setFilter] = useState("");
|
||||||
const [nieuweFunctie, setNieuweFunctie] = useState("");
|
const [nieuweFunctie, setNieuweFunctie] = useState("");
|
||||||
|
const [wizardAan, setWizardAan] = useState(false);
|
||||||
|
const [importView, setImportView] = useState(false);
|
||||||
|
|
||||||
async function maakFunctie() {
|
async function maakFunctie() {
|
||||||
const naam = nieuweFunctie.trim();
|
const naam = nieuweFunctie.trim();
|
||||||
|
|
@ -362,6 +364,7 @@ export function PersonenPage() {
|
||||||
listResource("activiteiten").then(setVakken).catch(() => {});
|
listResource("activiteiten").then(setVakken).catch(() => {});
|
||||||
listResource("subgroepen").then(setSubgroepen).catch(() => {});
|
listResource("subgroepen").then(setSubgroepen).catch(() => {});
|
||||||
listResource("leerpleinen").then(setLeerpleinen).catch(() => setLeerpleinen([]));
|
listResource("leerpleinen").then(setLeerpleinen).catch(() => setLeerpleinen([]));
|
||||||
|
getModules().then((ms) => setWizardAan(ms.some((m) => m.key === "personenwizard" && m.enabled))).catch(() => {});
|
||||||
}
|
}
|
||||||
useEffect(() => { laadHulplijsten(); }, []);
|
useEffect(() => { laadHulplijsten(); }, []);
|
||||||
|
|
||||||
|
|
@ -445,10 +448,13 @@ export function PersonenPage() {
|
||||||
<Foutmelding msg={error} />
|
<Foutmelding msg={error} />
|
||||||
|
|
||||||
<div className="tabbar">
|
<div className="tabbar">
|
||||||
<button className={modus === "leerling" ? "tab on" : "tab"} onClick={() => kiesModus("leerling")}>Leerling toevoegen</button>
|
<button className={!importView && modus === "leerling" ? "tab on" : "tab"} onClick={() => { kiesModus("leerling"); setImportView(false); }}>Leerling toevoegen</button>
|
||||||
<button className={modus === "medewerker" ? "tab on" : "tab"} onClick={() => kiesModus("medewerker")}>Medewerker toevoegen</button>
|
<button className={!importView && modus === "medewerker" ? "tab on" : "tab"} onClick={() => { kiesModus("medewerker"); setImportView(false); }}>Medewerker toevoegen</button>
|
||||||
|
{wizardAan && <button className={importView ? "tab on" : "tab"} onClick={() => setImportView(true)}>📋 Bulk / CSV-import</button>}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{importView ? <PersonenWizardPage /> : (<>
|
||||||
|
|
||||||
<form className="form-card" onSubmit={opslaan}>
|
<form className="form-card" onSubmit={opslaan}>
|
||||||
<div className="form-row">
|
<div className="form-row">
|
||||||
<input placeholder="Voornaam" value={form.voornaam} required onChange={(e) => setForm({ ...form, voornaam: e.target.value })} />
|
<input placeholder="Voornaam" value={form.voornaam} required onChange={(e) => setForm({ ...form, voornaam: e.target.value })} />
|
||||||
|
|
@ -564,6 +570,7 @@ export function PersonenPage() {
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
)}
|
)}
|
||||||
|
</>)}
|
||||||
</section>
|
</section>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -1892,6 +1899,7 @@ function Groepsindeling({ groepen, leerpleinen, subgroepen, medewerkers, blokken
|
||||||
export function RoosterScherm({ mode }) {
|
export function RoosterScherm({ mode }) {
|
||||||
const edit = mode === "edit";
|
const edit = mode === "edit";
|
||||||
const [printAan, setPrintAan] = useState(false);
|
const [printAan, setPrintAan] = useState(false);
|
||||||
|
const [blokwizardAan, setBlokwizardAan] = useState(false);
|
||||||
const [schooljaren, setSchooljaren] = useState([]);
|
const [schooljaren, setSchooljaren] = useState([]);
|
||||||
const [schooljaar, setSchooljaar] = useState("");
|
const [schooljaar, setSchooljaar] = useState("");
|
||||||
const [tijdsloten, setTijdsloten] = useState([]);
|
const [tijdsloten, setTijdsloten] = useState([]);
|
||||||
|
|
@ -1942,7 +1950,7 @@ export function RoosterScherm({ mode }) {
|
||||||
listResource("leerpleinen").then(setLeerpleinen).catch(() => {});
|
listResource("leerpleinen").then(setLeerpleinen).catch(() => {});
|
||||||
listResource("stages", "?actief=1").then(setStages).catch(() => setStages([]));
|
listResource("stages", "?actief=1").then(setStages).catch(() => setStages([]));
|
||||||
// Afdrukken alleen tonen als de print-module aanstaat.
|
// Afdrukken alleen tonen als de print-module aanstaat.
|
||||||
getModules().then((ms) => setPrintAan(ms.some((m) => m.key === "printing" && m.enabled))).catch(() => {});
|
getModules().then((ms) => { setPrintAan(ms.some((m) => m.key === "printing" && m.enabled)); setBlokwizardAan(ms.some((m) => m.key === "blokwizard" && m.enabled)); }).catch(() => {});
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
// Scope-context + gefilterde blokken (voor weergave/afdrukken).
|
// Scope-context + gefilterde blokken (voor weergave/afdrukken).
|
||||||
|
|
@ -2360,18 +2368,14 @@ export function RoosterScherm({ mode }) {
|
||||||
</select>
|
</select>
|
||||||
)}
|
)}
|
||||||
{scopeType !== "alles" && scopeId && <span className="muted small">{zichtbareBlokken.length} blok(ken)</span>}
|
{scopeType !== "alles" && scopeId && <span className="muted small">{zichtbareBlokken.length} blok(ken)</span>}
|
||||||
{edit && groepen.length > 0 && (
|
{(groepen.length > 0 || leerpleinen.length > 0) && (
|
||||||
<div className="klas-strip">
|
<div className="klas-strip">
|
||||||
<button type="button" className={scopeType === "alles" ? "chip on" : "chip"} onClick={() => { setScopeType("alles"); setScopeId(""); }}>Alle</button>
|
<button type="button" className={scopeType === "alles" ? "chip on" : "chip"} onClick={() => { setScopeType("alles"); setScopeId(""); }}>Alle</button>
|
||||||
{groepen.map((g) => (
|
{groepen.map((g) => (
|
||||||
<button
|
<button type="button" key={"g" + g.id} className={scopeType === "klas" && Number(scopeId) === g.id ? "chip on" : "chip"} onClick={() => { setScopeType("klas"); setScopeId(String(g.id)); }}>{g.naam}</button>
|
||||||
type="button"
|
))}
|
||||||
key={g.id}
|
{leerpleinen.map((lp) => (
|
||||||
className={scopeType === "klas" && Number(scopeId) === g.id ? "chip on" : "chip"}
|
<button type="button" key={"lp" + lp.id} className={scopeType === "leerplein" && Number(scopeId) === lp.id ? "chip on chip-plein" : "chip chip-plein"} onClick={() => { setScopeType("leerplein"); setScopeId(String(lp.id)); }}>{lp.naam}</button>
|
||||||
onClick={() => { setScopeType("klas"); setScopeId(String(g.id)); }}
|
|
||||||
>
|
|
||||||
{g.naam}
|
|
||||||
</button>
|
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
@ -2383,6 +2387,7 @@ export function RoosterScherm({ mode }) {
|
||||||
<button className={tab === "week" ? "tab on" : "tab"} title="Weekrooster" onClick={() => setTab("week")}>▦ Week</button>
|
<button className={tab === "week" ? "tab on" : "tab"} title="Weekrooster" onClick={() => setTab("week")}>▦ Week</button>
|
||||||
<button className={tab === "conflicten" ? "tab on" : "tab"} title="Conflicten" onClick={() => setTab("conflicten")}>⚠ Conflicten</button>
|
<button className={tab === "conflicten" ? "tab on" : "tab"} title="Conflicten" onClick={() => setTab("conflicten")}>⚠ Conflicten</button>
|
||||||
<button className={tab === "schooljaar" ? "tab on" : "tab"} title="Schooljaar" onClick={() => setTab("schooljaar")}>◷ Jaar</button>
|
<button className={tab === "schooljaar" ? "tab on" : "tab"} title="Schooljaar" onClick={() => setTab("schooljaar")}>◷ Jaar</button>
|
||||||
|
{blokwizardAan && <button className={tab === "wizard" ? "tab on" : "tab"} title="Vak-wizard" onClick={() => setTab("wizard")}>✨ Wizard</button>}
|
||||||
<button className={tab === "instellingen" ? "tab on" : "tab"} title="Instellingen" onClick={() => setTab("instellingen")}>⚙ Instellingen</button>
|
<button className={tab === "instellingen" ? "tab on" : "tab"} title="Instellingen" onClick={() => setTab("instellingen")}>⚙ Instellingen</button>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
@ -2540,6 +2545,8 @@ export function RoosterScherm({ mode }) {
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{edit && tab === "wizard" && blokwizardAan && <VakWizardPage />}
|
||||||
|
|
||||||
{tab === "instellingen" && (
|
{tab === "instellingen" && (
|
||||||
<div className="instellingen">
|
<div className="instellingen">
|
||||||
<p className="muted">Bouwstenen van het rooster — beheer ze hier zonder het Rooster te verlaten.</p>
|
<p className="muted">Bouwstenen van het rooster — beheer ze hier zonder het Rooster te verlaten.</p>
|
||||||
|
|
|
||||||
|
|
@ -487,3 +487,5 @@ input[type="color"] { padding: 2px; width: 44px; min-width: 44px; height: 38px;
|
||||||
.keuze-emoji { font-size: 40px; line-height: 1; }
|
.keuze-emoji { font-size: 40px; line-height: 1; }
|
||||||
.bulk-invoer { width: 100%; font-family: monospace; font-size: 13px; padding: 10px; border: 1px solid var(--line); border-radius: 8px; }
|
.bulk-invoer { width: 100%; font-family: monospace; font-size: 13px; padding: 10px; border: 1px solid var(--line); border-radius: 8px; }
|
||||||
.banner.ok { background: #ecfdf5; color: #065f46; border: 1px solid #a7f3d0; padding: 10px 14px; border-radius: 8px; }
|
.banner.ok { background: #ecfdf5; color: #065f46; border: 1px solid #a7f3d0; padding: 10px 14px; border-radius: 8px; }
|
||||||
|
|
||||||
|
.chip.chip-plein { border-style: dashed; }
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue