diff --git a/backend/routes/app/storage.route.js b/backend/routes/app/storage.route.js index 02f9834..5cebd5c 100644 --- a/backend/routes/app/storage.route.js +++ b/backend/routes/app/storage.route.js @@ -34,6 +34,16 @@ router.get("/all-storages", authenticate, async (req, res) => { router.post("/new-storage", authenticate, async (req, res) => { const { name, description } = req.body; + if (!name || !description) { + res.status(400).json({ + success: false, + code: "es000", + data: null, + message: "invalid request body", + }); + return; + } + const result = await newStorage(name, description); if (result.code === "es002") { diff --git a/frontend/src/components/StorageRow.tsx b/frontend/src/components/StorageRow.tsx index 3f478d7..f641f0e 100644 --- a/frontend/src/components/StorageRow.tsx +++ b/frontend/src/components/StorageRow.tsx @@ -66,11 +66,18 @@ export const StorageRow = ({ storage }: StorageRowProps) => { {formatDate(storage.updated_at)} + ); diff --git a/frontend/src/components/modals/AddStorageModal.tsx b/frontend/src/components/modals/AddStorageModal.tsx new file mode 100644 index 0000000..eebf46d --- /dev/null +++ b/frontend/src/components/modals/AddStorageModal.tsx @@ -0,0 +1,106 @@ +import { + Modal, + ModalDialog, + DialogTitle, + DialogContent, + Stack, + Input, + Button, + Alert, +} from "@mui/joy"; +import { useForm } from "@tanstack/react-form"; +import { useMutation, useQueryClient } from "@tanstack/react-query"; +import { useTranslation } from "react-i18next"; +import type { NewStorage, AlertInterface } from "../../misc/interfaces"; +import { mutateNewStorage } from "../../utils/uxFncs"; +import { useState } from "react"; + +interface AddStorageModalProps { + isOpen: boolean; + setOpen: (value: boolean) => void; +} + +export const AddStorageModal = (props: AddStorageModalProps) => { + const { t } = useTranslation(); + const queryClient = useQueryClient(); + const [alert, setAlert] = useState({ + isAlert: false, + type: "neutral", + header: "", + text: "", + }); + + const form = useForm({ + defaultValues: { + name: "", + description: "", + }, + onSubmit: async ({ value }) => { + mutate(value); + }, + }); + + const { mutate } = useMutation({ + mutationFn: (values: NewStorage) => mutateNewStorage(values), + onError: (error: { code?: string }) => { + setAlert({ + isAlert: true, + type: "danger", + header: t("error"), + text: error.code ? t(`errors.${error.code}`) : t("unknown-error"), + }); + }, + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: ["storages"] }); + props.setOpen(false); + }, + }); + + return ( + <> + props.setOpen(false)}> + + {t("new-storage-title")} + {t("new-storage-content")} +
{ + e.preventDefault(); + form.handleSubmit(); + }} + > + + + {(field) => ( + field.handleChange(e.target.value)} + placeholder={t("storage-name")} + variant="outlined" + /> + )} + + + {(field) => ( + field.handleChange(e.target.value)} + placeholder={t("description")} + variant="outlined" + /> + )} + + + +
+ {alert.isAlert && ( + + {alert.header} +
+ {alert.text} +
+ )} +
+
+ + ); +}; diff --git a/frontend/src/misc/interfaces.ts b/frontend/src/misc/interfaces.ts index 501d6cd..fc13ddb 100644 --- a/frontend/src/misc/interfaces.ts +++ b/frontend/src/misc/interfaces.ts @@ -27,4 +27,16 @@ export interface Storage { created_at: string; updated_at: string; uuid: string; -} \ No newline at end of file +} + +export interface NewStorage { + name: string; + description: string | null; +} + +export interface AlertInterface { + isAlert: boolean; + type: "success" | "warning" | "danger" | "neutral" | "primary"; + header: string; + text: string; +} diff --git a/frontend/src/pages/Storages.tsx b/frontend/src/pages/Storages.tsx index 1e80d94..c5bf8e3 100644 --- a/frontend/src/pages/Storages.tsx +++ b/frontend/src/pages/Storages.tsx @@ -1,12 +1,15 @@ import { useQuery } from "@tanstack/react-query"; import { getStorages } from "../utils/uxFncs"; -import { Sheet, Table, Button } from "@mui/joy"; +import { Sheet, Table, Button, CircularProgress } from "@mui/joy"; import { useTranslation } from "react-i18next"; import type { Storage } from "../misc/interfaces"; import { StorageRow } from "../components/StorageRow"; +import { useState } from "react"; +import { AddStorageModal } from "../components/modals/AddStorageModal"; export const Storages = () => { const { t } = useTranslation(); + const [modal, setModal] = useState(false); const { data: storages, isLoading } = useQuery({ queryKey: ["storages"], @@ -15,29 +18,36 @@ export const Storages = () => { return ( - - - - - - - - - - - - - {storages?.map((storage: Storage) => ( - - ))} - -
{t("name")}{t("description")}{t("created-at")}{t("updated-at")}
+ {isLoading ? ( + + ) : ( + <> + + + + + + + + + + + + + + {storages?.map((storage: Storage) => ( + + ))} + +
{t("name")}{t("description")}{t("created-at")}{t("updated-at")}
+ + )}
); }; diff --git a/frontend/src/utils/uxFncs.ts b/frontend/src/utils/uxFncs.ts index e40d86d..118f37e 100644 --- a/frontend/src/utils/uxFncs.ts +++ b/frontend/src/utils/uxFncs.ts @@ -1,6 +1,10 @@ import { API_BASE } from "../config/api.config"; import Cookies from "js-cookie"; -import type { ProductFormValues, Storage } from "../misc/interfaces"; +import type { + NewStorage, + ProductFormValues, + Storage, +} from "../misc/interfaces"; export const getProducts = async () => { const result = await fetch(`${API_BASE}/products/all-products`, { @@ -182,3 +186,25 @@ export const formatDate = (value?: string | null) => { } return date.toLocaleDateString("de-DE"); }; + +export const mutateNewStorage = async (values: NewStorage) => { + const result = await fetch(`${API_BASE}/storage/new-storage`, { + method: "POST", + body: JSON.stringify(values), + headers: { + Authorization: `Bearer ${Cookies.get("token") || ""}`, + "Content-Type": "application/json", + Accept: "application/json", + }, + }); + + const response = await result.json(); + + if (response.code === "es002") { + return { success: false, code: response.code }; + } + + if (response.code === "ss002") { + return response.data; + } +};