Skip to content
Merged
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
1 change: 0 additions & 1 deletion src/components/Header/Header.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,6 @@ export function Header(props) {
const [hasProfileLoaded, setHasProfileLoaded] = useState(false);
const dismissalKey = `lastDismissed_${userId}`;
const [lastDismissed, setLastDismissed] = useState(localStorage.getItem(dismissalKey));
const [isAckLoading, setIsAckLoading] = useState(false);
const unreadNotifications = props.notification?.unreadNotifications; // List of unread notifications
const dispatch = useDispatch();
const history = useHistory();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import React, { useMemo } from 'react';
import PropTypes from 'prop-types';
import {
BarChart,
Bar,
Expand All @@ -10,38 +11,41 @@
Label,
} from 'recharts';

const truncate = (str, max = 22) =>
typeof str === 'string' && str.length > max ? str.slice(0, max) + '…' : str;

const CustomTooltip = ({ active, payload, isDark, usePercentage }) => {
if (active && payload && payload.length) {
const job = payload[0].payload;
const job = payload[0]?.payload || {};

Check warning on line 19 in src/components/Reports/HitsAndApplicationRatio/ConvertedApplicationGraph.jsx

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

'payload[].payload' is missing in props validation

See more on https://sonarcloud.io/project/issues?id=OneCommunityGlobal_HighestGoodNetworkApp&issues=AZrHK54hGa6X8SbcLxro&open=AZrHK54hGa6X8SbcLxro&pullRequest=4463

return (
<div
className={`p-2 rounded shadow ${
isDark
? 'bg-space-cadet border border-yinmn-blue text-light'
? 'bg-space-cadet border border-yinmn-blue text-gray-100'
: 'bg-white border border-gray-300 text-gray-900'
}`}
style={{ fontSize: '0.875rem' }}
>
<p><span className="font-semibold">Role:</span> {job.title}</p>
<p><span className="font-semibold">Conversion Rate:</span>{' '}
{usePercentage ? `${job.conversionRate}%` : job.conversionRate}
</p>
<p><span className="font-semibold">Hits:</span> {job.hits}</p>
<p><span className="font-semibold">Applications:</span> {job.applications}</p>
<p><strong>Role:</strong> {job.title}</p>
<p><strong>Conversion Rate:</strong> {usePercentage ? `${job.conversionRate}%` : job.conversionRate}</p>
<p><strong>Hits:</strong> {job.hits}</p>
<p><strong>Applications:</strong> {job.applications}</p>
</div>
);
}
return null;
};

function ConvertedApplicationGraph({ data, usePercentage, isDark }) {
function ConvertedApplicationGraph({ data = [], usePercentage, isDark }) {

Check failure on line 40 in src/components/Reports/HitsAndApplicationRatio/ConvertedApplicationGraph.jsx

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Refactor this function to reduce its Cognitive Complexity from 24 to the 15 allowed.

See more on https://sonarcloud.io/project/issues?id=OneCommunityGlobal_HighestGoodNetworkApp&issues=AZrHK54hGa6X8SbcLxrp&open=AZrHK54hGa6X8SbcLxrp&pullRequest=4463
const sortedData = useMemo(() => {
const key = usePercentage ? 'conversionRate' : 'applications';
const toNum = (val) => (val == null ? 0 : Number(val));
const toNum = (v) => Number(v) || 0;

const cloned = [...data];
cloned.sort((a, b) => toNum(b[key]) - toNum(a[key]));

return [...data]
.sort((a, b) => toNum(b[key]) - toNum(a[key]))
.slice(0, 10);
return cloned.slice(0, 10);
}, [data, usePercentage]);

return (
Expand All @@ -53,56 +57,47 @@
<h2 className={`text-lg font-semibold mb-2 ${isDark ? 'text-azure' : ''}`}>
Top 10 Job Postings by {usePercentage ? 'Conversion Rate' : 'Applications'}
</h2>

{sortedData.length === 0 ? (
<p>No data available for the selected date range.</p>
) : (
<ResponsiveContainer width="100%" height={400}>
<BarChart
layout="vertical"
data={sortedData}
margin={{ top: 20, right: 20, bottom: 40, left: 150 }}
>
<BarChart layout="vertical" data={sortedData} margin={{ top: 20, right: 90, bottom: 40, left: 180 }}>
<XAxis
type="number"
domain={usePercentage ? [0, 100] : ['auto', 'auto']}
unit={usePercentage ? '%' : ''}
stroke={isDark ? '#4682B4' : '#374151'} // Azure in dark mode
stroke={isDark ? '#e2e8f0' : '#374151'}
>
<Label
value={
usePercentage
? 'Percentage of hits converted to applications'
: 'Applications'
}
value={usePercentage ? 'Percentage of hits converted to applications' : 'Applications'}
position="bottom"
offset={0}
fill={isDark ? '#4682B4' : '#374151'}
fill={isDark ? '#e2e8f0' : '#374151'}
/>
</XAxis>

<YAxis
type="category"
dataKey="title"
width={140}
stroke={isDark ? '#4682B4' : '#374151'}
width={180}
tickFormatter={(v) => v}
tick={{ fill: isDark ? '#e2e8f0' : '#374151', fontSize: 12 }}
stroke={isDark ? '#e2e8f0' : '#374151'}
>
<Label
value="Job Role"
angle={-90}
position="left"
offset={-5}
style={{ textAnchor: 'middle' }}
fill={isDark ? '#4682B4' : '#374151'}
fill={isDark ? '#e2e8f0' : '#374151'}
/>
</YAxis>
<Tooltip
content={<CustomTooltip isDark={isDark} usePercentage={usePercentage} />}
/>

<Tooltip content={<CustomTooltip isDark={isDark} usePercentage={usePercentage} />} />

<Bar dataKey={usePercentage ? 'conversionRate' : 'applications'} fill="#4CAF50">
<LabelList
dataKey={usePercentage ? 'conversionRate' : 'applications'}
position="right"
formatter={(value) => `${value}${usePercentage ? '%' : ''}`}
fill={isDark ? '#4682B4' : '#374151'}
style={{ fill: isDark ? '#FFFFFF' : '#374151', fontWeight: 600 }}
/>
</Bar>
</BarChart>
Expand All @@ -112,4 +107,17 @@
);
}

ConvertedApplicationGraph.propTypes = {
data: PropTypes.arrayOf(
PropTypes.shape({
title: PropTypes.string,
hits: PropTypes.number,
applications: PropTypes.number,
conversionRate: PropTypes.number,
})
),
usePercentage: PropTypes.bool.isRequired,
isDark: PropTypes.bool.isRequired,
};

export default ConvertedApplicationGraph;
164 changes: 97 additions & 67 deletions src/components/Reports/HitsAndApplicationRatio/JobAnalyticsPage.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,42 +13,45 @@
const [dateRange, setDateRange] = useState('All');
const [loading, setLoading] = useState(false);

// detect dark mode
// detect global dark mode but override layout ourselves
const [isDark, setIsDark] = useState(
typeof document !== 'undefined' && document.querySelector('.dark-mode') !== null
typeof document !== 'undefined' &&
document.body.classList.contains('dark-mode')
);

useEffect(() => {
if (typeof document === 'undefined') return;

const targetNode = document.body;
const observer = new MutationObserver(() => {
const darkActive = document.querySelector('.dark-mode') !== null;
setIsDark(darkActive);
setIsDark(document.body.classList.contains('dark-mode'));
});

observer.observe(targetNode, { attributes: true, subtree: true, attributeFilter: ['class'] });
observer.observe(document.body, {
attributes: true,
attributeFilter: ['class'],
});

return () => observer.disconnect();
}, []);

useEffect(() => {
const { startDate, endDate } = getDateRange(dateRange);

const fetchData = async () => {
setLoading(true);

try {
const [topRes, leastRes] = await Promise.all([
httpService.get(ENDPOINTS.TOP_CONVERTED(10, startDate, endDate)),
httpService.get(ENDPOINTS.LEAST_CONVERTED(10, startDate, endDate)),
]);

setConvertedData(topRes.data);
setNonConvertedData(leastRes.data);
} catch (err) {
// eslint-disable-next-line no-console
console.error('Error fetching job analytics:', err);
setConvertedData([]);
setNonConvertedData([]);
} finally {

Check warning on line 54 in src/components/Reports/HitsAndApplicationRatio/JobAnalyticsPage.jsx

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Handle this exception or don't catch it at all.

See more on https://sonarcloud.io/project/issues?id=OneCommunityGlobal_HighestGoodNetworkApp&issues=AZrHCyAgM7i0c6huOXex&open=AZrHCyAgM7i0c6huOXex&pullRequest=4463
setLoading(false);
}
};
Expand All @@ -57,71 +60,98 @@
}, [dateRange]);

return (
<div
className={`p-6 rounded-xl transition-colors duration-300 ${
isDark
? 'bg-oxford-blue text-light boxStyleDark'
: 'bg-white text-gray-900 boxStyle'
}`}
>
<div className="flex items-center gap-4 flex-wrap">
<div className="flex items-center gap-2">
<span className={isDark ? 'text-azure font-semibold' : 'font-semibold'}>
Date Range:
</span>
<select
value={dateRange}
onChange={(e) => setDateRange(e.target.value)}
className={`rounded px-2 py-1 ${
isDark ? 'bg-space-cadet text-light border border-yinmn-blue' : 'bg-white text-gray-900 border border-gray-300'
}`}
>
{dateOptions.map((option) => (
<option
key={option}
value={option}
className={isDark ? 'bg-space-cadet text-light' : 'bg-white text-gray-900'}
<>
{/* INLINE OVERRIDE: prevents global dark-mode from affecting page background */}
<style>{`
/* Force the Job Analytics background back to normal */
.job-analytics-wrapper {
background-color: #ffffff !important;
}
.job-analytics-wrapper.dark {
background-color: #0b1e39 !important;
}
`}</style>

<div
className={
`job-analytics-wrapper w-full px-4 py-6 ` +
(isDark ? 'dark text-light' : 'text-gray-900')
}
>
{/* FILTERS */}
<div className="w-full flex justify-center mt-6 mb-8">
<div className="flex flex-wrap items-center justify-center gap-6">

{/* Date Range */}
<div className="flex items-center gap-2">
<span className={isDark ? 'text-azure font-semibold' : 'font-semibold'}>
Date Range:
</span>

<select
value={dateRange}
onChange={(e) => setDateRange(e.target.value)}
className={`rounded px-2 py-1 ${
isDark
? 'bg-space-cadet text-light border border-yinmn-blue'
: 'bg-white text-gray-900 border border-gray-300'
}`}
>
{option}
</option>
))}
</select>
{dateOptions.map((option) => (
<option key={option} value={option}>
{option}
</option>
))}
</select>
</div>

{/* Show % */}
<div className="flex items-center gap-2">
<input
type="checkbox"
id="toggle-percentage"
checked={usePercentage}
onChange={() => setUsePercentage(!usePercentage)}
/>
<label
htmlFor="toggle-percentage"
className={isDark ? 'text-light cursor-pointer' : 'text-gray-900 cursor-pointer'}
>
Show %
</label>
</div>

</div>
</div>

<div className="ml-auto flex items-center gap-2">
<input
type="checkbox"
id="toggle-percentage"
checked={usePercentage}
onChange={() => setUsePercentage(!usePercentage)}
/>
<label
htmlFor="toggle-percentage"
className={isDark ? 'text-light cursor-pointer' : 'text-gray-900 cursor-pointer'}
>
Show %
</label>
{/* GRAPHS */}
<div
className={`rounded-xl p-6 ${
isDark ? 'bg-oxford-blue text-light' : 'bg-white text-gray-900'
}`}
>
{loading ? (
<p>Loading analytics...</p>
) : (
<>
<ConvertedApplicationGraph
data={convertedData}
usePercentage={usePercentage}
isDark={isDark}
/>

<NonConvertedApplicationsGraph
data={nonConvertedData}
usePercentage={usePercentage}
isDark={isDark}
/>
</>
)}
</div>
</div>

{loading ? (
<p className={isDark ? 'text-light' : 'text-gray-900'}>Loading analytics...</p>
) : (
<>
<ConvertedApplicationGraph
data={convertedData}
usePercentage={usePercentage}
isDark={isDark}
/>
<NonConvertedApplicationsGraph
data={nonConvertedData}
usePercentage={usePercentage}
isDark={isDark}
/>
</>
)}
</div>
</>
);
}

export default JobAnalyticsPage;

Loading
Loading