feat: dnd for options in select editor
This commit is contained in:
@ -43,7 +43,6 @@
|
|||||||
"next": "15.4.7",
|
"next": "15.4.7",
|
||||||
"phone": "^3.1.67",
|
"phone": "^3.1.67",
|
||||||
"react": "19.1.0",
|
"react": "19.1.0",
|
||||||
"react-dom": "19.1.0",
|
|
||||||
"react-imask": "^7.6.1",
|
"react-imask": "^7.6.1",
|
||||||
"react-redux": "^9.2.0",
|
"react-redux": "^9.2.0",
|
||||||
"redux-persist": "^6.0.0",
|
"redux-persist": "^6.0.0",
|
||||||
|
|||||||
@ -0,0 +1,49 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import React, { FC } from "react";
|
||||||
|
import { Drawer } from "@mantine/core";
|
||||||
|
import EditorBody from "@/app/attributes/drawers/AttrSelectEditorDrawer/components/EditorBody";
|
||||||
|
import { SelectEditorContextProvider } from "@/app/attributes/drawers/AttrSelectEditorDrawer/contexts/SelectEditorContext";
|
||||||
|
import { DrawerProps } from "@/drawers/types";
|
||||||
|
import useIsMobile from "@/hooks/utils/useIsMobile";
|
||||||
|
import { AttrSelectSchema, UpdateAttrSelectSchema } from "@/lib/client";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
select: AttrSelectSchema;
|
||||||
|
onSelectChange: (
|
||||||
|
values: UpdateAttrSelectSchema,
|
||||||
|
onSuccess: () => void
|
||||||
|
) => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
const AttrSelectEditorDrawer: FC<DrawerProps<Props>> = ({
|
||||||
|
onClose,
|
||||||
|
opened,
|
||||||
|
props,
|
||||||
|
}) => {
|
||||||
|
const isMobile = useIsMobile();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Drawer
|
||||||
|
size={isMobile ? "100%" : "30%"}
|
||||||
|
title={"Редактирование справочника"}
|
||||||
|
position={"left"}
|
||||||
|
onClose={onClose}
|
||||||
|
removeScrollProps={{ allowPinchZoom: true }}
|
||||||
|
withCloseButton
|
||||||
|
opened={opened}
|
||||||
|
trapFocus={false}
|
||||||
|
styles={{
|
||||||
|
body: {
|
||||||
|
display: "flex",
|
||||||
|
flexDirection: "column",
|
||||||
|
},
|
||||||
|
}}>
|
||||||
|
<SelectEditorContextProvider {...props}>
|
||||||
|
<EditorBody />
|
||||||
|
</SelectEditorContextProvider>
|
||||||
|
</Drawer>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default AttrSelectEditorDrawer;
|
||||||
@ -1,6 +1,6 @@
|
|||||||
import { Button, Flex, TextInput } from "@mantine/core";
|
import { Button, Flex, TextInput } from "@mantine/core";
|
||||||
import { useForm } from "@mantine/form";
|
import { useForm } from "@mantine/form";
|
||||||
import { useSelectEditorContext } from "@/app/attributes/modals/AttrSelectEditorModal/contexts/SelectEditorContext";
|
import { useSelectEditorContext } from "@/app/attributes/drawers/AttrSelectEditorDrawer/contexts/SelectEditorContext";
|
||||||
import { UpdateAttrSelectSchema } from "@/lib/client";
|
import { UpdateAttrSelectSchema } from "@/lib/client";
|
||||||
|
|
||||||
const CommonInfoEditor = () => {
|
const CommonInfoEditor = () => {
|
||||||
@ -1,6 +1,6 @@
|
|||||||
import { IconCheck } from "@tabler/icons-react";
|
import { IconCheck } from "@tabler/icons-react";
|
||||||
import { Flex, TextInput } from "@mantine/core";
|
import { Flex, TextInput } from "@mantine/core";
|
||||||
import { useSelectEditorContext } from "@/app/attributes/modals/AttrSelectEditorModal/contexts/SelectEditorContext";
|
import { useSelectEditorContext } from "@/app/attributes/drawers/AttrSelectEditorDrawer/contexts/SelectEditorContext";
|
||||||
import ActionIconWithTip from "@/components/ui/ActionIconWithTip/ActionIconWithTip";
|
import ActionIconWithTip from "@/components/ui/ActionIconWithTip/ActionIconWithTip";
|
||||||
import InlineButton from "@/components/ui/InlineButton/InlineButton";
|
import InlineButton from "@/components/ui/InlineButton/InlineButton";
|
||||||
|
|
||||||
@ -16,14 +16,20 @@ const CreateOptionButton = () => {
|
|||||||
|
|
||||||
if (!isCreatingOption) {
|
if (!isCreatingOption) {
|
||||||
return (
|
return (
|
||||||
<InlineButton onClick={onStartCreating}>
|
<Flex flex={1}>
|
||||||
Добавить опцию
|
<InlineButton
|
||||||
</InlineButton>
|
fullWidth
|
||||||
|
onClick={onStartCreating}>
|
||||||
|
Добавить опцию
|
||||||
|
</InlineButton>
|
||||||
|
</Flex>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Flex gap={"xs"}>
|
<Flex
|
||||||
|
gap={"xs"}
|
||||||
|
flex={1}>
|
||||||
<TextInput
|
<TextInput
|
||||||
{...createOptionForm.getInputProps("name")}
|
{...createOptionForm.getInputProps("name")}
|
||||||
flex={1}
|
flex={1}
|
||||||
@ -0,0 +1,22 @@
|
|||||||
|
import { Divider, Flex } from "@mantine/core";
|
||||||
|
import CommonInfoEditor from "@/app/attributes/drawers/AttrSelectEditorDrawer/components/CommonInfoEditor";
|
||||||
|
import CreateOptionButton from "@/app/attributes/drawers/AttrSelectEditorDrawer/components/CreateOptionButton";
|
||||||
|
import OptionsTable from "@/app/attributes/drawers/AttrSelectEditorDrawer/components/OptionsTable";
|
||||||
|
|
||||||
|
const EditorBody = () => {
|
||||||
|
return (
|
||||||
|
<Flex
|
||||||
|
gap={"xs"}
|
||||||
|
direction={"column"}>
|
||||||
|
<CommonInfoEditor />
|
||||||
|
<Divider
|
||||||
|
label={"Опции"}
|
||||||
|
my={"xs"}
|
||||||
|
/>
|
||||||
|
<CreateOptionButton />
|
||||||
|
<OptionsTable />
|
||||||
|
</Flex>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default EditorBody;
|
||||||
@ -0,0 +1,82 @@
|
|||||||
|
import React, { FC, ReactNode } from "react";
|
||||||
|
import { IconCheck, IconEdit, IconTrash } from "@tabler/icons-react";
|
||||||
|
import { Divider, Flex, Group, Stack, TextInput } from "@mantine/core";
|
||||||
|
import { useSelectEditorContext } from "@/app/attributes/drawers/AttrSelectEditorDrawer/contexts/SelectEditorContext";
|
||||||
|
import ActionIconWithTip from "@/components/ui/ActionIconWithTip/ActionIconWithTip";
|
||||||
|
import { AttrOptionSchema } from "@/lib/client";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
option: AttrOptionSchema;
|
||||||
|
renderDraggable?: (item: AttrOptionSchema) => ReactNode;
|
||||||
|
};
|
||||||
|
|
||||||
|
const OptionTableRow: FC<Props> = ({ option, renderDraggable }) => {
|
||||||
|
const {
|
||||||
|
optionsActions: {
|
||||||
|
onStartEditing,
|
||||||
|
onFinishEditing,
|
||||||
|
onDelete,
|
||||||
|
editingOptionsData,
|
||||||
|
setEditingOptionsData,
|
||||||
|
},
|
||||||
|
} = useSelectEditorContext();
|
||||||
|
|
||||||
|
const onChange = (
|
||||||
|
e: React.ChangeEvent<HTMLInputElement>,
|
||||||
|
optionId: number
|
||||||
|
) => {
|
||||||
|
setEditingOptionsData(prev => {
|
||||||
|
prev.set(optionId, e.currentTarget.value);
|
||||||
|
return new Map(prev);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Stack
|
||||||
|
gap={"xs"}
|
||||||
|
mt={"xs"}>
|
||||||
|
<Group
|
||||||
|
wrap={"nowrap"}
|
||||||
|
justify={"space-between"}>
|
||||||
|
<Group wrap={"nowrap"}>
|
||||||
|
{renderDraggable && renderDraggable(option)}
|
||||||
|
{editingOptionsData.has(option.id) ? (
|
||||||
|
<TextInput
|
||||||
|
value={editingOptionsData.get(option.id)}
|
||||||
|
onChange={e => onChange(e, option.id)}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
option.name
|
||||||
|
)}
|
||||||
|
</Group>
|
||||||
|
<Flex
|
||||||
|
justify={"center"}
|
||||||
|
gap={"xs"}>
|
||||||
|
{editingOptionsData.has(option.id) ? (
|
||||||
|
<ActionIconWithTip
|
||||||
|
onClick={() => onFinishEditing(option)}
|
||||||
|
tipLabel={"Сохранить"}>
|
||||||
|
<IconCheck />
|
||||||
|
</ActionIconWithTip>
|
||||||
|
) : (
|
||||||
|
<ActionIconWithTip
|
||||||
|
onClick={() => onStartEditing(option)}
|
||||||
|
tipLabel={"Редактировать"}>
|
||||||
|
<IconEdit />
|
||||||
|
</ActionIconWithTip>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<ActionIconWithTip
|
||||||
|
color={"red"}
|
||||||
|
onClick={() => onDelete(option)}
|
||||||
|
tipLabel={"Удалить"}>
|
||||||
|
<IconTrash />
|
||||||
|
</ActionIconWithTip>
|
||||||
|
</Flex>
|
||||||
|
</Group>
|
||||||
|
<Divider />
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default OptionTableRow;
|
||||||
@ -0,0 +1,38 @@
|
|||||||
|
import React from "react";
|
||||||
|
import { IconGripVertical } from "@tabler/icons-react";
|
||||||
|
import { Box, Divider, Stack } from "@mantine/core";
|
||||||
|
import OptionTableRow from "@/app/attributes/drawers/AttrSelectEditorDrawer/components/OptionTableRow";
|
||||||
|
import { useSelectEditorContext } from "@/app/attributes/drawers/AttrSelectEditorDrawer/contexts/SelectEditorContext";
|
||||||
|
import SortableDnd from "@/components/dnd/SortableDnd";
|
||||||
|
|
||||||
|
const OptionsTable = () => {
|
||||||
|
const { options } = useSelectEditorContext();
|
||||||
|
const { onDragEnd } = useSelectEditorContext();
|
||||||
|
|
||||||
|
const renderDraggable = () => (
|
||||||
|
<Box p={"xs"}>
|
||||||
|
<IconGripVertical />
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Stack gap={0}>
|
||||||
|
<Divider />
|
||||||
|
<SortableDnd
|
||||||
|
initialItems={options}
|
||||||
|
onDragEnd={onDragEnd}
|
||||||
|
renderItem={(item, renderDraggable) => (
|
||||||
|
<OptionTableRow
|
||||||
|
option={item}
|
||||||
|
renderDraggable={renderDraggable}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
renderDraggable={renderDraggable}
|
||||||
|
dragHandleStyle={{ width: "auto" }}
|
||||||
|
vertical
|
||||||
|
/>
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default OptionsTable;
|
||||||
@ -1,6 +1,6 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import useAttrOptionsList from "@/app/attributes/modals/AttrSelectEditorModal/hooks/useAttrOptionsList";
|
import useAttrOptionsList from "@/app/attributes/drawers/AttrSelectEditorDrawer/hooks/useAttrOptionsList";
|
||||||
import {
|
import {
|
||||||
AttrOptionSchema,
|
AttrOptionSchema,
|
||||||
AttrSelectSchema,
|
AttrSelectSchema,
|
||||||
@ -15,6 +15,7 @@ type SelectEditorContextState = {
|
|||||||
onSelectChange: (values: UpdateAttrSelectSchema) => void;
|
onSelectChange: (values: UpdateAttrSelectSchema) => void;
|
||||||
options: AttrOptionSchema[];
|
options: AttrOptionSchema[];
|
||||||
optionsActions: OptionsActions;
|
optionsActions: OptionsActions;
|
||||||
|
onDragEnd: (itemId: number, newLexorank: string) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
@ -31,7 +32,7 @@ const useSelectEditorContextState = ({
|
|||||||
}: Props): SelectEditorContextState => {
|
}: Props): SelectEditorContextState => {
|
||||||
const { options, queryKey } = useAttrOptionsList({ selectId: select.id });
|
const { options, queryKey } = useAttrOptionsList({ selectId: select.id });
|
||||||
|
|
||||||
const optionsActions = useOptionsActions({ queryKey, select });
|
const optionsActions = useOptionsActions({ queryKey, select, options });
|
||||||
|
|
||||||
const onSelectChangeWithMsg = (values: UpdateAttrSelectSchema) => {
|
const onSelectChangeWithMsg = (values: UpdateAttrSelectSchema) => {
|
||||||
onSelectChange(values, () => {
|
onSelectChange(values, () => {
|
||||||
@ -41,11 +42,16 @@ const useSelectEditorContextState = ({
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const onDragEnd = (itemId: number, newLexorank: string) => {
|
||||||
|
optionsActions.onUpdate(itemId, { lexorank: newLexorank });
|
||||||
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
select,
|
select,
|
||||||
onSelectChange: onSelectChangeWithMsg,
|
onSelectChange: onSelectChangeWithMsg,
|
||||||
options,
|
options,
|
||||||
optionsActions,
|
optionsActions,
|
||||||
|
onDragEnd,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -1,3 +1,4 @@
|
|||||||
|
import { LexoRank } from "lexorank";
|
||||||
import { useCrudOperations } from "@/hooks/cruds/baseCrud";
|
import { useCrudOperations } from "@/hooks/cruds/baseCrud";
|
||||||
import {
|
import {
|
||||||
AttrOptionSchema,
|
AttrOptionSchema,
|
||||||
@ -9,9 +10,12 @@ import {
|
|||||||
deleteAttrOptionMutation,
|
deleteAttrOptionMutation,
|
||||||
updateAttrOptionMutation,
|
updateAttrOptionMutation,
|
||||||
} from "@/lib/client/@tanstack/react-query.gen";
|
} from "@/lib/client/@tanstack/react-query.gen";
|
||||||
|
import { getNewLexorank } from "@/utils/lexorank/generation";
|
||||||
|
import { getMaxByLexorank } from "@/utils/lexorank/max";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
queryKey: any[];
|
queryKey: any[];
|
||||||
|
options: AttrOptionSchema[];
|
||||||
};
|
};
|
||||||
|
|
||||||
export type AttrOptionsCrud = {
|
export type AttrOptionsCrud = {
|
||||||
@ -27,7 +31,10 @@ export type AttrOptionsCrud = {
|
|||||||
onDelete: (option: AttrOptionSchema, onSuccess?: () => void) => void;
|
onDelete: (option: AttrOptionSchema, onSuccess?: () => void) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const useAttrOptionsCrud = ({ queryKey }: Props): AttrOptionsCrud => {
|
export const useAttrOptionsCrud = ({
|
||||||
|
queryKey,
|
||||||
|
options,
|
||||||
|
}: Props): AttrOptionsCrud => {
|
||||||
return useCrudOperations<
|
return useCrudOperations<
|
||||||
AttrOptionSchema,
|
AttrOptionSchema,
|
||||||
UpdateAttrOptionSchema,
|
UpdateAttrOptionSchema,
|
||||||
@ -40,13 +47,21 @@ export const useAttrOptionsCrud = ({ queryKey }: Props): AttrOptionsCrud => {
|
|||||||
update: updateAttrOptionMutation(),
|
update: updateAttrOptionMutation(),
|
||||||
delete: deleteAttrOptionMutation(),
|
delete: deleteAttrOptionMutation(),
|
||||||
},
|
},
|
||||||
getCreateEntity: data => ({
|
getCreateEntity: data => {
|
||||||
name: data.name!,
|
const lastOption = getMaxByLexorank(options);
|
||||||
selectId: data.selectId!,
|
const newLexorank = getNewLexorank(
|
||||||
}),
|
lastOption ? LexoRank.parse(lastOption.lexorank) : null
|
||||||
|
);
|
||||||
|
return {
|
||||||
|
name: data.name!,
|
||||||
|
selectId: data.selectId!,
|
||||||
|
lexorank: newLexorank.toString(),
|
||||||
|
};
|
||||||
|
},
|
||||||
getUpdateEntity: (old, update) => ({
|
getUpdateEntity: (old, update) => ({
|
||||||
...old,
|
...old,
|
||||||
name: update.name ?? old.name,
|
name: update.name ?? old.name,
|
||||||
|
lexorank: update.lexorank ?? old.lexorank,
|
||||||
}),
|
}),
|
||||||
getDeleteConfirmTitle: () => "Удаление опции",
|
getDeleteConfirmTitle: () => "Удаление опции",
|
||||||
});
|
});
|
||||||
@ -4,6 +4,7 @@ import {
|
|||||||
getAttrOptionsOptions,
|
getAttrOptionsOptions,
|
||||||
getAttrOptionsQueryKey,
|
getAttrOptionsQueryKey,
|
||||||
} from "@/lib/client/@tanstack/react-query.gen";
|
} from "@/lib/client/@tanstack/react-query.gen";
|
||||||
|
import { sortByLexorank } from "@/utils/lexorank/sort";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
selectId: number;
|
selectId: number;
|
||||||
@ -27,7 +28,7 @@ const useAttrOptionsList = ({ selectId }: Props) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
options: data?.items ?? [],
|
options: sortByLexorank(data?.items ?? []),
|
||||||
setOptions,
|
setOptions,
|
||||||
refetch,
|
refetch,
|
||||||
queryKey,
|
queryKey,
|
||||||
@ -1,16 +1,18 @@
|
|||||||
import { Dispatch, SetStateAction, useState } from "react";
|
import { Dispatch, SetStateAction, useState } from "react";
|
||||||
import { useForm, UseFormReturnType } from "@mantine/form";
|
import { useForm, UseFormReturnType } from "@mantine/form";
|
||||||
import { useAttrOptionsCrud } from "@/app/attributes/modals/AttrSelectEditorModal/hooks/useAttrOptionsCrud";
|
|
||||||
import {
|
import {
|
||||||
AttrOptionSchema,
|
AttrOptionSchema,
|
||||||
AttrSelectSchema,
|
AttrSelectSchema,
|
||||||
CreateAttrOptionSchema,
|
CreateAttrOptionSchema,
|
||||||
|
UpdateAttrOptionSchema,
|
||||||
} from "@/lib/client";
|
} from "@/lib/client";
|
||||||
import { notifications } from "@/lib/notifications";
|
import { notifications } from "@/lib/notifications";
|
||||||
|
import { useAttrOptionsCrud } from "@/app/attributes/drawers/AttrSelectEditorDrawer/hooks/useAttrOptionsCrud";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
queryKey: any[];
|
queryKey: any[];
|
||||||
select: AttrSelectSchema;
|
select: AttrSelectSchema;
|
||||||
|
options: AttrOptionSchema[];
|
||||||
};
|
};
|
||||||
|
|
||||||
export type OptionsActions = {
|
export type OptionsActions = {
|
||||||
@ -22,10 +24,11 @@ export type OptionsActions = {
|
|||||||
setEditingOptionsData: Dispatch<SetStateAction<Map<number, string>>>;
|
setEditingOptionsData: Dispatch<SetStateAction<Map<number, string>>>;
|
||||||
onStartEditing: (option: AttrOptionSchema) => void;
|
onStartEditing: (option: AttrOptionSchema) => void;
|
||||||
onFinishEditing: (option: AttrOptionSchema) => void;
|
onFinishEditing: (option: AttrOptionSchema) => void;
|
||||||
|
onUpdate: (optionId: number, data: UpdateAttrOptionSchema) => void;
|
||||||
onDelete: (option: AttrOptionSchema) => void;
|
onDelete: (option: AttrOptionSchema) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
const useOptionsActions = ({ queryKey, select }: Props) => {
|
const useOptionsActions = ({ queryKey, select, options }: Props) => {
|
||||||
const [isCreatingOption, setIsCreatingOption] = useState<boolean>(false);
|
const [isCreatingOption, setIsCreatingOption] = useState<boolean>(false);
|
||||||
const [editingOptionsData, setEditingOptionsData] = useState<
|
const [editingOptionsData, setEditingOptionsData] = useState<
|
||||||
Map<number, string>
|
Map<number, string>
|
||||||
@ -34,6 +37,7 @@ const useOptionsActions = ({ queryKey, select }: Props) => {
|
|||||||
const createOptionForm = useForm<CreateAttrOptionSchema>({
|
const createOptionForm = useForm<CreateAttrOptionSchema>({
|
||||||
initialValues: {
|
initialValues: {
|
||||||
name: "",
|
name: "",
|
||||||
|
lexorank: "",
|
||||||
selectId: select.id,
|
selectId: select.id,
|
||||||
},
|
},
|
||||||
validate: {
|
validate: {
|
||||||
@ -41,7 +45,7 @@ const useOptionsActions = ({ queryKey, select }: Props) => {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const optionCrud = useAttrOptionsCrud({ queryKey });
|
const optionCrud = useAttrOptionsCrud({ queryKey, options });
|
||||||
|
|
||||||
const onStartCreating = () => {
|
const onStartCreating = () => {
|
||||||
setIsCreatingOption(true);
|
setIsCreatingOption(true);
|
||||||
@ -69,7 +73,7 @@ const useOptionsActions = ({ queryKey, select }: Props) => {
|
|||||||
notifications.error({ message: "Название не может быть пустым" });
|
notifications.error({ message: "Название не может быть пустым" });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
optionCrud.onUpdate(option.id, { name: newName }, () => {
|
optionCrud.onUpdate(option.id, { ...option, name: newName }, () => {
|
||||||
notifications.success({ message: "Опция сохранена" });
|
notifications.success({ message: "Опция сохранена" });
|
||||||
setEditingOptionsData(prev => {
|
setEditingOptionsData(prev => {
|
||||||
prev.delete(option.id);
|
prev.delete(option.id);
|
||||||
@ -84,6 +88,8 @@ const useOptionsActions = ({ queryKey, select }: Props) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const onUpdate = optionCrud.onUpdate;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
isCreatingOption,
|
isCreatingOption,
|
||||||
createOptionForm,
|
createOptionForm,
|
||||||
@ -94,6 +100,7 @@ const useOptionsActions = ({ queryKey, select }: Props) => {
|
|||||||
onStartEditing,
|
onStartEditing,
|
||||||
onFinishEditing,
|
onFinishEditing,
|
||||||
onDelete,
|
onDelete,
|
||||||
|
onUpdate,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -1,4 +1,5 @@
|
|||||||
import { modals } from "@mantine/modals";
|
import { modals } from "@mantine/modals";
|
||||||
|
import { useDrawersContext } from "@/drawers/DrawersContext";
|
||||||
import { useAttrSelectsCrud } from "@/hooks/cruds/useSelectsCrud";
|
import { useAttrSelectsCrud } from "@/hooks/cruds/useSelectsCrud";
|
||||||
import { AttrSelectSchema } from "@/lib/client";
|
import { AttrSelectSchema } from "@/lib/client";
|
||||||
|
|
||||||
@ -14,6 +15,7 @@ export type SelectsActions = {
|
|||||||
|
|
||||||
const useSelectsActions = (props: Props): SelectsActions => {
|
const useSelectsActions = (props: Props): SelectsActions => {
|
||||||
const attrSelectsCrud = useAttrSelectsCrud(props);
|
const attrSelectsCrud = useAttrSelectsCrud(props);
|
||||||
|
const { openDrawer } = useDrawersContext();
|
||||||
|
|
||||||
const onCreate = () => {
|
const onCreate = () => {
|
||||||
modals.openContextModal({
|
modals.openContextModal({
|
||||||
@ -26,10 +28,9 @@ const useSelectsActions = (props: Props): SelectsActions => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const onUpdate = (select: AttrSelectSchema) => {
|
const onUpdate = (select: AttrSelectSchema) => {
|
||||||
modals.openContextModal({
|
openDrawer({
|
||||||
modal: "attrSelectEditorModal",
|
key: "attrSelectEditorDrawer",
|
||||||
title: "Редактирование справочника",
|
props: {
|
||||||
innerProps: {
|
|
||||||
onSelectChange: (values, onSuccess) =>
|
onSelectChange: (values, onSuccess) =>
|
||||||
attrSelectsCrud.onUpdate(select.id, values, onSuccess),
|
attrSelectsCrud.onUpdate(select.id, values, onSuccess),
|
||||||
select,
|
select,
|
||||||
|
|||||||
@ -1,24 +0,0 @@
|
|||||||
"use client";
|
|
||||||
|
|
||||||
import { ContextModalProps } from "@mantine/modals";
|
|
||||||
import EditorBody from "@/app/attributes/modals/AttrSelectEditorModal/components/EditorBody";
|
|
||||||
import { SelectEditorContextProvider } from "@/app/attributes/modals/AttrSelectEditorModal/contexts/SelectEditorContext";
|
|
||||||
import { AttrSelectSchema, UpdateAttrSelectSchema } from "@/lib/client";
|
|
||||||
|
|
||||||
type Props = {
|
|
||||||
select: AttrSelectSchema;
|
|
||||||
onSelectChange: (
|
|
||||||
values: UpdateAttrSelectSchema,
|
|
||||||
onSuccess: () => void
|
|
||||||
) => void;
|
|
||||||
};
|
|
||||||
|
|
||||||
const AttrSelectEditorModal = ({ innerProps }: ContextModalProps<Props>) => {
|
|
||||||
return (
|
|
||||||
<SelectEditorContextProvider {...innerProps}>
|
|
||||||
<EditorBody />
|
|
||||||
</SelectEditorContextProvider>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default AttrSelectEditorModal;
|
|
||||||
@ -1,20 +0,0 @@
|
|||||||
import { Divider, Stack } from "@mantine/core";
|
|
||||||
import CommonInfoEditor from "@/app/attributes/modals/AttrSelectEditorModal/components/CommonInfoEditor";
|
|
||||||
import CreateOptionButton from "@/app/attributes/modals/AttrSelectEditorModal/components/CreateOptionButton";
|
|
||||||
import OptionsTable from "@/app/attributes/modals/AttrSelectEditorModal/components/OptionsTable";
|
|
||||||
|
|
||||||
const EditorBody = () => {
|
|
||||||
return (
|
|
||||||
<Stack gap={"xs"}>
|
|
||||||
<CommonInfoEditor />
|
|
||||||
<Divider
|
|
||||||
label={"Опции"}
|
|
||||||
my={"xs"}
|
|
||||||
/>
|
|
||||||
<CreateOptionButton />
|
|
||||||
<OptionsTable />
|
|
||||||
</Stack>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default EditorBody;
|
|
||||||
@ -1,38 +0,0 @@
|
|||||||
import { IconMoodSad } from "@tabler/icons-react";
|
|
||||||
import { Group, Text } from "@mantine/core";
|
|
||||||
import { useSelectEditorContext } from "@/app/attributes/modals/AttrSelectEditorModal/contexts/SelectEditorContext";
|
|
||||||
import useOptionsTableColumns from "@/app/attributes/modals/AttrSelectEditorModal/hooks/useOptionsTableColumns";
|
|
||||||
import BaseTable from "@/components/ui/BaseTable/BaseTable";
|
|
||||||
import useIsMobile from "@/hooks/utils/useIsMobile";
|
|
||||||
|
|
||||||
const OptionsTable = () => {
|
|
||||||
const { options } = useSelectEditorContext();
|
|
||||||
const isMobile = useIsMobile();
|
|
||||||
const columns = useOptionsTableColumns();
|
|
||||||
|
|
||||||
return (
|
|
||||||
<BaseTable
|
|
||||||
withTableBorder
|
|
||||||
columns={columns}
|
|
||||||
records={options}
|
|
||||||
verticalSpacing={"md"}
|
|
||||||
emptyState={
|
|
||||||
<Group mt={options.length === 0 ? "xl" : 0}>
|
|
||||||
<Text>Нет опций</Text>
|
|
||||||
<IconMoodSad />
|
|
||||||
</Group>
|
|
||||||
}
|
|
||||||
groups={undefined}
|
|
||||||
styles={{
|
|
||||||
table: {
|
|
||||||
width: "100%",
|
|
||||||
minHeight: options.length === 0 ? "150px" : "auto",
|
|
||||||
},
|
|
||||||
header: { zIndex: 1 },
|
|
||||||
}}
|
|
||||||
mx={isMobile ? "xs" : 0}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default OptionsTable;
|
|
||||||
@ -1,84 +0,0 @@
|
|||||||
"use client";
|
|
||||||
|
|
||||||
import React, { useMemo } from "react";
|
|
||||||
import { IconCheck, IconEdit, IconTrash } from "@tabler/icons-react";
|
|
||||||
import { DataTableColumn } from "mantine-datatable";
|
|
||||||
import { Center, Flex, TextInput } from "@mantine/core";
|
|
||||||
import { useSelectEditorContext } from "@/app/attributes/modals/AttrSelectEditorModal/contexts/SelectEditorContext";
|
|
||||||
import ActionIconWithTip from "@/components/ui/ActionIconWithTip/ActionIconWithTip";
|
|
||||||
import useIsMobile from "@/hooks/utils/useIsMobile";
|
|
||||||
import { AttrOptionSchema } from "@/lib/client";
|
|
||||||
|
|
||||||
const useSelectsTableColumns = () => {
|
|
||||||
const isMobile = useIsMobile();
|
|
||||||
const {
|
|
||||||
optionsActions: {
|
|
||||||
onStartEditing,
|
|
||||||
onFinishEditing,
|
|
||||||
onDelete,
|
|
||||||
editingOptionsData,
|
|
||||||
setEditingOptionsData,
|
|
||||||
},
|
|
||||||
} = useSelectEditorContext();
|
|
||||||
|
|
||||||
const onChange = (
|
|
||||||
e: React.ChangeEvent<HTMLInputElement>,
|
|
||||||
optionId: number
|
|
||||||
) => {
|
|
||||||
setEditingOptionsData(prev => {
|
|
||||||
prev.set(optionId, e.currentTarget.value);
|
|
||||||
return new Map(prev);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
return useMemo(
|
|
||||||
() =>
|
|
||||||
[
|
|
||||||
{
|
|
||||||
title: "Название опции",
|
|
||||||
accessor: "name",
|
|
||||||
render: option =>
|
|
||||||
editingOptionsData.has(option.id) ? (
|
|
||||||
<TextInput
|
|
||||||
value={editingOptionsData.get(option.id)}
|
|
||||||
onChange={e => onChange(e, option.id)}
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
option.name
|
|
||||||
),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
accessor: "actions",
|
|
||||||
title: <Center>Действия</Center>,
|
|
||||||
width: "0%",
|
|
||||||
render: option => (
|
|
||||||
<Flex gap={"xs"}>
|
|
||||||
{editingOptionsData.has(option.id) ? (
|
|
||||||
<ActionIconWithTip
|
|
||||||
onClick={() => onFinishEditing(option)}
|
|
||||||
tipLabel={"Сохранить"}>
|
|
||||||
<IconCheck />
|
|
||||||
</ActionIconWithTip>
|
|
||||||
) : (
|
|
||||||
<ActionIconWithTip
|
|
||||||
onClick={() => onStartEditing(option)}
|
|
||||||
tipLabel={"Редактировать"}>
|
|
||||||
<IconEdit />
|
|
||||||
</ActionIconWithTip>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<ActionIconWithTip
|
|
||||||
color={"red"}
|
|
||||||
onClick={() => onDelete(option)}
|
|
||||||
tipLabel={"Удалить"}>
|
|
||||||
<IconTrash />
|
|
||||||
</ActionIconWithTip>
|
|
||||||
</Flex>
|
|
||||||
),
|
|
||||||
},
|
|
||||||
] as DataTableColumn<AttrOptionSchema>[],
|
|
||||||
[isMobile, editingOptionsData]
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default useSelectsTableColumns;
|
|
||||||
@ -18,8 +18,8 @@ const BoardsMobileEditorDrawer: FC<DrawerProps<Props>> = ({
|
|||||||
}) => {
|
}) => {
|
||||||
return (
|
return (
|
||||||
<Drawer
|
<Drawer
|
||||||
size={"100%"}
|
size={"50%"}
|
||||||
position={"right"}
|
position={"left"}
|
||||||
onClose={onClose}
|
onClose={onClose}
|
||||||
removeScrollProps={{ allowPinchZoom: true }}
|
removeScrollProps={{ allowPinchZoom: true }}
|
||||||
withCloseButton={false}
|
withCloseButton={false}
|
||||||
|
|||||||
@ -39,7 +39,15 @@ const DraggableTableRow: FC<Props> = ({
|
|||||||
rowProps.className,
|
rowProps.className,
|
||||||
classes["draggable-row"]
|
classes["draggable-row"]
|
||||||
)}
|
)}
|
||||||
{...provided.draggableProps}>
|
{...provided.draggableProps}
|
||||||
|
// style={{
|
||||||
|
// ...provided.draggableProps.style,
|
||||||
|
// left: "auto !important",
|
||||||
|
// top: "auto !important",
|
||||||
|
//
|
||||||
|
// // does not work after scroll
|
||||||
|
// }}
|
||||||
|
>
|
||||||
<TableTd maw={isMobile ? 2 : "auto"}>
|
<TableTd maw={isMobile ? 2 : "auto"}>
|
||||||
<Center
|
<Center
|
||||||
{...provided.dragHandleProps}
|
{...provided.dragHandleProps}
|
||||||
|
|||||||
@ -4,6 +4,7 @@ import DealEditorDrawer from "@/app/deals/drawers/DealEditorDrawer";
|
|||||||
import ProjectsMobileEditorDrawer from "@/app/deals/drawers/ProjectsMobileEditorDrawer";
|
import ProjectsMobileEditorDrawer from "@/app/deals/drawers/ProjectsMobileEditorDrawer";
|
||||||
import StatusesMobileEditorDrawer from "../app/deals/drawers/StatusesMobileEditorDrawer";
|
import StatusesMobileEditorDrawer from "../app/deals/drawers/StatusesMobileEditorDrawer";
|
||||||
import ProjectEditorDrawer from "./common/ProjectEditorDrawer";
|
import ProjectEditorDrawer from "./common/ProjectEditorDrawer";
|
||||||
|
import AttrSelectEditorDrawer from "@/app/attributes/drawers/AttrSelectEditorDrawer/AttrSelectEditorDrawer";
|
||||||
|
|
||||||
const drawerRegistry = {
|
const drawerRegistry = {
|
||||||
projectsMobileEditorDrawer: ProjectsMobileEditorDrawer,
|
projectsMobileEditorDrawer: ProjectsMobileEditorDrawer,
|
||||||
@ -12,6 +13,7 @@ const drawerRegistry = {
|
|||||||
dealEditorDrawer: DealEditorDrawer,
|
dealEditorDrawer: DealEditorDrawer,
|
||||||
projectEditorDrawer: ProjectEditorDrawer,
|
projectEditorDrawer: ProjectEditorDrawer,
|
||||||
clientMarketplaceDrawer: ClientMarketplaceDrawer,
|
clientMarketplaceDrawer: ClientMarketplaceDrawer,
|
||||||
|
attrSelectEditorDrawer: AttrSelectEditorDrawer,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default drawerRegistry;
|
export default drawerRegistry;
|
||||||
|
|||||||
@ -22,6 +22,10 @@ export type AttrOptionSchema = {
|
|||||||
* Name
|
* Name
|
||||||
*/
|
*/
|
||||||
name: string;
|
name: string;
|
||||||
|
/**
|
||||||
|
* Lexorank
|
||||||
|
*/
|
||||||
|
lexorank: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -317,6 +321,10 @@ export type CreateAttrOptionSchema = {
|
|||||||
* Name
|
* Name
|
||||||
*/
|
*/
|
||||||
name: string;
|
name: string;
|
||||||
|
/**
|
||||||
|
* Lexorank
|
||||||
|
*/
|
||||||
|
lexorank: string;
|
||||||
/**
|
/**
|
||||||
* Selectid
|
* Selectid
|
||||||
*/
|
*/
|
||||||
@ -2482,7 +2490,11 @@ export type UpdateAttrOptionSchema = {
|
|||||||
/**
|
/**
|
||||||
* Name
|
* Name
|
||||||
*/
|
*/
|
||||||
name: string;
|
name?: string | null;
|
||||||
|
/**
|
||||||
|
* Lexorank
|
||||||
|
*/
|
||||||
|
lexorank?: string | null;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@ -15,6 +15,7 @@ export const zAddAttributeResponse = z.object({
|
|||||||
export const zAttrOptionSchema = z.object({
|
export const zAttrOptionSchema = z.object({
|
||||||
id: z.int(),
|
id: z.int(),
|
||||||
name: z.string(),
|
name: z.string(),
|
||||||
|
lexorank: z.string(),
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -152,6 +153,7 @@ export const zClientSchema = z.object({
|
|||||||
*/
|
*/
|
||||||
export const zCreateAttrOptionSchema = z.object({
|
export const zCreateAttrOptionSchema = z.object({
|
||||||
name: z.string(),
|
name: z.string(),
|
||||||
|
lexorank: z.string(),
|
||||||
selectId: z.int(),
|
selectId: z.int(),
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -1421,7 +1423,8 @@ export const zSwitchDealTagResponse = z.object({
|
|||||||
* UpdateAttrOptionSchema
|
* UpdateAttrOptionSchema
|
||||||
*/
|
*/
|
||||||
export const zUpdateAttrOptionSchema = z.object({
|
export const zUpdateAttrOptionSchema = z.object({
|
||||||
name: z.string(),
|
name: z.optional(z.union([z.string(), z.null()])),
|
||||||
|
lexorank: z.optional(z.union([z.string(), z.null()])),
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@ -1,4 +1,3 @@
|
|||||||
import AttrSelectEditorModal from "@/app/attributes/modals/AttrSelectEditorModal/AttrSelectEditorModal";
|
|
||||||
import BarcodeTemplateEditorModal from "@/app/barcode-templates/modals/BarcodeTemplateFormModal/BarcodeTemplateEditorModal";
|
import BarcodeTemplateEditorModal from "@/app/barcode-templates/modals/BarcodeTemplateFormModal/BarcodeTemplateEditorModal";
|
||||||
import MarketplaceEditorModal from "@/app/clients/drawers/ClientMarketplacesDrawer/modals/MarketplaceEditorModal";
|
import MarketplaceEditorModal from "@/app/clients/drawers/ClientMarketplacesDrawer/modals/MarketplaceEditorModal";
|
||||||
import ClientEditorModal from "@/app/clients/modals/ClientFormModal/ClientFormModal";
|
import ClientEditorModal from "@/app/clients/modals/ClientFormModal/ClientFormModal";
|
||||||
@ -47,5 +46,4 @@ export const modals = {
|
|||||||
dealTagModal: DealTagModal,
|
dealTagModal: DealTagModal,
|
||||||
attributeEditorModal: AttributeEditorModal,
|
attributeEditorModal: AttributeEditorModal,
|
||||||
moduleCreatorModal: ModuleCreatorModal,
|
moduleCreatorModal: ModuleCreatorModal,
|
||||||
attrSelectEditorModal: AttrSelectEditorModal,
|
|
||||||
};
|
};
|
||||||
|
|||||||
19
yarn.lock
19
yarn.lock
@ -6207,7 +6207,6 @@ __metadata:
|
|||||||
postcss-simple-vars: "npm:^7.0.1"
|
postcss-simple-vars: "npm:^7.0.1"
|
||||||
prettier: "npm:^3.5.3"
|
prettier: "npm:^3.5.3"
|
||||||
react: "npm:19.1.0"
|
react: "npm:19.1.0"
|
||||||
react-dom: "npm:19.1.0"
|
|
||||||
react-imask: "npm:^7.6.1"
|
react-imask: "npm:^7.6.1"
|
||||||
react-redux: "npm:^9.2.0"
|
react-redux: "npm:^9.2.0"
|
||||||
redux-persist: "npm:^6.0.0"
|
redux-persist: "npm:^6.0.0"
|
||||||
@ -11754,17 +11753,6 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"react-dom@npm:19.1.0":
|
|
||||||
version: 19.1.0
|
|
||||||
resolution: "react-dom@npm:19.1.0"
|
|
||||||
dependencies:
|
|
||||||
scheduler: "npm:^0.26.0"
|
|
||||||
peerDependencies:
|
|
||||||
react: ^19.1.0
|
|
||||||
checksum: 10c0/3e26e89bb6c67c9a6aa86cb888c7a7f8258f2e347a6d2a15299c17eb16e04c19194e3452bc3255bd34000a61e45e2cb51e46292392340432f133e5a5d2dfb5fc
|
|
||||||
languageName: node
|
|
||||||
linkType: hard
|
|
||||||
|
|
||||||
"react-dropzone@npm:14.3.8":
|
"react-dropzone@npm:14.3.8":
|
||||||
version: 14.3.8
|
version: 14.3.8
|
||||||
resolution: "react-dropzone@npm:14.3.8"
|
resolution: "react-dropzone@npm:14.3.8"
|
||||||
@ -12417,13 +12405,6 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"scheduler@npm:^0.26.0":
|
|
||||||
version: 0.26.0
|
|
||||||
resolution: "scheduler@npm:0.26.0"
|
|
||||||
checksum: 10c0/5b8d5bfddaae3513410eda54f2268e98a376a429931921a81b5c3a2873aab7ca4d775a8caac5498f8cbc7d0daeab947cf923dbd8e215d61671f9f4e392d34356
|
|
||||||
languageName: node
|
|
||||||
linkType: hard
|
|
||||||
|
|
||||||
"schema-utils@npm:^3.1.1":
|
"schema-utils@npm:^3.1.1":
|
||||||
version: 3.3.0
|
version: 3.3.0
|
||||||
resolution: "schema-utils@npm:3.3.0"
|
resolution: "schema-utils@npm:3.3.0"
|
||||||
|
|||||||
Reference in New Issue
Block a user