mirror of
https://github.com/prowler-cloud/prowler.git
synced 2026-07-23 12:31:54 +00:00
feat: WIP
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
import { Input } from "@nextui-org/react";
|
||||
import debounce from "lodash.debounce";
|
||||
import { XCircle } from "lucide-react";
|
||||
import { SearchIcon, XCircle } from "lucide-react";
|
||||
import { useRouter, useSearchParams } from "next/navigation";
|
||||
import React, { useCallback, useEffect, useState } from "react";
|
||||
|
||||
@@ -39,9 +39,9 @@ export const CustomSearchInput: React.FC = () => {
|
||||
<Input
|
||||
variant="bordered"
|
||||
placeholder="Search..."
|
||||
label="Search"
|
||||
labelPlacement="inside"
|
||||
labelPlacement="outside"
|
||||
value={searchQuery}
|
||||
startContent={<SearchIcon className="text-default-400" width={16} />}
|
||||
onChange={(e) => {
|
||||
const value = e.target.value;
|
||||
setSearchQuery(value);
|
||||
@@ -54,7 +54,8 @@ export const CustomSearchInput: React.FC = () => {
|
||||
</button>
|
||||
)
|
||||
}
|
||||
size="sm"
|
||||
radius="full"
|
||||
size="lg"
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -595,3 +595,86 @@ export const SuccessIcon: React.FC<IconSvgProps> = ({
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
|
||||
export const ArrowUpIcon: React.FC<IconSvgProps> = ({
|
||||
size,
|
||||
height,
|
||||
width,
|
||||
...props
|
||||
}) => {
|
||||
return (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
height={size || height || "1em"}
|
||||
viewBox="0 0 12 12"
|
||||
width={size || width || "1em"}
|
||||
aria-hidden="true"
|
||||
focusable="false"
|
||||
role="presentation"
|
||||
{...props}
|
||||
>
|
||||
<path
|
||||
d="M3 7.5L6 4.5L9 7.5"
|
||||
stroke="currentColor"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth="1.5"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
};
|
||||
|
||||
export const ArrowDownIcon: React.FC<IconSvgProps> = ({
|
||||
size,
|
||||
height,
|
||||
width,
|
||||
...props
|
||||
}) => {
|
||||
return (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
height={size || height || "1em"}
|
||||
viewBox="0 0 12 12"
|
||||
width={size || width || "1em"}
|
||||
aria-hidden="true"
|
||||
focusable="false"
|
||||
role="presentation"
|
||||
{...props}
|
||||
>
|
||||
<path
|
||||
d="M3 4.5L6 7.5L9 4.5"
|
||||
stroke="currentColor"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth="1.5"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
};
|
||||
|
||||
export const ChevronsLeftRightIcon: React.FC<IconSvgProps> = ({
|
||||
size,
|
||||
height,
|
||||
width,
|
||||
...props
|
||||
}) => {
|
||||
return (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
height={size || height || 24}
|
||||
viewBox="0 0 24 24"
|
||||
width={size || width || 24}
|
||||
stroke="currentColor"
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
className="lucide lucide-chevrons-left-right ml-2 h-4 w-4 rotate-90"
|
||||
{...props}
|
||||
>
|
||||
<path d="m9 7-5 5 5 5M15 7l5 5-5 5" />
|
||||
</svg>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -2,13 +2,14 @@
|
||||
|
||||
import { Button } from "@nextui-org/react";
|
||||
import { Column } from "@tanstack/react-table";
|
||||
import { usePathname, useRouter, useSearchParams } from "next/navigation";
|
||||
import { HTMLAttributes } from "react";
|
||||
|
||||
import {
|
||||
ArrowDownIcon,
|
||||
ArrowUpIcon,
|
||||
ChevronsLeftRightIcon,
|
||||
} from "lucide-react";
|
||||
import { usePathname, useRouter, useSearchParams } from "next/navigation";
|
||||
import { HTMLAttributes } from "react";
|
||||
} from "@/components/icons";
|
||||
|
||||
interface DataTableColumnHeaderProps<TData, TValue>
|
||||
extends HTMLAttributes<HTMLDivElement> {
|
||||
@@ -64,12 +65,12 @@ export const DataTableColumnHeader = <TData, TValue>({
|
||||
currentSortParam === "" ||
|
||||
(currentSortParam !== param && currentSortParam !== `-${param}`)
|
||||
) {
|
||||
return <ChevronsLeftRightIcon className="ml-2 h-4 w-4 rotate-90" />;
|
||||
return <ChevronsLeftRightIcon size={14} className="ml-2 rotate-90" />;
|
||||
}
|
||||
return currentSortParam === `-${param}` ? (
|
||||
<ArrowDownIcon className="ml-2 h-4 w-4" />
|
||||
<ArrowDownIcon size={12} className="ml-2" />
|
||||
) : (
|
||||
<ArrowUpIcon className="ml-2 h-4 w-4" />
|
||||
<ArrowUpIcon size={12} className="ml-2" />
|
||||
);
|
||||
};
|
||||
|
||||
@@ -79,9 +80,7 @@ export const DataTableColumnHeader = <TData, TValue>({
|
||||
|
||||
return (
|
||||
<Button
|
||||
variant="light"
|
||||
size="md"
|
||||
className="text-slate-500 dark:text-slate-400 h-8"
|
||||
className="w-full justify-between px-0 text-left align-middle dark:text-slate-400 h-10 bg-transparent whitespace-nowrap text-foreground-500 text-tiny font-semibold outline-none"
|
||||
onClick={getToggleSortingHandler}
|
||||
>
|
||||
<span>{title}</span>
|
||||
|
||||
@@ -1,6 +1,15 @@
|
||||
"use client";
|
||||
|
||||
import { Button } from "@nextui-org/react";
|
||||
// import {
|
||||
// Button,
|
||||
// Input,
|
||||
// Popover,
|
||||
// PopoverContent,
|
||||
// PopoverTrigger,
|
||||
// RadioGroup,
|
||||
// } from "@nextui-org/react";
|
||||
// import { Icon, Radio, SearchIcon } from "lucide-react";
|
||||
import { useRouter, useSearchParams } from "next/navigation";
|
||||
import { useCallback, useMemo } from "react";
|
||||
|
||||
@@ -43,20 +52,84 @@ export function DataTableFilterCustom({ filters }: DataTableFilterCustomProps) {
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="flex flex-wrap items-center space-x-2">
|
||||
{filters.flatMap(({ key, values }) =>
|
||||
values.map((value) => (
|
||||
<Button
|
||||
key={`${key}-${value}`}
|
||||
onClick={() => applyFilter(key, value)}
|
||||
// eslint-disable-next-line security/detect-object-injection
|
||||
variant={activeFilters[key] === value ? "faded" : "light"}
|
||||
size="sm"
|
||||
>
|
||||
{value || "All"}
|
||||
</Button>
|
||||
)),
|
||||
)}
|
||||
</div>
|
||||
<>
|
||||
{/* <div className="flex items-center gap-4 overflow-auto px-[6px] py-[4px]">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="flex items-center gap-4">
|
||||
<Input
|
||||
className="min-w-[200px]"
|
||||
endContent={
|
||||
<SearchIcon className="text-default-400" width={16} />
|
||||
}
|
||||
placeholder="Search"
|
||||
size="sm"
|
||||
// value={filterValue}
|
||||
// onValueChange={onSearchChange}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<Popover placement="bottom">
|
||||
<PopoverTrigger>
|
||||
<Button className="bg-default-100 text-default-800" size="sm">
|
||||
Filter
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent className="w-80">
|
||||
<div className="flex w-full flex-col gap-6 px-2 py-4">
|
||||
<RadioGroup
|
||||
label="Worker Type"
|
||||
// value={workerTypeFilter}
|
||||
// onValueChange={setWorkerTypeFilter}
|
||||
>
|
||||
<Radio value="all">All</Radio>
|
||||
<Radio value="employee">Employee</Radio>
|
||||
<Radio value="contractor">Contractor</Radio>
|
||||
</RadioGroup>
|
||||
|
||||
<RadioGroup
|
||||
label="Status"
|
||||
// value={statusFilter}
|
||||
// onValueChange={setStatusFilter}
|
||||
>
|
||||
<Radio value="all">All</Radio>
|
||||
<Radio value="active">Active</Radio>
|
||||
<Radio value="inactive">Inactive</Radio>
|
||||
<Radio value="paused">Paused</Radio>
|
||||
<Radio value="vacation">Vacation</Radio>
|
||||
</RadioGroup>
|
||||
|
||||
<RadioGroup
|
||||
label="Start Date"
|
||||
// value={startDateFilter}
|
||||
// onValueChange={setStartDateFilter}
|
||||
>
|
||||
<Radio value="all">All</Radio>
|
||||
<Radio value="last7Days">Last 7 days</Radio>
|
||||
<Radio value="last30Days">Last 30 days</Radio>
|
||||
<Radio value="last60Days">Last 60 days</Radio>
|
||||
</RadioGroup>
|
||||
</div>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
</div>
|
||||
</div>
|
||||
</div> */}
|
||||
|
||||
<div className="flex flex-wrap items-center space-x-2">
|
||||
{filters.flatMap(({ key, values }) =>
|
||||
values.map((value) => (
|
||||
<Button
|
||||
key={`${key}-${value}`}
|
||||
onClick={() => applyFilter(key, value)}
|
||||
// eslint-disable-next-line security/detect-object-injection
|
||||
variant={activeFilters[key] === value ? "faded" : "light"}
|
||||
size="sm"
|
||||
>
|
||||
{value || "All"}
|
||||
</Button>
|
||||
)),
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
"use client";
|
||||
|
||||
import { Icon } from "@iconify/react";
|
||||
import { Button, Chip } from "@nextui-org/react";
|
||||
import {
|
||||
ColumnDef,
|
||||
ColumnFiltersState,
|
||||
@@ -11,7 +12,7 @@ import {
|
||||
SortingState,
|
||||
useReactTable,
|
||||
} from "@tanstack/react-table";
|
||||
import { useState } from "react";
|
||||
import { useMemo, useState } from "react";
|
||||
|
||||
import {
|
||||
Table,
|
||||
@@ -64,11 +65,36 @@ export function DataTableProvider<TData, TValue>({
|
||||
{ key: "connected", values: ["false", "true"] },
|
||||
// Add more filter categories as needed
|
||||
];
|
||||
const topBar = useMemo(() => {
|
||||
return (
|
||||
<div className="mb-[18px] w-full flex items-center justify-between">
|
||||
<div className="flex w-fit items-center gap-2">
|
||||
<h1 className="text-2xl font-[700] leading-[32px]">Providers</h1>
|
||||
<Chip
|
||||
className="hidden items-center text-default-500 sm:flex"
|
||||
size="sm"
|
||||
variant="flat"
|
||||
>
|
||||
3
|
||||
</Chip>
|
||||
</div>
|
||||
<Button
|
||||
color="primary"
|
||||
endContent={<Icon icon="solar:add-circle-bold" width={20} />}
|
||||
>
|
||||
Add Account
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<>
|
||||
{topBar}
|
||||
|
||||
<DataTableFilterCustom filters={filters} />
|
||||
<div className="rounded-md border w-full">
|
||||
|
||||
<div className="p-4 z-0 flex flex-col relative justify-between gap-4 bg-content1 overflow-auto rounded-large shadow-small w-full ">
|
||||
<Table>
|
||||
<TableHeader>
|
||||
{table.getHeaderGroups().map((headerGroup) => (
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
import { cn } from "@nextui-org/react";
|
||||
import * as React from "react";
|
||||
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
const Table = React.forwardRef<
|
||||
HTMLTableElement,
|
||||
React.HTMLAttributes<HTMLTableElement>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<div className="relative w-full overflow-auto">
|
||||
<div className="relative w-full overflow-auto rounded-lg">
|
||||
<table
|
||||
ref={ref}
|
||||
className={cn("w-full caption-bottom text-sm", className)}
|
||||
@@ -20,7 +19,14 @@ const TableHeader = React.forwardRef<
|
||||
HTMLTableSectionElement,
|
||||
React.HTMLAttributes<HTMLTableSectionElement>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<thead ref={ref} className={cn("[&_tr]:border-b", className)} {...props} />
|
||||
<thead
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"[&>tr]:first:rounded-lg [&>tr]:first:shadow-small",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
TableHeader.displayName = "TableHeader";
|
||||
|
||||
@@ -28,11 +34,7 @@ const TableBody = React.forwardRef<
|
||||
HTMLTableSectionElement,
|
||||
React.HTMLAttributes<HTMLTableSectionElement>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<tbody
|
||||
ref={ref}
|
||||
className={cn("[&_tr:last-child]:border-0", className)}
|
||||
{...props}
|
||||
/>
|
||||
<tbody ref={ref} className={cn("", className)} {...props} />
|
||||
));
|
||||
TableBody.displayName = "TableBody";
|
||||
|
||||
@@ -43,7 +45,7 @@ const TableFooter = React.forwardRef<
|
||||
<tfoot
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"border-t bg-slate-100/50 font-medium [&>tr]:last:border-b-0 dark:bg-slate-800/50",
|
||||
"font-medium [&>tr]:last:border-b-0 dark:bg-slate-800/50",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
@@ -58,7 +60,7 @@ const TableRow = React.forwardRef<
|
||||
<tr
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"border-b transition-colors hover:bg-slate-100/50 data-[state=selected]:bg-slate-100 dark:hover:bg-slate-800/50 dark:data-[state=selected]:bg-slate-800",
|
||||
"transition-colors hover:bg-slate-100/50 data-[state=selected]:bg-slate-100 dark:hover:bg-slate-800/50 dark:data-[state=selected]:bg-slate-800",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
@@ -73,7 +75,7 @@ const TableHead = React.forwardRef<
|
||||
<th
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"h-12 px-4 text-left align-middle font-medium text-slate-500 [&:has([role=checkbox])]:pr-0 dark:text-slate-400",
|
||||
"px-4 text-left align-middle bg-default-100 [&:has([role=checkbox])]:pr-0 dark:text-slate-400 h-10 rtl:text-right whitespace-nowrap text-foreground-500 text-tiny font-semibold first:rounded-l-lg rtl:first:rounded-r-lg rtl:first:rounded-l-[unset] last:rounded-r-lg rtl:last:rounded-l-lg rtl:last:rounded-r-[unset] data-[hover=true]:text-foreground-400 outline-none data-[focus-visible=true]:z-10 data-[focus-visible=true]:outline-2 data-[focus-visible=true]:outline-focus data-[focus-visible=true]:outline-offset-2",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
@@ -87,7 +89,10 @@ const TableCell = React.forwardRef<
|
||||
>(({ className, ...props }, ref) => (
|
||||
<td
|
||||
ref={ref}
|
||||
className={cn("p-4 align-middle [&:has([role=checkbox])]:pr-0", className)}
|
||||
className={cn(
|
||||
"px-4 py-2.5 align-middle [&:has([role=checkbox])]:pr-0",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
@@ -99,7 +104,7 @@ const TableCaption = React.forwardRef<
|
||||
>(({ className, ...props }, ref) => (
|
||||
<caption
|
||||
ref={ref}
|
||||
className={cn("mt-4 text-sm text-slate-500 dark:text-slate-400", className)}
|
||||
className={cn("mt-2 text-sm text-slate-500 dark:text-slate-400", className)}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
|
||||
@@ -19,6 +19,31 @@ module.exports = {
|
||||
},
|
||||
extend: {
|
||||
colors: {
|
||||
prowler: {
|
||||
blue: {
|
||||
midnight: "#030921",
|
||||
pale: "#f3fcff",
|
||||
},
|
||||
grey: {
|
||||
medium: "#353a4d",
|
||||
light: "#868994",
|
||||
},
|
||||
theme: {
|
||||
green: "#6af400",
|
||||
purple: "#5001d0",
|
||||
coral: "#ff5356",
|
||||
orange: "#f69000",
|
||||
yellow: "#ffdf16",
|
||||
},
|
||||
dark: {
|
||||
title: "#E2E8F0",
|
||||
text: "#94a3b8" /* primary default for dark mode */,
|
||||
},
|
||||
light: {
|
||||
title: "#1e293bff",
|
||||
text: "#64748b" /* primary default for light mode */,
|
||||
},
|
||||
},
|
||||
system: {
|
||||
success: {
|
||||
DEFAULT: "#09BF3D",
|
||||
@@ -56,6 +81,47 @@ module.exports = {
|
||||
sans: ["var(--font-sans)"],
|
||||
mono: ["var(--font-geist-mono)"],
|
||||
},
|
||||
boxShadow: {
|
||||
"box-light":
|
||||
"-16px -16px 40px rgba(255, 255, 255, 0.8), 16px 4px 64px rgba(18, 61, 101, 0.3), inset -8px -6px 80px rgba(255, 255, 255, 0.18)",
|
||||
"button-curved-default":
|
||||
"-4px -2px 16px rgba(255, 255, 255, 0.7), 4px 2px 16px rgba(136, 165, 191, 0.38)",
|
||||
"button-curved-pressed":
|
||||
"inset -4px -4px 16px rgba(255, 255, 255, 0.8), inset 4px 4px 12px rgba(136, 165, 191, 0.4)",
|
||||
"button-flat-nopressed":
|
||||
"-4px -2px 16px #FFFFFF, 4px 2px 16px rgba(136, 165, 191, 0.48)",
|
||||
"button-flat-pressed":
|
||||
"inset -3px -3px 7px #FFFFFF, inset 3px 3px 7px rgba(136, 165, 191, 0.48)",
|
||||
"box-down-light":
|
||||
"inset -3px -3px 7px #FFFFFF, inset 2px 2px 5px rgba(136, 165, 191, 0.38)",
|
||||
"box-up":
|
||||
"-4px -2px 16px #FFFFFF, 4px 2px 16px rgba(136, 165, 191, 0.54)",
|
||||
"box-dark":
|
||||
"-4px -2px 16px rgba(195, 200, 205, 0.09), 4px 4px 18px rgba(0, 0, 0, 0.5)",
|
||||
"box-dark-out": "inset 2px 2px 2px rgba(26, 32, 38, 0.4)",
|
||||
"buttons-box-dark":
|
||||
"-5px -6px 16px rgba(195, 200, 205, 0.04), 22px 22px 60px rgba(0, 0, 0, 0.5)",
|
||||
"button-curved-default-dark":
|
||||
"-4px -4px 16px rgba(195, 200, 205, 0.06), 4px 4px 18px rgba(0, 0, 0, 0.6)",
|
||||
"button-curved-pressed-dark":
|
||||
"-4px -2px 16px rgba(195, 200, 205, 0.07), 4px 4px 18px rgba(0, 0, 0, 0.44)",
|
||||
"sky-light":
|
||||
"-16px 20px 40px rgba(215, 215, 215, 0.3), -2px 2px 24px rgba(22, 28, 47, 0.3), -16px 28px 120px rgba(0, 0, 0, 0.1)",
|
||||
"midnight-dark":
|
||||
"-16px 20px 40px rgba(3, 9, 33, 0.3), -2px 2px 24px rgba(3, 9, 33, 0.6), -16px 28px 120px rgba(3, 9, 33, 0.1)",
|
||||
switcher:
|
||||
"0px -6px 24px #FFFFFF, 0px 7px 16px rgba(104, 132, 157, 0.5)",
|
||||
up: "0.3rem 0.3rem 0.6rem #c8d0e7, -0.2rem -0.2rem 0.5rem #fff",
|
||||
down: "inset 0.2rem 0.2rem 0.5rem #c8d0e7, inset -0.2rem -0.2rem 0.5rem #fff",
|
||||
},
|
||||
animation: {
|
||||
"fade-in": "fade-in 200ms ease-out 0s 1 normal forwards running",
|
||||
"fade-out": "fade-out 200ms ease-in 0s 1 normal forwards running",
|
||||
expand: "expand 400ms linear 0s 1 normal forwards running",
|
||||
"slide-in": "slide-in 400ms linear 0s 1 normal forwards running",
|
||||
"slide-out": "slide-out 400ms linear 0s 1 normal forwards running",
|
||||
collapse: "collapse 400ms linear 0s 1 normal forwards running",
|
||||
},
|
||||
keyframes: {
|
||||
"accordion-down": {
|
||||
from: { height: "0" },
|
||||
|
||||
Reference in New Issue
Block a user