Skip to content

Commit

Permalink
Merge pull request #374 from NIAEFEUP/feature/exchange-admin
Browse files Browse the repository at this point in the history
Feature/exchange admin
  • Loading branch information
tomaspalma authored Jan 30, 2025
2 parents 048313c + af7a83a commit 3a6a692
Show file tree
Hide file tree
Showing 63 changed files with 9,526 additions and 2,501 deletions.
7,644 changes: 5,319 additions & 2,325 deletions package-lock.json

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,13 +40,15 @@
"classnames": "^2.3.1",
"clsx": "^2.1.0",
"cmdk": "^0.2.1",
"date-fns": "^3.6.0",
"emoji-picker-react": "^4.6.4",
"html-to-image": "^1.11.11",
"js-cookie": "^3.0.5",
"lucide-react": "^0.307.0",
"node-fetch": "^3.2.8",
"plausible-tracker": "^0.3.9",
"react": "^18.2.0",
"react-day-picker": "^9.5.1",
"react-dom": "^18.1.0",
"react-hook-form": "^7.54.2",
"react-router-dom": "^6.3.0",
Expand Down
52 changes: 52 additions & 0 deletions src/@types/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,7 @@ export type DirectExchangeRequest = {
issuer_nmec: string,
accepted: boolean,
pending_motive: DirectExchangePendingMotive,
admin_state: string,
options: DirectExchangeParticipant[],
date: string
}
Expand All @@ -150,12 +151,63 @@ export type DirectExchangeParticipant = {
participant_nmec: string,
class_participant_goes_from: ClassInfo,
class_participant_goes_to: ClassInfo,
schedule: Array<ClassDescriptor>,
course_unit: string,
course_unit_id: string,
accepted: boolean
date: string
}

export type UrgentRequest = {
id: number,
user_nmec: string,
date: string,
message: string,
accepted: boolean,
admin_state: string,
options: Array<UrgentRequestOption>
schedule: Array<ClassDescriptor>
}

export type UrgentRequestOption = {
course_unit: CourseInfo,
class_user_goes_from: ClassInfo,
class_user_goes_to: ClassInfo,
}

export type CourseUnitEnrollment = {
id: number,
date: string,
user_nmec: string,
admin_state: string,
accepted: boolean,
schedule: Array<ClassDescriptor>,
options: Array<CourseUnitEnrollmentOption>
}

export type CourseUnitEnrollmentOption = {
course_unit: CourseInfo,
class_user_goes_to: ClassInfo,
}

export enum AdminRequestState {
ACCEPTED = "accepted",
PENDING = "pending",
TREATED = "treated",
REJECTED = "rejected"
}

export enum AdminRequestType {
DIRECT_EXCHANGE = "direct_exchange",
URGENT_EXCHANGE = "urgent_exchange",
ENROLLMENT = "enrollment"
}

export type StudentCourseMetadata = {
nmec: string,
fest_id: number
}

export type Student = {
name: string,
mecNumber: number,
Expand Down
16 changes: 15 additions & 1 deletion src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { Toaster } from './components/ui/toaster'
import { BrowserRouter, Routes, Route, Navigate, useLocation, useNavigationType, createRoutesFromChildren, matchRoutes } from 'react-router-dom'
import './app.css'
import CombinedProvider from './contexts/CombinedProvider'
import { AboutPage, TimeTableSelectorPage, FaqsPage, NotFoundPage, PrivacyPolicyPage, ExchangeVerifyPage } from './pages'
import { AboutPage, TimeTableSelectorPage, FaqsPage, NotFoundPage, PrivacyPolicyPage, ExchangeVerifyPage, AdminPage } from './pages'
import { getPath, config, dev_config, plausible } from './utils'
import Layout from './components/layout'
import Exchange from './pages/Exchange'
Expand Down Expand Up @@ -106,6 +106,20 @@ const App = () => {
element={<Navigate replace to={redirect.to} />}
/>
))}
<Route
path="admin"
key="page-admin"
element={
<AdminPage page="pedidos"/>
}
/>
<Route
path="admin/settings"
key="page-admin-settings"
element={
<AdminPage page="settings"/>
}
/>
</SentryRoutes>
</CombinedProvider>
</BrowserRouter>
Expand Down
9 changes: 7 additions & 2 deletions src/api/services/courseUnitEnrollmentService.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
import { CourseUnitEnrollmentType, EnrollmentOption } from "../../components/exchange/enrollments/Enrollments";
import api from "../backend";

