Improve customer list filtering UI
- Add dedicated filter section with improved styling - Add debouncing to filter inputs - Add clear filters functionality - Add individual clear buttons for text inputs - Add active filters display - Improve overall filter section layout
This commit is contained in:
@ -40,10 +40,13 @@ export default function AdminCustomers() {
|
|||||||
const [pageSize, setPageSize] = useState(initialPageSize);
|
const [pageSize, setPageSize] = useState(initialPageSize);
|
||||||
|
|
||||||
// Search filters state
|
// Search filters state
|
||||||
const [nameFilter, setNameFilter] = useState('');
|
const [filters, setFilters] = useState({
|
||||||
const [emailFilter, setEmailFilter] = useState('');
|
name: '',
|
||||||
const [urlFilter, setUrlFilter] = useState('');
|
email: '',
|
||||||
const [hasEmailFilter, setHasEmailFilter] = useState(false);
|
url: '',
|
||||||
|
hasEmail: false
|
||||||
|
});
|
||||||
|
const [debouncedFilters, setDebouncedFilters] = useState(filters);
|
||||||
|
|
||||||
// State for data
|
// State for data
|
||||||
const [customers, setCustomers] = useState<Customer[]>([]);
|
const [customers, setCustomers] = useState<Customer[]>([]);
|
||||||
@ -68,10 +71,10 @@ export default function AdminCustomers() {
|
|||||||
params.append('page', initialPage.toString());
|
params.append('page', initialPage.toString());
|
||||||
params.append('pageSize', initialPageSize.toString());
|
params.append('pageSize', initialPageSize.toString());
|
||||||
|
|
||||||
if (nameFilter) params.append('name', nameFilter);
|
if (debouncedFilters.name) params.append('name', debouncedFilters.name);
|
||||||
if (emailFilter) params.append('email', emailFilter);
|
if (debouncedFilters.email) params.append('email', debouncedFilters.email);
|
||||||
if (urlFilter) params.append('url', urlFilter);
|
if (debouncedFilters.url) params.append('url', debouncedFilters.url);
|
||||||
if (hasEmailFilter) params.append('hasEmail', 'true');
|
if (debouncedFilters.hasEmail) params.append('hasEmail', 'true');
|
||||||
|
|
||||||
const response = await fetch(`/api/customers?${params.toString()}`);
|
const response = await fetch(`/api/customers?${params.toString()}`);
|
||||||
|
|
||||||
@ -91,7 +94,31 @@ export default function AdminCustomers() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
fetchCustomers();
|
fetchCustomers();
|
||||||
}, [initialPage, initialPageSize, nameFilter, emailFilter, urlFilter, hasEmailFilter]);
|
}, [initialPage, initialPageSize, debouncedFilters]);
|
||||||
|
|
||||||
|
// Debounce filter changes
|
||||||
|
useEffect(() => {
|
||||||
|
const timer = setTimeout(() => {
|
||||||
|
setDebouncedFilters(filters);
|
||||||
|
}, 300);
|
||||||
|
|
||||||
|
return () => clearTimeout(timer);
|
||||||
|
}, [filters]);
|
||||||
|
|
||||||
|
// Handle filter changes
|
||||||
|
const handleFilterChange = (key: keyof typeof filters, value: string | boolean) => {
|
||||||
|
setFilters(prev => ({ ...prev, [key]: value }));
|
||||||
|
};
|
||||||
|
|
||||||
|
// Clear all filters
|
||||||
|
const handleClearFilters = () => {
|
||||||
|
setFilters({
|
||||||
|
name: '',
|
||||||
|
email: '',
|
||||||
|
url: '',
|
||||||
|
hasEmail: false
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
// Handle page change
|
// Handle page change
|
||||||
const handlePageChange = (newPage: number) => {
|
const handlePageChange = (newPage: number) => {
|
||||||
@ -125,45 +152,91 @@ export default function AdminCustomers() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Search Filters */}
|
{/* Search Filters */}
|
||||||
<div className="mb-6 grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-4">
|
<div className="mb-6 bg-white dark:bg-gray-800 shadow rounded-lg p-6">
|
||||||
|
<div className="flex justify-between items-center mb-4">
|
||||||
|
<h2 className="text-lg font-medium text-gray-900 dark:text-gray-100">Filters</h2>
|
||||||
|
<button
|
||||||
|
onClick={handleClearFilters}
|
||||||
|
className="text-sm text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200"
|
||||||
|
>
|
||||||
|
Clear all filters
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div className="grid grid-cols-1 gap-6 sm:grid-cols-2 lg:grid-cols-4">
|
||||||
<div>
|
<div>
|
||||||
<label htmlFor="name-filter" className="block text-sm font-medium text-gray-700 dark:text-gray-300">
|
<label htmlFor="name-filter" className="block text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||||
Search by Name
|
Search by Name
|
||||||
</label>
|
</label>
|
||||||
|
<div className="mt-1 relative rounded-md shadow-sm">
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
id="name-filter"
|
id="name-filter"
|
||||||
value={nameFilter}
|
value={filters.name}
|
||||||
onChange={(e) => setNameFilter(e.target.value)}
|
onChange={(e) => handleFilterChange('name', e.target.value)}
|
||||||
className="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 dark:bg-gray-700 dark:border-gray-600 dark:text-white sm:text-sm"
|
className="block w-full rounded-md border-gray-300 pr-10 focus:border-indigo-500 focus:ring-indigo-500 dark:bg-gray-700 dark:border-gray-600 dark:text-white sm:text-sm"
|
||||||
placeholder="Enter name..."
|
placeholder="Enter name..."
|
||||||
/>
|
/>
|
||||||
|
{filters.name && (
|
||||||
|
<button
|
||||||
|
onClick={() => handleFilterChange('name', '')}
|
||||||
|
className="absolute inset-y-0 right-0 pr-3 flex items-center"
|
||||||
|
>
|
||||||
|
<svg className="h-4 w-4 text-gray-400 hover:text-gray-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label htmlFor="email-filter" className="block text-sm font-medium text-gray-700 dark:text-gray-300">
|
<label htmlFor="email-filter" className="block text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||||
Search by Email
|
Search by Email
|
||||||
</label>
|
</label>
|
||||||
|
<div className="mt-1 relative rounded-md shadow-sm">
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
id="email-filter"
|
id="email-filter"
|
||||||
value={emailFilter}
|
value={filters.email}
|
||||||
onChange={(e) => setEmailFilter(e.target.value)}
|
onChange={(e) => handleFilterChange('email', e.target.value)}
|
||||||
className="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 dark:bg-gray-700 dark:border-gray-600 dark:text-white sm:text-sm"
|
className="block w-full rounded-md border-gray-300 pr-10 focus:border-indigo-500 focus:ring-indigo-500 dark:bg-gray-700 dark:border-gray-600 dark:text-white sm:text-sm"
|
||||||
placeholder="Enter email..."
|
placeholder="Enter email..."
|
||||||
/>
|
/>
|
||||||
|
{filters.email && (
|
||||||
|
<button
|
||||||
|
onClick={() => handleFilterChange('email', '')}
|
||||||
|
className="absolute inset-y-0 right-0 pr-3 flex items-center"
|
||||||
|
>
|
||||||
|
<svg className="h-4 w-4 text-gray-400 hover:text-gray-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label htmlFor="url-filter" className="block text-sm font-medium text-gray-700 dark:text-gray-300">
|
<label htmlFor="url-filter" className="block text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||||
Search by URL
|
Search by URL
|
||||||
</label>
|
</label>
|
||||||
|
<div className="mt-1 relative rounded-md shadow-sm">
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
id="url-filter"
|
id="url-filter"
|
||||||
value={urlFilter}
|
value={filters.url}
|
||||||
onChange={(e) => setUrlFilter(e.target.value)}
|
onChange={(e) => handleFilterChange('url', e.target.value)}
|
||||||
className="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 dark:bg-gray-700 dark:border-gray-600 dark:text-white sm:text-sm"
|
className="block w-full rounded-md border-gray-300 pr-10 focus:border-indigo-500 focus:ring-indigo-500 dark:bg-gray-700 dark:border-gray-600 dark:text-white sm:text-sm"
|
||||||
placeholder="Enter URL..."
|
placeholder="Enter URL..."
|
||||||
/>
|
/>
|
||||||
|
{filters.url && (
|
||||||
|
<button
|
||||||
|
onClick={() => handleFilterChange('url', '')}
|
||||||
|
className="absolute inset-y-0 right-0 pr-3 flex items-center"
|
||||||
|
>
|
||||||
|
<svg className="h-4 w-4 text-gray-400 hover:text-gray-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-3">
|
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-3">
|
||||||
@ -173,8 +246,8 @@ export default function AdminCustomers() {
|
|||||||
<input
|
<input
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
id="has-email"
|
id="has-email"
|
||||||
checked={hasEmailFilter}
|
checked={filters.hasEmail}
|
||||||
onChange={(e) => setHasEmailFilter(e.target.checked)}
|
onChange={(e) => handleFilterChange('hasEmail', e.target.checked)}
|
||||||
className="h-4 w-4 rounded border-gray-300 text-indigo-600 focus:ring-indigo-500 dark:bg-gray-700 dark:border-gray-600"
|
className="h-4 w-4 rounded border-gray-300 text-indigo-600 focus:ring-indigo-500 dark:bg-gray-700 dark:border-gray-600"
|
||||||
/>
|
/>
|
||||||
<label htmlFor="has-email" className="ml-2 block text-sm text-gray-700 dark:text-gray-300">
|
<label htmlFor="has-email" className="ml-2 block text-sm text-gray-700 dark:text-gray-300">
|
||||||
@ -183,6 +256,17 @@ export default function AdminCustomers() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
{(filters.name || filters.email || filters.url || filters.hasEmail) && (
|
||||||
|
<div className="mt-4 text-sm text-gray-500 dark:text-gray-400">
|
||||||
|
Active filters: {[
|
||||||
|
filters.name && 'Name',
|
||||||
|
filters.email && 'Email',
|
||||||
|
filters.url && 'URL',
|
||||||
|
filters.hasEmail && 'Has Email'
|
||||||
|
].filter(Boolean).join(', ')}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
{/* Error Message */}
|
{/* Error Message */}
|
||||||
{error && (
|
{error && (
|
||||||
|
|||||||
Reference in New Issue
Block a user