anycoder-d6a02b3a / index.html
akhaliq's picture
akhaliq HF Staff
Upload folder using huggingface_hub
54b9db0 verified
raw
history blame
39.3 kB
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>TaskFlow - Apple Style Todo App</title>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<style>
:root {
/* Apple-inspired color system */
--apple-blue: #007AFF;
--apple-green: #34C759;
--apple-orange: #FF9500;
--apple-red: #FF3B30;
--apple-purple: #AF52DE;
--apple-pink: #FF2D55;
--apple-gray: #8E8E93;
--apple-gray2: #AEAEB2;
--apple-gray3: #C7C7CC;
--apple-gray4: #D1D1D6;
--apple-gray5: #E5E5EA;
--apple-gray6: #F2F2F7;
/* System backgrounds */
--bg-primary: #FFFFFF;
--bg-secondary: #F2F2F7;
--bg-tertiary: #FFFFFF;
--bg-glass: rgba(255, 255, 255, 0.8);
--bg-glass-dark: rgba(255, 255, 255, 0.1);
/* Text colors */
--text-primary: #000000;
--text-secondary: #3C3C43;
--text-tertiary: #3C3C4399;
--text-quaternary: #3C3C4333;
/* Borders and separators */
--border-color: #C6C6C8;
--separator-color: #C6C6C84D;
/* Shadows */
--shadow-sm: 0 1px 3px rgba(0, 0, 0, 0.1);
--shadow-md: 0 4px 6px rgba(0, 0, 0, 0.07);
--shadow-lg: 0 10px 15px rgba(0, 0, 0, 0.1);
--shadow-xl: 0 20px 25px rgba(0, 0, 0, 0.1);
--shadow-floating: 0 8px 16px rgba(0, 0, 0, 0.08);
/* Transitions */
--transition-fast: 0.2s cubic-bezier(0.4, 0, 0.2, 1);
--transition-base: 0.3s cubic-bezier(0.4, 0, 0.2, 1);
--transition-slow: 0.5s cubic-bezier(0.4, 0, 0.2, 1);
/* Priority colors */
--priority-low: var(--apple-green);
--priority-medium: var(--apple-orange);
--priority-high: var(--apple-red);
}
[data-theme="dark"] {
--bg-primary: #000000;
--bg-secondary: #1C1C1E;
--bg-tertiary: #2C2C2E;
--bg-glass: rgba(28, 28, 30, 0.8);
--bg-glass-dark: rgba(44, 44, 46, 0.8);
--text-primary: #FFFFFF;
--text-secondary: #EBEBF5;
--text-tertiary: #EBEBF599;
--text-quaternary: #EBEBF533;
--border-color: #38383A;
--separator-color: #38383A4D;
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'SF Pro Display', 'SF Pro Text', system-ui, sans-serif;
background: var(--bg-secondary);
min-height: 100vh;
color: var(--text-primary);
transition: background-color var(--transition-base);
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
.container {
max-width: 1200px;
margin: 0 auto;
padding: 20px;
}
/* Header Section */
header {
background: var(--bg-glass);
backdrop-filter: saturate(180%) blur(20px);
-webkit-backdrop-filter: saturate(180%) blur(20px);
border-radius: 18px;
padding: 32px;
margin-bottom: 24px;
box-shadow: var(--shadow-floating);
animation: slideDown var(--transition-slow) ease-out;
border: 1px solid var(--separator-color);
}
.header-content {
display: flex;
justify-content: space-between;
align-items: center;
flex-wrap: wrap;
gap: 20px;
}
.logo {
display: flex;
align-items: center;
gap: 12px;
font-size: 28px;
font-weight: 600;
color: var(--text-primary);
letter-spacing: -0.5px;
}
.logo i {
font-size: 32px;
background: var(--apple-blue);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
.header-actions {
display: flex;
gap: 12px;
align-items: center;
}
.theme-toggle {
width: 44px;
height: 44px;
border-radius: 50%;
background: var(--bg-tertiary);
border: none;
cursor: pointer;
transition: all var(--transition-fast);
display: flex;
align-items: center;
justify-content: center;
font-size: 18px;
color: var(--text-secondary);
box-shadow: var(--shadow-sm);
}
.theme-toggle:hover {
transform: scale(1.05);
background: var(--apple-blue);
color: white;
}
.theme-toggle:active {
transform: scale(0.95);
}
.stats-container {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
gap: 16px;
margin-top: 24px;
}
.stat-card {
background: var(--bg-tertiary);
padding: 20px;
border-radius: 16px;
text-align: center;
transition: all var(--transition-fast);
border: 1px solid var(--separator-color);
}
.stat-card:hover {
transform: translateY(-2px);
box-shadow: var(--shadow-md);
}
.stat-number {
font-size: 32px;
font-weight: 600;
color: var(--apple-blue);
letter-spacing: -1px;
}
.stat-label {
font-size: 13px;
color: var(--text-tertiary);
margin-top: 4px;
font-weight: 500;
text-transform: uppercase;
letter-spacing: 0.5px;
}
/* Main Layout */
.main-content {
display: grid;
grid-template-columns: 320px 1fr;
gap: 24px;
animation: fadeIn var(--transition-slow) ease-out;
}
/* Sidebar */
.sidebar {
background: var(--bg-glass);
backdrop-filter: saturate(180%) blur(20px);
-webkit-backdrop-filter: saturate(180%) blur(20px);
border-radius: 18px;
padding: 24px;
box-shadow: var(--shadow-floating);
height: fit-content;
position: sticky;
top: 20px;
border: 1px solid var(--separator-color);
}
.add-task-form h3 {
font-size: 20px;
font-weight: 600;
margin-bottom: 20px;
color: var(--text-primary);
letter-spacing: -0.3px;
}
.form-group {
margin-bottom: 20px;
}
.form-label {
display: block;
margin-bottom: 8px;
font-weight: 500;
color: var(--text-secondary);
font-size: 13px;
text-transform: uppercase;
letter-spacing: 0.5px;
}
.form-input,
.form-select,
.form-textarea {
width: 100%;
padding: 12px 16px;
border: 1px solid var(--border-color);
border-radius: 12px;
background: var(--bg-primary);
color: var(--text-primary);
font-size: 16px;
transition: all var(--transition-fast);
font-family: inherit;
}
.form-input:focus,
.form-select:focus,
.form-textarea:focus {
outline: none;
border-color: var(--apple-blue);
box-shadow: 0 0 0 3px rgba(0, 122, 255, 0.1);
}
.form-textarea {
resize: vertical;
min-height: 80px;
line-height: 1.5;
}
/* Priority Selector */
.priority-selector {
display: flex;
gap: 8px;
}
.priority-btn {
flex: 1;
padding: 10px;
border: 1px solid var(--border-color);
border-radius: 10px;
background: var(--bg-primary);
cursor: pointer;
transition: all var(--transition-fast);
font-weight: 500;
font-size: 14px;
color: var(--text-secondary);
}
.priority-btn:hover {
transform: translateY(-1px);
box-shadow: var(--shadow-sm);
}
.priority-btn:active {
transform: translateY(0);
}
.priority-btn.active {
color: white;
border-color: transparent;
font-weight: 600;
}
.priority-btn.low.active {
background: var(--priority-low);
}
.priority-btn.medium.active {
background: var(--priority-medium);
}
.priority-btn.high.active {
background: var(--priority-high);
}
/* Category Tags */
.category-tags {
display: flex;
flex-wrap: wrap;
gap: 8px;
}
.category-tag {
padding: 8px 16px;
border-radius: 20px;
background: var(--bg-primary);
border: 1px solid var(--border-color);
cursor: pointer;
transition: all var(--transition-fast);
font-size: 14px;
font-weight: 500;
color: var(--text-secondary);
}
.category-tag:hover {
transform: translateY(-1px);
box-shadow: var(--shadow-sm);
}
.category-tag.active {
background: var(--apple-blue);
color: white;
border-color: var(--apple-blue);
}
/* Buttons */
.btn {
padding: 12px 24px;
border: none;
border-radius: 12px;
font-size: 16px;
font-weight: 600;
cursor: pointer;
transition: all var(--transition-fast);
display: inline-flex;
align-items: center;
gap: 8px;
font-family: inherit;
letter-spacing: -0.3px;
}
.btn-primary {
background: var(--apple-blue);
color: white;
width: 100%;
justify-content: center;
box-shadow: var(--shadow-sm);
}
.btn-primary:hover {
transform: translateY(-2px);
box-shadow: var(--shadow-md);
background: #0056CC;
}
.btn-primary:active {
transform: translateY(0);
box-shadow: var(--shadow-sm);
}
/* Content Area */
.content-area {
background: var(--bg-glass);
backdrop-filter: saturate(180%) blur(20px);
-webkit-backdrop-filter: saturate(180%) blur(20px);
border-radius: 18px;
padding: 24px;
box-shadow: var(--shadow-floating);
border: 1px solid var(--separator-color);
}
/* Search and Filter Bar */
.search-filter-bar {
display: flex;
gap: 16px;
margin-bottom: 24px;
flex-wrap: wrap;
}
.search-box {
flex: 1;
min-width: 200px;
position: relative;
}
.search-input {
width: 100%;
padding: 12px 16px 12px 44px;
border: 1px solid var(--border-color);
border-radius: 12px;
background: var(--bg-primary);
color: var(--text-primary);
font-size: 16px;
transition: all var(--transition-fast);
font-family: inherit;
}
.search-input:focus {
outline: none;
border-color: var(--apple-blue);
box-shadow: 0 0 0 3px rgba(0, 122, 255, 0.1);
}
.search-icon {
position: absolute;
left: 16px;
top: 50%;
transform: translateY(-50%);
color: var(--text-tertiary);
font-size: 16px;
}
.filter-tabs {
display: flex;
gap: 4px;
background: var(--bg-primary);
padding: 4px;
border-radius: 12px;
border: 1px solid var(--border-color);
}
.filter-tab {
padding: 8px 16px;
border: none;
background: transparent;
color: var(--text-secondary);
font-weight: 500;
cursor: pointer;
border-radius: 8px;
transition: all var(--transition-fast);
font-size: 14px;
font-family: inherit;
}
.filter-tab.active {
background: var(--apple-blue);
color: white;
box-shadow: var(--shadow-sm);
}
.sort-dropdown {
padding: 10px 16px;
border: 1px solid var(--border-color);
border-radius: 12px;
background: var(--bg-primary);
color: var(--text-primary);
cursor: pointer;
transition: all var(--transition-fast);
font-size: 14px;
font-family: inherit;
font-weight: 500;
}
.sort-dropdown:focus {
outline: none;
border-color: var(--apple-blue);
box-shadow: 0 0 0 3px rgba(0, 122, 255, 0.1);
}
/* Todo List */
.todo-list {
list-style: none;
margin-top: 20px;
}
.todo-item {
background: var(--bg-primary);
border: 1px solid var(--separator-color);
border-radius: 16px;
padding: 20px;
margin-bottom: 12px;
display: flex;
align-items: flex-start;
gap: 16px;
transition: all var(--transition-fast);
cursor: move;
animation: slideIn var(--transition-base) ease-out;
}
.todo-item:hover {
transform: translateX(4px);
box-shadow: var(--shadow-md);
border-color: var(--border-color);
}
.todo-item.dragging {
opacity: 0.5;
transform: rotate(1deg);
box-shadow: var(--shadow-lg);
}
.todo-item.completed {
opacity: 0.6;
}
.todo-checkbox {
width: 24px;
height: 24px;
border: 2px solid var(--border-color);
border-radius: 50%;
cursor: pointer;
transition: all var(--transition-fast);
flex-shrink: 0;
display: flex;
align-items: center;
justify-content: center;
margin-top: 2px;
background: var(--bg-primary);
}
.todo-checkbox:hover {
border-color: var(--apple-blue);
transform: scale(1.1);
}
.todo-checkbox.checked {
background: var(--apple-blue);
border-color: var(--apple-blue);
}
.todo-checkbox.checked::after {
content: '✓';
color: white;
font-weight: 600;
font-size: 14px;
}
.todo-content {
flex: 1;
}
.todo-header {
display: flex;
align-items: center;
gap: 10px;
margin-bottom: 8px;
}
.todo-title {
font-size: 16px;
font-weight: 500;
color: var(--text-primary);
transition: all var(--transition-fast);
letter-spacing: -0.2px;
line-height: 1.4;
}
.todo-item.completed .todo-title {
text-decoration: line-through;
color: var(--text-tertiary);
}
.priority-indicator {
padding: 4px 10px;
border-radius: 12px;
font-size: 11px;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.5px;
}
.priority-indicator.low {
background: var(--priority-low);
color: white;
}
.priority-indicator.medium {
background: var(--priority-medium);
color: white;
}
.priority-indicator.high {
background: var(--priority-high);
color: white;
}
.todo-description {
color: var(--text-tertiary);
font-size: 14px;
margin-bottom: 12px;
line-height: 1.5;
}
.todo-meta {
display: flex;
gap: 16px;
align-items: center;
flex-wrap: wrap;
}
.todo-date,
.todo-category {
display: flex;
align-items: center;
gap: 6px;
color: var(--text-tertiary);
font-size: 13px;
font-weight: 500;
}
.todo-actions {
display: flex;
gap: 8px;
}
.action-btn {
width: 36px;
height: 36px;
border: none;
border-radius: 50%;
background: var(--bg-secondary);
color: var(--text-secondary);
cursor: pointer;
transition: all var(--transition-fast);
display: flex;
align-items: center;
justify-content: center;
font-size: 14px;
}
.action-btn:hover {
transform: scale(1.1);
}
.action-btn:active {
transform: scale(0.95);
}
.action-btn.edit:hover {
background: var(--apple-blue);
color: white;
}
.action-btn.delete:hover {
background: var(--apple-red);
color: white;
}
/* Empty State */
.empty-state {
text-align: center;
padding: 80px 20px;
color: var(--text-tertiary);
}
.empty-state i {
font-size: 64px;
margin-bottom: 20px;
opacity: 0.3;
}
.empty-state h3 {
font-size: 20px;
margin-bottom: 8px;
font-weight: 600;
color: var(--text-secondary);
}
.empty-state p {
font-size: 14px;
}
/* Modal */
.modal {
display: none;
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.3);
backdrop-filter: blur(20px);
-webkit-backdrop-filter: blur(20px);
z-index: 1000;
animation: fadeIn var(--transition-base) ease-out;
}
.modal.active {
display: flex;
align-items: center;
justify-content: center;
}
.modal-content {
background: var(--bg-primary);
border-radius: 20px;
padding: 32px;
max-width: 500px;
width: 90%;
max-height: 90vh;
overflow-y: auto;
animation: slideUp var(--transition-base) ease-out;
box-shadow: var(--shadow-xl);
border: 1px solid var(--separator-color);
}
.modal-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 24px;
}
.modal-title {
font-size: 24px;
font-weight: 600;
color: var(--text-primary);
letter-spacing: -0.5px;
}
.modal-close {
width: 36px;
height: 36px;
border: none;
border-radius: 50%;
background: var(--bg-secondary);
color: var(--text-secondary);
cursor: pointer;
transition: all var(--transition-fast);
font-size: 16px;
display: flex;
align-items: center;
justify-content: center;
}
.modal-close:hover {
background: var(--apple-red);
color: white;
transform: rotate(90deg);
}
/* Toast */
.toast {
position: fixed;
bottom: 32px;
right: 32px;
background: var(--bg-primary);
padding: 16px 20px;
border-radius: 16px;
box-shadow: var(--shadow-xl);
display: flex;
align-items: center;
gap: 12px;
transform: translateX(400px);
transition: transform var(--transition-base) ease-out;
z-index: 2000;
border: 1px solid var(--separator-color);
backdrop-filter: saturate(180%) blur(20px);
-webkit-backdrop-filter: saturate(180%) blur(20px);
}
.toast.show {
transform: translateX(0);
}
.toast-icon {
font-size: 20px;
}
.toast.success .toast-icon {
color: var(--apple-green);
}
.toast.error .toast-icon {
color: var(--apple-red);
}
.toast.info .toast-icon {
color: var(--apple-blue);
}
.toast-message {
font-size: 14px;
font-weight: 500;
color: var(--text-primary);
}
/* Animations */
@keyframes slideDown {
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 slideUp {
from {
opacity: 0;
transform: translateY(20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
@keyframes fadeIn {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
/* Built with link */
.built-with {
color: var(--apple-blue);
font-size: 14px;
text-decoration: none;
transition: opacity var(--transition-fast);
font-weight: 500;
}
.built-with:hover {
opacity: 0.7;
text-decoration: none;
}
/* Responsive Design */
@media (max-width: 768px) {
.main-content {
grid-template-columns: 1fr;
}
.sidebar {
position: static;
}
.header-content {
flex-direction: column;
text-align: center;
}
.stats-container {
grid-template-columns: repeat(2, 1fr);
}
.search-filter-bar {
flex-direction: column;
}
.filter-tabs {
width: 100%;
justify-content: space-between;
}
.todo-item {
flex-direction: column;
align-items: flex-start;
}
.todo-actions {
width: 100%;
justify-content: flex-end;
margin-top: 12px;
}
.modal-content {
padding: 24px;
}
.toast {
right: 20px;
left: 20px;
transform: translateY(100px);
}
.toast.show {
transform: translateY(0);
}
}
/* Scrollbar Styling */
::-webkit-scrollbar {
width: 8px;
height: 8px;
}
::-webkit-scrollbar-track {
background: transparent;
}
::-webkit-scrollbar-thumb {
background: var(--border-color);
border-radius: 4px;
}
::-webkit-scrollbar-thumb:hover {
background: var(--apple-gray2);
}
/* Dark mode scrollbar */
[data-theme="dark"] ::-webkit-scrollbar-thumb {
background: var(--apple-gray3);
}
[data-theme="dark"] ::-webkit-scrollbar-thumb:hover {
background: var(--apple-gray2);
}
</style>
</head>
<body>
<div class="container">
<header>
<div class="header-content">
<div class="logo">
<i class="fas fa-check-circle"></i>
<span>TaskFlow</span>
</div>
<div class="header-actions">
<button class="theme-toggle" onclick="toggleTheme()">
<i class="fas fa-moon"></i>
</button>
<a href="https://huggingface.co/spaces/akhaliq/anycoder" target="_blank" class="built-with">
Built with anycoder
</a>
</div>
</div>
<div class="stats-container">
<div class="stat-card">
<div class="stat-number" id="totalTasks">0</div>
<div class="stat-label">Total Tasks</div>
</div>
<div class="stat-card">
<div class="stat-number" id="completedTasks">0</div>
<div class="stat-label">Completed</div>
</div>
<div class="stat-card">
<div class="stat-number" id="pendingTasks">0</div>
<div class="stat-label">Pending</div>
</div>
<div class="stat-card">
<div class="stat-number" id="completionRate">0%</div>
<div class="stat-label">Completion Rate</div>
</div>
</div>
</header>
<main class="main-content">
<aside class="sidebar">
<form class="add-task-form" id="addTaskForm">
<h3>Add New Task</h3>
<div class="form-group">
<label class="form-label">Task Title</label>
<input type="text" class="form-input" id="taskTitle" placeholder="Enter task title..." required>
</div>
<div class="form-group">
<label class="form-label">Description</label>
<textarea class="form-textarea" id="taskDescription" placeholder="Add description..."></textarea>
</div>
<div class="form-group">
<label class="form-label">Priority</label>
<div class="priority-selector">
<button type="button" class="priority-btn low" data-priority="low">Low</button>
<button type="button" class="priority-btn medium active" data-priority="medium">Medium</button>
<button type="button" class="priority-btn high" data-priority="high">High</button>
</div>
</div>
<div class="form-group">
<label class="form-label">Due Date</label>
<input type="date" class="form-input" id="taskDate">
</div>
<div class="form-group">
<label class="form-label">Category</label>
<div class="category-tags">
<span class="category-tag active" data-category="personal">Personal</span>
<span class="category-tag" data-category="work">Work</span>
<span class="category-tag" data-category="shopping">Shopping</span>
<span class="category-tag" data-category="health">Health</span>
<span class="category-tag" data-category="other">Other</span>
</div>
</div>
<button type="submit" class="btn btn-primary">
<i class="fas fa-plus"></i>
Add Task
</button>
</form>
<div style="margin-top: 24px;">
<button class="btn btn-primary" onclick="clearCompleted()" style="background: var(--apple-red);">
<i class="fas fa-trash"></i>
Clear Completed
</button>
</div>
</aside>
<section class="content-area">
<div class="search-filter-bar">
<div class="search-box">
<i class="fas fa-search search-icon"></i>
<input type="text" class="search-input" id="searchInput" placeholder="Search tasks...">
</div>
<div class="filter-tabs">
<button class="filter-tab active" data-filter="all">All</button>
<button class="filter-tab" data-filter="active">Active</button>
<button class="filter-tab" data-filter="completed">Completed</button>
</div>
<select class="sort-dropdown" id="sortDropdown">
<option value="newest">Newest First</option>
<option value="oldest">Oldest First</option>
<option value="priority">Priority</option>
<option value="date">Due Date</option>
</select>
</div>
<ul class="todo-list" id="todoList">
<!-- Tasks will be dynamically added here -->
</ul>
<div class="empty-state" id="emptyState" style="display: none;">
<i class="fas fa-clipboard-list"></i>
<h3>No tasks yet</h3>
<p>Start by adding your first task!</p>
</div>
</section>
</main>
</div>
<!-- Edit Modal -->
<div class="modal" id="editModal">
<div class="modal-content">
<div class="modal-header">
<h2 class="modal-title">Edit Task</h2>
<button class="modal-close" onclick="closeEditModal()">
<i class="fas fa-times"></i>
</button>
</div>
<form id="editTaskForm">
<input type="hidden" id="editTaskId">
<div class="form-group">
<label class="form-label">Task Title</label>
<input type="text" class="form-input" id="editTaskTitle" required>
</div>
<div class="form-group">
<label class="form-label">Description</label>
<textarea class="form-textarea" id="editTaskDescription"></textarea>
</div>
<div class="form-group">
<label class="form-label">Priority</label>
<div class="priority-selector" id="editPrioritySelector">
<button type="button" class="priority-btn low" data-priority="low">Low</button>
<button type="button" class="priority-btn medium" data-priority="medium">Medium</button>
<button type="button" class="priority-btn high" data-priority="high">High</button>
</div>
</div>
<div class="form-group">
<label class="form-label">Due Date</label>
<input type="date" class="form-input" id="editTaskDate">
</div>
<div class="form-group">
<label class="form-label">Category</label>
<div class="category-tags" id="editCategoryTags">
<span class="category-tag" data-category="personal">Personal</span>
<span class="category-tag" data-category="work">Work</span>
<span class="category-tag" data-category="shopping">Shopping</span>
<span class="category-tag" data-category="health">Health</span>
<span class="category-tag" data-category="other">Other</span>
</div>
</div>
<button type="submit" class="btn btn-primary">
<i class="fas fa-save"></i>
Save Changes
</button>
</form>
</div>
</div>
<!-- Toast Notification -->
<div class="toast" id="toast">
<i class="toast-icon fas fa-check-circle"></i>
<div class="toast-message" id="toastMessage">Task added successfully!</div>
</div>
<script>
// State Management
let todos = JSON.parse(localStorage.getItem('todos')) || [];
let currentFilter = 'all';
let currentSort = 'newest';
let selectedPriority = 'medium';
let selectedCategory = 'personal';
let editingTaskId = null;
// Initialize
document.addEventListener('DOMContentLoaded', () => {
initializeApp();
renderTodos();
updateStats();
});
function initializeApp() {
// Theme
const savedTheme = localStorage.getItem('theme') || 'light';
document.documentElement.setAttribute('data-theme', savedTheme);
updateThemeIcon(savedTheme);
// Event Listeners
document.getElementById('addTaskForm').addEventListener('submit', handleAddTask);
document.getElementById('editTaskForm').addEventListener('submit', handleEditTask);
document.getElementById('searchInput').addEventListener('input', handleSearch);
document.getElementById('sortDropdown').addEventListener('change', handleSort);
// Priority buttons
document.querySelectorAll('.priority-btn').forEach(btn => {
btn.addEventListener('click', (e) => {
if (e.target.closest('#editPrioritySelector')) {
document.querySelectorAll('#editPrioritySelector .priority-btn').forEach(b => b.classList.remove('active'));
e.target.classList.add('active');
} else {
document.querySelectorAll('.priority-selector:not(#editPrioritySelector) .priority-btn').forEach(b => b.classList.remove('active'));
e.target.classList.add('active');
selectedPriority = e.target.dataset.priority;
}
});
});
// Category tags
document.querySelectorAll('.category-tag').forEach(tag => {
tag.addEventListener('click', (e) => {
if (e.target.closest('#editCategoryTags')) {
document.querySelectorAll('#editCategoryTags .category-tag').forEach(t => t.classList.remove('active'));
e.target.classList.add('active');
} else {
document.querySelectorAll('.category-tags:not(#editCategoryTags) .category-tag').forEach(t => t.classList.remove('active'));
e.target.classList.add('active');
selectedCategory = e.target.dataset.category;
}
});
});
// Filter tabs
document.querySelectorAll('.filter-tab').forEach(tab => {
tab.addEventListener('click', (e) => {
document.querySelectorAll('.filter-tab').forEach(t => t.classList.remove('active'));
e.target.classList.add('active');
currentFilter = e.target.dataset.filter;
renderTodos();
});
});
// Drag and Drop
enableDragAndDrop();
// Keyboard shortcuts
document.addEventListener('keydown', (e) => {
if (e.ctrlKey && e.key === 'n') {
e.preventDefault();
document.getElementById('taskTitle').focus();
}
if (e.key === 'Escape') {
closeEditModal();
}
});
}
function handleAddTask(e) {
e.preventDefault();
const title = document.getElementById('taskTitle').value.trim();
const description = document.getElementById('taskDescription').value.trim();
const date = document.getElementById('taskDate').value;
if (!title) return;
const newTodo = {
id: Date.now(),
title,
description,
priority: selectedPriority,
date,
category: selectedCategory,
completed: false,
createdAt: new Date().toISOString()
};
todos.unshift(newTodo);
saveTodos();
renderTodos();
updateStats();
// Reset form
document.getElementById('addTaskForm').reset();
document.querySelectorAll('.priority-selector:not(#editPrioritySelector) .priority-btn').forEach(b => b.classList.remove('active'));
document.querySelector('.priority-selector:not(#editPrioritySelector) .priority-btn.medium').classList.add('active');
selectedPriority = 'medium';
showToast('Task added successfully!', 'success');
}
function handleEditTask(e) {
e.preventDefault();
const id = parseInt(document.getElementById('editTaskId').value);
const taskIndex = todos.findIndex(t => t.id === id);
if (taskIndex === -1) return;
todos[taskIndex] = {
...todos[taskIndex],
title: document.getElementById('editTaskTitle').value.trim(),
description: document.getElementById('editTaskDescription').value.trim(),
priority: document.querySelector('#editPrioritySelector .priority-btn.active').dataset.priority,
date: document.getElementById('editTaskDate').value,
category: document.querySelector('#editCategoryTags .category-tag.active').dataset.category
};
saveTodos();
renderTodos();
updateStats();
closeEditModal();
showToast('Task updated successfully!', 'success');
}
function toggleTask(id) {
const task = todos.find(t => t.id === id);
if (task) {
task.completed = !task.completed;
saveTodos();
renderTodos();
updateStats();
}
}
function deleteTask(id) {
todos = todos.filter(t => t.id !== id);
saveTodos();
renderTodos();
updateStats();
showToast('Task deleted!', 'info');
}
function openEditModal(id) {
const task = todos.find(t => t.id === id);
if (!task) return;
editingTaskId = id;
document.getElementById('editTaskId').value = id;
document.getElementById('editTaskTitle').value = task.title;
document.getElementById('editTaskDescription').value = task.description;
document.getElementById('editTaskDate').value = task.date;
// Set priority
document.querySelectorAll('#editPrioritySelector .priority-btn').forEach(btn => {
btn.classList.toggle('active', btn.dataset.priority === task.priority);
});
// Set category
document.querySelectorAll('#editCategoryTags .category-tag').forEach(tag => {
tag.classList.toggle('active', tag.dataset.category === task.category);
});
document.getElementById('editModal').classList.add('active');
}
function closeEditModal() {
document.getElementById('editModal').classList.remove('active');
editingTaskId = null;
}
function clearCompleted() {
const completedCount = todos.filter(t => t.completed).length;
if (completedCount === 0) {
showToast('No completed tasks to clear!', 'info');
return;
}
if (confirm(`Are you sure you want to delete ${completedCount} completed task(s)?`)) {
todos = todos.filter(t => !t.completed);
saveTodos();
renderTodos();
updateStats();
showToast(`Cleared ${completedCount} completed task(s)!`, 'success');
}
}
function handleSearch(e) {
const searchTerm = e.target.value.toLowerCase();
renderTodos(searchTerm);
}
function handleSort(e) {
currentSort = e.target.value;
renderTodos();
}
function renderTodos(searchTerm = '') {
const todoList = document.getElementById('todoList');
const emptyState = document.getElementById('emptyState');
let filteredTodos = [...todos];
// Apply filter
if (currentFilter === 'active') {
filteredTodos = filteredTodos.filter(t => !t.completed);
} else if (currentFilter === 'completed') {
filteredTodos = filteredTodos.filter(t => t.completed);
}
// Apply search
if (searchTerm) {
filteredTodos = filteredTodos.filter(t =>
t.title.toLowerCase().includes(searchTerm) ||
t.description.toLowerCase().includes(searchTerm) ||
t.category.toLowerCase().includes(searchTerm)
);
}
// Apply sorting
filteredTodos.sort((a, b) => {
switch (currentSort) {
case 'oldest':
return new Date(a.createdAt) - new Date(b.createdAt);
case 'priority':
const priorityOrder = { high: 0, medium: 1, low: 2 };
return priorityOrder[a.priority] - priorityOrder[b.priority];
case 'date':
if (!a.date) return 1;
if (!b.date) return -1;
return new Date(a.date) - new Date(b.date);
default: // newest
return new Date(b.createdAt) - new Date(a.createdAt);
}
});
// Render
if (filteredTodos.length === 0) {
todoList.innerHTML = '';
emptyState.style.display = 'block';
} else {
emptyState.style.display = 'none';
todoList.innerHTML = filteredTodos.map(todo => `
<li class="todo-item ${todo.completed ? 'completed' : ''}" draggable="true" data-id="${todo.id}">
<div class="todo-checkbox ${todo.completed ? 'checked' : ''}" onclick="toggleTask(${todo.id})"></div>
<div class="todo-content">
<div class="todo-header">
<div class="todo-title">${escapeHtml(todo.title)}</div>
<span class="priority-indicator ${todo.priority}">${todo.priority}</span>
</div>
${todo.description ? `<div class="todo-description">${escapeHtml(todo.description)}</div>` : ''}
<div class="todo-meta">
${todo.date ? `
<div class="todo-date">
<i class="far fa-calendar"></i>
${formatDate(todo.date)}
</div>
` : ''}
<div class="todo-category">
<i class="fas fa-tag"></i>
${todo.category}
</div>
</div>
</div>
<div class="todo-actions">
<button class="action-btn edit" onclick="openEditModal(${todo.id})">
<i class="fas fa-edit"></i>
</button>
<button class="action-btn delete" onclick="deleteTask(${todo.id})">
<i class="fas fa-trash"></i>
</button>
</div>
</li>
`).join('');
// Re-enable drag and drop
enableDragAndDrop();
}
}
function enableDragAndDrop() {
const items = document.querySelectorAll('.todo-item');
let draggedItem = null;
items.forEach(item => {
item.addEventListener('dragstart', (e) => {
draggedItem = item;
item.classList.add('dragging');
});
item.addEventListener('dragend', (e) => {
item.classList.remove('dragging');
});
item.addEventListener('dragover', (e) => {
e.preventDefault();
const afterElement = getDragAfterElement(e.currentTarget.parentElement, e.clientY);
if (afterElement == null) {
e.currentTarget.parentElement.appendChild(draggedItem);
} else {
e.currentTarget.parentElement.insertBefore(draggedItem, afterElement);
}
});
item.addEventListener('drop', (e) => {
e.preventDefault();
// Reorder todos array based on new DOM order
const newOrder = Array.from(document.querySelectorAll('.todo-item')).map(el =>
parseInt(el.dataset.id)
);
const reorderedTodos = [];
newOrder.forEach(id => {
const todo = todos.find(t => t