Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
48 commits
Select commit Hold shift + click to select a range
ea973d6
chore: CLAUDE.md gitignore 추가
Kwon812 Jul 6, 2026
4f2ee1c
feat: 위젯 공통 카드 및 AppShell 레이아웃 슬롯 추가
Kwon812 Jul 6, 2026
60034fc
feat: 스프린트 요약 카드 위젯 추가 (스프린트+통계 포인트)
Kwon812 Jul 6, 2026
08a1f30
feat: 내 업무 리스트 위젯 추가
Kwon812 Jul 6, 2026
a454e99
refactor: entities 레이어로 기존 위젯분리
Kwon812 Jul 6, 2026
1a0b6d5
feat: 벨로시티 막대 차트 위젯 추가
Kwon812 Jul 6, 2026
75139f3
feat: 백로그 위젯 추가
Kwon812 Jul 6, 2026
eae3629
feat: 최근 회의록 위젯 추가
Kwon812 Jul 6, 2026
dc1b477
feat: 대시보드 뷰 조립 및 라우트 연결 (react-grid-layout 그리드 + 편집)
Kwon812 Jul 6, 2026
d8acd1e
refactor: AppShell을 워크스페이스 layout으로 이동
Kwon812 Jul 6, 2026
b32c64d
refactor: 엔티티를 모델용으로 축소하고 UI를 각 위젯으로 이동
Kwon812 Jul 6, 2026
676c6c8
feat: 위젯 크기 판정 유틸 및 내 업무·회의록 sm 변형 추가
Kwon812 Jul 6, 2026
ebc9533
refactor: 사이드프로젝트 템플릿 코드를 레이어별 side-project 그룹으로 격리
Kwon812 Jul 6, 2026
9889579
refactor: 편집 인터랙션을 edit-dashboard-layout feature로 분리
Kwon812 Jul 6, 2026
6f425b1
feat: 오늘 일정 위젯 sm 변형 및 추가
Kwon812 Jul 7, 2026
35bbc33
feat: 내 업무·회의록·오늘 일정 위젯 lg 변형 및 모델 확장
Kwon812 Jul 7, 2026
066fe52
fix: 위젯 크기 판정에 높이 반영 및 리스트 오버플로 스크롤 처리
Kwon812 Jul 7, 2026
c4e3016
feat: 수정모드 레이아웃 추가
Kwon812 Jul 7, 2026
4ce6082
Merge remote-tracking branch 'origin/develop' into feat/#9/side-proje…
Kwon812 Jul 7, 2026
0d00aa3
feat: 사이드바, 헤더 적용
Kwon812 Jul 7, 2026
99a1389
fix: 기존 사이드바 수정
Kwon812 Jul 7, 2026
0aac056
feat: 캘린더 위젯 추가
Kwon812 Jul 7, 2026
70ec7ca
refactor: 레이아웃 영속화를 localStorage에서 DB 서버액션(TODO)으로 이관
Kwon812 Jul 7, 2026
246a2f4
refactor: 대시보드 페이지 뷰 이름 변경
Kwon812 Jul 7, 2026
7f0a2b9
feat: 위젯이 없을 때 빈 상태 안내 추가
Kwon812 Jul 7, 2026
db9d74d
refactor: 템플릿 공용 대시보드 엔진으로 분리하고 위젯 카탈로그 도입
Kwon812 Jul 7, 2026
6308910
Merge remote-tracking branch 'origin/develop' into feat/#9/side-proje…
Kwon812 Jul 7, 2026
8345d7d
fix: 중복 import 수정
Kwon812 Jul 7, 2026
9b7ec86
refactor: 대시보드 공용 UI를 shared/dashboard/ui로 이관
Kwon812 Jul 7, 2026
08ef1ee
refactor: 저장은 위젯 위치만, 제약은 카탈로그에서 머지해 데이터/UI 분리 순수화
Kwon812 Jul 7, 2026
0afd621
refactor: 레이아웃 조회를 RSC로 올리고 initialLayout 주입(마운트 페칭 제거)
Kwon812 Jul 7, 2026
e06f3e7
feat: todo 추가
Kwon812 Jul 7, 2026
9adc954
style:prettier 적용
Kwon812 Jul 7, 2026
b22558a
refactor: 타입용 파일명 .types.ts로 통일
Kwon812 Jul 7, 2026
0e49270
fix: getWorkspace()로 purpose동적 연결
Kwon812 Jul 7, 2026
cdf671d
fix: 편집토글버튼 색상값 토큰으로 수정
Kwon812 Jul 8, 2026
feea5b1
fix: todo제거
Kwon812 Jul 8, 2026
df58e1d
refactor: useContainerWidth의 mounted로 클라이언트 마운트 보장
Kwon812 Jul 8, 2026
e4c7e74
refactor: 엔티티에 id 추가하고 리스트 key를 id 기준으로 교체
Kwon812 Jul 8, 2026
ce3ff0f
Merge remote-tracking branch 'origin/develop' into feat/#9/side-proje…
Kwon812 Jul 8, 2026
ed1748f
refactor: task·backlog를 sprint 엔티티로 통합
Kwon812 Jul 8, 2026
21b52b2
Merge remote-tracking branch 'origin/develop' into feat/#9/side-proje…
Kwon812 Jul 8, 2026
7423801
feat: 최근 공지 위젯 추가
Kwon812 Jul 8, 2026
c86fd8c
refactor: Task를 sprint에서 독립 슬라이스로 분리
Kwon812 Jul 8, 2026
d039620
feat: 백로그,태스크 type 명시
Kwon812 Jul 8, 2026
a07900b
style: prettier
Kwon812 Jul 8, 2026
6a029c0
Merge remote-tracking branch 'origin/develop' into feat/#9/side-proje…
Kwon812 Jul 8, 2026
c751585
feat: 최근자료 위젯추가
Kwon812 Jul 8, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -42,5 +42,6 @@ next-env.d.ts

