add new storage modal and validation for storage creation
This commit is contained in:
@@ -34,6 +34,16 @@ router.get("/all-storages", authenticate, async (req, res) => {
|
|||||||
router.post("/new-storage", authenticate, async (req, res) => {
|
router.post("/new-storage", authenticate, async (req, res) => {
|
||||||
const { name, description } = req.body;
|
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);
|
const result = await newStorage(name, description);
|
||||||
|
|
||||||
if (result.code === "es002") {
|
if (result.code === "es002") {
|
||||||
|
|||||||
@@ -66,11 +66,18 @@ export const StorageRow = ({ storage }: StorageRowProps) => {
|
|||||||
<td>{formatDate(storage.updated_at)}</td>
|
<td>{formatDate(storage.updated_at)}</td>
|
||||||
<td>
|
<td>
|
||||||
<Button
|
<Button
|
||||||
|
color="primary"
|
||||||
onClick={form.handleSubmit}
|
onClick={form.handleSubmit}
|
||||||
disabled={!isDirty || mutation.isPending}
|
disabled={!isDirty || mutation.isPending}
|
||||||
>
|
>
|
||||||
{mutation.isPending ? "..." : "Save"}
|
{mutation.isPending ? "..." : "Save"}
|
||||||
</Button>
|
</Button>
|
||||||
|
<Button
|
||||||
|
color="danger"
|
||||||
|
onClick={() => console.log("Delete Storage: " + storage.uuid)}
|
||||||
|
>
|
||||||
|
{mutation.isPending ? "..." : "Delete"}
|
||||||
|
</Button>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</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>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -27,4 +27,16 @@ export interface Storage {
|
|||||||
created_at: string;
|
created_at: string;
|
||||||
updated_at: string;
|
updated_at: string;
|
||||||
uuid: 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;
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,12 +1,15 @@
|
|||||||
import { useQuery } from "@tanstack/react-query";
|
import { useQuery } from "@tanstack/react-query";
|
||||||
import { getStorages } from "../utils/uxFncs";
|
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 { useTranslation } from "react-i18next";
|
||||||
import type { Storage } from "../misc/interfaces";
|
import type { Storage } from "../misc/interfaces";
|
||||||
import { StorageRow } from "../components/StorageRow";
|
import { StorageRow } from "../components/StorageRow";
|
||||||
|
import { useState } from "react";
|
||||||
|
import { AddStorageModal } from "../components/modals/AddStorageModal";
|
||||||
|
|
||||||
export const Storages = () => {
|
export const Storages = () => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
const [modal, setModal] = useState(false);
|
||||||
|
|
||||||
const { data: storages, isLoading } = useQuery({
|
const { data: storages, isLoading } = useQuery({
|
||||||
queryKey: ["storages"],
|
queryKey: ["storages"],
|
||||||
@@ -15,29 +18,36 @@ export const Storages = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Sheet>
|
<Sheet>
|
||||||
<Button>+</Button>
|
{isLoading ? (
|
||||||
<Table
|
<CircularProgress />
|
||||||
borderAxis="x"
|
) : (
|
||||||
color="neutral"
|
<>
|
||||||
stickyHeader
|
<AddStorageModal isOpen={modal} setOpen={setModal} />
|
||||||
stripe="odd"
|
<Button onClick={() => setModal(true)}>+</Button>
|
||||||
variant="soft"
|
<Table
|
||||||
>
|
borderAxis="x"
|
||||||
<thead>
|
color="neutral"
|
||||||
<tr>
|
stickyHeader
|
||||||
<th>{t("name")}</th>
|
stripe="odd"
|
||||||
<th>{t("description")}</th>
|
variant="soft"
|
||||||
<th>{t("created-at")}</th>
|
>
|
||||||
<th>{t("updated-at")}</th>
|
<thead>
|
||||||
<th></th>
|
<tr>
|
||||||
</tr>
|
<th>{t("name")}</th>
|
||||||
</thead>
|
<th>{t("description")}</th>
|
||||||
<tbody>
|
<th>{t("created-at")}</th>
|
||||||
{storages?.map((storage: Storage) => (
|
<th>{t("updated-at")}</th>
|
||||||
<StorageRow key={storage.uuid} storage={storage} />
|
<th></th>
|
||||||
))}
|
</tr>
|
||||||
</tbody>
|
</thead>
|
||||||
</Table>
|
<tbody>
|
||||||
|
{storages?.map((storage: Storage) => (
|
||||||
|
<StorageRow key={storage.uuid} storage={storage} />
|
||||||
|
))}
|
||||||
|
</tbody>
|
||||||
|
</Table>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</Sheet>
|
</Sheet>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,6 +1,10 @@
|
|||||||
import { API_BASE } from "../config/api.config";
|
import { API_BASE } from "../config/api.config";
|
||||||
import Cookies from "js-cookie";
|
import Cookies from "js-cookie";
|
||||||
import type { ProductFormValues, Storage } from "../misc/interfaces";
|
import type {
|
||||||
|
NewStorage,
|
||||||
|
ProductFormValues,
|
||||||
|
Storage,
|
||||||
|
} from "../misc/interfaces";
|
||||||
|
|
||||||
export const getProducts = async () => {
|
export const getProducts = async () => {
|
||||||
const result = await fetch(`${API_BASE}/products/all-products`, {
|
const result = await fetch(`${API_BASE}/products/all-products`, {
|
||||||
@@ -182,3 +186,25 @@ export const formatDate = (value?: string | null) => {
|
|||||||
}
|
}
|
||||||
return date.toLocaleDateString("de-DE");
|
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;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user