Compare commits

..

2 Commits

Author SHA1 Message Date
e69ddf3749 fix pr 2026-09-07 19:16:07 +02:00
768fdb8d97 Add min cheque value and view contract at the end of form flow 2026-09-07 19:15:06 +02:00
17 changed files with 1044 additions and 475 deletions

View File

@@ -0,0 +1,33 @@
"""add minimum for payment method cheque
Revision ID: 31071e10af15
Revises: e777ed5729ce
Create Date: 2026-09-07 18:48:08.498571
"""
from typing import Sequence, Union
from alembic import op
import sqlalchemy as sa
import sqlmodel.sql.sqltypes
# revision identifiers, used by Alembic.
revision: str = '31071e10af15'
down_revision: Union[str, Sequence[str], None] = 'e777ed5729ce'
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
def upgrade() -> None:
"""Upgrade schema."""
# ### commands auto generated by Alembic - please adjust! ###
op.add_column('paymentmethod', sa.Column('min', sa.Integer(), nullable=True))
# ### end Alembic commands ###
def downgrade() -> None:
"""Downgrade schema."""
# ### commands auto generated by Alembic - please adjust! ###
op.drop_column('paymentmethod', 'min')
# ### end Alembic commands ###

View File

@@ -42,7 +42,6 @@ def get_one(
"""Get one contract""" """Get one contract"""
return session.get(models.Contract, contract_id) return session.get(models.Contract, contract_id)
def create_one( def create_one(
session: Session, session: Session,
contract: models.ContractCreate contract: models.ContractCreate

View File

@@ -55,6 +55,7 @@ class PaymentMethodBase(SQLModel):
name: str name: str
details: str details: str
max: int | None max: int | None
min: int | None
class PaymentMethod(PaymentMethodBase, table=True): class PaymentMethod(PaymentMethodBase, table=True):

View File

@@ -63,12 +63,14 @@ def update_one(
if 'payment_methods' in productor_updates: if 'payment_methods' in productor_updates:
new_productor.payment_methods.clear() new_productor.payment_methods.clear()
for pm in productor_updates['payment_methods']: for pm in productor_updates['payment_methods']:
print(pm)
new_productor.payment_methods.append( new_productor.payment_methods.append(
models.PaymentMethod( models.PaymentMethod(
name=pm['name'], name=pm['name'],
details=pm['details'], details=pm['details'],
productor_id=id, productor_id=id,
max=pm['max'] max=pm['max'],
min=pm['min']
) )
) )
del productor_updates['payment_methods'] del productor_updates['payment_methods']

View File

@@ -80,6 +80,7 @@ class TestUsersService:
role_names=['test role'] role_names=['test role']
) )
user_id = users[0].id user_id = users[0].id
print(user_id)
result = users_service.update_one(session, user_id, user_update) result = users_service.update_one(session, user_id, user_update)
assert result.id == user_id assert result.id == user_id

View File

@@ -72,7 +72,7 @@
"minimum shipment value": "minimum shipment value (€)", "minimum shipment value": "minimum shipment value (€)",
"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).", "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, 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).", "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": "some contracts require a minimum value per shipment. Ignore this field if it does not apply to your contract.", "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. This field is only for occasional products, ignore this field if it does not apply to your contract.",
"export contracts": "export contracts", "export contracts": "export contracts",
"download recap": "download recap", "download recap": "download recap",
"fill contract online": "fill contract online", "fill contract online": "fill contract online",
@@ -211,5 +211,10 @@
"your keycloak user has no roles, please contact your administrator": "your keycloak user has no roles, please contact your administrator", "your keycloak user has no roles, please contact your administrator": "your keycloak user has no roles, please contact your administrator",
"choose payment method": "choose your payment method (you do not need to pay now).", "choose payment method": "choose your payment method (you do not need to pay now).",
"the product unit will be assigned to the quantity requested in the form": "the product unit defines the unit used in the contract form.", "the product unit will be assigned to the quantity requested in the form": "the product unit defines the unit used in the contract form.",
"all theses informations are for contract generation": "all this information is required for contract generation." "all theses informations are for contract generation": "all this information is required for contract generation.",
"i have read and accept the mutual agreement": "i have read and accept the mutual agreement.",
"here": "here",
"your contract has been submited": "your contract has been submited.",
"show contract": "show contract",
"you must accept terms and conditions": "you must accept terms and conditions."
} }

