feat: barcode templates page

This commit is contained in:
2025-10-04 10:15:58 +04:00
parent 1a2895da59
commit f641e9ef8c
24 changed files with 1358 additions and 11 deletions

View File

@ -0,0 +1,19 @@
import { FC } from "react";
import { Group } from "@mantine/core";
import InlineButton from "@/components/ui/InlineButton/InlineButton";
type Props = {
onCreateClick: () => void;
};
const BarcodeTemplatesDesktopHeader: FC<Props> = ({ onCreateClick }) => {
return (
<Group>
<InlineButton onClick={onCreateClick}>
Создать шаблон
</InlineButton>
</Group>
);
};
export default BarcodeTemplatesDesktopHeader;

View File

@ -0,0 +1,23 @@
import { FC } from "react";
import { Box } from "@mantine/core";
import InlineButton from "@/components/ui/InlineButton/InlineButton";
type Props = {
onCreateClick: () => void;
};
const BarcodeTemplatesMobileHeader: FC<Props> = ({ onCreateClick }) => {
return (
<Box
px={"xs"}
pt={"xs"}>
<InlineButton
onClick={onCreateClick}
w={"100%"}>
Создать шаблон
</InlineButton>
</Box>
);
};
export default BarcodeTemplatesMobileHeader;

View File

@ -0,0 +1,26 @@
"use client";
import { FC } from "react";
import useBarcodeTemplateAttributesList from "@/app/barcode-templates/hooks/useBarcodeTemplateAttributesList";
import ObjectMultiSelect, {
ObjectMultiSelectProps,
} from "@/components/selects/ObjectMultiSelect/ObjectMultiSelect";
import { BarcodeTemplateAttributeSchema } from "@/lib/client";
type Props = Omit<
ObjectMultiSelectProps<BarcodeTemplateAttributeSchema>,
"data" | "getLabelFn" | "getValueFn"
>;
const BarcodeTemplateAttributeMultiselect: FC<Props> = (props: Props) => {
const { barcodeTemplateAttributes } = useBarcodeTemplateAttributesList();
return (
<ObjectMultiSelect
data={barcodeTemplateAttributes}
{...props}
/>
);
};
export default BarcodeTemplateAttributeMultiselect;

View File

@ -0,0 +1,22 @@
"use client";
import useBarcodeTemplateSizesList from "@/app/barcode-templates/hooks/useBarcodeTemplateSizesList";
import ObjectSelect, {
ObjectSelectProps,
} from "@/components/selects/ObjectSelect/ObjectSelect";
import { BarcodeTemplateSizeSchema } from "@/lib/client";
type Props = Omit<ObjectSelectProps<BarcodeTemplateSizeSchema>, "data">;
const BarcodeTemplateSizeSelect = (props: Props) => {
const { barcodeTemplateSizes } = useBarcodeTemplateSizesList();
return (
<ObjectSelect
data={barcodeTemplateSizes}
getLabelFn={size => `${size.name} (${size.width}x${size.height})`}
{...props}
/>
);
};
export default BarcodeTemplateSizeSelect;

View File

@ -0,0 +1,30 @@
import { FC } from "react";
import { useBarcodeTemplatesTableColumns } from "@/app/barcode-templates/components/shared/BarcodeTemplatesTable/columns";
import BaseTable from "@/components/ui/BaseTable/BaseTable";
import useIsMobile from "@/hooks/utils/useIsMobile";
import { BarcodeTemplateSchema } from "@/lib/client";
type Props = {
items: BarcodeTemplateSchema[];
onDelete: (template: BarcodeTemplateSchema) => void;
onChange: (template: BarcodeTemplateSchema) => void;
};
const BarcodeTemplatesTable: FC<Props> = ({ items, ...props }) => {
const isMobile = useIsMobile();
const columns = useBarcodeTemplatesTableColumns(props);
return (
<BaseTable
striped
withTableBorder
records={items}
columns={columns}
groups={undefined}
verticalSpacing={"md"}
mx={isMobile ? "xs" : ""}
/>
);
};
export default BarcodeTemplatesTable;

View File

@ -0,0 +1,60 @@
import { useMemo } from "react";
import { IconCheck, IconX } from "@tabler/icons-react";
import { DataTableColumn } from "mantine-datatable";
import { Center } from "@mantine/core";
import UpdateDeleteTableActions from "@/components/ui/BaseTable/components/UpdateDeleteTableActions";
import { BarcodeTemplateSchema } from "@/lib/client";
type Props = {
onDelete: (template: BarcodeTemplateSchema) => void;
onChange: (template: BarcodeTemplateSchema) => void;
};
export const useBarcodeTemplatesTableColumns = ({
onDelete,
onChange,
}: Props) => {
return useMemo(
() =>
[
{
accessor: "actions",
title: <Center>Действия</Center>,
width: "0%",
render: template => (
<UpdateDeleteTableActions
onDelete={() => onDelete(template)}
onChange={() => onChange(template)}
/>
),
},
{
accessor: "name",
title: "Название",
},
{
accessor: "attributes",
title: "Атрибуты",
render: template => (
<>
{template.attributes
.map(attr => attr.name)
.join(", ")}
</>
),
},
{
accessor: "size.name",
title: "Размер",
render: template => `${template.size.name} (${template.size.width}x${template.size.height})`
},
{
accessor: "isDefault",
title: "По умолчанию",
render: template =>
template.isDefault ? <IconCheck /> : <IconX />,
},
] as DataTableColumn<BarcodeTemplateSchema>[],
[]
);
};

