Skip to content

Commit 6d414a8

Browse files
committed
feat: enhance project and task management features with new API endpoints and UI updates
1 parent fc352fe commit 6d414a8

12 files changed

Lines changed: 62 additions & 28 deletions

File tree

src/components/apis/maintasks.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,14 @@ import type { SubTaskResponse } from './subtasks';
88
export interface CreateMainTaskData {
99
title: string;
1010
description?: string;
11+
projectId?: string;
1112
}
1213

1314
export interface MainTaskResponse {
1415
id: string;
1516
title: string;
1617
description?: string;
18+
projectId?: string;
1719
createdAt?: string;
1820
subTaskIds?: string[];
1921
}
@@ -28,6 +30,15 @@ export const mainTasksApi = {
2830
return response.data;
2931
},
3032

33+
/**
34+
* Get main tasks by project
35+
* Backend path: GET /api/maintasks/project/{projectId}
36+
*/
37+
getMainTasksByProject: async (projectId: string): Promise<MainTaskResponse[]> => {
38+
const response = await axiosInstance.get<MainTaskResponse[]>(`/api/maintasks/project/${projectId}`);
39+
return response.data;
40+
},
41+
3142
/**
3243
* Get main task by ID
3344
* Backend path: GET /api/maintasks/{id}

src/components/apis/projects.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,11 @@ export const projectsApi = {
6969
return response.data;
7070
},
7171

72+
getProjectsAsMember: async (): Promise<Project[]> => {
73+
const response = await axiosInstance.get<Project[]>('/api/projects/member/all');
74+
return response.data;
75+
},
76+
7277
createProject: async (projectData: CreateProjectRequest): Promise<Project> => {
7378
const response = await axiosInstance.post<Project>('/api/projects', projectData);
7479
return response.data;

src/components/headers/NavigationHeader.tsx

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
1-
import { Breadcrumb, BreadcrumbButton, BreadcrumbDivider, BreadcrumbItem, Button, Tooltip } from "@fluentui/react-components";
1+
import { Breadcrumb, BreadcrumbButton, BreadcrumbDivider, BreadcrumbItem, Button, Card, mergeClasses, Tooltip } from "@fluentui/react-components";
22
import { useLocation, useNavigate } from 'react-router-dom';
33
import { Folder20Regular, Board20Regular, TaskListSquareLtr20Regular, Settings20Regular, ChartMultiple20Regular } from '@fluentui/react-icons';
44
import React from 'react';
5+
import { mainLayoutStyles } from "../styles/Styles";
56

67
function titleCase(segment: string) {
78
// make a friendly label from a path segment
@@ -16,6 +17,7 @@ function titleCase(segment: string) {
1617
export default function NavigationHeader() {
1718
const location = useLocation();
1819
const navigate = useNavigate();
20+
const s = mainLayoutStyles()
1921

2022
// Build path segments, remove empty and leading 'home'
2123
const rawSegments = location.pathname.split('/').filter(Boolean);
@@ -50,7 +52,7 @@ export default function NavigationHeader() {
5052
const isSettingsPage = segments.length === 2 && segments[0] === 'project' && lastSegment !== 'kanban' && lastSegment !== 'tasks' && lastSegment !== 'analytics';
5153

5254
return (
53-
<div style={{ display: 'flex', alignItems: 'center', gap: 16 }}>
55+
<Card className={mergeClasses(s.flexRowFill, s.componentBorder)}>
5456
<Breadcrumb aria-label="Breadcrumb">
5557
{crumbs.map((c, idx) => (
5658
<React.Fragment key={c.path}>
@@ -117,6 +119,6 @@ export default function NavigationHeader() {
117119
)}
118120
</div>
119121
)}
120-
</div>
122+
</Card>
121123
);
122124
}

src/components/kanban/KanbanBoard.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -297,7 +297,8 @@ export default function KanbanBoard({ projectId }: KanbanBoardProps) {
297297
try {
298298
const newMainTask = await mainTasksApi.createMainTask({
299299
title: mainTaskForm.title,
300-
description: mainTaskForm.description
300+
description: mainTaskForm.description,
301+
projectId: projectId
301302
});
302303

303304
console.log('Created MainTask:', newMainTask);

src/components/sidebar/ProjectList.tsx

Lines changed: 26 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -38,17 +38,34 @@ export default function ProjectList({ openCategories, onNavigateToProjects, refr
3838
setLoading(true);
3939
setError("");
4040
try {
41-
// If a user is logged in, fetch projects for that user; otherwise fall back to all projects
42-
let data: Project[] = [];
41+
let allProjects: Project[] = [];
42+
4343
if (currentUser?.id) {
44-
data = await projectsApi.getProjectsByUser(currentUser.id);
44+
// Fetch projects created by the user
45+
const createdProjects = await projectsApi.getProjectsByUser(currentUser.id);
46+
if (Array.isArray(createdProjects)) {
47+
allProjects = allProjects.concat(createdProjects);
48+
} else if (createdProjects) {
49+
allProjects.push(createdProjects as Project);
50+
}
51+
52+
// Fetch projects where the user is a team member
53+
const memberProjects = await projectsApi.getProjectsAsMember();
54+
if (Array.isArray(memberProjects)) {
55+
allProjects = allProjects.concat(memberProjects);
56+
} else if (memberProjects) {
57+
allProjects.push(memberProjects as Project);
58+
}
59+
60+
// Remove duplicates by ID
61+
const uniqueProjects = Array.from(
62+
new Map(allProjects.map((p) => [p.id, p])).values()
63+
);
64+
setProjects(uniqueProjects);
4565
} else {
46-
data = await projectsApi.getAllProjects();
66+
const data = await projectsApi.getAllProjects();
67+
setProjects(Array.isArray(data) ? data : data ? [data as Project] : []);
4768
}
48-
if (!active) return;
49-
// backend may return a single project for certain ids; normalize to array
50-
if (!data) setProjects([]);
51-
else setProjects(Array.isArray(data) ? data : [data as Project]);
5269
} catch (err) {
5370
if (!active) return;
5471
setError(err instanceof Error ? err.message : "Unable to load projects");
@@ -89,7 +106,7 @@ export default function ProjectList({ openCategories, onNavigateToProjects, refr
89106
<NavSectionHeader>
90107
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', width: '100%' }}>
91108
<span>Projects</span>
92-
<Button aria-label="Create project" appearance="subtle" onClick={(e) => { e.stopPropagation(); navigate('/home/project/create'); }}>
109+
<Button aria-label="Create project" appearance="subtle" onClick={(e) => { e.stopPropagation(); navigate('/home/create'); }}>
93110
<AddCircle24Regular />
94111
</Button>
95112
</div>

src/layout/UserLayout.tsx

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ export default function UserLayout() {
2020
const isProfile = normalizedPath === "/home/profile";
2121
const isProjectRoot = normalizedPath === "/home/project";
2222
const isProjectWithName = /^\/home\/project\/[^/]+(\/.*)?$/.test(normalizedPath);
23+
const isCreate = normalizedPath === "/home/create";
2324

2425
return (
2526
<main className={mergeClasses(s.userLayout,
@@ -31,19 +32,15 @@ export default function UserLayout() {
3132

3233
<section className={mergeClasses(s.contentsLayout)}>
3334

34-
{!isProfile && !isProjectRoot && (
35+
{!isProfile && !isProjectRoot && !isCreate && (
3536
<div className={s.largeGap}>
3637
{isHome ? (
3738
<HomeHeader />
3839
) : isProjectWithName ? (
39-
<Card >
40-
<NavigationHeader />
41-
{/* <StatsHeader /> */}
42-
</Card>
40+
<NavigationHeader />
4341
) : (
4442
<>
4543
<NavigationHeader />
46-
{/* <StatsHeader /> */}
4744
</>
4845
)}
4946

