All checks were successful
dev - build & deploy naar test / build-and-deploy (push) Successful in 1m28s
434 lines
22 KiB
JavaScript
434 lines
22 KiB
JavaScript
/* teach - opdrachten samenstellen en delen. Nieuwe opdrachten komen uit de
|
||
volledige leerlingwidgetbibliotheek en staan los van het geopende bord.
|
||
Oude bordtoewijzingen blijven via dezelfde API achterwaarts compatibel. */
|
||
"use strict";
|
||
const btnCast = document.getElementById("btnCast");
|
||
btnCast.title = T("cast");
|
||
document.addEventListener("langchange", ()=>{
|
||
btnCast.title = T("cast");
|
||
if(castOpen) renderCast();
|
||
});
|
||
|
||
function h(tag, cls, text){
|
||
const el = document.createElement(tag);
|
||
if(cls) el.className = cls;
|
||
if(text != null) el.textContent = text;
|
||
return el;
|
||
}
|
||
|
||
const castBackdrop = h("div","am-sheet-backdrop");
|
||
castBackdrop.id = "castBackdrop";
|
||
document.body.appendChild(castBackdrop);
|
||
castBackdrop.addEventListener("click", e=>{ if(e.target===castBackdrop&&!castSaving) closeCast(); });
|
||
|
||
let castOpen = false;
|
||
let castLoading = false;
|
||
let castClasses = [], castPupils = [];
|
||
let castSelWidget = "", castSelMode = "werken", castSingleTheme = null;
|
||
let castPathTitle = "", castPathSteps = [];
|
||
let castShareKind = "path";
|
||
let castSelectedTargets = new Set();
|
||
let castPupilQuery = "";
|
||
let castSaving = false;
|
||
let castNotice = "";
|
||
/* toewijzingen die deze sessie al gelukt zijn ("type:id"), zodat je dezelfde
|
||
opdracht naar meerdere klassen/leerlingen achter elkaar kunt sturen zonder dat
|
||
de sheet na elke keer sluit */
|
||
let castActiveKeys = new Set();
|
||
let castError = "";
|
||
|
||
const CAST_WIDGET_CATEGORIES=new Set(["taal","rekenen"]);
|
||
const CAST_THEME_WIDGETS=new Set(["letters","flits","hak","ball"]);
|
||
function assignmentLibraryWidgets(){
|
||
return REGISTRY.filter(widget=>CAST_WIDGET_CATEGORIES.has(widget.cat));
|
||
}
|
||
function castWidgetLabel(widget){ return T("wg_"+widget.id); }
|
||
function castThemeCatalog(){
|
||
const builtIn=typeof languageThemeList==="function"?languageThemeList(LANG).map(theme=>({
|
||
id:"builtin:"+theme.id,name:theme.name,icon:theme.icon||"📚",words:theme.words||[],source:"builtin",
|
||
})):[];
|
||
const personal=(THEMES[LANG]||[]).filter(theme=>Array.isArray(theme.words)&&theme.words.length).map((theme,index)=>({
|
||
id:"personal:"+index,name:theme.name,icon:"★",words:theme.words,source:"personal",
|
||
}));
|
||
return builtIn.concat(personal);
|
||
}
|
||
function castThemePayload(theme){
|
||
if(!theme)return null;
|
||
return {
|
||
id:theme.id,name:theme.name,
|
||
words:(theme.words||[]).slice(0,120).map(word=>Array.isArray(word)?word.slice(0,4):word),
|
||
};
|
||
}
|
||
function castThemeSelect(theme,onChange){
|
||
const select=h("select","am-sel cast-step-theme");
|
||
select.setAttribute("aria-label",T("castTheme"));
|
||
select.appendChild(new Option(T("castThemeNone"),""));
|
||
const catalog=castThemeCatalog();
|
||
[["builtin","castBuiltInThemes"],["personal","castOwnThemes"]].forEach(([source,labelKey])=>{
|
||
const items=catalog.filter(item=>item.source===source);
|
||
if(!items.length)return;
|
||
const group=document.createElement("optgroup");group.label=T(labelKey);
|
||
items.forEach(item=>group.appendChild(new Option(`${item.icon} ${item.name} (${item.words.length})`,item.id)));
|
||
select.appendChild(group);
|
||
});
|
||
select.value=theme?.id||"";
|
||
select.disabled=castSaving;
|
||
select.addEventListener("change",()=>{
|
||
const selected=catalog.find(item=>item.id===select.value)||null;
|
||
onChange(selected);castActiveKeys.clear();castNotice="";
|
||
});
|
||
return select;
|
||
}
|
||
function castTargetKey(target){ return target.type+":"+target.id; }
|
||
function castClassSelected(classId){ return castSelectedTargets.has("class:"+classId); }
|
||
function castPupilCoveredByClass(target){
|
||
return target.type==="pupil" && target.classId && castClassSelected(target.classId);
|
||
}
|
||
function castToggleTarget(target, checked){
|
||
const key=castTargetKey(target);
|
||
if(checked){
|
||
castSelectedTargets.add(key);
|
||
if(target.type==="class"){
|
||
castPupils.filter(p=>String(p.classId)===String(target.id))
|
||
.forEach(p=>castSelectedTargets.delete("pupil:"+p.id));
|
||
}
|
||
}else castSelectedTargets.delete(key);
|
||
castNotice="";castError="";
|
||
}
|
||
function castSelectedTargetObjects(){
|
||
const targets=[];
|
||
castClasses.forEach(c=>{
|
||
const target={type:"class",id:c.id,label:c.name};
|
||
if(castSelectedTargets.has(castTargetKey(target)))targets.push(target);
|
||
});
|
||
castPupils.forEach(p=>{
|
||
const target={type:"pupil",id:p.id,label:p.displayName,classId:p.classId};
|
||
if(castSelectedTargets.has(castTargetKey(target)))targets.push(target);
|
||
});
|
||
return targets;
|
||
}
|
||
function castAffectedPupilCount(){
|
||
const pupilIds=new Set();
|
||
castSelectedTargetObjects().forEach(target=>{
|
||
if(target.type==="pupil")pupilIds.add(String(target.id));
|
||
else castPupils.filter(p=>String(p.classId)===String(target.id)).forEach(p=>pupilIds.add(String(p.id)));
|
||
});
|
||
return pupilIds.size;
|
||
}
|
||
function castPayload(){
|
||
const body={};
|
||
if(castShareKind==="path"){
|
||
body.title=castPathTitle.trim();
|
||
body.sequence=castPathSteps.map(step=>({
|
||
widgetType:step.widgetType,mode:step.mode,theme:castThemePayload(step.theme),
|
||
}));
|
||
}else if(castShareKind==="widget"){
|
||
const widget=assignmentLibraryWidgets().find(item=>item.id===castSelWidget);
|
||
body.title=widget?castWidgetLabel(widget):T("castSingleAssignment");
|
||
body.sequence=[{widgetType:castSelWidget,mode:castSelMode,theme:castThemePayload(castSingleTheme)}];
|
||
}
|
||
return body;
|
||
}
|
||
function castValidationKey(){
|
||
if(castShareKind==="path"&&!castPathSteps.length)return "castNeedPath";
|
||
if(castShareKind==="path"&&!castPathTitle.trim())return "castNeedTitle";
|
||
if(castShareKind==="widget"&&!castSelWidget)return "castNeedWidget";
|
||
if(!castSelectedTargetObjects().length)return "castNeedTarget";
|
||
return "";
|
||
}
|
||
function castRefreshSendValidation(){
|
||
const invalid=castValidationKey();
|
||
const validation=castBackdrop.querySelector(".cast-validation");
|
||
const send=castBackdrop.querySelector(".cast-send-button");
|
||
if(validation){validation.textContent=invalid?T(invalid):"";validation.hidden=!invalid;}
|
||
if(send)send.disabled=castSaving||!!invalid;
|
||
}
|
||
async function castSendSelected(){
|
||
const invalid=castValidationKey();
|
||
if(invalid){castError=T(invalid);renderCast(true);return;}
|
||
const targets=castSelectedTargetObjects();
|
||
const payload=castPayload();
|
||
castSaving=true;castError="";castNotice="";renderCast(true);
|
||
const results=await Promise.allSettled(targets.map(target=>
|
||
api(`/assignments/${target.type}/${target.id}`,{method:"PUT",body:payload})
|
||
));
|
||
let saved=0,failed=0;
|
||
results.forEach((result,index)=>{
|
||
const key=castTargetKey(targets[index]);
|
||
if(result.status==="fulfilled"){
|
||
saved+=1;castActiveKeys.add(key);castSelectedTargets.delete(key);
|
||
}else failed+=1;
|
||
});
|
||
castSaving=false;
|
||
if(saved)castNotice=T("castSent").replace("{count}",saved);
|
||
if(failed)castError=T("castPartial").replace("{count}",failed);
|
||
renderCast(true);
|
||
}
|
||
|
||
btnCast.addEventListener("click", async ()=>{
|
||
castOpen = true;
|
||
castSelWidget = ""; castSelMode = "werken"; castSingleTheme = null;
|
||
castPathTitle = ""; castPathSteps = [];
|
||
castShareKind = "path";
|
||
castSelectedTargets = new Set(); castPupilQuery = ""; castSaving = false; castNotice = "";
|
||
castActiveKeys = new Set(); castError = "";
|
||
castLoading = true;
|
||
renderCast();
|
||
try{
|
||
const [cRes, uRes] = await Promise.all([ api("/admin/classes"), api("/admin/users") ]);
|
||
castClasses = cRes.classes || [];
|
||
castPupils = (uRes.users || []).filter(u=>u.role==="pupil");
|
||
}catch(e){
|
||
castClasses = []; castPupils = []; castError = e.message;
|
||
}
|
||
castLoading = false;
|
||
renderCast();
|
||
});
|
||
|
||
function closeCast(){ castOpen = false; renderCast(); }
|
||
|
||
function castTargetPicker(target){
|
||
const row = h("label","cast-target-card");
|
||
const covered=castPupilCoveredByClass(target);
|
||
const checkbox=h("input","cast-target-check");
|
||
checkbox.type="checkbox";
|
||
checkbox.checked=covered||castSelectedTargets.has(castTargetKey(target));
|
||
checkbox.disabled=covered||castSaving;
|
||
checkbox.addEventListener("change",()=>{
|
||
castToggleTarget(target,checkbox.checked);
|
||
renderCast(true);
|
||
});
|
||
row.appendChild(checkbox);
|
||
const info = h("div","am-person-info");
|
||
info.appendChild(h("span","am-name", target.label));
|
||
if(target.sub) info.appendChild(h("span","am-role", target.sub));
|
||
row.appendChild(info);
|
||
if(covered)row.appendChild(h("span","cast-target-via",T("castViaClass")));
|
||
else if(castActiveKeys.has(castTargetKey(target)))row.appendChild(h("span","am-chip active",T("castActive")));
|
||
return row;
|
||
}
|
||
|
||
function renderCast(preserveScroll=false){
|
||
const previousScroll=preserveScroll ? castBackdrop.querySelector(".am-sheet")?.scrollTop || 0 : 0;
|
||
castBackdrop.classList.toggle("open", castOpen);
|
||
castBackdrop.innerHTML = "";
|
||
if(!castOpen) return;
|
||
|
||
const sheet = h("div","am-sheet cast-composer-sheet");
|
||
castBackdrop.appendChild(sheet);
|
||
if(preserveScroll)requestAnimationFrame(()=>{sheet.scrollTop=previousScroll;});
|
||
|
||
const head = h("div","am-kebab-row");
|
||
const titleWrap = h("div");
|
||
titleWrap.appendChild(h("h2","am-nav-title", T("castTitle")));
|
||
titleWrap.appendChild(h("div","am-role",T("castLibrarySubtitle")));
|
||
head.appendChild(titleWrap);
|
||
const closeBtn = h("button","am-btn", T("close"));
|
||
closeBtn.type = "button";
|
||
closeBtn.disabled = castSaving;
|
||
closeBtn.addEventListener("click", closeCast);
|
||
head.appendChild(closeBtn);
|
||
sheet.appendChild(head);
|
||
|
||
sheet.appendChild(h("p","cast-composer-intro",T("castIntro")));
|
||
const status=h("div","cast-status");
|
||
status.setAttribute("aria-live","polite");
|
||
if(castError){status.classList.add("error");status.textContent=castError;}
|
||
else if(castNotice){status.classList.add("success");status.textContent=castNotice;}
|
||
sheet.appendChild(status);
|
||
|
||
const guide=h("ol","cast-guide");
|
||
[T("castStepChoose"),T("castStepBuild"),T("castStepAudience")].forEach((label,index)=>{
|
||
const item=h("li");item.append(h("span",null,String(index+1)),h("strong",null,label));guide.appendChild(item);
|
||
});
|
||
sheet.appendChild(guide);
|
||
|
||
const composer=h("section","cast-composer-card");
|
||
composer.appendChild(h("h3",null,T("castWhat")));
|
||
const kindTabs=h("div","cast-kind-tabs");
|
||
[["path","castKindPath","castKindPathHelp"],["widget","castKindWidget","castKindWidgetHelp"]]
|
||
.forEach(([kind,labelKey,helpKey])=>{
|
||
const button=h("button","cast-kind-button"+(castShareKind===kind?" active":""));
|
||
button.type="button";button.disabled=castSaving;
|
||
button.append(h("strong",null,T(labelKey)),h("span",null,T(helpKey)));
|
||
button.addEventListener("click",()=>{
|
||
castShareKind=kind;castActiveKeys.clear();castError="";castNotice="";renderCast(true);
|
||
});
|
||
kindTabs.appendChild(button);
|
||
});
|
||
composer.appendChild(kindTabs);
|
||
composer.appendChild(h("p","cast-live-hint",T("castLiveStateHint")));
|
||
|
||
const learningWidgets=assignmentLibraryWidgets();
|
||
const widgetGrid=(selectOnly=false)=>{
|
||
const grid=h("div","cast-widget-grid");
|
||
learningWidgets.forEach(widget=>{
|
||
const selected=selectOnly&&castSelWidget===widget.id;
|
||
const added=castPathSteps.filter(step=>step.widgetType===widget.id).length;
|
||
const card=h("button","cast-widget-card"+(selected?" selected":""));
|
||
card.type="button";card.disabled=castSaving||(!selectOnly&&castPathSteps.length>=12);
|
||
card.setAttribute("aria-pressed",String(selected));
|
||
card.appendChild(h("span","cast-widget-icon",widget.icon));
|
||
const copy=h("span","cast-widget-copy");
|
||
copy.append(h("strong",null,castWidgetLabel(widget)),h("small",null,T(widget.cat==="taal"?"catTaal":"catRekenen")));
|
||
card.appendChild(copy);
|
||
if(added&&!selectOnly)card.appendChild(h("span","cast-widget-count",String(added)));
|
||
else card.appendChild(h("span","cast-widget-plus",selected?"✓":"+"));
|
||
card.addEventListener("click",()=>{
|
||
if(selectOnly){
|
||
if(castSelWidget!==widget.id)castSingleTheme=null;
|
||
castSelWidget=widget.id;
|
||
}
|
||
else{
|
||
castPathSteps.push({widgetType:widget.id,widgetLabel:castWidgetLabel(widget),icon:widget.icon,mode:"werken",theme:null});
|
||
if(!castPathTitle)castPathTitle=T("castPathDefault");
|
||
}
|
||
castActiveKeys.clear();castError="";castNotice="";renderCast(true);
|
||
});
|
||
grid.appendChild(card);
|
||
});
|
||
return grid;
|
||
};
|
||
|
||
if(castShareKind==="path"){
|
||
const titleField=h("label","cast-field");
|
||
titleField.appendChild(h("span",null,T("castPathName")));
|
||
const pathTitle=h("input","am-inp");
|
||
pathTitle.type="text";pathTitle.maxLength=80;pathTitle.placeholder=T("castPathPlaceholder");pathTitle.value=castPathTitle;
|
||
pathTitle.addEventListener("input",()=>{
|
||
castPathTitle=pathTitle.value;castActiveKeys.clear();castNotice="";castRefreshSendValidation();
|
||
});
|
||
titleField.appendChild(pathTitle);composer.appendChild(titleField);
|
||
|
||
const libraryHead=h("div","cast-section-head");
|
||
const libraryCopy=h("div");libraryCopy.append(h("strong",null,T("castWidgetLibrary")),h("small",null,T("castWidgetLibraryHint")));
|
||
libraryHead.appendChild(libraryCopy);
|
||
const libraryActions=h("div","cast-mini-actions");
|
||
const addAll=h("button","am-btn",T("castAddAll"));addAll.type="button";
|
||
addAll.disabled=castSaving||!learningWidgets.some(widget=>!castPathSteps.some(step=>step.widgetType===widget.id))||castPathSteps.length>=12;
|
||
addAll.addEventListener("click",()=>{
|
||
learningWidgets.filter(widget=>!castPathSteps.some(step=>step.widgetType===widget.id))
|
||
.slice(0,12-castPathSteps.length).forEach(widget=>castPathSteps.push({
|
||
widgetType:widget.id,widgetLabel:castWidgetLabel(widget),icon:widget.icon,mode:"werken",theme:null,
|
||
}));
|
||
if(castPathSteps.length&&!castPathTitle)castPathTitle=T("castPathDefault");
|
||
castActiveKeys.clear();castError="";castNotice="";renderCast(true);
|
||
});
|
||
const clearPath=h("button","am-btn",T("castClearPath"));clearPath.type="button";clearPath.disabled=castSaving||!castPathSteps.length;
|
||
clearPath.addEventListener("click",()=>{castPathSteps=[];castActiveKeys.clear();castError="";castNotice="";renderCast(true);});
|
||
libraryActions.append(addAll,clearPath);libraryHead.appendChild(libraryActions);composer.appendChild(libraryHead);
|
||
if(learningWidgets.length)composer.appendChild(widgetGrid());
|
||
else composer.appendChild(h("div","cast-empty",T("castNoLearningWidgets")));
|
||
|
||
const sequenceHead=h("div","cast-section-head");
|
||
sequenceHead.appendChild(h("strong",null,T("castSequenceOrder")));
|
||
const count=h("span","cast-sequence-count",T("castPathHint").replace("{count}",castPathSteps.length));
|
||
sequenceHead.appendChild(count);composer.appendChild(sequenceHead);
|
||
if(!castPathSteps.length)composer.appendChild(h("div","cast-path-empty",T("castPathEmpty")));
|
||
else{
|
||
const pathList=h("div","cast-path-list");
|
||
castPathSteps.forEach((step,index)=>{
|
||
const row=h("div","cast-path-step");
|
||
row.append(h("span","cast-path-number",String(index+1)),h("span","cast-step-icon",step.icon||"🧩"));
|
||
const label=h("span","cast-step-copy");label.append(h("strong",null,step.widgetLabel||T("wg_"+step.widgetType)),h("small",null,T("castIndependentStep")));
|
||
row.appendChild(label);
|
||
const themeWrap=h("label","cast-step-option cast-step-theme-option");
|
||
themeWrap.appendChild(h("span",null,T("castTheme")));
|
||
if(CAST_THEME_WIDGETS.has(step.widgetType))themeWrap.appendChild(castThemeSelect(step.theme,value=>{step.theme=value;}));
|
||
else{
|
||
const unavailable=h("span","cast-theme-na",T("castThemeNotApplicable"));themeWrap.appendChild(unavailable);
|
||
}
|
||
const modeWrap=h("label","cast-step-option");
|
||
modeWrap.appendChild(h("span",null,T("castStepMode")));
|
||
const mode=h("select","am-sel cast-step-mode");
|
||
mode.append(new Option(T("amModeWork"),"werken"),new Option(T("amModeWatch"),"kijken"));mode.value=step.mode;
|
||
mode.setAttribute("aria-label",T("castStepMode"));mode.disabled=castSaving;
|
||
mode.addEventListener("change",()=>{step.mode=mode.value;castActiveKeys.clear();castNotice="";});
|
||
modeWrap.appendChild(mode);
|
||
const controls=h("div","cast-step-controls");
|
||
const up=h("button","am-btn","↑");up.type="button";up.disabled=castSaving||index===0;up.title=T("castMoveUp");
|
||
up.addEventListener("click",()=>{[castPathSteps[index-1],castPathSteps[index]]=[castPathSteps[index],castPathSteps[index-1]];castActiveKeys.clear();renderCast(true);});
|
||
const down=h("button","am-btn","↓");down.type="button";down.disabled=castSaving||index===castPathSteps.length-1;down.title=T("castMoveDown");
|
||
down.addEventListener("click",()=>{[castPathSteps[index+1],castPathSteps[index]]=[castPathSteps[index],castPathSteps[index+1]];castActiveKeys.clear();renderCast(true);});
|
||
const remove=h("button","am-btn cast-step-remove","×");remove.type="button";remove.disabled=castSaving;remove.title=T("castRemoveStep");
|
||
remove.setAttribute("aria-label",T("castRemoveStep"));
|
||
remove.addEventListener("click",()=>{castPathSteps.splice(index,1);castActiveKeys.clear();castNotice="";renderCast(true);});
|
||
controls.append(up,down,remove);row.append(themeWrap,modeWrap,controls);pathList.appendChild(row);
|
||
});
|
||
composer.appendChild(pathList);
|
||
}
|
||
const progress=h("progress","cast-path-progress");progress.max=12;progress.value=castPathSteps.length;composer.appendChild(progress);
|
||
}else if(castShareKind==="widget"){
|
||
composer.appendChild(h("p","cast-kind-note",T("castSingleHint")));
|
||
composer.appendChild(widgetGrid(true));
|
||
if(castSelWidget){
|
||
const themeField=h("label","cast-field cast-theme-field");
|
||
themeField.appendChild(h("span",null,T("castTheme")));
|
||
if(CAST_THEME_WIDGETS.has(castSelWidget))themeField.appendChild(castThemeSelect(castSingleTheme,value=>{castSingleTheme=value;}));
|
||
else themeField.appendChild(h("span","cast-theme-na",T("castThemeNotApplicable")));
|
||
composer.appendChild(themeField);
|
||
}
|
||
const modeField=h("label","cast-field cast-mode-field");modeField.appendChild(h("span",null,T("castStepMode")));
|
||
const mode=h("select","am-sel");mode.append(new Option(T("amModeWork"),"werken"),new Option(T("amModeWatch"),"kijken"));mode.value=castSelMode;mode.disabled=castSaving;
|
||
mode.addEventListener("change",()=>{castSelMode=mode.value;castActiveKeys.clear();castNotice="";});
|
||
modeField.appendChild(mode);composer.appendChild(modeField);
|
||
}
|
||
sheet.appendChild(composer);
|
||
|
||
const audience=h("section","cast-composer-card cast-audience");
|
||
const audienceHead=h("div","cast-section-head");
|
||
const audienceCopy=h("div");audienceCopy.append(h("h3",null,T("castAudienceTitle")),h("small",null,T("castAudienceHint")));
|
||
const clearTargets=h("button","am-btn",T("castClearTargets"));clearTargets.type="button";clearTargets.disabled=castSaving||!castSelectedTargets.size;
|
||
clearTargets.addEventListener("click",()=>{castSelectedTargets.clear();castError="";castNotice="";renderCast(true);});
|
||
audienceHead.append(audienceCopy,clearTargets);audience.appendChild(audienceHead);
|
||
|
||
audience.appendChild(h("div","am-group",T("castClasses")));
|
||
const classList=h("div","cast-target-grid cast-class-grid");
|
||
if(castLoading)classList.appendChild(h("div","cast-empty","…"));
|
||
else if(!castClasses.length)classList.appendChild(h("div","cast-empty",T("castNoClasses")));
|
||
else castClasses.forEach(c=>{
|
||
const count=castPupils.filter(p=>String(p.classId)===String(c.id)).length;
|
||
classList.appendChild(castTargetPicker({type:"class",id:c.id,label:c.name,sub:T("castPupilCount").replace("{count}",count)}));
|
||
});
|
||
audience.appendChild(classList);
|
||
|
||
const pupilHead=h("div","cast-pupil-head");pupilHead.appendChild(h("div","am-group",T("castPupils")));
|
||
const selectVisible=h("button","am-btn",T("castSelectVisible"));selectVisible.type="button";selectVisible.disabled=castSaving||!castPupils.length;
|
||
pupilHead.appendChild(selectVisible);audience.appendChild(pupilHead);
|
||
const search=h("input","am-inp cast-pupil-search");search.type="search";search.placeholder=T("castAudienceSearch");search.value=castPupilQuery;
|
||
audience.appendChild(search);
|
||
const pupilList=h("div","cast-target-grid cast-pupil-grid");
|
||
const pupilRows=[];
|
||
if(castLoading)pupilList.appendChild(h("div","cast-empty","…"));
|
||
else if(!castPupils.length)pupilList.appendChild(h("div","cast-empty",T("castNoPupils")));
|
||
else castPupils.forEach(pupil=>{
|
||
const className=castClasses.find(c=>String(c.id)===String(pupil.classId))?.name||T("amUnassigned");
|
||
const target={type:"pupil",id:pupil.id,label:pupil.displayName,classId:pupil.classId,sub:className};
|
||
const row=castTargetPicker(target);row.dataset.search=(pupil.displayName+" "+className).toLocaleLowerCase();
|
||
pupilRows.push({row,target});pupilList.appendChild(row);
|
||
});
|
||
const applyPupilFilter=()=>{
|
||
castPupilQuery=search.value;
|
||
const query=castPupilQuery.trim().toLocaleLowerCase();
|
||
pupilRows.forEach(({row})=>{row.hidden=!!query&&!row.dataset.search.includes(query);});
|
||
};
|
||
search.addEventListener("input",applyPupilFilter);applyPupilFilter();
|
||
selectVisible.addEventListener("click",()=>{
|
||
pupilRows.filter(({row})=>!row.hidden).forEach(({target})=>{
|
||
if(!castPupilCoveredByClass(target))castSelectedTargets.add(castTargetKey(target));
|
||
});
|
||
castError="";castNotice="";renderCast(true);
|
||
});
|
||
audience.appendChild(pupilList);sheet.appendChild(audience);
|
||
|
||
const sendbar=h("footer","cast-sendbar");
|
||
const summary=h("div","cast-send-summary");
|
||
const targetCount=castSelectedTargetObjects().length,affected=castAffectedPupilCount();
|
||
summary.append(h("strong",null,T("castTargetCount").replace("{count}",targetCount)),
|
||
h("span",null,T("castAffectedPupils").replace("{count}",affected)));
|
||
const invalid=castValidationKey();
|
||
const validation=h("small","cast-validation",invalid?T(invalid):"");validation.hidden=!invalid;summary.appendChild(validation);
|
||
const send=h("button","cast-send-button",castSaving?T("castSending"):T("castSend").replace("{count}",targetCount));
|
||
send.type="button";send.disabled=castSaving||!!invalid;send.addEventListener("click",castSendSelected);
|
||
sendbar.append(summary,send);sheet.appendChild(sendbar);
|
||
}
|