Skip to content

Commit

Permalink
Fix project issues with areas and active filter
Browse files Browse the repository at this point in the history
  • Loading branch information
chrisvel committed Dec 16, 2024
1 parent 768e5cb commit 42aa10b
Show file tree
Hide file tree
Showing 7 changed files with 373 additions and 195 deletions.
92 changes: 67 additions & 25 deletions app/frontend/components/Project/ProjectDetails.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,16 @@ import ConfirmDialog from "../Shared/ConfirmDialog";
import { useDataContext } from "../../contexts/DataContext";
import NewTask from "../Task/NewTask";
import { Project } from "../../entities/Project";
import { Task } from "../../entities/Task";
import { PriorityType, Task } from "../../entities/Task";

type PriorityStyles = Record<PriorityType, string> & { default: string };

const priorityStyles: PriorityStyles = {
high: 'bg-red-500',
medium: 'bg-yellow-500',
low: 'bg-green-500',
default: 'bg-gray-400',
};

const ProjectDetails: React.FC = () => {
const { updateTask, deleteTask, updateProject, deleteProject } = useDataContext();
Expand All @@ -21,7 +30,7 @@ const ProjectDetails: React.FC = () => {

const { areas } = useDataContext();

const [project, setProject] = useState<Project>();
const [project, setProject] = useState<Project | undefined>(undefined);
const [tasks, setTasks] = useState<Task[]>([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
Expand Down Expand Up @@ -56,14 +65,15 @@ const ProjectDetails: React.FC = () => {
fetchProject();
}, [id]);

const handleTaskCreate = async (taskData: Partial<Task>) => {
if (!project?.id) {
console.error("Project ID is missing");
const handleTaskCreate = async (taskName: string) => {
if (!project || project.id === undefined) {
console.error("Cannot create task: Project or Project ID is missing");
return;
}

const taskPayload = {
...taskData,
name: taskName,
status: "not_started",
project_id: project.id,
};

Expand Down Expand Up @@ -124,10 +134,13 @@ const ProjectDetails: React.FC = () => {
};

const handleSaveProject = async (updatedProject: Project) => {
if (!updatedProject) return;
if (!updatedProject || updatedProject.id === undefined) {
console.error("Cannot save project: Project or Project ID is missing");
return;
}

try {
const savedProject = await updateProject(updatedProject.id!, updatedProject);
const savedProject = await updateProject(updatedProject.id, updatedProject);
setProject(savedProject);
setIsModalOpen(false);
} catch (err) {
Expand All @@ -136,10 +149,13 @@ const ProjectDetails: React.FC = () => {
};

const handleDeleteProject = async () => {
if (!project) return;
if (!project || project.id === undefined) {
console.error("Cannot delete project: Project or Project ID is missing");
return;
}

try {
await deleteProject(project.id!);
await deleteProject(project.id);
navigate("/projects");
} catch (err) {
console.error("Error deleting project:", err);
Expand All @@ -164,6 +180,14 @@ const ProjectDetails: React.FC = () => {
);
}

if (!project) {
return (
<div className="flex items-center justify-center h-screen bg-gray-100 dark:bg-gray-900">
<div className="text-red-500 text-lg">Project not found.</div>
</div>
);
}

const activeTasks = tasks.filter(task => task.status !== 'done');
const completedTasks = tasks.filter(task => task.status === 'done');

Expand All @@ -177,19 +201,29 @@ const ProjectDetails: React.FC = () => {
{/* Project Header */}
<div className="flex items-center justify-between mb-8">
<div className="flex items-center">
<FolderIcon className="h-6 w-6 text-gray-500 mr-2" />
<h2 className="text-2xl font-light text-gray-900 dark:text-gray-100">
<FolderIcon className="h-6 w-6 text-gray-500 mr-2" />
<h2 className="text-2xl font-light text-gray-900 dark:text-gray-100 mr-2">
{projectTitle}
</h2>
{/* Priority Circle placed after the title */}
{project.priority && (
<div
className={`w-4 h-4 rounded-full border-2 border-white dark:border-gray-800 ${priorityStyles[project.priority] || priorityStyles.default}`}
title={`Priority: ${priorityLabel(project.priority)}`}
aria-label={`Priority: ${priorityLabel(project.priority)}`}
></div>
)}
</div>
<div className="flex space-x-2">
{/* Edit Project Button */}
<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>

{/* Delete Project Button */}
<button
onClick={() => setIsConfirmDialogOpen(true)}
className="text-gray-500 hover:text-red-700 dark:hover:text-red-300 focus:outline-none"
Expand All @@ -200,11 +234,11 @@ const ProjectDetails: React.FC = () => {
</div>

{/* Project Area */}
{project?.area && (
{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}`}
to={`/projects/?area_id=${project.area.id}`}
className="text-gray-600 dark:text-gray-400 hover:underline"
>
{project.area.name.toUpperCase()}
Expand All @@ -213,21 +247,15 @@ const ProjectDetails: React.FC = () => {
)}

{/* Project Description */}
{project?.description && (
{project.description && (
<p className="text-gray-700 dark:text-gray-300 mb-6">
{project.description}
</p>
)}

{/* New Task Form */}
<NewTask
onTaskCreate={(taskName: string) =>
handleTaskCreate({
name: taskName,
status: "not_started",
project_id: project?.id,
})
}
onTaskCreate={handleTaskCreate}
/>

{/* Active Tasks */}
Expand All @@ -250,7 +278,7 @@ const ProjectDetails: React.FC = () => {
onClick={toggleCompleted}
className="flex items-center justify-between w-full px-4 py-2 bg-gray-200 dark:bg-gray-700 rounded-md focus:outline-none"
>
<span className="text-xl font-semibold">Completed Tasks</span>
<span className="text-sm uppercase font-medium">Completed Tasks</span>
<svg
className={`w-6 h-6 transform transition-transform duration-200 ${
isCompletedOpen ? "rotate-180" : "rotate-0"
Expand Down Expand Up @@ -291,14 +319,15 @@ const ProjectDetails: React.FC = () => {
isOpen={isModalOpen}
onClose={() => setIsModalOpen(false)}
onSave={handleSaveProject}
project={project || undefined}
project={project}
areas={areas}
/>

{/* Confirm Delete Dialog */}
{isConfirmDialogOpen && (
<ConfirmDialog
title="Delete Project"
message={`Are you sure you want to delete the project "${project?.name}"?`}
message={`Are you sure you want to delete the project "${project.name}"?`}
onConfirm={handleDeleteProject}
onCancel={() => setIsConfirmDialogOpen(false)}
/>
Expand All @@ -308,4 +337,17 @@ const ProjectDetails: React.FC = () => {
);
};

const priorityLabel = (priority: PriorityType) => {
switch (priority) {
case 'high':
return 'High';
case 'medium':
return 'Medium';
case 'low':
return 'Low';
default:
return '';
}
};

export default ProjectDetails;
137 changes: 137 additions & 0 deletions app/frontend/components/Project/ProjectItem.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
import React from "react";
import { Link } from "react-router-dom";
import { EllipsisVerticalIcon } from "@heroicons/react/24/solid";
import { Project } from "../../entities/Project";

interface ProjectItemProps {
project: Project;
viewMode: "cards" | "list";
color: string;
getCompletionPercentage: (projectId: number | undefined) => number;
activeDropdown: number | null;
setActiveDropdown: React.Dispatch<React.SetStateAction<number | null>>;
handleEditProject: (project: Project) => void;
setProjectToDelete: React.Dispatch<React.SetStateAction<Project | null>>;
setIsConfirmDialogOpen: React.Dispatch<React.SetStateAction<boolean>>;
}

const getProjectInitials = (name: string) => {
const words = name
.trim()
.split(" ")
.filter((word) => word.length > 0);
if (words.length === 1) {
return name.toUpperCase();
}
return words.map((word) => word[0].toUpperCase()).join("");
};

const ProjectItem: React.FC<ProjectItemProps> = ({
project,
viewMode,
color,
getCompletionPercentage,
activeDropdown,
setActiveDropdown,
handleEditProject,
setProjectToDelete,
setIsConfirmDialogOpen,
}) => {
return (
<div
className={`${
viewMode === "cards"
? "bg-gray-50 dark:bg-gray-900 rounded-lg shadow-md relative flex flex-col"
: "bg-gray-50 dark:bg-gray-900 rounded-lg shadow-md relative flex flex-row items-center p-4"
}`}
style={{
minHeight: viewMode === "cards" ? "250px" : "auto",
maxHeight: viewMode === "cards" ? "250px" : "auto",
}}
>
{viewMode === "cards" && (
<div
className="bg-gray-200 dark:bg-gray-700 flex items-center justify-center overflow-hidden rounded-t-lg"
style={{ height: "140px" }}
>
<span className="text-2xl font-extrabold text-gray-500 dark:text-gray-400 opacity-20">
{getProjectInitials(project.name)}
</span>
<div
className={`absolute top-2 left-2 w-3 h-3 rounded-full ${color}`}
></div>
</div>
)}

<div
className={`flex justify-between items-start ${
viewMode === "cards" ? "p-4 flex-1" : "flex-1"
}`}
>
<Link
to={`/project/${project.id}`}
className={`${
viewMode === "cards"
? "text-lg font-semibold text-gray-900 dark:text-gray-100 hover:underline line-clamp-2"
: "text-md font-semibold text-gray-900 dark:text-gray-100 hover:underline"
}`}
>
{project.name}
</Link>
<div className="relative">
<button
className="text-gray-500 hover:text-gray-700 dark:text-gray-300 dark:hover:text-gray-400 focus:outline-none"
onClick={() =>
setActiveDropdown(
activeDropdown === project.id ? null : project.id ?? null
)
}
>
<EllipsisVerticalIcon className="h-5 w-5" />
</button>

{activeDropdown === project.id && (
<div className="absolute right-0 mt-2 w-28 bg-white dark:bg-gray-700 shadow-lg rounded-md z-10">
<button
onClick={() => handleEditProject(project)}
className="block px-4 py-2 text-sm text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-600 w-full text-left"
>
Edit
</button>
<button
onClick={() => {
setProjectToDelete(project);
setIsConfirmDialogOpen(true);
setActiveDropdown(null);
}}
className="block px-4 py-2 text-sm text-red-500 dark:text-red-300 hover:bg-gray-100 dark:hover:bg-gray-600 w-full text-left"
>
Delete
</button>
</div>
)}
</div>
</div>

{viewMode === "cards" && (
<div className="absolute bottom-4 left-0 right-0 px-4">
<div className="flex items-center space-x-2">
<div className="w-full bg-gray-200 dark:bg-gray-700 rounded-full h-2">
<div
className="bg-blue-500 h-2 rounded-full"
style={{
width: `${getCompletionPercentage(project?.id)}%`,
}}
></div>
</div>
<span className="text-xs text-gray-500 dark:text-gray-400">
{getCompletionPercentage(project?.id)}%
</span>
</div>
</div>
)}
</div>
);
};

export default ProjectItem;
Loading

0 comments on commit 42aa10b

Please sign in to comment.