From 7277dd5d302fc2789777b20721209c58f6c2d80b Mon Sep 17 00:00:00 2001 From: 11GG20 Date: Sat, 27 Sep 2025 16:43:59 +0100 Subject: [PATCH 1/6] feat: /students & /students/:id pages --- src/app/(app)/schedule/page.tsx | 2 +- .../backoffice/students/[student]/page.tsx | 74 ++++ .../settings/backoffice/students/page.tsx | 367 ++++++++++++++++++ src/app/globals.css | 4 +- src/components/schedule-calendar.tsx | 28 +- src/components/sidebar-settings.tsx | 6 +- src/contexts/schedule-provider.tsx | 76 +--- src/lib/backoffice.ts | 22 +- src/lib/queries/backoffice.ts | 18 + src/lib/types.ts | 36 ++ src/lib/utils.ts | 107 +++++ 11 files changed, 635 insertions(+), 105 deletions(-) create mode 100644 src/app/(app)/settings/backoffice/students/[student]/page.tsx create mode 100644 src/app/(app)/settings/backoffice/students/page.tsx diff --git a/src/app/(app)/schedule/page.tsx b/src/app/(app)/schedule/page.tsx index 04a81e6c..ea640d0c 100644 --- a/src/app/(app)/schedule/page.tsx +++ b/src/app/(app)/schedule/page.tsx @@ -14,7 +14,7 @@ export default function Schedule() {
- +
diff --git a/src/app/(app)/settings/backoffice/students/[student]/page.tsx b/src/app/(app)/settings/backoffice/students/[student]/page.tsx new file mode 100644 index 00000000..02577a9c --- /dev/null +++ b/src/app/(app)/settings/backoffice/students/[student]/page.tsx @@ -0,0 +1,74 @@ +"use client"; + +import CalendarView from "@/components/calendar/calendar"; +import SettingsWrapper from "@/components/settings-wrapper"; +import TabsGroup, { + PanelContainer, + Tab, + TabsContainer, + TabPanel, +} from "@/components/tabs"; +import { useGetStudentScheduleById } from "@/lib/queries/backoffice"; +import { extractShifts, formatIShift } from "@/lib/utils"; +import { useParams } from "next/navigation"; + +export default function Student() { + const params = useParams(); + + const { data: studentSchedule } = useGetStudentScheduleById( + params.student as string, + ); + + console.log("response:", studentSchedule); + + const formattedShifts = formatIShift(extractShifts(studentSchedule || [])); + console.log("Formatted:", formattedShifts); + + return ( + +
+
+
+

+ {"Gonçalo Fernandes"}{" "} + a111855 +

+
+
+ + + + + + + + + +

actions

+
+ +
+ +
+
+
+
+
+
+ ); +} diff --git a/src/app/(app)/settings/backoffice/students/page.tsx b/src/app/(app)/settings/backoffice/students/page.tsx new file mode 100644 index 00000000..dc6f3382 --- /dev/null +++ b/src/app/(app)/settings/backoffice/students/page.tsx @@ -0,0 +1,367 @@ +"use client"; + +import { AuthCheck } from "@/components/auth-check"; +import Avatar from "@/components/avatar"; +import SettingsWrapper from "@/components/settings-wrapper"; +import { useListStudents } from "@/lib/queries/backoffice"; +import { + FlopMetaParams, + FlopMetaResponse, + SortDirection, + User, +} from "@/lib/types"; +import { firstLastName } from "@/lib/utils"; +import clsx from "clsx"; +import Link from "next/link"; +import { + useState, + createContext, + useContext, + useMemo, + useCallback, +} from "react"; +import { twMerge } from "tailwind-merge"; + +interface SortState { + column: string | null; + direction: SortDirection; +} + +interface ITableContext { + meta: FlopMetaResponse; + setCurrentPage: (page: number) => void; + sortState: SortState; + handleSort: (column: string) => void; + getSortDirection: (column: string) => SortDirection; +} + +const TableContext = createContext({ + meta: { + sort: [], + filters: [], + page_size: 8, + current_page: 0, + next_page: 1, + previous_page: null, + total_pages: 0, + has_next_page: true, + has_previous_page: false, + total_entries: 0, + }, + setCurrentPage: () => {}, + sortState: { column: null, direction: SortDirection.NONE }, + handleSort: () => {}, + getSortDirection: () => SortDirection.NONE, +}); + +function Table({ children }: { children: React.ReactNode }) { + return
{children}
; +} + +function TableHeader() { + return ( +
+ + + + +
+
+ ); +} + +function HeaderElement({ + title, + className, + value, + sortable = false, +}: { + title: string; + className?: string; + value?: string; + sortable?: boolean; +}) { + const { handleSort, getSortDirection } = useContext(TableContext); + const currentDirection = getSortDirection(value!); + + const getSortIcon = (direction: SortDirection) => { + switch (direction) { + case SortDirection.ASC: + return "keyboard_arrow_up"; + case SortDirection.DESC: + return "keyboard_arrow_down"; + default: + return "unfold_more"; + } + }; + + return ( +
+ +
+ ); +} + +function TableContent({ children }: { children: React.ReactNode }) { + return
{children}
; +} + +function UserCard({ user }: { user: User }) { + return ( +
+
+ +

{firstLastName(user.name)}

+
+ +

{user.student?.number}

+ +

{user.email}

+ +

{user.student?.special_status}

+ +
+ + + edit + + +
+
+ ); +} + +function TablePagination({ + range, + entries, +}: { + range: string; + entries: number; +}) { + const { setCurrentPage, meta } = useContext(TableContext); + + return ( +
+

+ Showing {range} of{" "} + {entries}{" "} +

+ +
+ + +
+
+ ); +} + +export default function Students() { + const [currentPage, setCurrentPage] = useState(1); + const [search, setSearch] = useState(""); + + const [sortState, setSortState] = useState({ + column: null, + direction: SortDirection.NONE, + }); + + const handleSort = useCallback((column: string) => { + setSortState((prevState) => { + if (prevState.column === column) { + switch (prevState.direction) { + case SortDirection.NONE: + return { column, direction: SortDirection.ASC }; + case SortDirection.ASC: + return { column, direction: SortDirection.DESC }; + case SortDirection.DESC: + return { column: null, direction: SortDirection.NONE }; + default: + return { column, direction: SortDirection.ASC }; + } + } + return { column, direction: SortDirection.ASC }; + }); + + setCurrentPage(1); + }, []); + + const getSortDirection = useCallback( + (column: string): SortDirection => { + return sortState.column === column + ? sortState.direction + : SortDirection.NONE; + }, + [sortState], + ); + + const PAGE_SIZE = 8; + + const queryParams = useMemo(() => { + const filters = []; + + if (search.trim()) { + filters.push({ + field: "name", + op: "ilike_or", + value: search.trim(), + }); + } + + const params: FlopMetaParams = { + filters: filters, + page_size: PAGE_SIZE, + page: currentPage, + }; + + if (sortState.column && sortState.direction !== SortDirection.NONE) { + params["order_by[]"] = sortState.column; + params["order_directions[]"] = sortState.direction; + } + + return params; + }, [currentPage, search, sortState]); + + const { data: studentsResponse, isLoading } = useListStudents(queryParams); + + const meta = studentsResponse?.meta || queryParams; + const studentsList = studentsResponse?.users || []; + + const contextValue = { + meta: meta || { + sort: [], + filters: [], + page_size: PAGE_SIZE, + page: currentPage, + next_page: null, + previous_page: null, + total_pages: 0, + has_next_page: false, + has_previous_page: false, + total_entries: 0, + }, + setCurrentPage, + sortState, + handleSort, + getSortDirection, + }; + + return ( + + Pombo | Students + + +
+
+
+

Students

+
+ +
+ { + setSearch(e.target.value); + setCurrentPage(1); + }} + /> +
+
+ +
+
+ + + + + + {isLoading ? ( +

+ Loading... +

+ ) : studentsList.length > 0 ? ( + studentsList.map((user: User) => ( + + )) + ) : ( +

No users

+ )} +
+
+ + +
+
+
+
+
+
+ ); +} diff --git a/src/app/globals.css b/src/app/globals.css index 8a40a235..c1b7eb07 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -110,13 +110,13 @@ input[type="number"] { .rbc-calendar { width: 100% !important; - height: calc(100dvh - 124px) !important; + height: calc(100dvh - var(--desktop_height_var, 124px)) !important; border-radius: 15px !important; } @media (max-width: 768px) { .rbc-calendar { - height: calc(100dvh - 154px) !important; + height: calc(100dvh - var(--mobile_height_var, 154px)) !important; } } diff --git a/src/components/schedule-calendar.tsx b/src/components/schedule-calendar.tsx index 399e1fe4..a38e3575 100644 --- a/src/components/schedule-calendar.tsx +++ b/src/components/schedule-calendar.tsx @@ -2,8 +2,8 @@ import CalendarView from "./calendar/calendar"; import { useContext } from "react"; -import moment from "moment"; import { ScheduleContext } from "@/contexts/schedule-provider"; +import { formatIShift } from "@/lib/utils"; export default function ScheduleCalendar() { const context = useContext(ScheduleContext); @@ -11,31 +11,7 @@ export default function ScheduleCalendar() { const { isEditing, editingShifts = [] } = context; // Converts an IShift to an Event - const formattedEvents = editingShifts.map((shift) => { - const [startHour, startMinute] = shift.start.split(":"); - const [endHour, endMinute] = shift.end.split(":"); - - return { - title: `${shift.shortCourseName} - ${shift.shiftType}${shift.shiftNumber}`, - start: moment() - .day(shift.weekday + 1) - .hour(+startHour) - .minute(+startMinute) - .toDate(), - /* (*) we're subtracting 1 minute here to solve an issue that occurs when - * the end time of an event is equal to the start time of another. - * this issue causes the event bellow to think it is overlapping with the top one, - * when the `dayLayoutAlgorithm` is set to `no-overlap`. - */ - end: moment() - .day(shift.weekday + 1) - .hour(+endHour) - .minute(+endMinute - 1) // (*) - .toDate(), - allDay: false, - resource: shift, - }; - }); + const formattedEvents = formatIShift(editingShifts); return (
diff --git a/src/components/sidebar-settings.tsx b/src/components/sidebar-settings.tsx index 6e742e12..7f8470d5 100644 --- a/src/components/sidebar-settings.tsx +++ b/src/components/sidebar-settings.tsx @@ -59,7 +59,11 @@ export default function SidebarSettings() { - + + + + + diff --git a/src/contexts/schedule-provider.tsx b/src/contexts/schedule-provider.tsx index aa3625a5..396a5793 100644 --- a/src/contexts/schedule-provider.tsx +++ b/src/contexts/schedule-provider.tsx @@ -6,7 +6,8 @@ import { useGetStudentOriginalSchedule, useGetStudentSchedule, } from "@/lib/queries/courses"; -import { ICourse, IShift, IShiftsSorted } from "@/lib/types"; +import { IShift, IShiftsSorted } from "@/lib/types"; +import { extractShifts } from "@/lib/utils"; import { createContext, useEffect, useState } from "react"; interface IScheduleProvider { @@ -124,79 +125,6 @@ function sortShiftsByYearCourse(mixedShifts: IShift[]): IShiftsSorted { })); } -function extractShifts(courses: ICourse[]): IShift[] { - const { parentCourse, normalCourses } = courses.reduce( - (acc: { parentCourse: ICourse[]; normalCourses: ICourse[] }, course) => { - if (course.courses.length > 0) { - acc.parentCourse.push(course); - } else { - acc.normalCourses.push(course); - } - return acc; - }, - { parentCourse: [], normalCourses: [] }, - ); - - const shiftsWithNoParents = normalCourses.flatMap((course) => { - if (course.shifts && course.shifts.length > 0) { - return course.shifts.flatMap((shiftGroup) => - shiftGroup.timeslots.map((shift) => { - const WEEK_DAYS = [ - "monday", - "tuesday", - "wednesday", - "thursday", - "friday", - ]; - - const SHIFT_TYPES: Record = { - theoretical: "T", - theoretical_practical: "TP", - practical_laboratory: "PL", - tutorial_guidance: "OL", - }; - - const convertShiftType = (type: string): "PL" | "T" | "TP" | "OL" => { - return SHIFT_TYPES[type as keyof typeof SHIFT_TYPES]; - }; - - return { - id: shiftGroup.id, - courseName: course.name, - courseId: course.id, - shortCourseName: course.shortname, - professor: shiftGroup.professor ?? undefined, - weekday: WEEK_DAYS.indexOf(shift.weekday), - start: shift.start, - end: shift.end, - shiftType: convertShiftType(shiftGroup.type), - shiftNumber: shiftGroup.number, - building: shift.building - ? Number(shift.building) <= 3 - ? `CP${shift.building}` - : `Building ${shift.building}` - : null, - room: shift.room || null, - year: course.year, - semester: course.semester, - eventColor: "#C3E5F9", - textColor: "#227AAE", - status: shiftGroup.enrollment_status, - }; - }), - ); - } - return []; - }); - - const childShifts = - parentCourse.length > 0 - ? extractShifts(parentCourse.flatMap((c) => c.courses)) - : []; - - return [...shiftsWithNoParents, ...childShifts]; -} - export const ScheduleContext = createContext({ originalSchedule: [], currentSchedule: [], diff --git a/src/lib/backoffice.ts b/src/lib/backoffice.ts index 56c976fa..b81118d1 100644 --- a/src/lib/backoffice.ts +++ b/src/lib/backoffice.ts @@ -1,5 +1,5 @@ import { api } from "./api"; -import { IJobProps } from "./types"; +import { FlopMetaParams, IJobProps } from "./types"; export async function listJobs() { try { @@ -54,3 +54,23 @@ export async function exportGroupEnrollments(course_id: string) { ); } } + +export async function listStudents(params: FlopMetaParams) { + try { + const res = await api.get("/students", { params }); + return res.data; + } catch { + throw new Error("Failed to list Students. Please try again later."); + } +} + +export async function getStudentScheduleById(student_id: string) { + try { + const res = await api.get(`/student/schedule/${student_id}`); + return res.data.courses; + } catch { + throw new Error( + "Failed to get student's schedule. Please try again later.", + ); + } +} diff --git a/src/lib/queries/backoffice.ts b/src/lib/queries/backoffice.ts index d1aee26c..358fcfce 100644 --- a/src/lib/queries/backoffice.ts +++ b/src/lib/queries/backoffice.ts @@ -3,8 +3,11 @@ import { exportGroupEnrollments, exportShiftGroups, getDegrees, + getStudentScheduleById, listJobs, + listStudents, } from "../backoffice"; +import { FlopMetaParams } from "../types"; export function useListJobs() { return useQuery({ @@ -36,3 +39,18 @@ export function useExportGroupEnrollments(courseId: string) { enabled: false, }); } + +export function useListStudents(params: FlopMetaParams) { + return useQuery({ + queryKey: ["students-list", params], + queryFn: () => listStudents(params), + placeholderData: (previousData) => previousData, + }); +} + +export function useGetStudentScheduleById(studentId: string) { + return useQuery({ + queryKey: [`student-${studentId}-schedule`, studentId], + queryFn: () => getStudentScheduleById(studentId), + }); +} diff --git a/src/lib/types.ts b/src/lib/types.ts index 0a64a159..a4cae21e 100644 --- a/src/lib/types.ts +++ b/src/lib/types.ts @@ -9,6 +9,14 @@ export interface User { name: string; email: string; type: string; + student?: Student; +} + +export interface Student { + id: string; + number: string; + degree_year: number | null; + special_status: string; } export interface ITimeSlot { @@ -106,3 +114,31 @@ export interface IItemProps { id: string; name: string; } + +export enum SortDirection { + NONE = "none", + ASC = "asc", + DESC = "desc", +} + +export interface FlopMetaParams { + "order_by[]"?: string; + "order_directions[]"?: string; + filters: { field: string; op: string; value: string }[]; + page_size: number; + page: number; +} + +export interface FlopMetaResponse { + sort: string[]; + filters: string[]; + page_size: number; + current_page: number; + + next_page?: number | null; + previous_page?: number | null; + total_pages?: number; + has_next_page?: boolean; + has_previous_page?: boolean; + total_entries?: number; +} diff --git a/src/lib/utils.ts b/src/lib/utils.ts index ab81fb5f..c67d9003 100644 --- a/src/lib/utils.ts +++ b/src/lib/utils.ts @@ -1,3 +1,6 @@ +import moment from "moment"; +import { ICourse, IShift } from "./types"; + export function firstLastName(name: string | undefined) { if (!name) return ""; @@ -12,6 +15,7 @@ export function firstLastName(name: string | undefined) { return name?.split(" ").filter(Boolean); } +// Converts a hex color to rgba, enabling opacity and darkening options export const editColor = (color: string, opacity: number, darken: number) => { const r = Math.floor(parseInt(color.slice(1, 3), 16) * darken); const g = Math.floor(parseInt(color.slice(3, 5), 16) * darken); @@ -21,3 +25,106 @@ export const editColor = (color: string, opacity: number, darken: number) => { return rgbaColor; }; + +// Converts an IShift to an Event +export function formatIShift(shifts: IShift[]) { + return shifts.map((shift) => { + const [startHour, startMinute] = shift.start.split(":"); + const [endHour, endMinute] = shift.end.split(":"); + + return { + title: `${shift.shortCourseName} - ${shift.shiftType}${shift.shiftNumber}`, + start: moment() + .day(shift.weekday + 1) + .hour(+startHour) + .minute(+startMinute) + .toDate(), + /* (*) we're subtracting 1 minute here to solve an issue that occurs when + * the end time of an event is equal to the start time of another. + * this issue causes the event bellow to think it is overlapping with the top one, + * when the `dayLayoutAlgorithm` is set to `no-overlap`. + */ + end: moment() + .day(shift.weekday + 1) + .hour(+endHour) + .minute(+endMinute - 1) // (*) + .toDate(), + allDay: false, + resource: shift, + }; + }); +} + +// Extracts IShifts from ICourses +export function extractShifts(courses: ICourse[]): IShift[] { + const { parentCourse, normalCourses } = courses.reduce( + (acc: { parentCourse: ICourse[]; normalCourses: ICourse[] }, course) => { + if (course.courses.length > 0) { + acc.parentCourse.push(course); + } else { + acc.normalCourses.push(course); + } + return acc; + }, + { parentCourse: [], normalCourses: [] }, + ); + + const shiftsWithNoParents = normalCourses.flatMap((course) => { + if (course.shifts && course.shifts.length > 0) { + return course.shifts.flatMap((shiftGroup) => + shiftGroup.timeslots.map((shift) => { + const WEEK_DAYS = [ + "monday", + "tuesday", + "wednesday", + "thursday", + "friday", + ]; + + const SHIFT_TYPES: Record = { + theoretical: "T", + theoretical_practical: "TP", + practical_laboratory: "PL", + tutorial_guidance: "OL", + }; + + const convertShiftType = (type: string): "PL" | "T" | "TP" | "OL" => { + return SHIFT_TYPES[type as keyof typeof SHIFT_TYPES]; + }; + + return { + id: shiftGroup.id, + courseName: course.name, + courseId: course.id, + shortCourseName: course.shortname, + professor: shiftGroup.professor ?? undefined, + weekday: WEEK_DAYS.indexOf(shift.weekday), + start: shift.start, + end: shift.end, + shiftType: convertShiftType(shiftGroup.type), + shiftNumber: shiftGroup.number, + building: shift.building + ? Number(shift.building) <= 3 + ? `CP${shift.building}` + : `Building ${shift.building}` + : null, + room: shift.room || null, + year: course.year, + semester: course.semester, + eventColor: "#C3E5F9", + textColor: "#227AAE", + status: shiftGroup.enrollment_status, + }; + }), + ); + } + return []; + }); + + const childShifts = + parentCourse.length > 0 + ? extractShifts(parentCourse.flatMap((c) => c.courses)) + : []; + + return [...shiftsWithNoParents, ...childShifts]; +} From af41436ded83ad94d9f37c8e44abc6923524369f Mon Sep 17 00:00:00 2001 From: gxnca <11gg20@gmail.com> Date: Tue, 7 Oct 2025 11:30:08 +0100 Subject: [PATCH 2/6] feat: connect to backend --- .../{[student] => [student_id]}/page.tsx | 47 ++++++++++++++----- .../settings/backoffice/students/page.tsx | 19 ++++---- src/lib/backoffice.ts | 11 +++++ src/lib/events.ts | 37 +++++++++++++++ src/lib/queries/backoffice.ts | 8 ++++ src/lib/queries/events.ts | 32 +++++++++++++ src/lib/types.ts | 16 +++---- 7 files changed, 142 insertions(+), 28 deletions(-) rename src/app/(app)/settings/backoffice/students/{[student] => [student_id]}/page.tsx (56%) create mode 100644 src/lib/events.ts create mode 100644 src/lib/queries/events.ts diff --git a/src/app/(app)/settings/backoffice/students/[student]/page.tsx b/src/app/(app)/settings/backoffice/students/[student_id]/page.tsx similarity index 56% rename from src/app/(app)/settings/backoffice/students/[student]/page.tsx rename to src/app/(app)/settings/backoffice/students/[student_id]/page.tsx index 02577a9c..2fdd25ee 100644 --- a/src/app/(app)/settings/backoffice/students/[student]/page.tsx +++ b/src/app/(app)/settings/backoffice/students/[student_id]/page.tsx @@ -8,21 +8,24 @@ import TabsGroup, { TabsContainer, TabPanel, } from "@/components/tabs"; -import { useGetStudentScheduleById } from "@/lib/queries/backoffice"; +import { useForgotPassword } from "@/lib/mutations/session"; +import { useGetStudentById, useGetStudentScheduleById } from "@/lib/queries/backoffice"; import { extractShifts, formatIShift } from "@/lib/utils"; +import clsx from "clsx"; import { useParams } from "next/navigation"; +import { twMerge } from "tailwind-merge"; export default function Student() { const params = useParams(); + + const { data: student} = useGetStudentById(params.student_id as string); - const { data: studentSchedule } = useGetStudentScheduleById( - params.student as string, - ); - - console.log("response:", studentSchedule); + const { data: studentSchedule } = useGetStudentScheduleById(params.student_id as string); const formattedShifts = formatIShift(extractShifts(studentSchedule || [])); - console.log("Formatted:", formattedShifts); + + const forgotPassword = useForgotPassword(); + return ( @@ -30,8 +33,8 @@ export default function Student() {

- {"Gonçalo Fernandes"}{" "} - a111855 + {`${student?.user.name} - `} + {student?.number}

@@ -47,8 +50,30 @@ export default function Student() { - -

actions

+ + + + {forgotPassword.isSuccess && ( +

Forgot Password Email was sent to the user

+ )} + + {forgotPassword.isError && ( +

Something went wrong

+ )} +
{children}
; } -function UserCard({ user }: { user: User }) { +function UserCard({ student }: { student: Student }) { return (
- -

{firstLastName(user.name)}

+ +

{firstLastName(student.user.name)}

-

{user.student?.number}

+

{student.number}

-

{user.email}

+

{student.user.email}

-

{user.student?.special_status}

+

{student.special_status}

@@ -344,8 +345,8 @@ export default function Students() { Loading...

) : studentsList.length > 0 ? ( - studentsList.map((user: User) => ( - + studentsList.map((student: Student) => ( + )) ) : (

No users

diff --git a/src/lib/backoffice.ts b/src/lib/backoffice.ts index b81118d1..0a8cffb8 100644 --- a/src/lib/backoffice.ts +++ b/src/lib/backoffice.ts @@ -74,3 +74,14 @@ export async function getStudentScheduleById(student_id: string) { ); } } + +export async function getStudentById(id: string) { + try { + const res = await api.get(`/student/${id}`); + return res.data.student; + } catch { + throw new Error( + `Failed to get student with id-${id}. Please try again later.`, + ); + } +} \ No newline at end of file diff --git a/src/lib/events.ts b/src/lib/events.ts new file mode 100644 index 00000000..7408d0b8 --- /dev/null +++ b/src/lib/events.ts @@ -0,0 +1,37 @@ +import { api } from "./api"; + +export async function getEvents() { + try { + const res = await api.get("/events"); + return res.data.events; + } catch { + throw new Error(`Failed to fetch events. Please try again later.`); + } +} + +export async function getEventById(id: string) { + try { + const res = await api.get(`/events/${id}`); + return res.data.events; + } catch { + throw new Error(`Failed to fetch event with id-${id}. Please try again later.`); + } +} + +export async function getCategories() { + try { + const res = await api.get("/event_categories"); + return res.data.categories; + } catch { + throw new Error(`Failed to fetch categories. Please try again later.`); + } +} + +export async function getCategoryById(id: string) { + try { + const res = await api.get(`/event_categories/${id}`); + return res.data.categories; + } catch { + throw new Error(`Failed to fetch category with id-${id}. Please try again later.`); + } +} \ No newline at end of file diff --git a/src/lib/queries/backoffice.ts b/src/lib/queries/backoffice.ts index 358fcfce..49f337d5 100644 --- a/src/lib/queries/backoffice.ts +++ b/src/lib/queries/backoffice.ts @@ -3,6 +3,7 @@ import { exportGroupEnrollments, exportShiftGroups, getDegrees, + getStudentById, getStudentScheduleById, listJobs, listStudents, @@ -54,3 +55,10 @@ export function useGetStudentScheduleById(studentId: string) { queryFn: () => getStudentScheduleById(studentId), }); } + +export function useGetStudentById(id: string) { + return useQuery({ + queryKey: ["student", id], + queryFn: () => getStudentById(id), + }) +} diff --git a/src/lib/queries/events.ts b/src/lib/queries/events.ts new file mode 100644 index 00000000..91459270 --- /dev/null +++ b/src/lib/queries/events.ts @@ -0,0 +1,32 @@ +import { useQuery } from "@tanstack/react-query"; +import { getCategories, getCategoryById, getEventById, getEvents } from "../events"; + +export function useGetEvents() { + return useQuery({ + queryKey: ["events"], + queryFn: getEvents, + }); +} + +export function useGetEventById(id: string) { + return useQuery({ + queryKey: ["event", id], + queryFn: () => getEventById(id), + }); +} + +export function useGetCategories() { + return useQuery({ + queryKey: ["categories"], + queryFn: getCategories, + }); +} + +export function useGetCategoryById(id: string) { + return useQuery({ + queryKey: ["category", id], + queryFn: () => getCategoryById(id), + }); +} + + diff --git a/src/lib/types.ts b/src/lib/types.ts index a4cae21e..bdc6f205 100644 --- a/src/lib/types.ts +++ b/src/lib/types.ts @@ -4,19 +4,19 @@ export enum UserType { professor, } -export interface User { - id: string; - name: string; - email: string; - type: string; - student?: Student; -} - export interface Student { id: string; number: string; degree_year: number | null; special_status: string; + user: User; +} + +export interface User { + id: string; + name: string; + email: string; + type: string; } export interface ITimeSlot { From 9a41b87b15029d14026737d7767712d28048cad6 Mon Sep 17 00:00:00 2001 From: gxnca <11gg20@gmail.com> Date: Tue, 7 Oct 2025 11:31:09 +0100 Subject: [PATCH 3/6] fix: format & lint --- .../backoffice/students/[student_id]/page.tsx | 29 ++++++++++++------- .../settings/backoffice/students/page.tsx | 1 - src/lib/backoffice.ts | 2 +- src/lib/events.ts | 10 +++++-- src/lib/queries/backoffice.ts | 2 +- src/lib/queries/events.ts | 9 ++++-- 6 files changed, 34 insertions(+), 19 deletions(-) diff --git a/src/app/(app)/settings/backoffice/students/[student_id]/page.tsx b/src/app/(app)/settings/backoffice/students/[student_id]/page.tsx index 2fdd25ee..553da5ae 100644 --- a/src/app/(app)/settings/backoffice/students/[student_id]/page.tsx +++ b/src/app/(app)/settings/backoffice/students/[student_id]/page.tsx @@ -9,7 +9,10 @@ import TabsGroup, { TabPanel, } from "@/components/tabs"; import { useForgotPassword } from "@/lib/mutations/session"; -import { useGetStudentById, useGetStudentScheduleById } from "@/lib/queries/backoffice"; +import { + useGetStudentById, + useGetStudentScheduleById, +} from "@/lib/queries/backoffice"; import { extractShifts, formatIShift } from "@/lib/utils"; import clsx from "clsx"; import { useParams } from "next/navigation"; @@ -17,16 +20,17 @@ import { twMerge } from "tailwind-merge"; export default function Student() { const params = useParams(); - - const { data: student} = useGetStudentById(params.student_id as string); - const { data: studentSchedule } = useGetStudentScheduleById(params.student_id as string); + const { data: student } = useGetStudentById(params.student_id as string); + + const { data: studentSchedule } = useGetStudentScheduleById( + params.student_id as string, + ); const formattedShifts = formatIShift(extractShifts(studentSchedule || [])); const forgotPassword = useForgotPassword(); - return (
@@ -58,22 +62,27 @@ export default function Student() { } }} disabled={!student?.user.email} - className={twMerge(clsx( - "w-s rounded-lg px-4 py-2 text-sm font-semibold text-white transition-all duration-200 md:text-base", - !student?.user.email ? "bg-gray-400 cursor-not-allowed" : "cursor-pointer bg-primary-400 hover:scale-98 hover:bg-primary-400/95") + className={twMerge( + clsx( + "w-s rounded-lg px-4 py-2 text-sm font-semibold text-white transition-all duration-200 md:text-base", + !student?.user.email + ? "cursor-not-allowed bg-gray-400" + : "bg-primary-400 hover:bg-primary-400/95 cursor-pointer hover:scale-98", + ), )} > Trigger Forgot Password {forgotPassword.isSuccess && ( -

Forgot Password Email was sent to the user

+

+ Forgot Password Email was sent to the user +

)} {forgotPassword.isError && (

Something went wrong

)} -
getStudentById(id), - }) + }); } diff --git a/src/lib/queries/events.ts b/src/lib/queries/events.ts index 91459270..8c61d421 100644 --- a/src/lib/queries/events.ts +++ b/src/lib/queries/events.ts @@ -1,5 +1,10 @@ import { useQuery } from "@tanstack/react-query"; -import { getCategories, getCategoryById, getEventById, getEvents } from "../events"; +import { + getCategories, + getCategoryById, + getEventById, + getEvents, +} from "../events"; export function useGetEvents() { return useQuery({ @@ -28,5 +33,3 @@ export function useGetCategoryById(id: string) { queryFn: () => getCategoryById(id), }); } - - From ee551eeedf41a26f7f3cd360aa8d2c0e96963ded Mon Sep 17 00:00:00 2001 From: gxnca <11gg20@gmail.com> Date: Wed, 15 Oct 2025 14:28:45 +0100 Subject: [PATCH 4/6] refactor: small changes --- .../settings/backoffice/students/page.tsx | 25 ++++++++++--------- 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/src/app/(app)/settings/backoffice/students/page.tsx b/src/app/(app)/settings/backoffice/students/page.tsx index 7d39d834..477c8732 100644 --- a/src/app/(app)/settings/backoffice/students/page.tsx +++ b/src/app/(app)/settings/backoffice/students/page.tsx @@ -312,23 +312,24 @@ export default function Students() {
-
+

Students

- { - setSearch(e.target.value); - setCurrentPage(1); - }} - /> +
+ search + { + setSearch(e.target.value); + setCurrentPage(1); + }} + /> +
From 629c4fdd76e696d4c861e3e6a63177b64dbcbc93 Mon Sep 17 00:00:00 2001 From: gxnca <11gg20@gmail.com> Date: Wed, 15 Oct 2025 14:38:44 +0100 Subject: [PATCH 5/6] refactor: small changes --- src/app/(app)/settings/backoffice/students/page.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/app/(app)/settings/backoffice/students/page.tsx b/src/app/(app)/settings/backoffice/students/page.tsx index 477c8732..e328fdc2 100644 --- a/src/app/(app)/settings/backoffice/students/page.tsx +++ b/src/app/(app)/settings/backoffice/students/page.tsx @@ -317,13 +317,13 @@ export default function Students() {

Students

-
+
+ "bg-muted border-dark/10 w-full rounded-lg border py-1.5 px-1.5 text-sm/6 flex items-center gap-2")}> search { setSearch(e.target.value); setCurrentPage(1); From 2dd414c688e1cabbf03ad37fe842790ba4283eb1 Mon Sep 17 00:00:00 2001 From: gxnca <11gg20@gmail.com> Date: Wed, 15 Oct 2025 14:41:49 +0100 Subject: [PATCH 6/6] fix: format --- src/app/(app)/settings/backoffice/students/page.tsx | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/app/(app)/settings/backoffice/students/page.tsx b/src/app/(app)/settings/backoffice/students/page.tsx index e328fdc2..65723f3f 100644 --- a/src/app/(app)/settings/backoffice/students/page.tsx +++ b/src/app/(app)/settings/backoffice/students/page.tsx @@ -318,12 +318,17 @@ export default function Students() {
-
- search +
+ + search + { setSearch(e.target.value); setCurrentPage(1);