Skip to content
Open
639 changes: 639 additions & 0 deletions src/components/BMDashboard/CostPrediction/CostPredictionPage.jsx

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,247 @@
/* Cost Prediction Page Styles */
.costPredictionPage {
padding: 20px;
background-color: var(--card-bg);
min-height: 100vh;
color: var(--text-color);
}

/* Chart Title Container */
.chartTitleContainer {
display: flex;
align-items: center;
justify-content: center;
margin-bottom: 15px;
padding: 0 10px;
position: relative;
}

.costPredictionTitle {
margin: 0;
font-size: 18px;
font-weight: 600;
color: var(--text-color);
text-align: center;
}

.costPredictionInfoButton {
background: none;
border: none;
cursor: pointer;
padding: 4px;
border-radius: 4px;
color: var(--text-color);
opacity: 0.7;
transition: opacity 0.2s ease;
position: absolute;
right: 10px;
}

.costPredictionInfoButton:hover {
opacity: 1;
}

.costPredictionTooltip {
max-width: 300px;
font-size: 12px;
line-height: 1.4;
z-index: 1000 !important;
position: fixed !important;
}

/* Dropdown Container */
.dropdownContainer {
display: flex;
gap: 10px;
margin-bottom: 15px;
padding: 0 10px;
flex-wrap: wrap;
}

.dropdownItem {
flex: 1;
min-width: 120px;
}

.multiSelect {
min-width: 150px;
}

/* Chart Wrapper - protects from external styles */
.costPredictionWrapper {
width: 100%;
height: 100%;
padding: 10px;
box-sizing: border-box;
position: relative;
background-color: var(--card-bg);
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}

/* Chart Container */
.costPredictionChartContainer {
position: relative;
height: 500px;
width: 100%;
padding: 0 10px;
margin-bottom: 5px;
background-color: transparent;
}

/* Chart Legend */
.chartLegend {
width: 100%;
text-align: center;
font-size: 14px;
margin-top: 2px;
color: var(--text-color);
display: flex;
justify-content: center;
gap: 6px;
flex-wrap: wrap;
align-items: center;
}

.costChartLoading,
.costChartError,
.costChartEmpty {
display: flex;
align-items: center;
justify-content: center;
height: 100%;
color: var(--text-color);
font-size: 16px;
}

.costChartError {
color: #ff4d4f;
}

/* Chart Tooltip Styling */
.costPredictionChartContainer :global(.recharts-tooltip-wrapper) {
z-index: 1000 !important;
}

.costPredictionChartContainer :global(.recharts-default-tooltip) {
background-color: var(--card-bg) !important;
border: 1px solid var(--button-hover) !important;
border-radius: 4px !important;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15) !important;
padding: 8px !important;
}

.costPredictionChartContainer :global(.recharts-tooltip-label) {
color: var(--text-color) !important;
font-weight: bold !important;
margin-bottom: 4px !important;
}

.costPredictionChartContainer :global(.recharts-tooltip-item) {
color: var(--text-color) !important;
padding: 2px 0 !important;
}

.costPredictionChartContainer :global(.recharts-tooltip-item-name) {
color: var(--text-color) !important;
}

.costPredictionChartContainer :global(.recharts-tooltip-item-value) {
color: var(--text-color) !important;
font-weight: bold !important;
}

/* Dark mode styles */
:global(.dark-mode) .costPredictionPage {
background-color: #1e2736;
color: #e0e0e0;
}

:global(.dark-mode) .costPredictionWrapper {
background-color: #2c3344;
}

:global(.dark-mode) .costPredictionChartContainer {
background-color: #1e2736;
color: #e0e0e0;
}

:global(.dark-mode) .costPredictionChartContainer :global(.recharts-default-tooltip) {
background-color: #2c3344 !important;
border-color: #364156 !important;
color: #e0e0e0 !important;
}

:global(.dark-mode) .costPredictionTitle {
color: #e0e0e0;
}

:global(.dark-mode) .costPredictionInfoButton {
color: #e0e0e0;
}

:global(.dark-mode) .costPredictionInfoButton:hover {
color: #ffffff;
}

