mirror of
https://github.com/prowler-cloud/prowler.git
synced 2026-10-31 15:54:24 +00:00
feat: CIS compliance detail view (#7913)
Co-authored-by: Víctor Fernández Poyatos <victor@prowler.com>
This commit is contained in:
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"
|
||||
|
||||
@@ -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">
|
||||
|
||||
Reference in new issue
Block a user