|
373 | 373 | <option value="medium" selected>medium</option> |
374 | 374 | <option value="high">high</option> |
375 | 375 | </select> |
| 376 | + <input class="todo-input" id="todo-due-input" type="date" style="flex:0 0 140px;color:var(--text-muted)" title="due date (optional)" /> |
376 | 377 | <button class="btn-primary" onclick="addTodo()">[ add ]</button> |
377 | 378 | </div> |
378 | 379 | <div class="todo-filters"> |
|
388 | 389 | <!-- Plans tab --> |
389 | 390 | <div id="tab-plans" class="tab-panel"> |
390 | 391 | <div class="card"> |
391 | | - <div class="card-title"><div class="card-title-left"><span class="card-icon"></span>kanban plans</div></div> |
| 392 | + <div class="card-title"><div class="card-title-left"><span class="card-icon"></span>plans</div></div> |
392 | 393 | <div class="plan-form"> |
393 | 394 | <input class="plan-input" id="plan-title-input" placeholder="plan title..." /> |
394 | 395 | <input class="plan-input" id="plan-desc-input" placeholder="description (optional)" style="flex:2" /> |
| 396 | + <input class="todo-input" id="plan-due-input" type="date" style="flex:0 0 140px;color:var(--text-muted);background:var(--surface2);border:1px solid var(--border);padding:8px 10px;font-family:var(--font-mono);font-size:11px;outline:none" title="due date (optional)" /> |
395 | 397 | <select class="todo-select" id="plan-col-input"> |
396 | 398 | <option value="backlog">backlog</option> |
397 | 399 | <option value="inprogress">in progress</option> |
|
498 | 500 |
|
499 | 501 | <script> |
500 | 502 | // ── CONFIG — update this ────────────────────────────────────── |
501 | | -const API_BASE = 'https://dev-dashboard-api.devsekarindhu18.workers.dev'; |
| 503 | +const API_BASE = 'https://YOUR-WORKER.workers.dev'; |
502 | 504 |
|
503 | 505 | // ── State ───────────────────────────────────────────────────── |
504 | 506 | let AUTH_TOKEN = ''; |
|
724 | 726 | function renderRepos(){ |
725 | 727 | const top=[...repoData].sort((a,b)=>b.stargazers_count-a.stargazers_count).slice(0,6); |
726 | 728 | document.getElementById('repo-list').innerHTML=top.map(r=>` |
727 | | - <div class="repo-item"> |
| 729 | + <a class="repo-item" href="${r.html_url}" target="_blank" rel="noopener" style="text-decoration:none;cursor:pointer;display:flex;align-items:flex-start;justify-content:space-between;padding:10px 0;border-bottom:1px solid var(--border);gap:12px;transition:background .15s;" onmouseover="this.style.background='rgba(0,255,136,0.04)'" onmouseout="this.style.background='transparent'"> |
728 | 730 | <div> |
729 | | - <div class="repo-name">${r.name}</div> |
| 731 | + <div class="repo-name" style="color:var(--blue)">${r.name} ↗</div> |
730 | 732 | <div class="repo-desc">${r.description||'no description'}</div> |
731 | 733 | <div style="margin-top:4px;font-size:11px">${r.language?`<span class="lang-dot" style="background:${lc(r.language)}"></span>${r.language}`:''}</div> |
732 | 734 | </div> |
733 | 735 | <div class="repo-meta"><span class="stars">★ ${r.stargazers_count}</span><span>⑂ ${r.forks_count}</span></div> |
734 | | - </div>`).join(''); |
| 736 | + </a>`).join(''); |
735 | 737 | } |
736 | 738 |
|
737 | 739 | // ── Todos ───────────────────────────────────────────────────── |
|
745 | 747 | const filtered=todoFilter==='all'?todos:todos.filter(t=>t.status===todoFilter); |
746 | 748 | document.getElementById('todo-list').innerHTML=filtered.length===0 |
747 | 749 | ?`<div style="color:var(--text-dim);font-size:12px;padding:12px 0">no todos here</div>` |
748 | | - :filtered.map(t=>` |
749 | | - <div class="todo-item"> |
750 | | - <div class="todo-check ${t.status==='done'?'checked':''}" onclick="toggleTodo(${t.id},'${t.status}')"></div> |
751 | | - <span class="todo-title ${t.status==='done'?'done':''}">${t.title}</span> |
752 | | - <span class="todo-badge badge-${t.priority}">${t.priority}</span> |
753 | | - <button class="todo-del" onclick="deleteTodo(${t.id})">×</button> |
754 | | - </div>`).join(''); |
| 750 | + :filtered.map(t=>{ |
| 751 | + const statusColors={todo:'var(--text-dim)',wip:'var(--amber)',done:'var(--green)'}; |
| 752 | + const nextStatus={todo:'wip',wip:'done',done:'todo'}; |
| 753 | + const statusLabel={todo:'TODO',wip:'WIP',done:'DONE'}; |
| 754 | + const due=t.due_date?new Date(t.due_date):null; |
| 755 | + const now=new Date(); |
| 756 | + const isOverdue=due&&due<now&&t.status!=='done'; |
| 757 | + const dueStr=due?due.toLocaleDateString('en-GB',{day:'2-digit',month:'short'}):''; |
| 758 | + return`<div class="todo-item"> |
| 759 | + <div class="todo-check ${t.status==='done'?'checked':''}" onclick="cycleTodo(${t.id},'${t.status}')" title="click to cycle: todo → wip → done"></div> |
| 760 | + <span class="todo-title ${t.status==='done'?'done':''}" style="flex:1">${t.title}</span> |
| 761 | + ${dueStr?`<span style="font-size:10px;color:${isOverdue?'var(--red)':'var(--text-dim)'};margin-right:6px;flex-shrink:0">${isOverdue?'⚠ ':''}${dueStr}</span>`:''} |
| 762 | + <span style="font-size:9px;padding:2px 6px;border:1px solid ${statusColors[t.status]};color:${statusColors[t.status]};flex-shrink:0;cursor:pointer;letter-spacing:.5px" onclick="cycleTodo(${t.id},'${t.status}')" title="click to advance status">${statusLabel[t.status]}</span> |
| 763 | + <span class="todo-badge badge-${t.priority}" style="margin-left:6px">${t.priority}</span> |
| 764 | + <button class="todo-del" onclick="deleteTodo(${t.id})">×</button> |
| 765 | + </div>`; |
| 766 | + }).join(''); |
755 | 767 | } |
756 | 768 |
|
757 | 769 | function filterTodos(f,btn){ |
|
764 | 776 | async function addTodo(){ |
765 | 777 | const title=document.getElementById('todo-title-input').value.trim(); |
766 | 778 | const priority=document.getElementById('todo-priority-input').value; |
| 779 | + const due_date=document.getElementById('todo-due-input').value||null; |
767 | 780 | if(!title)return; |
768 | 781 | document.getElementById('todo-title-input').value=''; |
769 | | - const todo=await api('POST','/api/todos',{title,priority}); |
| 782 | + document.getElementById('todo-due-input').value=''; |
| 783 | + const todo=await api('POST','/api/todos',{title,priority,due_date}); |
770 | 784 | if(todo.id){todos=[todo,...todos];renderTodos()} |
771 | 785 | } |
772 | 786 |
|
773 | | -async function toggleTodo(id,status){ |
774 | | - const newStatus=status==='done'?'todo':'done'; |
775 | | - const updated=await api('PUT',`/api/todos/${id}`,{status:newStatus}); |
| 787 | +async function cycleTodo(id,status){ |
| 788 | + const next={todo:'wip',wip:'done',done:'todo'}; |
| 789 | + const updated=await api('PUT',`/api/todos/${id}`,{status:next[status]}); |
776 | 790 | if(updated.id){todos=todos.map(t=>t.id===id?updated:t);renderTodos()} |
777 | 791 | } |
778 | 792 |
|
|
793 | 807 |
|
794 | 808 | function renderPlans(){ |
795 | 809 | ['backlog','inprogress','done'].forEach(col=>{ |
796 | | - const cards=plans.filter(p=>p.column===col); |
| 810 | + const cards=plans.filter(p=>p.plan_column===col); |
797 | 811 | document.getElementById('count-'+col).textContent=cards.length; |
798 | 812 | document.getElementById('cards-'+col).innerHTML=cards.map(p=>{ |
799 | 813 | const cols=['backlog','inprogress','done']; |
800 | 814 | const idx=cols.indexOf(col); |
801 | 815 | const prevCol=idx>0?cols[idx-1]:null; |
802 | 816 | const nextCol=idx<cols.length-1?cols[idx+1]:null; |
| 817 | + const pDue=p.due_date?new Date(p.due_date):null; |
| 818 | + const pNow=new Date(); |
| 819 | + const pOverdue=pDue&&pDue<pNow&&col!=='done'; |
| 820 | + const pDueStr=pDue?pDue.toLocaleDateString('en-GB',{day:'2-digit',month:'short',year:'2-digit'}):''; |
803 | 821 | return`<div class="kanban-card"> |
804 | | - <div class="kanban-card-title">${p.title}</div> |
| 822 | + <div style="display:flex;align-items:flex-start;justify-content:space-between;gap:8px;margin-bottom:4px"> |
| 823 | + <div class="kanban-card-title" style="flex:1">${p.title}</div> |
| 824 | + ${pDueStr?`<span style="font-size:10px;color:${pOverdue?'var(--red)':'var(--text-dim)'};flex-shrink:0;white-space:nowrap">${pOverdue?'⚠ ':''}${pDueStr}</span>`:''} |
| 825 | + </div> |
805 | 826 | ${p.description?`<div class="kanban-card-desc">${p.description}</div>`:''} |
806 | 827 | ${p.tags?.length?`<div class="kanban-card-tags">${p.tags.map(t=>`<span class="kanban-tag">${t}</span>`).join('')}</div>`:''} |
807 | 828 | <div class="kanban-card-actions"> |
|
818 | 839 | const title=document.getElementById('plan-title-input').value.trim(); |
819 | 840 | const desc=document.getElementById('plan-desc-input').value.trim(); |
820 | 841 | const column=document.getElementById('plan-col-input').value; |
| 842 | + const due_date=document.getElementById('plan-due-input').value||null; |
821 | 843 | if(!title)return; |
822 | 844 | document.getElementById('plan-title-input').value=''; |
823 | 845 | document.getElementById('plan-desc-input').value=''; |
824 | | - const plan=await api('POST','/api/plans',{title,description:desc||null,column}); |
| 846 | + document.getElementById('plan-due-input').value=''; |
| 847 | + const plan=await api('POST','/api/plans',{title,description:desc||null,plan_column:column,due_date}); |
825 | 848 | if(plan.id){plans=[...plans,plan];renderPlans()} |
826 | 849 | } |
827 | 850 |
|
828 | | -async function movePlan(id,column){ |
829 | | - const updated=await api('PUT',`/api/plans/${id}`,{column}); |
| 851 | +async function movePlan(id,plan_column){ |
| 852 | + const updated=await api('PUT',`/api/plans/${id}`,{plan_column}); |
830 | 853 | if(updated.id){plans=plans.map(p=>p.id===id?updated:p);renderPlans()} |
831 | 854 | } |
832 | 855 |
|
|
0 commit comments