feat: add ability to search

This commit is contained in:
2026-02-04 20:37:51 -08:00
parent ca288eaac4
commit a9764a4a82
7 changed files with 1149 additions and 23 deletions
@@ -391,6 +391,51 @@ import { Music, MusicStatus, MusicType, CreateMusicDto, UpdateMusicDto, Comment,
</form>
}
<div class="search-section">
<input
type="text"
[value]="searchQuery()"
(input)="searchQuery.set($any($event.target).value); currentPage.set(1)"
name="search"
placeholder="Search by title, artist, type, or notes..."
class="search-input"
>
<button (click)="toggleFilters()" class="btn btn-secondary btn-sm">
{{ showFilters() ? 'Hide' : 'Show' }} Advanced Filters
@if (selectedTags().length > 0) {
({{ selectedTags().length }})
}
</button>
@if (searchQuery() || selectedTags().length > 0) {
<button (click)="clearFilters()" class="btn btn-secondary btn-sm">
Clear All Filters
</button>
}
</div>
@if (showFilters()) {
<div class="advanced-filters">
<div class="filter-group">
<h4>Filter by Tags</h4>
<div class="tags-filter">
@for (tag of allTags(); track tag) {
<label class="tag-checkbox">
<input
type="checkbox"
[checked]="selectedTags().includes(tag)"
(change)="toggleTag(tag)"
>
<span>{{ tag }}</span>
</label>
}
@empty {
<p class="no-tags">No tags available</p>
}
</div>
</div>
</div>
}
<div class="filters">
<div class="filter-group">
<strong>Type:</strong>
@@ -716,6 +761,82 @@ import { Music, MusicStatus, MusicType, CreateMusicDto, UpdateMusicDto, Comment,
margin-top: 1rem;
}
.search-section {
display: flex;
gap: 1rem;
align-items: center;
margin-bottom: 1rem;
flex-wrap: wrap;
}
.search-input {
flex: 1;
min-width: 250px;
padding: 0.5rem 1rem;
border: 1px solid #ddd;
border-radius: 4px;
font-size: 1rem;
}
.search-input:focus {
outline: none;
border-color: #8b5cf6;
}
.advanced-filters {
background: #f8f9fa;
padding: 1rem;
border-radius: 8px;
margin-bottom: 1rem;
}
.filter-group h4 {
margin: 0 0 0.75rem 0;
color: #374151;
font-size: 0.95rem;
font-weight: 600;
}
.tags-filter {
display: flex;
flex-wrap: wrap;
gap: 0.75rem;
}
.tag-checkbox {
display: flex;
align-items: center;
gap: 0.5rem;
cursor: pointer;
padding: 0.25rem 0.75rem;
border: 1px solid #e5e7eb;
border-radius: 20px;
background: white;
transition: all 0.2s;
}
.tag-checkbox:hover {
border-color: #8b5cf6;
background: #f3e8ff;
}
.tag-checkbox input[type="checkbox"] {
width: 16px;
height: 16px;
cursor: pointer;
}
.tag-checkbox span {
font-size: 0.875rem;
color: #374151;
}
.no-tags {
color: #6b7280;
font-style: italic;
font-size: 0.875rem;
}
.filters {
display: flex;
flex-direction: column;
@@ -1265,6 +1386,11 @@ export class MusicListComponent implements OnInit {
currentPage = signal(1);
pageSize = signal(25);
// Search and filter state
searchQuery = signal('');
selectedTags = signal<string[]>([]);
showFilters = signal(false);
// Comments state
comments = signal<Record<string, Comment[]>>({});
commentsLoading = signal<Record<string, boolean>>({});
@@ -1304,6 +1430,14 @@ export class MusicListComponent implements OnInit {
completedCount = computed(() => this.music().filter(m => m.status === MusicStatus.completed).length);
wantToListenCount = computed(() => this.music().filter(m => m.status === MusicStatus.wantToListen).length);
allTags = computed(() => {
const tagsSet = new Set<string>();
this.music().forEach(music => {
music.tags?.forEach(tag => tagsSet.add(tag));
});
return Array.from(tagsSet).sort();
});
filteredMusic = computed(() => {
let filtered = this.music();
@@ -1317,6 +1451,25 @@ export class MusicListComponent implements OnInit {
filtered = filtered.filter(music => music.status === statusFilter);
}
// Apply search filter
const searchQuery = this.searchQuery().toLowerCase().trim();
if (searchQuery) {
filtered = filtered.filter(music =>
music.title.toLowerCase().includes(searchQuery) ||
music.artist.toLowerCase().includes(searchQuery) ||
music.notes?.toLowerCase().includes(searchQuery) ||
this.getTypeLabel(music.type).toLowerCase().includes(searchQuery)
);
}
// Apply tag filter
const selectedTags = this.selectedTags();
if (selectedTags.length > 0) {
filtered = filtered.filter(music =>
selectedTags.every(tag => music.tags?.includes(tag))
);
}
return filtered;
});
@@ -1377,6 +1530,28 @@ export class MusicListComponent implements OnInit {
this.currentPage.set(1); // Reset to first page when filter changes
}
toggleTag(tag: string) {
const current = this.selectedTags();
if (current.includes(tag)) {
this.selectedTags.set(current.filter(t => t !== tag));
} else {
this.selectedTags.set([...current, tag]);
}
this.currentPage.set(1); // Reset to first page when tags change
}
clearFilters() {
this.searchQuery.set('');
this.selectedTags.set([]);
this.typeFilter.set('all');
this.statusFilter.set('all');
this.currentPage.set(1);
}
toggleFilters() {
this.showFilters.update(v => !v);
}
onPageChange(page: number) {
this.currentPage.set(page);
}