diff --git a/cron/todo_notify.php b/cron/todo_notify.php index 250fa02..2db9681 100644 --- a/cron/todo_notify.php +++ b/cron/todo_notify.php @@ -1,7 +1,8 @@ query("SHOW TABLES LIKE 'pf_todos'")->fetchColumn()) continue; - // Get Discord webhook for this family $ws = $pdo->prepare("SELECT content FROM pf_notes WHERE note_type='todo_settings' AND reference_id='webhook_discord'"); $ws->execute(); $webhook = $ws->fetchColumn(); if (!$webhook) continue; - // Tasks due now: due_time reached, not done, not yet notified - // Handles: date+time, time only (null date = today), overdue with time + // Daily reminder: fires every day when due_time is reached, resets at midnight $stmt = $pdo->prepare(" - SELECT t.id, t.title, t.due_date, t.due_time, + SELECT t.id, t.title, t.due_time, l.name AS list_name, l.icon AS list_icon FROM pf_todos t LEFT JOIN pf_todo_lists l ON l.id = t.list_id WHERE t.due_time IS NOT NULL - AND (t.due_date IS NULL OR t.due_date <= CURDATE()) AND t.due_time <= CURTIME() AND t.done = 0 - AND t.notified = 0 + AND (t.notified_date IS NULL OR t.notified_date < CURDATE()) "); $stmt->execute(); foreach ($stmt->fetchAll() as $t) { $time = substr($t['due_time'], 0, 5); $list = $t['list_name'] ? " _({$t['list_icon']} {$t['list_name']})_" : ''; - $msg = "⏰ **Rappel** : {$t['title']}{$list} — prévu à {$time}"; + $msg = "⏰ **Rappel** : {$t['title']}{$list} — {$time}"; $ch = curl_init($webhook); curl_setopt_array($ch, [ @@ -93,7 +90,7 @@ foreach ($families as $family_id) { curl_close($ch); if ($ok) { - $pdo->prepare("UPDATE pf_todos SET notified=1 WHERE id=?")->execute([$t['id']]); + $pdo->prepare("UPDATE pf_todos SET notified_date = CURDATE() WHERE id=?")->execute([$t['id']]); } } } diff --git a/docker/schema_family.sql b/docker/schema_family.sql index baf878d..028b4b1 100644 --- a/docker/schema_family.sql +++ b/docker/schema_family.sql @@ -336,6 +336,7 @@ CREATE TABLE IF NOT EXISTS pf_todos ( due_date DATE DEFAULT NULL, due_time TIME DEFAULT NULL, notified TINYINT(1) DEFAULT 0, + notified_date DATE DEFAULT NULL, priority ENUM('none','low','medium','high') DEFAULT 'none', done TINYINT(1) DEFAULT 0, done_at DATETIME DEFAULT NULL, diff --git a/modules/todo/api.php b/modules/todo/api.php index efd1a27..8c8cb7e 100644 --- a/modules/todo/api.php +++ b/modules/todo/api.php @@ -122,10 +122,7 @@ if ($action === 'todos') { $new = $pdo->prepare("SELECT t.*, l.name as list_name, l.color as list_color, l.icon as list_icon FROM pf_todos t LEFT JOIN pf_todo_lists l ON l.id = t.list_id WHERE t.id=?"); $new->execute([$id]); $row = $new->fetch(); - // Only notify immediately if no due_time (otherwise cron sends it at the right time) - if (empty($d['due_time'])) { - discordNotify($pdo, "📋 **Nouvelle tâche** : " . $title . ($row['list_name'] ? " _(". $row['list_name'] .")_" : "")); - } + // Notifications handled by cron at due_time each day tOk($row); } @@ -148,8 +145,8 @@ if ($action === 'todos') { // Full update $title = trim($d['title'] ?? ''); if (!$title) tErr('Titre requis'); - // Reset notified if due_time changed - $pdo->prepare("UPDATE pf_todos SET list_id=?, title=?, notes=?, due_date=?, due_time=?, priority=?, notified=0, updated_at=NOW() WHERE id=?") + // Reset notified_date so cron fires again today if time changed + $pdo->prepare("UPDATE pf_todos SET list_id=?, title=?, notes=?, due_date=?, due_time=?, priority=?, notified_date=NULL, updated_at=NOW() WHERE id=?") ->execute([$d['list_id'] ?: null, $title, $d['notes'] ?? null, $d['due_date'] ?: null, $d['due_time'] ?: null, $d['priority'] ?? 'none', $id]); tOk(['updated' => true]); } diff --git a/modules/todo/assets/todo.js b/modules/todo/assets/todo.js index a420384..79e2bf8 100644 --- a/modules/todo/assets/todo.js +++ b/modules/todo/assets/todo.js @@ -47,20 +47,7 @@ async function loadSidebar(){ 📋 Toutes ${stats.pending||0} -
- ☀️ Aujourd'hui - ${stats.today||0} -
-
- 📅 À venir -
`; - if(parseInt(stats.overdue)>0){ - html+=`
- ⚠️ En retard - ${stats.overdue} -
`; - } - html+=`
+
Terminées ${stats.done||0}
`; @@ -91,11 +78,7 @@ function setFilter(f){ async function loadTodos(){ try{ let extra=''; - if(currentFilter==='all')extra=''; - else if(currentFilter==='done')extra='&list_id=done&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'; + if(currentFilter==='done')extra='&list_id=done&show_done=1'; else if(currentFilter.startsWith('list_'))extra='&list_id='+currentFilter.slice(5); if(showDone&¤tFilter!=='done')extra+='&show_done=1'; @@ -109,7 +92,7 @@ async function loadTodos(){ function updateHeader(){ const el=document.getElementById('todo-header-title'); if(!el)return; - const map={all:'Toutes les tâches',today:"Aujourd'hui",upcoming:'À venir',done:'Tâches terminées',overdue:'En retard'}; + const map={all:'Toutes les tâches',done:'Tâches terminées'}; if(map[currentFilter]){el.textContent=map[currentFilter];return;} if(currentFilter.startsWith('list_')){ const l=lists.find(x=>x.id==currentFilter.slice(5)); @@ -158,8 +141,7 @@ function todoItemHtml(t){ const checkClass='todo-check'+(isDone?' checked':''); const itemClass='todo-item'+(isDone?' done':''); const dueCls=isOverdue(t.due_date)&&!isDone?' overdue':isToday(t.due_date)&&!isDone?' today':''; - const timeLabel=t.due_time?fmtTime(t.due_time):null; - const dueLabel=t.due_date?(isToday(t.due_date)?'Aujourd\'hui'+(timeLabel?' à '+timeLabel:''):isOverdue(t.due_date)?'En retard '+fmtDate(t.due_date)+(timeLabel?' '+timeLabel:''):fmtDate(t.due_date)+(timeLabel?' à '+timeLabel:'')):null; + const dueLabel=t.due_time?('🔔 '+fmtTime(t.due_time)):null; const priBadge=t.priority&&t.priority!=='none'? `${t.priority==='high'?'Urgent':t.priority==='medium'?'Normal':'Bas'}`:''; const listBadge=t.list_name? @@ -170,7 +152,7 @@ function todoItemHtml(t){
${escHtml(t.title)}
${t.notes?`
${escHtml(t.notes)}
`:''}
- ${dueLabel?`📅 ${escHtml(dueLabel)}`:''} + ${dueLabel?`${escHtml(dueLabel)}`:''} ${priBadge}${listBadge}
@@ -220,7 +202,6 @@ function openAddTodo(){ 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-form-time').value=''; document.getElementById('todo-delete-btn').style.display='none'; const sel=document.getElementById('todo-form-list'); @@ -240,7 +221,6 @@ async function openEditTodo(id){ 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-form-time').value=t.due_time?t.due_time.slice(0,5):''; document.getElementById('todo-delete-btn').style.display=''; const sel=document.getElementById('todo-form-list'); @@ -262,11 +242,12 @@ function setPriority(p){ async function saveTodo(){ const title=document.getElementById('todo-form-title').value.trim(); if(!title){toast('Titre requis','error');return;} + const time=document.getElementById('todo-form-time').value; + if(!time){toast('Heure de rappel requise','error');return;} const data={ title, notes:document.getElementById('todo-form-notes').value||null, - due_date:document.getElementById('todo-form-due').value||null, - due_time:document.getElementById('todo-form-time').value||null, + due_time:time, list_id:document.getElementById('todo-form-list').value||null, priority:selectedPriority }; diff --git a/todo.php b/todo.php index f4c7e97..a65f8be 100644 --- a/todo.php +++ b/todo.php @@ -74,22 +74,17 @@ require __DIR__ . '/header.php';
- - + +
- - + +
-
- - -
-