feat: add new component for search input

This commit is contained in:
Pablo Lara
2024-09-06 07:59:35 +02:00
parent 1bc6ac06a4
commit 4e8aae4f9e
4 changed files with 82 additions and 92 deletions
+61
View File
@@ -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"
/>
);
};
+5 -7
View File
@@ -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>
);
}
+1 -1
View File
@@ -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 }),
}),