add i18n, products, productors and forms as tables
This commit is contained in:
28
frontend/src/pages/Dashboard/index.tsx
Normal file
28
frontend/src/pages/Dashboard/index.tsx
Normal file
@@ -0,0 +1,28 @@
|
||||
import { Tabs } from "@mantine/core";
|
||||
import { t } from "@/config/i18n";
|
||||
import { Outlet, useNavigate, useParams } from "react-router";
|
||||
|
||||
export default function Dashboard() {
|
||||
const navigate = useNavigate();
|
||||
const { tabValue } = useParams();
|
||||
|
||||
return (
|
||||
<Tabs
|
||||
w={{base: "100%", md: "80%", lg: "60%"}}
|
||||
keepMounted={false}
|
||||
defaultValue="productors"
|
||||
orientation={"horizontal"}
|
||||
value={tabValue}
|
||||
onChange={(value) => navigate(`/dashboard/${value}`)}
|
||||
>
|
||||
<Tabs.List>
|
||||
<Tabs.Tab value="productors">{t("productors", {capfirst: true})}</Tabs.Tab>
|
||||
<Tabs.Tab value="products">{t("products", {capfirst: true})}</Tabs.Tab>
|
||||
<Tabs.Tab value="templates">{t("templates", {capfirst: true})}</Tabs.Tab>
|
||||
<Tabs.Tab value="users">{t("users", {capfirst: true})}</Tabs.Tab>
|
||||
<Tabs.Tab value="forms">{t("forms", {capfirst: true})}</Tabs.Tab>
|
||||
</Tabs.List>
|
||||
<Outlet/>
|
||||
</Tabs>
|
||||
);
|
||||
}
|
||||
@@ -1,79 +0,0 @@
|
||||
import { Flex, Grid, Select, Stack, Text, TextInput, Title } from "@mantine/core";
|
||||
import { IconUser } from "@tabler/icons-react";
|
||||
import { getForm } from "../../../services/api";
|
||||
import { t } from "../../../config/i18n";
|
||||
import ShipmentCard from "../../../components/ShipmentCard";
|
||||
|
||||
export function ReadForm() {
|
||||
// const { isPending, error, data } = getForm(1);
|
||||
// console.log(isPending, error, data);
|
||||
return (
|
||||
<Flex
|
||||
w={{base: "100%", sm: "50%", lg: "60%"}}
|
||||
justify={"start"}
|
||||
align={"flex-start"}
|
||||
direction={"column"}
|
||||
>
|
||||
{/* <Stack>
|
||||
<Title>{t("form contract")}</Title>
|
||||
<Text>{t("contract description that is rather long to show how text will be displayed even with unnecessary elements like this end of sentence")}</Text>
|
||||
</Stack>
|
||||
<Stack>
|
||||
<Text>{t("contact phase")}</Text>
|
||||
<Grid>
|
||||
<Grid.Col span={{ base: 12, md: 6, lg: 3 }}>
|
||||
<TextInput
|
||||
radius="sm"
|
||||
label={t("firstname")}
|
||||
placeholder={t("firstname")}
|
||||
leftSection={<IconUser/>}
|
||||
/>
|
||||
<TextInput
|
||||
radius="sm"
|
||||
label={t("lastname")}
|
||||
placeholder={t("lastname")}
|
||||
leftSection={<IconUser/>}
|
||||
/>
|
||||
</Grid.Col>
|
||||
<Grid.Col span={{ base: 12, md: 6, lg: 3 }}>
|
||||
<TextInput
|
||||
radius="sm"
|
||||
label={t("email")}
|
||||
placeholder={t("email")}
|
||||
leftSection={<IconUser/>}
|
||||
/>
|
||||
<TextInput
|
||||
radius="sm"
|
||||
label={t("phone")}
|
||||
placeholder={t("phone")}
|
||||
leftSection={<IconUser/>}
|
||||
/>
|
||||
</Grid.Col>
|
||||
</Grid>
|
||||
</Stack>
|
||||
<Stack>
|
||||
<Text>{t("products reccurent phase")}</Text>
|
||||
{isPending ||
|
||||
<Select
|
||||
label={`${t("select reccurent product")} (${t("this product will be distributed for all shipments")})`}
|
||||
placeholder={t("select reccurent product")}
|
||||
data={data?.productor?.products.map(el=> el.name)}
|
||||
/>
|
||||
}
|
||||
</Stack>
|
||||
<Stack>
|
||||
<Text>{t("products planned phase")}</Text>
|
||||
<ShipmentCard
|
||||
title="Shipment 1"
|
||||
date="2025-10-10"
|
||||
product={{
|
||||
name: "rognons de veau",
|
||||
price: 1.20,
|
||||
priceKg: 10.9,
|
||||
unit: "piece"
|
||||
}}
|
||||
/>
|
||||
</Stack> */}
|
||||
</Flex>
|
||||
);
|
||||
}
|
||||
@@ -1,23 +1,25 @@
|
||||
import { Stack, Loader, Title, Group, ActionIcon, Flex, Tooltip } from "@mantine/core";
|
||||
import { createForm, createShipment, editForm, editShipment, getForms, type Form } from "../../services/api";
|
||||
import { t } from "../../config/i18n";
|
||||
import { Stack, Loader, Title, Group, ActionIcon, Tooltip, Table, ScrollArea } from "@mantine/core";
|
||||
import { createForm, createShipment, editForm, editShipment, getForms } from "@/services/api";
|
||||
import { t } from "@/config/i18n";
|
||||
import { useLocation, useNavigate, useSearchParams } from "react-router";
|
||||
import { IconPlus } from "@tabler/icons-react";
|
||||
import { useCallback, useMemo } from "react";
|
||||
import { FilterForms } from "../../components/Forms/FilterForms";
|
||||
import FormCard from "../../components/Forms/FormCard";
|
||||
import FormModal, { type FormInputs, type ShipmentInputs } from "../../components/Forms/FormModal";
|
||||
import FormModal from "@/components/Forms/Modal";
|
||||
import FormRow from "@/components/Forms/Row";
|
||||
import type { Form, FormInputs } from "@/services/resources/forms";
|
||||
import type { ShipmentEdit, ShipmentInputs } from "@/services/resources/shipments";
|
||||
import FilterForms from "@/components/Forms/Filter";
|
||||
|
||||
export function Forms() {
|
||||
const [ searchParams, setSearchParams ] = useSearchParams();
|
||||
const location = useLocation();
|
||||
const navigate = useNavigate();
|
||||
|
||||
const isCreate = location.pathname === "/form/create";
|
||||
const isCreate = location.pathname === "/dashboard/forms/create";
|
||||
const isEdit = location.pathname.includes("/edit");
|
||||
|
||||
const closeModal = () => {
|
||||
navigate("/forms");
|
||||
navigate("/dashboard/forms");
|
||||
};
|
||||
|
||||
const { isPending, data } = getForms(searchParams);
|
||||
@@ -43,7 +45,6 @@ export function Forms() {
|
||||
return;
|
||||
const newForm = await createFormMutation.mutateAsync({
|
||||
...form,
|
||||
shipment_ids: [],
|
||||
start: form?.start,
|
||||
end: form?.start,
|
||||
productor_id: Number(form.productor_id),
|
||||
@@ -61,50 +62,62 @@ export function Forms() {
|
||||
);
|
||||
});
|
||||
closeModal();
|
||||
}, []);
|
||||
}, [createFormMutation, createShipmentsMutation]);
|
||||
|
||||
const handleEditForm = useCallback(async (form: FormInputs, id?: number) => {
|
||||
if (!id)
|
||||
return;
|
||||
// edit all existing shipments
|
||||
// edit form
|
||||
form.shipments.filter(el => el.id).map(async (shipment) => {
|
||||
if (!shipment.name || !shipment.date || !shipment.form_id || !shipment.id)
|
||||
form.shipments
|
||||
.filter((el: ShipmentInputs) => el.id)
|
||||
.map(async (shipment: ShipmentInputs) => {
|
||||
if (
|
||||
!shipment.name ||
|
||||
!shipment.date ||
|
||||
!shipment.form_id ||
|
||||
!shipment.id
|
||||
)
|
||||
return
|
||||
const newShipment = {
|
||||
const newShipment: ShipmentEdit = {
|
||||
name: shipment.name,
|
||||
date: shipment.date,
|
||||
form_id: shipment.form_id,
|
||||
}
|
||||
await editShipmentsMutation.mutate({id: shipment.id, shipment: newShipment})
|
||||
};
|
||||
await editShipmentsMutation.mutate({
|
||||
id: shipment.id,
|
||||
shipment: newShipment
|
||||
});
|
||||
});
|
||||
const newForm = await editFormMutation.mutateAsync({
|
||||
id: id,
|
||||
form: {
|
||||
...form,
|
||||
shipment_ids: [],
|
||||
start: form.start,
|
||||
end: form.start,
|
||||
productor_id: Number(form.productor_id),
|
||||
referer_id: Number(form.referer_id)
|
||||
}
|
||||
});
|
||||
// if shipments to add -> create shipments
|
||||
form.shipments.filter(el => el.id === null).map(async (shipment) => {
|
||||
form.shipments
|
||||
.filter((el: ShipmentInputs) => el.id === null)
|
||||
.map(async (shipment: ShipmentInputs) => {
|
||||
if (!shipment.name || !shipment.date)
|
||||
return
|
||||
const newShipment = {
|
||||
name: shipment.name,
|
||||
date: shipment.date,
|
||||
}
|
||||
return await createShipmentsMutation.mutateAsync(
|
||||
{...newShipment, form_id: newForm.id}
|
||||
);
|
||||
return await createShipmentsMutation.mutateAsync({
|
||||
...newShipment,
|
||||
form_id: newForm.id,
|
||||
});
|
||||
});
|
||||
closeModal();
|
||||
}, []);
|
||||
}, [editShipmentsMutation, createShipmentsMutation, editFormMutation]);
|
||||
|
||||
const onFilterChange = useCallback((values: string[], filter: string) => {
|
||||
const onFilterChange = useCallback((
|
||||
values: string[],
|
||||
filter: string
|
||||
) => {
|
||||
setSearchParams(prev => {
|
||||
const params = new URLSearchParams(prev);
|
||||
params.delete(filter)
|
||||
@@ -121,21 +134,24 @@ export function Forms() {
|
||||
return (<Loader color="blue"/>);
|
||||
|
||||
return (
|
||||
<Stack w={{base: "100%", sm: "60%", lg: "60%"}}>
|
||||
<Stack>
|
||||
<Group justify="space-between">
|
||||
<Title order={1}>{t("All forms")}</Title>
|
||||
<Tooltip label={t("create new form")}>
|
||||
<Title order={2}>{t("all forms", {capfirst: true})}</Title>
|
||||
<Tooltip label={t("create new form", {capfirst: true})}>
|
||||
<ActionIcon
|
||||
size="xl"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
navigate(`/form/create`);
|
||||
navigate(`/dashboard/forms/create`);
|
||||
}}
|
||||
>
|
||||
<IconPlus/>
|
||||
</ActionIcon>
|
||||
</Tooltip>
|
||||
<FormModal opened={isCreate} onClose={closeModal} handleSubmit={handleCreateForm}/>
|
||||
<FormModal
|
||||
opened={isCreate}
|
||||
onClose={closeModal}
|
||||
handleSubmit={handleCreateForm}
|
||||
/>
|
||||
</Group>
|
||||
<FilterForms
|
||||
productors={productors || []}
|
||||
@@ -143,13 +159,41 @@ export function Forms() {
|
||||
filters={searchParams}
|
||||
onFilterChange={onFilterChange}
|
||||
/>
|
||||
<Flex gap="md" wrap="wrap" justify="center">
|
||||
<ScrollArea type="auto">
|
||||
<Table striped>
|
||||
<Table.Thead>
|
||||
<Table.Tr>
|
||||
<Table.Th>{t("name", {capfirst: true})}</Table.Th>
|
||||
<Table.Th>{t("type", {capfirst: true})}</Table.Th>
|
||||
<Table.Th>{t("start", {capfirst: true})}</Table.Th>
|
||||
<Table.Th>{t("end", {capfirst: true})}</Table.Th>
|
||||
<Table.Th>{t("productor", {capfirst: true})}</Table.Th>
|
||||
<Table.Th>{t("referer", {capfirst: true})}</Table.Th>
|
||||
<Table.Th>{t("actions", {capfirst: true})}</Table.Th>
|
||||
</Table.Tr>
|
||||
</Table.Thead>
|
||||
<Table.Tbody>
|
||||
{
|
||||
data.map((form) => (
|
||||
<FormRow
|
||||
form={form}
|
||||
isEdit={isEdit}
|
||||
closeModal={closeModal}
|
||||
handleSubmit={handleEditForm}
|
||||
/>
|
||||
))
|
||||
}
|
||||
</Table.Tbody>
|
||||
</Table>
|
||||
</ScrollArea>
|
||||
|
||||
{/* <Flex gap="md" wrap="wrap" justify="center">
|
||||
{
|
||||
data?.map((form: Form) => (
|
||||
<FormCard form={form} isEdit={isEdit} closeModal={closeModal} handleSubmit={handleEditForm}/>
|
||||
))
|
||||
}
|
||||
</Flex>
|
||||
</Flex> */}
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
@@ -1,8 +1,8 @@
|
||||
import { Text } from "@mantine/core";
|
||||
import { t } from "../../config/i18n";
|
||||
import { t } from "@/config/i18n";
|
||||
|
||||
export function Home() {
|
||||
return (
|
||||
<Text>{t("test")}</Text>
|
||||
<Text>{t("test", {capfirst: true})}</Text>
|
||||
);
|
||||
}
|
||||
127
frontend/src/pages/Productors/index.tsx
Normal file
127
frontend/src/pages/Productors/index.tsx
Normal file
@@ -0,0 +1,127 @@
|
||||
import { ActionIcon, Group, Loader, ScrollArea, Stack, Table, Title, Tooltip } from "@mantine/core";
|
||||
import { t } from "@/config/i18n";
|
||||
import { createProductor, editProductor, getProductors } from "@/services/api";
|
||||
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 } from "react";
|
||||
import type { Productor, ProductorInputs } from "@/services/resources/productors";
|
||||
import ProductorsFilters from "@/components/Productors/Filter";
|
||||
|
||||
export default function Productors() {
|
||||
const [ searchParams, setSearchParams ] = useSearchParams();
|
||||
const location = useLocation();
|
||||
const navigate = useNavigate();
|
||||
|
||||
const isCreate = location.pathname === "/dashboard/productors/create";
|
||||
const isEdit = location.pathname.includes("/edit");
|
||||
|
||||
const closeModal = () => {
|
||||
navigate("/dashboard/productors");
|
||||
};
|
||||
|
||||
const {data: productors, isPending} = getProductors(searchParams);
|
||||
const {data: allProductors } = getProductors();
|
||||
|
||||
const names = useMemo(() => {
|
||||
return allProductors?.map((productor: Productor) => (productor.name))
|
||||
.filter((season, index, array) => array.indexOf(season) === index)
|
||||
}, [allProductors])
|
||||
|
||||
const types = useMemo(() => {
|
||||
return allProductors?.map((productor: Productor) => (productor.type))
|
||||
.filter((productor, index, array) => array.indexOf(productor) === index)
|
||||
}, [allProductors])
|
||||
|
||||
const createProductorMutation = createProductor();
|
||||
const editProductorMutation = editProductor();
|
||||
|
||||
const handleCreateProductor = useCallback(async (productor: ProductorInputs) => {
|
||||
await createProductorMutation.mutateAsync({
|
||||
...productor
|
||||
});
|
||||
closeModal();
|
||||
}, [createProductorMutation]);
|
||||
|
||||
const handleEditProductor = useCallback(async (productor: ProductorInputs, id?: number) => {
|
||||
if (!id)
|
||||
return;
|
||||
await editProductorMutation.mutateAsync({
|
||||
id: id,
|
||||
productor: productor
|
||||
});
|
||||
closeModal();
|
||||
}, []);
|
||||
|
||||
const onFilterChange = useCallback((values: string[], filter: string) => {
|
||||
setSearchParams(prev => {
|
||||
const params = new URLSearchParams(prev);
|
||||
params.delete(filter)
|
||||
|
||||
values.forEach(value => {
|
||||
params.append(filter, value);
|
||||
});
|
||||
|
||||
return params;
|
||||
});
|
||||
}, [searchParams, setSearchParams])
|
||||
|
||||
if (!productors || isPending)
|
||||
return <Loader/>
|
||||
|
||||
return (
|
||||
<Stack>
|
||||
<Group justify="space-between">
|
||||
<Title order={2}>{t("all productors", {capfirst: true})}</Title>
|
||||
<Tooltip label={t("create productor", {capfirst: true})}>
|
||||
<ActionIcon
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
navigate(`/dashboard/productors/create`);
|
||||
}}
|
||||
>
|
||||
<IconPlus/>
|
||||
</ActionIcon>
|
||||
</Tooltip>
|
||||
<ProductorModal
|
||||
opened={isCreate}
|
||||
onClose={closeModal}
|
||||
handleSubmit={handleCreateProductor}
|
||||
/>
|
||||
</Group>
|
||||
<ProductorsFilters
|
||||
names={names || []}
|
||||
types={types || []}
|
||||
filters={searchParams}
|
||||
onFilterChange={onFilterChange}
|
||||
/>
|
||||
<ScrollArea type="auto">
|
||||
<Table striped>
|
||||
<Table.Thead>
|
||||
<Table.Tr>
|
||||
<Table.Th>{t("name", {capfirst: true})}</Table.Th>
|
||||
<Table.Th>{t("type", {capfirst: true})}</Table.Th>
|
||||
<Table.Th>{t("address", {capfirst: true})}</Table.Th>
|
||||
<Table.Th>{t("payment", {capfirst: true})}</Table.Th>
|
||||
<Table.Th>{t("actions", {capfirst: true})}</Table.Th>
|
||||
</Table.Tr>
|
||||
</Table.Thead>
|
||||
<Table.Tbody>
|
||||
{
|
||||
productors.map((productor) => (
|
||||
<ProductorRow
|
||||
productor={productor}
|
||||
isEdit={isEdit}
|
||||
closeModal={closeModal}
|
||||
handleSubmit={handleEditProductor}
|
||||
|
||||
/>
|
||||
))
|
||||
}
|
||||
</Table.Tbody>
|
||||
</Table>
|
||||
</ScrollArea>
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
129
frontend/src/pages/Products/index.tsx
Normal file
129
frontend/src/pages/Products/index.tsx
Normal file
@@ -0,0 +1,129 @@
|
||||
import { ActionIcon, Group, Loader, ScrollArea, Stack, Table, Title, Tooltip } from "@mantine/core";
|
||||
import { t } from "@/config/i18n";
|
||||
import { createProduct, editProduct, getProducts } from "@/services/api";
|
||||
import { IconPlus } from "@tabler/icons-react";
|
||||
import ProductRow from "@/components/Products/Row";
|
||||
import { useLocation, useNavigate, useSearchParams } from "react-router";
|
||||
import { ProductModal } from "@/components/Products/Modal";
|
||||
import { useCallback, useMemo } from "react";
|
||||
import type { Product, ProductInputs } from "@/services/resources/products";
|
||||
import ProductsFilters from "@/components/Products/Filter";
|
||||
|
||||
export default function Products() {
|
||||
const [ searchParams, setSearchParams ] = useSearchParams();
|
||||
const location = useLocation();
|
||||
const navigate = useNavigate();
|
||||
|
||||
const isCreate = location.pathname === "/dashboard/products/create";
|
||||
const isEdit = location.pathname.includes("/edit");
|
||||
|
||||
const closeModal = () => {
|
||||
navigate("/dashboard/products");
|
||||
};
|
||||
|
||||
const {data: products, isPending} = getProducts(searchParams);
|
||||
const {data: allProducts } = getProducts();
|
||||
|
||||
const names = useMemo(() => {
|
||||
return allProducts?.map((product: Product) => (product.name))
|
||||
.filter((season, index, array) => array.indexOf(season) === index)
|
||||
}, [allProducts])
|
||||
|
||||
const types = useMemo(() => {
|
||||
return allProducts?.map((product: Product) => (product.type))
|
||||
.filter((product, index, array) => array.indexOf(product) === index)
|
||||
}, [allProducts])
|
||||
|
||||
const createProductMutation = createProduct();
|
||||
const editProductMutation = editProduct();
|
||||
|
||||
const handleCreateProduct = useCallback(async (product: ProductInputs) => {
|
||||
await createProductMutation.mutateAsync({
|
||||
...product
|
||||
});
|
||||
closeModal();
|
||||
}, [createProductMutation]);
|
||||
|
||||
const handleEditProduct = useCallback(async (product: ProductInputs, id?: number) => {
|
||||
if (!id)
|
||||
return;
|
||||
await editProductMutation.mutateAsync({
|
||||
id: id,
|
||||
product: product
|
||||
});
|
||||
closeModal();
|
||||
}, []);
|
||||
|
||||
const onFilterChange = useCallback((values: string[], filter: string) => {
|
||||
setSearchParams(prev => {
|
||||
const params = new URLSearchParams(prev);
|
||||
params.delete(filter)
|
||||
|
||||
values.forEach(value => {
|
||||
params.append(filter, value);
|
||||
});
|
||||
|
||||
return params;
|
||||
});
|
||||
}, [searchParams, setSearchParams])
|
||||
|
||||
if (!products || isPending)
|
||||
return <Loader/>
|
||||
|
||||
return (
|
||||
<Stack>
|
||||
<Group justify="space-between">
|
||||
<Title order={2}>{t("all products", {capfirst: true})}</Title>
|
||||
<Tooltip label={t("create product", {capfirst: true})}>
|
||||
<ActionIcon
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
navigate(`/dashboard/products/create`);
|
||||
}}
|
||||
>
|
||||
<IconPlus/>
|
||||
</ActionIcon>
|
||||
</Tooltip>
|
||||
<ProductModal
|
||||
opened={isCreate}
|
||||
onClose={closeModal}
|
||||
handleSubmit={handleCreateProduct}
|
||||
/>
|
||||
</Group>
|
||||
<ProductsFilters
|
||||
names={names || []}
|
||||
types={types || []}
|
||||
filters={searchParams}
|
||||
onFilterChange={onFilterChange}
|
||||
/>
|
||||
<ScrollArea type="auto">
|
||||
<Table striped>
|
||||
<Table.Thead>
|
||||
<Table.Tr>
|
||||
<Table.Th>{t("name", {capfirst: true})}</Table.Th>
|
||||
<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("unit", {capfirst: true})}</Table.Th>
|
||||
<Table.Th>{t("actions", {capfirst: true})}</Table.Th>
|
||||
</Table.Tr>
|
||||
</Table.Thead>
|
||||
<Table.Tbody>
|
||||
{
|
||||
products.map((product) => (
|
||||
<ProductRow
|
||||
product={product}
|
||||
isEdit={isEdit}
|
||||
closeModal={closeModal}
|
||||
handleSubmit={handleEditProduct}
|
||||
|
||||
/>
|
||||
))
|
||||
}
|
||||
</Table.Tbody>
|
||||
</Table>
|
||||
</ScrollArea>
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
5
frontend/src/pages/Templates/index.tsx
Normal file
5
frontend/src/pages/Templates/index.tsx
Normal file
@@ -0,0 +1,5 @@
|
||||
export default function Templates() {
|
||||
return (
|
||||
<></>
|
||||
);
|
||||
}
|
||||
5
frontend/src/pages/Users/index.tsx
Normal file
5
frontend/src/pages/Users/index.tsx
Normal file
@@ -0,0 +1,5 @@
|
||||
export default function Users() {
|
||||
return (
|
||||
<></>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user