Skip to content

feat: 사이드 프로젝트 스프린트 보드 페이지 구현 - #27

Merged
Kwon812 merged 8 commits into
developfrom
feat/#23/side-project-sprint-board
Jul 9, 2026
Merged

feat: 사이드 프로젝트 스프린트 보드 페이지 구현#27
Kwon812 merged 8 commits into
developfrom
feat/#23/side-project-sprint-board

Conversation

@Kwon812

@Kwon812 Kwon812 commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

Pull Request

작업 내용

  • 사이드 프로젝트 워크스페이스의 스프린트 보드 페이지를 Figma 디자인 기준으로 구현했습니다.
  • 스프린트 요약 헤더 · 칸반 보드(대기/진행 중/완료) · 백로그 세 영역과, 업무 CRUD · 드래그 이동 · URL 기반 스프린트 전환까지 포함합니다.
  • 데이터는 RSC에서 seed하고 이후 상호작용은 클라이언트 낙관적 업데이트로 처리하도록 설계해, 실 API 전환 시 조회부와 훅 액션만 교체하면 되게 구성했습니다.

작업 결과

  • /workspaces/{workspaceId}/sprint-board 진입 시 스프린트 보드 렌더
  • 스프린트 요약 헤더: 현재 스프린트명·기간, 진행률 바(완료/총 포인트·%), 남은 일수/포인트
  • 칸반 보드: 상태별 3열 + 컬럼 헤더(상태 점 + 포인트 합계), 업무 카드(카테고리 태그·포인트·제목·담당자 아바타)
  • 업무 CRUD(목 데이터): 새 업무/백로그 항목 추가로 생성, 카드·행 호버 시 수정/삭제
  • 드래그 이동: 카드를 다른 컬럼으로 드롭해 상태 변경, 드롭 대상 컬럼 하이라이트
  • 담당자 선택: 워크스페이스 멤버 pill에서 선택
  • 스프린트 전환(목 데이터): 상단 셀렉터(?sprint=id) → 네비게이션 → RSC 재조회로 해당 스프린트 seed

변경 사항

Added

  • 라우트: app/workspaces/[workspaceId]/sprint-board/page.tsx — RSC에서 스프린트/업무/백로그/멤버 조회, ?sprint= 파라미터로 선택 스프린트 결정
  • : views/side-project/sprint-board
    • SprintBoardView (페이지 셸 + 조립)
    • SprintSummaryHeader (요약 헤더, 순수 표시)
    • SprintSelector (URL 기반 스프린트 전환, 서버 컴포넌트 <Link>)
  • 피처: features/sprint-board (상호작용 UI 일체)
    • ui/: SprintBoard, SprintColumn, TaskCard, BacklogSection, BacklogRow, TaskFormDialog
    • model/: use-sprint-board(상태·CRUD), use-task-dnd(드래그), sprint-board-columns(상태별 그룹핑·포인트 합계), task-form(폼↔Task 변환)
    • lib/: avatar-color(담당자명 기반 결정론적 색)
  • 엔티티: entities/side-project/sprint
    • api/get-sprints (워크스페이스 스프린트 목록)
    • model/sprint.selectors (resolveCurrentSprint — 진행 중 우선 → 없으면 최신)
    • Sprint 1 목데이터 + mockSprints

실행화면

스크린샷 2026-07-09 오전 11 15 16

테스트

  • 로컬 실행 확인
  • 주요 시나리오 확인 (스프린트 전환 / 업무 추가·수정·삭제 / 드래그 이동 )
  • 영향 범위 확인 (tsc --noEmit, ESLint 통과 / 대시보드 위젯은 currentSprint 기반 유지로 무영향)

