mirror of
https://github.com/Dictionarry-Hub/profilarr.git
synced 2026-01-22 10:51:02 +01:00
feat: integrate useSearch hook for enhanced filtering and sorting in FormatPage and RegexPage components
This commit is contained in:
@@ -13,6 +13,7 @@ import MassActionsBar from '@ui/MassActionsBar';
|
||||
import ImportModal from '@ui/ImportModal';
|
||||
import {importFormats} from '@api/import';
|
||||
import DataBar from '@ui/DataBar/DataBar';
|
||||
import useSearch from '@hooks/useSearch';
|
||||
|
||||
const loadingMessages = [
|
||||
'Formatting the formatters...',
|
||||
@@ -62,19 +63,40 @@ function FormatPage() {
|
||||
const [formats, setFormats] = useState([]);
|
||||
const [isModalOpen, setIsModalOpen] = useState(false);
|
||||
const [selectedFormat, setSelectedFormat] = useState(null);
|
||||
const [sortBy, setSortBy] = useState('name');
|
||||
const [filterType, setFilterType] = useState('none');
|
||||
const [filterValue, setFilterValue] = useState('');
|
||||
const [allTags, setAllTags] = useState([]);
|
||||
const [isCloning, setIsCloning] = useState(false);
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
const [mergeConflicts, setMergeConflicts] = useState([]);
|
||||
const [searchQuery, setSearchQuery] = useState('');
|
||||
const [isImportModalOpen, setIsImportModalOpen] = useState(false);
|
||||
const [willBeSelected, setWillBeSelected] = useState([]);
|
||||
|
||||
const navigate = useNavigate();
|
||||
|
||||
// Initialize useSearch hook
|
||||
const {
|
||||
searchTerms,
|
||||
currentInput,
|
||||
setCurrentInput,
|
||||
addSearchTerm,
|
||||
removeSearchTerm,
|
||||
clearSearchTerms,
|
||||
filterType,
|
||||
setFilterType,
|
||||
filterValue,
|
||||
setFilterValue,
|
||||
sortBy,
|
||||
setSortBy,
|
||||
items: filteredFormats
|
||||
} = useSearch(formats, {
|
||||
searchableFields: ['content.name', 'content.tags'],
|
||||
initialSortBy: 'name',
|
||||
sortOptions: {
|
||||
name: (a, b) => a.content.name.localeCompare(b.content.name),
|
||||
dateModified: (a, b) =>
|
||||
new Date(b.modified_date) - new Date(a.modified_date)
|
||||
}
|
||||
});
|
||||
|
||||
const {
|
||||
selectedItems,
|
||||
isSelectionMode,
|
||||
@@ -228,7 +250,6 @@ function FormatPage() {
|
||||
|
||||
const handleMassDelete = async () => {
|
||||
try {
|
||||
const filteredFormats = getFilteredAndSortedFormats();
|
||||
const selectedFormats = Array.from(selectedItems).map(
|
||||
index => filteredFormats[index]
|
||||
);
|
||||
@@ -246,7 +267,6 @@ function FormatPage() {
|
||||
'Failed to delete selected formats'
|
||||
);
|
||||
} finally {
|
||||
// Always reload data and reset selection state, regardless of success/failure
|
||||
fetchFormats();
|
||||
toggleSelectionMode();
|
||||
clearSelection();
|
||||
@@ -255,7 +275,6 @@ function FormatPage() {
|
||||
|
||||
const handleMassImport = async arr => {
|
||||
try {
|
||||
const filteredFormats = getFilteredAndSortedFormats();
|
||||
const selectedFormats = Array.from(selectedItems).map(
|
||||
index => filteredFormats[index]
|
||||
);
|
||||
@@ -275,7 +294,7 @@ function FormatPage() {
|
||||
if (e.shiftKey) {
|
||||
handleMouseEnter(index, true);
|
||||
}
|
||||
handleSelect(formatName, index, e, getFilteredAndSortedFormats());
|
||||
handleSelect(formatName, index, e, filteredFormats);
|
||||
};
|
||||
|
||||
const handleMouseEnter = (index, isShiftKey) => {
|
||||
@@ -283,7 +302,7 @@ function FormatPage() {
|
||||
const start = Math.min(lastSelectedIndex, index);
|
||||
const end = Math.max(lastSelectedIndex, index);
|
||||
|
||||
const potentialSelection = getFilteredAndSortedFormats()
|
||||
const potentialSelection = filteredFormats
|
||||
.slice(start, end + 1)
|
||||
.map((format, idx) => idx + start);
|
||||
|
||||
@@ -291,40 +310,6 @@ function FormatPage() {
|
||||
}
|
||||
};
|
||||
|
||||
const getFilteredAndSortedFormats = () => {
|
||||
let filtered = [...formats];
|
||||
|
||||
if (searchQuery) {
|
||||
filtered = filtered.filter(
|
||||
format =>
|
||||
format.content.name
|
||||
.toLowerCase()
|
||||
.includes(searchQuery.toLowerCase()) ||
|
||||
format.content.tags?.some(tag =>
|
||||
tag.toLowerCase().includes(searchQuery.toLowerCase())
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
if (filterType === 'tag' && filterValue) {
|
||||
filtered = filtered.filter(format =>
|
||||
format.content.tags?.includes(filterValue)
|
||||
);
|
||||
}
|
||||
|
||||
return filtered.sort((a, b) => {
|
||||
switch (sortBy) {
|
||||
case 'dateModified':
|
||||
return (
|
||||
new Date(b.modified_date) - new Date(a.modified_date)
|
||||
);
|
||||
case 'name':
|
||||
default:
|
||||
return a.content.name.localeCompare(b.content.name);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
if (isLoading) {
|
||||
return <LoadingState />;
|
||||
}
|
||||
@@ -338,7 +323,12 @@ function FormatPage() {
|
||||
return (
|
||||
<div className='w-full min-h-[70vh] space-y-2 flex flex-col'>
|
||||
<DataBar
|
||||
onSearch={setSearchQuery}
|
||||
searchTerms={searchTerms}
|
||||
currentInput={currentInput}
|
||||
onInputChange={setCurrentInput}
|
||||
onAddTerm={addSearchTerm}
|
||||
onRemoveTerm={removeSearchTerm}
|
||||
onClearTerms={clearSearchTerms}
|
||||
searchPlaceholder='Search by name or tag...'
|
||||
filterType={filterType}
|
||||
setFilterType={setFilterType}
|
||||
@@ -354,7 +344,7 @@ function FormatPage() {
|
||||
/>
|
||||
|
||||
<div className='grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4 flex-grow'>
|
||||
{getFilteredAndSortedFormats().map((format, index) => (
|
||||
{filteredFormats.map((format, index) => (
|
||||
<div
|
||||
key={format.file_name}
|
||||
data-format-index={index}
|
||||
|
||||
@@ -12,6 +12,7 @@ import MassActionsBar from '@ui/MassActionsBar';
|
||||
import ImportModal from '@ui/ImportModal';
|
||||
import {importProfiles} from '@api/import';
|
||||
import DataBar from '@ui/DataBar/DataBar';
|
||||
import useSearch from '@hooks/useSearch';
|
||||
|
||||
const LoadingState = () => (
|
||||
<div className='w-full min-h-[70vh] flex flex-col items-center justify-center'>
|
||||
@@ -57,19 +58,50 @@ function ProfilePage() {
|
||||
const [formats, setFormats] = useState([]);
|
||||
const [isModalOpen, setIsModalOpen] = useState(false);
|
||||
const [selectedProfile, setSelectedProfile] = useState(null);
|
||||
const [sortBy, setSortBy] = useState('name');
|
||||
const [filterType, setFilterType] = useState('none');
|
||||
const [filterValue, setFilterValue] = useState('');
|
||||
const [allTags, setAllTags] = useState([]);
|
||||
const [isCloning, setIsCloning] = useState(false);
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
const [mergeConflicts, setMergeConflicts] = useState([]);
|
||||
const [searchQuery, setSearchQuery] = useState('');
|
||||
const [isImportModalOpen, setIsImportModalOpen] = useState(false);
|
||||
const [willBeSelected, setWillBeSelected] = useState([]);
|
||||
|
||||
const navigate = useNavigate();
|
||||
|
||||
// Initialize useSearch hook with profile-specific configuration
|
||||
const {
|
||||
searchTerms,
|
||||
currentInput,
|
||||
setCurrentInput,
|
||||
addSearchTerm,
|
||||
removeSearchTerm,
|
||||
clearSearchTerms,
|
||||
filterType,
|
||||
setFilterType,
|
||||
filterValue,
|
||||
setFilterValue,
|
||||
sortBy,
|
||||
setSortBy,
|
||||
items: filteredProfiles
|
||||
} = useSearch(profiles, {
|
||||
searchableFields: ['content.name', 'content.tags'],
|
||||
initialSortBy: 'name',
|
||||
sortOptions: {
|
||||
name: (a, b) => a.content.name.localeCompare(b.content.name),
|
||||
dateModified: (a, b) =>
|
||||
new Date(b.modified_date) - new Date(a.modified_date),
|
||||
dateCreated: (a, b) =>
|
||||
new Date(b.created_date) - new Date(a.created_date)
|
||||
},
|
||||
customFilters: {
|
||||
date: (item, value) => {
|
||||
if (!value) return true;
|
||||
const itemDate = new Date(item.modified_date);
|
||||
const filterDate = new Date(value);
|
||||
return itemDate.toDateString() === filterDate.toDateString();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const {
|
||||
selectedItems,
|
||||
isSelectionMode,
|
||||
@@ -227,7 +259,7 @@ function ProfilePage() {
|
||||
const handleMassDelete = async () => {
|
||||
try {
|
||||
const selectedProfiles = Array.from(selectedItems).map(
|
||||
index => profiles[index]
|
||||
index => filteredProfiles[index]
|
||||
);
|
||||
for (const profile of selectedProfiles) {
|
||||
await Profiles.delete(profile.file_name.replace('.yml', ''));
|
||||
@@ -244,7 +276,7 @@ function ProfilePage() {
|
||||
const handleMassImport = async arr => {
|
||||
try {
|
||||
const selectedProfilesList = Array.from(selectedItems)
|
||||
.map(index => profiles[index])
|
||||
.map(index => filteredProfiles[index])
|
||||
.filter(profile => profile);
|
||||
|
||||
if (selectedProfilesList.length === 0) {
|
||||
@@ -269,7 +301,7 @@ function ProfilePage() {
|
||||
if (e.shiftKey) {
|
||||
handleMouseEnter(index, true);
|
||||
}
|
||||
handleSelect(profileName, index, e, getFilteredAndSortedProfiles());
|
||||
handleSelect(profileName, index, e, filteredProfiles);
|
||||
};
|
||||
|
||||
const handleMouseEnter = (index, isShiftKey) => {
|
||||
@@ -277,7 +309,7 @@ function ProfilePage() {
|
||||
const start = Math.min(lastSelectedIndex, index);
|
||||
const end = Math.max(lastSelectedIndex, index);
|
||||
|
||||
const potentialSelection = getFilteredAndSortedProfiles()
|
||||
const potentialSelection = filteredProfiles
|
||||
.slice(start, end + 1)
|
||||
.map((profile, idx) => idx + start);
|
||||
|
||||
@@ -285,54 +317,6 @@ function ProfilePage() {
|
||||
}
|
||||
};
|
||||
|
||||
const getFilteredAndSortedProfiles = () => {
|
||||
return profiles
|
||||
.filter(profile => {
|
||||
if (searchQuery) {
|
||||
return (
|
||||
profile.content.name
|
||||
.toLowerCase()
|
||||
.includes(searchQuery.toLowerCase()) ||
|
||||
profile.content.tags?.some(tag =>
|
||||
tag
|
||||
.toLowerCase()
|
||||
.includes(searchQuery.toLowerCase())
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
if (filterType === 'tag') {
|
||||
return profile.content.tags?.includes(filterValue);
|
||||
}
|
||||
|
||||
if (filterType === 'date') {
|
||||
const profileDate = new Date(profile.modified_date);
|
||||
const filterDate = new Date(filterValue);
|
||||
return (
|
||||
profileDate.toDateString() === filterDate.toDateString()
|
||||
);
|
||||
}
|
||||
|
||||
return true;
|
||||
})
|
||||
.sort((a, b) => {
|
||||
switch (sortBy) {
|
||||
case 'dateModified':
|
||||
return (
|
||||
new Date(b.modified_date) -
|
||||
new Date(a.modified_date)
|
||||
);
|
||||
case 'dateCreated':
|
||||
return (
|
||||
new Date(b.created_date) - new Date(a.created_date)
|
||||
);
|
||||
case 'name':
|
||||
default:
|
||||
return a.content.name.localeCompare(b.content.name);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const formatDate = dateString => {
|
||||
return new Date(dateString).toLocaleString();
|
||||
};
|
||||
@@ -347,12 +331,15 @@ function ProfilePage() {
|
||||
);
|
||||
}
|
||||
|
||||
const filteredProfiles = getFilteredAndSortedProfiles();
|
||||
|
||||
return (
|
||||
<div className='w-full space-y-2'>
|
||||
<DataBar
|
||||
onSearch={setSearchQuery}
|
||||
searchTerms={searchTerms}
|
||||
currentInput={currentInput}
|
||||
onInputChange={setCurrentInput}
|
||||
onAddTerm={addSearchTerm}
|
||||
onRemoveTerm={removeSearchTerm}
|
||||
onClearTerms={clearSearchTerms}
|
||||
searchPlaceholder='Search by name or tag...'
|
||||
filterType={filterType}
|
||||
setFilterType={setFilterType}
|
||||
|
||||
@@ -8,6 +8,7 @@ import {useMassSelection} from '@hooks/useMassSelection';
|
||||
import {useKeyboardShortcut} from '@hooks/useKeyboardShortcut';
|
||||
import MassActionsBar from '@ui/MassActionsBar';
|
||||
import DataBar from '@ui/DataBar/DataBar';
|
||||
import useSearch from '@hooks/useSearch';
|
||||
|
||||
const loadingMessages = [
|
||||
'Matching all the patterns...',
|
||||
@@ -32,19 +33,41 @@ const LoadingState = () => (
|
||||
);
|
||||
|
||||
function RegexPage() {
|
||||
// Basic state
|
||||
const [patterns, setPatterns] = useState([]);
|
||||
const [isModalOpen, setIsModalOpen] = useState(false);
|
||||
const [selectedPattern, setSelectedPattern] = useState(null);
|
||||
const [sortBy, setSortBy] = useState('name');
|
||||
const [filterType, setFilterType] = useState('none');
|
||||
const [filterValue, setFilterValue] = useState('');
|
||||
const [allTags, setAllTags] = useState([]);
|
||||
const [isCloning, setIsCloning] = useState(false);
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
const [searchQuery, setSearchQuery] = useState('');
|
||||
const [willBeSelected, setWillBeSelected] = useState([]);
|
||||
const [originalIndices, setOriginalIndices] = useState(new Map());
|
||||
|
||||
// Initialize useSearch hook
|
||||
const {
|
||||
searchTerms,
|
||||
currentInput,
|
||||
setCurrentInput,
|
||||
addSearchTerm,
|
||||
removeSearchTerm,
|
||||
clearSearchTerms,
|
||||
filterType,
|
||||
setFilterType,
|
||||
filterValue,
|
||||
setFilterValue,
|
||||
sortBy,
|
||||
setSortBy,
|
||||
items: filteredPatterns
|
||||
} = useSearch(patterns, {
|
||||
searchableFields: ['name', 'tags'],
|
||||
initialSortBy: 'name',
|
||||
sortOptions: {
|
||||
name: (a, b) => a.name.localeCompare(b.name),
|
||||
dateModified: (a, b) =>
|
||||
new Date(b.modified_date) - new Date(a.modified_date)
|
||||
}
|
||||
});
|
||||
|
||||
// Mass selection functionality
|
||||
const {
|
||||
selectedItems,
|
||||
isSelectionMode,
|
||||
@@ -54,12 +77,10 @@ function RegexPage() {
|
||||
lastSelectedIndex
|
||||
} = useMassSelection();
|
||||
|
||||
// Keyboard shortcuts
|
||||
useKeyboardShortcut('a', toggleSelectionMode, {ctrl: true});
|
||||
|
||||
useEffect(() => {
|
||||
loadPatterns();
|
||||
}, []);
|
||||
|
||||
// Mouse position tracking for shift-select
|
||||
useEffect(() => {
|
||||
const handleKeyDown = e => {
|
||||
if (e.key === 'Shift' && lastSelectedIndex !== null) {
|
||||
@@ -99,6 +120,11 @@ function RegexPage() {
|
||||
};
|
||||
}, [lastSelectedIndex]);
|
||||
|
||||
// Initial load
|
||||
useEffect(() => {
|
||||
loadPatterns();
|
||||
}, []);
|
||||
|
||||
const loadPatterns = async () => {
|
||||
setIsLoading(true);
|
||||
try {
|
||||
@@ -158,7 +184,6 @@ function RegexPage() {
|
||||
|
||||
const handleMassDelete = async () => {
|
||||
try {
|
||||
const filteredPatterns = getFilteredAndSortedPatterns();
|
||||
const selectedPatterns = Array.from(selectedItems).map(
|
||||
index => filteredPatterns[index]
|
||||
);
|
||||
@@ -173,7 +198,6 @@ function RegexPage() {
|
||||
console.error('Error deleting patterns:', error);
|
||||
Alert.error('Failed to delete selected patterns');
|
||||
} finally {
|
||||
// Always reload data and reset selection state, regardless of success/failure
|
||||
loadPatterns();
|
||||
toggleSelectionMode();
|
||||
clearSelection();
|
||||
@@ -184,59 +208,20 @@ function RegexPage() {
|
||||
if (e.shiftKey) {
|
||||
handleMouseEnter(index, true);
|
||||
}
|
||||
handleSelect(patternName, index, e, getFilteredAndSortedPatterns());
|
||||
handleSelect(patternName, index, e, filteredPatterns);
|
||||
};
|
||||
|
||||
const handleMouseEnter = (index, isShiftKey) => {
|
||||
if (isShiftKey && lastSelectedIndex !== null) {
|
||||
const start = Math.min(lastSelectedIndex, index);
|
||||
const end = Math.max(lastSelectedIndex, index);
|
||||
|
||||
const potentialSelection = getFilteredAndSortedPatterns()
|
||||
const potentialSelection = filteredPatterns
|
||||
.slice(start, end + 1)
|
||||
.map((pattern, idx) => idx + start);
|
||||
|
||||
setWillBeSelected(potentialSelection);
|
||||
}
|
||||
};
|
||||
|
||||
const getFilteredAndSortedPatterns = () => {
|
||||
let filtered = patterns.map((pattern, index) => ({
|
||||
...pattern,
|
||||
originalIndex: index
|
||||
}));
|
||||
|
||||
if (searchQuery) {
|
||||
filtered = filtered.filter(
|
||||
pattern =>
|
||||
pattern.name
|
||||
.toLowerCase()
|
||||
.includes(searchQuery.toLowerCase()) ||
|
||||
pattern.tags?.some(tag =>
|
||||
tag.toLowerCase().includes(searchQuery.toLowerCase())
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
if (filterType === 'tag' && filterValue) {
|
||||
filtered = filtered.filter(pattern =>
|
||||
pattern.tags?.includes(filterValue)
|
||||
);
|
||||
}
|
||||
|
||||
return filtered.sort((a, b) => {
|
||||
switch (sortBy) {
|
||||
case 'dateModified':
|
||||
return (
|
||||
new Date(b.modified_date) - new Date(a.modified_date)
|
||||
);
|
||||
case 'name':
|
||||
default:
|
||||
return a.name.localeCompare(b.name);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const formatDate = dateString => {
|
||||
return new Date(dateString).toLocaleString();
|
||||
};
|
||||
@@ -248,8 +233,12 @@ function RegexPage() {
|
||||
return (
|
||||
<div className='w-full space-y-2'>
|
||||
<DataBar
|
||||
onSearch={setSearchQuery}
|
||||
searchPlaceholder='Search by name or tag...'
|
||||
searchTerms={searchTerms}
|
||||
currentInput={currentInput}
|
||||
onInputChange={setCurrentInput}
|
||||
onAddTerm={addSearchTerm}
|
||||
onRemoveTerm={removeSearchTerm}
|
||||
onClearTerms={clearSearchTerms}
|
||||
filterType={filterType}
|
||||
setFilterType={setFilterType}
|
||||
filterValue={filterValue}
|
||||
@@ -264,7 +253,7 @@ function RegexPage() {
|
||||
/>
|
||||
|
||||
<div className='grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4'>
|
||||
{getFilteredAndSortedPatterns().map((pattern, index) => (
|
||||
{filteredPatterns.map((pattern, index) => (
|
||||
<div
|
||||
key={pattern.name}
|
||||
data-pattern-index={index}
|
||||
|
||||
Reference in New Issue
Block a user