diff --git a/src/@types/index.d.ts b/src/@types/index.d.ts index cc761e96..05697b3f 100644 --- a/src/@types/index.d.ts +++ b/src/@types/index.d.ts @@ -128,5 +128,5 @@ export type MarketplaceRequest = { export type Student = { name: string, - mecNumber: string + mecNumber: number } diff --git a/src/api/services/exchangeRequestService.ts b/src/api/services/exchangeRequestService.ts index d0b10221..b44115ae 100644 --- a/src/api/services/exchangeRequestService.ts +++ b/src/api/services/exchangeRequestService.ts @@ -1,4 +1,4 @@ -import { BareFetcher } from "swr"; +import { BareFetcher, Key } from "swr"; import { SWRInfiniteConfiguration } from "swr/dist/infinite"; import { CreateRequestData, MarketplaceRequest } from "../../@types"; import api from "../backend"; @@ -11,7 +11,7 @@ const isDirectExchange = (requests: IterableIterator) => { return true; } -const submitExchangeRequest = async (requests: Map) => { +const submitExchangeRequest = async (requests: Map) => { const formData = new FormData(); for (const request of requests.values()) { @@ -38,9 +38,23 @@ const retrieveMarketplaceRequest = async (url: string): Promise { + return fetch(`${api.BACKEND_URL}/course_unit/${courseUnitId}/exchange/metadata`).then(async (res) => { + if (res.ok) { + return await res.json(); + } + + return []; + }).catch((e) => { + console.error(e); + return []; + }); +} + const exchangeRequestService = { submitExchangeRequest, - retrieveMarketplaceRequest + retrieveMarketplaceRequest, + retrieveRequestCardMetadata } export default exchangeRequestService; diff --git a/src/components/auth/LoginButton.tsx b/src/components/auth/LoginButton.tsx new file mode 100644 index 00000000..f818180a --- /dev/null +++ b/src/components/auth/LoginButton.tsx @@ -0,0 +1,13 @@ +import { ArrowLeftEndOnRectangleIcon } from "@heroicons/react/24/outline" +import api from "../../api/backend" +import { Button } from "../ui/button" + +export const LoginButton = () => { + return + +} diff --git a/src/components/exchange/requests/issue/CreateRequest.tsx b/src/components/exchange/requests/issue/CreateRequest.tsx index 85545441..a40fddcd 100644 --- a/src/components/exchange/requests/issue/CreateRequest.tsx +++ b/src/components/exchange/requests/issue/CreateRequest.tsx @@ -14,30 +14,12 @@ type Props = { export const CreateRequest = ({ setCreatingRequest }: Props) => { - const [requests, setRequests] = useState>(new Map()); - const { schedule } = useContext(ScheduleContext); - const enrolledCourseUnits = useStudentCourseUnits(schedule); - - const requestEligbleCourseUnits: Array = [{ - courseUnitName: "Inteligência Artifical", - courseUnitAcronym: "IA", - requesterClassName: "3LEIC09", - availableClasses: [ - "3LEIC01", - "3LEIC02", - "3LEIC03", - "3LEIC04", - "3LEIC05", - "3LEIC06", - "3LEIC07", - "3LEIC08", - "3LEIC09", - "3LEIC10" - ] - }]; + const [requests, setRequests] = useState>(new Map()); + const { exchangeSchedule } = useContext(ScheduleContext); + const enrolledCourseUnits = useStudentCourseUnits(exchangeSchedule); return
-
+

Criar pedido

{Array.from(enrolledCourseUnits).map((courseInfo: CourseInfo) => ( @@ -48,7 +30,7 @@ export const CreateRequest = ({ ))} { - requestEligbleCourseUnits.length === 0 + enrolledCourseUnits.length === 0 ?

Não foram encontradas cadeiras com inscrição tua!

: <> } diff --git a/src/components/exchange/requests/issue/cards/CreateRequestCard.tsx b/src/components/exchange/requests/issue/cards/CreateRequestCard.tsx index 19f362ec..9e23c60b 100644 --- a/src/components/exchange/requests/issue/cards/CreateRequestCard.tsx +++ b/src/components/exchange/requests/issue/cards/CreateRequestCard.tsx @@ -1,7 +1,9 @@ import { ArrowLeftIcon, ArrowRightIcon, MinusIcon } from "@heroicons/react/24/outline" import { Dispatch, SetStateAction, useContext, useEffect, useState } from "react" -import { ClassDescriptor, CourseInfo, CreateRequestCardMetadata, CreateRequestData } from "../../../../../@types" +import { ClassDescriptor, CourseInfo, CreateRequestCardMetadata, CreateRequestData, Student } from "../../../../../@types" +import { ScrollArea } from '../../../../ui/scroll-area' import ScheduleContext from "../../../../../contexts/ScheduleContext" +import useRequestCardCourseMetadata from "../../../../../hooks/useRequestCardCourseMetadata" import { Button } from "../../../../ui/button" import { Card, CardContent, CardHeader, CardTitle } from "../../../../ui/card" import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from "../../../../ui/dropdown-menu" @@ -9,36 +11,35 @@ import { Switch } from "../../../../ui/switch" type Props = { courseInfo: CourseInfo - requestsHook: [Map, Dispatch>>] + requestsHook: [Map, Dispatch>>] } export const CreateRequestCard = ({ courseInfo, requestsHook }: Props) => { + const { data: requestMetadata } = useRequestCardCourseMetadata(courseInfo); const [expanded, setExpanded] = useState(false); const [hasStudentToExchange, setHasStudentToExchange] = useState(false); const [requests, setRequests] = requestsHook; const [issuerOriginClass, setIssuerOriginClass] = useState(null); const [selectedDestinationClass, setSelectedDestinationClass] = useState(null); - const [selectedDestinationStudent, setSelectedDestinationStudent] = useState(null); - const { schedule } = useContext(ScheduleContext); - - // agora é necessário ir buscar a metadata + const [selectedDestinationStudent, setSelectedDestinationStudent] = useState(null); + const { exchangeSchedule } = useContext(ScheduleContext); useEffect(() => { - if (schedule) { + if (exchangeSchedule) { setIssuerOriginClass( - schedule.find((scheduleItem: ClassDescriptor) => scheduleItem.courseInfo.id === courseInfo.id).classInfo.name + exchangeSchedule.find((scheduleItem: ClassDescriptor) => scheduleItem.courseInfo.id === courseInfo.id).classInfo.name ); } - }, [schedule]); + }, [exchangeSchedule]); const excludeClass = () => { setExpanded(false); - if (requests.get(courseInfo.name)) { + if (requests.get(courseInfo.id)) { const newRequests = new Map(requests); - newRequests.delete(courseInfo.name) + newRequests.delete(courseInfo.id) setRequests(newRequests); } @@ -59,29 +60,31 @@ export const CreateRequestCard = ({ setHasStudentToExchange(checked); } - // const addRequest = () => { - // const currentRequest: CreateRequestData = { - // classNameRequesterGoesFrom: requestMetadata.requesterClassName, - // classNameRequesterGoesTo: selectedDestinationClass - // } - // - // if (selectedDestinationStudent) currentRequest.other_student = Number(selectedDestinationStudent); - // - // const newRequests = new Map(requests); - // newRequests.set(requestMetadata.courseUnitName, currentRequest); - // setRequests(newRequests); - // } + const addRequest = () => { + const currentRequest: CreateRequestData = { + classNameRequesterGoesFrom: issuerOriginClass, + classNameRequesterGoesTo: selectedDestinationClass + } + + if (selectedDestinationStudent) currentRequest.other_student = selectedDestinationStudent.mecNumber; + + const newRequests = new Map(requests); + newRequests.set(courseInfo.id, currentRequest); + setRequests(newRequests); + + console.log("current new requests: ", newRequests); + } return - - {courseInfo.name} + + {courseInfo.name} { expanded ?
-
@@ -102,12 +105,14 @@ export const CreateRequestCard = ({ - {/*requestMetadata.availableClasses.filter((className) => className !== requestMetadata.requesterClassName) - .map((className: string) => ( - { setSelectedDestinationClass(className) }}> -

{className}

-
- ))*/} + + {requestMetadata?.classes.filter((currentClass) => currentClass.name !== issuerOriginClass) + .map((currentClass) => ( + { setSelectedDestinationClass(currentClass.name) }}> +

{currentClass.name}

+
+ ))} +
@@ -123,19 +128,23 @@ export const CreateRequestCard = ({ - - {/*requestMetadata.availableClasses.map((className: string) => ( - { setSelectedDestinationClass(className) }}> -

{className}

-
- ))*/} + + + {requestMetadata?.students.map((student) => ( + { + setSelectedDestinationStudent({ name: student.nome, mecNumber: Number(student.codigo) }) + }}> +

{student.nome}

+
+ ))} +
- + } diff --git a/src/components/exchange/requests/view/ViewRequests.tsx b/src/components/exchange/requests/view/ViewRequests.tsx index 62d7c7e7..e275a2ba 100644 --- a/src/components/exchange/requests/view/ViewRequests.tsx +++ b/src/components/exchange/requests/view/ViewRequests.tsx @@ -21,10 +21,10 @@ export const ViewRequests = ({ const requestCardsContainerRef = useRef(null); const [hiddenRequests, setHiddenRequests] = useState>(new Set()); const [currentRequestTypeFilter, setCurrentRequestTypeFilter] = useState(0); - const [filterCourseUnitNames, setFilterCourseUnitNames] = useState>(new Set()); + const [filterCourseUnitNames, setFilterCourseUnitNames] = useState>(new Set()); const { classes, loading } = useSchedule(); - const { data, size, setSize, isLoading } = useMarketplaceRequests(requestTypeFilters[currentRequestTypeFilter]); + const { data, size, setSize, isLoading } = useMarketplaceRequests(filterCourseUnitNames, requestTypeFilters[currentRequestTypeFilter]); const requests = data ? [].concat(...data) : []; @@ -47,6 +47,8 @@ export const ViewRequests = ({ } }, []); + console.log("current requests: ", requests); + return

Pedidos

@@ -70,7 +72,6 @@ export const ViewRequests = ({ availableClasses={["3LEIC01", "3LEIC02", "3LEIC03"]} filterCourseUnitsHook={[filterCourseUnitNames, setFilterCourseUnitNames]} /> -
{ isLoading @@ -90,8 +91,18 @@ export const ViewRequests = ({ }
- - + + + + + +
; diff --git a/src/components/exchange/requests/view/ViewRequestsFilters.tsx b/src/components/exchange/requests/view/ViewRequestsFilters.tsx index ca0a1ccc..a7c61db6 100644 --- a/src/components/exchange/requests/view/ViewRequestsFilters.tsx +++ b/src/components/exchange/requests/view/ViewRequestsFilters.tsx @@ -9,7 +9,7 @@ import { ScrollArea } from "../../../ui/scroll-area" type Props = { availableClasses: Array - filterCourseUnitsHook: [Set, Dispatch>>] + filterCourseUnitsHook: [Set, Dispatch>>] } export const ViewRequestsFilters = ({ @@ -17,8 +17,8 @@ export const ViewRequestsFilters = ({ filterCourseUnitsHook }: Props) => { const [filterCourseUnits, setFilterCourseUnits] = filterCourseUnitsHook - const { schedule } = useContext(ScheduleContext); - const enrolledCourseUnits = useStudentCourseUnits(schedule); + const { exchangeSchedule } = useContext(ScheduleContext); + const enrolledCourseUnits = useStudentCourseUnits(exchangeSchedule); return
{/* Course unit filters */} @@ -26,12 +26,12 @@ export const ViewRequestsFilters = ({ {Array.from(enrolledCourseUnits).map((courseUnit: CourseInfo) => (
{ const newFilterCourseUnits = new Set(filterCourseUnits); - if (newFilterCourseUnits.has(courseUnit.acronym)) newFilterCourseUnits.delete(courseUnit.acronym); - else newFilterCourseUnits.add(courseUnit.acronym); + if (newFilterCourseUnits.has(courseUnit.id)) newFilterCourseUnits.delete(courseUnit.id); + else newFilterCourseUnits.add(courseUnit.id); setFilterCourseUnits(newFilterCourseUnits); }} diff --git a/src/components/exchange/requests/view/cards/RequestCard.tsx b/src/components/exchange/requests/view/cards/RequestCard.tsx index d108901e..4bff6f47 100644 --- a/src/components/exchange/requests/view/cards/RequestCard.tsx +++ b/src/components/exchange/requests/view/cards/RequestCard.tsx @@ -1,7 +1,8 @@ import { ArchiveBoxIcon, ArrowDownIcon, ArrowRightIcon, ArrowUpIcon, ChevronDownIcon } from "@heroicons/react/24/outline" import { ChevronUpIcon } from "@heroicons/react/24/solid" -import { Dispatch, SetStateAction, useState } from "react" +import { Dispatch, SetStateAction, useContext, useState } from "react" import { MarketplaceRequest } from "../../../../../@types" +import ScheduleContext from "../../../../../contexts/ScheduleContext" import { Badge } from "../../../../ui/badge" import { Button } from "../../../../ui/button" import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from "../../../../ui/card" @@ -21,6 +22,7 @@ export const RequestCard = ({ hiddenRequests, setHiddenRequests }: Props) => { + const { setExchangeSchedule } = useContext(ScheduleContext); const [open, setOpen] = useState(false); const [hovered, setHovered] = useState(false); @@ -114,9 +116,22 @@ export const RequestCard = ({ Selecionar todas
- +
+ + +
diff --git a/src/components/exchange/schedule/ExchangeSchedule.tsx b/src/components/exchange/schedule/ExchangeSchedule.tsx index 1c94f6ac..e757548c 100644 --- a/src/components/exchange/schedule/ExchangeSchedule.tsx +++ b/src/components/exchange/schedule/ExchangeSchedule.tsx @@ -5,17 +5,17 @@ import useSchedule from "../../../hooks/useSchedule"; import { Schedule } from "../../planner"; const ExchangeSchedule = () => { - const { schedule } = useContext(ScheduleContext); + const { exchangeSchedule } = useContext(ScheduleContext); const [slots, setSlots] = useState([]); useEffect(() => { - if (!schedule) return; + if (!exchangeSchedule) return; - setSlots(schedule.map((currentClass: ClassDescriptor) => currentClass.classInfo.slots).flat()) - }, [schedule]) + setSlots(exchangeSchedule.map((currentClass: ClassDescriptor) => currentClass.classInfo.slots).flat()) + }, [exchangeSchedule]) return ; } diff --git a/src/components/layout/Header.tsx b/src/components/layout/Header.tsx index b8000cf7..a2a8c4df 100644 --- a/src/components/layout/Header.tsx +++ b/src/components/layout/Header.tsx @@ -20,6 +20,7 @@ import SessionContext from '../../contexts/SessionContext' import { useContext } from 'react' import api from '../../api/backend' import { Button } from '../ui/button' +import { LoginButton } from '../auth/LoginButton' const navigation = [ { @@ -103,16 +104,13 @@ const Header = ({ siteTitle, location }: Props) => {
{signedIn ? - - : - - + : }
diff --git a/src/contexts/ScheduleContext.tsx b/src/contexts/ScheduleContext.tsx index cc593075..55321dcc 100644 --- a/src/contexts/ScheduleContext.tsx +++ b/src/contexts/ScheduleContext.tsx @@ -3,12 +3,14 @@ import { createContext } from 'react' import { ClassDescriptor, CourseInfo, MultipleOptions } from '../@types' interface ScheduleContent { - schedule: Array + exchangeSchedule: Array + setExchangeSchedule: Dispatch>> enrolledCourseUnits: Array } const ScheduleContext: Context = createContext({ - schedule: [], + exchangeSchedule: [], + setExchangeSchedule: () => { }, enrolledCourseUnits: [] }); diff --git a/src/hooks/useCourseMetadata.tsx b/src/hooks/useCourseMetadata.tsx deleted file mode 100644 index 80d2dab0..00000000 --- a/src/hooks/useCourseMetadata.tsx +++ /dev/null @@ -1,25 +0,0 @@ -import useSWRInfinite from "swr/infinite"; -import { MarketplaceRequest } from "../@types"; -import api from "../api/backend"; -import exchangeRequestService from "../api/services/exchangeRequestService"; - -export default (filter: string) => { - const getKey = (pageIndex: number) => { - if (pageIndex === 0) return `${api.BACKEND_URL}/exchange/marketplace/?limit=10`; - - return `${api.BACKEND_URL}/exchange/marketplace/?page=${pageIndex + 1}&limit=10&filter=${filter}`; - } - - const { data, size, setSize, isLoading } = useSWRInfinite>( - getKey, - exchangeRequestService.retrieveMarketplaceRequest - ); - - return { - data, - isLoading, - size, - setSize - } -}; - diff --git a/src/hooks/useMarketplaceRequests.tsx b/src/hooks/useMarketplaceRequests.tsx index 80d2dab0..a92a45a9 100644 --- a/src/hooks/useMarketplaceRequests.tsx +++ b/src/hooks/useMarketplaceRequests.tsx @@ -3,11 +3,13 @@ import { MarketplaceRequest } from "../@types"; import api from "../api/backend"; import exchangeRequestService from "../api/services/exchangeRequestService"; -export default (filter: string) => { +export default (courseUnitNameFilter: Set, typeFilter: string) => { + const filters = `typeFilter=${typeFilter}&courseUnitNameFilter=${Array.from(courseUnitNameFilter).join(",")}` + const getKey = (pageIndex: number) => { - if (pageIndex === 0) return `${api.BACKEND_URL}/exchange/marketplace/?limit=10`; + if (pageIndex === 0) return `${api.BACKEND_URL}/exchange/marketplace/?limit=10&${filters}`; - return `${api.BACKEND_URL}/exchange/marketplace/?page=${pageIndex + 1}&limit=10&filter=${filter}`; + return `${api.BACKEND_URL}/exchange/marketplace/?page=${pageIndex + 1}&limit=10&${filters}`; } const { data, size, setSize, isLoading } = useSWRInfinite>( diff --git a/src/hooks/useRequestCardCourseMetadata.tsx b/src/hooks/useRequestCardCourseMetadata.tsx new file mode 100644 index 00000000..292fc71d --- /dev/null +++ b/src/hooks/useRequestCardCourseMetadata.tsx @@ -0,0 +1,21 @@ +import useSWR from "swr"; +import { CourseInfo, MarketplaceRequest } from "../@types"; +import api from "../api/backend"; +import exchangeRequestService from "../api/services/exchangeRequestService"; + +/* + * This hook contains logic to retrieve the students and classes of a course info to be used to populate the information + * on the request cards +*/ +export default (courseInfo: CourseInfo) => { + const { data, isLoading } = useSWR( + `${courseInfo.id}`, + exchangeRequestService.retrieveRequestCardMetadata + ); + + return { + data, + isLoading, + } +}; + diff --git a/src/hooks/useSession.tsx b/src/hooks/useSession.tsx index d742e87a..a76fe33f 100644 --- a/src/hooks/useSession.tsx +++ b/src/hooks/useSession.tsx @@ -1,10 +1,17 @@ import useSwr from "swr"; import api from "../api/backend"; +import Cookies from 'js-cookie'; const useSession = () => { const trySession = async (key) => { try { + await fetch(`${api.BACKEND_URL}/login/`, { + method: "POST", credentials: "include", headers: { + "X-CSRFToken": Cookies.get('csrftoken') + } + }); + const res = await fetch(`${api.BACKEND_URL}/${key}`, { method: "GET", }); @@ -15,13 +22,14 @@ const useSession = () => { } } - const { data, error, mutate } = useSwr("auth/info/", trySession, { + const { data, isLoading, error, mutate } = useSwr("auth/info/", trySession, { refreshInterval: 3600000000 }); return { signedIn: data ? data.signed : false, - user: data ? data : null + user: data ? data : null, + isLoading } } diff --git a/src/pages/Exchange.tsx b/src/pages/Exchange.tsx index 9db02521..88793dd2 100644 --- a/src/pages/Exchange.tsx +++ b/src/pages/Exchange.tsx @@ -1,26 +1,50 @@ +import { useEffect, useState } from "react"; +import { ClassDescriptor } from "../@types"; +import { LoginButton } from "../components/auth/LoginButton"; import { ExchangeSidebar } from "../components/exchange/ExchangeSidebar"; import ExchangeSchedule from "../components/exchange/schedule/ExchangeSchedule"; import { Schedule } from "../components/planner"; import ScheduleContext from "../contexts/ScheduleContext"; +import { useSession } from "../hooks"; import useSchedule from "../hooks/useSchedule"; import useStudentCourseUnits from "../hooks/useStudentCourseUnits"; const ExchangePage = () => { const { schedule } = useSchedule(); + const [exchangeSchedule, setExchangeSchedule] = useState>(); + const { signedIn, isLoading } = useSession(); const enrolledCourseUnits = useStudentCourseUnits(schedule); - return -
- {/* Schedule Preview */} -
-
- -
-
+ useEffect(() => { + setExchangeSchedule(schedule ? schedule : []); + }, [schedule]); + + return <> + {!isLoading && signedIn ? + +
+ {/* Schedule Preview */} +
+
+ +
+
- -
-
+ +
+
+ :
+

+ Trocas de Turmas +

+

+ Tens de iniciar sessão para acederes a esta funcionalidade. +

+
+ +
+
} + } export default ExchangePage; diff --git a/vite.config.ts b/vite.config.ts index d8d7cafe..e02e58f3 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -5,9 +5,13 @@ import viteTsconfigPaths from 'vite-tsconfig-paths' export default defineConfig({ plugins: [react(), viteTsconfigPaths()], server: { + host: '0.0.0.0', + hmr: { + host: 'tts-dev.niaefeup.pt', + }, port: 3100, }, build: { outDir: 'build' - } + }, })