View File

@@ -72,7 +72,7 @@
"minimum shipment value": "valeur minimum d'une livraison (€)", "minimum shipment value": "valeur minimum d'une livraison (€)",
"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).", "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, 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).", "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é s’il ne s’applique 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 s'applique UNIQUEMENT pour les produits occasonnels et peut être ignoré s’il ne s’applique pas à votre contrat.",
"by checking this option the form will be accessible publicly on the home page, only check it if everything is fine with your form": "en cochant cette option le formulaire sera accessible publiquement sur la page d'accueil, cochez cette option uniquement si tout est prêt avec votre formulaire.", "by checking this option the form will be accessible publicly on the home page, only check it if everything is fine with your form": "en cochant cette option le formulaire sera accessible publiquement sur la page d'accueil, cochez cette option uniquement si tout est prêt avec votre formulaire.",
"contracts": "contrats", "contracts": "contrats",
"hidden": "caché", "hidden": "caché",
@@ -97,8 +97,10 @@
"by selecting a form here you can download all contracts of your form": "en selectionnant un formulaire, vous téléchargez tous les contrats pour un formulaire donné.", "by selecting a form here you can download all contracts of your form": "en selectionnant un formulaire, vous téléchargez tous les contrats pour un formulaire donné.",
"edit user": "modifier l'utilisateur·trice", "edit user": "modifier l'utilisateur·trice",
"remove user": "supprimer l'utilisateur·trice", "remove user": "supprimer l'utilisateur·trice",
"max cheque number": "numbre maximum de cheques possible", "max cheque number": "nombre maximum de cheques possible",
"min cheque number": "nombre minimum de cheques possible",
"can be empty default to 3": "optionnel, la valeur par défaut est à 3 cheques", "can be empty default to 3": "optionnel, la valeur par défaut est à 3 cheques",
"can be empty default to 1": "optionnel, la valeur par défaut est à 1 cheque",
"all forms": "tous les formulaires de contrat", "all forms": "tous les formulaires de contrat",
"create new form": "créer un nouveau formulaire de contrat", "create new form": "créer un nouveau formulaire de contrat",
"actions": "actions", "actions": "actions",
@@ -202,7 +204,7 @@
"cheque value": "valeur du chèque", "cheque value": "valeur du chèque",
"enter cheque value": "entrez la valeur du chèque", "enter cheque value": "entrez la valeur du chèque",
"enter payment method": "sélectionnez votre méthode de paiement", "enter payment method": "sélectionnez votre méthode de paiement",
"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 {{min}} et {{max}}, entrez également les identifiants des chèques utilisés.",
"payment method": "méthode de paiement", "payment method": "méthode de paiement",
"your session has expired please log in again": "votre session a expiré veuillez vous reconnecter.", "your session has expired please log in again": "votre session a expiré veuillez vous reconnecter.",
"session expired": "session expirée", "session expired": "session expirée",
@@ -211,5 +213,10 @@
"your keycloak user has no roles, please contact your administrator": "votre utilisateur keycloak n'a pas de roles configurés, contactez votre administrateur.", "your keycloak user has no roles, please contact your administrator": "votre utilisateur keycloak n'a pas de roles configurés, contactez votre administrateur.",
"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.",
"i have read and accept the mutual agreement": "j'ai lu et j'accepte le contrat d'engagement réciproque.",
"here": "ici",
"your contract has been submited": "votre contrat a été envoyé au référent, si vous avez fait une erreur dans la saisie de votre contrat, contactez votre référent·e.",
"show contract": "voir le contrat",
"you must accept terms and conditions": "vous devez accepter les termes du contrat d'engagement réciproque."
} }

File diff suppressed because it is too large Load Diff

View File

