You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
91 lines
2.4 KiB
TypeScript
91 lines
2.4 KiB
TypeScript
// frontend/components/tasks/KanbanCard.tsx
|
|
"use client";
|
|
import { Icon } from "@/components/Icon";
|
|
import { cx } from "@/lib/cx";
|
|
import { dueLabel, isSoon, pct, stat } from "@/lib/tasks/tree";
|
|
import type { Person, Project, Task } from "@/lib/types";
|
|
import { Av, Tag } from "./bits";
|
|
|
|
export function KanbanCard({
|
|
task,
|
|
people,
|
|
projects,
|
|
onOpen,
|
|
onDragStart,
|
|
onDragEnd,
|
|
onDragOver,
|
|
dragging,
|
|
dropPos,
|
|
}: {
|
|
task: Task;
|
|
people: Record<string, Person>;
|
|
projects: Record<string, Project>;
|
|
onOpen: (id: string) => void;
|
|
onDragStart?: (e: React.DragEvent) => void;
|
|
onDragEnd?: () => void;
|
|
onDragOver?: (e: React.DragEvent) => void;
|
|
dragging?: boolean;
|
|
dropPos?: "before" | "after" | null;
|
|
}) {
|
|
const s = stat(task);
|
|
const soon = isSoon(task);
|
|
const proj = projects[task.project_id];
|
|
const person = task.assignee_id ? people[task.assignee_id] : null;
|
|
return (
|
|
<div
|
|
className={cx(
|
|
"kcard",
|
|
dragging && "dragging",
|
|
task.status === "done" && "done-card",
|
|
dropPos === "before" && "drop-before",
|
|
dropPos === "after" && "drop-after",
|
|
)}
|
|
draggable
|
|
onDragStart={onDragStart}
|
|
onDragEnd={onDragEnd}
|
|
onDragOver={onDragOver}
|
|
onClick={() => onOpen(task.id)}
|
|
>
|
|
<div className="kcard-top">
|
|
<Tag project={proj} />
|
|
<button
|
|
className="kmore"
|
|
onClick={(e) => {
|
|
e.stopPropagation();
|
|
onOpen(task.id);
|
|
}}
|
|
aria-label="더보기"
|
|
>
|
|
<Icon name="more" />
|
|
</button>
|
|
</div>
|
|
<div className="kcard-title">{task.title}</div>
|
|
{s.total > 0 && (
|
|
<div className="ksub">
|
|
<div className="bar">
|
|
<span style={{ width: pct(task) + "%" }} />
|
|
</div>
|
|
<span className="ksub-num">
|
|
<Icon name="branch" /> {s.done}/{s.total}
|
|
</span>
|
|
</div>
|
|
)}
|
|
<div className="kfoot">
|
|
<span className={cx("due", soon && "soon")}>
|
|
<Icon name="cal" /> {dueLabel(task.due)}
|
|
</span>
|
|
<span className={cx("prio-flag", `prio-${task.prio}`)}>
|
|
<Icon name="flag" /> {task.prio}
|
|
</span>
|
|
{task.delegated && (
|
|
<span className="deleg-chip">
|
|
<Icon name="users" />
|
|
위임
|
|
</span>
|
|
)}
|
|
<Av person={person} />
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|