Handle document list state + API payload
Signed-off-by: Émile Ré <nemile.re@gmail.com>
This commit is contained in:
@@ -1,73 +1,15 @@
|
||||
import {
|
||||
Badge,
|
||||
Button,
|
||||
Checkbox,
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogFooter,
|
||||
Field,
|
||||
Spinner,
|
||||
Table,
|
||||
Tbody,
|
||||
Td,
|
||||
Th,
|
||||
Thead,
|
||||
Tr,
|
||||
} from "@probo/ui";
|
||||
import type { TrustCenterAccess, TrustCenterDocumentAccessStatus } from "@probo/coredata";
|
||||
import { getTrustCenterDocumentAccessInfo, type TrustCenterDocumentAccessInfo } from "@probo/helpers";
|
||||
import { Button, Checkbox, Dialog, DialogContent, DialogFooter, Field, Spinner } from "@probo/ui";
|
||||
import { usePreloadedQuery, type PreloadedQuery, useQueryLoader } from "react-relay";
|
||||
import type { TrustCenterAccessGraphLoadDocumentAccessesQuery } from "/hooks/graph/__generated__/TrustCenterAccessGraphLoadDocumentAccessesQuery.graphql";
|
||||
import type { TrustCenterDocumentAccess } from "/coredata/TrustCenterDocumentAccess";
|
||||
import { loadTrustCenterAccessDocumentAccessesQuery, updateTrustCenterAccessMutation } from "/hooks/graph/TrustCenterAccessGraph";
|
||||
import { useTranslate } from "@probo/i18n";
|
||||
import z from "zod";
|
||||
import { useFormWithSchema } from "/hooks/useFormWithSchema";
|
||||
import type { TrustCenterAccess } from "/coredata/TrustCenterAccess";
|
||||
import { useMutationWithToasts } from "/hooks/useMutationWithToasts";
|
||||
import { Suspense, useEffect } from "react";
|
||||
|
||||
function getDocumentAccessInfo(
|
||||
docAccess: TrustCenterDocumentAccess,
|
||||
__: (key: string) => string
|
||||
) {
|
||||
if (docAccess.document) {
|
||||
return {
|
||||
variant: "info" as const,
|
||||
name: docAccess.document?.title,
|
||||
type: __("Document"),
|
||||
category: docAccess.document?.documentType,
|
||||
id: docAccess.document?.id,
|
||||
requested: docAccess.requested,
|
||||
active: docAccess.active,
|
||||
status: docAccess.status,
|
||||
};
|
||||
}
|
||||
if (docAccess.report) {
|
||||
return {
|
||||
variant: "success" as const,
|
||||
name: docAccess.report?.filename,
|
||||
type: __("Report"),
|
||||
category: docAccess.report?.audit?.framework?.name,
|
||||
id: docAccess.report?.id,
|
||||
requested: docAccess.requested,
|
||||
active: docAccess.active,
|
||||
status: docAccess.status,
|
||||
};
|
||||
}
|
||||
if (docAccess.trustCenterFile) {
|
||||
return {
|
||||
variant: "highlight" as const,
|
||||
name: docAccess.trustCenterFile?.name,
|
||||
type: __("File"),
|
||||
category: docAccess.trustCenterFile?.category,
|
||||
id: docAccess.trustCenterFile?.id,
|
||||
requested: docAccess.requested,
|
||||
active: docAccess.active,
|
||||
status: docAccess.status,
|
||||
};
|
||||
}
|
||||
|
||||
throw new Error("Unknown trust center access document type");
|
||||
}
|
||||
import { Suspense, useCallback, useEffect, useState } from "react";
|
||||
import { TrustCenterDocumentAccessList } from "./TrustCenterDocumentAccessList";
|
||||
|
||||
interface TrustCenterAccessEditDialogProps {
|
||||
access: TrustCenterAccess;
|
||||
@@ -121,7 +63,30 @@ export function TrustCenterAccessEditForm(props: TrustCenterAccessEditFormProps)
|
||||
loadTrustCenterAccessDocumentAccessesQuery,
|
||||
queryRef,
|
||||
)
|
||||
const documentAccesses: TrustCenterDocumentAccess[] = data.node.availableDocumentAccesses?.edges.map(edge => edge.node) ?? [];
|
||||
|
||||
const initialDocumentAccesses = (data.node.availableDocumentAccesses?.edges.map(edge => edge.node) ?? []).map(da => getTrustCenterDocumentAccessInfo(da, __))
|
||||
const initialStatusByID = initialDocumentAccesses.reduce<Record<string, TrustCenterDocumentAccessStatus>>((acc, docAccess) => {
|
||||
acc[docAccess.id] = docAccess.status;
|
||||
return acc
|
||||
}, {})
|
||||
const [documentAccesses, setDocumentAccesses] = useState<TrustCenterDocumentAccessInfo[]>(initialDocumentAccesses);
|
||||
|
||||
const handleUpdateDocumentAccessStatus = useCallback((documentAccess: TrustCenterDocumentAccessInfo, status: TrustCenterDocumentAccessStatus) => {
|
||||
setDocumentAccesses((prev) => {
|
||||
const nextDocumentAccesses = [...prev];
|
||||
const docAccessIndex = nextDocumentAccesses.findIndex(element => element.id === documentAccess.id)
|
||||
const previousDocAccess = nextDocumentAccesses[docAccessIndex];
|
||||
nextDocumentAccesses.splice(docAccessIndex, 1, { ...previousDocAccess, status });
|
||||
|
||||
return nextDocumentAccesses;
|
||||
})
|
||||
}, [])
|
||||
const handleGrantAllDocumentAccess = useCallback(() => {
|
||||
setDocumentAccesses((prev) => prev.map(element => ({...element, status: "GRANTED"})))
|
||||
}, [])
|
||||
const handleRejectOrRevokeAllDocumentAccess = useCallback(() => {
|
||||
setDocumentAccesses((prev) => prev.map(element => ({...element, status: initialStatusByID[element.id] === "GRANTED" ? "REVOKED" : "REJECTED"})))
|
||||
}, [initialStatusByID])
|
||||
|
||||
const editSchema = z.object({
|
||||
name: z.string().min(1, __("Name is required")).min(2, __("Name must be at least 2 characters long")),
|
||||
@@ -137,20 +102,23 @@ export function TrustCenterAccessEditForm(props: TrustCenterAccessEditFormProps)
|
||||
});
|
||||
|
||||
const handleSubmit = editForm.handleSubmit(async (data) => {
|
||||
const { documentIds, reportIds, trustCenterFileIds } = documentAccesses.reduce(
|
||||
(acc, docAccess) => {
|
||||
// TODO status update
|
||||
if (docAccess.document?.id) {
|
||||
acc.documentIds.push(docAccess.document.id);
|
||||
} else if (docAccess.report?.id) {
|
||||
acc.reportIds.push(docAccess.report.id);
|
||||
} else if (docAccess.trustCenterFile?.id) {
|
||||
acc.trustCenterFileIds.push(docAccess.trustCenterFile.id);
|
||||
}
|
||||
return acc;
|
||||
},
|
||||
{ documentIds: [] as string[], reportIds: [] as string[], trustCenterFileIds: [] as string[] }
|
||||
);
|
||||
const documents: {id: string, status: TrustCenterDocumentAccessStatus}[] = []
|
||||
const reports: {id: string, status: TrustCenterDocumentAccessStatus}[] = []
|
||||
const trustCenterFiles: {id: string, status: TrustCenterDocumentAccessStatus}[] = [];
|
||||
|
||||
for (const docAccess of documentAccesses) {
|
||||
switch (docAccess.type) {
|
||||
case "document":
|
||||
documents.push({id: docAccess.id, status: docAccess.status});
|
||||
break;
|
||||
case "report":
|
||||
reports.push({id: docAccess.id, status: docAccess.status});
|
||||
break;
|
||||
case "file":
|
||||
trustCenterFiles.push({id: docAccess.id, status: docAccess.status});
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
await updateTrustCenterAccess({
|
||||
variables: {
|
||||
@@ -158,14 +126,12 @@ export function TrustCenterAccessEditForm(props: TrustCenterAccessEditFormProps)
|
||||
id: access.id,
|
||||
name: data.name.trim(),
|
||||
active: data.active,
|
||||
documentIds,
|
||||
reportIds,
|
||||
trustCenterFileIds,
|
||||
documents,
|
||||
reports,
|
||||
trustCenterFiles,
|
||||
},
|
||||
},
|
||||
onSuccess: () => {
|
||||
onSubmit();
|
||||
},
|
||||
onSuccess: onSubmit,
|
||||
});
|
||||
});
|
||||
|
||||
@@ -201,7 +167,13 @@ export function TrustCenterAccessEditForm(props: TrustCenterAccessEditFormProps)
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<TrustCenterDocumentAccessList documentAccesses={documentAccesses} />
|
||||
<TrustCenterDocumentAccessList
|
||||
documentAccesses={documentAccesses}
|
||||
initialStatusByID={initialStatusByID}
|
||||
onGrantAll={handleGrantAllDocumentAccess}
|
||||
onRejectOrRevokeAll={handleRejectOrRevokeAllDocumentAccess}
|
||||
onUpdateStatus={handleUpdateDocumentAccessStatus}
|
||||
/>
|
||||
</DialogContent>
|
||||
|
||||
<DialogFooter>
|
||||
@@ -213,103 +185,3 @@ export function TrustCenterAccessEditForm(props: TrustCenterAccessEditFormProps)
|
||||
</form>
|
||||
);
|
||||
}
|
||||
|
||||
function TrustCenterDocumentAccessList(props: {
|
||||
documentAccesses: TrustCenterDocumentAccess[];
|
||||
}) {
|
||||
const { documentAccesses } = props;
|
||||
|
||||
const { __ } = useTranslate();
|
||||
const formattedDocumentAccesses: NonNullable<ReturnType<typeof getDocumentAccessInfo>>[] = documentAccesses
|
||||
?.map((docAccess) => getDocumentAccessInfo(docAccess, __)) ?? [];
|
||||
|
||||
const showGrantCTA = formattedDocumentAccesses.some(da => da.status !== "GRANTED");
|
||||
const showRejectCTA = formattedDocumentAccesses.some(da => da.status !== "REJECTED" && da.status !== "REVOKED");
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className="flex justify-between items-center mb-4">
|
||||
<h4 className="font-medium text-txt-primary">
|
||||
{__("Document Access Permissions")}
|
||||
</h4>
|
||||
{showGrantCTA &&
|
||||
<Button
|
||||
type="button"
|
||||
variant="tertiary"
|
||||
// TODO onClick
|
||||
className="text-xs h-7 min-h-7"
|
||||
>
|
||||
{__("Grant All")}
|
||||
</Button>
|
||||
}
|
||||
{showRejectCTA &&
|
||||
<Button
|
||||
type="button"
|
||||
variant="danger"
|
||||
// TODO onCLick
|
||||
className="text-xs h-7 min-h-7"
|
||||
>
|
||||
{__("Reject All")}
|
||||
</Button>
|
||||
}
|
||||
</div>
|
||||
|
||||
{formattedDocumentAccesses.length > 0 ? (
|
||||
<div className="bg-bg-secondary rounded-lg overflow-hidden">
|
||||
<Table>
|
||||
<Thead>
|
||||
<Tr>
|
||||
<Th>{__("Name")}</Th>
|
||||
<Th>{__("Type")}</Th>
|
||||
<Th>{__("Category")}</Th>
|
||||
<Th>
|
||||
{__("Access")}
|
||||
</Th>
|
||||
<Th></Th>
|
||||
</Tr>
|
||||
</Thead>
|
||||
<Tbody>
|
||||
{formattedDocumentAccesses.map((info) => {
|
||||
const { variant, name, type, category, id, status } = info;
|
||||
|
||||
return (
|
||||
<Tr key={id}>
|
||||
<Td>
|
||||
<div className="font-medium text-txt-primary">
|
||||
{name}
|
||||
</div>
|
||||
</Td>
|
||||
<Td>
|
||||
<Badge variant={variant}>
|
||||
{type}
|
||||
</Badge>
|
||||
</Td>
|
||||
<Td>
|
||||
<div className="text-txt-secondary">
|
||||
{category || "-"}
|
||||
</div>
|
||||
</Td>
|
||||
<Td>
|
||||
<Badge variant="info">
|
||||
{status}
|
||||
</Badge>
|
||||
</Td>
|
||||
<Td>
|
||||
<div className="flex justify-end">
|
||||
{/* TODO DROPDOWN */}
|
||||
</div>
|
||||
</Td>
|
||||
</Tr>
|
||||
);
|
||||
})}
|
||||
</Tbody>
|
||||
</Table>
|
||||
</div>
|
||||
) : (
|
||||
<div className="text-center text-txt-tertiary py-8">
|
||||
{__("No documents available")}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { TrustCenterAccess } from "@probo/coredata";
|
||||
import { Button, IconCheckmark1, IconCrossLargeX, IconPencil, IconTrashCan, Td, Tr } from "@probo/ui";
|
||||
import type { TrustCenterAccess } from "/coredata/TrustCenterAccess";
|
||||
import { formatDate } from "@probo/helpers";
|
||||
import { use, useCallback, useState } from "react";
|
||||
import { PermissionsContext } from "/providers/PermissionsContext";
|
||||
|
||||
@@ -26,8 +26,8 @@ import {
|
||||
import { useFormWithSchema } from "/hooks/useFormWithSchema";
|
||||
import { useMutationWithToasts } from "/hooks/useMutationWithToasts";
|
||||
import { PermissionsContext } from "/providers/PermissionsContext";
|
||||
import type { TrustCenterAccess } from "/coredata/TrustCenterAccess";
|
||||
import { TrustCenterAccessItem } from "./TrustCenterAccessItem";
|
||||
import type { TrustCenterAccess } from "@probo/coredata";
|
||||
|
||||
type ContextType = {
|
||||
organization: {
|
||||
|
||||
@@ -0,0 +1,125 @@
|
||||
import type { TrustCenterDocumentAccessStatus } from "@probo/coredata";
|
||||
import { getTrustCenterDocumentAccessStatusBadgeVariant, type TrustCenterDocumentAccessInfo } from "@probo/helpers";
|
||||
import { useTranslate } from "@probo/i18n";
|
||||
import { Badge, Button, Table, Tbody, Td, Th, Thead, Tr } from "@probo/ui";
|
||||
|
||||
interface TrustCenterDocumentAccessListProps {
|
||||
documentAccesses: TrustCenterDocumentAccessInfo[];
|
||||
initialStatusByID: Record<string, TrustCenterDocumentAccessStatus>;
|
||||
onGrantAll: () => void;
|
||||
onRejectOrRevokeAll: () => void;
|
||||
onUpdateStatus: (docAccess: TrustCenterDocumentAccessInfo, status: TrustCenterDocumentAccessStatus) => void;
|
||||
}
|
||||
|
||||
export function TrustCenterDocumentAccessList(props: TrustCenterDocumentAccessListProps) {
|
||||
const { documentAccesses, initialStatusByID, onGrantAll, onRejectOrRevokeAll, onUpdateStatus } = props;
|
||||
|
||||
const { __ } = useTranslate();
|
||||
|
||||
const showGrantCTA = documentAccesses.some(da => da.status !== "GRANTED");
|
||||
const showRejectCTA = documentAccesses.some(da => da.status !== "REJECTED" && da.status !== "REVOKED");
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className="flex justify-between items-center mb-4">
|
||||
<h4 className="font-medium text-txt-primary">
|
||||
{__("Document Access Permissions")}
|
||||
</h4>
|
||||
<div className="ml-auto flex items-center gap-4">
|
||||
{showGrantCTA &&
|
||||
<Button
|
||||
type="button"
|
||||
variant="success"
|
||||
onClick={onGrantAll}
|
||||
className="text-xs h-7 min-h-7"
|
||||
>
|
||||
{__("Grant All")}
|
||||
</Button>
|
||||
}
|
||||
{showRejectCTA &&
|
||||
<Button
|
||||
type="button"
|
||||
variant="danger"
|
||||
onClick={onRejectOrRevokeAll}
|
||||
className="text-xs h-7 min-h-7"
|
||||
>
|
||||
{__("Reject/Revoke All")}
|
||||
</Button>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{documentAccesses.length > 0 ? (
|
||||
<div className="bg-bg-secondary rounded-lg overflow-hidden">
|
||||
<Table>
|
||||
<Thead>
|
||||
<Tr>
|
||||
<Th>{__("Name")}</Th>
|
||||
<Th>{__("Type")}</Th>
|
||||
<Th>{__("Category")}</Th>
|
||||
<Th>
|
||||
{__("Access")}
|
||||
</Th>
|
||||
<Th></Th>
|
||||
</Tr>
|
||||
</Thead>
|
||||
<Tbody>
|
||||
{documentAccesses.map((docAccess) => {
|
||||
return (
|
||||
<Tr key={docAccess.id}>
|
||||
<Td>
|
||||
<div className="font-medium text-txt-primary">
|
||||
{docAccess.name}
|
||||
</div>
|
||||
</Td>
|
||||
<Td>
|
||||
<Badge variant={docAccess.variant}>
|
||||
{docAccess.type}
|
||||
</Badge>
|
||||
</Td>
|
||||
<Td>
|
||||
<div className="text-txt-secondary">
|
||||
{docAccess.category || "-"}
|
||||
</div>
|
||||
</Td>
|
||||
<Td>
|
||||
<Badge variant={getTrustCenterDocumentAccessStatusBadgeVariant(docAccess.status)}>
|
||||
{docAccess.status}
|
||||
</Badge>
|
||||
</Td>
|
||||
<Td className="flex justify-end">
|
||||
{docAccess.status !== "GRANTED" &&
|
||||
<Button
|
||||
type="button"
|
||||
variant="success"
|
||||
onClick={() => onUpdateStatus(docAccess, "GRANTED")}
|
||||
className="text-xs h-7 min-h-7"
|
||||
>
|
||||
{__("Grant")}
|
||||
</Button>
|
||||
}
|
||||
{docAccess.status !== "REJECTED" && docAccess.status !== "REVOKED" &&
|
||||
<Button
|
||||
type="button"
|
||||
variant="danger"
|
||||
onClick={() => onUpdateStatus(docAccess, initialStatusByID[docAccess.id] === "GRANTED" ? "REVOKED" : "REJECTED")}
|
||||
className="text-xs h-7 min-h-7"
|
||||
>
|
||||
{initialStatusByID[docAccess.id] === "GRANTED" ? __("Revoke") : __("Reject")}
|
||||
</Button>
|
||||
}
|
||||
</Td>
|
||||
</Tr>
|
||||
);
|
||||
})}
|
||||
</Tbody>
|
||||
</Table>
|
||||
</div>
|
||||
) : (
|
||||
<div className="text-center text-txt-tertiary py-8">
|
||||
{__("No documents available")}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user