Skip to content

Create Week09 Mission 1,2,3#78

Open
SJ01-max wants to merge 1 commit into
mainfrom
SJ01-max/week09
Open

Create Week09 Mission 1,2,3#78
SJ01-max wants to merge 1 commit into
mainfrom
SJ01-max/week09

Conversation

@SJ01-max
Copy link
Copy Markdown
Contributor

@SJ01-max SJ01-max commented May 26, 2026

📝 미션 번호

9주차 practice01

📋 구현 사항

  • useReducer를 활용한 직무 변경 실습 구현
  • 입력값이 카드메이커일 때만 직무 변경 가능하도록 reducer 로직 작성
  • 잘못된 입력값 제출 시 에러 메시지 출력
  • useState와 useReducer 예제 컴포넌트 분리 작성

📎 스크린샷

image

📝 미션 번호

9주차 Misson 1

📋 구현 사항

  • Redux Toolkit 기반 장바구니 전역 상태 관리 구현
  • cartItems Mock Data 분리 및 TypeScript 타입 정의
  • increase, decrease, removeItem, clearCart, calculateTotals 액션 구현
  • 전체 수량 및 총 금액 자동 계산
  • Tailwind CSS로 장바구니 리스트, 수량 버튼, 초기화 버튼 UI 구현

📎 스크린샷

image

📝 미션 번호

9주차 Misson 2

📋 구현 사항

  • cartSlice와 modalSlice를 분리하여 Redux Store에 등록
  • 모달 열림/닫힘 상태를 Redux Toolkit으로 전역 관리
  • 전체 삭제 버튼 클릭 시 확인 모달 표시
  • 모달 아니요 클릭 시 모달 닫기
  • 모달 네 클릭 시 clearCart() 실행 후 모달 닫기
  • 모달 상태 제어에서 useState 제거

📎 스크린샷

image

📝 미션 번호

9주차 Misson 3

📋 구현 사항

  • Redux Toolkit 구조를 Zustand 전역 Store 구조로 전환
  • 장바구니 상태와 모달 상태를 하나의 Zustand Store에서 관리
  • increase, decrease, removeItem, clearCart, calculateTotals 로직을 Zustand 액션으로 구현
  • openModal, closeModal, confirmClearCart 액션 구현
  • Redux Provider, Slice, Store 설정 제거
  • 기존 Mission2와 동일한 UI 및 동작 유지

📎 스크린샷

image

✅ 체크리스트

  • Merge 하려는 브랜치가 올바르게 설정되어 있나요?
  • 로컬에서 실행했을 때 에러가 발생하지 않나요?
  • 불필요한 주석이 제거되었나요?
  • 코드 스타일이 일관적인가요?

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 26, 2026

📝 Walkthrough

Walkthrough

네 개의 주요 React 학습 프로젝트가 추가됨: Redux 기반 단순 장바구니(Mission01), Redux + Modal 장바구니(Mission02), Zustand 기반 장바구니(Mission03), useReducer 연습(Practice01). 각 프로젝트는 TypeScript, Vite, Tailwind CSS로 구성된 완전한 SPA 애플리케이션.

Changes

Redux Cart - Simple (Mission01)

Layer / File(s) Summary
Redux Store & Cart Slice
src/store/cartSlice.ts, src/store/store.ts, src/hooks/redux.ts
cartSliceincrease, decrease, removeItem, clearCart, calculateTotals 리듀서로 장바구니 항목 관리; getTotals 헬퍼로 수량과 총액 집계. configureStore로 스토어 생성 후 RootState, AppDispatch 타입 내보냄.
Cart Components & Data
src/App.tsx, src/components/CartContainer.tsx, src/components/CartItem.tsx, src/constants/cartItems.ts
App은 헤더에 amount 표시; CartContainer는 빈/채운 상태 분기 및 총액 원화 포맷팅; CartItem-/+ 버튼으로 수량 조절; 고정 곡 목록 상수.
Vite/TypeScript Configuration
.gitignore, index.html, package.json, tsconfig.*.json, vite.config.ts, src/main.tsx, src/index.css
HTML 진입점, Redux/React/Tailwind 의존성 선언, TS 컴파일 설정(ES2022, JSX, strict 모드), Vite React/Tailwind 플러그인 활성화, 전역 스타일.

Redux Cart with Modal (Mission02)

Layer / File(s) Summary
Redux Store - Dual Slices
src/features/cart/cartSlice.ts, src/features/modal/modalSlice.ts, src/store/store.ts, src/hooks/redux.ts
cartSlice는 장바구니 항목/집계 관리; modalSliceisOpen boolean과 openModal/closeModal 액션. configureStore로 두 리듀서 결합 후 타입 내보냄.
Cart + Modal Components
src/App.tsx, src/components/CartContainer.tsx, src/components/CartItem.tsx, src/components/Modal.tsx
App에서 state.modal.isOpen 조건부 렌더링; Modal은 확인 시 clearCart()closeModal() 순차 디스패치; CartContainer의 "전체 삭제" 버튼이 openModal() 호출.
Cart Items & Configuration
src/constants/cartItems.ts, .gitignore, index.html, package.json, tsconfig.*.json, vite.config.ts, src/main.tsx, src/index.css
고정 곡 목록 상수; Vite/TypeScript/Redux 설정 및 HTML 진입점, 부트스트랩 렌더링, 전역 스타일.