리뷰 체크리스트

  • PR base branch가 올바릅니다. (feat/* -> develop)
  • 브랜치명이 Type/#issue-number/description 형식을 따릅니다. (feat/#23/side-project-sprint-board)
  • 커밋 메시지가 컨벤션을 따릅니다.
  • 불필요한 console.log, 주석, 임시 코드를 제거했습니다.
  • 타입 에러와 린트 에러를 확인했습니다.
  • CodeRabbit 1차 리뷰를 확인했습니다.
  • CodeRabbit 리뷰 반영 후 Discord에 공유했습니다.
  • 최소 1명 이상의 approve 후 merge합니다.

리뷰 요청사항

  • 데이터 흐름/아키텍처: RSC에서 초기 데이터 seed → 훅(use-sprint-board) 로컬 상태 → 낙관적 업데이트(재조회 없음). 실 API 전환 시 바뀌는 지점은 page.tsx 조회부와 훅 액션 내부뿐입니다. 이 방향이 적절한지 봐주세요.
  • FSD 배치 기준: "상호작용 있는 UI = feature / 순수 표시 = view·entity"로 나눴습니다. (카드·컬럼·다이얼로그는 feature, 요약 헤더·셀렉터는 view, 데이터·타입은 entity)
  • 스프린트 전환: URL 파라미터 + RSC 재조회 + key={sprint.id} 리마운트 방식이 옳을지 아니면 클라이언트에서 페칭하는 방식으로 갈지 각각 장단점이 있어서 현재 방식이 적절한지 봐주세요.
  • 목 데이터 한계(의도됨): DB 미연동이라 새로고침 시 상태 리셋. Sprint 1은 전용 태스크가 없어 선택 시 칸반이 비고 백로그(워크스페이스 공통)만 표시됩니다.

관련 이슈

Closes #23

Summary by CodeRabbit

  • New Features
    • 워크스페이스 스프린트 보드 페이지와 스프린트 선택 UI가 추가되어, 진행 중 스프린트가 자동으로 선택되고 스프린트를 전환해가며 확인할 수 있습니다.
    • 스프린트 요약 배너(기간, 진행률, 남은 포인트)와 상태별 컬럼/백로그 뷰를 제공합니다.
    • 업무 카드·백로그 항목에 우선순위/담당자 아바타(일관된 색) 표시 및 추가·수정·삭제 모달을 새로 제공합니다.
    • 드래그앤드롭으로 업무 상태를 변경할 수 있습니다.

@coderabbitai

coderabbitai Bot commented Jul 9, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

사이드 프로젝트 워크스페이스에 스프린트 보드 화면이 추가됩니다. 스프린트 조회와 현재 스프린트 선택, 칸반/백로그 상태 관리, 태스크 CRUD와 드래그앤드롭, 업무 폼 다이얼로그, 뷰 셸과 라우트 페이지가 함께 구현됩니다.

Changes

스프린트 보드 기능

Layer / File(s) Summary
스프린트 엔티티 데이터/선택 로직
src/entities/side-project/sprint/api/get-sprints.ts, src/entities/side-project/sprint/index.ts, src/entities/side-project/sprint/model/sprint.mock.ts, src/entities/side-project/sprint/model/sprint.selectors.ts
getSprints가 워크스페이스별 스프린트를 필터링하고, 목데이터와 공개 export가 확장되며, resolveCurrentSprint가 현재 스프린트를 판정합니다.
태스크 폼/컬럼/아바타 모델 유틸
src/features/sprint-board/lib/avatar-color.ts, src/features/sprint-board/model/sprint-board-columns.ts, src/features/sprint-board/model/task-form.ts
아바타 색상 결정, 상태별 컬럼 그룹핑, 폼 값과 Task 간 변환/생성 헬퍼가 추가됩니다.
스프린트 보드 상태/DnD 훅
src/features/sprint-board/model/use-sprint-board.ts, src/features/sprint-board/model/use-task-dnd.ts
useSprintBoard가 태스크 CRUD와 컬럼 파생을 관리하고, useTaskDnd가 드래그앤드롭 상태와 이동 이벤트를 처리합니다.
태스크 카드/컬럼/백로그 UI
src/features/sprint-board/index.ts, src/features/sprint-board/ui/BacklogRow.tsx, src/features/sprint-board/ui/BacklogSection.tsx, src/features/sprint-board/ui/SprintColumn.tsx, src/features/sprint-board/ui/TaskCard.tsx
보드 공용 export와 함께 카드, 컬럼, 백로그 행/섹션 UI가 렌더링 로직과 액션 버튼을 포함해 추가됩니다.
보드 조립 및 업무 폼 다이얼로그
src/features/sprint-board/ui/SprintBoard.tsx, src/features/sprint-board/ui/TaskFormDialog.tsx
SprintBoard가 훅과 UI를 조립하고, TaskFormDialog가 추가/수정 입력과 제출 흐름을 제공합니다.
뷰 조립 및 라우트 페이지
src/views/side-project/sprint-board/index.ts, src/views/side-project/sprint-board/ui/SprintBoardView.tsx, src/views/side-project/sprint-board/ui/SprintSelector.tsx, src/views/side-project/sprint-board/ui/SprintSummaryHeader.tsx, src/app/workspaces/[workspaceId]/sprint-board/page.tsx
RSC 페이지가 초기 데이터를 준비해 뷰에 주입하고, 뷰가 스프린트 선택/요약 헤더/보드를 조립합니다.

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 반환
Loading
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() 호출
Loading

Possibly related PRs

  • TeampleRun/syncly#18: src/entities/side-project/sprint/index.ts의 공개 API 확장 흐름이 이 변경의 barrel export 확장과 직접 연결됩니다.

Suggested reviewers: wjswlgh96, 0011810, JiWoongE, seongjinss555

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 18.75% 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 제목이 사이드 프로젝트 스프린트 보드 페이지 구현이라는 핵심 변경을 정확히 요약합니다.
Description check ✅ Passed 작업 내용, 결과, 변경 사항, 실행화면, 테스트, 리뷰 요청사항, 관련 이슈가 대부분 채워져 템플릿을 충분히 따릅니다.
Linked Issues check ✅ Passed 스프린트 보드 페이지, 요약 헤더, 3열 칸반, 백로그, 드래그, CRUD, 담당자 선택, 스프린트 전환이 요구사항과 맞게 구현되었습니다.
Out of Scope Changes check ✅ Passed 추가된 변경은 모두 스프린트 보드 화면 구현과 이를 위한 데이터·UI·상태 관리 범위 안에 있습니다.
✨ 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/#23/side-project-sprint-board

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

📥 Commits

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

📒 Files selected for processing (21)
  • src/app/workspaces/[workspaceId]/sprint-board/page.tsx
  • src/entities/side-project/sprint/api/get-sprints.ts
  • src/entities/side-project/sprint/index.ts
  • src/entities/side-project/sprint/model/sprint.mock.ts
  • src/entities/side-project/sprint/model/sprint.selectors.ts
  • src/features/sprint-board/index.ts
  • src/features/sprint-board/lib/avatar-color.ts
  • src/features/sprint-board/model/sprint-board-columns.ts
  • src/features/sprint-board/model/task-form.ts
  • src/features/sprint-board/model/use-sprint-board.ts
  • src/features/sprint-board/model/use-task-dnd.ts
  • src/features/sprint-board/ui/BacklogRow.tsx
  • src/features/sprint-board/ui/BacklogSection.tsx
  • src/features/sprint-board/ui/SprintBoard.tsx
  • src/features/sprint-board/ui/SprintColumn.tsx
  • src/features/sprint-board/ui/TaskCard.tsx
  • src/features/sprint-board/ui/TaskFormDialog.tsx
  • src/views/side-project/sprint-board/index.ts
  • src/views/side-project/sprint-board/ui/SprintBoardView.tsx
  • src/views/side-project/sprint-board/ui/SprintSelector.tsx
  • src/views/side-project/sprint-board/ui/SprintSummaryHeader.tsx

Comment thread src/app/workspaces/[workspaceId]/sprint-board/page.tsx Outdated
Comment thread src/app/workspaces/[workspaceId]/sprint-board/page.tsx
Comment thread src/entities/side-project/sprint/model/sprint.selectors.ts
Comment thread src/features/sprint-board/model/use-task-dnd.ts
Comment thread src/features/sprint-board/model/use-task-dnd.ts
Comment on lines +41 to +53
<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>

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

호버 액션 버튼이 포인트 배지와 같은 우측 상단 코너에서 겹칠 수 있음.

헤더 행(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.

Comment thread src/features/sprint-board/ui/TaskFormDialog.tsx Outdated
Comment thread src/features/sprint-board/ui/TaskFormDialog.tsx Outdated
Comment on lines +105 to +115
<input
type="number"
min={0}
aria-label="포인트"
value={values.point}
onChange={(event) =>
setValues((v) => ({ ...v, point: Number(event.target.value) || 0 }))
}
className={inputClass}
/>
</div>

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

포인트 입력값이 음수여도 허용됨.

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.

Suggested change
<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.

Comment on lines +167 to +193
{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>
);
})}

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

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

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

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

Repository: TeampleRun/syncly

Length of output: 4788


담당자 식별을 workspaceNickname 대신 userId로 바꾸세요.
TaskAssignee에 고유 식별자가 없어 닉네임이 같은 멤버가 있으면 선택 표시가 둘 다 활성화될 수 있습니다. assigneeuserId를 함께 저장하고 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 seongjinss555 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

해당 부분 수정만 하시면 될 거 같습니다~

Comment thread src/app/workspaces/[workspaceId]/sprint-board/page.tsx Outdated

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

membersworkspaceId와 무관하게 고정된 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

📥 Commits

Reviewing files that changed from the base of the PR and between ebf45d6 and 792fa45.

📒 Files selected for processing (5)
  • src/app/workspaces/[workspaceId]/sprint-board/page.tsx
  • src/entities/side-project/sprint/model/sprint.selectors.ts
  • src/features/sprint-board/ui/TaskFormDialog.tsx
  • src/views/side-project/sprint-board/index.ts
  • src/views/side-project/sprint-board/ui/SprintBoardView.tsx

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

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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

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

@Kwon812
Kwon812 merged commit 17197dd into develop Jul 9, 2026
1 check passed
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: 사이드 프로젝트 스프린트 보드 페이지 구현

2 participants