feat: search bar (#14)

* feat: add search / filter functionality
- new searchbar component
- adjusted layout of search, filter and sort for each data page

* fix: adjust profile name retrieval
This commit is contained in:
Sam Chau
2024-12-08 08:23:01 +10:30
committed by Sam Chau
parent 53bef9c996
commit 9970f52124
4 changed files with 164 additions and 52 deletions

View File

@@ -9,6 +9,7 @@ import FilterMenu from '../ui/FilterMenu';
import SortMenu from '../ui/SortMenu';
import {Loader} from 'lucide-react';
import Alert from '@ui/Alert';
import SearchBar from '@ui/SearchBar';
import {useFormatModal} from '@hooks/useFormatModal';
function FormatPage() {
@@ -23,6 +24,7 @@ function FormatPage() {
const [isCloning, setIsCloning] = useState(false);
const [isLoading, setIsLoading] = useState(true);
const [mergeConflicts, setMergeConflicts] = useState([]);
const [searchQuery, setSearchQuery] = useState('');
const navigate = useNavigate();
@@ -144,20 +146,27 @@ function FormatPage() {
const getFilteredAndSortedFormats = () => {
let filtered = [...formats];
// Apply filters
// Apply search filter
if (searchQuery) {
filtered = filtered.filter(
format =>
format.content.name
.toLowerCase()
.includes(searchQuery.toLowerCase()) ||
format.content.tags?.some(tag =>
tag.toLowerCase().includes(searchQuery.toLowerCase())
)
);
}
// Apply existing filters
if (filterType === 'tag' && filterValue) {
filtered = filtered.filter(format =>
format.content.tags?.includes(filterValue)
);
} else if (filterType === 'name' && filterValue) {
filtered = filtered.filter(format =>
format.content.name
.toLowerCase()
.includes(filterValue.toLowerCase())
);
}
// Apply sorting
// Rest of the sorting logic remains the same...
return filtered.sort((a, b) => {
switch (sortBy) {
case 'dateModified':
@@ -172,7 +181,6 @@ function FormatPage() {
}
});
};
if (isLoading) {
return (
<div className='flex flex-col items-center justify-center h-64'>
@@ -218,23 +226,23 @@ function FormatPage() {
return (
<div>
<div className='mb-4 flex items-center space-x-4'>
<SortMenu
sortBy={sortBy}
setSortBy={setSortBy}
options={[
{key: 'name', label: 'Name'},
{key: 'dateModified', label: 'Date Modified'},
{key: 'dateCreated', label: 'Date Created'}
]}
/>
<FilterMenu
filterType={filterType}
setFilterType={setFilterType}
filterValue={filterValue}
setFilterValue={setFilterValue}
allTags={allTags}
<div className='mb-4 flex items-center gap-4'>
<SearchBar
onSearch={setSearchQuery}
placeholder='Search by name or tag...'
/>
<div className='flex-none'>
<SortMenu sortBy={sortBy} setSortBy={setSortBy} />
</div>
<div className='flex-none'>
<FilterMenu
filterType={filterType}
setFilterType={setFilterType}
filterValue={filterValue}
setFilterValue={setFilterValue}
allTags={allTags}
/>
</div>
</div>
<div className='grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4 mb-4'>

View File

@@ -8,6 +8,7 @@ import {Profiles, CustomFormats} from '@api/data';
import FilterMenu from '../ui/FilterMenu';
import SortMenu from '../ui/SortMenu';
import {Loader} from 'lucide-react';
import SearchBar from '@ui/SearchBar';
function ProfilePage() {
const [profiles, setProfiles] = useState([]);
@@ -21,6 +22,7 @@ function ProfilePage() {
const [isCloning, setIsCloning] = useState(false);
const [isLoading, setIsLoading] = useState(true);
const [mergeConflicts, setMergeConflicts] = useState([]);
const [searchQuery, setSearchQuery] = useState('');
const navigate = useNavigate();
@@ -136,22 +138,44 @@ function ProfilePage() {
const sortedAndFilteredProfiles = profiles
.filter(profile => {
// Apply search filter
if (searchQuery) {
return (
profile.content.name
.toLowerCase()
.includes(searchQuery.toLowerCase()) ||
profile.content.tags?.some(
(
tag // Changed from profile.tags
) =>
tag
.toLowerCase()
.includes(searchQuery.toLowerCase())
)
);
}
// Apply existing filters
if (filterType === 'tag') {
return profile.tags && profile.tags.includes(filterValue);
return (
profile.content.tags &&
profile.content.tags.includes(filterValue)
); // Changed from profile.tags
}
if (filterType === 'date') {
const profileDate = new Date(profile.date_modified);
const profileDate = new Date(profile.modified_date); // This looks correct already
const filterDate = new Date(filterValue);
return profileDate.toDateString() === filterDate.toDateString();
}
return true;
})
.sort((a, b) => {
if (sortBy === 'name') return a.name.localeCompare(b.name);
if (sortBy === 'name')
return a.content.name.localeCompare(b.content.name); // Changed from a.name
if (sortBy === 'dateCreated')
return new Date(b.date_created) - new Date(a.date_created);
return new Date(b.created_date) - new Date(a.created_date);
if (sortBy === 'dateModified')
return new Date(b.date_modified) - new Date(a.date_modified);
return new Date(b.modified_date) - new Date(a.modified_date);
return 0;
});
@@ -200,15 +224,23 @@ function ProfilePage() {
return (
<div>
<div className='mb-4 flex items-center space-x-4'>
<SortMenu sortBy={sortBy} setSortBy={setSortBy} />
<FilterMenu
filterType={filterType}
setFilterType={setFilterType}
filterValue={filterValue}
setFilterValue={setFilterValue}
allTags={allTags}
<div className='mb-4 flex items-center gap-4'>
<SearchBar
onSearch={setSearchQuery}
placeholder='Search by name or tag...'
/>
<div className='flex-none'>
<SortMenu sortBy={sortBy} setSortBy={setSortBy} />
</div>
<div className='flex-none'>
<FilterMenu
filterType={filterType}
setFilterType={setFilterType}
filterValue={filterValue}
setFilterValue={setFilterValue}
allTags={allTags}
/>
</div>
</div>
<div className='grid grid-cols-1 sm:grid-cols-2 gap-4 mb-4'>
{sortedAndFilteredProfiles.map(profile => (

View File

@@ -7,6 +7,7 @@ import FilterMenu from '../ui/FilterMenu';
import SortMenu from '../ui/SortMenu';
import {Loader} from 'lucide-react';
import Alert from '@ui/Alert';
import SearchBar from '@ui/SearchBar';
function RegexPage() {
const [patterns, setPatterns] = useState([]);
@@ -18,6 +19,7 @@ function RegexPage() {
const [allTags, setAllTags] = useState([]);
const [isCloning, setIsCloning] = useState(false);
const [isLoading, setIsLoading] = useState(true);
const [searchQuery, setSearchQuery] = useState('');
// Load initial data
useEffect(() => {
@@ -84,18 +86,27 @@ function RegexPage() {
const getFilteredAndSortedPatterns = () => {
let filtered = [...patterns];
// Apply filters
// Apply search filter
if (searchQuery) {
filtered = filtered.filter(
pattern =>
pattern.name
.toLowerCase()
.includes(searchQuery.toLowerCase()) ||
pattern.tags?.some(tag =>
tag.toLowerCase().includes(searchQuery.toLowerCase())
)
);
}
// Apply existing filters
if (filterType === 'tag' && filterValue) {
filtered = filtered.filter(pattern =>
pattern.tags?.includes(filterValue)
);
} else if (filterType === 'name' && filterValue) {
filtered = filtered.filter(pattern =>
pattern.name.toLowerCase().includes(filterValue.toLowerCase())
);
}
// Apply sorting
// Rest of the sorting logic remains the same...
return filtered.sort((a, b) => {
switch (sortBy) {
case 'dateModified':
@@ -125,15 +136,23 @@ function RegexPage() {
return (
<div>
<div className='mb-4 flex items-center space-x-4'>
<SortMenu sortBy={sortBy} setSortBy={setSortBy} />
<FilterMenu
filterType={filterType}
setFilterType={setFilterType}
filterValue={filterValue}
setFilterValue={setFilterValue}
allTags={allTags}
<div className='mb-4 flex items-center gap-4'>
<SearchBar
onSearch={setSearchQuery}
placeholder='Search by name or tag...'
/>
<div className='flex-none'>
<SortMenu sortBy={sortBy} setSortBy={setSortBy} />
</div>
<div className='flex-none'>
<FilterMenu
filterType={filterType}
setFilterType={setFilterType}
filterValue={filterValue}
setFilterValue={setFilterValue}
allTags={allTags}
/>
</div>
</div>
<div className='grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4 mb-4'>
{getFilteredAndSortedPatterns().map(pattern => (

View File

@@ -0,0 +1,53 @@
import React, {useState, useEffect} from 'react';
import {Search, X} from 'lucide-react';
const SearchBar = ({
onSearch,
placeholder = 'Search...',
value = '',
className = ''
}) => {
const [searchTerm, setSearchTerm] = useState(value);
useEffect(() => {
setSearchTerm(value);
}, [value]);
const handleChange = e => {
const newValue = e.target.value;
setSearchTerm(newValue);
onSearch(newValue);
};
const clearSearch = () => {
setSearchTerm('');
onSearch('');
};
return (
<div className={`relative flex-1 min-w-0 ${className}`}>
<Search className='absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-gray-400' />
<input
type='text'
value={searchTerm}
onChange={handleChange}
placeholder={placeholder}
className='w-full h-10 pl-9 pr-8 rounded-md border border-gray-300
bg-white dark:bg-gray-800 dark:border-gray-700
text-gray-900 dark:text-gray-100
placeholder:text-gray-500 dark:placeholder:text-gray-400
focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent
transition-colors'
/>
{searchTerm && (
<button
onClick={clearSearch}
className='absolute right-3 top-1/2 -translate-y-1/2 p-1 rounded-full hover:bg-gray-100 dark:hover:bg-gray-700'>
<X className='h-4 w-4 text-gray-400' />
</button>
)}
</div>
);
};
export default SearchBar;