diff --git a/src/app/(app)/settings/backoffice/exports/page.tsx b/src/app/(app)/settings/backoffice/exports/page.tsx index 2b2c685..8380370 100644 --- a/src/app/(app)/settings/backoffice/exports/page.tsx +++ b/src/app/(app)/settings/backoffice/exports/page.tsx @@ -1,16 +1,153 @@ +"use client"; + import { AuthCheck } from "@/components/auth-check"; +import CustomCombobox from "@/components/combobox"; import SettingsWrapper from "@/components/settings-wrapper"; -import { Metadata } from "next"; +import { + useExportGroupEnrollments, + useExportShiftGroups, +} from "@/lib/queries/backoffice"; +import { useGetAllCourses } from "@/lib/queries/courses"; +import { ICourse } from "@/lib/types"; +import clsx from "clsx"; +import { useState } from "react"; +import { twMerge } from "tailwind-merge"; + +function downloadFile({ + data, + fileName, + fileType, +}: { + data: string; + fileName: string; + fileType: string; +}) { + const blob = new Blob([data], { type: fileType }); + + const a = document.createElement("a"); + a.download = fileName; + a.href = window.URL.createObjectURL(blob); + const clickEvt = new MouseEvent("click", { + view: window, + bubbles: true, + cancelable: true, + }); + a.dispatchEvent(clickEvt); + a.remove(); +} + +function formatCourses(courses: ICourse[] | undefined) { + if (!courses) return []; -export const metadata: Metadata = { - title: "Pombo | Exports", -}; + return courses.map((course) => { + return { id: course.id, name: course.name }; + }); +} export default function Exports() { + const [selectedCourse, setSelectedCourse] = useState<{ + id: string; + name: string; + } | null>(null); + + const { data: allCourses } = useGetAllCourses(); + const { refetch: getShiftGroups, isError: exportShiftsGroupError } = + useExportShiftGroups(selectedCourse?.id || ""); + const { refetch: getGroupEnrollment, isError: exportGroupEnrollmentsError } = + useExportGroupEnrollments(selectedCourse?.id || ""); + + const formattedCourses = formatCourses(allCourses); + + const handleShiftsGroupExport = async () => { + const result = await getShiftGroups(); + downloadFile({ + data: result.data, + fileName: `${selectedCourse?.name}-turmas.csv`, + fileType: "text/csv", + }); + }; + + const handleGroupEnrollmentsExport = async () => { + const result = await getGroupEnrollment(); + downloadFile({ + data: result.data, + fileName: `${selectedCourse?.name}-inscrições.csv`, + fileType: "text/csv", + }); + }; + + const validCourse = selectedCourse !== null; + return ( - -
Exports Page
+ Pombo | Exports + + +
+
+

Generate new schedule

+

Trigger the schedule generator with a few clicks

+
+ +
+
+
+

Courses

+ +
+
+ +
+ + + or + + +
+ + {exportShiftsGroupError && ( +

+ Failed to download Shifts Group! +

+ )} + + {exportGroupEnrollmentsError && ( +

+ Failed to download Group Enrollment! +

+ )} +
+
); diff --git a/src/app/(app)/settings/backoffice/generator/page.tsx b/src/app/(app)/settings/backoffice/generator/page.tsx index 6cb57b3..8ce6114 100644 --- a/src/app/(app)/settings/backoffice/generator/page.tsx +++ b/src/app/(app)/settings/backoffice/generator/page.tsx @@ -1,5 +1,6 @@ "use client"; +import { AuthCheck } from "@/components/auth-check"; import CustomSelect from "@/components/select"; import SettingsWrapper from "@/components/settings-wrapper"; import { useGenerateSchedule } from "@/lib/mutations/backoffice"; @@ -31,71 +32,73 @@ export default function GenerateSchedule() { }); return ( - -
-
-

Generate new schedule

-

Trigger the schedule generator with a few clicks

-
+ + +
+
+

Generate new schedule

+

Trigger the schedule generator with a few clicks

+
-
-
-
-

Degree

- -
+
+
+
+

Degree

+ +
-
-

Semester

- ({ - id: `semester-${semester}`, - name: semester.toString(), - }))} - selectedItem={selectedSemester} - setSelectedItem={setSelectedSemester} - /> +
+

Semester

