anycoder-b5183c30 / index.html
akhaliq's picture
akhaliq HF Staff
Upload folder using huggingface_hub
008747b verified
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Todo Master - Advanced Task Manager</title>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
:root {
--primary-color: #007AFF;
--secondary-color: #5856D6;
--success-color: #34C759;
--warning-color: #FF9500;
--danger-color: #FF3B30;
--background: #F2F2F7;
--surface: rgba(255, 255, 255, 0.8);
--surface-hover: rgba(255, 255, 255, 0.95);
--text-primary: #1C1C1E;
--text-secondary: #8E8E93;
--text-tertiary: #C7C7CC;
--border-color: rgba(0, 0, 0, 0.1);
--shadow-sm: 0 1px 3px rgba(0, 0, 0, 0.06);
--shadow-md: 0 4px 12px rgba(0, 0, 0, 0.08);
--shadow-lg: 0 8px 24px rgba(0, 0, 0, 0.12);
--shadow-xl: 0 16px 48px rgba(0, 0, 0, 0.16);
--blur-sm: 20px;
--blur-md: 40px;
--radius-sm: 8px;
--radius-md: 12px;
--radius-lg: 16px;
--radius-xl: 24px;
--transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
}
[data-theme="dark"] {
--background: #000000;
--surface: rgba(28, 28, 30, 0.8);
--surface-hover: rgba(44, 44, 46, 0.9);
--text-primary: #FFFFFF;
--text-secondary: #98989D;
--text-tertiary: #636366;
--border-color: rgba(255, 255, 255, 0.1);
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'SF Pro Display', 'Segoe UI', system-ui, sans-serif;
background: var(--background);
min-height: 100vh;
color: var(--text-primary);
position: relative;
overflow-x: hidden;
font-weight: 400;
line-height: 1.5;
}
body::before {
content: '';
position: fixed;
top: -50%;
left: -50%;
width: 200%;
height: 200%;
background: radial-gradient(circle at 20% 80%, rgba(0, 122, 255, 0.1) 0%, transparent 50%),
radial-gradient(circle at 80% 20%, rgba(88, 86, 214, 0.1) 0%, transparent 50%),
radial-gradient(circle at 40% 40%, rgba(52, 199, 89, 0.05) 0%, transparent 50%);
animation: float 20s ease-in-out infinite;
z-index: -1;
}
@keyframes float {
0%,
100% {
transform: translate(0, 0) rotate(0deg);
}
33% {
transform: translate(30px, -30px) rotate(120deg);
}
66% {
transform: translate(-20px, 20px) rotate(240deg);
}
}
.container {
max-width: 1200px;
margin: 0 auto;
padding: 2rem;
}
header {
text-align: center;
margin-bottom: 3rem;
animation: slideDown 0.6s cubic-bezier(0.4, 0, 0.2, 1);
}
.header-content {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 1.5rem;
}
.logo {
font-size: 2rem;
font-weight: 700;
background: linear-gradient(135deg, var(--primary-color), var(--secondary-color));
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
display: flex;
align-items: center;
gap: 0.75rem;
letter-spacing: -0.02em;
}
.logo i {
background: linear-gradient(135deg, var(--primary-color), var(--secondary-color));
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
.theme-toggle {
background: var(--surface);
backdrop-filter: blur(var(--blur-sm));
-webkit-backdrop-filter: blur(var(--blur-sm));
border: 1px solid var(--border-color);
border-radius: var(--radius-xl);
padding: 0.75rem 1.25rem;
cursor: pointer;
transition: var(--transition);
color: var(--text-primary);
font-size: 1.1rem;
box-shadow: var(--shadow-sm);
}
.theme-toggle:hover {
background: var(--surface-hover);
transform: scale(1.05);
box-shadow: var(--shadow-md);
}
.theme-toggle:active {
transform: scale(0.98);
}
.stats-container {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 1rem;
margin-bottom: 2rem;
animation: fadeIn 0.8s cubic-bezier(0.4, 0, 0.2, 1);
}
.stat-card {
background: var(--surface);
backdrop-filter: blur(var(--blur-sm));
-webkit-backdrop-filter: blur(var(--blur-sm));
border: 1px solid var(--border-color);
border-radius: var(--radius-lg);
padding: 1.5rem;
text-align: center;
transition: var(--transition);
box-shadow: var(--shadow-sm);
}
.stat-card:hover {
transform: translateY(-4px);
box-shadow: var(--shadow-lg);
background: var(--surface-hover);
}
.stat-number {
font-size: 2.5rem;
font-weight: 700;
margin-bottom: 0.5rem;
background: linear-gradient(135deg, var(--primary-color), var(--secondary-color));
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
.stat-label {
font-size: 0.875rem;
color: var(--text-secondary);
font-weight: 500;
letter-spacing: 0.01em;
}
.main-content {
display: grid;
grid-template-columns: 1fr 300px;
gap: 2rem;
animation: fadeIn 1s cubic-bezier(0.4, 0, 0.2, 1);
}
.todo-section {
background: var(--surface);
backdrop-filter: blur(var(--blur-md));
-webkit-backdrop-filter: blur(var(--blur-md));
border: 1px solid var(--border-color);
border-radius: var(--radius-xl);
padding: 2rem;
box-shadow: var(--shadow-lg);
}
.add-todo-form {
display: flex;
gap: 1rem;
margin-bottom: 2rem;
}
.todo-input-group {
flex: 1;
display: flex;
gap: 0.75rem;
}
.todo-input {
flex: 1;
background: var(--surface);
border: 1px solid var(--border-color);
border-radius: var(--radius-lg);
padding: 0.875rem 1.25rem;
font-size: 1rem;
color: var(--text-primary);
transition: var(--transition);
font-weight: 500;
}
.todo-input::placeholder {
color: var(--text-tertiary);
}
.todo-input:focus {
outline: none;
background: var(--surface-hover);
border-color: var(--primary-color);
box-shadow: 0 0 0 3px rgba(0, 122, 255, 0.1);
}
.priority-select,
.category-select {
background: var(--surface);
border: 1px solid var(--border-color);
border-radius: var(--radius-lg);
padding: 0.75rem 1rem;
color: var(--text-primary);
cursor: pointer;
transition: var(--transition);
font-weight: 500;
min-width: 120px;
}
.priority-select:focus,
.category-select:focus {
outline: none;
background: var(--surface-hover);
border-color: var(--primary-color);
box-shadow: 0 0 0 3px rgba(0, 122, 255, 0.1);
}
.priority-select option,
.category-select option {
background: var(--surface);
color: var(--text-primary);
}
.add-btn {
background: linear-gradient(135deg, var(--primary-color), var(--secondary-color));
border: none;
border-radius: var(--radius-lg);
padding: 0.875rem 1.75rem;
color: white;
font-weight: 600;
cursor: pointer;
transition: var(--transition);
display: flex;
align-items: center;
gap: 0.5rem;
box-shadow: var(--shadow-md);
font-size: 0.9375rem;
letter-spacing: 0.01em;
}
.add-btn:hover {
transform: translateY(-2px);
box-shadow: var(--shadow-lg);
}
.add-btn:active {
transform: translateY(0);
box-shadow: var(--shadow-sm);
}
.search-bar {
background: var(--surface);
border: 1px solid var(--border-color);
border-radius: var(--radius-lg);
padding: 0.875rem 1.25rem;
margin-bottom: 1.5rem;
display: flex;
align-items: center;
gap: 1rem;
color: var(--text-secondary);
transition: var(--transition);
}
.search-bar:focus-within {
border-color: var(--primary-color);
box-shadow: 0 0 0 3px rgba(0, 122, 255, 0.1);
}
.search-input {
flex: 1;
background: transparent;
border: none;
color: var(--text-primary);
font-size: 1rem;
font-weight: 500;
}
.search-input::placeholder {
color: var(--text-tertiary);
}
.search-input:focus {
outline: none;
}
.filter-tabs {
display: flex;
gap: 0.5rem;
margin-bottom: 2rem;
flex-wrap: wrap;
}
.filter-tab {
background: var(--surface);
border: 1px solid var(--border-color);
border-radius: var(--radius-md);
padding: 0.625rem 1.25rem;
color: var(--text-secondary);
cursor: pointer;
transition: var(--transition);
font-size: 0.875rem;
font-weight: 500;
letter-spacing: 0.01em;
}
.filter-tab:hover {
background: var(--surface-hover);
color: var(--text-primary);
}
.filter-tab.active {
background: linear-gradient(135deg, var(--primary-color), var(--secondary-color));
color: white;
border-color: transparent;
box-shadow: var(--shadow-md);
}
.todo-list {
max-height: 500px;
overflow-y: auto;
padding-right: 0.5rem;
}
.todo-list::-webkit-scrollbar {
width: 6px;
}
.todo-list::-webkit-scrollbar-track {
background: transparent;
}
.todo-list::-webkit-scrollbar-thumb {
background: var(--text-tertiary);
border-radius: var(--radius-sm);
transition: var(--transition);
}
.todo-list::-webkit-scrollbar-thumb:hover {
background: var(--text-secondary);
}
.todo-item {
background: var(--surface);
border: 1px solid var(--border-color);
border-radius: var(--radius-lg);
padding: 1.25rem;
margin-bottom: 0.75rem;
display: flex;
align-items: center;
gap: 1rem;
transition: var(--transition);
cursor: move;
animation: slideIn 0.3s cubic-bezier(0.4, 0, 0.2, 1);
box-shadow: var(--shadow-sm);
}
.todo-item:hover {
background: var(--surface-hover);
transform: translateX(4px);
box-shadow: var(--shadow-md);
}
.todo-item.dragging {
opacity: 0.5;
transform: rotate(2deg) scale(0.98);
}
.todo-item.completed {
opacity: 0.6;
}
.todo-item.completed .todo-text {
text-decoration: line-through;
color: var(--text-tertiary);
}
.todo-checkbox {
width: 24px;
height: 24px;
border: 2px solid var(--text-tertiary);
border-radius: 50%;
cursor: pointer;
transition: var(--transition);
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
position: relative;
}
.todo-checkbox:hover {
border-color: var(--primary-color);
background: rgba(0, 122, 255, 0.05);
}
.todo-checkbox.checked {
background: linear-gradient(135deg, var(--success-color), #30D158);
border-color: transparent;
animation: checkPulse 0.3s ease-out;
}
@keyframes checkPulse {
0% {
transform: scale(1);
}
50% {
transform: scale(1.2);
}
100% {
transform: scale(1);
}
}
.todo-checkbox.checked::after {
content: '✓';
color: white;
font-weight: 600;
font-size: 14px;
}
.todo-content {
flex: 1;
}
.todo-text {
color: var(--text-primary);
font-size: 1rem;
margin-bottom: 0.5rem;
word-break: break-word;
font-weight: 500;
}
.todo-meta {
display: flex;
gap: 0.75rem;
align-items: center;
flex-wrap: wrap;
}
.todo-date,
.todo-category {
font-size: 0.75rem;
color: var(--text-secondary);
display: flex;
align-items: center;
gap: 0.25rem;
font-weight: 500;
}
.priority-badge {
padding: 0.25rem 0.625rem;
border-radius: var(--radius-sm);
font-size: 0.6875rem;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.05em;
}
.priority-high {
background: linear-gradient(135deg, var(--danger-color), #FF6961);
color: white;
}
.priority-medium {
background: linear-gradient(135deg, var(--warning-color), #FFB347);
color: white;
}
.priority-low {
background: linear-gradient(135deg, var(--text-tertiary), var(--text-secondary));
color: white;
}
.todo-actions {
display: flex;
gap: 0.5rem;
}
.action-btn {
background: var(--surface);
border: 1px solid var(--border-color);
border-radius: var(--radius-md);
padding: 0.5rem;
color: var(--text-secondary);
cursor: pointer;
transition: var(--transition);
width: 32px;
height: 32px;
display: flex;
align-items: center;
justify-content: center;
}
.action-btn:hover {
background: var(--surface-hover);
color: var(--text-primary);
transform: scale(1.1);
}
.action-btn.delete:hover {
background: rgba(255, 59, 48, 0.1);
color: var(--danger-color);
border-color: var(--danger-color);
}
.sidebar {
display: flex;
flex-direction: column;
gap: 1.5rem;
}
.sidebar-card {
background: var(--surface);
backdrop-filter: blur(var(--blur-sm));
-webkit-backdrop-filter: blur(var(--blur-sm));
border: 1px solid var(--border-color);
border-radius: var(--radius-lg);
padding: 1.5rem;
box-shadow: var(--shadow-sm);
}
.sidebar-title {
font-size: 1.125rem;
font-weight: 600;
margin-bottom: 1rem;
color: var(--text-primary);
letter-spacing: -0.01em;
}
.progress-bar {
background: var(--background);
border-radius: var(--radius-sm);
height: 8px;
overflow: hidden;
margin-bottom: 1rem;
}
.progress-fill {
height: 100%;
background: linear-gradient(135deg, var(--success-color), #30D158);
transition: width 0.5s cubic-bezier(0.4, 0, 0.2, 1);
}
.category-list {
display: flex;
flex-direction: column;
gap: 0.625rem;
}
.category-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 0.75rem;
border-radius: var(--radius-md);
transition: var(--transition);
}
.category-item:hover {
background: var(--background);
}
.category-item span:first-child {
display: flex;
align-items: center;
gap: 0.5rem;
color: var(--text-secondary);
font-weight: 500;
}
.category-item span:last-child {
color: var(--text-primary);
font-weight: 600;
}
.clear-btn {
background: linear-gradient(135deg, var(--danger-color), #FF6961);
border: none;
border-radius: var(--radius-lg);
padding: 0.875rem 1.5rem;
color: white;
font-weight: 600;
cursor: pointer;
transition: var(--transition);
width: 100%;
margin-top: 1rem;
box-shadow: var(--shadow-md);
font-size: 0.9375rem;
letter-spacing: 0.01em;
}
.clear-btn:hover {
transform: translateY(-2px);
box-shadow: var(--shadow-lg);
}
.clear-btn:active {
transform: translateY(0);
box-shadow: var(--shadow-sm);
}
.empty-state {
text-align: center;
padding: 3rem;
color: var(--text-tertiary);
}
.empty-state i {
font-size: 3.5rem;
margin-bottom: 1rem;
opacity: 0.3;
}
.empty-state p {
font-weight: 500;
}
.edit-input {
background: var(--surface-hover);
border: 1px solid var(--primary-color);
border-radius: var(--radius-md);
padding: 0.5rem;
color: var(--text-primary);
font-size: 1rem;
width: 100%;
font-weight: 500;
box-shadow: 0 0 0 3px rgba(0, 122, 255, 0.1);
}
.edit-input:focus {
outline: none;
background: var(--surface);
}
@keyframes slideDown {
from {
opacity: 0;
transform: translateY(-20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
@keyframes fadeIn {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
@keyframes slideIn {
from {
opacity: 0;
transform: translateX(-20px);
}
to {
opacity: 1;
transform: translateX(0);
}
}
@media (max-width: 768px) {
.container {
padding: 1rem;
}
.main-content {
grid-template-columns: 1fr;
}
.stats-container {
grid-template-columns: repeat(2, 1fr);
}
.add-todo-form {
flex-direction: column;
}
.todo-input-group {
flex-direction: column;
}
.filter-tabs {
justify-content: center;
}
.logo {
font-size: 1.5rem;
}
}
@media (max-width: 480px) {
.stats-container {
grid-template-columns: 1fr;
}
}
.toast {
position: fixed;
bottom: 2rem;
right: 2rem;
background: var(--surface);
backdrop-filter: blur(var(--blur-md));
-webkit-backdrop-filter: blur(var(--blur-md));
color: var(--text-primary);
padding: 1rem 1.5rem;
border-radius: var(--radius-lg);
box-shadow: var(--shadow-xl);
transform: translateX(400px);
transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1);
z-index: 1000;
display: flex;
align-items: center;
gap: 1rem;
border: 1px solid var(--border-color);
}
.toast.show {
transform: translateX(0);
}
.toast i {
font-size: 1.25rem;
color: var(--success-color);
}
.anycoder-link {
color: var(--text-secondary);
text-decoration: none;
font-size: 0.875rem;
opacity: 0.8;
transition: var(--transition);
font-weight: 500;
}
.anycoder-link:hover {
color: var(--primary-color);
opacity: 1;
}
#dueDateInput {
min-width: auto;
font-weight: 500;
}
.quick-stats-item {
display: flex;
justify-content: space-between;
padding: 0.5rem 0;
border-bottom: 1px solid var(--border-color);
}
.quick-stats-item:last-child {
border-bottom: none;
}
.quick-stats-label {
color: var(--text-secondary);
font-size: 0.875rem;
font-weight: 500;
}
.quick-stats-value {
color: var(--text-primary);
font-weight: 600;
font-size: 0.9375rem;
}
</style>
</head>
<body>
<div class="container">
<header>
<div class="header-content">
<div class="logo">
<i class="fas fa-check-circle"></i>
Todo Master
</div>
<button class="theme-toggle" onclick="toggleTheme()">
<i class="fas fa-moon" id="themeIcon"></i>
</button>
</div>
<a href="https://huggingface.co/spaces/akhaliq/anycoder" class="anycoder-link">
Built with anycoder
</a>
</header>
<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>
<div class="main-content">
<div class="todo-section">
<form class="add-todo-form" onsubmit="addTodo(event)">
<div class="todo-input-group">
<input type="text" class="todo-input" id="todoInput" placeholder="What needs to be done?" required>
<select class="priority-select" id="prioritySelect">
<option value="low">Low</option>
<option value="medium" selected>Medium</option>
<option value="high">High</option>
</select>
<select class="category-select" id="categorySelect">
<option value="personal">Personal</option>
<option value="work">Work</option>
<option value="shopping">Shopping</option>
<option value="health">Health</option>
<option value="other">Other</option>
</select>
<input type="date" class="priority-select" id="dueDateInput" style="width: auto;">
</div>
<button type="submit" class="add-btn">
<i class="fas fa-plus"></i>
Add Task
</button>
</form>
<div class="search-bar">
<i class="fas fa-search"></i>
<input type="text" class="search-input" id="searchInput" placeholder="Search tasks..." oninput="searchTodos()">
</div>
<div class="filter-tabs">
<button class="filter-tab active" onclick="filterTodos('all')">All Tasks</button>
<button class="filter-tab" onclick="filterTodos('active')">Active</button>
<button class="filter-tab" onclick="filterTodos('completed')">Completed</button>
<button class="filter-tab" onclick="filterTodos('today')">Today</button>
<button class="filter-tab" onclick="filterTodos('high')">High Priority</button>
</div>
<div class="todo-list" id="todoList">
<div class="empty-state">
<i class="fas fa-clipboard-list"></i>
<p>No tasks yet. Add your first task above!</p>
</div>
</div>
<button class="clear-btn" onclick="clearCompleted()" style="display: none;" id="clearBtn">
<i class="fas fa-trash"></i> Clear Completed
</button>
</div>
<div class="sidebar">
<div class="sidebar-card">
<h3 class="sidebar-title">Progress Overview</h3>
<div class="progress-bar">
<div class="progress-fill" id="progressFill" style="width: 0%"></div>
</div>
<p style="font-size: 0.9rem; color: var(--text-secondary); font-weight: 500;">
<span id="progressText">0</span>% of tasks completed
</p>
</div>
<div class="sidebar-card">
<h3 class="sidebar-title">Categories</h3>
<div class="category-list" id="categoryList">
<div class="category-item">
<span><i class="fas fa-user"></i> Personal</span>
<span id="personalCount">0</span>
</div>
<div class="category-item">
<span><i class="fas fa-briefcase"></i> Work</span>
<span id="workCount">0</span>
</div>
<div class="category-item">
<span><i class="fas fa-shopping-cart"></i> Shopping</span>
<span id="shoppingCount">0</span>
</div>
<div class="category-item">
<span><i class="fas fa-heart"></i> Health</span>
<span id="healthCount">0</span>
</div>
<div class="category-item">
<span><i class="fas fa-folder"></i> Other</span>
<span id="otherCount">0</span>
</div>
</div>
</div>
<div class="sidebar-card">
<h3 class="sidebar-title">Quick Stats</h3>
<div style="display: flex; flex-direction: column; gap: 0;">
<div class="quick-stats-item">
<span class="quick-stats-label">Tasks due today</span>
<span class="quick-stats-value" id="todayCount">0</span>
</div>
<div class="quick-stats-item">
<span class="quick-stats-label">Overdue tasks</span>
<span class="quick-stats-value" id="overdueCount" style="color: var(--danger-color);">0</span>
</div>
<div class="quick-stats-item">
<span class="quick-stats-label">High priority</span>
<span class="quick-stats-value" id="highPriorityCount" style="color: var(--danger-color);">0</span>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="toast" id="toast">
<i class="fas fa-check-circle"></i>
<span id="toastMessage">Task added successfully!</span>
</div>
<script>
let todos = JSON.parse(localStorage.getItem('todos')) || [];
let currentFilter = 'all';
let draggedItem = null;
// Initialize
document.addEventListener('DOMContentLoaded', () => {
renderTodos();
updateStats();
// Set today's date as default
const today = new Date().toISOString().split('T')[0];
document.getElementById('dueDateInput').value = today;
// Check for saved theme preference
if (localStorage.getItem('theme') === 'dark') {
document.body.setAttribute('data-theme', 'dark');
document.getElementById('themeIcon').className = 'fas fa-sun';
}
});
function addTodo(event) {
event.preventDefault();
const input = document.getElementById('todoInput');
const priority = document.getElementById('prioritySelect').value;
const category = document.getElementById('categorySelect').value;
const dueDate = document.getElementById('dueDateInput').value;
const todo = {
id: Date.now(),
text: input.value,
completed: false,
priority: priority,
category: category,
dueDate: dueDate,
createdAt: new Date().toISOString()
};
todos.unshift(todo);
saveTodos();
renderTodos();
updateStats();
input.value = '';
showToast('Task added successfully!');
// Reset date to today
const today = new Date().toISOString().split('T')[0];
document.getElementById('dueDateInput').value = today;
}
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 active');
}
}
function deleteTodo(id) {
todos = todos.filter(t => t.id !== id);
saveTodos();
renderTodos();
updateStats();
showToast('Task deleted');
}
function editTodo(id) {
const todo = todos.find(t => t.id === id);
const todoElement = document.querySelector(`[data-id="${id}"]`);
const textElement = todoElement.querySelector('.todo-text');
const input = document.createElement('input');
input.type = 'text';
input.className = 'edit-input';
input.value = todo.text;
textElement.replaceWith(input);
input.focus();
input.select();
const saveEdit = () => {
if (input.value.trim()) {
todo.text = input.value;
saveTodos();
renderTodos();
showToast('Task updated');
} else {
renderTodos();
}
};
input.addEventListener('blur', saveEdit);
input.addEventListener('keypress', (e) => {
if (e.key === 'Enter') {
saveEdit();
}
});
}
function filterTodos(filter) {
currentFilter = filter;
// Update active tab
document.querySelectorAll('.filter-tab').forEach(tab => {
tab.classList.remove('active');
});
event.target.classList.add('active');
renderTodos();
}
function searchTodos() {
const searchTerm = document.getElementById('searchInput').value.toLowerCase();
renderTodos(searchTerm);
}
function clearCompleted() {
const completedCount = todos.filter(t => t.completed).length;
todos = todos.filter(t => !t.completed);
saveTodos();
renderTodos();
updateStats();
showToast(`Cleared ${completedCount} completed tasks`);
}
function renderTodos(searchTerm = '') {
const todoList = document.getElementById('todoList');
let filteredTodos = [...todos];
// Apply filter
if (currentFilter === 'active') {
filteredTodos = filteredTodos.filter(t => !t.completed);
} else if (currentFilter === 'completed') {
filteredTodos = filteredTodos.filter(t => t.completed);
} else if (currentFilter === 'today') {
const today = new Date().toISOString().split('T')[0];
filteredTodos = filteredTodos.filter(t => t.dueDate === today);
} else if (currentFilter === 'high') {
filteredTodos = filteredTodos.filter(t => t.priority === 'high');
}
// Apply search
if (searchTerm) {
filteredTodos = filteredTodos.filter(t =>
t.text.toLowerCase().includes(searchTerm) ||
t.category.toLowerCase().includes(searchTerm)
);
}
// Sort by priority and date
filteredTodos.sort((a, b) => {
const priorityOrder = { high: 0, medium: 1, low: 2 };
if (priorityOrder[a.priority] !== priorityOrder[b.priority]) {
return priorityOrder[a.priority] - priorityOrder[b.priority];
}
return new Date(a.dueDate) - new Date(b.dueDate);
});
if (filteredTodos.length === 0) {
todoList.innerHTML = `
<div class="empty-state">
<i class="fas fa-clipboard-list"></i>
<p>No tasks found</p>
</div>
`;
} else {
todoList.innerHTML = filteredTodos.map(todo => {
const isOverdue = new Date(todo.dueDate) < new Date() && !todo.completed;
const formattedDate = formatDate(todo.dueDate);
return `
<div class="todo-item ${todo.completed ? 'completed' : ''}"
data-id="${todo.id}"
draggable="true"
ondragstart="handleDragStart(event)"
ondragover="handleDragOver(event)"
ondrop="handleDrop(event)"
ondragend="handleDragEnd(event)">
<div class="todo-checkbox ${todo.completed ? 'checked' : ''}"
onclick="toggleTodo(${todo.id})"></div>
<div class="todo-content">
<div class="todo-text">${escapeHtml(todo.text)}</div>
<div class="todo-meta">
<span class="priority-badge priority-${todo.priority}">
${todo.priority}
</span>
<span class="todo-category">
<i class="fas fa-${getCategoryIcon(todo.category)}"></i>
${todo.category}
</span>
<span class="todo-date" style="${isOverdue ? 'color: var(--danger-color);' : ''}">
<i class="fas fa-calendar"></i>
${formattedDate}
</span>
</div>
</div>
<div class="todo-actions">
<button class="action-btn" onclick="editTodo(${todo.id})">
<i class="fas fa-edit"></i>
</button>
<button class="action-btn delete" onclick="deleteTodo(${todo.id})">
<i class="fas fa-trash"></i>
</button>
</div>
</div>
`;
}).join('');
}
// Show/hide clear button
const clearBtn = document.getElementById('clearBtn');
const hasCompleted = todos.some(t => t.completed);
clearBtn.style.display = hasCompleted ? 'block' : 'none';
}
function updateStats() {
const total = todos.length;
const completed = todos.filter(t => t.completed).length;
const pending = total - completed;
const rate = total > 0 ? Math.round((completed / total) * 100) : 0;
document.getElementById('totalTasks').textContent = total;
document.getElementById('completedTasks').textContent = completed;
document.getElementById('pendingTasks').textContent = pending;
document.getElementById('completionRate').textContent = rate + '%';
// Update progress bar
document.getElementById('progressFill').style.width = rate + '%';
document.getElementById('progressText').textContent = rate;
// Update categories
const categories = ['personal', 'work', 'shopping', 'health', 'other'];
categories.forEach(cat => {
const count = todos.filter(t => t.category === cat && !t.completed).length;
const element = document.getElementById(cat + 'Count');
if (element) element.textContent = count;
});
// Update quick stats
const today = new Date().toISOString().split('T')[0];
const todayTasks = todos.filter(t => t.dueDate === today && !t.completed).length;
const overdueTasks = todos.filter(t => new Date(t.dueDate) < new Date() && !t.completed).length;
const highPriorityTasks = todos.filter(t => t.priority === 'high' && !t.completed).length;
document.getElementById('todayCount').textContent = todayTasks;
document.getElementById('overdueCount').textContent = overdueTasks;
document.getElementById('highPriorityCount').textContent = highPriorityTasks;
}
function handleDragStart(e) {
draggedItem = this;
this.classList.add('dragging');
e.dataTransfer.effectAllowed = 'move';
e.dataTransfer.setData('text/html', this.innerHTML);
}
function handleDragOver(e) {
if (e.preventDefault) {
e.preventDefault();
}
e.dataTransfer.dropEffect = 'move';
const afterElement = getDragAfterElement(document.getElementById('todoList'), e.clientY);
if (afterElement == null) {
document.getElementById('todoList').appendChild(draggedItem);
} else {
document.getElementById('todoList').insertBefore(draggedItem, afterElement);
}
return false;
}
function handleDrop(e) {
if (e.stopPropagation) {
e.stopPropagation();
}
// Reorder todos array based on new order
const items = [...document.querySelectorAll('.todo-item')];
const newOrder = items.map(item => parseInt(item.dataset.id));
const reorderedTodos = [];
newOrder.forEach(id => {
const todo = todos.find(t => t.id === id);
if (todo) reorderedTodos.push(todo);
});
todos = reorderedTodos;
saveTodos();
return false;
}
function handleDragEnd(e) {
this.classList.remove('dragging');
}
function getDragAfterElement(container, y) {
const draggableElements = [...container.querySelectorAll('.todo-item:not(.dragging)')];
return draggableElements.reduce((closest, child) => {
const box = child.getBoundingClientRect();
const offset = y - box.top - box.height / 2;
if (offset < 0 && offset > closest.offset) {
return { offset: offset, element: child };
} else {
return closest;
}
}, { offset: Number.NEGATIVE_INFINITY }).element;
}
function toggleTheme() {
const body = document.body;
const icon = document.getElementById('themeIcon');
if (body.hasAttribute('data-theme')) {
body.removeAttribute('data-theme');
icon.className = 'fas fa-moon';
localStorage.setItem('theme', 'light');
} else {
body.setAttribute('data-theme', 'dark');
icon.className = 'fas fa-sun';
localStorage.setItem('theme', 'dark');
}
}
function formatDate(dateString) {
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' });
}
}
function getCategoryIcon(category) {
const icons = {
personal: 'user',
work: 'briefcase',
shopping: 'shopping-cart',
health: 'heart',
other: 'folder'
};
return icons[category] || 'folder';
}
function escapeHtml(text) {
const map = {
'&': '&amp;',
'<': '&lt;',
'>': '&gt;',
'"': '&quot;',
"'": '&#039;'
};
return text.replace(/[&<>"']/g, m => map[m]);
}
function showToast(message) {
const toast = document.getElementById('toast');
const toastMessage = document.getElementById('toastMessage');
toastMessage.textContent = message;
toast.classList.add('show');
setTimeout(() => {
toast.classList.remove('show');
}, 3000);
}
function saveTodos() {
localStorage.setItem('todos', JSON.stringify(todos));
}
</script>
</body>
</html>