diff --git a/backend/src/forms/forms.py b/backend/src/forms/forms.py index f6cc366..38ffaf8 100644 --- a/backend/src/forms/forms.py +++ b/backend/src/forms/forms.py @@ -16,7 +16,7 @@ async def get_forms( return service.get_all(session, seasons, productors) @router.get('/{id}', response_model=models.FormPublic) -async def get_forms(id: int, session: Session = Depends(get_session)): +async def get_form(id: int, session: Session = Depends(get_session)): result = service.get_one(session, id) if result is None: raise HTTPException(status_code=404, detail=messages.notfound) diff --git a/backend/src/models.py b/backend/src/models.py index 6246bdc..cef2eb4 100644 --- a/backend/src/models.py +++ b/backend/src/models.py @@ -24,6 +24,7 @@ class ProductorBase(SQLModel): name: str address: str payment: str + type: str class ProductorPublic(ProductorBase): id: int @@ -38,6 +39,7 @@ class ProductorUpdate(SQLModel): name: str | None address: str | None payment: str | None + type: str | None class ProductorCreate(ProductorBase): pass diff --git a/backend/src/productors/productors.py b/backend/src/productors/productors.py index a4be472..7e081e1 100644 --- a/backend/src/productors/productors.py +++ b/backend/src/productors/productors.py @@ -1,4 +1,4 @@ -from fastapi import APIRouter, HTTPException, Depends +from fastapi import APIRouter, HTTPException, Depends, Query import src.messages as messages import src.models as models from src.database import get_session @@ -8,11 +8,15 @@ import src.productors.service as service router = APIRouter(prefix='/productors') @router.get('/', response_model=list[models.ProductorPublic]) -def get_productors(session: Session = Depends(get_session)): - return service.get_all(session) +def get_productors( + names: list[str] = Query([]), + types: list[str] = Query([]), + session: Session = Depends(get_session) +): + return service.get_all(session, names, types) @router.get('/{id}', response_model=models.ProductorPublic) -def get_productors(id: int, session: Session = Depends(get_session)): +def get_productor(id: int, session: Session = Depends(get_session)): result = service.get_one(session, id) if result is None: raise HTTPException(status_code=404, detail=messages.notfound) diff --git a/backend/src/productors/service.py b/backend/src/productors/service.py index f753f2c..85604a8 100644 --- a/backend/src/productors/service.py +++ b/backend/src/productors/service.py @@ -1,8 +1,12 @@ from sqlmodel import Session, select import src.models as models -def get_all(session: Session) -> list[models.ProductorPublic]: +def get_all(session: Session, names: list[str], types: list[str]) -> list[models.ProductorPublic]: statement = select(models.Productor) + if len(names) > 0: + statement.where(models.Productor.name.in_(names)) + if len(types) > 0: + statement.where(models.Productor.type.in_(types)) return session.exec(statement).all() def get_one(session: Session, productor_id: int) -> models.ProductorPublic: diff --git a/backend/src/templates/templates.py b/backend/src/templates/templates.py index c0305df..0ede798 100644 --- a/backend/src/templates/templates.py +++ b/backend/src/templates/templates.py @@ -12,7 +12,7 @@ def get_templates(session: Session = Depends(get_session)): return service.get_all(session) @router.get('/{id}', response_model=models.TemplatePublic) -def get_templates(id: int, session: Session = Depends(get_session)): +def get_template(id: int, session: Session = Depends(get_session)): result = service.get_one(session, id) if result is None: raise HTTPException(status_code=404, detail=messages.notfound) diff --git a/frontend/locales/en.json b/frontend/locales/en.json index 544b7b4..f4a46a4 100644 --- a/frontend/locales/en.json +++ b/frontend/locales/en.json @@ -1,3 +1,69 @@ { - + "product name": "product name", + "product price": "product price", + "product weight": "product weight", + "product type": "product type", + "planned": "planned", + "reccurent": "reccurent", + "product price kg": "product price kg", + "product unit": "product unit", + "grams": "grams", + "kilo": "kilo", + "piece": "piece", + "filter by season": "filter by season", + "name": "name", + "season": "season", + "start": "start", + "end": "end", + "productor": "productor", + "referer": "referer", + "edit form": "edit form", + "form name": "form name", + "contact season": "contact season", + "start date": "start date", + "end date": "end date", + "nothing found": "nothing found", + "number of shipment": "number of shipment", + "cancel": "cancel", + "create form": "create form", + "edit productor": "edit productor", + "remove productor": "remove productor", + "home": "home", + "dashboard": "dashboard", + "filter by name": "filter by name", + "filter by type": "filter by type", + "address": "address", + "payment": "payment", + "type": "type", + "create productor": "create productor", + "productor name": "productor name", + "productor type": "productor type", + "productor address": "productor address", + "productor payment": "productor payment", + "priceKg": "priceKg", + "weight": "weight", + "price": "price", + "create product": "create product", + "informations": "informations", + "remove product": "remove product", + "edit product": "edit product", + "shipment name": "shipment name", + "shipment date": "shipment date", + "remove shipment": "remove shipment", + "productors": "productors", + "products": "products", + "templates": "templates", + "users": "users", + "forms": "forms", + "all forms": "all forms", + "create new form": "create new form", + "actions": "actions", + "all productors": "all productors", + "all products": "all products", + "a name": "a name", + "a season": "a season", + "a start date": "a start date", + "a end date": "a end date", + "a productor": "a productor", + "a referer": "a referer" } \ No newline at end of file diff --git a/frontend/locales/fr.json b/frontend/locales/fr.json index 544b7b4..ad4a481 100644 --- a/frontend/locales/fr.json +++ b/frontend/locales/fr.json @@ -1,3 +1,70 @@ { - + "product name": "nom du produit", + "product price": "prix du produit", + "product weight": "poids du produit", + "product type": "type de produit", + "planned": "planifié", + "reccurent": "récurrent", + "product price kg": "prix du produit au Kilo", + "product unit": "unité de vente du produit", + "grams": "grammes", + "kilo": "kilo", + "piece": "pièce", + "filter by season": "filtrer par saisons", + "name": "nom", + "season": "saison", + "start": "début", + "end": "fin", + "productor": "producteur·trice", + "referer": "référent·e", + "edit form": "modifier le formulaire de contrat", + "form name": "nom du formulaire de contrat", + "contact season": "saison du contrat", + "start date": "date de début", + "end date": "date de fin", + "nothing found": "rien à afficher", + "number of shipment": "nombre de livraisons", + "cancel": "annuler", + "create form": "créer un formulare de contrat", + "edit productor": "modifier le producteur·trice", + "remove productor": "supprimer le producteur·trice", + "home": "accueil", + "dashboard": "tableau de bord", + "filter by name": "filtrer par nom", + "filter by type": "filtrer par type", + "address": "adresse", + "payment": "ordre du chèque", + "type": "type", + "create productor": "créer le producteur·troce", + "productor name": "nom du producteur·trice", + "productor type": "type du producteur·trice", + "productor address": "adresse du producteur·trice", + "productor payment": "ordre du chèque du producteur·trice", + "priceKg": "prix au kilo", + "weight": "poids", + "price": "prix", + "create product": "créer le produit", + "informations": "informations", + "remove product": "supprimer le produit", + "edit product": "modifier le produit", + "shipment name": "nom de la livraison", + "shipment date": "date de la livraison", + "remove shipment": "supprimer la livraison", + "productors": "producteur·trices", + "products": "produits", + "templates": "modèles", + "users": "utilisateur·trices", + "forms": "formulaires de contrat", + "all forms": "tous les formulaires de contrat", + "create new form": "créer un nouveau formulaire de contrat", + "actions": "actions", + "all productors": "tous les producteur·trices", + "all products": "tous les produits", + "is required": "est requis·e", + "a name": "un nom", + "a season": "une saison", + "a start date": "une date de début", + "a end date": "une date de fin", + "a productor": "un(e) producteur·trice", + "a referer": "un référent·e" } \ No newline at end of file diff --git a/frontend/src/components/CreateProduct/index.tsx b/frontend/src/components/CreateProduct/index.tsx index 1a1ff4d..30a2b31 100644 --- a/frontend/src/components/CreateProduct/index.tsx +++ b/frontend/src/components/CreateProduct/index.tsx @@ -1,4 +1,4 @@ -import { Grid, NumberInput, Paper, Select, Stack, TextInput } from "@mantine/core"; +import { Grid, NumberInput, Select, Stack, TextInput } from "@mantine/core"; import { t } from "../../config/i18n"; export type CreateProductProps = { @@ -11,22 +11,22 @@ export default function CreateProduct({form}: CreateProductProps) { (); - const mutation = createProductor(); - - return ( - - {t("Informations")} - - - - {mutation.isError ? {t("an error occured")}:{mutation.error.message} : null} - {mutation.isSuccess ? {t("success")} : null} - - - - - - ); -} \ No newline at end of file diff --git a/frontend/src/components/Forms/FilterForms/index.tsx b/frontend/src/components/Forms/Filter/index.tsx similarity index 68% rename from frontend/src/components/Forms/FilterForms/index.tsx rename to frontend/src/components/Forms/Filter/index.tsx index 6f79f63..4a7ca33 100644 --- a/frontend/src/components/Forms/FilterForms/index.tsx +++ b/frontend/src/components/Forms/Filter/index.tsx @@ -1,5 +1,5 @@ import { Group, MultiSelect } from "@mantine/core"; -import { t } from "../../../config/i18n"; +import { t } from "@/config/i18n"; import { useMemo } from "react"; export type FilterFormsProps = { @@ -9,7 +9,12 @@ export type FilterFormsProps = { onFilterChange: (values: string[], filter: string) => void; } -export function FilterForms({seasons, productors, filters, onFilterChange}: FilterFormsProps) { +export default function FilterForms({ + seasons, + productors, + filters, + onFilterChange +}: FilterFormsProps) { const defaultProductors = useMemo(() => { return filters.getAll("productors") }, [filters]); @@ -20,8 +25,8 @@ export function FilterForms({seasons, productors, filters, onFilterChange}: Filt return ( { @@ -30,8 +35,8 @@ export function FilterForms({seasons, productors, filters, onFilterChange}: Filt clearable /> { diff --git a/frontend/src/components/Forms/FormCard/index.tsx b/frontend/src/components/Forms/FormCard/index.tsx deleted file mode 100644 index 192e0b3..0000000 --- a/frontend/src/components/Forms/FormCard/index.tsx +++ /dev/null @@ -1,77 +0,0 @@ -import { ActionIcon, Badge, Box, Group, Paper, Text, Title } from "@mantine/core"; -import { Link, useNavigate } from "react-router"; -import { deleteForm, getForm, type Form, type Shipment } from "../../../services/api"; -import { IconEdit, IconX } from "@tabler/icons-react"; -import { t } from "../../../config/i18n"; -import FormModal, { type FormInputs } from "../FormModal"; - -export type FormCardProps = { - form: Form; - isEdit: boolean; - closeModal: () => void; - handleSubmit: (form: FormInputs, id?: number) => void; -} - -export default function FormCard({form, isEdit, closeModal, handleSubmit}: FormCardProps) { - const deleteMutation = deleteForm(); - const navigate = useNavigate(); - const {data: currentForm, isPending} = getForm(form.id); - - return ( - - {/* TODO: Show only to logged users */} - - - { - e.stopPropagation(); - navigate(`/form/${form.id}/edit`); - }} - > - - - { - deleteMutation.mutate(form.id); - }} - > - - - - - - - {form.name} - - {form.season} - - - {form.productor.name} - {form.referer.name} - - - - ); -} \ No newline at end of file diff --git a/frontend/src/components/Forms/FormModal/index.tsx b/frontend/src/components/Forms/Modal/index.tsx similarity index 63% rename from frontend/src/components/Forms/FormModal/index.tsx rename to frontend/src/components/Forms/Modal/index.tsx index 3ed5a61..4dcca40 100644 --- a/frontend/src/components/Forms/FormModal/index.tsx +++ b/frontend/src/components/Forms/Modal/index.tsx @@ -1,36 +1,26 @@ import { ActionIcon, Button, Collapse, Group, Modal, NumberInput, Select, TextInput, type ModalBaseProps } from "@mantine/core"; -import { t } from "../../../config/i18n"; +import { t } from "@/config/i18n"; import { DatePickerInput } from "@mantine/dates"; import { IconCancel, IconChevronDown, IconChevronUp } from "@tabler/icons-react"; -import { getProductors, getUsers, type Form, type Shipment } from "../../../services/api"; +import { getProductors, getUsers } from "@/services/api"; import { useForm } from "@mantine/form"; import { useCallback, useEffect, useMemo } from "react"; import { useDisclosure } from "@mantine/hooks"; -import ShipmentForm from "../../ShipmentForm"; - -export type FormInputs = { - name: string; - season: string; - start: string | null; - end: string | null; - productor_id: string; - referer_id: string; - shipments: ShipmentInputs[]; -} - -export type ShipmentInputs = { - name: string | null; - date: string | null; - id: number | null; - form_id: number | null; -} +import type { Form, FormInputs } from "@/services/resources/forms"; +import type { ShipmentInputs } from "@/services/resources/shipments"; +import ShipmentForm from "@/components/Shipments/Form"; export type FormModalProps = ModalBaseProps & { currentForm?: Form; handleSubmit: (form: FormInputs, id?: number) => void; } -export default function FormModal({opened, onClose, currentForm, handleSubmit}: FormModalProps) { +export default function FormModal({ + opened, + onClose, + currentForm, + handleSubmit +}: FormModalProps) { const {data: productors} = getProductors(); const {data: users} = getUsers(); const form = useForm({ @@ -42,6 +32,20 @@ export default function FormModal({opened, onClose, currentForm, handleSubmit}: productor_id: "", referer_id: "", shipments: [], + }, + validate: { + name: (value) => + !value ? `${t("a name", {capfirst: true})} ${t('is required')}` : null, + season: (value) => + !value ? `${t("a season", {capfirst: true})} ${t('is required')}` : null, + start: (value) => + !value ? `${t("a start date", {capfirst: true})} ${t('is required')}` : null, + end: (value) => + !value ? `${t("a end date", {capfirst: true})} ${t('is required')}` : null, + productor_id: (value) => + !value ? `${t("a productor", {capfirst: true})} ${t('is required')}` : null, + referer_id: (value) => + !value ? `${t("a referer", {capfirst: true})} ${t('is required')}` : null } }); @@ -68,7 +72,10 @@ export default function FormModal({opened, onClose, currentForm, handleSubmit}: const [openedShipents, { toggle: toggleShipments }] = useDisclosure(true); - const editShipmentElement = useCallback((index: number, shipment: ShipmentInputs) => { + const editShipmentElement = useCallback(( + index: number, + shipment: ShipmentInputs + ) => { form.setFieldValue('shipments', (prev) => { return prev.map((elem, id) => { if (id === index) @@ -91,35 +98,35 @@ export default function FormModal({opened, onClose, currentForm, handleSubmit}: title={currentForm ? t("edit form") : t('create form')} > - + {openedShipents ? : } { form.getValues().shipments.map((value, index) => - } onClick={() => { form.reset(); + form.clearErrors(); onClose(); }} - >{t("cancel")} + >{t("cancel", {capfirst: true})} + aria-label={currentForm ? t("edit form", {capfirst: true}) : t('create form', {capfirst: true})} + onClick={() => { + form.validate(); + if (form.isValid()) + handleSubmit(form.getValues(), currentForm?.id) + }} + >{currentForm ? t("edit form", {capfirst: true}) : t('create form', {capfirst: true})} ); diff --git a/frontend/src/components/Forms/Row/index.tsx b/frontend/src/components/Forms/Row/index.tsx new file mode 100644 index 0000000..8a0b936 --- /dev/null +++ b/frontend/src/components/Forms/Row/index.tsx @@ -0,0 +1,123 @@ +import { ActionIcon, Table, Tooltip } from "@mantine/core"; +import { useNavigate } from "react-router"; +import { deleteForm, getForm} from "@/services/api"; +import { IconEdit, IconX } from "@tabler/icons-react"; +import { t } from "@/config/i18n"; +import type { Form, FormInputs } from "@/services/resources/forms"; +import FormModal from "@/components/Forms/Modal"; + +export type FormRowProps = { + form: Form; + isEdit: boolean; + closeModal: () => void; + handleSubmit: (form: FormInputs, id?: number) => void; +} + +export default function FormRow({ + form, + isEdit, + closeModal, + handleSubmit +}: FormRowProps) { + const deleteMutation = deleteForm(); + const navigate = useNavigate(); + const {data: currentForm, isPending} = getForm(form.id); + + return ( + + + {form.name} + {form.season} + {form.start} + {form.end} + {form.productor.name} + {form.referer.name} + + + { + e.stopPropagation(); + navigate(`/dashboard/forms/${form.id}/edit`); + }} + > + + + + + { + deleteMutation.mutate(form.id); + }} + > + + + + + + // + // {/* TODO: Show only to logged users */} + // + // + // { + // e.stopPropagation(); + // navigate(`/dashboard/forms/${form.id}/edit`); + // }} + // > + // + // + // { + // deleteMutation.mutate(form.id); + // }} + // > + // + // + // + // + // + // + // {form.name} + // + // {form.season} + // + // + // {form.productor.name} + // {form.referer.name} + // + // + // + ); +} \ No newline at end of file diff --git a/frontend/src/components/Navbar/index.tsx b/frontend/src/components/Navbar/index.tsx index 451bc3d..825c1c6 100644 --- a/frontend/src/components/Navbar/index.tsx +++ b/frontend/src/components/Navbar/index.tsx @@ -1,12 +1,12 @@ import { NavLink } from "react-router"; -import { t } from "../../config/i18n"; +import { t } from "@/config/i18n"; import "./index.css"; + export function Navbar() { return ( ); } \ No newline at end of file diff --git a/frontend/src/components/Productors/Filter/index.tsx b/frontend/src/components/Productors/Filter/index.tsx new file mode 100644 index 0000000..d120fa5 --- /dev/null +++ b/frontend/src/components/Productors/Filter/index.tsx @@ -0,0 +1,48 @@ +import { Group, MultiSelect } from "@mantine/core"; +import { useMemo } from "react"; +import { t } from "@/config/i18n"; + +export type ProductorsFiltersProps = { + names: string[]; + types: string[]; + filters: URLSearchParams; + onFilterChange: (values: string[], filter: string) => void; +} + +export default function ProductorsFilter({ + names, + types, + filters, + onFilterChange +}: ProductorsFiltersProps) { + const defaultNames = useMemo(() => { + return filters.getAll("name") + }, [filters]); + const defaultTypes = useMemo(() => { + return filters.getAll("type") + }, [filters]); + return ( + + { + onFilterChange(values, 'names') + }} + clearable + /> + { + onFilterChange(values, 'types') + }} + clearable + /> + + ); +} \ No newline at end of file diff --git a/frontend/src/components/Productors/Modal/index.tsx b/frontend/src/components/Productors/Modal/index.tsx new file mode 100644 index 0000000..ff4d4c5 --- /dev/null +++ b/frontend/src/components/Productors/Modal/index.tsx @@ -0,0 +1,108 @@ +import { Button, Group, Modal, Text, TextInput, Title, type ModalBaseProps } from "@mantine/core"; +import { t } from "@/config/i18n"; +import { useForm } from "@mantine/form"; +import { IconCancel } from "@tabler/icons-react"; +import type { Productor, ProductorInputs } from "@/services/resources/productors"; +import { useEffect } from "react"; + +export type ProductorModalProps = ModalBaseProps & { + currentProductor?: Productor; + handleSubmit: (productor: ProductorInputs, id?: number) => void; +} + +export function ProductorModal({ + opened, + onClose, + currentProductor, + handleSubmit +}: ProductorModalProps) { + const form = useForm({ + initialValues: { + name: "", + address: "", + payment: "", + type: "", + }, + validate: { + name: (value) => + !value ? `${t("name", {capfirst: true})} ${t('is required')}` : null, + address: (value) => + !value ? `${t("address", {capfirst: true})} ${t('is required')}` : null, + payment: (value) => + !value ? `${t("payment", {capfirst: true})} ${t('is required')}` : null, + type: (value) => + !value ? `${t("type", {capfirst: true})} ${t('is required')}` : null + } + }); + + useEffect(() => { + if (currentProductor) { + form.initialize({ + ...currentProductor, + }); + } + }, [currentProductor]); + + return ( + + {t("Informations", {capfirst: true})} + + + + + + + + + + + ); +} \ No newline at end of file diff --git a/frontend/src/components/Productors/Row/index.tsx b/frontend/src/components/Productors/Row/index.tsx new file mode 100644 index 0000000..bf8255b --- /dev/null +++ b/frontend/src/components/Productors/Row/index.tsx @@ -0,0 +1,67 @@ +import { ActionIcon, Table, Tooltip } from "@mantine/core"; +import { t } from "@/config/i18n"; +import { IconEdit, IconX } from "@tabler/icons-react"; +import type { Productor, ProductorInputs } from "@/services/resources/productors"; +import { ProductorModal } from "@/components/Productors/Modal"; +import { deleteProductor, getProductor } from "@/services/api"; +import { useNavigate } from "react-router"; + +export type ProductorRowProps = { + productor: Productor; + isEdit: boolean; + closeModal: () => void; + handleSubmit: (productor: ProductorInputs, id?: number) => void; +} + +export default function ProductorRow({ + productor, + isEdit, + closeModal, + handleSubmit +}: ProductorRowProps) { + const deleteMutation = deleteProductor(); + const navigate = useNavigate(); + const {data: currentProductor, isPending} = getProductor(productor.id); + + return ( + + + {productor.name} + {productor.type} + {productor.address} + {productor.payment} + + + { + e.stopPropagation(); + navigate(`/dashboard/productors/${productor.id}/edit`); + }} + > + + + + + { + deleteMutation.mutate(productor.id); + }} + > + + + + + + + ); +} \ No newline at end of file diff --git a/frontend/src/components/Products/Filter/index.tsx b/frontend/src/components/Products/Filter/index.tsx new file mode 100644 index 0000000..ccbda7f --- /dev/null +++ b/frontend/src/components/Products/Filter/index.tsx @@ -0,0 +1,48 @@ +import { Group, MultiSelect } from "@mantine/core"; +import { useMemo } from "react"; +import { t } from "@/config/i18n"; + +export type ProductsFiltersProps = { + names: string[]; + types: string[]; + filters: URLSearchParams; + onFilterChange: (values: string[], filter: string) => void; +} + +export default function ProductsFilters({ + names, + types, + filters, + onFilterChange +}: ProductsFiltersProps) { + const defaultNames = useMemo(() => { + return filters.getAll("name") + }, [filters]); + const defaultTypes = useMemo(() => { + return filters.getAll("type") + }, [filters]); + return ( + + { + onFilterChange(values, 'names') + }} + clearable + /> + { + onFilterChange(values, 'types') + }} + clearable + /> + + ); +} \ No newline at end of file diff --git a/frontend/src/components/Products/Modal/index.tsx b/frontend/src/components/Products/Modal/index.tsx new file mode 100644 index 0000000..ec8b133 --- /dev/null +++ b/frontend/src/components/Products/Modal/index.tsx @@ -0,0 +1,139 @@ +import { Button, Group, Modal, NumberInput, Select, TextInput, Title, type ModalBaseProps } from "@mantine/core"; +import { t } from "@/config/i18n"; +import { useForm } from "@mantine/form"; +import { IconCancel } from "@tabler/icons-react"; +import type { Product, ProductInputs } from "@/services/resources/products"; +import { useEffect, useMemo } from "react"; +import { getProductors } from "@/services/api"; + +export type ProductModalProps = ModalBaseProps & { + currentProduct?: Product; + handleSubmit: (product: ProductInputs, id?: number) => void; +} + +export function ProductModal({ + opened, + onClose, + currentProduct, + handleSubmit +}: ProductModalProps) { + const {data: productors} = getProductors(); + const form = useForm({ + initialValues: { + name: "", + unit: null, + price: null, + priceKg: null, + weight: null, + type: "", + productor_id: null, + }, + validate: { + name: (value) => + !value ? `${t("name", {capfirst: true})} ${t('is required')}` : null, + unit: (value) => + !value ? `${t("unit", {capfirst: true})} ${t('is required')}` : null, + price: (value) => + !value ? `${t("price", {capfirst: true})} ${t('is required')}` : null, + priceKg: (value) => + !value ? `${t("priceKg", {capfirst: true})} ${t('is required')}` : null, + weight: (value) => + !value ? `${t("weight", {capfirst: true})} ${t('is required')}` : null, + type: (value) => + !value ? `${t("type", {capfirst: true})} ${t('is required')}` : null, + productor_id: (value) => + !value ? `${t("productor", {capfirst: true})} ${t('is required')}` : null + } + }); + + useEffect(() => { + if (currentProduct) { + form.initialize({ + ...currentProduct, + }); + } + }, [currentProduct]); + + const productorsSelect = useMemo(() => { + return productors?.map(prod => ({value: String(prod.id), label: `${prod.name}`})) + }, [productors]) + + return ( + + {t("informations", {capfirst: true})} + + + + + + el.name)} - /> - } - - - {t("products planned phase")} - - */} - - ); -} \ No newline at end of file diff --git a/frontend/src/pages/Forms/index.tsx b/frontend/src/pages/Forms/index.tsx index 562333f..b759c8d 100644 --- a/frontend/src/pages/Forms/index.tsx +++ b/frontend/src/pages/Forms/index.tsx @@ -1,23 +1,25 @@ -import { Stack, Loader, Title, Group, ActionIcon, Flex, Tooltip } from "@mantine/core"; -import { createForm, createShipment, editForm, editShipment, getForms, type Form } from "../../services/api"; -import { t } from "../../config/i18n"; +import { Stack, Loader, Title, Group, ActionIcon, Tooltip, Table, ScrollArea } from "@mantine/core"; +import { createForm, createShipment, editForm, editShipment, getForms } from "@/services/api"; +import { t } from "@/config/i18n"; import { useLocation, useNavigate, useSearchParams } from "react-router"; import { IconPlus } from "@tabler/icons-react"; import { useCallback, useMemo } from "react"; -import { FilterForms } from "../../components/Forms/FilterForms"; -import FormCard from "../../components/Forms/FormCard"; -import FormModal, { type FormInputs, type ShipmentInputs } from "../../components/Forms/FormModal"; +import FormModal from "@/components/Forms/Modal"; +import FormRow from "@/components/Forms/Row"; +import type { Form, FormInputs } from "@/services/resources/forms"; +import type { ShipmentEdit, ShipmentInputs } from "@/services/resources/shipments"; +import FilterForms from "@/components/Forms/Filter"; export function Forms() { const [ searchParams, setSearchParams ] = useSearchParams(); const location = useLocation(); const navigate = useNavigate(); - const isCreate = location.pathname === "/form/create"; + const isCreate = location.pathname === "/dashboard/forms/create"; const isEdit = location.pathname.includes("/edit"); const closeModal = () => { - navigate("/forms"); + navigate("/dashboard/forms"); }; const { isPending, data } = getForms(searchParams); @@ -43,7 +45,6 @@ export function Forms() { return; const newForm = await createFormMutation.mutateAsync({ ...form, - shipment_ids: [], start: form?.start, end: form?.start, productor_id: Number(form.productor_id), @@ -61,50 +62,62 @@ export function Forms() { ); }); closeModal(); - }, []); + }, [createFormMutation, createShipmentsMutation]); const handleEditForm = useCallback(async (form: FormInputs, id?: number) => { if (!id) return; - // edit all existing shipments - // edit form - form.shipments.filter(el => el.id).map(async (shipment) => { - if (!shipment.name || !shipment.date || !shipment.form_id || !shipment.id) + form.shipments + .filter((el: ShipmentInputs) => el.id) + .map(async (shipment: ShipmentInputs) => { + if ( + !shipment.name || + !shipment.date || + !shipment.form_id || + !shipment.id + ) return - const newShipment = { + const newShipment: ShipmentEdit = { name: shipment.name, date: shipment.date, form_id: shipment.form_id, - } - await editShipmentsMutation.mutate({id: shipment.id, shipment: newShipment}) + }; + await editShipmentsMutation.mutate({ + id: shipment.id, + shipment: newShipment + }); }); const newForm = await editFormMutation.mutateAsync({ id: id, form: { ...form, - shipment_ids: [], start: form.start, end: form.start, productor_id: Number(form.productor_id), referer_id: Number(form.referer_id) } }); - // if shipments to add -> create shipments - form.shipments.filter(el => el.id === null).map(async (shipment) => { + form.shipments + .filter((el: ShipmentInputs) => el.id === null) + .map(async (shipment: ShipmentInputs) => { if (!shipment.name || !shipment.date) return const newShipment = { name: shipment.name, date: shipment.date, } - return await createShipmentsMutation.mutateAsync( - {...newShipment, form_id: newForm.id} - ); + return await createShipmentsMutation.mutateAsync({ + ...newShipment, + form_id: newForm.id, + }); }); closeModal(); - }, []); + }, [editShipmentsMutation, createShipmentsMutation, editFormMutation]); - const onFilterChange = useCallback((values: string[], filter: string) => { + const onFilterChange = useCallback(( + values: string[], + filter: string + ) => { setSearchParams(prev => { const params = new URLSearchParams(prev); params.delete(filter) @@ -121,21 +134,24 @@ export function Forms() { return (); return ( - + - {t("All forms")} - + {t("all forms", {capfirst: true})} + { e.stopPropagation(); - navigate(`/form/create`); + navigate(`/dashboard/forms/create`); }} > - + - + + + + + {t("name", {capfirst: true})} + {t("type", {capfirst: true})} + {t("start", {capfirst: true})} + {t("end", {capfirst: true})} + {t("productor", {capfirst: true})} + {t("referer", {capfirst: true})} + {t("actions", {capfirst: true})} + + + + { + data.map((form) => ( + + )) + } + +
+
+ + {/* { data?.map((form: Form) => ( )) } - +
*/}
); } \ No newline at end of file diff --git a/frontend/src/pages/Home/index.tsx b/frontend/src/pages/Home/index.tsx index 2f213d4..95a43e1 100644 --- a/frontend/src/pages/Home/index.tsx +++ b/frontend/src/pages/Home/index.tsx @@ -1,8 +1,8 @@ import { Text } from "@mantine/core"; -import { t } from "../../config/i18n"; +import { t } from "@/config/i18n"; export function Home() { return ( - {t("test")} + {t("test", {capfirst: true})} ); } \ No newline at end of file diff --git a/frontend/src/pages/Productors/index.tsx b/frontend/src/pages/Productors/index.tsx new file mode 100644 index 0000000..020bf3f --- /dev/null +++ b/frontend/src/pages/Productors/index.tsx @@ -0,0 +1,127 @@ +import { ActionIcon, Group, Loader, ScrollArea, Stack, Table, Title, Tooltip } from "@mantine/core"; +import { t } from "@/config/i18n"; +import { createProductor, editProductor, getProductors } from "@/services/api"; +import { IconPlus } from "@tabler/icons-react"; +import ProductorRow from "@/components/Productors/Row"; +import { useLocation, useNavigate, useSearchParams } from "react-router"; +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"; + +export default function Productors() { + const [ searchParams, setSearchParams ] = useSearchParams(); + const location = useLocation(); + const navigate = useNavigate(); + + const isCreate = location.pathname === "/dashboard/productors/create"; + const isEdit = location.pathname.includes("/edit"); + + const closeModal = () => { + navigate("/dashboard/productors"); + }; + + const {data: productors, isPending} = getProductors(searchParams); + const {data: allProductors } = getProductors(); + + const names = useMemo(() => { + return allProductors?.map((productor: Productor) => (productor.name)) + .filter((season, index, array) => array.indexOf(season) === index) + }, [allProductors]) + + const types = useMemo(() => { + return allProductors?.map((productor: Productor) => (productor.type)) + .filter((productor, index, array) => array.indexOf(productor) === index) + }, [allProductors]) + + const createProductorMutation = createProductor(); + const editProductorMutation = editProductor(); + + const handleCreateProductor = useCallback(async (productor: ProductorInputs) => { + await createProductorMutation.mutateAsync({ + ...productor + }); + closeModal(); + }, [createProductorMutation]); + + const handleEditProductor = useCallback(async (productor: ProductorInputs, id?: number) => { + if (!id) + return; + await editProductorMutation.mutateAsync({ + id: id, + productor: productor + }); + closeModal(); + }, []); + + const onFilterChange = useCallback((values: string[], filter: string) => { + setSearchParams(prev => { + const params = new URLSearchParams(prev); + params.delete(filter) + + values.forEach(value => { + params.append(filter, value); + }); + + return params; + }); + }, [searchParams, setSearchParams]) + + if (!productors || isPending) + return + + return ( + + + {t("all productors", {capfirst: true})} + + { + e.stopPropagation(); + navigate(`/dashboard/productors/create`); + }} + > + + + + + + + + + + + {t("name", {capfirst: true})} + {t("type", {capfirst: true})} + {t("address", {capfirst: true})} + {t("payment", {capfirst: true})} + {t("actions", {capfirst: true})} + + + + { + productors.map((productor) => ( + + )) + } + +
+
+
+ ); +} \ No newline at end of file diff --git a/frontend/src/pages/Products/index.tsx b/frontend/src/pages/Products/index.tsx new file mode 100644 index 0000000..c6aa517 --- /dev/null +++ b/frontend/src/pages/Products/index.tsx @@ -0,0 +1,129 @@ +import { ActionIcon, Group, Loader, ScrollArea, Stack, Table, Title, Tooltip } from "@mantine/core"; +import { t } from "@/config/i18n"; +import { createProduct, editProduct, getProducts } from "@/services/api"; +import { IconPlus } from "@tabler/icons-react"; +import ProductRow from "@/components/Products/Row"; +import { useLocation, useNavigate, useSearchParams } from "react-router"; +import { ProductModal } from "@/components/Products/Modal"; +import { useCallback, useMemo } from "react"; +import type { Product, ProductInputs } from "@/services/resources/products"; +import ProductsFilters from "@/components/Products/Filter"; + +export default function Products() { + const [ searchParams, setSearchParams ] = useSearchParams(); + const location = useLocation(); + const navigate = useNavigate(); + + const isCreate = location.pathname === "/dashboard/products/create"; + const isEdit = location.pathname.includes("/edit"); + + const closeModal = () => { + navigate("/dashboard/products"); + }; + + const {data: products, isPending} = getProducts(searchParams); + const {data: allProducts } = getProducts(); + + const names = useMemo(() => { + return allProducts?.map((product: Product) => (product.name)) + .filter((season, index, array) => array.indexOf(season) === index) + }, [allProducts]) + + const types = useMemo(() => { + return allProducts?.map((product: Product) => (product.type)) + .filter((product, index, array) => array.indexOf(product) === index) + }, [allProducts]) + + const createProductMutation = createProduct(); + const editProductMutation = editProduct(); + + const handleCreateProduct = useCallback(async (product: ProductInputs) => { + await createProductMutation.mutateAsync({ + ...product + }); + closeModal(); + }, [createProductMutation]); + + const handleEditProduct = useCallback(async (product: ProductInputs, id?: number) => { + if (!id) + return; + await editProductMutation.mutateAsync({ + id: id, + product: product + }); + closeModal(); + }, []); + + const onFilterChange = useCallback((values: string[], filter: string) => { + setSearchParams(prev => { + const params = new URLSearchParams(prev); + params.delete(filter) + + values.forEach(value => { + params.append(filter, value); + }); + + return params; + }); + }, [searchParams, setSearchParams]) + + if (!products || isPending) + return + + return ( + + + {t("all products", {capfirst: true})} + + { + e.stopPropagation(); + navigate(`/dashboard/products/create`); + }} + > + + + + + + + + + + + {t("name", {capfirst: true})} + {t("type", {capfirst: true})} + {t("price", {capfirst: true})} + {t("priceKg", {capfirst: true})} + {t("weight", {capfirst: true})} + {t("unit", {capfirst: true})} + {t("actions", {capfirst: true})} + + + + { + products.map((product) => ( + + )) + } + +
+
+
+ ); +} \ No newline at end of file diff --git a/frontend/src/pages/Templates/index.tsx b/frontend/src/pages/Templates/index.tsx new file mode 100644 index 0000000..5e156a3 --- /dev/null +++ b/frontend/src/pages/Templates/index.tsx @@ -0,0 +1,5 @@ +export default function Templates() { + return ( + <> + ); +} \ No newline at end of file diff --git a/frontend/src/pages/Users/index.tsx b/frontend/src/pages/Users/index.tsx new file mode 100644 index 0000000..4eaab83 --- /dev/null +++ b/frontend/src/pages/Users/index.tsx @@ -0,0 +1,5 @@ +export default function Users() { + return ( + <> + ); +} \ No newline at end of file diff --git a/frontend/src/root.tsx b/frontend/src/root.tsx index 565fd23..045f5ad 100644 --- a/frontend/src/root.tsx +++ b/frontend/src/root.tsx @@ -1,6 +1,6 @@ import { Outlet } from "react-router"; -import { Navbar } from "./components/Navbar"; -import { Footer } from "./components/Footer"; +import { Navbar } from "@/components/Navbar"; +import { Footer } from "@/components/Footer"; export default function Root() { return ( diff --git a/frontend/src/router.tsx b/frontend/src/router.tsx index e986acc..8d530f2 100644 --- a/frontend/src/router.tsx +++ b/frontend/src/router.tsx @@ -2,11 +2,15 @@ import { createBrowserRouter, } from "react-router"; -import Root from "./root"; -import { Home } from "./pages/Home"; -import { Forms } from "./pages/Forms"; -import { ReadForm } from "./pages/Forms/ReadForm" -// import { CreateForms } from "./pages/Forms/CreateForm"; +import Root from "@/root"; +import { Home } from "@/pages/Home"; +import { Forms } from "@/pages/Forms"; +import Dashboard from "@/pages/Dashboard"; +import Productors from "@/pages/Productors"; +import Products from "@/pages/Products"; +import Templates from "@/pages/Templates"; +import Users from "@/pages/Users"; +// import { CreateForms } from "@/pages/Forms/CreateForm"; export const router = createBrowserRouter([ { @@ -16,9 +20,21 @@ export const router = createBrowserRouter([ children: [ { index: true, Component: Home }, { path: "/forms", Component: Forms }, - { path: "/form/:id", Component: ReadForm }, - { path: "/form/:id/edit", Component: Forms }, - { path: "/form/create", Component: Forms }, + { path: "/dashboard", Component: Dashboard, children: [ + {path: "productors", Component: Productors}, + {path: "productors/create", Component: Productors}, + {path: "productors/:id/edit", Component: Productors}, + { path: "products", Component: Products }, + {path: "products/create", Component: Products}, + {path: "products/:id/edit", Component: Products}, + { path: "templates", Component: Templates }, + { path: "users", Component: Users }, + { path: "forms", Component: Forms }, + { path: "forms/:id/edit", Component: Forms }, + { path: "forms/create", Component: Forms }, + ] }, + + // { path: "/form/:id", Component: ReadForm }, ], }, ]); diff --git a/frontend/src/services/api.ts b/frontend/src/services/api.ts index d4b6729..9adc0c0 100644 --- a/frontend/src/services/api.ts +++ b/frontend/src/services/api.ts @@ -1,85 +1,16 @@ import { useMutation, useQuery, useQueryClient,type UseQueryResult } from "@tanstack/react-query"; -import { Config } from "../config/config"; +import { Config } from "@/config/config"; +import type { Form, FormCreate, FormEditPayload } from "@/services/resources/forms"; +import type { Shipment, ShipmentCreate, ShipmentEditPayload } from "@/services/resources/shipments"; +import type { Productor, ProductorCreate, ProductorEditPayload } from "@/services/resources/productors"; +import type { User } from "@/services/resources/users"; +import type { Product, ProductCreate, ProductEditPayload } from "./resources/products"; -export type Productor = { - id: number; - name: string; - address: string; - payment: string; -} - -export type Shipment = { - name: string; - date: string; - id?: number; - form_id: number; -} - -export type Form = { - id: number; - name: string; - season: string; - start: string; - end: string; - productor: Productor; - referer: User; - shipments: Shipment[]; -} - -export type FormCreate = { - name: string; - season: string; - start: string; - end: string; - productor_id: number; - referer_id: number; - shipment_ids: Shipment[]; -} - -export type FormEdit = { - name?: string | null; - season?: string | null; - start?: string | null; - end?: string | null; - productor_id?: number | null; - referer_id?: number | null; - shipment_ids: Shipment[]; -} - -export type Product = { - id: number; - productor: Productor; - name: string; - unit: number; - price: number; - priceKg: number | null; - weight: number; - type: number; -} - -export type User = { - id: number; - name: string; - email: string; - products: Product[]; -} - -export function getForm(id: number): UseQueryResult { - return useQuery
({ - queryKey: ['form'], +export function getUsers() { + return useQuery({ + queryKey: ['users'], queryFn: () => ( - fetch(`${Config.backend_uri}/forms/${id}`) - .then((res) => res.json()) - ), - }); -} - -export function getForms(filters?: URLSearchParams): UseQueryResult { - const queryString = filters?.toString() - return useQuery({ - queryKey: ['forms', queryString], - queryFn: () => ( - fetch(`${Config.backend_uri}/forms${filters ? `?${queryString}` : ""}`) + fetch(`${Config.backend_uri}/users`) .then((res) => res.json()) ), }); @@ -95,32 +26,11 @@ export function getShipments() { }); } -export function getProductors() { - return useQuery({ - queryKey: ['productors'], - queryFn: () => ( - fetch(`${Config.backend_uri}/productors`) - .then((res) => res.json()) - ), - }); -} - - -export function getUsers() { - return useQuery({ - queryKey: ['users'], - queryFn: () => ( - fetch(`${Config.backend_uri}/users`) - .then((res) => res.json()) - ), - }); -} - export function createShipment() { const queryClient = useQueryClient() return useMutation({ - mutationFn: (newShipment: Shipment) => { + mutationFn: (newShipment: ShipmentCreate) => { return fetch(`${Config.backend_uri}/shipments`, { method: 'POST', headers: { @@ -136,11 +46,6 @@ export function createShipment() { }) } -export type ShipmentEditPayload = { - id: number; - shipment: Shipment; -} - export function editShipment() { const queryClient = useQueryClient() @@ -160,11 +65,32 @@ export function editShipment() { }) } +export function getProductors(filters?: URLSearchParams) { + const queryString = filters?.toString() + return useQuery({ + queryKey: ['productors', filters], + queryFn: () => ( + fetch(`${Config.backend_uri}/productors${filters ? `?${queryString}` : ""}`) + .then((res) => res.json()) + ), + }); +} + +export function getProductor(id: number) { + return useQuery({ + queryKey: ['productor'], + queryFn: () => ( + fetch(`${Config.backend_uri}/productors/${id}`) + .then((res) => res.json()) + ), + }); +} + export function createProductor() { const queryClient = useQueryClient() return useMutation({ - mutationFn: (newProductor: Productor) => { + mutationFn: (newProductor: ProductorCreate) => { return fetch(`${Config.backend_uri}/productors`, { method: 'POST', headers: { @@ -179,6 +105,63 @@ export function createProductor() { }) } +export function editProductor() { + const queryClient = useQueryClient() + + return useMutation({ + mutationFn: ({productor, id}: ProductorEditPayload) => { + return fetch(`${Config.backend_uri}/productors/${id}`, { + method: 'PUT', + headers: { + "Content-Type": "application/json" + }, + body: JSON.stringify(productor), + }).then((res) => res.json()); + }, + onSuccess: async () => { + await queryClient.invalidateQueries({ queryKey: ['productors'] }) + } + }) +} + +export function deleteProductor() { + const queryClient = useQueryClient() + return useMutation({ + mutationFn: (id: number) => { + return fetch(`${Config.backend_uri}/productors/${id}`, { + method: 'DELETE', + headers: { + "Content-Type": "application/json" + }, + }).then((res) => res.json()); + }, + onSuccess: async () => { + await queryClient.invalidateQueries({ queryKey: ['productors'] }) + } + }); +} + +export function getForm(id: number): UseQueryResult { + return useQuery({ + queryKey: ['form'], + queryFn: () => ( + fetch(`${Config.backend_uri}/forms/${id}`) + .then((res) => res.json()) + ), + }); +} + +export function getForms(filters?: URLSearchParams): UseQueryResult { + const queryString = filters?.toString() + return useQuery({ + queryKey: ['forms', queryString], + queryFn: () => ( + fetch(`${Config.backend_uri}/forms${filters ? `?${queryString}` : ""}`) + .then((res) => res.json()) + ), + }); +} + export function createForm() { const queryClient = useQueryClient() @@ -215,11 +198,6 @@ export function deleteForm() { }); } -export type FormEditPayload = { - id: number; - form: FormEdit; -} - export function editForm() { const queryClient = useQueryClient() @@ -238,3 +216,79 @@ export function editForm() { } }); } + +export function getProduct(id: number): UseQueryResult { + return useQuery({ + queryKey: ['product'], + queryFn: () => ( + fetch(`${Config.backend_uri}/products/${id}`) + .then((res) => res.json()) + ), + }); +} + +export function getProducts(filters?: URLSearchParams): UseQueryResult { + const queryString = filters?.toString() + return useQuery({ + queryKey: ['products', queryString], + queryFn: () => ( + fetch(`${Config.backend_uri}/products${filters ? `?${queryString}` : ""}`) + .then((res) => res.json()) + ), + }); +} + +export function createProduct() { + const queryClient = useQueryClient() + + return useMutation({ + mutationFn: (newProduct: ProductCreate) => { + return fetch(`${Config.backend_uri}/products`, { + method: 'POST', + headers: { + "Content-Type": "application/json" + }, + body: JSON.stringify(newProduct), + }).then((res) => res.json()); + }, + onSuccess: async () => { + await queryClient.invalidateQueries({ queryKey: ['products'] }) + } + }); +} + +export function deleteProduct() { + const queryClient = useQueryClient() + return useMutation({ + mutationFn: (id: number) => { + return fetch(`${Config.backend_uri}/products/${id}`, { + method: 'DELETE', + headers: { + "Content-Type": "application/json" + }, + }).then((res) => res.json()); + }, + onSuccess: async () => { + await queryClient.invalidateQueries({ queryKey: ['products'] }) + } + }); +} + +export function editProduct() { + const queryClient = useQueryClient() + + return useMutation({ + mutationFn: ({id, product}: ProductEditPayload) => { + return fetch(`${Config.backend_uri}/products/${id}`, { + method: 'PUT', + headers: { + "Content-Type": "application/json" + }, + body: JSON.stringify(product), + }).then((res) => res.json()); + }, + onSuccess: async () => { + await queryClient.invalidateQueries({ queryKey: ['products'] }) + } + }); +} diff --git a/frontend/src/services/resources/forms.ts b/frontend/src/services/resources/forms.ts new file mode 100644 index 0000000..43579a1 --- /dev/null +++ b/frontend/src/services/resources/forms.ts @@ -0,0 +1,47 @@ +import type { Productor } from "@/services/resources/productors"; +import type { Shipment, ShipmentInputs } from "@/services/resources/shipments"; +import type { User } from "@/services/resources/users"; + +export type Form = { + id: number; + name: string; + season: string; + start: string; + end: string; + productor: Productor; + referer: User; + shipments: Shipment[]; +} + +export type FormCreate = { + name: string; + season: string; + start: string; + end: string; + productor_id: number; + referer_id: number; +} + +export type FormEdit = { + name?: string | null; + season?: string | null; + start?: string | null; + end?: string | null; + productor_id?: number | null; + referer_id?: number | null; +} + +export type FormEditPayload = { + id: number; + form: FormEdit; +} + +export type FormInputs = { + name: string; + season: string; + start: string | null; + end: string | null; + productor_id: string; + referer_id: string; + shipments: ShipmentInputs[]; +} \ No newline at end of file diff --git a/frontend/src/services/resources/productors.ts b/frontend/src/services/resources/productors.ts new file mode 100644 index 0000000..42fa513 --- /dev/null +++ b/frontend/src/services/resources/productors.ts @@ -0,0 +1,33 @@ +export type Productor = { + id: number; + name: string; + address: string; + payment: string; + type: string; +} + +export type ProductorCreate = { + name: string; + address: string; + payment: string; + type: string; +} + +export type ProductorEdit = { + name: string | null; + address: string | null; + payment: string | null; + type: string | null; +} + +export type ProductorInputs = { + name: string; + address: string; + payment: string; + type: string; +} + +export type ProductorEditPayload = { + productor: ProductorEdit; + id: number; +} \ No newline at end of file diff --git a/frontend/src/services/resources/products.ts b/frontend/src/services/resources/products.ts new file mode 100644 index 0000000..2b11198 --- /dev/null +++ b/frontend/src/services/resources/products.ts @@ -0,0 +1,49 @@ +import type { Productor } from "@/services/resources/productors"; +import type { Shipment } from "@/services/resources/shipments"; + +export type Product = { + id: number; + productor: Productor; + name: string; + unit: number; + price: number; + priceKg: number | null; + weight: number; + type: number; + shipments: Shipment[]; +} + +export type ProductCreate = { + productor_id: Productor; + name: string; + unit: number; + price: number; + priceKg: number | null; + weight: number; + type: number; +} + +export type ProductEdit = { + productor_id: Productor | null; + name: string | null; + unit: number | null; + price: number | null; + priceKg: number | null; + weight: number | null; + type: number | null; +} + +export type ProductInputs = { + productor_id: number | null; + name: string; + unit: number | null; + price: number | null; + priceKg: number | null; + weight: number | null; + type: string; +} + +export type ProductEditPayload = { + product: ProductEdit; + id: number; +} \ No newline at end of file diff --git a/frontend/src/services/resources/shipments.ts b/frontend/src/services/resources/shipments.ts new file mode 100644 index 0000000..20ff696 --- /dev/null +++ b/frontend/src/services/resources/shipments.ts @@ -0,0 +1,30 @@ +export type Shipment = { + name: string; + date: string; + id: number; + form_id: number; +} + +export type ShipmentCreate = { + name: string; + date: string; + form_id: number; +} + +export type ShipmentEdit = { + name: string | null; + date: string | null; + form_id: number | null; +} + +export type ShipmentEditPayload = { + id: number; + shipment: ShipmentEdit; +} + +export type ShipmentInputs = { + name: string | null; + date: string | null; + id: number | null; + form_id: number | null; +} \ No newline at end of file diff --git a/frontend/src/services/resources/users.ts b/frontend/src/services/resources/users.ts new file mode 100644 index 0000000..ffc1f32 --- /dev/null +++ b/frontend/src/services/resources/users.ts @@ -0,0 +1,8 @@ +import type { Product } from "@/services/resources/products"; + +export type User = { + id: number; + name: string; + email: string; + products: Product[]; +} diff --git a/frontend/tsconfig.app.json b/frontend/tsconfig.app.json index a9b5a59..5db930a 100644 --- a/frontend/tsconfig.app.json +++ b/frontend/tsconfig.app.json @@ -22,7 +22,11 @@ "noUnusedParameters": true, "erasableSyntaxOnly": true, "noFallthroughCasesInSwitch": true, - "noUncheckedSideEffectImports": true + "noUncheckedSideEffectImports": true, + "baseUrl": ".", + "paths": { + "@/*": ["src/*"] + } }, "include": ["src"] } diff --git a/frontend/tsconfig.json b/frontend/tsconfig.json index 1ffef60..84c8a23 100644 --- a/frontend/tsconfig.json +++ b/frontend/tsconfig.json @@ -3,5 +3,5 @@ "references": [ { "path": "./tsconfig.app.json" }, { "path": "./tsconfig.node.json" } - ] + ], } diff --git a/frontend/vite.config.ts b/frontend/vite.config.ts index 600a49e..582acc5 100644 --- a/frontend/vite.config.ts +++ b/frontend/vite.config.ts @@ -1,12 +1,18 @@ import { defineConfig } from 'vite' import react from '@vitejs/plugin-react' +import path from 'path'; // https://vite.dev/config/ export default defineConfig({ plugins: [react()], + resolve: { + alias: { + '@': path.resolve(__dirname, 'src'), + }, + }, server: { watch: { - usePolling: true, // Enable polling for file changes + usePolling: true, }, } })