Skip to content

feat:매장 운영 대시보드 위젯 추가(#25) - #26

Closed
seongjinss555 wants to merge 1 commit into
developfrom
feat/dashboard-layout-store-schedule
Closed

feat:매장 운영 대시보드 위젯 추가(#25)#26
seongjinss555 wants to merge 1 commit into
developfrom
feat/dashboard-layout-store-schedule

Conversation

@seongjinss555

@seongjinss555 seongjinss555 commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

Summary

  • 매장 운영 대시보드에서 업무 스케줄 위젯을 추가할 수 있게 했습니다.
  • 대시보드 위젯 렌더러에 workspaceId 컨텍스트를 넘겨 최근 자료 위젯이 현재 워크스페이스 자료만 보여주도록 정리했습니다.
  • 레이아웃 저장 호출을 상태 updater 밖으로 옮겨 위젯 추가/삭제 중 React 렌더 단계에서 서버 액션이 호출되지 않게 했습니다.

Validation

  • npm run lint
  • npm run typecheck
  • npm run build

Fixes #25


Compound Engineering
Codex

Summary by CodeRabbit

  • New Features

    • 대시보드에 업무 스케줄 위젯이 새로 추가되었습니다.
    • 매장 운영용 템플릿에 업무 스케줄 위젯이 포함되었습니다.
    • 최근 자료 위젯이 현재 작업공간 기준으로 표시되며, 데이터가 없을 때 빈 상태가 노출됩니다.
  • Bug Fixes

    • 대시보드 레이아웃 저장이 상태 변경 이후에 안정적으로 반영되도록 개선되었습니다.
    • 위젯 렌더링 시 작업공간 정보를 함께 전달하도록 업데이트되었습니다.

@coderabbitai

coderabbitai Bot commented Jul 9, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

대시보드 레이아웃 저장 방식이 콜백 기반 즉시 저장에서 useEffect 기반 지연 저장으로 변경되었고, 위젯 렌더 시그니처에 workspaceId 컨텍스트가 추가되어 DashboardGrid/DashboardView를 통해 전달되며, RecentResources가 workspaceId로 필터링되고 신규 WorkScheduleSummary 위젯이 추가됨.

Changes

대시보드 레이아웃 저장 흐름

Layer / File(s) Summary
레이아웃 변경 시 effect 기반 저장
src/features/dashboard/edit-layout/model/useDashboardLayout.ts
useEffect/useRef를 도입해 최초 마운트 이후 layout 상태 변경 시에만 saveDashboardLayout을 호출하고, handleLayoutChange/addWidget/removeWidget은 상태 업데이트만 수행하도록 변경.

위젯 workspaceId 컨텍스트 및 work-schedule 위젯

Layer / File(s) Summary
WidgetRenderContext 계약 정의
src/shared/dashboard/model/widget.types.ts
WidgetDefinition.render가 size와 WidgetRenderContext(workspaceId 포함)를 함께 받도록 시그니처 확장, WidgetRenderContext 인터페이스 신규 추가.
DashboardGrid/DashboardView의 workspaceId 전달
src/views/dashboard/ui/DashboardGrid.tsx, src/views/dashboard/ui/DashboardView.tsx
DashboardGridProps에 workspaceId 추가 및 widget.render 호출 시 { workspaceId }를 전달, DashboardView가 DashboardGrid에 workspaceId prop을 전달.
RecentResources의 workspaceId 필터링
src/widgets/store-operation/dashboard-recent-resources/ui/RecentResources.tsx
RecentResourcesProps에 workspaceId 필수 필드 추가, mockResources를 workspaceId로 필터링·정렬하고 결과 없을 시 빈 상태 UI 반환.
work-schedule 위젯 신규 추가 및 카탈로그 등록
src/widgets/store-operation/dashboard-work-schedule/ui/WorkScheduleSummary.tsx, .../index.ts, src/views/dashboard/config/widget-catalog.tsx, src/views/dashboard/config/template-widgets.ts
WorkScheduleSummary 컴포넌트를 신규 작성해 근무 인원/유형/요일별 요약을 크기별로 렌더링하고, 이를 index에서 export한 뒤 widget-catalog와 template-widgets(store-operation)에 등록.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Possibly related PRs

  • TeampleRun/syncly#12: work-schedule 관련 목업 데이터(mockWorkScheduleConfig, mockWorkspaceMembers)를 생성한 변경과 직접 연관됨.
  • TeampleRun/syncly#18: 동일한 useDashboardLayout.ts의 레이아웃 저장/편집 로직을 다룸.

Suggested reviewers: 0011810, Kwon812, wjswlgh96

🚥 Pre-merge checks | ✅ 1 | ❌ 4

❌ Failed checks (4 warnings)

Check name Status Explanation Resolution
Description check ⚠️ Warning 요약과 검증은 있으나 템플릿의 작업 내용, 변경 사항, 테스트 체크리스트 등 필수 섹션이 대부분 비어 있습니다. 템플릿의 모든 섹션을 채우고, 작업 내용·변경 사항·테스트 결과·리뷰 요청사항·관련 이슈를 구체적으로 작성하세요.
Linked Issues check ⚠️ Warning 연결 이슈 #25의 캘린더 기능 요구사항과 달리, 이번 변경은 대시보드 위젯과 레이아웃 저장 개선에 집중되어 있습니다. 캘린더 메인 UI, 일정 추가 UI, 라우트 연결, mock 데이터 구조 등 #25의 요구사항을 구현해야 합니다.
Out of Scope Changes check ⚠️ Warning 업무 스케줄 위젯, recent-resources의 workspaceId 연동, 레이아웃 저장 변경은 캘린더 기능 요구와 무관합니다. 캘린더 기능 범위에 맞게 변경을 제한하고, 대시보드 위젯 관련 수정은 별도 PR로 분리하세요.
Docstring Coverage ⚠️ Warning Docstring coverage is 14.29% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (1 passed)
Check name Status Explanation
Title check ✅ Passed 제목이 대시보드 위젯 추가라는 주요 변경을 대체로 반영합니다.
✨ 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 feat/dashboard-layout-store-schedule

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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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

📥 Commits

Reviewing files that changed from the base of the PR and between 376f240 and d72c777.

📒 Files selected for processing (9)
  • src/features/dashboard/edit-layout/model/useDashboardLayout.ts
  • src/shared/dashboard/model/widget.types.ts
  • src/views/dashboard/config/template-widgets.ts
  • src/views/dashboard/config/widget-catalog.tsx
  • src/views/dashboard/ui/DashboardGrid.tsx
  • src/views/dashboard/ui/DashboardView.tsx
  • src/widgets/store-operation/dashboard-recent-resources/ui/RecentResources.tsx
  • src/widgets/store-operation/dashboard-work-schedule/index.ts
  • src/widgets/store-operation/dashboard-work-schedule/ui/WorkScheduleSummary.tsx

Comment on lines +29 to +39
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]);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 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:


🏁 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 -S

Repository: 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 -S

Repository: 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 -S

Repository: 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.tsx

Repository: 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:


🌐 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:


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.

Comment on lines +47 to +53
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];
});
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 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.

@seongjinss555

Copy link
Copy Markdown
Contributor Author

PR 템플릿 적용과 커밋 단위 분리를 위해 새 브랜치/PR로 다시 올립니다.

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.

feat: 캘린더 기능 구현

1 participant