diff --git a/.github/workflows/dev.yml b/.github/workflows/dev.yml index 3c82e80e..b51c0cd5 100644 --- a/.github/workflows/dev.yml +++ b/.github/workflows/dev.yml @@ -2,7 +2,6 @@ name: Build and Deploy to Dev on: pull_request: - branches: [main] jobs: build-dev-container: diff --git a/client/package.json b/client/package.json index 6f96f2a3..77954404 100644 --- a/client/package.json +++ b/client/package.json @@ -58,6 +58,7 @@ "globals": "15.9.0", "html-webpack-plugin": "5.6.0", "mini-css-extract-plugin": "2.9.0", + "postcss": "8.4.41", "postcss-loader": "8.1.1", "postcss-preset-mantine": "1.17.0", "prettier": "3.3.3", diff --git a/client/src/app/Routes.tsx b/client/src/app/Routes.tsx index bce23bda..32fceb3b 100644 --- a/client/src/app/Routes.tsx +++ b/client/src/app/Routes.tsx @@ -14,13 +14,10 @@ const ThesisOverviewPage = lazy(() => import('../pages/ThesisOverviewPage/Thesis const DashboardPage = lazy(() => import('../pages/DashboardPage/DashboardPage')) const LogoutPage = lazy(() => import('../pages/LogoutPage/LogoutPage')) const MyInformationPage = lazy(() => import('../pages/MyInformationPage/MyInformationPage')) -const SubmitApplicationStepOnePage = lazy( - () => import('../pages/SubmitApplicationPage/SubmitApplicationStepOnePage'), +const SubmitApplicationPage = lazy( + () => import('../pages/SubmitApplicationPage/SubmitApplicationPage'), ) -const SubmitApplicationStepTwoPage = lazy( - () => import('../pages/SubmitApplicationPage/SubmitApplicationStepTwoPage'), -) -const ManageTopicPage = lazy(() => import('../pages/ManageTopicsPage/ManageTopicsPage')) +const ManageTopicsPage = lazy(() => import('../pages/ManageTopicsPage/ManageTopicsPage')) const TopicPage = lazy(() => import('../pages/TopicPage/TopicPage')) const ReviewApplicationPage = lazy( () => import('../pages/ReviewApplicationPage/ReviewApplicationPage'), @@ -50,18 +47,10 @@ const AppRoutes = () => { } /> - - - } - /> - - + } /> @@ -69,7 +58,7 @@ const AppRoutes = () => { path='/topics' element={ - + } /> diff --git a/client/src/app/layout/AuthenticatedArea/AuthenticatedArea.tsx b/client/src/app/layout/AuthenticatedArea/AuthenticatedArea.tsx index 2463172e..35bc37d5 100644 --- a/client/src/app/layout/AuthenticatedArea/AuthenticatedArea.tsx +++ b/client/src/app/layout/AuthenticatedArea/AuthenticatedArea.tsx @@ -21,6 +21,8 @@ import { SignOut, Sun, FolderSimplePlus, + User, + PaperPlaneTilt, } from 'phosphor-react' import { useIsSmallerBreakpoint } from '../../../hooks/theme' import { useAuthenticationContext } from '../../../hooks/authentication' @@ -42,6 +44,12 @@ const links: Array<{ groups: string[] | undefined }> = [ { link: '/dashboard', label: 'Dashboard', icon: NewspaperClipping, groups: undefined }, + { + link: '/submit-application', + label: 'Submit Application', + icon: PaperPlaneTilt, + groups: undefined, + }, { link: '/applications', label: 'Review Applications', @@ -159,14 +167,14 @@ const AuthenticatedArea = (props: PropsWithChildren) => ))} - {/* My Information - */} + Logout diff --git a/client/src/components/ApplicationData/ApplicationData.tsx b/client/src/components/ApplicationData/ApplicationData.tsx index b1ff49d1..ffdc3ca1 100644 --- a/client/src/components/ApplicationData/ApplicationData.tsx +++ b/client/src/components/ApplicationData/ApplicationData.tsx @@ -1,5 +1,5 @@ import { IApplication } from '../../requests/responses/application' -import { Stack, Group, Divider, Grid, Title, Badge } from '@mantine/core' +import { Stack, Group, Divider, Grid, Title, Badge, Accordion } from '@mantine/core' import AuthenticatedFilePreview from '../AuthenticatedFilePreview/AuthenticatedFilePreview' import React, { ReactNode } from 'react' import { GLOBAL_CONFIG } from '../../config/global' @@ -8,6 +8,7 @@ import { formatApplicationState, formatDate, formatUser } from '../../utils/form import LabeledItem from '../LabeledItem/LabeledItem' import DocumentEditor from '../DocumentEditor/DocumentEditor' import { ApplicationStateColor } from '../../config/colors' +import TopicAccordionItem from '../TopicAccordionItem/TopicAccordionItem' interface IApplicationDataProps { application: IApplication @@ -28,10 +29,13 @@ const ApplicationData = (props: IApplicationDataProps) => { {rightTitleSection} - + {application.topic ? ( + + + + ) : ( + + )} } diff --git a/client/src/components/ApplicationsTable/ApplicationsTable.tsx b/client/src/components/ApplicationsTable/ApplicationsTable.tsx index 1e137574..d33ae154 100644 --- a/client/src/components/ApplicationsTable/ApplicationsTable.tsx +++ b/client/src/components/ApplicationsTable/ApplicationsTable.tsx @@ -47,6 +47,7 @@ const ApplicationsTable = (props: IApplicationsTableProps) => { title: 'Suggested Title', ellipsis: true, width: 300, + render: (application) => application.thesisTitle || application.topic?.title, }, reviewed_at: { accessor: 'reviewedAt', diff --git a/client/src/components/AuthenticatedFilePreview/AuthenticatedFilePreview.tsx b/client/src/components/AuthenticatedFilePreview/AuthenticatedFilePreview.tsx index 20f1037b..2c88c558 100644 --- a/client/src/components/AuthenticatedFilePreview/AuthenticatedFilePreview.tsx +++ b/client/src/components/AuthenticatedFilePreview/AuthenticatedFilePreview.tsx @@ -1,9 +1,7 @@ -import { useEffect, useMemo, useState } from 'react' -import { doRequest } from '../../requests/request' +import { useMemo, useState } from 'react' import { Button, Space, Stack, Text } from '@mantine/core' -import { downloadPdf } from '../../utils/blob' -import { showSimpleError } from '../../utils/notification' -import { getApiResponseErrorMessage } from '../../requests/handler' +import { downloadFile } from '../../utils/blob' +import { useApiFile } from '../../hooks/fetcher' interface IAuthenticatedIframeProps { url: string @@ -16,27 +14,9 @@ interface IAuthenticatedIframeProps { const AuthenticatedFilePreview = (props: IAuthenticatedIframeProps) => { const { url, filename, allowDownload = true, title, height } = props - const [file, setFile] = useState() + const [file, setFile] = useState() - useEffect(() => { - setFile(undefined) - - return doRequest( - url, - { - method: 'GET', - requiresAuth: true, - responseType: 'blob', - }, - (res) => { - if (res.ok) { - setFile(res.data) - } else { - showSimpleError(getApiResponseErrorMessage(res)) - } - }, - ) - }, [url]) + useApiFile(url, filename, setFile) const iframeUrl = useMemo(() => { return file ? `${URL.createObjectURL(file)}#toolbar=0&navpanes=0` : undefined @@ -49,7 +29,7 @@ const AuthenticatedFilePreview = (props: IAuthenticatedIframeProps) => { {allowDownload && ( <> - diff --git a/client/src/components/TopicAccordionItem/TopicAccordionItem.tsx b/client/src/components/TopicAccordionItem/TopicAccordionItem.tsx new file mode 100644 index 00000000..7ed659a9 --- /dev/null +++ b/client/src/components/TopicAccordionItem/TopicAccordionItem.tsx @@ -0,0 +1,24 @@ +import { Accordion } from '@mantine/core' +import TopicData from '../TopicData/TopicData' +import React, { PropsWithChildren } from 'react' +import { ITopic } from '../../requests/responses/topic' + +interface ITopicAccordionItemProps { + topic: ITopic +} + +const TopicAccordionItem = (props: PropsWithChildren) => { + const { topic, children } = props + + return ( + + {topic.title} + + + {children} + + + ) +} + +export default TopicAccordionItem diff --git a/client/src/components/TopicData/TopicData.tsx b/client/src/components/TopicData/TopicData.tsx new file mode 100644 index 00000000..8a31cbcc --- /dev/null +++ b/client/src/components/TopicData/TopicData.tsx @@ -0,0 +1,55 @@ +import { ITopic } from '../../requests/responses/topic' +import { Grid, Stack } from '@mantine/core' +import LabeledItem from '../LabeledItem/LabeledItem' +import { formatDate, formatUser } from '../../utils/format' +import { GLOBAL_CONFIG } from '../../config/global' +import DocumentEditor from '../DocumentEditor/DocumentEditor' +import React from 'react' + +interface ITopicDataProps { + topic: ITopic +} + +const TopicData = (props: ITopicDataProps) => { + const { topic } = props + + return ( + + + + formatUser(user, { withUniversityId: false })) + .join(', ')} + /> + + + formatUser(user, { withUniversityId: false })) + .join(', ')} + /> + + + + + + + + + + {topic.goals && } + {topic.references && } + + ) +} + +export default TopicData diff --git a/client/src/components/TopicsTable/TopicsTable.tsx b/client/src/components/TopicsTable/TopicsTable.tsx index dc88339e..d6fa604d 100644 --- a/client/src/components/TopicsTable/TopicsTable.tsx +++ b/client/src/components/TopicsTable/TopicsTable.tsx @@ -1,4 +1,3 @@ -import React, { ReactNode } from 'react' import { useAutoAnimate } from '@formkit/auto-animate/react' import { DataTable, DataTableColumn } from 'mantine-datatable' import { formatDate, formatUser } from '../../utils/format' @@ -7,15 +6,15 @@ import { ITopic } from '../../requests/responses/topic' import { useNavigate } from 'react-router-dom' import { Badge } from '@mantine/core' -type TopicColumn = 'title' | 'advisor' | 'supervisor' | 'actions' | 'state' | 'createdAt' +type TopicColumn = 'title' | 'advisor' | 'supervisor' | 'state' | 'createdAt' | string interface ITopicsTableProps { columns?: TopicColumn[] - actions?: (topic: ITopic) => ReactNode + extraColumns?: Record> } const TopicsTable = (props: ITopicsTableProps) => { - const { actions, columns = ['title', 'supervisor', 'advisor'] } = props + const { extraColumns, columns = ['title', 'supervisor', 'advisor'] } = props const navigate = useNavigate() const [bodyRef] = useAutoAnimate() @@ -35,31 +34,25 @@ const TopicsTable = (props: ITopicsTableProps) => { accessor: 'title', title: 'Title', ellipsis: true, - width: 300, + width: 350, }, supervisor: { accessor: 'supervisor', title: 'Supervisor', - render: (topic) => topic.supervisors.map((user) => formatUser(user)).join(', '), + render: (topic) => + topic.supervisors.map((user) => formatUser(user, { withUniversityId: false })).join(', '), }, advisor: { accessor: 'advisor', title: 'Advisor', - render: (topic) => topic.advisors.map((user) => formatUser(user)).join(', '), + render: (topic) => + topic.advisors.map((user) => formatUser(user, { withUniversityId: false })).join(', '), }, createdAt: { accessor: 'createdAt', title: 'Created At', render: (record) => formatDate(record.createdAt), }, - actions: { - accessor: 'actions', - title: 'Actions', - textAlign: 'center', - noWrap: true, - width: 120, - render: (topic) => actions?.(topic), - }, } return ( @@ -79,7 +72,7 @@ const TopicsTable = (props: ITopicsTableProps) => { bodyRef={bodyRef} records={topics?.content} idAccessor='topicId' - columns={columns.map((column) => columnConfig[column])} + columns={columns.map((column) => columnConfig[column] || extraColumns?.[column])} onRowClick={({ record }) => navigate(`/topics/${record.topicId}`)} /> ) diff --git a/client/src/components/UploadArea/UploadArea.tsx b/client/src/components/UploadArea/UploadArea.tsx index b80b5294..f7698a1a 100644 --- a/client/src/components/UploadArea/UploadArea.tsx +++ b/client/src/components/UploadArea/UploadArea.tsx @@ -1,8 +1,9 @@ import { ActionIcon, Card, + Center, Group, - InputLabel, + Input, rem, Stack, Text, @@ -11,6 +12,7 @@ import { import { ImageSquare, UploadSimple, X } from 'phosphor-react' import { Dropzone, PDF_MIME_TYPE } from '@mantine/dropzone' import { showSimpleError } from '../../utils/notification' +import { useMemo } from 'react' interface IUploadAreaProps { value: File | undefined @@ -25,28 +27,34 @@ const UploadArea = (props: IUploadAreaProps) => { const theme = useMantineTheme() + const iframeUrl = useMemo(() => { + return value ? `${URL.createObjectURL(value)}#toolbar=0&navpanes=0` : undefined + }, [value]) + return ( - - {label && ( - - {label} - - )} + {value ? ( - - - {value.name} - - onChange(undefined)}> - - - +
+ +