feat(reporting): enhance time reports page with new filters and components

- Added new components for filtering by billable status, categories, projects, members, and teams in the time reports overview.
- Implemented a new header component to manage the layout and functionality of the time reports page.
- Refactored existing components to improve organization and maintainability, including the removal of deprecated files.
- Updated localization files to support new UI elements and ensure consistency across languages.
- Adjusted the language selector to reflect the correct language codes for Chinese.
This commit is contained in:
chamikaJ
2025-07-24 11:40:39 +05:30
parent fe3fb5e627
commit 20ce0c9687
44 changed files with 2897 additions and 1294 deletions

View File

@@ -17,7 +17,7 @@ const LanguageSelector = () => {
{ key: 'pt', label: 'Português' },
{ key: 'alb', label: 'Shqip' },
{ key: 'de', label: 'Deutsch' },
{ key: 'zh_cn', label: '简体中文' },
{ key: 'zh', label: '简体中文' },
];
const languageLabels = {
@@ -26,7 +26,7 @@ const LanguageSelector = () => {
pt: 'Pt',
alb: 'Sq',
de: 'de',
zh_cn: 'zh_cn',
zh: 'zh',
};
return (

View File

@@ -7,7 +7,7 @@ export enum Language {
PT = 'pt',
ALB = 'alb',
DE = 'de',
ZH_CN = 'zh_cn',
ZH = 'zh',
}
export type ILanguageType = `${Language}`;

View File

@@ -23,6 +23,12 @@ interface ITimeReportsOverviewState {
billable: boolean;
nonBillable: boolean;
};
members: any[];
loadingMembers: boolean;
utilization: any[];
loadingUtilization: boolean;
}
const initialState: ITimeReportsOverviewState = {
@@ -42,6 +48,15 @@ const initialState: ITimeReportsOverviewState = {
billable: true,
nonBillable: true,
},
members: [],
loadingMembers: false,
utilization: [],
loadingUtilization: false,
};
const selectedMembers = (state: ITimeReportsOverviewState) => {
return state.members.filter(member => member.selected).map(member => member.id) as string[];
};
const selectedTeams = (state: ITimeReportsOverviewState) => {
@@ -54,6 +69,76 @@ const selectedCategories = (state: ITimeReportsOverviewState) => {
.map(category => category.id) as string[];
};
const selectedUtilization = (state: ITimeReportsOverviewState) => {
return state.utilization
.filter(utilization => utilization.selected)
.map(utilization => utilization.id) as string[];
};
const allUtilization = (state: ITimeReportsOverviewState) => {
return state.utilization;
};
export const fetchReportingUtilization = createAsyncThunk(
'timeReportsOverview/fetchReportingUtilization',
async (_, { rejectWithValue }) => {
try {
const utilization = [
{ id: 'under', name: 'Under-utilized (Under 90%)', selected: true },
{ id: 'optimal', name: 'Optimal-utilized (90%-110%)', selected: true },
{ id: 'over', name: 'Over-utilized (Over 110%)', selected: true },
];
return utilization;
} catch (error) {
let errorMessage = 'An error occurred while fetching utilization';
if (error instanceof Error) {
errorMessage = error.message;
}
return rejectWithValue(errorMessage);
}
}
);
export const fetchReportingMembers = createAsyncThunk(
'timeReportsOverview/fetchReportingMembers',
async (_, { rejectWithValue, getState }) => {
const state = getState() as { timeReportsOverviewReducer: ITimeReportsOverviewState };
const { timeReportsOverviewReducer } = state;
try {
// If members array is empty (initial load), fetch all members without pagination
// Otherwise, use the selected members filter
let queryParams;
if (timeReportsOverviewReducer.members.length === 0) {
// Initial load - fetch all members with a large page size to avoid pagination
queryParams = {
size: 1000, // Large number to get all members
index: 1,
search: '',
field: 'name',
order: 'asc',
};
} else {
// Subsequent calls - use selected members
queryParams = selectedMembers(timeReportsOverviewReducer);
}
const res = await reportingApiService.getMembers(queryParams);
if (res.done) {
return res.body;
} else {
return rejectWithValue(res.message || 'Failed to fetch members');
}
} catch (error) {
let errorMessage = 'An error occurred while fetching members';
if (error instanceof Error) {
errorMessage = error.message;
}
return rejectWithValue(errorMessage);
}
}
);
export const fetchReportingTeams = createAsyncThunk(
'timeReportsOverview/fetchReportingTeams',
async () => {
@@ -141,6 +226,34 @@ const timeReportsOverviewSlice = createSlice({
setArchived: (state, action: PayloadAction<boolean>) => {
state.archived = action.payload;
},
setSelectOrDeselectMember: (
state,
action: PayloadAction<{ id: string; selected: boolean }>
) => {
const member = state.members.find(member => member.id === action.payload.id);
if (member) {
member.selected = action.payload.selected;
}
},
setSelectOrDeselectAllMembers: (state, action: PayloadAction<boolean>) => {
state.members.forEach(member => {
member.selected = action.payload;
});
},
setSelectOrDeselectUtilization: (
state,
action: PayloadAction<{ id: string; selected: boolean }>
) => {
const utilization = state.utilization.find(u => u.id === action.payload.id);
if (utilization) {
utilization.selected = action.payload.selected;
}
},
setSelectOrDeselectAllUtilization: (state, action: PayloadAction<boolean>) => {
state.utilization.forEach(utilization => {
utilization.selected = action.payload;
});
},
},
extraReducers: builder => {
builder.addCase(fetchReportingTeams.fulfilled, (state, action) => {
@@ -185,6 +298,37 @@ const timeReportsOverviewSlice = createSlice({
builder.addCase(fetchReportingProjects.rejected, state => {
state.loadingProjects = false;
});
builder.addCase(fetchReportingMembers.fulfilled, (state, action) => {
const members = action.payload.members.map((member: any) => ({
id: member.id,
name: member.name,
selected: true,
avatar_url: member.avatar_url,
email: member.email,
}));
state.members = members;
state.loadingMembers = false;
});
builder.addCase(fetchReportingMembers.pending, state => {
state.loadingMembers = true;
});
builder.addCase(fetchReportingMembers.rejected, (state, action) => {
state.loadingMembers = false;
console.error('Error fetching members:', action.payload);
});
builder.addCase(fetchReportingUtilization.fulfilled, (state, action) => {
state.utilization = action.payload;
state.loadingUtilization = false;
});
builder.addCase(fetchReportingUtilization.pending, state => {
state.loadingUtilization = true;
});
builder.addCase(fetchReportingUtilization.rejected, (state, action) => {
state.loadingUtilization = false;
console.error('Error fetching utilization:', action.payload);
});
},
});
@@ -197,6 +341,10 @@ export const {
setSelectOrDeselectProject,
setSelectOrDeselectAllProjects,
setSelectOrDeselectBillable,
setSelectOrDeselectMember,
setSelectOrDeselectAllMembers,
setSelectOrDeselectUtilization,
setSelectOrDeselectAllUtilization,
setNoCategory,
setArchived,
} = timeReportsOverviewSlice.actions;