Conversation
- B-2 디자인 수정을 반영해 InteriorStyle 스텝에 MoodboardCard 추가, 기존 CardImage 제거 - MoodboardCard는 공컴이 아니므로 InteriorStyle 폴더 안에 선언 - 164-240 반응형 적용
- div -> button 변경으로 키보드 포커스 등 접근성 제공 - 'children', 'type'을 omit (type은 button으로 고정) -
… 명세 변경 반영) - useActivityOptionsQuery → useActivitiesQuery + useFurnitureCategoriesQuery 분리 - API 엔드포인트 추가, queryKey 추가, - MSW 핸들러, prefetchStaticData에 주요활동/가구 API 변경사항 적용
- 새 API에서 필수가구까지 넘겨주므로 MAIN_ACTIVITY_VALIDATION(필수가구 하드코딩 매핑) 삭제 - 필수가구 매핑 관련 코드(onActivityChange, selectedValues) 삭제
- 새 API에서 필수가구까지 넘겨주므로 MAIN_ACTIVITY_VALIDATION(필수가구 하드코딩 매핑) 삭제 - 필수가구 매핑 관련 코드(onActivityChange, selectedValues) 삭제
- ActivityIcon.ts: 주요 활동 종류에 따라 적절한 아이콘 매핑
- ChipColor가 weak인 경우 추가(기존에는 strong인 경우뿐)
… 대응), SelectedTrigger 불필요한 isSelected 제거
📝 WalkthroughSummary by CodeRabbit릴리스 노트
Walkthrough이 PR은 이미지 설정 단계의 "Activity Info" 화면을 v1에서 v2 API 아키텍처로 마이그레이션합니다. 새로운 활동 및 가구 카테고리 엔드포인트를 도입하고, 관련 훅과 컴포넌트를 리팩토링하며, 레거시 카드 컴포넌트를 제거하고 새로운 UI 컴포넌트를 추가합니다. Changes
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes 검토 포인트:
Suggested reviewers
🚥 Pre-merge checks | ✅ 3✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches⚔️ Resolve merge conflicts
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. Comment |
빌드 결과빌드 성공 🎊 |
🎨 Storybook 빌드 완료!📚 Storybook: https://686a831b8e000345a949970a-uqpqtoxwfs.chromatic.com/ 📊 빌드 정보
|
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: af7c65b31d
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| onBackClick={ | ||
| currentStep === 'FloorPlanSelect' ? handleBackClick : undefined | ||
| } |
There was a problem hiding this comment.
Wire back navigation for non-floor funnel steps
The new header wiring passes undefined for onBackClick outside FloorPlanSelect, but the v2 TitleNavBar back button only invokes the provided callback and no longer falls back to navigate(-1). This makes the visible back button a no-op on InteriorStyle and ActivityInfo, so users cannot move back through the funnel from the header.
Useful? React with 👍 / 👎.
| : isPersistent | ||
| ? { height: expanded ? EXPANDED_HEIGHT : collapsedHeight } | ||
| : {}; // Dismissible: content 높이(auto) + CSS maxHeight가 상한 |
There was a problem hiding this comment.
Keep dismissible sheet height aligned with drag logic
For dismissible sheets, this now opens the panel at content height ({} style) while drag dismissal still uses expandedPxRef.current / 2 as the close threshold in handlePointerUp. When the content is shorter than half the viewport (common on larger screens), pressing the drag handle without meaningful movement leaves dragHeight below that threshold and immediately triggers onDismiss, causing accidental sheet closure.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Actionable comments posted: 13
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@src/main.tsx`:
- Around line 53-71: The app currently only calls createRoot(...).render(...)
inside the enableMocking() promise success path, so if enableMocking() rejects
the app never mounts; change the flow to always call createRoot(rootElement,
getSentryReactErrorHandlerOptions()).render(...) regardless of enableMocking()
outcome by moving the render call out of the then-only branch and handling
enableMocking() errors separately (e.g., catch the rejection, log the error,
then proceed to render), referencing enableMocking(), createRoot(...).render,
rootElement, getSentryReactErrorHandlerOptions(), and AppErrorFallback so the
ErrorBoundary/HelmetProvider/QueryClientProvider tree is mounted even when
mocking initialization fails.
In `@src/pages/imageSetup/components/layout/FunnelLayout.tsx`:
- Around line 15-22: The FunnelStepKey type is currently local but should be
exported so other funnel-related components can reuse it; change the declaration
to export the type (export type FunnelStepKey = ...) and keep
NAVBAR_TITLE_BY_STEP as Record<FunnelStepKey, string> to preserve typings, then
update any consumers to import { FunnelStepKey } from this module where needed.
In `@src/pages/imageSetup/hooks/activityInfo/useActivityInfo.ts`:
- Around line 148-168: The useEffect may suffer a stale-closure because
activitySelection.getRequiredFurnitureIds is not listed in the dependency array;
update the effect to include either activitySelection or
activitySelection.getRequiredFurnitureIds in the dependencies (instead of
silencing eslint) so the effect re-runs when the selector changes, or if you
deliberately rely on formData.activity driving recalculation, replace the
eslint-disable with a short comment in useActivityInfo explaining that
activitySelection is derived from formData.activity and therefore intentionally
omitted; touch the effect around useEffect and references to
activitySelection.getRequiredFurnitureIds and savedActivityInfo to make the
intent explicit.
- Around line 96-123: The code currently indexes categoriesData?.categories by
fixed positions when calling useCategorySelection (e.g.,
categoriesData?.categories[0]) which can mis-map if the server changes order or
omits items; replace those fixed-index calls by first implementing a helper like
getCategoryByNameEng(nameEng) that returns categoriesData?.categories.find(c =>
c.nameEng === nameEng) ?? null, then call useCategorySelection with
getCategoryByNameEng('BED'), getCategoryByNameEng('SOFA'),
getCategoryByNameEng('TABLE'), and getCategoryByNameEng('SELECTIVE')
respectively, keeping the same CATEGORY_SELECTION_MODE.*, formData, setFormData
and globalConstraints parameters and preserving null fallback behavior.
In `@src/pages/imageSetup/hooks/activityInfo/useCategorySelection.ts`:
- Around line 40-72: The toggleFurniture function is recreated on each render
and passed to each Chip's onClick; wrap toggleFurniture in React.useCallback to
stabilize its reference (so memoized Chips won't re-render unnecessarily).
Create useCallback around the existing toggleFurniture logic and include correct
dependencies: setFormData, formData.furnitureIds (or formData), mode,
globalConstraints, and category (or stable properties like category.furnitures);
ensure any unstable objects used inside (globalConstraints, category) are stable
or referenced appropriately in the deps array. Update places where
toggleFurniture is passed to Chip onClick to use the memoized function.
In `@src/pages/imageSetup/steps/activityInfo/ActivityInfo.tsx`:
- Around line 57-62: selectionByNameEng currently hardcodes keys and your
useActivityInfo logic indexes categoriesData?.categories[0..3], which breaks if
the server changes order or adds categories; instead, build a dynamic map keyed
by each category's nameEng (e.g., create categoryByNameEng from
categoriesData.categories using nameEng) and refactor uses of selectionByNameEng
and any index-based access in useActivityInfo to look up categories via
categoryByNameEng[nameEng]; ensure null/undefined checks for categoriesData and
fallbacks where a nameEng is missing.
In `@src/pages/imageSetup/steps/activityInfo/ActivityTypeSheet.css.ts`:
- Around line 22-40: The radioItem recipe currently only defines &:active and
includes redundant local button resets (border: 'none', font: 'inherit')—add an
explicit &:focus-visible selector on radioItem to provide a clear keyboard-focus
style (e.g., outline or box-shadow and visible offset), and remove the
duplicated global-reset properties (delete border: 'none' and font: 'inherit'
from radioItem) so the component relies on the shared global button reset; keep
selectors nested under radioItem and ensure the new focus-visible rule matches
the component's radius/padding variables for consistent visual styling.
In `@src/pages/imageSetup/steps/activityInfo/ActivityTypeSheet.tsx`:
- Around line 29-31: The local state localSelected (initialized from
selectedActivityCode via useState in ActivityTypeSheet) isn’t synchronized when
the prop changes; add a useEffect that watches selectedActivityCode and updates
localSelected using setLocalSelected(selectedActivityCode) so the component
reflects external updates, taking care not to clobber in-progress user edits if
you need that (e.g., add a conditional guard if necessary).
In `@src/pages/imageSetup/steps/activityInfo/SelectTrigger.tsx`:
- Around line 19-44: SelectTrigger's trigger button lacks an aria-expanded
attribute; add a boolean prop (e.g., isOpen or isExpanded) to the SelectTrigger
component's props/interface and have callers pass the parent sheet/dropdown open
state, then set aria-expanded={!!isOpen} on the <button> element to expose the
expanded/collapsed state to assistive tech; update the component signature and
all usages to supply the state.
In `@src/pages/imageSetup/steps/interiorStyle/InteriorStyle.css.ts`:
- Around line 30-31: In the buttonWrapper style object replace the hardcoded
layout values used to compute right and bottom ("44rem" and "2rem") with design
tokens: pull the container width token (used instead of 44rem) and the spacing
token (used instead of 2rem) and use those tokens in the right and bottom
calculations inside buttonWrapper so changes to global tokens update this
positioning automatically; update any imports to bring in the token module and
ensure the token identifiers are used in the right: 'max(...)' calc expression
and bottom property in the InteriorStyle.css.ts file.
In `@src/pages/imageSetup/steps/interiorStyle/MoodboardCard.tsx`:
- Around line 32-33: 무드보드 카드의 <img src={src} alt={alt} className={styles.image}
draggable={false} /> 태그에 로딩 전략을 추가하세요: 기본적으로 loading="lazy"를 적용하되(성능 향상), 화면 상단에
항상 표시되는 카드용으로는 eager 로딩을 허용하도록 MoodboardCard 컴포넌트에 새로운 prop(예: isAboveTheFold 또는
loadingPriority)이나 기존 props를 사용해 조건부로 loading="eager"를 반환하도록 구현합니다; 구현 대상 식별자:
MoodboardCard 컴포넌트와 해당 <img ... /> JSX, props 이름은 isAboveTheFold 또는 loading 등으로
추가/사용하세요.
In `@src/pages/imageSetup/types/funnel/activityInfo.ts`:
- Around line 12-18: CATEGORY_SELECTION_MODE currently uses Record<string,
CategorySelectionMode>, which loses compile-time key safety; define an explicit
union type for the API key (e.g., type CategoryNameEng = 'BED' | 'SOFA' |
'TABLE' | 'SELECTIVE') and replace Record<string, CategorySelectionMode> with
Record<CategoryNameEng, CategorySelectionMode>, exporting the union type if
needed and updating any usage that expects the API's nameEng to use this new
CategoryNameEng type to catch typos at compile time.
In `@src/pages/imageSetup/types/funnel/steps.ts`:
- Around line 21-22: The payload/initialization still uses the old selectiveIds
field while the ActivityInfo type was renamed to furnitureIds; update the
ImageSetupPage component to stop initializing or sending selectiveIds and
instead use the new furnitureIds field (adjust initialization, state keys and
any payload construction in ImageSetupPage.tsx where selectiveIds is set/used),
ensuring consistency with the ActivityInfo type and any functions that read/send
that data.
🪄 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: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro
Run ID: 00acddc1-c67d-4958-8fff-8040df7f3e23
⛔ Files ignored due to path filters (10)
src/shared/assets/v2/svg/BookBlack.svgis excluded by!**/*.svg,!**/*.svgand included bysrc/**src/shared/assets/v2/svg/BookGray.svgis excluded by!**/*.svg,!**/*.svgand included bysrc/**src/shared/assets/v2/svg/ChevronDownFill.svgis excluded by!**/*.svg,!**/*.svgand included bysrc/**src/shared/assets/v2/svg/CupBlack.svgis excluded by!**/*.svg,!**/*.svgand included bysrc/**src/shared/assets/v2/svg/CupGray.svgis excluded by!**/*.svg,!**/*.svgand included bysrc/**src/shared/assets/v2/svg/DeskBlack.svgis excluded by!**/*.svg,!**/*.svgand included bysrc/**src/shared/assets/v2/svg/DeskGray.svgis excluded by!**/*.svg,!**/*.svgand included bysrc/**src/shared/assets/v2/svg/Lock.svgis excluded by!**/*.svg,!**/*.svgand included bysrc/**src/shared/assets/v2/svg/MouseBlack.svgis excluded by!**/*.svg,!**/*.svgand included bysrc/**src/shared/assets/v2/svg/MouseGray.svgis excluded by!**/*.svg,!**/*.svgand included bysrc/**
📒 Files selected for processing (39)
src/main.tsxsrc/mocks/handlers.tssrc/pages/imageSetup/apis/queries/useActivitiesQuery.tssrc/pages/imageSetup/apis/queries/useActivityOptionsQuery.tssrc/pages/imageSetup/apis/queries/useFurnitureCategoriesQuery.tssrc/pages/imageSetup/components/layout/FunnelLayout.tsxsrc/pages/imageSetup/hooks/activityInfo/useActivityInfo.tssrc/pages/imageSetup/hooks/activityInfo/useActivitySelection.tssrc/pages/imageSetup/hooks/activityInfo/useCategorySelection.tssrc/pages/imageSetup/steps/activityInfo/ActivityInfo.css.tssrc/pages/imageSetup/steps/activityInfo/ActivityInfo.tsxsrc/pages/imageSetup/steps/activityInfo/ActivityTypeSheet.css.tssrc/pages/imageSetup/steps/activityInfo/ActivityTypeSheet.tsxsrc/pages/imageSetup/steps/activityInfo/SelectTrigger.css.tssrc/pages/imageSetup/steps/activityInfo/SelectTrigger.tsxsrc/pages/imageSetup/steps/activityInfo/activityIcons.tssrc/pages/imageSetup/steps/interiorStyle/InteriorStyle.css.tssrc/pages/imageSetup/steps/interiorStyle/InteriorStyle.tsxsrc/pages/imageSetup/steps/interiorStyle/MoodBoard.css.tssrc/pages/imageSetup/steps/interiorStyle/MoodBoard.tsxsrc/pages/imageSetup/steps/interiorStyle/MoodboardCard.css.tssrc/pages/imageSetup/steps/interiorStyle/MoodboardCard.tsxsrc/pages/imageSetup/stores/useFunnelStore.tssrc/pages/imageSetup/types/apis/activityInfo.tssrc/pages/imageSetup/types/funnel/activityInfo.tssrc/pages/imageSetup/types/funnel/steps.tssrc/pages/imageSetup/utils/staticDataPrefetch.tssrc/shared/components/card/cardImage/CardImage.css.tssrc/shared/components/card/cardImage/CardImage.tsxsrc/shared/components/card/cardImage/SkeletonCardImage.tsxsrc/shared/components/v2/bottomSheet/DragHandleBottomSheet.tsxsrc/shared/components/v2/chip/Chip.css.tssrc/shared/components/v2/chip/Chip.tsxsrc/shared/components/v2/icon/Icon.tsxsrc/shared/components/v2/textHeading/TextHeading.css.tssrc/shared/constants/apiEndpoints.tssrc/shared/constants/queryKey.tssrc/stories/CardImage.stories.tsxsrc/stories/SkeletonCardImage.stories.tsx
💤 Files with no reviewable changes (7)
- src/pages/imageSetup/steps/interiorStyle/MoodBoard.css.ts
- src/shared/components/card/cardImage/SkeletonCardImage.tsx
- src/pages/imageSetup/apis/queries/useActivityOptionsQuery.ts
- src/stories/SkeletonCardImage.stories.tsx
- src/stories/CardImage.stories.tsx
- src/shared/components/card/cardImage/CardImage.tsx
- src/shared/components/card/cardImage/CardImage.css.ts
| enableMocking().then(() => { | ||
| createRoot(rootElement, getSentryReactErrorHandlerOptions()).render( | ||
| // <StrictMode> | ||
| <ErrorBoundary FallbackComponent={AppErrorFallback}> | ||
| <HelmetProvider> | ||
| <QueryClientProvider client={queryClient}> | ||
| <OverlayProvider> | ||
| <App /> | ||
| <ToastContainer {...toastConfig} /> | ||
| {import.meta.env.DEV && ( | ||
| <ReactQueryDevtools initialIsOpen={false} /> | ||
| )} | ||
| </OverlayProvider> | ||
| </QueryClientProvider> | ||
| </HelmetProvider> | ||
| </ErrorBoundary> | ||
| // </StrictMode> | ||
| ); | ||
| }); |
There was a problem hiding this comment.
MSW 초기화 실패 시 앱이 렌더링되지 않는 경로가 있습니다.
현재는 enableMocking() 성공 시에만 render가 실행됩니다. 초기화 실패 시에도 앱은 뜨도록 fallback을 보장해야 합니다.
🛠️ 제안 수정안
-enableMocking().then(() => {
- createRoot(rootElement, getSentryReactErrorHandlerOptions()).render(
+const renderApp = () => {
+ createRoot(rootElement, getSentryReactErrorHandlerOptions()).render(
// <StrictMode>
<ErrorBoundary FallbackComponent={AppErrorFallback}>
<HelmetProvider>
<QueryClientProvider client={queryClient}>
<OverlayProvider>
@@
</ErrorBoundary>
// </StrictMode>
);
-});
+};
+
+void enableMocking()
+ .catch((error) => {
+ console.error('MSW initialization failed', error);
+ })
+ .finally(() => {
+ renderApp();
+ });🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/main.tsx` around lines 53 - 71, The app currently only calls
createRoot(...).render(...) inside the enableMocking() promise success path, so
if enableMocking() rejects the app never mounts; change the flow to always call
createRoot(rootElement, getSentryReactErrorHandlerOptions()).render(...)
regardless of enableMocking() outcome by moving the render call out of the
then-only branch and handling enableMocking() errors separately (e.g., catch the
rejection, log the error, then proceed to render), referencing enableMocking(),
createRoot(...).render, rootElement, getSentryReactErrorHandlerOptions(), and
AppErrorFallback so the ErrorBoundary/HelmetProvider/QueryClientProvider tree is
mounted even when mocking initialization fails.
| type FunnelStepKey = 'FloorPlanSelect' | 'InteriorStyle' | 'ActivityInfo'; | ||
|
|
||
| // 퍼널 스텝 별 NavBar 타이틀 매핑 | ||
| const NAVBAR_TITLE_BY_STEP: Record<FunnelStepKey, string> = { | ||
| FloorPlanSelect: '공간 선택하기', | ||
| InteriorStyle: '취향 선택하기', | ||
| ActivityInfo: '가구 선택하기', | ||
| }; |
There was a problem hiding this comment.
🧹 Nitpick | 🔵 Trivial
FunnelStepKey 타입 export 고려
FunnelStepKey 타입이 다른 퍼널 관련 컴포넌트에서도 사용될 수 있으므로, export 여부를 검토해주세요. 현재는 로컬 타입으로만 정의되어 있어요.
♻️ 타입 export 제안
-type FunnelStepKey = 'FloorPlanSelect' | 'InteriorStyle' | 'ActivityInfo';
+export type FunnelStepKey = 'FloorPlanSelect' | 'InteriorStyle' | 'ActivityInfo';📝 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.
| type FunnelStepKey = 'FloorPlanSelect' | 'InteriorStyle' | 'ActivityInfo'; | |
| // 퍼널 스텝 별 NavBar 타이틀 매핑 | |
| const NAVBAR_TITLE_BY_STEP: Record<FunnelStepKey, string> = { | |
| FloorPlanSelect: '공간 선택하기', | |
| InteriorStyle: '취향 선택하기', | |
| ActivityInfo: '가구 선택하기', | |
| }; | |
| export type FunnelStepKey = 'FloorPlanSelect' | 'InteriorStyle' | 'ActivityInfo'; | |
| // 퍼널 스텝 별 NavBar 타이틀 매핑 | |
| const NAVBAR_TITLE_BY_STEP: Record<FunnelStepKey, string> = { | |
| FloorPlanSelect: '공간 선택하기', | |
| InteriorStyle: '취향 선택하기', | |
| ActivityInfo: '가구 선택하기', | |
| }; |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/pages/imageSetup/components/layout/FunnelLayout.tsx` around lines 15 -
22, The FunnelStepKey type is currently local but should be exported so other
funnel-related components can reuse it; change the declaration to export the
type (export type FunnelStepKey = ...) and keep NAVBAR_TITLE_BY_STEP as
Record<FunnelStepKey, string> to preserve typings, then update any consumers to
import { FunnelStepKey } from this module where needed.
| const bed = useCategorySelection( | ||
| activityOptionsData?.categories[0] || null, | ||
| categoriesData?.categories[0] ?? null, | ||
| CATEGORY_SELECTION_MODE.BED, | ||
| formData, | ||
| setFormData, | ||
| globalConstraints | ||
| ); | ||
| const sofa = useCategorySelection( | ||
| activityOptionsData?.categories[1] || null, | ||
| formData, | ||
| setFormData, | ||
| globalConstraints | ||
| ); | ||
| const storage = useCategorySelection( | ||
| activityOptionsData?.categories[2] || null, | ||
| categoriesData?.categories[1] ?? null, | ||
| CATEGORY_SELECTION_MODE.SOFA, | ||
| formData, | ||
| setFormData, | ||
| globalConstraints | ||
| ); | ||
| const table = useCategorySelection( | ||
| activityOptionsData?.categories[3] || null, | ||
| categoriesData?.categories[2] ?? null, | ||
| CATEGORY_SELECTION_MODE.TABLE, | ||
| formData, | ||
| setFormData, | ||
| globalConstraints | ||
| ); | ||
| const selective = useCategorySelection( | ||
| activityOptionsData?.categories[4] || null, | ||
| categoriesData?.categories[3] ?? null, | ||
| CATEGORY_SELECTION_MODE.SELECTIVE, | ||
| formData, | ||
| setFormData, | ||
| globalConstraints | ||
| ); |
There was a problem hiding this comment.
카테고리 인덱스 기반 접근의 잠재적 취약점
categoriesData?.categories[0], [1], [2], [3]으로 고정 인덱스 접근하고 있어요. 서버에서 카테고리 순서가 변경되거나 일부 카테고리가 누락되면 잘못된 매핑이 발생할 수 있어요.
nameEng 기준으로 동적 매핑하면 더 안전해요.
🔧 nameEng 기반 동적 매핑 제안
const getCategoryByNameEng = (nameEng: string) =>
categoriesData?.categories.find((c) => c.nameEng === nameEng) ?? null;
const bed = useCategorySelection(
getCategoryByNameEng('BED'),
CATEGORY_SELECTION_MODE.BED,
// ...
);🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/pages/imageSetup/hooks/activityInfo/useActivityInfo.ts` around lines 96 -
123, The code currently indexes categoriesData?.categories by fixed positions
when calling useCategorySelection (e.g., categoriesData?.categories[0]) which
can mis-map if the server changes order or omits items; replace those
fixed-index calls by first implementing a helper like
getCategoryByNameEng(nameEng) that returns categoriesData?.categories.find(c =>
c.nameEng === nameEng) ?? null, then call useCategorySelection with
getCategoryByNameEng('BED'), getCategoryByNameEng('SOFA'),
getCategoryByNameEng('TABLE'), and getCategoryByNameEng('SELECTIVE')
respectively, keeping the same CATEGORY_SELECTION_MODE.*, formData, setFormData
and globalConstraints parameters and preserving null fallback behavior.
| useEffect(() => { | ||
| // Zustand에 저장된 데이터가 있으면 해당 데이터 유지 | ||
| if (savedActivityInfo?.activityType === formData.activityType) { | ||
| // Zustand에 저장된 데이터가 있으면 해당 데이터 유지 (복원 케이스 스킵) | ||
| if (savedActivityInfo?.activity === formData.activity) { | ||
| return; | ||
| } | ||
|
|
||
| if (formData.activityType) { | ||
| if (formData.activity) { | ||
| const requiredIds = activitySelection.getRequiredFurnitureIds(); | ||
| setFormData((prev) => ({ | ||
| ...prev, | ||
| selectiveIds: requiredIds, | ||
| furnitureIds: requiredIds, | ||
| })); | ||
| } else { | ||
| // 주요활동이 해제된 경우 모든 가구 선택 해제 | ||
| setFormData((prev) => ({ | ||
| ...prev, | ||
| selectiveIds: [], | ||
| furnitureIds: [], | ||
| })); | ||
| } | ||
| }, [formData.activityType, savedActivityInfo]); | ||
| // eslint-disable-next-line react-hooks/exhaustive-deps | ||
| }, [formData.activity, savedActivityInfo]); |
There was a problem hiding this comment.
🧹 Nitpick | 🔵 Trivial
eslint-disable 주석과 의존성 배열 검토
activitySelection.getRequiredFurnitureIds가 의존성 배열에 없어서 stale closure 이슈가 있을 수 있어요. 현재는 formData.activity 변경 시 activitySelection도 새로 계산되므로 동작에는 문제없지만, 명시적으로 의존성에 추가하거나 주석으로 이유를 남기면 좋아요.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/pages/imageSetup/hooks/activityInfo/useActivityInfo.ts` around lines 148
- 168, The useEffect may suffer a stale-closure because
activitySelection.getRequiredFurnitureIds is not listed in the dependency array;
update the effect to include either activitySelection or
activitySelection.getRequiredFurnitureIds in the dependencies (instead of
silencing eslint) so the effect re-runs when the selector changes, or if you
deliberately rely on formData.activity driving recalculation, replace the
eslint-disable with a short comment in useActivityInfo explaining that
activitySelection is derived from formData.activity and therefore intentionally
omitted; touch the effect around useEffect and references to
activitySelection.getRequiredFurnitureIds and savedActivityInfo to make the
intent explicit.
| return ( | ||
| <button type="button" className={styles.trigger} onClick={onClick}> | ||
| <div className={styles.leftContainer}> | ||
| {activityIconName && <Icon name={activityIconName} size="20" />} | ||
| {selectedActivity ? ( | ||
| <div className={styles.labelContainer}> | ||
| <span className={styles.selectedLabel}> | ||
| {selectedActivity.label} | ||
| </span> | ||
| {requiredFurnitureLabel && ( | ||
| <> | ||
| <span className={styles.divider} /> | ||
| <span className={styles.requiredLabel}> | ||
| {requiredFurnitureLabel} | ||
| </span> | ||
| </> | ||
| )} | ||
| </div> | ||
| ) : ( | ||
| <span className={styles.placeholderLabel}> | ||
| 활동 형태를 선택해주세요 | ||
| </span> | ||
| )} | ||
| </div> | ||
| <Icon name="ChevronDownFill" size="20" /> | ||
| </button> |
There was a problem hiding this comment.
🧹 Nitpick | 🔵 Trivial
접근성: aria-expanded 속성 추가 권장
드롭다운/바텀시트 트리거 버튼에 aria-expanded 속성을 추가하면 스크린 리더 사용자에게 현재 상태를 명확히 전달할 수 있어요. 부모 컴포넌트에서 시트 열림 상태를 전달받아 적용하는 것을 고려해주세요.
♻️ 접근성 개선 제안
interface SelectTriggerProps {
selectedActivity?: ActivityItem;
onClick: () => void;
+ isOpen?: boolean;
}
-const SelectTrigger = ({ selectedActivity, onClick }: SelectTriggerProps) => {
+const SelectTrigger = ({ selectedActivity, onClick, isOpen = false }: SelectTriggerProps) => {
// ...
return (
- <button type="button" className={styles.trigger} onClick={onClick}>
+ <button
+ type="button"
+ className={styles.trigger}
+ onClick={onClick}
+ aria-expanded={isOpen}
+ aria-haspopup="dialog"
+ >🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/pages/imageSetup/steps/activityInfo/SelectTrigger.tsx` around lines 19 -
44, SelectTrigger's trigger button lacks an aria-expanded attribute; add a
boolean prop (e.g., isOpen or isExpanded) to the SelectTrigger component's
props/interface and have callers pass the parent sheet/dropdown open state, then
set aria-expanded={!!isOpen} on the <button> element to expose the
expanded/collapsed state to assistive tech; update the component signature and
all usages to supply the state.
📌 Summary
해당 PR에 대한 작업 내용을 요약하여 작성해주세요.
📄 Tasks
해당 PR에 수행한 작업을 작성해주세요.
🔍 To Reviewer
리뷰어에게 요청하는 내용을 작성해주세요.
📸 Screenshot
2026-04-09.6.45.05.mov
작업한 내용에 대한 스크린샷을 첨부해주세요.