Add changelog dialog when bulk publishing

Signed-off-by: Sacha Al Himdani <sacha@getprobo.com>
This commit is contained in:
Grafikart
2025-07-11 15:05:00 +02:00
committed by Sacha Al Himdani
parent 75656b1ae1
commit 8fd8319ad4
3 changed files with 152 additions and 65 deletions

View File

@@ -41,6 +41,7 @@ import type { DocumentsPageRowFragment$key } from "./__generated__/DocumentsPage
import { SortableTable, SortableTh } from "/components/SortableTable"; import { SortableTable, SortableTh } from "/components/SortableTable";
import { useMutationWithToasts } from "/hooks/useMutationWithToasts.ts"; import { useMutationWithToasts } from "/hooks/useMutationWithToasts.ts";
import { usePeople } from "/hooks/graph/PeopleGraph.ts"; import { usePeople } from "/hooks/graph/PeopleGraph.ts";
import { PublishDocumentsDialog } from "./dialogs/PublishDocumentsDialog.tsx";
const documentsFragment = graphql` const documentsFragment = graphql`
fragment DocumentsPageListFragment on Organization fragment DocumentsPageListFragment on Organization
@@ -73,25 +74,8 @@ const documentsFragment = graphql`
} }
`; `;
const documentsPublishMutation = graphql`
mutation DocumentsPagePublishMutation(
$input: BulkPublishDocumentVersionsInput!
) {
bulkPublishDocumentVersions(input: $input) {
documentEdges {
node {
id
...DocumentsPageRowFragment
}
}
}
}
`;
const documentsSignatureMutation = graphql` const documentsSignatureMutation = graphql`
mutation DocumentsPageSignatureMutation( mutation DocumentsPageSignatureMutation($input: BulkRequestSignaturesInput!) {
$input: BulkRequestSignaturesInput!
) {
bulkRequestSignatures(input: $input) { bulkRequestSignatures(input: $input) {
documentVersionSignatureEdges { documentVersionSignatureEdges {
node { node {
@@ -112,11 +96,11 @@ export default function DocumentsPage(props: Props) {
const organization = usePreloadedQuery( const organization = usePreloadedQuery(
documentsQuery, documentsQuery,
props.queryRef, props.queryRef
).organization; ).organization;
const pagination = usePaginationFragment( const pagination = usePaginationFragment(
documentsFragment, documentsFragment,
organization as DocumentsPageListFragment$key, organization as DocumentsPageListFragment$key
); );
const documents = pagination.data.documents.edges.map((edge) => edge.node); const documents = pagination.data.documents.edges.map((edge) => edge.node);
@@ -124,15 +108,13 @@ export default function DocumentsPage(props: Props) {
const [sendSigningNotifications] = useSendSigningNotificationsMutation(); const [sendSigningNotifications] = useSendSigningNotificationsMutation();
const { list: selection, toggle, clear, reset } = useList<string>([]); const { list: selection, toggle, clear, reset } = useList<string>([]);
const [publishMutation, isPublishing] = useMutationWithToasts(documentsPublishMutation, { const [signatureMutation, isSigning] = useMutationWithToasts(
successMessage: sprintf(__("%s documents published"), selection.length), documentsSignatureMutation,
errorMessage: sprintf(__("Failed to publish %s documents"), selection.length), {
}) successMessage: __("Signature request send successfully"),
errorMessage: __("Failed to send signature requests"),
const [signatureMutation, isSigning] = useMutationWithToasts(documentsSignatureMutation, { }
successMessage: __("Signature request send successfully"), );
errorMessage: __("Failed to send signature requests"),
})
const people = usePeople(organization.id); const people = usePeople(organization.id);
usePageTitle(__("Documents")); usePageTitle(__("Documents"));
@@ -144,29 +126,17 @@ export default function DocumentsPage(props: Props) {
}); });
}; };
const publish = async () => {
await publishMutation({
variables: {
input: {
documentIds: selection,
changelog: '',
}
}
})
clear();
}
const requestSignatures = async () => { const requestSignatures = async () => {
await signatureMutation({ await signatureMutation({
variables: { variables: {
input: { input: {
documentIds: selection, documentIds: selection,
signatoryIds: people.map(p => p.id), signatoryIds: people.map((p) => p.id),
} },
} },
}) });
clear(); clear();
} };
return ( return (
<div className="space-y-6"> <div className="space-y-6">
@@ -221,8 +191,18 @@ export default function DocumentsPage(props: Props) {
</button> </button>
</div> </div>
<div className="flex gap-2"> <div className="flex gap-2">
<Button icon={IconCheckmark1} onClick={publish} disabled={isPublishing}>{__("Publish")}</Button> <PublishDocumentsDialog
<Button variant="secondary" icon={IconSignature} onClick={requestSignatures} disabled={isSigning}> documentIds={selection}
onSave={clear}
>
<Button icon={IconCheckmark1}>{__("Publish")}</Button>
</PublishDocumentsDialog>
<Button
variant="secondary"
icon={IconSignature}
onClick={requestSignatures}
disabled={isSigning}
>
{__("Request signature")} {__("Request signature")}
</Button> </Button>
</div> </div>
@@ -293,14 +273,14 @@ function DocumentRow({
}) { }) {
const document = useFragment<DocumentsPageRowFragment$key>( const document = useFragment<DocumentsPageRowFragment$key>(
rowFragment, rowFragment,
documentKey, documentKey
); );
const lastVersion = document.versions.edges[0].node; const lastVersion = document.versions.edges[0].node;
const isDraft = lastVersion.status === "DRAFT"; const isDraft = lastVersion.status === "DRAFT";
const { __, dateFormat } = useTranslate(); const { __, dateFormat } = useTranslate();
const signatures = lastVersion.signatures.edges.map((edge) => edge.node); const signatures = lastVersion.signatures.edges.map((edge) => edge.node);
const signedCount = signatures.filter( const signedCount = signatures.filter(
(signature) => signature.state === "SIGNED", (signature) => signature.state === "SIGNED"
).length; ).length;
const [deleteDocument] = useDeleteDocumentMutation(); const [deleteDocument] = useDeleteDocumentMutation();
const confirm = useConfirm(); const confirm = useConfirm();
@@ -317,11 +297,11 @@ function DocumentRow({
{ {
message: sprintf( message: sprintf(
__( __(
'This will permanently delete the document "%s". This action cannot be undone.', 'This will permanently delete the document "%s". This action cannot be undone.'
), ),
document.title, document.title
), ),
}, }
); );
}; };

View File

@@ -0,0 +1,107 @@
import {
Button,
Dialog,
DialogContent,
DialogFooter,
Field,
useDialogRef,
Breadcrumb,
} from "@probo/ui";
import { type ReactNode } from "react";
import { useTranslate } from "@probo/i18n";
import z from "zod";
import { useFormWithSchema } from "/hooks/useFormWithSchema.ts";
import { graphql } from "relay-runtime";
import { sprintf } from "@probo/helpers";
import { useMutationWithToasts } from "/hooks/useMutationWithToasts.ts";
type Props = {
documentIds: string[];
children: ReactNode;
onSave: () => void;
};
const documentsPublishMutation = graphql`
mutation PublishDocumentsDialogMutation(
$input: BulkPublishDocumentVersionsInput!
) {
bulkPublishDocumentVersions(input: $input) {
documentEdges {
node {
id
...DocumentsPageRowFragment
}
}
}
}
`;
const schema = z.object({
changelog: z.string().min(1, "Changelog is required"),
});
export function PublishDocumentsDialog({
documentIds,
children,
onSave,
}: Props) {
const { __ } = useTranslate();
const dialogRef = useDialogRef();
const {
handleSubmit,
register,
formState: { isSubmitting, errors },
} = useFormWithSchema(schema, {
defaultValues: {
changelog: "",
},
});
const onSubmit = handleSubmit(async (data) => {
await publishMutation({
variables: {
input: {
documentIds,
changelog: data.changelog,
},
},
});
dialogRef.current?.close();
onSave();
});
const [publishMutation] = useMutationWithToasts(documentsPublishMutation, {
successMessage: sprintf(__("%s documents published"), documentIds.length),
errorMessage: sprintf(
__("Failed to publish %s documents"),
documentIds.length
),
});
return (
<Dialog
className="max-w-xl"
ref={dialogRef}
trigger={children}
title={<Breadcrumb items={[__("Documents"), __("Publish documents")]} />}
>
<form onSubmit={onSubmit}>
<DialogContent padded>
<Field
id="changelog"
aria-label={__("Changelog")}
required
variant="title"
placeholder={__("Changelog")}
{...register("changelog")}
error={errors.changelog?.message}
/>
</DialogContent>
<DialogFooter>
<Button type="submit" disabled={isSubmitting}>
{sprintf(__("Publish %s documents"), documentIds.length)}
</Button>
</DialogFooter>
</form>
</Dialog>
);
}

View File

@@ -1,5 +1,5 @@
/** /**
* @generated SignedSource<<ceff42181ddc76c55b97c976cfb1b697>> * @generated SignedSource<<2961ff4f50f912f350acab5b0a341042>>
* @lightSyntaxTransform * @lightSyntaxTransform
* @nogrep * @nogrep
*/ */
@@ -14,10 +14,10 @@ export type BulkPublishDocumentVersionsInput = {
changelog: string; changelog: string;
documentIds: ReadonlyArray<string>; documentIds: ReadonlyArray<string>;
}; };
export type DocumentsPagePublishMutation$variables = { export type PublishDocumentsDialogMutation$variables = {
input: BulkPublishDocumentVersionsInput; input: BulkPublishDocumentVersionsInput;
}; };
export type DocumentsPagePublishMutation$data = { export type PublishDocumentsDialogMutation$data = {
readonly bulkPublishDocumentVersions: { readonly bulkPublishDocumentVersions: {
readonly documentEdges: ReadonlyArray<{ readonly documentEdges: ReadonlyArray<{
readonly node: { readonly node: {
@@ -27,9 +27,9 @@ export type DocumentsPagePublishMutation$data = {
}>; }>;
}; };
}; };
export type DocumentsPagePublishMutation = { export type PublishDocumentsDialogMutation = {
response: DocumentsPagePublishMutation$data; response: PublishDocumentsDialogMutation$data;
variables: DocumentsPagePublishMutation$variables; variables: PublishDocumentsDialogMutation$variables;
}; };
const node: ConcreteRequest = (function(){ const node: ConcreteRequest = (function(){
@@ -59,7 +59,7 @@ return {
"argumentDefinitions": (v0/*: any*/), "argumentDefinitions": (v0/*: any*/),
"kind": "Fragment", "kind": "Fragment",
"metadata": null, "metadata": null,
"name": "DocumentsPagePublishMutation", "name": "PublishDocumentsDialogMutation",
"selections": [ "selections": [
{ {
"alias": null, "alias": null,
@@ -108,7 +108,7 @@ return {
"operation": { "operation": {
"argumentDefinitions": (v0/*: any*/), "argumentDefinitions": (v0/*: any*/),
"kind": "Operation", "kind": "Operation",
"name": "DocumentsPagePublishMutation", "name": "PublishDocumentsDialogMutation",
"selections": [ "selections": [
{ {
"alias": null, "alias": null,
@@ -288,16 +288,16 @@ return {
] ]
}, },
"params": { "params": {
"cacheID": "7cd29425535c6d30017201fee219447b", "cacheID": "bcc8589071b241670107f00c127f70d0",
"id": null, "id": null,
"metadata": {}, "metadata": {},
"name": "DocumentsPagePublishMutation", "name": "PublishDocumentsDialogMutation",
"operationKind": "mutation", "operationKind": "mutation",
"text": "mutation DocumentsPagePublishMutation(\n $input: BulkPublishDocumentVersionsInput!\n) {\n bulkPublishDocumentVersions(input: $input) {\n documentEdges {\n node {\n id\n ...DocumentsPageRowFragment\n }\n }\n }\n}\n\nfragment DocumentsPageRowFragment on Document {\n id\n title\n description\n documentType\n updatedAt\n owner {\n id\n fullName\n }\n versions(first: 1) {\n edges {\n node {\n id\n status\n signatures(first: 100) {\n edges {\n node {\n id\n state\n }\n }\n }\n }\n }\n }\n}\n" "text": "mutation PublishDocumentsDialogMutation(\n $input: BulkPublishDocumentVersionsInput!\n) {\n bulkPublishDocumentVersions(input: $input) {\n documentEdges {\n node {\n id\n ...DocumentsPageRowFragment\n }\n }\n }\n}\n\nfragment DocumentsPageRowFragment on Document {\n id\n title\n description\n documentType\n updatedAt\n owner {\n id\n fullName\n }\n versions(first: 1) {\n edges {\n node {\n id\n status\n signatures(first: 100) {\n edges {\n node {\n id\n state\n }\n }\n }\n }\n }\n }\n}\n"
} }
}; };
})(); })();
(node as any).hash = "baea3e7dce61a42bf5bf2caefda998ea"; (node as any).hash = "c9d699258bb8638d5474ab5e6b5e4585";
export default node; export default node;