Files
probo/apps/console/src/pages/organizations/documents/DocumentLayoutLoader.tsx
Sacha Al Himdani 03708d45c3 Consolidate document draft management into updateDocument
Replace the three separate draft mutations (createDraftDocumentVersion,
updateDocumentVersion, deleteDraftDocumentVersion) with automatic draft
lifecycle management inside updateDocument. The backend now auto-creates
a draft when a published document is edited, updates the existing draft
on subsequent edits, and auto-deletes the draft when content reverts to
match the published version.

A new deleteDocumentDraft mutation provides explicit draft deletion.

Backend:
- Merge version-level fields (content, title, classification,
  documentType) into UpdateDocumentRequest
- Convert CreateDraft, UpdateVersion, DeleteDraft into private
  transaction helpers called from Update
- Update returns (*Document, *DocumentVersion, error) with the version
  present only when a draft exists

Frontend:
- Remove all create/update/delete draft mutations from components
- Auto-save via updateDocument with layout refetch on draft status
  transitions while preserving editor cursor (data-generation key)
- Title, type, and classification editable on published versions
  (backend auto-creates draft)
- Forms use react-hook-form values option to stay synced with Relay
  fragment data across draft/publish transitions

API surface (GraphQL, MCP, CLI, n8n) updated consistently:
- Removed: createDraftDocumentVersion, updateDocumentVersion,
  deleteDraftDocumentVersion
- Added: deleteDocumentDraft (document-level)
- Updated: updateDocument accepts content, classification, documentType

Signed-off-by: Sacha Al Himdani <sacha@getprobo.com>
2026-04-14 15:01:20 +02:00

65 lines
2.3 KiB
TypeScript

// Copyright (c) 2026 Probo Inc <hello@getprobo.com>.
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
import { Suspense, useCallback, useEffect } from "react";
import { useQueryLoader } from "react-relay";
import { useParams } from "react-router";
import type { DocumentLayoutQuery } from "#/__generated__/core/DocumentLayoutQuery.graphql";
import { PageSkeleton } from "#/components/skeletons/PageSkeleton";
import { CoreRelayProvider } from "#/providers/CoreRelayProvider";
import { DocumentLayout, documentLayoutQuery } from "./DocumentLayout";
function DocumentLayoutQueryLoader() {
const { documentId, versionId } = useParams();
if (!documentId) {
throw new Error(":documentId missing in route params");
}
const [queryRef, loadQuery] = useQueryLoader<DocumentLayoutQuery>(documentLayoutQuery);
useEffect(() => {
if (!queryRef) {
loadQuery({
documentId,
versionId: versionId ?? "",
versionSpecified: !!versionId,
});
}
});
const onRefetch = useCallback(() => {
loadQuery(
{ documentId, versionId: versionId ?? "", versionSpecified: !!versionId },
{ fetchPolicy: "store-and-network" },
);
}, [documentId, versionId, loadQuery]);
if (!queryRef) return <PageSkeleton />;
return <DocumentLayout queryRef={queryRef} onRefetch={onRefetch} />;
}
export default function DocumentLayoutLoader() {
const { documentId, versionId } = useParams();
return (
<CoreRelayProvider>
<Suspense key={`${documentId}-${versionId}`} fallback={<PageSkeleton />}>
<DocumentLayoutQueryLoader />
</Suspense>
</CoreRelayProvider>
);
}