@@ -10,35 +10,31 @@ export type FormCardProps = {
}; };
export function FormCard({ form }: FormCardProps) { export function FormCard({ form }: FormCardProps) {
const contractBaseTemplate = useGetContractFileTemplate() const contractBaseTemplate = useGetContractFileTemplate();
return ( return (
<Paper shadow="xl" p="xl" miw={{ base: "100vw", md: "25vw", lg: "20vw" }}> <Paper shadow="xl" p="xl" miw={{ base: "100vw", md: "25vw", lg: "20vw" }}>
<Group justify="start" mb="md"> <Group justify="start" mb="md">
<Tooltip <Tooltip label={t("download base template to print", { capfirst: true })}>
label={t("download base template to print")}
>
<ActionIcon <ActionIcon
variant={"outline"} variant={"outline"}
aria-label={t("download base template to print")} aria-label={t("download base template to print", { capfirst: true })}
onClick={async () => { onClick={async () => {
await contractBaseTemplate.mutateAsync(form.id) await contractBaseTemplate.mutateAsync(form.id);
}} }}
> >
<IconDownload/> <IconDownload />
</ActionIcon> </ActionIcon>
</Tooltip> </Tooltip>
<Tooltip <Tooltip label={t("fill contract online", { capfirst: true })}>
label={t("fill contract online")}
>
<ActionIcon <ActionIcon
variant={"outline"} variant={"outline"}
aria-label={t("fill contract online")} aria-label={t("fill contract online", { capfirst: true })}
component={Link} component={Link}
to={`/form/${form.id}`} to={`/form/${form.id}`}
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
> >
<IconExternalLink/> <IconExternalLink />
</ActionIcon> </ActionIcon>
</Tooltip> </Tooltip>
</Group> </Group>

View File

@@ -43,21 +43,27 @@ export function ContractCheque({ inputForm, price, productor }: ContractChequePr
}, [inputForm.values.cheque_quantity, price, inputForm.values.cheques]); }, [inputForm.values.cheque_quantity, price, inputForm.values.cheques]);
const paymentMethod = useMemo(() => { const paymentMethod = useMemo(() => {
return productor?.payment_methods.find((el) => el.name === "cheque") return productor?.payment_methods.find((el) => el.name === "cheque");
}, [productor]); }, [productor]);
return ( return (
<Stack> <Stack>
<Title order={4}>{`${t("order name")} : ${paymentMethod?.details}`}</Title> <Title
order={4}
>{`${t("order name", { capfirst: true })} : ${paymentMethod?.details}`}</Title>
<NumberInput <NumberInput
label={t("cheque quantity", { capfirst: true })} label={t("cheque quantity", { capfirst: true })}
placeholder={t("enter cheque quantity", { capfirst: true })} placeholder={t("enter cheque quantity", { capfirst: true })}
description={t( description={t(
"number of cheques between 1 and 3 cheques also enter your cheques identifiers, value is calculated automatically", "number of cheques between 1 and 3 cheques also enter your cheques identifiers, value is calculated automatically",
{ capfirst: true }, { capfirst: true, min: paymentMethod?.min || 1, max: paymentMethod?.max || 3 },
)} )}
min={1} min={
max={paymentMethod?.max && paymentMethod?.max !== "" ? Number(paymentMethod?.max) : 3} paymentMethod?.min && paymentMethod?.min !== "" ? Number(paymentMethod?.min) : 1
}
max={
paymentMethod?.max && paymentMethod?.max !== "" ? Number(paymentMethod?.max) : 3
}
{...inputForm.getInputProps(`cheque_quantity`)} {...inputForm.getInputProps(`cheque_quantity`)}
/> />
<Group grow> <Group grow>
@@ -67,11 +73,7 @@ export function ContractCheque({ inputForm, price, productor }: ContractChequePr
label={t("cheque id", { capfirst: true })} label={t("cheque id", { capfirst: true })}
placeholder={t("cheque id", { capfirst: true })} placeholder={t("cheque id", { capfirst: true })}
{...inputForm.getInputProps(`cheques.${index}.name`)} {...inputForm.getInputProps(`cheques.${index}.name`)}
error={ error={cheque.name == "" ? inputForm?.errors.cheques : null}
cheque.name == "" ?
inputForm?.errors.cheques :
null
}
/> />
<NumberInput <NumberInput
readOnly readOnly

View File

@@ -0,0 +1,17 @@
export type PdfViewerProps = {
file: string;
};
export function PdfViewer({ file }: PdfViewerProps) {
return (
<div onContextMenu={(e) => e.preventDefault()}>
<iframe
src={`${file.toString()}#toolbar=0&navpanes=0`}
title="Contract PDF"
width="100%"
height="800px"
style={{ border: 0 }}
/>
</div>
);
}

View File

@@ -49,16 +49,18 @@ export function ProductorModal({
type: (value) => type: (value) =>
!value ? `${t("type", { capfirst: true })} ${t("is required")}` : null, !value ? `${t("type", { capfirst: true })} ${t("is required")}` : null,
payment_methods: (value) => payment_methods: (value) =>
value.length === 0 || value.some( value.length === 0 ||
(payment) => value.some((payment) => payment.name === "cheque" && payment.details === "")
payment.name === "cheque" && ? `${t("a payment method", { capfirst: true })} ${t("is required")}`
payment.details === "") ? : null,
`${t("a payment method", { capfirst: true })} ${t("is required")}` : null,
}, },
}); });
const roleSelect = useMemo(() => { const roleSelect = useMemo(() => {
return loggedUser?.user?.roles?.map((role) => ({ value: String(role.name), label: role.name })); return loggedUser?.user?.roles?.map((role) => ({
value: String(role.name),
label: role.name,
}));
}, [loggedUser?.user?.roles]); }, [loggedUser?.user?.roles]);
return ( return (
@@ -110,6 +112,7 @@ export function ProductorModal({
name, name,
details: "", details: "",
max: "", max: "",
min: "",
} }
); );
}), }),
@@ -124,10 +127,16 @@ export function ProductorModal({
placeholder={t("order name", { capfirst: true })} placeholder={t("order name", { capfirst: true })}
{...form.getInputProps(`payment_methods.${index}.details`)} {...form.getInputProps(`payment_methods.${index}.details`)}
/> />
<NumberInput
label={t("min cheque number", { capfirst: true })}
placeholder={t("min cheque number", { capfirst: true })}
description={t("can be empty default to 1", { capfirst: true })}
{...form.getInputProps(`payment_methods.${index}.min`)}
/>
<NumberInput <NumberInput
label={t("max cheque number", { capfirst: true })} label={t("max cheque number", { capfirst: true })}
placeholder={t("max cheque number", { capfirst: true })} placeholder={t("max cheque number", { capfirst: true })}
description={t("can be empty default to 3", {capfirst: true})} description={t("can be empty default to 3", { capfirst: true })}
{...form.getInputProps(`payment_methods.${index}.max`)} {...form.getInputProps(`payment_methods.${index}.max`)}
/> />
</Stack> </Stack>

View File

@@ -19,15 +19,23 @@ import {
} from "@mantine/core"; } from "@mantine/core";
import { useForm } from "@mantine/form"; import { useForm } from "@mantine/form";
import { IconDownload, IconMail, IconPhone, IconUser } from "@tabler/icons-react"; import { IconDownload, IconMail, IconPhone, IconUser } from "@tabler/icons-react";
import { useCallback, useMemo, useRef } from "react"; import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import { useParams } from "react-router"; import { useParams } from "react-router";
import { computePrices } from "./price"; import { computePrices } from "./price";
import { ContractCheque } from "@/components/PaymentMethods/Cheque"; import { ContractCheque } from "@/components/PaymentMethods/Cheque";
import { tranformProducts, type ContractInputs } from "@/services/resources/contracts"; import { tranformProducts, type ContractInputs } from "@/services/resources/contracts";
import { useDisclosure } from "@mantine/hooks";
import { ContractRecap } from "./recap";
export function Contract() { export function Contract() {
const { id } = useParams(); const { id } = useParams();
const [pdfUrl, setPdfUrl] = useState<string | undefined>();
const { data: form } = useGetForm(Number(id), { enabled: !!id }); const { data: form } = useGetForm(Number(id), { enabled: !!id });
const [isRecapModalOpened, { open: openRecapModal, close: closeRecapModal }] =
useDisclosure(false);
const minCheques = useMemo(() => {
return form?.productor?.payment_methods.find((pm) => pm.name === "cheque")?.min;
}, [form]);
const inputForm = useForm<ContractInputs>({ const inputForm = useForm<ContractInputs>({
initialValues: { initialValues: {
firstname: "", firstname: "",
@@ -51,10 +59,18 @@ export function Contract() {
payment_method: (value) => payment_method: (value) =>
!value ? `${t("a payment method", { capfirst: true })} ${t("is required")}` : null, !value ? `${t("a payment method", { capfirst: true })} ${t("is required")}` : null,
cheques: (value, values) => cheques: (value, values) =>
values.payment_method === "cheque" && value.some((val) => val.name == "") ? `${t("cheque id", {capfirst: true})} ${t("is required")}` : null, values.payment_method === "cheque" && value.some((val) => val.name == "")
? `${t("cheque id", { capfirst: true })} ${t("is required")}`
: null,
}, },
}); });
useEffect(() => {
if (!form) return;
console.log("aze");
inputForm.setFieldValue("cheque_quantity", Number(minCheques) || 1);
}, [form]);
const createContractMutation = useCreateContract(); const createContractMutation = useCreateContract();
const productsRecurent = useMemo(() => { const productsRecurent = useMemo(() => {
@@ -137,12 +153,15 @@ export function Contract() {
const formValues = inputForm.getValues(); const formValues = inputForm.getValues();
const contract = { const contract = {
...formValues, ...formValues,
cheque_quantity: formValues.payment_method === "cheque" ? formValues.cheque_quantity : 0, cheque_quantity:
formValues.payment_method === "cheque" ? formValues.cheque_quantity : 0,
form_id: form.id, form_id: form.id,
products: tranformProducts(withDefaultValues(formValues.products)), products: tranformProducts(withDefaultValues(formValues.products)),
}; };
await createContractMutation.mutateAsync(contract); const { blob } = await createContractMutation.mutateAsync(contract);
window.location.href = '/'; const url = URL.createObjectURL(blob);
setPdfUrl(url);
openRecapModal();
} else { } else {
const firstErrorField = Object.keys(errors.errors)[0]; const firstErrorField = Object.keys(errors.errors)[0];
const ref = inputRefs.current[firstErrorField]; const ref = inputRefs.current[firstErrorField];
@@ -155,6 +174,7 @@ export function Contract() {
form, form,
createContractMutation, createContractMutation,
withDefaultValues, withDefaultValues,
setPdfUrl,
]); ]);
if (!form || !shipments || !productsRecurent) if (!form || !shipments || !productsRecurent)
@@ -165,7 +185,7 @@ export function Contract() {
); );
return ( return (
<Stack w={{ base: "100%", md: "80%", lg: "50%" }} p={{base: 'xs'}}> <Stack w={{ base: "100%", md: "80%", lg: "50%" }} p={{ base: "xs" }}>
<Title order={2}>{form.name}</Title> <Title order={2}>{form.name}</Title>
<Title order={3}>{t("informations", { capfirst: true })}</Title> <Title order={3}>{t("informations", { capfirst: true })}</Title>
<Text size="sm"> <Text size="sm">
@@ -289,17 +309,14 @@ export function Contract() {
}} }}
/> />
{inputForm.values.payment_method === "cheque" ? ( {inputForm.values.payment_method === "cheque" ? (
<ContractCheque <ContractCheque productor={form?.productor} price={price} inputForm={inputForm} />
productor={form?.productor}
price={price}
inputForm={inputForm}
/>
) : null} ) : null}
{inputForm.values.payment_method === "transfer" ? ( {inputForm.values.payment_method === "transfer" ? (
<Text> <Text>
{t("for transfer method contact your referer or productor", { capfirst: true })} {t("for transfer method contact your referer or productor", { capfirst: true })}
</Text> </Text>
) : null} ) : null}
<ContractRecap opened={isRecapModalOpened} close={closeRecapModal} pdfUrl={pdfUrl} />
<Space h="15vh"></Space> <Space h="15vh"></Space>
<Overlay <Overlay
bg={"lightGray"} bg={"lightGray"}
@@ -321,9 +338,11 @@ export function Contract() {
}).format(price)} }).format(price)}
</Text> </Text>
<Button <Button
leftSection={<IconDownload/>} leftSection={<IconDownload />}
aria-label={t("submit contracts")} onClick={handleSubmit}> aria-label={t("submit contracts")}
{t("submit", {capfirst: true})} onClick={handleSubmit}
>
{t("submit contract", { capfirst: true })}
</Button> </Button>
</Overlay> </Overlay>
</Stack> </Stack>

