delete
This commit is contained in:
@@ -0,0 +1,20 @@
|
||||
<div class="delete-dialog-icon">
|
||||
<mat-icon aria-hidden="true">delete</mat-icon>
|
||||
</div>
|
||||
|
||||
<h2 mat-dialog-title>Liste loeschen?</h2>
|
||||
|
||||
<mat-dialog-content>
|
||||
<p>
|
||||
<strong>{{ data.listName }}</strong> wird geloescht und ist danach nicht
|
||||
mehr in deinen Listen sichtbar.
|
||||
</p>
|
||||
</mat-dialog-content>
|
||||
|
||||
<mat-dialog-actions align="end">
|
||||
<button mat-button type="button" mat-dialog-close>Abbrechen</button>
|
||||
<button mat-flat-button type="button" color="warn" [mat-dialog-close]="true">
|
||||
<mat-icon aria-hidden="true">delete</mat-icon>
|
||||
Loeschen
|
||||
</button>
|
||||
</mat-dialog-actions>
|
||||
@@ -0,0 +1,17 @@
|
||||
import { Component, inject } from '@angular/core';
|
||||
import { MAT_DIALOG_DATA, MatDialogModule } from '@angular/material/dialog';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
import { MatIconModule } from '@angular/material/icon';
|
||||
|
||||
export interface ConfirmDeleteListDialogData {
|
||||
listName: string;
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'app-confirm-delete-list-dialog',
|
||||
imports: [MatButtonModule, MatDialogModule, MatIconModule],
|
||||
templateUrl: './confirm-delete-list-dialog.component.html',
|
||||
})
|
||||
export class ConfirmDeleteListDialogComponent {
|
||||
protected readonly data = inject<ConfirmDeleteListDialogData>(MAT_DIALOG_DATA);
|
||||
}
|
||||
@@ -61,6 +61,23 @@
|
||||
<mat-icon aria-hidden="true">{{ showEditor() ? 'close' : 'edit' }}</mat-icon>
|
||||
{{ showEditor() ? 'Abbrechen' : 'Bearbeiten' }}
|
||||
</button>
|
||||
|
||||
@if (canDeleteList()) {
|
||||
<button
|
||||
mat-stroked-button
|
||||
type="button"
|
||||
color="warn"
|
||||
[disabled]="deletingList()"
|
||||
(click)="deleteList()"
|
||||
>
|
||||
@if (deletingList()) {
|
||||
<mat-progress-spinner mode="indeterminate" diameter="18" />
|
||||
} @else {
|
||||
<mat-icon aria-hidden="true">delete</mat-icon>
|
||||
}
|
||||
Loeschen
|
||||
</button>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</mat-card-header>
|
||||
|
||||
@@ -7,6 +7,7 @@ import { finalize } from 'rxjs';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
import { MatCardModule } from '@angular/material/card';
|
||||
import { MatCheckboxModule } from '@angular/material/checkbox';
|
||||
import { MatDialog, MatDialogModule } from '@angular/material/dialog';
|
||||
import { MatFormFieldModule } from '@angular/material/form-field';
|
||||
import { MatIconModule } from '@angular/material/icon';
|
||||
import { MatInputModule } from '@angular/material/input';
|
||||
@@ -16,6 +17,7 @@ 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 { ConfirmDeleteListDialogComponent } from '../confirm-delete-list-dialog/confirm-delete-list-dialog.component';
|
||||
import {
|
||||
ListItemSuggestion,
|
||||
ListRealtimeEvent,
|
||||
@@ -34,6 +36,7 @@ import { ListsService } from '../lists.service';
|
||||
MatButtonModule,
|
||||
MatCardModule,
|
||||
MatCheckboxModule,
|
||||
MatDialogModule,
|
||||
MatFormFieldModule,
|
||||
MatIconModule,
|
||||
MatInputModule,
|
||||
@@ -47,6 +50,7 @@ export class ListDetailComponent implements OnInit {
|
||||
private readonly destroyRef = inject(DestroyRef);
|
||||
private readonly formBuilder = inject(NonNullableFormBuilder);
|
||||
private readonly authService = inject(AuthService);
|
||||
private readonly dialog = inject(MatDialog);
|
||||
private readonly listsService = inject(ListsService);
|
||||
private readonly listsRealtimeService = inject(ListsRealtimeService);
|
||||
private readonly route = inject(ActivatedRoute);
|
||||
@@ -59,6 +63,7 @@ export class ListDetailComponent implements OnInit {
|
||||
protected readonly loading = signal(true);
|
||||
protected readonly saving = signal(false);
|
||||
protected readonly creatingTemplate = signal(false);
|
||||
protected readonly deletingList = signal(false);
|
||||
protected readonly editing = signal(false);
|
||||
protected readonly addingItem = signal(false);
|
||||
protected readonly loadingSuggestions = signal(false);
|
||||
@@ -76,6 +81,9 @@ export class ListDetailComponent implements OnInit {
|
||||
protected readonly canManageShares = computed(
|
||||
() => this.list()?.accessRole === 'owner' && !this.isCreateMode(),
|
||||
);
|
||||
protected readonly canDeleteList = computed(
|
||||
() => this.list()?.accessRole === 'owner' && !this.isCreateMode(),
|
||||
);
|
||||
protected readonly showShareControls = computed(
|
||||
() => this.canManageShares() && this.showEditor(),
|
||||
);
|
||||
@@ -233,6 +241,48 @@ export class ListDetailComponent implements OnInit {
|
||||
});
|
||||
}
|
||||
|
||||
protected deleteList(): void {
|
||||
const listId = this.listId();
|
||||
const list = this.list();
|
||||
|
||||
if (!listId || !list || !this.canDeleteList() || this.deletingList()) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.dialog
|
||||
.open<ConfirmDeleteListDialogComponent, { listName: string }, boolean>(
|
||||
ConfirmDeleteListDialogComponent,
|
||||
{
|
||||
data: { listName: list.name },
|
||||
maxWidth: '420px',
|
||||
width: 'calc(100vw - 32px)',
|
||||
},
|
||||
)
|
||||
.afterClosed()
|
||||
.subscribe((confirmed) => {
|
||||
if (!confirmed) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.deletingList.set(true);
|
||||
|
||||
this.listsService
|
||||
.deleteList(listId)
|
||||
.pipe(finalize(() => this.deletingList.set(false)))
|
||||
.subscribe({
|
||||
next: () => {
|
||||
this.snackBar.open('Liste geloescht.', 'OK', { duration: 3000 });
|
||||
void this.router.navigateByUrl('/lists');
|
||||
},
|
||||
error: (error: unknown) => {
|
||||
this.snackBar.open(getAuthErrorMessage(error), 'OK', {
|
||||
duration: 5000,
|
||||
});
|
||||
},
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
protected loadSuggestions(): void {
|
||||
const listId = this.listId();
|
||||
|
||||
|
||||
@@ -167,6 +167,23 @@
|
||||
</mat-card-content>
|
||||
|
||||
<mat-card-actions align="end">
|
||||
@if (list.accessRole === 'owner') {
|
||||
<button
|
||||
mat-button
|
||||
type="button"
|
||||
color="warn"
|
||||
[disabled]="deletingListId() === list.id"
|
||||
(click)="deleteList(list)"
|
||||
>
|
||||
@if (deletingListId() === list.id) {
|
||||
<mat-progress-spinner mode="indeterminate" diameter="18" />
|
||||
} @else {
|
||||
<mat-icon aria-hidden="true">delete</mat-icon>
|
||||
}
|
||||
Loeschen
|
||||
</button>
|
||||
}
|
||||
|
||||
<a
|
||||
mat-button
|
||||
[routerLink]="['/lists', list.id]"
|
||||
|
||||
@@ -2,16 +2,20 @@ import { DatePipe } from '@angular/common';
|
||||
import { Component, DestroyRef, OnInit, computed, inject, signal } from '@angular/core';
|
||||
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
||||
import { RouterLink } from '@angular/router';
|
||||
import { finalize } from 'rxjs';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
import { MatButtonToggleModule } from '@angular/material/button-toggle';
|
||||
import { MatCardModule } from '@angular/material/card';
|
||||
import { MatDialog, MatDialogModule } from '@angular/material/dialog';
|
||||
import { MatFormFieldModule } from '@angular/material/form-field';
|
||||
import { MatIconModule } from '@angular/material/icon';
|
||||
import { MatInputModule } from '@angular/material/input';
|
||||
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
|
||||
import { MatSelectModule } from '@angular/material/select';
|
||||
import { MatSnackBar, MatSnackBarModule } from '@angular/material/snack-bar';
|
||||
import { getAuthErrorMessage } from '../auth/error-message';
|
||||
import { OnboardingService } from '../onboarding/onboarding.service';
|
||||
import { ConfirmDeleteListDialogComponent } from './confirm-delete-list-dialog/confirm-delete-list-dialog.component';
|
||||
import { ListTemplateKind } from '../templates/templates.models';
|
||||
import { ListRealtimeEvent, UserList, UserListItem } from './lists.models';
|
||||
import { ListsRealtimeService } from './lists-realtime.service';
|
||||
@@ -34,23 +38,28 @@ type ListKindFilter = ListTemplateKind | 'all';
|
||||
MatButtonModule,
|
||||
MatButtonToggleModule,
|
||||
MatCardModule,
|
||||
MatDialogModule,
|
||||
MatFormFieldModule,
|
||||
MatIconModule,
|
||||
MatInputModule,
|
||||
MatProgressSpinnerModule,
|
||||
MatSelectModule,
|
||||
MatSnackBarModule,
|
||||
],
|
||||
templateUrl: './lists.component.html',
|
||||
styleUrls: ['../workspace-page.scss', './lists.component.scss'],
|
||||
})
|
||||
export class ListsComponent implements OnInit {
|
||||
private readonly destroyRef = inject(DestroyRef);
|
||||
private readonly dialog = inject(MatDialog);
|
||||
private readonly listsService = inject(ListsService);
|
||||
private readonly listsRealtimeService = inject(ListsRealtimeService);
|
||||
private readonly snackBar = inject(MatSnackBar);
|
||||
protected readonly onboarding = inject(OnboardingService);
|
||||
|
||||
protected readonly lists = signal<UserList[]>([]);
|
||||
protected readonly loading = signal(true);
|
||||
protected readonly deletingListId = signal<string | null>(null);
|
||||
protected readonly errorMessage = signal<string | null>(null);
|
||||
protected readonly searchTerm = signal('');
|
||||
protected readonly kindFilter = signal<ListKindFilter>('all');
|
||||
@@ -171,6 +180,47 @@ export class ListsComponent implements OnInit {
|
||||
this.sortOption.set('updated-desc');
|
||||
}
|
||||
|
||||
protected deleteList(list: UserList): void {
|
||||
if (list.accessRole !== 'owner' || this.deletingListId()) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.dialog
|
||||
.open<ConfirmDeleteListDialogComponent, { listName: string }, boolean>(
|
||||
ConfirmDeleteListDialogComponent,
|
||||
{
|
||||
data: { listName: list.name },
|
||||
maxWidth: '420px',
|
||||
width: 'calc(100vw - 32px)',
|
||||
},
|
||||
)
|
||||
.afterClosed()
|
||||
.subscribe((confirmed) => {
|
||||
if (!confirmed) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.deletingListId.set(list.id);
|
||||
|
||||
this.listsService
|
||||
.deleteList(list.id)
|
||||
.pipe(finalize(() => this.deletingListId.set(null)))
|
||||
.subscribe({
|
||||
next: () => {
|
||||
this.lists.update((lists) =>
|
||||
lists.filter((existingList) => existingList.id !== list.id),
|
||||
);
|
||||
this.snackBar.open('Liste geloescht.', 'OK', { duration: 3000 });
|
||||
},
|
||||
error: (error: unknown) => {
|
||||
this.snackBar.open(getAuthErrorMessage(error), 'OK', {
|
||||
duration: 5000,
|
||||
});
|
||||
},
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private subscribeToRealtime(): void {
|
||||
this.listsRealtimeService
|
||||
.events()
|
||||
|
||||
@@ -32,6 +32,10 @@ export class ListsService {
|
||||
return this.http.patch<UserList>(`${this.apiUrl}/${listId}`, data);
|
||||
}
|
||||
|
||||
deleteList(listId: string): Observable<{ message: string }> {
|
||||
return this.http.delete<{ message: string }>(`${this.apiUrl}/${listId}`);
|
||||
}
|
||||
|
||||
shareList(listId: string, userId: string): Observable<UserList> {
|
||||
return this.http.post<UserList>(`${this.apiUrl}/${listId}/shares`, { userId });
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user