// HouseHub — Todo module const API = '/modules/todo/api.php'; // ─── Helpers ───────────────────────────────────────────────────────────────── function escHtml(s){const d=document.createElement('div');d.textContent=String(s??'');return d.innerHTML;} function fmtDate(d){if(!d)return null;const dt=new Date(d+'T00:00:00');return dt.toLocaleDateString('fr-FR',{day:'2-digit',month:'2-digit'});} function isToday(d){if(!d)return false;return d===new Date().toISOString().slice(0,10);} function isOverdue(d){if(!d)return false;return dt.remove(),3000); } async function api(action,method='GET',data=null,extra=''){ const opts={method,headers:{}}; if(data){opts.headers['Content-Type']='application/json';opts.body=JSON.stringify(data);} const r=await fetch(API+'?action='+action+extra,opts); const j=await r.json(); if(!j.ok)throw new Error(j.error||'Erreur'); return j.data; } // ─── State ─────────────────────────────────────────────────────────────────── let currentFilter='all'; let showDone=false; let lists=[]; let editTodoId=null; let editListId=null; let selectedPriority='none'; let selectedColor='#3b82f6'; const COLORS=['#3b82f6','#10b981','#f59e0b','#ef4444','#8b5cf6','#ec4899','#06b6d4','#84cc16','#f97316','#64748b']; const ICONS=['📋','🏠','🛒','💼','💪','🎯','📚','✈️','🎮','❤️','⭐','🔧']; // ─── Sidebar ───────────────────────────────────────────────────────────────── async function loadSidebar(){ try{ const[data,stats]=await Promise.all([api('lists'),api('stats')]); lists=data; const el=document.getElementById('todo-sidebar-lists'); // Smart views let smartHtml=`
Vue
📋 Toutes ${stats.pending}
📅 Aujourd'hui ${stats.today}
🗓️ À venir
`; if(parseInt(stats.overdue)>0){ smartHtml+=`
🔴 En retard ${stats.overdue}
`; } smartHtml+=`
✅ Terminées ${stats.done}
`; // Lists let listsHtml='
Listes
'; lists.forEach(l=>{ const act=currentFilter==='list_'+l.id?' active':''; listsHtml+=`
${escHtml(l.icon)} ${escHtml(l.name)} ${l.pending||0}
`; }); el.innerHTML=smartHtml+listsHtml; }catch(e){} } function setFilter(f){ currentFilter=f;showDone=f==='done'; loadSidebar();loadTodos(); } // ─── Todo list ──────────────────────────────────────────────────────────────── async function loadTodos(){ try{ let extra=''; if(currentFilter==='all'){extra='';} else if(currentFilter==='done'){extra='&show_done=1';} else if(currentFilter==='today'){extra='&list_id=today';} else if(currentFilter==='upcoming'){extra='&list_id=upcoming';} else if(currentFilter==='overdue'){extra='&list_id=overdue';} else if(currentFilter.startsWith('list_')){extra='&list_id='+currentFilter.slice(5);} if(showDone&¤tFilter!=='done')extra+='&show_done=1'; const todos=await api('todos','GET',null,extra); renderTodos(todos); // Update quick-add list selector updateQuickAddList(); // Header title updateHeader(); }catch(e){toast(e.message,'error');} } function updateHeader(){ const el=document.getElementById('todo-header-title'); if(!el)return; if(currentFilter==='all')el.textContent='Toutes les tâches'; else if(currentFilter==='today')el.textContent='Aujourd\'hui'; else if(currentFilter==='upcoming')el.textContent='À venir'; else if(currentFilter==='done')el.textContent='Tâches terminées'; else if(currentFilter==='overdue')el.textContent='En retard'; else if(currentFilter.startsWith('list_')){ const l=lists.find(x=>x.id==currentFilter.slice(5)); el.textContent=l?(l.icon+' '+l.name):'Liste'; } } function updateQuickAddList(){ const sel=document.getElementById('quick-add-list'); if(!sel)return; sel.innerHTML=''+ lists.map(l=>``).join(''); // Pre-select current list if(currentFilter.startsWith('list_')) sel.value=currentFilter.slice(5); } function renderTodos(todos){ const el=document.getElementById('todo-list'); if(!todos.length){ el.innerHTML=`
${currentFilter==='done'?'✅':'📋'}

