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