From 575a4994bdff31b45bb13a42cf1f6d67baf4f204 Mon Sep 17 00:00:00 2001 From: eilifhl Date: Tue, 23 Sep 2025 20:59:19 +0200 Subject: [PATCH 01/61] Add darkmode to storybook --- .../Components/OpeningHours/OpeningHours.module.scss | 12 ++++++++++++ .../src/Components/OpeningHours/OpeningHours.tsx | 8 +++++--- 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/frontend/src/Components/OpeningHours/OpeningHours.module.scss b/frontend/src/Components/OpeningHours/OpeningHours.module.scss index ded5733fc..5a5822013 100644 --- a/frontend/src/Components/OpeningHours/OpeningHours.module.scss +++ b/frontend/src/Components/OpeningHours/OpeningHours.module.scss @@ -1,4 +1,5 @@ @use 'src/constants' as *; +@use 'src/mixins' as *; .container { background-color: $white; @@ -7,8 +8,19 @@ display: flex; flex-direction: column; align-items: center; + + & td { + border-left: 0; + border-right: 0; +} + + @include theme-dark { + background-color: $black-2; + color: $white; + } } + .openingHoursText { margin: 0 0.2em; } diff --git a/frontend/src/Components/OpeningHours/OpeningHours.tsx b/frontend/src/Components/OpeningHours/OpeningHours.tsx index ad2c24308..fd568954b 100644 --- a/frontend/src/Components/OpeningHours/OpeningHours.tsx +++ b/frontend/src/Components/OpeningHours/OpeningHours.tsx @@ -1,4 +1,6 @@ import { useTranslation } from 'react-i18next'; +import { Text } from '~/Components/Text/Text'; +import { Link } from '~/Components/Link/Link'; import { TimeDuration } from '~/Components'; import { KEY } from '~/i18n/constants'; import styles from './OpeningHours.module.scss'; @@ -18,14 +20,14 @@ export function OpeningHours({ venues }: OpeningHoursProps) { const { t } = useTranslation(); return (
-

{t(KEY.common_opening_hours)}

+ {t(KEY.common_opening_hours)} {venues.map((venue) => ( ))} diff --git a/frontend/src/dto.ts b/frontend/src/dto.ts index 55431ead0..6bc1e2e25 100644 --- a/frontend/src/dto.ts +++ b/frontend/src/dto.ts @@ -139,6 +139,12 @@ export type VenueDto = { closing_sunday?: string; }; +export type OpenVenuesDto = { + name: string; + opening: string; + closing: string; +} + // ==================== // // Event // // ==================== // From a1d65d0f5d91d5d278fd4f599fe0788c03ed1ac0 Mon Sep 17 00:00:00 2001 From: eilifhl Date: Thu, 25 Sep 2025 18:51:52 +0200 Subject: [PATCH 04/61] Change route naming --- backend/root/utils/routes.py | 2 +- frontend/src/api.ts | 7 +++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/backend/root/utils/routes.py b/backend/root/utils/routes.py index ebe6d0f60..a2784cffe 100644 --- a/backend/root/utils/routes.py +++ b/backend/root/utils/routes.py @@ -486,7 +486,7 @@ samfundet__eventgroups_detail = 'samfundet:eventgroups-detail' samfundet__venues_list = 'samfundet:venues-list' samfundet__venues_detail = 'samfundet:venues-detail' -samfundet__venues_open_today = 'samfundet:venues_open_today' +samfundet__open_venues = 'samfundet:open_venues' samfundet__closedperiods_list = 'samfundet:closedperiods-list' samfundet__closedperiods_detail = 'samfundet:closedperiods-detail' samfundet__gangs_list = 'samfundet:gangs-list' diff --git a/frontend/src/api.ts b/frontend/src/api.ts index 06c129a55..68558d80b 100644 --- a/frontend/src/api.ts +++ b/frontend/src/api.ts @@ -19,6 +19,7 @@ import type { MenuDto, MenuItemDto, OccupiedTimeslotDto, + OpenVenuesDto, OrganizationDto, PermissionDto, PositionsByTagResponse, @@ -186,6 +187,12 @@ export async function patchVenue(slug: string | number, venue: Partial return response.data; } +export async function getOpenVenues(): Promise { + const url = BACKEND_DOMAIN + ROUTES.backend.samfundet__open_venues; + const response = await axios.patch(url, { withCredentials: true }); + return response.data; +} + export async function getPermissions(): Promise { const url = BACKEND_DOMAIN + ROUTES.backend.samfundet__permissions_list; const response = await axios.get(url, { withCredentials: true }); From 9d3231fcdc0647cad1327bd963f9af928ebd6a4d Mon Sep 17 00:00:00 2001 From: eilifhl Date: Thu, 25 Sep 2025 18:53:07 +0200 Subject: [PATCH 05/61] Change request from patch to get --- frontend/src/api.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/api.ts b/frontend/src/api.ts index 68558d80b..c3c5cd56b 100644 --- a/frontend/src/api.ts +++ b/frontend/src/api.ts @@ -189,7 +189,7 @@ export async function patchVenue(slug: string | number, venue: Partial export async function getOpenVenues(): Promise { const url = BACKEND_DOMAIN + ROUTES.backend.samfundet__open_venues; - const response = await axios.patch(url, { withCredentials: true }); + const response = await axios.get(url, { withCredentials: true }); return response.data; } From cb6307124899079e2e990126d6f348bceefc71a5 Mon Sep 17 00:00:00 2001 From: eilifhl Date: Thu, 25 Sep 2025 19:23:53 +0200 Subject: [PATCH 06/61] Create open_venues function in VenueView --- backend/samfundet/view/general_views.py | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/backend/samfundet/view/general_views.py b/backend/samfundet/view/general_views.py index 31825dfc3..2e440a688 100644 --- a/backend/samfundet/view/general_views.py +++ b/backend/samfundet/view/general_views.py @@ -16,7 +16,7 @@ from rest_framework.permissions import AllowAny from django.utils import timezone -from django.db.models import QuerySet +from django.db.models import Q, QuerySet from django.shortcuts import get_object_or_404 from root.custom_classes.permission_classes import RoleProtectedOrAnonReadOnlyObjectPermissions @@ -113,6 +113,22 @@ class VenueView(ModelViewSet): queryset = Venue.objects.all() lookup_field = 'slug' + @action(detail=False, methods=['get']) + def open_venues(self, request: Request) -> Response: + now = timezone.now() + day_name = now.strftime('%A').lower() + + q = Q( + **{ + f'opening_{day_name}__lte': now.time(), + f'closing_{day_name}__gte': now.time(), + } + ) + + open_venues = Venue.objects.filter(q) + serializer = self.get_serializer(open_venues, many=True) + return Response(serializer.data) + class ClosedPeriodView(ModelViewSet): permission_classes = (RoleProtectedOrAnonReadOnlyObjectPermissions,) From 532c81bb682cf65b6e50c82734e88d51ca414c57 Mon Sep 17 00:00:00 2001 From: eilifhl Date: Thu, 25 Sep 2025 19:24:06 +0200 Subject: [PATCH 07/61] Generate routes --- backend/root/utils/routes.py | 9 ++++++++- frontend/src/routes/backend.ts | 10 +++++++++- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/backend/root/utils/routes.py b/backend/root/utils/routes.py index a2784cffe..2172812bb 100644 --- a/backend/root/utils/routes.py +++ b/backend/root/utils/routes.py @@ -485,8 +485,8 @@ samfundet__eventgroups_list = 'samfundet:eventgroups-list' samfundet__eventgroups_detail = 'samfundet:eventgroups-detail' samfundet__venues_list = 'samfundet:venues-list' +samfundet__venues_open_venues = 'samfundet:venues-open-venues' samfundet__venues_detail = 'samfundet:venues-detail' -samfundet__open_venues = 'samfundet:open_venues' samfundet__closedperiods_list = 'samfundet:closedperiods-list' samfundet__closedperiods_detail = 'samfundet:closedperiods-detail' samfundet__gangs_list = 'samfundet:gangs-list' @@ -554,6 +554,13 @@ samfundet__recruitment_applications_for_position_detail = 'samfundet:recruitment_applications_for_position-detail' samfundet__interview_list = 'samfundet:interview-list' samfundet__interview_detail = 'samfundet:interview-detail' +samfundet__billig_event_list = 'samfundet:billig_event-list' +samfundet__billig_event_detail = 'samfundet:billig_event-detail' +samfundet__billig_price_group_list = 'samfundet:billig_price_group-list' +samfundet__billig_price_group_detail = 'samfundet:billig_price_group-detail' +samfundet__billig_ticket_group_list = 'samfundet:billig_ticket_group-list' +samfundet__billig_ticket_group_detail = 'samfundet:billig_ticket_group-detail' +samfundet__api_root = 'samfundet:api-root' samfundet__api_root = 'samfundet:api-root' samfundet__schema = 'samfundet:schema' samfundet__swagger_ui = 'samfundet:swagger_ui' diff --git a/frontend/src/routes/backend.ts b/frontend/src/routes/backend.ts index d0d027a2b..485fbfa7d 100644 --- a/frontend/src/routes/backend.ts +++ b/frontend/src/routes/backend.ts @@ -484,6 +484,7 @@ export const ROUTES_BACKEND = { samfundet__eventgroups_list: '/api/eventgroups/', samfundet__eventgroups_detail: '/api/eventgroups/:pk/', samfundet__venues_list: '/api/venues/', + samfundet__venues_open_venues: '/api/venues/open_venues/', samfundet__venues_detail: '/api/venues/:slug/', samfundet__closedperiods_list: '/api/closed/', samfundet__closedperiods_detail: '/api/closed/:pk/', @@ -552,7 +553,14 @@ export const ROUTES_BACKEND = { samfundet__recruitment_applications_for_position_detail: '/api/recruitment-applications-for-position/:pk/', samfundet__interview_list: '/api/interview/', samfundet__interview_detail: '/api/interview/:pk/', + samfundet__billig_event_list: '/api/billig-event/', + samfundet__billig_event_detail: '/api/billig-event/:pk/', + samfundet__billig_price_group_list: '/api/billig-price-group/', + samfundet__billig_price_group_detail: '/api/billig-price-group/:pk/', + samfundet__billig_ticket_group_list: '/api/billig-ticket-group/', + samfundet__billig_ticket_group_detail: '/api/billig-ticket-group/:pk/', samfundet__api_root: '/api/', + samfundet__api_root: '/api/:format', samfundet__schema: '/schema/', samfundet__swagger_ui: '/schema/swagger-ui/', samfundet__redoc: '/schema/redoc/', @@ -610,4 +618,4 @@ export const ROUTES_BACKEND = { samfundet__recruitment_all_applications: '/recruitment/all-applications/', static__path: '/static/:path', media__path: '/media/:path', -} as const; +} as const; \ No newline at end of file From 1bd43d8f9b008a23fb698e849a9a92643b897e53 Mon Sep 17 00:00:00 2001 From: eilifhl Date: Tue, 30 Sep 2025 19:17:23 +0200 Subject: [PATCH 08/61] Remove irrelevant routes changes --- backend/root/utils/routes.py | 7 ------- frontend/src/routes/backend.ts | 9 +-------- 2 files changed, 1 insertion(+), 15 deletions(-) diff --git a/backend/root/utils/routes.py b/backend/root/utils/routes.py index 2172812bb..83a4dd799 100644 --- a/backend/root/utils/routes.py +++ b/backend/root/utils/routes.py @@ -554,13 +554,6 @@ samfundet__recruitment_applications_for_position_detail = 'samfundet:recruitment_applications_for_position-detail' samfundet__interview_list = 'samfundet:interview-list' samfundet__interview_detail = 'samfundet:interview-detail' -samfundet__billig_event_list = 'samfundet:billig_event-list' -samfundet__billig_event_detail = 'samfundet:billig_event-detail' -samfundet__billig_price_group_list = 'samfundet:billig_price_group-list' -samfundet__billig_price_group_detail = 'samfundet:billig_price_group-detail' -samfundet__billig_ticket_group_list = 'samfundet:billig_ticket_group-list' -samfundet__billig_ticket_group_detail = 'samfundet:billig_ticket_group-detail' -samfundet__api_root = 'samfundet:api-root' samfundet__api_root = 'samfundet:api-root' samfundet__schema = 'samfundet:schema' samfundet__swagger_ui = 'samfundet:swagger_ui' diff --git a/frontend/src/routes/backend.ts b/frontend/src/routes/backend.ts index 485fbfa7d..af0dc8645 100644 --- a/frontend/src/routes/backend.ts +++ b/frontend/src/routes/backend.ts @@ -553,14 +553,7 @@ export const ROUTES_BACKEND = { samfundet__recruitment_applications_for_position_detail: '/api/recruitment-applications-for-position/:pk/', samfundet__interview_list: '/api/interview/', samfundet__interview_detail: '/api/interview/:pk/', - samfundet__billig_event_list: '/api/billig-event/', - samfundet__billig_event_detail: '/api/billig-event/:pk/', - samfundet__billig_price_group_list: '/api/billig-price-group/', - samfundet__billig_price_group_detail: '/api/billig-price-group/:pk/', - samfundet__billig_ticket_group_list: '/api/billig-ticket-group/', - samfundet__billig_ticket_group_detail: '/api/billig-ticket-group/:pk/', samfundet__api_root: '/api/', - samfundet__api_root: '/api/:format', samfundet__schema: '/schema/', samfundet__swagger_ui: '/schema/swagger-ui/', samfundet__redoc: '/schema/redoc/', @@ -618,4 +611,4 @@ export const ROUTES_BACKEND = { samfundet__recruitment_all_applications: '/recruitment/all-applications/', static__path: '/static/:path', media__path: '/media/:path', -} as const; \ No newline at end of file +} as const; From 97819384c6c64a477b85b063109d9c823e19ad83 Mon Sep 17 00:00:00 2001 From: eilifhl Date: Tue, 30 Sep 2025 19:52:55 +0200 Subject: [PATCH 09/61] Refactor api call to deliver venue list instead of new datatype --- frontend/src/api.ts | 6 +++--- frontend/src/dto.ts | 6 +----- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/frontend/src/api.ts b/frontend/src/api.ts index c3c5cd56b..8987d63c9 100644 --- a/frontend/src/api.ts +++ b/frontend/src/api.ts @@ -187,9 +187,9 @@ export async function patchVenue(slug: string | number, venue: Partial return response.data; } -export async function getOpenVenues(): Promise { - const url = BACKEND_DOMAIN + ROUTES.backend.samfundet__open_venues; - const response = await axios.get(url, { withCredentials: true }); +export async function getOpenVenues(): Promise { + const url = BACKEND_DOMAIN + ROUTES.backend.samfundet__venues_open_venues; + const response = await axios.get(url, { withCredentials: true }); return response.data; } diff --git a/frontend/src/dto.ts b/frontend/src/dto.ts index 6bc1e2e25..9ba1c2def 100644 --- a/frontend/src/dto.ts +++ b/frontend/src/dto.ts @@ -139,11 +139,7 @@ export type VenueDto = { closing_sunday?: string; }; -export type OpenVenuesDto = { - name: string; - opening: string; - closing: string; -} +export type OpenVenuesDto = VenueDto[]; // ==================== // // Event // From 567abe2842c07747e68d2769e2f4675c071f81e1 Mon Sep 17 00:00:00 2001 From: eilifhl Date: Tue, 30 Sep 2025 21:36:37 +0200 Subject: [PATCH 10/61] Create container component --- .../OpeningHours/OpeningHoursContainer.tsx | 38 +++++++++++++++++++ frontend/src/Pages/HomePage/HomePage.tsx | 2 + 2 files changed, 40 insertions(+) create mode 100644 frontend/src/Components/OpeningHours/OpeningHoursContainer.tsx diff --git a/frontend/src/Components/OpeningHours/OpeningHoursContainer.tsx b/frontend/src/Components/OpeningHours/OpeningHoursContainer.tsx new file mode 100644 index 000000000..10a90ed62 --- /dev/null +++ b/frontend/src/Components/OpeningHours/OpeningHoursContainer.tsx @@ -0,0 +1,38 @@ +import { useQuery } from '@tanstack/react-query'; +import { useTranslation } from 'react-i18next'; +import { getOpenVenues } from '~/api'; +import { Text } from '~/Components/Text'; +import { VenueDto } from '~/dto'; +import { KEY } from '~/i18n/constants'; +import { OpeningHours } from './OpeningHours'; + +export function OpeningHoursContainer() { + const { t } = useTranslation(); + const { data: openVenues, isLoading, isError } = useQuery({ + queryKey: ['openVenues'], + queryFn: getOpenVenues, + }); + + if (isLoading) { + return {t(KEY.common_loading)}; + } + + if (isError || !openVenues) { + return {t(KEY.error_generic)}; + } + + const today = new Date().toISOString().split('T')[0]; + const day = new Date().toLocaleDateString('en-US', { weekday: 'long' }).toLowerCase(); + + const venues = openVenues.map((venue) => { + const openingTime = venue[`opening_${day}` as keyof VenueDto] as string; + const closingTime = venue[`closing_${day}` as keyof VenueDto] as string; + return { + name: venue.name, + opening: `${today}T${openingTime}`, + closing: `${today}T${closingTime}`, + }; + }); + + return ; +} diff --git a/frontend/src/Pages/HomePage/HomePage.tsx b/frontend/src/Pages/HomePage/HomePage.tsx index cb675d67a..4e637006c 100644 --- a/frontend/src/Pages/HomePage/HomePage.tsx +++ b/frontend/src/Pages/HomePage/HomePage.tsx @@ -1,6 +1,7 @@ import { useEffect, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { toast } from 'react-toastify'; +import { OpeningHoursContainer } from '~/Components/OpeningHours/OpeningHoursContainer'; import { EventCarousel, LargeCard } from '~/Pages/HomePage/components'; import { getHomeData } from '~/api'; import type { HomePageDto, HomePageElementDto } from '~/dto'; @@ -52,6 +53,7 @@ export function HomePage() { <>
+ {/**/} {isLoading && skeleton} From e01573dde381065cea572a990410e2c5c763f165 Mon Sep 17 00:00:00 2001 From: eilifhl Date: Tue, 30 Sep 2025 22:03:07 +0200 Subject: [PATCH 11/61] Refactor so all logic is in OpeningHours.tsx --- .../Components/OpeningHours/OpeningHours.tsx | 65 ++++++++++++------- .../OpeningHours/OpeningHoursContainer.tsx | 27 +------- 2 files changed, 43 insertions(+), 49 deletions(-) diff --git a/frontend/src/Components/OpeningHours/OpeningHours.tsx b/frontend/src/Components/OpeningHours/OpeningHours.tsx index d93cf76b8..6fb1162b0 100644 --- a/frontend/src/Components/OpeningHours/OpeningHours.tsx +++ b/frontend/src/Components/OpeningHours/OpeningHours.tsx @@ -1,38 +1,57 @@ import { useTranslation } from 'react-i18next'; -import { Text } from '~/Components/Text/Text'; -import { Link } from '~/Components/Link/Link'; import { TimeDuration } from '~/Components'; +import { Link } from '~/Components/Link/Link'; +import { Text } from '~/Components/Text/Text'; +import { VenueDto } from '~/dto'; import { KEY } from '~/i18n/constants'; import styles from './OpeningHours.module.scss'; -type VenueOpeningProp = { - name: string; - opening: string; - closing: string; -}; - type OpeningHoursProps = { - venues: VenueOpeningProp[]; + venues: VenueDto[] | undefined; + isLoading: boolean; + isError: boolean; }; -export function OpeningHours({ venues }: OpeningHoursProps) { +export function OpeningHours({ venues, isLoading, isError }: OpeningHoursProps) { const { t } = useTranslation(); + + if (isLoading) { + return {t(KEY.common_loading)}; + } + + if (isError || !venues) { + return {t(KEY.error_generic)}; + } + + const today = new Date().toISOString().split('T')[0]; + const day = new Date().toLocaleDateString('en-US', { weekday: 'long' }).toLowerCase(); + return (
- {t(KEY.common_opening_hours)} + + {t(KEY.common_opening_hours)} +
- +

{venue.name}

-
+
From fa443e50ac20c96d947639c928f2de1ee0535936 Mon Sep 17 00:00:00 2001 From: eilifhl Date: Tue, 23 Sep 2025 21:15:28 +0200 Subject: [PATCH 02/61] Remove bg color --- frontend/src/Components/OpeningHours/OpeningHours.module.scss | 1 - 1 file changed, 1 deletion(-) diff --git a/frontend/src/Components/OpeningHours/OpeningHours.module.scss b/frontend/src/Components/OpeningHours/OpeningHours.module.scss index 5a5822013..a62f5a46d 100644 --- a/frontend/src/Components/OpeningHours/OpeningHours.module.scss +++ b/frontend/src/Components/OpeningHours/OpeningHours.module.scss @@ -2,7 +2,6 @@ @use 'src/mixins' as *; .container { - background-color: $white; text-align: center; margin-bottom: 20px; display: flex; From f14c2a2cc2195f032f3d537732851b1f39327331 Mon Sep 17 00:00:00 2001 From: eilifhl Date: Tue, 23 Sep 2025 21:40:36 +0200 Subject: [PATCH 03/61] Revise Venue Opening Dto --- backend/root/utils/routes.py | 1 + .../OpeningHours/OpeningHours.stories.tsx | 11 +++++------ .../src/Components/OpeningHours/OpeningHours.tsx | 13 ++++++------- frontend/src/dto.ts | 6 ++++++ 4 files changed, 18 insertions(+), 13 deletions(-) diff --git a/backend/root/utils/routes.py b/backend/root/utils/routes.py index 880250448..ebe6d0f60 100644 --- a/backend/root/utils/routes.py +++ b/backend/root/utils/routes.py @@ -486,6 +486,7 @@ samfundet__eventgroups_detail = 'samfundet:eventgroups-detail' samfundet__venues_list = 'samfundet:venues-list' samfundet__venues_detail = 'samfundet:venues-detail' +samfundet__venues_open_today = 'samfundet:venues_open_today' samfundet__closedperiods_list = 'samfundet:closedperiods-list' samfundet__closedperiods_detail = 'samfundet:closedperiods-detail' samfundet__gangs_list = 'samfundet:gangs-list' diff --git a/frontend/src/Components/OpeningHours/OpeningHours.stories.tsx b/frontend/src/Components/OpeningHours/OpeningHours.stories.tsx index 71e1162a8..2bf56de89 100644 --- a/frontend/src/Components/OpeningHours/OpeningHours.stories.tsx +++ b/frontend/src/Components/OpeningHours/OpeningHours.stories.tsx @@ -16,13 +16,12 @@ export const Basic: Story = { venues: [ { name: 'Daglighallen', - url: 'www.google.com', - start: '2011-10-05T14:00:00.000Z', - end: '2011-10-05T17:00:00.000Z', + opening: '2011-10-05T14:00:00.000Z', + closing: '2011-10-05T17:00:00.000Z', }, - { name: 'Edgar', url: 'www.google.com', start: '2011-10-05T12:00:00.000Z', end: '2011-10-05T17:00:00.000Z' }, - { name: 'Klubben', url: 'www.google.com', start: '2011-10-05T16:00:00.000Z', end: '2011-10-05T18:00:00.000Z' }, - { name: 'Storsalen', url: 'www.google.com', start: '2011-10-05T14:00:00.000Z', end: '2011-10-05T22:00:00.000Z' }, + { name: 'Edgar', opening: '2011-10-05T12:00:00.000Z', closing: '2011-10-05T17:00:00.000Z' }, + { name: 'Klubben', opening: '2011-10-05T16:00:00.000Z', closing: '2011-10-05T18:00:00.000Z' }, + { name: 'Storsalen', opening: '2011-10-05T14:00:00.000Z', closing: '2011-10-05T22:00:00.000Z' }, ], }, }; diff --git a/frontend/src/Components/OpeningHours/OpeningHours.tsx b/frontend/src/Components/OpeningHours/OpeningHours.tsx index fd568954b..d93cf76b8 100644 --- a/frontend/src/Components/OpeningHours/OpeningHours.tsx +++ b/frontend/src/Components/OpeningHours/OpeningHours.tsx @@ -5,15 +5,14 @@ import { TimeDuration } from '~/Components'; import { KEY } from '~/i18n/constants'; import styles from './OpeningHours.module.scss'; -type FakeVenue = { +type VenueOpeningProp = { name: string; - url: string; - start: string; - end: string; + opening: string; + closing: string; }; type OpeningHoursProps = { - venues: FakeVenue[]; + venues: VenueOpeningProp[]; }; export function OpeningHours({ venues }: OpeningHoursProps) { @@ -25,12 +24,12 @@ export function OpeningHours({ venues }: OpeningHoursProps) { {venues.map((venue) => (
- +

{venue.name}

- +
- {venues.map((venue) => ( - - - - - ))} + {venues.map((venue) => { + const openingTime = venue[`opening_${day}` as keyof VenueDto] as string; + const closingTime = venue[`closing_${day}` as keyof VenueDto] as string; + return ( + + + + + ); + })}
- -

{venue.name}

- -
- -
+ +

{venue.name}

+ +
+ +
); diff --git a/frontend/src/Components/OpeningHours/OpeningHoursContainer.tsx b/frontend/src/Components/OpeningHours/OpeningHoursContainer.tsx index 10a90ed62..88c5232e7 100644 --- a/frontend/src/Components/OpeningHours/OpeningHoursContainer.tsx +++ b/frontend/src/Components/OpeningHours/OpeningHoursContainer.tsx @@ -1,38 +1,13 @@ import { useQuery } from '@tanstack/react-query'; -import { useTranslation } from 'react-i18next'; import { getOpenVenues } from '~/api'; -import { Text } from '~/Components/Text'; import { VenueDto } from '~/dto'; -import { KEY } from '~/i18n/constants'; import { OpeningHours } from './OpeningHours'; export function OpeningHoursContainer() { - const { t } = useTranslation(); const { data: openVenues, isLoading, isError } = useQuery({ queryKey: ['openVenues'], queryFn: getOpenVenues, }); - if (isLoading) { - return {t(KEY.common_loading)}; - } - - if (isError || !openVenues) { - return {t(KEY.error_generic)}; - } - - const today = new Date().toISOString().split('T')[0]; - const day = new Date().toLocaleDateString('en-US', { weekday: 'long' }).toLowerCase(); - - const venues = openVenues.map((venue) => { - const openingTime = venue[`opening_${day}` as keyof VenueDto] as string; - const closingTime = venue[`closing_${day}` as keyof VenueDto] as string; - return { - name: venue.name, - opening: `${today}T${openingTime}`, - closing: `${today}T${closingTime}`, - }; - }); - - return ; + return ; } From fcb4de4ed10558f39ec7f3a7e0ae6f702f4aea74 Mon Sep 17 00:00:00 2001 From: eilifhl Date: Thu, 2 Oct 2025 18:32:45 +0200 Subject: [PATCH 12/61] Remove OpeningHoursContainer from HomePage --- frontend/src/Pages/HomePage/HomePage.tsx | 2 -- 1 file changed, 2 deletions(-) diff --git a/frontend/src/Pages/HomePage/HomePage.tsx b/frontend/src/Pages/HomePage/HomePage.tsx index 4e637006c..cb675d67a 100644 --- a/frontend/src/Pages/HomePage/HomePage.tsx +++ b/frontend/src/Pages/HomePage/HomePage.tsx @@ -1,7 +1,6 @@ import { useEffect, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { toast } from 'react-toastify'; -import { OpeningHoursContainer } from '~/Components/OpeningHours/OpeningHoursContainer'; import { EventCarousel, LargeCard } from '~/Pages/HomePage/components'; import { getHomeData } from '~/api'; import type { HomePageDto, HomePageElementDto } from '~/dto'; @@ -53,7 +52,6 @@ export function HomePage() { <>
- {/**/} {isLoading && skeleton} From dc90eb25af3295ab071173fbd0c761270537d9c5 Mon Sep 17 00:00:00 2001 From: eilifhl Date: Thu, 2 Oct 2025 18:50:17 +0200 Subject: [PATCH 13/61] BIOMEEEEEE --- frontend/src/Components/OpeningHours/OpeningHours.tsx | 4 ++-- .../src/Components/OpeningHours/OpeningHoursContainer.tsx | 8 ++++++-- frontend/src/api.ts | 1 - 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/frontend/src/Components/OpeningHours/OpeningHours.tsx b/frontend/src/Components/OpeningHours/OpeningHours.tsx index 6fb1162b0..d1290edcb 100644 --- a/frontend/src/Components/OpeningHours/OpeningHours.tsx +++ b/frontend/src/Components/OpeningHours/OpeningHours.tsx @@ -2,7 +2,7 @@ import { useTranslation } from 'react-i18next'; import { TimeDuration } from '~/Components'; import { Link } from '~/Components/Link/Link'; import { Text } from '~/Components/Text/Text'; -import { VenueDto } from '~/dto'; +import type { VenueDto } from '~/dto'; import { KEY } from '~/i18n/constants'; import styles from './OpeningHours.module.scss'; @@ -38,7 +38,7 @@ export function OpeningHours({ venues, isLoading, isError }: OpeningHoursProps) return ( - +

{venue.name}

diff --git a/frontend/src/Components/OpeningHours/OpeningHoursContainer.tsx b/frontend/src/Components/OpeningHours/OpeningHoursContainer.tsx index 88c5232e7..65bbb7c46 100644 --- a/frontend/src/Components/OpeningHours/OpeningHoursContainer.tsx +++ b/frontend/src/Components/OpeningHours/OpeningHoursContainer.tsx @@ -1,10 +1,14 @@ import { useQuery } from '@tanstack/react-query'; import { getOpenVenues } from '~/api'; -import { VenueDto } from '~/dto'; +import type { VenueDto } from '~/dto'; import { OpeningHours } from './OpeningHours'; export function OpeningHoursContainer() { - const { data: openVenues, isLoading, isError } = useQuery({ + const { + data: openVenues, + isLoading, + isError, + } = useQuery({ queryKey: ['openVenues'], queryFn: getOpenVenues, }); diff --git a/frontend/src/api.ts b/frontend/src/api.ts index 8987d63c9..1b4982be9 100644 --- a/frontend/src/api.ts +++ b/frontend/src/api.ts @@ -19,7 +19,6 @@ import type { MenuDto, MenuItemDto, OccupiedTimeslotDto, - OpenVenuesDto, OrganizationDto, PermissionDto, PositionsByTagResponse, From 28806264a30f211a23c72ce97949a94ce38d8b9d Mon Sep 17 00:00:00 2001 From: eilifhl Date: Thu, 2 Oct 2025 19:14:44 +0200 Subject: [PATCH 14/61] STYLELIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIINTTTTTT --- .../Components/OpeningHours/OpeningHours.module.scss | 10 +++++----- frontend/src/Components/OpeningHours/OpeningHours.tsx | 4 ++-- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/frontend/src/Components/OpeningHours/OpeningHours.module.scss b/frontend/src/Components/OpeningHours/OpeningHours.module.scss index a62f5a46d..f238771f0 100644 --- a/frontend/src/Components/OpeningHours/OpeningHours.module.scss +++ b/frontend/src/Components/OpeningHours/OpeningHours.module.scss @@ -1,4 +1,5 @@ @use 'src/constants' as *; + @use 'src/mixins' as *; .container { @@ -8,10 +9,10 @@ flex-direction: column; align-items: center; - & td { - border-left: 0; - border-right: 0; -} + .tableCell { + border-left: 0; + border-right: 0; + } @include theme-dark { background-color: $black-2; @@ -19,7 +20,6 @@ } } - .openingHoursText { margin: 0 0.2em; } diff --git a/frontend/src/Components/OpeningHours/OpeningHours.tsx b/frontend/src/Components/OpeningHours/OpeningHours.tsx index d1290edcb..01d9f3925 100644 --- a/frontend/src/Components/OpeningHours/OpeningHours.tsx +++ b/frontend/src/Components/OpeningHours/OpeningHours.tsx @@ -37,12 +37,12 @@ export function OpeningHours({ venues, isLoading, isError }: OpeningHoursProps) const closingTime = venue[`closing_${day}` as keyof VenueDto] as string; return ( - +

{venue.name}

- + Date: Thu, 2 Oct 2025 19:47:59 +0200 Subject: [PATCH 15/61] Add correct storybook data --- .../OpeningHours/OpeningHours.stories.tsx | 78 +++++++++++++++++-- 1 file changed, 73 insertions(+), 5 deletions(-) diff --git a/frontend/src/Components/OpeningHours/OpeningHours.stories.tsx b/frontend/src/Components/OpeningHours/OpeningHours.stories.tsx index 2bf56de89..4a3a13132 100644 --- a/frontend/src/Components/OpeningHours/OpeningHours.stories.tsx +++ b/frontend/src/Components/OpeningHours/OpeningHours.stories.tsx @@ -15,13 +15,81 @@ export const Basic: Story = { args: { venues: [ { + id: 1, + slug: 'daglighallen', name: 'Daglighallen', - opening: '2011-10-05T14:00:00.000Z', - closing: '2011-10-05T17:00:00.000Z', + opening_monday: '08:00:00', + closing_monday: '20:00:00', + opening_tuesday: '08:00:00', + closing_tuesday: '20:00:00', + opening_wednesday: '08:00:00', + closing_wednesday: '20:00:00', + opening_thursday: '08:00:00', + closing_thursday: '20:00:00', + opening_friday: '08:00:00', + closing_friday: '20:00:00', + opening_saturday: '08:00:00', + closing_saturday: '20:00:00', + opening_sunday: '08:00:00', + closing_sunday: '20:00:00', + }, + { + id: 2, + slug: 'edgar', + name: 'Edgar', + opening_monday: '08:00:00', + closing_monday: '20:00:00', + opening_tuesday: '08:00:00', + closing_tuesday: '20:00:00', + opening_wednesday: '08:00:00', + closing_wednesday: '20:00:00', + opening_thursday: '08:00:00', + closing_thursday: '20:00:00', + opening_friday: '08:00:00', + closing_friday: '20:00:00', + opening_saturday: '08:00:00', + closing_saturday: '20:00:00', + opening_sunday: '08:00:00', + closing_sunday: '20:00:00', + }, + { + id: 3, + slug: 'klubben', + name: 'Klubben', + opening_monday: '08:00:00', + closing_monday: '20:00:00', + opening_tuesday: '08:00:00', + closing_tuesday: '20:00:00', + opening_wednesday: '08:00:00', + closing_wednesday: '20:00:00', + opening_thursday: '08:00:00', + closing_thursday: '20:00:00', + opening_friday: '08:00:00', + closing_friday: '20:00:00', + opening_saturday: '08:00:00', + closing_saturday: '20:00:00', + opening_sunday: '08:00:00', + closing_sunday: '20:00:00', + }, + { + id: 4, + slug: 'storsalen', + name: 'Storsalen', + opening_monday: '08:00:00', + closing_monday: '20:00:00', + opening_tuesday: '08:00:00', + closing_tuesday: '20:00:00', + opening_wednesday: '08:00:00', + closing_wednesday: '20:00:00', + opening_thursday: '08:00:00', + closing_thursday: '20:00:00', + opening_friday: '08:00:00', + closing_friday: '20:00:00', + opening_saturday: '08:00:00', + closing_saturday: '20:00:00', + opening_sunday: '08:00:00', + closing_sunday: '20:00:00', }, - { name: 'Edgar', opening: '2011-10-05T12:00:00.000Z', closing: '2011-10-05T17:00:00.000Z' }, - { name: 'Klubben', opening: '2011-10-05T16:00:00.000Z', closing: '2011-10-05T18:00:00.000Z' }, - { name: 'Storsalen', opening: '2011-10-05T14:00:00.000Z', closing: '2011-10-05T22:00:00.000Z' }, ], }, }; From 388bed04b6fee12a10d8aba3cf43cc92fb913dce Mon Sep 17 00:00:00 2001 From: eilifhl Date: Tue, 7 Oct 2025 21:06:11 +0200 Subject: [PATCH 16/61] Fix styling of tableCell border --- .../OpeningHours/OpeningHours.module.scss | 13 ++++++++----- .../src/Components/OpeningHours/OpeningHours.tsx | 4 ++-- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/frontend/src/Components/OpeningHours/OpeningHours.module.scss b/frontend/src/Components/OpeningHours/OpeningHours.module.scss index f238771f0..25bf61013 100644 --- a/frontend/src/Components/OpeningHours/OpeningHours.module.scss +++ b/frontend/src/Components/OpeningHours/OpeningHours.module.scss @@ -9,17 +9,20 @@ flex-direction: column; align-items: center; - .tableCell { - border-left: 0; - border-right: 0; - } - @include theme-dark { background-color: $black-2; color: $white; } } +.tableCell { + @include theme-dark { + border-left: 0; + border-right: 0; + } +} + .openingHoursText { margin: 0 0.2em; } + diff --git a/frontend/src/Components/OpeningHours/OpeningHours.tsx b/frontend/src/Components/OpeningHours/OpeningHours.tsx index 01d9f3925..6c4e9b57e 100644 --- a/frontend/src/Components/OpeningHours/OpeningHours.tsx +++ b/frontend/src/Components/OpeningHours/OpeningHours.tsx @@ -37,12 +37,12 @@ export function OpeningHours({ venues, isLoading, isError }: OpeningHoursProps) const closingTime = venue[`closing_${day}` as keyof VenueDto] as string; return ( - +

{venue.name}

- + Date: Tue, 7 Oct 2025 22:03:54 +0200 Subject: [PATCH 17/61] Venues with midnight opening, closing are closed --- backend/samfundet/view/general_views.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/backend/samfundet/view/general_views.py b/backend/samfundet/view/general_views.py index 2e440a688..8c32afcda 100644 --- a/backend/samfundet/view/general_views.py +++ b/backend/samfundet/view/general_views.py @@ -4,6 +4,7 @@ from __future__ import annotations from typing import Any +from datetime import time from itertools import chain from rest_framework import status @@ -115,13 +116,12 @@ class VenueView(ModelViewSet): @action(detail=False, methods=['get']) def open_venues(self, request: Request) -> Response: - now = timezone.now() - day_name = now.strftime('%A').lower() + day_name = timezone.now().strftime('%A').lower() - q = Q( + q = ~Q( **{ - f'opening_{day_name}__lte': now.time(), - f'closing_{day_name}__gte': now.time(), + f'opening_{day_name}': time(0, 0, 0), + f'closing_{day_name}': time(0, 0, 0), } ) From 1b2162037442e023f4b0fc6052b715cf33191fdd Mon Sep 17 00:00:00 2001 From: specter Date: Thu, 16 Oct 2025 20:48:37 +0200 Subject: [PATCH 18/61] Opening hours now display a descriptiv message when samf is in a closed period --- .../OpeningHours/OpeningHours.module.scss | 8 +++ .../Components/OpeningHours/OpeningHours.tsx | 67 ++++++++++++------- frontend/src/Components/OpeningHours/index.ts | 1 + frontend/src/Components/index.ts | 2 +- frontend/src/Pages/HomePage/HomePage.tsx | 2 + frontend/src/api.ts | 1 + 6 files changed, 57 insertions(+), 24 deletions(-) diff --git a/frontend/src/Components/OpeningHours/OpeningHours.module.scss b/frontend/src/Components/OpeningHours/OpeningHours.module.scss index 25bf61013..f0b7d6f8a 100644 --- a/frontend/src/Components/OpeningHours/OpeningHours.module.scss +++ b/frontend/src/Components/OpeningHours/OpeningHours.module.scss @@ -26,3 +26,11 @@ margin: 0 0.2em; } +.closedBox { + border: 2px $red_samf solid; + border-radius: 10px; + padding: 20px; + margin-bottom: 2rem; + margin-top: 1rem; +} + diff --git a/frontend/src/Components/OpeningHours/OpeningHours.tsx b/frontend/src/Components/OpeningHours/OpeningHours.tsx index 6c4e9b57e..e20c802b9 100644 --- a/frontend/src/Components/OpeningHours/OpeningHours.tsx +++ b/frontend/src/Components/OpeningHours/OpeningHours.tsx @@ -1,9 +1,12 @@ +import { useEffect, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { TimeDuration } from '~/Components'; import { Link } from '~/Components/Link/Link'; import { Text } from '~/Components/Text/Text'; +import { getClosedPeriods } from '~/api'; import type { VenueDto } from '~/dto'; import { KEY } from '~/i18n/constants'; +import { dbT } from '~/utils'; import styles from './OpeningHours.module.scss'; type OpeningHoursProps = { @@ -14,6 +17,21 @@ type OpeningHoursProps = { export function OpeningHours({ venues, isLoading, isError }: OpeningHoursProps) { const { t } = useTranslation(); + const [isClosed, setIsClosed] = useState(false); + const [closedText, setClosedText] = useState('Samf is closed'); + + useEffect(() => { + getClosedPeriods().then((periods) => { + const now = new Date(); + for (const period of periods) { + if (new Date(period.start_dt) < now && now < new Date(period.end_dt)) { + setIsClosed(true); + setClosedText(dbT(period, 'description')); + break; + } + } + }); + }); if (isLoading) { return {t(KEY.common_loading)}; @@ -25,34 +43,37 @@ export function OpeningHours({ venues, isLoading, isError }: OpeningHoursProps) const today = new Date().toISOString().split('T')[0]; const day = new Date().toLocaleDateString('en-US', { weekday: 'long' }).toLowerCase(); - return (
{t(KEY.common_opening_hours)} - - {venues.map((venue) => { - const openingTime = venue[`opening_${day}` as keyof VenueDto] as string; - const closingTime = venue[`closing_${day}` as keyof VenueDto] as string; - return ( - - - - - ); - })} -
- -

{venue.name}

- -
- -
+ {isClosed ? ( +
{closedText}
+ ) : ( + + {venues.map((venue) => { + const openingTime = venue[`opening_${day}` as keyof VenueDto] as string; + const closingTime = venue[`closing_${day}` as keyof VenueDto] as string; + return ( + + + + + ); + })} +
+ +

{venue.name}

+ +
+ +
+ )}
); } diff --git a/frontend/src/Components/OpeningHours/index.ts b/frontend/src/Components/OpeningHours/index.ts index beb1448d2..37a62d505 100644 --- a/frontend/src/Components/OpeningHours/index.ts +++ b/frontend/src/Components/OpeningHours/index.ts @@ -1 +1,2 @@ export { OpeningHours } from './OpeningHours'; +export { OpeningHoursContainer } from './OpeningHoursContainer'; diff --git a/frontend/src/Components/index.ts b/frontend/src/Components/index.ts index 134bf2db1..b42396548 100644 --- a/frontend/src/Components/index.ts +++ b/frontend/src/Components/index.ts @@ -48,7 +48,7 @@ export { Navbar } from './Navbar'; export { NotificationBadge } from './NotificationBadge'; export { NumberInput } from './NumberInput'; export { OccupiedForm, OccupiedFormModal } from './OccupiedForm'; -export { OpeningHours } from './OpeningHours'; +export { OpeningHours, OpeningHoursContainer } from './OpeningHours'; export { Page } from './Page'; export { PagedPagination } from './Pagination'; export { PermissionRoute } from './PermissionRoute'; diff --git a/frontend/src/Pages/HomePage/HomePage.tsx b/frontend/src/Pages/HomePage/HomePage.tsx index cb675d67a..6afbadbce 100644 --- a/frontend/src/Pages/HomePage/HomePage.tsx +++ b/frontend/src/Pages/HomePage/HomePage.tsx @@ -1,6 +1,7 @@ import { useEffect, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { toast } from 'react-toastify'; +import { OpeningHoursContainer } from '~/Components'; import { EventCarousel, LargeCard } from '~/Pages/HomePage/components'; import { getHomeData } from '~/api'; import type { HomePageDto, HomePageElementDto } from '~/dto'; @@ -51,6 +52,7 @@ export function HomePage() { return ( <> +
{/**/} {isLoading && skeleton} diff --git a/frontend/src/api.ts b/frontend/src/api.ts index 1b4982be9..8fab91687 100644 --- a/frontend/src/api.ts +++ b/frontend/src/api.ts @@ -492,6 +492,7 @@ export async function putGang(id: string | number, data: Partial): Prom } export async function getClosedPeriods(): Promise { + // start_dt og end_dt er strings ikke Date -_- const url = BACKEND_DOMAIN + ROUTES.backend.samfundet__closedperiods_list; const response = await axios.get(url, { withCredentials: true }); return response.data; From 1452dcf72e3e5760a0263716dd899f7268332230 Mon Sep 17 00:00:00 2001 From: specter Date: Thu, 16 Oct 2025 20:56:45 +0200 Subject: [PATCH 19/61] changed text used from description to message --- frontend/src/Components/OpeningHours/OpeningHours.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/Components/OpeningHours/OpeningHours.tsx b/frontend/src/Components/OpeningHours/OpeningHours.tsx index e20c802b9..6148f3088 100644 --- a/frontend/src/Components/OpeningHours/OpeningHours.tsx +++ b/frontend/src/Components/OpeningHours/OpeningHours.tsx @@ -26,7 +26,7 @@ export function OpeningHours({ venues, isLoading, isError }: OpeningHoursProps) for (const period of periods) { if (new Date(period.start_dt) < now && now < new Date(period.end_dt)) { setIsClosed(true); - setClosedText(dbT(period, 'description')); + setClosedText(dbT(period, 'message')); break; } } From 9da6fd786c0c6302fe1a8ff0e6244dff21f8ffad Mon Sep 17 00:00:00 2001 From: specter Date: Tue, 21 Oct 2025 19:59:22 +0200 Subject: [PATCH 20/61] work in progress --- .../ClosedPeriodFormAdminPage.tsx | 19 ++----------------- 1 file changed, 2 insertions(+), 17 deletions(-) diff --git a/frontend/src/PagesAdmin/ClosedPeriodFormAdminPage/ClosedPeriodFormAdminPage.tsx b/frontend/src/PagesAdmin/ClosedPeriodFormAdminPage/ClosedPeriodFormAdminPage.tsx index caaf9f9e2..9af154565 100644 --- a/frontend/src/PagesAdmin/ClosedPeriodFormAdminPage/ClosedPeriodFormAdminPage.tsx +++ b/frontend/src/PagesAdmin/ClosedPeriodFormAdminPage/ClosedPeriodFormAdminPage.tsx @@ -16,8 +16,6 @@ import styles from './ClosedPeriodFormAdminPage.module.scss'; type formType = { message_no: string; message_en: string; - description_no: string; - description_en: string; start_dt: Date; end_dt: Date; }; @@ -46,11 +44,10 @@ export function ClosedPeriodFormAdminPage() { getClosedPeriod(id) .then((data) => { // setClosedPeriod(data); For posting + console.log(data) setInitialData({ message_no: data.message_no, message_en: data.message_en, - description_no: data.description_no, - description_en: data.description_en, start_dt: new Date(data.start_dt), end_dt: new Date(data.end_dt), }); @@ -67,6 +64,7 @@ export function ClosedPeriodFormAdminPage() { }, [id]); function handleOnSubmit(data: formType) { + // handle invalid dates, handle conflicting dates, handle short messages if (id !== undefined) { // TODO patch data } else { @@ -77,7 +75,6 @@ export function ClosedPeriodFormAdminPage() { } const labelMessage = `${t(KEY.common_message)} under '${t(KEY.common_opening_hours)}'`; - const labelDescription = `${t(KEY.common_description)} under '${t(KEY.common_whatsup)}'`; const title = id ? t(KEY.admin_closed_period_edit_period) : t(KEY.admin_closed_period_new_period); useTitle(title); @@ -88,18 +85,6 @@ export function ClosedPeriodFormAdminPage() {
-
- - -
From fd87ed68dc7649724af9f77bb29c4e7b4c2eb42d Mon Sep 17 00:00:00 2001 From: specter Date: Tue, 21 Oct 2025 21:07:05 +0200 Subject: [PATCH 21/61] ... --- backend/samfundet/models/general.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/backend/samfundet/models/general.py b/backend/samfundet/models/general.py index 84f9461ca..203498420 100644 --- a/backend/samfundet/models/general.py +++ b/backend/samfundet/models/general.py @@ -255,11 +255,11 @@ def __str__(self) -> str: class ClosedPeriod(CustomBaseModel): - message_nb = models.TextField(blank=True, null=True, verbose_name='Melding (norsk)') + message_no = models.TextField(blank=True, null=True, verbose_name='Melding (norsk)') message_en = models.TextField(blank=True, null=True, verbose_name='Melding (engelsk)') - description_nb = models.TextField(blank=True, null=True, verbose_name='Beskrivelse (norsk)') - description_en = models.TextField(blank=True, null=True, verbose_name='Beskrivelse (engelsk)') + #description_no = models.TextField(blank=True, null=True, verbose_name='Beskrivelse (norsk)') + #description_en = models.TextField(blank=True, null=True, verbose_name='Beskrivelse (engelsk)') start_dt = models.DateField(blank=True, null=False, verbose_name='Start dato') end_dt = models.DateField(blank=True, null=False, verbose_name='Slutt dato') @@ -269,7 +269,7 @@ class Meta: verbose_name_plural = 'ClosedPeriods' def __str__(self) -> str: - return f'{self.message_nb} {self.start_dt}-{self.end_dt}' + return f'{self.message_no} {self.start_dt}-{self.end_dt}' # GANGS ### From 5b2cf308d0c5e0ce517088468cfcc85ba6809cb4 Mon Sep 17 00:00:00 2001 From: specter Date: Tue, 21 Oct 2025 21:24:18 +0200 Subject: [PATCH 22/61] renamed message_nb to message_no for consistancy. removed desvription as currently not in use, and not expected to be used --- ...ve_closedperiod_description_en_and_more.py | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 backend/samfundet/migrations/0002_remove_closedperiod_description_en_and_more.py diff --git a/backend/samfundet/migrations/0002_remove_closedperiod_description_en_and_more.py b/backend/samfundet/migrations/0002_remove_closedperiod_description_en_and_more.py new file mode 100644 index 000000000..821b7f62f --- /dev/null +++ b/backend/samfundet/migrations/0002_remove_closedperiod_description_en_and_more.py @@ -0,0 +1,30 @@ +# Generated by Django 5.1.9 on 2025-10-21 19:19 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('samfundet', '0001_initial'), + ] + + operations = [ + migrations.RemoveField( + model_name='closedperiod', + name='description_en', + ), + migrations.RemoveField( + model_name='closedperiod', + name='description_nb', + ), + migrations.RemoveField( + model_name='closedperiod', + name='message_nb', + ), + migrations.AddField( + model_name='closedperiod', + name='message_no', + field=models.TextField(blank=True, null=True, verbose_name='Melding (norsk)'), + ), + ] From ab0969bc91feef3feea0ef62822636254e49b6fc Mon Sep 17 00:00:00 2001 From: specter Date: Tue, 21 Oct 2025 21:59:26 +0200 Subject: [PATCH 23/61] begynnelse av data sjekk --- .../ClosedPeriodFormAdminPage.tsx | 27 ++++++++++++++++--- frontend/src/dto.ts | 4 +-- 2 files changed, 25 insertions(+), 6 deletions(-) diff --git a/frontend/src/PagesAdmin/ClosedPeriodFormAdminPage/ClosedPeriodFormAdminPage.tsx b/frontend/src/PagesAdmin/ClosedPeriodFormAdminPage/ClosedPeriodFormAdminPage.tsx index 9af154565..39abef6da 100644 --- a/frontend/src/PagesAdmin/ClosedPeriodFormAdminPage/ClosedPeriodFormAdminPage.tsx +++ b/frontend/src/PagesAdmin/ClosedPeriodFormAdminPage/ClosedPeriodFormAdminPage.tsx @@ -16,8 +16,8 @@ import styles from './ClosedPeriodFormAdminPage.module.scss'; type formType = { message_no: string; message_en: string; - start_dt: Date; - end_dt: Date; + start_dt: string; + end_dt: string; }; export function ClosedPeriodFormAdminPage() { @@ -48,8 +48,8 @@ export function ClosedPeriodFormAdminPage() { setInitialData({ message_no: data.message_no, message_en: data.message_en, - start_dt: new Date(data.start_dt), - end_dt: new Date(data.end_dt), + start_dt: data.start_dt, + end_dt: data.end_dt, }); setShowSpinner(false); }) @@ -65,6 +65,25 @@ export function ClosedPeriodFormAdminPage() { function handleOnSubmit(data: formType) { // handle invalid dates, handle conflicting dates, handle short messages + const start_dt = new Date(data.start_dt) + const end_dt = new Date(data.end_dt) + const now = new Date() + + if (end_dt === start_dt) { + alert("end is equal to start, please set it to be more") + return + } + if (end_dt > start_dt) { + alert("end is less then start") + return + } + if (data.message_no.length <= 4) { + alert("message norsk må være lengere") + } + if (data.message_en.length <= 4) { + alert("message engelsk må være lengere") + } + if (id !== undefined) { // TODO patch data } else { diff --git a/frontend/src/dto.ts b/frontend/src/dto.ts index 9ba1c2def..45da7ed74 100644 --- a/frontend/src/dto.ts +++ b/frontend/src/dto.ts @@ -358,8 +358,8 @@ export type ClosedPeriodDto = { description_no: string; message_en: string; description_en: string; - start_dt: Date; - end_dt: Date; + start_dt: string; + end_dt: string; }; export type TagDto = { From 0f257d0ae79a54c83d8d543c6321311dc9157135 Mon Sep 17 00:00:00 2001 From: specter Date: Tue, 28 Oct 2025 21:30:25 +0100 Subject: [PATCH 24/61] You can now safely post closedPeriods in the admin panal, aswell as it being shown as closed instead of opening hours under opening hours --- .../ClosedPeriodFormAdminPage.tsx | 52 ++++++++----------- frontend/src/api.ts | 2 +- frontend/src/dto.ts | 2 - 3 files changed, 23 insertions(+), 33 deletions(-) diff --git a/frontend/src/PagesAdmin/ClosedPeriodFormAdminPage/ClosedPeriodFormAdminPage.tsx b/frontend/src/PagesAdmin/ClosedPeriodFormAdminPage/ClosedPeriodFormAdminPage.tsx index 39abef6da..e048b319a 100644 --- a/frontend/src/PagesAdmin/ClosedPeriodFormAdminPage/ClosedPeriodFormAdminPage.tsx +++ b/frontend/src/PagesAdmin/ClosedPeriodFormAdminPage/ClosedPeriodFormAdminPage.tsx @@ -5,13 +5,14 @@ import { useParams } from 'react-router'; import { toast } from 'react-toastify'; import { SamfForm } from '~/Forms/SamfForm'; import { SamfFormField } from '~/Forms/SamfFormField'; -import { getClosedPeriod } from '~/api'; +import { getClosedPeriod, postClosedPeriod, putClosedPeriod } from '~/api'; import { useCustomNavigate, useTitle } from '~/hooks'; import { STATUS } from '~/http_status_codes'; import { KEY } from '~/i18n/constants'; import { ROUTES } from '~/routes'; import { AdminPageLayout } from '../AdminPageLayout/AdminPageLayout'; import styles from './ClosedPeriodFormAdminPage.module.scss'; +import { reverse } from '~/named-urls'; type formType = { message_no: string; @@ -31,6 +32,8 @@ export function ClosedPeriodFormAdminPage() { // If form has a id, check if it exists, and then load that item. const { id } = useParams(); + const min_length_message = 10; + // Stuff to do on first render. // TODO add permissions on render // biome-ignore lint/correctness/useExhaustiveDependencies: t and navigate do not need to be in deplist @@ -64,33 +67,22 @@ export function ClosedPeriodFormAdminPage() { }, [id]); function handleOnSubmit(data: formType) { - // handle invalid dates, handle conflicting dates, handle short messages - const start_dt = new Date(data.start_dt) - const end_dt = new Date(data.end_dt) - const now = new Date() - - if (end_dt === start_dt) { - alert("end is equal to start, please set it to be more") - return - } - if (end_dt > start_dt) { - alert("end is less then start") - return - } - if (data.message_no.length <= 4) { - alert("message norsk må være lengere") - } - if (data.message_en.length <= 4) { - alert("message engelsk må være lengere") - } - if (id !== undefined) { - // TODO patch data + putClosedPeriod(id, data) + .then() + .catch(err => { + alert(err) + }) } else { - // TODO post data + postClosedPeriod(data) + .then(() => { + toast.success(t(KEY.common_creation_successful)) + navigate({url: reverse({pattern: ROUTES.frontend.admin_closed})}) + }) + .catch(() => { + toast.error(t(KEY.common_something_went_wrong)) + }) } - alert('TODO Submit'); - console.log(JSON.stringify(data)); } const labelMessage = `${t(KEY.common_message)} under '${t(KEY.common_opening_hours)}'`; @@ -99,14 +91,14 @@ export function ClosedPeriodFormAdminPage() { return ( - + onSubmit={handleOnSubmit} initialData={initialData}>
- - + state.message_no.length > min_length_message} field="message_no" required={true} type="text_long" label={`${labelMessage} (${t(KEY.common_norwegian)})`} /> + state.message_en.length > min_length_message} field="message_en" required={true} type="text_long" label={`${labelMessage} (${t(KEY.common_english)})`} />
- - + state.end_dt ? new Date(state.start_dt) <= new Date(state.end_dt) : true} field="start_dt" type="date" label={`${t(KEY.start_time)}`} /> + state.start_dt ? new Date(state.start_dt) <= new Date(state.end_dt) : true} field="end_dt" type="date" label={`${t(KEY.end_time)}`} />
diff --git a/frontend/src/api.ts b/frontend/src/api.ts index 8fab91687..b62329072 100644 --- a/frontend/src/api.ts +++ b/frontend/src/api.ts @@ -512,7 +512,7 @@ export async function putClosedPeriod(id: string | number, data: Partial { +export async function postClosedPeriod(data: Partial): Promise { const url = BACKEND_DOMAIN + ROUTES.backend.samfundet__closedperiods_list; const response = await axios.post(url, data, { withCredentials: true }); return response.data; diff --git a/frontend/src/dto.ts b/frontend/src/dto.ts index 45da7ed74..387ac46c6 100644 --- a/frontend/src/dto.ts +++ b/frontend/src/dto.ts @@ -355,9 +355,7 @@ export type GangSectionDto = { export type ClosedPeriodDto = { id: number; message_no: string; - description_no: string; message_en: string; - description_en: string; start_dt: string; end_dt: string; }; From 4509f482d60330633f69b7f75b52325ee8050625 Mon Sep 17 00:00:00 2001 From: specter Date: Tue, 28 Oct 2025 22:01:44 +0100 Subject: [PATCH 25/61] added a checkbox for making samf closed, needs to be implemented in more places then just openinghours as it uses the globalcontext --- .../Components/OpeningHours/OpeningHours.tsx | 7 +++++++ .../AdminPageLayout/AdminPageLayout.module.scss | 1 + .../ClosedPeriodAdminPage.module.scss | 9 +++++++++ .../ClosedPeriodAdminPage.tsx | 17 ++++++++++++----- frontend/src/api.ts | 1 - frontend/src/context/GlobalContextProvider.tsx | 14 ++++++++++++++ 6 files changed, 43 insertions(+), 6 deletions(-) diff --git a/frontend/src/Components/OpeningHours/OpeningHours.tsx b/frontend/src/Components/OpeningHours/OpeningHours.tsx index 6148f3088..103a744b1 100644 --- a/frontend/src/Components/OpeningHours/OpeningHours.tsx +++ b/frontend/src/Components/OpeningHours/OpeningHours.tsx @@ -7,6 +7,7 @@ import { getClosedPeriods } from '~/api'; import type { VenueDto } from '~/dto'; import { KEY } from '~/i18n/constants'; import { dbT } from '~/utils'; +import { useGlobalContext } from '~/context/GlobalContextProvider'; import styles from './OpeningHours.module.scss'; type OpeningHoursProps = { @@ -19,8 +20,14 @@ export function OpeningHours({ venues, isLoading, isError }: OpeningHoursProps) const { t } = useTranslation(); const [isClosed, setIsClosed] = useState(false); const [closedText, setClosedText] = useState('Samf is closed'); + const globalContext = useGlobalContext() useEffect(() => { + if (globalContext.isClosed) { + setIsClosed(true) + setClosedText("Samfundet is closed"); + return + } getClosedPeriods().then((periods) => { const now = new Date(); for (const period of periods) { diff --git a/frontend/src/PagesAdmin/AdminPageLayout/AdminPageLayout.module.scss b/frontend/src/PagesAdmin/AdminPageLayout/AdminPageLayout.module.scss index 92b3009ab..eb91dd4b8 100644 --- a/frontend/src/PagesAdmin/AdminPageLayout/AdminPageLayout.module.scss +++ b/frontend/src/PagesAdmin/AdminPageLayout/AdminPageLayout.module.scss @@ -36,6 +36,7 @@ $header-bg-dark: #111111; flex-direction: row; padding-bottom: 1.5em; gap: 1em; + align-items: center; } .spinner_container { diff --git a/frontend/src/PagesAdmin/ClosedPeriodAdminPage/ClosedPeriodAdminPage.module.scss b/frontend/src/PagesAdmin/ClosedPeriodAdminPage/ClosedPeriodAdminPage.module.scss index 2a8635da1..e208fcd75 100644 --- a/frontend/src/PagesAdmin/ClosedPeriodAdminPage/ClosedPeriodAdminPage.module.scss +++ b/frontend/src/PagesAdmin/ClosedPeriodAdminPage/ClosedPeriodAdminPage.module.scss @@ -1,3 +1,12 @@ @use 'src/mixins' as *; @use 'src/constants' as *; + +.admin_closed_switch { + display: flex; + align-items: center; + padding: 5px; + gap: 10px; + border-radius: 10px; + border: $red-samf 2px solid; +} diff --git a/frontend/src/PagesAdmin/ClosedPeriodAdminPage/ClosedPeriodAdminPage.tsx b/frontend/src/PagesAdmin/ClosedPeriodAdminPage/ClosedPeriodAdminPage.tsx index 9c20e233b..37b097ba6 100644 --- a/frontend/src/PagesAdmin/ClosedPeriodAdminPage/ClosedPeriodAdminPage.tsx +++ b/frontend/src/PagesAdmin/ClosedPeriodAdminPage/ClosedPeriodAdminPage.tsx @@ -1,7 +1,7 @@ import { useCallback, useEffect, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { toast } from 'react-toastify'; -import { Button, TimeDisplay } from '~/Components'; +import { Button, TimeDisplay, ToggleSwitch } from '~/Components'; import { Table } from '~/Components/Table'; import { deleteClosedPeriod, getClosedPeriods } from '~/api'; import type { ClosedPeriodDto } from '~/dto'; @@ -11,11 +11,13 @@ import { reverse } from '~/named-urls'; import { ROUTES } from '~/routes'; import { AdminPageLayout } from '../AdminPageLayout/AdminPageLayout'; import styles from './ClosedPeriodAdminPage.module.scss'; +import { useGlobalContext } from '~/context/GlobalContextProvider'; export function ClosedPeriodAdminPage() { const [closedPeriods, setClosedPeriods] = useState([]); const [showSpinner, setShowSpinner] = useState(true); const { t } = useTranslation(); + const globalContext = useGlobalContext(); useTitle(t(KEY.command_menu_shortcut_closed)); const getAllClosedPeriods = useCallback(() => { @@ -51,9 +53,15 @@ export function ClosedPeriodAdminPage() { } const header = ( - + <> + + + Admin Closed + globalContext.toggleIsClosed()}/> + + ); const backendUrl = ROUTES.backend.admin__samfundet_closedperiod_changelist; @@ -76,7 +84,6 @@ export function ClosedPeriodAdminPage() { data={closedPeriods.map((element) => ({ cells: [ element.message_no, - element.description_no, { content: }, { content: }, { diff --git a/frontend/src/api.ts b/frontend/src/api.ts index b62329072..77d5318b8 100644 --- a/frontend/src/api.ts +++ b/frontend/src/api.ts @@ -492,7 +492,6 @@ export async function putGang(id: string | number, data: Partial): Prom } export async function getClosedPeriods(): Promise { - // start_dt og end_dt er strings ikke Date -_- const url = BACKEND_DOMAIN + ROUTES.backend.samfundet__closedperiods_list; const response = await axios.get(url, { withCredentials: true }); return response.data; diff --git a/frontend/src/context/GlobalContextProvider.tsx b/frontend/src/context/GlobalContextProvider.tsx index bf387cdc2..2700351db 100644 --- a/frontend/src/context/GlobalContextProvider.tsx +++ b/frontend/src/context/GlobalContextProvider.tsx @@ -30,6 +30,10 @@ type GlobalContextProps = { setIsMobileNavigation: SetState; keyValues: KeyValueMap; + + // AdminToggels + isClosed: boolean; + toggleIsClosed: () => void; }; /** @@ -76,6 +80,8 @@ export function GlobalContextProvider({ children, enabled = true }: GlobalContex const [mirrorDimension, setMirrorDimension] = useState(false); const { isMouseTrail, setIsMouseTrail, toggleMouseTrail } = useMouseTrail(); + const [isClosed, setIsClosed] = useState(false); + // =================================== // // Effects // // =================================== // @@ -86,6 +92,7 @@ export function GlobalContextProvider({ children, enabled = true }: GlobalContex setMirrorDimension(user.user_preference.mirror_dimension); }, [user, enabled]); + // Stuff to do on first render. useEffect(() => { if (!enabled) return; @@ -140,6 +147,11 @@ export function GlobalContextProvider({ children, enabled = true }: GlobalContex return toggledValue; } + + function toggleIsClosed() { + setIsClosed(v => !v) + } + // =================================== // // Finalize context with values // // =================================== // @@ -158,6 +170,8 @@ export function GlobalContextProvider({ children, enabled = true }: GlobalContex toggleMouseTrail, toggleMirrorDimension, keyValues, + isClosed, + toggleIsClosed }; return {children}; From d162b0afd8a72836f4ee23fe0670a742f25440db Mon Sep 17 00:00:00 2001 From: specter Date: Thu, 30 Oct 2025 19:30:12 +0100 Subject: [PATCH 26/61] should be fixed now --- backend/samfundet/models/general.py | 4 ++-- frontend/src/Components/OpeningHours/OpeningHours.tsx | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/backend/samfundet/models/general.py b/backend/samfundet/models/general.py index 203498420..1ad080819 100644 --- a/backend/samfundet/models/general.py +++ b/backend/samfundet/models/general.py @@ -255,7 +255,7 @@ def __str__(self) -> str: class ClosedPeriod(CustomBaseModel): - message_no = models.TextField(blank=True, null=True, verbose_name='Melding (norsk)') + message_nb = models.TextField(blank=True, null=True, verbose_name='Melding (norsk)') message_en = models.TextField(blank=True, null=True, verbose_name='Melding (engelsk)') #description_no = models.TextField(blank=True, null=True, verbose_name='Beskrivelse (norsk)') @@ -269,7 +269,7 @@ class Meta: verbose_name_plural = 'ClosedPeriods' def __str__(self) -> str: - return f'{self.message_no} {self.start_dt}-{self.end_dt}' + return f'{self.message_nb} {self.start_dt}-{self.end_dt}' # GANGS ### diff --git a/frontend/src/Components/OpeningHours/OpeningHours.tsx b/frontend/src/Components/OpeningHours/OpeningHours.tsx index 103a744b1..4f2f58246 100644 --- a/frontend/src/Components/OpeningHours/OpeningHours.tsx +++ b/frontend/src/Components/OpeningHours/OpeningHours.tsx @@ -33,6 +33,7 @@ export function OpeningHours({ venues, isLoading, isError }: OpeningHoursProps) for (const period of periods) { if (new Date(period.start_dt) < now && now < new Date(period.end_dt)) { setIsClosed(true); + console.log(period) setClosedText(dbT(period, 'message')); break; } From b54fbefc96e23822d80b31219c75e9fb6cc00ffc Mon Sep 17 00:00:00 2001 From: specter Date: Thu, 30 Oct 2025 19:31:30 +0100 Subject: [PATCH 27/61] ok now its good --- frontend/src/dto.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/dto.ts b/frontend/src/dto.ts index 387ac46c6..ef739a468 100644 --- a/frontend/src/dto.ts +++ b/frontend/src/dto.ts @@ -354,7 +354,7 @@ export type GangSectionDto = { export type ClosedPeriodDto = { id: number; - message_no: string; + message_nb: string; message_en: string; start_dt: string; end_dt: string; From 5aa8e0c9a03a55d1411e577dd83d96d41eed3587 Mon Sep 17 00:00:00 2001 From: specter Date: Thu, 30 Oct 2025 19:34:20 +0100 Subject: [PATCH 28/61] changed closedPeriodadmin to match --- .../ClosedPeriodAdminPage/ClosedPeriodAdminPage.tsx | 4 ++-- .../ClosedPeriodFormAdminPage/ClosedPeriodFormAdminPage.tsx | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/frontend/src/PagesAdmin/ClosedPeriodAdminPage/ClosedPeriodAdminPage.tsx b/frontend/src/PagesAdmin/ClosedPeriodAdminPage/ClosedPeriodAdminPage.tsx index 37b097ba6..2e3f021da 100644 --- a/frontend/src/PagesAdmin/ClosedPeriodAdminPage/ClosedPeriodAdminPage.tsx +++ b/frontend/src/PagesAdmin/ClosedPeriodAdminPage/ClosedPeriodAdminPage.tsx @@ -83,7 +83,7 @@ export function ClosedPeriodAdminPage() { ]} data={closedPeriods.map((element) => ({ cells: [ - element.message_no, + element.message_nb, { content: }, { content: }, { @@ -105,7 +105,7 @@ export function ClosedPeriodAdminPage() { display="block" className={styles.smallButtons} onClick={() => { - if (window.confirm(`${t(KEY.form_confirm)} ${t(KEY.common_delete)} ${element.message_no}`)) { + if (window.confirm(`${t(KEY.form_confirm)} ${t(KEY.common_delete)} ${element.message_nb}`)) { deleteSelectedEvent(element.id); } }} diff --git a/frontend/src/PagesAdmin/ClosedPeriodFormAdminPage/ClosedPeriodFormAdminPage.tsx b/frontend/src/PagesAdmin/ClosedPeriodFormAdminPage/ClosedPeriodFormAdminPage.tsx index e048b319a..a0b04a78e 100644 --- a/frontend/src/PagesAdmin/ClosedPeriodFormAdminPage/ClosedPeriodFormAdminPage.tsx +++ b/frontend/src/PagesAdmin/ClosedPeriodFormAdminPage/ClosedPeriodFormAdminPage.tsx @@ -15,7 +15,7 @@ import styles from './ClosedPeriodFormAdminPage.module.scss'; import { reverse } from '~/named-urls'; type formType = { - message_no: string; + message_nb: string; message_en: string; start_dt: string; end_dt: string; @@ -49,7 +49,7 @@ export function ClosedPeriodFormAdminPage() { // setClosedPeriod(data); For posting console.log(data) setInitialData({ - message_no: data.message_no, + message_nb: data.message_nb, message_en: data.message_en, start_dt: data.start_dt, end_dt: data.end_dt, @@ -93,7 +93,7 @@ export function ClosedPeriodFormAdminPage() { onSubmit={handleOnSubmit} initialData={initialData}>
- state.message_no.length > min_length_message} field="message_no" required={true} type="text_long" label={`${labelMessage} (${t(KEY.common_norwegian)})`} /> + state.message_nb.length > min_length_message} field="message_nb" required={true} type="text_long" label={`${labelMessage} (${t(KEY.common_norwegian)})`} /> state.message_en.length > min_length_message} field="message_en" required={true} type="text_long" label={`${labelMessage} (${t(KEY.common_english)})`} />
From b0edf9656ddef3f1045e2fb834f3fc4b1b75fc55 Mon Sep 17 00:00:00 2001 From: specter Date: Thu, 30 Oct 2025 20:33:29 +0100 Subject: [PATCH 29/61] added radio buttons for choosing default, forced open or forced closed in closed periods --- frontend/' | 125 ++++++++++++++++++ .../Components/OpeningHours/OpeningHours.tsx | 8 +- .../ClosedPeriodAdminPage.module.scss | 10 +- .../ClosedPeriodAdminPage.tsx | 27 +++- .../src/context/GlobalContextProvider.tsx | 13 +- 5 files changed, 163 insertions(+), 20 deletions(-) create mode 100644 frontend/' diff --git a/frontend/' b/frontend/' new file mode 100644 index 000000000..d09090710 --- /dev/null +++ b/frontend/' @@ -0,0 +1,125 @@ +import { useCallback, useEffect, useState } from 'react'; +import { useTranslation } from 'react-i18next'; +import { toast } from 'react-toastify'; +import { Button, TimeDisplay, ToggleSwitch } from '~/Components'; +import { Table } from '~/Components/Table'; +import { deleteClosedPeriod, getClosedPeriods } from '~/api'; +import type { ClosedPeriodDto } from '~/dto'; +import { useTitle } from '~/hooks'; +import { KEY } from '~/i18n/constants'; +import { reverse } from '~/named-urls'; +import { ROUTES } from '~/routes'; +import { AdminPageLayout } from '../AdminPageLayout/AdminPageLayout'; +import styles from './ClosedPeriodAdminPage.module.scss'; +import { useGlobalContext } from '~/context/GlobalContextProvider'; +import { dbT } from '~/utils'; + +export function ClosedPeriodAdminPage() { + const [closedPeriods, setClosedPeriods] = useState([]); + const [showSpinner, setShowSpinner] = useState(true); + const { t } = useTranslation(); + const globalContext = useGlobalContext(); + useTitle(t(KEY.command_menu_shortcut_closed)); + + const getAllClosedPeriods = useCallback(() => { + setShowSpinner(true); + getClosedPeriods() + .then((data) => { + setClosedPeriods(data); + setShowSpinner(false); + }) + .catch((error) => { + toast.error(t(KEY.common_something_went_wrong)); + console.error(error); + }); + }, [t]); + + // Stuff to do on first render. + // TODO add permissions on render + + useEffect(() => { + getAllClosedPeriods(); + }, [getAllClosedPeriods]); + + function deleteSelectedEvent(id: number) { + deleteClosedPeriod(id) + .then(() => { + getAllClosedPeriods(); + toast.success(t(KEY.common_delete_successful)); + }) + .catch((error) => { + toast.error(t(KEY.common_something_went_wrong)); + console.error(error); + }); + } + + const header = ( + <> + + + {dbT({"text_nb": "Stenging status", "text_en": "Closing status"}, "text")} + globalContext.toggleIsClosed()}/> + + + ); + const backendUrl = ROUTES.backend.admin__samfundet_closedperiod_changelist; + + return ( + +
+ ({ + cells: [ + element.message_nb, + { content: }, + { content: }, + { + content: ( +
+ + {' '} +
+ ), + }, + ], + }))} + /> + + + ); +} diff --git a/frontend/src/Components/OpeningHours/OpeningHours.tsx b/frontend/src/Components/OpeningHours/OpeningHours.tsx index 4f2f58246..8193c7d94 100644 --- a/frontend/src/Components/OpeningHours/OpeningHours.tsx +++ b/frontend/src/Components/OpeningHours/OpeningHours.tsx @@ -23,9 +23,11 @@ export function OpeningHours({ venues, isLoading, isError }: OpeningHoursProps) const globalContext = useGlobalContext() useEffect(() => { - if (globalContext.isClosed) { - setIsClosed(true) - setClosedText("Samfundet is closed"); + if (globalContext.isClosed !== "default") { + if (globalContext.isClosed === "closed") { + setIsClosed(true) + setClosedText("Samfundet is closed"); + } return } getClosedPeriods().then((periods) => { diff --git a/frontend/src/PagesAdmin/ClosedPeriodAdminPage/ClosedPeriodAdminPage.module.scss b/frontend/src/PagesAdmin/ClosedPeriodAdminPage/ClosedPeriodAdminPage.module.scss index e208fcd75..02ae74981 100644 --- a/frontend/src/PagesAdmin/ClosedPeriodAdminPage/ClosedPeriodAdminPage.module.scss +++ b/frontend/src/PagesAdmin/ClosedPeriodAdminPage/ClosedPeriodAdminPage.module.scss @@ -2,11 +2,13 @@ @use 'src/constants' as *; -.admin_closed_switch { +.admin_closed_override_container { display: flex; align-items: center; - padding: 5px; + justify-content: center; gap: 10px; - border-radius: 10px; - border: $red-samf 2px solid; +} + +.admin_closed_radio { + margin-top: 10px; } diff --git a/frontend/src/PagesAdmin/ClosedPeriodAdminPage/ClosedPeriodAdminPage.tsx b/frontend/src/PagesAdmin/ClosedPeriodAdminPage/ClosedPeriodAdminPage.tsx index 2e3f021da..2c59a0ed0 100644 --- a/frontend/src/PagesAdmin/ClosedPeriodAdminPage/ClosedPeriodAdminPage.tsx +++ b/frontend/src/PagesAdmin/ClosedPeriodAdminPage/ClosedPeriodAdminPage.tsx @@ -1,7 +1,7 @@ import { useCallback, useEffect, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { toast } from 'react-toastify'; -import { Button, TimeDisplay, ToggleSwitch } from '~/Components'; +import { Button, RadioButton, TimeDisplay, ToggleSwitch } from '~/Components'; import { Table } from '~/Components/Table'; import { deleteClosedPeriod, getClosedPeriods } from '~/api'; import type { ClosedPeriodDto } from '~/dto'; @@ -12,6 +12,7 @@ import { ROUTES } from '~/routes'; import { AdminPageLayout } from '../AdminPageLayout/AdminPageLayout'; import styles from './ClosedPeriodAdminPage.module.scss'; import { useGlobalContext } from '~/context/GlobalContextProvider'; +import { dbT } from '~/utils'; export function ClosedPeriodAdminPage() { const [closedPeriods, setClosedPeriods] = useState([]); @@ -57,9 +58,27 @@ export function ClosedPeriodAdminPage() { - - Admin Closed - globalContext.toggleIsClosed()}/> + + + {dbT({"text_nb": "Stenging status", "text_en": "Closing status"}, "text")} + + + + {dbT({"text_nb": "standar", "text_en": "default"}, "text")} + + + globalContext.setIsClosed("default")}/> + + + {dbT({"text_nb": "stengt", "text_en": "closed"}, "text")} + + globalContext.setIsClosed("closed")}/> + + + {dbT({"text_nb": "åpent", "text_en": "open"}, "text")} + + globalContext.setIsClosed("open")}/> + ); diff --git a/frontend/src/context/GlobalContextProvider.tsx b/frontend/src/context/GlobalContextProvider.tsx index 2700351db..f8bff93de 100644 --- a/frontend/src/context/GlobalContextProvider.tsx +++ b/frontend/src/context/GlobalContextProvider.tsx @@ -32,8 +32,8 @@ type GlobalContextProps = { keyValues: KeyValueMap; // AdminToggels - isClosed: boolean; - toggleIsClosed: () => void; + isClosed: string; + setIsClosed: React.Dispatch>; }; /** @@ -80,7 +80,7 @@ export function GlobalContextProvider({ children, enabled = true }: GlobalContex const [mirrorDimension, setMirrorDimension] = useState(false); const { isMouseTrail, setIsMouseTrail, toggleMouseTrail } = useMouseTrail(); - const [isClosed, setIsClosed] = useState(false); + const [isClosed, setIsClosed] = useState("default"); // =================================== // // Effects // @@ -147,11 +147,6 @@ export function GlobalContextProvider({ children, enabled = true }: GlobalContex return toggledValue; } - - function toggleIsClosed() { - setIsClosed(v => !v) - } - // =================================== // // Finalize context with values // // =================================== // @@ -171,7 +166,7 @@ export function GlobalContextProvider({ children, enabled = true }: GlobalContex toggleMirrorDimension, keyValues, isClosed, - toggleIsClosed + setIsClosed }; return {children}; From 833e732c51f98af5608693b8501790bc8f3de273 Mon Sep 17 00:00:00 2001 From: specter Date: Tue, 4 Nov 2025 21:08:53 +0100 Subject: [PATCH 30/61] biome fix --- .../Components/OpeningHours/OpeningHours.tsx | 16 +++--- .../ClosedPeriodAdminPage.tsx | 44 ++++++++-------- .../ClosedPeriodFormAdminPage.tsx | 50 ++++++++++++++----- .../src/context/GlobalContextProvider.tsx | 5 +- 4 files changed, 71 insertions(+), 44 deletions(-) diff --git a/frontend/src/Components/OpeningHours/OpeningHours.tsx b/frontend/src/Components/OpeningHours/OpeningHours.tsx index 8193c7d94..9c2a7f1ae 100644 --- a/frontend/src/Components/OpeningHours/OpeningHours.tsx +++ b/frontend/src/Components/OpeningHours/OpeningHours.tsx @@ -4,10 +4,10 @@ import { TimeDuration } from '~/Components'; import { Link } from '~/Components/Link/Link'; import { Text } from '~/Components/Text/Text'; import { getClosedPeriods } from '~/api'; +import { useGlobalContext } from '~/context/GlobalContextProvider'; import type { VenueDto } from '~/dto'; import { KEY } from '~/i18n/constants'; import { dbT } from '~/utils'; -import { useGlobalContext } from '~/context/GlobalContextProvider'; import styles from './OpeningHours.module.scss'; type OpeningHoursProps = { @@ -20,22 +20,22 @@ export function OpeningHours({ venues, isLoading, isError }: OpeningHoursProps) const { t } = useTranslation(); const [isClosed, setIsClosed] = useState(false); const [closedText, setClosedText] = useState('Samf is closed'); - const globalContext = useGlobalContext() + const globalContext = useGlobalContext(); useEffect(() => { - if (globalContext.isClosed !== "default") { - if (globalContext.isClosed === "closed") { - setIsClosed(true) - setClosedText("Samfundet is closed"); + if (globalContext.isClosed !== 'default') { + if (globalContext.isClosed === 'closed') { + setIsClosed(true); + setClosedText('Samfundet is closed'); } - return + return; } getClosedPeriods().then((periods) => { const now = new Date(); for (const period of periods) { if (new Date(period.start_dt) < now && now < new Date(period.end_dt)) { setIsClosed(true); - console.log(period) + console.log(period); setClosedText(dbT(period, 'message')); break; } diff --git a/frontend/src/PagesAdmin/ClosedPeriodAdminPage/ClosedPeriodAdminPage.tsx b/frontend/src/PagesAdmin/ClosedPeriodAdminPage/ClosedPeriodAdminPage.tsx index 2c59a0ed0..62f5c268b 100644 --- a/frontend/src/PagesAdmin/ClosedPeriodAdminPage/ClosedPeriodAdminPage.tsx +++ b/frontend/src/PagesAdmin/ClosedPeriodAdminPage/ClosedPeriodAdminPage.tsx @@ -1,18 +1,18 @@ import { useCallback, useEffect, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { toast } from 'react-toastify'; -import { Button, RadioButton, TimeDisplay, ToggleSwitch } from '~/Components'; +import { Button, RadioButton, TimeDisplay } from '~/Components'; import { Table } from '~/Components/Table'; import { deleteClosedPeriod, getClosedPeriods } from '~/api'; +import { useGlobalContext } from '~/context/GlobalContextProvider'; import type { ClosedPeriodDto } from '~/dto'; import { useTitle } from '~/hooks'; import { KEY } from '~/i18n/constants'; import { reverse } from '~/named-urls'; import { ROUTES } from '~/routes'; +import { dbT } from '~/utils'; import { AdminPageLayout } from '../AdminPageLayout/AdminPageLayout'; import styles from './ClosedPeriodAdminPage.module.scss'; -import { useGlobalContext } from '~/context/GlobalContextProvider'; -import { dbT } from '~/utils'; export function ClosedPeriodAdminPage() { const [closedPeriods, setClosedPeriods] = useState([]); @@ -59,26 +59,30 @@ export function ClosedPeriodAdminPage() { {t(KEY.admin_closed_period_new_period)} - - {dbT({"text_nb": "Stenging status", "text_en": "Closing status"}, "text")} - + + {dbT({ text_nb: 'Stenging status', text_en: 'Closing status' }, 'text')} + - - {dbT({"text_nb": "standar", "text_en": "default"}, "text")} - - - globalContext.setIsClosed("default")}/> - + {dbT({ text_nb: 'standar', text_en: 'default' }, 'text')} + + globalContext.setIsClosed('default')} + /> + - {dbT({"text_nb": "stengt", "text_en": "closed"}, "text")} - - globalContext.setIsClosed("closed")}/> - + {dbT({ text_nb: 'stengt', text_en: 'closed' }, 'text')} + + globalContext.setIsClosed('closed')} + /> + - {dbT({"text_nb": "åpent", "text_en": "open"}, "text")} - - globalContext.setIsClosed("open")}/> - + {dbT({ text_nb: 'åpent', text_en: 'open' }, 'text')} + + globalContext.setIsClosed('open')} /> + ); diff --git a/frontend/src/PagesAdmin/ClosedPeriodFormAdminPage/ClosedPeriodFormAdminPage.tsx b/frontend/src/PagesAdmin/ClosedPeriodFormAdminPage/ClosedPeriodFormAdminPage.tsx index a0b04a78e..99b48f9de 100644 --- a/frontend/src/PagesAdmin/ClosedPeriodFormAdminPage/ClosedPeriodFormAdminPage.tsx +++ b/frontend/src/PagesAdmin/ClosedPeriodFormAdminPage/ClosedPeriodFormAdminPage.tsx @@ -9,10 +9,10 @@ import { getClosedPeriod, postClosedPeriod, putClosedPeriod } from '~/api'; import { useCustomNavigate, useTitle } from '~/hooks'; 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 './ClosedPeriodFormAdminPage.module.scss'; -import { reverse } from '~/named-urls'; type formType = { message_nb: string; @@ -47,7 +47,7 @@ export function ClosedPeriodFormAdminPage() { getClosedPeriod(id) .then((data) => { // setClosedPeriod(data); For posting - console.log(data) + console.log(data); setInitialData({ message_nb: data.message_nb, message_en: data.message_en, @@ -70,18 +70,18 @@ export function ClosedPeriodFormAdminPage() { if (id !== undefined) { putClosedPeriod(id, data) .then() - .catch(err => { - alert(err) - }) + .catch((err) => { + alert(err); + }); } else { postClosedPeriod(data) .then(() => { - toast.success(t(KEY.common_creation_successful)) - navigate({url: reverse({pattern: ROUTES.frontend.admin_closed})}) + toast.success(t(KEY.common_creation_successful)); + navigate({ url: reverse({ pattern: ROUTES.frontend.admin_closed }) }); }) .catch(() => { - toast.error(t(KEY.common_something_went_wrong)) - }) + toast.error(t(KEY.common_something_went_wrong)); + }); } } @@ -93,12 +93,36 @@ export function ClosedPeriodFormAdminPage() { onSubmit={handleOnSubmit} initialData={initialData}>
- state.message_nb.length > min_length_message} field="message_nb" required={true} type="text_long" label={`${labelMessage} (${t(KEY.common_norwegian)})`} /> - state.message_en.length > min_length_message} field="message_en" required={true} type="text_long" label={`${labelMessage} (${t(KEY.common_english)})`} /> + state.message_nb.length > min_length_message} + field="message_nb" + required={true} + type="text_long" + label={`${labelMessage} (${t(KEY.common_norwegian)})`} + /> + state.message_en.length > min_length_message} + field="message_en" + required={true} + type="text_long" + label={`${labelMessage} (${t(KEY.common_english)})`} + />
- state.end_dt ? new Date(state.start_dt) <= new Date(state.end_dt) : true} field="start_dt" type="date" label={`${t(KEY.start_time)}`} /> - state.start_dt ? new Date(state.start_dt) <= new Date(state.end_dt) : true} field="end_dt" type="date" label={`${t(KEY.end_time)}`} /> + (state.end_dt ? new Date(state.start_dt) <= new Date(state.end_dt) : true)} + field="start_dt" + type="date" + label={`${t(KEY.start_time)}`} + /> + + state.start_dt ? new Date(state.start_dt) <= new Date(state.end_dt) : true + } + field="end_dt" + type="date" + label={`${t(KEY.end_time)}`} + />
diff --git a/frontend/src/context/GlobalContextProvider.tsx b/frontend/src/context/GlobalContextProvider.tsx index f8bff93de..e831d2c95 100644 --- a/frontend/src/context/GlobalContextProvider.tsx +++ b/frontend/src/context/GlobalContextProvider.tsx @@ -80,7 +80,7 @@ export function GlobalContextProvider({ children, enabled = true }: GlobalContex const [mirrorDimension, setMirrorDimension] = useState(false); const { isMouseTrail, setIsMouseTrail, toggleMouseTrail } = useMouseTrail(); - const [isClosed, setIsClosed] = useState("default"); + const [isClosed, setIsClosed] = useState('default'); // =================================== // // Effects // @@ -92,7 +92,6 @@ export function GlobalContextProvider({ children, enabled = true }: GlobalContex setMirrorDimension(user.user_preference.mirror_dimension); }, [user, enabled]); - // Stuff to do on first render. useEffect(() => { if (!enabled) return; @@ -166,7 +165,7 @@ export function GlobalContextProvider({ children, enabled = true }: GlobalContex toggleMirrorDimension, keyValues, isClosed, - setIsClosed + setIsClosed, }; return {children}; From 5a09679db0eadebe8904ca7f279046709acb8375 Mon Sep 17 00:00:00 2001 From: specter Date: Tue, 4 Nov 2025 21:19:34 +0100 Subject: [PATCH 31/61] ruff reformat --- backend/samfundet/models/general.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/backend/samfundet/models/general.py b/backend/samfundet/models/general.py index 1ad080819..f85a12df2 100644 --- a/backend/samfundet/models/general.py +++ b/backend/samfundet/models/general.py @@ -258,8 +258,8 @@ class ClosedPeriod(CustomBaseModel): message_nb = models.TextField(blank=True, null=True, verbose_name='Melding (norsk)') message_en = models.TextField(blank=True, null=True, verbose_name='Melding (engelsk)') - #description_no = models.TextField(blank=True, null=True, verbose_name='Beskrivelse (norsk)') - #description_en = models.TextField(blank=True, null=True, verbose_name='Beskrivelse (engelsk)') + # description_no = models.TextField(blank=True, null=True, verbose_name='Beskrivelse (norsk)') + # description_en = models.TextField(blank=True, null=True, verbose_name='Beskrivelse (engelsk)') start_dt = models.DateField(blank=True, null=False, verbose_name='Start dato') end_dt = models.DateField(blank=True, null=False, verbose_name='Slutt dato') From 5f5b7af56c258ac26e9a8ba0a07c2428a0e7740d Mon Sep 17 00:00:00 2001 From: specter Date: Tue, 4 Nov 2025 21:26:29 +0100 Subject: [PATCH 32/61] make migration problems that now should be fixed --- ...ename_message_no_closedperiod_message_nb.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 backend/samfundet/migrations/0003_rename_message_no_closedperiod_message_nb.py diff --git a/backend/samfundet/migrations/0003_rename_message_no_closedperiod_message_nb.py b/backend/samfundet/migrations/0003_rename_message_no_closedperiod_message_nb.py new file mode 100644 index 000000000..32f8588c2 --- /dev/null +++ b/backend/samfundet/migrations/0003_rename_message_no_closedperiod_message_nb.py @@ -0,0 +1,18 @@ +# Generated by Django 5.1.9 on 2025-11-04 20:25 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('samfundet', '0002_remove_closedperiod_description_en_and_more'), + ] + + operations = [ + migrations.RenameField( + model_name='closedperiod', + old_name='message_no', + new_name='message_nb', + ), + ] From b73730bfb353ceedd845d278bf2a7b6532c73341 Mon Sep 17 00:00:00 2001 From: specter Date: Tue, 4 Nov 2025 21:30:13 +0100 Subject: [PATCH 33/61] removed the shamefull file named: ' its a real disgrace --- frontend/' | 125 ----------------------------------------------------- 1 file changed, 125 deletions(-) delete mode 100644 frontend/' diff --git a/frontend/' b/frontend/' deleted file mode 100644 index d09090710..000000000 --- a/frontend/' +++ /dev/null @@ -1,125 +0,0 @@ -import { useCallback, useEffect, useState } from 'react'; -import { useTranslation } from 'react-i18next'; -import { toast } from 'react-toastify'; -import { Button, TimeDisplay, ToggleSwitch } from '~/Components'; -import { Table } from '~/Components/Table'; -import { deleteClosedPeriod, getClosedPeriods } from '~/api'; -import type { ClosedPeriodDto } from '~/dto'; -import { useTitle } from '~/hooks'; -import { KEY } from '~/i18n/constants'; -import { reverse } from '~/named-urls'; -import { ROUTES } from '~/routes'; -import { AdminPageLayout } from '../AdminPageLayout/AdminPageLayout'; -import styles from './ClosedPeriodAdminPage.module.scss'; -import { useGlobalContext } from '~/context/GlobalContextProvider'; -import { dbT } from '~/utils'; - -export function ClosedPeriodAdminPage() { - const [closedPeriods, setClosedPeriods] = useState([]); - const [showSpinner, setShowSpinner] = useState(true); - const { t } = useTranslation(); - const globalContext = useGlobalContext(); - useTitle(t(KEY.command_menu_shortcut_closed)); - - const getAllClosedPeriods = useCallback(() => { - setShowSpinner(true); - getClosedPeriods() - .then((data) => { - setClosedPeriods(data); - setShowSpinner(false); - }) - .catch((error) => { - toast.error(t(KEY.common_something_went_wrong)); - console.error(error); - }); - }, [t]); - - // Stuff to do on first render. - // TODO add permissions on render - - useEffect(() => { - getAllClosedPeriods(); - }, [getAllClosedPeriods]); - - function deleteSelectedEvent(id: number) { - deleteClosedPeriod(id) - .then(() => { - getAllClosedPeriods(); - toast.success(t(KEY.common_delete_successful)); - }) - .catch((error) => { - toast.error(t(KEY.common_something_went_wrong)); - console.error(error); - }); - } - - const header = ( - <> - - - {dbT({"text_nb": "Stenging status", "text_en": "Closing status"}, "text")} - globalContext.toggleIsClosed()}/> - - - ); - const backendUrl = ROUTES.backend.admin__samfundet_closedperiod_changelist; - - return ( - -
-
({ - cells: [ - element.message_nb, - { content: }, - { content: }, - { - content: ( -
- - {' '} -
- ), - }, - ], - }))} - /> - - - ); -} From a68b469c4f445d4ef60b949c22946a1b7bb7d1d7 Mon Sep 17 00:00:00 2001 From: specter Date: Thu, 13 Nov 2025 19:05:06 +0100 Subject: [PATCH 34/61] changed radio buttons to dropdown and imporved styling --- .../ClosedPeriodAdminPage.module.scss | 6 ++ .../ClosedPeriodAdminPage.tsx | 57 +++++++++---------- .../ClosedPeriodFormAdminPage.tsx | 9 ++- 3 files changed, 38 insertions(+), 34 deletions(-) diff --git a/frontend/src/PagesAdmin/ClosedPeriodAdminPage/ClosedPeriodAdminPage.module.scss b/frontend/src/PagesAdmin/ClosedPeriodAdminPage/ClosedPeriodAdminPage.module.scss index 02ae74981..0132b5b57 100644 --- a/frontend/src/PagesAdmin/ClosedPeriodAdminPage/ClosedPeriodAdminPage.module.scss +++ b/frontend/src/PagesAdmin/ClosedPeriodAdminPage/ClosedPeriodAdminPage.module.scss @@ -12,3 +12,9 @@ .admin_closed_radio { margin-top: 10px; } + +.edit_buttons { + display: flex; + flex-direction: column; + gap: 5px; +} diff --git a/frontend/src/PagesAdmin/ClosedPeriodAdminPage/ClosedPeriodAdminPage.tsx b/frontend/src/PagesAdmin/ClosedPeriodAdminPage/ClosedPeriodAdminPage.tsx index 62f5c268b..ea058114b 100644 --- a/frontend/src/PagesAdmin/ClosedPeriodAdminPage/ClosedPeriodAdminPage.tsx +++ b/frontend/src/PagesAdmin/ClosedPeriodAdminPage/ClosedPeriodAdminPage.tsx @@ -1,7 +1,8 @@ import { useCallback, useEffect, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { toast } from 'react-toastify'; -import { Button, RadioButton, TimeDisplay } from '~/Components'; +import { Button, TimeDisplay } from '~/Components'; +import { Dropdown } from '~/Components'; import { Table } from '~/Components/Table'; import { deleteClosedPeriod, getClosedPeriods } from '~/api'; import { useGlobalContext } from '~/context/GlobalContextProvider'; @@ -58,32 +59,25 @@ export function ClosedPeriodAdminPage() { - - - {dbT({ text_nb: 'Stenging status', text_en: 'Closing status' }, 'text')} - - - {dbT({ text_nb: 'standar', text_en: 'default' }, 'text')} - - globalContext.setIsClosed('default')} - /> - - - {dbT({ text_nb: 'stengt', text_en: 'closed' }, 'text')} - - globalContext.setIsClosed('closed')} - /> - - - {dbT({ text_nb: 'åpent', text_en: 'open' }, 'text')} - - globalContext.setIsClosed('open')} /> - - + {dbT({ text_nb: 'Stenging status', text_en: 'Closing status' }, 'text')} + globalContext.setIsClosed(value)} + /> ); const backendUrl = ROUTES.backend.admin__samfundet_closedperiod_changelist; @@ -98,20 +92,21 @@ export function ClosedPeriodAdminPage() {
({ cells: [ element.message_nb, + element.message_en, { content: }, { content: }, { content: ( -
+
{dbT({ text_nb: 'Stenging status', text_en: 'Closing status' }, 'text')} From 5987fad34d5e501d06ef38e1f0e819933f15c3c2 Mon Sep 17 00:00:00 2001 From: specter Date: Tue, 10 Feb 2026 21:31:01 +0100 Subject: [PATCH 43/61] using react query instead --- .../ClosedPeriodAdminPage.tsx | 3 +- .../ClosedPeriodFormAdminPage.tsx | 100 +++++++++--------- frontend/src/i18n/constants.ts | 1 + frontend/src/i18n/translations.ts | 2 + 4 files changed, 53 insertions(+), 53 deletions(-) diff --git a/frontend/src/PagesAdmin/ClosedPeriodAdminPage/ClosedPeriodAdminPage.tsx b/frontend/src/PagesAdmin/ClosedPeriodAdminPage/ClosedPeriodAdminPage.tsx index a64547eaa..8020bf956 100644 --- a/frontend/src/PagesAdmin/ClosedPeriodAdminPage/ClosedPeriodAdminPage.tsx +++ b/frontend/src/PagesAdmin/ClosedPeriodAdminPage/ClosedPeriodAdminPage.tsx @@ -11,7 +11,6 @@ import { useTitle } from '~/hooks'; import { KEY } from '~/i18n/constants'; import { reverse } from '~/named-urls'; import { ROUTES } from '~/routes'; -import { dbT } from '~/utils'; import { AdminPageLayout } from '../AdminPageLayout/AdminPageLayout'; import styles from './ClosedPeriodAdminPage.module.scss'; @@ -59,7 +58,7 @@ export function ClosedPeriodAdminPage() { - {dbT({ text_nb: 'Stenging status', text_en: 'Closing status' }, 'text')} + {t(KEY.admin_closed_period_closing_status)} (true); - // const [closedPeriod, setClosedPeriod] = useState(undefined); For posting - const [initialData, setInitialData] = useState(undefined); - - // If form has a id, check if it exists, and then load that item. const { id } = useParams(); const min_length_message = 10; - // Stuff to do on first render. - // TODO add permissions on render - // biome-ignore lint/correctness/useExhaustiveDependencies: t and navigate do not need to be in deplist + const { + data: initialData, + isLoading, + isError, + error, + } = useQuery({ + queryKey: ['closed-period', id], + queryFn: () => getClosedPeriod(id as string), + enabled: !!id, + select: (data) => ({ + message_nb: data.message_nb, + message_en: data.message_en, + start_dt: data.start_dt, + end_dt: data.end_dt, + }), + }); + useEffect(() => { - // TODO add fix on no id on editpage - if (id === undefined) { - setShowSpinner(false); - return; + if (isError) { + navigate({ url: ROUTES.frontend.admin_closed, replace: true }); + toast.error(t(KEY.common_something_went_wrong)); } + }, [isError, t, navigate]); + + const updateMutation = useMutation({ + mutationFn: (data: FormType) => putClosedPeriod(id as string, data), + onSuccess: () => { + toast.success(t(KEY.common_update_successful)); + navigate({ url: reverse({ pattern: ROUTES.frontend.admin_closed }) }); + }, + onError: () => { + toast.error(t(KEY.common_something_went_wrong)); + }, + }); - getClosedPeriod(id) - .then((data) => { - setInitialData({ - message_nb: data.message_nb, - message_en: data.message_en, - start_dt: data.start_dt, - end_dt: data.end_dt, - }); - setShowSpinner(false); - }) - .catch((data: AxiosError) => { - if (data.request.status === STATUS.HTTP_404_NOT_FOUND) { - navigate({ url: ROUTES.frontend.admin_closed, replace: true }); - } - toast.error(t(KEY.common_something_went_wrong)); - }); - }, [id]); + const createMutation = useMutation({ + mutationFn: (data: formType) => postClosedPeriod(data), + onSuccess: () => { + toast.success(t(KEY.common_creation_successful)); + navigate({ url: reverse({ pattern: ROUTES.frontend.admin_closed }) }); + }, + onError: () => { + toast.error(t(KEY.common_something_went_wrong)); + }, + }); function handleOnSubmit(data: formType) { - if (id !== undefined) { - putClosedPeriod(id, data) - .then(() => { - toast.success(t(KEY.common_update_successful)); - navigate({ url: reverse({ pattern: ROUTES.frontend.admin_closed }) }); - }) - .catch(() => { - toast.error(t(KEY.common_something_went_wrong)); - }); + if (id) { + updateMutation.mutate(data); } else { - postClosedPeriod(data) - .then(() => { - toast.success(t(KEY.common_creation_successful)); - navigate({ url: reverse({ pattern: ROUTES.frontend.admin_closed }) }); - }) - .catch(() => { - toast.error(t(KEY.common_something_went_wrong)); - }); + createMutation.mutate(data); } } @@ -89,7 +87,7 @@ export function ClosedPeriodFormAdminPage() { useTitle(title); return ( - + onSubmit={handleOnSubmit} initialData={initialData}>
Date: Tue, 10 Feb 2026 21:37:59 +0100 Subject: [PATCH 44/61] biome --- .../ClosedPeriodFormAdminPage/ClosedPeriodFormAdminPage.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/frontend/src/PagesAdmin/ClosedPeriodFormAdminPage/ClosedPeriodFormAdminPage.tsx b/frontend/src/PagesAdmin/ClosedPeriodFormAdminPage/ClosedPeriodFormAdminPage.tsx index aad74e13d..118b27b22 100644 --- a/frontend/src/PagesAdmin/ClosedPeriodFormAdminPage/ClosedPeriodFormAdminPage.tsx +++ b/frontend/src/PagesAdmin/ClosedPeriodFormAdminPage/ClosedPeriodFormAdminPage.tsx @@ -7,7 +7,6 @@ import { type FormType, SamfForm } from '~/Forms/SamfForm'; import { SamfFormField } from '~/Forms/SamfFormField'; import { getClosedPeriod, postClosedPeriod, putClosedPeriod } from '~/api'; import { useCustomNavigate, useTitle } from '~/hooks'; -import { STATUS } from '~/http_status_codes'; import { KEY } from '~/i18n/constants'; import { reverse } from '~/named-urls'; import { ROUTES } from '~/routes'; From 264f72f40e74fc69f795cafd5b0fa739a3ffba89 Mon Sep 17 00:00:00 2001 From: specter Date: Tue, 10 Mar 2026 12:25:47 +0100 Subject: [PATCH 45/61] renamed and closed override is now saved as a user preference in the datebase --- .../0006_userpreference_closed_override.py | 18 ++++++++++++++++ backend/samfundet/models/general.py | 1 + .../Components/OpeningHours/OpeningHours.tsx | 4 ++-- .../ClosedPeriodAdminPage.tsx | 4 ++-- .../src/context/GlobalContextProvider.tsx | 21 ++++++++++++++----- frontend/src/dto.ts | 1 + 6 files changed, 40 insertions(+), 9 deletions(-) create mode 100644 backend/samfundet/migrations/0006_userpreference_closed_override.py diff --git a/backend/samfundet/migrations/0006_userpreference_closed_override.py b/backend/samfundet/migrations/0006_userpreference_closed_override.py new file mode 100644 index 000000000..d179a28a4 --- /dev/null +++ b/backend/samfundet/migrations/0006_userpreference_closed_override.py @@ -0,0 +1,18 @@ +# Generated by Django 5.2.10 on 2026-03-10 11:21 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('samfundet', '0005_medlemsinfo'), + ] + + operations = [ + migrations.AddField( + model_name='userpreference', + name='closed_override', + field=models.CharField(default='default', max_length=30), + ), + ] diff --git a/backend/samfundet/models/general.py b/backend/samfundet/models/general.py index 6575a574a..90fa2ca5b 100644 --- a/backend/samfundet/models/general.py +++ b/backend/samfundet/models/general.py @@ -166,6 +166,7 @@ class UserPreference(FullCleanSaveMixin): theme = models.CharField(max_length=30, choices=UserPreferenceTheme.choices, default=UserPreferenceTheme.LIGHT, blank=True, null=True) mirror_dimension = models.BooleanField(default=False) cursor_trail = models.BooleanField(default=False) + closed_override = models.CharField(max_length=30, default="default") created_at = models.DateTimeField(null=True, blank=True, auto_now_add=True) updated_at = models.DateTimeField(null=True, blank=True, auto_now=True) diff --git a/frontend/src/Components/OpeningHours/OpeningHours.tsx b/frontend/src/Components/OpeningHours/OpeningHours.tsx index 9bbec101a..a208f531f 100644 --- a/frontend/src/Components/OpeningHours/OpeningHours.tsx +++ b/frontend/src/Components/OpeningHours/OpeningHours.tsx @@ -23,8 +23,8 @@ export function OpeningHours({ venues, isLoading, isError }: OpeningHoursProps) const globalContext = useGlobalContext(); useEffect(() => { - if (globalContext.isClosed !== 'default') { - if (globalContext.isClosed === 'closed') { + if (globalContext.closedOverride !== 'default') { + if (globalContext.closedOverride === 'closed') { setIsClosed(true); setClosedText(t(KEY.admin_closed_message)); } diff --git a/frontend/src/PagesAdmin/ClosedPeriodAdminPage/ClosedPeriodAdminPage.tsx b/frontend/src/PagesAdmin/ClosedPeriodAdminPage/ClosedPeriodAdminPage.tsx index 8020bf956..3391db127 100644 --- a/frontend/src/PagesAdmin/ClosedPeriodAdminPage/ClosedPeriodAdminPage.tsx +++ b/frontend/src/PagesAdmin/ClosedPeriodAdminPage/ClosedPeriodAdminPage.tsx @@ -74,8 +74,8 @@ export function ClosedPeriodAdminPage() { value: 'closed', }, ]} - value={globalContext.isClosed} - onChange={(value) => globalContext.setIsClosed(value)} + value={globalContext.closedOverride} + onChange={(value) => globalContext.switchClosedOverride(value)} /> ); diff --git a/frontend/src/context/GlobalContextProvider.tsx b/frontend/src/context/GlobalContextProvider.tsx index 51bfdb5ee..4533d51c2 100644 --- a/frontend/src/context/GlobalContextProvider.tsx +++ b/frontend/src/context/GlobalContextProvider.tsx @@ -32,8 +32,9 @@ type GlobalContextProps = { keyValues: KeyValueMap; // AdminToggels - isClosed: string; - setIsClosed: React.Dispatch>; + closedOverride: string; + setClosedOverride: SetState; + switchClosedOverride: (override: string) => void; }; /** @@ -80,7 +81,7 @@ export function GlobalContextProvider({ children, enabled = true }: GlobalContex const [mirrorDimension, setMirrorDimension] = useState(false); const { isMouseTrail, setIsMouseTrail, toggleMouseTrail } = useMouseTrail(); - const [isClosed, setIsClosed] = useState('default'); + const [closedOverride, setClosedOverride] = useState('default'); // =================================== // // Effects // @@ -90,6 +91,7 @@ export function GlobalContextProvider({ children, enabled = true }: GlobalContex useEffect(() => { if (!user || !enabled) return; setMirrorDimension(user.user_preference.mirror_dimension); + setClosedOverride(user.user_preference.closed_override); }, [user, enabled]); // Stuff to do on first render. @@ -146,6 +148,14 @@ export function GlobalContextProvider({ children, enabled = true }: GlobalContex return toggledValue; } + /** changes closed override and saves it to user preference */ + function switchClosedOverride(override: string): void { + setClosedOverride(override); + if (user) { + putUserPreference(user.user_preference.id, { closed_override: override }); + } + } + // =================================== // // Finalize context with values // // =================================== // @@ -164,8 +174,9 @@ export function GlobalContextProvider({ children, enabled = true }: GlobalContex toggleMouseTrail, toggleMirrorDimension, keyValues, - isClosed, - setIsClosed, + closedOverride, + setClosedOverride, + switchClosedOverride, }; return {children}; diff --git a/frontend/src/dto.ts b/frontend/src/dto.ts index 6b692d9a2..11fe20fb6 100644 --- a/frontend/src/dto.ts +++ b/frontend/src/dto.ts @@ -213,6 +213,7 @@ export type UserPreferenceDto = { theme: ThemeValue; mirror_dimension: boolean; cursor_trail: boolean; + closed_override: string; }; export type InformationPageDto = { From fa328b1a5df2545b974a76ee08ed1df586d09147 Mon Sep 17 00:00:00 2001 From: specter Date: Tue, 10 Mar 2026 12:37:53 +0100 Subject: [PATCH 46/61] ruff format --- backend/samfundet/models/general.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/samfundet/models/general.py b/backend/samfundet/models/general.py index 90fa2ca5b..daeb906c0 100644 --- a/backend/samfundet/models/general.py +++ b/backend/samfundet/models/general.py @@ -166,7 +166,7 @@ class UserPreference(FullCleanSaveMixin): theme = models.CharField(max_length=30, choices=UserPreferenceTheme.choices, default=UserPreferenceTheme.LIGHT, blank=True, null=True) mirror_dimension = models.BooleanField(default=False) cursor_trail = models.BooleanField(default=False) - closed_override = models.CharField(max_length=30, default="default") + closed_override = models.CharField(max_length=30, default='default') created_at = models.DateTimeField(null=True, blank=True, auto_now_add=True) updated_at = models.DateTimeField(null=True, blank=True, auto_now=True) From 144335b3c94e38bbad02291b213eb08d16c107f2 Mon Sep 17 00:00:00 2001 From: specter Date: Tue, 10 Mar 2026 12:44:47 +0100 Subject: [PATCH 47/61] using translations --- .../ClosedPeriodAdminPage/ClosedPeriodAdminPage.tsx | 6 +++--- frontend/src/i18n/constants.ts | 2 ++ frontend/src/i18n/translations.ts | 4 ++++ 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/frontend/src/PagesAdmin/ClosedPeriodAdminPage/ClosedPeriodAdminPage.tsx b/frontend/src/PagesAdmin/ClosedPeriodAdminPage/ClosedPeriodAdminPage.tsx index 3391db127..c22401f96 100644 --- a/frontend/src/PagesAdmin/ClosedPeriodAdminPage/ClosedPeriodAdminPage.tsx +++ b/frontend/src/PagesAdmin/ClosedPeriodAdminPage/ClosedPeriodAdminPage.tsx @@ -62,15 +62,15 @@ export function ClosedPeriodAdminPage() { Date: Thu, 9 Apr 2026 19:12:02 +0200 Subject: [PATCH 48/61] improvments to codebase --- ...07_alter_userpreference_closed_override.py | 18 ++++ backend/samfundet/models/general.py | 4 +- backend/samfundet/models/model_choices.py | 5 ++ .../Components/OpeningHours/OpeningHours.tsx | 28 +++--- .../BannerAdminPage.module.scss | 3 + .../BannerAdminPage/BannerAdminPage.tsx | 89 +++++++++++++++++++ .../src/PagesAdmin/BannerAdminPage/index.ts | 1 + .../ClosedPeriodAdminPage.module.scss | 11 --- .../ClosedPeriodAdminPage.tsx | 2 +- .../ClosedPeriodFormAdminPage.tsx | 5 +- 10 files changed, 138 insertions(+), 28 deletions(-) create mode 100644 backend/samfundet/migrations/0007_alter_userpreference_closed_override.py create mode 100644 frontend/src/PagesAdmin/BannerAdminPage/BannerAdminPage.module.scss create mode 100644 frontend/src/PagesAdmin/BannerAdminPage/BannerAdminPage.tsx create mode 100644 frontend/src/PagesAdmin/BannerAdminPage/index.ts diff --git a/backend/samfundet/migrations/0007_alter_userpreference_closed_override.py b/backend/samfundet/migrations/0007_alter_userpreference_closed_override.py new file mode 100644 index 000000000..a9afe3411 --- /dev/null +++ b/backend/samfundet/migrations/0007_alter_userpreference_closed_override.py @@ -0,0 +1,18 @@ +# Generated by Django 5.2.11 on 2026-04-09 17:11 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('samfundet', '0006_userpreference_closed_override'), + ] + + operations = [ + migrations.AlterField( + model_name='userpreference', + name='closed_override', + field=models.CharField(choices=[('default', 'Default'), ('closed', 'Closed'), ('open', 'Open')], default='default', max_length=30), + ), + ] diff --git a/backend/samfundet/models/general.py b/backend/samfundet/models/general.py index daeb906c0..4ed918aa9 100644 --- a/backend/samfundet/models/general.py +++ b/backend/samfundet/models/general.py @@ -22,7 +22,7 @@ from root.utils import permissions from root.utils.mixins import CustomBaseModel, FullCleanSaveMixin -from samfundet.models.model_choices import ReservationOccasion, UserPreferenceTheme, SaksdokumentCategory +from samfundet.models.model_choices import ReservationOccasion, UserPreferenceTheme, SaksdokumentCategory, ClosedOverride from .utils.fields import LowerCaseField, PhoneNumberField from .utils.string_utils import ellipsize @@ -166,7 +166,7 @@ class UserPreference(FullCleanSaveMixin): theme = models.CharField(max_length=30, choices=UserPreferenceTheme.choices, default=UserPreferenceTheme.LIGHT, blank=True, null=True) mirror_dimension = models.BooleanField(default=False) cursor_trail = models.BooleanField(default=False) - closed_override = models.CharField(max_length=30, default='default') + closed_override = models.CharField(max_length=30, default=ClosedOverride.DEFAULT, choices=ClosedOverride.choices) created_at = models.DateTimeField(null=True, blank=True, auto_now_add=True) updated_at = models.DateTimeField(null=True, blank=True, auto_now=True) diff --git a/backend/samfundet/models/model_choices.py b/backend/samfundet/models/model_choices.py index 56ec07ecb..6d4ee42ef 100644 --- a/backend/samfundet/models/model_choices.py +++ b/backend/samfundet/models/model_choices.py @@ -101,6 +101,11 @@ class SaksdokumentCategory(models.TextChoices): RADET = 'RADET', _('Rådet') ARSBERETNINGER = 'ARSBERETNINGER', _('Årsberetninger, regnskap og budsjettkunngjøringer') +class ClosedOverride(models.TextChoices): + DEFAULT = 'default', 'Default' + CLOSED = 'closed', 'Closed' + OPEN = 'open', 'Open' + class RecruitmentApplicantStates(models.IntegerChoices): # Mainly a descriptor diff --git a/frontend/src/Components/OpeningHours/OpeningHours.tsx b/frontend/src/Components/OpeningHours/OpeningHours.tsx index a208f531f..4a4ff36c4 100644 --- a/frontend/src/Components/OpeningHours/OpeningHours.tsx +++ b/frontend/src/Components/OpeningHours/OpeningHours.tsx @@ -9,6 +9,7 @@ import type { VenueDto } from '~/dto'; import { KEY } from '~/i18n/constants'; import { dbT } from '~/utils'; import styles from './OpeningHours.module.scss'; +import { toast } from 'react-toastify'; type OpeningHoursProps = { venues: VenueDto[] | undefined; @@ -19,7 +20,7 @@ type OpeningHoursProps = { export function OpeningHours({ venues, isLoading, isError }: OpeningHoursProps) { const { t } = useTranslation(); const [isClosed, setIsClosed] = useState(false); - const [closedText, setClosedText] = useState('Samf is closed'); + const [closedText, setClosedText] = useState(undefined); const globalContext = useGlobalContext(); useEffect(() => { @@ -30,17 +31,22 @@ export function OpeningHours({ venues, isLoading, isError }: OpeningHoursProps) } return; } - getClosedPeriods().then((periods) => { - const now = new Date(); - for (const period of periods) { - if (new Date(period.start_dt) < now && now < new Date(period.end_dt)) { - setIsClosed(true); - setClosedText(dbT(period, 'message')); - return; + getClosedPeriods() + .then((periods) => { + const now = new Date(); + for (const period of periods) { + if (new Date(period.start_dt) < now && now < new Date(period.end_dt)) { + setIsClosed(true); + setClosedText(dbT(period, 'message')); + return; + } } - } - }); - }); + }) + .catch((error) => { + toast.error(t(KEY.common_something_went_wrong)); + console.error(error); + }); + }, [globalContext.closedOverride, t]); if (isLoading) { return {t(KEY.common_loading)}; diff --git a/frontend/src/PagesAdmin/BannerAdminPage/BannerAdminPage.module.scss b/frontend/src/PagesAdmin/BannerAdminPage/BannerAdminPage.module.scss new file mode 100644 index 000000000..2a8635da1 --- /dev/null +++ b/frontend/src/PagesAdmin/BannerAdminPage/BannerAdminPage.module.scss @@ -0,0 +1,3 @@ +@use 'src/mixins' as *; + +@use 'src/constants' as *; diff --git a/frontend/src/PagesAdmin/BannerAdminPage/BannerAdminPage.tsx b/frontend/src/PagesAdmin/BannerAdminPage/BannerAdminPage.tsx new file mode 100644 index 000000000..ad6fbcfc3 --- /dev/null +++ b/frontend/src/PagesAdmin/BannerAdminPage/BannerAdminPage.tsx @@ -0,0 +1,89 @@ +import { keepPreviousData, useQuery } from '@tanstack/react-query'; +import { useCallback, useEffect, useRef, useState } from 'react'; +import { useTranslation } from 'react-i18next'; +import { Button, InputField } from '~/Components'; +import { PagedPagination } from '~/Components/Pagination'; +import { getImagesPaginated } from '~/api'; +import { useTitle } from '~/hooks'; +import { KEY } from '~/i18n/constants'; +import { imageKeys } from '~/queryKeys'; +import { ROUTES } from '~/routes'; +import { lowerCapitalize } from '~/utils'; +import { AdminPageLayout } from '../AdminPageLayout/AdminPageLayout'; +import styles from './ImageAdminPage.module.scss'; +import { AdminImage } from './components'; + +const PAGE_SIZE = 20; + +export function ImageAdminPage() { + const [currentPage, setCurrentPage] = useState(1); + const [searchInput, setSearchInput] = useState(''); + const [debouncedSearch, setDebouncedSearch] = useState(''); + const debounceTimeout = useRef(); + const { t } = useTranslation(); + useTitle(t(KEY.admin_images_title)); + + // Debounce search input + useEffect(() => { + clearTimeout(debounceTimeout.current); + debounceTimeout.current = setTimeout(() => { + setDebouncedSearch(searchInput); + }, 300); + + return () => clearTimeout(debounceTimeout.current); + }, [searchInput]); + + // Reset to page 1 when search changes + // biome-ignore lint/correctness/useExhaustiveDependencies: Need to trigger on debouncedSearch change + useEffect(() => { + setCurrentPage(1); + }, [debouncedSearch]); + + // Fetch images using React Query + const { data, isLoading } = useQuery({ + queryKey: imageKeys.list(currentPage, debouncedSearch || undefined), + queryFn: () => getImagesPaginated(currentPage, PAGE_SIZE, debouncedSearch || undefined), + placeholderData: keepPreviousData, + }); + + const images = data?.results ?? []; + const totalCount = data?.count ?? 0; + + const title = t(KEY.admin_images_title); + const backendUrl = ROUTES.backend.admin__samfundet_image_changelist; + const header = ( + + ); + + const handleSearchChange = useCallback((value: string) => { + setSearchInput(value); + }, []); + + return ( + +
+ +
+
+ {images.map((element) => ( + + ))} +
+
+ +
+
+ ); +} diff --git a/frontend/src/PagesAdmin/BannerAdminPage/index.ts b/frontend/src/PagesAdmin/BannerAdminPage/index.ts new file mode 100644 index 000000000..4c81d916d --- /dev/null +++ b/frontend/src/PagesAdmin/BannerAdminPage/index.ts @@ -0,0 +1 @@ +export { ImageAdminPage } from './ImageAdminPage'; diff --git a/frontend/src/PagesAdmin/ClosedPeriodAdminPage/ClosedPeriodAdminPage.module.scss b/frontend/src/PagesAdmin/ClosedPeriodAdminPage/ClosedPeriodAdminPage.module.scss index 0132b5b57..b1d85eb72 100644 --- a/frontend/src/PagesAdmin/ClosedPeriodAdminPage/ClosedPeriodAdminPage.module.scss +++ b/frontend/src/PagesAdmin/ClosedPeriodAdminPage/ClosedPeriodAdminPage.module.scss @@ -2,17 +2,6 @@ @use 'src/constants' as *; -.admin_closed_override_container { - display: flex; - align-items: center; - justify-content: center; - gap: 10px; -} - -.admin_closed_radio { - margin-top: 10px; -} - .edit_buttons { display: flex; flex-direction: column; diff --git a/frontend/src/PagesAdmin/ClosedPeriodAdminPage/ClosedPeriodAdminPage.tsx b/frontend/src/PagesAdmin/ClosedPeriodAdminPage/ClosedPeriodAdminPage.tsx index c22401f96..c2ae56e5f 100644 --- a/frontend/src/PagesAdmin/ClosedPeriodAdminPage/ClosedPeriodAdminPage.tsx +++ b/frontend/src/PagesAdmin/ClosedPeriodAdminPage/ClosedPeriodAdminPage.tsx @@ -122,7 +122,7 @@ export function ClosedPeriodAdminPage() { display="block" className={styles.smallButtons} onClick={() => { - // :TODO: window.comfirm should be replaced with a non-browser implementation (not built-in popup) + // :TODO: window.confirm should be replaced with a non-browser implementation (not built-in popup) if (window.confirm(`${t(KEY.form_confirm)} ${t(KEY.common_delete)} ${element.message_nb}`)) { deleteSelectedEvent(element.id); } diff --git a/frontend/src/PagesAdmin/ClosedPeriodFormAdminPage/ClosedPeriodFormAdminPage.tsx b/frontend/src/PagesAdmin/ClosedPeriodFormAdminPage/ClosedPeriodFormAdminPage.tsx index 118b27b22..2d6ba4a97 100644 --- a/frontend/src/PagesAdmin/ClosedPeriodFormAdminPage/ClosedPeriodFormAdminPage.tsx +++ b/frontend/src/PagesAdmin/ClosedPeriodFormAdminPage/ClosedPeriodFormAdminPage.tsx @@ -3,7 +3,7 @@ import { useEffect } from 'react'; import { useTranslation } from 'react-i18next'; import { useParams } from 'react-router'; import { toast } from 'react-toastify'; -import { type FormType, SamfForm } from '~/Forms/SamfForm'; +import { SamfForm } from '~/Forms/SamfForm'; import { SamfFormField } from '~/Forms/SamfFormField'; import { getClosedPeriod, postClosedPeriod, putClosedPeriod } from '~/api'; import { useCustomNavigate, useTitle } from '~/hooks'; @@ -31,7 +31,6 @@ export function ClosedPeriodFormAdminPage() { data: initialData, isLoading, isError, - error, } = useQuery({ queryKey: ['closed-period', id], queryFn: () => getClosedPeriod(id as string), @@ -52,7 +51,7 @@ export function ClosedPeriodFormAdminPage() { }, [isError, t, navigate]); const updateMutation = useMutation({ - mutationFn: (data: FormType) => putClosedPeriod(id as string, data), + mutationFn: (data: formType) => putClosedPeriod(id as string, data), onSuccess: () => { toast.success(t(KEY.common_update_successful)); navigate({ url: reverse({ pattern: ROUTES.frontend.admin_closed }) }); From 63c84de9763a45bb0677fe76a9f8c5017d0580a6 Mon Sep 17 00:00:00 2001 From: specter Date: Thu, 9 Apr 2026 19:16:44 +0200 Subject: [PATCH 49/61] opsis --- .../BannerAdminPage.module.scss | 3 - .../BannerAdminPage/BannerAdminPage.tsx | 89 ------------------- .../src/PagesAdmin/BannerAdminPage/index.ts | 1 - 3 files changed, 93 deletions(-) delete mode 100644 frontend/src/PagesAdmin/BannerAdminPage/BannerAdminPage.module.scss delete mode 100644 frontend/src/PagesAdmin/BannerAdminPage/BannerAdminPage.tsx delete mode 100644 frontend/src/PagesAdmin/BannerAdminPage/index.ts diff --git a/frontend/src/PagesAdmin/BannerAdminPage/BannerAdminPage.module.scss b/frontend/src/PagesAdmin/BannerAdminPage/BannerAdminPage.module.scss deleted file mode 100644 index 2a8635da1..000000000 --- a/frontend/src/PagesAdmin/BannerAdminPage/BannerAdminPage.module.scss +++ /dev/null @@ -1,3 +0,0 @@ -@use 'src/mixins' as *; - -@use 'src/constants' as *; diff --git a/frontend/src/PagesAdmin/BannerAdminPage/BannerAdminPage.tsx b/frontend/src/PagesAdmin/BannerAdminPage/BannerAdminPage.tsx deleted file mode 100644 index ad6fbcfc3..000000000 --- a/frontend/src/PagesAdmin/BannerAdminPage/BannerAdminPage.tsx +++ /dev/null @@ -1,89 +0,0 @@ -import { keepPreviousData, useQuery } from '@tanstack/react-query'; -import { useCallback, useEffect, useRef, useState } from 'react'; -import { useTranslation } from 'react-i18next'; -import { Button, InputField } from '~/Components'; -import { PagedPagination } from '~/Components/Pagination'; -import { getImagesPaginated } from '~/api'; -import { useTitle } from '~/hooks'; -import { KEY } from '~/i18n/constants'; -import { imageKeys } from '~/queryKeys'; -import { ROUTES } from '~/routes'; -import { lowerCapitalize } from '~/utils'; -import { AdminPageLayout } from '../AdminPageLayout/AdminPageLayout'; -import styles from './ImageAdminPage.module.scss'; -import { AdminImage } from './components'; - -const PAGE_SIZE = 20; - -export function ImageAdminPage() { - const [currentPage, setCurrentPage] = useState(1); - const [searchInput, setSearchInput] = useState(''); - const [debouncedSearch, setDebouncedSearch] = useState(''); - const debounceTimeout = useRef(); - const { t } = useTranslation(); - useTitle(t(KEY.admin_images_title)); - - // Debounce search input - useEffect(() => { - clearTimeout(debounceTimeout.current); - debounceTimeout.current = setTimeout(() => { - setDebouncedSearch(searchInput); - }, 300); - - return () => clearTimeout(debounceTimeout.current); - }, [searchInput]); - - // Reset to page 1 when search changes - // biome-ignore lint/correctness/useExhaustiveDependencies: Need to trigger on debouncedSearch change - useEffect(() => { - setCurrentPage(1); - }, [debouncedSearch]); - - // Fetch images using React Query - const { data, isLoading } = useQuery({ - queryKey: imageKeys.list(currentPage, debouncedSearch || undefined), - queryFn: () => getImagesPaginated(currentPage, PAGE_SIZE, debouncedSearch || undefined), - placeholderData: keepPreviousData, - }); - - const images = data?.results ?? []; - const totalCount = data?.count ?? 0; - - const title = t(KEY.admin_images_title); - const backendUrl = ROUTES.backend.admin__samfundet_image_changelist; - const header = ( - - ); - - const handleSearchChange = useCallback((value: string) => { - setSearchInput(value); - }, []); - - return ( - -
- -
-
- {images.map((element) => ( - - ))} -
-
- -
-
- ); -} diff --git a/frontend/src/PagesAdmin/BannerAdminPage/index.ts b/frontend/src/PagesAdmin/BannerAdminPage/index.ts deleted file mode 100644 index 4c81d916d..000000000 --- a/frontend/src/PagesAdmin/BannerAdminPage/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { ImageAdminPage } from './ImageAdminPage'; From 4fab3c5a5b05cead0f8354b59fa2603d19c82ced Mon Sep 17 00:00:00 2001 From: specter Date: Thu, 9 Apr 2026 19:18:38 +0200 Subject: [PATCH 50/61] ruff ruff --- backend/samfundet/models/general.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/samfundet/models/general.py b/backend/samfundet/models/general.py index 4ed918aa9..2b3a51290 100644 --- a/backend/samfundet/models/general.py +++ b/backend/samfundet/models/general.py @@ -22,7 +22,7 @@ from root.utils import permissions from root.utils.mixins import CustomBaseModel, FullCleanSaveMixin -from samfundet.models.model_choices import ReservationOccasion, UserPreferenceTheme, SaksdokumentCategory, ClosedOverride +from samfundet.models.model_choices import ClosedOverride, ReservationOccasion, UserPreferenceTheme, SaksdokumentCategory from .utils.fields import LowerCaseField, PhoneNumberField from .utils.string_utils import ellipsize From 6db5d9c20fa3947d60e805cb87f7369b101b32d4 Mon Sep 17 00:00:00 2001 From: specter Date: Thu, 9 Apr 2026 19:20:38 +0200 Subject: [PATCH 51/61] biome is like the appendix of samf4 --- frontend/src/Components/OpeningHours/OpeningHours.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/Components/OpeningHours/OpeningHours.tsx b/frontend/src/Components/OpeningHours/OpeningHours.tsx index 4a4ff36c4..00942c6ff 100644 --- a/frontend/src/Components/OpeningHours/OpeningHours.tsx +++ b/frontend/src/Components/OpeningHours/OpeningHours.tsx @@ -1,5 +1,6 @@ import { useEffect, useState } from 'react'; import { useTranslation } from 'react-i18next'; +import { toast } from 'react-toastify'; import { TimeDuration } from '~/Components'; import { Link } from '~/Components/Link/Link'; import { Text } from '~/Components/Text/Text'; @@ -9,7 +10,6 @@ import type { VenueDto } from '~/dto'; import { KEY } from '~/i18n/constants'; import { dbT } from '~/utils'; import styles from './OpeningHours.module.scss'; -import { toast } from 'react-toastify'; type OpeningHoursProps = { venues: VenueDto[] | undefined; From c8107cb44b5f92a3485d8bb702405084edf32802 Mon Sep 17 00:00:00 2001 From: specter Date: Thu, 9 Apr 2026 19:22:23 +0200 Subject: [PATCH 52/61] ruff ruff format ruff ruff --- backend/samfundet/models/model_choices.py | 1 + 1 file changed, 1 insertion(+) diff --git a/backend/samfundet/models/model_choices.py b/backend/samfundet/models/model_choices.py index 6d4ee42ef..0b059c323 100644 --- a/backend/samfundet/models/model_choices.py +++ b/backend/samfundet/models/model_choices.py @@ -101,6 +101,7 @@ class SaksdokumentCategory(models.TextChoices): RADET = 'RADET', _('Rådet') ARSBERETNINGER = 'ARSBERETNINGER', _('Årsberetninger, regnskap og budsjettkunngjøringer') + class ClosedOverride(models.TextChoices): DEFAULT = 'default', 'Default' CLOSED = 'closed', 'Closed' From 0ac6cf59208242436a32b63bf06ab10f540eef36 Mon Sep 17 00:00:00 2001 From: Specter <121578250+0xSpecter@users.noreply.github.com> Date: Mon, 13 Apr 2026 11:33:32 +0200 Subject: [PATCH 53/61] typo fix Co-authored-by: andsamfu --- frontend/src/context/GlobalContextProvider.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/context/GlobalContextProvider.tsx b/frontend/src/context/GlobalContextProvider.tsx index 4533d51c2..9edb93d6a 100644 --- a/frontend/src/context/GlobalContextProvider.tsx +++ b/frontend/src/context/GlobalContextProvider.tsx @@ -31,7 +31,7 @@ type GlobalContextProps = { keyValues: KeyValueMap; - // AdminToggels + // AdminToggles closedOverride: string; setClosedOverride: SetState; switchClosedOverride: (override: string) => void; From 4ca224b58dcec49a213c3b9d4e87ae3c17734b5a Mon Sep 17 00:00:00 2001 From: specter Date: Tue, 14 Apr 2026 22:03:20 +0200 Subject: [PATCH 54/61] removed closedOverride as i feelt like it did not fit and converted to using isClosed view in frontend --- ...8_remove_userpreference_closed_override.py | 17 +++++++++ backend/samfundet/models/general.py | 3 +- backend/samfundet/models/model_choices.py | 7 ---- .../Components/OpeningHours/OpeningHours.tsx | 26 ++++--------- .../ClosedPeriodAdminPage.tsx | 22 ----------- frontend/src/api.ts | 38 +++++++++++++++++-- .../src/context/GlobalContextProvider.tsx | 19 ---------- 7 files changed, 60 insertions(+), 72 deletions(-) create mode 100644 backend/samfundet/migrations/0008_remove_userpreference_closed_override.py diff --git a/backend/samfundet/migrations/0008_remove_userpreference_closed_override.py b/backend/samfundet/migrations/0008_remove_userpreference_closed_override.py new file mode 100644 index 000000000..0bd66143c --- /dev/null +++ b/backend/samfundet/migrations/0008_remove_userpreference_closed_override.py @@ -0,0 +1,17 @@ +# Generated by Django 5.2.11 on 2026-04-14 19:59 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('samfundet', '0007_alter_userpreference_closed_override'), + ] + + operations = [ + migrations.RemoveField( + model_name='userpreference', + name='closed_override', + ), + ] diff --git a/backend/samfundet/models/general.py b/backend/samfundet/models/general.py index 2b3a51290..6575a574a 100644 --- a/backend/samfundet/models/general.py +++ b/backend/samfundet/models/general.py @@ -22,7 +22,7 @@ from root.utils import permissions from root.utils.mixins import CustomBaseModel, FullCleanSaveMixin -from samfundet.models.model_choices import ClosedOverride, ReservationOccasion, UserPreferenceTheme, SaksdokumentCategory +from samfundet.models.model_choices import ReservationOccasion, UserPreferenceTheme, SaksdokumentCategory from .utils.fields import LowerCaseField, PhoneNumberField from .utils.string_utils import ellipsize @@ -166,7 +166,6 @@ class UserPreference(FullCleanSaveMixin): theme = models.CharField(max_length=30, choices=UserPreferenceTheme.choices, default=UserPreferenceTheme.LIGHT, blank=True, null=True) mirror_dimension = models.BooleanField(default=False) cursor_trail = models.BooleanField(default=False) - closed_override = models.CharField(max_length=30, default=ClosedOverride.DEFAULT, choices=ClosedOverride.choices) created_at = models.DateTimeField(null=True, blank=True, auto_now_add=True) updated_at = models.DateTimeField(null=True, blank=True, auto_now=True) diff --git a/backend/samfundet/models/model_choices.py b/backend/samfundet/models/model_choices.py index 0b059c323..ae29b58f7 100644 --- a/backend/samfundet/models/model_choices.py +++ b/backend/samfundet/models/model_choices.py @@ -101,13 +101,6 @@ class SaksdokumentCategory(models.TextChoices): RADET = 'RADET', _('Rådet') ARSBERETNINGER = 'ARSBERETNINGER', _('Årsberetninger, regnskap og budsjettkunngjøringer') - -class ClosedOverride(models.TextChoices): - DEFAULT = 'default', 'Default' - CLOSED = 'closed', 'Closed' - OPEN = 'open', 'Open' - - class RecruitmentApplicantStates(models.IntegerChoices): # Mainly a descriptor # The lower, except 0, the least more likely to get this applicant diff --git a/frontend/src/Components/OpeningHours/OpeningHours.tsx b/frontend/src/Components/OpeningHours/OpeningHours.tsx index 00942c6ff..4004894c5 100644 --- a/frontend/src/Components/OpeningHours/OpeningHours.tsx +++ b/frontend/src/Components/OpeningHours/OpeningHours.tsx @@ -4,8 +4,7 @@ import { toast } from 'react-toastify'; import { TimeDuration } from '~/Components'; import { Link } from '~/Components/Link/Link'; import { Text } from '~/Components/Text/Text'; -import { getClosedPeriods } from '~/api'; -import { useGlobalContext } from '~/context/GlobalContextProvider'; +import { getActiveClosedPeriods } from '~/api'; import type { VenueDto } from '~/dto'; import { KEY } from '~/i18n/constants'; import { dbT } from '~/utils'; @@ -21,32 +20,21 @@ export function OpeningHours({ venues, isLoading, isError }: OpeningHoursProps) const { t } = useTranslation(); const [isClosed, setIsClosed] = useState(false); const [closedText, setClosedText] = useState(undefined); - const globalContext = useGlobalContext(); useEffect(() => { - if (globalContext.closedOverride !== 'default') { - if (globalContext.closedOverride === 'closed') { - setIsClosed(true); - setClosedText(t(KEY.admin_closed_message)); - } - return; - } - getClosedPeriods() + getActiveClosedPeriods() .then((periods) => { - const now = new Date(); - for (const period of periods) { - if (new Date(period.start_dt) < now && now < new Date(period.end_dt)) { - setIsClosed(true); - setClosedText(dbT(period, 'message')); - return; - } + if (periods.length !== 0) { + setIsClosed(true); + setClosedText(dbT(periods[0], 'message')); + return; } }) .catch((error) => { toast.error(t(KEY.common_something_went_wrong)); console.error(error); }); - }, [globalContext.closedOverride, t]); + }, [t]); if (isLoading) { return {t(KEY.common_loading)}; diff --git a/frontend/src/PagesAdmin/ClosedPeriodAdminPage/ClosedPeriodAdminPage.tsx b/frontend/src/PagesAdmin/ClosedPeriodAdminPage/ClosedPeriodAdminPage.tsx index c2ae56e5f..f77095887 100644 --- a/frontend/src/PagesAdmin/ClosedPeriodAdminPage/ClosedPeriodAdminPage.tsx +++ b/frontend/src/PagesAdmin/ClosedPeriodAdminPage/ClosedPeriodAdminPage.tsx @@ -2,10 +2,8 @@ import { useCallback, useEffect, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { toast } from 'react-toastify'; import { Button, TimeDisplay } from '~/Components'; -import { Dropdown } from '~/Components'; import { Table } from '~/Components/Table'; import { deleteClosedPeriod, getClosedPeriods } from '~/api'; -import { useGlobalContext } from '~/context/GlobalContextProvider'; import type { ClosedPeriodDto } from '~/dto'; import { useTitle } from '~/hooks'; import { KEY } from '~/i18n/constants'; @@ -18,7 +16,6 @@ export function ClosedPeriodAdminPage() { const [closedPeriods, setClosedPeriods] = useState([]); const [showSpinner, setShowSpinner] = useState(true); const { t } = useTranslation(); - const globalContext = useGlobalContext(); useTitle(t(KEY.command_menu_shortcut_closed)); const getAllClosedPeriods = useCallback(() => { @@ -58,25 +55,6 @@ export function ClosedPeriodAdminPage() { - {t(KEY.admin_closed_period_closing_status)} - globalContext.switchClosedOverride(value)} - /> ); const backendUrl = ROUTES.backend.admin__samfundet_closedperiod_changelist; diff --git a/frontend/src/api.ts b/frontend/src/api.ts index e461842f5..a8d5bf1d2 100644 --- a/frontend/src/api.ts +++ b/frontend/src/api.ts @@ -531,12 +531,36 @@ export async function putGang(id: string | number, data: Partial): Prom return response; } +/** + * returns an array of all closedPeriods + */ export async function getClosedPeriods(): Promise { const url = BACKEND_DOMAIN + ROUTES.backend.samfundet__closedperiods_list; const response = await axios.get(url, { withCredentials: true }); return response.data; } +/** + * Returns a list of all currently active/closed closedPeriods, Samfundet is open if the array is empty + */ +export async function getActiveClosedPeriods(): Promise { + const url = BACKEND_DOMAIN + ROUTES.backend.samfundet__isclosed; + const response = await axios.get(url, { withCredentials: true }); + return response.data; +} + +/** + * Returns only if Samfundet is currently closed but not the closedPeriod responsible + */ +export async function getIsClosed(): Promise { + const url = BACKEND_DOMAIN + ROUTES.backend.samfundet__isclosed; + const response = await axios.get(url, { withCredentials: true }); + return (response.data as Array).length !== 0; +} + +/** + * get a closedPeriod by id + */ export async function getClosedPeriod(id: string | number): Promise { const url = BACKEND_DOMAIN + reverse({ pattern: ROUTES.backend.samfundet__closedperiods_detail, urlParams: { pk: id } }); @@ -544,6 +568,9 @@ export async function getClosedPeriod(id: string | number): Promise): Promise { const url = BACKEND_DOMAIN + reverse({ pattern: ROUTES.backend.samfundet__closedperiods_detail, urlParams: { pk: id } }); @@ -551,12 +578,18 @@ export async function putClosedPeriod(id: string | number, data: Partial): Promise { const url = BACKEND_DOMAIN + ROUTES.backend.samfundet__closedperiods_list; const response = await axios.post(url, data, { withCredentials: true }); return response.data; } +/** + * delete a closedPeriod by id + */ export async function deleteClosedPeriod(id: string | number): Promise { const url = BACKEND_DOMAIN + reverse({ pattern: ROUTES.backend.samfundet__closedperiods_detail, urlParams: { pk: id } }); @@ -1303,13 +1336,12 @@ export async function getPositionsByTag( tags: string, currentPositionId: number, ): Promise { - const url = `${ - BACKEND_DOMAIN + + const url = `${BACKEND_DOMAIN + reverse({ pattern: ROUTES.backend.samfundet__recruitment_positions_by_tags, urlParams: { id: recruitmentId }, }) - }?tags=${encodeURIComponent(tags)}&position_id=${currentPositionId}`; + }?tags=${encodeURIComponent(tags)}&position_id=${currentPositionId}`; const response = await axios.get(url, { withCredentials: true }); return response.data; diff --git a/frontend/src/context/GlobalContextProvider.tsx b/frontend/src/context/GlobalContextProvider.tsx index 4533d51c2..cd52db431 100644 --- a/frontend/src/context/GlobalContextProvider.tsx +++ b/frontend/src/context/GlobalContextProvider.tsx @@ -30,11 +30,6 @@ type GlobalContextProps = { setIsMobileNavigation: SetState; keyValues: KeyValueMap; - - // AdminToggels - closedOverride: string; - setClosedOverride: SetState; - switchClosedOverride: (override: string) => void; }; /** @@ -81,8 +76,6 @@ export function GlobalContextProvider({ children, enabled = true }: GlobalContex const [mirrorDimension, setMirrorDimension] = useState(false); const { isMouseTrail, setIsMouseTrail, toggleMouseTrail } = useMouseTrail(); - const [closedOverride, setClosedOverride] = useState('default'); - // =================================== // // Effects // // =================================== // @@ -91,7 +84,6 @@ export function GlobalContextProvider({ children, enabled = true }: GlobalContex useEffect(() => { if (!user || !enabled) return; setMirrorDimension(user.user_preference.mirror_dimension); - setClosedOverride(user.user_preference.closed_override); }, [user, enabled]); // Stuff to do on first render. @@ -148,14 +140,6 @@ export function GlobalContextProvider({ children, enabled = true }: GlobalContex return toggledValue; } - /** changes closed override and saves it to user preference */ - function switchClosedOverride(override: string): void { - setClosedOverride(override); - if (user) { - putUserPreference(user.user_preference.id, { closed_override: override }); - } - } - // =================================== // // Finalize context with values // // =================================== // @@ -174,9 +158,6 @@ export function GlobalContextProvider({ children, enabled = true }: GlobalContex toggleMouseTrail, toggleMirrorDimension, keyValues, - closedOverride, - setClosedOverride, - switchClosedOverride, }; return {children}; From a715d2a4a1c47349c36f874694b3d485ec09b14c Mon Sep 17 00:00:00 2001 From: specter Date: Thu, 23 Apr 2026 20:14:20 +0200 Subject: [PATCH 55/61] converted from samfform and styled --- backend/manage.py | 2 +- backend/root/utils/debugpy.py | 2 +- backend/samfundet/apps.py | 2 +- backend/samfundet/models/model_choices.py | 1 + .../ClosedPeriodFormAdminPage.module.scss | 18 +++ .../ClosedPeriodFormAdminPage.tsx | 141 ++++++++++++------ frontend/src/api.ts | 5 +- frontend/src/i18n/constants.ts | 1 + frontend/src/i18n/translations.ts | 2 + frontend/src/schema/closedPeriod.ts | 4 + 10 files changed, 127 insertions(+), 51 deletions(-) create mode 100644 frontend/src/schema/closedPeriod.ts diff --git a/backend/manage.py b/backend/manage.py index cbdd160a6..3c7872ace 100644 --- a/backend/manage.py +++ b/backend/manage.py @@ -10,7 +10,7 @@ def main() -> None: os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'root.settings') try: - from django.core.management import execute_from_command_line # noqa: PLC0415 + from django.core.management import execute_from_command_line except ImportError as exc: raise ImportError( diff --git a/backend/root/utils/debugpy.py b/backend/root/utils/debugpy.py index 19a86c1a0..3c5cb0a14 100644 --- a/backend/root/utils/debugpy.py +++ b/backend/root/utils/debugpy.py @@ -17,7 +17,7 @@ def initialize_debugpy() -> None: This may be called in wsgi.py if hosting with gunicorn or in manage.py::__main__. """ if os.environ.get('ENABLE_DEBUGPY') == 'yes': - import debugpy # noqa: PLC0415 + import debugpy # This is okay as long as ENABLE_DEBUGPY only is enabled during development and NOT in production. HOST = '0.0.0.0' # noqa: N806, S104 diff --git a/backend/samfundet/apps.py b/backend/samfundet/apps.py index b9d926637..56f74f345 100644 --- a/backend/samfundet/apps.py +++ b/backend/samfundet/apps.py @@ -16,7 +16,7 @@ class SamfundetConfig(AppConfig): name = 'samfundet' def ready(self) -> None: - from . import signals # noqa: F401, PLC0415 # Important, this enables signals. + from . import signals # noqa: F401 # Important, this enables signals. if os.environ['ENV'] == Environment.DEV: try: diff --git a/backend/samfundet/models/model_choices.py b/backend/samfundet/models/model_choices.py index ae29b58f7..56ec07ecb 100644 --- a/backend/samfundet/models/model_choices.py +++ b/backend/samfundet/models/model_choices.py @@ -101,6 +101,7 @@ class SaksdokumentCategory(models.TextChoices): RADET = 'RADET', _('Rådet') ARSBERETNINGER = 'ARSBERETNINGER', _('Årsberetninger, regnskap og budsjettkunngjøringer') + class RecruitmentApplicantStates(models.IntegerChoices): # Mainly a descriptor # The lower, except 0, the least more likely to get this applicant diff --git a/frontend/src/PagesAdmin/ClosedPeriodFormAdminPage/ClosedPeriodFormAdminPage.module.scss b/frontend/src/PagesAdmin/ClosedPeriodFormAdminPage/ClosedPeriodFormAdminPage.module.scss index 021567115..fcf9ebbc2 100644 --- a/frontend/src/PagesAdmin/ClosedPeriodFormAdminPage/ClosedPeriodFormAdminPage.module.scss +++ b/frontend/src/PagesAdmin/ClosedPeriodFormAdminPage/ClosedPeriodFormAdminPage.module.scss @@ -7,7 +7,25 @@ flex-direction: column; margin-top: 0.5em; gap: 1.5em; + @include for-tablet-up { flex-direction: row; } } + +.container { + display: flex; + flex-direction: row; + justify-content: center; + gap: 20px; + + >div { + flex: 1; + } +} + +.form { + display: flex; + flex-direction: column; + gap: 30px; +} diff --git a/frontend/src/PagesAdmin/ClosedPeriodFormAdminPage/ClosedPeriodFormAdminPage.tsx b/frontend/src/PagesAdmin/ClosedPeriodFormAdminPage/ClosedPeriodFormAdminPage.tsx index 2d6ba4a97..e171b3216 100644 --- a/frontend/src/PagesAdmin/ClosedPeriodFormAdminPage/ClosedPeriodFormAdminPage.tsx +++ b/frontend/src/PagesAdmin/ClosedPeriodFormAdminPage/ClosedPeriodFormAdminPage.tsx @@ -1,31 +1,44 @@ +import { zodResolver } from '@hookform/resolvers/zod'; import { useMutation, useQuery } from '@tanstack/react-query'; import { useEffect } from 'react'; +import { useForm } from 'react-hook-form'; import { useTranslation } from 'react-i18next'; import { useParams } from 'react-router'; import { toast } from 'react-toastify'; -import { SamfForm } from '~/Forms/SamfForm'; -import { SamfFormField } from '~/Forms/SamfFormField'; +import { z } from 'zod'; +import { Button, Form, FormField, FormItem, FormLabel, Input, Textarea } from '~/Components'; +import { FormControl, FormMessage } from '~/Components/Forms/Form'; import { getClosedPeriod, postClosedPeriod, putClosedPeriod } from '~/api'; import { useCustomNavigate, useTitle } from '~/hooks'; import { KEY } from '~/i18n/constants'; import { reverse } from '~/named-urls'; import { ROUTES } from '~/routes'; +import { DATE, MESSAGE } from '~/schema/closedPeriod'; import { AdminPageLayout } from '../AdminPageLayout/AdminPageLayout'; import styles from './ClosedPeriodFormAdminPage.module.scss'; -type formType = { - message_nb: string; - message_en: string; - start_dt: string; - end_dt: string; -}; - export function ClosedPeriodFormAdminPage() { const navigate = useCustomNavigate(); const { t } = useTranslation(); const { id } = useParams(); - const min_length_message = 10; + const schema = z + .object({ + message_nb: MESSAGE, + message_en: MESSAGE, + start_dt: DATE, + end_dt: DATE, + }) + .refine((data) => data.end_dt > data.start_dt, { + message: t(KEY.admin_closed_period_end_before_start), + path: ['end_dt'], + }); + + type formType = z.infer; + + const form = useForm({ + resolver: zodResolver(schema), + }); const { data: initialData, @@ -72,7 +85,7 @@ export function ClosedPeriodFormAdminPage() { }, }); - function handleOnSubmit(data: formType) { + function onSubmit(data: formType) { if (id) { updateMutation.mutate(data); } else { @@ -80,46 +93,82 @@ export function ClosedPeriodFormAdminPage() { } } - const labelMessage = `${t(KEY.common_message)} under '${t(KEY.common_opening_hours)}'`; + useEffect(() => { + if (initialData) { + form.reset(initialData); + } + }, [initialData, form]); + const title = id ? t(KEY.admin_closed_period_edit_period) : t(KEY.admin_closed_period_new_period); useTitle(title); return ( - onSubmit={handleOnSubmit} initialData={initialData}> -
- state.message_nb.length > min_length_message} - field="message_nb" - required={true} - type="text_long" - label={`${labelMessage} (${t(KEY.common_norwegian)})`} - /> - state.message_en.length > min_length_message} - field="message_en" - required={true} - type="text_long" - label={`${labelMessage} (${t(KEY.common_english)})`} - /> -
-
- (state.end_dt ? new Date(state.start_dt) <= new Date(state.end_dt) : true)} - field="start_dt" - type="date" - label={`${t(KEY.start_time)}`} - /> - - state.start_dt ? new Date(state.start_dt) <= new Date(state.end_dt) : true - } - field="end_dt" - type="date" - label={`${t(KEY.end_time)}`} - /> -
- +
+ +
+ ( + + + {t(KEY.common_message)} {t(KEY.common_norwegian)} + + +