diff --git a/src/lib/dashboard-mock-data.ts b/src/lib/dashboard-mock-data.ts
new file mode 100644
index 0000000..48d92bb
--- /dev/null
+++ b/src/lib/dashboard-mock-data.ts
@@ -0,0 +1,80 @@
+import type { ColumnType, TaskType } from '@/components/dashboard/type';
+
+// Mock 컬럼 데이터
+export const mockColumns: ColumnType[] = [
+ {
+ id: '1',
+ title: 'To do',
+ tasks: [
+ {
+ id: 't1',
+ title: 'Test',
+ description:
+ '새로운 테스트 태스크입니다. sample-image를 포함한 테스트용 데이터입니다.',
+ tags: [
+ { label: '테스트', color: 'blue' },
+ { label: '샘플', color: 'green' },
+ ],
+ dueDate: '2025-01-15 14:30',
+ imageUrl: '/dashboard/sample-image.png',
+ manager: {
+ id: 'm1',
+ name: '테스터',
+ nickname: '테스터',
+ profileColor: '#3B82F6',
+ },
+ },
+ {
+ id: 't3',
+ title: '123433333',
+ description: '123433333',
+ tags: [
+ { label: '프로젝트', color: 'orange' },
+ { label: '백엔드', color: 'green' },
+ ],
+ dueDate: '2025-08-28 10:00',
+ imageUrl: '',
+ manager: {
+ id: 'm3',
+ name: 'te',
+ nickname: 'te',
+ profileColor: '#8B5CF6',
+ },
+ },
+ ],
+ },
+ {
+ id: '2',
+ title: 'On progress',
+ tasks: [
+ {
+ id: 't2',
+ title: '2222',
+ description: '2222',
+ tags: [{ label: '프로젝트', color: 'orange' }],
+ dueDate: '2025-08-28 10:30',
+ imageUrl: '',
+ manager: {
+ id: 'm2',
+ name: 'te',
+ nickname: 'te',
+ profileColor: '#8B5CF6',
+ },
+ },
+ ],
+ },
+ {
+ id: '3',
+ title: 'Done',
+ tasks: [],
+ },
+];
+
+// Mock 프로필 색상들
+export const mockProfileColors = [
+ '#8B5CF6', // 보라색
+ '#10B981', // 초록색
+ '#F59E0B', // 주황색
+ '#EF4444', // 빨간색
+ '#3B82F6', // 파란색
+] as const;
diff --git a/src/pages/dashboard/[dashboardId]/index.tsx b/src/pages/dashboard/[dashboardId]/index.tsx
index 9e61023..e359242 100644
--- a/src/pages/dashboard/[dashboardId]/index.tsx
+++ b/src/pages/dashboard/[dashboardId]/index.tsx
@@ -1,104 +1,57 @@
-import { type ReactNode, useState } from 'react';
+import type { GetServerSideProps } from 'next';
+import React, { useState } from 'react';
import ColumnLayout from '@/components/dashboard/column-layout';
import CreateColumnModal from '@/components/dashboard/create-column-modal';
+import CreateTaskModal from '@/components/dashboard/create-task-modal';
import EditTaskModal from '@/components/dashboard/edit-task-modal';
import ManageColumnModal from '@/components/dashboard/manage-column-modal';
import TaskDetailModal from '@/components/dashboard/task-detail-modal';
import type {
ColumnType,
CreateColumnFormData,
+ CreateTaskFormData,
EditTaskFormData,
ManageColumnFormData,
TaskType,
} from '@/components/dashboard/type';
+import DashboardLayout from '@/components/layout/dashboard-layout';
+import { mockColumns, mockProfileColors } from '@/lib/dashboard-mock-data';
+import type { UserType } from '@/lib/users/type';
-export default function DashboardDetailPage(): ReactNode {
+interface DashboardDetailPageProps {
+ userInfo: UserType | null;
+}
+
+export default function DashboardDetailPage({
+ userInfo,
+}: DashboardDetailPageProps): React.ReactElement {
const [isColumnModalOpen, setIsColumnModalOpen] = useState(false);
const [isManageColumnModalOpen, setIsManageColumnModalOpen] = useState(false);
const [selectedColumn, setSelectedColumn] = useState
(null);
const [isDetailModalOpen, setIsDetailModalOpen] = useState(false);
const [selectedTask, setSelectedTask] = useState(null);
const [isEditTaskModalOpen, setIsEditTaskModalOpen] = useState(false);
- const mockColumns: ColumnType[] = [
- {
- id: '1',
- title: 'To do',
- tasks: [
- {
- id: 't1',
- title: '1234',
- description:
- 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.',
- tags: [
- { label: '프로젝트', color: 'orange' },
- { label: '백엔드', color: 'green' },
- ],
- dueDate: '2025-08-28 10:30',
- imageUrl: '/dashboard/sample-image.png',
- manager: {
- id: 'm1',
- name: 'te',
- nickname: 'te',
- profileColor: '#8B5CF6',
- },
- },
- {
- id: 't3',
- title: '123433333',
- description: '123433333',
- tags: [
- { label: '프로젝트', color: 'orange' },
- { label: '백엔드', color: 'green' },
- ],
- dueDate: '2025-08-28 10:00',
- imageUrl: '',
- manager: {
- id: 'm3',
- name: 'te',
- nickname: 'te',
- profileColor: '#8B5CF6',
- },
- },
- ],
- },
- {
- id: '2',
- title: 'On progress',
- tasks: [
- {
- id: 't2',
- title: '2222',
- description: '2222',
- tags: [{ label: '프로젝트', color: 'orange' }],
- dueDate: '2025-08-28 10:30',
- imageUrl: '',
- manager: {
- id: 'm2',
- name: 'te',
- nickname: 'te',
- profileColor: '#8B5CF6',
- },
- },
- ],
- },
- {
- id: '3',
- title: 'Done',
- tasks: [],
- },
- ];
+ const [isCreateTaskModalOpen, setIsCreateTaskModalOpen] = useState(false);
+ const [selectedColumnId, setSelectedColumnId] = useState(null);
+ const [columns, setColumns] = useState(mockColumns);
const handleAddColumnClick = () => {
setIsColumnModalOpen(true);
};
const handleColumnSubmit = (columnData: CreateColumnFormData) => {
- // TODO: 컬럼 생성 API 호출
+ const newColumn: ColumnType = {
+ id: Date.now().toString(),
+ title: columnData.name,
+ tasks: [],
+ };
+
+ setColumns((prevColumns) => [...prevColumns, newColumn]);
setIsColumnModalOpen(false);
};
const handleColumnSettingsClick = (columnId: string) => {
- const column = mockColumns.find((col) => col.id === columnId);
+ const column = columns.find((col) => col.id === columnId);
if (column) {
setSelectedColumn(column);
@@ -110,12 +63,18 @@ export default function DashboardDetailPage(): ReactNode {
columnId: string,
columnData: ManageColumnFormData
) => {
- // TODO: 컬럼 업데이트 API 호출
+ setColumns((prevColumns) => {
+ return prevColumns.map((col) =>
+ col.id === columnId ? { ...col, title: columnData.name } : col
+ );
+ });
setIsManageColumnModalOpen(false);
};
const handleColumnDelete = (columnId: string) => {
- // TODO: 컬럼 삭제 API 호출
+ setColumns((prevColumns) =>
+ prevColumns.filter((col) => col.id !== columnId)
+ );
setIsManageColumnModalOpen(false);
};
@@ -124,6 +83,16 @@ export default function DashboardDetailPage(): ReactNode {
setIsDetailModalOpen(true);
};
+ const getSelectedTaskColumn = () => {
+ if (!selectedTask) {
+ return null;
+ }
+
+ return columns.find((col) =>
+ col.tasks.some((task) => task.id === selectedTask.id)
+ );
+ };
+
const handleTaskEdit = (task: TaskType) => {
setSelectedTask(task);
setIsDetailModalOpen(false);
@@ -131,59 +100,146 @@ export default function DashboardDetailPage(): ReactNode {
};
const handleTaskUpdate = (taskData: EditTaskFormData) => {
- // TODO: 태스크 업데이트 API 호출
+ if (!selectedTask) {
+ return;
+ }
+
+ const updatedTask: TaskType = {
+ ...selectedTask,
+ title: taskData.title,
+ description: taskData.description,
+ tags: taskData.tags,
+ dueDate: taskData.dueDate,
+ imageUrl: taskData.imageFile
+ ? URL.createObjectURL(taskData.imageFile)
+ : (taskData.existingImageUrl ?? ''),
+ manager: {
+ ...selectedTask.manager,
+ name: taskData.assignee,
+ nickname: taskData.assignee,
+ },
+ };
+
+ const currentColumn = columns.find((col) =>
+ col.tasks.some((task) => task.id === selectedTask.id)
+ );
+ const targetColumn = columns.find((col) => col.title === taskData.status);
+
+ setColumns((prevColumns) => {
+ if (
+ currentColumn &&
+ targetColumn &&
+ currentColumn.id !== targetColumn.id
+ ) {
+ return prevColumns.map((col) => {
+ if (col.id === currentColumn.id) {
+ return {
+ ...col,
+ tasks: col.tasks.filter((task) => task.id !== selectedTask.id),
+ };
+ }
+ if (col.id === targetColumn.id) {
+ return {
+ ...col,
+ tasks: [...col.tasks, updatedTask],
+ };
+ }
+
+ return col;
+ });
+ }
+
+ return prevColumns.map((col) => {
+ return {
+ ...col,
+ tasks: col.tasks.map((task) =>
+ task.id === selectedTask.id ? updatedTask : task
+ ),
+ };
+ });
+ });
+
setIsEditTaskModalOpen(false);
+ setSelectedTask(null);
+ };
+
+ const handleAddTaskClick = (columnId: string) => {
+ setSelectedColumnId(columnId);
+ setIsCreateTaskModalOpen(true);
+ };
+
+ const handleCreateTask = (taskData: CreateTaskFormData) => {
+ if (!selectedColumnId) {
+ return;
+ }
+
+ const newTask: TaskType = {
+ id: `task_${String(Date.now())}_${Math.random().toString(36).slice(2, 11)}`,
+ title: taskData.title,
+ description: taskData.description,
+ tags: taskData.tags,
+ dueDate: taskData.dueDate,
+ imageUrl: taskData.imageFile
+ ? URL.createObjectURL(taskData.imageFile)
+ : '',
+ manager: {
+ id: 'm1',
+ name: taskData.assignee,
+ nickname: taskData.assignee,
+ profileColor: mockProfileColors[0],
+ },
+ };
+
+ setColumns((prevColumns) => {
+ return prevColumns.map((col) => {
+ return col.id === selectedColumnId
+ ? { ...col, tasks: [...col.tasks, newTask] }
+ : col;
+ });
+ });
};
return (
- {/* 사이드바 영역 */}
-
-
- {/* 메인 콘텐츠 */}
-
- {/* 상단 네비게이션 헤더 */}
-
-
- {/* 대시보드 메인 콘텐츠 */}
-
- {
- // TODO: 태스크 생성 모달 열기
- }}
- />
-
-
+ {/* 대시보드 메인 콘텐츠 */}
+
+
+
{/* 컬럼 생성 모달 */}
col.title)}
+ maxColumns={10}
onSubmit={handleColumnSubmit}
onClose={() => {
setIsColumnModalOpen(false);
}}
/>
+ {/* 태스크 생성 모달 */}
+ {
+ setIsCreateTaskModalOpen(false);
+ setSelectedColumnId(null);
+ }}
+ />
+
{/* 컬럼 관리 모달 */}
col.title)}
onUpdate={handleColumnUpdate}
onDelete={handleColumnDelete}
onClose={() => {
@@ -195,19 +251,37 @@ export default function DashboardDetailPage(): ReactNode {
{
setIsDetailModalOpen(false);
}}
onDelete={(taskId) => {
- // TODO: 태스크 삭제 API 호출
+ setColumns((prevColumns) => {
+ return prevColumns.map((col) => {
+ return {
+ ...col,
+ tasks: col.tasks.filter((task) => task.id !== taskId),
+ };
+ });
+ });
+ setIsDetailModalOpen(false);
+ setSelectedTask(null);
}}
/>
{/* 할일 수정 모달 */}
({ id: col.id, title: col.title }))}
+ currentColumnTitle={getSelectedTaskColumn()?.title ?? undefined}
+ userInfo={userInfo}
onSubmit={handleTaskUpdate}
onClose={() => {
setIsEditTaskModalOpen(false);
@@ -216,3 +290,46 @@ export default function DashboardDetailPage(): ReactNode {
);
}
+
+export const getServerSideProps: GetServerSideProps = async (context) => {
+ const { req } = context;
+
+ const accessToken = req.cookies.access_token;
+ const isAuthenticated = Boolean(accessToken);
+
+ if (!isAuthenticated) {
+ return {
+ redirect: {
+ destination: '/login',
+ permanent: false,
+ },
+ };
+ }
+
+ let userInfo = null;
+
+ try {
+ const response = await fetch(`/api/users/me`, {
+ headers: {
+ 'Content-Type': 'application/json',
+ Authorization: `Bearer ${String(accessToken)}`,
+ },
+ });
+
+ if (response.ok) {
+ userInfo = await response.json();
+ }
+ } catch {
+ // 서버에서 사용자 정보 조회 실패 시 기본값 사용
+ }
+
+ return {
+ props: {
+ userInfo,
+ },
+ };
+};
+
+DashboardDetailPage.getLayout = function getLayout(page: React.ReactElement) {
+ return {page};
+};
diff --git a/src/styles/globals.css b/src/styles/globals.css
index da25932..9853ef1 100644
--- a/src/styles/globals.css
+++ b/src/styles/globals.css
@@ -35,7 +35,6 @@
--color-chip-green-bg: #e7f7db;
--color-chip-brown: #d58d49;
--color-chip-brown-bg: #f9eee3;
-
--color-chip-red: #d25b68;
--color-chip-red-bg: #f4d7da;
diff --git a/src/utils/profile-color.ts b/src/utils/profile-color.ts
new file mode 100644
index 0000000..bfd9c91
--- /dev/null
+++ b/src/utils/profile-color.ts
@@ -0,0 +1,53 @@
+/**
+ * HEX 색상을 ChipProfile 컴포넌트의 color prop으로 변환하는 함수
+ */
+export const getProfileColor = (
+ profileColor: string
+): 'green' | 'blue' | 'orange' | 'yellow' | 'brown' | 'red' => {
+ switch (profileColor.toLowerCase()) {
+ // HEX 색상
+ case '#8b5cf6': // 보라색
+ case '#10b981': { // 초록색
+ return 'green';
+ }
+ case '#f59e0b': { // 주황색
+ return 'orange';
+ }
+ case '#ef4444': { // 빨간색
+ return 'red';
+ }
+ case '#3b82f6': { // 파란색
+ return 'blue';
+ }
+ case '#eab308': { // 노란색
+ return 'yellow';
+ }
+ case '#a16207': { // 갈색
+ return 'brown';
+ }
+
+ // 문자열 색상 (사용자 프로필에서 사용)
+ case 'green': {
+ return 'green';
+ }
+ case 'orange': {
+ return 'orange';
+ }
+ case 'red': {
+ return 'red';
+ }
+ case 'blue': {
+ return 'blue';
+ }
+ case 'yellow': {
+ return 'yellow';
+ }
+ case 'brown': {
+ return 'brown';
+ }
+
+ default: {
+ return 'green';
+ }
+ }
+};