View File

@@ -0,0 +1,70 @@
import { PdfViewer } from "@/components/PdfViewer";
import { t } from "@/config/i18n";
import { Button, Checkbox, Loader, Modal, Stack, Text } from "@mantine/core";
import { isNotEmpty, useForm } from "@mantine/form";
import { useState } from "react";
export type ContractRecapProps = {
opened: boolean;
close: () => void;
pdfUrl?: string;
};
export function ContractRecap({ opened, close, pdfUrl }: ContractRecapProps) {
const [viewPdf, setViewPdf] = useState<boolean>(false);
const form = useForm({
mode: "uncontrolled",
initialValues: { terms: false },
validate: {
terms: isNotEmpty(t("you must accept terms and conditions", { capfirst: true })),
},
});
if (!pdfUrl)
return (
<Modal
opened={opened}
onClose={close}
size="100%"
title={t("contract recap", { capfirst: true })}
>
<Loader></Loader>
</Modal>
);
return (
<Modal
opened={opened}
onClose={close}
size={"55rem"}
title={t("contract recap", { capfirst: true })}
>
<Stack>
<Text>{t("your contract has been submited", { capfirst: true })}</Text>
<Button onClick={() => setViewPdf(!viewPdf)}>
{t("show contract", { capfirst: true })}
</Button>
{viewPdf ? <PdfViewer file={pdfUrl} /> : null}
<form
onSubmit={form.onSubmit(() => {
const link = document.createElement("a");
link.href = pdfUrl;
link.target = "_blank";
link.click();
URL.revokeObjectURL(pdfUrl);
})}
>
<Stack>
<Checkbox
label={t("i have read and accept the mutual agreement", {
capfirst: true,
})}
key={form.key("terms")}
{...form.getInputProps("terms", { type: "checkbox" })}
/>
<Button type="submit">{t("download contract", { capfirst: true })}</Button>
</Stack>
</form>
</Stack>
</Modal>
);
}

