anycoder-aba68275 / index.html
akhaliq's picture
akhaliq HF Staff
Upload folder using huggingface_hub
fc09957 verified
raw
history blame
37.3 kB
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>FlowTask - Advanced Todo Manager</title>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<style>
:root {
--primary: #667eea;
--primary-dark: #5a67d8;
--secondary: #48bb78;
--danger: #f56565;
--warning: #ed8936;
--info: #4299e1;
--dark: #2d3748;
--light: #f7fafc;
--gray: #718096;
--border: #e2e8f0;
--shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1);
--shadow-lg: 0 20px 25px -5px rgba(0, 0, 0, 0.1);
--radius: 12px;
--transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
}
[data-theme="dark"] {
--primary: #7c3aed;
--primary-dark: #6d28d9;
--secondary: #10b981;
--dark: #f9fafb;
--light: #111827;
--gray: #9ca3af;
--border: #374151;
--shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.5);
--shadow-lg: 0 20px 25px -5px rgba(0, 0, 0, 0.5);
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, sans-serif;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
min-height: 100vh;
color: var(--dark);
transition: var(--transition);
position: relative;
overflow-x: hidden;
}
[data-theme="dark"] body {
background: linear-gradient(135deg, #1e293b 0%, #334155 100%);
}
.animated-bg {
position: fixed;
width: 100%;
height: 100%;
top: 0;
left: 0;
z-index: -1;
opacity: 0.3;
}
.animated-bg::before,
.animated-bg::after {
content: '';
position: absolute;
width: 400px;
height: 400px;
border-radius: 50%;
background: radial-gradient(circle, rgba(255, 255, 255, 0.1) 0%, transparent 70%);
animation: float 20s infinite ease-in-out;
}
.animated-bg::before {
top: -200px;
right: -200px;
}
.animated-bg::after {
bottom: -200px;
left: -200px;
animation-delay: 10s;
}
@keyframes float {
0%,
100% {
transform: translate(0, 0) scale(1);
}
25% {
transform: translate(30px, -30px) scale(1.1);
}
50% {
transform: translate(-20px, 20px) scale(0.9);
}
75% {
transform: translate(-30px, -20px) scale(1.05);
}
}
.container {
max-width: 1200px;
margin: 0 auto;
padding: 20px;
}
header {
text-align: center;
padding: 30px 0;
color: white;
position: relative;
}
h1 {
font-size: 3rem;
font-weight: 800;
margin-bottom: 10px;
text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.2);
animation: slideDown 0.5s ease;
}
.subtitle {
font-size: 1.1rem;
opacity: 0.9;
margin-bottom: 20px;
}
.header-actions {
display: flex;
justify-content: center;
gap: 15px;
margin-top: 20px;
}
.theme-toggle {
background: rgba(255, 255, 255, 0.2);
border: 2px solid rgba(255, 255, 255, 0.3);
color: white;
padding: 10px 20px;
border-radius: 50px;
cursor: pointer;
transition: var(--transition);
font-size: 1rem;
backdrop-filter: blur(10px);
}
.theme-toggle:hover {
background: rgba(255, 255, 255, 0.3);
transform: translateY(-2px);
}
.main-content {
display: grid;
grid-template-columns: 1fr 350px;
gap: 30px;
margin-top: 30px;
}
@media (max-width: 968px) {
.main-content {
grid-template-columns: 1fr;
}
}
.todo-section {
background: white;
border-radius: var(--radius);
padding: 30px;
box-shadow: var(--shadow-lg);
animation: slideUp 0.5s ease;
}
[data-theme="dark"] .todo-section {
background: #1f2937;
color: var(--dark);
}
.input-group {
display: flex;
gap: 10px;
margin-bottom: 25px;
}
.todo-input {
flex: 1;
padding: 15px 20px;
border: 2px solid var(--border);
border-radius: var(--radius);
font-size: 1rem;
transition: var(--transition);
background: var(--light);
color: var(--dark);
}
.todo-input:focus {
outline: none;
border-color: var(--primary);
box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1);
}
.input-options {
display: flex;
gap: 10px;
margin-bottom: 20px;
flex-wrap: wrap;
}
.select-input,
.date-input {
padding: 10px 15px;
border: 2px solid var(--border);
border-radius: 8px;
background: var(--light);
color: var(--dark);
cursor: pointer;
transition: var(--transition);
}
.select-input:focus,
.date-input:focus {
outline: none;
border-color: var(--primary);
}
.btn {
padding: 15px 25px;
border: none;
border-radius: var(--radius);
font-size: 1rem;
cursor: pointer;
transition: var(--transition);
font-weight: 600;
display: inline-flex;
align-items: center;
gap: 8px;
}
.btn-primary {
background: linear-gradient(135deg, var(--primary) 0%, var(--primary-dark) 100%);
color: white;
}
.btn-primary:hover {
transform: translateY(-2px);
box-shadow: var(--shadow);
}
.filter-tabs {
display: flex;
gap: 10px;
margin-bottom: 25px;
border-bottom: 2px solid var(--border);
padding-bottom: 10px;
}
.filter-tab {
padding: 10px 20px;
background: transparent;
border: none;
color: var(--gray);
cursor: pointer;
transition: var(--transition);
font-weight: 500;
position: relative;
}
.filter-tab.active {
color: var(--primary);
}
.filter-tab.active::after {
content: '';
position: absolute;
bottom: -12px;
left: 0;
right: 0;
height: 3px;
background: var(--primary);
border-radius: 3px;
}
.search-box {
position: relative;
margin-bottom: 20px;
}
.search-input {
width: 100%;
padding: 12px 45px 12px 20px;
border: 2px solid var(--border);
border-radius: var(--radius);
background: var(--light);
color: var(--dark);
}
.search-icon {
position: absolute;
right: 15px;
top: 50%;
transform: translateY(-50%);
color: var(--gray);
}
.todo-list {
list-style: none;
max-height: 500px;
overflow-y: auto;
padding-right: 10px;
}
.todo-list::-webkit-scrollbar {
width: 8px;
}
.todo-list::-webkit-scrollbar-track {
background: var(--border);
border-radius: 10px;
}
.todo-list::-webkit-scrollbar-thumb {
background: var(--primary);
border-radius: 10px;
}
.todo-item {
background: var(--light);
border: 2px solid var(--border);
border-radius: var(--radius);
padding: 15px;
margin-bottom: 12px;
display: flex;
align-items: center;
gap: 15px;
transition: var(--transition);
cursor: move;
position: relative;
animation: slideIn 0.3s ease;
}
.todo-item:hover {
transform: translateX(5px);
box-shadow: var(--shadow);
}
.todo-item.dragging {
opacity: 0.5;
transform: rotate(2deg);
}
.todo-item.completed {
opacity: 0.7;
}
.todo-item.completed .todo-text {
text-decoration: line-through;
color: var(--gray);
}
.todo-checkbox {
width: 24px;
height: 24px;
cursor: pointer;
accent-color: var(--primary);
}
.todo-content {
flex: 1;
}
.todo-text {
font-size: 1rem;
margin-bottom: 5px;
color: var(--dark);
}
.todo-meta {
display: flex;
gap: 10px;
align-items: center;
font-size: 0.85rem;
color: var(--gray);
}
.priority-badge {
padding: 2px 8px;
border-radius: 12px;
font-size: 0.75rem;
font-weight: 600;
text-transform: uppercase;
}
.priority-high {
background: rgba(245, 101, 101, 0.2);
color: var(--danger);
}
.priority-medium {
background: rgba(237, 137, 54, 0.2);
color: var(--warning);
}
.priority-low {
background: rgba(66, 153, 225, 0.2);
color: var(--info);
}
.category-tag {
padding: 2px 8px;
border-radius: 12px;
font-size: 0.75rem;
background: rgba(102, 126, 234, 0.2);
color: var(--primary);
}
.todo-actions {
display: flex;
gap: 8px;
}
.action-btn {
width: 32px;
height: 32px;
border: none;
background: transparent;
color: var(--gray);
cursor: pointer;
border-radius: 8px;
transition: var(--transition);
display: flex;
align-items: center;
justify-content: center;
}
.action-btn:hover {
background: var(--border);
color: var(--primary);
}
.action-btn.delete:hover {
color: var(--danger);
}
.sidebar {
display: flex;
flex-direction: column;
gap: 20px;
}
.stats-card {
background: white;
border-radius: var(--radius);
padding: 25px;
box-shadow: var(--shadow-lg);
animation: slideUp 0.5s ease 0.1s both;
}
[data-theme="dark"] .stats-card {
background: #1f2937;
}
.stats-title {
font-size: 1.2rem;
font-weight: 700;
margin-bottom: 20px;
color: var(--dark);
}
.stat-item {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 15px;
padding-bottom: 15px;
border-bottom: 1px solid var(--border);
}
.stat-item:last-child {
border-bottom: none;
margin-bottom: 0;
padding-bottom: 0;
}
.stat-label {
color: var(--gray);
font-size: 0.9rem;
}
.stat-value {
font-size: 1.5rem;
font-weight: 700;
color: var(--primary);
}
.progress-bar {
width: 100%;
height: 8px;
background: var(--border);
border-radius: 10px;
overflow: hidden;
margin-top: 10px;
}
.progress-fill {
height: 100%;
background: linear-gradient(90deg, var(--primary) 0%, var(--secondary) 100%);
border-radius: 10px;
transition: width 0.5s ease;
}
.clear-completed {
background: linear-gradient(135deg, var(--danger) 0%, #e53e3e 100%);
color: white;
width: 100%;
justify-content: center;
}
.clear-completed:hover {
transform: translateY(-2px);
box-shadow: var(--shadow);
}
.empty-state {
text-align: center;
padding: 60px 20px;
color: var(--gray);
}
.empty-icon {
font-size: 4rem;
margin-bottom: 20px;
opacity: 0.5;
}
.toast {
position: fixed;
bottom: 30px;
right: 30px;
background: white;
padding: 15px 20px;
border-radius: var(--radius);
box-shadow: var(--shadow-lg);
display: flex;
align-items: center;
gap: 10px;
transform: translateX(400px);
transition: transform 0.3s ease;
z-index: 1000;
}
[data-theme="dark"] .toast {
background: #1f2937;
}
.toast.show {
transform: translateX(0);
}
.toast-icon {
font-size: 1.2rem;
}
.toast.success .toast-icon {
color: var(--secondary);
}
.toast.error .toast-icon {
color: var(--danger);
}
.modal {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.5);
display: none;
align-items: center;
justify-content: center;
z-index: 2000;
backdrop-filter: blur(5px);
}
.modal.active {
display: flex;
}
.modal-content {
background: white;
border-radius: var(--radius);
padding: 30px;
max-width: 500px;
width: 90%;
animation: modalSlideIn 0.3s ease;
}
[data-theme="dark"] .modal-content {
background: #1f2937;
}
.modal-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
}
.modal-title {
font-size: 1.5rem;
font-weight: 700;
}
.modal-close {
background: none;
border: none;
font-size: 1.5rem;
cursor: pointer;
color: var(--gray);
transition: var(--transition);
}
.modal-close:hover {
color: var(--danger);
}
.modal-body {
margin-bottom: 20px;
}
.modal-input {
width: 100%;
padding: 12px;
border: 2px solid var(--border);
border-radius: 8px;
background: var(--light);
color: var(--dark);
margin-bottom: 15px;
}
.modal-footer {
display: flex;
justify-content: flex-end;
gap: 10px;
}
.btn-secondary {
background: var(--border);
color: var(--dark);
}
.btn-secondary:hover {
background: var(--gray);
color: white;
}
@keyframes slideDown {
from {
opacity: 0;
transform: translateY(-30px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
@keyframes slideUp {
from {
opacity: 0;
transform: translateY(30px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
@keyframes slideIn {
from {
opacity: 0;
transform: translateX(-20px);
}
to {
opacity: 1;
transform: translateX(0);
}
}
@keyframes modalSlideIn {
from {
opacity: 0;
transform: scale(0.9);
}
to {
opacity: 1;
transform: scale(1);
}
}
.built-with {
color: white;
text-decoration: none;
opacity: 0.8;
transition: opacity 0.3s;
display: inline-block;
margin-top: 10px;
}
.built-with:hover {
opacity: 1;
text-decoration: underline;
}
@media (max-width: 640px) {
h1 {
font-size: 2rem;
}
.input-group {
flex-direction: column;
}
.filter-tabs {
overflow-x: auto;
}
.todo-item {
flex-wrap: wrap;
}
.todo-actions {
width: 100%;
justify-content: flex-end;
}
}
</style>
</head>
<body>
<div class="animated-bg"></div>
<header>
<h1><i class="fas fa-tasks"></i> FlowTask</h1>
<p class="subtitle">Organize your life with style</p>
<div class="header-actions">
<button class="theme-toggle" onclick="toggleTheme()">
<i class="fas fa-moon"></i> <span id="themeText">Dark Mode</span>
</button>
<button class="theme-toggle" onclick="exportTodos()">
<i class="fas fa-download"></i> Export
</button>
<button class="theme-toggle" onclick="document.getElementById('importFile').click()">
<i class="fas fa-upload"></i> Import
</button>
<input type="file" id="importFile" style="display: none;" accept=".json" onchange="importTodos(event)">
</div>
<a href="https://huggingface.co/spaces/akhaliq/anycoder" target="_blank" class="built-with">
Built with anycoder
</a>
</header>
<div class="container">
<div class="main-content">
<section class="todo-section">
<div class="input-group">
<input type="text" class="todo-input" id="todoInput" placeholder="What needs to be done today?" onkeypress="handleKeyPress(event)">
<button class="btn btn-primary" onclick="addTodo()">
<i class="fas fa-plus"></i> Add Task
</button>
</div>
<div class="input-options">
<select class="select-input" id="prioritySelect">
<option value="low">Low Priority</option>
<option value="medium" selected>Medium Priority</option>
<option value="high">High Priority</option>
</select>
<select class="select-input" id="categorySelect">
<option value="personal">Personal</option>
<option value="work">Work</option>
<option value="shopping">Shopping</option>
<option value="health">Health</option>
<option value="education">Education</option>
<option value="other">Other</option>
</select>
<input type="date" class="date-input" id="dueDateInput">
</div>
<div class="search-box">
<input type="text" class="search-input" id="searchInput" placeholder="Search tasks..." oninput="searchTodos()">
<i class="fas fa-search search-icon"></i>
</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')">Due Today</button>
</div>
<ul class="todo-list" id="todoList"></ul>
<div class="empty-state" id="emptyState" style="display: none;">
<div class="empty-icon">
<i class="fas fa-clipboard-list"></i>
</div>
<h3>No tasks yet</h3>
<p>Start adding tasks to organize your day!</p>
</div>
</section>
<aside class="sidebar">
<div class="stats-card">
<h3 class="stats-title">📊 Statistics</h3>
<div class="stat-item">
<span class="stat-label">Total Tasks</span>
<span class="stat-value" id="totalTasks">0</span>
</div>
<div class="stat-item">
<span class="stat-label">Completed</span>
<span class="stat-value" id="completedTasks">0</span>
</div>
<div class="stat-item">
<span class="stat-label">Active</span>
<span class="stat-value" id="activeTasks">0</span>
</div>
<div class="stat-item">
<span class="stat-label">Progress</span>
<div style="flex: 1; margin-left: 20px;">
<div class="progress-bar">
<div class="progress-fill" id="progressFill" style="width: 0%"></div>
</div>
</div>
</div>
</div>
<div class="stats-card">
<h3 class="stats-title">🏆 Productivity</h3>
<div class="stat-item">
<span class="stat-label">Tasks Completed Today</span>
<span class="stat-value" id="todayCompleted">0</span>
</div>
<div class="stat-item">
<span class="stat-label">Streak Days</span>
<span class="stat-value" id="streakDays">0</span>
</div>
<div class="stat-item">
<span class="stat-label">Completion Rate</span>
<span class="stat-value" id="completionRate">0%</span>
</div>
</div>
<button class="btn clear-completed" onclick="clearCompleted()">
<i class="fas fa-trash"></i> Clear Completed
</button>
</aside>
</div>
</div>
<div class="toast" id="toast">
<i class="toast-icon fas"></i>
<span id="toastMessage"></span>
</div>
<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>
<div class="modal-body">
<input type="text" class="modal-input" id="editInput" placeholder="Task description">
<select class="modal-input" id="editPriority">
<option value="low">Low Priority</option>
<option value="medium">Medium Priority</option>
<option value="high">High Priority</option>
</select>
<select class="modal-input" id="editCategory">
<option value="personal">Personal</option>
<option value="work">Work</option>
<option value="shopping">Shopping</option>
<option value="health">Health</option>
<option value="education">Education</option>
<option value="other">Other</option>
</select>
<input type="date" class="modal-input" id="editDueDate">
</div>
<div class="modal-footer">
<button class="btn btn-secondary" onclick="closeEditModal()">Cancel</button>
<button class="btn btn-primary" onclick="saveEdit()">
<i class="fas fa-save"></i> Save Changes
</button>
</div>
</div>
</div>
<script>
let todos = JSON.parse(localStorage.getItem('todos')) || [];
let currentFilter = 'all';
let editingTodo = null;
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
const savedTheme = localStorage.getItem('theme') || 'light';
document.documentElement.setAttribute('data-theme', savedTheme);
updateThemeButton(savedTheme);
});
function addTodo() {
const input = document.getElementById('todoInput');
const text = input.value.trim();
if (text === '') {
showToast('Please enter a task', 'error');
return;
}
const todo = {
id: Date.now(),
text: text,
completed: false,
priority: document.getElementById('prioritySelect').value,
category: document.getElementById('categorySelect').value,
dueDate: document.getElementById('dueDateInput').value,
createdAt: new Date().toISOString(),
completedAt: null
};
todos.unshift(todo);
saveTodos();
renderTodos();
updateStats();
input.value = '';
showToast('Task added successfully!', 'success');
// Reset date to today
const today = new Date().toISOString().split('T')[0];
document.getElementById('dueDateInput').value = today;
}
function handleKeyPress(event) {
if (event.key === 'Enter') {
addTodo();
}
}
function toggleTodo(id) {
const todo = todos.find(t => t.id === id);
if (todo) {
todo.completed = !todo.completed;
todo.completedAt = todo.completed ? new Date().toISOString() : null;
saveTodos();
renderTodos();
updateStats();
if (todo.completed) {
showToast('Task completed! 🎉', 'success');
updateStreak();
}
}
}
function deleteTodo(id) {
todos = todos.filter(t => t.id !== id);
saveTodos();
renderTodos();
updateStats();
showToast('Task deleted', 'success');
}
function editTodo(id) {
const todo = todos.find(t => t.id === id);
if (todo) {
editingTodo = todo;
document.getElementById('editInput').value = todo.text;
document.getElementById('editPriority').value = todo.priority;
document.getElementById('editCategory').value = todo.category;
document.getElementById('editDueDate').value = todo.dueDate;
document.getElementById('editModal').classList.add('active');
}
}
function saveEdit() {
if (editingTodo) {
editingTodo.text = document.getElementById('editInput').value;
editingTodo.priority = document.getElementById('editPriority').value;
editingTodo.category = document.getElementById('editCategory').value;
editingTodo.dueDate = document.getElementById('editDueDate').value;
saveTodos();
renderTodos();
updateStats();
closeEditModal();
showToast('Task updated successfully!', 'success');
}
}
function closeEditModal() {
document.getElementById('editModal').classList.remove('active');
editingTodo = null;
}
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() {
renderTodos();
}
function renderTodos() {
const todoList = document.getElementById('todoList');
const emptyState = document.getElementById('emptyState');
const searchTerm = document.getElementById('searchInput').value.toLowerCase();
let filteredTodos = todos;
// Apply filter
if (currentFilter === 'active') {
filteredTodos = todos.filter(t => !t.completed);
} else if (currentFilter === 'completed') {
filteredTodos = todos.filter(t => t.completed);
} else if (currentFilter === 'today') {
const today = new Date().toISOString().split('T')[0];
filteredTodos = todos.filter(t => t.dueDate === today);
}
// Apply search
if (searchTerm) {
filteredTodos = filteredTodos.filter(t =>
t.text.toLowerCase().includes(searchTerm) ||
t.category.toLowerCase().includes(searchTerm)
);
}
if (filteredTodos.length === 0) {
todoList.style.display = 'none';
emptyState.style.display = 'block';
return;
}
todoList.style.display = 'block';
emptyState.style.display = 'none';
todoList.innerHTML = filteredTodos.map(todo => {
const isOverdue = new Date(todo.dueDate) < new Date() && !todo.completed;
const dueDateFormatted = new Date(todo.dueDate).toLocaleDateString('en-US', {
month: 'short',
day: 'numeric'
});
return `
<li class="todo-item ${todo.completed ? 'completed' : ''}"
draggable="true"
ondragstart="handleDragStart(event, ${todo.id})"
ondragover="handleDragOver(event)"
ondrop="handleDrop(event, ${todo.id})"
ondragend="handleDragEnd(event)">
<input type="checkbox"
class="todo-checkbox"
${todo.completed ? 'checked' : ''}
onchange="toggleTodo(${todo.id})">
<div class="todo-content">
<div class="todo-text">${todo.text}</div>
<div class="todo-meta">
<span class="priority-badge priority-${todo.priority}">
${todo.priority}
</span>
<span class="category-tag">
<i class="fas fa-tag"></i> ${todo.category}
</span>
<span style="color: ${isOverdue ? 'var(--danger)' : 'var(--gray)'}">
<i class="fas fa-calendar"></i> ${dueDateFormatted}
${isOverdue ? ' (Overdue)' : ''}
</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>
</li>
`;
}).join('');
}
function handleDragStart(event, id) {
draggedItem = id;
event.dataTransfer.effectAllowed = 'move';
event.target.classList.add('dragging');
}
function handleDragOver(event) {
event.preventDefault();
event.dataTransfer.dropEffect = 'move';
}
function handleDrop(event, targetId) {
event.preventDefault();
if (draggedItem !== targetId) {
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();
}
}
}
function handleDragEnd(event) {
event.target.classList.remove('dragging');
draggedItem = null;
}
function clearCompleted() {
const completedCount = todos.filter(t => t.completed).length;
if (completedCount === 0) {
showToast('No completed tasks to clear', 'error');
return;
}
todos = todos.filter(t => !t.completed);
saveTodos();
renderTodos();
updateStats();
showToast(`Cleared ${completedCount} completed tasks`, 'success');
}
function updateStats() {
const total = todos.length;
const completed = todos.filter(t => t.completed).length;
const active = total - completed;
const today = new Date().toISOString().split('T')[0];
const todayCompleted = todos.filter(t =>
t.completed && t.completedAt && t.completedAt.split('T')[0] === today
).length;
document.getElementById('totalTasks').textContent = total;
document.getElementById('completedTasks').textContent = completed;
document.getElementById('activeTasks').textContent = active;
document.getElementById('todayCompleted').textContent = todayCompleted;
const progress = total > 0 ? (completed / total * 100) : 0;
document.getElementById('progressFill').style.width = `${progress}%`;
const completionRate = total > 0 ? Math.round(progress) : 0;
document.getElementById('completionRate').textContent = `${completionRate}%`;
updateStreak();
}
function updateStreak() {
// Simple streak calculation - can be enhanced
const today = new Date();
let streak = 0;
for (let i = 0; i < 30; i++) {
const checkDate = new Date(today);
checkDate.setDate(today.getDate() - i);
const dateStr = checkDate.toISOString().split('T')[0];
const hasCompletedOnDate = todos.some(t =>
t.completedAt && t.completedAt.split('T')[0] === dateStr
);
if (hasCompletedOnDate) {
streak++;
} else if (i > 0) {
break;
}
}
document.getElementById('streakDays').textContent = streak;
}
function toggleTheme() {
const currentTheme = document.documentElement.getAttribute('data-theme');
const newTheme = currentTheme === 'dark' ? 'light' : 'dark';
document.documentElement.setAttribute('data-theme', newTheme);
localStorage.setItem('theme', newTheme);
updateThemeButton(newTheme);
}
function updateThemeButton(theme) {
const themeText = document.getElementById('themeText');
const themeIcon = document.querySelector('.theme-toggle i');
if (theme === 'dark') {
themeText.textContent = 'Light Mode';
themeIcon.className = 'fas fa-sun';
} else {
themeText.textContent = 'Dark Mode';
themeIcon.className = 'fas fa-moon';
}
}
function exportTodos() {
const dataStr = JSON.stringify(todos, null, 2);
const dataBlob = new Blob([dataStr], { type: 'application/json' });
const url = URL.createObjectURL(dataBlob);
const link = document.createElement('a');
link.href = url;
link.download = `todos-${new Date().toISOString().split('T')[0]}.json`;
link.click();
URL.revokeObjectURL(url);
showToast('Tasks exported successfully!', 'success');
}
function importTodos(event) {
const file = event.target.files[0];
if (!file) return;
const reader = new FileReader();
reader.onload = function(e) {
try {
const imported = JSON.parse(e.target.result);
todos = [...todos, ...imported];
saveTodos();
renderTodos();
updateStats();
showToast('Tasks imported successfully!', 'success');
} catch (error) {
showToast('Invalid file format', 'error');
}
};
reader.readAsText(file);
event.target.value = '';
}
function showToast(message, type = 'success') {
const toast = document.getElementById('toast');
const toastMessage = document.getElementById('toastMessage');
const toastIcon = toast.querySelector('.toast-icon');
toastMessage.textContent = message;
toast.className = `toast ${type} show`;
if (type === 'success') {
toastIcon.className = 'toast-icon fas fa-check-circle';
} else if (type === 'error') {
toastIcon.className = 'toast-icon fas fa-exclamation-circle';
}
setTimeout(() => {
toast.classList.remove('show');
}, 3000);
}
function saveTodos() {
localStorage.setItem('todos', JSON.stringify(todos));
}
// Keyboard shortcuts
document.addEventListener('keydown', (e) => {
if (e.ctrlKey || e.metaKey) {
switch(e.key) {
case 'k':
e.preventDefault();
document.getElementById('searchInput').focus();
break;
case 'n':
e.preventDefault();
document.getElementById('todoInput').focus();
break;
case 'd':
e.preventDefault();
toggleTheme();
break;
}
}
});
</script>
</body>
</html>