diff --git a/.vscode/extensions.json b/.vscode/extensions.json index c1843a2c0..d93c29e5e 100644 --- a/.vscode/extensions.json +++ b/.vscode/extensions.json @@ -14,6 +14,7 @@ "yzhang.markdown-all-in-one", "stylelint.vscode-stylelint", "ms-python.mypy-type-checker", - "visualstudioexptteam.vscodeintellicode" + "visualstudioexptteam.vscodeintellicode", + "ms-vscode-remote.vscode-remote-extensionpack" ] } diff --git a/README.md b/README.md index e24335d94..949710e1c 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,54 @@ Samfundet4 is the latest and greatest iteration of samfundet.no. It's built using Django and React. -## Documentation -Looking for install guides and technical documentation? Go to the [Documentation Overview](./docs/README.md)! +## Documentation Overview + +> [!TIP] +> If you're new, start by going through the [Introduction to Samfundet4](./docs/introduction.md) guide. + +### Frontend + +- [Creating react components (conventions)](./docs/technical/frontend/components.md) +- [Forms and schemas](./docs/technical/frontend/forms.md) + - [*Deprecated: SamfForm*](./docs/technical/frontend/samfform.md) +- [Cypress Setup Documentation](./docs/technical/frontend/cypress.md) +- [Data fetching and State management](./docs/technical/frontend/data-fetching.md) + +### Backend + +- [🌐 API documentation](./docs/api-docs.md) +- [Billig (payment system)](./docs/technical/backend/billig.md) +- [Seed scripts](./docs/technical/backend/seed.md) +- [Role system](./docs/technical/backend/rolesystem.md) + +### Other + +- [Automatic Interview Scheduling](./docs/intervew-scheduling.md) + +### Workflow + +- [Work Methodology](./docs/work-methodology.md) + - How to contribute to the project +- [Useful Commands](./docs/useful-commands.md) +- [Useful Docker aliases](./docs/docker-project-specific-commands.md) +- [Common error messages](./docs/common-errors.md) + +### Pipelines & Deployment + +- [Pipeline (mypy, Biome, tsc, ...)](./docs/technical/pipeline.md) + +### Install + +- Linux: [Docker](./docs/install/linux-docker.md) – [Native](./docs/install/linux-native.md) +- MacOS: [Docker](./docs/install/mac-docker.md) – [Native](./docs/install/mac-native.md) +- Windows: [Docker](./docs/install/windows-docker.md) – [WSL](./docs/install/windows-wsl.md) +- [Install script](./docs/install/install-script.md) +- [Post-install instructions](./docs/install/post-install.md) + +### Editor configuration + +* [JetBrains (WebStorm, PyCharm, etc...)](./docs/editors/jetbrains.md) +* [VS Code](./docs/editors/vscode.md) +* [Vim/Neovim](./docs/editors/vim.md) +* [Emacs](./docs/editors/emacs.md) diff --git a/backend/root/utils/routes.py b/backend/root/utils/routes.py index 4008c6524..91bd8250a 100644 --- a/backend/root/utils/routes.py +++ b/backend/root/utils/routes.py @@ -611,5 +611,6 @@ samfundet__recruitment_availability = 'samfundet:recruitment_availability' samfundet__feedback = 'samfundet:feedback' samfundet__purchase_feedback = 'samfundet:purchase_feedback' +samfundet__gang_application_stats = 'samfundet:gang-application-stats' static__path = '' media__path = '' diff --git a/backend/samfundet/urls.py b/backend/samfundet/urls.py index b2b8ab36e..3348d65ef 100644 --- a/backend/samfundet/urls.py +++ b/backend/samfundet/urls.py @@ -146,4 +146,5 @@ path('recruitment//availability/', views.RecruitmentAvailabilityView.as_view(), name='recruitment_availability'), path('feedback/', views.UserFeedbackView.as_view(), name='feedback'), path('purchase-feedback/', views.PurchaseFeedbackView.as_view(), name='purchase_feedback'), + path('recruitment//gang//stats/', views.GangApplicationCountView.as_view(), name='gang-application-stats'), ] diff --git a/backend/samfundet/utils.py b/backend/samfundet/utils.py index 6755ad78d..6964f4003 100644 --- a/backend/samfundet/utils.py +++ b/backend/samfundet/utils.py @@ -42,6 +42,15 @@ def event_query(*, query: QueryDict, events: QuerySet[Event] = None) -> QuerySet return events +def user_query(*, query: QueryDict, users: QuerySet[User] = None) -> QuerySet[User]: + if not users: + users = User.objects.all() + search = query.get('search', None) + if search: + users = users.filter(Q(username__icontains=search) | Q(first_name__icontains=search) | Q(last_name__icontains=search)) + return users + + def generate_timeslots(start_time: datetime.time, end_time: datetime.time, interval_minutes: int) -> list[str]: # Convert from datetime.time objects to datetime.datetime start_datetime = datetime.datetime.combine(datetime.datetime.today(), start_time) diff --git a/backend/samfundet/views.py b/backend/samfundet/views.py index 6cb22a266..1ea43afde 100644 --- a/backend/samfundet/views.py +++ b/backend/samfundet/views.py @@ -39,7 +39,7 @@ REQUESTED_IMPERSONATE_USER, ) -from .utils import event_query, generate_timeslots, get_occupied_timeslots_from_request +from .utils import user_query, event_query, generate_timeslots, get_occupied_timeslots_from_request from .homepage import homepage from .models.role import Role, UserOrgRole, UserGangRole, UserGangSectionRole from .serializers import ( @@ -137,6 +137,7 @@ Recruitment, InterviewRoom, OccupiedTimeslot, + RecruitmentGangStat, RecruitmentPosition, RecruitmentStatistics, RecruitmentApplication, @@ -492,6 +493,10 @@ class AllUsersView(ListAPIView): serializer_class = UserSerializer queryset = User.objects.all() + def get(self, request: Request) -> Response: + users = user_query(query=request.query_params) + return Response(data=UserSerializer(users, many=True).data) + class ImpersonateView(APIView): permission_classes = [IsAuthenticated] # TODO: Permission check. @@ -1369,3 +1374,21 @@ def post(self, request: Request) -> Response: form=purchase_model, ) return Response(status=status.HTTP_201_CREATED, data={'message': 'Feedback submitted successfully!'}) + + +class GangApplicationCountView(APIView): + permission_classes = [IsAuthenticated] + + def get(self, request: Request, recruitment_id: int, gang_id: int) -> Response: + # Get total applications from RecruitmentGangStat + gang_stat = get_object_or_404(RecruitmentGangStat, gang_id=gang_id, recruitment_stats__recruitment_id=recruitment_id) + + return Response( + { + 'total_applications': gang_stat.application_count, + 'total_applicants': gang_stat.applicant_count, + 'average_priority': gang_stat.average_priority, + 'total_accepted': gang_stat.total_accepted, + 'total_rejected': gang_stat.total_rejected, + } + ) diff --git a/docs/README.md b/docs/README.md deleted file mode 100644 index 6c13d881c..000000000 --- a/docs/README.md +++ /dev/null @@ -1,52 +0,0 @@ -[**← Back: Samfundet4**](../) - -# Documentation Overview - -> [!TIP] -> If you're new, start by going through the [Introduction to Samfundet4](./introduction.md) guide. - -## Frontend - -- [Creating react components (conventions)](./technical/frontend/components.md) -- [Forms and schemas](./technical/frontend/forms.md) - - [*Deprecated: SamfForm*](./technical/frontend/samfform.md) -- [Cypress Setup Documentation](./technical/frontend/cypress.md) -- [Data fetching and State management](./technical/frontend/data-fetching.md) - -## Backend - -- [🌐 API documentation](./api-docs.md) -- [Billig (payment system)](./technical/backend/billig.md) -- [Seed scripts](./technical/backend/seed.md) -- [Role system](./technical/backend/rolesystem.md) - -## Other - -- [Automatic Interview Scheduling](./intervew-scheduling.md) - -## Workflow - -- [Work Methodology](./work-methodology.md) - - How to contribute to the project -- [Useful Commands](./useful-commands.md) -- [Useful Docker aliases](./docker-project-specific-commands.md) -- [Common error messages](./common-errors.md) - -## Pipelines & Deployment - -- [Pipeline (mypy, Biome, tsc, ...)](./technical/pipeline.md) - -## Install - -- Linux: [Docker](./install/linux-docker.md) – [Native](./install/linux-native.md) -- MacOS: [Docker](./install/mac-docker.md) – [Native](./install/mac-native.md) -- Windows: [Docker](./install/windows-docker.md) – [WSL](./install/windows-wsl.md) -- [Install script](./install/install-script.md) -- [Post-install instructions](./install/post-install.md) - -## Editor configuration - -* [JetBrains (WebStorm, PyCharm, etc...)](./editors/jetbrains.md) -* [VS Code](./editors/vscode.md) -* [Vim/Neovim](./editors/vim.md) -* [Emacs](./editors/emacs.md) diff --git a/docs/api-docs.md b/docs/api-docs.md index e3162ed87..d2126b96c 100644 --- a/docs/api-docs.md +++ b/docs/api-docs.md @@ -1,4 +1,4 @@ -[**← Back: Documentation Overview**](./README.md) +[**← Back: Documentation Overview**](../README.md#documentation-overview) # API docs diff --git a/docs/common-errors.md b/docs/common-errors.md index 99deef8b1..c016372e6 100644 --- a/docs/common-errors.md +++ b/docs/common-errors.md @@ -1,4 +1,4 @@ -[**← Back: Documentation Overview**](./README.md) +[**← Back: Documentation Overview**](../README.md#documentation-overview) # Common error messages diff --git a/docs/docker-project-specific-commands.md b/docs/docker-project-specific-commands.md index 0b81ff628..c3ab04921 100644 --- a/docs/docker-project-specific-commands.md +++ b/docs/docker-project-specific-commands.md @@ -1,4 +1,4 @@ -[**← Back: Documentation Overview**](./README.md) +[**← Back: Documentation Overview**](../README.md#documentation-overview) # Useful project specific Docker actions diff --git a/docs/install/windows-docker.md b/docs/install/windows-docker.md index 14347c924..aa44d4c91 100644 --- a/docs/install/windows-docker.md +++ b/docs/install/windows-docker.md @@ -3,10 +3,6 @@ > [!WARNING] > This guide is not complete! Feel free to submit a PR to improve it :-) -> [!NOTE] -> We do not recommend running the project this way. This is essentially running nested virtualization, which will lead -> to poor performance. Prefer running [directly in WSL](./windows-wsl.md). - # Installing on Windows (Docker in WSL) ## Install WSL diff --git a/docs/intervew-scheduling.md b/docs/intervew-scheduling.md index 2e2af558d..4b5db06a0 100644 --- a/docs/intervew-scheduling.md +++ b/docs/intervew-scheduling.md @@ -1,4 +1,4 @@ -[**← Back: Documentation Overview**](./README.md) +[**← Back: Documentation Overview**](../README.md#documentation-overview) > [!NOTE] > This document is a work in progress. diff --git a/docs/introduction.md b/docs/introduction.md index 5d5f5d90c..6f763d5d0 100644 --- a/docs/introduction.md +++ b/docs/introduction.md @@ -1,4 +1,4 @@ -[**← Back: Documentation Overview**](./README.md) +[**← Back: Documentation Overview**](../README.md#documentation-overview) # Introduction to Samfundet4 diff --git a/docs/technical/backend/billig.md b/docs/technical/backend/billig.md index 26701c339..7b431350f 100644 --- a/docs/technical/backend/billig.md +++ b/docs/technical/backend/billig.md @@ -1,4 +1,4 @@ -[**← Back: Documentation Overview**](../../README.md) +[**← Back: Documentation Overview**](../../../README.md#documentation-overview) # Billig Integration diff --git a/docs/technical/backend/rolesystem.md b/docs/technical/backend/rolesystem.md index 1b94d38a6..b16bc93ec 100644 --- a/docs/technical/backend/rolesystem.md +++ b/docs/technical/backend/rolesystem.md @@ -1,4 +1,4 @@ -[**← Back: Documentation Overview**](../../README.md) +[**← Back: Documentation Overview**](../../../README.md#documentation-overview) # Role system diff --git a/docs/technical/backend/seed.md b/docs/technical/backend/seed.md index c8bb318f5..6cc980cd5 100644 --- a/docs/technical/backend/seed.md +++ b/docs/technical/backend/seed.md @@ -1,4 +1,4 @@ -[**← Back: Documentation Overview**](../../README.md) +[**← Back: Documentation Overview**](../../../README.md#documentation-overview) # Seeding diff --git a/docs/technical/frontend/components.md b/docs/technical/frontend/components.md index 90d23b7a6..25590da5b 100644 --- a/docs/technical/frontend/components.md +++ b/docs/technical/frontend/components.md @@ -1,4 +1,4 @@ -[**← Back: Documentation Overview**](../../README.md) +[**← Back: Documentation Overview**](../../../README.md#documentation-overview) # Components diff --git a/docs/technical/frontend/cypress.md b/docs/technical/frontend/cypress.md index 41f17a61e..bfb8842ed 100644 --- a/docs/technical/frontend/cypress.md +++ b/docs/technical/frontend/cypress.md @@ -1,4 +1,4 @@ -[**← Back: Documentation Overview**](../../README.md) +[**← Back: Documentation Overview**](../../../README.md#documentation-overview) # Cypress Setup Documentation diff --git a/docs/technical/frontend/data-fetching.md b/docs/technical/frontend/data-fetching.md index d402a06e2..20c1ccce6 100644 --- a/docs/technical/frontend/data-fetching.md +++ b/docs/technical/frontend/data-fetching.md @@ -1,4 +1,4 @@ -[**← Back: Documentation Overview**](../../README.md) +[**← Back: Documentation Overview**](../../../README.md#documentation-overview) # Data fetching and State management diff --git a/docs/technical/frontend/forms.md b/docs/technical/frontend/forms.md index fca7ee760..bb7f23aac 100644 --- a/docs/technical/frontend/forms.md +++ b/docs/technical/frontend/forms.md @@ -1,4 +1,4 @@ -[**← Back: Documentation Overview**](../../README.md) +[**← Back: Documentation Overview**](../../../README.md#documentation-overview) # Forms diff --git a/docs/technical/frontend/samfform.md b/docs/technical/frontend/samfform.md index 75ed8c0cf..296882b0a 100644 --- a/docs/technical/frontend/samfform.md +++ b/docs/technical/frontend/samfform.md @@ -1,4 +1,4 @@ -[**← Back: Documentation Overview**](../../README.md) +[**← Back: Documentation Overview**](../../../README.md#documentation-overview) > [!WARNING] > SamfForm is deprecated, and will slowly be replaced with [our wrappers](./forms.md) around React Hook Form. diff --git a/docs/technical/pipeline.md b/docs/technical/pipeline.md index 265c01305..6c91c4da9 100644 --- a/docs/technical/pipeline.md +++ b/docs/technical/pipeline.md @@ -1,4 +1,4 @@ -[**← Back: Documentation Overview**](../README.md) +[**← Back: Documentation Overview**](../../README.md#documentation-overview) # Pipelines diff --git a/docs/useful-commands.md b/docs/useful-commands.md index 5433adaea..f523b23f5 100644 --- a/docs/useful-commands.md +++ b/docs/useful-commands.md @@ -1,4 +1,4 @@ -[**← Back: Documentation Overview**](./README.md) +[**← Back: Documentation Overview**](../README.md#documentation-overview) # Useful commands diff --git a/docs/work-methodology.md b/docs/work-methodology.md index 43d66d013..b170c8de5 100644 --- a/docs/work-methodology.md +++ b/docs/work-methodology.md @@ -1,4 +1,4 @@ -[**← Back: Documentation Overview**](./README.md) +[**← Back: Documentation Overview**](../README.md#documentation-overview) # Work methodology diff --git a/frontend/src/Components/Button/Button.module.scss b/frontend/src/Components/Button/Button.module.scss index 6c0871b46..978f711ff 100644 --- a/frontend/src/Components/Button/Button.module.scss +++ b/frontend/src/Components/Button/Button.module.scss @@ -14,7 +14,8 @@ justify-content: center; // Remove underline when button is a link - &, &:hover { + &, + &:hover { text-decoration: none; } @@ -37,6 +38,19 @@ color: black; } +.button_selected { + background-color: white; + color: black; + box-shadow: inset 0 0 1.5px 2px rgba(0, 0, 0, 0.15); + cursor: default; + + // Override hover effects for selected buttons + &:hover:not([disabled]) { + filter: none; + box-shadow: inset 0 0 1.5px 2px rgba(0, 0, 0, 0.15); + } +} + .button_samf { background-color: $red-samf; color: white; @@ -169,4 +183,3 @@ text-decoration: underline; } } - diff --git a/frontend/src/Components/Button/utils.ts b/frontend/src/Components/Button/utils.ts index f4152a17a..853dbd25a 100644 --- a/frontend/src/Components/Button/utils.ts +++ b/frontend/src/Components/Button/utils.ts @@ -3,6 +3,7 @@ import styles from './Button.module.scss'; export const themeToStyleMap = { basic: styles.button_basic, + selected: styles.button_selected, pure: styles.pure, text: styles.button_text, samf: styles.button_samf, diff --git a/frontend/src/Components/ExpandableHeader/ExpandableHeader.module.scss b/frontend/src/Components/ExpandableHeader/ExpandableHeader.module.scss index fa089cbb8..619f88fa4 100644 --- a/frontend/src/Components/ExpandableHeader/ExpandableHeader.module.scss +++ b/frontend/src/Components/ExpandableHeader/ExpandableHeader.module.scss @@ -2,13 +2,12 @@ @import 'src/mixins'; - .container { width: 100%; border: 2px solid $grey-3; overflow: hidden; - @include theme-dark{ + @include theme-dark { border: 1px solid $grey-0; } } @@ -16,9 +15,9 @@ .parent { border-radius: 10px; border-color: $grey-4; - background-color: $grey_4; + background-color: $grey-4; - @include theme-dark{ + @include theme-dark { background-color: #1d0809; border-color: transparent; } @@ -30,7 +29,7 @@ margin: 10px; width: auto; - @include theme-dark{ + @include theme-dark { border-color: transparent; } } @@ -51,16 +50,16 @@ text-align: left; width: 100%; - @include theme-dark{ + @include theme-dark { background-color: #1d0809; color: white; } } -.extendable_header_wrapper:hover{ +.extendable_header_wrapper:hover { background-color: #dbdbdb; - @include theme-dark{ + @include theme-dark { background-color: #1d0809; } } diff --git a/frontend/src/Components/RejectionMail/RejectionMail.tsx b/frontend/src/Components/RejectionMail/RejectionMail.tsx index 7109290e1..a97215347 100644 --- a/frontend/src/Components/RejectionMail/RejectionMail.tsx +++ b/frontend/src/Components/RejectionMail/RejectionMail.tsx @@ -2,7 +2,7 @@ import { t } from 'i18next'; import { useState } from 'react'; import { useParams } from 'react-router-dom'; import { toast } from 'react-toastify'; -import { postRejectionMail } from '~/api'; +//import { postRejectionMail } from '~/api'; import { KEY } from '~/i18n/constants'; import { Button } from '../Button'; import { InputField } from '../InputField'; @@ -15,7 +15,7 @@ export function RejectionMail() { function handleSubmit() { if (recruitmentId) { - postRejectionMail(recruitmentId, { subject, text }); + //postRejectionMail(recruitmentId, { subject, text }); toast.success(t(KEY.common_save_successful)); } else { toast.error(t(KEY.common_something_went_wrong)); diff --git a/frontend/src/Pages/OrganizationRecruitmentPage/OrganizationRecruitmentPage.module.scss b/frontend/src/Pages/OrganizationRecruitmentPage/OrganizationRecruitmentPage.module.scss index 684795018..7aa3aabe3 100644 --- a/frontend/src/Pages/OrganizationRecruitmentPage/OrganizationRecruitmentPage.module.scss +++ b/frontend/src/Pages/OrganizationRecruitmentPage/OrganizationRecruitmentPage.module.scss @@ -13,7 +13,7 @@ margin: 1rem; } -.organizationHeader{ +.organizationHeader { display: grid; grid-template-columns: auto auto; align-items: center; @@ -27,12 +27,12 @@ width: 50%; aspect-ratio: 16 / 9; border: none; - @include for-tablet-down{ + @include for-tablet-down { width: 80%; } } -.basicRecruitmentSubHeader{ +.basicRecruitmentSubHeader { @include flex-row-center; width: 100%; padding: 0.25rem; @@ -40,16 +40,15 @@ text-align: center; } -.samfRecruitmentSubHeader{ +.samfRecruitmentSubHeader { @extend .basicRecruitmentSubHeader; color: white; background-color: $red-samf; } -.optionsContainer{ +.optionsContainer { @include flex-row-center; gap: 1rem; - } .personalRow { @@ -57,21 +56,27 @@ gap: 1rem; } -.ukaRecruitmentSubHeader{ +.ukaRecruitmentSubHeader { @extend .samfRecruitmentSubHeader; background-color: $blue-uka; } -.isfitRecruitmentSubHeader{ +.isfitRecruitmentSubHeader { @extend .samfRecruitmentSubHeader; background-color: $blue-isfit; } .openPositionsWrapper { - @include flex-column-center; + @include flex-column-center; width: 100%; gap: 1rem; } - - +.viewModeControll { + display: flex; + width: 100%; + gap: 2rem; + background-color: $grey-4; + padding: 0.25rem; + border-radius: 0.25rem; +} diff --git a/frontend/src/Pages/OrganizationRecruitmentPage/OrganizationRecruitmentPage.tsx b/frontend/src/Pages/OrganizationRecruitmentPage/OrganizationRecruitmentPage.tsx index eb0b49c75..f567826df 100644 --- a/frontend/src/Pages/OrganizationRecruitmentPage/OrganizationRecruitmentPage.tsx +++ b/frontend/src/Pages/OrganizationRecruitmentPage/OrganizationRecruitmentPage.tsx @@ -2,7 +2,7 @@ import classNames from 'classnames'; import { useEffect, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { useParams } from 'react-router-dom'; -import { Logo, OccupiedFormModal, Page, SamfundetLogoSpinner, Text, ToggleSwitch, Video } from '~/Components'; +import { Button, Logo, OccupiedFormModal, Page, SamfundetLogoSpinner, Text, Video } from '~/Components'; import { PersonalRow } from '~/Pages/RecruitmentPage'; import { getOrganization, getRecruitment } from '~/api'; import { useOrganizationContext } from '~/context/OrgContextProvider'; @@ -14,6 +14,8 @@ import { dbT, getObjectFieldOrNumber } from '~/utils'; import { GangSeparatePositions, GangTypeContainer, RecruitmentTabs } from './Components'; import styles from './OrganizationRecruitmentPage.module.scss'; +type ViewMode = 'list' | 'tab'; + export function OrganizationRecruitmentPage() { const isDesktop = useDesktop(); const { recruitmentId } = useParams<{ recruitmentId: string }>(); @@ -23,6 +25,8 @@ export function OrganizationRecruitmentPage() { const [recruitment, setRecruitment] = useState(); const [organizationName, setOrganizationName] = useState(OrgNameType.FALLBACK); const [loading, setLoading] = useState(true); + const [positionsViewMode, setViewMode] = useState('list'); + useTitle(dbT(recruitment, 'name') ?? ''); useEffect(() => { @@ -76,13 +80,7 @@ export function OrganizationRecruitmentPage() { {dbT(recruitment, 'name')} - {recruitment?.promo_media ? ( - <> -