Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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 src/entities/progress-chart/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@ export type {
ProgressChartAssigneeItem,
ProgressChartStatusItem,
} from './model/progress-chart.types';
export { createProgressChartSummary } from './model/create-progress-chart-summary';
78 changes: 78 additions & 0 deletions src/entities/progress-chart/model/create-progress-chart-summary.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import type { Task, TaskStatus } from '@/entities/task';

import type {
ProgressChartAssigneeItem,
ProgressChartStatusItem,
ProgressChartSummary,
} from './progress-chart.types';

const STATUS_META: Record<TaskStatus, Pick<ProgressChartStatusItem, 'label' | 'color'>> = {
done: {
label: '완료',
color: '#574BEA',
},
'in-progress': {
label: '진행 중',
color: '#7D6AF3',
},
todo: {
label: '대기',
color: '#DED9FF',
},
};

function toPercentage(value: number, total: number) {
if (total === 0) {
return 0;
}

return Math.round((value / total) * 1000) / 10;
}
Comment thread
JiWoongE marked this conversation as resolved.

function countByStatus(tasks: Task[], status: TaskStatus) {
return tasks.filter((task) => task.status === status).length;
}

export function createProgressChartSummary(tasks: Task[]): ProgressChartSummary {
const totalTaskCount = tasks.length;
const doneTaskCount = countByStatus(tasks, 'done');
const inProgressTaskCount = countByStatus(tasks, 'in-progress');
const overallProgressRate = toPercentage(doneTaskCount, totalTaskCount);

const assigneeMap = new Map<string, ProgressChartAssigneeItem>();
tasks.forEach((task) => {
const current = assigneeMap.get(task.assignee) ?? {
name: task.assignee,
count: 0,
};

current.count += 1;
assigneeMap.set(task.assignee, current);
});

const assigneeItems = [...assigneeMap.values()].sort((left, right) => {
if (right.count !== left.count) {
return right.count - left.count;
}

return left.name.localeCompare(right.name, 'ko');
});

const statusItems: ProgressChartStatusItem[] = (
Object.entries(STATUS_META) as Array<[TaskStatus, (typeof STATUS_META)[TaskStatus]]>
).map(([status, meta]) => ({
id: status,
label: meta.label,
count: countByStatus(tasks, status),
color: meta.color,
}));

return {
totalTaskCount,
doneTaskCount,
inProgressTaskCount,
overallProgressRate,
assigneeItems,
statusItems,
};
}
248 changes: 246 additions & 2 deletions src/entities/task/model/mock-tasks-by-workspace.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
import type { Task } from './task.types';

const TEAM_PROJECT_WORKSPACE_UUID = '00000000-0000-0000-0000-000000001001';
const SIDE_PROJECT_WORKSPACE_UUID = '00000000-0000-0000-0000-000000001002';
const STORE_OPERATION_WORKSPACE_UUID = '00000000-0000-0000-0000-000000001003';

