+
-
-
- setDateRange({ ...dateRange, end: e.target.value })}
- />
-
-
-
+
+
+
);
}
-export default LongestOpenIssuesChart;
+export default IssuesCharts;
From 7c525e71cbd02c27c230311b65c4c62a83ac82df Mon Sep 17 00:00:00 2001
From: Nikita Kolla <42067463+ArtemisNyx3@users.noreply.github.com>
Date: Sat, 17 May 2025 17:08:49 -0400
Subject: [PATCH 04/17] refined styles and added type dropdown
---
.../BMDashboard/Issues/IssueCharts.module.css | 18 ++++++++++++++++--
.../Issues/LongestOpenIssuesChart.jsx | 12 ++++++++++++
2 files changed, 28 insertions(+), 2 deletions(-)
diff --git a/src/components/BMDashboard/Issues/IssueCharts.module.css b/src/components/BMDashboard/Issues/IssueCharts.module.css
index e8a582c553..340c110d92 100644
--- a/src/components/BMDashboard/Issues/IssueCharts.module.css
+++ b/src/components/BMDashboard/Issues/IssueCharts.module.css
@@ -9,11 +9,11 @@
.inputGroup {
display: flex;
align-items: center;
- gap: 0.5rem;
+ gap: 0.35rem;
}
.inputGroup label {
- font-size: 0.875rem;
+ font-size: 0.75rem;
font-weight: 500;
}
@@ -25,3 +25,17 @@
font-size: 0.75rem;
}
+.dateInputs {
+ display: flex;
+ gap: 0.5rem;
+}
+
+.dateInputs input {
+ max-width: 120px;
+}
+.noData {
+ text-align: center;
+ padding: 2rem;
+ color: #6c757d;
+ font-style: italic;
+}
diff --git a/src/components/BMDashboard/Issues/LongestOpenIssuesChart.jsx b/src/components/BMDashboard/Issues/LongestOpenIssuesChart.jsx
index dec60dcfba..942c0aa09f 100644
--- a/src/components/BMDashboard/Issues/LongestOpenIssuesChart.jsx
+++ b/src/components/BMDashboard/Issues/LongestOpenIssuesChart.jsx
@@ -128,6 +128,18 @@ function IssuesCharts() {
return (
+
+
+
+
+
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() {
@@ -157,6 +201,7 @@ function IssuesCharts() {
type="date"
value={dateRange.end}
onChange={e => setDateRange({ ...dateRange, end: e.target.value })}
+ className={styles.input}
/>
@@ -167,6 +212,7 @@ function IssuesCharts() {
id="project"
value={selectedProject}
onChange={e => setSelectedProject(e.target.value)}
+ className={styles.select}
>
{projects.map(project => (
- {filteredData.length > 0 ? (
-
- ) : (
-
No issues match the selected filters.
- )}
+
+ {filteredData.length > 0 ? (
+
+ ) : (
+
No issues match the selected filters.
+ )}
+
);
}
From 18fff16dd4fe7646cd07cd7c5923a96412c8ec78 Mon Sep 17 00:00:00 2001
From: Nikita Kolla <42067463+ArtemisNyx3@users.noreply.github.com>
Date: Sat, 24 May 2025 17:53:51 -0400
Subject: [PATCH 06/17] redux setup
---
src/actions/bmdashboard/issueChartActions.js | 57 ++++++
.../Issues/LongestOpenIssuesChart.jsx | 188 ++++++------------
.../WeeklyProjectSummary.jsx | 37 +++-
src/constants/bmdashboard/issueConstants.js | 8 +
src/reducers/bmdashboard/issueReducer.js | 23 +++
src/reducers/index.js | 2 +
src/utils/URL.js | 4 +
7 files changed, 195 insertions(+), 124 deletions(-)
diff --git a/src/actions/bmdashboard/issueChartActions.js b/src/actions/bmdashboard/issueChartActions.js
index 961a2c41e9..07a77d76a9 100644
--- a/src/actions/bmdashboard/issueChartActions.js
+++ b/src/actions/bmdashboard/issueChartActions.js
@@ -6,6 +6,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';
import { ENDPOINTS } from '../../utils/URL'; // Import the endpoints
@@ -52,3 +58,54 @@ export const fetchIssueTypesAndYears = () => async dispatch => {
});
}
};
+
+// GET / issues / longest - open ? projectIds = proj1, proj2 & startDate=xxx & endDate=xxx
+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 });
+
+ // Add validation
+ if (!response.data) {
+ throw new Error('Empty response from server');
+ }
+
+ dispatch({
+ type: FETCH_LONGEST_OPEN_ISSUES_SUCCESS,
+ payload: response.data
+ });
+ } catch (error) {
+ console.error('API Error:', error.response || error); // Log full error
+
+ dispatch({
+ type: FETCH_LONGEST_OPEN_ISSUES_FAILURE,
+ payload: error.message || 'Failed to parse server response'
+ });
+ }
+};
+
+// GET /issues/most-expensive?projectIds=proj1,proj2&startDate=xxx&endDate=xxx
+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 });
+
+ if (!response.data) {
+ throw new Error('Empty response from server');
+ }
+
+ dispatch({
+ type: FETCH_MOST_EXPENSIVE_ISSUES_SUCCESS,
+ payload: response.data
+ });
+ } catch (error) {
+ console.error('API Error:', error.response || error); // Log full 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 2782752da7..79358a5f65 100644
--- a/src/components/BMDashboard/Issues/LongestOpenIssuesChart.jsx
+++ b/src/components/BMDashboard/Issues/LongestOpenIssuesChart.jsx
@@ -1,4 +1,4 @@
-import { useState } from 'react';
+import { useState, useEffect } from 'react';
import {
Chart as ChartJS,
CategoryScale,
@@ -15,88 +15,37 @@ import styles from './IssueCharts.module.css';
// Register required Chart.js components
ChartJS.register(CategoryScale, LinearScale, BarElement, Title, Tooltip, Legend, ChartDataLabels);
-// Mock data
-const allIssuesData = [
- {
- IssueId: '662d6479aa9fee05811b7d2e',
- title: 'testing issue',
- daysOpen: 701,
- totalCost: 12500,
- project: 'Project A',
- openedDate: '2022-05-15',
- },
- {
- IssueId: '67b7c723432ea2100f636011',
- title: 'safety concern',
- daysOpen: 73,
- totalCost: 8500,
- project: 'Project A',
- openedDate: '2023-10-01',
- },
- {
- IssueId: '67b7c774432ea2100f63601a',
- title: 'worker safety issue',
- daysOpen: 73,
- totalCost: 42000,
- project: 'Project C',
- openedDate: '2023-10-01',
- },
- {
- IssueId: '67b7c832432ea2100f63601d',
- title: 'minor injury report',
- daysOpen: 73,
- totalCost: 3200,
- project: 'Project B',
- openedDate: '2023-10-01',
- },
- {
- IssueId: '68a8d832432ea2100f73642e',
- title: 'new feature request',
- daysOpen: 45,
- totalCost: 1500,
- project: 'Project A',
- openedDate: '2024-01-20',
- },
-];
-
-function IssuesCharts() {
- const [dateRange, setDateRange] = useState({
- start: '2022-01-01',
- end: '2024-12-31',
- });
- const [selectedProject, setSelectedProject] = useState('All Projects');
+function IssuesCharts({
+ bmProjects = [],
+ longestOpenIssues = [],
+ mostExpensiveIssues = [],
+ loading = false,
+ error = null,
+ fetchLongestOpenIssues,
+ fetchMostExpensiveIssues,
+}) {
const [graphType, setGraphType] = useState('Longest Open');
+ const [selectedProject, setSelectedProject] = useState('all');
- // 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);
+ // Fetch data on mount and when graph type or selected project changes
+ useEffect(() => {
+ if (graphType === 'Longest Open') {
+ fetchLongestOpenIssues(selectedProject === 'all' ? null : selectedProject);
+ } else {
+ fetchMostExpensiveIssues(selectedProject === 'all' ? null : selectedProject);
+ }
+ }, [graphType, selectedProject, fetchLongestOpenIssues, fetchMostExpensiveIssues]);
- return projectMatch && dateMatch;
- })
- .sort((a, b) => {
- if (graphType === 'Longest Open') {
- return b.daysOpen - a.daysOpen;
- }
- return b.totalCost - a.totalCost;
- });
+ // Determine which data to use based on graph type
+ const chartData = graphType === 'Longest Open' ? longestOpenIssues : mostExpensiveIssues;
- // Prepare chart data based on selected type
+ // Prepare chart data
const data = {
- labels: filteredData.map(issue => issue.title || issue.IssueId),
+ labels: chartData.map(issue => issue.title || issue.IssueId),
datasets: [
{
label: graphType === 'Longest Open' ? 'Days Open' : 'Total Cost ($)',
- data: filteredData.map(issue =>
+ data: chartData.map(issue =>
graphType === 'Longest Open' ? issue.daysOpen : issue.totalCost,
),
backgroundColor:
@@ -114,19 +63,27 @@ function IssuesCharts() {
plugins: {
title: {
display: true,
- text: graphType === 'Longest Open' ? 'Longest Open Issues' : 'Most Expensive Issues',
- font: { size: 14 },
+ 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 },
},
legend: { display: false },
datalabels: {
anchor: 'end',
align: 'right',
- formatter: value =>
- graphType === 'Longest Open' ? `${value} days` : `$${value.toLocaleString()}`,
+ formatter: value => (graphType === 'Longest Open' ? `${value} days` : `$${value}`),
color: '#000',
- font: {
- weight: 'bold',
- },
+ font: { weight: 'bold' },
},
},
scales: {
@@ -134,56 +91,32 @@ function IssuesCharts() {
title: {
display: true,
text: graphType === 'Longest Open' ? 'Days Open' : 'Total Cost ($)',
- font: {
- size: 14,
- },
- },
- ticks: {
- font: {
- size: 12,
- },
+ font: { size: 12 },
},
},
y: {
title: {
display: true,
text: 'Issue Title',
- font: {
- size: 14,
- },
- },
- ticks: {
- font: {
- size: 12,
- },
+ font: { size: 12 },
},
},
},
elements: {
bar: {
- borderRadius: 4, // Rounded corners for bars
- borderSkipped: false, // Applies border radius to all sides
+ borderRadius: 4,
+ borderSkipped: false,
},
},
};
+ if (loading) return
Loading...
;
+ if (error) return
Error: {error}
;
+
return (
-
-
-
-
-
-
*/}
+
+
+
+
-
- {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 (
+
{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 = [] }) {
@@ -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 (
-
+
-
+ {/* */}
-
+ {/*
*/}