Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
85 changes: 62 additions & 23 deletions src/actions/bmdashboard/issueChartActions.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,23 @@ import {
FETCH_ISSUES_BARCHART_REQUEST,
FETCH_ISSUES_BARCHART_SUCCESS,
FETCH_ISSUES_BARCHART_FAILURE,
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,
SET_DATE_FILTER,
SET_PROJECT_FILTER,
} from '../../constants/bmdashboard/issueConstants';
import { ENDPOINTS, ApiEndpoint } from '../../utils/URL';
import { ENDPOINTS } from '../../utils/URL';

// eslint-disable-next-line import/prefer-default-export
export const fetchIssues = () => async dispatch => {
try {
dispatch({ type: FETCH_ISSUES_BARCHART_REQUEST });

// Fetch all data without applying any filters
const { data } = await axios.get(ENDPOINTS.BM_ISSUE_CHART);
dispatch({ type: FETCH_ISSUES_BARCHART_SUCCESS, payload: data });
} catch (error) {
Expand All @@ -27,34 +30,70 @@ export const fetchIssues = () => async dispatch => {
}
};


export const fetchLongestOpenIssues = (dates = [], projects = []) => async dispatch => {
export const fetchIssueTypesAndYears = () => async dispatch => {
try {
dispatch({ type: FETCH_LONGEST_OPEN_ISSUES_REQUEST });
dispatch({ type: FETCH_ISSUE_TYPES_YEARS_REQUEST });
const { data } = await axios.get(ENDPOINTS.BM_ISSUE_CHART);
const issueTypes = [...new Set(data.map(item => item._id.issueType))];
const years = [...new Set(data.map(item => item._id.issueYear))];
dispatch({ type: FETCH_ISSUE_TYPES_YEARS_SUCCESS, payload: { issueTypes, years } });
} catch (error) {
dispatch({
type: FETCH_ISSUE_TYPES_YEARS_FAILURE,
payload: error.message || 'Failed to fetch issue types and years',
});
}
};

const params = new URLSearchParams();
if (dates.length) params.append('dates', dates.join(','));
if (projects.length) params.append('projects', projects.join(','));
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;
};

const response = await axios.get(`${ApiEndpoint}/bm/issues/longest-open?${params}`);
dispatch({
type: FETCH_LONGEST_OPEN_ISSUES_SUCCESS,
payload: response.data,
export const fetchLongestOpenIssues = filters => async dispatch => {
try {
dispatch({ type: FETCH_LONGEST_OPEN_ISSUES_REQUEST });
const formattedFilters = formatFilters(filters);
const response = await axios.get(ENDPOINTS.BM_LONGEST_OPEN_ISSUES, {
params: formattedFilters,
});
dispatch({ type: FETCH_LONGEST_OPEN_ISSUES_SUCCESS, payload: response.data });
} catch (error) {
dispatch({
type: FETCH_LONGEST_OPEN_ISSUES_FAILURE,
payload: error.message,
payload: error.message || 'Failed to fetch longest open issues',
});
}
};

export const fetchMostExpensiveIssues = filters => async dispatch => {
try {
dispatch({ type: FETCH_MOST_EXPENSIVE_ISSUES_REQUEST });
const formattedFilters = formatFilters(filters);
const response = await axios.get(ENDPOINTS.BM_MOST_EXPENSIVE_ISSUES, {
params: formattedFilters,
});
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',
});
}
};

export const setDateFilter = dates => ({
type: SET_DATE_FILTER,
payload: dates,
});
export const setDateFilter = dates => ({ type: SET_DATE_FILTER, payload: dates });

export const setProjectFilter = projects => ({
type: SET_PROJECT_FILTER,
payload: projects,
});
export const setProjectFilter = projects => ({ type: SET_PROJECT_FILTER, payload: projects });
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,138 @@
Issue Charts Module CSS
=========================== */

/* ----- Longest/Most Expensive Bar Charts ----- */
.dark {
background: #2b3e59;
color: white;
}

.dark h2 {
color: white;
}

.container {
display: flex;
gap: 0.5rem;
margin-bottom: 1rem;
flex-wrap: wrap;
align-items: center;
padding: 1rem;
}

.inputGroup {
display: flex;
align-items: center;
gap: 0.35rem;
}

.dateInputs {
display: flex;
gap: 0.5rem;
align-items: center;
}

.multiSelectWrapper {
min-width: 220px;
flex: 1;
}

.noData {
text-align: center;
padding: 2rem;
color: #6c757d;
font-style: italic;
}

.dateInput {
border: 1px solid #ced4da;
border-radius: 0.25rem;
padding: 0.25rem 0.5rem;
font-size: 0.875rem;
}

.dateDark {
background: #2b3e59;
color: white;
border: 1px solid #4a5a72;
border-radius: 0.25rem;
padding: 0.25rem 0.5rem;
font-size: 0.875rem;
}

/* Fix react-datepicker navigation arrow contrast — scoped via calendarClassName
so it isn't overridden by other components' global datepicker overrides
(the calendar renders in a portal, outside this module's DOM scope) */
.lightCalendar :global(.react-datepicker__navigation-icon::before) {
border-color: #333 !important;
border-width: 3px 3px 0 0 !important;
}

.lightCalendar :global(.react-datepicker__navigation:hover .react-datepicker__navigation-icon::before) {
border-color: #000 !important;
}

/* Dark mode datepicker — scoped via calendarClassName prop */
.darkCalendar {
background-color: #1e2d42 !important;
border-color: #4a5a72 !important;
}

.darkCalendar :global(.react-datepicker__header) {
background-color: #243450 !important;
border-bottom-color: #4a5a72 !important;
}

.darkCalendar :global(.react-datepicker__current-month),
.darkCalendar :global(.react-datepicker__day-name) {
color: #e0e6f0 !important;
}

.darkCalendar :global(.react-datepicker__day) {
color: #ccd1dc !important;
}

.darkCalendar :global(.react-datepicker__day:hover) {
background-color: #4a5a72 !important;
color: #fff !important;
}

.darkCalendar :global(.react-datepicker__day--selected),
.darkCalendar :global(.react-datepicker__day--selected:hover) {
background-color: #1e40af !important;
color: #fff !important;
}

.darkCalendar :global(.react-datepicker__day--today) {
background-color: #1e3a5f !important;
color: #fff !important;
font-weight: bold !important;
}

.darkCalendar :global(.react-datepicker__day--outside-month) {
color: #6b7a8d !important;
}

.darkCalendar :global(.react-datepicker__navigation-icon::before) {
border-color: #ccc !important;
}

.select {
padding: 0.25rem 0.5rem;
border: 1px solid #ced4da;
border-radius: 0.25rem;
font-size: 0.75rem;
}

.selectDark {
background: #2b3e59;
color: white;
border: 1px solid #4a5a72;
border-radius: 0.25rem;
padding: 0.25rem 0.5rem;
font-size: 0.75rem;
}

/* ----- Containers ----- */
.issueChartContainer,
.issueChartEventContainer {
Expand Down Expand Up @@ -73,9 +205,9 @@ Issue Charts Module CSS

.chartButtonActive,
.chartButtonActiveDark {
background-color: #4caf50;
background-color: #276228;
color: #fff;
border-color: #4caf50;
border-color: #276228;
}

/* ----- Chart Wrapper ----- */
Expand Down
Loading
Loading