View File

@ -0,0 +1,51 @@
"use client";
import { Stack } from "@mantine/core";
import BarcodeTemplatesDesktopHeader from "@/app/barcode-templates/components/desktop/BarcodeTemplatesDesktopHeader/BarcodeTemplatesDesktopHeader";
import BarcodeTemplatesMobileHeader from "@/app/barcode-templates/components/mobile/BarcodeTemplatesMobileHeader/BarcodeTemplatesMobileHeader";
import BarcodeTemplatesTable from "@/app/barcode-templates/components/shared/BarcodeTemplatesTable/BarcodeTemplatesTable";
import useBarcodeTemplateActions from "@/app/barcode-templates/hooks/useBarcodeTemplateActions";
import { useBarcodeTemplatesCrud } from "@/app/barcode-templates/hooks/useBarcodeTemplatesCrud";
import useBarcodeTemplatesList from "@/app/barcode-templates/hooks/useBarcodeTemplatesList";
import PageBlock from "@/components/layout/PageBlock/PageBlock";
import useIsMobile from "@/hooks/utils/useIsMobile";
const PageBody = () => {
const isMobile = useIsMobile();
const { barcodeTemplates, queryKey } = useBarcodeTemplatesList();
const barcodeTemplatesCrud = useBarcodeTemplatesCrud({ queryKey });
const { onCreate, onChange } = useBarcodeTemplateActions();
return (
<Stack h={"100%"}>
{!isMobile && (
<PageBlock>
<BarcodeTemplatesDesktopHeader onCreateClick={onCreate} />
</PageBlock>
)}
<PageBlock
style={{ flex: 1, minHeight: 0 }}
fullScreenMobile>
<Stack
gap={"xs"}
h={"100%"}>
{isMobile && (
<BarcodeTemplatesMobileHeader
onCreateClick={onCreate}
/>
)}
<div style={{ flex: 1, overflow: "auto" }}>
<BarcodeTemplatesTable
items={barcodeTemplates}
onChange={onChange}
onDelete={barcodeTemplatesCrud.onDelete}
/>
</div>
</Stack>
</PageBlock>
</Stack>
);
};
export default PageBody;

View File

@ -0,0 +1,42 @@
import { modals } from "@mantine/modals";
import { useBarcodeTemplatesCrud } from "@/app/barcode-templates/hooks/useBarcodeTemplatesCrud";
import useBarcodeTemplatesList from "@/app/barcode-templates/hooks/useBarcodeTemplatesList";
import { BarcodeTemplateSchema } from "@/lib/client";
const useBarcodeTemplateActions = () => {
const { queryKey } = useBarcodeTemplatesList();
const barcodeTemplatesCrud = useBarcodeTemplatesCrud({ queryKey });
const onChange = (template: BarcodeTemplateSchema) => {
modals.openContextModal({
modal: "barcodeTemplateEditorModal",
title: "Редактирование шаблона",
withCloseButton: false,
innerProps: {
onChange: updated =>
barcodeTemplatesCrud.onUpdate(template.id, updated),
entity: template,
isEditing: true,
},
});
};
const onCreate = () => {
modals.openContextModal({
modal: "barcodeTemplateEditorModal",
title: "Создание шаблона",
withCloseButton: false,
innerProps: {
onCreate: barcodeTemplatesCrud.onCreate,
isEditing: false,
},
});
};
return {
onChange,
onCreate,
};
};
export default useBarcodeTemplateActions;

View File

@ -0,0 +1,11 @@
import { useQuery } from "@tanstack/react-query";
import { getBarcodeTemplateAttributesOptions } from "@/lib/client/@tanstack/react-query.gen";
const useBarcodeTemplateAttributesList = () => {
const { isLoading, data, refetch } = useQuery(
getBarcodeTemplateAttributesOptions()
);
return { barcodeTemplateAttributes: data?.items ?? [], refetch, isLoading };
};
export default useBarcodeTemplateAttributesList;

View File

@ -0,0 +1,12 @@
import { useQuery } from "@tanstack/react-query";
import { getBarcodeTemplateSizesOptions } from "@/lib/client/@tanstack/react-query.gen";
const useBarcodeTemplateSizesList = () => {
const { isLoading, data, refetch } = useQuery(
getBarcodeTemplateSizesOptions()
);
return { barcodeTemplateSizes: data?.items ?? [], refetch, isLoading };
};
export default useBarcodeTemplateSizesList;

View File

