feat: showProgress toggle and auto display flags on task type move
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -56,6 +56,7 @@ model Task {
|
||||
showDescription Boolean @default(true)
|
||||
showStatus Boolean @default(true)
|
||||
showIssue Boolean @default(true)
|
||||
showProgress Boolean @default(true)
|
||||
keywords String?
|
||||
creatorId String
|
||||
assigneeId String?
|
||||
|
||||
@@ -57,7 +57,7 @@ router.post('/', async (req, res, next) => {
|
||||
try {
|
||||
const { title, description, status, priority, quarter, category,
|
||||
section, tag, taskType, progress, issueNote, startDate, dueDate, assigneeId, showDate,
|
||||
showDescription, showStatus, showIssue, keywords } =
|
||||
showDescription, showStatus, showIssue, showProgress, keywords } =
|
||||
req.body as Record<string, any>;
|
||||
|
||||
if (!title || !quarter) {
|
||||
@@ -83,6 +83,7 @@ router.post('/', async (req, res, next) => {
|
||||
showDescription: showDescription !== undefined ? showDescription === 'true' || showDescription === true : true,
|
||||
showStatus: showStatus !== undefined ? showStatus === 'true' || showStatus === true : true,
|
||||
showIssue: showIssue !== undefined ? showIssue === 'true' || showIssue === true : true,
|
||||
showProgress: showProgress !== undefined ? showProgress === 'true' || showProgress === true : true,
|
||||
keywords: keywords || null,
|
||||
assigneeId: assigneeId || null,
|
||||
creatorId: (req.body as Record<string, string>).creatorId ?? 'system',
|
||||
@@ -103,7 +104,7 @@ router.patch('/:id', async (req, res, next) => {
|
||||
|
||||
const { title, description, status, priority, quarter, category,
|
||||
section, tag, taskType, progress, issueNote, startDate, dueDate, assigneeId, showDate,
|
||||
showDescription, showStatus, showIssue, keywords } =
|
||||
showDescription, showStatus, showIssue, showProgress, keywords } =
|
||||
req.body as Record<string, any>;
|
||||
|
||||
const task = await prisma.task.update({
|
||||
@@ -127,6 +128,7 @@ router.patch('/:id', async (req, res, next) => {
|
||||
...(showDescription !== undefined && { showDescription: showDescription === true || showDescription === 'true' }),
|
||||
...(showStatus !== undefined && { showStatus: showStatus === true || showStatus === 'true' }),
|
||||
...(showIssue !== undefined && { showIssue: showIssue === true || showIssue === 'true' }),
|
||||
...(showProgress !== undefined && { showProgress: showProgress === true || showProgress === 'true' }),
|
||||
...(keywords !== undefined && { keywords: keywords || null }),
|
||||
},
|
||||
});
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -31,6 +31,7 @@ export interface Task {
|
||||
showDescription: boolean;
|
||||
showStatus: boolean;
|
||||
showIssue: boolean;
|
||||
showProgress: boolean;
|
||||
keywords: string | null;
|
||||
creatorId: string;
|
||||
assigneeId: string | null;
|
||||
|
||||
Reference in New Issue
Block a user