mirror of
https://github.com/docmost/docmost.git
synced 2026-07-25 02:44:42 +10:00
feat: bases - WIP
This commit is contained in:
@@ -0,0 +1,154 @@
|
||||
import { useMutation } from "@tanstack/react-query";
|
||||
import {
|
||||
createProperty,
|
||||
updateProperty,
|
||||
deleteProperty,
|
||||
reorderProperty,
|
||||
} from "@/features/base/services/base-service";
|
||||
import {
|
||||
IBase,
|
||||
IBaseProperty,
|
||||
CreatePropertyInput,
|
||||
UpdatePropertyInput,
|
||||
DeletePropertyInput,
|
||||
ReorderPropertyInput,
|
||||
UpdatePropertyResult,
|
||||
} from "@/features/base/types/base.types";
|
||||
import { notifications } from "@mantine/notifications";
|
||||
import { queryClient } from "@/main";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
export function useCreatePropertyMutation() {
|
||||
const { t } = useTranslation();
|
||||
return useMutation<IBaseProperty, Error, CreatePropertyInput>({
|
||||
mutationFn: (data) => createProperty(data),
|
||||
onSuccess: (newProperty) => {
|
||||
queryClient.setQueryData<IBase>(
|
||||
["bases", newProperty.baseId],
|
||||
(old) => {
|
||||
if (!old) return old;
|
||||
return {
|
||||
...old,
|
||||
properties: [...old.properties, newProperty],
|
||||
};
|
||||
},
|
||||
);
|
||||
},
|
||||
onError: () => {
|
||||
notifications.show({
|
||||
message: t("Failed to create property"),
|
||||
color: "red",
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export function useUpdatePropertyMutation() {
|
||||
const { t } = useTranslation();
|
||||
return useMutation<UpdatePropertyResult, Error, UpdatePropertyInput>({
|
||||
mutationFn: (data) => updateProperty(data),
|
||||
onSuccess: (result, variables) => {
|
||||
queryClient.setQueryData<IBase>(
|
||||
["bases", variables.baseId],
|
||||
(old) => {
|
||||
if (!old) return old;
|
||||
return {
|
||||
...old,
|
||||
properties: old.properties.map((p) =>
|
||||
p.id === result.property.id ? result.property : p,
|
||||
),
|
||||
};
|
||||
},
|
||||
);
|
||||
|
||||
if (result.conversionSummary || variables.type) {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: ["base-rows", variables.baseId],
|
||||
});
|
||||
}
|
||||
},
|
||||
onError: () => {
|
||||
notifications.show({
|
||||
message: t("Failed to update property"),
|
||||
color: "red",
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export function useDeletePropertyMutation() {
|
||||
const { t } = useTranslation();
|
||||
return useMutation<void, Error, DeletePropertyInput>({
|
||||
mutationFn: (data) => deleteProperty(data),
|
||||
onSuccess: (_, variables) => {
|
||||
queryClient.setQueryData<IBase>(
|
||||
["bases", variables.baseId],
|
||||
(old) => {
|
||||
if (!old) return old;
|
||||
return {
|
||||
...old,
|
||||
properties: old.properties.filter(
|
||||
(p) => p.id !== variables.propertyId,
|
||||
),
|
||||
};
|
||||
},
|
||||
);
|
||||
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: ["base-rows", variables.baseId],
|
||||
});
|
||||
},
|
||||
onError: () => {
|
||||
notifications.show({
|
||||
message: t("Failed to delete property"),
|
||||
color: "red",
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export function useReorderPropertyMutation() {
|
||||
const { t } = useTranslation();
|
||||
return useMutation<void, Error, ReorderPropertyInput, { previous: IBase | undefined }>({
|
||||
mutationFn: (data) => reorderProperty(data),
|
||||
onMutate: async (variables) => {
|
||||
await queryClient.cancelQueries({
|
||||
queryKey: ["bases", variables.baseId],
|
||||
});
|
||||
|
||||
const previous = queryClient.getQueryData<IBase>([
|
||||
"bases",
|
||||
variables.baseId,
|
||||
]);
|
||||
|
||||
queryClient.setQueryData<IBase>(
|
||||
["bases", variables.baseId],
|
||||
(old) => {
|
||||
if (!old) return old;
|
||||
return {
|
||||
...old,
|
||||
properties: old.properties.map((p) =>
|
||||
p.id === variables.propertyId
|
||||
? { ...p, position: variables.position }
|
||||
: p,
|
||||
),
|
||||
};
|
||||
},
|
||||
);
|
||||
|
||||
return { previous };
|
||||
},
|
||||
onError: (_, variables, context) => {
|
||||
if (context?.previous) {
|
||||
queryClient.setQueryData(
|
||||
["bases", variables.baseId],
|
||||
context.previous,
|
||||
);
|
||||
}
|
||||
notifications.show({
|
||||
message: t("Failed to reorder property"),
|
||||
color: "red",
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
import {
|
||||
useMutation,
|
||||
useQuery,
|
||||
UseQueryResult,
|
||||
} from "@tanstack/react-query";
|
||||
import {
|
||||
createBase,
|
||||
getBaseInfo,
|
||||
updateBase,
|
||||
deleteBase,
|
||||
} from "@/features/base/services/base-service";
|
||||
import {
|
||||
IBase,
|
||||
CreateBaseInput,
|
||||
UpdateBaseInput,
|
||||
} from "@/features/base/types/base.types";
|
||||
import { notifications } from "@mantine/notifications";
|
||||
import { queryClient } from "@/main";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
export function useBaseQuery(
|
||||
baseId: string | undefined,
|
||||
): UseQueryResult<IBase, Error> {
|
||||
return useQuery({
|
||||
queryKey: ["bases", baseId],
|
||||
queryFn: () => getBaseInfo(baseId!),
|
||||
enabled: !!baseId,
|
||||
staleTime: 5 * 60 * 1000,
|
||||
});
|
||||
}
|
||||
|
||||
export function useCreateBaseMutation() {
|
||||
const { t } = useTranslation();
|
||||
return useMutation<IBase, Error, CreateBaseInput>({
|
||||
mutationFn: (data) => createBase(data),
|
||||
onSuccess: (data) => {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: ["bases", "list", data.spaceId],
|
||||
});
|
||||
},
|
||||
onError: () => {
|
||||
notifications.show({
|
||||
message: t("Failed to create base"),
|
||||
color: "red",
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export function useUpdateBaseMutation() {
|
||||
const { t } = useTranslation();
|
||||
return useMutation<IBase, Error, UpdateBaseInput>({
|
||||
mutationFn: (data) => updateBase(data),
|
||||
onSuccess: (data) => {
|
||||
queryClient.setQueryData<IBase>(["bases", data.id], (old) => {
|
||||
if (!old) return old;
|
||||
return { ...old, ...data };
|
||||
});
|
||||
},
|
||||
onError: () => {
|
||||
notifications.show({
|
||||
message: t("Failed to update base"),
|
||||
color: "red",
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export function useDeleteBaseMutation() {
|
||||
const { t } = useTranslation();
|
||||
return useMutation<void, Error, { baseId: string; spaceId: string }>({
|
||||
mutationFn: ({ baseId }) => deleteBase(baseId),
|
||||
onSuccess: (_, { baseId, spaceId }) => {
|
||||
queryClient.removeQueries({ queryKey: ["bases", baseId] });
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: ["bases", "list", spaceId],
|
||||
});
|
||||
notifications.show({ message: t("Base deleted") });
|
||||
},
|
||||
onError: () => {
|
||||
notifications.show({
|
||||
message: t("Failed to delete base"),
|
||||
color: "red",
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,240 @@
|
||||
import {
|
||||
useInfiniteQuery,
|
||||
useMutation,
|
||||
InfiniteData,
|
||||
} from "@tanstack/react-query";
|
||||
import {
|
||||
createRow,
|
||||
updateRow,
|
||||
deleteRow,
|
||||
listRows,
|
||||
reorderRow,
|
||||
} from "@/features/base/services/base-service";
|
||||
import {
|
||||
IBaseRow,
|
||||
CreateRowInput,
|
||||
UpdateRowInput,
|
||||
DeleteRowInput,
|
||||
ReorderRowInput,
|
||||
} from "@/features/base/types/base.types";
|
||||
import { notifications } from "@mantine/notifications";
|
||||
import { queryClient } from "@/main";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { IPagination } from "@/lib/types";
|
||||
|
||||
type RowCacheContext = {
|
||||
previous: InfiniteData<IPagination<IBaseRow>> | undefined;
|
||||
};
|
||||
|
||||
export function useBaseRowsQuery(baseId: string | undefined) {
|
||||
return useInfiniteQuery({
|
||||
queryKey: ["base-rows", baseId],
|
||||
queryFn: ({ pageParam }) =>
|
||||
listRows(baseId!, { cursor: pageParam, limit: 100 }),
|
||||
enabled: !!baseId,
|
||||
initialPageParam: undefined as string | undefined,
|
||||
getNextPageParam: (lastPage: IPagination<IBaseRow>) =>
|
||||
lastPage.meta?.nextCursor ?? undefined,
|
||||
staleTime: 5 * 60 * 1000,
|
||||
});
|
||||
}
|
||||
|
||||
export function flattenRows(
|
||||
data: InfiniteData<IPagination<IBaseRow>> | undefined,
|
||||
): IBaseRow[] {
|
||||
if (!data) return [];
|
||||
return data.pages.flatMap((page) => page.items);
|
||||
}
|
||||
|
||||
export function useCreateRowMutation() {
|
||||
const { t } = useTranslation();
|
||||
return useMutation<IBaseRow, Error, CreateRowInput>({
|
||||
mutationFn: (data) => createRow(data),
|
||||
onSuccess: (newRow) => {
|
||||
queryClient.setQueryData<InfiniteData<IPagination<IBaseRow>>>(
|
||||
["base-rows", newRow.baseId],
|
||||
(old) => {
|
||||
if (!old) return old;
|
||||
const lastPageIndex = old.pages.length - 1;
|
||||
return {
|
||||
...old,
|
||||
pages: old.pages.map((page, index) => {
|
||||
if (index === lastPageIndex) {
|
||||
return { ...page, items: [...page.items, newRow] };
|
||||
}
|
||||
return page;
|
||||
}),
|
||||
};
|
||||
},
|
||||
);
|
||||
},
|
||||
onError: () => {
|
||||
notifications.show({
|
||||
message: t("Failed to create row"),
|
||||
color: "red",
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export function useUpdateRowMutation() {
|
||||
const { t } = useTranslation();
|
||||
return useMutation<IBaseRow, Error, UpdateRowInput, RowCacheContext>({
|
||||
mutationFn: (data) => updateRow(data),
|
||||
onMutate: async (variables) => {
|
||||
await queryClient.cancelQueries({
|
||||
queryKey: ["base-rows", variables.baseId],
|
||||
});
|
||||
|
||||
const previous = queryClient.getQueryData<
|
||||
InfiniteData<IPagination<IBaseRow>>
|
||||
>(["base-rows", variables.baseId]);
|
||||
|
||||
queryClient.setQueryData<InfiniteData<IPagination<IBaseRow>>>(
|
||||
["base-rows", variables.baseId],
|
||||
(old) => {
|
||||
if (!old) return old;
|
||||
return {
|
||||
...old,
|
||||
pages: old.pages.map((page) => ({
|
||||
...page,
|
||||
items: page.items.map((row) =>
|
||||
row.id === variables.rowId
|
||||
? {
|
||||
...row,
|
||||
cells: { ...row.cells, ...variables.cells },
|
||||
}
|
||||
: row,
|
||||
),
|
||||
})),
|
||||
};
|
||||
},
|
||||
);
|
||||
|
||||
return { previous };
|
||||
},
|
||||
onError: (_, variables, context) => {
|
||||
if (context?.previous) {
|
||||
queryClient.setQueryData(
|
||||
["base-rows", variables.baseId],
|
||||
context.previous,
|
||||
);
|
||||
}
|
||||
notifications.show({
|
||||
message: t("Failed to update row"),
|
||||
color: "red",
|
||||
});
|
||||
},
|
||||
onSuccess: (updatedRow) => {
|
||||
queryClient.setQueryData<InfiniteData<IPagination<IBaseRow>>>(
|
||||
["base-rows", updatedRow.baseId],
|
||||
(old) => {
|
||||
if (!old) return old;
|
||||
return {
|
||||
...old,
|
||||
pages: old.pages.map((page) => ({
|
||||
...page,
|
||||
items: page.items.map((row) =>
|
||||
row.id === updatedRow.id
|
||||
? { ...row, ...updatedRow, cells: { ...row.cells, ...updatedRow.cells } }
|
||||
: row,
|
||||
),
|
||||
})),
|
||||
};
|
||||
},
|
||||
);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export function useDeleteRowMutation() {
|
||||
const { t } = useTranslation();
|
||||
return useMutation<void, Error, DeleteRowInput, RowCacheContext>({
|
||||
mutationFn: (data) => deleteRow(data),
|
||||
onMutate: async (variables) => {
|
||||
await queryClient.cancelQueries({
|
||||
queryKey: ["base-rows", variables.baseId],
|
||||
});
|
||||
|
||||
const previous = queryClient.getQueryData<
|
||||
InfiniteData<IPagination<IBaseRow>>
|
||||
>(["base-rows", variables.baseId]);
|
||||
|
||||
queryClient.setQueryData<InfiniteData<IPagination<IBaseRow>>>(
|
||||
["base-rows", variables.baseId],
|
||||
(old) => {
|
||||
if (!old) return old;
|
||||
return {
|
||||
...old,
|
||||
pages: old.pages.map((page) => ({
|
||||
...page,
|
||||
items: page.items.filter((row) => row.id !== variables.rowId),
|
||||
})),
|
||||
};
|
||||
},
|
||||
);
|
||||
|
||||
return { previous };
|
||||
},
|
||||
onError: (_, variables, context) => {
|
||||
if (context?.previous) {
|
||||
queryClient.setQueryData(
|
||||
["base-rows", variables.baseId],
|
||||
context.previous,
|
||||
);
|
||||
}
|
||||
notifications.show({
|
||||
message: t("Failed to delete row"),
|
||||
color: "red",
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export function useReorderRowMutation() {
|
||||
const { t } = useTranslation();
|
||||
return useMutation<void, Error, ReorderRowInput, RowCacheContext>({
|
||||
mutationFn: (data) => reorderRow(data),
|
||||
onMutate: async (variables) => {
|
||||
await queryClient.cancelQueries({
|
||||
queryKey: ["base-rows", variables.baseId],
|
||||
});
|
||||
|
||||
const previous = queryClient.getQueryData<
|
||||
InfiniteData<IPagination<IBaseRow>>
|
||||
>(["base-rows", variables.baseId]);
|
||||
|
||||
queryClient.setQueryData<InfiniteData<IPagination<IBaseRow>>>(
|
||||
["base-rows", variables.baseId],
|
||||
(old) => {
|
||||
if (!old) return old;
|
||||
return {
|
||||
...old,
|
||||
pages: old.pages.map((page) => ({
|
||||
...page,
|
||||
items: page.items.map((row) =>
|
||||
row.id === variables.rowId
|
||||
? { ...row, position: variables.position }
|
||||
: row,
|
||||
),
|
||||
})),
|
||||
};
|
||||
},
|
||||
);
|
||||
|
||||
return { previous };
|
||||
},
|
||||
onError: (_, variables, context) => {
|
||||
if (context?.previous) {
|
||||
queryClient.setQueryData(
|
||||
["base-rows", variables.baseId],
|
||||
context.previous,
|
||||
);
|
||||
}
|
||||
notifications.show({
|
||||
message: t("Failed to reorder row"),
|
||||
color: "red",
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,137 @@
|
||||
import { useMutation } from "@tanstack/react-query";
|
||||
import {
|
||||
createView,
|
||||
updateView,
|
||||
deleteView,
|
||||
} from "@/features/base/services/base-service";
|
||||
import {
|
||||
IBase,
|
||||
IBaseView,
|
||||
CreateViewInput,
|
||||
UpdateViewInput,
|
||||
DeleteViewInput,
|
||||
} from "@/features/base/types/base.types";
|
||||
import { notifications } from "@mantine/notifications";
|
||||
import { queryClient } from "@/main";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
export function useCreateViewMutation() {
|
||||
const { t } = useTranslation();
|
||||
return useMutation<IBaseView, Error, CreateViewInput>({
|
||||
mutationFn: (data) => createView(data),
|
||||
onSuccess: (newView) => {
|
||||
queryClient.setQueryData<IBase>(
|
||||
["bases", newView.baseId],
|
||||
(old) => {
|
||||
if (!old) return old;
|
||||
return {
|
||||
...old,
|
||||
views: [...old.views, newView],
|
||||
};
|
||||
},
|
||||
);
|
||||
},
|
||||
onError: () => {
|
||||
notifications.show({
|
||||
message: t("Failed to create view"),
|
||||
color: "red",
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export function useUpdateViewMutation() {
|
||||
const { t } = useTranslation();
|
||||
return useMutation<IBaseView, Error, UpdateViewInput, { previous: IBase | undefined }>({
|
||||
mutationFn: (data) => updateView(data),
|
||||
onMutate: async (variables) => {
|
||||
await queryClient.cancelQueries({
|
||||
queryKey: ["bases", variables.baseId],
|
||||
});
|
||||
|
||||
const previous = queryClient.getQueryData<IBase>([
|
||||
"bases",
|
||||
variables.baseId,
|
||||
]);
|
||||
|
||||
queryClient.setQueryData<IBase>(
|
||||
["bases", variables.baseId],
|
||||
(old) => {
|
||||
if (!old) return old;
|
||||
return {
|
||||
...old,
|
||||
views: old.views.map((v) =>
|
||||
v.id === variables.viewId
|
||||
? {
|
||||
...v,
|
||||
...(variables.name !== undefined && {
|
||||
name: variables.name,
|
||||
}),
|
||||
...(variables.type !== undefined && {
|
||||
type: variables.type,
|
||||
}),
|
||||
...(variables.config !== undefined && {
|
||||
config: variables.config,
|
||||
}),
|
||||
}
|
||||
: v,
|
||||
),
|
||||
};
|
||||
},
|
||||
);
|
||||
|
||||
return { previous };
|
||||
},
|
||||
onError: (_, variables, context) => {
|
||||
if (context?.previous) {
|
||||
queryClient.setQueryData(
|
||||
["bases", variables.baseId],
|
||||
context.previous,
|
||||
);
|
||||
}
|
||||
notifications.show({
|
||||
message: t("Failed to update view"),
|
||||
color: "red",
|
||||
});
|
||||
},
|
||||
onSuccess: (updatedView) => {
|
||||
queryClient.setQueryData<IBase>(
|
||||
["bases", updatedView.baseId],
|
||||
(old) => {
|
||||
if (!old) return old;
|
||||
return {
|
||||
...old,
|
||||
views: old.views.map((v) =>
|
||||
v.id === updatedView.id ? updatedView : v,
|
||||
),
|
||||
};
|
||||
},
|
||||
);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export function useDeleteViewMutation() {
|
||||
const { t } = useTranslation();
|
||||
return useMutation<void, Error, DeleteViewInput>({
|
||||
mutationFn: (data) => deleteView(data),
|
||||
onSuccess: (_, variables) => {
|
||||
queryClient.setQueryData<IBase>(
|
||||
["bases", variables.baseId],
|
||||
(old) => {
|
||||
if (!old) return old;
|
||||
return {
|
||||
...old,
|
||||
views: old.views.filter((v) => v.id !== variables.viewId),
|
||||
};
|
||||
},
|
||||
);
|
||||
},
|
||||
onError: () => {
|
||||
notifications.show({
|
||||
message: t("Failed to delete view"),
|
||||
color: "red",
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user