Files
kandev/apps/web/components/task/task-plan-toolbar.tsx
T

283 lines
9.9 KiB
TypeScript

'use client';
import { memo } from 'react';
import {
IconDeviceFloppy,
IconX,
IconLoader2,
IconRobot,
IconUser,
IconArrowRight,
IconCopy,
IconRefresh,
IconTrash,
IconSend,
} from '@tabler/icons-react';
import { Button } from '@kandev/ui/button';
import { Tooltip, TooltipContent, TooltipTrigger } from '@kandev/ui/tooltip';
import { cn, formatRelativeTime } from '@/lib/utils';
import type { TaskPlan } from '@/lib/types/http';
type TaskPlanToolbarProps = {
plan: TaskPlan | null;
hasUnsavedChanges: boolean;
hasDraftContent: boolean;
isSaving: boolean;
isAgentBusy: boolean;
isReanalyzing: boolean;
hasActiveSession: boolean;
showApproveButton?: boolean;
// Comments props
commentCount?: number;
isSubmittingComments?: boolean;
onSubmitComments?: () => void;
onClearComments?: () => void;
// Existing handlers
onSave: () => void;
onDiscard: () => void;
onCopy: () => void;
onReanalyze: () => void;
onClear: () => void;
onApprove?: () => void;
};
export const TaskPlanToolbar = memo(function TaskPlanToolbar({
plan,
hasUnsavedChanges,
hasDraftContent,
isSaving,
isAgentBusy,
isReanalyzing,
hasActiveSession,
showApproveButton,
commentCount = 0,
isSubmittingComments = false,
onSubmitComments,
onClearComments,
onSave,
onDiscard,
onCopy,
onReanalyze,
onClear,
onApprove,
}: TaskPlanToolbarProps) {
const hasComments = commentCount > 0;
return (
<div className="mt-2">
<div
className={cn(
'rounded-2xl border border-border bg-background shadow-sm overflow-hidden',
hasUnsavedChanges && 'border-amber-500/50 border-dashed'
)}
>
<div className="flex items-center gap-1 px-2 py-2">
{/* Left: Status info */}
<div className="flex items-center gap-1 flex-1">
{plan && (
<Tooltip>
<TooltipTrigger asChild>
<div className="flex items-center gap-1.5 px-2 py-1 rounded-md text-xs text-muted-foreground hover:bg-muted/40 cursor-default">
{plan.created_by === 'agent' ? (
<IconRobot className="h-3.5 w-3.5" />
) : (
<IconUser className="h-3.5 w-3.5" />
)}
<span className="capitalize">{plan.created_by}</span>
</div>
</TooltipTrigger>
<TooltipContent>Created by {plan.created_by}</TooltipContent>
</Tooltip>
)}
{hasUnsavedChanges ? (
<span className="text-xs text-amber-500 px-2">
Unsaved changes <span className="text-amber-500/60">· ⌘S to save</span>
</span>
) : plan?.updated_at ? (
<span className="text-xs text-muted-foreground/60 px-2">
Saved {formatRelativeTime(plan.updated_at)}
</span>
) : null}
</div>
{/* Right: Actions */}
<div className="flex items-center gap-0.5 shrink-0">
{hasUnsavedChanges && (
<>
<Tooltip>
<TooltipTrigger asChild>
<Button
type="button"
variant="ghost"
size="sm"
className="h-7 gap-1.5 px-2 cursor-pointer hover:bg-muted/40 text-muted-foreground hover:text-destructive"
onClick={onDiscard}
disabled={isSaving}
>
<IconX className="h-4 w-4" />
</Button>
</TooltipTrigger>
<TooltipContent>Discard changes</TooltipContent>
</Tooltip>
<Tooltip>
<TooltipTrigger asChild>
<Button
type="button"
variant="ghost"
size="sm"
className="h-7 gap-1.5 px-2 cursor-pointer hover:bg-muted/40"
onClick={onSave}
disabled={isSaving}
>
{isSaving ? (
<IconLoader2 className="h-4 w-4 animate-spin" />
) : (
<IconDeviceFloppy className="h-4 w-4" />
)}
</Button>
</TooltipTrigger>
<TooltipContent>Save plan</TooltipContent>
</Tooltip>
</>
)}
{/* Separator before utility actions */}
{(hasUnsavedChanges || plan || hasDraftContent) && (
<div className="h-4 w-px bg-border mx-1" />
)}
{/* Copy to clipboard */}
<Tooltip>
<TooltipTrigger asChild>
<Button
type="button"
variant="ghost"
size="sm"
className="h-7 gap-1.5 px-2 cursor-pointer hover:bg-muted/40 text-muted-foreground"
onClick={onCopy}
disabled={!hasDraftContent}
>
<IconCopy className="h-4 w-4" />
</Button>
</TooltipTrigger>
<TooltipContent>Copy to clipboard</TooltipContent>
</Tooltip>
{/* Re-analyze with agent */}
<Tooltip>
<TooltipTrigger asChild>
<Button
type="button"
variant="ghost"
size="sm"
className="h-7 gap-1.5 px-2 cursor-pointer hover:bg-muted/40 text-muted-foreground"
onClick={onReanalyze}
disabled={isAgentBusy || !hasActiveSession || isReanalyzing}
>
{isReanalyzing ? (
<IconLoader2 className="h-4 w-4 animate-spin" />
) : (
<IconRefresh className="h-4 w-4" />
)}
</Button>
</TooltipTrigger>
<TooltipContent>Ask agent to review plan</TooltipContent>
</Tooltip>
{/* Clear/Delete plan */}
<Tooltip>
<TooltipTrigger asChild>
<Button
type="button"
variant="ghost"
size="sm"
className="h-7 gap-1.5 px-2 cursor-pointer hover:bg-muted/40 text-muted-foreground hover:text-destructive"
onClick={onClear}
disabled={!plan && !hasDraftContent}
>
<IconTrash className="h-4 w-4" />
</Button>
</TooltipTrigger>
<TooltipContent>Clear plan</TooltipContent>
</Tooltip>
{/* Separator before comments */}
{hasComments && <div className="h-4 w-px bg-border mx-1" />}
{/* Comments section */}
{hasComments && (
<div className="flex items-center gap-1">
{/* Clear comments */}
<Tooltip>
<TooltipTrigger asChild>
<Button
type="button"
variant="ghost"
size="sm"
className="h-7 w-7 p-0 cursor-pointer hover:bg-muted/40 text-muted-foreground hover:text-destructive"
onClick={onClearComments}
disabled={isSubmittingComments}
>
<IconX className="h-3.5 w-3.5" />
</Button>
</TooltipTrigger>
<TooltipContent>Clear all comments</TooltipContent>
</Tooltip>
{/* Submit comments button - round with count inside */}
<Tooltip>
<TooltipTrigger asChild>
<Button
type="button"
size="icon"
className="h-7 w-7 rounded-full cursor-pointer bg-primary hover:bg-primary/90 text-primary-foreground relative"
onClick={onSubmitComments}
disabled={isSubmittingComments || isAgentBusy}
>
{isSubmittingComments ? (
<IconLoader2 className="h-4 w-4 animate-spin" />
) : (
<>
<IconSend className="h-3.5 w-3.5" />
<span className="absolute -top-1 -right-1 flex h-4 w-4 items-center justify-center rounded-full bg-primary-foreground text-primary text-[10px] font-semibold">
{commentCount}
</span>
</>
)}
</Button>
</TooltipTrigger>
<TooltipContent>Send {commentCount} comment{commentCount !== 1 ? 's' : ''} to agent</TooltipContent>
</Tooltip>
</div>
)}
{/* Separator before approve button */}
{showApproveButton && onApprove && (
<div className="h-4 w-px bg-border mx-1" />
)}
{/* Approve button - styled like submit button */}
{showApproveButton && onApprove && (
<div className="ml-1">
<Tooltip>
<TooltipTrigger asChild>
<Button
type="button"
size="icon"
className="h-7 w-7 rounded-full cursor-pointer bg-emerald-500 hover:bg-emerald-600 text-white"
onClick={onApprove}
>
<IconArrowRight className="h-4 w-4" />
</Button>
</TooltipTrigger>
<TooltipContent>Approve plan and continue</TooltipContent>
</Tooltip>
</div>
)}
</div>
</div>
</div>
</div>
);
});