feat:매장 운영 대시보드 위젯 추가(#25) - #26
Conversation
📝 WalkthroughWalkthrough대시보드 레이아웃 저장 방식이 콜백 기반 즉시 저장에서 useEffect 기반 지연 저장으로 변경되었고, 위젯 렌더 시그니처에 workspaceId 컨텍스트가 추가되어 DashboardGrid/DashboardView를 통해 전달되며, RecentResources가 workspaceId로 필터링되고 신규 WorkScheduleSummary 위젯이 추가됨. Changes대시보드 레이아웃 저장 흐름
위젯 workspaceId 컨텍스트 및 work-schedule 위젯
Estimated code review effort: 3 (Moderate) | ~25 minutes Possibly related PRs
Suggested reviewers: 🚥 Pre-merge checks | ✅ 1 | ❌ 4❌ Failed checks (4 warnings)
✅ Passed checks (1 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 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 `@src/features/dashboard/edit-layout/model/useDashboardLayout.ts`:
- Around line 29-39: The current didMountRef-based guard in useDashboardLayout
does not reliably block the initial save under StrictMode, so the first mount
can still trigger saveDashboardLayout. Update the effect logic in
useDashboardLayout to track whether the layout has actually changed since the
initial render, using a separate dirty flag or previous-layout comparison
instead of relying on didMountRef. Keep the saveDashboardLayout(workspaceId,
pageType, { layout }) call behind that change detection so only real edits are
persisted.
In
`@src/widgets/store-operation/dashboard-work-schedule/ui/WorkScheduleSummary.tsx`:
- Around line 47-53: `getNextWeekdays` is including `today` as the first item,
which duplicates the already-shown current day in `WorkScheduleSummary`. Update
the helper so the sequence starts from the day after `today` (for example by
offsetting the index in `getNextWeekdays`), and keep the existing `weekdays`
lookup logic in `WorkScheduleSummary` aligned with the “오늘 근무 유형별 인원 + 다음 4일”
summary.
🪄 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 Plus
Run ID: 2e01341f-559c-4c6e-8e57-f5d5b61d9720
📒 Files selected for processing (9)
src/features/dashboard/edit-layout/model/useDashboardLayout.tssrc/shared/dashboard/model/widget.types.tssrc/views/dashboard/config/template-widgets.tssrc/views/dashboard/config/widget-catalog.tsxsrc/views/dashboard/ui/DashboardGrid.tsxsrc/views/dashboard/ui/DashboardView.tsxsrc/widgets/store-operation/dashboard-recent-resources/ui/RecentResources.tsxsrc/widgets/store-operation/dashboard-work-schedule/index.tssrc/widgets/store-operation/dashboard-work-schedule/ui/WorkScheduleSummary.tsx
| const didMountRef = useRef(false); | ||
|
|
||
| // TODO: DB 연동 — 변경 저장 (드래그 중 잦은 호출은 debounce 예정) | ||
| const commit = useCallback( | ||
| (next: Layout) => { | ||
| void saveDashboardLayout(workspaceId, pageType, { layout: next }); | ||
| }, | ||
| [workspaceId, pageType], | ||
| ); | ||
| useEffect(() => { | ||
| if (!didMountRef.current) { | ||
| didMountRef.current = true; | ||
| return; | ||
| } | ||
|
|
||
| void saveDashboardLayout(workspaceId, pageType, { layout }); | ||
| }, [layout, workspaceId, pageType]); |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
🧩 Analysis chain
🌐 Web query:
React 19 StrictMode useEffect double invocation ref guard first render
💡 Result:
In React 19, Strict Mode continues the behavior of double-invoking useEffect in development to ensure that components are resilient to being mounted and unmounted multiple times [1][2][3]. This is a diagnostic tool, not a bug [1][4]. Using a ref guard (e.g., a useRef boolean) to prevent an effect from running more than once is generally discouraged as a primary solution [2][5]. This pattern often hides underlying issues—such as missing cleanup functions or non-idempotent code—that can still cause bugs in production during navigation or fast component re-mounting [2][6][4]. The recommended approach is to ensure your effects are idempotent by implementing a proper cleanup function [2][3][4]: 1. Implement Cleanup: If your effect sets up a subscription, timer, or network request, your cleanup function should explicitly stop or undo that action (e.g., unsubscribe, clearTimeout, or use AbortController for fetches) [1][7]. 2. Design for Idempotency: Write your effect as if it could be called, cleaned up, and called again at any time [2][4]. If the effect works correctly after this sequence in development, it is considered production-ready [1][2]. 3. Analyze the Need: If you feel a ref guard is necessary, it often indicates the logic should not be inside a useEffect at all [7][5]. Consider moving the logic to event handlers (if it is a response to user action) or to router-level listeners (if it is a response to navigation) [7][4]. There is a known issue where, due to how Strict Mode handles simulated unmounts, ref values may persist in ways that cause ref-guarded logic to execute incorrectly [8]. Relying on ref guards for critical logic is therefore brittle and risks state leaks or duplicate executions [8][5]. In production, these effects will only run once per mount, as the double-invocation behavior is stripped from production builds [1][2][4].
Citations:
- 1: https://pockit.tools/blog/react-19-useeffect-strict-mode-guide/
- 2: https://stackoverflow.com/questions/72238175/why-useeffect-running-twice-and-how-to-handle-it-well-in-react
- 3: https://fixdevs.com/blog/react-useeffect-runs-twice/
- 4: https://frontendatlas.com/react/trivia/react-strictmode-double-invoke-effects
- 5: https://github.com/alejandrobailo/no-use-effect/blob/main/references/patterns.md
- 6: Bug: useEffect runs twice on component mount (StrictMode, NODE_ENV=development) react/react#24502
- 7: https://afterbuildlabs.com/fix/react-strict-mode-double-render
- 8: https://usertourkit.com/blog/why-product-tour-libraries-break-strict-mode
🏁 Script executed:
sed -n '1,220p' src/features/dashboard/edit-layout/model/useDashboardLayout.ts && printf '\n---\n' && rg -n "saveDashboardLayout|didMountRef|useEffect" src/features/dashboard -SRepository: TeampleRun/syncly
Length of output: 3119
🏁 Script executed:
sed -n '1,220p' src/entities/dashboard-layout/index.ts && printf '\n---\n' && rg -n "export .*saveDashboardLayout|function saveDashboardLayout|const saveDashboardLayout" src/entities/dashboard-layout -SRepository: TeampleRun/syncly
Length of output: 638
🏁 Script executed:
sed -n '1,220p' src/entities/dashboard-layout/api/save-dashboard-layout.ts && printf '\n---\n' && sed -n '1,220p' src/entities/dashboard-layout/api/get-dashboard-layout.ts && printf '\n---\n' && rg -n "initialLayout|useDashboardLayout\\(" src -SRepository: TeampleRun/syncly
Length of output: 2336
🏁 Script executed:
sed -n '1,220p' src/views/dashboard/ui/DashboardView.tsx && printf '\n---\n' && sed -n '1,220p' src/app/workspaces/[workspaceId]/dashboard/page.tsxRepository: TeampleRun/syncly
Length of output: 3422
🌐 Web query:
React StrictMode remount preserve useRef state effect second mount React 19
💡 Result:
In React Strict Mode (available in React 18 and 19), components are intentionally double-invoked in development to help identify side effects and impurity issues [1][2]. Regarding the behavior of useRef and effect lifecycles: 1. useRef and State Preservation: While useRef is designed to persist values across renders, Strict Mode's simulated unmount and remount cycle can create confusion [3][2]. During this development-only cycle, the component is unmounted and then remounted [3][2]. The useRef object itself is re-created during the second mount, and the previous reference is discarded [4]. If your application logic relies on useRef state persisting across this simulated cycle, it will break because the initial value (or the value set during the first mount) is lost [5][6]. 2. Effect Cleanup: Because the component remounts, the useEffect cleanup function from the first mount is called, and the effect setup code is run again for the second mount [3][2]. If your code does not properly clean up resources (such as event listeners, timers, or socket connections) in the first pass, you will encounter bugs—such as orphaned processes or memory leaks—because the second mount initiates a fresh, duplicate resource [5][2]. 3. Development vs. Production: This behavior is strictly for development [4][2]. In production, components mount once and the effects run accordingly, meaning useRef values will persist as expected for the component's lifetime [4][2]. To ensure your components are resilient: - Always provide a cleanup function in your useEffect to destroy or reset resources [7][2]. - Avoid reading or writing to useRef.current during the render phase [4][8]. - Do not rely on useRef to initialize state that must persist through simulated unmount/remount cycles; if data must survive, it should be managed using useState or useReducer, which React preserves across remounts [3]. React 19 further refines these checks, including updates to how ref callbacks are invoked, to ensure that components handle the mounting and unmounting lifecycle more predictably [9]. If you are experiencing issues where a cleanup function uses the "wrong" ref, ensure that your setup and cleanup are strictly coupled within the same effect and that you are not leaking variables outside the scope of that effect [6][2].
Citations:
- 1: https://react.dev/reference/react/StrictMode
- 2: https://pockit.tools/blog/react-19-useeffect-strict-mode-guide/
- 3: https://stackoverflow.com/questions/72295136/userefs-reference-value-change-on-re-render-caused-by-strict-mode-while-usestat
- 4: https://react.dev/reference/react/useRef
- 5: Bug: Problem with StrictMode in development (useEffect + useRef/Memo). useEffect ignores first value of useRef/Memo and uses second twice react/react#31607
- 6: Bug: useRef cleanup in StrictMode refers to second ref twice react/react#26315
- 7: How to support Reusable State in Effects reactwg/react-18#18
- 8: Bug: StrictMode is causing issue when value is calculated using ref value react/react#28206
- 9: https://github.com/facebook/react/blob/ee0855f427832e899767f7659c5289364218ab9e/CHANGELOG.md
🌐 Web query:
site:react.dev StrictMode useRef preserved remount effect double invoke development
💡 Result:
In React development mode, when StrictMode is enabled, React intentionally calls your component functions twice [1][2] and invokes Effect setup and cleanup functions twice [3][4]. This behavior is designed to stress-test your components, ensuring they are pure [2][5] and that your Effects correctly handle synchronization, mounting, and unmounting [3][4]. Regarding useRef and StrictMode: 1. Persistence of Refs: Refs are preserved between re-renders of the same component instance [6]. While StrictMode causes the component function to run twice, React maintains the ref object's persistence across these calls [6][7]. 2. Ref Callbacks: If you are using ref callbacks (passing a function to the ref attribute), these may be invoked twice in development due to the double-mounting behavior of StrictMode [7]. This is an intentional check to ensure that you are handling the cleanup of those refs (e.g., nulling them out) correctly [4][7]. 3. Development-only Behavior: These double-invocations are strictly for development to help you identify bugs, such as side effects during rendering or incomplete cleanup logic [8][2]. This behavior does not occur in production, nor does it impact the state or ref persistence your application relies on for its core logic [2]. If your application behavior is negatively affected by this double-invocation, it typically indicates that your components are performing side effects during the render phase or failing to properly clean up Effects, both of which are common issues that StrictMode is intended to reveal [3][4][2].
Citations:
- 1: https://react.dev/reference/react/useRef
- 2: https://react.dev/learn/keeping-components-pure
- 3: https://react.dev/reference/react/useEffect
- 4: https://react.dev/learn/lifecycle-of-reactive-effects
- 5: https://react.dev/reference/react/useState
- 6: https://react.dev/learn/referencing-values-with-refs
- 7: https://react.dev/learn/manipulating-the-dom-with-refs
- 8: https://react.dev/reference/react/StrictMode
didMountRef 가드는 초기 저장을 막지 못합니다.
StrictMode 개발 모드에서는 effect가 한 번 더 실행되므로, 첫 실행에서 didMountRef.current = true가 된 뒤 다시 saveDashboardLayout(...)이 호출될 수 있습니다. DB 저장이 붙으면 마운트 직후 불필요한 쓰기가 생기니, 초기 실행 여부를 별도 dirty 플래그나 이전 layout 비교로 분리하는 쪽이 안전합니다.
🤖 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 `@src/features/dashboard/edit-layout/model/useDashboardLayout.ts` around lines
29 - 39, The current didMountRef-based guard in useDashboardLayout does not
reliably block the initial save under StrictMode, so the first mount can still
trigger saveDashboardLayout. Update the effect logic in useDashboardLayout to
track whether the layout has actually changed since the initial render, using a
separate dirty flag or previous-layout comparison instead of relying on
didMountRef. Keep the saveDashboardLayout(workspaceId, pageType, { layout })
call behind that change detection so only real edits are persisted.
| function getNextWeekdays(today: WeekdayKey, count: number) { | ||
| const todayIndex = weekdays.findIndex((weekday) => weekday.key === today); | ||
| return Array.from({ length: count }, (_, index) => { | ||
| const weekdayIndex = (todayIndex + index) % weekdays.length; | ||
| return weekdays[weekdayIndex]; | ||
| }); | ||
| } |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
"다음 4일" 요약에 오늘이 중복 포함됨.
getNextWeekdays(today, count)는 index=0일 때 todayIndex + 0이므로 첫 항목이 오늘 자신입니다. 파일 상단 주석(Line 4)은 "오늘 근무 유형별 인원 + 다음 4일 근무 인원"이라고 명시하는데, 실제로는 오늘을 포함해 3일치만 "다음" 정보로 추가됩니다. 상단에서 이미 오늘 근무 인원을 보여주므로 그리드 첫 칸이 중복입니다.
🐛 제안 수정
function getNextWeekdays(today: WeekdayKey, count: number) {
const todayIndex = weekdays.findIndex((weekday) => weekday.key === today);
return Array.from({ length: count }, (_, index) => {
- const weekdayIndex = (todayIndex + index) % weekdays.length;
+ const weekdayIndex = (todayIndex + index + 1) % weekdays.length;
return weekdays[weekdayIndex];
});
}Also applies to: 122-122
🤖 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
`@src/widgets/store-operation/dashboard-work-schedule/ui/WorkScheduleSummary.tsx`
around lines 47 - 53, `getNextWeekdays` is including `today` as the first item,
which duplicates the already-shown current day in `WorkScheduleSummary`. Update
the helper so the sequence starts from the day after `today` (for example by
offsetting the index in `getNextWeekdays`), and keep the existing `weekdays`
lookup logic in `WorkScheduleSummary` aligned with the “오늘 근무 유형별 인원 + 다음 4일”
summary.
|
PR 템플릿 적용과 커밋 단위 분리를 위해 새 브랜치/PR로 다시 올립니다. |
Summary
Validation
Fixes #25
Summary by CodeRabbit
New Features
Bug Fixes