Files
prowler/ui/components/compliance/compliance-card.tsx
2025-11-19 11:37:17 +01:00

164 lines
4.7 KiB
TypeScript

"use client";
import { Progress } from "@heroui/progress";
import Image from "next/image";
import { useRouter, useSearchParams } from "next/navigation";
import { useState } from "react";
import { Card, CardContent } from "@/components/shadcn/card/card";
import { DownloadIconButton, toast } from "@/components/ui";
import { downloadComplianceCsv } from "@/lib/helper";
import { ScanEntity } from "@/types/scans";
import { getComplianceIcon } from "../icons";
interface ComplianceCardProps {
title: string;
version: string;
passingRequirements: number;
totalRequirements: number;
prevPassingRequirements: number;
prevTotalRequirements: number;
scanId: string;
complianceId: string;
id: string;
selectedScan?: ScanEntity;
}
export const ComplianceCard: React.FC<ComplianceCardProps> = ({
title,
version,
passingRequirements,
totalRequirements,
scanId,
complianceId,
id,
selectedScan,
}) => {
const searchParams = useSearchParams();
const router = useRouter();
const hasRegionFilter = searchParams.has("filter[region__in]");
const [isDownloading, setIsDownloading] = useState<boolean>(false);
const formatTitle = (title: string) => {
return title.split("-").join(" ");
};
const ratingPercentage = Math.floor(
(passingRequirements / totalRequirements) * 100,
);
const getRatingColor = (ratingPercentage: number) => {
if (ratingPercentage <= 10) {
return "danger";
}
if (ratingPercentage <= 40) {
return "warning";
}
return "success";
};
const navigateToDetail = () => {
const formattedTitleForUrl = encodeURIComponent(title);
const path = `/compliance/${formattedTitleForUrl}`;
const params = new URLSearchParams();
params.set("complianceId", id);
params.set("version", version);
params.set("scanId", scanId);
if (selectedScan) {
params.set(
"scanData",
JSON.stringify({
id: selectedScan.id,
providerInfo: selectedScan.providerInfo,
attributes: selectedScan.attributes,
}),
);
}
const regionFilter = searchParams.get("filter[region__in]");
if (regionFilter) {
params.set("filter[region__in]", regionFilter);
}
router.push(`${path}?${params.toString()}`);
};
const handleDownload = async () => {
setIsDownloading(true);
try {
await downloadComplianceCsv(scanId, complianceId, toast);
} finally {
setIsDownloading(false);
}
};
return (
<Card
variant="base"
padding="md"
className="cursor-pointer transition-shadow hover:shadow-md"
onClick={navigateToDetail}
>
<CardContent className="p-0">
<div className="flex w-full items-center gap-4">
{getComplianceIcon(title) && (
<Image
src={getComplianceIcon(title)}
alt={`${title} logo`}
className="h-10 w-10 min-w-10 rounded-md border border-gray-300 bg-white object-contain p-1"
/>
)}
<div className="flex w-full flex-col">
<h4 className="text-small mb-1 leading-5 font-bold">
{formatTitle(title)}
{version ? ` - ${version}` : ""}
</h4>
<Progress
label="Score:"
size="sm"
aria-label="Compliance score"
value={ratingPercentage}
showValueLabel={true}
classNames={{
track: "drop-shadow-sm border border-default",
label: "tracking-wider font-medium text-default-600 text-xs",
value: "text-foreground/60 -mb-2",
}}
color={getRatingColor(ratingPercentage)}
/>
<div className="mt-2 flex items-center justify-between">
<small>
<span className="mr-1 text-xs font-semibold">
{passingRequirements} / {totalRequirements}
</span>
Passing Requirements
</small>
<div
onClick={(e) => e.stopPropagation()}
onKeyDown={(e) => {
if (e.key === "Enter" || e.key === " ") {
e.stopPropagation();
}
}}
role="button"
tabIndex={0}
>
<DownloadIconButton
paramId={complianceId}
onDownload={handleDownload}
textTooltip="Download compliance CSV report"
isDisabled={hasRegionFilter}
isDownloading={isDownloading}
/>
</div>
</div>
</div>
</div>
</CardContent>
</Card>
);
};