Refactor submission state management so it is reusable
This commit is contained in:
parent
a3475c5f30
commit
886153afb5
@ -67,6 +67,10 @@ export interface Internship extends Identifiable {
|
||||
office: BranchOffice;
|
||||
}
|
||||
|
||||
export interface Plan extends Identifiable {
|
||||
|
||||
}
|
||||
|
||||
export interface Mentor {
|
||||
name: string;
|
||||
surname: string;
|
||||
|
@ -15,10 +15,11 @@ import { InternshipProposalActions, useDispatch } from "@/state/actions";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useSelector } from "react-redux";
|
||||
import { AppState } from "@/state/reducer";
|
||||
import { useHistory } from "react-router-dom";
|
||||
import { Link as RouterLink, useHistory } from "react-router-dom";
|
||||
import { route } from "@/routing";
|
||||
import { useProxyState } from "@/hooks";
|
||||
import { getInternshipProposal } from "@/state/reducer/proposal";
|
||||
import { Actions } from "@/components";
|
||||
|
||||
export type InternshipFormProps = {}
|
||||
|
||||
@ -161,7 +162,13 @@ export const InternshipForm: React.FunctionComponent<InternshipFormProps> = prop
|
||||
<InternshipDurationForm internship={ internship } onChange={ setInternship }/>
|
||||
<Typography variant="h3" className="section-header">Miejsce odbywania praktyki</Typography>
|
||||
<CompanyForm internship={ internship } onChange={ setInternship }/>
|
||||
<Button variant="contained" color="primary" onClick={ handleSubmit }>{ t("confirm") }</Button>
|
||||
<Actions>
|
||||
<Button variant="contained" color="primary" onClick={ handleSubmit }>{ t("confirm") }</Button>
|
||||
|
||||
<Button component={ RouterLink } to={ route("home") }>
|
||||
{ t('go-back') }
|
||||
</Button>
|
||||
</Actions>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
@ -10,13 +10,14 @@ import { getMissingStudentData, Student } from "@/data";
|
||||
import { Deadlines, Edition, getEditionDeadlines } from "@/data/edition";
|
||||
import { Description as DescriptionIcon } from "@material-ui/icons"
|
||||
import { Actions, Step } from "@/components";
|
||||
import { getInternshipProposalStatus, InternshipProposalState, InternshipProposalStatus } from "@/state/reducer/proposal";
|
||||
import { InternshipProposalState } from "@/state/reducer/proposal";
|
||||
import { createStyles, makeStyles } from "@material-ui/core/styles";
|
||||
import { ClipboardEditOutline, CommentQuestion, FileFind } from "mdi-material-ui";
|
||||
import { Alert, AlertTitle } from "@material-ui/lab";
|
||||
import { getSubmissionStatus, SubmissionStatus } from "@/state/reducer/submission";
|
||||
|
||||
|
||||
const getColorByStatus = (status: InternshipProposalStatus, theme: Theme) => {
|
||||
const getColorByStatus = (status: SubmissionStatus, theme: Theme) => {
|
||||
switch (status) {
|
||||
case "awaiting":
|
||||
return theme.palette.info.dark;
|
||||
@ -45,7 +46,7 @@ const useStatusStyles = makeStyles((theme: Theme) => {
|
||||
})
|
||||
|
||||
const ProposalStatus = () => {
|
||||
const status = useSelector<AppState, InternshipProposalStatus>(state => getInternshipProposalStatus(state.proposal))
|
||||
const status = useSelector<AppState, SubmissionStatus>(state => getSubmissionStatus(state.proposal))
|
||||
const classes = useStatusStyles({ status });
|
||||
|
||||
const { t } = useTranslation();
|
||||
@ -54,7 +55,7 @@ const ProposalStatus = () => {
|
||||
}
|
||||
|
||||
const ProposalActions = () => {
|
||||
const status = useSelector<AppState, InternshipProposalStatus>(state => getInternshipProposalStatus(state.proposal));
|
||||
const status = useSelector<AppState, SubmissionStatus>(state => getSubmissionStatus(state.proposal));
|
||||
const { t } = useTranslation();
|
||||
|
||||
const ReviewAction = (props: ButtonProps) =>
|
||||
@ -96,17 +97,17 @@ export const ProposalComment = (props: HTMLProps<HTMLDivElement>) => {
|
||||
const { comment, declined } = useSelector<AppState, InternshipProposalState>(state => state.proposal);
|
||||
const { t } = useTranslation();
|
||||
|
||||
return <Alert severity={declined ? "error" : "warning"} {...props as any}>
|
||||
return comment ? <Alert severity={declined ? "error" : "warning"} {...props as any}>
|
||||
<AlertTitle>{ t('comments') }</AlertTitle>
|
||||
{ comment }
|
||||
</Alert>
|
||||
</Alert> : null
|
||||
}
|
||||
|
||||
const ProposalStep = (props: StepProps) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const { sent, comment, declined } = useSelector<AppState, InternshipProposalState>(state => state.proposal);
|
||||
const status = useSelector<AppState, InternshipProposalStatus>(state => getInternshipProposalStatus(state.proposal));
|
||||
const status = useSelector<AppState, SubmissionStatus>(state => getSubmissionStatus(state.proposal));
|
||||
const deadlines = useSelector<AppState, Deadlines>(state => getEditionDeadlines(state.edition as Edition)); // edition cannot be null at this point
|
||||
|
||||
return <Step { ...props }
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { SerializationTransformer } from "@/serialization/types";
|
||||
import moment, { Moment } from "moment";
|
||||
|
||||
export const momentSerializationTransformer: SerializationTransformer<Moment, string> = {
|
||||
transform: (subject: Moment) => subject.toISOString(),
|
||||
reverseTransform: (subject: string) => moment(subject),
|
||||
export const momentSerializationTransformer: SerializationTransformer<Moment | null, string> = {
|
||||
transform: (subject: Moment) => subject && subject.toISOString(),
|
||||
reverseTransform: (subject: string) => subject ? moment(subject) : null,
|
||||
}
|
||||
|
@ -1,5 +1,11 @@
|
||||
import { Action } from "@/state/actions/base";
|
||||
import { Internship } from "@/data";
|
||||
import {
|
||||
ReceiveSubmissionApproveAction,
|
||||
ReceiveSubmissionDeclineAction,
|
||||
ReceiveSubmissionUpdateAction,
|
||||
SaveSubmissionAction,
|
||||
SendSubmissionAction
|
||||
} from "@/state/actions/submission";
|
||||
|
||||
export enum InternshipProposalActions {
|
||||
Send = "SEND_PROPOSAL",
|
||||
@ -9,23 +15,23 @@ export enum InternshipProposalActions {
|
||||
Receive = "RECEIVE_PROPOSAL_STATE",
|
||||
}
|
||||
|
||||
export interface SendProposalAction extends Action<InternshipProposalActions.Send> {
|
||||
export interface SendProposalAction extends SendSubmissionAction<InternshipProposalActions.Send> {
|
||||
internship: Internship;
|
||||
}
|
||||
|
||||
export interface ReceiveProposalApproveAction extends Action<InternshipProposalActions.Approve> {
|
||||
export interface ReceiveProposalApproveAction extends ReceiveSubmissionApproveAction<InternshipProposalActions.Approve> {
|
||||
comment: string | null;
|
||||
}
|
||||
|
||||
export interface ReceiveProposalDeclineAction extends Action<InternshipProposalActions.Decline> {
|
||||
export interface ReceiveProposalDeclineAction extends ReceiveSubmissionDeclineAction<InternshipProposalActions.Decline> {
|
||||
comment: string;
|
||||
}
|
||||
|
||||
export interface ReceiveProposalUpdateAction extends Action<InternshipProposalActions.Receive> {
|
||||
export interface ReceiveProposalUpdateAction extends ReceiveSubmissionUpdateAction<InternshipProposalActions.Receive> {
|
||||
|
||||
}
|
||||
|
||||
export interface SaveProposalAction extends Action<InternshipProposalActions.Save> {
|
||||
export interface SaveProposalAction extends SaveSubmissionAction<InternshipProposalActions.Save> {
|
||||
internship: Internship;
|
||||
}
|
||||
|
||||
|
26
src/state/actions/submission.ts
Normal file
26
src/state/actions/submission.ts
Normal file
@ -0,0 +1,26 @@
|
||||
import { Action } from "@/state/actions/base";
|
||||
|
||||
export enum SubmissionAction {
|
||||
Send = "SEND",
|
||||
Save = "SAVE",
|
||||
Approve = "RECEIVE_APPROVE",
|
||||
Decline = "RECEIVE_DECLINE",
|
||||
Receive = "RECEIVE_STATE",
|
||||
}
|
||||
|
||||
export interface SendSubmissionAction<T extends string> extends Action<T> {
|
||||
}
|
||||
|
||||
export interface ReceiveSubmissionApproveAction<T extends string> extends Action<T> {
|
||||
comment: string | null;
|
||||
}
|
||||
|
||||
export interface ReceiveSubmissionDeclineAction<T extends string> extends Action<T> {
|
||||
comment: string;
|
||||
}
|
||||
|
||||
export interface ReceiveSubmissionUpdateAction<T extends string> extends Action<T> {
|
||||
}
|
||||
|
||||
export interface SaveSubmissionAction<T extends string> extends Action<T> {
|
||||
}
|
@ -1,80 +1,47 @@
|
||||
import { DeanApproval } from "@/data/deanApproval";
|
||||
import { InternshipProposalAction, InternshipProposalActions } from "@/state/actions";
|
||||
import { Internship } from "@/data";
|
||||
import moment from "moment";
|
||||
import { Serializable } from "@/serialization/types";
|
||||
import { internshipSerializationTransformer, momentSerializationTransformer } from "@/serialization";
|
||||
import { internshipSerializationTransformer } from "@/serialization";
|
||||
import {
|
||||
createSubmissionReducer,
|
||||
defaultDeanApprovalsState,
|
||||
defaultSubmissionState,
|
||||
MayRequireDeanApproval,
|
||||
SubmissionState
|
||||
} from "@/state/reducer/submission";
|
||||
import { Reducer } from "react";
|
||||
import { SubmissionAction } from "@/state/actions/submission";
|
||||
|
||||
export type InternshipProposalStatus = "draft" | "awaiting" | "accepted" | "declined";
|
||||
|
||||
export type InternshipProposalState = {
|
||||
accepted: boolean;
|
||||
sent: boolean;
|
||||
sentOn: string | null;
|
||||
declined: boolean;
|
||||
requiredDeanApprovals: DeanApproval[];
|
||||
export type InternshipProposalState = SubmissionState & MayRequireDeanApproval & {
|
||||
proposal: Serializable<Internship> | null;
|
||||
comment: string | null;
|
||||
}
|
||||
|
||||
const defaultInternshipProposalState: InternshipProposalState = {
|
||||
accepted: false,
|
||||
declined: false,
|
||||
sentOn: null,
|
||||
...defaultDeanApprovalsState,
|
||||
...defaultSubmissionState,
|
||||
proposal: null,
|
||||
requiredDeanApprovals: [],
|
||||
sent: false,
|
||||
comment: null
|
||||
}
|
||||
|
||||
export const getInternshipProposalStatus = ({ accepted, declined, sent }: InternshipProposalState): InternshipProposalStatus => {
|
||||
switch (true) {
|
||||
case !sent:
|
||||
return "draft";
|
||||
case sent && accepted:
|
||||
return "accepted"
|
||||
case sent && declined:
|
||||
return "declined"
|
||||
case sent && (!accepted && !declined):
|
||||
return "awaiting"
|
||||
default:
|
||||
throw new Error("Invalid proposal state " + JSON.stringify({ accepted, declined, sent }));
|
||||
}
|
||||
}
|
||||
|
||||
export const getInternshipProposal = ({ proposal }: InternshipProposalState): Internship | null =>
|
||||
proposal && internshipSerializationTransformer.reverseTransform(proposal);
|
||||
|
||||
const internshipProposalSubmissionReducer: Reducer<InternshipProposalState, InternshipProposalAction> = createSubmissionReducer({
|
||||
[InternshipProposalActions.Approve]: SubmissionAction.Approve,
|
||||
[InternshipProposalActions.Decline]: SubmissionAction.Decline,
|
||||
[InternshipProposalActions.Receive]: SubmissionAction.Receive,
|
||||
[InternshipProposalActions.Save]: SubmissionAction.Save,
|
||||
[InternshipProposalActions.Send]: SubmissionAction.Send,
|
||||
})
|
||||
|
||||
const internshipProposalReducer = (state: InternshipProposalState = defaultInternshipProposalState, action: InternshipProposalAction): InternshipProposalState => {
|
||||
state = internshipProposalSubmissionReducer(state, action);
|
||||
|
||||
switch (action.type) {
|
||||
case InternshipProposalActions.Approve:
|
||||
return {
|
||||
...state,
|
||||
accepted: true,
|
||||
declined: false,
|
||||
comment: action.comment,
|
||||
}
|
||||
case InternshipProposalActions.Decline:
|
||||
return {
|
||||
...state,
|
||||
accepted: false,
|
||||
declined: true,
|
||||
comment: action.comment
|
||||
}
|
||||
case InternshipProposalActions.Save:
|
||||
return {
|
||||
...state,
|
||||
proposal: internshipSerializationTransformer.transform(action.internship),
|
||||
}
|
||||
case InternshipProposalActions.Send:
|
||||
return {
|
||||
...state,
|
||||
proposal: internshipSerializationTransformer.transform(action.internship),
|
||||
sent: true,
|
||||
sentOn: momentSerializationTransformer.transform(moment()),
|
||||
accepted: false,
|
||||
declined: false,
|
||||
comment: null,
|
||||
}
|
||||
default:
|
||||
return state;
|
||||
|
80
src/state/reducer/submission.ts
Normal file
80
src/state/reducer/submission.ts
Normal file
@ -0,0 +1,80 @@
|
||||
import { DeanApproval } from "@/data/deanApproval";
|
||||
import { Action } from "@/state/actions";
|
||||
import { momentSerializationTransformer } from "@/serialization";
|
||||
import moment from "moment";
|
||||
import { ReceiveSubmissionApproveAction, ReceiveSubmissionDeclineAction, SubmissionAction } from "@/state/actions/submission";
|
||||
|
||||
export type SubmissionStatus = "draft" | "awaiting" | "accepted" | "declined";
|
||||
|
||||
export type SubmissionState = {
|
||||
accepted: boolean;
|
||||
sent: boolean;
|
||||
sentOn: string | null;
|
||||
declined: boolean;
|
||||
comment: string | null;
|
||||
}
|
||||
|
||||
export type MayRequireDeanApproval = {
|
||||
requiredDeanApprovals: DeanApproval[],
|
||||
}
|
||||
|
||||
export const defaultSubmissionState: SubmissionState = {
|
||||
accepted: false,
|
||||
sent: false,
|
||||
sentOn: null,
|
||||
declined: false,
|
||||
comment: null,
|
||||
}
|
||||
|
||||
export const defaultDeanApprovalsState: MayRequireDeanApproval = {
|
||||
requiredDeanApprovals: [],
|
||||
}
|
||||
|
||||
export const getSubmissionStatus = ({ accepted, declined, sent }: SubmissionState): SubmissionStatus => {
|
||||
switch (true) {
|
||||
case !sent:
|
||||
return "draft";
|
||||
case sent && accepted:
|
||||
return "accepted"
|
||||
case sent && declined:
|
||||
return "declined"
|
||||
case sent && (!accepted && !declined):
|
||||
return "awaiting"
|
||||
default:
|
||||
throw new Error("Invalid submission state " + JSON.stringify({ accepted, declined, sent }));
|
||||
}
|
||||
}
|
||||
|
||||
export function createSubmissionReducer<TState, TActionType, TAction extends Action>(mapping: { [TAction in keyof TActionType]: SubmissionAction }) {
|
||||
return (state: TState, action: TAction) => {
|
||||
const mappedAction = mapping[action.type as keyof TActionType];
|
||||
|
||||
switch (mappedAction) {
|
||||
case SubmissionAction.Approve:
|
||||
return {
|
||||
...state,
|
||||
accepted: true,
|
||||
declined: false,
|
||||
comment: (action as ReceiveSubmissionApproveAction<any>).comment,
|
||||
}
|
||||
case SubmissionAction.Decline:
|
||||
return {
|
||||
...state,
|
||||
accepted: false,
|
||||
declined: true,
|
||||
comment: (action as ReceiveSubmissionDeclineAction<any>).comment,
|
||||
}
|
||||
case SubmissionAction.Send:
|
||||
return {
|
||||
...state,
|
||||
sent: true,
|
||||
sentOn: momentSerializationTransformer.transform(moment()),
|
||||
accepted: false,
|
||||
declined: false,
|
||||
comment: null,
|
||||
}
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user