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

71/manage production route #84

Merged
merged 22 commits into from
Apr 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
4f77116
fix: updated delete-fetch
malmen237 Apr 16, 2024
bb214bd
fix: changed form-container to form instead of div
malmen237 Apr 16, 2024
b977471
feat: added manage-button to landing-page
malmen237 Apr 16, 2024
68f03b3
feat: added manage-page with delete-production
malmen237 Apr 16, 2024
3555e4d
feat: added routing to manage-page
malmen237 Apr 16, 2024
e316f17
feat: added some styling
malmen237 Apr 16, 2024
cf5eaab
Update src/components/manage-productions/manage-productions.tsx
malmen237 Apr 18, 2024
e4b6de8
fix: removed unneccessary verification
malmen237 Apr 18, 2024
c4952ee
feat: added nav-button as a separate component, added styling
malmen237 Apr 18, 2024
b647097
fix: added disable to confirm-removal button and disabled function on…
malmen237 Apr 18, 2024
b93928d
fix: moved manage-prod-btn to productions-list and
malmen237 Apr 18, 2024
a6ae6ff
fix: moved manage-button to landing-page
malmen237 Apr 18, 2024
33d1d9d
feat: added arrow-back icon
malmen237 Apr 17, 2024
1796803
feat: added arrow-back to icons-file
malmen237 Apr 17, 2024
edb4ae3
feat: added a generic button that returns to previous page
malmen237 Apr 17, 2024
3b3a734
feat: added back-button to manage-productions
malmen237 Apr 17, 2024
02e1663
fix: changed navigate to root
malmen237 Apr 17, 2024
d4395b8
fix: update button to action-button
malmen237 Apr 18, 2024
21fce0e
fix: updated button-colors
malmen237 Apr 18, 2024
3776687
fix: renamed back-button to navigate-to-root-button
malmen237 Apr 19, 2024
1f8d855
feat: created a delete-production-hook
malmen237 Apr 19, 2024
2855852
fix: removed delete-logic from component and cleaned
malmen237 Apr 19, 2024
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
6 changes: 6 additions & 0 deletions src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { ErrorBanner } from "./components/error";
import { useFetchDevices } from "./use-fetch-devices.ts";
import { FlexContainer } from "./components/generic-components.ts";
import { DisplayWarning } from "./components/display-box.tsx";
import { ManageProductions } from "./components/manage-productions/manage-productions.tsx";

const DisplayBoxPositioningContainer = styled(FlexContainer)`
justify-content: center;
Expand Down Expand Up @@ -60,6 +61,11 @@ const App = () => {
element={<LandingPage />}
errorElement={<ErrorPage />}
/>
<Route
path="/manage-productions"
element={<ManageProductions />}
errorElement={<ErrorPage />}
/>
<Route
path="/production/:productionId/line/:lineId"
element={<ProductionLine />}
Expand Down
7 changes: 3 additions & 4 deletions src/api/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,10 +83,9 @@ export const API = {
handleFetchRequest<TFetchProductionResponse>(
fetch(`${API_URL}productions/${id}`, { method: "GET" })
),
// TODO apply handleFetchRequest
deleteProduction: (id: number) =>
fetch(`${API_URL}productions/${id}`, { method: "DELETE" }).then(
(response) => response.json()
deleteProduction: (id: number): Promise<string> =>
handleFetchRequest<string>(
fetch(`${API_URL}productions/${id}`, { method: "DELETE" })
),
listProductionLines: (id: number) =>
handleFetchRequest<TLine[]>(
Expand Down
4 changes: 4 additions & 0 deletions src/assets/icons/arrow_back.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions src/assets/icons/icon.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import styled from "@emotion/styled";
import MicMute from "./mic_off.svg";
import MicUnmute from "./mic_on.svg";
import Arrow from "./arrow_back.svg";
import RemoveSvg from "./clear.svg";

const Icon = styled.img`
Expand All @@ -13,4 +14,6 @@ export const MicMuted = () => <Icon src={MicMute} alt="off-microphone" />;

export const MicUnmuted = () => <Icon src={MicUnmute} alt="on-microphone" />;

export const BackArrow = () => <Icon src={Arrow} alt="arrow-left" />;

export const RemoveIcon = () => <Icon src={RemoveSvg} alt="cross" />;
2 changes: 1 addition & 1 deletion src/components/landing-page/form-elements.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import styled from "@emotion/styled";

