Skip to content

Commit 43e9671

Browse files
Merge pull request #4463 from OneCommunityGlobal/Neeraj_Fix_Job_Posting_Analytics_Frontend
Neeraj_Fix_Job_Posting_Analytics_Frontend
2 parents fb06224 + aab02e7 commit 43e9671

7 files changed

Lines changed: 300 additions & 144 deletions

File tree

Lines changed: 45 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import React, { useMemo } from 'react';
2+
import PropTypes from 'prop-types';
23
import {
34
BarChart,
45
Bar,
@@ -10,38 +11,41 @@ import {
1011
Label,
1112
} from 'recharts';
1213

14+
const truncate = (str, max = 22) =>
15+
typeof str === 'string' && str.length > max ? str.slice(0, max) + '…' : str;
16+
1317
const CustomTooltip = ({ active, payload, isDark, usePercentage }) => {
1418
if (active && payload && payload.length) {
15-
const job = payload[0].payload;
19+
const job = payload[0]?.payload || {};
20+
1621
return (
1722
<div
1823
className={`p-2 rounded shadow ${
1924
isDark
20-
? 'bg-space-cadet border border-yinmn-blue text-light'
25+
? 'bg-space-cadet border border-yinmn-blue text-gray-100'
2126
: 'bg-white border border-gray-300 text-gray-900'
2227
}`}
2328
style={{ fontSize: '0.875rem' }}
2429
>
25-
<p><span className="font-semibold">Role:</span> {job.title}</p>
26-
<p><span className="font-semibold">Conversion Rate:</span>{' '}
27-
{usePercentage ? `${job.conversionRate}%` : job.conversionRate}
28-
</p>
29-
<p><span className="font-semibold">Hits:</span> {job.hits}</p>
30-
<p><span className="font-semibold">Applications:</span> {job.applications}</p>
30+
<p><strong>Role:</strong> {job.title}</p>
31+
<p><strong>Conversion Rate:</strong> {usePercentage ? `${job.conversionRate}%` : job.conversionRate}</p>
32+
<p><strong>Hits:</strong> {job.hits}</p>
33+
<p><strong>Applications:</strong> {job.applications}</p>
3134
</div>
3235
);
3336
}
3437
return null;
3538
};
3639

37-
function ConvertedApplicationGraph({ data, usePercentage, isDark }) {
40+
function ConvertedApplicationGraph({ data = [], usePercentage, isDark }) {
3841
const sortedData = useMemo(() => {
3942
const key = usePercentage ? 'conversionRate' : 'applications';
40-
const toNum = (val) => (val == null ? 0 : Number(val));
43+
const toNum = (v) => Number(v) || 0;
44+
45+
const cloned = [...data];
46+
cloned.sort((a, b) => toNum(b[key]) - toNum(a[key]));
4147

42-
return [...data]
43-
.sort((a, b) => toNum(b[key]) - toNum(a[key]))
44-
.slice(0, 10);
48+
return cloned.slice(0, 10);
4549
}, [data, usePercentage]);
4650

4751
return (
@@ -53,56 +57,47 @@ function ConvertedApplicationGraph({ data, usePercentage, isDark }) {
5357
<h2 className={`text-lg font-semibold mb-2 ${isDark ? 'text-azure' : ''}`}>
5458
Top 10 Job Postings by {usePercentage ? 'Conversion Rate' : 'Applications'}
5559
</h2>
60+
5661
{sortedData.length === 0 ? (
5762
<p>No data available for the selected date range.</p>
5863
) : (
5964
<ResponsiveContainer width="100%" height={400}>
60-
<BarChart
61-
layout="vertical"
62-
data={sortedData}
63-
margin={{ top: 20, right: 20, bottom: 40, left: 150 }}
64-
>
65+
<BarChart layout="vertical" data={sortedData} margin={{ top: 20, right: 90, bottom: 40, left: 180 }}>
6566
<XAxis
6667
type="number"
6768
domain={usePercentage ? [0, 100] : ['auto', 'auto']}
68-
unit={usePercentage ? '%' : ''}
69-
stroke={isDark ? '#4682B4' : '#374151'} // Azure in dark mode
69+
stroke={isDark ? '#e2e8f0' : '#374151'}
7070
>
7171
<Label
72-
value={
73-
usePercentage
74-
? 'Percentage of hits converted to applications'
75-
: 'Applications'
76-
}
72+
value={usePercentage ? 'Percentage of hits converted to applications' : 'Applications'}
7773
position="bottom"
78-
offset={0}
79-
fill={isDark ? '#4682B4' : '#374151'}
74+
fill={isDark ? '#e2e8f0' : '#374151'}
8075
/>
8176
</XAxis>
77+
8278
<YAxis
8379
type="category"
8480
dataKey="title"
85-
width={140}
86-
stroke={isDark ? '#4682B4' : '#374151'}
81+
width={180}
82+
tickFormatter={(v) => v}
83+
tick={{ fill: isDark ? '#e2e8f0' : '#374151', fontSize: 12 }}
84+
stroke={isDark ? '#e2e8f0' : '#374151'}
8785
>
8886
<Label
8987
value="Job Role"
9088
angle={-90}
9189
position="left"
92-
offset={-5}
93-
style={{ textAnchor: 'middle' }}
94-
fill={isDark ? '#4682B4' : '#374151'}
90+
fill={isDark ? '#e2e8f0' : '#374151'}
9591
/>
9692
</YAxis>
97-
<Tooltip
98-
content={<CustomTooltip isDark={isDark} usePercentage={usePercentage} />}
99-
/>
93+
94+
<Tooltip content={<CustomTooltip isDark={isDark} usePercentage={usePercentage} />} />
95+
10096
<Bar dataKey={usePercentage ? 'conversionRate' : 'applications'} fill="#4CAF50">
10197
<LabelList
10298
dataKey={usePercentage ? 'conversionRate' : 'applications'}
10399
position="right"
104-
formatter={(value) => `${value}${usePercentage ? '%' : ''}`}
105-
fill={isDark ? '#4682B4' : '#374151'}
100+
style={{ fill: isDark ? '#FFFFFF' : '#374151', fontWeight: 600 }}
106101
/>
107102
</Bar>
108103
</BarChart>
@@ -112,4 +107,17 @@ function ConvertedApplicationGraph({ data, usePercentage, isDark }) {
112107
);
113108
}
114109

110+
ConvertedApplicationGraph.propTypes = {
111+
data: PropTypes.arrayOf(
112+
PropTypes.shape({
113+
title: PropTypes.string,
114+
hits: PropTypes.number,
115+
applications: PropTypes.number,
116+
conversionRate: PropTypes.number,
117+
})
118+
),
119+
usePercentage: PropTypes.bool.isRequired,
120+
isDark: PropTypes.bool.isRequired,
121+
};
122+
115123
export default ConvertedApplicationGraph;

src/components/Reports/HitsAndApplicationRatio/JobAnalyticsPage.jsx

Lines changed: 97 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -13,39 +13,42 @@ function JobAnalyticsPage() {
1313
const [dateRange, setDateRange] = useState('All');
1414
const [loading, setLoading] = useState(false);
1515

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

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

24-
const targetNode = document.body;
2525
const observer = new MutationObserver(() => {
26-
const darkActive = document.querySelector('.dark-mode') !== null;
27-
setIsDark(darkActive);
26+
setIsDark(document.body.classList.contains('dark-mode'));
2827
});
2928

30-
observer.observe(targetNode, { attributes: true, subtree: true, attributeFilter: ['class'] });
29+
observer.observe(document.body, {
30+
attributes: true,
31+
attributeFilter: ['class'],
32+
});
3133

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

3537
useEffect(() => {
3638
const { startDate, endDate } = getDateRange(dateRange);
39+
3740
const fetchData = async () => {
3841
setLoading(true);
42+
3943
try {
4044
const [topRes, leastRes] = await Promise.all([
4145
httpService.get(ENDPOINTS.TOP_CONVERTED(10, startDate, endDate)),
4246
httpService.get(ENDPOINTS.LEAST_CONVERTED(10, startDate, endDate)),
4347
]);
48+
4449
setConvertedData(topRes.data);
4550
setNonConvertedData(leastRes.data);
4651
} catch (err) {
47-
// eslint-disable-next-line no-console
48-
console.error('Error fetching job analytics:', err);
4952
setConvertedData([]);
5053
setNonConvertedData([]);
5154
} finally {
@@ -57,71 +60,98 @@ function JobAnalyticsPage() {
5760
}, [dateRange]);
5861

5962
return (
60-
<div
61-
className={`p-6 rounded-xl transition-colors duration-300 ${
62-
isDark
63-
? 'bg-oxford-blue text-light boxStyleDark'
64-
: 'bg-white text-gray-900 boxStyle'
65-
}`}
66-
>
67-
<div className="flex items-center gap-4 flex-wrap">
68-
<div className="flex items-center gap-2">
69-
<span className={isDark ? 'text-azure font-semibold' : 'font-semibold'}>
70-
Date Range:
71-
</span>
72-
<select
73-
value={dateRange}
74-
onChange={(e) => setDateRange(e.target.value)}
75-
className={`rounded px-2 py-1 ${
76-
isDark ? 'bg-space-cadet text-light border border-yinmn-blue' : 'bg-white text-gray-900 border border-gray-300'
77-
}`}
78-
>
79-
{dateOptions.map((option) => (
80-
<option
81-
key={option}
82-
value={option}
83-
className={isDark ? 'bg-space-cadet text-light' : 'bg-white text-gray-900'}
63+
<>
64+
{/* INLINE OVERRIDE: prevents global dark-mode from affecting page background */}
65+
<style>{`
66+
/* Force the Job Analytics background back to normal */
67+
.job-analytics-wrapper {
68+
background-color: #ffffff !important;
69+
}
70+
.job-analytics-wrapper.dark {
71+
background-color: #0b1e39 !important;
72+
}
73+
`}</style>
74+
75+
<div
76+
className={
77+
`job-analytics-wrapper w-full px-4 py-6 ` +
78+
(isDark ? 'dark text-light' : 'text-gray-900')
79+
}
80+
>
81+
{/* FILTERS */}
82+
<div className="w-full flex justify-center mt-6 mb-8">
83+
<div className="flex flex-wrap items-center justify-center gap-6">
84+
85+
{/* Date Range */}
86+
<div className="flex items-center gap-2">
87+
<span className={isDark ? 'text-azure font-semibold' : 'font-semibold'}>
88+
Date Range:
89+
</span>
90+
91+
<select
92+
value={dateRange}
93+
onChange={(e) => setDateRange(e.target.value)}
94+
className={`rounded px-2 py-1 ${
95+
isDark
96+
? 'bg-space-cadet text-light border border-yinmn-blue'
97+
: 'bg-white text-gray-900 border border-gray-300'
98+
}`}
8499
>
85-
{option}
86-
</option>
87-
))}
88-
</select>
100+
{dateOptions.map((option) => (
101+
<option key={option} value={option}>
102+
{option}
103+
</option>
104+
))}
105+
</select>
106+
</div>
107+
108+
{/* Show % */}
109+
<div className="flex items-center gap-2">
110+
<input
111+
type="checkbox"
112+
id="toggle-percentage"
113+
checked={usePercentage}
114+
onChange={() => setUsePercentage(!usePercentage)}
115+
/>
116+
<label
117+
htmlFor="toggle-percentage"
118+
className={isDark ? 'text-light cursor-pointer' : 'text-gray-900 cursor-pointer'}
119+
>
120+
Show %
121+
</label>
122+
</div>
123+
124+
</div>
89125
</div>
90126

91-
<div className="ml-auto flex items-center gap-2">
92-
<input
93-
type="checkbox"
94-
id="toggle-percentage"
95-
checked={usePercentage}
96-
onChange={() => setUsePercentage(!usePercentage)}
97-
/>
98-
<label
99-
htmlFor="toggle-percentage"
100-
className={isDark ? 'text-light cursor-pointer' : 'text-gray-900 cursor-pointer'}
101-
>
102-
Show %
103-
</label>
127+
{/* GRAPHS */}
128+
<div
129+
className={`rounded-xl p-6 ${
130+
isDark ? 'bg-oxford-blue text-light' : 'bg-white text-gray-900'
131+
}`}
132+
>
133+
{loading ? (
134+
<p>Loading analytics...</p>
135+
) : (
136+
<>
137+
<ConvertedApplicationGraph
138+
data={convertedData}
139+
usePercentage={usePercentage}
140+
isDark={isDark}
141+
/>
142+
143+
<NonConvertedApplicationsGraph
144+
data={nonConvertedData}
145+
usePercentage={usePercentage}
146+
isDark={isDark}
147+
/>
148+
</>
149+
)}
104150
</div>
105151
</div>
106-
107-
{loading ? (
108-
<p className={isDark ? 'text-light' : 'text-gray-900'}>Loading analytics...</p>
109-
) : (
110-
<>
111-
<ConvertedApplicationGraph
112-
data={convertedData}
113-
usePercentage={usePercentage}
114-
isDark={isDark}
115-
/>
116-
<NonConvertedApplicationsGraph
117-
data={nonConvertedData}
118-
usePercentage={usePercentage}
119-
isDark={isDark}
120-
/>
121-
</>
122-
)}
123-
</div>
152+
</>
124153
);
125154
}
126155

127156
export default JobAnalyticsPage;
157+

0 commit comments

Comments
 (0)