stephaneibens commited on
Commit
e636ecd
·
verified ·
1 Parent(s): 812a292

Tu es DeepSite V2. Crée une application web responsive de gestion de tâches (To-Do List) avec dates d’échéance, rappels, catégories, priorités, et vues par catégorie/priorité. 🎯 Objectif - Un gestionnaire de tâches simple, rapide, multi-appareils (desktop + mobile), clair et efficace. - Toutes les données sont privées à l’utilisateur connecté. 🔐 Authentification - Activer l’inscription/connexion (email + mot de passe). - Chaque ressource est isolée par user_id (multi-tenant). 🗃️ Modèle de données (DB) - users(id, email, password_hash, created_at) - categories(id, user_id, name UNIQUE par user, color HEX, icon TEXT, sort_index INT, created_at) - tasks( id, user_id, title VARCHAR(140) NOT NULL, description TEXT, status ENUM('todo','in_progress','done','archived') DEFAULT 'todo', priority ENUM('low','normal','high','urgent') DEFAULT 'normal', category_id FK -> categories.id NULL, due_at TIMESTAMP NULL, completed_at TIMESTAMP NULL, created_at TIMESTAMP NOT NULL, updated_at TIMESTAMP NOT NULL ) + index(user_id, status, priority, due_at, category_id) - reminders( id, user_id, task_id FK -> tasks.id, remind_at TIMESTAMP NOT NULL, channel ENUM('in_app','email','push') DEFAULT 'in_app', created_at ) + index(user_id, remind_at) 🧭 Règles & validations - title obligatoire (≤ 140 caractères). - Max 5 rappels par tâche. - due_at optionnel; si passé et status ≠ 'done' → marquer “en retard”. - category.name unique par utilisateur. - Supprimer une catégorie n’efface pas les tâches : category_id passe à NULL. 🔔 Rappels & notifications - Workflow planifié : à l’heure remind_at, envoyer une notification in-app; si l’utilisateur a un email → envoyer aussi email (optionnel dans Paramètres). - Boutons “Snooze” : +10 min, +1 h, demain 09:00, lundi 09:00 (recrée un reminder). - Si due_at atteint et tâche non faite → bannière “Échéance dépassée” + alerte unique. 🖥️ Écrans / UX 1) Tableau de Bord - Sections : “En retard”, “Aujourd’hui”, “Demain”, “À venir”. - Carte tâche : checkbox, title, badges (priorité, catégorie), due_at relatif, menu “…” (Éditer, Dupliquer, Archiver, Supprimer). - Quick-add en haut (“Que devez-vous faire ?”) avec saisie rapide de date (#catégorie, !priorité). 2) Toutes les Tâches (liste + filtres) - Filtres combinables : statut, catégorie, priorité, date (aujourd’hui/semaine/mois/plage), “avec rappel”, “sans échéance”. - Recherche plein texte (title + description). - Actions groupées : marquer fait, changer catégorie/priorité, supprimer. - Tri : due_at, priorité, créé le, mis à jour le. - Drag-and-drop pour réordonner dans la vue courante. 3) Vue Catégories - Sidebar catégories (nom, couleur, compteur). - Liste des tâches de la catégorie sélectionnée. - CRUD Catégories (nom, couleur, icône, ordre). 4) Vue Priorités - 4 colonnes (Urgent, High, Normal, Low) type Kanban. - Drag-and-drop entre colonnes = change la priorité. 5) Détail Tâche (drawer/modal) - Champs : title, description (markdown basique), due_at (date+heure), priorité, catégorie, statut. - Bloc “Rappels” : liste des remind_at + bouton “Ajouter un rappel”. - Boutons rapides : “Terminer”, “Snooze”, “Dupliquer”, “Archiver”. 6) Calendrier (optionnel mais souhaité) - Vue Mois/Semaine avec tâches par date d’échéance; drag pour changer due_at. 7) Paramètres - Préférences : rappel par défaut (ex: 30 min avant), canaux (in-app/email), créneaux de travail (8h–18h). - Thème clair/sombre. 🎨 UI/Design - Minimal, moderne, accessible (WCAG AA). - Codes couleur : - Urgent = accent rouge, High = accent orange, Normal = accent bleu, Low = accent gris. - Badges arrondis, icônes simples, animations légères. - Mobile : barre d’onglets (Dashboard, Toutes, Catégories, Priorités, Paramètres). 🔗 API/Actions (exemples) - POST /tasks (title, description?, due_at?, priority, category_id?, reminders[]?) - PATCH /tasks/:id (tous champs éditables) - POST /tasks/:id/complete (toggle) - POST /tasks/:id/snooze (preset → crée reminder) - CRUD /categories - CRUD /reminders 🧪 Cas de test/acceptance - Créer une tâche “Payer l’assurance” due_at demain 17:00, priorité High, catégorie “Administration”, 2 rappels (−1 h, −10 min) → notifications reçues aux bonnes heures. - Passer une tâche à “done” supprime les notifications futures. - Filtrer “Aujourd’hui + Urgent” n’affiche que les tâches correspondantes. - Changer la priorité via la vue Kanban met à jour la carte dans Toutes les Tâches. - Supprimer la catégorie conserve les tâches (catégorie = vide). - Recherche “facture” trouve les tâches avec ce mot dans title ou description. 🌱 Données de démo - Catégories : Travail (#2563eb), Personnel (#16a34a), Immobilier (#7c3aed), Administration (#db2777) - 6–10 tâches couvrant tous les cas (en retard, aujourd’hui, avec/sans rappel, 4 niveaux de priorité). ⚙️ Performances & qualité - Pagination/infinite scroll sur longues listes. - Optimistic UI sur création/édition/suppression. - Index DB sur (user_id, due_at), (user_id, priority), (user_id, status). Livrables attendus - Base de données configurée, API opérationnelle, workflows de rappels actifs. - UI complète (écrans cités), thème dark/light, responsive. - Seed de démo + jeu de tests/acceptance. - Initial Deployment

Browse files
Files changed (2) hide show
  1. README.md +7 -5
  2. index.html +1547 -19
README.md CHANGED
@@ -1,10 +1,12 @@
1
  ---
2
- title: Task Manager
3
- emoji: 🌖
4
- colorFrom: pink
5
- colorTo: green
6
  sdk: static
7
  pinned: false
 
 
8
  ---
9
 
10
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
1
  ---
2
+ title: task-manager
3
+ emoji: 🐳
4
+ colorFrom: blue
5
+ colorTo: gray
6
  sdk: static
7
  pinned: false
8
+ tags:
9
+ - deepsite
10
  ---
11
 
12
+ Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
index.html CHANGED
@@ -1,19 +1,1547 @@
1
- <!doctype html>
2
- <html>
3
- <head>
4
- <meta charset="utf-8" />
5
- <meta name="viewport" content="width=device-width" />
6
- <title>My static Space</title>
7
- <link rel="stylesheet" href="style.css" />
8
- </head>
9
- <body>
10
- <div class="card">
11
- <h1>Welcome to your static Space!</h1>
12
- <p>You can modify this app directly by editing <i>index.html</i> in the Files and versions tab.</p>
13
- <p>
14
- Also don't forget to check the
15
- <a href="https://huggingface.co/docs/hub/spaces" target="_blank">Spaces documentation</a>.
16
- </p>
17
- </div>
18
- </body>
19
- </html>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="fr" class="h-full">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>TaskMaster - Gestionnaire de tâches</title>
7
+ <script src="https://cdn.tailwindcss.com"></script>
8
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
9
+ <script>
10
+ tailwind.config = {
11
+ darkMode: 'class',
12
+ theme: {
13
+ extend: {
14
+ colors: {
15
+ urgent: '#dc2626',
16
+ high: '#ea580c',
17
+ normal: '#2563eb',
18
+ low: '#6b7280',
19
+ work: '#2563eb',
20
+ personal: '#16a34a',
21
+ property: '#7c3aed',
22
+ admin: '#db2777',
23
+ }
24
+ }
25
+ }
26
+ }
27
+ </script>
28
+ <style>
29
+ .task-card:hover .task-actions {
30
+ opacity: 1;
31
+ }
32
+ .priority-column {
33
+ min-height: calc(100vh - 200px);
34
+ }
35
+ .drawer {
36
+ transition: transform 0.3s ease-in-out;
37
+ }
38
+ .drawer-closed {
39
+ transform: translateX(100%);
40
+ }
41
+ .drawer-open {
42
+ transform: translateX(0);
43
+ }
44
+ .fade-in {
45
+ animation: fadeIn 0.3s ease-in-out;
46
+ }
47
+ @keyframes fadeIn {
48
+ from { opacity: 0; }
49
+ to { opacity: 1; }
50
+ }
51
+ .slide-up {
52
+ animation: slideUp 0.2s ease-out;
53
+ }
54
+ @keyframes slideUp {
55
+ from { transform: translateY(20px); opacity: 0; }
56
+ to { transform: translateY(0); opacity: 1; }
57
+ }
58
+ .checkbox-container {
59
+ position: relative;
60
+ padding-left: 35px;
61
+ cursor: pointer;
62
+ }
63
+ .checkbox-container input {
64
+ position: absolute;
65
+ opacity: 0;
66
+ cursor: pointer;
67
+ height: 0;
68
+ width: 0;
69
+ }
70
+ .checkmark {
71
+ position: absolute;
72
+ top: 0;
73
+ left: 0;
74
+ height: 24px;
75
+ width: 24px;
76
+ border: 2px solid #d1d5db;
77
+ border-radius: 6px;
78
+ transition: all 0.2s;
79
+ }
80
+ .checkbox-container:hover .checkmark {
81
+ border-color: #9ca3af;
82
+ }
83
+ .checkbox-container input:checked ~ .checkmark {
84
+ background-color: #2563eb;
85
+ border-color: #2563eb;
86
+ }
87
+ .checkmark:after {
88
+ content: "";
89
+ position: absolute;
90
+ display: none;
91
+ }
92
+ .checkbox-container input:checked ~ .checkmark:after {
93
+ display: block;
94
+ }
95
+ .checkbox-container .checkmark:after {
96
+ left: 7px;
97
+ top: 3px;
98
+ width: 6px;
99
+ height: 12px;
100
+ border: solid white;
101
+ border-width: 0 2px 2px 0;
102
+ transform: rotate(45deg);
103
+ }
104
+ .dark .checkbox-container input:checked ~ .checkmark {
105
+ background-color: #3b82f6;
106
+ border-color: #3b82f6;
107
+ }
108
+ .dark .checkmark {
109
+ border-color: #4b5563;
110
+ }
111
+ .dark .checkbox-container:hover .checkmark {
112
+ border-color: #6b7280;
113
+ }
114
+ </style>
115
+ </head>
116
+ <body class="h-full bg-gray-50 dark:bg-gray-900 text-gray-900 dark:text-gray-100 transition-colors duration-200">
117
+ <!-- Auth Modal -->
118
+ <div id="auth-modal" class="fixed inset-0 z-50 flex items-center justify-center bg-black bg-opacity-50 hidden">
119
+ <div class="bg-white dark:bg-gray-800 rounded-lg shadow-xl w-full max-w-md p-6 mx-4 fade-in">
120
+ <div class="flex justify-between items-center mb-6">
121
+ <h2 class="text-2xl font-bold" id="auth-modal-title">Connexion</h2>
122
+ <button onclick="toggleAuthModal()" class="text-gray-500 hover:text-gray-700 dark:hover:text-gray-300">
123
+ <i class="fas fa-times"></i>
124
+ </button>
125
+ </div>
126
+
127
+ <div id="auth-tabs" class="flex border-b border-gray-200 dark:border-gray-700 mb-6">
128
+ <button onclick="switchAuthTab('login')" class="px-4 py-2 font-medium border-b-2 border-transparent hover:text-blue-600 dark:hover:text-blue-400" id="login-tab">Connexion</button>
129
+ <button onclick="switchAuthTab('register')" class="px-4 py-2 font-medium border-b-2 border-transparent hover:text-blue-600 dark:hover:text-blue-400" id="register-tab">Inscription</button>
130
+ </div>
131
+
132
+ <form id="login-form" class="space-y-4">
133
+ <div>
134
+ <label for="login-email" class="block text-sm font-medium mb-1">Email</label>
135
+ <input type="email" id="login-email" class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 dark:bg-gray-700">
136
+ </div>
137
+ <div>
138
+ <label for="login-password" class="block text-sm font-medium mb-1">Mot de passe</label>
139
+ <input type="password" id="login-password" class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 dark:bg-gray-700">
140
+ </div>
141
+ <button type="button" onclick="login()" class="w-full bg-blue-600 hover:bg-blue-700 text-white py-2 px-4 rounded-md transition duration-200">
142
+ Se connecter
143
+ </button>
144
+ </form>
145
+
146
+ <form id="register-form" class="space-y-4 hidden">
147
+ <div>
148
+ <label for="register-email" class="block text-sm font-medium mb-1">Email</label>
149
+ <input type="email" id="register-email" class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 dark:bg-gray-700">
150
+ </div>
151
+ <div>
152
+ <label for="register-password" class="block text-sm font-medium mb-1">Mot de passe</label>
153
+ <input type="password" id="register-password" class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 dark:bg-gray-700">
154
+ </div>
155
+ <div>
156
+ <label for="register-confirm-password" class="block text-sm font-medium mb-1">Confirmer le mot de passe</label>
157
+ <input type="password" id="register-confirm-password" class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 dark:bg-gray-700">
158
+ </div>
159
+ <button type="button" onclick="register()" class="w-full bg-blue-600 hover:bg-blue-700 text-white py-2 px-4 rounded-md transition duration-200">
160
+ S'inscrire
161
+ </button>
162
+ </form>
163
+ </div>
164
+ </div>
165
+
166
+ <!-- Notification -->
167
+ <div id="notification" class="fixed top-4 right-4 z-50 px-4 py-3 rounded-md shadow-lg hidden slide-up" style="max-width: 350px;">
168
+ <div class="flex items-center">
169
+ <div id="notification-icon" class="mr-3"></div>
170
+ <div>
171
+ <h4 id="notification-title" class="font-medium"></h4>
172
+ <p id="notification-message" class="text-sm"></p>
173
+ </div>
174
+ <button onclick="hideNotification()" class="ml-auto text-gray-500 hover:text-gray-700 dark:hover:text-gray-300">
175
+ <i class="fas fa-times"></i>
176
+ </button>
177
+ </div>
178
+ </div>
179
+
180
+ <!-- Overdue Banner -->
181
+ <div id="overdue-banner" class="bg-red-100 dark:bg-red-900 text-red-800 dark:text-red-100 px-4 py-3 mb-4 rounded-md hidden">
182
+ <div class="flex items-center">
183
+ <i class="fas fa-exclamation-circle mr-2"></i>
184
+ <span>Vous avez <span id="overdue-count">0</span> tâche(s) en retard</span>
185
+ <button onclick="hideOverdueBanner()" class="ml-auto text-red-500 hover:text-red-700 dark:hover:text-red-300">
186
+ <i class="fas fa-times"></i>
187
+ </button>
188
+ </div>
189
+ </div>
190
+
191
+ <!-- Task Detail Drawer -->
192
+ <div id="task-drawer" class="drawer drawer-closed fixed inset-y-0 right-0 z-40 w-full max-w-md bg-white dark:bg-gray-800 shadow-xl overflow-y-auto">
193
+ <div class="p-6">
194
+ <div class="flex justify-between items-center mb-6">
195
+ <h3 class="text-xl font-bold" id="task-drawer-title">Détails de la tâche</h3>
196
+ <button onclick="closeTaskDrawer()" class="text-gray-500 hover:text-gray-700 dark:hover:text-gray-300">
197
+ <i class="fas fa-times"></i>
198
+ </button>
199
+ </div>
200
+
201
+ <div class="space-y-6">
202
+ <div>
203
+ <label class="block text-sm font-medium mb-1">Titre</label>
204
+ <input type="text" id="task-title" class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 dark:bg-gray-700">
205
+ </div>
206
+
207
+ <div>
208
+ <label class="block text-sm font-medium mb-1">Description</label>
209
+ <textarea id="task-description" rows="3" class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 dark:bg-gray-700"></textarea>
210
+ </div>
211
+
212
+ <div class="grid grid-cols-2 gap-4">
213
+ <div>
214
+ <label class="block text-sm font-medium mb-1">Date d'échéance</label>
215
+ <input type="datetime-local" id="task-due" class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 dark:bg-gray-700">
216
+ </div>
217
+
218
+ <div>
219
+ <label class="block text-sm font-medium mb-1">Priorité</label>
220
+ <select id="task-priority" class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 dark:bg-gray-700">
221
+ <option value="low">Basse</option>
222
+ <option value="normal">Normale</option>
223
+ <option value="high">Haute</option>
224
+ <option value="urgent">Urgente</option>
225
+ </select>
226
+ </div>
227
+ </div>
228
+
229
+ <div>
230
+ <label class="block text-sm font-medium mb-1">Catégorie</label>
231
+ <select id="task-category" class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 dark:bg-gray-700">
232
+ <option value="">Aucune catégorie</option>
233
+ </select>
234
+ </div>
235
+
236
+ <div>
237
+ <label class="block text-sm font-medium mb-1">Statut</label>
238
+ <select id="task-status" class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 dark:bg-gray-700">
239
+ <option value="todo">À faire</option>
240
+ <option value="in_progress">En cours</option>
241
+ <option value="done">Terminée</option>
242
+ <option value="archived">Archivée</option>
243
+ </select>
244
+ </div>
245
+
246
+ <div>
247
+ <div class="flex justify-between items-center mb-2">
248
+ <label class="block text-sm font-medium">Rappels</label>
249
+ <button onclick="addReminder()" class="text-sm text-blue-600 dark:text-blue-400 hover:underline">Ajouter un rappel</button>
250
+ </div>
251
+ <div id="task-reminders" class="space-y-2">
252
+ <!-- Reminders will be added here -->
253
+ </div>
254
+ </div>
255
+ </div>
256
+
257
+ <div class="mt-8 flex justify-between">
258
+ <button onclick="deleteTask()" class="px-4 py-2 text-red-600 dark:text-red-400 hover:bg-red-50 dark:hover:bg-red-900 rounded-md transition duration-200">
259
+ Supprimer
260
+ </button>
261
+ <div class="space-x-2">
262
+ <button onclick="saveTask()" class="px-4 py-2 bg-blue-600 hover:bg-blue-700 text-white rounded-md transition duration-200">
263
+ Enregistrer
264
+ </button>
265
+ </div>
266
+ </div>
267
+ </div>
268
+ </div>
269
+
270
+ <!-- Main App Layout -->
271
+ <div class="flex flex-col h-full">
272
+ <!-- Header -->
273
+ <header class="bg-white dark:bg-gray-800 shadow-sm">
274
+ <div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
275
+ <div class="flex justify-between items-center h-16">
276
+ <div class="flex items-center">
277
+ <i class="fas fa-tasks text-blue-600 dark:text-blue-400 mr-2 text-xl"></i>
278
+ <h1 class="text-xl font-bold">TaskMaster</h1>
279
+ </div>
280
+ <div class="flex items-center space-x-4">
281
+ <button onclick="toggleDarkMode()" class="p-2 rounded-full hover:bg-gray-200 dark:hover:bg-gray-700">
282
+ <i id="dark-mode-icon" class="fas fa-moon"></i>
283
+ </button>
284
+ <div class="relative">
285
+ <button onclick="toggleUserMenu()" class="flex items-center space-x-2 focus:outline-none">
286
+ <div class="w-8 h-8 rounded-full bg-blue-500 flex items-center justify-center text-white">
287
+ <span id="user-initial">U</span>
288
+ </div>
289
+ </button>
290
+ <div id="user-menu" class="hidden absolute right-0 mt-2 w-48 bg-white dark:bg-gray-800 rounded-md shadow-lg py-1 z-50">
291
+ <a href="#" class="block px-4 py-2 text-sm text-gray-700 dark:text-gray-200 hover:bg-gray-100 dark:hover:bg-gray-700">Profil</a>
292
+ <a href="#" class="block px-4 py-2 text-sm text-gray-700 dark:text-gray-200 hover:bg-gray-100 dark:hover:bg-gray-700">Paramètres</a>
293
+ <a href="#" onclick="logout()" class="block px-4 py-2 text-sm text-gray-700 dark:text-gray-200 hover:bg-gray-100 dark:hover:bg-gray-700">Déconnexion</a>
294
+ </div>
295
+ </div>
296
+ </div>
297
+ </div>
298
+ </div>
299
+ </header>
300
+
301
+ <!-- Main Content -->
302
+ <main class="flex-1 overflow-hidden">
303
+ <div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 h-full">
304
+ <div class="flex h-full">
305
+ <!-- Sidebar -->
306
+ <aside class="hidden md:block w-64 border-r border-gray-200 dark:border-gray-700 pr-4 py-6 overflow-y-auto">
307
+ <div class="space-y-1">
308
+ <button onclick="switchView('dashboard')" class="w-full flex items-center px-3 py-2 text-sm font-medium rounded-md bg-blue-100 dark:bg-blue-900 text-blue-700 dark:text-blue-200">
309
+ <i class="fas fa-tachometer-alt mr-3"></i>
310
+ Tableau de bord
311
+ </button>
312
+ <button onclick="switchView('all-tasks')" class="w-full flex items-center px-3 py-2 text-sm font-medium rounded-md hover:bg-gray-200 dark:hover:bg-gray-700">
313
+ <i class="fas fa-list-ul mr-3"></i>
314
+ Toutes les tâches
315
+ </button>
316
+ <button onclick="switchView('categories')" class="w-full flex items-center px-3 py-2 text-sm font-medium rounded-md hover:bg-gray-200 dark:hover:bg-gray-700">
317
+ <i class="fas fa-tags mr-3"></i>
318
+ Catégories
319
+ </button>
320
+ <button onclick="switchView('priorities')" class="w-full flex items-center px-3 py-2 text-sm font-medium rounded-md hover:bg-gray-200 dark:hover:bg-gray-700">
321
+ <i class="fas fa-flag mr-3"></i>
322
+ Priorités
323
+ </button>
324
+ <button onclick="switchView('calendar')" class="w-full flex items-center px-3 py-2 text-sm font-medium rounded-md hover:bg-gray-200 dark:hover:bg-gray-700">
325
+ <i class="fas fa-calendar-alt mr-3"></i>
326
+ Calendrier
327
+ </button>
328
+ </div>
329
+
330
+ <div class="mt-8">
331
+ <h3 class="px-3 text-xs font-semibold text-gray-500 dark:text-gray-400 uppercase tracking-wider">
332
+ Catégories
333
+ </h3>
334
+ <div class="mt-1 space-y-1" id="category-list">
335
+ <!-- Categories will be added here -->
336
+ </div>
337
+ </div>
338
+ </aside>
339
+
340
+ <!-- Main Content Area -->
341
+ <div class="flex-1 flex flex-col overflow-hidden">
342
+ <!-- Mobile Navigation -->
343
+ <div class="md:hidden flex justify-around items-center border-b border-gray-200 dark:border-gray-700 py-2">
344
+ <button onclick="switchView('dashboard')" class="flex flex-col items-center px-2 py-1 text-xs rounded-md">
345
+ <i class="fas fa-tachometer-alt mb-1"></i>
346
+ <span>Dashboard</span>
347
+ </button>
348
+ <button onclick="switchView('all-tasks')" class="flex flex-col items-center px-2 py-1 text-xs rounded-md">
349
+ <i class="fas fa-list-ul mb-1"></i>
350
+ <span>Toutes</span>
351
+ </button>
352
+ <button onclick="switchView('categories')" class="flex flex-col items-center px-2 py-1 text-xs rounded-md">
353
+ <i class="fas fa-tags mb-1"></i>
354
+ <span>Catégories</span>
355
+ </button>
356
+ <button onclick="switchView('priorities')" class="flex flex-col items-center px-2 py-1 text-xs rounded-md">
357
+ <i class="fas fa-flag mb-1"></i>
358
+ <span>Priorités</span>
359
+ </button>
360
+ </div>
361
+
362
+ <!-- Content -->
363
+ <div class="flex-1 overflow-y-auto p-4">
364
+ <!-- Dashboard View -->
365
+ <div id="dashboard-view">
366
+ <div class="mb-6">
367
+ <div class="relative">
368
+ <input type="text" id="quick-add-input" placeholder="Que devez-vous faire ? #catégorie !priorité" class="w-full px-4 py-3 border border-gray-300 dark:border-gray-600 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 dark:bg-gray-700">
369
+ <button onclick="quickAddTask()" class="absolute right-2 top-2 p-2 text-blue-600 dark:text-blue-400 hover:text-blue-800 dark:hover:text-blue-300">
370
+ <i class="fas fa-plus"></i>
371
+ </button>
372
+ </div>
373
+ </div>
374
+
375
+ <div id="overdue-banner-container"></div>
376
+
377
+ <div class="space-y-6">
378
+ <div>
379
+ <h2 class="text-lg font-semibold mb-3 flex items-center">
380
+ <span class="w-2 h-2 bg-red-500 rounded-full mr-2"></span>
381
+ En retard
382
+ </h2>
383
+ <div id="overdue-tasks" class="space-y-2">
384
+ <!-- Overdue tasks will be added here -->
385
+ </div>
386
+ </div>
387
+
388
+ <div>
389
+ <h2 class="text-lg font-semibold mb-3 flex items-center">
390
+ <span class="w-2 h-2 bg-orange-500 rounded-full mr-2"></span>
391
+ Aujourd'hui
392
+ </h2>
393
+ <div id="today-tasks" class="space-y-2">
394
+ <!-- Today's tasks will be added here -->
395
+ </div>
396
+ </div>
397
+
398
+ <div>
399
+ <h2 class="text-lg font-semibold mb-3 flex items-center">
400
+ <span class="w-2 h-2 bg-yellow-500 rounded-full mr-2"></span>
401
+ Demain
402
+ </h2>
403
+ <div id="tomorrow-tasks" class="space-y-2">
404
+ <!-- Tomorrow's tasks will be added here -->
405
+ </div>
406
+ </div>
407
+
408
+ <div>
409
+ <h2 class="text-lg font-semibold mb-3 flex items-center">
410
+ <span class="w-2 h-2 bg-blue-500 rounded-full mr-2"></span>
411
+ À venir
412
+ </h2>
413
+ <div id="upcoming-tasks" class="space-y-2">
414
+ <!-- Upcoming tasks will be added here -->
415
+ </div>
416
+ </div>
417
+ </div>
418
+ </div>
419
+
420
+ <!-- All Tasks View -->
421
+ <div id="all-tasks-view" class="hidden">
422
+ <div class="mb-6 flex flex-col md:flex-row md:items-center md:justify-between space-y-3 md:space-y-0">
423
+ <div class="relative w-full md:w-64">
424
+ <input type="text" id="task-search" placeholder="Rechercher des tâches..." class="w-full pl-10 pr-4 py-2 border border-gray-300 dark:border-gray-600 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 dark:bg-gray-700">
425
+ <i class="fas fa-search absolute left-3 top-3 text-gray-400"></i>
426
+ </div>
427
+ <div class="flex space-x-2">
428
+ <select id="task-filter-status" class="px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 dark:bg-gray-700">
429
+ <option value="">Tous les statuts</option>
430
+ <option value="todo">À faire</option>
431
+ <option value="in_progress">En cours</option>
432
+ <option value="done">Terminée</option>
433
+ <option value="archived">Archivée</option>
434
+ </select>
435
+ <select id="task-filter-priority" class="px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 dark:bg-gray-700">
436
+ <option value="">Toutes priorités</option>
437
+ <option value="urgent">Urgente</option>
438
+ <option value="high">Haute</option>
439
+ <option value="normal">Normale</option>
440
+ <option value="low">Basse</option>
441
+ </select>
442
+ </div>
443
+ </div>
444
+
445
+ <div class="mb-4 flex justify-between items-center">
446
+ <h2 class="text-lg font-semibold">Toutes les tâches</h2>
447
+ <select id="task-sort" class="px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 dark:bg-gray-700">
448
+ <option value="due_asc">Échéance (proche)</option>
449
+ <option value="due_desc">Échéance (lointaine)</option>
450
+ <option value="priority">Priorité</option>
451
+ <option value="created">Date de création</option>
452
+ <option value="updated">Dernière modification</option>
453
+ </select>
454
+ </div>
455
+
456
+ <div id="all-tasks-list" class="space-y-2">
457
+ <!-- All tasks will be added here -->
458
+ </div>
459
+ </div>
460
+
461
+ <!-- Categories View -->
462
+ <div id="categories-view" class="hidden">
463
+ <div class="mb-6 flex justify-between items-center">
464
+ <h2 class="text-lg font-semibold">Catégories</h2>
465
+ <button onclick="showAddCategoryModal()" class="px-4 py-2 bg-blue-600 hover:bg-blue-700 text-white rounded-md transition duration-200">
466
+ <i class="fas fa-plus mr-1"></i> Nouvelle catégorie
467
+ </button>
468
+ </div>
469
+
470
+ <div id="categories-list" class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
471
+ <!-- Categories will be added here -->
472
+ </div>
473
+ </div>
474
+
475
+ <!-- Priorities View -->
476
+ <div id="priorities-view" class="hidden">
477
+ <div class="mb-6">
478
+ <h2 class="text-lg font-semibold">Vue par priorités</h2>
479
+ </div>
480
+
481
+ <div class="grid grid-cols-1 md:grid-cols-4 gap-4">
482
+ <div class="priority-column bg-red-50 dark:bg-red-900/20 p-4 rounded-lg">
483
+ <div class="flex items-center mb-4">
484
+ <h3 class="font-medium text-red-600 dark:text-red-400">Urgent</h3>
485
+ <span class="ml-auto px-2 py-1 text-xs bg-red-100 dark:bg-red-900/30 text-red-800 dark:text-red-200 rounded-full">3</span>
486
+ </div>
487
+ <div id="urgent-tasks" class="space-y-2">
488
+ <!-- Urgent tasks will be added here -->
489
+ </div>
490
+ </div>
491
+
492
+ <div class="priority-column bg-orange-50 dark:bg-orange-900/20 p-4 rounded-lg">
493
+ <div class="flex items-center mb-4">
494
+ <h3 class="font-medium text-orange-600 dark:text-orange-400">Haute</h3>
495
+ <span class="ml-auto px-2 py-1 text-xs bg-orange-100 dark:bg-orange-900/30 text-orange-800 dark:text-orange-200 rounded-full">2</span>
496
+ </div>
497
+ <div id="high-tasks" class="space-y-2">
498
+ <!-- High priority tasks will be added here -->
499
+ </div>
500
+ </div>
501
+
502
+ <div class="priority-column bg-blue-50 dark:bg-blue-900/20 p-4 rounded-lg">
503
+ <div class="flex items-center mb-4">
504
+ <h3 class="font-medium text-blue-600 dark:text-blue-400">Normale</h3>
505
+ <span class="ml-auto px-2 py-1 text-xs bg-blue-100 dark:bg-blue-900/30 text-blue-800 dark:text-blue-200 rounded-full">4</span>
506
+ </div>
507
+ <div id="normal-tasks" class="space-y-2">
508
+ <!-- Normal priority tasks will be added here -->
509
+ </div>
510
+ </div>
511
+
512
+ <div class="priority-column bg-gray-100 dark:bg-gray-800 p-4 rounded-lg">
513
+ <div class="flex items-center mb-4">
514
+ <h3 class="font-medium text-gray-600 dark:text-gray-300">Basse</h3>
515
+ <span class="ml-auto px-2 py-1 text-xs bg-gray-200 dark:bg-gray-700 text-gray-800 dark:text-gray-200 rounded-full">1</span>
516
+ </div>
517
+ <div id="low-tasks" class="space-y-2">
518
+ <!-- Low priority tasks will be added here -->
519
+ </div>
520
+ </div>
521
+ </div>
522
+ </div>
523
+
524
+ <!-- Calendar View -->
525
+ <div id="calendar-view" class="hidden">
526
+ <div class="mb-6 flex justify-between items-center">
527
+ <h2 class="text-lg font-semibold">Calendrier</h2>
528
+ <div class="flex items-center space-x-2">
529
+ <button class="p-2 rounded-full hover:bg-gray-200 dark:hover:bg-gray-700">
530
+ <i class="fas fa-chevron-left"></i>
531
+ </button>
532
+ <span class="font-medium">Mai 2023</span>
533
+ <button class="p-2 rounded-full hover:bg-gray-200 dark:hover:bg-gray-700">
534
+ <i class="fas fa-chevron-right"></i>
535
+ </button>
536
+ </div>
537
+ </div>
538
+
539
+ <div class="bg-white dark:bg-gray-800 rounded-lg shadow overflow-hidden">
540
+ <div class="grid grid-cols-7 gap-px bg-gray-200 dark:bg-gray-700">
541
+ <div class="bg-gray-100 dark:bg-gray-800 py-2 text-center text-xs font-medium text-gray-500 dark:text-gray-400">Lun</div>
542
+ <div class="bg-gray-100 dark:bg-gray-800 py-2 text-center text-xs font-medium text-gray-500 dark:text-gray-400">Mar</div>
543
+ <div class="bg-gray-100 dark:bg-gray-800 py-2 text-center text-xs font-medium text-gray-500 dark:text-gray-400">Mer</div>
544
+ <div class="bg-gray-100 dark:bg-gray-800 py-2 text-center text-xs font-medium text-gray-500 dark:text-gray-400">Jeu</div>
545
+ <div class="bg-gray-100 dark:bg-gray-800 py-2 text-center text-xs font-medium text-gray-500 dark:text-gray-400">Ven</div>
546
+ <div class="bg-gray-100 dark:bg-gray-800 py-2 text-center text-xs font-medium text-gray-500 dark:text-gray-400">Sam</div>
547
+ <div class="bg-gray-100 dark:bg-gray-800 py-2 text-center text-xs font-medium text-gray-500 dark:text-gray-400">Dim</div>
548
+ </div>
549
+ <div class="grid grid-cols-7 gap-px bg-gray-200 dark:bg-gray-700">
550
+ <!-- Calendar days will be added here -->
551
+ </div>
552
+ </div>
553
+ </div>
554
+ </div>
555
+ </div>
556
+ </div>
557
+ </div>
558
+ </main>
559
+ </div>
560
+
561
+ <script>
562
+ // State management
563
+ let currentUser = null;
564
+ let tasks = [];
565
+ let categories = [];
566
+ let reminders = [];
567
+ let currentView = 'dashboard';
568
+ let currentTaskId = null;
569
+
570
+ // Demo data
571
+ const demoCategories = [
572
+ { id: 1, name: 'Travail', color: 'work', icon: 'briefcase' },
573
+ { id: 2, name: 'Personnel', color: 'personal', icon: 'home' },
574
+ { id: 3, name: 'Immobilier', color: 'property', icon: 'building' },
575
+ { id: 4, name: 'Administration', color: 'admin', icon: 'file-alt' }
576
+ ];
577
+
578
+ const demoTasks = [
579
+ {
580
+ id: 1,
581
+ title: 'Payer l\'assurance',
582
+ description: 'Payer l\'assurance voiture avant la date limite',
583
+ status: 'todo',
584
+ priority: 'high',
585
+ category_id: 4,
586
+ due_at: new Date(Date.now() + 86400000).toISOString(), // Tomorrow
587
+ created_at: new Date().toISOString(),
588
+ updated_at: new Date().toISOString()
589
+ },
590
+ {
591
+ id: 2,
592
+ title: 'Préparer présentation client',
593
+ description: 'Préparer les slides pour la réunion de mercredi',
594
+ status: 'in_progress',
595
+ priority: 'urgent',
596
+ category_id: 1,
597
+ due_at: new Date(Date.now() + 172800000).toISOString(), // 2 days
598
+ created_at: new Date().toISOString(),
599
+ updated_at: new Date().toISOString()
600
+ },
601
+ {
602
+ id: 3,
603
+ title: 'Faire les courses',
604
+ description: 'Acheter fruits, légumes et produits ménagers',
605
+ status: 'todo',
606
+ priority: 'normal',
607
+ category_id: 2,
608
+ due_at: new Date(Date.now() + 259200000).toISOString(), // 3 days
609
+ created_at: new Date().toISOString(),
610
+ updated_at: new Date().toISOString()
611
+ },
612
+ {
613
+ id: 4,
614
+ title: 'Réparer robinet cuisine',
615
+ description: 'Le robinet fuit depuis plusieurs jours',
616
+ status: 'todo',
617
+ priority: 'low',
618
+ category_id: 2,
619
+ due_at: null,
620
+ created_at: new Date().toISOString(),
621
+ updated_at: new Date().toISOString()
622
+ },
623
+ {
624
+ id: 5,
625
+ title: 'Rappel taxe foncière',
626
+ description: 'Payer la taxe foncière avant le 15 octobre',
627
+ status: 'todo',
628
+ priority: 'high',
629
+ category_id: 3,
630
+ due_at: new Date('2023-10-15').toISOString(),
631
+ created_at: new Date().toISOString(),
632
+ updated_at: new Date().toISOString()
633
+ },
634
+ {
635
+ id: 6,
636
+ title: 'Réunion équipe',
637
+ description: 'Préparer le point sur les projets en cours',
638
+ status: 'done',
639
+ priority: 'normal',
640
+ category_id: 1,
641
+ due_at: new Date(Date.now() - 86400000).toISOString(), // Yesterday
642
+ completed_at: new Date().toISOString(),
643
+ created_at: new Date().toISOString(),
644
+ updated_at: new Date().toISOString()
645
+ },
646
+ {
647
+ id: 7,
648
+ title: 'Rendez-vous médecin',
649
+ description: 'Contrôle annuel à 14h30',
650
+ status: 'todo',
651
+ priority: 'urgent',
652
+ category_id: 2,
653
+ due_at: new Date(Date.now() + 3600000).toISOString(), // In 1 hour
654
+ created_at: new Date().toISOString(),
655
+ updated_at: new Date().toISOString()
656
+ }
657
+ ];
658
+
659
+ const demoReminders = [
660
+ { id: 1, task_id: 1, remind_at: new Date(Date.now() + 82800000).toISOString(), channel: 'in_app' }, // 1h before
661
+ { id: 2, task_id: 1, remind_at: new Date(Date.now() + 86040000).toISOString(), channel: 'in_app' }, // 10min before
662
+ { id: 3, task_id: 7, remind_at: new Date(Date.now() + 1800000).toISOString(), channel: 'in_app' } // 30min before
663
+ ];
664
+
665
+ // Initialize app
666
+ document.addEventListener('DOMContentLoaded', function() {
667
+ // Check if user is logged in
668
+ const storedUser = localStorage.getItem('currentUser');
669
+ if (storedUser) {
670
+ currentUser = JSON.parse(storedUser);
671
+ initApp();
672
+ } else {
673
+ toggleAuthModal();
674
+ }
675
+
676
+ // Load demo data if no user data exists
677
+ if (!localStorage.getItem('tasks')) {
678
+ categories = demoCategories;
679
+ tasks = demoTasks;
680
+ reminders = demoReminders;
681
+ saveData();
682
+ } else {
683
+ loadData();
684
+ }
685
+
686
+ // Check for overdue tasks
687
+ checkOverdueTasks();
688
+ });
689
+
690
+ // Data management
691
+ function saveData() {
692
+ if (currentUser) {
693
+ localStorage.setItem(`tasks_${currentUser.id}`, JSON.stringify(tasks));
694
+ localStorage.setItem(`categories_${currentUser.id}`, JSON.stringify(categories));
695
+ localStorage.setItem(`reminders_${currentUser.id}`, JSON.stringify(reminders));
696
+ }
697
+ }
698
+
699
+ function loadData() {
700
+ if (currentUser) {
701
+ const storedTasks = localStorage.getItem(`tasks_${currentUser.id}`);
702
+ const storedCategories = localStorage.getItem(`categories_${currentUser.id}`);
703
+ const storedReminders = localStorage.getItem(`reminders_${currentUser.id}`);
704
+
705
+ if (storedTasks) tasks = JSON.parse(storedTasks);
706
+ if (storedCategories) categories = JSON.parse(storedCategories);
707
+ if (storedReminders) reminders = JSON.parse(storedReminders);
708
+ }
709
+ }
710
+
711
+ // Auth functions
712
+ function toggleAuthModal() {
713
+ const modal = document.getElementById('auth-modal');
714
+ modal.classList.toggle('hidden');
715
+ }
716
+
717
+ function switchAuthTab(tab) {
718
+ const loginForm = document.getElementById('login-form');
719
+ const registerForm = document.getElementById('register-form');
720
+ const loginTab = document.getElementById('login-tab');
721
+ const registerTab = document.getElementById('register-tab');
722
+ const modalTitle = document.getElementById('auth-modal-title');
723
+
724
+ if (tab === 'login') {
725
+ loginForm.classList.remove('hidden');
726
+ registerForm.classList.add('hidden');
727
+ loginTab.classList.add('border-blue-500', 'text-blue-600', 'dark:text-blue-400');
728
+ loginTab.classList.remove('border-transparent');
729
+ registerTab.classList.add('border-transparent');
730
+ registerTab.classList.remove('border-blue-500', 'text-blue-600', 'dark:text-blue-400');
731
+ modalTitle.textContent = 'Connexion';
732
+ } else {
733
+ loginForm.classList.add('hidden');
734
+ registerForm.classList.remove('hidden');
735
+ registerTab.classList.add('border-blue-500', 'text-blue-600', 'dark:text-blue-400');
736
+ registerTab.classList.remove('border-transparent');
737
+ loginTab.classList.add('border-transparent');
738
+ loginTab.classList.remove('border-blue-500', 'text-blue-600', 'dark:text-blue-400');
739
+ modalTitle.textContent = 'Inscription';
740
+ }
741
+ }
742
+
743
+ function login() {
744
+ const email = document.getElementById('login-email').value;
745
+ const password = document.getElementById('login-password').value;
746
+
747
+ // Simple validation
748
+ if (!email || !password) {
749
+ showNotification('error', 'Erreur', 'Veuillez remplir tous les champs');
750
+ return;
751
+ }
752
+
753
+ // In a real app, this would be an API call
754
+ currentUser = {
755
+ id: 1,
756
+ email: email,
757
+ name: email.split('@')[0]
758
+ };
759
+ localStorage.setItem('currentUser', JSON.stringify(currentUser));
760
+
761
+ toggleAuthModal();
762
+ initApp();
763
+ showNotification('success', 'Connecté', 'Vous êtes maintenant connecté');
764
+ }
765
+
766
+ function register() {
767
+ const email = document.getElementById('register-email').value;
768
+ const password = document.getElementById('register-password').value;
769
+ const confirmPassword = document.getElementById('register-confirm-password').value;
770
+
771
+ // Simple validation
772
+ if (!email || !password || !confirmPassword) {
773
+ showNotification('error', 'Erreur', 'Veuillez remplir tous les champs');
774
+ return;
775
+ }
776
+
777
+ if (password !== confirmPassword) {
778
+ showNotification('error', 'Erreur', 'Les mots de passe ne correspondent pas');
779
+ return;
780
+ }
781
+
782
+ // In a real app, this would be an API call
783
+ currentUser = {
784
+ id: 1,
785
+ email: email,
786
+ name: email.split('@')[0]
787
+ };
788
+ localStorage.setItem('currentUser', JSON.stringify(currentUser));
789
+
790
+ toggleAuthModal();
791
+ initApp();
792
+ showNotification('success', 'Inscription réussie', 'Votre compte a été créé');
793
+ }
794
+
795
+ function logout() {
796
+ currentUser = null;
797
+ localStorage.removeItem('currentUser');
798
+ toggleAuthModal();
799
+ document.getElementById('user-menu').classList.add('hidden');
800
+ }
801
+
802
+ function initApp() {
803
+ loadData();
804
+ renderUserInfo();
805
+ renderCategories();
806
+ renderTasks();
807
+ setupEventListeners();
808
+ }
809
+
810
+ function renderUserInfo() {
811
+ if (currentUser) {
812
+ document.getElementById('user-initial').textContent = currentUser.name.charAt(0).toUpperCase();
813
+ }
814
+ }
815
+
816
+ // UI functions
817
+ function toggleUserMenu() {
818
+ const menu = document.getElementById('user-menu');
819
+ menu.classList.toggle('hidden');
820
+ }
821
+
822
+ function toggleDarkMode() {
823
+ document.documentElement.classList.toggle('dark');
824
+ const icon = document.getElementById('dark-mode-icon');
825
+ if (document.documentElement.classList.contains('dark')) {
826
+ icon.classList.remove('fa-moon');
827
+ icon.classList.add('fa-sun');
828
+ } else {
829
+ icon.classList.remove('fa-sun');
830
+ icon.classList.add('fa-moon');
831
+ }
832
+ }
833
+
834
+ function showNotification(type, title, message) {
835
+ const notification = document.getElementById('notification');
836
+ const icon = document.getElementById('notification-icon');
837
+ const notificationTitle = document.getElementById('notification-title');
838
+ const notificationMessage = document.getElementById('notification-message');
839
+
840
+ // Set notification content
841
+ notificationTitle.textContent = title;
842
+ notificationMessage.textContent = message;
843
+
844
+ // Set notification style based on type
845
+ notification.className = `fixed top-4 right-4 z-50 px-4 py-3 rounded-md shadow-lg slide-up ${type === 'error' ? 'bg-red-100 dark:bg-red-900 text-red-800 dark:text-red-100' : 'bg-green-100 dark:bg-green-900 text-green-800 dark:text-green-100'}`;
846
+
847
+ // Set icon
848
+ icon.className = `fas ${type === 'error' ? 'fa-exclamation-circle text-red-500 dark:text-red-300' : 'fa-check-circle text-green-500 dark:text-green-300'}`;
849
+
850
+ // Show notification
851
+ notification.classList.remove('hidden');
852
+
853
+ // Hide after 5 seconds
854
+ setTimeout(hideNotification, 5000);
855
+ }
856
+
857
+ function hideNotification() {
858
+ const notification = document.getElementById('notification');
859
+ notification.classList.add('hidden');
860
+ }
861
+
862
+ function showOverdueBanner(count) {
863
+ const banner = document.getElementById('overdue-banner');
864
+ const countElement = document.getElementById('overdue-count');
865
+
866
+ countElement.textContent = count;
867
+ banner.classList.remove('hidden');
868
+
869
+ // Also add to dashboard view
870
+ const dashboardBanner = document.getElementById('overdue-banner-container');
871
+ dashboardBanner.innerHTML = `
872
+ <div class="bg-red-100 dark:bg-red-900 text-red-800 dark:text-red-100 px-4 py-3 mb-4 rounded-md">
873
+ <div class="flex items-center">
874
+ <i class="fas fa-exclamation-circle mr-2"></i>
875
+ <span>Vous avez ${count} tâche(s) en retard</span>
876
+ <button onclick="hideOverdueBanner()" class="ml-auto text-red-500 hover:text-red-700 dark:hover:text-red-300">
877
+ <i class="fas fa-times"></i>
878
+ </button>
879
+ </div>
880
+ </div>
881
+ `;
882
+ }
883
+
884
+ function hideOverdueBanner() {
885
+ const banner = document.getElementById('overdue-banner');
886
+ banner.classList.add('hidden');
887
+ document.getElementById('overdue-banner-container').innerHTML = '';
888
+ }
889
+
890
+ function checkOverdueTasks() {
891
+ const now = new Date();
892
+ const overdueTasks = tasks.filter(task =>
893
+ task.due_at &&
894
+ new Date(task.due_at) < now &&
895
+ task.status !== 'done' &&
896
+ task.status !== 'archived'
897
+ );
898
+
899
+ if (overdueTasks.length > 0) {
900
+ showOverdueBanner(overdueTasks.length);
901
+ }
902
+ }
903
+
904
+ // View switching
905
+ function switchView(view) {
906
+ currentView = view;
907
+
908
+ // Hide all views
909
+ document.getElementById('dashboard-view').classList.add('hidden');
910
+ document.getElementById('all-tasks-view').classList.add('hidden');
911
+ document.getElementById('categories-view').classList.add('hidden');
912
+ document.getElementById('priorities-view').classList.add('hidden');
913
+ document.getElementById('calendar-view').classList.add('hidden');
914
+
915
+ // Show selected view
916
+ document.getElementById(`${view}-view`).classList.remove('hidden');
917
+
918
+ // Update sidebar active state
919
+ updateSidebarActiveState();
920
+
921
+ // Render specific content if needed
922
+ if (view === 'priorities') {
923
+ renderPriorityColumns();
924
+ } else if (view === 'calendar') {
925
+ renderCalendar();
926
+ }
927
+ }
928
+
929
+ function updateSidebarActiveState() {
930
+ // Reset all buttons
931
+ const sidebarButtons = document.querySelectorAll('aside button');
932
+ sidebarButtons.forEach(button => {
933
+ button.classList.remove('bg-blue-100', 'dark:bg-blue-900', 'text-blue-700', 'dark:text-blue-200');
934
+ button.classList.add('hover:bg-gray-200', 'dark:hover:bg-gray-700');
935
+ });
936
+
937
+ // Set active button
938
+ const activeButton = document.querySelector(`aside button[onclick="switchView('${currentView}')"]`);
939
+ if (activeButton) {
940
+ activeButton.classList.add('bg-blue-100', 'dark:bg-blue-900', 'text-blue-700', 'dark:text-blue-200');
941
+ activeButton.classList.remove('hover:bg-gray-200', 'dark:hover:bg-gray-700');
942
+ }
943
+ }
944
+
945
+ // Task functions
946
+ function renderTasks() {
947
+ renderDashboardTasks();
948
+ renderAllTasks();
949
+ renderPriorityColumns();
950
+ }
951
+
952
+ function renderDashboardTasks() {
953
+ const now = new Date();
954
+ const today = new Date(now.getFullYear(), now.getMonth(), now.getDate());
955
+ const tomorrow = new Date(today);
956
+ tomorrow.setDate(tomorrow.getDate() + 1);
957
+
958
+ // Overdue tasks (due before today and not done)
959
+ const overdueTasks = tasks.filter(task =>
960
+ task.due_at &&
961
+ new Date(task.due_at) < today &&
962
+ task.status !== 'done' &&
963
+ task.status !== 'archived'
964
+ );
965
+
966
+ // Today's tasks (due today)
967
+ const todayTasks = tasks.filter(task =>
968
+ task.due_at &&
969
+ new Date(task.due_at) >= today &&
970
+ new Date(task.due_at) < tomorrow &&
971
+ task.status !== 'done' &&
972
+ task.status !== 'archived'
973
+ );
974
+
975
+ // Tomorrow's tasks (due tomorrow)
976
+ const tomorrowTasks = tasks.filter(task =>
977
+ task.due_at &&
978
+ new Date(task.due_at) >= tomorrow &&
979
+ new Date(task.due_at) < new Date(tomorrow.getTime() + 86400000) &&
980
+ task.status !== 'done' &&
981
+ task.status !== 'archived'
982
+ );
983
+
984
+ // Upcoming tasks (due after tomorrow)
985
+ const upcomingTasks = tasks.filter(task =>
986
+ task.due_at &&
987
+ new Date(task.due_at) >= new Date(tomorrow.getTime() + 86400000) &&
988
+ task.status !== 'done' &&
989
+ task.status !== 'archived'
990
+ );
991
+
992
+ // Render each section
993
+ renderTaskList('overdue-tasks', overdueTasks);
994
+ renderTaskList('today-tasks', todayTasks);
995
+ renderTaskList('tomorrow-tasks', tomorrowTasks);
996
+ renderTaskList('upcoming-tasks', upcomingTasks);
997
+ }
998
+
999
+ function renderAllTasks() {
1000
+ const filteredTasks = filterAndSortTasks();
1001
+ renderTaskList('all-tasks-list', filteredTasks);
1002
+ }
1003
+
1004
+ function filterAndSortTasks() {
1005
+ const statusFilter = document.getElementById('task-filter-status').value;
1006
+ const priorityFilter = document.getElementById('task-filter-priority').value;
1007
+ const searchQuery = document.getElementById('task-search').value.toLowerCase();
1008
+ const sortOption = document.getElementById('task-sort').value;
1009
+
1010
+ let filteredTasks = [...tasks];
1011
+
1012
+ // Apply status filter
1013
+ if (statusFilter) {
1014
+ filteredTasks = filteredTasks.filter(task => task.status === statusFilter);
1015
+ }
1016
+
1017
+ // Apply priority filter
1018
+ if (priorityFilter) {
1019
+ filteredTasks = filteredTasks.filter(task => task.priority === priorityFilter);
1020
+ }
1021
+
1022
+ // Apply search filter
1023
+ if (searchQuery) {
1024
+ filteredTasks = filteredTasks.filter(task =>
1025
+ task.title.toLowerCase().includes(searchQuery) ||
1026
+ (task.description && task.description.toLowerCase().includes(searchQuery))
1027
+ );
1028
+ }
1029
+
1030
+ // Apply sorting
1031
+ switch (sortOption) {
1032
+ case 'due_asc':
1033
+ filteredTasks.sort((a, b) => {
1034
+ if (!a.due_at && !b.due_at) return 0;
1035
+ if (!a.due_at) return 1;
1036
+ if (!b.due_at) return -1;
1037
+ return new Date(a.due_at) - new Date(b.due_at);
1038
+ });
1039
+ break;
1040
+ case 'due_desc':
1041
+ filteredTasks.sort((a, b) => {
1042
+ if (!a.due_at && !b.due_at) return 0;
1043
+ if (!a.due_at) return 1;
1044
+ if (!b.due_at) return -1;
1045
+ return new Date(b.due_at) - new Date(a.due_at);
1046
+ });
1047
+ break;
1048
+ case 'priority':
1049
+ const priorityOrder = { 'urgent': 0, 'high': 1, 'normal': 2, 'low': 3 };
1050
+ filteredTasks.sort((a, b) => priorityOrder[a.priority] - priorityOrder[b.priority]);
1051
+ break;
1052
+ case 'created':
1053
+ filteredTasks.sort((a, b) => new Date(b.created_at) - new Date(a.created_at));
1054
+ break;
1055
+ case 'updated':
1056
+ filteredTasks.sort((a, b) => new Date(b.updated_at) - new Date(a.updated_at));
1057
+ break;
1058
+ }
1059
+
1060
+ return filteredTasks;
1061
+ }
1062
+
1063
+ function renderPriorityColumns() {
1064
+ const urgentTasks = tasks.filter(task =>
1065
+ task.priority === 'urgent' &&
1066
+ task.status !== 'done' &&
1067
+ task.status !== 'archived'
1068
+ );
1069
+
1070
+ const highTasks = tasks.filter(task =>
1071
+ task.priority === 'high' &&
1072
+ task.status !== 'done' &&
1073
+ task.status !== 'archived'
1074
+ );
1075
+
1076
+ const normalTasks = tasks.filter(task =>
1077
+ task.priority === 'normal' &&
1078
+ task.status !== 'done' &&
1079
+ task.status !== 'archived'
1080
+ );
1081
+
1082
+ const lowTasks = tasks.filter(task =>
1083
+ task.priority === 'low' &&
1084
+ task.status !== 'done' &&
1085
+ task.status !== 'archived'
1086
+ );
1087
+
1088
+ // Update counts
1089
+ document.querySelector('#priorities-view .priority-column:nth-child(1) span').textContent = urgentTasks.length;
1090
+ document.querySelector('#priorities-view .priority-column:nth-child(2) span').textContent = highTasks.length;
1091
+ document.querySelector('#priorities-view .priority-column:nth-child(3) span').textContent = normalTasks.length;
1092
+ document.querySelector('#priorities-view .priority-column:nth-child(4) span').textContent = lowTasks.length;
1093
+
1094
+ // Render tasks
1095
+ renderTaskList('urgent-tasks', urgentTasks);
1096
+ renderTaskList('high-tasks', highTasks);
1097
+ renderTaskList('normal-tasks', normalTasks);
1098
+ renderTaskList('low-tasks', lowTasks);
1099
+ }
1100
+
1101
+ function renderTaskList(containerId, taskList) {
1102
+ const container = document.getElementById(containerId);
1103
+ container.innerHTML = '';
1104
+
1105
+ if (taskList.length === 0) {
1106
+ container.innerHTML = '<p class="text-gray-500 dark:text-gray-400 text-center py-4">Aucune tâche à afficher</p>';
1107
+ return;
1108
+ }
1109
+
1110
+ taskList.forEach(task => {
1111
+ const taskElement = createTaskElement(task);
1112
+ container.appendChild(taskElement);
1113
+ });
1114
+ }
1115
+
1116
+ function createTaskElement(task) {
1117
+ const category = categories.find(c => c.id === task.category_id);
1118
+ const taskReminders = reminders.filter(r => r.task_id === task.id);
1119
+
1120
+ const taskElement = document.createElement('div');
1121
+ taskElement.className = 'task-card bg-white dark:bg-gray-800 rounded-lg shadow-sm p-4 border border-gray-200 dark:border-gray-700 hover:shadow-md transition duration-200';
1122
+ taskElement.dataset.taskId = task.id;
1123
+
1124
+ // Priority indicator
1125
+ let priorityClass = '';
1126
+ let priorityText = '';
1127
+ switch (task.priority) {
1128
+ case 'urgent':
1129
+ priorityClass = 'bg-urgent text-white';
1130
+ priorityText = 'Urgent';
1131
+ break;
1132
+ case 'high':
1133
+ priorityClass = 'bg-high text-white';
1134
+ priorityText = 'Haute';
1135
+ break;
1136
+ case 'normal':
1137
+ priorityClass = 'bg-normal text-white';
1138
+ priorityText = 'Normale';
1139
+ break;
1140
+ case 'low':
1141
+ priorityClass = 'bg-low text-white';
1142
+ priorityText = 'Basse';
1143
+ break;
1144
+ }
1145
+
1146
+ // Status checkbox
1147
+ const isChecked = task.status === 'done';
1148
+
1149
+ // Due date display
1150
+ let dueDateDisplay = '';
1151
+ if (task.due_at) {
1152
+ const dueDate = new Date(task.due_at);
1153
+ const now = new Date();
1154
+ const today = new Date(now.getFullYear(), now.getMonth(), now.getDate());
1155
+ const tomorrow = new Date(today);
1156
+ tomorrow.setDate(tomorrow.getDate() + 1);
1157
+
1158
+ if (dueDate < today && task.status !== 'done') {
1159
+ dueDateDisplay = `<span class="text-red-500 dark:text-red-400 text-sm"><i class="fas fa-exclamation-circle mr-1"></i> En retard</span>`;
1160
+ } else if (dueDate >= today && dueDate < tomorrow) {
1161
+ dueDateDisplay = `<span class="text-orange-500 dark:text-orange-400 text-sm"><i class="far fa-clock mr-1"></i> Aujourd'hui à ${dueDate.toLocaleTimeString([], {hour: '2-digit', minute:'2-digit'})}</span>`;
1162
+ } else if (dueDate >= tomorrow && dueDate < new Date(tomorrow.getTime() + 86400000)) {
1163
+ dueDateDisplay = `<span class="text-blue-500 dark:text-blue-400 text-sm"><i class="far fa-clock mr-1"></i> Demain à ${dueDate.toLocaleTimeString([], {hour: '2-digit', minute:'2-digit'})}</span>`;
1164
+ } else {
1165
+ dueDateDisplay = `<span class="text-gray-500 dark:text-gray-400 text-sm"><i class="far fa-calendar-alt mr-1"></i> ${dueDate.toLocaleDateString()}</span>`;
1166
+ }
1167
+ }
1168
+
1169
+ taskElement.innerHTML = `
1170
+ <div class="flex items-start">
1171
+ <label class="checkbox-container">
1172
+ <input type="checkbox" ${isChecked ? 'checked' : ''} onchange="toggleTaskStatus(${task.id})">
1173
+ <span class="checkmark"></span>
1174
+ </label>
1175
+ <div class="flex-1 ml-3">
1176
+ <div class="flex items-center justify-between">
1177
+ <h3 class="font-medium ${isChecked ? 'line-through text-gray-400 dark:text-gray-500' : ''}">${task.title}</h3>
1178
+ <div class="task-actions opacity-0 md:opacity-100 transition-opacity duration-200">
1179
+ <button onclick="event.stopPropagation();openTaskDrawer(${task.id})" class="p-1 text-gray-500 hover:text-gray-700 dark:hover:text-gray-300">
1180
+ <i class="fas fa-pencil-alt"></i>
1181
+ </button>
1182
+ <button onclick="event.stopPropagation();duplicateTask(${task.id})" class="p-1 text-gray-500 hover:text-gray-700 dark:hover:text-gray-300">
1183
+ <i class="fas fa-copy"></i>
1184
+ </button>
1185
+ <button onclick="event.stopPropagation();deleteTask(${task.id})" class="p-1 text-gray-500 hover:text-gray-700 dark:hover:text-gray-300">
1186
+ <i class="fas fa-trash-alt"></i>
1187
+ </button>
1188
+ </div>
1189
+ </div>
1190
+ ${task.description ? `<p class="text-sm text-gray-600 dark:text-gray-400 mt-1">${task.description}</p>` : ''}
1191
+ <div class="mt-2 flex flex-wrap items-center gap-2">
1192
+ ${task.priority !== 'normal' ? `<span class="px-2 py-1 text-xs rounded-full ${priorityClass}">${priorityText}</span>` : ''}
1193
+ ${category ? `<span class="px-2 py-1 text-xs rounded-full bg-${category.color}-100 dark:bg-${category.color}-900/30 text-${category.color}-800 dark:text-${category.color}-200"><i class="fas fa-${category.icon} mr-1"></i> ${category.name}</span>` : ''}
1194
+ ${task.due_at ? dueDateDisplay : ''}
1195
+ ${taskReminders.length > 0 ? `<span class="text-yellow-500 dark:text-yellow-400 text-sm"><i class="fas fa-bell mr-1"></i> ${taskReminders.length} rappel(s)</span>` : ''}
1196
+ </div>
1197
+ </div>
1198
+ </div>
1199
+ `;
1200
+
1201
+ // Add click handler to open task drawer
1202
+ taskElement.addEventListener('click', () => openTaskDrawer(task.id));
1203
+
1204
+ return taskElement;
1205
+ }
1206
+
1207
+ function toggleTaskStatus(taskId) {
1208
+ const taskIndex = tasks.findIndex(t => t.id === taskId);
1209
+ if (taskIndex === -1) return;
1210
+
1211
+ const task = tasks[taskIndex];
1212
+ const isChecked = document.querySelector(`.task-card[data-task-id="${taskId}"] input[type="checkbox"]`).checked;
1213
+
1214
+ if (isChecked) {
1215
+ task.status = 'done';
1216
+ task.completed_at = new Date().toISOString();
1217
+
1218
+ // Remove future reminders
1219
+ reminders = reminders.filter(r => r.task_id !== taskId || new Date(r.remind_at) < new Date());
1220
+ } else {
1221
+ task.status = 'todo';
1222
+ task.completed_at = null;
1223
+ }
1224
+
1225
+ task.updated_at = new Date().toISOString();
1226
+ tasks[taskIndex] = task;
1227
+ saveData();
1228
+ renderTasks();
1229
+
1230
+ showNotification('success', 'Tâche mise à jour', `La tâche a été marquée comme ${isChecked ? 'terminée' : 'à faire'}`);
1231
+ }
1232
+
1233
+ function quickAddTask() {
1234
+ const input = document.getElementById('quick-add-input');
1235
+ const text = input.value.trim();
1236
+ if (!text) return;
1237
+
1238
+ // Parse quick add syntax (#category !priority)
1239
+ let categoryName = null;
1240
+ let priority = 'normal';
1241
+
1242
+ const categoryMatch = text.match(/#(\w+)/);
1243
+ if (categoryMatch) {
1244
+ categoryName = categoryMatch[1];
1245
+ }
1246
+
1247
+ const priorityMatch = text.match(/!(\w+)/);
1248
+ if (priorityMatch) {
1249
+ const priorityText = priorityMatch[1].toLowerCase();
1250
+ if (['urgent', 'high', 'normal', 'low'].includes(priorityText)) {
1251
+ priority = priorityText;
1252
+ }
1253
+ }
1254
+
1255
+ // Clean title (remove tags)
1256
+ const title = text.replace(/#\w+/g, '').replace(/!\w+/g, '').trim();
1257
+
1258
+ // Find category
1259
+ let categoryId = null;
1260
+ if (categoryName) {
1261
+ const category = categories.find(c => c.name.toLowerCase() === categoryName.toLowerCase());
1262
+ if (category) {
1263
+ categoryId = category.id;
1264
+ }
1265
+ }
1266
+
1267
+ // Create task
1268
+ const newTask = {
1269
+ id: Date.now(),
1270
+ title: title,
1271
+ description: '',
1272
+ status: 'todo',
1273
+ priority: priority,
1274
+ category_id: categoryId,
1275
+ due_at: null,
1276
+ created_at: new Date().toISOString(),
1277
+ updated_at: new Date().toISOString()
1278
+ };
1279
+
1280
+ tasks.push(newTask);
1281
+ saveData();
1282
+ renderTasks();
1283
+
1284
+ input.value = '';
1285
+ showNotification('success', 'Tâche ajoutée', 'La nouvelle tâche a été créée');
1286
+ }
1287
+
1288
+ function openTaskDrawer(taskId) {
1289
+ currentTaskId = taskId;
1290
+ const task = tasks.find(t => t.id === taskId);
1291
+ if (!task) return;
1292
+
1293
+ // Fill form
1294
+ document.getElementById('task-title').value = task.title;
1295
+ document.getElementById('task-description').value = task.description || '';
1296
+ document.getElementById('task-due').value = task.due_at ? formatDateTimeForInput(task.due_at) : '';
1297
+ document.getElementById('task-priority').value = task.priority;
1298
+ document.getElementById('task-status').value = task.status;
1299
+
1300
+ // Fill category select
1301
+ const categorySelect = document.getElementById('task-category');
1302
+ categorySelect.innerHTML = '<option value="">Aucune catégorie</option>';
1303
+ categories.forEach(category => {
1304
+ const option = document.createElement('option');
1305
+ option.value = category.id;
1306
+ option.textContent = category.name;
1307
+ option.selected = task.category_id === category.id;
1308
+ categorySelect.appendChild(option);
1309
+ });
1310
+
1311
+ // Fill reminders
1312
+ const remindersContainer = document.getElementById('task-reminders');
1313
+ remindersContainer.innerHTML = '';
1314
+
1315
+ const taskReminders = reminders.filter(r => r.task_id === taskId);
1316
+ taskReminders.forEach(reminder => {
1317
+ const reminderElement = document.createElement('div');
1318
+ reminderElement.className = 'flex items-center justify-between p-2 bg-gray-100 dark:bg-gray-700 rounded';
1319
+ reminderElement.innerHTML = `
1320
+ <span>${formatDateTime(new Date(reminder.remind_at))}</span>
1321
+ <button onclick="removeReminder(${reminder.id})" class="text-red-500 hover:text-red-700 dark:hover:text-red-300">
1322
+ <i class="fas fa-trash-alt"></i>
1323
+ </button>
1324
+ `;
1325
+ remindersContainer.appendChild(reminderElement);
1326
+ });
1327
+
1328
+ // Open drawer
1329
+ document.getElementById('task-drawer').classList.remove('drawer-closed');
1330
+ document.getElementById('task-drawer').classList.add('drawer-open');
1331
+ }
1332
+
1333
+ function closeTaskDrawer() {
1334
+ document.getElementById('task-drawer').classList.remove('drawer-open');
1335
+ document.getElementById('task-drawer').classList.add('drawer-closed');
1336
+ currentTaskId = null;
1337
+ }
1338
+
1339
+ function saveTask() {
1340
+ if (!currentTaskId) return;
1341
+
1342
+ const taskIndex = tasks.findIndex(t => t.id === currentTaskId);
1343
+ if (taskIndex === -1) return;
1344
+
1345
+ const task = tasks[taskIndex];
1346
+
1347
+ // Update task
1348
+ task.title = document.getElementById('task-title').value;
1349
+ task.description = document.getElementById('task-description').value;
1350
+ task.due_at = document.getElementById('task-due').value ? new Date(document.getElementById('task-due').value).toISOString() : null;
1351
+ task.priority = document.getElementById('task-priority').value;
1352
+ task.status = document.getElementById('task-status').value;
1353
+ task.category_id = document.getElementById('task-category').value ? parseInt(document.getElementById('task-category').value) : null;
1354
+ task.updated_at = new Date().toISOString();
1355
+
1356
+ tasks[taskIndex] = task;
1357
+ saveData();
1358
+ renderTasks();
1359
+ closeTaskDrawer();
1360
+
1361
+ showNotification('success', 'Tâche sauvegardée', 'Les modifications ont été enregistrées');
1362
+ }
1363
+
1364
+ function deleteTask(taskId) {
1365
+ if (!taskId) taskId = currentTaskId;
1366
+ if (!taskId) return;
1367
+
1368
+ if (confirm('Êtes-vous sûr de vouloir supprimer cette tâche ?')) {
1369
+ tasks = tasks.filter(t => t.id !== taskId);
1370
+ reminders = reminders.filter(r => r.task_id !== taskId);
1371
+ saveData();
1372
+ renderTasks();
1373
+
1374
+ if (taskId === currentTaskId) {
1375
+ closeTaskDrawer();
1376
+ }
1377
+
1378
+ showNotification('success', 'Tâche supprimée', 'La tâche a été supprimée');
1379
+ }
1380
+ }
1381
+
1382
+ function duplicateTask(taskId) {
1383
+ const task = tasks.find(t => t.id === taskId);
1384
+ if (!task) return;
1385
+
1386
+ const newTask = {
1387
+ ...task,
1388
+ id: Date.now(),
1389
+ status: 'todo',
1390
+ completed_at: null,
1391
+ created_at: new Date().toISOString(),
1392
+ updated_at: new Date().toISOString()
1393
+ };
1394
+
1395
+ tasks.push(newTask);
1396
+ saveData();
1397
+ renderTasks();
1398
+
1399
+ showNotification('success', 'Tâche dupliquée', 'La tâche a été dupliquée');
1400
+ }
1401
+
1402
+ function addReminder() {
1403
+ if (!currentTaskId) return;
1404
+
1405
+ // Check if we already have 5 reminders for this task
1406
+ const taskReminders = reminders.filter(r => r.task_id === currentTaskId);
1407
+ if (taskReminders.length >= 5) {
1408
+ showNotification('error', 'Erreur', 'Maximum 5 rappels par tâche');
1409
+ return;
1410
+ }
1411
+
1412
+ // In a real app, we would show a modal to select date/time
1413
+ // For demo, we'll just add a reminder for 1 hour before due date
1414
+ const task = tasks.find(t => t.id === currentTaskId);
1415
+ if (!task.due_at) {
1416
+ showNotification('error', 'Erreur', 'Ajoutez d\'abord une date d\'échéance');
1417
+ return;
1418
+ }
1419
+
1420
+ const dueDate = new Date(task.due_at);
1421
+ const reminderDate = new Date(dueDate.getTime() - 3600000); // 1 hour before
1422
+
1423
+ const newReminder = {
1424
+ id: Date.now(),
1425
+ task_id: currentTaskId,
1426
+ remind_at: reminderDate.toISOString(),
1427
+ channel: 'in_app',
1428
+ created_at: new Date().toISOString()
1429
+ };
1430
+
1431
+ reminders.push(newReminder);
1432
+ saveData();
1433
+
1434
+ // Refresh task drawer
1435
+ openTaskDrawer(currentTaskId);
1436
+
1437
+ showNotification('success', 'Rappel ajouté', 'Un nouveau rappel a été programmé');
1438
+ }
1439
+
1440
+ function removeReminder(reminderId) {
1441
+ reminders = reminders.filter(r => r.id !== reminderId);
1442
+ saveData();
1443
+
1444
+ // Refresh task drawer
1445
+ openTaskDrawer(currentTaskId);
1446
+ }
1447
+
1448
+ // Category functions
1449
+ function renderCategories() {
1450
+ // Sidebar categories
1451
+ const sidebarCategoryList = document.getElementById('category-list');
1452
+ sidebarCategoryList.innerHTML = '';
1453
+
1454
+ // Categories view
1455
+ const categoriesViewList = document.getElementById('categories-list');
1456
+ categoriesViewList.innerHTML = '';
1457
+
1458
+ categories.forEach(category => {
1459
+ // Count tasks in this category
1460
+ const taskCount = tasks.filter(t => t.category_id === category.id && t.status !== 'done' && t.status !== 'archived').length;
1461
+
1462
+ // Sidebar item
1463
+ const sidebarItem = document.createElement('button');
1464
+ sidebarItem.className = 'w-full flex items-center px-3 py-2 text-sm font-medium rounded-md hover:bg-gray-200 dark:hover:bg-gray-700';
1465
+ sidebarItem.innerHTML = `
1466
+ <span class="w-3 h-3 rounded-full bg-${category.color}-500 mr-3"></span>
1467
+ <span class="truncate">${category.name}</span>
1468
+ <span class="ml-auto px-2 py-1 text-xs bg-gray-200 dark:bg-gray-700 rounded-full">${taskCount}</span>
1469
+ `;
1470
+ sidebarItem.onclick = () => filterByCategory(category.id);
1471
+ sidebarCategoryList.appendChild(sidebarItem);
1472
+
1473
+ // Categories view card
1474
+ const categoryCard = document.createElement('div');
1475
+ categoryCard.className = 'bg-white dark:bg-gray-800 rounded-lg shadow-sm p-4 border border-gray-200 dark:border-gray-700';
1476
+ categoryCard.innerHTML = `
1477
+ <div class="flex items-center mb-3">
1478
+ <span class="w-4 h-4 rounded-full bg-${category.color}-500 mr-3"></span>
1479
+ <h3 class="font-medium flex-1">${category.name}</h3>
1480
+ <div class="flex space-x-2">
1481
+ <button onclick="editCategory(${category.id})" class="p-1 text-gray-500 hover:text-gray-700 dark:hover:text-gray-300">
1482
+ <i class="fas fa-pencil-alt"></i>
1483
+ </button>
1484
+ <button onclick="deleteCategory(${category.id})" class="p-1 text-gray-500 hover:text-gray-700 dark:hover:text-gray-300">
1485
+ <i class="fas fa-trash-alt"></i>
1486
+ </button>
1487
+ </div>
1488
+ </div>
1489
+ <div class="flex justify-between items-center">
1490
+ <span class="text-sm text-gray-500 dark:text-gray-400"><i class="fas fa-${category.icon} mr-1"></i> ${taskCount} tâche(s)</span>
1491
+ <button onclick="filterByCategory(${category.id})" class="text-sm text-blue-600 dark:text-blue-400 hover:underline">
1492
+ Voir les tâches
1493
+ </button>
1494
+ </div>
1495
+ `;
1496
+ categoriesViewList.appendChild(categoryCard);
1497
+ });
1498
+ }
1499
+
1500
+ function filterByCategory(categoryId) {
1501
+ // Switch to all tasks view
1502
+ switchView('all-tasks');
1503
+
1504
+ // Set category filter
1505
+ document.getElementById('task-filter-status').value = '';
1506
+ document.getElementById('task-filter-priority').value = '';
1507
+ document.getElementById('task-search').value = '';
1508
+ document.getElementById('task-sort').value = 'due_asc';
1509
+
1510
+ // Trigger filter
1511
+ renderAllTasks();
1512
+ }
1513
+
1514
+ function showAddCategoryModal() {
1515
+ // In a real app, we would show a modal to add a new category
1516
+ // For demo, we'll just add a random category
1517
+ const colors = ['work', 'personal', 'property', 'admin'];
1518
+ const icons = ['briefcase', 'home', 'building', 'file-alt', 'shopping-cart', 'utensils', 'car', 'heart'];
1519
+
1520
+ const newCategory = {
1521
+ id: Date.now(),
1522
+ name: `Nouvelle catégorie ${categories.length + 1}`,
1523
+ color: colors[Math.floor(Math.random() * colors.length)],
1524
+ icon: icons[Math.floor(Math.random() * icons.length)],
1525
+ created_at: new Date().toISOString()
1526
+ };
1527
+
1528
+ categories.push(newCategory);
1529
+ saveData();
1530
+ renderCategories();
1531
+
1532
+ showNotification('success', 'Catégorie ajoutée', 'Une nouvelle catégorie a été créée');
1533
+ }
1534
+
1535
+ function editCategory(categoryId) {
1536
+ // In a real app, we would show a modal to edit the category
1537
+ const category = categories.find(c => c.id === categoryId);
1538
+ if (!category) return;
1539
+
1540
+ // For demo, we'll just add a " (modifié)" to the name
1541
+ category.name = `${category.name} (modifié)`;
1542
+ saveData();
1543
+ renderCategories();
1544
+
1545
+ showNotification('success', 'Catégorie modifiée', 'La cat
1546
+ <p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - 🧬 <a href="https://enzostvs-deepsite.hf.space?remix=stephaneibens/task-manager" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body>
1547
+ </html>