diff --git a/backend/src/forms/forms.py b/backend/src/forms/forms.py index f2e68e4..3d69f92 100644 --- a/backend/src/forms/forms.py +++ b/backend/src/forms/forms.py @@ -30,6 +30,31 @@ async def get_forms_filtered( return service.get_all(session, seasons, productors, current_season, user) +@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('forms', 'delete') + ) + try: + result = service.get_delete_dependencies( + session, + _id + ) + print(result) + except exceptions.FormNotFoundError as error: + raise HTTPException(status_code=404, detail=str(error)) from error + return result + + @router.get('/{_id}', response_model=models.FormPublic) async def get_form( _id: int, @@ -68,7 +93,7 @@ async def create_form( @router.put('/{_id}', response_model=models.FormPublic) async def update_form( - _id: int, + _id: int, form: models.FormUpdate, user: models.User = Depends(get_current_user), session: Session = Depends(get_session) diff --git a/backend/src/forms/service.py b/backend/src/forms/service.py index 23b8a1e..d62b832 100644 --- a/backend/src/forms/service.py +++ b/backend/src/forms/service.py @@ -107,6 +107,44 @@ def delete_one(session: Session, _id: int) -> models.FormPublic: return result +def get_delete_dependencies( + session: Session, + _id: int +) -> list[models.DeleteDependency]: + statement = select(models.Form).where(models.Form.id == _id) + result = session.exec(statement) + form = result.first() + if not form: + raise exceptions.FormNotFoundError(messages.Messages.not_found('form')) + print(_id) + statement_shipment = ( + select(models.Shipment) + .where(models.Shipment.form_id == _id) + .distinct() + ) + statement_contracts = ( + select(models.Contract) + .where(models.Contract.form_id == _id) + .distinct() + ) + shipments = session.exec(statement_shipment).all() + contracts = session.exec(statement_contracts).all() + result = [ + models.DeleteDependency( + name=sh.name, + id=sh.id, + type='shipment' + ) for sh in shipments + ] + [ + models.DeleteDependency( + name=f'{co.firstname} {co.lastname}', + id=co.id, + type='contract' + ) for co in contracts + ] + return result + + def is_allowed( session: Session, user: models.User, diff --git a/backend/src/models.py b/backend/src/models.py index c17e8f4..ab60a26 100644 --- a/backend/src/models.py +++ b/backend/src/models.py @@ -5,6 +5,12 @@ from typing import Optional from sqlmodel import Column, Field, LargeBinary, Relationship, SQLModel +class DeleteDependency(SQLModel): + id: int + name: str + type: str + + class ContractType(SQLModel, table=True): id: int | None = Field( default=None, diff --git a/backend/src/shipments/service.py b/backend/src/shipments/service.py index 32792be..c49ef85 100644 --- a/backend/src/shipments/service.py +++ b/backend/src/shipments/service.py @@ -138,11 +138,7 @@ def is_allowed( return False if not _id: statement = ( - select(models.Shipment) - .join( - models.Form, - models.Shipment.form_id == models.Form.id - ) + select(models.Form) .where(models.Form.id == shipment.form_id) ) form = session.exec(statement).first() @@ -162,4 +158,3 @@ def is_allowed( .distinct() ) return len(session.exec(statement).all()) > 0 - diff --git a/frontend/locales/en.json b/frontend/locales/en.json index 2d5c1b9..a9d74bf 100644 --- a/frontend/locales/en.json +++ b/frontend/locales/en.json @@ -84,6 +84,8 @@ "once all contracts downloaded, you can delete the form (to avoid new submissions) and hide it from the home page": "once all contracts downloaded, you can delete the form (to avoid new submissions) and hide it from the home page", "by checking this option the form will be accessible publicly on the home page, only check it if everything is fine with your form": "by checking this option the form will be accessible publicly on the home page, only check it if everything is fine with your form", "contracts": "contracts", + "contract": "contract", + "user": "user", "hidden": "hidden", "visible": "visible", "minimum price for this shipment should be at least": "minimum price for this shipment should be at least", @@ -179,6 +181,10 @@ "grams": "grams (g)", "kilo": "kilograms (kg)", "liter": "liters (L)", + "are you sure you want to delete": "are you sure you want to delete", + "this will also delete": "this will also delete", + "delete entity": "delete {{entity}}", + "delete": "delete", "success": "success", "success edit": "{{entity}} correctly edited", "success create": "{{entity}} correctly created", diff --git a/frontend/locales/fr.json b/frontend/locales/fr.json index 0099a3d..1f8485a 100644 --- a/frontend/locales/fr.json +++ b/frontend/locales/fr.json @@ -81,6 +81,8 @@ "there is": "il y a", "for this contract": "pour ce contrat.", "remove shipment": "supprimer la livraison", + "contract": "contrat", + "user": "utilisateur·trice", "productors": "producteur·trices", "products": "produits", "templates": "modèles", @@ -179,6 +181,10 @@ "grams": "grammes (g)", "kilo": "kilogrammes (kg)", "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": "supprimer", "success": "succès", "success edit": "{{entity}} correctement édité", "success create": "{{entity}} correctement créé", diff --git a/frontend/src/components/DeleteModal/index.tsx b/frontend/src/components/DeleteModal/index.tsx new file mode 100644 index 0000000..cf0c1d7 --- /dev/null +++ b/frontend/src/components/DeleteModal/index.tsx @@ -0,0 +1,76 @@ +import { t } from "@/config/i18n"; +import type { DeleteDependencies } from "@/services/resources/common"; +import { Button, Group, List, Modal, Text, type ModalBaseProps } from "@mantine/core"; +import { IconCancel, IconCheck } from "@tabler/icons-react"; +import { Link } from "react-router"; + +export type DeleteModalProps = ModalBaseProps & { + handleSubmit: (id: number) => void; + entityType: string; + entity: {name: string, id: number}; + dependencies: DeleteDependencies[]; +} + +export function DeleteModal({ + opened, + onClose, + handleSubmit, + entityType, + entity, + dependencies +}: DeleteModalProps) { + return ( + + {`${t("are you sure you want to delete", {capfirst: true})} : "${entity.name}"`} + {`${t("this will also delete", {capfirst: true})} :`} + { + + { + dependencies?.map((dependency) => ( + + { + dependency.type === 'contract' ? `${t(dependency.type, {capfirst: true})} - ${dependency.name}` : + + {`${t(dependency.type, {capfirst: true})} - ${dependency.name}`} + + + } + + )) + } + + } + + + + + + ); +} \ No newline at end of file diff --git a/frontend/src/components/Forms/Row/index.tsx b/frontend/src/components/Forms/Row/index.tsx index 058f7b9..45b9388 100644 --- a/frontend/src/components/Forms/Row/index.tsx +++ b/frontend/src/components/Forms/Row/index.tsx @@ -1,9 +1,11 @@ import { ActionIcon, Badge, Table, Tooltip } from "@mantine/core"; import { useNavigate, useSearchParams } from "react-router"; -import { useDeleteForm } from "@/services/api"; +import { useDeleteForm, useGetDeleteDependencies } from "@/services/api"; import { IconEdit, IconX } from "@tabler/icons-react"; import { t } from "@/config/i18n"; import type { Form } from "@/services/resources/forms"; +import { DeleteModal } from "@/components/DeleteModal"; +import { useDisclosure } from "@mantine/hooks"; export type FormRowProps = { form: Form; @@ -13,7 +15,8 @@ export default function FormRow({ form }: FormRowProps) { const [searchParams] = useSearchParams(); const deleteMutation = useDeleteForm(); const navigate = useNavigate(); - + const [deleteOpened, { open: deleteOpen, close: deleteClose }] = useDisclosure(false); + const {data: deleteDependencies} = useGetDeleteDependencies("form", form.id); return ( @@ -29,7 +32,7 @@ export default function FormRow({ form }: FormRowProps) { {form.productor.name} {form.referer.name} - + - + { + deleteMutation.mutate(id); + }} + entityType={"form"} + entity={form} + dependencies={deleteDependencies || []} + /> + { - deleteMutation.mutate(form.id); - }} + onClick={deleteOpen} > diff --git a/frontend/src/pages/Forms/index.tsx b/frontend/src/pages/Forms/index.tsx index 3897793..358dcb2 100644 --- a/frontend/src/pages/Forms/index.tsx +++ b/frontend/src/pages/Forms/index.tsx @@ -36,12 +36,14 @@ export function Forms() { const { data: allForms } = useGetReferentForms(); const seasons = useMemo(() => { + if (!allForms) return []; return allForms ?.map((form: Form) => form.season) .filter((season, index, array) => array.indexOf(season) === index); }, [allForms]); const productors = useMemo(() => { + if (!allForms) return []; return allForms ?.map((form: Form) => form.productor.name) .filter((productor, index, array) => array.indexOf(productor) === index); diff --git a/frontend/src/pages/Shipments/index.tsx b/frontend/src/pages/Shipments/index.tsx index 61df4f0..d803447 100644 --- a/frontend/src/pages/Shipments/index.tsx +++ b/frontend/src/pages/Shipments/index.tsx @@ -151,7 +151,7 @@ export default function Shipments() { - {shipments.map((shipment) => ( + {shipments?.map((shipment) => ( ))} diff --git a/frontend/src/services/api.ts b/frontend/src/services/api.ts index 6d9ad49..ba58865 100644 --- a/frontend/src/services/api.ts +++ b/frontend/src/services/api.ts @@ -24,6 +24,7 @@ import type { Product, ProductCreate, ProductEditPayload } from "./resources/pro import type { Contract, ContractCreate } from "./resources/contracts"; import { notifications } from "@mantine/notifications"; import { t } from "@/config/i18n"; +import type { DeleteDependencies, EntityName } from "./resources/common"; export async function refreshToken() { return await fetch(`${Config.backend_uri}/auth/refresh`, {method: "POST", credentials: "include"}); @@ -321,6 +322,25 @@ export function useGetForm( }); } +export function useGetDeleteDependencies( + entity: EntityName, + id?: number, +) { + return useQuery({ + queryKey: [`${entity}_delete_preview`], + queryFn: () => + fetchWithAuth(`${Config.backend_uri}/${entity}s/${id}/preview-delete`, { + credentials: "include", + }).then((res) => { + const result = res.json() + console.log(result) + return result + }), + enabled: !!id, + }); +} + + export function useGetForms(filters?: URLSearchParams): UseQueryResult { const queryString = filters?.toString(); return useQuery({ diff --git a/frontend/src/services/resources/common.ts b/frontend/src/services/resources/common.ts new file mode 100644 index 0000000..dbb7314 --- /dev/null +++ b/frontend/src/services/resources/common.ts @@ -0,0 +1,16 @@ +export const ENTITY_NAMES = [ + 'contract', + 'form', + 'productor', + 'product', + 'shipment', + 'user', +] + +export type EntityName = (typeof ENTITY_NAMES)[number]; + +export type DeleteDependencies = { + name: string; + id: number; + type: EntityName; +} \ No newline at end of file