${currentFilter==='done'?'Aucune tâche terminée.':'Aucune tâche. Ajoutez-en une !'}

`; return; } // Group: pending then done const pending=todos.filter(t=>!t.done); const done=todos.filter(t=>t.done); let html=''; pending.forEach(t=>{ html+=todoItemHtml(t); }); if(done.length&&showDone){ html+=`
Terminées (${done.length})
`; done.forEach(t=>{ html+=todoItemHtml(t); }); } else if(done.length&&!showDone){ html+=`
`; } el.innerHTML=html; } function todoItemHtml(t){ const checkClass='todo-check'+(t.done?' checked':''); const itemClass='todo-item'+(t.done?' done':''); const dueCls=isOverdue(t.due_date)&&!t.done?' overdue':isToday(t.due_date)&&!t.done?' today':''; const dueLabel=t.due_date?(isToday(t.due_date)?'Aujourd\'hui':isOverdue(t.due_date)?'En retard '+fmtDate(t.due_date):fmtDate(t.due_date)):null; const priBadge=t.priority&&t.priority!=='none'? `${t.priority==='high'?'Urgent':t.priority==='medium'?'Normal':'Bas'}`:''; const listBadge=t.list_name? `${escHtml(t.list_icon||'')} ${escHtml(t.list_name)}`:''; return `
${escHtml(t.title)}
${t.notes?`
${escHtml(t.notes)}
`:''}
${dueLabel?`📅 ${escHtml(dueLabel)}`:''} ${priBadge}${listBadge}
`; } async function toggleDone(e,id,done){ e.stopPropagation(); try{ await api('todos','PUT',{done:done===1},'&id='+id); const item=document.querySelector(`.todo-item[data-id="${id}"]`); if(item){ const check=item.querySelector('.todo-check'); check.classList.add('just-done'); setTimeout(()=>{check.classList.remove('just-done');loadTodos();loadSidebar();},300); } }catch(e){toast(e.message,'error');} } async function deleteTodo(e,id){ e.stopPropagation(); if(!confirm('Supprimer cette tâche ?'))return; try{await api('todos','DELETE',null,'&id='+id);toast('Supprimée');loadTodos();loadSidebar();} catch(e){toast(e.message,'error');} } // ─── Quick add ──────────────────────────────────────────────────────────────── async function quickAdd(e){ if(e.key!=='Enter')return; const inp=document.getElementById('quick-add-input'); const title=inp.value.trim(); if(!title)return; const listSel=document.getElementById('quick-add-list'); try{ await api('todos','POST',{title,list_id:listSel?.value||null,priority:'none'}); inp.value='';toast('Tâche ajoutée');loadTodos();loadSidebar(); }catch(e){toast(e.message,'error');} } // ─── Todo modal ─────────────────────────────────────────────────────────────── function openAddTodo(){ editTodoId=null;selectedPriority='none'; document.getElementById('todo-modal-title').textContent='Nouvelle tâche'; document.getElementById('todo-form-title').value=''; document.getElementById('todo-form-notes').value=''; document.getElementById('todo-form-due').value=''; document.getElementById('todo-delete-btn').style.display='none'; // List selector const sel=document.getElementById('todo-form-list'); sel.innerHTML=''+lists.map(l=>``).join(''); if(currentFilter.startsWith('list_')) sel.value=currentFilter.slice(5); setPriority('none'); openModal('todo-modal'); } async function openEditTodo(id){ editTodoId=id; try{ const todos=await api('todos','GET',null,'&action=todos'); // fetch single - use filter by all and find const all=await api('todos','GET',null,'&show_done=1'); const t=all.find(x=>x.id==id); if(!t)return; document.getElementById('todo-modal-title').textContent='Modifier la tâche'; document.getElementById('todo-form-title').value=t.title; document.getElementById('todo-form-notes').value=t.notes||''; document.getElementById('todo-form-due').value=t.due_date||''; document.getElementById('todo-delete-btn').style.display=''; const sel=document.getElementById('todo-form-list'); sel.innerHTML=''+lists.map(l=>``).join(''); sel.value=t.list_id||''; setPriority(t.priority||'none'); openModal('todo-modal'); }catch(e){toast(e.message,'error');} } function setPriority(p){ selectedPriority=p; document.querySelectorAll('.priority-opt').forEach(el=>{ el.className='priority-opt'; if(el.dataset.p===p) el.classList.add('sel-'+p); }); } async function saveTodo(){ const title=document.getElementById('todo-form-title').value.trim(); if(!title){toast('Titre requis','error');return;} const data={ title, notes:document.getElementById('todo-form-notes').value||null, due_date:document.getElementById('todo-form-due').value||null, list_id:document.getElementById('todo-form-list').value||null, priority:selectedPriority }; try{ if(editTodoId){await api('todos','PUT',data,'&id='+editTodoId);toast('Tâche mise à jour');} else{await api('todos','POST',data);toast('Tâche ajoutée');} closeModal('todo-modal');loadTodos();loadSidebar(); }catch(e){toast(e.message,'error');} } async function deleteTodoFromModal(){ if(!editTodoId||!confirm('Supprimer cette tâche ?'))return; try{await api('todos','DELETE',null,'&id='+editTodoId);closeModal('todo-modal');toast('Supprimée');loadTodos();loadSidebar();} catch(e){toast(e.message,'error');} } // ─── List modal ─────────────────────────────────────────────────────────────── function openListModal(id=null){ editListId=id;selectedColor='#3b82f6'; let list=id?lists.find(l=>l.id==id):null; document.getElementById('list-modal-title').textContent=id?'Modifier la liste':'Nouvelle liste'; document.getElementById('list-form-name').value=list?.name||''; document.getElementById('list-delete-btn').style.display=id?'':'none'; selectedColor=list?.color||'#3b82f6'; // Render swatches document.getElementById('list-color-swatches').innerHTML=COLORS.map(c=> `
`).join(''); // Render icons document.getElementById('list-icon-opts').innerHTML=ICONS.map(i=> ``).join(''); openModal('list-modal'); } function selectColor(c){ selectedColor=c; document.querySelectorAll('.color-swatch').forEach(s=>{s.classList.toggle('selected',s.style.background===c||rgbToHex(s.style.background)===c);}); } function rgbToHex(rgb){ const m=rgb.match(/\d+/g);if(!m||m.length<3)return rgb; return '#'+m.slice(0,3).map(x=>parseInt(x).toString(16).padStart(2,'0')).join(''); } let selectedIcon='📋'; function selectIcon(btn,icon){ selectedIcon=icon; document.querySelectorAll('#list-icon-opts button').forEach(b=>{b.style.background='';b.style.borderColor='';}); btn.style.background='#eff6ff';btn.style.borderColor='var(--primary)'; } async function saveList(){ const name=document.getElementById('list-form-name').value.trim(); if(!name){toast('Nom requis','error');return;} const data={name,color:selectedColor,icon:selectedIcon}; try{ if(editListId){await api('lists','PUT',data,'&id='+editListId);} else{await api('lists','POST',data);} closeModal('list-modal');toast(editListId?'Liste mise à jour':'Liste créée'); loadSidebar(); }catch(e){toast(e.message,'error');} } async function deleteList(){ if(!editListId||!confirm('Supprimer la liste et toutes ses tâches ?'))return; try{await api('lists','DELETE',null,'&id='+editListId);closeModal('list-modal');toast('Liste supprimée');setFilter('all');} catch(e){toast(e.message,'error');} } // ─── Modal helpers ──────────────────────────────────────────────────────────── function openModal(id){document.getElementById(id)?.classList.add('show');} function closeModal(id){document.getElementById(id)?.classList.remove('show');} // ─── Init ───────────────────────────────────────────────────────────────────── document.addEventListener('DOMContentLoaded',()=>{ loadSidebar(); loadTodos(); // Quick add enter document.getElementById('quick-add-input')?.addEventListener('keydown',quickAdd); // Modal backdrops document.querySelectorAll('.todo-modal-backdrop').forEach(m=>m.addEventListener('click',e=>{if(e.target===m)m.classList.remove('show');})); });