Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
9032777
feat: add constants and endpoint for material cost correlation
Aditya-gam Dec 19, 2025
1ab27fb
Revert "feat: add constants and endpoint for material cost correlation"
Aditya-gam Dec 19, 2025
e9b4d05
feat: add constants and endpoint for material cost correlation
Aditya-gam Dec 19, 2025
7af6ead
feat: add Redux actions and reducer for material cost correlation
Aditya-gam Dec 19, 2025
b1573ba
feat: implement Material Cost Correlation chart component
Aditya-gam Dec 19, 2025
2dc1b57
feat: add comprehensive styling and dark mode support for material co…
Aditya-gam Dec 19, 2025
c414edc
feat: integrate Material Cost Correlation chart into Weekly Project S…
Aditya-gam Dec 19, 2025
ec30bc2
feat: enhance error handling and logging for material cost correlation
Aditya-gam Dec 19, 2025
13751a7
refactor: replace console statements with logger service
Aditya-gam Dec 19, 2025
f85403b
refactor: improve code quality and documentation
Aditya-gam Dec 19, 2025
b992595
refactor: convert to single combined bar and line chart
Aditya-gam Dec 19, 2025
8086799
chore: remove unused ChartTypeToggle component files
Aditya-gam Dec 19, 2025
b274ebf
refactor: remove chart information section and fix axis text overflow
Aditya-gam Dec 19, 2025
15aaa89
refactor: reduce padding to increase chart size
Aditya-gam Dec 19, 2025
646c72a
fix: optimize padding and margins for smaller screens
Aditya-gam Dec 19, 2025
71ee921
fix: remove redundant conditional in error handling
Aditya-gam Dec 19, 2025
dd8b535
Merge branch 'development' into Aditya-feat/material-cost-correlation…
Aditya-gam Feb 19, 2026
b8630f9
fix(material-cost-correlation): responsive X-axis and Y-axis for mobi…
Aditya-gam Feb 19, 2026
66c32dc
fix(material-cost-correlation): prevent horizontal overflow on small …
Aditya-gam Feb 19, 2026
2a9d2de
refactor: minor fixes to improve code quality and fix linting errors
Aditya-gam Feb 19, 2026
dacb0ea
Merge branch 'development' into Aditya-feat/material-cost-correlation…
Aditya-gam Mar 3, 2026
b9fcf74
refactor(MaterialCostCorrelation): move preset date buttons below fil…
Aditya-gam Mar 3, 2026
70e13cd
fix(MaterialCostCorrelation): use container width for chart breakpoints
Aditya-gam Mar 3, 2026
59a8bd2
feat(WeeklyProjectSummary): add Material Consumption 3-column grid la…
Aditya-gam Mar 3, 2026
65f9db3
fix(WeeklyProjectSummary): collapse Material Consumption grid at 1080…
Aditya-gam Mar 3, 2026
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
232 changes: 232 additions & 0 deletions src/actions/bmdashboard/materialCostCorrelationActions.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,232 @@
import axios from 'axios';
import { toast } from 'react-toastify';
import {
FETCH_MATERIAL_COST_CORRELATION_REQUEST,
FETCH_MATERIAL_COST_CORRELATION_SUCCESS,
FETCH_MATERIAL_COST_CORRELATION_FAILURE,
SET_MATERIAL_COST_CORRELATION_PROJECT_FILTER,
SET_MATERIAL_COST_CORRELATION_MATERIAL_TYPE_FILTER,
SET_MATERIAL_COST_CORRELATION_DATE_RANGE_FILTER,
RESET_MATERIAL_COST_CORRELATION_FILTERS,
} from '../../constants/bmdashboard/materialCostCorrelationConstants';
import { ENDPOINTS } from '../../utils/URL';
import logger from '../../services/logService';

// Action Creators
export const fetchMaterialCostCorrelationRequest = () => ({
type: FETCH_MATERIAL_COST_CORRELATION_REQUEST,
});

export const fetchMaterialCostCorrelationSuccess = payload => ({
type: FETCH_MATERIAL_COST_CORRELATION_SUCCESS,
payload,
});

export const fetchMaterialCostCorrelationFailure = error => ({
type: FETCH_MATERIAL_COST_CORRELATION_FAILURE,
payload: error,
});

export const setProjectFilter = projectIds => ({
type: SET_MATERIAL_COST_CORRELATION_PROJECT_FILTER,
payload: projectIds,
});

export const setMaterialTypeFilter = materialTypeIds => ({
type: SET_MATERIAL_COST_CORRELATION_MATERIAL_TYPE_FILTER,
payload: materialTypeIds,
});

export const setDateRangeFilter = (startDate, endDate) => ({
type: SET_MATERIAL_COST_CORRELATION_DATE_RANGE_FILTER,
payload: { startDate, endDate },
});

export const resetFilters = () => ({
type: RESET_MATERIAL_COST_CORRELATION_FILTERS,
});

// Helper function to format date to YYYY-MM-DD
const formatDate = date => {
if (!date) return null;
if (typeof date === 'string') return date;
if (date instanceof Date) {
return date.toISOString().split('T')[0];
}
return null;
};