Zustand Cart Implementation (Mission03)

Layer / File(s) Summary
Zustand Store
src/store/usePlaylistStore.ts
단일 usePlaylistStore 훅으로 cartItems, amount, total, isOpen 상태 및 increase, decrease, clearCart, openModal, closeModal, confirmClearCart 액션 구현; getTotals 헬퍼로 집계.
Cart Components with Zustand
src/App.tsx, src/components/CartContainer.tsx, src/components/CartItem.tsx, src/components/Modal.tsx
usePlaylistStore()로 상태/액션 선택; App은 조건부 Modal 렌더링; 장바구니 UI 및 삭제 확인 흐름 동일.
Cart Items & Configuration
src/constants/cartItems.ts, .gitignore, index.html, package.json, tsconfig.*.json, vite.config.ts, src/main.tsx, src/index.css
고정 곡 목록 상수; Vite/TypeScript/Zustand 설정 및 HTML 진입점, 부트스트랩, 전역 스타일.

useReducer Learning (Practice01)

Layer / File(s) Summary
useReducer Components
src/components/UseReducerCompany.tsx, src/components/UseReducerPage.tsx, src/App.tsx
UseReducerCompany: reducer로 부서명(department) 검증 + 에러 상태 관리; 입력 trim() 후 허용 직무 확인. UseReducerPage: useState vs useReducer 카운터 비교.
Global Styles & Configuration
src/style.css, .gitignore, index.html, package.json, tsconfig.*.json, vite.config.ts, src/main.tsx
전역 box-sizing 리셋, 회사 페이지(중앙 정렬 레이아웃, 헤딩, 폼 그리드, 버튼 호버/액티브 전환), 카운터 페이지, 반응형 미디어 쿼리(760px); Vite/TypeScript 설정, HTML 진입점, 부트스트랩.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~15 minutes

Suggested reviewers

  • wantkdd

Poem

🐰 Redux에서 Zustand로, 카트는 계속 굴러가고,
모달 열고 닫고, 부서 검증하며,
타입은 엄격하고, 스타일은 반응형,
네 개 프로젝트가 어깨를 나란히 한다! 🛒✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed PR 제목이 'Create Week09 Mission 1,2,3'로 3개 미션을 모두 다루고 있으며, 개발자의 관점에서 주요 변경사항(3주차 미션)을 명확하게 요약하고 있습니다.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description check ✅ Passed PR 설명이 필요한 주요 섹션을 모두 포함하고 있으며, 세 가지 미션별로 구현 사항을 명확하게 기술하고 스크린샷과 체크리스트를 완비하고 있습니다.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch SJ01-max/week09

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@SJ01-max SJ01-max requested a review from wantkdd May 26, 2026 06:45
@SJ01-max SJ01-max self-assigned this May 26, 2026
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 8

🧹 Nitpick comments (5)
Week09/Mission03/src/App.tsx (1)

6-6: ⚡ Quick win

Zustand 전체 스토어 구독 대신 selector 구독으로 바꿔주세요.

Line 6은 스토어 전체를 구독해서, amount/isOpen과 무관한 변경에도 App이 다시 렌더링됩니다. selector 분리 구독이 더 안정적입니다.

