feat(members-time-sheet): enhance API response handling and add noCategory filter

This commit is contained in:
chamiakJ
2025-06-03 14:33:22 +05:30
parent 66b7dc5322
commit e82bb23cd5
2 changed files with 118 additions and 26 deletions

View File

@@ -77,8 +77,8 @@ export default class ReportingAllocationController extends ReportingControllerBa
sps.icon AS status_icon,
(SELECT COUNT(*)
FROM tasks
WHERE CASE WHEN ($1 IS TRUE) THEN project_id IS NOT NULL ELSE archived = FALSE END ${billableQuery}
AND project_id = projects.id) AS all_tasks_count,
WHERE CASE WHEN ($1 IS TRUE) THEN project_id IS NOT NULL ELSE archived = FALSE END
AND project_id = projects.id ${billableQuery}) AS all_tasks_count,
(SELECT COUNT(*)
FROM tasks
WHERE CASE WHEN ($1 IS TRUE) THEN project_id IS NOT NULL ELSE archived = FALSE END
@@ -95,9 +95,10 @@ export default class ReportingAllocationController extends ReportingControllerBa
(SELECT COALESCE(SUM(time_spent), 0)
FROM task_work_log
LEFT JOIN tasks ON task_work_log.task_id = tasks.id
WHERE user_id = users.id ${billableQuery}
WHERE user_id = users.id
AND CASE WHEN ($1 IS TRUE) THEN tasks.project_id IS NOT NULL ELSE tasks.archived = FALSE END
AND tasks.project_id = projects.id
${billableQuery}
${duration}) AS time_logged
FROM users
WHERE id IN (${userIds})
@@ -121,10 +122,11 @@ export default class ReportingAllocationController extends ReportingControllerBa
const q = `(SELECT id,
(SELECT COALESCE(SUM(time_spent), 0)
FROM task_work_log
LEFT JOIN tasks ON task_work_log.task_id = tasks.id ${billableQuery}
LEFT JOIN tasks ON task_work_log.task_id = tasks.id
WHERE user_id = users.id
AND CASE WHEN ($1 IS TRUE) THEN tasks.project_id IS NOT NULL ELSE tasks.archived = FALSE END
AND tasks.project_id IN (${projectIds})
${billableQuery}
${duration}) AS time_logged
FROM users
WHERE id IN (${userIds})
@@ -346,6 +348,8 @@ export default class ReportingAllocationController extends ReportingControllerBa
const projects = (req.body.projects || []) as string[];
const projectIds = projects.map(p => `'${p}'`).join(",");
const categories = (req.body.categories || []) as string[];
const noCategory = req.body.noCategory || false;
const billable = req.body.billable;
if (!teamIds || !projectIds.length)
@@ -361,6 +365,33 @@ export default class ReportingAllocationController extends ReportingControllerBa
const billableQuery = this.buildBillableQuery(billable);
// Prepare projects filter
let projectsFilter = "";
if (projectIds.length > 0) {
projectsFilter = `AND p.id IN (${projectIds})`;
} else {
// If no projects are selected, don't show any data
projectsFilter = `AND 1=0`; // This will match no rows
}
// Prepare categories filter - updated logic
let categoriesFilter = "";
if (categories.length > 0 && noCategory) {
// Both specific categories and "No Category" are selected
const categoryIds = categories.map(id => `'${id}'`).join(",");
categoriesFilter = `AND (p.category_id IS NULL OR p.category_id IN (${categoryIds}))`;
} else if (categories.length === 0 && noCategory) {
// Only "No Category" is selected
categoriesFilter = `AND p.category_id IS NULL`;
} else if (categories.length > 0 && !noCategory) {
// Only specific categories are selected
const categoryIds = categories.map(id => `'${id}'`).join(",");
categoriesFilter = `AND p.category_id IN (${categoryIds})`;
} else {
// categories.length === 0 && !noCategory - no categories selected, show nothing
categoriesFilter = `AND 1=0`; // This will match no rows
}
const q = `
SELECT p.id,
p.name,
@@ -368,15 +399,13 @@ export default class ReportingAllocationController extends ReportingControllerBa
SUM(total_minutes) AS estimated,
color_code
FROM projects p
LEFT JOIN tasks ON tasks.project_id = p.id ${billableQuery}
LEFT JOIN tasks ON tasks.project_id = p.id
LEFT JOIN task_work_log ON task_work_log.task_id = tasks.id
WHERE p.id IN (${projectIds}) ${durationClause} ${archivedClause}
WHERE p.id IN (${projectIds}) ${durationClause} ${archivedClause} ${categoriesFilter} ${billableQuery}
GROUP BY p.id, p.name
ORDER BY logged_time DESC;`;
console.log('Query:', q);
const result = await db.query(q, []);
console.log('Query result count:', result.rows.length);
console.log('Query results:', result.rows);
const utilization = (req.body.utilization || []) as string[];
const data = [];
@@ -406,6 +435,7 @@ export default class ReportingAllocationController extends ReportingControllerBa
const projectIds = projects.map(p => `'${p}'`).join(",");
const categories = (req.body.categories || []) as string[];
const noCategory = req.body.noCategory || false;
const billable = req.body.billable;
if (!teamIds)
@@ -529,13 +559,27 @@ export default class ReportingAllocationController extends ReportingControllerBa
let projectsFilter = "";
if (projectIds.length > 0) {
projectsFilter = `AND p.id IN (${projectIds})`;
} else {
// If no projects are selected, don't show any data
projectsFilter = `AND 1=0`; // This will match no rows
}
// Prepare categories filter
// Prepare categories filter - updated logic
let categoriesFilter = "";
if (categories.length > 0) {
if (categories.length > 0 && noCategory) {
// Both specific categories and "No Category" are selected
const categoryIds = categories.map(id => `'${id}'`).join(",");
categoriesFilter = `AND (p.category_id IS NULL OR p.category_id IN (${categoryIds}))`;
} else if (categories.length === 0 && noCategory) {
// Only "No Category" is selected
categoriesFilter = `AND p.category_id IS NULL`;
} else if (categories.length > 0 && !noCategory) {
// Only specific categories are selected
const categoryIds = categories.map(id => `'${id}'`).join(",");
categoriesFilter = `AND p.category_id IN (${categoryIds})`;
} else {
// categories.length === 0 && !noCategory - no categories selected, show nothing
categoriesFilter = `AND 1=0`; // This will match no rows
}
// Create custom duration clause for twl table alias
@@ -569,13 +613,14 @@ export default class ReportingAllocationController extends ReportingControllerBa
COALESCE(
(SELECT SUM(twl.time_spent)
FROM task_work_log twl
LEFT JOIN tasks t ON t.id = twl.task_id ${billableQuery}
LEFT JOIN tasks t ON t.id = twl.task_id
LEFT JOIN projects p ON p.id = t.project_id
WHERE twl.user_id = tmiv.user_id
${customDurationClause}
${projectsFilter}
${categoriesFilter}
${archivedClause}
${billableQuery}
AND p.team_id = tmiv.team_id
), 0
) AS logged_time
@@ -585,7 +630,7 @@ export default class ReportingAllocationController extends ReportingControllerBa
${membersFilter}
GROUP BY tmiv.email, tmiv.name, tmiv.team_member_id, tmiv.user_id, tmiv.team_id
ORDER BY logged_time DESC;`;
const result = await db.query(q, []);
const utilization = (req.body.utilization || []) as string[];
@@ -728,6 +773,9 @@ export default class ReportingAllocationController extends ReportingControllerBa
const projects = (req.body.projects || []) as string[];
const projectIds = projects.map(p => `'${p}'`).join(",");
const categories = (req.body.categories || []) as string[];
const noCategory = req.body.noCategory || false;
const { type, billable } = req.body;
if (!teamIds || !projectIds.length)
@@ -743,6 +791,33 @@ export default class ReportingAllocationController extends ReportingControllerBa
const billableQuery = this.buildBillableQuery(billable);
// Prepare projects filter
let projectsFilter = "";
if (projectIds.length > 0) {
projectsFilter = `AND p.id IN (${projectIds})`;
} else {
// If no projects are selected, don't show any data
projectsFilter = `AND 1=0`; // This will match no rows
}
// Prepare categories filter - updated logic
let categoriesFilter = "";
if (categories.length > 0 && noCategory) {
// Both specific categories and "No Category" are selected
const categoryIds = categories.map(id => `'${id}'`).join(",");
categoriesFilter = `AND (p.category_id IS NULL OR p.category_id IN (${categoryIds}))`;
} else if (categories.length === 0 && noCategory) {
// Only "No Category" is selected
categoriesFilter = `AND p.category_id IS NULL`;
} else if (categories.length > 0 && !noCategory) {
// Only specific categories are selected
const categoryIds = categories.map(id => `'${id}'`).join(",");
categoriesFilter = `AND p.category_id IN (${categoryIds})`;
} else {
// categories.length === 0 && !noCategory - no categories selected, show nothing
categoriesFilter = `AND 1=0`; // This will match no rows
}
const q = `
SELECT p.id,
p.name,
@@ -756,9 +831,9 @@ export default class ReportingAllocationController extends ReportingControllerBa
WHERE project_id = p.id) AS estimated,
color_code
FROM projects p
LEFT JOIN tasks ON tasks.project_id = p.id ${billableQuery}
LEFT JOIN tasks ON tasks.project_id = p.id
LEFT JOIN task_work_log ON task_work_log.task_id = tasks.id
WHERE p.id IN (${projectIds}) ${durationClause} ${archivedClause}
WHERE p.id IN (${projectIds}) ${durationClause} ${archivedClause} ${categoriesFilter} ${billableQuery}
GROUP BY p.id, p.name
ORDER BY logged_time DESC;`;
const result = await db.query(q, []);

View File

@@ -15,7 +15,6 @@ import { useTranslation } from 'react-i18next';
import { reportingTimesheetApiService } from '@/api/reporting/reporting.timesheet.api.service';
import { IRPTTimeMember } from '@/types/reporting/reporting.types';
import logger from '@/utils/errorLogger';
import { useAppDispatch } from '@/hooks/useAppDispatch';
import { format } from 'date-fns';
ChartJS.register(CategoryScale, LinearScale, BarElement, Title, Tooltip, Legend, ChartDataLabels);
@@ -29,7 +28,6 @@ export interface MembersTimeSheetRef {
const MembersTimeSheet = forwardRef<MembersTimeSheetRef, MembersTimeSheetProps>(({ onTotalsUpdate }, ref) => {
const { t } = useTranslation('time-report');
const dispatch = useAppDispatch();
const chartRef = React.useRef<ChartJS<'bar', string[], unknown>>(null);
const {
@@ -45,6 +43,7 @@ const MembersTimeSheet = forwardRef<MembersTimeSheetRef, MembersTimeSheetProps>(
loadingUtilization,
billable,
archived,
noCategory,
} = useAppSelector(state => state.timeReportsOverviewReducer);
const { duration, dateRange } = useAppSelector(state => state.reportingReducer);
@@ -223,23 +222,41 @@ const MembersTimeSheet = forwardRef<MembersTimeSheetRef, MembersTimeSheetProps>(
duration,
date_range: formattedDateRange,
billable,
noCategory,
};
const res = await reportingTimesheetApiService.getMemberTimeSheets(body, archived);
if (res.done) {
setJsonData(res.body.filteredRows || []);
// Ensure filteredRows is always an array, even if API returns null/undefined
setJsonData(res.body?.filteredRows || []);
const totalsRaw = res.body.totals || {};
const totals = {
total_time_logs: totalsRaw.total_time_logs ?? "0",
total_estimated_hours: totalsRaw.total_estimated_hours ?? "0",
total_utilization: totalsRaw.total_utilization ?? "0",
};
onTotalsUpdate(totals);
const totalsRaw = res.body?.totals || {};
const totals = {
total_time_logs: totalsRaw.total_time_logs ?? "0",
total_estimated_hours: totalsRaw.total_estimated_hours ?? "0",
total_utilization: totalsRaw.total_utilization ?? "0",
};
onTotalsUpdate(totals);
} else {
// Handle API error case
setJsonData([]);
onTotalsUpdate({
total_time_logs: "0",
total_estimated_hours: "0",
total_utilization: "0"
});
}
} catch (error) {
console.error('Error fetching chart data:', error);
logger.error('Error fetching chart data:', error);
// Reset data on error
setJsonData([]);
onTotalsUpdate({
total_time_logs: "0",
total_estimated_hours: "0",
total_utilization: "0"
});
} finally {
setLoading(false);
}
@@ -247,7 +264,7 @@ const MembersTimeSheet = forwardRef<MembersTimeSheetRef, MembersTimeSheetProps>(
useEffect(() => {
fetchChartData();
}, [dispatch, duration, dateRange, billable, archived, teams, filterProjects, categories, members, utilization]);
}, [duration, dateRange, billable, archived, teams, filterProjects, categories, members, utilization]);
const exportChart = () => {
if (chartRef.current) {