Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
89 changes: 49 additions & 40 deletions dashboard/src/components/EventFiltersBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { memo } from 'react';
import { useEventStore } from '../store/eventStore';
import { useEventCount, useEventFilters, useFilterOptions } from '../hooks/useEventSelectors';
import { SearchAutocomplete } from './SearchAutocomplete';
import { SavedFiltersManager } from './SavedFiltersManager';

export const EventFiltersBar = memo(function EventFiltersBar() {
const filters = useEventFilters();
Expand All @@ -12,49 +13,57 @@ export const EventFiltersBar = memo(function EventFiltersBar() {
const setEventTypeFilter = useEventStore((state) => state.setEventTypeFilter);

return (
<section className="event-filters" aria-label="Event filters">
<div className="event-filters__group">
<label htmlFor="event-search">Search</label>
<SearchAutocomplete
value={filters.search}
onChange={(value) => setSearch(value)}
/>
</div>
<>
<section className="event-filters" aria-label="Event filters">
<div className="event-filters__group">
<label htmlFor="event-search">Search</label>
<SearchAutocomplete
value={filters.search}
onChange={(value) => setSearch(value)}
/>
</div>

<div className="event-filters__group">
<label htmlFor="contract-filter">Contract</label>
<select
id="contract-filter"
value={filters.contractAddress}
onChange={(event) => setContractFilter(event.target.value)}
>
<option value="all">All contracts</option>
{contractOptions.map((address) => (
<option key={address} value={address}>
{address.slice(0, 8)}...
</option>
))}
</select>
</div>
<div className="event-filters__group">
<label htmlFor="contract-filter">Contract</label>
<select
id="contract-filter"
value={filters.contractAddress}
onChange={(event) => setContractFilter(event.target.value)}
>
<option value="all">All contracts</option>
{contractOptions.map((address) => (
<option key={address} value={address}>
{address.slice(0, 8)}...
</option>
))}
</select>
</div>

<div className="event-filters__group">
<label htmlFor="event-type-filter">Event type</label>
<select
id="event-type-filter"
value={filters.eventType}
onChange={(event) => setEventTypeFilter(event.target.value)}
>
<option value="all">All types</option>
{eventTypeOptions.map((name) => (
<option key={name} value={name}>
{name}
</option>
))}
</select>
</div>
<div className="event-filters__group">
<label htmlFor="event-type-filter">Event type</label>
<select
id="event-type-filter"
value={filters.eventType}
onChange={(event) => setEventTypeFilter(event.target.value)}
>
<option value="all">All types</option>
{eventTypeOptions.map((name) => (
<option key={name} value={name}>
{name}
</option>
))}
</select>
</div>

<p className="event-filters__count" aria-live="polite" role="status">
{totalCount.toLocaleString()} events loaded
<p className="event-filters__count" aria-live="polite" role="status">
{totalCount.toLocaleString()} events loaded
</p>
</section>

<SavedFiltersManager />
</>
);
});
</p> </section>
);
});
165 changes: 165 additions & 0 deletions dashboard/src/components/SavedFiltersManager.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
import { memo, useState } from 'react';
import { useEventStore } from '../store/eventStore';
import { useShallow } from 'zustand/react/shallow';

Check failure on line 3 in dashboard/src/components/SavedFiltersManager.tsx

View workflow job for this annotation

GitHub Actions / Frontend (lint, typecheck, test)

'useShallow' is defined but never used
import type { SavedFilter } from '../types/event';

export const SavedFiltersManager = memo(function SavedFiltersManager() {
const [isOpen, setIsOpen] = useState(false);
const [filterName, setFilterName] = useState('');
const [editingId, setEditingId] = useState<string | null>(null);
const [editingName, setEditingName] = useState('');

const savedFilters = useEventStore((state) => state.savedFilters);
const saveCurrentFilter = useEventStore((state) => state.saveCurrentFilter);
const loadSavedFilter = useEventStore((state) => state.loadSavedFilter);
const renameSavedFilter = useEventStore((state) => state.renameSavedFilter);
const deleteSavedFilter = useEventStore((state) => state.deleteSavedFilter);

const handleSave = () => {
if (filterName.trim()) {
saveCurrentFilter(filterName.trim());
setFilterName('');
}
};

const handleLoad = (id: string) => {
loadSavedFilter(id);
setIsOpen(false);
};

const handleStartEdit = (filter: SavedFilter) => {
setEditingId(filter.id);
setEditingName(filter.name);
};

const handleSaveEdit = () => {
if (editingId && editingName.trim()) {
renameSavedFilter(editingId, editingName.trim());
setEditingId(null);
setEditingName('');
}
};

const handleCancelEdit = () => {
setEditingId(null);
setEditingName('');
};

const handleDelete = (id: string) => {
if (confirm('Are you sure you want to delete this saved filter?')) {
deleteSavedFilter(id);
if (editingId === id) {
setEditingId(null);
setEditingName('');
}
}
};

return (
<div className="saved-filters">
<div className="saved-filters__header">
<button
type="button"
className="saved-filters__toggle"
onClick={() => setIsOpen(!isOpen)}
aria-expanded={isOpen}
>
Saved Filters ({savedFilters.length})
</button>
</div>

{isOpen && (
<div className="saved-filters__panel">
<div className="saved-filters__save">
<input
type="text"
placeholder="Filter name"
value={filterName}
onChange={(e) => setFilterName(e.target.value)}
onKeyDown={(e) => e.key === 'Enter' && handleSave()}
className="saved-filters__input"
/>
<button
type="button"
onClick={handleSave}
disabled={!filterName.trim()}
className="saved-filters__save-btn"
>
Save Current
</button>
</div>

{savedFilters.length > 0 ? (
<ul className="saved-filters__list">
{savedFilters.map((filter) => (
<li key={filter.id} className="saved-filters__item">
{editingId === filter.id ? (
<div className="saved-filters__edit">
<input
type="text"
value={editingName}
onChange={(e) => setEditingName(e.target.value)}
onKeyDown={(e) => {
if (e.key === 'Enter') handleSaveEdit();
if (e.key === 'Escape') handleCancelEdit();
}}
className="saved-filters__input"
autoFocus
/>
<button
type="button"
onClick={handleSaveEdit}
className="saved-filters__btn saved-filters__btn--save"
title="Save"
>
</button>
<button
type="button"
onClick={handleCancelEdit}
className="saved-filters__btn saved-filters__btn--cancel"
title="Cancel"
>
</button>
</div>
) : (
<div className="saved-filters__view">
<button
type="button"
onClick={() => handleLoad(filter.id)}
className="saved-filters__name"
>
{filter.name}
</button>
<div className="saved-filters__actions">
<button
type="button"
onClick={() => handleStartEdit(filter)}
className="saved-filters__btn saved-filters__btn--edit"
title="Rename"
>
</button>
<button
type="button"
onClick={() => handleDelete(filter.id)}
className="saved-filters__btn saved-filters__btn--delete"
title="Delete"
>
🗑
</button>
</div>
</div>
)}
</li>
))}
</ul>
) : (
<p className="saved-filters__empty">No saved filters yet</p>
)}
</div>
)}
</div>
);
});
Loading
Loading