Skip to content

Commit d7bc641

Browse files
author
Test
committed
Merge origin/development into veda-review-volume-over-time-chart
Made-with: Cursor
2 parents bee4719 + bf213ca commit d7bc641

106 files changed

Lines changed: 11688 additions & 5878 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.eslintignore.bak

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
# Dependencies
2+
node_modules
3+
4+
# Build output
5+
build
6+
dist
7+
*.min.js
8+
9+
# Vendor / third-party (tinymce copied to public by postinstall)
10+
public/tinymce
11+
12+
# Scripts not part of app source
13+
postinstall.js
14+
refactor-css-classes.js
15+
16+
# Coverage and test artifacts
17+
coverage
18+
*.test.js.snap

.github/workflows/deploy.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,4 +62,4 @@ jobs:
6262
domain: ${{ vars.SURGE_DOMAIN }}
6363
project: './build'
6464
login: ${{ secrets.SURGE_LOGIN }}
65-
token: ${{ secrets.SURGE_TOKEN }}
65+
token: ${{ secrets.SURGE_TOKEN }}

.github/workflows/pull_request_test.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,4 +55,4 @@ jobs:
5555
- name: Run Unit Tests for Changed Files Only
5656
run: yarn run test:changed
5757
- name: Run Lint
58-
run: yarn run lint
58+
run: yarn run lint

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,4 +33,5 @@ yarn-error.log*
3333

3434
**\ **
3535

36-
/public/tinymce/
36+
/public/tinymce/
37+
package-lock.json

.husky/pre-commit

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,6 @@ if [ -f .nvmrc ]; then
77
nvm use 20 2>/dev/null || nvm install 20 && nvm use 20
88
fi
99

10-
. "$(dirname "$0")/_/husky.sh"
11-
1210
echo ""
1311
echo "🛡️ Husky pre-commit hook triggered"
1412

