refactor: clean up GlobalTaskList and enhance TaskDetailDialog
- Removed unnecessary border styling in `GlobalTaskList` for a cleaner layout. - Added logic in `ActivityTimeline` to identify and set team lead information based on member roles. - Improved `TaskDetailDialog` to display owner information with enhanced styling and avatar support, utilizing team colors for better visual distinction. - Streamlined dependency display in `TaskDetailDialog` for improved readability and user interaction.
This commit is contained in:
parent
1b6f7be767
commit
361828f21a
3 changed files with 164 additions and 128 deletions
|
|
@ -236,10 +236,7 @@ export const GlobalTaskList = ({
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Grouping mode */}
|
{/* Grouping mode */}
|
||||||
<div
|
<div className="flex shrink-0 items-center gap-2 px-2 py-1">
|
||||||
className="flex shrink-0 items-center gap-2 border-b px-2 py-1"
|
|
||||||
style={{ borderColor: 'var(--color-border)' }}
|
|
||||||
>
|
|
||||||
<span className="shrink-0 text-[11px] text-text-muted">Group by:</span>
|
<span className="shrink-0 text-[11px] text-text-muted">Group by:</span>
|
||||||
<Select value={groupingMode} onValueChange={(v) => setGroupingMode(v as TaskGroupingMode)}>
|
<Select value={groupingMode} onValueChange={(v) => setGroupingMode(v as TaskGroupingMode)}>
|
||||||
<SelectTrigger className="h-7 min-w-0 flex-1 border-[var(--color-border)] px-2 text-[11px]">
|
<SelectTrigger className="h-7 min-w-0 flex-1 border-[var(--color-border)] px-2 text-[11px]">
|
||||||
|
|
|
||||||
|
|
@ -111,6 +111,18 @@ export const ActivityTimeline = ({
|
||||||
memberInfo.set(m.agentType, info);
|
memberInfo.set(m.agentType, info);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
const leadMember = members.find(
|
||||||
|
(m) => m.agentType === 'team-lead' || m.role?.toLowerCase().includes('lead')
|
||||||
|
);
|
||||||
|
if (leadMember) {
|
||||||
|
const leadInfo = memberInfo.get(leadMember.name);
|
||||||
|
if (leadInfo) {
|
||||||
|
const teamLeadColor = leadInfo.color ?? getMemberColorByName('team-lead');
|
||||||
|
const resolvedLeadInfo = { role: leadInfo.role, color: teamLeadColor };
|
||||||
|
memberInfo.set('team-lead', resolvedLeadInfo);
|
||||||
|
memberInfo.set(leadMember.name, resolvedLeadInfo);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleMemberNameClick = (name: string): void => {
|
const handleMemberNameClick = (name: string): void => {
|
||||||
|
|
|
||||||
|
|
@ -13,8 +13,10 @@ import {
|
||||||
DialogHeader,
|
DialogHeader,
|
||||||
DialogTitle,
|
DialogTitle,
|
||||||
} from '@renderer/components/ui/dialog';
|
} from '@renderer/components/ui/dialog';
|
||||||
|
import { getTeamColorSet } from '@renderer/constants/teamColors';
|
||||||
import { markAsRead } from '@renderer/services/commentReadStorage';
|
import { markAsRead } from '@renderer/services/commentReadStorage';
|
||||||
import {
|
import {
|
||||||
|
agentAvatarUrl,
|
||||||
KANBAN_COLUMN_DISPLAY,
|
KANBAN_COLUMN_DISPLAY,
|
||||||
TASK_STATUS_LABELS,
|
TASK_STATUS_LABELS,
|
||||||
TASK_STATUS_STYLES,
|
TASK_STATUS_STYLES,
|
||||||
|
|
@ -98,6 +100,7 @@ export const TaskDetailDialog = ({
|
||||||
t.id !== currentTask.id && Array.isArray(t.related) && t.related.includes(currentTask.id)
|
t.id !== currentTask.id && Array.isArray(t.related) && t.related.includes(currentTask.id)
|
||||||
)
|
)
|
||||||
.map((t) => t.id);
|
.map((t) => t.id);
|
||||||
|
const ownerMember = currentTask.owner ? members.find((m) => m.name === currentTask.owner) : null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog open={open} onOpenChange={(v) => !v && onClose()}>
|
<Dialog open={open} onOpenChange={(v) => !v && onClose()}>
|
||||||
|
|
@ -121,11 +124,33 @@ export const TaskDetailDialog = ({
|
||||||
|
|
||||||
{/* Metadata */}
|
{/* Metadata */}
|
||||||
<div className="grid grid-cols-2 gap-x-4 gap-y-2 text-xs sm:grid-cols-3">
|
<div className="grid grid-cols-2 gap-x-4 gap-y-2 text-xs sm:grid-cols-3">
|
||||||
<div className="flex items-center gap-1.5 text-[var(--color-text-muted)]">
|
<div className="flex min-w-0 items-center gap-2">
|
||||||
<User size={12} />
|
{ownerMember ? (
|
||||||
<span className="text-[var(--color-text-secondary)]">
|
<div
|
||||||
{currentTask.owner ?? '\u2014'}
|
className="flex min-w-0 items-center gap-2 rounded-md px-2 py-1"
|
||||||
</span>
|
style={{
|
||||||
|
borderLeft: `3px solid ${getTeamColorSet(ownerMember.color ?? '').border}`,
|
||||||
|
backgroundColor: getTeamColorSet(ownerMember.color ?? '').badge,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
src={agentAvatarUrl(ownerMember.name, 32)}
|
||||||
|
alt={ownerMember.name}
|
||||||
|
className="size-6 shrink-0 rounded-full bg-[var(--color-surface-raised)]"
|
||||||
|
loading="lazy"
|
||||||
|
/>
|
||||||
|
<span className="min-w-0 truncate font-medium text-[var(--color-text)]">
|
||||||
|
{ownerMember.name}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div className="flex items-center gap-1.5 text-[var(--color-text-muted)]">
|
||||||
|
<User size={12} />
|
||||||
|
<span className="text-[var(--color-text-secondary)]">
|
||||||
|
{currentTask.owner ?? '\u2014'}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
{currentTask.createdBy ? (
|
{currentTask.createdBy ? (
|
||||||
<div className="flex items-center gap-1.5 text-[var(--color-text-muted)]">
|
<div className="flex items-center gap-1.5 text-[var(--color-text-muted)]">
|
||||||
|
|
@ -159,126 +184,128 @@ export const TaskDetailDialog = ({
|
||||||
)}
|
)}
|
||||||
</CollapsibleTeamSection>
|
</CollapsibleTeamSection>
|
||||||
|
|
||||||
{/* Dependencies */}
|
<div className="mb-3 space-y-2">
|
||||||
{blockedByIds.length > 0 ? (
|
{/* Dependencies */}
|
||||||
<div className="flex flex-wrap items-center gap-1.5">
|
{blockedByIds.length > 0 ? (
|
||||||
<span className="inline-flex items-center gap-0.5 text-xs text-yellow-300">
|
<div className="flex flex-wrap items-center gap-1.5">
|
||||||
<ArrowLeftFromLine size={12} />
|
<span className="inline-flex items-center gap-0.5 text-xs text-yellow-300">
|
||||||
Blocked by
|
<ArrowLeftFromLine size={12} />
|
||||||
</span>
|
Blocked by
|
||||||
{blockedByIds.map((id) => {
|
|
||||||
const depTask = taskMap.get(id);
|
|
||||||
const isCompleted = depTask?.status === 'completed';
|
|
||||||
return (
|
|
||||||
<button
|
|
||||||
key={id}
|
|
||||||
type="button"
|
|
||||||
className={`inline-flex items-center rounded px-1.5 py-0.5 text-[10px] font-medium transition-colors ${
|
|
||||||
isCompleted
|
|
||||||
? 'bg-emerald-500/15 text-emerald-400 hover:bg-emerald-500/25'
|
|
||||||
: 'bg-yellow-500/15 text-yellow-300 hover:bg-yellow-500/25'
|
|
||||||
} cursor-pointer`}
|
|
||||||
title={depTask ? `#${id}: ${depTask.subject}` : `#${id}`}
|
|
||||||
onClick={() => handleDependencyClick(id)}
|
|
||||||
>
|
|
||||||
#{id}
|
|
||||||
</button>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</div>
|
|
||||||
) : null}
|
|
||||||
|
|
||||||
{blocksIds.length > 0 ? (
|
|
||||||
<div className="flex flex-wrap items-center gap-1.5">
|
|
||||||
<span className="inline-flex items-center gap-0.5 text-xs text-blue-400">
|
|
||||||
<ArrowRightFromLine size={12} />
|
|
||||||
Blocks
|
|
||||||
</span>
|
|
||||||
{blocksIds.map((id) => {
|
|
||||||
const depTask = taskMap.get(id);
|
|
||||||
const isCompleted = depTask?.status === 'completed';
|
|
||||||
return (
|
|
||||||
<button
|
|
||||||
key={id}
|
|
||||||
type="button"
|
|
||||||
className={`inline-flex items-center rounded px-1.5 py-0.5 text-[10px] font-medium transition-colors ${
|
|
||||||
isCompleted
|
|
||||||
? 'bg-emerald-500/15 text-emerald-400 hover:bg-emerald-500/25'
|
|
||||||
: 'bg-blue-500/15 text-blue-400 hover:bg-blue-500/25'
|
|
||||||
} cursor-pointer`}
|
|
||||||
title={depTask ? `#${id}: ${depTask.subject}` : `#${id}`}
|
|
||||||
onClick={() => handleDependencyClick(id)}
|
|
||||||
>
|
|
||||||
#{id}
|
|
||||||
</button>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</div>
|
|
||||||
) : null}
|
|
||||||
|
|
||||||
{/* Related tasks (explicit) */}
|
|
||||||
{relatedIds.length > 0 || relatedByIds.length > 0 ? (
|
|
||||||
<div className="space-y-2">
|
|
||||||
<div className="flex items-center gap-1.5 text-xs font-medium text-[var(--color-text-muted)]">
|
|
||||||
<Link2 size={12} />
|
|
||||||
Related tasks
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{relatedIds.length > 0 ? (
|
|
||||||
<div className="flex flex-wrap items-center gap-1.5">
|
|
||||||
<span className="text-xs text-[var(--color-text-muted)]">Links</span>
|
|
||||||
{relatedIds.map((id) => {
|
|
||||||
const depTask = taskMap.get(id);
|
|
||||||
return (
|
|
||||||
<button
|
|
||||||
key={`related:${currentTask.id}:${id}`}
|
|
||||||
type="button"
|
|
||||||
className="inline-flex items-center rounded bg-purple-500/15 px-1.5 py-0.5 text-[10px] font-medium text-purple-300 transition-colors hover:bg-purple-500/25"
|
|
||||||
title={depTask ? `#${id}: ${depTask.subject}` : `#${id}`}
|
|
||||||
onClick={() => handleDependencyClick(id)}
|
|
||||||
>
|
|
||||||
#{id}
|
|
||||||
</button>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</div>
|
|
||||||
) : null}
|
|
||||||
|
|
||||||
{relatedByIds.length > 0 ? (
|
|
||||||
<div className="flex flex-wrap items-center gap-1.5">
|
|
||||||
<span className="text-xs text-[var(--color-text-muted)]">Linked from</span>
|
|
||||||
{relatedByIds.map((id) => {
|
|
||||||
const depTask = taskMap.get(id);
|
|
||||||
return (
|
|
||||||
<button
|
|
||||||
key={`related-by:${currentTask.id}:${id}`}
|
|
||||||
type="button"
|
|
||||||
className="inline-flex items-center rounded bg-fuchsia-500/15 px-1.5 py-0.5 text-[10px] font-medium text-fuchsia-300 transition-colors hover:bg-fuchsia-500/25"
|
|
||||||
title={depTask ? `#${id}: ${depTask.subject}` : `#${id}`}
|
|
||||||
onClick={() => handleDependencyClick(id)}
|
|
||||||
>
|
|
||||||
#{id}
|
|
||||||
</button>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</div>
|
|
||||||
) : null}
|
|
||||||
</div>
|
|
||||||
) : null}
|
|
||||||
|
|
||||||
{/* Review info */}
|
|
||||||
{kanbanTaskState ? (
|
|
||||||
<div className="flex items-center gap-2">
|
|
||||||
{kanbanTaskState.reviewer ? (
|
|
||||||
<span className="text-xs text-[var(--color-text-secondary)]">
|
|
||||||
Reviewer: {kanbanTaskState.reviewer}
|
|
||||||
</span>
|
</span>
|
||||||
) : null}
|
{blockedByIds.map((id) => {
|
||||||
{kanbanTaskState.errorDescription ? (
|
const depTask = taskMap.get(id);
|
||||||
<span className="text-xs text-red-400">{kanbanTaskState.errorDescription}</span>
|
const isCompleted = depTask?.status === 'completed';
|
||||||
) : null}
|
return (
|
||||||
</div>
|
<button
|
||||||
) : null}
|
key={id}
|
||||||
|
type="button"
|
||||||
|
className={`inline-flex items-center rounded px-1.5 py-0.5 text-[10px] font-medium transition-colors ${
|
||||||
|
isCompleted
|
||||||
|
? 'bg-emerald-500/15 text-emerald-400 hover:bg-emerald-500/25'
|
||||||
|
: 'bg-yellow-500/15 text-yellow-300 hover:bg-yellow-500/25'
|
||||||
|
} cursor-pointer`}
|
||||||
|
title={depTask ? `#${id}: ${depTask.subject}` : `#${id}`}
|
||||||
|
onClick={() => handleDependencyClick(id)}
|
||||||
|
>
|
||||||
|
#{id}
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
|
|
||||||
|
{blocksIds.length > 0 ? (
|
||||||
|
<div className="flex flex-wrap items-center gap-1.5">
|
||||||
|
<span className="inline-flex items-center gap-0.5 text-xs text-blue-400">
|
||||||
|
<ArrowRightFromLine size={12} />
|
||||||
|
Blocks
|
||||||
|
</span>
|
||||||
|
{blocksIds.map((id) => {
|
||||||
|
const depTask = taskMap.get(id);
|
||||||
|
const isCompleted = depTask?.status === 'completed';
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
key={id}
|
||||||
|
type="button"
|
||||||
|
className={`inline-flex items-center rounded px-1.5 py-0.5 text-[10px] font-medium transition-colors ${
|
||||||
|
isCompleted
|
||||||
|
? 'bg-emerald-500/15 text-emerald-400 hover:bg-emerald-500/25'
|
||||||
|
: 'bg-blue-500/15 text-blue-400 hover:bg-blue-500/25'
|
||||||
|
} cursor-pointer`}
|
||||||
|
title={depTask ? `#${id}: ${depTask.subject}` : `#${id}`}
|
||||||
|
onClick={() => handleDependencyClick(id)}
|
||||||
|
>
|
||||||
|
#{id}
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
|
|
||||||
|
{/* Related tasks (explicit) */}
|
||||||
|
{relatedIds.length > 0 || relatedByIds.length > 0 ? (
|
||||||
|
<div className="space-y-2">
|
||||||
|
<div className="flex items-center gap-1.5 text-xs font-medium text-[var(--color-text-muted)]">
|
||||||
|
<Link2 size={12} />
|
||||||
|
Related tasks
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{relatedIds.length > 0 ? (
|
||||||
|
<div className="flex flex-wrap items-center gap-1.5">
|
||||||
|
<span className="text-xs text-[var(--color-text-muted)]">Links</span>
|
||||||
|
{relatedIds.map((id) => {
|
||||||
|
const depTask = taskMap.get(id);
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
key={`related:${currentTask.id}:${id}`}
|
||||||
|
type="button"
|
||||||
|
className="inline-flex items-center rounded bg-purple-500/15 px-1.5 py-0.5 text-[10px] font-medium text-purple-300 transition-colors hover:bg-purple-500/25"
|
||||||
|
title={depTask ? `#${id}: ${depTask.subject}` : `#${id}`}
|
||||||
|
onClick={() => handleDependencyClick(id)}
|
||||||
|
>
|
||||||
|
#{id}
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
|
|
||||||
|
{relatedByIds.length > 0 ? (
|
||||||
|
<div className="flex flex-wrap items-center gap-1.5">
|
||||||
|
<span className="text-xs text-[var(--color-text-muted)]">Linked from</span>
|
||||||
|
{relatedByIds.map((id) => {
|
||||||
|
const depTask = taskMap.get(id);
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
key={`related-by:${currentTask.id}:${id}`}
|
||||||
|
type="button"
|
||||||
|
className="inline-flex items-center rounded bg-fuchsia-500/15 px-1.5 py-0.5 text-[10px] font-medium text-fuchsia-300 transition-colors hover:bg-fuchsia-500/25"
|
||||||
|
title={depTask ? `#${id}: ${depTask.subject}` : `#${id}`}
|
||||||
|
onClick={() => handleDependencyClick(id)}
|
||||||
|
>
|
||||||
|
#{id}
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
|
|
||||||
|
{/* Review info */}
|
||||||
|
{kanbanTaskState ? (
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
{kanbanTaskState.reviewer ? (
|
||||||
|
<span className="text-xs text-[var(--color-text-secondary)]">
|
||||||
|
Reviewer: {kanbanTaskState.reviewer}
|
||||||
|
</span>
|
||||||
|
) : null}
|
||||||
|
{kanbanTaskState.errorDescription ? (
|
||||||
|
<span className="text-xs text-red-400">{kanbanTaskState.errorDescription}</span>
|
||||||
|
) : null}
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
|
</div>
|
||||||
|
|
||||||
{/* Comments */}
|
{/* Comments */}
|
||||||
<CollapsibleTeamSection
|
<CollapsibleTeamSection
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue