Refatcor policies management

Signed-off-by: Bryan Frimin <bryan@getprobo.com>
This commit is contained in:
Bryan Frimin
2025-04-28 16:03:42 -07:00
parent c208131b85
commit a7cd94956a
59 changed files with 10112 additions and 2303 deletions

View File

@@ -290,7 +290,7 @@ function BreadcrumbPolicyOverview() {
policy: node(id: $policyId) {
id
... on Policy {
name
title
}
}
}
@@ -306,7 +306,7 @@ function BreadcrumbPolicyOverview() {
<BreadcrumbNavLink
to={`/organizations/${organizationId}/policies/${policyId}`}
>
{data.policy?.name}
{data.policy?.title}
</BreadcrumbNavLink>
</BreadcrumbItem>
<Outlet />

View File

@@ -19,9 +19,8 @@ import { NewPeoplePage } from "./people/NewPeoplePage";
import { PeopleListPage } from "./people/PeopleListPage";
import { PeoplePage } from "./people/PeoplePage";
import { EditPolicyPage } from "./policies/EditPolicyPage";
import { NewPolicyPage } from "./policies/NewPolicyPage";
import { PolicyListPage } from "./policies/PolicyListPage";
import { PolicyPage } from "./policies/PolicyPage";
import { ShowPolicyPage } from "./policies/ShowPolicyPage";
import { EditRiskPage } from "./risks/EditRiskPage";
import { NewRiskPage } from "./risks/NewRiskPage";
import { ListRiskPage } from "./risks/ListRiskPage";
@@ -54,9 +53,8 @@ export function OrganizationsRoutes() {
<Route path="mesures/:mesureId/edit" element={<EditMesurePage />} />
<Route path="vendors/:vendorId" element={<VendorPage />} />
<Route path="policies" element={<PolicyListPage />} />
<Route path="policies/new" element={<NewPolicyPage />} />
<Route path="policies/:policyId" element={<PolicyPage />} />
<Route path="policies/:policyId/edit" element={<EditPolicyPage />} />
<Route path="policies/:policyId" element={<ShowPolicyPage />} />
<Route path="policies/:policyId/versions/:versionId/edit" element={<EditPolicyPage />} />
<Route path="risks" element={<ListRiskPage />} />
<Route path="risks/new" element={<NewRiskPage />} />
<Route path="risks/:riskId" element={<ShowRiskView />} />

View File

@@ -1,5 +1,5 @@
/**
* @generated SignedSource<<5f81eaad8c66c579401d9c73180f8f91>>
* @generated SignedSource<<ca410ce7df7231383e55d134d37916bd>>
* @lightSyntaxTransform
* @nogrep
*/
@@ -15,7 +15,7 @@ export type OrganizationBreadcrumbBreadcrumbPolicyOverviewQuery$variables = {
export type OrganizationBreadcrumbBreadcrumbPolicyOverviewQuery$data = {
readonly policy: {
readonly id: string;
readonly name?: string;
readonly title?: string;
};
};
export type OrganizationBreadcrumbBreadcrumbPolicyOverviewQuery = {
@@ -52,7 +52,7 @@ v3 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "name",
"name": "title",
"storageKey": null
}
],
@@ -112,16 +112,16 @@ return {
]
},
"params": {
"cacheID": "13d6ac9cf6f904f8a03f141b6b6e2564",
"cacheID": "bc0b04bbe75c94660de230461532773a",
"id": null,
"metadata": {},
"name": "OrganizationBreadcrumbBreadcrumbPolicyOverviewQuery",
"operationKind": "query",
"text": "query OrganizationBreadcrumbBreadcrumbPolicyOverviewQuery(\n $policyId: ID!\n) {\n policy: node(id: $policyId) {\n __typename\n id\n ... on Policy {\n name\n }\n }\n}\n"
"text": "query OrganizationBreadcrumbBreadcrumbPolicyOverviewQuery(\n $policyId: ID!\n) {\n policy: node(id: $policyId) {\n __typename\n id\n ... on Policy {\n title\n }\n }\n}\n"
}
};
})();
(node as any).hash = "6ed45dd2d1fa369329edde6b77eec92d";
(node as any).hash = "ced380d1e6eb1c31ecc5f2dc9096133b";
export default node;

View File

@@ -106,10 +106,15 @@ const linkedPoliciesQuery = graphql`
edges {
node {
id
name
content
status
reviewDate
title
description
currentPublishedVersion
createdAt
updatedAt
owner {
id
fullName
}
}
}
}
@@ -128,10 +133,15 @@ const organizationPoliciesQuery = graphql`
edges {
node {
id
name
content
status
reviewDate
title
description
currentPublishedVersion
createdAt
updatedAt
owner {
id
fullName
}
}
}
}
@@ -582,7 +592,7 @@ export function Control({
return policies.filter((policy) => {
return (
!policySearchQuery ||
policy.name.toLowerCase().includes(policySearchQuery.toLowerCase()) ||
policy.name?.toLowerCase().includes(policySearchQuery.toLowerCase()) ||
(policy.content &&
policy.content
.toLowerCase()
@@ -1066,10 +1076,7 @@ export function Control({
<thead className="sticky top-0 bg-white">
<tr className="border-b text-left text-sm text-secondary bg-invert-bg">
<th className="py-3 px-4 font-medium">Name</th>
<th className="py-3 px-4 font-medium">Status</th>
<th className="py-3 px-4 font-medium">
Review Date
</th>
<th className="py-3 px-4 font-medium">Review Date</th>
<th className="py-3 px-4 font-medium text-right">
Actions
</th>
@@ -1093,17 +1100,6 @@ export function Control({
</div>
)}
</td>
<td className="py-3 px-4">
<div
className={`px-2 py-0.5 rounded-full text-xs ${
policy.status === "ACTIVE"
? "bg-success-bg text-success"
: "bg-secondary-bg text-secondary"
} inline-block`}
>
{policy.status}
</div>
</td>
<td className="py-3 px-4">
{policy.reviewDate
? new Date(
@@ -1137,7 +1133,7 @@ export function Control({
handleLinkPolicy(policy.id)
}
disabled={isLinkingPolicy}
className="text-xs h-7 text-info border-info-b hover:bg-h-info-bg"
className="text-xs h-7 text-info border-info-b hover:bg-h-info-bg"
>
{isLinkingPolicy ? (
<Loader2 className="w-4 h-4 animate-spin" />
@@ -1193,7 +1189,6 @@ export function Control({
<thead>
<tr className="border-b text-left text-sm text-secondary bg-invert-bg">
<th className="py-3 px-4 font-medium">Name</th>
<th className="py-3 px-4 font-medium">Status</th>
<th className="py-3 px-4 font-medium">Review Date</th>
<th className="py-3 px-4 font-medium text-right">
Actions
@@ -1214,17 +1209,6 @@ export function Control({
</div>
)}
</td>
<td className="py-3 px-4">
<div
className={`px-2 py-0.5 rounded-full text-xs ${
policy.status === "ACTIVE"
? "bg-success-bg text-success"
: "bg-secondary-bg text-secondary"
} inline-block`}
>
{policy.status}
</div>
</td>
<td className="py-3 px-4">
{policy.reviewDate
? new Date(policy.reviewDate).toLocaleDateString()

View File

@@ -1,5 +1,5 @@
/**
* @generated SignedSource<<9bddb18bf52f14881a85995793bdc1ca>>
* @generated SignedSource<<85bdd7f83672d1a88247d958f4b7dfaf>>
* @lightSyntaxTransform
* @nogrep
*/
@@ -9,7 +9,6 @@
// @ts-nocheck
import { ConcreteRequest } from 'relay-runtime';
export type PolicyStatus = "ACTIVE" | "DRAFT";
export type ControlLinkedPoliciesQuery$variables = {
controlId: string;
};
@@ -19,11 +18,16 @@ export type ControlLinkedPoliciesQuery$data = {
readonly policies?: {
readonly edges: ReadonlyArray<{
readonly node: {
readonly content: string;
readonly createdAt: string;
readonly currentPublishedVersion: number | null | undefined;
readonly description: string;
readonly id: string;
readonly name: string;
readonly reviewDate: string | null | undefined;
readonly status: PolicyStatus;
readonly owner: {
readonly fullName: string;
readonly id: string;
};
readonly title: string;
readonly updatedAt: string;
};
}>;
};
@@ -85,28 +89,54 @@ v4 = [
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "name",
"name": "title",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "content",
"name": "description",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "status",
"name": "currentPublishedVersion",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "reviewDate",
"name": "createdAt",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "updatedAt",
"storageKey": null
},
{
"alias": null,
"args": null,
"concreteType": "People",
"kind": "LinkedField",
"name": "owner",
"plural": false,
"selections": [
(v2/*: any*/),
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "fullName",
"storageKey": null
}
],
"storageKey": null
},
(v3/*: any*/)
@@ -244,7 +274,7 @@ return {
]
},
"params": {
"cacheID": "67f9bf1d5f255f5406e932abde540e08",
"cacheID": "24c5ce380772f28348df8cf089a58a8e",
"id": null,
"metadata": {
"connection": [
@@ -261,11 +291,11 @@ return {
},
"name": "ControlLinkedPoliciesQuery",
"operationKind": "query",
"text": "query ControlLinkedPoliciesQuery(\n $controlId: ID!\n) {\n control: node(id: $controlId) {\n __typename\n id\n ... on Control {\n policies(first: 100) {\n edges {\n node {\n id\n name\n content\n status\n reviewDate\n __typename\n }\n cursor\n }\n pageInfo {\n endCursor\n hasNextPage\n }\n }\n }\n }\n}\n"
"text": "query ControlLinkedPoliciesQuery(\n $controlId: ID!\n) {\n control: node(id: $controlId) {\n __typename\n id\n ... on Control {\n policies(first: 100) {\n edges {\n node {\n id\n title\n description\n currentPublishedVersion\n createdAt\n updatedAt\n owner {\n id\n fullName\n }\n __typename\n }\n cursor\n }\n pageInfo {\n endCursor\n hasNextPage\n }\n }\n }\n }\n}\n"
}
};
})();
(node as any).hash = "3904c2344754177177fa7c16129dc894";
(node as any).hash = "d4d16cac2a05ea1d38fde55d04f21c01";
export default node;

View File

@@ -1,5 +1,5 @@
/**
* @generated SignedSource<<cfe85b9fdabaf24f9a7f575eca8a6417>>
* @generated SignedSource<<1f9de20276a5c0b1291c5e25dc11990b>>
* @lightSyntaxTransform
* @nogrep
*/
@@ -9,7 +9,6 @@
// @ts-nocheck
import { ConcreteRequest } from 'relay-runtime';
export type PolicyStatus = "ACTIVE" | "DRAFT";
export type ControlOrganizationPoliciesQuery$variables = {
organizationId: string;
};
@@ -19,11 +18,16 @@ export type ControlOrganizationPoliciesQuery$data = {
readonly policies?: {
readonly edges: ReadonlyArray<{
readonly node: {
readonly content: string;
readonly createdAt: string;
readonly currentPublishedVersion: number | null | undefined;
readonly description: string;
readonly id: string;
readonly name: string;
readonly reviewDate: string | null | undefined;
readonly status: PolicyStatus;
readonly owner: {
readonly fullName: string;
readonly id: string;
};
readonly title: string;
readonly updatedAt: string;
};
}>;
};
@@ -85,28 +89,54 @@ v4 = [
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "name",
"name": "title",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "content",
"name": "description",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "status",
"name": "currentPublishedVersion",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "reviewDate",
"name": "createdAt",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "updatedAt",
"storageKey": null
},
{
"alias": null,
"args": null,
"concreteType": "People",
"kind": "LinkedField",
"name": "owner",
"plural": false,
"selections": [
(v2/*: any*/),
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "fullName",
"storageKey": null
}
],
"storageKey": null
},
(v3/*: any*/)
@@ -244,7 +274,7 @@ return {
]
},
"params": {
"cacheID": "3a3c91f3e18e8a4b66011aa583e262e4",
"cacheID": "94e32c7758c8df264b99a44150de687d",
"id": null,
"metadata": {
"connection": [
@@ -261,11 +291,11 @@ return {
},
"name": "ControlOrganizationPoliciesQuery",
"operationKind": "query",
"text": "query ControlOrganizationPoliciesQuery(\n $organizationId: ID!\n) {\n organization: node(id: $organizationId) {\n __typename\n id\n ... on Organization {\n policies(first: 100) {\n edges {\n node {\n id\n name\n content\n status\n reviewDate\n __typename\n }\n cursor\n }\n pageInfo {\n endCursor\n hasNextPage\n }\n }\n }\n }\n}\n"
"text": "query ControlOrganizationPoliciesQuery(\n $organizationId: ID!\n) {\n organization: node(id: $organizationId) {\n __typename\n id\n ... on Organization {\n policies(first: 100) {\n edges {\n node {\n id\n title\n description\n currentPublishedVersion\n createdAt\n updatedAt\n owner {\n id\n fullName\n }\n __typename\n }\n cursor\n }\n pageInfo {\n endCursor\n hasNextPage\n }\n }\n }\n }\n}\n"
}
};
})();
(node as any).hash = "f4699d729e6df6192efd201047689204";
(node as any).hash = "47aaf55ffb65d3220df2b0fe2688bc12";
export default node;

View File

@@ -10,27 +10,30 @@ import {
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Textarea } from "@/components/ui/textarea";
import { Label } from "@/components/ui/label";
import { toast } from "@/hooks/use-toast";
import { Calendar, User } from "lucide-react";
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
import { Suspense } from "react";
import PolicyEditor from "@/components/PolicyEditor";
import PeopleSelector from "@/components/PeopleSelector";
import type { EditPolicyViewQuery } from "./__generated__/EditPolicyViewQuery.graphql";
import type { EditPolicyViewMutation as EditPolicyViewMutationType } from "./__generated__/EditPolicyViewMutation.graphql";
import { PageTemplate } from "@/components/PageTemplate";
import { EditPolicyViewSkeleton } from "./EditPolicyPage";
import { User } from "lucide-react";
const editPolicyViewQuery = graphql`
query EditPolicyViewQuery($policyId: ID!, $organizationId: ID!) {
query EditPolicyViewQuery($policyId: ID!, $organizationId: ID!, $policyVersionId: ID!) {
policyVersion: node(id: $policyVersionId) {
id
... on PolicyVersion {
content
}
}
policy: node(id: $policyId) {
id
... on Policy {
name
content
status
reviewDate
title
owner {
id
fullName
@@ -44,18 +47,11 @@ const editPolicyViewQuery = graphql`
`;
const UpdatePolicyMutation = graphql`
mutation EditPolicyViewMutation($input: UpdatePolicyInput!) {
updatePolicy(input: $input) {
policy {
mutation EditPolicyViewMutation($input: UpdatePolicyVersionInput!) {
updatePolicyVersion(input: $input) {
policyVersion {
id
name
content
status
reviewDate
owner {
id
fullName
}
}
}
}
@@ -67,56 +63,28 @@ function EditPolicyViewContent({
queryRef: PreloadedQuery<EditPolicyViewQuery>;
}) {
const navigate = useNavigate();
const { organizationId, policyId } = useParams();
const { organizationId, policyId, versionId } = useParams();
const data = usePreloadedQuery<EditPolicyViewQuery>(
editPolicyViewQuery,
queryRef
);
console.log("EditPolicyView data:", data.policy);
const [content, setContent] = useState(data.policyVersion.content || "");
const [name, setName] = useState(data.policy.name);
const [content, setContent] = useState(data.policy.content || "");
const [status, setStatus] = useState(data.policy.status);
const [reviewDate, setReviewDate] = useState(data.policy.reviewDate || "");
const [ownerId, setOwnerId] = useState<string | null>(
data.policy.owner?.id || null
);
const [isSubmitting, setIsSubmitting] = useState(false);
console.log(
"EditPolicyView state - content:",
content
? content.substring(0, 50) + (content.length > 50 ? "..." : "")
: "empty"
);
const [commitMutation] =
const [updatePolicy, isSubmitting] =
useMutation<EditPolicyViewMutationType>(UpdatePolicyMutation);
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
setIsSubmitting(true);
// Convert reviewDate string to ISO format for the API
let reviewDateValue = null;
if (reviewDate) {
reviewDateValue = new Date(reviewDate).toISOString();
}
commitMutation({
updatePolicy({
variables: {
input: {
id: data.policy.id,
name,
policyVersionId: data.policyVersion.id,
content,
status,
reviewDate: reviewDateValue,
ownerId,
},
},
onCompleted: (response, errors) => {
setIsSubmitting(false);
onCompleted: (_, errors) => {
if (errors) {
console.error("Error updating policy:", errors);
toast({
@@ -134,7 +102,6 @@ function EditPolicyViewContent({
navigate(`/organizations/${organizationId}/policies/${policyId}`);
},
onError: (error) => {
setIsSubmitting(false);
console.error("Error updating policy:", error);
toast({
title: "Error",
@@ -153,77 +120,20 @@ function EditPolicyViewContent({
<CardTitle>Policy Information</CardTitle>
</CardHeader>
<CardContent className="space-y-4">
<div className="space-y-2">
<Label htmlFor="name">Policy Name</Label>
<Input
id="name"
placeholder="Enter policy name"
value={name}
onChange={(e) => setName(e.target.value)}
required
/>
</div>
<div className="space-y-2">
<Label htmlFor="content">Policy Content</Label>
<div className="min-h-[300px]">
<PolicyEditor
initialContent={content}
onChange={(html) => setContent(html)}
<div className="min-h-[500px]">
<Textarea
id="content"
placeholder="Enter policy description"
value={content}
onChange={(e) => setContent(e.target.value)}
required
rows={20}
className="min-h-[500px] resize-y"
/>
</div>
</div>
<div className="space-y-2">
<Label>Status</Label>
<RadioGroup
value={status}
onValueChange={(value) =>
setStatus(value as "DRAFT" | "ACTIVE")
}
className="flex space-x-4"
>
<div className="flex items-center space-x-2">
<RadioGroupItem value="DRAFT" id="draft" />
<Label htmlFor="draft" className="cursor-pointer">
Draft
</Label>
</div>
<div className="flex items-center space-x-2">
<RadioGroupItem value="ACTIVE" id="active" />
<Label htmlFor="active" className="cursor-pointer">
Active
</Label>
</div>
</RadioGroup>
</div>
<div className="space-y-2">
<Label htmlFor="owner" className="flex items-center gap-2">
<User className="h-4 w-4" />
Policy Owner
</Label>
<PeopleSelector
organizationRef={data.organization}
selectedPersonId={ownerId}
onSelect={setOwnerId}
placeholder="Select policy owner"
required
/>
</div>
<div className="space-y-2">
<Label htmlFor="reviewDate" className="flex items-center gap-2">
<Calendar className="h-4 w-4" />
Review Date
</Label>
<Input
id="reviewDate"
type="date"
value={reviewDate}
onChange={(e) => setReviewDate(e.target.value)}
/>
</div>
</CardContent>
</Card>
@@ -250,15 +160,17 @@ function EditPolicyViewContent({
}
export default function EditPolicyView() {
const { organizationId, policyId } = useParams();
const { organizationId, policyId, versionId } = useParams();
const [queryRef, loadQuery] =
useQueryLoader<EditPolicyViewQuery>(editPolicyViewQuery);
useEffect(() => {
if (organizationId && policyId) {
loadQuery({ organizationId, policyId });
}
}, [organizationId, policyId, loadQuery]);
loadQuery({
organizationId: organizationId!,
policyId: policyId!,
policyVersionId: versionId!,
});
}, [organizationId, policyId, versionId]);
if (!queryRef) {
return <EditPolicyViewSkeleton />;
@@ -266,7 +178,7 @@ export default function EditPolicyView() {
return (
<Suspense fallback={<EditPolicyViewSkeleton />}>
{queryRef && <EditPolicyViewContent queryRef={queryRef} />}
<EditPolicyViewContent queryRef={queryRef}/>
</Suspense>
);
}

View File

@@ -1,30 +0,0 @@
import { PageTemplateSkeleton } from "@/components/PageTemplate";
import { Suspense } from "react";
import { useLocation } from "react-router";
import { ErrorBoundaryWithLocation } from "../ErrorBoundary";
import { lazy } from "@probo/react-lazy";
const NewPolicyView = lazy(() => import("./NewPolicyView"));
export function NewPolicyViewSkeleton() {
return (
<PageTemplateSkeleton
title="New Policy"
description="Add a new policy for your organization"
>
<div className="bg-subtle-bg animate-pulse rounded-lg h-[600px]" />
</PageTemplateSkeleton>
);
}
export function NewPolicyPage() {
const location = useLocation();
return (
<Suspense key={location.pathname} fallback={<NewPolicyViewSkeleton />}>
<ErrorBoundaryWithLocation>
<NewPolicyView />
</ErrorBoundaryWithLocation>
</Suspense>
);
}

View File

@@ -1,281 +0,0 @@
import { useState, useEffect } from "react";
import { useNavigate, useParams } from "react-router";
import {
ConnectionHandler,
graphql,
useMutation,
useQueryLoader,
usePreloadedQuery,
PreloadedQuery,
} from "react-relay";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { useToast } from "@/hooks/use-toast";
import { Calendar, User } from "lucide-react";
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
import PolicyEditor from "@/components/PolicyEditor";
import PeopleSelector from "@/components/PeopleSelector";
import { Suspense } from "react";
import type { NewPolicyViewMutation } from "./__generated__/NewPolicyViewMutation.graphql";
import type { NewPolicyViewQuery } from "./__generated__/NewPolicyViewQuery.graphql";
import { PageTemplate } from "@/components/PageTemplate";
import { NewPolicyViewSkeleton } from "./NewPolicyPage";
const newPolicyQuery = graphql`
query NewPolicyViewQuery($organizationId: ID!) {
organization: node(id: $organizationId) {
...PeopleSelector_organization
}
}
`;
const CreatePolicyMutation = graphql`
mutation NewPolicyViewMutation(
$input: CreatePolicyInput!
$connections: [ID!]!
) {
createPolicy(input: $input) {
policyEdge @prependEdge(connections: $connections) {
node {
id
name
content
status
reviewDate
owner {
id
fullName
}
}
}
}
}
`;
function CreatePolicyForm({
queryRef,
}: {
queryRef: PreloadedQuery<NewPolicyViewQuery>;
}) {
const navigate = useNavigate();
const { organizationId } = useParams();
const data = usePreloadedQuery<NewPolicyViewQuery>(newPolicyQuery, queryRef);
const [name, setName] = useState("");
const [content, setContent] = useState("");
const [status, setStatus] = useState<"DRAFT" | "ACTIVE">("DRAFT");
const [reviewDate, setReviewDate] = useState("");
const [ownerId, setOwnerId] = useState<string | null>(null);
const [isSubmitting, setIsSubmitting] = useState(false);
const { toast } = useToast();
console.log(
"NewPolicyView state - content:",
content
? content.substring(0, 50) + (content.length > 50 ? "..." : "")
: "empty"
);
const [createPolicy] =
useMutation<NewPolicyViewMutation>(CreatePolicyMutation);
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
if (!ownerId) {
toast({
title: "Error",
description: "Please select an owner for the policy.",
variant: "destructive",
});
return;
}
setIsSubmitting(true);
// Convert reviewDate string to ISO format for the API
let reviewDateValue = null;
if (reviewDate) {
reviewDateValue = new Date(reviewDate).toISOString();
}
const input = {
organizationId: organizationId!,
name,
content,
status,
reviewDate: reviewDateValue,
ownerId,
};
createPolicy({
variables: {
input,
connections: [
ConnectionHandler.getConnectionID(
organizationId!,
"PolicyListView_policies"
),
],
},
onCompleted: (response, errors) => {
setIsSubmitting(false);
if (errors) {
console.error("Error creating policy:", errors);
toast({
title: "Error",
description: "Failed to create policy. Please try again.",
variant: "destructive",
});
return;
}
toast({
title: "Success",
description: "Policy created successfully!",
});
navigate(
`/organizations/${organizationId}/policies/${response.createPolicy.policyEdge.node.id}`
);
},
onError: (error) => {
setIsSubmitting(false);
console.error("Error creating policy:", error);
toast({
title: "Error",
description: "Failed to create policy. Please try again.",
variant: "destructive",
});
},
});
};
return (
<PageTemplate
title="Create Policy"
description="Create a new policy for your organization"
>
<form onSubmit={handleSubmit}>
<div className="grid gap-6">
<Card>
<CardHeader>
<CardTitle>Policy Information</CardTitle>
</CardHeader>
<CardContent className="space-y-4">
<div className="space-y-2">
<Label htmlFor="name">Policy Name</Label>
<Input
id="name"
placeholder="Enter policy name"
value={name}
onChange={(e) => setName(e.target.value)}
required
/>
</div>
<div className="space-y-2">
<Label htmlFor="content">Policy Content</Label>
<div className="min-h-[300px]">
<PolicyEditor
initialContent={content}
onChange={(html) => setContent(html)}
/>
</div>
</div>
<div className="space-y-2">
<Label>Status</Label>
<RadioGroup
value={status}
onValueChange={(value: "DRAFT" | "ACTIVE") =>
setStatus(value)
}
className="flex space-x-4"
>
<div className="flex items-center space-x-2">
<RadioGroupItem value="DRAFT" id="draft" />
<Label htmlFor="draft" className="cursor-pointer">
Draft
</Label>
</div>
<div className="flex items-center space-x-2">
<RadioGroupItem value="ACTIVE" id="active" />
<Label htmlFor="active" className="cursor-pointer">
Active
</Label>
</div>
</RadioGroup>
</div>
<div className="space-y-2">
<Label htmlFor="owner" className="flex items-center gap-2">
<User className="h-4 w-4" />
Policy Owner
</Label>
<PeopleSelector
organizationRef={data.organization}
selectedPersonId={ownerId}
onSelect={setOwnerId}
placeholder="Select policy owner"
required
/>
</div>
<div className="space-y-2">
<Label htmlFor="reviewDate" className="flex items-center gap-2">
<Calendar className="h-4 w-4" />
Review Date
</Label>
<Input
id="reviewDate"
type="date"
value={reviewDate}
onChange={(e) => setReviewDate(e.target.value)}
/>
</div>
</CardContent>
</Card>
<div className="flex justify-end gap-4">
<Button
type="button"
variant="outline"
onClick={() =>
navigate(`/organizations/${organizationId}/policies`)
}
>
Cancel
</Button>
<Button type="submit" disabled={isSubmitting}>
{isSubmitting ? "Creating..." : "Create Policy"}
</Button>
</div>
</div>
</form>
</PageTemplate>
);
}
export default function NewPolicyView() {
const { organizationId } = useParams();
const [queryRef, loadQuery] =
useQueryLoader<NewPolicyViewQuery>(newPolicyQuery);
useEffect(() => {
if (organizationId) {
loadQuery({ organizationId });
}
}, [organizationId, loadQuery]);
if (!queryRef) {
return <NewPolicyViewSkeleton />;
}
return (
<Suspense fallback={<NewPolicyViewSkeleton />}>
{<CreatePolicyForm queryRef={queryRef} />}
</Suspense>
);
}

View File

@@ -15,6 +15,7 @@ import {
MoreHorizontal,
Trash2,
Eye,
X,
} from "lucide-react";
import { Badge } from "@/components/ui/badge";
import {
@@ -24,24 +25,59 @@ import {
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
import { useToast } from "@/hooks/use-toast";
import type { PolicyListViewQuery as PolicyListViewQueryType } from "./__generated__/PolicyListViewQuery.graphql";
import { format } from "date-fns";
import type { PolicyListViewQuery, PolicyListViewQuery$data } from "./__generated__/PolicyListViewQuery.graphql";
import type { PolicyListViewDeleteMutation } from "./__generated__/PolicyListViewDeleteMutation.graphql";
import type { PolicyListViewCreateMutation } from "./__generated__/PolicyListViewCreateMutation.graphql";
import { PageTemplate } from "@/components/PageTemplate";
import { PolicyListViewSkeleton } from "./PolicyListPage";
import {
Dialog,
DialogContent,
DialogFooter,
DialogTitle,
DialogDescription,
} from "@/components/ui/dialog";
import { Avatar } from "@/components/ui/avatar";
import { cn } from "@/lib/utils";
import PeopleSelector from "@/components/PeopleSelector";
import type { PeopleSelector_organization$key } from "@/components/__generated__/PeopleSelector_organization.graphql";
import { Input } from "@/components/ui/input";
import { Textarea } from "@/components/ui/textarea";
const PolicyListViewQuery = graphql`
const policyListViewQuery = graphql`
query PolicyListViewQuery($organizationId: ID!) {
viewer {
user {
id
}
}
organization: node(id: $organizationId) {
... on Organization {
policies(first: 100) @connection(key: "PolicyListView_policies") {
...PeopleSelector_organization
policies(first: 50, orderBy: {field: TITLE, direction: ASC}) @connection(key: "PolicyListView_policies") {
edges {
node {
id
name
content
title
description
currentPublishedVersion
createdAt
updatedAt
status
owner {
id
fullName
}
versions(first: 1) {
edges {
node {
id
status
updatedAt
}
}
}
}
}
}
@@ -61,29 +97,47 @@ const DeletePolicyMutation = graphql`
}
`;
const createPolicyMutation = graphql`
mutation PolicyListViewCreateMutation(
$input: CreatePolicyInput!
$connections: [ID!]!
) {
createPolicy(input: $input) {
policyEdge @prependEdge(connections: $connections) {
node {
id
title
description
createdAt
updatedAt
owner {
id
fullName
}
}
}
}
}
`;
function PolicyTableRow({
policy,
organizationId,
}: {
policy: {
id: string;
name: string;
content?: string;
status?: string;
updatedAt: string;
};
policy: NonNullable<PolicyListViewQuery$data["organization"]["policies"]>["edges"][0]["node"];
organizationId: string;
}) {
const navigate = useNavigate();
const { toast } = useToast();
const [isDeleting, setIsDeleting] = useState(false);
const [commitDeleteMutation] = useMutation<PolicyListViewDeleteMutation>(DeletePolicyMutation);
const [deletePolicy, isDeleting] = useMutation<PolicyListViewDeleteMutation>(DeletePolicyMutation);
const handleDelete = (e: React.MouseEvent) => {
const latestVersion = policy.versions?.edges[0]?.node;
const status = latestVersion?.status || "DRAFT";
const handleDeletePolicy = (e: React.MouseEvent) => {
e.stopPropagation();
if (window.confirm("Are you sure you want to delete this policy? This action cannot be undone.")) {
setIsDeleting(true);
commitDeleteMutation({
deletePolicy({
variables: {
input: {
policyId: policy.id,
@@ -96,7 +150,6 @@ function PolicyTableRow({
],
},
onCompleted: (_, errors) => {
setIsDeleting(false);
if (errors) {
console.error("Error deleting policy:", errors);
toast({
@@ -112,7 +165,6 @@ function PolicyTableRow({
});
},
onError: (error) => {
setIsDeleting(false);
console.error("Error deleting policy:", error);
toast({
title: "Error",
@@ -131,27 +183,33 @@ function PolicyTableRow({
return (
<tr
className="border-t border-[#ECEFEC] hover:bg-[rgba(5,77,5,0.01)] cursor-pointer"
className="border-t border-solid-b hover:bg-subtle-bg cursor-pointer"
onClick={() => {
navigate(`/organizations/${organizationId}/policies/${policy.id}`);
}}
>
<td className="py-4 px-6">
<div className="flex flex-col">
<span className="font-medium text-[#141E12]">{policy.name}</span>
<span className="text-sm text-[#818780]">
Description
<span className="font-medium text-primary">{policy.title}</span>
<span className="text-sm text-tertiary">
{policy.description || "No description provided"}
</span>
</div>
</td>
<td className="py-4 px-6">
<span className="text-sm text-[#141E12]">Mon, 8 Mar. 2025</span>
<span className="text-sm text-primary">
{format(new Date(policy.updatedAt), "MMM d, yyyy")}
</span>
</td>
<td className="py-4 px-6">
<Badge
className="bg-[rgba(5,77,5,0.03)] text-[#6B716A] font-medium border-0 py-0 px-[6px] h-5 text-xs rounded-md"
className={`font-medium border-0 py-0 px-[6px] h-5 text-xs rounded-md ${
status === "PUBLISHED"
? "bg-success-bg text-success"
: "bg-secondary-bg text-tertiary"
}`}
>
Draft
{status === "PUBLISHED" ? "Published" : "Draft"}
</Badge>
</td>
<td className="py-4 px-6 text-right">
@@ -172,8 +230,8 @@ function PolicyTableRow({
View
</DropdownMenuItem>
<DropdownMenuItem
onClick={handleDelete}
className="text-red-600 focus:text-red-600 focus:bg-red-50"
onClick={handleDeletePolicy}
className="text-danger focus:text-danger focus:bg-danger-bg"
disabled={isDeleting}
>
<Trash2 className="mr-2 h-4 w-4" />
@@ -186,60 +244,264 @@ function PolicyTableRow({
);
}
function CreatePolicyModal({
open,
onOpenChange,
organizationId,
organizationRef,
}: {
open: boolean;
onOpenChange: (open: boolean) => void;
organizationId: string;
organizationRef: PeopleSelector_organization$key;
}) {
const { toast } = useToast();
const [title, setTitle] = useState("");
const [content, setContent] = useState("");
const [ownerId, setOwnerId] = useState<string | null>(null);
const [createPolicy, isCreating] = useMutation<PolicyListViewCreateMutation>(createPolicyMutation);
// Reset form fields
const resetForm = () => {
setTitle("");
setContent("");
setOwnerId(null);
};
const handleCreatePolicy = () => {
if (!title.trim()) {
toast({
title: "Error",
description: "Please enter a policy title.",
variant: "destructive",
});
return;
}
if (!ownerId) {
toast({
title: "Error",
description: "Please select an owner for the policy.",
variant: "destructive",
});
return;
}
const input = {
organizationId,
title,
content,
ownerId,
};
createPolicy({
variables: {
input,
connections: [
ConnectionHandler.getConnectionID(
organizationId,
"PolicyListView_policies",
{orderBy: {field: "TITLE", direction: "ASC"}}
),
],
},
onCompleted: (response, errors) => {
if (errors) {
console.error("Error creating policy:", errors);
toast({
title: "Error",
description: "Failed to create policy. Please try again.",
variant: "destructive",
});
return;
}
toast({
title: "Success",
description: "Policy created successfully!",
});
resetForm();
onOpenChange(false);
},
onError: (error) => {
console.error("Error creating policy:", error);
toast({
title: "Error",
description: "Failed to create policy. Please try again.",
variant: "destructive",
});
},
});
};
// Reset form when modal closes
useEffect(() => {
if (!open) {
resetForm();
}
}, [open]);
return (
<Dialog open={open} onOpenChange={onOpenChange}>
<DialogContent className="sm:max-w-[1080px] h-[700px] max-h-[700px] p-0 gap-0 flex flex-col">
<DialogTitle className="sr-only">Create new policy</DialogTitle>
<DialogDescription className="sr-only">Form to create a new policy with title, content, and owner information.</DialogDescription>
<div className="flex justify-between items-center py-2 px-4 border-b border-solid-b h-[40px]">
<div className="flex items-center gap-1 text-sm">
<span className="text-tertiary">Policies</span>
<ChevronDown className="h-3 w-3 text-quaternary rotate-[270deg]" />
<span className="font-medium">New policy</span>
</div>
</div>
<div className="flex flex-1 overflow-hidden">
<div className="flex-1 p-6 overflow-y-auto">
<div className="mb-6">
<h1
className={`text-4xl leading-tight font-bold outline-none focus:outline-none ${!title ? 'text-gray-400' : 'text-black'}`}
contentEditable
suppressContentEditableWarning
onBlur={(e) => setTitle(e.currentTarget.textContent || "")}
style={{ WebkitTapHighlightColor: 'transparent' }}
onClick={(e) => {
if (!title) {
e.currentTarget.textContent = '';
}
}}
onFocus={(e) => {
if (!title) {
e.currentTarget.textContent = '';
}
}}
>
{title || "Enter policy title..."}
</h1>
</div>
<Textarea
placeholder="This Privacy Policy outlines how NovaSoft collects, uses, and protects personal information provided by users of its services. By accessing or using our platform, you agree to the collection and use of information in accordance with this policy..."
className="min-h-[300px] border-none resize-none p-0 focus-visible:ring-0 focus-visible:ring-offset-0"
value={content}
onChange={(e) => setContent(e.target.value)}
/>
</div>
<div className="w-[420px] bg-[rgba(5,77,5,0.03)] p-6 flex flex-col gap-4">
<h3 className="font-medium text-base">Properties</h3>
<div className="flex flex-col gap-4">
<div className="flex justify-between items-center py-2 border-t border-[rgba(2,42,2,0.08)]">
<span className="text-sm font-medium text-tertiary">Status</span>
<div className="bg-[rgba(0,39,0,0.05)] py-1.5 px-2 rounded-lg">
<span className="text-sm font-medium">Draft</span>
</div>
</div>
<div className="flex justify-between items-center py-2 border-t border-[rgba(2,42,2,0.08)]">
<span className="text-sm font-medium text-tertiary">Owner</span>
<div className="relative">
<PeopleSelector
organizationRef={organizationRef}
selectedPersonId={ownerId}
onSelect={setOwnerId}
placeholder="Select owner"
/>
</div>
</div>
<div className="flex justify-between items-center py-2 border-t border-[rgba(2,42,2,0.08)]">
<span className="text-sm font-medium text-tertiary">Review date</span>
<Button size="icon" variant="outline" className="h-8 w-8 rounded-full bg-[rgba(0,39,0,0.05)] border-none">
<Plus className="h-4 w-4" />
</Button>
</div>
</div>
</div>
</div>
<DialogFooter className="flex justify-end items-center gap-3 px-4 py-2 border-t border-solid-b h-[60px]">
<Button
variant="outline"
onClick={() => onOpenChange(false)}
className="h-9"
>
Cancel
</Button>
<Button
onClick={handleCreatePolicy}
disabled={isCreating}
className="h-9"
>
{isCreating ? "Creating..." : "Create policy"}
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
);
}
function PolicyListViewContent({
queryRef,
}: {
queryRef: PreloadedQuery<PolicyListViewQueryType>;
queryRef: PreloadedQuery<PolicyListViewQuery>;
}) {
const data = usePreloadedQuery<PolicyListViewQueryType>(
PolicyListViewQuery,
const data = usePreloadedQuery<PolicyListViewQuery>(
policyListViewQuery,
queryRef
);
const { organizationId } = useParams();
const policies =
data.organization.policies?.edges.map((edge) => edge?.node) ?? [];
data.organization?.policies?.edges.map((edge) => edge?.node) ?? [];
const [createModalOpen, setCreateModalOpen] = useState(false);
const handleOpenModal = () => {
setCreateModalOpen(true);
};
const handleCloseModal = (open: boolean) => {
setCreateModalOpen(open);
};
return (
<PageTemplate
title="Policies"
actions={
<Button asChild>
<Link to={`/organizations/${organizationId}/policies/new`}>
<Plus className="mr-2 h-4 w-4" />
Create policy
</Link>
<Button onClick={handleOpenModal}>
<Plus className="mr-2 h-4 w-4" />
New policy
</Button>
}
>
{/* Policy table */}
<div className="rounded-lg border border-[#ECEFEC] overflow-hidden bg-white">
<div className="rounded-lg border border-solid-b overflow-hidden bg-level-1">
<table className="w-full">
<thead>
<tr className="bg-white text-left">
<th className="py-3 px-6 text-xs font-medium text-[#818780] border-b border-[rgba(2,42,2,0.08)]">
<tr className="bg-level-1 text-left">
<th className="py-3 px-6 text-xs font-medium text-tertiary border-b border-low-b">
<div className="flex items-center gap-1">
Vendor
<ChevronDown className="h-3 w-3 text-[#C3C8C2]" />
Policy
<ChevronDown className="h-3 w-3 text-quaternary" />
</div>
</th>
<th className="py-3 px-6 text-xs font-medium text-[#818780] border-b border-[rgba(2,42,2,0.08)]">
<th className="py-3 px-6 text-xs font-medium text-tertiary border-b border-low-b">
<div className="flex items-center gap-1">
Last update
<ChevronDown className="h-3 w-3 text-[#C3C8C2]" />
<ChevronDown className="h-3 w-3 text-quaternary" />
</div>
</th>
<th className="py-3 px-6 text-xs font-medium text-[#818780] border-b border-[rgba(2,42,2,0.08)]">
<th className="py-3 px-6 text-xs font-medium text-tertiary border-b border-low-b">
<div className="flex items-center gap-1">
Status
<ChevronDown className="h-3 w-3 text-[#C3C8C2]" />
<ChevronDown className="h-3 w-3 text-quaternary" />
</div>
</th>
<th className="py-3 px-6 text-right text-xs font-medium text-[#818780] border-b border-[rgba(2,42,2,0.08)]"></th>
<th className="py-3 px-6 text-right text-xs font-medium text-tertiary border-b border-low-b"></th>
</tr>
</thead>
<tbody className="bg-white">
<tbody className="bg-level-1">
{policies.length > 0 ? (
policies.map((policy) => (
policies.map((policy: any) => (
<PolicyTableRow
key={policy.id}
policy={policy}
@@ -251,14 +513,12 @@ function PolicyListViewContent({
<td colSpan={4} className="py-12 text-center">
<div className="flex flex-col items-center gap-2">
<h3 className="text-lg font-medium">No policies found</h3>
<p className="text-[#818780]">
<p className="text-tertiary">
Create your first policy to get started
</p>
<Button asChild>
<Link to={`/organizations/${organizationId}/policies/new`}>
<Plus className="mr-2 h-4 w-4" />
Create policy
</Link>
<Button onClick={handleOpenModal}>
<Plus className="mr-2 h-4 w-4" />
Create policy
</Button>
</div>
</td>
@@ -267,13 +527,20 @@ function PolicyListViewContent({
</tbody>
</table>
</div>
<CreatePolicyModal
open={createModalOpen}
onOpenChange={handleCloseModal}
organizationId={organizationId!}
organizationRef={data.organization}
/>
</PageTemplate>
);
}
export default function PolicyListView() {
const [queryRef, loadQuery] =
useQueryLoader<PolicyListViewQueryType>(PolicyListViewQuery);
useQueryLoader<PolicyListViewQuery>(policyListViewQuery);
const { organizationId } = useParams();

View File

@@ -1,423 +0,0 @@
import { Suspense, useEffect, useState, useCallback } from "react";
import { useParams, Link, useNavigate } from "react-router";
import {
graphql,
PreloadedQuery,
usePreloadedQuery,
useQueryLoader,
useMutation,
ConnectionHandler,
} from "react-relay";
import { Edit, Download, Shield, User, FileText, Calendar } from "lucide-react";
import { Card, CardContent } from "@/components/ui/card";
import { Button } from "@/components/ui/button";
import { Badge } from "@/components/ui/badge";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
import type { PolicyViewQuery as PolicyViewQueryType } from "./__generated__/PolicyViewQuery.graphql";
import type { PolicyViewDeleteMutation } from "./__generated__/PolicyViewDeleteMutation.graphql";
import { useToast } from "@/hooks/use-toast";
import { PageTemplate } from "@/components/PageTemplate";
import { PolicyViewSkeleton } from "./PolicyPage";
const PolicyViewQuery = graphql`
query PolicyViewQuery($policyId: ID!) {
node(id: $policyId) {
id
... on Policy {
name
content
createdAt
updatedAt
reviewDate
status
owner {
id
fullName
primaryEmailAddress
}
}
}
}
`;
const DeletePolicyMutation = graphql`
mutation PolicyViewDeleteMutation(
$input: DeletePolicyInput!
$connections: [ID!]!
) {
deletePolicy(input: $input) {
deletedPolicyId @deleteEdge(connections: $connections)
}
}
`;
function PolicyViewContent({
queryRef,
}: {
queryRef: PreloadedQuery<PolicyViewQueryType>;
}) {
const data = usePreloadedQuery(PolicyViewQuery, queryRef);
const policy = data.node;
const { organizationId } = useParams();
const navigate = useNavigate();
const [activeTab, setActiveTab] = useState("content");
const [isDeleting, setIsDeleting] = useState(false);
const { toast } = useToast();
const [commitDeleteMutation] =
useMutation<PolicyViewDeleteMutation>(DeletePolicyMutation);
const handleDeletePolicy = useCallback(() => {
if (
window.confirm(
"Are you sure you want to delete this policy? This action cannot be undone."
)
) {
setIsDeleting(true);
commitDeleteMutation({
variables: {
input: {
policyId: policy.id,
},
connections: [
ConnectionHandler.getConnectionID(
organizationId!,
"PolicyListPage_policies"
),
],
},
onCompleted: (_, errors) => {
setIsDeleting(false);
if (errors) {
console.error("Error deleting policy:", errors);
toast({
title: "Error",
description: "Failed to delete policy. Please try again.",
variant: "destructive",
});
return;
}
toast({
title: "Success",
description: "Policy deleted successfully.",
});
navigate(`/organizations/${organizationId}/policies`);
},
onError: (error) => {
setIsDeleting(false);
console.error("Error deleting policy:", error);
toast({
title: "Error",
description: "Failed to delete policy. Please try again.",
variant: "destructive",
});
},
});
}
}, [policy.id, organizationId, commitDeleteMutation, navigate]);
// Extract a short description from the content
const getDescription = (content: string | undefined) => {
if (!content) return "No description available";
// Remove HTML tags
const withoutTags = content.replace(/<[^>]*>/g, "");
// Decode HTML entities
const decoded = withoutTags
.replace(/&amp;/g, "&")
.replace(/&lt;/g, "<")
.replace(/&gt;/g, ">")
.replace(/&quot;/g, '"')
.replace(/&#039;/g, "'")
.replace(/&nbsp;/g, " ");
// Get first paragraph or first 150 characters
const firstParagraph = decoded.split("\n\n")[0].trim();
return firstParagraph.length > 150
? firstParagraph.substring(0, 150) + "..."
: firstParagraph;
};
const formatDate = (dateString: string | undefined) => {
if (!dateString) return "N/A";
const date = new Date(dateString);
return date.toISOString().split("T")[0]; // YYYY-MM-DD format
};
return (
<PageTemplate
title={policy.name ?? ""}
description={
<div className="flex items-center gap-2 text-tertiary">
<Badge variant={policy.status === "ACTIVE" ? "default" : "outline"}>
{policy.status === "ACTIVE" ? "Active" : "Draft"}
</Badge>
{policy.owner && (
<div className="flex items-center gap-1">
<User className="h-3 w-3" />
<Link
to={`/organizations/${organizationId}/people/${policy.owner.id}`}
className="hover:underline"
>
{policy.owner.fullName}
</Link>
</div>
)}
{policy.reviewDate && (
<div className="flex items-center gap-1">
<Calendar className="h-3 w-3" />
<span>Review: {formatDate(policy.reviewDate)}</span>
</div>
)}
</div>
}
actions={
<div className="flex gap-2">
<Button variant="secondary" asChild>
<Link
to={`/organizations/${organizationId}/policies/${policy.id}/edit`}
>
<Edit className="h-4 w-4 mr-2" />
Edit
</Link>
</Button>
<Button
onClick={() => {
// Logic to download policy content as PDF or text
const element = document.createElement("a");
const file = new Blob([policy.content || ""], {
type: "text/plain",
});
element.href = URL.createObjectURL(file);
element.download = `${policy.name}.txt`;
document.body.appendChild(element);
element.click();
document.body.removeChild(element);
}}
>
<Download className="h-4 w-4 mr-2" />
Download
</Button>
</div>
}
>
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
<div className="lg:col-span-2">
<Card className="border shadow-sm">
<CardContent className="p-6">
<div className="flex items-center gap-3 mb-4">
<div className="p-2 rounded-md bg-slate-100">
<Shield className="h-6 w-6" />
</div>
<div className="flex gap-2">
{policy.status && (
<Badge
className={`px-3 py-1 rounded-md font-medium ${
policy.status === "ACTIVE"
? "bg-success-bg text-success hover:bg-h-success-bg"
: policy.status === "DRAFT"
? "bg-warning-bg text-warning hover:bg-h-warning-bg"
: "bg-secondary-bg text-secondary hover:bg-h-secondary-bg"
}`}
>
{policy.status === "ACTIVE"
? "Active"
: policy.status === "DRAFT"
? "Draft"
: policy.status}
</Badge>
)}
</div>
</div>
<h1 className="text-3xl font-bold mb-3">{policy.name}</h1>
<p className="text-tertiary mb-6">
{getDescription(policy.content)}
</p>
<Tabs
defaultValue="content"
value={activeTab}
onValueChange={setActiveTab}
className="mb-6"
>
<TabsList className="border-b w-full rounded-none p-0 h-auto">
<TabsTrigger
value="content"
className={`px-4 py-2 font-medium ${
activeTab === "content"
? "border-primary text-primary"
: "text-tertiary"
}`}
>
Policy Content
</TabsTrigger>
<TabsTrigger
value="history"
className={`px-4 py-2 font-medium ${
activeTab === "history"
? "border-primary text-primary"
: "text-tertiary"
}`}
>
Version History
</TabsTrigger>
<TabsTrigger
value="approvals"
className={`px-4 py-2 font-medium ${
activeTab === "approvals"
? "border-primary text-primary"
: "text-tertiary"
}`}
>
Approvals
</TabsTrigger>
</TabsList>
<TabsContent value="content" className="pt-6">
<div className="prose prose-sm md:prose-base lg:prose-lg max-w-none">
<div
className="policy-content"
dangerouslySetInnerHTML={{
__html: policy.content || "",
}}
/>
</div>
</TabsContent>
<TabsContent value="history" className="pt-6">
<div className="text-center py-12">
<p className="text-tertiary">
Version history will be available soon.
</p>
</div>
</TabsContent>
<TabsContent value="approvals" className="pt-6">
<div className="text-center py-12">
<p className="text-tertiary">
Approval workflow will be available soon.
</p>
</div>
</TabsContent>
</Tabs>
</CardContent>
</Card>
</div>
<div className="lg:col-span-1">
<Card className="border shadow-sm mb-6">
<CardContent className="p-6">
<h2 className="text-xl font-semibold mb-6">Policy Details</h2>
<div className="space-y-6">
<div>
<div className="flex items-center gap-2 text-tertiary mb-1">
<FileText className="h-4 w-4" />
<span className="text-sm">Last Updated</span>
</div>
<p className="font-medium">{formatDate(policy.updatedAt)}</p>
</div>
<div>
<div className="flex items-center gap-2 text-tertiary mb-1">
<FileText className="h-4 w-4" />
<span className="text-sm">Review Due</span>
</div>
<p className="font-medium">
{policy.reviewDate
? formatDate(policy.reviewDate)
: "Not set"}
</p>
</div>
<div>
<div className="flex items-center gap-2 text-tertiary mb-1">
<User className="h-4 w-4" />
<span className="text-sm">Owner</span>
</div>
<Link
to={`/organizations/${organizationId}/people/${policy.owner?.id}`}
className="hover:underline"
>
<p className="font-medium">
{policy.owner ? policy.owner.fullName : "Not assigned"}
</p>
</Link>
</div>
</div>
</CardContent>
</Card>
<Button asChild className="w-full">
<Link
to={`/organizations/${organizationId}/policies/${policy.id}/edit`}
>
<Edit className="mr-2 h-4 w-4" />
Edit Policy
</Link>
</Button>
<Card className="border shadow-sm mt-6 bg-danger-bg">
<CardContent className="p-6">
<h3 className="text-danger font-semibold mb-3">Danger Zone</h3>
<p className="text-sm text-tertiary mb-4">
Permanently delete this policy and all of its data. This action
cannot be undone.
</p>
<Button
variant="destructive"
className="w-full"
onClick={handleDeletePolicy}
disabled={isDeleting}
>
<svg
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
className="mr-2"
>
<path d="M3 6h18"></path>
<path d="M19 6v14c0 1-1 2-2 2H7c-1 0-2-1-2-2V6"></path>
<path d="M8 6V4c0-1 1-2 2-2h4c1 0 2 1 2 2v2"></path>
</svg>
{isDeleting ? "Deleting..." : "Delete Policy"}
</Button>
</CardContent>
</Card>
</div>
</div>
</PageTemplate>
);
}
export default function PolicyView() {
const [queryRef, loadQuery] =
useQueryLoader<PolicyViewQueryType>(PolicyViewQuery);
const { policyId } = useParams();
useEffect(() => {
loadQuery({ policyId: policyId! });
}, [loadQuery, policyId]);
if (!queryRef) {
return <PolicyViewSkeleton />;
}
return (
<Suspense fallback={<PolicyViewSkeleton />}>
{queryRef && <PolicyViewContent queryRef={queryRef} />}
</Suspense>
);
}

View File

@@ -5,9 +5,9 @@ import { useLocation } from "react-router";
import { ErrorBoundaryWithLocation } from "../ErrorBoundary";
import { lazy } from "@probo/react-lazy";
const PolicyView = lazy(() => import("./PolicyView"));
const ShowPolicyView = lazy(() => import("./ShowPolicyView"));
export function PolicyViewSkeleton() {
export function ShowPolicyViewSkeleton() {
return (
<PageTemplateSkeleton
withDescription
@@ -108,13 +108,13 @@ export function PolicyViewSkeleton() {
);
}
export function PolicyPage() {
export function ShowPolicyPage() {
const location = useLocation();
return (
<Suspense key={location.pathname} fallback={<PolicyViewSkeleton />}>
<Suspense key={location.pathname} fallback={<ShowPolicyViewSkeleton />}>
<ErrorBoundaryWithLocation>
<PolicyView />
<ShowPolicyView />
</ErrorBoundaryWithLocation>
</Suspense>
);

View File

@@ -0,0 +1,426 @@
import { Suspense, useEffect, useState, useCallback, ReactNode } from "react";
import { useParams, Link, useNavigate } from "react-router";
import {
graphql,
PreloadedQuery,
usePreloadedQuery,
useQueryLoader,
useMutation,
} from "react-relay";
import { Clock, Download, Edit, Trash2, MoreHorizontal, X, FileSignature } from "lucide-react";
import { Button } from "@/components/ui/button";
import { useToast } from "@/hooks/use-toast";
import { PageTemplate } from "@/components/PageTemplate";
import type { ShowPolicyViewQuery } from "./__generated__/ShowPolicyViewQuery.graphql";
import { ShowPolicyViewPublishMutation } from "./__generated__/ShowPolicyViewPublishMutation.graphql";
import { ShowPolicyViewCreateDraftMutation } from "./__generated__/ShowPolicyViewCreateDraftMutation.graphql";
import ReactMarkdown from "react-markdown";
import { ShowPolicyViewSkeleton } from "./ShowPolicyPage";
import { format } from "date-fns";
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
import {
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
} from "@/components/ui/dialog";
import { SignaturesModal } from "./SignaturesModal";
import { VersionHistoryModal } from "./VersionHistoryModal";
const policyViewQuery = graphql`
query ShowPolicyViewQuery($policyId: ID!) {
node(id: $policyId) {
id
... on Policy {
title
description
createdAt
updatedAt
currentPublishedVersion
owner {
id
fullName
primaryEmailAddress
}
...SignaturesModal_policyVersions
...VersionHistoryModal_policyVersions
latestVersion: versions(first: 1) {
edges {
node {
id
version
status
content
changelog
publishedAt
publishedBy {
fullName
}
createdAt
updatedAt
}
}
}
}
}
}
`;
const publishPolicyVersionMutation = graphql`
mutation ShowPolicyViewPublishMutation($input: PublishPolicyVersionInput!) {
publishPolicyVersion(input: $input) {
policy {
id
currentPublishedVersion
}
policyVersion {
id
status
publishedAt
publishedBy {
fullName
}
}
}
}
`;
const createDraftPolicyVersionMutation = graphql`
mutation ShowPolicyViewCreateDraftMutation($input: CreateDraftPolicyVersionInput!) {
createDraftPolicyVersion(input: $input) {
policyVersionEdge {
node {
id
version
status
}
}
}
}
`;
function ShowPolicyContent({
queryRef,
}: {
queryRef: PreloadedQuery<ShowPolicyViewQuery>;
}) {
const data = usePreloadedQuery<ShowPolicyViewQuery>(policyViewQuery, queryRef);
const policy = data.node;
const { organizationId } = useParams();
const navigate = useNavigate();
const { toast } = useToast();
const [queryRef2, loadQuery] = useQueryLoader<ShowPolicyViewQuery>(policyViewQuery);
const [isDeleteDialogOpen, setIsDeleteDialogOpen] = useState(false);
const [isDeleting, setIsDeleting] = useState(false);
const [isVersionHistoryOpen, setIsVersionHistoryOpen] = useState(false);
const [isSignaturesModalOpen, setIsSignaturesModalOpen] = useState(false);
const [publishDraft, isPublishInFlight] = useMutation<ShowPolicyViewPublishMutation>(publishPolicyVersionMutation);
const [createDraft, isCreateDraftInFlight] = useMutation<ShowPolicyViewCreateDraftMutation>(createDraftPolicyVersionMutation);
const latestVersionEdge = policy.latestVersion?.edges[0];
const latestVersionNode = latestVersionEdge?.node;
const isDraft = latestVersionNode?.status === "DRAFT";
useEffect(() => {
// No need to update selectedVersion state since we're using the VersionHistoryModal component
}, []);
// Handle delete policy
const handleDeletePolicy = useCallback(() => {
setIsDeleteDialogOpen(true);
}, []);
// Confirm delete policy
const confirmDeletePolicy = useCallback(() => {
setIsDeleting(true);
setTimeout(() => {
toast({
title: "Policy deleted",
description: "The policy has been deleted successfully",
});
setIsDeleting(false);
setIsDeleteDialogOpen(false);
navigate(`/organizations/${organizationId}/policies`);
}, 1000);
}, [toast, navigate, organizationId]);
// Navigate to publish flow
const handlePublish = useCallback(() => {
if (!policy.id) return;
publishDraft({
variables: {
input: {
policyId: policy.id
}
},
onCompleted: (_, errors) => {
if (errors) {
toast({
title: "Error publishing policy",
description: errors[0]?.message || "An unknown error occurred",
variant: "destructive"
});
return;
}
toast({
title: "Policy published",
description: `The policy has been published successfully`,
});
// Reload the query to refresh the data
loadQuery({ policyId: policy.id });
},
onError: (error) => {
toast({
title: "Error publishing policy",
description: error.message || "An unknown error occurred",
variant: "destructive"
});
}
});
}, [policy.id, publishDraft, toast, loadQuery]);
// Open version history modal
const handleVersionHistoryClick = useCallback(() => {
setIsVersionHistoryOpen(true);
}, []);
// Restore version
const handleRestoreVersion = useCallback((versionNumber: number) => {
// Here you would implement the logic to restore a version
toast({
title: "Version restored",
description: `Version ${versionNumber} has been restored`,
});
setIsVersionHistoryOpen(false);
// Reload the query to refresh the data
if (policy.id) {
loadQuery({ policyId: policy.id });
}
}, [policy.id, loadQuery, toast]);
// Handle edit policy
const handleEditPolicy = useCallback(() => {
if (!policy.id) return;
if (latestVersionNode?.status === "PUBLISHED") {
// Create a new draft version first
createDraft({
variables: {
input: {
policyID: policy.id
}
},
onCompleted: (response, errors) => {
if (errors) {
toast({
title: "Error creating draft",
description: errors[0]?.message || "An unknown error occurred",
variant: "destructive"
});
return;
}
const newDraftId = response.createDraftPolicyVersion.policyVersionEdge.node.id;
navigate(`/organizations/${organizationId}/policies/${policy.id}/versions/${newDraftId}/edit`);
},
onError: (error) => {
toast({
title: "Error creating draft",
description: error.message || "An unknown error occurred",
variant: "destructive"
});
}
});
} else {
// Navigate directly to edit if it's already a draft
navigate(`/organizations/${organizationId}/policies/${policy.id}/versions/${latestVersionNode?.id}/edit`);
}
}, [policy.id, latestVersionNode, createDraft, navigate, organizationId, toast]);
// Format date helper
const formatDate = (dateString?: string) => {
if (!dateString) return "N/A";
const date = new Date(dateString);
return format(date, "MMM d, yyyy");
};
return (
<PageTemplate
title={policy.title!}
actions={
<div className="flex items-center gap-2">
<Button
variant="outline"
size="sm"
className="rounded-full h-9 px-3 gap-1.5 shadow-sm border-[#022A0214] bg-white text-[#141E12]"
onClick={handleVersionHistoryClick}
>
<Clock className="h-4 w-4" />
<span className="font-medium">Version history</span>
</Button>
<Button
variant="outline"
size="sm"
className="rounded-full h-9 px-3 gap-1.5 shadow-sm border-[#022A0214] bg-white text-[#141E12]"
onClick={() => setIsSignaturesModalOpen(true)}
>
<FileSignature className="h-4 w-4" />
<span className="font-medium">Signature history</span>
</Button>
{isDraft && (
<Button
variant="default"
size="sm"
className="rounded-full h-9 px-3 gap-1.5 shadow-sm"
onClick={handlePublish}
disabled={isPublishInFlight}
>
<span className="font-medium">Publish version</span>
</Button>
)}
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button
variant="outline"
size="sm"
className="rounded-full h-9 w-9 shadow-sm border-[#022A0214] bg-white text-[#141E12]"
>
<MoreHorizontal className="h-4 w-4" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuItem asChild>
<div onClick={handleEditPolicy}>
<Edit className="mr-2 h-4 w-4" />
{latestVersionNode?.status === "PUBLISHED" ? "Create new draft" : "Edit draft policy"}
</div>
</DropdownMenuItem>
<DropdownMenuItem
onClick={handleDeletePolicy}
className="text-danger focus:text-danger focus:bg-danger-bg"
>
<Trash2 className="mr-2 h-4 w-4" />
Delete policy
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</div>
}
>
<div className="space-y-4">
{latestVersionNode ? (
<div className="bg-white rounded-lg border border-solid-b shadow-sm p-6">
{latestVersionNode.changelog && (
<div className="mb-6 p-4 bg-level-1 rounded-md border border-solid-b">
<div className="text-xs text-tertiary uppercase font-medium mb-1">Change summary</div>
<div className="text-sm text-secondary">
{latestVersionNode.changelog}
</div>
</div>
)}
<div className="prose prose-olive max-w-none">
<ReactMarkdown>
{latestVersionNode.content || "No content available"}
</ReactMarkdown>
</div>
<div className="mt-8 pt-4 border-t border-solid-b text-xs text-tertiary flex justify-between items-center">
<div>
{latestVersionNode.status === "PUBLISHED"
? `Published on ${formatDate(latestVersionNode.publishedAt || "")}${latestVersionNode.publishedBy ? ` by ${latestVersionNode.publishedBy.fullName}` : ''}`
: `Last modified on ${formatDate(latestVersionNode.updatedAt)} by ${policy.owner?.fullName || 'Unknown'}`}
</div>
</div>
</div>
) : (
<div className="bg-white rounded-lg border border-solid-b shadow-sm p-6 text-center text-tertiary">
No content available
</div>
)}
</div>
{/* Signatures Modal */}
<SignaturesModal
isOpen={isSignaturesModalOpen}
onClose={() => setIsSignaturesModalOpen(false)}
policyRef={policy}
owner={policy.owner}
/>
{/* Version History Modal */}
<VersionHistoryModal
isOpen={isVersionHistoryOpen}
onClose={() => setIsVersionHistoryOpen(false)}
policyRef={policy}
onRestoreVersion={handleRestoreVersion}
/>
{/* Delete Confirmation Dialog */}
<Dialog open={isDeleteDialogOpen} onOpenChange={setIsDeleteDialogOpen}>
<DialogContent>
<DialogHeader>
<DialogTitle>Delete Policy</DialogTitle>
<DialogDescription>
Are you sure you want to delete the policy "{policy.title}"? This action cannot be undone.
</DialogDescription>
</DialogHeader>
<DialogFooter>
<Button
variant="outline"
onClick={() => setIsDeleteDialogOpen(false)}
disabled={isDeleting}
>
Cancel
</Button>
<Button
variant="destructive"
onClick={confirmDeletePolicy}
disabled={isDeleting}
>
{isDeleting ? "Deleting..." : "Delete"}
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
</PageTemplate>
);
}
export default function ShowPolicyView() {
const [queryRef, loadQuery] = useQueryLoader<ShowPolicyViewQuery>(policyViewQuery);
const { policyId } = useParams();
useEffect(() => {
loadQuery({ policyId: policyId! });
}, [loadQuery, policyId]);
if (!queryRef) {
return <ShowPolicyViewSkeleton />;
}
return (
<Suspense fallback={<ShowPolicyViewSkeleton />}>
<ShowPolicyContent queryRef={queryRef} />
</Suspense>
);
}

View File

@@ -0,0 +1,74 @@
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
import { format } from "date-fns";
interface PolicyVersionSignature {
id: string;
state: "REQUESTED" | "SIGNED";
signedBy: {
fullName: string;
};
signedAt?: string;
requestedAt: string;
requestedBy: {
fullName: string;
};
}
interface SignaturesListProps {
signatures: PolicyVersionSignature[];
}
export function SignaturesList({ signatures }: SignaturesListProps) {
const formatDateTime = (dateString: string) => {
const date = new Date(dateString);
return format(date, "h:mm a • MMM d, yyyy");
};
return (
<div className="space-y-4">
{signatures.map((signature) => (
<div key={signature.id} className="bg-white rounded-lg border border-solid-b shadow-sm p-6">
<div className="flex items-center gap-3 mb-4">
<Avatar className="h-10 w-10">
<AvatarImage src="" alt={signature.signedBy.fullName} />
<AvatarFallback>
{signature.signedBy.fullName.charAt(0)}
</AvatarFallback>
</Avatar>
<div>
<div className="font-medium">{signature.signedBy.fullName}</div>
<div className="text-sm text-tertiary">
{signature.state === "SIGNED" && signature.signedAt
? formatDateTime(signature.signedAt)
: formatDateTime(signature.requestedAt)}
</div>
</div>
</div>
<div className="space-y-2">
<div className="text-sm">
<span className="text-muted-foreground">Status:</span>{" "}
<span className={`px-2 py-0.5 rounded-full ${
signature.state === "SIGNED"
? "bg-green-100 text-green-800"
: "bg-yellow-100 text-yellow-800"
}`}>
{signature.state}
</span>
</div>
<div className="text-sm">
<span className="text-muted-foreground">Requested by:</span>{" "}
{signature.requestedBy.fullName}
</div>
{signature.state === "SIGNED" && signature.signedAt && (
<div className="text-sm">
<span className="text-muted-foreground">Signed at:</span>{" "}
{formatDateTime(signature.signedAt)}
</div>
)}
</div>
</div>
))}
</div>
);
}

View File

@@ -0,0 +1,469 @@
import { Dialog, DialogContent } from "@/components/ui/dialog";
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
import { format } from "date-fns";
import { Button } from "@/components/ui/button";
import { useState, useEffect } from "react";
import { graphql, useFragment, useMutation, useQueryLoader, usePreloadedQuery, ConnectionHandler } from "react-relay";
import type { SignaturesModal_policyVersions$data, SignaturesModal_policyVersions$key } from "./__generated__/SignaturesModal_policyVersions.graphql";
import type { SignaturesModalRequestSignatureMutation } from "./__generated__/SignaturesModalRequestSignatureMutation.graphql";
import type { SignaturesModalOrganizationQuery } from "./__generated__/SignaturesModalOrganizationQuery.graphql";
import { useToast } from "@/hooks/use-toast";
import { useParams } from "react-router";
import { Loader2, CheckCircle2, Clock } from "lucide-react";
import { PreloadedQuery } from "react-relay";
export const policyVersionsFragment = graphql`
fragment SignaturesModal_policyVersions on Policy {
title
policyVersions: versions(first: 10) {
edges {
node {
id
version
status
publishedAt
updatedAt
publishedBy {
fullName
}
signatures(first: 100) @connection(key: "SignaturesModal_policyVersions_signatures") {
edges {
node {
id
state
signedAt
requestedAt
signedBy {
fullName
id
}
requestedBy {
fullName
}
}
}
}
}
}
}
}
`;
const requestSignatureMutation = graphql`
mutation SignaturesModalRequestSignatureMutation($input: RequestSignatureInput!, $connections: [ID!]!) {
requestSignature(input: $input) {
policyVersionSignatureEdge @prependEdge(connections: $connections) {
node {
id
state
signedAt
requestedAt
signedBy {
fullName
id
}
requestedBy {
fullName
}
}
}
}
}
`;
const organizationQuery = graphql`
query SignaturesModalOrganizationQuery($organizationId: ID!) {
organization: node(id: $organizationId) {
... on Organization {
id
peoples(first: 100, orderBy: { direction: ASC, field: FULL_NAME }) {
edges {
node {
id
fullName
primaryEmailAddress
kind
}
}
}
}
}
}
`;
interface SignaturesModalProps {
isOpen: boolean;
onClose: () => void;
policyRef: SignaturesModal_policyVersions$key;
owner?: {
fullName: string;
} | null;
}
// Define the signature type based on the GraphQL response
// interface Signature {
// id: string;
// state: string;
// signedAt?: string | null;
// requestedAt: string;
// signedBy: {
// fullName: string;
// id: string;
// } | null;
// requestedBy: {
// fullName: string;
// } | null;
// }
export function SignaturesModal({
isOpen,
onClose,
policyRef,
owner,
}: SignaturesModalProps) {
const data = useFragment<SignaturesModal_policyVersions$key>(
policyVersionsFragment,
policyRef
);
// Safely access and extract version nodes
const versionNodes = (data?.policyVersions?.edges?.map(edge => edge.node) || []);
// Filter to just published versions and sort by version number
const publishedVersions = versionNodes
.filter(v => v.status === "PUBLISHED")
.sort((a, b) => b.version - a.version);
const [selectedVersion, setSelectedVersion] = useState<number>(
publishedVersions[0]?.version || 0
);
const selectedVersionData = versionNodes.find(v => v.version === selectedVersion);
const { organizationId } = useParams();
const [queryRef, loadQuery] = useQueryLoader<SignaturesModalOrganizationQuery>(organizationQuery);
const { toast } = useToast();
const [isRequesting, setIsRequesting] = useState<string | null>(null);
const [commitRequestSignature] = useMutation<SignaturesModalRequestSignatureMutation>(requestSignatureMutation);
// Load organization data when the modal opens and we have a valid version selected
useEffect(() => {
if (isOpen && organizationId && selectedVersionData?.status === "PUBLISHED") {
loadQuery({ organizationId });
}
}, [isOpen, organizationId, loadQuery, selectedVersionData]);
const formatDateTime = (dateString?: string | null) => {
if (!dateString) return "N/A";
const date = new Date(dateString);
return format(date, "h:mm a • MMM d, yyyy");
};
// Safely type signatures as our defined Signature interface
const signatures = selectedVersionData?.signatures?.edges?.map(edge => edge.node) || [];
const handleRequestSignature = (personId: string) => {
if (!selectedVersionData) return;
setIsRequesting(personId);
commitRequestSignature({
variables: {
input: {
policyVersionId: selectedVersionData.id,
signatoryId: personId,
},
connections: [
ConnectionHandler.getConnectionID(
selectedVersionData.id,
"SignaturesModal_policyVersions_signatures"
),
],
},
onCompleted: () => {
toast({
title: "Success",
description: "Signature request sent successfully",
});
setIsRequesting(null);
},
onError: (error) => {
toast({
title: "Error",
description: error.message,
variant: "destructive",
});
setIsRequesting(null);
},
});
};
return (
<Dialog open={isOpen} onOpenChange={onClose}>
<DialogContent className="max-w-[1080px] h-[744px] p-0 overflow-hidden flex flex-col">
<div className="flex flex-1 overflow-hidden">
{/* Version History Sidebar */}
<div className="w-[316px] border-r border-solid-b h-full overflow-y-auto">
<h2 className="text-lg font-semibold p-6 pb-4">Version history</h2>
<div className="overflow-y-auto">
{versionNodes.length > 0 ? (
versionNodes.map(version => {
const isPublished = version.status === "PUBLISHED";
const isSelected = selectedVersion === version.version;
return (
<div
key={version.id}
className={`flex items-center gap-3 p-6 py-4 ${
isPublished ? 'cursor-pointer' : 'cursor-not-allowed opacity-50'
} ${isSelected ? 'bg-slate-50' : ''}`}
onClick={() => isPublished && setSelectedVersion(version.version)}
>
<Avatar className="h-10 w-10">
<AvatarImage src="" alt={version.publishedBy?.fullName || owner?.fullName || ""} />
<AvatarFallback>
{(version.publishedBy?.fullName || owner?.fullName || "").charAt(0)}
</AvatarFallback>
</Avatar>
<div className="flex-1">
<div className="flex items-center gap-2">
<span className="font-medium">Version {version.version}</span>
{isPublished ? (
<span className="text-xs px-2 py-0.5 rounded-full bg-green-100 text-green-800">
Published
</span>
) : (
<span className="text-xs px-2 py-0.5 rounded-full bg-yellow-100 text-yellow-800">
Draft
</span>
)}
</div>
<div className="text-sm text-tertiary">
{version.publishedBy?.fullName || owner?.fullName || "Unknown"} • {formatDateTime(version.publishedAt || version.updatedAt)}
</div>
</div>
</div>
);
})
) : (
<div className="p-6 text-center text-tertiary">
No versions available
</div>
)}
</div>
</div>
{/* Main Content Area */}
<div className="flex-1 flex flex-col overflow-hidden">
{/* Section header */}
<div className="p-6 border-b border-solid-b">
<h2 className="text-2xl font-semibold mb-2">
{selectedVersionData?.status === "PUBLISHED" ? "Signatures & Requests" : "Signatures"}
</h2>
{selectedVersionData?.status === "PUBLISHED" && (
<p className="text-sm text-muted-foreground">
Click request to ask for a signature from people in your organization
</p>
)}
</div>
{/* Content area with unified people and signatures list */}
<div className="flex-1 overflow-y-auto p-6">
{selectedVersionData ? (
selectedVersionData.status === "PUBLISHED" && queryRef ? (
<PeopleAndSignaturesList
queryRef={queryRef}
onRequestSignature={handleRequestSignature}
requestingId={isRequesting}
existingSignatures={signatures}
formatDateTime={formatDateTime}
/>
) : (
<div className="space-y-4">
{signatures.length > 0 ? (
<div className="divide-y divide-border">
{signatures.map(signature => (
<div key={signature.id} className="py-4">
<div className="flex justify-between items-center">
<div className="flex items-center gap-3">
<Avatar className="h-10 w-10">
<AvatarFallback>
{signature.signedBy?.fullName?.charAt(0) || signature.requestedBy?.fullName?.charAt(0)}
</AvatarFallback>
</Avatar>
<div>
<div className="font-medium">{signature.signedBy?.fullName || signature.requestedBy?.fullName}</div>
<div className="text-sm text-tertiary">
{signature.state === "SIGNED"
? `Signed on ${formatDateTime(signature.signedAt)}`
: `Requested on ${formatDateTime(signature.requestedAt)}`}
</div>
</div>
</div>
<span className={`text-xs px-2 py-0.5 rounded-full ${
signature.state === "SIGNED"
? 'bg-green-100 text-green-800'
: 'bg-yellow-100 text-yellow-800'
}`}>
{signature.state === "SIGNED" ? "Signed" : "Pending"}
</span>
</div>
</div>
))}
</div>
) : (
<div className="text-center py-8">
<p className="text-tertiary">No signatures available for this version</p>
{selectedVersionData.status !== "PUBLISHED" && (
<p className="text-sm text-muted-foreground mt-2">Only published versions can have signatures</p>
)}
</div>
)}
</div>
)
) : (
<div className="text-center text-tertiary py-8">
No version selected
</div>
)}
{selectedVersionData?.status === "PUBLISHED" && !queryRef && (
<div className="py-8 text-center">
<Loader2 className="h-6 w-6 animate-spin mx-auto mb-2" />
<p className="text-tertiary">Loading...</p>
</div>
)}
</div>
</div>
</div>
<div className="flex justify-end p-4 border-t border-solid-b mt-auto">
<Button
variant="outline"
onClick={onClose}
className="mr-2"
>
Close
</Button>
</div>
</DialogContent>
</Dialog>
);
}
// Combined people and signatures list component
function PeopleAndSignaturesList({
queryRef,
onRequestSignature,
requestingId,
existingSignatures,
formatDateTime,
}: {
queryRef: PreloadedQuery<SignaturesModalOrganizationQuery>,
onRequestSignature: (personId: string) => void,
requestingId: string | null,
existingSignatures: Array<SignaturesModal_policyVersions$data["policyVersions"]["edges"][0]["node"]["signatures"]["edges"][0]["node"]>,
formatDateTime: (date?: string | null) => string,
}) {
const data = usePreloadedQuery<SignaturesModalOrganizationQuery>(
organizationQuery,
queryRef
);
const people = data?.organization?.peoples?.edges || [];
if (!data?.organization) {
return (
<div className="text-center py-8">
Organization not found
</div>
);
}
if (people.length === 0) {
return (
<div className="text-center py-8">
No people available to request signatures from
</div>
);
}
// Create lookup for signatures by person ID
const signaturesByPersonId = new Map();
existingSignatures.forEach(sig => {
if (sig.signedBy?.id) {
signaturesByPersonId.set(sig.signedBy.id, sig);
}
});
return (
<div className="divide-y divide-border">
{people.map((edge: any) => {
const person = edge.node;
const signature = signaturesByPersonId.get(person.id);
const isRequesting = requestingId === person.id;
return (
<div key={person.id} className="py-4">
<div className="flex justify-between items-center">
<div className="flex items-center gap-3">
<Avatar className="h-10 w-10">
<AvatarFallback>
{person.fullName.charAt(0)}
</AvatarFallback>
</Avatar>
<div>
<div className="font-medium">{person.fullName}</div>
<div className="text-sm text-tertiary">
{signature ? (
signature.state === "SIGNED" ? (
<div className="flex items-center gap-1 text-green-700">
<CheckCircle2 className="h-3.5 w-3.5" />
<span>Signed on {formatDateTime(signature.signedAt)}</span>
</div>
) : (
<div className="flex items-center gap-1 text-amber-700">
<Clock className="h-3.5 w-3.5" />
<span>Requested on {formatDateTime(signature.requestedAt)}</span>
</div>
)
) : (
person.primaryEmailAddress
)}
</div>
</div>
</div>
{signature ? (
<span className={`text-xs px-2 py-0.5 rounded-full ${
signature.state === "SIGNED"
? 'bg-green-100 text-green-800'
: 'bg-yellow-100 text-yellow-800'
}`}>
{signature.state === "SIGNED" ? "Signed" : "Pending"}
</span>
) : (
<Button
variant="outline"
size="sm"
onClick={() => onRequestSignature(person.id)}
disabled={isRequesting}
>
{isRequesting ? (
<>
<Loader2 className="h-4 w-4 mr-2 animate-spin" />
Requesting...
</>
) : (
"Request Signature"
)}
</Button>
)}
</div>
</div>
);
})}
</div>
);
}

View File

@@ -0,0 +1,149 @@
import { Dialog, DialogContent } from "@/components/ui/dialog";
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
import { format } from "date-fns";
import { Button } from "@/components/ui/button";
import { useState } from "react";
import ReactMarkdown from "react-markdown";
import { graphql, useFragment } from "react-relay";
import type { VersionHistoryModal_policyVersions$key } from "./__generated__/VersionHistoryModal_policyVersions.graphql";
export const policyVersionsFragment = graphql`
fragment VersionHistoryModal_policyVersions on Policy {
title
owner {
fullName
}
versionHistory: versions(first: 20) {
edges {
node {
id
version
status
content
changelog
publishedAt
updatedAt
publishedBy {
fullName
}
}
}
}
}
`;
interface VersionHistoryModalProps {
isOpen: boolean;
onClose: () => void;
policyRef: VersionHistoryModal_policyVersions$key;
onRestoreVersion?: (versionNumber: number) => void;
}
export function VersionHistoryModal({
isOpen,
onClose,
policyRef,
onRestoreVersion
}: VersionHistoryModalProps) {
const data = useFragment<VersionHistoryModal_policyVersions$key>(
policyVersionsFragment,
policyRef
);
// Safely access and extract version nodes
const versionNodes = data?.versionHistory?.edges?.map(edge => edge.node) || [];
// Sort versions by version number (descending)
const versions = [...versionNodes].sort((a, b) => b.version - a.version);
const latestVersion = versions.length > 0 ? versions[0].version : 0;
const [selectedVersion, setSelectedVersion] = useState<number>(latestVersion);
const currentVersionData = versions.find(v => v.version === selectedVersion);
// Format time helper for version history
const formatDateTime = (dateString?: string | null) => {
if (!dateString) return "N/A";
const date = new Date(dateString);
return format(date, "h:mm a • MMM d, yyyy");
};
return (
<Dialog open={isOpen} onOpenChange={onClose}>
<DialogContent className="max-w-[1080px] h-[744px] p-0 overflow-hidden flex flex-col">
<div className="flex flex-1 overflow-hidden">
{/* Version History Sidebar */}
<div className="w-[316px] border-r border-solid-b h-full overflow-y-auto">
<h2 className="text-lg font-semibold p-6 pb-4">Version history</h2>
<div className="overflow-y-auto">
{versions.map((version) => (
<div
key={version.id}
className={`flex items-center gap-3 p-6 py-4 cursor-pointer ${selectedVersion === version.version ? 'bg-slate-50' : ''}`}
onClick={() => setSelectedVersion(version.version)}
>
<Avatar className="h-10 w-10">
<AvatarImage src="" alt={version.publishedBy?.fullName || data.owner?.fullName || ""} />
<AvatarFallback>
{(version.publishedBy?.fullName || data.owner?.fullName || "").charAt(0)}
</AvatarFallback>
</Avatar>
<div className="flex-1">
<div className="flex items-center gap-2">
<span className="font-medium">Version {version.version}</span>
{version.status === "PUBLISHED" ? (
<span className="text-xs px-2 py-0.5 rounded-full bg-green-100 text-green-800">
Published
</span>
) : (
<span className="text-xs px-2 py-0.5 rounded-full bg-yellow-100 text-yellow-800">
Draft
</span>
)}
</div>
<div className="text-sm text-tertiary">
{version.publishedBy?.fullName || data.owner?.fullName || "Unknown"} • {formatDateTime(version.publishedAt || version.updatedAt)}
</div>
</div>
</div>
))}
</div>
</div>
{/* Content Area */}
<div className="flex-1 p-6 relative overflow-y-auto">
<h2 className="text-2xl font-semibold mb-6">{data.title}</h2>
<div className="prose prose-olive max-w-none">
{selectedVersion && (
<ReactMarkdown>
{versions.find(v => v.version === selectedVersion)?.content || "No content available"}
</ReactMarkdown>
)}
</div>
</div>
</div>
<div className="flex justify-end p-4 border-t border-solid-b mt-auto">
<Button
variant="outline"
onClick={onClose}
className="mr-2"
>
Cancel
</Button>
{onRestoreVersion && (
<Button
variant="default"
onClick={() => onRestoreVersion(selectedVersion)}
disabled={selectedVersion === latestVersion}
>
Restore version
</Button>
)}
</div>
</DialogContent>
</Dialog>
);
}

View File

@@ -1,5 +1,5 @@
/**
* @generated SignedSource<<171200736f3a443faae3f8b7c2085f4f>>
* @generated SignedSource<<d8d76cb73e37916ddc34017550ff3702>>
* @lightSyntaxTransform
* @nogrep
*/
@@ -9,30 +9,18 @@
// @ts-nocheck
import { ConcreteRequest } from 'relay-runtime';
export type PolicyStatus = "ACTIVE" | "DRAFT";
export type UpdatePolicyInput = {
content?: string | null | undefined;
id: string;
name?: string | null | undefined;
ownerId?: string | null | undefined;
reviewDate?: string | null | undefined;
status?: PolicyStatus | null | undefined;
export type UpdatePolicyVersionInput = {
content: string;
policyVersionId: string;
};
export type EditPolicyViewMutation$variables = {
input: UpdatePolicyInput;
input: UpdatePolicyVersionInput;
};
export type EditPolicyViewMutation$data = {
readonly updatePolicy: {
readonly policy: {
readonly updatePolicyVersion: {
readonly policyVersion: {
readonly content: string;
readonly id: string;
readonly name: string;
readonly owner: {
readonly fullName: string;
readonly id: string;
};
readonly reviewDate: string | null | undefined;
readonly status: PolicyStatus;
};
};
};
@@ -49,14 +37,7 @@ var v0 = [
"name": "input"
}
],
v1 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "id",
"storageKey": null
},
v2 = [
v1 = [
{
"alias": null,
"args": [
@@ -66,25 +47,24 @@ v2 = [
"variableName": "input"
}
],
"concreteType": "UpdatePolicyPayload",
"concreteType": "UpdatePolicyVersionPayload",
"kind": "LinkedField",
"name": "updatePolicy",
"name": "updatePolicyVersion",
"plural": false,
"selections": [
{
"alias": null,
"args": null,
"concreteType": "Policy",
"concreteType": "PolicyVersion",
"kind": "LinkedField",
"name": "policy",
"name": "policyVersion",
"plural": false,
"selections": [
(v1/*: any*/),
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "name",
"name": "id",
"storageKey": null
},
{
@@ -93,39 +73,6 @@ v2 = [
"kind": "ScalarField",
"name": "content",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "status",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "reviewDate",
"storageKey": null
},
{
"alias": null,
"args": null,
"concreteType": "People",
"kind": "LinkedField",
"name": "owner",
"plural": false,
"selections": [
(v1/*: any*/),
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "fullName",
"storageKey": null
}
],
"storageKey": null
}
],
"storageKey": null
@@ -140,7 +87,7 @@ return {
"kind": "Fragment",
"metadata": null,
"name": "EditPolicyViewMutation",
"selections": (v2/*: any*/),
"selections": (v1/*: any*/),
"type": "Mutation",
"abstractKey": null
},
@@ -149,19 +96,19 @@ return {
"argumentDefinitions": (v0/*: any*/),
"kind": "Operation",
"name": "EditPolicyViewMutation",
"selections": (v2/*: any*/)
"selections": (v1/*: any*/)
},
"params": {
"cacheID": "643990a3ab54cb51144f434b023531e7",
"cacheID": "02b56043d606c6237dd502a7695fea50",
"id": null,
"metadata": {},
"name": "EditPolicyViewMutation",
"operationKind": "mutation",
"text": "mutation EditPolicyViewMutation(\n $input: UpdatePolicyInput!\n) {\n updatePolicy(input: $input) {\n policy {\n id\n name\n content\n status\n reviewDate\n owner {\n id\n fullName\n }\n }\n }\n}\n"
"text": "mutation EditPolicyViewMutation(\n $input: UpdatePolicyVersionInput!\n) {\n updatePolicyVersion(input: $input) {\n policyVersion {\n id\n content\n }\n }\n}\n"
}
};
})();
(node as any).hash = "41d4945568bc1f43eeedc727a968654d";
(node as any).hash = "556fbcc4fadc0011c1a2154dcffeb389";
export default node;

View File

@@ -1,5 +1,5 @@
/**
* @generated SignedSource<<312876323f4072e43048dfe1b2e21d08>>
* @generated SignedSource<<7738a95cc83ee25c08f6ddc556f144c1>>
* @lightSyntaxTransform
* @nogrep
*/
@@ -10,25 +10,26 @@
import { ConcreteRequest } from 'relay-runtime';
import { FragmentRefs } from "relay-runtime";
export type PolicyStatus = "ACTIVE" | "DRAFT";
export type EditPolicyViewQuery$variables = {
organizationId: string;
policyId: string;
policyVersionId: string;
};
export type EditPolicyViewQuery$data = {
readonly organization: {
readonly " $fragmentSpreads": FragmentRefs<"PeopleSelector_organization">;
};
readonly policy: {
readonly content?: string;
readonly id: string;
readonly name?: string;
readonly owner?: {
readonly fullName: string;
readonly id: string;
};
readonly reviewDate?: string | null | undefined;
readonly status?: PolicyStatus;
readonly title?: string;
};
readonly policyVersion: {
readonly content?: string;
readonly id: string;
};
};
export type EditPolicyViewQuery = {
@@ -47,56 +48,61 @@ v1 = {
"kind": "LocalArgument",
"name": "policyId"
},
v2 = [
v2 = {
"defaultValue": null,
"kind": "LocalArgument",
"name": "policyVersionId"
},
v3 = [
{
"kind": "Variable",
"name": "id",
"variableName": "policyId"
"variableName": "policyVersionId"
}
],
v3 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "id",
"storageKey": null
},
v4 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "fullName",
"name": "id",
"storageKey": null
},
v5 = {
"kind": "InlineFragment",
"selections": [
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "name",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "content",
"storageKey": null
},
}
],
"type": "PolicyVersion",
"abstractKey": null
},
v6 = [
{
"kind": "Variable",
"name": "id",
"variableName": "policyId"
}
],
v7 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "fullName",
"storageKey": null
},
v8 = {
"kind": "InlineFragment",
"selections": [
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "status",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "reviewDate",
"name": "title",
"storageKey": null
},
{
@@ -107,8 +113,8 @@ v5 = {
"name": "owner",
"plural": false,
"selections": [
(v3/*: any*/),
(v4/*: any*/)
(v4/*: any*/),
(v7/*: any*/)
],
"storageKey": null
}
@@ -116,21 +122,21 @@ v5 = {
"type": "Policy",
"abstractKey": null
},
v6 = [
v9 = [
{
"kind": "Variable",
"name": "id",
"variableName": "organizationId"
}
],
v7 = {
v10 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "__typename",
"storageKey": null
},
v8 = [
v11 = [
{
"kind": "Literal",
"name": "first",
@@ -149,32 +155,46 @@ return {
"fragment": {
"argumentDefinitions": [
(v0/*: any*/),
(v1/*: any*/)
(v1/*: any*/),
(v2/*: any*/)
],
"kind": "Fragment",
"metadata": null,
"name": "EditPolicyViewQuery",
"selections": [
{
"alias": "policy",
"args": (v2/*: any*/),
"alias": "policyVersion",
"args": (v3/*: any*/),
"concreteType": null,
"kind": "LinkedField",
"name": "node",
"plural": false,
"selections": [
(v3/*: any*/),
(v4/*: any*/),
(v5/*: any*/)
],
"storageKey": null
},
{
"alias": "organization",
"alias": "policy",
"args": (v6/*: any*/),
"concreteType": null,
"kind": "LinkedField",
"name": "node",
"plural": false,
"selections": [
(v4/*: any*/),
(v8/*: any*/)
],
"storageKey": null
},
{
"alias": "organization",
"args": (v9/*: any*/),
"concreteType": null,
"kind": "LinkedField",
"name": "node",
"plural": false,
"selections": [
{
"args": null,
@@ -192,41 +212,56 @@ return {
"operation": {
"argumentDefinitions": [
(v1/*: any*/),
(v0/*: any*/)
(v0/*: any*/),
(v2/*: any*/)
],
"kind": "Operation",
"name": "EditPolicyViewQuery",
"selections": [
{
"alias": "policy",
"args": (v2/*: any*/),
"alias": "policyVersion",
"args": (v3/*: any*/),
"concreteType": null,
"kind": "LinkedField",
"name": "node",
"plural": false,
"selections": [
(v7/*: any*/),
(v3/*: any*/),
(v10/*: any*/),
(v4/*: any*/),
(v5/*: any*/)
],
"storageKey": null
},
{
"alias": "organization",
"alias": "policy",
"args": (v6/*: any*/),
"concreteType": null,
"kind": "LinkedField",
"name": "node",
"plural": false,
"selections": [
(v7/*: any*/),
(v3/*: any*/),
(v10/*: any*/),
(v4/*: any*/),
(v8/*: any*/)
],
"storageKey": null
},
{
"alias": "organization",
"args": (v9/*: any*/),
"concreteType": null,
"kind": "LinkedField",
"name": "node",
"plural": false,
"selections": [
(v10/*: any*/),
(v4/*: any*/),
{
"kind": "InlineFragment",
"selections": [
{
"alias": null,
"args": (v8/*: any*/),
"args": (v11/*: any*/),
"concreteType": "PeopleConnection",
"kind": "LinkedField",
"name": "peoples",
@@ -248,8 +283,8 @@ return {
"name": "node",
"plural": false,
"selections": [
(v3/*: any*/),
(v4/*: any*/),
(v7/*: any*/),
{
"alias": null,
"args": null,
@@ -257,7 +292,7 @@ return {
"name": "primaryEmailAddress",
"storageKey": null
},
(v7/*: any*/)
(v10/*: any*/)
],
"storageKey": null
},
@@ -301,7 +336,7 @@ return {
},
{
"alias": null,
"args": (v8/*: any*/),
"args": (v11/*: any*/),
"filters": [
"orderBy"
],
@@ -320,16 +355,16 @@ return {
]
},
"params": {
"cacheID": "4d52d22f9d8acc7912a57d974f558248",
"cacheID": "4d586701fc5f2e4976bb87d9be61c594",
"id": null,
"metadata": {},
"name": "EditPolicyViewQuery",
"operationKind": "query",
"text": "query EditPolicyViewQuery(\n $policyId: ID!\n $organizationId: ID!\n) {\n policy: node(id: $policyId) {\n __typename\n id\n ... on Policy {\n name\n content\n status\n reviewDate\n owner {\n id\n fullName\n }\n }\n }\n organization: node(id: $organizationId) {\n __typename\n ...PeopleSelector_organization\n id\n }\n}\n\nfragment PeopleSelector_organization on Organization {\n id\n peoples(first: 100, orderBy: {direction: ASC, field: FULL_NAME}) {\n edges {\n node {\n id\n fullName\n primaryEmailAddress\n __typename\n }\n cursor\n }\n pageInfo {\n endCursor\n hasNextPage\n }\n }\n}\n"
"text": "query EditPolicyViewQuery(\n $policyId: ID!\n $organizationId: ID!\n $policyVersionId: ID!\n) {\n policyVersion: node(id: $policyVersionId) {\n __typename\n id\n ... on PolicyVersion {\n content\n }\n }\n policy: node(id: $policyId) {\n __typename\n id\n ... on Policy {\n title\n owner {\n id\n fullName\n }\n }\n }\n organization: node(id: $organizationId) {\n __typename\n ...PeopleSelector_organization\n id\n }\n}\n\nfragment PeopleSelector_organization on Organization {\n id\n peoples(first: 100, orderBy: {direction: ASC, field: FULL_NAME}) {\n edges {\n node {\n id\n fullName\n primaryEmailAddress\n __typename\n }\n cursor\n }\n pageInfo {\n endCursor\n hasNextPage\n }\n }\n}\n"
}
};
})();
(node as any).hash = "d16bdd881b27b1aa5368130ce036fd20";
(node as any).hash = "2a934195ed33bd073e3976e8cc19a925";
export default node;

View File

@@ -1,230 +0,0 @@
/**
* @generated SignedSource<<84789bb949f94e924de1c9b9d566b4a5>>
* @lightSyntaxTransform
* @nogrep
*/
/* tslint:disable */
/* eslint-disable */
// @ts-nocheck
import { ConcreteRequest } from 'relay-runtime';
import { FragmentRefs } from "relay-runtime";
export type NewPolicyViewQuery$variables = {
organizationId: string;
};
export type NewPolicyViewQuery$data = {
readonly organization: {
readonly " $fragmentSpreads": FragmentRefs<"PeopleSelector_organization">;
};
};
export type NewPolicyViewQuery = {
response: NewPolicyViewQuery$data;
variables: NewPolicyViewQuery$variables;
};
const node: ConcreteRequest = (function(){
var v0 = [
{
"defaultValue": null,
"kind": "LocalArgument",
"name": "organizationId"
}
],
v1 = [
{
"kind": "Variable",
"name": "id",
"variableName": "organizationId"
}
],
v2 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "__typename",
"storageKey": null
},
v3 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "id",
"storageKey": null
},
v4 = [
{
"kind": "Literal",
"name": "first",
"value": 100
},
{
"kind": "Literal",
"name": "orderBy",
"value": {
"direction": "ASC",
"field": "FULL_NAME"
}
}
];
return {
"fragment": {
"argumentDefinitions": (v0/*: any*/),
"kind": "Fragment",
"metadata": null,
"name": "NewPolicyViewQuery",
"selections": [
{
"alias": "organization",
"args": (v1/*: any*/),
"concreteType": null,
"kind": "LinkedField",
"name": "node",
"plural": false,
"selections": [
{
"args": null,
"kind": "FragmentSpread",
"name": "PeopleSelector_organization"
}
],
"storageKey": null
}
],
"type": "Query",
"abstractKey": null
},
"kind": "Request",
"operation": {
"argumentDefinitions": (v0/*: any*/),
"kind": "Operation",
"name": "NewPolicyViewQuery",
"selections": [
{
"alias": "organization",
"args": (v1/*: any*/),
"concreteType": null,
"kind": "LinkedField",
"name": "node",
"plural": false,
"selections": [
(v2/*: any*/),
(v3/*: any*/),
{
"kind": "InlineFragment",
"selections": [
{
"alias": null,
"args": (v4/*: any*/),
"concreteType": "PeopleConnection",
"kind": "LinkedField",
"name": "peoples",
"plural": false,
"selections": [
{
"alias": null,
"args": null,
"concreteType": "PeopleEdge",
"kind": "LinkedField",
"name": "edges",
"plural": true,
"selections": [
{
"alias": null,
"args": null,
"concreteType": "People",
"kind": "LinkedField",
"name": "node",
"plural": false,
"selections": [
(v3/*: any*/),
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "fullName",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "primaryEmailAddress",
"storageKey": null
},
(v2/*: any*/)
],
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "cursor",
"storageKey": null
}
],
"storageKey": null
},
{
"alias": null,
"args": null,
"concreteType": "PageInfo",
"kind": "LinkedField",
"name": "pageInfo",
"plural": false,
"selections": [
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "endCursor",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "hasNextPage",
"storageKey": null
}
],
"storageKey": null
}
],
"storageKey": "peoples(first:100,orderBy:{\"direction\":\"ASC\",\"field\":\"FULL_NAME\"})"
},
{
"alias": null,
"args": (v4/*: any*/),
"filters": [
"orderBy"
],
"handle": "connection",
"key": "PeopleSelector_organization_peoples",
"kind": "LinkedHandle",
"name": "peoples"
}
],
"type": "Organization",
"abstractKey": null
}
],
"storageKey": null
}
]
},
"params": {
"cacheID": "552067b4b0632972c14f7a8dcaf171aa",
"id": null,
"metadata": {},
"name": "NewPolicyViewQuery",
"operationKind": "query",
"text": "query NewPolicyViewQuery(\n $organizationId: ID!\n) {\n organization: node(id: $organizationId) {\n __typename\n ...PeopleSelector_organization\n id\n }\n}\n\nfragment PeopleSelector_organization on Organization {\n id\n peoples(first: 100, orderBy: {direction: ASC, field: FULL_NAME}) {\n edges {\n node {\n id\n fullName\n primaryEmailAddress\n __typename\n }\n cursor\n }\n pageInfo {\n endCursor\n hasNextPage\n }\n }\n}\n"
}
};
})();
(node as any).hash = "98fa41bfaf0d76adce1c91cf8d6ade28";
export default node;

View File

@@ -1,5 +1,5 @@
/**
* @generated SignedSource<<2026b385ee5eb895da6ca9c919762913>>
* @generated SignedSource<<885dc2a54ba3b8d4663e197cfe61d704>>
* @lightSyntaxTransform
* @nogrep
*/
@@ -9,39 +9,36 @@
// @ts-nocheck
import { ConcreteRequest } from 'relay-runtime';
export type PolicyStatus = "ACTIVE" | "DRAFT";
export type CreatePolicyInput = {
content: string;
name: string;
organizationId: string;
ownerId: string;
reviewDate?: string | null | undefined;
status: PolicyStatus;
title: string;
};
export type NewPolicyViewMutation$variables = {
export type PolicyListViewCreateMutation$variables = {
connections: ReadonlyArray<string>;
input: CreatePolicyInput;
};
export type NewPolicyViewMutation$data = {
export type PolicyListViewCreateMutation$data = {
readonly createPolicy: {
readonly policyEdge: {
readonly node: {
readonly content: string;
readonly createdAt: string;
readonly description: string;
readonly id: string;
readonly name: string;
readonly owner: {
readonly fullName: string;
readonly id: string;
};
readonly reviewDate: string | null | undefined;
readonly status: PolicyStatus;
readonly title: string;
readonly updatedAt: string;
};
};
};
};
export type NewPolicyViewMutation = {
response: NewPolicyViewMutation$data;
variables: NewPolicyViewMutation$variables;
export type PolicyListViewCreateMutation = {
response: PolicyListViewCreateMutation$data;
variables: PolicyListViewCreateMutation$variables;
};
const node: ConcreteRequest = (function(){
@@ -90,28 +87,28 @@ v4 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "name",
"name": "title",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "content",
"name": "description",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "status",
"name": "createdAt",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "reviewDate",
"name": "updatedAt",
"storageKey": null
},
{
@@ -147,7 +144,7 @@ return {
],
"kind": "Fragment",
"metadata": null,
"name": "NewPolicyViewMutation",
"name": "PolicyListViewCreateMutation",
"selections": [
{
"alias": null,
@@ -172,7 +169,7 @@ return {
(v0/*: any*/)
],
"kind": "Operation",
"name": "NewPolicyViewMutation",
"name": "PolicyListViewCreateMutation",
"selections": [
{
"alias": null,
@@ -205,16 +202,16 @@ return {
]
},
"params": {
"cacheID": "aa29cde3106d1c8d35c6ae8e74f310b7",
"cacheID": "f7aae6d7a0d276e71caec643e43b5ee5",
"id": null,
"metadata": {},
"name": "NewPolicyViewMutation",
"name": "PolicyListViewCreateMutation",
"operationKind": "mutation",
"text": "mutation NewPolicyViewMutation(\n $input: CreatePolicyInput!\n) {\n createPolicy(input: $input) {\n policyEdge {\n node {\n id\n name\n content\n status\n reviewDate\n owner {\n id\n fullName\n }\n }\n }\n }\n}\n"
"text": "mutation PolicyListViewCreateMutation(\n $input: CreatePolicyInput!\n) {\n createPolicy(input: $input) {\n policyEdge {\n node {\n id\n title\n description\n createdAt\n updatedAt\n owner {\n id\n fullName\n }\n }\n }\n }\n}\n"
}
};
})();
(node as any).hash = "f6dcabf6c51e3858c9750e00fcc0cc15";
(node as any).hash = "d55ac010a7ad352d830d3a38f2f8d5a8";
export default node;

View File

@@ -1,5 +1,5 @@
/**
* @generated SignedSource<<391e3817107bb96f50aad61d20cad0a8>>
* @generated SignedSource<<5b24e53258fceb03535363014f57a15e>>
* @lightSyntaxTransform
* @nogrep
*/
@@ -9,7 +9,8 @@
// @ts-nocheck
import { ConcreteRequest } from 'relay-runtime';
export type PolicyStatus = "ACTIVE" | "DRAFT";
import { FragmentRefs } from "relay-runtime";
export type PolicyStatus = "DRAFT" | "PUBLISHED";
export type PolicyListViewQuery$variables = {
organizationId: string;
};
@@ -18,15 +19,34 @@ export type PolicyListViewQuery$data = {
readonly policies?: {
readonly edges: ReadonlyArray<{
readonly node: {
readonly content: string;
readonly createdAt: string;
readonly currentPublishedVersion: number | null | undefined;
readonly description: string;
readonly id: string;
readonly name: string;
readonly status: PolicyStatus;
readonly owner: {
readonly fullName: string;
readonly id: string;
};
readonly title: string;
readonly updatedAt: string;
readonly versions: {
readonly edges: ReadonlyArray<{
readonly node: {
readonly id: string;
readonly status: PolicyStatus;
readonly updatedAt: string;
};
}>;
};
};
}>;
};
readonly " $fragmentSpreads": FragmentRefs<"PeopleSelector_organization">;
};
readonly viewer: {
readonly user: {
readonly id: string;
};
};
};
export type PolicyListViewQuery = {
@@ -42,28 +62,94 @@ var v0 = [
"name": "organizationId"
}
],
v1 = [
{
"kind": "Variable",
"name": "id",
"variableName": "organizationId"
}
],
v2 = {
v1 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "id",
"storageKey": null
},
v3 = {
v2 = {
"alias": null,
"args": null,
"concreteType": "User",
"kind": "LinkedField",
"name": "user",
"plural": false,
"selections": [
(v1/*: any*/)
],
"storageKey": null
},
v3 = [
{
"kind": "Variable",
"name": "id",
"variableName": "organizationId"
}
],
v4 = {
"kind": "Literal",
"name": "orderBy",
"value": {
"direction": "ASC",
"field": "TITLE"
}
},
v5 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "updatedAt",
"storageKey": null
},
v6 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "fullName",
"storageKey": null
},
v7 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "__typename",
"storageKey": null
},
v4 = [
v8 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "cursor",
"storageKey": null
},
v9 = {
"alias": null,
"args": null,
"concreteType": "PageInfo",
"kind": "LinkedField",
"name": "pageInfo",
"plural": false,
"selections": [
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "endCursor",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "hasNextPage",
"storageKey": null
}
],
"storageKey": null
},
v10 = [
{
"alias": null,
"args": null,
@@ -80,19 +166,26 @@ v4 = [
"name": "node",
"plural": false,
"selections": [
(v2/*: any*/),
(v1/*: any*/),
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "name",
"name": "title",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "content",
"name": "description",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "currentPublishedVersion",
"storageKey": null
},
{
@@ -102,66 +195,103 @@ v4 = [
"name": "createdAt",
"storageKey": null
},
(v5/*: any*/),
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "updatedAt",
"concreteType": "People",
"kind": "LinkedField",
"name": "owner",
"plural": false,
"selections": [
(v1/*: any*/),
(v6/*: any*/)
],
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "status",
"storageKey": null
"args": [
{
"kind": "Literal",
"name": "first",
"value": 1
}
],
"concreteType": "PolicyVersionConnection",
"kind": "LinkedField",
"name": "versions",
"plural": false,
"selections": [
{
"alias": null,
"args": null,
"concreteType": "PolicyVersionEdge",
"kind": "LinkedField",
"name": "edges",
"plural": true,
"selections": [
{
"alias": null,
"args": null,
"concreteType": "PolicyVersion",
"kind": "LinkedField",
"name": "node",
"plural": false,
"selections": [
(v1/*: any*/),
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "status",
"storageKey": null
},
(v5/*: any*/)
],
"storageKey": null
}
],
"storageKey": null
}
],
"storageKey": "versions(first:1)"
},
(v3/*: any*/)
(v7/*: any*/)
],
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "cursor",
"storageKey": null
}
(v8/*: any*/)
],
"storageKey": null
},
{
"alias": null,
"args": null,
"concreteType": "PageInfo",
"kind": "LinkedField",
"name": "pageInfo",
"plural": false,
"selections": [
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "endCursor",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "hasNextPage",
"storageKey": null
}
],
"storageKey": null
}
(v9/*: any*/)
],
v5 = [
v11 = [
{
"kind": "Literal",
"name": "first",
"value": 100
},
{
"kind": "Literal",
"name": "orderBy",
"value": {
"direction": "ASC",
"field": "FULL_NAME"
}
}
],
v12 = [
"orderBy"
],
v13 = [
{
"kind": "Literal",
"name": "first",
"value": 50
},
(v4/*: any*/)
];
return {
"fragment": {
@@ -170,9 +300,21 @@ return {
"metadata": null,
"name": "PolicyListViewQuery",
"selections": [
{
"alias": null,
"args": null,
"concreteType": "Viewer",
"kind": "LinkedField",
"name": "viewer",
"plural": false,
"selections": [
(v2/*: any*/)
],
"storageKey": null
},
{
"alias": "organization",
"args": (v1/*: any*/),
"args": (v3/*: any*/),
"concreteType": null,
"kind": "LinkedField",
"name": "node",
@@ -182,14 +324,21 @@ return {
"kind": "InlineFragment",
"selections": [
{
"alias": "policies",
"args": null,
"kind": "FragmentSpread",
"name": "PeopleSelector_organization"
},
{
"alias": "policies",
"args": [
(v4/*: any*/)
],
"concreteType": "PolicyConnection",
"kind": "LinkedField",
"name": "__PolicyListView_policies_connection",
"plural": false,
"selections": (v4/*: any*/),
"storageKey": null
"selections": (v10/*: any*/),
"storageKey": "__PolicyListView_policies_connection(orderBy:{\"direction\":\"ASC\",\"field\":\"TITLE\"})"
}
],
"type": "Organization",
@@ -208,32 +357,100 @@ return {
"kind": "Operation",
"name": "PolicyListViewQuery",
"selections": [
{
"alias": null,
"args": null,
"concreteType": "Viewer",
"kind": "LinkedField",
"name": "viewer",
"plural": false,
"selections": [
(v2/*: any*/),
(v1/*: any*/)
],
"storageKey": null
},
{
"alias": "organization",
"args": (v1/*: any*/),
"args": (v3/*: any*/),
"concreteType": null,
"kind": "LinkedField",
"name": "node",
"plural": false,
"selections": [
(v3/*: any*/),
(v7/*: any*/),
(v1/*: any*/),
{
"kind": "InlineFragment",
"selections": [
{
"alias": null,
"args": (v5/*: any*/),
"args": (v11/*: any*/),
"concreteType": "PeopleConnection",
"kind": "LinkedField",
"name": "peoples",
"plural": false,
"selections": [
{
"alias": null,
"args": null,
"concreteType": "PeopleEdge",
"kind": "LinkedField",
"name": "edges",
"plural": true,
"selections": [
{
"alias": null,
"args": null,
"concreteType": "People",
"kind": "LinkedField",
"name": "node",
"plural": false,
"selections": [
(v1/*: any*/),
(v6/*: any*/),
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "primaryEmailAddress",
"storageKey": null
},
(v7/*: any*/)
],
"storageKey": null
},
(v8/*: any*/)
],
"storageKey": null
},
(v9/*: any*/)
],
"storageKey": "peoples(first:100,orderBy:{\"direction\":\"ASC\",\"field\":\"FULL_NAME\"})"
},
{
"alias": null,
"args": (v11/*: any*/),
"filters": (v12/*: any*/),
"handle": "connection",
"key": "PeopleSelector_organization_peoples",
"kind": "LinkedHandle",
"name": "peoples"
},
{
"alias": null,
"args": (v13/*: any*/),
"concreteType": "PolicyConnection",
"kind": "LinkedField",
"name": "policies",
"plural": false,
"selections": (v4/*: any*/),
"storageKey": "policies(first:100)"
"selections": (v10/*: any*/),
"storageKey": "policies(first:50,orderBy:{\"direction\":\"ASC\",\"field\":\"TITLE\"})"
},
{
"alias": null,
"args": (v5/*: any*/),
"filters": null,
"args": (v13/*: any*/),
"filters": (v12/*: any*/),
"handle": "connection",
"key": "PolicyListView_policies",
"kind": "LinkedHandle",
@@ -242,15 +459,14 @@ return {
],
"type": "Organization",
"abstractKey": null
},
(v2/*: any*/)
}
],
"storageKey": null
}
]
},
"params": {
"cacheID": "6434c8135f68ac64eb2f06983ddc3595",
"cacheID": "ed650f54cc226d3adea27a78a777890c",
"id": null,
"metadata": {
"connection": [
@@ -267,11 +483,11 @@ return {
},
"name": "PolicyListViewQuery",
"operationKind": "query",
"text": "query PolicyListViewQuery(\n $organizationId: ID!\n) {\n organization: node(id: $organizationId) {\n __typename\n ... on Organization {\n policies(first: 100) {\n edges {\n node {\n id\n name\n content\n createdAt\n updatedAt\n status\n __typename\n }\n cursor\n }\n pageInfo {\n endCursor\n hasNextPage\n }\n }\n }\n id\n }\n}\n"
"text": "query PolicyListViewQuery(\n $organizationId: ID!\n) {\n viewer {\n user {\n id\n }\n id\n }\n organization: node(id: $organizationId) {\n __typename\n ... on Organization {\n ...PeopleSelector_organization\n policies(first: 50, orderBy: {field: TITLE, direction: ASC}) {\n edges {\n node {\n id\n title\n description\n currentPublishedVersion\n createdAt\n updatedAt\n owner {\n id\n fullName\n }\n versions(first: 1) {\n edges {\n node {\n id\n status\n updatedAt\n }\n }\n }\n __typename\n }\n cursor\n }\n pageInfo {\n endCursor\n hasNextPage\n }\n }\n }\n id\n }\n}\n\nfragment PeopleSelector_organization on Organization {\n id\n peoples(first: 100, orderBy: {direction: ASC, field: FULL_NAME}) {\n edges {\n node {\n id\n fullName\n primaryEmailAddress\n __typename\n }\n cursor\n }\n pageInfo {\n endCursor\n hasNextPage\n }\n }\n}\n"
}
};
})();
(node as any).hash = "5567339ad9b2be90b94edc8b1a16fe1e";
(node as any).hash = "b3b0798be0779c09013a4743d015e87f";
export default node;

View File

@@ -1,132 +0,0 @@
/**
* @generated SignedSource<<4e559f39bd9ec2b3782113ef034cad03>>
* @lightSyntaxTransform
* @nogrep
*/
/* tslint:disable */
/* eslint-disable */
// @ts-nocheck
import { ConcreteRequest } from 'relay-runtime';
export type DeletePolicyInput = {
policyId: string;
};
export type PolicyViewDeleteMutation$variables = {
connections: ReadonlyArray<string>;
input: DeletePolicyInput;
};
export type PolicyViewDeleteMutation$data = {
readonly deletePolicy: {
readonly deletedPolicyId: string;
};
};
export type PolicyViewDeleteMutation = {
response: PolicyViewDeleteMutation$data;
variables: PolicyViewDeleteMutation$variables;
};
const node: ConcreteRequest = (function(){
var v0 = {
"defaultValue": null,
"kind": "LocalArgument",
"name": "connections"
},
v1 = {
"defaultValue": null,
"kind": "LocalArgument",
"name": "input"
},
v2 = [
{
"kind": "Variable",
"name": "input",
"variableName": "input"
}
],
v3 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "deletedPolicyId",
"storageKey": null
};
return {
"fragment": {
"argumentDefinitions": [
(v0/*: any*/),
(v1/*: any*/)
],
"kind": "Fragment",
"metadata": null,
"name": "PolicyViewDeleteMutation",
"selections": [
{
"alias": null,
"args": (v2/*: any*/),
"concreteType": "DeletePolicyPayload",
"kind": "LinkedField",
"name": "deletePolicy",
"plural": false,
"selections": [
(v3/*: any*/)
],
"storageKey": null
}
],
"type": "Mutation",
"abstractKey": null
},
"kind": "Request",
"operation": {
"argumentDefinitions": [
(v1/*: any*/),
(v0/*: any*/)
],
"kind": "Operation",
"name": "PolicyViewDeleteMutation",
"selections": [
{
"alias": null,
"args": (v2/*: any*/),
"concreteType": "DeletePolicyPayload",
"kind": "LinkedField",
"name": "deletePolicy",
"plural": false,
"selections": [
(v3/*: any*/),
{
"alias": null,
"args": null,
"filters": null,
"handle": "deleteEdge",
"key": "",
"kind": "ScalarHandle",
"name": "deletedPolicyId",
"handleArgs": [
{
"kind": "Variable",
"name": "connections",
"variableName": "connections"
}
]
}
],
"storageKey": null
}
]
},
"params": {
"cacheID": "67e67147e0c7ec0489346b1ad86899c9",
"id": null,
"metadata": {},
"name": "PolicyViewDeleteMutation",
"operationKind": "mutation",
"text": "mutation PolicyViewDeleteMutation(\n $input: DeletePolicyInput!\n) {\n deletePolicy(input: $input) {\n deletedPolicyId\n }\n}\n"
}
};
})();
(node as any).hash = "7d421e9b068f3f676a4c3069769b1c18";
export default node;

View File

@@ -1,199 +0,0 @@
/**
* @generated SignedSource<<eaeef5694bdd615c03bea3b3f8957df3>>
* @lightSyntaxTransform
* @nogrep
*/
/* tslint:disable */
/* eslint-disable */
// @ts-nocheck
import { ConcreteRequest } from 'relay-runtime';
export type PolicyStatus = "ACTIVE" | "DRAFT";
export type PolicyViewQuery$variables = {
policyId: string;
};
export type PolicyViewQuery$data = {
readonly node: {
readonly content?: string;
readonly createdAt?: string;
readonly id: string;
readonly name?: string;
readonly owner?: {
readonly fullName: string;
readonly id: string;
readonly primaryEmailAddress: string;
};
readonly reviewDate?: string | null | undefined;
readonly status?: PolicyStatus;
readonly updatedAt?: string;
};
};
export type PolicyViewQuery = {
response: PolicyViewQuery$data;
variables: PolicyViewQuery$variables;
};
const node: ConcreteRequest = (function(){
var v0 = [
{
"defaultValue": null,
"kind": "LocalArgument",
"name": "policyId"
}
],
v1 = [
{
"kind": "Variable",
"name": "id",
"variableName": "policyId"
}
],
v2 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "id",
"storageKey": null
},
v3 = {
"kind": "InlineFragment",
"selections": [
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "name",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "content",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "createdAt",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "updatedAt",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "reviewDate",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "status",
"storageKey": null
},
{
"alias": null,
"args": null,
"concreteType": "People",
"kind": "LinkedField",
"name": "owner",
"plural": false,
"selections": [
(v2/*: any*/),
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "fullName",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "primaryEmailAddress",
"storageKey": null
}
],
"storageKey": null
}
],
"type": "Policy",
"abstractKey": null
};
return {
"fragment": {
"argumentDefinitions": (v0/*: any*/),
"kind": "Fragment",
"metadata": null,
"name": "PolicyViewQuery",
"selections": [
{
"alias": null,
"args": (v1/*: any*/),
"concreteType": null,
"kind": "LinkedField",
"name": "node",
"plural": false,
"selections": [
(v2/*: any*/),
(v3/*: any*/)
],
"storageKey": null
}
],
"type": "Query",
"abstractKey": null
},
"kind": "Request",
"operation": {
"argumentDefinitions": (v0/*: any*/),
"kind": "Operation",
"name": "PolicyViewQuery",
"selections": [
{
"alias": null,
"args": (v1/*: any*/),
"concreteType": null,
"kind": "LinkedField",
"name": "node",
"plural": false,
"selections": [
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "__typename",
"storageKey": null
},
(v2/*: any*/),
(v3/*: any*/)
],
"storageKey": null
}
]
},
"params": {
"cacheID": "26bf57eeab9c600a3146e01085ecae8e",
"id": null,
"metadata": {},
"name": "PolicyViewQuery",
"operationKind": "query",
"text": "query PolicyViewQuery(\n $policyId: ID!\n) {\n node(id: $policyId) {\n __typename\n id\n ... on Policy {\n name\n content\n createdAt\n updatedAt\n reviewDate\n status\n owner {\n id\n fullName\n primaryEmailAddress\n }\n }\n }\n}\n"
}
};
})();
(node as any).hash = "0fcc28fc290df69d14d7d549bd09216f";
export default node;

View File

@@ -0,0 +1,135 @@
/**
* @generated SignedSource<<290d3c4fb382c7b99c60eeee5cfa8c5a>>
* @lightSyntaxTransform
* @nogrep
*/
/* tslint:disable */
/* eslint-disable */
// @ts-nocheck
import { ConcreteRequest } from 'relay-runtime';
export type PolicyStatus = "DRAFT" | "PUBLISHED";
export type CreateDraftPolicyVersionInput = {
policyID: string;
};
export type ShowPolicyViewCreateDraftMutation$variables = {
input: CreateDraftPolicyVersionInput;
};
export type ShowPolicyViewCreateDraftMutation$data = {
readonly createDraftPolicyVersion: {
readonly policyVersionEdge: {
readonly node: {
readonly id: string;
readonly status: PolicyStatus;
readonly version: number;
};
};
};
};
export type ShowPolicyViewCreateDraftMutation = {
response: ShowPolicyViewCreateDraftMutation$data;
variables: ShowPolicyViewCreateDraftMutation$variables;
};
const node: ConcreteRequest = (function(){
var v0 = [
{
"defaultValue": null,
"kind": "LocalArgument",
"name": "input"
}
],
v1 = [
{
"alias": null,
"args": [
{
"kind": "Variable",
"name": "input",
"variableName": "input"
}
],
"concreteType": "CreateDraftPolicyVersionPayload",
"kind": "LinkedField",
"name": "createDraftPolicyVersion",
"plural": false,
"selections": [
{
"alias": null,
"args": null,
"concreteType": "PolicyVersionEdge",
"kind": "LinkedField",
"name": "policyVersionEdge",
"plural": false,
"selections": [
{
"alias": null,
"args": null,
"concreteType": "PolicyVersion",
"kind": "LinkedField",
"name": "node",
"plural": false,
"selections": [
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "id",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "version",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "status",
"storageKey": null
}
],
"storageKey": null
}
],
"storageKey": null
}
],
"storageKey": null
}
];
return {
"fragment": {
"argumentDefinitions": (v0/*: any*/),
"kind": "Fragment",
"metadata": null,
"name": "ShowPolicyViewCreateDraftMutation",
"selections": (v1/*: any*/),
"type": "Mutation",
"abstractKey": null
},
"kind": "Request",
"operation": {
"argumentDefinitions": (v0/*: any*/),
"kind": "Operation",
"name": "ShowPolicyViewCreateDraftMutation",
"selections": (v1/*: any*/)
},
"params": {
"cacheID": "e96d226e93260cd989dc122193fbcbce",
"id": null,
"metadata": {},
"name": "ShowPolicyViewCreateDraftMutation",
"operationKind": "mutation",
"text": "mutation ShowPolicyViewCreateDraftMutation(\n $input: CreateDraftPolicyVersionInput!\n) {\n createDraftPolicyVersion(input: $input) {\n policyVersionEdge {\n node {\n id\n version\n status\n }\n }\n }\n}\n"
}
};
})();
(node as any).hash = "49282edca41fc2bdca1a1c6db4b2dc11";
export default node;

View File

@@ -0,0 +1,211 @@
/**
* @generated SignedSource<<024ec8d58aa5f37adad3ac5e98c15a90>>
* @lightSyntaxTransform
* @nogrep
*/
/* tslint:disable */
/* eslint-disable */
// @ts-nocheck
import { ConcreteRequest } from 'relay-runtime';
export type PolicyStatus = "DRAFT" | "PUBLISHED";
export type PublishPolicyVersionInput = {
policyId: string;
};
export type ShowPolicyViewPublishMutation$variables = {
input: PublishPolicyVersionInput;
};
export type ShowPolicyViewPublishMutation$data = {
readonly publishPolicyVersion: {
readonly policy: {
readonly currentPublishedVersion: number | null | undefined;
readonly id: string;
};
readonly policyVersion: {
readonly id: string;
readonly publishedAt: string | null | undefined;
readonly publishedBy: {
readonly fullName: string;
} | null | undefined;
readonly status: PolicyStatus;
};
};
};
export type ShowPolicyViewPublishMutation = {
response: ShowPolicyViewPublishMutation$data;
variables: ShowPolicyViewPublishMutation$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": "id",
"storageKey": null
},
v3 = {
"alias": null,
"args": null,
"concreteType": "Policy",
"kind": "LinkedField",
"name": "policy",
"plural": false,
"selections": [
(v2/*: any*/),
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "currentPublishedVersion",
"storageKey": null
}
],
"storageKey": null
},
v4 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "status",
"storageKey": null
},
v5 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "publishedAt",
"storageKey": null
},
v6 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "fullName",
"storageKey": null
};
return {
"fragment": {
"argumentDefinitions": (v0/*: any*/),
"kind": "Fragment",
"metadata": null,
"name": "ShowPolicyViewPublishMutation",
"selections": [
{
"alias": null,
"args": (v1/*: any*/),
"concreteType": "PublishPolicyVersionPayload",
"kind": "LinkedField",
"name": "publishPolicyVersion",
"plural": false,
"selections": [
(v3/*: any*/),
{
"alias": null,
"args": null,
"concreteType": "PolicyVersion",
"kind": "LinkedField",
"name": "policyVersion",
"plural": false,
"selections": [
(v2/*: any*/),
(v4/*: any*/),
(v5/*: any*/),
{
"alias": null,
"args": null,
"concreteType": "People",
"kind": "LinkedField",
"name": "publishedBy",
"plural": false,
"selections": [
(v6/*: any*/)
],
"storageKey": null
}
],
"storageKey": null
}
],
"storageKey": null
}
],
"type": "Mutation",
"abstractKey": null
},
"kind": "Request",
"operation": {
"argumentDefinitions": (v0/*: any*/),
"kind": "Operation",
"name": "ShowPolicyViewPublishMutation",
"selections": [
{
"alias": null,
"args": (v1/*: any*/),
"concreteType": "PublishPolicyVersionPayload",
"kind": "LinkedField",
"name": "publishPolicyVersion",
"plural": false,
"selections": [
(v3/*: any*/),
{
"alias": null,
"args": null,
"concreteType": "PolicyVersion",
"kind": "LinkedField",
"name": "policyVersion",
"plural": false,
"selections": [
(v2/*: any*/),
(v4/*: any*/),
(v5/*: any*/),
{
"alias": null,
"args": null,
"concreteType": "People",
"kind": "LinkedField",
"name": "publishedBy",
"plural": false,
"selections": [
(v6/*: any*/),
(v2/*: any*/)
],
"storageKey": null
}
],
"storageKey": null
}
],
"storageKey": null
}
]
},
"params": {
"cacheID": "2047a011abeae6642230cb7b31080b08",
"id": null,
"metadata": {},
"name": "ShowPolicyViewPublishMutation",
"operationKind": "mutation",
"text": "mutation ShowPolicyViewPublishMutation(\n $input: PublishPolicyVersionInput!\n) {\n publishPolicyVersion(input: $input) {\n policy {\n id\n currentPublishedVersion\n }\n policyVersion {\n id\n status\n publishedAt\n publishedBy {\n fullName\n id\n }\n }\n }\n}\n"
}
};
})();
(node as any).hash = "d81485a797858cf5f2834cf131d248cc";
export default node;

View File

@@ -0,0 +1,606 @@
/**
* @generated SignedSource<<351444982c4868267f30cc9fc8a2399a>>
* @lightSyntaxTransform
* @nogrep
*/
/* tslint:disable */
/* eslint-disable */
// @ts-nocheck
import { ConcreteRequest } from 'relay-runtime';
import { FragmentRefs } from "relay-runtime";
export type PolicyStatus = "DRAFT" | "PUBLISHED";
export type ShowPolicyViewQuery$variables = {
policyId: string;
};
export type ShowPolicyViewQuery$data = {
readonly node: {
readonly createdAt?: string;
readonly currentPublishedVersion?: number | null | undefined;
readonly description?: string;
readonly id: string;
readonly latestVersion?: {
readonly edges: ReadonlyArray<{
readonly node: {
readonly changelog: string;
readonly content: string;
readonly createdAt: string;
readonly id: string;
readonly publishedAt: string | null | undefined;
readonly publishedBy: {
readonly fullName: string;
} | null | undefined;
readonly status: PolicyStatus;
readonly updatedAt: string;
readonly version: number;
};
}>;
};
readonly owner?: {
readonly fullName: string;
readonly id: string;
readonly primaryEmailAddress: string;
};
readonly title?: string;
readonly updatedAt?: string;
readonly " $fragmentSpreads": FragmentRefs<"SignaturesModal_policyVersions" | "VersionHistoryModal_policyVersions">;
};
};
export type ShowPolicyViewQuery = {
response: ShowPolicyViewQuery$data;
variables: ShowPolicyViewQuery$variables;
};
const node: ConcreteRequest = (function(){
var v0 = [
{
"defaultValue": null,
"kind": "LocalArgument",
"name": "policyId"
}
],
v1 = [
{
"kind": "Variable",
"name": "id",
"variableName": "policyId"
}
],
v2 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "id",
"storageKey": null
},
v3 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "title",
"storageKey": null
},
v4 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "description",
"storageKey": null
},
v5 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "createdAt",
"storageKey": null
},
v6 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "updatedAt",
"storageKey": null
},
v7 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "currentPublishedVersion",
"storageKey": null
},
v8 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "fullName",
"storageKey": null
},
v9 = {
"alias": null,
"args": null,
"concreteType": "People",
"kind": "LinkedField",
"name": "owner",
"plural": false,
"selections": [
(v2/*: any*/),
(v8/*: any*/),
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "primaryEmailAddress",
"storageKey": null
}
],
"storageKey": null
},
v10 = [
{
"kind": "Literal",
"name": "first",
"value": 1
}
],
v11 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "version",
"storageKey": null
},
v12 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "status",
"storageKey": null
},
v13 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "content",
"storageKey": null
},
v14 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "changelog",
"storageKey": null
},
v15 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "publishedAt",
"storageKey": null
},
v16 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "__typename",
"storageKey": null
},
v17 = [
(v8/*: any*/),
(v2/*: any*/)
],
v18 = {
"alias": null,
"args": null,
"concreteType": "People",
"kind": "LinkedField",
"name": "publishedBy",
"plural": false,
"selections": (v17/*: any*/),
"storageKey": null
},
v19 = [
{
"kind": "Literal",
"name": "first",
"value": 100
}
];
return {
"fragment": {
"argumentDefinitions": (v0/*: any*/),
"kind": "Fragment",
"metadata": null,
"name": "ShowPolicyViewQuery",
"selections": [
{
"alias": null,
"args": (v1/*: any*/),
"concreteType": null,
"kind": "LinkedField",
"name": "node",
"plural": false,
"selections": [
(v2/*: any*/),
{
"kind": "InlineFragment",
"selections": [
(v3/*: any*/),
(v4/*: any*/),
(v5/*: any*/),
(v6/*: any*/),
(v7/*: any*/),
(v9/*: any*/),
{
"args": null,
"kind": "FragmentSpread",
"name": "SignaturesModal_policyVersions"
},
{
"args": null,
"kind": "FragmentSpread",
"name": "VersionHistoryModal_policyVersions"
},
{
"alias": "latestVersion",
"args": (v10/*: any*/),
"concreteType": "PolicyVersionConnection",
"kind": "LinkedField",
"name": "versions",
"plural": false,
"selections": [
{
"alias": null,
"args": null,
"concreteType": "PolicyVersionEdge",
"kind": "LinkedField",
"name": "edges",
"plural": true,
"selections": [
{
"alias": null,
"args": null,
"concreteType": "PolicyVersion",
"kind": "LinkedField",
"name": "node",
"plural": false,
"selections": [
(v2/*: any*/),
(v11/*: any*/),
(v12/*: any*/),
(v13/*: any*/),
(v14/*: any*/),
(v15/*: any*/),
{
"alias": null,
"args": null,
"concreteType": "People",
"kind": "LinkedField",
"name": "publishedBy",
"plural": false,
"selections": [
(v8/*: any*/)
],
"storageKey": null
},
(v5/*: any*/),
(v6/*: any*/)
],
"storageKey": null
}
],
"storageKey": null
}
],
"storageKey": "versions(first:1)"
}
],
"type": "Policy",
"abstractKey": null
}
],
"storageKey": null
}
],
"type": "Query",
"abstractKey": null
},
"kind": "Request",
"operation": {
"argumentDefinitions": (v0/*: any*/),
"kind": "Operation",
"name": "ShowPolicyViewQuery",
"selections": [
{
"alias": null,
"args": (v1/*: any*/),
"concreteType": null,
"kind": "LinkedField",
"name": "node",
"plural": false,
"selections": [
(v16/*: any*/),
(v2/*: any*/),
{
"kind": "InlineFragment",
"selections": [
(v3/*: any*/),
(v4/*: any*/),
(v5/*: any*/),
(v6/*: any*/),
(v7/*: any*/),
(v9/*: any*/),
{
"alias": "policyVersions",
"args": [
{
"kind": "Literal",
"name": "first",
"value": 10
}
],
"concreteType": "PolicyVersionConnection",
"kind": "LinkedField",
"name": "versions",
"plural": false,
"selections": [
{
"alias": null,
"args": null,
"concreteType": "PolicyVersionEdge",
"kind": "LinkedField",
"name": "edges",
"plural": true,
"selections": [
{
"alias": null,
"args": null,
"concreteType": "PolicyVersion",
"kind": "LinkedField",
"name": "node",
"plural": false,
"selections": [
(v2/*: any*/),
(v11/*: any*/),
(v12/*: any*/),
(v15/*: any*/),
(v6/*: any*/),
(v18/*: any*/),
{
"alias": null,
"args": (v19/*: any*/),
"concreteType": "PolicyVersionSignatureConnection",
"kind": "LinkedField",
"name": "signatures",
"plural": false,
"selections": [
{
"alias": null,
"args": null,
"concreteType": "PolicyVersionSignatureEdge",
"kind": "LinkedField",
"name": "edges",
"plural": true,
"selections": [
{
"alias": null,
"args": null,
"concreteType": "PolicyVersionSignature",
"kind": "LinkedField",
"name": "node",
"plural": false,
"selections": [
(v2/*: any*/),
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "state",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "signedAt",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "requestedAt",
"storageKey": null
},
{
"alias": null,
"args": null,
"concreteType": "People",
"kind": "LinkedField",
"name": "signedBy",
"plural": false,
"selections": (v17/*: any*/),
"storageKey": null
},
{
"alias": null,
"args": null,
"concreteType": "People",
"kind": "LinkedField",
"name": "requestedBy",
"plural": false,
"selections": (v17/*: any*/),
"storageKey": null
},
(v16/*: any*/)
],
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "cursor",
"storageKey": null
}
],
"storageKey": null
},
{
"alias": null,
"args": null,
"concreteType": "PageInfo",
"kind": "LinkedField",
"name": "pageInfo",
"plural": false,
"selections": [
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "endCursor",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "hasNextPage",
"storageKey": null
}
],
"storageKey": null
}
],
"storageKey": "signatures(first:100)"
},
{
"alias": null,
"args": (v19/*: any*/),
"filters": null,
"handle": "connection",
"key": "SignaturesModal_policyVersions_signatures",
"kind": "LinkedHandle",
"name": "signatures"
}
],
"storageKey": null
}
],
"storageKey": null
}
],
"storageKey": "versions(first:10)"
},
{
"alias": "versionHistory",
"args": [
{
"kind": "Literal",
"name": "first",
"value": 20
}
],
"concreteType": "PolicyVersionConnection",
"kind": "LinkedField",
"name": "versions",
"plural": false,
"selections": [
{
"alias": null,
"args": null,
"concreteType": "PolicyVersionEdge",
"kind": "LinkedField",
"name": "edges",
"plural": true,
"selections": [
{
"alias": null,
"args": null,
"concreteType": "PolicyVersion",
"kind": "LinkedField",
"name": "node",
"plural": false,
"selections": [
(v2/*: any*/),
(v11/*: any*/),
(v12/*: any*/),
(v13/*: any*/),
(v14/*: any*/),
(v15/*: any*/),
(v6/*: any*/),
(v18/*: any*/)
],
"storageKey": null
}
],
"storageKey": null
}
],
"storageKey": "versions(first:20)"
},
{
"alias": "latestVersion",
"args": (v10/*: any*/),
"concreteType": "PolicyVersionConnection",
"kind": "LinkedField",
"name": "versions",
"plural": false,
"selections": [
{
"alias": null,
"args": null,
"concreteType": "PolicyVersionEdge",
"kind": "LinkedField",
"name": "edges",
"plural": true,
"selections": [
{
"alias": null,
"args": null,
"concreteType": "PolicyVersion",
"kind": "LinkedField",
"name": "node",
"plural": false,
"selections": [
(v2/*: any*/),
(v11/*: any*/),
(v12/*: any*/),
(v13/*: any*/),
(v14/*: any*/),
(v15/*: any*/),
(v18/*: any*/),
(v5/*: any*/),
(v6/*: any*/)
],
"storageKey": null
}
],
"storageKey": null
}
],
"storageKey": "versions(first:1)"
}
],
"type": "Policy",
"abstractKey": null
}
],
"storageKey": null
}
]
},
"params": {
"cacheID": "751e6cc23218ca2da7b5bf44d3eef59c",
"id": null,
"metadata": {},
"name": "ShowPolicyViewQuery",
"operationKind": "query",
"text": "query ShowPolicyViewQuery(\n $policyId: ID!\n) {\n node(id: $policyId) {\n __typename\n id\n ... on Policy {\n title\n description\n createdAt\n updatedAt\n currentPublishedVersion\n owner {\n id\n fullName\n primaryEmailAddress\n }\n ...SignaturesModal_policyVersions\n ...VersionHistoryModal_policyVersions\n latestVersion: versions(first: 1) {\n edges {\n node {\n id\n version\n status\n content\n changelog\n publishedAt\n publishedBy {\n fullName\n id\n }\n createdAt\n updatedAt\n }\n }\n }\n }\n }\n}\n\nfragment SignaturesModal_policyVersions on Policy {\n title\n policyVersions: versions(first: 10) {\n edges {\n node {\n id\n version\n status\n publishedAt\n updatedAt\n publishedBy {\n fullName\n id\n }\n signatures(first: 100) {\n edges {\n node {\n id\n state\n signedAt\n requestedAt\n signedBy {\n fullName\n id\n }\n requestedBy {\n fullName\n id\n }\n __typename\n }\n cursor\n }\n pageInfo {\n endCursor\n hasNextPage\n }\n }\n }\n }\n }\n}\n\nfragment VersionHistoryModal_policyVersions on Policy {\n title\n owner {\n fullName\n id\n }\n versionHistory: versions(first: 20) {\n edges {\n node {\n id\n version\n status\n content\n changelog\n publishedAt\n updatedAt\n publishedBy {\n fullName\n id\n }\n }\n }\n }\n}\n"
}
};
})();
(node as any).hash = "58adb8690071648aecb252b2f807ea7d";
export default node;

View File

@@ -0,0 +1,206 @@
/**
* @generated SignedSource<<070580dfc3d76a6f1ca8b0227dc8c1c6>>
* @lightSyntaxTransform
* @nogrep
*/
/* tslint:disable */
/* eslint-disable */
// @ts-nocheck
import { ConcreteRequest } from 'relay-runtime';
export type PeopleKind = "CONTRACTOR" | "EMPLOYEE" | "SERVICE_ACCOUNT";
export type SignaturesModalOrganizationQuery$variables = {
organizationId: string;
};
export type SignaturesModalOrganizationQuery$data = {
readonly organization: {
readonly id?: string;
readonly peoples?: {
readonly edges: ReadonlyArray<{
readonly node: {
readonly fullName: string;
readonly id: string;
readonly kind: PeopleKind;
readonly primaryEmailAddress: string;
};
}>;
};
};
};
export type SignaturesModalOrganizationQuery = {
response: SignaturesModalOrganizationQuery$data;
variables: SignaturesModalOrganizationQuery$variables;
};
const node: ConcreteRequest = (function(){
var v0 = [
{
"defaultValue": null,
"kind": "LocalArgument",
"name": "organizationId"
}
],
v1 = [
{
"kind": "Variable",
"name": "id",
"variableName": "organizationId"
}
],
v2 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "id",
"storageKey": null
},
v3 = {
"alias": null,
"args": [
{
"kind": "Literal",
"name": "first",
"value": 100
},
{
"kind": "Literal",
"name": "orderBy",
"value": {
"direction": "ASC",
"field": "FULL_NAME"
}
}
],
"concreteType": "PeopleConnection",
"kind": "LinkedField",
"name": "peoples",
"plural": false,
"selections": [
{
"alias": null,
"args": null,
"concreteType": "PeopleEdge",
"kind": "LinkedField",
"name": "edges",
"plural": true,
"selections": [
{
"alias": null,
"args": null,
"concreteType": "People",
"kind": "LinkedField",
"name": "node",
"plural": false,
"selections": [
(v2/*: any*/),
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "fullName",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "primaryEmailAddress",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "kind",
"storageKey": null
}
],
"storageKey": null
}
],
"storageKey": null
}
],
"storageKey": "peoples(first:100,orderBy:{\"direction\":\"ASC\",\"field\":\"FULL_NAME\"})"
};
return {
"fragment": {
"argumentDefinitions": (v0/*: any*/),
"kind": "Fragment",
"metadata": null,
"name": "SignaturesModalOrganizationQuery",
"selections": [
{
"alias": "organization",
"args": (v1/*: any*/),
"concreteType": null,
"kind": "LinkedField",
"name": "node",
"plural": false,
"selections": [
{
"kind": "InlineFragment",
"selections": [
(v2/*: any*/),
(v3/*: any*/)
],
"type": "Organization",
"abstractKey": null
}
],
"storageKey": null
}
],
"type": "Query",
"abstractKey": null
},
"kind": "Request",
"operation": {
"argumentDefinitions": (v0/*: any*/),
"kind": "Operation",
"name": "SignaturesModalOrganizationQuery",
"selections": [
{
"alias": "organization",
"args": (v1/*: any*/),
"concreteType": null,
"kind": "LinkedField",
"name": "node",
"plural": false,
"selections": [
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "__typename",
"storageKey": null
},
(v2/*: any*/),
{
"kind": "InlineFragment",
"selections": [
(v3/*: any*/)
],
"type": "Organization",
"abstractKey": null
}
],
"storageKey": null
}
]
},
"params": {
"cacheID": "a2551c513c1c12a46c0da5328086475a",
"id": null,
"metadata": {},
"name": "SignaturesModalOrganizationQuery",
"operationKind": "query",
"text": "query SignaturesModalOrganizationQuery(\n $organizationId: ID!\n) {\n organization: node(id: $organizationId) {\n __typename\n ... on Organization {\n id\n peoples(first: 100, orderBy: {direction: ASC, field: FULL_NAME}) {\n edges {\n node {\n id\n fullName\n primaryEmailAddress\n kind\n }\n }\n }\n }\n id\n }\n}\n"
}
};
})();
(node as any).hash = "39bec552bdf8762487cec72839225b84";
export default node;

View File

@@ -0,0 +1,264 @@
/**
* @generated SignedSource<<fda822cf46d8b9a49852a53edd8e363d>>
* @lightSyntaxTransform
* @nogrep
*/
/* tslint:disable */
/* eslint-disable */
// @ts-nocheck
import { ConcreteRequest } from 'relay-runtime';
export type PolicyVersionSignatureState = "REQUESTED" | "SIGNED";
export type RequestSignatureInput = {
policyVersionId: string;
signatoryId: string;
};
export type SignaturesModalRequestSignatureMutation$variables = {
connections: ReadonlyArray<string>;
input: RequestSignatureInput;
};
export type SignaturesModalRequestSignatureMutation$data = {
readonly requestSignature: {
readonly policyVersionSignatureEdge: {
readonly node: {
readonly id: string;
readonly requestedAt: string;
readonly requestedBy: {
readonly fullName: string;
};
readonly signedAt: string | null | undefined;
readonly signedBy: {
readonly fullName: string;
readonly id: string;
};
readonly state: PolicyVersionSignatureState;
};
};
};
};
export type SignaturesModalRequestSignatureMutation = {
response: SignaturesModalRequestSignatureMutation$data;
variables: SignaturesModalRequestSignatureMutation$variables;
};
const node: ConcreteRequest = (function(){
var v0 = {
"defaultValue": null,
"kind": "LocalArgument",
"name": "connections"
},
v1 = {
"defaultValue": null,
"kind": "LocalArgument",
"name": "input"
},
v2 = [
{
"kind": "Variable",
"name": "input",
"variableName": "input"
}
],
v3 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "id",
"storageKey": null
},
v4 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "state",
"storageKey": null
},
v5 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "signedAt",
"storageKey": null
},
v6 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "requestedAt",
"storageKey": null
},
v7 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "fullName",
"storageKey": null
},
v8 = [
(v7/*: any*/),
(v3/*: any*/)
],
v9 = {
"alias": null,
"args": null,
"concreteType": "People",
"kind": "LinkedField",
"name": "signedBy",
"plural": false,
"selections": (v8/*: any*/),
"storageKey": null
};
return {
"fragment": {
"argumentDefinitions": [
(v0/*: any*/),
(v1/*: any*/)
],
"kind": "Fragment",
"metadata": null,
"name": "SignaturesModalRequestSignatureMutation",
"selections": [
{
"alias": null,
"args": (v2/*: any*/),
"concreteType": "RequestSignaturePayload",
"kind": "LinkedField",
"name": "requestSignature",
"plural": false,
"selections": [
{
"alias": null,
"args": null,
"concreteType": "PolicyVersionSignatureEdge",
"kind": "LinkedField",
"name": "policyVersionSignatureEdge",
"plural": false,
"selections": [
{
"alias": null,
"args": null,
"concreteType": "PolicyVersionSignature",
"kind": "LinkedField",
"name": "node",
"plural": false,
"selections": [
(v3/*: any*/),
(v4/*: any*/),
(v5/*: any*/),
(v6/*: any*/),
(v9/*: any*/),
{
"alias": null,
"args": null,
"concreteType": "People",
"kind": "LinkedField",
"name": "requestedBy",
"plural": false,
"selections": [
(v7/*: any*/)
],
"storageKey": null
}
],
"storageKey": null
}
],
"storageKey": null
}
],
"storageKey": null
}
],
"type": "Mutation",
"abstractKey": null
},
"kind": "Request",
"operation": {
"argumentDefinitions": [
(v1/*: any*/),
(v0/*: any*/)
],
"kind": "Operation",
"name": "SignaturesModalRequestSignatureMutation",
"selections": [
{
"alias": null,
"args": (v2/*: any*/),
"concreteType": "RequestSignaturePayload",
"kind": "LinkedField",
"name": "requestSignature",
"plural": false,
"selections": [
{
"alias": null,
"args": null,
"concreteType": "PolicyVersionSignatureEdge",
"kind": "LinkedField",
"name": "policyVersionSignatureEdge",
"plural": false,
"selections": [
{
"alias": null,
"args": null,
"concreteType": "PolicyVersionSignature",
"kind": "LinkedField",
"name": "node",
"plural": false,
"selections": [
(v3/*: any*/),
(v4/*: any*/),
(v5/*: any*/),
(v6/*: any*/),
(v9/*: any*/),
{
"alias": null,
"args": null,
"concreteType": "People",
"kind": "LinkedField",
"name": "requestedBy",
"plural": false,
"selections": (v8/*: any*/),
"storageKey": null
}
],
"storageKey": null
}
],
"storageKey": null
},
{
"alias": null,
"args": null,
"filters": null,
"handle": "prependEdge",
"key": "",
"kind": "LinkedHandle",
"name": "policyVersionSignatureEdge",
"handleArgs": [
{
"kind": "Variable",
"name": "connections",
"variableName": "connections"
}
]
}
],
"storageKey": null
}
]
},
"params": {
"cacheID": "4c6a5b6ed95c76c157f8627409a4481b",
"id": null,
"metadata": {},
"name": "SignaturesModalRequestSignatureMutation",
"operationKind": "mutation",
"text": "mutation SignaturesModalRequestSignatureMutation(\n $input: RequestSignatureInput!\n) {\n requestSignature(input: $input) {\n policyVersionSignatureEdge {\n node {\n id\n state\n signedAt\n requestedAt\n signedBy {\n fullName\n id\n }\n requestedBy {\n fullName\n id\n }\n }\n }\n }\n}\n"
}
};
})();
(node as any).hash = "2b17fe4feae2614a6697c63a7334a1cf";
export default node;

View File

@@ -0,0 +1,298 @@
/**
* @generated SignedSource<<ee81b03b50b0bc65932295b898b856d4>>
* @lightSyntaxTransform
* @nogrep
*/
/* tslint:disable */
/* eslint-disable */
// @ts-nocheck
import { ReaderFragment } from 'relay-runtime';
export type PolicyStatus = "DRAFT" | "PUBLISHED";
export type PolicyVersionSignatureState = "REQUESTED" | "SIGNED";
import { FragmentRefs } from "relay-runtime";
export type SignaturesModal_policyVersions$data = {
readonly policyVersions: {
readonly edges: ReadonlyArray<{
readonly node: {
readonly id: string;
readonly publishedAt: string | null | undefined;
readonly publishedBy: {
readonly fullName: string;
} | null | undefined;
readonly signatures: {
readonly edges: ReadonlyArray<{
readonly node: {
readonly id: string;
readonly requestedAt: string;
readonly requestedBy: {
readonly fullName: string;
};
readonly signedAt: string | null | undefined;
readonly signedBy: {
readonly fullName: string;
readonly id: string;
};
readonly state: PolicyVersionSignatureState;
};
}>;
};
readonly status: PolicyStatus;
readonly updatedAt: string;
readonly version: number;
};
}>;
};
readonly title: string;
readonly " $fragmentType": "SignaturesModal_policyVersions";
};
export type SignaturesModal_policyVersions$key = {
readonly " $data"?: SignaturesModal_policyVersions$data;
readonly " $fragmentSpreads": FragmentRefs<"SignaturesModal_policyVersions">;
};
const node: ReaderFragment = (function(){
var v0 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "id",
"storageKey": null
},
v1 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "fullName",
"storageKey": null
},
v2 = [
(v1/*: any*/)
];
return {
"argumentDefinitions": [],
"kind": "Fragment",
"metadata": {
"connection": [
{
"count": null,
"cursor": null,
"direction": "forward",
"path": null
}
]
},
"name": "SignaturesModal_policyVersions",
"selections": [
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "title",
"storageKey": null
},
{
"alias": "policyVersions",
"args": [
{
"kind": "Literal",
"name": "first",
"value": 10
}
],
"concreteType": "PolicyVersionConnection",
"kind": "LinkedField",
"name": "versions",
"plural": false,
"selections": [
{
"alias": null,
"args": null,
"concreteType": "PolicyVersionEdge",
"kind": "LinkedField",
"name": "edges",
"plural": true,
"selections": [
{
"alias": null,
"args": null,
"concreteType": "PolicyVersion",
"kind": "LinkedField",
"name": "node",
"plural": false,
"selections": [
(v0/*: any*/),
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "version",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "status",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "publishedAt",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "updatedAt",
"storageKey": null
},
{
"alias": null,
"args": null,
"concreteType": "People",
"kind": "LinkedField",
"name": "publishedBy",
"plural": false,
"selections": (v2/*: any*/),
"storageKey": null
},
{
"alias": "signatures",
"args": null,
"concreteType": "PolicyVersionSignatureConnection",
"kind": "LinkedField",
"name": "__SignaturesModal_policyVersions_signatures_connection",
"plural": false,
"selections": [
{
"alias": null,
"args": null,
"concreteType": "PolicyVersionSignatureEdge",
"kind": "LinkedField",
"name": "edges",
"plural": true,
"selections": [
{
"alias": null,
"args": null,
"concreteType": "PolicyVersionSignature",
"kind": "LinkedField",
"name": "node",
"plural": false,
"selections": [
(v0/*: any*/),
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "state",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "signedAt",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "requestedAt",
"storageKey": null
},
{
"alias": null,
"args": null,
"concreteType": "People",
"kind": "LinkedField",
"name": "signedBy",
"plural": false,
"selections": [
(v1/*: any*/),
(v0/*: any*/)
],
"storageKey": null
},
{
"alias": null,
"args": null,
"concreteType": "People",
"kind": "LinkedField",
"name": "requestedBy",
"plural": false,
"selections": (v2/*: any*/),
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "__typename",
"storageKey": null
}
],
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "cursor",
"storageKey": null
}
],
"storageKey": null
},
{
"alias": null,
"args": null,
"concreteType": "PageInfo",
"kind": "LinkedField",
"name": "pageInfo",
"plural": false,
"selections": [
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "endCursor",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "hasNextPage",
"storageKey": null
}
],
"storageKey": null
}
],
"storageKey": null
}
],
"storageKey": null
}
],
"storageKey": null
}
],
"storageKey": "versions(first:10)"
}
],
"type": "Policy",
"abstractKey": null
};
})();
(node as any).hash = "da9bf0fc12618afb0f9ec8f2337b9b9b";
export default node;

View File

@@ -0,0 +1,181 @@
/**
* @generated SignedSource<<21626cad90fcb579071d73bc47e3fb7a>>
* @lightSyntaxTransform
* @nogrep
*/
/* tslint:disable */
/* eslint-disable */
// @ts-nocheck
import { ReaderFragment } from 'relay-runtime';
export type PolicyStatus = "DRAFT" | "PUBLISHED";
import { FragmentRefs } from "relay-runtime";
export type VersionHistoryModal_policyVersions$data = {
readonly owner: {
readonly fullName: string;
};
readonly title: string;
readonly versionHistory: {
readonly edges: ReadonlyArray<{
readonly node: {
readonly changelog: string;
readonly content: string;
readonly id: string;
readonly publishedAt: string | null | undefined;
readonly publishedBy: {
readonly fullName: string;
} | null | undefined;
readonly status: PolicyStatus;
readonly updatedAt: string;
readonly version: number;
};
}>;
};
readonly " $fragmentType": "VersionHistoryModal_policyVersions";
};
export type VersionHistoryModal_policyVersions$key = {
readonly " $data"?: VersionHistoryModal_policyVersions$data;
readonly " $fragmentSpreads": FragmentRefs<"VersionHistoryModal_policyVersions">;
};
const node: ReaderFragment = (function(){
var v0 = [
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "fullName",
"storageKey": null
}
];
return {
"argumentDefinitions": [],
"kind": "Fragment",
"metadata": null,
"name": "VersionHistoryModal_policyVersions",
"selections": [
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "title",
"storageKey": null
},
{
"alias": null,
"args": null,
"concreteType": "People",
"kind": "LinkedField",
"name": "owner",
"plural": false,
"selections": (v0/*: any*/),
"storageKey": null
},
{
"alias": "versionHistory",
"args": [
{
"kind": "Literal",
"name": "first",
"value": 20
}
],
"concreteType": "PolicyVersionConnection",
"kind": "LinkedField",
"name": "versions",
"plural": false,
"selections": [
{
"alias": null,
"args": null,
"concreteType": "PolicyVersionEdge",
"kind": "LinkedField",
"name": "edges",
"plural": true,
"selections": [
{
"alias": null,
"args": null,
"concreteType": "PolicyVersion",
"kind": "LinkedField",
"name": "node",
"plural": false,
"selections": [
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "id",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "version",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "status",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "content",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "changelog",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "publishedAt",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "updatedAt",
"storageKey": null
},
{
"alias": null,
"args": null,
"concreteType": "People",
"kind": "LinkedField",
"name": "publishedBy",
"plural": false,
"selections": (v0/*: any*/),
"storageKey": null
}
],
"storageKey": null
}
],
"storageKey": null
}
],
"storageKey": "versions(first:20)"
}
],
"type": "Policy",
"abstractKey": null
};
})();
(node as any).hash = "09cd9142880cfbb239ce866ea81afe48";
export default node;

View File

@@ -101,8 +101,7 @@ const showRiskViewQuery = graphql`
edges {
node {
id
name
status
title
createdAt
}
}
@@ -155,8 +154,7 @@ const organizationPoliciesQuery = graphql`
edges {
node {
id
name
status
title
}
}
}
@@ -530,7 +528,7 @@ function ShowRiskViewContent({
return policies.filter((policy) => {
return (
!policySearchQuery ||
policy.name.toLowerCase().includes(policySearchQuery.toLowerCase())
policy.title.toLowerCase().includes(policySearchQuery.toLowerCase())
);
});
}, [getPolicies, policySearchQuery]);
@@ -731,7 +729,7 @@ function ShowRiskViewContent({
toast({
title: "Success",
description: `Linked policy "${policy.name}" to this risk.`,
description: `Linked policy "${policy.title}" to this risk.`,
});
},
onError: (error) => {
@@ -803,7 +801,7 @@ function ShowRiskViewContent({
toast({
title: "Success",
description: `Unlinked policy "${policy.name}" from this risk.`,
description: `Unlinked policy "${policy.title}" from this risk.`,
});
},
onError: (error) => {
@@ -1090,15 +1088,26 @@ function ShowRiskViewContent({
<TabsContent value="policies" className="space-y-4">
<div className="flex justify-between items-center mb-4">
<h2 className="text-xl font-semibold">Risk Policies</h2>
<Button
onClick={() => {
setIsPolicyDialogOpen(true);
loadPoliciesData();
}}
>
<Plus className="mr-2 h-4 w-4" />
Link Policy
</Button>
<div className="flex space-x-2">
<Button
variant="outline"
asChild
>
<Link to={`/organizations/${organizationId}/policies/new?riskId=${risk.id}`}>
<Plus className="mr-2 h-4 w-4" />
New Version
</Link>
</Button>
<Button
onClick={() => {
setIsPolicyDialogOpen(true);
loadPoliciesData();
}}
>
<Plus className="mr-2 h-4 w-4" />
Link Policy
</Button>
</div>
</div>
{policies.length > 0 ? (
<div className="rounded-md border">
@@ -1117,7 +1126,7 @@ function ShowRiskViewContent({
to={`/organizations/${organizationId}/policies/${policy.id}`}
className="font-medium text-blue-600 hover:underline"
>
{policy.name}
{policy.title}
</Link>
</TableCell>
<TableCell>
@@ -1350,7 +1359,7 @@ function ShowRiskViewContent({
>
<div className="flex justify-between items-center">
<div className="flex items-center gap-2">
<h3 className="font-medium">{policy.name}</h3>
<h3 className="font-medium">{policy.title}</h3>
</div>
{isLinked ? (
<Button

View File

@@ -1,5 +1,5 @@
/**
* @generated SignedSource<<86be887d4ec59050fd69900fd80863a2>>
* @generated SignedSource<<0335bb26d78f02170c02c7c2279a6bfd>>
* @lightSyntaxTransform
* @nogrep
*/
@@ -9,7 +9,6 @@
// @ts-nocheck
import { ConcreteRequest } from 'relay-runtime';
export type PolicyStatus = "ACTIVE" | "DRAFT";
export type ShowRiskViewOrganizationPoliciesQuery$variables = {
organizationId: string;
};
@@ -20,8 +19,7 @@ export type ShowRiskViewOrganizationPoliciesQuery$data = {
readonly edges: ReadonlyArray<{
readonly node: {
readonly id: string;
readonly name: string;
readonly status: PolicyStatus;
readonly title: string;
};
}>;
};
@@ -83,14 +81,7 @@ v4 = [
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "name",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "status",
"name": "title",
"storageKey": null
},
(v3/*: any*/)
@@ -228,7 +219,7 @@ return {
]
},
"params": {
"cacheID": "a229f1f805e79f8de378305e021d7fea",
"cacheID": "a92a6b2390e95e67b42fd18cc1030a9a",
"id": null,
"metadata": {
"connection": [
@@ -245,11 +236,11 @@ return {
},
"name": "ShowRiskViewOrganizationPoliciesQuery",
"operationKind": "query",
"text": "query ShowRiskViewOrganizationPoliciesQuery(\n $organizationId: ID!\n) {\n organization: node(id: $organizationId) {\n __typename\n id\n ... on Organization {\n policies(first: 100) {\n edges {\n node {\n id\n name\n status\n __typename\n }\n cursor\n }\n pageInfo {\n endCursor\n hasNextPage\n }\n }\n }\n }\n}\n"
"text": "query ShowRiskViewOrganizationPoliciesQuery(\n $organizationId: ID!\n) {\n organization: node(id: $organizationId) {\n __typename\n id\n ... on Organization {\n policies(first: 100) {\n edges {\n node {\n id\n title\n __typename\n }\n cursor\n }\n pageInfo {\n endCursor\n hasNextPage\n }\n }\n }\n }\n}\n"
}
};
})();
(node as any).hash = "26cbf19cd716aee7413931357b9c5354";
(node as any).hash = "b47adf6c3a247367a6b23dd161df2702";
export default node;

View File

@@ -1,5 +1,5 @@
/**
* @generated SignedSource<<e318c9e0fe4355eb4923acb54da59ebb>>
* @generated SignedSource<<5d1745636bcff5f05d946bcd613463e5>>
* @lightSyntaxTransform
* @nogrep
*/
@@ -10,7 +10,6 @@
import { ConcreteRequest } from 'relay-runtime';
export type MesureState = "IMPLEMENTED" | "IN_PROGRESS" | "NOT_APPLICABLE" | "NOT_STARTED";
export type PolicyStatus = "ACTIVE" | "DRAFT";
export type RiskTreatment = "ACCEPTED" | "AVOIDED" | "MITIGATED" | "TRANSFERRED";
export type ShowRiskViewQuery$variables = {
riskId: string;
@@ -56,8 +55,7 @@ export type ShowRiskViewQuery$data = {
readonly node: {
readonly createdAt: string;
readonly id: string;
readonly name: string;
readonly status: PolicyStatus;
readonly title: string;
};
}>;
};
@@ -285,12 +283,11 @@ v18 = [
"plural": false,
"selections": [
(v2/*: any*/),
(v3/*: any*/),
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "status",
"name": "title",
"storageKey": null
},
(v12/*: any*/),
@@ -517,7 +514,7 @@ return {
]
},
"params": {
"cacheID": "05f0fe48c916ecda883f606eed0c9244",
"cacheID": "a88eba4c398c292d41deaddd1d77bb3b",
"id": null,
"metadata": {
"connection": [
@@ -552,11 +549,11 @@ return {
},
"name": "ShowRiskViewQuery",
"operationKind": "query",
"text": "query ShowRiskViewQuery(\n $riskId: ID!\n) {\n node(id: $riskId) {\n __typename\n id\n ... on Risk {\n name\n description\n treatment\n owner {\n id\n fullName\n }\n inherentLikelihood\n inherentImpact\n residualLikelihood\n residualImpact\n note\n createdAt\n updatedAt\n mesures(first: 100) {\n edges {\n node {\n id\n name\n description\n category\n createdAt\n state\n __typename\n }\n cursor\n }\n pageInfo {\n endCursor\n hasNextPage\n }\n }\n policies(first: 100) {\n edges {\n node {\n id\n name\n status\n createdAt\n __typename\n }\n cursor\n }\n pageInfo {\n endCursor\n hasNextPage\n }\n }\n controls(first: 100) {\n edges {\n node {\n id\n referenceId\n name\n description\n createdAt\n __typename\n }\n cursor\n }\n pageInfo {\n endCursor\n hasNextPage\n }\n }\n }\n }\n}\n"
"text": "query ShowRiskViewQuery(\n $riskId: ID!\n) {\n node(id: $riskId) {\n __typename\n id\n ... on Risk {\n name\n description\n treatment\n owner {\n id\n fullName\n }\n inherentLikelihood\n inherentImpact\n residualLikelihood\n residualImpact\n note\n createdAt\n updatedAt\n mesures(first: 100) {\n edges {\n node {\n id\n name\n description\n category\n createdAt\n state\n __typename\n }\n cursor\n }\n pageInfo {\n endCursor\n hasNextPage\n }\n }\n policies(first: 100) {\n edges {\n node {\n id\n title\n createdAt\n __typename\n }\n cursor\n }\n pageInfo {\n endCursor\n hasNextPage\n }\n }\n controls(first: 100) {\n edges {\n node {\n id\n referenceId\n name\n description\n createdAt\n __typename\n }\n cursor\n }\n pageInfo {\n endCursor\n hasNextPage\n }\n }\n }\n }\n}\n"
}
};
})();
(node as any).hash = "25e67618b61999a955716e937a53a470";
(node as any).hash = "96158658ecb7c406fc969e086917c03e";
export default node;