feat: center header, remove tag, show description, routine tasks at bottom
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -215,7 +215,7 @@ export function DepartmentColumn({ title: initialTitle, titleEn, subtitle: initi
|
|||||||
{/* 컬럼 헤더 (noHeader 시 숨김) */}
|
{/* 컬럼 헤더 (noHeader 시 숨김) */}
|
||||||
{!noHeader && (
|
{!noHeader && (
|
||||||
<div
|
<div
|
||||||
className="h-10 shrink-0 select-none flex items-center px-4 gap-2"
|
className="h-10 shrink-0 select-none flex items-center justify-center px-4 gap-2 relative"
|
||||||
style={headerStyle}
|
style={headerStyle}
|
||||||
onContextMenu={(e) => { e.preventDefault(); setCtxMenu({ x: e.clientX, y: e.clientY, type: 'header' }); }}
|
onContextMenu={(e) => { e.preventDefault(); setCtxMenu({ x: e.clientX, y: e.clientY, type: 'header' }); }}
|
||||||
>
|
>
|
||||||
@@ -223,7 +223,7 @@ export function DepartmentColumn({ title: initialTitle, titleEn, subtitle: initi
|
|||||||
{titleEnState && (
|
{titleEnState && (
|
||||||
<span className="text-white/60 text-xs font-medium truncate hidden xl:block">{titleEnState}</span>
|
<span className="text-white/60 text-xs font-medium truncate hidden xl:block">{titleEnState}</span>
|
||||||
)}
|
)}
|
||||||
<span className="ml-auto shrink-0 text-xs font-black bg-white/20 text-white px-2 py-0.5 rounded-full">
|
<span className="absolute right-3 shrink-0 text-xs font-black bg-white/20 text-white px-2 py-0.5 rounded-full">
|
||||||
{tasks.length}건
|
{tasks.length}건
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -238,13 +238,31 @@ export function DepartmentColumn({ title: initialTitle, titleEn, subtitle: initi
|
|||||||
<div className="flex items-center justify-center h-40 text-2xl text-gray-300">
|
<div className="flex items-center justify-center h-40 text-2xl text-gray-300">
|
||||||
해당 업무 없음
|
해당 업무 없음
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (() => {
|
||||||
<DndContext sensors={sensors} collisionDetection={closestCenter} onDragEnd={handleDragEnd}>
|
const projectTasks = orderedTasks.filter((t) => t.taskType !== '상시업무');
|
||||||
<SortableContext items={orderedTasks.map((t) => t.id)} strategy={verticalListSortingStrategy}>
|
const routineTasks = orderedTasks.filter((t) => t.taskType === '상시업무');
|
||||||
{orderedTasks.map((task) => <SortableTaskCard key={task.id} task={task} sectionOptions={sectionOptions} onSelect={onSelectTask} />)}
|
return (
|
||||||
</SortableContext>
|
<DndContext sensors={sensors} collisionDetection={closestCenter} onDragEnd={handleDragEnd}>
|
||||||
</DndContext>
|
<SortableContext items={orderedTasks.map((t) => t.id)} strategy={verticalListSortingStrategy}>
|
||||||
)}
|
{projectTasks.map((task) => (
|
||||||
|
<SortableTaskCard key={task.id} task={task} sectionOptions={sectionOptions} onSelect={onSelectTask} />
|
||||||
|
))}
|
||||||
|
{routineTasks.length > 0 && (
|
||||||
|
<>
|
||||||
|
<div className="flex items-center gap-2 my-2">
|
||||||
|
<div className="flex-1 border-t border-dashed border-gray-300" />
|
||||||
|
<span className="text-xs text-gray-400 font-semibold shrink-0">상시업무</span>
|
||||||
|
<div className="flex-1 border-t border-dashed border-gray-300" />
|
||||||
|
</div>
|
||||||
|
{routineTasks.map((task) => (
|
||||||
|
<SortableTaskCard key={task.id} task={task} sectionOptions={sectionOptions} onSelect={onSelectTask} />
|
||||||
|
))}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</SortableContext>
|
||||||
|
</DndContext>
|
||||||
|
);
|
||||||
|
})()}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import { useSortable } from '@dnd-kit/sortable';
|
|||||||
import { CSS } from '@dnd-kit/utilities';
|
import { CSS } from '@dnd-kit/utilities';
|
||||||
import type { DraggableAttributes } from '@dnd-kit/core';
|
import type { DraggableAttributes } from '@dnd-kit/core';
|
||||||
import type { SyntheticListenerMap } from '@dnd-kit/core/dist/hooks/utilities';
|
import type { SyntheticListenerMap } from '@dnd-kit/core/dist/hooks/utilities';
|
||||||
import { sendTaskSelected } from '../../lib/dualMonitor';
|
|
||||||
import { apiClient } from '../../lib/apiClient';
|
import { apiClient } from '../../lib/apiClient';
|
||||||
import { ContextMenu } from '../common/ContextMenu';
|
import { ContextMenu } from '../common/ContextMenu';
|
||||||
import { TaskModal } from '../common/TaskModal';
|
import { TaskModal } from '../common/TaskModal';
|
||||||
@@ -98,7 +97,6 @@ export function TaskCard({
|
|||||||
onCardClick?: () => void;
|
onCardClick?: () => void;
|
||||||
}) {
|
}) {
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
const tagCfg = TAG_CONFIG[task.tag ?? ''] ?? { bg: 'bg-gray-100', text: 'text-gray-600' };
|
|
||||||
|
|
||||||
// ── API mutations ──────────────────────────────────────────
|
// ── API mutations ──────────────────────────────────────────
|
||||||
const create = useMutation({
|
const create = useMutation({
|
||||||
@@ -174,7 +172,7 @@ export function TaskCard({
|
|||||||
style={dragStyle}
|
style={dragStyle}
|
||||||
{...dragAttributes}
|
{...dragAttributes}
|
||||||
{...dragListeners}
|
{...dragListeners}
|
||||||
className="bg-white rounded-2xl px-5 py-3 mb-3 shadow-sm border border-gray-100 hover:shadow-md hover:border-gray-200 transition-all select-none h-[112px] cursor-grab active:cursor-grabbing overflow-hidden"
|
className="bg-white rounded-2xl px-5 py-3 mb-3 shadow-sm border border-gray-100 hover:shadow-md hover:border-gray-200 transition-all select-none cursor-grab active:cursor-grabbing overflow-hidden"
|
||||||
onPointerDown={(e) => {
|
onPointerDown={(e) => {
|
||||||
// 우클릭(button !== 0)을 dnd-kit에 전달하지 않음
|
// 우클릭(button !== 0)을 dnd-kit에 전달하지 않음
|
||||||
// dnd-kit은 pointerdown→pointerup 시 synthetic click을 발화하는데
|
// dnd-kit은 pointerdown→pointerup 시 synthetic click을 발화하는데
|
||||||
@@ -202,15 +200,16 @@ export function TaskCard({
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
{/* ── 태그 + 기간 + 상태 ── */}
|
{/* ── description ── */}
|
||||||
|
{task.description && (
|
||||||
|
<div className="mt-1 text-sm text-gray-500 font-medium truncate">
|
||||||
|
{task.description}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* ── 기간 + 상태 ── */}
|
||||||
<div className="mt-1.5 flex items-center gap-2">
|
<div className="mt-1.5 flex items-center gap-2">
|
||||||
<span
|
<span className="text-sm text-gray-400 font-medium flex-1 truncate">
|
||||||
className={`shrink-0 text-sm font-black px-2.5 py-0.5 rounded-full ${tagCfg.bg} ${tagCfg.text} cursor-pointer`}
|
|
||||||
onClick={() => sendTaskSelected(task.id)}
|
|
||||||
>
|
|
||||||
{task.tag}
|
|
||||||
</span>
|
|
||||||
<span className="text-base text-gray-400 font-medium flex-1 truncate">
|
|
||||||
{(task.startDate || task.dueDate)
|
{(task.startDate || task.dueDate)
|
||||||
? `${task.startDate ? fmtDate(task.startDate) : '?'} ~ ${task.dueDate ? fmtDate(task.dueDate) : '?'}`
|
? `${task.startDate ? fmtDate(task.startDate) : '?'} ~ ${task.dueDate ? fmtDate(task.dueDate) : '?'}`
|
||||||
: ''}
|
: ''}
|
||||||
@@ -222,7 +221,7 @@ export function TaskCard({
|
|||||||
|
|
||||||
{/* ── 이슈 메모 ── */}
|
{/* ── 이슈 메모 ── */}
|
||||||
{task.issueNote && (
|
{task.issueNote && (
|
||||||
<div className="mt-2 flex gap-2 text-sm font-semibold text-red-600 min-w-0">
|
<div className="mt-1.5 flex gap-2 text-sm font-semibold text-red-600 min-w-0">
|
||||||
<span className="shrink-0">▶ 이슈 :</span>
|
<span className="shrink-0">▶ 이슈 :</span>
|
||||||
<span className="truncate">{task.issueNote}</span>
|
<span className="truncate">{task.issueNote}</span>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user