Add request signature dialog
Signed-off-by: Sacha Al Himdani <sacha@getprobo.com>
This commit is contained in:
committed by
Sacha Al Himdani
parent
8fd8319ad4
commit
9f03bc1873
@@ -56,7 +56,7 @@ export const paginatedPeopleQuery = graphql`
|
|||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const paginatedPeopleFragment = graphql`
|
export const paginatedPeopleFragment = graphql`
|
||||||
fragment PeopleGraphPaginatedFragment on Organization
|
fragment PeopleGraphPaginatedFragment on Organization
|
||||||
@refetchable(queryName: "PeopleListQuery")
|
@refetchable(queryName: "PeopleListQuery")
|
||||||
@argumentDefinitions(
|
@argumentDefinitions(
|
||||||
|
|||||||
@@ -39,9 +39,9 @@ import { sprintf, getDocumentTypeLabel } from "@probo/helpers";
|
|||||||
import { CreateDocumentDialog } from "./dialogs/CreateDocumentDialog";
|
import { CreateDocumentDialog } from "./dialogs/CreateDocumentDialog";
|
||||||
import type { DocumentsPageRowFragment$key } from "./__generated__/DocumentsPageRowFragment.graphql";
|
import type { DocumentsPageRowFragment$key } from "./__generated__/DocumentsPageRowFragment.graphql";
|
||||||
import { SortableTable, SortableTh } from "/components/SortableTable";
|
import { SortableTable, SortableTh } from "/components/SortableTable";
|
||||||
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";
|
import { PublishDocumentsDialog } from "./dialogs/PublishDocumentsDialog.tsx";
|
||||||
|
import { SignatureDocumentsDialog } from "./dialogs/SignatureDocumentsDialog.tsx";
|
||||||
|
|
||||||
const documentsFragment = graphql`
|
const documentsFragment = graphql`
|
||||||
fragment DocumentsPageListFragment on Organization
|
fragment DocumentsPageListFragment on Organization
|
||||||
@@ -74,19 +74,6 @@ const documentsFragment = graphql`
|
|||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const documentsSignatureMutation = graphql`
|
|
||||||
mutation DocumentsPageSignatureMutation($input: BulkRequestSignaturesInput!) {
|
|
||||||
bulkRequestSignatures(input: $input) {
|
|
||||||
documentVersionSignatureEdges {
|
|
||||||
node {
|
|
||||||
id
|
|
||||||
state
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
queryRef: PreloadedQuery<DocumentGraphListQuery>;
|
queryRef: PreloadedQuery<DocumentGraphListQuery>;
|
||||||
};
|
};
|
||||||
@@ -108,13 +95,6 @@ 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 [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"));
|
||||||
|
|
||||||
@@ -126,18 +106,6 @@ export default function DocumentsPage(props: Props) {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const requestSignatures = async () => {
|
|
||||||
await signatureMutation({
|
|
||||||
variables: {
|
|
||||||
input: {
|
|
||||||
documentIds: selection,
|
|
||||||
signatoryIds: people.map((p) => p.id),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
clear();
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
<PageHeader
|
<PageHeader
|
||||||
@@ -197,14 +165,14 @@ export default function DocumentsPage(props: Props) {
|
|||||||
>
|
>
|
||||||
<Button icon={IconCheckmark1}>{__("Publish")}</Button>
|
<Button icon={IconCheckmark1}>{__("Publish")}</Button>
|
||||||
</PublishDocumentsDialog>
|
</PublishDocumentsDialog>
|
||||||
<Button
|
<SignatureDocumentsDialog
|
||||||
variant="secondary"
|
documentIds={selection}
|
||||||
icon={IconSignature}
|
onSave={clear}
|
||||||
onClick={requestSignatures}
|
|
||||||
disabled={isSigning}
|
|
||||||
>
|
>
|
||||||
|
<Button variant="secondary" icon={IconSignature}>
|
||||||
{__("Request signature")}
|
{__("Request signature")}
|
||||||
</Button>
|
</Button>
|
||||||
|
</SignatureDocumentsDialog>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Th>
|
</Th>
|
||||||
|
|||||||
@@ -0,0 +1,172 @@
|
|||||||
|
import {
|
||||||
|
Button,
|
||||||
|
Dialog,
|
||||||
|
DialogContent,
|
||||||
|
DialogFooter,
|
||||||
|
useDialogRef,
|
||||||
|
Breadcrumb,
|
||||||
|
Tr,
|
||||||
|
Tbody,
|
||||||
|
Td,
|
||||||
|
Avatar,
|
||||||
|
Spinner,
|
||||||
|
Checkbox,
|
||||||
|
Table,
|
||||||
|
IconChevronDown,
|
||||||
|
} from "@probo/ui";
|
||||||
|
import { Suspense, type FormEventHandler, type ReactNode } from "react";
|
||||||
|
import { useTranslate } from "@probo/i18n";
|
||||||
|
import { graphql } from "relay-runtime";
|
||||||
|
import { sprintf } from "@probo/helpers";
|
||||||
|
import { useMutationWithToasts } from "/hooks/useMutationWithToasts.ts";
|
||||||
|
import { useOrganizationId } from "/hooks/useOrganizationId.ts";
|
||||||
|
import {
|
||||||
|
paginatedPeopleFragment,
|
||||||
|
paginatedPeopleQuery,
|
||||||
|
} from "/hooks/graph/PeopleGraph.ts";
|
||||||
|
import { useList } from "@probo/hooks";
|
||||||
|
import { useLazyLoadQuery, usePaginationFragment } from "react-relay";
|
||||||
|
import type { PeopleGraphPaginatedFragment$key } from "/hooks/graph/__generated__/PeopleGraphPaginatedFragment.graphql.ts";
|
||||||
|
import type { PeopleGraphPaginatedQuery } from "/hooks/graph/__generated__/PeopleGraphPaginatedQuery.graphql.ts";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
documentIds: string[];
|
||||||
|
children: ReactNode;
|
||||||
|
onSave: () => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
const documentsSignatureMutation = graphql`
|
||||||
|
mutation SignatureDocumentsDialogMutation(
|
||||||
|
$input: BulkRequestSignaturesInput!
|
||||||
|
) {
|
||||||
|
bulkRequestSignatures(input: $input) {
|
||||||
|
documentVersionSignatureEdges {
|
||||||
|
node {
|
||||||
|
id
|
||||||
|
state
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
export function SignatureDocumentsDialog({
|
||||||
|
documentIds,
|
||||||
|
children,
|
||||||
|
onSave,
|
||||||
|
}: Props) {
|
||||||
|
const { __ } = useTranslate();
|
||||||
|
const dialogRef = useDialogRef();
|
||||||
|
const { list: selectedPeople, toggle } = useList<string>([]);
|
||||||
|
const [publishMutation] = useMutationWithToasts(documentsSignatureMutation, {
|
||||||
|
successMessage: sprintf(
|
||||||
|
__("%s signature requests sent"),
|
||||||
|
documentIds.length
|
||||||
|
),
|
||||||
|
errorMessage: __("Failed to send signature requests"),
|
||||||
|
});
|
||||||
|
|
||||||
|
const onSubmit: FormEventHandler<HTMLFormElement> = async (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
await publishMutation({
|
||||||
|
variables: {
|
||||||
|
input: {
|
||||||
|
documentIds,
|
||||||
|
signatoryIds: selectedPeople,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
dialogRef.current?.close();
|
||||||
|
onSave();
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Dialog
|
||||||
|
className="max-w-xl"
|
||||||
|
ref={dialogRef}
|
||||||
|
trigger={children}
|
||||||
|
title={<Breadcrumb items={[__("Documents"), __("Signature requests")]} />}
|
||||||
|
>
|
||||||
|
<form onSubmit={onSubmit}>
|
||||||
|
<DialogContent>
|
||||||
|
<Suspense fallback={<Spinner />}>
|
||||||
|
<PeopleList onChange={toggle} selectedPeople={selectedPeople} />
|
||||||
|
</Suspense>
|
||||||
|
</DialogContent>
|
||||||
|
<DialogFooter>
|
||||||
|
<Button type="submit" disabled={selectedPeople.length === 0}>
|
||||||
|
{__("Send signature requests")}
|
||||||
|
</Button>
|
||||||
|
</DialogFooter>
|
||||||
|
</form>
|
||||||
|
</Dialog>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function PeopleList({
|
||||||
|
onChange,
|
||||||
|
selectedPeople,
|
||||||
|
}: {
|
||||||
|
onChange: (id: string) => void;
|
||||||
|
selectedPeople: string[];
|
||||||
|
}) {
|
||||||
|
const { __ } = useTranslate();
|
||||||
|
const organizationId = useOrganizationId();
|
||||||
|
const data = useLazyLoadQuery<PeopleGraphPaginatedQuery>(
|
||||||
|
paginatedPeopleQuery,
|
||||||
|
{
|
||||||
|
organizationId,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
const {
|
||||||
|
data: page,
|
||||||
|
hasNext,
|
||||||
|
loadNext,
|
||||||
|
isLoadingNext,
|
||||||
|
} = usePaginationFragment(
|
||||||
|
paginatedPeopleFragment,
|
||||||
|
data.organization as PeopleGraphPaginatedFragment$key
|
||||||
|
);
|
||||||
|
const people = page.peoples.edges.map((edge) => edge.node);
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Table className="border-none rounded-none">
|
||||||
|
<Tbody>
|
||||||
|
{people.map((person) => (
|
||||||
|
<Tr key={person.id}>
|
||||||
|
<Td width={75}>
|
||||||
|
<Checkbox
|
||||||
|
checked={selectedPeople.includes(person.id)}
|
||||||
|
onChange={() => onChange(person.id)}
|
||||||
|
/>
|
||||||
|
</Td>
|
||||||
|
<Td>
|
||||||
|
<div className="flex gap-3 items-center">
|
||||||
|
<Avatar name={person.fullName} />
|
||||||
|
<div>
|
||||||
|
<div className="text-sm">{person.fullName}</div>
|
||||||
|
<div className="text-xs text-txt-tertiary">
|
||||||
|
{person.primaryEmailAddress}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Td>
|
||||||
|
</Tr>
|
||||||
|
))}
|
||||||
|
</Tbody>
|
||||||
|
</Table>
|
||||||
|
{isLoadingNext && <Spinner className="mt-3 mx-auto" />}
|
||||||
|
{hasNext && (
|
||||||
|
<Button
|
||||||
|
variant="tertiary"
|
||||||
|
onClick={() => loadNext(20)}
|
||||||
|
className="mx-auto"
|
||||||
|
icon={IconChevronDown}
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
{sprintf(__("Show %s more"), people.length)}
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
/**
|
/**
|
||||||
* @generated SignedSource<<4c3514470041046f80f410ea5a3275d6>>
|
* @generated SignedSource<<14890a4f10a8862a8aaf30ee1dc6f065>>
|
||||||
* @lightSyntaxTransform
|
* @lightSyntaxTransform
|
||||||
* @nogrep
|
* @nogrep
|
||||||
*/
|
*/
|
||||||
@@ -14,10 +14,10 @@ export type BulkRequestSignaturesInput = {
|
|||||||
documentIds: ReadonlyArray<string>;
|
documentIds: ReadonlyArray<string>;
|
||||||
signatoryIds: ReadonlyArray<string>;
|
signatoryIds: ReadonlyArray<string>;
|
||||||
};
|
};
|
||||||
export type DocumentsPageSignatureMutation$variables = {
|
export type SignatureDocumentsDialogMutation$variables = {
|
||||||
input: BulkRequestSignaturesInput;
|
input: BulkRequestSignaturesInput;
|
||||||
};
|
};
|
||||||
export type DocumentsPageSignatureMutation$data = {
|
export type SignatureDocumentsDialogMutation$data = {
|
||||||
readonly bulkRequestSignatures: {
|
readonly bulkRequestSignatures: {
|
||||||
readonly documentVersionSignatureEdges: ReadonlyArray<{
|
readonly documentVersionSignatureEdges: ReadonlyArray<{
|
||||||
readonly node: {
|
readonly node: {
|
||||||
@@ -27,9 +27,9 @@ export type DocumentsPageSignatureMutation$data = {
|
|||||||
}>;
|
}>;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
export type DocumentsPageSignatureMutation = {
|
export type SignatureDocumentsDialogMutation = {
|
||||||
response: DocumentsPageSignatureMutation$data;
|
response: SignatureDocumentsDialogMutation$data;
|
||||||
variables: DocumentsPageSignatureMutation$variables;
|
variables: SignatureDocumentsDialogMutation$variables;
|
||||||
};
|
};
|
||||||
|
|
||||||
const node: ConcreteRequest = (function(){
|
const node: ConcreteRequest = (function(){
|
||||||
@@ -100,7 +100,7 @@ return {
|
|||||||
"argumentDefinitions": (v0/*: any*/),
|
"argumentDefinitions": (v0/*: any*/),
|
||||||
"kind": "Fragment",
|
"kind": "Fragment",
|
||||||
"metadata": null,
|
"metadata": null,
|
||||||
"name": "DocumentsPageSignatureMutation",
|
"name": "SignatureDocumentsDialogMutation",
|
||||||
"selections": (v1/*: any*/),
|
"selections": (v1/*: any*/),
|
||||||
"type": "Mutation",
|
"type": "Mutation",
|
||||||
"abstractKey": null
|
"abstractKey": null
|
||||||
@@ -109,20 +109,20 @@ return {
|
|||||||
"operation": {
|
"operation": {
|
||||||
"argumentDefinitions": (v0/*: any*/),
|
"argumentDefinitions": (v0/*: any*/),
|
||||||
"kind": "Operation",
|
"kind": "Operation",
|
||||||
"name": "DocumentsPageSignatureMutation",
|
"name": "SignatureDocumentsDialogMutation",
|
||||||
"selections": (v1/*: any*/)
|
"selections": (v1/*: any*/)
|
||||||
},
|
},
|
||||||
"params": {
|
"params": {
|
||||||
"cacheID": "a61a828bc67d8b569c38f4826b42d00f",
|
"cacheID": "4426d76c855d441caab5c932c2ceb2ba",
|
||||||
"id": null,
|
"id": null,
|
||||||
"metadata": {},
|
"metadata": {},
|
||||||
"name": "DocumentsPageSignatureMutation",
|
"name": "SignatureDocumentsDialogMutation",
|
||||||
"operationKind": "mutation",
|
"operationKind": "mutation",
|
||||||
"text": "mutation DocumentsPageSignatureMutation(\n $input: BulkRequestSignaturesInput!\n) {\n bulkRequestSignatures(input: $input) {\n documentVersionSignatureEdges {\n node {\n id\n state\n }\n }\n }\n}\n"
|
"text": "mutation SignatureDocumentsDialogMutation(\n $input: BulkRequestSignaturesInput!\n) {\n bulkRequestSignatures(input: $input) {\n documentVersionSignatureEdges {\n node {\n id\n state\n }\n }\n }\n}\n"
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
})();
|
})();
|
||||||
|
|
||||||
(node as any).hash = "1cb2b7dfa938b35cf75b09b51cdd2dca";
|
(node as any).hash = "1dbd9a7870416bf75d0a6fe44ebde408";
|
||||||
|
|
||||||
export default node;
|
export default node;
|
||||||
Reference in New Issue
Block a user