목차
폴더구조
Next.js를 사용하였으며 /todolist 경로 이동시 사용할 page.tsx와
containers/TodoList를 사용하였다.
파일 내용
/src/app/todolist/page.tsx
import TodoList from '@/containers/TodoList/TodoList';
export default function Page() {
return <TodoList />;
}
/todolist 경로에서 보여줄 컨테이너
/src/containers/TodoList/TodoList.styles.ts
import styled from 'styled-components';
const StyledTodoList = styled.div`
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
height: 100vh;
.TodoList__wrapper {
border: 1px solid black;
padding: 10px;
}
.TodoList__cancel {
text-decoration: line-through;
}
`;
export default StyledTodoList;
TodoList 컨테이너의 스타일을 담당할 파일
/src/containers/TodoList/TodoList.tsx
선언부
'use client';
import { useState, ChangeEvent } from 'react';
import { v4 as uuidv4 } from 'uuid';
import StyledTodoList from './TodoList.styles';
type StatusType = '미시작' | '진행중' | '완료';
type SortType = 'Ascending' | 'Descending';
interface Todo {
id: number;
text: string;
status: StatusType;
date: Date;
}
const INITIAL_TODOS: Todo[] = [];
const SORT_TEXT = {
Ascending: '오름차순',
Descending: '내림차순',
};
- use client : useState등 클라이언트 컴포넌트에만 사용가능한 함수가 있기 때문에 클라이언트 컴포넌트 선언을 위해 사용
- uuid : Array.map으로 반환되는 react컴포넌트의 고유한 Key를 제공하기위해 사용
- StyledTodoList : TodoList의 styled-components 파일
- type StatusType : 할일마다 dropdown으로 지정하기 위한 타입 선언
- type SortType : 오름차순, 내림차순을 적용하기위한 선언
- interface Todo : 할일의 인터페이스 선언
- INITIAL_TODOS : 할일 배열의 초기값 선언
- SORT_TEXT : 영어로 되어있는 SortType의 매치되는 한글 선언
컴포넌트내 선언
const [todoList, setTodoList] = useState<Todo[]>(INITIAL_TODOS);
const [input, setInput] = useState<string>('');
const [sort, setSort] = useState<SortType>('Ascending');
const [filter, setFilter] = useState<string>('');
일정 추가
/**
* 할일을 추가하는 함수
*/
const addTodo = () => {
const today = new Date();
setTodoList([
...todoList,
{ id: uuidv4(), text: input, status: '미시작', date: today },
]);
setInput('');
};
일정 제거
/**
* 할일을 제거하는 함수
* @param id
*/
const deleteTodo = (id: number) => {
setTodoList((prevTodo) => prevTodo.filter((todo) => todo.id !== id));
};
일정의 상태를 완료로 만들기
/**
* 할일을 완료로 만드는 함수
* @param id
*/
const markTodoAsCompleted = (id: number) => {
setTodoList((prevTodolist) =>
prevTodolist.map((todo) =>
todo.id === id ? { ...todo, status: '완료' } : todo,
),
);
};
일정의 상태를 변경
/**
* 할일을 status에 맞게 변경하는 함수
* @param id
* @param status
*/
const changeTodoStatus = (id: number, status: StatusType) => {
setTodoList((prevTodolist) =>
prevTodolist.map((todo) => (todo.id === id ? { ...todo, status } : todo)),
);
};
일정을 정렬
/**
* 할일을 정렬하는 함수
*/
const toggleSortOrder = () => {
setSort((prevSort) =>
prevSort === 'Ascending' ? 'Descending' : 'Ascending',
);
};
각 Element의 Event들
const handleChangeInput = (e: ChangeEvent<HTMLInputElement>) => {
setInput(e.target.value);
};
const handleChangeFilter = (e: ChangeEvent<HTMLInputElement>) => {
setFilter(e.target.value);
};
const handleClickAdd = () => {
addTodo();
};
const handleClickDelete = (id: number) => {
deleteTodo(id);
};
const handleClickComplete = (id: number) => {
markTodoAsCompleted(id);
};
const handleClickSort = () => {
toggleSortOrder();
};
const handleChangeStatus = (id: number, status: StatusType) => {
changeTodoStatus(id, status);
};
코드 전체
'use client';
import { useState, ChangeEvent } from 'react';
import { v4 as uuidv4 } from 'uuid';
import StyledTodoList from './TodoList.styles';
type StatusType = '미시작' | '진행중' | '완료';
type SortType = 'Ascending' | 'Descending';
interface Todo {
id: number;
text: string;
status: StatusType;
date: Date;
}
const INITIAL_TODOS: Todo[] = [];
const SORT_TEXT = {
Ascending: '오름차순',
Descending: '내림차순',
};
export default function TodoList() {
const [todoList, setTodoList] = useState<Todo[]>(INITIAL_TODOS);
const [input, setInput] = useState<string>('');
const [sort, setSort] = useState<SortType>('Ascending');
const [filter, setFilter] = useState<string>('');
/**
* 할일을 추가하는 함수
*/
const addTodo = () => {
const today = new Date();
setTodoList([
...todoList,
{ id: uuidv4(), text: input, status: '미시작', date: today },
]);
setInput('');
};
/**
* 할일을 제거하는 함수
* @param id
*/
const deleteTodo = (id: number) => {
setTodoList((prevTodo) => prevTodo.filter((todo) => todo.id !== id));
};
/**
* 할일을 완료로 만드는 함수
* @param id
*/
const markTodoAsCompleted = (id: number) => {
setTodoList((prevTodolist) =>
prevTodolist.map((todo) =>
todo.id === id ? { ...todo, status: '완료' } : todo,
),
);
};
/**
* 할일을 status에 맞게 변경하는 함수
* @param id
* @param status
*/
const changeTodoStatus = (id: number, status: StatusType) => {
setTodoList((prevTodolist) =>
prevTodolist.map((todo) => (todo.id === id ? { ...todo, status } : todo)),
);
};
/**
* 할일을 정렬하는 함수
*/
const toggleSortOrder = () => {
setSort((prevSort) =>
prevSort === 'Ascending' ? 'Descending' : 'Ascending',
);
};
const handleChangeInput = (e: ChangeEvent<HTMLInputElement>) => {
setInput(e.target.value);
};
const handleChangeFilter = (e: ChangeEvent<HTMLInputElement>) => {
setFilter(e.target.value);
};
const handleClickAdd = () => {
addTodo();
};
const handleClickDelete = (id: number) => {
deleteTodo(id);
};
const handleClickComplete = (id: number) => {
markTodoAsCompleted(id);
};
const handleClickSort = () => {
toggleSortOrder();
};
const handleChangeStatus = (id: number, status: StatusType) => {
changeTodoStatus(id, status);
};
return (
<StyledTodoList>
<div className="TodoList__wrapper">
<h1>Todo List</h1>
<div style={{ display: 'flex', flexDirection: 'column', gap: '10px' }}>
<div style={{ display: 'flex', gap: '10px' }}>
<p>작업 추가 영역 : </p>
<input
value={input}
onChange={handleChangeInput}
placeholder="입력 필드"
/>
<button type="button" onClick={handleClickAdd}>
작업 추가
</button>
</div>
<div style={{ display: 'flex', gap: '10px' }}>
<p>검색 영역: </p>
<input
value={filter}
onChange={handleChangeFilter}
placeholder="검색 항목을 넣어주세요"
/>
</div>
<div style={{ display: 'flex', gap: '10px' }}>
<p>정렬 상태 :</p>
<button type="button" onClick={handleClickSort}>
{SORT_TEXT[sort]}
</button>
</div>
<div>----------------------</div>
{todoList &&
todoList
.filter((todo) => todo.text.includes(filter))
.sort((a, b) => {
if (sort === 'Ascending') {
return a.text.localeCompare(b.text);
}
return b.text.localeCompare(a.text);
})
.map((list, index) => {
const { id, text, status, date } = list;
return (
<div
key={`${index + Math.random()}`}
style={{ display: 'flex', gap: '20px' }}
className={list.status === '완료' ? 'TodoList__cancel' : ''}
>
<p>{text}</p>
<select
onChange={(e) =>
handleChangeStatus(id, e.target.value as StatusType)
}
value={status}
>
<option value="미완성">미시작</option>
<option value="진행중">진행중</option>
<option value="완료">완료</option>
</select>
<button
type="button"
onClick={() => {
handleClickComplete(id);
}}
>
완료
</button>
<p>{date.toUTCString()}</p>
<button type="button" onClick={() => handleClickDelete(id)}>
제거
</button>
</div>
);
})}
</div>
</div>
</StyledTodoList>
);
}
실행 모습
'프론트엔드' 카테고리의 다른 글
Next.js + TypeScript + @reduxjs/toolkit 사용하기 < 2 / 2 > 개인기록 (1) | 2024.05.22 |
---|---|
Next.js + TypeScript + @reduxjs/toolkit 사용하기 < 1 / 2 > 개인기록 (0) | 2024.05.22 |