refactor: crud objects in contexts

This commit is contained in:
2025-08-23 11:20:32 +04:00
parent f2084ae3d4
commit 6ad813ea1d
25 changed files with 120 additions and 128 deletions

View File

@ -13,7 +13,7 @@ type Props = {
};
const Board: FC<Props> = ({ board }) => {
const { selectedBoard, onUpdateBoard, onDeleteBoard } = useBoardsContext();
const { selectedBoard, boardsCrud } = useBoardsContext();
const isMobile = useIsMobile();
const [isHovered, setIsHovered] = useState(false);
@ -30,7 +30,9 @@ const Board: FC<Props> = ({ board }) => {
onMouseLeave={() => setIsHovered(false)}>
<InPlaceInput
defaultValue={board.name}
onComplete={value => onUpdateBoard(board.id, { name: value })}
onComplete={value =>
boardsCrud.onUpdate(board.id, { name: value })
}
inputStyles={{
input: {
height: 24,
@ -49,7 +51,7 @@ const Board: FC<Props> = ({ board }) => {
isHovered={
selectedBoard?.id === board.id || isHovered
}
onDeleteBoard={onDeleteBoard}
onDeleteBoard={boardsCrud.onDelete}
board={board}
startEditing={startEditing}
/>

View File

@ -11,13 +11,13 @@ import { BoardSchema } from "@/lib/client";
import styles from "./Boards.module.css";
const Boards = () => {
const { boards, setSelectedBoardId, onUpdateBoard } = useBoardsContext();
const { boards, setSelectedBoardId, boardsCrud } = useBoardsContext();
const isMobile = useIsMobile();
const renderBoard = (board: BoardSchema) => <Board board={board} />;
const onDragEnd = (itemId: number, newLexorank: string) => {
onUpdateBoard(itemId, { lexorank: newLexorank });
boardsCrud.onUpdate(itemId, { lexorank: newLexorank });
};
const selectBoard = (board: BoardSchema) => {

View File

@ -5,13 +5,13 @@ import InPlaceInput from "@/components/ui/InPlaceInput/InPlaceInput";
import styles from "./CreateBoardButton.module.css";
const CreateBoardButton = () => {
const { onCreateBoard } = useBoardsContext();
const { boardsCrud } = useBoardsContext();
return (
<Flex style={{ borderBottom: "2px solid gray" }}>
<InPlaceInput
placeholder={"Название доски"}
onComplete={onCreateBoard}
onComplete={boardsCrud.onCreate}
getChildren={startEditing => (
<Box
onClick={startEditing}

View File

@ -7,7 +7,7 @@ import useIsMobile from "@/hooks/useIsMobile";
import styles from "./CreateStatusButton.module.css";
const CreateStatusButton = () => {
const { onCreateStatus } = useStatusesContext();
const { statusesCrud } = useStatusesContext();
const isMobile = useIsMobile();
return (
@ -15,7 +15,7 @@ const CreateStatusButton = () => {
<Box className={styles["inner-container"]}>
<InPlaceInput
placeholder={"Название колонки"}
onComplete={onCreateStatus}
onComplete={statusesCrud.onCreate}
getChildren={startEditing => (
<Center
p={"sm"}

View File

@ -13,14 +13,13 @@ type Props = {
};
const StatusColumnHeader: FC<Props> = ({ status, isDragging }) => {
const { onUpdateStatus, onDeleteStatus, refetchStatuses } =
useStatusesContext();
const { statusesCrud, refetchStatuses } = useStatusesContext();
const { selectedBoard } = useBoardsContext();
const handleSave = (value: string) => {
const newValue = value.trim();
if (newValue && newValue !== status.name) {
onUpdateStatus(status.id, { name: newValue });
statusesCrud.onUpdate(status.id, { name: newValue });
}
};
@ -55,7 +54,7 @@ const StatusColumnHeader: FC<Props> = ({ status, isDragging }) => {
status={status}
handleEdit={startEditing}
refetchStatuses={refetchStatuses}
onDeleteStatus={onDeleteStatus}
onDeleteStatus={statusesCrud.onDelete}
/>
</>
)}

View File

@ -2,9 +2,9 @@
import React, { createContext, FC, useContext, useState } from "react";
import { useProjectsContext } from "@/app/deals/contexts/ProjectsContext";
import { BoardsCrud, useBoardsCrud } from "@/hooks/useBoardsCrud";
import useBoardsList from "@/hooks/useBoardsList";
import { useBoardsOperations } from "@/hooks/useBoardsOperations";
import { BoardSchema, UpdateBoardSchema } from "@/lib/client";
import { BoardSchema } from "@/lib/client";
type BoardsContextState = {
boards: BoardSchema[];
@ -12,9 +12,7 @@ type BoardsContextState = {
selectedBoard: BoardSchema | null;
setSelectedBoardId: React.Dispatch<React.SetStateAction<number | null>>;
refetchBoards: () => void;
onCreateBoard: (name: string) => void;
onUpdateBoard: (boardId: number, board: UpdateBoardSchema) => void;
onDeleteBoard: (board: BoardSchema) => void;
boardsCrud: BoardsCrud;
isEditorDrawerOpened: boolean;
setIsEditorDrawerOpened: React.Dispatch<React.SetStateAction<boolean>>;
};
@ -39,7 +37,7 @@ const useBoardsContextState = () => {
setSelectedBoardId(boards[0].id);
}
const { onCreate, onUpdate, onDelete } = useBoardsOperations({
const boardsCrud = useBoardsCrud({
boards,
setBoards,
refetchBoards,
@ -52,9 +50,7 @@ const useBoardsContextState = () => {
selectedBoard,
setSelectedBoardId,
refetchBoards,
onCreateBoard: onCreate,
onUpdateBoard: onUpdate,
onDeleteBoard: onDelete,
boardsCrud,
isEditorDrawerOpened,
setIsEditorDrawerOpened,
};

View File

@ -1,18 +1,16 @@
"use client";
import React, { createContext, FC, useContext, useState } from "react";
import { ProjectsCrud, useProjectsCrud } from "@/hooks/useProjectsCrud";
import useProjectsList from "@/hooks/useProjectsList";
import { useProjectsOperations } from "@/hooks/useProjectsOperations";
import { ProjectSchema, UpdateProjectSchema } from "@/lib/client";
import { ProjectSchema } from "@/lib/client";
type ProjectsContextState = {
selectedProject: ProjectSchema | null;
setSelectedProjectId: React.Dispatch<React.SetStateAction<number | null>>;
refetchProjects: () => Promise<void>;
projects: ProjectSchema[];
onCreateProject: (name: string) => void;
onUpdateProject: (projectId: number, project: UpdateProjectSchema) => void;
onDeleteProject: (project: ProjectSchema) => void;
projectsCrud: ProjectsCrud;
};
const ProjectsContext = createContext<ProjectsContextState | undefined>(
@ -36,7 +34,7 @@ const useProjectsContextState = () => {
setSelectedProjectId(projects[0].id);
}
const { onCreate, onUpdate, onDelete } = useProjectsOperations({
const projectsCrud = useProjectsCrud({
projects,
setProjects,
refetchProjects,
@ -47,9 +45,7 @@ const useProjectsContextState = () => {
selectedProject,
refetchProjects,
setSelectedProjectId,
onCreateProject: onCreate,
onUpdateProject: onUpdate,
onDeleteProject: onDelete,
projectsCrud,
};
};

View File

@ -2,17 +2,15 @@
import React, { createContext, FC, useContext } from "react";
import { useBoardsContext } from "@/app/deals/contexts/BoardsContext";
import { StatusesCrud, useStatusesCrud } from "@/hooks/useStatusesCrud";
import useStatusesList from "@/hooks/useStatusesList";
import { useStatusesOperations } from "@/hooks/useStatusesOperations";
import { StatusSchema, UpdateStatusSchema } from "@/lib/client";
import { StatusSchema } from "@/lib/client";
type StatusesContextState = {
statuses: StatusSchema[];
setStatuses: React.Dispatch<React.SetStateAction<StatusSchema[]>>;
refetchStatuses: () => void;
onCreateStatus: (name: string) => void;
onUpdateStatus: (statusId: number, status: UpdateStatusSchema) => void;
onDeleteStatus: (status: StatusSchema) => void;
statusesCrud: StatusesCrud;
};
const StatusesContext = createContext<StatusesContextState | undefined>(
@ -29,7 +27,7 @@ const useStatusesContextState = () => {
boardId: selectedBoard?.id,
});
const { onCreate, onUpdate, onDelete } = useStatusesOperations({
const statusesCrud = useStatusesCrud({
statuses,
setStatuses,
refetchStatuses,
@ -40,9 +38,7 @@ const useStatusesContextState = () => {
statuses,
setStatuses,
refetchStatuses,
onCreateStatus: onCreate,
onUpdateStatus: onUpdate,
onDeleteStatus: onDelete,
statusesCrud,
};
};

View File

@ -5,7 +5,7 @@ import { modals } from "@mantine/modals";
import { useBoardStatusesContext } from "@/app/deals/drawers/BoardStatusesEditorDrawer/contexts/BoardStatusesContext";
const CreateStatusButton: FC = () => {
const { onCreateStatus } = useBoardStatusesContext();
const { statusesCrud } = useBoardStatusesContext();
const onStartCreating = () => {
modals.openContextModal({
@ -13,7 +13,7 @@ const CreateStatusButton: FC = () => {
title: "Создание колонки",
withCloseButton: true,
innerProps: {
onComplete: onCreateStatus,
onComplete: statusesCrud.onCreate,
},
});
};

View File

@ -11,7 +11,7 @@ type Props = {
};
const StatusMobile: FC<Props> = ({ status, board }) => {
const { onUpdateStatus, onDeleteStatus } = useBoardStatusesContext();
const { statusesCrud } = useBoardStatusesContext();
const startEditing = () => {
modals.openContextModal({
@ -19,7 +19,7 @@ const StatusMobile: FC<Props> = ({ status, board }) => {
title: "Редактирование статуса",
withCloseButton: true,
innerProps: {
onComplete: name => onUpdateStatus(status.id, { name }),
onComplete: name => statusesCrud.onUpdate(status.id, { name }),
defaultValue: status.name,
},
});
@ -38,7 +38,7 @@ const StatusMobile: FC<Props> = ({ status, board }) => {
<StatusMenu
status={status}
board={board}
onDeleteStatus={onDeleteStatus}
onDeleteStatus={statusesCrud.onDelete}
handleEdit={startEditing}
withChangeOrderButton={false}
/>

View File

@ -12,7 +12,7 @@ type Props = {
};
const StatusesDrawerBody: FC<Props> = ({ onClose }) => {
const { onUpdateStatus, board, statuses } = useBoardStatusesContext();
const { statusesCrud, board, statuses } = useBoardStatusesContext();
const renderDraggable = () => (
<Box p={"xs"}>
@ -36,7 +36,7 @@ const StatusesDrawerBody: FC<Props> = ({ onClose }) => {
};
const onDragEnd = (itemId: number, newLexorank: string) =>
onUpdateStatus(itemId, { lexorank: newLexorank });
statusesCrud.onUpdate(itemId, { lexorank: newLexorank });
return (
<>

View File

@ -1,18 +1,16 @@
"use client";
import React, { createContext, FC, useContext } from "react";
import { StatusesCrud, useStatusesCrud } from "@/hooks/useStatusesCrud";
import useStatusesList from "@/hooks/useStatusesList";
import { useStatusesOperations } from "@/hooks/useStatusesOperations";
import { BoardSchema, StatusSchema, UpdateStatusSchema } from "@/lib/client";
import { BoardSchema, StatusSchema } from "@/lib/client";
type BoardStatusesContextState = {
board: BoardSchema;
statuses: StatusSchema[];
setStatuses: React.Dispatch<React.SetStateAction<StatusSchema[]>>;
refetchStatuses: () => void;
onCreateStatus: (name: string) => void;
onUpdateStatus: (statusId: number, status: UpdateStatusSchema) => void;
onDeleteStatus: (status: StatusSchema) => void;
statusesCrud: StatusesCrud;
};
const BoardStatusesContext = createContext<
@ -32,7 +30,7 @@ const useBoardStatusesContextState = ({ board }: Props) => {
boardId: board.id,
});
const { onCreate, onUpdate, onDelete } = useStatusesOperations({
const statusesCrud = useStatusesCrud({
statuses,
setStatuses,
refetchStatuses,
@ -44,9 +42,7 @@ const useBoardStatusesContextState = ({ board }: Props) => {
statuses,
setStatuses,
refetchStatuses,
onCreateStatus: onCreate,
onUpdateStatus: onUpdate,
onDeleteStatus: onDelete,
statusesCrud,
};
};

View File

@ -10,7 +10,7 @@ type Props = {
};
const BoardMobile: FC<Props> = ({ board }) => {
const { onUpdateBoard, onDeleteBoard } = useProjectBoardsContext();
const { boardsCrud } = useProjectBoardsContext();
const startEditing = () => {
modals.openContextModal({
@ -18,7 +18,7 @@ const BoardMobile: FC<Props> = ({ board }) => {
title: "Редактирование доски",
withCloseButton: true,
innerProps: {
onComplete: name => onUpdateBoard(board.id, { name }),
onComplete: name => boardsCrud.onUpdate(board.id, { name }),
defaultValue: board.name,
},
});
@ -37,7 +37,7 @@ const BoardMobile: FC<Props> = ({ board }) => {
<BoardMenu
board={board}
startEditing={startEditing}
onDeleteBoard={onDeleteBoard}
onDeleteBoard={boardsCrud.onDelete}
/>
</Group>
);

View File

@ -12,8 +12,7 @@ type Props = {
};
const BoardsDrawerBody: FC<Props> = ({ onClose }) => {
const { boards, onUpdateBoard, project, onCreateBoard } =
useProjectBoardsContext();
const { boards, boardsCrud, project } = useProjectBoardsContext();
const renderDraggable = () => (
<Box p={"xs"}>
@ -34,7 +33,7 @@ const BoardsDrawerBody: FC<Props> = ({ onClose }) => {
};
const onDragEnd = (itemId: number, newLexorank: string) => {
onUpdateBoard(itemId, { lexorank: newLexorank });
boardsCrud.onUpdate(itemId, { lexorank: newLexorank });
};
return (
@ -59,7 +58,7 @@ const BoardsDrawerBody: FC<Props> = ({ onClose }) => {
dragHandleStyle={{ width: "auto" }}
vertical
/>
<CreateBoardButton onCreateBoard={onCreateBoard} />
<CreateBoardButton onCreateBoard={boardsCrud.onCreate} />
</>
);
};

View File

@ -1,18 +1,16 @@
"use client";
import React, { createContext, FC, useContext } from "react";
import { BoardsCrud, useBoardsCrud } from "@/hooks/useBoardsCrud";
import useBoardsList from "@/hooks/useBoardsList";
import { useBoardsOperations } from "@/hooks/useBoardsOperations";
import { BoardSchema, ProjectSchema, UpdateBoardSchema } from "@/lib/client";
import { BoardSchema, ProjectSchema } from "@/lib/client";
type ProjectBoardsContextState = {
boards: BoardSchema[];
setBoards: React.Dispatch<React.SetStateAction<BoardSchema[]>>;
project: ProjectSchema;
refetchBoards: () => void;
onCreateBoard: (name: string) => void;
onUpdateBoard: (boardId: number, board: UpdateBoardSchema) => void;
onDeleteBoard: (board: BoardSchema) => void;
boardsCrud: BoardsCrud;
};
const ProjectBoardsContext = createContext<
@ -30,7 +28,7 @@ const useProjectBoardsContextState = ({ project }: Props) => {
refetch: refetchBoards,
} = useBoardsList({ projectId: project?.id });
const { onCreate, onUpdate, onDelete } = useBoardsOperations({
const boardsCrud = useBoardsCrud({
boards,
setBoards,
refetchBoards,
@ -42,9 +40,7 @@ const useProjectBoardsContextState = ({ project }: Props) => {
setBoards,
project,
refetchBoards,
onCreateBoard: onCreate,
onUpdateBoard: onUpdate,
onDeleteBoard: onDelete,
boardsCrud,
};
};

View File

@ -5,7 +5,7 @@ import { modals } from "@mantine/modals";
import { useProjectsContext } from "@/app/deals/contexts/ProjectsContext";
const CreateProjectButton: FC = () => {
const { onCreateProject } = useProjectsContext();
const { projectsCrud } = useProjectsContext();
const onStartCreating = () => {
modals.openContextModal({
@ -13,7 +13,7 @@ const CreateProjectButton: FC = () => {
title: "Создание проекта",
withCloseButton: true,
innerProps: {
onComplete: onCreateProject,
onComplete: projectsCrud.onCreate,
},
});
};

View File

@ -11,7 +11,7 @@ type Props = {
};
const ProjectMenu: FC<Props> = ({ project, startEditing }) => {
const { onDeleteProject } = useProjectsContext();
const { projectsCrud } = useProjectsContext();
return (
<Menu>
@ -36,7 +36,7 @@ const ProjectMenu: FC<Props> = ({ project, startEditing }) => {
<Menu.Item
onClick={e => {
e.stopPropagation();
onDeleteProject(project);
projectsCrud.onDelete(project);
}}>
<Group wrap={"nowrap"}>
<IconTrash />

View File

@ -17,7 +17,7 @@ const ProjectMobile: FC<Props> = ({
setSelectedProjectId,
closeDrawer,
}) => {
const { onUpdateProject } = useProjectsContext();
const { projectsCrud } = useProjectsContext();
const startEditing = () => {
modals.openContextModal({
@ -25,7 +25,7 @@ const ProjectMobile: FC<Props> = ({
title: "Редактирование проекта",
withCloseButton: true,
innerProps: {
onComplete: name => onUpdateProject(project.id, { name }),
onComplete: name => projectsCrud.onUpdate(project.id, { name }),
defaultValue: project.name,
},
});

View File

@ -24,7 +24,7 @@ const useDealsAndStatusesDnd = (): ReturnType => {
const swiperRef = useRef<SwiperRef>(null);
const [activeDeal, setActiveDeal] = useState<DealSchema | null>(null);
const [activeStatus, setActiveStatus] = useState<StatusSchema | null>(null);
const { statuses, setStatuses, onUpdateStatus } = useStatusesContext();
const { statuses, setStatuses, statusesCrud } = useStatusesContext();
const { deals, setDeals, updateDeal } = useDealsContext();
const sortedStatuses = useMemo(() => sortByLexorank(statuses), [statuses]);
const isMobile = useIsMobile();
@ -229,7 +229,7 @@ const useDealsAndStatusesDnd = (): ReturnType => {
};
const onStatusDragEnd = (statusId: number, lexorank: string) => {
onUpdateStatus(statusId, { lexorank });
statusesCrud.onUpdate(statusId, { lexorank });
};
const handleDealDragEnd = (activeId: number | string, over: Over) => {

View File

@ -0,0 +1,32 @@
import { Options } from "@/lib/client";
export type BaseEntity = {
id: number;
name: string;
lexorank?: string;
};
export type CreateMutationOptions = Options<{
body: any;
query: undefined;
path: undefined;
url: string;
}>;
export type UpdateMutationOptions = Options<{
body: any;
query: undefined;
path: {
pk: number;
};
url: string;
}>;
export type DeleteMutationOptions = Options<{
body: undefined;
query: undefined;
path: {
pk: number;
};
url: string;
}>;

View File

@ -3,46 +3,23 @@ import { useMutation, UseMutationOptions } from "@tanstack/react-query";
import { AxiosError } from "axios";
import { Text } from "@mantine/core";
import { modals } from "@mantine/modals";
import { HttpValidationError, Options } from "@/lib/client";
import {
BaseEntity,
CreateMutationOptions,
DeleteMutationOptions,
UpdateMutationOptions,
} from "@/hooks/baseCrud/types";
import { HttpValidationError } from "@/lib/client";
import { notifications } from "@/lib/notifications";
import { sortByLexorank } from "@/utils/lexorank";
type BaseEntity = {
id: number;
name: string;
lexorank?: string;
type CrudOperations<TEntity, TUpdate> = {
onCreate: (name: string) => void;
onUpdate: (id: number, update: TUpdate) => void;
onDelete: (entity: TEntity) => void;
};
type CreateMutationOptions = Options<{
body: any;
query: undefined;
path: undefined;
url: string;
}>;
type UpdateMutationOptions = Options<{
body: any;
query: undefined;
path: {
pk: number;
};
url: string;
}>;
type DeleteMutationOptions = Options<{
body: undefined;
query: undefined;
path: {
pk: number;
};
url: string;
}>;
type UseEntityOperationsProps<
TEntity extends BaseEntity,
TUpdate,
TCreate,
> = {
type UseEntityOperationsProps<TEntity extends BaseEntity, TUpdate, TCreate> = {
entities: TEntity[];
setEntities: React.Dispatch<React.SetStateAction<TEntity[]>>;
refetch: () => void;
@ -80,7 +57,10 @@ const useCrudOperations = <
getCreateEntity,
getUpdateEntity,
getDeleteConfirmTitle,
}: UseEntityOperationsProps<TEntity, TUpdate, TCreate>) => {
}: UseEntityOperationsProps<TEntity, TUpdate, TCreate>): CrudOperations<
TEntity,
TUpdate
> => {
const onError = (error: AxiosError<HttpValidationError>) => {
console.error(error);
notifications.error({

View File

@ -1,6 +1,6 @@
import React from "react";
import { LexoRank } from "lexorank";
import { useCrudOperations } from "@/hooks/base";
import { useCrudOperations } from "@/hooks/baseCrud";
import {
BoardSchema,
CreateBoardSchema,
@ -20,18 +20,18 @@ type UseBoardsOperationsProps = {
projectId?: number;
};
type BoardsOperations = {
export type BoardsCrud = {
onCreate: (name: string) => void;
onUpdate: (boardId: number, board: UpdateBoardSchema) => void;
onDelete: (board: BoardSchema) => void;
};
export const useBoardsOperations = ({
export const useBoardsCrud = ({
boards,
setBoards,
refetchBoards,
projectId,
}: UseBoardsOperationsProps): BoardsOperations => {
}: UseBoardsOperationsProps): BoardsCrud => {
return useCrudOperations<BoardSchema, UpdateBoardSchema, CreateBoardSchema>(
{
entities: boards,

View File

@ -1,5 +1,5 @@
import React from "react";
import { useCrudOperations } from "@/hooks/base";
import { useCrudOperations } from "@/hooks/baseCrud";
import {
CreateProjectSchema,
ProjectSchema,
@ -17,17 +17,17 @@ type Props = {
refetchProjects: () => void;
};
type ProjectsOperations = {
export type ProjectsCrud = {
onCreate: (name: string) => void;
onUpdate: (projectId: number, project: UpdateProjectSchema) => void;
onDelete: (project: ProjectSchema) => void;
};
export const useProjectsOperations = ({
export const useProjectsCrud = ({
projects,
setProjects,
refetchProjects,
}: Props): ProjectsOperations => {
}: Props): ProjectsCrud => {
return useCrudOperations<
ProjectSchema,
UpdateProjectSchema,

View File

@ -1,6 +1,6 @@
import React from "react";
import { LexoRank } from "lexorank";
import { useCrudOperations } from "@/hooks/base";
import { useCrudOperations } from "@/hooks/baseCrud";
import {
CreateStatusSchema,
StatusSchema,
@ -20,18 +20,18 @@ type Props = {
boardId?: number;
};
type StatusesOperations = {
export type StatusesCrud = {
onCreate: (name: string) => void;
onUpdate: (statusId: number, status: UpdateStatusSchema) => void;
onDelete: (status: StatusSchema) => void;
};
export const useStatusesOperations = ({
export const useStatusesCrud = ({
statuses,
setStatuses,
refetchStatuses,
boardId,
}: Props): StatusesOperations => {
}: Props): StatusesCrud => {
return useCrudOperations<
StatusSchema,
UpdateStatusSchema,