view react_games/src/TODO/main.tsx @ 186:8cf4ec5e2191 hg-web

Fixed merge conflict.
author MrJuneJune <me@mrjunejune.com>
date Fri, 23 Jan 2026 22:38:59 -0800
parents fb9bcd3145cb
children
line wrap: on
line source

/*
 * 📝 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
}