Skip to content

Commit

Permalink
feat: show schedule and dynamically show course units and request cards
Browse files Browse the repository at this point in the history
  • Loading branch information
tomaspalma committed Sep 13, 2024
1 parent 1347741 commit 96dfbfb
Show file tree
Hide file tree
Showing 15 changed files with 180 additions and 83 deletions.
6 changes: 5 additions & 1 deletion src/api/backend.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import { dev_config, getSemester, config } from "../utils"
const prod_val = import.meta.env.VITE_APP_PROD
const BE_CONFIG = Number(prod_val) ? config : dev_config
const BACKEND_URL = import.meta.env.VITE_APP_BACKEND_URL || `${BE_CONFIG.api.protocol}://${BE_CONFIG.api.host}:${BE_CONFIG.api.port}${BE_CONFIG.api.pathPrefix}`
const OIDC_LOGIN_URL = `${BACKEND_URL}/oidc-auth/authenticate`
const OIDC_LOGOUT_URL = `${BACKEND_URL}/oidc-auth/logout`
const SEMESTER = import.meta.env.VITE_APP_SEMESTER || getSemester()

// If we are in september 2024 we use 2024, if we are january 2025 we use 2024 because the first year of the academic year (2024/2025)
Expand Down Expand Up @@ -131,7 +133,9 @@ const api = {
getCourseUnit,
getInfo,
getCourseUnitHashes,
BACKEND_URL
BACKEND_URL,
OIDC_LOGIN_URL,
OIDC_LOGOUT_URL
}

export default api
12 changes: 9 additions & 3 deletions src/components/exchange/requests/issue/CreateRequest.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import { Dispatch, SetStateAction, useState } from "react"
import { Dispatch, SetStateAction, useContext, useState } from "react"
import { CourseInfo, CreateRequestCardMetadata, CreateRequestData } from "../../../../@types"
import exchangeRequestService from "../../../../api/services/exchangeRequestService"
import ScheduleContext from "../../../../contexts/ScheduleContext"
import useSchedule from "../../../../hooks/useSchedule"
import useStudentCourseUnits from "../../../../hooks/useStudentCourseUnits"
import { Button } from "../../../ui/button"
import { CreateRequestCard } from "./cards/CreateRequestCard"

