188 lines
5.1 KiB
TypeScript
188 lines
5.1 KiB
TypeScript
import React from "react";
|
||
import {
|
||
useMutation,
|
||
UseMutationOptions,
|
||
useQueryClient,
|
||
} from "@tanstack/react-query";
|
||
import { AxiosError } from "axios";
|
||
import { Text } from "@mantine/core";
|
||
import { modals } from "@mantine/modals";
|
||
import { HttpValidationError } from "@/lib/client";
|
||
import { notifications } from "@/lib/notifications";
|
||
import { sortByLexorank } from "@/utils/lexorank";
|
||
import {
|
||
BaseEntity,
|
||
CreateMutationOptions,
|
||
DeleteMutationOptions,
|
||
UpdateMutationOptions,
|
||
} from "./types";
|
||
|
||
type CrudOperations<TEntity, TUpdate> = {
|
||
onCreate: (name: string) => void;
|
||
onUpdate: (id: number, update: TUpdate) => void;
|
||
onDelete: (entity: TEntity) => void;
|
||
};
|
||
|
||
type UseEntityOperationsProps<TEntity extends BaseEntity, TUpdate, TCreate> = {
|
||
key: string;
|
||
queryKey: any[];
|
||
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,
|
||
>({
|
||
key,
|
||
queryKey,
|
||
mutations,
|
||
getCreateEntity,
|
||
getUpdateEntity,
|
||
getDeleteConfirmTitle,
|
||
}: UseEntityOperationsProps<TEntity, TUpdate, TCreate>): CrudOperations<
|
||
TEntity,
|
||
TUpdate
|
||
> => {
|
||
const queryClient = useQueryClient();
|
||
|
||
const onError = (
|
||
error: AxiosError<HttpValidationError>,
|
||
_: any,
|
||
context: any
|
||
) => {
|
||
console.error(error);
|
||
notifications.error({
|
||
message: error.response?.data?.detail as string | undefined,
|
||
});
|
||
if (context?.previous) {
|
||
queryClient.setQueryData(queryKey, context.previous);
|
||
}
|
||
};
|
||
|
||
const onSettled = () => {
|
||
queryClient.invalidateQueries({
|
||
predicate: (query: { queryKey: any }) =>
|
||
query.queryKey[0]?._id === key,
|
||
});
|
||
};
|
||
|
||
const createMutation = useMutation({
|
||
...mutations.create,
|
||
onError,
|
||
onSettled,
|
||
});
|
||
|
||
const updateMutation = useMutation({
|
||
...mutations.update,
|
||
onError,
|
||
onSettled,
|
||
onMutate: async ({ body: { entity: update } }) => {
|
||
await queryClient.cancelQueries({ queryKey: [key] });
|
||
|
||
const previous = queryClient.getQueryData(queryKey);
|
||
|
||
queryClient.setQueryData(queryKey, (old: { items: TEntity[] }) => {
|
||
let updated = old.items.map((entity: TEntity) =>
|
||
entity.id === update.id
|
||
? getUpdateEntity(entity, update)
|
||
: entity
|
||
);
|
||
|
||
if ("lexorank" in update) {
|
||
updated = sortByLexorank(
|
||
updated as (TEntity & { lexorank: string })[]
|
||
);
|
||
}
|
||
|
||
return {
|
||
...old,
|
||
items: updated,
|
||
};
|
||
});
|
||
|
||
return { previous };
|
||
},
|
||
});
|
||
|
||
const deleteMutation = useMutation({
|
||
...mutations.delete,
|
||
onError,
|
||
onSettled,
|
||
onMutate: async ({ path: { pk } }) => {
|
||
await queryClient.cancelQueries({ queryKey: [key] });
|
||
|
||
const previous = queryClient.getQueryData(queryKey);
|
||
|
||
queryClient.setQueryData(queryKey, (old: { items: TEntity[] }) => {
|
||
const filtered = old.items.filter(e => e.id !== pk);
|
||
return {
|
||
...old,
|
||
items: filtered,
|
||
};
|
||
});
|
||
|
||
return { previous };
|
||
},
|
||
});
|
||
|
||
const onCreate = (name: string) => {
|
||
const entity = getCreateEntity(name);
|
||
if (!entity) return;
|
||
createMutation.mutate({
|
||
body: {
|
||
entity,
|
||
},
|
||
path: undefined,
|
||
query: undefined,
|
||
});
|
||
};
|
||
|
||
const onUpdate = async (id: number, update: TUpdate) => {
|
||
updateMutation.mutate({
|
||
body: {
|
||
entity: update,
|
||
},
|
||
path: { pk: id },
|
||
query: undefined,
|
||
});
|
||
};
|
||
|
||
const onDelete = (entity: TEntity, onSuccess?: () => void) => {
|
||
modals.openConfirmModal({
|
||
title: getDeleteConfirmTitle(entity),
|
||
children: (
|
||
<Text>Вы уверены, что хотите удалить "{entity.name}"?</Text>
|
||
),
|
||
confirmProps: { color: "red" },
|
||
onConfirm: () => {
|
||
deleteMutation.mutate({ path: { pk: entity.id } } as any);
|
||
onSuccess && onSuccess();
|
||
},
|
||
});
|
||
};
|
||
|
||
return { onCreate, onUpdate, onDelete };
|
||
};
|
||
|
||
export default useCrudOperations;
|