Skip to content

Commit

Permalink
Fix projects dropdown in tasks modal
Browse files Browse the repository at this point in the history
  • Loading branch information
chrisvel committed Nov 12, 2024
1 parent 7cd1d1b commit cc3ad37
Show file tree
Hide file tree
Showing 8 changed files with 150 additions and 83 deletions.
26 changes: 15 additions & 11 deletions app/frontend/Layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ const Layout: React.FC<LayoutProps> = ({
tags,
areas,
notes,
projects,
isLoading,
isError,
createNote,
Expand Down Expand Up @@ -172,6 +173,16 @@ const Layout: React.FC<LayoutProps> = ({
closeProjectModal();
};

const handleCreateProject = async (name: string): Promise<Project> => {
try {
const newProject = await createProject({ name });
return newProject;
} catch (error) {
console.error("Error creating project:", error);
throw error;
}
};

const handleSaveArea = async (areaData: Area) => {
try {
if (areaData.id) {
Expand Down Expand Up @@ -338,23 +349,16 @@ const Layout: React.FC<LayoutProps> = ({
task={
newTask || {
id: undefined,
name: '',
status: 'not_started',
name: "",
status: "not_started",
project_id: undefined,
tags: [],
}
}
onSave={handleSaveTask}
onDelete={() => {}}
projects={[]} // Provide project list as necessary
onCreateProject={async (name: string) => {
return {
id: Math.random(),
name,
active: true,
pin_to_sidebar: false,
}; // Ensure all required fields are covered
}}
projects={projects}
onCreateProject={handleCreateProject}
/>
)}

Expand Down
40 changes: 17 additions & 23 deletions app/frontend/components/Projects.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,37 +26,30 @@ const Projects: React.FC = () => {
const [projectToDelete, setProjectToDelete] = useState<Project | null>(null);
const [isConfirmDialogOpen, setIsConfirmDialogOpen] = useState<boolean>(false);
const [activeDropdown, setActiveDropdown] = useState<number | null>(null);
const [searchQuery, setSearchQuery] = useState<string>('');
const [searchQuery, setSearchQuery] = useState<string>("");

const [searchParams, setSearchParams] = useSearchParams();

// Set default URL parameter ?active=true if not provided
useEffect(() => {
if (!searchParams.has("active")) {
searchParams.set("active", "true");
setSearchParams(searchParams);
}
}, [searchParams, setSearchParams]);

const activeFilter = searchParams.get("active") ?? "active";
const areaFilter = searchParams.get("area_id") ?? "";
const activeFilter = searchParams.get("active") || "all";
const areaFilter = searchParams.get("area_id") || "";

const {
projects,
taskStatusCounts: fetchedTaskStatusCounts,
isLoading,
isError,
mutate,
} = useFetchProjects(activeFilter, areaFilter);
} = useFetchProjects({ activeFilter, areaFilter });

useEffect(() => {
setTaskStatusCounts(fetchedTaskStatusCounts);
}, [fetchedTaskStatusCounts]);
setTaskStatusCounts(fetchedTaskStatusCounts || {});
}, [fetchedTaskStatusCounts]);

const getCompletionPercentage = (projectId: number | undefined) => {
if (!projectId) return 0;
const taskStatus = taskStatusCounts[projectId] || {};
const totalTasks = (taskStatus.done || 0) + (taskStatus.not_started || 0) + (taskStatus.in_progress || 0);
const totalTasks =
(taskStatus.done || 0) + (taskStatus.not_started || 0) + (taskStatus.in_progress || 0);

if (totalTasks === 0) return 0;

Expand Down Expand Up @@ -102,11 +95,13 @@ const Projects: React.FC = () => {
const handleAreaFilterChange = (e: React.ChangeEvent<HTMLSelectElement>) => {
const newAreaFilter = e.target.value;
const params = new URLSearchParams(searchParams);
if (newAreaFilter) {
params.set("area_id", newAreaFilter);
} else {

if (newAreaFilter === "") {
params.delete("area_id");
} else {
params.set("area_id", newAreaFilter);
}

setSearchParams(params);
};

Expand Down Expand Up @@ -163,7 +158,7 @@ const Projects: React.FC = () => {
</label>
<select
id="activeFilter"
value={activeFilter || "all"}
value={activeFilter}
onChange={handleActiveFilterChange}
className="block w-full p-2 border border-gray-300 dark:border-gray-700 bg-white dark:bg-gray-800 text-gray-700 dark:text-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
>
Expand Down Expand Up @@ -196,7 +191,7 @@ const Projects: React.FC = () => {
</div>
</div>

{/* Search Bar with Icon */}
{/* Search Bar with Icon */}
<div className="mb-4">
<div className="flex items-center bg-white dark:bg-gray-800 border border-gray-300 dark:border-gray-700 rounded-md shadow-sm p-2">
<MagnifyingGlassIcon className="h-5 w-5 text-gray-500 dark:text-gray-400 mr-2" />
Expand Down Expand Up @@ -227,7 +222,7 @@ const Projects: React.FC = () => {
<div
key={project.id}
className="bg-gray-50 dark:bg-gray-900 rounded-lg shadow-md relative"
style={{ minHeight: "280px", maxHeight: "280px" }} // Consistent card height
style={{ minHeight: "280px", maxHeight: "280px" }}
>
<div
className="bg-gray-200 dark:bg-gray-700 flex items-center justify-center overflow-hidden rounded-t-lg"
Expand All @@ -242,7 +237,7 @@ const Projects: React.FC = () => {
<Link
to={`/project/${project.id}`}
className="text-lg font-semibold text-gray-900 dark:text-gray-100 hover:underline line-clamp-2"
style={{ minHeight: "3.3rem", maxHeight: "3.3rem" }} // Fixed title height
style={{ minHeight: "3.3rem", maxHeight: "3.3rem" }}
>
{project.name}
</Link>
Expand Down Expand Up @@ -333,4 +328,3 @@ const Projects: React.FC = () => {
};

export default Projects;

6 changes: 2 additions & 4 deletions app/frontend/components/Sidebar/SidebarProjects.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import React, { useState, useEffect } from 'react';
import React from 'react';
import { Location } from 'react-router-dom';
import { FolderIcon, PlusCircleIcon } from '@heroicons/react/24/outline';
import { Project } from '../../entities/Project';

interface SidebarProjectsProps {
handleNavClick: (path: string, title: string, icon: JSX.Element) => void;
Expand All @@ -24,12 +23,11 @@ const SidebarProjects: React.FC<SidebarProjectsProps> = ({
return (
<>
<ul className="flex flex-col space-y-1 mt-4">
{/* "PROJECTS" Title with Add Button */}
<li
className={`flex justify-between items-center px-4 py-2 uppercase rounded-md text-xs tracking-wider cursor-pointer hover:text-black dark:hover:text-white ${isActiveProject(
'/projects'
)}`}
onClick={() => handleNavClick('/projects', 'Projects', <FolderIcon className="h-5 w-5 mr-2" />)}
onClick={() => handleNavClick('/projects?active=true', 'Projects', <FolderIcon className="h-5 w-5 mr-2" />)}
>
<span className="flex items-center">
<FolderIcon className="h-5 w-5 mr-2" />
Expand Down
4 changes: 4 additions & 0 deletions app/frontend/components/Task/TaskModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,10 @@ const TaskModal: React.FC<TaskModalProps> = ({
}
};

useEffect(() => {
setFilteredProjects(projects);
}, [projects]);

useEffect(() => {
const handleClickOutside = (event: MouseEvent) => {
if (
Expand Down
37 changes: 29 additions & 8 deletions app/frontend/contexts/DataContext.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,22 @@
// app/frontend/contexts/DataContext.tsx

import React, { createContext, useContext } from 'react';
import useFetchTags from '../hooks/useFetchTags';
import useFetchAreas from '../hooks/useFetchAreas';
import useFetchProjects from '../hooks/useFetchProjects'; // Use the updated hook
import useManageAreas from '../hooks/useManageAreas';
import useManageNotes from '../hooks/useManageNotes';
import useManageProjects from '../hooks/useManageProjects';
import useManageTags from '../hooks/useManageTags';
import useManageTasks from '../hooks/useManageTasks';
import useManageTasks from '../hooks/useManageTasks';
import { Project } from '../entities/Project';

interface DataContextProps {
tasks: any[];
tags: any[];
areas: any[];
notes: any[];
projects: Project[];
isLoading: boolean;
isError: boolean;
createNote: (noteData: any) => Promise<void>;
Expand All @@ -20,7 +25,7 @@ interface DataContextProps {
createArea: (areaData: any) => Promise<void>;
updateArea: (areaId: number, areaData: any) => Promise<void>;
deleteArea: (areaId: number) => Promise<void>;
createProject: (projectData: any) => Promise<void>;
createProject: (projectData: any) => Promise<Project>;
updateProject: (projectId: number, projectData: any) => Promise<void>;
deleteProject: (projectId: number) => Promise<void>;
createTag: (tagData: any) => Promise<void>;
Expand All @@ -32,6 +37,7 @@ interface DataContextProps {
mutateTags: () => void;
mutateAreas: () => void;
mutateNotes: () => void;
mutateProjects: () => void; // Include mutateProjects
}

const DataContext = createContext<DataContextProps | undefined>(undefined);
Expand All @@ -47,10 +53,23 @@ export const useDataContext = () => {
export const DataProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
const { tags, isLoading: isLoadingTags, isError: isErrorTags, mutate: mutateTags } = useFetchTags();
const { areas, isLoading: isLoadingAreas, isError: isErrorAreas, mutate: mutateAreas } = useFetchAreas();
const {
projects,
isLoading: isLoadingProjects,
isError: isErrorProjects,
mutate: mutateProjects,
} = useFetchProjects(); // Use the updated hook without options
const { createArea, updateArea, deleteArea } = useManageAreas();
const { createProject, updateProject, deleteProject } = useManageProjects();
const { createTag, updateTag, deleteTag } = useManageTags();
const { tasks, isLoading: isLoadingTasks, isError: isErrorTasks, createTask, updateTask, deleteTask } = useManageTasks();
const {
tasks,
isLoading: isLoadingTasks,
isError: isErrorTasks,
createTask,
updateTask,
deleteTask,
} = useManageTasks();
const {
notes,
isLoading: isLoadingNotes,
Expand All @@ -61,8 +80,8 @@ export const DataProvider: React.FC<{ children: React.ReactNode }> = ({ children
mutate: mutateNotes,
} = useManageNotes();

const isLoading = isLoadingTags || isLoadingAreas || isLoadingNotes || isLoadingTasks;
const isError = isErrorTags || isErrorAreas || isErrorNotes || isErrorTasks;
const isLoading = isLoadingTags || isLoadingAreas || isLoadingNotes || isLoadingTasks || isLoadingProjects;
const isError = isErrorTags || isErrorAreas || isErrorNotes || isErrorTasks || isErrorProjects;

return (
<DataContext.Provider
Expand All @@ -71,6 +90,7 @@ export const DataProvider: React.FC<{ children: React.ReactNode }> = ({ children
tags,
areas,
notes,
projects,
isLoading,
isError,
createNote,
Expand All @@ -85,12 +105,13 @@ export const DataProvider: React.FC<{ children: React.ReactNode }> = ({ children
createTag,
updateTag,
deleteTag,
createTask,
updateTask,
deleteTask,
createTask,
updateTask,
deleteTask,
mutateTags,
mutateAreas,
mutateNotes,
mutateProjects, // Include mutateProjects
}}
>
{children}
Expand Down
81 changes: 62 additions & 19 deletions app/frontend/hooks/useFetchProjects.ts
Original file line number Diff line number Diff line change
@@ -1,33 +1,76 @@
import useSWR from 'swr';
// app/frontend/hooks/useFetchProjects.ts

import { useState, useEffect } from 'react';
import { Project } from '../entities/Project';
import { fetcher } from '../utils/fetcher';

interface ProjectsAPIResponse {
interface UseFetchProjectsOptions {
activeFilter?: string;
areaFilter?: string;
}

interface UseFetchProjectsResult {
projects: Project[];
task_status_counts: Record<number, any>;
taskStatusCounts?: any;
isLoading: boolean;
isError: boolean;
mutate: () => void;
}

import { useMemo } from 'react';
const useFetchProjects = (options?: UseFetchProjectsOptions): UseFetchProjectsResult => {
const [projects, setProjects] = useState<Project[]>([]);
const [taskStatusCounts, setTaskStatusCounts] = useState<any>();
const [isLoading, setIsLoading] = useState<boolean>(true);
const [isError, setIsError] = useState<boolean>(false);

const useFetchProjects = (activeFilter: string, areaFilter: string) => {
const url = useMemo(() => {
const queryParams = new URLSearchParams();
const fetchProjects = async () => {
setIsLoading(true);
setIsError(false);
try {
let url = '/api/projects';
const params = new URLSearchParams();

if (activeFilter !== 'all') queryParams.append('active', activeFilter);
if (areaFilter) queryParams.append('area_id', areaFilter);
if (options?.activeFilter !== undefined) {
params.append('active', String(options.activeFilter));
}
if (options?.areaFilter !== undefined) {
params.append('area', options.areaFilter);
}

return `/api/projects?${queryParams.toString()}`;
}, [activeFilter, areaFilter]);
if (params.toString()) {
url += `?${params.toString()}`;
}

const { data, error, mutate } = useSWR<ProjectsAPIResponse>(url, fetcher);
const response = await fetch(url, {
credentials: 'include',
headers: { Accept: 'application/json' },
});

return {
projects: data?.projects || [],
taskStatusCounts: data?.task_status_counts || {},
isLoading: !error && !data,
isError: error,
mutate,
if (response.ok) {
const data = await response.json();

if (data.projects) {
setProjects(data.projects);
setTaskStatusCounts(data.taskStatusCounts);
} else {
setProjects(data);
setTaskStatusCounts(undefined);
}
} else {
throw new Error('Failed to fetch projects.');
}
} catch (error) {
console.error('Error fetching projects:', error);
setIsError(true);
} finally {
setIsLoading(false);
}
};

useEffect(() => {
fetchProjects();
}, [options?.activeFilter, options?.areaFilter]);

return { projects, taskStatusCounts, isLoading, isError, mutate: fetchProjects };
};

export default useFetchProjects;
Loading

0 comments on commit cc3ad37

Please sign in to comment.