From 9dbdde6baaffffaec28ad4a14c392dfb1515972c Mon Sep 17 00:00:00 2001 From: Kacper Donat Date: Tue, 12 Jan 2021 00:22:24 +0100 Subject: [PATCH] Stuff --- src/api/dto/internship-registration.ts | 1 + src/data/report.ts | 2 +- src/management/api/document.ts | 4 +- src/management/api/index.ts | 4 +- src/management/api/internship.ts | 10 +- src/management/api/report.ts | 27 +++++ src/management/edition/manage.tsx | 2 +- src/management/edition/report/list.tsx | 141 +++++++++++++++++++++++++ src/management/routing.tsx | 2 + src/pages/steps/report.tsx | 45 +++++++- translations/management.pl.yaml | 2 + 11 files changed, 229 insertions(+), 11 deletions(-) create mode 100644 src/management/api/report.ts create mode 100644 src/management/edition/report/list.tsx diff --git a/src/api/dto/internship-registration.ts b/src/api/dto/internship-registration.ts index 829f308..8368889 100644 --- a/src/api/dto/internship-registration.ts +++ b/src/api/dto/internship-registration.ts @@ -130,6 +130,7 @@ export interface InternshipInfoDTO extends Identifiable { export const internshipReportDtoTransformer: OneWayTransformer = { transform(subject: InternshipReportDTO, context?: unknown): Report { return { + id: subject.id, fields: JSON.parse(subject.value), ...statefulDtoTransformer.transform(subject), } diff --git a/src/data/report.ts b/src/data/report.ts index 03760d0..b95e700 100644 --- a/src/data/report.ts +++ b/src/data/report.ts @@ -33,7 +33,7 @@ export type ReportFieldValues = { [field: string]: ReportFieldValue }; export type ReportSchema = ReportFieldDefinition[]; export type ReportFieldType = ReportFieldDefinition['type']; -export interface Report extends Stateful { +export interface Report extends Stateful, Identifiable { fields: ReportFieldValues; } diff --git a/src/management/api/document.ts b/src/management/api/document.ts index 4d1beef..728fb10 100644 --- a/src/management/api/document.ts +++ b/src/management/api/document.ts @@ -3,8 +3,8 @@ import { axios } from "@/api"; import { prepare } from "@/routing"; import { InternshipDocument } from "@/api/dto/internship-registration"; -const DOCUMENT_ACCEPT_ENDPOINT = "/management/document/accept/:id"; -const DOCUMENT_REJECT_ENDPOINT = "/management/document/reject/:id"; +const DOCUMENT_ACCEPT_ENDPOINT = "/management/document/:id/accept"; +const DOCUMENT_REJECT_ENDPOINT = "/management/document/:id/reject"; export async function accept(document: OneOrMany, comment?: string): Promise { const documents = encapsulate(document) diff --git a/src/management/api/index.ts b/src/management/api/index.ts index c75b0a0..2c6309a 100644 --- a/src/management/api/index.ts +++ b/src/management/api/index.ts @@ -5,6 +5,7 @@ import * as course from "./course" import * as internship from "./internship" import * as document from "./document" import * as field from "./field" +import * as report from "./report" export const api = { edition, @@ -13,7 +14,8 @@ export const api = { course, internship, document, - field + field, + report } export default api; diff --git a/src/management/api/internship.ts b/src/management/api/internship.ts index 0d331b7..2c95045 100644 --- a/src/management/api/internship.ts +++ b/src/management/api/internship.ts @@ -8,7 +8,7 @@ import { InternshipDocument, InternshipDocumentDTO, internshipDocumentDtoTransformer, - InternshipInfoDTO, + InternshipInfoDTO, internshipReportDtoTransformer, submissionStateDtoTransformer } from "@/api/dto/internship-registration"; import { Transformer } from "@/serialization"; @@ -17,22 +17,25 @@ import { internshipTypeDtoTransformer } from "@/api/dto/type"; import { studentDtoTransfer } from "@/api/dto/student"; import { programEntryDtoTransformer } from "@/api/dto/edition"; import { UploadType } from "@/api/upload"; +import { Report } from "@/data/report"; export type InternshipSubmission = Nullable & { state: SubmissionStatus, changed: Moment | null, ipp: InternshipDocument | null, + report: Report | null, } const INTERNSHIP_MANAGEMENT_INDEX_ENDPOINT = "/management/internship"; const INTERNSHIP_MANAGEMENT_ENDPOINT = "/management/internship/:id"; -const INTERNSHIP_ACCEPT_ENDPOINT = "/management/internship/accept/:id"; -const INTERNSHIP_REJECT_ENDPOINT = "/management/internship/reject/:id"; +const INTERNSHIP_ACCEPT_ENDPOINT = "/management/internship/:id/registration/accept"; +const INTERNSHIP_REJECT_ENDPOINT = "/management/internship/:id/registration/reject"; const internshipInfoDtoTransformer: Transformer = { transform(subject: InternshipInfoDTO, context?: never): InternshipSubmission { // @ts-ignore const ipp = subject.documentation.find(doc => doc.type === UploadType.Ipp); + const report = subject.report; return { changed: moment(subject.internshipRegistration.submissionDate), @@ -50,6 +53,7 @@ const internshipInfoDtoTransformer: Transformer, comment?: string): Promise { + const documents = encapsulate(document) + + await Promise.all(documents.map(document => axios.put( + prepare(REPORT_ACCEPT_ENDPOINT, { id: document.id || ""}), + JSON.stringify(comment), + { headers: { 'Content-Type': 'application/json' } } + ))) +} + +export async function discard(document: OneOrMany, comment: string): Promise { + const documents = encapsulate(document) + + await Promise.all(documents.map(document => axios.put( + prepare(REPORT_REJECT_ENDPOINT, { id: document.id || ""}), + JSON.stringify(comment), + { headers: { 'Content-Type': 'application/json' } } + ))) +} diff --git a/src/management/edition/manage.tsx b/src/management/edition/manage.tsx index 7814f14..1893043 100644 --- a/src/management/edition/manage.tsx +++ b/src/management/edition/manage.tsx @@ -53,7 +53,7 @@ export const EditionManagement = ({ edition }: EditionManagementProps) => { } route={ route("management:edition_ipp_index", { edition: edition.id || "" }) }> { t("management:edition.ipp.title") } - } route={ route("management:edition_report_form", { edition: edition.id || "" }) }> + } route={ route("management:edition_reports", { edition: edition.id || "" }) }> { t("management:edition.reports.title") } } route={ route("management:edition_report_form", { edition: edition.id || "" }) }> diff --git a/src/management/edition/report/list.tsx b/src/management/edition/report/list.tsx new file mode 100644 index 0000000..430d8a8 --- /dev/null +++ b/src/management/edition/report/list.tsx @@ -0,0 +1,141 @@ +import React, { useCallback, useEffect, useState } from "react"; +import { useTranslation } from "react-i18next"; +import { useAsync, useAsyncState } from "@/hooks"; +import { useSpacing } from "@/styles"; +import api from "@/management/api"; +import { Box, Button, Container, IconButton, Typography } from "@material-ui/core"; +import MaterialTable, { Column } from "material-table"; +import { actionsColumn } from "@/management/common/helpers"; +import { FileDownloadOutline, FileFind, Refresh, StickerCheckOutline, StickerRemoveOutline } from "mdi-material-ui"; +import { Page } from "@/pages/base"; +import { Actions } from "@/components"; +import { BulkActions } from "@/management/common/BulkActions"; +import { Async } from "@/components/async"; +import { MaterialTableTitle } from "@/management/common/MaterialTableTitle"; +import { EditionManagement, EditionManagementProps } from "@/management/edition/manage"; +import { Link as RouterLink } from "react-router-dom"; +import { route } from "@/routing"; +import { AcceptSubmissionDialog, DiscardSubmissionDialog } from "@/components/acceptance-action"; +import { ProposalPreview } from "@/components/proposalPreview"; +import { InternshipSubmission } from "@/management/api/internship"; +import { StateLabel } from "@/management/edition/internship/common"; +import { createPortal } from "react-dom"; +import { Internship, Stateful } from "@/data"; +import { FileInfo } from "@/components/fileinfo"; +import { Alert } from "@material-ui/lab"; +import { InternshipDocument } from "@/api/dto/internship-registration"; +import { Report } from "@/data/report"; + +const title = "edition.reports.title"; + +export const canAccept = (subject: Stateful | null) => !!(subject && ["declined", "awaiting"].includes(subject.state)); +export const canDiscard = (subject: Stateful | null) => !!(subject && ["accepted", "awaiting"].includes(subject.state)); + +export const ReportManagement = ({ edition }: EditionManagementProps) => { + const { t } = useTranslation("management"); + const [result, setInternshipsPromise] = useAsyncState(); + const [selected, setSelected] = useState([]); + const spacing = useSpacing(2); + + const updateInternshipList = () => { + setInternshipsPromise(api.internship.all(edition)); + } + + useEffect(updateInternshipList, []); + + const AcceptAction = ({ internship }: { internship: InternshipSubmission }) => { + const [open, setOpen] = useState(false); + + const handleSubmissionAccept = async (comment?: string) => { + setOpen(false); + await api.report.accept(internship.report as Report, comment); + updateInternshipList(); + } + + return <> + setOpen(true) }> + { createPortal( + setOpen(false) }/>, + document.getElementById("modals") as Element, + ) } + ; + } + + const DiscardAction = ({ internship }: { internship: InternshipSubmission }) => { + const [open, setOpen] = useState(false); + + const handleSubmissionDiscard = async (comment: string) => { + setOpen(false); + await api.report.discard(internship.report as Report, comment); + updateInternshipList(); + } + + return <> + setOpen(true) }> + { createPortal( + setOpen(false) }/>, + document.getElementById("modals") as Element, + ) } + ; + } + + const InternshipDetails = ({ summary }: { summary: InternshipSubmission }) => { + return + { summary.report && JSON.stringify(summary.report) } + + } + + const columns: Column[] = [ + { + title: t("internship.column.student"), + render: internship => <>{internship.intern?.name} {internship.intern?.surname}, + }, + { + title: t("internship.column.album"), + field: "intern.albumNumber", + }, + { + title: t("internship.column.type"), + field: "type.label.pl", + }, + { + title: t("internship.column.changed"), + render: summary => summary.changed?.format("yyyy-MM-DD") + }, + { + title: t("internship.column.status"), + render: summary => + }, + actionsColumn(internship => <> + { canAccept(internship.report) && } + { canDiscard(internship.report) && } + ) + ]; + + return + + + { t(title) } + + { t(title) } + + + + + + { selected.length > 0 && + + } + { + internships => } + columns={ columns } + data={ internships } + onSelectionChange={ internships => setSelected(internships) } + options={ { selection: true, pageSize: 10 } } + detailPanel={ summary => } + /> + } + + +} diff --git a/src/management/routing.tsx b/src/management/routing.tsx index 863fe7b..d1f4d2d 100644 --- a/src/management/routing.tsx +++ b/src/management/routing.tsx @@ -11,6 +11,7 @@ import { InternshipManagement } from "@/management/edition/internship/list"; import { InternshipDetails } from "@/management/edition/internship/details"; import { PlanManagement } from "@/management/edition/ipp/list"; import { ReportFields } from "@/management/report/fields/list"; +import { ReportManagement } from "@/management/edition/report/list"; export const managementRoutes: Route[] = ([ { name: "index", path: "/", content: ManagementIndex, exact: true }, @@ -20,6 +21,7 @@ export const managementRoutes: Route[] = ([ { name: "edition_manage", path: "/editions/:edition", content: EditionManagement, tags: ["edition"], exact: true }, { name: "edition_internship", path: "/editions/:edition/internships/:internship", content: InternshipDetails, tags: ["edition"] }, { name: "edition_internships", path: "/editions/:edition/internships", content: InternshipManagement, tags: ["edition"] }, + { name: "edition_reports", path: "/editions/:edition/reports", content: ReportManagement, tags: ["edition"] }, { name: "edition_ipp_index", path: "/editions/:edition/ipp", content: PlanManagement, tags: ["edition"] }, { name: "editions", path: "/editions", content: EditionsManagement }, diff --git a/src/pages/steps/report.tsx b/src/pages/steps/report.tsx index 083021f..c1e99c6 100644 --- a/src/pages/steps/report.tsx +++ b/src/pages/steps/report.tsx @@ -2,16 +2,37 @@ import { useSelector } from "react-redux"; import { AppState } from "@/state/reducer"; import { getSubmissionStatus, SubmissionState, SubmissionStatus } from "@/state/reducer/submission"; import { useTranslation } from "react-i18next"; -import { Box, Button, ButtonProps, StepProps } from "@material-ui/core"; -import { FileUploadOutline } from "mdi-material-ui/index"; +import { Box, Button, ButtonProps, Dialog, DialogContent, DialogProps, DialogTitle, StepProps, Typography } from "@material-ui/core"; +import { FileFind, FileUploadOutline } from "mdi-material-ui/index"; import { route } from "@/routing"; import { Link as RouterLink } from "react-router-dom"; import { Actions, Step } from "@/components"; -import React, { HTMLProps } from "react"; +import React, { HTMLProps, useState } from "react"; import { Alert, AlertTitle } from "@material-ui/lab"; import { ContactButton, Status } from "@/pages/steps/common"; import { useCurrentEdition, useDeadlines } from "@/hooks"; import { useSpacing } from "@/styles"; +import { Report } from "@/data/report"; +import { sampleReportSchema } from "@/provider/dummy/report"; +import { createPortal } from "react-dom"; +import { getInternshipReport } from "@/state/reducer/report"; + +export type ReportPreviewDialogProps = { + report: Report; +} & DialogProps; + +export const ReportPreviewDialog = ({ report, ...props }: ReportPreviewDialogProps) => { + const schema = sampleReportSchema; + const { t } = useTranslation(); + + return + { t("steps.report.preview") } + { schema.map(field => <> + { field.label.pl } + { JSON.stringify(report.fields[`field_${field.id}`]) } + )} + +} const ReportActions = () => { const status = useSelector(state => getSubmissionStatus(state.report)); @@ -23,9 +44,27 @@ const ReportActions = () => { { children } + const ReviewAction = (props: ButtonProps) => { + const [open, setOpen,] = useState(false); + const report = useSelector(state => getInternshipReport(state.report) as Report); + + return <> + + { createPortal( + setOpen(false) }/>, + document.getElementById("modals") as Element, + ) } + + } + switch (status) { case "awaiting": return + case "accepted": return diff --git a/translations/management.pl.yaml b/translations/management.pl.yaml index 3d9a77c..cae6424 100644 --- a/translations/management.pl.yaml +++ b/translations/management.pl.yaml @@ -28,6 +28,8 @@ edition: title: Indywidualne Plany Praktyk index: title: "Edycje praktyk" + reports: + title: "Raporty praktyki" field: id: Identyfikator start: PoczÄ…tek