# agent files (개인 작업용)
AGENTS.md
CLAUDE.md
.agents/
.claude/
26 changes: 26 additions & 0 deletions src/app/workspaces/[workspaceId]/dashboard/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// 워크스페이스 대시보드 라우트 — 레이아웃은 서버(RSC)에서 조회해 initialLayout으로 주입한다.
// (레이아웃 조회 키: user_id + workspace_id + page_type / 저장분 없으면 빈 대시보드로 시작)
// purpose는 추가 가능한 위젯을 템플릿별로 거르는 데만 쓰인다.
import { getDashboardLayout } from '@/entities/dashboard-layout';
import { DashboardView } from '@/views/dashboard';
import { getMockWorkspaceById } from '@/entities/workspace';

interface DashboardPageProps {
params: Promise<{ workspaceId: string }>;
}

export default async function DashboardPage({ params }: DashboardPageProps) {
const { workspaceId } = await params;

const workspace = getMockWorkspaceById(workspaceId)!;

const initialLayout = await getDashboardLayout(workspaceId, 'dashboard');
return (
<DashboardView
key={workspaceId}
workspaceId={workspaceId}
purpose={workspace.purpose}
initialLayout={initialLayout}
/>
Comment thread
coderabbitai[bot] marked this conversation as resolved.
);
}
4 changes: 1 addition & 3 deletions src/app/workspaces/[workspaceId]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,7 @@ interface WorkspaceHomePageProps {
}>;
}

