[WIP] add styles
This commit is contained in:
20
frontend/Dockerfile
Normal file
20
frontend/Dockerfile
Normal file
@@ -0,0 +1,20 @@
|
||||
FROM node:20.19-alpine AS build
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
ARG VITE_API_URL
|
||||
ENV VITE_API_URL=$VITE_API_URL
|
||||
|
||||
COPY frontend/package.json frontend/package-lock.json /app/
|
||||
RUN npm install
|
||||
|
||||
COPY frontend/ .
|
||||
|
||||
RUN npm run build
|
||||
|
||||
FROM nginx:latest
|
||||
|
||||
COPY --from=build /app/dist /srv/www/frontend
|
||||
|
||||
RUN rm /etc/nginx/conf.d/default.conf
|
||||
COPY --from=build /app/nginx/default.conf /etc/nginx/conf.d/default.conf
|
||||
12
frontend/Dockerfile.dev
Normal file
12
frontend/Dockerfile.dev
Normal file
@@ -0,0 +1,12 @@
|
||||
FROM node:20 AS dev
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY frontend/package.json ./
|
||||
COPY frontend/package-lock.json ./
|
||||
|
||||
RUN npm install
|
||||
|
||||
COPY frontend .
|
||||
|
||||
CMD ["npm", "run", "dev", "--", "--host"]
|
||||
@@ -75,12 +75,17 @@
|
||||
"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.",
|
||||
"export contracts": "export contracts",
|
||||
"download recap": "download recap",
|
||||
"fill contract online": "fill contract online",
|
||||
"download base template to print": "download base template to print",
|
||||
"to export contracts submissions before sending to the productor go to the contracts section": "to export contracts submissions before sending to the productor go to the contracts section.",
|
||||
"in this page you can view all contracts submissions, you can remove duplicates submission or download a specific contract": "in this page you can view all contracts submissions, you can remove duplicates submission or download a specific contract",
|
||||
"you can download all contracts for your form using the export all": "you can download all contracts for your form using the export all",
|
||||
"in the same corner you can download a recap by clicking on the button": "in the same corner you can download a recap by clicking on the",
|
||||
"once all contracts downloaded, you can delete the form (to avoid new submissions) and hide it from the home page": "once all contracts downloaded, you can delete the form (to avoid new submissions) and hide it from the home page",
|
||||
"by checking this option the form will be accessible publicly on the home page, only check it if everything is fine with your form": "by checking this option the form will be accessible publicly on the home page, only check it if everything is fine with your form",
|
||||
"contracts": "contracts",
|
||||
"hidden": "hidden",
|
||||
"visible": "visible",
|
||||
"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.",
|
||||
@@ -90,6 +95,8 @@
|
||||
"templates": "templates",
|
||||
"users": "users",
|
||||
"forms": "contract forms",
|
||||
"max cheque number": "max cheque number",
|
||||
"can be empty default to 3": "can be empty default to 3",
|
||||
"form": "contract form",
|
||||
"select a form": "select a form",
|
||||
"download contracts": "download contracts",
|
||||
@@ -199,6 +206,7 @@
|
||||
"your session has expired please log in again": "your session has expired please log in again",
|
||||
"session expired": "session expired",
|
||||
"user not allowed": "user not allowed",
|
||||
"roles": "roles",
|
||||
"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).",
|
||||
"the product unit will be assigned to the quantity requested in the form": "the product unit defines the unit used in the contract form.",
|
||||
|
||||
@@ -73,7 +73,10 @@
|
||||
"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).",
|
||||
"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.",
|
||||
"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",
|
||||
"hidden": "caché",
|
||||
"visible": "visible",
|
||||
"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",
|
||||
"for this contract": "pour ce contrat.",
|
||||
@@ -89,9 +92,13 @@
|
||||
"all contracts": "tous les contrats",
|
||||
"remove contract": "supprimer le contrat",
|
||||
"download contract": "télécharger le contrat",
|
||||
"fill contract online": "remplir le contrat en ligne",
|
||||
"download base template to print": "télécharger le contrat à remplir sur papier",
|
||||
"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",
|
||||
"remove user": "supprimer l'utilisateur·trice",
|
||||
"max cheque number": "numbre maximum de cheques possible",
|
||||
"can be empty default to 3": "optionnel, la valeur par défaut est à 3 cheques",
|
||||
"all forms": "tous les formulaires de contrat",
|
||||
"create new form": "créer un nouveau formulaire de contrat",
|
||||
"actions": "actions",
|
||||
@@ -162,7 +169,7 @@
|
||||
"with cheque and transfer": "avec chèques et virements configuré pour le producteur",
|
||||
"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.",
|
||||
"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ée à 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.",
|
||||
@@ -199,6 +206,7 @@
|
||||
"your session has expired please log in again": "votre session a expiré veuillez vous reconnecter.",
|
||||
"session expired": "session expirée",
|
||||
"user not allowed": "utilisateur non authorisé",
|
||||
"roles": "roles",
|
||||
"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).",
|
||||
"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.",
|
||||
|
||||
22
frontend/nginx/default.conf
Normal file
22
frontend/nginx/default.conf
Normal file
@@ -0,0 +1,22 @@
|
||||
server {
|
||||
listen 80 default_server;
|
||||
listen [::]:80 default_server;
|
||||
|
||||
server_name localhost;
|
||||
root /srv/www/frontend;
|
||||
index index.html;
|
||||
|
||||
location /api/ {
|
||||
proxy_set_header Host $http_host;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
|
||||
proxy_redirect off;
|
||||
proxy_buffering off;
|
||||
proxy_pass http://backend:8000;
|
||||
}
|
||||
|
||||
location / {
|
||||
try_files $uri /index.html;
|
||||
}
|
||||
}
|
||||
@@ -1,17 +1,53 @@
|
||||
import { Badge, Box, Group, Paper, Text, Title } from "@mantine/core";
|
||||
import { ActionIcon, Badge, Box, Group, Paper, Text, Title, Tooltip } from "@mantine/core";
|
||||
import { Link } from "react-router";
|
||||
import type { Form } from "@/services/resources/forms";
|
||||
import { IconDownload, IconExternalLink } from "@tabler/icons-react";
|
||||
import { t } from "@/config/i18n";
|
||||
import { useGetContractFileTemplate } from "@/services/api";
|
||||
|
||||
export type FormCardProps = {
|
||||
form: Form;
|
||||
};
|
||||
|
||||
export function FormCard({ form }: FormCardProps) {
|
||||
const contractBaseTemplate = useGetContractFileTemplate()
|
||||
return (
|
||||
<Paper shadow="xl" p="xl" miw={{ base: "100vw", md: "25vw", lg: "20vw" }}>
|
||||
<Group justify="start" mb="md">
|
||||
<Tooltip
|
||||
label={t("download base template to print")}
|
||||
>
|
||||
<ActionIcon
|
||||
variant={"outline"}
|
||||
aria-label={t("download base template to print")}
|
||||
onClick={async () => {
|
||||
await contractBaseTemplate.mutateAsync(form.id)
|
||||
}}
|
||||
>
|
||||
<IconDownload/>
|
||||
</ActionIcon>
|
||||
</Tooltip>
|
||||
<Tooltip
|
||||
label={t("fill contract online")}
|
||||
>
|
||||
<ActionIcon
|
||||
variant={"outline"}
|
||||
aria-label={t("fill contract online")}
|
||||
component={Link}
|
||||
to={`/form/${form.id}`}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<IconExternalLink/>
|
||||
</ActionIcon>
|
||||
</Tooltip>
|
||||
</Group>
|
||||
|
||||
<Box
|
||||
component={Link}
|
||||
to={`/form/${form.id}`}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
style={{ textDecoration: "none", color: "black" }}
|
||||
>
|
||||
<Group justify="space-between" wrap="nowrap">
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import {
|
||||
Button,
|
||||
Checkbox,
|
||||
Group,
|
||||
Modal,
|
||||
NumberInput,
|
||||
@@ -33,6 +34,7 @@ export default function FormModal({ opened, onClose, currentForm, handleSubmit }
|
||||
productor_id: currentForm?.productor?.id.toString() ?? "",
|
||||
referer_id: currentForm?.referer?.id.toString() ?? "",
|
||||
minimum_shipment_value: currentForm?.minimum_shipment_value ?? null,
|
||||
visible: currentForm?.visible ?? false
|
||||
},
|
||||
validate: {
|
||||
name: (value) =>
|
||||
@@ -136,6 +138,11 @@ export default function FormModal({ opened, onClose, currentForm, handleSubmit }
|
||||
radius="sm"
|
||||
{...form.getInputProps("minimum_shipment_value")}
|
||||
/>
|
||||
<Checkbox mt="lg"
|
||||
label={t("visible", {capfirst: true})}
|
||||
description={t("by checking this option the form will be accessible publicly on the home page, only check it if everything is fine with your form", {capfirst: true})}
|
||||
{...form.getInputProps("visible", {type: "checkbox"})}
|
||||
/>
|
||||
<Group mt="sm" justify="space-between">
|
||||
<Button
|
||||
variant="filled"
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { ActionIcon, Table, Tooltip } from "@mantine/core";
|
||||
import { ActionIcon, Badge, Table, Tooltip } from "@mantine/core";
|
||||
import { useNavigate, useSearchParams } from "react-router";
|
||||
import { useDeleteForm } from "@/services/api";
|
||||
import { IconEdit, IconX } from "@tabler/icons-react";
|
||||
@@ -16,6 +16,12 @@ export default function FormRow({ form }: FormRowProps) {
|
||||
|
||||
return (
|
||||
<Table.Tr key={form.id}>
|
||||
<Table.Td>
|
||||
{form.visible ?
|
||||
<Badge color="green">{t("visible", {capfirst: true})}</Badge> :
|
||||
<Badge color="red">{t("hidden", {capfirst: true})}</Badge>
|
||||
}
|
||||
</Table.Td>
|
||||
<Table.Td>{form.name}</Table.Td>
|
||||
<Table.Td>{form.season}</Table.Td>
|
||||
<Table.Td>{form.start}</Table.Td>
|
||||
|
||||
@@ -29,13 +29,13 @@ export function Navbar() {
|
||||
) : null}
|
||||
</Group>
|
||||
{!user?.logged ? (
|
||||
<NavLink
|
||||
<a
|
||||
href={`${Config.backend_uri}/auth/login`}
|
||||
className={"navLink"}
|
||||
aria-label={t("login with keycloak", { capfirst: true })}
|
||||
to={`${Config.backend_uri}/auth/login`}
|
||||
>
|
||||
{t("login with keycloak", { capfirst: true })}
|
||||
</NavLink>
|
||||
</a>
|
||||
) : (
|
||||
<a
|
||||
href={`${Config.backend_uri}/auth/logout`}
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
import { t } from "@/config/i18n";
|
||||
import type { ContractInputs } from "@/services/resources/contracts";
|
||||
import { Group, NumberInput, Stack, Text, TextInput, Title } from "@mantine/core";
|
||||
import type { Productor } from "@/services/resources/productors";
|
||||
import { Group, NumberInput, Stack, TextInput, Title } from "@mantine/core";
|
||||
import type { UseFormReturnType } from "@mantine/form";
|
||||
import { useEffect } from "react";
|
||||
import { useEffect, useMemo } from "react";
|
||||
|
||||
export type ContractChequeProps = {
|
||||
inputForm: UseFormReturnType<ContractInputs>;
|
||||
price: number;
|
||||
chequeOrder: string;
|
||||
productor: Productor;
|
||||
};
|
||||
|
||||
export type Cheque = {
|
||||
@@ -15,7 +16,7 @@ export type Cheque = {
|
||||
value: string;
|
||||
};
|
||||
|
||||
export function ContractCheque({ inputForm, price, chequeOrder }: ContractChequeProps) {
|
||||
export function ContractCheque({ inputForm, price, productor }: ContractChequeProps) {
|
||||
useEffect(() => {
|
||||
if (!inputForm.values.payment_method.includes("cheque")) {
|
||||
return;
|
||||
@@ -41,9 +42,13 @@ export function ContractCheque({ inputForm, price, chequeOrder }: ContractCheque
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [inputForm.values.cheque_quantity, price, inputForm.values.cheques]);
|
||||
|
||||
const paymentMethod = useMemo(() => {
|
||||
return productor?.payment_methods.find((el) => el.name === "cheque")
|
||||
}, [productor]);
|
||||
|
||||
return (
|
||||
<Stack>
|
||||
<Title order={4}>{`${t("order name")} : ${chequeOrder}`}</Title>
|
||||
<Title order={4}>{`${t("order name")} : ${paymentMethod?.details}`}</Title>
|
||||
<NumberInput
|
||||
label={t("cheque quantity", { capfirst: true })}
|
||||
placeholder={t("enter cheque quantity", { capfirst: true })}
|
||||
@@ -52,7 +57,7 @@ export function ContractCheque({ inputForm, price, chequeOrder }: ContractCheque
|
||||
{ capfirst: true },
|
||||
)}
|
||||
min={1}
|
||||
max={3}
|
||||
max={paymentMethod?.max && paymentMethod?.max !== "" ? Number(paymentMethod?.max) : 3}
|
||||
{...inputForm.getInputProps(`cheque_quantity`)}
|
||||
/>
|
||||
<Group grow>
|
||||
@@ -64,7 +69,7 @@ export function ContractCheque({ inputForm, price, chequeOrder }: ContractCheque
|
||||
{...inputForm.getInputProps(`cheques.${index}.name`)}
|
||||
error={
|
||||
cheque.name == "" ?
|
||||
<Text size="sm" c="red">{inputForm?.errors.cheques}</Text> :
|
||||
inputForm?.errors.cheques :
|
||||
null
|
||||
}
|
||||
/>
|
||||
|
||||
@@ -3,7 +3,9 @@ import {
|
||||
Group,
|
||||
Modal,
|
||||
MultiSelect,
|
||||
NumberInput,
|
||||
Select,
|
||||
Stack,
|
||||
TextInput,
|
||||
Title,
|
||||
type ModalBaseProps,
|
||||
@@ -107,6 +109,7 @@ export function ProductorModal({
|
||||
existing ?? {
|
||||
name,
|
||||
details: "",
|
||||
max: "",
|
||||
}
|
||||
);
|
||||
}),
|
||||
@@ -115,12 +118,19 @@ export function ProductorModal({
|
||||
/>
|
||||
{form.values.payment_methods.map((method, index) =>
|
||||
method.name === "cheque" ? (
|
||||
<TextInput
|
||||
key={index}
|
||||
label={t("order name", { capfirst: true })}
|
||||
placeholder={t("order name", { capfirst: true })}
|
||||
{...form.getInputProps(`payment_methods.${index}.details`)}
|
||||
/>
|
||||
<Stack key={index}>
|
||||
<TextInput
|
||||
label={t("order name", { capfirst: true })}
|
||||
placeholder={t("order name", { capfirst: true })}
|
||||
{...form.getInputProps(`payment_methods.${index}.details`)}
|
||||
/>
|
||||
<NumberInput
|
||||
label={t("max cheque number", { capfirst: true })}
|
||||
placeholder={t("max cheque number", { capfirst: true })}
|
||||
description={t("can be empty default to 3", {capfirst: true})}
|
||||
{...form.getInputProps(`payment_methods.${index}.max`)}
|
||||
/>
|
||||
</Stack>
|
||||
) : null,
|
||||
)}
|
||||
<Group mt="sm" justify="space-between">
|
||||
|
||||
@@ -36,7 +36,7 @@ export function ProductModal({ opened, onClose, currentProduct, handleSubmit }:
|
||||
quantity: currentProduct?.quantity ?? null,
|
||||
quantity_unit: currentProduct?.quantity_unit ?? null,
|
||||
type: currentProduct?.type ?? null,
|
||||
productor_id: currentProduct ? String(currentProduct.productor.id) : null,
|
||||
productor_id: currentProduct ? String(currentProduct?.productor?.id) : null,
|
||||
},
|
||||
validate: {
|
||||
name: (value) =>
|
||||
|
||||
@@ -13,7 +13,7 @@ import { IconCancel, IconEdit, IconPlus } from "@tabler/icons-react";
|
||||
import { useForm } from "@mantine/form";
|
||||
import { useMemo } from "react";
|
||||
import { type Shipment, type ShipmentInputs } from "@/services/resources/shipments";
|
||||
import { useGetForms, useGetProductors, useGetProducts } from "@/services/api";
|
||||
import { useGetReferentForms, useGetProductors, useGetProducts } from "@/services/api";
|
||||
|
||||
export type ShipmentModalProps = ModalBaseProps & {
|
||||
currentShipment?: Shipment;
|
||||
@@ -43,7 +43,7 @@ export default function ShipmentModal({
|
||||
},
|
||||
});
|
||||
|
||||
const { data: allForms } = useGetForms();
|
||||
const { data: allForms } = useGetReferentForms();
|
||||
const { data: allProducts } = useGetProducts(new URLSearchParams("types=1"));
|
||||
const { data: allProductors } = useGetProductors();
|
||||
|
||||
|
||||
@@ -3,7 +3,6 @@ import {
|
||||
Group,
|
||||
Modal,
|
||||
MultiSelect,
|
||||
Select,
|
||||
TextInput,
|
||||
Title,
|
||||
type ModalBaseProps,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { ActionIcon, Table, Tooltip } from "@mantine/core";
|
||||
import { ActionIcon, Badge, Box, Table, Tooltip } from "@mantine/core";
|
||||
import { t } from "@/config/i18n";
|
||||
import { IconEdit, IconX } from "@tabler/icons-react";
|
||||
import { type User } from "@/services/resources/users";
|
||||
@@ -18,6 +18,31 @@ export default function UserRow({ user }: UserRowProps) {
|
||||
<Table.Tr key={user.id}>
|
||||
<Table.Td>{user.name}</Table.Td>
|
||||
<Table.Td>{user.email}</Table.Td>
|
||||
<Table.Td style={{maxWidth: 200}}>
|
||||
<Box
|
||||
style={{
|
||||
display: 'flex',
|
||||
gap: 4
|
||||
}}
|
||||
>
|
||||
{user.roles.slice(0, 3).map((value) => (
|
||||
<Badge key={value.id} size="xs">
|
||||
{t(value.name, { capfirst: true })}
|
||||
</Badge>
|
||||
))}
|
||||
{
|
||||
user.roles.length > 3 && (
|
||||
<Tooltip
|
||||
label={user.roles.slice(3).map(role=>`${role.name} `)}
|
||||
>
|
||||
<Badge size="xs" variant="light">
|
||||
+{user.roles.length - 3}
|
||||
</Badge>
|
||||
</Tooltip>
|
||||
)
|
||||
}
|
||||
</Box>
|
||||
</Table.Td>
|
||||
<Table.Td>
|
||||
<Tooltip label={t("edit user", { capfirst: true })}>
|
||||
<ActionIcon
|
||||
|
||||
@@ -18,7 +18,7 @@ import {
|
||||
Title,
|
||||
} from "@mantine/core";
|
||||
import { useForm } from "@mantine/form";
|
||||
import { IconMail, IconPhone, IconUser } from "@tabler/icons-react";
|
||||
import { IconDownload, IconMail, IconPhone, IconUser } from "@tabler/icons-react";
|
||||
import { useCallback, useMemo, useRef } from "react";
|
||||
import { useParams } from "react-router";
|
||||
import { computePrices } from "./price";
|
||||
@@ -35,7 +35,7 @@ export function Contract() {
|
||||
email: "",
|
||||
phone: "",
|
||||
payment_method: "",
|
||||
cheque_quantity: 0,
|
||||
cheque_quantity: 1,
|
||||
cheques: [],
|
||||
products: {},
|
||||
},
|
||||
@@ -134,12 +134,15 @@ export function Contract() {
|
||||
return;
|
||||
}
|
||||
if (inputForm.isValid() && isShipmentsMinimumValue()) {
|
||||
const formValues = inputForm.getValues();
|
||||
const contract = {
|
||||
...inputForm.getValues(),
|
||||
...formValues,
|
||||
cheque_quantity: formValues.payment_method === "cheque" ? formValues.cheque_quantity : 0,
|
||||
form_id: form.id,
|
||||
products: tranformProducts(withDefaultValues(inputForm.getValues().products)),
|
||||
products: tranformProducts(withDefaultValues(formValues.products)),
|
||||
};
|
||||
await createContractMutation.mutateAsync(contract);
|
||||
window.location.href = '/';
|
||||
} else {
|
||||
const firstErrorField = Object.keys(errors.errors)[0];
|
||||
const ref = inputRefs.current[firstErrorField];
|
||||
@@ -283,10 +286,7 @@ export function Contract() {
|
||||
/>
|
||||
{inputForm.values.payment_method === "cheque" ? (
|
||||
<ContractCheque
|
||||
chequeOrder={
|
||||
form?.productor?.payment_methods.find((el) => el.name === "cheque")
|
||||
?.details || ""
|
||||
}
|
||||
productor={form?.productor}
|
||||
price={price}
|
||||
inputForm={inputForm}
|
||||
/>
|
||||
@@ -316,8 +316,10 @@ export function Contract() {
|
||||
currency: "EUR",
|
||||
}).format(price)}
|
||||
</Text>
|
||||
<Button aria-label={t("submit contract")} onClick={handleSubmit}>
|
||||
{t("submit contract")}
|
||||
<Button
|
||||
leftSection={<IconDownload/>}
|
||||
aria-label={t("submit contracts")} onClick={handleSubmit}>
|
||||
{t("submit contract", {capfirst: true})}
|
||||
</Button>
|
||||
</Overlay>
|
||||
</Stack>
|
||||
|
||||
@@ -89,6 +89,7 @@ export default function Contracts() {
|
||||
label={t("download recap", { capfirst: true })}
|
||||
>
|
||||
<ActionIcon
|
||||
disabled={false}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
navigate(
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Stack, Loader, Title, Group, ActionIcon, Tooltip, Table, ScrollArea } from "@mantine/core";
|
||||
import { useCreateForm, useEditForm, useGetForm, useGetForms } from "@/services/api";
|
||||
import { useCreateForm, useEditForm, useGetForm, useGetReferentForms } from "@/services/api";
|
||||
import { t } from "@/config/i18n";
|
||||
import { useLocation, useNavigate, useSearchParams } from "react-router";
|
||||
import { IconPlus } from "@tabler/icons-react";
|
||||
@@ -28,12 +28,12 @@ export function Forms() {
|
||||
navigate(`/dashboard/forms${searchParams ? `?${searchParams.toString()}` : ""}`);
|
||||
}, [navigate, searchParams]);
|
||||
|
||||
const { isPending, data } = useGetForms(searchParams);
|
||||
const { isPending, data } = useGetReferentForms(searchParams);
|
||||
const { data: currentForm } = useGetForm(Number(editId), {
|
||||
enabled: !!editId,
|
||||
});
|
||||
|
||||
const { data: allForms } = useGetForms();
|
||||
const { data: allForms } = useGetReferentForms();
|
||||
|
||||
const seasons = useMemo(() => {
|
||||
return allForms
|
||||
@@ -148,6 +148,7 @@ export function Forms() {
|
||||
<Table striped>
|
||||
<Table.Thead>
|
||||
<Table.Tr>
|
||||
<Table.Th>{t("visible", { capfirst: true })}</Table.Th>
|
||||
<Table.Th>{t("name", { capfirst: true })}</Table.Th>
|
||||
<Table.Th>{t("type", { capfirst: true })}</Table.Th>
|
||||
<Table.Th>{t("start", { capfirst: true })}</Table.Th>
|
||||
|
||||
@@ -58,6 +58,11 @@ export default function Productors() {
|
||||
async (productor: ProductorInputs) => {
|
||||
await createProductorMutation.mutateAsync({
|
||||
...productor,
|
||||
payment_methods: productor.payment_methods.map((payment) =>( {
|
||||
name: payment.name,
|
||||
details: payment.details,
|
||||
max: payment.max === "" ? null : payment.max
|
||||
}))
|
||||
});
|
||||
closeModal();
|
||||
},
|
||||
@@ -69,7 +74,14 @@ export default function Productors() {
|
||||
if (!id) return;
|
||||
await editProductorMutation.mutateAsync({
|
||||
id: id,
|
||||
productor: productor,
|
||||
productor: {
|
||||
...productor,
|
||||
payment_methods: productor.payment_methods.map((payment) =>( {
|
||||
name: payment.name,
|
||||
details: payment.details,
|
||||
max: payment.max === "" ? null : payment.max
|
||||
}))
|
||||
},
|
||||
});
|
||||
closeModal();
|
||||
},
|
||||
@@ -146,7 +158,7 @@ export default function Productors() {
|
||||
</Table.Tr>
|
||||
</Table.Thead>
|
||||
<Table.Tbody>
|
||||
{productors.map((productor) => (
|
||||
{productors?.map((productor) => (
|
||||
<ProductorRow productor={productor} key={productor.id} />
|
||||
))}
|
||||
</Table.Tbody>
|
||||
|
||||
@@ -127,11 +127,12 @@ export default function Users() {
|
||||
<Table.Tr>
|
||||
<Table.Th>{t("name", { capfirst: true })}</Table.Th>
|
||||
<Table.Th>{t("email", { capfirst: true })}</Table.Th>
|
||||
<Table.Th>{t("roles", { capfirst: true })}</Table.Th>
|
||||
<Table.Th>{t("actions", { capfirst: true })}</Table.Th>
|
||||
</Table.Tr>
|
||||
</Table.Thead>
|
||||
<Table.Tbody>
|
||||
{users.map((user) => (
|
||||
{users?.map((user) => (
|
||||
<UserRow user={user} key={user.id} />
|
||||
))}
|
||||
</Table.Tbody>
|
||||
|
||||
@@ -29,7 +29,7 @@ export async function refreshToken() {
|
||||
return await fetch(`${Config.backend_uri}/auth/refresh`, {method: "POST", credentials: "include"});
|
||||
}
|
||||
|
||||
export async function fetchWithAuth(input: RequestInfo, options?: RequestInit) {
|
||||
export async function fetchWithAuth(input: RequestInfo, options?: RequestInit, redirect: boolean = true) {
|
||||
const res = await fetch(input, {
|
||||
credentials: "include",
|
||||
...options,
|
||||
@@ -38,7 +38,8 @@ export async function fetchWithAuth(input: RequestInfo, options?: RequestInit) {
|
||||
if (res.status === 401) {
|
||||
const refresh = await refreshToken();
|
||||
if (refresh.status == 400 || refresh.status == 401) {
|
||||
window.location.href = `/?sessionExpired=True`;
|
||||
if (redirect)
|
||||
window.location.href = `/?sessionExpired=True`;
|
||||
|
||||
const error = new Error("Unauthorized");
|
||||
error.cause = 401
|
||||
@@ -329,6 +330,15 @@ export function useGetForms(filters?: URLSearchParams): UseQueryResult<Form[], E
|
||||
});
|
||||
}
|
||||
|
||||
export function useGetReferentForms(filters?: URLSearchParams): UseQueryResult<Form[], Error> {
|
||||
const queryString = filters?.toString();
|
||||
return useQuery<Form[]>({
|
||||
queryKey: ["forms", queryString],
|
||||
queryFn: () =>
|
||||
fetchWithAuth(`${Config.backend_uri}/forms/referents${filters ? `?${queryString}` : ""}`).then((res) => res.json()),
|
||||
});
|
||||
}
|
||||
|
||||
export function useCreateForm() {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
@@ -710,6 +720,33 @@ export function useGetContractFile() {
|
||||
});
|
||||
}
|
||||
|
||||
export function useGetContractFileTemplate() {
|
||||
return useMutation({
|
||||
mutationFn: async (form_id: number) => {
|
||||
const res = await fetchWithAuth(`${Config.backend_uri}/contracts/${form_id}/base`)
|
||||
.then((res) => res);
|
||||
|
||||
if (!res.ok) throw new Error();
|
||||
const blob = await res.blob();
|
||||
const disposition = res.headers.get("Content-Disposition");
|
||||
const filename =
|
||||
disposition && disposition?.includes("filename=")
|
||||
? disposition.split("filename=")[1].replace(/"/g, "")
|
||||
: `contract_${form_id}.pdf`;
|
||||
return { blob, filename };
|
||||
},
|
||||
onSuccess: ({ blob, filename }) => {
|
||||
const url = URL.createObjectURL(blob);
|
||||
const link = document.createElement("a");
|
||||
link.href = url;
|
||||
link.download = filename;
|
||||
link.click();
|
||||
URL.revokeObjectURL(url);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
export function useGetRecap() {
|
||||
return useMutation({
|
||||
mutationFn: async (form_id: number) => {
|
||||
@@ -751,7 +788,6 @@ export function useGetAllContractFile() {
|
||||
disposition && disposition?.includes("filename=")
|
||||
? disposition.split("filename=")[1].replace(/"/g, "")
|
||||
: `contract_${form_id}.zip`;
|
||||
console.log(disposition);
|
||||
return { blob, filename };
|
||||
},
|
||||
onSuccess: ({ blob, filename }) => {
|
||||
@@ -769,20 +805,28 @@ export function useCreateContract() {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation({
|
||||
mutationFn: (newContract: ContractCreate) => {
|
||||
return fetch(`${Config.backend_uri}/contracts`, {
|
||||
mutationFn: async (newContract: ContractCreate) => {
|
||||
const res = await fetch(`${Config.backend_uri}/contracts`, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify(newContract),
|
||||
}).then(async (res) => await res.blob());
|
||||
}).then((res) => res);
|
||||
if (!res.ok) throw new Error();
|
||||
const blob = await res.blob();
|
||||
const disposition = res.headers.get("Content-Disposition");
|
||||
const filename =
|
||||
disposition && disposition?.includes("filename=")
|
||||
? disposition.split("filename=")[1].replace(/"/g, "")
|
||||
: `contract_${newContract.form_id}.pdf`;
|
||||
return { blob, filename };
|
||||
},
|
||||
onSuccess: async (pdfBlob) => {
|
||||
const url = URL.createObjectURL(pdfBlob);
|
||||
onSuccess: async ({ blob, filename }) => {
|
||||
const url = URL.createObjectURL(blob);
|
||||
const link = document.createElement("a");
|
||||
link.href = url;
|
||||
link.download = `contract.pdf`;
|
||||
link.download = filename;
|
||||
link.click();
|
||||
URL.revokeObjectURL(url);
|
||||
await queryClient.invalidateQueries({ queryKey: ["contracts"] });
|
||||
@@ -836,9 +880,9 @@ export function useCurrentUser() {
|
||||
return useQuery<UserLogged>({
|
||||
queryKey: ["currentUser"],
|
||||
queryFn: () => {
|
||||
return fetch(`${Config.backend_uri}/auth/user/me`, {
|
||||
return fetchWithAuth(`${Config.backend_uri}/auth/user/me`, {
|
||||
credentials: "include",
|
||||
}).then((res) => res.json());
|
||||
}, false).then((res) => res.json());
|
||||
},
|
||||
retry: false,
|
||||
});
|
||||
|
||||
@@ -12,6 +12,7 @@ export type Form = {
|
||||
referer: User;
|
||||
shipments: Shipment[];
|
||||
minimum_shipment_value: number | null;
|
||||
visible: boolean;
|
||||
};
|
||||
|
||||
export type FormCreate = {
|
||||
@@ -22,6 +23,7 @@ export type FormCreate = {
|
||||
productor_id: number;
|
||||
referer_id: number;
|
||||
minimum_shipment_value: number | null;
|
||||
visible: boolean;
|
||||
};
|
||||
|
||||
export type FormEdit = {
|
||||
@@ -32,6 +34,7 @@ export type FormEdit = {
|
||||
productor_id?: number | null;
|
||||
referer_id?: number | null;
|
||||
minimum_shipment_value: number | null;
|
||||
visible: boolean;
|
||||
};
|
||||
|
||||
export type FormEditPayload = {
|
||||
@@ -47,4 +50,5 @@ export type FormInputs = {
|
||||
productor_id: string;
|
||||
referer_id: string;
|
||||
minimum_shipment_value: number | string | null;
|
||||
visible: boolean;
|
||||
};
|
||||
|
||||
@@ -9,6 +9,7 @@ export const PaymentMethods = [
|
||||
export type PaymentMethod = {
|
||||
name: string;
|
||||
details: string;
|
||||
max: number | string | null;
|
||||
};
|
||||
|
||||
export type Productor = {
|
||||
|
||||
@@ -15,7 +15,7 @@ export type User = {
|
||||
name: string;
|
||||
email: string;
|
||||
products: Product[];
|
||||
roles: string[];
|
||||
roles: Role[];
|
||||
};
|
||||
|
||||
export type UserInputs = {
|
||||
|
||||
Reference in New Issue
Block a user