From f8cc5a32110b08cfac1625916e9bef8d7a0b1121 Mon Sep 17 00:00:00 2001 From: Oliwier Popielarczyk <71080644+olipop210@users.noreply.github.com> Date: Fri, 10 Jul 2026 22:54:30 +0200 Subject: [PATCH 01/12] fix: add 'use server' declaration Add "use server" declaration to server actions. None of them is working without this --- src/actions/v2/get-class-group-dates.ts | 2 ++ src/actions/v2/get-course-editions-details.ts | 2 ++ src/actions/v2/get-course-editions-preview.ts | 2 ++ src/actions/v2/get-course-groups-for-planner.ts | 2 ++ src/actions/v2/get-faculty-registrations.ts | 2 ++ src/actions/v2/get-lecturer.ts | 2 ++ src/actions/v2/get-round-courses.ts | 2 ++ 7 files changed, 14 insertions(+) diff --git a/src/actions/v2/get-class-group-dates.ts b/src/actions/v2/get-class-group-dates.ts index 61c66b05..7bbfd12c 100644 --- a/src/actions/v2/get-class-group-dates.ts +++ b/src/actions/v2/get-class-group-dates.ts @@ -1,3 +1,5 @@ +'use server'; + import redis from "@/lib/redis"; import { getOrSetRedis } from "@/lib/redis/get-set"; import { fetchUsosApi } from "@/lib/usos"; diff --git a/src/actions/v2/get-course-editions-details.ts b/src/actions/v2/get-course-editions-details.ts index dda42e0c..a045d836 100644 --- a/src/actions/v2/get-course-editions-details.ts +++ b/src/actions/v2/get-course-editions-details.ts @@ -1,3 +1,5 @@ +"use server"; + import redis from "@/lib/redis"; import { getOrSetRedis } from "@/lib/redis/get-set"; import { fetchUsosApi } from "@/lib/usos"; diff --git a/src/actions/v2/get-course-editions-preview.ts b/src/actions/v2/get-course-editions-preview.ts index b6d004e6..5eef808e 100644 --- a/src/actions/v2/get-course-editions-preview.ts +++ b/src/actions/v2/get-course-editions-preview.ts @@ -1,3 +1,5 @@ +"use server"; + import { createHash } from "node:crypto"; import redis from "@/lib/redis"; diff --git a/src/actions/v2/get-course-groups-for-planner.ts b/src/actions/v2/get-course-groups-for-planner.ts index 4edd7e6f..64414b43 100644 --- a/src/actions/v2/get-course-groups-for-planner.ts +++ b/src/actions/v2/get-course-groups-for-planner.ts @@ -1,3 +1,5 @@ +"use server"; + import type { GroupSchedulePattern } from "@/lib/utils/build-group-schedule-pattern"; import { buildGroupSchedulePattern } from "@/lib/utils/build-group-schedule-pattern"; import type { ClassgroupDate } from "@/types"; diff --git a/src/actions/v2/get-faculty-registrations.ts b/src/actions/v2/get-faculty-registrations.ts index 77593aed..606535eb 100644 --- a/src/actions/v2/get-faculty-registrations.ts +++ b/src/actions/v2/get-faculty-registrations.ts @@ -1,3 +1,5 @@ +"use server"; + import redis from "@/lib/redis"; import { getOrSetRedis } from "@/lib/redis/get-set"; import { fetchUsosApi } from "@/lib/usos"; diff --git a/src/actions/v2/get-lecturer.ts b/src/actions/v2/get-lecturer.ts index aeb5c5c9..863ccc71 100644 --- a/src/actions/v2/get-lecturer.ts +++ b/src/actions/v2/get-lecturer.ts @@ -1,3 +1,5 @@ +'use server'; + import redis from "@/lib/redis"; import { getOrSetRedis } from "@/lib/redis/get-set"; import { fetchUsosApi } from "@/lib/usos"; diff --git a/src/actions/v2/get-round-courses.ts b/src/actions/v2/get-round-courses.ts index ef40eded..dc9e1b2c 100644 --- a/src/actions/v2/get-round-courses.ts +++ b/src/actions/v2/get-round-courses.ts @@ -1,3 +1,5 @@ +"use server"; + import redis from "@/lib/redis"; import { getOrSetRedis } from "@/lib/redis/get-set"; import { fetchUsosApi } from "@/lib/usos"; From 3e37ea22268b5b385de954d5d722484e724340cd Mon Sep 17 00:00:00 2001 From: Oliwier Popielarczyk <71080644+olipop210@users.noreply.github.com> Date: Fri, 10 Jul 2026 22:59:33 +0200 Subject: [PATCH 02/12] fix: repair server actions still wip on get-course-edition-details --- src/actions/v2/get-course-edition-details.ts | 11 +++++++---- src/actions/v2/get-registration-rounds.ts | 5 +++-- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/src/actions/v2/get-course-edition-details.ts b/src/actions/v2/get-course-edition-details.ts index bf360a32..945a16fa 100644 --- a/src/actions/v2/get-course-edition-details.ts +++ b/src/actions/v2/get-course-edition-details.ts @@ -1,7 +1,11 @@ +"use server"; + import redis from "@/lib/redis"; import { getOrSetRedis } from "@/lib/redis/get-set"; import { fetchUsosApi } from "@/lib/usos"; +//todo: w api usos nie ma czegos takiego jak lecturer ids dla tego endpointu (albo cos pojebalem), wiec trzeba to poprawic bo obecnie to jest chyba undefined + interface UsosCourseEdition { course_id: string; term_id: string; @@ -17,7 +21,6 @@ interface UsosCourseUnitGroup { interface UsosCourseUnit { id: string; classtype_id: string; - use_groups: boolean; groups: UsosCourseUnitGroup[] | null; } @@ -37,12 +40,12 @@ export interface CourseEditionDetailsDTO { async function fetchCourseUnitGroups( unitId: string, ): Promise { - const data = await fetchUsosApi("courses/course_unit", { + const data = await fetchUsosApi("courses/unit", { unit_id: unitId, - fields: "id|classtype_id|use_groups|groups[group_number|lecturer_ids]", + fields: "id|classtype_id|groups[group_number|lecturers]", }); - if (!data.use_groups || data.groups == null || data.groups.length === 0) { + if (data.groups == null || data.groups.length === 0) { return [ { unitId: data.id, diff --git a/src/actions/v2/get-registration-rounds.ts b/src/actions/v2/get-registration-rounds.ts index dada6be2..a06d624c 100644 --- a/src/actions/v2/get-registration-rounds.ts +++ b/src/actions/v2/get-registration-rounds.ts @@ -1,3 +1,5 @@ +"use server"; + import redis from "@/lib/redis"; import { getOrSetRedis } from "@/lib/redis/get-set"; import { fetchUsosApi } from "@/lib/usos"; @@ -37,10 +39,9 @@ export async function getRegistrationRoundsAction( `registrations/course_registration_rounds`, { registration_id: registrationId, - fields: "fields=id|name|status|registration_mode|start_date|end_date", + fields: "id|name|status|registration_mode|start_date|end_date", }, ); - return data.toSorted(sortByStartDateFunction); }, }); From f1252fc52e3bafe007413dc3726ffe4501f5ca0d Mon Sep 17 00:00:00 2001 From: Oliwier Popielarczyk <71080644+olipop210@users.noreply.github.com> Date: Fri, 10 Jul 2026 23:00:12 +0200 Subject: [PATCH 03/12] feat: integrate server actions to frontend --- .../edit/[id]/_components/app-sidebar.tsx | 88 +++++++++++--- src/app/plans/edit/[id]/page.client.tsx | 115 +++++++++++++++--- src/lib/utils/build-group-schedule-pattern.ts | 6 +- src/lib/utils/server-to-local-plan.ts | 11 +- 4 files changed, 186 insertions(+), 34 deletions(-) diff --git a/src/app/plans/edit/[id]/_components/app-sidebar.tsx b/src/app/plans/edit/[id]/_components/app-sidebar.tsx index cef47044..72f0fbc6 100644 --- a/src/app/plans/edit/[id]/_components/app-sidebar.tsx +++ b/src/app/plans/edit/[id]/_components/app-sidebar.tsx @@ -6,7 +6,8 @@ import { isEqual } from "date-fns"; import { format } from "date-fns/format"; import React from "react"; -import { getFaculties } from "@/actions/get-faculties"; +import { getFacultyRegistrationsAction } from "@/actions/v2/get-faculty-registrations"; +//import { getFaculties } from "@/actions/get-faculties"; import { Alerts } from "@/components/alerts"; import { AlgorithmDialog } from "@/components/algo-dialog"; import { GroupsAccordionItem } from "@/components/groups-accordion"; @@ -29,7 +30,7 @@ import { SidebarHeader, } from "@/components/ui/sidebar"; import { Skeleton } from "@/components/ui/skeleton"; -import { fetchClient } from "@/lib/fetch"; +//import { fetchClient } from "@/lib/fetch"; import type { usePlanType } from "@/lib/use-plan"; import { registrationReplacer } from "@/lib/utils"; import { serverToLocalPlan } from "@/lib/utils/server-to-local-plan"; @@ -64,26 +65,79 @@ export function AppSidebar({ offlineAlert: boolean; faculty: string | null; }) { - const faculties = useQuery({ - queryKey: ["faculties"], - queryFn: getFaculties, - }); + // const faculties = useQuery({ + // queryKey: ["faculties"], + // queryFn: getFaculties, + // }); + + //hardcoded lista wydziałów, wzięta z seedów ze starego planera, do usunięcia po dodaniu fetch faculties + + const faculties = { + data: [ + { + value: "W4N", + name: "W4N", + url: "https://web.usos.pwr.edu.pl/kontroler.php?_action=news/rejestracje/rejJednostki&jed_org_kod=CS", + }, + { + value: "W02-DEF-ZY-2", + name: "Department of Mathematics", + url: "https://web.usos.pwr.edu.pl/kontroler.php?_action=news/rejestracje/rejJednostki&jed_org_kod=MATH", + }, + { + value: "W03-GHI-WX-3", + name: "Department of Physics", + url: "https://web.usos.pwr.edu.pl/kontroler.php?_action=news/rejestracje/rejJednostki&jed_org_kod=PHYS", + }, + { + value: "W04-JKL-VU-4", + name: "Department of Chemistry", + url: "https://web.usos.pwr.edu.pl/kontroler.php?_action=news/rejestracje/rejJednostki&jed_org_kod=CHEM", + }, + { + value: "W05-MNO-TS-5", + name: "Department of Biology", + url: "https://web.usos.pwr.edu.pl/kontroler.php?_action=news/rejestracje/rejJednostki&jed_org_kod=BIO", + }, + { + value: "W06-PQR-RQ-6", + name: "Department of Engineering", + url: "https://web.usos.pwr.edu.pl/kontroler.php?_action=news/rejestracje/rejJednostki&jed_org_kod=ENG", + }, + ], + isLoading: false, + isError: false, + }; const registrations = useQuery({ - enabled: faculty !== null && faculty !== "", + //enabled: faculty !== null && faculty !== "", queryKey: ["registrations", faculty], queryFn: async () => { - const response = await fetchClient({ - url: `/departments/${encodeURIComponent(faculty ?? "")}/registrations`, - method: "GET", - }); + const registrationsDTO = await getFacultyRegistrationsAction( + faculty ?? "", + ); - if (!response.ok) { - throw new Error("Network response was not ok"); - } - - return response.json() as Promise; + // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion + return registrationsDTO.map((registrationDTO) => { + return { + id: registrationDTO.id, + name: registrationDTO.description, + departmentId: faculty ?? "W4N", + }; + }) as unknown as FacultyType; }, + // queryFn: async () => { + // const response = await fetchClient({ + // url: `/departments/${encodeURIComponent(faculty ?? "")}/registrations`, + // method: "GET", + // }); + + // if (!response.ok) { + // throw new Error("Network response was not ok"); + // } + + // return response.json() as Promise; + // }, }); return ( @@ -190,7 +244,7 @@ export function AppSidebar({ ) : ( - {faculties.data?.map((f) => ( + {faculties.data.map((f) => ( { + const date = new Date(startTime).getDay(); + switch (date) { + case 0: { + return "niedziela"; + } + case 1: { + return "poniedziałek"; + } + case 2: { + return "wtorek"; + } + case 3: { + return "środa"; + } + case 4: { + return "czwartek"; + } + case 5: { + return "piątek"; + } + case 6: { + return "sobota"; + } + default: { + return "poniedziałek"; + } + } +}; + export function CreateNewPlanPage({ planId }: { planId: string }) { const session = useSession(); const isLoggedIn = session.data !== null; @@ -78,22 +116,71 @@ export function CreateNewPlanPage({ planId }: { planId: string }) { const coursesFunction = useMutation({ mutationKey: ["courses"], mutationFn: async (registrationId: string) => { - const onlindeFacultyId = - onlinePlan?.registrations[0]?.departmentId ?? - plan.registrations[0]?.departmentId; - const response = await fetchClient({ - url: `/departments/${encodeURIComponent(onlindeFacultyId)}/registrations/${encodeURIComponent(registrationId)}/courses`, - method: "GET", - }); + const rounds = await getRegistrationRoundsAction(registrationId); + + const nominalRound = rounds[0]; + + const roundCourses = await getRegistrationRoundCoursesAction( + nominalRound.id, + ); + + const normalizedCourses: CourseType = []; + + let index = 0; - if (!response.ok) { - toast.error( - "Coś poszło nie tak podczas pobierania kursów, spróbuj ponownie", + for (const course of roundCourses) { + const groups = await getPlannerCourseGroupsAction( + course.courseId, + course.termId, ); - throw new Error("Network response was not ok"); + + const newCourse: SingleCourse = { + id: course.courseId, + name: course.courseName, + groups: [], + registrationId, + }; + //console.log("przed zmianami", groups) + for (const group of groups) { + newCourse.groups.push({ + id: index, + name: course.courseName, + averageRating: "0.0", + opinionsCount: 0, + type: group.classtypeId as "W" | "C" | "L" | "S" | "P", + courseId: course.courseId, + createdAt: "", + updatedAt: "", + spotsOccupied: 0, + spotsTotal: 0, + isActive: true, + url: "", + lecturer: "", + lecturers: [], + group: group.groupNumber, + + meetings: [ + { + id: Math.floor(Math.random() * 1000), + groupId: Number.parseInt(group.groupNumber), + startTime: group.schedulePattern?.endTime ?? "7:30", + endTime: group.schedulePattern?.endTime ?? "9:00", + week: "-", + createdAt: "", + updatedAt: "", + day: getDayOfWeek( + group.schedulePattern?.startTime ?? "01.01.1970", + ), + }, + ], + }); + index++; + } + + normalizedCourses.push(newCourse); } - return response.json() as Promise; + return normalizedCourses; }, }); const { diff --git a/src/lib/utils/build-group-schedule-pattern.ts b/src/lib/utils/build-group-schedule-pattern.ts index 9422867c..5cea6025 100644 --- a/src/lib/utils/build-group-schedule-pattern.ts +++ b/src/lib/utils/build-group-schedule-pattern.ts @@ -62,7 +62,11 @@ export function buildGroupSchedulePattern( const sorted = [...dates].toSorted((a, b) => a.date.localeCompare(b.date)); const firstDate = new Date(sorted[0].date); - const firstEntry = sorted[0]; + let firstEntry = sorted[0]; + if (sorted.length > 1) { + firstEntry = sorted[1]; //first date is often irregular, like in 15h course first classes can be 45mins, so if its possible, we can take second + } + //const firstEntry = sorted[0]; const lastEntry = sorted.at(-1) ?? firstEntry; if (sorted.length === 1) { diff --git a/src/lib/utils/server-to-local-plan.ts b/src/lib/utils/server-to-local-plan.ts index b0dc2f77..7a6f0a4b 100644 --- a/src/lib/utils/server-to-local-plan.ts +++ b/src/lib/utils/server-to-local-plan.ts @@ -8,6 +8,10 @@ import type { SingleGroup, } from "@/types"; +const formatMeetingTime = (groupMeetingTime: string): string => { + return `${groupMeetingTime.split(":")[0].slice(groupMeetingTime.split(":")[0].length - 2, groupMeetingTime.split(":")[0].length)}:${groupMeetingTime.split(":")[1]}`; +}; + export const serverToLocalPlan = ( courses: CourseType, shouldCourseBeChecked: ((course: SingleCourse) => boolean) | boolean, @@ -49,8 +53,10 @@ export const serverToLocalPlan = ( meeting.week === "-" ? "" : (meeting.week as "" | "TN" | "TP" | "!"), - endTime: meeting.endTime.split(":").slice(0, 2).join(":"), - startTime: meeting.startTime.split(":").slice(0, 2).join(":"), + //endTime: meeting.endTime.split(":").slice(0, 2).join(":"), + endTime: formatMeetingTime(meeting.endTime), + //startTime: meeting.startTime.split(":").slice(0, 2).join(":"), + startTime: formatMeetingTime(meeting.startTime), spotsOccupied: g.spotsOccupied, spotsTotal: g.spotsTotal, averageRating: Number.parseFloat(g.averageRating), @@ -61,5 +67,6 @@ export const serverToLocalPlan = ( .toSorted((a, b) => { return a.name.localeCompare(b.name); }); + //console.log("extended courses", extendedCourses) return extendedCourses; }; From 053d5a56dba20d902ceb3c9107278e61710f6260 Mon Sep 17 00:00:00 2001 From: Oliwier Popielarczyk <71080644+olipop210@users.noreply.github.com> Date: Fri, 10 Jul 2026 23:23:20 +0200 Subject: [PATCH 04/12] fix: typo --- src/app/plans/edit/[id]/page.client.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/plans/edit/[id]/page.client.tsx b/src/app/plans/edit/[id]/page.client.tsx index 61bf7660..a21336ca 100644 --- a/src/app/plans/edit/[id]/page.client.tsx +++ b/src/app/plans/edit/[id]/page.client.tsx @@ -163,7 +163,7 @@ export function CreateNewPlanPage({ planId }: { planId: string }) { { id: Math.floor(Math.random() * 1000), groupId: Number.parseInt(group.groupNumber), - startTime: group.schedulePattern?.endTime ?? "7:30", + startTime: group.schedulePattern?.startTime ?? "7:30", endTime: group.schedulePattern?.endTime ?? "9:00", week: "-", createdAt: "", From eeaa78406d76460293ab1cd1dd54eb2d1437e2eb Mon Sep 17 00:00:00 2001 From: Oliwier Popielarczyk <71080644+olipop210@users.noreply.github.com> Date: Thu, 16 Jul 2026 12:48:54 +0200 Subject: [PATCH 05/12] feat: get-faculties action move faculties to separate method --- src/actions/v2/get-faculties.ts | 97 +++++++++++++++++++ .../edit/[id]/_components/app-sidebar.tsx | 65 +------------ 2 files changed, 102 insertions(+), 60 deletions(-) create mode 100644 src/actions/v2/get-faculties.ts diff --git a/src/actions/v2/get-faculties.ts b/src/actions/v2/get-faculties.ts new file mode 100644 index 00000000..95088ff1 --- /dev/null +++ b/src/actions/v2/get-faculties.ts @@ -0,0 +1,97 @@ +interface Faculty { + value: string, + name: string +} + +interface getFacultiesDTO { + data: Faculty[], + isLoading: boolean, + isError: boolean +} + +//in future can be replaced with database get + +export function getFacultiesAction(): getFacultiesDTO { + return { + data: [ + { + value: "W1", + name: "Wydział Architektury", + }, + { + value: "W2", + name: "Wydział Budownictwa Lądowego i Wodnego", + }, + { + value: "W3", + name: "Wydział Chemiczny", + }, + { + value: "W4N", + name: "Wydział Informatyki i Telekomunikacji", + }, + { + value: "W5", + name: "Wydział Elektryczny", + }, + { + value: "W6", + name: "Wydział Geoinżynierii, Górnictwa i Geologii", + }, + { + value: "W7", + name: "Wydział Inżynierii Środowiska", + }, + { + value: "W8N", + name: "Wydział Zarządzania", + }, + { + value: "W9", + name: "Wydział Mechaniczno-Energetyczny", + }, + { + value: "W10", + name: "Wydział Mechaniczny", + }, + { + value: "W11", + name: "Wydział Podstawowych Problemów Techniki", + }, + { + value: "W12", + name: "Wydział Elektroniki, Fotoniki i Mikrosystemów", + }, + { + value: "W13", + name: "Wydział Matematyki", + }, + { + value: "W14", + name: "Wydział Medyczny", + }, + { + value: "FLG", + name: "Filia w Legnicy", + }, + { + value: "SD1", + name: "Szkoła Doktorska Politechniki Wrocławskiej", + }, + { + value: "PRK24/S1", + name: "PRK24/S1", + }, + { + value: "PRK24/S3", + name: "PRK24/S3", + }, + { + value: "PWR", + name: "PWR", + }, + ], + isLoading: false, + isError: false, + } +} \ No newline at end of file diff --git a/src/app/plans/edit/[id]/_components/app-sidebar.tsx b/src/app/plans/edit/[id]/_components/app-sidebar.tsx index 72f0fbc6..667005ce 100644 --- a/src/app/plans/edit/[id]/_components/app-sidebar.tsx +++ b/src/app/plans/edit/[id]/_components/app-sidebar.tsx @@ -7,7 +7,6 @@ import { format } from "date-fns/format"; import React from "react"; import { getFacultyRegistrationsAction } from "@/actions/v2/get-faculty-registrations"; -//import { getFaculties } from "@/actions/get-faculties"; import { Alerts } from "@/components/alerts"; import { AlgorithmDialog } from "@/components/algo-dialog"; import { GroupsAccordionItem } from "@/components/groups-accordion"; @@ -30,15 +29,15 @@ import { SidebarHeader, } from "@/components/ui/sidebar"; import { Skeleton } from "@/components/ui/skeleton"; -//import { fetchClient } from "@/lib/fetch"; import type { usePlanType } from "@/lib/use-plan"; import { registrationReplacer } from "@/lib/utils"; import { serverToLocalPlan } from "@/lib/utils/server-to-local-plan"; -import type { CourseType, FacultyType, PlanResponseType } from "@/types"; +import type { CourseType, PlanResponseType } from "@/types"; import { OfflineAlert } from "./offline-alert"; import { SyncErrorAlert } from "./sync-error-alert"; import { SyncedButton } from "./synced-button"; +import { getFacultiesAction } from "@/actions/v2/get-faculties"; export function AppSidebar({ plan, @@ -65,79 +64,25 @@ export function AppSidebar({ offlineAlert: boolean; faculty: string | null; }) { - // const faculties = useQuery({ - // queryKey: ["faculties"], - // queryFn: getFaculties, - // }); - //hardcoded lista wydziałów, wzięta z seedów ze starego planera, do usunięcia po dodaniu fetch faculties - - const faculties = { - data: [ - { - value: "W4N", - name: "W4N", - url: "https://web.usos.pwr.edu.pl/kontroler.php?_action=news/rejestracje/rejJednostki&jed_org_kod=CS", - }, - { - value: "W02-DEF-ZY-2", - name: "Department of Mathematics", - url: "https://web.usos.pwr.edu.pl/kontroler.php?_action=news/rejestracje/rejJednostki&jed_org_kod=MATH", - }, - { - value: "W03-GHI-WX-3", - name: "Department of Physics", - url: "https://web.usos.pwr.edu.pl/kontroler.php?_action=news/rejestracje/rejJednostki&jed_org_kod=PHYS", - }, - { - value: "W04-JKL-VU-4", - name: "Department of Chemistry", - url: "https://web.usos.pwr.edu.pl/kontroler.php?_action=news/rejestracje/rejJednostki&jed_org_kod=CHEM", - }, - { - value: "W05-MNO-TS-5", - name: "Department of Biology", - url: "https://web.usos.pwr.edu.pl/kontroler.php?_action=news/rejestracje/rejJednostki&jed_org_kod=BIO", - }, - { - value: "W06-PQR-RQ-6", - name: "Department of Engineering", - url: "https://web.usos.pwr.edu.pl/kontroler.php?_action=news/rejestracje/rejJednostki&jed_org_kod=ENG", - }, - ], - isLoading: false, - isError: false, - }; + const faculties = getFacultiesAction(); const registrations = useQuery({ - //enabled: faculty !== null && faculty !== "", + enabled: faculty !== null && faculty !== "", queryKey: ["registrations", faculty], queryFn: async () => { const registrationsDTO = await getFacultyRegistrationsAction( faculty ?? "", ); - // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion return registrationsDTO.map((registrationDTO) => { return { id: registrationDTO.id, name: registrationDTO.description, departmentId: faculty ?? "W4N", }; - }) as unknown as FacultyType; + }); }, - // queryFn: async () => { - // const response = await fetchClient({ - // url: `/departments/${encodeURIComponent(faculty ?? "")}/registrations`, - // method: "GET", - // }); - - // if (!response.ok) { - // throw new Error("Network response was not ok"); - // } - - // return response.json() as Promise; - // }, }); return ( From bea30f05b42c04d4ea815c7d34326033a43eb4e6 Mon Sep 17 00:00:00 2001 From: Oliwier Popielarczyk <71080644+olipop210@users.noreply.github.com> Date: Thu, 16 Jul 2026 12:51:09 +0200 Subject: [PATCH 06/12] fix: get-course-edition-details fix lecturers normalization --- src/actions/v2/get-course-edition-details.ts | 27 +++++++++++++++----- 1 file changed, 21 insertions(+), 6 deletions(-) diff --git a/src/actions/v2/get-course-edition-details.ts b/src/actions/v2/get-course-edition-details.ts index 945a16fa..f1b8da76 100644 --- a/src/actions/v2/get-course-edition-details.ts +++ b/src/actions/v2/get-course-edition-details.ts @@ -3,8 +3,7 @@ import redis from "@/lib/redis"; import { getOrSetRedis } from "@/lib/redis/get-set"; import { fetchUsosApi } from "@/lib/usos"; - -//todo: w api usos nie ma czegos takiego jak lecturer ids dla tego endpointu (albo cos pojebalem), wiec trzeba to poprawic bo obecnie to jest chyba undefined +import type { UsosLecturer, LecturerDTO } from "./get-lecturer"; interface UsosCourseEdition { course_id: string; @@ -12,10 +11,26 @@ interface UsosCourseEdition { course_units_ids: string[] | null; } +function normalizeLecturers(lecturers: UsosLecturer[]): LecturerDTO[] { + + return lecturers.map((lecturer) => { + return { + id: lecturer.id, + firstName: lecturer.first_name, + lastName: lecturer.last_name, + titlesBefore: lecturer.titles?.before ?? null, + titlesAfter: lecturer.titles?.after ?? null, + photoUrl: lecturer.photo_urls?.["50x50"] ?? null, + homepageUrl: lecturer.homepage_url ?? null, + profileUrl: lecturer.profile_url ?? null, + } + }) +} + interface UsosCourseUnitGroup { course_unit_id: string; group_number: number; - lecturer_ids: string[] | null; + lecturers: UsosLecturer[]; } interface UsosCourseUnit { @@ -28,7 +43,7 @@ export interface CourseGroupDTO { unitId: string; groupNumber: string; classtypeId: string; - lecturerIds: string[]; + lecturers: LecturerDTO[]; } export interface CourseEditionDetailsDTO { @@ -51,7 +66,7 @@ async function fetchCourseUnitGroups( unitId: data.id, groupNumber: "1", classtypeId: data.classtype_id, - lecturerIds: [], + lecturers: [], }, ]; } @@ -60,7 +75,7 @@ async function fetchCourseUnitGroups( unitId: data.id, groupNumber: String(group.group_number), classtypeId: data.classtype_id, - lecturerIds: group.lecturer_ids ?? [], + lecturers: normalizeLecturers(group.lecturers), })); } From 6044a017e8dea1e0d5fa8f530b06b72a2bfde86e Mon Sep 17 00:00:00 2001 From: Oliwier Popielarczyk <71080644+olipop210@users.noreply.github.com> Date: Thu, 16 Jul 2026 12:57:16 +0200 Subject: [PATCH 07/12] fix: groups pattern and parity fix detecting group pattern and parity --- .../v2/get-course-groups-for-planner.ts | 62 ++++++++++++------- src/actions/v2/get-term.ts | 48 ++++++++++++++ src/app/plans/edit/[id]/page.client.tsx | 59 +++++++++++++++--- src/lib/utils/build-group-schedule-pattern.ts | 33 ++++++++-- 4 files changed, 167 insertions(+), 35 deletions(-) create mode 100644 src/actions/v2/get-term.ts diff --git a/src/actions/v2/get-course-groups-for-planner.ts b/src/actions/v2/get-course-groups-for-planner.ts index 64414b43..cf50b541 100644 --- a/src/actions/v2/get-course-groups-for-planner.ts +++ b/src/actions/v2/get-course-groups-for-planner.ts @@ -1,6 +1,9 @@ "use server"; -import type { GroupSchedulePattern } from "@/lib/utils/build-group-schedule-pattern"; +import type { + GroupSchedulePattern, + ScheduleParity, +} from "@/lib/utils/build-group-schedule-pattern"; import { buildGroupSchedulePattern } from "@/lib/utils/build-group-schedule-pattern"; import type { ClassgroupDate } from "@/types"; @@ -8,7 +11,9 @@ import { getClassgroupDatesAction } from "./get-class-group-dates"; import type { CourseGroupDTO } from "./get-course-edition-details"; import { getCourseEditionDetailsAction } from "./get-course-edition-details"; import type { LecturerDTO } from "./get-lecturer"; -import { getLecturerAction } from "./get-lecturer"; +import { getTermAction } from "./get-term"; + +//import { getLecturerAction } from "./get-lecturer"; export interface PlannerGroupDTO { unitId: string; @@ -36,15 +41,40 @@ function toClassgroupDates( })); } -async function fetchGroupWithPattern(group: CourseGroupDTO): Promise<{ +function daysBetween(a: string, b: string): number { + const msPerDay = 1000 * 60 * 60 * 24; + return Math.round((new Date(b).getTime() - new Date(a).getTime()) / msPerDay); +} + +async function getSchedulePatternParity( + termId: string, + classGroupStartTime: string, +): Promise { + const term = await getTermAction(termId); + const weekNumber = + Math.floor(daysBetween(classGroupStartTime, term.startDate) / 7) + 1; + return weekNumber % 2 === 0 ? "odd" : "even"; +} + +async function fetchGroupWithPattern( + group: CourseGroupDTO, + termId: string, +): Promise<{ group: CourseGroupDTO; schedulePattern: GroupSchedulePattern | null; }> { const dates = await getClassgroupDatesAction(group.unitId, group.groupNumber); const classgroupDates = toClassgroupDates(dates); + const schedulePattern = buildGroupSchedulePattern(classgroupDates); + if (schedulePattern?.pattern === "biweekly") { + schedulePattern.parity = await getSchedulePatternParity( + termId, + dates[1].startTime ?? "", + ); + } return { group, - schedulePattern: buildGroupSchedulePattern(classgroupDates), + schedulePattern, }; } @@ -54,31 +84,17 @@ export async function getPlannerCourseGroupsAction( ): Promise { const editionDetails = await getCourseEditionDetailsAction(courseId, termId); - const [groupsWithPatterns, lecturersMap] = await Promise.all([ - Promise.all( - editionDetails.groups.map(async (group) => fetchGroupWithPattern(group)), + const groupsWithPatterns = await Promise.all( + editionDetails.groups.map(async (group) => + fetchGroupWithPattern(group, termId), ), - (async () => { - const uniqueLecturerIds = [ - ...new Set(editionDetails.groups.flatMap((g) => g.lecturerIds)), - ]; - const lecturerEntries = await Promise.all( - uniqueLecturerIds.map(async (id) => { - const lecturer = await getLecturerAction(id); - return [id, lecturer] as const; - }), - ); - return new Map(lecturerEntries); - })(), - ]); + ); return groupsWithPatterns.map(({ group, schedulePattern }) => ({ unitId: group.unitId, groupNumber: group.groupNumber, classtypeId: group.classtypeId, - lecturers: group.lecturerIds - .map((id) => lecturersMap.get(id)) - .filter((l): l is LecturerDTO => l != null), + lecturers: group.lecturers, schedulePattern, })); } diff --git a/src/actions/v2/get-term.ts b/src/actions/v2/get-term.ts new file mode 100644 index 00000000..7e5c1521 --- /dev/null +++ b/src/actions/v2/get-term.ts @@ -0,0 +1,48 @@ +"use server"; + +import redis from "@/lib/redis"; +import { getOrSetRedis } from "@/lib/redis/get-set"; +import { fetchUsosApi } from "@/lib/usos"; + +interface TermDTO { + id: string; + name: string; + startDate: string; + endDate: string; + finishDate: string; + isActive: boolean; +} + +interface UsosTerm { + id: string; + name: { pl: string }; + start_date: string; + end_date: string; + finish_date: string; + is_active: boolean; +} + +function normalizeTerm(data: UsosTerm): TermDTO { + return { + id: data.id, + name: data.name.pl, + startDate: data.start_date, + endDate: data.end_date, + finishDate: data.finish_date, + isActive: data.is_active, + }; +} + +export async function getTermAction(termId: string): Promise { + return getOrSetRedis({ + redis, + key: `usos:term:${termId}:`, + ttlSeconds: 60 * 60 * 24 * 7, + fetcher: async () => { + const data = await fetchUsosApi("terms/term", { + term_id: termId, + }); + return normalizeTerm(data); + }, + }); +} diff --git a/src/app/plans/edit/[id]/page.client.tsx b/src/app/plans/edit/[id]/page.client.tsx index a21336ca..57dbc076 100644 --- a/src/app/plans/edit/[id]/page.client.tsx +++ b/src/app/plans/edit/[id]/page.client.tsx @@ -10,6 +10,7 @@ import { toast } from "sonner"; import { getPlan } from "@/actions/plans"; //import { CourseGroupDTO, getCourseEditionGroupsAction } from "@/actions/v2/get-course-editions-details"; import { getPlannerCourseGroupsAction } from "@/actions/v2/get-course-groups-for-planner"; +import type { LecturerDTO } from "@/actions/v2/get-lecturer"; import { getRegistrationRoundsAction } from "@/actions/v2/get-registration-rounds"; //import { getBatchCoursePreviewAction } from "@/actions/v2/get-course-editions-preview"; import { getRegistrationRoundCoursesAction } from "@/actions/v2/get-round-courses"; @@ -31,6 +32,7 @@ import { useShare } from "@/hooks/use-share"; //import { fetchClient } from "@/lib/fetch"; import { usePlan } from "@/lib/use-plan"; import { cn } from "@/lib/utils"; +import type { ScheduleParity } from "@/lib/utils/build-group-schedule-pattern"; import { updateSpotsOccupied } from "@/lib/utils/update-spots-occupied"; import { Day } from "@/types"; import type { CourseType, SingleCourse /*SingleGroup*/ } from "@/types"; @@ -43,7 +45,8 @@ import { SaveOfflineFunction } from "./_components/save-offline"; import { SaveOnlineFunction } from "./_components/save-online"; //TODO: usunac zakomentowane importy i logi, moze przeniesc funkcje pomocnicza? -//Dodac walidacje group schedule pattern starttime/endtime: line 146-151 +//Dodac walidacje group schedule pattern starttime/endtime +//zajetosc grup const getDayOfWeek = (startTime: string) => { const date = new Date(startTime).getDay(); @@ -75,6 +78,34 @@ const getDayOfWeek = (startTime: string) => { } }; +const getLecturersString = (lecturers: LecturerDTO[]): string => { + let output = ""; + for (const l of lecturers) { + output += `${l.firstName} ${l.lastName}`; + } + //console.log("Wykladowcy", output) + return output; +}; + +const translateSchedulePatternParity = ( + parity: ScheduleParity, +): "-" | "TN" | "TP" | "!" => { + switch (parity) { + case "all": { + return "-"; + } + case "even": { + return "TP"; + } + case "odd": { + return "TN"; + } + case "unknown": { + return "!"; + } + } +}; + export function CreateNewPlanPage({ planId }: { planId: string }) { const session = useSession(); const isLoggedIn = session.data !== null; @@ -142,8 +173,9 @@ export function CreateNewPlanPage({ planId }: { planId: string }) { }; //console.log("przed zmianami", groups) for (const group of groups) { + //console.log("grupa", group) newCourse.groups.push({ - id: index, + id: Math.random() * index, name: course.courseName, averageRating: "0.0", opinionsCount: 0, @@ -151,12 +183,23 @@ export function CreateNewPlanPage({ planId }: { planId: string }) { courseId: course.courseId, createdAt: "", updatedAt: "", - spotsOccupied: 0, - spotsTotal: 0, + spotsOccupied: course.registrationsCount, + spotsTotal: course.limits, isActive: true, url: "", - lecturer: "", - lecturers: [], + lecturer: getLecturersString(group.lecturers), + lecturers: group.lecturers.map((lecturer) => { + return { + ...lecturer, + name: lecturer.firstName, + surname: lecturer.lastName, + createdAt: "", + updatedAt: "", + averageRating: "0", + opinionsCount: "0", + id: Number.parseInt(lecturer.id), + }; + }), group: group.groupNumber, meetings: [ @@ -165,7 +208,9 @@ export function CreateNewPlanPage({ planId }: { planId: string }) { groupId: Number.parseInt(group.groupNumber), startTime: group.schedulePattern?.startTime ?? "7:30", endTime: group.schedulePattern?.endTime ?? "9:00", - week: "-", + week: translateSchedulePatternParity( + group.schedulePattern?.parity ?? "all", + ), createdAt: "", updatedAt: "", day: getDayOfWeek( diff --git a/src/lib/utils/build-group-schedule-pattern.ts b/src/lib/utils/build-group-schedule-pattern.ts index 5cea6025..0ea0aa5a 100644 --- a/src/lib/utils/build-group-schedule-pattern.ts +++ b/src/lib/utils/build-group-schedule-pattern.ts @@ -21,7 +21,6 @@ export interface GroupSchedulePattern { } const WEEKLY_GAP = 7; -const BIWEEKLY_GAP = 14; const GAP_TOLERANCE = 2; function daysBetween(a: string, b: string): number { @@ -53,6 +52,32 @@ function addDays(isoDate: string, days: number): string { return d.toISOString().slice(0, 10); } +function getGapsMode(numbers: number[]): number[] { + if (numbers.length === 0) { + return []; + } + + const frequencyMap: Record = {}; + let maxFreq = 0; + + for (const gap of numbers) { + frequencyMap[gap] = (frequencyMap[gap] || 0) + 1; + if (frequencyMap[gap] > maxFreq) { + maxFreq = frequencyMap[gap]; + } + } + + const modes: number[] = []; + + for (const key in frequencyMap) { + if (frequencyMap[key] === maxFreq) { + modes.push(Number(key)); + } + } + + return modes; +} + export function buildGroupSchedulePattern( dates: ClassgroupDate[], ): GroupSchedulePattern | null { @@ -66,7 +91,6 @@ export function buildGroupSchedulePattern( if (sorted.length > 1) { firstEntry = sorted[1]; //first date is often irregular, like in 15h course first classes can be 45mins, so if its possible, we can take second } - //const firstEntry = sorted[0]; const lastEntry = sorted.at(-1) ?? firstEntry; if (sorted.length === 1) { @@ -88,15 +112,14 @@ export function buildGroupSchedulePattern( .map((entry, index) => daysBetween(sorted[index].date, entry.date)); const weeklyGaps = gaps.filter((g) => isNear(g, WEEKLY_GAP)); - const biweeklyGaps = gaps.filter((g) => isNear(g, BIWEEKLY_GAP)); const total = gaps.length; let pattern: SchedulePattern; let parity: ScheduleParity; let exceptions: string[] = []; - const allWeekly = weeklyGaps.length === total; - const allBiweekly = biweeklyGaps.length === total; + const allWeekly = getGapsMode(gaps).includes(7); + const allBiweekly = getGapsMode(gaps).includes(14); if (allWeekly) { pattern = "weekly"; From 91b14eaf36df0c4aab4980e0316509f7efdc45f9 Mon Sep 17 00:00:00 2001 From: Oliwier Popielarczyk <71080644+olipop210@users.noreply.github.com> Date: Thu, 16 Jul 2026 12:59:31 +0200 Subject: [PATCH 08/12] fix: hour divider height "screen" value was too short for big registrations --- src/components/hour.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/hour.tsx b/src/components/hour.tsx index eecae80a..c81ebd7f 100644 --- a/src/components/hour.tsx +++ b/src/components/hour.tsx @@ -15,7 +15,7 @@ function Hour({ hour, widthPx = "100" }: { hour: string; widthPx?: string }) { "relative z-0 text-xs leading-6 text-gray-500 after:absolute after:-z-10 after:bg-slate-200 dark:after:bg-slate-800", isHorizontal ? `after:top-1/2 after:h-[1px] after:w-[var(--after-width)] after:min-w-[130px]` - : "-translate-x-1/2 transform text-center after:left-1/2 after:top-[-30px] after:h-screen after:w-[1px]", + : "-translate-x-1/2 transform text-center after:left-1/2 after:top-[-30px] after:h-[5000px] after:w-[1px]", )} style={ isHorizontal From 014cd3d5cd6181376a819647383fecf025720323 Mon Sep 17 00:00:00 2001 From: Oliwier Popielarczyk <71080644+olipop210@users.noreply.github.com> Date: Thu, 16 Jul 2026 12:59:44 +0200 Subject: [PATCH 09/12] fix: format --- src/actions/v2/get-class-group-dates.ts | 2 +- src/actions/v2/get-course-edition-details.ts | 8 +- src/actions/v2/get-faculties.ts | 176 +++++++++--------- .../edit/[id]/_components/app-sidebar.tsx | 3 +- 4 files changed, 94 insertions(+), 95 deletions(-) diff --git a/src/actions/v2/get-class-group-dates.ts b/src/actions/v2/get-class-group-dates.ts index 7bbfd12c..034d8c0e 100644 --- a/src/actions/v2/get-class-group-dates.ts +++ b/src/actions/v2/get-class-group-dates.ts @@ -1,4 +1,4 @@ -'use server'; +"use server"; import redis from "@/lib/redis"; import { getOrSetRedis } from "@/lib/redis/get-set"; diff --git a/src/actions/v2/get-course-edition-details.ts b/src/actions/v2/get-course-edition-details.ts index f1b8da76..2aeff867 100644 --- a/src/actions/v2/get-course-edition-details.ts +++ b/src/actions/v2/get-course-edition-details.ts @@ -3,7 +3,8 @@ import redis from "@/lib/redis"; import { getOrSetRedis } from "@/lib/redis/get-set"; import { fetchUsosApi } from "@/lib/usos"; -import type { UsosLecturer, LecturerDTO } from "./get-lecturer"; + +import type { LecturerDTO, UsosLecturer } from "./get-lecturer"; interface UsosCourseEdition { course_id: string; @@ -12,7 +13,6 @@ interface UsosCourseEdition { } function normalizeLecturers(lecturers: UsosLecturer[]): LecturerDTO[] { - return lecturers.map((lecturer) => { return { id: lecturer.id, @@ -23,8 +23,8 @@ function normalizeLecturers(lecturers: UsosLecturer[]): LecturerDTO[] { photoUrl: lecturer.photo_urls?.["50x50"] ?? null, homepageUrl: lecturer.homepage_url ?? null, profileUrl: lecturer.profile_url ?? null, - } - }) + }; + }); } interface UsosCourseUnitGroup { diff --git a/src/actions/v2/get-faculties.ts b/src/actions/v2/get-faculties.ts index 95088ff1..4e8968df 100644 --- a/src/actions/v2/get-faculties.ts +++ b/src/actions/v2/get-faculties.ts @@ -1,97 +1,97 @@ interface Faculty { - value: string, - name: string + value: string; + name: string; } interface getFacultiesDTO { - data: Faculty[], - isLoading: boolean, - isError: boolean + data: Faculty[]; + isLoading: boolean; + isError: boolean; } //in future can be replaced with database get export function getFacultiesAction(): getFacultiesDTO { - return { - data: [ - { - value: "W1", - name: "Wydział Architektury", - }, - { - value: "W2", - name: "Wydział Budownictwa Lądowego i Wodnego", - }, - { - value: "W3", - name: "Wydział Chemiczny", - }, - { - value: "W4N", - name: "Wydział Informatyki i Telekomunikacji", - }, - { - value: "W5", - name: "Wydział Elektryczny", - }, - { - value: "W6", - name: "Wydział Geoinżynierii, Górnictwa i Geologii", - }, - { - value: "W7", - name: "Wydział Inżynierii Środowiska", - }, - { - value: "W8N", - name: "Wydział Zarządzania", - }, - { - value: "W9", - name: "Wydział Mechaniczno-Energetyczny", - }, - { - value: "W10", - name: "Wydział Mechaniczny", - }, - { - value: "W11", - name: "Wydział Podstawowych Problemów Techniki", - }, - { - value: "W12", - name: "Wydział Elektroniki, Fotoniki i Mikrosystemów", - }, - { - value: "W13", - name: "Wydział Matematyki", - }, - { - value: "W14", - name: "Wydział Medyczny", - }, - { - value: "FLG", - name: "Filia w Legnicy", - }, - { - value: "SD1", - name: "Szkoła Doktorska Politechniki Wrocławskiej", - }, - { - value: "PRK24/S1", - name: "PRK24/S1", - }, - { - value: "PRK24/S3", - name: "PRK24/S3", - }, - { - value: "PWR", - name: "PWR", - }, - ], - isLoading: false, - isError: false, - } -} \ No newline at end of file + return { + data: [ + { + value: "W1", + name: "Wydział Architektury", + }, + { + value: "W2", + name: "Wydział Budownictwa Lądowego i Wodnego", + }, + { + value: "W3", + name: "Wydział Chemiczny", + }, + { + value: "W4N", + name: "Wydział Informatyki i Telekomunikacji", + }, + { + value: "W5", + name: "Wydział Elektryczny", + }, + { + value: "W6", + name: "Wydział Geoinżynierii, Górnictwa i Geologii", + }, + { + value: "W7", + name: "Wydział Inżynierii Środowiska", + }, + { + value: "W8N", + name: "Wydział Zarządzania", + }, + { + value: "W9", + name: "Wydział Mechaniczno-Energetyczny", + }, + { + value: "W10", + name: "Wydział Mechaniczny", + }, + { + value: "W11", + name: "Wydział Podstawowych Problemów Techniki", + }, + { + value: "W12", + name: "Wydział Elektroniki, Fotoniki i Mikrosystemów", + }, + { + value: "W13", + name: "Wydział Matematyki", + }, + { + value: "W14", + name: "Wydział Medyczny", + }, + { + value: "FLG", + name: "Filia w Legnicy", + }, + { + value: "SD1", + name: "Szkoła Doktorska Politechniki Wrocławskiej", + }, + { + value: "PRK24/S1", + name: "PRK24/S1", + }, + { + value: "PRK24/S3", + name: "PRK24/S3", + }, + { + value: "PWR", + name: "PWR", + }, + ], + isLoading: false, + isError: false, + }; +} diff --git a/src/app/plans/edit/[id]/_components/app-sidebar.tsx b/src/app/plans/edit/[id]/_components/app-sidebar.tsx index 667005ce..b960f635 100644 --- a/src/app/plans/edit/[id]/_components/app-sidebar.tsx +++ b/src/app/plans/edit/[id]/_components/app-sidebar.tsx @@ -6,6 +6,7 @@ import { isEqual } from "date-fns"; import { format } from "date-fns/format"; import React from "react"; +import { getFacultiesAction } from "@/actions/v2/get-faculties"; import { getFacultyRegistrationsAction } from "@/actions/v2/get-faculty-registrations"; import { Alerts } from "@/components/alerts"; import { AlgorithmDialog } from "@/components/algo-dialog"; @@ -37,7 +38,6 @@ import type { CourseType, PlanResponseType } from "@/types"; import { OfflineAlert } from "./offline-alert"; import { SyncErrorAlert } from "./sync-error-alert"; import { SyncedButton } from "./synced-button"; -import { getFacultiesAction } from "@/actions/v2/get-faculties"; export function AppSidebar({ plan, @@ -64,7 +64,6 @@ export function AppSidebar({ offlineAlert: boolean; faculty: string | null; }) { - const faculties = getFacultiesAction(); const registrations = useQuery({ From 44f5a9c983e8269df2b79e34277658148f1d0f32 Mon Sep 17 00:00:00 2001 From: Oliwier Popielarczyk <71080644+olipop210@users.noreply.github.com> Date: Thu, 16 Jul 2026 13:00:01 +0200 Subject: [PATCH 10/12] fix: format --- src/actions/v2/get-lecturer.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/actions/v2/get-lecturer.ts b/src/actions/v2/get-lecturer.ts index 863ccc71..652fc4c3 100644 --- a/src/actions/v2/get-lecturer.ts +++ b/src/actions/v2/get-lecturer.ts @@ -1,10 +1,10 @@ -'use server'; +"use server"; import redis from "@/lib/redis"; import { getOrSetRedis } from "@/lib/redis/get-set"; import { fetchUsosApi } from "@/lib/usos"; -interface UsosLecturer { +export interface UsosLecturer { id: string; first_name: string; last_name: string; From a8a2d92a995a9fb4633536721be21dd30649882c Mon Sep 17 00:00:00 2001 From: Oliwier Popielarczyk <71080644+olipop210@users.noreply.github.com> Date: Thu, 16 Jul 2026 13:49:13 +0200 Subject: [PATCH 11/12] fix: groups pattern and parity detecting startTime and endTime is now based on mode value --- src/lib/utils/build-group-schedule-pattern.ts | 62 ++++++++++++++++--- 1 file changed, 54 insertions(+), 8 deletions(-) diff --git a/src/lib/utils/build-group-schedule-pattern.ts b/src/lib/utils/build-group-schedule-pattern.ts index 0ea0aa5a..85d23025 100644 --- a/src/lib/utils/build-group-schedule-pattern.ts +++ b/src/lib/utils/build-group-schedule-pattern.ts @@ -78,6 +78,53 @@ function getGapsMode(numbers: number[]): number[] { return modes; } +function getHoursMode(hours: string[]): string { + const frequencyMap: Record = {}; + let maxCount = 0; + let mode = hours[0]; + + for (const item of hours) { + frequencyMap[item] = (frequencyMap[item] || 0) + 1; + + if (frequencyMap[item] > maxCount) { + maxCount = frequencyMap[item]; + mode = item; + } + } + + return mode; +} + +export function getMostFrequentTimes(entries: ClassgroupDate[]): { + startTime: string; + endTime: string; +} { + const startTimes: string[] = []; + const endTimes: string[] = []; + + for (const entry of entries) { + const startTimePart = entry.startTime.split(" ")[1]; + const endTimePart = entry.endTime.split(" ")[1]; + + if (startTimePart) { + startTimes.push(startTimePart); + } + if (endTimePart) { + endTimes.push(endTimePart); + } + } + + const modeStartTime = getHoursMode(startTimes); + const modeEndTime = getHoursMode(endTimes); + + const baseDate = entries[0].date; + + return { + startTime: `${baseDate} ${modeStartTime}`, + endTime: `${baseDate} ${modeEndTime}`, + }; +} + export function buildGroupSchedulePattern( dates: ClassgroupDate[], ): GroupSchedulePattern | null { @@ -87,19 +134,18 @@ export function buildGroupSchedulePattern( const sorted = [...dates].toSorted((a, b) => a.date.localeCompare(b.date)); const firstDate = new Date(sorted[0].date); - let firstEntry = sorted[0]; - if (sorted.length > 1) { - firstEntry = sorted[1]; //first date is often irregular, like in 15h course first classes can be 45mins, so if its possible, we can take second - } + const firstEntry = sorted[0]; const lastEntry = sorted.at(-1) ?? firstEntry; + const { startTime, endTime } = getMostFrequentTimes(dates); + if (sorted.length === 1) { return { pattern: "irregular", parity: "unknown", weekday: isoWeekday(firstDate), - startTime: firstEntry.startTime, - endTime: firstEntry.endTime, + startTime, + endTime, firstOccurrence: firstEntry.date, lastOccurrence: lastEntry.date, occurrencesCount: 1, @@ -154,8 +200,8 @@ export function buildGroupSchedulePattern( pattern, parity, weekday: isoWeekday(firstDate), - startTime: firstEntry.startTime, - endTime: firstEntry.endTime, + startTime, + endTime, firstOccurrence: firstEntry.date, lastOccurrence: lastEntry.date, occurrencesCount: sorted.length, From 5c713e1fd2582906ce0049605d731eb468960eec Mon Sep 17 00:00:00 2001 From: Oliwier Popielarczyk <71080644+olipop210@users.noreply.github.com> Date: Thu, 16 Jul 2026 13:57:07 +0200 Subject: [PATCH 12/12] chore: code cleaning --- src/actions/v2/get-course-groups-for-planner.ts | 2 -- src/app/plans/edit/[id]/page.client.tsx | 2 -- 2 files changed, 4 deletions(-) diff --git a/src/actions/v2/get-course-groups-for-planner.ts b/src/actions/v2/get-course-groups-for-planner.ts index cf50b541..0804cf79 100644 --- a/src/actions/v2/get-course-groups-for-planner.ts +++ b/src/actions/v2/get-course-groups-for-planner.ts @@ -13,8 +13,6 @@ import { getCourseEditionDetailsAction } from "./get-course-edition-details"; import type { LecturerDTO } from "./get-lecturer"; import { getTermAction } from "./get-term"; -//import { getLecturerAction } from "./get-lecturer"; - export interface PlannerGroupDTO { unitId: string; groupNumber: string; diff --git a/src/app/plans/edit/[id]/page.client.tsx b/src/app/plans/edit/[id]/page.client.tsx index 57dbc076..46e6d912 100644 --- a/src/app/plans/edit/[id]/page.client.tsx +++ b/src/app/plans/edit/[id]/page.client.tsx @@ -8,11 +8,9 @@ import { useEffect, useRef, useState } from "react"; import { toast } from "sonner"; import { getPlan } from "@/actions/plans"; -//import { CourseGroupDTO, getCourseEditionGroupsAction } from "@/actions/v2/get-course-editions-details"; import { getPlannerCourseGroupsAction } from "@/actions/v2/get-course-groups-for-planner"; import type { LecturerDTO } from "@/actions/v2/get-lecturer"; import { getRegistrationRoundsAction } from "@/actions/v2/get-registration-rounds"; -//import { getBatchCoursePreviewAction } from "@/actions/v2/get-course-editions-preview"; import { getRegistrationRoundCoursesAction } from "@/actions/v2/get-round-courses"; import { ClassSchedule } from "@/components/class-schedule"; import { Icons } from "@/components/icons";