Fix documents UI
- Fix redirection after document deletion in detail page - Fix refetch after publication - Return proper error when trying to update a published document Signed-off-by: Sacha Al Himdani <sacha@getprobo.com>
This commit is contained in:
@@ -1,5 +1,5 @@
|
|||||||
/**
|
/**
|
||||||
* @generated SignedSource<<6b7916e6ff46a629bf97a7b072db2d8b>>
|
* @generated SignedSource<<64c2c92a2a0806f34243f8fc9904167c>>
|
||||||
* @lightSyntaxTransform
|
* @lightSyntaxTransform
|
||||||
* @nogrep
|
* @nogrep
|
||||||
*/
|
*/
|
||||||
@@ -13,6 +13,7 @@ export type DeleteDocumentInput = {
|
|||||||
documentId: string;
|
documentId: string;
|
||||||
};
|
};
|
||||||
export type DocumentGraphDeleteMutation$variables = {
|
export type DocumentGraphDeleteMutation$variables = {
|
||||||
|
connections: ReadonlyArray<string>;
|
||||||
input: DeleteDocumentInput;
|
input: DeleteDocumentInput;
|
||||||
};
|
};
|
||||||
export type DocumentGraphDeleteMutation$data = {
|
export type DocumentGraphDeleteMutation$data = {
|
||||||
@@ -26,21 +27,24 @@ export type DocumentGraphDeleteMutation = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const node: ConcreteRequest = (function(){
|
const node: ConcreteRequest = (function(){
|
||||||
var v0 = [
|
var v0 = {
|
||||||
{
|
"defaultValue": null,
|
||||||
"defaultValue": null,
|
"kind": "LocalArgument",
|
||||||
"kind": "LocalArgument",
|
"name": "connections"
|
||||||
"name": "input"
|
},
|
||||||
}
|
v1 = {
|
||||||
],
|
"defaultValue": null,
|
||||||
v1 = [
|
"kind": "LocalArgument",
|
||||||
|
"name": "input"
|
||||||
|
},
|
||||||
|
v2 = [
|
||||||
{
|
{
|
||||||
"kind": "Variable",
|
"kind": "Variable",
|
||||||
"name": "input",
|
"name": "input",
|
||||||
"variableName": "input"
|
"variableName": "input"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
v2 = {
|
v3 = {
|
||||||
"alias": null,
|
"alias": null,
|
||||||
"args": null,
|
"args": null,
|
||||||
"kind": "ScalarField",
|
"kind": "ScalarField",
|
||||||
@@ -49,20 +53,23 @@ v2 = {
|
|||||||
};
|
};
|
||||||
return {
|
return {
|
||||||
"fragment": {
|
"fragment": {
|
||||||
"argumentDefinitions": (v0/*: any*/),
|
"argumentDefinitions": [
|
||||||
|
(v0/*: any*/),
|
||||||
|
(v1/*: any*/)
|
||||||
|
],
|
||||||
"kind": "Fragment",
|
"kind": "Fragment",
|
||||||
"metadata": null,
|
"metadata": null,
|
||||||
"name": "DocumentGraphDeleteMutation",
|
"name": "DocumentGraphDeleteMutation",
|
||||||
"selections": [
|
"selections": [
|
||||||
{
|
{
|
||||||
"alias": null,
|
"alias": null,
|
||||||
"args": (v1/*: any*/),
|
"args": (v2/*: any*/),
|
||||||
"concreteType": "DeleteDocumentPayload",
|
"concreteType": "DeleteDocumentPayload",
|
||||||
"kind": "LinkedField",
|
"kind": "LinkedField",
|
||||||
"name": "deleteDocument",
|
"name": "deleteDocument",
|
||||||
"plural": false,
|
"plural": false,
|
||||||
"selections": [
|
"selections": [
|
||||||
(v2/*: any*/)
|
(v3/*: any*/)
|
||||||
],
|
],
|
||||||
"storageKey": null
|
"storageKey": null
|
||||||
}
|
}
|
||||||
@@ -72,27 +79,37 @@ return {
|
|||||||
},
|
},
|
||||||
"kind": "Request",
|
"kind": "Request",
|
||||||
"operation": {
|
"operation": {
|
||||||
"argumentDefinitions": (v0/*: any*/),
|
"argumentDefinitions": [
|
||||||
|
(v1/*: any*/),
|
||||||
|
(v0/*: any*/)
|
||||||
|
],
|
||||||
"kind": "Operation",
|
"kind": "Operation",
|
||||||
"name": "DocumentGraphDeleteMutation",
|
"name": "DocumentGraphDeleteMutation",
|
||||||
"selections": [
|
"selections": [
|
||||||
{
|
{
|
||||||
"alias": null,
|
"alias": null,
|
||||||
"args": (v1/*: any*/),
|
"args": (v2/*: any*/),
|
||||||
"concreteType": "DeleteDocumentPayload",
|
"concreteType": "DeleteDocumentPayload",
|
||||||
"kind": "LinkedField",
|
"kind": "LinkedField",
|
||||||
"name": "deleteDocument",
|
"name": "deleteDocument",
|
||||||
"plural": false,
|
"plural": false,
|
||||||
"selections": [
|
"selections": [
|
||||||
(v2/*: any*/),
|
(v3/*: any*/),
|
||||||
{
|
{
|
||||||
"alias": null,
|
"alias": null,
|
||||||
"args": null,
|
"args": null,
|
||||||
"filters": null,
|
"filters": null,
|
||||||
"handle": "deleteRecord",
|
"handle": "deleteEdge",
|
||||||
"key": "",
|
"key": "",
|
||||||
"kind": "ScalarHandle",
|
"kind": "ScalarHandle",
|
||||||
"name": "deletedDocumentId"
|
"name": "deletedDocumentId",
|
||||||
|
"handleArgs": [
|
||||||
|
{
|
||||||
|
"kind": "Variable",
|
||||||
|
"name": "connections",
|
||||||
|
"variableName": "connections"
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"storageKey": null
|
"storageKey": null
|
||||||
@@ -110,6 +127,6 @@ return {
|
|||||||
};
|
};
|
||||||
})();
|
})();
|
||||||
|
|
||||||
(node as any).hash = "471020248ed9d398ab80655e04cb823d";
|
(node as any).hash = "355f3a70caecabb2146075657665633e";
|
||||||
|
|
||||||
export default node;
|
export default node;
|
||||||
|
|||||||
@@ -11,9 +11,12 @@ import { useMutationWithToasts } from "../useMutationWithToasts";
|
|||||||
export const DocumentsConnectionKey = "DocumentsListQuery_documents";
|
export const DocumentsConnectionKey = "DocumentsListQuery_documents";
|
||||||
|
|
||||||
const deleteDocumentMutation = graphql`
|
const deleteDocumentMutation = graphql`
|
||||||
mutation DocumentGraphDeleteMutation($input: DeleteDocumentInput!) {
|
mutation DocumentGraphDeleteMutation(
|
||||||
|
$input: DeleteDocumentInput!
|
||||||
|
$connections: [ID!]!
|
||||||
|
) {
|
||||||
deleteDocument(input: $input) {
|
deleteDocument(input: $input) {
|
||||||
deletedDocumentId @deleteRecord
|
deletedDocumentId @deleteEdge(connections: $connections)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|||||||
@@ -77,8 +77,8 @@ const publishDocumentVersionMutation = graphql`
|
|||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export function DocumentLayout(props: { queryRef: PreloadedQuery<DocumentLayoutQuery> }) {
|
export function DocumentLayout(props: { queryRef: PreloadedQuery<DocumentLayoutQuery>; onRefetch: () => void }) {
|
||||||
const { queryRef } = props;
|
const { queryRef, onRefetch } = props;
|
||||||
|
|
||||||
const organizationId = useOrganizationId();
|
const organizationId = useOrganizationId();
|
||||||
const { versionId } = useParams();
|
const { versionId } = useParams();
|
||||||
@@ -112,6 +112,7 @@ export function DocumentLayout(props: { queryRef: PreloadedQuery<DocumentLayoutQ
|
|||||||
variables: {
|
variables: {
|
||||||
input: { documentId: document.id },
|
input: { documentId: document.id },
|
||||||
},
|
},
|
||||||
|
onSuccess: onRefetch,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -146,7 +147,7 @@ export function DocumentLayout(props: { queryRef: PreloadedQuery<DocumentLayoutQ
|
|||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
<DocumentVersionsDropdown />
|
<DocumentVersionsDropdown />
|
||||||
<DocumentActionsDropdownn documentFragmentRef={document} versionFragmentRef={currentVersion} />
|
<DocumentActionsDropdownn documentFragmentRef={document} versionFragmentRef={currentVersion} onRefetch={onRefetch} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { Suspense, useEffect } from "react";
|
import { Suspense, useCallback, useEffect } from "react";
|
||||||
import { useQueryLoader } from "react-relay";
|
import { useQueryLoader } from "react-relay";
|
||||||
import { useParams } from "react-router";
|
import { useParams } from "react-router";
|
||||||
|
|
||||||
@@ -25,9 +25,16 @@ function DocumentLayoutQueryLoader() {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const onRefetch = useCallback(() => {
|
||||||
|
loadQuery(
|
||||||
|
{ documentId, versionId: versionId ?? "", versionSpecified: !!versionId },
|
||||||
|
{ fetchPolicy: "network-only" },
|
||||||
|
);
|
||||||
|
}, [documentId, versionId, loadQuery]);
|
||||||
|
|
||||||
if (!queryRef) return <PageSkeleton />;
|
if (!queryRef) return <PageSkeleton />;
|
||||||
|
|
||||||
return <DocumentLayout queryRef={queryRef} />;
|
return <DocumentLayout queryRef={queryRef} onRefetch={onRefetch} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function DocumentLayoutLoader() {
|
export default function DocumentLayoutLoader() {
|
||||||
|
|||||||
@@ -3,14 +3,14 @@ import { useTranslate } from "@probo/i18n";
|
|||||||
import { ActionDropdown, DropdownItem, IconArrowDown, IconPencil, IconTrashCan, useConfirm } from "@probo/ui";
|
import { ActionDropdown, DropdownItem, IconArrowDown, IconPencil, IconTrashCan, useConfirm } from "@probo/ui";
|
||||||
import { use, useRef } from "react";
|
import { use, useRef } from "react";
|
||||||
import { useFragment } from "react-relay";
|
import { useFragment } from "react-relay";
|
||||||
import { useNavigate } from "react-router";
|
import { useNavigate, useParams } from "react-router";
|
||||||
import { graphql } from "relay-runtime";
|
import { ConnectionHandler, graphql } from "relay-runtime";
|
||||||
|
|
||||||
import type { DocumentActionsDropdown_documentFragment$key } from "#/__generated__/core/DocumentActionsDropdown_documentFragment.graphql";
|
import type { DocumentActionsDropdown_documentFragment$key } from "#/__generated__/core/DocumentActionsDropdown_documentFragment.graphql";
|
||||||
import type { DocumentActionsDropdown_versionFragment$key } from "#/__generated__/core/DocumentActionsDropdown_versionFragment.graphql";
|
import type { DocumentActionsDropdown_versionFragment$key } from "#/__generated__/core/DocumentActionsDropdown_versionFragment.graphql";
|
||||||
import type { DocumentActionsDropdownn_exportVersionMutation } from "#/__generated__/core/DocumentActionsDropdownn_exportVersionMutation.graphql";
|
import type { DocumentActionsDropdownn_exportVersionMutation } from "#/__generated__/core/DocumentActionsDropdownn_exportVersionMutation.graphql";
|
||||||
import { PdfDownloadDialog, type PdfDownloadDialogRef } from "#/components/documents/PdfDownloadDialog";
|
import { PdfDownloadDialog, type PdfDownloadDialogRef } from "#/components/documents/PdfDownloadDialog";
|
||||||
import { useDeleteDocumentMutation, useDeleteDraftDocumentVersionMutation } from "#/hooks/graph/DocumentGraph";
|
import { DocumentsConnectionKey, useDeleteDocumentMutation, useDeleteDraftDocumentVersionMutation } from "#/hooks/graph/DocumentGraph";
|
||||||
import { useMutationWithToasts } from "#/hooks/useMutationWithToasts";
|
import { useMutationWithToasts } from "#/hooks/useMutationWithToasts";
|
||||||
import { useOrganizationId } from "#/hooks/useOrganizationId";
|
import { useOrganizationId } from "#/hooks/useOrganizationId";
|
||||||
import { CurrentUser } from "#/providers/CurrentUser";
|
import { CurrentUser } from "#/providers/CurrentUser";
|
||||||
@@ -53,11 +53,13 @@ const exportDocumentVersionMutation = graphql`
|
|||||||
export function DocumentActionsDropdownn(props: {
|
export function DocumentActionsDropdownn(props: {
|
||||||
documentFragmentRef: DocumentActionsDropdown_documentFragment$key;
|
documentFragmentRef: DocumentActionsDropdown_documentFragment$key;
|
||||||
versionFragmentRef: DocumentActionsDropdown_versionFragment$key;
|
versionFragmentRef: DocumentActionsDropdown_versionFragment$key;
|
||||||
|
onRefetch: () => void;
|
||||||
}) {
|
}) {
|
||||||
const { documentFragmentRef, versionFragmentRef } = props;
|
const { documentFragmentRef, versionFragmentRef, onRefetch } = props;
|
||||||
|
|
||||||
const organizationId = useOrganizationId();
|
const organizationId = useOrganizationId();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
const { versionId } = useParams();
|
||||||
const { __ } = useTranslate();
|
const { __ } = useTranslate();
|
||||||
const { email: defaultEmail } = use(CurrentUser);
|
const { email: defaultEmail } = use(CurrentUser);
|
||||||
const updateDialogRef = useRef<{ open: () => void }>(null);
|
const updateDialogRef = useRef<{ open: () => void }>(null);
|
||||||
@@ -82,11 +84,17 @@ export function DocumentActionsDropdownn(props: {
|
|||||||
);
|
);
|
||||||
|
|
||||||
const handleDelete = () => {
|
const handleDelete = () => {
|
||||||
|
const connectionId = ConnectionHandler.getConnectionID(
|
||||||
|
organizationId,
|
||||||
|
DocumentsConnectionKey,
|
||||||
|
{ orderBy: { direction: "ASC", field: "TITLE" } },
|
||||||
|
);
|
||||||
confirm(
|
confirm(
|
||||||
() =>
|
() =>
|
||||||
deleteDocument({
|
deleteDocument({
|
||||||
variables: {
|
variables: {
|
||||||
input: { documentId: document.id },
|
input: { documentId: document.id },
|
||||||
|
connections: [connectionId],
|
||||||
},
|
},
|
||||||
onSuccess() {
|
onSuccess() {
|
||||||
void navigate(`/organizations/${organizationId}/documents`);
|
void navigate(`/organizations/${organizationId}/documents`);
|
||||||
@@ -104,15 +112,23 @@ export function DocumentActionsDropdownn(props: {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleDeleteDraft = () => {
|
const handleDeleteDraft = () => {
|
||||||
|
const versionsConnectionId = ConnectionHandler.getConnectionID(
|
||||||
|
document.id,
|
||||||
|
"DocumentLayout_versions",
|
||||||
|
);
|
||||||
confirm(
|
confirm(
|
||||||
() =>
|
() =>
|
||||||
deleteDraftDocumentVersion({
|
deleteDraftDocumentVersion({
|
||||||
variables: {
|
variables: {
|
||||||
input: { documentVersionId: version.id },
|
input: { documentVersionId: version.id },
|
||||||
connections: [document.versions.__id],
|
connections: [document.versions.__id, versionsConnectionId],
|
||||||
},
|
},
|
||||||
onSuccess() {
|
onSuccess() {
|
||||||
window.location.href = `/organizations/${organizationId}/documents/${document.id}`;
|
if (versionId) {
|
||||||
|
void navigate(`/organizations/${organizationId}/documents/${document.id}`);
|
||||||
|
} else {
|
||||||
|
onRefetch();
|
||||||
|
}
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import {
|
|||||||
} from "@probo/ui";
|
} from "@probo/ui";
|
||||||
import { type RefObject, useEffect } from "react";
|
import { type RefObject, useEffect } from "react";
|
||||||
import { useFragment, useMutation } from "react-relay";
|
import { useFragment, useMutation } from "react-relay";
|
||||||
|
import { useNavigate } from "react-router";
|
||||||
import { graphql } from "relay-runtime";
|
import { graphql } from "relay-runtime";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
@@ -92,6 +93,7 @@ export default function UpdateVersionDialog(props: UpdateVersionDialogProps) {
|
|||||||
const { fKey, ref } = props;
|
const { fKey, ref } = props;
|
||||||
|
|
||||||
const organizationId = useOrganizationId();
|
const organizationId = useOrganizationId();
|
||||||
|
const navigate = useNavigate();
|
||||||
const { __ } = useTranslate();
|
const { __ } = useTranslate();
|
||||||
const { toast } = useToast();
|
const { toast } = useToast();
|
||||||
const dialogRef = useDialogRef();
|
const dialogRef = useDialogRef();
|
||||||
@@ -174,11 +176,12 @@ export default function UpdateVersionDialog(props: UpdateVersionDialogProps) {
|
|||||||
},
|
},
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
dialogRef.current?.close();
|
dialogRef.current?.close();
|
||||||
window.location.href = `/organizations/${organizationId}/documents/${document.id}`;
|
void navigate(`/organizations/${organizationId}/documents/${document.id}/versions/${newVersionId}`);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
dialogRef.current?.close();
|
dialogRef.current?.close();
|
||||||
|
void navigate(`/organizations/${organizationId}/documents/${document.id}/versions/${newVersionId}`);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -46,6 +46,9 @@ type (
|
|||||||
ErrDocumentVersionNoChanges struct {
|
ErrDocumentVersionNoChanges struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ErrDocumentVersionNotDraft struct {
|
||||||
|
}
|
||||||
|
|
||||||
ErrDocumentVersionSignatureAlreadySigned struct {
|
ErrDocumentVersionSignatureAlreadySigned struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -154,6 +157,10 @@ func (e ErrDocumentVersionNoChanges) Error() string {
|
|||||||
return "no changes detected"
|
return "no changes detected"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (e ErrDocumentVersionNotDraft) Error() string {
|
||||||
|
return "cannot update a published document version"
|
||||||
|
}
|
||||||
|
|
||||||
func (e ErrDocumentVersionSignatureAlreadySigned) Error() string {
|
func (e ErrDocumentVersionSignatureAlreadySigned) Error() string {
|
||||||
return "document version signature already signed"
|
return "document version signature already signed"
|
||||||
}
|
}
|
||||||
@@ -781,7 +788,7 @@ func (s *DocumentService) UpdateVersion(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if documentVersion.Status != coredata.DocumentStatusDraft {
|
if documentVersion.Status != coredata.DocumentStatusDraft {
|
||||||
return fmt.Errorf("cannot update published version")
|
return &ErrDocumentVersionNotDraft{}
|
||||||
}
|
}
|
||||||
|
|
||||||
documentVersion.Title = document.Title
|
documentVersion.Title = document.Title
|
||||||
|
|||||||
@@ -4429,8 +4429,16 @@ func (r *mutationResolver) UpdateDocumentVersion(ctx context.Context, input type
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// TODO no panic use gqlutils.InternalError
|
if errors.Is(err, coredata.ErrResourceNotFound) {
|
||||||
panic(fmt.Errorf("cannot update document version: %w", err))
|
return nil, gqlutils.NotFound(ctx, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if errNotDraft, ok := errors.AsType[*probo.ErrDocumentVersionNotDraft](err); ok {
|
||||||
|
return nil, gqlutils.Conflict(ctx, errNotDraft)
|
||||||
|
}
|
||||||
|
|
||||||
|
r.logger.ErrorCtx(ctx, "cannot update document version", log.Error(err))
|
||||||
|
return nil, gqlutils.Internal(ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
return &types.UpdateDocumentVersionPayload{
|
return &types.UpdateDocumentVersionPayload{
|
||||||
|
|||||||
Reference in New Issue
Block a user