From bd94ad08a430a030b9f41cbb942155f485d500f1 Mon Sep 17 00:00:00 2001 From: Kashish Gupta Date: Mon, 18 May 2026 16:22:10 +0530 Subject: [PATCH 1/2] feat(dashboard): persist todos to localStorage and add filter + clear completed - Load todos from localStorage on mount (fallback to defaults; resilient to bad JSON) - Persist todos on every change via useEffect - Add All/Active/Completed filter buttons with aria-pressed state - Add 'items left' counter and 'Clear completed' action - Show contextual empty-state message per filter --- src/pages/Dashboard.css | 70 ++++++++++++++++++++++++ src/pages/Dashboard.js | 118 ++++++++++++++++++++++++++++++++++------ 2 files changed, 170 insertions(+), 18 deletions(-) diff --git a/src/pages/Dashboard.css b/src/pages/Dashboard.css index da8a7fc..3eeb19e 100644 --- a/src/pages/Dashboard.css +++ b/src/pages/Dashboard.css @@ -40,6 +40,76 @@ color: var(--color-text); } +.todo-toolbar { + display: flex; + flex-wrap: wrap; + align-items: center; + gap: 0.75rem; + margin-bottom: 0.75rem; +} + +.todo-filters { + display: flex; + gap: 0.25rem; +} + +.todo-filter { + background: transparent; + border: 1px solid var(--color-border); + color: var(--color-text); + padding: 0.35rem 0.75rem; + border-radius: 4px; + font-size: 0.9rem; + cursor: pointer; +} + +.todo-filter:hover { + border-color: var(--color-primary); +} + +.todo-filter.active { + background: var(--color-primary); + border-color: var(--color-primary); + color: var(--color-table-header-text, white); +} + +.todo-count { + margin-left: auto; + color: var(--color-text-secondary); + font-size: 0.9rem; +} + +.todo-clear-completed { + background: transparent; + border: 1px solid var(--color-border); + color: var(--color-text); + padding: 0.35rem 0.75rem; + border-radius: 4px; + font-size: 0.9rem; + cursor: pointer; +} + +.todo-clear-completed:hover:not(:disabled) { + border-color: var(--color-danger); + color: var(--color-danger); +} + +.todo-clear-completed:disabled { + opacity: 0.5; + cursor: not-allowed; +} + +.todo-empty { + display: block !important; + text-align: center; + color: var(--color-text-secondary); + font-style: italic; + background: var(--color-surface); + border-radius: 6px; + padding: 1rem; + box-shadow: 0 1px 4px rgba(0,0,0,0.06); +} + .todo-list { list-style: none; padding: 0; diff --git a/src/pages/Dashboard.js b/src/pages/Dashboard.js index 1c7a74e..b7c9157 100644 --- a/src/pages/Dashboard.js +++ b/src/pages/Dashboard.js @@ -1,7 +1,9 @@ -import React, { useState } from 'react'; +import React, { useEffect, useMemo, useState } from 'react'; import './Dashboard.css'; -const initialTodos = [ +const TODOS_STORAGE_KEY = 'dashboard.todos.v1'; + +const defaultTodos = [ { id: 1, text: 'Review pull requests', done: false }, { id: 2, text: 'Deploy to staging', done: true }, { id: 3, text: 'Write documentation', done: false }, @@ -13,9 +15,38 @@ const activityData = [ { user: 'Charlie', action: 'Updated profile', date: '2026-05-12' }, ]; +const FILTERS = ['all', 'active', 'completed']; + +function loadTodos() { + if (typeof window === 'undefined' || !window.localStorage) { + return defaultTodos; + } + try { + const raw = window.localStorage.getItem(TODOS_STORAGE_KEY); + if (!raw) return defaultTodos; + const parsed = JSON.parse(raw); + if (!Array.isArray(parsed)) return defaultTodos; + return parsed + .filter(t => t && typeof t.text === 'string') + .map(t => ({ id: t.id ?? Date.now(), text: t.text, done: !!t.done })); + } catch { + return defaultTodos; + } +} + function Dashboard() { - const [todos, setTodos] = useState(initialTodos); + const [todos, setTodos] = useState(loadTodos); const [newTodo, setNewTodo] = useState(''); + const [filter, setFilter] = useState('all'); + + useEffect(() => { + if (typeof window === 'undefined' || !window.localStorage) return; + try { + window.localStorage.setItem(TODOS_STORAGE_KEY, JSON.stringify(todos)); + } catch { + /* storage may be full or unavailable; ignore */ + } + }, [todos]); const addTodo = (e) => { e.preventDefault(); @@ -32,6 +63,19 @@ function Dashboard() { setTodos(todos.filter(t => t.id !== id)); }; + const clearCompleted = () => { + setTodos(todos.filter(t => !t.done)); + }; + + const visibleTodos = useMemo(() => { + if (filter === 'active') return todos.filter(t => !t.done); + if (filter === 'completed') return todos.filter(t => t.done); + return todos; + }, [todos, filter]); + + const remainingCount = todos.filter(t => !t.done).length; + const hasCompleted = todos.some(t => t.done); + return (

Dashboard

@@ -63,25 +107,63 @@ function Dashboard() { /> -