export const FormContainer = styled.div``;
export const FormContainer = styled.form``;

export const FormInput = styled.input`
width: 100%;
Expand Down
24 changes: 24 additions & 0 deletions src/components/landing-page/manage-production-button.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { useNavigate } from "react-router-dom";
import styled from "@emotion/styled";
import { ActionButton } from "./form-elements";

const ButtonWrapper = styled.div`
display: flex;
justify-content: end;
padding: 2rem;
`;

export const ManageProductionButton = () => {
const navigate = useNavigate();

return (
<ButtonWrapper>
<ActionButton
type="button"
onClick={() => navigate("/manage-productions")}
>
Manage Productions
</ActionButton>
</ButtonWrapper>
);
};
2 changes: 2 additions & 0 deletions src/components/landing-page/productions-list.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { LoaderDots } from "../loader/loader.tsx";
import { useRefreshAnimation } from "./use-refresh-animation.ts";
import { DisplayContainerHeader } from "./display-container-header.tsx";
import { DisplayContainer } from "../generic-components.ts";
import { ManageProductionButton } from "./manage-production-button.tsx";
import { LocalError } from "../error.tsx";

const ProductionListContainer = styled.div`
Expand Down Expand Up @@ -115,6 +116,7 @@ export const ProductionsList = () => {
</ProductionItem>
))}
</ProductionListContainer>
{productions.length && <ManageProductionButton />}
</>
);
};
207 changes: 207 additions & 0 deletions src/components/manage-productions/manage-productions.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,207 @@
import { ErrorMessage } from "@hookform/error-message";
import { SubmitHandler, useForm, useWatch } from "react-hook-form";
import { useEffect, useState } from "react";
import styled from "@emotion/styled";
import { DisplayContainerHeader } from "../landing-page/display-container-header";
import {
ActionButton,
DecorativeLabel,
FormInput,
FormLabel,
StyledWarningMessage,
} from "../landing-page/form-elements";
import { Spinner } from "../loader/loader";
import { useFetchProduction } from "../landing-page/use-fetch-production";
import { darkText, errorColour } from "../../css-helpers/defaults";
import { useDeleteProduction } from "./use-delete-production";
import { NavigateToRootButton } from "../navigate-to-root-button/navigate-to-root-button";

type FormValue = {
productionId: string;
};

const Container = styled.form`
max-width: 45rem;
padding: 1rem 0 0 2rem;
`;

const RemoveConfirmation = styled.div`
background: #91fa8c;
padding: 1rem;
border-radius: 0.5rem;
border: 1px solid #b2ffa1;
color: #1a1a1a;
`;

const FetchErrorMessage = styled.div`
background: ${errorColour};
color: ${darkText};
padding: 0.5rem;
margin: 1rem 0;
`;

const VerifyBtnWrapper = styled.div`
padding: 1rem 0 0 2rem;
`;

const VerifyButtons = styled.div`
display: flex;
padding: 1rem 0 0 0;
`;

const Button = styled(ActionButton)`
margin: 0 1rem 0 0;
`;
const StyledBackBtnIcon = styled.div`
padding: 0 0 3rem 0;
width: 4rem;
`;

