From 7a5d4c851af62f8c20c60af686ca1df750a3d0c3 Mon Sep 17 00:00:00 2001 From: Sacha Al Himdani Date: Tue, 14 Apr 2026 16:50:35 +0200 Subject: [PATCH] Replace document properties drawer with inline details card - Remove the right-side drawer and display document properties in a 3-column Card below the page header - Move status badge to the PageHeader (right-aligned, matching compliance page style) Signed-off-by: Sacha Al Himdani --- .../documents/DocumentLayout.tsx | 94 +++- .../documents/DocumentLayoutLoader.tsx | 31 +- .../_components/DocumentDetailsCard.tsx | 467 ++++++++++++++++++ .../_components/DocumentLayoutDrawer.tsx | 445 ----------------- .../_components/DocumentTitleForm.tsx | 9 +- .../_components/DocumentVersionsDropdown.tsx | 7 +- .../DocumentVersionsDropdownItem.tsx | 9 +- .../DocumentVersionsDropdownMenu.tsx | 4 +- .../description/DocumentDescriptionPage.tsx | 25 +- .../DocumentDescriptionPageLoader.tsx | 2 +- 10 files changed, 585 insertions(+), 508 deletions(-) create mode 100644 apps/console/src/pages/organizations/documents/_components/DocumentDetailsCard.tsx delete mode 100644 apps/console/src/pages/organizations/documents/_components/DocumentLayoutDrawer.tsx diff --git a/apps/console/src/pages/organizations/documents/DocumentLayout.tsx b/apps/console/src/pages/organizations/documents/DocumentLayout.tsx index 312ac7005..1c166576d 100644 --- a/apps/console/src/pages/organizations/documents/DocumentLayout.tsx +++ b/apps/console/src/pages/organizations/documents/DocumentLayout.tsx @@ -13,17 +13,17 @@ // PERFORMANCE OF THIS SOFTWARE. import { useTranslate } from "@probo/i18n"; -import { Breadcrumb, Button, IconUpload, PageHeader, TabBadge, TabLink, Tabs } from "@probo/ui"; +import { Badge, Breadcrumb, Button, IconUpload, PageHeader, TabBadge, TabLink, Tabs } from "@probo/ui"; import { useCallback, useRef, useState } from "react"; import { type PreloadedQuery, usePreloadedQuery } from "react-relay"; -import { Outlet, useParams } from "react-router"; +import { Outlet, useLocation, useNavigate, useParams } from "react-router"; import { graphql } from "relay-runtime"; import type { DocumentLayoutQuery } from "#/__generated__/core/DocumentLayoutQuery.graphql"; import { useOrganizationId } from "#/hooks/useOrganizationId"; import { DocumentActionsDropdown } from "./_components/DocumentActionsDropdown"; -import { DocumentLayoutDrawer } from "./_components/DocumentLayoutDrawer"; +import { DocumentDetailsCard } from "./_components/DocumentDetailsCard"; import { DocumentTitleForm } from "./_components/DocumentTitleForm"; import { DocumentVersionsDropdown } from "./_components/DocumentVersionsDropdown"; import { PublishDialog, type PublishDialogRef } from "./_components/PublishDialog"; @@ -39,7 +39,7 @@ export const documentLayoutQuery = graphql` status ...DocumentTitleFormFragment ...DocumentActionsDropdown_versionFragment - ...DocumentLayoutDrawer_versionFragment + ...DocumentDetailsCard_versionFragment signatures(first: 0 filter: { activeContract: true }) { totalCount } @@ -73,9 +73,9 @@ export const documentLayoutQuery = graphql` totalCount } ...DocumentActionsDropdown_documentFragment - ...DocumentLayoutDrawer_documentFragment - # We use this on /documents/:documentId - lastVersion: versions(first: 1 orderBy: { field: CREATED_AT, direction: DESC }) @skip(if: $versionSpecified) { + ...DocumentDetailsCard_documentFragment + lastVersion: versions(first: 1 orderBy: { field: CREATED_AT, direction: DESC }) + @connection(key: "DocumentLayout_lastVersion") { edges { node { id @@ -83,7 +83,7 @@ export const documentLayoutQuery = graphql` status ...DocumentTitleFormFragment ...DocumentActionsDropdown_versionFragment - ...DocumentLayoutDrawer_versionFragment + ...DocumentDetailsCard_versionFragment signatures(first: 0 filter: { activeContract: true }) { totalCount } @@ -117,6 +117,8 @@ export function DocumentLayout(props: { queryRef: PreloadedQuery { - onRefetch(); - setVersionChangedAt(Date.now()); - }, [onRefetch]); - const { document, version } = usePreloadedQuery(documentLayoutQuery, queryRef); if (document.__typename !== "Document" || (version && version.__typename !== "DocumentVersion")) { throw new Error("invalid node type"); } - const lastVersion = document.lastVersion?.edges[0].node; + const lastVersion = document.lastVersion?.edges[0]?.node; if (!version && !lastVersion) { throw new Error("current version not specified"); } - // It is ok to cas as NonNullable here since we know we have either version or lastVersion - const currentVersion = version ?? lastVersion as NonNullable; + const currentVersion = version ?? lastVersion; + const isLatestVersion = currentVersion.id === lastVersion?.id; + const isPendingApproval = currentVersion.status === "PENDING_APPROVAL"; const isDraft = currentVersion.status === "DRAFT"; const isPublished = currentVersion.status === "PUBLISHED"; + const isEditable = isLatestVersion && !isPendingApproval; const lastQuorum = currentVersion.approvalQuorums?.edges?.[0]?.node ?? null; const hasApprovals = lastQuorum != null; + const currentTab = location.pathname.split("/").at(-1); + + // For changes on the current version (type, classification, title, content). + // Refreshes layout data but does NOT remount the editor. + const handleDocumentUpdated = useCallback(() => { + if (versionId) { + void navigate( + `/organizations/${organizationId}/documents/${document.id}/${currentTab}`, + { replace: true }, + ); + } else { + onRefetch(); + } + }, [versionId, currentTab, navigate, organizationId, document.id, onRefetch]); + + // For structural version changes (delete draft, revert). + // Refreshes layout data AND remounts the editor via versionChangedAt. + const handleVersionChanged = useCallback(() => { + if (versionId) { + void navigate( + `/organizations/${organizationId}/documents/${document.id}/${currentTab}`, + { replace: true }, + ); + } else { + onRefetch(); + setVersionChangedAt(Date.now()); + } + }, [versionId, currentTab, navigate, organizationId, document.id, onRefetch]); + const urlPrefix = versionId ? `/organizations/${organizationId}/documents/${document.id}/versions/${versionId}` : `/organizations/${organizationId}/documents/${document.id}`; @@ -180,7 +208,7 @@ export function DocumentLayout(props: { queryRef: PreloadedQuery )} - + )} + > + + {currentVersion.status === "PUBLISHED" ? __("Published") : currentVersion.status === "PENDING_APPROVAL" ? __("Pending approval") : __("Draft")} + + + + @@ -228,15 +270,17 @@ export function DocumentLayout(props: { queryRef: PreloadedQuery - + - - (documentLayoutQuery); + // Detect param changes (e.g. navigating from versioned to versionless URL) + // and refetch without remounting the component tree. + const paramsKey = `${documentId}-${versionId}`; + const [prevParamsKey, setPrevParamsKey] = useState(paramsKey); + if (queryRef && paramsKey !== prevParamsKey) { + setPrevParamsKey(paramsKey); + loadQuery( + { documentId, versionId: versionId ?? "", versionSpecified: !!versionId }, + { fetchPolicy: "store-and-network" }, + ); + } + useEffect(() => { if (!queryRef) { - loadQuery({ - documentId, - versionId: versionId ?? "", - versionSpecified: !!versionId, - }); + loadQuery( + { + documentId, + versionId: versionId ?? "", + versionSpecified: !!versionId, + }, + { fetchPolicy: "store-and-network" }, + ); } }); @@ -52,11 +67,11 @@ function DocumentLayoutQueryLoader() { } export default function DocumentLayoutLoader() { - const { documentId, versionId } = useParams(); + const { documentId } = useParams(); return ( - }> + }> diff --git a/apps/console/src/pages/organizations/documents/_components/DocumentDetailsCard.tsx b/apps/console/src/pages/organizations/documents/_components/DocumentDetailsCard.tsx new file mode 100644 index 000000000..d795941f4 --- /dev/null +++ b/apps/console/src/pages/organizations/documents/_components/DocumentDetailsCard.tsx @@ -0,0 +1,467 @@ +// Copyright (c) 2026 Probo Inc . +// +// 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 { documentClassifications, documentTypes, formatDate, getDocumentClassificationLabel, getDocumentTypeLabel } from "@probo/helpers"; +import { useTranslate } from "@probo/i18n"; +import { Badge, Button, Card, IconCheckmark1, IconCrossLargeX, IconPencil, useToast } from "@probo/ui"; +import { useState } from "react"; +import { useFragment, useMutation } from "react-relay"; +import { graphql } from "relay-runtime"; +import { z } from "zod"; + +import type { DocumentDetailsCard_documentFragment$key } from "#/__generated__/core/DocumentDetailsCard_documentFragment.graphql"; +import type { DocumentDetailsCard_updateApproversMutation } from "#/__generated__/core/DocumentDetailsCard_updateApproversMutation.graphql"; +import type { DocumentDetailsCard_updateClassificationMutation } from "#/__generated__/core/DocumentDetailsCard_updateClassificationMutation.graphql"; +import type { DocumentDetailsCard_versionFragment$key } from "#/__generated__/core/DocumentDetailsCard_versionFragment.graphql"; +import type { DocumentDetailsCardMutation } from "#/__generated__/core/DocumentDetailsCardMutation.graphql"; +import { ControlledField } from "#/components/form/ControlledField"; +import { DocumentClassificationOptions } from "#/components/form/DocumentClassificationOptions"; +import { DocumentTypeOptions } from "#/components/form/DocumentTypeOptions"; +import { PeopleMultiSelectField } from "#/components/form/PeopleMultiSelectField"; +import { useFormWithSchema } from "#/hooks/useFormWithSchema"; +import { useOrganizationId } from "#/hooks/useOrganizationId"; + +const documentFragment = graphql` + fragment DocumentDetailsCard_documentFragment on Document { + id + archivedAt + canUpdate: permission(action: "core:document:update") + defaultApprovers { + id + fullName + emailAddress + } + } +`; + +const versionFragment = graphql` + fragment DocumentDetailsCard_versionFragment on DocumentVersion { + id + documentType + classification + major + minor + updatedAt + publishedAt + } +`; + +const updateDocumentTypeMutation = graphql` + mutation DocumentDetailsCardMutation($input: UpdateDocumentInput!) { + updateDocument(input: $input) { + document { + id + versions(first: 1, orderBy: { field: CREATED_AT, direction: DESC }) { + edges { + node { + id + documentType + } + } + } + } + } + } +`; + +const updateClassificationMutation = graphql` + mutation DocumentDetailsCard_updateClassificationMutation($input: UpdateDocumentInput!) { + updateDocument(input: $input) { + document { + id + versions(first: 1, orderBy: { field: CREATED_AT, direction: DESC }) { + edges { + node { + id + classification + } + } + } + } + } + } +`; + +const updateApproversMutation = graphql` + mutation DocumentDetailsCard_updateApproversMutation($input: UpdateDocumentInput!) { + updateDocument(input: $input) { + document { + id + defaultApprovers { + id + fullName + emailAddress + } + } + } + } +`; + +const schema = z.object({ + documentType: z.enum(documentTypes), +}); + +const classificationSchema = z.object({ + classification: z.enum(documentClassifications), +}); + +const approversSchema = z.object({ + approverIds: z.array(z.string()), +}); + +export function DocumentDetailsCard(props: { + documentFragmentRef: DocumentDetailsCard_documentFragment$key; + versionFragmentRef: DocumentDetailsCard_versionFragment$key; + isEditable: boolean; + onDocumentUpdated: () => void; +}) { + const { documentFragmentRef, versionFragmentRef, isEditable, onDocumentUpdated } = props; + + const { __ } = useTranslate(); + const organizationId = useOrganizationId(); + + const [isEditingType, setIsEditingType] = useState(false); + const [isEditingClassification, setIsEditingClassification] = useState(false); + const [isEditingApprovers, setIsEditingApprovers] = useState(false); + + const { toast } = useToast(); + const document = useFragment(documentFragment, documentFragmentRef); + const version = useFragment(versionFragment, versionFragmentRef); + + const canEdit = document.canUpdate && isEditable; + + const { control, handleSubmit, reset } = useFormWithSchema( + schema, + { + values: { + documentType: version.documentType, + }, + }, + ); + + const { + control: classificationControl, + handleSubmit: handleClassificationSubmit, + reset: resetClassification, + } = useFormWithSchema( + classificationSchema, + { + values: { + classification: version.classification, + }, + }, + ); + + const { + control: approversControl, + handleSubmit: handleApproversSubmit, + reset: resetApprovers, + } = useFormWithSchema( + approversSchema, + { + values: { + approverIds: document.defaultApprovers.map(a => a.id), + }, + }, + ); + + const [updateDocumentType, isUpdatingDocumentType] + = useMutation(updateDocumentTypeMutation); + + const [updateClassification, isUpdatingClassification] + = useMutation(updateClassificationMutation); + + const [updateApprovers, isUpdatingApprovers] + = useMutation(updateApproversMutation); + + const handleUpdateDocumentType = (data: { + documentType: (typeof documentTypes)[number]; + }) => { + updateDocumentType({ + variables: { + input: { + id: document.id, + documentType: data.documentType, + }, + }, + onCompleted: () => { + setIsEditingType(false); + onDocumentUpdated(); + toast({ + title: __("Success"), + description: __("Document type updated successfully"), + variant: "success", + }); + }, + onError: () => { + toast({ + title: __("Error"), + description: __("Failed to update document type"), + variant: "error", + }); + }, + }); + }; + + const handleUpdateClassification = (data: { + classification: (typeof documentClassifications)[number]; + }) => { + updateClassification({ + variables: { + input: { + id: document.id, + classification: data.classification, + }, + }, + onCompleted: () => { + setIsEditingClassification(false); + onDocumentUpdated(); + toast({ + title: __("Success"), + description: __("Document classification updated successfully"), + variant: "success", + }); + }, + onError: () => { + toast({ + title: __("Error"), + description: __("Failed to update document classification"), + variant: "error", + }); + }, + }); + }; + + const handleUpdateApprovers = (data: { approverIds: string[] }) => { + updateApprovers({ + variables: { + input: { + id: document.id, + defaultApproverIds: data.approverIds, + }, + }, + onCompleted: () => { + setIsEditingApprovers(false); + toast({ + title: __("Success"), + description: __("Approvers updated successfully"), + variant: "success", + }); + }, + onError: () => { + toast({ + title: __("Error"), + description: __("Failed to update approvers"), + variant: "error", + }); + }, + }); + }; + + return ( + +
+
+
+ {__("Approvers")} +
+ {isEditingApprovers + ? ( +
+
+ ({ + id: a.id, + fullName: a.fullName, + emailAddress: a.emailAddress, + }))} + placeholder={__("Add approvers...")} + /> +
+
+ ) + : ( +
+
+ {document.defaultApprovers.length > 0 + ? document.defaultApprovers.map(a => a.fullName).join(", ") + : __("None")} +
+ {canEdit && ( +
+ )} +
+
+
+ {__("Type")} +
+ {isEditingType + ? ( +
+
+ + + +
+
+ ) + : ( +
+
+ {getDocumentTypeLabel(__, version.documentType)} +
+ {canEdit && ( +
+ )} +
+
+
+ {__("Classification")} +
+ {isEditingClassification + ? ( +
+
+ + + +
+
+ ) + : ( +
+
+ {getDocumentClassificationLabel(__, version.classification)} +
+ {canEdit && ( +
+ )} +
+
+
+
+
+ {__("Version")} +
+
+ {version.major} + . + {version.minor} +
+
+
+
+ {__("Last modified")} +
+
+ {formatDate(version.updatedAt)} +
+
+
+ {version.publishedAt && ( + <> +
+ {__("Published Date")} +
+
+ {formatDate(version.publishedAt)} +
+ + )} + {document.archivedAt && ( + <> +
+ {__("Archived on")} +
+ + {formatDate(document.archivedAt)} + + + )} +
+
+
+ ); +} diff --git a/apps/console/src/pages/organizations/documents/_components/DocumentLayoutDrawer.tsx b/apps/console/src/pages/organizations/documents/_components/DocumentLayoutDrawer.tsx deleted file mode 100644 index 3e90fa522..000000000 --- a/apps/console/src/pages/organizations/documents/_components/DocumentLayoutDrawer.tsx +++ /dev/null @@ -1,445 +0,0 @@ -// Copyright (c) 2026 Probo Inc . -// -// 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 { documentClassifications, documentTypes, formatDate, getDocumentClassificationLabel, getDocumentTypeLabel } from "@probo/helpers"; -import { useTranslate } from "@probo/i18n"; -import { Badge, Button, Drawer, IconCheckmark1, IconCrossLargeX, IconPencil, PropertyRow, useToast } from "@probo/ui"; -import { useState } from "react"; -import { useFragment, useMutation } from "react-relay"; -import { graphql } from "relay-runtime"; -import { z } from "zod"; - -import type { DocumentLayoutDrawer_documentFragment$key } from "#/__generated__/core/DocumentLayoutDrawer_documentFragment.graphql"; -import type { DocumentLayoutDrawer_updateApproversMutation } from "#/__generated__/core/DocumentLayoutDrawer_updateApproversMutation.graphql"; -import type { DocumentLayoutDrawer_versionFragment$key } from "#/__generated__/core/DocumentLayoutDrawer_versionFragment.graphql"; -import type { DocumentLayoutDrawerMutation } from "#/__generated__/core/DocumentLayoutDrawerMutation.graphql"; -import { ControlledField } from "#/components/form/ControlledField"; -import { DocumentClassificationOptions } from "#/components/form/DocumentClassificationOptions"; -import { DocumentTypeOptions } from "#/components/form/DocumentTypeOptions"; -import { PeopleMultiSelectField } from "#/components/form/PeopleMultiSelectField"; -import { useFormWithSchema } from "#/hooks/useFormWithSchema"; -import { useOrganizationId } from "#/hooks/useOrganizationId"; - -const documentFragment = graphql` - fragment DocumentLayoutDrawer_documentFragment on Document { - id - status - archivedAt - canUpdate: permission(action: "core:document:update") - defaultApprovers { - id - fullName - emailAddress - } - } -`; - -const versionFragment = graphql` - fragment DocumentLayoutDrawer_versionFragment on DocumentVersion { - id - documentType - classification - major - minor - status - updatedAt - publishedAt - } -`; - -const updateDocumentMutation = graphql` - mutation DocumentLayoutDrawerMutation($input: UpdateDocumentInput!) { - updateDocument(input: $input) { - document { - id - } - documentVersion { - id - documentType - classification - major - minor - status - updatedAt - publishedAt - } - } - } -`; - -const updateApproversMutation = graphql` - mutation DocumentLayoutDrawer_updateApproversMutation($input: UpdateDocumentInput!) { - updateDocument(input: $input) { - document { - id - defaultApprovers { - id - fullName - emailAddress - } - } - } - } -`; - -const schema = z.object({ - documentType: z.enum(documentTypes), -}); - -const classificationSchema = z.object({ - classification: z.enum(documentClassifications), -}); - -const approversSchema = z.object({ - approverIds: z.array(z.string()), -}); - -export function DocumentLayoutDrawer(props: { - documentFragmentRef: DocumentLayoutDrawer_documentFragment$key; - versionFragmentRef: DocumentLayoutDrawer_versionFragment$key; - onVersionChanged: () => void; -}) { - const { documentFragmentRef, versionFragmentRef, onVersionChanged } = props; - - const { __ } = useTranslate(); - const organizationId = useOrganizationId(); - - const [isEditingType, setIsEditingType] = useState(false); - const [isEditingClassification, setIsEditingClassification] = useState(false); - const [isEditingApprovers, setIsEditingApprovers] = useState(false); - - const { toast } = useToast(); - const document = useFragment(documentFragment, documentFragmentRef); - const version = useFragment(versionFragment, versionFragmentRef); - - const isDraft = version.status === "DRAFT"; - const canEdit = document.canUpdate && document.status !== "ARCHIVED"; - - const { control, handleSubmit, reset } = useFormWithSchema( - schema, - { - values: { - documentType: version.documentType, - }, - }, - ); - - const { - control: classificationControl, - handleSubmit: handleClassificationSubmit, - reset: resetClassification, - } = useFormWithSchema( - classificationSchema, - { - values: { - classification: version.classification, - }, - }, - ); - - const { - control: approversControl, - handleSubmit: handleApproversSubmit, - reset: resetApprovers, - } = useFormWithSchema( - approversSchema, - { - values: { - approverIds: document.defaultApprovers.map(a => a.id), - }, - }, - ); - - const [updateDocument, isUpdatingDocument] - = useMutation(updateDocumentMutation); - - const [updateApprovers, isUpdatingApprovers] - = useMutation(updateApproversMutation); - - const handleUpdateDocumentType = (data: { - documentType: (typeof documentTypes)[number]; - }) => { - updateDocument({ - variables: { - input: { - id: document.id, - documentType: data.documentType, - }, - }, - onCompleted: (data) => { - setIsEditingType(false); - const draftReturned = !!data.updateDocument.documentVersion; - if (isDraft !== draftReturned) { - onVersionChanged(); - } - toast({ - title: __("Success"), - description: __("Document type updated successfully"), - variant: "success", - }); - }, - onError: () => { - toast({ - title: __("Error"), - description: __("Failed to update document type"), - variant: "error", - }); - }, - }); - }; - - const handleUpdateClassification = (data: { - classification: (typeof documentClassifications)[number]; - }) => { - updateDocument({ - variables: { - input: { - id: document.id, - classification: data.classification, - }, - }, - onCompleted: (data) => { - setIsEditingClassification(false); - const draftReturned = !!data.updateDocument.documentVersion; - if (isDraft !== draftReturned) { - onVersionChanged(); - } - toast({ - title: __("Success"), - description: __("Document classification updated successfully"), - variant: "success", - }); - }, - onError: () => { - toast({ - title: __("Error"), - description: __("Failed to update document classification"), - variant: "error", - }); - }, - }); - }; - - const handleUpdateApprovers = (data: { approverIds: string[] }) => { - updateApprovers({ - variables: { - input: { - id: document.id, - defaultApproverIds: data.approverIds, - }, - }, - onCompleted: () => { - setIsEditingApprovers(false); - toast({ - title: __("Success"), - description: __("Approvers updated successfully"), - variant: "success", - }); - }, - onError: () => { - toast({ - title: __("Error"), - description: __("Failed to update approvers"), - variant: "error", - }); - }, - }); - }; - - return ( - -
- {__("Properties")} -
- - {isEditingApprovers - ? ( - void handleApproversSubmit(handleUpdateApprovers)()} - onCancel={() => { - setIsEditingApprovers(false); - resetApprovers({ approverIds: document.defaultApprovers.map(a => a.id) }); - }} - disabled={isUpdatingApprovers} - > - ({ - id: a.id, - fullName: a.fullName, - emailAddress: a.emailAddress, - }))} - placeholder={__("Add approvers...")} - /> - - ) - : ( - setIsEditingApprovers(true)} - canEdit={canEdit} - > -
- {document.defaultApprovers.length > 0 - ? document.defaultApprovers.map(a => a.fullName).join(", ") - : __("None")} -
-
- )} -
- - {isEditingType - ? ( - void handleSubmit(handleUpdateDocumentType)()} - onCancel={() => { - setIsEditingType(false); - reset({ documentType: version.documentType }); - }} - disabled={isUpdatingDocument} - > - - - - - ) - : ( - setIsEditingType(true)} - canEdit={canEdit} - > -
- {getDocumentTypeLabel(__, version.documentType)} -
-
- )} -
- - {isEditingClassification - ? ( - void handleClassificationSubmit(handleUpdateClassification)()} - onCancel={() => { - setIsEditingClassification(false); - resetClassification({ classification: version.classification }); - }} - disabled={isUpdatingDocument} - > - - - - - ) - : ( - setIsEditingClassification(true)} - canEdit={canEdit} - > -
- {getDocumentClassificationLabel(__, version.classification)} -
-
- )} -
- - - {version.status === "PUBLISHED" ? __("Published") : version.status === "PENDING_APPROVAL" ? __("Pending approval") : __("Draft")} - - - -
- {version.major} - . - {version.minor} -
-
- -
- {formatDate(version.updatedAt)} -
-
- {version.publishedAt && ( - -
- {formatDate(version.publishedAt)} -
-
- )} - {document.archivedAt && ( - - - {formatDate(document.archivedAt)} - - - )} -
- ); -} - -function EditablePropertyContent({ - children, - onSave, - onCancel, - disabled, -}: { - children: React.ReactNode; - onSave: () => void; - onCancel: () => void; - disabled?: boolean; -}) { - return ( -
-
{children}
-
- ); -} - -function ReadOnlyPropertyContent({ - children, - onEdit, - canEdit = true, -}: { - children: React.ReactNode; - onEdit: () => void; - canEdit?: boolean; -}) { - return ( -
- {children} - {canEdit && ( -
- ); -} diff --git a/apps/console/src/pages/organizations/documents/_components/DocumentTitleForm.tsx b/apps/console/src/pages/organizations/documents/_components/DocumentTitleForm.tsx index b12e3b55f..5de195ffa 100644 --- a/apps/console/src/pages/organizations/documents/_components/DocumentTitleForm.tsx +++ b/apps/console/src/pages/organizations/documents/_components/DocumentTitleForm.tsx @@ -50,9 +50,10 @@ export function DocumentTitleForm(props: { fKey: DocumentTitleFormFragment$key; documentId: string; documentStatus: string; - onVersionChanged: () => void; + isEditable: boolean; + onDocumentUpdated: () => void; }) { - const { fKey, documentId, documentStatus, onVersionChanged } = props; + const { fKey, documentId, documentStatus, isEditable, onDocumentUpdated } = props; const { __ } = useTranslate(); const { toast } = useToast(); @@ -72,7 +73,7 @@ export function DocumentTitleForm(props: { ); const isDraft = version.status === "DRAFT"; - const canEdit = version.canUpdate && documentStatus !== "ARCHIVED"; + const canEdit = version.canUpdate && isEditable && documentStatus !== "ARCHIVED"; const handleUpdateTitle = (data: { title: string }) => { updateDocument({ @@ -90,7 +91,7 @@ export function DocumentTitleForm(props: { setIsEditingTitle(false); const draftReturned = !!data.updateDocument.documentVersion; if (isDraft !== draftReturned) { - onVersionChanged(); + onDocumentUpdated(); } }, onError(error) { diff --git a/apps/console/src/pages/organizations/documents/_components/DocumentVersionsDropdown.tsx b/apps/console/src/pages/organizations/documents/_components/DocumentVersionsDropdown.tsx index 131d4260e..e8f48bb14 100644 --- a/apps/console/src/pages/organizations/documents/_components/DocumentVersionsDropdown.tsx +++ b/apps/console/src/pages/organizations/documents/_components/DocumentVersionsDropdown.tsx @@ -22,7 +22,8 @@ import type { DocumentVersionsDropdownMenuQuery } from "#/__generated__/core/Doc import { DocumentVersionsDropdownMenu, documentVersionsDropdownMenuQuery } from "./DocumentVersionsDropdownMenu"; -export function DocumentVersionsDropdown() { +export function DocumentVersionsDropdown(props: { currentTab: string | undefined }) { + const { currentTab } = props; const { documentId, versionId } = useParams(); if (!documentId) { throw new Error(":documentId missing in route params"); @@ -33,7 +34,7 @@ export function DocumentVersionsDropdown() { return ( open && !queryRef && loadQuery({ documentId, versionId: versionId ?? "", versionSpecified: !!versionId })} + onOpenChange={open => open && loadQuery({ documentId, versionId: versionId ?? "", versionSpecified: !!versionId }, { fetchPolicy: "network-only" })} toggle={(