const mockTasksByWorkspaceId: Record<string, Task[]> = {
test: [
{
Expand Down Expand Up @@ -73,8 +77,248 @@ const mockTasksByWorkspaceId: Record<string, Task[]> = {
status: 'done',
},
],
'team-workspace': [
{
id: 'task-1',
workspaceId: 'team-workspace',
title: '사용자 인터뷰 설문지 제작',
assignee: '박서준',
assigneeInitial: '박',
assigneeColor: '#00C950',
dueDate: '7/5',
status: 'todo',
},
{
id: 'task-2',
workspaceId: 'team-workspace',
title: 'DB 스키마 설계',
assignee: '김지은',
assigneeInitial: '김',
assigneeColor: '#FE9A00',
dueDate: '7/6',
status: 'todo',
},
{
id: 'task-3',
workspaceId: 'team-workspace',
title: '스프린트 1 회고 준비',
assignee: '이하은',
assigneeInitial: '이',
assigneeColor: '#615FFF',
dueDate: '7/10',
status: 'todo',
},
{
id: 'task-4',
workspaceId: 'team-workspace',
title: '와이어프레임 초안 작성',
assignee: '김지은',
assigneeInitial: '김',
assigneeColor: '#00B8DB',
dueDate: '7/3',
status: 'in-progress',
},
{
id: 'task-5',
workspaceId: 'team-workspace',
title: '랜딩 페이지 디자인',
assignee: '최민준',
assigneeInitial: '최',
assigneeColor: '#2B7FFF',
dueDate: '7/8',
status: 'in-progress',
},
{
id: 'task-6',
workspaceId: 'team-workspace',
title: 'API 명세서 문서화',
assignee: '이하은',
assigneeInitial: '이',
assigneeColor: '#615FFF',
dueDate: '7/2',
status: 'done',
},
{
id: 'task-7',
workspaceId: 'team-workspace',
title: '로고 시안 3종 작성',
assignee: '박서준',
assigneeInitial: '박',
assigneeColor: '#FE9A00',
dueDate: '6/30',
status: 'done',
},
{
id: 'task-8',
workspaceId: 'team-workspace',
title: '중간 발표 리허설 진행',
assignee: '최민준',
assigneeInitial: '최',
assigneeColor: '#2B7FFF',
dueDate: '7/12',
status: 'in-progress',
},
{
id: 'task-9',
workspaceId: 'team-workspace',
title: '최종 보고서 목차 정리',
assignee: '김지은',
assigneeInitial: '김',
assigneeColor: '#FE9A00',
dueDate: '7/14',
status: 'todo',
},
{
id: 'task-10',
workspaceId: 'team-workspace',
title: '인터뷰 대상자 일정 조율',
assignee: '이하은',
assigneeInitial: '이',
assigneeColor: '#615FFF',
dueDate: '7/16',
status: 'todo',
},
{
id: 'task-11',
workspaceId: 'team-workspace',
title: '프로토타입 수정 사항 반영',
assignee: '최민준',
assigneeInitial: '최',
assigneeColor: '#2B7FFF',
dueDate: '7/4',
status: 'done',
},
{
id: 'task-12',
workspaceId: 'team-workspace',
title: '경쟁 서비스 비교표 작성',
assignee: '박서준',
assigneeInitial: '박',
assigneeColor: '#00C950',
dueDate: '7/1',
status: 'done',
},
{
id: 'task-13',
workspaceId: 'team-workspace',
title: '발표 스크립트 1차 작성',
assignee: '김지은',
assigneeInitial: '김',
assigneeColor: '#FE9A00',
dueDate: '7/2',
status: 'done',
},
{
id: 'task-14',
workspaceId: 'team-workspace',
title: '사용성 테스트 결과 정리',
assignee: '이하은',
assigneeInitial: '이',
assigneeColor: '#615FFF',
dueDate: '7/3',
status: 'done',
},
{
id: 'task-15',
workspaceId: 'team-workspace',
title: '발표 자료 시각 보정',
assignee: '정우진',
assigneeInitial: '정',
assigneeColor: '#8B5CF6',
dueDate: '7/5',
status: 'done',
},
],
'side-workspace': [
{
id: 'side-task-1',
workspaceId: 'side-workspace',
title: '온보딩 플로우 개선',
assignee: '최민준',
assigneeInitial: '최',
assigneeColor: '#2B7FFF',
dueDate: '7/14',
status: 'todo',
},
{
id: 'side-task-2',
workspaceId: 'side-workspace',
title: '베타 테스터 모집 공고',
assignee: '김지은',
assigneeInitial: '김',
assigneeColor: '#FE9A00',
dueDate: '7/15',
status: 'todo',
},
{
id: 'side-task-3',
workspaceId: 'side-workspace',
title: '운동 통계 차트',
assignee: '박서준',
assigneeInitial: '박',
assigneeColor: '#00B8DB',
dueDate: '7/12',
status: 'in-progress',
},
{
id: 'side-task-4',
workspaceId: 'side-workspace',
title: '푸시 알림 설정',
assignee: '김지은',
assigneeInitial: '김',
assigneeColor: '#FE9A00',
dueDate: '7/11',
status: 'in-progress',
},
{
id: 'side-task-5',
workspaceId: 'side-workspace',
title: '소셜 로그인 연동',
assignee: '이하은',
assigneeInitial: '이',
assigneeColor: '#615FFF',
dueDate: '7/9',
status: 'done',
},
{
id: 'side-task-6',
workspaceId: 'side-workspace',
title: '운동 기록 CRUD API',
assignee: '이하은',
assigneeInitial: '이',
assigneeColor: '#615FFF',
dueDate: '7/8',
status: 'done',
},
],
};

export function getMockTasksByWorkspaceId(workspaceId: string): Task[] {
return mockTasksByWorkspaceId[workspaceId]?.map((task) => ({ ...task })) ?? [];
const taskWorkspaceAliasById: Record<string, keyof typeof mockTasksByWorkspaceId> = {
[TEAM_PROJECT_WORKSPACE_UUID]: 'team-workspace',
[SIDE_PROJECT_WORKSPACE_UUID]: 'side-workspace',
[STORE_OPERATION_WORKSPACE_UUID]: 'store-workspace',
};

function resolveTaskWorkspaceKey(
workspaceId: string,
fallbackWorkspaceId?: keyof typeof mockTasksByWorkspaceId,
) {
if (workspaceId in mockTasksByWorkspaceId) {
return workspaceId as keyof typeof mockTasksByWorkspaceId;
}

return taskWorkspaceAliasById[workspaceId] ?? fallbackWorkspaceId;
}

export function getMockTasksByWorkspaceId(
workspaceId: string,
fallbackWorkspaceId?: keyof typeof mockTasksByWorkspaceId,
): Task[] {
const resolvedWorkspaceId = resolveTaskWorkspaceKey(workspaceId, fallbackWorkspaceId);

if (!resolvedWorkspaceId) {
return [];
}

return mockTasksByWorkspaceId[resolvedWorkspaceId]?.map((task) => ({ ...task })) ?? [];
}
Loading