feat: showProgress toggle and auto display flags on task type move

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
EENE Dashboard
2026-06-02 16:53:57 +09:00
parent 395680ea20
commit 849897b141
9 changed files with 62 additions and 16 deletions

View File

@@ -1,7 +1,7 @@
import { useState } from 'react';
import { createPortal } from 'react-dom';
import type { Task } from '../../types';
import { normalizeTaskType } from '../../lib/taskType';
import { normalizeTaskType, displayFlagsForTaskType } from '../../lib/taskType';
const STATUS_OPTIONS = [
{ value: 'TODO', label: '대기' },
@@ -26,6 +26,7 @@ export interface TaskFormData {
showDescription: boolean;
showStatus: boolean;
showIssue: boolean;
showProgress: boolean;
keywords: string;
}
@@ -62,6 +63,7 @@ export function TaskModal({ mode, task, defaultSection = 'HR', defaultQuarter =
showDescription: task?.showDescription ?? true,
showStatus: task?.showStatus ?? true,
showIssue: task?.showIssue ?? true,
showProgress: task?.showProgress ?? true,
keywords: task?.keywords ?? '',
});
@@ -130,7 +132,14 @@ export function TaskModal({ mode, task, defaultSection = 'HR', defaultQuarter =
<label className="block text-sm font-bold text-gray-500 mb-1.5"> </label>
<select
value={form.taskType}
onChange={(e) => set('taskType', e.target.value)}
onChange={(e) => {
const newType = e.target.value;
setForm((prev) => ({
...prev,
taskType: newType,
...displayFlagsForTaskType(newType),
}));
}}
className="w-full border border-gray-200 rounded-xl px-4 py-2.5 outline-none focus:border-blue-400 focus:ring-2 focus:ring-blue-100 transition bg-white"
>
<option value="기반업무"></option>
@@ -165,10 +174,21 @@ export function TaskModal({ mode, task, defaultSection = 'HR', defaultQuarter =
</select>
</div>
<div>
<label className="block text-sm font-bold text-gray-500 mb-1.5">
<span className="ml-2 font-black text-gray-800">{form.progress}%</span>
</label>
<div className="flex items-center justify-between mb-1.5">
<label className="text-sm font-bold text-gray-500">
<span className="ml-2 font-black text-gray-800">{form.progress}%</span>
</label>
<label className="flex items-center gap-1.5 cursor-pointer select-none">
<input
type="checkbox"
checked={form.showProgress}
onChange={(e) => set('showProgress', e.target.checked)}
className="w-4 h-4 accent-blue-500 cursor-pointer"
/>
<span className="text-xs font-semibold text-gray-400"> </span>
</label>
</div>
<div className="flex items-center gap-3">
<input
type="range"

View File

@@ -196,6 +196,7 @@ export function DepartmentColumn({ title: initialTitle, titleEn, subtitle: initi
showDescription: data.showDescription,
showStatus: data.showStatus,
showIssue: data.showIssue,
showProgress: data.showProgress,
keywords: data.keywords || null,
quarter: data.quarter,
priority: 'MEDIUM',
@@ -215,6 +216,7 @@ export function DepartmentColumn({ title: initialTitle, titleEn, subtitle: initi
startDate: data.startDate || null, dueDate: data.dueDate || null,
showDate: data.showDate, showDescription: data.showDescription,
showStatus: data.showStatus, showIssue: data.showIssue,
showProgress: data.showProgress,
keywords: data.keywords || null,
},
});

View File

@@ -88,12 +88,14 @@ export function TaskCard({
<span className="min-w-0 flex-1 truncate text-2xl font-black leading-snug text-slate-900">
{task.title}
</span>
<span className={`mt-0.5 min-w-[4rem] shrink-0 text-right text-2xl font-black ${
task.progress >= 70 ? 'text-emerald-500' :
task.progress >= 40 ? 'text-blue-400' : 'text-orange-400'
}`}>
{task.progress}%
</span>
{task.showProgress !== false && (
<span className={`mt-0.5 min-w-[4rem] shrink-0 text-right text-2xl font-black ${
task.progress >= 70 ? 'text-emerald-500' :
task.progress >= 40 ? 'text-blue-400' : 'text-orange-400'
}`}>
{task.progress}%
</span>
)}
</div>
<div className="mt-1 flex items-center gap-2">

View File

@@ -71,6 +71,7 @@ export function TaskManager({ tasks, sectionOptions, quarter, onClose }: TaskMan
showDescription: data.showDescription,
showStatus: data.showStatus,
showIssue: data.showIssue,
showProgress: data.showProgress,
keywords: data.keywords || null,
quarter: data.quarter,
priority: 'MEDIUM',
@@ -92,6 +93,7 @@ export function TaskManager({ tasks, sectionOptions, quarter, onClose }: TaskMan
showDescription: data.showDescription,
showStatus: data.showStatus,
showIssue: data.showIssue,
showProgress: data.showProgress,
keywords: data.keywords || null,
},
});

View File

@@ -14,3 +14,15 @@ export function normalizeTaskType(taskType: string | null | undefined): string {
if (taskType === '상시업무') return '기반업무';
return taskType;
}
/** 실행과제 카드에 표시할 필드 플래그 (기반업무는 모두 숨김) */
export function displayFlagsForTaskType(taskType: string | null | undefined) {
const visible = isProjectTask(taskType);
return {
showDate: visible,
showDescription: visible,
showStatus: visible,
showIssue: visible,
showProgress: visible,
};
}

View File

@@ -18,7 +18,7 @@ import { DepartmentColumn } from '../components/dashboard/DepartmentColumn';
import { TaskManager } from '../components/dashboard/TaskManager';
import { useSocket } from '../contexts/SocketContext';
import { sendTaskSelected, openDetailWindow } from '../lib/dualMonitor';
import { isRoutineTask } from '../lib/taskType';
import { isRoutineTask, displayFlagsForTaskType } from '../lib/taskType';
const QUARTER = '2026-Q2';
const SECTIONS = ['인사관리', '학습성장', '운영지원', '전산관리'] as const;
@@ -134,8 +134,10 @@ export default function DashboardPage() {
if (targetSection !== srcSection) updateData.section = targetSection;
if (areaType === 'routine' && !isRoutineTask(draggedTask.taskType)) {
updateData.taskType = '기반업무';
Object.assign(updateData, displayFlagsForTaskType('기반업무'));
} else if (areaType === 'project' && isRoutineTask(draggedTask.taskType)) {
updateData.taskType = '실행과제';
Object.assign(updateData, displayFlagsForTaskType('실행과제'));
}
if (Object.keys(updateData).length > 0) {
patchTask.mutate({ id: activeId, data: updateData });
@@ -154,7 +156,9 @@ export default function DashboardPage() {
const updateData: Record<string, any> = {};
if (dstSection !== srcSection) updateData.section = dstSection;
if (typeChanged) {
updateData.taskType = isRoutineTask(overTask.taskType) ? '기반업무' : '실행과제';
const newType = isRoutineTask(overTask.taskType) ? '기반업무' : '실행과제';
updateData.taskType = newType;
Object.assign(updateData, displayFlagsForTaskType(newType));
}
patchTask.mutate({ id: activeId, data: updateData });
if (dstSection !== srcSection) return;

View File

@@ -31,6 +31,7 @@ export interface Task {
showDescription: boolean;
showStatus: boolean;
showIssue: boolean;
showProgress: boolean;
keywords: string | null;
creatorId: string;
assigneeId: string | null;