Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
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 @@ -19,21 +19,35 @@ export default function AnniversaryCelebrated({ isLoading, data, darkMode }) {
);
}

const sixMonthsData = data['6Months'];
const oneYearData = data['1Year'];
const hasComparisonData = [sixMonthsData, oneYearData].every(
dataset => dataset.comparisonPercentage,
);
if (!data) {
return <div className="text-center p-3">No anniversary data available for this period.</div>;
}

const sixMonthsData = data?.['6Months'] ?? {
users: [],
comparisonPercentage: 0,
};

const oneYearData = data?.['1Year'] ?? {
users: [],
comparisonPercentage: 0,
};

const hasComparisonData =
sixMonthsData?.comparisonPercentage !== undefined &&
oneYearData?.comparisonPercentage !== undefined;

let sixMonthsPercent = 0;
let oneYearPercent = 0;
let is6MonthsPositive = true;
let isOneYearPositive = true;

let sixMonthsPercent;
let oneYearPercent;
let is6MonthsPositive;
let isOneYearPositive;
if (hasComparisonData) {
sixMonthsPercent = sixMonthsData.comparisonPercentage;
oneYearPercent = oneYearData.comparisonPercentage;
is6MonthsPositive = sixMonthsPercent.toString().charAt(0) !== '-';
isOneYearPositive = oneYearPercent.toString().charAt(0) !== '-';
sixMonthsPercent = sixMonthsData.comparisonPercentage ?? 0;
oneYearPercent = oneYearData.comparisonPercentage ?? 0;

is6MonthsPositive = Number(sixMonthsPercent) >= 0;
isOneYearPositive = Number(oneYearPercent) >= 0;
}

