feat: 사이드 프로젝트 스프린트 보드 페이지 구현 - #27
Conversation
📝 WalkthroughWalkthrough사이드 프로젝트 워크스페이스에 스프린트 보드 화면이 추가됩니다. 스프린트 조회와 현재 스프린트 선택, 칸반/백로그 상태 관리, 태스크 CRUD와 드래그앤드롭, 업무 폼 다이얼로그, 뷰 셸과 라우트 페이지가 함께 구현됩니다. Changes스프린트 보드 기능
Estimated code review effort: 4 (Complex) | ~60 minutes Sequence Diagram(s)sequenceDiagram
participant SprintBoardPage
participant getSprints
participant resolveCurrentSprint
participant SprintBoardView
participant SprintBoard
participant useSprintBoard
SprintBoardPage->>getSprints: workspaceId로 스프린트 목록 조회
SprintBoardPage->>resolveCurrentSprint: sprint 쿼리 없을 때 현재 스프린트 판정
SprintBoardPage->>SprintBoardView: sprint, sprints, initialTasks, initialBacklog 주입
SprintBoardView->>SprintBoard: workspaceId, sprintId, members 전달
SprintBoard->>useSprintBoard: 초기 데이터 seed
useSprintBoard-->>SprintBoard: columns, backlogTasks 반환
sequenceDiagram
participant User
participant SprintBoard
participant TaskFormDialog
participant useSprintBoard
User->>SprintBoard: 업무 추가/편집 버튼 클릭
SprintBoard->>TaskFormDialog: dialog 상태와 초기값 전달
User->>TaskFormDialog: 폼 입력 후 저장
TaskFormDialog->>SprintBoard: onSubmit(values) 호출
SprintBoard->>useSprintBoard: addSprintTask/addBacklogTask/updateTask 호출
TaskFormDialog->>SprintBoard: onClose() 호출
Possibly related PRs
Suggested reviewers: 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 10
🤖 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/app/workspaces/`[workspaceId]/sprint-board/page.tsx:
- Line 8: The page is importing the sprint board view through the internal ui
path instead of the view layer’s public API, which bypasses the barrel export.
Update the import in SprintBoardPage to use the public export from the
sprint-board index entry, and change the render usage from SprintBoardView to
SprintBoardPage so the component is consumed through the intended FSD boundary.
- Line 43: The Sprint Board page is passing mockWorkspaceMembers directly, which
is tied to store-workspace and can show the wrong assignee options for other
workspaceId values. Update the page component that renders this members prop so
it derives the member list from the current workspaceId instead of using the
fixed mockWorkspaceMembers constant, using the existing workspace-related data
source or selector in page.tsx.
In `@src/entities/side-project/sprint/model/sprint.selectors.ts`:
- Around line 6-14: The current date calculation in resolveCurrentSprint uses
toISOString(), which evaluates in UTC and can misclassify the active sprint in
local time zones. Update resolveCurrentSprint to derive a local YYYY-MM-DD value
instead of using new Date().toISOString().slice(0, 10), and keep the ongoing
comparison logic unchanged so sprint start/end checks use local calendar date
boundaries.
In `@src/features/sprint-board/model/use-task-dnd.ts`:
- Around line 29-41: The drag-over highlight in use-task-dnd is not cleared when
the pointer leaves a drop zone, so add an onDragLeave handler in dropProps
alongside onDragOver/onDrop to reset dragOverStatus when leaving the current
status. Update the use-task-dnd hook’s drag state handling so dragOverStatus is
cleared for non-drop-area exits while still preserving reset() on drop and drag
end.
- Around line 9-44: The sprint board currently relies on drag-and-drop in
useTaskDnd for moving tasks, so keyboard-only users have no way to change
status. Update TaskFormDialog to include a task status field alongside the
existing title/point/priority/category/assignee inputs, and wire it into the
same task update flow so users can move a task between columns without dragging.
Make sure the new status control uses the existing TaskStatus model and stays
consistent with the onMove behavior used by useTaskDnd.
In `@src/features/sprint-board/ui/TaskCard.tsx`:
- Around line 41-53: The header row in TaskCard is using the same top-right
space as the hover action buttons, which can cause the point badge and the
absolute action controls to overlap. Update TaskCard’s header/layout around the
task.point text and the hover action block so the point label has enough
right-side spacing or the hover buttons are repositioned away from that corner.
Keep the fix aligned with the TaskCard component structure and the hover action
container that uses absolute positioning.
In `@src/features/sprint-board/ui/TaskFormDialog.tsx`:
- Around line 105-115: The TaskFormDialog point field currently allows negative
values because the onChange handler in the input update logic uses
Number(event.target.value) || 0, which preserves truthy negatives; update the
point normalization so the value is clamped at a minimum of 0 before storing it
in setValues, keeping the existing input and values.point flow intact.
- Line 96: Remove the unnecessary autoFocus from the input in TaskFormDialog;
the issue is in the component’s form field props where autoFocus is set, and it
should be deleted so focus is not forced when the dialog opens.
- Around line 72-78: The TaskFormDialog modal currently uses a custom
div/section with role="dialog" and no focus trap, so keyboard focus can escape
the overlay. Update TaskFormDialog to use a native dialog element with
showModal()/close() driven by a dialogRef, and keep the existing open/close flow
wired through the component so focus trapping, Escape handling, and backdrop
behavior are handled natively.
- Around line 167-193: Use the unique member identifier for assignee selection
in TaskFormDialog instead of workspaceNickname. Update the assignee state in the
button onClick handler to store member.userId alongside the display fields in
TaskAssignee, and change the selected comparison to match on userId so only the
intended member is highlighted when names collide.
🪄 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: 668464b9-4d5d-487e-a52b-d2538ae459b7
📒 Files selected for processing (21)
src/app/workspaces/[workspaceId]/sprint-board/page.tsxsrc/entities/side-project/sprint/api/get-sprints.tssrc/entities/side-project/sprint/index.tssrc/entities/side-project/sprint/model/sprint.mock.tssrc/entities/side-project/sprint/model/sprint.selectors.tssrc/features/sprint-board/index.tssrc/features/sprint-board/lib/avatar-color.tssrc/features/sprint-board/model/sprint-board-columns.tssrc/features/sprint-board/model/task-form.tssrc/features/sprint-board/model/use-sprint-board.tssrc/features/sprint-board/model/use-task-dnd.tssrc/features/sprint-board/ui/BacklogRow.tsxsrc/features/sprint-board/ui/BacklogSection.tsxsrc/features/sprint-board/ui/SprintBoard.tsxsrc/features/sprint-board/ui/SprintColumn.tsxsrc/features/sprint-board/ui/TaskCard.tsxsrc/features/sprint-board/ui/TaskFormDialog.tsxsrc/views/side-project/sprint-board/index.tssrc/views/side-project/sprint-board/ui/SprintBoardView.tsxsrc/views/side-project/sprint-board/ui/SprintSelector.tsxsrc/views/side-project/sprint-board/ui/SprintSummaryHeader.tsx
| <div className="flex items-center justify-between gap-2"> | ||
| {category ? ( | ||
| <span | ||
| className="rounded-md px-2 py-0.5 text-xs font-semibold" | ||
| style={{ backgroundColor: category.bg, color: category.text }} | ||
| > | ||
| {category.label} | ||
| </span> | ||
| ) : ( | ||
| <span /> | ||
| )} | ||
| <span className="text-brand-muted text-xs">{task.point}pt</span> | ||
| </div> |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
호버 액션 버튼이 포인트 배지와 같은 우측 상단 코너에서 겹칠 수 있음.
헤더 행(Line 41-53)의 포인트 텍스트({task.point}pt)와 호버 시 나타나는 액션 버튼(Line 70, absolute top-2 right-2)이 같은 우측 상단 코너에 위치해, 호버 시 두 요소가 겹치는 시각적 충돌이 발생할 수 있습니다.
💡 제안: 포인트 텍스트에 우측 여백 확보 또는 액션 버튼 위치 조정
<div className="flex items-center justify-between gap-2">
{category ? (
<span ...>{category.label}</span>
) : (
<span />
)}
- <span className="text-brand-muted text-xs">{task.point}pt</span>
+ <span className={`text-brand-muted text-xs ${hasActions ? 'pr-14' : ''}`}>
+ {task.point}pt
+ </span>
</div>Also applies to: 69-92
🤖 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/sprint-board/ui/TaskCard.tsx` around lines 41 - 53, The header
row in TaskCard is using the same top-right space as the hover action buttons,
which can cause the point badge and the absolute action controls to overlap.
Update TaskCard’s header/layout around the task.point text and the hover action
block so the point label has enough right-side spacing or the hover buttons are
repositioned away from that corner. Keep the fix aligned with the TaskCard
component structure and the hover action container that uses absolute
positioning.
| <input | ||
| type="number" | ||
| min={0} | ||
| aria-label="포인트" | ||
| value={values.point} | ||
| onChange={(event) => | ||
| setValues((v) => ({ ...v, point: Number(event.target.value) || 0 })) | ||
| } | ||
| className={inputClass} | ||
| /> | ||
| </div> |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
포인트 입력값이 음수여도 허용됨.
min={0}은 HTML 힌트일 뿐, 사용자가 직접 -5 등을 입력하면 Number(event.target.value) || 0이 -5(truthy)를 그대로 반환해 음수 포인트가 생성될 수 있습니다.
🔧 음수 클램프 추가
- onChange={(event) =>
- setValues((v) => ({ ...v, point: Number(event.target.value) || 0 }))
- }
+ onChange={(event) =>
+ setValues((v) => ({ ...v, point: Math.max(0, Number(event.target.value) || 0) }))
+ }📝 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.
| <input | |
| type="number" | |
| min={0} | |
| aria-label="포인트" | |
| value={values.point} | |
| onChange={(event) => | |
| setValues((v) => ({ ...v, point: Number(event.target.value) || 0 })) | |
| } | |
| className={inputClass} | |
| /> | |
| </div> | |
| <input | |
| type="number" | |
| min={0} | |
| aria-label="포인트" | |
| value={values.point} | |
| onChange={(event) => | |
| setValues((v) => ({ ...v, point: Math.max(0, Number(event.target.value) || 0) })) | |
| } | |
| className={inputClass} | |
| /> | |
| </div> |
🤖 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/sprint-board/ui/TaskFormDialog.tsx` around lines 105 - 115, The
TaskFormDialog point field currently allows negative values because the onChange
handler in the input update logic uses Number(event.target.value) || 0, which
preserves truthy negatives; update the point normalization so the value is
clamped at a minimum of 0 before storing it in setValues, keeping the existing
input and values.point flow intact.
| {members.map((member) => { | ||
| const selected = values.assignee?.name === member.workspaceNickname; | ||
| return ( | ||
| <button | ||
| key={member.userId} | ||
| type="button" | ||
| onClick={() => | ||
| setValues((v) => ({ | ||
| ...v, | ||
| assignee: { | ||
| name: member.workspaceNickname, | ||
| avatarLabel: member.avatarLabel, | ||
| }, | ||
| })) | ||
| } | ||
| className={cn(pillClass, selected && pillActiveClass)} | ||
| > | ||
| <span | ||
| className="flex size-5 items-center justify-center rounded-full text-[10px] font-semibold text-white" | ||
| style={{ backgroundColor: getAvatarColor(member.avatarLabel) }} | ||
| > | ||
| {member.avatarLabel} | ||
| </span> | ||
| {member.workspaceNickname} | ||
| </button> | ||
| ); | ||
| })} |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
ast-grep run --pattern 'interface TaskAssignee { $$$ }' --lang typescript src/entities/side-project/task
rg -n "TaskAssignee" src/entities/side-project/task -A 10Repository: TeampleRun/syncly
Length of output: 1923
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== TaskFormDialog relevant slice =="
sed -n '140,220p' src/features/sprint-board/ui/TaskFormDialog.tsx
echo
echo "== workspaceNickname usages =="
rg -n "workspaceNickname" src -A 3 -B 3
echo
echo "== task types =="
sed -n '1,120p' src/entities/side-project/task/model/task.types.tsRepository: TeampleRun/syncly
Length of output: 14297
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== sprint board member model usages =="
rg -n "type .*Member|interface .*Member|workspaceNickname|avatarLabel|userId" src/features src/entities src/shared -A 4 -B 2
echo
echo "== files exporting member types in side-project domain =="
git ls-files 'src/entities/side-project/**' | sed -n '1,200p'Repository: TeampleRun/syncly
Length of output: 30668
🏁 Script executed:
#!/bin/bash
set -euo pipefail
sed -n '1,220p' src/features/sprint-board/model/task-form.ts
echo
sed -n '1,140p' src/features/sprint-board/ui/TaskCard.tsxRepository: TeampleRun/syncly
Length of output: 4788
담당자 식별을 workspaceNickname 대신 userId로 바꾸세요.
TaskAssignee에 고유 식별자가 없어 닉네임이 같은 멤버가 있으면 선택 표시가 둘 다 활성화될 수 있습니다. assignee에 userId를 함께 저장하고 selected 비교도 그 값으로 맞추는 편이 안전합니다.
🤖 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/sprint-board/ui/TaskFormDialog.tsx` around lines 167 - 193, Use
the unique member identifier for assignee selection in TaskFormDialog instead of
workspaceNickname. Update the assignee state in the button onClick handler to
store member.userId alongside the display fields in TaskAssignee, and change the
selected comparison to match on userId so only the intended member is
highlighted when names collide.
seongjinss555
left a comment
There was a problem hiding this comment.
해당 부분 수정만 하시면 될 거 같습니다~
There was a problem hiding this comment.
Actionable comments posted: 1
♻️ Duplicate comments (4)
src/features/sprint-board/ui/TaskFormDialog.tsx (3)
100-107: 📐 Maintainability & Code Quality | 🟡 Minor | 💤 Low value
autoFocus가 여전히 존재함.이전 리뷰(React Doctor)에서 지적된
autoFocus제거가 반영되지 않았습니다.🤖 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/sprint-board/ui/TaskFormDialog.tsx` around lines 100 - 107, The TaskFormDialog input still has autoFocus enabled, so remove the autoFocus prop from the title input in TaskFormDialog and keep the rest of the controlled input behavior unchanged. Locate the input element bound to values.title and onChange in TaskFormDialog, and ensure no other autofocus behavior remains in this dialog.
111-121: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win포인트 음수 입력 클램프가 여전히 누락됨.
Number(event.target.value) || 0은-5같은 음수를 그대로 허용합니다. 이전 리뷰에서 지적된 사항이 반영되지 않았습니다.🤖 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/sprint-board/ui/TaskFormDialog.tsx` around lines 111 - 121, The point input handler in TaskFormDialog still allows negative values because Number(event.target.value) only falls back for NaN and does not clamp below zero. Update the onChange logic for the values.point field so it explicitly normalizes the parsed number to a minimum of 0 before calling setValues, keeping the existing input control and point state handling intact.
173-199: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win담당자 식별을
workspaceNickname으로 하는 문제가 여전히 존재함.
values.assignee?.name === member.workspaceNickname비교와assignee저장 시userId를 포함하지 않아, 닉네임이 같은 멤버가 있으면 선택 표시가 잘못될 수 있습니다. 이전 리뷰에서 지적된 사항이 반영되지 않았습니다.🤖 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/sprint-board/ui/TaskFormDialog.tsx` around lines 173 - 199, The assignee selection in TaskFormDialog still identifies members by workspaceNickname, which can collide for duplicate nicknames. Update the selection logic in the members.map block to use a stable identifier such as member.userId, and store that identifier in values.assignee when setValues runs instead of only name/avatarLabel. Make sure the selected comparison and the saved assignee shape both reference the same unique field so the correct member is highlighted and persisted.src/app/workspaces/[workspaceId]/sprint-board/page.tsx (1)
43-43: 🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win
members가workspaceId와 무관하게 고정된mockWorkspaceMembers를 사용함.이전 리뷰에서 지적된 대로,
mockWorkspaceMembers는 특정 워크스페이스 전용 데이터라 다른workspaceId로 진입 시 담당자 후보가 잘못 표시됩니다. 아직 반영되지 않았습니다.🤖 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/app/workspaces/`[workspaceId]/sprint-board/page.tsx at line 43, The SprintBoard page is still passing a fixed mock member list instead of workspace-specific data, so the assignee candidates do not change with the current workspace. Update the `SprintBoard` usage in `page.tsx` to source `members` from the active `workspaceId` rather than `mockWorkspaceMembers`, and wire it through the workspace-aware data path or fetch logic used by this page. Use the `members` prop and `workspaceId` handling in the page component to locate the change.
🤖 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/sprint-board/ui/TaskFormDialog.tsx`:
- Around line 58-70: The TaskFormDialog useEffect is coupling dialog opening
with the onClose callback, which can cause showModal() to run again when onClose
gets a new reference from parent re-renders. Split the logic in TaskFormDialog
so showModal() is only called once on mount for dialogRef/current, and keep a
separate effect or stable listener setup for the cancel handler that updates
with onClose changes. Make sure the dialog.addEventListener/removeEventListener
lifecycle still uses the latest onClose without reopening an already-open
dialog.
---
Duplicate comments:
In `@src/app/workspaces/`[workspaceId]/sprint-board/page.tsx:
- Line 43: The SprintBoard page is still passing a fixed mock member list
instead of workspace-specific data, so the assignee candidates do not change
with the current workspace. Update the `SprintBoard` usage in `page.tsx` to
source `members` from the active `workspaceId` rather than
`mockWorkspaceMembers`, and wire it through the workspace-aware data path or
fetch logic used by this page. Use the `members` prop and `workspaceId` handling
in the page component to locate the change.
In `@src/features/sprint-board/ui/TaskFormDialog.tsx`:
- Around line 100-107: The TaskFormDialog input still has autoFocus enabled, so
remove the autoFocus prop from the title input in TaskFormDialog and keep the
rest of the controlled input behavior unchanged. Locate the input element bound
to values.title and onChange in TaskFormDialog, and ensure no other autofocus
behavior remains in this dialog.
- Around line 111-121: The point input handler in TaskFormDialog still allows
negative values because Number(event.target.value) only falls back for NaN and
does not clamp below zero. Update the onChange logic for the values.point field
so it explicitly normalizes the parsed number to a minimum of 0 before calling
setValues, keeping the existing input control and point state handling intact.
- Around line 173-199: The assignee selection in TaskFormDialog still identifies
members by workspaceNickname, which can collide for duplicate nicknames. Update
the selection logic in the members.map block to use a stable identifier such as
member.userId, and store that identifier in values.assignee when setValues runs
instead of only name/avatarLabel. Make sure the selected comparison and the
saved assignee shape both reference the same unique field so the correct member
is highlighted and persisted.
🪄 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: 0bdb99c5-bc31-4627-8542-93ceeb9b609a
📒 Files selected for processing (5)
src/app/workspaces/[workspaceId]/sprint-board/page.tsxsrc/entities/side-project/sprint/model/sprint.selectors.tssrc/features/sprint-board/ui/TaskFormDialog.tsxsrc/views/side-project/sprint-board/index.tssrc/views/side-project/sprint-board/ui/SprintBoardView.tsx
| useEffect(() => { | ||
| const dialog = dialogRef.current; | ||
| if (!dialog) return undefined; | ||
|
|
||
| dialog.showModal(); | ||
|
|
||
| const handleCancel = (event: Event) => { | ||
| event.preventDefault(); | ||
| onClose(); | ||
| }; | ||
| dialog.addEventListener('cancel', handleCancel); | ||
| return () => dialog.removeEventListener('cancel', handleCancel); | ||
| }, [onClose]); |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "[1] file map"
git ls-files 'src/features/sprint-board/ui/*' 'src/features/sprint-board/**/*' | sed -n '1,120p'
echo
echo "[2] outline"
ast-grep outline src/features/sprint-board/ui/TaskFormDialog.tsx --view expanded || true
echo
echo "[3] file with line numbers"
cat -n src/features/sprint-board/ui/TaskFormDialog.tsx | sed -n '1,220p'
echo
echo "[4] search for TaskFormDialog usage / onClose patterns"
rg -n "TaskFormDialog|onClose=" src/features/sprint-board -SRepository: TeampleRun/syncly
Length of output: 10946
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "[1] SprintBoard outline"
ast-grep outline src/features/sprint-board/ui/SprintBoard.tsx --view expanded || true
echo
echo "[2] relevant lines"
cat -n src/features/sprint-board/ui/SprintBoard.tsx | sed -n '1,180p'Repository: TeampleRun/syncly
Length of output: 4310
showModal() 호출을 onClose 변경과 분리하세요 (src/features/sprint-board/ui/TaskFormDialog.tsx:58-70)
부모가 다시 렌더되면 inline onClose가 새 참조가 되어 이 effect가 재실행되고, 이미 열린 <dialog>에 showModal()이 다시 호출되어 InvalidStateError가 날 수 있습니다. showModal()은 마운트 시 1회만 실행하고, cancel 리스너만 onClose 변경에 맞춰 갱신하세요.
🤖 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/sprint-board/ui/TaskFormDialog.tsx` around lines 58 - 70, The
TaskFormDialog useEffect is coupling dialog opening with the onClose callback,
which can cause showModal() to run again when onClose gets a new reference from
parent re-renders. Split the logic in TaskFormDialog so showModal() is only
called once on mount for dialogRef/current, and keep a separate effect or stable
listener setup for the cancel handler that updates with onClose changes. Make
sure the dialog.addEventListener/removeEventListener lifecycle still uses the
latest onClose without reopening an already-open dialog.
Pull Request
작업 내용
작업 결과
/workspaces/{workspaceId}/sprint-board진입 시 스프린트 보드 렌더새 업무/백로그 항목 추가로 생성, 카드·행 호버 시 수정/삭제?sprint=id) → 네비게이션 → RSC 재조회로 해당 스프린트 seed변경 사항
Added
app/workspaces/[workspaceId]/sprint-board/page.tsx— RSC에서 스프린트/업무/백로그/멤버 조회,?sprint=파라미터로 선택 스프린트 결정views/side-project/sprint-boardSprintBoardView(페이지 셸 + 조립)SprintSummaryHeader(요약 헤더, 순수 표시)SprintSelector(URL 기반 스프린트 전환, 서버 컴포넌트<Link>)features/sprint-board(상호작용 UI 일체)ui/:SprintBoard,SprintColumn,TaskCard,BacklogSection,BacklogRow,TaskFormDialogmodel/:use-sprint-board(상태·CRUD),use-task-dnd(드래그),sprint-board-columns(상태별 그룹핑·포인트 합계),task-form(폼↔Task 변환)lib/:avatar-color(담당자명 기반 결정론적 색)entities/side-project/sprintapi/get-sprints(워크스페이스 스프린트 목록)model/sprint.selectors(resolveCurrentSprint— 진행 중 우선 → 없으면 최신)Sprint 1목데이터 +mockSprints실행화면
테스트
tsc --noEmit, ESLint 통과 / 대시보드 위젯은currentSprint기반 유지로 무영향)리뷰 체크리스트
feat/*->develop)Type/#issue-number/description형식을 따릅니다. (feat/#23/side-project-sprint-board)console.log, 주석, 임시 코드를 제거했습니다.리뷰 요청사항
use-sprint-board) 로컬 상태 → 낙관적 업데이트(재조회 없음). 실 API 전환 시 바뀌는 지점은page.tsx조회부와 훅 액션 내부뿐입니다. 이 방향이 적절한지 봐주세요.key={sprint.id}리마운트 방식이 옳을지 아니면 클라이언트에서 페칭하는 방식으로 갈지 각각 장단점이 있어서 현재 방식이 적절한지 봐주세요.Sprint 1은 전용 태스크가 없어 선택 시 칸반이 비고 백로그(워크스페이스 공통)만 표시됩니다.관련 이슈
Closes #23
Summary by CodeRabbit