add i18n, products, productors and forms as tables

This commit is contained in:
2026-02-12 00:30:28 +01:00
parent 1813e2893e
commit 025b78d5dd
43 changed files with 1623 additions and 527 deletions

View 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>
);
}

View File

@@ -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>
);
}

View File

@@ -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>
);
}

View File

@@ -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>
);
}

View 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>
);
}

View 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>
);
}

View File

@@ -0,0 +1,5 @@
export default function Templates() {
return (
<></>
);
}

View File

@@ -0,0 +1,5 @@
export default function Users() {
return (
<></>
);
}