Skip to content

Commit a7e223e

Browse files
committed
feat: Add CreateMainTaskDialog and EditMainTaskDialog components for task management
- Implemented CreateMainTaskDialog for creating main tasks with subtasks. - Added EditMainTaskDialog for editing existing main tasks and their subtasks. - Integrated analytics page to display summary statistics for users, projects, and tasks. - Enhanced user experience with loading states and error handling in dialogs.
1 parent c1c85ed commit a7e223e

25 files changed

Lines changed: 2584 additions & 554 deletions

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
"@fluentui/react-components": "^9.72.4",
1818
"@fluentui/react-datepicker-compat": "^0.6.20",
1919
"axios": "^1.13.2",
20+
"browser-image-compression": "^2.0.2",
2021
"react": "19",
2122
"react-dom": "19",
2223
"react-hook-form": "^7.66.0",
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
import { Card, Text, Spinner, mergeClasses } from '@fluentui/react-components';
2+
import { mainLayoutStyles } from '../styles/Styles';
3+
import { tokens } from '@fluentui/react-components';
4+
5+
interface StatCardProps {
6+
title: string;
7+
value: number | string;
8+
icon?: React.ReactNode;
9+
color?: 'brand' | 'success' | 'warning' | 'danger' | 'neutral';
10+
}
11+
12+
export default function StatCard({ title, value, icon, color = 'neutral' }: StatCardProps) {
13+
const styles = mainLayoutStyles();
14+
15+
const colorMap = {
16+
brand: tokens.colorBrandForeground1,
17+
success: tokens.colorPaletteGreenForeground1,
18+
warning: tokens.colorPaletteYellowForeground1,
19+
danger: tokens.colorPaletteRedForeground1,
20+
neutral: tokens.colorNeutralForeground1,
21+
};
22+
23+
const bgColorMap = {
24+
brand: tokens.colorBrandBackground2,
25+
success: tokens.colorPaletteGreenBackground2,
26+
warning: tokens.colorPaletteYellowBackground2,
27+
danger: tokens.colorPaletteRedBackground2,
28+
neutral: tokens.colorNeutralBackground2,
29+
};
30+
31+
return (
32+
<Card
33+
className={mergeClasses(styles.artifCard)}
34+
style={{
35+
padding: tokens.spacingVerticalL,
36+
display: 'flex',
37+
flexDirection: 'column',
38+
gap: tokens.spacingVerticalM,
39+
minWidth: '200px',
40+
}}
41+
>
42+
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
43+
<Text style={{ fontSize: tokens.fontSizeBase300, color: tokens.colorNeutralForeground3 }}>
44+
{title}
45+
</Text>
46+
{icon && (
47+
<div
48+
style={{
49+
width: '32px',
50+
height: '32px',
51+
borderRadius: '50%',
52+
backgroundColor: bgColorMap[color],
53+
color: colorMap[color],
54+
display: 'flex',
55+
alignItems: 'center',
56+
justifyContent: 'center',
57+
}}
58+
>
59+
{icon}
60+
</div>
61+
)}
62+
</div>
63+
<Text
64+
style={{
65+
fontSize: tokens.fontSizeBase600,
66+
fontWeight: tokens.fontWeightBold,
67+
color: colorMap[color],
68+
}}
69+
>
70+
{typeof value === 'number' ? value.toLocaleString() : value}
71+
</Text>
72+
</Card>
73+
);
74+
}
75+
76+
export function StatCardSkeleton() {
77+
const styles = mainLayoutStyles();
78+
79+
return (
80+
<Card
81+
className={mergeClasses(styles.artifCard)}
82+
style={{
83+
padding: tokens.spacingVerticalL,
84+
display: 'flex',
85+
alignItems: 'center',
86+
justifyContent: 'center',
87+
minWidth: '200px',
88+
minHeight: '120px',
89+
}}
90+
>
91+
<Spinner size="small" />
92+
</Card>
93+
);
94+
}