+ ({ + id: `semester-${semester}`, + name: semester.toString(), + }))} + selectedItem={selectedSemester} + setSelectedItem={setSelectedSemester} + /> +
-
- + - {generateSchedule.isPending && ( -

Pending...

- )} + {generateSchedule.isPending && ( +

Pending...

+ )} - {generateSchedule.isSuccess && ( -

- {generateSchedule.data.message} -

- )} + {generateSchedule.isSuccess && ( +

+ {generateSchedule.data.message} +

+ )} - {generateSchedule.isError && ( -

- {generateSchedule.error.message} -

- )} -
-
- + {generateSchedule.isError && ( +

+ {generateSchedule.error.message} +

+ )} +
+
+
+
); } diff --git a/src/app/(app)/settings/connections/page.tsx b/src/app/(app)/settings/connections/page.tsx deleted file mode 100644 index 97064ab..0000000 --- a/src/app/(app)/settings/connections/page.tsx +++ /dev/null @@ -1,14 +0,0 @@ -import SettingsWrapper from "@/components/settings-wrapper"; -import { Metadata } from "next"; - -export const metadata: Metadata = { - title: "Pombo | Connections", -}; - -export default function Connections() { - return ( - -
Connections Page
-
- ); -} diff --git a/src/app/(app)/settings/notifications/page.tsx b/src/app/(app)/settings/notifications/page.tsx deleted file mode 100644 index 05ae2c7..0000000 --- a/src/app/(app)/settings/notifications/page.tsx +++ /dev/null @@ -1,14 +0,0 @@ -import SettingsWrapper from "@/components/settings-wrapper"; -import { Metadata } from "next"; - -export const metadata: Metadata = { - title: "Pombo | Notifications", -}; - -export default function Notifications() { - return ( - -
Notifications Page
-
- ); -} diff --git a/src/app/(app)/settings/preferences/page.tsx b/src/app/(app)/settings/preferences/page.tsx deleted file mode 100644 index f8da506..0000000 --- a/src/app/(app)/settings/preferences/page.tsx +++ /dev/null @@ -1,14 +0,0 @@ -import SettingsWrapper from "@/components/settings-wrapper"; -import { Metadata } from "next"; - -export const metadata: Metadata = { - title: "Pombo | Preferences", -}; - -export default function Preferences() { - return ( - -
Preferences Page
-
- ); -} diff --git a/src/app/(app)/settings/privacy/page.tsx b/src/app/(app)/settings/privacy/page.tsx deleted file mode 100644 index e1bbd53..0000000 --- a/src/app/(app)/settings/privacy/page.tsx +++ /dev/null @@ -1,14 +0,0 @@ -import SettingsWrapper from "@/components/settings-wrapper"; -import { Metadata } from "next"; - -export const metadata: Metadata = { - title: "Pombo | Privacy", -}; - -export default function Privacy() { - return ( - -
Privacy Page
-
- ); -} diff --git a/src/components/calendar/event-card.tsx b/src/components/calendar/event-card.tsx index 87859aa..f6fcd13 100644 --- a/src/components/calendar/event-card.tsx +++ b/src/components/calendar/event-card.tsx @@ -2,8 +2,8 @@ import { EventProps } from "react-big-calendar"; import { editColor } from "@/lib/utils"; export default function EventCard({ event }: EventProps) { - const building = event.resource?.building || ""; - const room = event.resource?.room || ""; + const building = event.resource?.building; + const room = event.resource?.room; const textColor = event.resource?.textColor; const location = `${building} - ${room}`; @@ -26,9 +26,9 @@ export default function EventCard({ event }: EventProps) { >

{event.title}

-

- {location || "No location"} -

+ {building && room && ( +

{location}

+ )}
); diff --git a/src/components/calendar/event-modal.tsx b/src/components/calendar/event-modal.tsx index 2a27b75..8c40330 100644 --- a/src/components/calendar/event-modal.tsx +++ b/src/components/calendar/event-modal.tsx @@ -152,11 +152,13 @@ export default function EventModal({ /> )} - + {event.building && event.room && ( + + )} {type === "calendar" && event.link && ( void; + className?: string; +} + +export default function CustomCombobox({ + items, + selectedItem, + setSelectedItem, + className, +}: ICustomCombobox) { + const [query, setQuery] = useState(""); + + const filteredItems = + query === "" + ? items + : Object.values(items).filter((item) => { + return item.name.toLowerCase().includes(query.toLowerCase()); + }); + + return ( + setQuery("")} + > +
+ item?.name || ""} + onChange={(event) => setQuery(event.target.value)} + className={clsx( + "bg-muted border-dark/10 w-full rounded-lg border py-1.5 pr-8 pl-3 text-sm/6", + "focus:not-data-focus:outline-none data-focus:outline-2 data-focus:-outline-offset-2 data-focus:outline-white/25", + )} + /> + + + + keyboard_arrow_down + + +
+ + {filteredItems.map((item) => ( + + + check + +
{item.name}
+
+ ))} +
+
+ ); +} diff --git a/src/components/exchange/cards-section.tsx b/src/components/exchange/cards-section.tsx index 4a8c537..c36eb52 100644 --- a/src/components/exchange/cards-section.tsx +++ b/src/components/exchange/cards-section.tsx @@ -33,10 +33,14 @@ export default function CardsSection({ const { data: exchangeDate } = useGetExchangeDate(); const now = new Date(); - const opening = new Date(exchangeDate?.data?.start ?? ""); - const deadline = new Date(exchangeDate?.data?.end ?? ""); + const opening = new Date(exchangeDate?.data?.start); + const deadline = new Date(exchangeDate?.data?.end); console.log({ now, opening, deadline }); - const hasExchangeDateClosed = now > deadline || now < opening; + const hasExchangeDateClosed = + now > deadline || + now < opening || + !exchangeDate?.data?.end || + !exchangeDate?.data?.start; return (
diff --git a/src/components/select.tsx b/src/components/select.tsx index 9bb8472..8256b25 100644 --- a/src/components/select.tsx +++ b/src/components/select.tsx @@ -1,3 +1,4 @@ +import { IItemProps } from "@/lib/types"; import { Listbox, ListboxButton, @@ -6,11 +7,6 @@ import { } from "@headlessui/react"; import clsx from "clsx"; -interface IItemProps { - id: string; - name: string; -} - interface ICustomSelectProps { items: IItemProps[]; selectedItem: IItemProps; diff --git a/src/components/sidebar-settings.tsx b/src/components/sidebar-settings.tsx index b4975e5..6e742e1 100644 --- a/src/components/sidebar-settings.tsx +++ b/src/components/sidebar-settings.tsx @@ -32,10 +32,6 @@ export default function SidebarSettings() { - - - - {user.data && ["admin", "professor"].includes(user.data.type) && ( diff --git a/src/contexts/schedule-provider.tsx b/src/contexts/schedule-provider.tsx index 2207ca9..ecd2a56 100644 --- a/src/contexts/schedule-provider.tsx +++ b/src/contexts/schedule-provider.tsx @@ -171,11 +171,12 @@ function extractShifts(courses: ICourse[]): IShift[] { end: shift.end, shiftType: convertShiftType(shiftGroup.type), shiftNumber: shiftGroup.number, - building: - Number(shift.building) <= 3 + building: shift.building + ? Number(shift.building) <= 3 ? `CP${shift.building}` - : `Building ${shift.building}`, - room: shift.room, + : `Building ${shift.building}` + : null, + room: shift.room || null, year: course.year, semester: course.semester, eventColor: "#C3E5F9", diff --git a/src/lib/backoffice.ts b/src/lib/backoffice.ts index d17ecfb..56c976f 100644 --- a/src/lib/backoffice.ts +++ b/src/lib/backoffice.ts @@ -32,3 +32,25 @@ export async function generateSchedule(params: { ); } } + +export async function exportShiftGroups(course_id: string) { + try { + const res = await api.get(`/export/blackboard/${course_id}/groups`); + return res.data; + } catch { + throw new Error("Failed to export Shift Groups. Please try again later."); + } +} + +export async function exportGroupEnrollments(course_id: string) { + try { + const res = await api.get( + `/export/blackboard/${course_id}/group_enrollments`, + ); + return res.data; + } catch { + throw new Error( + "Failed to export Group Enrollment. Please try again later.", + ); + } +} diff --git a/src/lib/queries/backoffice.ts b/src/lib/queries/backoffice.ts index 2f930ec..d1aee26 100644 --- a/src/lib/queries/backoffice.ts +++ b/src/lib/queries/backoffice.ts @@ -1,5 +1,10 @@ import { useQuery } from "@tanstack/react-query"; -import { getDegrees, listJobs } from "../backoffice"; +import { + exportGroupEnrollments, + exportShiftGroups, + getDegrees, + listJobs, +} from "../backoffice"; export function useListJobs() { return useQuery({ @@ -15,3 +20,19 @@ export function useGetDegrees() { queryFn: getDegrees, }); } + +export function useExportShiftGroups(courseId: string) { + return useQuery({ + queryKey: ["shift-groups-export"], + queryFn: () => exportShiftGroups(courseId), + enabled: false, + }); +} + +export function useExportGroupEnrollments(courseId: string) { + return useQuery({ + queryKey: ["group-enrollments-export"], + queryFn: () => exportGroupEnrollments(courseId), + enabled: false, + }); +} diff --git a/src/lib/types.ts b/src/lib/types.ts index 7639fec..0a64a15 100644 --- a/src/lib/types.ts +++ b/src/lib/types.ts @@ -16,8 +16,8 @@ export interface ITimeSlot { start: string; end: string; weekday: string; - room: string; - building: string; + room: string | null; + building: string | null; } export interface IShiftResponse { @@ -51,8 +51,8 @@ export interface IShift { end: string; // hour only shiftType: "T" | "TP" | "PL" | "OL"; shiftNumber: number; - building: string; - room: string; + building: string | null; + room: string | null; year: number; semester: number; eventColor: string; @@ -101,3 +101,8 @@ export interface IJobProps { inserted_at: Date; user_id: string; } + +export interface IItemProps { + id: string; + name: string; +}