Add done tasks to projects
This commit is contained in:
parent
62325a0c2d
commit
c74cbea888
5 changed files with 101 additions and 106 deletions
|
|
@ -121,7 +121,7 @@ Set up the necessary environment variables:
|
|||
-e TUDUDI_INTERNAL_SSL_ENABLED=false \
|
||||
-v ~/tududi_db:/usr/src/app/tududi_db \
|
||||
-p 9292:9292 \
|
||||
-d chrisvel/tududi:0.30
|
||||
-d chrisvel/tududi:latest
|
||||
```
|
||||
|
||||
3. Navigate to [https://localhost:9292](https://localhost:9292) and login with your credentials.
|
||||
|
|
|
|||
|
|
@ -15,7 +15,6 @@ import { Project } from "./entities/Project";
|
|||
import { Task } from "./entities/Task";
|
||||
import { useDataContext } from "./contexts/DataContext";
|
||||
import { User } from "./entities/User";
|
||||
import { BookOpenIcon, ClipboardIcon, FolderIcon, PlusCircleIcon, Squares2X2Icon } from "@heroicons/react/24/solid";
|
||||
|
||||
interface LayoutProps {
|
||||
currentUser: User;
|
||||
|
|
@ -75,35 +74,6 @@ const Layout: React.FC<LayoutProps> = ({
|
|||
return () => window.removeEventListener("resize", handleResize);
|
||||
}, []);
|
||||
|
||||
const [isFABDropdownOpen, setFABDropdownOpen] = useState(false);
|
||||
|
||||
const handleFABDropdownSelect = (type: string) => {
|
||||
switch (type) {
|
||||
case 'Task':
|
||||
openTaskModal();
|
||||
break;
|
||||
case 'Project':
|
||||
openProjectModal();
|
||||
break;
|
||||
case 'Note':
|
||||
openNoteModal(null);
|
||||
break;
|
||||
case 'Area':
|
||||
openAreaModal(null);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
setFABDropdownOpen(false);
|
||||
};
|
||||
|
||||
const fabDropdownItems = [
|
||||
{ label: 'Task', icon: <ClipboardIcon className="h-5 w-5 mr-2" /> },
|
||||
{ label: 'Project', icon: <FolderIcon className="h-5 w-5 mr-2" /> },
|
||||
{ label: 'Note', icon: <BookOpenIcon className="h-5 w-5 mr-2" /> },
|
||||
{ label: 'Area', icon: <Squares2X2Icon className="h-5 w-5 mr-2" /> },
|
||||
];
|
||||
|
||||
const openNoteModal = (note: Note | null = null) => {
|
||||
setSelectedNote(note);
|
||||
setIsNoteModalOpen(true);
|
||||
|
|
@ -331,30 +301,25 @@ const Layout: React.FC<LayoutProps> = ({
|
|||
|
||||
{/* Floating Action Button */}
|
||||
<button
|
||||
onClick={() => setFABDropdownOpen(!isFABDropdownOpen)}
|
||||
className="bg-blue-500 hover:bg-blue-600 text-white rounded-full p-4 shadow-lg focus:outline-none transform transition-transform duration-200 hover:scale-110"
|
||||
aria-label="Open Create New Dropdown"
|
||||
onClick={openTaskModal}
|
||||
className="fixed bottom-6 right-6 bg-blue-500 hover:bg-blue-600 text-white rounded-full p-4 shadow-lg focus:outline-none transform transition-transform duration-200 hover:scale-110"
|
||||
aria-label="Open Task Modal"
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
className="h-6 w-6"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
strokeWidth={2}
|
||||
>
|
||||
<PlusCircleIcon className="h-6 w-6" />
|
||||
</button>
|
||||
|
||||
{isFABDropdownOpen && (
|
||||
<div className="absolute right-0 mt-2 w-48 rounded-md shadow-lg bg-white dark:bg-gray-800 ring-1 ring-black ring-opacity-5 z-20">
|
||||
<ul className="py-1" role="menu" aria-orientation="vertical">
|
||||
{fabDropdownItems.map(({ label, icon }) => (
|
||||
<li
|
||||
key={label}
|
||||
className="block px-4 py-2 text-sm text-gray-700 dark:text-gray-200 hover:bg-gray-100 dark:hover:bg-gray-700 cursor-pointer flex items-center"
|
||||
onClick={() => handleFABDropdownSelect(label)}
|
||||
role="menuitem"
|
||||
>
|
||||
{icon}
|
||||
{label}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
)}
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
d="M12 4v16m8-8H4"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
{/* Modals */}
|
||||
{isTaskModalOpen && (
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
// app/frontend/components/Project/ProjectDetails.tsx
|
||||
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { useParams, useLocation, useNavigate, Link } from "react-router-dom";
|
||||
import {
|
||||
|
|
@ -12,16 +14,14 @@ import { useDataContext } from "../../contexts/DataContext";
|
|||
import NewTask from "../Task/NewTask";
|
||||
import { Project } from "../../entities/Project";
|
||||
import { Task } from "../../entities/Task";
|
||||
import useManageTasks from "../../hooks/useManageTasks";
|
||||
|
||||
const ProjectDetails: React.FC = () => {
|
||||
const { updateTask, deleteTask } = useManageTasks();
|
||||
const { updateTask, deleteTask, updateProject, deleteProject } = useDataContext();
|
||||
const { id } = useParams<{ id: string }>();
|
||||
const location = useLocation();
|
||||
const navigate = useNavigate();
|
||||
|
||||
const { areas } = useDataContext();
|
||||
const { updateProject, deleteProject } = useDataContext();
|
||||
|
||||
const [project, setProject] = useState<Project>();
|
||||
const [tasks, setTasks] = useState<Task[]>([]);
|
||||
|
|
@ -34,6 +34,9 @@ const ProjectDetails: React.FC = () => {
|
|||
const projectTitle = stateTitle || project?.name || "Project";
|
||||
const projectIcon = stateIcon || "bi-folder-fill";
|
||||
|
||||
// State for Collapsible Completed Tasks
|
||||
const [isCompletedOpen, setIsCompletedOpen] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
const fetchProject = async () => {
|
||||
try {
|
||||
|
|
@ -58,7 +61,7 @@ const ProjectDetails: React.FC = () => {
|
|||
fetchProject();
|
||||
}, [id]);
|
||||
|
||||
const handleTaskCreate = async (taskData: Partial<any>) => {
|
||||
const handleTaskCreate = async (taskData: Partial<Task>) => {
|
||||
if (!project?.id) {
|
||||
console.error("Project ID is missing");
|
||||
return;
|
||||
|
|
@ -166,9 +169,18 @@ const ProjectDetails: React.FC = () => {
|
|||
);
|
||||
}
|
||||
|
||||
// Separate tasks into active and completed
|
||||
const activeTasks = tasks.filter(task => task.status !== 'done');
|
||||
const completedTasks = tasks.filter(task => task.status === 'done');
|
||||
|
||||
const toggleCompleted = () => {
|
||||
setIsCompletedOpen(!isCompletedOpen);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex justify-center px-4 lg:px-2 ">
|
||||
<div className="flex justify-center px-4 lg:px-2">
|
||||
<div className="w-full max-w-5xl">
|
||||
{/* Project Header */}
|
||||
<div className="flex items-center justify-between mb-8">
|
||||
<div className="flex items-center">
|
||||
<i className={`bi ${projectIcon} text-xl mr-2`}></i>
|
||||
|
|
@ -193,6 +205,7 @@ const ProjectDetails: React.FC = () => {
|
|||
</div>
|
||||
</div>
|
||||
|
||||
{/* 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" />
|
||||
|
|
@ -205,31 +218,81 @@ const ProjectDetails: React.FC = () => {
|
|||
</div>
|
||||
)}
|
||||
|
||||
{/* Project Description */}
|
||||
{project?.description && (
|
||||
<p className="text-gray-700 dark:text-gray-300 mb-6">
|
||||
{project.description}
|
||||
</p>
|
||||
)}
|
||||
|
||||
{/* Create a new task for this project */}
|
||||
{/* New Task Form */}
|
||||
<NewTask
|
||||
onTaskCreate={(taskName: string) =>
|
||||
handleTaskCreate({
|
||||
name: taskName,
|
||||
status: "not_started",
|
||||
project: project,
|
||||
project_id: project?.id, // Ensure project_id is correctly assigned
|
||||
})
|
||||
}
|
||||
/>
|
||||
|
||||
<TaskList
|
||||
tasks={tasks}
|
||||
onTaskCreate={handleTaskCreate}
|
||||
onTaskUpdate={handleTaskUpdate}
|
||||
onTaskDelete={handleTaskDelete}
|
||||
projects={project ? [project] : []}
|
||||
/>
|
||||
{/* Active Tasks */}
|
||||
<div className="mt-2">
|
||||
{activeTasks.length > 0 ? (
|
||||
<TaskList
|
||||
tasks={activeTasks}
|
||||
onTaskUpdate={handleTaskUpdate}
|
||||
onTaskDelete={handleTaskDelete}
|
||||
projects={project ? [project] : []}
|
||||
/>
|
||||
) : (
|
||||
<p className="text-gray-500 dark:text-gray-400">No active tasks.</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Collapsible Completed Tasks */}
|
||||
<div className="mt-6">
|
||||
<button
|
||||
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>
|
||||
<svg
|
||||
className={`w-6 h-6 transform transition-transform duration-200 ${
|
||||
isCompletedOpen ? "rotate-180" : "rotate-0"
|
||||
}`}
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth={2}
|
||||
d="M19 9l-7 7-7-7"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
{isCompletedOpen && (
|
||||
<div className="mt-4">
|
||||
{completedTasks.length > 0 ? (
|
||||
<TaskList
|
||||
tasks={completedTasks}
|
||||
onTaskUpdate={handleTaskUpdate}
|
||||
onTaskDelete={handleTaskDelete}
|
||||
projects={project ? [project] : []}
|
||||
/>
|
||||
) : (
|
||||
<p className="text-gray-500 dark:text-gray-400">
|
||||
No completed tasks.
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Modals */}
|
||||
<ProjectModal
|
||||
isOpen={isModalOpen}
|
||||
onClose={() => setIsModalOpen(false)}
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ import { Task } from '../../entities/Task';
|
|||
interface TaskListProps {
|
||||
tasks: Task[];
|
||||
onTaskUpdate: (task: Task) => void;
|
||||
onTaskCreate: (task: Task) => void;
|
||||
onTaskCreate?: (task: Task) => void;
|
||||
onTaskDelete: (taskId: number) => void;
|
||||
projects: Project[];
|
||||
}
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
Loading…
Add table
Add a link
Reference in a new issue