From 7c5b546f7d937299cb0f69c5ef458f7433c04ee6 Mon Sep 17 00:00:00 2001 From: sw3205933776 <3205933776@qq.com> Date: Wed, 19 Nov 2025 16:06:15 +0800 Subject: [PATCH] Fix delete button behavior in Project Settings --- .../GroupedHistoryView/ProjectDialog.tsx | 460 +++++++++--------- .../GroupedHistoryView/TaskItem.tsx | 88 ++-- src/components/ui/alertDialog.tsx | 4 +- 3 files changed, 290 insertions(+), 262 deletions(-) diff --git a/src/components/GroupedHistoryView/ProjectDialog.tsx b/src/components/GroupedHistoryView/ProjectDialog.tsx index 611ced799..55d635367 100644 --- a/src/components/GroupedHistoryView/ProjectDialog.tsx +++ b/src/components/GroupedHistoryView/ProjectDialog.tsx @@ -1,249 +1,273 @@ import { useState, useEffect, useRef } from "react"; import { ProjectGroup } from "@/types/history"; import { - Dialog, - DialogContent, - DialogHeader, - DialogContentSection, - DialogFooter, + Dialog, + DialogContent, + DialogHeader, + DialogContentSection, + DialogFooter, } from "@/components/ui/dialog"; import { Input } from "@/components/ui/input"; import { Button } from "@/components/ui/button"; import TaskItem from "./TaskItem"; import { useTranslation } from "react-i18next"; -import { Hash, CheckCircle, Clock, Pin, Loader2, LoaderCircle } from "lucide-react"; +import { + Hash, + CheckCircle, + Clock, + Pin, + Loader2, + LoaderCircle, +} from "lucide-react"; import { useProjectStore } from "@/store/projectStore"; interface ProjectDialogProps { - open: boolean; - onOpenChange: (open: boolean) => void; - project: ProjectGroup; - onProjectRename: (projectId: string, newName: string) => void; - onTaskSelect: (projectId: string, question: string, historyId: string) => void; - onTaskDelete: (taskId: string) => void; - onTaskShare: (taskId: string) => void; - activeTaskId?: string; + open: boolean; + onOpenChange: (open: boolean) => void; + project: ProjectGroup; + onProjectRename: (projectId: string, newName: string) => void; + onTaskSelect: ( + projectId: string, + question: string, + historyId: string + ) => void; + onTaskDelete: (taskId: string) => void; + onTaskShare: (taskId: string) => void; + activeTaskId?: string; } export default function ProjectDialog({ - open, - onOpenChange, - project, - onProjectRename, - onTaskSelect, - onTaskDelete, - onTaskShare, - activeTaskId, + open, + onOpenChange, + project, + onProjectRename, + onTaskSelect, + onTaskDelete, + onTaskShare, + activeTaskId, }: ProjectDialogProps) { - const { t } = useTranslation(); - const projectStore = useProjectStore(); - const [projectName, setProjectName] = useState(project.project_name || t("layout.new-project")); - const [isSaving, setIsSaving] = useState(false); - const saveTimeoutRef = useRef(null); - const lastSavedNameRef = useRef(project.project_name || t("layout.new-project")); + const { t } = useTranslation(); + const projectStore = useProjectStore(); + const [projectName, setProjectName] = useState( + project.project_name || t("layout.new-project") + ); + const [isSaving, setIsSaving] = useState(false); + const saveTimeoutRef = useRef(null); + const lastSavedNameRef = useRef( + project.project_name || t("layout.new-project") + ); - // Update state when project changes - useEffect(() => { - const name = project.project_name || t("layout.new-project"); - setProjectName(name); - lastSavedNameRef.current = name; - }, [project.project_name, project.project_id, t]); + // Update state when project changes + useEffect(() => { + const name = project.project_name || t("layout.new-project"); + setProjectName(name); + lastSavedNameRef.current = name; + }, [project.project_name, project.project_id, t]); - // Auto-save with debouncing - useEffect(() => { - // Clear any existing timeout - if (saveTimeoutRef.current) { - clearTimeout(saveTimeoutRef.current); - } + // Auto-save with debouncing + useEffect(() => { + // Clear any existing timeout + if (saveTimeoutRef.current) { + clearTimeout(saveTimeoutRef.current); + } - const trimmedName = projectName.trim(); - - // Only save if the name has actually changed and is not empty - if (trimmedName && trimmedName !== lastSavedNameRef.current) { - setIsSaving(true); - - // Debounce: wait 800ms after user stops typing - saveTimeoutRef.current = setTimeout(() => { - // Update via callback (for history API) - onProjectRename(project.project_id, trimmedName); - - // Also update in projectStore if the project exists there - const storeProject = projectStore.getProjectById(project.project_id); - if (storeProject) { - projectStore.updateProject(project.project_id, { name: trimmedName }); - } - - lastSavedNameRef.current = trimmedName; - setIsSaving(false); - }, 800); - } else if (!trimmedName) { - // If empty, don't show saving state - setIsSaving(false); - } + const trimmedName = projectName.trim(); - // Cleanup timeout on unmount or when projectName changes - return () => { - if (saveTimeoutRef.current) { - clearTimeout(saveTimeoutRef.current); - } - }; - }, [projectName, project.project_id, onProjectRename, projectStore]); + // Only save if the name has actually changed and is not empty + if (trimmedName && trimmedName !== lastSavedNameRef.current) { + setIsSaving(true); - // Cleanup on unmount - useEffect(() => { - return () => { - if (saveTimeoutRef.current) { - clearTimeout(saveTimeoutRef.current); - } - }; - }, []); + // Debounce: wait 800ms after user stops typing + saveTimeoutRef.current = setTimeout(() => { + // Update via callback (for history API) + onProjectRename(project.project_id, trimmedName); - return ( - - e.stopPropagation()} - onMouseDown={(e) => e.stopPropagation()} - > - e.stopPropagation()} - onMouseDown={(e) => e.stopPropagation()} - /> + // Also update in projectStore if the project exists there + const storeProject = projectStore.getProjectById(project.project_id); + if (storeProject) { + projectStore.updateProject(project.project_id, { name: trimmedName }); + } - e.stopPropagation()} - onMouseDown={(e) => e.stopPropagation()} - > - {/* Project Name Section - Inline Edit with Auto-Save */} -
e.stopPropagation()} - onMouseDown={(e) => e.stopPropagation()} - > - -
e.stopPropagation()} - onMouseDown={(e) => e.stopPropagation()} - > - setProjectName(e.target.value)} - onClick={(e) => e.stopPropagation()} - onMouseDown={(e) => e.stopPropagation()} - onFocus={(e) => e.stopPropagation()} - placeholder={t("layout.enter-project-name")} - /> - {isSaving ? ( - - ) : ( - <> - )} -
-
+ lastSavedNameRef.current = trimmedName; + setIsSaving(false); + }, 800); + } else if (!trimmedName) { + // If empty, don't show saving state + setIsSaving(false); + } - {/* Project Stats */} -
-
- - {t("layout.total-tokens")} - -
- - - {project.total_tokens.toLocaleString()} - -
-
+ // Cleanup timeout on unmount or when projectName changes + return () => { + if (saveTimeoutRef.current) { + clearTimeout(saveTimeoutRef.current); + } + }; + }, [projectName, project.project_id, onProjectRename, projectStore]); -
- - {t("layout.total-tasks")} - -
- - - {project.task_count} - -
-
+ // Cleanup on unmount + useEffect(() => { + return () => { + if (saveTimeoutRef.current) { + clearTimeout(saveTimeoutRef.current); + } + }; + }, []); -
- - {t("layout.completed")} - -
- - - {project.total_completed_tasks} - -
-
+ return ( + + e.stopPropagation()} + onMouseDown={(e) => e.stopPropagation()} + > + e.stopPropagation()} + onMouseDown={(e) => e.stopPropagation()} + /> -
- - {t("layout.ongoing")} - -
- - - {project.total_ongoing_tasks} - -
-
-
+ e.stopPropagation()} + onMouseDown={(e) => e.stopPropagation()} + > + {/* Project Name Section - Inline Edit with Auto-Save */} +
e.stopPropagation()} + onMouseDown={(e) => e.stopPropagation()} + > + +
e.stopPropagation()} + onMouseDown={(e) => e.stopPropagation()} + > + setProjectName(e.target.value)} + onClick={(e) => e.stopPropagation()} + onMouseDown={(e) => e.stopPropagation()} + onFocus={(e) => e.stopPropagation()} + placeholder={t("layout.enter-project-name")} + /> + {isSaving ? ( + + ) : ( + <> + )} +
+
- {/* Tasks List */} -
-
- {project.tasks.length > 0 ? ( - project.tasks.map((task, index) => ( - onTaskSelect(project.project_id, task.question, task.id.toString())} - onDelete={() => onTaskDelete(task.id.toString())} - onShare={() => onTaskShare(task.id.toString())} - isLast={index === project.tasks.length - 1} - /> - )) - ) : ( -
+
+ + {t("layout.total-tokens")} + +
+ + + {project.total_tokens.toLocaleString()} + +
+
+ +
+ + {t("layout.total-tasks")} + +
+ + + {project.task_count} + +
+
+ +
+ + {t("layout.completed")} + +
+ + + {project.total_completed_tasks} + +
+
+ +
+ + {t("layout.ongoing")} + +
+ + + {project.total_ongoing_tasks} + +
+
+
+ + {/* Tasks List */} +
+
+ {project.tasks.length > 0 ? ( + project.tasks.map((task, index) => ( + + onTaskSelect( + project.project_id, + task.question, + task.id.toString() + ) + } + onDelete={() => onTaskDelete(task.id.toString())} + onShare={() => onTaskShare(task.id.toString())} + isLast={index === project.tasks.length - 1} + showActions={false} + /> + )) + ) : ( +
- - {t("layout.no-tasks-in-project")} -
- )} -
-
- + " + > + + {t("layout.no-tasks-in-project")} +
+ )} +
+ +
- e.stopPropagation()} - onMouseDown={(e) => e.stopPropagation()} - > - - -
-
- ); + e.stopPropagation()} + onMouseDown={(e) => e.stopPropagation()} + > + + + + + ); } - diff --git a/src/components/GroupedHistoryView/TaskItem.tsx b/src/components/GroupedHistoryView/TaskItem.tsx index 135a31fd8..e93f7a89a 100644 --- a/src/components/GroupedHistoryView/TaskItem.tsx +++ b/src/components/GroupedHistoryView/TaskItem.tsx @@ -23,6 +23,7 @@ interface TaskItemProps { isOngoing?: boolean; onPause?: () => void; onResume?: () => void; + showActions?: boolean; } export default function TaskItem({ @@ -34,7 +35,8 @@ export default function TaskItem({ isLast, isOngoing = false, onPause, - onResume + onResume, + showActions = true }: TaskItemProps) { const { t } = useTranslation(); @@ -146,23 +148,40 @@ export default function TaskItem({ )} - - - + + - - - - -
- {!isOngoing && ( +
+ {!isOngoing && ( + + + + )} + - )} - - - - -
- - +
+
+
+ )} ); diff --git a/src/components/ui/alertDialog.tsx b/src/components/ui/alertDialog.tsx index 76674c999..04bfe76e1 100644 --- a/src/components/ui/alertDialog.tsx +++ b/src/components/ui/alertDialog.tsx @@ -33,7 +33,7 @@ export default function ConfirmModal({ initial={{ opacity: 0 }} animate={{ opacity: 1 }} exit={{ opacity: 0 }} - className="fixed inset-0 bg-white/5 backdrop-blur-sm z-100 alert-dialog" + className="fixed inset-0 bg-white/5 z-100 alert-dialog" onClick={onClose} /> @@ -42,7 +42,7 @@ export default function ConfirmModal({ initial={{ opacity: 0, scale: 0.9, y: 20 }} animate={{ opacity: 1, scale: 1, y: 0 }} exit={{ opacity: 0, scale: 0.9, y: 20 }} - className="fixed max-w-md alert-dialog-wrapper rounded-xl backdrop-blur-xl shadow-perfect" + className="fixed max-w-md alert-dialog-wrapper rounded-xl shadow-perfect" >