diff --git a/index.html b/index.html index f7ac4e4..d31d805 100644 --- a/index.html +++ b/index.html @@ -3,14 +3,24 @@ + + Todo
+ + src="https://unpkg.com/ionicons@7.1.0/dist/ionicons/ionicons.esm.js" + > + diff --git a/package.json b/package.json index caf6289..fbde171 100644 --- a/package.json +++ b/package.json @@ -10,8 +10,11 @@ "preview": "vite preview" }, "dependencies": { + "@tailwindcss/vite": "^4.1.7", "react": "^19.0.0", - "react-dom": "^19.0.0" + "react-dom": "^19.0.0", + "tailwindcss": "^4.1.7", + "zustand": "^5.0.4" }, "devDependencies": { "@eslint/js": "^9.21.0", diff --git a/public/vite.svg b/public/vite.svg deleted file mode 100644 index e7b8dfb..0000000 --- a/public/vite.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/src/App.jsx b/src/App.jsx index 5427540..869f13b 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -1,5 +1,15 @@ +import ToDoForm from "./components/ToDoForm"; +import TodoList from "./components/TodoList"; + export const App = () => { return ( -

React Boilerplate

- ) -} +
+
+

Todoodle

+

My To-Do List

+ + +
+
+ ); +}; diff --git a/src/components/TaskCounter.jsx b/src/components/TaskCounter.jsx new file mode 100644 index 0000000..26eb0e8 --- /dev/null +++ b/src/components/TaskCounter.jsx @@ -0,0 +1,31 @@ +import useTodoStore from "../stores/useTodoStore"; + +const TaskCounter = () => { + const todos = useTodoStore((state) => state.todos); + + return ( +
+

Your progress:

+
+ +

All tasks: {todos.length}

+
+
+ +

+ Tasks completed:{" "} + {todos.filter((todo) => todo.complete === true).length}{" "} +

+
+
+ +

+ Tasks remaining:{" "} + {todos.filter((todo) => todo.complete === false).length} +

+
+
+ ); +}; + +export default TaskCounter; diff --git a/src/components/ToDoForm.jsx b/src/components/ToDoForm.jsx new file mode 100644 index 0000000..ee6a2c0 --- /dev/null +++ b/src/components/ToDoForm.jsx @@ -0,0 +1,97 @@ +import { useState } from "react"; +import useTodoStore from "../stores/useTodoStore"; + +const ToDoForm = () => { + const [message, setMessage] = useState(""); + const [category, setCategory] = useState(""); + const createTodo = useTodoStore((state) => state.createTodo); + + const handleSubmit = (e) => { + e.preventDefault(); + createTodo(message, category); + setMessage(""); + setCategory(""); + }; + + return ( +
+

What do you need to do?

+ setMessage(e.target.value)} + required + value={message} + className="border border-gray-300 h-10 px-3 focus:border-rose-200 focus:ring-rose-300 focus:outline-none focus:ring-2 " + /> +

Task category:

+
+ + + + +
+ +
+ ); +}; + +export default ToDoForm; diff --git a/src/components/ToDoMessage.jsx b/src/components/ToDoMessage.jsx new file mode 100644 index 0000000..8b8fa75 --- /dev/null +++ b/src/components/ToDoMessage.jsx @@ -0,0 +1,67 @@ +import useTodoStore from "../stores/useTodoStore"; + +const ToDoMessage = ({ id, message, complete, category }) => { + const removeTodo = useTodoStore((state) => state.removeTodo); + const completeTodo = useTodoStore((state) => state.completeTodo); + const uncompleteTodo = useTodoStore((state) => state.uncompleteTodo); + + return ( +
+
+

{category}

+
+ +

+ {message} +

+ +
+

Complete?

+ + {complete ? ( + + ) : ( + + )} +
+ +
+ + + +
+

Task added: {new Date(id).toLocaleString()}

+
+ ); +}; + +export default ToDoMessage; diff --git a/src/components/TodoList.jsx b/src/components/TodoList.jsx new file mode 100644 index 0000000..e3cea21 --- /dev/null +++ b/src/components/TodoList.jsx @@ -0,0 +1,93 @@ +import { useState } from "react"; +import useTodoStore from "../stores/useTodoStore"; +import ToDoMessage from "./ToDoMessage"; +import TaskCounter from "./TaskCounter"; + +const TodoList = () => { + const todos = useTodoStore((state) => state.todos); + const [filter, setFilter] = useState("all"); + + const filteredTodos = todos.filter((todo) => { + if (filter === "complete") return todo.complete; + if (filter === "incomplete") return !todo.complete; + return true; + }); + + return ( +
+ +
+

Filter tasks by:

+
+ + + +
+
+
+ {filteredTodos.length === 0 ? ( + filter === "complete" ? ( +
+ +

No completed tasks yet. Complete a task to see it here.

+
+ ) : filter === "incomplete" ? ( +
+ +

+ No incomplete tasks yet. Add a task or uncheck a completed one. +

{" "} +
+ ) : ( +
+ +

You have no tasks yet. Add a to-do to get started!

{" "} +
+ ) + ) : ( + filteredTodos.map((todo) => ) + )} +
+
+ ); +}; + +export default TodoList; diff --git a/src/index.css b/src/index.css index f7c0aef..7a4a7b5 100644 --- a/src/index.css +++ b/src/index.css @@ -1,3 +1,5 @@ +@import "tailwindcss"; + :root { - font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif; + font-family: Quicksand, system-ui, Avenir, Helvetica, Arial, sans-serif; } diff --git a/src/stores/useTodoStore.jsx b/src/stores/useTodoStore.jsx new file mode 100644 index 0000000..b54eda0 --- /dev/null +++ b/src/stores/useTodoStore.jsx @@ -0,0 +1,43 @@ +import { create } from "zustand"; + +const initialState = { + todos: [], +}; + +const useTodoStore = create((set) => ({ + ...initialState, + + createTodo: (message, category) => { + const newTodo = { + id: Date.now(), + message, + complete: false, + category, + }; + set((state) => ({ todos: [newTodo, ...state.todos] })); + }, + + removeTodo: (id) => { + set((state) => ({ + todos: state.todos.filter((todo) => todo.id !== id), + })); + }, + + completeTodo: (id) => { + set((state) => ({ + todos: state.todos.map((todo) => + todo.id === id ? { ...todo, complete: true } : todo + ), + })); + }, + + uncompleteTodo: (id) => { + set((state) => ({ + todos: state.todos.map((todo) => + todo.id === id ? { ...todo, complete: false } : todo + ), + })); + }, +})); + +export default useTodoStore; diff --git a/vite.config.js b/vite.config.js index ba24244..282fc60 100644 --- a/vite.config.js +++ b/vite.config.js @@ -1,7 +1,9 @@ -import react from '@vitejs/plugin-react' -import { defineConfig } from 'vite' +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()], +});