add contract page with dynamic form elements
This commit is contained in:
@@ -58,7 +58,6 @@ def callback(code: str, session: Session = Depends(get_session)):
|
|||||||
email=decoded_token.get("email"),
|
email=decoded_token.get("email"),
|
||||||
name=decoded_token.get("preferred_username")
|
name=decoded_token.get("preferred_username")
|
||||||
)
|
)
|
||||||
print(user_create)
|
|
||||||
user = service.get_or_create_user(session, user_create)
|
user = service.get_or_create_user(session, user_create)
|
||||||
return {
|
return {
|
||||||
"access_token": token_data["access_token"],
|
"access_token": token_data["access_token"],
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ def get_all(
|
|||||||
statement = statement.where(models.Form.season.in_(seasons))
|
statement = statement.where(models.Form.season.in_(seasons))
|
||||||
if len(productors) > 0:
|
if len(productors) > 0:
|
||||||
statement = statement.join(models.Productor).where(models.Productor.name.in_(productors))
|
statement = statement.join(models.Productor).where(models.Productor.name.in_(productors))
|
||||||
return session.exec(statement).all()
|
return session.exec(statement.order_by(models.Form.name)).all()
|
||||||
|
|
||||||
def get_one(session: Session, form_id: int) -> models.FormPublic:
|
def get_one(session: Session, form_id: int) -> models.FormPublic:
|
||||||
return session.get(models.Form, form_id)
|
return session.get(models.Form, form_id)
|
||||||
|
|||||||
@@ -33,7 +33,12 @@ class ProductorPublic(ProductorBase):
|
|||||||
class Productor(ProductorBase, table=True):
|
class Productor(ProductorBase, table=True):
|
||||||
id: int | None = Field(default=None, primary_key=True)
|
id: int | None = Field(default=None, primary_key=True)
|
||||||
|
|
||||||
products: list["Product"] = Relationship(back_populates='productor')
|
products: list["Product"] = Relationship(
|
||||||
|
back_populates='productor',
|
||||||
|
sa_relationship_kwargs={
|
||||||
|
"order_by": "Product.name"
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
class ProductorUpdate(SQLModel):
|
class ProductorUpdate(SQLModel):
|
||||||
name: str | None
|
name: str | None
|
||||||
@@ -60,9 +65,10 @@ class ShipmentProductLink(SQLModel, table=True):
|
|||||||
class ProductBase(SQLModel):
|
class ProductBase(SQLModel):
|
||||||
name: str
|
name: str
|
||||||
unit: Unit
|
unit: Unit
|
||||||
price: float
|
price: float | None
|
||||||
price_kg: float | None
|
price_kg: float | None
|
||||||
weight: float | None
|
quantity: float | None
|
||||||
|
quantity_unit: str | None
|
||||||
type: ProductType
|
type: ProductType
|
||||||
productor_id: int | None = Field(default=None, foreign_key="productor.id")
|
productor_id: int | None = Field(default=None, foreign_key="productor.id")
|
||||||
|
|
||||||
@@ -81,12 +87,13 @@ class ProductUpdate(SQLModel):
|
|||||||
unit: Unit | None
|
unit: Unit | None
|
||||||
price: float | None
|
price: float | None
|
||||||
price_kg: float | None
|
price_kg: float | None
|
||||||
weight: float | None
|
quantity: float | None
|
||||||
|
quantity_unit: str | None
|
||||||
productor_id: int | None
|
productor_id: int | None
|
||||||
shipment_ids: list[int] | None = []
|
type: ProductType | None
|
||||||
|
|
||||||
class ProductCreate(ProductBase):
|
class ProductCreate(ProductBase):
|
||||||
shipment_ids: list[int] | None = []
|
pass
|
||||||
|
|
||||||
class FormBase(SQLModel):
|
class FormBase(SQLModel):
|
||||||
name: str
|
name: str
|
||||||
@@ -106,7 +113,13 @@ class Form(FormBase, table=True):
|
|||||||
id: int | None = Field(default=None, primary_key=True)
|
id: int | None = Field(default=None, primary_key=True)
|
||||||
productor: Optional['Productor'] = Relationship()
|
productor: Optional['Productor'] = Relationship()
|
||||||
referer: Optional['User'] = Relationship()
|
referer: Optional['User'] = Relationship()
|
||||||
shipments: list["Shipment"] = Relationship(back_populates="form", cascade_delete=True)
|
shipments: list["Shipment"] = Relationship(
|
||||||
|
back_populates="form",
|
||||||
|
cascade_delete=True,
|
||||||
|
sa_relationship_kwargs={
|
||||||
|
"order_by": "Shipment.name"
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
class FormUpdate(SQLModel):
|
class FormUpdate(SQLModel):
|
||||||
name: str | None
|
name: str | None
|
||||||
@@ -161,7 +174,13 @@ class ShipmentPublic(ShipmentBase):
|
|||||||
|
|
||||||
class Shipment(ShipmentBase, table=True):
|
class Shipment(ShipmentBase, table=True):
|
||||||
id: int | None = Field(default=None, primary_key=True)
|
id: int | None = Field(default=None, primary_key=True)
|
||||||
products: list[Product] = Relationship(back_populates="shipments", link_model=ShipmentProductLink)
|
products: list[Product] = Relationship(
|
||||||
|
back_populates="shipments",
|
||||||
|
link_model=ShipmentProductLink,
|
||||||
|
sa_relationship_kwargs={
|
||||||
|
"order_by": "Product.name"
|
||||||
|
},
|
||||||
|
)
|
||||||
form: Optional[Form] = Relationship(back_populates="shipments")
|
form: Optional[Form] = Relationship(back_populates="shipments")
|
||||||
|
|
||||||
class ShipmentUpdate(SQLModel):
|
class ShipmentUpdate(SQLModel):
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ def get_all(session: Session, names: list[str], types: list[str]) -> list[models
|
|||||||
statement = statement.where(models.Productor.name.in_(names))
|
statement = statement.where(models.Productor.name.in_(names))
|
||||||
if len(types) > 0:
|
if len(types) > 0:
|
||||||
statement = statement.where(models.Productor.type.in_(types))
|
statement = statement.where(models.Productor.type.in_(types))
|
||||||
return session.exec(statement).all()
|
return session.exec(statement.order_by(models.Productor.name)).all()
|
||||||
|
|
||||||
def get_one(session: Session, productor_id: int) -> models.ProductorPublic:
|
def get_one(session: Session, productor_id: int) -> models.ProductorPublic:
|
||||||
return session.get(models.Productor, productor_id)
|
return session.get(models.Productor, productor_id)
|
||||||
|
|||||||
@@ -11,9 +11,10 @@ router = APIRouter(prefix='/products')
|
|||||||
def get_products(
|
def get_products(
|
||||||
session: Session = Depends(get_session),
|
session: Session = Depends(get_session),
|
||||||
names: list[str] = Query([]),
|
names: list[str] = Query([]),
|
||||||
|
types: list[str] = Query([]),
|
||||||
productors: list[str] = Query([]),
|
productors: list[str] = Query([]),
|
||||||
):
|
):
|
||||||
return service.get_all(session, names, productors)
|
return service.get_all(session, names, productors, types)
|
||||||
|
|
||||||
@router.get('/{id}', response_model=models.ProductPublic)
|
@router.get('/{id}', response_model=models.ProductPublic)
|
||||||
def get_product(id: int, session: Session = Depends(get_session)):
|
def get_product(id: int, session: Session = Depends(get_session)):
|
||||||
|
|||||||
@@ -4,23 +4,25 @@ import src.models as models
|
|||||||
def get_all(
|
def get_all(
|
||||||
session: Session,
|
session: Session,
|
||||||
names: list[str],
|
names: list[str],
|
||||||
productors: list[str]
|
productors: list[str],
|
||||||
|
types: list[str],
|
||||||
|
|
||||||
) -> list[models.ProductPublic]:
|
) -> list[models.ProductPublic]:
|
||||||
statement = select(models.Product)
|
statement = select(models.Product)
|
||||||
if len(names) > 0:
|
if len(names) > 0:
|
||||||
statement = statement.where(models.Product.name.in_(names))
|
statement = statement.where(models.Product.name.in_(names))
|
||||||
if len(productors) > 0:
|
if len(productors) > 0:
|
||||||
statement = statement.join(models.Productor).where(models.Productor.name.in_(productors))
|
statement = statement.join(models.Productor).where(models.Productor.name.in_(productors))
|
||||||
return session.exec(statement).all()
|
if len(types) > 0:
|
||||||
|
statement = statement.where(models.Product.type.in_(types))
|
||||||
|
return session.exec(statement.order_by(models.Product.name)).all()
|
||||||
|
|
||||||
def get_one(session: Session, product_id: int) -> models.ProductPublic:
|
def get_one(session: Session, product_id: int) -> models.ProductPublic:
|
||||||
return session.get(models.Product, product_id)
|
return session.get(models.Product, product_id)
|
||||||
|
|
||||||
def create_one(session: Session, product: models.ProductCreate) -> models.ProductPublic:
|
def create_one(session: Session, product: models.ProductCreate) -> models.ProductPublic:
|
||||||
shipments = session.exec(select(models.Shipment).where(models.Shipment.id.in_(product.shipment_ids))).all()
|
product_create = product.model_dump(exclude_unset=True)
|
||||||
|
new_product = models.Product(**product_create)
|
||||||
product_create = product.model_dump(exclude_unset=True, exclude={'shipment_ids'})
|
|
||||||
new_product = models.Product(**product_create, shipments=shipments)
|
|
||||||
session.add(new_product)
|
session.add(new_product)
|
||||||
session.commit()
|
session.commit()
|
||||||
session.refresh(new_product)
|
session.refresh(new_product)
|
||||||
@@ -32,13 +34,7 @@ def update_one(session: Session, id: int, product: models.ProductUpdate) -> mode
|
|||||||
new_product = result.first()
|
new_product = result.first()
|
||||||
if not new_product:
|
if not new_product:
|
||||||
return None
|
return None
|
||||||
|
product_updates = product.model_dump(exclude_unset=True)
|
||||||
shipments_to_add = session.exec(select(models.Shipment).where(models.Shipment.id.in_(product.shipment_ids))).all()
|
|
||||||
new_product.shipments.clear()
|
|
||||||
for add in shipments_to_add:
|
|
||||||
new_product.shipments.append(add)
|
|
||||||
|
|
||||||
product_updates = product.model_dump(exclude_unset=True, exclude={"shipment_ids"})
|
|
||||||
for key, value in product_updates.items():
|
for key, value in product_updates.items():
|
||||||
setattr(new_product, key, value)
|
setattr(new_product, key, value)
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import src.models as models
|
|||||||
|
|
||||||
def get_all(session: Session) -> list[models.ShipmentPublic]:
|
def get_all(session: Session) -> list[models.ShipmentPublic]:
|
||||||
statement = select(models.Shipment)
|
statement = select(models.Shipment)
|
||||||
return session.exec(statement).all()
|
return session.exec(statement.order_by(models.Shipment.name)).all()
|
||||||
|
|
||||||
def get_one(session: Session, shipment_id: int) -> models.ShipmentPublic:
|
def get_one(session: Session, shipment_id: int) -> models.ShipmentPublic:
|
||||||
return session.get(models.Shipment, shipment_id)
|
return session.get(models.Shipment, shipment_id)
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import src.models as models
|
|||||||
|
|
||||||
def get_all(session: Session) -> list[models.TemplatePublic]:
|
def get_all(session: Session) -> list[models.TemplatePublic]:
|
||||||
statement = select(models.Template)
|
statement = select(models.Template)
|
||||||
return session.exec(statement).all()
|
return session.exec(statement.order_by(models.Template.name)).all()
|
||||||
|
|
||||||
def get_one(session: Session, template_id: int) -> models.TemplatePublic:
|
def get_one(session: Session, template_id: int) -> models.TemplatePublic:
|
||||||
return session.get(models.Template, template_id)
|
return session.get(models.Template, template_id)
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import src.models as models
|
|||||||
|
|
||||||
def get_all(session: Session) -> list[models.UserPublic]:
|
def get_all(session: Session) -> list[models.UserPublic]:
|
||||||
statement = select(models.User)
|
statement = select(models.User)
|
||||||
return session.exec(statement).all()
|
return session.exec(statement.order_by(models.User.name)).all()
|
||||||
|
|
||||||
def get_one(session: Session, user_id: int) -> models.UserPublic:
|
def get_one(session: Session, user_id: int) -> models.UserPublic:
|
||||||
return session.get(models.User, user_id)
|
return session.get(models.User, user_id)
|
||||||
|
|||||||
@@ -1,10 +1,15 @@
|
|||||||
{
|
{
|
||||||
"product name": "product name",
|
"product name": "product name",
|
||||||
"product price": "product price",
|
"product price": "product price",
|
||||||
"product weight": "product weight",
|
"product quantity": "product quantity",
|
||||||
|
"product quantity unit": "product quantity unit",
|
||||||
"product type": "product type",
|
"product type": "product type",
|
||||||
"planned": "planned",
|
"planned": "planned",
|
||||||
"reccurent": "reccurent",
|
"planned products": "planned products",
|
||||||
|
"select products per shipment": "select products per shipment.",
|
||||||
|
"recurrent products": "recurrent products",
|
||||||
|
"your selection in this category will apply for all shipments": "your selection in this category will apply for all shipments.",
|
||||||
|
"recurrent": "recurrent",
|
||||||
"product price kg": "product price kg",
|
"product price kg": "product price kg",
|
||||||
"product unit": "product unit",
|
"product unit": "product unit",
|
||||||
"grams": "grams",
|
"grams": "grams",
|
||||||
@@ -19,7 +24,8 @@
|
|||||||
"referer": "referer",
|
"referer": "referer",
|
||||||
"edit form": "edit form",
|
"edit form": "edit form",
|
||||||
"form name": "form name",
|
"form name": "form name",
|
||||||
"contact season": "contact season",
|
"contract season": "contract season",
|
||||||
|
"contract season recommandation": "recommandation : <Season>-<year> (example: Winter-2025)",
|
||||||
"start date": "start date",
|
"start date": "start date",
|
||||||
"end date": "end date",
|
"end date": "end date",
|
||||||
"nothing found": "nothing found",
|
"nothing found": "nothing found",
|
||||||
@@ -41,13 +47,19 @@
|
|||||||
"productor address": "productor address",
|
"productor address": "productor address",
|
||||||
"productor payment": "productor payment",
|
"productor payment": "productor payment",
|
||||||
"priceKg": "priceKg",
|
"priceKg": "priceKg",
|
||||||
"weight": "weight",
|
"quantity": "quantity",
|
||||||
|
"quantity unit": "quantity unit",
|
||||||
|
"unit": "sell unit",
|
||||||
"price": "price",
|
"price": "price",
|
||||||
"create product": "create product",
|
"create product": "create product",
|
||||||
"informations": "informations",
|
"informations": "informations",
|
||||||
"remove product": "remove product",
|
"remove product": "remove product",
|
||||||
"edit product": "edit product",
|
"edit product": "edit product",
|
||||||
"shipment name": "shipment name",
|
"shipment name": "shipment name",
|
||||||
|
"shipments": "shipments",
|
||||||
|
"shipment": "shipment",
|
||||||
|
"there is": "there is",
|
||||||
|
"for this contract": "for this contact.",
|
||||||
"shipment date": "shipment date",
|
"shipment date": "shipment date",
|
||||||
"remove shipment": "remove shipment",
|
"remove shipment": "remove shipment",
|
||||||
"productors": "productors",
|
"productors": "productors",
|
||||||
@@ -65,5 +77,11 @@
|
|||||||
"a start date": "a start date",
|
"a start date": "a start date",
|
||||||
"a end date": "a end date",
|
"a end date": "a end date",
|
||||||
"a productor": "a productor",
|
"a productor": "a productor",
|
||||||
"a referer": "a referer"
|
"a referer": "a referer",
|
||||||
|
"a phone": "a phone",
|
||||||
|
"a fistname": "a fistname",
|
||||||
|
"a lastname": "a lastname",
|
||||||
|
"a email": "a email",
|
||||||
|
"submit contract": "submit contract",
|
||||||
|
"all theses informations are for contract generation, no informations is stored outside of contracts": "all theses informations are for contract generation, no informations is stored outside of contracts."
|
||||||
}
|
}
|
||||||
@@ -1,15 +1,21 @@
|
|||||||
{
|
{
|
||||||
"product name": "nom du produit",
|
"product name": "nom du produit",
|
||||||
"product price": "prix du produit",
|
"product price": "prix du produit",
|
||||||
"product weight": "poids du produit",
|
"product quantity": "quantité du produit",
|
||||||
"product type": "type de produit",
|
"product type": "type de produit",
|
||||||
"planned": "planifié",
|
"planned": "planifié",
|
||||||
"reccurent": "récurrent",
|
"planned products": "Produits planifiés par livraison",
|
||||||
|
"select products per shipment": "Selectionnez les produits pour chaque livraison.",
|
||||||
|
"recurrent": "récurrent",
|
||||||
|
"recurrent products": "Produits récurents",
|
||||||
|
"your selection in this category will apply for all shipments": "votre selection sera appliquée pour chaque livraisons (Exemple: 6 livraisons, le produits sera comptés 6 fois : une fois par livraison).",
|
||||||
"product price kg": "prix du produit au Kilo",
|
"product price kg": "prix du produit au Kilo",
|
||||||
"product unit": "unité de vente du produit",
|
"product unit": "unité de vente du produit",
|
||||||
"grams": "grammes",
|
"grams": "grammes",
|
||||||
"kilo": "kilo",
|
"kilo": "kilo",
|
||||||
"piece": "pièce",
|
"piece": "pièce",
|
||||||
|
"in": "en",
|
||||||
|
"enter quantity": "entrez la quantitée",
|
||||||
"filter by season": "filtrer par saisons",
|
"filter by season": "filtrer par saisons",
|
||||||
"name": "nom",
|
"name": "nom",
|
||||||
"season": "saison",
|
"season": "saison",
|
||||||
@@ -19,7 +25,8 @@
|
|||||||
"referer": "référent·e",
|
"referer": "référent·e",
|
||||||
"edit form": "modifier le formulaire de contrat",
|
"edit form": "modifier le formulaire de contrat",
|
||||||
"form name": "nom du formulaire de contrat",
|
"form name": "nom du formulaire de contrat",
|
||||||
"contact season": "saison du contrat",
|
"contract season": "saison du contrat",
|
||||||
|
"contract season recommandation": "recommandation : <Saison>-<année> (Exemple: Hiver-2025)",
|
||||||
"start date": "date de début",
|
"start date": "date de début",
|
||||||
"end date": "date de fin",
|
"end date": "date de fin",
|
||||||
"nothing found": "rien à afficher",
|
"nothing found": "rien à afficher",
|
||||||
@@ -35,13 +42,15 @@
|
|||||||
"address": "adresse",
|
"address": "adresse",
|
||||||
"payment": "ordre du chèque",
|
"payment": "ordre du chèque",
|
||||||
"type": "type",
|
"type": "type",
|
||||||
"create productor": "créer le producteur·troce",
|
"create productor": "créer le producteur·trice",
|
||||||
"productor name": "nom du producteur·trice",
|
"productor name": "nom du producteur·trice",
|
||||||
"productor type": "type du producteur·trice",
|
"productor type": "type du producteur·trice",
|
||||||
"productor address": "adresse du producteur·trice",
|
"productor address": "adresse du producteur·trice",
|
||||||
"productor payment": "ordre du chèque du producteur·trice",
|
"productor payment": "ordre du chèque du producteur·trice",
|
||||||
"priceKg": "prix au kilo",
|
"priceKg": "prix au kilo",
|
||||||
"weight": "poids",
|
"quantity": "quantité",
|
||||||
|
"quantity unit": "unité de quantité",
|
||||||
|
"unit": "Unité de vente",
|
||||||
"price": "prix",
|
"price": "prix",
|
||||||
"create product": "créer le produit",
|
"create product": "créer le produit",
|
||||||
"informations": "informations",
|
"informations": "informations",
|
||||||
@@ -49,6 +58,10 @@
|
|||||||
"edit product": "modifier le produit",
|
"edit product": "modifier le produit",
|
||||||
"shipment name": "nom de la livraison",
|
"shipment name": "nom de la livraison",
|
||||||
"shipment date": "date de la livraison",
|
"shipment date": "date de la livraison",
|
||||||
|
"shipments": "livraisons",
|
||||||
|
"shipment": "livraison",
|
||||||
|
"there is": "il y a",
|
||||||
|
"for this contract": "pour ce contrat.",
|
||||||
"remove shipment": "supprimer la livraison",
|
"remove shipment": "supprimer la livraison",
|
||||||
"productors": "producteur·trices",
|
"productors": "producteur·trices",
|
||||||
"products": "produits",
|
"products": "produits",
|
||||||
@@ -66,5 +79,11 @@
|
|||||||
"a start date": "une date de début",
|
"a start date": "une date de début",
|
||||||
"a end date": "une date de fin",
|
"a end date": "une date de fin",
|
||||||
"a productor": "un(e) producteur·trice",
|
"a productor": "un(e) producteur·trice",
|
||||||
"a referer": "un référent·e"
|
"a referer": "un référent·e",
|
||||||
|
"a phone": "un numéro de téléphone",
|
||||||
|
"a fistname": "un prénom",
|
||||||
|
"a lastname": "un nom",
|
||||||
|
"a email": "une adresse email",
|
||||||
|
"submit contract": "Envoyer le contrat",
|
||||||
|
"all theses informations are for contract generation, no informations is stored outside of contracts": "ces informations sont nécéssaires pour la génération de contrat, aucune information personnelle n'est gardée ailleurs que dans les contrats générés."
|
||||||
}
|
}
|
||||||
@@ -67,7 +67,7 @@ export default function FormModal({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
size="50%"
|
w={{base: "100%", md: "80%", lg: "50%"}}
|
||||||
opened={opened}
|
opened={opened}
|
||||||
onClose={onClose}
|
onClose={onClose}
|
||||||
title={currentForm ? t("edit form") : t('create form')}
|
title={currentForm ? t("edit form") : t('create form')}
|
||||||
@@ -80,24 +80,27 @@ export default function FormModal({
|
|||||||
{...form.getInputProps('name')}
|
{...form.getInputProps('name')}
|
||||||
/>
|
/>
|
||||||
<TextInput
|
<TextInput
|
||||||
label={t("contact season", {capfirst: true})}
|
label={t("contract season", {capfirst: true})}
|
||||||
placeholder={t("contact season", {capfirst: true})}
|
placeholder={t("contract season", {capfirst: true})}
|
||||||
|
description={t("contract season recommandation", {capfirst: true})}
|
||||||
radius="sm"
|
radius="sm"
|
||||||
withAsterisk
|
withAsterisk
|
||||||
{...form.getInputProps('season')}
|
{...form.getInputProps('season')}
|
||||||
/>
|
/>
|
||||||
<DatePickerInput
|
<Group grow>
|
||||||
label={t("start date", {capfirst: true})}
|
<DatePickerInput
|
||||||
placeholder={t("start date", {capfirst: true})}
|
label={t("start date", {capfirst: true})}
|
||||||
withAsterisk
|
placeholder={t("start date", {capfirst: true})}
|
||||||
{...form.getInputProps('start')}
|
withAsterisk
|
||||||
/>
|
{...form.getInputProps('start')}
|
||||||
<DatePickerInput
|
/>
|
||||||
label={t("end date", {capfirst: true})}
|
<DatePickerInput
|
||||||
placeholder={t("end date", {capfirst: true})}
|
label={t("end date", {capfirst: true})}
|
||||||
withAsterisk
|
placeholder={t("end date", {capfirst: true})}
|
||||||
{...form.getInputProps('end')}
|
withAsterisk
|
||||||
/>
|
{...form.getInputProps('end')}
|
||||||
|
/>
|
||||||
|
</Group>
|
||||||
<Select
|
<Select
|
||||||
label={t("referer", {capfirst: true})}
|
label={t("referer", {capfirst: true})}
|
||||||
placeholder={t("referer", {capfirst: true})}
|
placeholder={t("referer", {capfirst: true})}
|
||||||
@@ -127,7 +130,6 @@ export default function FormModal({
|
|||||||
aria-label={t("cancel", {capfirst: true})}
|
aria-label={t("cancel", {capfirst: true})}
|
||||||
leftSection={<IconCancel/>}
|
leftSection={<IconCancel/>}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
form.reset();
|
|
||||||
form.clearErrors();
|
form.clearErrors();
|
||||||
onClose();
|
onClose();
|
||||||
}}
|
}}
|
||||||
@@ -139,7 +141,6 @@ export default function FormModal({
|
|||||||
form.validate();
|
form.validate();
|
||||||
if (form.isValid()) {
|
if (form.isValid()) {
|
||||||
handleSubmit(form.getValues(), currentForm?.id)
|
handleSubmit(form.getValues(), currentForm?.id)
|
||||||
form.reset();
|
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
>{currentForm ? t("edit form", {capfirst: true}) : t('create form', {capfirst: true})}</Button>
|
>{currentForm ? t("edit form", {capfirst: true}) : t('create form', {capfirst: true})}</Button>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { ActionIcon, Table, Tooltip } from "@mantine/core";
|
import { ActionIcon, Table, Tooltip } from "@mantine/core";
|
||||||
import { useNavigate } from "react-router";
|
import { useNavigate, useSearchParams } from "react-router";
|
||||||
import { deleteForm} from "@/services/api";
|
import { deleteForm} from "@/services/api";
|
||||||
import { IconEdit, IconX } from "@tabler/icons-react";
|
import { IconEdit, IconX } from "@tabler/icons-react";
|
||||||
import { t } from "@/config/i18n";
|
import { t } from "@/config/i18n";
|
||||||
@@ -12,6 +12,7 @@ export type FormRowProps = {
|
|||||||
export default function FormRow({
|
export default function FormRow({
|
||||||
form,
|
form,
|
||||||
}: FormRowProps) {
|
}: FormRowProps) {
|
||||||
|
const [searchParams, _] = useSearchParams();
|
||||||
const deleteMutation = deleteForm();
|
const deleteMutation = deleteForm();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
@@ -30,7 +31,7 @@ export default function FormRow({
|
|||||||
mr="5"
|
mr="5"
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
navigate(`/dashboard/forms/${form.id}/edit`);
|
navigate(`/dashboard/forms/${form.id}/edit${searchParams ? `?${searchParams.toString()}` : ""}`);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<IconEdit/>
|
<IconEdit/>
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ export function Navbar() {
|
|||||||
return (
|
return (
|
||||||
<nav>
|
<nav>
|
||||||
<NavLink to="/">{t("home", {capfirst: true})}</NavLink>
|
<NavLink to="/">{t("home", {capfirst: true})}</NavLink>
|
||||||
<NavLink to="/dashboard">{t("dashboard", {capfirst: true})}</NavLink>
|
<NavLink to="/dashboard/productors">{t("dashboard", {capfirst: true})}</NavLink>
|
||||||
</nav>
|
</nav>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -33,6 +33,7 @@ export default function ProductorsFilter({
|
|||||||
onFilterChange(values, 'names')
|
onFilterChange(values, 'names')
|
||||||
}}
|
}}
|
||||||
clearable
|
clearable
|
||||||
|
searchable
|
||||||
/>
|
/>
|
||||||
<MultiSelect
|
<MultiSelect
|
||||||
aria-label={t("filter by type", {capfirst: true})}
|
aria-label={t("filter by type", {capfirst: true})}
|
||||||
@@ -43,6 +44,7 @@ export default function ProductorsFilter({
|
|||||||
onFilterChange(values, 'types')
|
onFilterChange(values, 'types')
|
||||||
}}
|
}}
|
||||||
clearable
|
clearable
|
||||||
|
searchable
|
||||||
/>
|
/>
|
||||||
</Group>
|
</Group>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ export function ProductorModal({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
size="50%"
|
w={{base: "100%", md: "80%", lg: "50%"}}
|
||||||
opened={opened}
|
opened={opened}
|
||||||
onClose={onClose}
|
onClose={onClose}
|
||||||
title={t("create productor", {capfirst: true})}
|
title={t("create productor", {capfirst: true})}
|
||||||
@@ -87,7 +87,6 @@ export function ProductorModal({
|
|||||||
aria-label={t("cancel", {capfirst: true})}
|
aria-label={t("cancel", {capfirst: true})}
|
||||||
leftSection={<IconCancel/>}
|
leftSection={<IconCancel/>}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
form.reset();
|
|
||||||
form.clearErrors();
|
form.clearErrors();
|
||||||
onClose();
|
onClose();
|
||||||
}}
|
}}
|
||||||
@@ -99,7 +98,6 @@ export function ProductorModal({
|
|||||||
form.validate();
|
form.validate();
|
||||||
if (form.isValid()) {
|
if (form.isValid()) {
|
||||||
handleSubmit(form.getValues(), currentProductor?.id)
|
handleSubmit(form.getValues(), currentProductor?.id)
|
||||||
form.reset();
|
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
>{currentProductor ? t("edit productor", {capfirst: true}) : t('create productor', {capfirst: true})}</Button>
|
>{currentProductor ? t("edit productor", {capfirst: true}) : t('create productor', {capfirst: true})}</Button>
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { t } from "@/config/i18n";
|
|||||||
import { IconEdit, IconX } from "@tabler/icons-react";
|
import { IconEdit, IconX } from "@tabler/icons-react";
|
||||||
import type { Productor } from "@/services/resources/productors";
|
import type { Productor } from "@/services/resources/productors";
|
||||||
import { deleteProductor } from "@/services/api";
|
import { deleteProductor } from "@/services/api";
|
||||||
import { useNavigate } from "react-router";
|
import { useNavigate, useSearchParams } from "react-router";
|
||||||
|
|
||||||
export type ProductorRowProps = {
|
export type ProductorRowProps = {
|
||||||
productor: Productor;
|
productor: Productor;
|
||||||
@@ -12,6 +12,7 @@ export type ProductorRowProps = {
|
|||||||
export default function ProductorRow({
|
export default function ProductorRow({
|
||||||
productor,
|
productor,
|
||||||
}: ProductorRowProps) {
|
}: ProductorRowProps) {
|
||||||
|
const [searchParams, _] = useSearchParams();
|
||||||
const deleteMutation = deleteProductor();
|
const deleteMutation = deleteProductor();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
@@ -28,7 +29,7 @@ export default function ProductorRow({
|
|||||||
mr="5"
|
mr="5"
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
navigate(`/dashboard/productors/${productor.id}/edit`);
|
navigate(`/dashboard/productors/${productor.id}/edit${searchParams ? `?${searchParams.toString()}` : ""}`);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<IconEdit/>
|
<IconEdit/>
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ export default function ProductsFilters({
|
|||||||
onFilterChange(values, 'names')
|
onFilterChange(values, 'names')
|
||||||
}}
|
}}
|
||||||
clearable
|
clearable
|
||||||
|
searchable
|
||||||
/>
|
/>
|
||||||
<MultiSelect
|
<MultiSelect
|
||||||
aria-label={t("filter by productor", {capfirst: true})}
|
aria-label={t("filter by productor", {capfirst: true})}
|
||||||
@@ -43,6 +44,7 @@ export default function ProductsFilters({
|
|||||||
onFilterChange(values, 'productors')
|
onFilterChange(values, 'productors')
|
||||||
}}
|
}}
|
||||||
clearable
|
clearable
|
||||||
|
searchable
|
||||||
/>
|
/>
|
||||||
</Group>
|
</Group>
|
||||||
);
|
);
|
||||||
|
|||||||
44
frontend/src/components/Products/Form/index.tsx
Normal file
44
frontend/src/components/Products/Form/index.tsx
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
import { t } from "@/config/i18n";
|
||||||
|
import { ProductUnit, type Product } from "@/services/resources/products";
|
||||||
|
import type { Shipment } from "@/services/resources/shipments";
|
||||||
|
import { Group, NumberInput } from "@mantine/core";
|
||||||
|
import type { UseFormReturnType } from "@mantine/form";
|
||||||
|
|
||||||
|
export type ProductFormProps = {
|
||||||
|
inputForm: UseFormReturnType<Record<string, string | number>>;
|
||||||
|
product: Product;
|
||||||
|
shipment?: Shipment;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function ProductForm({
|
||||||
|
inputForm,
|
||||||
|
product,
|
||||||
|
shipment,
|
||||||
|
}: ProductFormProps) {
|
||||||
|
return (
|
||||||
|
<Group mb="sm" grow>
|
||||||
|
<NumberInput
|
||||||
|
label={
|
||||||
|
`${product.name}
|
||||||
|
${product.quantity || ""}${product.quantity ? product.quantity_unit : ""}
|
||||||
|
${
|
||||||
|
product.price ?
|
||||||
|
Intl.NumberFormat(
|
||||||
|
"fr-FR",
|
||||||
|
{style: "currency", currency: "EUR"}
|
||||||
|
).format(product.price) :
|
||||||
|
product.price_kg && Intl.NumberFormat(
|
||||||
|
"fr-FR",
|
||||||
|
{style: "currency", currency: "EUR"}
|
||||||
|
).format(product.price_kg)
|
||||||
|
}
|
||||||
|
${product.price ? `/ ${t(ProductUnit[product.unit])}` : "/ kg"}`
|
||||||
|
}
|
||||||
|
description={`${t("enter quantity", {capfirst: true})} ${t('in')} ${t(ProductUnit[product.unit])}`}
|
||||||
|
aria-label={t("enter quantity")}
|
||||||
|
placeholder={`${t("enter quantity", {capfirst: true})} ${t('in')} ${t(ProductUnit[product.unit])}`}
|
||||||
|
{...inputForm.getInputProps(shipment ? `planned-${shipment.id}-${product.id}` : `recurrent-${product.id}`)}
|
||||||
|
/>
|
||||||
|
</Group>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
import { Button, Group, Modal, NumberInput, Select, TextInput, Title, type ModalBaseProps } from "@mantine/core";
|
import { Button, Group, Modal, NumberInput, Pill, Select, TextInput, Title, Tooltip, type ModalBaseProps } from "@mantine/core";
|
||||||
import { t } from "@/config/i18n";
|
import { t } from "@/config/i18n";
|
||||||
import { useForm } from "@mantine/form";
|
import { useForm } from "@mantine/form";
|
||||||
import { IconCancel } from "@tabler/icons-react";
|
import { IconCancel, IconInfoCircle } from "@tabler/icons-react";
|
||||||
import { productToProductInputs, type Product, type ProductInputs } from "@/services/resources/products";
|
import { ProductQuantityUnit, productToProductInputs, ProductUnit, type Product, type ProductInputs } from "@/services/resources/products";
|
||||||
import { useEffect, useMemo } from "react";
|
import { useEffect, useMemo } from "react";
|
||||||
import { getProductors } from "@/services/api";
|
import { getProductors } from "@/services/api";
|
||||||
|
|
||||||
@@ -24,7 +24,8 @@ export function ProductModal({
|
|||||||
unit: null,
|
unit: null,
|
||||||
price: null,
|
price: null,
|
||||||
price_kg: null,
|
price_kg: null,
|
||||||
weight: null,
|
quantity: null,
|
||||||
|
quantity_unit: null,
|
||||||
type: null,
|
type: null,
|
||||||
productor_id: null,
|
productor_id: null,
|
||||||
},
|
},
|
||||||
@@ -33,8 +34,10 @@ export function ProductModal({
|
|||||||
!value ? `${t("name", {capfirst: true})} ${t('is required')}` : null,
|
!value ? `${t("name", {capfirst: true})} ${t('is required')}` : null,
|
||||||
unit: (value) =>
|
unit: (value) =>
|
||||||
!value ? `${t("unit", {capfirst: true})} ${t('is required')}` : null,
|
!value ? `${t("unit", {capfirst: true})} ${t('is required')}` : null,
|
||||||
price: (value) =>
|
price: (value, values) =>
|
||||||
!value ? `${t("price", {capfirst: true})} ${t('is required')}` : null,
|
!value && !values.price_kg ? `${t("price or price_kg", {capfirst: true})} ${t('is required')}` : null,
|
||||||
|
price_kg: (value, values) =>
|
||||||
|
!value && !values.price ? `${t("price or price_kg", {capfirst: true})} ${t('is required')}` : null,
|
||||||
type: (value) =>
|
type: (value) =>
|
||||||
!value ? `${t("type", {capfirst: true})} ${t('is required')}` : null,
|
!value ? `${t("type", {capfirst: true})} ${t('is required')}` : null,
|
||||||
productor_id: (value) =>
|
productor_id: (value) =>
|
||||||
@@ -54,70 +57,85 @@ export function ProductModal({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
size="50%"
|
w={{base: "100%", md: "80%", lg: "50%"}}
|
||||||
opened={opened}
|
opened={opened}
|
||||||
onClose={onClose}
|
onClose={onClose}
|
||||||
title={t("create product", {capfirst: true})}
|
title={t("create product", {capfirst: true})}
|
||||||
>
|
>
|
||||||
<Title order={4}>{t("informations", {capfirst: true})}</Title>
|
<Title order={4}>{t("informations", {capfirst: true})}</Title>
|
||||||
<TextInput
|
|
||||||
label={t("product name", {capfirst: true})}
|
|
||||||
placeholder={t("product name", {capfirst: true})}
|
|
||||||
radius="sm"
|
|
||||||
withAsterisk
|
|
||||||
{...form.getInputProps('name')}
|
|
||||||
/>
|
|
||||||
<Select
|
<Select
|
||||||
label={t("product type", {capfirst: true})}
|
|
||||||
placeholder={t("product type", {capfirst: true})}
|
|
||||||
radius="sm"
|
|
||||||
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})}
|
|
||||||
radius="sm"
|
|
||||||
withAsterisk
|
|
||||||
{...form.getInputProps('price')}
|
|
||||||
/>
|
|
||||||
<NumberInput
|
|
||||||
label={t("product priceKg", {capfirst: true})}
|
|
||||||
placeholder={t("product priceKg", {capfirst: true})}
|
|
||||||
radius="sm"
|
|
||||||
{...form.getInputProps('price_kg')}
|
|
||||||
/>
|
|
||||||
<NumberInput
|
|
||||||
label={t("product weight", {capfirst: true})}
|
|
||||||
placeholder={t("product weight", {capfirst: true})}
|
|
||||||
radius="sm"
|
|
||||||
{...form.getInputProps('weight', {capfirst: true})}
|
|
||||||
/>
|
|
||||||
<Select
|
|
||||||
label={t("productor", {capfirst: true})}
|
label={t("productor", {capfirst: true})}
|
||||||
placeholder={t("productor")}
|
placeholder={t("productor")}
|
||||||
nothingFoundMessage={t("nothing found", {capfirst: true})}
|
nothingFoundMessage={t("nothing found", {capfirst: true})}
|
||||||
withAsterisk
|
withAsterisk
|
||||||
clearable
|
clearable
|
||||||
allowDeselect
|
|
||||||
searchable
|
searchable
|
||||||
data={productorsSelect || []}
|
data={productorsSelect || []}
|
||||||
{...form.getInputProps('productor_id')}
|
{...form.getInputProps('productor_id')}
|
||||||
/>
|
/>
|
||||||
|
<Group grow>
|
||||||
|
<TextInput
|
||||||
|
label={t("product name", {capfirst: true})}
|
||||||
|
placeholder={t("product name", {capfirst: true})}
|
||||||
|
radius="sm"
|
||||||
|
withAsterisk
|
||||||
|
{...form.getInputProps('name')}
|
||||||
|
/>
|
||||||
|
<Select
|
||||||
|
label={t("product type", {capfirst: true})}
|
||||||
|
placeholder={t("product type", {capfirst: true})}
|
||||||
|
radius="sm"
|
||||||
|
withAsterisk
|
||||||
|
searchable
|
||||||
|
clearable
|
||||||
|
data={[
|
||||||
|
{value: "1", label: t("planned")},
|
||||||
|
{value: "2", label: t("recurrent")}
|
||||||
|
]}
|
||||||
|
{...form.getInputProps('type')}
|
||||||
|
/>
|
||||||
|
</Group>
|
||||||
|
<Select
|
||||||
|
label={t("product unit", {capfirst: true})}
|
||||||
|
placeholder={t("product unit", {capfirst: true})}
|
||||||
|
description={t("the product unit will be assigned to the quantity requested in the form", { capfirst: true})}
|
||||||
|
radius="sm"
|
||||||
|
withAsterisk
|
||||||
|
searchable
|
||||||
|
clearable
|
||||||
|
data={Object.entries(ProductUnit).map(([key, value]) => ({value: key, label: t(value)}))}
|
||||||
|
{...form.getInputProps('unit')}
|
||||||
|
/>
|
||||||
|
<Group grow>
|
||||||
|
<NumberInput
|
||||||
|
label={t("product price", {capfirst: true})}
|
||||||
|
placeholder={t("product price", {capfirst: true})}
|
||||||
|
radius="sm"
|
||||||
|
{...form.getInputProps('price')}
|
||||||
|
/>
|
||||||
|
<NumberInput
|
||||||
|
label={t("product price kg", {capfirst: true})}
|
||||||
|
placeholder={t("product price kg", {capfirst: true})}
|
||||||
|
radius="sm"
|
||||||
|
{...form.getInputProps('price_kg')}
|
||||||
|
/>
|
||||||
|
</Group>
|
||||||
|
<Group grow>
|
||||||
|
<NumberInput
|
||||||
|
label={t("product quantity", {capfirst: true})}
|
||||||
|
placeholder={t("product quantity", {capfirst: true})}
|
||||||
|
radius="sm"
|
||||||
|
{...form.getInputProps('quantity', {capfirst: true})}
|
||||||
|
/>
|
||||||
|
<Select
|
||||||
|
label={t("product quantity unit", {capfirst: true})}
|
||||||
|
placeholder={t("product quantity unit", {capfirst: true})}
|
||||||
|
radius="sm"
|
||||||
|
data={Object.entries(ProductQuantityUnit).map(([key, value]) => ({value: key, label: t(value)}))}
|
||||||
|
{...form.getInputProps('quantity_unit', {capfirst: true})}
|
||||||
|
/>
|
||||||
|
|
||||||
|
</Group>
|
||||||
<Group mt="sm" justify="space-between">
|
<Group mt="sm" justify="space-between">
|
||||||
<Button
|
<Button
|
||||||
variant="filled"
|
variant="filled"
|
||||||
@@ -125,7 +143,6 @@ export function ProductModal({
|
|||||||
aria-label={t("cancel", {capfirst: true})}
|
aria-label={t("cancel", {capfirst: true})}
|
||||||
leftSection={<IconCancel/>}
|
leftSection={<IconCancel/>}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
form.reset();
|
|
||||||
form.clearErrors();
|
form.clearErrors();
|
||||||
onClose();
|
onClose();
|
||||||
}}
|
}}
|
||||||
@@ -138,7 +155,6 @@ export function ProductModal({
|
|||||||
console.log(form.isValid(), form.getValues())
|
console.log(form.isValid(), form.getValues())
|
||||||
if (form.isValid()) {
|
if (form.isValid()) {
|
||||||
handleSubmit(form.getValues(), currentProduct?.id)
|
handleSubmit(form.getValues(), currentProduct?.id)
|
||||||
form.reset();
|
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
>{currentProduct ? t("edit product", {capfirst: true}) : t('create product', {capfirst: true})}</Button>
|
>{currentProduct ? t("edit product", {capfirst: true}) : t('create product', {capfirst: true})}</Button>
|
||||||
|
|||||||
@@ -1,10 +1,9 @@
|
|||||||
import { ActionIcon, Table, Tooltip } from "@mantine/core";
|
import { ActionIcon, Table, Tooltip } from "@mantine/core";
|
||||||
import { t } from "@/config/i18n";
|
import { t } from "@/config/i18n";
|
||||||
import { IconEdit, IconX } from "@tabler/icons-react";
|
import { IconEdit, IconX } from "@tabler/icons-react";
|
||||||
import { ProductType, ProductUnit, type Product, type ProductInputs } from "@/services/resources/products";
|
import { ProductType, ProductUnit, type Product } from "@/services/resources/products";
|
||||||
import { ProductModal } from "@/components/Products/Modal";
|
import { deleteProduct } from "@/services/api";
|
||||||
import { deleteProduct, getProduct } from "@/services/api";
|
import { useNavigate, useSearchParams } from "react-router";
|
||||||
import { useNavigate } from "react-router";
|
|
||||||
|
|
||||||
export type ProductRowProps = {
|
export type ProductRowProps = {
|
||||||
product: Product;
|
product: Product;
|
||||||
@@ -13,17 +12,19 @@ export type ProductRowProps = {
|
|||||||
export default function ProductRow({
|
export default function ProductRow({
|
||||||
product,
|
product,
|
||||||
}: ProductRowProps) {
|
}: ProductRowProps) {
|
||||||
|
const [searchParams, _] = useSearchParams();
|
||||||
const deleteMutation = deleteProduct();
|
const deleteMutation = deleteProduct();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Table.Tr key={product.id}>
|
<Table.Tr key={product.id}>
|
||||||
<Table.Td>{product.name}</Table.Td>
|
<Table.Td>{product.name}</Table.Td>
|
||||||
<Table.Td>{ProductType[product.type]}</Table.Td>
|
<Table.Td>{t(ProductType[product.type])}</Table.Td>
|
||||||
<Table.Td>{product.price}</Table.Td>
|
<Table.Td>{product.price}</Table.Td>
|
||||||
<Table.Td>{product.price_kg}</Table.Td>
|
<Table.Td>{product.price_kg}</Table.Td>
|
||||||
<Table.Td>{product.weight}</Table.Td>
|
<Table.Td>{product.quantity}</Table.Td>
|
||||||
<Table.Td>{ProductUnit[product.unit]}</Table.Td>
|
<Table.Td>{product.quantity_unit}</Table.Td>
|
||||||
|
<Table.Td>{t(ProductUnit[product.unit])}</Table.Td>
|
||||||
<Table.Td>
|
<Table.Td>
|
||||||
<Tooltip label={t("edit product", {capfirst: true})}>
|
<Tooltip label={t("edit product", {capfirst: true})}>
|
||||||
<ActionIcon
|
<ActionIcon
|
||||||
@@ -31,7 +32,7 @@ export default function ProductRow({
|
|||||||
mr="5"
|
mr="5"
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
navigate(`/dashboard/products/${product.id}/edit`);
|
navigate(`/dashboard/products/${product.id}/edit${searchParams ? `?${searchParams.toString()}` : ""}`);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<IconEdit/>
|
<IconEdit/>
|
||||||
|
|||||||
@@ -1,62 +1,57 @@
|
|||||||
import { ActionIcon, Group, TextInput, Tooltip } from "@mantine/core";
|
import { Accordion, Group, Text } from "@mantine/core";
|
||||||
import { DatePickerInput } from "@mantine/dates";
|
import type { Shipment } from "@/services/resources/shipments";
|
||||||
import { IconX } from "@tabler/icons-react";
|
import { ProductForm } from "@/components/Products/Form";
|
||||||
import { t } from "@/config/i18n";
|
import type { UseFormReturnType } from "@mantine/form";
|
||||||
import type { ShipmentInputs } from "@/services/resources/shipments";
|
import { useMemo } from "react";
|
||||||
|
import { computePrices } from "@/pages/Contract";
|
||||||
|
|
||||||
export type ShipmentFormProps = {
|
export type ShipmentFormProps = {
|
||||||
|
inputForm: UseFormReturnType<Record<string, string | number>>;
|
||||||
|
shipment: Shipment;
|
||||||
index: number;
|
index: number;
|
||||||
setShipmentElement: (index: number, shipment: ShipmentInputs) => void;
|
|
||||||
deleteShipmentElement: (index: number) => void;
|
|
||||||
shipment: ShipmentInputs;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function ShipmentForm({
|
export default function ShipmentForm({
|
||||||
index,
|
shipment,
|
||||||
setShipmentElement,
|
index,
|
||||||
deleteShipmentElement,
|
inputForm,
|
||||||
shipment
|
|
||||||
}: ShipmentFormProps) {
|
}: ShipmentFormProps) {
|
||||||
return (
|
const shipmentPrice = useMemo(() => {
|
||||||
<Group justify="space-between" key={`shipment_${index}`}>
|
const values = Object
|
||||||
<Group grow maw="80%">
|
.entries(inputForm.getValues())
|
||||||
<TextInput
|
.filter(([key]) =>
|
||||||
label={t("shipment name", {capfirst: true})}
|
key.includes("planned") &&
|
||||||
placeholder={t("shipment name", {capfirst: true})}
|
key.split("-")[1] === String(shipment.id)
|
||||||
radius="sm"
|
);
|
||||||
withAsterisk
|
return computePrices(values, shipment.products);
|
||||||
value={shipment.name || ""}
|
}, [inputForm, shipment.products]);
|
||||||
onChange={(event) => {
|
|
||||||
const value = event.target.value;
|
|
||||||
setShipmentElement(index, {...shipment, name: value})
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<DatePickerInput
|
|
||||||
label={t("shipment date", {capfirst: true})}
|
|
||||||
placeholder={t("shipment date", {capfirst: true})}
|
|
||||||
radius="sm"
|
|
||||||
withAsterisk
|
|
||||||
value={shipment.date || null}
|
|
||||||
onChange={(event) => {
|
|
||||||
const value = event || "";
|
|
||||||
setShipmentElement(index, {...shipment, date: value})
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</Group>
|
|
||||||
<Tooltip label={t("remove shipment", {capfirst: true})}>
|
|
||||||
<ActionIcon
|
|
||||||
flex={{base: "1", md: "0"}}
|
|
||||||
style={{alignSelf: "flex-end"}}
|
|
||||||
color="red"
|
|
||||||
aria-label={t("remove shipment", {capfirst: true})}
|
|
||||||
onClick={() => {
|
|
||||||
deleteShipmentElement(index)
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<IconX/>
|
|
||||||
</ActionIcon>
|
|
||||||
</Tooltip>
|
|
||||||
</Group>
|
|
||||||
|
|
||||||
)
|
return (
|
||||||
|
<Accordion.Item value={String(index)}>
|
||||||
|
<Accordion.Control>
|
||||||
|
<Group justify="space-between">
|
||||||
|
<Text>{shipment.name}</Text>
|
||||||
|
<Text>{
|
||||||
|
Intl.NumberFormat(
|
||||||
|
"fr-FR",
|
||||||
|
{style: "currency", currency: "EUR"}
|
||||||
|
).format(shipmentPrice)
|
||||||
|
}</Text>
|
||||||
|
<Text mr="lg">{shipment.date}</Text>
|
||||||
|
</Group>
|
||||||
|
</Accordion.Control>
|
||||||
|
<Accordion.Panel>
|
||||||
|
{
|
||||||
|
shipment?.products.map((product) => (
|
||||||
|
<ProductForm
|
||||||
|
key={product.id}
|
||||||
|
product={product}
|
||||||
|
shipment={shipment}
|
||||||
|
inputForm={inputForm}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
}
|
||||||
|
</Accordion.Panel>
|
||||||
|
</Accordion.Item>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
@@ -5,7 +5,7 @@ import { IconCancel } from "@tabler/icons-react";
|
|||||||
import { useForm } from "@mantine/form";
|
import { useForm } from "@mantine/form";
|
||||||
import { useEffect, useMemo } from "react";
|
import { useEffect, useMemo } from "react";
|
||||||
import { shipmentToShipmentInputs, type Shipment, type ShipmentInputs } from "@/services/resources/shipments";
|
import { shipmentToShipmentInputs, type Shipment, type ShipmentInputs } from "@/services/resources/shipments";
|
||||||
import { getForms, getProducts } from "@/services/api";
|
import { getForms, getProductors, getProducts } from "@/services/api";
|
||||||
|
|
||||||
export type ShipmentModalProps = ModalBaseProps & {
|
export type ShipmentModalProps = ModalBaseProps & {
|
||||||
currentShipment?: Shipment;
|
currentShipment?: Shipment;
|
||||||
@@ -42,18 +42,28 @@ export default function ShipmentModal({
|
|||||||
}, [currentShipment]);
|
}, [currentShipment]);
|
||||||
|
|
||||||
const { data: allForms } = getForms();
|
const { data: allForms } = getForms();
|
||||||
const { data: allProducts } = getProducts();
|
const { data: allProducts } = getProducts(new URLSearchParams("types=1"));
|
||||||
|
const { data: allProductors } = getProductors()
|
||||||
const formsSelect = useMemo(() => {
|
const formsSelect = useMemo(() => {
|
||||||
return allForms?.map(form => ({value: String(form.id), label: `${form.name} ${form.season}`}))
|
return allForms?.map(form => ({value: String(form.id), label: `${form.name} ${form.season}`}))
|
||||||
}, [allForms]);
|
}, [allForms]);
|
||||||
|
|
||||||
const productsSelect = useMemo(() => {
|
const productsSelect = useMemo(() => {
|
||||||
return allProducts?.map(product => ({value: String(product.id), label: `${product.name}`}))
|
if (!allProducts)
|
||||||
}, [allProducts]);
|
return;
|
||||||
|
return allProductors?.map(productor => {
|
||||||
|
return {
|
||||||
|
group: productor.name,
|
||||||
|
items: allProducts
|
||||||
|
.filter((product) => product.productor.id === productor.id)
|
||||||
|
.map((product) => ({value: String(product.id), label: `${product.name}`}))
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}, [allProducts, form]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
size="50%"
|
w={{base: "100%", md: "80%", lg: "50%"}}
|
||||||
opened={opened}
|
opened={opened}
|
||||||
onClose={onClose}
|
onClose={onClose}
|
||||||
title={currentShipment ? t("edit shipment") : t('create shipment')}
|
title={currentShipment ? t("edit shipment") : t('create shipment')}
|
||||||
@@ -83,8 +93,9 @@ export default function ShipmentModal({
|
|||||||
<MultiSelect
|
<MultiSelect
|
||||||
label={t("shipment products", {capfirst: true})}
|
label={t("shipment products", {capfirst: true})}
|
||||||
placeholder={t("shipment products", {capfirst: true})}
|
placeholder={t("shipment products", {capfirst: true})}
|
||||||
data={productsSelect}
|
data={productsSelect || []}
|
||||||
clearable
|
clearable
|
||||||
|
searchable
|
||||||
{...form.getInputProps('product_ids')}
|
{...form.getInputProps('product_ids')}
|
||||||
/>
|
/>
|
||||||
<Group mt="sm" justify="space-between">
|
<Group mt="sm" justify="space-between">
|
||||||
@@ -105,7 +116,6 @@ export default function ShipmentModal({
|
|||||||
form.validate();
|
form.validate();
|
||||||
if (form.isValid()) {
|
if (form.isValid()) {
|
||||||
handleSubmit(form.getValues(), currentShipment?.id)
|
handleSubmit(form.getValues(), currentShipment?.id)
|
||||||
// form.reset();
|
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
>{currentShipment ? t("edit shipment", {capfirst: true}) : t('create shipment', {capfirst: true})}</Button>
|
>{currentShipment ? t("edit shipment", {capfirst: true}) : t('create shipment', {capfirst: true})}</Button>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { ActionIcon, Table, Tooltip } from "@mantine/core";
|
import { ActionIcon, Table, Tooltip } from "@mantine/core";
|
||||||
import { useNavigate } from "react-router";
|
import { useNavigate, useSearchParams } from "react-router";
|
||||||
import { deleteShipment} from "@/services/api";
|
import { deleteShipment} from "@/services/api";
|
||||||
import { IconEdit, IconX } from "@tabler/icons-react";
|
import { IconEdit, IconX } from "@tabler/icons-react";
|
||||||
import { t } from "@/config/i18n";
|
import { t } from "@/config/i18n";
|
||||||
@@ -12,6 +12,7 @@ export type ShipmentRowProps = {
|
|||||||
export default function ShipmentRow({
|
export default function ShipmentRow({
|
||||||
shipment,
|
shipment,
|
||||||
}: ShipmentRowProps) {
|
}: ShipmentRowProps) {
|
||||||
|
const [searchParams, _] = useSearchParams();
|
||||||
const deleteMutation = deleteShipment();
|
const deleteMutation = deleteShipment();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
@@ -27,7 +28,7 @@ export default function ShipmentRow({
|
|||||||
mr="5"
|
mr="5"
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
navigate(`/dashboard/shipments/${shipment.id}/edit`);
|
navigate(`/dashboard/shipments/${shipment.id}/edit${searchParams ? `?${searchParams.toString()}` : ""}`);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<IconEdit/>
|
<IconEdit/>
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ export function UserModal({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
size="50%"
|
w={{base: "100%", md: "80%", lg: "50%"}}
|
||||||
opened={opened}
|
opened={opened}
|
||||||
onClose={onClose}
|
onClose={onClose}
|
||||||
title={t("create user", {capfirst: true})}
|
title={t("create user", {capfirst: true})}
|
||||||
@@ -64,7 +64,6 @@ export function UserModal({
|
|||||||
aria-label={t("cancel", {capfirst: true})}
|
aria-label={t("cancel", {capfirst: true})}
|
||||||
leftSection={<IconCancel/>}
|
leftSection={<IconCancel/>}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
form.reset();
|
|
||||||
form.clearErrors();
|
form.clearErrors();
|
||||||
onClose();
|
onClose();
|
||||||
}}
|
}}
|
||||||
@@ -77,7 +76,6 @@ export function UserModal({
|
|||||||
console.log(form.isValid(), form.getValues())
|
console.log(form.isValid(), form.getValues())
|
||||||
if (form.isValid()) {
|
if (form.isValid()) {
|
||||||
handleSubmit(form.getValues(), currentUser?.id)
|
handleSubmit(form.getValues(), currentUser?.id)
|
||||||
form.reset();
|
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
>{currentUser ? t("edit user", {capfirst: true}) : t('create user', {capfirst: true})}</Button>
|
>{currentUser ? t("edit user", {capfirst: true}) : t('create user', {capfirst: true})}</Button>
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import { IconEdit, IconX } from "@tabler/icons-react";
|
|||||||
import { type User, type UserInputs } from "@/services/resources/users";
|
import { type User, type UserInputs } from "@/services/resources/users";
|
||||||
import { UserModal } from "@/components/Users/Modal";
|
import { UserModal } from "@/components/Users/Modal";
|
||||||
import { deleteUser, getUser } from "@/services/api";
|
import { deleteUser, getUser } from "@/services/api";
|
||||||
import { useNavigate } from "react-router";
|
import { useNavigate, useSearchParams } from "react-router";
|
||||||
|
|
||||||
export type UserRowProps = {
|
export type UserRowProps = {
|
||||||
user: User;
|
user: User;
|
||||||
@@ -13,6 +13,7 @@ export type UserRowProps = {
|
|||||||
export default function UserRow({
|
export default function UserRow({
|
||||||
user,
|
user,
|
||||||
}: UserRowProps) {
|
}: UserRowProps) {
|
||||||
|
const [searchParams, _] = useSearchParams();
|
||||||
const deleteMutation = deleteUser();
|
const deleteMutation = deleteUser();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
@@ -27,7 +28,7 @@ export default function UserRow({
|
|||||||
mr="5"
|
mr="5"
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
navigate(`/dashboard/users/${user.id}/edit`);
|
navigate(`/dashboard/users/${user.id}/edit${searchParams ? `?${searchParams.toString()}` : ""}`);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<IconEdit/>
|
<IconEdit/>
|
||||||
|
|||||||
@@ -15,11 +15,10 @@ const resources = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
i18next
|
i18next
|
||||||
.use(LanguageDetector)
|
|
||||||
.use(initReactI18next)
|
.use(initReactI18next)
|
||||||
.init({
|
.init({
|
||||||
resources: resources,
|
resources: resources,
|
||||||
fallbackLng: "en",
|
fallbackLng: "fr",
|
||||||
debug: Config.debug,
|
debug: Config.debug,
|
||||||
detection: {
|
detection: {
|
||||||
caches: [],
|
caches: [],
|
||||||
|
|||||||
@@ -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 { 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 { useMemo } from "react";
|
||||||
import { useParams } from "react-router";
|
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() {
|
export function Contract() {
|
||||||
const { id } = useParams();
|
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(() => {
|
const productsRecurent = useMemo(() => {
|
||||||
console.log(form)
|
|
||||||
return form?.productor?.products.filter((el) => el.type === "2")
|
return form?.productor?.products.filter((el) => el.type === "2")
|
||||||
}, [form])
|
}, [form]);
|
||||||
|
|
||||||
const shipments = useMemo(() => {
|
const shipments = useMemo(() => {
|
||||||
return form?.shipments
|
return form?.shipments;
|
||||||
|
}, [form]);
|
||||||
|
|
||||||
|
const allProducts = useMemo(() => {
|
||||||
|
return form?.productor?.products;
|
||||||
}, [form])
|
}, [form])
|
||||||
|
|
||||||
|
const price = useMemo(() => {
|
||||||
|
const values = Object.entries(inputForm.getValues());
|
||||||
|
return computePrices(values, allProducts, form?.shipments.length);
|
||||||
|
}, [inputForm, allProducts, form?.shipments]);
|
||||||
|
|
||||||
if (!form)
|
if (!form)
|
||||||
return <Loader/>
|
return <Loader/>;
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Stack>
|
<Stack w={{base: "100%", md: "80%", lg: "50%"}}>
|
||||||
<Title>{form.name}</Title>
|
<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) => (
|
productsRecurent.length > 0 ?
|
||||||
<Group>
|
<>
|
||||||
<Text>{el.name}</Text>
|
<Title order={3}>{t('recurrent products', {capfirst: true})}</Title>
|
||||||
<NumberInput/>
|
<Text size="sm">{t('your selection in this category will apply for all shipments', {capfirst: true})}</Text>
|
||||||
</Group>
|
{
|
||||||
))
|
productsRecurent.map((product) => (
|
||||||
|
<ProductForm
|
||||||
|
key={product.id}
|
||||||
|
product={product}
|
||||||
|
inputForm={inputForm}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
}
|
||||||
|
</> :
|
||||||
|
null
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
shipments.map((shipment) => (
|
shipments.some(shipment => shipment.products.length > 0) ?
|
||||||
<>
|
<>
|
||||||
<Text>{shipment.name}</Text>
|
<Title order={3}>{t("planned products")}</Title>
|
||||||
{
|
<Text>{t("select products per shipment")}</Text>
|
||||||
shipment?.products.map((product) => (
|
<Accordion defaultValue={"0"}>
|
||||||
<Group>
|
{
|
||||||
<Text>{product.name}</Text>
|
shipments.map((shipment, index) => (
|
||||||
<NumberInput/>
|
<ShipmentForm
|
||||||
</Group>
|
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>
|
</Stack>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -25,7 +25,7 @@ export function Forms() {
|
|||||||
}, [location, isEdit])
|
}, [location, isEdit])
|
||||||
|
|
||||||
const closeModal = () => {
|
const closeModal = () => {
|
||||||
navigate("/dashboard/forms");
|
navigate(`/dashboard/forms${searchParams ? `?${searchParams.toString()}` : ""}`);
|
||||||
};
|
};
|
||||||
|
|
||||||
const { isPending, data } = getForms(searchParams);
|
const { isPending, data } = getForms(searchParams);
|
||||||
@@ -102,7 +102,7 @@ export function Forms() {
|
|||||||
<ActionIcon
|
<ActionIcon
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
navigate(`/dashboard/forms/create`);
|
navigate(`/dashboard/forms/create${searchParams ? `?${searchParams.toString()}` : ""}`);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<IconPlus/>
|
<IconPlus/>
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import { IconPlus } from "@tabler/icons-react";
|
|||||||
import ProductorRow from "@/components/Productors/Row";
|
import ProductorRow from "@/components/Productors/Row";
|
||||||
import { useLocation, useNavigate, useSearchParams } from "react-router";
|
import { useLocation, useNavigate, useSearchParams } from "react-router";
|
||||||
import { ProductorModal } from "@/components/Productors/Modal";
|
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 type { Productor, ProductorInputs } from "@/services/resources/productors";
|
||||||
import ProductorsFilters from "@/components/Productors/Filter";
|
import ProductorsFilters from "@/components/Productors/Filter";
|
||||||
|
|
||||||
@@ -25,7 +25,7 @@ export default function Productors() {
|
|||||||
}, [location, isEdit])
|
}, [location, isEdit])
|
||||||
|
|
||||||
const closeModal = () => {
|
const closeModal = () => {
|
||||||
navigate("/dashboard/productors");
|
navigate(`/dashboard/productors${searchParams ? `?${searchParams.toString()}` : ""}`);
|
||||||
};
|
};
|
||||||
|
|
||||||
const { data: productors, isPending } = getProductors(searchParams);
|
const { data: productors, isPending } = getProductors(searchParams);
|
||||||
@@ -85,7 +85,7 @@ export default function Productors() {
|
|||||||
<ActionIcon
|
<ActionIcon
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
navigate(`/dashboard/productors/create`);
|
navigate(`/dashboard/productors/create${searchParams ? `?${searchParams.toString()}` : ""}`);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<IconPlus/>
|
<IconPlus/>
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ export default function Products() {
|
|||||||
}, [location, isEdit])
|
}, [location, isEdit])
|
||||||
|
|
||||||
const closeModal = () => {
|
const closeModal = () => {
|
||||||
navigate("/dashboard/products");
|
navigate(`/dashboard/products${searchParams ? `?${searchParams.toString()}` : ""}`);
|
||||||
};
|
};
|
||||||
|
|
||||||
const { data: products, isPending } = getProducts(searchParams);
|
const { data: products, isPending } = getProducts(searchParams);
|
||||||
@@ -38,7 +38,7 @@ export default function Products() {
|
|||||||
}, [allProducts])
|
}, [allProducts])
|
||||||
|
|
||||||
const productors = useMemo(() => {
|
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)
|
.filter((productor, index, array) => array.indexOf(productor) === index)
|
||||||
}, [allProducts])
|
}, [allProducts])
|
||||||
|
|
||||||
@@ -55,7 +55,7 @@ export default function Products() {
|
|||||||
return;
|
return;
|
||||||
await editProductMutation.mutateAsync({
|
await editProductMutation.mutateAsync({
|
||||||
id: id,
|
id: id,
|
||||||
product: product
|
product: productCreateFromProductInputs(product)
|
||||||
});
|
});
|
||||||
closeModal();
|
closeModal();
|
||||||
}, []);
|
}, []);
|
||||||
@@ -83,7 +83,7 @@ export default function Products() {
|
|||||||
<ActionIcon
|
<ActionIcon
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
navigate(`/dashboard/products/create`);
|
navigate(`/dashboard/products/create${searchParams ? `?${searchParams.toString()}` : ""}`);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<IconPlus/>
|
<IconPlus/>
|
||||||
@@ -115,7 +115,8 @@ export default function Products() {
|
|||||||
<Table.Th>{t("type", {capfirst: true})}</Table.Th>
|
<Table.Th>{t("type", {capfirst: true})}</Table.Th>
|
||||||
<Table.Th>{t("price", {capfirst: true})}</Table.Th>
|
<Table.Th>{t("price", {capfirst: true})}</Table.Th>
|
||||||
<Table.Th>{t("priceKg", {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("unit", {capfirst: true})}</Table.Th>
|
||||||
<Table.Th>{t("actions", {capfirst: true})}</Table.Th>
|
<Table.Th>{t("actions", {capfirst: true})}</Table.Th>
|
||||||
</Table.Tr>
|
</Table.Tr>
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ export default function Shipments() {
|
|||||||
}, [location, isEdit])
|
}, [location, isEdit])
|
||||||
|
|
||||||
const closeModal = () => {
|
const closeModal = () => {
|
||||||
navigate("/dashboard/shipments");
|
navigate(`/dashboard/shipments${searchParams ? `?${searchParams.toString()}` : ""}`);
|
||||||
};
|
};
|
||||||
|
|
||||||
const { data: shipments, isPending } = getShipments(searchParams);
|
const { data: shipments, isPending } = getShipments(searchParams);
|
||||||
@@ -78,7 +78,7 @@ export default function Shipments() {
|
|||||||
<ActionIcon
|
<ActionIcon
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
navigate(`/dashboard/shipments/create`);
|
navigate(`/dashboard/shipments/create${searchParams ? `?${searchParams.toString()}` : ""}`);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<IconPlus/>
|
<IconPlus/>
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ export default function Users() {
|
|||||||
}, [location, isEdit])
|
}, [location, isEdit])
|
||||||
|
|
||||||
const closeModal = () => {
|
const closeModal = () => {
|
||||||
navigate("/dashboard/users");
|
navigate(`/dashboard/users${searchParams ? `?${searchParams.toString()}` : ""}`);
|
||||||
};
|
};
|
||||||
|
|
||||||
const {data: users, isPending} = getUsers(searchParams);
|
const {data: users, isPending} = getUsers(searchParams);
|
||||||
@@ -79,7 +79,7 @@ export default function Users() {
|
|||||||
<ActionIcon
|
<ActionIcon
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
navigate(`/dashboard/users/create`);
|
navigate(`/dashboard/users/create${searchParams ? `?${searchParams.toString()}` : ""}`);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<IconPlus/>
|
<IconPlus/>
|
||||||
|
|||||||
@@ -1,29 +1,37 @@
|
|||||||
import { t } from "@/config/i18n";
|
|
||||||
import type { Productor } from "@/services/resources/productors";
|
import type { Productor } from "@/services/resources/productors";
|
||||||
import type { Shipment } from "@/services/resources/shipments";
|
import type { Shipment } from "@/services/resources/shipments";
|
||||||
|
|
||||||
export const ProductType = [
|
type ProductTypeKey = "1" | "2";
|
||||||
"none",
|
type ProductUnitKey = "1" | "2" | "3";
|
||||||
t("planned"),
|
|
||||||
t("reccurent"),
|
|
||||||
];
|
|
||||||
|
|
||||||
export const ProductUnit = [
|
export const ProductType = {
|
||||||
"none",
|
"1": "planned",
|
||||||
t("grams"),
|
"2": "recurrent",
|
||||||
t("kilo"),
|
};
|
||||||
t("piece"),
|
|
||||||
];
|
export const ProductUnit = {
|
||||||
|
"1": "grams",
|
||||||
|
"2": "kilo",
|
||||||
|
"3": "piece",
|
||||||
|
};
|
||||||
|
|
||||||
|
export const ProductQuantityUnit = {
|
||||||
|
"ml": "mililiter",
|
||||||
|
"L": "liter",
|
||||||
|
"g": "grams",
|
||||||
|
"kg": "kilo"
|
||||||
|
}
|
||||||
|
|
||||||
export type Product = {
|
export type Product = {
|
||||||
id: number;
|
id: number;
|
||||||
productor: Productor;
|
productor: Productor;
|
||||||
name: string;
|
name: string;
|
||||||
unit: string;
|
unit: ProductUnitKey;
|
||||||
price: number;
|
price: number | null;
|
||||||
price_kg: number | null;
|
price_kg: number | null;
|
||||||
weight: number;
|
quantity: number | null;
|
||||||
type: string;
|
quantity_unit: string | null;
|
||||||
|
type: ProductTypeKey;
|
||||||
shipments: Shipment[];
|
shipments: Shipment[];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -31,9 +39,10 @@ export type ProductCreate = {
|
|||||||
productor_id: number;
|
productor_id: number;
|
||||||
name: string;
|
name: string;
|
||||||
unit: string;
|
unit: string;
|
||||||
price: number;
|
price: number | null;
|
||||||
price_kg: number | null;
|
price_kg: number | null;
|
||||||
weight: number | null;
|
quantity: number | null;
|
||||||
|
quantity_unit: string | null;
|
||||||
type: string;
|
type: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -43,7 +52,8 @@ export type ProductEdit = {
|
|||||||
unit: string | null;
|
unit: string | null;
|
||||||
price: number | null;
|
price: number | null;
|
||||||
price_kg: number | null;
|
price_kg: number | null;
|
||||||
weight: number | null;
|
quantity: number | null;
|
||||||
|
quantity_unit: string | null;
|
||||||
type: string | null;
|
type: string | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -53,7 +63,8 @@ export type ProductInputs = {
|
|||||||
unit: string | null;
|
unit: string | null;
|
||||||
price: number | null;
|
price: number | null;
|
||||||
price_kg: number | null;
|
price_kg: number | null;
|
||||||
weight: number | null;
|
quantity: number | null;
|
||||||
|
quantity_unit: string | null;
|
||||||
type: string | null;
|
type: string | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -69,7 +80,8 @@ export function productToProductInputs(product: Product): ProductInputs {
|
|||||||
unit: product.unit,
|
unit: product.unit,
|
||||||
price: product.price,
|
price: product.price,
|
||||||
price_kg: product.price_kg,
|
price_kg: product.price_kg,
|
||||||
weight: product.weight,
|
quantity: product.quantity,
|
||||||
|
quantity_unit: product.quantity_unit,
|
||||||
type: product.type,
|
type: product.type,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -81,7 +93,8 @@ export function productCreateFromProductInputs(productInput: ProductInputs): Pro
|
|||||||
unit: productInput.unit!,
|
unit: productInput.unit!,
|
||||||
price: productInput.price!,
|
price: productInput.price!,
|
||||||
price_kg: productInput.price_kg,
|
price_kg: productInput.price_kg,
|
||||||
weight: productInput.weight,
|
quantity: productInput.quantity,
|
||||||
|
quantity_unit: productInput.quantity_unit,
|
||||||
type: productInput.type!,
|
type: productInput.type!,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user