Rooster gesplitst in weergave + Roostermaker, printmodule afgebouwd (subtiele Afdrukken-knop met print-CSS)
This commit is contained in:
parent
3318ae601e
commit
e8e0022359
7 changed files with 89 additions and 30 deletions
|
|
@ -18,6 +18,12 @@ rustiger is). De zijbalk groepeert items onder inklapbare submenu's (Personen,
|
|||
Groepen) en heeft onderaan een subtiel **Instellingen**-blok met Gebruikers en
|
||||
Modulebeheer.
|
||||
|
||||
Er zijn twee rooster-schermen: **Rooster** (alleen-lezen weergave, om te bekijken
|
||||
en af te drukken) en **Roostermaker** (de aanpasser met drag-and-drop, tabs en
|
||||
conflictdetectie). De module **Printen/Exporteren** voegt — als die aanstaat — een
|
||||
subtiele **Afdrukken**-knop toe aan beide; die drukt het weekrooster
|
||||
printvriendelijk af (of bewaart als PDF via de printdialoog).
|
||||
|
||||
Optionele module **Leerplein**: koppelt subgroepen aan groepen en bundelt
|
||||
groepen, subgroepen én losse leerlingen onder een overkoepelend leerplein.
|
||||
Optionele module **Stage**: interne en externe stages van leerlingen — beide met
|
||||
|
|
|
|||
16
ROADMAP.md
16
ROADMAP.md
|
|
@ -48,6 +48,22 @@ volgt.
|
|||
(de betreffende blokken vervallen voor die leerling).
|
||||
- [ ] Stage instelbaar **per blok, per dagdeel of per dag**.
|
||||
|
||||
## Module: Urenoverzicht
|
||||
|
||||
- [ ] **Module die berekent hoeveel uur een groep per vak krijgt.**
|
||||
- [ ] Optie om dit **per leerling** te tonen, **inclusief stage-uren**.
|
||||
- [ ] **Geschiedenis** van dit overzicht **per leerling** tonen (verloop over de
|
||||
tijd).
|
||||
- [ ] **Vrije dagen tellen niet mee** in deze urenberekening.
|
||||
|
||||
## Roostermaker — vrije dagen & blok vervallen
|
||||
|
||||
- [ ] **Vrije/vakantie-/studiedagen kunnen niet gebruikt worden** in de
|
||||
Roostermaker (geen blokken plannen op die dagen).
|
||||
- [ ] De actie **"Deze dag laten vervallen"** subtieler wegwerken **en de tekst
|
||||
corrigeren** — het is per *blok*, niet per dag.
|
||||
- [ ] Mogelijkheid om **een hele dag op vervallen** te zetten (i.p.v. per blok).
|
||||
|
||||
## Later
|
||||
|
||||
- [ ] **Module: automatisch rooster genereren.** De gebruiker geeft vereisten/
|
||||
|
|
|
|||
|
|
@ -27,8 +27,9 @@ class CoreConfig(AppConfig):
|
|||
category="Kern",
|
||||
core=True,
|
||||
menu_items=(
|
||||
# Hoofdniveau (Schooljaar zit nu als tab in het Rooster)
|
||||
# Hoofdniveau: Rooster = weergave, Roostermaker = aanpasser.
|
||||
MenuItem("Rooster", "/rooster", icon="calendar", order=10),
|
||||
MenuItem("Roostermaker", "/roostermaker", icon="calendar", order=12),
|
||||
# Submenu Personen
|
||||
MenuItem("Personen", "/personen", icon="users", order=20, group="Personen"),
|
||||
MenuItem("Functies", "/functies", icon="badge", order=25, group="Personen"),
|
||||
|
|
|
|||
|
|
@ -15,15 +15,16 @@ class PrintingConfig(AppConfig):
|
|||
key="printing",
|
||||
name="Printen / Exporteren",
|
||||
description=(
|
||||
"Roosters afdrukken of als PDF exporteren, per groep, "
|
||||
"leerling of leerkracht."
|
||||
"Voegt een subtiele 'Afdrukken'-knop toe aan het Rooster en de "
|
||||
"Roostermaker, die het weekrooster printvriendelijk afdrukt "
|
||||
"(of als PDF bewaart via de printdialoog)."
|
||||
),
|
||||
version="0.1.0",
|
||||
category="Uitvoer",
|
||||
core=False,
|
||||
default_enabled=True,
|
||||
menu_items=(
|
||||
MenuItem("Afdrukken", "/afdrukken", icon="printer", order=200),
|
||||
),
|
||||
# Geen eigen menu-item: de knop verschijnt in de rooster-schermen
|
||||
# zodra deze module aanstaat.
|
||||
menu_items=(),
|
||||
)
|
||||
)
|
||||
|
|
|
|||
|
|
@ -3,7 +3,8 @@ import { getMe, login, logout, getModules, setModuleState } from "./api.js";
|
|||
import Logo from "./Logo.jsx";
|
||||
import {
|
||||
GroepenPage, PersonenPage, SubgroepenPage, FunctiesPage,
|
||||
ActiviteitenPage, LocatiesPage, TijdslotenPage, SchooljaarPage, RoosterPage,
|
||||
ActiviteitenPage, LocatiesPage, TijdslotenPage, SchooljaarPage,
|
||||
RoosterWeergavePage, RoostermakerPage,
|
||||
AfwezigheidPage, LeerpleinPage, StagePage,
|
||||
} from "./pages.jsx";
|
||||
|
||||
|
|
@ -16,7 +17,8 @@ const ICONS = {
|
|||
// Koppel een menu-pad aan een echt scherm. Onbekende paden tonen een
|
||||
// placeholder (worden in een latere fase ingevuld).
|
||||
const PAGES = {
|
||||
"/rooster": RoosterPage,
|
||||
"/rooster": RoosterWeergavePage,
|
||||
"/roostermaker": RoostermakerPage,
|
||||
"/personen": PersonenPage,
|
||||
"/functies": FunctiesPage,
|
||||
"/groepen": GroepenPage,
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import {
|
|||
createResource,
|
||||
updateResource,
|
||||
deleteResource,
|
||||
getModules,
|
||||
} from "./api.js";
|
||||
|
||||
const ROLLEN = [
|
||||
|
|
@ -1013,9 +1014,12 @@ function maandagVan(d) { const x = new Date(d); x.setHours(0, 0, 0, 0); const wd
|
|||
function plusDagen(d, n) { const x = new Date(d); x.setDate(x.getDate() + n); return x; }
|
||||
|
||||
/* ---------------------------------- Rooster ------------------------------- */
|
||||
// Weekweergave met effectief rooster (vrije dagen, afwezigheid, uitzonderingen)
|
||||
// en drag-and-drop (muis + touch) om een blok naar een ander tijdslot te slepen.
|
||||
export function RoosterPage() {
|
||||
// Eén component met twee modi:
|
||||
// - mode="view" -> Rooster (alleen-lezen weergave, om te bekijken/printen)
|
||||
// - mode="edit" -> Roostermaker (volledige bewerking + drag-and-drop)
|
||||
export function RoosterScherm({ mode }) {
|
||||
const edit = mode === "edit";
|
||||
const [printAan, setPrintAan] = useState(false);
|
||||
const [schooljaren, setSchooljaren] = useState([]);
|
||||
const [schooljaar, setSchooljaar] = useState("");
|
||||
const [tijdsloten, setTijdsloten] = useState([]);
|
||||
|
|
@ -1052,6 +1056,8 @@ export function RoosterPage() {
|
|||
listResource("subgroepen").then(setSubgroepen).catch(() => {});
|
||||
listResource("personen", "?rol=leerling").then(setLeerlingen).catch(() => {});
|
||||
listResource("personen").then((ps) => setMedewerkers(ps.filter((p) => p.rol !== "leerling"))).catch(() => {});
|
||||
// Afdrukken alleen tonen als de print-module aanstaat.
|
||||
getModules().then((ms) => setPrintAan(ms.some((m) => m.key === "printing" && m.enabled))).catch(() => {});
|
||||
}, []);
|
||||
|
||||
const weekDatums = [0, 1, 2, 3, 4].map((i) => plusDagen(maandag, i));
|
||||
|
|
@ -1140,12 +1146,16 @@ export function RoosterPage() {
|
|||
const persoonAfwezig = (id, iso) => afwezig.some((a) => a.persoon === id && a.start_datum <= iso && a.eind_datum >= iso);
|
||||
|
||||
return (
|
||||
<section>
|
||||
<h1>Rooster</h1>
|
||||
<p className="muted">Sleep een blok naar een ander tijdslot om het te verplaatsen. Vrije dagen, afwezigheid en uitzonderingen worden meegenomen.</p>
|
||||
<section className="rooster-scherm">
|
||||
<h1>{edit ? "Roostermaker" : "Rooster"}</h1>
|
||||
<p className="muted no-print">
|
||||
{edit
|
||||
? "Sleep een blok naar een ander tijdslot om het te verplaatsen. Vrije dagen, afwezigheid en uitzonderingen worden meegenomen."
|
||||
: "Bekijk het weekrooster. Vrije dagen, afwezigheid en uitzonderingen zijn verwerkt."}
|
||||
</p>
|
||||
<Foutmelding msg={error} />
|
||||
|
||||
<div className="rooster-balk">
|
||||
<div className="rooster-balk no-print">
|
||||
<label className="veld">Schooljaar
|
||||
<select value={schooljaar} onChange={(e) => setSchooljaar(e.target.value)}>
|
||||
<option value="">— kies —</option>
|
||||
|
|
@ -1160,18 +1170,23 @@ export function RoosterPage() {
|
|||
<span className="week-label">week van {fmtDM(maandag)}</span>
|
||||
</div>
|
||||
)}
|
||||
{schooljaar && tab === "week" && <button className="btn primary" onClick={() => setToonForm((v) => !v)}>{toonForm ? "Sluiten" : "+ Blok"}</button>}
|
||||
{edit && schooljaar && tab === "week" && <button className="btn primary" onClick={() => setToonForm((v) => !v)}>{toonForm ? "Sluiten" : "+ Blok"}</button>}
|
||||
{printAan && schooljaar && (edit ? tab === "week" : true) && (
|
||||
<button className="btn print-knop" title="Rooster afdrukken" onClick={() => window.print()}>🖨 Afdrukken</button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="tabbar">
|
||||
{edit && (
|
||||
<div className="tabbar no-print">
|
||||
<button className={tab === "week" ? "tab on" : "tab"} onClick={() => setTab("week")}>Weekrooster</button>
|
||||
<button className={tab === "conflicten" ? "tab on" : "tab"} onClick={() => setTab("conflicten")}>Conflicten</button>
|
||||
<button className={tab === "schooljaar" ? "tab on" : "tab"} onClick={() => setTab("schooljaar")}>Schooljaar</button>
|
||||
<button className={tab === "instellingen" ? "tab on" : "tab"} onClick={() => setTab("instellingen")}>Instellingen</button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{schooljaar && tab === "week" && toonForm && (
|
||||
<form className="form-card" onSubmit={opslaan}>
|
||||
{edit && schooljaar && tab === "week" && toonForm && (
|
||||
<form className="form-card no-print" onSubmit={opslaan}>
|
||||
<div className="form-row">
|
||||
<select value={form.tijdslot} required onChange={(e) => setForm({ ...form, tijdslot: e.target.value })}>
|
||||
<option value="">— tijdslot —</option>
|
||||
|
|
@ -1244,9 +1259,9 @@ export function RoosterPage() {
|
|||
style={{ borderLeftColor: b.activiteit_kleur }}
|
||||
>
|
||||
<div className="rb-top">
|
||||
<span className="rb-greep" onPointerDown={(e) => startDrag(e, b)} title="Sleep naar ander tijdslot">⠿</span>
|
||||
{edit && <span className="rb-greep" onPointerDown={(e) => startDrag(e, b)} title="Sleep naar ander tijdslot">⠿</span>}
|
||||
<strong>{b.activiteit_naam}</strong>
|
||||
<button className="link danger rb-x" onClick={() => verwijder(b.id)} title="Blok verwijderen">×</button>
|
||||
{edit && <button className="link danger rb-x" onClick={() => verwijder(b.id)} title="Blok verwijderen">×</button>}
|
||||
</div>
|
||||
<div className="small">🎯 {b.doel_naam} <span className="muted">({b.doel_type})</span></div>
|
||||
{b.locatie_naam && <div className="small">📍 {b.locatie_naam}</div>}
|
||||
|
|
@ -1256,10 +1271,10 @@ export function RoosterPage() {
|
|||
))}</div>
|
||||
)}
|
||||
{kal && <div className="small rb-status">Vervalt — {kal.type_label}</div>}
|
||||
{!kal && uit && uit.vervalt && <div className="small rb-status">Vervalt {uit.opmerking ? `(${uit.opmerking})` : ""} · <button className="link" onClick={() => herstel(uit.id)}>herstel</button></div>}
|
||||
{!kal && uit && uit.vervalt && <div className="small rb-status">Vervalt {uit.opmerking ? `(${uit.opmerking})` : ""}{edit && <> · <button className="link" onClick={() => herstel(uit.id)}>herstel</button></>}</div>}
|
||||
{!kal && uit && !uit.vervalt && uit.vervanger_naam && <div className="small rb-verv">↪ Vervanger: {uit.vervanger_naam}</div>}
|
||||
{!kal && !uit && (
|
||||
<button className="link danger small rb-verval" onClick={() => laatVervallen(b, iso)}>deze dag laten vervallen</button>
|
||||
{edit && !kal && !uit && (
|
||||
<button className="link danger small rb-verval no-print" onClick={() => laatVervallen(b, iso)}>deze dag laten vervallen</button>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
|
|
@ -1291,7 +1306,11 @@ export function RoosterPage() {
|
|||
</div>
|
||||
)}
|
||||
|
||||
{ghost && <div className="drag-ghost" style={{ left: ghost.x + 10, top: ghost.y + 10 }}>{ghost.label}</div>}
|
||||
{edit && ghost && <div className="drag-ghost" style={{ left: ghost.x + 10, top: ghost.y + 10 }}>{ghost.label}</div>}
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
// Twee schermen op basis van dezelfde component.
|
||||
export function RoosterWeergavePage() { return <RoosterScherm mode="view" />; }
|
||||
export function RoostermakerPage() { return <RoosterScherm mode="edit" />; }
|
||||
|
|
|
|||
|
|
@ -147,6 +147,20 @@ input[type="color"] { padding: 2px; width: 44px; min-width: 44px; height: 38px;
|
|||
|
||||
.drag-ghost { position: fixed; z-index: 1000; pointer-events: none; background: var(--brand); color: #fff; padding: 6px 12px; border-radius: 8px; font-size: 13px; font-weight: 600; box-shadow: 0 6px 18px rgba(0,0,0,.25); }
|
||||
|
||||
/* --- Afdrukken --- */
|
||||
.print-knop { margin-left: auto; background: #fff; }
|
||||
@media print {
|
||||
.sidebar, .no-print, .tabbar, .rooster-balk, .banner,
|
||||
.rb-greep, .rb-x, .rb-verval, .print-knop, .drag-ghost { display: none !important; }
|
||||
body { background: #fff; }
|
||||
.app { display: block; }
|
||||
.content { padding: 0; }
|
||||
.rooster-scherm h1 { margin: 0 0 8px; }
|
||||
.rooster-grid { grid-template-columns: repeat(5, 1fr); gap: 4px; }
|
||||
.rooster-kolom { border-color: #999; }
|
||||
.rooster-blok { break-inside: avoid; }
|
||||
}
|
||||
|
||||
/* --- Onderkant zijbalk: subtiel Instellingen-blok --- */
|
||||
.sidebar-foot { margin-top: auto; padding-top: 10px; border-top: 1px solid var(--line); }
|
||||
.nav-group-head.subtiel { color: var(--muted); opacity: .8; }
|
||||
|
|
|
|||
Loading…
Reference in a new issue