2025-07-11 19:27:33 +02:00
|
|
|
import { memo, useContext, useState } from 'react';
|
2025-07-11 11:31:28 +02:00
|
|
|
import { ChevronDown, ChevronUp, Edit, Trash2, X } from 'lucide-react';
|
2025-06-25 12:06:29 +01:00
|
|
|
import { Button } from '@/components/ui/button';
|
|
|
|
|
import { Chip } from '@/components/ui/chip';
|
|
|
|
|
import {
|
|
|
|
|
Tooltip,
|
|
|
|
|
TooltipContent,
|
|
|
|
|
TooltipProvider,
|
|
|
|
|
TooltipTrigger,
|
|
|
|
|
} from '@/components/ui/tooltip';
|
|
|
|
|
import type { TaskStatus, TaskWithAttemptStatus } from 'shared/types';
|
2025-07-11 11:31:28 +02:00
|
|
|
import { TaskDetailsContext } from '@/components/context/taskDetailsContext.ts';
|
2025-06-25 12:06:29 +01:00
|
|
|
|
|
|
|
|
interface TaskDetailsHeaderProps {
|
|
|
|
|
onClose: () => void;
|
|
|
|
|
onEditTask?: (task: TaskWithAttemptStatus) => void;
|
|
|
|
|
onDeleteTask?: (taskId: string) => void;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const statusLabels: Record<TaskStatus, string> = {
|
|
|
|
|
todo: 'To Do',
|
|
|
|
|
inprogress: 'In Progress',
|
|
|
|
|
inreview: 'In Review',
|
|
|
|
|
done: 'Done',
|
|
|
|
|
cancelled: 'Cancelled',
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const getTaskStatusDotColor = (status: TaskStatus): string => {
|
|
|
|
|
switch (status) {
|
|
|
|
|
case 'todo':
|
|
|
|
|
return 'bg-gray-400';
|
|
|
|
|
case 'inprogress':
|
|
|
|
|
return 'bg-blue-500';
|
|
|
|
|
case 'inreview':
|
|
|
|
|
return 'bg-yellow-500';
|
|
|
|
|
case 'done':
|
|
|
|
|
return 'bg-green-500';
|
|
|
|
|
case 'cancelled':
|
|
|
|
|
return 'bg-red-500';
|
|
|
|
|
default:
|
|
|
|
|
return 'bg-gray-400';
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
2025-07-11 19:27:33 +02:00
|
|
|
function TaskDetailsHeader({
|
2025-06-25 12:06:29 +01:00
|
|
|
onClose,
|
|
|
|
|
onEditTask,
|
|
|
|
|
onDeleteTask,
|
|
|
|
|
}: TaskDetailsHeaderProps) {
|
2025-07-11 11:31:28 +02:00
|
|
|
const { task } = useContext(TaskDetailsContext);
|
2025-06-25 12:06:29 +01:00
|
|
|
const [isDescriptionExpanded, setIsDescriptionExpanded] = useState(false);
|
|
|
|
|
|
|
|
|
|
return (
|
2025-06-30 18:35:30 +01:00
|
|
|
<div>
|
2025-06-25 12:06:29 +01:00
|
|
|
{/* Title and Task Actions */}
|
2025-07-04 00:47:21 +01:00
|
|
|
<div className="p-4 pb-2">
|
2025-06-25 12:06:29 +01:00
|
|
|
<div className="flex items-start justify-between">
|
|
|
|
|
<div className="flex-1 min-w-0">
|
2025-07-04 00:47:21 +01:00
|
|
|
<h2 className="text-lg font-bold mb-1 line-clamp-2">
|
2025-06-25 12:06:29 +01:00
|
|
|
{task.title}
|
|
|
|
|
</h2>
|
|
|
|
|
<div className="flex items-center gap-2 text-sm text-muted-foreground">
|
|
|
|
|
<Chip dotColor={getTaskStatusDotColor(task.status)}>
|
|
|
|
|
{statusLabels[task.status]}
|
|
|
|
|
</Chip>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
<div className="flex items-center gap-1">
|
|
|
|
|
{onEditTask && (
|
|
|
|
|
<TooltipProvider>
|
|
|
|
|
<Tooltip>
|
|
|
|
|
<TooltipTrigger asChild>
|
|
|
|
|
<Button
|
|
|
|
|
variant="ghost"
|
|
|
|
|
size="icon"
|
|
|
|
|
onClick={() => onEditTask(task)}
|
|
|
|
|
>
|
|
|
|
|
<Edit className="h-4 w-4" />
|
|
|
|
|
</Button>
|
|
|
|
|
</TooltipTrigger>
|
|
|
|
|
<TooltipContent>
|
|
|
|
|
<p>Edit task</p>
|
|
|
|
|
</TooltipContent>
|
|
|
|
|
</Tooltip>
|
|
|
|
|
</TooltipProvider>
|
|
|
|
|
)}
|
|
|
|
|
{onDeleteTask && (
|
|
|
|
|
<TooltipProvider>
|
|
|
|
|
<Tooltip>
|
|
|
|
|
<TooltipTrigger asChild>
|
|
|
|
|
<Button
|
|
|
|
|
variant="ghost"
|
|
|
|
|
size="icon"
|
|
|
|
|
onClick={() => onDeleteTask(task.id)}
|
|
|
|
|
>
|
|
|
|
|
<Trash2 className="h-4 w-4 text-red-500" />
|
|
|
|
|
</Button>
|
|
|
|
|
</TooltipTrigger>
|
|
|
|
|
<TooltipContent>
|
|
|
|
|
<p>Delete task</p>
|
|
|
|
|
</TooltipContent>
|
|
|
|
|
</Tooltip>
|
|
|
|
|
</TooltipProvider>
|
|
|
|
|
)}
|
|
|
|
|
<TooltipProvider>
|
|
|
|
|
<Tooltip>
|
|
|
|
|
<TooltipTrigger asChild>
|
|
|
|
|
<Button variant="ghost" size="icon" onClick={onClose}>
|
|
|
|
|
<X className="h-4 w-4" />
|
|
|
|
|
</Button>
|
|
|
|
|
</TooltipTrigger>
|
|
|
|
|
<TooltipContent>
|
|
|
|
|
<p>Close panel</p>
|
|
|
|
|
</TooltipContent>
|
2025-06-27 13:32:32 +01:00
|
|
|
</Tooltip>
|
2025-06-25 12:06:29 +01:00
|
|
|
</TooltipProvider>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
{/* Description */}
|
2025-07-04 00:47:21 +01:00
|
|
|
<div className="mt-2">
|
2025-07-09 17:28:36 +01:00
|
|
|
<div className="p-2 bg-muted/20 rounded border-l-2 border-muted max-h-48 overflow-y-auto">
|
2025-06-25 12:06:29 +01:00
|
|
|
{task.description ? (
|
|
|
|
|
<div>
|
|
|
|
|
<p
|
2025-07-04 00:47:21 +01:00
|
|
|
className={`text-xs whitespace-pre-wrap text-muted-foreground ${
|
|
|
|
|
!isDescriptionExpanded && task.description.length > 150
|
|
|
|
|
? 'line-clamp-3'
|
2025-06-25 12:06:29 +01:00
|
|
|
: ''
|
|
|
|
|
}`}
|
|
|
|
|
>
|
|
|
|
|
{task.description}
|
|
|
|
|
</p>
|
2025-07-04 00:47:21 +01:00
|
|
|
{task.description.length > 150 && (
|
2025-06-25 12:06:29 +01:00
|
|
|
<Button
|
|
|
|
|
variant="ghost"
|
|
|
|
|
size="sm"
|
|
|
|
|
onClick={() =>
|
|
|
|
|
setIsDescriptionExpanded(!isDescriptionExpanded)
|
|
|
|
|
}
|
2025-07-04 00:47:21 +01:00
|
|
|
className="mt-1 p-0 h-auto text-xs text-muted-foreground hover:text-foreground"
|
2025-06-25 12:06:29 +01:00
|
|
|
>
|
|
|
|
|
{isDescriptionExpanded ? (
|
|
|
|
|
<>
|
|
|
|
|
<ChevronUp className="h-3 w-3 mr-1" />
|
|
|
|
|
Show less
|
|
|
|
|
</>
|
|
|
|
|
) : (
|
|
|
|
|
<>
|
|
|
|
|
<ChevronDown className="h-3 w-3 mr-1" />
|
|
|
|
|
Show more
|
|
|
|
|
</>
|
|
|
|
|
)}
|
|
|
|
|
</Button>
|
|
|
|
|
)}
|
|
|
|
|
</div>
|
|
|
|
|
) : (
|
2025-07-04 00:47:21 +01:00
|
|
|
<p className="text-xs text-muted-foreground italic">
|
2025-06-25 12:06:29 +01:00
|
|
|
No description provided
|
|
|
|
|
</p>
|
|
|
|
|
)}
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
}
|
2025-07-11 19:27:33 +02:00
|
|
|
|
|
|
|
|
export default memo(TaskDetailsHeader);
|