Files
maturity/assessments/index.html
2025-09-29 21:31:55 +02:00

186 lines
8.7 KiB
HTML

<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Assessments · Agile Maturity</title>
<link rel="icon" href="data:,">
<style>
:root{--bg:#0b0d10;--card:#12161b;--text:#e6edf3;--muted:#9da7b3;--brand:#5bb8ff}
*{box-sizing:border-box}
body{margin:0;font-family:system-ui,-apple-system,Segoe UI,Roboto,Ubuntu,Cantarell,Noto Sans,sans-serif;background:var(--bg);color:var(--text)}
.container{max-width:960px;margin:0 auto;padding:20px}
a{color:var(--brand)}
.card{background:var(--card);border:1px solid #222831;border-radius:12px;padding:20px}
input[type="text"],input[type="number"],select,textarea{background:#0d1117;border:1px solid #1f2630;border-radius:8px;padding:10px;color:var(--text);width:100%}
button{background:var(--brand);color:#00111f;border:0;border-radius:8px;padding:10px 14px;cursor:pointer;font-weight:600}
button.secondary{background:#232b36;color:var(--text);border:1px solid #2c3644}
.row{display:flex;gap:12px;flex-wrap:wrap}
pre{background:#0d1117;border:1px solid #1f2630;border-radius:10px;padding:12px;overflow:auto}
label{display:block;font-size:12px;color:var(--muted);margin-top:8px}
.dimension{display:grid;grid-template-columns:1fr 120px;gap:8px;margin:6px 0}
</style>
</head>
<body>
<div class="container">
<p><a href="../team/index.html">← Teams</a> · <a href="../index.html">Home</a></p>
<h1>Assessments</h1>
<section class="card">
<h3>Create an assessment</h3>
<div class="row">
<select id="companySelect"></select>
<select id="teamSelect"></select>
<input id="assessor" type="text" placeholder="Assessor name">
<input id="framework" type="text" placeholder="Framework version (e.g., v1.0)">
</div>
<label>Dimensions (key and score 0..5)</label>
<div id="dims"></div>
<div class="row">
<button class="secondary" id="addDim">Add dimension</button>
<button id="saveAssessment">Save assessment</button>
</div>
<label>Assessments for selected team</label>
<div id="list"></div>
<div class="row" style="margin-top:12px">
<button class="secondary" id="exportBtn">Export</button>
<input id="importFile" type="file" accept="application/json">
</div>
</section>
</div>
<script>
const assessKey = 'assessments';
function loadCompanies(){ try{ return JSON.parse(localStorage.getItem('companies')||'[]'); }catch{ return []; } }
function loadTeams(){ try{ return JSON.parse(localStorage.getItem('teams')||'[]'); }catch{ return []; } }
function loadAssessments(){ try{ return JSON.parse(localStorage.getItem(assessKey)||'[]'); }catch{ return []; } }
function saveAssessments(list){ localStorage.setItem(assessKey, JSON.stringify(list)); }
function uuid(){ return (crypto && crypto.randomUUID) ? crypto.randomUUID() : 'id-'+Math.random().toString(36).slice(2); }
function initSelectors(){
const params = new URLSearchParams(location.search);
const cid = params.get('companyId');
const tid = params.get('teamId');
const companies = loadCompanies();
const cs = document.getElementById('companySelect');
cs.innerHTML = '';
companies.forEach(c=>{ const o=document.createElement('option'); o.value=c.id; o.textContent=c.name; cs.appendChild(o); });
if(cid) cs.value = cid;
cs.addEventListener('change', ()=>{ fillTeams(); render(); });
fillTeams();
const ts = document.getElementById('teamSelect');
if(tid) ts.value = tid;
ts.addEventListener('change', render);
}
function fillTeams(){
const ts = document.getElementById('teamSelect');
const cid = document.getElementById('companySelect').value;
const teams = loadTeams().filter(t=>t.companyId===cid);
ts.innerHTML = '';
teams.forEach(t=>{ const o=document.createElement('option'); o.value=t.id; o.textContent=t.name; ts.appendChild(o); });
}
function addDimView(key='', score=''){
const dims = document.getElementById('dims');
const wrap = document.createElement('div');
wrap.className = 'dimension';
wrap.innerHTML = `<input type="text" placeholder="key (e.g., teamwork)" value="${key}"><input type="number" min="0" max="5" step="1" placeholder="score" value="${score}">`;
dims.appendChild(wrap);
}
function gatherDims(){
return Array.from(document.querySelectorAll('#dims .dimension')).map(d=>{
const [kEl,sEl] = d.querySelectorAll('input');
const k = (kEl.value||'').trim();
const s = Number(sEl.value||0);
if(!k) return null;
return { key:k, score: isNaN(s)?0:Math.max(0,Math.min(5,s)) };
}).filter(Boolean);
}
function onSave(){
const companyId = document.getElementById('companySelect').value;
const teamId = document.getElementById('teamSelect').value;
if(!companyId || !teamId){ alert('Choose company and team'); return; }
const assessor = document.getElementById('assessor').value.trim();
const frameworkVersion = document.getElementById('framework').value.trim() || 'v1.0';
const dimensions = gatherDims();
if(dimensions.length===0){ alert('Add at least one dimension'); return; }
const overall = dimensions.reduce((a,d)=>a+d.score,0)/dimensions.length;
const list = loadAssessments();
list.push({ id: uuid(), companyId, teamId, assessedAt: new Date().toISOString(), assessor: { name: assessor }, frameworkVersion, dimensions, overallScore: Number(overall.toFixed(2)), version: 1 });
saveAssessments(list);
document.getElementById('dims').innerHTML='';
addDimView();
render();
}
function render(){
const cid = document.getElementById('companySelect').value;
const tid = document.getElementById('teamSelect').value;
const list = document.getElementById('list');
const data = loadAssessments().filter(a=>a.companyId===cid && a.teamId===tid).sort((a,b)=>b.assessedAt.localeCompare(a.assessedAt));
if(data.length===0){ list.innerHTML = '<p class="small">No assessments yet.</p>'; return; }
list.innerHTML = '';
data.forEach(a=>{
const row = document.createElement('div');
row.className = 'row';
row.style.alignItems='center';
row.style.justifyContent='space-between';
const info = document.createElement('div');
info.textContent = `${new Date(a.assessedAt).toLocaleString()} · Overall ${a.overallScore}`;
const actions = document.createElement('div');
actions.className = 'row';
const btnView = document.createElement('button');
btnView.className = 'secondary';
btnView.textContent = 'View JSON';
btnView.onclick = ()=>{
const blob = new Blob([JSON.stringify(a,null,2)], {type:'application/json'});
const url = URL.createObjectURL(blob);
window.open(url, '_blank');
setTimeout(()=>URL.revokeObjectURL(url), 5000);
};
const btnDel = document.createElement('button');
btnDel.textContent = 'Delete';
btnDel.style.background = '#ff6666';
btnDel.onclick = ()=>{
const rest = loadAssessments().filter(x=>x.id!==a.id);
saveAssessments(rest);
render();
};
actions.append(btnView, btnDel);
row.append(info, actions);
list.appendChild(row);
});
}
function onExport(){
const cid = document.getElementById('companySelect').value;
const tid = document.getElementById('teamSelect').value;
const data = loadAssessments().filter(a=>a.companyId===cid && a.teamId===tid);
const blob = new Blob([JSON.stringify(data,null,2)], {type:'application/json'});
const a = document.createElement('a');
a.href = URL.createObjectURL(blob);
a.download = `assessments.${cid}.${tid}.export.json`;
a.click();
URL.revokeObjectURL(a.href);
}
function onImport(ev){
const file = ev.target.files && ev.target.files[0];
if(!file) return;
file.text().then(text=>{
try{
const json = JSON.parse(text);
if(!Array.isArray(json)) throw new Error('Expected an array');
const existing = loadAssessments();
saveAssessments([...existing, ...json]);
render();
}catch(e){ alert('Invalid JSON: '+e.message); }
});
}
document.getElementById('addDim').addEventListener('click', ()=>addDimView());
document.getElementById('saveAssessment').addEventListener('click', onSave);
document.getElementById('exportBtn').addEventListener('click', onExport);
document.getElementById('importFile').addEventListener('change', onImport);
initSelectors();
addDimView();
render();
</script>
</body>
</html>