fix i18n add help page

This commit is contained in:
Julien Aldon
2026-02-16 16:38:34 +01:00
parent be8e32ebed
commit 5354a74cac
21 changed files with 524 additions and 224 deletions

View File

@@ -19,10 +19,10 @@ def compute_recurrent_prices(products_quantities: list[dict], nb_shipment: int):
result += compute_product_price(product, quantity, nb_shipment) result += compute_product_price(product, quantity, nb_shipment)
return result return result
def compute_planned_prices(planneds: list[dict]): def compute_occasional_prices(occasionals: list[dict]):
result = 0 result = 0
for planned in planneds: for occasional in occasionals:
result += planned['price'] result += occasional['price']
return result return result
def compute_product_price(product: models.Product, quantity: int, nb_shipment: int = 1): def compute_product_price(product: models.Product, quantity: int, nb_shipment: int = 1):
@@ -37,7 +37,7 @@ def find_dict_in_list(lst, key, value):
return i return i
return -1 return -1
def create_planned_dict(contract_products: list[models.ContractProduct]): def create_occasional_dict(contract_products: list[models.ContractProduct]):
result = [] result = []
for contract_product in contract_products: for contract_product in contract_products:
existing_id = find_dict_in_list( existing_id = find_dict_in_list(
@@ -74,18 +74,18 @@ async def create_contract(
session: Session = Depends(get_session) session: Session = Depends(get_session)
): ):
new_contract = service.create_one(session, contract) new_contract = service.create_one(session, contract)
planned_contract_products = list(filter(lambda contract_product: contract_product.product.type == models.ProductType.PLANNED, new_contract.products)) occasional_contract_products = list(filter(lambda contract_product: contract_product.product.type == models.ProductType.OCCASIONAL, new_contract.products))
planneds = create_planned_dict(planned_contract_products) occasionals = create_occasional_dict(occasional_contract_products)
recurrents = list(map(lambda x: {"product": x.product, "quantity": x.quantity}, filter(lambda contract_product: contract_product.product.type == models.ProductType.RECCURENT, new_contract.products))) recurrents = list(map(lambda x: {"product": x.product, "quantity": x.quantity}, filter(lambda contract_product: contract_product.product.type == models.ProductType.RECCURENT, new_contract.products)))
recurrent_price = compute_recurrent_prices(recurrents, len(new_contract.form.shipments)) recurrent_price = compute_recurrent_prices(recurrents, len(new_contract.form.shipments))
total_price = '{:10.2f}'.format(recurrent_price + compute_planned_prices(planneds)) total_price = '{:10.2f}'.format(recurrent_price + compute_occasional_prices(occasionals))
cheques = list(map(lambda x: {"name": x.name, "value": x.value}, new_contract.cheques)) cheques = list(map(lambda x: {"name": x.name, "value": x.value}, new_contract.cheques))
# TODO: send contract to referer # TODO: send contract to referer
try: try:
pdf_bytes = generate_html_contract( pdf_bytes = generate_html_contract(
new_contract, new_contract,
cheques, cheques,
planneds, occasionals,
recurrents, recurrents,
recurrent_price, recurrent_price,
total_price total_price

View File

@@ -7,7 +7,7 @@ from weasyprint import HTML
def generate_html_contract( def generate_html_contract(
contract: models.Contract, contract: models.Contract,
cheques: list[dict], cheques: list[dict],
planneds: list[dict], occasionals: list[dict],
reccurents: list[dict], reccurents: list[dict],
recurrent_price: float, recurrent_price: float,
total_price: float total_price: float
@@ -32,7 +32,7 @@ def generate_html_contract(
member_phone=html.escape(contract.phone), member_phone=html.escape(contract.phone),
contract_start_date=contract.form.start, contract_start_date=contract.form.start,
contract_end_date=contract.form.end, contract_end_date=contract.form.end,
planneds=planneds, occasionals=occasionals,
recurrents=reccurents, recurrents=reccurents,
recurrent_price=recurrent_price, recurrent_price=recurrent_price,
total_price=total_price, total_price=total_price,

View File

@@ -290,11 +290,11 @@
</tbody> </tbody>
</table> </table>
</div> </div>
{% endif %} {% if planneds|length > 0 %} {% endif %} {% if occasionals|length > 0 %}
<div class="container"> <div class="container">
<h4>Produits planifiés (par livraison)</h4> <h4>Produits occasionnels (par livraison)</h4>
{% for plan in planneds %} {% for occasional in occasionals %}
<h5>{{plan.shipment.name}} {{plan.shipment.date}}</h5> <h5>{{occasional.shipment.name}} {{occasional.shipment.date}}</h5>
<table> <table>
<thead> <thead>
<tr> <tr>
@@ -306,7 +306,7 @@
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{% for product in plan.products %} {% for product in occasional.products %}
<tr> <tr>
<td>{{product.product.name}}</td> <td>{{product.product.name}}</td>
<td> <td>
@@ -328,7 +328,7 @@
{% endfor%} {% endfor%}
<tr> <tr>
<th scope="row" colspan="4">Total</th> <th scope="row" colspan="4">Total</th>
<td>{{plan.price}}€</td> <td>{{occasional.price}}€</td>
</tr> </tr>
</tbody> </tbody>
</table> </table>

View File

@@ -74,7 +74,7 @@ class Unit(StrEnum):
PIECE = "3" PIECE = "3"
class ProductType(StrEnum): class ProductType(StrEnum):
PLANNED = "1" OCCASIONAL = "1"
RECCURENT = "2" RECCURENT = "2"
class ShipmentProductLink(SQLModel, table=True): class ShipmentProductLink(SQLModel, table=True):

View File

@@ -1,140 +1,182 @@
{ {
"product name": "name of the product", "help": "help",
"product price": "price of the product", "how to use dashboard": "How to use the dashboard",
"product quantity": "quantity of the product", "product name": "product name",
"product quantity unit": "unit of the product quantity", "product price": "product price",
"product type": "type of the product", "product quantity": "product quantity",
"planned": "planned", "product quantity unit": "product quantity unit",
"planned products": "planned products", "product type": "product type",
"select products per shipment": "select products for each shipment", "occasional": "occasional",
"recurrent products": "recurrent products", "occasional products": "Occasional products per shipment",
"your selection in this category will apply for all shipments": "selection in this category applies to all shipments", "select products per shipment": "Select products for each shipment.",
"recurrent": "recurrent", "recurrent": "recurrent",
"product price kg": "price per kilogram", "recurrent products": "Recurrent products",
"product unit": "product unit", "your selection in this category will apply for all shipments": "your selection will apply to all shipments (Example: For 6 shipments, the product will be counted 6 times: once per shipment).",
"grams": "grams", "product price kg": "product price per kilogram",
"kilo": "kilogram", "product unit": "product sales unit",
"piece": "piece", "piece": "piece",
"in": "in",
"enter quantity": "enter quantity",
"filter by season": "filter by season", "filter by season": "filter by season",
"filter by form": "filter by form", "filter by form": "filter by form",
"filter by productor": "filter by productor", "filter by productor": "filter by producer",
"name": "name", "name": "name",
"season": "season", "season": "season",
"start": "start", "start": "start",
"end": "end", "end": "end",
"productor": "productor", "productor": "producer",
"referer": "referer", "referer": "referent",
"edit form": "edit form", "edit form": "edit contract form",
"form name": "name of the form", "form name": "contract form name",
"contract season": "contract season", "contract season": "contract season",
"contract season recommandation": "recommendation: <Season>-<year> (example: Winter-2025)", "contract season recommandation": "recommendation: <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 to display",
"number of shipment": "number of shipments", "number of shipment": "number of shipments",
"cancel": "cancel", "cancel": "cancel",
"create form": "create form", "create form": "create contract form",
"edit productor": "edit productor", "create productor": "create producer",
"remove productor": "remove productor", "edit productor": "edit producer",
"remove productor": "remove producer",
"home": "home", "home": "home",
"dashboard": "dashboard", "dashboard": "dashboard",
"filter by name": "filter by name", "filter by name": "filter by name",
"filter by type": "filter by type", "filter by type": "filter by type",
"address": "address", "address": "address",
"payment methods": "payment methods", "payment methods": "payment methods",
"cheque": "cheque",
"transfer": "transfer",
"type": "type", "type": "type",
"create productor": "create productor", "cheque": "cheque",
"productor name": "name of the productor", "transfer": "bank transfer",
"productor type": "type of the productor", "order name": "cheque payable to",
"productor address": "address of the productor", "productor name": "producer name",
"productor payment": "payment method of the productor", "productor type": "producer type",
"productor address": "producer address",
"productor payment": "producer payment methods",
"priceKg": "price per kilogram", "priceKg": "price per kilogram",
"quantity": "quantity", "quantity": "quantity",
"quantity unit": "unit of quantity", "quantity unit": "quantity unit",
"unit": "sell unit", "unit": "sales unit",
"price": "price", "price": "price",
"create product": "create product", "create product": "create product",
"informations": "information", "informations": "information",
"remove product": "remove product", "remove product": "remove product",
"edit product": "edit product", "edit product": "edit product",
"shipment name": "shipment name", "shipment name": "shipment name",
"shipment date": "shipment date",
"shipments": "shipments", "shipments": "shipments",
"shipment": "shipment", "shipment": "shipment",
"there is": "there is",
"for this contract": "for this contract",
"shipment date": "shipment date",
"shipment products": "shipment products", "shipment products": "shipment products",
"minimum shipment value": "minimum shipment value", "shipment form": "shipment related form",
"shipment form": "shipment form", "minimum shipment value": "minimum shipment value (€)",
"shipment products is necessary only for planned products (if all products are recurrent leave empty)": "shipment products required only for planned products (leave empty if all products are recurrent)", "shipment products is necessary only for occasional products (if all products are recurrent leave empty)": "shipment products configuration is only necessary for occasional products (leave empty if all products are recurrent).",
"recurrent product is for all shipments, planned product is for a specific shipment (see shipment form)": "recurrent products apply to all shipments, planned products apply to a specific shipment (see shipment form)", "recurrent product is for all shipments, occasional product is for a specific shipment (see shipment form)": "recurrent products are for all shipments, occasional products are for a specific shipment (see shipment form).",
"some contracts require a minimum value per shipment, ignore this field if it's not the case": "ignore this field if minimum shipment value is not required", "some contracts require a minimum value per shipment, ignore this field if it's not the case": "some contracts require a minimum value per shipment. Ignore this field if it does not apply to your contract.",
"minimum price for this shipment should be at least": "minimum price for this shipment", "minimum price for this shipment should be at least": "minimum price for this shipment should be at least",
"there is": "there is",
"for this contract": "for this contract.",
"remove shipment": "remove shipment", "remove shipment": "remove shipment",
"productors": "productors", "productors": "producers",
"products": "products", "products": "products",
"templates": "templates", "templates": "templates",
"users": "users", "users": "users",
"forms": "forms", "forms": "contract forms",
"all forms": "all forms", "all forms": "all contract forms",
"create new form": "create new form", "create new form": "create new contract form",
"actions": "actions", "actions": "actions",
"all productors": "all productors", "all productors": "all producers",
"all products": "all products", "all products": "all products",
"all shipments": "all shipments", "all shipments": "all shipments",
"all referers": "all referers", "all referers": "all referents",
"is required": "is required",
"a name": "a name", "a name": "a name",
"a season": "a season", "a season": "a season",
"a start date": "a start date", "a start date": "a start date",
"a end date": "a end date", "a end date": "an end date",
"a productor": "a productor", "a productor": "a producer",
"a referer": "a referer", "a referer": "a referent",
"a phone": "a phone", "a phone": "a phone number",
"a fistname": "a firstname", "a fistname": "a first name",
"a lastname": "a lastname", "a lastname": "a last name",
"a email": "an email", "a email": "an email address",
"a price or priceKg": "a price or price per kilogram",
"a address": "an address",
"a type": "a type",
"a form": "a contract form",
"a price": "a price",
"a priceKg": "a price per kilogram",
"a quantity": "a quantity",
"a product": "a product",
"a quantity unit": "a quantity unit",
"a payment method": "a payment method", "a payment method": "a payment method",
"a sell unit": "a sales unit",
"sell unit": "sales unit",
"product": "product",
"a shipment": "a shipment",
"the products": "the products",
"the shipments": "the shipments",
"link to the section": "link to section: {{section}}",
"to add a use the": "to add {{section}} use the button",
"to edit a use the": "to edit {{section}} use the button",
"to delete a use the": "to delete {{section}} use the button",
"button in top left of the page": "at the top left of the {{section}} page.",
"button in front of the line you want to edit": "in front of the line you want to edit (in the actions column).",
"button in front of the line you want to delete": "in front of the line you want to delete (in the actions column).",
"glossary": "glossary",
"start to create a productor in the productors section": "start by creating a producer in the \"Producers\" section.",
"add all products linked to this productor in the products section": "add your products linked to the producer in the \"Products\" section.",
"create your contract form, it will create a form in the home page (accessible to users)": "create your contract form in the \"Contract Forms\" section. Adding an entry here will create a form on the home page.",
"create shipments for your contract form": "create shipments for your contract",
"creation order": "creation order",
"dashboard is for referers only, with this dashboard you can create productors, products, forms and shipments": "The dashboard is only visible to referents. You can create your producer, products, contract forms, and shipments.",
"is defined by": "is defined by",
"a product type define the way it will be organized on the final contract form (showed to users) it can be reccurent or occassional. Recurrent products will be set for all shipments if selected by user, Occasional products can be choosen for each shipments": "a product type defines how it will be organized in the final contract form. It can be recurrent or occasional. Recurrent products will be set for all shipments if selected. Occasional products can be chosen for each shipment.",
"and/or": "and/or",
"form name recommandation": "recommendation: Contract <contract-type> (Example: Pork-Lamb Contract)",
"submit contract": "submit contract", "submit contract": "submit contract",
"example in user forms": "example in user contract form",
"occasional product": "occasional product",
"recurrent product": "recurrent product",
"with grams as product unit selected": "with grams selected as product unit",
"product example": "product example",
"payment methods are defined for a productor. At the end of a form a section payment method let the user select his prefered payment method": "payment methods are defined for a producer. At the end of the form, users can select their preferred payment method.",
"with cheque and transfer": "with cheque and transfer configured for the producer",
"mililiter": "milliliters (ml)",
"this field is optionnal a product can have a quantity if configured inside the product it will be shown inside the form": "this field is optional. It represents the product quantity and will be shown in the form.",
"this field is also optionnal if a product have a quantity you can select the correct unit (metric system). It will be shown next to product quantity inside the form": "this field is optional. It represents the measurement unit and will be shown next to the quantity.",
"with 150 set as quantity and g as quantity unit in product": "with 150 set as quantity and grams selected as quantity unit",
"all shipments should be recreated for each form creation": "shipments must be recreated for each new contract form.",
"a productor can be edited if its informations change, it should not be recreated for each contracts": "a producer can be edited if information changes. It should not be recreated for each contract.",
"a product can be edited if its informations change, it should not be recreated for each contracts": "a product can be edited if information changes. It should not be recreated for each contract.",
"a new contract form should be created for each new season, do not edit a previous contract and change it's values (for history purpose)": "a new contract form must be created for each new season. Do not edit past contracts.",
"grams": "grams (g)",
"kilo": "kilograms (kg)",
"liter": "liters (L)",
"success": "success", "success": "success",
"successfully edited user": "user edited successfully", "success edit": "{{entity}} correctly edited",
"successfully edited form": "form edited successfully", "success create": "{{entity}} correctly created",
"successfully edited product": "product edited successfully", "success delete": "{{entity}} correctly deleted",
"successfully edited productor": "productor edited successfully",
"successfully edited shipment": "shipment edited successfully",
"successfully created user": "user created successfully",
"successfully created form": "form created successfully",
"successfully created product": "product created successfully",
"successfully created productor": "productor created successfully",
"successfully created shipment": "shipment created successfully",
"error": "error", "error": "error",
"error editing user": "error editing user", "error edit": "error during edit {{entity}}",
"error editing form": "error editing form", "error create": "error during create {{entity}}",
"error editing product": "error editing product", "error delete": "error during suppress {{entity}}",
"error editing productor": "error editing productor", "of the user": "of the user",
"error editing shipment": "error editing shipment", "of the form": "of the form",
"error creating user": "error creating user", "of the product": "of the product",
"error creating form": "error creating form", "of the productor": "of the producer",
"error creating product": "error creating product", "of the shipment": "of the shipment",
"error creating productor": "error creating productor", "of the contract": "of the contract",
"error creating shipment": "error creating shipment", "there is no contract for now": "There is no contract at the moment.",
"error deleting user": "error deleting user", "for transfer method contact your referer or productor": "for bank transfer, contact your referent or producer.",
"error deleting form": "error deleting form",
"error deleting product": "error deleting product",
"error deleting productor": "error deleting productor",
"error deleting shipment": "error deleting shipment",
"there is no contract for now": "no contracts available currently",
"for transfer method contact your referer or productor": "for transfer method, contact your referer or productor",
"cheque quantity": "number of cheques", "cheque quantity": "number of cheques",
"enter cheque quantity": "enter number of cheques", "enter cheque quantity": "enter number of cheques",
"cheque id": "cheque identifier", "cheque id": "cheque identifier",
"cheque value": "cheque value", "cheque value": "cheque amount",
"enter cheque value": "enter cheque value", "enter cheque value": "enter cheque amount",
"enter payment method": "select your payment method",
"number of cheques between 1 and 3 cheques also enter your cheques identifiers, value is calculated automatically": "number of cheques between 1 and 3. Also enter cheque identifiers.",
"payment method": "payment method", "payment method": "payment method",
"enter payment method": "enter payment method", "choose payment method": "choose your payment method (you do not need to pay now).",
"choose payment method": "choose a payment method (no actual payments, information only)", "the product unit will be assigned to the quantity requested in the form": "the product unit defines the unit used in the contract form.",
"number of cheques between 1 and 3 cheques also enter your cheques identifiers, value is calculated automatically": "enter 1 to 3 cheques and their identifiers; values are calculated automatically", "all theses informations are for contract generation": "all this information is required for contract generation."
"the product unit will be assigned to the quantity requested in the form": "product unit matches quantity requested in the form",
"all theses informations are for contract generation": "all this information is used for contract generation"
} }

View File

@@ -1,15 +1,17 @@
{ {
"help": "aide",
"how to use dashboard": "comment utiliser le tableau de bord",
"product name": "nom du produit", "product name": "nom du produit",
"product price": "prix du produit", "product price": "prix du produit",
"product quantity": "quantité du produit", "product quantity": "quantité du produit",
"product quantity unit": "Unité de quantité du produit", "product quantity unit": "unité de quantité du produit",
"product type": "type de produit", "product type": "type de produit",
"planned": "planifié", "occasional": "occasionnel",
"planned products": "Produits planifiés par livraison", "occasional products": "produits occasionnels par livraison",
"select products per shipment": "Sélectionnez les produits pour chaque livraison.", "select products per shipment": "Sélectionnez les produits pour chaque livraison.",
"recurrent": "récurent", "recurrent": "récurent",
"recurrent products": "Produits récurrents", "recurrent products": "produits récurrents",
"your selection in this category will apply for all shipments": "votre sélection sera appliquée pour chaque livraisons (Exemple: Pour 6 livraisons, le produits sera comptés 6 fois : une fois par livraison).", "your selection in this category will apply for all shipments": "votre sélection sera appliquée pour chaque livraisons (Exemple: Pour 6 livraisons, le produits sera compté 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",
"piece": "pièce", "piece": "pièce",
@@ -46,8 +48,7 @@
"type": "type", "type": "type",
"cheque": "chèque", "cheque": "chèque",
"transfer": "virement", "transfer": "virement",
"order name": "Ordre du chèque", "order name": "ordre du chèque",
"IBAN": "IBAN",
"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",
@@ -55,7 +56,7 @@
"priceKg": "prix au kilo", "priceKg": "prix au kilo",
"quantity": "quantité", "quantity": "quantité",
"quantity unit": "unité de quantité", "quantity unit": "unité de quantité",
"unit": "Unité de vente", "unit": "unité de vente",
"price": "prix", "price": "prix",
"create product": "créer le produit", "create product": "créer le produit",
"informations": "informations", "informations": "informations",
@@ -68,8 +69,8 @@
"shipment products": "produits pour la livraison", "shipment products": "produits pour la livraison",
"shipment form": "formulaire lié a la livraison", "shipment form": "formulaire lié a la livraison",
"minimum shipment value": "valeur minimum d'une livraison (€)", "minimum shipment value": "valeur minimum d'une livraison (€)",
"shipment products is necessary only for planned products (if all products are recurrent leave empty)": "il est nécessaire de configurer les produits pour la livraison uniquement si il y a des produits planifiés (laisser vide si tous les produits sont récurents).", "shipment products is necessary only for occasional products (if all products are recurrent leave empty)": "il est nécessaire de configurer les produits pour la livraison uniquement si il y a des produits occasionnels (laisser vide si tous les produits sont récurents).",
"recurrent product is for all shipments, planned product is for a specific shipment (see shipment form)": "les produits récurrents sont pour toutes les livraisons, les produits planifiés sont pour une livraison particulière (voir formulaire de création de livraison).", "recurrent product is for all shipments, occasional product is for a specific shipment (see shipment form)": "les produits récurrents sont pour toutes les livraisons, les produits occasionnels sont pour une livraison particulière (voir formulaire de création de livraison).",
"some contracts require a minimum value per shipment, ignore this field if it's not the case": "certains contrats nécessitent une valeur minimum par livraison. Ce champ peut être ignoré sil ne sapplique pas à votre contrat.", "some contracts require a minimum value per shipment, ignore this field if it's not the case": "certains contrats nécessitent une valeur minimum par livraison. Ce champ peut être ignoré sil ne sapplique pas à votre contrat.",
"minimum price for this shipment should be at least": "le prix minimum d'une livraison doit être au moins de", "minimum price for this shipment should be at least": "le prix minimum d'une livraison doit être au moins de",
"there is": "il y a", "there is": "il y a",
@@ -98,44 +99,73 @@
"a fistname": "un prénom", "a fistname": "un prénom",
"a lastname": "un nom", "a lastname": "un nom",
"a email": "une adresse email", "a email": "une adresse email",
"a price or priceKg": "un prix ou un prix au kilo",
"a address": "une adresse",
"a type": "un type",
"a form": "un formulaire de contrat",
"a price": "un prix",
"a priceKg": "un prix au kilo",
"a quantity": "une quantité",
"a product": "un produit",
"a quantity unit": "une unité de quantité",
"a payment method": "une méthode de paiement", "a payment method": "une méthode de paiement",
"submit contract": "Envoyer le contrat", "a sell unit": "une unité de vente",
"sell unit": "unité de vente",
"product": "produit",
"a shipment": "une livraison",
"the products": "les produits",
"the shipments": "les livraisons",
"link to the section": "lien vers la section : {{section}}",
"to add a use the": "pour ajouter {{section}} utilisez le bouton",
"to edit a use the": "pour éditer {{section}} utilisez le bouton",
"to delete a use the": "pour supprimer {{section}} utilisez le bouton",
"button in top left of the page": "en haut à gauche de la page {{section}}.",
"button in front of the line you want to edit": "en face de la ligne que vous souhaitez éditer. (dans la colonne actions).",
"button in front of the line you want to delete": "en face de la ligne que vous souhaitez supprimer. (dans la colonne actions).",
"glossary": "glossaire",
"start to create a productor in the productors section": "commencez par créer un(e) producteur·trice dans la section \"Producteur·trices\".",
"add all products linked to this productor in the products section": "ajoutez vos produits liés au/à la producteur·trice dans la section \"Produits\".",
"create your contract form, it will create a form in the home page (accessible to users)": "créez votre formulaire de contrat dans la section \"Formulaire de contrat\". Ajouter une entrée dans cette section ajoutera un formulaire dans la page d'accueil.",
"create shipments for your contract form": "créez les livraisons pour votre contrat",
"creation order": "ordre de création",
"dashboard is for referers only, with this dashboard you can create productors, products, forms and shipments": "Le tableau de bord est visible uniquement pour les référents, vous pouvez créer votre producteur, vos produits, vos formulaires de contrat et vos livraison.",
"is defined by": "est defini par",
"a product type define the way it will be organized on the final contract form (showed to users) it can be reccurent or occassional. Recurrent products will be set for all shipments if selected by user, Occasional products can be choosen for each shipments": "un type de produit définit la manière dont un produit va être présenté aux amapiens dans le formulaire de contrat. Il peut être récurrent ou occasionnel. Un produit récurrent si selectionné sera compté pour toutes les livraisons. Un produit occasionnel sera facultatif pour chaques livraison (l'amapien devra selectionner la quantité voulue pour chaque livraisons).",
"and/or": "et/ou",
"form name recommandation": "recommandation : Contrat <contract-type> (Exemple : Contrat Porc-Agneau)",
"submit contract": "envoyer le contrat",
"example in user forms": "exemple dans le formulaire à destination des amapiens",
"occasional product": "produit occasionnel",
"recurrent product": "produit récurrent",
"with grams as product unit selected": "avec \"grammes\" selectionné pour l'unité de produit",
"product example": "exemple de produit",
"payment methods are defined for a productor. At the end of a form a section payment method let the user select his prefered payment method": "les méthodes de paiement sont définies par producteurs. À la fin du formulaire de contrat l'amapien pourra séléctionner sa méthode de paiement parmis celles que vous avez ajoutés.",
"with cheque and transfer": "avec chèques et virements configuré pour le producteur",
"mililiter": "mililitres (ml)", "mililiter": "mililitres (ml)",
"this field is optionnal a product can have a quantity if configured inside the product it will be shown inside the form": "ce champ est optionnel dans la configuration d'un produit, il représente la quantité d'un produit (poids d'une tranche de foie, poids d'un panier, taille d'un bocal...). Si ce champs est renseigné il sera affiché dans le formulaire à destination des amapiens.",
"this field is also optionnal if a product have a quantity you can select the correct unit (metric system). It will be shown next to product quantity inside the form": "ce champs est optionnel dans la configuation d'un produit, il représente l'unité de mesure associé à la quantité d'un produit (g, kg, ml, L). Si ce champs est renseigné il sera affiché dans le formulaire à destination des amapiens à coté de la quantité du produit.",
"with 150 set as quantity and g as quantity unit in product": "avec \"150\" en quantité de produit et \"grammes\" selectionné dans l'unité de quantité du produit",
"all shipments should be recreated for each form creation": "les livraisons étant liées à un formulaire elles doivent être recréés pour chaque nouveau formulaire.",
"a productor can be edited if its informations change, it should not be recreated for each contracts": "un(e) producteur·trice peut être édité si ses informations changent, il/elle ne doit pas être recréé pour chaque nouveau contrat.",
"a product can be edited if its informations change, it should not be recreated for each contracts": "un produit peut être édité si ses informations changent, il ne doit pas être recréé pour chaque nouveau formulaire de contrat.",
"a new contract form should be created for each new season, do not edit a previous contract and change it's values (for history purpose)": "un formulaire de contrat doit être créé pour chaque nouvelle saison, pour des raison d'historique, n'éditez pas un formulaire de contrat passé pour une nouvelle saison, recréez en un nouveau.",
"grams": "grammes (g)", "grams": "grammes (g)",
"kilo": "kilogrammes (kg)", "kilo": "kilogrammes (kg)",
"liter": "litres (L)", "liter": "litres (L)",
"success": "succès", "success": "succès",
"successfully edited user": "utilisateur·trice correctement édité", "success edit": "{{entity}} correctement édité",
"successfully edited form": "formulaire correctement édité", "success create": "{{entity}} correctement créé",
"successfully edited product": "produit correctement édité", "success delete": "{{entity}} correctement supprimé",
"successfully edited productor": "producteur·trice correctement édité(e)",
"successfully edited shipment": "livraison correctement éditée",
"successfully created user": "utilisateur·trice correctement créé(e)",
"successfully created form": "formulaire correctement créé",
"successfully created product": "produit correctement créé",
"successfully created productor": "producteur·trice correctement créé(e)",
"successfully created shipment": "livraison correctement créée",
"successfully deleted user": "utilisateur·trice correctement supprimé",
"successfully deleted form": "formulaire correctement supprimé",
"successfully deleted product": "produit correctement supprimé",
"successfully deleted productor": "producteur·trice correctement supprimé(e)",
"successfully deleted shipment": "livraison correctement supprimée",
"error": "erreur", "error": "erreur",
"error editing user": "erreur pendant l'édition de l'utilisateur·trice", "error edit": "erreur pendant l'édition {{entity}}",
"error editing form": "erreur pendant l'édition du formulaire", "error create": "erreur pendant la création {{entity}}",
"error editing product": "erreur pendant l'édition du produit", "error delete": "erreur pendant la suppression {{entity}}",
"error editing productor": "erreur pendant l'édition du producteur·trice", "of the user": "de l'utilisateur·trice",
"error editing shipment": "erreur pendant l'édition de la livraison", "of the form": "du formulaire",
"error creating user": "erreur pendant la création de l'utilisateur·trice", "of the product": "du produit",
"error creating form": "erreur pendant la création du formulaire", "of the productor": "du producteur·trice",
"error creating product": "erreur pendant la création du produit", "of the shipment": "de la livraison",
"error creating productor": "erreur pendant la création du producteur·trice", "of the contract": "du contrat",
"error creating shipment": "erreur pendant la création de la livraison",
"error deleting user": "erreur pendant la suppression de l'utilisateur·trice",
"error deleting form": "erreur pendant la suppression du formulaire",
"error deleting product": "erreur pendant la suppression du produit",
"error deleting productor": "erreur pendant la suppression du producteur·trice",
"error deleting shipment": "erreur pendant la suppression de la livraison",
"there is no contract for now": "Il n'y a pas de contrats pour le moment.", "there is no contract for now": "Il n'y a pas de contrats pour le moment.",
"for transfer method contact your referer or productor": "pour mettre en place le virement automatique, contactez votre référent ou le producteur.", "for transfer method contact your referer or productor": "pour mettre en place le virement automatique, contactez votre référent ou le producteur.",
"cheque quantity": "quantité de chèques (pour le paiement en plusieurs fois)", "cheque quantity": "quantité de chèques (pour le paiement en plusieurs fois)",
@@ -147,6 +177,6 @@
"number of cheques between 1 and 3 cheques also enter your cheques identifiers, value is calculated automatically": "nombre de chèques entre 1 et 3, entrez également les identifiants des chèques utilisés.", "number of cheques between 1 and 3 cheques also enter your cheques identifiers, value is calculated automatically": "nombre de chèques entre 1 et 3, entrez également les identifiants des chèques utilisés.",
"payment method": "méthode de paiement", "payment method": "méthode de paiement",
"choose payment method": "choisissez votre méthode de paiement (vous n'avez pas à payer tout de suite, uniquement renseigner comment vous souhaitez régler votre commande).", "choose payment method": "choisissez votre méthode de paiement (vous n'avez pas à payer tout de suite, uniquement renseigner comment vous souhaitez régler votre commande).",
"the product unit will be assigned to the quantity requested in the form": "L'unité de vente du produit définit l'unité associée à la quantité demandée dans le formulaire des amapiens.", "the product unit will be assigned to the quantity requested in the form": "l'unité de vente du produit définit l'unité associée à la quantité demandée dans le formulaire des amapiens.",
"all theses informations are for contract generation": "ces informations sont nécessaires pour la génération de contrat." "all theses informations are for contract generation": "ces informations sont nécessaires pour la génération de contrat."
} }

View File

@@ -77,6 +77,7 @@ export default function FormModal({ opened, onClose, currentForm, handleSubmit }
<TextInput <TextInput
label={t("form name", { capfirst: true })} label={t("form name", { capfirst: true })}
placeholder={t("form name", { capfirst: true })} placeholder={t("form name", { capfirst: true })}
description={t("form name recommandation", { capfirst: true })}
radius="sm" radius="sm"
withAsterisk withAsterisk
{...form.getInputProps("name")} {...form.getInputProps("name")}

View File

@@ -14,7 +14,7 @@ export function Navbar() {
<NavLink <NavLink
className={"navLink"} className={"navLink"}
aria-label={t("dashboard")} aria-label={t("dashboard")}
to="/dashboard/productors" to="/dashboard/help"
> >
{t("dashboard", { capfirst: true })} {t("dashboard", { capfirst: true })}
</NavLink> </NavLink>

View File

@@ -35,7 +35,7 @@ export function ProductForm({ inputForm, product, shipment }: ProductFormProps)
placeholder={`${t("enter quantity", { capfirst: true })} ${t("in")} ${t(ProductUnit[product.unit])}`} placeholder={`${t("enter quantity", { capfirst: true })} ${t("in")} ${t(ProductUnit[product.unit])}`}
{...inputForm.getInputProps( {...inputForm.getInputProps(
shipment shipment
? `products.planned-${shipment.id}-${product.id}` ? `products.occasional-${shipment.id}-${product.id}`
: `products.recurrent-${product.id}`, : `products.recurrent-${product.id}`,
)} )}
/> />

View File

@@ -41,19 +41,19 @@ export function ProductModal({ opened, onClose, currentProduct, handleSubmit }:
}, },
validate: { validate: {
name: (value) => name: (value) =>
!value ? `${t("name", { capfirst: true })} ${t("is required")}` : null, !value ? `${t("a name", { capfirst: true })} ${t("is required")}` : null,
unit: (value) => unit: (value) =>
!value ? `${t("unit", { capfirst: true })} ${t("is required")}` : null, !value ? `${t("a sell unit", { capfirst: true })} ${t("is required")}` : null,
price: (value, values) => price: (value, values) =>
!value && !values.price_kg !value && !values.price_kg
? `${t("price or price_kg", { capfirst: true })} ${t("is required")}` ? `${t("a price or priceKg", { capfirst: true })} ${t("is required")}`
: null, : null,
price_kg: (value, values) => price_kg: (value, values) =>
!value && !values.price !value && !values.price
? `${t("price or price_kg", { capfirst: true })} ${t("is required")}` ? `${t("a price or priceKg", { capfirst: true })} ${t("is required")}`
: null, : null,
type: (value) => type: (value) =>
!value ? `${t("type", { capfirst: true })} ${t("is required")}` : null, !value ? `${t("a type", { capfirst: true })} ${t("is required")}` : null,
productor_id: (value) => productor_id: (value) =>
!value ? `${t("productor", { capfirst: true })} ${t("is required")}` : null, !value ? `${t("productor", { capfirst: true })} ${t("is required")}` : null,
}, },
@@ -79,35 +79,27 @@ export function ProductModal({ opened, onClose, currentProduct, handleSubmit }:
data={productorsSelect || []} data={productorsSelect || []}
{...form.getInputProps("productor_id")} {...form.getInputProps("productor_id")}
/> />
<Group grow> <TextInput
<TextInput label={t("product name", { capfirst: true })}
label={t("product name", { capfirst: true })} placeholder={t("product name", { capfirst: true })}
placeholder={t("product name", { capfirst: true })} radius="sm"
radius="sm" withAsterisk
{...form.getInputProps("name")} {...form.getInputProps("name")}
/> />
<Select <Select
label={ label={t("product type", { capfirst: true })}
<InputLabel placeholder={t("product type", { capfirst: true })}
label={t("product type", { capfirst: true })} radius="sm"
info={t( description={t("a product type define the way it will be organized on the final contract form (showed to users) it can be reccurent or occassional. Recurrent products will be set for all shipments if selected by user, Occasional products can be choosen for each shipments", {capfirst: true})}
"recurrent product is for all shipments, planned product is for a specific shipment (see shipment form)", searchable
{ capfirst: true }, clearable
)} withAsterisk
isRequired data={[
/> { value: "1", label: t("occasional", { capfirst: true }) },
} { value: "2", label: t("recurrent", { capfirst: true }) },
placeholder={t("product type", { capfirst: true })} ]}
radius="sm" {...form.getInputProps("type")}
searchable />
clearable
data={[
{ value: "1", label: t("planned", { capfirst: true }) },
{ value: "2", label: t("recurrent", { capfirst: true }) },
]}
{...form.getInputProps("type")}
/>
</Group>
<Select <Select
label={t("product unit", { capfirst: true })} label={t("product unit", { capfirst: true })}
placeholder={t("product unit", { capfirst: true })} placeholder={t("product unit", { capfirst: true })}

View File

@@ -22,7 +22,7 @@ export default function ShipmentForm({
}: ShipmentFormProps) { }: ShipmentFormProps) {
const shipmentPrice = useMemo(() => { const shipmentPrice = useMemo(() => {
const values = Object.entries(inputForm.getValues().products).filter( const values = Object.entries(inputForm.getValues().products).filter(
([key]) => key.includes("planned") && key.split("-")[1] === String(shipment.id), ([key]) => key.includes("occasional") && key.split("-")[1] === String(shipment.id),
); );
return computePrices(values, shipment.products); return computePrices(values, shipment.products);
}, [inputForm, shipment.products, shipment.id]); }, [inputForm, shipment.products, shipment.id]);

View File

@@ -101,7 +101,7 @@ export default function ShipmentModal({
label={t("shipment products", { capfirst: true })} label={t("shipment products", { capfirst: true })}
placeholder={t("shipment products", { capfirst: true })} placeholder={t("shipment products", { capfirst: true })}
description={t( description={t(
"shipment products is necessary only for planned products (if all products are recurrent leave empty)", "shipment products is necessary only for occasional products (if all products are recurrent leave empty)",
{ capfirst: true }, { capfirst: true },
)} )}
data={productsSelect || []} data={productsSelect || []}

View File

@@ -1,5 +1,5 @@
import i18next from "i18next"; import i18next from "i18next";
// import LanguageDetector from "i18next-browser-languagedetector"; import LanguageDetector from "i18next-browser-languagedetector";
import { Settings } from "luxon"; import { Settings } from "luxon";
import { initReactI18next } from "react-i18next"; import { initReactI18next } from "react-i18next";
@@ -13,6 +13,7 @@ const resources = {
}; };
i18next i18next
.use(LanguageDetector)
.use(initReactI18next) .use(initReactI18next)
.init({ .init({
resources: resources, resources: resources,

View File

@@ -114,7 +114,7 @@ export function Contract() {
form.shipments.forEach((shipment) => { form.shipments.forEach((shipment) => {
shipment.products.forEach((product) => { shipment.products.forEach((product) => {
const key = `planned-${shipment.id}-${product.id}`; const key = `occasional-${shipment.id}-${product.id}`;
if (result[key] === undefined || result[key] === "") { if (result[key] === undefined || result[key] === "") {
result[key] = 0; result[key] = 0;
} }
@@ -250,7 +250,7 @@ export function Contract() {
) : null} ) : null}
{shipments.some((shipment) => shipment.products.length > 0) ? ( {shipments.some((shipment) => shipment.products.length > 0) ? (
<> <>
<Title order={3}>{t("planned products")}</Title> <Title order={3}>{t("occasional products")}</Title>
<Text>{t("select products per shipment")}</Text> <Text>{t("select products per shipment")}</Text>
<Accordion defaultValue={"0"}> <Accordion defaultValue={"0"}>
{shipments.map((shipment, index) => ( {shipments.map((shipment, index) => (

View File

@@ -11,10 +11,11 @@ export default function Dashboard() {
w={{ base: "100%", md: "80%", lg: "60%" }} w={{ base: "100%", md: "80%", lg: "60%" }}
orientation={"horizontal"} orientation={"horizontal"}
value={location.pathname.split("/")[2]} value={location.pathname.split("/")[2]}
defaultValue={"productors"} defaultValue={"help"}
onChange={(value) => navigate(`/dashboard/${value}`)} onChange={(value) => navigate(`/dashboard/${value}`)}
> >
<Tabs.List> <Tabs.List>
<Tabs.Tab value="help">{t("help", { capfirst: true })}</Tabs.Tab>
<Tabs.Tab value="productors">{t("productors", { capfirst: true })}</Tabs.Tab> <Tabs.Tab value="productors">{t("productors", { capfirst: true })}</Tabs.Tab>
<Tabs.Tab value="products">{t("products", { capfirst: true })}</Tabs.Tab> <Tabs.Tab value="products">{t("products", { capfirst: true })}</Tabs.Tab>
<Tabs.Tab value="forms">{t("forms", { capfirst: true })}</Tabs.Tab> <Tabs.Tab value="forms">{t("forms", { capfirst: true })}</Tabs.Tab>

View File

@@ -0,0 +1,231 @@
import { t } from "@/config/i18n";
import { Accordion, ActionIcon, Blockquote, Group, NumberInput, Paper, Select, Stack, TableOfContents, Text, TextInput, Title } from "@mantine/core";
import { IconEdit, IconInfoCircle, IconLink, IconPlus, IconTestPipe, IconX } from "@tabler/icons-react";
import { Link } from "react-router";
export function Help() {
return (
<Stack>
<Title order={2}>{t("how to use dashboard", {capfirst: true})}</Title>
<Text>
{t("dashboard is for referers only, with this dashboard you can create productors, products, forms and shipments", {capfirst: true})}
</Text>
<TableOfContents
variant="filled"
color="blue"
size="sm"
radius="sm"
scrollSpyOptions={{
selector: 'h3, h4',
}}
getControlProps={({ data }) => ({
onClick: () => data.getNode().scrollIntoView(),
children: data.value,
})}
/>
<Title order={3}>{t("creation order", {capfirst: true})}</Title>
<Stack gap="1">
<Title order={4}>{t("a productor", {capfirst: true})}</Title>
<Text>
{t("start to create a productor in the productors section", {capfirst: true})}
<ActionIcon
ml="4"
size="xs"
component={Link}
to="/dashboard/productors"
aria-label={t("link to the section", {capfirst: true, section: t("productors")})}
style={{cursor: "pointer", alignSelf: "center"}}
><IconLink/></ActionIcon>
</Text>
<Text>{t("a productor can be edited if its informations change, it should not be recreated for each contracts", {capfirst: true})}</Text>
<Blockquote
mt="md"
icon={<IconInfoCircle/>}
>
<Text>{t("to add a use the", {capfirst: true, section: t("a productor")})} <ActionIcon size="sm"><IconPlus/></ActionIcon> {t("button in top left of the page", {section: t("productors")})}</Text>
<Text>{t("to edit a use the", {capfirst: true, section: t("a productor")})} <ActionIcon size="sm"><IconEdit/></ActionIcon> {t("button in front of the line you want to edit")}</Text>
<Text>{t("to delete a use the", {capfirst: true, section: t("a productor")})} <ActionIcon size="sm" color="red"><IconX/></ActionIcon> {t("button in front of the line you want to delete")}</Text>
</Blockquote>
</Stack>
<Stack gap="1">
<Title order={4}>{t("the products", {capfirst: true})}</Title>
<Text>
{t("add all products linked to this productor in the products section", {capfirst: true})}
<ActionIcon
ml="4"
size="xs"
component={Link}
to="/dashboard/products"
aria-label={t("link to the section", {capfirst: true, section: t("products")})}
style={{cursor: "pointer", alignSelf: "center"}}
><IconLink/></ActionIcon>
</Text>
<Text>{t("a product can be edited if its informations change, it should not be recreated for each contracts", {capfirst: true})}</Text>
<Blockquote
mt="md"
icon={<IconInfoCircle/>}
>
<Text>{t("to add a use the", {capfirst: true, section: t("a product")})} <ActionIcon size="sm"><IconPlus/></ActionIcon> {t("button in top left of the page", {section: t("products")})}</Text>
<Text>{t("to edit a use the", {capfirst: true, section: t("a productor")})} <ActionIcon size="sm"><IconEdit/></ActionIcon> {t("button in front of the line you want to edit")}</Text>
<Text>{t("to delete a use the", {capfirst: true, section: t("a productor")})} <ActionIcon size="sm" color="red"><IconX/></ActionIcon> {t("button in front of the line you want to delete")}</Text>
</Blockquote>
</Stack>
<Stack gap="1">
<Title order={4}>{t("a form", {capfirst: true})}</Title>
<Text>
{t("create your contract form, it will create a form in the home page (accessible to users)", {capfirst: true})}
<ActionIcon
ml="4"
size="xs"
component={Link}
to="/dashboard/products"
aria-label={t("link to the section", {capfirst: true, section: t("forms")})}
style={{cursor: "pointer", alignSelf: "center"}}
><IconLink/></ActionIcon>
</Text>
<Text>{t("a new contract form should be created for each new season, do not edit a previous contract and change it's values (for history purpose)", {capfirst: true})}</Text>
<Blockquote
mt="md"
icon={<IconInfoCircle/>}
>
<Text>{t("to add a use the", {capfirst: true, section: t("a form")})} <ActionIcon size="sm"><IconPlus/></ActionIcon> {t("button in top left of the page", {section: t("forms")})}</Text>
<Text>{t("to edit a use the", {capfirst: true, section: t("a productor")})} <ActionIcon size="sm"><IconEdit/></ActionIcon> {t("button in front of the line you want to edit")}</Text>
<Text>{t("to delete a use the", {capfirst: true, section: t("a productor")})} <ActionIcon size="sm" color="red"><IconX/></ActionIcon> {t("button in front of the line you want to delete")}</Text>
</Blockquote>
</Stack>
<Stack gap="1">
<Title order={4}>{t("the shipments", {capfirst: true})}</Title>
<Text>
{t("create shipments for your contract form", {capfirst: true})}
<ActionIcon
ml="4"
size="xs"
component={Link}
to="/dashboard/products"
aria-label={t("link to the section", {capfirst: true, section: t("shipments")})}
style={{cursor: "pointer", alignSelf: "center"}}
><IconLink/></ActionIcon>
</Text>
<Text>
{t("all shipments should be recreated for each form creation", {capfirst: true})}
</Text>
<Blockquote
mt="md"
icon={<IconInfoCircle/>}
>
<Text>{t("to add a use the", {capfirst: true, section: t("a shipment")})} <ActionIcon size="sm"><IconPlus/></ActionIcon> {t("button in top left of the page", {section: t("shipments")})}</Text>
<Text>{t("to edit a use the", {capfirst: true, section: t("a productor")})} <ActionIcon size="sm"><IconEdit/></ActionIcon> {t("button in front of the line you want to edit")}</Text>
<Text>{t("to delete a use the", {capfirst: true, section: t("a productor")})} <ActionIcon size="sm" color="red"><IconX/></ActionIcon> {t("button in front of the line you want to delete")}</Text>
</Blockquote>
</Stack>
<Title order={3}>{t("glossary", {capfirst: true})}</Title>
<Stack>
<Title order={4} fw={700}>{t("product type", {capfirst: true})}</Title>
<Text>{t("a product type define the way it will be organized on the final contract form (showed to users) it can be reccurent or occassional. Recurrent products will be set for all shipments if selected by user, Occasional products can be choosen for each shipments", {capfirst: true})}</Text>
<Stack>
<Text>{t("example in user forms", {capfirst: true})} ({t("recurrent product")}) :</Text>
<Blockquote
color="black"
icon={<IconTestPipe/>}
>
<Title order={5}>{t('recurrent products')}</Title>
<Text size="sm">
{t("your selection in this category will apply for all shipments", {
capfirst: true,
})}
</Text>
<NumberInput
label={t("product example", {capfirst: true})}
description={`${t("enter quantity", { capfirst: true })} ${t("in")} ${t("piece")}`}
aria-label={t("enter quantity")}
placeholder={`${t("enter quantity", { capfirst: true })} ${t("in")} ${t("piece")}`}
/>
</Blockquote>
<Text>{t("example in user forms", {capfirst: true})} ({t("occasional product")}) :</Text>
<Blockquote
color="black"
icon={<IconTestPipe/>}
>
<Title order={5}>{t('occasional products')}</Title>
<Text>{t("select products per shipment")}</Text>
<Accordion defaultValue={"0"}>
<Accordion.Item value={"example1"}>
<Accordion.Control>{t("shipment", {capfirst: true})} 1</Accordion.Control>
<Accordion.Panel>
<NumberInput
label={t("product example", {capfirst: true})}
description={`${t("enter quantity", { capfirst: true })} ${t("in")} ${t("piece")}`}
aria-label={t("enter quantity")}
placeholder={`${t("enter quantity", { capfirst: true })} ${t("in")} ${t("piece")}`}
/>
</Accordion.Panel>
</Accordion.Item>
<Accordion.Item value={"example2"}>
<Accordion.Control>{t("shipment", {capfirst: true})} 2</Accordion.Control>
<Accordion.Panel>
<NumberInput
label={t("product example", {capfirst: true})}
description={`${t("enter quantity", { capfirst: true })} ${t("in")} ${t("piece")}`}
aria-label={t("enter quantity")}
placeholder={`${t("enter quantity", { capfirst: true })} ${t("in")} ${t("piece")}`}
/>
</Accordion.Panel>
</Accordion.Item>
</Accordion>
</Blockquote>
</Stack>
<Title order={4} fw={700}>{t("sell unit", {capfirst: true})}</Title>
<Text>{t("the product unit will be assigned to the quantity requested in the form")}</Text>
<Stack w={"100%"}>
<Text>{t("example in user forms", {capfirst: true})} ({t("with grams as product unit selected")}) :</Text>
<Blockquote
color="black"
icon={<IconTestPipe/>}
>
<NumberInput
label={t("product example", {capfirst: true})}
description={`${t("enter quantity", { capfirst: true })} ${t("in")} ${t("grams")}`}
aria-label={t("enter quantity")}
placeholder={`${t("enter quantity", { capfirst: true })} ${t("in")} ${t("grams")}`}
/>
</Blockquote>
</Stack>
<Title order={4} fw={700}>{t("payment methods", {capfirst: true})}</Title>
<Text>{t("payment methods are defined for a productor. At the end of a form a section payment method let the user select his prefered payment method", {capfirst: true})}</Text>
<Stack>
<Text>{t("example in user forms", {capfirst: true})} ({t("with cheque and transfer")}) :</Text>
<Blockquote
color="black"
icon={<IconTestPipe/>}
>
<Title order={5}>{t("payment method", { capfirst: true })}</Title>
<Select
label={t("payment method", { capfirst: true })}
placeholder={t("enter payment method", { capfirst: true })}
description={t("choose payment method", { capfirst: true })}
data={[t("cheque", {capfirst: true}), t("transfer", {capfirst: true})]}
/>
</Blockquote>
</Stack>
<Title order={4} fw={700}>{t("product quantity", {capfirst: true})}</Title>
<Text>{t("this field is optionnal a product can have a quantity if configured inside the product it will be shown inside the form", {capfirst: true})}</Text>
<Title order={4} fw={700}>{t("product quantity unit", {capfirst: true})}</Title>
<Text>{t("this field is also optionnal if a product have a quantity you can select the correct unit (metric system). It will be shown next to product quantity inside the form", {capfirst: true})}</Text>
<Text>{t("example in user forms", {capfirst: true})} ({t("with 150 set as quantity and g as quantity unit in product")}):</Text>
<Blockquote
color="black"
icon={<IconTestPipe/>}
>
<NumberInput
label={`${t("product example", {capfirst: true})} 150g`}
description={`${t("enter quantity", { capfirst: true })} ${t("in")} ${t("pieces")}`}
aria-label={t("enter quantity")}
placeholder={`${t("enter quantity", { capfirst: true })} ${t("in")} ${t("pieces")}`}
/>
</Blockquote>
</Stack>
</Stack>
);
}

View File

@@ -142,7 +142,7 @@ export default function Shipments() {
<Table.Tr> <Table.Tr>
<Table.Th>{t("name", { capfirst: true })}</Table.Th> <Table.Th>{t("name", { capfirst: true })}</Table.Th>
<Table.Th>{t("date", { capfirst: true })}</Table.Th> <Table.Th>{t("date", { capfirst: true })}</Table.Th>
<Table.Th>{t("formulare", { capfirst: true })}</Table.Th> <Table.Th>{t("form", { capfirst: true })}</Table.Th>
<Table.Th>{t("actions", { capfirst: true })}</Table.Th> <Table.Th>{t("actions", { capfirst: true })}</Table.Th>
</Table.Tr> </Table.Tr>
</Table.Thead> </Table.Thead>

View File

@@ -11,6 +11,7 @@ import Shipments from "./pages/Shipments";
import { Contract } from "./pages/Contract"; import { Contract } from "./pages/Contract";
import { NotFound } from "./pages/NotFound"; import { NotFound } from "./pages/NotFound";
import Contracts from "./pages/Contracts"; import Contracts from "./pages/Contracts";
import { Help } from "./pages/Help";
export const router = createBrowserRouter([ export const router = createBrowserRouter([
{ {
@@ -24,6 +25,7 @@ export const router = createBrowserRouter([
path: "/dashboard", path: "/dashboard",
Component: Dashboard, Component: Dashboard,
children: [ children: [
{ path: "help", Component: Help },
{ path: "productors", Component: Productors }, { path: "productors", Component: Productors },
{ path: "productors/create", Component: Productors }, { path: "productors/create", Component: Productors },
{ path: "productors/:id/edit", Component: Productors }, { path: "productors/:id/edit", Component: Productors },

View File

@@ -58,14 +58,14 @@ export function useCreateShipment() {
onSuccess: async () => { onSuccess: async () => {
notifications.show({ notifications.show({
title: t("success", { capfirst: true }), title: t("success", { capfirst: true }),
message: t("successfully created shipment", { capfirst: true }), message: t("success create", { capfirst: true, entity: t("shipment") }),
}); });
await queryClient.invalidateQueries({ queryKey: ["shipments"] }); await queryClient.invalidateQueries({ queryKey: ["shipments"] });
}, },
onError: (error) => { onError: (error) => {
notifications.show({ notifications.show({
title: t("error", { capfirst: true }), title: t("error", { capfirst: true }),
message: error?.message || t(`error editing shipment`, { capfirst: true }), message: error?.message || t(`error create`, { capfirst: true, entity: t("of the shipment") }),
color: "red", color: "red",
}); });
}, },
@@ -88,14 +88,14 @@ export function useEditShipment() {
onSuccess: async () => { onSuccess: async () => {
notifications.show({ notifications.show({
title: t("success", { capfirst: true }), title: t("success", { capfirst: true }),
message: t("successfully edited shipment", { capfirst: true }), message: t("success edit", { capfirst: true, entity: t("shipment") }),
}); });
await queryClient.invalidateQueries({ queryKey: ["shipments"] }); await queryClient.invalidateQueries({ queryKey: ["shipments"] });
}, },
onError: (error) => { onError: (error) => {
notifications.show({ notifications.show({
title: t("error", { capfirst: true }), title: t("error", { capfirst: true }),
message: error?.message || t(`error editing shipment`, { capfirst: true }), message: error?.message || t(`error edit`, { capfirst: true, entity: t("of the shipment") }),
color: "red", color: "red",
}); });
}, },
@@ -116,14 +116,14 @@ export function useDeleteShipment() {
onSuccess: async () => { onSuccess: async () => {
notifications.show({ notifications.show({
title: t("success", { capfirst: true }), title: t("success", { capfirst: true }),
message: t("successfully deleted shipment", { capfirst: true }), message: t("success delete", { capfirst: true, entity: t("shipment") }),
}); });
await queryClient.invalidateQueries({ queryKey: ["shipments"] }); await queryClient.invalidateQueries({ queryKey: ["shipments"] });
}, },
onError: (error) => { onError: (error) => {
notifications.show({ notifications.show({
title: t("error", { capfirst: true }), title: t("error", { capfirst: true }),
message: error?.message || t(`error deleting shipment`, { capfirst: true }), message: error?.message || t(`error delete`, { capfirst: true, entity: t("of the contract") }),
color: "red", color: "red",
}); });
}, },
@@ -169,14 +169,14 @@ export function useCreateProductor() {
onSuccess: async () => { onSuccess: async () => {
notifications.show({ notifications.show({
title: t("success", { capfirst: true }), title: t("success", { capfirst: true }),
message: t("successfully created productor", { capfirst: true }), message: t("success create", { capfirst: true, entity: t("productor") }),
}); });
await queryClient.invalidateQueries({ queryKey: ["productors"] }); await queryClient.invalidateQueries({ queryKey: ["productors"] });
}, },
onError: (error) => { onError: (error) => {
notifications.show({ notifications.show({
title: t("error", { capfirst: true }), title: t("error", { capfirst: true }),
message: error?.message || t(`error editing productor`, { capfirst: true }), message: error?.message || t(`error create`, { capfirst: true, entity: t("of the productor") }),
color: "red", color: "red",
}); });
}, },
@@ -199,14 +199,14 @@ export function useEditProductor() {
onSuccess: async () => { onSuccess: async () => {
notifications.show({ notifications.show({
title: t("success", { capfirst: true }), title: t("success", { capfirst: true }),
message: t("successfully edited productor", { capfirst: true }), message: t("success edit", { capfirst: true, entity: t("productor") }),
}); });
await queryClient.invalidateQueries({ queryKey: ["productors"] }); await queryClient.invalidateQueries({ queryKey: ["productors"] });
}, },
onError: (error) => { onError: (error) => {
notifications.show({ notifications.show({
title: t("error", { capfirst: true }), title: t("error", { capfirst: true }),
message: error?.message || t(`error editing productor`, { capfirst: true }), message: error?.message || t(`error edit`, { capfirst: true, entity: t("of the productor") }),
color: "red", color: "red",
}); });
}, },
@@ -227,14 +227,14 @@ export function useDeleteProductor() {
onSuccess: async () => { onSuccess: async () => {
notifications.show({ notifications.show({
title: t("success", { capfirst: true }), title: t("success", { capfirst: true }),
message: t("successfully deleted productor", { capfirst: true }), message: t("success delete", { capfirst: true, entity: t("productor") }),
}); });
await queryClient.invalidateQueries({ queryKey: ["productors"] }); await queryClient.invalidateQueries({ queryKey: ["productors"] });
}, },
onError: (error) => { onError: (error) => {
notifications.show({ notifications.show({
title: t("error", { capfirst: true }), title: t("error", { capfirst: true }),
message: error?.message || t(`error deleting productor`, { capfirst: true }), message: error?.message || t(`error delete`, { capfirst: true, entity: t("of the productor") }),
color: "red", color: "red",
}); });
}, },
@@ -297,14 +297,14 @@ export function useDeleteForm() {
onSuccess: async () => { onSuccess: async () => {
notifications.show({ notifications.show({
title: t("success", { capfirst: true }), title: t("success", { capfirst: true }),
message: t("successfully deleted form", { capfirst: true }), message: t("success delete", { capfirst: true, entity: t("form") }),
}); });
await queryClient.invalidateQueries({ queryKey: ["forms"] }); await queryClient.invalidateQueries({ queryKey: ["forms"] });
}, },
onError: (error) => { onError: (error) => {
notifications.show({ notifications.show({
title: t("error", { capfirst: true }), title: t("error", { capfirst: true }),
message: error?.message || t(`error deleting form`, { capfirst: true }), message: error?.message || t(`error delete`, { capfirst: true, entity: t("of the form") }),
color: "red", color: "red",
}); });
}, },
@@ -327,14 +327,14 @@ export function useEditForm() {
onSuccess: async () => { onSuccess: async () => {
notifications.show({ notifications.show({
title: t("success", { capfirst: true }), title: t("success", { capfirst: true }),
message: t("successfully edited form", { capfirst: true }), message: t("success edit", { capfirst: true, entity: t("form") }),
}); });
await queryClient.invalidateQueries({ queryKey: ["forms"] }); await queryClient.invalidateQueries({ queryKey: ["forms"] });
}, },
onError: (error) => { onError: (error) => {
notifications.show({ notifications.show({
title: t("error", { capfirst: true }), title: t("error", { capfirst: true }),
message: error?.message || t(`error editing form`, { capfirst: true }), message: error?.message || t(`error edit`, { capfirst: true, entity: t("of the form") }),
color: "red", color: "red",
}); });
}, },
@@ -380,14 +380,14 @@ export function useCreateProduct() {
onSuccess: async () => { onSuccess: async () => {
notifications.show({ notifications.show({
title: t("success", { capfirst: true }), title: t("success", { capfirst: true }),
message: t("successfully created product", { capfirst: true }), message: t("success create", { capfirst: true, entity: t("product") }),
}); });
await queryClient.invalidateQueries({ queryKey: ["products"] }); await queryClient.invalidateQueries({ queryKey: ["products"] });
}, },
onError: (error) => { onError: (error) => {
notifications.show({ notifications.show({
title: t("error", { capfirst: true }), title: t("error", { capfirst: true }),
message: error?.message || t(`error editing product`, { capfirst: true }), message: error?.message || t(`error create`, { capfirst: true, entity: t("of the productor") }),
color: "red", color: "red",
}); });
}, },
@@ -408,14 +408,14 @@ export function useDeleteProduct() {
onSuccess: async () => { onSuccess: async () => {
notifications.show({ notifications.show({
title: t("success", { capfirst: true }), title: t("success", { capfirst: true }),
message: t("successfully deleted product", { capfirst: true }), message: t("success delete", { capfirst: true, entity: t("product") }),
}); });
await queryClient.invalidateQueries({ queryKey: ["products"] }); await queryClient.invalidateQueries({ queryKey: ["products"] });
}, },
onError: (error) => { onError: (error) => {
notifications.show({ notifications.show({
title: t("error", { capfirst: true }), title: t("error", { capfirst: true }),
message: error?.message || t(`error deleting product`, { capfirst: true }), message: error?.message || t(`error delete`, { capfirst: true, entity: t("of the product") }),
color: "red", color: "red",
}); });
}, },
@@ -438,14 +438,14 @@ export function useEditProduct() {
onSuccess: async () => { onSuccess: async () => {
notifications.show({ notifications.show({
title: t("success", { capfirst: true }), title: t("success", { capfirst: true }),
message: t("successfully edited product", { capfirst: true }), message: t("success edit", { capfirst: true, entity: t("product") }),
}); });
await queryClient.invalidateQueries({ queryKey: ["products"] }); await queryClient.invalidateQueries({ queryKey: ["products"] });
}, },
onError: (error) => { onError: (error) => {
notifications.show({ notifications.show({
title: t("error", { capfirst: true }), title: t("error", { capfirst: true }),
message: error?.message || t(`error editing product`, { capfirst: true }), message: error?.message || t(`error edit`, { capfirst: true, entity: t("of the product") }),
color: "red", color: "red",
}); });
}, },
@@ -491,14 +491,14 @@ export function useCreateUser() {
onSuccess: async () => { onSuccess: async () => {
notifications.show({ notifications.show({
title: t("success", { capfirst: true }), title: t("success", { capfirst: true }),
message: t("successfully created user", { capfirst: true }), message: t("success create", { capfirst: true, entity: t("user") }),
}); });
await queryClient.invalidateQueries({ queryKey: ["users"] }); await queryClient.invalidateQueries({ queryKey: ["users"] });
}, },
onError: (error) => { onError: (error) => {
notifications.show({ notifications.show({
title: t("error", { capfirst: true }), title: t("error", { capfirst: true }),
message: error?.message || t(`error editing user`, { capfirst: true }), message: error?.message || t(`error create`, { capfirst: true, entity: t("of the user") }),
color: "red", color: "red",
}); });
}, },
@@ -519,14 +519,14 @@ export function useDeleteUser() {
onSuccess: async () => { onSuccess: async () => {
notifications.show({ notifications.show({
title: t("success", { capfirst: true }), title: t("success", { capfirst: true }),
message: t("successfully deleted user", { capfirst: true }), message: t("success delete", { capfirst: true, entity: t("user") }),
}); });
await queryClient.invalidateQueries({ queryKey: ["users"] }); await queryClient.invalidateQueries({ queryKey: ["users"] });
}, },
onError: (error) => { onError: (error) => {
notifications.show({ notifications.show({
title: t("error", { capfirst: true }), title: t("error", { capfirst: true }),
message: error?.message || t(`error deleting user`, { capfirst: true }), message: error?.message || t(`error delete`, { capfirst: true, entity: t("of the user") }),
color: "red", color: "red",
}); });
}, },
@@ -549,14 +549,14 @@ export function useEditUser() {
onSuccess: async () => { onSuccess: async () => {
notifications.show({ notifications.show({
title: t("success", { capfirst: true }), title: t("success", { capfirst: true }),
message: t("successfully edited user", { capfirst: true }), message: t("success edit", { capfirst: true, entity: t("user") }),
}); });
await queryClient.invalidateQueries({ queryKey: ["users"] }); await queryClient.invalidateQueries({ queryKey: ["users"] });
}, },
onError: (error) => { onError: (error) => {
notifications.show({ notifications.show({
title: t("error", { capfirst: true }), title: t("error", { capfirst: true }),
message: error?.message || t(`error editing user`, { capfirst: true }), message: error?.message || t(`error edit`, { capfirst: true, entity: t("of the user") }),
color: "red", color: "red",
}); });
}, },
@@ -625,14 +625,14 @@ export function useDeleteContract() {
onSuccess: async () => { onSuccess: async () => {
notifications.show({ notifications.show({
title: t("success", { capfirst: true }), title: t("success", { capfirst: true }),
message: t("successfully deleted contract", { capfirst: true }), message: t("success delete", { capfirst: true, entity: t("contract") }),
}); });
await queryClient.invalidateQueries({ queryKey: ["contracts"] }); await queryClient.invalidateQueries({ queryKey: ["contracts"] });
}, },
onError: (error) => { onError: (error) => {
notifications.show({ notifications.show({
title: t("error", { capfirst: true }), title: t("error", { capfirst: true }),
message: error?.message || t(`error deleting contract`, { capfirst: true }), message: error?.message || t(`error delete`, { capfirst: true, entity: t("of the contract") }),
color: "red", color: "red",
}); });
}, },

View File

@@ -61,8 +61,8 @@ export function tranformProducts(
return Object.entries(products).map(([key, value]) => { return Object.entries(products).map(([key, value]) => {
const quantity = value; const quantity = value;
const parts = key.split("-"); const parts = key.split("-");
const shipment_id = parts[0] === "planned" ? Number(parts[1]) : null; const shipment_id = parts[0] === "occasional" ? Number(parts[1]) : null;
const product_id = parts[0] === "planned" ? Number(parts[2]) : Number(parts[1]); const product_id = parts[0] === "occasional" ? Number(parts[2]) : Number(parts[1]);
return { return {
quantity: Number(quantity), quantity: Number(quantity),
shipment_id: shipment_id, shipment_id: shipment_id,

View File

@@ -5,7 +5,7 @@ type ProductTypeKey = "1" | "2";
type ProductUnitKey = "1" | "2" | "3"; type ProductUnitKey = "1" | "2" | "3";
export const ProductType = { export const ProductType = {
"1": "planned", "1": "occasional",
"2": "recurrent", "2": "recurrent",
}; };