View File

@@ -62,11 +62,12 @@ export default function Productors() {
async (productor: ProductorInputs) => { async (productor: ProductorInputs) => {
await createProductorMutation.mutateAsync({ await createProductorMutation.mutateAsync({
...productor, ...productor,
payment_methods: productor.payment_methods.map((payment) =>( { payment_methods: productor.payment_methods.map((payment) => ({
name: payment.name, name: payment.name,
details: payment.details, details: payment.details,
max: payment.max === "" ? null : payment.max max: payment.max === "" ? null : payment.max,
})) min: payment.min === "" ? null : payment.min,
})),
}); });
closeModal(); closeModal();
}, },
@@ -80,11 +81,12 @@ export default function Productors() {
id: id, id: id,
productor: { productor: {
...productor, ...productor,
payment_methods: productor.payment_methods.map((payment) =>( { payment_methods: productor.payment_methods.map((payment) => ({
name: payment.name, name: payment.name,
details: payment.details, details: payment.details,
max: payment.max === "" ? null : payment.max max: payment.max === "" ? null : payment.max,
})) min: payment.min === "" ? null : payment.min,
})),
}, },
}); });
closeModal(); closeModal();

View File

@@ -26,10 +26,17 @@ import { notifications } from "@mantine/notifications";
import { t } from "@/config/i18n"; import { t } from "@/config/i18n";
export async function refreshToken() { export async function refreshToken() {
return await fetch(`${Config.backend_uri}/auth/refresh`, {method: "POST", credentials: "include"}); return await fetch(`${Config.backend_uri}/auth/refresh`, {
method: "POST",
credentials: "include",
});
} }
export async function fetchWithAuth(input: RequestInfo, options?: RequestInit, redirect: boolean = true) { export async function fetchWithAuth(
input: RequestInfo,
options?: RequestInit,
redirect: boolean = true,
) {
const res = await fetch(input, { const res = await fetch(input, {
credentials: "include", credentials: "include",
...options, ...options,
@@ -38,11 +45,10 @@ export async function fetchWithAuth(input: RequestInfo, options?: RequestInit, r
if (res.status === 401) { if (res.status === 401) {
const refresh = await refreshToken(); const refresh = await refreshToken();
if (refresh.status == 400 || refresh.status == 401) { if (refresh.status == 400 || refresh.status == 401) {
if (redirect) if (redirect) window.location.href = `/?sessionExpired=True`;
window.location.href = `/?sessionExpired=True`;
const error = new Error("Unauthorized"); const error = new Error("Unauthorized");
error.cause = 401 error.cause = 401;
throw error; throw error;
} }
const newRes = await fetch(input, { const newRes = await fetch(input, {
@@ -188,9 +194,7 @@ export function useGetProductors(filters?: URLSearchParams): UseQueryResult<Prod
queryFn: () => queryFn: () =>
fetchWithAuth(`${Config.backend_uri}/productors${filters ? `?${queryString}` : ""}`, { fetchWithAuth(`${Config.backend_uri}/productors${filters ? `?${queryString}` : ""}`, {
credentials: "include", credentials: "include",
}) }).then((res) => res.json()),
.then((res) => res.json()
),
}); });
} }
@@ -326,7 +330,9 @@ export function useGetForms(filters?: URLSearchParams): UseQueryResult<Form[], E
return useQuery<Form[]>({ return useQuery<Form[]>({
queryKey: ["forms", queryString], queryKey: ["forms", queryString],
queryFn: () => queryFn: () =>
fetch(`${Config.backend_uri}/forms${filters ? `?${queryString}` : ""}`).then((res) => res.json()), fetch(`${Config.backend_uri}/forms${filters ? `?${queryString}` : ""}`).then((res) =>
res.json(),
),
}); });
} }
@@ -335,7 +341,9 @@ export function useGetReferentForms(filters?: URLSearchParams): UseQueryResult<F
return useQuery<Form[]>({ return useQuery<Form[]>({
queryKey: ["forms", queryString], queryKey: ["forms", queryString],
queryFn: () => queryFn: () =>
fetchWithAuth(`${Config.backend_uri}/forms/referents${filters ? `?${queryString}` : ""}`).then((res) => res.json()), fetchWithAuth(
`${Config.backend_uri}/forms/referents${filters ? `?${queryString}` : ""}`,
).then((res) => res.json()),
}); });
} }
@@ -723,8 +731,9 @@ export function useGetContractFile() {
export function useGetContractFileTemplate() { export function useGetContractFileTemplate() {
return useMutation({ return useMutation({
mutationFn: async (form_id: number) => { mutationFn: async (form_id: number) => {
const res = await fetchWithAuth(`${Config.backend_uri}/contracts/${form_id}/base`) const res = await fetchWithAuth(`${Config.backend_uri}/contracts/${form_id}/base`).then(
.then((res) => res); (res) => res,
);
if (!res.ok) throw new Error(); if (!res.ok) throw new Error();
const blob = await res.blob(); const blob = await res.blob();
@@ -746,7 +755,6 @@ export function useGetContractFileTemplate() {
}); });
} }
export function useGetRecap() { export function useGetRecap() {
return useMutation({ return useMutation({
mutationFn: async (form_id: number) => { mutationFn: async (form_id: number) => {
@@ -822,13 +830,7 @@ export function useCreateContract() {
: `contract_${newContract.form_id}.pdf`; : `contract_${newContract.form_id}.pdf`;
return { blob, filename }; return { blob, filename };
}, },
onSuccess: async ({ blob, filename }) => { onSuccess: async () => {
const url = URL.createObjectURL(blob);
const link = document.createElement("a");
link.href = url;
link.download = filename;
link.click();
URL.revokeObjectURL(url);
await queryClient.invalidateQueries({ queryKey: ["contracts"] }); await queryClient.invalidateQueries({ queryKey: ["contracts"] });
}, },
}); });
@@ -880,9 +882,13 @@ export function useCurrentUser() {
return useQuery<UserLogged>({ return useQuery<UserLogged>({
queryKey: ["currentUser"], queryKey: ["currentUser"],
queryFn: () => { queryFn: () => {
return fetchWithAuth(`${Config.backend_uri}/auth/user/me`, { return fetchWithAuth(
`${Config.backend_uri}/auth/user/me`,
{
credentials: "include", credentials: "include",
}, false).then((res) => res.json()); },
false,
).then((res) => res.json());
}, },
retry: false, retry: false,
}); });

View File

@@ -10,6 +10,7 @@ export type PaymentMethod = {
name: string; name: string;
details: string; details: string;
max: number | string | null; max: number | string | null;
min: number | string | null;
}; };
export type Productor = { export type Productor = {