src/components/apis/analytics.ts

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
import axiosInstance from './axiosInstance';
2+
3+
export interface AnalyticsSummary {
4+
totalUsers: number;
5+
totalProjects: number;
6+
totalMainTasks: number;
7+
totalSubTasks: number;
8+
tasksCompleted: number;
9+
tasksPending: number;
10+
tasksOverdue: number;
11+
activeProjects: number;
12+
}
13+
14+
export interface ProjectStats {
15+
projectId: string;
16+
projectName: string;
17+
totalTasks: number;
18+
completedTasks: number;
19+
pendingTasks: number;
20+
overdueTasks: number;
21+
teamSize: number;
22+
}
23+
24+
export interface UserOverview {
25+
userId: string;
26+
userName: string;
27+
assignedTasks: number;
28+
completedTasks: number;
29+
pendingTasks: number;
30+
overdueTasks: number;
31+
}
32+
33+
export interface TimelineDataPoint {
34+
date: string;
35+
tasksCreated: number;
36+
tasksCompleted: number;
37+
}
38+
39+
export interface TopPerformer {
40+
userId: string;
41+
userName: string;
42+
firstName: string;
43+
lastName: string;
44+
tasksCompleted: number;
45+
completionRate: number;
46+
}
47+
48+
export interface KanbanStats {
49+
projectId: string;
50+
projectName: string;
51+
categories: Array<{
52+
categoryId: string;
53+
categoryName: string;
54+
taskCount: number;
55+
}>;
56+
totalTasks: number;
57+
}
58+
59+
export const analyticsApi = {
60+
/**
61+
* Get overall system summary statistics
62+
*/
63+
async getSummary(): Promise<AnalyticsSummary> {
64+
const response = await axiosInstance.get('/api/analytics/summary');
65+
return response.data;
66+
},
67+
68+
/**
69+
* Get project-specific statistics
70+
*/
71+
async getProjectStats(projectId: string): Promise<ProjectStats> {
72+
const response = await axiosInstance.get(`/api/analytics/projects/${projectId}/stats`);
73+
return response.data;
74+
},
75+
76+
/**
77+
* Get user overview (assignment summary)
78+
*/
79+
async getUserOverview(userId: string): Promise<UserOverview> {
80+
const response = await axiosInstance.get(`/api/analytics/users/${userId}/overview`);
81+
return response.data;
82+
},
83+
84+
/**
85+
* Get tasks timeline (task creation trends)
86+
*/
87+
async getTasksTimeline(days: number = 30): Promise<TimelineDataPoint[]> {
88+
const response = await axiosInstance.get(`/api/analytics/tasks/timeline?days=${days}`);
89+
return response.data;
90+
},
91+
92+
/**
93+
* Get top performers by task completion
94+
*/
95+
async getTopPerformers(limit: number = 5): Promise<TopPerformer[]> {
96+
const response = await axiosInstance.get(`/api/analytics/top-performers?limit=${limit}`);
97+
return response.data;
98+
},
99+
100+
/**
101+
* Get Kanban board statistics for a project
102+
*/
103+
async getKanbanStats(projectId: string): Promise<KanbanStats> {
104+
const response = await axiosInstance.get(`/api/analytics/kanban/${projectId}`);
105+
return response.data;
106+
},
107+
};

src/components/apis/axiosInstance.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
import axios from 'axios';
22

3-
export const API_BASE_URL = "https://flowboard-backend.azurewebsites.net/";
3+
// export const API_BASE_URL = "https://flowboard-backend.azurewebsites.net/";
44
// export const API_BASE_URL = "https://animated-space-fiesta-w6wpx564wqxh5vwj-5158.app.github.dev//";
5-
// export const API_BASE_URL = "http://localhost:5158/";
5+
export const API_BASE_URL = "http://localhost:5158/";
66

77
const axiosInstance = axios.create({
88
baseURL: API_BASE_URL,

src/components/apis/index.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
1+
export * from './analytics';
12
export * from './auth';
2-
export * from './tasks';
3+
export * from './tasks'; // Legacy API, now redirects to maintasks/subtasks
4+
export * from './maintasks'; // New MainTask API
5+
export * from './subtasks'; // New SubTask API
36
export * from './users';
47
export * from './projects';
58
export * from './categories';

src/components/apis/maintasks.ts

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
export interface UpdateMainTaskData {
2+
title: string;
3+
description?: string;
4+
}
5+
import axiosInstance from './axiosInstance';
6+
import type { SubTaskResponse } from './subtasks';
7+
8+
export interface CreateMainTaskData {
9+
title: string;
10+
description?: string;
11+
}
12+
13+
export interface MainTaskResponse {
14+
id: string;
15+
title: string;
16+
description?: string;
17+
createdAt?: string;
18+
subTaskIds?: string[];
19+
}
20+
21+
export const mainTasksApi = {
22+
/**
23+
* Get all main tasks
24+
* Backend path: GET /api/maintasks
25+
*/
26+
getMainTasks: async (): Promise<MainTaskResponse[]> => {
27+
const response = await axiosInstance.get<MainTaskResponse[]>('/api/maintasks');
28+
return response.data;
29+
},
30+
31+
/**
32+
* Get main task by ID
33+
* Backend path: GET /api/maintasks/{id}
34+
*/
35+
getMainTaskById: async (mainTaskId: string): Promise<MainTaskResponse> => {
36+
const response = await axiosInstance.get<MainTaskResponse>(`/api/maintasks/${mainTaskId}`);
37+
return response.data;
38+
},
39+
40+
/**
41+
* Create a new main task
42+
* Backend path: POST /api/maintasks
43+
*/
44+
createMainTask: async (mainTaskData: CreateMainTaskData): Promise<MainTaskResponse> => {
45+
const response = await axiosInstance.post<MainTaskResponse>('/api/maintasks', mainTaskData);
46+
return response.data;
47+
},
48+
49+
/**
50+
* Get all subtasks for a main task
51+
* Backend path: GET /api/maintasks/{id}/subtasks
52+
*/
53+
getSubTasksForMainTask: async (mainTaskId: string): Promise<SubTaskResponse[]> => {
54+
const response = await axiosInstance.get<SubTaskResponse[]>(`/api/maintasks/${mainTaskId}/subtasks`);
55+
return response.data;
56+
},
57+
58+
/**
59+
* Delete a main task
60+
* Backend path: DELETE /api/maintasks/{id}
61+
*/
62+
deleteMainTask: async (mainTaskId: string): Promise<void> => {
63+
await axiosInstance.delete(`/api/maintasks/${mainTaskId}`);
64+
},
65+
66+
/**
67+
* Update a main task
68+
* Backend path: PUT /api/maintasks/{id}
69+
*/
70+
updateMainTask: async (mainTaskId: string, data: UpdateMainTaskData): Promise<void> => {
71+
await axiosInstance.put(`/api/maintasks/${mainTaskId}`, data);
72+
},
73+
};

0 commit comments

Comments
 (0)