From ea72a07a748f69f77ea28b01b84fb512fd344cdb Mon Sep 17 00:00:00 2001 From: Fabian Emilius Date: Mon, 7 Oct 2024 18:21:39 +0200 Subject: [PATCH] Improve presentation scheduling logic --- .../PresentationsTable/PresentationsTable.tsx | 26 +++++-- .../PublicPresentationsTable.tsx | 75 +++++++++++++++++++ .../components/ThesesTable/ThesesTable.tsx | 14 ++-- client/src/hooks/authentication.ts | 4 + .../src/pages/DashboardPage/DashboardPage.tsx | 4 +- .../PublicPresentationsSection.tsx | 52 ++----------- .../NotificationSettings.tsx | 4 +- .../ThesisOverviewPage/ThesisOverviewPage.tsx | 4 +- .../ReplacePresentationModal.tsx | 49 +++++++----- client/src/pages/TopicPage/TopicPage.tsx | 4 +- client/src/requests/responses/thesis.ts | 7 +- .../PublishedPresentationController.java | 2 + .../ls1/dto/PublishedPresentationDto.java | 3 + .../ls1/service/DashboardService.java | 11 +++ .../service/ThesisPresentationService.java | 8 +- 15 files changed, 172 insertions(+), 95 deletions(-) create mode 100644 client/src/components/PublicPresentationsTable/PublicPresentationsTable.tsx diff --git a/client/src/components/PresentationsTable/PresentationsTable.tsx b/client/src/components/PresentationsTable/PresentationsTable.tsx index 0e0f3642..799dff24 100644 --- a/client/src/components/PresentationsTable/PresentationsTable.tsx +++ b/client/src/components/PresentationsTable/PresentationsTable.tsx @@ -2,15 +2,19 @@ import React from 'react' import { DataTable, DataTableColumn } from 'mantine-datatable' import { IPublishedPresentation, + isPublishedPresentation, isThesisPresentation, + IThesis, IThesisPresentation, } from '../../requests/responses/thesis' import { formatDate, formatPresentationType } from '../../utils/format' import { GLOBAL_CONFIG } from '../../config/global' import { Anchor, Badge, Center } from '@mantine/core' +import AvatarUserList from '../AvatarUserList/AvatarUserList' type PresentationColumn = | 'state' + | 'students' | 'type' | 'location' | 'streamUrl' @@ -51,14 +55,22 @@ const PresentationsTable = (
- {isThesisPresentation(presentation) && ( - - {presentation.state} - - )} + + {presentation.state} +
), }, + students: { + accessor: 'students', + title: 'Student', + width: 180, + ellipsis: true, + render: (presentation) => + isPublishedPresentation(presentation) && ( + + ), + }, type: { accessor: 'type', title: 'Type', @@ -122,7 +134,7 @@ const PresentationsTable = columnConfig[column])} + columns={columns.filter((column) => column).map((column) => columnConfig[column])} onRowClick={onRowClick ? ({ record }) => onRowClick(record) : undefined} /> ) @@ -138,7 +150,7 @@ const PresentationsTable = columnConfig[column])} + columns={columns.filter((column) => column).map((column) => columnConfig[column])} /> ) } diff --git a/client/src/components/PublicPresentationsTable/PublicPresentationsTable.tsx b/client/src/components/PublicPresentationsTable/PublicPresentationsTable.tsx new file mode 100644 index 00000000..911136b6 --- /dev/null +++ b/client/src/components/PublicPresentationsTable/PublicPresentationsTable.tsx @@ -0,0 +1,75 @@ +import PresentationsTable from '../PresentationsTable/PresentationsTable' +import React, { useEffect, useState } from 'react' +import { PaginationResponse } from '../../requests/responses/pagination' +import { IPublishedPresentation } from '../../requests/responses/thesis' +import { doRequest } from '../../requests/request' +import { showSimpleError } from '../../utils/notification' +import { getApiResponseErrorMessage } from '../../requests/handler' +import { useNavigate } from 'react-router-dom' + +interface IPublicPresentationsTableProps { + includeDrafts?: boolean + limit?: number + reducedData?: boolean +} + +const PublicPresentationsTable = (props: IPublicPresentationsTableProps) => { + const { includeDrafts = false, limit = 10, reducedData = false } = props + + const navigate = useNavigate() + + const [presentations, setPresentations] = useState>() + const [page, setPage] = useState(0) + + useEffect(() => { + setPresentations(undefined) + + return doRequest>( + `/v2/published-presentations`, + { + method: 'GET', + requiresAuth: false, + params: { + page, + limit, + includeDrafts, + }, + }, + (res) => { + if (res.ok) { + setPresentations(res.data) + } else { + showSimpleError(getApiResponseErrorMessage(res)) + } + }, + ) + }, [page, limit, includeDrafts]) + + return ( + navigate(`/presentations/${presentation.presentationId}`)} + pagination={{ + totalRecords: presentations?.totalElements ?? 0, + recordsPerPage: limit, + page: page + 1, + onPageChange: (newPage) => setPage(newPage - 1), + }} + /> + ) +} + +export default PublicPresentationsTable diff --git a/client/src/components/ThesesTable/ThesesTable.tsx b/client/src/components/ThesesTable/ThesesTable.tsx index 5a5ad6ff..bde5a428 100644 --- a/client/src/components/ThesesTable/ThesesTable.tsx +++ b/client/src/components/ThesesTable/ThesesTable.tsx @@ -45,7 +45,7 @@ const ThesesTable = (props: IThesesTableProps) => { title: 'State', textAlign: 'center', width: 150, - render: (thesis: IThesis) => { + render: (thesis) => { return (
@@ -57,28 +57,28 @@ const ThesesTable = (props: IThesesTableProps) => { accessor: 'supervisors', title: 'Supervisor', width: 180, - render: (thesis: IThesis) => , + render: (thesis) => , }, advisors: { accessor: 'advisors', title: 'Advisor(s)', ellipsis: true, width: 180, - render: (thesis: IThesis) => , + render: (thesis) => , }, students: { accessor: 'students', title: 'Student(s)', ellipsis: true, width: 180, - render: (thesis: IThesis) => , + render: (thesis) => , }, type: { accessor: 'type', title: 'Type', ellipsis: true, width: 150, - render: (thesis: IThesis) => formatThesisType(thesis.type), + render: (thesis) => formatThesisType(thesis.type), }, title: { accessor: 'title', @@ -91,7 +91,7 @@ const ThesesTable = (props: IThesesTableProps) => { sortable: true, ellipsis: true, width: 130, - render: (thesis: IThesis) => formatDate(thesis.startDate, { withTime: false }), + render: (thesis) => formatDate(thesis.startDate, { withTime: false }), }, end_date: { accessor: 'endDate', @@ -99,7 +99,7 @@ const ThesesTable = (props: IThesesTableProps) => { sortable: true, ellipsis: true, width: 130, - render: (thesis: IThesis) => formatDate(thesis.endDate, { withTime: false }), + render: (thesis) => formatDate(thesis.endDate, { withTime: false }), }, ...extraColumns, } diff --git a/client/src/hooks/authentication.ts b/client/src/hooks/authentication.ts index 9c7ca114..062aeacd 100644 --- a/client/src/hooks/authentication.ts +++ b/client/src/hooks/authentication.ts @@ -59,3 +59,7 @@ export function useHasGroupAccess(...groups: string[]) { return user?.groups.some((group) => groups.includes(group)) ?? false } + +export function useManagementAccess() { + return useHasGroupAccess('admin', 'supervisor', 'advisor') +} diff --git a/client/src/pages/DashboardPage/DashboardPage.tsx b/client/src/pages/DashboardPage/DashboardPage.tsx index 99c3d4d7..779e70f7 100644 --- a/client/src/pages/DashboardPage/DashboardPage.tsx +++ b/client/src/pages/DashboardPage/DashboardPage.tsx @@ -7,7 +7,7 @@ import ThesesProvider from '../../contexts/ThesesProvider/ThesesProvider' import { Button, Center, Group, Stack, Title } from '@mantine/core' import { ApplicationState, IApplication } from '../../requests/responses/application' import ThesesGanttChart from '../../components/ThesesGanttChart/ThesesGanttChart' -import { useHasGroupAccess } from '../../hooks/authentication' +import { useHasGroupAccess, useManagementAccess } from '../../hooks/authentication' import { Link } from 'react-router-dom' import ApplicationModal from '../../components/ApplicationModal/ApplicationModal' import MyTasksSection from './components/MyTasksSection/MyTasksSection' @@ -20,7 +20,7 @@ const DashboardPage = () => { const [application, setApplication] = useState() - const managementAccess = useHasGroupAccess('admin', 'supervisor', 'advisor') + const managementAccess = useManagementAccess() return ( diff --git a/client/src/pages/DashboardPage/components/PublicPresentationsSection/PublicPresentationsSection.tsx b/client/src/pages/DashboardPage/components/PublicPresentationsSection/PublicPresentationsSection.tsx index 62d6be8f..c39e020d 100644 --- a/client/src/pages/DashboardPage/components/PublicPresentationsSection/PublicPresentationsSection.tsx +++ b/client/src/pages/DashboardPage/components/PublicPresentationsSection/PublicPresentationsSection.tsx @@ -8,47 +8,14 @@ import { Title, Tooltip, } from '@mantine/core' -import React, { useEffect, useState } from 'react' -import PresentationsTable from '../../../../components/PresentationsTable/PresentationsTable' -import { IPublishedPresentation } from '../../../../requests/responses/thesis' -import { doRequest } from '../../../../requests/request' -import { PaginationResponse } from '../../../../requests/responses/pagination' -import { showSimpleError } from '../../../../utils/notification' -import { getApiResponseErrorMessage } from '../../../../requests/handler' +import React from 'react' import { GLOBAL_CONFIG } from '../../../../config/global' import { Check, Copy } from 'phosphor-react' -import { useNavigate } from 'react-router-dom' +import { useManagementAccess } from '../../../../hooks/authentication' +import PublicPresentationsTable from '../../../../components/PublicPresentationsTable/PublicPresentationsTable' const PublicPresentationsSection = () => { - const limit = 10 - - const navigate = useNavigate() - - const [presentations, setPresentations] = useState>() - const [page, setPage] = useState(0) - - useEffect(() => { - setPresentations(undefined) - - return doRequest>( - `/v2/published-presentations`, - { - method: 'GET', - requiresAuth: false, - params: { - page, - limit, - }, - }, - (res) => { - if (res.ok) { - setPresentations(res.data) - } else { - showSimpleError(getApiResponseErrorMessage(res)) - } - }, - ) - }, [page, limit]) + const managementAccess = useManagementAccess() const calendarUrl = GLOBAL_CONFIG.calendar_url || `${GLOBAL_CONFIG.server_host}/api/v2/calendar/presentations` @@ -77,16 +44,7 @@ const PublicPresentationsSection = () => { - navigate(`/presentations/${presentation.presentationId}`)} - pagination={{ - totalRecords: presentations?.totalElements ?? 0, - recordsPerPage: limit, - page: page + 1, - onPageChange: (newPage) => setPage(newPage - 1), - }} - /> + ) } diff --git a/client/src/pages/SettingsPage/components/NotificationSettings/NotificationSettings.tsx b/client/src/pages/SettingsPage/components/NotificationSettings/NotificationSettings.tsx index f0b32270..1d8a5be7 100644 --- a/client/src/pages/SettingsPage/components/NotificationSettings/NotificationSettings.tsx +++ b/client/src/pages/SettingsPage/components/NotificationSettings/NotificationSettings.tsx @@ -1,6 +1,6 @@ import { Group, Stack, Text } from '@mantine/core' import { usePageTitle } from '../../../../hooks/theme' -import { useHasGroupAccess, useLoggedInUser } from '../../../../hooks/authentication' +import { useHasGroupAccess, useLoggedInUser, useManagementAccess } from '../../../../hooks/authentication' import ThesesTable from '../../../../components/ThesesTable/ThesesTable' import ThesesProvider from '../../../../contexts/ThesesProvider/ThesesProvider' import React, { useEffect, useState } from 'react' @@ -12,7 +12,7 @@ const NotificationSettings = () => { usePageTitle('Notification Settings') const user = useLoggedInUser() - const managementAccess = useHasGroupAccess('admin', 'supervisor', 'advisor') + const managementAccess = useManagementAccess() const [settings, setSettings] = useState>() diff --git a/client/src/pages/ThesisOverviewPage/ThesisOverviewPage.tsx b/client/src/pages/ThesisOverviewPage/ThesisOverviewPage.tsx index 3ff16aee..e4a9c85d 100644 --- a/client/src/pages/ThesisOverviewPage/ThesisOverviewPage.tsx +++ b/client/src/pages/ThesisOverviewPage/ThesisOverviewPage.tsx @@ -8,14 +8,14 @@ import { Button, Group, Space, Stack, Title } from '@mantine/core' import { ThesisState } from '../../requests/responses/thesis' import CreateThesisModal from './components/CreateThesisModal/CreateThesisModal' import { Plus } from 'phosphor-react' -import { useHasGroupAccess } from '../../hooks/authentication' +import { useManagementAccess } from '../../hooks/authentication' const ThesisOverviewPage = () => { usePageTitle('Theses') const [openCreateThesisModal, setOpenCreateThesisModal] = useState(false) - const managementAccess = useHasGroupAccess('admin', 'supervisor', 'advisor') + const managementAccess = useManagementAccess() return ( diff --git a/client/src/pages/ThesisPage/components/ThesisWritingSection/components/ReplacePresentationModal/ReplacePresentationModal.tsx b/client/src/pages/ThesisPage/components/ThesisWritingSection/components/ReplacePresentationModal/ReplacePresentationModal.tsx index c866a6fa..799f3af8 100644 --- a/client/src/pages/ThesisPage/components/ThesisWritingSection/components/ReplacePresentationModal/ReplacePresentationModal.tsx +++ b/client/src/pages/ThesisPage/components/ThesisWritingSection/components/ReplacePresentationModal/ReplacePresentationModal.tsx @@ -5,12 +5,13 @@ import { useLoadedThesisContext, useThesisUpdateAction, } from '../../../../../../contexts/ThesisProvider/hooks' -import { Alert, Button, Group, Modal, Select, Stack, TextInput } from '@mantine/core' +import { Accordion, Alert, Button, Divider, Group, Modal, Select, Stack, TextInput } from '@mantine/core' import { doRequest } from '../../../../../../requests/request' import { IThesis, IThesisPresentation } from '../../../../../../requests/responses/thesis' import { ApiError } from '../../../../../../requests/handler' import { formatPresentationType } from '../../../../../../utils/format' import { GLOBAL_CONFIG } from '../../../../../../config/global' +import PublicPresentationsTable from '../../../../../../components/PublicPresentationsTable/PublicPresentationsTable' interface IReplacePresentationModalProps { opened: boolean @@ -18,19 +19,21 @@ interface IReplacePresentationModalProps { presentation?: IThesisPresentation } +interface IFormValues { + type: string + visibility: string + location: string + streamUrl: string + language: string | null + date: DateValue +} + const ReplacePresentationModal = (props: IReplacePresentationModalProps) => { const { presentation, opened, onClose } = props const { thesis } = useLoadedThesisContext() - const form = useForm<{ - type: string - visibility: string - location: string - streamUrl: string - language: string | null - date: DateValue - }>({ + const form = useForm({ mode: 'controlled', initialValues: { type: 'INTERMEDIATE', @@ -122,6 +125,7 @@ const ReplacePresentationModal = (props: IReplacePresentationModalProps) => { return ( { presentation. )} + + + Currently Scheduled Presentations + + + + +