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
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ const projectStatusButtonData = [
['Completed Projects', 'completedProjects', '#F3FCFF', '#C1EFFB', '#328D1B'],
['Delayed Projects', 'delayedProjects', '#FFE9FA', '#FECFF3', '#C82F2F'],
['Active Projects', 'activeProjects', '#E8E8FF', '#CBCBFE', '#328D1B'],
['Avg Project Duration', 'avgProjectDuration', '#FFF6EE', '#FFD8A5', '#FFD8A5'],
['Avg Completed Project Duration (hours)', 'avgProjectDuration', '#FFF6EE', '#FFD8A5', '#FFD8A5'],
['Total Material Cost', 'totalMaterialCost', '#FFF3F3', '#FBC1C2', '#328D1B'],
['Total Material Used', 'totalMaterialUsed', '#DAC8FF', '#B28ECC', '#328D1B'],
['Total Labor Hours Invested', 'totalLaborHoursInvested', '#E5C1FC', '#F6E1FB', '#328D1B'],
Expand Down Expand Up @@ -325,12 +325,18 @@ function WeeklyProjectSummary() {
{projectStatusCards.map(button => {
const unavailableTooltipId = `project-status-unavailable-${button.metricKey}`;
return (
<div
<section
key={button.metricKey}
aria-label={button.title}
className={`${styles.weeklyProjectSummaryCard} ${styles.statusCard}`}
style={{ backgroundColor: button.bgColor }}
>
<div className={`${styles.weeklyCardTitle}`}>{button.title}</div>
{button.metricKey === 'avgProjectDuration' && (
<div className={styles.projectDurationDescription}>
Start to completion for projects completed in the selected period.
</div>
)}
<div
className={`${styles.weeklyStatusButton}`}
style={{ backgroundColor: button.buttonColor }}
Expand Down Expand Up @@ -380,7 +386,7 @@ function WeeklyProjectSummary() {
{button.change}
</div>
)}
</div>
</section>
);
})}
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -610,7 +610,9 @@
/* Fill the responsive grid track instead of capping cards at a fixed desktop width. */
max-width: none;
min-width: 0;
height: 190px;
min-height: 190px;
height: auto;
align-self: stretch;
text-align: center;
box-shadow: 0 4px 8px rgb(0 0 0 / 15%);
padding: 20px;
Expand All @@ -627,6 +629,13 @@
transition: color 0.3s ease;
}

.projectDurationDescription {
margin-top: 4px;
color: #333;
font-size: 12px;
line-height: 1.4;
}

.weeklyStatusValue {
color: #000;
font-size: 40px;
Expand Down Expand Up @@ -686,6 +695,7 @@
}

/* Keep all Project Status text readable on the preserved pastel status cards in dark mode. */
.darkMode .statusCard .projectDurationDescription,
.darkMode .statusCard .weeklyCardTitle,
.darkMode .statusCard .weeklyStatusValue {
color: #000 !important;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { render, screen, fireEvent, waitFor } from '@testing-library/react';
import { render, screen, fireEvent, waitFor, within } from '@testing-library/react';
import { Provider } from 'react-redux';
import configureMockStore from 'redux-mock-store';
import WeeklyProjectSummary from './WeeklyProjectSummary';
Expand Down Expand Up @@ -163,6 +163,75 @@ describe('WeeklyProjectSummary Project Status request state', () => {
expect(screen.queryByRole('alert')).not.toBeInTheDocument();
});

it.each([false, true])('renders Active Projects only once (dark mode: %s)', darkMode => {
const state = createState();
state.theme.darkMode = darkMode;
state.weeklyProjectSummary.projectStatusData = {
current: { metrics: { activeProjects: { value: 265, comparisonType: 'CURRENT_ONLY' } } },
};
renderWithState(state);

expect(screen.getAllByText('Active Projects')).toHaveLength(1);
expect(screen.getAllByText('265')).toHaveLength(1);
});

it.each([0, 17, 1008])(
'defines the duration metric and preserves the API hour value %s',
value => {
const state = createState();
state.weeklyProjectSummary.projectStatusData = {
current: {
metrics: {
avgProjectDuration: {
value,
unit: 'hrs',
comparisonType: 'PERIOD_COMPARABLE',
percentageChange: 0.13,
},
},
},
};
renderWithState(state);

const durationCard = screen.getByRole('region', {
name: 'Avg Completed Project Duration (hours)',
});
expect(within(durationCard).getByText(`${value} hrs`)).toBeInTheDocument();
expect(
within(durationCard).getByText(
'Start to completion for projects completed in the selected period.',
),
).toBeInTheDocument();
expect(within(durationCard).getByText('+13% month over month')).toBeInTheDocument();
},
);

it('keeps missing duration unavailable instead of inventing 17 hours', () => {
const state = createState();
state.weeklyProjectSummary.projectStatusData = {
current: {
metrics: {
avgProjectDuration: {
value: null,
unit: 'hrs',
comparisonType: 'UNAVAILABLE',
unavailableReason: 'No completed projects in this period.',
},
},
},
};
renderWithState(state);

const durationCard = screen.getByRole('region', {
name: 'Avg Completed Project Duration (hours)',
});
expect(within(durationCard).getByText('N/A')).toBeInTheDocument();
expect(
within(durationCard).getByRole('button', { name: 'No completed projects in this period.' }),
).toBeInTheDocument();
expect(screen.queryByText('17 hrs')).not.toBeInTheDocument();
});

it('shows loading and suppresses stale comparison text when filters change before useEffect dispatches', () => {
renderWithState(
createState({
Expand Down
Loading