import React, { useState, useEffect } from 'react'; import { MagnifyingGlassIcon, FolderIcon, Squares2X2Icon, Bars3Icon, } from '@heroicons/react/24/solid'; import ConfirmDialog from './Shared/ConfirmDialog'; import ProjectModal from './Project/ProjectModal'; import { useStore } from '../store/useStore'; import { fetchGroupedProjects, createProject, updateProject, deleteProject, } from '../utils/projectsService'; import { fetchAreas } from '../utils/areasService'; import { useTranslation } from 'react-i18next'; import { Project } from '../entities/Project'; import { PriorityType } from '../entities/Task'; import { useSearchParams } from 'react-router-dom'; import ProjectItem from './Project/ProjectItem'; const getPriorityStyles = (priority: PriorityType) => { switch (priority) { case 'low': return { color: 'bg-green-500' }; case 'medium': return { color: 'bg-yellow-500' }; case 'high': return { color: 'bg-red-500' }; default: return { color: 'bg-gray-500' }; } }; const Projects: React.FC = () => { const { t } = useTranslation(); const { areas, setAreas, setError: setAreasError, } = useStore((state) => state.areasStore); const { projects, setProjects, setLoading: setProjectsLoading, setError: setProjectsError, } = useStore((state) => state.projectsStore); const { isLoading, isError } = useStore((state) => state.projectsStore); const [groupedProjects, setGroupedProjects] = useState< Record >({}); const [isProjectModalOpen, setIsProjectModalOpen] = useState(false); const [projectToEdit, setProjectToEdit] = useState(null); const [projectToDelete, setProjectToDelete] = useState( null ); const [isConfirmDialogOpen, setIsConfirmDialogOpen] = useState(false); const [activeDropdown, setActiveDropdown] = useState(null); const [searchQuery, setSearchQuery] = useState(''); const [viewMode, setViewMode] = useState<'cards' | 'list'>('cards'); const [searchParams, setSearchParams] = useSearchParams(); const activeFilter = searchParams.get('active') || 'all'; const areaFilter = searchParams.get('area_id') || ''; useEffect(() => { const loadAreas = async () => { try { const areasData = await fetchAreas(); setAreas(areasData); } catch (error) { console.error('Failed to fetch areas:', error); setAreasError(true); } }; loadAreas(); }, []); useEffect(() => { const loadProjects = async () => { try { const groupedProjectsData = await fetchGroupedProjects( activeFilter, areaFilter ); setGroupedProjects(groupedProjectsData); } catch (error) { console.error('Failed to fetch projects:', error); setProjectsError(true); } }; loadProjects(); }, [activeFilter, areaFilter]); const handleSaveProject = async (project: Project) => { setProjectsLoading(true); try { if (project.id) { await updateProject(project.id, project); } else { await createProject(project); } const groupedProjectsData = await fetchGroupedProjects( activeFilter, areaFilter ); setGroupedProjects(groupedProjectsData); } catch (error) { console.error('Error saving project:', error); setProjectsError(true); } finally { setProjectsLoading(false); setIsProjectModalOpen(false); } }; const handleEditProject = (project: Project) => { setProjectToEdit(project); setIsProjectModalOpen(true); }; const handleDeleteProject = async () => { if (!projectToDelete) return; try { if (projectToDelete.id !== undefined) { setProjectsLoading(true); await deleteProject(projectToDelete.id); const groupedProjectsData = await fetchGroupedProjects( activeFilter, areaFilter ); setGroupedProjects(groupedProjectsData); } else { console.error('Cannot delete project: ID is undefined.'); } } catch (error) { console.error('Error deleting project:', error); setProjectsError(true); } finally { setProjectsLoading(false); setIsConfirmDialogOpen(false); setProjectToDelete(null); } }; const getCompletionPercentage = (project: Project) => { // Now the completion percentage comes directly from the backend return (project as any).completion_percentage || 0; }; const handleActiveFilterChange = ( e: React.ChangeEvent ) => { const newActiveFilter = e.target.value; const params = new URLSearchParams(searchParams); if (newActiveFilter === 'all') { params.delete('active'); } else { params.set('active', newActiveFilter); } setSearchParams(params); }; const handleAreaFilterChange = ( e: React.ChangeEvent ) => { const newAreaFilter = e.target.value; const params = new URLSearchParams(searchParams); if (newAreaFilter === '') { params.delete('area_id'); } else { params.set('area_id', newAreaFilter); } setSearchParams(params); }; // Apply search filter to the grouped projects from backend const searchFilteredGroupedProjects = Object.keys(groupedProjects).reduce< Record >((acc, areaName) => { const projectsInArea = groupedProjects[areaName]; // Defensive check: ensure projectsInArea is an array if (!Array.isArray(projectsInArea)) { console.warn( `Projects for area "${areaName}" is not an array:`, projectsInArea ); return acc; } const filteredProjects = projectsInArea.filter((project) => project.name.toLowerCase().includes(searchQuery.toLowerCase()) ); if (filteredProjects.length > 0) { acc[areaName] = filteredProjects; } return acc; }, {}); if (isLoading) { return (
{t('projects.loading')}
); } if (isError) { return (
{t('projects.error')}
); } return (

{t('projects.title')}

{/* View Mode and Filters */}
{/* Search Bar */}
setSearchQuery(e.target.value)} className="w-full bg-transparent border-none focus:ring-0 focus:outline-none dark:text-white" />
{/* Projects Grid/List */}
{Object.keys(searchFilteredGroupedProjects).length === 0 ? (
{t('projects.noProjectsFound')}
) : ( Object.keys(searchFilteredGroupedProjects).map( (areaName) => (

{areaName}

{searchFilteredGroupedProjects[ areaName ].map((project) => { const { color } = getPriorityStyles( project.priority || 'low' ); return ( getCompletionPercentage( project ) } activeDropdown={activeDropdown} setActiveDropdown={ setActiveDropdown } handleEditProject={ handleEditProject } setProjectToDelete={ setProjectToDelete } setIsConfirmDialogOpen={ setIsConfirmDialogOpen } /> ); })}
) ) )}
{isProjectModalOpen && ( { setIsProjectModalOpen(false); setProjectToEdit(null); }} onSave={handleSaveProject} onDelete={async (projectId) => { try { await deleteProject(projectId); setProjects( projects.filter( (p: Project) => p.id !== projectId ) ); setIsProjectModalOpen(false); setProjectToEdit(null); } catch (error) { console.error('Error deleting project:', error); } }} project={projectToEdit || undefined} areas={areas} /> )} {isConfirmDialogOpen && ( setIsConfirmDialogOpen(false)} /> )}
); }; export default Projects;