export default async function WorkspaceHomePage({
params,
}: WorkspaceHomePageProps) {
export default async function WorkspaceHomePage({ params }: WorkspaceHomePageProps) {
const { workspaceId } = await params;
const workspace = getMockWorkspaceById(workspaceId);

Expand Down
18 changes: 18 additions & 0 deletions src/entities/dashboard-layout/api/get-dashboard-layout.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// 대시보드 레이아웃 조회 — DB 연동 자리.
// 저장분이 없으면(신규) 빈 레이아웃으로 시작한다 — 템플릿 기반 기본값/폴백은 두지 않는다.

import type { DashboardLayoutState } from '../model/dashboard-layout.types';

// TODO: DB 연동 — WORKSPACE_LAYOUTS에서 (workspace_id, user_id(세션), page_type) 기준 select
export async function getDashboardLayout(
workspaceId: string,
pageType: string,
): Promise<DashboardLayoutState> {
void workspaceId;
void pageType;
// 임시 목 저장분 — DB의 layout jsonb를 흉내낸다. 위치(i,x,y,w,h)만 담고,
// 제약(minW/minH)은 저장하지 않는다(렌더 시 카탈로그에서 머지됨).
return {
layout: [],
};
}
16 changes: 16 additions & 0 deletions src/entities/dashboard-layout/api/save-dashboard-layout.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
// 대시보드 레이아웃 저장 — DB 연동 자리(서버액션).
// layout jsonb 한 행 = DashboardLayoutState 통째. 드래그 중 잦은 호출은 debounce 필요.
'use server';

import type { DashboardLayoutState } from '../model/dashboard-layout.types';

// TODO: DB 연동 — WORKSPACE_LAYOUTS upsert (workspace_id, user_id(세션), page_type, layout)
export async function saveDashboardLayout(
workspaceId: string,
pageType: string,
state: DashboardLayoutState,
): Promise<void> {
void workspaceId;
void pageType;
void state;
}
4 changes: 4 additions & 0 deletions src/entities/dashboard-layout/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
// dashboard-layout 엔티티의 Public API — 개인 대시보드 레이아웃 조회/저장.
export type { DashboardLayoutState } from './model/dashboard-layout.types';
export { getDashboardLayout } from './api/get-dashboard-layout';
export { saveDashboardLayout } from './api/save-dashboard-layout';
7 changes: 7 additions & 0 deletions src/entities/dashboard-layout/model/dashboard-layout.types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
// 대시보드 레이아웃 저장 상태 타입 — DB(WORKSPACE_LAYOUTS)의 layout jsonb 한 행에 직렬화된다.
// 화면에 배치된 위젯 = layout. 배치되지 않은 위젯은 카탈로그에서 "추가"로 꺼낸다(별도 hidden 개념 없음).
import type { Layout } from 'react-grid-layout';

export interface DashboardLayoutState {
layout: Layout;
}
9 changes: 5 additions & 4 deletions src/entities/project-column/ui/ProjectColumn.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -52,9 +52,9 @@ export function ProjectColumn({
<header className="flex items-center justify-between">
<div className="flex items-center gap-2.5">
<span className={cn('size-2.5 rounded-full', toneStyles[column.tone])} />
<h3 className="text-[17px] font-bold text-brand-ink">{column.title}</h3>
<h3 className="text-brand-ink text-[17px] font-bold">{column.title}</h3>
</div>
<span className="text-[15px] font-semibold text-brand-muted">{column.tasks.length}</span>
<span className="text-brand-muted text-[15px] font-semibold">{column.tasks.length}</span>
</header>

<div
Expand All @@ -70,7 +70,8 @@ export function ProjectColumn({
key={task.id}
className={cn(
'rounded-[18px] transition-all',
dragOverIndex === index && 'relative before:absolute before:-top-2 before:left-0 before:h-1 before:w-full before:rounded-full before:bg-brand',
dragOverIndex === index &&
'before:bg-brand relative before:absolute before:-top-2 before:left-0 before:h-1 before:w-full before:rounded-full',
)}
onDragOver={(event) => {
event.preventDefault();
Expand All @@ -90,7 +91,7 @@ export function ProjectColumn({
))}

{dragOverIndex === column.tasks.length ? (
<div className="h-1 w-full rounded-full bg-brand" />
<div className="bg-brand h-1 w-full rounded-full" />
) : null}
</div>
</section>
Expand Down
3 changes: 3 additions & 0 deletions src/entities/side-project/meeting-note/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
// meeting-note 엔티티의 Public API (모델)
export { type MeetingNote } from './model/meeting-note.types';
export { mockMeetingNotes } from './model/meeting-note.mock';
29 changes: 29 additions & 0 deletions src/entities/side-project/meeting-note/model/meeting-note.mock.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
// 회의록 목데이터
import type { MeetingNote } from './meeting-note.types';

export const mockMeetingNotes: MeetingNote[] = [
{
id: 'note-1',
title: 'Sprint 2 플래닝',
date: '2025-07-01',
summary: '스프린트 목표와 백로그 우선순위를 확정했습니다.',
},
{
id: 'note-2',
title: 'Sprint 1 회고',
date: '2025-06-28',
summary: '지난 스프린트의 성과와 개선점을 논의했습니다.',
},
{
id: 'note-3',
title: '디자인 시스템 논의',
date: '2025-06-24',
summary: '공통 컴포넌트와 디자인 토큰 구조를 정리했습니다.',
},
{
id: 'note-4',
title: 'API 명세 리뷰',
date: '2025-06-20',
summary: '엔드포인트 규격과 에러 응답 형식을 검토했습니다.',
},
];
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
// 회의록(MeetingNote) 도메인 모델
export interface MeetingNote {
id: string;
title: string;
date: string;
/** 본문 요약(미리보기) */
summary: string;
}
8 changes: 8 additions & 0 deletions src/entities/side-project/schedule-event/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
// schedule-event 엔티티의 Public API (모델)
export {
SCHEDULE_TYPE_COLOR,
type ScheduleEvent,
type ScheduleEventType,
type CalendarMonth,
} from './model/schedule-event.types';
export { mockTodaySchedule, mockCalendar } from './model/schedule-event.mock';
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// 오늘 일정·월간 캘린더 목데이터
import type { CalendarMonth, ScheduleEvent } from './schedule-event.types';

export const mockTodaySchedule: ScheduleEvent[] = [
{ id: 'event-1', title: '디자인 리뷰 회의', time: '11:00', type: 'meeting' },
{ id: 'event-2', title: '팀 점심', time: '12:30', type: 'meeting' },
{ id: 'event-3', title: '스프린트 데일리', time: '14:00', type: 'meeting' },
{ id: 'event-4', title: '코드 리뷰', time: '15:00', type: 'meeting' },
{ id: 'event-5', title: 'API 명세 마감', time: '16:00', type: 'deadline' },
{ id: 'event-6', title: '회고 준비', time: '17:00', type: 'meeting' },
];

export const mockCalendar: CalendarMonth = {
year: 2025,
month: 7,
today: 30,
eventDays: [2, 5, 8, 10, 14, 22, 28],
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// 일정(ScheduleEvent)·캘린더 도메인 모델 + 유형 색상
export type ScheduleEventType = 'meeting' | 'deadline';

/** 일정 유형별 액센트 바 색상 (회의=보라 brand-end, 마감=빨강) */
export const SCHEDULE_TYPE_COLOR: Record<ScheduleEventType, string> = {
meeting: '#8e51ff',
deadline: '#fb2c36',
};

export interface ScheduleEvent {
id: string;
title: string;
time: string;
type: ScheduleEventType;
}

/** 월간 캘린더 데이터 */
export interface CalendarMonth {
year: number;
/** 1-12 */
month: number;
/** 오늘 날짜(일). 해당 월이 아니면 null */
today: number | null;
/** 이벤트 점이 표시될 날짜(일) */
eventDays: number[];
}
4 changes: 4 additions & 0 deletions src/entities/side-project/sprint/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
// sprint 엔티티의 Public API — 스프린트 메타 + 벨로시티
// 업무(Task)는 별도 슬라이스(@/entities/side-project/task)로 분리됨
export { VELOCITY_MAX, type Sprint, type VelocityPoint } from './model/sprint.types';
export { currentSprint, sprintVelocity, SIDE_PROJECT_WORKSPACE_ID } from './model/sprint.mock';
23 changes: 23 additions & 0 deletions src/entities/side-project/sprint/model/sprint.mock.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// 스프린트 목데이터 — 현재 스프린트 메타 + 벨로시티
// 업무(Task)는 여기서 소유하지 않는다 → task.mock.ts / task.selectors.ts 참고
import type { Sprint, VelocityPoint } from './sprint.types';

// 사이드 프로젝트 데모 워크스페이스 id — 스프린트·업무 목데이터가 공유하는 소유 워크스페이스
export const SIDE_PROJECT_WORKSPACE_ID = 'ws-side-project';

export const currentSprint: Sprint = {
id: 'sprint-2',
workspaceId: SIDE_PROJECT_WORKSPACE_ID,
name: 'Sprint 2',
startDate: '2025-07-01',
endDate: '2025-07-14',
daysLeft: 8,
totalPoints: 42,
completedPoints: 28,
};

/** 스프린트별 계획/완료 포인트 추이 */
export const sprintVelocity: VelocityPoint[] = [
{ sprint: 'S1', planned: 38, completed: 34 },
{ sprint: 'S2', planned: 42, completed: 28 },
];
26 changes: 26 additions & 0 deletions src/entities/side-project/sprint/model/sprint.types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// 스프린트 도메인 모델 — 기간·포인트 메타만 가진다.
// 업무(Task)는 Sprint가 소유하지 않고, Task.sprintId로 이 스프린트를 참조한다.
// 스프린트 업무 조회는 getSprintTasks(sprintId), 백로그는 getBacklogTasks(workspaceId)를 쓴다.
export interface Sprint {
id: string;
/** 소유 워크스페이스 — 스프린트/백로그를 이 값으로 조회한다 */
workspaceId: string;
name: string;
/** ISO 날짜 (YYYY-MM-DD) */
startDate: string;
endDate: string;
daysLeft: number;
/** 스프린트 계획 포인트 총합 */
totalPoints: number;
/** 완료 포인트 */
completedPoints: number;
}

export interface VelocityPoint {
sprint: string;
planned: number;
completed: number;
}

/** 벨로시티 차트 Y축 최댓값 */
export const VELOCITY_MAX = 60;
9 changes: 9 additions & 0 deletions src/entities/side-project/task/api/get-backlog-tasks.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
// 백로그(스프린트 미편입) 업무 조회 — Mock 구현.
// 백엔드 준비 시 supabase.from('tasks').select().eq('workspace_id', workspaceId).is('sprint_id', null) 로 교체한다.
// TODO(async): Supabase 전환 시 Promise 반환으로 바꾸고, 소비 위젯을 페칭 구조로 함께 옮긴다.
import type { Task } from '../model/task.types';
import { mockTasks } from './task.mock';

export function getBacklogTasks(workspaceId: string): Task[] {
return mockTasks.filter((task) => task.workspaceId === workspaceId && task.sprintId === null);
}
9 changes: 9 additions & 0 deletions src/entities/side-project/task/api/get-sprint-tasks.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
// 특정 스프린트에 편입된 업무 조회 — Mock 구현.
// 백엔드 준비 시 supabase.from('tasks').select().eq('sprint_id', sprintId) 로 교체한다.
// TODO(async): Supabase 전환 시 Promise 반환으로 바꾸고, 소비 위젯을 페칭 구조로 함께 옮긴다.
import type { Task } from '../model/task.types';
import { mockTasks } from './task.mock';

export function getSprintTasks(sprintId: string): Task[] {
return mockTasks.filter((task) => task.sprintId === sprintId);
}
Loading