tududi/app/frontend/components/Project/ProjectDetails.tsx
Chris Veleris dfcb97a355 Move to React
Add .gitignore

Removed node_modules from previous commit

Fix task modes

Fix task modes

Fix task modes

Remove node_modules

Update basic task modal

Add notes functionality

Improve UI

Setup views

Add scopes

Fix projects layout

Restructure

Fix rest of the UI issues

Cleanup old views

Add .env to .gitignore
2024-10-25 21:03:43 +03:00

205 lines
6.4 KiB
TypeScript

import React, { useEffect, useState } from 'react';
import { useParams, useLocation, useNavigate, Link } from 'react-router-dom';
import { PencilSquareIcon, TrashIcon, Squares2X2Icon } from '@heroicons/react/24/solid';
import NewTask from '../../NewTask';
import TaskList from '../Task/TaskList';
import ProjectModal from '../Project/ProjectModal';
import ConfirmDialog from '../Shared/ConfirmDialog';
import { useDataContext } from '../../contexts/DataContext';
const ProjectDetails: React.FC = () => {
const { id } = useParams<{ id: string }>();
const location = useLocation();
const navigate = useNavigate();
const { areas, createArea, updateArea, deleteArea } = useDataContext();
const { projects, updateProject, deleteProject } = useDataContext();
const [project, setProject] = useState<any>(null);
const [tasks, setTasks] = useState<any[]>([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
const [isModalOpen, setIsModalOpen] = useState(false);
const [isConfirmDialogOpen, setIsConfirmDialogOpen] = useState(false);
const { title: stateTitle, icon: stateIcon } = location.state || {};
const projectTitle = stateTitle || project?.name || 'Project';
const projectIcon = stateIcon || 'bi-folder-fill';
useEffect(() => {
const fetchProject = async () => {
try {
const response = await fetch(`/api/project/${id}`, {
credentials: 'include',
headers: { Accept: 'application/json' },
});
const data = await response.json();
if (response.ok) {
setProject(data);
setTasks(data.tasks || []);
} else {
throw new Error(data.error || 'Failed to fetch project.');
}
} catch (error) {
setError((error as Error).message);
} finally {
setLoading(false);
}
};
fetchProject();
}, [id]);
const handleTaskCreate = async (taskData: Partial<any>) => {
if (!project?.id) {
console.error('Project ID is missing');
return;
}
// Add the project_id to the taskData payload
const taskPayload = {
...taskData,
project_id: project.id,
};
try {
const response = await fetch(`/api/task`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Accept: 'application/json',
},
credentials: 'include',
body: JSON.stringify(taskPayload),
});
const newTask = await response.json();
if (response.ok) {
setTasks([...tasks, newTask]);
} else {
throw new Error(newTask.error || 'Failed to create task');
}
} catch (err) {
console.error('Error creating task:', err);
}
};
const handleTaskUpdate = async (updatedTask: any) => {
// Simulated function for task update
};
const handleTaskDelete = async (taskId: number) => {
// Simulated function for task deletion
};
const handleEditProject = () => {
setIsModalOpen(true);
};
const handleSaveProject = async (updatedProject: any) => {
try {
await updateProject(updatedProject.id, updatedProject);
setProject(updatedProject);
setIsModalOpen(false);
} catch (err) {
console.error('Error saving project:', err);
}
};
const handleDeleteProject = async () => {
if (!project) return;
try {
await deleteProject(project.id);
navigate('/projects');
} catch (err) {
console.error('Error deleting project:', err);
}
};
if (loading) {
return (
<div className="flex items-center justify-center h-screen bg-gray-100 dark:bg-gray-900">
<div className="text-xl font-semibold text-gray-700 dark:text-gray-200">
Loading project details...
</div>
</div>
);
}
if (error) {
return (
<div className="flex items-center justify-center h-screen bg-gray-100 dark:bg-gray-900">
<div className="text-red-500 text-lg">{error}</div>
</div>
);
}
return (
<div className="flex justify-center px-4">
<div className="w-full max-w-4xl">
<div className="flex items-center justify-between mb-8">
<div className="flex items-center">
<i className={`bi ${projectIcon} text-xl mr-2`}></i>
<h2 className="text-2xl font-light text-gray-900 dark:text-gray-100">{projectTitle}</h2>
</div>
<div className="flex space-x-2">
<button
onClick={handleEditProject}
className="text-gray-500 hover:text-blue-700 dark:hover:text-blue-300 focus:outline-none"
>
<PencilSquareIcon className="h-5 w-5" />
</button>
<button
onClick={() => setIsConfirmDialogOpen(true)}
className="text-gray-500 hover:text-red-700 dark:hover:text-red-300 focus:outline-none"
>
<TrashIcon className="h-5 w-5" />
</button>
</div>
</div>
{project?.area && (
<div className="flex items-center mb-4">
<Squares2X2Icon className="h-5 w-5 text-gray-500 dark:text-gray-400 mr-2" />
<Link
to={`/projects/?area_id=${project?.area.id}`}
className="text-gray-600 dark:text-gray-400 hover:underline"
>
{project.area.name.toUpperCase()}
</Link>
</div>
)}
{project?.description && (
<p className="text-gray-700 dark:text-gray-300 mb-6">{project.description}</p>
)}
{/* Create a new task for this project */}
<NewTask onTaskCreate={(taskName: string) => handleTaskCreate({ name: taskName, status: 'not_started', project: project })} />
<TaskList tasks={tasks} onTaskCreate={handleTaskCreate} onTaskUpdate={handleTaskUpdate} onTaskDelete={handleTaskDelete} projects={[project]} />
<ProjectModal
isOpen={isModalOpen}
onClose={() => setIsModalOpen(false)}
onSave={handleSaveProject}
project={project || undefined}
areas={areas}
/>
{isConfirmDialogOpen && (
<ConfirmDialog
title="Delete Project"
message={`Are you sure you want to delete the project "${project?.name}"?`}
onConfirm={handleDeleteProject}
onCancel={() => setIsConfirmDialogOpen(false)}
/>
)}
</div>
</div>
);
};
export default ProjectDetails;