feat(projects): implement grouped project retrieval and UI enhancements

- Added a new endpoint for retrieving projects grouped by category, client, or status.
- Enhanced the ProjectsController with a method to handle grouped project queries.
- Updated the projects API router to include the new grouped endpoint.
- Improved the frontend to support displaying grouped projects with pagination and filtering options.
- Updated localization files for English, Spanish, and Portuguese to include new grouping options.
- Refactored project list components to accommodate the new grouped view and improved UI elements.
This commit is contained in:
chamikaJ
2025-06-13 15:46:03 +05:30
parent 81f55adb41
commit 3cae2771de
12 changed files with 1284 additions and 323 deletions

View File

@@ -5,12 +5,17 @@ import { IProjectViewModel } from '@/types/project/projectViewModel.types';
import { IProjectCategory } from '@/types/project/projectCategory.types';
import { DEFAULT_PAGE_SIZE } from '@/shared/constants';
import { IProjectManager } from '@/types/project/projectManager.types';
import { IGroupedProjectsViewModel } from '@/types/project/groupedProjectsViewModel.types';
interface ProjectState {
projects: {
data: IProjectViewModel[];
total: number;
};
groupedProjects: {
data: IGroupedProjectsViewModel | null;
loading: boolean;
};
categories: IProjectCategory[];
loading: boolean;
creatingProject: boolean;
@@ -29,6 +34,17 @@ interface ProjectState {
statuses: string | null;
categories: string | null;
};
groupedRequestParams: {
index: number;
size: number;
field: string;
order: string;
search: string;
groupBy: string;
filter: number;
statuses: string | null;
categories: string | null;
};
projectManagers: IProjectManager[];
projectManagersLoading: boolean;
}
@@ -38,6 +54,10 @@ const initialState: ProjectState = {
data: [],
total: 0,
},
groupedProjects: {
data: null,
loading: false,
},
categories: [],
loading: false,
creatingProject: false,
@@ -56,6 +76,17 @@ const initialState: ProjectState = {
statuses: null,
categories: null,
},
groupedRequestParams: {
index: 1,
size: DEFAULT_PAGE_SIZE,
field: 'name',
order: 'ascend',
search: '',
groupBy: '',
filter: 0,
statuses: null,
categories: null,
},
projectManagers: [],
projectManagersLoading: false,
};
@@ -98,6 +129,46 @@ export const fetchProjects = createAsyncThunk(
}
);
// Create async thunk for fetching grouped projects
export const fetchGroupedProjects = createAsyncThunk(
'projects/fetchGroupedProjects',
async (
params: {
index: number;
size: number;
field: string;
order: string;
search: string;
groupBy: string;
filter: number;
statuses: string | null;
categories: string | null;
},
{ rejectWithValue }
) => {
try {
const groupedProjectsResponse = await projectsApiService.getGroupedProjects(
params.index,
params.size,
params.field,
params.order,
params.search,
params.groupBy,
params.filter,
params.statuses,
params.categories
);
return groupedProjectsResponse.body;
} catch (error) {
logger.error('Fetch Grouped Projects', error);
if (error instanceof Error) {
return rejectWithValue(error.message);
}
return rejectWithValue('Failed to fetch grouped projects');
}
}
);
export const toggleFavoriteProject = createAsyncThunk(
'projects/toggleFavoriteProject',
async (id: string, { rejectWithValue }) => {
@@ -131,7 +202,7 @@ export const createProject = createAsyncThunk(
export const updateProject = createAsyncThunk(
'projects/updateProject',
async ({ id, project }: { id: string; project: IProjectViewModel }, { rejectWithValue }) => {
const response = await projectsApiService.updateProject(id, project);
const response = await projectsApiService.updateProject({ id, ...project });
return response.body;
}
);
@@ -196,6 +267,12 @@ const projectSlice = createSlice({
...action.payload,
};
},
setGroupedRequestParams: (state, action: PayloadAction<Partial<ProjectState['groupedRequestParams']>>) => {
state.groupedRequestParams = {
...state.groupedRequestParams,
...action.payload,
};
},
},
extraReducers: builder => {
builder
@@ -213,6 +290,16 @@ const projectSlice = createSlice({
.addCase(fetchProjects.rejected, state => {
state.loading = false;
})
.addCase(fetchGroupedProjects.pending, state => {
state.groupedProjects.loading = true;
})
.addCase(fetchGroupedProjects.fulfilled, (state, action) => {
state.groupedProjects.loading = false;
state.groupedProjects.data = action.payload;
})
.addCase(fetchGroupedProjects.rejected, state => {
state.groupedProjects.loading = false;
})
.addCase(createProject.pending, state => {
state.creatingProject = true;
})
@@ -248,5 +335,6 @@ export const {
setFilteredCategories,
setFilteredStatuses,
setRequestParams,
setGroupedRequestParams,
} = projectSlice.actions;
export default projectSlice.reducer;