refactor: base crud hook
This commit is contained in:
3
src/hooks/base/index.ts
Normal file
3
src/hooks/base/index.ts
Normal file
@ -0,0 +1,3 @@
|
||||
import useCrudOperations from "./useCrudOperations";
|
||||
|
||||
export { useCrudOperations };
|
||||
161
src/hooks/base/useCrudOperations.tsx
Normal file
161
src/hooks/base/useCrudOperations.tsx
Normal file
@ -0,0 +1,161 @@
|
||||
import React from "react";
|
||||
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 { notifications } from "@/lib/notifications";
|
||||
import { sortByLexorank } from "@/utils/lexorank";
|
||||
|
||||
type BaseEntity = {
|
||||
id: number;
|
||||
name: string;
|
||||
lexorank?: string;
|
||||
};
|
||||
|
||||
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,
|
||||
> = {
|
||||
entities: TEntity[];
|
||||
setEntities: React.Dispatch<React.SetStateAction<TEntity[]>>;
|
||||
refetch: () => void;
|
||||
mutations: {
|
||||
create: UseMutationOptions<
|
||||
any,
|
||||
AxiosError<HttpValidationError>,
|
||||
CreateMutationOptions
|
||||
>;
|
||||
update: UseMutationOptions<
|
||||
any,
|
||||
AxiosError<HttpValidationError>,
|
||||
UpdateMutationOptions
|
||||
>;
|
||||
delete: UseMutationOptions<
|
||||
any,
|
||||
AxiosError<HttpValidationError>,
|
||||
DeleteMutationOptions
|
||||
>;
|
||||
};
|
||||
getCreateEntity: (name: string) => TCreate | null;
|
||||
getUpdateEntity: (oldEntity: TEntity, update: TUpdate) => TEntity;
|
||||
getDeleteConfirmTitle: (entity: TEntity) => string;
|
||||
};
|
||||
|
||||
const useCrudOperations = <
|
||||
TEntity extends BaseEntity,
|
||||
TUpdate extends object,
|
||||
TCreate extends object,
|
||||
>({
|
||||
entities,
|
||||
setEntities,
|
||||
refetch,
|
||||
mutations,
|
||||
getCreateEntity,
|
||||
getUpdateEntity,
|
||||
getDeleteConfirmTitle,
|
||||
}: UseEntityOperationsProps<TEntity, TUpdate, TCreate>) => {
|
||||
const onError = (error: AxiosError<HttpValidationError>) => {
|
||||
console.error(error);
|
||||
notifications.error({
|
||||
message: error.response?.data?.detail as string | undefined,
|
||||
});
|
||||
refetch();
|
||||
};
|
||||
|
||||
const createMutation = useMutation({
|
||||
...mutations.create,
|
||||
onError,
|
||||
onSuccess: (res: { entity: TEntity }) => {
|
||||
setEntities([...entities, res.entity]);
|
||||
},
|
||||
});
|
||||
|
||||
const updateMutation = useMutation({
|
||||
...mutations.update,
|
||||
onError,
|
||||
});
|
||||
|
||||
const deleteMutation = useMutation({
|
||||
...mutations.delete,
|
||||
onError,
|
||||
});
|
||||
|
||||
const onCreate = (name: string) => {
|
||||
const entity = getCreateEntity(name);
|
||||
if (!entity) return;
|
||||
createMutation.mutate({
|
||||
body: {
|
||||
entity,
|
||||
},
|
||||
path: undefined,
|
||||
query: undefined,
|
||||
});
|
||||
};
|
||||
|
||||
const onUpdate = (id: number, update: TUpdate) => {
|
||||
updateMutation.mutate({
|
||||
body: {
|
||||
entity: update,
|
||||
},
|
||||
path: { pk: id },
|
||||
query: undefined,
|
||||
});
|
||||
setEntities(prev => {
|
||||
const updated = prev.map(entity =>
|
||||
entity.id === id ? getUpdateEntity(entity, update) : entity
|
||||
);
|
||||
if ("lexorank" in update) {
|
||||
return sortByLexorank(
|
||||
updated as (TEntity & { lexorank: string })[]
|
||||
);
|
||||
}
|
||||
return updated;
|
||||
});
|
||||
};
|
||||
|
||||
const onDelete = (entity: TEntity) => {
|
||||
modals.openConfirmModal({
|
||||
title: getDeleteConfirmTitle(entity),
|
||||
children: (
|
||||
<Text>Вы уверены, что хотите удалить "{entity.name}"?</Text>
|
||||
),
|
||||
labels: { confirm: "Да", cancel: "Нет" },
|
||||
confirmProps: { color: "red" },
|
||||
onConfirm: () => {
|
||||
deleteMutation.mutate({ path: { pk: entity.id } } as any);
|
||||
setEntities(prev => prev.filter(e => e.id !== entity.id));
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
return { onCreate, onUpdate, onDelete };
|
||||
};
|
||||
|
||||
export default useCrudOperations;
|
||||
@ -1,12 +1,9 @@
|
||||
import React from "react";
|
||||
import { useMutation } from "@tanstack/react-query";
|
||||
import { AxiosError } from "axios";
|
||||
import { LexoRank } from "lexorank";
|
||||
import { Text } from "@mantine/core";
|
||||
import { modals } from "@mantine/modals";
|
||||
import { useCrudOperations } from "@/hooks/base";
|
||||
import {
|
||||
BoardSchema,
|
||||
HttpValidationError,
|
||||
CreateBoardSchema,
|
||||
UpdateBoardSchema,
|
||||
} from "@/lib/client";
|
||||
import {
|
||||
@ -14,12 +11,7 @@ import {
|
||||
deleteBoardMutation,
|
||||
updateBoardMutation,
|
||||
} from "@/lib/client/@tanstack/react-query.gen";
|
||||
import { notifications } from "@/lib/notifications";
|
||||
import {
|
||||
getMaxByLexorank,
|
||||
getNewLexorank,
|
||||
sortByLexorank,
|
||||
} from "@/utils/lexorank";
|
||||
import { getMaxByLexorank, getNewLexorank } from "@/utils/lexorank";
|
||||
|
||||
type UseBoardsOperationsProps = {
|
||||
boards: BoardSchema[];
|
||||
@ -29,9 +21,9 @@ type UseBoardsOperationsProps = {
|
||||
};
|
||||
|
||||
type BoardsOperations = {
|
||||
onCreateBoard: (name: string) => void;
|
||||
onUpdateBoard: (boardId: number, board: UpdateBoardSchema) => void;
|
||||
onDeleteBoard: (board: BoardSchema) => void;
|
||||
onCreate: (name: string) => void;
|
||||
onUpdate: (boardId: number, board: UpdateBoardSchema) => void;
|
||||
onDelete: (board: BoardSchema) => void;
|
||||
};
|
||||
|
||||
export const useBoardsOperations = ({
|
||||
@ -40,95 +32,34 @@ export const useBoardsOperations = ({
|
||||
refetchBoards,
|
||||
projectId,
|
||||
}: UseBoardsOperationsProps): BoardsOperations => {
|
||||
const onError = (error: AxiosError<HttpValidationError>) => {
|
||||
console.error(error);
|
||||
notifications.error({
|
||||
message: error.response?.data?.detail as string | undefined,
|
||||
});
|
||||
refetchBoards();
|
||||
};
|
||||
|
||||
const createBoard = useMutation({
|
||||
...createBoardMutation(),
|
||||
onError,
|
||||
onSuccess: res => {
|
||||
setBoards([...boards, res.board]);
|
||||
},
|
||||
});
|
||||
|
||||
const updateBoard = useMutation({
|
||||
...updateBoardMutation(),
|
||||
onError,
|
||||
});
|
||||
|
||||
const deleteBoard = useMutation({
|
||||
...deleteBoardMutation(),
|
||||
onError,
|
||||
});
|
||||
|
||||
const onCreateBoard = (name: string) => {
|
||||
if (!projectId) return;
|
||||
const lastBoard = getMaxByLexorank(boards);
|
||||
const newLexorank = getNewLexorank(
|
||||
lastBoard ? LexoRank.parse(lastBoard.lexorank) : null
|
||||
);
|
||||
|
||||
createBoard.mutate({
|
||||
body: {
|
||||
board: {
|
||||
return useCrudOperations<BoardSchema, UpdateBoardSchema, CreateBoardSchema>(
|
||||
{
|
||||
entities: boards,
|
||||
setEntities: setBoards,
|
||||
refetch: refetchBoards,
|
||||
mutations: {
|
||||
create: createBoardMutation(),
|
||||
update: updateBoardMutation(),
|
||||
delete: deleteBoardMutation(),
|
||||
},
|
||||
getCreateEntity: name => {
|
||||
if (!projectId) return null;
|
||||
const lastBoard = getMaxByLexorank(boards);
|
||||
const newLexorank = getNewLexorank(
|
||||
lastBoard ? LexoRank.parse(lastBoard.lexorank) : null
|
||||
);
|
||||
return {
|
||||
name,
|
||||
projectId,
|
||||
lexorank: newLexorank.toString(),
|
||||
},
|
||||
};
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const onUpdateBoard = (boardId: number, board: UpdateBoardSchema) => {
|
||||
updateBoard.mutate({
|
||||
path: { boardId },
|
||||
body: { board },
|
||||
});
|
||||
|
||||
setBoards(boards =>
|
||||
sortByLexorank(
|
||||
boards.map(oldBoard =>
|
||||
oldBoard.id !== boardId
|
||||
? oldBoard
|
||||
: {
|
||||
id: oldBoard.id,
|
||||
name: board.name ? board.name : oldBoard.name,
|
||||
lexorank: board.lexorank
|
||||
? board.lexorank
|
||||
: oldBoard.lexorank,
|
||||
}
|
||||
)
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
const onDeleteBoard = (board: BoardSchema) => {
|
||||
modals.openConfirmModal({
|
||||
title: "Удаление доски",
|
||||
children: (
|
||||
<Text>
|
||||
Вы уверены, что хотите удалить доску "{board.name}"?
|
||||
</Text>
|
||||
),
|
||||
labels: { confirm: "Да", cancel: "Нет" },
|
||||
confirmProps: { color: "red" },
|
||||
onConfirm: () => {
|
||||
deleteBoard.mutate({
|
||||
path: { boardId: board.id },
|
||||
});
|
||||
setBoards(boards => boards.filter(b => b.id !== board.id));
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
return {
|
||||
onCreateBoard,
|
||||
onUpdateBoard,
|
||||
onDeleteBoard,
|
||||
};
|
||||
getUpdateEntity: (old, update) => ({
|
||||
...old,
|
||||
name: update.name ?? old.name,
|
||||
lexorank: update.lexorank ?? old.lexorank,
|
||||
}),
|
||||
getDeleteConfirmTitle: () => "Удаление доски",
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
@ -1,10 +1,7 @@
|
||||
import React from "react";
|
||||
import { useMutation } from "@tanstack/react-query";
|
||||
import { AxiosError } from "axios";
|
||||
import { Text } from "@mantine/core";
|
||||
import { modals } from "@mantine/modals";
|
||||
import { useCrudOperations } from "@/hooks/base";
|
||||
import {
|
||||
HttpValidationError,
|
||||
CreateProjectSchema,
|
||||
ProjectSchema,
|
||||
UpdateProjectSchema,
|
||||
} from "@/lib/client";
|
||||
@ -13,7 +10,6 @@ import {
|
||||
deleteProjectMutation,
|
||||
updateProjectMutation,
|
||||
} from "@/lib/client/@tanstack/react-query.gen";
|
||||
import { notifications } from "@/lib/notifications";
|
||||
|
||||
type Props = {
|
||||
projects: ProjectSchema[];
|
||||
@ -22,9 +18,9 @@ type Props = {
|
||||
};
|
||||
|
||||
type ProjectsOperations = {
|
||||
onCreateProject: (name: string) => void;
|
||||
onUpdateProject: (projectId: number, project: UpdateProjectSchema) => void;
|
||||
onDeleteProject: (project: ProjectSchema) => void;
|
||||
onCreate: (name: string) => void;
|
||||
onUpdate: (projectId: number, project: UpdateProjectSchema) => void;
|
||||
onDelete: (project: ProjectSchema) => void;
|
||||
};
|
||||
|
||||
export const useProjectsOperations = ({
|
||||
@ -32,87 +28,24 @@ export const useProjectsOperations = ({
|
||||
setProjects,
|
||||
refetchProjects,
|
||||
}: Props): ProjectsOperations => {
|
||||
const onError = (error: AxiosError<HttpValidationError>) => {
|
||||
console.error(error);
|
||||
notifications.error({
|
||||
message: error.response?.data?.detail as string | undefined,
|
||||
});
|
||||
refetchProjects();
|
||||
};
|
||||
|
||||
const createProject = useMutation({
|
||||
...createProjectMutation(),
|
||||
onError,
|
||||
onSuccess: res => {
|
||||
setProjects([...projects, res.project]);
|
||||
return useCrudOperations<
|
||||
ProjectSchema,
|
||||
UpdateProjectSchema,
|
||||
CreateProjectSchema
|
||||
>({
|
||||
entities: projects,
|
||||
setEntities: setProjects,
|
||||
refetch: refetchProjects,
|
||||
mutations: {
|
||||
create: createProjectMutation(),
|
||||
update: updateProjectMutation(),
|
||||
delete: deleteProjectMutation(),
|
||||
},
|
||||
getCreateEntity: name => ({ name }),
|
||||
getUpdateEntity: (old, update) => ({
|
||||
...old,
|
||||
name: update.name ?? old.name,
|
||||
}),
|
||||
getDeleteConfirmTitle: () => "Удаление проекта",
|
||||
});
|
||||
|
||||
const updateProject = useMutation({
|
||||
...updateProjectMutation(),
|
||||
onError,
|
||||
});
|
||||
|
||||
const deleteProject = useMutation({
|
||||
...deleteProjectMutation(),
|
||||
onError,
|
||||
});
|
||||
|
||||
const onCreateProject = (name: string) => {
|
||||
createProject.mutate({
|
||||
body: {
|
||||
project: {
|
||||
name,
|
||||
},
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const onUpdateProject = (
|
||||
projectId: number,
|
||||
project: UpdateProjectSchema
|
||||
) => {
|
||||
updateProject.mutate({
|
||||
query: { projectId },
|
||||
body: { project },
|
||||
});
|
||||
|
||||
setProjects(boards =>
|
||||
boards.map(oldProject =>
|
||||
oldProject.id !== projectId
|
||||
? oldProject
|
||||
: {
|
||||
id: oldProject.id,
|
||||
name: project.name ? project.name : oldProject.name,
|
||||
}
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
const onDeleteProject = (project: ProjectSchema) => {
|
||||
modals.openConfirmModal({
|
||||
title: "Удаление проекта",
|
||||
children: (
|
||||
<Text>
|
||||
Вы уверены, что хотите удалить проект "{project.name}"?
|
||||
</Text>
|
||||
),
|
||||
labels: { confirm: "Да", cancel: "Нет" },
|
||||
confirmProps: { color: "red" },
|
||||
onConfirm: () => {
|
||||
deleteProject.mutate({
|
||||
query: { projectId: project.id },
|
||||
});
|
||||
setProjects(projects =>
|
||||
projects.filter(p => p.id !== project.id)
|
||||
);
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
return {
|
||||
onCreateProject,
|
||||
onUpdateProject,
|
||||
onDeleteProject,
|
||||
};
|
||||
};
|
||||
|
||||
@ -1,11 +1,8 @@
|
||||
import React from "react";
|
||||
import { useMutation } from "@tanstack/react-query";
|
||||
import { AxiosError } from "axios";
|
||||
import { LexoRank } from "lexorank";
|
||||
import { Text } from "@mantine/core";
|
||||
import { modals } from "@mantine/modals";
|
||||
import { useCrudOperations } from "@/hooks/base";
|
||||
import {
|
||||
HttpValidationError,
|
||||
CreateStatusSchema,
|
||||
StatusSchema,
|
||||
UpdateStatusSchema,
|
||||
} from "@/lib/client";
|
||||
@ -14,12 +11,7 @@ import {
|
||||
deleteStatusMutation,
|
||||
updateStatusMutation,
|
||||
} from "@/lib/client/@tanstack/react-query.gen";
|
||||
import { notifications } from "@/lib/notifications";
|
||||
import {
|
||||
getMaxByLexorank,
|
||||
getNewLexorank,
|
||||
sortByLexorank,
|
||||
} from "@/utils/lexorank";
|
||||
import { getMaxByLexorank, getNewLexorank } from "@/utils/lexorank";
|
||||
|
||||
type Props = {
|
||||
statuses: StatusSchema[];
|
||||
@ -29,9 +21,9 @@ type Props = {
|
||||
};
|
||||
|
||||
type StatusesOperations = {
|
||||
onCreateStatus: (name: string) => void;
|
||||
onUpdateStatus: (statusId: number, status: UpdateStatusSchema) => void;
|
||||
onDeleteStatus: (status: StatusSchema) => void;
|
||||
onCreate: (name: string) => void;
|
||||
onUpdate: (statusId: number, status: UpdateStatusSchema) => void;
|
||||
onDelete: (status: StatusSchema) => void;
|
||||
};
|
||||
|
||||
export const useStatusesOperations = ({
|
||||
@ -40,97 +32,36 @@ export const useStatusesOperations = ({
|
||||
refetchStatuses,
|
||||
boardId,
|
||||
}: Props): StatusesOperations => {
|
||||
const onError = (error: AxiosError<HttpValidationError>) => {
|
||||
console.error(error);
|
||||
notifications.error({
|
||||
message: error.response?.data?.detail as string | undefined,
|
||||
});
|
||||
refetchStatuses();
|
||||
};
|
||||
|
||||
const createStatus = useMutation({
|
||||
...createStatusMutation(),
|
||||
onError,
|
||||
onSuccess: res => {
|
||||
setStatuses([...statuses, res.status]);
|
||||
return useCrudOperations<
|
||||
StatusSchema,
|
||||
UpdateStatusSchema,
|
||||
CreateStatusSchema
|
||||
>({
|
||||
entities: statuses,
|
||||
setEntities: setStatuses,
|
||||
refetch: refetchStatuses,
|
||||
mutations: {
|
||||
create: createStatusMutation(),
|
||||
update: updateStatusMutation(),
|
||||
delete: deleteStatusMutation(),
|
||||
},
|
||||
getCreateEntity: name => {
|
||||
if (!boardId) return null;
|
||||
const lastBoard = getMaxByLexorank(statuses);
|
||||
const newLexorank = getNewLexorank(
|
||||
lastBoard ? LexoRank.parse(lastBoard.lexorank) : null
|
||||
);
|
||||
return {
|
||||
name,
|
||||
boardId,
|
||||
lexorank: newLexorank.toString(),
|
||||
};
|
||||
},
|
||||
getUpdateEntity: (old, update) => ({
|
||||
...old,
|
||||
name: update.name ?? old.name,
|
||||
lexorank: update.lexorank ?? old.lexorank,
|
||||
}),
|
||||
getDeleteConfirmTitle: () => "Удаление статуса",
|
||||
});
|
||||
|
||||
const updateStatus = useMutation({
|
||||
...updateStatusMutation(),
|
||||
onError,
|
||||
});
|
||||
|
||||
const deleteStatus = useMutation({
|
||||
...deleteStatusMutation(),
|
||||
onError,
|
||||
});
|
||||
|
||||
const onCreateStatus = (name: string) => {
|
||||
if (!boardId) return;
|
||||
const lastStatus = getMaxByLexorank(statuses);
|
||||
const newLexorank = getNewLexorank(
|
||||
lastStatus ? LexoRank.parse(lastStatus.lexorank) : null
|
||||
);
|
||||
|
||||
createStatus.mutate({
|
||||
body: {
|
||||
status: {
|
||||
name,
|
||||
boardId,
|
||||
lexorank: newLexorank.toString(),
|
||||
},
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const onUpdateStatus = (statusId: number, status: UpdateStatusSchema) => {
|
||||
updateStatus.mutate({
|
||||
path: { statusId },
|
||||
body: { status },
|
||||
});
|
||||
|
||||
setStatuses(statuses =>
|
||||
sortByLexorank(
|
||||
statuses.map(oldStatus =>
|
||||
oldStatus.id !== statusId
|
||||
? oldStatus
|
||||
: {
|
||||
id: oldStatus.id,
|
||||
name: status.name ? status.name : oldStatus.name,
|
||||
lexorank: status.lexorank
|
||||
? status.lexorank
|
||||
: oldStatus.lexorank,
|
||||
}
|
||||
)
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
const onDeleteStatus = (status: StatusSchema) => {
|
||||
modals.openConfirmModal({
|
||||
title: "Удаление колонки",
|
||||
children: (
|
||||
<Text>
|
||||
Вы уверены, что хотите удалить колонку "{status.name}"?
|
||||
</Text>
|
||||
),
|
||||
labels: { confirm: "Да", cancel: "Нет" },
|
||||
confirmProps: { color: "red" },
|
||||
onConfirm: () => {
|
||||
deleteStatus.mutate({
|
||||
path: { statusId: status.id },
|
||||
});
|
||||
setStatuses(statuses =>
|
||||
statuses.filter(s => s.id !== status.id)
|
||||
);
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
return {
|
||||
onCreateStatus,
|
||||
onUpdateStatus,
|
||||
onDeleteStatus,
|
||||
};
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user