Vorschläge

This commit is contained in:
Bastian Wagner
2026-06-15 09:44:59 +02:00
parent 22c93f9ca1
commit cb938d3dc8
12 changed files with 681 additions and 13 deletions

View File

@@ -16,7 +16,12 @@ import { AuthService } from '../../auth/auth.service';
import { getAuthErrorMessage } from '../../auth/error-message';
import { PublicUserSearchResult } from '../../auth/auth.models';
import { OnboardingService } from '../../onboarding/onboarding.service';
import { ListRealtimeEvent, UserList, UserListItem } from '../lists.models';
import {
ListItemSuggestion,
ListRealtimeEvent,
UserList,
UserListItem,
} from '../lists.models';
import { ListsRealtimeService } from '../lists-realtime.service';
import { ListsService } from '../lists.service';
@@ -55,8 +60,12 @@ export class ListDetailComponent implements OnInit {
protected readonly saving = signal(false);
protected readonly editing = signal(false);
protected readonly addingItem = signal(false);
protected readonly loadingSuggestions = signal(false);
protected readonly suggestionsLoaded = signal(false);
protected readonly errorMessage = signal<string | null>(null);
protected readonly updatingItemId = signal<string | null>(null);
protected readonly addingSuggestionTitle = signal<string | null>(null);
protected readonly itemSuggestions = signal<ListItemSuggestion[]>([]);
protected readonly shareSearchTerm = signal('');
protected readonly shareSearchResults = signal<PublicUserSearchResult[]>([]);
protected readonly searchingUsers = signal(false);
@@ -201,6 +210,63 @@ export class ListDetailComponent implements OnInit {
});
}
protected loadSuggestions(): void {
const listId = this.listId();
if (!listId || !this.canEditItems() || this.loadingSuggestions()) {
return;
}
this.loadingSuggestions.set(true);
this.suggestionsLoaded.set(false);
this.listsService
.suggestItems(listId)
.pipe(finalize(() => this.loadingSuggestions.set(false)))
.subscribe({
next: (response) => {
this.itemSuggestions.set(response.suggestions);
this.suggestionsLoaded.set(true);
},
error: (error: unknown) => {
this.snackBar.open(getAuthErrorMessage(error), 'OK', { duration: 5000 });
},
});
}
protected addSuggestion(suggestion: ListItemSuggestion): void {
const listId = this.listId();
if (!listId || this.addingSuggestionTitle()) {
return;
}
this.addingSuggestionTitle.set(suggestion.title);
this.listsService
.addItem(listId, {
title: suggestion.title,
notes: suggestion.notes,
quantity: suggestion.quantity,
required: suggestion.required,
})
.pipe(finalize(() => this.addingSuggestionTitle.set(null)))
.subscribe({
next: (list) => {
this.setList(list);
this.itemSuggestions.update((suggestions) =>
suggestions.filter(
(itemSuggestion) =>
this.suggestionKey(itemSuggestion.title) !==
this.suggestionKey(suggestion.title),
),
);
},
error: (error: unknown) => {
this.snackBar.open(getAuthErrorMessage(error), 'OK', { duration: 5000 });
},
});
}
protected toggleItem(item: UserListItem, checked: boolean): void {
const listId = this.listId();
const currentList = this.list();
@@ -258,6 +324,10 @@ export class ListDetailComponent implements OnInit {
return list.items.filter((item) => item.checked).length;
}
protected visibleItems(list: UserList): UserListItem[] {
return this.uncheckedFirst(list.items);
}
protected searchShareUsers(term: string): void {
this.shareSearchTerm.set(term);
@@ -374,4 +444,21 @@ export class ListDetailComponent implements OnInit {
private listId(): string | null {
return this.route.snapshot.paramMap.get('listId');
}
private uncheckedFirst(items: UserListItem[]): UserListItem[] {
return items
.map((item, index) => ({ item, index }))
.sort((a, b) => {
if (a.item.checked !== b.item.checked) {
return a.item.checked ? 1 : -1;
}
return a.index - b.index;
})
.map(({ item }) => item);
}
private suggestionKey(value: string): string {
return value.trim().replace(/\s+/g, ' ').toLowerCase();
}
}