refactor: modals refactored

This commit is contained in:
2025-09-05 14:25:36 +04:00
parent 7694b4ae03
commit d0c734d481
24 changed files with 292 additions and 97 deletions

View File

@ -9,14 +9,8 @@ import { useDrawersContext } from "@/drawers/DrawersContext";
import { DealSchema } from "@/lib/client";
const DealsTable: FC = () => {
const {
deals,
paginationInfo,
page,
setPage,
dealsFiltersForm,
dealsCrud,
} = useDealsContext();
const { deals, paginationInfo, page, setPage, sortingForm, dealsCrud } =
useDealsContext();
const { selectedProject } = useProjectsContext();
const { openDrawer } = useDrawersContext();
@ -40,20 +34,20 @@ const DealsTable: FC = () => {
return (
<Stack
gap={"xs"}
h={"calc(100vh - 125px)"}>
h={"calc(100vh - var(--mantine-spacing-xl) * 4)"}>
<BaseTable
records={[...deals]}
columns={columns}
sortStatus={{
columnAccessor: dealsFiltersForm.values.sortingField ?? "",
direction: dealsFiltersForm.values.sortingDirection,
columnAccessor: sortingForm.values.sortingField ?? "",
direction: sortingForm.values.sortingDirection,
}}
onSortStatusChange={sorting => {
dealsFiltersForm.setFieldValue(
sortingForm.setFieldValue(
"sortingField",
sorting.columnAccessor
);
dealsFiltersForm.setFieldValue(
sortingForm.setFieldValue(
"sortingDirection",
sorting.direction
);

View File

@ -35,7 +35,7 @@ const TopToolPanel: FC<Props> = ({ view, setView }) => {
title: "Создание проекта",
withCloseButton: true,
innerProps: {
onComplete: projectsCrud.onCreate,
onChange: values => projectsCrud.onCreate(values.name),
},
});
};
@ -49,9 +49,15 @@ const TopToolPanel: FC<Props> = ({ view, setView }) => {
});
};
const viewFiltersModalMap = {
table: "dealsTableFiltersModal",
board: "dealsBoardFiltersModal",
schedule: "dealsScheduleFiltersModal",
};
const onFiltersClick = () => {
modals.openContextModal({
modal: "dealsFiltersModal",
modal: viewFiltersModalMap[view],
title: "Фильтры",
withCloseButton: true,
innerProps: {

View File

@ -29,8 +29,8 @@ const Board: FC<Props> = ({ board }) => {
onMouseEnter={() => setIsHovered(true)}
onMouseLeave={() => setIsHovered(false)}>
<InPlaceInput
defaultValue={board.name}
onComplete={value =>
value={board.name}
onChange={value =>
boardsCrud.onUpdate(board.id, { name: value })
}
inputStyles={{

View File

@ -11,7 +11,7 @@ const CreateBoardButton = () => {
<Flex style={{ borderBottom: "2px solid gray" }}>
<InPlaceInput
placeholder={"Название доски"}
onComplete={boardsCrud.onCreate}
onChange={boardsCrud.onCreate}
getChildren={startEditing => (
<Box
onClick={startEditing}

View File

@ -17,7 +17,7 @@ const CreateStatusButton = () => {
className={styles["inner-container"]}>
<InPlaceInput
placeholder={"Название колонки"}
onComplete={statusesCrud.onCreate}
onChange={statusesCrud.onCreate}
getChildren={startEditing => (
<Center
p={"sm"}

View File

@ -31,8 +31,8 @@ const StatusColumnHeader: FC<Props> = ({ status, isDragging }) => {
mb={"xs"}
className={styles.header}>
<InPlaceInput
defaultValue={status.name}
onComplete={value => handleSave(value)}
value={status.name}
onChange={value => handleSave(value)}
inputStyles={{
input: {
height: 25,

View File

@ -6,6 +6,7 @@ import { useStatusesContext } from "@/app/deals/contexts/StatusesContext";
import { DealsFiltersForm } from "@/app/deals/hooks/useDealsFilters";
import { DealsCrud, useDealsCrud } from "@/hooks/cruds/useDealsCrud";
import useDealsList from "@/hooks/lists/useDealsList";
import { SortingForm } from "@/hooks/utils/useSorting";
import { DealSchema, PaginationInfoSchema } from "@/lib/client";
import makeContext from "@/lib/contextFactory/contextFactory";
@ -19,6 +20,7 @@ type DealsContextState = {
setPage: React.Dispatch<React.SetStateAction<number>>;
dealsFiltersForm: UseFormReturnType<DealsFiltersForm>;
isChangedFilters: boolean;
sortingForm: UseFormReturnType<SortingForm>;
};
type Props = {

View File

@ -13,7 +13,7 @@ const CreateStatusButton: FC = () => {
title: "Создание колонки",
withCloseButton: true,
innerProps: {
onComplete: statusesCrud.onCreate,
onChange: values => statusesCrud.onCreate(values.name),
},
});
};

View File

@ -19,8 +19,8 @@ const StatusMobile: FC<Props> = ({ status, board }) => {
title: "Редактирование статуса",
withCloseButton: true,
innerProps: {
onComplete: name => statusesCrud.onUpdate(status.id, { name }),
defaultValue: status.name,
onChange: values => statusesCrud.onUpdate(status.id, values),
value: status,
},
});
};

View File

@ -18,8 +18,8 @@ const BoardMobile: FC<Props> = ({ board }) => {
title: "Редактирование доски",
withCloseButton: true,
innerProps: {
onComplete: name => boardsCrud.onUpdate(board.id, { name }),
defaultValue: board.name,
onChange: values => boardsCrud.onUpdate(board.id, values),
value: board,
},
});
};

View File

@ -14,7 +14,7 @@ const CreateBoardButton: FC<Props> = ({ onCreateBoard }) => {
title: "Создание доски",
withCloseButton: true,
innerProps: {
onComplete: onCreateBoard,
onChange: values => onCreateBoard(values.name),
},
});
};

View File

@ -13,7 +13,7 @@ const CreateProjectButton: FC = () => {
title: "Создание проекта",
withCloseButton: true,
innerProps: {
onComplete: projectsCrud.onCreate,
onChange: values => projectsCrud.onCreate(values.name),
},
});
};

View File

@ -25,8 +25,8 @@ const ProjectMobile: FC<Props> = ({
title: "Редактирование проекта",
withCloseButton: true,
innerProps: {
onComplete: name => projectsCrud.onUpdate(project.id, { name }),
defaultValue: project.name,
onChange: values => projectsCrud.onUpdate(project.id, values),
value: project,
},
});
};

View File

@ -1,14 +1,12 @@
import { isEqual } from "lodash";
import { useForm, UseFormReturnType } from "@mantine/form";
import { BoardSchema, SortDir, StatusSchema } from "@/lib/client";
import { BoardSchema, StatusSchema } from "@/lib/client";
export type DealsFiltersForm = {
id: number | null;
name: string;
board: BoardSchema | null;
status: StatusSchema | null;
sortingField?: string;
sortingDirection: SortDir;
};
type ReturnType = {
@ -17,20 +15,18 @@ type ReturnType = {
};
const useDealsFilters = (): ReturnType => {
const initialValues = {
const initialFilters = {
id: null,
board: null,
status: null,
name: "",
sortingField: "createdAt",
sortingDirection: "asc" as SortDir,
};
const form = useForm<DealsFiltersForm>({
initialValues,
initialValues: initialFilters,
});
const isChangedFilters = !isEqual(form.values, initialValues);
const isChangedFilters = !isEqual(form.values, initialFilters);
return {
form,

View File

@ -0,0 +1,71 @@
"use client";
import {
Button,
Flex,
NumberInput,
rem,
Space,
Text,
TextInput,
} from "@mantine/core";
import { useForm } from "@mantine/form";
import { ContextModalProps } from "@mantine/modals";
import { DealsFiltersForm } from "@/app/deals/hooks/useDealsFilters";
import { ProjectSchema } from "@/lib/client";
type Props = {
value: DealsFiltersForm;
onChange: (values: DealsFiltersForm) => void;
project: ProjectSchema | null;
};
const DealsBoardFiltersModal = ({
id,
context,
innerProps,
}: ContextModalProps<Props>) => {
const filtersForm = useForm({
initialValues: innerProps.value,
});
const onSubmit = (values: DealsFiltersForm) => {
innerProps.onChange(values);
context.closeModal(id);
};
return (
<form onSubmit={filtersForm.onSubmit(onSubmit)}>
<Flex
gap={rem(10)}
direction={"column"}>
<NumberInput
label={"ID"}
placeholder={"Введите ID"}
{...filtersForm.getInputProps("id")}
value={filtersForm.values.id ?? ""}
onChange={value =>
typeof value === "number"
? filtersForm.setFieldValue("id", Number(value))
: filtersForm.setFieldValue("id", null)
}
hideControls
min={1}
/>
<TextInput
label={"Название"}
placeholder={"Введите название"}
{...filtersForm.getInputProps("name")}
/>
<Space />
<Button
variant={"default"}
type={"submit"}>
<Text>Сохранить</Text>
</Button>
</Flex>
</form>
);
};
export default DealsBoardFiltersModal;

View File

@ -20,10 +20,9 @@ type Props = {
value: DealsFiltersForm;
onChange: (values: DealsFiltersForm) => void;
project: ProjectSchema | null;
boardAndStatusEnabled: boolean;
};
const DealsFiltersModal = ({
const DealsScheduleFiltersModal = ({
id,
context,
innerProps,
@ -60,8 +59,6 @@ const DealsFiltersModal = ({
placeholder={"Введите название"}
{...filtersForm.getInputProps("name")}
/>
{innerProps.boardAndStatusEnabled && (
<>
<BoardSelect
label={"Доска"}
{...filtersForm.getInputProps("board")}
@ -75,8 +72,6 @@ const DealsFiltersModal = ({
clearable
clearOnBoardChange
/>
</>
)}
<Space />
<Button
variant={"default"}
@ -88,4 +83,4 @@ const DealsFiltersModal = ({
);
};
export default DealsFiltersModal;
export default DealsScheduleFiltersModal;

View File

@ -0,0 +1,86 @@
"use client";
import {
Button,
Flex,
NumberInput,
rem,
Space,
Text,
TextInput,
} from "@mantine/core";
import { useForm } from "@mantine/form";
import { ContextModalProps } from "@mantine/modals";
import { DealsFiltersForm } from "@/app/deals/hooks/useDealsFilters";
import BoardSelect from "@/components/selects/BoardSelect/BoardSelect";
import StatusSelect from "@/components/selects/StatusSelect/StatusSelect";
import { ProjectSchema } from "@/lib/client";
type Props = {
value: DealsFiltersForm;
onChange: (values: DealsFiltersForm) => void;
project: ProjectSchema | null;
};
const DealsTableFiltersModal = ({
id,
context,
innerProps,
}: ContextModalProps<Props>) => {
const filtersForm = useForm({
initialValues: innerProps.value,
});
const onSubmit = (values: DealsFiltersForm) => {
innerProps.onChange(values);
context.closeModal(id);
};
return (
<form onSubmit={filtersForm.onSubmit(onSubmit)}>
<Flex
gap={rem(10)}
direction={"column"}>
<NumberInput
label={"ID"}
placeholder={"Введите ID"}
{...filtersForm.getInputProps("id")}
value={filtersForm.values.id ?? ""}
onChange={value =>
typeof value === "number"
? filtersForm.setFieldValue("id", Number(value))
: filtersForm.setFieldValue("id", null)
}
hideControls
min={1}
/>
<TextInput
label={"Название"}
placeholder={"Введите название"}
{...filtersForm.getInputProps("name")}
/>
<BoardSelect
label={"Доска"}
{...filtersForm.getInputProps("board")}
projectId={innerProps.project?.id}
clearable
/>
<StatusSelect
label={"Статус"}
{...filtersForm.getInputProps("status")}
boardId={filtersForm.values.board?.id}
clearable
clearOnBoardChange
/>
<Space />
<Button
variant={"default"}
type={"submit"}>
<Text>Сохранить</Text>
</Button>
</Flex>
</form>
);
};
export default DealsTableFiltersModal;

View File

@ -5,8 +5,8 @@ import InPlaceInputDesktop from "./InPlaceInputDesktop";
import InPlaceInputMobile from "./InPlaceInputMobile";
type Props = {
defaultValue?: string;
onComplete: (value: string) => void;
value?: string;
onChange: (value: string) => void;
placeholder?: string;
getChildren: (startEditing: () => void) => ReactNode;
inputStyles?: Styles<any>;

View File

@ -3,22 +3,22 @@ import { TextInput } from "@mantine/core";
import { Styles } from "@mantine/core/lib/core/styles-api/styles-api.types";
type Props = {
defaultValue?: string;
onComplete: (value: string) => void;
value?: string;
onChange: (value: string) => void;
placeholder?: string;
getChildren: (startEditing: () => void) => ReactNode;
inputStyles?: Styles<any>;
};
const InPlaceInputDesktop: FC<Props> = ({
onComplete,
onChange,
placeholder,
inputStyles,
getChildren,
defaultValue = "",
value = "",
}) => {
const [isWriting, setIsWriting] = useState<boolean>(false);
const [value, setValue] = useState<string>(defaultValue);
const [localValue, setLocalValue] = useState<string>(value);
const inputRef = useRef<HTMLInputElement>(null);
useEffect(() => {
@ -41,22 +41,22 @@ const InPlaceInputDesktop: FC<Props> = ({
document.addEventListener("mousedown", handleClickOutside);
return () =>
document.removeEventListener("mousedown", handleClickOutside);
}, [isWriting, value]);
}, [isWriting, localValue]);
const onStartCreating = () => {
setValue(defaultValue);
setLocalValue(localValue);
setIsWriting(true);
};
const onCancelCreating = () => {
setValue(defaultValue);
setLocalValue(localValue);
setIsWriting(false);
};
const onCompleteCreating = () => {
const localValue = value.trim();
if (localValue) {
onComplete(localValue);
const val = localValue.trim();
if (val) {
onChange(val);
}
setIsWriting(false);
};
@ -67,8 +67,8 @@ const InPlaceInputDesktop: FC<Props> = ({
ref={inputRef}
placeholder={placeholder}
variant={"unstyled"}
value={value}
onChange={e => setValue(e.target.value)}
value={localValue}
onChange={e => setLocalValue(e.target.value)}
onKeyDown={e => {
e.stopPropagation();
if (e.key === "Enter") onCompleteCreating();

View File

@ -2,17 +2,17 @@ import { FC, ReactNode } from "react";
import { modals } from "@mantine/modals";
type Props = {
defaultValue?: string;
onComplete: (value: string) => void;
value?: string;
onChange: (value: string) => void;
getChildren: (startEditing: () => void) => ReactNode;
modalTitle?: string;
};
const InPlaceInputMobile: FC<Props> = ({
onComplete,
onChange,
getChildren,
modalTitle = "",
defaultValue = "",
value = "",
}) => {
const onStartCreating = () => {
modals.openContextModal({
@ -20,8 +20,8 @@ const InPlaceInputMobile: FC<Props> = ({
title: modalTitle,
withCloseButton: true,
innerProps: {
onComplete,
defaultValue,
onChange: values => onChange(values.name),
value: { name: value },
},
});
};

View File

@ -5,6 +5,7 @@ import { useDebouncedValue } from "@mantine/hooks";
import useDealsFilters, {
DealsFiltersForm,
} from "@/app/deals/hooks/useDealsFilters";
import useSorting, { SortingForm } from "@/hooks/utils/useSorting";
import { DealSchema, GetDealsData, PaginationInfoSchema } from "@/lib/client";
import {
getDealsOptions,
@ -22,6 +23,7 @@ type ReturnType = {
setDeals: (deals: DealSchema[]) => void;
dealsFiltersForm: UseFormReturnType<DealsFiltersForm>;
isChangedFilters: boolean;
sortingForm: UseFormReturnType<SortingForm>;
refetchDeals: () => void;
page: number;
setPage: Dispatch<SetStateAction<number>>;
@ -37,22 +39,32 @@ const useDealsList = ({
const queryClient = useQueryClient();
const [page, setPage] = useState(1);
const itemsPerPage = 10;
const { form, isChangedFilters } = useDealsFilters();
const { form: dealsFiltersForm, isChangedFilters } = useDealsFilters();
const { form: sortingForm } = useSorting();
const [debouncedId] = useDebouncedValue(form.values.id, 300);
const [debouncedName] = useDebouncedValue(form.values.name, 300);
const [debouncedId] = useDebouncedValue(dealsFiltersForm.values.id, 300);
const [debouncedName] = useDebouncedValue(
dealsFiltersForm.values.name,
300
);
const options: Omit<GetDealsData, "url"> = {
query: {
page: withPagination ? page : null,
itemsPerPage: withPagination ? itemsPerPage : null,
sortingField: withPagination ? form.values.sortingField : null,
sortingField: withPagination
? sortingForm.values.sortingField
: null,
sortingDirection: withPagination
? form.values.sortingDirection
? sortingForm.values.sortingDirection
: null,
projectId: withPagination ? projectId : null,
boardId: withPagination ? form.values.board?.id : boardId,
statusId: withPagination ? form.values.status?.id : null,
boardId: withPagination
? dealsFiltersForm.values.board?.id
: boardId,
statusId: withPagination
? dealsFiltersForm.values.status?.id
: null,
name: debouncedName,
id: debouncedId,
},
@ -72,7 +84,8 @@ const useDealsList = ({
return {
deals: data?.items ?? [],
setDeals,
dealsFiltersForm: form,
dealsFiltersForm,
sortingForm,
isChangedFilters,
refetchDeals: refetch,
page,

View File

@ -0,0 +1,28 @@
import { useForm, UseFormReturnType } from "@mantine/form";
import { SortDir } from "@/lib/client";
export type SortingForm = {
sortingField?: string;
sortingDirection: SortDir;
};
type ReturnType = {
form: UseFormReturnType<SortingForm>;
};
const useSorting = (): ReturnType => {
const initialFilters = {
sortingField: "createdAt",
sortingDirection: "asc" as SortDir,
};
const form = useForm<SortingForm>({
initialValues: initialFilters,
});
return {
form,
};
};
export default useSorting;

View File

@ -4,23 +4,23 @@ import { Button, Flex, rem, TextInput } from "@mantine/core";
import { useForm } from "@mantine/form";
import { ContextModalProps } from "@mantine/modals";
type Props = {
onComplete: (value: string) => void;
defaultValue?: string;
};
type FormType = {
name: string;
};
type Props = {
onChange: (value: FormType) => void;
value?: FormType;
};
const EnterNameModal = ({
id,
context,
innerProps,
}: ContextModalProps<Props>) => {
const form = useForm<FormType>({
initialValues: {
name: innerProps.defaultValue ?? "",
initialValues: innerProps.value || {
name: "",
},
validate: {
name: name => !name && "Введите название",
@ -28,7 +28,7 @@ const EnterNameModal = ({
});
const onSubmit = (values: FormType) => {
innerProps.onComplete(values.name);
innerProps.onChange(values);
context.closeModal(id);
};

View File

@ -1,7 +1,11 @@
import DealsFiltersModal from "@/app/deals/modals/DealsFiltersModal/DealsFiltersModal";
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 EnterNameModal from "@/modals/EnterNameModal/EnterNameModal";
export const modals = {
enterNameModal: EnterNameModal,
dealsFiltersModal: DealsFiltersModal,
dealsTableFiltersModal: DealsTableFiltersModal,
dealsBoardFiltersModal: DealsBoardFiltersModal,
dealsScheduleFiltersModal: DealsScheduleFiltersModal,
};