mirror of
https://github.com/prowler-cloud/prowler.git
synced 2026-07-18 10:01:56 +00:00
chore: finding details tweaks
This commit is contained in:
@@ -63,7 +63,7 @@ export const CustomDatePicker = () => {
|
||||
CalendarTopContent={
|
||||
<ButtonGroup
|
||||
fullWidth
|
||||
className="bg-content1 px-3 pb-2 pt-3 [&>button]:border-default-200/60 [&>button]:text-default-500"
|
||||
className="bg-content1 px-3 pb-2 pt-3 dark:bg-prowler-blue-400 [&>button]:border-default-200/60 [&>button]:text-default-500"
|
||||
radius="full"
|
||||
size="sm"
|
||||
variant="bordered"
|
||||
|
||||
@@ -1,8 +1,40 @@
|
||||
"use client";
|
||||
|
||||
import { FindingDetail } from "@/components/findings/table";
|
||||
import { FindingProps } from "@/types";
|
||||
// import { usePathname, useRouter, useSearchParams } from "next/navigation";
|
||||
// import { useEffect } from "react";
|
||||
|
||||
export const DataTableRowDetails = ({ finding }: { finding: FindingProps }) => {
|
||||
return <FindingDetail findingDetails={finding} />;
|
||||
import { FindingProps } from "@/types/components";
|
||||
|
||||
import { FindingDetail } from "./finding-detail";
|
||||
|
||||
export const DataTableRowDetails = ({
|
||||
// entityId,
|
||||
findingDetails,
|
||||
}: {
|
||||
entityId: string;
|
||||
findingDetails: FindingProps;
|
||||
}) => {
|
||||
// const router = useRouter();
|
||||
// const pathname = usePathname();
|
||||
// const searchParams = useSearchParams();
|
||||
|
||||
// useEffect(() => {
|
||||
// if (entityId) {
|
||||
// const params = new URLSearchParams(searchParams.toString());
|
||||
// params.set("id", entityId);
|
||||
// router.push(`${pathname}?${params.toString()}`, { scroll: false });
|
||||
// }
|
||||
|
||||
// return () => {
|
||||
// if (entityId) {
|
||||
// const cleanupParams = new URLSearchParams(searchParams.toString());
|
||||
// cleanupParams.delete("id");
|
||||
// router.push(`${pathname}?${cleanupParams.toString()}`, {
|
||||
// scroll: false,
|
||||
// });
|
||||
// }
|
||||
// };
|
||||
// }, [entityId, pathname, router, searchParams]);
|
||||
|
||||
return <FindingDetail findingDetails={findingDetails} />;
|
||||
};
|
||||
|
||||
@@ -1,14 +1,11 @@
|
||||
"use client";
|
||||
|
||||
import {
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableColumn,
|
||||
TableHeader,
|
||||
TableRow,
|
||||
} from "@nextui-org/react";
|
||||
import { Snippet } from "@nextui-org/react";
|
||||
import Link from "next/link";
|
||||
|
||||
import { SnippetId } from "@/components/ui/entities";
|
||||
import { DateWithTime } from "@/components/ui/entities/date-with-time";
|
||||
import { SeverityBadge } from "@/components/ui/table/severity-badge";
|
||||
import { FindingProps } from "@/types";
|
||||
|
||||
export const FindingDetail = ({
|
||||
@@ -17,108 +14,203 @@ export const FindingDetail = ({
|
||||
findingDetails: FindingProps;
|
||||
}) => {
|
||||
const finding = findingDetails;
|
||||
const attributes = finding.attributes;
|
||||
const resource = finding.relationships.resource.attributes;
|
||||
|
||||
const remediation = attributes.check_metadata.remediation;
|
||||
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
<div className="flex flex-col gap-6 rounded-lg">
|
||||
{/* Header */}
|
||||
<div className="flex items-center justify-between">
|
||||
<Table aria-label="Example static collection table">
|
||||
<TableHeader>
|
||||
<TableColumn>Name </TableColumn>
|
||||
<TableColumn>Value</TableColumn>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
<TableRow key="1">
|
||||
<TableCell>Resource ID</TableCell>
|
||||
<TableCell>{finding.relationships.resource.id}</TableCell>
|
||||
</TableRow>
|
||||
<TableRow key="2">
|
||||
<TableCell>Resource ARN</TableCell>
|
||||
<TableCell>
|
||||
{finding.relationships.resource.attributes.uid}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow key="3">
|
||||
<TableCell>Check ID</TableCell>
|
||||
<TableCell>{finding.attributes.check_id}</TableCell>
|
||||
</TableRow>
|
||||
<TableRow key="4">
|
||||
<TableCell>Types</TableCell>
|
||||
<TableCell>
|
||||
{finding.attributes.check_metadata.checktype}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow key="5">
|
||||
<TableCell>Scan time</TableCell>
|
||||
<TableCell>{finding.attributes.inserted_at}</TableCell>
|
||||
</TableRow>
|
||||
<TableRow key="6">
|
||||
<TableCell>Prowler Finding ID</TableCell>
|
||||
<TableCell>
|
||||
{finding.relationships.resource.attributes.uid}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow key="7">
|
||||
<TableCell>Severity</TableCell>
|
||||
<TableCell>{finding.id}</TableCell>
|
||||
</TableRow>
|
||||
<TableRow key="8">
|
||||
<TableCell>Status</TableCell>
|
||||
<TableCell>{finding.attributes.status}</TableCell>
|
||||
</TableRow>
|
||||
<TableRow key="9">
|
||||
<TableCell>Region</TableCell>
|
||||
<TableCell>
|
||||
{finding.relationships.resource.attributes.region}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow key="10">
|
||||
<TableCell>Service</TableCell>
|
||||
<TableCell>
|
||||
{finding.relationships.resource.attributes.service}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow key="11">
|
||||
<TableCell>Account</TableCell>
|
||||
<TableCell>
|
||||
{finding.relationships.provider.attributes.uid}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow key="12">
|
||||
<TableCell>Details</TableCell>
|
||||
<TableCell>{finding.attributes.status_extended}</TableCell>
|
||||
</TableRow>
|
||||
<TableRow key="13">
|
||||
<TableCell>Risk</TableCell>
|
||||
<TableCell>{finding.attributes.check_metadata.risk}</TableCell>
|
||||
</TableRow>
|
||||
<TableRow key="14">
|
||||
<TableCell>Recommendation</TableCell>
|
||||
<TableCell>
|
||||
{
|
||||
finding.attributes.check_metadata.remediation.recommendation
|
||||
.text
|
||||
}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow key="15">
|
||||
<TableCell>CLI</TableCell>
|
||||
<TableCell>
|
||||
{finding.attributes.check_metadata.remediation.code.cli}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow key="16">
|
||||
<TableCell>Other</TableCell>
|
||||
<TableCell>
|
||||
{finding.attributes.check_metadata.remediation.code.other}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow key="17">
|
||||
<TableCell>Terraform</TableCell>
|
||||
<TableCell>
|
||||
{finding.attributes.check_metadata.remediation.code.terraform}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
</TableBody>
|
||||
</Table>
|
||||
<div>
|
||||
<h2 className="line-clamp-2 text-xl font-bold leading-tight text-gray-800 dark:text-prowler-theme-pale/90">
|
||||
{attributes.check_metadata.checktitle}
|
||||
</h2>
|
||||
<p className="text-sm text-gray-500 dark:text-prowler-theme-pale/70">
|
||||
{resource.service}
|
||||
</p>
|
||||
</div>
|
||||
<div
|
||||
className={`rounded-lg px-3 py-1 text-sm font-semibold ${
|
||||
attributes.status === "PASS"
|
||||
? "bg-green-100 text-green-600"
|
||||
: attributes.status === "MANUAL"
|
||||
? "bg-gray-100 text-gray-600"
|
||||
: "bg-red-100 text-red-600"
|
||||
}`}
|
||||
>
|
||||
{attributes.status}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Check Metadata */}
|
||||
<div className="flex flex-col gap-4 rounded-lg p-4 shadow dark:bg-prowler-blue-400">
|
||||
<div className="flex items-center justify-between">
|
||||
<h3 className="text-lg font-bold text-gray-800 dark:text-prowler-theme-pale/90">
|
||||
Check Metadata
|
||||
</h3>
|
||||
<SeverityBadge severity={attributes.severity} />
|
||||
</div>
|
||||
{attributes.status === "FAIL" && (
|
||||
<Snippet
|
||||
className="max-w-full py-4"
|
||||
color="danger"
|
||||
hideCopyButton
|
||||
hideSymbol
|
||||
>
|
||||
<p className="text-sm font-semibold dark:text-prowler-theme-pale">
|
||||
Risk
|
||||
</p>
|
||||
<p className="whitespace-pre-line text-gray-800 dark:text-prowler-theme-pale/90">
|
||||
{attributes.check_metadata.risk}
|
||||
</p>
|
||||
</Snippet>
|
||||
)}
|
||||
|
||||
<div className="flex flex-col gap-2">
|
||||
<p className="text-sm font-semibold dark:text-prowler-theme-pale">
|
||||
Description
|
||||
</p>
|
||||
<p className="text-gray-800 dark:text-prowler-theme-pale/90">
|
||||
{attributes.check_metadata.description}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col gap-2">
|
||||
<h3 className="text-sm font-semibold dark:text-prowler-theme-pale">
|
||||
Remediation
|
||||
</h3>
|
||||
<div className="text-gray-800 dark:text-prowler-theme-pale/90">
|
||||
{remediation.recommendation && (
|
||||
<>
|
||||
<p className="text-sm font-semibold">Recommendation:</p>
|
||||
<p>{remediation.recommendation.text}</p>
|
||||
<Link
|
||||
target="_blank"
|
||||
href={remediation.recommendation.url}
|
||||
className="mt-2 inline-block text-sm text-blue-500 underline"
|
||||
>
|
||||
Learn more
|
||||
</Link>
|
||||
</>
|
||||
)}
|
||||
{remediation.code &&
|
||||
Object.values(remediation.code).some(Boolean) && (
|
||||
<>
|
||||
<p className="mt-4 text-sm font-semibold">
|
||||
Check these links:
|
||||
</p>
|
||||
<div className="flex flex-col gap-2">
|
||||
{remediation.code.cli && (
|
||||
<div>
|
||||
<p className="text-sm font-semibold">CLI Command:</p>
|
||||
<Snippet hideSymbol size="sm" className="max-w-full">
|
||||
<p className="whitespace-pre-line">
|
||||
{remediation.code.cli}
|
||||
</p>
|
||||
</Snippet>
|
||||
</div>
|
||||
)}
|
||||
{Object.entries(remediation.code)
|
||||
.filter(([key]) => key !== "cli")
|
||||
.map(([key, value]) =>
|
||||
value ? (
|
||||
<Link
|
||||
key={key}
|
||||
href={value}
|
||||
target="_blank"
|
||||
className="text-sm font-medium text-blue-500"
|
||||
>
|
||||
{key === "other"
|
||||
? "External doc"
|
||||
: key.charAt(0).toUpperCase() +
|
||||
key.slice(1).toLowerCase()}
|
||||
</Link>
|
||||
) : null,
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Resources Section */}
|
||||
<div className="flex flex-col gap-4 rounded-lg p-4 shadow dark:bg-prowler-blue-400">
|
||||
<h3 className="text-lg font-bold text-gray-800 dark:text-prowler-theme-pale/90">
|
||||
Resource Details
|
||||
</h3>
|
||||
<div className="grid grid-cols-2 gap-6">
|
||||
<div className="col-span-2">
|
||||
<p className="text-sm font-semibold dark:text-prowler-theme-pale">
|
||||
Resource ID
|
||||
</p>
|
||||
<Snippet size="sm" hideSymbol className="max-w-full">
|
||||
<p className="whitespace-pre-line">{resource.uid}</p>
|
||||
</Snippet>
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-sm font-semibold dark:text-prowler-theme-pale">
|
||||
Resource Name
|
||||
</p>
|
||||
<p className="text-gray-800 dark:text-prowler-theme-pale/90">
|
||||
{resource.name}
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-sm font-semibold dark:text-prowler-theme-pale">
|
||||
Region
|
||||
</p>
|
||||
<p className="text-gray-800 dark:text-prowler-theme-pale/90">
|
||||
{resource.region}
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-sm font-semibold dark:text-prowler-theme-pale">
|
||||
Resource Type
|
||||
</p>
|
||||
<p className="text-gray-800 dark:text-prowler-theme-pale/90">
|
||||
{resource.type}
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-sm font-semibold dark:text-prowler-theme-pale">
|
||||
Severity
|
||||
</p>
|
||||
<SeverityBadge severity={attributes.severity} />
|
||||
</div>
|
||||
{resource.tags &&
|
||||
Object.entries(resource.tags).map(([key, value]) => (
|
||||
<div key={key}>
|
||||
<p className="text-sm font-semibold dark:text-prowler-theme-pale">
|
||||
Tag: {key}
|
||||
</p>
|
||||
<SnippetId
|
||||
entityId={value}
|
||||
hideSymbol
|
||||
size="sm"
|
||||
className="max-w-full"
|
||||
>
|
||||
<p className="whitespace-pre-line">{value}</p>
|
||||
</SnippetId>
|
||||
</div>
|
||||
))}
|
||||
<div className="col-span-2 grid grid-cols-2 gap-6">
|
||||
<div>
|
||||
<p className="text-sm font-semibold dark:text-prowler-theme-pale">
|
||||
Inserted At
|
||||
</p>
|
||||
<DateWithTime inline dateTime={resource.inserted_at} />
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-sm font-semibold dark:text-prowler-theme-pale">
|
||||
Updated At
|
||||
</p>
|
||||
<DateWithTime inline dateTime={resource.updated_at} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -32,7 +32,7 @@ export const InvitationDetails = ({ attributes }: InvitationDetailsProps) => {
|
||||
<div className="flex flex-col gap-x-4 gap-y-8">
|
||||
<Card
|
||||
isBlurred
|
||||
className="border-none bg-background/60 dark:bg-default-100/50"
|
||||
className="border-none bg-background/60 dark:bg-prowler-blue-800"
|
||||
shadow="sm"
|
||||
>
|
||||
<CardBody>
|
||||
|
||||
@@ -55,7 +55,7 @@ export const WorkflowSendInvite = () => {
|
||||
<VerticalSteps
|
||||
hideProgressBars
|
||||
currentStep={currentStep}
|
||||
stepClassName="border border-default-200 dark:border-default-50 aria-[current]:bg-default-100 dark:aria-[current]:bg-default-50 cursor-default"
|
||||
stepClassName="border border-default-200 dark:border-default-50 aria-[current]:bg-default-100 dark:aria-[current]:bg-prowler-blue-800 cursor-default"
|
||||
steps={steps}
|
||||
/>
|
||||
<Spacer y={4} />
|
||||
|
||||
@@ -67,7 +67,7 @@ export const WorkflowAddProvider = () => {
|
||||
<VerticalSteps
|
||||
hideProgressBars
|
||||
currentStep={currentStep}
|
||||
stepClassName="border border-default-200 dark:border-default-50 aria-[current]:bg-default-100 dark:aria-[current]:bg-default-50 cursor-default"
|
||||
stepClassName="border border-default-200 dark:border-default-50 aria-[current]:bg-default-100 dark:aria-[current]:bg-prowler-blue-800 cursor-default"
|
||||
steps={steps}
|
||||
/>
|
||||
<Spacer y={4} />
|
||||
|
||||
@@ -17,22 +17,25 @@ export const ScanDetail = ({ scanDetails }: ScanDetailsProps) => {
|
||||
const taskDetails = scanDetails.taskDetails;
|
||||
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
<div className="flex flex-col gap-6 rounded-lg">
|
||||
{/* Header */}
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex flex-col items-baseline md:flex-row md:gap-x-4">
|
||||
<h2 className="text-2xl font-bold">Scan Details</h2>
|
||||
</div>
|
||||
|
||||
<h2 className="text-2xl font-bold text-gray-800 dark:text-prowler-theme-pale/90">
|
||||
Scan Details
|
||||
</h2>
|
||||
<StatusBadge
|
||||
size="lg"
|
||||
status={scanOnDemand.state}
|
||||
loadingProgress={scanOnDemand.progress}
|
||||
/>
|
||||
</div>
|
||||
<Divider />
|
||||
<div className="relative z-0 flex w-full flex-col justify-between gap-4 overflow-auto rounded-large bg-content1 p-4 shadow-small">
|
||||
<div className="grid grid-cols-1 gap-4 md:grid-cols-2">
|
||||
<div className="space-y-4">
|
||||
|
||||
<Divider className="border-gray-300 dark:border-gray-600" />
|
||||
|
||||
{/* Details Section */}
|
||||
<div className="flex flex-col gap-4 rounded-lg p-4 shadow dark:bg-prowler-blue-400">
|
||||
<div className="grid grid-cols-1 gap-6 md:grid-cols-2">
|
||||
<div className="flex flex-col gap-4">
|
||||
<DetailItem label="Scan Name" value={scanOnDemand.name} />
|
||||
<DetailItem
|
||||
label="ID"
|
||||
@@ -49,7 +52,7 @@ export const ScanDetail = ({ scanDetails }: ScanDetailsProps) => {
|
||||
value={`${scanOnDemand.duration} seconds`}
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-4">
|
||||
<div className="flex flex-col gap-4">
|
||||
<DateItem
|
||||
label="Started At"
|
||||
value={
|
||||
@@ -68,7 +71,7 @@ export const ScanDetail = ({ scanDetails }: ScanDetailsProps) => {
|
||||
dateTime={scanOnDemand.completed_at.toString()}
|
||||
/>
|
||||
) : (
|
||||
"Not Started"
|
||||
"Not Completed"
|
||||
)
|
||||
}
|
||||
/>
|
||||
@@ -109,17 +112,21 @@ export const ScanDetail = ({ scanDetails }: ScanDetailsProps) => {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<Card className="relative w-full border-small border-default-100 p-3 shadow-lg">
|
||||
<CardHeader className="py-2">
|
||||
<h2 className="text-2xl font-bold">Scan arguments</h2>
|
||||
|
||||
{/* Scan Arguments Section */}
|
||||
<Card className="rounded-lg p-4 shadow dark:bg-prowler-blue-400">
|
||||
<CardHeader className="pb-4">
|
||||
<h3 className="text-lg font-bold text-gray-800 dark:text-prowler-theme-pale/90">
|
||||
Scan Arguments
|
||||
</h3>
|
||||
</CardHeader>
|
||||
|
||||
<Divider />
|
||||
|
||||
<CardBody className="p-4">
|
||||
<Divider className="border-gray-300 dark:border-gray-600" />
|
||||
<CardBody className="pt-4">
|
||||
<div className="flex flex-col gap-2">
|
||||
<span className="font-semibold text-default-500">Checks</span>
|
||||
<span className="text-default-700">
|
||||
<span className="text-sm font-semibold text-gray-600 dark:text-gray-300">
|
||||
Checks
|
||||
</span>
|
||||
<span className="text-gray-800 dark:text-prowler-theme-pale/90">
|
||||
{(scanOnDemand.scanner_args as any)?.checks_to_execute?.join(
|
||||
", ",
|
||||
) || "N/A"}
|
||||
@@ -127,25 +134,28 @@ export const ScanDetail = ({ scanDetails }: ScanDetailsProps) => {
|
||||
</div>
|
||||
</CardBody>
|
||||
</Card>
|
||||
|
||||
{/* Task Details Section */}
|
||||
{taskDetails && (
|
||||
<Card className="relative w-full border-small border-default-100 p-3 shadow-lg">
|
||||
<CardHeader className="py-2">
|
||||
<h2 className="text-2xl font-bold">State details</h2>
|
||||
<Card className="rounded-lg p-4 shadow dark:bg-prowler-blue-400">
|
||||
<CardHeader className="pb-4">
|
||||
<h3 className="text-lg font-bold text-gray-800 dark:text-prowler-theme-pale/90">
|
||||
State Details
|
||||
</h3>
|
||||
</CardHeader>
|
||||
<Divider />
|
||||
<CardBody className="p-4">
|
||||
<div className="flex flex-col gap-2">
|
||||
<Divider className="border-gray-300 dark:border-gray-600" />
|
||||
<CardBody className="pt-4">
|
||||
<div className="flex flex-col gap-4">
|
||||
<DetailItem label="State" value={taskDetails.attributes.state} />
|
||||
<DetailItem
|
||||
label="Completed At"
|
||||
value={taskDetails.attributes.completed_at}
|
||||
value={taskDetails.attributes.completed_at || "N/A"}
|
||||
/>
|
||||
|
||||
{taskDetails.attributes.result && (
|
||||
<>
|
||||
<DetailItem
|
||||
label="Error Type"
|
||||
value={taskDetails.attributes.result.exc_type}
|
||||
value={taskDetails.attributes.result.exc_type || "N/A"}
|
||||
/>
|
||||
{taskDetails.attributes.result.exc_message && (
|
||||
<DetailItem
|
||||
@@ -157,12 +167,13 @@ export const ScanDetail = ({ scanDetails }: ScanDetailsProps) => {
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
|
||||
<DetailItem
|
||||
label="Checks to Execute"
|
||||
value={taskDetails.attributes.task_args.checks_to_execute?.join(
|
||||
", ",
|
||||
)}
|
||||
value={
|
||||
taskDetails.attributes.task_args.checks_to_execute?.join(
|
||||
", ",
|
||||
) || "N/A"
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</CardBody>
|
||||
@@ -180,8 +191,10 @@ const DateItem = ({
|
||||
value: React.ReactNode;
|
||||
}) => (
|
||||
<div className="flex items-center justify-between">
|
||||
<span className="font-semibold text-default-500">{label}:</span>
|
||||
<span className="text-default-700">{value}</span>
|
||||
<p className="text-sm font-semibold text-gray-600 dark:text-gray-300">
|
||||
{label}:
|
||||
</p>
|
||||
<p className="text-gray-800 dark:text-prowler-theme-pale/90">{value}</p>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -193,7 +206,9 @@ const DetailItem = ({
|
||||
value: React.ReactNode;
|
||||
}) => (
|
||||
<div className="flex items-center justify-between">
|
||||
<span className="font-semibold text-default-500">{label}:</span>
|
||||
<span className="text-default-700">{value}</span>
|
||||
<p className="text-sm font-semibold text-gray-600 dark:text-gray-300">
|
||||
{label}:
|
||||
</p>
|
||||
<p className="text-gray-800 dark:text-prowler-theme-pale/90">{value}</p>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -8,7 +8,8 @@ import { NextUIColors, NextUIVariants } from "@/types";
|
||||
|
||||
export const buttonClasses = {
|
||||
base: "px-4 inline-flex items-center justify-center relative z-0 text-center whitespace-nowrap",
|
||||
primary: "bg-default-100 hover:bg-default-200 text-default-800",
|
||||
primary:
|
||||
"bg-default-100 hover:bg-default-200 text-default-800 dark:bg-prowler-blue-800",
|
||||
secondary: "bg-prowler-grey-light dark:bg-prowler-grey-medium text-white",
|
||||
action: "bg-prowler-theme-green font-bold text-prowler-theme-midnight",
|
||||
dashed:
|
||||
|
||||
@@ -4,11 +4,13 @@ import React from "react";
|
||||
interface DateWithTimeProps {
|
||||
dateTime: string | null; // e.g., "2024-07-17T09:55:14.191475Z"
|
||||
showTime?: boolean;
|
||||
inline?: boolean;
|
||||
}
|
||||
|
||||
export const DateWithTime: React.FC<DateWithTimeProps> = ({
|
||||
dateTime,
|
||||
showTime = true,
|
||||
inline = false,
|
||||
}) => {
|
||||
if (!dateTime) return <span>--</span>;
|
||||
const date = parseISO(dateTime);
|
||||
@@ -17,7 +19,9 @@ export const DateWithTime: React.FC<DateWithTimeProps> = ({
|
||||
|
||||
return (
|
||||
<div className="max-w-fit">
|
||||
<div className="flex flex-col items-start">
|
||||
<div
|
||||
className={`flex ${inline ? "flex-row items-center gap-2" : "flex-col"}`}
|
||||
>
|
||||
<span className="text-md font-semibold">{formattedDate}</span>
|
||||
{showTime && (
|
||||
<span className="text-sm text-gray-500">{formattedTime}</span>
|
||||
|
||||
@@ -31,7 +31,7 @@ const SheetOverlay = React.forwardRef<
|
||||
SheetOverlay.displayName = SheetPrimitive.Overlay.displayName;
|
||||
|
||||
const sheetVariants = cva(
|
||||
"fixed z-50 gap-4 bg-white p-6 shadow-lg transition ease-in-out data-[state=closed]:duration-300 data-[state=open]:duration-500 data-[state=open]:animate-in data-[state=closed]:animate-out dark:bg-neutral-950",
|
||||
"fixed z-50 gap-4 bg-white p-6 shadow-lg transition ease-in-out data-[state=closed]:duration-300 data-[state=open]:duration-500 data-[state=open]:animate-in data-[state=closed]:animate-out dark:bg-neutral-950 dark:border-prowler-blue-800",
|
||||
{
|
||||
variants: {
|
||||
side: {
|
||||
@@ -40,7 +40,7 @@ const sheetVariants = cva(
|
||||
"inset-x-0 bottom-0 border-t data-[state=closed]:slide-out-to-bottom data-[state=open]:slide-in-from-bottom",
|
||||
left: "inset-y-0 left-0 h-full w-3/4 border-r data-[state=closed]:slide-out-to-left data-[state=open]:slide-in-from-left",
|
||||
right:
|
||||
"inset-y-0 right-0 h-full w-3/4 border-l data-[state=closed]:slide-out-to-right data-[state=open]:slide-in-from-right",
|
||||
"inset-y-0 right-0 h-full w-3/4 border-t-1 border-b-1 border-l-2 data-[state=closed]:slide-out-to-right data-[state=open]:slide-in-from-right",
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
|
||||
@@ -27,7 +27,7 @@ export function TriggerSheet({
|
||||
<SheetTrigger className="flex items-center gap-2">
|
||||
{triggerComponent}
|
||||
</SheetTrigger>
|
||||
<SheetContent className="my-4 max-h-[calc(100vh-2rem)] max-w-[95vw] overflow-y-auto rounded-l-xl pt-10 md:my-8 md:max-h-[calc(100vh-4rem)] md:max-w-[55vw]">
|
||||
<SheetContent className="my-4 max-h-[calc(100vh-2rem)] max-w-[95vw] overflow-y-auto rounded-l-xl pt-10 dark:bg-prowler-theme-midnight md:my-8 md:max-h-[calc(100vh-4rem)] md:max-w-[55vw]">
|
||||
<SheetHeader>
|
||||
<SheetTitle className="sr-only">{title}</SheetTitle>
|
||||
<SheetDescription className="sr-only">{description}</SheetDescription>
|
||||
|
||||
@@ -280,7 +280,7 @@ const Sidebar = React.forwardRef<HTMLElement, SidebarProps>(
|
||||
itemClasses={{
|
||||
...itemClasses,
|
||||
base: clsx(
|
||||
"px-3 rounded-large data-[selected=true]:bg-default-100",
|
||||
"px-3 rounded-large data-[selected=true]:bg-default-100 dark:data-[selected=true]:bg-prowler-blue-800",
|
||||
itemClasses?.base,
|
||||
),
|
||||
title: clsx(
|
||||
|
||||
@@ -49,7 +49,7 @@ export function DataTablePagination({ metadata }: DataTablePaginationProps) {
|
||||
<div className="flex items-center space-x-2">
|
||||
<Link
|
||||
aria-label="Go to first page"
|
||||
className="page-link relative block rounded border-0 bg-transparent px-3 py-1.5 text-gray-800 outline-none transition-all duration-300 hover:bg-gray-200 hover:text-gray-800 focus:shadow-none"
|
||||
className="page-link relative block rounded border-0 bg-transparent px-3 py-1.5 text-gray-800 outline-none transition-all duration-300 hover:bg-gray-200 hover:text-gray-800 focus:shadow-none dark:text-prowler-theme-green"
|
||||
href={createPageUrl(1)}
|
||||
aria-disabled="true"
|
||||
>
|
||||
@@ -57,7 +57,7 @@ export function DataTablePagination({ metadata }: DataTablePaginationProps) {
|
||||
</Link>
|
||||
<Link
|
||||
aria-label="Go to previous page"
|
||||
className="page-link relative block rounded border-0 bg-transparent px-3 py-1.5 text-gray-800 outline-none transition-all duration-300 hover:bg-gray-200 hover:text-gray-800 focus:shadow-none"
|
||||
className="page-link relative block rounded border-0 bg-transparent px-3 py-1.5 text-gray-800 outline-none transition-all duration-300 hover:bg-gray-200 hover:text-gray-800 focus:shadow-none dark:text-prowler-theme-green"
|
||||
href={createPageUrl(currentPage - 1)}
|
||||
aria-disabled="true"
|
||||
>
|
||||
@@ -65,14 +65,14 @@ export function DataTablePagination({ metadata }: DataTablePaginationProps) {
|
||||
</Link>
|
||||
<Link
|
||||
aria-label="Go to next page"
|
||||
className="page-link relative block rounded border-0 bg-transparent px-3 py-1.5 text-gray-800 outline-none transition-all duration-300 hover:bg-gray-200 hover:text-gray-800 focus:shadow-none"
|
||||
className="page-link relative block rounded border-0 bg-transparent px-3 py-1.5 text-gray-800 outline-none transition-all duration-300 hover:bg-gray-200 hover:text-gray-800 focus:shadow-none dark:text-prowler-theme-green"
|
||||
href={createPageUrl(currentPage + 1)}
|
||||
>
|
||||
<ChevronRightIcon className="size-4" aria-hidden="true" />
|
||||
</Link>
|
||||
<Link
|
||||
aria-label="Go to last page"
|
||||
className="page-link relative block rounded border-0 bg-transparent px-3 py-1.5 text-gray-800 outline-none transition-all duration-300 hover:bg-gray-200 hover:text-gray-800 focus:shadow-none"
|
||||
className="page-link relative block rounded border-0 bg-transparent px-3 py-1.5 text-gray-800 outline-none transition-all duration-300 hover:bg-gray-200 hover:text-gray-800 focus:shadow-none dark:text-prowler-theme-green"
|
||||
href={createPageUrl(totalPages)}
|
||||
>
|
||||
<DoubleArrowRightIcon className="size-4" aria-hidden="true" />
|
||||
|
||||
@@ -63,7 +63,7 @@ export function DataTable<TData, TValue>({
|
||||
<DataTableFilterCustom filters={customFilters || []} />
|
||||
</div>
|
||||
)}
|
||||
<div className="relative z-0 flex w-full flex-col justify-between gap-4 overflow-auto rounded-large bg-content1 p-4 shadow-small">
|
||||
<div className="relative z-0 flex w-full flex-col justify-between gap-4 overflow-auto rounded-large bg-content1 p-4 shadow-small dark:bg-prowler-blue-400">
|
||||
<Table>
|
||||
<TableHeader>
|
||||
{table.getHeaderGroups().map((headerGroup) => (
|
||||
|
||||
@@ -75,7 +75,7 @@ const TableHead = React.forwardRef<
|
||||
<th
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"h-10 whitespace-nowrap bg-default-100 px-4 text-left align-middle text-tiny font-semibold text-foreground-500 outline-none first:rounded-l-lg last:rounded-r-lg data-[focus-visible=true]:z-10 data-[hover=true]:text-foreground-400 data-[focus-visible=true]:outline-2 data-[focus-visible=true]:outline-offset-2 data-[focus-visible=true]:outline-focus dark:text-slate-400 rtl:text-right rtl:first:rounded-l-[unset] rtl:first:rounded-r-lg rtl:last:rounded-l-lg rtl:last:rounded-r-[unset] [&:has([role=checkbox])]:pr-0",
|
||||
"h-10 whitespace-nowrap bg-default-100 px-4 text-left align-middle text-tiny font-semibold text-foreground-500 outline-none first:rounded-l-lg last:rounded-r-lg data-[focus-visible=true]:z-10 data-[hover=true]:text-foreground-400 data-[focus-visible=true]:outline-2 data-[focus-visible=true]:outline-offset-2 data-[focus-visible=true]:outline-focus dark:bg-prowler-blue-800 dark:text-slate-400 rtl:text-right rtl:first:rounded-l-[unset] rtl:first:rounded-r-lg rtl:last:rounded-l-lg rtl:last:rounded-r-[unset] [&:has([role=checkbox])]:pr-0",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
|
||||
@@ -31,6 +31,7 @@ module.exports = {
|
||||
},
|
||||
blue: {
|
||||
800: "#1e293bff",
|
||||
400: "#1A202C",
|
||||
},
|
||||
grey: {
|
||||
medium: "#353a4d",
|
||||
|
||||
Reference in New Issue
Block a user