Spaces:
Running
Running
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>TaskFlow - Modern Todo App</title> | |
| <!-- Font Awesome for icons --> | |
| <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"> | |
| <style> | |
| :root { | |
| --primary: #6366f1; | |
| --primary-dark: #4f46e5; | |
| --secondary: #8b5cf6; | |
| --success: #10b981; | |
| --warning: #f59e0b; | |
| --danger: #ef4444; | |
| --dark: #1f2937; | |
| --light: #f3f4f6; | |
| --white: #ffffff; | |
| --gray: #6b7280; | |
| --gray-light: #e5e7eb; | |
| --shadow-sm: 0 1px 2px 0 rgb(0 0 0 / 0.05); | |
| --shadow: 0 4px 6px -1px rgb(0 0 0 / 0.1); | |
| --shadow-lg: 0 10px 15px -3px rgb(0 0 0 / 0.1); | |
| --shadow-xl: 0 20px 25px -5px rgb(0 0 0 / 0.1); | |
| --transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); | |
| } | |
| [data-theme="dark"] { | |
| --bg-primary: #0f172a; | |
| --bg-secondary: #1e293b; | |
| --bg-tertiary: #334155; | |
| --text-primary: #f1f5f9; | |
| --text-secondary: #cbd5e1; | |
| --text-tertiary: #94a3b8; | |
| --border: #334155; | |
| --card-bg: #1e293b; | |
| --hover-bg: #334155; | |
| } | |
| [data-theme="light"] { | |
| --bg-primary: #ffffff; | |
| --bg-secondary: #f9fafb; | |
| --bg-tertiary: #f3f4f6; | |
| --text-primary: #111827; | |
| --text-secondary: #374151; | |
| --text-tertiary: #6b7280; | |
| --border: #e5e7eb; | |
| --card-bg: #ffffff; | |
| --hover-bg: #f9fafb; | |
| } | |
| * { | |
| margin: 0; | |
| padding: 0; | |
| box-sizing: border-box; | |
| } | |
| body { | |
| font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif; | |
| background: var(--bg-primary); | |
| color: var(--text-primary); | |
| min-height: 100vh; | |
| transition: var(--transition); | |
| position: relative; | |
| overflow-x: hidden; | |
| } | |
| body::before { | |
| content: ''; | |
| position: fixed; | |
| top: 0; | |
| left: 0; | |
| right: 0; | |
| height: 300px; | |
| background: linear-gradient(135deg, var(--primary) 0%, var(--secondary) 100%); | |
| opacity: 0.1; | |
| z-index: -1; | |
| } | |
| .container { | |
| max-width: 1200px; | |
| margin: 0 auto; | |
| padding: 2rem 1rem; | |
| } | |
| header { | |
| display: flex; | |
| justify-content: space-between; | |
| align-items: center; | |
| margin-bottom: 2rem; | |
| padding: 1.5rem; | |
| background: var(--card-bg); | |
| border-radius: 16px; | |
| box-shadow: var(--shadow); | |
| backdrop-filter: blur(10px); | |
| animation: slideDown 0.5s ease; | |
| } | |
| .logo { | |
| display: flex; | |
| align-items: center; | |
| gap: 1rem; | |
| font-size: 1.5rem; | |
| font-weight: 700; | |
| color: var(--primary); | |
| } | |
| .logo i { | |
| font-size: 2rem; | |
| animation: pulse 2s infinite; | |
| } | |
| .header-actions { | |
| display: flex; | |
| gap: 1rem; | |
| align-items: center; | |
| } | |
| .theme-toggle { | |
| background: var(--bg-tertiary); | |
| border: none; | |
| border-radius: 50%; | |
| width: 40px; | |
| height: 40px; | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| cursor: pointer; | |
| transition: var(--transition); | |
| color: var(--text-primary); | |
| } | |
| .theme-toggle:hover { | |
| transform: scale(1.1); | |
| background: var(--hover-bg); | |
| } | |
| .stats-container { | |
| display: grid; | |
| grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); | |
| gap: 1rem; | |
| margin-bottom: 2rem; | |
| } | |
| .stat-card { | |
| background: var(--card-bg); | |
| padding: 1.5rem; | |
| border-radius: 12px; | |
| box-shadow: var(--shadow); | |
| display: flex; | |
| align-items: center; | |
| gap: 1rem; | |
| transition: var(--transition); | |
| animation: fadeIn 0.5s ease; | |
| } | |
| .stat-card:hover { | |
| transform: translateY(-2px); | |
| box-shadow: var(--shadow-lg); | |
| } | |
| .stat-icon { | |
| width: 48px; | |
| height: 48px; | |
| border-radius: 12px; | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| font-size: 1.5rem; | |
| } | |
| .stat-icon.total { | |
| background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); | |
| color: white; | |
| } | |
| .stat-icon.completed { | |
| background: linear-gradient(135deg, #10b981 0%, #059669 100%); | |
| color: white; | |
| } | |
| .stat-icon.pending { | |
| background: linear-gradient(135deg, #f59e0b 0%, #d97706 100%); | |
| color: white; | |
| } | |
| .stat-icon.overdue { | |
| background: linear-gradient(135deg, #ef4444 0%, #dc2626 100%); | |
| color: white; | |
| } | |
| .stat-info h3 { | |
| font-size: 1.5rem; | |
| font-weight: 700; | |
| } | |
| .stat-info p { | |
| color: var(--text-tertiary); | |
| font-size: 0.875rem; | |
| } | |
| .main-content { | |
| display: grid; | |
| grid-template-columns: 1fr 2fr; | |
| gap: 2rem; | |
| animation: slideUp 0.5s ease; | |
| } | |
| .sidebar { | |
| background: var(--card-bg); | |
| padding: 1.5rem; | |
| border-radius: 16px; | |
| box-shadow: var(--shadow); | |
| height: fit-content; | |
| position: sticky; | |
| top: 2rem; | |
| } | |
| .add-todo-form { | |
| margin-bottom: 2rem; | |
| } | |
| .form-group { | |
| margin-bottom: 1rem; | |
| } | |
| .form-group label { | |
| display: block; | |
| margin-bottom: 0.5rem; | |
| color: var(--text-secondary); | |
| font-size: 0.875rem; | |
| font-weight: 500; | |
| } | |
| .form-control { | |
| width: 100%; | |
| padding: 0.75rem; | |
| border: 1px solid var(--border); | |
| border-radius: 8px; | |
| background: var(--bg-secondary); | |
| color: var(--text-primary); | |
| transition: var(--transition); | |
| font-size: 0.875rem; | |
| } | |
| .form-control:focus { | |
| outline: none; | |
| border-color: var(--primary); | |
| box-shadow: 0 0 0 3px rgba(99, 102, 241, 0.1); | |
| } | |
| .priority-select, | |
| .category-select { | |
| display: grid; | |
| grid-template-columns: repeat(3, 1fr); | |
| gap: 0.5rem; | |
| } | |
| .priority-btn, | |
| .category-btn { | |
| padding: 0.5rem; | |
| border: 1px solid var(--border); | |
| background: var(--bg-secondary); | |
| color: var(--text-secondary); | |
| border-radius: 6px; | |
| cursor: pointer; | |
| transition: var(--transition); | |
| font-size: 0.75rem; | |
| text-align: center; | |
| } | |
| .priority-btn.active, | |
| .category-btn.active { | |
| background: var(--primary); | |
| color: white; | |
| border-color: var(--primary); | |
| } | |
| .priority-btn:hover, | |
| .category-btn:hover { | |
| transform: translateY(-1px); | |
| box-shadow: var(--shadow-sm); | |
| } | |
| .btn { | |
| padding: 0.75rem 1.5rem; | |
| border: none; | |
| border-radius: 8px; | |
| font-weight: 500; | |
| cursor: pointer; | |
| transition: var(--transition); | |
| display: inline-flex; | |
| align-items: center; | |
| gap: 0.5rem; | |
| font-size: 0.875rem; | |
| } | |
| .btn-primary { | |
| background: linear-gradient(135deg, var(--primary) 0%, var(--primary-dark) 100%); | |
| color: white; | |
| width: 100%; | |
| justify-content: center; | |
| } | |
| .btn-primary:hover { | |
| transform: translateY(-2px); | |
| box-shadow: var(--shadow-lg); | |
| } | |
| .filter-section { | |
| margin-top: 2rem; | |
| padding-top: 2rem; | |
| border-top: 1px solid var(--border); | |
| } | |
| .filter-title { | |
| font-size: 0.875rem; | |
| font-weight: 600; | |
| color: var(--text-secondary); | |
| margin-bottom: 1rem; | |
| text-transform: uppercase; | |
| letter-spacing: 0.05em; | |
| } | |
| .filter-tabs { | |
| display: flex; | |
| flex-direction: column; | |
| gap: 0.5rem; | |
| } | |
| .filter-tab { | |
| padding: 0.75rem; | |
| background: var(--bg-secondary); | |
| border: 1px solid transparent; | |
| border-radius: 8px; | |
| cursor: pointer; | |
| transition: var(--transition); | |
| display: flex; | |
| align-items: center; | |
| gap: 0.75rem; | |
| color: var(--text-secondary); | |
| } | |
| .filter-tab:hover { | |
| background: var(--hover-bg); | |
| transform: translateX(4px); | |
| } | |
| .filter-tab.active { | |
| background: var(--primary); | |
| color: white; | |
| border-color: var(--primary); | |
| } | |
| .filter-tab i { | |
| width: 20px; | |
| text-align: center; | |
| } | |
| .filter-tab .badge { | |
| margin-left: auto; | |
| background: rgba(255, 255, 255, 0.2); | |
| padding: 0.125rem 0.5rem; | |
| border-radius: 12px; | |
| font-size: 0.75rem; | |
| font-weight: 600; | |
| } | |
| .todo-container { | |
| background: var(--card-bg); | |
| padding: 1.5rem; | |
| border-radius: 16px; | |
| box-shadow: var(--shadow); | |
| } | |
| .todo-header { | |
| display: flex; | |
| justify-content: space-between; | |
| align-items: center; | |
| margin-bottom: 1.5rem; | |
| } | |
| .search-box { | |
| position: relative; | |
| flex: 1; | |
| max-width: 400px; | |
| } | |
| .search-box input { | |
| width: 100%; | |
| padding: 0.75rem 1rem 0.75rem 2.5rem; | |
| border: 1px solid var(--border); | |
| border-radius: 8px; | |
| background: var(--bg-secondary); | |
| color: var(--text-primary); | |
| transition: var(--transition); | |
| } | |
| .search-box i { | |
| position: absolute; | |
| left: 0.75rem; | |
| top: 50%; | |
| transform: translateY(-50%); | |
| color: var(--text-tertiary); | |
| } | |
| .sort-dropdown { | |
| position: relative; | |
| } | |
| .sort-btn { | |
| padding: 0.75rem 1rem; | |
| background: var(--bg-secondary); | |
| border: 1px solid var(--border); | |
| border-radius: 8px; | |
| color: var(--text-primary); | |
| cursor: pointer; | |
| transition: var(--transition); | |
| display: flex; | |
| align-items: center; | |
| gap: 0.5rem; | |
| } | |
| .sort-btn:hover { | |
| background: var(--hover-bg); | |
| } | |
| .todo-list { | |
| min-height: 400px; | |
| } | |
| .todo-item { | |
| background: var(--bg-secondary); | |
| border: 1px solid var(--border); | |
| border-radius: 12px; | |
| padding: 1rem; | |
| margin-bottom: 0.75rem; | |
| transition: var(--transition); | |
| cursor: move; | |
| animation: slideIn 0.3s ease; | |
| } | |
| .todo-item:hover { | |
| transform: translateX(4px); | |
| box-shadow: var(--shadow); | |
| } | |
| .todo-item.dragging { | |
| opacity: 0.5; | |
| transform: scale(0.95); | |
| } | |
| .todo-item.completed { | |
| opacity: 0.7; | |
| } | |
| .todo-item.completed .todo-text { | |
| text-decoration: line-through; | |
| color: var(--text-tertiary); | |
| } | |
| .todo-content { | |
| display: flex; | |
| align-items: flex-start; | |
| gap: 1rem; | |
| } | |
| .todo-checkbox { | |
| width: 20px; | |
| height: 20px; | |
| border-radius: 50%; | |
| border: 2px solid var(--border); | |
| background: var(--bg-tertiary); | |
| cursor: pointer; | |
| transition: var(--transition); | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| flex-shrink: 0; | |
| margin-top: 2px; | |
| } | |
| .todo-checkbox:hover { | |
| border-color: var(--primary); | |
| } | |
| .todo-checkbox.checked { | |
| background: var(--success); | |
| border-color: var(--success); | |
| } | |
| .todo-checkbox.checked i { | |
| color: white; | |
| font-size: 0.75rem; | |
| } | |
| .todo-details { | |
| flex: 1; | |
| } | |
| .todo-header-row { | |
| display: flex; | |
| align-items: center; | |
| gap: 0.5rem; | |
| margin-bottom: 0.5rem; | |
| } | |
| .todo-text { | |
| font-size: 1rem; | |
| color: var(--text-primary); | |
| flex: 1; | |
| } | |
| .todo-priority { | |
| padding: 0.125rem 0.5rem; | |
| border-radius: 4px; | |
| font-size: 0.75rem; | |
| font-weight: 600; | |
| text-transform: uppercase; | |
| } | |
| .priority-high { | |
| background: rgba(239, 68, 68, 0.1); | |
| color: var(--danger); | |
| } | |
| .priority-medium { | |
| background: rgba(245, 158, 11, 0.1); | |
| color: var(--warning); | |
| } | |
| .priority-low { | |
| background: rgba(16, 185, 129, 0.1); | |
| color: var(--success); | |
| } | |
| .todo-meta { | |
| display: flex; | |
| align-items: center; | |
| gap: 1rem; | |
| font-size: 0.875rem; | |
| color: var(--text-tertiary); | |
| } | |
| .todo-category { | |
| display: inline-flex; | |
| align-items: center; | |
| gap: 0.25rem; | |
| } | |
| .todo-date { | |
| display: inline-flex; | |
| align-items: center; | |
| gap: 0.25rem; | |
| } | |
| .todo-actions { | |
| display: flex; | |
| gap: 0.5rem; | |
| } | |
| .action-btn { | |
| width: 32px; | |
| height: 32px; | |
| border-radius: 6px; | |
| background: transparent; | |
| border: none; | |
| color: var(--text-tertiary); | |
| cursor: pointer; | |
| transition: var(--transition); | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| } | |
| .action-btn:hover { | |
| background: var(--hover-bg); | |
| color: var(--text-primary); | |
| } | |
| .action-btn.delete:hover { | |
| background: rgba(239, 68, 68, 0.1); | |
| color: var(--danger); | |
| } | |
| .empty-state { | |
| text-align: center; | |
| padding: 3rem; | |
| color: var(--text-tertiary); | |
| } | |
| .empty-state i { | |
| font-size: 4rem; | |
| margin-bottom: 1rem; | |
| opacity: 0.5; | |
| } | |
| .toast { | |
| position: fixed; | |
| bottom: 2rem; | |
| right: 2rem; | |
| background: var(--card-bg); | |
| padding: 1rem 1.5rem; | |
| border-radius: 8px; | |
| box-shadow: var(--shadow-xl); | |
| display: flex; | |
| align-items: center; | |
| gap: 1rem; | |
| transform: translateX(400px); | |
| transition: transform 0.3s ease; | |
| z-index: 1000; | |
| } | |
| .toast.show { | |
| transform: translateX(0); | |
| } | |
| .toast.success { | |
| border-left: 4px solid var(--success); | |
| } | |
| .toast.error { | |
| border-left: 4px solid var(--danger); | |
| } | |
| .toast.info { | |
| border-left: 4px solid var(--primary); | |
| } | |
| @keyframes slideDown { | |
| from { | |
| opacity: 0; | |
| transform: translateY(-20px); | |
| } | |
| to { | |
| opacity: 1; | |
| transform: translateY(0); | |
| } | |
| } | |
| @keyframes slideUp { | |
| from { | |
| opacity: 0; | |
| transform: translateY(20px); | |
| } | |
| to { | |
| opacity: 1; | |
| transform: translateY(0); | |
| } | |
| } | |
| @keyframes slideIn { | |
| from { | |
| opacity: 0; | |
| transform: translateX(-20px); | |
| } | |
| to { | |
| opacity: 1; | |
| transform: translateX(0); | |
| } | |
| } | |
| @keyframes fadeIn { | |
| from { | |
| opacity: 0; | |
| } | |
| to { | |
| opacity: 1; | |
| } | |
| } | |
| @keyframes pulse { | |
| 0%, | |
| 100% { | |
| transform: scale(1); | |
| } | |
| 50% { | |
| transform: scale(1.05); | |
| } | |
| } | |
| @media (max-width: 768px) { | |
| .main-content { | |
| grid-template-columns: 1fr; | |
| } | |
| .sidebar { | |
| position: static; | |
| } | |
| .stats-container { | |
| grid-template-columns: repeat(2, 1fr); | |
| } | |
| header { | |
| flex-direction: column; | |
| gap: 1rem; | |
| } | |
| .todo-header { | |
| flex-direction: column; | |
| gap: 1rem; | |
| } | |
| .search-box { | |
| max-width: 100%; | |
| } | |
| } | |
| @media (max-width: 480px) { | |
| .stats-container { | |
| grid-template-columns: 1fr; | |
| } | |
| .container { | |
| padding: 1rem; | |
| } | |
| .todo-item { | |
| padding: 0.75rem; | |
| } | |
| .todo-meta { | |
| flex-direction: column; | |
| align-items: flex-start; | |
| gap: 0.5rem; | |
| } | |
| } | |
| .built-with { | |
| position: fixed; | |
| bottom: 1rem; | |
| left: 1rem; | |
| font-size: 0.75rem; | |
| color: var(--text-tertiary); | |
| opacity: 0.7; | |
| transition: opacity 0.3s; | |
| } | |
| .built-with:hover { | |
| opacity: 1; | |
| } | |
| .built-with a { | |
| color: var(--primary); | |
| text-decoration: none; | |
| font-weight: 500; | |
| } | |
| </style> | |
| </head> | |
| <body data-theme="light"> | |
| <div class="container"> | |
| <header> | |
| <div class="logo"> | |
| <i class="fas fa-tasks"></i> | |
| <span>TaskFlow</span> | |
| </div> | |
| <div class="header-actions"> | |
| <button class="theme-toggle" onclick="toggleTheme()"> | |
| <i class="fas fa-moon" id="themeIcon"></i> | |
| </button> | |
| </div> | |
| </header> | |
| <div class="stats-container"> | |
| <div class="stat-card"> | |
| <div class="stat-icon total"> | |
| <i class="fas fa-list"></i> | |
| </div> | |
| <div class="stat-info"> | |
| <h3 id="totalTasks">0</h3> | |
| <p>Total Tasks</p> | |
| </div> | |
| </div> | |
| <div class="stat-card"> | |
| <div class="stat-icon completed"> | |
| <i class="fas fa-check-circle"></i> | |
| </div> | |
| <div class="stat-info"> | |
| <h3 id="completedTasks">0</h3> | |
| <p>Completed</p> | |
| </div> | |
| </div> | |
| <div class="stat-card"> | |
| <div class="stat-icon pending"> | |
| <i class="fas fa-clock"></i> | |
| </div> | |
| <div class="stat-info"> | |
| <h3 id="pendingTasks">0</h3> | |
| <p>Pending</p> | |
| </div> | |
| </div> | |
| <div class="stat-card"> | |
| <div class="stat-icon overdue"> | |
| <i class="fas fa-exclamation-triangle"></i> | |
| </div> | |
| <div class="stat-info"> | |
| <h3 id="overdueTasks">0</h3> | |
| <p>Overdue</p> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="main-content"> | |
| <aside class="sidebar"> | |
| <form class="add-todo-form" id="todoForm"> | |
| <div class="form-group"> | |
| <label for="todoInput">Task Description</label> | |
| <input type="text" id="todoInput" class="form-control" placeholder="What needs to be done?" required> | |
| </div> | |
| <div class="form-group"> | |
| <label>Priority</label> | |
| <div class="priority-select"> | |
| <button type="button" class="priority-btn" data-priority="low" onclick="selectPriority('low')"> | |
| <i class="fas fa-flag"></i> Low | |
| </button> | |
| <button type="button" class="priority-btn active" data-priority="medium" onclick="selectPriority('medium')"> | |
| <i class="fas fa-flag"></i> Medium | |
| </button> | |
| <button type="button" class="priority-btn" data-priority="high" onclick="selectPriority('high')"> | |
| <i class="fas fa-flag"></i> High | |
| </button> | |
| </div> | |
| </div> | |
| <div class="form-group"> | |
| <label>Category</label> | |
| <div class="category-select"> | |
| <button type="button" class="category-btn active" data-category="personal" onclick="selectCategory('personal')"> | |
| <i class="fas fa-user"></i> Personal | |
| </button> | |
| <button type="button" class="category-btn" data-category="work" onclick="selectCategory('work')"> | |
| <i class="fas fa-briefcase"></i> Work | |
| </button> | |
| <button type="button" class="category-btn" data-category="shopping" onclick="selectCategory('shopping')"> | |
| <i class="fas fa-shopping-cart"></i> Shopping | |
| </button> | |
| <button type="button" class="category-btn" data-category="health" onclick="selectCategory('health')"> | |
| <i class="fas fa-heartbeat"></i> Health | |
| </button> | |
| <button type="button" class="category-btn" data-category="education" onclick="selectCategory('education')"> | |
| <i class="fas fa-graduation-cap"></i> Education | |
| </button> | |
| <button type="button" class="category-btn" data-category="other" onclick="selectCategory('other')"> | |
| <i class="fas fa-ellipsis-h"></i> Other | |
| </button> | |
| </div> | |
| </div> | |
| <div class="form-group"> | |
| <label for="dueDate">Due Date</label> | |
| <input type="date" id="dueDate" class="form-control"> | |
| </div> | |
| <button type="submit" class="btn btn-primary"> | |
| <i class="fas fa-plus"></i> Add Task | |
| </button> | |
| </form> | |
| <div class="filter-section"> | |
| <div class="filter-title">Filter Tasks</div> | |
| <div class="filter-tabs"> | |
| <div class="filter-tab active" onclick="filterTodos('all')" data-filter="all"> | |
| <i class="fas fa-list"></i> | |
| <span>All Tasks</span> | |
| <span class="badge" id="allCount">0</span> | |
| </div> | |
| <div class="filter-tab" onclick="filterTodos('pending')" data-filter="pending"> | |
| <i class="fas fa-clock"></i> | |
| <span>Pending</span> | |
| <span class="badge" id="pendingCount">0</span> | |
| </div> | |
| <div class="filter-tab" onclick="filterTodos('completed')" data-filter="completed"> | |
| <i class="fas fa-check"></i> | |
| <span>Completed</span> | |
| <span class="badge" id="completedCount">0</span> | |
| </div> | |
| <div class="filter-tab" onclick="filterTodos('today')" data-filter="today"> | |
| <i class="fas fa-calendar-day"></i> | |
| <span>Today</span> | |
| <span class="badge" id="todayCount">0</span> | |
| </div> | |
| <div class="filter-tab" onclick="filterTodos('overdue')" data-filter="overdue"> | |
| <i class="fas fa-exclamation"></i> | |
| <span>Overdue</span> | |
| <span class="badge" id="overdueCount">0</span> | |
| </div> | |
| </div> | |
| </div> | |
| </aside> | |
| <main class="todo-container"> | |
| <div class="todo-header"> | |
| <div class="search-box"> | |
| <i class="fas fa-search"></i> | |
| <input type="text" placeholder="Search tasks..." id="searchInput" onkeyup="searchTodos()"> | |
| </div> | |
| <div class="sort-dropdown"> | |
| <button class="sort-btn" onclick="toggleSortMenu()"> | |
| <i class="fas fa-sort"></i> | |
| <span id="sortText">Sort by Date</span> | |
| </button> | |
| </div> | |
| </div> | |
| <div class="todo-list" id="todoList"> | |
| <div class="empty-state"> | |
| <i class="fas fa-clipboard-list"></i> | |
| <h3>No tasks yet</h3> | |
| <p>Add your first task to get started!</p> | |
| </div> | |
| </div> | |
| </main> | |
| </div> | |
| </div> | |
| <div class="toast" id="toast"> | |
| <i class="fas fa-check-circle" id="toastIcon"></i> | |
| <span id="toastMessage">Task added successfully!</span> | |
| </div> | |
| <div class="built-with"> | |
| Built with <a href="https://huggingface.co/spaces/akhaliq/anycoder" target="_blank">anycoder</a> | |
| </div> | |
| <script> | |
| let todos = []; | |
| let currentFilter = 'all'; | |
| let currentSort = 'date'; | |
| let selectedPriority = 'medium'; | |
| let selectedCategory = 'personal'; | |
| let draggedItem = null; | |
| // Initialize app | |
| document.addEventListener('DOMContentLoaded', function() { | |
| loadTodos(); | |
| updateStats(); | |
| renderTodos(); | |
| // Set today's date as default | |
| const today = new Date().toISOString().split('T')[0]; | |
| document.getElementById('dueDate').value = today; | |
| }); | |
| // Form submission | |
| document.getElementById('todoForm').addEventListener('submit', function(e) { | |
| e.preventDefault(); | |
| addTodo(); | |
| }); | |
| function addTodo() { | |
| const text = document.getElementById('todoInput').value.trim(); | |
| const dueDate = document.getElementById('dueDate').value; | |
| if (!text) return; | |
| const todo = { | |
| id: Date.now(), | |
| text: text, | |
| priority: selectedPriority, | |
| category: selectedCategory, | |
| dueDate: dueDate, | |
| completed: false, | |
| createdAt: new Date().toISOString() | |
| }; | |
| todos.unshift(todo); | |
| saveTodos(); | |
| renderTodos(); | |
| updateStats(); | |
| // Reset form | |
| document.getElementById('todoInput').value = ''; | |
| document.getElementById('todoForm').reset(); | |
| const today = new Date().toISOString().split('T')[0]; | |
| document.getElementById('dueDate').value = today; | |
| showToast('Task added successfully!', 'success'); | |
| } | |
| function toggleTodo(id) { | |
| const todo = todos.find(t => t.id === id); | |
| if (todo) { | |
| todo.completed = !todo.completed; | |
| saveTodos(); | |
| renderTodos(); | |
| updateStats(); | |
| showToast(todo.completed ? 'Task completed!' : 'Task marked as pending', 'info'); | |
| } | |
| } | |
| function deleteTodo(id) { | |
| todos = todos.filter(t => t.id !== id); | |
| saveTodos(); | |
| renderTodos(); | |
| updateStats(); | |
| showToast('Task deleted', 'error'); | |
| } | |
| function renderTodos() { | |
| const todoList = document.getElementById('todoList'); | |
| let filteredTodos = filterTodosArray(); | |
| if (filteredTodos.length === 0) { | |
| todoList.innerHTML = ` | |
| <div class="empty-state"> | |
| <i class="fas fa-clipboard-list"></i> | |
| <h3>No tasks found</h3> | |
| <p>${currentFilter === 'all' ? 'Add your first task to get started!' : 'No tasks match this filter'}</p> | |
| </div> | |
| `; | |
| return; | |
| } | |
| // Sort todos | |
| filteredTodos = sortTodosArray(filteredTodos); | |
| todoList.innerHTML = filteredTodos.map(todo => ` | |
| <div class="todo-item ${todo.completed ? 'completed' : ''}" | |
| draggable="true" | |
| ondragstart="dragStart(event, ${todo.id})" | |
| ondragover="dragOver(event)" | |
| ondrop="drop(event, ${todo.id})"> | |
| <div class="todo-content"> | |
| <div class="todo-checkbox ${todo.completed ? 'checked' : ''}" | |
| onclick="toggleTodo(${todo.id})"> | |
| ${todo.completed ? '<i class="fas fa-check"></i>' : ''} | |
| </div> | |
| <div class="todo-details"> | |
| <div class="todo-header-row"> | |
| <div class="todo-text">${escapeHtml(todo.text)}</div> | |
| <span class="todo-priority priority-${todo.priority}">${todo.priority}</span> | |
| </div> | |
| <div class="todo-meta"> | |
| <span class="todo-category"> | |
| <i class="fas ${getCategoryIcon(todo.category)}"></i> | |
| ${todo.category} | |
| </span> | |
| ${todo.dueDate ? ` | |
| <span class="todo-date"> | |
| <i class="fas fa-calendar"></i> | |
| ${formatDate(todo.dueDate)} | |
| ${isOverdue(todo.dueDate) && !todo.completed ? '<span style="color: var(--danger);"> (Overdue)</span>' : ''} | |
| </span> | |
| ` : ''} | |
| </div> | |
| </div> | |
| <div class="todo-actions"> | |
| <button class="action-btn delete" onclick="deleteTodo(${todo.id})"> | |
| <i class="fas fa-trash"></i> | |
| </button> | |
| </div> | |
| </div> | |
| </div> | |
| `).join(''); | |
| } | |
| function filterTodosArray() { | |
| const searchTerm = document.getElementById('searchInput').value.toLowerCase(); | |
| let filtered = todos.filter(todo => | |
| todo.text.toLowerCase().includes(searchTerm) | |
| ); | |
| switch(currentFilter) { | |
| case 'completed': | |
| filtered = filtered.filter(t => t.completed); | |
| break; | |
| case 'pending': | |
| filtered = filtered.filter(t => !t.completed); | |
| break; | |
| case 'today': | |
| const today = new Date().toISOString().split('T')[0]; | |
| filtered = filtered.filter(t => t.dueDate === today); | |
| break; | |
| case 'overdue': | |
| filtered = filtered.filter(t => isOverdue(t.dueDate) && !t.completed); | |
| break; | |
| } | |
| return filtered; | |
| } | |
| function sortTodosArray(todosArray) { | |
| return [...todosArray].sort((a, b) => { | |
| switch(currentSort) { | |
| case 'priority': | |
| const priorityOrder = { high: 0, medium: 1, low: 2 }; | |
| return priorityOrder[a.priority] - priorityOrder[b.priority]; | |
| case 'date': | |
| return new Date(b.dueDate || '9999-12-31') - new Date(a.dueDate || '9999-12-31'); | |
| case 'name': | |
| return a.text.localeCompare(b.text); | |
| default: | |
| return 0; | |
| } | |
| }); | |
| } | |
| function filterTodos(filter) { | |
| currentFilter = filter; | |
| // Update active tab | |
| document.querySelectorAll('.filter-tab').forEach(tab => { | |
| tab.classList.remove('active'); | |
| }); | |
| document.querySelector(`[data-filter="${filter}"]`).classList.add('active'); | |
| renderTodos(); | |
| } | |
| function searchTodos() { | |
| renderTodos(); | |
| } | |
| function toggleSortMenu() { | |
| const sorts = ['date', 'priority', 'name']; | |
| const currentIndex = sorts.indexOf(currentSort); | |
| currentSort = sorts[(currentIndex + 1) % sorts.length]; | |
| const sortText = { | |
| 'date': 'Sort by Date', | |
| 'priority': 'Sort by Priority', | |
| 'name': 'Sort by Name' | |
| }; | |
| document.getElementById('sortText').textContent = sortText[currentSort]; | |
| renderTodos(); | |
| } | |
| function selectPriority(priority) { | |
| selectedPriority = priority; | |
| document.querySelectorAll('.priority-btn').forEach(btn => { | |
| btn.classList.remove('active'); | |
| }); | |
| document.querySelector(`[data-priority="${priority}"]`).classList.add('active'); | |
| } | |
| function selectCategory(category) { | |
| selectedCategory = category; | |
| document.querySelectorAll('.category-btn').forEach(btn => { | |
| btn.classList.remove('active'); | |
| }); | |
| document.querySelector(`[data-category="${category}"]`).classList.add('active'); | |
| } | |
| function updateStats() { | |
| const total = todos.length; | |
| const completed = todos.filter(t => t.completed).length; | |
| const pending = todos.filter(t => !t.completed).length; | |
| const overdue = todos.filter(t => isOverdue(t.dueDate) && !t.completed).length; | |
| document.getElementById('totalTasks').textContent = total; | |
| document.getElementById('completedTasks').textContent = completed; | |
| document.getElementById('pendingTasks').textContent = pending; | |
| document.getElementById('overdueTasks').textContent = overdue; | |
| // Update filter badges | |
| document.getElementById('allCount').textContent = total; | |
| document.getElementById('pendingCount').textContent = pending; | |
| document.getElementById('completedCount').textContent = completed; | |
| document.getElementById('todayCount').textContent = todos.filter(t => { | |
| const today = new Date().toISOString().split('T')[0]; | |
| return t.dueDate === today; | |
| }).length; | |
| document.getElementById('overdueCount').textContent = overdue; | |
| } | |
| function isOverdue(dueDate) { | |
| if (!dueDate) return false; | |
| const today = new Date().toISOString().split('T')[0]; | |
| return dueDate < today; | |
| } | |
| function formatDate(dateString) { | |
| if (!dateString) return ''; | |
| const date = new Date(dateString); | |
| const today = new Date(); | |
| const tomorrow = new Date(today); | |
| tomorrow.setDate(tomorrow.getDate() + 1); | |
| if (dateString === today.toISOString().split('T')[0]) { | |
| return 'Today'; | |
| } else if (dateString === tomorrow.toISOString().split('T')[0]) { | |
| return 'Tomorrow'; | |
| } else { | |
| return date.toLocaleDateString('en-US', { month: 'short', day: 'numeric', year: 'numeric' }); | |
| } | |
| } | |
| function getCategoryIcon(category) { | |
| const icons = { | |
| 'personal': 'fa-user', | |
| 'work': 'fa-briefcase', | |
| 'shopping': 'fa-shopping-cart', | |
| 'health': 'fa-heartbeat', | |
| 'education': 'fa-graduation-cap', | |
| 'other': 'fa-ellipsis-h' | |
| }; | |
| return icons[category] || 'fa-circle'; | |
| } | |
| function escapeHtml(text) { | |
| const map = { | |
| '&': '&', | |
| '<': '<', | |
| '>': '>', | |
| '"': '"', | |
| "'": ''' | |
| }; | |
| return text.replace(/[&<>"']/g, m => map[m]); | |
| } | |
| function toggleTheme() { | |
| const body = document.body; | |
| const currentTheme = body.getAttribute('data-theme'); | |
| const newTheme = currentTheme === 'light' ? 'dark' : 'light'; | |
| body.setAttribute('data-theme', newTheme); | |
| localStorage.setItem('theme', newTheme); | |
| const icon = document.getElementById('themeIcon'); | |
| icon.className = newTheme === 'light' ? 'fas fa-moon' : 'fas fa-sun'; | |
| } | |
| function showToast(message, type = 'success') { | |
| const toast = document.getElementById('toast'); | |
| const toastMessage = document.getElementById('toastMessage'); | |
| const toastIcon = document.getElementById('toastIcon'); | |
| toastMessage.textContent = message; | |
| toast.className = `toast ${type}`; | |
| const icons = { | |
| 'success': 'fa-check-circle', | |
| 'error': 'fa-times-circle', | |
| 'info': 'fa-info-circle' | |
| }; | |
| toastIcon.className = `fas ${icons[type]}`; | |
| toast.classList.add('show'); | |
| setTimeout(() => { | |
| toast.classList.remove('show'); | |
| }, 3000); | |
| } | |
| // Drag and drop functions | |
| function dragStart(e, id) { | |
| draggedItem = id; | |
| e.target.classList.add('dragging'); | |
| } | |
| function dragOver(e) { | |
| e.preventDefault(); | |
| } | |
| function drop(e, targetId) { | |
| e.preventDefault(); | |
| if (draggedItem === null || draggedItem === targetId) return; | |
| const draggedIndex = todos.findIndex(t => t.id === draggedItem); | |
| const targetIndex = todos.findIndex(t => t.id === targetId); | |
| if (draggedIndex !== -1 && targetIndex !== -1) { | |
| const [removed] = todos.splice(draggedIndex, 1); | |
| todos.splice(targetIndex, 0, removed); | |
| saveTodos(); | |
| renderTodos(); | |
| } | |
| document.querySelectorAll('.dragging').forEach(item => { | |
| item.classList.remove('dragging'); | |
| }); | |
| draggedItem = null; | |
| } | |
| // Local storage functions | |
| function saveTodos() { | |
| localStorage.setItem('todos', JSON.stringify(todos)); | |
| } | |
| function loadTodos() { | |
| const saved = localStorage.getItem('todos'); | |
| if (saved) { | |
| todos = JSON.parse(saved); | |
| } | |
| // Load theme | |
| const savedTheme = localStorage.getItem('theme'); | |
| if (savedTheme) { | |
| document.body.setAttribute('data-theme', savedTheme); | |
| const icon = document.getElementById('themeIcon'); | |
| icon.className = savedTheme === 'light' ? 'fas fa-moon' : 'fas fa-sun'; | |
| } | |
| } | |
| </script> | |
| </body> | |
| </html> |