Update remaining page headers
Signed-off-by: Émile Ré <nemile.re@gmail.com>
This commit is contained in:
@@ -291,6 +291,7 @@ function BreadcrumbControlOverview() {
|
||||
{data.control?.name}
|
||||
</BreadCrumbNavLink>
|
||||
</BreadcrumbItem>
|
||||
<Outlet />
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -306,6 +307,17 @@ function BreadcrumbCreateControl() {
|
||||
);
|
||||
}
|
||||
|
||||
function BreadcrumbUpdateControl() {
|
||||
return (
|
||||
<>
|
||||
<BreadcrumbSeparator />
|
||||
<BreadcrumbItem>
|
||||
<BreadcrumbPage>Update Control</BreadcrumbPage>
|
||||
</BreadcrumbItem>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
function BreadcrumbPolicyList() {
|
||||
const { organizationId } = useParams();
|
||||
return (
|
||||
@@ -413,7 +425,12 @@ export default function ConsoleLayout() {
|
||||
<BreadcrumbControlOverview />
|
||||
</Suspense>
|
||||
}
|
||||
/>
|
||||
>
|
||||
<Route
|
||||
path="update"
|
||||
element={<BreadcrumbUpdateControl />}
|
||||
/>
|
||||
</Route>
|
||||
<Route
|
||||
path="update"
|
||||
element={<BreadcrumbUpdateFramework />}
|
||||
|
||||
@@ -75,6 +75,7 @@ import type { ControlOverviewPageAssignTaskMutation as ControlOverviewPageAssign
|
||||
import type { ControlOverviewPageUnassignTaskMutation as ControlOverviewPageUnassignTaskMutationType } from "./__generated__/ControlOverviewPageUnassignTaskMutation.graphql";
|
||||
import type { ControlOverviewPageOrganizationQuery$data } from "./__generated__/ControlOverviewPageOrganizationQuery.graphql";
|
||||
import type { ControlOverviewPageUpdateControlStateMutation as ControlOverviewPageUpdateControlStateMutationType } from "./__generated__/ControlOverviewPageUpdateControlStateMutation.graphql";
|
||||
import { PageHeader } from "./PageHeader";
|
||||
|
||||
// Function to format ISO8601 duration to human-readable format
|
||||
const formatDuration = (isoDuration: string): string => {
|
||||
@@ -1131,10 +1132,11 @@ function ControlOverviewPageContent({
|
||||
<Helmet>
|
||||
<title>{data.control.name || "Control"} - Probo</title>
|
||||
</Helmet>
|
||||
<div className="min-h-screen bg-white p-6 space-y-6">
|
||||
<div className="space-y-4 mb-8">
|
||||
<div className="flex items-center justify-between">
|
||||
<h1 className="text-2xl font-semibold">{data.control.name}</h1>
|
||||
<div className="container">
|
||||
<PageHeader
|
||||
className="mb-17"
|
||||
title={data.control.name ?? ""}
|
||||
actions={
|
||||
<div className="flex items-center gap-2">
|
||||
<Button variant="outline" size="sm" onClick={handleEditControl}>
|
||||
Edit Control
|
||||
@@ -1167,7 +1169,9 @@ function ControlOverviewPageContent({
|
||||
{formatImportance(data.control.importance)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
<div className="space-y-4 mb-8">
|
||||
<Card className="mt-4">
|
||||
<CardContent className="pt-6">
|
||||
<div className="prose prose-gray prose-sm md:prose-base text-gray-600 max-w-3xl">
|
||||
|
||||
@@ -21,6 +21,7 @@ import {
|
||||
CreateControlPageCreateControlMutation,
|
||||
ControlImportance,
|
||||
} from "./__generated__/CreateControlPageCreateControlMutation.graphql";
|
||||
import { PageHeader } from "./PageHeader";
|
||||
|
||||
const createControlMutation = graphql`
|
||||
mutation CreateControlPageCreateControlMutation(
|
||||
@@ -185,13 +186,12 @@ function CreateControlPageContent() {
|
||||
<Helmet>
|
||||
<title>Create Control - Probo</title>
|
||||
</Helmet>
|
||||
<div className="container mx-auto py-6">
|
||||
<div className="mb-6">
|
||||
<h1 className="text-2xl font-bold">Create Control</h1>
|
||||
<p className="text-muted-foreground">
|
||||
Create a new control for your framework
|
||||
</p>
|
||||
</div>
|
||||
<div className="container">
|
||||
<PageHeader
|
||||
className="mb-17"
|
||||
title="Create Control"
|
||||
description="Create a new control for your framework"
|
||||
/>
|
||||
|
||||
<Card className="max-w-2xl">
|
||||
<form onSubmit={handleSubmit} className="p-6 space-y-6">
|
||||
|
||||
@@ -10,6 +10,7 @@ import { useToast } from "@/hooks/use-toast";
|
||||
import { HelpCircle } from "lucide-react";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { CreatePeoplePageCreatePeopleMutation } from "./__generated__/CreatePeoplePageCreatePeopleMutation.graphql";
|
||||
import { PageHeader } from "./PageHeader";
|
||||
|
||||
const createPeopleMutation = graphql`
|
||||
mutation CreatePeoplePageCreatePeopleMutation(
|
||||
@@ -153,10 +154,15 @@ function CreatePeoplePageContent() {
|
||||
<Helmet>
|
||||
<title>Create Person - Probo Console</title>
|
||||
</Helmet>
|
||||
<div className="container">
|
||||
<PageHeader
|
||||
className="mb-17"
|
||||
title="Add a Person"
|
||||
description="Add a new person interacting with organization"
|
||||
/>
|
||||
|
||||
<form onSubmit={handleSubmit}>
|
||||
<div className="space-y-6 p-4 md:p-6 lg:p-8">
|
||||
<div className="mx-auto max-w-4xl space-y-6">
|
||||
<form onSubmit={handleSubmit}>
|
||||
<div className="max-w-4xl space-y-6">
|
||||
<EditableField
|
||||
label="Full Name"
|
||||
value={formData.fullName}
|
||||
@@ -291,19 +297,23 @@ function CreatePeoplePageContent() {
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
<div className="fixed bottom-6 right-6 flex gap-2">
|
||||
<Button type="button" variant="outline" onClick={() => navigate(-1)}>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
type="submit"
|
||||
className="bg-primary text-primary-foreground hover:bg-primary/90"
|
||||
>
|
||||
Create Person
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
<div className="fixed bottom-6 right-6 flex gap-2">
|
||||
<Button
|
||||
type="button"
|
||||
variant="outline"
|
||||
onClick={() => navigate(-1)}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
type="submit"
|
||||
className="bg-primary text-primary-foreground hover:bg-primary/90"
|
||||
>
|
||||
Create Person
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -14,13 +14,14 @@ 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 { FileText, Calendar, User } from "lucide-react";
|
||||
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 { CreatePolicyPageMutation } from "./__generated__/CreatePolicyPageMutation.graphql";
|
||||
import type { CreatePolicyPageQuery as CreatePolicyPageQueryType } from "./__generated__/CreatePolicyPageQuery.graphql";
|
||||
import { PageHeader } from "./PageHeader";
|
||||
|
||||
const CreatePolicyQuery = graphql`
|
||||
query CreatePolicyPageQuery($organizationId: ID!) {
|
||||
@@ -159,20 +160,12 @@ function CreatePolicyForm({
|
||||
<Helmet>
|
||||
<title>Create Policy - Probo Console</title>
|
||||
</Helmet>
|
||||
<div className="container mx-auto py-6">
|
||||
<div className="flex items-center mb-6">
|
||||
<div className="mr-4">
|
||||
<div className="flex h-12 w-12 items-center justify-center rounded-lg bg-primary/10">
|
||||
<FileText className="h-6 w-6 text-primary" />
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<h1 className="text-2xl font-bold">Create Policy</h1>
|
||||
<p className="text-muted-foreground">
|
||||
Create a new policy for your organization
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="container">
|
||||
<PageHeader
|
||||
className="mb-17"
|
||||
title="Create Policy"
|
||||
description="Create a new policy for your organization"
|
||||
/>
|
||||
|
||||
<form onSubmit={handleSubmit}>
|
||||
<div className="grid gap-6">
|
||||
|
||||
@@ -3,14 +3,16 @@ import { ReactNode } from "react";
|
||||
|
||||
export function PageHeader({
|
||||
className,
|
||||
children,
|
||||
title,
|
||||
actions,
|
||||
description,
|
||||
}: {
|
||||
className?: string;
|
||||
children?: ReactNode;
|
||||
title: string;
|
||||
actions?: ReactNode;
|
||||
description: string;
|
||||
description?: string;
|
||||
}) {
|
||||
return (
|
||||
<div className={cn("space-y-6", className)}>
|
||||
@@ -22,7 +24,8 @@ export function PageHeader({
|
||||
) : (
|
||||
<PageHeading title={title} />
|
||||
)}
|
||||
<PageDescription>{description}</PageDescription>
|
||||
{description && <PageDescription>{description}</PageDescription>}
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ import type { PeopleOverviewPageQuery as PeopleOverviewPageQueryType } from "./_
|
||||
import { useParams } from "react-router";
|
||||
import { Helmet } from "react-helmet-async";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { PageHeader } from "./PageHeader";
|
||||
|
||||
const peopleOverviewPageQuery = graphql`
|
||||
query PeopleOverviewPageQuery($peopleId: ID!) {
|
||||
@@ -161,140 +162,156 @@ function PeopleOverviewPageContent({
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="space-y-6 p-4 md:p-6 lg:p-8">
|
||||
<div className="mx-auto max-w-4xl space-y-6">
|
||||
<EditableField
|
||||
label="Full Name"
|
||||
value={formData.fullName}
|
||||
onChange={(value) => handleFieldChange("fullName", value)}
|
||||
/>
|
||||
<Helmet>
|
||||
<title>Person - Probo</title>
|
||||
</Helmet>
|
||||
<div className="container">
|
||||
<PageHeader className="mb-17" title={formData.fullName} />
|
||||
<div className="space-y-6">
|
||||
<div className="mx-auto max-w-4xl space-y-6">
|
||||
<EditableField
|
||||
label="Full Name"
|
||||
value={formData.fullName}
|
||||
onChange={(value) => handleFieldChange("fullName", value)}
|
||||
/>
|
||||
|
||||
<EditableField
|
||||
label="Primary Email"
|
||||
value={formData.primaryEmailAddress}
|
||||
type="email"
|
||||
onChange={(value) =>
|
||||
handleFieldChange("primaryEmailAddress", value)
|
||||
}
|
||||
/>
|
||||
<EditableField
|
||||
label="Primary Email"
|
||||
value={formData.primaryEmailAddress}
|
||||
type="email"
|
||||
onChange={(value) =>
|
||||
handleFieldChange("primaryEmailAddress", value)
|
||||
}
|
||||
/>
|
||||
|
||||
<div className="space-y-2">
|
||||
<div className="flex items-center gap-2">
|
||||
<HelpCircle className="h-4 w-4 text-gray-400" />
|
||||
<Label className="text-sm">Additional Email Addresses</Label>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
{formData.additionalEmailAddresses.map((email, index) => (
|
||||
<div key={index} className="flex gap-2">
|
||||
<Input
|
||||
type="email"
|
||||
value={email}
|
||||
onChange={(e) => {
|
||||
const newEmails = [...formData.additionalEmailAddresses];
|
||||
newEmails[index] = e.target.value;
|
||||
handleFieldChange("additionalEmailAddresses", newEmails);
|
||||
}}
|
||||
/>
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={() => {
|
||||
const newEmails =
|
||||
formData.additionalEmailAddresses.filter(
|
||||
(_, i) => i !== index
|
||||
);
|
||||
handleFieldChange("additionalEmailAddresses", newEmails);
|
||||
}}
|
||||
>
|
||||
Remove
|
||||
</Button>
|
||||
</div>
|
||||
))}
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={() => {
|
||||
handleFieldChange("additionalEmailAddresses", [
|
||||
...formData.additionalEmailAddresses,
|
||||
"",
|
||||
]);
|
||||
}}
|
||||
>
|
||||
Add Email
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Card className="p-6">
|
||||
<div className="space-y-4">
|
||||
<div className="space-y-2">
|
||||
<h2 className="text-lg font-medium">Additional Information</h2>
|
||||
<p className="text-sm text-gray-500">
|
||||
Additional details about the person
|
||||
</p>
|
||||
<div className="flex items-center gap-2">
|
||||
<HelpCircle className="h-4 w-4 text-gray-400" />
|
||||
<Label className="text-sm">Additional Email Addresses</Label>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
{formData.additionalEmailAddresses.map((email, index) => (
|
||||
<div key={index} className="flex gap-2">
|
||||
<Input
|
||||
type="email"
|
||||
value={email}
|
||||
onChange={(e) => {
|
||||
const newEmails = [
|
||||
...formData.additionalEmailAddresses,
|
||||
];
|
||||
newEmails[index] = e.target.value;
|
||||
handleFieldChange(
|
||||
"additionalEmailAddresses",
|
||||
newEmails
|
||||
);
|
||||
}}
|
||||
/>
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={() => {
|
||||
const newEmails =
|
||||
formData.additionalEmailAddresses.filter(
|
||||
(_, i) => i !== index
|
||||
);
|
||||
handleFieldChange(
|
||||
"additionalEmailAddresses",
|
||||
newEmails
|
||||
);
|
||||
}}
|
||||
>
|
||||
Remove
|
||||
</Button>
|
||||
</div>
|
||||
))}
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={() => {
|
||||
handleFieldChange("additionalEmailAddresses", [
|
||||
...formData.additionalEmailAddresses,
|
||||
"",
|
||||
]);
|
||||
}}
|
||||
>
|
||||
Add Email
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Card className="p-6">
|
||||
<div className="space-y-4">
|
||||
<div className="space-y-2">
|
||||
<div className="flex items-center gap-2">
|
||||
<HelpCircle className="h-4 w-4 text-gray-400" />
|
||||
<Label className="text-sm">Kind</Label>
|
||||
</div>
|
||||
<div className="flex gap-2">
|
||||
<button
|
||||
onClick={() => handleFieldChange("kind", "EMPLOYEE")}
|
||||
className={cn(
|
||||
"rounded-full px-4 py-1 text-sm transition-colors",
|
||||
formData.kind === "EMPLOYEE"
|
||||
? "bg-blue-100 text-blue-900 ring-2 ring-blue-600 ring-offset-2"
|
||||
: "bg-gray-100 text-gray-900 hover:bg-gray-200"
|
||||
)}
|
||||
>
|
||||
Employee
|
||||
</button>
|
||||
<button
|
||||
onClick={() => handleFieldChange("kind", "CONTRACTOR")}
|
||||
className={cn(
|
||||
"rounded-full px-4 py-1 text-sm transition-colors",
|
||||
formData.kind === "CONTRACTOR"
|
||||
? "bg-purple-100 text-purple-900 ring-2 ring-purple-600 ring-offset-2"
|
||||
: "bg-gray-100 text-gray-900 hover:bg-gray-200"
|
||||
)}
|
||||
>
|
||||
Contractor
|
||||
</button>
|
||||
<button
|
||||
onClick={() =>
|
||||
handleFieldChange("kind", "SERVICE_ACCOUNT")
|
||||
}
|
||||
className={cn(
|
||||
"rounded-full px-4 py-1 text-sm transition-colors",
|
||||
formData.kind === "SERVICE_ACCOUNT"
|
||||
? "bg-green-100 text-green-900 ring-2 ring-green-600 ring-offset-2"
|
||||
: "bg-gray-100 text-gray-900 hover:bg-gray-200"
|
||||
)}
|
||||
>
|
||||
Service Account
|
||||
</button>
|
||||
<h2 className="text-lg font-medium">
|
||||
Additional Information
|
||||
</h2>
|
||||
<p className="text-sm text-gray-500">
|
||||
Additional details about the person
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="space-y-4">
|
||||
<div className="space-y-2">
|
||||
<div className="flex items-center gap-2">
|
||||
<HelpCircle className="h-4 w-4 text-gray-400" />
|
||||
<Label className="text-sm">Kind</Label>
|
||||
</div>
|
||||
<div className="flex gap-2">
|
||||
<button
|
||||
onClick={() => handleFieldChange("kind", "EMPLOYEE")}
|
||||
className={cn(
|
||||
"rounded-full px-4 py-1 text-sm transition-colors",
|
||||
formData.kind === "EMPLOYEE"
|
||||
? "bg-blue-100 text-blue-900 ring-2 ring-blue-600 ring-offset-2"
|
||||
: "bg-gray-100 text-gray-900 hover:bg-gray-200"
|
||||
)}
|
||||
>
|
||||
Employee
|
||||
</button>
|
||||
<button
|
||||
onClick={() => handleFieldChange("kind", "CONTRACTOR")}
|
||||
className={cn(
|
||||
"rounded-full px-4 py-1 text-sm transition-colors",
|
||||
formData.kind === "CONTRACTOR"
|
||||
? "bg-purple-100 text-purple-900 ring-2 ring-purple-600 ring-offset-2"
|
||||
: "bg-gray-100 text-gray-900 hover:bg-gray-200"
|
||||
)}
|
||||
>
|
||||
Contractor
|
||||
</button>
|
||||
<button
|
||||
onClick={() =>
|
||||
handleFieldChange("kind", "SERVICE_ACCOUNT")
|
||||
}
|
||||
className={cn(
|
||||
"rounded-full px-4 py-1 text-sm transition-colors",
|
||||
formData.kind === "SERVICE_ACCOUNT"
|
||||
? "bg-green-100 text-green-900 ring-2 ring-green-600 ring-offset-2"
|
||||
: "bg-gray-100 text-gray-900 hover:bg-gray-200"
|
||||
)}
|
||||
>
|
||||
Service Account
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{hasChanges && (
|
||||
<div className="fixed bottom-6 right-6 flex gap-2">
|
||||
<Button variant="outline" onClick={handleCancel}>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
onClick={handleSave}
|
||||
className="bg-primary text-primary-foreground hover:bg-primary/90"
|
||||
>
|
||||
Save Changes
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
{hasChanges && (
|
||||
<div className="fixed bottom-6 right-6 flex gap-2">
|
||||
<Button variant="outline" onClick={handleCancel}>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
onClick={handleSave}
|
||||
className="bg-primary text-primary-foreground hover:bg-primary/90"
|
||||
>
|
||||
Save Changes
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ import type { PolicyOverviewPageDeleteMutation } from "./__generated__/PolicyOve
|
||||
import { Helmet } from "react-helmet-async";
|
||||
import "../styles/policy-content.css";
|
||||
import { useToast } from "@/hooks/use-toast";
|
||||
import { PageHeader } from "./PageHeader";
|
||||
|
||||
const PolicyOverviewPageQuery = graphql`
|
||||
query PolicyOverviewPageQuery($policyId: ID!) {
|
||||
@@ -152,72 +153,65 @@ function PolicyOverviewPageContent({
|
||||
<Helmet>
|
||||
<title>{policy.name} - Probo Console</title>
|
||||
</Helmet>
|
||||
<div className="container mx-auto py-6">
|
||||
<div className="flex justify-between items-start mb-6">
|
||||
<div className="flex items-center">
|
||||
<div className="mr-4">
|
||||
<div className="flex h-12 w-12 items-center justify-center rounded-lg bg-primary/10">
|
||||
<FileText className="h-6 w-6 text-primary" />
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<h1 className="text-2xl font-bold">{policy.name}</h1>
|
||||
<div className="flex items-center gap-2 text-muted-foreground">
|
||||
<Badge
|
||||
variant={policy.status === "ACTIVE" ? "default" : "outline"}
|
||||
<div className="container">
|
||||
<PageHeader
|
||||
className="mb-17"
|
||||
title={policy.name ?? ""}
|
||||
actions={
|
||||
<div className="flex gap-2">
|
||||
<Button variant="outline" size="sm" asChild>
|
||||
<Link
|
||||
to={`/organizations/${organizationId}/policies/${policy.id}/update`}
|
||||
>
|
||||
{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>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex gap-2">
|
||||
<Button variant="outline" size="sm" asChild>
|
||||
<Link
|
||||
to={`/organizations/${organizationId}/policies/${policy.id}/update`}
|
||||
<Edit className="h-4 w-4 mr-2" />
|
||||
Edit
|
||||
</Link>
|
||||
</Button>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
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);
|
||||
}}
|
||||
>
|
||||
<Edit className="h-4 w-4 mr-2" />
|
||||
Edit
|
||||
</Link>
|
||||
</Button>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
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>
|
||||
<Download className="h-4 w-4 mr-2" />
|
||||
Download
|
||||
</Button>
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<div className="flex items-center gap-2 text-muted-foreground">
|
||||
<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>
|
||||
</div>
|
||||
</PageHeader>
|
||||
|
||||
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
|
||||
<div className="lg:col-span-2">
|
||||
|
||||
@@ -28,6 +28,7 @@ import type {
|
||||
ControlState,
|
||||
ControlImportance,
|
||||
} from "./__generated__/UpdateControlPageUpdateControlMutation.graphql";
|
||||
import { PageHeader } from "./PageHeader";
|
||||
|
||||
const updateControlMutation = graphql`
|
||||
mutation UpdateControlPageUpdateControlMutation($input: UpdateControlInput!) {
|
||||
@@ -253,11 +254,12 @@ function UpdateControlPageContent({
|
||||
<Helmet>
|
||||
<title>Update Control - Probo</title>
|
||||
</Helmet>
|
||||
<div className="container mx-auto py-6">
|
||||
<div className="mb-6">
|
||||
<h1 className="text-2xl font-bold">Update Control</h1>
|
||||
<p className="text-muted-foreground">Update the control details</p>
|
||||
</div>
|
||||
<div className="container">
|
||||
<PageHeader
|
||||
className="mb-17"
|
||||
title="Update Control"
|
||||
description="Update the control details"
|
||||
/>
|
||||
|
||||
<Card className="max-w-2xl">
|
||||
<form onSubmit={handleSubmit} className="p-6 space-y-6">
|
||||
|
||||
@@ -18,6 +18,7 @@ import { HelpCircle } from "lucide-react";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { UpdateFrameworkPageUpdateFrameworkMutation } from "./__generated__/UpdateFrameworkPageUpdateFrameworkMutation.graphql";
|
||||
import { UpdateFrameworkPageQuery as UpdateFrameworkPageQueryType } from "./__generated__/UpdateFrameworkPageQuery.graphql";
|
||||
import { PageHeader } from "./PageHeader";
|
||||
|
||||
const updateFrameworkMutation = graphql`
|
||||
mutation UpdateFrameworkPageUpdateFrameworkMutation(
|
||||
@@ -87,7 +88,7 @@ function EditableField({
|
||||
}
|
||||
className={cn(
|
||||
"w-full resize-none",
|
||||
required && !value && "border-red-500",
|
||||
required && !value && "border-red-500"
|
||||
)}
|
||||
placeholder={`Enter ${label.toLowerCase()}`}
|
||||
rows={4}
|
||||
@@ -134,7 +135,7 @@ function UpdateFrameworkPageContent({
|
||||
|
||||
const [commit, isInFlight] =
|
||||
useMutation<UpdateFrameworkPageUpdateFrameworkMutation>(
|
||||
updateFrameworkMutation,
|
||||
updateFrameworkMutation
|
||||
);
|
||||
|
||||
const handleFieldChange = (field: keyof typeof formData, value: unknown) => {
|
||||
@@ -202,11 +203,12 @@ function UpdateFrameworkPageContent({
|
||||
<Helmet>
|
||||
<title>Update Framework - Probo</title>
|
||||
</Helmet>
|
||||
<div className="container mx-auto py-6">
|
||||
<div className="mb-6">
|
||||
<h1 className="text-2xl font-bold">Update Framework</h1>
|
||||
<p className="text-muted-foreground">Update the framework details</p>
|
||||
</div>
|
||||
<div className="container">
|
||||
<PageHeader
|
||||
className="mb-17"
|
||||
title="Update Framework"
|
||||
description="Update the framework details"
|
||||
/>
|
||||
|
||||
<Card className="max-w-2xl">
|
||||
<form onSubmit={handleSubmit} className="p-6 space-y-6">
|
||||
|
||||
@@ -13,13 +13,14 @@ import { Button } from "@/components/ui/button";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import { toast } from "@/hooks/use-toast";
|
||||
import { FileText, Calendar, User } from "lucide-react";
|
||||
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 { UpdatePolicyPageQuery as UpdatePolicyPageQueryType } from "./__generated__/UpdatePolicyPageQuery.graphql";
|
||||
import type { UpdatePolicyPageMutation as UpdatePolicyPageMutationType } from "./__generated__/UpdatePolicyPageMutation.graphql";
|
||||
import { PageHeader } from "./PageHeader";
|
||||
|
||||
const UpdatePolicyPageQuery = graphql`
|
||||
query UpdatePolicyPageQuery($policyId: ID!, $organizationId: ID!) {
|
||||
@@ -151,18 +152,12 @@ function UpdatePolicyPageContent({
|
||||
<Helmet>
|
||||
<title>Update Policy - Probo Console</title>
|
||||
</Helmet>
|
||||
<div className="container mx-auto py-6">
|
||||
<div className="flex items-center mb-6">
|
||||
<div className="mr-4">
|
||||
<div className="flex h-12 w-12 items-center justify-center rounded-lg bg-primary/10">
|
||||
<FileText className="h-6 w-6 text-primary" />
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<h1 className="text-2xl font-bold">Update Policy</h1>
|
||||
<p className="text-muted-foreground">Update an existing policy</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="container">
|
||||
<PageHeader
|
||||
className="mb-17"
|
||||
title="Update Policy"
|
||||
description="Update an existing policy"
|
||||
/>
|
||||
|
||||
<form onSubmit={handleSubmit}>
|
||||
<div className="grid gap-6">
|
||||
|
||||
@@ -18,6 +18,7 @@ import type { VendorOverviewPageQuery as VendorOverviewPageQueryType } from "./_
|
||||
import { useParams } from "react-router";
|
||||
import { Helmet } from "react-helmet-async";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { PageHeader } from "./PageHeader";
|
||||
|
||||
const vendorOverviewPageQuery = graphql`
|
||||
query VendorOverviewPageQuery($vendorId: ID!) {
|
||||
@@ -205,8 +206,12 @@ function VendorOverviewPageContent({
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="space-y-6 p-4 md:p-6 lg:p-8">
|
||||
<div className="mx-auto max-w-4xl space-y-6">
|
||||
<Helmet>
|
||||
<title>Vendor - Probo</title>
|
||||
</Helmet>
|
||||
<div className="container">
|
||||
<PageHeader className="mb-17" title={formData.name} />
|
||||
<div className="max-w-4xl space-y-6">
|
||||
<EditableField
|
||||
label="Name"
|
||||
value={formData.name}
|
||||
|
||||
Reference in New Issue
Block a user