Skip to content

Commit 1620fa4

Browse files
Merge branch 'main' into landingpages/navbars
2 parents 45fb8b4 + 6d414a8 commit 1620fa4

34 files changed

Lines changed: 3673 additions & 1169 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: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import axios from 'axios';
22

33
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: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
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+
projectId?: string;
12+
}
13+
14+
export interface MainTaskResponse {
15+
id: string;
16+
title: string;
17+
description?: string;
18+
projectId?: string;
19+
createdAt?: string;
20+
subTaskIds?: string[];
21+
}
22+
23+
export const mainTasksApi = {
24+
/**
25+
* Get all main tasks
26+
* Backend path: GET /api/maintasks
27+
*/
28+
getMainTasks: async (): Promise<MainTaskResponse[]> => {
29+
const response = await axiosInstance.get<MainTaskResponse[]>('/api/maintasks');
30+
return response.data;
31+
},
32+
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+
42+
/**
43+
* Get main task by ID
44+
* Backend path: GET /api/maintasks/{id}
45+
*/
46+
getMainTaskById: async (mainTaskId: string): Promise<MainTaskResponse> => {
47+
const response = await axiosInstance.get<MainTaskResponse>(`/api/maintasks/${mainTaskId}`);
48+
return response.data;
49+
},
50+
51+
/**
52+
* Create a new main task
53+
* Backend path: POST /api/maintasks
54+
*/
55+
createMainTask: async (mainTaskData: CreateMainTaskData): Promise<MainTaskResponse> => {
56+
const response = await axiosInstance.post<MainTaskResponse>('/api/maintasks', mainTaskData);
57+
return response.data;
58+
},
59+
60+
/**
61+
* Get all subtasks for a main task
62+
* Backend path: GET /api/maintasks/{id}/subtasks
63+
*/
64+
getSubTasksForMainTask: async (mainTaskId: string): Promise<SubTaskResponse[]> => {
65+
const response = await axiosInstance.get<SubTaskResponse[]>(`/api/maintasks/${mainTaskId}/subtasks`);
66+
return response.data;
67+
},
68+
69+
/**
70+
* Delete a main task
71+
* Backend path: DELETE /api/maintasks/{id}
72+
*/
73+
deleteMainTask: async (mainTaskId: string): Promise<void> => {
74+
await axiosInstance.delete(`/api/maintasks/${mainTaskId}`);
75+
},
76+
77+
/**
78+
* Update a main task
79+
* Backend path: PUT /api/maintasks/{id}
80+
*/
81+
updateMainTask: async (mainTaskId: string, data: UpdateMainTaskData): Promise<void> => {
82+
await axiosInstance.put(`/api/maintasks/${mainTaskId}`, data);
83+
},
84+
};

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;

0 commit comments

Comments
 (0)