mirror of
https://github.com/prowler-cloud/prowler.git
synced 2026-07-22 20:11:53 +00:00
feat: add new component for search input
This commit is contained in:
@@ -0,0 +1,61 @@
|
||||
import { Input } from "@nextui-org/react";
|
||||
import { XCircle } from "lucide-react";
|
||||
import { useRouter, useSearchParams } from "next/navigation";
|
||||
import React, { useCallback, useEffect, useState } from "react";
|
||||
|
||||
import { useDebounce } from "../../hooks/useDebounce";
|
||||
|
||||
export const CustomSearchInput: React.FC = () => {
|
||||
const router = useRouter();
|
||||
const searchParams = useSearchParams();
|
||||
const [searchQuery, setSearchQuery] = useState(() => {
|
||||
return searchParams.get("filter[search]") || "";
|
||||
});
|
||||
const debouncedSearchQuery = useDebounce(searchQuery, 300);
|
||||
|
||||
const applySearch = useCallback(
|
||||
(query: string) => {
|
||||
const params = new URLSearchParams(searchParams.toString());
|
||||
if (query) {
|
||||
params.set("filter[search]", query);
|
||||
} else {
|
||||
params.delete("filter[search]");
|
||||
}
|
||||
router.replace(`?${params.toString()}`, { scroll: false });
|
||||
},
|
||||
[router, searchParams],
|
||||
);
|
||||
|
||||
const clearIconSearch = () => {
|
||||
setSearchQuery("");
|
||||
applySearch("");
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
applySearch(debouncedSearchQuery);
|
||||
}, [debouncedSearchQuery, applySearch]);
|
||||
|
||||
return (
|
||||
<Input
|
||||
variant="bordered"
|
||||
placeholder="Search..."
|
||||
label="Search"
|
||||
labelPlacement="inside"
|
||||
value={searchQuery}
|
||||
onChange={(e) => setSearchQuery(e.target.value)}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === "Enter") {
|
||||
applySearch(searchQuery);
|
||||
}
|
||||
}}
|
||||
endContent={
|
||||
searchQuery && (
|
||||
<button onClick={clearIconSearch} className="focus:outline-none">
|
||||
<XCircle className="h-4 w-4 text-default-400" />
|
||||
</button>
|
||||
)
|
||||
}
|
||||
size="sm"
|
||||
/>
|
||||
);
|
||||
};
|
||||
@@ -2,30 +2,26 @@
|
||||
|
||||
import { Button } from "@nextui-org/react";
|
||||
import { useRouter, useSearchParams } from "next/navigation";
|
||||
import React from "react";
|
||||
import { useCallback } from "react";
|
||||
import React, { useCallback } from "react";
|
||||
|
||||
// import { SearchParamsProps } from "../../types/components";
|
||||
import { CustomAccountSelection } from "./CustomAccountSelection";
|
||||
import { CustomCheckboxMutedFindings } from "./CustomCheckboxMutedFindings";
|
||||
import { CustomDatePicker } from "./CustomDatePicker";
|
||||
import { CustomSearchInput } from "./CustomSearchInput";
|
||||
import { CustomSelectProvider } from "./CustomSelectProvider";
|
||||
|
||||
interface FilterControlsProps {
|
||||
mutedFindings?: boolean;
|
||||
// searchParams: SearchParamsProps;
|
||||
}
|
||||
|
||||
export const FilterControls: React.FC<FilterControlsProps> = ({
|
||||
mutedFindings = true,
|
||||
// searchParams,
|
||||
}) => {
|
||||
const router = useRouter();
|
||||
const searchParams = useSearchParams();
|
||||
|
||||
const clearAllFilters = useCallback(() => {
|
||||
const params = new URLSearchParams(searchParams.toString());
|
||||
// Remove all filter parameters
|
||||
Array.from(params.keys()).forEach((key) => {
|
||||
if (key.startsWith("filter[")) {
|
||||
params.delete(key);
|
||||
@@ -33,14 +29,16 @@ export const FilterControls: React.FC<FilterControlsProps> = ({
|
||||
});
|
||||
router.push(`?${params.toString()}`, { scroll: false });
|
||||
}, [router, searchParams]);
|
||||
|
||||
return (
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-4 gap-x-4 gap-y-4 items-center">
|
||||
<CustomSearchInput />
|
||||
<CustomSelectProvider />
|
||||
<CustomDatePicker />
|
||||
<CustomAccountSelection />
|
||||
<CustomCheckboxMutedFindings mutedFindings={mutedFindings} />
|
||||
<Button
|
||||
className="w-fit"
|
||||
className="w-full md:w-fit"
|
||||
onClick={clearAllFilters}
|
||||
variant="flat"
|
||||
color="default"
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
import { Button, Input } from "@nextui-org/react";
|
||||
import { XCircle } from "lucide-react"; // Import the clear icon
|
||||
import { Button } from "@nextui-org/react";
|
||||
import { useRouter, useSearchParams } from "next/navigation";
|
||||
import { useCallback, useEffect, useMemo, useState } from "react";
|
||||
|
||||
import { useDebounce } from "@/hooks/useDebounce";
|
||||
import { useCallback, useMemo } from "react";
|
||||
|
||||
interface FilterColumnTableProps {
|
||||
filters: { key: string; values: string[] }[];
|
||||
@@ -12,11 +9,6 @@ interface FilterColumnTableProps {
|
||||
export function FilterColumnTable({ filters }: FilterColumnTableProps) {
|
||||
const router = useRouter();
|
||||
const searchParams = useSearchParams();
|
||||
const [searchQuery, setSearchQuery] = useState(() => {
|
||||
// Initialize searchQuery with the current search filter value from URL
|
||||
return searchParams.get("filter[search]") || "";
|
||||
});
|
||||
const debouncedSearchQuery = useDebounce(searchQuery, 300);
|
||||
|
||||
const activeFilters = useMemo(() => {
|
||||
const currentFilters: Record<string, string> = {};
|
||||
@@ -24,7 +16,6 @@ export function FilterColumnTable({ filters }: FilterColumnTableProps) {
|
||||
if (key.startsWith("filter[") && key.endsWith("]")) {
|
||||
const filterKey = key.slice(7, -1);
|
||||
if (filters.some((filter) => filter.key === filterKey)) {
|
||||
// eslint-disable-next-line security/detect-object-injection
|
||||
currentFilters[filterKey] = value;
|
||||
}
|
||||
}
|
||||
@@ -38,10 +29,8 @@ export function FilterColumnTable({ filters }: FilterColumnTableProps) {
|
||||
const filterKey = `filter[${key}]`;
|
||||
|
||||
if (params.get(filterKey) === value) {
|
||||
// If the filter is already set, remove it
|
||||
params.delete(filterKey);
|
||||
} else {
|
||||
// Otherwise, set or update the filter
|
||||
params.set(filterKey, value);
|
||||
}
|
||||
|
||||
@@ -50,78 +39,20 @@ export function FilterColumnTable({ filters }: FilterColumnTableProps) {
|
||||
[router, searchParams],
|
||||
);
|
||||
|
||||
const applySearch = useCallback(
|
||||
(query: string) => {
|
||||
const params = new URLSearchParams(searchParams.toString());
|
||||
if (query) {
|
||||
params.set("filter[search]", query);
|
||||
} else {
|
||||
params.delete("filter[search]");
|
||||
}
|
||||
router.replace(`?${params.toString()}`, { scroll: false });
|
||||
},
|
||||
[router, searchParams],
|
||||
);
|
||||
|
||||
const clearAllFilters = useCallback(() => {
|
||||
const params = new URLSearchParams();
|
||||
router.push(`?${params.toString()}`, { scroll: false });
|
||||
setSearchQuery(""); // Clear the search input
|
||||
}, [router]);
|
||||
|
||||
const clearIconSearch = () => {
|
||||
setSearchQuery("");
|
||||
applySearch("");
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
applySearch(debouncedSearchQuery);
|
||||
}, [debouncedSearchQuery, applySearch]);
|
||||
|
||||
return (
|
||||
<div className="flex flex-col md:flex-row justify-between space-y-4 w-full items-center">
|
||||
<div className="w-full md:w-1/3 flex space-x-2">
|
||||
<Input
|
||||
placeholder="Search..."
|
||||
value={searchQuery}
|
||||
onChange={(e) => setSearchQuery(e.target.value)}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === "Enter") {
|
||||
applySearch(searchQuery);
|
||||
}
|
||||
}}
|
||||
endContent={
|
||||
searchQuery && (
|
||||
<button onClick={clearIconSearch} className="focus:outline-none">
|
||||
<XCircle className="h-4 w-4 text-default-400" />
|
||||
</button>
|
||||
)
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex items-center space-x-2 flex-wrap">
|
||||
{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>
|
||||
)),
|
||||
)}
|
||||
<Button
|
||||
onClick={clearAllFilters}
|
||||
variant="flat"
|
||||
color="default"
|
||||
size="sm"
|
||||
>
|
||||
Clear Filters
|
||||
</Button>
|
||||
</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)}
|
||||
variant={activeFilters[key] === value ? "faded" : "light"}
|
||||
size="sm"
|
||||
>
|
||||
{value || "All"}
|
||||
</Button>
|
||||
)),
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ interface SidebarStoreState {
|
||||
export const useUIStore = create<SidebarStoreState>()(
|
||||
persist(
|
||||
(set) => ({
|
||||
isSideMenuOpen: true,
|
||||
isSideMenuOpen: false,
|
||||
openSideMenu: () => set({ isSideMenuOpen: true }),
|
||||
closeSideMenu: () => set({ isSideMenuOpen: false }),
|
||||
}),
|
||||
|
||||
Reference in New Issue
Block a user