task-manager / index.html
stephaneibens's picture
le bouton + dans le cadre de saisie ne fonctionne pas - Follow Up Deployment
87b1086 verified
Raw History Blame Contribute Delete
78.4 kB
<!DOCTYPE html>
<html lang="fr" class="h-full">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>TaskMaster - Gestionnaire de tâches</title>
<script src="https://cdn.tailwindcss.com"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<script>
tailwind.config = {
darkMode: 'class',
theme: {
extend: {
colors: {
urgent: '#dc2626',
high: '#ea580c',
normal: '#2563eb',
low: '#6b7280',
work: '#2563eb',
personal: '#16a34a',
property: '#7c3aed',
admin: '#db2777',
}
}
}
}
</script>
<style>
.task-card:hover .task-actions {
opacity: 1;
}
.priority-column {
min-height: calc(100vh - 200px);
}
.drawer {
transition: transform 0.3s ease-in-out;
}
.drawer-closed {
transform: translateX(100%);
}
.drawer-open {
transform: translateX(0);
}
.fade-in {
animation: fadeIn 0.3s ease-in-out;
}
@keyframes fadeIn {
from { opacity: 0; }
to { opacity: 1; }
}
.slide-up {
animation: slideUp 0.2s ease-out;
}
@keyframes slideUp {
from { transform: translateY(20px); opacity: 0; }
to { transform: translateY(0); opacity: 1; }
}
.checkbox-container {
position: relative;
padding-left: 35px;
cursor: pointer;
}
.checkbox-container input {
position: absolute;
opacity: 0;
cursor: pointer;
height: 0;
width: 0;
}
.checkmark {
position: absolute;
top: 0;
left: 0;
height: 24px;
width: 24px;
border: 2px solid #d1d5db;
border-radius: 6px;
transition: all 0.2s;
}
.checkbox-container:hover .checkmark {
border-color: #9ca3af;
}
.checkbox-container input:checked ~ .checkmark {
background-color: #2563eb;
border-color: #2563eb;
}
.checkmark:after {
content: "";
position: absolute;
display: none;
}
.checkbox-container input:checked ~ .checkmark:after {
display: block;
}
.checkbox-container .checkmark:after {
left: 7px;
top: 3px;
width: 6px;
height: 12px;
border: solid white;
border-width: 0 2px 2px 0;
transform: rotate(45deg);
}
.dark .checkbox-container input:checked ~ .checkmark {
background-color: #3b82f6;
border-color: #3b82f6;
}
.dark .checkmark {
border-color: #4b5563;
}
.dark .checkbox-container:hover .checkmark {
border-color: #6b7280;
}
</style>
</head>
<body class="h-full bg-gray-50 dark:bg-gray-900 text-gray-900 dark:text-gray-100 transition-colors duration-200">
<!-- Auth Modal -->
<div id="auth-modal" class="fixed inset-0 z-50 flex items-center justify-center bg-black bg-opacity-50 hidden">
<div class="bg-white dark:bg-gray-800 rounded-lg shadow-xl w-full max-w-md p-6 mx-4 fade-in">
<div class="flex justify-between items-center mb-6">
<h2 class="text-2xl font-bold" id="auth-modal-title">Connexion</h2>
<button onclick="toggleAuthModal()" class="text-gray-500 hover:text-gray-700 dark:hover:text-gray-300">
<i class="fas fa-times"></i>
</button>
</div>
<div id="auth-tabs" class="flex border-b border-gray-200 dark:border-gray-700 mb-6">
<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>
<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>
</div>
<form id="login-form" class="space-y-4">
<div>
<label for="login-email" class="block text-sm font-medium mb-1">Email</label>
<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">
</div>
<div>
<label for="login-password" class="block text-sm font-medium mb-1">Mot de passe</label>
<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">
</div>
<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">
Se connecter
</button>
</form>
<form id="register-form" class="space-y-4 hidden">
<div>
<label for="register-email" class="block text-sm font-medium mb-1">Email</label>
<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">
</div>
<div>
<label for="register-password" class="block text-sm font-medium mb-1">Mot de passe</label>
<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">
</div>
<div>
<label for="register-confirm-password" class="block text-sm font-medium mb-1">Confirmer le mot de passe</label>
<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">
</div>
<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">
S'inscrire
</button>
</form>
</div>
</div>
<!-- Notification -->
<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;">
<div class="flex items-center">
<div id="notification-icon" class="mr-3"></div>
<div>
<h4 id="notification-title" class="font-medium"></h4>
<p id="notification-message" class="text-sm"></p>
</div>
<button onclick="hideNotification()" class="ml-auto text-gray-500 hover:text-gray-700 dark:hover:text-gray-300">
<i class="fas fa-times"></i>
</button>
</div>
</div>
<!-- Overdue Banner -->
<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">
<div class="flex items-center">
<i class="fas fa-exclamation-circle mr-2"></i>
<span>Vous avez <span id="overdue-count">0</span> tâche(s) en retard</span>
<button onclick="hideOverdueBanner()" class="ml-auto text-red-500 hover:text-red-700 dark:hover:text-red-300">
<i class="fas fa-times"></i>
</button>
</div>
</div>
<!-- Task Detail Drawer -->
<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">
<div class="p-6">
<div class="flex justify-between items-center mb-6">
<h3 class="text-xl font-bold" id="task-drawer-title">Détails de la tâche</h3>
<button onclick="closeTaskDrawer()" class="text-gray-500 hover:text-gray-700 dark:hover:text-gray-300">
<i class="fas fa-times"></i>
</button>
</div>
<div class="space-y-6">
<div>
<label class="block text-sm font-medium mb-1">Titre</label>
<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">
</div>
<div>
<label class="block text-sm font-medium mb-1">Description</label>
<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>
</div>
<div class="grid grid-cols-2 gap-4">
<div>
<label class="block text-sm font-medium mb-1">Date d'échéance</label>
<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">
</div>
<div>
<label class="block text-sm font-medium mb-1">Priorité</label>
<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">
<option value="low">Basse</option>
<option value="normal">Normale</option>
<option value="high">Haute</option>
<option value="urgent">Urgente</option>
</select>
</div>
</div>
<div>
<label class="block text-sm font-medium mb-1">Catégorie</label>
<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">
<option value="">Aucune catégorie</option>
</select>
</div>
<div>
<label class="block text-sm font-medium mb-1">Statut</label>
<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">
<option value="todo">À faire</option>
<option value="in_progress">En cours</option>
<option value="done">Terminée</option>
<option value="archived">Archivée</option>
</select>
</div>
<div>
<div class="flex justify-between items-center mb-2">
<label class="block text-sm font-medium">Rappels</label>
<button onclick="addReminder()" class="text-sm text-blue-600 dark:text-blue-400 hover:underline">Ajouter un rappel</button>
</div>
<div id="task-reminders" class="space-y-2">
<!-- Reminders will be added here -->
</div>
</div>
</div>
<div class="mt-8 flex justify-between">
<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">
Supprimer
</button>
<div class="space-x-2">
<button onclick="saveTask()" class="px-4 py-2 bg-blue-600 hover:bg-blue-700 text-white rounded-md transition duration-200">
Enregistrer
</button>
</div>
</div>
</div>
</div>
<!-- Main App Layout -->
<div class="flex flex-col h-full">
<!-- Header -->
<header class="bg-white dark:bg-gray-800 shadow-sm">
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div class="flex justify-between items-center h-16">
<div class="flex items-center">
<i class="fas fa-tasks text-blue-600 dark:text-blue-400 mr-2 text-xl"></i>
<h1 class="text-xl font-bold">TaskMaster</h1>
</div>
<div class="flex items-center space-x-4">
<button onclick="toggleDarkMode()" class="p-2 rounded-full hover:bg-gray-200 dark:hover:bg-gray-700">
<i id="dark-mode-icon" class="fas fa-moon"></i>
</button>
<div class="relative">
<button onclick="toggleUserMenu()" class="flex items-center space-x-2 focus:outline-none">
<div class="w-8 h-8 rounded-full bg-blue-500 flex items-center justify-center text-white">
<span id="user-initial">U</span>
</div>
</button>
<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">
<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>
<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>
<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>
</div>
</div>
</div>
</div>
</div>
</header>
<!-- Main Content -->
<main class="flex-1 overflow-hidden">
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 h-full">
<div class="flex h-full">
<!-- Sidebar -->
<aside class="hidden md:block w-64 border-r border-gray-200 dark:border-gray-700 pr-4 py-6 overflow-y-auto">
<div class="space-y-1">
<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">
<i class="fas fa-tachometer-alt mr-3"></i>
Tableau de bord
</button>
<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">
<i class="fas fa-list-ul mr-3"></i>
Toutes les tâches
</button>
<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">
<i class="fas fa-tags mr-3"></i>
Catégories
</button>
<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">
<i class="fas fa-flag mr-3"></i>
Priorités
</button>
<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">
<i class="fas fa-calendar-alt mr-3"></i>
Calendrier
</button>
</div>
<div class="mt-8">
<h3 class="px-3 text-xs font-semibold text-gray-500 dark:text-gray-400 uppercase tracking-wider">
Catégories
</h3>
<div class="mt-1 space-y-1" id="category-list">
<!-- Categories will be added here -->
</div>
</div>
</aside>
<!-- Main Content Area -->
<div class="flex-1 flex flex-col overflow-hidden">
<!-- Mobile Navigation -->
<div class="md:hidden flex justify-around items-center border-b border-gray-200 dark:border-gray-700 py-2">
<button onclick="switchView('dashboard')" class="flex flex-col items-center px-2 py-1 text-xs rounded-md">
<i class="fas fa-tachometer-alt mb-1"></i>
<span>Dashboard</span>
</button>
<button onclick="switchView('all-tasks')" class="flex flex-col items-center px-2 py-1 text-xs rounded-md">
<i class="fas fa-list-ul mb-1"></i>
<span>Toutes</span>
</button>
<button onclick="switchView('categories')" class="flex flex-col items-center px-2 py-1 text-xs rounded-md">
<i class="fas fa-tags mb-1"></i>
<span>Catégories</span>
</button>
<button onclick="switchView('priorities')" class="flex flex-col items-center px-2 py-1 text-xs rounded-md">
<i class="fas fa-flag mb-1"></i>
<span>Priorités</span>
</button>
</div>
<!-- Content -->
<div class="flex-1 overflow-y-auto p-4">
<!-- Dashboard View -->
<div id="dashboard-view">
<div class="mb-6">
<div class="relative">
<input type="text" id="quick-add-input" placeholder="Que devez-vous faire ? #catégorie !priorité" class="w-full px-4 py-3 pr-10 border border-gray-300 dark:border-gray-600 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 dark:bg-gray-700">
<button onclick="quickAddTask()" class="absolute right-3 top-1/2 transform -translate-y-1/2 p-2 text-blue-600 dark:text-blue-400 hover:text-blue-800 dark:hover:text-blue-300">
<i class="fas fa-plus"></i>
</button>
</div>
</div>
<div id="overdue-banner-container"></div>
<div class="space-y-6">
<div>
<h2 class="text-lg font-semibold mb-3 flex items-center">
<span class="w-2 h-2 bg-red-500 rounded-full mr-2"></span>
En retard
</h2>
<div id="overdue-tasks" class="space-y-2">
<!-- Overdue tasks will be added here -->
</div>
</div>
<div>
<h2 class="text-lg font-semibold mb-3 flex items-center">
<span class="w-2 h-2 bg-orange-500 rounded-full mr-2"></span>
Aujourd'hui
</h2>
<div id="today-tasks" class="space-y-2">
<!-- Today's tasks will be added here -->
</div>
</div>
<div>
<h2 class="text-lg font-semibold mb-3 flex items-center">
<span class="w-2 h-2 bg-yellow-500 rounded-full mr-2"></span>
Demain
</h2>
<div id="tomorrow-tasks" class="space-y-2">
<!-- Tomorrow's tasks will be added here -->
</div>
</div>
<div>
<h2 class="text-lg font-semibold mb-3 flex items-center">
<span class="w-2 h-2 bg-blue-500 rounded-full mr-2"></span>
À venir
</h2>
<div id="upcoming-tasks" class="space-y-2">
<!-- Upcoming tasks will be added here -->
</div>
</div>
</div>
</div>
<!-- All Tasks View -->
<div id="all-tasks-view" class="hidden">
<div class="mb-6 flex flex-col md:flex-row md:items-center md:justify-between space-y-3 md:space-y-0">
<div class="relative w-full md:w-64">
<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">
<i class="fas fa-search absolute left-3 top-3 text-gray-400"></i>
</div>
<div class="flex space-x-2">
<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">
<option value="">Tous les statuts</option>
<option value="todo">À faire</option>
<option value="in_progress">En cours</option>
<option value="done">Terminée</option>
<option value="archived">Archivée</option>
</select>
<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">
<option value="">Toutes priorités</option>
<option value="urgent">Urgente</option>
<option value="high">Haute</option>
<option value="normal">Normale</option>
<option value="low">Basse</option>
</select>
</div>
</div>
<div class="mb-4 flex justify-between items-center">
<h2 class="text-lg font-semibold">Toutes les tâches</h2>
<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">
<option value="due_asc">Échéance (proche)</option>
<option value="due_desc">Échéance (lointaine)</option>
<option value="priority">Priorité</option>
<option value="created">Date de création</option>
<option value="updated">Dernière modification</option>
</select>
</div>
<div id="all-tasks-list" class="space-y-2">
<!-- All tasks will be added here -->
</div>
</div>
<!-- Categories View -->
<div id="categories-view" class="hidden">
<div class="mb-6 flex justify-between items-center">
<h2 class="text-lg font-semibold">Catégories</h2>
<button onclick="showAddCategoryModal()" class="px-4 py-2 bg-blue-600 hover:bg-blue-700 text-white rounded-md transition duration-200">
<i class="fas fa-plus mr-1"></i> Nouvelle catégorie
</button>
</div>
<div id="categories-list" class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
<!-- Categories will be added here -->
</div>
</div>
<!-- Priorities View -->
<div id="priorities-view" class="hidden">
<div class="mb-6">
<h2 class="text-lg font-semibold">Vue par priorités</h2>
</div>
<div class="grid grid-cols-1 md:grid-cols-4 gap-4">
<div class="priority-column bg-red-50 dark:bg-red-900/20 p-4 rounded-lg">
<div class="flex items-center mb-4">
<h3 class="font-medium text-red-600 dark:text-red-400">Urgent</h3>
<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>
</div>
<div id="urgent-tasks" class="space-y-2">
<!-- Urgent tasks will be added here -->
</div>
</div>
<div class="priority-column bg-orange-50 dark:bg-orange-900/20 p-4 rounded-lg">
<div class="flex items-center mb-4">
<h3 class="font-medium text-orange-600 dark:text-orange-400">Haute</h3>
<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>
</div>
<div id="high-tasks" class="space-y-2">
<!-- High priority tasks will be added here -->
</div>
</div>
<div class="priority-column bg-blue-50 dark:bg-blue-900/20 p-4 rounded-lg">
<div class="flex items-center mb-4">
<h3 class="font-medium text-blue-600 dark:text-blue-400">Normale</h3>
<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>
</div>
<div id="normal-tasks" class="space-y-2">
<!-- Normal priority tasks will be added here -->
</div>
</div>
<div class="priority-column bg-gray-100 dark:bg-gray-800 p-4 rounded-lg">
<div class="flex items-center mb-4">
<h3 class="font-medium text-gray-600 dark:text-gray-300">Basse</h3>
<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>
</div>
<div id="low-tasks" class="space-y-2">
<!-- Low priority tasks will be added here -->
</div>
</div>
</div>
</div>
<!-- Calendar View -->
<div id="calendar-view" class="hidden">
<div class="mb-6 flex justify-between items-center">
<h2 class="text-lg font-semibold">Calendrier</h2>
<div class="flex items-center space-x-2">
<button class="p-2 rounded-full hover:bg-gray-200 dark:hover:bg-gray-700">
<i class="fas fa-chevron-left"></i>
</button>
<span class="font-medium">Mai 2023</span>
<button class="p-2 rounded-full hover:bg-gray-200 dark:hover:bg-gray-700">
<i class="fas fa-chevron-right"></i>
</button>
</div>
</div>
<div class="bg-white dark:bg-gray-800 rounded-lg shadow overflow-hidden">
<div class="grid grid-cols-7 gap-px bg-gray-200 dark:bg-gray-700">
<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>
<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>
<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>
<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>
<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>
<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>
<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>
</div>
<div class="grid grid-cols-7 gap-px bg-gray-200 dark:bg-gray-700">
<!-- Calendar days will be added here -->
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</main>
</div>
<script>
// State management
let currentUser = null;
let tasks = [];
let categories = [];
let reminders = [];
let currentView = 'dashboard';
let currentTaskId = null;
// Demo data
const demoCategories = [
{ id: 1, name: 'Travail', color: 'work', icon: 'briefcase' },
{ id: 2, name: 'Personnel', color: 'personal', icon: 'home' },
{ id: 3, name: 'Immobilier', color: 'property', icon: 'building' },
{ id: 4, name: 'Administration', color: 'admin', icon: 'file-alt' }
];
const demoTasks = [
{
id: 1,
title: 'Payer l\'assurance',
description: 'Payer l\'assurance voiture avant la date limite',
status: 'todo',
priority: 'high',
category_id: 4,
due_at: new Date(Date.now() + 86400000).toISOString(), // Tomorrow
created_at: new Date().toISOString(),
updated_at: new Date().toISOString()
},
{
id: 2,
title: 'Préparer présentation client',
description: 'Préparer les slides pour la réunion de mercredi',
status: 'in_progress',
priority: 'urgent',
category_id: 1,
due_at: new Date(Date.now() + 172800000).toISOString(), // 2 days
created_at: new Date().toISOString(),
updated_at: new Date().toISOString()
},
{
id: 3,
title: 'Faire les courses',
description: 'Acheter fruits, légumes et produits ménagers',
status: 'todo',
priority: 'normal',
category_id: 2,
due_at: new Date(Date.now() + 259200000).toISOString(), // 3 days
created_at: new Date().toISOString(),
updated_at: new Date().toISOString()
},
{
id: 4,
title: 'Réparer robinet cuisine',
description: 'Le robinet fuit depuis plusieurs jours',
status: 'todo',
priority: 'low',
category_id: 2,
due_at: null,
created_at: new Date().toISOString(),
updated_at: new Date().toISOString()
},
{
id: 5,
title: 'Rappel taxe foncière',
description: 'Payer la taxe foncière avant le 15 octobre',
status: 'todo',
priority: 'high',
category_id: 3,
due_at: new Date('2023-10-15').toISOString(),
created_at: new Date().toISOString(),
updated_at: new Date().toISOString()
},
{
id: 6,
title: 'Réunion équipe',
description: 'Préparer le point sur les projets en cours',
status: 'done',
priority: 'normal',
category_id: 1,
due_at: new Date(Date.now() - 86400000).toISOString(), // Yesterday
completed_at: new Date().toISOString(),
created_at: new Date().toISOString(),
updated_at: new Date().toISOString()
},
{
id: 7,
title: 'Rendez-vous médecin',
description: 'Contrôle annuel à 14h30',
status: 'todo',
priority: 'urgent',
category_id: 2,
due_at: new Date(Date.now() + 3600000).toISOString(), // In 1 hour
created_at: new Date().toISOString(),
updated_at: new Date().toISOString()
}
];
const demoReminders = [
{ id: 1, task_id: 1, remind_at: new Date(Date.now() + 82800000).toISOString(), channel: 'in_app' }, // 1h before
{ id: 2, task_id: 1, remind_at: new Date(Date.now() + 86040000).toISOString(), channel: 'in_app' }, // 10min before
{ id: 3, task_id: 7, remind_at: new Date(Date.now() + 1800000).toISOString(), channel: 'in_app' } // 30min before
];
// Initialize app
document.addEventListener('DOMContentLoaded', function() {
// Check if user is logged in
const storedUser = localStorage.getItem('currentUser');
if (storedUser) {
currentUser = JSON.parse(storedUser);
initApp();
} else {
toggleAuthModal();
}
// Load demo data if no user data exists
if (!localStorage.getItem('tasks')) {
categories = demoCategories;
tasks = demoTasks;
reminders = demoReminders;
saveData();
} else {
loadData();
}
// Check for overdue tasks
checkOverdueTasks();
});
// Data management
function saveData() {
if (currentUser) {
localStorage.setItem(`tasks_${currentUser.id}`, JSON.stringify(tasks));
localStorage.setItem(`categories_${currentUser.id}`, JSON.stringify(categories));
localStorage.setItem(`reminders_${currentUser.id}`, JSON.stringify(reminders));
}
}
function loadData() {
if (currentUser) {
const storedTasks = localStorage.getItem(`tasks_${currentUser.id}`);
const storedCategories = localStorage.getItem(`categories_${currentUser.id}`);
const storedReminders = localStorage.getItem(`reminders_${currentUser.id}`);
if (storedTasks) tasks = JSON.parse(storedTasks);
if (storedCategories) categories = JSON.parse(storedCategories);
if (storedReminders) reminders = JSON.parse(storedReminders);
}
}
// Auth functions
function toggleAuthModal() {
const modal = document.getElementById('auth-modal');
modal.classList.toggle('hidden');
}
function switchAuthTab(tab) {
const loginForm = document.getElementById('login-form');
const registerForm = document.getElementById('register-form');
const loginTab = document.getElementById('login-tab');
const registerTab = document.getElementById('register-tab');
const modalTitle = document.getElementById('auth-modal-title');
if (tab === 'login') {
loginForm.classList.remove('hidden');
registerForm.classList.add('hidden');
loginTab.classList.add('border-blue-500', 'text-blue-600', 'dark:text-blue-400');
loginTab.classList.remove('border-transparent');
registerTab.classList.add('border-transparent');
registerTab.classList.remove('border-blue-500', 'text-blue-600', 'dark:text-blue-400');
modalTitle.textContent = 'Connexion';
} else {
loginForm.classList.add('hidden');
registerForm.classList.remove('hidden');
registerTab.classList.add('border-blue-500', 'text-blue-600', 'dark:text-blue-400');
registerTab.classList.remove('border-transparent');
loginTab.classList.add('border-transparent');
loginTab.classList.remove('border-blue-500', 'text-blue-600', 'dark:text-blue-400');
modalTitle.textContent = 'Inscription';
}
}
function login() {
const email = document.getElementById('login-email').value;
const password = document.getElementById('login-password').value;
// Simple validation
if (!email || !password) {
showNotification('error', 'Erreur', 'Veuillez remplir tous les champs');
return;
}
// In a real app, this would be an API call
currentUser = {
id: 1,
email: email,
name: email.split('@')[0]
};
localStorage.setItem('currentUser', JSON.stringify(currentUser));
toggleAuthModal();
initApp();
showNotification('success', 'Connecté', 'Vous êtes maintenant connecté');
}
function register() {
const email = document.getElementById('register-email').value;
const password = document.getElementById('register-password').value;
const confirmPassword = document.getElementById('register-confirm-password').value;
// Simple validation
if (!email || !password || !confirmPassword) {
showNotification('error', 'Erreur', 'Veuillez remplir tous les champs');
return;
}
if (password !== confirmPassword) {
showNotification('error', 'Erreur', 'Les mots de passe ne correspondent pas');
return;
}
// In a real app, this would be an API call
currentUser = {
id: 1,
email: email,
name: email.split('@')[0]
};
localStorage.setItem('currentUser', JSON.stringify(currentUser));
toggleAuthModal();
initApp();
showNotification('success', 'Inscription réussie', 'Votre compte a été créé');
}
function logout() {
currentUser = null;
localStorage.removeItem('currentUser');
toggleAuthModal();
document.getElementById('user-menu').classList.add('hidden');
}
function initApp() {
loadData();
renderUserInfo();
renderCategories();
renderTasks();
setupEventListeners();
}
function renderUserInfo() {
if (currentUser) {
document.getElementById('user-initial').textContent = currentUser.name.charAt(0).toUpperCase();
}
}
// UI functions
function toggleUserMenu() {
const menu = document.getElementById('user-menu');
menu.classList.toggle('hidden');
}
function toggleDarkMode() {
document.documentElement.classList.toggle('dark');
const icon = document.getElementById('dark-mode-icon');
if (document.documentElement.classList.contains('dark')) {
icon.classList.remove('fa-moon');
icon.classList.add('fa-sun');
} else {
icon.classList.remove('fa-sun');
icon.classList.add('fa-moon');
}
}
function showNotification(type, title, message) {
const notification = document.getElementById('notification');
const icon = document.getElementById('notification-icon');
const notificationTitle = document.getElementById('notification-title');
const notificationMessage = document.getElementById('notification-message');
// Set notification content
notificationTitle.textContent = title;
notificationMessage.textContent = message;
// Set notification style based on type
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'}`;
// Set icon
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'}`;
// Show notification
notification.classList.remove('hidden');
// Hide after 5 seconds
setTimeout(hideNotification, 5000);
}
function hideNotification() {
const notification = document.getElementById('notification');
notification.classList.add('hidden');
}
function showOverdueBanner(count) {
const banner = document.getElementById('overdue-banner');
const countElement = document.getElementById('overdue-count');
countElement.textContent = count;
banner.classList.remove('hidden');
// Also add to dashboard view
const dashboardBanner = document.getElementById('overdue-banner-container');
dashboardBanner.innerHTML = `
<div class="bg-red-100 dark:bg-red-900 text-red-800 dark:text-red-100 px-4 py-3 mb-4 rounded-md">
<div class="flex items-center">
<i class="fas fa-exclamation-circle mr-2"></i>
<span>Vous avez ${count} tâche(s) en retard</span>
<button onclick="hideOverdueBanner()" class="ml-auto text-red-500 hover:text-red-700 dark:hover:text-red-300">
<i class="fas fa-times"></i>
</button>
</div>
</div>
`;
}
function hideOverdueBanner() {
const banner = document.getElementById('overdue-banner');
banner.classList.add('hidden');
document.getElementById('overdue-banner-container').innerHTML = '';
}
function checkOverdueTasks() {
const now = new Date();
const overdueTasks = tasks.filter(task =>
task.due_at &&
new Date(task.due_at) < now &&
task.status !== 'done' &&
task.status !== 'archived'
);
if (overdueTasks.length > 0) {
showOverdueBanner(overdueTasks.length);
}
}
// View switching
function switchView(view) {
currentView = view;
// Hide all views
document.getElementById('dashboard-view').classList.add('hidden');
document.getElementById('all-tasks-view').classList.add('hidden');
document.getElementById('categories-view').classList.add('hidden');
document.getElementById('priorities-view').classList.add('hidden');
document.getElementById('calendar-view').classList.add('hidden');
// Show selected view
document.getElementById(`${view}-view`).classList.remove('hidden');
// Update sidebar active state
updateSidebarActiveState();
// Render specific content if needed
if (view === 'priorities') {
renderPriorityColumns();
} else if (view === 'calendar') {
renderCalendar();
}
}
function updateSidebarActiveState() {
// Reset all buttons
const sidebarButtons = document.querySelectorAll('aside button');
sidebarButtons.forEach(button => {
button.classList.remove('bg-blue-100', 'dark:bg-blue-900', 'text-blue-700', 'dark:text-blue-200');
button.classList.add('hover:bg-gray-200', 'dark:hover:bg-gray-700');
});
// Set active button
const activeButton = document.querySelector(`aside button[onclick="switchView('${currentView}')"]`);
if (activeButton) {
activeButton.classList.add('bg-blue-100', 'dark:bg-blue-900', 'text-blue-700', 'dark:text-blue-200');
activeButton.classList.remove('hover:bg-gray-200', 'dark:hover:bg-gray-700');
}
}
// Task functions
function renderTasks() {
renderDashboardTasks();
renderAllTasks();
renderPriorityColumns();
}
function renderDashboardTasks() {
const now = new Date();
const today = new Date(now.getFullYear(), now.getMonth(), now.getDate());
const tomorrow = new Date(today);
tomorrow.setDate(tomorrow.getDate() + 1);
// Overdue tasks (due before today and not done)
const overdueTasks = tasks.filter(task =>
task.due_at &&
new Date(task.due_at) < today &&
task.status !== 'done' &&
task.status !== 'archived'
);
// Today's tasks (due today)
const todayTasks = tasks.filter(task =>
task.due_at &&
new Date(task.due_at) >= today &&
new Date(task.due_at) < tomorrow &&
task.status !== 'done' &&
task.status !== 'archived'
);
// Tomorrow's tasks (due tomorrow)
const tomorrowTasks = tasks.filter(task =>
task.due_at &&
new Date(task.due_at) >= tomorrow &&
new Date(task.due_at) < new Date(tomorrow.getTime() + 86400000) &&
task.status !== 'done' &&
task.status !== 'archived'
);
// Upcoming tasks (due after tomorrow)
const upcomingTasks = tasks.filter(task =>
task.due_at &&
new Date(task.due_at) >= new Date(tomorrow.getTime() + 86400000) &&
task.status !== 'done' &&
task.status !== 'archived'
);
// Render each section
renderTaskList('overdue-tasks', overdueTasks);
renderTaskList('today-tasks', todayTasks);
renderTaskList('tomorrow-tasks', tomorrowTasks);
renderTaskList('upcoming-tasks', upcomingTasks);
}
function renderAllTasks() {
const filteredTasks = filterAndSortTasks();
renderTaskList('all-tasks-list', filteredTasks);
}
function filterAndSortTasks() {
const statusFilter = document.getElementById('task-filter-status').value;
const priorityFilter = document.getElementById('task-filter-priority').value;
const searchQuery = document.getElementById('task-search').value.toLowerCase();
const sortOption = document.getElementById('task-sort').value;
let filteredTasks = [...tasks];
// Apply status filter
if (statusFilter) {
filteredTasks = filteredTasks.filter(task => task.status === statusFilter);
}
// Apply priority filter
if (priorityFilter) {
filteredTasks = filteredTasks.filter(task => task.priority === priorityFilter);
}
// Apply search filter
if (searchQuery) {
filteredTasks = filteredTasks.filter(task =>
task.title.toLowerCase().includes(searchQuery) ||
(task.description && task.description.toLowerCase().includes(searchQuery))
);
}
// Apply sorting
switch (sortOption) {
case 'due_asc':
filteredTasks.sort((a, b) => {
if (!a.due_at && !b.due_at) return 0;
if (!a.due_at) return 1;
if (!b.due_at) return -1;
return new Date(a.due_at) - new Date(b.due_at);
});
break;
case 'due_desc':
filteredTasks.sort((a, b) => {
if (!a.due_at && !b.due_at) return 0;
if (!a.due_at) return 1;
if (!b.due_at) return -1;
return new Date(b.due_at) - new Date(a.due_at);
});
break;
case 'priority':
const priorityOrder = { 'urgent': 0, 'high': 1, 'normal': 2, 'low': 3 };
filteredTasks.sort((a, b) => priorityOrder[a.priority] - priorityOrder[b.priority]);
break;
case 'created':
filteredTasks.sort((a, b) => new Date(b.created_at) - new Date(a.created_at));
break;
case 'updated':
filteredTasks.sort((a, b) => new Date(b.updated_at) - new Date(a.updated_at));
break;
}
return filteredTasks;
}
function renderPriorityColumns() {
const urgentTasks = tasks.filter(task =>
task.priority === 'urgent' &&
task.status !== 'done' &&
task.status !== 'archived'
);
const highTasks = tasks.filter(task =>
task.priority === 'high' &&
task.status !== 'done' &&
task.status !== 'archived'
);
const normalTasks = tasks.filter(task =>
task.priority === 'normal' &&
task.status !== 'done' &&
task.status !== 'archived'
);
const lowTasks = tasks.filter(task =>
task.priority === 'low' &&
task.status !== 'done' &&
task.status !== 'archived'
);
// Update counts
document.querySelector('#priorities-view .priority-column:nth-child(1) span').textContent = urgentTasks.length;
document.querySelector('#priorities-view .priority-column:nth-child(2) span').textContent = highTasks.length;
document.querySelector('#priorities-view .priority-column:nth-child(3) span').textContent = normalTasks.length;
document.querySelector('#priorities-view .priority-column:nth-child(4) span').textContent = lowTasks.length;
// Render tasks
renderTaskList('urgent-tasks', urgentTasks);
renderTaskList('high-tasks', highTasks);
renderTaskList('normal-tasks', normalTasks);
renderTaskList('low-tasks', lowTasks);
}
function renderTaskList(containerId, taskList) {
const container = document.getElementById(containerId);
container.innerHTML = '';
if (taskList.length === 0) {
container.innerHTML = '<p class="text-gray-500 dark:text-gray-400 text-center py-4">Aucune tâche à afficher</p>';
return;
}
taskList.forEach(task => {
const taskElement = createTaskElement(task);
container.appendChild(taskElement);
});
}
function createTaskElement(task) {
const category = categories.find(c => c.id === task.category_id);
const taskReminders = reminders.filter(r => r.task_id === task.id);
const taskElement = document.createElement('div');
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';
taskElement.dataset.taskId = task.id;
// Priority indicator
let priorityClass = '';
let priorityText = '';
switch (task.priority) {
case 'urgent':
priorityClass = 'bg-urgent text-white';
priorityText = 'Urgent';
break;
case 'high':
priorityClass = 'bg-high text-white';
priorityText = 'Haute';
break;
case 'normal':
priorityClass = 'bg-normal text-white';
priorityText = 'Normale';
break;
case 'low':
priorityClass = 'bg-low text-white';
priorityText = 'Basse';
break;
}
// Status checkbox
const isChecked = task.status === 'done';
// Due date display
let dueDateDisplay = '';
if (task.due_at) {
const dueDate = new Date(task.due_at);
const now = new Date();
const today = new Date(now.getFullYear(), now.getMonth(), now.getDate());
const tomorrow = new Date(today);
tomorrow.setDate(tomorrow.getDate() + 1);
if (dueDate < today && task.status !== 'done') {
dueDateDisplay = `<span class="text-red-500 dark:text-red-400 text-sm"><i class="fas fa-exclamation-circle mr-1"></i> En retard</span>`;
} else if (dueDate >= today && dueDate < tomorrow) {
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>`;
} else if (dueDate >= tomorrow && dueDate < new Date(tomorrow.getTime() + 86400000)) {
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>`;
} else {
dueDateDisplay = `<span class="text-gray-500 dark:text-gray-400 text-sm"><i class="far fa-calendar-alt mr-1"></i> ${dueDate.toLocaleDateString()}</span>`;
}
}
taskElement.innerHTML = `
<div class="flex items-start">
<label class="checkbox-container">
<input type="checkbox" ${isChecked ? 'checked' : ''} onchange="toggleTaskStatus(${task.id})">
<span class="checkmark"></span>
</label>
<div class="flex-1 ml-3">
<div class="flex items-center justify-between">
<h3 class="font-medium ${isChecked ? 'line-through text-gray-400 dark:text-gray-500' : ''}">${task.title}</h3>
<div class="task-actions opacity-0 md:opacity-100 transition-opacity duration-200">
<button onclick="event.stopPropagation();openTaskDrawer(${task.id})" class="p-1 text-gray-500 hover:text-gray-700 dark:hover:text-gray-300">
<i class="fas fa-pencil-alt"></i>
</button>
<button onclick="event.stopPropagation();duplicateTask(${task.id})" class="p-1 text-gray-500 hover:text-gray-700 dark:hover:text-gray-300">
<i class="fas fa-copy"></i>
</button>
<button onclick="event.stopPropagation();deleteTask(${task.id})" class="p-1 text-gray-500 hover:text-gray-700 dark:hover:text-gray-300">
<i class="fas fa-trash-alt"></i>
</button>
</div>
</div>
${task.description ? `<p class="text-sm text-gray-600 dark:text-gray-400 mt-1">${task.description}</p>` : ''}
<div class="mt-2 flex flex-wrap items-center gap-2">
${task.priority !== 'normal' ? `<span class="px-2 py-1 text-xs rounded-full ${priorityClass}">${priorityText}</span>` : ''}
${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>` : ''}
${task.due_at ? dueDateDisplay : ''}
${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>` : ''}
</div>
</div>
</div>
`;
// Add click handler to open task drawer
taskElement.addEventListener('click', () => openTaskDrawer(task.id));
return taskElement;
}
function toggleTaskStatus(taskId) {
const taskIndex = tasks.findIndex(t => t.id === taskId);
if (taskIndex === -1) return;
const task = tasks[taskIndex];
const isChecked = document.querySelector(`.task-card[data-task-id="${taskId}"] input[type="checkbox"]`).checked;
if (isChecked) {
task.status = 'done';
task.completed_at = new Date().toISOString();
// Remove future reminders
reminders = reminders.filter(r => r.task_id !== taskId || new Date(r.remind_at) < new Date());
} else {
task.status = 'todo';
task.completed_at = null;
}
task.updated_at = new Date().toISOString();
tasks[taskIndex] = task;
saveData();
renderTasks();
showNotification('success', 'Tâche mise à jour', `La tâche a été marquée comme ${isChecked ? 'terminée' : 'à faire'}`);
}
function quickAddTask() {
const input = document.getElementById('quick-add-input');
const text = input.value.trim();
if (!text) return;
// Parse quick add syntax (#category !priority)
let categoryName = null;
let priority = 'normal';
const categoryMatch = text.match(/#(\w+)/);
if (categoryMatch) {
categoryName = categoryMatch[1];
}
const priorityMatch = text.match(/!(\w+)/);
if (priorityMatch) {
const priorityText = priorityMatch[1].toLowerCase();
if (['urgent', 'high', 'normal', 'low'].includes(priorityText)) {
priority = priorityText;
}
}
// Clean title (remove tags)
const title = text.replace(/#\w+/g, '').replace(/!\w+/g, '').trim();
// Find category
let categoryId = null;
if (categoryName) {
const category = categories.find(c => c.name.toLowerCase() === categoryName.toLowerCase());
if (category) {
categoryId = category.id;
}
}
// Create task
const newTask = {
id: Date.now(),
title: title,
description: '',
status: 'todo',
priority: priority,
category_id: categoryId,
due_at: null,
created_at: new Date().toISOString(),
updated_at: new Date().toISOString()
};
tasks.push(newTask);
saveData();
renderTasks();
input.value = '';
showNotification('success', 'Tâche ajoutée', 'La nouvelle tâche a été créée');
}
function openTaskDrawer(taskId) {
currentTaskId = taskId;
const task = tasks.find(t => t.id === taskId);
if (!task) return;
// Fill form
document.getElementById('task-title').value = task.title;
document.getElementById('task-description').value = task.description || '';
document.getElementById('task-due').value = task.due_at ? formatDateTimeForInput(task.due_at) : '';
document.getElementById('task-priority').value = task.priority;
document.getElementById('task-status').value = task.status;
// Fill category select
const categorySelect = document.getElementById('task-category');
categorySelect.innerHTML = '<option value="">Aucune catégorie</option>';
categories.forEach(category => {
const option = document.createElement('option');
option.value = category.id;
option.textContent = category.name;
option.selected = task.category_id === category.id;
categorySelect.appendChild(option);
});
// Fill reminders
const remindersContainer = document.getElementById('task-reminders');
remindersContainer.innerHTML = '';
const taskReminders = reminders.filter(r => r.task_id === taskId);
taskReminders.forEach(reminder => {
const reminderElement = document.createElement('div');
reminderElement.className = 'flex items-center justify-between p-2 bg-gray-100 dark:bg-gray-700 rounded';
reminderElement.innerHTML = `
<span>${formatDateTime(new Date(reminder.remind_at))}</span>
<button onclick="removeReminder(${reminder.id})" class="text-red-500 hover:text-red-700 dark:hover:text-red-300">
<i class="fas fa-trash-alt"></i>
</button>
`;
remindersContainer.appendChild(reminderElement);
});
// Open drawer
document.getElementById('task-drawer').classList.remove('drawer-closed');
document.getElementById('task-drawer').classList.add('drawer-open');
}
function closeTaskDrawer() {
document.getElementById('task-drawer').classList.remove('drawer-open');
document.getElementById('task-drawer').classList.add('drawer-closed');
currentTaskId = null;
}
function saveTask() {
if (!currentTaskId) return;
const taskIndex = tasks.findIndex(t => t.id === currentTaskId);
if (taskIndex === -1) return;
const task = tasks[taskIndex];
// Update task
task.title = document.getElementById('task-title').value;
task.description = document.getElementById('task-description').value;
task.due_at = document.getElementById('task-due').value ? new Date(document.getElementById('task-due').value).toISOString() : null;
task.priority = document.getElementById('task-priority').value;
task.status = document.getElementById('task-status').value;
task.category_id = document.getElementById('task-category').value ? parseInt(document.getElementById('task-category').value) : null;
task.updated_at = new Date().toISOString();
tasks[taskIndex] = task;
saveData();
renderTasks();
closeTaskDrawer();
showNotification('success', 'Tâche sauvegardée', 'Les modifications ont été enregistrées');
}
function deleteTask(taskId) {
if (!taskId) taskId = currentTaskId;
if (!taskId) return;
if (confirm('Êtes-vous sûr de vouloir supprimer cette tâche ?')) {
tasks = tasks.filter(t => t.id !== taskId);
reminders = reminders.filter(r => r.task_id !== taskId);
saveData();
renderTasks();
if (taskId === currentTaskId) {
closeTaskDrawer();
}
showNotification('success', 'Tâche supprimée', 'La tâche a été supprimée');
}
}
function duplicateTask(taskId) {
const task = tasks.find(t => t.id === taskId);
if (!task) return;
const newTask = {
...task,
id: Date.now(),
status: 'todo',
completed_at: null,
created_at: new Date().toISOString(),
updated_at: new Date().toISOString()
};
tasks.push(newTask);
saveData();
renderTasks();
showNotification('success', 'Tâche dupliquée', 'La tâche a été dupliquée');
}
function addReminder() {
if (!currentTaskId) return;
// Check if we already have 5 reminders for this task
const taskReminders = reminders.filter(r => r.task_id === currentTaskId);
if (taskReminders.length >= 5) {
showNotification('error', 'Erreur', 'Maximum 5 rappels par tâche');
return;
}
// In a real app, we would show a modal to select date/time
// For demo, we'll just add a reminder for 1 hour before due date
const task = tasks.find(t => t.id === currentTaskId);
if (!task.due_at) {
showNotification('error', 'Erreur', 'Ajoutez d\'abord une date d\'échéance');
return;
}
const dueDate = new Date(task.due_at);
const reminderDate = new Date(dueDate.getTime() - 3600000); // 1 hour before
const newReminder = {
id: Date.now(),
task_id: currentTaskId,
remind_at: reminderDate.toISOString(),
channel: 'in_app',
created_at: new Date().toISOString()
};
reminders.push(newReminder);
saveData();
// Refresh task drawer
openTaskDrawer(currentTaskId);
showNotification('success', 'Rappel ajouté', 'Un nouveau rappel a été programmé');
}
function removeReminder(reminderId) {
reminders = reminders.filter(r => r.id !== reminderId);
saveData();
// Refresh task drawer
openTaskDrawer(currentTaskId);
}
// Category functions
function renderCategories() {
// Sidebar categories
const sidebarCategoryList = document.getElementById('category-list');
sidebarCategoryList.innerHTML = '';
// Categories view
const categoriesViewList = document.getElementById('categories-list');
categoriesViewList.innerHTML = '';
categories.forEach(category => {
// Count tasks in this category
const taskCount = tasks.filter(t => t.category_id === category.id && t.status !== 'done' && t.status !== 'archived').length;
// Sidebar item
const sidebarItem = document.createElement('button');
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';
sidebarItem.innerHTML = `
<span class="w-3 h-3 rounded-full bg-${category.color}-500 mr-3"></span>
<span class="truncate">${category.name}</span>
<span class="ml-auto px-2 py-1 text-xs bg-gray-200 dark:bg-gray-700 rounded-full">${taskCount}</span>
`;
sidebarItem.onclick = () => filterByCategory(category.id);
sidebarCategoryList.appendChild(sidebarItem);
// Categories view card
const categoryCard = document.createElement('div');
categoryCard.className = 'bg-white dark:bg-gray-800 rounded-lg shadow-sm p-4 border border-gray-200 dark:border-gray-700';
categoryCard.innerHTML = `
<div class="flex items-center mb-3">
<span class="w-4 h-4 rounded-full bg-${category.color}-500 mr-3"></span>
<h3 class="font-medium flex-1">${category.name}</h3>
<div class="flex space-x-2">
<button onclick="editCategory(${category.id})" class="p-1 text-gray-500 hover:text-gray-700 dark:hover:text-gray-300">
<i class="fas fa-pencil-alt"></i>
</button>
<button onclick="deleteCategory(${category.id})" class="p-1 text-gray-500 hover:text-gray-700 dark:hover:text-gray-300">
<i class="fas fa-trash-alt"></i>
</button>
</div>
</div>
<div class="flex justify-between items-center">
<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>
<button onclick="filterByCategory(${category.id})" class="text-sm text-blue-600 dark:text-blue-400 hover:underline">
Voir les tâches
</button>
</div>
`;
categoriesViewList.appendChild(categoryCard);
});
}
function filterByCategory(categoryId) {
// Switch to all tasks view
switchView('all-tasks');
// Set category filter
document.getElementById('task-filter-status').value = '';
document.getElementById('task-filter-priority').value = '';
document.getElementById('task-search').value = '';
document.getElementById('task-sort').value = 'due_asc';
// Trigger filter
renderAllTasks();
}
function showAddCategoryModal() {
// In a real app, we would show a modal to add a new category
// For demo, we'll just add a random category
const colors = ['work', 'personal', 'property', 'admin'];
const icons = ['briefcase', 'home', 'building', 'file-alt', 'shopping-cart', 'utensils', 'car', 'heart'];
const newCategory = {
id: Date.now(),
name: `Nouvelle catégorie ${categories.length + 1}`,
color: colors[Math.floor(Math.random() * colors.length)],
icon: icons[Math.floor(Math.random() * icons.length)],
created_at: new Date().toISOString()
};
categories.push(newCategory);
saveData();
renderCategories();
showNotification('success', 'Catégorie ajoutée', 'Une nouvelle catégorie a été créée');
}
function editCategory(categoryId) {
// In a real app, we would show a modal to edit the category
const category = categories.find(c => c.id === categoryId);
if (!category) return;
// For demo, we'll just add a " (modifié)" to the name
category.name = `${category.name} (modifié)`;
saveData();
renderCategories();
showNotification('success', 'Catégorie modifiée', 'La cat
<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>
</html>