add shipment forms and start contract from form

This commit is contained in:
2026-02-13 01:12:42 +01:00
parent fe27595931
commit ef7403f213
20 changed files with 553 additions and 312 deletions

View File

@@ -1,72 +0,0 @@
import { Grid, NumberInput, Select, Stack, TextInput } from "@mantine/core";
import { t } from "../../config/i18n";
export type CreateProductProps = {
form: Record<string, any>;
}
export default function CreateProduct({form}: CreateProductProps) {
return (
<Stack>
<Grid>
<Grid.Col span={{ base: 12, md: 6, lg: 6 }}>
<TextInput
label={t("product name", {capfirst: true})}
placeholder={t("product name", {capfirst: true})}
radius="sm"
withAsterisk
{...form.getInputProps('name')}
/>
<NumberInput
label={t("product price", {capfirst: true})}
placeholder={t("product price", {capfirst: true})}
radius="sm"
withAsterisk
{...form.getInputProps('price')}
/>
<TextInput
label={t("product weight", {capfirst: true})}
placeholder={t("product weight", {capfirst: true})}
radius="sm"
withAsterisk
{...form.getInputProps('weight')}
/>
</Grid.Col>
<Grid.Col span={{ base: 12, md: 6, lg: 6 }}>
<Select
label={t("product type", {capfirst: true})}
placeholder={t("product type", {capfirst: true})}
radius="sm"
data={[
{value: "1", label: t("planned", {capfirst: true})},
{value: "2", label: t("reccurent", {capfirst: true})}
]}
defaultValue={"1"}
clearable
{...form.getInputProps('type')}
/>
<NumberInput
label={t("product price kg", {capfirst: true})}
placeholder={t("product price kg", {capfirst: true})}
radius="sm"
withAsterisk
{...form.getInputProps('pricekg', {capfirst: true})}
/>
<Select
label={t("product unit", {capfirst: true})}
placeholder={t("product unit", {capfirst: true})}
radius="sm"
data={[
{value: "1", label: t("grams", {capfirst: true})},
{value: "2", label: t("kilo", {capfirst: true})},
{value: "3", label: t("piece", {capfirst: true})}
]}
defaultValue={"2"}
clearable
{...form.getInputProps('unit')}
/>
</Grid.Col>
</Grid>
</Stack>
);
}

View File

@@ -0,0 +1,38 @@
import { Badge, Box, Group, Paper, Text, Title } from "@mantine/core";
import { Link } from "react-router";
import type { Form } from "@/services/resources/forms";
export type FormCardProps = {
form: Form;
}
export function FormCard({form}: FormCardProps) {
return (
<Paper
shadow="xl"
p="xl"
miw={{base: "100vw", md: "25vw", lg:"20vw"}}
>
<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>
);
}

View File

