diff --git a/CHANGELOG.md b/CHANGELOG.md index 4b95c24..e246040 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,9 @@ Versionierung [semantisch](https://semver.org/lang/de/). ## [Unreleased] – 2026-07-13 · BugHunter, NetWorker & echter Lernfortschritt ### ServiceClash +- **Auswertung repariert:** Nach dem letzten Ticket wurde die Abschlussansicht nicht + geöffnet, weil der Renderer noch auf ein nicht vorhandenes Folgeticket zugriff. Die + Abschlussprüfung erfolgt nun vor jeder Ticketzustands-Abfrage. - **Letztes geplantes Spiel ausgeliefert:** drei Service-Desk-Schichten mit insgesamt **24 kuratierten Tickets** aus Incident-, Request-, Change-, Monitoring-, Backup- und Security-Szenarien. Jede Aufgabe führt verständlich durch Art, Priorität und passende diff --git a/src/main/resources/static/js/serviceclash.js b/src/main/resources/static/js/serviceclash.js index 827d830..6f19b90 100644 --- a/src/main/resources/static/js/serviceclash.js +++ b/src/main/resources/static/js/serviceclash.js @@ -1,7 +1,7 @@ (()=>{'use strict';const view=document.getElementById('scView');let catalog=[],session=null,index=0,started=0,states={};const esc=value=>String(value??'').replace(/[&<>"']/g,c=>({'&':'&','<':'<','>':'>','"':'"',"'":'''}[c]));const api=(url,opt={})=>fetch('/api/serviceclash'+url,{headers:{'Content-Type':'application/json'},...opt}).then(async r=>{const data=await r.json();if(!r.ok)throw new Error(data.message||'Serverfehler');return data});const post=(url,data)=>api(url,{method:'POST',body:JSON.stringify(data)});const seconds=()=>Math.max(1,Math.round((Date.now()-started)/1000));const current=()=>session.tickets[index]; function renderCatalog(){view.innerHTML=`
Service Desk Simulation

Welche Schicht übernimmst du?

ServiceClash erklärt die Triage direkt im Spiel: Ordne jedes Ticket zuerst als Störung oder Anfrage ein, schätze seine Dringlichkeit ein und wähle dann die beste erste Maßnahme. Du darfst Fehler machen – bei Bedarf zeigt dir „Aufdecken“ die Begründung.

${catalog.map(s=>`
${esc(s.difficulty)} · ${esc(s.examScope)}

${esc(s.title)}

${esc(s.description)}

${s.ticketCount} Tickets${s.waves} Wellen
`).join('')}
`;view.querySelectorAll('[data-shift]').forEach(b=>b.onclick=()=>brief(b.dataset.shift));} async function brief(slug){try{session=await api('/session/'+encodeURIComponent(slug));index=0;states={};view.innerHTML=`
${esc(session.difficulty)} · ${session.tickets.length} Tickets

${esc(session.title)}

${esc(session.description)}

So spielst du: Eine Störung bedeutet, dass etwas bereits nicht funktioniert. Eine Anfrage wünscht eine geplante Leistung. Priorität hoch wählst du bei größerer Auswirkung, Sicherheitsrisiko oder Zeitkritik. Die Punkte berücksichtigen alle drei Entscheidungen.

`;document.getElementById('scStart').onclick=()=>{started=Date.now();renderRun()}}catch(error){fail(error)}} -function ticketState(ticket){return states[ticket.ticketId]||{step:'classify',done:[]}}function renderRun(){const ticket=current(),state=ticketState(ticket),done=Object.values(states).filter(s=>s.done?.length===3).length;const wave=Math.max(...session.tickets.map(t=>t.wave));if(!ticket){finish();return}const choices=choiceMarkup(ticket,state);view.innerHTML=`
WELLE ${ticket.wave}/${wave} · ${esc(ticket.requester)}${ticket.points} PTS

${esc(ticket.title)}

${esc(ticket.text)}

1 ART2 PRIORITÄT3 MASSNAHME
${choices}${state.feedback?``:''}${state.done.length===3?`
Debrief: ${esc(state.explanation||'Ticket dokumentiert.')}
`:''}
`;view.querySelectorAll('[data-ticket]').forEach(b=>b.onclick=()=>{const target=Number(b.dataset.ticket);if(target<=index||ticketState(session.tickets[target]).done?.length===3){index=target;renderRun()}});view.querySelectorAll('[data-value]').forEach(b=>b.onclick=()=>check(state.step,b.dataset.value));const reveal=document.getElementById('scReveal');if(reveal)reveal.onclick=()=>check(state.step,'',true);const next=document.getElementById('scNext');if(next)next.onclick=()=>{index++;renderRun()}} +function ticketState(ticket){return states[ticket.ticketId]||{step:'classify',done:[]}}function renderRun(){const ticket=current();if(!ticket){finish();return}const state=ticketState(ticket),done=Object.values(states).filter(s=>s.done?.length===3).length;const wave=Math.max(...session.tickets.map(t=>t.wave));const choices=choiceMarkup(ticket,state);view.innerHTML=`
WELLE ${ticket.wave}/${wave} · ${esc(ticket.requester)}${ticket.points} PTS

${esc(ticket.title)}

${esc(ticket.text)}

1 ART2 PRIORITÄT3 MASSNAHME
${choices}${state.feedback?``:''}${state.done.length===3?`
Debrief: ${esc(state.explanation||'Ticket dokumentiert.')}
`:''}
`;view.querySelectorAll('[data-ticket]').forEach(b=>b.onclick=()=>{const target=Number(b.dataset.ticket);if(target<=index||ticketState(session.tickets[target]).done?.length===3){index=target;renderRun()}});view.querySelectorAll('[data-value]').forEach(b=>b.onclick=()=>check(state.step,b.dataset.value));const reveal=document.getElementById('scReveal');if(reveal)reveal.onclick=()=>check(state.step,'',true);const next=document.getElementById('scNext');if(next)next.onclick=()=>{index++;renderRun()}} function choiceMarkup(ticket,state){if(state.done.length===3)return '';if(state.step==='classify')return `
${revealButton()}`;if(state.step==='priority')return `
${revealButton()}`;return `
${ticket.options.map(o=>``).join('')}
${revealButton()}`} const revealButton=()=>``; async function check(step,value,reveal=false){const ticket=current();try{const result=await post('/check',{ticketId:ticket.ticketId,step,value,reveal});const state=ticketState(ticket);states[ticket.ticketId]=state;if(result.resolved){state.done=[...state.done,step];state.step=step==='classify'?'priority':step==='priority'?'solve':'done'}state.bad=!result.correct;state.feedback=result.correct?'Richtig eingeordnet.':`${reveal?'Aufgedeckt':'Noch nicht passend.'} ${result.answer?`Richtig wäre: ${esc(label(result.answer,ticket))}.`:''}`;if(result.ticketDone){state.explanation=result.explanation;state.feedback='Ticket abgeschlossen. Prüfe kurz das Debrief und entscheide dann selbst, wann du weitergehst.'}renderRun()}catch(error){fail(error)}} diff --git a/src/main/resources/templates/serviceclash.html b/src/main/resources/templates/serviceclash.html index 016e8a4..53bd12d 100644 --- a/src/main/resources/templates/serviceclash.html +++ b/src/main/resources/templates/serviceclash.html @@ -1 +1 @@ -ServiceClash · Mindflux
ServiceClashLEITSTAND · TRIAGE

LEITSTAND WIRD HOCHGEFAHREN …

+ServiceClash · Mindflux
ServiceClashLEITSTAND · TRIAGE

LEITSTAND WIRD HOCHGEFAHREN …