const submitEnrollmentRequest = async (courses: Map<number, number>) => {
const submitEnrollmentRequest = async (courses: Map<number, EnrollmentOption>) => {
const formData = new FormData();

for (const [key, value] of courses) {
formData.append(`enrollCourses[]`, JSON.stringify({ course_unit_id: key, class_id: value }));
formData.append(`enrollCourses[]`, JSON.stringify({
course_unit_id: key,
class_id: value.classId,
enrolling: value.type === CourseUnitEnrollmentType.ENROLLING
}));
}

return fetch(`${api.BACKEND_URL}/course_unit/enrollment/`, {
Expand Down
26 changes: 24 additions & 2 deletions src/api/services/exchangeRequestService.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Key } from "swr";
import { CreateRequestData, MarketplaceRequest } from "../../@types";
import { AdminRequestType, CreateRequestData, MarketplaceRequest } from "../../@types";
import api from "../backend";

const isDirectExchange = (requests: IterableIterator<CreateRequestData>) => {
Expand All @@ -17,7 +17,7 @@ const submitExchangeRequest = async (requests: Map<number, CreateRequestData>, u
formData.append("exchangeChoices[]", JSON.stringify(request));
}

if(urgentMessage !== "") formData.append("urgentMessage", urgentMessage);
if (urgentMessage !== "") formData.append("urgentMessage", urgentMessage);

return fetch(
`${api.BACKEND_URL}/exchange/${isDirectExchange(requests.values()) ? "direct/" : "marketplace/"}`,
Expand Down Expand Up @@ -57,6 +57,26 @@ const retrieveRequestCardMetadata = async (courseUnitId: Key) => {
});
}

const adminRejectExchangeRequest = async (requestType: AdminRequestType, id: number) => {
return fetch(`${api.BACKEND_URL}/exchange/admin/request/${requestType}/${id}/reject/`, {
method: "PUT",
credentials: "include",
headers: {
"X-CSRFToken": api.getCSRFToken(),
}
});
}

const adminAcceptExchangeRequest = async (requestType: AdminRequestType, id: number) => {
return fetch(`${api.BACKEND_URL}/exchange/admin/request/${requestType}/${id}/accept/`, {
method: "PUT",
credentials: "include",
headers: {
"X-CSRFToken": api.getCSRFToken(),
}
})
}

const verifyExchangeRequest = async (token: string): Promise<boolean>=> {
return fetch(`${api.BACKEND_URL}/exchange/verify/${token}`, {
method: "POST",
Expand All @@ -80,6 +100,8 @@ const exchangeRequestService = {
submitExchangeRequest,
retrieveMarketplaceRequest,
retrieveRequestCardMetadata,
adminRejectExchangeRequest,
adminAcceptExchangeRequest,
verifyExchangeRequest
}

Expand Down
27 changes: 27 additions & 0 deletions src/app.css
Original file line number Diff line number Diff line change
Expand Up @@ -126,3 +126,30 @@ button {
.success-button {
@apply bg-green-200 text-green-600 border border-green-600 hover:bg-white;
}

.rdp-caption {
@apply flex flex-row gap-x-2;
}

@layer base {
:root {
--sidebar-background: 0 0% 98%;
--sidebar-foreground: 240 5.3% 26.1%;
--sidebar-primary: 240 5.9% 10%;
--sidebar-primary-foreground: 0 0% 98%;
--sidebar-accent: 240 4.8% 95.9%;
--sidebar-accent-foreground: 240 5.9% 10%;
--sidebar-border: 220 13% 91%;
--sidebar-ring: 217.2 91.2% 59.8%;
}
.dark {
--sidebar-background: 240 5.9% 10%;
--sidebar-foreground: 240 4.8% 95.9%;
--sidebar-primary: 224.3 76.3% 48%;
--sidebar-primary-foreground: 0 0% 100%;
--sidebar-accent: 240 3.7% 15.9%;
--sidebar-accent-foreground: 240 4.8% 95.9%;
--sidebar-border: 240 3.7% 15.9%;
--sidebar-ring: 217.2 91.2% 59.8%;
}
}
105 changes: 105 additions & 0 deletions src/components/admin/AdminExchangeSettings.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
"use client";

import { Card, CardContent, CardHeader, CardTitle } from '../ui/card'
import { Button } from '../ui/button'
import { Table, TableHead, TableRow, TableBody, TableCell } from '../ui/table'

import { useState } from 'react'
import useAdminExchangeCourses from '../../hooks/admin/useAdminExchangeCourses'
import { CheckIcon, PlusIcon } from 'lucide-react'
import { Input } from '../ui/input'
// import { DateTimePicker } from '../ui/datetime-picker';

export const AdminExchangeSettings = () => {
const { courses } = useAdminExchangeCourses();

const [selectedGroup, setSelectedGroup] = useState<number>(0);
const [addingPeriod, setAddingPeriod] = useState<boolean>(false)
// const [startDate, setStartDate] = useState<Date>();
// const [endDate, setEndDate] = useState<Date>();

return (
<div className="flex flex-col gap-y-8">
<div>
<h1 className="text-3xl font-bold">Definições</h1>
<p>Aqui podem ser definidas configurações para o período de trocas dos grupos de cadeiras responsáveis.</p>
</div>
<div className="flex flex-row justify-between">
<div className="w-1/2">
<div className="flex flex-row gap-x-4 items-center">
<h2 className="text-lg font-bold">Períodos de troca ativos</h2>
<Button onClick={() => setAddingPeriod(prev => !prev)}>
<PlusIcon className="h-5 w-5" />
</Button>
</div>
{addingPeriod &&
<form className="flex flex-row gap-x-2 mt-4">
<div className="flex flex-row space-x-2">
<Input type="text" placeholder="Nome do período" />
{/* <DateTimePicker
value={startDate}
onChange={setStartDate}
showOutsideDays={false}
weekStartsOn={1}
showWeekNumber={false}
placeholder="Início"
/>
<DateTimePicker
value={endDate}
onChange={setEndDate}
showOutsideDays={false}
weekStartsOn={1}
showWeekNumber={false}
placeholder="Fim"
/> */}
</div>
<div>
<Button onClick={() => setAddingPeriod(false)}>
<CheckIcon className="h-5 w-5" />
</Button>
</div>
</form>
}
<Table className="w-1/2">
<TableRow>
<TableHead>Período</TableHead>
<TableHead>Data de início</TableHead>
<TableHead>Data de término</TableHead>
</TableRow>
<TableBody>
<TableRow>
<TableCell>1</TableCell>
<TableCell>2023-01-01</TableCell>
<TableCell>2023-12-31</TableCell>
</TableRow>
<TableRow>
<TableCell>2</TableCell>
<TableCell>2024-01-01</TableCell>
<TableCell>2024-12-31</TableCell>
</TableRow>
</TableBody>
</Table>
</div>
<div className="flex flex-row gap-x-4 mx-auto">
<Card className="rounded-md">
<CardHeader>
<CardTitle className="text-lg">Cadeiras responsáveis</CardTitle>
</CardHeader>
<CardContent className="flex flex-col gap-y-2 m-2">
{courses?.map((course, idx) => (
<Button
key={course.id}
variant="ghost"
className={`${selectedGroup === idx ? "bg-gray-100" : ""}`}
onClick={() => setSelectedGroup(idx)}
>
{course.acronym}
</Button>
))}
</CardContent>
</Card>
</div>
</div>
</div>
)
}
Loading

0 comments on commit 3a6a692

Please sign in to comment.