// frontend/src/pages/TodosPage.tsx import { useState, useEffect, useCallback } from 'react' import type { Todo, TodoCreate, TodoFilters } from '../api/todos' import { fetchTodos, createTodo, updateTodo, deleteTodo, postponeTodo } from '../api/todos' import SwipeableRow from '../components/todos/SwipeableRow' import TodoForm, { DOMAIN_COLORS } from '../components/todos/TodoForm' import Modal from '../components/Modal' import { useActionButton } from '../contexts/ActionButtonContext' type EditingTodo = Todo | null const DOMAINS = [ 'informatique', 'diy', 'electronique', 'domotique', 'bricolage', 'jardin', 'cuisine', 'voyage', 'animaux', ] const STATUS_LABELS: Record = { pending: 'En cours', done: 'Terminé', cancelled: 'Annulé', } const PRIORITY_COLORS: Record = { high: 'var(--err)', medium: 'var(--warn)', low: 'var(--ink-3)', } const selectStyle: React.CSSProperties = { background: 'var(--bg-3)', border: '1px solid var(--bg-5)', borderRadius: 8, padding: '6px 10px', color: 'var(--ink-1)', fontFamily: 'var(--font-ui)', fontSize: 13, } const noSelect: React.CSSProperties = { userSelect: 'none' } export default function TodosPage() { const [todos, setTodos] = useState([]) const [loading, setLoading] = useState(true) const [error, setError] = useState(null) const [showForm, setShowForm] = useState(false) const [editingTodo, setEditingTodo] = useState(null) const [filters, setFilters] = useState({ status: 'pending' }) const { setActionButton } = useActionButton() useEffect(() => { setActionButton( ) return () => setActionButton(null) }, [setActionButton]) const load = useCallback(async () => { setLoading(true) setError(null) try { setTodos(await fetchTodos(filters)) } catch { setError('Erreur lors du chargement des tâches') } finally { setLoading(false) } }, [filters]) useEffect(() => { void load() }, [load]) async function handleCreate(data: TodoCreate) { try { await createTodo(data) setShowForm(false) void load() } catch { setError('Erreur lors de la création') } } async function handleUpdate(id: string, data: TodoCreate) { try { await updateTodo(id, data) setEditingTodo(null) void load() } catch { setError('Erreur lors de la mise à jour') } } async function handleDone(id: string) { try { await updateTodo(id, { status: 'done' }) void load() } catch { setError('Erreur lors de la mise à jour') } } async function handleDelete(id: string) { try { await deleteTodo(id) void load() } catch { setError('Erreur lors de la suppression') } } async function handlePostpone(id: string, days: 1 | 7) { try { await postponeTodo(id, days) void load() } catch { setError('Erreur lors du report') } } // Grouper par domaine (un todo peut apparaître dans plusieurs groupes) const grouped = DOMAINS.reduce>((acc, d) => { const items = todos.filter(t => t.domains.includes(d)) if (items.length > 0) acc[d] = items return acc }, {}) const sansDomaine = todos.filter(t => t.domains.length === 0) if (sansDomaine.length > 0) grouped['—'] = sansDomaine return (
{/* En-tête + filtre statut uniquement */}

Tâches

{error && (

{error}

)} {/* Modal création */} {showForm && ( setShowForm(false)}> setShowForm(false)} /> )} {/* Modal édition */} {editingTodo && ( setEditingTodo(null)}> handleUpdate(editingTodo.id, data)} onCancel={() => setEditingTodo(null)} submitLabel="Enregistrer" initialValues={{ title: editingTodo.title, domains: editingTodo.domains, priority: editingTodo.priority, due_date: editingTodo.due_date ?? undefined, body: editingTodo.body ?? undefined, url: editingTodo.url ?? undefined, tags: editingTodo.tags, photo_path: editingTodo.photo_path ?? undefined, gps_lat: editingTodo.gps_lat ?? undefined, gps_lng: editingTodo.gps_lng ?? undefined, }} /> )} {loading && (

Chargement…

)} {/* Vue mobile — liste groupée par domaine */}
{!loading && Object.entries(grouped).map(([domain, items]) => (
{domain} {items.length}
{items.map((todo, idx) => ( void handleDone(todo.id)} onSwipeLeft={() => setEditingTodo(todo)} rightContent={
} >
{todo.title}
{todo.domains.map(d => ( {d} ))} {todo.due_date && ( {new Date(todo.due_date).toLocaleDateString('fr-FR')} {todo.postponed_count > 0 && ` · reporté ${todo.postponed_count}×`} )}
))}
))} {!loading && todos.length === 0 && (

Aucune tâche

)}
{/* Vue laptop — tableau filtrable */}
{!loading && ( <> {/* Filtre période */}
Période : setFilters(f => ({ ...f, due_after: e.target.value || undefined }))} /> setFilters(f => ({ ...f, due_before: e.target.value || undefined }))} />
{['Titre', 'Domaines', 'Priorité', 'Statut', 'Date objectif', 'Reports', 'Actions'].map(h => ( ))} {todos.map((todo, idx) => ( ))}
{h}
setEditingTodo(todo)} title="Double-clic pour modifier" >
{todo.title}
{todo.tags.length > 0 && (
{todo.tags.map(tag => ( {tag} ))}
)}
{todo.domains.length === 0 ? ( ) : todo.domains.map(d => ( {d} ))}
{todo.priority} {STATUS_LABELS[todo.status] ?? todo.status} {todo.due_date ? new Date(todo.due_date).toLocaleDateString('fr-FR') : '—'} {todo.postponed_count > 0 ? `${todo.postponed_count}×` : '—'}
{todo.status === 'pending' && ( <> )}
{todos.length === 0 && (

Aucune tâche

)}
)}
) }