@@ -27,6 +27,7 @@
|
|||||||
"@radix-ui/react-tooltip": "^1.1.8",
|
"@radix-ui/react-tooltip": "^1.1.8",
|
||||||
"class-variance-authority": "^0.7.1",
|
"class-variance-authority": "^0.7.1",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
|
"date-fns": "^4.1.0",
|
||||||
"fuse.js": "^7.1.0",
|
"fuse.js": "^7.1.0",
|
||||||
"lexical": "^0.27.0",
|
"lexical": "^0.27.0",
|
||||||
"lucide-react": "^0.475.0",
|
"lucide-react": "^0.475.0",
|
||||||
|
|||||||
@@ -1,15 +1,38 @@
|
|||||||
import { Suspense, useEffect } from "react";
|
import { Suspense, useEffect, useState } from "react";
|
||||||
import {
|
import {
|
||||||
graphql,
|
graphql,
|
||||||
PreloadedQuery,
|
PreloadedQuery,
|
||||||
usePreloadedQuery,
|
usePreloadedQuery,
|
||||||
useQueryLoader,
|
useQueryLoader,
|
||||||
} from "react-relay";
|
} from "react-relay";
|
||||||
import { Card, CardContent } from "@/components/ui/card";
|
import { Card, CardContent, CardFooter } from "@/components/ui/card";
|
||||||
import { Link, useParams } from "react-router";
|
import { Link, useParams } from "react-router";
|
||||||
import { Helmet } from "react-helmet-async";
|
import { Helmet } from "react-helmet-async";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { Plus, FileText } from "lucide-react";
|
import { Input } from "@/components/ui/input";
|
||||||
|
import {
|
||||||
|
Plus,
|
||||||
|
FileText,
|
||||||
|
Search,
|
||||||
|
Clock,
|
||||||
|
Filter,
|
||||||
|
ArrowUpDown,
|
||||||
|
} from "lucide-react";
|
||||||
|
import {
|
||||||
|
Select,
|
||||||
|
SelectContent,
|
||||||
|
SelectItem,
|
||||||
|
SelectTrigger,
|
||||||
|
SelectValue,
|
||||||
|
} from "@/components/ui/select";
|
||||||
|
import {
|
||||||
|
DropdownMenu,
|
||||||
|
DropdownMenuContent,
|
||||||
|
DropdownMenuItem,
|
||||||
|
DropdownMenuTrigger,
|
||||||
|
} from "@/components/ui/dropdown-menu";
|
||||||
|
import { Badge } from "@/components/ui/badge";
|
||||||
|
import { format } from "date-fns";
|
||||||
import type { PolicyListPageQuery as PolicyListPageQueryType } from "./__generated__/PolicyListPageQuery.graphql";
|
import type { PolicyListPageQuery as PolicyListPageQueryType } from "./__generated__/PolicyListPageQuery.graphql";
|
||||||
|
|
||||||
const PolicyListPageQuery = graphql`
|
const PolicyListPageQuery = graphql`
|
||||||
@@ -21,6 +44,7 @@ const PolicyListPageQuery = graphql`
|
|||||||
node {
|
node {
|
||||||
id
|
id
|
||||||
name
|
name
|
||||||
|
content
|
||||||
createdAt
|
createdAt
|
||||||
updatedAt
|
updatedAt
|
||||||
status
|
status
|
||||||
@@ -34,41 +58,102 @@ const PolicyListPageQuery = graphql`
|
|||||||
|
|
||||||
function PolicyCard({
|
function PolicyCard({
|
||||||
title,
|
title,
|
||||||
icon,
|
content,
|
||||||
status,
|
status,
|
||||||
|
updatedAt,
|
||||||
}: {
|
}: {
|
||||||
title: string;
|
title: string;
|
||||||
icon: React.ReactNode;
|
content?: string;
|
||||||
status?: string;
|
status?: string;
|
||||||
|
updatedAt: string;
|
||||||
}) {
|
}) {
|
||||||
return (
|
const formattedUpdatedAt = new Date(updatedAt);
|
||||||
<Card className="relative overflow-hidden border bg-card p-6">
|
|
||||||
<div className="flex flex-col gap-4">
|
|
||||||
<div className="size-16">{icon}</div>
|
|
||||||
|
|
||||||
<div className="space-y-2">
|
// Extract a short description from the content and strip HTML tags
|
||||||
<div className="flex items-center gap-2">
|
const stripHtmlTags = (html: string) => {
|
||||||
<h3 className="font-semibold">{title}</h3>
|
// First remove HTML tags
|
||||||
|
const withoutTags = html.replace(/<[^>]*>/g, "");
|
||||||
|
// Then decode HTML entities
|
||||||
|
const decoded = withoutTags
|
||||||
|
.replace(/&/g, "&")
|
||||||
|
.replace(/</g, "<")
|
||||||
|
.replace(/>/g, ">")
|
||||||
|
.replace(/"/g, '"')
|
||||||
|
.replace(/'/g, "'")
|
||||||
|
.replace(/ /g, " ");
|
||||||
|
// Remove markdown headers
|
||||||
|
return decoded.replace(/#.*?\n/, "").trim();
|
||||||
|
};
|
||||||
|
|
||||||
|
const description = content
|
||||||
|
? stripHtmlTags(content).substring(0, 120) +
|
||||||
|
(content.length > 120 ? "..." : "")
|
||||||
|
: "No description available";
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Card className="relative overflow-hidden border bg-card transition-all hover:shadow-md h-full flex flex-col">
|
||||||
|
<CardContent className="p-6 flex-grow">
|
||||||
|
<div className="flex flex-col h-full">
|
||||||
|
<div className="flex justify-between items-start mb-3">
|
||||||
|
<h3 className="font-semibold text-xl">{title}</h3>
|
||||||
{status && (
|
{status && (
|
||||||
<span
|
<Badge
|
||||||
className={`rounded-full px-2 py-0.5 text-xs ${
|
className={`${
|
||||||
status === "ACTIVE"
|
status === "ACTIVE"
|
||||||
? "bg-green-100 text-green-700"
|
? "bg-green-100 text-green-700 hover:bg-green-200"
|
||||||
: status === "DRAFT"
|
: status === "DRAFT"
|
||||||
? "bg-yellow-100 text-yellow-700"
|
? "bg-yellow-100 text-yellow-700 hover:bg-yellow-200"
|
||||||
: "bg-gray-100 text-gray-700"
|
: "bg-gray-100 text-gray-700 hover:bg-gray-200"
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
{status === "ACTIVE"
|
{status === "ACTIVE"
|
||||||
? "Active"
|
? "Security"
|
||||||
: status === "DRAFT"
|
: status === "DRAFT"
|
||||||
? "Draft"
|
? "Draft"
|
||||||
: status}
|
: status}
|
||||||
</span>
|
</Badge>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<p className="text-muted-foreground text-sm line-clamp-3 mb-4">
|
||||||
|
{description}
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div className="flex items-center gap-2 text-sm text-muted-foreground mt-auto">
|
||||||
|
<Clock className="h-4 w-4" />
|
||||||
|
<span>
|
||||||
|
Last updated: {format(formattedUpdatedAt, "yyyy-MM-dd")}
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
<CardFooter className="p-0 border-t">
|
||||||
|
<div className="w-full grid grid-cols-2">
|
||||||
|
<Button variant="ghost" className="rounded-none h-12 border-r">
|
||||||
|
<FileText className="h-5 w-5 mr-2" />
|
||||||
|
View Policy
|
||||||
|
</Button>
|
||||||
|
<Button variant="ghost" className="rounded-none h-12">
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
width="20"
|
||||||
|
height="20"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
strokeWidth="2"
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
className="mr-2"
|
||||||
|
>
|
||||||
|
<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
|
||||||
|
<polyline points="7 10 12 15 17 10" />
|
||||||
|
<line x1="12" y1="15" x2="12" y2="3" />
|
||||||
|
</svg>
|
||||||
|
Download
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</CardFooter>
|
||||||
</Card>
|
</Card>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -86,6 +171,53 @@ function PolicyListPageContent({
|
|||||||
const policies =
|
const policies =
|
||||||
data.organization.policies?.edges.map((edge) => edge?.node) ?? [];
|
data.organization.policies?.edges.map((edge) => edge?.node) ?? [];
|
||||||
|
|
||||||
|
// State for search, filtering and sorting
|
||||||
|
const [searchQuery, setSearchQuery] = useState("");
|
||||||
|
const [statusFilter, setStatusFilter] = useState("ALL");
|
||||||
|
const [sortBy, setSortBy] = useState("name-asc");
|
||||||
|
|
||||||
|
// Filter and sort policies
|
||||||
|
const filteredPolicies = policies
|
||||||
|
.filter((policy) => {
|
||||||
|
// Filter by search query
|
||||||
|
const matchesSearch = policy.name
|
||||||
|
.toLowerCase()
|
||||||
|
.includes(searchQuery.toLowerCase());
|
||||||
|
|
||||||
|
// Filter by status
|
||||||
|
const matchesStatus =
|
||||||
|
statusFilter === "ALL" || policy.status === statusFilter;
|
||||||
|
|
||||||
|
return matchesSearch && matchesStatus;
|
||||||
|
})
|
||||||
|
.sort((a, b) => {
|
||||||
|
// Sort policies
|
||||||
|
switch (sortBy) {
|
||||||
|
case "name-asc":
|
||||||
|
return a.name.localeCompare(b.name);
|
||||||
|
case "name-desc":
|
||||||
|
return b.name.localeCompare(a.name);
|
||||||
|
case "updated-desc":
|
||||||
|
return (
|
||||||
|
new Date(b.updatedAt).getTime() - new Date(a.updatedAt).getTime()
|
||||||
|
);
|
||||||
|
case "updated-asc":
|
||||||
|
return (
|
||||||
|
new Date(a.updatedAt).getTime() - new Date(b.updatedAt).getTime()
|
||||||
|
);
|
||||||
|
case "created-desc":
|
||||||
|
return (
|
||||||
|
new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime()
|
||||||
|
);
|
||||||
|
case "created-asc":
|
||||||
|
return (
|
||||||
|
new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime()
|
||||||
|
);
|
||||||
|
default:
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Helmet>
|
<Helmet>
|
||||||
@@ -106,25 +238,118 @@ function PolicyListPageContent({
|
|||||||
</Link>
|
</Link>
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Search and filter controls */}
|
||||||
|
<div className="flex flex-col md:flex-row gap-4 mb-6">
|
||||||
|
<div className="relative flex-1">
|
||||||
|
<Search className="absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-muted-foreground" />
|
||||||
|
<Input
|
||||||
|
placeholder="Search policies..."
|
||||||
|
className="pl-10"
|
||||||
|
value={searchQuery}
|
||||||
|
onChange={(e) => setSearchQuery(e.target.value)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex gap-2">
|
||||||
|
<Select value={statusFilter} onValueChange={setStatusFilter}>
|
||||||
|
<SelectTrigger className="w-[180px]">
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<Filter className="h-4 w-4" />
|
||||||
|
<SelectValue placeholder="Filter by status" />
|
||||||
|
</div>
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectItem value="ALL">All Statuses</SelectItem>
|
||||||
|
<SelectItem value="ACTIVE">Active</SelectItem>
|
||||||
|
<SelectItem value="DRAFT">Draft</SelectItem>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
|
||||||
|
<DropdownMenu>
|
||||||
|
<DropdownMenuTrigger asChild>
|
||||||
|
<Button variant="outline" className="flex items-center gap-2">
|
||||||
|
<ArrowUpDown className="h-4 w-4" />
|
||||||
|
Sort
|
||||||
|
</Button>
|
||||||
|
</DropdownMenuTrigger>
|
||||||
|
<DropdownMenuContent align="end">
|
||||||
|
<DropdownMenuItem onClick={() => setSortBy("name-asc")}>
|
||||||
|
Name (A-Z)
|
||||||
|
</DropdownMenuItem>
|
||||||
|
<DropdownMenuItem onClick={() => setSortBy("name-desc")}>
|
||||||
|
Name (Z-A)
|
||||||
|
</DropdownMenuItem>
|
||||||
|
<DropdownMenuItem onClick={() => setSortBy("updated-desc")}>
|
||||||
|
Recently Updated
|
||||||
|
</DropdownMenuItem>
|
||||||
|
<DropdownMenuItem onClick={() => setSortBy("updated-asc")}>
|
||||||
|
Oldest Updated
|
||||||
|
</DropdownMenuItem>
|
||||||
|
<DropdownMenuItem onClick={() => setSortBy("created-desc")}>
|
||||||
|
Recently Created
|
||||||
|
</DropdownMenuItem>
|
||||||
|
<DropdownMenuItem onClick={() => setSortBy("created-asc")}>
|
||||||
|
Oldest Created
|
||||||
|
</DropdownMenuItem>
|
||||||
|
</DropdownMenuContent>
|
||||||
|
</DropdownMenu>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Results summary */}
|
||||||
|
<div className="mb-4 text-sm text-muted-foreground">
|
||||||
|
Showing {filteredPolicies.length} of {policies.length} policies
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Policy grid */}
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
|
{filteredPolicies.length > 0 ? (
|
||||||
<div className="grid gap-4 md:grid-cols-2 lg:grid-cols-3">
|
<div className="grid gap-4 md:grid-cols-2 lg:grid-cols-3">
|
||||||
{policies.map((policy) => (
|
{filteredPolicies.map((policy) => (
|
||||||
<Link
|
<Link
|
||||||
key={policy.id}
|
key={policy.id}
|
||||||
to={`/organizations/${organizationId}/policies/${policy.id}`}
|
to={`/organizations/${organizationId}/policies/${policy.id}`}
|
||||||
|
className="group"
|
||||||
>
|
>
|
||||||
<PolicyCard
|
<PolicyCard
|
||||||
title={policy.name}
|
title={policy.name}
|
||||||
icon={
|
content={(policy as any).content}
|
||||||
<div className="flex size-full items-center justify-center rounded-full bg-blue-100">
|
|
||||||
<FileText className="h-8 w-8 text-blue-900" />
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
status={policy.status}
|
status={policy.status}
|
||||||
|
updatedAt={policy.updatedAt}
|
||||||
/>
|
/>
|
||||||
</Link>
|
</Link>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
) : (
|
||||||
|
<div className="text-center py-12 border rounded-lg bg-muted/20">
|
||||||
|
<FileText className="mx-auto h-8 w-8 text-muted-foreground mb-3" />
|
||||||
|
<h3 className="text-lg font-medium">No policies found</h3>
|
||||||
|
<p className="text-muted-foreground mb-4">
|
||||||
|
{searchQuery || statusFilter !== "ALL"
|
||||||
|
? "Try adjusting your search or filters"
|
||||||
|
: "Create your first policy to get started"}
|
||||||
|
</p>
|
||||||
|
{searchQuery || statusFilter !== "ALL" ? (
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
onClick={() => {
|
||||||
|
setSearchQuery("");
|
||||||
|
setStatusFilter("ALL");
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Clear filters
|
||||||
|
</Button>
|
||||||
|
) : (
|
||||||
|
<Button asChild>
|
||||||
|
<Link to={`/organizations/${organizationId}/policies/create`}>
|
||||||
|
<Plus className="mr-2 h-4 w-4" />
|
||||||
|
Create Policy
|
||||||
|
</Link>
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
@@ -133,22 +358,52 @@ function PolicyListPageContent({
|
|||||||
|
|
||||||
function PolicyListPageFallback() {
|
function PolicyListPageFallback() {
|
||||||
return (
|
return (
|
||||||
<div className="space-y-6">
|
<div className="container mx-auto py-6">
|
||||||
|
<div className="flex justify-between items-center mb-6">
|
||||||
<div>
|
<div>
|
||||||
<div className="h-8 w-48 bg-muted animate-pulse rounded" />
|
<div className="h-8 w-48 bg-muted animate-pulse rounded" />
|
||||||
<div className="h-4 w-96 bg-muted animate-pulse rounded mt-1" />
|
<div className="h-4 w-96 bg-muted animate-pulse rounded mt-1" />
|
||||||
</div>
|
</div>
|
||||||
<div className="grid gap-6 md:grid-cols-2 lg:grid-cols-3">
|
<div className="h-10 w-36 bg-muted animate-pulse rounded" />
|
||||||
{[1, 2, 3].map((i) => (
|
</div>
|
||||||
<Card key={i} className="bg-card/50">
|
|
||||||
<CardContent className="p-6">
|
{/* Search and filter controls skeleton */}
|
||||||
<div className="relative mb-6">
|
<div className="flex flex-col md:flex-row gap-4 mb-6">
|
||||||
<div className="bg-muted w-24 h-24 rounded-full animate-pulse mb-4" />
|
<div className="flex-1 h-10 bg-muted animate-pulse rounded" />
|
||||||
<div className="h-6 w-48 bg-muted animate-pulse rounded mb-2" />
|
<div className="flex gap-2">
|
||||||
<div className="h-20 w-full bg-muted animate-pulse rounded" />
|
<div className="h-10 w-[180px] bg-muted animate-pulse rounded" />
|
||||||
|
<div className="h-10 w-24 bg-muted animate-pulse rounded" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Results summary skeleton */}
|
||||||
|
<div className="mb-4 h-4 w-48 bg-muted animate-pulse rounded" />
|
||||||
|
|
||||||
|
{/* Policy grid skeleton */}
|
||||||
|
<div className="grid gap-4 md:grid-cols-2 lg:grid-cols-3">
|
||||||
|
{[1, 2, 3, 4, 5, 6].map((i) => (
|
||||||
|
<Card key={i} className="bg-card/50 h-full flex flex-col">
|
||||||
|
<CardContent className="p-6 flex-grow">
|
||||||
|
<div className="flex justify-between items-start mb-3">
|
||||||
|
<div className="h-7 w-48 bg-muted animate-pulse rounded" />
|
||||||
|
<div className="h-6 w-20 bg-muted animate-pulse rounded-full" />
|
||||||
|
</div>
|
||||||
|
<div className="space-y-2 mb-4">
|
||||||
|
<div className="h-4 w-full bg-muted animate-pulse rounded" />
|
||||||
|
<div className="h-4 w-full bg-muted animate-pulse rounded" />
|
||||||
|
<div className="h-4 w-2/3 bg-muted animate-pulse rounded" />
|
||||||
|
</div>
|
||||||
|
<div className="mt-auto space-y-2">
|
||||||
|
<div className="h-4 w-40 bg-muted animate-pulse rounded" />
|
||||||
|
<div className="h-4 w-40 bg-muted animate-pulse rounded" />
|
||||||
</div>
|
</div>
|
||||||
<div className="h-4 w-32 bg-muted animate-pulse rounded" />
|
|
||||||
</CardContent>
|
</CardContent>
|
||||||
|
<CardFooter className="p-0 border-t">
|
||||||
|
<div className="w-full grid grid-cols-2">
|
||||||
|
<div className="h-12 border-r bg-muted/20 animate-pulse" />
|
||||||
|
<div className="h-12 bg-muted/20 animate-pulse" />
|
||||||
|
</div>
|
||||||
|
</CardFooter>
|
||||||
</Card>
|
</Card>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/**
|
/**
|
||||||
* @generated SignedSource<<4225a13c40221ef783ffebcfda157db9>>
|
* @generated SignedSource<<a49c4b1ca92e109b1091a1431fe0c871>>
|
||||||
* @lightSyntaxTransform
|
* @lightSyntaxTransform
|
||||||
* @nogrep
|
* @nogrep
|
||||||
*/
|
*/
|
||||||
@@ -18,6 +18,7 @@ export type PolicyListPageQuery$data = {
|
|||||||
readonly policies?: {
|
readonly policies?: {
|
||||||
readonly edges: ReadonlyArray<{
|
readonly edges: ReadonlyArray<{
|
||||||
readonly node: {
|
readonly node: {
|
||||||
|
readonly content: string;
|
||||||
readonly createdAt: string;
|
readonly createdAt: string;
|
||||||
readonly id: string;
|
readonly id: string;
|
||||||
readonly name: string;
|
readonly name: string;
|
||||||
@@ -87,6 +88,13 @@ v4 = [
|
|||||||
"name": "name",
|
"name": "name",
|
||||||
"storageKey": null
|
"storageKey": null
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"alias": null,
|
||||||
|
"args": null,
|
||||||
|
"kind": "ScalarField",
|
||||||
|
"name": "content",
|
||||||
|
"storageKey": null
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"alias": null,
|
"alias": null,
|
||||||
"args": null,
|
"args": null,
|
||||||
@@ -242,7 +250,7 @@ return {
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"params": {
|
"params": {
|
||||||
"cacheID": "abc2b7659c3b58aa3df934d4ee3aa78a",
|
"cacheID": "5aa6e466b6daa957aa9c582530e385ec",
|
||||||
"id": null,
|
"id": null,
|
||||||
"metadata": {
|
"metadata": {
|
||||||
"connection": [
|
"connection": [
|
||||||
@@ -259,11 +267,11 @@ return {
|
|||||||
},
|
},
|
||||||
"name": "PolicyListPageQuery",
|
"name": "PolicyListPageQuery",
|
||||||
"operationKind": "query",
|
"operationKind": "query",
|
||||||
"text": "query PolicyListPageQuery(\n $organizationId: ID!\n) {\n organization: node(id: $organizationId) {\n __typename\n ... on Organization {\n policies(first: 25) {\n edges {\n node {\n id\n name\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 PolicyListPageQuery(\n $organizationId: ID!\n) {\n organization: node(id: $organizationId) {\n __typename\n ... on Organization {\n policies(first: 25) {\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"
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
})();
|
})();
|
||||||
|
|
||||||
(node as any).hash = "75262b569185ae2e6bfda8c19fd7ac9d";
|
(node as any).hash = "853e3413fd1b3f8781b11a1b7679c08f";
|
||||||
|
|
||||||
export default node;
|
export default node;
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/**
|
/**
|
||||||
* @generated SignedSource<<0d5fd744f67f5e3c2d357a41e392acfb>>
|
* @generated SignedSource<<be4844f007d260c332082a01fe125a57>>
|
||||||
* @lightSyntaxTransform
|
* @lightSyntaxTransform
|
||||||
* @nogrep
|
* @nogrep
|
||||||
*/
|
*/
|
||||||
@@ -16,11 +16,11 @@ export type PolicyOverviewPageQuery$variables = {
|
|||||||
export type PolicyOverviewPageQuery$data = {
|
export type PolicyOverviewPageQuery$data = {
|
||||||
readonly node: {
|
readonly node: {
|
||||||
readonly content?: string;
|
readonly content?: string;
|
||||||
readonly createdAt?: any;
|
readonly createdAt?: string;
|
||||||
readonly id: string;
|
readonly id: string;
|
||||||
readonly name?: string;
|
readonly name?: string;
|
||||||
readonly status?: PolicyStatus;
|
readonly status?: PolicyStatus;
|
||||||
readonly updatedAt?: any;
|
readonly updatedAt?: string;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
export type PolicyOverviewPageQuery = {
|
export type PolicyOverviewPageQuery = {
|
||||||
|
|||||||
11
package-lock.json
generated
11
package-lock.json
generated
@@ -34,6 +34,7 @@
|
|||||||
"@radix-ui/react-tooltip": "^1.1.8",
|
"@radix-ui/react-tooltip": "^1.1.8",
|
||||||
"class-variance-authority": "^0.7.1",
|
"class-variance-authority": "^0.7.1",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
|
"date-fns": "^4.1.0",
|
||||||
"fuse.js": "^7.1.0",
|
"fuse.js": "^7.1.0",
|
||||||
"lexical": "^0.27.0",
|
"lexical": "^0.27.0",
|
||||||
"lucide-react": "^0.475.0",
|
"lucide-react": "^0.475.0",
|
||||||
@@ -5063,6 +5064,16 @@
|
|||||||
"url": "https://github.com/sponsors/ljharb"
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/date-fns": {
|
||||||
|
"version": "4.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/date-fns/-/date-fns-4.1.0.tgz",
|
||||||
|
"integrity": "sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"funding": {
|
||||||
|
"type": "github",
|
||||||
|
"url": "https://github.com/sponsors/kossnocorp"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/debug": {
|
"node_modules/debug": {
|
||||||
"version": "4.4.0",
|
"version": "4.4.0",
|
||||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz",
|
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz",
|
||||||
|
|||||||
Reference in New Issue
Block a user