mirror of
https://github.com/cssgunc/compass.git
synced 2025-04-07 05:00:16 -04:00
Merge remote-tracking branch 'origin/Ilakkiya-admin-GEN-103-filter' into admin-GEN-57-all-together-now
This commit is contained in:
commit
38d640edc9
53
compass/components/FilterBox/ContainsDropdown.tsx
Normal file
53
compass/components/FilterBox/ContainsDropdown.tsx
Normal file
|
@ -0,0 +1,53 @@
|
||||||
|
import { useState } from "react";
|
||||||
|
import { ChevronDownIcon } from "@heroicons/react/24/solid";
|
||||||
|
|
||||||
|
const mockTags = ["food relief", "period poverty", "nutrition education"];
|
||||||
|
|
||||||
|
type FilterType = "contains" | "does not contain" | "is empty" | "is not empty";
|
||||||
|
|
||||||
|
export const ContainsDropdown = ({
|
||||||
|
isDropdownOpen,
|
||||||
|
setIsDropdownOpen,
|
||||||
|
filterType,
|
||||||
|
setFilterType,
|
||||||
|
}) => {
|
||||||
|
const handleFilterTypeChange = (type: FilterType) => {
|
||||||
|
setFilterType(type);
|
||||||
|
setIsDropdownOpen(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="relative">
|
||||||
|
<div
|
||||||
|
className={`absolute z-10 mt-8 -top-28 bg-white border border-gray-300 rounded-md shadow-md p-2 ${
|
||||||
|
isDropdownOpen ? "block" : "hidden"
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="cursor-pointer hover:bg-gray-100 rounded"
|
||||||
|
onClick={() => handleFilterTypeChange("contains")}
|
||||||
|
>
|
||||||
|
Contains
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className="cursor-pointer hover:bg-gray-100 rounded"
|
||||||
|
onClick={() => handleFilterTypeChange("does not contain")}
|
||||||
|
>
|
||||||
|
Does not contain
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className="cursor-pointer hover:bg-gray-100 rounded"
|
||||||
|
onClick={() => handleFilterTypeChange("is empty")}
|
||||||
|
>
|
||||||
|
Is empty
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className="cursor-pointer hover:bg-gray-100 rounded"
|
||||||
|
onClick={() => handleFilterTypeChange("is not empty")}
|
||||||
|
>
|
||||||
|
Is not empty
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
93
compass/components/FilterBox/index.tsx
Normal file
93
compass/components/FilterBox/index.tsx
Normal file
|
@ -0,0 +1,93 @@
|
||||||
|
// FilterBox.tsx
|
||||||
|
import { useState } from "react";
|
||||||
|
import { ChevronDownIcon } from "@heroicons/react/24/solid";
|
||||||
|
import { ContainsDropdown } from "./ContainsDropdown";
|
||||||
|
|
||||||
|
const mockTags = ["food relief", "period poverty", "nutrition education"];
|
||||||
|
|
||||||
|
type FilterType = "contains" | "does not contain" | "is empty" | "is not empty";
|
||||||
|
|
||||||
|
export const FilterBox = () => {
|
||||||
|
const [selectedTags, setSelectedTags] = useState<string[]>([]);
|
||||||
|
const [searchTerm, setSearchTerm] = useState("");
|
||||||
|
const [showContainsDropdown, setShowContainsDropdown] = useState(false);
|
||||||
|
const [filterType, setFilterType] = useState<FilterType>("contains");
|
||||||
|
|
||||||
|
const handleTagChange = (tag: string) => {
|
||||||
|
setSelectedTags((prevTags) =>
|
||||||
|
prevTags.includes(tag)
|
||||||
|
? prevTags.filter((t) => t !== tag)
|
||||||
|
: [...prevTags, tag]
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSearchChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
setSearchTerm(e.target.value);
|
||||||
|
};
|
||||||
|
|
||||||
|
const renderSelectedTags = () =>
|
||||||
|
selectedTags.map((tag) => (
|
||||||
|
<div
|
||||||
|
key={tag}
|
||||||
|
className="bg-blue-100 text-blue-800 px-2 py-1 rounded-md flex items-center mr-2"
|
||||||
|
>
|
||||||
|
<span>{tag}</span>
|
||||||
|
<span
|
||||||
|
className="ml-2 cursor-pointer"
|
||||||
|
onClick={() => handleTagChange(tag)}
|
||||||
|
>
|
||||||
|
×
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
));
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="text-xs bg-white border border-gray-300 z-50 rounded-md p-2 shadow absolute right-5 top-[200px]">
|
||||||
|
<div className="mb-2">
|
||||||
|
<span className="font-semibold">
|
||||||
|
Tags{" "}
|
||||||
|
<button
|
||||||
|
onClick={() => setShowContainsDropdown((prevState) => !prevState)}
|
||||||
|
className="hover:bg-gray-50 text-gray-500 hover:text-gray-700"
|
||||||
|
>
|
||||||
|
{filterType} <ChevronDownIcon className="inline h-3" />
|
||||||
|
</button>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-wrap mb-2 px-2 py-1 border border-gray-300 rounded w-full">
|
||||||
|
{selectedTags.length > 0 && renderSelectedTags()}
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
value={searchTerm}
|
||||||
|
onChange={handleSearchChange}
|
||||||
|
placeholder="Search tags..."
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="max-h-48 overflow-y-auto">
|
||||||
|
{mockTags
|
||||||
|
.filter((tag) =>
|
||||||
|
tag.toLowerCase().includes(searchTerm.toLowerCase())
|
||||||
|
)
|
||||||
|
.map((tag) => (
|
||||||
|
<div key={tag} className="flex items-center">
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
checked={selectedTags.includes(tag)}
|
||||||
|
onChange={() => handleTagChange(tag)}
|
||||||
|
className="mr-2"
|
||||||
|
/>
|
||||||
|
<label>{tag}</label>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
{showContainsDropdown && (
|
||||||
|
<ContainsDropdown
|
||||||
|
isDropdownOpen={showContainsDropdown}
|
||||||
|
setIsDropdownOpen={setShowContainsDropdown}
|
||||||
|
filterType={filterType}
|
||||||
|
setFilterType={setFilterType}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
|
@ -135,6 +135,7 @@ export const Table = () => {
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Filtering
|
// TODO: Filtering
|
||||||
|
|
||||||
// TODO: Sorting
|
// TODO: Sorting
|
||||||
|
|
||||||
// added this fn for editing rows
|
// added this fn for editing rows
|
||||||
|
|
|
@ -1,46 +1,64 @@
|
||||||
/** The actions (Filter, Sort, Search) at the top of the table. */
|
// TableAction.tsx
|
||||||
import { MagnifyingGlassIcon } from "@heroicons/react/24/solid";
|
import { MagnifyingGlassIcon } from "@heroicons/react/24/solid";
|
||||||
import { ChangeEventHandler, Dispatch, FunctionComponent, SetStateAction, useRef, useState } from "react";
|
import { ChangeEventHandler, FunctionComponent, useRef, useState } from "react";
|
||||||
|
import { FilterBox } from "../FilterBox";
|
||||||
|
|
||||||
type TableActionProps = {
|
type TableActionProps = {
|
||||||
query: string
|
query: string;
|
||||||
handleChange: ChangeEventHandler<HTMLInputElement>
|
handleChange: ChangeEventHandler<HTMLInputElement>;
|
||||||
}
|
};
|
||||||
|
|
||||||
export const TableAction: FunctionComponent<TableActionProps> = ({query, handleChange}) => {
|
export const TableAction: FunctionComponent<TableActionProps> = ({
|
||||||
|
query,
|
||||||
|
handleChange,
|
||||||
|
}) => {
|
||||||
const searchInput = useRef<HTMLInputElement>(null);
|
const searchInput = useRef<HTMLInputElement>(null);
|
||||||
const [searchActive, setSearchActive] = useState(false);
|
const [searchActive, setSearchActive] = useState(false);
|
||||||
|
const [showFilterBox, setShowFilterBox] = useState(false);
|
||||||
|
|
||||||
const activateSearch = () => {
|
const activateSearch = () => {
|
||||||
setSearchActive(true);
|
setSearchActive(true);
|
||||||
if (searchInput.current === null) { return; }
|
if (searchInput.current === null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
searchInput.current.focus();
|
searchInput.current.focus();
|
||||||
searchInput.current.addEventListener("focusout", () => {
|
searchInput.current.addEventListener("focusout", () => {
|
||||||
if (searchInput.current?.value.trim() === "") {
|
if (searchInput.current?.value.trim() === "") {
|
||||||
searchInput.current.value = "";
|
searchInput.current.value = "";
|
||||||
deactivateSearch();
|
deactivateSearch();
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
}
|
};
|
||||||
|
|
||||||
const deactivateSearch = () => setSearchActive(false);
|
const deactivateSearch = () => setSearchActive(false);
|
||||||
|
|
||||||
|
const toggleFilterBox = () => setShowFilterBox((prev) => !prev);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="w-auto flex flex-row gap-x-0.5 items-center justify-between text-xs font-medium text-gray-500 p-2">
|
<div className="w-auto flex flex-row gap-x-0.5 items-center justify-between text-xs font-medium text-gray-500 p-2">
|
||||||
<span className="p-1 rounded hover:bg-gray-100">Filter</span>
|
<span
|
||||||
|
className="p-1 rounded hover:text-purple-700 focus:bg-purple-50 hover:bg-purple-50"
|
||||||
|
onClick={toggleFilterBox}
|
||||||
|
>
|
||||||
|
Filter
|
||||||
|
</span>
|
||||||
|
{showFilterBox && <FilterBox />}
|
||||||
<span className="p-1 rounded hover:bg-gray-100">Sort</span>
|
<span className="p-1 rounded hover:bg-gray-100">Sort</span>
|
||||||
<span className="p-1 rounded hover:bg-gray-100" onClick={() => activateSearch()}>
|
<span className="p-1 rounded hover:bg-gray-100" onClick={activateSearch}>
|
||||||
<MagnifyingGlassIcon className="w-4 h-4 inline" />
|
<MagnifyingGlassIcon className="w-4 h-4 inline" />
|
||||||
</span>
|
</span>
|
||||||
<input
|
<input
|
||||||
ref={searchInput}
|
ref={searchInput}
|
||||||
className={"outline-none transition-all duration-300 " + (searchActive ? "w-48" : "w-0")}
|
className={
|
||||||
|
"outline-none transition-all duration-300 " +
|
||||||
|
(searchActive ? "w-48" : "w-0")
|
||||||
|
}
|
||||||
type="text"
|
type="text"
|
||||||
name="search"
|
name="search"
|
||||||
placeholder="Type to search..."
|
placeholder="Type to search..."
|
||||||
value={query ?? ""}
|
value={query ?? ""}
|
||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
|
@ -1,9 +1,12 @@
|
||||||
import { MagnifyingGlassIcon, XMarkIcon } from "@heroicons/react/24/solid"
|
import { ChevronDownIcon, MagnifyingGlassIcon, XMarkIcon } from "@heroicons/react/24/solid"
|
||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
import Image from 'next/image';
|
import Image from 'next/image';
|
||||||
|
import { FilterBox } from "../FilterBox";
|
||||||
|
|
||||||
export const LandingSearchBar: React.FC = () => {
|
export const LandingSearchBar: React.FC = () => {
|
||||||
const [searchTerm, setSearchTerm] = useState('');
|
const [searchTerm, setSearchTerm] = useState('');
|
||||||
|
const [showFilterBox, setShowFilterBox] = useState(false);
|
||||||
|
const toggleFilterBox = () => setShowFilterBox((prev) => !prev);
|
||||||
|
|
||||||
const handleSearchChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
const handleSearchChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
setSearchTerm(event.target.value);
|
setSearchTerm(event.target.value);
|
||||||
|
@ -34,7 +37,9 @@ export const LandingSearchBar: React.FC = () => {
|
||||||
<XMarkIcon className="h-5 w-5 text-gray-500" aria-hidden="true" />
|
<XMarkIcon className="h-5 w-5 text-gray-500" aria-hidden="true" />
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
<div className="p-3">
|
<div className="flex flex-row space-x-1 p-3">
|
||||||
|
<span><ChevronDownIcon className="h-5 w-5 text-gray-500" onClick={toggleFilterBox} /></span>
|
||||||
|
{showFilterBox && <FilterBox className="relative top-50" />}
|
||||||
<MagnifyingGlassIcon className="h-5 w-5 text-gray-500" aria-hidden="true" />
|
<MagnifyingGlassIcon className="h-5 w-5 text-gray-500" aria-hidden="true" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
6
package-lock.json
generated
Normal file
6
package-lock.json
generated
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
{
|
||||||
|
"name": "compass",
|
||||||
|
"lockfileVersion": 3,
|
||||||
|
"requires": true,
|
||||||
|
"packages": {}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user