src/pages/project/CreateProjectPage.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,7 @@ export default function CreateProjectPage() {
127127
};
128128

129129
return (
130-
<Card className={mergeClasses(s.flexColFill, s.layoutPadding)}>
130+
<Card className={mergeClasses(s.componentBorder, s.flexColFill, s.layoutPadding)}>
131131
{/* Title and Actions Row - flex row space-between */}
132132
<div className={mergeClasses(s.flexRowFit, s.wFull, s.spaceBetween)}>
133133
<h1 className={mergeClasses(s.brand, s.pageTitle)}>Create Project</h1>

src/pages/project/KanbanPage.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ export default function KanbanPage() {
2929
}, [decodedName]);
3030

3131
return (
32-
<Card className={mergeClasses(styles.artifCard, styles.layoutPadding, styles.flexColFill, styles.hFull)} style={{ minHeight: '70vh' }}>
32+
<Card className={mergeClasses(styles.artifCard, styles.layoutPadding, styles.flexColFill, styles.hFull, styles.componentBorder)} style={{ minHeight: '70vh' }}>
3333
{projectError && (
3434
<div style={{ color: tokens.colorPaletteRedForeground3 }}>{projectError}</div>
3535
)}

src/pages/project/ProjectPage.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -354,7 +354,7 @@ export default function ProjectPage() {
354354

355355
return (
356356
<Card
357-
className={mergeClasses(styles.artifCard, styles.layoutPadding, styles.wFull)}
357+
className={mergeClasses(styles.artifCard, styles.layoutPadding, styles.wFull, styles.hFull, styles.componentBorder)}
358358
style={{ minHeight: 'calc(100vh - 160px)' }}
359359
>
360360
{loading && (

src/pages/project/TaskListPage.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,7 @@ export default function TaskListPage() {
116116
if (!project?.id) return;
117117
try {
118118
setLoadingMainTasks(true);
119-
const tasks = await mainTasksApi.getMainTasks();
119+
const tasks = await mainTasksApi.getMainTasksByProject(project.id);
120120
setMainTasks(tasks);
121121

122122
// Load SubTask counts for each MainTask
@@ -193,6 +193,7 @@ export default function TaskListPage() {
193193
await mainTasksApi.createMainTask({
194194
title: mainTaskForm.title,
195195
description: mainTaskForm.description,
196+
projectId: project?.id,
196197
});
197198
setCreateMainTaskOpen(false);
198199
setMainTaskForm({ title: '', description: '' });
@@ -343,7 +344,7 @@ export default function TaskListPage() {
343344
if (error || !project) return (<Card style={{ padding: tokens.spacingVerticalXXL }}><div>{error ?? 'Project not found'}</div></Card>);
344345

345346
return (
346-
<Card className={mergeClasses(s.artifCard, s.wFull, s.layoutPadding)}>
347+
<Card className={mergeClasses(s.artifCard, s.wFull, s.layoutPadding, s.hFull, s.componentBorder)}>
347348
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: tokens.spacingVerticalL }}>
348349
<h1 style={{ margin: 0 }}>{project.projectName} - Tasks</h1>
349350
{activeTab === 'mainTasks' && (

0 commit comments

Comments
 (0)