diff --git a/frontend/STYLE-BACKUP.md b/frontend/STYLE-BACKUP.md index 28a1056..dfe8093 100644 --- a/frontend/STYLE-BACKUP.md +++ b/frontend/STYLE-BACKUP.md @@ -4,4 +4,10 @@ De oorspronkelijke vormgeving staat ongewijzigd in `src/styles.css` en is vastge De moderne vormgeving wordt afzonderlijk geladen uit `src/modern.css`. Verwijder de import van `./modern.css` uit `src/main.jsx` om tijdelijk terug te keren. -Versie vóór modernisering: `v0.1.0`. Moderne dev-versie: `v0.2.00 beta`. +Versie vóór modernisering: `v0.1.0`. Moderne dev-versie: `v0.2.01 beta`. + +## v0.2.01 beta — rooster + +De roostermaker en de roosterweergave hebben een rustiger, minimalistische +opmaak gekregen (onderaan in `src/modern.css`, sectie "Roostermaker & +roosterweergave"). De legacy-opmaak in `src/styles.css` is ongewijzigd. diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 837b52e..32ce625 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -1,12 +1,12 @@ { "name": "roosterwijs-frontend", - "version": "0.2.0-beta", + "version": "0.2.1-beta", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "roosterwijs-frontend", - "version": "0.2.0-beta", + "version": "0.2.1-beta", "dependencies": { "react": "^18.3.1", "react-dom": "^18.3.1" diff --git a/frontend/package.json b/frontend/package.json index f97cf7e..bdb6bbd 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,7 +1,7 @@ { "name": "roosterwijs-frontend", "private": true, - "version": "0.2.0-beta", + "version": "0.2.1-beta", "type": "module", "scripts": { "dev": "vite", diff --git a/frontend/src/modern.css b/frontend/src/modern.css index 5928196..aef100c 100644 --- a/frontend/src/modern.css +++ b/frontend/src/modern.css @@ -35,7 +35,7 @@ button,a,input,select{transition:border-color .15s,background-color .15s,color . .cog-btn{width:100%;min-height:38px;justify-content:flex-start;font-size:15px;padding:0} .cog-btn::after{content:"Beheer en instellingen";margin-left:8px;font-size:13px} .cog-dropdown{position:static;min-width:0;margin:4px 0 0;box-shadow:none} -.topbar::after{content:"v0.2.00 beta";order:4;margin:10px 8px 0;color:#98a2b3;font-size:10px} +.topbar::after{content:"v0.2.01 beta";order:4;margin:10px 8px 0;color:#98a2b3;font-size:10px} .content{margin-left:var(--sidebar);width:calc(100% - var(--sidebar));max-width:1680px;padding:32px 38px 48px} .content-wide{padding:24px 28px 42px} h1{font-size:clamp(24px,2.4vw,30px);line-height:1.2;letter-spacing:-.025em;font-weight:750} @@ -73,3 +73,188 @@ input:focus,select:focus,textarea:focus{outline:3px solid rgba(36,87,214,.13);bo } @media print{.content,.content-wide{margin:0;width:100%;padding:0}} @media(prefers-reduced-motion:reduce){*,*::before,*::after{transition-duration:.01ms!important;animation-duration:.01ms!important}} + +/* =================================================================== + Roostermaker & roosterweergave — rustig, minimalistisch, met detail. + Uitgangspunt: minder geneste kaders. Vroeger stapelden kolom → tijdvak + → splitsing → vakkaart vier randen op elkaar; nu dragen alleen de + dagkolom en de vakkaart een rand, de rest scheidt met witruimte en + haarlijnen. Vakkleur en conflictsignalen blijven leidend. + =================================================================== */ +:root{--rw-hair:#eef1f7;--rw-rand:#e6eaf2} + +/* --- Kop, weeknavigatie en weergaveopties ------------------------- */ +.rooster-kop{align-items:flex-start;margin-bottom:16px} +.rooster-scherm .page-subtitle{font-size:13px;color:var(--muted)} +.rooster-toolbar{gap:12px 16px;padding:10px 12px;border-radius:var(--radius)} +.rooster-scherm .veld{font-size:12px;font-weight:650;color:#98a2b3;letter-spacing:.01em} +.week-nav{gap:2px;padding:3px;background:#f4f6fb;border:1px solid var(--rw-rand);border-radius:10px} +.week-nav .btn.icon{width:32px;min-height:32px;padding:0;border:0;background:transparent;color:var(--muted);font-size:17px;border-radius:7px} +.week-nav .btn.icon:hover{background:#fff;border:0;color:var(--brand);box-shadow:0 1px 2px rgba(23,32,51,.1)} +.week-label{min-width:152px;font-size:13px;font-weight:650;color:var(--ink);font-variant-numeric:tabular-nums} +.week-nav .link.small{margin-left:4px;padding:0 8px;font-size:12px;font-weight:600} +.weergave-opties{gap:5px} +.icon-toggle{width:36px;height:36px;border-radius:9px;font-size:15px} +.icon-toggle:hover{background:#f5f7fb} +.icon-toggle.on{background:var(--brand-soft);color:var(--brand);border-color:#c8d5f8} +.segmented{padding:2px;gap:2px;background:#f4f6fb;border-color:var(--rw-rand);border-radius:10px;overflow:visible} +.detail-keuze .seg{padding:6px 11px;border:0;border-radius:7px;background:transparent;color:var(--muted);font-size:12px;font-weight:650} +.detail-keuze .seg:hover{color:var(--ink)} +.detail-keuze .seg.on{background:#fff;color:var(--ink);box-shadow:0 1px 2px rgba(23,32,51,.1)} +.sneltoets-hint{margin:-2px 0 10px;color:#a4adbd;font-size:11px} + +/* --- Scopebalk: filteren op klas, leerplein of persoon ------------- + Geen eigen paneel: de chips dragen zelf al hun vorm, dus alleen lucht. */ +.rooster-scherm .scope-balk{gap:4px 20px;margin:10px 0 0;padding:0 2px;background:transparent;border:0;box-shadow:none} +.scope-label{font-size:10px;letter-spacing:.1em;color:#a4adbd} +.scope-balk .chip{min-height:30px;padding:5px 11px;border-radius:8px;font-size:12px;color:#475467} +.scope-balk .chip:hover{background:#f5f7fb;border-color:#ced6e2} +.scope-balk .chip.on{background:var(--brand-soft);border-color:#b9c9f5;color:var(--brand);font-weight:700} +.scope-persoon{min-height:36px;padding:6px 10px;font-size:13px} +.scope-aantal{font-size:12px} + +/* --- Tabbalk (alleen roostermaker) -------------------------------- */ +.tabbar{gap:2px;margin:16px 0;border-bottom-color:var(--rw-rand)} +.tab{padding:9px 13px;border-bottom-width:2px;border-radius:8px 8px 0 0;font-size:13.5px;font-weight:600} +.tab:hover{background:#f5f7fb} +.tab.on{background:transparent;border-bottom-color:var(--brand)} + +/* --- Weekraster: dagkolommen -------------------------------------- */ +.rooster-grid{gap:12px;margin-top:16px} +.rooster-kolom{padding:0 0 6px;background:#fff;border:1px solid var(--rw-rand);border-radius:var(--radius);min-height:120px} +.rooster-kolom.vrij{background:#fcfcfd} +.rooster-dag{position:sticky;top:0;z-index:3;display:flex;align-items:baseline;flex-wrap:wrap;gap:2px 7px;min-height:0;margin:0;padding:11px 74px 10px 13px;background:#fff;border-bottom:1px solid var(--rw-hair);border-radius:calc(var(--radius) - 1px) calc(var(--radius) - 1px) 0 0;font-size:13px;font-weight:700;letter-spacing:-.01em;color:var(--ink)} +.dag-datum{font-size:12px;font-weight:600;color:#98a2b3;font-variant-numeric:tabular-nums} +.rooster-dag .tag{flex:0 0 100%;margin-top:5px;font-size:11px} +/* Vandaag: rustig gemarkeerd, niet schreeuwerig. */ +.rooster-kolom.vandaag{border-color:#c4d3f8} +.rooster-kolom.vandaag .rooster-dag{background:var(--brand-soft);border-bottom-color:#d3dffb;color:var(--brand)} +.rooster-kolom.vandaag .dag-datum{color:var(--brand);opacity:.72} +/* Dagacties (vervallen/leegmaken/eenmalig) blijven uit beeld tot je erbij bent. */ +.dag-acties{top:8px;right:8px;gap:1px;background:transparent;opacity:0;transition:opacity .15s} +.rooster-kolom:hover .dag-acties,.dag-acties:focus-within{opacity:1} +.dag-acties .icon-btn{width:24px;height:24px;border-radius:7px;font-size:12px} + +/* --- Tijdvakken: scheiden met een haarlijn, geen doos-in-doos ------ */ +.slot-cel{margin:0;padding:10px 12px 12px;background:transparent;border:0;border-top:1px solid var(--rw-hair);border-radius:0} +.rooster-dag+.slot-cel{border-top:0} +.slot-cel.hover{background:var(--brand-soft);box-shadow:inset 0 0 0 1px #c8d5f8} +.slot-tijd{display:flex;align-items:baseline;gap:7px;max-width:100%;margin-bottom:7px;padding:0;background:transparent;border:0;border-radius:0;font-size:11px;font-weight:700;overflow:visible} +.st-tijd{color:#475467;letter-spacing:.02em;font-variant-numeric:tabular-nums;white-space:nowrap} +.st-naam{min-width:0;color:#a4adbd;font-weight:600;overflow:hidden;text-overflow:ellipsis;white-space:nowrap} +.slot-leeg{color:#d3d9e4;font-size:12px;line-height:1} +.slot-add{margin-top:8px;padding:6px;border:1px dashed #dfe4ee;border-radius:9px;color:#a4adbd;font-size:12px;font-weight:600;opacity:0} +.rooster-edit .slot-add{padding:6px;opacity:0} +.slot-cel:hover .slot-add,.rooster-edit .slot-cel:hover .slot-add,.slot-add:focus-visible{opacity:1} +.slot-add:hover{border-color:#b9c9f5;background:var(--brand-soft);color:var(--brand)} +/* Vrije dag: de getinte kolom en het dagtype-label zijn het signaal — + de oude diagonale arcering was alleen maar onrustig. */ +.rooster-kolom.vrij .slot-cel{opacity:.5} +.slot-cel[data-vrij]{background:transparent} + +/* --- Doelgroep-kop boven de vakken (klas / leerling / leerplein) --- */ +.blok-splitsing,.blok-splitsing.enkel{margin:0 0 12px;padding:0;background:transparent;border:0;border-radius:0} +.slot-cel .blok-splitsing:last-of-type{margin-bottom:0} +.split-header{gap:6px;margin-bottom:7px;padding:0 0 5px;background:transparent;border:0;border-bottom:1px solid var(--rw-hair);border-radius:0} +.split-icon{width:16px;height:16px;background:transparent;color:inherit;font-size:12px;filter:grayscale(1);opacity:.7} +.split-title strong{font-size:12px;font-weight:700;color:var(--ink)} +.split-title span{font-size:10px;color:#a4adbd;letter-spacing:.02em} +.split-count{min-width:19px;height:19px;background:#f1f4fa;color:#667085;border-radius:6px;font-size:11px;font-weight:700} +/* Vertakking naar subgroepen: neutrale haarlijnen in plaats van blauwe balken. */ +.split-kolommen{gap:10px;margin-left:5px;padding:8px 0 0 13px;border-left:1px solid var(--rw-rand)} +.split-kolommen::before{left:-1px;height:8px;border-left:1px solid var(--rw-rand)} +.split-kolom::before{top:11px;left:-13px;width:13px;border-top:1px solid var(--rw-rand)} +.split-node-kop{margin-bottom:5px;padding:0 0 3px;background:transparent;border:0;color:#667085;font-size:11px;font-weight:700} +.split-vakken{gap:7px;min-width:0} +.split-vakken.meerdere-vakken{gap:7px;padding-left:0;border-left:0} +.split-vakken.meerdere-vakken .vak-kaart{border-radius:10px} + +/* --- De vakkaart: het enige echte kaartje in het rooster ---------- */ +/* min-width:0 is nodig: als grid-item zou de kaart anders niet onder zijn + min-content-breedte krimpen en over de kolomrand heen lopen. */ +.vak-kaart{height:auto;min-height:0;min-width:0;gap:5px;padding:9px 11px;border:1px solid var(--rw-rand);border-left:3px solid var(--brand);border-radius:10px;box-shadow:none;overflow:visible} +.vak-kaart.klikbaar:hover{border-color:#ced6e2;border-left-color:var(--vk-kleur);box-shadow:0 4px 14px rgba(23,32,51,.09)} +.vak-kaart.open{border-color:var(--brand);border-left-color:var(--brand);box-shadow:0 0 0 3px rgba(36,87,214,.13)} +.vak-kaart.vervallen{opacity:1;background:#fbfbfc;border-color:var(--rw-hair);border-left-color:#d3d9e4} +.vak-kaart.vervallen .vk-naam,.vak-kaart.vervallen .vk-meta{color:#98a2b3} +.vk-kop{gap:6px;align-items:flex-start} +/* Lange vaknamen mogen over twee regels: in smalle subgroepkolommen is + afkappen op één regel te veel informatieverlies. */ +.vk-naam{display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;font-size:13px;font-weight:650;letter-spacing:-.01em;line-height:1.32;color:var(--ink);white-space:normal;overflow-wrap:break-word;overflow:hidden} +.detail-compact .vk-naam{-webkit-line-clamp:1} +/* Meta zonder pil-achtergronden: het icoon groepeert al, witruimte scheidt. */ +.vk-meta{align-items:center;gap:3px 10px;margin-top:1px;overflow:visible} +.vk-tag{gap:4px;padding:0;background:transparent;border-radius:0;color:#667085;font-size:11px;font-weight:550;line-height:1.45} +.vk-opmerking{margin-top:3px;padding-top:0;color:#98a2b3;font-size:11px} +.rb-stage{color:var(--warn);font-size:11px} +.rb-status,.rb-verv{font-size:11px} +.rb-status{color:var(--danger)} +.rb-verv{color:var(--on)} +/* Bewerkacties zweven rechtsboven en verschijnen bij hover: zo blijft de + volle kaartbreedte voor de vaknaam, ook in smalle subgroepkolommen. */ +.vk-acties{position:absolute;top:6px;right:6px;z-index:2;display:inline-flex;gap:1px;padding:2px;background:#fff;border:1px solid var(--rw-rand);border-radius:8px;box-shadow:0 2px 8px rgba(23,32,51,.13);opacity:0;pointer-events:none;transition:opacity .12s} +.vak-kaart:hover .vk-acties,.vak-kaart:focus-within .vk-acties,.vak-kaart.open .vk-acties{opacity:1;pointer-events:auto} +.vk-acties .icon-btn{width:24px;height:24px;border-radius:6px;font-size:13px} +.rb-greep{padding:0 1px;font-size:14px;opacity:0;transition:opacity .12s} +.vak-kaart:hover .rb-greep,.vak-kaart.open .rb-greep{opacity:.6} +.rb-status .icon-btn{opacity:1} +/* Volledig gekleurde variant blijft leesbaar zonder extra randen. */ +.vak-kaart.kleur-vlak{border-color:transparent;border-left-color:rgba(0,0,0,.16);box-shadow:inset 0 0 0 1px rgba(0,0,0,.05)} +.vak-kaart.kleur-vlak .vk-tag,.vak-kaart.kleur-vlak .vk-opmerking,.vak-kaart.kleur-vlak .rb-stage{color:inherit;opacity:.85} +.vak-kaart.kleur-vlak.vervallen{opacity:.45} +.vak-kaart.kleur-vlak.vervallen .vk-naam,.vak-kaart.kleur-vlak.vervallen .vk-meta{color:inherit} + +/* --- Detailniveaus ------------------------------------------------ */ +.rooster-view.details-compact .vak-kaart,.detail-uitgebreid .vak-kaart{height:auto;min-height:0} +.rooster-view.details-compact .vak-kaart{padding:7px 11px} +.rooster-view.details-compact .blok-splitsing{background:transparent;border:0} +.rooster-view.details-compact .split-header{background:transparent;margin-bottom:6px} +.details-compact .split-vakken,.details-compact .split-kolommen{gap:6px} +.detail-uitgebreid .vak-kaart{padding:10px 12px} + +/* --- Conflicten: duidelijk, maar zonder rode schreeuw -------------- */ +.conflict-banner{margin-bottom:16px;border-color:#f3c2c7;border-radius:var(--radius)} +.conflict-kop{padding:11px 14px;color:#a4303f;font-weight:600} +.conflict-lijst li{font-size:13px;color:#8f3540} +.rooster-blok.in-conflict,.split-vak.in-conflict,.vak-kaart.in-conflict{box-shadow:none;background:#fffafa;border-color:#eda9b0;border-left-color:var(--danger)} +.rooster-view .vak-kaart.in-conflict::after{content:"⚠";position:absolute;top:8px;right:9px;color:var(--danger);font-size:11px;line-height:1} +.rooster-view .vak-kaart.in-conflict .vk-naam{padding-right:14px} +/* Bij een volledig gekleurd vak blijft de vakkleur staan; het conflict + wordt dan met een rode ring gemarkeerd in plaats van met een vlak. */ +.vak-kaart.kleur-vlak.in-conflict{background:var(--vk-kleur);border-color:var(--danger);border-left-color:var(--danger);box-shadow:0 0 0 2px rgba(200,62,77,.4)} +.rooster-view .vak-kaart.kleur-vlak.in-conflict::after{color:inherit} + +/* --- Sleep-etiket + modaal ---------------------------------------- */ +.drag-ghost{padding:7px 13px;border-radius:9px;font-size:12.5px;box-shadow:0 10px 26px rgba(23,32,51,.28)} +.modal{border-top-width:1px;border-color:var(--line);border-radius:16px;box-shadow:0 26px 70px rgba(23,32,51,.22)} +.modal-kop h2{font-size:17px;letter-spacing:-.02em} +.modal-editor{background:#fbfcfe;border-color:var(--rw-rand);border-radius:12px} +.editor-detail summary{font-size:12px;letter-spacing:.01em} + +/* --- "Dagen onder elkaar": tijdvakken krijgen daar wél een kader --- */ +.rooster-grid.lijst{gap:14px} +.rooster-grid.lijst .rooster-kolom{padding:0 12px 12px;gap:10px} +.rooster-grid.lijst .rooster-dag{margin:0 -12px 2px} +.rooster-grid.lijst .slot-cel{padding:9px 11px 11px;border:1px solid var(--rw-hair);border-radius:11px} + +/* --- Aanraakschermen: verborgen acties altijd tonen ---------------- + Zonder hover moet alles bereikbaar zijn; de actieknoppen gaan dan + terug in de regel in plaats van over de vaknaam heen te zweven. */ +@media(hover:none){ + .rb-greep,.dag-acties,.slot-add,.rooster-edit .slot-add{opacity:1} + .vk-acties{position:static;margin-left:auto;padding:0;background:transparent;border:0;box-shadow:none;opacity:1;pointer-events:auto} +} +@media(max-width:900px){ + .rooster-toolbar{gap:10px}.week-label{min-width:0;flex:1} + .rooster-kolom{min-height:0} +} +@media print{ + .rooster-kolom,.rooster-grid.lijst .slot-cel{border-color:#bbb} + .rooster-dag{position:static;border-bottom-color:#bbb;padding-right:13px} + .rooster-kolom.vandaag,.rooster-kolom.vandaag .rooster-dag{border-color:#bbb;background:#fff;color:#000} + .slot-cel{border-top-color:#ddd} + .vak-kaart{break-inside:avoid;box-shadow:none} + .slot-leeg,.slot-add{display:none!important} + /* Bij "vakken in kleur" moeten ook lokaal/begeleiding meekleuren. */ + body.print-kleur .vak-kaart .vk-tag,body.print-kleur .vak-kaart .vk-opmerking,body.print-kleur .vak-kaart .rb-stage{color:inherit!important} +} diff --git a/frontend/src/pages.jsx b/frontend/src/pages.jsx index abff120..434a03d 100644 --- a/frontend/src/pages.jsx +++ b/frontend/src/pages.jsx @@ -2151,6 +2151,7 @@ export function RoosterScherm({ mode }) { const weekDatums = [0, 1, 2, 3, 4].map((i) => plusDagen(maandag, i)); const vanISO = fmtISO(maandag); const totISO = fmtISO(plusDagen(maandag, 6)); + const vandaagISO = fmtISO(new Date()); async function laadBlokken() { if (!schooljaar) { setBlokken([]); return; } @@ -2417,9 +2418,14 @@ export function RoosterScherm({ mode }) {
{edit && startDrag(e, b)} onClick={(e) => e.stopPropagation()} title="Sleep naar ander tijdslot">⠿} {b.activiteit_pictogram && } - {b.activiteit_icoon || vakIcoon(b.activiteit_naam)} {b.activiteit_naam} - {edit && } - {edit && } + {b.activiteit_icoon || vakIcoon(b.activiteit_naam)} {b.activiteit_naam} + {edit && ( + + {!kal && !uit && } + + + + )}
{(b.locatie_naam || b.lokaal_naam || b.begeleiders_namen.length > 0) && (
@@ -2437,9 +2443,6 @@ export function RoosterScherm({ mode }) { {kal &&
Vervalt — {kal.type_label}
} {!kal && uit && uit.vervalt &&
Vervalt {uit.opmerking ? `(${uit.opmerking})` : ""}{edit && }
} {!kal && uit && !uit.vervalt && uit.vervanger_naam &&
Vervanger: {uit.vervanger_naam}
} - {edit && !kal && !uit && ( - - )}
); } @@ -2783,9 +2786,10 @@ export function RoosterScherm({ mode }) { const kal = kalenderOp(iso); const slots = tijdsloten.filter((t) => t.dag === d.value).sort((a, b) => a.volgorde - b.volgorde); return ( -
+
- {d.label} {fmtDM(datum)} + {d.label} + {fmtDM(datum)} {kal &&
{kal.type_label}{kal.omschrijving ? `: ${kal.omschrijving}` : ""}
} {edit && ( @@ -2811,8 +2815,12 @@ export function RoosterScherm({ mode }) { data-vrij={kal ? "1" : undefined} key={t.id} > -
{t.start_tijd?.slice(0, 5)} {t.naam}
+
+ {t.start_tijd?.slice(0, 5)}{t.eind_tijd ? `–${t.eind_tijd.slice(0, 5)}` : ""} + {t.naam && {t.naam}} +
{renderSlotBlokken(slotBlokken, iso, kal)} + {!edit && slotBlokken.length === 0 &&
} {edit && !kal && ( )}