mirror of
https://github.com/Dictionarry-Hub/profilarr.git
synced 2026-01-29 05:50:51 +01:00
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:
@@ -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'>
|
||||
|
||||
@@ -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 => (
|
||||
|
||||
@@ -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 => (
|
||||
|
||||
53
frontend/src/components/ui/SearchBar.jsx
Normal file
53
frontend/src/components/ui/SearchBar.jsx
Normal 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;
|
||||
Reference in New Issue
Block a user