EditModal 컴포넌트 생성
App.tsx
- modalActive의 상태 데이터를 리덕스 스토어에서 useSelector로 가져오기
- modalActive가 true일 때 <EditModal> 컴포넌트 보여주고, 아닐 경우 null
const modalActive = useTypedSelector(state => state.boards.modalActive);
{modalActive ? <EditModal /> : null}
<BoardList
activeBoardId={activeBoardId}
setActiveBoardId={setActiveBoardId}
/>
EditModal.tsx
모달 데이터 수정
- 태스크 클릭 시 태스크에 담긴 데이터를 editModal에 뿌려줘야 하므로 리덕스 스토어에서 modal state를 가져온다.
- 받아온 데이터를 수정하기 위해 useState 사용 : 초기값으로 스토어에서 가져온 editingState 넣어줌
const editingState = useTypedSelector(state => state.modal);
// state에 넣어주기
const [data, setdata] = useState(editingState);
- EditModal에 input 항목인 '제목, 설명, 생성한 사람'에 각각 onChange 이벤트 건다.
- handleNameChange에 setData로 editingState 내용 얕은 복사 후 수정
➡️ 수정할 부분 e.target.value로 대입
- handleNameChange에 setData로 editingState 내용 얕은 복사 후 수정
const handleNameChange = (e:ChangeEvent<HTMLInputElement>) => {
// 얕은 복사 : 주소값 복사. 원본도 수정
// Object.assign(), 스프레드 연산자(...)등
setdata({
...data,
task: {
...data.task,
taskName:e.target.value
}
})
}
const handleDescriptionChange = (e:ChangeEvent<HTMLInputElement>) => {
setdata({
...data,
task: {
...data.task,
taskDescription:e.target.value
}
})
}
const handleAuthorChange = (e:ChangeEvent<HTMLInputElement>) => {
setdata({
...data,
task: {
...data.task,
taskOwner:e.target.value
}
})
}
<div className={title}>제목</div>
<input
className={input}
type='text'
value={data.task.taskName}
onChange={handleNameChange}
/>
<div className={title}>설명</div>
<input
className={input}
type='text'
value={data.task.taskDescription}
onChange={handleDescriptionChange}
/>
<div className={title}>생성한 사람</div>
<input
className={input}
type='text'
value={data.task.taskOwner}
onChange={handleAuthorChange}
/>
일 수정하기 기능 구현
- handleUpdate 함수 선언
- dispatch action 전달
➡️ 일 수정하기(updateTask), 로그 남기기(addLog), 모달창 닫기(setModalActive)
// EditModal.tsx
const handleUpdate = () => {
// 일 수정하기
dispatch(updateTask({
boardId: editingState.boardId, // 스토어 데이터(안바뀌는 부분)
listId: editingState.listId, // 스토어 데이터(안바뀌는 부분)
task: data.task // useState 데이터(바뀌는 부분)
}));
// 로그 남기기
dispatch(
addLog({
logId: uuidv4(),
logMessage: `일 수정하기: ${editingState.task.taskName}`,
logAuthor: 'admin',
logTimestamp: String(Date.now())
})
)
// 모달창 닫기
dispatch(setModalActive(false));
}
일 수정하기(updateTask)
- boardsSlice.ts 에서 컴포넌트에서 전달한 action.payload 가공
- boardArray에서 payload로 보내준 boardId값과 일치하는지 확인
- 해당 board 얕은 복사 ➡️ payload.listId 값과 일치한 list 얕은 복사 ➡️ payload.taskId와 같은 값 찾아서 task 수정
// boardsSlice.ts
type TAddTaskAction = {
boardId: string;
listId: string;
task: ITask;
}
updateTask: (state, {payload} : PayloadAction<TAddTaskAction>) => {
state.boardArray = state.boardArray.map(
board =>
board.boardId === payload.boardId
? {
...board,
lists: board.lists.map(
list =>
list.listId === payload.listId
? {
...list,
tasks: list.tasks.map(
task =>
task.taskId === payload.task.taskId
? payload.task
: task
)
}
: list
)
}
: board
)
},
로그 남기기(addLog)
loggerSlice.ts에서 action.payload를 logArray 배열에 추가
// loggerSlice.ts
reducers: {
addLog: (state, {payload}: PayloadAction<ILogItem>) => {
state.logArray.push(payload);
}
}
모달창 닫기(setModalActive)
boardsSlice.ts 에서 컴포넌트에서 전달한 action.payload 가공
setModalActive: (state, {payload} : PayloadAction<boolean>) => {
state.modalActive = payload;
}
일 삭제하기 기능 구현
- 변경 데이터 안 넘겨주고 id만 넘겨주면 된다.
- dispatch 액션으로 일 삭제하기(deleteTask) 넘겨주기 (나머지는 일 수정하기랑 똑같다.)
// 일 삭제하기
const handleDelete = () => {
dispatch(deleteTask({
boardId: editingState.boardId,
listId: editingState.listId,
taskId: editingState.task.taskId
}));
dispatch(
addLog({
logId: uuidv4(),
logMessage: `일 삭제하기: ${editingState.task.taskName}`,
logAuthor: 'admin',
logTimestamp: String(Date.now())
})
)
dispatch(setModalActive(false));
}
일 삭제하기(deleteTask)
- boardsSlice.ts 에서 컴포넌트에서 전달한 action.payload 가공
- 삭제 시에는 map 대신 filter 함수 사용해 taskId가 payload.taskId랑 다른 것만 남긴다.
// boardsSlice.ts
type TDeleteTaskAction = {
boardId: string;
listId: string;
taskId: string;
}
deleteTask: (state, {payload} : PayloadAction<TDeleteTaskAction>) => {
state.boardArray = state.boardArray.map(
board =>
board.boardId === payload.boardId
? {
...board,
lists: board.lists.map(
list =>
list.listId === payload.listId
? {
...list,
tasks: list.tasks.filter(
task =>
task.taskId !== payload.taskId
)
}
: list
)
}
: board
)
},
모달 입력 폼 닫기 기능 구현
- 모달의 x버튼 클릭 시 dispatch로 setModalActive(false) 액션 함수 전달
// EditModal.ts
const handleCloseButton = () => {
dispatch(setModalActive(false));
}
LoggerModal 컴포넌트 생성
App.tsx
- LoggetModal 오픈 여부를 useState로 isLoggerOpen 변수 생성
- isLoggerOpen 이 true면 <LoggerModal /> 컴포넌트 띄워주고 props로 setIsLoggerOpen 넘겨준다.
- 활동목록 버튼에 onClick= setIsLoggerOpen(!isLoggerOpen)으로 토글
const [isLoggerOpen, setIsLoggerOpen] = useState(false);
{isLoggerOpen ? <LoggerModal setIsLoggerOpen={setIsLoggerOpen} /> : null}
<button className={loggerButton} onClick={() => setIsLoggerOpen(!isLoggerOpen)}>
{isLoggerOpen ? "활동목록 숨기기" : "활동목록 보이기"}
</button>
LoggerModal.tsx
- props로 받은 setIsLoggerOpen에 대한 타입 지정
- useSelector로 리덕스 스토어에서 logArray 데이터 가져오기
- 받아온 데이터 logs를 map 돌려서 <LogItem / > 컴포넌트 뿌려주기
- key 셋팅 잊지 않기
- logItem 뿌려주기 위한 데이터 props로 넘겨주기
- X아이콘 버튼에 props로 받아온 모달창 닫는 setIsLoggerOpen함수 이벤트 걸기
type TLoggerModalProps = {
setIsLoggerOpen: React.Dispatch<React.SetStateAction<boolean>>
}
const LoggerModal : FC<TLoggerModalProps> = ({
setIsLoggerOpen
}) => {
const logs = useTypedSelector(state => state.logger.logArray);
return (
<div className={wrapper}>
<div className={modalWindow}>
<div className={header}>
<div className={title}>활동 기록</div>
<FiX className={closeButton} onClick={() => setIsLoggerOpen(false)}/>
</div>
<div className={body}>
{logs.map((log, index) => (
<LogItem key={log.logId} logItem={log} />
))}
</div>
</div>
</div>
)
}
LogItem 컴포넌트 생성
- props로 받은 logItem에 대한 타입 지정
- 로그 기록이므로 로그를 남긴 시간이 중요 ➡️ timeOffset 설정
- 분수가 0보다 크면 분 설정, 초수가 0보다 크면 초 설정
- 사람 아이콘 : BsFillPersonFill
- logItem 데이터에서 생성자(logAuthor), 내용(logMessage) 가져와서 뿌리기
- 로그 시간 기록 : showOffsetTime
type TLogItemProps = {
logItem: ILogItem;
}
const LogItem : FC<TLogItemProps> = ({
logItem
}) => {
// 언제 올렸는지 시간 셋팅하기
let timeOffset = new Date(Date.now() - Number(logItem.logTimestamp));
console.log('timeOffset', timeOffset);
console.log('timeOffset.getMinutes()', timeOffset.getMinutes());
console.log('timeOffset.getSeconds()', timeOffset.getSeconds());
const showOffsetTime = `
${timeOffset.getMinutes() > 0 ? `${timeOffset.getMinutes()}m` : ""}
${timeOffset.getSeconds() > 0 ? `${timeOffset.getSeconds()}s` : ""}
${timeOffset.getSeconds() === 0 ? `just now` : ""}
`
return (
<div className={logItemWrap}>
<div className={author}>
{/* 사람아이콘 */}
<BsFillPersonFill />
{logItem.logAuthor}
</div>
<div className={message}>{logItem.logMessage}</div>
<div className={date}>{showOffsetTime}</div>
</div>
)
}
게시판 삭제 기능 생성
- 게시판 1개일 경우 삭제 안 되고 알림창 띄운다.
- 삭제 시 활성화 인덱스를 변경한다.
App.tsx
게시판 삭제하기 함수 내용
- 게시판 수가 1이하면 '최소 게시판 개수는 한 개입니다.' 알림창 뜨기
- 게시판 수가 1 초과면 action 함수 전달
➡️ 게시판삭제(deleteBoard), 로그 생성(addLog) - 삭제 시 활성화 인덱스 변경
- findIndex로 getActiveBoard의 boardId를 삭제할 boardId로 정의하고 해당 인덱스 찾기
- 삭제할 게시판 인덱스가 0이면 다음 인덱스, 0아니면 이전 인덱스 리턴
- 게시판 활성화 함수 setActiveBoardId에 리턴한 인덱스값 넣어 activeBoardId 재설정
// App.tsx
// 게시판 삭제하기
const handleDeleteBoard = () => {
if(boards.length > 1){
dispatch(deleteBoard({boardId: getActiveBoard.boardId})); // 게시판 삭제
// 로그 생성
dispatch(
addLog({
logId: uuidv4(),
logMessage: `게시판 지우기: ${getActiveBoard.boardName}`,
logAuthor: 'admin',
logTimestamp: String(Date.now())
})
);
// 삭제 시 활성화 인덱스 변경
const newIndexToSet = () => {
// 삭제 인덱스 찾기
const indexToBeDeleted = boards.findIndex(
board => board.boardId === getActiveBoard.boardId
);
// 삭제 인덱스가 0이면 그 다음, 아니면 그 전 인덱스 리턴
return indexToBeDeleted === 0 ? indexToBeDeleted + 1 : indexToBeDeleted - 1;
}
// 활성화 아이디 셋팅
setActiveBoardId(boards[newIndexToSet()].boardId);
} else{
alert('최소 게시판 개수는 한 개입니다.');
}
}
게시판 삭제(deleteBoard)
- boardsSlice.ts 에서 컴포넌트에서 전달한 action.payload 가공
- 삭제 시에는 map 대신 filter 함수 사용해 taskId가 payload.boardId랑 다른 것만 남긴다.
// boardsSlice.ts
deleteBoard: (state, {payload} : PayloadAction<TDeleteBoardAction>) => {
state.boardArray = state.boardArray.filter(
board => board.boardId !== payload.boardId
)
},
리덕스 스토어 활용법과 액션 함수 활용, props로 데이터 주고받는 것에 조금씩 익숙해지고 있다.
'프로젝트 > Task 어플리케이션' 카테고리의 다른 글
| 1120 Firebase로 배포하기 (0) | 2025.11.21 |
|---|---|
| 1120 드래그 앤 드롭 기능 만들기 (0) | 2025.11.20 |
| 1118 Board List 생성, Side Form 생성, Style 생성 - vanilla extract (0) | 2025.11.18 |
| 1117 리액트를 이용한 태스크 정리 앱 만들기 - Vite, Redux 셋팅, Redux Hooks 타입 지정, 전역 스타일 생성 (0) | 2025.11.17 |