/* Dark-mode body overrides for Recharts (replaces injected <style>) */
:global(.dark-mode-body) :global(.recharts-wrapper),
:global(.dark-mode-body) :global(.recharts-surface) {
background-color: #1e2736 !important;
}

:global(.dark-mode-body) :global(.recharts-cartesian-grid-horizontal) line,
:global(.dark-mode-body) :global(.recharts-cartesian-grid-vertical) line {
stroke: #364156 !important;
}

:global(.dark-mode-body) :global(.recharts-text) {
fill: #e0e0e0 !important;
}

:global(.dark-mode-body) :global(.recharts-default-legend) {
background-color: #1e2736 !important;
}

:global(.dark-mode-body) :global(.recharts-legend-item-text) {
color: #e0e0e0 !important;
}

:global(.dark-mode-body) :global(.recharts-tooltip-wrapper) {
background-color: transparent !important;
}

/* Responsive adjustments */
@media (max-width: 768px) {
.dropdownContainer {
flex-direction: column;
}

.dropdownItem {
min-width: 100%;
}

.costPredictionTitle {
font-size: 16px;
}

.costPredictionChartContainer {
height: 400px;
}

.costPredictionPage {
padding: 10px;
}
}

@media (max-width: 480px) {
.chartTitleContainer {
flex-direction: column;
align-items: flex-start;
gap: 8px;
}

.costPredictionChartContainer {
height: 350px;
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { useState, useEffect } from 'react';
import PropTypes from 'prop-types';
import { useDispatch, useSelector } from 'react-redux';
import {
LineChart,
Expand All @@ -13,6 +14,7 @@ import {
} from 'recharts';
import styles from './CostPredictionChart.module.css';
import projectCostService from '../../../services/projectCostService';
import { getTooltipStyles } from '../../../utils/bmChartStyles';

// Custom dot renderer (unchanged)
function renderDotTopOrBottom(lineKey, color) {
Expand Down Expand Up @@ -145,6 +147,7 @@ function CostPredictionChart({ projectId }) {
y={y + 15} // push text down so it doesn’t overlap axis line
textAnchor="middle"
fill={darkMode ? '#e5e7eb' : '#9ca3af'}
fontSize={12}
>
{payload.value}
</text>
Expand All @@ -153,13 +156,22 @@ function CostPredictionChart({ projectId }) {
/>
<YAxis
tick={({ x, y, payload }) => (
<text x={x} y={y} textAnchor="end" fill={darkMode ? '#e5e7eb' : '#9ca3af'}>
<text
x={x}
y={y}
textAnchor="end"
fill={darkMode ? '#e5e7eb' : '#9ca3af'}
fontSize={12}
>
{payload.value}
</text>
)}
/>
{/* Tooltip & Legend */}
<Tooltip />
<Tooltip
{...getTooltipStyles(darkMode)}
cursor={{ stroke: darkMode ? '#e0e0e0' : '#999' }}
/>
<Legend
verticalAlign="bottom"
height={48}
Expand All @@ -170,7 +182,7 @@ function CostPredictionChart({ projectId }) {
<li key={item.label} className={styles.legendListItem}>
{/* icon */}
{item.type === 'circle' ? (
<span className={styles.legendItem} />
<span className={styles.legendItem} style={{ backgroundColor: item.color }} />
) : (
<svg width="18" height="12">
<line
Expand All @@ -185,7 +197,7 @@ function CostPredictionChart({ projectId }) {
</svg>
)}
{/* label */}
<span style={{ color: item.color }}>{item.label}</span>
<span style={{ color: darkMode ? '#e5e7eb' : '#374151' }}>{item.label}</span>
</li>
))}
</ul>
Expand Down Expand Up @@ -230,4 +242,8 @@ function CostPredictionChart({ projectId }) {
);
}

CostPredictionChart.propTypes = {
projectId: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired,
};

export default CostPredictionChart;
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,14 @@
list-style: none;
margin: 0;
padding: 0;
font-size: 12px;
}

.legendItem {
display: inline-block;
width: 12px;
height: 12px;
border-radius: 50%;
background: var(--text-color);
}

.legendListItem {
Expand Down
Loading
Loading