add all suppress modal
All checks were successful
Deploy Amap / deploy (push) Successful in 41s

This commit is contained in:
Julien Aldon
2026-03-06 16:48:38 +01:00
parent 74bf1474e2
commit 46b369ecd9
39 changed files with 654 additions and 372 deletions

View File

@@ -24,3 +24,6 @@
### Show on cascade deletion
## Update contract after (without registration)
## Preview form (if not visible can be accessed by referer nothing is stored)
## View and edit contract application (dashboard/contracts/id/edit/)

View File

@@ -196,6 +196,24 @@ def get_contract_file(
)
@router.get(
'/{_id}/preview-delete',
response_model=list[models.DeleteDependency]
)
async def preview_delete(
_id: int,
session: Session = Depends(get_session),
user: models.User = Depends(get_current_user),
):
if not service.is_allowed(session, user, _id=_id):
raise HTTPException(
status_code=403,
detail=messages.Messages.not_allowed('contract', 'delete')
)
result = []
return result
@router.get('/{form_id}/files')
def get_contract_files(
form_id: int,

View File

@@ -18,6 +18,30 @@ def get_productors(
return service.get_all(session, user, names, types)
@router.get(
'/{_id}/preview-delete',
response_model=list[models.DeleteDependency]
)
async def preview_delete(
_id: int,
session: Session = Depends(get_session),
user: models.User = Depends(get_current_user),
):
if not service.is_allowed(session, user, _id=_id):
raise HTTPException(
status_code=403,
detail=messages.Messages.not_allowed('productors', 'delete')
)
try:
result = service.get_delete_dependencies(
session,
_id
)
except exceptions.ProductorNotFoundError as error:
raise HTTPException(status_code=404, detail=str(error)) from error
return result
@router.get('/{_id}', response_model=models.ProductorPublic)
def get_productor(
_id: int,

View File

@@ -93,6 +93,33 @@ def delete_one(session: Session, _id: int) -> models.ProductorPublic:
session.commit()
return result
def get_delete_dependencies(
session: Session,
_id: int
) -> list[models.DeleteDependency]:
statement = select(models.Productor).where(models.Productor.id == _id)
result = session.exec(statement)
productor = result.first()
if not productor:
raise exceptions.ProductorNotFoundError(
messages.Messages.not_found('productor'))
products_statement = (
select(models.Product)
.where(models.Product.productor_id == _id)
.distinct()
)
products = session.exec(products_statement).all()
result = [
models.DeleteDependency(
name=pro.name,
id=pro.id,
type='product'
) for pro in products
]
return result
def is_allowed(
session: Session,
user: models.User,

View File

@@ -26,6 +26,23 @@ def get_products(
)
@router.get(
'/{_id}/preview-delete',
response_model=list[models.DeleteDependency]
)
async def preview_delete(
_id: int,
session: Session = Depends(get_session),
user: models.User = Depends(get_current_user),
):
if not service.is_allowed(session, user, _id=_id):
raise HTTPException(
status_code=403,
detail=messages.Messages.not_allowed('product', 'delete')
)
return []
@router.get('/{_id}', response_model=models.ProductPublic)
def get_product(
_id: int,

View File

@@ -93,6 +93,7 @@ def delete_one(
session.commit()
return result
def is_allowed(
session: Session,
user: models.User,
@@ -103,12 +104,8 @@ def is_allowed(
return False
if not _id:
statement = (
select(models.Product)
.join(
models.Productor,
models.Product.productor_id == models.Productor.id
)
.where(models.Product.id == product.productor_id)
select(models.Productor)
.where(models.Productor.id == product.productor_id)
)
productor = session.exec(statement).first()
return productor.type in [r.name for r in user.roles]

View File

@@ -26,6 +26,23 @@ def get_shipments(
)
@router.get(
'/{_id}/preview-delete',
response_model=list[models.DeleteDependency]
)
async def preview_delete(
_id: int,
session: Session = Depends(get_session),
user: models.User = Depends(get_current_user),
):
if not service.is_allowed(session, user, _id=_id):
raise HTTPException(
status_code=403,
detail=messages.Messages.not_allowed('shipment', 'delete')
)
return []
@router.get('/{_id}', response_model=models.ShipmentPublic)
def get_shipment(
_id: int,

View File

@@ -36,6 +36,22 @@ def get_roles(
return service.get_roles(session)
@router.get(
'/{_id}/preview-delete',
response_model=list[models.DeleteDependency]
)
async def preview_delete(
_id: int,
user: models.User = Depends(get_current_user),
):
if not service.is_allowed(user):
raise HTTPException(
status_code=403,
detail=messages.Messages.not_allowed('user', 'delete')
)
return []
@router.get('/{_id}', response_model=models.UserPublic)
def get_user(
_id: int,

View File

@@ -39,6 +39,10 @@
"create productor": "create producer",
"edit productor": "edit producer",
"remove productor": "remove producer",
"remove form": "remove form",
"edit shipment": "edit shipment",
"create shipment": "create shipment",
"create user": "create user",
"home": "home",
"dashboard": "dashboard",
"filter by name": "filter by name",

View File

@@ -39,6 +39,10 @@
"create productor": "créer le/la producteur·trice",
"edit productor": "modifier le/la producteur·trice",
"remove productor": "supprimer le/la producteur·trice",
"remove form": "supprimer un formulaire de contrat",
"edit shipment": "modifier la livraison",
"create shipment": "créer la livraison",
"create user": "créer l'utilisateur·trice",
"home": "accueil",
"dashboard": "tableau de bord",
"filter by name": "filtrer par nom",
@@ -183,7 +187,7 @@
"liter": "litres (L)",
"are you sure you want to delete": "êtes vous sûr de vouloir supprimer",
"this will also delete": "cette action supprimera aussi",
"delete entity": "supprimer un {{entity}}",
"delete entity": "supprimer le/la {{entity}}",
"delete": "supprimer",
"success": "succès",
"success edit": "{{entity}} correctement édité",

View File

@@ -2,16 +2,17 @@ import { ActionIcon, Table, Tooltip } from "@mantine/core";
import { type Contract } from "@/services/resources/contracts";
import { IconDownload, IconX } from "@tabler/icons-react";
import { t } from "@/config/i18n";
import { useDeleteContract, useGetContractFile } from "@/services/api";
import { useGetContractFile } from "@/services/api";
import { useCallback } from "react";
import { useNavigate } from "react-router";
export type ContractRowProps = {
contract: Contract;
};
export default function ContractRow({ contract }: ContractRowProps) {
const deleteMutation = useDeleteContract();
const getContractMutation = useGetContractFile();
const navigate = useNavigate();
const handleDownload = useCallback(async () => {
getContractMutation.mutateAsync(contract.id);
@@ -29,12 +30,10 @@ export default function ContractRow({ contract }: ContractRowProps) {
{contract.cheque_quantity > 0 && contract.cheque_quantity} {contract.payment_method}
</Table.Td>
<Table.Td>
{
`${Intl.NumberFormat("fr-FR", {
{`${Intl.NumberFormat("fr-FR", {
style: "currency",
currency: "EUR",
}).format(contract.total_price)}`
}
}).format(contract.total_price)}`}
</Table.Td>
<Table.Td>
<Tooltip label={t("download contract", { capfirst: true })}>
@@ -54,8 +53,9 @@ export default function ContractRow({ contract }: ContractRowProps) {
color="red"
size="sm"
mr="5"
onClick={() => {
deleteMutation.mutate(contract.id);
onClick={(e) => {
e.stopPropagation();
navigate(`/dashboard/contracts/${contract.id}/delete`);
}}
>
<IconX />

View File

@@ -7,8 +7,8 @@ import { Link } from "react-router";
export type DeleteModalProps = ModalBaseProps & {
handleSubmit: (id: number) => void;
entityType: string;
entity?: {name: string, id: number} | undefined;
}
entity?: { name: string; id: number } | undefined;
};
export function DeleteModal({
opened,
@@ -18,7 +18,7 @@ export function DeleteModal({
entity,
}: DeleteModalProps) {
if (!entity) {
return null
return null;
}
const { data: deleteDependencies } = useGetDeleteDependencies(entityType, entity.id);
@@ -29,16 +29,16 @@ export function DeleteModal({
title={t("delete entity", { capfirst: true, entity: t(entityType) })}
>
<Text>{`${t("are you sure you want to delete", { capfirst: true })} : "${entity.name}"`}</Text>
{deleteDependencies && deleteDependencies.length > 0 ? <Text>{`${t("this will also delete", {capfirst: true})} :`}</Text> : null}
{deleteDependencies && deleteDependencies.length > 0 ? (
<Text>{`${t("this will also delete", { capfirst: true })} :`}</Text>
) : null}
{
<List>
{
deleteDependencies?.map((dependency) => (
<List.Item
key={dependency.id}
>
{
dependency.type === 'contract' ? `${t(dependency.type, {capfirst: true})} - ${dependency.name}` :
{deleteDependencies?.map((dependency) => (
<List.Item key={dependency.id}>
{dependency.type === "contract" ? (
`${t(dependency.type, { capfirst: true })} - ${dependency.name}`
) : (
<Link
to={`/dashboard/${dependency.type}s/${dependency.id}/edit`}
target="_blank"
@@ -46,11 +46,9 @@ export function DeleteModal({
>
{`${t(dependency.type, { capfirst: true })} - ${dependency.name}`}
</Link>
}
)}
</List.Item>
))
}
))}
</List>
}
<Group mt="sm" justify="space-between">

View File

@@ -10,26 +10,22 @@ export type FormCardProps = {
};
export function FormCard({ form }: FormCardProps) {
const contractBaseTemplate = useGetContractFileTemplate()
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")}
>
<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)
await contractBaseTemplate.mutateAsync(form.id);
}}
>
<IconDownload />
</ActionIcon>
</Tooltip>
<Tooltip
label={t("fill contract online")}
>
<Tooltip label={t("fill contract online")}>
<ActionIcon
variant={"outline"}
aria-label={t("fill contract online")}

View File

@@ -34,7 +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
visible: currentForm?.visible ?? false,
},
validate: {
name: (value) =>
@@ -53,8 +53,7 @@ export default function FormModal({ opened, onClose, currentForm, handleSubmit }
});
const usersSelect = useMemo(() => {
if (!users)
return [];
if (!users) return [];
return users?.map((user) => ({
value: String(user.id),
label: `${user.name}`,
@@ -62,8 +61,7 @@ export default function FormModal({ opened, onClose, currentForm, handleSubmit }
}, [users]);
const productorsSelect = useMemo(() => {
if (!productors)
return [];
if (!productors) return [];
return productors?.map((prod) => ({
value: String(prod.id),
label: `${prod.name}`,
@@ -142,9 +140,13 @@ export default function FormModal({ opened, onClose, currentForm, handleSubmit }
radius="sm"
{...form.getInputProps("minimum_shipment_value")}
/>
<Checkbox mt="lg"
<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})}
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">

View File

@@ -4,7 +4,6 @@ import { IconEdit, IconX } from "@tabler/icons-react";
import { t } from "@/config/i18n";
import type { Form } from "@/services/resources/forms";
export type FormRowProps = {
form: Form;
};
@@ -15,10 +14,11 @@ export default function FormRow({ form }: FormRowProps) {
return (
<Table.Tr key={form.id}>
<Table.Td>
{form.visible ?
<Badge color="green">{t("visible", {capfirst: true})}</Badge> :
{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>

View File

@@ -43,7 +43,7 @@ export function ContractCheque({ inputForm, price, productor }: ContractChequePr
}, [inputForm.values.cheque_quantity, price, inputForm.values.cheques]);
const paymentMethod = useMemo(() => {
return productor?.payment_methods.find((el) => el.name === "cheque")
return productor?.payment_methods.find((el) => el.name === "cheque");
}, [productor]);
return (
@@ -57,7 +57,9 @@ export function ContractCheque({ inputForm, price, productor }: ContractChequePr
{ capfirst: true },
)}
min={1}
max={paymentMethod?.max && paymentMethod?.max !== "" ? Number(paymentMethod?.max) : 3}
max={
paymentMethod?.max && paymentMethod?.max !== "" ? Number(paymentMethod?.max) : 3
}
{...inputForm.getInputProps(`cheque_quantity`)}
/>
<Group grow>
@@ -67,11 +69,7 @@ export function ContractCheque({ inputForm, price, productor }: ContractChequePr
label={t("cheque id", { capfirst: true })}
placeholder={t("cheque id", { capfirst: true })}
{...inputForm.getInputProps(`cheques.${index}.name`)}
error={
cheque.name == "" ?
inputForm?.errors.cheques :
null
}
error={cheque.name == "" ? inputForm?.errors.cheques : null}
/>
<NumberInput
readOnly

View File

@@ -49,20 +49,30 @@ export function ProductorModal({
type: (value) =>
!value ? `${t("type", { capfirst: true })} ${t("is required")}` : null,
payment_methods: (value) =>
value.length === 0 || value.some(
(payment) =>
payment.name === "cheque" &&
payment.details === "") ?
`${t("a payment method", { capfirst: true })} ${t("is required")}` : null,
value.length === 0 ||
value.some((payment) => payment.name === "cheque" && payment.details === "")
? `${t("a payment method", { capfirst: true })} ${t("is required")}`
: null,
},
});
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]);
return (
<Modal opened={opened} onClose={onClose} title={t("create productor", { capfirst: true })}>
<Modal
opened={opened}
onClose={onClose}
title={
currentProductor
? t("edit productor", { capfirst: true })
: t("create productor", { capfirst: true })
}
>
<Title order={4}>{t("Informations", { capfirst: true })}</Title>
<TextInput
label={t("productor name", { capfirst: true })}

View File

@@ -2,7 +2,6 @@ import { ActionIcon, Badge, Table, Tooltip } from "@mantine/core";
import { t } from "@/config/i18n";
import { IconEdit, IconX } from "@tabler/icons-react";
import type { Productor } from "@/services/resources/productors";
import { useDeleteProductor } from "@/services/api";
import { useNavigate, useSearchParams } from "react-router";
export type ProductorRowProps = {
@@ -11,7 +10,6 @@ export type ProductorRowProps = {
export default function ProductorRow({ productor }: ProductorRowProps) {
const [searchParams] = useSearchParams();
const deleteMutation = useDeleteProductor();
const navigate = useNavigate();
return (
@@ -46,8 +44,11 @@ export default function ProductorRow({ productor }: ProductorRowProps) {
color="red"
size="sm"
mr="5"
onClick={() => {
deleteMutation.mutate(productor.id);
onClick={(e) => {
e.stopPropagation();
navigate(
`/dashboard/productors/${productor.id}/delete${searchParams ? `?${searchParams.toString()}` : ""}`,
);
}}
>
<IconX />

View File

@@ -59,8 +59,7 @@ export function ProductModal({ opened, onClose, currentProduct, handleSubmit }:
});
const productorsSelect = useMemo(() => {
if (!productors)
return [];
if (!productors) return [];
return productors?.map((productor) => ({
value: String(productor.id),
label: `${productor.name}`,
@@ -68,7 +67,15 @@ export function ProductModal({ opened, onClose, currentProduct, handleSubmit }:
}, [productors]);
return (
<Modal opened={opened} onClose={onClose} title={t("create product", { capfirst: true })}>
<Modal
opened={opened}
onClose={onClose}
title={
currentProduct
? t("edit product", { capfirst: true })
: t("create product", { capfirst: true })
}
>
<Title order={4}>{t("informations", { capfirst: true })}</Title>
<Select
label={t("productor", { capfirst: true })}

View File

@@ -2,7 +2,6 @@ import { ActionIcon, Table, Tooltip } from "@mantine/core";
import { t } from "@/config/i18n";
import { IconEdit, IconX } from "@tabler/icons-react";
import { ProductType, ProductUnit, type Product } from "@/services/resources/products";
import { useDeleteProduct } from "@/services/api";
import { useNavigate, useSearchParams } from "react-router";
export type ProductRowProps = {
@@ -11,7 +10,6 @@ export type ProductRowProps = {
export default function ProductRow({ product }: ProductRowProps) {
const [searchParams] = useSearchParams();
const deleteMutation = useDeleteProduct();
const navigate = useNavigate();
return (
@@ -59,8 +57,11 @@ export default function ProductRow({ product }: ProductRowProps) {
color="red"
size="sm"
mr="5"
onClick={() => {
deleteMutation.mutate(product.id);
onClick={(e) => {
e.stopPropagation();
navigate(
`/dashboard/products/${product.id}/delete${searchParams ? `?${searchParams.toString()}` : ""}`,
);
}}
>
<IconX />

View File

@@ -48,8 +48,7 @@ export default function ShipmentModal({
const { data: allProductors } = useGetProductors();
const formsSelect = useMemo(() => {
if (!allForms)
return [];
if (!allForms) return [];
return allForms?.map((currentForm) => ({
value: String(currentForm.id),
label: `${currentForm.name} ${currentForm.season}`,
@@ -75,7 +74,11 @@ export default function ShipmentModal({
<Modal
opened={opened}
onClose={onClose}
title={currentShipment ? t("edit shipment") : t("create shipment")}
title={
currentShipment
? t("edit shipment", { capfirst: true })
: t("create shipment", { capfirst: true })
}
>
<TextInput
label={t("shipment name", { capfirst: true })}

View File

@@ -1,6 +1,5 @@
import { ActionIcon, Table, Tooltip } from "@mantine/core";
import { useNavigate, useSearchParams } from "react-router";
import { useDeleteShipment } from "@/services/api";
import { IconEdit, IconX } from "@tabler/icons-react";
import { t } from "@/config/i18n";
import type { Shipment } from "@/services/resources/shipments";
@@ -11,7 +10,6 @@ export type ShipmentRowProps = {
export default function ShipmentRow({ shipment }: ShipmentRowProps) {
const [searchParams] = useSearchParams();
const deleteMutation = useDeleteShipment();
const navigate = useNavigate();
return (
@@ -20,7 +18,7 @@ export default function ShipmentRow({ shipment }: ShipmentRowProps) {
<Table.Td>{shipment.date}</Table.Td>
<Table.Td>{`${shipment.form.name} ${shipment.form.season}`}</Table.Td>
<Table.Td>
<Tooltip label={t("edit productor", { capfirst: true })}>
<Tooltip label={t("edit shipment", { capfirst: true })}>
<ActionIcon
size="sm"
mr="5"
@@ -34,13 +32,16 @@ export default function ShipmentRow({ shipment }: ShipmentRowProps) {
<IconEdit />
</ActionIcon>
</Tooltip>
<Tooltip label={t("remove productor", { capfirst: true })}>
<Tooltip label={t("remove shipment", { capfirst: true })}>
<ActionIcon
color="red"
size="sm"
mr="5"
onClick={() => {
deleteMutation.mutate(shipment.id);
onClick={(e) => {
e.stopPropagation();
navigate(
`/dashboard/shipments/${shipment.id}/delete${searchParams ? `?${searchParams.toString()}` : ""}`,
);
}}
>
<IconX />

View File

@@ -36,13 +36,20 @@ export function UserModal({ opened, onClose, currentUser, handleSubmit }: UserMo
});
const roleSelect = useMemo(() => {
if (!allRoles)
return [];
if (!allRoles) return [];
return allRoles?.map((role) => ({ value: String(role.name), label: role.name }));
}, [allRoles]);
return (
<Modal opened={opened} onClose={onClose} title={t("create user", { capfirst: true })}>
<Modal
opened={opened}
onClose={onClose}
title={
currentUser
? t("edit user", { capfirst: true })
: t("create user", { capfirst: true })
}
>
<Title order={4}>{t("informations", { capfirst: true })}</Title>
<TextInput
label={t("user name", { capfirst: true })}

View File

@@ -2,7 +2,6 @@ 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";
import { useDeleteUser } from "@/services/api";
import { useNavigate, useSearchParams } from "react-router";
export type UserRowProps = {
@@ -11,7 +10,6 @@ export type UserRowProps = {
export default function UserRow({ user }: UserRowProps) {
const [searchParams] = useSearchParams();
const deleteMutation = useDeleteUser();
const navigate = useNavigate();
return (
@@ -21,8 +19,8 @@ export default function UserRow({ user }: UserRowProps) {
<Table.Td style={{ maxWidth: 200 }}>
<Box
style={{
display: 'flex',
gap: 4
display: "flex",
gap: 4,
}}
>
{user.roles.slice(0, 3).map((value) => (
@@ -30,17 +28,13 @@ export default function UserRow({ user }: UserRowProps) {
{t(value.name, { capfirst: true })}
</Badge>
))}
{
user.roles.length > 3 && (
<Tooltip
label={user.roles.slice(3).map(role=>`${role.name} `)}
>
{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>
@@ -63,8 +57,11 @@ export default function UserRow({ user }: UserRowProps) {
color="red"
size="sm"
mr="5"
onClick={() => {
deleteMutation.mutate(user.id);
onClick={(e) => {
e.stopPropagation();
navigate(
`/dashboard/users/${user.id}/delete${searchParams ? `?${searchParams.toString()}` : ""}`,
);
}}
>
<IconX />

View File

@@ -51,7 +51,9 @@ export function Contract() {
payment_method: (value) =>
!value ? `${t("a payment method", { capfirst: true })} ${t("is required")}` : null,
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,
},
});
@@ -137,12 +139,13 @@ export function Contract() {
const formValues = inputForm.getValues();
const contract = {
...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,
products: tranformProducts(withDefaultValues(formValues.products)),
};
await createContractMutation.mutateAsync(contract);
window.location.href = '/';
window.location.href = "/";
} else {
const firstErrorField = Object.keys(errors.errors)[0];
const ref = inputRefs.current[firstErrorField];
@@ -165,7 +168,7 @@ export function Contract() {
);
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={3}>{t("informations", { capfirst: true })}</Title>
<Text size="sm">
@@ -289,11 +292,7 @@ export function Contract() {
}}
/>
{inputForm.values.payment_method === "cheque" ? (
<ContractCheque
productor={form?.productor}
price={price}
inputForm={inputForm}
/>
<ContractCheque productor={form?.productor} price={price} inputForm={inputForm} />
) : null}
{inputForm.values.payment_method === "transfer" ? (
<Text>
@@ -322,7 +321,9 @@ export function Contract() {
</Text>
<Button
leftSection={<IconDownload />}
aria-label={t("submit contracts")} onClick={handleSubmit}>
aria-label={t("submit contracts")}
onClick={handleSubmit}
>
{t("submit", { capfirst: true })}
</Button>
</Overlay>

View File

@@ -1,6 +1,12 @@
import { ActionIcon, Group, Loader, ScrollArea, Stack, Table, Title, Tooltip } from "@mantine/core";
import { t } from "@/config/i18n";
import { useGetAllContractFile, useGetContracts, useGetRecap } from "@/services/api";
import {
useDeleteContract,
useGetAllContractFile,
useGetContract,
useGetContracts,
useGetRecap,
} from "@/services/api";
import { IconDownload, IconTableExport } from "@tabler/icons-react";
import ContractRow from "@/components/Contracts/Row";
import { useLocation, useNavigate, useSearchParams } from "react-router";
@@ -8,6 +14,7 @@ import { ContractModal } from "@/components/Contracts/Modal";
import { useCallback, useMemo } from "react";
import { type Contract } from "@/services/resources/contracts";
import ContractsFilters from "@/components/Contracts/Filter";
import { DeleteModal } from "@/components/DeleteModal";
export default function Contracts() {
const [searchParams, setSearchParams] = useSearchParams();
@@ -17,18 +24,29 @@ export default function Contracts() {
const getRecapMutation = useGetRecap();
const isdownload = location.pathname.includes("/download");
const isrecap = location.pathname.includes("/export");
const isDelete = location.pathname.includes("/delete");
const deleteId = useMemo(() => {
if (isDelete) {
return location.pathname.split("/")[3];
}
return null;
}, [location]);
const closeModal = useCallback(() => {
navigate(`/dashboard/contracts${searchParams ? `?${searchParams.toString()}` : ""}`);
}, [navigate, searchParams]);
const { data: contracts, isPending } = useGetContracts(searchParams);
const { data: currentContract } = useGetContract(Number(deleteId), {
enabled: !!deleteId,
});
const { data: allContracts } = useGetContracts();
const deleteContractMutation = useDeleteContract();
const forms = useMemo(() => {
if (!allContracts)
return [];
if (!allContracts) return [];
return allContracts
?.map((contract: Contract) => contract.form.name)
.filter((contract, index, array) => array.indexOf(contract) === index);
@@ -61,7 +79,7 @@ export default function Contracts() {
await getRecapMutation.mutateAsync(id);
},
[getAllContractFilesMutation],
)
);
if (!contracts || isPending)
return (
@@ -87,9 +105,7 @@ export default function Contracts() {
<IconDownload />
</ActionIcon>
</Tooltip>
<Tooltip
label={t("download recap", { capfirst: true })}
>
<Tooltip label={t("download recap", { capfirst: true })}>
<ActionIcon
disabled={false}
onClick={(e) => {
@@ -114,6 +130,18 @@ export default function Contracts() {
onClose={closeModal}
handleSubmit={handleDownloadRecap}
/>
<DeleteModal
opened={isDelete}
onClose={closeModal}
handleSubmit={(id: number) => {
deleteContractMutation.mutate(id);
}}
entityType={"contract"}
entity={{
name: `${currentContract?.firstname} ${currentContract?.lastname}`,
id: currentContract?.id || 0,
}}
/>
</Group>
<ContractsFilters
forms={forms || []}

View File

@@ -16,17 +16,50 @@ export default function Dashboard() {
onChange={(value) => navigate(`/dashboard/${value}`)}
>
<Tabs.List mb="md">
<Tabs.Tab renderRoot={(props) => (<Link to="/dashboard/help" {...props}></Link>)} value="help">{t("help", { capfirst: true })}</Tabs.Tab>
<Tabs.Tab renderRoot={(props) => (<Link to="/dashboard/productors" {...props}></Link>)} value="productors">{t("productors", { capfirst: true })}</Tabs.Tab>
<Tabs.Tab renderRoot={(props) => (<Link to="/dashboard/products" {...props}></Link>)} value="products">{t("products", { capfirst: true })}</Tabs.Tab>
<Tabs.Tab renderRoot={(props) => (<Link to="/dashboard/forms" {...props}></Link>)} value="forms">{t("forms", { capfirst: true })}</Tabs.Tab>
<Tabs.Tab renderRoot={(props) => (<Link to="/dashboard/shipments" {...props}></Link>)} value="shipments">{t("shipments", { capfirst: true })}</Tabs.Tab>
<Tabs.Tab renderRoot={(props) => (<Link to="/dashboard/contracts" {...props}></Link>)} value="contracts">{t("contracts", { capfirst: true })}</Tabs.Tab>
{
loggedUser?.user?.roles && loggedUser?.user?.roles?.length > 5 ?
<Tabs.Tab renderRoot={(props) => (<Link to="/dashboard/users" {...props}></Link>)} value="users">{t("users", { capfirst: true })}</Tabs.Tab> :
null
}
<Tabs.Tab
renderRoot={(props) => <Link to="/dashboard/help" {...props}></Link>}
value="help"
>
{t("help", { capfirst: true })}
</Tabs.Tab>
<Tabs.Tab
renderRoot={(props) => <Link to="/dashboard/productors" {...props}></Link>}
value="productors"
>
{t("productors", { capfirst: true })}
</Tabs.Tab>
<Tabs.Tab
renderRoot={(props) => <Link to="/dashboard/products" {...props}></Link>}
value="products"
>
{t("products", { capfirst: true })}
</Tabs.Tab>
<Tabs.Tab
renderRoot={(props) => <Link to="/dashboard/forms" {...props}></Link>}
value="forms"
>
{t("forms", { capfirst: true })}
</Tabs.Tab>
<Tabs.Tab
renderRoot={(props) => <Link to="/dashboard/shipments" {...props}></Link>}
value="shipments"
>
{t("shipments", { capfirst: true })}
</Tabs.Tab>
<Tabs.Tab
renderRoot={(props) => <Link to="/dashboard/contracts" {...props}></Link>}
value="contracts"
>
{t("contracts", { capfirst: true })}
</Tabs.Tab>
{loggedUser?.user?.roles && loggedUser?.user?.roles?.length > 5 ? (
<Tabs.Tab
renderRoot={(props) => <Link to="/dashboard/users" {...props}></Link>}
value="users"
>
{t("users", { capfirst: true })}
</Tabs.Tab>
) : null}
</Tabs.List>
<Outlet />
</Tabs>

View File

@@ -1,5 +1,11 @@
import { Stack, Loader, Title, Group, ActionIcon, Tooltip, Table, ScrollArea } from "@mantine/core";
import { useCreateForm, useDeleteForm, useEditForm, useGetForm, useGetReferentForms } from "@/services/api";
import {
useCreateForm,
useDeleteForm,
useEditForm,
useGetForm,
useGetReferentForms,
} from "@/services/api";
import { t } from "@/config/i18n";
import { useLocation, useNavigate, useSearchParams } from "react-router";
import { IconPlus } from "@tabler/icons-react";

View File

@@ -245,7 +245,10 @@ export function Help() {
<Title order={3}>{t("export contracts", { capfirst: true })}</Title>
<Stack>
<Text>
{t("to export contracts submissions before sending to the productor go to the contracts section", {capfirst: true})}
{t(
"to export contracts submissions before sending to the productor go to the contracts section",
{ capfirst: true },
)}
<ActionIcon
ml="4"
size="xs"
@@ -260,21 +263,32 @@ export function Help() {
<IconLink />
</ActionIcon>
</Text>
<Text>{t("in this page you can view all contracts submissions, you can remove duplicates submission or download a specific contract", {capfirst: true})}</Text>
<Text>
{t("you can download all contracts for your form using the export all", {capfirst: true})}{" "}
{t(
"in this page you can view all contracts submissions, you can remove duplicates submission or download a specific contract",
{ capfirst: true },
)}
</Text>
<Text>
{t("you can download all contracts for your form using the export all", {
capfirst: true,
})}{" "}
<ActionIcon size="sm">
<IconDownload />
</ActionIcon>{" "}
{t("button in top right of the page", { section: t("contracts") })}{" "}
{t("in the same corner you can download a recap by clicking on the button", {capfirst: true})}{" "}
{t("in the same corner you can download a recap by clicking on the button", {
capfirst: true,
})}{" "}
<ActionIcon size="sm">
<IconTableExport />
</ActionIcon>{" "}
</Text>
<Text>
{t("once all contracts downloaded, you can delete the form (to avoid new submissions) and hide it from the home page", {capfirst: true})}
{t(
"once all contracts downloaded, you can delete the form (to avoid new submissions) and hide it from the home page",
{ capfirst: true },
)}
</Text>
</Stack>
<Title order={3}>{t("glossary", { capfirst: true })}</Title>

View File

@@ -23,12 +23,14 @@ export function Home() {
if (searchParams.get("userNotAllowed")) {
showNotification({
title: t("user not allowed", { capfirst: true }),
message: t("your keycloak user has no roles, please contact your administrator", {capfirst: true}),
message: t("your keycloak user has no roles, please contact your administrator", {
capfirst: true,
}),
color: "red",
autoClose: 5000,
});
}
}, [searchParams])
}, [searchParams]);
return (
<Stack mt="lg">

View File

@@ -2,6 +2,7 @@ import { ActionIcon, Group, Loader, ScrollArea, Stack, Table, Title, Tooltip } f
import { t } from "@/config/i18n";
import {
useCreateProductor,
useDeleteProductor,
useEditProductor,
useGetProductor,
useGetProductors,
@@ -13,6 +14,7 @@ import { ProductorModal } from "@/components/Productors/Modal";
import { useCallback, useMemo } from "react";
import type { Productor, ProductorInputs } from "@/services/resources/productors";
import ProductorsFilters from "@/components/Productors/Filter";
import { DeleteModal } from "@/components/DeleteModal";
export default function Productors() {
const [searchParams, setSearchParams] = useSearchParams();
@@ -23,9 +25,10 @@ export default function Productors() {
const { data: allProductors } = useGetProductors();
const isCreate = location.pathname === "/dashboard/productors/create";
const isEdit = location.pathname.includes("/edit");
const isDelete = location.pathname.includes("/delete");
const editId = useMemo(() => {
if (isEdit) {
if (isEdit || isDelete) {
return location.pathname.split("/")[3];
}
return null;
@@ -40,16 +43,14 @@ export default function Productors() {
}, [navigate, searchParams]);
const names = useMemo(() => {
if (!allProductors)
return [];
if (!allProductors) return [];
return allProductors
?.map((productor: Productor) => productor.name)
.filter((season, index, array) => array.indexOf(season) === index);
}, [allProductors]);
const types = useMemo(() => {
if (!allProductors)
return [];
if (!allProductors) return [];
return allProductors
?.map((productor: Productor) => productor.type)
.filter((productor, index, array) => array.indexOf(productor) === index);
@@ -57,6 +58,7 @@ export default function Productors() {
const createProductorMutation = useCreateProductor();
const editProductorMutation = useEditProductor();
const deleteProductorMutation = useDeleteProductor();
const handleCreateProductor = useCallback(
async (productor: ProductorInputs) => {
@@ -65,8 +67,8 @@ export default function Productors() {
payment_methods: productor.payment_methods.map((payment) => ({
name: payment.name,
details: payment.details,
max: payment.max === "" ? null : payment.max
}))
max: payment.max === "" ? null : payment.max,
})),
});
closeModal();
},
@@ -83,8 +85,8 @@ export default function Productors() {
payment_methods: productor.payment_methods.map((payment) => ({
name: payment.name,
details: payment.details,
max: payment.max === "" ? null : payment.max
}))
max: payment.max === "" ? null : payment.max,
})),
},
});
closeModal();
@@ -143,6 +145,15 @@ export default function Productors() {
currentProductor={currentProductor}
handleSubmit={handleEditProductor}
/>
<DeleteModal
opened={isDelete}
onClose={closeModal}
handleSubmit={(id: number) => {
deleteProductorMutation.mutate(id);
}}
entityType={"productor"}
entity={currentProductor}
/>
</Group>
<ProductorsFilters
names={names || []}

View File

@@ -1,6 +1,12 @@
import { ActionIcon, Group, Loader, ScrollArea, Stack, Table, Title, Tooltip } from "@mantine/core";
import { t } from "@/config/i18n";
import { useCreateProduct, useEditProduct, useGetProduct, useGetProducts } from "@/services/api";
import {
useCreateProduct,
useDeleteProduct,
useEditProduct,
useGetProduct,
useGetProducts,
} from "@/services/api";
import { IconPlus } from "@tabler/icons-react";
import ProductRow from "@/components/Products/Row";
import { useLocation, useNavigate, useSearchParams } from "react-router";
@@ -12,6 +18,7 @@ import {
type ProductInputs,
} from "@/services/resources/products";
import ProductsFilters from "@/components/Products/Filter";
import { DeleteModal } from "@/components/DeleteModal";
export default function Products() {
const [searchParams, setSearchParams] = useSearchParams();
@@ -19,9 +26,10 @@ export default function Products() {
const navigate = useNavigate();
const isCreate = location.pathname === "/dashboard/products/create";
const isEdit = location.pathname.includes("/edit");
const isDelete = location.pathname.includes("/delete");
const editId = useMemo(() => {
if (isEdit) {
if (isEdit || isDelete) {
return location.pathname.split("/")[3];
}
return null;
@@ -38,16 +46,14 @@ export default function Products() {
const { data: allProducts } = useGetProducts();
const names = useMemo(() => {
if (!allProducts)
return [];
if (!allProducts) return [];
return allProducts
?.map((product: Product) => product.name)
.filter((season, index, array) => array.indexOf(season) === index);
}, [allProducts]);
const productors = useMemo(() => {
if (!allProducts)
return [];
if (!allProducts) return [];
return allProducts
?.map((product: Product) => product.productor.name)
.filter((productor, index, array) => array.indexOf(productor) === index);
@@ -55,6 +61,7 @@ export default function Products() {
const createProductMutation = useCreateProduct();
const editProductMutation = useEditProduct();
const deleteProductMutation = useDeleteProduct();
const handleCreateProduct = useCallback(
async (product: ProductInputs) => {
@@ -134,6 +141,15 @@ export default function Products() {
currentProduct={currentProduct}
handleSubmit={handleEditProduct}
/>
<DeleteModal
opened={isDelete}
onClose={closeModal}
handleSubmit={(id: number) => {
deleteProductMutation.mutate(id);
}}
entityType={"product"}
entity={currentProduct}
/>
<ScrollArea type="auto">
<Table striped>
<Table.Thead>

View File

@@ -2,6 +2,7 @@ import { ActionIcon, Group, Loader, ScrollArea, Stack, Table, Title, Tooltip } f
import { t } from "@/config/i18n";
import {
useCreateShipment,
useDeleteShipment,
useEditShipment,
useGetShipment,
useGetShipments,
@@ -17,6 +18,7 @@ import {
} from "@/services/resources/shipments";
import ShipmentModal from "@/components/Shipments/Modal";
import ShipmentsFilters from "@/components/Shipments/Filter";
import { DeleteModal } from "@/components/DeleteModal";
export default function Shipments() {
const [searchParams, setSearchParams] = useSearchParams();
@@ -25,9 +27,10 @@ export default function Shipments() {
const isCreate = location.pathname === "/dashboard/shipments/create";
const isEdit = location.pathname.includes("/edit");
const isDelete = location.pathname.includes("/delete");
const editId = useMemo(() => {
if (isEdit) {
if (isEdit || isDelete) {
return location.pathname.split("/")[3];
}
return null;
@@ -44,16 +47,14 @@ export default function Shipments() {
const { data: allShipments } = useGetShipments();
const names = useMemo(() => {
if (!allShipments)
return [];
if (!allShipments) return [];
return allShipments
?.map((shipment: Shipment) => shipment.name)
.filter((season, index, array) => array.indexOf(season) === index);
}, [allShipments]);
const forms = useMemo(() => {
if (!allShipments)
return [];
if (!allShipments) return [];
return allShipments
?.map((shipment: Shipment) => shipment.form.name)
.filter((season, index, array) => array.indexOf(season) === index);
@@ -61,6 +62,7 @@ export default function Shipments() {
const createShipmentMutation = useCreateShipment();
const editShipmentMutation = useEditShipment();
const deleteShipmentMutation = useDeleteShipment();
const handleCreateShipment = useCallback(
async (shipment: ShipmentInputs) => {
@@ -133,6 +135,15 @@ export default function Shipments() {
currentShipment={currentShipment}
handleSubmit={handleEditShipment}
/>
<DeleteModal
opened={isDelete}
onClose={closeModal}
handleSubmit={(id: number) => {
deleteShipmentMutation.mutate(id);
}}
entityType={"shipment"}
entity={currentShipment}
/>
</Group>
<ShipmentsFilters
forms={forms || []}

View File

@@ -1,6 +1,6 @@
import { ActionIcon, Group, Loader, ScrollArea, Stack, Table, Title, Tooltip } from "@mantine/core";
import { t } from "@/config/i18n";
import { useCreateUser, useEditUser, useGetUser, useGetUsers } from "@/services/api";
import { useCreateUser, useDeleteUser, useEditUser, useGetUser, useGetUsers } from "@/services/api";
import { IconPlus } from "@tabler/icons-react";
import UserRow from "@/components/Users/Row";
import { useLocation, useNavigate, useSearchParams } from "react-router";
@@ -8,6 +8,7 @@ import { UserModal } from "@/components/Users/Modal";
import { useCallback, useMemo } from "react";
import { type User, type UserInputs } from "@/services/resources/users";
import UsersFilters from "@/components/Users/Filter";
import { DeleteModal } from "@/components/DeleteModal";
export default function Users() {
const [searchParams, setSearchParams] = useSearchParams();
@@ -16,9 +17,10 @@ export default function Users() {
const isCreate = location.pathname === "/dashboard/users/create";
const isEdit = location.pathname.includes("/edit");
const isDelete = location.pathname.includes("/delete");
const editId = useMemo(() => {
if (isEdit) {
if (isEdit || isDelete) {
return location.pathname.split("/")[3];
}
return null;
@@ -36,8 +38,7 @@ export default function Users() {
const { data: allUsers } = useGetUsers();
const names = useMemo(() => {
if (!allUsers)
return [];
if (!allUsers) return [];
return allUsers
?.map((user: User) => user.name)
.filter((season, index, array) => array.indexOf(season) === index);
@@ -45,6 +46,7 @@ export default function Users() {
const createUserMutation = useCreateUser();
const editUserMutation = useEditUser();
const deleteUserMutation = useDeleteUser();
const handleCreateUser = useCallback(
async (user: UserInputs) => {
@@ -117,6 +119,15 @@ export default function Users() {
currentUser={currentUser}
handleSubmit={handleEditUser}
/>
<DeleteModal
opened={isDelete}
onClose={closeModal}
handleSubmit={(id: number) => {
deleteUserMutation.mutate(id);
}}
entityType={"user"}
entity={currentUser}
/>
</Group>
<UsersFilters
names={names || []}

View File

@@ -34,21 +34,26 @@ export const router = createBrowserRouter([
{ path: "productors", Component: Productors },
{ path: "productors/create", Component: Productors },
{ path: "productors/:id/edit", Component: Productors },
{ path: "productors/:id/delete", Component: Productors },
{ path: "products", Component: Products },
{ path: "products/create", Component: Products },
{ path: "products/:id/edit", Component: Products },
{ path: "products/:id/delete", Component: Products },
{ path: "contracts", Component: Contracts },
{ path: "contracts/download", Component: Contracts },
{ path: "contracts/export", Component: Contracts },
{ path: "contracts/:id/delete", Component: Contracts },
{ path: "users", Component: Users },
{ path: "users/create", Component: Users },
{ path: "users/:id/edit", Component: Users },
{ path: "users/:id/delete", Component: Users },
{ path: "forms", Component: Forms },
{ path: "forms/:id/edit", Component: Forms },
{ path: "forms/:id/delete", Component: Forms },
{ path: "forms/create", Component: Forms },
{ path: "shipments", Component: Shipments },
{ path: "shipments/:id/edit", Component: Shipments },
{ path: "shipments/:id/delete", Component: Shipments },
{ path: "shipments/create", Component: Shipments },
],
},

View File

@@ -5,9 +5,9 @@ import type { UserLogged } from "../resources/users";
export type Auth = {
loggedUser: UserLogged | null;
isLoading: boolean;
}
};
const AuthContext = createContext<Auth | undefined>(undefined)
const AuthContext = createContext<Auth | undefined>(undefined);
export function AuthProvider({ children }: { children: React.ReactNode }) {
const { data: loggedUser, isLoading } = useCurrentUser();
@@ -17,11 +17,7 @@ export function AuthProvider({ children }: {children: React.ReactNode}) {
isLoading,
};
return (
<AuthContext.Provider value={value}>
{children}
</AuthContext.Provider>
)
return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>;
}
export function useAuth(): Auth {