add contract page with dynamic form elements
This commit is contained in:
@@ -1,52 +1,193 @@
|
||||
import { ProductForm } from "@/components/Products/Form";
|
||||
import ShipmentForm from "@/components/Shipments/Form";
|
||||
import { t } from "@/config/i18n";
|
||||
import { getForm } from "@/services/api";
|
||||
import { Group, Loader, NumberInput, Stack, Text, Title } from "@mantine/core";
|
||||
import { type Product } from "@/services/resources/products";
|
||||
import { Accordion, Button, Group, List, Loader, Overlay, Stack, Text, TextInput, Title } from "@mantine/core";
|
||||
import { useForm } from "@mantine/form";
|
||||
import { IconMail, IconPhone, IconUser } from "@tabler/icons-react";
|
||||
import { useMemo } from "react";
|
||||
import { useParams } from "react-router";
|
||||
|
||||
export function computePrices(values: [string, any][], products: Product[], nbShipment?: number) {
|
||||
return values.reduce((prev, [key, value]) => {
|
||||
const keyArray = key.split("-");
|
||||
const productId = Number(keyArray[keyArray.length - 1]);
|
||||
const product = products.find((product) => product.id === productId);
|
||||
if (!product) {
|
||||
return 0;
|
||||
}
|
||||
const isRecurent = key.includes("recurrent") && nbShipment;
|
||||
const productPrice = Number(product.price || product.price_kg);
|
||||
const productQuantityUnit = product.unit === "2" ? 1 : 1000;
|
||||
const productQuantity = Number(product.price ? value : value / productQuantityUnit);
|
||||
return(prev + productPrice * productQuantity * (isRecurent ? nbShipment : 1));
|
||||
}, 0);
|
||||
}
|
||||
|
||||
export function Contract() {
|
||||
const { id } = useParams();
|
||||
const { data: form } = getForm(Number(id), {enabled: !!id})
|
||||
const { data: form } = getForm(Number(id), {enabled: !!id});
|
||||
const inputForm = useForm<Record<string, number | string>>({
|
||||
validate: {
|
||||
firstname: (value) => !value ? `${t("a firstname", {capfirst: true})} ${t("is required")}` : null,
|
||||
lastname: (value) => !value ? `${t("a lastname", {capfirst: true})} ${t("is required")}` : null,
|
||||
email: (value) => !value ? `${t("a email", {capfirst: true})} ${t("is required")}` : null,
|
||||
phone: (value) => !value ? `${t("a phone", {capfirst: true})} ${t("is required")}` : null,
|
||||
}
|
||||
});
|
||||
|
||||
const productsRecurent = useMemo(() => {
|
||||
console.log(form)
|
||||
return form?.productor?.products.filter((el) => el.type === "2")
|
||||
}, [form])
|
||||
}, [form]);
|
||||
|
||||
const shipments = useMemo(() => {
|
||||
return form?.shipments
|
||||
return form?.shipments;
|
||||
}, [form]);
|
||||
|
||||
const allProducts = useMemo(() => {
|
||||
return form?.productor?.products;
|
||||
}, [form])
|
||||
|
||||
const price = useMemo(() => {
|
||||
const values = Object.entries(inputForm.getValues());
|
||||
return computePrices(values, allProducts, form?.shipments.length);
|
||||
}, [inputForm, allProducts, form?.shipments]);
|
||||
|
||||
if (!form)
|
||||
return <Loader/>
|
||||
return <Loader/>;
|
||||
|
||||
|
||||
return (
|
||||
<Stack>
|
||||
<Title>{form.name}</Title>
|
||||
<Stack w={{base: "100%", md: "80%", lg: "50%"}}>
|
||||
<Title order={2}>{form.name}</Title>
|
||||
<Title order={3}>{t("informations", {capfirst: true})}</Title>
|
||||
<Text size="sm">
|
||||
{t("all theses informations are for contract generation, no informations is stored outside of contracts", {capfirst: true})}
|
||||
</Text>
|
||||
<Group grow>
|
||||
<TextInput
|
||||
label={t("firstname", {capfirst: true})}
|
||||
placeholder={t("firstname", {capfirst: true})}
|
||||
radius="sm"
|
||||
withAsterisk
|
||||
required
|
||||
leftSection={<IconUser/>}
|
||||
{...inputForm.getInputProps('firstname')}
|
||||
/>
|
||||
<TextInput
|
||||
label={t("lastname", {capfirst: true})}
|
||||
placeholder={t("lastname", {capfirst: true})}
|
||||
radius="sm"
|
||||
withAsterisk
|
||||
required
|
||||
leftSection={<IconUser/>}
|
||||
{...inputForm.getInputProps('lastname')}
|
||||
/>
|
||||
</Group>
|
||||
<Group grow>
|
||||
<TextInput
|
||||
label={t("email", {capfirst: true})}
|
||||
placeholder={t("email", {capfirst: true})}
|
||||
radius="sm"
|
||||
withAsterisk
|
||||
required
|
||||
leftSection={<IconMail/>}
|
||||
{...inputForm.getInputProps('email')}
|
||||
/>
|
||||
<TextInput
|
||||
label={t("phone", {capfirst: true})}
|
||||
placeholder={t("phone", {capfirst: true})}
|
||||
radius="sm"
|
||||
withAsterisk
|
||||
required
|
||||
leftSection={<IconPhone/>}
|
||||
{...inputForm.getInputProps('phone')}
|
||||
/>
|
||||
</Group>
|
||||
<Title order={3}>{t('shipments', {capfirst: true})}</Title>
|
||||
<Text>{`${t("there is", {capfirst: true})} ${shipments.length} ${shipments.length > 1 ? t("shipments") : t("shipment")} ${t("for this contract")}`}</Text>
|
||||
<List>
|
||||
{
|
||||
shipments.map(shipment => (
|
||||
<List.Item key={shipment.id}>{`${shipment.name} :
|
||||
${
|
||||
new Date(shipment.date).toLocaleDateString("fr-FR", {
|
||||
weekday: "long",
|
||||
year: "numeric",
|
||||
month: "long",
|
||||
day: "numeric",
|
||||
})
|
||||
}`}
|
||||
</List.Item>
|
||||
))
|
||||
}
|
||||
</List>
|
||||
{
|
||||
productsRecurent.map((el) => (
|
||||
<Group>
|
||||
<Text>{el.name}</Text>
|
||||
<NumberInput/>
|
||||
</Group>
|
||||
))
|
||||
productsRecurent.length > 0 ?
|
||||
<>
|
||||
<Title order={3}>{t('recurrent products', {capfirst: true})}</Title>
|
||||
<Text size="sm">{t('your selection in this category will apply for all shipments', {capfirst: true})}</Text>
|
||||
{
|
||||
productsRecurent.map((product) => (
|
||||
<ProductForm
|
||||
key={product.id}
|
||||
product={product}
|
||||
inputForm={inputForm}
|
||||
/>
|
||||
))
|
||||
}
|
||||
</> :
|
||||
null
|
||||
}
|
||||
{
|
||||
shipments.map((shipment) => (
|
||||
<>
|
||||
<Text>{shipment.name}</Text>
|
||||
{
|
||||
shipment?.products.map((product) => (
|
||||
<Group>
|
||||
<Text>{product.name}</Text>
|
||||
<NumberInput/>
|
||||
</Group>
|
||||
|
||||
))
|
||||
}
|
||||
</>
|
||||
|
||||
))
|
||||
shipments.some(shipment => shipment.products.length > 0) ?
|
||||
<>
|
||||
<Title order={3}>{t("planned products")}</Title>
|
||||
<Text>{t("select products per shipment")}</Text>
|
||||
<Accordion defaultValue={"0"}>
|
||||
{
|
||||
shipments.map((shipment, index) => (
|
||||
<ShipmentForm
|
||||
shipment={shipment}
|
||||
index={index}
|
||||
inputForm={inputForm}
|
||||
key={shipment.id}
|
||||
/>
|
||||
))
|
||||
}
|
||||
</Accordion>
|
||||
</> :
|
||||
null
|
||||
}
|
||||
<Overlay
|
||||
bg={"lightGray"}
|
||||
h="10vh"
|
||||
p="sm"
|
||||
style={{
|
||||
display: "flex",
|
||||
justifyContent: "space-between",
|
||||
alignItems: "center",
|
||||
position: "sticky",
|
||||
bottom: "0px",
|
||||
}}
|
||||
>
|
||||
<Text>{
|
||||
t("total", {capfirst: true})} : {Intl.NumberFormat(
|
||||
"fr-FR",
|
||||
{style: "currency", currency: "EUR"}
|
||||
).format(price)}
|
||||
</Text>
|
||||
<Button
|
||||
aria-label={t('submit contract')}
|
||||
onClick={() => {
|
||||
inputForm.validate();
|
||||
console.log(inputForm.getValues())
|
||||
}}
|
||||
>
|
||||
{t('submit contract')}
|
||||
</Button>
|
||||
</Overlay>
|
||||
</Stack>
|
||||
)
|
||||
}
|
||||
@@ -25,7 +25,7 @@ export function Forms() {
|
||||
}, [location, isEdit])
|
||||
|
||||
const closeModal = () => {
|
||||
navigate("/dashboard/forms");
|
||||
navigate(`/dashboard/forms${searchParams ? `?${searchParams.toString()}` : ""}`);
|
||||
};
|
||||
|
||||
const { isPending, data } = getForms(searchParams);
|
||||
@@ -102,7 +102,7 @@ export function Forms() {
|
||||
<ActionIcon
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
navigate(`/dashboard/forms/create`);
|
||||
navigate(`/dashboard/forms/create${searchParams ? `?${searchParams.toString()}` : ""}`);
|
||||
}}
|
||||
>
|
||||
<IconPlus/>
|
||||
|
||||
@@ -5,7 +5,7 @@ import { IconPlus } from "@tabler/icons-react";
|
||||
import ProductorRow from "@/components/Productors/Row";
|
||||
import { useLocation, useNavigate, useSearchParams } from "react-router";
|
||||
import { ProductorModal } from "@/components/Productors/Modal";
|
||||
import { useCallback, useMemo, useState } from "react";
|
||||
import { useCallback, useMemo } from "react";
|
||||
import type { Productor, ProductorInputs } from "@/services/resources/productors";
|
||||
import ProductorsFilters from "@/components/Productors/Filter";
|
||||
|
||||
@@ -25,7 +25,7 @@ export default function Productors() {
|
||||
}, [location, isEdit])
|
||||
|
||||
const closeModal = () => {
|
||||
navigate("/dashboard/productors");
|
||||
navigate(`/dashboard/productors${searchParams ? `?${searchParams.toString()}` : ""}`);
|
||||
};
|
||||
|
||||
const { data: productors, isPending } = getProductors(searchParams);
|
||||
@@ -85,7 +85,7 @@ export default function Productors() {
|
||||
<ActionIcon
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
navigate(`/dashboard/productors/create`);
|
||||
navigate(`/dashboard/productors/create${searchParams ? `?${searchParams.toString()}` : ""}`);
|
||||
}}
|
||||
>
|
||||
<IconPlus/>
|
||||
|
||||
@@ -25,7 +25,7 @@ export default function Products() {
|
||||
}, [location, isEdit])
|
||||
|
||||
const closeModal = () => {
|
||||
navigate("/dashboard/products");
|
||||
navigate(`/dashboard/products${searchParams ? `?${searchParams.toString()}` : ""}`);
|
||||
};
|
||||
|
||||
const { data: products, isPending } = getProducts(searchParams);
|
||||
@@ -38,7 +38,7 @@ export default function Products() {
|
||||
}, [allProducts])
|
||||
|
||||
const productors = useMemo(() => {
|
||||
return allProducts?.map((form: Product) => (form.productor.name))
|
||||
return allProducts?.map((product: Product) => (product.productor.name))
|
||||
.filter((productor, index, array) => array.indexOf(productor) === index)
|
||||
}, [allProducts])
|
||||
|
||||
@@ -55,7 +55,7 @@ export default function Products() {
|
||||
return;
|
||||
await editProductMutation.mutateAsync({
|
||||
id: id,
|
||||
product: product
|
||||
product: productCreateFromProductInputs(product)
|
||||
});
|
||||
closeModal();
|
||||
}, []);
|
||||
@@ -83,7 +83,7 @@ export default function Products() {
|
||||
<ActionIcon
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
navigate(`/dashboard/products/create`);
|
||||
navigate(`/dashboard/products/create${searchParams ? `?${searchParams.toString()}` : ""}`);
|
||||
}}
|
||||
>
|
||||
<IconPlus/>
|
||||
@@ -115,7 +115,8 @@ export default function Products() {
|
||||
<Table.Th>{t("type", {capfirst: true})}</Table.Th>
|
||||
<Table.Th>{t("price", {capfirst: true})}</Table.Th>
|
||||
<Table.Th>{t("priceKg", {capfirst: true})}</Table.Th>
|
||||
<Table.Th>{t("weight", {capfirst: true})}</Table.Th>
|
||||
<Table.Th>{t("quantity", {capfirst: true})}</Table.Th>
|
||||
<Table.Th>{t("quantity unit", {capfirst: true})}</Table.Th>
|
||||
<Table.Th>{t("unit", {capfirst: true})}</Table.Th>
|
||||
<Table.Th>{t("actions", {capfirst: true})}</Table.Th>
|
||||
</Table.Tr>
|
||||
|
||||
@@ -25,7 +25,7 @@ export default function Shipments() {
|
||||
}, [location, isEdit])
|
||||
|
||||
const closeModal = () => {
|
||||
navigate("/dashboard/shipments");
|
||||
navigate(`/dashboard/shipments${searchParams ? `?${searchParams.toString()}` : ""}`);
|
||||
};
|
||||
|
||||
const { data: shipments, isPending } = getShipments(searchParams);
|
||||
@@ -78,7 +78,7 @@ export default function Shipments() {
|
||||
<ActionIcon
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
navigate(`/dashboard/shipments/create`);
|
||||
navigate(`/dashboard/shipments/create${searchParams ? `?${searchParams.toString()}` : ""}`);
|
||||
}}
|
||||
>
|
||||
<IconPlus/>
|
||||
|
||||
@@ -25,7 +25,7 @@ export default function Users() {
|
||||
}, [location, isEdit])
|
||||
|
||||
const closeModal = () => {
|
||||
navigate("/dashboard/users");
|
||||
navigate(`/dashboard/users${searchParams ? `?${searchParams.toString()}` : ""}`);
|
||||
};
|
||||
|
||||
const {data: users, isPending} = getUsers(searchParams);
|
||||
@@ -79,7 +79,7 @@ export default function Users() {
|
||||
<ActionIcon
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
navigate(`/dashboard/users/create`);
|
||||
navigate(`/dashboard/users/create${searchParams ? `?${searchParams.toString()}` : ""}`);
|
||||
}}
|
||||
>
|
||||
<IconPlus/>
|
||||
|
||||
Reference in New Issue
Block a user