feat: modules page and module editor page for mobiles

This commit is contained in:
2025-11-06 12:09:27 +04:00
parent d3270a3532
commit cc910c8495
14 changed files with 210 additions and 47 deletions

View File

@ -1,7 +1,7 @@
"use client";
import { RefObject, useMemo, useRef } from "react";
import { IconList, IconTag } from "@tabler/icons-react";
import { IconApps, IconList, IconTag } from "@tabler/icons-react";
import { SimpleGrid, Stack } from "@mantine/core";
import Action from "@/app/actions/components/Action/Action";
import mobileButtonsData from "@/app/actions/data/mobileButtonsData";
@ -28,6 +28,11 @@ const PageBody = () => {
label: "Атрибуты",
href: "/attributes",
},
{
icon: IconApps,
label: "Модули",
href: "/modules",
},
{
icon: IconTag,
label: "Теги",

View File

@ -28,7 +28,7 @@ const mobileButtonsData: LinkData[] = [
},
{
icon: IconFileBarcode,
label: "Шаблоны штрихкодов",
label: "Шаблоны ШК",
href: "/barcode-templates",
moduleName: ModuleNames.FULFILLMENT_BASE,
},

View File

@ -0,0 +1,40 @@
"use client";
import { FC, useMemo } from "react";
import useAttributesList from "@/app/module-editor/[moduleId]/hooks/useAttributesList";
import ObjectSelect, {
ObjectSelectProps,
} from "@/components/selects/ObjectSelect/ObjectSelect";
import { AttributeSchema } from "@/lib/client";
type Props = Omit<
ObjectSelectProps<AttributeSchema | null>,
"data" | "getLabelFn" | "getValueFn"
> & {
attributesToExclude?: AttributeSchema[];
};
const AttributeSelect: FC<Props> = ({ attributesToExclude, ...props }) => {
const { attributes } = useAttributesList();
const availableAttributes = useMemo(() => {
const attrIdsToExcludeSet = new Set(
attributesToExclude?.map(a => a.id)
);
return attributes.filter(a => !attrIdsToExcludeSet.has(a.id));
}, [attributes, attributesToExclude]);
return (
<ObjectSelect
searchable
placeholder={"Выберите статус"}
onClear={() => props.onChange(null)}
getLabelFn={(option: AttributeSchema) => option.label}
data={availableAttributes}
{...props}
/>
);
};
export default AttributeSelect;

View File

@ -24,7 +24,7 @@ const useAttributesTableColumns = () => {
accessor: "type.name",
render: attr =>
attr.type.type === "select"
? `Выбор "${attr.select?.label}"`
? `Выбор "${attr.select?.name}"`
: attr.type.name,
},
{

View File

@ -42,7 +42,7 @@ const ModuleAttribute: FC<Props> = ({ attribute }) => {
<>{getAttrLabelText()}</>
<Text>
Тип: {attribute.type.name}{" "}
{attribute.select && `"${attribute.select.label}"`}
{attribute.select && `"${attribute.select.name}"`}
</Text>
</Stack>
<Group

View File

@ -1,11 +1,18 @@
import React, { ReactNode } from "react";
import { Flex } from "@mantine/core";
import { modals } from "@mantine/modals";
import ModuleAttribute from "@/app/module-editor/[moduleId]/components/shared/ModuleAttribute/ModuleAttribute";
import { useModuleEditorContext } from "@/app/module-editor/[moduleId]/contexts/ModuleEditorContext";
import FormFlexRow from "@/components/ui/FormFlexRow/FormFlexRow";
import InlineButton from "@/components/ui/InlineButton/InlineButton";
import useIsMobile from "@/hooks/utils/useIsMobile";
const ModuleAttributesEditor = () => {
const { module } = useModuleEditorContext();
const {
module,
attributeActions: { addAttributeToModule },
} = useModuleEditorContext();
const isMobile = useIsMobile();
const getAttributesRows = () => {
if (!module?.attributes) return [];
@ -29,10 +36,27 @@ const ModuleAttributesEditor = () => {
return rows;
};
const onAddAttributeClick = () => {
modals.openContextModal({
modal: "addAttributeModal",
title: "Добавление атрибута",
withCloseButton: true,
innerProps: {
onSubmit: addAttributeToModule,
usedAttributes: module?.attributes ?? [],
},
});
};
return (
<Flex
gap={"xs"}
gap={isMobile ? "md" : "xs"}
direction={"column"}>
{isMobile && (
<InlineButton onClick={onAddAttributeClick}>
Добавить атрибут
</InlineButton>
)}
{getAttributesRows()}
</Flex>
);

View File

@ -1,6 +1,7 @@
import { Button, Flex, Group, Textarea, TextInput } from "@mantine/core";
import { useForm } from "@mantine/form";
import { useModuleEditorContext } from "@/app/module-editor/[moduleId]/contexts/ModuleEditorContext";
import useIsMobile from "@/hooks/utils/useIsMobile";
import { ModuleInfo } from "../../../hooks/useModulesActions";
const ModuleCommonInfoEditor = () => {
@ -8,6 +9,7 @@ const ModuleCommonInfoEditor = () => {
module,
moduleActions: { updateCommonInfo },
} = useModuleEditorContext();
const isMobile = useIsMobile();
const form = useForm<ModuleInfo>({
initialValues: module ?? {
@ -39,6 +41,7 @@ const ModuleCommonInfoEditor = () => {
<Group>
<Button
variant={"default"}
w={isMobile ? "100%" : "auto"}
disabled={!form.isDirty()}
type={"submit"}>
Сохранить

View File

@ -7,17 +7,34 @@ import ModuleAttributesEditor from "@/app/module-editor/[moduleId]/components/sh
import ModuleCommonInfoEditor from "@/app/module-editor/[moduleId]/components/shared/ModuleCommonInfoEditor/ModuleCommonInfoEditor";
import { useModuleEditorContext } from "@/app/module-editor/[moduleId]/contexts/ModuleEditorContext";
import PageBlock from "@/components/layout/PageBlock/PageBlock";
import useIsMobile from "@/hooks/utils/useIsMobile";
const PageBody = () => {
const { module } = useModuleEditorContext();
const isMobile = useIsMobile();
if (!module) return;
return (
<Stack h={"100%"}>
<PageBlock
style={{ flex: 1, minHeight: 0 }}
fullScreenMobile>
const renderMobile = () => (
<Flex
m={"xs"}
gap={"xs"}
flex={2}
direction={"column"}>
<Fieldset
flex={1}
legend={"Общие данные модуля"}>
{module && <ModuleCommonInfoEditor />}
</Fieldset>
<Fieldset
flex={3}
legend={"Атрибуты модуля"}>
<ModuleAttributesEditor />
</Fieldset>
</Flex>
);
const renderDesktop = () => (
<Flex
w={"100%"}
h={"100%"}
@ -53,6 +70,14 @@ const PageBody = () => {
</Fieldset>
</Flex>
</Flex>
);
return (
<Stack h={"100%"}>
<PageBlock
style={{ flex: 1, minHeight: 0 }}
fullScreenMobile>
{isMobile ? renderMobile() : renderDesktop()}
</PageBlock>
</Stack>
);

View File

@ -5,7 +5,10 @@ import useAttributesCrud from "@/app/module-editor/[moduleId]/hooks/useAttribute
import { AttributeSchema, ModuleSchemaOutput } from "@/lib/client";
export type AttributesActions = {
addAttributeToModule: (attribute: AttributeSchema) => void;
addAttributeToModule: (
attribute: AttributeSchema,
onSuccess?: () => void
) => void;
removeAttributeFromModule: (attribute: AttributeSchema) => void;
onEditAttributeLabel: (attribute: AttributeSchema) => void;
onCreate: () => void;
@ -22,8 +25,10 @@ type Props = {
const useAttributesActions = (props: Props): AttributesActions => {
const crud = useAttributesCrud(props);
const addAttributeToModule = (attribute: AttributeSchema) =>
crud.onToggleAttributeInModule(attribute, true);
const addAttributeToModule = (
attribute: AttributeSchema,
onSuccess?: () => void
) => crud.onToggleAttributeInModule(attribute, true, onSuccess);
const removeAttributeFromModule = (attribute: AttributeSchema) => {
modals.openConfirmModal({

View File

@ -26,7 +26,8 @@ type Props = {
type AttributesCrud = {
onToggleAttributeInModule: (
attribute: AttributeSchema,
isAdding: boolean
isAdding: boolean,
onSuccess?: () => void,
) => void;
onUpdateLabel: (attributeId: number, name: string) => void;
onCreate: (attribute: CreateAttributeSchema) => void;
@ -77,7 +78,8 @@ const useAttributesCrud = ({
const onToggleAttributeInModule = (
attribute: AttributeSchema,
isAdding: boolean
isAdding: boolean,
onSuccess?: () => void,
) => {
if (!module) return;
const mutation = isAdding
@ -97,6 +99,7 @@ const useAttributesCrud = ({
refetchModule && refetchModule();
refetchAttributes();
removeGetDealModuleAttrQuery();
onSuccess && onSuccess();
},
}
);

View File

@ -0,0 +1,51 @@
"use client";
import { Button, Stack } from "@mantine/core";
import { useForm } from "@mantine/form";
import { ContextModalProps } from "@mantine/modals";
import AttributeSelect from "@/app/module-editor/[moduleId]/components/mobile/AttributeSelect/AttributeSelect";
import { AttributeSchema } from "@/lib/client";
type Props = {
onSubmit: (attribute: AttributeSchema, onSuccess?: () => void) => void;
usedAttributes: AttributeSchema[];
};
type AddAttrForm = {
attribute?: AttributeSchema;
};
const AddAttributeModal = ({
context,
id,
innerProps,
}: ContextModalProps<Props>) => {
const form = useForm<AddAttrForm>({
validate: {
attribute: attribute => !attribute && "Атрибут не выбран",
},
});
const onClose = () => context.closeContextModal(id);
return (
<form
onSubmit={form.onSubmit(values =>
innerProps.onSubmit(values.attribute!, onClose)
)}>
<Stack gap={"md"}>
<AttributeSelect
attributesToExclude={innerProps.usedAttributes}
{...form.getInputProps("attribute")}
/>
<Button
type={"submit"}
variant={"default"}>
Добавить
</Button>
</Stack>
</form>
);
};
export default AddAttributeModal;

View File

@ -25,23 +25,26 @@ const useAttributesInnerTableColumns = () => {
accessor: "type.name",
render: attr =>
attr.type.type === "select"
? `Выбор "${attr.select?.label}"`
? `Выбор "${attr.select?.name}"`
: attr.type.name,
},
{
title: "Значение по умолчанию",
accessor: "defaultValue",
render: attr => <AttributeDefaultValue attribute={attr} />,
hidden: isMobile,
},
{
title: "Синхронизировано в группе",
accessor: "isApplicableToGroup",
render: attr => renderCheck(attr.isApplicableToGroup),
hidden: isMobile,
},
{
title: "Может быть пустым",
accessor: "isNullable",
render: attr => renderCheck(attr.isNullable),
hidden: isMobile,
},
{
title: "Описаниие",

View File

@ -74,6 +74,7 @@ const useModulesTableColumns = ({
{
title: "Описание",
accessor: "description",
hidden: isMobile,
},
{
title: "Зависит от модулей",
@ -83,6 +84,7 @@ const useModulesTableColumns = ({
{module.dependsOn?.map(m => m.label).join(", ")}
</Text>
),
hidden: isMobile,
},
{
accessor: "actions",

View File

@ -5,6 +5,7 @@ import ColorPickerModal from "@/app/deals/modals/ColorPickerModal/ColorPickerMod
import DealsBoardFiltersModal from "@/app/deals/modals/DealsBoardFiltersModal/DealsBoardFiltersModal";
import DealsScheduleFiltersModal from "@/app/deals/modals/DealsScheduleFiltersModal/DealsScheduleFiltersModal";
import DealsTableFiltersModal from "@/app/deals/modals/DealsTableFiltersModal/DealsTableFiltersModal";
import AddAttributeModal from "@/app/module-editor/[moduleId]/modals/AddAttributeModal";
import AttributeEditorModal from "@/app/module-editor/[moduleId]/modals/AttributeEditorModal";
import ModuleCreatorModal from "@/app/modules/modals/ModuleCreatorModal";
import {
@ -46,4 +47,5 @@ export const modals = {
dealTagModal: DealTagModal,
attributeEditorModal: AttributeEditorModal,
moduleCreatorModal: ModuleCreatorModal,
addAttributeModal: AddAttributeModal,
};