diff --git a/worklenz-backend/src/controllers/reporting/reporting-allocation-controller.ts b/worklenz-backend/src/controllers/reporting/reporting-allocation-controller.ts index e948726d..0bef5bb1 100644 --- a/worklenz-backend/src/controllers/reporting/reporting-allocation-controller.ts +++ b/worklenz-backend/src/controllers/reporting/reporting-allocation-controller.ts @@ -15,6 +15,25 @@ enum IToggleOptions { } export default class ReportingAllocationController extends ReportingControllerBase { + // Helper method to build billable query with custom table alias + private static buildBillableQueryWithAlias(selectedStatuses: { billable: boolean; nonBillable: boolean }, tableAlias: string = 'tasks'): string { + const { billable, nonBillable } = selectedStatuses; + + if (billable && nonBillable) { + // Both are enabled, no need to filter + return ""; + } else if (billable && !nonBillable) { + // Only billable is enabled - show only billable tasks + return ` AND ${tableAlias}.billable IS TRUE`; + } else if (!billable && nonBillable) { + // Only non-billable is enabled - show only non-billable tasks + return ` AND ${tableAlias}.billable IS FALSE`; + } else { + // Neither selected - this shouldn't happen in normal UI flow + return ""; + } + } + private static async getTimeLoggedByProjects(projects: string[], users: string[], key: string, dateRange: string[], archived = false, user_id = "", billable: { billable: boolean; nonBillable: boolean }): Promise { try { const projectIds = projects.map(p => `'${p}'`).join(","); @@ -545,7 +564,7 @@ export default class ReportingAllocationController extends ReportingControllerBa ? "" : `AND p.id NOT IN (SELECT project_id FROM archived_projects WHERE project_id = p.id AND user_id = '${req.user?.id}') `; - const billableQuery = this.buildBillableQuery(billable); + const billableQuery = this.buildBillableQueryWithAlias(billable, 't'); const members = (req.body.members || []) as string[]; // Prepare members filter diff --git a/worklenz-frontend/src/pages/reporting/time-reports/members-time-sheet/members-time-sheet.tsx b/worklenz-frontend/src/pages/reporting/time-reports/members-time-sheet/members-time-sheet.tsx index ee465bc2..72b74b6b 100644 --- a/worklenz-frontend/src/pages/reporting/time-reports/members-time-sheet/members-time-sheet.tsx +++ b/worklenz-frontend/src/pages/reporting/time-reports/members-time-sheet/members-time-sheet.tsx @@ -262,9 +262,35 @@ const MembersTimeSheet = forwardRef( } }; + // Create stable references for selected items to prevent unnecessary re-renders + const selectedTeamIds = React.useMemo(() => + teams.filter(team => team.selected).map(t => t.id).join(','), + [teams] + ); + + const selectedProjectIds = React.useMemo(() => + filterProjects.filter(project => project.selected).map(p => p.id).join(','), + [filterProjects] + ); + + const selectedCategoryIds = React.useMemo(() => + categories.filter(category => category.selected).map(c => c.id).join(','), + [categories] + ); + + const selectedMemberIds = React.useMemo(() => + members.filter(member => member.selected).map(m => m.id).join(','), + [members] + ); + + const selectedUtilizationIds = React.useMemo(() => + utilization.filter(item => item.selected).map(u => u.id).join(','), + [utilization] + ); + useEffect(() => { fetchChartData(); - }, [duration, dateRange, billable, archived, teams, filterProjects, categories, members, utilization]); + }, [duration, dateRange, billable, archived, noCategory, selectedTeamIds, selectedProjectIds, selectedCategoryIds, selectedMemberIds, selectedUtilizationIds]); const exportChart = () => { if (chartRef.current) {