diff --git a/backend/root/management/commands/seed_scripts/campus.py b/backend/root/management/commands/seed_scripts/campus.py index 87caeb146..a9ba9debf 100644 --- a/backend/root/management/commands/seed_scripts/campus.py +++ b/backend/root/management/commands/seed_scripts/campus.py @@ -16,10 +16,6 @@ 'name_en': 'Norwegian School of Photography', 'name_nb': 'Fotofagskolen', }, - { - 'name_en': 'Kristiania University College', - 'name_nb': 'Høyskolen Kristiania', - }, { 'name_en': 'Trondheim Academy of Fine Art', 'name_nb': 'Kunstakademiet i Trondheim', @@ -41,10 +37,6 @@ 'name_en': 'NTNU Øya', 'name_nb': 'NTNU Øya', }, - { - 'name_en': 'NTNU Rotvoll', - 'name_nb': 'NTNU Rotvoll', - }, { 'name_en': 'NTNU Tunga', 'name_nb': 'NTNU Tunga', diff --git a/backend/root/utils/routes.py b/backend/root/utils/routes.py index 8b0d4b2be..7e288b81d 100644 --- a/backend/root/utils/routes.py +++ b/backend/root/utils/routes.py @@ -534,6 +534,8 @@ samfundet__recruitment_for_recruiter_detail = 'samfundet:recruitment_for_recruiter-detail' samfundet__recruitment_stats_list = 'samfundet:recruitment_stats-list' samfundet__recruitment_stats_detail = 'samfundet:recruitment_stats-detail' +samfundet__recruitment_separateposition_list = 'samfundet:recruitment_separateposition-list' +samfundet__recruitment_separateposition_detail = 'samfundet:recruitment_separateposition-detail' samfundet__recruitment_position_list = 'samfundet:recruitment_position-list' samfundet__recruitment_position_detail = 'samfundet:recruitment_position-detail' samfundet__recruitment_position_for_applicant_list = 'samfundet:recruitment_position_for_applicant-list' diff --git a/backend/samfundet/migrations/0004_campus_total_students_and_more.py b/backend/samfundet/migrations/0004_campus_total_students_and_more.py new file mode 100644 index 000000000..82f9f3299 --- /dev/null +++ b/backend/samfundet/migrations/0004_campus_total_students_and_more.py @@ -0,0 +1,78 @@ +# Generated by Django 5.1.1 on 2024-09-24 17:14 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + dependencies = [ + ("samfundet", "0003_remove_gang_event_admin_group_and_more"), + ] + + operations = [ + migrations.AddField( + model_name="campus", + name="total_students", + field=models.PositiveIntegerField( + default=1, verbose_name="Total students enrolled" + ), + ), + migrations.AddField( + model_name="recruitmentcampusstat", + name="applicant_percentage", + field=models.PositiveIntegerField( + blank=True, + default=0, + null=True, + verbose_name="Percentages of enrolled students applied for campus", + ), + ), + migrations.AddField( + model_name="recruitmentgangstat", + name="average_priority", + field=models.FloatField( + blank=True, null=True, verbose_name="Average priority" + ), + ), + migrations.AddField( + model_name="recruitmentgangstat", + name="total_accepted", + field=models.PositiveIntegerField( + blank=True, null=True, verbose_name="Total accepted" + ), + ), + migrations.AddField( + model_name="recruitmentgangstat", + name="total_rejected", + field=models.PositiveIntegerField( + blank=True, null=True, verbose_name="Total called and rejected" + ), + ), + migrations.AddField( + model_name="recruitmentstatistics", + name="average_applications_per_applicant", + field=models.FloatField( + blank=True, null=True, verbose_name="Gang diversity" + ), + ), + migrations.AddField( + model_name="recruitmentstatistics", + name="average_gangs_applied_to_per_applicant", + field=models.FloatField( + blank=True, null=True, verbose_name="Gang diversity" + ), + ), + migrations.AddField( + model_name="recruitmentstatistics", + name="total_accepted", + field=models.PositiveIntegerField( + blank=True, null=True, verbose_name="Total accepted applicants" + ), + ), + migrations.AddField( + model_name="recruitmentstatistics", + name="total_withdrawn", + field=models.PositiveIntegerField( + blank=True, null=True, verbose_name="Total Withdrawn applications" + ), + ), + ] diff --git a/backend/samfundet/migrations/0005_role_content_type_role_created_at_role_created_by_and_more.py b/backend/samfundet/migrations/0005_role_content_type_role_created_at_role_created_by_and_more.py new file mode 100644 index 000000000..69d002827 --- /dev/null +++ b/backend/samfundet/migrations/0005_role_content_type_role_created_at_role_created_by_and_more.py @@ -0,0 +1,46 @@ +# Generated by Django 5.1.1 on 2024-09-24 22:26 + +import django.db.models.deletion +from django.conf import settings +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('contenttypes', '0002_remove_content_type_name'), + ('samfundet', '0004_campus_total_students_and_more'), + ] + + operations = [ + migrations.AddField( + model_name='role', + name='content_type', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='contenttypes.contenttype'), + ), + migrations.AddField( + model_name='role', + name='created_at', + field=models.DateTimeField(blank=True, editable=False, null=True), + ), + migrations.AddField( + model_name='role', + name='created_by', + field=models.ForeignKey(blank=True, editable=False, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to=settings.AUTH_USER_MODEL), + ), + migrations.AddField( + model_name='role', + name='updated_at', + field=models.DateTimeField(blank=True, editable=False, null=True), + ), + migrations.AddField( + model_name='role', + name='updated_by', + field=models.ForeignKey(blank=True, editable=False, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to=settings.AUTH_USER_MODEL), + ), + migrations.AddField( + model_name='role', + name='version', + field=models.PositiveIntegerField(blank=True, default=0, editable=False, null=True), + ), + ] diff --git a/backend/samfundet/models/general.py b/backend/samfundet/models/general.py index 1a9c25197..de01e0c96 100644 --- a/backend/samfundet/models/general.py +++ b/backend/samfundet/models/general.py @@ -86,6 +86,7 @@ class Campus(FullCleanSaveMixin): name_nb = models.CharField(max_length=64, unique=True, blank=False, null=False) name_en = models.CharField(max_length=64, unique=True, blank=False, null=False) abbreviation = models.CharField(max_length=10, blank=True, null=True) + total_students = models.PositiveIntegerField(null=False, blank=False, default=1, verbose_name='Total students enrolled') def __str__(self) -> str: if not self.abbreviation: diff --git a/backend/samfundet/models/recruitment.py b/backend/samfundet/models/recruitment.py index b7d351329..e48ef433b 100644 --- a/backend/samfundet/models/recruitment.py +++ b/backend/samfundet/models/recruitment.py @@ -203,7 +203,7 @@ def resolve_org(self, *, return_id: bool = False) -> Organization | int: return self.recruitment.resolve_org(return_id=return_id) def __str__(self) -> str: - return f'Seperate recruitment: {self.name_nb} ({self.recruitment})' + return f'Separate recruitment: {self.name_nb} ({self.recruitment})' class InterviewRoom(CustomBaseModel): @@ -455,9 +455,33 @@ class RecruitmentStatistics(FullCleanSaveMixin): total_applicants = models.PositiveIntegerField(null=True, blank=True, verbose_name='Total applicants') total_applications = models.PositiveIntegerField(null=True, blank=True, verbose_name='Total applications') + # Total withdrawn applications + total_withdrawn = models.PositiveIntegerField(null=True, blank=True, verbose_name='Total Withdrawn applications') + + # Total accepted applicants + total_accepted = models.PositiveIntegerField(null=True, blank=True, verbose_name='Total accepted applicants') + + # Average amount of different gangs an applicant applies for + average_gangs_applied_to_per_applicant = models.FloatField(null=True, blank=True, verbose_name='Gang diversity') + + # Average amount of applications for an applicant + average_applications_per_applicant = models.FloatField(null=True, blank=True, verbose_name='Gang diversity') + def save(self, *args: tuple, **kwargs: dict) -> None: self.total_applications = self.recruitment.applications.count() self.total_applicants = self.recruitment.applications.values('user').distinct().count() + self.total_withdrawn = self.recruitment.applications.filter(withdrawn=True).count() + self.total_accepted = ( + self.recruitment.applications.filter(recruiter_status=RecruitmentStatusChoices.CALLED_AND_ACCEPTED).values('user').distinct().count() + ) + if self.total_applicants > 0: + self.average_gangs_applied_to_per_applicant = ( + self.recruitment.applications.values('user', 'recruitment_position__gang').distinct().count() / self.total_applicants + ) + self.average_applications_per_applicant = self.total_applications / self.total_applicants if self.total_applicants > 0 else 0 + else: + self.average_gangs_applied_to_per_applicant = 0 + self.average_applications_per_applicant = 0 super().save(*args, **kwargs) self.generate_time_stats() self.generate_date_stats() @@ -542,6 +566,7 @@ class RecruitmentCampusStat(models.Model): campus = models.ForeignKey(Campus, on_delete=models.CASCADE, blank=False, null=False, related_name='date_stats') count = models.PositiveIntegerField(null=False, blank=False, verbose_name='Count') + applicant_percentage = models.PositiveIntegerField(null=True, blank=True, default=0, verbose_name='Percentages of enrolled students applied for campus') def __str__(self) -> str: return f'{self.recruitment_stats} {self.campus} {self.count}' @@ -550,8 +575,19 @@ def save(self, *args: tuple, **kwargs: dict) -> None: self.count = User.objects.filter( id__in=self.recruitment_stats.recruitment.applications.values_list('user', flat=True).distinct(), campus=self.campus ).count() + self.applicant_percentage = self.count / (self.campus.total_students if self.campus.total_students else 1) super().save(*args, **kwargs) + def normalized_applicant_percentage(self) -> float: + applicant_percentages = list( + RecruitmentCampusStat.objects.filter(recruitment_stats=self.recruitment_stats).values_list('applicant_percentage', flat=True) + ) + max_percent = max(applicant_percentages) + min_percent = min(applicant_percentages) + if max_percent - min_percent == 0: + return 0 + return (self.applicant_percentage - min_percent) / (max_percent - min_percent) + def resolve_org(self, *, return_id: bool = False) -> Organization | int: return self.recruitment_stats.resolve_org(return_id=return_id) @@ -563,11 +599,19 @@ class RecruitmentGangStat(models.Model): application_count = models.PositiveIntegerField(null=False, blank=False, verbose_name='Count') applicant_count = models.PositiveIntegerField(null=False, blank=False, verbose_name='Count') + average_priority = models.FloatField(null=True, blank=True, verbose_name='Average priority') + total_accepted = models.PositiveIntegerField(null=True, blank=True, verbose_name='Total accepted') + total_rejected = models.PositiveIntegerField(null=True, blank=True, verbose_name='Total called and rejected') + def __str__(self) -> str: return f'{self.recruitment_stats} {self.gang} {self.application_count}' def save(self, *args: tuple, **kwargs: dict) -> None: applications = RecruitmentApplication.objects.filter(recruitment=self.recruitment_stats.recruitment, recruitment_position__gang=self.gang) self.application_count = applications.count() - self.applicant_count = applications.values_list('user', flat=True).distinct().count() + self.applicant_count = applications.values('user').distinct().count() + + self.average_priority = applications.aggregate(models.Avg('applicant_priority'))['applicant_priority__avg'] if len(applications) > 0 else 0 + self.total_accepted = applications.filter(recruiter_status=RecruitmentStatusChoices.CALLED_AND_ACCEPTED).values('user').distinct().count() + self.total_rejected = applications.filter(recruiter_status=RecruitmentStatusChoices.CALLED_AND_REJECTED).values('user').distinct().count() super().save(*args, **kwargs) diff --git a/backend/samfundet/models/role.py b/backend/samfundet/models/role.py index 826ffab14..27f7d93d0 100644 --- a/backend/samfundet/models/role.py +++ b/backend/samfundet/models/role.py @@ -2,13 +2,15 @@ from django.db import models from django.conf import settings +from django.contrib.contenttypes.models import ContentType from root.utils.mixins import CustomBaseModel -class Role(models.Model): +class Role(CustomBaseModel): name = models.CharField(max_length=255) permissions = models.ManyToManyField('auth.Permission') + content_type = models.ForeignKey(ContentType, null=True, blank=True, on_delete=models.CASCADE) def __str__(self) -> str: return self.name diff --git a/backend/samfundet/serializers.py b/backend/samfundet/serializers.py index d2032f4ee..2313d3a5e 100644 --- a/backend/samfundet/serializers.py +++ b/backend/samfundet/serializers.py @@ -18,6 +18,7 @@ from root.constants import PHONE_NUMBER_REGEX from root.utils.mixins import CustomBaseSerializer +from .models.role import Role from .models.event import Event, EventGroup, EventCustomTicket, PurchaseFeedbackModel, PurchaseFeedbackQuestion, PurchaseFeedbackAlternative from .models.billig import BilligEvent, BilligPriceGroup, BilligTicketGroup from .models.general import ( @@ -484,6 +485,12 @@ class Meta: fields = '__all__' +class RoleSerializer(CustomBaseSerializer): + class Meta: + model = Role + fields = '__all__' + + class SaksdokumentSerializer(CustomBaseSerializer): # Read only url file path used in frontend url = serializers.SerializerMethodField(method_name='get_url', read_only=True) @@ -604,6 +611,7 @@ class Meta: class RecruitmentCampusStatSerializer(serializers.ModelSerializer): campus = serializers.SerializerMethodField(method_name='campus_name', read_only=True) + applicant_percentage = serializers.SerializerMethodField(method_name='get_applicant_percentage', read_only=True) class Meta: model = RecruitmentCampusStat @@ -612,6 +620,9 @@ class Meta: def campus_name(self, stat: RecruitmentCampusStat) -> str: return stat.campus.name_nb if stat.campus else None + def get_applicant_percentage(self, stat: RecruitmentCampusStat) -> float: + return stat.normalized_applicant_percentage() + class RecruitmentGangStatSerializer(serializers.ModelSerializer): gang = serializers.SerializerMethodField(method_name='gang_name', read_only=True) @@ -709,6 +720,8 @@ class RecruitmentSeparatePositionSerializer(CustomBaseSerializer): class Meta: model = RecruitmentSeparatePosition fields = [ + 'id', + 'recruitment', 'name_nb', 'name_en', 'description_nb', @@ -731,7 +744,7 @@ def to_representation(self, instance: Recruitment) -> dict: class RecruitmentForRecruiterSerializer(CustomBaseSerializer): - seperate_positions = RecruitmentSeparatePositionSerializer(many=True, read_only=True) + separate_positions = RecruitmentSeparatePositionSerializer(many=True, read_only=True) recruitment_progress = serializers.SerializerMethodField(method_name='get_recruitment_progress', read_only=True) statistics = RecruitmentStatisticsSerializer(read_only=True) diff --git a/backend/samfundet/urls.py b/backend/samfundet/urls.py index cc49592f0..136dd8273 100644 --- a/backend/samfundet/urls.py +++ b/backend/samfundet/urls.py @@ -36,11 +36,13 @@ router.register('key-value', views.KeyValueView, 'key_value') router.register('organizations', views.OrganizationView, 'organizations') router.register('merch', views.MerchView, 'merch') +router.register('role', views.RoleView, 'role') ########## Recruitment ########## router.register('recruitment', views.RecruitmentView, 'recruitment') router.register('recruitment-for-recruiter', views.RecruitmentForRecruiterView, 'recruitment_for_recruiter') router.register('recruitment-stats', views.RecruitmentStatisticsView, 'recruitment_stats') +router.register('recruitment-separateposition', views.RecruitmentSeparatePositionView, 'recruitment_separateposition') router.register('recruitment-position', views.RecruitmentPositionView, 'recruitment_position') router.register('recruitment-position-for-applicant', views.RecruitmentPositionForApplicantView, 'recruitment_position_for_applicant') router.register('recruitment-applications-for-applicant', views.RecruitmentApplicationForApplicantView, 'recruitment_applications_for_applicant') diff --git a/backend/samfundet/views.py b/backend/samfundet/views.py index d8f9122ae..b21947936 100644 --- a/backend/samfundet/views.py +++ b/backend/samfundet/views.py @@ -38,10 +38,12 @@ from .utils import event_query, generate_timeslots, get_occupied_timeslots_from_request from .homepage import homepage +from .models.role import Role from .serializers import ( TagSerializer, GangSerializer, MenuSerializer, + RoleSerializer, UserSerializer, EventSerializer, GroupSerializer, @@ -79,6 +81,7 @@ RecruitmentPositionSerializer, RecruitmentStatisticsSerializer, RecruitmentForRecruiterSerializer, + RecruitmentSeparatePositionSerializer, RecruitmentApplicationForGangSerializer, RecruitmentUpdateUserPrioritySerializer, RecruitmentPositionForApplicantSerializer, @@ -129,6 +132,7 @@ RecruitmentPosition, RecruitmentStatistics, RecruitmentApplication, + RecruitmentSeparatePosition, RecruitmentInterviewAvailability, ) from .models.model_choices import RecruitmentStatusChoices, RecruitmentPriorityChoices @@ -310,6 +314,12 @@ class BlogPostView(ModelViewSet): queryset = BlogPost.objects.all() +class RoleView(ModelViewSet): + permission_classes = (DjangoModelPermissionsOrAnonReadOnly,) + serializer_class = RoleSerializer + queryset = Role.objects.all() + + # =============================== # # Sulten # # =============================== # @@ -641,6 +651,13 @@ class RecruitmentPositionForApplicantView(ModelViewSet): queryset = RecruitmentPosition.objects.all() +@method_decorator(ensure_csrf_cookie, 'dispatch') +class RecruitmentSeparatePositionView(ModelViewSet): + permission_classes = [IsAuthenticated] + serializer_class = RecruitmentSeparatePositionSerializer + queryset = RecruitmentSeparatePosition.objects.all() + + class RecruitmentApplicationView(ModelViewSet): permission_classes = [AllowAny] serializer_class = RecruitmentApplicationForGangSerializer diff --git a/docs/technical/pipeline.md b/docs/technical/pipeline.md index a020a7a8e..40bdb4d8e 100644 --- a/docs/technical/pipeline.md +++ b/docs/technical/pipeline.md @@ -1,17 +1,89 @@ # Pipelines +Is your PR not passing the pipeline checks? Look no further. +Below you will find a collection of commands thats run by the pipeline and will allow you to check and fix issues locally. +At the bottom of each section is a link to a comprehensive collection of all commands, should you discover a missing command you find it there. (In that case please add the command to this doc) + ## Frontend -_No docs yet_ +_Install yarn_ + +``` +yarn install +``` + +_Run Biome_ + +``` +yarn run biome:ci +``` + +_fix biome_ + +``` +yarn run biome:fix +``` + +_Run Stylelint_ + +``` +yarn run stylelint:check +``` + +_Run typescript compiler check_ + +``` +yarn run tsc:check +``` + +Didnt find what you were looking for? See all backend commands [here](../../frontend/package.json) ## Backend -_No docs yet_ +``` +poetry install +``` + +_Run Ruff_ + +``` +poetry run ruff check +``` + +_Run Ruff fix_ + +``` +poetry run ruff check --fix +``` + +_Verify migrations_ + +``` +poetry run python manage.py makemigrations --check --dry-run --noinput --verbosity 2 +``` + +_Apply migrations_ + +``` +poetry run python manage.py migrate +``` + +_Run (Py)tests_ + +``` +poetry run pytest +``` + +_Run mypy_ -### Yapf +``` +poetry run mypy --config-file mypy.ini . +``` -Python formatter +_Run seed_ -### mypy +``` +poetry run python manage.py seed +``` -Static typing +Didnt find what you were looking for? See all frontend commands [here](../../backend/aliases.sh) diff --git a/frontend/src/Pages/ContributorsPage/ContributorsPage.tsx b/frontend/src/Pages/ContributorsPage/ContributorsPage.tsx index 5affaf8ee..2d090cc5f 100644 --- a/frontend/src/Pages/ContributorsPage/ContributorsPage.tsx +++ b/frontend/src/Pages/ContributorsPage/ContributorsPage.tsx @@ -12,14 +12,20 @@ import { type Contributor, ContributorItem } from './components'; // biome-ignore format: array should not be formatted const CONTRIBUTORS: Contributor[] = [ // H17 - { name: 'Kevin Kristiansen', github: 'KevinKristiansen', from: 'H17', to: 'V20', websjef: { from: 'V18', to: 'H18' } }, + { + name: 'Kevin Kristiansen', + github: 'KevinKristiansen', + from: 'H17', + to: 'V20', + websjef: { from: 'V18', to: 'H18' }, + }, // H19 { name: 'Emil Telstad', github: 'emilte', from: 'H19' }, { name: 'Sigve Røkenes', github: 'evgiz', from: 'H19', to: 'H23', websjef: { from: 'H20', to: 'V21' } }, // V20 { name: 'Marcus Frenje', github: 'Frenje123', from: 'V20' }, // H20 - { name: 'Magnus Øvre Sygard', github: 'magsyg', picture:sygard, from: 'H20' }, + { name: 'Magnus Øvre Sygard', github: 'magsyg', picture: sygard, from: 'H20' }, { name: 'Snorre Skjellestad Kristiansen', github: 'snorrekr', from: 'H20', websjef: { from: 'H21', to: 'V22' } }, // V21 { name: 'Johanne Dybevik', github: 'JohanneD', from: 'V21', to: 'V24' }, @@ -27,13 +33,19 @@ const CONTRIBUTORS: Contributor[] = [ { name: 'Mathias Aas', github: 'Mathias-a', from: 'H21', websjef: { from: 'H22', to: 'V23' } }, // V22 { name: 'Sindre Lothe', github: 'sindrelothe', from: 'V22', to: 'H23' }, - { name: 'Simen Myrrusten', github: 'nemisis84', from: 'V22', picture: simenProffen}, + { name: 'Simen Myrrusten', github: 'nemisis84', from: 'V22', picture: simenProffen }, // H22 { name: 'Snorre Sæther', github: 'Snorre98', from: 'H22', picture: snorre98, websjef: { from: 'H24', to: 'V25' } }, { name: 'Eirik Hoem', github: 'eiriksho', from: 'H22', to: 'V23' }, { name: 'Simen Seeberg-Rommetveit', github: 'simensee', from: 'H22' }, // V23 - { name: 'Robin Espinosa Jelle', github: 'robines', from: 'V23', picture: robines, websjef: { from: 'H23', to: 'V24' } }, + { + name: 'Robin Espinosa Jelle', + github: 'robines', + from: 'V23', + picture: robines, + websjef: { from: 'H23', to: 'V24' }, + }, { name: 'Johanne Grønlien Gjedrem', github: 'johannegg', from: 'V23' }, { name: 'Tinius Presterud', github: 'tiniuspre', from: 'V23' }, // H23 @@ -42,7 +54,10 @@ const CONTRIBUTORS: Contributor[] = [ { name: 'Heidi Herfindal Rasmussen', github: 'hei98', from: 'H23' }, { name: 'Erik Hoff', github: 'aTrueYety', from: 'H23' }, // V24 - { name: 'Emil Solberg', github: 'emsoraffa', from: 'H24' }, + { name: 'Emil Solberg', github: 'emsoraffa', from: 'V24' }, + // H24 + { name: 'Anders Meyer Hegre', github: 'andsamfu', from: 'H24'}, + { name: 'Lida Victoria Johnsen', github: 'lidavic', from: 'H24' }, ]; export function ContributorsPage() { diff --git a/frontend/src/PagesAdmin/RecruitmentGangOverviewPage/RecruitmentGangOverviewPage.tsx b/frontend/src/PagesAdmin/RecruitmentGangOverviewPage/RecruitmentGangOverviewPage.tsx index f16d4ddb1..7a5f1d97b 100644 --- a/frontend/src/PagesAdmin/RecruitmentGangOverviewPage/RecruitmentGangOverviewPage.tsx +++ b/frontend/src/PagesAdmin/RecruitmentGangOverviewPage/RecruitmentGangOverviewPage.tsx @@ -1,44 +1,52 @@ -import { useEffect, useState } from 'react'; +import { type ReactElement, useEffect, useState } from 'react'; import { useTranslation } from 'react-i18next'; -import { useParams, useRouteLoaderData } from 'react-router-dom'; -import { Button, Link, OccupiedFormModal } from '~/Components'; +import { useParams } from 'react-router-dom'; +import { Button, CrudButtons, Link, OccupiedFormModal, type Tab, TabView } from '~/Components'; import { Table } from '~/Components/Table'; -import { getRecruitmentGangs } from '~/api'; -import type { RecruitmentGangDto } from '~/dto'; -import { useTitle } from '~/hooks'; +import { deleteRecruitmentSeparatePosition, getRecruitment, getRecruitmentGangs } from '~/api'; +import type { RecruitmentDto, RecruitmentGangDto, RecruitmentSeparatePositionDto } from '~/dto'; +import { useCustomNavigate, useTitle } from '~/hooks'; import { KEY } from '~/i18n/constants'; import { reverse } from '~/named-urls'; -import type { RecruitmentLoader } from '~/router/loaders'; import { ROUTES } from '~/routes'; -import { dbT } from '~/utils'; +import { dbT, lowerCapitalize } from '~/utils'; import { AdminPageLayout } from '../AdminPageLayout/AdminPageLayout'; export function RecruitmentGangOverviewPage() { - const { recruitment } = useRouteLoaderData('recruitment') as RecruitmentLoader; const { recruitmentId } = useParams(); + const navigate = useCustomNavigate(); const [gangs, setGangs] = useState([]); + const [recruitment, setRecruitment] = useState(); const [loading, setLoading] = useState(true); const { t } = useTranslation(); const title = dbT(recruitment, 'name') || t(KEY.common_unknown); useTitle(title); useEffect(() => { - if (!recruitment?.id) { + if (!recruitmentId) { return; } - getRecruitmentGangs(recruitment.id).then((data) => { - setGangs(data); + Promise.all([ + getRecruitmentGangs(recruitmentId).then((data) => { + setGangs(data); + setLoading(false); + }), + getRecruitment(recruitmentId).then((response) => { + setRecruitment(response.data); + setLoading(false); + }), + ]).then(() => { setLoading(false); }); - }, [recruitment]); + }, [recruitmentId]); - const tableColumns = [ + const tableGangColumns = [ { content: t(KEY.common_gang), sortable: true }, { content: t(KEY.recruitment_positions), sortable: true }, ]; // TODO: Only show gangs that user has access to, and only show gangs that are recruiting. ISSUE #1121 - const data = gangs.map((gang) => { + const tableGangData = gangs.map((gang) => { const pageUrl = reverse({ pattern: ROUTES.frontend.admin_recruitment_gang_position_overview, urlParams: { recruitmentId: recruitmentId, gangId: gang.id }, @@ -47,6 +55,49 @@ export function RecruitmentGangOverviewPage() { return [{ content: {dbT(gang, 'name')} }, gang.recruitment_positions]; }); + const tableSeparatePositionColumns = [ + { content: t(KEY.common_gang), sortable: true }, + { content: t(KEY.common_url), sortable: true }, + { content: t(KEY.common_administrate), sortable: false }, + ]; + + async function deleteSeparatePositionHandler(separate_position: RecruitmentSeparatePositionDto) { + if (separate_position.id && recruitmentId) { + const msg = lowerCapitalize(`${t(KEY.form_confirm)} ${t(KEY.common_delete)}`); + if (window.confirm(`${msg} ${dbT(separate_position, 'name')}`)) { + deleteRecruitmentSeparatePosition(separate_position.id.toString()).then(() => + getRecruitment(recruitmentId).then((response) => { + setRecruitment(response.data); + }), + ); + } + } + } + + const tableSeparatePositionData = recruitment?.separate_positions?.map((separate_position) => { + const pageUrl = reverse({ + pattern: ROUTES.frontend.admin_recruitment_gang_separateposition_edit, + urlParams: { recruitmentId: recruitmentId, separatePositionId: separate_position.id }, + }); + + return [ + { content: {dbT(separate_position, 'name')} }, + { content: {separate_position.url} }, + { + content: ( + { + deleteSeparatePositionHandler(separate_position); + }} + onEdit={() => { + navigate({ url: pageUrl }); + }} + /> + ), + }, + ]; + }); + const backendUrl = ROUTES.backend.admin__samfundet_informationpage_changelist; const header = ( <> @@ -102,13 +153,43 @@ export function RecruitmentGangOverviewPage() { > {t(KEY.common_edit)} + + {recruitmentId && } ); + // TODO: Fix rerender + const tabs: Tab[] = [ + { key: 1, label: t(KEY.common_gangs), value: }, + { + key: 2, + label: t(KEY.recruitment_gangs_with_separate_positions), + value:
, + }, + ]; + return ( -
+ ); } diff --git a/frontend/src/PagesAdmin/RecruitmentPositionOverviewPage/RecruitmentPositionOverviewPage.tsx b/frontend/src/PagesAdmin/RecruitmentPositionOverviewPage/RecruitmentPositionOverviewPage.tsx index 743d62bba..a1d57ad34 100644 --- a/frontend/src/PagesAdmin/RecruitmentPositionOverviewPage/RecruitmentPositionOverviewPage.tsx +++ b/frontend/src/PagesAdmin/RecruitmentPositionOverviewPage/RecruitmentPositionOverviewPage.tsx @@ -24,11 +24,15 @@ export function RecruitmentPositionOverviewPage() { const [rejectedApplicants, setRejectedApplicants] = useState([]); const [acceptedApplicants, setAcceptedApplicants] = useState([]); + const [recruiterStatuses, setRecruiterStatuses] = useState<[][]>([]); + const [showSpinner, setShowSpinner] = useState(true); const { t } = useTranslation(); useEffect(() => { + // getRecruitmentApplicationStateChoices recruitmentId && gangId && + positionId && getRecruitmentApplicationsForGang(gangId, recruitmentId) .then((data) => { setRecruitmentApplicants( @@ -36,13 +40,14 @@ export function RecruitmentPositionOverviewPage() { (recruitmentApplicant) => !recruitmentApplicant.withdrawn && recruitmentApplicant.recruiter_status === 0 && - recruitmentApplicant.recruitment_position?.id === positionId, + recruitmentApplicant.recruitment_position?.id === Number.parseInt(positionId), ), ); setWithdrawnApplicants( data.data.filter( (recruitmentApplicant) => - recruitmentApplicant.withdrawn && recruitmentApplicant.recruitment_position?.id === positionId, + recruitmentApplicant.withdrawn && + recruitmentApplicant.recruitment_position?.id === Number.parseInt(positionId), ), ); setRejectedApplicants( @@ -50,7 +55,7 @@ export function RecruitmentPositionOverviewPage() { (recruitmentApplicant) => !recruitmentApplicant.withdrawn && (recruitmentApplicant.recruiter_status === 2 || recruitmentApplicant.recruiter_status === 3) && - recruitmentApplicant.recruitment_position?.id === positionId, + recruitmentApplicant.recruitment_position?.id === Number.parseInt(positionId), ), ); setAcceptedApplicants( @@ -58,7 +63,7 @@ export function RecruitmentPositionOverviewPage() { (recruitmentApplicant) => !recruitmentApplicant.withdrawn && recruitmentApplicant.recruiter_status === 1 && - recruitmentApplicant.recruitment_position?.id === positionId, + recruitmentApplicant.recruitment_position?.id === Number.parseInt(positionId), ), ); setShowSpinner(false); @@ -72,44 +77,46 @@ export function RecruitmentPositionOverviewPage() { }, [recruitmentId, gangId, positionId, navigate, t]); const updateApplicationState = (id: string, data: RecruitmentApplicationStateDto) => { - updateRecruitmentApplicationStateForPosition(id, data) - .then((data) => { - setRecruitmentApplicants( - data.data.filter( - (recruitmentApplicant) => - !recruitmentApplicant.withdrawn && - recruitmentApplicant.recruiter_status === 0 && - recruitmentApplicant.recruitment_position?.id === positionId, - ), - ); - setWithdrawnApplicants( - data.data.filter( - (recruitmentApplicant) => - recruitmentApplicant.withdrawn && recruitmentApplicant.recruitment_position?.id === positionId, - ), - ); - setRejectedApplicants( - data.data.filter( - (recruitmentApplicant) => - !recruitmentApplicant.withdrawn && - (recruitmentApplicant.recruiter_status === 2 || recruitmentApplicant.recruiter_status === 3) && - recruitmentApplicant.recruitment_position?.id === positionId, - ), - ); - setAcceptedApplicants( - data.data.filter( - (recruitmentApplicant) => - !recruitmentApplicant.withdrawn && - recruitmentApplicant.recruiter_status === 1 && - recruitmentApplicant.recruitment_position?.id === positionId, - ), - ); - setShowSpinner(false); - }) - .catch((data) => { - toast.error(t(KEY.common_something_went_wrong)); - console.error(data); - }); + positionId && + updateRecruitmentApplicationStateForPosition(id, data) + .then((data) => { + setRecruitmentApplicants( + data.data.filter( + (recruitmentApplicant) => + !recruitmentApplicant.withdrawn && + recruitmentApplicant.recruiter_status === 0 && + recruitmentApplicant.recruitment_position?.id === Number.parseInt(positionId), + ), + ); + setWithdrawnApplicants( + data.data.filter( + (recruitmentApplicant) => + recruitmentApplicant.withdrawn && + recruitmentApplicant.recruitment_position?.id === Number.parseInt(positionId), + ), + ); + setRejectedApplicants( + data.data.filter( + (recruitmentApplicant) => + !recruitmentApplicant.withdrawn && + (recruitmentApplicant.recruiter_status === 2 || recruitmentApplicant.recruiter_status === 3) && + recruitmentApplicant.recruitment_position?.id === Number.parseInt(positionId), + ), + ); + setAcceptedApplicants( + data.data.filter( + (recruitmentApplicant) => + !recruitmentApplicant.withdrawn && + recruitmentApplicant.recruiter_status === 1 && + recruitmentApplicant.recruitment_position?.id === Number.parseInt(positionId), + ), + ); + setShowSpinner(false); + }) + .catch((data) => { + toast.error(t(KEY.common_something_went_wrong)); + console.error(data); + }); }; const title = t(KEY.recruitment_administrate_applications); diff --git a/frontend/src/PagesAdmin/RecruitmentSeparatePositionFormAdminPage/RecruitmentSeparatePositionFormAdminPage.module.scss b/frontend/src/PagesAdmin/RecruitmentSeparatePositionFormAdminPage/RecruitmentSeparatePositionFormAdminPage.module.scss new file mode 100644 index 000000000..8c9795666 --- /dev/null +++ b/frontend/src/PagesAdmin/RecruitmentSeparatePositionFormAdminPage/RecruitmentSeparatePositionFormAdminPage.module.scss @@ -0,0 +1,26 @@ +@import 'src/mixins'; + +@import 'src/constants'; + +.wrapper { + margin: 0; + width: 100%; + display: flex; + flex-direction: column; +} + +.row { + display: flex; + flex-direction: column; + margin-top: 1em; + gap: 1em; + @include for-tablet-up { + flex-direction: row; + } +} + +.spinner { + display: flex; + justify-content: center; + margin-top: 20%; +} diff --git a/frontend/src/PagesAdmin/RecruitmentSeparatePositionFormAdminPage/RecruitmentSeparatePositionFormAdminPage.tsx b/frontend/src/PagesAdmin/RecruitmentSeparatePositionFormAdminPage/RecruitmentSeparatePositionFormAdminPage.tsx new file mode 100644 index 000000000..79e63679f --- /dev/null +++ b/frontend/src/PagesAdmin/RecruitmentSeparatePositionFormAdminPage/RecruitmentSeparatePositionFormAdminPage.tsx @@ -0,0 +1,159 @@ +import { useEffect, useState } from 'react'; +import { useTranslation } from 'react-i18next'; +import { useNavigate, useParams } from 'react-router-dom'; +import { toast } from 'react-toastify'; +import { SamfundetLogoSpinner } from '~/Components'; +import { SamfForm } from '~/Forms/SamfForm'; +import { SamfFormField } from '~/Forms/SamfFormField'; +import { getRecruitmentSeparatePosition, postRecruitmentSeparatePosition, putRecruitmentSeparatePosition } from '~/api'; +import type { RecruitmentSeparatePositionDto } from '~/dto'; +import { STATUS } from '~/http_status_codes'; +import { KEY } from '~/i18n/constants'; +import { reverse } from '~/named-urls'; +import { ROUTES } from '~/routes'; +import { AdminPageLayout } from '../AdminPageLayout/AdminPageLayout'; +import styles from './RecruitmentSeparatePositionFormAdminPage.module.scss'; + +type FormType = { + name_nb: string; + name_en: string; + + description_nb: string; + description_en: string; + + url: string; +}; + +export function RecruitmentSeparatePositionFormAdminPage() { + const { t } = useTranslation(); + const navigate = useNavigate(); + + // Form data + const { recruitmentId, separatePositionId } = useParams(); + const [showSpinner, setShowSpinner] = useState(true); + const [separatePosition, setSeparatePosition] = useState>(); + + // Fetch data if edit mode. + useEffect(() => { + if (separatePositionId) { + getRecruitmentSeparatePosition(separatePositionId) + .then((response) => { + setSeparatePosition(response.data); + setShowSpinner(false); + }) + .catch((response) => { + if (response.request.status === STATUS.HTTP_404_NOT_FOUND) { + navigate( + reverse({ + pattern: ROUTES.frontend.admin_recruitment_gang_overview, + urlParams: { recruitmentId: recruitmentId }, + }), + { replace: true }, + ); + } + toast.error(t(KEY.common_something_went_wrong)); + }); + } else { + setShowSpinner(false); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [separatePositionId, navigate, recruitmentId, t]); + + const initialData: Partial = { + name_nb: separatePosition?.name_nb, + name_en: separatePosition?.name_en, + description_nb: separatePosition?.description_nb, + description_en: separatePosition?.description_en, + url: separatePosition?.url, + recruitment: separatePosition ? separatePosition?.recruitment : recruitmentId, + }; + + const submitText = separatePositionId ? t(KEY.common_save) : t(KEY.common_create); + + // Loading. + if (showSpinner) { + return ( +
+ +
+ ); + } + + function handleOnSubmit(data: RecruitmentSeparatePositionDto) { + const updatedPosition = data; + if (separatePositionId) { + // Update page. + putRecruitmentSeparatePosition(separatePositionId, updatedPosition) + .then(() => { + toast.success(t(KEY.common_update_successful)); + navigate( + reverse({ + pattern: ROUTES.frontend.admin_recruitment_gang_overview, + urlParams: { recruitmentId: recruitmentId }, + }), + ); + }) + .catch((error) => { + toast.error(t(KEY.common_something_went_wrong)); + console.error(error); + }); + } else { + // Post new page. + postRecruitmentSeparatePosition(updatedPosition) + .then(() => { + navigate( + reverse({ + pattern: ROUTES.frontend.admin_recruitment_gang_overview, + urlParams: { recruitmentId: recruitmentId }, + }), + ); + toast.success(t(KEY.common_creation_successful)); + }) + .catch((error) => { + toast.error(t(KEY.common_something_went_wrong)); + console.error(error); + }); + } + } + return ( + <> + +
+ onSubmit={handleOnSubmit} initialData={initialData} submitText={submitText}> +
+ + field="name_nb" + type="text" + label={`${t(KEY.common_name)} ${t(KEY.common_norwegian)}`} + required={true} + /> + + field="name_en" + type="text" + required={true} + label={`${t(KEY.common_name)} ${t(KEY.common_english)}`} + /> +
+
+ + field="description_nb" + type="text_long" + label={`${t(KEY.common_short_description)} ${t(KEY.common_norwegian)}`} + required={true} + /> + + field="description_en" + required={true} + type="text_long" + label={`${t(KEY.common_short_description)} ${t(KEY.common_english)}`} + /> +
+
+ field="url" type="text" label={t(KEY.common_url)} required={true} /> +
+ +
+
+ + ); +} diff --git a/frontend/src/PagesAdmin/RecruitmentSeparatePositionFormAdminPage/index.ts b/frontend/src/PagesAdmin/RecruitmentSeparatePositionFormAdminPage/index.ts new file mode 100644 index 000000000..6f45aac48 --- /dev/null +++ b/frontend/src/PagesAdmin/RecruitmentSeparatePositionFormAdminPage/index.ts @@ -0,0 +1 @@ +export { RecruitmentSeparatePositionFormAdminPage } from './RecruitmentSeparatePositionFormAdminPage'; diff --git a/frontend/src/PagesAdmin/RoomAdminPage/CreateInterviewRoomPage/CreateInterviewRoomPage.tsx b/frontend/src/PagesAdmin/RoomAdminPage/CreateInterviewRoomPage/CreateInterviewRoomPage.tsx new file mode 100644 index 000000000..f2c90feda --- /dev/null +++ b/frontend/src/PagesAdmin/RoomAdminPage/CreateInterviewRoomPage/CreateInterviewRoomPage.tsx @@ -0,0 +1,146 @@ +import { useEffect, useState } from 'react'; +import { useTranslation } from 'react-i18next'; +import { useNavigate, useParams } from 'react-router-dom'; +import { toast } from 'react-toastify'; +import { SamfundetLogoSpinner } from '~/Components'; +import { SamfForm } from '~/Forms/SamfForm'; +import { SamfFormField } from '~/Forms/SamfFormField'; +import { AdminPageLayout } from '~/PagesAdmin/AdminPageLayout/AdminPageLayout'; +import { getInterviewRoom, postInterviewRoom, putInterviewRoom } from '~/api'; +import type { InterviewRoomDto } from '~/dto'; +import { STATUS } from '~/http_status_codes'; +import { KEY } from '~/i18n/constants'; +import { reverse } from '~/named-urls'; +import { ROUTES } from '~/routes'; + +type FormType = { + name: string; + location: string; + start_time: string; + end_time: string; +}; + +export function CreateInterviewRoomPage() { + const { t } = useTranslation(); + const navigate = useNavigate(); + + const { recruitmentId, roomId } = useParams(); + const [showSpinner, setShowSpinner] = useState(true); + const [room, setRoom] = useState>(); + + useEffect(() => { + if (roomId) { + getInterviewRoom(roomId) + .then((data) => { + setRoom(data.data); + setShowSpinner(false); + }) + .catch((data) => { + if (data.request.status === STATUS.HTTP_404_NOT_FOUND) { + navigate( + reverse({ + pattern: ROUTES.frontend.admin_recruitment_room_overview, + urlParams: { recruitmentId: recruitmentId }, + }), + { replace: true }, + ); + } + toast.error(t(KEY.common_something_went_wrong)); + }); + } else { + setShowSpinner(false); + } + }, [roomId, recruitmentId, navigate, t]); + + const initialData: Partial = { + name: room?.name, + location: room?.location, + start_time: room?.start_time, + end_time: room?.end_time, + }; + + const submitText = roomId ? t(KEY.common_save) : t(KEY.common_create); + + if (showSpinner) { + return ( +
+ +
+ ); + } + + function handleOnSubmit(data: InterviewRoomDto) { + const updatedRoom = { + ...data, + recruitment: recruitmentId, + }; + + if (roomId) { + putInterviewRoom(roomId, updatedRoom) + .then(() => { + toast.success(t(KEY.common_update_successful)); + navigate( + reverse({ + pattern: ROUTES.frontend.admin_recruitment_room_overview, + urlParams: { recruitmentId: recruitmentId }, + }), + ); + }) + .catch((error) => { + toast.error(t(KEY.common_something_went_wrong)); + console.error(error); + }); + } else { + postInterviewRoom(updatedRoom) + .then(() => { + navigate( + reverse({ + pattern: ROUTES.frontend.admin_recruitment_room_overview, + urlParams: { recruitmentId: recruitmentId }, + }), + ); + toast.success(t(KEY.common_creation_successful)); + }) + .catch((error) => { + toast.error(t(KEY.common_something_went_wrong)); + console.error(error); + }); + } + } + + return ( + +
+ onSubmit={handleOnSubmit} initialData={initialData} submitText={submitText}> +
+ field="name" type="text" label={t(KEY.common_name)} required={true} /> +
+
+ + field="location" + type="text" + label={t(KEY.recruitment_interview_location)} + required={true} + /> +
+
+ + field="start_time" + type="date_time" + label={t(KEY.start_time)} + required={true} + /> +
+
+ + field="end_time" + type="date_time" + label={t(KEY.end_time)} + required={true} + /> +
+ +
+
+ ); +} diff --git a/frontend/src/PagesAdmin/RoomAdminPage/CreateInterviewRoomPage/index.ts b/frontend/src/PagesAdmin/RoomAdminPage/CreateInterviewRoomPage/index.ts new file mode 100644 index 000000000..884880e21 --- /dev/null +++ b/frontend/src/PagesAdmin/RoomAdminPage/CreateInterviewRoomPage/index.ts @@ -0,0 +1 @@ +export { CreateInterviewRoomPage } from './CreateInterviewRoomPage'; diff --git a/frontend/src/PagesAdmin/RoomAdminPage/RoomAdminPage.tsx b/frontend/src/PagesAdmin/RoomAdminPage/RoomAdminPage.tsx new file mode 100644 index 000000000..1618e6bc6 --- /dev/null +++ b/frontend/src/PagesAdmin/RoomAdminPage/RoomAdminPage.tsx @@ -0,0 +1,86 @@ +import { useEffect, useState } from 'react'; +import { useTranslation } from 'react-i18next'; +import { useRouteLoaderData } from 'react-router-dom'; +import { toast } from 'react-toastify'; +import { Button, CrudButtons, Table } from '~/Components'; +import { deleteInterviewRoom, getInterviewRoomsForRecruitment } from '~/api'; +import type { InterviewRoomDto } from '~/dto'; +import { useCustomNavigate } from '~/hooks'; +import { KEY } from '~/i18n/constants'; +import { reverse } from '~/named-urls'; +import type { RecruitmentLoader } from '~/router/loaders'; +import { ROUTES } from '~/routes'; + +export function RoomAdminPage() { + const [interviewRooms, setInterviewRooms] = useState(); + const data = useRouteLoaderData('recruitment') as RecruitmentLoader | undefined; + const navigate = useCustomNavigate(); + const { t } = useTranslation(); + + useEffect(() => { + if (data?.recruitment?.id) { + getInterviewRoomsForRecruitment(data.recruitment.id.toString()).then((response) => + setInterviewRooms(response.data), + ); + } + }, [data?.recruitment?.id]); + + if (!interviewRooms) { + return

No rooms found

; + } + + const columns = [ + { content: 'Room Name', sortable: true }, + { content: 'Location', sortable: true }, + { content: 'Start Time', sortable: true }, + { content: 'End Time', sortable: true }, + { content: 'Recruitment', sortable: true }, + { content: 'Gang', sortable: true }, + { content: 'Actions', sortable: false }, + ]; + + const tableData = interviewRooms.map((room) => [ + room.name, + room.location, + new Date(room.start_time), + new Date(room.end_time), + room.recruitment, + room.gang !== undefined ? room.gang : 'N/A', + { + content: ( + + navigate({ + url: reverse({ + pattern: ROUTES.frontend.admin_recruitment_room_edit, + urlParams: { recruitmentId: data?.recruitment?.id, roomId: room.id.toString() }, + }), + }) + } + onDelete={() => { + deleteInterviewRoom(room.id.toString()).then(() => { + toast.success('Interview room deleted'); + setInterviewRooms(interviewRooms.filter((r) => r.id !== room.id)); + }); + }} + /> + ), + }, + ]); + + return ( + <> + +
+ + ); +} diff --git a/frontend/src/PagesAdmin/RoomAdminPage/index.ts b/frontend/src/PagesAdmin/RoomAdminPage/index.ts new file mode 100644 index 000000000..6ee984d91 --- /dev/null +++ b/frontend/src/PagesAdmin/RoomAdminPage/index.ts @@ -0,0 +1,2 @@ +export { CreateInterviewRoomPage } from './CreateInterviewRoomPage'; +export { RoomAdminPage } from './RoomAdminPage'; diff --git a/frontend/src/PagesAdmin/index.ts b/frontend/src/PagesAdmin/index.ts index 2994f6282..8d55ecfe4 100644 --- a/frontend/src/PagesAdmin/index.ts +++ b/frontend/src/PagesAdmin/index.ts @@ -22,14 +22,16 @@ export { RecruitmentGangOverviewPage } from './RecruitmentGangOverviewPage'; export { RecruitmentOverviewPage } from './RecruitmentOverviewPage'; export { RecruitmentPositionFormAdminPage } from './RecruitmentPositionFormAdminPage'; export { RecruitmentPositionOverviewPage } from './RecruitmentPositionOverviewPage'; +export { RecruitmentRecruiterDashboardPage } from './RecruitmentRecruiterDashboardPage'; +export { RecruitmentSeparatePositionFormAdminPage } from './RecruitmentSeparatePositionFormAdminPage'; export { RecruitmentUnprocessedApplicantsPage } from './RecruitmentUnprocessedApplicantsPage'; export { RecruitmentUsersWithoutInterviewGangPage } from './RecruitmentUsersWithoutInterviewGangPage'; export { RecruitmentUsersWithoutThreeInterviewCriteriaPage } from './RecruitmentUsersWithoutThreeInterviewCriteriaPage'; export { RolesAdminPage } from './RolesAdminPage'; +export { CreateInterviewRoomPage, RoomAdminPage } from './RoomAdminPage'; export { SaksdokumentAdminPage } from './SaksdokumentAdminPage'; export { SaksdokumentFormAdminPage } from './SaksdokumentFormAdminPage'; export { SultenMenuAdminPage } from './SultenMenuAdminPage'; export { SultenMenuItemFormAdminPage } from './SultenMenuItemFormAdminPage'; export { SultenReservationAdminPage } from './SultenReservationAdminPage'; export { UsersAdminPage } from './UsersAdminPage'; -export { RecruitmentRecruiterDashboardPage } from './RecruitmentRecruiterDashboardPage'; diff --git a/frontend/src/api.ts b/frontend/src/api.ts index 0723bf81e..340334443 100644 --- a/frontend/src/api.ts +++ b/frontend/src/api.ts @@ -13,6 +13,7 @@ import type { ImagePostDto, InformationPageDto, InterviewDto, + InterviewRoomDto, KeyValueDto, MenuDto, MenuItemDto, @@ -27,6 +28,7 @@ import type { RecruitmentDto, RecruitmentGangDto, RecruitmentPositionDto, + RecruitmentSeparatePositionDto, RecruitmentStatsDto, RecruitmentUserDto, RegistrationDto, @@ -624,6 +626,49 @@ export async function putRecruitmentPosition( return response; } +export async function getRecruitmentSeparatePosition( + separatePositionId: string, +): Promise> { + const url = + BACKEND_DOMAIN + + reverse({ + pattern: ROUTES.backend.samfundet__recruitment_separateposition_detail, + urlParams: { pk: separatePositionId }, + }); + return await axios.get(url, { withCredentials: true }); +} + +export async function postRecruitmentSeparatePosition( + recruitmentSeparatePosition: RecruitmentSeparatePositionDto, +): Promise { + const url = BACKEND_DOMAIN + ROUTES.backend.samfundet__recruitment_separateposition_list; + + return await axios.post(url, recruitmentSeparatePosition, { withCredentials: true }); +} + +export async function putRecruitmentSeparatePosition( + separatePositionId: string, + recruitmentSeparatePosition: Partial, +): Promise { + const url = + BACKEND_DOMAIN + + reverse({ + pattern: ROUTES.backend.samfundet__recruitment_separateposition_detail, + urlParams: { pk: separatePositionId }, + }); + return await axios.put(url, recruitmentSeparatePosition, { withCredentials: true }); +} + +export async function deleteRecruitmentSeparatePosition(separatePositionId: string): Promise { + const url = + BACKEND_DOMAIN + + reverse({ + pattern: ROUTES.backend.samfundet__recruitment_separateposition_detail, + urlParams: { pk: separatePositionId }, + }); + return await axios.delete(url, { withCredentials: true }); +} + export async function getRecruitmentRecruiterDashboard(recruitmentId: string): Promise { const url = BACKEND_DOMAIN + @@ -893,6 +938,46 @@ export async function putRecruitmentApplicationInterview( const response = await axios.put(url, interview, { withCredentials: true }); return response; } + +// ############################################################ +// Interview rooms +// ############################################################ + +export async function getInterviewRoomsForRecruitment( + recruitmentId: string, +): Promise> { + const url = + BACKEND_DOMAIN + + reverse({ + pattern: ROUTES.backend.samfundet__interview_rooms_list, + queryParams: { recruitment: recruitmentId }, + }); + return await axios.get(url, { withCredentials: true }); +} + +export async function getInterviewRoom(id: string): Promise> { + const url = + BACKEND_DOMAIN + reverse({ pattern: ROUTES.backend.samfundet__interview_rooms_detail, urlParams: { pk: id } }); + return await axios.get(url, { withCredentials: true }); +} + +export async function postInterviewRoom(data: Partial): Promise { + const url = BACKEND_DOMAIN + ROUTES.backend.samfundet__interview_rooms_list; + return await axios.post(url, data, { withCredentials: true }); +} + +export async function putInterviewRoom(id: string, data: Partial): Promise { + const url = + BACKEND_DOMAIN + reverse({ pattern: ROUTES.backend.samfundet__interview_rooms_detail, urlParams: { pk: id } }); + return await axios.put(url, data, { withCredentials: true }); +} + +export async function deleteInterviewRoom(id: string): Promise { + const url = + BACKEND_DOMAIN + reverse({ pattern: ROUTES.backend.samfundet__interview_rooms_detail, urlParams: { pk: id } }); + return await axios.delete(url, { withCredentials: true }); +} + // ############################################################ // Purchase Feedback // ############################################################ diff --git a/frontend/src/dto.ts b/frontend/src/dto.ts index aabd01caa..bd7d561b2 100644 --- a/frontend/src/dto.ts +++ b/frontend/src/dto.ts @@ -412,9 +412,13 @@ export type RecruitmentDto = { }; export type RecruitmentSeparatePositionDto = { + id?: number; name_nb: string; name_en: string; + description_nb: string; + description_en: string; url: string; + recruitment?: string; }; export type UserPriorityDto = { @@ -422,7 +426,7 @@ export type UserPriorityDto = { }; export type RecruitmentPositionDto = { - id: string; + id: number; name_nb: string; name_en: string; @@ -506,6 +510,16 @@ export type RecruitmentDateStatDto = { export type RecruitmentCampusStatDto = { campus: string; count: number; + applicant_percentage: number; +}; + +export type RecruitmentGangStatDto = { + gang: string; + application_count: number; + applicant_count: number; + average_priority: number; + total_accepted: number; + total_rejected: number; }; export type RecruitmentStatsDto = { @@ -513,11 +527,26 @@ export type RecruitmentStatsDto = { recruitment?: number; total_applicants: number; total_applications: number; + total_withdrawn: number; + total_accepted: number; + average_gangs_applied_to_per_applicant: number; + average_applications_per_applicant: number; time_stats: RecruitmentTimeStatDto[]; date_stats: RecruitmentDateStatDto[]; + gang_stats: RecruitmentGangDto[]; campus_stats: RecruitmentCampusStatDto[]; }; +export type InterviewRoomDto = { + id: number; + name: string; + location: string; + start_time: string; + end_time: string; + recruitment: string; + gang?: number; +}; + // ############################################################ // Purchase Feedback // ############################################################ diff --git a/frontend/src/i18n/constants.ts b/frontend/src/i18n/constants.ts index 7cca42f5e..0a6d276ba 100644 --- a/frontend/src/i18n/constants.ts +++ b/frontend/src/i18n/constants.ts @@ -56,6 +56,7 @@ export const KEY = { common_tomorrow: 'common_tomorrow', // No category: + common_url: 'common_url', common_manage: 'common_manage', common_yes: 'common_yes', common_no: 'common_no', @@ -176,7 +177,7 @@ export const KEY = { common_come_back_later: 'common_come_back_later', common_be: 'common_be', common_filter: 'common_filter', - + common_administrate: 'common_administrate', //About page common_age_limit: 'common_age_limit', common_rent_services: 'common_rent_services', @@ -295,6 +296,7 @@ export const KEY = { recruitment_all_applications: 'recruitment_all_applications', recruitment_not_applied: 'recruitment_not_applied', recruitment_will_be_anonymized: 'recruitment_will_be_anonymized', + recruitment_create_room: 'recruitment_create_room', shown_application_deadline: 'shown_application_deadline', actual_application_deadline: 'actual_application_deadline', recruitment_number_of_applications: 'recruitment_number_of_applications', diff --git a/frontend/src/i18n/translations.ts b/frontend/src/i18n/translations.ts index ab8940c7f..c7d3616f5 100644 --- a/frontend/src/i18n/translations.ts +++ b/frontend/src/i18n/translations.ts @@ -43,6 +43,7 @@ export const nb = prepareTranslations({ [KEY.common_tomorrow]: 'I morgen', // Other common + [KEY.common_url]: 'URL', [KEY.common_manage]: 'Håndter', [KEY.common_yes]: 'Ja', [KEY.common_no]: 'Nei', @@ -162,7 +163,7 @@ export const nb = prepareTranslations({ [KEY.common_come_back_later]: 'Kom tilbake senere', [KEY.common_be]: 'bli', [KEY.common_filter]: 'Filter', - + [KEY.common_administrate]: 'Administrer', //About page [KEY.common_rent_services]: 'leie og tjenester', [KEY.common_press]: 'presse', @@ -359,6 +360,7 @@ export const nb = prepareTranslations({ [KEY.admin_information_confirm_delete]: 'Er du sikker du vil slette denne informasjonssiden?', [KEY.admin_information_confirm_cancel]: 'Er du sikker på at du vil gå tilbake uten å lagre?', [KEY.admin_gangsadminpage_abbreviation]: 'Forkortelse', + [KEY.recruitment_create_room]: 'Opprett rom', // CommandMenu: [KEY.command_menu_label]: 'Global kommando meny', @@ -467,6 +469,7 @@ export const en = prepareTranslations({ [KEY.common_tomorrow]: 'Tomorrow', // No category: + [KEY.common_url]: 'URL', [KEY.common_manage]: 'Manage', [KEY.common_yes]: 'Yes', [KEY.common_no]: 'No', @@ -604,7 +607,7 @@ export const en = prepareTranslations({ [KEY.common_contact_information]: 'Contact information', [KEY.common_about_samfundet]: 'About Samfundet', [KEY.common_processed]: 'Processed', - + [KEY.common_administrate]: 'Administrate', // Price groups: [KEY.common_ticket_type]: 'Ticket', [KEY.common_ticket_type_free]: 'Free', @@ -761,6 +764,8 @@ export const en = prepareTranslations({ [KEY.error_recruitment_form_4]: 'Group reprioritization deadline cannot be before the reprioritization deadline', [KEY.recruitment_dashboard_description]: 'Here you have an overview of your job as a recruiter for the recruitment, here you can see your upcomming interviews, the positions you have a responsibility for, and setting the time you are available to host an interview', + [KEY.recruitment_create_room]: 'Create room', + // Admin: [KEY.admin_organizer]: 'Organizer', [KEY.admin_saksdokument]: 'Case document', diff --git a/frontend/src/router/loaders.ts b/frontend/src/router/loaders.ts index dea06cd04..bf861bb5f 100644 --- a/frontend/src/router/loaders.ts +++ b/frontend/src/router/loaders.ts @@ -1,6 +1,6 @@ import type { LoaderFunctionArgs } from 'react-router-dom'; -import { getGang, getRecruitment, getRecruitmentPosition } from '~/api'; -import type { GangDto, RecruitmentDto, RecruitmentPositionDto } from '~/dto'; +import { getGang, getRecruitment, getRecruitmentPosition, getRecruitmentSeparatePosition } from '~/api'; +import type { GangDto, RecruitmentDto, RecruitmentPositionDto, RecruitmentSeparatePositionDto } from '~/dto'; export type RecruitmentLoader = { recruitment: RecruitmentDto | undefined; @@ -14,10 +14,18 @@ export type PositionLoader = { position: RecruitmentPositionDto | undefined; }; +export type SeparatePositionLoader = { + separatePosition: RecruitmentSeparatePositionDto | undefined; +}; + export async function recruitmentLoader({ params }: LoaderFunctionArgs): Promise { return { recruitment: (await getRecruitment(params.recruitmentId as string)).data }; } +export async function separatePositionLoader({ params }: LoaderFunctionArgs): Promise { + return { separatePosition: (await getRecruitmentSeparatePosition(params.separatePositionId as string)).data }; +} + export async function positionLoader({ params }: LoaderFunctionArgs): Promise { return { position: (await getRecruitmentPosition(params.positionId as string)).data }; } @@ -35,3 +43,9 @@ export async function recruitmentGangPositionLoader( ): Promise { return { ...(await recruitmentLoader(params)), ...(await gangLoader(params)), ...(await positionLoader(params)) }; } + +export async function recruitmentSeparatePositionLoader( + params: LoaderFunctionArgs, +): Promise { + return { ...(await recruitmentLoader(params)), ...(await separatePositionLoader(params)) }; +} diff --git a/frontend/src/router/router.tsx b/frontend/src/router/router.tsx index 6ad6476e4..8352bf12a 100644 --- a/frontend/src/router/router.tsx +++ b/frontend/src/router/router.tsx @@ -34,6 +34,7 @@ import { AdminLayout, ClosedPeriodAdminPage, ClosedPeriodFormAdminPage, + CreateInterviewRoomPage, EventCreatorAdminPage, EventsAdminPage, GangsAdminPage, @@ -54,10 +55,12 @@ import { RecruitmentOverviewPage, RecruitmentPositionFormAdminPage, RecruitmentPositionOverviewPage, + RecruitmentSeparatePositionFormAdminPage, RecruitmentUnprocessedApplicantsPage, RecruitmentUsersWithoutInterviewGangPage, RecruitmentUsersWithoutThreeInterviewCriteriaPage, RolesAdminPage, + RoomAdminPage, SaksdokumentAdminPage, SaksdokumentFormAdminPage, SultenMenuAdminPage, @@ -77,9 +80,11 @@ import { type GangLoader, type PositionLoader, type RecruitmentLoader, + type SeparatePositionLoader, recruitmentGangLoader, recruitmentGangPositionLoader, recruitmentLoader, + separatePositionLoader, } from '~/router/loaders'; import { dbT, lowerCapitalize } from '~/utils'; @@ -424,6 +429,57 @@ export const router = createBrowserRouter( }, }} > + } + /> + } + loader={recruitmentLoader} + handle={{ + crumb: ({ recruitment }: RecruitmentLoader) => { + if (!recruitment) return {t(KEY.common_unknown)}; + return ( + + {t(KEY.common_create)} {t(KEY.recruitment_gangs_with_separate_positions)} + + ); + }, + }} + /> + } + /> + } + loader={separatePositionLoader} + handle={{ + crumb: ({ recruitment, separatePosition }: RecruitmentLoader & SeparatePositionLoader) => { + if (!recruitment || !separatePosition) return {t(KEY.common_unknown)}; + return ( + + {t(KEY.common_edit)} {t(KEY.recruitment_gangs_with_separate_positions)} -{' '} + {dbT(separatePosition, 'name')} + + ); + }, + }} + /> } @@ -444,6 +500,7 @@ export const router = createBrowserRouter( }, }} /> + } @@ -464,6 +521,9 @@ export const router = createBrowserRouter( }, }} /> + } /> + } /> + } /> } />} diff --git a/frontend/src/routes/backend.ts b/frontend/src/routes/backend.ts index b1b8171d9..91b057ef9 100644 --- a/frontend/src/routes/backend.ts +++ b/frontend/src/routes/backend.ts @@ -68,7 +68,8 @@ export const ROUTES_BACKEND = { adminsamfundetusergangsectionrole__objectId: '/admin/samfundet/usergangsectionrole/:objectId/', admin__auth_permission_permissions: '/admin/auth/permission/:objectPk/permissions/', admin__auth_permission_permissions_manage_user: '/admin/auth/permission/:objectPk/permissions/user-manage/:userId/', - admin__auth_permission_permissions_manage_group: '/admin/auth/permission/:objectPk/permissions/group-manage/:groupId/', + admin__auth_permission_permissions_manage_group: + '/admin/auth/permission/:objectPk/permissions/group-manage/:groupId/', admin__auth_permission_changelist: '/admin/auth/permission/', admin__auth_permission_add: '/admin/auth/permission/add/', admin__auth_permission_history: '/admin/auth/permission/:objectId/history/', @@ -76,8 +77,10 @@ export const ROUTES_BACKEND = { admin__auth_permission_change: '/admin/auth/permission/:objectId/change/', adminauthpermission__objectId: '/admin/auth/permission/:objectId/', admin__contenttypes_contenttype_permissions: '/admin/contenttypes/contenttype/:objectPk/permissions/', - admin__contenttypes_contenttype_permissions_manage_user: '/admin/contenttypes/contenttype/:objectPk/permissions/user-manage/:userId/', - admin__contenttypes_contenttype_permissions_manage_group: '/admin/contenttypes/contenttype/:objectPk/permissions/group-manage/:groupId/', + admin__contenttypes_contenttype_permissions_manage_user: + '/admin/contenttypes/contenttype/:objectPk/permissions/user-manage/:userId/', + admin__contenttypes_contenttype_permissions_manage_group: + '/admin/contenttypes/contenttype/:objectPk/permissions/group-manage/:groupId/', admin__contenttypes_contenttype_changelist: '/admin/contenttypes/contenttype/', admin__contenttypes_contenttype_add: '/admin/contenttypes/contenttype/add/', admin__contenttypes_contenttype_history: '/admin/contenttypes/contenttype/:objectId/history/', @@ -95,7 +98,8 @@ export const ROUTES_BACKEND = { adminadminlogentry__objectId: '/admin/admin/logentry/:objectId/', admin__sessions_session_permissions: '/admin/sessions/session/:objectPk/permissions/', admin__sessions_session_permissions_manage_user: '/admin/sessions/session/:objectPk/permissions/user-manage/:userId/', - admin__sessions_session_permissions_manage_group: '/admin/sessions/session/:objectPk/permissions/group-manage/:groupId/', + admin__sessions_session_permissions_manage_group: + '/admin/sessions/session/:objectPk/permissions/group-manage/:groupId/', admin__sessions_session_changelist: '/admin/sessions/session/', admin__sessions_session_add: '/admin/sessions/session/add/', admin__sessions_session_history: '/admin/sessions/session/:objectId/history/', @@ -103,8 +107,10 @@ export const ROUTES_BACKEND = { admin__sessions_session_change: '/admin/sessions/session/:objectId/change/', adminsessionssession__objectId: '/admin/sessions/session/:objectId/', admin__guardian_groupobjectpermission_permissions: '/admin/guardian/groupobjectpermission/:objectPk/permissions/', - admin__guardian_groupobjectpermission_permissions_manage_user: '/admin/guardian/groupobjectpermission/:objectPk/permissions/user-manage/:userId/', - admin__guardian_groupobjectpermission_permissions_manage_group: '/admin/guardian/groupobjectpermission/:objectPk/permissions/group-manage/:groupId/', + admin__guardian_groupobjectpermission_permissions_manage_user: + '/admin/guardian/groupobjectpermission/:objectPk/permissions/user-manage/:userId/', + admin__guardian_groupobjectpermission_permissions_manage_group: + '/admin/guardian/groupobjectpermission/:objectPk/permissions/group-manage/:groupId/', admin__guardian_groupobjectpermission_changelist: '/admin/guardian/groupobjectpermission/', admin__guardian_groupobjectpermission_add: '/admin/guardian/groupobjectpermission/add/', admin__guardian_groupobjectpermission_history: '/admin/guardian/groupobjectpermission/:objectId/history/', @@ -112,8 +118,10 @@ export const ROUTES_BACKEND = { admin__guardian_groupobjectpermission_change: '/admin/guardian/groupobjectpermission/:objectId/change/', adminguardiangroupobjectpermission__objectId: '/admin/guardian/groupobjectpermission/:objectId/', admin__guardian_userobjectpermission_permissions: '/admin/guardian/userobjectpermission/:objectPk/permissions/', - admin__guardian_userobjectpermission_permissions_manage_user: '/admin/guardian/userobjectpermission/:objectPk/permissions/user-manage/:userId/', - admin__guardian_userobjectpermission_permissions_manage_group: '/admin/guardian/userobjectpermission/:objectPk/permissions/group-manage/:groupId/', + admin__guardian_userobjectpermission_permissions_manage_user: + '/admin/guardian/userobjectpermission/:objectPk/permissions/user-manage/:userId/', + admin__guardian_userobjectpermission_permissions_manage_group: + '/admin/guardian/userobjectpermission/:objectPk/permissions/group-manage/:groupId/', admin__guardian_userobjectpermission_changelist: '/admin/guardian/userobjectpermission/', admin__guardian_userobjectpermission_add: '/admin/guardian/userobjectpermission/add/', admin__guardian_userobjectpermission_history: '/admin/guardian/userobjectpermission/:objectId/history/', @@ -122,7 +130,8 @@ export const ROUTES_BACKEND = { adminguardianuserobjectpermission__objectId: '/admin/guardian/userobjectpermission/:objectId/', admin__samfundet_campus_permissions: '/admin/samfundet/campus/:objectPk/permissions/', admin__samfundet_campus_permissions_manage_user: '/admin/samfundet/campus/:objectPk/permissions/user-manage/:userId/', - admin__samfundet_campus_permissions_manage_group: '/admin/samfundet/campus/:objectPk/permissions/group-manage/:groupId/', + admin__samfundet_campus_permissions_manage_group: + '/admin/samfundet/campus/:objectPk/permissions/group-manage/:groupId/', admin__samfundet_campus_changelist: '/admin/samfundet/campus/', admin__samfundet_campus_add: '/admin/samfundet/campus/add/', admin__samfundet_campus_history: '/admin/samfundet/campus/:objectId/history/', @@ -130,8 +139,10 @@ export const ROUTES_BACKEND = { admin__samfundet_campus_change: '/admin/samfundet/campus/:objectId/change/', adminsamfundetcampus__objectId: '/admin/samfundet/campus/:objectId/', admin__samfundet_userpreference_permissions: '/admin/samfundet/userpreference/:objectPk/permissions/', - admin__samfundet_userpreference_permissions_manage_user: '/admin/samfundet/userpreference/:objectPk/permissions/user-manage/:userId/', - admin__samfundet_userpreference_permissions_manage_group: '/admin/samfundet/userpreference/:objectPk/permissions/group-manage/:groupId/', + admin__samfundet_userpreference_permissions_manage_user: + '/admin/samfundet/userpreference/:objectPk/permissions/user-manage/:userId/', + admin__samfundet_userpreference_permissions_manage_group: + '/admin/samfundet/userpreference/:objectPk/permissions/group-manage/:groupId/', admin__samfundet_userpreference_changelist: '/admin/samfundet/userpreference/', admin__samfundet_userpreference_add: '/admin/samfundet/userpreference/add/', admin__samfundet_userpreference_history: '/admin/samfundet/userpreference/:objectId/history/', @@ -139,8 +150,10 @@ export const ROUTES_BACKEND = { admin__samfundet_userpreference_change: '/admin/samfundet/userpreference/:objectId/change/', adminsamfundetuserpreference__objectId: '/admin/samfundet/userpreference/:objectId/', admin__samfundet_profile_permissions: '/admin/samfundet/profile/:objectPk/permissions/', - admin__samfundet_profile_permissions_manage_user: '/admin/samfundet/profile/:objectPk/permissions/user-manage/:userId/', - admin__samfundet_profile_permissions_manage_group: '/admin/samfundet/profile/:objectPk/permissions/group-manage/:groupId/', + admin__samfundet_profile_permissions_manage_user: + '/admin/samfundet/profile/:objectPk/permissions/user-manage/:userId/', + admin__samfundet_profile_permissions_manage_group: + '/admin/samfundet/profile/:objectPk/permissions/group-manage/:groupId/', admin__samfundet_profile_changelist: '/admin/samfundet/profile/', admin__samfundet_profile_add: '/admin/samfundet/profile/add/', admin__samfundet_profile_history: '/admin/samfundet/profile/:objectId/history/', @@ -148,8 +161,10 @@ export const ROUTES_BACKEND = { admin__samfundet_profile_change: '/admin/samfundet/profile/:objectId/change/', adminsamfundetprofile__objectId: '/admin/samfundet/profile/:objectId/', admin__samfundet_eventregistration_permissions: '/admin/samfundet/eventregistration/:objectPk/permissions/', - admin__samfundet_eventregistration_permissions_manage_user: '/admin/samfundet/eventregistration/:objectPk/permissions/user-manage/:userId/', - admin__samfundet_eventregistration_permissions_manage_group: '/admin/samfundet/eventregistration/:objectPk/permissions/group-manage/:groupId/', + admin__samfundet_eventregistration_permissions_manage_user: + '/admin/samfundet/eventregistration/:objectPk/permissions/user-manage/:userId/', + admin__samfundet_eventregistration_permissions_manage_group: + '/admin/samfundet/eventregistration/:objectPk/permissions/group-manage/:groupId/', admin__samfundet_eventregistration_changelist: '/admin/samfundet/eventregistration/', admin__samfundet_eventregistration_add: '/admin/samfundet/eventregistration/add/', admin__samfundet_eventregistration_history: '/admin/samfundet/eventregistration/:objectId/history/', @@ -158,7 +173,8 @@ export const ROUTES_BACKEND = { adminsamfundeteventregistration__objectId: '/admin/samfundet/eventregistration/:objectId/', admin__samfundet_event_permissions: '/admin/samfundet/event/:objectPk/permissions/', admin__samfundet_event_permissions_manage_user: '/admin/samfundet/event/:objectPk/permissions/user-manage/:userId/', - admin__samfundet_event_permissions_manage_group: '/admin/samfundet/event/:objectPk/permissions/group-manage/:groupId/', + admin__samfundet_event_permissions_manage_group: + '/admin/samfundet/event/:objectPk/permissions/group-manage/:groupId/', admin__samfundet_event_changelist: '/admin/samfundet/event/', admin__samfundet_event_add: '/admin/samfundet/event/add/', admin__samfundet_event_history: '/admin/samfundet/event/:objectId/history/', @@ -176,7 +192,8 @@ export const ROUTES_BACKEND = { adminsamfundettag__objectId: '/admin/samfundet/tag/:objectId/', admin__samfundet_image_permissions: '/admin/samfundet/image/:objectPk/permissions/', admin__samfundet_image_permissions_manage_user: '/admin/samfundet/image/:objectPk/permissions/user-manage/:userId/', - admin__samfundet_image_permissions_manage_group: '/admin/samfundet/image/:objectPk/permissions/group-manage/:groupId/', + admin__samfundet_image_permissions_manage_group: + '/admin/samfundet/image/:objectPk/permissions/group-manage/:groupId/', admin__samfundet_image_changelist: '/admin/samfundet/image/', admin__samfundet_image_add: '/admin/samfundet/image/add/', admin__samfundet_image_history: '/admin/samfundet/image/:objectId/history/', @@ -184,8 +201,10 @@ export const ROUTES_BACKEND = { admin__samfundet_image_change: '/admin/samfundet/image/:objectId/change/', adminsamfundetimage__objectId: '/admin/samfundet/image/:objectId/', admin__samfundet_eventgroup_permissions: '/admin/samfundet/eventgroup/:objectPk/permissions/', - admin__samfundet_eventgroup_permissions_manage_user: '/admin/samfundet/eventgroup/:objectPk/permissions/user-manage/:userId/', - admin__samfundet_eventgroup_permissions_manage_group: '/admin/samfundet/eventgroup/:objectPk/permissions/group-manage/:groupId/', + admin__samfundet_eventgroup_permissions_manage_user: + '/admin/samfundet/eventgroup/:objectPk/permissions/user-manage/:userId/', + admin__samfundet_eventgroup_permissions_manage_group: + '/admin/samfundet/eventgroup/:objectPk/permissions/group-manage/:groupId/', admin__samfundet_eventgroup_changelist: '/admin/samfundet/eventgroup/', admin__samfundet_eventgroup_add: '/admin/samfundet/eventgroup/add/', admin__samfundet_eventgroup_history: '/admin/samfundet/eventgroup/:objectId/history/', @@ -194,7 +213,8 @@ export const ROUTES_BACKEND = { adminsamfundeteventgroup__objectId: '/admin/samfundet/eventgroup/:objectId/', admin__samfundet_venue_permissions: '/admin/samfundet/venue/:objectPk/permissions/', admin__samfundet_venue_permissions_manage_user: '/admin/samfundet/venue/:objectPk/permissions/user-manage/:userId/', - admin__samfundet_venue_permissions_manage_group: '/admin/samfundet/venue/:objectPk/permissions/group-manage/:groupId/', + admin__samfundet_venue_permissions_manage_group: + '/admin/samfundet/venue/:objectPk/permissions/group-manage/:groupId/', admin__samfundet_venue_changelist: '/admin/samfundet/venue/', admin__samfundet_venue_add: '/admin/samfundet/venue/add/', admin__samfundet_venue_history: '/admin/samfundet/venue/:objectId/history/', @@ -211,8 +231,10 @@ export const ROUTES_BACKEND = { admin__samfundet_gang_change: '/admin/samfundet/gang/:objectId/change/', adminsamfundetgang__objectId: '/admin/samfundet/gang/:objectId/', admin__samfundet_gangtype_permissions: '/admin/samfundet/gangtype/:objectPk/permissions/', - admin__samfundet_gangtype_permissions_manage_user: '/admin/samfundet/gangtype/:objectPk/permissions/user-manage/:userId/', - admin__samfundet_gangtype_permissions_manage_group: '/admin/samfundet/gangtype/:objectPk/permissions/group-manage/:groupId/', + admin__samfundet_gangtype_permissions_manage_user: + '/admin/samfundet/gangtype/:objectPk/permissions/user-manage/:userId/', + admin__samfundet_gangtype_permissions_manage_group: + '/admin/samfundet/gangtype/:objectPk/permissions/group-manage/:groupId/', admin__samfundet_gangtype_changelist: '/admin/samfundet/gangtype/', admin__samfundet_gangtype_add: '/admin/samfundet/gangtype/add/', admin__samfundet_gangtype_history: '/admin/samfundet/gangtype/:objectId/history/', @@ -220,8 +242,10 @@ export const ROUTES_BACKEND = { admin__samfundet_gangtype_change: '/admin/samfundet/gangtype/:objectId/change/', adminsamfundetgangtype__objectId: '/admin/samfundet/gangtype/:objectId/', admin__samfundet_gangsection_permissions: '/admin/samfundet/gangsection/:objectPk/permissions/', - admin__samfundet_gangsection_permissions_manage_user: '/admin/samfundet/gangsection/:objectPk/permissions/user-manage/:userId/', - admin__samfundet_gangsection_permissions_manage_group: '/admin/samfundet/gangsection/:objectPk/permissions/group-manage/:groupId/', + admin__samfundet_gangsection_permissions_manage_user: + '/admin/samfundet/gangsection/:objectPk/permissions/user-manage/:userId/', + admin__samfundet_gangsection_permissions_manage_group: + '/admin/samfundet/gangsection/:objectPk/permissions/group-manage/:groupId/', admin__samfundet_gangsection_changelist: '/admin/samfundet/gangsection/', admin__samfundet_gangsection_add: '/admin/samfundet/gangsection/add/', admin__samfundet_gangsection_history: '/admin/samfundet/gangsection/:objectId/history/', @@ -229,8 +253,10 @@ export const ROUTES_BACKEND = { admin__samfundet_gangsection_change: '/admin/samfundet/gangsection/:objectId/change/', adminsamfundetgangsection__objectId: '/admin/samfundet/gangsection/:objectId/', admin__samfundet_informationpage_permissions: '/admin/samfundet/informationpage/:objectPk/permissions/', - admin__samfundet_informationpage_permissions_manage_user: '/admin/samfundet/informationpage/:objectPk/permissions/user-manage/:userId/', - admin__samfundet_informationpage_permissions_manage_group: '/admin/samfundet/informationpage/:objectPk/permissions/group-manage/:groupId/', + admin__samfundet_informationpage_permissions_manage_user: + '/admin/samfundet/informationpage/:objectPk/permissions/user-manage/:userId/', + admin__samfundet_informationpage_permissions_manage_group: + '/admin/samfundet/informationpage/:objectPk/permissions/group-manage/:groupId/', admin__samfundet_informationpage_changelist: '/admin/samfundet/informationpage/', admin__samfundet_informationpage_add: '/admin/samfundet/informationpage/add/', admin__samfundet_informationpage_history: '/admin/samfundet/informationpage/:objectId/history/', @@ -238,8 +264,10 @@ export const ROUTES_BACKEND = { admin__samfundet_informationpage_change: '/admin/samfundet/informationpage/:objectId/change/', adminsamfundetinformationpage__objectId: '/admin/samfundet/informationpage/:objectId/', admin__samfundet_blogpost_permissions: '/admin/samfundet/blogpost/:objectPk/permissions/', - admin__samfundet_blogpost_permissions_manage_user: '/admin/samfundet/blogpost/:objectPk/permissions/user-manage/:userId/', - admin__samfundet_blogpost_permissions_manage_group: '/admin/samfundet/blogpost/:objectPk/permissions/group-manage/:groupId/', + admin__samfundet_blogpost_permissions_manage_user: + '/admin/samfundet/blogpost/:objectPk/permissions/user-manage/:userId/', + admin__samfundet_blogpost_permissions_manage_group: + '/admin/samfundet/blogpost/:objectPk/permissions/group-manage/:groupId/', admin__samfundet_blogpost_changelist: '/admin/samfundet/blogpost/', admin__samfundet_blogpost_add: '/admin/samfundet/blogpost/add/', admin__samfundet_blogpost_history: '/admin/samfundet/blogpost/:objectId/history/', @@ -248,7 +276,8 @@ export const ROUTES_BACKEND = { adminsamfundetblogpost__objectId: '/admin/samfundet/blogpost/:objectId/', admin__samfundet_table_permissions: '/admin/samfundet/table/:objectPk/permissions/', admin__samfundet_table_permissions_manage_user: '/admin/samfundet/table/:objectPk/permissions/user-manage/:userId/', - admin__samfundet_table_permissions_manage_group: '/admin/samfundet/table/:objectPk/permissions/group-manage/:groupId/', + admin__samfundet_table_permissions_manage_group: + '/admin/samfundet/table/:objectPk/permissions/group-manage/:groupId/', admin__samfundet_table_changelist: '/admin/samfundet/table/', admin__samfundet_table_add: '/admin/samfundet/table/add/', admin__samfundet_table_history: '/admin/samfundet/table/:objectId/history/', @@ -256,8 +285,10 @@ export const ROUTES_BACKEND = { admin__samfundet_table_change: '/admin/samfundet/table/:objectId/change/', adminsamfundettable__objectId: '/admin/samfundet/table/:objectId/', admin__samfundet_reservation_permissions: '/admin/samfundet/reservation/:objectPk/permissions/', - admin__samfundet_reservation_permissions_manage_user: '/admin/samfundet/reservation/:objectPk/permissions/user-manage/:userId/', - admin__samfundet_reservation_permissions_manage_group: '/admin/samfundet/reservation/:objectPk/permissions/group-manage/:groupId/', + admin__samfundet_reservation_permissions_manage_user: + '/admin/samfundet/reservation/:objectPk/permissions/user-manage/:userId/', + admin__samfundet_reservation_permissions_manage_group: + '/admin/samfundet/reservation/:objectPk/permissions/group-manage/:groupId/', admin__samfundet_reservation_changelist: '/admin/samfundet/reservation/', admin__samfundet_reservation_add: '/admin/samfundet/reservation/add/', admin__samfundet_reservation_history: '/admin/samfundet/reservation/:objectId/history/', @@ -274,8 +305,10 @@ export const ROUTES_BACKEND = { admin__samfundet_menu_change: '/admin/samfundet/menu/:objectId/change/', adminsamfundetmenu__objectId: '/admin/samfundet/menu/:objectId/', admin__samfundet_menuitem_permissions: '/admin/samfundet/menuitem/:objectPk/permissions/', - admin__samfundet_menuitem_permissions_manage_user: '/admin/samfundet/menuitem/:objectPk/permissions/user-manage/:userId/', - admin__samfundet_menuitem_permissions_manage_group: '/admin/samfundet/menuitem/:objectPk/permissions/group-manage/:groupId/', + admin__samfundet_menuitem_permissions_manage_user: + '/admin/samfundet/menuitem/:objectPk/permissions/user-manage/:userId/', + admin__samfundet_menuitem_permissions_manage_group: + '/admin/samfundet/menuitem/:objectPk/permissions/group-manage/:groupId/', admin__samfundet_menuitem_changelist: '/admin/samfundet/menuitem/', admin__samfundet_menuitem_add: '/admin/samfundet/menuitem/add/', admin__samfundet_menuitem_history: '/admin/samfundet/menuitem/:objectId/history/', @@ -283,8 +316,10 @@ export const ROUTES_BACKEND = { admin__samfundet_menuitem_change: '/admin/samfundet/menuitem/:objectId/change/', adminsamfundetmenuitem__objectId: '/admin/samfundet/menuitem/:objectId/', admin__samfundet_foodcategory_permissions: '/admin/samfundet/foodcategory/:objectPk/permissions/', - admin__samfundet_foodcategory_permissions_manage_user: '/admin/samfundet/foodcategory/:objectPk/permissions/user-manage/:userId/', - admin__samfundet_foodcategory_permissions_manage_group: '/admin/samfundet/foodcategory/:objectPk/permissions/group-manage/:groupId/', + admin__samfundet_foodcategory_permissions_manage_user: + '/admin/samfundet/foodcategory/:objectPk/permissions/user-manage/:userId/', + admin__samfundet_foodcategory_permissions_manage_group: + '/admin/samfundet/foodcategory/:objectPk/permissions/group-manage/:groupId/', admin__samfundet_foodcategory_changelist: '/admin/samfundet/foodcategory/', admin__samfundet_foodcategory_add: '/admin/samfundet/foodcategory/add/', admin__samfundet_foodcategory_history: '/admin/samfundet/foodcategory/:objectId/history/', @@ -292,8 +327,10 @@ export const ROUTES_BACKEND = { admin__samfundet_foodcategory_change: '/admin/samfundet/foodcategory/:objectId/change/', adminsamfundetfoodcategory__objectId: '/admin/samfundet/foodcategory/:objectId/', admin__samfundet_foodpreference_permissions: '/admin/samfundet/foodpreference/:objectPk/permissions/', - admin__samfundet_foodpreference_permissions_manage_user: '/admin/samfundet/foodpreference/:objectPk/permissions/user-manage/:userId/', - admin__samfundet_foodpreference_permissions_manage_group: '/admin/samfundet/foodpreference/:objectPk/permissions/group-manage/:groupId/', + admin__samfundet_foodpreference_permissions_manage_user: + '/admin/samfundet/foodpreference/:objectPk/permissions/user-manage/:userId/', + admin__samfundet_foodpreference_permissions_manage_group: + '/admin/samfundet/foodpreference/:objectPk/permissions/group-manage/:groupId/', admin__samfundet_foodpreference_changelist: '/admin/samfundet/foodpreference/', admin__samfundet_foodpreference_add: '/admin/samfundet/foodpreference/add/', admin__samfundet_foodpreference_history: '/admin/samfundet/foodpreference/:objectId/history/', @@ -301,8 +338,10 @@ export const ROUTES_BACKEND = { admin__samfundet_foodpreference_change: '/admin/samfundet/foodpreference/:objectId/change/', adminsamfundetfoodpreference__objectId: '/admin/samfundet/foodpreference/:objectId/', admin__samfundet_saksdokument_permissions: '/admin/samfundet/saksdokument/:objectPk/permissions/', - admin__samfundet_saksdokument_permissions_manage_user: '/admin/samfundet/saksdokument/:objectPk/permissions/user-manage/:userId/', - admin__samfundet_saksdokument_permissions_manage_group: '/admin/samfundet/saksdokument/:objectPk/permissions/group-manage/:groupId/', + admin__samfundet_saksdokument_permissions_manage_user: + '/admin/samfundet/saksdokument/:objectPk/permissions/user-manage/:userId/', + admin__samfundet_saksdokument_permissions_manage_group: + '/admin/samfundet/saksdokument/:objectPk/permissions/group-manage/:groupId/', admin__samfundet_saksdokument_changelist: '/admin/samfundet/saksdokument/', admin__samfundet_saksdokument_add: '/admin/samfundet/saksdokument/add/', admin__samfundet_saksdokument_history: '/admin/samfundet/saksdokument/:objectId/history/', @@ -310,8 +349,10 @@ export const ROUTES_BACKEND = { admin__samfundet_saksdokument_change: '/admin/samfundet/saksdokument/:objectId/change/', adminsamfundetsaksdokument__objectId: '/admin/samfundet/saksdokument/:objectId/', admin__samfundet_booking_permissions: '/admin/samfundet/booking/:objectPk/permissions/', - admin__samfundet_booking_permissions_manage_user: '/admin/samfundet/booking/:objectPk/permissions/user-manage/:userId/', - admin__samfundet_booking_permissions_manage_group: '/admin/samfundet/booking/:objectPk/permissions/group-manage/:groupId/', + admin__samfundet_booking_permissions_manage_user: + '/admin/samfundet/booking/:objectPk/permissions/user-manage/:userId/', + admin__samfundet_booking_permissions_manage_group: + '/admin/samfundet/booking/:objectPk/permissions/group-manage/:groupId/', admin__samfundet_booking_changelist: '/admin/samfundet/booking/', admin__samfundet_booking_add: '/admin/samfundet/booking/add/', admin__samfundet_booking_history: '/admin/samfundet/booking/:objectId/history/', @@ -319,8 +360,10 @@ export const ROUTES_BACKEND = { admin__samfundet_booking_change: '/admin/samfundet/booking/:objectId/change/', adminsamfundetbooking__objectId: '/admin/samfundet/booking/:objectId/', admin__samfundet_closedperiod_permissions: '/admin/samfundet/closedperiod/:objectPk/permissions/', - admin__samfundet_closedperiod_permissions_manage_user: '/admin/samfundet/closedperiod/:objectPk/permissions/user-manage/:userId/', - admin__samfundet_closedperiod_permissions_manage_group: '/admin/samfundet/closedperiod/:objectPk/permissions/group-manage/:groupId/', + admin__samfundet_closedperiod_permissions_manage_user: + '/admin/samfundet/closedperiod/:objectPk/permissions/user-manage/:userId/', + admin__samfundet_closedperiod_permissions_manage_group: + '/admin/samfundet/closedperiod/:objectPk/permissions/group-manage/:groupId/', admin__samfundet_closedperiod_changelist: '/admin/samfundet/closedperiod/', admin__samfundet_closedperiod_add: '/admin/samfundet/closedperiod/add/', admin__samfundet_closedperiod_history: '/admin/samfundet/closedperiod/:objectId/history/', @@ -328,8 +371,10 @@ export const ROUTES_BACKEND = { admin__samfundet_closedperiod_change: '/admin/samfundet/closedperiod/:objectId/change/', adminsamfundetclosedperiod__objectId: '/admin/samfundet/closedperiod/:objectId/', admin__samfundet_infobox_permissions: '/admin/samfundet/infobox/:objectPk/permissions/', - admin__samfundet_infobox_permissions_manage_user: '/admin/samfundet/infobox/:objectPk/permissions/user-manage/:userId/', - admin__samfundet_infobox_permissions_manage_group: '/admin/samfundet/infobox/:objectPk/permissions/group-manage/:groupId/', + admin__samfundet_infobox_permissions_manage_user: + '/admin/samfundet/infobox/:objectPk/permissions/user-manage/:userId/', + admin__samfundet_infobox_permissions_manage_group: + '/admin/samfundet/infobox/:objectPk/permissions/group-manage/:groupId/', admin__samfundet_infobox_changelist: '/admin/samfundet/infobox/', admin__samfundet_infobox_add: '/admin/samfundet/infobox/add/', admin__samfundet_infobox_history: '/admin/samfundet/infobox/:objectId/history/', @@ -337,8 +382,10 @@ export const ROUTES_BACKEND = { admin__samfundet_infobox_change: '/admin/samfundet/infobox/:objectId/change/', adminsamfundetinfobox__objectId: '/admin/samfundet/infobox/:objectId/', admin__samfundet_textitem_permissions: '/admin/samfundet/textitem/:objectPk/permissions/', - admin__samfundet_textitem_permissions_manage_user: '/admin/samfundet/textitem/:objectPk/permissions/user-manage/:userId/', - admin__samfundet_textitem_permissions_manage_group: '/admin/samfundet/textitem/:objectPk/permissions/group-manage/:groupId/', + admin__samfundet_textitem_permissions_manage_user: + '/admin/samfundet/textitem/:objectPk/permissions/user-manage/:userId/', + admin__samfundet_textitem_permissions_manage_group: + '/admin/samfundet/textitem/:objectPk/permissions/group-manage/:groupId/', admin__samfundet_textitem_changelist: '/admin/samfundet/textitem/', admin__samfundet_textitem_add: '/admin/samfundet/textitem/add/', admin__samfundet_textitem_history: '/admin/samfundet/textitem/:objectId/history/', @@ -346,8 +393,10 @@ export const ROUTES_BACKEND = { admin__samfundet_textitem_change: '/admin/samfundet/textitem/:objectId/change/', adminsamfundettextitem__objectId: '/admin/samfundet/textitem/:objectId/', admin__samfundet_keyvalue_permissions: '/admin/samfundet/keyvalue/:objectPk/permissions/', - admin__samfundet_keyvalue_permissions_manage_user: '/admin/samfundet/keyvalue/:objectPk/permissions/user-manage/:userId/', - admin__samfundet_keyvalue_permissions_manage_group: '/admin/samfundet/keyvalue/:objectPk/permissions/group-manage/:groupId/', + admin__samfundet_keyvalue_permissions_manage_user: + '/admin/samfundet/keyvalue/:objectPk/permissions/user-manage/:userId/', + admin__samfundet_keyvalue_permissions_manage_group: + '/admin/samfundet/keyvalue/:objectPk/permissions/group-manage/:groupId/', admin__samfundet_keyvalue_changelist: '/admin/samfundet/keyvalue/', admin__samfundet_keyvalue_add: '/admin/samfundet/keyvalue/add/', admin__samfundet_keyvalue_history: '/admin/samfundet/keyvalue/:objectId/history/', @@ -355,26 +404,34 @@ export const ROUTES_BACKEND = { admin__samfundet_keyvalue_change: '/admin/samfundet/keyvalue/:objectId/change/', adminsamfundetkeyvalue__objectId: '/admin/samfundet/keyvalue/:objectId/', admin__samfundet_recruitment_permissions: '/admin/samfundet/recruitment/:objectPk/permissions/', - admin__samfundet_recruitment_permissions_manage_user: '/admin/samfundet/recruitment/:objectPk/permissions/user-manage/:userId/', - admin__samfundet_recruitment_permissions_manage_group: '/admin/samfundet/recruitment/:objectPk/permissions/group-manage/:groupId/', + admin__samfundet_recruitment_permissions_manage_user: + '/admin/samfundet/recruitment/:objectPk/permissions/user-manage/:userId/', + admin__samfundet_recruitment_permissions_manage_group: + '/admin/samfundet/recruitment/:objectPk/permissions/group-manage/:groupId/', admin__samfundet_recruitment_changelist: '/admin/samfundet/recruitment/', admin__samfundet_recruitment_add: '/admin/samfundet/recruitment/add/', admin__samfundet_recruitment_history: '/admin/samfundet/recruitment/:objectId/history/', admin__samfundet_recruitment_delete: '/admin/samfundet/recruitment/:objectId/delete/', admin__samfundet_recruitment_change: '/admin/samfundet/recruitment/:objectId/change/', adminsamfundetrecruitment__objectId: '/admin/samfundet/recruitment/:objectId/', - admin__samfundet_recruitmentseparateposition_permissions: '/admin/samfundet/recruitmentseparateposition/:objectPk/permissions/', - admin__samfundet_recruitmentseparateposition_permissions_manage_user: '/admin/samfundet/recruitmentseparateposition/:objectPk/permissions/user-manage/:userId/', - admin__samfundet_recruitmentseparateposition_permissions_manage_group: '/admin/samfundet/recruitmentseparateposition/:objectPk/permissions/group-manage/:groupId/', + admin__samfundet_recruitmentseparateposition_permissions: + '/admin/samfundet/recruitmentseparateposition/:objectPk/permissions/', + admin__samfundet_recruitmentseparateposition_permissions_manage_user: + '/admin/samfundet/recruitmentseparateposition/:objectPk/permissions/user-manage/:userId/', + admin__samfundet_recruitmentseparateposition_permissions_manage_group: + '/admin/samfundet/recruitmentseparateposition/:objectPk/permissions/group-manage/:groupId/', admin__samfundet_recruitmentseparateposition_changelist: '/admin/samfundet/recruitmentseparateposition/', admin__samfundet_recruitmentseparateposition_add: '/admin/samfundet/recruitmentseparateposition/add/', - admin__samfundet_recruitmentseparateposition_history: '/admin/samfundet/recruitmentseparateposition/:objectId/history/', + admin__samfundet_recruitmentseparateposition_history: + '/admin/samfundet/recruitmentseparateposition/:objectId/history/', admin__samfundet_recruitmentseparateposition_delete: '/admin/samfundet/recruitmentseparateposition/:objectId/delete/', admin__samfundet_recruitmentseparateposition_change: '/admin/samfundet/recruitmentseparateposition/:objectId/change/', adminsamfundetrecruitmentseparateposition__objectId: '/admin/samfundet/recruitmentseparateposition/:objectId/', admin__samfundet_recruitmentposition_permissions: '/admin/samfundet/recruitmentposition/:objectPk/permissions/', - admin__samfundet_recruitmentposition_permissions_manage_user: '/admin/samfundet/recruitmentposition/:objectPk/permissions/user-manage/:userId/', - admin__samfundet_recruitmentposition_permissions_manage_group: '/admin/samfundet/recruitmentposition/:objectPk/permissions/group-manage/:groupId/', + admin__samfundet_recruitmentposition_permissions_manage_user: + '/admin/samfundet/recruitmentposition/:objectPk/permissions/user-manage/:userId/', + admin__samfundet_recruitmentposition_permissions_manage_group: + '/admin/samfundet/recruitmentposition/:objectPk/permissions/group-manage/:groupId/', admin__samfundet_recruitmentposition_changelist: '/admin/samfundet/recruitmentposition/', admin__samfundet_recruitmentposition_add: '/admin/samfundet/recruitmentposition/add/', admin__samfundet_recruitmentposition_history: '/admin/samfundet/recruitmentposition/:objectId/history/', @@ -382,8 +439,10 @@ export const ROUTES_BACKEND = { admin__samfundet_recruitmentposition_change: '/admin/samfundet/recruitmentposition/:objectId/change/', adminsamfundetrecruitmentposition__objectId: '/admin/samfundet/recruitmentposition/:objectId/', admin__samfundet_recruitmentapplication_permissions: '/admin/samfundet/recruitmentapplication/:objectPk/permissions/', - admin__samfundet_recruitmentapplication_permissions_manage_user: '/admin/samfundet/recruitmentapplication/:objectPk/permissions/user-manage/:userId/', - admin__samfundet_recruitmentapplication_permissions_manage_group: '/admin/samfundet/recruitmentapplication/:objectPk/permissions/group-manage/:groupId/', + admin__samfundet_recruitmentapplication_permissions_manage_user: + '/admin/samfundet/recruitmentapplication/:objectPk/permissions/user-manage/:userId/', + admin__samfundet_recruitmentapplication_permissions_manage_group: + '/admin/samfundet/recruitmentapplication/:objectPk/permissions/group-manage/:groupId/', admin__samfundet_recruitmentapplication_changelist: '/admin/samfundet/recruitmentapplication/', admin__samfundet_recruitmentapplication_add: '/admin/samfundet/recruitmentapplication/add/', admin__samfundet_recruitmentapplication_history: '/admin/samfundet/recruitmentapplication/:objectId/history/', @@ -391,8 +450,10 @@ export const ROUTES_BACKEND = { admin__samfundet_recruitmentapplication_change: '/admin/samfundet/recruitmentapplication/:objectId/change/', adminsamfundetrecruitmentapplication__objectId: '/admin/samfundet/recruitmentapplication/:objectId/', admin__samfundet_organization_permissions: '/admin/samfundet/organization/:objectPk/permissions/', - admin__samfundet_organization_permissions_manage_user: '/admin/samfundet/organization/:objectPk/permissions/user-manage/:userId/', - admin__samfundet_organization_permissions_manage_group: '/admin/samfundet/organization/:objectPk/permissions/group-manage/:groupId/', + admin__samfundet_organization_permissions_manage_user: + '/admin/samfundet/organization/:objectPk/permissions/user-manage/:userId/', + admin__samfundet_organization_permissions_manage_group: + '/admin/samfundet/organization/:objectPk/permissions/group-manage/:groupId/', admin__samfundet_organization_changelist: '/admin/samfundet/organization/', admin__samfundet_organization_add: '/admin/samfundet/organization/add/', admin__samfundet_organization_history: '/admin/samfundet/organization/:objectId/history/', @@ -400,8 +461,10 @@ export const ROUTES_BACKEND = { admin__samfundet_organization_change: '/admin/samfundet/organization/:objectId/change/', adminsamfundetorganization__objectId: '/admin/samfundet/organization/:objectId/', admin__samfundet_interviewroom_permissions: '/admin/samfundet/interviewroom/:objectPk/permissions/', - admin__samfundet_interviewroom_permissions_manage_user: '/admin/samfundet/interviewroom/:objectPk/permissions/user-manage/:userId/', - admin__samfundet_interviewroom_permissions_manage_group: '/admin/samfundet/interviewroom/:objectPk/permissions/group-manage/:groupId/', + admin__samfundet_interviewroom_permissions_manage_user: + '/admin/samfundet/interviewroom/:objectPk/permissions/user-manage/:userId/', + admin__samfundet_interviewroom_permissions_manage_group: + '/admin/samfundet/interviewroom/:objectPk/permissions/group-manage/:groupId/', admin__samfundet_interviewroom_changelist: '/admin/samfundet/interviewroom/', admin__samfundet_interviewroom_add: '/admin/samfundet/interviewroom/add/', admin__samfundet_interviewroom_history: '/admin/samfundet/interviewroom/:objectId/history/', @@ -409,8 +472,10 @@ export const ROUTES_BACKEND = { admin__samfundet_interviewroom_change: '/admin/samfundet/interviewroom/:objectId/change/', adminsamfundetinterviewroom__objectId: '/admin/samfundet/interviewroom/:objectId/', admin__samfundet_interview_permissions: '/admin/samfundet/interview/:objectPk/permissions/', - admin__samfundet_interview_permissions_manage_user: '/admin/samfundet/interview/:objectPk/permissions/user-manage/:userId/', - admin__samfundet_interview_permissions_manage_group: '/admin/samfundet/interview/:objectPk/permissions/group-manage/:groupId/', + admin__samfundet_interview_permissions_manage_user: + '/admin/samfundet/interview/:objectPk/permissions/user-manage/:userId/', + admin__samfundet_interview_permissions_manage_group: + '/admin/samfundet/interview/:objectPk/permissions/group-manage/:groupId/', admin__samfundet_interview_changelist: '/admin/samfundet/interview/', admin__samfundet_interview_add: '/admin/samfundet/interview/add/', admin__samfundet_interview_history: '/admin/samfundet/interview/:objectId/history/', @@ -418,8 +483,10 @@ export const ROUTES_BACKEND = { admin__samfundet_interview_change: '/admin/samfundet/interview/:objectId/change/', adminsamfundetinterview__objectId: '/admin/samfundet/interview/:objectId/', admin__samfundet_recruitmentstatistics_permissions: '/admin/samfundet/recruitmentstatistics/:objectPk/permissions/', - admin__samfundet_recruitmentstatistics_permissions_manage_user: '/admin/samfundet/recruitmentstatistics/:objectPk/permissions/user-manage/:userId/', - admin__samfundet_recruitmentstatistics_permissions_manage_group: '/admin/samfundet/recruitmentstatistics/:objectPk/permissions/group-manage/:groupId/', + admin__samfundet_recruitmentstatistics_permissions_manage_user: + '/admin/samfundet/recruitmentstatistics/:objectPk/permissions/user-manage/:userId/', + admin__samfundet_recruitmentstatistics_permissions_manage_group: + '/admin/samfundet/recruitmentstatistics/:objectPk/permissions/group-manage/:groupId/', admin__samfundet_recruitmentstatistics_changelist: '/admin/samfundet/recruitmentstatistics/', admin__samfundet_recruitmentstatistics_add: '/admin/samfundet/recruitmentstatistics/add/', admin__samfundet_recruitmentstatistics_history: '/admin/samfundet/recruitmentstatistics/:objectId/history/', @@ -428,7 +495,8 @@ export const ROUTES_BACKEND = { adminsamfundetrecruitmentstatistics__objectId: '/admin/samfundet/recruitmentstatistics/:objectId/', admin__samfundet_merch_permissions: '/admin/samfundet/merch/:objectPk/permissions/', admin__samfundet_merch_permissions_manage_user: '/admin/samfundet/merch/:objectPk/permissions/user-manage/:userId/', - admin__samfundet_merch_permissions_manage_group: '/admin/samfundet/merch/:objectPk/permissions/group-manage/:groupId/', + admin__samfundet_merch_permissions_manage_group: + '/admin/samfundet/merch/:objectPk/permissions/group-manage/:groupId/', admin__samfundet_merch_changelist: '/admin/samfundet/merch/', admin__samfundet_merch_add: '/admin/samfundet/merch/add/', admin__samfundet_merch_history: '/admin/samfundet/merch/:objectId/history/', @@ -436,26 +504,37 @@ export const ROUTES_BACKEND = { admin__samfundet_merch_change: '/admin/samfundet/merch/:objectId/change/', adminsamfundetmerch__objectId: '/admin/samfundet/merch/:objectId/', admin__samfundet_merchvariation_permissions: '/admin/samfundet/merchvariation/:objectPk/permissions/', - admin__samfundet_merchvariation_permissions_manage_user: '/admin/samfundet/merchvariation/:objectPk/permissions/user-manage/:userId/', - admin__samfundet_merchvariation_permissions_manage_group: '/admin/samfundet/merchvariation/:objectPk/permissions/group-manage/:groupId/', + admin__samfundet_merchvariation_permissions_manage_user: + '/admin/samfundet/merchvariation/:objectPk/permissions/user-manage/:userId/', + admin__samfundet_merchvariation_permissions_manage_group: + '/admin/samfundet/merchvariation/:objectPk/permissions/group-manage/:groupId/', admin__samfundet_merchvariation_changelist: '/admin/samfundet/merchvariation/', admin__samfundet_merchvariation_add: '/admin/samfundet/merchvariation/add/', admin__samfundet_merchvariation_history: '/admin/samfundet/merchvariation/:objectId/history/', admin__samfundet_merchvariation_delete: '/admin/samfundet/merchvariation/:objectId/delete/', admin__samfundet_merchvariation_change: '/admin/samfundet/merchvariation/:objectId/change/', adminsamfundetmerchvariation__objectId: '/admin/samfundet/merchvariation/:objectId/', - admin__samfundet_recruitmentinterviewavailability_permissions: '/admin/samfundet/recruitmentinterviewavailability/:objectPk/permissions/', - admin__samfundet_recruitmentinterviewavailability_permissions_manage_user: '/admin/samfundet/recruitmentinterviewavailability/:objectPk/permissions/user-manage/:userId/', - admin__samfundet_recruitmentinterviewavailability_permissions_manage_group: '/admin/samfundet/recruitmentinterviewavailability/:objectPk/permissions/group-manage/:groupId/', + admin__samfundet_recruitmentinterviewavailability_permissions: + '/admin/samfundet/recruitmentinterviewavailability/:objectPk/permissions/', + admin__samfundet_recruitmentinterviewavailability_permissions_manage_user: + '/admin/samfundet/recruitmentinterviewavailability/:objectPk/permissions/user-manage/:userId/', + admin__samfundet_recruitmentinterviewavailability_permissions_manage_group: + '/admin/samfundet/recruitmentinterviewavailability/:objectPk/permissions/group-manage/:groupId/', admin__samfundet_recruitmentinterviewavailability_changelist: '/admin/samfundet/recruitmentinterviewavailability/', admin__samfundet_recruitmentinterviewavailability_add: '/admin/samfundet/recruitmentinterviewavailability/add/', - admin__samfundet_recruitmentinterviewavailability_history: '/admin/samfundet/recruitmentinterviewavailability/:objectId/history/', - admin__samfundet_recruitmentinterviewavailability_delete: '/admin/samfundet/recruitmentinterviewavailability/:objectId/delete/', - admin__samfundet_recruitmentinterviewavailability_change: '/admin/samfundet/recruitmentinterviewavailability/:objectId/change/', - adminsamfundetrecruitmentinterviewavailability__objectId: '/admin/samfundet/recruitmentinterviewavailability/:objectId/', + admin__samfundet_recruitmentinterviewavailability_history: + '/admin/samfundet/recruitmentinterviewavailability/:objectId/history/', + admin__samfundet_recruitmentinterviewavailability_delete: + '/admin/samfundet/recruitmentinterviewavailability/:objectId/delete/', + admin__samfundet_recruitmentinterviewavailability_change: + '/admin/samfundet/recruitmentinterviewavailability/:objectId/change/', + adminsamfundetrecruitmentinterviewavailability__objectId: + '/admin/samfundet/recruitmentinterviewavailability/:objectId/', admin__samfundet_userfeedbackmodel_permissions: '/admin/samfundet/userfeedbackmodel/:objectPk/permissions/', - admin__samfundet_userfeedbackmodel_permissions_manage_user: '/admin/samfundet/userfeedbackmodel/:objectPk/permissions/user-manage/:userId/', - admin__samfundet_userfeedbackmodel_permissions_manage_group: '/admin/samfundet/userfeedbackmodel/:objectPk/permissions/group-manage/:groupId/', + admin__samfundet_userfeedbackmodel_permissions_manage_user: + '/admin/samfundet/userfeedbackmodel/:objectPk/permissions/user-manage/:userId/', + admin__samfundet_userfeedbackmodel_permissions_manage_group: + '/admin/samfundet/userfeedbackmodel/:objectPk/permissions/group-manage/:groupId/', admin__samfundet_userfeedbackmodel_changelist: '/admin/samfundet/userfeedbackmodel/', admin__samfundet_userfeedbackmodel_add: '/admin/samfundet/userfeedbackmodel/add/', admin__samfundet_userfeedbackmodel_history: '/admin/samfundet/userfeedbackmodel/:objectId/history/', @@ -463,8 +542,10 @@ export const ROUTES_BACKEND = { admin__samfundet_userfeedbackmodel_change: '/admin/samfundet/userfeedbackmodel/:objectId/change/', adminsamfundetuserfeedbackmodel__objectId: '/admin/samfundet/userfeedbackmodel/:objectId/', admin__samfundet_purchasefeedbackmodel_permissions: '/admin/samfundet/purchasefeedbackmodel/:objectPk/permissions/', - admin__samfundet_purchasefeedbackmodel_permissions_manage_user: '/admin/samfundet/purchasefeedbackmodel/:objectPk/permissions/user-manage/:userId/', - admin__samfundet_purchasefeedbackmodel_permissions_manage_group: '/admin/samfundet/purchasefeedbackmodel/:objectPk/permissions/group-manage/:groupId/', + admin__samfundet_purchasefeedbackmodel_permissions_manage_user: + '/admin/samfundet/purchasefeedbackmodel/:objectPk/permissions/user-manage/:userId/', + admin__samfundet_purchasefeedbackmodel_permissions_manage_group: + '/admin/samfundet/purchasefeedbackmodel/:objectPk/permissions/group-manage/:groupId/', admin__samfundet_purchasefeedbackmodel_changelist: '/admin/samfundet/purchasefeedbackmodel/', admin__samfundet_purchasefeedbackmodel_add: '/admin/samfundet/purchasefeedbackmodel/add/', admin__samfundet_purchasefeedbackmodel_history: '/admin/samfundet/purchasefeedbackmodel/:objectId/history/', @@ -533,6 +614,8 @@ export const ROUTES_BACKEND = { samfundet__recruitment_for_recruiter_detail: '/api/recruitment-for-recruiter/:pk/', samfundet__recruitment_stats_list: '/api/recruitment-stats/', samfundet__recruitment_stats_detail: '/api/recruitment-stats/:pk/', + samfundet__recruitment_separateposition_list: '/api/recruitment-separateposition/', + samfundet__recruitment_separateposition_detail: '/api/recruitment-separateposition/:pk/', samfundet__recruitment_position_list: '/api/recruitment-position/', samfundet__recruitment_position_detail: '/api/recruitment-position/:pk/', samfundet__recruitment_position_for_applicant_list: '/api/recruitment-position-for-applicant/', @@ -577,9 +660,11 @@ export const ROUTES_BACKEND = { samfundet__recruitment_withdraw_application_recruiter: '/recruitment-withdraw-application-recruiter/:pk/', samfundet__active_recruitment_positions: '/active-recruitment-positions/', samfundet__applicants_without_interviews: '/recruitment-applicants-without-interviews/:pk/', - samfundet__applicants_without_three_interview_criteria: '/recruitment-applicants-without-three-interview-criteria/:pk/', + samfundet__applicants_without_three_interview_criteria: + '/recruitment-applicants-without-three-interview-criteria/:pk/', samfundet__recruitment_recruiter_dashboard: '/recruitment-recruiter-dashboard/:pk/', - samfundet__recruitment_download_gang_application_csv: '/recruitment-download-gang-application-csv/:recruitmentId/:gangId', + samfundet__recruitment_download_gang_application_csv: + '/recruitment-download-gang-application-csv/:recruitmentId/:gangId', samfundet__occupied_timeslots: '/occupiedtimeslot/', samfundet__recruitment_interview_availability: '/recruitment-interview-availability/', samfundet__recruitment_availability: '/recruitment/:id/availability/', diff --git a/frontend/src/routes/frontend.ts b/frontend/src/routes/frontend.ts index d49b59766..e027e3401 100644 --- a/frontend/src/routes/frontend.ts +++ b/frontend/src/routes/frontend.ts @@ -81,7 +81,13 @@ export const ROUTES_FRONTEND = { admin_recruitment_gang_position_overview: '/control-panel/recruitment/:recruitmentId/gang/:gangId', admin_recruitment_gang_position_create: '/control-panel/recruitment/:recruitmentId/gang/:gangId/create/', admin_recruitment_gang_position_edit: '/control-panel/recruitment/:recruitmentId/gang/:gangId/edit/:positionId', + admin_recruitment_gang_separateposition_create: '/control-panel/recruitment/:recruitmentId/separateposition/create', + admin_recruitment_gang_separateposition_edit: + '/control-panel/recruitment/:recruitmentId/separateposition/edit/:separatePositionId', admin_recruitment_recruiter_dashboard: '/control-panel/recruitment/:recruitmentId/recruiter/dashboard/', + admin_recruitment_room_overview: '/control-panel/recruitment/:recruitmentId/room-overview/', + admin_recruitment_room_create: '/control-panel/recruitment/:recruitmentId/room/create/', + admin_recruitment_room_edit: '/control-panel/recruitment/:recruitmentId/room/edit/:roomId/', admin_recruitment_gang_position_applicants_overview: '/control-panel/recruitment/:recruitmentId/gang/:gangId/position/:positionId', diff --git a/frontend/yarn.lock b/frontend/yarn.lock index 1b95554f5..86ba1e5fa 100644 --- a/frontend/yarn.lock +++ b/frontend/yarn.lock @@ -7386,9 +7386,9 @@ __metadata: linkType: hard "caniuse-lite@npm:^1.0.0, caniuse-lite@npm:^1.0.30001538, caniuse-lite@npm:^1.0.30001541": - version: 1.0.30001547 - resolution: "caniuse-lite@npm:1.0.30001547" - checksum: 10c0/bd8ef400fdd6a76aa5a4bc490a5b9b8adffbff1657d36ee1516b4be30315f1a3cfaa51ab872a46d5e7db17424eaa335593cd27e640248b4df3897b113650a7d3 + version: 1.0.30001662 + resolution: "caniuse-lite@npm:1.0.30001662" + checksum: 10c0/4af44610db30b9e63443d984be9d48ab93eef584609b3e87201c10972b9daff0b52674e3ed01f7b7b240460763428a3aa8ef7328d14b76ed31ed464203677007 languageName: node linkType: hard