add shipments create and form create
This commit is contained in:
@@ -1,56 +0,0 @@
|
||||
import { Button, Group, Loader, Modal, Text, TextInput, Title, type ModalBaseProps } from "@mantine/core";
|
||||
import { t } from "../../config/i18n";
|
||||
import { useForm } from "@mantine/form";
|
||||
import { IconCancel, IconPlus } from "@tabler/icons-react";
|
||||
import { DatePickerInput } from "@mantine/dates";
|
||||
import { createShipment, type ShipmentCreate } from "../../services/api";
|
||||
import { useEffect } from "react";
|
||||
|
||||
export function CreateShipmentModal({opened, onClose}: ModalBaseProps) {
|
||||
const form = useForm<ShipmentCreate>();
|
||||
const mutation = createShipment();
|
||||
|
||||
return (
|
||||
<Modal
|
||||
size="50%"
|
||||
opened={opened}
|
||||
onClose={onClose}
|
||||
title={t("create shipment")}
|
||||
>
|
||||
<Title order={4}>{t("informations")}</Title>
|
||||
<TextInput
|
||||
label={t("shipment name")}
|
||||
placeholder={t("shipment name")}
|
||||
radius="sm"
|
||||
withAsterisk
|
||||
{...form.getInputProps('name')}
|
||||
/>
|
||||
<DatePickerInput
|
||||
label={t("shipment date")}
|
||||
placeholder={t("shipment date")}
|
||||
radius="sm"
|
||||
withAsterisk
|
||||
{...form.getInputProps('date')}
|
||||
/>
|
||||
{mutation.isError ? <Text>{t("an error occured")}:{mutation.error.message}</Text> : null}
|
||||
{mutation.isSuccess ? <Text>{t("success")}</Text> : null}
|
||||
<Group mt="sm" justify="space-between">
|
||||
<Button
|
||||
variant="filled"
|
||||
color="red"
|
||||
aria-label={t("cancel")}
|
||||
leftSection={<IconCancel/>}
|
||||
onClick={onClose}
|
||||
>{t("cancel")}</Button>
|
||||
<Button
|
||||
variant="filled"
|
||||
aria-label={t("create shipment")}
|
||||
leftSection={mutation.isPending ? <Loader/> : <IconPlus/>}
|
||||
onClick={() => {
|
||||
mutation.mutate({...form.getValues(), product_ids: []});
|
||||
}}
|
||||
>{t("create shipment")}</Button>
|
||||
</Group>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
44
frontend/src/components/Forms/FilterForms/index.tsx
Normal file
44
frontend/src/components/Forms/FilterForms/index.tsx
Normal file
@@ -0,0 +1,44 @@
|
||||
import { Group, MultiSelect } from "@mantine/core";
|
||||
import { t } from "../../../config/i18n";
|
||||
import { useMemo } from "react";
|
||||
|
||||
export type FilterFormsProps = {
|
||||
seasons: string[];
|
||||
productors: string[];
|
||||
filters: URLSearchParams;
|
||||
onFilterChange: (values: string[], filter: string) => void;
|
||||
}
|
||||
|
||||
export function FilterForms({seasons, productors, filters, onFilterChange}: FilterFormsProps) {
|
||||
const defaultProductors = useMemo(() => {
|
||||
return filters.getAll("productors")
|
||||
}, [filters]);
|
||||
const defaultSeasons = useMemo(() => {
|
||||
return filters.getAll("seasons")
|
||||
}, [filters]);
|
||||
|
||||
return (
|
||||
<Group>
|
||||
<MultiSelect
|
||||
aria-label={t("filter by season")}
|
||||
placeholder={t("filter by season")}
|
||||
data={seasons}
|
||||
defaultValue={defaultSeasons}
|
||||
onChange={(values: string[]) => {
|
||||
onFilterChange(values, 'seasons')
|
||||
}}
|
||||
clearable
|
||||
/>
|
||||
<MultiSelect
|
||||
aria-label={t("filter by productor")}
|
||||
placeholder={t("filter by productor")}
|
||||
data={productors}
|
||||
defaultValue={defaultProductors}
|
||||
onChange={(values: string[]) => {
|
||||
onFilterChange(values, 'productors')
|
||||
}}
|
||||
clearable
|
||||
/>
|
||||
</Group>
|
||||
);
|
||||
}
|
||||
77
frontend/src/components/Forms/FormCard/index.tsx
Normal file
77
frontend/src/components/Forms/FormCard/index.tsx
Normal file
@@ -0,0 +1,77 @@
|
||||
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 (
|
||||
<Paper
|
||||
key={form.id}
|
||||
shadow="xl"
|
||||
p="xl"
|
||||
miw={{base: "100vw", md: "25vw", lg:"20vw"}}
|
||||
>
|
||||
{/* TODO: Show only to logged users */}
|
||||
<FormModal
|
||||
opened={isEdit}
|
||||
onClose={closeModal}
|
||||
currentForm={currentForm}
|
||||
handleSubmit={handleSubmit}
|
||||
/>
|
||||
<Group justify="space-between" mb="sm">
|
||||
<ActionIcon
|
||||
size={"sm"}
|
||||
aria-label={t("edit form")}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
navigate(`/form/${form.id}/edit`);
|
||||
}}
|
||||
>
|
||||
<IconEdit/>
|
||||
</ActionIcon>
|
||||
<ActionIcon
|
||||
size={"sm"}
|
||||
aria-label={t("delete form")}
|
||||
color="red"
|
||||
onClick={() => {
|
||||
deleteMutation.mutate(form.id);
|
||||
}}
|
||||
>
|
||||
<IconX/>
|
||||
</ActionIcon>
|
||||
</Group>
|
||||
<Box
|
||||
component={Link}
|
||||
to={`/form/${form.id}`}
|
||||
>
|
||||
<Group justify="space-between" wrap="nowrap">
|
||||
<Title
|
||||
order={3}
|
||||
textWrap="wrap"
|
||||
lineClamp={1}
|
||||
>
|
||||
{form.name}
|
||||
</Title>
|
||||
<Badge>{form.season}</Badge>
|
||||
</Group>
|
||||
<Group justify="space-between">
|
||||
<Text>{form.productor.name}</Text>
|
||||
<Text>{form.referer.name}</Text>
|
||||
</Group>
|
||||
</Box>
|
||||
</Paper>
|
||||
);
|
||||
}
|
||||
201
frontend/src/components/Forms/FormModal/index.tsx
Normal file
201
frontend/src/components/Forms/FormModal/index.tsx
Normal file
@@ -0,0 +1,201 @@
|
||||
import { ActionIcon, Button, Collapse, Group, Modal, NumberInput, Select, TextInput, type ModalBaseProps } from "@mantine/core";
|
||||
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 { 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;
|
||||
}
|
||||
|
||||
export type FormModalProps = ModalBaseProps & {
|
||||
currentForm?: Form;
|
||||
handleSubmit: (form: FormInputs, id?: number) => void;
|
||||
}
|
||||
|
||||
export default function FormModal({opened, onClose, currentForm, handleSubmit}: FormModalProps) {
|
||||
const {data: productors} = getProductors();
|
||||
const {data: users} = getUsers();
|
||||
const form = useForm<FormInputs>({
|
||||
initialValues: {
|
||||
name: "",
|
||||
season: "",
|
||||
start: null,
|
||||
end: null,
|
||||
productor_id: "",
|
||||
referer_id: "",
|
||||
shipments: [],
|
||||
}
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (currentForm) {
|
||||
form.initialize({
|
||||
...currentForm,
|
||||
start: currentForm.start || null,
|
||||
end: currentForm.end || null,
|
||||
shipments: currentForm.shipments || [],
|
||||
productor_id: String(currentForm.productor.id),
|
||||
referer_id: String(currentForm.referer.id)
|
||||
});
|
||||
}
|
||||
}, [currentForm]);
|
||||
|
||||
const usersSelect = useMemo(() => {
|
||||
return users?.map(user => ({value: String(user.id), label: `${user.name}`}))
|
||||
}, [users])
|
||||
|
||||
const productorsSelect = useMemo(() => {
|
||||
return productors?.map(prod => ({value: String(prod.id), label: `${prod.name}`}))
|
||||
}, [productors])
|
||||
|
||||
const [openedShipents, { toggle: toggleShipments }] = useDisclosure(true);
|
||||
|
||||
const editShipmentElement = useCallback((index: number, shipment: ShipmentInputs) => {
|
||||
form.setFieldValue('shipments', (prev) => {
|
||||
return prev.map((elem, id) => {
|
||||
if (id === index)
|
||||
return {...shipment}
|
||||
return elem;
|
||||
})
|
||||
});
|
||||
}, [form])
|
||||
|
||||
const deleteShipmentElement = useCallback((index: number) => {
|
||||
form.setFieldValue('shipments', (prev) => {
|
||||
return prev.filter((_, i) => i === index)
|
||||
});
|
||||
}, [form])
|
||||
|
||||
return (
|
||||
<Modal
|
||||
opened={opened}
|
||||
onClose={onClose}
|
||||
title={currentForm ? t("edit form") : t('create form')}
|
||||
>
|
||||
<TextInput
|
||||
label={t("form name")}
|
||||
placeholder={t("form name")}
|
||||
radius="sm"
|
||||
withAsterisk
|
||||
{...form.getInputProps('name')}
|
||||
/>
|
||||
<TextInput
|
||||
label={t("contact season")}
|
||||
placeholder={t("contact season")}
|
||||
radius="sm"
|
||||
withAsterisk
|
||||
{...form.getInputProps('season')}
|
||||
/>
|
||||
<DatePickerInput
|
||||
label={t("start date")}
|
||||
placeholder={t("start date")}
|
||||
withAsterisk
|
||||
{...form.getInputProps('start')}
|
||||
/>
|
||||
<DatePickerInput
|
||||
label={t("end date")}
|
||||
placeholder={t("end date")}
|
||||
withAsterisk
|
||||
{...form.getInputProps('end')}
|
||||
/>
|
||||
<Select
|
||||
label={t("referer")}
|
||||
placeholder={t("referer")}
|
||||
nothingFoundMessage={t("nothing found")}
|
||||
withAsterisk
|
||||
clearable
|
||||
allowDeselect
|
||||
searchable
|
||||
data={usersSelect || []}
|
||||
{...form.getInputProps('referer_id')}
|
||||
/>
|
||||
<Select
|
||||
label={t("productor")}
|
||||
placeholder={t("productor")}
|
||||
nothingFoundMessage={t("nothing found")}
|
||||
withAsterisk
|
||||
clearable
|
||||
allowDeselect
|
||||
searchable
|
||||
data={productorsSelect || []}
|
||||
{...form.getInputProps('productor_id')}
|
||||
/>
|
||||
<Group align="end">
|
||||
<NumberInput
|
||||
label={t("number of shipment")}
|
||||
placeholder={t("number of shipment")}
|
||||
radius="sm"
|
||||
withAsterisk
|
||||
flex="2"
|
||||
value={form.getValues().shipments.length}
|
||||
defaultValue={form.getValues().shipments.length}
|
||||
onChange={(value: number | string) => {
|
||||
const target = Number(value);
|
||||
form.setFieldValue('shipments', (prev) => {
|
||||
const itemsToAdd = Array.from(
|
||||
{length: target - prev.length},
|
||||
() => ({name: "", date: "", form_id: null, id: null})
|
||||
);
|
||||
return (
|
||||
target > prev.length ?
|
||||
[...prev, ...itemsToAdd] :
|
||||
prev.slice(0, target)
|
||||
);
|
||||
})
|
||||
}}
|
||||
/>
|
||||
<ActionIcon onClick={toggleShipments} disabled={form.getValues().shipments.length === 0}>
|
||||
{openedShipents ? <IconChevronUp/> : <IconChevronDown/>}
|
||||
</ActionIcon>
|
||||
</Group>
|
||||
<Collapse in={openedShipents}>
|
||||
{
|
||||
form.getValues().shipments.map((value, index) =>
|
||||
<ShipmentForm
|
||||
key={index}
|
||||
index={index}
|
||||
setShipmentElement={editShipmentElement}
|
||||
deleteShipmentElement={deleteShipmentElement}
|
||||
shipment={value}
|
||||
/>
|
||||
)
|
||||
}
|
||||
</Collapse>
|
||||
<Group mt="sm" justify="space-between">
|
||||
<Button
|
||||
variant="filled"
|
||||
color="red"
|
||||
aria-label={t("cancel")}
|
||||
leftSection={<IconCancel/>}
|
||||
onClick={() => {
|
||||
form.reset();
|
||||
onClose();
|
||||
}}
|
||||
>{t("cancel")}</Button>
|
||||
<Button
|
||||
variant="filled"
|
||||
aria-label={currentForm ? t("edit form") : t('create form')}
|
||||
onClick={() => handleSubmit(form.getValues(), currentForm?.id)}
|
||||
>{currentForm ? t("edit form") : t('create form')}</Button>
|
||||
</Group>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
63
frontend/src/components/ShipmentForm/index.tsx
Normal file
63
frontend/src/components/ShipmentForm/index.tsx
Normal file
@@ -0,0 +1,63 @@
|
||||
import { ActionIcon, Group, TextInput, Tooltip } from "@mantine/core";
|
||||
import { DatePickerInput } from "@mantine/dates";
|
||||
import { t } from "../../config/i18n";
|
||||
import type { Shipment } from "../../services/api";
|
||||
import { IconX } from "@tabler/icons-react";
|
||||
import type { ShipmentInputs } from "../Forms/FormModal";
|
||||
|
||||
export type ShipmentFormProps = {
|
||||
index: number;
|
||||
setShipmentElement: (index: number, shipment: ShipmentInputs) => void;
|
||||
deleteShipmentElement: (index: number) => void;
|
||||
shipment: ShipmentInputs;
|
||||
}
|
||||
|
||||
export default function ShipmentForm({
|
||||
index,
|
||||
setShipmentElement,
|
||||
deleteShipmentElement,
|
||||
shipment
|
||||
}: ShipmentFormProps) {
|
||||
return (
|
||||
<Group justify="space-between" key={`shipment_${index}`}>
|
||||
<Group grow maw="80%">
|
||||
<TextInput
|
||||
label={t("shipment name")}
|
||||
placeholder={t("shipment name")}
|
||||
radius="sm"
|
||||
withAsterisk
|
||||
value={shipment.name || ""}
|
||||
onChange={(event) => {
|
||||
const value = event.target.value;
|
||||
setShipmentElement(index, {...shipment, name: value})
|
||||
}}
|
||||
/>
|
||||
<DatePickerInput
|
||||
label={t("shipment date")}
|
||||
placeholder={t("shipment date")}
|
||||
radius="sm"
|
||||
withAsterisk
|
||||
value={shipment.date}
|
||||
onChange={(event) => {
|
||||
const value = event || "";
|
||||
setShipmentElement(index, {...shipment, date: value})
|
||||
}}
|
||||
/>
|
||||
</Group>
|
||||
<Tooltip label={t("remove shipment")}>
|
||||
<ActionIcon
|
||||
flex={{base: "1", md: "0"}}
|
||||
style={{alignSelf: "flex-end"}}
|
||||
color="red"
|
||||
aria-label={t("remove shipment")}
|
||||
onClick={() => {
|
||||
deleteShipmentElement(index)
|
||||
}}
|
||||
>
|
||||
<IconX/>
|
||||
</ActionIcon>
|
||||
</Tooltip>
|
||||
</Group>
|
||||
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user