feat: CIS compliance detail view (#7913)

Co-authored-by: Víctor Fernández Poyatos <victor@prowler.com>
This commit is contained in:
Alejandro BailoandVíctor Fernández Poyatos authored and GitHub committed 2025-06-03 15:47:46 +02:00
1 parent 9ee78fe65f
commit 49e501c4be
28 files changed
+2336 -211

No files matched your search

@@ -1,12 +1,13 @@
import { Suspense, use } from "react";
import { ReactNode, Suspense, use } from "react";
import { getUserInfo } from "@/actions/users/users";
import { Navbar } from "../nav-bar/navbar";
import { SkeletonContentLayout } from "./skeleton-content-layout";
interface ContentLayoutProps {
title: string;
icon: string;
icon: string | ReactNode;
children: React.ReactNode;
}
@@ -12,7 +12,13 @@ import {
} from "@nextui-org/react";
import { ChevronDown, X } from "lucide-react";
import { useSearchParams } from "next/navigation";
import React, { useCallback, useEffect, useMemo, useState } from "react";
import React, {
useCallback,
useEffect,
useMemo,
useRef,
useState,
} from "react";
import { CustomDropdownFilterProps } from "@/types";
@@ -25,6 +31,7 @@ export const CustomDropdownFilter = ({
const searchParams = useSearchParams();
const [groupSelected, setGroupSelected] = useState(new Set<string>());
const [isOpen, setIsOpen] = useState(false);
const hasUserInteracted = useRef(false);
const filterValues = useMemo(() => filter?.values || [], [filter?.values]);
const selectedValues = Array.from(groupSelected).filter(
@@ -42,24 +49,66 @@ export const CustomDropdownFilter = ({
useEffect(() => {
if (activeFilterValue.length > 0) {
const newSelection = new Set(activeFilterValue);
if (newSelection.size === filterValues.length) {
if (
newSelection.size === filterValues.length &&
filter?.showSelectAll !== false
) {
newSelection.add("all");
}
setGroupSelected(newSelection);
} else {
setGroupSelected(new Set());
} else if (!hasUserInteracted.current) {
// Handle default behavior when no URL params exist
// Only apply defaults if user hasn't interacted yet
// Only set visual state, don't trigger URL changes automatically
if (filter?.defaultToSelectAll && filterValues.length > 0) {
const newSelection = new Set(filterValues);
if (filter?.showSelectAll !== false) {
newSelection.add("all");
}
setGroupSelected(newSelection);
// DON'T notify parent automatically - wait for user interaction
} else if (filter?.defaultValues && filter.defaultValues.length > 0) {
// Handle specific default values
const validDefaultValues = filter.defaultValues.filter((value) =>
filterValues.includes(value),
);
const newSelection = new Set(validDefaultValues);
// Add "all" if all items are selected and showSelectAll is not false
if (
validDefaultValues.length === filterValues.length &&
filter?.showSelectAll !== false
) {
newSelection.add("all");
}
setGroupSelected(newSelection);
// DON'T notify parent automatically - wait for user interaction
} else {
setGroupSelected(new Set());
}
}
}, [activeFilterValue, filterValues.length]);
}, [
activeFilterValue,
filterValues,
filter?.defaultToSelectAll,
filter?.defaultValues,
filter?.showSelectAll,
]);
const updateSelection = useCallback(
(newValues: string[]) => {
// Mark that user has interacted with the filter
hasUserInteracted.current = true;
const actualValues = newValues.filter((key) => key !== "all");
const newSelection = new Set(actualValues);
// Auto-add "all" if all items are selected
// Auto-add "all" if all items are selected and showSelectAll is not false
if (
actualValues.length === filterValues.length &&
filterValues.length > 0
filterValues.length > 0 &&
filter?.showSelectAll !== false
) {
newSelection.add("all");
}
@@ -69,7 +118,7 @@ export const CustomDropdownFilter = ({
// Notify parent with actual values (excluding "all")
onFilterChange?.(filter.key, actualValues);
},
[filterValues.length, onFilterChange, filter.key],
[filterValues.length, onFilterChange, filter.key, filter?.showSelectAll],
);
const onSelectionChange = useCallback(
@@ -194,16 +243,20 @@ export const CustomDropdownFilter = ({
onValueChange={onSelectionChange}
className="font-bold"
>
<Checkbox
classNames={{
label: "text-small font-normal",
wrapper: "checkbox-update",
}}
value="all"
>
Select All
</Checkbox>
<Divider orientation="horizontal" className="mt-2" />
{filter?.showSelectAll !== false && (
<>
<Checkbox
classNames={{
label: "text-small font-normal",
wrapper: "checkbox-update",
}}
value="all"
>
Select All
</Checkbox>
<Divider orientation="horizontal" className="mt-2" />
</>
)}
<ScrollShadow
hideScrollBar
className="flex max-h-96 max-w-full flex-col gap-y-2 py-2"
+15 -7
View File
@@ -1,13 +1,15 @@
import { Icon } from "@iconify/react";
import { ReactNode } from "react";
import { ThemeSwitch } from "@/components/ThemeSwitch";
import { UserProfileProps } from "@/types";
import { SheetMenu } from "../sidebar/sheet-menu";
import { UserNav } from "../user-nav/user-nav";
interface NavbarProps {
title: string;
icon: string;
icon: string | ReactNode;
user: UserProfileProps;
}
@@ -17,12 +19,18 @@ export function Navbar({ title, icon, user }: NavbarProps) {
<div className="mx-4 flex h-14 items-center sm:mx-8">
<div className="flex items-center space-x-2">
<SheetMenu />
<Icon
className="text-default-500"
height={24}
icon={icon}
width={24}
/>
{typeof icon === "string" ? (
<Icon
className="text-default-500"
height={24}
icon={icon}
width={24}
/>
) : (
<div className="flex h-10 w-10 items-center justify-center [&>*]:h-full [&>*]:w-full">
{icon}
</div>
)}
<h1 className="text-sm font-bold text-default-700">{title}</h1>
</div>
<div className="flex flex-1 items-center justify-end gap-3">