improved product details page
This commit is contained in:
@@ -91,3 +91,42 @@ export const allProducts = async () => {
|
|||||||
return { code: "ep002" };
|
return { code: "ep002" };
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const setAmount = async (itemUUID, amount) => {
|
||||||
|
const [result] = await pool.query(
|
||||||
|
`
|
||||||
|
UPDATE products SET amount = ? WHERE uuid = UUID_TO_BIN(?)
|
||||||
|
`,
|
||||||
|
[amount, itemUUID],
|
||||||
|
);
|
||||||
|
|
||||||
|
if (result.affectedRows > 0) {
|
||||||
|
return { code: "sp004" }; // success
|
||||||
|
} else {
|
||||||
|
return { code: "ep004" }; // error
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const updateItem = async (itemUUID, newValues) => {
|
||||||
|
const [result] = await pool.query(
|
||||||
|
`
|
||||||
|
UPDATE products SET name = ?, description = ?, price = ?, amount = ?, storage_location = UUID_TO_BIN(?), expiry_date = ?, bottling_date = ? WHERE uuid = UUID_TO_BIN(?);
|
||||||
|
`,
|
||||||
|
[
|
||||||
|
newValues.name,
|
||||||
|
newValues.description,
|
||||||
|
newValues.price,
|
||||||
|
newValues.amount,
|
||||||
|
newValues.storage_location_uuid,
|
||||||
|
newValues.expiry_date,
|
||||||
|
newValues.bottling_date,
|
||||||
|
itemUUID,
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
|
if (result.affectedRows > 0) {
|
||||||
|
return { code: "sp005" }; // success
|
||||||
|
} else {
|
||||||
|
return { code: "ep005" }; // error
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|||||||
@@ -5,6 +5,8 @@ import {
|
|||||||
allProducts,
|
allProducts,
|
||||||
newProduct,
|
newProduct,
|
||||||
productDetails,
|
productDetails,
|
||||||
|
setAmount,
|
||||||
|
updateItem,
|
||||||
} from "./database/products.database.js";
|
} from "./database/products.database.js";
|
||||||
dotenv.config();
|
dotenv.config();
|
||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
@@ -95,4 +97,54 @@ router.get("/view", authenticate, async (req, res) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
router.put("/mutate/set-amount", authenticate, async (req, res) => {
|
||||||
|
const amount = req.query.amount;
|
||||||
|
const itemUUID = req.query.item;
|
||||||
|
|
||||||
|
const result = await setAmount(itemUUID, amount);
|
||||||
|
|
||||||
|
if (result.code === "ep004") {
|
||||||
|
res.status(406).json({
|
||||||
|
success: false,
|
||||||
|
code: "ep004",
|
||||||
|
data: null,
|
||||||
|
message: "Error while updating product amount",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result.code === "sp004") {
|
||||||
|
res.status(200).json({
|
||||||
|
success: true,
|
||||||
|
code: "sp004",
|
||||||
|
data: null,
|
||||||
|
message: "",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
router.post("/mutate/update-item", authenticate, async (req, res) => {
|
||||||
|
const itemUUID = req.query.item;
|
||||||
|
const newValues = req.body;
|
||||||
|
|
||||||
|
const result = await updateItem(itemUUID, newValues);
|
||||||
|
|
||||||
|
if (result.code === "ep005") {
|
||||||
|
res.status(406).json({
|
||||||
|
success: false,
|
||||||
|
code: "ep005",
|
||||||
|
data: null,
|
||||||
|
message: "Error while updating product",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result.code === "sp005") {
|
||||||
|
res.status(200).json({
|
||||||
|
success: true,
|
||||||
|
code: "sp005",
|
||||||
|
data: null,
|
||||||
|
message: "",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
export default router;
|
export default router;
|
||||||
|
|||||||
@@ -0,0 +1,22 @@
|
|||||||
|
export interface productDetailsInterface {
|
||||||
|
amount: number;
|
||||||
|
bottling_date: string;
|
||||||
|
description: string;
|
||||||
|
expiry_date: string;
|
||||||
|
name: string;
|
||||||
|
picture: string | null;
|
||||||
|
price: string;
|
||||||
|
storage_location_name: string;
|
||||||
|
storage_location_uuid: string;
|
||||||
|
uuid: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type ProductFormValues = {
|
||||||
|
amount: number;
|
||||||
|
bottling_date: string;
|
||||||
|
description: string;
|
||||||
|
expiry_date: string;
|
||||||
|
name: string;
|
||||||
|
price: string;
|
||||||
|
storage_location_uuid: string;
|
||||||
|
};
|
||||||
@@ -1,22 +1,20 @@
|
|||||||
import { useQuery } from "@tanstack/react-query";
|
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
||||||
import { getProductDetails } from "../utils/uxFncs";
|
import { getProductDetails, getStorages } from "../utils/uxFncs";
|
||||||
import { CircularProgress, Typography } from "@mui/joy";
|
import {
|
||||||
|
CircularProgress,
|
||||||
|
Typography,
|
||||||
|
Select,
|
||||||
|
Option,
|
||||||
|
Input,
|
||||||
|
Button,
|
||||||
|
} from "@mui/joy";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { useEffect } from "react";
|
import { useEffect } from "react";
|
||||||
import { formatDate } from "../utils/uxFncs";
|
import { useForm } from "@tanstack/react-form";
|
||||||
|
import { mutateProduct } from "../utils/uxFncs";
|
||||||
interface productDetailsInterface {
|
import { toInputDate } from "../utils/uxFncs";
|
||||||
amount: number;
|
import type { ProductFormValues } from "../misc/interfaces";
|
||||||
bottling_date: string;
|
import type { productDetailsInterface } from "../misc/interfaces";
|
||||||
description: string;
|
|
||||||
expiry_date: string;
|
|
||||||
name: string;
|
|
||||||
picture: string | null;
|
|
||||||
price: string;
|
|
||||||
storage_location_name: string;
|
|
||||||
storage_location_uuid: string;
|
|
||||||
uuid: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface ViewProductProps {
|
interface ViewProductProps {
|
||||||
uuid: string;
|
uuid: string;
|
||||||
@@ -25,6 +23,7 @@ interface ViewProductProps {
|
|||||||
export const ViewProduct = (props: ViewProductProps) => {
|
export const ViewProduct = (props: ViewProductProps) => {
|
||||||
const uuid = props.uuid;
|
const uuid = props.uuid;
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
const {
|
const {
|
||||||
data: productDetails,
|
data: productDetails,
|
||||||
@@ -35,9 +34,62 @@ export const ViewProduct = (props: ViewProductProps) => {
|
|||||||
queryFn: () => getProductDetails(uuid),
|
queryFn: () => getProductDetails(uuid),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const { data: storages } = useQuery({
|
||||||
|
queryKey: ["storages"],
|
||||||
|
queryFn: () => getStorages(),
|
||||||
|
});
|
||||||
|
|
||||||
|
const form = useForm({
|
||||||
|
defaultValues: {
|
||||||
|
amount: 0,
|
||||||
|
bottling_date: "",
|
||||||
|
description: "",
|
||||||
|
expiry_date: "",
|
||||||
|
name: "",
|
||||||
|
price: "",
|
||||||
|
storage_location_uuid: "",
|
||||||
|
},
|
||||||
|
onSubmit: async ({ value }) => {
|
||||||
|
if (!productDetails?.uuid) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
mutate({ values: value, uuid: productDetails.uuid });
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const { mutate, isPending } = useMutation({
|
||||||
|
mutationFn: ({
|
||||||
|
values,
|
||||||
|
uuid,
|
||||||
|
}: {
|
||||||
|
values: ProductFormValues;
|
||||||
|
uuid: string;
|
||||||
|
}) => mutateProduct(values, uuid),
|
||||||
|
onSuccess: (_data, variables) => {
|
||||||
|
queryClient.invalidateQueries({ queryKey: ["product", variables.uuid] });
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
console.log(productDetails);
|
if (!productDetails) {
|
||||||
}, [isSuccess]);
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
form.setFieldValue("amount", productDetails.amount ?? 0);
|
||||||
|
form.setFieldValue(
|
||||||
|
"bottling_date",
|
||||||
|
toInputDate(productDetails.bottling_date),
|
||||||
|
);
|
||||||
|
form.setFieldValue("description", productDetails.description ?? "");
|
||||||
|
form.setFieldValue("expiry_date", toInputDate(productDetails.expiry_date));
|
||||||
|
form.setFieldValue("name", productDetails.name ?? "");
|
||||||
|
form.setFieldValue("price", productDetails.price ?? "");
|
||||||
|
form.setFieldValue(
|
||||||
|
"storage_location_uuid",
|
||||||
|
productDetails.storage_location_uuid ?? "",
|
||||||
|
);
|
||||||
|
}, [form, productDetails]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@@ -45,11 +97,100 @@ export const ViewProduct = (props: ViewProductProps) => {
|
|||||||
{productDetailsLoading && <CircularProgress size="sm" />}
|
{productDetailsLoading && <CircularProgress size="sm" />}
|
||||||
{isSuccess && (
|
{isSuccess && (
|
||||||
<>
|
<>
|
||||||
<Typography level="h3">{productDetails.name}</Typography>
|
<form
|
||||||
<Typography level="body-lg">{productDetails.description}</Typography>
|
onSubmit={(e) => {
|
||||||
<Typography level="body-lg">
|
e.preventDefault();
|
||||||
{t("expiry-date") + " " + formatDate(productDetails.expiry_date)}
|
form.handleSubmit();
|
||||||
</Typography>
|
}}
|
||||||
|
>
|
||||||
|
<form.Field name="name">
|
||||||
|
{(field) => (
|
||||||
|
<Input
|
||||||
|
type="text"
|
||||||
|
value={field.state.value}
|
||||||
|
onChange={(e) => field.handleChange(e.target.value)}
|
||||||
|
onBlur={field.handleBlur}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</form.Field>
|
||||||
|
<form.Field name="description">
|
||||||
|
{(field) => (
|
||||||
|
<Input
|
||||||
|
type="text"
|
||||||
|
value={field.state.value}
|
||||||
|
onChange={(e) => field.handleChange(e.target.value)}
|
||||||
|
onBlur={field.handleBlur}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</form.Field>
|
||||||
|
{t("expiry-date")}
|
||||||
|
<form.Field name="expiry_date">
|
||||||
|
{(field) => (
|
||||||
|
<Input
|
||||||
|
type="date"
|
||||||
|
value={field.state.value}
|
||||||
|
onChange={(e) => field.handleChange(e.target.value)}
|
||||||
|
onBlur={field.handleBlur}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</form.Field>
|
||||||
|
{t("bottling-date")}
|
||||||
|
<form.Field name="bottling_date">
|
||||||
|
{(field) => (
|
||||||
|
<Input
|
||||||
|
type="date"
|
||||||
|
value={field.state.value}
|
||||||
|
onChange={(e) => field.handleChange(e.target.value)}
|
||||||
|
onBlur={field.handleBlur}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</form.Field>
|
||||||
|
<form.Field name="amount">
|
||||||
|
{(field) => (
|
||||||
|
<Input
|
||||||
|
type="number"
|
||||||
|
color="neutral"
|
||||||
|
id="amountInput"
|
||||||
|
placeholder={t("amount")}
|
||||||
|
value={field.state.value}
|
||||||
|
variant="soft"
|
||||||
|
onChange={(e) => {
|
||||||
|
const nextValue = Number(e.target.value);
|
||||||
|
field.handleChange(Number.isNaN(nextValue) ? 0 : nextValue);
|
||||||
|
}}
|
||||||
|
onBlur={field.handleBlur}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</form.Field>
|
||||||
|
<form.Field name="price">
|
||||||
|
{(field) => (
|
||||||
|
<Input
|
||||||
|
type="text"
|
||||||
|
value={field.state.value}
|
||||||
|
onChange={(e) => field.handleChange(e.target.value)}
|
||||||
|
onBlur={field.handleBlur}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</form.Field>
|
||||||
|
{t("currency")}
|
||||||
|
<form.Field name="storage_location_uuid">
|
||||||
|
{(field) => (
|
||||||
|
<Select
|
||||||
|
value={field.state.value}
|
||||||
|
onChange={(_event, value) => field.handleChange(value ?? "")}
|
||||||
|
>
|
||||||
|
{storages?.map((storage: { uuid: string; name: string }) => (
|
||||||
|
<Option key={storage.uuid} value={storage.uuid}>
|
||||||
|
{storage.name}
|
||||||
|
</Option>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
|
)}
|
||||||
|
</form.Field>
|
||||||
|
<Button type="submit" loading={isPending}>
|
||||||
|
{t("save")}
|
||||||
|
</Button>
|
||||||
|
</form>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
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 } 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`, {
|
||||||
@@ -40,10 +41,73 @@ export const getProductDetails = async (uuid: string) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const formatDate = (isoString: string) => {
|
export const toInputDate = (value?: string) => {
|
||||||
const date = new Date(isoString);
|
if (!value) {
|
||||||
const day = String(date.getDate()).padStart(2, "0");
|
return "";
|
||||||
const month = String(date.getMonth() + 1).padStart(2, "0");
|
}
|
||||||
const year = date.getFullYear();
|
|
||||||
return `${day}.${month}.${year}`;
|
if (/^\d{4}-\d{2}-\d{2}$/.test(value)) {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
const date = new Date(value);
|
||||||
|
if (Number.isNaN(date.getTime())) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
return date.toISOString().slice(0, 10);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const mutateProduct = async (
|
||||||
|
values: ProductFormValues,
|
||||||
|
itemUUID: string,
|
||||||
|
) => {
|
||||||
|
const payload = {
|
||||||
|
...values,
|
||||||
|
expiry_date: values.expiry_date || null,
|
||||||
|
bottling_date: values.bottling_date || null,
|
||||||
|
};
|
||||||
|
|
||||||
|
const result = await fetch(
|
||||||
|
`${API_BASE}/products/mutate/update-item?item=${itemUUID}`,
|
||||||
|
{
|
||||||
|
method: "POST",
|
||||||
|
body: JSON.stringify(payload),
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${Cookies.get("token") || ""}`,
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
Accept: "application/json",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
const response = await result.json();
|
||||||
|
|
||||||
|
if (response.code === "ep004") {
|
||||||
|
return { success: false, code: response.code };
|
||||||
|
}
|
||||||
|
|
||||||
|
if (response.code === "sp004") {
|
||||||
|
return response.data;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getStorages = async () => {
|
||||||
|
const result = await fetch(`${API_BASE}/storage/all-storages`, {
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${Cookies.get("token") || ""}`,
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
Accept: "application/json",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const response = await result.json();
|
||||||
|
|
||||||
|
if (response.code === "es001") {
|
||||||
|
return { success: false, code: response.code };
|
||||||
|
}
|
||||||
|
|
||||||
|
if (response.code === "ss001") {
|
||||||
|
return response.data;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user