제안 diff
 export default function App() {
-  const { amount, isOpen } = usePlaylistStore();
+  const amount = usePlaylistStore((state) => state.amount);
+  const isOpen = usePlaylistStore((state) => state.isOpen);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Week09/Mission03/src/App.tsx` at line 6, The App component currently
subscribes to the entire Zustand store via usePlaylistStore(), causing unrelated
store changes to rerender App; instead subscribe to only the needed slices by
replacing the whole-store call with two selector subscriptions such as using
usePlaylistStore(state => state.amount) for amount and usePlaylistStore(state =>
state.isOpen) for isOpen (or a single selector that returns an object and
shallow compares if you prefer), updating references in App to those selectors
to avoid full-store re-renders.
Week09/Mission03/src/components/CartContainer.tsx (1)

7-7: ⚡ Quick win

CartContainer도 selector 기반으로 구독 범위를 줄여주세요.

Line 7에서 전체 스토어를 구독하고 있어 불필요한 리스트 재렌더링이 발생할 수 있습니다.

제안 diff
 export default function CartContainer() {
-  const { amount, cartItems, openModal, total } = usePlaylistStore();
+  const amount = usePlaylistStore((state) => state.amount);
+  const cartItems = usePlaylistStore((state) => state.cartItems);
+  const total = usePlaylistStore((state) => state.total);
+  const openModal = usePlaylistStore((state) => state.openModal);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Week09/Mission03/src/components/CartContainer.tsx` at line 7, CartContainer
is subscribing to the whole store by destructuring usePlaylistStore() which
causes unnecessary re-renders; change to selector-based subscriptions e.g.
replace const { amount, cartItems, openModal, total } = usePlaylistStore(); with
selector calls that pick only needed slices (either a single object selector:
usePlaylistStore(s => ({ amount: s.amount, cartItems: s.cartItems, openModal:
s.openModal, total: s.total }), or separate calls like usePlaylistStore(s =>
s.cartItems) and usePlaylistStore(s => s.amount) etc.), and if needed pass a
shallow/equality function to avoid re-rendering when values are referentially
equal — refer to CartContainer and usePlaylistStore for the exact symbols to
change.
Week09/Mission01/src/constants/cartItems.ts (1)

1-7: ⚡ Quick win

pricestring 대신 number로 관리하는 쪽이 안전합니다.

금액을 숫자 타입으로 정의하면 계산 시 런타임 파싱을 제거할 수 있고, NaN 리스크를 줄일 수 있습니다.

리팩터 예시
 export interface CartItem {
   id: string;
   title: string;
   singer: string;
-  price: string;
+  price: number;
   img: string;
   amount: number;
 }

 const cartItems: CartItem[] = [
   {
@@
-    price: "25000",
+    price: 25000,
@@
-    price: "18000",
+    price: 18000,
@@
-    price: "28000",
+    price: 28000,
@@
-    price: "20000",
+    price: 20000,
@@
-    price: "30000",
+    price: 30000,
@@
-    price: "12000",
+    price: 12000,
@@
-    price: "32000",
+    price: 32000,
@@
-    price: "22000",
+    price: 22000,
@@
-    price: "20000",
+    price: 20000,
@@
-    price: "25000",
+    price: 25000,
@@
-    price: "23000",
+    price: 23000,
@@
-    price: "21000",
+    price: 21000,

Also applies to: 15-106

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Week09/Mission01/src/constants/cartItems.ts` around lines 1 - 7, Change the
CartItem type so its price field is a number (update interface CartItem in
constants/cartItems.ts from price: string to price: number) and then update all
sites that construct or read CartItem.price (parsers, initial state, action
payloads, components, and any calculation logic) to use numeric values (parse
incoming strings to Number when creating CartItem objects and remove
string-to-number conversions where totals are computed). Ensure tests/usage that
assumed string prices are updated accordingly to use numbers and handle NaN
validation at creation time.
Week09/Mission02/src/components/Modal.tsx (1)

14-42: ⚡ Quick win

모달 키보드 접근성(초기 포커스/ESC 닫기)을 보강하는 것이 좋습니다.

현재도 동작은 하지만, 모달 오픈 시 포커스 이동과 Escape 닫기를 추가하면 접근성이 더 안정적입니다.

♿ 제안 수정안
+import { useEffect, useRef, type KeyboardEvent } from "react";
 import { clearCart } from "../features/cart/cartSlice";
 import { closeModal } from "../features/modal/modalSlice";
 import { useAppDispatch } from "../hooks/redux";
@@
 export default function Modal() {
   const dispatch = useAppDispatch();
+  const cancelButtonRef = useRef<HTMLButtonElement>(null);
+
+  useEffect(() => {
+    cancelButtonRef.current?.focus();
+  }, []);
 
   const handleConfirm = () => {
     dispatch(clearCart());
     dispatch(closeModal());
   };
+
+  const handleKeyDown = (event: KeyboardEvent<HTMLElement>) => {
+    if (event.key === "Escape") {
+      dispatch(closeModal());
+    }
+  };
@@
       <section
         role="dialog"
         aria-modal="true"
         aria-labelledby="delete-cart-title"
+        tabIndex={-1}
+        onKeyDown={handleKeyDown}
         className="w-full max-w-[294px] rounded-lg bg-white px-8 py-9 text-center shadow-xl"
       >
@@
           <button
+            ref={cancelButtonRef}
             type="button"
             onClick={() => dispatch(closeModal())}
             className="rounded-md bg-slate-200 px-6 py-3 text-lg font-bold text-slate-700 transition hover:bg-slate-300"
           >
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Week09/Mission02/src/components/Modal.tsx` around lines 14 - 42, The modal
needs keyboard accessibility: add an initial focus and Escape-to-close behavior.
In the Modal component, create a ref for the primary interactive element (e.g.,
noButtonRef for the "아니요" button) and in a useEffect set
focus(noButtonRef.current) when the modal mounts and return a cleanup to restore
focus if desired; also register a keydown listener in that same useEffect that
listens for "Escape" and calls dispatch(closeModal()), and remove the listener
on unmount. Keep existing handlers (handleConfirm, dispatch(closeModal())) but
ensure the role="dialog" container has aria-modal="true" (already present) and
that the focused element is inside the dialog so keyboard users can operate it
immediately.
Week09/Mission02/src/constants/cartItems.ts (1)

5-5: ⚡ Quick win

price는 문자열 대신 숫자 타입으로 고정하는 게 안전합니다.

Line 5처럼 price: string으로 두면 합계 계산 시 매번 파싱이 필요하고, 포맷이 섞이는 순간 NaN 리스크가 생깁니다. 장바구니 도메인에서는 숫자 모델이 더 안정적입니다.

🔧 제안 수정안
 export interface CartItem {
   id: string;
   title: string;
   singer: string;
-  price: string;
+  price: number;
   img: string;
   amount: number;
 }
@@
-    price: "25000",
+    price: 25000,
@@
-    price: "18000",
+    price: 18000,
@@
-    price: "28000",
+    price: 28000,
@@
-    price: "20000",
+    price: 20000,
@@
-    price: "30000",
+    price: 30000,
@@
-    price: "12000",
+    price: 12000,
@@
-    price: "32000",
+    price: 32000,
@@
-    price: "22000",
+    price: 22000,
@@
-    price: "20000",
+    price: 20000,
@@
-    price: "25000",
+    price: 25000,
@@
-    price: "23000",
+    price: 23000,
@@
-    price: "21000",
+    price: 21000,

Also applies to: 15-15, 23-23, 31-31, 39-39, 47-47, 55-55, 63-63, 71-71, 79-79, 87-87, 95-95, 103-103

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Week09/Mission02/src/constants/cartItems.ts` at line 5, The cart item type
currently declares price as a string which risks NaN and parsing overhead;
change the price property in the cart item type/constant definitions from string
to number (i.e., replace all occurrences of "price: string" with "price:
number") and ensure any literal values in the corresponding cart item objects
are numeric (no quotes) so totals and arithmetic (e.g., sum/total calculations)
operate on numbers without parsing; update any related initialization or tests
that supply string prices to use numeric literals.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@Week09/Mission01/src/components/CartItem.tsx`:
- Around line 27-29: The price is displayed with a hardcoded "$" in the CartItem
component; replace this with a Korean won formatted string (use
Intl.NumberFormat with 'ko-KR' and currency 'KRW' or a shared formatPrice
utility) and render the formatted value instead of `${item.price}`; update the
JSX in the CartItem render where item.price is used so all prices match the 원
currency format.

In `@Week09/Mission01/src/store/cartSlice.ts`:
- Around line 36-61: The increase, decrease and removeItem reducers update
state.cartItems but do not update aggregated fields (amount/total), causing
inconsistent state; fix by invoking the slice's existing calculateTotals logic
after each mutation (or recomputing totals inline) so totals stay in sync—e.g.,
after modifying state.cartItems in increase, decrease, and removeItem call
calculateTotals(state) (or update state.total/state.amount using the same
reduction logic) to keep the invariants.

In `@Week09/Mission02/index.html`:
- Line 6: 제목이 잘못되어 있습니다: index.html의 <title> 태그 값이 "Week09 Mission1"으로 되어 있으니 이를
현재 프로젝트명에 맞게 "Week09 Mission02" 또는 단순히 "Mission02"로 변경하세요; 대상은 index.html의
<title> 요소이며 파일 내 <title> 태그만 수정하면 됩니다.

In `@Week09/Mission02/src/components/CartItem.tsx`:
- Around line 27-29: The CartItem component is rendering item.price with a
leading "$" which conflicts with the total's Korean won formatting; update the
CartItem.tsx rendering in the CartItem component to format item.price using the
same Korean won formatter used for the total (e.g., Intl.NumberFormat('ko-KR', {
style: 'currency', currency: 'KRW' }) or the project's currency helper) so the
list rows display prices in 원 and match the total; locate the JSX that currently
shows "${item.price}" and replace it with the shared KRW formatting call for
item.price.

In `@Week09/Mission02/src/features/cart/cartSlice.ts`:
- Around line 36-61: The reducers increase, decrease, and removeItem update
cartItems but do not update derived totals (amount and total), causing a
temporary state mismatch; fix by ensuring each of these reducers also updates
state.amount and state.total immediately after modifying cartItems (either by
invoking the existing calculateTotals reducer logic directly like
calculateTotals(state) or by recomputing amount/total inline) so the store
remains consistent; update the implementations of increase, decrease, and
removeItem to recalc totals right after changing cartItems.

In `@Week09/Mission03/index.html`:
- Line 6: HTML 문서의 <title> 요소 값이 "Week09 Mission1"으로 잘못되어 현재 프로젝트와 불일치합니다;
index.html의 <title> 내용을 현재 미션명(예: "Week09 Mission03" 또는 프로젝트명)으로 변경하여 브라우저 탭과
북마크에 맞게 정확한 미션명을 반영하세요.

In `@Week09/Mission03/src/components/CartItem.tsx`:
- Line 27: The currency symbol in the CartItem component is inconsistent:
replace the hardcoded "$" at the interpolation of item.price in CartItem.tsx
with the same currency/format used elsewhere (Korean won) or use a centralized
formatter; update the interpolation that currently reads `${item.price}` to
render with "원" (e.g., `${item.price}원`) or call the shared formatter function
(e.g., formatCurrency(item.price)) so the displayed unit matches the cart total
and app-wide currency.

In `@Week09/Practice01/src/components/UseReducerCompany.tsx`:
- Around line 59-70: Add accessible labeling and a live error region: give the
text input (currently bound to the department state via value={department} and
onChange={handleChange}) a visible label element tied with htmlFor/id, mark the
input with aria-invalid={Boolean(state.error)} and add aria-describedby that
points to the error message id; convert the error <p> (which currently renders
{state.error}) into a role="alert" or aria-live="assertive" region with a stable
id so screen readers are notified when state.error changes. Ensure identifiers
referenced (department input, handleChange, state.error, and the error <p>) are
updated consistently.

---

Nitpick comments:
In `@Week09/Mission01/src/constants/cartItems.ts`:
- Around line 1-7: Change the CartItem type so its price field is a number
(update interface CartItem in constants/cartItems.ts from price: string to
price: number) and then update all sites that construct or read CartItem.price
(parsers, initial state, action payloads, components, and any calculation logic)
to use numeric values (parse incoming strings to Number when creating CartItem
objects and remove string-to-number conversions where totals are computed).
Ensure tests/usage that assumed string prices are updated accordingly to use
numbers and handle NaN validation at creation time.

In `@Week09/Mission02/src/components/Modal.tsx`:
- Around line 14-42: The modal needs keyboard accessibility: add an initial
focus and Escape-to-close behavior. In the Modal component, create a ref for the
primary interactive element (e.g., noButtonRef for the "아니요" button) and in a
useEffect set focus(noButtonRef.current) when the modal mounts and return a
cleanup to restore focus if desired; also register a keydown listener in that
same useEffect that listens for "Escape" and calls dispatch(closeModal()), and
remove the listener on unmount. Keep existing handlers (handleConfirm,
dispatch(closeModal())) but ensure the role="dialog" container has
aria-modal="true" (already present) and that the focused element is inside the
dialog so keyboard users can operate it immediately.

In `@Week09/Mission02/src/constants/cartItems.ts`:
- Line 5: The cart item type currently declares price as a string which risks
NaN and parsing overhead; change the price property in the cart item
type/constant definitions from string to number (i.e., replace all occurrences
of "price: string" with "price: number") and ensure any literal values in the
corresponding cart item objects are numeric (no quotes) so totals and arithmetic
(e.g., sum/total calculations) operate on numbers without parsing; update any
related initialization or tests that supply string prices to use numeric
literals.

In `@Week09/Mission03/src/App.tsx`:
- Line 6: The App component currently subscribes to the entire Zustand store via
usePlaylistStore(), causing unrelated store changes to rerender App; instead
subscribe to only the needed slices by replacing the whole-store call with two
selector subscriptions such as using usePlaylistStore(state => state.amount) for
amount and usePlaylistStore(state => state.isOpen) for isOpen (or a single
selector that returns an object and shallow compares if you prefer), updating
references in App to those selectors to avoid full-store re-renders.

In `@Week09/Mission03/src/components/CartContainer.tsx`:
- Line 7: CartContainer is subscribing to the whole store by destructuring
usePlaylistStore() which causes unnecessary re-renders; change to selector-based
subscriptions e.g. replace const { amount, cartItems, openModal, total } =
usePlaylistStore(); with selector calls that pick only needed slices (either a
single object selector: usePlaylistStore(s => ({ amount: s.amount, cartItems:
s.cartItems, openModal: s.openModal, total: s.total }), or separate calls like
usePlaylistStore(s => s.cartItems) and usePlaylistStore(s => s.amount) etc.),
and if needed pass a shallow/equality function to avoid re-rendering when values
are referentially equal — refer to CartContainer and usePlaylistStore for the
exact symbols to change.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: d29e2ef1-b712-49e8-bdf5-583d02ef0e25

📥 Commits

Reviewing files that changed from the base of the PR and between 59f3ae1 and 1e72629.

⛔ Files ignored due to path filters (4)
  • Week09/Mission01/package-lock.json is excluded by !**/package-lock.json
  • Week09/Mission02/package-lock.json is excluded by !**/package-lock.json
  • Week09/Mission03/package-lock.json is excluded by !**/package-lock.json
  • Week09/Practice01/package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (61)
  • Week09/Mission01/.gitignore
  • Week09/Mission01/index.html
  • Week09/Mission01/package.json
  • Week09/Mission01/src/App.tsx
  • Week09/Mission01/src/components/CartContainer.tsx
  • Week09/Mission01/src/components/CartItem.tsx
  • Week09/Mission01/src/constants/cartItems.ts
  • Week09/Mission01/src/hooks/redux.ts
  • Week09/Mission01/src/index.css
  • Week09/Mission01/src/main.tsx
  • Week09/Mission01/src/store/cartSlice.ts
  • Week09/Mission01/src/store/store.ts
  • Week09/Mission01/tsconfig.app.json
  • Week09/Mission01/tsconfig.json
  • Week09/Mission01/tsconfig.node.json
  • Week09/Mission01/vite.config.ts
  • Week09/Mission02/.gitignore
  • Week09/Mission02/index.html
  • Week09/Mission02/package.json
  • Week09/Mission02/src/App.tsx
  • Week09/Mission02/src/components/CartContainer.tsx
  • Week09/Mission02/src/components/CartItem.tsx
  • Week09/Mission02/src/components/Modal.tsx
  • Week09/Mission02/src/constants/cartItems.ts
  • Week09/Mission02/src/features/cart/cartSlice.ts
  • Week09/Mission02/src/features/modal/modalSlice.ts
  • Week09/Mission02/src/hooks/redux.ts
  • Week09/Mission02/src/index.css
  • Week09/Mission02/src/main.tsx
  • Week09/Mission02/src/store/store.ts
  • Week09/Mission02/tsconfig.app.json
  • Week09/Mission02/tsconfig.json
  • Week09/Mission02/tsconfig.node.json
  • Week09/Mission02/vite.config.ts
  • Week09/Mission03/.gitignore
  • Week09/Mission03/index.html
  • Week09/Mission03/package.json
  • Week09/Mission03/src/App.tsx
  • Week09/Mission03/src/components/CartContainer.tsx
  • Week09/Mission03/src/components/CartItem.tsx
  • Week09/Mission03/src/components/Modal.tsx
  • Week09/Mission03/src/constants/cartItems.ts
  • Week09/Mission03/src/index.css
  • Week09/Mission03/src/main.tsx
  • Week09/Mission03/src/store/usePlaylistStore.ts
  • Week09/Mission03/tsconfig.app.json
  • Week09/Mission03/tsconfig.json
  • Week09/Mission03/tsconfig.node.json
  • Week09/Mission03/vite.config.ts
  • Week09/Practice01/.gitignore
  • Week09/Practice01/index.html
  • Week09/Practice01/package.json
  • Week09/Practice01/src/App.tsx
  • Week09/Practice01/src/components/UseReducerCompany.tsx
  • Week09/Practice01/src/components/UseReducerPage.tsx
  • Week09/Practice01/src/main.tsx
  • Week09/Practice01/src/style.css
  • Week09/Practice01/tsconfig.app.json
  • Week09/Practice01/tsconfig.json
  • Week09/Practice01/tsconfig.node.json
  • Week09/Practice01/vite.config.ts

Comment on lines +27 to +29
<p className="mt-1 text-[20px] font-extrabold leading-tight text-slate-800">
${item.price}
</p>
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

가격 표기를 원화 포맷으로 통일해 주세요.

아이템 가격이 $로 표시되어 총액()과 통화 표기가 다릅니다. 동일한 원화 포맷으로 맞추는 것이 좋습니다.

수정 예시
-        <p className="mt-1 text-[20px] font-extrabold leading-tight text-slate-800">
-          ${item.price}
-        </p>
+        <p className="mt-1 text-[20px] font-extrabold leading-tight text-slate-800">
+          {Number(item.price).toLocaleString("ko-KR")}원
+        </p>
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<p className="mt-1 text-[20px] font-extrabold leading-tight text-slate-800">
${item.price}
</p>
<p className="mt-1 text-[20px] font-extrabold leading-tight text-slate-800">
{Number(item.price).toLocaleString("ko-KR")}
</p>
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Week09/Mission01/src/components/CartItem.tsx` around lines 27 - 29, The price
is displayed with a hardcoded "$" in the CartItem component; replace this with a
Korean won formatted string (use Intl.NumberFormat with 'ko-KR' and currency
'KRW' or a shared formatPrice utility) and render the formatted value instead of
`${item.price}`; update the JSX in the CartItem render where item.price is used
so all prices match the 원 currency format.

Comment on lines +36 to +61
increase: (state, action: PayloadAction<string>) => {
const item = state.cartItems.find((cartItem) => cartItem.id === action.payload);

if (item) {
item.amount += 1;
}
},
decrease: (state, action: PayloadAction<string>) => {
const item = state.cartItems.find((cartItem) => cartItem.id === action.payload);

if (!item) return;

if (item.amount === 1) {
state.cartItems = state.cartItems.filter(
(cartItem) => cartItem.id !== action.payload,
);
return;
}

item.amount -= 1;
},
removeItem: (state, action: PayloadAction<string>) => {
state.cartItems = state.cartItems.filter(
(cartItem) => cartItem.id !== action.payload,
);
},
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

장바구니 변경 액션이 amount/total을 즉시 동기화하지 않습니다.

현재는 increase/decrease/removeItem 이후 집계값이 자동으로 맞춰지지 않아, calculateTotals를 따로 호출하지 않으면 상태가 불일치합니다. 슬라이스 내부에서 불변식을 유지하도록 각 변경 액션에서 totals를 함께 갱신하는 게 안전합니다.

수정 예시
     increase: (state, action: PayloadAction<string>) => {
       const item = state.cartItems.find((cartItem) => cartItem.id === action.payload);

       if (item) {
         item.amount += 1;
+        const totals = getTotals(state.cartItems);
+        state.amount = totals.amount;
+        state.total = totals.total;
       }
     },
     decrease: (state, action: PayloadAction<string>) => {
       const item = state.cartItems.find((cartItem) => cartItem.id === action.payload);

       if (!item) return;

       if (item.amount === 1) {
         state.cartItems = state.cartItems.filter(
           (cartItem) => cartItem.id !== action.payload,
         );
-        return;
+      } else {
+        item.amount -= 1;
       }
-
-      item.amount -= 1;
+      const totals = getTotals(state.cartItems);
+      state.amount = totals.amount;
+      state.total = totals.total;
     },
     removeItem: (state, action: PayloadAction<string>) => {
       state.cartItems = state.cartItems.filter(
         (cartItem) => cartItem.id !== action.payload,
       );
+      const totals = getTotals(state.cartItems);
+      state.amount = totals.amount;
+      state.total = totals.total;
     },
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Week09/Mission01/src/store/cartSlice.ts` around lines 36 - 61, The increase,
decrease and removeItem reducers update state.cartItems but do not update
aggregated fields (amount/total), causing inconsistent state; fix by invoking
the slice's existing calculateTotals logic after each mutation (or recomputing
totals inline) so totals stay in sync—e.g., after modifying state.cartItems in
increase, decrease, and removeItem call calculateTotals(state) (or update
state.total/state.amount using the same reduction logic) to keep the invariants.

<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Week09 Mission1</title>
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

브라우저 탭 제목을 Mission02로 맞춰주세요.

Line 6의 <title>"Week09 Mission1"로 되어 있어 현재 프로젝트(Mission02)와 불일치합니다.

수정 예시
-    <title>Week09 Mission1</title>
+    <title>Week09 Mission2</title>
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<title>Week09 Mission1</title>
<title>Week09 Mission2</title>
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Week09/Mission02/index.html` at line 6, 제목이 잘못되어 있습니다: index.html의 <title> 태그
값이 "Week09 Mission1"으로 되어 있으니 이를 현재 프로젝트명에 맞게 "Week09 Mission02" 또는 단순히
"Mission02"로 변경하세요; 대상은 index.html의 <title> 요소이며 파일 내 <title> 태그만 수정하면 됩니다.

Comment on lines +27 to +29
<p className="mt-1 text-[20px] font-extrabold leading-tight text-slate-800">
${item.price}
</p>
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

아이템 가격 통화 표기가 총액 표기와 불일치합니다.

리스트 행은 $를 쓰고, 총액은 포맷을 사용하고 있어 UX가 혼란스럽습니다. 동일한 원화 포맷으로 맞춰주세요.

🔧 제안 수정안
+const formatWon = (price: number) => `${price.toLocaleString("ko-KR")}원`;
@@
-        <p className="mt-1 text-[20px] font-extrabold leading-tight text-slate-800">
-          ${item.price}
-        </p>
+        <p className="mt-1 text-[20px] font-extrabold leading-tight text-slate-800">
+          {formatWon(item.price)}
+        </p>
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Week09/Mission02/src/components/CartItem.tsx` around lines 27 - 29, The
CartItem component is rendering item.price with a leading "$" which conflicts
with the total's Korean won formatting; update the CartItem.tsx rendering in the
CartItem component to format item.price using the same Korean won formatter used
for the total (e.g., Intl.NumberFormat('ko-KR', { style: 'currency', currency:
'KRW' }) or the project's currency helper) so the list rows display prices in 원
and match the total; locate the JSX that currently shows "${item.price}" and
replace it with the shared KRW formatting call for item.price.

Comment on lines +36 to +61
increase: (state, action: PayloadAction<string>) => {
const item = state.cartItems.find((cartItem) => cartItem.id === action.payload);

if (item) {
item.amount += 1;
}
},
decrease: (state, action: PayloadAction<string>) => {
const item = state.cartItems.find((cartItem) => cartItem.id === action.payload);

if (!item) return;

if (item.amount === 1) {
state.cartItems = state.cartItems.filter(
(cartItem) => cartItem.id !== action.payload,
);
return;
}

item.amount -= 1;
},
removeItem: (state, action: PayloadAction<string>) => {
state.cartItems = state.cartItems.filter(
(cartItem) => cartItem.id !== action.payload,
);
},
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

파생 상태(amount, total)가 액션 직후 일시적으로 불일치합니다.

increase/decrease/removeItem에서 cartItems만 바꾸고 합계를 갱신하지 않아, 별도 calculateTotals가 실행되기 전까지 상태가 stale 할 수 있습니다. 합계 갱신을 reducer 내부에서 같이 처리해 상태 일관성을 보장하는 편이 안전합니다.

🔧 제안 수정안
 interface CartState {
   cartItems: CartItem[];
   amount: number;
   total: number;
 }
@@
 const initialState: CartState = {
   cartItems,
   amount: initialTotals.amount,
   total: initialTotals.total,
 };
+
+function syncTotals(state: CartState) {
+  const totals = getTotals(state.cartItems);
+  state.amount = totals.amount;
+  state.total = totals.total;
+}
@@
     increase: (state, action: PayloadAction<string>) => {
       const item = state.cartItems.find((cartItem) => cartItem.id === action.payload);
 
       if (item) {
         item.amount += 1;
+        syncTotals(state);
       }
     },
@@
       if (item.amount === 1) {
         state.cartItems = state.cartItems.filter(
           (cartItem) => cartItem.id !== action.payload,
         );
+        syncTotals(state);
         return;
       }
 
       item.amount -= 1;
+      syncTotals(state);
     },
     removeItem: (state, action: PayloadAction<string>) => {
       state.cartItems = state.cartItems.filter(
         (cartItem) => cartItem.id !== action.payload,
       );
+      syncTotals(state);
     },
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Week09/Mission02/src/features/cart/cartSlice.ts` around lines 36 - 61, The
reducers increase, decrease, and removeItem update cartItems but do not update
derived totals (amount and total), causing a temporary state mismatch; fix by
ensuring each of these reducers also updates state.amount and state.total
immediately after modifying cartItems (either by invoking the existing
calculateTotals reducer logic directly like calculateTotals(state) or by
recomputing amount/total inline) so the store remains consistent; update the
implementations of increase, decrease, and removeItem to recalc totals right
after changing cartItems.

<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Week09 Mission1</title>
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

문서 제목이 현재 미션과 불일치합니다.

<title>Week09 Mission1로 되어 있어 Mission03 프로젝트와 맞지 않습니다. 브라우저 탭/북마크에서 혼동을 줄 수 있으니 현재 미션명으로 맞춰주세요.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Week09/Mission03/index.html` at line 6, HTML 문서의 <title> 요소 값이 "Week09
Mission1"으로 잘못되어 현재 프로젝트와 불일치합니다; index.html의 <title> 내용을 현재 미션명(예: "Week09
Mission03" 또는 프로젝트명)으로 변경하여 브라우저 탭과 북마크에 맞게 정확한 미션명을 반영하세요.

{item.singer}
</p>
<p className="mt-1 text-[20px] font-extrabold leading-tight text-slate-800">
${item.price}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

통화 표기가 총액 표시와 불일치합니다.

Line 27은 $를 사용하지만 화면의 합계/문구는 기준이라 사용자에게 혼동을 줄 수 있습니다.

제안 diff
-        <p className="mt-1 text-[20px] font-extrabold leading-tight text-slate-800">
-          ${item.price}
-        </p>
+        <p className="mt-1 text-[20px] font-extrabold leading-tight text-slate-800">
+          {Number(item.price).toLocaleString("ko-KR")}원
+        </p>
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
${item.price}
<p className="mt-1 text-[20px] font-extrabold leading-tight text-slate-800">
{Number(item.price).toLocaleString("ko-KR")}
</p>
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Week09/Mission03/src/components/CartItem.tsx` at line 27, The currency symbol
in the CartItem component is inconsistent: replace the hardcoded "$" at the
interpolation of item.price in CartItem.tsx with the same currency/format used
elsewhere (Korean won) or use a centralized formatter; update the interpolation
that currently reads `${item.price}` to render with "원" (e.g., `${item.price}원`)
or call the shared formatter function (e.g., formatCurrency(item.price)) so the
displayed unit matches the cart total and app-wide currency.

Comment on lines +59 to +70
<p className={`error-message ${state.error ? "visible" : ""}`}>
{state.error}
</p>

<div className="form-row">
<input
type="text"
value={department}
onChange={handleChange}
placeholder="카드메이커"
className="department-input"
/>
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

접근성: 입력 필드 라벨과 오류 알림 라이브 리전을 추가해주세요.

현재 입력 목적이 명시적으로 연결되지 않고, 오류 변경도 보조기기에 즉시 공지되지 않습니다. 스크린리더 사용자의 입력/수정 흐름이 끊길 수 있습니다.

수정 예시
-        <p className={`error-message ${state.error ? "visible" : ""}`}>
+        <p
+          className={`error-message ${state.error ? "visible" : ""}`}
+          role="alert"
+          aria-live="assertive"
+        >
           {state.error}
         </p>

         <div className="form-row">
+          <label htmlFor="department-input" className="sr-only">
+            직무 입력
+          </label>
           <input
+            id="department-input"
             type="text"
             value={department}
             onChange={handleChange}
             placeholder="카드메이커"
             className="department-input"
           />
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Week09/Practice01/src/components/UseReducerCompany.tsx` around lines 59 - 70,
Add accessible labeling and a live error region: give the text input (currently
bound to the department state via value={department} and
onChange={handleChange}) a visible label element tied with htmlFor/id, mark the
input with aria-invalid={Boolean(state.error)} and add aria-describedby that
points to the error message id; convert the error <p> (which currently renders
{state.error}) into a role="alert" or aria-live="assertive" region with a stable
id so screen readers are notified when state.error changes. Ensure identifiers
referenced (department input, handleChange, state.error, and the error <p>) are
updated consistently.

Copy link
Copy Markdown
Contributor

@wantkdd wantkdd left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Redux Toolkit과 Zustand 전환까지 9주차 미션 흐름은 전반적으로 잘 구현되어 있는데, 현재 파일들이 Week09/Mission01처럼 공용 경로에 바로 올라와 있어서 이후 다른 제출물과 경로 충돌이 날 수 있으니 다음 제출부터는 닉네임/이름 폴더로 한 번 감싸두면 더 안전할 것 같습니다. Zustand 버전에서는 usePlaylistStore()를 통째로 구독하는 컴포넌트가 많아 amount, total, modal 상태 변화에도 장바구니 아이템 쪽까지 같이 리렌더될 수 있으므로, 필요한 값별 selector로 나누면 이번 주차의 부분 구독 의도까지 더 잘 살아날 것 같습니다. 전체 기능 흐름은 잘 갖춰져 있으니 이 두 부분만 다음 개선 때 챙겨보면 좋겠습니다, 수고하셨습니다!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants