view react_games/src/TODO/main.tsx @ 71:75de5903355c

Giagantic changes that update Dowa library to be more align with stb style array and hashmap. Updated Seobeo to be caching on server side instead of file level caching. Deleted bunch of things I don't really use.
author June Park <parkjune1995@gmail.com>
date Sun, 28 Dec 2025 20:34:22 -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
}