Add ipp management
This commit is contained in:
parent
dffc279b4a
commit
d6de1fb959
@ -82,9 +82,16 @@ export interface InternshipRegistrationDTO extends Identifiable {
|
|||||||
declaredHours: number,
|
declaredHours: number,
|
||||||
subjects: { subject: ProgramEntryDTO }[],
|
subjects: { subject: ProgramEntryDTO }[],
|
||||||
submissionDate: string,
|
submissionDate: string,
|
||||||
|
changeStateComment: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface InternshipDocument extends Identifiable {
|
export interface InternshipDocument extends Identifiable {
|
||||||
|
description: null,
|
||||||
|
type: UploadType,
|
||||||
|
state: SubmissionStatus,
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface InternshipDocumentDTO extends Identifiable {
|
||||||
description: null,
|
description: null,
|
||||||
type: UploadType,
|
type: UploadType,
|
||||||
state: SubmissionState,
|
state: SubmissionState,
|
||||||
@ -94,7 +101,7 @@ const reference = (subject: Identifiable | null): Identifiable | null => subject
|
|||||||
|
|
||||||
export interface InternshipInfoDTO extends Identifiable {
|
export interface InternshipInfoDTO extends Identifiable {
|
||||||
internshipRegistration: InternshipRegistrationDTO;
|
internshipRegistration: InternshipRegistrationDTO;
|
||||||
documentation: InternshipDocument[],
|
documentation: InternshipDocumentDTO[],
|
||||||
student: StudentDTO,
|
student: StudentDTO,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -139,3 +146,12 @@ export const internshipRegistrationDtoTransformer: OneWayTransformer<InternshipR
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const internshipDocumentDtoTransformer: OneWayTransformer<InternshipDocumentDTO, InternshipDocument> = {
|
||||||
|
transform(dto: InternshipDocumentDTO, context?: unknown): InternshipDocument {
|
||||||
|
return {
|
||||||
|
...dto,
|
||||||
|
state: submissionStateDtoTransformer.transform(dto.state),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -7,7 +7,7 @@ import {
|
|||||||
DialogActions,
|
DialogActions,
|
||||||
DialogContent,
|
DialogContent,
|
||||||
DialogProps,
|
DialogProps,
|
||||||
DialogTitle,
|
DialogTitle, FormControl,
|
||||||
Menu,
|
Menu,
|
||||||
MenuItem,
|
MenuItem,
|
||||||
TextField,
|
TextField,
|
||||||
@ -17,6 +17,10 @@ import { MenuDown, StickerCheckOutline, StickerRemoveOutline } from "mdi-materia
|
|||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { useVerticalSpacing } from "@/styles";
|
import { useVerticalSpacing } from "@/styles";
|
||||||
import { createPortal } from "react-dom";
|
import { createPortal } from "react-dom";
|
||||||
|
// @ts-ignore
|
||||||
|
import { CKEditor } from '@ckeditor/ckeditor5-react';
|
||||||
|
// @ts-ignore
|
||||||
|
import ClassicEditor from '@ckeditor/ckeditor5-build-classic';
|
||||||
|
|
||||||
type AcceptSubmissionDialogProps = {
|
type AcceptSubmissionDialogProps = {
|
||||||
onAccept: (comment?: string) => void;
|
onAccept: (comment?: string) => void;
|
||||||
@ -28,11 +32,11 @@ export function AcceptSubmissionDialog({ onAccept, label, ...props }: AcceptSubm
|
|||||||
const [comment, setComment] = useState<string>("");
|
const [comment, setComment] = useState<string>("");
|
||||||
const classes = useVerticalSpacing(3);
|
const classes = useVerticalSpacing(3);
|
||||||
|
|
||||||
return <Dialog maxWidth="md" { ...props }>
|
return <Dialog maxWidth="xl" { ...props }>
|
||||||
<DialogTitle>{ t(label + ".accept.title") }</DialogTitle>
|
<DialogTitle>{ t(label + ".accept.title") }</DialogTitle>
|
||||||
<DialogContent className={ classes.root }>
|
<DialogContent className={ classes.root }>
|
||||||
<Typography variant="body1">{ t(label + ".accept.info") }</Typography>
|
<Typography variant="body1">{ t(label + ".accept.info") }</Typography>
|
||||||
<TextField multiline value={ comment } onChange={ ev => setComment(ev.target.value) } fullWidth label={ t("comments") } rows={3}/>
|
<CKEditor data={ comment } editor={ ClassicEditor } onChange={ (_: any, ed: any) => setComment(ed.getData()) }/>
|
||||||
|
|
||||||
<DialogActions>
|
<DialogActions>
|
||||||
<Button onClick={ ev => props.onClose?.(ev, "backdropClick") }>
|
<Button onClick={ ev => props.onClose?.(ev, "backdropClick") }>
|
||||||
@ -56,11 +60,11 @@ export function DiscardSubmissionDialog({ onDiscard, label, ...props }: DiscardS
|
|||||||
const [comment, setComment] = useState<string>("");
|
const [comment, setComment] = useState<string>("");
|
||||||
const classes = useVerticalSpacing(3);
|
const classes = useVerticalSpacing(3);
|
||||||
|
|
||||||
return <Dialog maxWidth="md" { ...props }>
|
return <Dialog maxWidth="xl" { ...props }>
|
||||||
<DialogTitle>{ t(label + ".accept.title") }</DialogTitle>
|
<DialogTitle>{ t(label + ".accept.title") }</DialogTitle>
|
||||||
<DialogContent className={ classes.root }>
|
<DialogContent className={ classes.root }>
|
||||||
<Typography variant="body1">{ t(label + ".accept.info") }</Typography>
|
<Typography variant="body1">{ t(label + ".accept.info") }</Typography>
|
||||||
<TextField multiline value={ comment } onChange={ ev => setComment(ev.target.value) } fullWidth label={ t("comments") } rows={3}/>
|
<CKEditor data={ comment } editor={ ClassicEditor } onChange={ (_: any, ed: any) => setComment(ed.getData()) }/>
|
||||||
|
|
||||||
<DialogActions>
|
<DialogActions>
|
||||||
<Button onClick={ ev => props.onClose?.(ev, "backdropClick") }>
|
<Button onClick={ ev => props.onClose?.(ev, "backdropClick") }>
|
||||||
|
27
src/management/api/document.ts
Normal file
27
src/management/api/document.ts
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
import { encapsulate, OneOrMany } from "@/helpers";
|
||||||
|
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";
|
||||||
|
|
||||||
|
export async function accept(document: OneOrMany<InternshipDocument>, comment?: string): Promise<void> {
|
||||||
|
const documents = encapsulate(document)
|
||||||
|
|
||||||
|
await Promise.all(documents.map(document => axios.put(
|
||||||
|
prepare(DOCUMENT_ACCEPT_ENDPOINT, { id: document.id || ""}),
|
||||||
|
JSON.stringify(comment),
|
||||||
|
{ headers: { 'Content-Type': 'application/json' } }
|
||||||
|
)))
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function discard(document: OneOrMany<InternshipDocument>, comment: string): Promise<void> {
|
||||||
|
const documents = encapsulate(document)
|
||||||
|
|
||||||
|
await Promise.all(documents.map(document => axios.put(
|
||||||
|
prepare(DOCUMENT_REJECT_ENDPOINT, { id: document.id || ""}),
|
||||||
|
JSON.stringify(comment),
|
||||||
|
{ headers: { 'Content-Type': 'application/json' } }
|
||||||
|
)))
|
||||||
|
}
|
@ -3,13 +3,15 @@ import * as page from "./page"
|
|||||||
import * as type from "./type"
|
import * as type from "./type"
|
||||||
import * as course from "./course"
|
import * as course from "./course"
|
||||||
import * as internship from "./internship"
|
import * as internship from "./internship"
|
||||||
|
import * as document from "./document"
|
||||||
|
|
||||||
export const api = {
|
export const api = {
|
||||||
edition,
|
edition,
|
||||||
page,
|
page,
|
||||||
type,
|
type,
|
||||||
course,
|
course,
|
||||||
internship
|
internship,
|
||||||
|
document
|
||||||
}
|
}
|
||||||
|
|
||||||
export default api;
|
export default api;
|
||||||
|
@ -1,55 +1,27 @@
|
|||||||
import { Identifiable, Identifier, Internship } from "@/data";
|
import { Identifiable, Identifier, Internship } from "@/data";
|
||||||
import { sampleCompanies, sampleStudent } from "@/provider/dummy";
|
|
||||||
import moment, { Moment } from "moment-timezone";
|
import moment, { Moment } from "moment-timezone";
|
||||||
import { encapsulate, Nullable, OneOrMany } from "@/helpers";
|
import { encapsulate, Nullable, OneOrMany } from "@/helpers";
|
||||||
import { SubmissionStatus } from "@/state/reducer/submission";
|
import { SubmissionStatus } from "@/state/reducer/submission";
|
||||||
import { axios } from "@/api";
|
import { axios } from "@/api";
|
||||||
import { prepare, query } from "@/routing";
|
import { prepare, query } from "@/routing";
|
||||||
import { InternshipInfoDTO, submissionStateDtoTransformer } from "@/api/dto/internship-registration";
|
import {
|
||||||
|
InternshipDocument,
|
||||||
|
InternshipDocumentDTO,
|
||||||
|
internshipDocumentDtoTransformer,
|
||||||
|
InternshipInfoDTO,
|
||||||
|
submissionStateDtoTransformer
|
||||||
|
} from "@/api/dto/internship-registration";
|
||||||
import { Transformer } from "@/serialization";
|
import { Transformer } from "@/serialization";
|
||||||
import { mentorDtoTransformer } from "@/api/dto/mentor";
|
import { mentorDtoTransformer } from "@/api/dto/mentor";
|
||||||
import { internshipTypeDtoTransformer } from "@/api/dto/type";
|
import { internshipTypeDtoTransformer } from "@/api/dto/type";
|
||||||
import { studentDtoTransfer } from "@/api/dto/student";
|
import { studentDtoTransfer } from "@/api/dto/student";
|
||||||
import { programEntryDtoTransformer } from "@/api/dto/edition";
|
import { programEntryDtoTransformer } from "@/api/dto/edition";
|
||||||
|
import { UploadType } from "@/api/upload";
|
||||||
|
|
||||||
export type InternshipSubmission = Nullable<Internship> & {
|
export type InternshipSubmission = Nullable<Internship> & {
|
||||||
state: SubmissionStatus,
|
state: SubmissionStatus,
|
||||||
changed: Moment | null,
|
changed: Moment | null,
|
||||||
}
|
ipp: InternshipDocument | null,
|
||||||
|
|
||||||
const sampleInternship: InternshipSubmission = {
|
|
||||||
id: "test",
|
|
||||||
company: sampleCompanies[0],
|
|
||||||
startDate: moment('2020-07-01'),
|
|
||||||
endDate: moment('2020-08-31'),
|
|
||||||
hours: 180,
|
|
||||||
intern: sampleStudent,
|
|
||||||
isAccepted: false,
|
|
||||||
lengthInWeeks: 0,
|
|
||||||
mentor: {
|
|
||||||
email: "test@test.com",
|
|
||||||
name: "Test",
|
|
||||||
surname: "Testowy",
|
|
||||||
phone: "+48 123 456 789"
|
|
||||||
},
|
|
||||||
office: sampleCompanies[0].offices[0],
|
|
||||||
program: [
|
|
||||||
{ description: "Test" },
|
|
||||||
],
|
|
||||||
type: {
|
|
||||||
description: {
|
|
||||||
pl: "Przykładowy typ",
|
|
||||||
en: "Example type"
|
|
||||||
},
|
|
||||||
label: {
|
|
||||||
pl: "Przykładowy",
|
|
||||||
en: "Example"
|
|
||||||
},
|
|
||||||
requiresDeanApproval: true,
|
|
||||||
requiresInsurance: false
|
|
||||||
},
|
|
||||||
state: "declined",
|
|
||||||
changed: moment(),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const INTERNSHIP_MANAGEMENT_INDEX_ENDPOINT = "/management/internship";
|
const INTERNSHIP_MANAGEMENT_INDEX_ENDPOINT = "/management/internship";
|
||||||
@ -59,6 +31,9 @@ const INTERNSHIP_REJECT_ENDPOINT = "/management/internship/reject/:id"
|
|||||||
|
|
||||||
const internshipInfoDtoTransformer: Transformer<InternshipInfoDTO, InternshipSubmission> = {
|
const internshipInfoDtoTransformer: Transformer<InternshipInfoDTO, InternshipSubmission> = {
|
||||||
transform(subject: InternshipInfoDTO, context?: never): InternshipSubmission {
|
transform(subject: InternshipInfoDTO, context?: never): InternshipSubmission {
|
||||||
|
// @ts-ignore
|
||||||
|
const ipp = subject.documentation.find<InternshipDocumentDTO>(doc => doc.type === UploadType.Ipp);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
changed: moment(subject.internshipRegistration.submissionDate),
|
changed: moment(subject.internshipRegistration.submissionDate),
|
||||||
company: subject.internshipRegistration.company,
|
company: subject.internshipRegistration.company,
|
||||||
@ -74,6 +49,7 @@ const internshipInfoDtoTransformer: Transformer<InternshipInfoDTO, InternshipSub
|
|||||||
program: (subject.internshipRegistration.subjects || []).map(subject => programEntryDtoTransformer.transform(subject.subject)),
|
program: (subject.internshipRegistration.subjects || []).map(subject => programEntryDtoTransformer.transform(subject.subject)),
|
||||||
state: submissionStateDtoTransformer.transform(subject.internshipRegistration.state),
|
state: submissionStateDtoTransformer.transform(subject.internshipRegistration.state),
|
||||||
type: subject.internshipRegistration.type && internshipTypeDtoTransformer.transform(subject.internshipRegistration.type),
|
type: subject.internshipRegistration.type && internshipTypeDtoTransformer.transform(subject.internshipRegistration.type),
|
||||||
|
ipp: ipp && internshipDocumentDtoTransformer.transform(ipp),
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
reverseTransform(subject: InternshipSubmission, context: undefined): InternshipInfoDTO {
|
reverseTransform(subject: InternshipSubmission, context: undefined): InternshipInfoDTO {
|
||||||
@ -96,11 +72,19 @@ export async function get(id: Identifier): Promise<InternshipSubmission> {
|
|||||||
export async function accept(internship: OneOrMany<Internship>, comment?: string): Promise<void> {
|
export async function accept(internship: OneOrMany<Internship>, comment?: string): Promise<void> {
|
||||||
const internships = encapsulate(internship)
|
const internships = encapsulate(internship)
|
||||||
|
|
||||||
await Promise.all(internships.map(internship => axios.put(prepare(INTERNSHIP_ACCEPT_ENDPOINT, { id: internship.id || ""}))))
|
await Promise.all(internships.map(internship => axios.put(
|
||||||
|
prepare(INTERNSHIP_ACCEPT_ENDPOINT, { id: internship.id || ""}),
|
||||||
|
JSON.stringify(comment),
|
||||||
|
{ headers: { 'Content-Type': 'application/json' } }
|
||||||
|
)))
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function discard(internship: OneOrMany<Internship>, comment: string): Promise<void> {
|
export async function discard(internship: OneOrMany<Internship>, comment: string): Promise<void> {
|
||||||
const internships = encapsulate(internship)
|
const internships = encapsulate(internship)
|
||||||
|
|
||||||
await Promise.all(internships.map(internship => axios.put(prepare(INTERNSHIP_REJECT_ENDPOINT, { id: internship.id || ""}))))
|
await Promise.all(internships.map(internship => axios.put(
|
||||||
|
prepare(INTERNSHIP_REJECT_ENDPOINT, { id: internship.id || ""}),
|
||||||
|
JSON.stringify(comment),
|
||||||
|
{ headers: { 'Content-Type': 'application/json' } }
|
||||||
|
)))
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { SubmissionStatus } from "@/state/reducer/submission";
|
import { SubmissionStatus } from "@/state/reducer/submission";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { ClockOutline, NotebookCheckOutline, NotebookEditOutline, NotebookRemoveOutline } from "mdi-material-ui";
|
import { ClockOutline, FileQuestion, NotebookCheckOutline, NotebookEditOutline, NotebookRemoveOutline } from "mdi-material-ui";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { Chip } from "@material-ui/core";
|
import { Chip } from "@material-ui/core";
|
||||||
import { createStyles, makeStyles, Theme } from "@material-ui/core/styles";
|
import { createStyles, makeStyles, Theme } from "@material-ui/core/styles";
|
||||||
@ -24,9 +24,11 @@ const useStateLabelStyles = makeStyles((theme: Theme) => createStyles<Submission
|
|||||||
}))
|
}))
|
||||||
|
|
||||||
export type StateLabelProps = {
|
export type StateLabelProps = {
|
||||||
state: SubmissionStatus;
|
state: SubmissionStatus | null;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const isValidState = (state: string | null) => ["accepted", "draft", "awaiting", "declined"].includes(state as string)
|
||||||
|
|
||||||
export const StateLabel = ({ state }: StateLabelProps) => {
|
export const StateLabel = ({ state }: StateLabelProps) => {
|
||||||
const icons: { [sate in SubmissionStatus]: React.ReactElement } = {
|
const icons: { [sate in SubmissionStatus]: React.ReactElement } = {
|
||||||
accepted: <NotebookCheckOutline/>,
|
accepted: <NotebookCheckOutline/>,
|
||||||
@ -39,7 +41,9 @@ export const StateLabel = ({ state }: StateLabelProps) => {
|
|||||||
|
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
return <Chip icon={ icons[state] } label={ t(`translation:submission.status.${ state }`) } variant="outlined" className={ classes[state] }/>
|
return isValidState(state)
|
||||||
|
? <Chip icon={ icons[state as SubmissionStatus] } label={ t(`translation:submission.status.${ state }`) } variant="outlined" className={ classes[state as SubmissionStatus] }/>
|
||||||
|
: <Chip icon={ <FileQuestion /> } label={ t(`translation:submission.status.empty`) } variant="outlined"/>
|
||||||
}
|
}
|
||||||
|
|
||||||
export const canEdit = (internship: InternshipSubmission) => internship.state != "draft";
|
export const canEdit = (internship: InternshipSubmission) => internship.state != "draft";
|
||||||
|
143
src/management/edition/ipp/list.tsx
Normal file
143
src/management/edition/ipp/list.tsx
Normal file
@ -0,0 +1,143 @@
|
|||||||
|
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 } from "@/data";
|
||||||
|
import { FileInfo } from "@/components/fileinfo";
|
||||||
|
import { Alert } from "@material-ui/lab";
|
||||||
|
import { InternshipDocument } from "@/api/dto/internship-registration";
|
||||||
|
|
||||||
|
const title = "edition.ipp.title";
|
||||||
|
|
||||||
|
export const canEdit = (ipp: InternshipDocument | null) => !!(ipp && ipp.state != "draft");
|
||||||
|
export const canDownload = (ipp: InternshipDocument | null) => !!(ipp && ipp.id);
|
||||||
|
export const canAccept = (ipp: InternshipDocument | null) => !!(ipp && ["declined", "awaiting"].includes(ipp.state));
|
||||||
|
export const canDiscard = (ipp: InternshipDocument | null) => !!(ipp && ["accepted", "awaiting"].includes(ipp.state));
|
||||||
|
|
||||||
|
export const PlanManagement = ({ edition }: EditionManagementProps) => {
|
||||||
|
const { t } = useTranslation("management");
|
||||||
|
const [result, setInternshipsPromise] = useAsyncState<InternshipSubmission[]>();
|
||||||
|
const [selected, setSelected] = useState<InternshipSubmission[]>([]);
|
||||||
|
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.document.accept(internship.ipp as InternshipDocument, comment);
|
||||||
|
updateInternshipList();
|
||||||
|
}
|
||||||
|
|
||||||
|
return <>
|
||||||
|
<IconButton onClick={ () => setOpen(true) }><StickerCheckOutline /></IconButton>
|
||||||
|
{ createPortal(
|
||||||
|
<AcceptSubmissionDialog onAccept={ handleSubmissionAccept } label="plan" open={ open } onClose={ () => 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.document.discard(internship.ipp as InternshipDocument, comment);
|
||||||
|
updateInternshipList();
|
||||||
|
}
|
||||||
|
|
||||||
|
return <>
|
||||||
|
<IconButton onClick={ () => setOpen(true) }><StickerRemoveOutline /></IconButton>
|
||||||
|
{ createPortal(
|
||||||
|
<DiscardSubmissionDialog onDiscard={ handleSubmissionDiscard } label="plan" open={ open } onClose={ () => setOpen(false) }/>,
|
||||||
|
document.getElementById("modals") as Element,
|
||||||
|
) }
|
||||||
|
</>;
|
||||||
|
}
|
||||||
|
|
||||||
|
const InternshipDetails = ({ summary }: { summary: InternshipSubmission }) => {
|
||||||
|
return <Box m={ 3 }>
|
||||||
|
{ summary.ipp ? <FileInfo document={ summary.ipp }/> : <Alert severity="warning" title={ t("ipp.no-submission.title") }>{ t("ipp.no-submission.info") }</Alert> }
|
||||||
|
</Box>
|
||||||
|
}
|
||||||
|
|
||||||
|
const columns: Column<InternshipSubmission>[] = [
|
||||||
|
{
|
||||||
|
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 => <StateLabel state={ summary.ipp?.state } />
|
||||||
|
},
|
||||||
|
actionsColumn(internship => <>
|
||||||
|
{ canAccept(internship.ipp) && <AcceptAction internship={ internship } /> }
|
||||||
|
{ canDiscard(internship.ipp) && <DiscardAction internship={ internship } /> }
|
||||||
|
{ canDownload(internship.ipp) && <IconButton component={ RouterLink } to={ route("management:edition_internship", { edition: edition.id || "", internship: internship.id || "" }) }><FileDownloadOutline /></IconButton> }
|
||||||
|
</>)
|
||||||
|
];
|
||||||
|
|
||||||
|
return <Page>
|
||||||
|
<Page.Header maxWidth="lg">
|
||||||
|
<EditionManagement.Breadcrumbs>
|
||||||
|
<Typography color="textPrimary">{ t(title) }</Typography>
|
||||||
|
</EditionManagement.Breadcrumbs>
|
||||||
|
<Page.Title>{ t(title) }</Page.Title>
|
||||||
|
</Page.Header>
|
||||||
|
<Container maxWidth="lg" className={ spacing.vertical }>
|
||||||
|
<Actions>
|
||||||
|
<Button onClick={ updateInternshipList } startIcon={ <Refresh /> }>{ t("refresh") }</Button>
|
||||||
|
</Actions>
|
||||||
|
{ selected.length > 0 && <BulkActions>
|
||||||
|
|
||||||
|
</BulkActions> }
|
||||||
|
<Async async={ result } keepValue>{
|
||||||
|
internships => <MaterialTable
|
||||||
|
title={ <MaterialTableTitle result={ result } label={ t(title) }/> }
|
||||||
|
columns={ columns }
|
||||||
|
data={ internships }
|
||||||
|
onSelectionChange={ internships => setSelected(internships) }
|
||||||
|
options={ { selection: true, pageSize: 10 } }
|
||||||
|
detailPanel={ summary => <InternshipDetails summary={ summary } /> }
|
||||||
|
/>
|
||||||
|
}</Async>
|
||||||
|
</Container>
|
||||||
|
</Page>
|
||||||
|
}
|
@ -53,7 +53,7 @@ export const EditionManagement = ({ edition }: EditionManagementProps) => {
|
|||||||
<ManagementLink icon={ <BriefcaseAccount/> } route={ route("management:edition_internships", { edition: edition.id || "" }) }>
|
<ManagementLink icon={ <BriefcaseAccount/> } route={ route("management:edition_internships", { edition: edition.id || "" }) }>
|
||||||
{ t("management:edition.internships.title") }
|
{ t("management:edition.internships.title") }
|
||||||
</ManagementLink>
|
</ManagementLink>
|
||||||
<ManagementLink icon={ <FormatPageBreak/> } route={ route("management:edition_report_form", { edition: edition.id || "" }) }>
|
<ManagementLink icon={ <FormatPageBreak/> } route={ route("management:edition_ipp_index", { edition: edition.id || "" }) }>
|
||||||
{ t("management:edition.ipp.title") }
|
{ t("management:edition.ipp.title") }
|
||||||
</ManagementLink>
|
</ManagementLink>
|
||||||
<ManagementLink icon={ <FileChartOutline/> } route={ route("management:edition_report_form", { edition: edition.id || "" }) }>
|
<ManagementLink icon={ <FileChartOutline/> } route={ route("management:edition_report_form", { edition: edition.id || "" }) }>
|
||||||
|
@ -10,6 +10,7 @@ import { EditionReportFields } from "@/management/edition/report/fields/list";
|
|||||||
import { EditionSettings } from "@/management/edition/settings";
|
import { EditionSettings } from "@/management/edition/settings";
|
||||||
import { InternshipManagement } from "@/management/edition/internship/list";
|
import { InternshipManagement } from "@/management/edition/internship/list";
|
||||||
import { InternshipDetails } from "@/management/edition/internship/details";
|
import { InternshipDetails } from "@/management/edition/internship/details";
|
||||||
|
import { PlanManagement } from "@/management/edition/ipp/list";
|
||||||
|
|
||||||
export const managementRoutes: Route[] = ([
|
export const managementRoutes: Route[] = ([
|
||||||
{ name: "index", path: "/", content: ManagementIndex, exact: true },
|
{ 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_manage", path: "/editions/:edition", content: EditionManagement, tags: ["edition"], exact: true },
|
||||||
{ name: "edition_internship", path: "/editions/:edition/internships/:internship", content: InternshipDetails, tags: ["edition"] },
|
{ 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_internships", path: "/editions/:edition/internships", content: InternshipManagement, tags: ["edition"] },
|
||||||
|
{ name: "edition_ipp_index", path: "/editions/:edition/ipp", content: PlanManagement, tags: ["edition"] },
|
||||||
{ name: "editions", path: "/editions", content: EditionsManagement },
|
{ name: "editions", path: "/editions", content: EditionsManagement },
|
||||||
|
|
||||||
{ name: "types", path: "/types", content: InternshipTypeManagement },
|
{ name: "types", path: "/types", content: InternshipTypeManagement },
|
||||||
|
@ -51,20 +51,6 @@ export const InternshipProposalFormPage = () => {
|
|||||||
export const InternshipProposalPreviewPage = () => {
|
export const InternshipProposalPreviewPage = () => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const proposal = useSelector<AppState, Internship | null>(state => state.proposal.proposal && internshipSerializationTransformer.reverseTransform(state.proposal.proposal));
|
const proposal = useSelector<AppState, Internship | null>(state => state.proposal.proposal && internshipSerializationTransformer.reverseTransform(state.proposal.proposal));
|
||||||
|
|
||||||
const dispatch = useDispatch();
|
|
||||||
const history = useHistory();
|
|
||||||
|
|
||||||
const handleAccept = (comment?: string) => {
|
|
||||||
dispatch({ type: InternshipProposalActions.Approve, comment: comment || null });
|
|
||||||
history.push(route("home"));
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleDiscard = (comment: string) => {
|
|
||||||
dispatch({ type: InternshipProposalActions.Decline, comment: comment });
|
|
||||||
history.push(route("home"));
|
|
||||||
}
|
|
||||||
|
|
||||||
const classes = useVerticalSpacing(3);
|
const classes = useVerticalSpacing(3);
|
||||||
|
|
||||||
return <Page title={ t("") }>
|
return <Page title={ t("") }>
|
||||||
@ -80,8 +66,6 @@ export const InternshipProposalPreviewPage = () => {
|
|||||||
{ proposal && <ProposalPreview proposal={ proposal } /> }
|
{ proposal && <ProposalPreview proposal={ proposal } /> }
|
||||||
|
|
||||||
<Actions>
|
<Actions>
|
||||||
<AcceptanceActions onAccept={ handleAccept } onDiscard={ handleDiscard } label="internship" />
|
|
||||||
|
|
||||||
<Button component={ RouterLink } to={ route("home") }>
|
<Button component={ RouterLink } to={ route("home") }>
|
||||||
{ t('go-back') }
|
{ t('go-back') }
|
||||||
</Button>
|
</Button>
|
||||||
|
@ -13,7 +13,6 @@ import { PlanStep } from "@/pages/steps/plan";
|
|||||||
import { InsuranceState } from "@/state/reducer/insurance";
|
import { InsuranceState } from "@/state/reducer/insurance";
|
||||||
import { InsuranceStep } from "@/pages/steps/insurance";
|
import { InsuranceStep } from "@/pages/steps/insurance";
|
||||||
import { StudentStep } from "@/pages/steps/student";
|
import { StudentStep } from "@/pages/steps/student";
|
||||||
import { useCurrentEdition, useDeadlines } from "@/hooks";
|
|
||||||
import api from "@/api";
|
import api from "@/api";
|
||||||
import { AppDispatch, InternshipPlanActions, InternshipProposalActions, useDispatch } from "@/state/actions";
|
import { AppDispatch, InternshipPlanActions, InternshipProposalActions, useDispatch } from "@/state/actions";
|
||||||
import { internshipRegistrationDtoTransformer } from "@/api/dto/internship-registration";
|
import { internshipRegistrationDtoTransformer } from "@/api/dto/internship-registration";
|
||||||
@ -26,6 +25,7 @@ export const updateInternshipInfo = async (dispatch: AppDispatch) => {
|
|||||||
dispatch({
|
dispatch({
|
||||||
type: InternshipProposalActions.Receive,
|
type: InternshipProposalActions.Receive,
|
||||||
state: internship.internshipRegistration.state,
|
state: internship.internshipRegistration.state,
|
||||||
|
comment: internship.internshipRegistration.changeStateComment,
|
||||||
internship: internshipRegistrationDtoTransformer.transform(internship.internshipRegistration),
|
internship: internshipRegistrationDtoTransformer.transform(internship.internshipRegistration),
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -48,7 +48,6 @@ const PlanActions = () => {
|
|||||||
switch (status) {
|
switch (status) {
|
||||||
case "awaiting":
|
case "awaiting":
|
||||||
return <Actions>
|
return <Actions>
|
||||||
<AcceptanceActions onAccept={ handleAccept } onDiscard={ handleDiscard } label="plan" />
|
|
||||||
</Actions>
|
</Actions>
|
||||||
case "accepted":
|
case "accepted":
|
||||||
return <Actions>
|
return <Actions>
|
||||||
|
@ -60,7 +60,7 @@ export const ProposalComment = (props: HTMLProps<HTMLDivElement>) => {
|
|||||||
|
|
||||||
return comment ? <Alert severity={ declined ? "error" : "warning" } { ...props as any }>
|
return comment ? <Alert severity={ declined ? "error" : "warning" } { ...props as any }>
|
||||||
<AlertTitle>{ t('comments') }</AlertTitle>
|
<AlertTitle>{ t('comments') }</AlertTitle>
|
||||||
{ comment }
|
<div dangerouslySetInnerHTML={{ __html: comment }}/>
|
||||||
</Alert> : null
|
</Alert> : null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -27,7 +27,8 @@ export interface ReceiveProposalDeclineAction extends ReceiveSubmissionDeclineAc
|
|||||||
|
|
||||||
export interface ReceiveProposalUpdateAction extends ReceiveSubmissionUpdateAction<InternshipProposalActions.Receive> {
|
export interface ReceiveProposalUpdateAction extends ReceiveSubmissionUpdateAction<InternshipProposalActions.Receive> {
|
||||||
internship: Internship;
|
internship: Internship;
|
||||||
state: SubmissionState,
|
state: SubmissionState;
|
||||||
|
comment?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SaveProposalAction extends SaveSubmissionAction<InternshipProposalActions.Save> {
|
export interface SaveProposalAction extends SaveSubmissionAction<InternshipProposalActions.Save> {
|
||||||
|
@ -58,6 +58,7 @@ const internshipProposalReducer = (state: InternshipProposalState = defaultInter
|
|||||||
ApiSubmissionState.Submitted
|
ApiSubmissionState.Submitted
|
||||||
].includes(action.state),
|
].includes(action.state),
|
||||||
proposal: internshipSerializationTransformer.transform(action.internship),
|
proposal: internshipSerializationTransformer.transform(action.internship),
|
||||||
|
comment: action.comment || "",
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
return state;
|
return state;
|
||||||
|
@ -24,6 +24,8 @@ internship:
|
|||||||
edition:
|
edition:
|
||||||
internships:
|
internships:
|
||||||
title: Zgłoszenia praktyk
|
title: Zgłoszenia praktyk
|
||||||
|
ipp:
|
||||||
|
title: Indywidualne Plany Praktyk
|
||||||
index:
|
index:
|
||||||
title: "Edycje praktyk"
|
title: "Edycje praktyk"
|
||||||
field:
|
field:
|
||||||
|
@ -111,6 +111,10 @@ forms:
|
|||||||
fields:
|
fields:
|
||||||
key: Klucz dostępu do edycji
|
key: Klucz dostępu do edycji
|
||||||
|
|
||||||
|
report:
|
||||||
|
instructions: >
|
||||||
|
Wypełnij wszystkie pola formularza w celu sfinalizowania praktyki.
|
||||||
|
|
||||||
student:
|
student:
|
||||||
name: imię
|
name: imię
|
||||||
surname: mazwisko
|
surname: mazwisko
|
||||||
@ -125,6 +129,7 @@ submission:
|
|||||||
accepted: "zaakceptowano"
|
accepted: "zaakceptowano"
|
||||||
declined: "do poprawy"
|
declined: "do poprawy"
|
||||||
draft: "wersja robocza"
|
draft: "wersja robocza"
|
||||||
|
empty: "brak zgłoszenia"
|
||||||
|
|
||||||
internship:
|
internship:
|
||||||
validation:
|
validation:
|
||||||
@ -218,6 +223,18 @@ steps:
|
|||||||
download: Twój indywidualny program praktyki
|
download: Twój indywidualny program praktyki
|
||||||
report:
|
report:
|
||||||
header: "Raport z praktyki"
|
header: "Raport z praktyki"
|
||||||
|
info:
|
||||||
|
draft: >
|
||||||
|
Po ukończeniu praktyki należy wypełnić z niej raport oraz przesłać ocenę praktyki przygotowaną przez Twojego zakładowego opiekuna praktyki.
|
||||||
|
awaiting: >
|
||||||
|
Twój raport musi zostać zweryfikowany i zatwierdzony. Po weryfikacji zostaniesz poinformowany o
|
||||||
|
akceptacji bądź konieczności wprowadzenia zmian.
|
||||||
|
accepted: >
|
||||||
|
Twój raport został zweryfikowany i zaakceptowany.
|
||||||
|
declined: >
|
||||||
|
Twój raport został zweryfikowany i odrzucony. Popraw zgłoszone uwagi i wyślij raport ponownie. W razie
|
||||||
|
pytań możesz również skontaktować się z pełnomocnikiem ds. praktyk Twojego kierunku.
|
||||||
|
submit: Uzupełnij raport
|
||||||
grade:
|
grade:
|
||||||
header: "Ocena z praktyki"
|
header: "Ocena z praktyki"
|
||||||
insurance:
|
insurance:
|
||||||
|
Loading…
Reference in New Issue
Block a user