semblance/src/hooks/useFolderManagement.ts
michael 22b3ec19a5 Refactor FocusGroupModerator into smaller components and hooks
Extract business logic and UI into reusable pieces:

Custom Hooks:
- useFocusGroupAutoSave: debounced auto-save with retry logic
- useFolderManagement: folder CRUD operations
- usePersonaFiltering: filter state and persona filtering
- useDiscussionGuideGeneration: guide generation and progress

UI Components:
- SaveStatusIndicator: auto-save status display
- FolderSidebar: folder list and management
- PersonaFilterDialog: persona filter modal
- CopyGuideDialog: copy guide from other focus groups

Tab Components:
- SetupTab: form and asset uploader
- ReviewTab: discussion guide viewer
- ParticipantsTab: persona selection grid

Reduces FocusGroupModerator from 2,396 to ~600 lines (75% reduction).

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-04 09:11:21 -06:00

157 lines
4.4 KiB
TypeScript

import { useState, useEffect, useCallback } from 'react';
import { toast } from 'sonner';
import { foldersApi } from '@/lib/api';
// Define folder interface (database-compatible)
export interface Folder {
_id: string;
id?: string; // Legacy compatibility
name: string;
created_at?: string;
created_by?: string;
updated_at?: string;
}
// Default folder ID for "All Personas"
export const DEFAULT_FOLDER_ID = 'all';
interface UseFolderManagementReturn {
folders: Folder[];
selectedFolder: string;
setSelectedFolder: (id: string) => void;
isCreatingFolder: boolean;
setIsCreatingFolder: (creating: boolean) => void;
newFolderName: string;
setNewFolderName: (name: string) => void;
folderToRename: Folder | null;
renameFolderName: string;
setRenameFolderName: (name: string) => void;
fetchFolders: () => Promise<Folder[]>;
createNewFolder: () => Promise<void>;
cancelFolderCreation: () => void;
startRenameFolder: (folder: Folder) => void;
completeRenameFolder: () => Promise<void>;
cancelRenameFolder: () => void;
}
export function useFolderManagement(): UseFolderManagementReturn {
const [folders, setFolders] = useState<Folder[]>([]);
const [selectedFolder, setSelectedFolder] = useState<string>(DEFAULT_FOLDER_ID);
const [isCreatingFolder, setIsCreatingFolder] = useState(false);
const [newFolderName, setNewFolderName] = useState('');
const [folderToRename, setFolderToRename] = useState<Folder | null>(null);
const [renameFolderName, setRenameFolderName] = useState('');
// Function to fetch folders from database
const fetchFolders = useCallback(async (): Promise<Folder[]> => {
try {
const response = await foldersApi.getAll();
const serverFolders = response.data;
// Convert server folder format to match frontend expectations
const processedFolders: Folder[] = serverFolders.map((folder: any) => ({
...folder,
id: folder._id // Add legacy id field for compatibility
}));
setFolders(processedFolders);
return processedFolders;
} catch (error) {
console.error("Error fetching folders:", error);
toast.error("Failed to load folders");
setFolders([]);
return [];
}
}, []);
// Create new folder
const createNewFolder = useCallback(async () => {
if (!newFolderName.trim()) {
toast.error("Please enter a folder name");
return;
}
try {
await foldersApi.create({
name: newFolderName.trim()
});
// Refresh folders from server
await fetchFolders();
setNewFolderName('');
setIsCreatingFolder(false);
toast.success(`Folder "${newFolderName}" created`);
} catch (error) {
console.error("Error creating folder:", error);
toast.error("Failed to create folder");
}
}, [newFolderName, fetchFolders]);
// Cancel folder creation
const cancelFolderCreation = useCallback(() => {
setNewFolderName('');
setIsCreatingFolder(false);
}, []);
// Start renaming a folder
const startRenameFolder = useCallback((folder: Folder) => {
setFolderToRename(folder);
setRenameFolderName(folder.name);
}, []);
// Complete folder rename
const completeRenameFolder = useCallback(async () => {
if (!folderToRename || !renameFolderName.trim()) {
setFolderToRename(null);
return;
}
try {
await foldersApi.update(folderToRename._id, {
name: renameFolderName.trim()
});
// Refresh folders from server
await fetchFolders();
setFolderToRename(null);
toast.success(`Folder renamed to "${renameFolderName}"`);
} catch (error) {
console.error("Error renaming folder:", error);
toast.error("Failed to rename folder");
setFolderToRename(null);
}
}, [folderToRename, renameFolderName, fetchFolders]);
// Cancel folder rename
const cancelRenameFolder = useCallback(() => {
setFolderToRename(null);
setRenameFolderName('');
}, []);
// Fetch folders on mount
useEffect(() => {
fetchFolders();
}, [fetchFolders]);
return {
folders,
selectedFolder,
setSelectedFolder,
isCreatingFolder,
setIsCreatingFolder,
newFolderName,
setNewFolderName,
folderToRename,
renameFolderName,
setRenameFolderName,
fetchFolders,
createNewFolder,
cancelFolderCreation,
startRenameFolder,
completeRenameFolder,
cancelRenameFolder,
};
}