mirror of
https://github.com/docmost/docmost.git
synced 2026-05-07 06:23:06 +08:00
feat(base): mount draft banner and wire save-for-everyone flow
This commit is contained in:
@@ -1,5 +1,6 @@
|
|||||||
import { useCallback, useEffect, useMemo } from "react";
|
import { useCallback, useEffect, useMemo } from "react";
|
||||||
import { Text, Stack } from "@mantine/core";
|
import { Text, Stack } from "@mantine/core";
|
||||||
|
import { notifications } from "@mantine/notifications";
|
||||||
import { useAtom } from "jotai";
|
import { useAtom } from "jotai";
|
||||||
import { IconDatabase } from "@tabler/icons-react";
|
import { IconDatabase } from "@tabler/icons-react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
@@ -18,14 +19,24 @@ import {
|
|||||||
import { useUpdateRowMutation } from "@/features/base/queries/base-row-query";
|
import { useUpdateRowMutation } from "@/features/base/queries/base-row-query";
|
||||||
import { useCreateRowMutation } from "@/features/base/queries/base-row-query";
|
import { useCreateRowMutation } from "@/features/base/queries/base-row-query";
|
||||||
import { useReorderRowMutation } from "@/features/base/queries/base-row-query";
|
import { useReorderRowMutation } from "@/features/base/queries/base-row-query";
|
||||||
import { useCreateViewMutation } from "@/features/base/queries/base-view-query";
|
import {
|
||||||
|
useCreateViewMutation,
|
||||||
|
useUpdateViewMutation,
|
||||||
|
} from "@/features/base/queries/base-view-query";
|
||||||
import { activeViewIdAtom } from "@/features/base/atoms/base-atoms";
|
import { activeViewIdAtom } from "@/features/base/atoms/base-atoms";
|
||||||
import { useBaseTable } from "@/features/base/hooks/use-base-table";
|
import { useBaseTable } from "@/features/base/hooks/use-base-table";
|
||||||
import { useRowSelection } from "@/features/base/hooks/use-row-selection";
|
import { useRowSelection } from "@/features/base/hooks/use-row-selection";
|
||||||
import useCurrentUser from "@/features/user/hooks/use-current-user";
|
import useCurrentUser from "@/features/user/hooks/use-current-user";
|
||||||
import { useViewDraft } from "@/features/base/hooks/use-view-draft";
|
import { useViewDraft } from "@/features/base/hooks/use-view-draft";
|
||||||
|
import { useSpaceQuery } from "@/features/space/queries/space-query";
|
||||||
|
import { useSpaceAbility } from "@/features/space/permissions/use-space-ability";
|
||||||
|
import {
|
||||||
|
SpaceCaslAction,
|
||||||
|
SpaceCaslSubject,
|
||||||
|
} from "@/features/space/permissions/permissions.type";
|
||||||
import { GridContainer } from "@/features/base/components/grid/grid-container";
|
import { GridContainer } from "@/features/base/components/grid/grid-container";
|
||||||
import { BaseToolbar } from "@/features/base/components/base-toolbar";
|
import { BaseToolbar } from "@/features/base/components/base-toolbar";
|
||||||
|
import { BaseViewDraftBanner } from "@/features/base/components/base-view-draft-banner";
|
||||||
import { BaseTableSkeleton } from "@/features/base/components/base-table-skeleton";
|
import { BaseTableSkeleton } from "@/features/base/components/base-table-skeleton";
|
||||||
import classes from "@/features/base/styles/grid.module.css";
|
import classes from "@/features/base/styles/grid.module.css";
|
||||||
|
|
||||||
@@ -90,6 +101,17 @@ export function BaseTable({ baseId }: BaseTableProps) {
|
|||||||
// source of truth once drafts are involved.
|
// source of truth once drafts are involved.
|
||||||
const activeFilter = effectiveFilter;
|
const activeFilter = effectiveFilter;
|
||||||
const activeSorts = effectiveSorts;
|
const activeSorts = effectiveSorts;
|
||||||
|
|
||||||
|
// `useSpaceQuery` is guarded by `enabled: !!spaceId` internally, so
|
||||||
|
// passing `""` when `base` hasn't loaded yet is safe. See
|
||||||
|
// use-history-restore.tsx for the same pattern.
|
||||||
|
const { data: space } = useSpaceQuery(base?.spaceId ?? "");
|
||||||
|
const spaceAbility = useSpaceAbility(space?.membership?.permissions);
|
||||||
|
const canSave = spaceAbility.can(
|
||||||
|
SpaceCaslAction.Edit,
|
||||||
|
SpaceCaslSubject.Base,
|
||||||
|
);
|
||||||
|
|
||||||
// Hold the rows query until `base` has loaded. Otherwise the query
|
// Hold the rows query until `base` has loaded. Otherwise the query
|
||||||
// fires once with `activeFilter` / `activeSorts` still undefined
|
// fires once with `activeFilter` / `activeSorts` still undefined
|
||||||
// (a "bland" list request), then fires a second time as soon as the
|
// (a "bland" list request), then fires a second time as soon as the
|
||||||
@@ -102,6 +124,7 @@ export function BaseTable({ baseId }: BaseTableProps) {
|
|||||||
const createRowMutation = useCreateRowMutation();
|
const createRowMutation = useCreateRowMutation();
|
||||||
const reorderRowMutation = useReorderRowMutation();
|
const reorderRowMutation = useReorderRowMutation();
|
||||||
const createViewMutation = useCreateViewMutation();
|
const createViewMutation = useCreateViewMutation();
|
||||||
|
const updateViewMutation = useUpdateViewMutation();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (activeView && activeViewId !== activeView.id) {
|
if (activeView && activeViewId !== activeView.id) {
|
||||||
@@ -197,6 +220,34 @@ export function BaseTable({ baseId }: BaseTableProps) {
|
|||||||
[setDraftFilter],
|
[setDraftFilter],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const handleSaveDraft = useCallback(async () => {
|
||||||
|
if (!activeView || !base) return;
|
||||||
|
// `buildPromotedConfig` preserves all non-draft baseline fields
|
||||||
|
// (widths/order/visibility) and only overwrites filter/sorts when the
|
||||||
|
// draft has divergent values.
|
||||||
|
const config = buildPromotedConfig(activeView.config);
|
||||||
|
try {
|
||||||
|
await updateViewMutation.mutateAsync({
|
||||||
|
viewId: activeView.id,
|
||||||
|
baseId: base.id,
|
||||||
|
config,
|
||||||
|
});
|
||||||
|
resetDraft();
|
||||||
|
notifications.show({ message: t("View updated for everyone") });
|
||||||
|
} catch {
|
||||||
|
// `useUpdateViewMutation` already shows a red toast on error and
|
||||||
|
// rolls back the optimistic cache; keep the draft so the user can
|
||||||
|
// retry without re-typing.
|
||||||
|
}
|
||||||
|
}, [
|
||||||
|
activeView,
|
||||||
|
base,
|
||||||
|
buildPromotedConfig,
|
||||||
|
resetDraft,
|
||||||
|
t,
|
||||||
|
updateViewMutation,
|
||||||
|
]);
|
||||||
|
|
||||||
const handleRowReorder = useCallback(
|
const handleRowReorder = useCallback(
|
||||||
(rowId: string, targetRowId: string, dropPosition: "above" | "below") => {
|
(rowId: string, targetRowId: string, dropPosition: "above" | "below") => {
|
||||||
const remainingRows = rows.filter((r) => r.id !== rowId);
|
const remainingRows = rows.filter((r) => r.id !== rowId);
|
||||||
@@ -251,6 +302,13 @@ export function BaseTable({ baseId }: BaseTableProps) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div style={{ display: "flex", flexDirection: "column", height: "100%" }}>
|
<div style={{ display: "flex", flexDirection: "column", height: "100%" }}>
|
||||||
|
<BaseViewDraftBanner
|
||||||
|
isDirty={isDirty}
|
||||||
|
canSave={canSave}
|
||||||
|
onReset={resetDraft}
|
||||||
|
onSave={handleSaveDraft}
|
||||||
|
saving={updateViewMutation.isPending}
|
||||||
|
/>
|
||||||
<BaseToolbar
|
<BaseToolbar
|
||||||
base={base}
|
base={base}
|
||||||
activeView={effectiveView}
|
activeView={effectiveView}
|
||||||
|
|||||||
Reference in New Issue
Block a user