feat: integrate useSearch hook for enhanced filtering and sorting in FormatPage and RegexPage components

This commit is contained in:
Sam Chau
2025-01-12 02:52:22 +10:30
parent cd94508d2c
commit af2f47b82f
3 changed files with 125 additions and 159 deletions

View File

@@ -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}

View File

@@ -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}

View File

@@ -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}