Skip to content

Commit 0befbd6

Browse files
Merge pull request #3489 from OneCommunityGlobal/haoyue-job-posting-age-bar-chart
Haoyue job posting application age bar chart
2 parents 2eb99c1 + 9a1ef55 commit 0befbd6

9 files changed

Lines changed: 289 additions & 24 deletions

File tree

package-lock.json

Lines changed: 39 additions & 23 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@
7171
"reactjs-popup": "^2.0.5",
7272
"reactstrap": "^8.4.1",
7373
"read-excel-file": "^5.5.3",
74-
"recharts": "^2.12.7",
74+
"recharts": "^2.15.2",
7575
"redux": "^4.0.5",
7676
"redux-actions": "^2.6.5",
7777
"redux-concatenate-reducers": "^1.0.0",
@@ -108,6 +108,7 @@
108108
"@testing-library/react": "^10.4.9",
109109
"@testing-library/user-event": "^12.0.14",
110110
"@types/dompurify": "^3.0.5",
111+
"@types/react-datepicker": "^7.0.0",
111112
"@types/react-router-dom": "^5.3.3",
112113
"babel-eslint": "^10.1.0",
113114
"cross-env": "^5.2.1",
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
import {
2+
BarChart,
3+
Bar,
4+
XAxis,
5+
YAxis,
6+
CartesianGrid,
7+
Tooltip,
8+
ResponsiveContainer,
9+
LabelList,
10+
} from 'recharts';
11+
12+
function AgeChart({ data, compareLabel }) {
13+
const formatTooltip = (value, name, props) => {
14+
const { change } = props.payload;
15+
let changeText = '';
16+
if (compareLabel && change !== undefined) {
17+
if (change > 0) {
18+
changeText = `${change}% more than ${compareLabel}`;
19+
} else if (change < 0) {
20+
changeText = `${Math.abs(change)}% less than ${compareLabel}`;
21+
} else {
22+
changeText = `No change from ${compareLabel}`;
23+
}
24+
return [`${value} (${changeText})`, 'Applicants'];
25+
}
26+
return [`${value}`, 'Applicants'];
27+
};
28+
29+
return (
30+
<div style={{ width: '800px', height: 500, margin: '0 auto', padding: '20px' }}>
31+
<h2>Applicants grouped by Age</h2>
32+
<ResponsiveContainer width="100%" height="100%">
33+
<BarChart data={data} margin={{ top: 20, right: 30, left: 20, bottom: 20 }} barSize={80}>
34+
<CartesianGrid strokeDasharray="3 3" />
35+
<XAxis dataKey="ageGroup" />
36+
<YAxis />
37+
<Tooltip formatter={formatTooltip} />
38+
<Bar dataKey="applicants" fill="#3b82f6">
39+
<LabelList dataKey="applicants" position="top" />
40+
</Bar>
41+
</BarChart>
42+
</ResponsiveContainer>
43+
</div>
44+
);
45+
}
46+
47+
export default AgeChart;
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
import {
2+
BarChart,
3+
Bar,
4+
XAxis,
5+
YAxis,
6+
CartesianGrid,
7+
Tooltip,
8+
ResponsiveContainer,
9+
LabelList,
10+
} from 'recharts';
11+
12+
const data = [
13+
{ ageGroup: '18 - 21', applicants: 25, change: 10 },
14+
{ ageGroup: '21 - 24', applicants: 60, change: -5 },
15+
{ ageGroup: '24 - 27', applicants: 45, change: 15 },
16+
{ ageGroup: '27 - 30', applicants: 7, change: -3 },
17+
{ ageGroup: '30 - 33', applicants: 10, change: 0 },
18+
];
19+
20+
function ApplicantsChartPage() {
21+
const formatTooltip = (value, name, props) => {
22+
const { change } = props.payload;
23+
let changeText = '';
24+
if (change > 0) {
25+
changeText = `${change}% more than last week`;
26+
} else if (change < 0) {
27+
changeText = `${Math.abs(change)}% less than last week`;
28+
} else {
29+
changeText = `No change from last week`;
30+
}
31+
return [`${value} (${changeText})`, 'Applicants'];
32+
};
33+
34+
return (
35+
<div style={{ width: '800px', height: 500, margin: '0 auto', padding: '20px' }}>
36+
<h2>Applicants grouped by Age</h2>
37+
<ResponsiveContainer width="100%" height="100%">
38+
<BarChart data={data} margin={{ top: 20, right: 30, left: 20, bottom: 20 }} barSize={80}>
39+
<CartesianGrid strokeDasharray="3 3" />
40+
<XAxis
41+
dataKey="ageGroup"
42+
label={{ value: 'Age Group', position: 'insideBottom', offset: -10 }}
43+
/>
44+
<YAxis label={{ value: 'Applicants', angle: -90, position: 'insideLeft' }} />
45+
<Tooltip formatter={formatTooltip} />
46+
<Bar dataKey="applicants" fill="#3b82f6">
47+
<LabelList dataKey="applicants" position="top" />
48+
</Bar>
49+
</BarChart>
50+
</ResponsiveContainer>
51+
</div>
52+
);
53+
}
54+
55+
export default ApplicantsChartPage;
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
import { useState, useEffect } from 'react';
2+
import DatePicker from 'react-datepicker';
3+
import 'react-datepicker/dist/react-datepicker.css';
4+
5+
function TimeFilter({ onFilterChange }) {
6+
const [selectedOption, setSelectedOption] = useState('weekly');
7+
const [startDate, setStartDate] = useState(null);
8+
const [endDate, setEndDate] = useState(null);
9+
10+
useEffect(() => {
11+
onFilterChange({ selectedOption, startDate, endDate });
12+
}, [selectedOption, startDate, endDate]);
13+
14+
return (
15+
<div
16+
style={{
17+
display: 'flex',
18+
justifyContent: 'center',
19+
alignItems: 'center',
20+
gap: '16px',
21+
margin: '20px auto',
22+
flexWrap: 'wrap',
23+
}}
24+
>
25+
<label htmlFor="timeFilterSelect" style={{ fontWeight: 500 }}>
26+
Time Filter:
27+
</label>
28+
<select
29+
id="timeFilterSelect"
30+
value={selectedOption}
31+
onChange={e => setSelectedOption(e.target.value)}
32+
style={{
33+
padding: '6px 12px',
34+
borderRadius: '4px',
35+
border: '1px solid #ccc',
36+
fontSize: '14px',
37+
}}
38+
>
39+
<option value="weekly">Weekly</option>
40+
<option value="monthly">Monthly</option>
41+
<option value="yearly">Yearly</option>
42+
<option value="custom">Custom Dates</option>
43+
</select>
44+
45+
{selectedOption === 'custom' && (
46+
<>
47+
<DatePicker
48+
selected={startDate}
49+
onChange={date => setStartDate(date)}
50+
placeholderText="Start Date"
51+
dateFormat="yyyy/MM/dd"
52+
style={{ marginRight: '10px' }}
53+
/>
54+
<span>to</span>
55+
<DatePicker
56+
selected={endDate}
57+
onChange={date => setEndDate(date)}
58+
placeholderText="End Date"
59+
dateFormat="yyyy/MM/dd"
60+
/>
61+
</>
62+
)}
63+
</div>
64+
);
65+
}
66+
67+
export default TimeFilter;
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import data from './data';
2+
3+
const fetchApplicantsData = filter =>
4+
new Promise(resolve => {
5+
setTimeout(() => {
6+
resolve(data[filter.selectedOption]);
7+
}, 500);
8+
});
9+
10+
export default fetchApplicantsData;
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
const sampleData = {
2+
weekly: [
3+
{ ageGroup: '18 - 21', applicants: 25, change: 10 },
4+
{ ageGroup: '21 - 24', applicants: 60, change: -5 },
5+
{ ageGroup: '24 - 27', applicants: 45, change: 15 },
6+
{ ageGroup: '27 - 30', applicants: 7, change: -3 },
7+
{ ageGroup: '30 - 33', applicants: 10, change: 0 },
8+
],
9+
monthly: [
10+
{ ageGroup: '18 - 21', applicants: 100, change: 20 },
11+
{ ageGroup: '21 - 24', applicants: 150, change: -10 },
12+
{ ageGroup: '24 - 27', applicants: 130, change: 25 },
13+
{ ageGroup: '27 - 30', applicants: 30, change: -12 },
14+
{ ageGroup: '30 - 33', applicants: 40, change: 5 },
15+
],
16+
yearly: [
17+
{ ageGroup: '18 - 21', applicants: 1200, change: 5 },
18+
{ ageGroup: '21 - 24', applicants: 1400, change: 12 },
19+
{ ageGroup: '24 - 27', applicants: 1100, change: 8 },
20+
{ ageGroup: '27 - 30', applicants: 300, change: -10 },
21+
{ ageGroup: '30 - 33', applicants: 450, change: 15 },
22+
],
23+
custom: [
24+
{ ageGroup: '18 - 21', applicants: 200 },
25+
{ ageGroup: '21 - 24', applicants: 300 },
26+
{ ageGroup: '24 - 27', applicants: 250 },
27+
{ ageGroup: '27 - 30', applicants: 70 },
28+
{ ageGroup: '30 - 33', applicants: 100 },
29+
],
30+
};
31+
32+
export default sampleData;
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import { useState, useEffect } from 'react';
2+
import TimeFilter from './TimeFilter';
3+
import AgeChart from './AgeChart';
4+
import fetchApplicantsData from './api';
5+
6+
function ApplicantsChart() {
7+
const [chartData, setChartData] = useState([]);
8+
const [compareLabel, setCompareLabel] = useState('last week');
9+
const [loading, setLoading] = useState(false);
10+
11+
const handleFilterChange = async filter => {
12+
setLoading(true);
13+
const data = await fetchApplicantsData(filter);
14+
setChartData(data);
15+
16+
setCompareLabel(
17+
filter.selectedOption === 'custom' ? null : `last ${filter.selectedOption.slice(0, -2)}`,
18+
);
19+
setLoading(false);
20+
};
21+
22+
useEffect(() => {
23+
handleFilterChange({ selectedOption: 'weekly' });
24+
}, []);
25+
26+
return (
27+
<div>
28+
<TimeFilter onFilterChange={handleFilterChange} />
29+
{loading ? <p>Loading...</p> : <AgeChart data={chartData} compareLabel={compareLabel} />}
30+
</div>
31+
);
32+
}
33+
34+
export default ApplicantsChart;

0 commit comments

Comments
 (0)