package.json

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@
5555
"date-fns-tz": "^2.0.1",
5656
"dayjs": "^1.11.13",
5757
"diff": "^8.0.3",
58-
"dompurify": "^3.2.5",
58+
"dompurify": "^3.3.2",
5959
"elliptic": "^6.6.1",
6060
"font-awesome": "^4.7.0",
6161
"fs-extra": "^11.3.0",
@@ -129,7 +129,9 @@
129129
},
130130
"resolutions": {
131131
"react": "18.3.1",
132-
"react-dom": "18.3.1"
132+
"react-dom": "18.3.1",
133+
"mdn-data": "2.12.2",
134+
"ansi-escapes": "7.1.1"
133135
},
134136
"packageManager": "yarn@1.22.22",
135137
"scripts": {

src/actions/kiCalendarAction.js

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import { createApi, fetchBaseQuery } from "@reduxjs/toolkit/query/react";
2+
import { ENDPOINTS } from '~/utils/URL';
3+
import axios from 'axios';
4+
5+
export const kiCalendarApi = createApi({
6+
reducerPath: "kiCalendarApi",
7+
baseQuery: fetchBaseQuery({
8+
prepareHeaders: (headers) => {
9+
const token = axios.defaults.headers.common.Authorization;
10+
if (token) headers.set("Authorization", token);
11+
return headers;
12+
},
13+
}),
14+
endpoints: (builder) => ({
15+
getKICalendarEvents: builder.query({
16+
query: ({ month, year }) =>
17+
ENDPOINTS.KI_CALENDAR_EVENTS(month, year),
18+
// `kitchenandinventory/calendar?month=${month}&year=${year}`,
19+
}),
20+
}),
21+
});
22+
23+
export const { useGetKICalendarEventsQuery } = kiCalendarApi;

src/components/ApplicantVolunteerRatio/ApplicantVolunteerRatio.jsx

Lines changed: 128 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -9,77 +9,61 @@ import 'react-datepicker/dist/react-datepicker.css';
99

1010
function ApplicantVolunteerRatio() {
1111
const darkMode = useSelector(state => state.theme.darkMode);
12+
1213
const [data, setData] = useState([]);
13-
const [allRoles, setAllRoles] = useState([]); // Store all available roles
14+
const [allRoles, setAllRoles] = useState([]);
1415
const [loading, setLoading] = useState(true);
1516
const [error, setError] = useState(null);
1617
const [selectedRoles, setSelectedRoles] = useState([]);
1718
const [startDate, setStartDate] = useState(null);
1819
const [endDate, setEndDate] = useState(null);
1920
const [validationError, setValidationError] = useState('');
21+
const [viewMode, setViewMode] = useState('count');
2022

21-
// Fetch all available roles (without filtering)
23+
// Fetch all available roles
2224
useEffect(() => {
2325
const fetchAllRoles = async () => {
2426
try {
2527
const response = await getAllApplicantVolunteerRatios({});
26-
const apiData = response.data;
27-
28-
// Get all unique roles
28+
const apiData = response?.data ?? [];
2929
const uniqueRoles = [...new Set(apiData.map(item => item.role))];
3030
const roleOptions = uniqueRoles.map(role => ({ label: role, value: role }));
31-
3231
setAllRoles(roleOptions);
33-
34-
// Set all roles as selected by default
3532
setSelectedRoles(roleOptions);
3633
} catch (err) {
37-
// Error fetching all roles
3834
setError('Failed to load roles. Please try again.');
3935
}
4036
};
41-
4237
fetchAllRoles();
4338
}, []);
4439

45-
// Fetch filtered data based on selected roles and date range
40+
// Fetch filtered data
4641
useEffect(() => {
4742
const fetchFilteredData = async () => {
48-
// Validate date range: start must be before or equal to end
4943
if (startDate && endDate && startDate > endDate) {
5044
setValidationError('Start date must be earlier than or equal to End date.');
5145
setData([]);
5246
setLoading(false);
5347
return;
5448
}
5549

56-
// clear previous validation error when dates are valid
5750
if (validationError) setValidationError('');
5851

5952
if (selectedRoles.length === 0) {
6053
setData([]);
6154
return;
6255
}
63-
6456
try {
6557
setLoading(true);
66-
67-
// Prepare filters
6858
const filters = {};
69-
if (startDate) {
70-
filters.startDate = startDate.toISOString().split('T')[0]; // Format as YYYY-MM-DD
71-
}
72-
if (endDate) {
73-
filters.endDate = endDate.toISOString().split('T')[0]; // Format as YYYY-MM-DD
74-
}
59+
if (startDate) filters.startDate = startDate.toISOString().split('T')[0];
60+
if (endDate) filters.endDate = endDate.toISOString().split('T')[0];
7561
if (selectedRoles.length > 0) {
7662
filters.roles = selectedRoles.map(role => role.value).join(',');
7763
}
7864

7965
const response = await getAllApplicantVolunteerRatios(filters);
80-
const apiData = response.data;
81-
82-
// Transform API data to match chart format
66+
const apiData = response?.data ?? [];
8367
const transformedData = apiData.map(item => ({
8468
role: item.role,
8569
applicants: item.totalApplicants,
@@ -88,23 +72,34 @@ function ApplicantVolunteerRatio() {
8872

8973
setData(transformedData);
9074
} catch (err) {
91-
// Error fetching applicant volunteer ratio data
9275
setError('Failed to load data. Please try again.');
9376
} finally {
9477
setLoading(false);
9578
}
9679
};
97-
9880
fetchFilteredData();
99-
}, [startDate, endDate, selectedRoles]); // Re-fetch when date range or selected roles change
81+
}, [startDate, endDate, selectedRoles]);
10082

101-
// Filter and transform data for chart
102-
const chartData = useMemo(
103-
() => data.filter(d => selectedRoles.map(r => r.value).includes(d.role)),
104-
[data, selectedRoles],
105-
);
83+
// Prepare chart data
84+
const chartData = useMemo(() => {
85+
const filtered = data.filter(d => selectedRoles.map(r => r.value).includes(d.role));
86+
87+
if (viewMode === 'percentage') {
88+
return filtered.map(item => {
89+
const percentage =
90+
item.applicants > 0 ? Number(((item.hired / item.applicants) * 100).toFixed(1)) : 0;
91+
92+
return {
93+
...item,
94+
hiredPercentage: percentage,
95+
};
96+
});
97+
}
10698

107-
// Apply dark mode to document body
99+
return filtered;
100+
}, [data, selectedRoles, viewMode]);
101+
102+
// Apply dark mode
108103
useEffect(() => {
109104
if (darkMode) {
110105
document.body.classList.add('dark-mode-body');
@@ -138,10 +133,12 @@ function ApplicantVolunteerRatio() {
138133
return (
139134
<div className={`${styles.page} ${darkMode ? styles.dark : ''}`}>
140135
<h2 className={styles.heading}>Number of People Hired vs. Total Applications</h2>
136+
137+
{/* Filters */}
141138
<div className={styles.filters}>
142139
<div className={styles.filterGroup}>
143140
<label htmlFor="start-date" className={styles.label}>
144-
Date Range:{' '}
141+
Date Range:
145142
</label>
146143
<div className={styles.dateInputWrapper}>
147144
<DatePicker
@@ -173,9 +170,10 @@ function ApplicantVolunteerRatio() {
173170
</div>
174171
)}
175172
</div>
173+
176174
<div className={styles.filterGroupInline}>
177175
<label htmlFor="role-select" className={styles.label}>
178-
Role:{' '}
176+
Role:
179177
</label>
180178
<Select
181179
id="role-select"
@@ -189,39 +187,119 @@ function ApplicantVolunteerRatio() {
189187
/>
190188
</div>
191189
</div>
190+
191+
{/* Toggle Buttons */}
192+
<div style={{ marginBottom: '12px', display: 'flex', gap: '8px' }}>
193+
<button
194+
type="button"
195+
onClick={() => setViewMode('count')}
196+
style={{
197+
padding: '6px 12px',
198+
cursor: 'pointer',
199+
backgroundColor: viewMode === 'count' ? '#1976d2' : '#e0e0e0',
200+
color: viewMode === 'count' ? '#fff' : '#000',
201+
border: 'none',
202+
borderRadius: '4px',
203+
}}
204+
>
205+
Count View
206+
</button>
207+
208+
<button
209+
type="button"
210+
onClick={() => setViewMode('percentage')}
211+
style={{
212+
padding: '6px 12px',
213+
cursor: 'pointer',
214+
backgroundColor: viewMode === 'percentage' ? '#1976d2' : '#e0e0e0',
215+
color: viewMode === 'percentage' ? '#fff' : '#000',
216+
border: 'none',
217+
borderRadius: '4px',
218+
}}
219+
>
220+
Percentage View
221+
</button>
222+
</div>
223+
192224
{chartData.length > 0 ? (
193225
<div className={styles.chartContainer}>
194-
<ResponsiveContainer width="100%" height={400}>
226+
<ResponsiveContainer width="100%" height={350}>
195227
<BarChart
196228
data={chartData}
197229
layout="vertical"
198230
margin={{ top: 20, right: 40, left: 80, bottom: 20 }}
199231
barCategoryGap={24}
232+
barSize={16}
200233
>
201234
<XAxis
202235
type="number"
203-
label={{
204-
value: 'Percentage of People Hired vs. Total Applications',
205-
position: 'insideBottom',
206-
offset: -5,
207-
}}
208-
allowDecimals={false}
236+
domain={viewMode === 'percentage' ? [0, 100] : ['auto', 'auto']}
237+
allowDecimals={viewMode === 'percentage'}
209238
/>
239+
210240
<YAxis
211241
dataKey="role"
212242
type="category"
213243
width={180}
214-
label={{ value: 'Name of Role', angle: -90, position: 'insideLeft' }}
244+
label={{ value: 'Role', angle: -90, position: 'insideLeft' }}
215245
/>
246+
216247
<Tooltip />
217-
<Bar dataKey="applicants" fill="#1976d2" name="Total Applicants">
218-
<LabelList dataKey="applicants" position="right" />
219-
</Bar>
220-
<Bar dataKey="hired" fill="#43a047" name="Total Hired">
221-
<LabelList dataKey="hired" position="right" />
222-
</Bar>
248+
249+
{viewMode === 'count' ? (
250+
<>
251+
<Bar dataKey="applicants" fill="#1976d2">
252+
<LabelList dataKey="applicants" position="right" />
253+
</Bar>
254+
255+
<Bar dataKey="hired" fill="#43a047">
256+
<LabelList dataKey="hired" position="right" />
257+
</Bar>
258+
</>
259+
) : (
260+
<Bar dataKey="hiredPercentage" fill="#43a047">
261+
<LabelList
262+
dataKey="hiredPercentage"
263+
position="right"
264+
formatter={value => `${value}%`}
265+
/>
266+
</Bar>
267+
)}
223268
</BarChart>
224269
</ResponsiveContainer>
270+
271+
{/* Manual Legend */}
272+
<div
273+
style={{
274+
display: 'flex',
275+
justifyContent: 'center',
276+
gap: '16px',
277+
marginTop: '12px',
278+
fontWeight: 500,
279+
}}
280+
>
281+
{viewMode === 'count' ? (
282+
<>
283+
<span style={{ color: '#1976d2' }}>■ Total Applications</span>
284+
<span style={{ color: '#43a047' }}>■ People Hired</span>
285+
</>
286+
) : (
287+
<span style={{ color: '#43a047' }}>■ People Hired (%)</span>
288+
)}
289+
</div>
290+
291+
{/* Axis Title */}
292+
<div
293+
style={{
294+
textAlign: 'center',
295+
marginTop: '10px',
296+
fontWeight: 500,
297+
}}
298+
>
299+
{viewMode === 'percentage'
300+
? 'Percentage of People Hired (%)'
301+
: 'Number of Applications / Hires'}
302+
</div>
225303
</div>
226304
) : (
227305
<div className={styles.noData}>

0 commit comments

Comments
 (0)