Skip to content

Commit 9b726a4

Browse files
authored
feat: exports page (#81)
1 parent 72e334a commit 9b726a4

File tree

16 files changed

+365
-152
lines changed

16 files changed

+365
-152
lines changed

src/app/(app)/settings/backoffice/exports/page.tsx

Lines changed: 143 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,153 @@
1+
"use client";
2+
13
import { AuthCheck } from "@/components/auth-check";
4+
import CustomCombobox from "@/components/combobox";
25
import SettingsWrapper from "@/components/settings-wrapper";
3-
import { Metadata } from "next";
6+
import {
7+
useExportGroupEnrollments,
8+
useExportShiftGroups,
9+
} from "@/lib/queries/backoffice";
10+
import { useGetAllCourses } from "@/lib/queries/courses";
11+
import { ICourse } from "@/lib/types";
12+
import clsx from "clsx";
13+
import { useState } from "react";
14+
import { twMerge } from "tailwind-merge";
15+
16+
function downloadFile({
17+
data,
18+
fileName,
19+
fileType,
20+
}: {
21+
data: string;
22+
fileName: string;
23+
fileType: string;
24+
}) {
25+
const blob = new Blob([data], { type: fileType });
26+
27+
const a = document.createElement("a");
28+
a.download = fileName;
29+
a.href = window.URL.createObjectURL(blob);
30+
const clickEvt = new MouseEvent("click", {
31+
view: window,
32+
bubbles: true,
33+
cancelable: true,
34+
});
35+
a.dispatchEvent(clickEvt);
36+
a.remove();
37+
}
38+
39+
function formatCourses(courses: ICourse[] | undefined) {
40+
if (!courses) return [];
441

5-
export const metadata: Metadata = {
6-
title: "Pombo | Exports",
7-
};
42+
return courses.map((course) => {
43+
return { id: course.id, name: course.name };
44+
});
45+
}
846

947
export default function Exports() {
48+
const [selectedCourse, setSelectedCourse] = useState<{
49+
id: string;
50+
name: string;
51+
} | null>(null);
52+
53+
const { data: allCourses } = useGetAllCourses();
54+
const { refetch: getShiftGroups, isError: exportShiftsGroupError } =
55+
useExportShiftGroups(selectedCourse?.id || "");
56+
const { refetch: getGroupEnrollment, isError: exportGroupEnrollmentsError } =
57+
useExportGroupEnrollments(selectedCourse?.id || "");
58+
59+
const formattedCourses = formatCourses(allCourses);
60+
61+
const handleShiftsGroupExport = async () => {
62+
const result = await getShiftGroups();
63+
downloadFile({
64+
data: result.data,
65+
fileName: `${selectedCourse?.name}-turmas.csv`,
66+
fileType: "text/csv",
67+
});
68+
};
69+
70+
const handleGroupEnrollmentsExport = async () => {
71+
const result = await getGroupEnrollment();
72+
downloadFile({
73+
data: result.data,
74+
fileName: `${selectedCourse?.name}-inscrições.csv`,
75+
fileType: "text/csv",
76+
});
77+
};
78+
79+
const validCourse = selectedCourse !== null;
80+
1081
return (
1182
<AuthCheck userTypes={["admin", "professor"]}>
12-
<SettingsWrapper title="Export data">
13-
<div>Exports Page</div>
83+
<title>Pombo | Exports</title>
84+
85+
<SettingsWrapper title="Schedule Generator">
86+
<div className="flex h-full flex-col gap-8">
87+
<section className="space-y-2">
88+
<h2 className="text-2xl font-semibold">Generate new schedule</h2>
89+
<p>Trigger the schedule generator with a few clicks</p>
90+
</section>
91+
92+
<section className="space-y-6">
93+
<div className="max-w-2xl space-y-6">
94+
<div className="space-y-1">
95+
<p className="pl-2 font-semibold select-none">Courses</p>
96+
<CustomCombobox
97+
items={formattedCourses}
98+
selectedItem={selectedCourse}
99+
setSelectedItem={setSelectedCourse}
100+
/>
101+
</div>
102+
</div>
103+
104+
<div className="mt-6 inline-flex w-full max-w-2xl items-center gap-4">
105+
<button
106+
disabled={!validCourse}
107+
onClick={handleShiftsGroupExport}
108+
className={twMerge(
109+
clsx(
110+
"w-1/2 rounded-lg px-4 py-2 text-sm font-semibold text-white transition-all duration-200 md:text-base",
111+
!validCourse
112+
? "cursor-not-allowed bg-gray-400"
113+
: "bg-primary-400 hover:bg-primary-400/95 cursor-pointer hover:scale-98",
114+
),
115+
)}
116+
>
117+
Shift Groups
118+
</button>
119+
120+
<span className="text-dark/80 font-semibold">or</span>
121+
122+
<button
123+
disabled={!validCourse}
124+
onClick={handleGroupEnrollmentsExport}
125+
className={twMerge(
126+
clsx(
127+
"w-1/2 rounded-lg px-4 py-2 text-sm font-semibold text-white transition-all duration-200 md:text-base",
128+
!validCourse
129+
? "cursor-not-allowed bg-gray-400"
130+
: "bg-primary-400 hover:bg-primary-400/95 cursor-pointer hover:scale-98",
131+
),
132+
)}
133+
>
134+
Group Enrollments
135+
</button>
136+
</div>
137+
138+
{exportShiftsGroupError && (
139+
<p className="text-dark/50 font-semibold">
140+
Failed to download Shifts Group!
141+
</p>
142+
)}
143+
144+
{exportGroupEnrollmentsError && (
145+
<p className="text-dark/50 font-semibold">
146+
Failed to download Group Enrollment!
147+
</p>
148+
)}
149+
</section>
150+
</div>
14151
</SettingsWrapper>
15152
</AuthCheck>
16153
);
Lines changed: 62 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
"use client";
22

3+
import { AuthCheck } from "@/components/auth-check";
34
import CustomSelect from "@/components/select";
45
import SettingsWrapper from "@/components/settings-wrapper";
56
import { useGenerateSchedule } from "@/lib/mutations/backoffice";
@@ -31,71 +32,73 @@ export default function GenerateSchedule() {
3132
});
3233

3334
return (
34-
<SettingsWrapper title="Schedule Generator">
35-
<div className="flex h-full flex-col gap-8">
36-
<section className="space-y-2">
37-
<h2 className="text-2xl font-semibold">Generate new schedule</h2>
38-
<p>Trigger the schedule generator with a few clicks</p>
39-
</section>
35+
<AuthCheck userTypes={["admin", "professor"]}>
36+
<SettingsWrapper title="Schedule Generator">
37+
<div className="flex h-full flex-col gap-8">
38+
<section className="space-y-2">
39+
<h2 className="text-2xl font-semibold">Generate new schedule</h2>
40+
<p>Trigger the schedule generator with a few clicks</p>
41+
</section>
4042

41-
<section className="space-y-6">
42-
<div className="max-w-2xl space-y-6">
43-
<div className="space-y-1">
44-
<p className="pl-2 font-semibold">Degree</p>
45-
<CustomSelect
46-
items={degrees || []}
47-
selectedItem={
48-
selectedDegree || { id: "", name: "Select a course" }
49-
}
50-
setSelectedItem={setselectedDegree}
51-
/>
52-
</div>
43+
<section className="space-y-6">
44+
<div className="max-w-2xl space-y-6">
45+
<div className="space-y-1">
46+
<p className="pl-2 font-semibold">Degree</p>
47+
<CustomSelect
48+
items={degrees || []}
49+
selectedItem={
50+
selectedDegree || { id: "", name: "Select a course" }
51+
}
52+
setSelectedItem={setselectedDegree}
53+
/>
54+
</div>
5355

54-
<div className="space-y-1">
55-
<p className="pl-2 font-semibold">Semester</p>
56-
<CustomSelect
57-
items={[1, 2].map((semester) => ({
58-
id: `semester-${semester}`,
59-
name: semester.toString(),
60-
}))}
61-
selectedItem={selectedSemester}
62-
setSelectedItem={setSelectedSemester}
63-
/>
56+
<div className="space-y-1">
57+
<p className="pl-2 font-semibold">Semester</p>
58+
<CustomSelect
59+
items={[1, 2].map((semester) => ({
60+
id: `semester-${semester}`,
61+
name: semester.toString(),
62+
}))}
63+
selectedItem={selectedSemester}
64+
setSelectedItem={setSelectedSemester}
65+
/>
66+
</div>
6467
</div>
65-
</div>
6668

67-
<button
68-
disabled={!selectedDegree}
69-
onClick={onGenerate}
70-
className={twMerge(
71-
clsx(
72-
"mt-6 min-w-1/4 rounded-lg px-4 py-2 font-semibold text-white transition-all duration-200",
73-
!selectedDegree
74-
? "cursor-not-allowed bg-gray-400"
75-
: "bg-primary-400 hover:bg-primary-400/95 cursor-pointer hover:scale-98",
76-
),
77-
)}
78-
>
79-
Generate Schedule
80-
</button>
69+
<button
70+
disabled={!selectedDegree}
71+
onClick={onGenerate}
72+
className={twMerge(
73+
clsx(
74+
"mt-6 min-w-1/4 rounded-lg px-4 py-2 font-semibold text-white transition-all duration-200",
75+
!selectedDegree
76+
? "cursor-not-allowed bg-gray-400"
77+
: "bg-primary-400 hover:bg-primary-400/95 cursor-pointer hover:scale-98",
78+
),
79+
)}
80+
>
81+
Generate Schedule
82+
</button>
8183

82-
{generateSchedule.isPending && (
83-
<p className="text-dark/50 font-semibold">Pending...</p>
84-
)}
84+
{generateSchedule.isPending && (
85+
<p className="text-dark/50 font-semibold">Pending...</p>
86+
)}
8587

86-
{generateSchedule.isSuccess && (
87-
<p className="text-dark/50 font-semibold">
88-
{generateSchedule.data.message}
89-
</p>
90-
)}
88+
{generateSchedule.isSuccess && (
89+
<p className="text-dark/50 font-semibold">
90+
{generateSchedule.data.message}
91+
</p>
92+
)}
9193

92-
{generateSchedule.isError && (
93-
<p className="text-dark/50 font-semibold">
94-
{generateSchedule.error.message}
95-
</p>
96-
)}
97-
</section>
98-
</div>
99-
</SettingsWrapper>
94+
{generateSchedule.isError && (
95+
<p className="text-dark/50 font-semibold">
96+
{generateSchedule.error.message}
97+
</p>
98+
)}
99+
</section>
100+
</div>
101+
</SettingsWrapper>
102+
</AuthCheck>
100103
);
101104
}

