diff --git a/frontend/components/task-dialog/header.tsx b/frontend/components/task-dialog/header.tsx index d04277de7..5f655d6d5 100644 --- a/frontend/components/task-dialog/header.tsx +++ b/frontend/components/task-dialog/header.tsx @@ -1,10 +1,12 @@ "use client"; +import type { Task } from "@/app/api/queries/useGetTasksQuery"; import { DialogHeader, DialogTitle } from "@/components/ui/dialog"; import { useIsCloudBrand } from "@/contexts/brand-context"; import { ALL_TASK_FILE_TYPES, formatTaskFileTypeLabel, + isTaskInProgressStatus, type TaskFileStatusCategory, } from "@/lib/task-utils"; import { cn } from "@/lib/utils"; @@ -13,6 +15,7 @@ import { TaskDialogFilters } from "./filters"; interface TaskDialogHeaderProps { taskId: string; + taskStatus?: Task["status"]; search: string; onSearchChange: (value: string) => void; fileType: string; @@ -27,6 +30,7 @@ interface TaskDialogHeaderProps { export function TaskDialogHeader({ taskId, + taskStatus, search, onSearchChange, fileType, @@ -44,6 +48,8 @@ export function TaskDialogHeader({ fileType === ALL_TASK_FILE_TYPES ? allTypesLabel : formatTaskFileTypeLabel(fileType); + const titlePrefix = + taskStatus && isTaskInProgressStatus(taskStatus) ? "Active task" : "Task"; return (
- Task {taskId} + {titlePrefix} {taskId} diff --git a/frontend/components/task-dialog/task-dialog.tsx b/frontend/components/task-dialog/task-dialog.tsx index e20d75ac4..e384cd979 100644 --- a/frontend/components/task-dialog/task-dialog.tsx +++ b/frontend/components/task-dialog/task-dialog.tsx @@ -60,6 +60,7 @@ function TaskDialogContent({ // Always offer "All file types"; only disable while task data is loading. const fileTypeDisabled = !task; const showRetryActions = retryableCount > 0; + const isCancelOnly = !showRetryActions; return (
- {showRetryActions && selectedCount > 0 ? ( ) : null} +
); diff --git a/frontend/components/task-error-content.tsx b/frontend/components/task-error-content.tsx index c03d9fe44..96271c3f4 100644 --- a/frontend/components/task-error-content.tsx +++ b/frontend/components/task-error-content.tsx @@ -4,14 +4,13 @@ import * as AccordionPrimitive from "@radix-ui/react-accordion"; import { AlertCircle, ChevronDown, Flag, XCircle } from "lucide-react"; import { useMemo, useState } from "react"; import { IncidentReporterIcon } from "@/components/icons/incident-reporter-icon"; -import TaskDialog from "@/components/task-dialog"; import { Accordion, AccordionContent, AccordionItem, } from "@/components/ui/accordion"; import { useIsCloudBrand } from "@/contexts/brand-context"; -import { type Task } from "@/contexts/task-context"; +import { type Task, useTask } from "@/contexts/task-context"; import { formatApiComponent, resolveTaskFileError, @@ -43,10 +42,10 @@ export function TaskErrorContent({ defaultExpanded = false, }: TaskErrorContentProps) { const isCloudBrand = useIsCloudBrand(); + const { openTaskDialog } = useTask(); const [accordionValue, setAccordionValue] = useState( defaultExpanded ? "failed-files" : "", ); - const [isTaskDialogOpen, setIsTaskDialogOpen] = useState(false); const isExpanded = accordionValue === "failed-files"; const failedEntries = useMemo(() => getFailedFileEntries(task), [task]); @@ -90,7 +89,7 @@ export function TaskErrorContent({ type="button" aria-label="Open task details" className="inline-flex shrink-0 items-center justify-center text-muted-foreground hover:text-foreground" - onClick={() => setIsTaskDialogOpen(true)} + onClick={() => openTaskDialog(task.task_id)} > @@ -237,13 +236,6 @@ export function TaskErrorContent({ - - setIsTaskDialogOpen(false)} - /> ); } diff --git a/frontend/components/task-notification-menu.tsx b/frontend/components/task-notification-menu.tsx index f2a2ea4fc..9efa3378f 100644 --- a/frontend/components/task-notification-menu.tsx +++ b/frontend/components/task-notification-menu.tsx @@ -10,6 +10,7 @@ import { XCircle, } from "lucide-react"; import { useEffect, useMemo, useRef, useState } from "react"; +import { IncidentReporterIcon } from "@/components/icons/incident-reporter-icon"; import { TaskCollapsibleSection } from "@/components/task-collapsible-section"; import { TaskErrorContent } from "@/components/task-error-content"; import { TaskPanelHeader } from "@/components/task-panel-header"; @@ -43,6 +44,7 @@ export function TaskNotificationMenu() { selectedTaskTrigger, cancelTask, closeMenu, + openTaskDialog, } = useTask(); const [isPastOpen, setIsPastOpen] = useState(true); const lastHandledSelectionTriggerRef = useRef(0); @@ -361,8 +363,8 @@ export function TaskNotificationMenu() { className="bg-card/50 border-0 shadow-none py-mmd px-4" > -
- +
+ {showTaskIcon && getTaskIcon( task.status, @@ -371,6 +373,14 @@ export function TaskNotificationMenu() { )} Task {task.task_id.substring(0, 8)}... +
Started {formatRelativeTime(task.created_at)} diff --git a/frontend/contexts/task-context.tsx b/frontend/contexts/task-context.tsx index ba904933a..bd7784be2 100644 --- a/frontend/contexts/task-context.tsx +++ b/frontend/contexts/task-context.tsx @@ -20,6 +20,7 @@ import { useGetTasksQuery, } from "@/app/api/queries/useGetTasksQuery"; import type { ListFilesResponse } from "@/app/api/queries/useListFiles"; +import TaskDialog from "@/components/task-dialog"; import { useAuth } from "@/contexts/auth-context"; import { useOnboardingState } from "@/hooks/use-onboarding-state"; import { trackProcessFailure, trackProcessSuccess } from "@/lib/analytics"; @@ -74,6 +75,10 @@ interface TaskContextType { setSelectedTaskId: (taskId: string | null) => void; selectedTaskTrigger: number; selectTask: (taskId: string | null) => void; + isTaskDialogOpen: boolean; + taskDialogTaskId: string | null; + openTaskDialog: (taskId: string) => void; + closeTaskDialog: () => void; // React Query states isLoading: boolean; error: Error | null; @@ -87,7 +92,17 @@ export function TaskProvider({ children }: { children: React.ReactNode }) { const [isRecentTasksExpanded, setIsRecentTasksExpanded] = useState(false); const [selectedTaskId, setSelectedTaskId] = useState(null); const [selectedTaskTrigger, setSelectedTaskTrigger] = useState(0); + const [taskDialogTaskId, setTaskDialogTaskId] = useState(null); + const [isTaskDialogOpen, setIsTaskDialogOpen] = useState(false); const previousTasksRef = useRef([]); + const openTaskDialog = useCallback((taskId: string) => { + setTaskDialogTaskId(taskId); + setIsTaskDialogOpen(true); + }, []); + const closeTaskDialog = useCallback(() => { + setIsTaskDialogOpen(false); + setTaskDialogTaskId(null); + }, []); const selectTask = useCallback((taskId: string | null) => { setSelectedTaskId(taskId); if (taskId) { @@ -636,11 +651,32 @@ export function TaskProvider({ children }: { children: React.ReactNode }) { setSelectedTaskId, selectedTaskTrigger, selectTask, + isTaskDialogOpen, + taskDialogTaskId, + openTaskDialog, + closeTaskDialog, isLoading, error, }; - return {children}; + return ( + + {children} + {taskDialogTaskId ? ( + { + setIsTaskDialogOpen(open); + if (!open) { + setTaskDialogTaskId(null); + } + }} + task_id={taskDialogTaskId} + onClose={closeTaskDialog} + /> + ) : null} + + ); } export function useTask() {