import { mdiChartTimelineVariant } from '@mdi/js'; import axios from 'axios'; import Head from 'next/head'; import { useRouter } from 'next/router'; import React, { ReactElement, useEffect, useState } from 'react'; import { toast, ToastContainer } from 'react-toastify'; import BaseButton from '../../components/BaseButton'; import CardBox from '../../components/CardBox'; import LayoutAuthenticated from '../../layouts/Authenticated'; import SectionMain from '../../components/SectionMain'; import SectionTitleLineWithButton from '../../components/SectionTitleLineWithButton'; import { getPageTitle } from '../../config'; import { useAppDispatch, useAppSelector } from '../../stores/hooks'; import { fetch as fetchProjects, create as createProject, } from '../../stores/projects/projectsSlice'; import type { Project } from '../../types/entities'; import { logger } from '../../lib/logger'; const ProjectsListPage = () => { const router = useRouter(); const dispatch = useAppDispatch(); const projectsRaw = useAppSelector((state) => state.projects.projects) as | Project[] | Project | undefined; // Handle both array (from list fetch) and single object (after edit fetch) const projects: Project[] = Array.isArray(projectsRaw) ? projectsRaw : projectsRaw ? [projectsRaw] : []; const isLoading = useAppSelector((state) => state.projects.loading); const [isCreating, setIsCreating] = useState(false); const [isCloning, setIsCloning] = useState(false); const [isCloneOpen, setIsCloneOpen] = useState(false); const [cloneSourceId, setCloneSourceId] = useState(''); useEffect(() => { dispatch( fetchProjects({ query: '?limit=100&page=0&sort=desc&field=updatedAt' }), ); }, [dispatch]); useEffect(() => { if (projects.length > 0 && !cloneSourceId) { setCloneSourceId(projects[0].id); } }, [projects, cloneSourceId]); const buildNewProjectDraft = (): Partial => { const stamp = Date.now(); return { name: `New Project ${new Date(stamp).toISOString().slice(0, 16).replace('T', ' ')}`, slug: `project-${stamp}`, description: '', }; }; const handleOpenProjectAssets = async (projectId: string) => { await router.push(`/projects/${projectId}`); }; const handleCreateNewProject = async () => { setIsCreating(true); try { const result = await dispatch( createProject(buildNewProjectDraft()), ).unwrap(); const createdId = result?.id; if (!createdId) { throw new Error('Project was created but id is missing in response'); } await router.push(`/projects/${createdId}`); } catch (error: unknown) { const errorMessage = error instanceof Error ? error.message : 'Unknown error'; logger.error( 'Failed to create project:', error instanceof Error ? error : { error: errorMessage }, ); toast('Failed to create project', { type: 'error', position: 'bottom-center', }); } finally { setIsCreating(false); } }; const handleCloneProject = async () => { if (!cloneSourceId) { toast('Select source project first', { type: 'warning', position: 'bottom-center', }); return; } setIsCloning(true); try { const response = await axios.post(`/projects/${cloneSourceId}/clone`); const createdId = response?.data?.id; if (!createdId) { throw new Error('Cloned project id is missing in response'); } await router.push(`/projects/${createdId}`); } catch (error: unknown) { const errorMessage = error instanceof Error ? error.message : 'Unknown error'; logger.error( 'Failed to clone project:', error instanceof Error ? error : { error: errorMessage }, ); toast('Failed to clone project', { type: 'error', position: 'bottom-center', }); } finally { setIsCloning(false); } }; return ( <> {getPageTitle('Projects')} {''} setIsCloneOpen((prev) => !prev)} disabled={isCreating || isCloning || projects.length === 0} /> {isCloneOpen && (
)}
{!isLoading && projects.map((project) => ( ))} {!isLoading && projects.length === 0 && (

No projects found.

)}
); }; ProjectsListPage.getLayout = function getLayout(page: ReactElement) { return ( {page} ); }; export default ProjectsListPage;