dashboard
This commit is contained in:
@@ -13,7 +13,7 @@
|
||||
|
||||
<a
|
||||
class="brand"
|
||||
[routerLink]="auth.isAuthenticated() ? '/lists' : '/login'"
|
||||
[routerLink]="auth.isAuthenticated() ? '/dashboard' : '/login'"
|
||||
aria-label="Listify Startseite"
|
||||
>
|
||||
<mat-icon aria-hidden="true">checklist</mat-icon>
|
||||
@@ -64,6 +64,16 @@
|
||||
>
|
||||
<nav aria-label="Hauptnavigation">
|
||||
<mat-nav-list>
|
||||
<a
|
||||
mat-list-item
|
||||
routerLink="/dashboard"
|
||||
routerLinkActive="active-nav-link"
|
||||
ariaCurrentWhenActive="page"
|
||||
(click)="closeSidebarOnCompact()"
|
||||
>
|
||||
<mat-icon matListItemIcon aria-hidden="true">space_dashboard</mat-icon>
|
||||
<span matListItemTitle>Dashboard</span>
|
||||
</a>
|
||||
<a
|
||||
mat-list-item
|
||||
routerLink="/templates"
|
||||
@@ -120,6 +130,15 @@
|
||||
<app-assistant-chat />
|
||||
|
||||
<nav class="bottom-nav" aria-label="Mobile Hauptnavigation">
|
||||
<a
|
||||
class="bottom-nav-link"
|
||||
routerLink="/dashboard"
|
||||
routerLinkActive="active-bottom-link"
|
||||
ariaCurrentWhenActive="page"
|
||||
>
|
||||
<mat-icon aria-hidden="true">space_dashboard</mat-icon>
|
||||
<span>Dashboard</span>
|
||||
</a>
|
||||
<a
|
||||
class="bottom-nav-link"
|
||||
routerLink="/templates"
|
||||
|
||||
@@ -11,7 +11,7 @@ import { TemplatesComponent } from './templates/templates.component';
|
||||
import { TemplateDetailComponent } from './templates/template-detail/template-detail.component';
|
||||
|
||||
export const routes: Routes = [
|
||||
{ path: '', pathMatch: 'full', redirectTo: 'lists' },
|
||||
{ path: '', pathMatch: 'full', redirectTo: 'dashboard' },
|
||||
{ path: 'login', component: LoginComponent, canActivate: [unauthGuard] },
|
||||
{ path: 'register', component: RegisterComponent, canActivate: [unauthGuard] },
|
||||
{ path: 'verify-email', component: VerifyEmailComponent, canActivate: [unauthGuard] },
|
||||
@@ -19,6 +19,12 @@ export const routes: Routes = [
|
||||
path: 'auth',
|
||||
children: [{ path: 'verify-email', component: VerifyEmailComponent }],
|
||||
},
|
||||
{
|
||||
path: 'dashboard',
|
||||
loadComponent: () =>
|
||||
import('./dashboard/dashboard.component').then((module) => module.DashboardComponent),
|
||||
canActivate: [authGuard],
|
||||
},
|
||||
{ path: 'templates', component: TemplatesComponent, canActivate: [authGuard] },
|
||||
{
|
||||
path: 'templates/new',
|
||||
|
||||
@@ -2,8 +2,16 @@
|
||||
display: block;
|
||||
min-height: 100dvh;
|
||||
background:
|
||||
linear-gradient(140deg, color-mix(in srgb, var(--mat-sys-primary) 14%, transparent), transparent 38%),
|
||||
linear-gradient(320deg, color-mix(in srgb, var(--mat-sys-tertiary) 12%, transparent), transparent 36%),
|
||||
linear-gradient(
|
||||
140deg,
|
||||
color-mix(in srgb, var(--mat-sys-primary) 14%, transparent),
|
||||
transparent 38%
|
||||
),
|
||||
linear-gradient(
|
||||
320deg,
|
||||
color-mix(in srgb, var(--mat-sys-tertiary) 12%, transparent),
|
||||
transparent 36%
|
||||
),
|
||||
var(--mat-sys-surface-container);
|
||||
}
|
||||
|
||||
@@ -140,7 +148,7 @@
|
||||
left: 0;
|
||||
z-index: 20;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||
grid-template-columns: repeat(4, minmax(0, 1fr));
|
||||
gap: 0.25rem;
|
||||
padding: 0.35rem 0.5rem calc(0.35rem + env(safe-area-inset-bottom));
|
||||
border-top: 1px solid color-mix(in srgb, var(--mat-sys-outline-variant) 72%, transparent);
|
||||
|
||||
183
listify-client/src/app/dashboard/dashboard.component.html
Normal file
183
listify-client/src/app/dashboard/dashboard.component.html
Normal file
@@ -0,0 +1,183 @@
|
||||
<section class="workspace-page dashboard-page">
|
||||
<header class="page-header">
|
||||
<div>
|
||||
<h1>Dashboard</h1>
|
||||
<p>Deine wichtigsten Listen und neue Ideen fuer diese Woche.</p>
|
||||
</div>
|
||||
|
||||
<a mat-flat-button routerLink="/lists/new">
|
||||
<mat-icon aria-hidden="true">add</mat-icon>
|
||||
Neue Liste
|
||||
</a>
|
||||
</header>
|
||||
|
||||
@if (loading()) {
|
||||
<mat-card class="state-card" appearance="outlined">
|
||||
<mat-card-content>
|
||||
<mat-progress-spinner mode="indeterminate" diameter="40" />
|
||||
<h2>Dashboard wird geladen</h2>
|
||||
</mat-card-content>
|
||||
</mat-card>
|
||||
} @else if (errorMessage()) {
|
||||
<mat-card class="state-card error-state" appearance="outlined">
|
||||
<mat-card-content>
|
||||
<mat-icon aria-hidden="true">error</mat-icon>
|
||||
<h2>Dashboard konnte nicht geladen werden</h2>
|
||||
<p>{{ errorMessage() }}</p>
|
||||
<button mat-stroked-button type="button" (click)="loadDashboard()">
|
||||
<mat-icon aria-hidden="true">refresh</mat-icon>
|
||||
Erneut laden
|
||||
</button>
|
||||
</mat-card-content>
|
||||
</mat-card>
|
||||
} @else if (dashboard()) {
|
||||
<section class="dashboard-section" aria-labelledby="important-lists-title">
|
||||
<div class="section-heading">
|
||||
<div>
|
||||
<h2 id="important-lists-title">Wichtige Listen heute</h2>
|
||||
<p>{{ dashboard()!.dateKey }} - {{ dashboard()!.timezone }}</p>
|
||||
</div>
|
||||
<a mat-stroked-button routerLink="/lists">
|
||||
<mat-icon aria-hidden="true">format_list_bulleted</mat-icon>
|
||||
Alle Listen
|
||||
</a>
|
||||
</div>
|
||||
|
||||
@if (hasImportantLists()) {
|
||||
<div class="important-list-grid">
|
||||
@for (item of dashboard()!.importantLists; track item.list.id) {
|
||||
<mat-card class="important-list-card" appearance="outlined">
|
||||
<mat-card-header>
|
||||
<mat-card-title>{{ item.list.name }}</mat-card-title>
|
||||
<mat-card-subtitle>
|
||||
{{ item.checkedItems }} / {{ item.totalItems }} erledigt
|
||||
@if (item.list.accessRole === 'collaborator') {
|
||||
- geteilt
|
||||
}
|
||||
</mat-card-subtitle>
|
||||
</mat-card-header>
|
||||
|
||||
<mat-card-content>
|
||||
<mat-progress-bar mode="determinate" [value]="progressPercent(item.progress)" />
|
||||
|
||||
<div class="dashboard-meta">
|
||||
<span>
|
||||
<mat-icon aria-hidden="true">radio_button_unchecked</mat-icon>
|
||||
{{ item.openItems }} offen
|
||||
</span>
|
||||
@if (item.list.reminderAt) {
|
||||
<span>
|
||||
<mat-icon aria-hidden="true">notifications</mat-icon>
|
||||
{{ item.list.reminderAt | date: 'dd.MM., HH:mm' }}
|
||||
</span>
|
||||
}
|
||||
<span>
|
||||
<mat-icon aria-hidden="true">
|
||||
{{ item.source === 'ai' ? 'auto_awesome' : 'rule' }}
|
||||
</mat-icon>
|
||||
{{ item.source === 'ai' ? 'KI' : 'Fallback' }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<p class="ai-reason">{{ item.reason }}</p>
|
||||
</mat-card-content>
|
||||
|
||||
<mat-card-actions align="end">
|
||||
<a mat-button [routerLink]="['/lists', item.list.id]">
|
||||
<mat-icon aria-hidden="true">open_in_new</mat-icon>
|
||||
Oeffnen
|
||||
</a>
|
||||
</mat-card-actions>
|
||||
</mat-card>
|
||||
}
|
||||
</div>
|
||||
} @else {
|
||||
<mat-card class="state-card" appearance="outlined">
|
||||
<mat-card-content>
|
||||
<mat-icon aria-hidden="true">format_list_bulleted</mat-icon>
|
||||
<h2>Noch keine wichtigen Listen</h2>
|
||||
<p>Erstelle eine Liste oder teile eine vorhandene Liste mit deinem Account.</p>
|
||||
</mat-card-content>
|
||||
</mat-card>
|
||||
}
|
||||
</section>
|
||||
|
||||
<section class="dashboard-section" aria-labelledby="weekly-suggestions-title">
|
||||
<div class="section-heading">
|
||||
<div>
|
||||
<h2 id="weekly-suggestions-title">Listenvorschlaege dieser Woche</h2>
|
||||
<p>{{ dashboard()!.weekKey }}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@if (dashboard()!.weeklySuggestions.errorMessage && suggestions().length === 0) {
|
||||
<mat-card class="state-card error-state" appearance="outlined">
|
||||
<mat-card-content>
|
||||
<mat-icon aria-hidden="true">tips_and_updates</mat-icon>
|
||||
<h2>Keine Vorschlaege verfuegbar</h2>
|
||||
<p>{{ dashboard()!.weeklySuggestions.errorMessage }}</p>
|
||||
</mat-card-content>
|
||||
</mat-card>
|
||||
} @else if (suggestions().length > 0) {
|
||||
<div class="suggestion-grid">
|
||||
@for (suggestion of suggestions(); track suggestion.id) {
|
||||
<mat-card class="suggestion-card" appearance="outlined">
|
||||
<mat-card-header>
|
||||
<mat-card-title>{{ suggestion.title }}</mat-card-title>
|
||||
@if (suggestion.description) {
|
||||
<mat-card-subtitle>{{ suggestion.description }}</mat-card-subtitle>
|
||||
}
|
||||
</mat-card-header>
|
||||
|
||||
<mat-card-content>
|
||||
<p class="ai-reason">{{ suggestion.reason }}</p>
|
||||
|
||||
@if (suggestion.items.length > 0) {
|
||||
<ul class="suggestion-items">
|
||||
@for (item of suggestion.items.slice(0, 5); track item.title) {
|
||||
<li>
|
||||
<mat-icon aria-hidden="true">add_task</mat-icon>
|
||||
<span>{{ item.title }}</span>
|
||||
</li>
|
||||
}
|
||||
</ul>
|
||||
}
|
||||
</mat-card-content>
|
||||
|
||||
<mat-card-actions align="end">
|
||||
@if (suggestion.createdListId) {
|
||||
<a mat-button [routerLink]="['/lists', suggestion.createdListId]">
|
||||
<mat-icon aria-hidden="true">open_in_new</mat-icon>
|
||||
Zur Liste
|
||||
</a>
|
||||
} @else {
|
||||
<button
|
||||
mat-flat-button
|
||||
type="button"
|
||||
[disabled]="creatingSuggestionId() === suggestion.id"
|
||||
(click)="createSuggestion(suggestion)"
|
||||
>
|
||||
@if (creatingSuggestionId() === suggestion.id) {
|
||||
<mat-progress-spinner mode="indeterminate" diameter="18" />
|
||||
} @else {
|
||||
<mat-icon aria-hidden="true">playlist_add</mat-icon>
|
||||
}
|
||||
Liste erstellen
|
||||
</button>
|
||||
}
|
||||
</mat-card-actions>
|
||||
</mat-card>
|
||||
}
|
||||
</div>
|
||||
} @else {
|
||||
<mat-card class="state-card" appearance="outlined">
|
||||
<mat-card-content>
|
||||
<mat-icon aria-hidden="true">tips_and_updates</mat-icon>
|
||||
<h2>Keine Vorschlaege verfuegbar</h2>
|
||||
<p>Fuer diese Woche wurden keine neuen Listenvorschlaege gespeichert.</p>
|
||||
</mat-card-content>
|
||||
</mat-card>
|
||||
}
|
||||
</section>
|
||||
}
|
||||
</section>
|
||||
142
listify-client/src/app/dashboard/dashboard.component.scss
Normal file
142
listify-client/src/app/dashboard/dashboard.component.scss
Normal file
@@ -0,0 +1,142 @@
|
||||
.dashboard-page {
|
||||
display: grid;
|
||||
align-content: start;
|
||||
gap: 1.5rem;
|
||||
}
|
||||
|
||||
.dashboard-section {
|
||||
display: grid;
|
||||
gap: 0.85rem;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.section-heading {
|
||||
display: flex;
|
||||
align-items: stretch;
|
||||
flex-direction: column;
|
||||
gap: 0.75rem;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.section-heading h2 {
|
||||
margin: 0;
|
||||
font-size: 1.25rem;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.section-heading p {
|
||||
margin: 0.2rem 0 0;
|
||||
color: var(--mat-sys-on-surface-variant);
|
||||
}
|
||||
|
||||
.important-list-grid,
|
||||
.suggestion-grid {
|
||||
display: grid;
|
||||
gap: 0.75rem;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.important-list-card,
|
||||
.suggestion-card {
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
border: 1px solid color-mix(in srgb, var(--mat-sys-outline-variant) 72%, transparent);
|
||||
border-radius: 8px;
|
||||
background: var(--mat-sys-surface);
|
||||
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
|
||||
.important-list-card mat-card-title,
|
||||
.important-list-card mat-card-subtitle,
|
||||
.suggestion-card mat-card-title,
|
||||
.suggestion-card mat-card-subtitle {
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
|
||||
.dashboard-meta {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.45rem 0.75rem;
|
||||
margin-top: 0.85rem;
|
||||
color: var(--mat-sys-on-surface-variant);
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
|
||||
.dashboard-meta span {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.3rem;
|
||||
}
|
||||
|
||||
.dashboard-meta mat-icon,
|
||||
.suggestion-items mat-icon {
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
color: var(--mat-sys-primary);
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.ai-reason {
|
||||
margin: 0.85rem 0 0;
|
||||
color: var(--mat-sys-on-surface-variant);
|
||||
line-height: 1.45;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
|
||||
.suggestion-items {
|
||||
display: grid;
|
||||
gap: 0.45rem;
|
||||
margin: 1rem 0 0;
|
||||
padding: 0;
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
.suggestion-items li {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.suggestion-items span {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
button mat-progress-spinner,
|
||||
a mat-progress-spinner {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
@media (min-width: 701px) {
|
||||
.dashboard-page {
|
||||
gap: 2rem;
|
||||
}
|
||||
|
||||
.section-heading {
|
||||
align-items: center;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.important-list-grid {
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.suggestion-grid {
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
gap: 1rem;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 1040px) {
|
||||
.important-list-grid {
|
||||
grid-template-columns: repeat(5, minmax(0, 1fr));
|
||||
}
|
||||
|
||||
.suggestion-grid {
|
||||
grid-template-columns: repeat(5, minmax(0, 1fr));
|
||||
}
|
||||
}
|
||||
105
listify-client/src/app/dashboard/dashboard.component.ts
Normal file
105
listify-client/src/app/dashboard/dashboard.component.ts
Normal file
@@ -0,0 +1,105 @@
|
||||
import { DatePipe } from '@angular/common';
|
||||
import { Component, OnInit, computed, inject, signal } from '@angular/core';
|
||||
import { RouterLink } from '@angular/router';
|
||||
import { finalize } from 'rxjs';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
import { MatCardModule } from '@angular/material/card';
|
||||
import { MatIconModule } from '@angular/material/icon';
|
||||
import { MatProgressBarModule } from '@angular/material/progress-bar';
|
||||
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
|
||||
import { MatSnackBar, MatSnackBarModule } from '@angular/material/snack-bar';
|
||||
import { getAuthErrorMessage } from '../auth/error-message';
|
||||
import { DashboardResponse, DashboardWeeklySuggestion } from './dashboard.models';
|
||||
import { DashboardService } from './dashboard.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-dashboard',
|
||||
imports: [
|
||||
DatePipe,
|
||||
RouterLink,
|
||||
MatButtonModule,
|
||||
MatCardModule,
|
||||
MatIconModule,
|
||||
MatProgressBarModule,
|
||||
MatProgressSpinnerModule,
|
||||
MatSnackBarModule,
|
||||
],
|
||||
templateUrl: './dashboard.component.html',
|
||||
styleUrls: ['../workspace-page.scss', './dashboard.component.scss'],
|
||||
})
|
||||
export class DashboardComponent implements OnInit {
|
||||
private readonly dashboardService = inject(DashboardService);
|
||||
private readonly snackBar = inject(MatSnackBar);
|
||||
|
||||
protected readonly dashboard = signal<DashboardResponse | null>(null);
|
||||
protected readonly loading = signal(true);
|
||||
protected readonly errorMessage = signal<string | null>(null);
|
||||
protected readonly creatingSuggestionId = signal<string | null>(null);
|
||||
protected readonly hasImportantLists = computed(
|
||||
() => (this.dashboard()?.importantLists.length ?? 0) > 0,
|
||||
);
|
||||
protected readonly suggestions = computed(
|
||||
() => this.dashboard()?.weeklySuggestions.suggestions ?? [],
|
||||
);
|
||||
|
||||
ngOnInit(): void {
|
||||
this.loadDashboard();
|
||||
}
|
||||
|
||||
protected loadDashboard(): void {
|
||||
this.loading.set(true);
|
||||
this.errorMessage.set(null);
|
||||
|
||||
this.dashboardService.getDashboard().subscribe({
|
||||
next: (dashboard) => {
|
||||
this.dashboard.set(dashboard);
|
||||
this.loading.set(false);
|
||||
},
|
||||
error: (error: unknown) => {
|
||||
this.errorMessage.set(getAuthErrorMessage(error));
|
||||
this.loading.set(false);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
protected createSuggestion(suggestion: DashboardWeeklySuggestion): void {
|
||||
if (suggestion.createdListId || this.creatingSuggestionId()) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.creatingSuggestionId.set(suggestion.id);
|
||||
this.dashboardService
|
||||
.createSuggestion(suggestion.id)
|
||||
.pipe(finalize(() => this.creatingSuggestionId.set(null)))
|
||||
.subscribe({
|
||||
next: (response) => {
|
||||
this.dashboard.update((dashboard) =>
|
||||
dashboard
|
||||
? {
|
||||
...dashboard,
|
||||
weeklySuggestions: {
|
||||
...dashboard.weeklySuggestions,
|
||||
suggestions: dashboard.weeklySuggestions.suggestions.map(
|
||||
(existingSuggestion) =>
|
||||
existingSuggestion.id === response.suggestion.id
|
||||
? response.suggestion
|
||||
: existingSuggestion,
|
||||
),
|
||||
},
|
||||
}
|
||||
: dashboard,
|
||||
);
|
||||
this.snackBar.open('Liste erstellt.', 'OK', { duration: 2500 });
|
||||
},
|
||||
error: (error: unknown) => {
|
||||
this.snackBar.open(getAuthErrorMessage(error), 'OK', {
|
||||
duration: 5000,
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
protected progressPercent(value: number): number {
|
||||
return Math.round(value * 100);
|
||||
}
|
||||
}
|
||||
45
listify-client/src/app/dashboard/dashboard.models.ts
Normal file
45
listify-client/src/app/dashboard/dashboard.models.ts
Normal file
@@ -0,0 +1,45 @@
|
||||
import { UserList } from '../lists/lists.models';
|
||||
|
||||
export interface DashboardImportantList {
|
||||
list: UserList;
|
||||
reason: string;
|
||||
source: 'ai' | 'fallback';
|
||||
checkedItems: number;
|
||||
openItems: number;
|
||||
totalItems: number;
|
||||
progress: number;
|
||||
hasReminder: boolean;
|
||||
}
|
||||
|
||||
export interface DashboardWeeklySuggestionItem {
|
||||
title: string;
|
||||
notes?: string;
|
||||
quantity?: number;
|
||||
required: boolean;
|
||||
}
|
||||
|
||||
export interface DashboardWeeklySuggestion {
|
||||
id: string;
|
||||
title: string;
|
||||
description?: string;
|
||||
items: DashboardWeeklySuggestionItem[];
|
||||
reason: string;
|
||||
createdListId?: string;
|
||||
}
|
||||
|
||||
export interface DashboardResponse {
|
||||
timezone: string;
|
||||
dateKey: string;
|
||||
weekKey: string;
|
||||
importantLists: DashboardImportantList[];
|
||||
weeklySuggestions: {
|
||||
suggestions: DashboardWeeklySuggestion[];
|
||||
errorMessage?: string;
|
||||
generatedAt?: string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface CreateDashboardSuggestionResponse {
|
||||
list: UserList;
|
||||
suggestion: DashboardWeeklySuggestion;
|
||||
}
|
||||
21
listify-client/src/app/dashboard/dashboard.service.ts
Normal file
21
listify-client/src/app/dashboard/dashboard.service.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
import { HttpClient } from '@angular/common/http';
|
||||
import { Injectable, inject } from '@angular/core';
|
||||
import { Observable } from 'rxjs';
|
||||
import { CreateDashboardSuggestionResponse, DashboardResponse } from './dashboard.models';
|
||||
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class DashboardService {
|
||||
private readonly http = inject(HttpClient);
|
||||
private readonly apiUrl = '/api/dashboard';
|
||||
|
||||
getDashboard(): Observable<DashboardResponse> {
|
||||
return this.http.get<DashboardResponse>(this.apiUrl);
|
||||
}
|
||||
|
||||
createSuggestion(suggestionId: string): Observable<CreateDashboardSuggestionResponse> {
|
||||
return this.http.post<CreateDashboardSuggestionResponse>(
|
||||
`${this.apiUrl}/weekly-suggestions/${suggestionId}/create`,
|
||||
{},
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user