Expand All @@ -12,6 +15,9 @@ export const CreateRequest = ({
setCreatingRequest
}: Props) => {
const [requests, setRequests] = useState<Map<string, CreateRequestData>>(new Map());
const { schedule } = useContext(ScheduleContext);
const enrolledCourseUnits = useStudentCourseUnits(schedule);

const requestEligbleCourseUnits: Array<CreateRequestCardMetadata> = [{
courseUnitName: "Inteligência Artifical",
courseUnitAcronym: "IA",
Expand All @@ -34,10 +40,10 @@ export const CreateRequest = ({
<div className="flex flex-col gap-y-4">
<h1 className="font-bold text-xl">Criar pedido</h1>
<div className="flex flex-col gap-y-2">
{requestEligbleCourseUnits.map((metadata: CreateRequestCardMetadata) => (
{Array.from(enrolledCourseUnits).map((courseInfo: CourseInfo) => (
<CreateRequestCard
requestsHook={[requests, setRequests]}
requestMetadata={metadata}
courseInfo={courseInfo}
/>
))}

Expand Down
65 changes: 39 additions & 26 deletions src/components/exchange/requests/issue/cards/CreateRequestCard.tsx
Original file line number Diff line number Diff line change
@@ -1,31 +1,44 @@
import { ArrowLeftIcon, ArrowRightIcon, MinusIcon } from "@heroicons/react/24/outline"
import { Dispatch, SetStateAction, useState } from "react"
import { CreateRequestCardMetadata, CreateRequestData } from "../../../../../@types"
import { Dispatch, SetStateAction, useContext, useEffect, useState } from "react"
import { ClassDescriptor, CourseInfo, CreateRequestCardMetadata, CreateRequestData } from "../../../../../@types"
import ScheduleContext from "../../../../../contexts/ScheduleContext"
import { Button } from "../../../../ui/button"
import { Card, CardContent, CardHeader, CardTitle } from "../../../../ui/card"
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from "../../../../ui/dropdown-menu"
import { Switch } from "../../../../ui/switch"

type Props = {
requestMetadata: CreateRequestCardMetadata
courseInfo: CourseInfo
requestsHook: [Map<string, CreateRequestData>, Dispatch<SetStateAction<Map<string, CreateRequestData>>>]
}

export const CreateRequestCard = ({
requestMetadata,
courseInfo,
requestsHook
}: Props) => {
const [expanded, setExpanded] = useState<boolean>(false);
const [hasStudentToExchange, setHasStudentToExchange] = useState<boolean>(false);
const [requests, setRequests] = requestsHook;
const [issuerOriginClass, setIssuerOriginClass] = useState<string | null>(null);
const [selectedDestinationClass, setSelectedDestinationClass] = useState<string | null>(null);
const [selectedDestinationStudent, setSelectedDestinationStudent] = useState<string | null>(null);
const { schedule } = useContext(ScheduleContext);

// agora é necessário ir buscar a metadata

useEffect(() => {
if (schedule) {
setIssuerOriginClass(
schedule.find((scheduleItem: ClassDescriptor) => scheduleItem.courseInfo.id === courseInfo.id).classInfo.name
);
}
}, [schedule]);

const excludeClass = () => {
setExpanded(false);
if (requests.get(requestMetadata.courseUnitName)) {
if (requests.get(courseInfo.name)) {
const newRequests = new Map(requests);
newRequests.delete(requestMetadata.courseUnitName)
newRequests.delete(courseInfo.name)
setRequests(newRequests);
}

Expand All @@ -46,29 +59,29 @@ 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: requestMetadata.requesterClassName,
// classNameRequesterGoesTo: selectedDestinationClass
// }
//
// if (selectedDestinationStudent) currentRequest.other_student = Number(selectedDestinationStudent);
//
// const newRequests = new Map(requests);
// newRequests.set(requestMetadata.courseUnitName, currentRequest);
// setRequests(newRequests);
// }

return <Card key={requestMetadata.courseUnitName} className="shadow-md">
return <Card key={courseInfo.name} className="shadow-md">
<CardHeader className="flex flex-row justify-between items-center">
<CardTitle className="text-xl">{requestMetadata.courseUnitName}</CardTitle>
<CardTitle className="text-xl">{courseInfo.name}</CardTitle>
{
expanded ?
<div className="flex flex-row items-center gap-x-2">
<Button variant="destructive" className="p-4 h-7" onClick={() => excludeClass()}>
-
</Button>
<Button className="p-4 h-7" onClick={() => addRequest()}>
<Button className="p-4 h-7" onClick={() => { }}>
+
</Button>
</div>
Expand All @@ -79,7 +92,7 @@ export const CreateRequestCard = ({
</CardHeader>
<CardContent className={`flex flex-col gap-y-4 ${expanded ? "" : "hidden"}`}>
<div className="flex flex-row items-center gap-x-2">
<p>{requestMetadata.requesterClassName}</p>
<p>{issuerOriginClass}</p>
<ArrowRightIcon className="w-5 h-5" />
<div className="p-2 rounded-md w-full">
<DropdownMenu>
Expand All @@ -89,12 +102,12 @@ export const CreateRequestCard = ({
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent className="w-full">
{requestMetadata.availableClasses.filter((className) => className !== requestMetadata.requesterClassName)
{/*requestMetadata.availableClasses.filter((className) => className !== requestMetadata.requesterClassName)
.map((className: string) => (
<DropdownMenuItem className="w-full" onSelect={() => { setSelectedDestinationClass(className) }}>
<p className="w-full">{className}</p>
</DropdownMenuItem>
))}
))*/}
</DropdownMenuContent>
</DropdownMenu>
</div>
Expand All @@ -114,11 +127,11 @@ export const CreateRequestCard = ({
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent className="w-full">
{requestMetadata.availableClasses.map((className: string) => (
{/*requestMetadata.availableClasses.map((className: string) => (
<DropdownMenuItem className="w-full" onSelect={() => { setSelectedDestinationClass(className) }}>
<p className="w-full">{className}</p>
</DropdownMenuItem>
))}
))*/}
</DropdownMenuContent>
</DropdownMenu>
</div>
Expand Down
5 changes: 3 additions & 2 deletions src/components/exchange/requests/view/ViewRequests.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { PlusIcon } from "@heroicons/react/24/outline";
import { Dispatch, SetStateAction, useEffect, useRef, useState } from "react";
import { MarketplaceRequest } from "../../../../@types";
import { ClassDescriptor, MarketplaceRequest } from "../../../../@types";
import useMarketplaceRequests from "../../../../hooks/useMarketplaceRequests";
import useSchedule from "../../../../hooks/useSchedule";
import { Badge } from "../../../ui/badge";
import { Button } from "../../../ui/button";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "../../../ui/tabs";
Expand All @@ -21,6 +22,7 @@ export const ViewRequests = ({
const [hiddenRequests, setHiddenRequests] = useState<Set<number>>(new Set());
const [currentRequestTypeFilter, setCurrentRequestTypeFilter] = useState<number>(0);
const [filterCourseUnitNames, setFilterCourseUnitNames] = useState<Set<string>>(new Set());
const { classes, loading } = useSchedule();

const { data, size, setSize, isLoading } = useMarketplaceRequests(requestTypeFilters[currentRequestTypeFilter]);

Expand Down Expand Up @@ -65,7 +67,6 @@ export const ViewRequests = ({
</TabsList>
<TabsContent value="todos">
<ViewRequestsFilters
enrolledCourseUnits={["CG", "CPD", "IA"]}
availableClasses={["3LEIC01", "3LEIC02", "3LEIC03"]}
filterCourseUnitsHook={[filterCourseUnitNames, setFilterCourseUnitNames]}
/>
Expand Down
30 changes: 16 additions & 14 deletions src/components/exchange/requests/view/ViewRequestsFilters.tsx
Original file line number Diff line number Diff line change
@@ -1,51 +1,54 @@
import { Checkbox } from "@radix-ui/react-checkbox"
import { Dispatch, SetStateAction, useState } from "react"
import { Dispatch, SetStateAction, useContext, useState } from "react"
import { CourseInfo } from "../../../../@types"
import ScheduleContext from "../../../../contexts/ScheduleContext"
import useStudentCourseUnits from "../../../../hooks/useStudentCourseUnits"
import { Schedule } from "../../../planner"
import { Badge } from "../../../ui/badge"
import { ScrollArea } from "../../../ui/scroll-area"

type Props = {
enrolledCourseUnits: Array<string>
availableClasses: Array<string>
filterCourseUnitsHook: [Set<string>, Dispatch<SetStateAction<Set<string>>>]
}

export const ViewRequestsFilters = ({
enrolledCourseUnits,
availableClasses,
filterCourseUnitsHook
}: Props) => {
const [filterCourseUnits, setFilterCourseUnits] = filterCourseUnitsHook
const { schedule } = useContext(ScheduleContext);
const enrolledCourseUnits = useStudentCourseUnits(schedule);

return <div className="flex flex-row justify-between">
return <div className="flex flex-row justify-between w-full">
{/* Course unit filters */}
<div className="flex flex-row gap-x-2">
{enrolledCourseUnits.map((courseUnitName: string) => (
<div className="flex flex-row gap-2 w-2/3 flex-wrap">
{Array.from(enrolledCourseUnits).map((courseUnit: CourseInfo) => (
<div>
<Badge
className={`${filterCourseUnits.has(courseUnitName) ? "bg-black text-white" : "bg-gray-200 text-gray-700"} cursor-pointer hover:text-white`}
className={`${filterCourseUnits.has(courseUnit.acronym) ? "bg-black text-white" : "bg-gray-200 text-gray-700"} cursor-pointer hover:text-white`}
onClick={() => {
const newFilterCourseUnits = new Set(filterCourseUnits);

if (newFilterCourseUnits.has(courseUnitName)) newFilterCourseUnits.delete(courseUnitName);
else newFilterCourseUnits.add(courseUnitName);
if (newFilterCourseUnits.has(courseUnit.acronym)) newFilterCourseUnits.delete(courseUnit.acronym);
else newFilterCourseUnits.add(courseUnit.acronym);

setFilterCourseUnits(newFilterCourseUnits);
}}
>
{courseUnitName}
{courseUnit.acronym}
</Badge>
</div>
))}
</div>

{/* Classes filter */}
{/* <div className="flex flex-row"> */}
{/**/}
{/* <div className="flex flex-row w-1/3"> */}
{/* <ScrollArea className="mx-5 h-72 rounded px-3"> */}
{/* { */}
{/* availableClasses.map((className: string) => ( */}
{/* <div */}
{/* // key={key} */}
{/* // key={key} */}
{/* className="mt-1 flex items-center space-x-2 rounded p-1 hover:cursor-pointer hover:bg-slate-100 hover:dark:bg-slate-700" */}
{/* > */}
{/* <Checkbox id={className} /> */}
Expand All @@ -59,6 +62,5 @@ export const ViewRequestsFilters = ({
{/* ))} */}
{/* </ScrollArea> */}
{/* </div> */}

</div>
}
16 changes: 7 additions & 9 deletions src/components/exchange/schedule/ExchangeSchedule.tsx
Original file line number Diff line number Diff line change
@@ -1,23 +1,21 @@
import { useContext, useEffect, useState } from "react";
import { ClassDescriptor, SlotInfo } from "../../../@types";
import CourseContext from "../../../contexts/CourseContext";
import MultipleOptionsContext from "../../../contexts/MultipleOptionsContext";
import ScheduleContext from "../../../contexts/ScheduleContext";
import useSchedule from "../../../hooks/useSchedule";
import { Schedule } from "../../planner";

const ExchangeSchedule = () => {
const { classes, loading } = useSchedule();
const { schedule } = useContext(ScheduleContext);
const [slots, setSlots] = useState<SlotInfo[]>([]);

useEffect(() => {
// setSlots(classes
// ? classes.map((currentClass: ClassDescriptor) => currentClass.classInfo.slots).flat()
// : []
// )
}, [classes])
if (!schedule) return;

setSlots(schedule.map((currentClass: ClassDescriptor) => currentClass.classInfo.slots).flat())
}, [schedule])

return <Schedule
classes={[]}
classes={schedule ?? []}
slots={slots ?? []}
/>;
}
Expand Down
16 changes: 11 additions & 5 deletions src/components/layout/Header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ import useVerifyCourseUnitHashes from '../../hooks/useVerifyCourseUnitHashes'
import CourseContext from '../../contexts/CourseContext'
import SessionContext from '../../contexts/SessionContext'
import { useContext } from 'react'
import api from '../../api/backend'
import { Button } from '../ui/button'

const navigation = [
{
Expand Down Expand Up @@ -47,7 +49,7 @@ type Props = {
}

const Header = ({ siteTitle, location }: Props) => {
const { pickedCourses,} =useContext(CourseContext);
const { pickedCourses, } = useContext(CourseContext);
const { mismatchedMap } = useVerifyCourseUnitHashes(pickedCourses);
const { signedIn } = useContext(SessionContext);

Expand Down Expand Up @@ -101,10 +103,14 @@ const Header = ({ siteTitle, location }: Props) => {
<div className="hidden self-center md:inline-flex items-center gap-x-2">
<DarkModeSwitch />
{signedIn ?
<a href="index.html">
<ArrowLeftStartOnRectangleIcon className="w-5 h-5" />
</a>
: <a href="http://localhost/Shibboleth.sso/Login">
<Button variant="icon" onClick={async () => {
await fetch(`${api.OIDC_LOGOUT_URL}/`, { method: "POST", credentials: 'include' });
}}>
<a>
<ArrowLeftStartOnRectangleIcon className="w-5 h-5" />
</a>
</Button>
: <a href={`${api.OIDC_LOGIN_URL}`}>
<ArrowLeftEndOnRectangleIcon className="w-5 h-5" />
</a>
}
Expand Down
4 changes: 3 additions & 1 deletion src/contexts/CombinedProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ const CombinedProvider = ({ children }) => {
const [coursesInfo, setCoursesInfo] = useState([]);
const [pickedCourses, setPickedCourses] = useState<CourseInfo[]>(StorageAPI.getPickedCoursesStorage());
const [checkboxedCourses, setCheckboxedCourses] = useState<CourseInfo[]>(StorageAPI.getPickedCoursesStorage());
const [ucsModalOpen, setUcsModalOpen] = useState<boolean>(false);

//TODO: Looks suspicious
const [choosingNewCourse, setChoosingNewCourse] = useState<boolean>(false);
Expand Down Expand Up @@ -48,7 +49,8 @@ const CombinedProvider = ({ children }) => {
pickedCourses, setPickedCourses,
coursesInfo, setCoursesInfo,
checkboxedCourses, setCheckboxedCourses,
choosingNewCourse, setChoosingNewCourse
choosingNewCourse, setChoosingNewCourse,
ucsModalOpen, setUcsModalOpen
}
}>
<MultipleOptionsContext.Provider value={{ multipleOptions, setMultipleOptions, selectedOption, setSelectedOption }}>
Expand Down
16 changes: 16 additions & 0 deletions src/contexts/ScheduleContext.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { Context, Dispatch, SetStateAction } from 'react'
import { createContext } from 'react'
import { ClassDescriptor, CourseInfo, MultipleOptions } from '../@types'

interface ScheduleContent {
schedule: Array<ClassDescriptor>
enrolledCourseUnits: Array<CourseInfo>
}

const ScheduleContext: Context<ScheduleContent> = createContext({
schedule: [],
enrolledCourseUnits: []
});

export default ScheduleContext;

Loading

0 comments on commit 96dfbfb

Please sign in to comment.