@@ -4,6 +4,7 @@ import { useMutationWithToasts } from "../useMutationWithToasts";
|
||||
import type { DocumentGraphDeleteMutation } from "./__generated__/DocumentGraphDeleteMutation.graphql";
|
||||
import type { DocumentGraphSendSigningNotificationsMutation } from "./__generated__/DocumentGraphSendSigningNotificationsMutation.graphql";
|
||||
import type { DocumentGraphDeleteDraftMutation } from "./__generated__/DocumentGraphDeleteDraftMutation.graphql";
|
||||
import type { DocumentGraphBulkExportDocumentsMutation } from "./__generated__/DocumentGraphBulkExportDocumentsMutation.graphql";
|
||||
|
||||
export const documentsQuery = graphql`
|
||||
query DocumentGraphListQuery($organizationId: ID!) {
|
||||
@@ -61,6 +62,28 @@ export function useDeleteDraftDocumentVersionMutation() {
|
||||
);
|
||||
}
|
||||
|
||||
const bulkDeleteDocumentsMutation = graphql`
|
||||
mutation DocumentGraphBulkDeleteDocumentsMutation(
|
||||
$input: BulkDeleteDocumentsInput!
|
||||
) {
|
||||
bulkDeleteDocuments(input: $input) {
|
||||
deletedDocumentIds @deleteRecord
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export function useBulkDeleteDocumentsMutation() {
|
||||
const { __ } = useTranslate();
|
||||
|
||||
return useMutationWithToasts(
|
||||
bulkDeleteDocumentsMutation,
|
||||
{
|
||||
successMessage: __("Documents deleted successfully."),
|
||||
errorMessage: __("Failed to delete documents. Please try again."),
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
const sendSigningNotificationsMutation = graphql`
|
||||
mutation DocumentGraphSendSigningNotificationsMutation(
|
||||
$input: SendSigningNotificationsInput!
|
||||
@@ -83,6 +106,28 @@ export function useSendSigningNotificationsMutation() {
|
||||
);
|
||||
}
|
||||
|
||||
const bulkExportDocumentsMutation = graphql`
|
||||
mutation DocumentGraphBulkExportDocumentsMutation(
|
||||
$input: BulkExportDocumentsInput!
|
||||
) {
|
||||
bulkExportDocuments(input: $input) {
|
||||
exportJobId
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export function useBulkExportDocumentsMutation() {
|
||||
const { __ } = useTranslate();
|
||||
|
||||
return useMutationWithToasts<DocumentGraphBulkExportDocumentsMutation>(
|
||||
bulkExportDocumentsMutation,
|
||||
{
|
||||
successMessage: __("Document export started successfully. You will receive an email when the export is ready."),
|
||||
errorMessage: __("Failed to start document export. Please try again."),
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
export const documentNodeQuery = graphql`
|
||||
query DocumentGraphNodeQuery($documentId: ID!) {
|
||||
node(id: $documentId) {
|
||||
|
||||
115
apps/console/src/hooks/graph/__generated__/DocumentGraphBulkDeleteDocumentsMutation.graphql.ts
generated
Normal file
115
apps/console/src/hooks/graph/__generated__/DocumentGraphBulkDeleteDocumentsMutation.graphql.ts
generated
Normal file
@@ -0,0 +1,115 @@
|
||||
/**
|
||||
* @generated SignedSource<<a26f31885b4f92b2ad9398bce7f538cf>>
|
||||
* @lightSyntaxTransform
|
||||
* @nogrep
|
||||
*/
|
||||
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
// @ts-nocheck
|
||||
|
||||
import { ConcreteRequest } from 'relay-runtime';
|
||||
export type BulkDeleteDocumentsInput = {
|
||||
documentIds: ReadonlyArray<string>;
|
||||
};
|
||||
export type DocumentGraphBulkDeleteDocumentsMutation$variables = {
|
||||
input: BulkDeleteDocumentsInput;
|
||||
};
|
||||
export type DocumentGraphBulkDeleteDocumentsMutation$data = {
|
||||
readonly bulkDeleteDocuments: {
|
||||
readonly deletedDocumentIds: ReadonlyArray<string>;
|
||||
};
|
||||
};
|
||||
export type DocumentGraphBulkDeleteDocumentsMutation = {
|
||||
response: DocumentGraphBulkDeleteDocumentsMutation$data;
|
||||
variables: DocumentGraphBulkDeleteDocumentsMutation$variables;
|
||||
};
|
||||
|
||||
const node: ConcreteRequest = (function(){
|
||||
var v0 = [
|
||||
{
|
||||
"defaultValue": null,
|
||||
"kind": "LocalArgument",
|
||||
"name": "input"
|
||||
}
|
||||
],
|
||||
v1 = [
|
||||
{
|
||||
"kind": "Variable",
|
||||
"name": "input",
|
||||
"variableName": "input"
|
||||
}
|
||||
],
|
||||
v2 = {
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "deletedDocumentIds",
|
||||
"storageKey": null
|
||||
};
|
||||
return {
|
||||
"fragment": {
|
||||
"argumentDefinitions": (v0/*: any*/),
|
||||
"kind": "Fragment",
|
||||
"metadata": null,
|
||||
"name": "DocumentGraphBulkDeleteDocumentsMutation",
|
||||
"selections": [
|
||||
{
|
||||
"alias": null,
|
||||
"args": (v1/*: any*/),
|
||||
"concreteType": "BulkDeleteDocumentsPayload",
|
||||
"kind": "LinkedField",
|
||||
"name": "bulkDeleteDocuments",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
(v2/*: any*/)
|
||||
],
|
||||
"storageKey": null
|
||||
}
|
||||
],
|
||||
"type": "Mutation",
|
||||
"abstractKey": null
|
||||
},
|
||||
"kind": "Request",
|
||||
"operation": {
|
||||
"argumentDefinitions": (v0/*: any*/),
|
||||
"kind": "Operation",
|
||||
"name": "DocumentGraphBulkDeleteDocumentsMutation",
|
||||
"selections": [
|
||||
{
|
||||
"alias": null,
|
||||
"args": (v1/*: any*/),
|
||||
"concreteType": "BulkDeleteDocumentsPayload",
|
||||
"kind": "LinkedField",
|
||||
"name": "bulkDeleteDocuments",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
(v2/*: any*/),
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"filters": null,
|
||||
"handle": "deleteRecord",
|
||||
"key": "",
|
||||
"kind": "ScalarHandle",
|
||||
"name": "deletedDocumentIds"
|
||||
}
|
||||
],
|
||||
"storageKey": null
|
||||
}
|
||||
]
|
||||
},
|
||||
"params": {
|
||||
"cacheID": "655f43d282b8e0007f3b93e381e7ff84",
|
||||
"id": null,
|
||||
"metadata": {},
|
||||
"name": "DocumentGraphBulkDeleteDocumentsMutation",
|
||||
"operationKind": "mutation",
|
||||
"text": "mutation DocumentGraphBulkDeleteDocumentsMutation(\n $input: BulkDeleteDocumentsInput!\n) {\n bulkDeleteDocuments(input: $input) {\n deletedDocumentIds\n }\n}\n"
|
||||
}
|
||||
};
|
||||
})();
|
||||
|
||||
(node as any).hash = "919857a24f366b8ebe004fcadf503c3d";
|
||||
|
||||
export default node;
|
||||
92
apps/console/src/hooks/graph/__generated__/DocumentGraphBulkExportDocumentsMutation.graphql.ts
generated
Normal file
92
apps/console/src/hooks/graph/__generated__/DocumentGraphBulkExportDocumentsMutation.graphql.ts
generated
Normal file
@@ -0,0 +1,92 @@
|
||||
/**
|
||||
* @generated SignedSource<<a398c636bba3d35aa7caef66c62c9ac6>>
|
||||
* @lightSyntaxTransform
|
||||
* @nogrep
|
||||
*/
|
||||
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
// @ts-nocheck
|
||||
|
||||
import { ConcreteRequest } from 'relay-runtime';
|
||||
export type BulkExportDocumentsInput = {
|
||||
documentIds: ReadonlyArray<string>;
|
||||
};
|
||||
export type DocumentGraphBulkExportDocumentsMutation$variables = {
|
||||
input: BulkExportDocumentsInput;
|
||||
};
|
||||
export type DocumentGraphBulkExportDocumentsMutation$data = {
|
||||
readonly bulkExportDocuments: {
|
||||
readonly exportJobId: string;
|
||||
};
|
||||
};
|
||||
export type DocumentGraphBulkExportDocumentsMutation = {
|
||||
response: DocumentGraphBulkExportDocumentsMutation$data;
|
||||
variables: DocumentGraphBulkExportDocumentsMutation$variables;
|
||||
};
|
||||
|
||||
const node: ConcreteRequest = (function(){
|
||||
var v0 = [
|
||||
{
|
||||
"defaultValue": null,
|
||||
"kind": "LocalArgument",
|
||||
"name": "input"
|
||||
}
|
||||
],
|
||||
v1 = [
|
||||
{
|
||||
"alias": null,
|
||||
"args": [
|
||||
{
|
||||
"kind": "Variable",
|
||||
"name": "input",
|
||||
"variableName": "input"
|
||||
}
|
||||
],
|
||||
"concreteType": "BulkExportDocumentsPayload",
|
||||
"kind": "LinkedField",
|
||||
"name": "bulkExportDocuments",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "exportJobId",
|
||||
"storageKey": null
|
||||
}
|
||||
],
|
||||
"storageKey": null
|
||||
}
|
||||
];
|
||||
return {
|
||||
"fragment": {
|
||||
"argumentDefinitions": (v0/*: any*/),
|
||||
"kind": "Fragment",
|
||||
"metadata": null,
|
||||
"name": "DocumentGraphBulkExportDocumentsMutation",
|
||||
"selections": (v1/*: any*/),
|
||||
"type": "Mutation",
|
||||
"abstractKey": null
|
||||
},
|
||||
"kind": "Request",
|
||||
"operation": {
|
||||
"argumentDefinitions": (v0/*: any*/),
|
||||
"kind": "Operation",
|
||||
"name": "DocumentGraphBulkExportDocumentsMutation",
|
||||
"selections": (v1/*: any*/)
|
||||
},
|
||||
"params": {
|
||||
"cacheID": "2f4eb206613f8fba307aeee5bad27b93",
|
||||
"id": null,
|
||||
"metadata": {},
|
||||
"name": "DocumentGraphBulkExportDocumentsMutation",
|
||||
"operationKind": "mutation",
|
||||
"text": "mutation DocumentGraphBulkExportDocumentsMutation(\n $input: BulkExportDocumentsInput!\n) {\n bulkExportDocuments(input: $input) {\n exportJobId\n }\n}\n"
|
||||
}
|
||||
};
|
||||
})();
|
||||
|
||||
(node as any).hash = "0a7bae4b58713bcc824a774c31bd6dca";
|
||||
|
||||
export default node;
|
||||
@@ -524,12 +524,14 @@ export default function DocumentDetailPage(props: Props) {
|
||||
{__("Controls")}
|
||||
<TabBadge>{controlsCount}</TabBadge>
|
||||
</TabLink>
|
||||
<TabLink to={`${urlPrefix}/signatures`}>
|
||||
{__("Signatures")}
|
||||
<TabBadge>
|
||||
{signedSignatures.length}/{signatures.length}
|
||||
</TabBadge>
|
||||
</TabLink>
|
||||
{!isDraft && (
|
||||
<TabLink to={`${urlPrefix}/signatures`}>
|
||||
{__("Signatures")}
|
||||
<TabBadge>
|
||||
{signedSignatures.length}/{signatures.length}
|
||||
</TabBadge>
|
||||
</TabLink>
|
||||
)}
|
||||
</Tabs>
|
||||
|
||||
<Outlet context={{ document, version: currentVersion }} />
|
||||
|
||||
@@ -19,6 +19,8 @@ import {
|
||||
IconCrossLargeX,
|
||||
IconSignature,
|
||||
IconCheckmark1,
|
||||
IconArrowDown,
|
||||
Card,
|
||||
} from "@probo/ui";
|
||||
import {
|
||||
useFragment,
|
||||
@@ -32,6 +34,8 @@ import {
|
||||
documentsQuery,
|
||||
useDeleteDocumentMutation,
|
||||
useSendSigningNotificationsMutation,
|
||||
useBulkDeleteDocumentsMutation,
|
||||
useBulkExportDocumentsMutation,
|
||||
} from "/hooks/graph/DocumentGraph";
|
||||
import type { DocumentsPageListFragment$key } from "./__generated__/DocumentsPageListFragment.graphql";
|
||||
import { useList, usePageTitle } from "@probo/hooks";
|
||||
@@ -94,7 +98,10 @@ export default function DocumentsPage(props: Props) {
|
||||
.filter(Boolean);
|
||||
const connectionId = pagination.data.documents.__id;
|
||||
const [sendSigningNotifications] = useSendSigningNotificationsMutation();
|
||||
const [bulkDeleteDocuments] = useBulkDeleteDocumentsMutation();
|
||||
const [bulkExportDocuments] = useBulkExportDocumentsMutation();
|
||||
const { list: selection, toggle, clear, reset } = useList<string>([]);
|
||||
const confirm = useConfirm();
|
||||
|
||||
usePageTitle(__("Documents"));
|
||||
|
||||
@@ -106,6 +113,39 @@ export default function DocumentsPage(props: Props) {
|
||||
});
|
||||
};
|
||||
|
||||
const handleBulkDelete = () => {
|
||||
const documentCount = selection.length;
|
||||
confirm(
|
||||
() =>
|
||||
bulkDeleteDocuments({
|
||||
variables: {
|
||||
input: { documentIds: selection },
|
||||
},
|
||||
}).then(() => {
|
||||
clear();
|
||||
}),
|
||||
{
|
||||
message: sprintf(
|
||||
__(
|
||||
'This will permanently delete %s document%s. This action cannot be undone.'
|
||||
),
|
||||
documentCount,
|
||||
documentCount > 1 ? 's' : ''
|
||||
),
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
const handleBulkExport = () => {
|
||||
bulkExportDocuments({
|
||||
variables: {
|
||||
input: { documentIds: selection },
|
||||
},
|
||||
}).then(() => {
|
||||
clear();
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<PageHeader
|
||||
@@ -126,72 +166,103 @@ export default function DocumentsPage(props: Props) {
|
||||
/>
|
||||
</div>
|
||||
</PageHeader>
|
||||
<SortableTable {...pagination}>
|
||||
<Thead>
|
||||
{selection.length === 0 ? (
|
||||
<Tr>
|
||||
<Th>
|
||||
<Checkbox
|
||||
checked={selection.length === documents.length && documents.length > 0}
|
||||
onChange={() => reset(documents.map((d) => d.id))}
|
||||
/>
|
||||
</Th>
|
||||
<SortableTh field="TITLE">{__("Name")}</SortableTh>
|
||||
<Th>{__("Status")}</Th>
|
||||
<SortableTh field="DOCUMENT_TYPE">{__("Type")}</SortableTh>
|
||||
<Th>{__("Owner")}</Th>
|
||||
<Th>{__("Last update")}</Th>
|
||||
<Th>{__("Signatures")}</Th>
|
||||
<Th></Th>
|
||||
</Tr>
|
||||
) : (
|
||||
<Tr>
|
||||
<Th colspan={8}>
|
||||
<div className="flex justify-between items-center">
|
||||
<div className="flex gap-2 items-center">
|
||||
{sprintf(__("%s documents selected"), selection.length)} -
|
||||
<button
|
||||
onClick={clear}
|
||||
className="flex gap-1 items-center hover:text-txt-primary"
|
||||
>
|
||||
<IconCrossLargeX size={12} />
|
||||
{__("Clear selection")}
|
||||
</button>
|
||||
</div>
|
||||
<div className="flex gap-2">
|
||||
<PublishDocumentsDialog
|
||||
documentIds={selection}
|
||||
onSave={clear}
|
||||
>
|
||||
<Button icon={IconCheckmark1}>{__("Publish")}</Button>
|
||||
</PublishDocumentsDialog>
|
||||
{documents.length > 0 ? (
|
||||
<SortableTable {...pagination}>
|
||||
<Thead>
|
||||
{selection.length === 0 ? (
|
||||
<Tr>
|
||||
<Th className="w-18">
|
||||
<Checkbox
|
||||
checked={selection.length === documents.length && documents.length > 0}
|
||||
onChange={() => reset(documents.map((d) => d.id))}
|
||||
/>
|
||||
</Th>
|
||||
<SortableTh field="TITLE" className="min-w-0">{__("Name")}</SortableTh>
|
||||
<Th className="w-24">{__("Status")}</Th>
|
||||
<SortableTh field="DOCUMENT_TYPE" className="w-28">{__("Type")}</SortableTh>
|
||||
<Th className="w-60">{__("Owner")}</Th>
|
||||
<Th className="w-60">{__("Last update")}</Th>
|
||||
<Th className="w-20">{__("Signatures")}</Th>
|
||||
<Th className="w-18"></Th>
|
||||
</Tr>
|
||||
) : (
|
||||
<Tr>
|
||||
<Th colspan={8} compact>
|
||||
<div className="flex justify-between items-center h-8">
|
||||
<div className="flex gap-2 items-center">
|
||||
{sprintf(__("%s documents selected"), selection.length)} -
|
||||
<button
|
||||
onClick={clear}
|
||||
className="flex gap-1 items-center hover:text-txt-primary"
|
||||
>
|
||||
<IconCrossLargeX size={12} />
|
||||
{__("Clear selection")}
|
||||
</button>
|
||||
</div>
|
||||
<div className="flex gap-2 items-center">
|
||||
<PublishDocumentsDialog
|
||||
documentIds={selection}
|
||||
onSave={clear}
|
||||
>
|
||||
<Button icon={IconCheckmark1} className="py-0.5 px-2 text-xs h-6 min-h-6">
|
||||
{__("Publish")}
|
||||
</Button>
|
||||
</PublishDocumentsDialog>
|
||||
<SignatureDocumentsDialog
|
||||
documentIds={selection}
|
||||
onSave={clear}
|
||||
>
|
||||
<Button variant="secondary" icon={IconSignature}>
|
||||
<Button variant="secondary" icon={IconSignature} className="py-0.5 px-2 text-xs h-6 min-h-6">
|
||||
{__("Request signature")}
|
||||
</Button>
|
||||
</SignatureDocumentsDialog>
|
||||
<Button
|
||||
variant="secondary"
|
||||
icon={IconArrowDown}
|
||||
onClick={handleBulkExport}
|
||||
className="py-0.5 px-2 text-xs h-6 min-h-6"
|
||||
>
|
||||
{__("Export")}
|
||||
</Button>
|
||||
<Button
|
||||
variant="danger"
|
||||
icon={IconTrashCan}
|
||||
onClick={handleBulkDelete}
|
||||
className="py-0.5 px-2 text-xs h-6 min-h-6"
|
||||
>
|
||||
{__("Delete")}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Th>
|
||||
</Tr>
|
||||
)}
|
||||
</Thead>
|
||||
<Tbody>
|
||||
{documents.map((document) => (
|
||||
<DocumentRow
|
||||
checked={selection.includes(document.id)}
|
||||
onCheck={() => toggle(document.id)}
|
||||
key={document.id}
|
||||
document={document}
|
||||
organizationId={organization.id}
|
||||
connectionId={connectionId}
|
||||
/>
|
||||
))}
|
||||
</Tbody>
|
||||
</SortableTable>
|
||||
</Th>
|
||||
</Tr>
|
||||
)}
|
||||
</Thead>
|
||||
<Tbody>
|
||||
{documents.map((document) => (
|
||||
<DocumentRow
|
||||
checked={selection.includes(document.id)}
|
||||
onCheck={() => toggle(document.id)}
|
||||
key={document.id}
|
||||
document={document}
|
||||
organizationId={organization.id}
|
||||
connectionId={connectionId}
|
||||
/>
|
||||
))}
|
||||
</Tbody>
|
||||
</SortableTable>
|
||||
) : (
|
||||
<Card padded>
|
||||
<div className="text-center py-12">
|
||||
<h3 className="text-lg font-semibold mb-2">
|
||||
{__("No documents yet")}
|
||||
</h3>
|
||||
<p className="text-txt-tertiary mb-4">
|
||||
{__("Create your first document to get started.")}
|
||||
</p>
|
||||
</div>
|
||||
</Card>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -278,34 +349,27 @@ function DocumentRow({
|
||||
|
||||
return (
|
||||
<Tr to={`/organizations/${organizationId}/documents/${document.id}`}>
|
||||
<Td noLink>
|
||||
<Td noLink className="w-18">
|
||||
<Checkbox checked={checked} onChange={onCheck} />
|
||||
</Td>
|
||||
<Td>
|
||||
<Td className="min-w-0">
|
||||
<div className="flex gap-4 items-center">
|
||||
<img
|
||||
src="/document.png"
|
||||
alt=""
|
||||
width={28}
|
||||
height={36}
|
||||
className="border-4 border-highlight rounded box-content"
|
||||
/>
|
||||
{document.title}
|
||||
</div>
|
||||
</Td>
|
||||
<Td>
|
||||
<Td className="w-24">
|
||||
<Badge variant={isDraft ? "neutral" : "success"}>
|
||||
{isDraft ? __("Draft") : __("Published")}
|
||||
</Badge>
|
||||
</Td>
|
||||
<Td>{getDocumentTypeLabel(__, document.documentType)}</Td>
|
||||
<Td>
|
||||
<Td className="w-28">{getDocumentTypeLabel(__, document.documentType)}</Td>
|
||||
<Td className="w-60">
|
||||
<div className="flex gap-2 items-center">
|
||||
<Avatar name={document.owner?.fullName ?? ""} />
|
||||
{document.owner?.fullName}
|
||||
</div>
|
||||
</Td>
|
||||
<Td>
|
||||
<Td className="w-60">
|
||||
{dateFormat(document.updatedAt, {
|
||||
year: "numeric",
|
||||
month: "short",
|
||||
@@ -313,10 +377,10 @@ function DocumentRow({
|
||||
weekday: "short",
|
||||
})}
|
||||
</Td>
|
||||
<Td>
|
||||
<Td className="w-20">
|
||||
{signedCount}/{signatures.length}
|
||||
</Td>
|
||||
<Td noLink width={50} className="text-end">
|
||||
<Td noLink width={50} className="text-end w-18">
|
||||
<ActionDropdown>
|
||||
<DropdownItem
|
||||
variant="danger"
|
||||
|
||||
@@ -118,7 +118,7 @@ export default function FrameworkDetailPage(props: Props) {
|
||||
exportFrameworkMutation,
|
||||
{
|
||||
errorMessage: "Failed to export framework",
|
||||
successMessage: "Framework export started successfully",
|
||||
successMessage: "Framework export started successfully. You will receive an email when the export is ready.",
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user