More stuff

This commit is contained in:
Kacper Donat 2021-01-10 00:30:24 +01:00
parent e31d89b688
commit dffc279b4a
4 changed files with 125 additions and 29 deletions

View File

@ -1,5 +1,5 @@
import { Address, Company, Identifiable, Internship, Mentor, Office } from "@/data";
import { momentSerializationTransformer, OneWayTransformer } from "@/serialization";
import { Address, Company, Identifiable, Internship, Mentor, Office, Student } from "@/data";
import { momentSerializationTransformer, OneWayTransformer, Transformer } from "@/serialization";
import { Nullable } from "@/helpers";
import { MentorDTO, mentorDtoTransformer } from "@/api/dto/mentor";
import { InternshipTypeDTO, internshipTypeDtoTransformer } from "@/api/dto/type";
@ -7,6 +7,8 @@ import { Moment } from "moment-timezone";
import { sampleStudent } from "@/provider/dummy";
import { UploadType } from "@/api/upload";
import { ProgramEntryDTO, programEntryDtoTransformer } from "@/api/dto/edition";
import { StudentDTO } from "@/api/dto/student";
import { SubmissionStatus } from "@/state/reducer/submission";
export enum SubmissionState {
Draft = "Draft",
@ -16,6 +18,35 @@ export enum SubmissionState {
Archival = "Archival",
}
export const submissionStateDtoTransformer: Transformer<SubmissionState, SubmissionStatus> = {
reverseTransform(subject: SubmissionStatus, context: undefined): SubmissionState {
switch (subject) {
case "draft":
return SubmissionState.Draft;
case "awaiting":
return SubmissionState.Submitted;
case "accepted":
return SubmissionState.Accepted;
case "declined":
return SubmissionState.Rejected;
}
},
transform(subject: SubmissionState, context: undefined): SubmissionStatus {
switch (subject) {
case SubmissionState.Draft:
return "draft";
case SubmissionState.Submitted:
return "awaiting";
case SubmissionState.Accepted:
return "accepted";
case SubmissionState.Rejected:
return "declined";
case SubmissionState.Archival:
return "declined";
}
}
}
export interface NewBranchOffice extends Address {
}
@ -50,6 +81,7 @@ export interface InternshipRegistrationDTO extends Identifiable {
branchAddress: Office,
declaredHours: number,
subjects: { subject: ProgramEntryDTO }[],
submissionDate: string,
}
export interface InternshipDocument extends Identifiable {
@ -60,9 +92,10 @@ export interface InternshipDocument extends Identifiable {
const reference = (subject: Identifiable | null): Identifiable | null => subject && { id: subject.id };
export interface InternshipInfoDTO {
export interface InternshipInfoDTO extends Identifiable {
internshipRegistration: InternshipRegistrationDTO;
documentation: InternshipDocument[],
student: StudentDTO,
}
export const internshipRegistrationUpdateTransformer: OneWayTransformer<Nullable<Internship>, Nullable<InternshipRegistrationUpdate>> = {

View File

@ -24,7 +24,7 @@ export const ProposalPreview = ({ proposal }: ProposalPreviewProps) => {
<StudentPreview student={ proposal.intern } />
</div>
<Section>
{ proposal.company && proposal.office && <Section>
<Label>{ t('internship.sections.place') }</Label>
<Typography className="proposal__primary">
{ proposal.company.name }
@ -36,12 +36,12 @@ export const ProposalPreview = ({ proposal }: ProposalPreviewProps) => {
<Label>{ t('internship.office') }</Label>
<Typography className="proposal__primary">{ t('internship.address.city', proposal.office.address) }</Typography>
<Typography className="proposal__secondary">{ t('internship.address.street', proposal.office.address) }</Typography>
</Section>
</Section> }
<Section>
{ proposal.type && <Section>
<Label>{ t('internship.sections.kind') }</Label>
<Typography className="proposal__primary">{ proposal.type.label.pl }</Typography>
</Section>
</Section> }
<Section>
<Label>{ t('internship.sections.program') }</Label>

View File

@ -1,12 +1,20 @@
import { Identifiable, Identifier, Internship } from "@/data";
import { sampleCompanies, sampleStudent } from "@/provider/dummy";
import moment, { Moment } from "moment-timezone";
import { OneOrMany } from "@/helpers";
import { SubmissionState, SubmissionStatus } from "@/state/reducer/submission";
import { encapsulate, Nullable, OneOrMany } from "@/helpers";
import { SubmissionStatus } from "@/state/reducer/submission";
import { axios } from "@/api";
import { prepare, query } from "@/routing";
import { InternshipInfoDTO, submissionStateDtoTransformer } from "@/api/dto/internship-registration";
import { Transformer } from "@/serialization";
import { mentorDtoTransformer } from "@/api/dto/mentor";
import { internshipTypeDtoTransformer } from "@/api/dto/type";
import { studentDtoTransfer } from "@/api/dto/student";
import { programEntryDtoTransformer } from "@/api/dto/edition";
export type InternshipSubmission = Internship & {
export type InternshipSubmission = Nullable<Internship> & {
state: SubmissionStatus,
changed: Moment
changed: Moment | null,
}
const sampleInternship: InternshipSubmission = {
@ -44,15 +52,55 @@ const sampleInternship: InternshipSubmission = {
changed: moment(),
}
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 internshipInfoDtoTransformer: Transformer<InternshipInfoDTO, InternshipSubmission> = {
transform(subject: InternshipInfoDTO, context?: never): InternshipSubmission {
return {
changed: moment(subject.internshipRegistration.submissionDate),
company: subject.internshipRegistration.company,
startDate: moment(subject.internshipRegistration.start),
endDate: moment(subject.internshipRegistration.end),
hours: subject.internshipRegistration.declaredHours,
id: subject.id,
intern: subject.student && studentDtoTransfer.transform(subject.student),
isAccepted: false,
lengthInWeeks: 0,
mentor: subject.internshipRegistration.mentor && mentorDtoTransformer.transform(subject.internshipRegistration.mentor),
office: subject.internshipRegistration.branchAddress,
program: (subject.internshipRegistration.subjects || []).map(subject => programEntryDtoTransformer.transform(subject.subject)),
state: submissionStateDtoTransformer.transform(subject.internshipRegistration.state),
type: subject.internshipRegistration.type && internshipTypeDtoTransformer.transform(subject.internshipRegistration.type),
};
},
reverseTransform(subject: InternshipSubmission, context: undefined): InternshipInfoDTO {
return {} as any;
},
}
export async function all(edition: Identifiable): Promise<InternshipSubmission[]> {
return [
sampleInternship,
]
const result = await axios.get<InternshipInfoDTO[]>(query(INTERNSHIP_MANAGEMENT_INDEX_ENDPOINT, { EditionId: edition.id || "" }));
return result.data.map(result => internshipInfoDtoTransformer.transform(result))
}
export async function get(id: Identifier): Promise<InternshipSubmission> {
return sampleInternship;
const result = await axios.get<InternshipInfoDTO>(prepare(INTERNSHIP_MANAGEMENT_ENDPOINT, { id }))
return internshipInfoDtoTransformer.transform(result.data);
}
export async function approve(internship: OneOrMany<Internship>, comment?: string): Promise<void> {}
export async function decline(internship: OneOrMany<Internship>, comment: string): Promise<void> {}
export async function accept(internship: OneOrMany<Internship>, comment?: string): Promise<void> {
const internships = encapsulate(internship)
await Promise.all(internships.map(internship => axios.put(prepare(INTERNSHIP_ACCEPT_ENDPOINT, { id: internship.id || ""}))))
}
export async function discard(internship: OneOrMany<Internship>, comment: string): Promise<void> {
const internships = encapsulate(internship)
await Promise.all(internships.map(internship => axios.put(prepare(INTERNSHIP_REJECT_ENDPOINT, { id: internship.id || ""}))))
}

View File

@ -1,6 +1,6 @@
import React, { useEffect, useState } from "react";
import React, { useCallback, useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
import { useAsyncState } from "@/hooks";
import { useAsync, useAsyncState } from "@/hooks";
import { useSpacing } from "@/styles";
import api from "@/management/api";
import { Box, Button, Container, IconButton, Typography } from "@material-ui/core";
@ -15,11 +15,12 @@ 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 { AcceptanceActions, AcceptSubmissionDialog, DiscardSubmissionDialog } from "@/components/acceptance-action";
import { AcceptSubmissionDialog, DiscardSubmissionDialog } from "@/components/acceptance-action";
import { ProposalPreview } from "@/components/proposalPreview";
import { InternshipSubmission } from "@/management/api/internship";
import { canAccept, canDiscard, StateLabel } from "@/management/edition/internship/common";
import { createPortal } from "react-dom";
import { Internship } from "@/data";
const title = "edition.internships.title";
@ -35,16 +36,19 @@ export const InternshipManagement = ({ edition }: EditionManagementProps) => {
useEffect(updateInternshipList, []);
const handleSubmissionAccept = api.internship.approve
const handleSubmissionDiscard = api.internship.decline
const AcceptAction = ({ internship }: { internship: InternshipSubmission }) => {
const [open, setOpen] = useState(false);
const handleSubmissionAccept = async (comment?: string) => {
setOpen(false);
await api.internship.accept(internship as Internship, comment);
updateInternshipList();
}
return <>
<IconButton onClick={ () => setOpen(true) }><StickerCheckOutline /></IconButton>
{ createPortal(
<AcceptSubmissionDialog onAccept={ comment => handleSubmissionAccept(internship, comment) } label="internship" open={ open } onClose={ () => setOpen(false) }/>,
<AcceptSubmissionDialog onAccept={ handleSubmissionAccept } label="internship" open={ open } onClose={ () => setOpen(false) }/>,
document.getElementById("modals") as Element,
) }
</>;
@ -53,19 +57,30 @@ export const InternshipManagement = ({ edition }: EditionManagementProps) => {
const DiscardAction = ({ internship }: { internship: InternshipSubmission }) => {
const [open, setOpen] = useState(false);
const handleSubmissionDiscard = async (comment: string) => {
setOpen(false);
await api.internship.discard(internship as Internship, comment);
updateInternshipList();
}
return <>
<IconButton onClick={ () => setOpen(true) }><StickerRemoveOutline /></IconButton>
{ createPortal(
<DiscardSubmissionDialog onDiscard={ comment => handleSubmissionDiscard(internship, comment) } label="internship" open={ open } onClose={ () => setOpen(false) }/>,
<DiscardSubmissionDialog onDiscard={ handleSubmissionDiscard } label="internship" open={ open } onClose={ () => setOpen(false) }/>,
document.getElementById("modals") as Element,
) }
</>;
}
const InternshipDetails = ({ summary }: { summary: InternshipSubmission }) => {
const internship = useAsync(useCallback(() => api.internship.get(summary.id || ""), [ summary.id ]))
return <Box m={ 3 }><Async async={ internship }>{ internship => <ProposalPreview proposal={ internship as Internship } /> }</Async> </Box>
}
const columns: Column<InternshipSubmission>[] = [
{
title: t("internship.column.student"),
render: internship => <>{internship.intern.name} {internship.intern.surname}</>,
render: internship => <>{internship.intern?.name} {internship.intern?.surname}</>,
},
{
title: t("internship.column.album"),
@ -77,7 +92,7 @@ export const InternshipManagement = ({ edition }: EditionManagementProps) => {
},
{
title: t("internship.column.changed"),
render: summary => summary.changed.format("yyyy-MM-DD HH:mm")
render: summary => summary.changed?.format("yyyy-MM-DD")
},
{
title: t("internship.column.status"),
@ -102,7 +117,7 @@ export const InternshipManagement = ({ edition }: EditionManagementProps) => {
<Button onClick={ updateInternshipList } startIcon={ <Refresh /> }>{ t("refresh") }</Button>
</Actions>
{ selected.length > 0 && <BulkActions>
<AcceptanceActions label="internship" onAccept={ comment => handleSubmissionAccept(selected, comment) } onDiscard={ comment => handleSubmissionDiscard(selected, comment) } />
</BulkActions> }
<Async async={ result } keepValue>{
internships => <MaterialTable
@ -111,7 +126,7 @@ export const InternshipManagement = ({ edition }: EditionManagementProps) => {
data={ internships }
onSelectionChange={ internships => setSelected(internships) }
options={ { selection: true, pageSize: 10 } }
detailPanel={ internship => <Box m={ 3 }><ProposalPreview proposal={ internship } /></Box> }
detailPanel={ summary => <InternshipDetails summary={ summary } /> }
/>
}</Async>
</Container>