From 532d03c231409d06a2a0bfab3d8819e6ecb74a2f Mon Sep 17 00:00:00 2001 From: Test User Date: Sat, 20 Dec 2025 11:27:39 -0500 Subject: [PATCH] refactor: Introduce useBoardBackgroundSettings hook for managing board background settings with persistence - Refactored BoardBackgroundModal to utilize the new useBoardBackgroundSettings hook, improving code organization and reusability. - Updated methods for setting board background, card opacity, column opacity, and other settings to include server persistence. - Enhanced error handling and user feedback with toast notifications for successful and failed operations. - Added keyboard shortcut support for selecting folders in FileBrowserDialog, improving user experience. - Improved KanbanCard component layout and added dropdown menu for editing and viewing model information. --- .../dialogs/board-background-modal.tsx | 42 ++-- .../dialogs/file-browser-dialog.tsx | 27 ++- .../board-view/components/kanban-card.tsx | 98 +++++++--- .../hooks/use-board-background-settings.ts | 182 ++++++++++++++++++ 4 files changed, 294 insertions(+), 55 deletions(-) create mode 100644 apps/ui/src/hooks/use-board-background-settings.ts diff --git a/apps/ui/src/components/dialogs/board-background-modal.tsx b/apps/ui/src/components/dialogs/board-background-modal.tsx index bf3ccbd4..3244dfdf 100644 --- a/apps/ui/src/components/dialogs/board-background-modal.tsx +++ b/apps/ui/src/components/dialogs/board-background-modal.tsx @@ -15,6 +15,7 @@ import { Checkbox } from "@/components/ui/checkbox"; import { cn } from "@/lib/utils"; import { useAppStore, defaultBackgroundSettings } from "@/store/app-store"; import { getHttpApiClient } from "@/lib/http-api-client"; +import { useBoardBackgroundSettings } from "@/hooks/use-board-background-settings"; import { toast } from "sonner"; const ACCEPTED_IMAGE_TYPES = [ @@ -35,9 +36,8 @@ export function BoardBackgroundModal({ open, onOpenChange, }: BoardBackgroundModalProps) { + const { currentProject, boardBackgroundByProject } = useAppStore(); const { - currentProject, - boardBackgroundByProject, setBoardBackground, setCardOpacity, setColumnOpacity, @@ -47,7 +47,7 @@ export function BoardBackgroundModal({ setCardBorderOpacity, setHideScrollbar, clearBoardBackground, - } = useAppStore(); + } = useBoardBackgroundSettings(); const [isDragOver, setIsDragOver] = useState(false); const [isProcessing, setIsProcessing] = useState(false); const fileInputRef = useRef(null); @@ -139,8 +139,8 @@ export function BoardBackgroundModal({ ); if (result.success && result.path) { - // Update store with the relative path (live update) - setBoardBackground(currentProject.path, result.path); + // Update store and persist to server + await setBoardBackground(currentProject.path, result.path); toast.success("Background image saved"); } else { toast.error(result.error || "Failed to save background image"); @@ -214,7 +214,7 @@ export function BoardBackgroundModal({ ); if (result.success) { - clearBoardBackground(currentProject.path); + await clearBoardBackground(currentProject.path); setPreviewImage(null); toast.success("Background image cleared"); } else { @@ -228,59 +228,59 @@ export function BoardBackgroundModal({ } }, [currentProject, clearBoardBackground]); - // Live update opacity when sliders change + // Live update opacity when sliders change (with persistence) const handleCardOpacityChange = useCallback( - (value: number[]) => { + async (value: number[]) => { if (!currentProject) return; - setCardOpacity(currentProject.path, value[0]); + await setCardOpacity(currentProject.path, value[0]); }, [currentProject, setCardOpacity] ); const handleColumnOpacityChange = useCallback( - (value: number[]) => { + async (value: number[]) => { if (!currentProject) return; - setColumnOpacity(currentProject.path, value[0]); + await setColumnOpacity(currentProject.path, value[0]); }, [currentProject, setColumnOpacity] ); const handleColumnBorderToggle = useCallback( - (checked: boolean) => { + async (checked: boolean) => { if (!currentProject) return; - setColumnBorderEnabled(currentProject.path, checked); + await setColumnBorderEnabled(currentProject.path, checked); }, [currentProject, setColumnBorderEnabled] ); const handleCardGlassmorphismToggle = useCallback( - (checked: boolean) => { + async (checked: boolean) => { if (!currentProject) return; - setCardGlassmorphism(currentProject.path, checked); + await setCardGlassmorphism(currentProject.path, checked); }, [currentProject, setCardGlassmorphism] ); const handleCardBorderToggle = useCallback( - (checked: boolean) => { + async (checked: boolean) => { if (!currentProject) return; - setCardBorderEnabled(currentProject.path, checked); + await setCardBorderEnabled(currentProject.path, checked); }, [currentProject, setCardBorderEnabled] ); const handleCardBorderOpacityChange = useCallback( - (value: number[]) => { + async (value: number[]) => { if (!currentProject) return; - setCardBorderOpacity(currentProject.path, value[0]); + await setCardBorderOpacity(currentProject.path, value[0]); }, [currentProject, setCardBorderOpacity] ); const handleHideScrollbarToggle = useCallback( - (checked: boolean) => { + async (checked: boolean) => { if (!currentProject) return; - setHideScrollbar(currentProject.path, checked); + await setHideScrollbar(currentProject.path, checked); }, [currentProject, setHideScrollbar] ); diff --git a/apps/ui/src/components/dialogs/file-browser-dialog.tsx b/apps/ui/src/components/dialogs/file-browser-dialog.tsx index 289ffbfe..1687218a 100644 --- a/apps/ui/src/components/dialogs/file-browser-dialog.tsx +++ b/apps/ui/src/components/dialogs/file-browser-dialog.tsx @@ -208,13 +208,31 @@ export function FileBrowserDialog({ } }; - const handleSelect = () => { + const handleSelect = useCallback(() => { if (currentPath) { addRecentFolder(currentPath); onSelect(currentPath); onOpenChange(false); } - }; + }, [currentPath, onSelect, onOpenChange]); + + // Handle Command/Ctrl+Enter keyboard shortcut to select current folder + useEffect(() => { + if (!open) return; + + const handleKeyDown = (e: KeyboardEvent) => { + // Check for Command+Enter (Mac) or Ctrl+Enter (Windows/Linux) + if (e.key === "Enter" && (e.metaKey || e.ctrlKey)) { + e.preventDefault(); + if (currentPath && !loading) { + handleSelect(); + } + } + }; + + window.addEventListener("keydown", handleKeyDown); + return () => window.removeEventListener("keydown", handleKeyDown); + }, [open, currentPath, loading, handleSelect]); // Helper to get folder name from path const getFolderName = (path: string) => { @@ -399,9 +417,12 @@ export function FileBrowserDialog({ - diff --git a/apps/ui/src/components/views/board-view/components/kanban-card.tsx b/apps/ui/src/components/views/board-view/components/kanban-card.tsx index 9b31771e..3994ce21 100644 --- a/apps/ui/src/components/views/board-view/components/kanban-card.tsx +++ b/apps/ui/src/components/views/board-view/components/kanban-card.tsx @@ -483,21 +483,54 @@ export const KanbanCard = memo(function KanbanCard({ )} > {isCurrentAutoTask && ( -
- - - {formatModelName(feature.model ?? DEFAULT_MODEL)} - - {feature.startedAt && ( - - )} +
+
+ + {feature.startedAt && ( + + )} +
+ + + + + + { + e.stopPropagation(); + onEdit(); + }} + data-testid={`edit-running-${feature.id}`} + className="text-xs" + > + + Edit + + {/* Model info in dropdown */} +
+
+ + {formatModelName(feature.model ?? DEFAULT_MODEL)} +
+
+
+
)} {!isCurrentAutoTask && feature.status === "backlog" && ( -
+
)} -
-
-
- -
)}
diff --git a/apps/ui/src/hooks/use-board-background-settings.ts b/apps/ui/src/hooks/use-board-background-settings.ts new file mode 100644 index 00000000..c8529d5f --- /dev/null +++ b/apps/ui/src/hooks/use-board-background-settings.ts @@ -0,0 +1,182 @@ +import { useCallback } from "react"; +import { useAppStore } from "@/store/app-store"; +import { getHttpApiClient } from "@/lib/http-api-client"; +import { toast } from "sonner"; + +/** + * Hook for managing board background settings with automatic persistence to server + */ +export function useBoardBackgroundSettings() { + const store = useAppStore(); + const httpClient = getHttpApiClient(); + + // Helper to persist settings to server + const persistSettings = useCallback( + async (projectPath: string, settingsToUpdate: Record) => { + try { + const result = await httpClient.settings.updateProject( + projectPath, + { + boardBackground: settingsToUpdate, + } + ); + + if (!result.success) { + console.error("Failed to persist settings:", result.error); + toast.error("Failed to save settings"); + } + } catch (error) { + console.error("Failed to persist settings:", error); + toast.error("Failed to save settings"); + } + }, + [httpClient] + ); + + // Get current background settings for a project + const getCurrentSettings = useCallback( + (projectPath: string) => { + const current = store.boardBackgroundByProject[projectPath]; + return current || { + imagePath: null, + cardOpacity: 100, + columnOpacity: 100, + columnBorderEnabled: true, + cardGlassmorphism: true, + cardBorderEnabled: true, + cardBorderOpacity: 100, + hideScrollbar: false, + }; + }, + [store.boardBackgroundByProject] + ); + + // Persisting wrappers for store actions + const setBoardBackground = useCallback( + async (projectPath: string, imagePath: string | null) => { + // Get current settings first + const current = getCurrentSettings(projectPath); + + // Prepare the updated settings + const toUpdate = { + ...current, + imagePath, + imageVersion: imagePath ? Date.now() : undefined, + }; + + // Update local store + store.setBoardBackground(projectPath, imagePath); + + // Persist to server + await persistSettings(projectPath, toUpdate); + }, + [store, persistSettings, getCurrentSettings] + ); + + const setCardOpacity = useCallback( + async (projectPath: string, opacity: number) => { + const current = getCurrentSettings(projectPath); + store.setCardOpacity(projectPath, opacity); + await persistSettings(projectPath, { ...current, cardOpacity: opacity }); + }, + [store, persistSettings, getCurrentSettings] + ); + + const setColumnOpacity = useCallback( + async (projectPath: string, opacity: number) => { + const current = getCurrentSettings(projectPath); + store.setColumnOpacity(projectPath, opacity); + await persistSettings(projectPath, { ...current, columnOpacity: opacity }); + }, + [store, persistSettings, getCurrentSettings] + ); + + const setColumnBorderEnabled = useCallback( + async (projectPath: string, enabled: boolean) => { + const current = getCurrentSettings(projectPath); + store.setColumnBorderEnabled(projectPath, enabled); + await persistSettings(projectPath, { + ...current, + columnBorderEnabled: enabled, + }); + }, + [store, persistSettings, getCurrentSettings] + ); + + const setCardGlassmorphism = useCallback( + async (projectPath: string, enabled: boolean) => { + const current = getCurrentSettings(projectPath); + store.setCardGlassmorphism(projectPath, enabled); + await persistSettings(projectPath, { + ...current, + cardGlassmorphism: enabled, + }); + }, + [store, persistSettings, getCurrentSettings] + ); + + const setCardBorderEnabled = useCallback( + async (projectPath: string, enabled: boolean) => { + const current = getCurrentSettings(projectPath); + store.setCardBorderEnabled(projectPath, enabled); + await persistSettings(projectPath, { + ...current, + cardBorderEnabled: enabled, + }); + }, + [store, persistSettings, getCurrentSettings] + ); + + const setCardBorderOpacity = useCallback( + async (projectPath: string, opacity: number) => { + const current = getCurrentSettings(projectPath); + store.setCardBorderOpacity(projectPath, opacity); + await persistSettings(projectPath, { + ...current, + cardBorderOpacity: opacity, + }); + }, + [store, persistSettings, getCurrentSettings] + ); + + const setHideScrollbar = useCallback( + async (projectPath: string, hide: boolean) => { + const current = getCurrentSettings(projectPath); + store.setHideScrollbar(projectPath, hide); + await persistSettings(projectPath, { ...current, hideScrollbar: hide }); + }, + [store, persistSettings, getCurrentSettings] + ); + + const clearBoardBackground = useCallback( + async (projectPath: string) => { + store.clearBoardBackground(projectPath); + // Clear the boardBackground settings + await persistSettings(projectPath, { + imagePath: null, + imageVersion: undefined, + cardOpacity: 100, + columnOpacity: 100, + columnBorderEnabled: true, + cardGlassmorphism: true, + cardBorderEnabled: true, + cardBorderOpacity: 100, + hideScrollbar: false, + }); + }, + [store, persistSettings] + ); + + return { + setBoardBackground, + setCardOpacity, + setColumnOpacity, + setColumnBorderEnabled, + setCardGlassmorphism, + setCardBorderEnabled, + setCardBorderOpacity, + setHideScrollbar, + clearBoardBackground, + getCurrentSettings, + }; +}