add new storage modal and validation for storage creation

This commit is contained in:
2026-05-29 17:33:17 +02:00
parent b0731b22db
commit 8c4f194164
6 changed files with 197 additions and 26 deletions
+10
View File
@@ -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") {
+7
View File
@@ -66,11 +66,18 @@ export const StorageRow = ({ storage }: StorageRowProps) => {
<td>{formatDate(storage.updated_at)}</td>
<td>
<Button
color="primary"
onClick={form.handleSubmit}
disabled={!isDirty || mutation.isPending}
>
{mutation.isPending ? "..." : "Save"}
</Button>
<Button
color="danger"
onClick={() => console.log("Delete Storage: " + storage.uuid)}
>
{mutation.isPending ? "..." : "Delete"}
</Button>
</td>
</tr>
);
@@ -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<AlertInterface>({
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 (
<>
<Modal open={props.isOpen} onClose={() => props.setOpen(false)}>
<ModalDialog>
<DialogTitle>{t("new-storage-title")}</DialogTitle>
<DialogContent>{t("new-storage-content")}</DialogContent>
<form
onSubmit={(e) => {
e.preventDefault();
form.handleSubmit();
}}
>
<Stack>
<form.Field name="name">
{(field) => (
<Input
value={field.state.value}
onChange={(e) => field.handleChange(e.target.value)}
placeholder={t("storage-name")}
variant="outlined"
/>
)}
</form.Field>
<form.Field name="description">
{(field) => (
<Input
value={field.state.value}
onChange={(e) => field.handleChange(e.target.value)}
placeholder={t("description")}
variant="outlined"
/>
)}
</form.Field>
<Button type="submit">{t("submit")}</Button>
</Stack>
</form>
{alert.isAlert && (
<Alert variant="soft" color={alert.type}>
{alert.header}
<br />
{alert.text}
</Alert>
)}
</ModalDialog>
</Modal>
</>
);
};
+12
View File
@@ -28,3 +28,15 @@ export interface Storage {
updated_at: string;
uuid: string;
}
export interface NewStorage {
name: string;
description: string | null;
}
export interface AlertInterface {
isAlert: boolean;
type: "success" | "warning" | "danger" | "neutral" | "primary";
header: string;
text: string;
}
+34 -24
View File
@@ -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 (
<Sheet>
<Button>+</Button>
<Table
borderAxis="x"
color="neutral"
stickyHeader
stripe="odd"
variant="soft"
>
<thead>
<tr>
<th>{t("name")}</th>
<th>{t("description")}</th>
<th>{t("created-at")}</th>
<th>{t("updated-at")}</th>
<th></th>
</tr>
</thead>
<tbody>
{storages?.map((storage: Storage) => (
<StorageRow key={storage.uuid} storage={storage} />
))}
</tbody>
</Table>
{isLoading ? (
<CircularProgress />
) : (
<>
<AddStorageModal isOpen={modal} setOpen={setModal} />
<Button onClick={() => setModal(true)}>+</Button>
<Table
borderAxis="x"
color="neutral"
stickyHeader
stripe="odd"
variant="soft"
>
<thead>
<tr>
<th>{t("name")}</th>
<th>{t("description")}</th>
<th>{t("created-at")}</th>
<th>{t("updated-at")}</th>
<th></th>
</tr>
</thead>
<tbody>
{storages?.map((storage: Storage) => (
<StorageRow key={storage.uuid} storage={storage} />
))}
</tbody>
</Table>
</>
)}
</Sheet>
);
};
+27 -1
View File
@@ -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;
}
};