diff --git a/index.css b/index.css
new file mode 100644
index 0000000..fa10bf9
--- /dev/null
+++ b/index.css
@@ -0,0 +1,5 @@
+/* @tailwind base;
+@tailwind components;
+@tailwind utilities; */
+
+@import "tailwindcss";
\ No newline at end of file
diff --git a/package.json b/package.json
index caf6289..8cd844c 100644
--- a/package.json
+++ b/package.json
@@ -10,18 +10,25 @@
"preview": "vite preview"
},
"dependencies": {
+ "date-fns": "^4.1.0",
"react": "^19.0.0",
- "react-dom": "^19.0.0"
+ "react-dom": "^19.0.0",
+ "zustand": "^5.0.5"
},
"devDependencies": {
"@eslint/js": "^9.21.0",
+ "@tailwindcss/postcss": "^4.1.8",
+ "@tailwindcss/vite": "^4.1.7",
"@types/react": "^19.0.10",
"@types/react-dom": "^19.0.4",
"@vitejs/plugin-react": "^4.3.4",
+ "autoprefixer": "^10.4.21",
"eslint": "^9.21.0",
"eslint-plugin-react-hooks": "^5.1.0",
"eslint-plugin-react-refresh": "^0.4.19",
"globals": "^15.15.0",
+ "postcss": "^8.5.4",
+ "tailwindcss": "^4.1.8",
"vite": "^6.2.0"
}
}
diff --git a/postcss.config.js b/postcss.config.js
new file mode 100644
index 0000000..44d2119
--- /dev/null
+++ b/postcss.config.js
@@ -0,0 +1,6 @@
+export default {
+ plugins: {
+ '@tailwindcss/postcss': {},
+ autoprefixer: {},
+ },
+};
\ No newline at end of file
diff --git a/src/App.jsx b/src/App.jsx
index 5427540..bd387c5 100644
--- a/src/App.jsx
+++ b/src/App.jsx
@@ -1,5 +1,176 @@
-export const App = () => {
+import { useState, useEffect } from "react";
+import useTaskStore from "./data/useTaskData";
+import TaskForm from "./Components/TaskForm";
+import TaskList from "./Components/TaskList";
+import TaskStats from "./Components/TaskStats";
+
+export default function App() {
+ const { tasks, addTask, removeTask, toggleTask, completeAllTasks ,projects, addProject} =
+ useTaskStore();
+ const [input, setInput] = useState("");
+ const [dueDate, setDueDate] = useState(""); // date state
+ const [darkMode, setDarkMode] = useState(false);
+
+ // Filter state
+ const [filterStatus, setFilterStatus] = useState("all"); // all, completed, uncompleted
+ const [filterDate, setFilterDate] = useState(""); // yyyy-MM-dd
+
+ // new category state
+ const [category, setCategory] = useState("General");
+
+ const [selectedProjectId, setSelectedProjectId] = useState(null);
+ const [newProjectName, setNewProjectName] = useState("");
+
+
+ useEffect(() => {
+ const root = window.document.documentElement;
+ if (darkMode) {
+ root.classList.add("dark");
+ } else {
+ root.classList.remove("dark");
+ }
+ }, [darkMode]);
+
+ // Apply filters to tasks here
+ const filteredTasks = tasks.filter((task) => {
+ // Filter by status
+ if (filterStatus === "completed" && !task.completed) return false;
+ if (filterStatus === "uncompleted" && task.completed) return false;
+
+ // Filter by creation date if date filter set
+ if (filterDate) {
+ const filterDateObj = new Date(filterDate);
+ const taskCreatedDate = new Date(task.createdAt);
+ if (taskCreatedDate < filterDateObj) return false;
+ }
+
+ return true;
+ });
+
+ const handleSubmit = (e) => {
+ e.preventDefault();
+ if (!input.trim()) return;
+ addTask(input.trim(), dueDate, category); // pass dueDate to addTask
+ setInput("");
+ setCategory("General"); // reset to default after adding
+ setDueDate("");
+ };
+
return (
-
React Boilerplate
- )
+
+
+
+
+ Task Manager
+
+
+
+ {/* Add new project */}
+
+ setNewProjectName(e.target.value)}
+ />
+
+
+
+ {/* Select project */}
+
+
+
+
+
+
+ {/* Filter controls */}
+
+ {/* Status filter */}
+
+
+ {/* Created after date filter */}
+ setFilterDate(e.target.value)}
+ className="border rounded px-3 py-1 dark:bg-gray-700 dark:text-white"
+ aria-label="Show tasks created after date"
+ />
+
+ {/* Clear filters button */}
+
+
+
+
+
+
+
+
+ );
}
diff --git a/src/Components/TaskForm.jsx b/src/Components/TaskForm.jsx
new file mode 100644
index 0000000..6d5f588
--- /dev/null
+++ b/src/Components/TaskForm.jsx
@@ -0,0 +1,66 @@
+export default function TaskForm({
+ onSubmit,
+ input,
+ setInput,
+ dueDate,
+ setDueDate,
+ category,
+ setCategory,
+ projectId,
+ setProjectId,
+ projects = [],
+}) {
+ return (
+
+ );
+}
diff --git a/src/Components/TaskItem.jsx b/src/Components/TaskItem.jsx
new file mode 100644
index 0000000..ba7fff6
--- /dev/null
+++ b/src/Components/TaskItem.jsx
@@ -0,0 +1,52 @@
+import { format, isBefore, startOfDay } from "date-fns";
+
+export default function TaskItem({ task, onToggle, onRemove }) {
+ const today = startOfDay(new Date());
+ const isOverdue =
+ task.dueDate && isBefore(new Date(task.dueDate), today) && !task.completed;
+
+ return (
+
+ onToggle(task.id)}
+ onKeyDown={(e) => e.key === "Enter" && onToggle(task.id)}
+ >
+
+ {task.text}
+ {task.category && (
+
+ {task.category}
+
+ )}
+
+ {task.dueDate && (
+
+ (Due: {format(new Date(task.dueDate), "dd/MM/yyyy")})
+
+ )}
+ {task.createdAt && (
+
+ Added: {format(new Date(task.createdAt), "dd MMM yyyy, p")}
+
+ )}
+
+
+
+
+ );
+}
diff --git a/src/Components/TaskList.jsx b/src/Components/TaskList.jsx
new file mode 100644
index 0000000..ee63164
--- /dev/null
+++ b/src/Components/TaskList.jsx
@@ -0,0 +1,70 @@
+import TaskItem from "./TaskItem";
+
+export default function TaskList({
+ tasks,
+ onToggle,
+ onRemove,
+ onCompleteAll,
+ setDarkMode,
+ darkMode,
+ projects,
+}) {
+ if (tasks.length === 0) {
+ return (
+
+ No tasks yet. Start by adding one!
+
+ );
+ }
+
+ const allCompleted = tasks.every((task) => task.completed);
+
+ // Group tasks by projectId
+ const groupedTasks = tasks.reduce((groups, task) => {
+ const key = task.projectId || "none";
+ if (!groups[key]) groups[key] = [];
+ groups[key].push(task);
+ return groups;
+ }, {});
+
+ return (
+
+ {/* Dark mode toggle button */}
+
+
+
+
+ {/* Complete All button */}
+
+
+
+
+ {/* Task List */}
+
+ {tasks.map((task) => (
+
+ ))}
+
+
+ );
+}
diff --git a/src/Components/TaskStats.jsx b/src/Components/TaskStats.jsx
new file mode 100644
index 0000000..788d066
--- /dev/null
+++ b/src/Components/TaskStats.jsx
@@ -0,0 +1,12 @@
+export default function TaskStats({ tasks }) {
+ const total = tasks.length;
+ const uncompleted = tasks.filter((t) => !t.completed).length;
+
+ return (
+
+ Total: {total}
+ |
+ Uncompleted: {uncompleted}
+
+ );
+}
\ No newline at end of file
diff --git a/src/data/useTaskData.js b/src/data/useTaskData.js
new file mode 100644
index 0000000..42caebe
--- /dev/null
+++ b/src/data/useTaskData.js
@@ -0,0 +1,100 @@
+import { create } from "zustand";
+import { persist } from "zustand/middleware";
+
+const useTaskStore = create(
+ persist(
+ (set, get) => ({
+ tasks: [],
+ projects: [],
+
+ // Add a new task with optional category and projectId
+ addTask: (text, dueDate, category = "General", projectId = null) =>
+ set((state) => ({
+ tasks: [
+ ...state.tasks,
+ {
+ id: Date.now(),
+ text,
+ dueDate,
+ category,
+ projectId,
+ completed: false,
+ createdAt: new Date().toISOString(),
+ },
+ ],
+ })),
+
+ // Remove task
+ removeTask: (id) =>
+ set((state) => ({
+ tasks: state.tasks.filter((task) => task.id !== id),
+ })),
+
+ // Toggle task complete
+ toggleTask: (id) =>
+ set((state) => {
+ const updatedTasks = state.tasks.map((task) =>
+ task.id === id ? { ...task, completed: !task.completed } : task
+ );
+
+ // Optionally mark project as complete if all its tasks are complete
+ const affectedTask = state.tasks.find((t) => t.id === id);
+ const projectId = affectedTask?.projectId;
+ let updatedProjects = state.projects;
+
+ if (projectId) {
+ const tasksInProject = updatedTasks.filter(
+ (t) => t.projectId === projectId
+ );
+ const allDone = tasksInProject.every((t) => t.completed);
+
+ updatedProjects = state.projects.map((p) =>
+ p.id === projectId ? { ...p, completed: allDone } : p
+ );
+ }
+
+ return {
+ tasks: updatedTasks,
+ projects: updatedProjects,
+ };
+ }),
+
+ // Complete all tasks
+ completeAllTasks: () =>
+ set((state) => {
+ const updatedTasks = state.tasks.map((task) => ({
+ ...task,
+ completed: true,
+ }));
+
+ // Update project completion status
+ const updatedProjects = state.projects.map((project) => {
+ const projectTasks = updatedTasks.filter(
+ (task) => task.projectId === project.id
+ );
+ const allCompleted = projectTasks.every((t) => t.completed);
+ return { ...project, completed: allCompleted };
+ });
+
+ return {
+ tasks: updatedTasks,
+ projects: updatedProjects,
+ };
+ }),
+
+ // Add a new project
+ addProject: (name) =>
+ set((state) => ({
+ projects: [
+ ...state.projects,
+ { id: Date.now(), name, completed: false },
+ ],
+ })),
+ }),
+ {
+ name: "task-storage", // persisted key in localStorage
+ }
+ )
+);
+
+export default useTaskStore;
diff --git a/src/index.css b/src/index.css
index f7c0aef..2ababe5 100644
--- a/src/index.css
+++ b/src/index.css
@@ -1,3 +1,10 @@
-:root {
- font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;
-}
+/*
+@tailwind base;
+@tailwind components;
+@tailwind utilities; */
+
+/* @tailwind base;
+@tailwind components;
+@tailwind utilities; */
+
+@import "tailwindcss";
\ No newline at end of file
diff --git a/src/main.jsx b/src/main.jsx
index 1b8ffe9..cbf4411 100644
--- a/src/main.jsx
+++ b/src/main.jsx
@@ -1,7 +1,7 @@
import React from 'react'
import ReactDOM from 'react-dom/client'
-import { App } from './App.jsx'
+import App from './App.jsx'
import './index.css'
diff --git a/tailwind.config.js b/tailwind.config.js
new file mode 100644
index 0000000..1653668
--- /dev/null
+++ b/tailwind.config.js
@@ -0,0 +1,9 @@
+/** @type {import('tailwindcss').Config} */
+export default {
+ content: ['./index.html', './src/**/*.{js,jsx,ts,tsx}'],
+ darkMode: 'class', //
+ theme: {
+ extend: {},
+ },
+ plugins: [],
+};
\ No newline at end of file
diff --git a/vite.config.js b/vite.config.js
index ba24244..87f6aae 100644
--- a/vite.config.js
+++ b/vite.config.js
@@ -1,7 +1,7 @@
import react from '@vitejs/plugin-react'
import { defineConfig } from 'vite'
-
+import tailwindcss from '@tailwindcss/vite'
// https://vitejs.dev/config/
export default defineConfig({
- plugins: [react()]
+ plugins: [react(),tailwindcss()]
})