feat(ui): kanban board improvements, comment event subscription

This commit is contained in:
viettranx committed 2026-03-20 21:46:44 +07:00
1 parent 6fe59faa0c
commit 107140b32c
4 files changed
+35 -16

No files matched your search

@@ -272,6 +272,7 @@ export const BoardContainer = memo(function BoardContainer({
isTeamV2={isTeamV2}
groupBy={groupBy}
emojiLookup={emojiLookup}
memberLookup={memberLookup}
taskLookup={taskLookup}
onTaskClick={handleTaskClick}
onDeleteTask={deleteTask ? handleDeleteTask : undefined}
@@ -9,12 +9,13 @@ interface KanbanBoardProps {
isTeamV2?: boolean;
groupBy: GroupBy;
emojiLookup?: Map<string, string>;
memberLookup?: Map<string, string>;
taskLookup?: Map<string, string>;
onTaskClick: (task: TeamTaskData) => void;
onDeleteTask?: (taskId: string) => void;
}
export const KanbanBoard = memo(function KanbanBoard({ tasks, isTeamV2, groupBy, emojiLookup, taskLookup, onTaskClick, onDeleteTask }: KanbanBoardProps) {
export const KanbanBoard = memo(function KanbanBoard({ tasks, isTeamV2, groupBy, emojiLookup, memberLookup, taskLookup, onTaskClick, onDeleteTask }: KanbanBoardProps) {
const grouped = useMemo(() => groupTasksBy(tasks, groupBy), [tasks, groupBy]);
const columns = useMemo(() => {
@@ -40,6 +41,7 @@ export const KanbanBoard = memo(function KanbanBoard({ tasks, isTeamV2, groupBy,
tasks={grouped.get(col) ?? []}
isTeamV2={isTeamV2}
emojiLookup={emojiLookup}
memberLookup={memberLookup}
taskLookup={taskLookup}
onTaskClick={onTaskClick}
onDeleteTask={onDeleteTask}
+28 -14
View File
@@ -1,6 +1,6 @@
import { memo } from "react";
import { motion } from "framer-motion";
import { Trash2, Ban } from "lucide-react";
import { Trash2, Ban, MessageSquare, Paperclip } from "lucide-react";
import { Badge } from "@/components/ui/badge";
import { useTranslation } from "react-i18next";
import { isTaskLocked } from "./board-utils";
@@ -25,16 +25,18 @@ interface KanbanCardProps {
task: TeamTaskData;
isTeamV2?: boolean;
emojiLookup?: Map<string, string>;
memberLookup?: Map<string, string>;
taskLookup?: Map<string, string>;
onClick: () => void;
onDelete?: (taskId: string) => void;
}
export const KanbanCard = memo(function KanbanCard({ task, isTeamV2, emojiLookup, taskLookup, onClick, onDelete }: KanbanCardProps) {
export const KanbanCard = memo(function KanbanCard({ task, isTeamV2, emojiLookup, memberLookup, taskLookup, onClick, onDelete }: KanbanCardProps) {
const { t } = useTranslation("teams");
const locked = isTaskLocked(task);
const blocked = task.status === "blocked";
const ownerEmoji = task.owner_agent_id && emojiLookup?.get(task.owner_agent_id);
const ownerName = (task.owner_agent_id && memberLookup?.get(task.owner_agent_id)) || task.owner_agent_key;
const canDelete = onDelete && isTerminalStatus(task.status);
const prio = PRIORITY_LABELS[task.priority] ?? PRIORITY_LABELS[0]!;
const hasBlockers = task.blocked_by && task.blocked_by.length > 0;
@@ -51,11 +53,19 @@ export const KanbanCard = memo(function KanbanCard({ task, isTeamV2, emojiLookup
}
onClick={onClick}
>
{/* Top row: identifier + running badge + delete button */}
{/* Top row: identifier + priority + running badge + delete button */}
<div className="mb-1 flex items-center justify-between">
<span className="font-mono text-[10px] text-muted-foreground">
{task.identifier || `#${task.task_number ?? ""}`}
</span>
<div className="flex items-center gap-1.5">
<span className="font-mono text-[10px] text-muted-foreground">
{task.identifier || `#${task.task_number ?? ""}`}
</span>
<span
className={`font-mono text-[10px] font-medium ${prio.color}`}
title={`${PRIORITY_TOOLTIPS[task.priority] ?? "Low"} priority`}
>
{prio.label}
</span>
</div>
<div className="flex items-center gap-1.5">
{locked && (
<span className="flex items-center gap-1 text-[10px] text-green-600 dark:text-green-400">
@@ -87,21 +97,25 @@ export const KanbanCard = memo(function KanbanCard({ task, isTeamV2, emojiLookup
</p>
)}
{/* Bottom row: owner + type badge + priority */}
{/* Bottom row: owner + type badge + counts */}
<div className="mt-2 flex items-center gap-1.5">
{ownerEmoji && <span className="text-sm leading-none">{ownerEmoji}</span>}
<span className="truncate text-xs text-muted-foreground">
{task.owner_agent_key || t("board.unassigned")}
{ownerName || t("board.unassigned")}
</span>
{task.task_type && task.task_type !== "general" && (
<Badge variant="outline" className="text-[10px] px-1 py-0">{task.task_type}</Badge>
)}
<span
className={`ml-auto shrink-0 font-mono text-[10px] font-medium ${prio.color}`}
title={`${PRIORITY_TOOLTIPS[task.priority] ?? "Low"} priority`}
>
{prio.label}
</span>
{(task.comment_count ?? 0) > 0 && (
<span className="flex items-center gap-0.5 text-[10px] text-muted-foreground ml-auto">
<MessageSquare className="h-3 w-3" /> {task.comment_count}
</span>
)}
{(task.attachment_count ?? 0) > 0 && (
<span className={`flex items-center gap-0.5 text-[10px] text-muted-foreground ${(task.comment_count ?? 0) === 0 ? "ml-auto" : ""}`}>
<Paperclip className="h-3 w-3" /> {task.attachment_count}
</span>
)}
</div>
{isTeamV2 && task.progress_percent != null && task.progress_percent > 0 && !isTerminalStatus(task.status) && (
@@ -12,12 +12,13 @@ interface KanbanColumnProps {
tasks: TeamTaskData[];
isTeamV2?: boolean;
emojiLookup?: Map<string, string>;
memberLookup?: Map<string, string>;
taskLookup?: Map<string, string>;
onTaskClick: (task: TeamTaskData) => void;
onDeleteTask?: (taskId: string) => void;
}
export const KanbanColumn = memo(function KanbanColumn({ columnId, title, tasks, isTeamV2, emojiLookup, taskLookup, onTaskClick, onDeleteTask }: KanbanColumnProps) {
export const KanbanColumn = memo(function KanbanColumn({ columnId, title, tasks, isTeamV2, emojiLookup, memberLookup, taskLookup, onTaskClick, onDeleteTask }: KanbanColumnProps) {
const { t } = useTranslation("teams");
return (
@@ -40,6 +41,7 @@ export const KanbanColumn = memo(function KanbanColumn({ columnId, title, tasks,
task={task}
isTeamV2={isTeamV2}
emojiLookup={emojiLookup}
memberLookup={memberLookup}
taskLookup={taskLookup}
onClick={() => onTaskClick(task)}
onDelete={onDeleteTask}