add products

This commit is contained in:
Julien Aldon
2026-02-12 17:39:53 +01:00
parent 025b78d5dd
commit 1a98957466
12 changed files with 129 additions and 75 deletions

View File

@@ -16,10 +16,10 @@ export default function ProductorsFilter({
onFilterChange
}: ProductorsFiltersProps) {
const defaultNames = useMemo(() => {
return filters.getAll("name")
return filters.getAll("names")
}, [filters]);
const defaultTypes = useMemo(() => {
return filters.getAll("type")
return filters.getAll("types")
}, [filters]);
return (
<Group>

View File

@@ -4,22 +4,22 @@ import { t } from "@/config/i18n";
export type ProductsFiltersProps = {
names: string[];
types: string[];
productors: string[];
filters: URLSearchParams;
onFilterChange: (values: string[], filter: string) => void;
}
export default function ProductsFilters({
names,
types,
productors,
filters,
onFilterChange
}: ProductsFiltersProps) {
const defaultNames = useMemo(() => {
return filters.getAll("name")
return filters.getAll("names")
}, [filters]);
const defaultTypes = useMemo(() => {
return filters.getAll("type")
const defaultProductors = useMemo(() => {
return filters.getAll("productors")
}, [filters]);
return (
<Group>
@@ -34,12 +34,12 @@ export default function ProductsFilters({
clearable
/>
<MultiSelect
aria-label={t("filter by type", {capfirst: true})}
placeholder={t("filter by type", {capfirst: true})}
data={types}
defaultValue={defaultTypes}
aria-label={t("filter by productor", {capfirst: true})}
placeholder={t("filter by productor", {capfirst: true})}
data={productors}
defaultValue={defaultProductors}
onChange={(values: string[]) => {
onFilterChange(values, 'types')
onFilterChange(values, 'productors')
}}
clearable
/>

View File

@@ -2,7 +2,7 @@ import { Button, Group, Modal, NumberInput, Select, TextInput, Title, type Modal
import { t } from "@/config/i18n";
import { useForm } from "@mantine/form";
import { IconCancel } from "@tabler/icons-react";
import type { Product, ProductInputs } from "@/services/resources/products";
import { productToProductInputs, type Product, type ProductInputs } from "@/services/resources/products";
import { useEffect, useMemo } from "react";
import { getProductors } from "@/services/api";
@@ -23,9 +23,9 @@ export function ProductModal({
name: "",
unit: null,
price: null,
priceKg: null,
price_kg: null,
weight: null,
type: "",
type: null,
productor_id: null,
},
validate: {
@@ -35,10 +35,6 @@ export function ProductModal({
!value ? `${t("unit", {capfirst: true})} ${t('is required')}` : null,
price: (value) =>
!value ? `${t("price", {capfirst: true})} ${t('is required')}` : null,
priceKg: (value) =>
!value ? `${t("priceKg", {capfirst: true})} ${t('is required')}` : null,
weight: (value) =>
!value ? `${t("weight", {capfirst: true})} ${t('is required')}` : null,
type: (value) =>
!value ? `${t("type", {capfirst: true})} ${t('is required')}` : null,
productor_id: (value) =>
@@ -48,9 +44,7 @@ export function ProductModal({
useEffect(() => {
if (currentProduct) {
form.initialize({
...currentProduct,
});
form.initialize(productToProductInputs(currentProduct));
}
}, [currentProduct]);
@@ -73,13 +67,27 @@ export function ProductModal({
withAsterisk
{...form.getInputProps('name')}
/>
<TextInput
label={t("product type", {capfirst: true}, {capfirst: true}, {capfirst: true})}
placeholder={t("product type", {capfirst: true}, {capfirst: true}, {capfirst: true})}
<Select
label={t("product type", {capfirst: true})}
placeholder={t("product type", {capfirst: true})}
radius="sm"
withAsterisk
data={[
{value: "1", label: t("planned")},
{value: "2", label: t("reccurent")}
]}
{...form.getInputProps('type')}
/>
<Select
label={t("product unit", {capfirst: true})}
placeholder={t("product unit", {capfirst: true})}
radius="sm"
data={[
{value: "1", label: t("grams")},
{value: "2", label: t("kilo")},
{value: "3", label: t("piece")}
]}
{...form.getInputProps('unit')}
/>
<NumberInput
label={t("product price", {capfirst: true})}
placeholder={t("product price", {capfirst: true})}
@@ -88,17 +96,15 @@ export function ProductModal({
{...form.getInputProps('price')}
/>
<NumberInput
label={t("product priceKg", {capfirst: true}, {capfirst: true})}
placeholder={t("product priceKg", {capfirst: true}, {capfirst: true})}
label={t("product priceKg", {capfirst: true})}
placeholder={t("product priceKg", {capfirst: true})}
radius="sm"
withAsterisk
{...form.getInputProps('priceKg')}
{...form.getInputProps('price_kg')}
/>
<NumberInput
label={t("product weight", {capfirst: true})}
placeholder={t("product weight", {capfirst: true})}
radius="sm"
withAsterisk
{...form.getInputProps('weight', {capfirst: true})}
/>
<Select
@@ -129,6 +135,7 @@ export function ProductModal({
aria-label={currentProduct ? t("edit product", {capfirst: true}) : t('create product', {capfirst: true})}
onClick={() => {
form.validate();
console.log(form.isValid(), form.getValues())
if (form.isValid())
handleSubmit(form.getValues(), currentProduct?.id)
}}

View File

@@ -1,7 +1,7 @@
import { ActionIcon, Table, Tooltip } from "@mantine/core";
import { t } from "@/config/i18n";
import { IconEdit, IconX } from "@tabler/icons-react";
import type { Product, ProductInputs } from "@/services/resources/products";
import { ProductType, ProductUnit, type Product, type ProductInputs } from "@/services/resources/products";
import { ProductModal } from "@/components/Products/Modal";
import { deleteProduct, getProduct } from "@/services/api";
import { useNavigate } from "react-router";
@@ -32,11 +32,11 @@ export default function ProductRow({
handleSubmit={handleSubmit}
/>
<Table.Td>{product.name}</Table.Td>
<Table.Td>{product.type}</Table.Td>
<Table.Td>{ProductType[product.type]}</Table.Td>
<Table.Td>{product.price}</Table.Td>
<Table.Td>{product.priceKg}</Table.Td>
<Table.Td>{product.price_kg}</Table.Td>
<Table.Td>{product.weight}</Table.Td>
<Table.Td>{product.unit}</Table.Td>
<Table.Td>{ProductUnit[product.unit]}</Table.Td>
<Table.Td>
<Tooltip label={t("edit product", {capfirst: true})}>
<ActionIcon

View File

@@ -1,18 +1,18 @@
import { Tabs } from "@mantine/core";
import { t } from "@/config/i18n";
import { Outlet, useNavigate, useParams } from "react-router";
import { Outlet, useLocation, useNavigate } from "react-router";
export default function Dashboard() {
const navigate = useNavigate();
const { tabValue } = useParams();
const location = useLocation();
return (
<Tabs
w={{base: "100%", md: "80%", lg: "60%"}}
keepMounted={false}
defaultValue="productors"
orientation={"horizontal"}
value={tabValue}
value={location.pathname.split('/')[2]}
defaultValue={location.pathname.split('/')[2]}
onChange={(value) => navigate(`/dashboard/${value}`)}
>
<Tabs.List>

View File

@@ -62,7 +62,6 @@ export default function Productors() {
values.forEach(value => {
params.append(filter, value);
});
return params;
});
}, [searchParams, setSearchParams])

View File

@@ -6,7 +6,7 @@ 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 { productCreateFromProductInputs, type Product, type ProductInputs } from "@/services/resources/products";
import ProductsFilters from "@/components/Products/Filter";
export default function Products() {
@@ -28,19 +28,17 @@ export default function Products() {
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)
const productors = useMemo(() => {
return allProducts?.map((form: Product) => (form.productor.name))
.filter((productor, index, array) => array.indexOf(productor) === index)
}, [allProducts])
const createProductMutation = createProduct();
const editProductMutation = editProduct();
const handleCreateProduct = useCallback(async (product: ProductInputs) => {
await createProductMutation.mutateAsync({
...product
});
await createProductMutation.mutateAsync(productCreateFromProductInputs(product));
closeModal();
}, [createProductMutation]);
@@ -57,7 +55,7 @@ export default function Products() {
const onFilterChange = useCallback((values: string[], filter: string) => {
setSearchParams(prev => {
const params = new URLSearchParams(prev);
params.delete(filter)
params.delete(filter);
values.forEach(value => {
params.append(filter, value);
@@ -91,8 +89,8 @@ export default function Products() {
/>
</Group>
<ProductsFilters
productors = {productors || []}
names={names || []}
types={types || []}
filters={searchParams}
onFilterChange={onFilterChange}
/>

View File

@@ -1,34 +1,48 @@
import { t } from "@/config/i18n";
import type { Productor } from "@/services/resources/productors";
import type { Shipment } from "@/services/resources/shipments";
export const ProductType = [
"none",
t("planned"),
t("reccurent"),
];
export const ProductUnit = [
"none",
t("grams"),
t("kilo"),
t("piece"),
];
export type Product = {
id: number;
productor: Productor;
name: string;
unit: number;
price: number;
priceKg: number | null;
price_kg: number | null;
weight: number;
type: number;
shipments: Shipment[];
}
export type ProductCreate = {
productor_id: Productor;
productor_id: number;
name: string;
unit: number;
price: number;
priceKg: number | null;
weight: number;
price_kg: number | null;
weight: number | null;
type: number;
}
export type ProductEdit = {
productor_id: Productor | null;
productor_id: number | null;
name: string | null;
unit: number | null;
price: number | null;
priceKg: number | null;
price_kg: number | null;
weight: number | null;
type: number | null;
}
@@ -38,12 +52,36 @@ export type ProductInputs = {
name: string;
unit: number | null;
price: number | null;
priceKg: number | null;
price_kg: number | null;
weight: number | null;
type: string;
type: number | null;
}
export type ProductEditPayload = {
product: ProductEdit;
id: number;
}
export function productToProductInputs(product: Product): ProductInputs {
return {
productor_id: product.productor.id,
name: product.name,
unit: product.unit,
price: product.price,
price_kg: product.price_kg,
weight: product.weight,
type: product.type,
};
}
export function productCreateFromProductInputs(productInput: ProductInputs): ProductCreate {
return {
productor_id: productInput.productor_id!,
name: productInput.name,
unit: productInput.unit!,
price: productInput.price!,
price_kg: productInput.price_kg,
weight: productInput.weight,
type: productInput.type!,
}
}