diff --git a/src/components/ProjectManager.tsx b/src/components/ProjectManager.tsx index c0fa930..d039571 100644 --- a/src/components/ProjectManager.tsx +++ b/src/components/ProjectManager.tsx @@ -1,4 +1,4 @@ -import { useState, useEffect } from 'react' +import { useProjectManager } from '@/hooks/use-project-manager' import { Button } from '@/components/ui/button' import { Card, CardContent, CardDescription, CardHeader, CardTitle, CardFooter } from '@/components/ui/card' import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from '@/components/ui/dialog' @@ -9,9 +9,8 @@ import { ScrollArea } from '@/components/ui/scroll-area' import { Badge } from '@/components/ui/badge' import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle } from '@/components/ui/alert-dialog' import { FloppyDisk, FolderOpen, Trash, Copy, DownloadSimple, UploadSimple, Plus, FolderPlus } from '@phosphor-icons/react' -import { ProjectService, SavedProject } from '@/lib/project-service' +import { SavedProject } from '@/lib/project-service' import { Project } from '@/types/project' -import { toast } from 'sonner' import { cn } from '@/lib/utils' import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip' @@ -21,161 +20,36 @@ interface ProjectManagerProps { } export function ProjectManager({ currentProject, onProjectLoad }: ProjectManagerProps) { - const [projects, setProjects] = useState([]) - const [saveDialogOpen, setSaveDialogOpen] = useState(false) - const [loadDialogOpen, setLoadDialogOpen] = useState(false) - const [newProjectDialogOpen, setNewProjectDialogOpen] = useState(false) - const [deleteDialogOpen, setDeleteDialogOpen] = useState(false) - const [importDialogOpen, setImportDialogOpen] = useState(false) - const [projectToDelete, setProjectToDelete] = useState(null) - const [projectName, setProjectName] = useState('') - const [projectDescription, setProjectDescription] = useState('') - const [currentProjectId, setCurrentProjectId] = useState(null) - const [importJson, setImportJson] = useState('') - const [isLoading, setIsLoading] = useState(false) - - useEffect(() => { - loadProjectsList() - }, []) - - const loadProjectsList = async () => { - setIsLoading(true) - try { - const list = await ProjectService.listProjects() - setProjects(list) - } catch (error) { - console.error('Failed to load projects:', error) - toast.error('Failed to load projects list') - } finally { - setIsLoading(false) - } - } - - const handleSaveProject = async () => { - if (!projectName.trim()) { - toast.error('Please enter a project name') - return - } - - try { - const id = currentProjectId || ProjectService.generateProjectId() - - await ProjectService.saveProject( - id, - projectName, - currentProject, - projectDescription - ) - - setCurrentProjectId(id) - toast.success('Project saved successfully!') - setSaveDialogOpen(false) - await loadProjectsList() - } catch (error) { - console.error('Failed to save project:', error) - toast.error('Failed to save project') - } - } - - const handleLoadProject = async (project: SavedProject) => { - try { - onProjectLoad(project.data) - setCurrentProjectId(project.id) - setProjectName(project.name) - setProjectDescription(project.description || '') - setLoadDialogOpen(false) - toast.success(`Loaded project: ${project.name}`) - } catch (error) { - console.error('Failed to load project:', error) - toast.error('Failed to load project') - } - } - - const handleDeleteProject = async () => { - if (!projectToDelete) return - - try { - await ProjectService.deleteProject(projectToDelete) - toast.success('Project deleted successfully') - setDeleteDialogOpen(false) - setProjectToDelete(null) - - if (currentProjectId === projectToDelete) { - setCurrentProjectId(null) - setProjectName('') - setProjectDescription('') - } - - await loadProjectsList() - } catch (error) { - console.error('Failed to delete project:', error) - toast.error('Failed to delete project') - } - } - - const handleDuplicateProject = async (id: string, name: string) => { - try { - const duplicated = await ProjectService.duplicateProject(id, `${name} (Copy)`) - if (duplicated) { - toast.success('Project duplicated successfully') - await loadProjectsList() - } - } catch (error) { - console.error('Failed to duplicate project:', error) - toast.error('Failed to duplicate project') - } - } - - const handleExportProject = async (id: string, name: string) => { - try { - const json = await ProjectService.exportProjectAsJSON(id) - if (json) { - const blob = new Blob([json], { type: 'application/json' }) - const url = URL.createObjectURL(blob) - const a = document.createElement('a') - a.href = url - a.download = `${name.replace(/[^a-z0-9]/gi, '_').toLowerCase()}.json` - document.body.appendChild(a) - a.click() - document.body.removeChild(a) - URL.revokeObjectURL(url) - toast.success('Project exported successfully') - } - } catch (error) { - console.error('Failed to export project:', error) - toast.error('Failed to export project') - } - } - - const handleImportProject = async () => { - if (!importJson.trim()) { - toast.error('Please paste project JSON') - return - } - - try { - const imported = await ProjectService.importProjectFromJSON(importJson) - if (imported) { - toast.success('Project imported successfully') - setImportDialogOpen(false) - setImportJson('') - await loadProjectsList() - } else { - toast.error('Invalid project JSON') - } - } catch (error) { - console.error('Failed to import project:', error) - toast.error('Failed to import project') - } - } - - const handleNewProject = () => { - setCurrentProjectId(null) - setProjectName('') - setProjectDescription('') - setNewProjectDialogOpen(false) - toast.success('New project started') - } + const { + projects, + isLoading, + currentProjectId, + projectName, + projectDescription, + importJson, + saveDialogOpen, + loadDialogOpen, + newProjectDialogOpen, + deleteDialogOpen, + importDialogOpen, + projectToDelete, + setSaveDialogOpen, + setLoadDialogOpen, + setNewProjectDialogOpen, + setDeleteDialogOpen, + setImportDialogOpen, + setProjectToDelete, + setProjectName, + setProjectDescription, + setImportJson, + handleSaveProject, + handleLoadProject, + handleDeleteProject, + handleDuplicateProject, + handleExportProject, + handleImportProject, + handleNewProject, + } = useProjectManager({ currentProject, onProjectLoad }) const formatDate = (timestamp: number) => { return new Date(timestamp).toLocaleString() diff --git a/src/hooks/use-project-manager.ts b/src/hooks/use-project-manager.ts new file mode 100644 index 0000000..6de0829 --- /dev/null +++ b/src/hooks/use-project-manager.ts @@ -0,0 +1,199 @@ +import { useCallback, useEffect, useState } from 'react' +import { toast } from 'sonner' +import { ProjectService, SavedProject } from '@/lib/project-service' +import { Project } from '@/types/project' + +interface UseProjectManagerOptions { + currentProject: Project + onProjectLoad: (project: Project) => void +} + +export function useProjectManager({ currentProject, onProjectLoad }: UseProjectManagerOptions) { + const [projects, setProjects] = useState([]) + const [saveDialogOpen, setSaveDialogOpen] = useState(false) + const [loadDialogOpen, setLoadDialogOpen] = useState(false) + const [newProjectDialogOpen, setNewProjectDialogOpen] = useState(false) + const [deleteDialogOpen, setDeleteDialogOpen] = useState(false) + const [importDialogOpen, setImportDialogOpen] = useState(false) + const [projectToDelete, setProjectToDelete] = useState(null) + const [projectName, setProjectName] = useState('') + const [projectDescription, setProjectDescription] = useState('') + const [currentProjectId, setCurrentProjectId] = useState(null) + const [importJson, setImportJson] = useState('') + const [isLoading, setIsLoading] = useState(false) + + const loadProjectsList = useCallback(async () => { + setIsLoading(true) + try { + const list = await ProjectService.listProjects() + setProjects(list) + } catch (error) { + console.error('Failed to load projects:', error) + toast.error('Failed to load projects list') + } finally { + setIsLoading(false) + } + }, []) + + useEffect(() => { + void loadProjectsList() + }, [loadProjectsList]) + + const handleSaveProject = useCallback(async () => { + if (!projectName.trim()) { + toast.error('Please enter a project name') + return + } + + try { + const id = currentProjectId || ProjectService.generateProjectId() + + await ProjectService.saveProject( + id, + projectName, + currentProject, + projectDescription + ) + + setCurrentProjectId(id) + toast.success('Project saved successfully!') + setSaveDialogOpen(false) + await loadProjectsList() + } catch (error) { + console.error('Failed to save project:', error) + toast.error('Failed to save project') + } + }, [currentProject, currentProjectId, loadProjectsList, projectDescription, projectName]) + + const handleLoadProject = useCallback(async (project: SavedProject) => { + try { + onProjectLoad(project.data) + setCurrentProjectId(project.id) + setProjectName(project.name) + setProjectDescription(project.description || '') + setLoadDialogOpen(false) + toast.success(`Loaded project: ${project.name}`) + } catch (error) { + console.error('Failed to load project:', error) + toast.error('Failed to load project') + } + }, [onProjectLoad]) + + const handleDeleteProject = useCallback(async () => { + if (!projectToDelete) return + + try { + await ProjectService.deleteProject(projectToDelete) + toast.success('Project deleted successfully') + setDeleteDialogOpen(false) + setProjectToDelete(null) + + if (currentProjectId === projectToDelete) { + setCurrentProjectId(null) + setProjectName('') + setProjectDescription('') + } + + await loadProjectsList() + } catch (error) { + console.error('Failed to delete project:', error) + toast.error('Failed to delete project') + } + }, [currentProjectId, loadProjectsList, projectToDelete]) + + const handleDuplicateProject = useCallback(async (id: string, name: string) => { + try { + const duplicated = await ProjectService.duplicateProject(id, `${name} (Copy)`) + if (duplicated) { + toast.success('Project duplicated successfully') + await loadProjectsList() + } + } catch (error) { + console.error('Failed to duplicate project:', error) + toast.error('Failed to duplicate project') + } + }, [loadProjectsList]) + + const handleExportProject = useCallback(async (id: string, name: string) => { + try { + const json = await ProjectService.exportProjectAsJSON(id) + if (json) { + const blob = new Blob([json], { type: 'application/json' }) + const url = URL.createObjectURL(blob) + const a = document.createElement('a') + a.href = url + a.download = `${name.replace(/[^a-z0-9]/gi, '_').toLowerCase()}.json` + document.body.appendChild(a) + a.click() + document.body.removeChild(a) + URL.revokeObjectURL(url) + toast.success('Project exported successfully') + } + } catch (error) { + console.error('Failed to export project:', error) + toast.error('Failed to export project') + } + }, []) + + const handleImportProject = useCallback(async () => { + if (!importJson.trim()) { + toast.error('Please paste project JSON') + return + } + + try { + const imported = await ProjectService.importProjectFromJSON(importJson) + if (imported) { + toast.success('Project imported successfully') + setImportDialogOpen(false) + setImportJson('') + await loadProjectsList() + } else { + toast.error('Invalid project JSON') + } + } catch (error) { + console.error('Failed to import project:', error) + toast.error('Failed to import project') + } + }, [importJson, loadProjectsList]) + + const handleNewProject = useCallback(() => { + setCurrentProjectId(null) + setProjectName('') + setProjectDescription('') + setNewProjectDialogOpen(false) + toast.success('New project started') + }, []) + + return { + projects, + isLoading, + currentProjectId, + projectName, + projectDescription, + importJson, + saveDialogOpen, + loadDialogOpen, + newProjectDialogOpen, + deleteDialogOpen, + importDialogOpen, + projectToDelete, + setSaveDialogOpen, + setLoadDialogOpen, + setNewProjectDialogOpen, + setDeleteDialogOpen, + setImportDialogOpen, + setProjectToDelete, + setProjectName, + setProjectDescription, + setImportJson, + loadProjectsList, + handleSaveProject, + handleLoadProject, + handleDeleteProject, + handleDuplicateProject, + handleExportProject, + handleImportProject, + handleNewProject, + } +}