Skip to content

Commit 18a61f0

Browse files
authored
Merge pull request #459 from NIAEFEUP/main
Merge hotfixes in main to develop
2 parents cb5c7f9 + fefba1c commit 18a61f0

26 files changed

+330
-53
lines changed

dev.sh

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
#!/bin/bash
22
set -e # abort script if any command fails
33

4-
docker compose up $1 tts-frontend
4+
docker compose up $1 tts-frontend

src/@types/index.d.ts

+1
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,7 @@ export type MarketplaceRequest = {
132132
classes?: Array<ClassInfo>,
133133
pending_motive?: DirectExchangePendingMotive,
134134
accepted: boolean,
135+
admin_state: string,
135136
canceled: boolean
136137
}
137138

src/api/services/exchangeRequestService.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ const submitExchangeRequest = async (requests: Map<number, CreateRequestData>, u
4646
const retrieveMarketplaceRequest = async (url: string): Promise<MarketplaceRequest[]> => {
4747
return fetch(url).then(async (res) => {
4848
const json = await res.json();
49-
return json.data;
49+
return json;
5050
}).catch((e) => {
5151
console.error(e);
5252
return [];

src/components/admin/AdminMainContent.tsx

+13-3
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import { AdminRequestState } from "../../contexts/admin/RequestFiltersContext";
88
import RequestFiltersContext from "../../contexts/admin/RequestFiltersContext";
99
import { AdminPagination } from "./AdminPagination";
1010
import AdminPaginationContext from "../../contexts/admin/AdminPaginationContext";
11+
import { AdminMarketplaceExhanges } from "./requests/AdminMarketplaceExhanges";
1112

1213
export const AdminMainContent = () => {
1314
const [activeCourse, setActiveCourse] = useState<number | undefined>(undefined);
@@ -39,24 +40,30 @@ export const AdminMainContent = () => {
3940
</div>
4041
<Tabs defaultValue="exchange-with-student">
4142
<TabsList className="w-full">
42-
<TabsTrigger
43+
<TabsTrigger
4344
value="exchange-with-student"
4445
onClick={() => setCurrPage(1)}
4546
>
4647
Trocas entre estudantes
4748
</TabsTrigger>
48-
<TabsTrigger
49+
<TabsTrigger
4950
value="exchange-singular"
5051
onClick={() => setCurrPage(1)}
5152
>
5253
Trocas individuais
5354
</TabsTrigger>
54-
<TabsTrigger
55+
<TabsTrigger
5556
value="enrollments"
5657
onClick={() => setCurrPage(1)}
5758
>
5859
Inscrições
5960
</TabsTrigger>
61+
<TabsTrigger
62+
value="admin-marketplace"
63+
onClick={() => setCurrPage(1)}
64+
>
65+
Marketplace
66+
</TabsTrigger>
6067
</TabsList>
6168
<TabsContent value="exchange-with-student">
6269
<MultipleStudentExchanges />
@@ -67,6 +74,9 @@ export const AdminMainContent = () => {
6774
<TabsContent value="enrollments">
6875
<StudentEnrollments />
6976
</TabsContent>
77+
<TabsContent value="admin-marketplace">
78+
<AdminMarketplaceExhanges />
79+
</TabsContent>
7080

7181
<div className="mt-8">
7282
<AdminPagination />
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
import { useState } from "react"
2+
import { Button } from "../ui/button"
3+
import { Card, CardContent, CardHeader, CardTitle } from "../ui/card"
4+
import { ExchangeStatus } from "./requests/cards/ExchangeStatus"
5+
import { Person } from "./requests/cards/Person"
6+
import { RequestDate } from "./requests/cards/RequestDate"
7+
import { ArrowRightIcon, ChevronDownIcon, ChevronUpIcon } from "lucide-react"
8+
import { AdminPreviewSchedule } from "./requests/AdminPreviewSchedule"
9+
import useStudentsSchedule from "../../hooks/admin/useStudentsSchedule"
10+
import { ClassDescriptor, MarketplaceRequest } from "../../@types"
11+
import { AdminRequestCardFooter } from "./requests/cards/AdminRequestCardFooter"
12+
import { listEmailExchanges } from "../../utils/mail"
13+
import { AdminRequestType } from "../../utils/exchange"
14+
15+
type Props = {
16+
exchange: MarketplaceRequest
17+
}
18+
19+
export const AdminMarketplaceExhangesCard = ({
20+
exchange
21+
}: Props) => {
22+
const [open, setOpen] = useState<boolean>(false);
23+
const [exchangeState, setExchangeState] = useState(exchange);
24+
25+
const { schedule } = useStudentsSchedule(exchange.issuer_nmec);
26+
27+
return (<>
28+
<Card>
29+
<CardHeader className="flex flex-row justify-between items-center">
30+
<div className="flex gap-4 items-center">
31+
<div className="flex flex-col gap-1 ">
32+
<div className="flex gap-2 items-center">
33+
<CardTitle>
34+
<h2 className="font-bold">
35+
{`#${exchange.id}`}
36+
</h2>
37+
</CardTitle>
38+
<ExchangeStatus exchange={exchangeState} />
39+
</div>
40+
<RequestDate
41+
date={exchange.date}
42+
/>
43+
</div>
44+
{!open && <>
45+
<Person name={exchange.issuer_name} nmec={exchange.issuer_nmec} />
46+
</>}
47+
48+
</div>
49+
<div>
50+
<Button
51+
onClick={() => setOpen(prev => !prev)}
52+
className="bg-white text-black border-2 border-black hover:text-white"
53+
>
54+
{open
55+
? <ChevronUpIcon className="w-5 h-5" />
56+
: <ChevronDownIcon className="w-5 h-5" />
57+
}
58+
</Button>
59+
</div>
60+
</CardHeader>
61+
62+
<CardContent className="w-full ">
63+
{open &&
64+
<div className="flex flex-col gap-y-8" key={crypto.randomUUID()}>
65+
<div className="flex justify-between">
66+
<Person name={exchange.issuer_name} nmec={exchange.issuer_nmec} />
67+
<div>
68+
<div
69+
key={crypto.randomUUID()}
70+
className="flex flex-col gap-y-2 items-center border-gray-200 border-2 rounded-md p-2 px-4"
71+
>
72+
<>{exchange.options.map((option) => (
73+
<div key={crypto.randomUUID()} className="flex gap-5 items-center">
74+
<h2 className="font-bold">{option.course_info.acronym}</h2>
75+
<div className="flex gap-2 items-center">
76+
<p>{option.class_issuer_goes_from.name}</p>
77+
<ArrowRightIcon className="w-5 h-5" />
78+
<p>{option.class_issuer_goes_to.name}</p>
79+
</div>
80+
</div>
81+
))}
82+
</>
83+
</div>
84+
</div>
85+
<div>
86+
<AdminPreviewSchedule
87+
originalSchedule={schedule}
88+
classesToAdd={
89+
exchange.options.map((option): ClassDescriptor => {
90+
return {
91+
classInfo: option.class_issuer_goes_to,
92+
courseInfo: option.course_info,
93+
slotInfo: null
94+
}
95+
})
96+
}
97+
98+
/>
99+
</div>
100+
</div>
101+
</div>
102+
}
103+
</CardContent>
104+
105+
{open &&
106+
<AdminRequestCardFooter
107+
nmecs={[exchange.issuer_nmec]}
108+
exchangeMessage={listEmailExchanges(
109+
exchange.options.map(option => ({
110+
participant_name: undefined,
111+
participant_nmec: exchange.issuer_nmec,
112+
goes_from: option.class_issuer_goes_from?.name,
113+
goes_to: option.class_issuer_goes_to.name,
114+
course_acronym: option.course_info.acronym
115+
}))
116+
)}
117+
requestType={AdminRequestType.URGENT_EXCHANGE}
118+
requestId={exchange.id}
119+
setExchange={setExchangeState}
120+
courseId={exchange.options.map(option => option.course_info.course)}
121+
/>
122+
}
123+
</Card>
124+
</>
125+
)
126+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import { useContext } from "react";
2+
import RequestFiltersContext from "../../../contexts/admin/RequestFiltersContext";
3+
import useAdminAllMarketplaceExchanges from "../../../hooks/admin/useAdminAllMarketplaceExchanges"
4+
import AdminPaginationContext from "../../../contexts/admin/AdminPaginationContext";
5+
import { BarLoader } from "react-spinners";
6+
import { AdminMarketplaceExhangesCard } from "../AdminMarketplaceExchangesCard";
7+
8+
export const AdminMarketplaceExhanges = () => {
9+
const filterContext = useContext(RequestFiltersContext);
10+
const { currPage } = useContext(AdminPaginationContext);
11+
const { exchanges, loading } = useAdminAllMarketplaceExchanges(filterContext, currPage);
12+
13+
return (<>
14+
{loading && <BarLoader className="w-full" />}
15+
16+
{exchanges?.map((exchange) => (
17+
<AdminMarketplaceExhangesCard
18+
key={`admin-marketplace-${exchange.id}`}
19+
exchange={exchange}
20+
/>
21+
))}
22+
</>
23+
)
24+
}

src/components/admin/requests/AdminSendEmail.tsx

+5-1
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,11 @@ export const AdminSendEmail = ({
1616
message=""
1717
}: Props) => {
1818
return <>
19-
<a href={`${mailtoStringBuilder(nmec)}?subject=${subject}&cc=inscricoes.turmas.leic@fe.up.pt&body=Viva,%0D%0A%0D%0A ${message} %0D%0A%0D%0ACmpts,%0D%0ADaniel Silva%0D%0A(pela comissão de inscrição em turmas)`}>
19+
<a
20+
target="_blank"
21+
rel="noopener noreferrer"
22+
href={`${mailtoStringBuilder(nmec)}?subject=${subject}&cc=inscricoes.turmas.leic@fe.up.pt&body=Viva,%0D%0A%0D%0A ${message} %0D%0A%0D%0ACmpts,%0D%0ADaniel Silva%0D%0A(pela comissão de inscrição em turmas)`}
23+
>
2024
<Button
2125
variant="secondary"
2226
>

src/components/admin/requests/cards/AdminRequestCardFooter.tsx

+8-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { Dispatch, SetStateAction } from "react"
2-
import { CourseUnitEnrollment, DirectExchangeRequest, UrgentRequest } from "../../../../@types"
2+
import { CourseUnitEnrollment, DirectExchangeRequest, MarketplaceRequest, UrgentRequest } from "../../../../@types"
33
import { AdminRequestType } from "../../../../utils/exchange"
44
import exchangeRequestService from "../../../../api/services/exchangeRequestService"
55
import { mailtoStringBuilder } from "../../../../utils/mail"
@@ -15,7 +15,7 @@ type Props = {
1515
requestType: AdminRequestType,
1616
requestId: number,
1717
showTreatButton?: boolean,
18-
setExchange?: Dispatch<SetStateAction<DirectExchangeRequest | UrgentRequest | CourseUnitEnrollment>>
18+
setExchange?: Dispatch<SetStateAction<DirectExchangeRequest | UrgentRequest | CourseUnitEnrollment | MarketplaceRequest>>
1919
courseId: Array<number>
2020
}
2121

@@ -29,6 +29,9 @@ const rejectRequest = async (
2929
await exchangeRequestService.adminRejectExchangeRequest(requestType, id);
3030

3131
const a = document.createElement('a');
32+
a.target = "_blank";
33+
a.rel = "noopener noreferrer";
34+
3235
if(requestType === AdminRequestType.DIRECT_EXCHANGE || requestType === AdminRequestType.URGENT_EXCHANGE) {
3336
a.href = `${mailtoStringBuilder(nmecs)}?subject=Pedido de Alteração de Turma&cc=inscricoes.turmas.leic@fe.up.pt&body=Viva,%0D%0A%0D%0AA alteração pedida não pode ser efetuada.%0D%0A${exchangeMessage}%0D%0A%0D%0ACmpts,%0D%0ADaniel Silva%0D%0A(pela comissão de inscrição em turmas)`;
3437
} else {
@@ -51,11 +54,14 @@ const acceptRequest = async (
5154
await exchangeRequestService.adminAcceptExchangeRequest(requestType, id);
5255

5356
const a = document.createElement('a');
57+
a.target = "_blank";
58+
a.rel = "noopener noreferrer";
5459
if(requestType === AdminRequestType.DIRECT_EXCHANGE || requestType === AdminRequestType.URGENT_EXCHANGE) {
5560
a.href = `${mailtoStringBuilder(nmecs)}?subject=Pedido de Troca de Turma&cc=inscricoes.turmas.leic@fe.up.pt&body=Viva,%0D%0A%0D%0AA alteração pedida foi efetuada.%0D%0A${exchangeMessage}%0D%0A%0D%0ACmpts,%0D%0ADaniel Silva%0D%0A(pela comissão de inscrição em turmas)`;
5661
} else {
5762
a.href = `${mailtoStringBuilder(nmecs)}?subject=Pedido de Inscrição em Unidades Curriculares&cc=inscricoes.turmas.leic@fe.up.pt&body=Viva,%0D%0A%0D%0AA alteração pedida foi efetuada.%0D%0A${exchangeMessage}%0D%0A%0D%0ACmpts,%0D%0ADaniel Silva%0D%0A(pela comissão de inscrição em turmas)`;
5863
}
64+
5965
a.click();
6066
} catch (e) {
6167
console.error(e);

src/components/admin/requests/cards/ExchangeStatus.tsx

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,16 @@
1-
import { CourseUnitEnrollment, DirectExchangeRequest, UrgentRequest } from "../../../../@types"
1+
import { CourseUnitEnrollment, DirectExchangeRequest, MarketplaceRequest, UrgentRequest } from "../../../../@types"
22
import { cn } from "../../../../utils"
33

44
type Props = {
5-
exchange: DirectExchangeRequest | UrgentRequest | CourseUnitEnrollment
5+
exchange: DirectExchangeRequest | UrgentRequest | CourseUnitEnrollment | MarketplaceRequest
66
}
77

88
type ExchangeStatusProperty = {
99
message: string,
1010
color: string
1111
}
1212

13-
const exchangeStatusProperties = (exchange: DirectExchangeRequest | UrgentRequest | CourseUnitEnrollment) => {
13+
const exchangeStatusProperties = (exchange: DirectExchangeRequest | UrgentRequest | CourseUnitEnrollment | MarketplaceRequest) => {
1414
switch (exchange.admin_state) {
1515
case "accepted": case "treated":
1616
return {

src/components/exchange/enrollments/AlreadyEnrolledCourseUnitCard.tsx

+8-2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { Dispatch, SetStateAction, useState } from "react"
1+
import { Dispatch, SetStateAction, useEffect, useState } from "react"
22
import { CourseInfo } from "../../../@types"
33
import { Button } from "../../ui/button"
44
import { Card, CardHeader, CardTitle } from "../../ui/card"
@@ -22,6 +22,12 @@ export const AlreadyEnrolledCourseUnitCard = ({
2222
}: Props) => {
2323
const [removeSelected, setRemoveSelected] = useState<boolean>(false);
2424

25+
useEffect(() => {
26+
if(enrollmentChoices.size === 0) {
27+
setRemoveSelected(false);
28+
}
29+
}, [enrollmentChoices])
30+
2531
return (
2632
<Card>
2733
<CardHeader className="flex flex-row justify-between items-center py-2 px-4">
@@ -47,7 +53,7 @@ export const AlreadyEnrolledCourseUnitCard = ({
4753
>
4854
{removeSelected
4955
? "Cancelar pedido"
50-
: "Anular inscrição"
56+
: "Anular inscrição em turma"
5157
}
5258
</Button>
5359
</CardHeader>

src/components/exchange/enrollments/Enrollments.tsx

+4-3
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ import { Button } from "../../ui/button";
99
import courseUnitEnrollmentService from "../../../api/services/courseUnitEnrollmentService";
1010
import { ExchangeSidebarStatus } from "../../../pages/Exchange"
1111
import { useToast } from "../../ui/use-toast";
12-
import useLocalStorage from "../../../hooks/useLocalStorage";
1312
import useStudentCourseUnits from "../../../hooks/useStudentCourseUnits";
1413
import { AlreadyEnrolledCourseUnitCard } from "./AlreadyEnrolledCourseUnitCard";
1514
import { EnrollingCourseUnitCard } from "./EnrollingCourseUnitCard";
@@ -32,7 +31,7 @@ export const Enrollments = ({
3231
}: Props) => {
3332
const parentCourseContext = useContext(CourseContext);
3433

35-
const [enrollCourses, setEnrollCourses] = useLocalStorage<CourseInfo[]>("enrollCourses", []);
34+
const [enrollCourses, setEnrollCourses] = useState<CourseInfo[]>([]);
3635
const [enrollmentChoices, setEnrollmentChoices] = useState<Map<number, EnrollmentOption>>(new Map());
3736
const [disenrollmentChoices, setDisenrollmentChoices] = useState<Map<number, EnrollmentOption>>(new Map());
3837
const [coursesInfo, setCoursesInfo] = useState<CourseInfo[]>([]);
@@ -112,7 +111,7 @@ export const Enrollments = ({
112111
))}
113112
</div>
114113

115-
{(enrollmentChoices.size > 0) &&
114+
{(enrollmentChoices.size > 0 || disenrollmentChoices.size > 0) &&
116115
<form onSubmit={async (e) => {
117116
e.preventDefault();
118117

@@ -125,6 +124,8 @@ export const Enrollments = ({
125124
description: 'Pedido de inscrição submetida com sucesso',
126125
});
127126
setEnrollCourses([]);
127+
setEnrollmentChoices(new Map());
128+
setDisenrollmentChoices(new Map());
128129
} else {
129130
const json = await res.json();
130131
toast({

src/components/exchange/requests/issue/CustomizeRequest.tsx

+1
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ export const CustomizeRequest = ({
3333
const [submittingRequest, setSubmittingRequest] = useState<boolean>(false);
3434
const [previewingForm, setPreviewingForm] = useState<boolean>(false);
3535

36+
3637
const submitRequest = async (urgentMessage: string) => {
3738
setSubmittingRequest(true);
3839
const res = await exchangeRequestService.submitExchangeRequest(requests, urgentMessage);

0 commit comments

Comments
 (0)