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 ### Show on cascade deletion
## Update contract after (without registration) ## 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') @router.get('/{form_id}/files')
def get_contract_files( def get_contract_files(
form_id: int, form_id: int,

View File

@@ -18,6 +18,30 @@ def get_productors(
return service.get_all(session, user, names, types) 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) @router.get('/{_id}', response_model=models.ProductorPublic)
def get_productor( def get_productor(
_id: int, _id: int,

View File

@@ -93,6 +93,33 @@ def delete_one(session: Session, _id: int) -> models.ProductorPublic:
session.commit() session.commit()
return result 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( def is_allowed(
session: Session, session: Session,
user: models.User, 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) @router.get('/{_id}', response_model=models.ProductPublic)
def get_product( def get_product(
_id: int, _id: int,

View File

@@ -93,6 +93,7 @@ def delete_one(
session.commit() session.commit()
return result return result
def is_allowed( def is_allowed(
session: Session, session: Session,
user: models.User, user: models.User,
@@ -103,12 +104,8 @@ def is_allowed(
return False return False
if not _id: if not _id:
statement = ( statement = (
select(models.Product) select(models.Productor)
.join( .where(models.Productor.id == product.productor_id)
models.Productor,
models.Product.productor_id == models.Productor.id
)
.where(models.Product.id == product.productor_id)
) )
productor = session.exec(statement).first() productor = session.exec(statement).first()
return productor.type in [r.name for r in user.roles] 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) @router.get('/{_id}', response_model=models.ShipmentPublic)
def get_shipment( def get_shipment(
_id: int, _id: int,

View File

@@ -36,6 +36,22 @@ def get_roles(
return service.get_roles(session) 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) @router.get('/{_id}', response_model=models.UserPublic)
def get_user( def get_user(
_id: int, _id: int,

View File

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

View File

@@ -39,6 +39,10 @@
"create productor": "créer le/la producteur·trice", "create productor": "créer le/la producteur·trice",
"edit productor": "modifier le/la producteur·trice", "edit productor": "modifier le/la producteur·trice",
"remove productor": "supprimer 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", "home": "accueil",
"dashboard": "tableau de bord", "dashboard": "tableau de bord",
"filter by name": "filtrer par nom", "filter by name": "filtrer par nom",
@@ -183,7 +187,7 @@
"liter": "litres (L)", "liter": "litres (L)",
"are you sure you want to delete": "êtes vous sûr de vouloir supprimer", "are you sure you want to delete": "êtes vous sûr de vouloir supprimer",
"this will also delete": "cette action supprimera aussi", "this will also delete": "cette action supprimera aussi",
"delete entity": "supprimer un {{entity}}", "delete entity": "supprimer le/la {{entity}}",
"delete": "supprimer", "delete": "supprimer",
"success": "succès", "success": "succès",
"success edit": "{{entity}} correctement édité", "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 { type Contract } from "@/services/resources/contracts";
import { IconDownload, IconX } from "@tabler/icons-react"; import { IconDownload, IconX } from "@tabler/icons-react";
import { t } from "@/config/i18n"; import { t } from "@/config/i18n";
import { useDeleteContract, useGetContractFile } from "@/services/api"; import { useGetContractFile } from "@/services/api";
import { useCallback } from "react"; import { useCallback } from "react";
import { useNavigate } from "react-router";
export type ContractRowProps = { export type ContractRowProps = {
contract: Contract; contract: Contract;
}; };
export default function ContractRow({ contract }: ContractRowProps) { export default function ContractRow({ contract }: ContractRowProps) {
const deleteMutation = useDeleteContract();
const getContractMutation = useGetContractFile(); const getContractMutation = useGetContractFile();
const navigate = useNavigate();
const handleDownload = useCallback(async () => { const handleDownload = useCallback(async () => {
getContractMutation.mutateAsync(contract.id); getContractMutation.mutateAsync(contract.id);
@@ -29,12 +30,10 @@ export default function ContractRow({ contract }: ContractRowProps) {
{contract.cheque_quantity > 0 && contract.cheque_quantity} {contract.payment_method} {contract.cheque_quantity > 0 && contract.cheque_quantity} {contract.payment_method}
</Table.Td> </Table.Td>
<Table.Td> <Table.Td>
{ {`${Intl.NumberFormat("fr-FR", {
`${Intl.NumberFormat("fr-FR", { style: "currency",
style: "currency", currency: "EUR",
currency: "EUR", }).format(contract.total_price)}`}
}).format(contract.total_price)}`
}
</Table.Td> </Table.Td>
<Table.Td> <Table.Td>
<Tooltip label={t("download contract", { capfirst: true })}> <Tooltip label={t("download contract", { capfirst: true })}>
@@ -54,8 +53,9 @@ export default function ContractRow({ contract }: ContractRowProps) {
color="red" color="red"
size="sm" size="sm"
mr="5" mr="5"
onClick={() => { onClick={(e) => {
deleteMutation.mutate(contract.id); e.stopPropagation();
navigate(`/dashboard/contracts/${contract.id}/delete`);
}} }}
> >
<IconX /> <IconX />

View File

@@ -7,50 +7,48 @@ import { Link } from "react-router";
export type DeleteModalProps = ModalBaseProps & { export type DeleteModalProps = ModalBaseProps & {
handleSubmit: (id: number) => void; handleSubmit: (id: number) => void;
entityType: string; entityType: string;
entity?: {name: string, id: number} | undefined; entity?: { name: string; id: number } | undefined;
} };
export function DeleteModal({ export function DeleteModal({
opened, opened,
onClose, onClose,
handleSubmit, handleSubmit,
entityType, entityType,
entity, entity,
}: DeleteModalProps) { }: DeleteModalProps) {
if (!entity) { if (!entity) {
return null return null;
} }
const {data: deleteDependencies} = useGetDeleteDependencies(entityType, entity.id); const { data: deleteDependencies } = useGetDeleteDependencies(entityType, entity.id);
return ( return (
<Modal <Modal
opened={opened} opened={opened}
onClose={onClose} onClose={onClose}
title={t("delete entity", { capfirst: true, entity: t(entityType)})} title={t("delete entity", { capfirst: true, entity: t(entityType) })}
> >
<Text>{`${t("are you sure you want to delete", {capfirst: true})} : "${entity.name}"`}</Text> <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> <List>
{ {deleteDependencies?.map((dependency) => (
deleteDependencies?.map((dependency) => ( <List.Item key={dependency.id}>
<List.Item {dependency.type === "contract" ? (
key={dependency.id} `${t(dependency.type, { capfirst: true })} - ${dependency.name}`
> ) : (
{ <Link
dependency.type === 'contract' ? `${t(dependency.type, {capfirst: true})} - ${dependency.name}` : to={`/dashboard/${dependency.type}s/${dependency.id}/edit`}
<Link target="_blank"
to={`/dashboard/${dependency.type}s/${dependency.id}/edit`} rel="noopener noreferrer"
target="_blank" >
rel="noopener noreferrer" {`${t(dependency.type, { capfirst: true })} - ${dependency.name}`}
> </Link>
{`${t(dependency.type, {capfirst: true})} - ${dependency.name}`} )}
</Link> </List.Item>
))}
}
</List.Item>
))
}
</List> </List>
} }
<Group mt="sm" justify="space-between"> <Group mt="sm" justify="space-between">
@@ -65,16 +63,16 @@ export function DeleteModal({
</Button> </Button>
<Button <Button
variant="filled" variant="filled"
aria-label={t("delete entity", { capfirst: true, entity: t(entityType)})} aria-label={t("delete entity", { capfirst: true, entity: t(entityType) })}
leftSection={<IconCheck />} leftSection={<IconCheck />}
onClick={() => { onClick={() => {
handleSubmit(entity.id); handleSubmit(entity.id);
onClose(); onClose();
}} }}
> >
{t("delete", { capfirst: true})} {t("delete", { capfirst: true })}
</Button> </Button>
</Group> </Group>
</Modal> </Modal>
); );
} }

View File

@@ -10,37 +10,33 @@ 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")}>
label={t("download base template to print")} <ActionIcon
> variant={"outline"}
<ActionIcon aria-label={t("download base template to print")}
variant={"outline"} onClick={async () => {
aria-label={t("download base template to print")} await contractBaseTemplate.mutateAsync(form.id);
onClick={async () => { }}
await contractBaseTemplate.mutateAsync(form.id) >
}} <IconDownload />
> </ActionIcon>
<IconDownload/> </Tooltip>
</ActionIcon> <Tooltip label={t("fill contract online")}>
</Tooltip> <ActionIcon
<Tooltip variant={"outline"}
label={t("fill contract online")} aria-label={t("fill contract online")}
> component={Link}
<ActionIcon to={`/form/${form.id}`}
variant={"outline"} target="_blank"
aria-label={t("fill contract online")} rel="noopener noreferrer"
component={Link} >
to={`/form/${form.id}`} <IconExternalLink />
target="_blank" </ActionIcon>
rel="noopener noreferrer" </Tooltip>
>
<IconExternalLink/>
</ActionIcon>
</Tooltip>
</Group> </Group>
<Box <Box

View File

@@ -34,7 +34,7 @@ export default function FormModal({ opened, onClose, currentForm, handleSubmit }
productor_id: currentForm?.productor?.id.toString() ?? "", productor_id: currentForm?.productor?.id.toString() ?? "",
referer_id: currentForm?.referer?.id.toString() ?? "", referer_id: currentForm?.referer?.id.toString() ?? "",
minimum_shipment_value: currentForm?.minimum_shipment_value ?? null, minimum_shipment_value: currentForm?.minimum_shipment_value ?? null,
visible: currentForm?.visible ?? false visible: currentForm?.visible ?? false,
}, },
validate: { validate: {
name: (value) => name: (value) =>
@@ -53,8 +53,7 @@ export default function FormModal({ opened, onClose, currentForm, handleSubmit }
}); });
const usersSelect = useMemo(() => { const usersSelect = useMemo(() => {
if (!users) if (!users) return [];
return [];
return users?.map((user) => ({ return users?.map((user) => ({
value: String(user.id), value: String(user.id),
label: `${user.name}`, label: `${user.name}`,
@@ -62,8 +61,7 @@ export default function FormModal({ opened, onClose, currentForm, handleSubmit }
}, [users]); }, [users]);
const productorsSelect = useMemo(() => { const productorsSelect = useMemo(() => {
if (!productors) if (!productors) return [];
return [];
return productors?.map((prod) => ({ return productors?.map((prod) => ({
value: String(prod.id), value: String(prod.id),
label: `${prod.name}`, label: `${prod.name}`,
@@ -142,10 +140,14 @@ export default function FormModal({ opened, onClose, currentForm, handleSubmit }
radius="sm" radius="sm"
{...form.getInputProps("minimum_shipment_value")} {...form.getInputProps("minimum_shipment_value")}
/> />
<Checkbox mt="lg" <Checkbox
label={t("visible", {capfirst: true})} mt="lg"
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})} label={t("visible", { capfirst: true })}
{...form.getInputProps("visible", {type: "checkbox"})} 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"> <Group mt="sm" justify="space-between">
<Button <Button

View File

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

View File

@@ -23,11 +23,11 @@ export function Navbar() {
<nav> <nav>
<Group> <Group>
<NavLink className={"navLink"} aria-label={t("home")} to="/"> <NavLink className={"navLink"} aria-label={t("home")} to="/">
{isPhone ? <IconHome/> : t("home", { capfirst: true })} {isPhone ? <IconHome /> : t("home", { capfirst: true })}
</NavLink> </NavLink>
{user?.logged ? ( {user?.logged ? (
<NavLink className={"navLink"} aria-label={t("dashboard")} to="/dashboard/help"> <NavLink className={"navLink"} aria-label={t("dashboard")} to="/dashboard/help">
{isPhone ? <IconSettings/> : t("dashboard", { capfirst: true })} {isPhone ? <IconSettings /> : t("dashboard", { capfirst: true })}
</NavLink> </NavLink>
) : null} ) : null}
</Group> </Group>
@@ -37,7 +37,7 @@ export function Navbar() {
className={"navLink"} className={"navLink"}
aria-label={t("login with keycloak", { capfirst: true })} aria-label={t("login with keycloak", { capfirst: true })}
> >
{isPhone ? <IconLogin/> : t("login with keycloak", { capfirst: true })} {isPhone ? <IconLogin /> : t("login with keycloak", { capfirst: true })}
</a> </a>
) : ( ) : (
<a <a
@@ -45,7 +45,7 @@ export function Navbar() {
className={"navLink"} className={"navLink"}
aria-label={t("logout", { capfirst: true })} aria-label={t("logout", { capfirst: true })}
> >
{isPhone ? <IconLogout/> : t("logout", { capfirst: true })} {isPhone ? <IconLogout /> : t("logout", { capfirst: true })}
</a> </a>
)} )}
</nav> </nav>

View File

@@ -1,88 +1,86 @@
import { t } from "@/config/i18n"; import { t } from "@/config/i18n";
import type { ContractInputs } from "@/services/resources/contracts"; import type { ContractInputs } from "@/services/resources/contracts";
import type { Productor } from "@/services/resources/productors"; import type { Productor } from "@/services/resources/productors";
import { Group, NumberInput, Stack, TextInput, Title } from "@mantine/core"; import { Group, NumberInput, Stack, TextInput, Title } from "@mantine/core";
import type { UseFormReturnType } from "@mantine/form"; import type { UseFormReturnType } from "@mantine/form";
import { useEffect, useMemo } from "react"; import { useEffect, useMemo } from "react";
export type ContractChequeProps = { export type ContractChequeProps = {
inputForm: UseFormReturnType<ContractInputs>; inputForm: UseFormReturnType<ContractInputs>;
price: number; price: number;
productor: Productor; productor: Productor;
}; };
export type Cheque = { export type Cheque = {
name: string; name: string;
value: string; value: string;
}; };
export function ContractCheque({ inputForm, price, productor }: ContractChequeProps) { export function ContractCheque({ inputForm, price, productor }: ContractChequeProps) {
useEffect(() => { useEffect(() => {
if (!inputForm.values.payment_method.includes("cheque")) { if (!inputForm.values.payment_method.includes("cheque")) {
return; return;
} }
const quantity = Number(inputForm.values.cheque_quantity); const quantity = Number(inputForm.values.cheque_quantity);
if (!quantity || quantity <= 0) return; if (!quantity || quantity <= 0) return;
const cheques = inputForm.values.cheques || []; const cheques = inputForm.values.cheques || [];
if (cheques.length !== quantity) { if (cheques.length !== quantity) {
const newCheques = Array.from({ length: quantity }, (_, i) => ({ const newCheques = Array.from({ length: quantity }, (_, i) => ({
name: cheques[i]?.name ?? "", name: cheques[i]?.name ?? "",
value: cheques[i]?.value ?? 0, value: cheques[i]?.value ?? 0,
})); }));
inputForm.setFieldValue("cheques", newCheques); inputForm.setFieldValue("cheques", newCheques);
} }
const totalCents = Math.round(price * 100); const totalCents = Math.round(price * 100);
const base = Math.floor(totalCents / quantity); const base = Math.floor(totalCents / quantity);
const rest = totalCents - base * quantity; const rest = totalCents - base * quantity;
for (let i = 0; i < quantity; i++) { for (let i = 0; i < quantity; i++) {
const val = (i === quantity - 1 ? base + rest : base) / 100; const val = (i === quantity - 1 ? base + rest : base) / 100;
inputForm.setFieldValue(`cheques.${i}.value`, val.toFixed(2)); inputForm.setFieldValue(`cheques.${i}.value`, val.toFixed(2));
} }
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
}, [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")} : ${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={1} min={1}
max={paymentMethod?.max && paymentMethod?.max !== "" ? Number(paymentMethod?.max) : 3} max={
{...inputForm.getInputProps(`cheque_quantity`)} paymentMethod?.max && paymentMethod?.max !== "" ? Number(paymentMethod?.max) : 3
/> }
<Group grow> {...inputForm.getInputProps(`cheque_quantity`)}
{inputForm.values.cheques.map((cheque, index) => ( />
<Stack key={`${index}`}> <Group grow>
<TextInput {inputForm.values.cheques.map((cheque, index) => (
label={t("cheque id", { capfirst: true })} <Stack key={`${index}`}>
placeholder={t("cheque id", { capfirst: true })} <TextInput
{...inputForm.getInputProps(`cheques.${index}.name`)} label={t("cheque id", { capfirst: true })}
error={ placeholder={t("cheque id", { capfirst: true })}
cheque.name == "" ? {...inputForm.getInputProps(`cheques.${index}.name`)}
inputForm?.errors.cheques : error={cheque.name == "" ? inputForm?.errors.cheques : null}
null />
} <NumberInput
/> readOnly
<NumberInput label={t("cheque value", { capfirst: true })}
readOnly suffix={"€"}
label={t("cheque value", { capfirst: true })} placeholder={t("enter cheque value", { capfirst: true })}
suffix={"€"} {...inputForm.getInputProps(`cheques.${index}.value`)}
placeholder={t("enter cheque value", { capfirst: true })} />
{...inputForm.getInputProps(`cheques.${index}.value`)} </Stack>
/> ))}
</Stack> </Group>
))} </Stack>
</Group> );
</Stack> }
);
}

View File

@@ -48,21 +48,31 @@ export function ProductorModal({
!value ? `${t("address", { capfirst: true })} ${t("is required")}` : null, !value ? `${t("address", { capfirst: true })} ${t("is required")}` : null,
type: (value) => type: (value) =>
!value ? `${t("type", { capfirst: true })} ${t("is required")}` : null, !value ? `${t("type", { capfirst: true })} ${t("is required")}` : null,
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 (
<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> <Title order={4}>{t("Informations", { capfirst: true })}</Title>
<TextInput <TextInput
label={t("productor name", { capfirst: true })} label={t("productor name", { capfirst: true })}
@@ -127,7 +137,7 @@ export function ProductorModal({
<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

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -36,13 +36,20 @@ export function UserModal({ opened, onClose, currentUser, handleSubmit }: UserMo
}); });
const roleSelect = useMemo(() => { const roleSelect = useMemo(() => {
if (!allRoles) if (!allRoles) return [];
return [];
return allRoles?.map((role) => ({ value: String(role.name), label: role.name })); return allRoles?.map((role) => ({ value: String(role.name), label: role.name }));
}, [allRoles]); }, [allRoles]);
return ( 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> <Title order={4}>{t("informations", { capfirst: true })}</Title>
<TextInput <TextInput
label={t("user name", { capfirst: true })} 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 { t } from "@/config/i18n";
import { IconEdit, IconX } from "@tabler/icons-react"; import { IconEdit, IconX } from "@tabler/icons-react";
import { type User } from "@/services/resources/users"; import { type User } from "@/services/resources/users";
import { useDeleteUser } from "@/services/api";
import { useNavigate, useSearchParams } from "react-router"; import { useNavigate, useSearchParams } from "react-router";
export type UserRowProps = { export type UserRowProps = {
@@ -11,36 +10,31 @@ export type UserRowProps = {
export default function UserRow({ user }: UserRowProps) { export default function UserRow({ user }: UserRowProps) {
const [searchParams] = useSearchParams(); const [searchParams] = useSearchParams();
const deleteMutation = useDeleteUser();
const navigate = useNavigate(); const navigate = useNavigate();
return ( return (
<Table.Tr key={user.id}> <Table.Tr key={user.id}>
<Table.Td>{user.name}</Table.Td> <Table.Td>{user.name}</Table.Td>
<Table.Td>{user.email}</Table.Td> <Table.Td>{user.email}</Table.Td>
<Table.Td style={{maxWidth: 200}}> <Table.Td style={{ maxWidth: 200 }}>
<Box <Box
style={{ style={{
display: 'flex', display: "flex",
gap: 4 gap: 4,
}} }}
> >
{user.roles.slice(0, 3).map((value) => ( {user.roles.slice(0, 3).map((value) => (
<Badge key={value.id} size="xs"> <Badge key={value.id} size="xs">
{t(value.name, { capfirst: true })} {t(value.name, { capfirst: true })}
</Badge> </Badge>
))} ))}
{ {user.roles.length > 3 && (
user.roles.length > 3 && ( <Tooltip label={user.roles.slice(3).map((role) => `${role.name} `)}>
<Tooltip
label={user.roles.slice(3).map(role=>`${role.name} `)}
>
<Badge size="xs" variant="light"> <Badge size="xs" variant="light">
+{user.roles.length - 3} +{user.roles.length - 3}
</Badge> </Badge>
</Tooltip> </Tooltip>
) )}
}
</Box> </Box>
</Table.Td> </Table.Td>
<Table.Td> <Table.Td>
@@ -63,8 +57,11 @@ export default function UserRow({ user }: UserRowProps) {
color="red" color="red"
size="sm" size="sm"
mr="5" mr="5"
onClick={() => { onClick={(e) => {
deleteMutation.mutate(user.id); e.stopPropagation();
navigate(
`/dashboard/users/${user.id}/delete${searchParams ? `?${searchParams.toString()}` : ""}`,
);
}} }}
> >
<IconX /> <IconX />

View File

@@ -50,8 +50,10 @@ export function Contract() {
!value ? `${t("a phone", { capfirst: true })} ${t("is required")}` : null, !value ? `${t("a phone", { capfirst: true })} ${t("is required")}` : null,
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,
}, },
}); });
@@ -137,12 +139,13 @@ 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); await createContractMutation.mutateAsync(contract);
window.location.href = '/'; window.location.href = "/";
} 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];
@@ -165,7 +168,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">
@@ -283,17 +286,13 @@ export function Contract() {
ref={(el) => { ref={(el) => {
inputRefs.current.payment_method = el; inputRefs.current.payment_method = el;
}} }}
comboboxProps={{ comboboxProps={{
withinPortal: false, withinPortal: false,
position: "bottom-start", position: "bottom-start",
}} }}
/> />
{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>
@@ -320,10 +319,12 @@ export function Contract() {
currency: "EUR", currency: "EUR",
}).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", { capfirst: true })}
</Button> </Button>
</Overlay> </Overlay>
</Stack> </Stack>

View File

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

View File

@@ -6,7 +6,7 @@ import { useAuth } from "@/services/auth/AuthProvider";
export default function Dashboard() { export default function Dashboard() {
const navigate = useNavigate(); const navigate = useNavigate();
const location = useLocation(); const location = useLocation();
const {loggedUser} = useAuth(); const { loggedUser } = useAuth();
return ( return (
<Tabs <Tabs
w={{ base: "100%", md: "80%", lg: "60%" }} w={{ base: "100%", md: "80%", lg: "60%" }}
@@ -16,17 +16,50 @@ export default function Dashboard() {
onChange={(value) => navigate(`/dashboard/${value}`)} onChange={(value) => navigate(`/dashboard/${value}`)}
> >
<Tabs.List mb="md"> <Tabs.List mb="md">
<Tabs.Tab renderRoot={(props) => (<Link to="/dashboard/help" {...props}></Link>)} value="help">{t("help", { capfirst: true })}</Tabs.Tab> <Tabs.Tab
<Tabs.Tab renderRoot={(props) => (<Link to="/dashboard/productors" {...props}></Link>)} value="productors">{t("productors", { capfirst: true })}</Tabs.Tab> renderRoot={(props) => <Link to="/dashboard/help" {...props}></Link>}
<Tabs.Tab renderRoot={(props) => (<Link to="/dashboard/products" {...props}></Link>)} value="products">{t("products", { capfirst: true })}</Tabs.Tab> value="help"
<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> {t("help", { capfirst: true })}
<Tabs.Tab renderRoot={(props) => (<Link to="/dashboard/contracts" {...props}></Link>)} value="contracts">{t("contracts", { capfirst: true })}</Tabs.Tab> </Tabs.Tab>
{ <Tabs.Tab
loggedUser?.user?.roles && loggedUser?.user?.roles?.length > 5 ? renderRoot={(props) => <Link to="/dashboard/productors" {...props}></Link>}
<Tabs.Tab renderRoot={(props) => (<Link to="/dashboard/users" {...props}></Link>)} value="users">{t("users", { capfirst: true })}</Tabs.Tab> : value="productors"
null >
} {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> </Tabs.List>
<Outlet /> <Outlet />
</Tabs> </Tabs>

View File

@@ -1,5 +1,11 @@
import { Stack, Loader, Title, Group, ActionIcon, Tooltip, Table, ScrollArea } from "@mantine/core"; 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 { t } from "@/config/i18n";
import { useLocation, useNavigate, useSearchParams } from "react-router"; import { useLocation, useNavigate, useSearchParams } from "react-router";
import { IconPlus } from "@tabler/icons-react"; import { IconPlus } from "@tabler/icons-react";
@@ -25,7 +31,7 @@ export function Forms() {
} }
return null; return null;
}, [location, isEdit]); }, [location, isEdit]);
const closeModal = useCallback(() => { const closeModal = useCallback(() => {
navigate(`/dashboard/forms${searchParams ? `?${searchParams.toString()}` : ""}`); navigate(`/dashboard/forms${searchParams ? `?${searchParams.toString()}` : ""}`);
}, [navigate, searchParams]); }, [navigate, searchParams]);

View File

@@ -242,10 +242,13 @@ export function Help() {
</Text> </Text>
</Blockquote> </Blockquote>
</Stack> </Stack>
<Title order={3}>{t("export contracts", {capfirst: true})}</Title> <Title order={3}>{t("export contracts", { capfirst: true })}</Title>
<Stack> <Stack>
<Text> <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 <ActionIcon
ml="4" ml="4"
size="xs" size="xs"
@@ -260,21 +263,32 @@ export function Help() {
<IconLink /> <IconLink />
</ActionIcon> </ActionIcon>
</Text> </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> <Text>
{t("you can download all contracts for your form using the export all", {capfirst: true})}{" "} {t(
<ActionIcon size="sm"> "in this page you can view all contracts submissions, you can remove duplicates submission or download a specific contract",
<IconDownload/> { capfirst: true },
</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})}{" "}
<ActionIcon size="sm">
<IconTableExport/>
</ActionIcon>{" "}
</Text> </Text>
<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("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,
})}{" "}
<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 },
)}
</Text> </Text>
</Stack> </Stack>
<Title order={3}>{t("glossary", { capfirst: true })}</Title> <Title order={3}>{t("glossary", { capfirst: true })}</Title>

View File

@@ -14,21 +14,23 @@ export function Home() {
useEffect(() => { useEffect(() => {
if (searchParams.get("sessionExpired")) { if (searchParams.get("sessionExpired")) {
showNotification({ showNotification({
title: t("session expired", {capfirst: true}), title: t("session expired", { capfirst: true }),
message: t("your session has expired please log in again", {capfirst: true}), message: t("your session has expired please log in again", { capfirst: true }),
color: "red", color: "red",
autoClose: 5000, autoClose: 5000,
}); });
} }
if (searchParams.get("userNotAllowed")) { if (searchParams.get("userNotAllowed")) {
showNotification({ showNotification({
title: t("user not allowed", {capfirst: true}), 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", color: "red",
autoClose: 5000, autoClose: 5000,
}); });
} }
}, [searchParams]) }, [searchParams]);
return ( return (
<Stack mt="lg"> <Stack mt="lg">

View File

@@ -1,22 +1,22 @@
import { Loader } from "@mantine/core"; import { Loader } from "@mantine/core";
import { useEffect } from "react"; import { useEffect } from "react";
import { useSearchParams } from "react-router"; import { useSearchParams } from "react-router";
export function Login() { export function Login() {
const [searchParams] = useSearchParams(); const [searchParams] = useSearchParams();
useEffect(() => { useEffect(() => {
const accessToken = searchParams.get("access_token"); const accessToken = searchParams.get("access_token");
const idToken = searchParams.get("id_token"); const idToken = searchParams.get("id_token");
const refreshToken = searchParams.get("refresh_token"); const refreshToken = searchParams.get("refresh_token");
if (accessToken && idToken) { if (accessToken && idToken) {
localStorage.setItem("access_token", accessToken); localStorage.setItem("access_token", accessToken);
localStorage.setItem("id_token", idToken); localStorage.setItem("id_token", idToken);
localStorage.setItem("refresh_token", refreshToken || ""); localStorage.setItem("refresh_token", refreshToken || "");
window.location.href = "/"; window.location.href = "/";
} }
}, [searchParams]); }, [searchParams]);
return <Loader />; return <Loader />;
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -5,23 +5,19 @@ import type { UserLogged } from "../resources/users";
export type Auth = { export type Auth = {
loggedUser: UserLogged | null; loggedUser: UserLogged | null;
isLoading: boolean; isLoading: boolean;
} };
const AuthContext = createContext<Auth | undefined>(undefined) const AuthContext = createContext<Auth | undefined>(undefined);
export function AuthProvider({ children }: {children: React.ReactNode}) { export function AuthProvider({ children }: { children: React.ReactNode }) {
const {data: loggedUser, isLoading} = useCurrentUser(); const { data: loggedUser, isLoading } = useCurrentUser();
const value: Auth = { const value: Auth = {
loggedUser: loggedUser ?? null, loggedUser: loggedUser ?? null,
isLoading, isLoading,
}; };
return ( return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>;
<AuthContext.Provider value={value}>
{children}
</AuthContext.Provider>
)
} }
export function useAuth(): Auth { export function useAuth(): Auth {
@@ -30,4 +26,4 @@ export function useAuth(): Auth {
throw new Error("useAuth must be used inside AuthProvider"); throw new Error("useAuth must be used inside AuthProvider");
} }
return context; return context;
} }

View File

@@ -1,20 +1,20 @@
import { Group, Loader } from "@mantine/core"; import { Group, Loader } from "@mantine/core";
import { Navigate, Outlet } from "react-router"; import { Navigate, Outlet } from "react-router";
import { useAuth } from "../AuthProvider"; import { useAuth } from "../AuthProvider";
export function ProtectedRoute() { export function ProtectedRoute() {
const { loggedUser, isLoading } = useAuth(); const { loggedUser, isLoading } = useAuth();
if (!loggedUser && isLoading) if (!loggedUser && isLoading)
return ( return (
<Group align="center" justify="center" h="80vh" w="100%"> <Group align="center" justify="center" h="80vh" w="100%">
<Loader color="pink" /> <Loader color="pink" />
</Group> </Group>
); );
if (!loggedUser?.logged) { if (!loggedUser?.logged) {
return <Navigate to="/" replace />; return <Navigate to="/" replace />;
} }
return <Outlet />; return <Outlet />;
} }