src/app/(app)/settings/connections/page.tsx

Lines changed: 0 additions & 14 deletions
This file was deleted.

src/app/(app)/settings/notifications/page.tsx

Lines changed: 0 additions & 14 deletions
This file was deleted.

src/app/(app)/settings/preferences/page.tsx

Lines changed: 0 additions & 14 deletions
This file was deleted.

src/app/(app)/settings/privacy/page.tsx

Lines changed: 0 additions & 14 deletions
This file was deleted.

src/components/calendar/event-card.tsx

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@ import { EventProps } from "react-big-calendar";
22
import { editColor } from "@/lib/utils";
33

44
export default function EventCard({ event }: EventProps) {
5-
const building = event.resource?.building || "";
6-
const room = event.resource?.room || "";
5+
const building = event.resource?.building;
6+
const room = event.resource?.room;
77
const textColor = event.resource?.textColor;
88

99
const location = `${building} - ${room}`;
@@ -26,9 +26,9 @@ export default function EventCard({ event }: EventProps) {
2626
>
2727
<div className="space-y-0.5">
2828
<h3>{event.title}</h3>
29-
<p className="text-xs opacity-70 sm:text-sm">
30-
{location || "No location"}
31-
</p>
29+
{building && room && (
30+
<p className="text-xs opacity-70 sm:text-sm">{location}</p>
31+
)}
3232
</div>
3333
</div>
3434
);

0 commit comments

Comments
 (0)