Skip to content
Open
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
.header{
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
gap: 1rem;
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import { reverse } from '~/named-urls';
import { ROUTES } from '~/routes';
import { dbT, getObjectFieldOrNumber, lowerCapitalize } from '~/utils';
import { AdminPageLayout } from '../AdminPageLayout/AdminPageLayout';

import styles from './RecruitmentAdminPage.module.scss';
export function RecruitmentAdminPage() {
const navigate = useNavigate();
const [recruitments, setRecruitments] = useState<RecruitmentDto[]>([]);
Expand Down Expand Up @@ -99,11 +99,14 @@ export function RecruitmentAdminPage() {

const backendUrl = ROUTES.backend.admin__samfundet_recruitment_changelist;
const header = (
<>
<div className={styles.header}>
<Button theme="success" rounded={true} link={ROUTES.frontend.admin_recruitment_create}>
{lowerCapitalize(`${t(KEY.common_create)} ${t(KEY.common_recruitment)}`)}
</Button>
</>
<Button theme="yellow" rounded={true} link={ROUTES.frontend.admin_recruitment_statistics}>
{lowerCapitalize(`${t(KEY.recruitment_statistics)}`)}
</Button>
</div>
);

return (
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
@import 'src/constants.scss';
@use 'src/constants' as *;

.textBox,
.markdownWrapper {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
.charts_group {
display: flex;
flex-wrap: wrap;
align-items: center;
justify-content: center;
gap: 3rem;
background-color: rgba(201, 201, 201, 0.25);
}

.chart_wrapper {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 1rem;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import { Chart } from '~/Components';
import type { CartesianChartsData } from '~/Components/Chart/CartesianCharts/utils/types';
import styles from './ApplicantCountCharts.module.scss';

type ApplicantCountChartsProps = {
indexedHistoricUniqueApplicants: CartesianChartsData[];
indexedHistoricUniqueApplicantsSpring: CartesianChartsData[];
indexedHistoricUniqueApplicantsAutumn: CartesianChartsData[];
};

export function ApplicantCountCharts({
indexedHistoricUniqueApplicants,
indexedHistoricUniqueApplicantsSpring,
indexedHistoricUniqueApplicantsAutumn,
}: ApplicantCountChartsProps) {
return (
<div className={styles.charts_group}>
<div className={styles.chart_wrapper}>
<Chart
type="bar"
chartTitle={'Spring/Autumn'}
size="xlarge"
yAxisLegend={'Applicants'}
xAxisLegend={'Year'}
yLabelCount={10}
data={indexedHistoricUniqueApplicants}
/>
</div>
<div className={styles.chart_wrapper}>
<Chart
type="bar"
chartTitle={'Spring'}
size="xlarge"
yAxisLegend={'Applicants'}
xAxisLegend={'Semester'}
yLabelCount={10}
data={indexedHistoricUniqueApplicantsSpring}
/>
</div>
<div className={styles.chart_wrapper}>
<Chart
type="bar"
chartTitle={'Autumn'}
size="xlarge"
yAxisLegend={'Applicants'}
xAxisLegend={'Semester'}
yLabelCount={10}
data={indexedHistoricUniqueApplicantsAutumn}
/>
</div>
</div>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
.charts_container {
display: flex;
flex-direction: column;
gap: 2rem;
}

.charts_group {
display: flex;
overflow-x: scroll;
gap: 3rem;
background-color: rgba(201, 201, 201, 0.25);
border: 2px solid black;
}

.chart_wrapper {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 1rem;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { Chart, H4 } from '~/Components';
import type { CircularChartData } from '~/Components/Chart/CircularCharts';
import styles from './CampusDistributionCharts.module.scss';

type CampusDistributionChartsProps = {
historicCampusDistribution: Array<{ semester: string; distribution: CircularChartData[] }>;
};

export function CampusDistributionCharts({ historicCampusDistribution }: CampusDistributionChartsProps) {
if (historicCampusDistribution.length === 0) {
return <div className={styles.charts_group}>Loading campus distribution data...</div>;
}

return (
<>
<H4>These boxes contain the exact same data so that you can scroll to compare semesters.</H4>
<div className={styles.charts_container}>
<div className={styles.charts_group}>
{historicCampusDistribution.map((data) => (
<div key={data.semester} className={styles.chart_wrapper}>
<Chart
type="pie"
chartTitle={`${data.semester}`}
size="small"
data={data.distribution}
legend="Weighted by campus population"
/>
</div>
))}
</div>
<div className={styles.charts_group}>
{historicCampusDistribution.map((data) => (
<div key={data.semester} className={styles.chart_wrapper}>
<Chart
type="pie"
chartTitle={`${data.semester}`}
size="small"
data={data.distribution}
legend="Weighted by campus population"
/>
</div>
))}
</div>
</div>
</>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.under_construction {
background-color: yellow;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
import { type ReactNode, useEffect, useMemo, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { H1, type Tab, TabView } from '~/Components';
import type { CartesianChartsData } from '~/Components/Chart/CartesianCharts';
import type { CircularChartData } from '~/Components/Chart/CircularCharts';
import { AdminPageLayout } from '~/PagesAdmin/AdminPageLayout/AdminPageLayout';
import { useTitle } from '~/hooks';
import { KEY } from '~/i18n/constants';
import { ApplicantCountCharts } from './ApplicantCountCharts/ApplicantCountCharts';
import { CampusDistributionCharts } from './CampusDistributionCharts/CampusDistributionCharts';
import styles from './RecruitmentHistoricStatisticsAdminPage.module.scss';
import {
indexed_historic_unique_applicants,
indexed_historic_unique_applicants_h,
indexed_historic_unique_applicants_v,
} from './mock-applicant-count';
import { type CampusApplicantDataset, mock_campus_applicant_data } from './mock-campus-distribution';

const mapApplicantCountData = (data: { number: number; label: string }[]): CartesianChartsData[] => {
/**
* #######################################################################
* TODO:
* This map will probably have to change to
* adhere to the final backend solution.
* There will also probably be raw application count
* data in backend, where we calculate "safe-to-send" indexed data
* #######################################################################
*/
return data.map((dataItem) => ({
value: dataItem.number,
label: dataItem.label,
}));
};

const computeWeightedDistribution = (data: CampusApplicantDataset) => {
/**
* ################################################################
* TODO:
* This computation should probably be done in backend, or we should
* store the data in such a way that no computation is needed
* ################################################################
*/
return data.map((semesterData) => {
// Calculate the raw weighted values for each campus
const rawWeightedValues = semesterData.campus_applicant_data.map((campus) => ({
label: campus.label,
weightedValue: campus.applicants / campus.student_campus_count,
}));

// Calculate the total of all weighted values
const totalWeightedValue = rawWeightedValues.reduce((sum, campus) => sum + campus.weightedValue, 0);

// Normalize values to percentages (adding up to 100%)
const normalizedDistribution: CircularChartData[] = rawWeightedValues.map((campus) => ({
label: campus.label,
value: Number(((campus.weightedValue / totalWeightedValue) * 100).toFixed(2)),
}));

// Return object with semester and normalized campus distribution
return {
semester: semesterData.semester,
distribution: normalizedDistribution,
};
});
};

export function RecruitmentHistoricStatisticsAdminPage() {
const { t } = useTranslation();

useTitle(t(KEY.recruitment_overview));

// ----------------------------------------------------------------------------------------------------------------------------------------
/**
* #####################
* TODO:
* This will be replaced
* with actuall queries
* #####################
*
*/
const [indexedHistoricUniqueApplicants, setindexedHistoricUniqueApplicants] = useState<CartesianChartsData[]>([]);
const [indexedHistoricUniqueApplicantsSpring, setIndexedHistoricUniqueApplicantsSpring] = useState<
CartesianChartsData[]
>([]);
const [indexedHistoricUniqueApplicantsAutumn, setIndexedHistoricUniqueApplicantsAutumn] = useState<
CartesianChartsData[]
>([]);

/* Campus distribution data state */
const [historicCampusDistribution, setHistoricCampusDistribution] = useState<
Array<{ semester: string; distribution: CircularChartData[] }>
>([]);

useEffect(() => {
/* Applicant count state */
setindexedHistoricUniqueApplicants(mapApplicantCountData(indexed_historic_unique_applicants));
setIndexedHistoricUniqueApplicantsSpring(mapApplicantCountData(indexed_historic_unique_applicants_v));
setIndexedHistoricUniqueApplicantsAutumn(mapApplicantCountData(indexed_historic_unique_applicants_h));

/* Campus distribution state */
setHistoricCampusDistribution(computeWeightedDistribution(mock_campus_applicant_data));
}, []);
// ----------------------------------------------------------------------------------------------------------------------------------------

const tabs: Tab<ReactNode>[] = useMemo(() => {
return [
{
key: 1,
label: 'Indexed historic applicant count',
value: (
<ApplicantCountCharts
indexedHistoricUniqueApplicants={indexedHistoricUniqueApplicants}
indexedHistoricUniqueApplicantsSpring={indexedHistoricUniqueApplicantsSpring}
indexedHistoricUniqueApplicantsAutumn={indexedHistoricUniqueApplicantsAutumn}
/>
),
},
{
key: 2,
label: 'Weighted historic campus distribution',
value: <CampusDistributionCharts historicCampusDistribution={historicCampusDistribution} />,
},
];
}, [
indexedHistoricUniqueApplicants,
indexedHistoricUniqueApplicantsSpring,
indexedHistoricUniqueApplicantsAutumn,
historicCampusDistribution,
]);

return (
<AdminPageLayout title={'Historic recruitment data'}>
<H1 className={styles.under_construction}>This page is under construction, it is displaying mock data!</H1>
<TabView tabs={tabs} />
</AdminPageLayout>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { RecruitmentHistoricStatisticsAdminPage } from './RecruitmentHistoricStatisticsAdminPage';
Loading