From 8106ea0d243aad9084ff660e9e85386f71c20ebc Mon Sep 17 00:00:00 2001 From: Nikita Kolla <42067463+ArtemisNyx3@users.noreply.github.com> Date: Sat, 3 May 2025 15:21:23 -0400 Subject: [PATCH 01/17] initial barchart --- .../Issues/LongestOpenIssuesChart.jsx | 88 +++++++++++++++++++ .../WeeklyProjectSummary.jsx | 3 +- 2 files changed, 90 insertions(+), 1 deletion(-) create mode 100644 src/components/BMDashboard/Issues/LongestOpenIssuesChart.jsx diff --git a/src/components/BMDashboard/Issues/LongestOpenIssuesChart.jsx b/src/components/BMDashboard/Issues/LongestOpenIssuesChart.jsx new file mode 100644 index 0000000000..6c9447a275 --- /dev/null +++ b/src/components/BMDashboard/Issues/LongestOpenIssuesChart.jsx @@ -0,0 +1,88 @@ +import React from 'react'; +import { + Chart as ChartJS, + CategoryScale, + LinearScale, + BarElement, + Title, + Tooltip, + Legend, +} from 'chart.js'; +import { Bar } from 'react-chartjs-2'; +import ChartDataLabels from 'chartjs-plugin-datalabels'; + +// Register required Chart.js components +ChartJS.register(CategoryScale, LinearScale, BarElement, Title, Tooltip, Legend, ChartDataLabels); + +function LongestOpenIssuesChart() { + const processedData = [ + { + IssueId: '662d6479aa9fee05811b7d2e', + title: ['testing', 'testing'], + daysOpen: 701, + }, + { + IssueId: '662d69367813950e4f454d44', + title: ['testing', 'testing'], + daysOpen: 701, + }, + { + IssueId: '67b7c723432ea2100f636011', + title: ['safty', 'minor injury'], + daysOpen: 73, + }, + { + IssueId: '67b7c774432ea2100f63601a', + title: ['worker safty ', 'minor injury'], + daysOpen: 73, + }, + { + IssueId: '67b7c832432ea2100f63601d', + title: ['safty', 'minor injury'], + daysOpen: 73, + }, + ]; + const data = { + labels: processedData.map(issue => issue.title || issue.id), + datasets: [ + { + label: 'Days Open', + data: processedData.map(issue => issue.daysOpen), + backgroundColor: 'rgba(54, 162, 235, 0.7)', + borderColor: 'rgba(54, 162, 235, 1)', + borderWidth: 1, + }, + ], + }; + + const options = { + indexAxis: 'y', // horizontal bar chart + responsive: true, + plugins: { + title: { + display: true, + text: 'Top 5 Longest Open Issues', + font: { size: 12 }, + }, + legend: { display: false }, + datalabels: { + anchor: 'end', + align: 'right', + formatter: value => `${value} days`, + color: '#000', + }, + }, + scales: { + x: { + title: { display: true, text: 'Days Open' }, + }, + y: { + title: { display: true, text: 'Issue Title or ID' }, + }, + }, + }; + + return ; +} + +export default LongestOpenIssuesChart; diff --git a/src/components/BMDashboard/WeeklyProjectSummary/WeeklyProjectSummary.jsx b/src/components/BMDashboard/WeeklyProjectSummary/WeeklyProjectSummary.jsx index c987dba2c6..2a152eb228 100644 --- a/src/components/BMDashboard/WeeklyProjectSummary/WeeklyProjectSummary.jsx +++ b/src/components/BMDashboard/WeeklyProjectSummary/WeeklyProjectSummary.jsx @@ -5,6 +5,7 @@ import { v4 as uuidv4 } from 'uuid'; import WeeklyProjectSummaryHeader from './WeeklyProjectSummaryHeader'; import { fetchAllMaterials } from '../../../actions/bmdashboard/materialsActions'; import QuantityOfMaterialsUsed from './QuantityOfMaterialsUsed/QuantityOfMaterialsUsed'; +import LongestOpenIssuesChart from '../Issues/LongestOpenIssuesChart'; const projectStatusButtons = [ { @@ -187,7 +188,7 @@ export default function WeeklyProjectSummary() { title: 'Issue Tracking', key: 'Issue Tracking', className: 'small', - content:
📊 Card
, + content: , }, { title: 'Tools and Equipment Tracking', From c77485fe77cd13616dce0a9aa6e9ddb2e16eb56c Mon Sep 17 00:00:00 2001 From: Nikita Kolla <42067463+ArtemisNyx3@users.noreply.github.com> Date: Sat, 10 May 2025 18:28:43 -0400 Subject: [PATCH 02/17] filters for the longest open issues --- .../Issues/LongestOpenIssuesChart.jsx | 158 ++++++++++++++---- 1 file changed, 123 insertions(+), 35 deletions(-) diff --git a/src/components/BMDashboard/Issues/LongestOpenIssuesChart.jsx b/src/components/BMDashboard/Issues/LongestOpenIssuesChart.jsx index 6c9447a275..150525a8f5 100644 --- a/src/components/BMDashboard/Issues/LongestOpenIssuesChart.jsx +++ b/src/components/BMDashboard/Issues/LongestOpenIssuesChart.jsx @@ -1,4 +1,4 @@ -import React from 'react'; +import { useState } from 'react'; import { Chart as ChartJS, CategoryScale, @@ -14,40 +14,85 @@ import ChartDataLabels from 'chartjs-plugin-datalabels'; // Register required Chart.js components ChartJS.register(CategoryScale, LinearScale, BarElement, Title, Tooltip, Legend, ChartDataLabels); +// Mock data - in a real app, this would likely come from an API +const allIssuesData = [ + { + IssueId: '662d6479aa9fee05811b7d2e', + title: 'testing issue', + daysOpen: 701, + project: 'Project A', + openedDate: '2022-05-15', + }, + // { + // IssueId: '662d69367813950e4f454d44', + // title: 'testing another issue', + // daysOpen: 701, + // project: 'Project B', + // openedDate: '2022-05-15', + // }, + { + IssueId: '67b7c723432ea2100f636011', + title: 'safety concern', + daysOpen: 73, + project: 'Project A', + openedDate: '2023-10-01', + }, + { + IssueId: '67b7c774432ea2100f63601a', + title: 'worker safety issue', + daysOpen: 73, + project: 'Project C', + openedDate: '2023-10-01', + }, + { + IssueId: '67b7c832432ea2100f63601d', + title: 'minor injury report', + daysOpen: 73, + project: 'Project B', + openedDate: '2023-10-01', + }, + { + IssueId: '68a8d832432ea2100f73642e', + title: 'new feature request', + daysOpen: 45, + project: 'Project A', + openedDate: '2024-01-20', + }, +]; + function LongestOpenIssuesChart() { - const processedData = [ - { - IssueId: '662d6479aa9fee05811b7d2e', - title: ['testing', 'testing'], - daysOpen: 701, - }, - { - IssueId: '662d69367813950e4f454d44', - title: ['testing', 'testing'], - daysOpen: 701, - }, - { - IssueId: '67b7c723432ea2100f636011', - title: ['safty', 'minor injury'], - daysOpen: 73, - }, - { - IssueId: '67b7c774432ea2100f63601a', - title: ['worker safty ', 'minor injury'], - daysOpen: 73, - }, - { - IssueId: '67b7c832432ea2100f63601d', - title: ['safty', 'minor injury'], - daysOpen: 73, - }, - ]; + // State for filters + const [dateRange, setDateRange] = useState({ + start: '2022-01-01', + end: '2024-12-31', + }); + const [selectedProject, setSelectedProject] = useState('All Projects'); + + // Get unique projects for dropdown + const projects = ['All Projects', ...new Set(allIssuesData.map(issue => issue.project))]; + + // Filter data based on selections + const filteredData = allIssuesData + .filter(issue => { + // Filter by project + const projectMatch = selectedProject === 'All Projects' || issue.project === selectedProject; + + // Filter by date range + const dateMatch = + new Date(issue.openedDate) >= new Date(dateRange.start) && + new Date(issue.openedDate) <= new Date(dateRange.end); + + return projectMatch && dateMatch; + }) + .sort((a, b) => b.daysOpen - a.daysOpen); // Sort by daysOpen descending + + // Prepare chart data const data = { - labels: processedData.map(issue => issue.title || issue.id), + labels: filteredData.map(issue => issue.title || issue.IssueId), datasets: [ { label: 'Days Open', - data: processedData.map(issue => issue.daysOpen), + data: filteredData.map(issue => issue.daysOpen), backgroundColor: 'rgba(54, 162, 235, 0.7)', borderColor: 'rgba(54, 162, 235, 1)', borderWidth: 1, @@ -56,13 +101,13 @@ function LongestOpenIssuesChart() { }; const options = { - indexAxis: 'y', // horizontal bar chart + indexAxis: 'y', responsive: true, plugins: { title: { display: true, - text: 'Top 5 Longest Open Issues', - font: { size: 12 }, + text: 'Longest Open Issues', + font: { size: 16 }, }, legend: { display: false }, datalabels: { @@ -77,12 +122,55 @@ function LongestOpenIssuesChart() { title: { display: true, text: 'Days Open' }, }, y: { - title: { display: true, text: 'Issue Title or ID' }, + title: { display: true, text: 'Issue Title' }, }, }, }; - return ; + return ( +
+
+
+ + setDateRange({ ...dateRange, start: e.target.value })} + /> +
+
+ + setDateRange({ ...dateRange, end: e.target.value })} + /> +
+
+ + +
+
+ + {filteredData.length > 0 ? ( + + ) : ( +

No issues match the selected filters.

+ )} +
+ ); } export default LongestOpenIssuesChart; From b68bf9bfa937ac465b2ab8132ca3a03e7104b1b1 Mon Sep 17 00:00:00 2001 From: Nikita Kolla <42067463+ArtemisNyx3@users.noreply.github.com> Date: Sat, 17 May 2025 15:04:59 -0400 Subject: [PATCH 03/17] styles added - Ininital --- .../BMDashboard/Issues/IssueCharts.module.css | 27 ++++++++ .../Issues/LongestOpenIssuesChart.jsx | 65 ++++++++++--------- 2 files changed, 60 insertions(+), 32 deletions(-) create mode 100644 src/components/BMDashboard/Issues/IssueCharts.module.css diff --git a/src/components/BMDashboard/Issues/IssueCharts.module.css b/src/components/BMDashboard/Issues/IssueCharts.module.css new file mode 100644 index 0000000000..e8a582c553 --- /dev/null +++ b/src/components/BMDashboard/Issues/IssueCharts.module.css @@ -0,0 +1,27 @@ +.container { + display: flex; + gap: 0.5rem; + margin-bottom: 1rem; + flex-wrap: wrap; + align-items: center; +} + +.inputGroup { + display: flex; + align-items: center; + gap: 0.5rem; +} + +.inputGroup label { + font-size: 0.875rem; + font-weight: 500; +} + +.inputGroup input, +.inputGroup select { + padding: 0.25rem 0.5rem; + border: 1px solid #ced4da; + border-radius: 0.25rem; + font-size: 0.75rem; +} + diff --git a/src/components/BMDashboard/Issues/LongestOpenIssuesChart.jsx b/src/components/BMDashboard/Issues/LongestOpenIssuesChart.jsx index 150525a8f5..dec60dcfba 100644 --- a/src/components/BMDashboard/Issues/LongestOpenIssuesChart.jsx +++ b/src/components/BMDashboard/Issues/LongestOpenIssuesChart.jsx @@ -10,30 +10,26 @@ import { } from 'chart.js'; import { Bar } from 'react-chartjs-2'; import ChartDataLabels from 'chartjs-plugin-datalabels'; +import styles from './IssueCharts.module.css'; // Register required Chart.js components ChartJS.register(CategoryScale, LinearScale, BarElement, Title, Tooltip, Legend, ChartDataLabels); -// Mock data - in a real app, this would likely come from an API +// Mock data const allIssuesData = [ { IssueId: '662d6479aa9fee05811b7d2e', title: 'testing issue', daysOpen: 701, + totalCost: 12500, project: 'Project A', openedDate: '2022-05-15', }, - // { - // IssueId: '662d69367813950e4f454d44', - // title: 'testing another issue', - // daysOpen: 701, - // project: 'Project B', - // openedDate: '2022-05-15', - // }, { IssueId: '67b7c723432ea2100f636011', title: 'safety concern', daysOpen: 73, + totalCost: 8500, project: 'Project A', openedDate: '2023-10-01', }, @@ -41,6 +37,7 @@ const allIssuesData = [ IssueId: '67b7c774432ea2100f63601a', title: 'worker safety issue', daysOpen: 73, + totalCost: 42000, project: 'Project C', openedDate: '2023-10-01', }, @@ -48,6 +45,7 @@ const allIssuesData = [ IssueId: '67b7c832432ea2100f63601d', title: 'minor injury report', daysOpen: 73, + totalCost: 3200, project: 'Project B', openedDate: '2023-10-01', }, @@ -55,13 +53,13 @@ const allIssuesData = [ IssueId: '68a8d832432ea2100f73642e', title: 'new feature request', daysOpen: 45, + totalCost: 1500, project: 'Project A', openedDate: '2024-01-20', }, ]; -function LongestOpenIssuesChart() { - // State for filters +function IssuesCharts() { const [dateRange, setDateRange] = useState({ start: '2022-01-01', end: '2024-12-31', @@ -129,27 +127,30 @@ function LongestOpenIssuesChart() { return (
-
-
- - setDateRange({ ...dateRange, start: e.target.value })} - /> +
+
+
+ + setDateRange({ ...dateRange, start: e.target.value })} + /> +
+
+ + setDateRange({ ...dateRange, end: e.target.value })} + /> +
-
- - setDateRange({ ...dateRange, end: e.target.value })} - /> -
-
- + +
+ setSelectedProject(e.target.value)} + > + + + +
+
From d002511263c13145a3d6d18fc934b9fd2450ce69 Mon Sep 17 00:00:00 2001 From: Nikita Kolla <42067463+ArtemisNyx3@users.noreply.github.com> Date: Sat, 17 May 2025 18:45:12 -0400 Subject: [PATCH 05/17] added most expensive issues --- .../BMDashboard/Issues/IssueCharts.module.css | 6 ++ .../Issues/LongestOpenIssuesChart.jsx | 84 +++++++++++++++---- 2 files changed, 72 insertions(+), 18 deletions(-) diff --git a/src/components/BMDashboard/Issues/IssueCharts.module.css b/src/components/BMDashboard/Issues/IssueCharts.module.css index 340c110d92..cf7ff5013a 100644 --- a/src/components/BMDashboard/Issues/IssueCharts.module.css +++ b/src/components/BMDashboard/Issues/IssueCharts.module.css @@ -33,9 +33,15 @@ .dateInputs input { max-width: 120px; } + .noData { text-align: center; padding: 2rem; color: #6c757d; font-style: italic; } + +.chartContainer { + width: 100%; + margin: 0; +} diff --git a/src/components/BMDashboard/Issues/LongestOpenIssuesChart.jsx b/src/components/BMDashboard/Issues/LongestOpenIssuesChart.jsx index 942c0aa09f..2782752da7 100644 --- a/src/components/BMDashboard/Issues/LongestOpenIssuesChart.jsx +++ b/src/components/BMDashboard/Issues/LongestOpenIssuesChart.jsx @@ -65,6 +65,7 @@ function IssuesCharts() { end: '2024-12-31', }); const [selectedProject, setSelectedProject] = useState('All Projects'); + const [graphType, setGraphType] = useState('Longest Open'); // Get unique projects for dropdown const projects = ['All Projects', ...new Set(allIssuesData.map(issue => issue.project))]; @@ -82,17 +83,26 @@ function IssuesCharts() { return projectMatch && dateMatch; }) - .sort((a, b) => b.daysOpen - a.daysOpen); // Sort by daysOpen descending + .sort((a, b) => { + if (graphType === 'Longest Open') { + return b.daysOpen - a.daysOpen; + } + return b.totalCost - a.totalCost; + }); - // Prepare chart data + // Prepare chart data based on selected type const data = { labels: filteredData.map(issue => issue.title || issue.IssueId), datasets: [ { - label: 'Days Open', - data: filteredData.map(issue => issue.daysOpen), - backgroundColor: 'rgba(54, 162, 235, 0.7)', - borderColor: 'rgba(54, 162, 235, 1)', + label: graphType === 'Longest Open' ? 'Days Open' : 'Total Cost ($)', + data: filteredData.map(issue => + graphType === 'Longest Open' ? issue.daysOpen : issue.totalCost, + ), + backgroundColor: + graphType === 'Longest Open' ? 'rgba(54, 162, 235, 0.7)' : 'rgba(255, 99, 132, 0.7)', + borderColor: + graphType === 'Longest Open' ? 'rgba(54, 162, 235, 1)' : 'rgba(255, 99, 132, 1)', borderWidth: 1, }, ], @@ -104,23 +114,55 @@ function IssuesCharts() { plugins: { title: { display: true, - text: 'Longest Open Issues', - font: { size: 16 }, + text: graphType === 'Longest Open' ? 'Longest Open Issues' : 'Most Expensive Issues', + font: { size: 14 }, }, legend: { display: false }, datalabels: { anchor: 'end', align: 'right', - formatter: value => `${value} days`, + formatter: value => + graphType === 'Longest Open' ? `${value} days` : `$${value.toLocaleString()}`, color: '#000', + font: { + weight: 'bold', + }, }, }, scales: { x: { - title: { display: true, text: 'Days Open' }, + title: { + display: true, + text: graphType === 'Longest Open' ? 'Days Open' : 'Total Cost ($)', + font: { + size: 14, + }, + }, + ticks: { + font: { + size: 12, + }, + }, }, y: { - title: { display: true, text: 'Issue Title' }, + title: { + display: true, + text: 'Issue Title', + font: { + size: 14, + }, + }, + ticks: { + font: { + size: 12, + }, + }, + }, + }, + elements: { + bar: { + borderRadius: 4, // Rounded corners for bars + borderSkipped: false, // Applies border radius to all sides }, }, }; @@ -132,8 +174,9 @@ function IssuesCharts() { setGraphType(e.target.value)} - className={styles.select} - > - - - -
- -
+ {/*
-
- +
*/}
+
+ + +
-
- {filteredData.length > 0 ? ( + {chartData.length > 0 ? ( ) : ( -

No issues match the selected filters.

+

No issues found.

)}
diff --git a/src/components/BMDashboard/WeeklyProjectSummary/WeeklyProjectSummary.jsx b/src/components/BMDashboard/WeeklyProjectSummary/WeeklyProjectSummary.jsx index 2a152eb228..39508037d5 100644 --- a/src/components/BMDashboard/WeeklyProjectSummary/WeeklyProjectSummary.jsx +++ b/src/components/BMDashboard/WeeklyProjectSummary/WeeklyProjectSummary.jsx @@ -4,8 +4,13 @@ import { useDispatch, useSelector } from 'react-redux'; import { v4 as uuidv4 } from 'uuid'; import WeeklyProjectSummaryHeader from './WeeklyProjectSummaryHeader'; import { fetchAllMaterials } from '../../../actions/bmdashboard/materialsActions'; +import { + fetchLongestOpenIssues, + fetchMostExpensiveIssues, +} from '../../../actions/bmdashboard/issueChartActions'; +import { fetchBMProjects } from '../../../actions/bmdashboard/projectActions'; import QuantityOfMaterialsUsed from './QuantityOfMaterialsUsed/QuantityOfMaterialsUsed'; -import LongestOpenIssuesChart from '../Issues/LongestOpenIssuesChart'; +import IssuesCharts from '../Issues/LongestOpenIssuesChart'; const projectStatusButtons = [ { @@ -132,6 +137,24 @@ export default function WeeklyProjectSummary() { })); }; + const bmProjects = useSelector(state => state.bmProjects || []); + const { longestOpenIssues, mostExpensiveIssues, loading, error } = useSelector( + state => state.issue || [], + ); + // Fetch initial data + useEffect(() => { + if (materials.length === 0) { + dispatch(fetchAllMaterials()); + } + + if (bmProjects.length === 0) { + dispatch(fetchBMProjects()); + } + + dispatch(fetchLongestOpenIssues()); + dispatch(fetchMostExpensiveIssues()); + }, [dispatch, materials.length, bmProjects.length]); + const sections = useMemo( () => [ { @@ -188,7 +211,17 @@ export default function WeeklyProjectSummary() { title: 'Issue Tracking', key: 'Issue Tracking', className: 'small', - content: , + content: ( + dispatch(fetchLongestOpenIssues())} + fetchMostExpensiveIssues={() => dispatch(fetchMostExpensiveIssues())} + /> + ), }, { title: 'Tools and Equipment Tracking', diff --git a/src/constants/bmdashboard/issueConstants.js b/src/constants/bmdashboard/issueConstants.js index 08b118095e..0d22cecd85 100644 --- a/src/constants/bmdashboard/issueConstants.js +++ b/src/constants/bmdashboard/issueConstants.js @@ -6,3 +6,11 @@ export const FETCH_ISSUES_BARCHART_FAILURE = 'FETCH_ISSUES_BARCHART_FAILURE'; export const FETCH_ISSUE_TYPES_YEARS_REQUEST = 'FETCH_ISSUE_TYPES_YEARS_REQUEST'; export const FETCH_ISSUE_TYPES_YEARS_SUCCESS = 'FETCH_ISSUE_TYPES_YEARS_SUCCESS'; export const FETCH_ISSUE_TYPES_YEARS_FAILURE = 'FETCH_ISSUE_TYPES_YEARS_FAILURE'; + +export const FETCH_LONGEST_OPEN_ISSUES_REQUEST = 'FETCH_LONGEST_OPEN_ISSUES_REQUEST'; +export const FETCH_LONGEST_OPEN_ISSUES_SUCCESS = 'FETCH_LONGEST_OPEN_ISSUES_SUCCESS'; +export const FETCH_LONGEST_OPEN_ISSUES_FAILURE = 'FETCH_LONGEST_OPEN_ISSUES_FAILURE'; + +export const FETCH_MOST_EXPENSIVE_ISSUES_REQUEST = 'FETCH_MOST_EXPENSIVE_ISSUES_REQUEST'; +export const FETCH_MOST_EXPENSIVE_ISSUES_SUCCESS = 'FETCH_MOST_EXPENSIVE_ISSUES_SUCCESS'; +export const FETCH_MOST_EXPENSIVE_ISSUES_FAILURE = 'FETCH_MOST_EXPENSIVE_ISSUES_FAILURE'; diff --git a/src/reducers/bmdashboard/issueReducer.js b/src/reducers/bmdashboard/issueReducer.js index 91fd36fd57..0ea0edc3ae 100644 --- a/src/reducers/bmdashboard/issueReducer.js +++ b/src/reducers/bmdashboard/issueReducer.js @@ -5,6 +5,12 @@ import { FETCH_ISSUE_TYPES_YEARS_REQUEST, FETCH_ISSUE_TYPES_YEARS_SUCCESS, FETCH_ISSUE_TYPES_YEARS_FAILURE, + FETCH_LONGEST_OPEN_ISSUES_REQUEST, + FETCH_LONGEST_OPEN_ISSUES_SUCCESS, + FETCH_LONGEST_OPEN_ISSUES_FAILURE, + FETCH_MOST_EXPENSIVE_ISSUES_REQUEST, + FETCH_MOST_EXPENSIVE_ISSUES_SUCCESS, + FETCH_MOST_EXPENSIVE_ISSUES_FAILURE, } from '../../constants/bmdashboard/issueConstants'; const initialState = { @@ -12,6 +18,8 @@ const initialState = { issues: [], issueTypes: [], // Store for issue types years: [], // Store for years + longestOpenIssues: [], // Store for longest open issues + mostExpensiveIssues: [], // Store for most expensive issues error: null, }; @@ -37,6 +45,21 @@ const issueReducer = (state = initialState, action) => { }; case FETCH_ISSUE_TYPES_YEARS_FAILURE: return { ...state, loading: false, error: action.payload }; + // Cases for longest open issues + case FETCH_LONGEST_OPEN_ISSUES_REQUEST: + return { ...state, loading: true, error: null }; + case FETCH_LONGEST_OPEN_ISSUES_SUCCESS: + return { ...state, loading: false, longestOpenIssues: action.payload.data }; + case FETCH_LONGEST_OPEN_ISSUES_FAILURE: + return { ...state, loading: false, error: action.payload }; + + // Cases for most expensive issues + case FETCH_MOST_EXPENSIVE_ISSUES_REQUEST: + return { ...state, loading: true, error: null }; + case FETCH_MOST_EXPENSIVE_ISSUES_SUCCESS: + return { ...state, loading: false, mostExpensiveIssues: action.payload.data }; + case FETCH_MOST_EXPENSIVE_ISSUES_FAILURE: + return { ...state, loading: false, error: action.payload }; default: return state; diff --git a/src/reducers/index.js b/src/reducers/index.js index f4922911b4..9366c223d3 100644 --- a/src/reducers/index.js +++ b/src/reducers/index.js @@ -57,6 +57,7 @@ import { totalOrgSummaryReducer } from './totalOrgSummaryReducer'; import { allUsersTimeEntriesReducer } from './allUsersTimeEntriesReducer'; import HGNFormReducer from './hgnFormReducers'; import { weeklyProjectSummaryReducer } from './bmdashboard/weeklyProjectSummaryReducer'; +import issueReducer from './bmdashboard/issueReducer'; const localReducers = { auth: authReducer, @@ -107,6 +108,7 @@ const localReducers = { bmReusables: reusablesReducer, dashboard: dashboardReducer, weeklyProjectSummary: weeklyProjectSummaryReducer, + issue: issueReducer, }; const sessionReducers = { diff --git a/src/utils/URL.js b/src/utils/URL.js index 5ff2ff74a5..ae90dbc456 100644 --- a/src/utils/URL.js +++ b/src/utils/URL.js @@ -226,6 +226,10 @@ export const ENDPOINTS = { BM_TAG_ADD: `${APIEndpoint}/bm/tags`, BM_TAGS_DELETE: `${APIEndpoint}/bm/tags`, + BM_LONGEST_OPEN_ISSUES: `${APIEndpoint}/issues/longest-open`, + BM_MOST_EXPENSIVE_ISSUES: `${APIEndpoint}/issues/most-expensive`, + + GET_TIME_OFF_REQUESTS: () => `${APIEndpoint}/getTimeOffRequests`, ADD_TIME_OFF_REQUEST: () => `${APIEndpoint}/setTimeOffRequest`, UPDATE_TIME_OFF_REQUEST: id => `${APIEndpoint}/updateTimeOffRequest/${id}`, From 6de2a63a3f6960a665e1af0e37844dcafc44f191 Mon Sep 17 00:00:00 2001 From: Nikita Kolla <42067463+ArtemisNyx3@users.noreply.github.com> Date: Sat, 24 May 2025 17:56:24 -0400 Subject: [PATCH 07/17] removed console statements --- src/actions/bmdashboard/issueChartActions.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/actions/bmdashboard/issueChartActions.js b/src/actions/bmdashboard/issueChartActions.js index 07a77d76a9..283e04e6c0 100644 --- a/src/actions/bmdashboard/issueChartActions.js +++ b/src/actions/bmdashboard/issueChartActions.js @@ -76,7 +76,6 @@ export const fetchLongestOpenIssues = (filters) => async (dispatch) => { payload: response.data }); } catch (error) { - console.error('API Error:', error.response || error); // Log full error dispatch({ type: FETCH_LONGEST_OPEN_ISSUES_FAILURE, @@ -101,7 +100,6 @@ export const fetchMostExpensiveIssues = (filters) => async dispatch => { payload: response.data }); } catch (error) { - console.error('API Error:', error.response || error); // Log full error dispatch({ type: FETCH_MOST_EXPENSIVE_ISSUES_FAILURE, From 6ef45ea719993ff2668fb6386c691bc0aa4a4d74 Mon Sep 17 00:00:00 2001 From: Nikita Kolla <42067463+ArtemisNyx3@users.noreply.github.com> Date: Sun, 1 Jun 2025 02:49:29 -0400 Subject: [PATCH 08/17] All requirements --- src/actions/bmdashboard/issueChartActions.js | 47 ++++++-- .../Issues/LongestOpenIssuesChart.jsx | 100 +++++++++++------- .../WeeklyProjectSummary.jsx | 24 +---- 3 files changed, 99 insertions(+), 72 deletions(-) diff --git a/src/actions/bmdashboard/issueChartActions.js b/src/actions/bmdashboard/issueChartActions.js index 283e04e6c0..0e7228c9aa 100644 --- a/src/actions/bmdashboard/issueChartActions.js +++ b/src/actions/bmdashboard/issueChartActions.js @@ -59,14 +59,40 @@ export const fetchIssueTypesAndYears = () => async dispatch => { } }; -// GET / issues / longest - open ? projectIds = proj1, proj2 & startDate=xxx & endDate=xxx +const formatFilters = (filters) => { + const { projectIds, startDate, endDate } = filters; + const formatted = {}; + + // Only add projectIds if it's a non-empty string or non-empty array + if ( + (typeof projectIds === 'string' && projectIds.trim() !== '') || + (Array.isArray(projectIds) && projectIds.length > 0) + ) { + formatted.projectIds = Array.isArray(projectIds) + ? projectIds.join(',') + : projectIds.trim(); + } + + if (startDate !== undefined) { + formatted.startDate = startDate; + } + + if (endDate !== undefined) { + formatted.endDate = endDate; + } + return formatted; +}; + export const fetchLongestOpenIssues = (filters) => async (dispatch) => { try { dispatch({ type: FETCH_LONGEST_OPEN_ISSUES_REQUEST }); - const response = await axios.get(ENDPOINTS.BM_LONGEST_OPEN_ISSUES, { params: filters }); + const formattedFilters = formatFilters(filters); - // Add validation + const response = await axios.get(ENDPOINTS.BM_LONGEST_OPEN_ISSUES, { params: formattedFilters }); +// console.log( +// "DEBUG - filters --> ",formattedFilters +// ) if (!response.data) { throw new Error('Empty response from server'); } @@ -76,31 +102,30 @@ export const fetchLongestOpenIssues = (filters) => async (dispatch) => { payload: response.data }); } catch (error) { - dispatch({ type: FETCH_LONGEST_OPEN_ISSUES_FAILURE, - payload: error.message || 'Failed to parse server response' + payload: error.message || 'Failed to fetch longest open issues' }); } }; -// GET /issues/most-expensive?projectIds=proj1,proj2&startDate=xxx&endDate=xxx -export const fetchMostExpensiveIssues = (filters) => async dispatch => { +export const fetchMostExpensiveIssues = (filters) => async (dispatch) => { try { dispatch({ type: FETCH_MOST_EXPENSIVE_ISSUES_REQUEST }); - const response = await axios.get(ENDPOINTS.BM_MOST_EXPENSIVE_ISSUES, { params: filters }); + const formattedFilters = formatFilters(filters); + + const response = await axios.get(ENDPOINTS.BM_MOST_EXPENSIVE_ISSUES, { params: formattedFilters }); - if (!response.data) { + if (!response.data) { throw new Error('Empty response from server'); - } + } dispatch({ type: FETCH_MOST_EXPENSIVE_ISSUES_SUCCESS, payload: response.data }); } catch (error) { - dispatch({ type: FETCH_MOST_EXPENSIVE_ISSUES_FAILURE, payload: error.message || 'Failed to fetch most expensive issues', diff --git a/src/components/BMDashboard/Issues/LongestOpenIssuesChart.jsx b/src/components/BMDashboard/Issues/LongestOpenIssuesChart.jsx index 79358a5f65..3bddd74644 100644 --- a/src/components/BMDashboard/Issues/LongestOpenIssuesChart.jsx +++ b/src/components/BMDashboard/Issues/LongestOpenIssuesChart.jsx @@ -1,45 +1,59 @@ import { useState, useEffect } from 'react'; -import { - Chart as ChartJS, - CategoryScale, - LinearScale, - BarElement, - Title, - Tooltip, - Legend, -} from 'chart.js'; +import { useSelector, useDispatch } from 'react-redux'; import { Bar } from 'react-chartjs-2'; import ChartDataLabels from 'chartjs-plugin-datalabels'; import styles from './IssueCharts.module.css'; -// Register required Chart.js components -ChartJS.register(CategoryScale, LinearScale, BarElement, Title, Tooltip, Legend, ChartDataLabels); - -function IssuesCharts({ - bmProjects = [], - longestOpenIssues = [], - mostExpensiveIssues = [], - loading = false, - error = null, +// Import Redux actions +import { fetchLongestOpenIssues, fetchMostExpensiveIssues, -}) { +} from '../../../actions/bmdashboard/issueChartActions'; + +function IssuesCharts({ bmProjects = [] }) { + const dispatch = useDispatch(); + const [graphType, setGraphType] = useState('Longest Open'); const [selectedProject, setSelectedProject] = useState('all'); + const [dateRange, setDateRange] = useState({ start: '', end: '' }); + + const { longestOpenIssues = [], mostExpensiveIssues = [], loading, error } = useSelector( + state => state.issue || {}, + ); + + const formatFilters = ({ projectIds, startDate, endDate }) => { + const formatted = {}; + if ( + (typeof projectIds === 'string' && projectIds.trim() !== '') || + (Array.isArray(projectIds) && projectIds.length > 0) + ) { + formatted.projectIds = Array.isArray(projectIds) ? projectIds.join(',') : projectIds.trim(); + } + if (startDate !== undefined && startDate !== '') { + formatted.startDate = startDate; + } + if (endDate !== undefined && endDate !== '') { + formatted.endDate = endDate; + } + return formatted; + }; - // Fetch data on mount and when graph type or selected project changes useEffect(() => { + const params = formatFilters({ + projectIds: selectedProject === 'all' ? undefined : selectedProject, + startDate: dateRange.start, + endDate: dateRange.end, + }); + if (graphType === 'Longest Open') { - fetchLongestOpenIssues(selectedProject === 'all' ? null : selectedProject); + dispatch(fetchLongestOpenIssues(params)); } else { - fetchMostExpensiveIssues(selectedProject === 'all' ? null : selectedProject); + dispatch(fetchMostExpensiveIssues(params)); } - }, [graphType, selectedProject, fetchLongestOpenIssues, fetchMostExpensiveIssues]); + }, [graphType, selectedProject, dateRange.start, dateRange.end, dispatch]); - // Determine which data to use based on graph type const chartData = graphType === 'Longest Open' ? longestOpenIssues : mostExpensiveIssues; - // Prepare chart data const data = { labels: chartData.map(issue => issue.title || issue.IssueId), datasets: [ @@ -61,6 +75,14 @@ function IssuesCharts({ indexAxis: 'y', responsive: true, plugins: { + legend: { display: false }, + datalabels: { + anchor: 'end', + align: 'right', + formatter: value => (graphType === 'Longest Open' ? `${value} days` : `$${value}`), + color: '#000', + font: { weight: 'bold' }, + }, title: { display: true, text: @@ -77,14 +99,6 @@ function IssuesCharts({ }`, font: { size: 12 }, }, - legend: { display: false }, - datalabels: { - anchor: 'end', - align: 'right', - formatter: value => (graphType === 'Longest Open' ? `${value} days` : `$${value}`), - color: '#000', - font: { weight: 'bold' }, - }, }, scales: { x: { @@ -116,14 +130,14 @@ function IssuesCharts({ return (
- {/*
+
setDateRange({ ...dateRange, start: e.target.value })} + onChange={e => setDateRange(prev => ({ ...prev, start: e.target.value }))} className={styles.input} />
@@ -133,11 +147,11 @@ function IssuesCharts({ id="endDate" type="date" value={dateRange.end} - onChange={e => setDateRange({ ...dateRange, end: e.target.value })} + onChange={e => setDateRange(prev => ({ ...prev, end: e.target.value }))} className={styles.input} />
-
*/} +
+
{chartData.length > 0 ? ( - + ) : (

No issues found.

)} diff --git a/src/components/BMDashboard/WeeklyProjectSummary/WeeklyProjectSummary.jsx b/src/components/BMDashboard/WeeklyProjectSummary/WeeklyProjectSummary.jsx index 39508037d5..6eedce6c48 100644 --- a/src/components/BMDashboard/WeeklyProjectSummary/WeeklyProjectSummary.jsx +++ b/src/components/BMDashboard/WeeklyProjectSummary/WeeklyProjectSummary.jsx @@ -4,10 +4,7 @@ import { useDispatch, useSelector } from 'react-redux'; import { v4 as uuidv4 } from 'uuid'; import WeeklyProjectSummaryHeader from './WeeklyProjectSummaryHeader'; import { fetchAllMaterials } from '../../../actions/bmdashboard/materialsActions'; -import { - fetchLongestOpenIssues, - fetchMostExpensiveIssues, -} from '../../../actions/bmdashboard/issueChartActions'; + import { fetchBMProjects } from '../../../actions/bmdashboard/projectActions'; import QuantityOfMaterialsUsed from './QuantityOfMaterialsUsed/QuantityOfMaterialsUsed'; import IssuesCharts from '../Issues/LongestOpenIssuesChart'; @@ -138,9 +135,7 @@ export default function WeeklyProjectSummary() { }; const bmProjects = useSelector(state => state.bmProjects || []); - const { longestOpenIssues, mostExpensiveIssues, loading, error } = useSelector( - state => state.issue || [], - ); + // Fetch initial data useEffect(() => { if (materials.length === 0) { @@ -150,9 +145,6 @@ export default function WeeklyProjectSummary() { if (bmProjects.length === 0) { dispatch(fetchBMProjects()); } - - dispatch(fetchLongestOpenIssues()); - dispatch(fetchMostExpensiveIssues()); }, [dispatch, materials.length, bmProjects.length]); const sections = useMemo( @@ -211,17 +203,7 @@ export default function WeeklyProjectSummary() { title: 'Issue Tracking', key: 'Issue Tracking', className: 'small', - content: ( - dispatch(fetchLongestOpenIssues())} - fetchMostExpensiveIssues={() => dispatch(fetchMostExpensiveIssues())} - /> - ), + content: , }, { title: 'Tools and Equipment Tracking', From 4e5f46e60712c0860dd8050c0dc62d4dd0343a9a Mon Sep 17 00:00:00 2001 From: Nikita Kolla <42067463+ArtemisNyx3@users.noreply.github.com> Date: Sat, 7 Jun 2025 16:35:41 -0400 Subject: [PATCH 09/17] react date picker --- .../Issues/LongestOpenIssuesChart.jsx | 31 ++++++++++++++----- 1 file changed, 23 insertions(+), 8 deletions(-) diff --git a/src/components/BMDashboard/Issues/LongestOpenIssuesChart.jsx b/src/components/BMDashboard/Issues/LongestOpenIssuesChart.jsx index 3bddd74644..9fda734fe2 100644 --- a/src/components/BMDashboard/Issues/LongestOpenIssuesChart.jsx +++ b/src/components/BMDashboard/Issues/LongestOpenIssuesChart.jsx @@ -2,6 +2,7 @@ import { useState, useEffect } from 'react'; import { useSelector, useDispatch } from 'react-redux'; import { Bar } from 'react-chartjs-2'; import ChartDataLabels from 'chartjs-plugin-datalabels'; +import DatePicker from 'react-datepicker'; import styles from './IssueCharts.module.css'; // Import Redux actions @@ -17,9 +18,10 @@ function IssuesCharts({ bmProjects = [] }) { const [selectedProject, setSelectedProject] = useState('all'); const [dateRange, setDateRange] = useState({ start: '', end: '' }); - const { longestOpenIssues = [], mostExpensiveIssues = [], loading, error } = useSelector( + const { longestOpenIssues = [], mostExpensiveIssues = [] } = useSelector( state => state.issue || {}, ); + // loading, error const formatFilters = ({ projectIds, startDate, endDate }) => { const formatted = {}; @@ -124,8 +126,12 @@ function IssuesCharts({ bmProjects = [] }) { }, }; - if (loading) return
Loading...
; - if (error) return
Error: {error}
; + // if (loading) return
Loading...
; + // if (error) return
Error: {error}
; + + const handleDateChange = (dateName, dateValue) => { + setDateRange({ ...dateRange, [dateName]: dateValue }); + }; return (
@@ -133,22 +139,31 @@ function IssuesCharts({ bmProjects = [] }) {
- setDateRange(prev => ({ ...prev, start: e.target.value }))} + onChange={value => handleDateChange('start', value)} className={styles.input} + /> */} + handleDateChange('start', value)} />
- setDateRange(prev => ({ ...prev, end: e.target.value }))} + // onChange={e => setDateRange(prev => ({ ...prev, end: e.target.value }))} + onChange={value => handleDateChange('end', value)} className={styles.input} + /> */} + handleDateChange('end', value)} />
@@ -181,7 +196,6 @@ function IssuesCharts({ bmProjects = [] }) {
-
{chartData.length > 0 ? ( ) : (

No issues found.

From bc05ad79c7c6288f4a1be50e9c51fe334ee47086 Mon Sep 17 00:00:00 2001 From: Nikita Kolla <42067463+ArtemisNyx3@users.noreply.github.com> Date: Sat, 7 Jun 2025 18:03:40 -0400 Subject: [PATCH 10/17] dark mode initial --- .../BMDashboard/Issues/IssueCharts.module.css | 14 ++ .../Issues/LongestOpenIssuesChart.jsx | 144 +++++++++--------- 2 files changed, 85 insertions(+), 73 deletions(-) diff --git a/src/components/BMDashboard/Issues/IssueCharts.module.css b/src/components/BMDashboard/Issues/IssueCharts.module.css index cf7ff5013a..781416992f 100644 --- a/src/components/BMDashboard/Issues/IssueCharts.module.css +++ b/src/components/BMDashboard/Issues/IssueCharts.module.css @@ -1,3 +1,7 @@ +.dark { + background: #2b3e59; + color: white; +} .container { display: flex; gap: 0.5rem; @@ -45,3 +49,13 @@ width: 100%; margin: 0; } + +.dateDark { + background: #2b3e59; + color: white; +} + +.selectDark{ + background: #2b3e59; + color: white; +} diff --git a/src/components/BMDashboard/Issues/LongestOpenIssuesChart.jsx b/src/components/BMDashboard/Issues/LongestOpenIssuesChart.jsx index 9fda734fe2..a879d8c7a8 100644 --- a/src/components/BMDashboard/Issues/LongestOpenIssuesChart.jsx +++ b/src/components/BMDashboard/Issues/LongestOpenIssuesChart.jsx @@ -1,4 +1,4 @@ -import { useState, useEffect } from 'react'; +import { useState, useEffect, useMemo } from 'react'; import { useSelector, useDispatch } from 'react-redux'; import { Bar } from 'react-chartjs-2'; import ChartDataLabels from 'chartjs-plugin-datalabels'; @@ -21,7 +21,7 @@ function IssuesCharts({ bmProjects = [] }) { const { longestOpenIssues = [], mostExpensiveIssues = [] } = useSelector( state => state.issue || {}, ); - // loading, error + const darkMode = useSelector(state => state.theme.darkMode); const formatFilters = ({ projectIds, startDate, endDate }) => { const formatted = {}; @@ -39,7 +39,6 @@ function IssuesCharts({ bmProjects = [] }) { } return formatted; }; - useEffect(() => { const params = formatFilters({ projectIds: selectedProject === 'all' ? undefined : selectedProject, @@ -52,7 +51,7 @@ function IssuesCharts({ bmProjects = [] }) { } else { dispatch(fetchMostExpensiveIssues(params)); } - }, [graphType, selectedProject, dateRange.start, dateRange.end, dispatch]); + }, [graphType, selectedProject, dateRange.start, dateRange.end, dispatch, darkMode]); const chartData = graphType === 'Longest Open' ? longestOpenIssues : mostExpensiveIssues; @@ -73,107 +72,106 @@ function IssuesCharts({ bmProjects = [] }) { ], }; - const options = { - indexAxis: 'y', - responsive: true, - plugins: { - legend: { display: false }, - datalabels: { - anchor: 'end', - align: 'right', - formatter: value => (graphType === 'Longest Open' ? `${value} days` : `$${value}`), - color: '#000', - font: { weight: 'bold' }, - }, - title: { - display: true, - text: - graphType === 'Longest Open' - ? `Longest Open Issues${ - selectedProject !== 'all' - ? ` (${bmProjects.find(p => p._id === selectedProject)?.name || ''})` - : '' - }` - : `Most Expensive Issues${ - selectedProject !== 'all' - ? ` (${bmProjects.find(p => p._id === selectedProject)?.name || ''})` - : '' - }`, - font: { size: 12 }, - }, - }, - scales: { - x: { + const options = useMemo( + () => ({ + indexAxis: 'y', + responsive: true, + plugins: { + legend: { display: false }, + datalabels: { + anchor: 'end', + align: 'right', + formatter: value => (graphType === 'Longest Open' ? `${value} days` : `$${value}`), + color: darkMode ? '#fff' : '#000', + font: { weight: 'bold' }, + }, title: { display: true, - text: graphType === 'Longest Open' ? 'Days Open' : 'Total Cost ($)', + text: + graphType === 'Longest Open' + ? `Longest Open Issues${ + selectedProject !== 'all' + ? ` (${bmProjects.find(p => p._id === selectedProject)?.name || ''})` + : '' + }` + : `Most Expensive Issues${ + selectedProject !== 'all' + ? ` (${bmProjects.find(p => p._id === selectedProject)?.name || ''})` + : '' + }`, font: { size: 12 }, + color: darkMode ? '#fff' : '#000', }, }, - y: { - title: { - display: true, - text: 'Issue Title', - font: { size: 12 }, + scales: { + x: { + title: { + display: true, + text: graphType === 'Longest Open' ? 'Days Open' : 'Total Cost ($)', + font: { size: 12 }, + color: darkMode ? '#fff' : '#000', + }, + ticks: { + color: darkMode ? '#ccc' : '#333', + }, + }, + y: { + title: { + display: true, + text: 'Issue Title', + font: { size: 12 }, + color: darkMode ? '#fff' : '#000', + }, + ticks: { + color: darkMode ? '#ccc' : '#333', + }, }, }, - }, - elements: { - bar: { - borderRadius: 4, - borderSkipped: false, + elements: { + bar: { + borderRadius: 4, + borderSkipped: false, + }, }, - }, - }; - - // if (loading) return
Loading...
; - // if (error) return
Error: {error}
; + }), + [graphType, selectedProject, darkMode, bmProjects], + ); const handleDateChange = (dateName, dateValue) => { setDateRange({ ...dateRange, [dateName]: dateValue }); }; return ( -
+
- - {/* handleDateChange('start', value)} - className={styles.input} - /> */} + {/* */} + handleDateChange('start', value)} + className={darkMode ? styles.dateDark : ''} />
+ to
- - {/* setDateRange(prev => ({ ...prev, end: e.target.value }))} - onChange={value => handleDateChange('end', value)} - className={styles.input} - /> */} + {/* */} + handleDateChange('end', value)} + className={darkMode ? styles.dateDark : ''} />
- + {/* */}
- + {/* */}