add products
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
from sqlmodel import Field, SQLModel, Relationship
|
||||
from enum import Enum
|
||||
from enum import StrEnum
|
||||
from typing import Optional
|
||||
import datetime
|
||||
|
||||
@@ -44,14 +44,14 @@ class ProductorUpdate(SQLModel):
|
||||
class ProductorCreate(ProductorBase):
|
||||
pass
|
||||
|
||||
class Unit(Enum):
|
||||
GRAMS = 1
|
||||
KILO = 2
|
||||
PIECE = 3
|
||||
class Unit(StrEnum):
|
||||
GRAMS = "1"
|
||||
KILO = "2"
|
||||
PIECE = "3"
|
||||
|
||||
class ProductType(Enum):
|
||||
PLANNED = 1
|
||||
RECCURENT = 2
|
||||
class ProductType(StrEnum):
|
||||
PLANNED = "1"
|
||||
RECCURENT = "2"
|
||||
|
||||
class ShipmentProductLink(SQLModel, table=True):
|
||||
shipment_id: Optional[int] = Field(default=None, foreign_key="shipment.id", primary_key=True)
|
||||
@@ -62,7 +62,7 @@ class ProductBase(SQLModel):
|
||||
unit: Unit
|
||||
price: float
|
||||
price_kg: float | None
|
||||
weight: float
|
||||
weight: float | None
|
||||
type: ProductType
|
||||
productor_id: int | None = Field(default=None, foreign_key="productor.id")
|
||||
|
||||
@@ -83,10 +83,10 @@ class ProductUpdate(SQLModel):
|
||||
price_kg: float | None
|
||||
weight: float | None
|
||||
productor_id: int | None
|
||||
shipment_ids: list[int] | None
|
||||
shipment_ids: list[int] | None = []
|
||||
|
||||
class ProductCreate(ProductBase):
|
||||
shipment_ids: list[int] | None
|
||||
shipment_ids: list[int] | None = []
|
||||
|
||||
class FormBase(SQLModel):
|
||||
name: str
|
||||
|
||||
@@ -4,9 +4,9 @@ import src.models as models
|
||||
def get_all(session: Session, names: list[str], types: list[str]) -> list[models.ProductorPublic]:
|
||||
statement = select(models.Productor)
|
||||
if len(names) > 0:
|
||||
statement.where(models.Productor.name.in_(names))
|
||||
statement = statement.where(models.Productor.name.in_(names))
|
||||
if len(types) > 0:
|
||||
statement.where(models.Productor.type.in_(types))
|
||||
statement = statement.where(models.Productor.type.in_(types))
|
||||
return session.exec(statement).all()
|
||||
|
||||
def get_one(session: Session, productor_id: int) -> models.ProductorPublic:
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
from fastapi import APIRouter, HTTPException, Depends
|
||||
from fastapi import APIRouter, HTTPException, Depends, Query
|
||||
import src.messages as messages
|
||||
import src.models as models
|
||||
from src.database import get_session
|
||||
@@ -8,8 +8,12 @@ from src.auth.auth import get_current_user
|
||||
router = APIRouter(prefix='/products')
|
||||
#user=Depends(get_current_user)
|
||||
@router.get('/', response_model=list[models.ProductPublic], )
|
||||
def get_products(session: Session = Depends(get_session)):
|
||||
return service.get_all(session)
|
||||
def get_products(
|
||||
session: Session = Depends(get_session),
|
||||
names: list[str] = Query([]),
|
||||
productors: list[str] = Query([]),
|
||||
):
|
||||
return service.get_all(session, names, productors)
|
||||
|
||||
@router.get('/{id}', response_model=models.ProductPublic)
|
||||
def get_product(id: int, session: Session = Depends(get_session)):
|
||||
|
||||
@@ -1,8 +1,16 @@
|
||||
from sqlmodel import Session, select
|
||||
import src.models as models
|
||||
|
||||
def get_all(session: Session) -> list[models.ProductPublic]:
|
||||
def get_all(
|
||||
session: Session,
|
||||
names: list[str],
|
||||
productors: list[str]
|
||||
) -> list[models.ProductPublic]:
|
||||
statement = select(models.Product)
|
||||
if len(names) > 0:
|
||||
statement = statement.where(models.Product.name.in_(names))
|
||||
if len(productors) > 0:
|
||||
statement = statement.join(models.Productor).where(models.Productor.name.in_(productors))
|
||||
return session.exec(statement).all()
|
||||
|
||||
def get_one(session: Session, product_id: int) -> models.ProductPublic:
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
/>
|
||||
|
||||
@@ -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)
|
||||
}}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -62,7 +62,6 @@ export default function Productors() {
|
||||
values.forEach(value => {
|
||||
params.append(filter, value);
|
||||
});
|
||||
|
||||
return params;
|
||||
});
|
||||
}, [searchParams, setSearchParams])
|
||||
|
||||
@@ -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}
|
||||
/>
|
||||
|
||||
@@ -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!,
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user