diff react_games/src/TODO/main.tsx @ 37:fb9bcd3145cb

[ReactGames] Few games I made using react just to practice few things.
author MrJuneJune <me@mrjunejune.com>
date Mon, 01 Dec 2025 20:22:47 -0800
parents
children
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/react_games/src/TODO/main.tsx	Mon Dec 01 20:22:47 2025 -0800
@@ -0,0 +1,223 @@
+/*
+ * 📝 React Todo App Challenge (1 Hour)
+ * ❓Problem
+ * Build a simple Todo application using React. The app should allow users to:
+ * 
+ *  - Add a new todo item.
+ * 
+ *  - Mark a todo as completed.
+ * 
+ *  - Delete a todo.
+ * 
+ * Filter todos by:
+ * 
+ *   -  All
+ *   -  Active
+ *   -  Completed
+ * 
+ * 💡Bonus (if time allows)
+ * Persist todos in localStorage.
+ * 
+ * Use React Context + useReducer to manage global state.
+ * 
+ * Add a clear completed button.
+ * 
+ * 📦 Requirements
+ * Use functional components + hooks only.
+ *
+ *
+ * 
+ * No styling is required, but if you have time, make it readable.
+ * 
+ * Don’t use external state management libraries like Redux.
+ *
+ * Components
+ *
+ *   TODOs
+ *   Inputs
+ *   buttons filtering
+ *
+ * TODO States 
+ *   - Active 
+ *   - Completed
+ *   - Deleted
+ */
+import React, { CSSProperties, Dispatch, InputHTMLAttributes, memo, useMemo, useRef, useState } from "react";
+
+enum TodoState {
+  ACTIVE="active",
+  COMPLETED="completed",
+  DELETED="deleted",
+}
+
+interface Todo {
+  id: string; 
+  value: string;
+  state: TodoState;
+}
+
+const INITIAL_TODOS: Todo[] = [
+  {id: "1", value: "I want to buy pizza", state: TodoState.ACTIVE},
+  {id: "2", value: "I want to buy pizza completed", state: TodoState.COMPLETED},
+  {id: "3", value: "I want to buy pizza deleted", state: TodoState.DELETED},
+]
+
+function changeStateTodo(
+  todoId: string,
+  todoState: TodoState,
+  setTodoCallback: Dispatch<React.SetStateAction<Todo[]>>,
+): void {
+  setTodoCallback(
+    (todos: Todo[]) => todos.map((todo: Todo) => {
+      if (todo.id === todoId) {
+        return {
+          ...todo,
+          state: todoState
+        };
+      }
+      return todo;
+    })
+  )
+}
+
+interface TodoComponentProp {
+  todo: Todo;
+  setTodos: Dispatch<React.SetStateAction<Todo[]>>;
+}
+
+const TodoComponent = memo(({todo, setTodos}: TodoComponentProp) => {
+  const tdStyle: CSSProperties = {
+    padding: "0.5rem",
+    borderBottom: "1px solid #ddd",
+  };
+  
+  const rowStyle: CSSProperties = {
+    backgroundColor: "#fff",
+  };
+
+  return (
+      <tr key={todo.id} style={rowStyle}>
+        <td style={tdStyle}>{todo.value}</td>
+        <td style={tdStyle}>
+          <span style={{ textTransform: "capitalize" }}>{todo.state}</span>
+        </td>
+        <td style={tdStyle}>
+          {todo.state !== TodoState.COMPLETED && (
+            <button onClick={() => changeStateTodo(todo.id, TodoState.COMPLETED, setTodos)}>
+              ✅
+            </button>
+          )}
+          {todo.state !== TodoState.DELETED && (
+            <button onClick={() => changeStateTodo(todo.id, TodoState.DELETED, setTodos)}>
+              🗑️
+            </button>
+          )}
+          {todo.state !== TodoState.ACTIVE && (
+            <button onClick={() => changeStateTodo(todo.id, TodoState.ACTIVE, setTodos)}>
+              🟢
+            </button>
+          )}
+        </td>
+      </tr>
+  )
+})
+
+const TODO = () => {
+  const [todos, setTodos] = useState<Todo[]>(INITIAL_TODOS);
+  const [todoFilter, setTodoFilters] = useState<TodoState | null>(null);
+  const inputRef =  useRef<HTMLInputElement>(null);
+
+  const style: CSSProperties = {
+    display: "flex",
+    justifyContent: "center",
+    alignItems: "center",
+    height: "100vh",
+    width: "100vw",
+  }
+
+  const filterTodos = useMemo(() => {
+    return todoFilter ? todos.filter((t) => t.state === todoFilter) : todos;
+  }, [todos, todoFilter]);
+
+  const handleCreateTodo = (event: React.FormEvent) => {
+    event.preventDefault();
+    const input = inputRef.current;
+    if (!input || !input.value.trim()) return;
+
+    const newValue = input.value.trim();
+    setTodos(
+      (todos) => [
+        ...todos,
+        {
+          value: newValue, 
+          id: crypto.randomUUID(),
+          state: TodoState.ACTIVE
+        }
+      ]
+    );
+    input.value = "";
+  }
+
+  const thStyle: CSSProperties = {
+    textAlign: "left",
+    padding: "0.5rem",
+    backgroundColor: "#f0f0f0",
+    borderBottom: "2px solid #ddd",
+  };
+
+  const buttonStyle = (isActive: boolean): CSSProperties => {
+    return {
+      padding: "0.5rem 1rem",
+      borderRadius: "6px",
+      border: "1px solid #ccc",
+      backgroundColor: isActive ? "#efefef" : "#fff",
+      marginBottom: "1rem",
+    }
+  };
+
+  return (
+    <div style={style}> 
+      <div>
+        <h1> TODOs: </h1>
+        <h2> Filter by: {todoFilter != null ? todoFilter : "All"}  </h2>
+        <div style={{display: "flex", width: "100%", justifyContent: "space-between"}}>
+          <button style={buttonStyle(todoFilter===TodoState.ACTIVE)} onClick={() => setTodoFilters(TodoState.ACTIVE)}> Active </button>
+          <button style={buttonStyle(todoFilter===TodoState.DELETED)} onClick={() => setTodoFilters(TodoState.DELETED)}> Deleted </button>
+          <button style={buttonStyle(todoFilter===TodoState.COMPLETED)} onClick={() => setTodoFilters(TodoState.COMPLETED)}> Completed </button>
+          <button style={buttonStyle(todoFilter===null)} onClick={() => setTodoFilters(null)}> ALL </button>
+        </div>
+        <table style={{ borderCollapse: "collapse", width: "100%", marginBottom: "1rem" }}>
+          <thead>
+            <tr>
+              <th style={thStyle}>Task</th>
+              <th style={thStyle}>Status</th>
+              <th style={thStyle}>Action</th>
+            </tr>
+          </thead>
+          <tbody>
+          {
+            filterTodos.map((todo) => (
+              <TodoComponent key={todo.id} todo={todo} setTodos={setTodos}/>
+            ))
+          }
+          </tbody>
+        </table>
+        <form onSubmit={handleCreateTodo}>
+          <input ref={inputRef} placeholder={"Add a new todo..."} style={{
+            padding: "0.5rem",
+            border: "1px solid #ccc",
+            borderRadius: "4px",
+            width: "300px",
+            marginRight: "0.5rem",            
+          }}
+          />
+          <button type="submit" style={{ padding: "0.5rem 1rem", borderRadius: "4px" }}>Add</button>
+        </form>
+      </div>
+    </div>
+  );
+}
+
+export {
+  TODO
+}