2025-07-11 19:27:33 +02:00
|
|
|
import { memo, useContext, useState } from 'react';
|
2025-08-23 17:39:42 +01:00
|
|
|
import {
|
|
|
|
|
ChevronDown,
|
|
|
|
|
ChevronUp,
|
|
|
|
|
Edit,
|
|
|
|
|
Trash2,
|
|
|
|
|
X,
|
|
|
|
|
Maximize2,
|
|
|
|
|
Minimize2,
|
|
|
|
|
} 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;
|
2025-08-13 18:10:19 +01:00
|
|
|
hideCloseButton?: boolean;
|
2025-08-23 17:39:42 +01:00
|
|
|
isFullScreen?: boolean;
|
|
|
|
|
setFullScreen?: (isFullScreen: boolean) => void;
|
2025-06-25 12:06:29 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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,
|
2025-08-13 18:10:19 +01:00
|
|
|
hideCloseButton = false,
|
2025-08-23 17:39:42 +01:00
|
|
|
isFullScreen,
|
|
|
|
|
setFullScreen,
|
2025-06-25 12:06:29 +01:00
|
|
|
}: 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-08-23 17:39:42 +01:00
|
|
|
<div className="p-4 pb-2 border-b-2 border-muted">
|
|
|
|
|
{/* Top row: title and action icons */}
|
2025-06-25 12:06:29 +01:00
|
|
|
<div className="flex items-start justify-between">
|
2025-08-23 17:39:42 +01:00
|
|
|
<div className="flex-1 min-w-0 flex items-start gap-2">
|
|
|
|
|
<div className="min-w-0 flex-1">
|
|
|
|
|
<h2 className="text-lg font-bold mb-1 line-clamp-2">
|
|
|
|
|
{task.title}
|
|
|
|
|
<Chip
|
|
|
|
|
className="ml-2 -mt-2 relative top-[-2px]"
|
|
|
|
|
dotColor={getTaskStatusDotColor(task.status)}
|
|
|
|
|
>
|
|
|
|
|
{statusLabels[task.status]}
|
|
|
|
|
</Chip>
|
|
|
|
|
</h2>
|
2025-06-25 12:06:29 +01:00
|
|
|
</div>
|
2025-08-23 17:39:42 +01:00
|
|
|
{setFullScreen && (
|
2025-06-25 12:06:29 +01:00
|
|
|
<TooltipProvider>
|
|
|
|
|
<Tooltip>
|
|
|
|
|
<TooltipTrigger asChild>
|
|
|
|
|
<Button
|
|
|
|
|
variant="ghost"
|
|
|
|
|
size="icon"
|
2025-08-23 17:39:42 +01:00
|
|
|
onClick={() => setFullScreen(!isFullScreen)}
|
|
|
|
|
aria-label={
|
|
|
|
|
isFullScreen
|
|
|
|
|
? 'Collapse to sidebar'
|
|
|
|
|
: 'Expand to fullscreen'
|
|
|
|
|
}
|
2025-06-25 12:06:29 +01:00
|
|
|
>
|
2025-08-23 17:39:42 +01:00
|
|
|
{isFullScreen ? (
|
|
|
|
|
<Minimize2 className="h-4 w-4" />
|
|
|
|
|
) : (
|
|
|
|
|
<Maximize2 className="h-4 w-4" />
|
|
|
|
|
)}
|
2025-06-25 12:06:29 +01:00
|
|
|
</Button>
|
|
|
|
|
</TooltipTrigger>
|
|
|
|
|
<TooltipContent>
|
2025-08-23 17:39:42 +01:00
|
|
|
<p>
|
|
|
|
|
{isFullScreen
|
|
|
|
|
? 'Collapse to sidebar'
|
|
|
|
|
: 'Expand to fullscreen'}
|
|
|
|
|
</p>
|
2025-08-13 18:10:19 +01:00
|
|
|
</TooltipContent>
|
|
|
|
|
</Tooltip>
|
|
|
|
|
</TooltipProvider>
|
|
|
|
|
)}
|
2025-08-23 17:39:42 +01:00
|
|
|
<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>
|
|
|
|
|
)}
|
|
|
|
|
{!hideCloseButton && (
|
|
|
|
|
<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>
|
|
|
|
|
</Tooltip>
|
|
|
|
|
</TooltipProvider>
|
|
|
|
|
)}
|
|
|
|
|
</div>
|
2025-06-25 12:06:29 +01:00
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
2025-08-23 17:39:42 +01:00
|
|
|
{/* Description + Status (sidebar view only) lives below icons */}
|
|
|
|
|
{!isFullScreen && (
|
|
|
|
|
<div className="mt-2">
|
|
|
|
|
<div className="p-2 bg-muted/20 rounded border-l-2 border-muted max-h-48 overflow-y-auto">
|
|
|
|
|
<div className="flex items-start gap-2 text-xs text-muted-foreground">
|
|
|
|
|
{task.description ? (
|
|
|
|
|
<div className="flex-1 min-w-0">
|
|
|
|
|
<p
|
|
|
|
|
className={`whitespace-pre-wrap ${
|
|
|
|
|
!isDescriptionExpanded && task.description.length > 150
|
|
|
|
|
? 'line-clamp-3'
|
|
|
|
|
: ''
|
|
|
|
|
}`}
|
|
|
|
|
>
|
|
|
|
|
{task.description}
|
|
|
|
|
</p>
|
|
|
|
|
{task.description.length > 150 && (
|
|
|
|
|
<Button
|
|
|
|
|
variant="ghost"
|
|
|
|
|
size="sm"
|
|
|
|
|
onClick={() =>
|
|
|
|
|
setIsDescriptionExpanded(!isDescriptionExpanded)
|
|
|
|
|
}
|
|
|
|
|
className="mt-1 p-0 h-auto text-xs text-muted-foreground hover:text-foreground"
|
|
|
|
|
>
|
|
|
|
|
{isDescriptionExpanded ? (
|
|
|
|
|
<>
|
|
|
|
|
<ChevronUp className="h-3 w-3 mr-1" />
|
|
|
|
|
Show less
|
|
|
|
|
</>
|
|
|
|
|
) : (
|
|
|
|
|
<>
|
|
|
|
|
<ChevronDown className="h-3 w-3 mr-1" />
|
|
|
|
|
Show more
|
|
|
|
|
</>
|
|
|
|
|
)}
|
|
|
|
|
</Button>
|
2025-06-25 12:06:29 +01:00
|
|
|
)}
|
2025-08-23 17:39:42 +01:00
|
|
|
</div>
|
|
|
|
|
) : (
|
|
|
|
|
<p className="italic">No description provided</p>
|
2025-06-25 12:06:29 +01:00
|
|
|
)}
|
|
|
|
|
</div>
|
2025-08-23 17:39:42 +01:00
|
|
|
</div>
|
2025-06-25 12:06:29 +01:00
|
|
|
</div>
|
2025-08-23 17:39:42 +01:00
|
|
|
)}
|
2025-06-25 12:06:29 +01:00
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
}
|
2025-07-11 19:27:33 +02:00
|
|
|
|
|
|
|
|
export default memo(TaskDetailsHeader);
|