|
|
<!DOCTYPE html> |
|
|
<html lang="en"> |
|
|
<head> |
|
|
<meta charset="UTF-8"> |
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0"> |
|
|
<title>FuchsiTodos</title> |
|
|
<script src="https://cdn.tailwindcss.com"></script> |
|
|
<script src="https://unpkg.com/feather-icons"></script> |
|
|
<script src="https://cdn.jsdelivr.net/npm/feather-icons/dist/feather.min.js"></script> |
|
|
<style> |
|
|
@import url('https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;500;600;700&display=swap'); |
|
|
body { |
|
|
font-family: 'Poppins', sans-serif; |
|
|
} |
|
|
.glow { |
|
|
box-shadow: 0 0 15px rgba(217, 70, 239, 0.5); |
|
|
} |
|
|
.task-checkbox:checked + .task-label { |
|
|
text-decoration: line-through; |
|
|
color: #9ca3af; |
|
|
} |
|
|
.fade-in { |
|
|
animation: fadeIn 0.3s ease-in; |
|
|
} |
|
|
@keyframes fadeIn { |
|
|
from { opacity: 0; transform: translateY(10px); } |
|
|
to { opacity: 1; transform: translateY(0); } |
|
|
} |
|
|
</style> |
|
|
</head> |
|
|
<body class="bg-gray-50 min-h-screen"> |
|
|
<div class="container mx-auto px-4 py-8 max-w-2xl"> |
|
|
|
|
|
<header class="flex justify-between items-center mb-10"> |
|
|
<h1 class="text-3xl font-bold text-fuchsia-600"> |
|
|
<span class="inline-flex items-center"> |
|
|
<i data-feather="check-circle" class="mr-2"></i> |
|
|
FuchsiTodos |
|
|
</span> |
|
|
</h1> |
|
|
<div class="flex space-x-2"> |
|
|
<button id="todoTab" class="px-4 py-2 rounded-lg bg-fuchsia-600 text-white font-medium hover:bg-fuchsia-700 transition">Todo</button> |
|
|
<button id="historyTab" class="px-4 py-2 rounded-lg bg-gray-200 text-gray-700 font-medium hover:bg-gray-300 transition">History</button> |
|
|
</div> |
|
|
</header> |
|
|
|
|
|
|
|
|
<section id="todoSection" class="space-y-6"> |
|
|
|
|
|
<div class="flex items-center space-x-2 mb-6"> |
|
|
<input type="text" id="newTaskInput" placeholder="Add a new task..." class="flex-1 px-4 py-3 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-fuchsia-500 focus:border-transparent"> |
|
|
<button id="addTaskBtn" class="px-6 py-3 bg-fuchsia-600 text-white rounded-lg hover:bg-fuchsia-700 transition flex items-center"> |
|
|
<i data-feather="plus" class="mr-1"></i> Add |
|
|
</button> |
|
|
</div> |
|
|
|
|
|
|
|
|
<div id="taskList" class="space-y-3"> |
|
|
|
|
|
<div class="text-center py-10 text-gray-400" id="emptyState"> |
|
|
<i data-feather="inbox" class="w-12 h-12 mx-auto mb-3"></i> |
|
|
<p>No tasks yet. Add one above!</p> |
|
|
</div> |
|
|
</div> |
|
|
</section> |
|
|
|
|
|
|
|
|
<section id="historySection" class="space-y-6 hidden"> |
|
|
<div class="flex justify-between items-center mb-4"> |
|
|
<h2 class="text-xl font-semibold text-gray-700">Completed Tasks</h2> |
|
|
<button id="clearHistoryBtn" class="text-sm text-fuchsia-600 hover:text-fuchsia-800 flex items-center"> |
|
|
<i data-feather="trash-2" class="w-4 h-4 mr-1"></i> Clear All |
|
|
</button> |
|
|
</div> |
|
|
<div id="historyList" class="space-y-3"> |
|
|
|
|
|
<div class="text-center py-10 text-gray-400" id="emptyHistoryState"> |
|
|
<i data-feather="clock" class="w-12 h-12 mx-auto mb-3"></i> |
|
|
<p>No completed tasks yet. Get to work!</p> |
|
|
</div> |
|
|
</div> |
|
|
</section> |
|
|
|
|
|
|
|
|
<div class="mt-10 p-4 bg-white rounded-xl shadow-sm"> |
|
|
<div class="flex justify-between items-center"> |
|
|
<div class="text-center"> |
|
|
<p class="text-sm text-gray-500">Tasks Today</p> |
|
|
<p id="taskCount" class="text-2xl font-bold text-gray-800">0</p> |
|
|
</div> |
|
|
<div class="text-center"> |
|
|
<p class="text-sm text-gray-500">Completed</p> |
|
|
<p id="completedCount" class="text-2xl font-bold text-fuchsia-600">0</p> |
|
|
</div> |
|
|
<div class="text-center"> |
|
|
<p class="text-sm text-gray-500">Pending</p> |
|
|
<p id="pendingCount" class="text-2xl font-bold text-gray-800">0</p> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
<script> |
|
|
|
|
|
feather.replace(); |
|
|
|
|
|
|
|
|
const todoSection = document.getElementById('todoSection'); |
|
|
const historySection = document.getElementById('historySection'); |
|
|
const todoTab = document.getElementById('todoTab'); |
|
|
const historyTab = document.getElementById('historyTab'); |
|
|
const newTaskInput = document.getElementById('newTaskInput'); |
|
|
const addTaskBtn = document.getElementById('addTaskBtn'); |
|
|
const taskList = document.getElementById('taskList'); |
|
|
const historyList = document.getElementById('historyList'); |
|
|
const clearHistoryBtn = document.getElementById('clearHistoryBtn'); |
|
|
const emptyState = document.getElementById('emptyState'); |
|
|
const emptyHistoryState = document.getElementById('emptyHistoryState'); |
|
|
const taskCount = document.getElementById('taskCount'); |
|
|
const completedCount = document.getElementById('completedCount'); |
|
|
const pendingCount = document.getElementById('pendingCount'); |
|
|
|
|
|
|
|
|
let tasks = JSON.parse(localStorage.getItem('tasks')) || []; |
|
|
let history = JSON.parse(localStorage.getItem('history')) || []; |
|
|
|
|
|
|
|
|
todoTab.addEventListener('click', () => { |
|
|
todoSection.classList.remove('hidden'); |
|
|
historySection.classList.add('hidden'); |
|
|
todoTab.classList.remove('bg-gray-200', 'text-gray-700'); |
|
|
todoTab.classList.add('bg-fuchsia-600', 'text-white'); |
|
|
historyTab.classList.remove('bg-fuchsia-600', 'text-white'); |
|
|
historyTab.classList.add('bg-gray-200', 'text-gray-700'); |
|
|
}); |
|
|
|
|
|
historyTab.addEventListener('click', () => { |
|
|
todoSection.classList.add('hidden'); |
|
|
historySection.classList.remove('hidden'); |
|
|
todoTab.classList.add('bg-gray-200', 'text-gray-700'); |
|
|
todoTab.classList.remove('bg-fuchsia-600', 'text-white'); |
|
|
historyTab.classList.add('bg-fuchsia-600', 'text-white'); |
|
|
historyTab.classList.remove('bg-gray-200', 'text-gray-700'); |
|
|
renderHistory(); |
|
|
}); |
|
|
|
|
|
|
|
|
function addTask() { |
|
|
const taskText = newTaskInput.value.trim(); |
|
|
if (taskText) { |
|
|
const newTask = { |
|
|
id: Date.now(), |
|
|
text: taskText, |
|
|
completed: false, |
|
|
timestamp: new Date().toISOString() |
|
|
}; |
|
|
tasks.unshift(newTask); |
|
|
saveTasks(); |
|
|
renderTasks(); |
|
|
newTaskInput.value = ''; |
|
|
updateStats(); |
|
|
} |
|
|
} |
|
|
|
|
|
addTaskBtn.addEventListener('click', addTask); |
|
|
newTaskInput.addEventListener('keypress', (e) => { |
|
|
if (e.key === 'Enter') addTask(); |
|
|
}); |
|
|
|
|
|
|
|
|
function toggleTaskCompletion(taskId) { |
|
|
const taskIndex = tasks.findIndex(task => task.id === taskId); |
|
|
if (taskIndex !== -1) { |
|
|
const task = tasks[taskIndex]; |
|
|
task.completed = !task.completed; |
|
|
|
|
|
if (task.completed) { |
|
|
|
|
|
history.unshift({...task, completedAt: new Date().toISOString()}); |
|
|
tasks.splice(taskIndex, 1); |
|
|
} |
|
|
|
|
|
saveTasks(); |
|
|
saveHistory(); |
|
|
renderTasks(); |
|
|
updateStats(); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
function deleteTask(taskId) { |
|
|
tasks = tasks.filter(task => task.id !== taskId); |
|
|
saveTasks(); |
|
|
renderTasks(); |
|
|
updateStats(); |
|
|
} |
|
|
|
|
|
|
|
|
function clearHistory() { |
|
|
history = []; |
|
|
saveHistory(); |
|
|
renderHistory(); |
|
|
} |
|
|
|
|
|
clearHistoryBtn.addEventListener('click', clearHistory); |
|
|
|
|
|
|
|
|
function renderTasks() { |
|
|
if (tasks.length === 0) { |
|
|
emptyState.classList.remove('hidden'); |
|
|
taskList.innerHTML = ''; |
|
|
} else { |
|
|
emptyState.classList.add('hidden'); |
|
|
taskList.innerHTML = ''; |
|
|
|
|
|
tasks.forEach(task => { |
|
|
const taskElement = document.createElement('div'); |
|
|
taskElement.className = 'fade-in bg-white p-4 rounded-lg shadow-sm hover:shadow-md transition flex items-center justify-between'; |
|
|
|
|
|
taskElement.innerHTML = ` |
|
|
<div class="flex items-center space-x-3"> |
|
|
<input type="checkbox" id="task-${task.id}" class="task-checkbox h-5 w-5 text-fuchsia-600 rounded focus:ring-fuchsia-500" ${task.completed ? 'checked' : ''}> |
|
|
<label for="task-${task.id}" class="task-label text-gray-800 ${task.completed ? 'line-through text-gray-400' : ''}">${task.text}</label> |
|
|
</div> |
|
|
<button data-id="${task.id}" class="delete-btn text-gray-400 hover:text-fuchsia-600 transition"> |
|
|
<i data-feather="trash-2" class="w-5 h-5"></i> |
|
|
</button> |
|
|
`; |
|
|
|
|
|
taskList.appendChild(taskElement); |
|
|
|
|
|
|
|
|
const checkbox = taskElement.querySelector(`#task-${task.id}`); |
|
|
checkbox.addEventListener('change', () => toggleTaskCompletion(task.id)); |
|
|
|
|
|
const deleteBtn = taskElement.querySelector('.delete-btn'); |
|
|
deleteBtn.addEventListener('click', () => deleteTask(task.id)); |
|
|
}); |
|
|
} |
|
|
feather.replace(); |
|
|
} |
|
|
|
|
|
|
|
|
function renderHistory() { |
|
|
if (history.length === 0) { |
|
|
emptyHistoryState.classList.remove('hidden'); |
|
|
historyList.innerHTML = ''; |
|
|
} else { |
|
|
emptyHistoryState.classList.add('hidden'); |
|
|
historyList.innerHTML = ''; |
|
|
|
|
|
history.forEach(task => { |
|
|
const historyElement = document.createElement('div'); |
|
|
historyElement.className = 'fade-in bg-white p-4 rounded-lg shadow-sm hover:shadow-md transition'; |
|
|
|
|
|
const completedDate = new Date(task.completedAt).toLocaleString(); |
|
|
|
|
|
historyElement.innerHTML = ` |
|
|
<div class="flex justify-between items-center"> |
|
|
<span class="text-gray-600 line-through">${task.text}</span> |
|
|
<span class="text-xs text-gray-400">${completedDate}</span> |
|
|
</div> |
|
|
`; |
|
|
|
|
|
historyList.appendChild(historyElement); |
|
|
}); |
|
|
} |
|
|
feather.replace(); |
|
|
} |
|
|
|
|
|
|
|
|
function updateStats() { |
|
|
const totalTasks = tasks.length + history.length; |
|
|
taskCount.textContent = totalTasks; |
|
|
completedCount.textContent = history.length; |
|
|
pendingCount.textContent = tasks.filter(t => !t.completed).length; |
|
|
} |
|
|
|
|
|
|
|
|
function saveTasks() { |
|
|
localStorage.setItem('tasks', JSON.stringify(tasks)); |
|
|
} |
|
|
|
|
|
function saveHistory() { |
|
|
localStorage.setItem('history', JSON.stringify(history)); |
|
|
} |
|
|
|
|
|
|
|
|
renderTasks(); |
|
|
updateStats(); |
|
|
</script> |
|
|
</body> |
|
|
</html> |
|
|
|