mirror of
https://github.com/prowler-cloud/prowler.git
synced 2026-11-09 04:38:49 +00:00
feat(ui): new resources side drawer with redesigned detail panel (#10673)
This commit is contained in:
1 parent
f3a042933f
commit
89d72cf8fd
20 files changed
+637
-502
No files matched your search
@@ -1,5 +1,4 @@
|
||||
export * from "./date-with-time";
|
||||
export * from "./entity-info";
|
||||
export * from "./get-provider-logo";
|
||||
export * from "./info-field";
|
||||
export * from "./scan-status";
|
||||
@@ -1,79 +0,0 @@
|
||||
import { Tooltip } from "@heroui/tooltip";
|
||||
import clsx from "clsx";
|
||||
import { InfoIcon } from "lucide-react";
|
||||
|
||||
interface InfoFieldProps {
|
||||
label: string;
|
||||
children: React.ReactNode;
|
||||
variant?: "default" | "simple" | "transparent";
|
||||
className?: string;
|
||||
tooltipContent?: string;
|
||||
inline?: boolean;
|
||||
}
|
||||
|
||||
<Tooltip
|
||||
className="text-xs"
|
||||
content="Download a ZIP file that includes the JSON (OCSF), CSV, and HTML scan reports, along with the compliance report."
|
||||
>
|
||||
<div className="flex items-center gap-2">
|
||||
<InfoIcon className="text-primary mb-1" size={12} />
|
||||
</div>
|
||||
</Tooltip>;
|
||||
|
||||
export const InfoField = ({
|
||||
label,
|
||||
children,
|
||||
variant = "default",
|
||||
tooltipContent,
|
||||
className,
|
||||
inline = false,
|
||||
}: InfoFieldProps) => {
|
||||
if (inline) {
|
||||
return (
|
||||
<div className={clsx("flex items-center gap-2", className)}>
|
||||
<span className="text-text-neutral-tertiary text-xs font-bold">
|
||||
<span className="flex items-center gap-1">
|
||||
{label}:
|
||||
{tooltipContent && (
|
||||
<Tooltip className="text-xs" content={tooltipContent}>
|
||||
<div className="flex cursor-pointer items-center gap-2">
|
||||
<InfoIcon className="text-bg-data-info mb-1" size={12} />
|
||||
</div>
|
||||
</Tooltip>
|
||||
)}
|
||||
</span>
|
||||
</span>
|
||||
<div className="text-text-neutral-primary text-sm">{children}</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={clsx("flex flex-col gap-1", className)}>
|
||||
<span className="text-text-neutral-tertiary text-xs font-bold">
|
||||
<span className="flex items-center gap-1">
|
||||
{label}
|
||||
{tooltipContent && (
|
||||
<Tooltip className="text-xs" content={tooltipContent}>
|
||||
<div className="flex cursor-pointer items-center gap-2">
|
||||
<InfoIcon className="text-bg-data-info mb-1" size={12} />
|
||||
</div>
|
||||
</Tooltip>
|
||||
)}
|
||||
</span>
|
||||
</span>
|
||||
|
||||
{variant === "simple" ? (
|
||||
<div className="text-text-neutral-primary text-sm break-all">
|
||||
{children}
|
||||
</div>
|
||||
) : variant === "transparent" ? (
|
||||
<div className="text-text-neutral-primary text-sm">{children}</div>
|
||||
) : (
|
||||
<div className="border-border-neutral-tertiary bg-bg-neutral-tertiary text-text-neutral-primary rounded-lg border px-3 py-2 text-sm">
|
||||
{children}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -166,7 +166,7 @@ describe("DataTableFilterCustom — batch vs instant mode", () => {
|
||||
render(<DataTableFilterCustom filters={[severityFilter]} />);
|
||||
|
||||
// Then — renders without crashing
|
||||
expect(screen.getByText("Severity")).toBeInTheDocument();
|
||||
expect(screen.getByText("All Severity")).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -185,7 +185,9 @@ export const DataTableFilterCustom = ({
|
||||
onValuesChange={(values) => pushDropdownFilter(filter, values)}
|
||||
>
|
||||
<MultiSelectTrigger size="default">
|
||||
<MultiSelectValue placeholder={filter.labelCheckboxGroup} />
|
||||
<MultiSelectValue
|
||||
placeholder={`All ${filter.labelCheckboxGroup}`}
|
||||
/>
|
||||
</MultiSelectTrigger>
|
||||
<MultiSelectContent search={false}>
|
||||
<MultiSelectSelectAll>Select All</MultiSelectSelectAll>
|
||||
|
||||
@@ -108,6 +108,8 @@ interface DataTableProviderProps<TData, TValue> {
|
||||
renderAfterRow?: (row: Row<TData>) => ReactNode;
|
||||
/** Badge shown inside the search input (e.g., active drill-down group) */
|
||||
searchBadge?: { label: string; onDismiss: () => void };
|
||||
/** Optional click handler for top-level rows. */
|
||||
onRowClick?: (row: Row<TData>) => void;
|
||||
}
|
||||
|
||||
export function DataTable<TData, TValue>({
|
||||
@@ -137,6 +139,7 @@ export function DataTable<TData, TValue>({
|
||||
searchPlaceholder,
|
||||
renderAfterRow,
|
||||
searchBadge,
|
||||
onRowClick,
|
||||
}: DataTableProviderProps<TData, TValue>) {
|
||||
const [sorting, setSorting] = useState<SortingState>([]);
|
||||
const [columnFilters, setColumnFilters] = useState<ColumnFiltersState>([]);
|
||||
@@ -213,6 +216,18 @@ export function DataTable<TData, TValue>({
|
||||
|
||||
const rows = table.getRowModel().rows;
|
||||
|
||||
const handleRowClick = (row: Row<TData>, target: HTMLElement | null) => {
|
||||
if (!onRowClick) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (target?.closest("a, button, input, [role=menuitem]")) {
|
||||
return;
|
||||
}
|
||||
|
||||
onRowClick(row);
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
@@ -282,7 +297,13 @@ export function DataTable<TData, TValue>({
|
||||
/>
|
||||
) : (
|
||||
<Fragment key={row.id}>
|
||||
<TableRow data-state={row.getIsSelected() && "selected"}>
|
||||
<TableRow
|
||||
data-state={row.getIsSelected() && "selected"}
|
||||
className={cn(onRowClick && "cursor-pointer")}
|
||||
onClick={(event) =>
|
||||
handleRowClick(row, event.target as HTMLElement)
|
||||
}
|
||||
>
|
||||
{row.getVisibleCells().map((cell) => (
|
||||
<TableCell key={cell.id}>
|
||||
{flexRender(
|
||||
|
||||
Reference in new issue
Block a user