Add document types filtering and rename ISMS to GOVERNANCE
Adds 5 new document types (PLAN, REGISTER, RECORD, REPORT, TEMPLATE), renames ISMS to GOVERNANCE, and implements type-based filtering across GraphQL, MCP, and frontend. Includes migration, enum updates, filter implementation with SQL array support, and frontend dropdown UI with Relay refetch pattern. Signed-off-by: Bryan Frimin <bryan@getprobo.com>
This commit is contained in:
@@ -6,7 +6,7 @@ export const documentSchema = z.object({
|
||||
title: z.string().min(1, "Title is required"),
|
||||
content: z.string().min(1, "Content is required"),
|
||||
approverIds: z.array(z.string()).min(1, "At least one approver is required"),
|
||||
documentType: z.enum(["OTHER", "ISMS", "POLICY", "PROCEDURE"]),
|
||||
documentType: z.enum(["OTHER", "GOVERNANCE", "POLICY", "PROCEDURE", "PLAN", "REGISTER", "RECORD", "REPORT", "TEMPLATE"]),
|
||||
classification: z.enum(["PUBLIC", "INTERNAL", "CONFIDENTIAL", "SECRET"]),
|
||||
});
|
||||
|
||||
|
||||
@@ -29,7 +29,7 @@ export const documentsPageQuery = graphql`
|
||||
... on Organization {
|
||||
canCreateDocument: permission(action: "core:document:create")
|
||||
...DocumentListFragment @arguments(first: 50, order: { field: TITLE, direction: ASC })
|
||||
documents(first: 50, orderBy: { field: TITLE, direction: ASC }) {
|
||||
allDocuments: documents(first: 50, orderBy: { field: TITLE, direction: ASC }) {
|
||||
edges {
|
||||
node {
|
||||
canSendSigningNotifications: permission(
|
||||
@@ -63,7 +63,7 @@ export default function DocumentsPage(props: {
|
||||
|
||||
usePageTitle(__("Documents"));
|
||||
|
||||
const canSendAnySignatureNotifications = organization.documents.edges.some(
|
||||
const canSendAnySignatureNotifications = organization.allDocuments.edges.some(
|
||||
({ node: { canSendSigningNotifications } }) => canSendSigningNotifications,
|
||||
);
|
||||
|
||||
@@ -71,7 +71,10 @@ export default function DocumentsPage(props: {
|
||||
ConnectionHandler.getConnectionID(
|
||||
organizationId,
|
||||
"DocumentsListQuery_documents",
|
||||
{ orderBy: { direction: "ASC", field: "TITLE" } },
|
||||
{
|
||||
orderBy: { direction: "ASC", field: "TITLE" },
|
||||
filter: { documentTypes: null },
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import { sprintf } from "@probo/helpers";
|
||||
import { documentTypes, getDocumentTypeLabel, sprintf } from "@probo/helpers";
|
||||
import { useList } from "@probo/hooks";
|
||||
import { useTranslate } from "@probo/i18n";
|
||||
import { Button, Card, Checkbox, IconArrowDown, IconCheckmark1, IconCrossLargeX, IconSignature, IconTrashCan, Tbody, Th, Thead, Tr, useConfirm } from "@probo/ui";
|
||||
import { type ComponentProps, use, useRef } from "react";
|
||||
import { Button, Card, Checkbox, IconArrowDown, IconCheckmark1, IconCrossLargeX, IconSignature, IconTrashCan, Option, Select, Tbody, Th, Thead, Tr, useConfirm } from "@probo/ui";
|
||||
import { type ComponentProps, use, useRef, useState, useTransition } from "react";
|
||||
import { usePaginationFragment } from "react-relay";
|
||||
import { ConnectionHandler, graphql } from "relay-runtime";
|
||||
|
||||
import type { DocumentListFragment$key } from "#/__generated__/core/DocumentListFragment.graphql";
|
||||
import type { DocumentsListQuery } from "#/__generated__/core/DocumentsListQuery.graphql";
|
||||
import type { DocumentsListQuery, DocumentType } from "#/__generated__/core/DocumentsListQuery.graphql";
|
||||
import { BulkExportDialog, type BulkExportDialogRef } from "#/components/documents/BulkExportDialog";
|
||||
import { type Order, SortableTable, SortableTh } from "#/components/SortableTable";
|
||||
import { useBulkDeleteDocumentsMutation, useBulkExportDocumentsMutation } from "#/hooks/graph/DocumentGraph";
|
||||
@@ -30,6 +30,7 @@ const fragment = graphql`
|
||||
after: { type: "CursorKey", defaultValue: null }
|
||||
before: { type: "CursorKey", defaultValue: null }
|
||||
last: { type: "Int", defaultValue: null }
|
||||
documentTypes: { type: "[DocumentType!]", defaultValue: null }
|
||||
) {
|
||||
documents(
|
||||
first: $first
|
||||
@@ -37,7 +38,8 @@ const fragment = graphql`
|
||||
last: $last
|
||||
before: $before
|
||||
orderBy: $order
|
||||
) @connection(key: "DocumentsListQuery_documents" filters: ["orderBy"]) {
|
||||
filter: { documentTypes: $documentTypes }
|
||||
) @connection(key: "DocumentsListQuery_documents" filters: ["orderBy", "filter"]) {
|
||||
__id
|
||||
edges {
|
||||
node {
|
||||
@@ -79,6 +81,19 @@ export function DocumentList(props: {
|
||||
= useBulkExportDocumentsMutation();
|
||||
const { list: selection, toggle, clear, reset } = useList<string>([]);
|
||||
const confirm = useConfirm();
|
||||
const [isPending, startTransition] = useTransition();
|
||||
const [documentTypeFilter, setDocumentTypeFilter] = useState<DocumentType | null>(null);
|
||||
|
||||
const handleDocumentTypeFilterChange = (value: string) => {
|
||||
const newType = value === "ALL" ? null : (value as DocumentType);
|
||||
setDocumentTypeFilter(newType);
|
||||
startTransition(() => {
|
||||
pagination.refetch(
|
||||
{ documentTypes: newType ? [newType] : null },
|
||||
{ fetchPolicy: "network-only" },
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
const canDeleteAny = documents.some(({ canDelete }) => canDelete);
|
||||
const canUpdateAny = documents.some(({ canUpdate }) => canUpdate);
|
||||
@@ -134,12 +149,31 @@ export function DocumentList(props: {
|
||||
ConnectionHandler.getConnectionID(
|
||||
organizationId,
|
||||
"DocumentsListQuery_documents",
|
||||
{ orderBy: order },
|
||||
{
|
||||
orderBy: order,
|
||||
filter: { documentTypes: documentTypeFilter ? [documentTypeFilter] : null },
|
||||
},
|
||||
),
|
||||
);
|
||||
};
|
||||
|
||||
return documents.length > 0
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
<div className="flex items-center gap-4">
|
||||
<Select
|
||||
value={documentTypeFilter ?? "ALL"}
|
||||
onValueChange={handleDocumentTypeFilterChange}
|
||||
>
|
||||
<Option value="ALL">{__("All types")}</Option>
|
||||
{documentTypes.map((type) => (
|
||||
<Option key={type} value={type}>
|
||||
{getDocumentTypeLabel(__, type) ?? type}
|
||||
</Option>
|
||||
))}
|
||||
</Select>
|
||||
</div>
|
||||
<div className={isPending ? "opacity-50 pointer-events-none transition-opacity" : ""}>
|
||||
{documents.length > 0
|
||||
? (
|
||||
<SortableTable
|
||||
{...pagination}
|
||||
@@ -273,5 +307,8 @@ export function DocumentList(props: {
|
||||
</p>
|
||||
</div>
|
||||
</Card>
|
||||
);
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user