@ -0,0 +1,50 @@
import { useCrudOperations } from "@/hooks/cruds/baseCrud";
import {
BarcodeTemplateSchema,
CreateBarcodeTemplateSchema,
UpdateBarcodeTemplateSchema,
} from "@/lib/client";
import {
createBarcodeTemplateMutation,
deleteBarcodeTemplateMutation,
updateBarcodeTemplateMutation,
} from "@/lib/client/@tanstack/react-query.gen";
type UseBarcodeTemplateOperationsProps = {
queryKey: any[];
};
export type BarcodeTemplateCrud = {
onCreate: (template: CreateBarcodeTemplateSchema) => void;
onUpdate: (
templateId: number,
template: UpdateBarcodeTemplateSchema
) => void;
onDelete: (template: BarcodeTemplateSchema) => void;
};
export const useBarcodeTemplatesCrud = ({
queryKey,
}: UseBarcodeTemplateOperationsProps): BarcodeTemplateCrud => {
return useCrudOperations<
BarcodeTemplateSchema,
UpdateBarcodeTemplateSchema,
CreateBarcodeTemplateSchema
>({
key: "getBarcodeTemplates",
queryKey,
mutations: {
create: createBarcodeTemplateMutation(),
update: updateBarcodeTemplateMutation(),
delete: deleteBarcodeTemplateMutation(),
},
getUpdateEntity: (old, update) => ({
...old,
name: update.name ?? old.name,
attributes: update.attributes ?? old.attributes,
size: update.size ?? old.size,
isDefault: update.isDefault ?? old.isDefault,
}),
getDeleteConfirmTitle: () => "Удаление шаблона штрихкода",
});
};

View File

@ -0,0 +1,15 @@
import { useQuery } from "@tanstack/react-query";
import {
getBarcodeTemplatesOptions,
getBarcodeTemplatesQueryKey,
} from "@/lib/client/@tanstack/react-query.gen";
const useBarcodeTemplatesList = () => {
const { isLoading, data, refetch } = useQuery(getBarcodeTemplatesOptions());
const queryKey = getBarcodeTemplatesQueryKey();
return { barcodeTemplates: data?.items ?? [], queryKey, refetch, isLoading };
};
export default useBarcodeTemplatesList;

View File

@ -0,0 +1,87 @@
"use client";
import { Checkbox, Flex, TextInput } from "@mantine/core";
import { useForm } from "@mantine/form";
import { ContextModalProps } from "@mantine/modals";
import BarcodeTemplateAttributeMultiselect from "@/app/barcode-templates/components/shared/BarcodeTemplateAttributeMultiselect/BarcodeTemplateAttributeMultiselect";
import BarcodeTemplateSizeSelect from "@/app/barcode-templates/components/shared/BarcodeTemplateSizeSelect/BarcodeTemplateSizeSelect";
import {
BarcodeTemplateSchema,
CreateBarcodeTemplateSchema,
UpdateBarcodeTemplateSchema,
} from "@/lib/client";
import BaseFormModal, {
CreateEditFormProps,
} from "@/modals/base/BaseFormModal/BaseFormModal";
type Props = CreateEditFormProps<
BarcodeTemplateSchema,
CreateBarcodeTemplateSchema,
UpdateBarcodeTemplateSchema
>;
const BarcodeTemplateEditorModal = ({
context,
id,
innerProps,
}: ContextModalProps<Props>) => {
const initialValues = innerProps.isEditing
? innerProps.entity
: ({
name: "",
isDefault: false,
attributes: [],
} as Partial<CreateBarcodeTemplateSchema>);
const form = useForm({
initialValues,
validate: {
attributes: attributes =>
!attributes && "Необходимо добавить хотя бы один атрибут",
name: name =>
!name ||
(name.trim() === "" && "Необходимо ввести название шаблона"),
size: size => !size && "Необходимо выбрать размер шаблона",
},
});
return (
<BaseFormModal
{...innerProps}
closeOnSubmit
form={form}
onClose={() => context.closeContextModal(id)}>
<Flex
direction={"column"}
gap={"md"}>
<TextInput
label={"Название"}
placeholder={"Введите название шаблона"}
{...form.getInputProps("name")}
/>
<BarcodeTemplateSizeSelect
label={"Размер"}
placeholder={"Выберите размер шаблона"}
{...form.getInputProps("size")}
/>
<BarcodeTemplateAttributeMultiselect
label={"Стандартные атрибуты"}
placeholder={
!form.values.attributes?.length
? "Выберите атрибуты"
: undefined
}
{...form.getInputProps("attributes")}
/>
<Checkbox
label={"Использовать по умолчанию"}
{...form.getInputProps("isDefault", {
type: "checkbox",
})}
/>
</Flex>
</BaseFormModal>
);
};
export default BarcodeTemplateEditorModal;

View File

@ -0,0 +1,19 @@
import { Suspense } from "react";
import { Center, Loader } from "@mantine/core";
import PageBody from "@/app/barcode-templates/components/shared/PageBody/PageBody";
import PageContainer from "@/components/layout/PageContainer/PageContainer";
export default async function BarcodeTemplatesPage() {
return (
<Suspense
fallback={
<Center h="50vh">
<Loader size="lg" />
</Center>
}>
<PageContainer>
<PageBody />
</PageContainer>
</Suspense>
);
}