// Helper function to validate MongoDB ObjectId format
const isValidObjectId = id => {
if (!id || typeof id !== 'string') return false;
return /^[0-9a-fA-F]{24}$/.test(id);
};

// Helper function to validate date range
const validateDateRange = (startDate, endDate) => {
if (!startDate || !endDate) return { valid: true };
const start = startDate instanceof Date ? startDate : new Date(startDate);
const end = endDate instanceof Date ? endDate : new Date(endDate);
if (start > end) {
return {
valid: false,
error: 'Start date cannot be after end date',
};
}
return { valid: true };
};

// Thunk Action for Fetching Data
export const fetchMaterialCostCorrelation = (
projectIds = [],
materialTypeIds = [],
startDate = null,
endDate = null,
) => async dispatch => {
try {
// Client-side validation
const dateValidation = validateDateRange(startDate, endDate);
if (!dateValidation.valid) {
const validationError = dateValidation.error;
logger.logInfo(
`[MaterialCostCorrelation] Validation failed: ${validationError} - Start: ${startDate}, End: ${endDate}`,
);
toast.warning(validationError, {
toastId: 'materialCostCorrelationValidation',
autoClose: 5000,
});
dispatch(fetchMaterialCostCorrelationFailure(validationError));
return;
}

// Validate ObjectIds if provided
const invalidProjectIds =
projectIds && projectIds.length > 0
? projectIds.filter(id => !isValidObjectId(id))
: [];
const invalidMaterialTypeIds =
materialTypeIds && materialTypeIds.length > 0
? materialTypeIds.filter(id => !isValidObjectId(id))
: [];

if (invalidProjectIds.length > 0 || invalidMaterialTypeIds.length > 0) {
const validationError = 'Invalid project or material type IDs provided';
logger.logInfo(
`[MaterialCostCorrelation] Validation failed: ${validationError} - Invalid Project IDs: ${JSON.stringify(invalidProjectIds)}, Invalid Material Type IDs: ${JSON.stringify(invalidMaterialTypeIds)}`,
);
toast.warning(validationError, {
toastId: 'materialCostCorrelationValidation',
autoClose: 5000,
});
dispatch(fetchMaterialCostCorrelationFailure(validationError));
return;
}

dispatch(fetchMaterialCostCorrelationRequest());

// Build query parameters
const params = new URLSearchParams();

if (projectIds && projectIds.length > 0) {
params.append('projectId', projectIds.join(','));
}

if (materialTypeIds && materialTypeIds.length > 0) {
params.append('materialType', materialTypeIds.join(','));
}

const formattedStartDate = formatDate(startDate);
if (formattedStartDate) {
params.append('startDate', formattedStartDate);
}

const formattedEndDate = formatDate(endDate);
if (formattedEndDate) {
params.append('endDate', formattedEndDate);
}

// Construct URL with query string
const queryString = params.toString();
const url = queryString
? `${ENDPOINTS.BM_MATERIAL_COST_CORRELATION}?${queryString}`
: ENDPOINTS.BM_MATERIAL_COST_CORRELATION;

// Make API request
const response = await axios.get(url);

// Validate response structure
if (!response.data) {
throw new Error('Invalid response: missing data property');
}

// Dispatch success action with response data
dispatch(fetchMaterialCostCorrelationSuccess(response.data));
} catch (error) {
// Extract error message based on error type
let errorMessage = 'Failed to fetch material cost correlation data';
let errorType = 'unknown';
let statusCode = null;

if (error.response) {
// Server responded with error status
statusCode = error.response.status;
errorType = 'server';

if (statusCode === 401) {
errorMessage = 'Your session has expired. Please log in again.';
// Store attempted action for retry after login
sessionStorage.setItem(
'materialCostCorrelationRetryAction',
JSON.stringify({ projectIds, materialTypeIds, startDate, endDate }),
);
// Redirect to login after short delay
setTimeout(() => {
window.location.href = '/bmdashboard/login';
}, 2000);
} else if (statusCode === 403) {
errorMessage =
'You do not have permission to access this data. Please contact an administrator for BM Portal access.';
errorType = 'permission';
} else if (statusCode >= 500) {
errorMessage = 'Server error. Please try again later or contact support if the issue persists.';
errorType = 'server';
} else {
errorMessage =
error.response.data?.error ||
error.response.data?.message ||
`Request failed with status ${statusCode}`;
}
} else if (error.request) {
// Request was made but no response received
errorMessage = 'Network error: Unable to connect to server. Please check your internet connection.';
errorType = 'network';
} else {
// Something else happened
errorMessage = error.message || errorMessage;
if (errorMessage.includes('Invalid response')) {
errorType = 'malformed';
}
}

logger.logError(
new Error(
`[MaterialCostCorrelation] Error - Type: ${errorType}, Status: ${statusCode}, Message: ${errorMessage}`,
),
);

// Dispatch failure action
dispatch(fetchMaterialCostCorrelationFailure(errorMessage));

// Display error toast notification with appropriate configuration
const toastOptions = {
toastId: `materialCostCorrelationError-${errorType}`,
autoClose: errorType === 'network' ? false : errorType === 'permission' ? false : 5000,
position: 'top-right',
closeOnClick: true,
pauseOnHover: true,
};

toast.error(errorMessage, toastOptions);
}
};

Loading
Loading