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
205 lines
6.4 KiB
TypeScript
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;
|