const handleEmailClick = email => {
Expand All @@ -42,6 +56,7 @@ export default function AnniversaryCelebrated({ isLoading, data, darkMode }) {

const getAnniversaryListItem = (userData = {}, anniversaryMonths = 6) => {
const { _id, profilePic, email, firstName, lastName, createdDate } = userData;

return (
<li key={_id} className="d-flex flex-column">
<div
Expand Down Expand Up @@ -74,13 +89,14 @@ export default function AnniversaryCelebrated({ isLoading, data, darkMode }) {

<div style={{ alignSelf: 'center' }}>
<p className="m-0" style={{ color: darkMode ? '#fff' : '#000' }}>
{`${firstName} ${lastName}`}
{`${firstName || ''} ${lastName || ''}`}
</p>
{/* show created date */}

<small style={{ color: darkMode ? '#aaa' : '#555' }}>
Joined: {new Date(createdDate).toLocaleDateString()}
Joined: {createdDate ? new Date(createdDate).toLocaleDateString() : 'N/A'}
</small>
</div>

<img
src={anniversaryMonths === 6 ? sixMonthsAward : oneYearAward}
alt="award"
Expand All @@ -93,28 +109,34 @@ export default function AnniversaryCelebrated({ isLoading, data, darkMode }) {

const exportData = () => {
const exportObj = {
sixMonths: sixMonthsData.users,
oneYear: oneYearData.users,
sixMonths: sixMonthsData?.users ?? [],
oneYear: oneYearData?.users ?? [],
};

const blob = new Blob([JSON.stringify(exportObj, null, 2)], {
type: 'application/json',
});

const url = URL.createObjectURL(blob);

const link = document.createElement('a');
link.href = url;
link.download = 'anniversaries.json';
// eslint-disable-next-line testing-library/no-node-access
link.click();

URL.revokeObjectURL(url);
};

// filter users by search
const filterUsers = users =>
users.filter(u => `${u.firstName} ${u.lastName}`.toLowerCase().includes(search.toLowerCase()));
users.filter(user =>
`${user.firstName || ''} ${user.lastName || ''}`.toLowerCase().includes(search.toLowerCase()),
);

const usersFilteredSixMonthsData = filterUsers(sixMonthsData.users).map(item =>
const usersFilteredSixMonthsData = filterUsers(sixMonthsData?.users ?? []).map(item =>
getAnniversaryListItem(item, 6),
);
const usersFilteredOneYearData = filterUsers(oneYearData.users).map(item =>

const usersFilteredOneYearData = filterUsers(oneYearData?.users ?? []).map(item =>
getAnniversaryListItem(item, 12),
);

Expand All @@ -134,12 +156,16 @@ export default function AnniversaryCelebrated({ isLoading, data, darkMode }) {
return (
<li
className="text-center"
style={{ color: darkMode ? '#fff' : '#000', listStyle: 'none' }}
style={{
color: darkMode ? '#fff' : '#000',
listStyle: 'none',
}}
>
No anniversaries found
</li>
);
}

return (
<>
{usersFilteredSixMonthsData}
Expand All @@ -155,9 +181,8 @@ export default function AnniversaryCelebrated({ isLoading, data, darkMode }) {
#anniversary-search::placeholder {
color: ${darkMode ? 'rgba(248,250,252,0.75)' : 'rgba(17,24,39,0.6)'} !important;
}


.anniversaryGmailIcon {
.anniversaryGmailIcon {
color: #ea4335 !important;
fill: #ea4335 !important;
}
Expand All @@ -167,7 +192,7 @@ export default function AnniversaryCelebrated({ isLoading, data, darkMode }) {
}
`}
</style>
{/* Comparison percentages with counts */}

{hasComparisonData && (
<span
style={{
Expand All @@ -180,29 +205,25 @@ export default function AnniversaryCelebrated({ isLoading, data, darkMode }) {
}}
>
<p style={{ display: 'flex', gap: '5px', marginBottom: '5px' }}>
<span style={{ color: darkMode ? '#fff' : 'gray' }}>6 months: </span>
<span
className={`text-center ${is6MonthsPositive ? 'text-success' : 'text-danger'}`}
style={{ margin: 0 }}
>
{`${is6MonthsPositive ? '+' : ''}${sixMonthsPercent}%`}({sixMonthsData.users.length}{' '}
users)
<span style={{ color: darkMode ? '#fff' : 'gray' }}>6 months:</span>

<span className={`text-center ${is6MonthsPositive ? 'text-success' : 'text-danger'}`}>
{`${is6MonthsPositive ? '+' : ''}${sixMonthsPercent}%`}(
{sixMonthsData?.users?.length ?? 0} users)
</span>
</p>

<p style={{ display: 'flex', gap: '5px' }}>
<span style={{ color: darkMode ? '#fff' : 'gray' }}>1 year: </span>
<span
className={`text-center ${isOneYearPositive ? 'text-success' : 'text-danger'}`}
style={{ margin: 0 }}
>
{`${isOneYearPositive ? '+' : ''}${oneYearPercent}%`}({oneYearData.users.length}{' '}
users)
<span style={{ color: darkMode ? '#fff' : 'gray' }}>1 year:</span>

<span className={`text-center ${isOneYearPositive ? 'text-success' : 'text-danger'}`}>
{`${isOneYearPositive ? '+' : ''}${oneYearPercent}%`}(
{oneYearData?.users?.length ?? 0} users)
</span>
</p>
</span>
)}

{/* Search + Export Controls */}
<div className="d-flex justify-content-between align-items-center mb-2">
<input
id="anniversary-search"
Expand All @@ -212,12 +233,12 @@ export default function AnniversaryCelebrated({ isLoading, data, darkMode }) {
onChange={e => setSearch(e.target.value)}
style={searchInputStyle}
/>

<button onClick={exportData} className="btn btn-secondary">
Export Data
</button>
</div>

{/* List of anniversaries */}
<ul className="w-90 overflow-auto" style={{ maxHeight: '410px' }}>
{renderAnniversariesList()}
</ul>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { render, screen } from '@testing-library/react';
import { MemoryRouter } from 'react-router-dom';
import { describe, expect, it } from 'vitest';
import AnniversaryCelebrated from './AnniversaryCelebrated';

describe('AnniversaryCelebrated', () => {
it('renders without crashing when anniversary data is missing', () => {
render(
<MemoryRouter>
<AnniversaryCelebrated isLoading={false} data={undefined} darkMode={false} />
</MemoryRouter>,
);

expect(screen.getByText('No anniversaries found')).toBeInTheDocument();

Check failure on line 14 in src/components/TotalOrgSummary/AnniversaryCelebrated/AnniversaryCelebrated.test.jsx

View workflow job for this annotation

GitHub Actions / test

src/components/TotalOrgSummary/AnniversaryCelebrated/AnniversaryCelebrated.test.jsx > AnniversaryCelebrated > renders without crashing when anniversary data is missing

TestingLibraryElementError: Unable to find an element with the text: No anniversaries found. This could be because the text is broken up by multiple elements. In this case, you can provide a function for your text matcher to make your matcher more flexible. <body> <div> <div class="text-center p-3" > No anniversary data available for this period. </div> </div> </body> ❯ Object.getElementError node_modules/@testing-library/react/node_modules/@testing-library/dom/dist/config.js:37:19 ❯ node_modules/@testing-library/react/node_modules/@testing-library/dom/dist/query-helpers.js:90:38 ❯ node_modules/@testing-library/react/node_modules/@testing-library/dom/dist/query-helpers.js:62:17 ❯ node_modules/@testing-library/react/node_modules/@testing-library/dom/dist/query-helpers.js:111:19 ❯ src/components/TotalOrgSummary/AnniversaryCelebrated/AnniversaryCelebrated.test.jsx:14:19

Check failure on line 14 in src/components/TotalOrgSummary/AnniversaryCelebrated/AnniversaryCelebrated.test.jsx

View workflow job for this annotation

GitHub Actions / test

src/components/TotalOrgSummary/AnniversaryCelebrated/AnniversaryCelebrated.test.jsx > AnniversaryCelebrated > renders without crashing when anniversary data is missing

TestingLibraryElementError: Unable to find an element with the text: No anniversaries found. This could be because the text is broken up by multiple elements. In this case, you can provide a function for your text matcher to make your matcher more flexible. <body> <div> <div class="text-center p-3" > No anniversary data available for this period. </div> </div> </body> ❯ Object.getElementError node_modules/@testing-library/react/node_modules/@testing-library/dom/dist/config.js:37:19 ❯ node_modules/@testing-library/react/node_modules/@testing-library/dom/dist/query-helpers.js:90:38 ❯ node_modules/@testing-library/react/node_modules/@testing-library/dom/dist/query-helpers.js:62:17 ❯ node_modules/@testing-library/react/node_modules/@testing-library/dom/dist/query-helpers.js:111:19 ❯ src/components/TotalOrgSummary/AnniversaryCelebrated/AnniversaryCelebrated.test.jsx:14:19

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This newly added test fails on the current head: the missing-data branch renders 'No anniversary data available for this period.', not 'No anniversaries found'. Running all four new suites yields 5 passes / 1 failure. Please assert the intended empty-state message so the PR's regression tests pass.

});
});
56 changes: 42 additions & 14 deletions src/components/TotalOrgSummary/BlueSquareStats/BlueSquareStats.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,25 +15,53 @@ function BlueSquareStats({ isLoading, blueSquareStats, comparisonType, darkMode
);
}

// Handle undefined/null data safely
if (!blueSquareStats) {
return (
<section className={styles.blueSquareStats}>
<div className={donutStyles.donutNoData}>
<p className={donutStyles.noDataText}>No Blue Square data available for this period.</p>
</div>
</section>
);
}

const {
totalBlueSquares,
missingHours,
missingSummary,
missingHoursAndSummary,
vacationTime,
other,
totalBlueSquares = {},
missingHours = {},
missingSummary = {},
missingHoursAndSummary = {},
vacationTime = {},
other = {},
} = blueSquareStats;

const data = [
{ label: 'Missing Hours', value: missingHours.count },
{ label: 'Missing Summary', value: missingSummary.count },
{ label: 'Missing Both Hours & Summary', value: missingHoursAndSummary.count },
{ label: 'Vacation Time', value: vacationTime.count },
{ label: 'Other', value: other.count },
{
label: 'Missing Hours',
value: missingHours?.count ?? 0,
},
{
label: 'Missing Summary',
value: missingSummary?.count ?? 0,
},
{
label: 'Missing Both Hours & Summary',
value: missingHoursAndSummary?.count ?? 0,
},
{
label: 'Vacation Time',
value: vacationTime?.count ?? 0,
},
{
label: 'Other',
value: other?.count ?? 0,
},
];

const hasData = data.some(item => item.value !== 0);
const pctChange = totalBlueSquares.comparisonPercentage ?? totalBlueSquares.percentageChange ?? 0;
const hasData = data.some(item => item.value > 0);

const pctChange =
totalBlueSquares?.comparisonPercentage ?? totalBlueSquares?.percentageChange ?? 0;

if (!hasData) {
return (
Expand All @@ -50,7 +78,7 @@ function BlueSquareStats({ isLoading, blueSquareStats, comparisonType, darkMode
<div className={styles.blueSquareStatsPieChart}>
<DonutChart
title="TOTAL BLUE SQUARES"
totalCount={totalBlueSquares.count}
totalCount={totalBlueSquares?.count ?? 0}
percentageChange={Number(pctChange)}
data={data}
colors={BLUE_SQUARE_STATS_COLORS}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { render, screen } from '@testing-library/react';
import { describe, expect, it } from 'vitest';
import BlueSquareStats from './BlueSquareStats';

describe('BlueSquareStats', () => {
it('renders the empty-state when blueSquareStats is missing', () => {
render(
<BlueSquareStats
isLoading={false}
blueSquareStats={undefined}
comparisonType="previous"
darkMode={false}
/>,
);

expect(screen.getByText('No Blue Square data available for this period.')).toBeInTheDocument();
});
});
Loading
Loading