Initial: NOVA Control Basis (index.html aus Fix-Brief)
This commit is contained in:
+461
@@ -0,0 +1,461 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>NOVA Control</title>
|
||||
<style>
|
||||
*{margin:0;padding:0;box-sizing:border-box}
|
||||
:root{--bg:#f5f5f7;--surface:#fff;--text:#1d1d1f;--secondary:#6e6e73;--accent:#0071e3;--green:#34c759;--red:#ff3b30;--border:#e5e5ea}
|
||||
body{font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',system-ui,sans-serif;background:var(--bg);color:var(--text)}
|
||||
.layout{display:grid;grid-template-columns:220px 1fr;min-height:100vh}
|
||||
.sidebar{background:var(--surface);border-right:1px solid var(--border);padding:20px 0;display:flex;flex-direction:column;justify-content:space-between}
|
||||
.sidebar .log{padding:10px 20px;font-size:22px;font-weight:800;color:var(--accent);border-bottom:1px solid var(--border)}
|
||||
.sidebar nav{flex-grow:1}
|
||||
.sidebar button{width:100%;padding:10px 20px;border:none;background:none;cursor:pointer;text-align:left;font-size:14px;color:var(--text)}
|
||||
.sidebar button:hover{background:var(--bg)}
|
||||
.sidebar button.sel{background:var(--accent);color:#fff}
|
||||
.theme-btns{padding:10px 20px;display:flex;gap:6px;border-top:1px solid var(--border)}
|
||||
.theme-btns button{flex:1;padding:6px;border:1px solid var(--border);background:var(--bg);border-radius:6px;cursor:pointer;font-size:14px}
|
||||
.theme-btns button.sel{background:var(--accent);color:#fff;border-color:var(--accent)}
|
||||
.content{padding:24px;overflow-y:auto;height:100vh}
|
||||
.section{display:none}
|
||||
.section.show{display:block}
|
||||
h2{font-size:22px;font-weight:700;margin-bottom:16px}
|
||||
.toolbar{display:flex;gap:8px;margin-bottom:16px;align-items:center;flex-wrap:wrap}
|
||||
.toolbar input{padding:6px 12px;border:1px solid var(--border);border-radius:6px;font-size:13px;background:var(--surface)}
|
||||
.toolbar input[type=text]{flex:1;min-width:120px}
|
||||
.toolbar select{padding:6px 10px;border:1px solid var(--border);border-radius:6px;font-size:13px;background:var(--surface)}
|
||||
.button{padding:6px 14px;border:none;border-radius:6px;cursor:pointer;font-size:13px;font-weight:600}
|
||||
.button.primary{background:var(--accent);color:#fff}
|
||||
.button.secondary{background:var(--bg);color:var(--text);border:1px solid var(--border)}
|
||||
.board{display:grid;grid-template-columns:repeat(4,1fr);gap:12px}
|
||||
.col{background:var(--surface);border-radius:8px;padding:12px;min-height:350px}
|
||||
.col-head{display:flex;justify-content:space-between;font-size:12px;font-weight:700;text-transform:uppercase;color:var(--secondary);padding-bottom:8px;border-bottom:2px solid var(--border);margin-bottom:8px}
|
||||
.tcount{background:var(--bg);padding:2px 8px;border-radius:10px;font-size:11px;color:var(--secondary)}
|
||||
.task{background:var(--bg);border:1px solid var(--border);border-radius:6px;padding:10px;margin-bottom:6px;cursor:pointer}
|
||||
.task:hover{border-color:var(--accent)}
|
||||
.task h3{font-size:13px;font-weight:600;margin-bottom:4px}
|
||||
.task p{font-size:11px;color:var(--secondary);margin-bottom:6px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
|
||||
.task .meta{display:flex;justify-content:space-between;font-size:10px}
|
||||
.badge{padding:2px 6px;border-radius:4px;font-size:9px;font-weight:700;text-transform:uppercase}
|
||||
.badge.h{background:#ffebee;color:#c62828}
|
||||
.badge.m{background:#fff3e0;color:#e65100}
|
||||
.badge.l{background:#e8f5e9;color:#2e7d32}
|
||||
.modal-bg{position:fixed;inset:0;background:rgba(0,0,0,.4);display:none;align-items:center;justify-content:center;z-index:100}
|
||||
.modal-bg.open{display:flex}
|
||||
.modal{background:var(--surface);border-radius:10px;padding:24px;width:420px;max-width:90vw;max-height:80vh;overflow-y:auto}
|
||||
.modal h3{font-size:16px;font-weight:700;margin-bottom:16px}
|
||||
.field{margin-bottom:12px}
|
||||
.field label{display:block;font-size:11px;font-weight:600;color:var(--secondary);margin-bottom:3px}
|
||||
.field input,.field textarea,.field select{width:100%;padding:7px 10px;border:1px solid var(--border);border-radius:5px;font-size:13px}
|
||||
.field textarea{resize:vertical;min-height:60px}
|
||||
.modal-actions{display:flex;justify-content:space-between;margin-top:16px}
|
||||
.palette-bg{position:fixed;inset:0;background:rgba(0,0,0,.4);display:none;align-items:flex-start;justify-content:center;padding-top:10vh;z-index:200}
|
||||
.palette-bg.open{display:flex}
|
||||
.palette{background:var(--surface);border-radius:10px;width:440px;max-width:90vw;box-shadow:0 12px 32px rgba(0,0,0,.2)}
|
||||
.palette input{width:100%;padding:14px 18px;border:none;font-size:15px;background:transparent;outline:none}
|
||||
.palette-list{max-height:50vh;overflow-y:auto}
|
||||
.pitem{padding:10px 18px;display:flex;justify-content:space-between;border-radius:6px;cursor:pointer}
|
||||
.pitem:hover,.pitem.sel{background:var(--bg)}
|
||||
.pitem .name{font-size:13px}
|
||||
.pitem .key{font-size:10px;color:var(--secondary);background:var(--bg);border:1px solid var(--border);padding:2px 5px;border-radius:3px}
|
||||
.stats{display:grid;grid-template-columns:repeat(auto-fit,minmax(140px,1fr));gap:12px;margin-bottom:20px}
|
||||
.sc{background:var(--surface);border:1px solid var(--border);border-radius:8px;padding:14px;text-align:center}
|
||||
.sc .v{font-size:24px;font-weight:800;color:var(--accent)}
|
||||
.sc .l{font-size:11px;color:var(--secondary);margin-top:4px}
|
||||
.chart{background:var(--surface);border:1px solid var(--border);border-radius:8px;padding:16px}
|
||||
.canvas-wrap{width:100%;overflow-x:auto}
|
||||
canvas{display:block;min-height:180px}
|
||||
.streak{background:var(--surface);border:1px solid var(--border);border-radius:10px;padding:16px;margin-bottom:14px}
|
||||
.streak h3{font-size:14px;font-weight:600;margin-bottom:10px}
|
||||
.sg{display:grid;grid-template-columns:repeat(28,1fr);gap:2px}
|
||||
.sl{width:11px;height:11px;border-radius:2px;background:var(--border);outline:none}
|
||||
.sl[data-d="1"]{background:#a5d6a7}.sl[data-d="2"]{background:#66bb6a}
|
||||
.sl[data-d="3"]{background:#2e7d32}.sl[data-d="4"]{background:#1b5e20}
|
||||
.toast-box{position:fixed;bottom:16px;right:16px;z-index:300;display:flex;flex-direction:column;gap:4px}
|
||||
.toast{padding:8px 14px;border-radius:6px;font-size:12px;font-weight:600;color:#fff;animation:toastIn .3s}
|
||||
@keyframes toastIn{from{opacity:0;transform:translateY(10px)}to{opacity:1;transform:translateY(0)}}
|
||||
.toast.grn{background:var(--green)}.toast.red{background:var(--red)}.toast.blu{background:var(--accent)}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="layout">
|
||||
<aside class="sidebar">
|
||||
<div>
|
||||
<div class="log">NOVA</div>
|
||||
<nav>
|
||||
<button class="sel" data-v="board">Board</button>
|
||||
<button data-v="streaks">Streaks</button>
|
||||
<button data-v="insights">Insights</button>
|
||||
</nav>
|
||||
</div>
|
||||
<div class="theme-btns">
|
||||
<button data-th="light">☀️</button>
|
||||
<button data-th="dark">🌙</button>
|
||||
<button data-th="system" class="sel">📱</button>
|
||||
</div>
|
||||
</aside>
|
||||
<div class="content">
|
||||
<div id="board-section" class="section show">
|
||||
<div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:16px">
|
||||
<h2 style="margin:0">Board</h2>
|
||||
<button class="button primary" id="btn-new">+ Neues Task</button>
|
||||
</div>
|
||||
<div class="toolbar">
|
||||
<input type="text" id="q" placeholder="Suchen...">
|
||||
<select id="pq"><option value="">Alle Prioritäten</option><option>Tief</option><option>Mittel</option><option>Hoch</option></select>
|
||||
</div>
|
||||
<div class="board" id="board"></div>
|
||||
</div>
|
||||
<div id="streaks-section" class="section">
|
||||
<h2>Streaks</h2>
|
||||
<div id="streak-grid"></div>
|
||||
</div>
|
||||
<div id="insights-section" class="section">
|
||||
<h2>Insights</h2>
|
||||
<div class="stats" id="insights-stats"></div>
|
||||
<div class="chart"><div class="canvas-wrap"><canvas id="chart"></canvas></div></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-bg" id="modal-bg">
|
||||
<div class="modal">
|
||||
<h3 id="modal-title">Neues Task</h3>
|
||||
<div class="field"><label>Titel</label><input type="text" id="f-title" maxlength="200"></div>
|
||||
<div class="field"><label>Beschreibung</label><textarea id="f-desc"></textarea></div>
|
||||
<div class="field"><label>Priorität</label><select id="f-pr"><option>Hoch</option><option>Mittel</option><option>Tief</option></select></div>
|
||||
<div class="field"><label>Status</label><select id="f-st"></select></div>
|
||||
<div class="field"><label>Fällig</label><input type="date" id="f-due"></div>
|
||||
<div class="modal-actions">
|
||||
<div><button class="button secondary" id="f-del" style="display:none">Löschen</button></div>
|
||||
<div><button class="button secondary" id="f-cancel">Abbrechen</button><button class="button primary" id="f-save">Speichern</button></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="palette-bg" id="pal-bg"><div class="palette"><input id="pal-q" placeholder="Befehl suchen..."><div class="palette-list" id="pal-list"></div></div></div>
|
||||
<div class="toast-box" id="toasts"></div>
|
||||
<input type="file" id="imp" accept=".json" style="display:none">
|
||||
|
||||
<script>
|
||||
(function(){
|
||||
'use strict';
|
||||
|
||||
var C=[['Backlog','backlog','📋'],['Aktiv','aktiv','🟢'],['Review','review','🔄'],['Fertig','fertig','✅']];
|
||||
var tasks=[],habits=[],theme='system',view='board',pf=null;
|
||||
|
||||
function uid(){return'd'+Date.now().toString(36)+'_'+Math.random().toString(36).slice(2,6)}
|
||||
function esc(s){if(!s)return'';return s.replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>').replace(/"/g,'"')}
|
||||
function fm(d){if(!d)return'';try{return new Date(d).toLocaleDateString('de-DE')}catch(e){return''}}
|
||||
function ts(m,t){var n=document.createElement('div');n.className='toast '+((t||'blu').length>0?(t==='ok'?'grn':t==='err'?'red':'blu'):'blu');n.textContent=m;var c=document.getElementById('toasts');if(c){c.appendChild(n);setTimeout(function(){n.parentNode.removeChild(n)},2600)}}
|
||||
function sv(){try{localStorage.setItem('nv_t',JSON.stringify(tasks));localStorage.setItem('nv_h',JSON.stringify(habits));localStorage.setItem('nv_th',theme)}catch(e){}}
|
||||
function ld(){try{var a=localStorage.getItem('nv_t');var b=localStorage.getItem('nv_h');var c=localStorage.getItem('nv_th');if(a)tasks=JSON.parse(a)||[];if(b)habits=JSON.parse(b)||[];if(c)theme=c}catch(e){tasks=[];habits=[];theme='system'}}
|
||||
|
||||
function at(){
|
||||
var w=window.matchMedia('(prefers-color-scheme:dark)'),d=document.documentElement;
|
||||
if(theme==='system')d.setAttribute('data-theme',w.matches?'dark':'light');
|
||||
else d.setAttribute('data-theme',theme);
|
||||
var b=document.querySelectorAll('.theme-btns button');
|
||||
for(var i=0;i<b.length;i++){b[i].classList.toggle('sel',b[i].getAttribute('data-th')===theme)}
|
||||
}
|
||||
|
||||
document.querySelectorAll('.sidebar button[data-v]').forEach(function(b){
|
||||
b.addEventListener('click',function(){
|
||||
view=b.getAttribute('data-v');
|
||||
var bs=document.querySelectorAll('.sidebar nav button');
|
||||
for(var i=0;i<bs.length;i++){bs[i].classList.toggle('sel',bs[i].getAttribute('data-v')===view)}
|
||||
var s=document.querySelectorAll('.section');
|
||||
for(var i=0;i<s.length;i++){s[i].classList.toggle('show',s[i].id===view+'-section')}
|
||||
rv();
|
||||
});
|
||||
});
|
||||
|
||||
document.querySelectorAll('.theme-btns button').forEach(function(b){
|
||||
b.addEventListener('click',function(){
|
||||
theme=b.getAttribute('data-th');at();sv();rv();
|
||||
});
|
||||
});
|
||||
|
||||
function rv(){
|
||||
if(view==='board')rb();
|
||||
else if(view==='streaks')rs();
|
||||
else if(view==='insights')ri();
|
||||
}
|
||||
|
||||
function rb(){
|
||||
var q=document.getElementById('q').value.toLowerCase(),pq=document.getElementById('pq').value;
|
||||
var h='<div class="board" id="board">';
|
||||
C.forEach(function(c){
|
||||
var fl=tasks.filter(function(t){
|
||||
if(t.st!==c[1])return false;
|
||||
if(q&&t.tl.toLowerCase().indexOf(q)===-1)return false;
|
||||
if(pq&&t.p!==pq)return false;
|
||||
return true;
|
||||
});
|
||||
var ts='';
|
||||
for(var i=0;i<fl.length;i++){
|
||||
var x=fl[i];
|
||||
var pc=x.p==='Hoch'?'h':x.p==='Tief'?'l':'m';
|
||||
ts+='<div class="task" data-id="'+x.id+'" tabindex="0">'+
|
||||
'<h3>'+esc(x.tl)+'</h3>'+
|
||||
(x.ds?'<p>'+esc(x.ds)+'</p>':'')+
|
||||
'<div class="meta"><span class="badge '+pc+'">'+esc(x.p)+'</span>'+(x.d?'<span>'+fm(x.d)+'</span>':'')+
|
||||
'</div></div>';
|
||||
}
|
||||
h+='<div class="col"><div class="col-head"><span>'+c[2]+' '+c[0]+'</span><span class="tcount">'+fl.length+'</span></div>'+ts+'</div>';
|
||||
});
|
||||
h+='</div>';
|
||||
var app=document.getElementById('board-section');
|
||||
app.innerHTML='<div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:16px"><h2 style="margin:0">Board</h2><button class="button primary" id="btn-new">+ Neues Task</button></div>'+
|
||||
'<div class="toolbar"><input type="text" id="q" placeholder="Suchen..."><select id="pq"><option value="">Alle Prioritäten</option><option>Tief</option><option>Mittel</option><option>Hoch</option></select></div>'+
|
||||
h;
|
||||
var s=document.getElementById('q'),p=document.getElementById('pq');
|
||||
if(s)s.addEventListener('input',rb);
|
||||
if(p)p.addEventListener('change',rb);
|
||||
var a=document.getElementById('btn-new');
|
||||
if(a)a.addEventListener('click',function(){op(null)});
|
||||
app.addEventListener('click',function(e){
|
||||
var t=e.target.closest('.task');if(!t)return;
|
||||
var f=tasks.find(function(x){return x.id===t.getAttribute('data-id')});
|
||||
if(f)op(f);
|
||||
});
|
||||
app.addEventListener('keydown',function(e){
|
||||
if(e.key==='Delete'){
|
||||
var t=e.target.closest('.task');
|
||||
if(!t)return;
|
||||
if(!confirm('Löschen?'))return;
|
||||
tasks=tasks.filter(function(x){return x.id!==t.getAttribute('data-id')});
|
||||
sv();rv();ts('Gelöscht','err');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
document.getElementById('btn-new').addEventListener('click',function(){op(null)});
|
||||
|
||||
function op(task){
|
||||
pf=document.activeElement;
|
||||
var ie=!!task;
|
||||
var t=task?task.tl:'',d=task?task.ds:'',p=task?task.p:'Mittel',s=task?task.st:'aktiv',du=task&&task.d?task.d.slice(0,10):'';
|
||||
document.getElementById('modal-title').textContent=ie?'Task bearbeiten':'Neues Task';
|
||||
document.getElementById('f-title').value=t;
|
||||
document.getElementById('f-desc').value=d;
|
||||
document.getElementById('f-pr').value=p;
|
||||
document.getElementById('f-due').value=du;
|
||||
var opts='';
|
||||
for(var i=0;i<C.length;i++){opts+='<option'+(s===C[i][1]?' selected':'')+'>'+esc(C[i][0])+'</option>'}
|
||||
document.getElementById('f-st').innerHTML=opts;
|
||||
document.getElementById('modal-bg').classList.add('open');
|
||||
document.getElementById('f-title').focus();
|
||||
|
||||
document.getElementById('f-save').onclick=function(){
|
||||
var nt=document.getElementById('f-title').value.trim();
|
||||
if(!nt){ts('Bitte Titel eingaben','err');document.getElementById('f-title').focus();return}
|
||||
var nd=document.getElementById('f-desc').value.trim();
|
||||
var np=document.getElementById('f-pr').value;
|
||||
var ns=document.getElementById('f-st').value;
|
||||
var ndue=document.getElementById('f-due').value?new Date(document.getElementById('f-due').value+'T00:00:00').toISOString():null;
|
||||
if(ie){
|
||||
var x=tasks.find(function(x){return x.id===task.id});
|
||||
if(x){x.tl=nt;x.ds=nd;x.p=np;x.st=ns;x.d=ndue;if(ns==='fertig'&&!x.df)x.df=Date.now()}
|
||||
}else{
|
||||
tasks.push({id:uid(),tl:nt,ds:nd,p:np,du:ndue,'dt':Date.now(),st:ns,'df':ns==='fertig'?Date.now():null});
|
||||
}
|
||||
sv();rv();cl();ts('Gespeichert','grn');
|
||||
};
|
||||
document.getElementById('f-cancel').onclick=cl;
|
||||
document.getElementById('f-del').onclick=function(){
|
||||
if(!confirm('Löschen?'))return;
|
||||
tasks=tasks.filter(function(x){return x.id!==task.id});
|
||||
sv();rv();cl();ts('Gelöscht','err');
|
||||
};
|
||||
}
|
||||
|
||||
function cl(){
|
||||
document.getElementById('modal-bg').classList.remove('open');
|
||||
if(pf)try{pf.focus()}catch(e){}
|
||||
}
|
||||
|
||||
function rs(){
|
||||
var h='<div id="streak-grid">';
|
||||
if(habits.length===0){h+='<p>Keine Habits. Es gibt keine Streak-Daten.</p>'}
|
||||
else{
|
||||
habits.forEach(function(hb){
|
||||
h+='<div class="streak"><h3>'+esc(hb.n)+'</h3><div class="sg">';
|
||||
for(var i=0;i<28;i++){
|
||||
var d=new Date();d.setDate(d.getDate()-i);
|
||||
var k=d.toISOString().slice(0,10);
|
||||
var v=hb.v?hb.v[k]||0:0;
|
||||
h+='<div class="sl" data-d="'+v+'" data-k="'+k+'" tabindex="0"></div>';
|
||||
}
|
||||
h+='</div></div>';
|
||||
});
|
||||
}
|
||||
h+='</div>';
|
||||
document.getElementById('streaks-section').innerHTML='<h2>Streaks</h2>'+h;
|
||||
document.getElementById('streak-grid').addEventListener('click',function(e){
|
||||
var c=e.target.closest('.sl');if(!c)return;
|
||||
var cur=parseInt(c.getAttribute('data-d')),nxt=(cur+1)%5;
|
||||
c.setAttribute('data-d',nxt);
|
||||
var hn=c.closest('.streak').querySelector('h3').textContent;
|
||||
var ha=habits.find(function(x){return x.n===hn});
|
||||
if(ha){if(!ha.v)ha.v={};ha.v[c.getAttribute('data-k')]=nxt;sv();rs()}
|
||||
});
|
||||
}
|
||||
|
||||
var palIdx=0;
|
||||
var cmds=[];
|
||||
function inipal(){
|
||||
palIdx=0;
|
||||
var items=[];
|
||||
cmds.forEach(function(c,i){items[i]=c});
|
||||
var html='';
|
||||
items.forEach(function(c,i){html+='<div class="pitem" data-i="'+i+'"><span class="name">'+esc(c.n)+'</span><span class="key">'+esc(c.k)+'</span></div>'});
|
||||
document.getElementById('pal-list').innerHTML=html;
|
||||
document.getElementById('pal-list').querySelectorAll('.pitem').forEach(function(el){
|
||||
el.addEventListener('mouseenter',function(){palIdx=parseInt(this.getAttribute('data-i'))});
|
||||
el.addEventListener('click',function(){clp();cmds[parseInt(this.getAttribute('data-i'))].f()});
|
||||
});
|
||||
}
|
||||
|
||||
document.getElementById('pal-q').addEventListener('input',function(){inipal();filterPal()});
|
||||
document.getElementById('pal-list').addEventListener('keydown',function(e){
|
||||
var items=this.querySelectorAll('.pitem');if(!items.length)return;
|
||||
e.preventDefault();
|
||||
if(e.key==='ArrowDown'){palIdx=Math.min(palIdx+1,items.length-1)}
|
||||
else if(e.key==='ArrowUp'){palIdx=Math.max(palIdx-1,0)}
|
||||
else if(e.key==='Enter'){var s=this.querySelector('.pitem.sel');if(s)s.click()}
|
||||
else return;
|
||||
items.forEach(function(el,j){el.classList.toggle('sel',j===palIdx)});
|
||||
});
|
||||
|
||||
function filterPal(){
|
||||
var q=document.getElementById('pal-q').value.toLowerCase(),items=document.querySelectorAll('#pal-list .pitem');
|
||||
items.forEach(function(el){var n=el.querySelector('.name').textContent.toLowerCase();el.style.display=n.indexOf(q)===-1?'none':'flex'});
|
||||
}
|
||||
|
||||
function openPal(){
|
||||
document.getElementById('pal-bg').classList.add('open');
|
||||
document.getElementById('pal-q').focus();
|
||||
inipal();
|
||||
}
|
||||
function clp(){document.getElementById('pal-bg').classList.remove('open')}
|
||||
|
||||
document.querySelector('.palette-bg').addEventListener('click',function(e){
|
||||
if(e.target.id==='pal-bg'||e.target==document.querySelector('.palette-bg'))clp();
|
||||
});
|
||||
|
||||
document.addEventListener('keydown',function(e){
|
||||
if((e.metaKey||e.ctrlKey)&&e.key==='k'){e.preventDefault();openPal();return}
|
||||
if(e.key==='Escape'){cl();clp()}
|
||||
});
|
||||
|
||||
function rp(){
|
||||
cmds=[{n:'Board',k:'Cmd+K',f:function(){clickTab('board')}},{n:'Streaks',k:'Cmd+K',f:function(){clickTab('streaks')}},{n:'Insights',k:'Cmd+K',f:function(){clickTab('insights')}}];
|
||||
}
|
||||
|
||||
function clickTab(name){
|
||||
view=name;
|
||||
var bs=document.querySelectorAll('.sidebar nav button');
|
||||
for(var i=0;i<bs.length;i++){bs[i].classList.toggle('sel',bs[i].getAttribute('data-v')===name)}
|
||||
var s=document.querySelectorAll('.section');
|
||||
for(var i=0;i<s.length;i++){s[i].classList.toggle('show',s[i].id===name+'-section')}
|
||||
rv();
|
||||
}
|
||||
|
||||
function ri(){
|
||||
var tot=tasks.length,dn=0,act=0,ov=0;
|
||||
for(var i=0;i<tasks.length;i++){
|
||||
var t=tasks[i];
|
||||
if(t.st==='fertig')dn++;
|
||||
else if(t.st==='aktiv')act++;
|
||||
else if(t.d&&new Date(t.d)<new Date())ov++;
|
||||
}
|
||||
document.getElementById('insights-stats').innerHTML=
|
||||
'<div class="sc"><div class="v">'+tot+'</div><div class="l">Alle</div></div>'+
|
||||
'<div class="sc"><div class="v" style="color:var(--green)">'+dn+'</div><div class="l">Erledigt</div></div>'+
|
||||
'<div class="sc"><div class="v">'+act+'</div><div class="l">Aktiv</div></div>'+
|
||||
'<div class="sc"><div class="v" style="color:var(--red)">'+ov+'</div><div class="l">Überfällig</div></div>';
|
||||
drawChart();
|
||||
}
|
||||
|
||||
function drawChart(){
|
||||
var cv=document.getElementById('chart');
|
||||
if(!cv)return;
|
||||
cv.width=700;cv.height=200;
|
||||
var x=cv.getContext('2d');
|
||||
x.clearRect(0,0,cv.width,cv.height);
|
||||
var now=new Date();
|
||||
var pts=[];
|
||||
for(var j=0;j<12;j++){
|
||||
var d=new Date(now.getFullYear(),now.getMonth()-j,1);
|
||||
var c=0;
|
||||
for(var i=0;i<tasks.length;i++){
|
||||
if(!tasks[i].df)continue;
|
||||
var fd=new Date(tasks[i].df);
|
||||
if(fd.getMonth()==d.getMonth()&&fd.getFullYear()==d.getFullYear())c++;
|
||||
}
|
||||
pts.push({lb:(d.getMonth()+1)+'/','ct':c});
|
||||
}
|
||||
pts.reverse();
|
||||
var mx=0;
|
||||
for(var i=0;i<pts.length;i++)if(pts[i].ct>mx)mx=pts[i].ct;
|
||||
if(mx===0)mx=1;
|
||||
var mL=35,mR=10,mT=10,mB=25,gW=cv.width-mL-mR,gH=cv.height-mT-mB,bw=42,gp=(gW/12-bw);
|
||||
x.strokeStyle='rgba(0,0,0,0.15)';x.lineWidth=1;
|
||||
for(var i=0;i<=4;i++){
|
||||
var gy=mT+gH-(i/4)*gH;
|
||||
x.beginPath();x.moveTo(mL,gy);x.lineTo(cv.width-mR,gy);x.stroke();
|
||||
x.fillStyle='#6e6e73';x.font='10px sans-serif';x.textAlign='right';
|
||||
x.fillText(Math.round(mx/4*i),mL-5,gy+4);
|
||||
}
|
||||
x.fillStyle='#0071e3';
|
||||
for(var i=0;i<pts.length;i++){
|
||||
var bx=mL+i*(bw+gp/2),bh=Math.max((pts[i].ct/mx)*gH,1),by=mT+gH-bh;
|
||||
x.fillRect(bx,by,bw,bh);
|
||||
x.fillStyle='#6e6e73';x.textAlign='center';x.font='9px sans-serif';
|
||||
x.fillText(pts[i].lb,bx+bw/2,cv.height-mB+12);
|
||||
if(pts[i].ct>0){x.fillStyle='#1d1d1f';x.fillText(pts[i].ct,bx+bw/2,by-4)}
|
||||
}
|
||||
}
|
||||
|
||||
document.getElementById('imp').addEventListener('change',function(e){
|
||||
var f=e.target.files[0];if(!f)return;
|
||||
var r=new FileReader();
|
||||
r.onload=function(ev){
|
||||
try{
|
||||
var d=JSON.parse(ev.target.result);
|
||||
if(!d.tasks||!Array.isArray(d.tasks))throw new Error('tasks fehlt');
|
||||
tasks=d.tasks;
|
||||
if(d.habits)habits=d.habits;
|
||||
sv();rv();ts('Import OK','grn');
|
||||
}catch(err){ts('Import fehlgeschlagen: '+err.message,'err')}
|
||||
e.target.value='';
|
||||
};
|
||||
r.readAsText(f);
|
||||
});
|
||||
document.getElementById('imp').click();
|
||||
|
||||
var loaded=false;
|
||||
function ldDemo(){
|
||||
if(loaded&&tasks.length>0)return;
|
||||
loaded=true;
|
||||
tasks=[];habits=[];
|
||||
var titles=['Dashboard','API','Cache','Logging','Tests','Docs','CI/CD','Auth','Login','Panel','Widget','Chart','Setup','Deploy','Monitor','Alert','Backup','Config','Schema','Migration','Refactor','Bugfix','Feature','Story','Epic','Release','Hotfix','Review','Sprint','Retro'];
|
||||
for(var i=0;i<40;i++){
|
||||
var s=['backlog','aktiv','review','fertig'][i%4];
|
||||
var p=['Hoch','Tief','Mittel','Mittel','Tief'][i%5];
|
||||
tasks.push({id:uid(),tl:titles[i]+' #'+(i+1),ds:'Beschreibung für '+titles[i]+' #'+(i+1),p:p,d:new Date(Date.now()+i*864e5).toISOString(),'dt':Date.now()-i*864e5,st:s,'df':s==='fertig'?Date.now():null});
|
||||
}
|
||||
habits=[{n:'Code',v:gv(15)},{n:'Sport',v:gv(10)},{n:'Lesen',v:gv(8)},{n:'Meditation',v:gv(12)}];
|
||||
sv();
|
||||
}
|
||||
|
||||
function gv(n){var v={};for(var i=0;i<n;i++){var d=new Date();d.setDate(d.getDate()-i);v[d.toISOString().slice(0,10)]=Math.random()<0.65?Math.floor(Math.random()*4)+1:0}return v}
|
||||
|
||||
ld();at();
|
||||
if(!tasks.length&&!habits.length){ldDemo();sv()}
|
||||
rv();
|
||||
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user