Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Incident Form Redux Thunk #516

Draft
wants to merge 2 commits into
base: develop
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -186,7 +186,11 @@ export const CheckedOutTables = () =>
<TableCell align="right">
<Link
to={`/incident-form?data=${JSON.stringify(
row
{
...row,
checkedOutOrderId:
checkedOutOrder.id,
}
)}`}
>
<Button
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ import Header from "components/general/Header/Header";
import ArrowLeft from "../../assets/images/icons/arrow-left-solid.svg";
import { displaySnackbar } from "slices/ui/uiSlice";
import { useDispatch } from "react-redux";
import { OrderItemTableRow } from "api/types";
import { IncidentRequestBody, createIncident } from "slices/incident/incidentSlice";

export const INCIDENT_ERROR_MSG = {
state: "Please select an option",
Expand Down Expand Up @@ -95,6 +97,11 @@ const IncidentForm = () => {
// get info from url
const searchParams = new URLSearchParams(location.search);

let checkedOutOrder:
| (OrderItemTableRow & { checkedOutOrderId: number })
| undefined = undefined;
let hardwareQuantity: number; // quantity used for dropdown

useFirstRenderEffect(() => {
if (searchParams.toString() === "") {
// check if there are empty query params
Expand All @@ -110,47 +117,48 @@ const IncidentForm = () => {
}
});

let hardwareQuantity: number; // quantity used for dropdown
try {
const data = searchParams.get("data") ?? "";
const checkedOutOrder = JSON.parse(data); // parse string from url into an object
checkedOutOrder = JSON.parse(data) as OrderItemTableRow & {
checkedOutOrderId: number;
}; // parse string from url into an object
hardwareQuantity = checkedOutOrder?.quantityGranted; // set the hardware qty for dropdown
console.log(checkedOutOrder, hardwareQuantity);
} catch {
hardwareQuantity = 0; // set the qty to 0 if there is an error parsing
}

return (
<>
{searchParams.toString() === "" ? (
<></>
) : (
<IncidentFormRender hardwareQuantity={hardwareQuantity} />
)}
<IncidentFormRender
checkedOutOrder={checkedOutOrder}
hardwareQuantity={hardwareQuantity}
/>
</>
);
};

const IncidentFormRender = ({ hardwareQuantity }: { hardwareQuantity: number }) => {
const IncidentFormRender = ({
hardwareQuantity,
checkedOutOrder,
}: {
hardwareQuantity: number;
checkedOutOrder: (OrderItemTableRow & { checkedOutOrderId: number }) | undefined;
}) => {
const muiClasses = useStyles();

const dispatch = useDispatch();
const history = useHistory();

const handleSubmit = async (values: FormikValues, { resetForm }: any) => {
// TODO: submit the form
const incident: IncidentRequestBody = {
state: values.state,
time_occurred: new Date(values.when).toISOString(),
description: `${values.what}\n${values.where}`,
order_item: checkedOutOrder?.id ?? 0, // order item is id?
};

resetForm();
// display success snackbar
dispatch(
displaySnackbar({
message: `Successfully submitted incident form!`,
options: {
variant: "success",
},
})
);
// navigate back to previous page
history.goBack();
dispatch(createIncident(incident));
};

return (
Expand Down Expand Up @@ -183,7 +191,14 @@ const IncidentFormRender = ({ hardwareQuantity }: { hardwareQuantity: number })
type: "radio",
id: "state",
name: "state",
options: ["broken", "lost", "other"],
options: [
"Heavily Used",
"Broken",
"Missing",
"Minor Repair Required",
"Major Repair Required",
"Not Sure If Works",
],
helperText: errors?.state,
testId: "radio-state",
},
Expand Down Expand Up @@ -212,11 +227,11 @@ const IncidentFormRender = ({ hardwareQuantity }: { hardwareQuantity: number })
helperText: errors?.what,
},
{
type: "text",
type: "time",
id: "when",
name: "when",
label: "",
description: "",
description: "When did this occur?",
placeholder: "When did this occur?",
value: values.when,
error: !!errors?.when,
Expand Down Expand Up @@ -421,11 +436,41 @@ const IncidentFormRender = ({ hardwareQuantity }: { hardwareQuantity: number })
handleChange
}
variant="outlined"
type="text"
type={
item.name ===
"when"
? "time"
: "text"
}
multiline
fullWidth
/>
</div>
) : item.type ===
"time" ? (
<>
<TextField
id={item.id}
name={
item.name
}
type="datetime-local"
value={
item.value
}
onChange={
handleChange
}
error={
item.error
}
helperText={
item.helperText
}
variant="outlined"
fullWidth
/>
</>
) : (
<></>
)}
Expand Down
132 changes: 132 additions & 0 deletions hackathon_site/dashboard/frontend/src/slices/incident/incidentSlice.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
import {
createAsyncThunk,
createEntityAdapter,
createSelector,
createSlice,
} from "@reduxjs/toolkit";
import { post } from "api/api";
import { Incident } from "api/types";
import { push } from "connected-react-router";
import { AppDispatch, RootState } from "slices/store";
import { displaySnackbar } from "slices/ui/uiSlice";
import { IncidentState } from "api/types";

export interface IncidentRequestBody {
state: IncidentState;
time_occurred: string; //($date-time)
description: string;
order_item: number;
}

export interface IncidentResponse {
id: number;
state: string;
time_occurred: string;
description: string;
order_item: number;
team_id: number;
created_at: string;
updated_at: string;
}

export interface IncidentInitialState {
isLoading: boolean;
error: null | string;
incident: null | IncidentResponse;
}

const extraState: IncidentInitialState = {
isLoading: false,
error: null,
incident: null,
};

const incidentAdapter = createEntityAdapter<Incident>();

export const incidentReducerName = "incident";
export const initialState: IncidentInitialState =
incidentAdapter.getInitialState(extraState);

interface RejectValue {
status: number;
message: any;
}

export const createIncident = createAsyncThunk<
IncidentResponse,
IncidentRequestBody,
{ state: RootState; rejectValue: RejectValue; dispatch: AppDispatch }
>(
`${incidentReducerName}/createIncident`,
async (incident, { dispatch, rejectWithValue }) => {
try {
const response = await post<IncidentResponse>(
"/api/hardware/incidents/",
incident
);

dispatch(push("/"));
dispatch(
displaySnackbar({
message: `Successfully submitted incident form!`,
options: { variant: "success" },
})
);
return response.data;
} catch (e: any) {
dispatch(
displaySnackbar({
message: `An error has occurred! We couldn't submit your incident!`,
options: { variant: "error" },
})
);
return rejectWithValue({
status: e.response.status,
message: e.response.data,
});
}
}
);

const incidentSlice = createSlice({
name: incidentReducerName,
initialState,
reducers: {}, // used when we want to change state (w/ network req.)
extraReducers: (builder) => {
// used specifically for thunks
// pending, fulfilled, rejects are built in states from redux
builder.addCase(createIncident.pending, (state) => {
state.isLoading = true;
state.error = null;
});
builder.addCase(createIncident.fulfilled, (state, { payload }) => {
state.isLoading = false;
state.error = null;
state.incident = payload;
// need to use the payload and put it into incident
});
builder.addCase(createIncident.rejected, (state, { payload }) => {
state.isLoading = false;
state.error =
payload === undefined
? "An internal server error has occurred, please inform hackathon organizers if this continues to happen."
: payload.message;
});
},
});

export const { reducer, actions } = incidentSlice;
export default reducer;

// Selectors
export const incidentSliceSelector = (state: RootState) => state[incidentReducerName];

export const isLoadingSelector = createSelector(
[incidentSliceSelector],
(incidentSlice) => incidentSlice.isLoading
);

export const errorSelector = createSelector(
[incidentSliceSelector],
(incidentSlice) => incidentSlice.error
);
2 changes: 2 additions & 0 deletions hackathon_site/dashboard/frontend/src/slices/store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import teamAdminReducer, { teamAdminReducerName } from "slices/event/teamAdminSl
import teamDetailReducer, { teamDetailReducerName } from "slices/event/teamDetailSlice";
import teamOrderReducer, { teamOrderReducerName } from "slices/order/teamOrderSlice";
import adminOrderReducer, { adminOrderReducerName } from "slices/order/adminOrderSlice";
import incidentReducer, { incidentReducerName } from "slices/incident/incidentSlice";

export const history = createBrowserHistory();

Expand All @@ -40,6 +41,7 @@ const reducers = {
[uiReducerName]: uiReducer,
[teamAdminReducerName]: teamAdminReducer,
[adminOrderReducerName]: adminOrderReducer,
[incidentReducerName]: incidentReducer,
router: connectRouter(history),
};

Expand Down
Loading