@@ -1,14 +1,11 @@
import { ActionIcon, Button, Collapse, Group, Modal, NumberInput, Select, TextInput, type ModalBaseProps } from "@mantine/core";
import { Button, Group, Modal, 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 { IconCancel } from "@tabler/icons-react";
import { getProductors, getUsers } from "@/services/api";
import { useForm } from "@mantine/form";
import { useCallback, useEffect, useMemo } from "react";
import { useDisclosure } from "@mantine/hooks";
import { useEffect, useMemo } from "react";
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;
@@ -31,7 +28,6 @@ export default function FormModal({
end: null,
productor_id: "",
referer_id: "",
shipments: [],
},
validate: {
name: (value) =>
@@ -55,7 +51,6 @@ export default function FormModal({
...currentForm,
start: currentForm.start || null,
end: currentForm.end || null,
shipments: currentForm.shipments || [],
productor_id: String(currentForm.productor.id),
referer_id: String(currentForm.referer.id)
});
@@ -70,27 +65,6 @@ export default function FormModal({
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
size="50%"
@@ -146,50 +120,6 @@ export default function FormModal({
data={productorsSelect || []}
{...form.getInputProps('productor_id')}
/>
<Group align="end">
<NumberInput
label={t("number of shipment", {capfirst: true})}
placeholder={t("number of shipment", {capfirst: true})}
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"

View File

@@ -1,10 +1,9 @@
import { ActionIcon, Table, Tooltip } from "@mantine/core";
import { useNavigate } from "react-router";
import { deleteForm, getForm} from "@/services/api";
import { deleteForm} 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";
import type { Form } from "@/services/resources/forms";
export type FormRowProps = {
form: Form;
@@ -51,60 +50,5 @@ export default function FormRow({
</Tooltip>
</Table.Td>
</Table.Tr>
// <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", {capfirst: true})}
// onClick={(e) => {
// e.stopPropagation();
// navigate(`/dashboard/forms/${form.id}/edit`);
// }}
// >
// <IconEdit/>
// </ActionIcon>
// <ActionIcon
// size={"sm"}
// aria-label={t("delete form", {capfirst: true})}
// 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>
);
}

View File

@@ -49,7 +49,7 @@ export function ProductModal({
}, [currentProduct]);
const productorsSelect = useMemo(() => {
return productors?.map(prod => ({value: String(prod.id), label: `${prod.name}`}))
return productors?.map(productor => ({value: String(productor.id), label: `${productor.name}`}))
}, [productors])
return (

View File

@@ -0,0 +1,34 @@
import { Group, MultiSelect } from "@mantine/core";
import { useMemo } from "react";
import { t } from "@/config/i18n";
export type ShipmentFiltersProps = {
names: string[];
filters: URLSearchParams;
onFilterChange: (values: string[], filter: string) => void;
}
export default function ShipmentsFilters({
names,
filters,
onFilterChange
}: ShipmentFiltersProps) {
const defaultNames = useMemo(() => {
return filters.getAll("names")
}, [filters]);
return (
<Group>
<MultiSelect
aria-label={t("filter by name", {capfirst: true})}
placeholder={t("filter by name", {capfirst: true})}
data={names}
defaultValue={defaultNames}
onChange={(values: string[]) => {
onFilterChange(values, 'names')
}}
clearable
/>
</Group>
);
}

View File

@@ -0,0 +1,115 @@
import { Button, Group, Modal, MultiSelect, Select, TextInput, type ModalBaseProps } from "@mantine/core";
import { t } from "@/config/i18n";
import { DatePickerInput } from "@mantine/dates";
import { IconCancel } from "@tabler/icons-react";
import { useForm } from "@mantine/form";
import { useEffect, useMemo } from "react";
import { shipmentToShipmentInputs, type Shipment, type ShipmentInputs } from "@/services/resources/shipments";
import { getForms, getProducts } from "@/services/api";
export type ShipmentModalProps = ModalBaseProps & {
currentShipment?: Shipment;
handleSubmit: (shipment: ShipmentInputs, id?: number) => void;
}
export default function ShipmentModal({
opened,
onClose,
currentShipment,
handleSubmit
}: ShipmentModalProps) {
const form = useForm<ShipmentInputs>({
initialValues: {
name: "",
date: null,
form_id: "",
product_ids: []
},
validate: {
name: (value) =>
!value ? `${t("a name", {capfirst: true})} ${t('is required')}` : null,
date: (value) =>
!value ? `${t("a shipment date", {capfirst: true})} ${t('is required')}` : null,
form_id: (value) =>
!value ? `${t("a form", {capfirst: true})} ${t('is required')}` : null,
}
});
useEffect(() => {
if (currentShipment) {
form.setValues(shipmentToShipmentInputs(currentShipment));
}
}, [currentShipment]);
const { data: allForms } = getForms();
const { data: allProducts } = getProducts();
const formsSelect = useMemo(() => {
return allForms?.map(form => ({value: String(form.id), label: `${form.name} ${form.season}`}))
}, [allForms]);
const productsSelect = useMemo(() => {
return allProducts?.map(product => ({value: String(product.id), label: `${product.name}`}))
}, [allProducts]);
return (
<Modal
size="50%"
opened={opened}
onClose={onClose}
title={currentShipment ? t("edit shipment") : t('create shipment')}
>
<TextInput
label={t("shipment name", {capfirst: true})}
placeholder={t("shipment name", {capfirst: true})}
radius="sm"
withAsterisk
{...form.getInputProps('name')}
/>
<DatePickerInput
label={t("shipment date", {capfirst: true})}
placeholder={t("shipment date", {capfirst: true})}
withAsterisk
{...form.getInputProps('date')}
/>
<Select
label={t("shipment form", {capfirst: true})}
placeholder={t("shipment form", {capfirst: true})}
radius="sm"
data={formsSelect || []}
clearable
withAsterisk
{...form.getInputProps('form_id')}
/>
<MultiSelect
label={t("shipment products", {capfirst: true})}
placeholder={t("shipment products", {capfirst: true})}
data={productsSelect}
clearable
{...form.getInputProps('product_ids')}
/>
<Group mt="sm" justify="space-between">
<Button
variant="filled"
color="red"
aria-label={t("cancel", {capfirst: true})}
leftSection={<IconCancel/>}
onClick={() => {
form.clearErrors();
onClose();
}}
>{t("cancel", {capfirst: true})}</Button>
<Button
variant="filled"
aria-label={currentShipment ? t("edit shipment", {capfirst: true}) : t('create shipment', {capfirst: true})}
onClick={() => {
form.validate();
if (form.isValid()) {
handleSubmit(form.getValues(), currentShipment?.id)
// form.reset();
}
}}
>{currentShipment ? t("edit shipment", {capfirst: true}) : t('create shipment', {capfirst: true})}</Button>
</Group>
</Modal>
);
}

View File

@@ -0,0 +1,51 @@
import { ActionIcon, Table, Tooltip } from "@mantine/core";
import { useNavigate } from "react-router";
import { deleteShipment} from "@/services/api";
import { IconEdit, IconX } from "@tabler/icons-react";
import { t } from "@/config/i18n";
import type { Shipment } from "@/services/resources/shipments";
export type ShipmentRowProps = {
shipment: Shipment;
}
export default function ShipmentRow({
shipment,
}: ShipmentRowProps) {
const deleteMutation = deleteShipment();
const navigate = useNavigate();
return (
<Table.Tr key={shipment.id}>
<Table.Td>{shipment.name}</Table.Td>
<Table.Td>{shipment.date}</Table.Td>
<Table.Td>{`${shipment.form.name} ${shipment.form.season}`}</Table.Td>
<Table.Td>
<Tooltip label={t("edit productor", {capfirst: true})}>
<ActionIcon
size="sm"
mr="5"
onClick={(e) => {
e.stopPropagation();
navigate(`/dashboard/shipments/${shipment.id}/edit`);
}}
>
<IconEdit/>
</ActionIcon>
</Tooltip>
<Tooltip label={t("remove productor", {capfirst: true})}>
<ActionIcon
color="red"
size="sm"
mr="5"
onClick={() => {
deleteMutation.mutate(shipment.id);
}}
>
<IconX/>
</ActionIcon>
</Tooltip>
</Table.Td>
</Table.Tr>
);
}