export const ManageProductions = () => {
const [showDeleteDoneMessage, setShowDeleteDoneMessage] =
useState<boolean>(false);
const [verifyRemove, setVerifyRemove] = useState<boolean>(false);
const [removeId, setRemoveId] = useState<null | number>(null);
const {
control,
reset,
formState,
formState: { errors, isSubmitSuccessful },
register,
handleSubmit,
} = useForm<FormValue>();

const productionId = useWatch({ name: "productionId", control });

const { onChange, onBlur, name, ref } = register("productionId", {
required: "Production ID is required",
min: 1,
});

const { error: productionFetchError, production } = useFetchProduction(
parseInt(productionId, 10)
);

const {
loading,
error: productionDeleteError,
successfullDelete,
} = useDeleteProduction(removeId);

useEffect(() => {
if (formState.isSubmitSuccessful) {
reset({
productionId: "",
});
setVerifyRemove(false);
}
}, [formState.isSubmitSuccessful, isSubmitSuccessful, reset]);

useEffect(() => {
if (successfullDelete) {
setVerifyRemove(false);
setShowDeleteDoneMessage(true);
}
}, [successfullDelete]);

const onSubmit: SubmitHandler<FormValue> = (value) => {
if (loading) return;

setRemoveId(parseInt(value.productionId, 10));
};
// TODO return button

return (
<Container>
<StyledBackBtnIcon>
<NavigateToRootButton />
</StyledBackBtnIcon>
<DisplayContainerHeader>Remove Production</DisplayContainerHeader>
<FormLabel>
<DecorativeLabel>Production ID</DecorativeLabel>
<FormInput
onChange={(ev) => {
setShowDeleteDoneMessage(false);
onChange(ev);
}}
name={name}
ref={ref}
onBlur={onBlur}
type="number"
autoComplete="off"
placeholder="Production ID"
/>
</FormLabel>
{productionFetchError && (
<FetchErrorMessage>
The production ID could not be fetched. {productionFetchError.name}{" "}
{productionFetchError.message}.
</FetchErrorMessage>
)}
{productionDeleteError && (
<FetchErrorMessage>
The production ID could not be deleted. {productionDeleteError.name}{" "}
{productionDeleteError.message}.
</FetchErrorMessage>
)}
<ErrorMessage
errors={errors}
name="productionId"
as={StyledWarningMessage}
/>
{production ? (
<>
<DecorativeLabel>Production name: {production.name}</DecorativeLabel>
{!verifyRemove && (
<ActionButton
type="submit"
className={loading ? "submit" : ""}
onClick={() => setVerifyRemove(true)}
>
Remove
{loading && <Spinner className="create-production" />}
</ActionButton>
)}
{verifyRemove && (
<VerifyBtnWrapper>
<p>Are you sure?</p>
<VerifyButtons>
<Button
type="submit"
malmen237 marked this conversation as resolved.
Show resolved Hide resolved
className={loading ? "submit" : ""}
disabled={loading}
onClick={handleSubmit(onSubmit)}
>
Yes
{loading && <Spinner className="create-production" />}
</Button>
<Button
type="submit"
className={loading ? "submit" : ""}
onClick={() => {
setVerifyRemove(false);
reset({
productionId: "",
});
}}
>
Go back
{loading && <Spinner className="create-production" />}
</Button>
</VerifyButtons>
</VerifyBtnWrapper>
)}
</>
) : (
<StyledWarningMessage>
Please enter a production id
</StyledWarningMessage>
)}
{showDeleteDoneMessage && (
<RemoveConfirmation>
The production {production?.name} has been removed
</RemoveConfirmation>
)}
</Container>
);
};
57 changes: 57 additions & 0 deletions src/components/manage-productions/use-delete-production.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { useEffect, useState } from "react";
import { API } from "../../api/api";
import { useGlobalState } from "../../global-state/context-provider";

type TUseDeleteProduction = (id: number | null) => {
loading: boolean;
error: Error | null;
successfullDelete: boolean;
};

export const useDeleteProduction: TUseDeleteProduction = (id) => {
const [successfullDelete, setSuccessfullDelete] = useState(false);
const [error, setError] = useState<Error | null>(null);
const [loading, setLoading] = useState<boolean>(false);

const [, dispatch] = useGlobalState();

useEffect(() => {
let aborted = false;
setError(null);
setSuccessfullDelete(false);
setLoading(true);
if (id) {
API.deleteProduction(id)
.then(() => {
if (aborted) return;

setSuccessfullDelete(true);
setLoading(false);
setError(null);
})
.catch((err) => {
dispatch({
type: "ERROR",
payload:
err instanceof Error
? err
: new Error("Failed to delete production"),
});
setError(err);
setLoading(false);
});
} else {
setLoading(false);
}

return () => {
aborted = true;
};
}, [dispatch, id]);

return {
loading,
error,
successfullDelete,
};
};
19 changes: 19 additions & 0 deletions src/components/navigate-to-root-button/navigate-to-root-button.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import styled from "@emotion/styled";
import { useNavigate } from "react-router-dom";
import { BackArrow } from "../../assets/icons/icon";
import { ActionButton } from "../landing-page/form-elements";

const StyledBackBtn = styled(ActionButton)`
padding: 0;
margin: 0;
`;

export const NavigateToRootButton = () => {
const navigate = useNavigate();

return (
<StyledBackBtn type="button" onClick={() => navigate("/")}>
<BackArrow />
</StyledBackBtn>
);
};
Loading