generated from nhcarrigan/template
feat: add start and end dates
This commit is contained in:
@@ -39,22 +39,22 @@ import { Suggestion, SuggestionStatus, SuggestionEntity } from '@library/shared-
|
||||
All ({{ suggestions().length }})
|
||||
</button>
|
||||
<button
|
||||
(click)="setFilter(SuggestionStatus.UNREVIEWED)"
|
||||
[class.active]="statusFilter() === SuggestionStatus.UNREVIEWED"
|
||||
(click)="setFilter(SuggestionStatus.unreviewed)"
|
||||
[class.active]="statusFilter() === SuggestionStatus.unreviewed"
|
||||
class="filter-btn pending"
|
||||
>
|
||||
Pending ({{ unreviewedCount() }})
|
||||
</button>
|
||||
<button
|
||||
(click)="setFilter(SuggestionStatus.ACCEPTED)"
|
||||
[class.active]="statusFilter() === SuggestionStatus.ACCEPTED"
|
||||
(click)="setFilter(SuggestionStatus.accepted)"
|
||||
[class.active]="statusFilter() === SuggestionStatus.accepted"
|
||||
class="filter-btn accepted"
|
||||
>
|
||||
Accepted ({{ acceptedCount() }})
|
||||
</button>
|
||||
<button
|
||||
(click)="setFilter(SuggestionStatus.DECLINED)"
|
||||
[class.active]="statusFilter() === SuggestionStatus.DECLINED"
|
||||
(click)="setFilter(SuggestionStatus.declined)"
|
||||
[class.active]="statusFilter() === SuggestionStatus.declined"
|
||||
class="filter-btn declined"
|
||||
>
|
||||
Declined ({{ declinedCount() }})
|
||||
@@ -171,7 +171,7 @@ import { Suggestion, SuggestionStatus, SuggestionEntity } from '@library/shared-
|
||||
}
|
||||
</div>
|
||||
|
||||
@if (suggestion.status === SuggestionStatus.DECLINED && suggestion.declineReason) {
|
||||
@if (suggestion.status === SuggestionStatus.declined && suggestion.declineReason) {
|
||||
<div class="decline-reason">
|
||||
<strong>Decline reason:</strong> {{ suggestion.declineReason }}
|
||||
</div>
|
||||
@@ -180,7 +180,7 @@ import { Suggestion, SuggestionStatus, SuggestionEntity } from '@library/shared-
|
||||
<div class="suggestion-footer">
|
||||
<span class="date">Suggested on {{ formatDate(suggestion.createdAt) }}</span>
|
||||
|
||||
@if (suggestion.status === SuggestionStatus.UNREVIEWED) {
|
||||
@if (suggestion.status === SuggestionStatus.unreviewed) {
|
||||
<div class="actions">
|
||||
<button (click)="acceptSuggestion(suggestion)" class="btn btn-accept">
|
||||
Accept
|
||||
@@ -248,7 +248,7 @@ import { Suggestion, SuggestionStatus, SuggestionEntity } from '@library/shared-
|
||||
</div>
|
||||
|
||||
@switch (editingSuggestion()!.entityType) {
|
||||
@case ('BOOK') {
|
||||
@case (SuggestionEntity.book) {
|
||||
<div class="form-group">
|
||||
<label for="edit-author">Author</label>
|
||||
<input
|
||||
@@ -269,7 +269,7 @@ import { Suggestion, SuggestionStatus, SuggestionEntity } from '@library/shared-
|
||||
>
|
||||
</div>
|
||||
}
|
||||
@case ('GAME') {
|
||||
@case (SuggestionEntity.game) {
|
||||
<div class="form-group">
|
||||
<label for="edit-platform">Platform</label>
|
||||
<input
|
||||
@@ -280,7 +280,7 @@ import { Suggestion, SuggestionStatus, SuggestionEntity } from '@library/shared-
|
||||
>
|
||||
</div>
|
||||
}
|
||||
@case ('MUSIC') {
|
||||
@case (SuggestionEntity.music) {
|
||||
<div class="form-group">
|
||||
<label for="edit-artist">Artist</label>
|
||||
<input
|
||||
@@ -300,7 +300,7 @@ import { Suggestion, SuggestionStatus, SuggestionEntity } from '@library/shared-
|
||||
</select>
|
||||
</div>
|
||||
}
|
||||
@case ('ART') {
|
||||
@case (SuggestionEntity.art) {
|
||||
<div class="form-group">
|
||||
<label for="edit-artist">Artist</label>
|
||||
<input
|
||||
@@ -331,7 +331,7 @@ import { Suggestion, SuggestionStatus, SuggestionEntity } from '@library/shared-
|
||||
>
|
||||
</div>
|
||||
}
|
||||
@case ('SHOW') {
|
||||
@case (SuggestionEntity.show) {
|
||||
<div class="form-group">
|
||||
<label for="edit-type">Type</label>
|
||||
<select id="edit-type" [(ngModel)]="editedData.type" name="type" required>
|
||||
@@ -342,7 +342,7 @@ import { Suggestion, SuggestionStatus, SuggestionEntity } from '@library/shared-
|
||||
</select>
|
||||
</div>
|
||||
}
|
||||
@case ('MANGA') {
|
||||
@case (SuggestionEntity.manga) {
|
||||
<div class="form-group">
|
||||
<label for="edit-author">Author</label>
|
||||
<input
|
||||
@@ -366,7 +366,7 @@ import { Suggestion, SuggestionStatus, SuggestionEntity } from '@library/shared-
|
||||
></textarea>
|
||||
</div>
|
||||
|
||||
@if (editingSuggestion()!.entityType !== 'ART') {
|
||||
@if (editingSuggestion()!.entityType !== SuggestionEntity.art) {
|
||||
<div class="form-group">
|
||||
<label for="edit-coverImage">Cover Image URL</label>
|
||||
<input
|
||||
@@ -727,10 +727,11 @@ export class AdminSuggestionsComponent implements OnInit {
|
||||
pageSize = signal(25);
|
||||
|
||||
SuggestionStatus = SuggestionStatus;
|
||||
SuggestionEntity = SuggestionEntity;
|
||||
|
||||
unreviewedCount = () => this.suggestions().filter(s => s.status === SuggestionStatus.UNREVIEWED).length;
|
||||
acceptedCount = () => this.suggestions().filter(s => s.status === SuggestionStatus.ACCEPTED).length;
|
||||
declinedCount = () => this.suggestions().filter(s => s.status === SuggestionStatus.DECLINED).length;
|
||||
unreviewedCount = () => this.suggestions().filter(s => s.status === SuggestionStatus.unreviewed).length;
|
||||
acceptedCount = () => this.suggestions().filter(s => s.status === SuggestionStatus.accepted).length;
|
||||
declinedCount = () => this.suggestions().filter(s => s.status === SuggestionStatus.declined).length;
|
||||
|
||||
filteredSuggestions = computed(() => {
|
||||
const filter = this.statusFilter();
|
||||
@@ -788,20 +789,20 @@ export class AdminSuggestionsComponent implements OnInit {
|
||||
|
||||
getStatusLabel(status: SuggestionStatus): string {
|
||||
switch (status) {
|
||||
case SuggestionStatus.UNREVIEWED: return 'Pending';
|
||||
case SuggestionStatus.ACCEPTED: return 'Accepted';
|
||||
case SuggestionStatus.DECLINED: return 'Declined';
|
||||
case SuggestionStatus.unreviewed: return 'Pending';
|
||||
case SuggestionStatus.accepted: return 'Accepted';
|
||||
case SuggestionStatus.declined: return 'Declined';
|
||||
}
|
||||
}
|
||||
|
||||
getEntityIcon(entityType: SuggestionEntity): string {
|
||||
switch (entityType) {
|
||||
case SuggestionEntity.GAME: return '🎮';
|
||||
case SuggestionEntity.BOOK: return '📚';
|
||||
case SuggestionEntity.MUSIC: return '🎵';
|
||||
case SuggestionEntity.MANGA: return '📖';
|
||||
case SuggestionEntity.SHOW: return '📺';
|
||||
case SuggestionEntity.ART: return '🎨';
|
||||
case SuggestionEntity.game: return '🎮';
|
||||
case SuggestionEntity.book: return '📚';
|
||||
case SuggestionEntity.music: return '🎵';
|
||||
case SuggestionEntity.manga: return '📖';
|
||||
case SuggestionEntity.show: return '📺';
|
||||
case SuggestionEntity.art: return '🎨';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -822,39 +823,39 @@ export class AdminSuggestionsComponent implements OnInit {
|
||||
|
||||
// Add entity-specific data
|
||||
switch (suggestion.entityType) {
|
||||
case 'BOOK':
|
||||
case SuggestionEntity.book:
|
||||
const bookData = suggestion.bookData as any;
|
||||
this.editedData.author = bookData?.author || '';
|
||||
this.editedData.isbn = bookData?.isbn || '';
|
||||
this.editedData.notes = bookData?.notes || '';
|
||||
this.editedData.coverImage = bookData?.coverImage || '';
|
||||
break;
|
||||
case 'GAME':
|
||||
case SuggestionEntity.game:
|
||||
const gameData = suggestion.gameData as any;
|
||||
this.editedData.platform = gameData?.platform || '';
|
||||
this.editedData.notes = gameData?.notes || '';
|
||||
this.editedData.coverImage = gameData?.coverImage || '';
|
||||
break;
|
||||
case 'MUSIC':
|
||||
case SuggestionEntity.music:
|
||||
const musicData = suggestion.musicData as any;
|
||||
this.editedData.artist = musicData?.artist || '';
|
||||
this.editedData.type = musicData?.type || 'ALBUM';
|
||||
this.editedData.notes = musicData?.notes || '';
|
||||
this.editedData.coverArt = musicData?.coverArt || '';
|
||||
break;
|
||||
case 'ART':
|
||||
case SuggestionEntity.art:
|
||||
const artData = suggestion.artData as any;
|
||||
this.editedData.artist = artData?.artist || '';
|
||||
this.editedData.description = artData?.description || '';
|
||||
this.editedData.imageUrl = artData?.imageUrl || '';
|
||||
break;
|
||||
case 'SHOW':
|
||||
case SuggestionEntity.show:
|
||||
const showData = suggestion.showData as any;
|
||||
this.editedData.type = showData?.type || 'TV_SERIES';
|
||||
this.editedData.notes = showData?.notes || '';
|
||||
this.editedData.coverImage = showData?.coverImage || '';
|
||||
break;
|
||||
case 'MANGA':
|
||||
case SuggestionEntity.manga:
|
||||
const mangaData = suggestion.mangaData as any;
|
||||
this.editedData.author = mangaData?.author || '';
|
||||
this.editedData.notes = mangaData?.notes || '';
|
||||
|
||||
@@ -1571,7 +1571,7 @@ export class ArtGalleryComponent implements OnInit {
|
||||
|
||||
try {
|
||||
await this.suggestionService.createSuggestion({
|
||||
entityType: SuggestionEntity.ART,
|
||||
entityType: SuggestionEntity.art,
|
||||
title: this.suggestedArt.title,
|
||||
artist: this.suggestedArt.artist,
|
||||
imageUrl: this.suggestedArt.imageUrl,
|
||||
|
||||
@@ -82,6 +82,26 @@ import { Book, BookStatus, CreateBookDto, UpdateBookDto, Comment, SuggestionEnti
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="dateStarted">Date Started</label>
|
||||
<input
|
||||
type="date"
|
||||
id="dateStarted"
|
||||
[(ngModel)]="newBook.dateStarted"
|
||||
name="dateStarted"
|
||||
>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="dateFinished">Date Finished</label>
|
||||
<input
|
||||
type="date"
|
||||
id="dateFinished"
|
||||
[(ngModel)]="newBook.dateFinished"
|
||||
name="dateFinished"
|
||||
>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="rating">Rating (1-10)</label>
|
||||
<input
|
||||
@@ -223,6 +243,26 @@ import { Book, BookStatus, CreateBookDto, UpdateBookDto, Comment, SuggestionEnti
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="edit-dateStarted">Date Started</label>
|
||||
<input
|
||||
type="date"
|
||||
id="edit-dateStarted"
|
||||
[(ngModel)]="editBook.dateStarted"
|
||||
name="dateStarted"
|
||||
>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="edit-dateFinished">Date Finished</label>
|
||||
<input
|
||||
type="date"
|
||||
id="edit-dateFinished"
|
||||
[(ngModel)]="editBook.dateFinished"
|
||||
name="dateFinished"
|
||||
>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="edit-rating">Rating (1-10)</label>
|
||||
<input
|
||||
@@ -543,6 +583,12 @@ import { Book, BookStatus, CreateBookDto, UpdateBookDto, Comment, SuggestionEnti
|
||||
</div>
|
||||
}
|
||||
|
||||
@if (book.dateStarted) {
|
||||
<p class="date-started">
|
||||
Started: {{ formatDate(book.dateStarted) }}
|
||||
</p>
|
||||
}
|
||||
|
||||
@if (book.dateFinished) {
|
||||
<p class="date-finished">
|
||||
Finished: {{ formatDate(book.dateFinished) }}
|
||||
@@ -984,6 +1030,7 @@ import { Book, BookStatus, CreateBookDto, UpdateBookDto, Comment, SuggestionEnti
|
||||
margin: 0.5rem 0;
|
||||
}
|
||||
|
||||
.date-started,
|
||||
.date-finished {
|
||||
font-size: 0.85rem;
|
||||
color: var(--witch-plum);
|
||||
@@ -1462,11 +1509,13 @@ export class BooksListComponent implements OnInit {
|
||||
|
||||
totalFilteredBooks = computed(() => this.filteredBooks().length);
|
||||
|
||||
newBook: Partial<CreateBookDto> = {
|
||||
newBook: Partial<CreateBookDto> & { dateStarted?: Date; dateFinished?: Date } = {
|
||||
title: '',
|
||||
author: '',
|
||||
isbn: '',
|
||||
status: BookStatus.toRead,
|
||||
dateStarted: undefined,
|
||||
dateFinished: undefined,
|
||||
rating: undefined,
|
||||
notes: '',
|
||||
tags: [],
|
||||
@@ -1563,6 +1612,8 @@ export class BooksListComponent implements OnInit {
|
||||
author: '',
|
||||
isbn: '',
|
||||
status: BookStatus.toRead,
|
||||
dateStarted: undefined,
|
||||
dateFinished: undefined,
|
||||
rating: undefined,
|
||||
notes: '',
|
||||
coverImage: undefined,
|
||||
@@ -1629,6 +1680,8 @@ export class BooksListComponent implements OnInit {
|
||||
author: this.newBook.author,
|
||||
isbn: this.newBook.isbn,
|
||||
status: this.newBook.status,
|
||||
dateStarted: this.newBook.dateStarted ? new Date(this.newBook.dateStarted) : undefined,
|
||||
dateFinished: this.newBook.dateFinished ? new Date(this.newBook.dateFinished) : undefined,
|
||||
rating: this.newBook.rating,
|
||||
notes: this.newBook.notes,
|
||||
coverImage: this.newBook.coverImage,
|
||||
@@ -1657,6 +1710,8 @@ export class BooksListComponent implements OnInit {
|
||||
author: book.author,
|
||||
isbn: book.isbn,
|
||||
status: book.status,
|
||||
dateStarted: book.dateStarted,
|
||||
dateFinished: book.dateFinished,
|
||||
rating: book.rating,
|
||||
notes: book.notes,
|
||||
coverImage: book.coverImage,
|
||||
@@ -1685,7 +1740,13 @@ export class BooksListComponent implements OnInit {
|
||||
const book = this.editingBook();
|
||||
if (!book || !this.editBook.title || !this.editBook.author || !this.editBook.status) return;
|
||||
|
||||
this.booksService.updateBook(book.id, this.editBook).subscribe(() => {
|
||||
const updateData = {
|
||||
...this.editBook,
|
||||
dateStarted: this.editBook.dateStarted ? new Date(this.editBook.dateStarted) : undefined,
|
||||
dateFinished: this.editBook.dateFinished ? new Date(this.editBook.dateFinished) : undefined,
|
||||
};
|
||||
|
||||
this.booksService.updateBook(book.id, updateData).subscribe(() => {
|
||||
this.loadBooks();
|
||||
this.cancelEdit();
|
||||
});
|
||||
@@ -1883,7 +1944,7 @@ export class BooksListComponent implements OnInit {
|
||||
|
||||
try {
|
||||
await this.suggestionService.createSuggestion({
|
||||
entityType: SuggestionEntity.BOOK,
|
||||
entityType: SuggestionEntity.book,
|
||||
title: this.suggestedBook.title,
|
||||
author: this.suggestedBook.author,
|
||||
isbn: this.suggestedBook.isbn,
|
||||
|
||||
@@ -70,6 +70,26 @@ import { Game, GameStatus, CreateGameDto, UpdateGameDto, Comment, SuggestionEnti
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="dateStarted">Date Started</label>
|
||||
<input
|
||||
type="date"
|
||||
id="dateStarted"
|
||||
[(ngModel)]="newGame.dateStarted"
|
||||
name="dateStarted"
|
||||
>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="dateFinished">Date Finished</label>
|
||||
<input
|
||||
type="date"
|
||||
id="dateFinished"
|
||||
[(ngModel)]="newGame.dateFinished"
|
||||
name="dateFinished"
|
||||
>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="rating">Rating (1-10)</label>
|
||||
<input
|
||||
@@ -199,6 +219,26 @@ import { Game, GameStatus, CreateGameDto, UpdateGameDto, Comment, SuggestionEnti
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="edit-dateStarted">Date Started</label>
|
||||
<input
|
||||
type="date"
|
||||
id="edit-dateStarted"
|
||||
[(ngModel)]="editGame.dateStarted"
|
||||
name="dateStarted"
|
||||
>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="edit-dateFinished">Date Finished</label>
|
||||
<input
|
||||
type="date"
|
||||
id="edit-dateFinished"
|
||||
[(ngModel)]="editGame.dateFinished"
|
||||
name="dateFinished"
|
||||
>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="edit-rating">Rating (1-10)</label>
|
||||
<input
|
||||
@@ -500,6 +540,18 @@ import { Game, GameStatus, CreateGameDto, UpdateGameDto, Comment, SuggestionEnti
|
||||
</div>
|
||||
}
|
||||
|
||||
@if (game.dateStarted) {
|
||||
<p class="date-started">
|
||||
Started: {{ formatDate(game.dateStarted) }}
|
||||
</p>
|
||||
}
|
||||
|
||||
@if (game.dateFinished) {
|
||||
<p class="date-finished">
|
||||
Finished: {{ formatDate(game.dateFinished) }}
|
||||
</p>
|
||||
}
|
||||
|
||||
@if (authService.isAdmin()) {
|
||||
<div class="actions">
|
||||
<button (click)="startEdit(game)" class="btn btn-secondary btn-sm">
|
||||
@@ -854,6 +906,13 @@ import { Game, GameStatus, CreateGameDto, UpdateGameDto, Comment, SuggestionEnti
|
||||
margin: 0.5rem 0;
|
||||
}
|
||||
|
||||
.date-started,
|
||||
.date-finished {
|
||||
font-size: 0.85rem;
|
||||
color: #4b5563;
|
||||
margin-top: 0.5rem;
|
||||
}
|
||||
|
||||
.actions {
|
||||
margin-top: 1rem;
|
||||
}
|
||||
@@ -1257,10 +1316,12 @@ export class GamesListComponent implements OnInit {
|
||||
|
||||
totalFilteredGames = computed(() => this.filteredGames().length);
|
||||
|
||||
newGame: Partial<CreateGameDto> = {
|
||||
newGame: Partial<CreateGameDto> & { dateStarted?: Date; dateFinished?: Date } = {
|
||||
title: '',
|
||||
platform: '',
|
||||
status: GameStatus.backlog,
|
||||
dateStarted: undefined,
|
||||
dateFinished: undefined,
|
||||
rating: undefined,
|
||||
notes: '',
|
||||
tags: [],
|
||||
@@ -1352,6 +1413,8 @@ export class GamesListComponent implements OnInit {
|
||||
title: '',
|
||||
platform: '',
|
||||
status: GameStatus.backlog,
|
||||
dateStarted: undefined,
|
||||
dateFinished: undefined,
|
||||
rating: undefined,
|
||||
notes: '',
|
||||
coverImage: undefined,
|
||||
@@ -1420,6 +1483,8 @@ export class GamesListComponent implements OnInit {
|
||||
rating: this.newGame.rating,
|
||||
notes: this.newGame.notes,
|
||||
coverImage: this.newGame.coverImage,
|
||||
dateStarted: this.newGame.dateStarted ? new Date(this.newGame.dateStarted) : undefined,
|
||||
dateFinished: this.newGame.dateFinished ? new Date(this.newGame.dateFinished) : undefined,
|
||||
tags: this.newGame.tags || [],
|
||||
links: this.newGame.links || []
|
||||
};
|
||||
@@ -1444,6 +1509,8 @@ export class GamesListComponent implements OnInit {
|
||||
title: game.title,
|
||||
platform: game.platform,
|
||||
status: game.status,
|
||||
dateStarted: game.dateStarted,
|
||||
dateFinished: game.dateFinished,
|
||||
rating: game.rating,
|
||||
notes: game.notes,
|
||||
coverImage: game.coverImage,
|
||||
@@ -1472,7 +1539,13 @@ export class GamesListComponent implements OnInit {
|
||||
const game = this.editingGame();
|
||||
if (!game || !this.editGame.title || !this.editGame.status) return;
|
||||
|
||||
this.gamesService.updateGame(game.id, this.editGame).subscribe(() => {
|
||||
const updateData: UpdateGameDto = {
|
||||
...this.editGame,
|
||||
dateStarted: this.editGame.dateStarted ? new Date(this.editGame.dateStarted) : undefined,
|
||||
dateFinished: this.editGame.dateFinished ? new Date(this.editGame.dateFinished) : undefined
|
||||
};
|
||||
|
||||
this.gamesService.updateGame(game.id, updateData).subscribe(() => {
|
||||
this.loadGames();
|
||||
this.cancelEdit();
|
||||
});
|
||||
@@ -1669,7 +1742,7 @@ export class GamesListComponent implements OnInit {
|
||||
|
||||
try {
|
||||
await this.suggestionService.createSuggestion({
|
||||
entityType: SuggestionEntity.GAME,
|
||||
entityType: SuggestionEntity.game,
|
||||
title: this.suggestedGame.title,
|
||||
platform: this.suggestedGame.platform,
|
||||
notes: this.suggestedGame.notes,
|
||||
|
||||
@@ -71,6 +71,26 @@ import { Manga, MangaStatus, CreateMangaDto, UpdateMangaDto, Comment, Suggestion
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="dateStarted">Date Started</label>
|
||||
<input
|
||||
type="date"
|
||||
id="dateStarted"
|
||||
[(ngModel)]="newManga.dateStarted"
|
||||
name="dateStarted"
|
||||
>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="dateFinished">Date Finished</label>
|
||||
<input
|
||||
type="date"
|
||||
id="dateFinished"
|
||||
[(ngModel)]="newManga.dateFinished"
|
||||
name="dateFinished"
|
||||
>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="rating">Rating (1-10)</label>
|
||||
<input
|
||||
@@ -201,6 +221,26 @@ import { Manga, MangaStatus, CreateMangaDto, UpdateMangaDto, Comment, Suggestion
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="edit-dateStarted">Date Started</label>
|
||||
<input
|
||||
type="date"
|
||||
id="edit-dateStarted"
|
||||
[(ngModel)]="editManga.dateStarted"
|
||||
name="dateStarted"
|
||||
>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="edit-dateFinished">Date Finished</label>
|
||||
<input
|
||||
type="date"
|
||||
id="edit-dateFinished"
|
||||
[(ngModel)]="editManga.dateFinished"
|
||||
name="dateFinished"
|
||||
>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="edit-rating">Rating (1-10)</label>
|
||||
<input
|
||||
@@ -501,6 +541,18 @@ import { Manga, MangaStatus, CreateMangaDto, UpdateMangaDto, Comment, Suggestion
|
||||
</div>
|
||||
}
|
||||
|
||||
@if (manga.dateStarted) {
|
||||
<p class="date-started">
|
||||
Started: {{ formatDate(manga.dateStarted) }}
|
||||
</p>
|
||||
}
|
||||
|
||||
@if (manga.dateFinished) {
|
||||
<p class="date-finished">
|
||||
Finished: {{ formatDate(manga.dateFinished) }}
|
||||
</p>
|
||||
}
|
||||
|
||||
@if (authService.isAdmin()) {
|
||||
<div class="actions">
|
||||
<button (click)="startEdit(manga)" class="btn btn-secondary btn-sm">
|
||||
@@ -857,6 +909,13 @@ import { Manga, MangaStatus, CreateMangaDto, UpdateMangaDto, Comment, Suggestion
|
||||
margin: 0.5rem 0;
|
||||
}
|
||||
|
||||
.date-started,
|
||||
.date-finished {
|
||||
font-size: 0.85rem;
|
||||
color: #4b5563;
|
||||
margin-top: 0.5rem;
|
||||
}
|
||||
|
||||
.actions {
|
||||
margin-top: 1rem;
|
||||
}
|
||||
@@ -1260,10 +1319,12 @@ export class MangaListComponent implements OnInit {
|
||||
|
||||
totalFilteredManga = computed(() => this.filteredManga().length);
|
||||
|
||||
newManga: Partial<CreateMangaDto> = {
|
||||
newManga: Partial<CreateMangaDto> & { dateStarted?: Date; dateFinished?: Date } = {
|
||||
title: '',
|
||||
author: '',
|
||||
status: MangaStatus.wantToRead,
|
||||
dateStarted: undefined,
|
||||
dateFinished: undefined,
|
||||
rating: undefined,
|
||||
notes: '',
|
||||
tags: [],
|
||||
@@ -1355,6 +1416,8 @@ export class MangaListComponent implements OnInit {
|
||||
title: '',
|
||||
author: '',
|
||||
status: MangaStatus.wantToRead,
|
||||
dateStarted: undefined,
|
||||
dateFinished: undefined,
|
||||
rating: undefined,
|
||||
notes: '',
|
||||
coverImage: undefined,
|
||||
@@ -1420,6 +1483,8 @@ export class MangaListComponent implements OnInit {
|
||||
title: this.newManga.title,
|
||||
author: this.newManga.author,
|
||||
status: this.newManga.status,
|
||||
dateStarted: this.newManga.dateStarted ? new Date(this.newManga.dateStarted) : undefined,
|
||||
dateFinished: this.newManga.dateFinished ? new Date(this.newManga.dateFinished) : undefined,
|
||||
rating: this.newManga.rating,
|
||||
notes: this.newManga.notes,
|
||||
coverImage: this.newManga.coverImage,
|
||||
@@ -1447,6 +1512,8 @@ export class MangaListComponent implements OnInit {
|
||||
title: manga.title,
|
||||
author: manga.author,
|
||||
status: manga.status,
|
||||
dateStarted: manga.dateStarted,
|
||||
dateFinished: manga.dateFinished,
|
||||
rating: manga.rating,
|
||||
notes: manga.notes,
|
||||
coverImage: manga.coverImage,
|
||||
@@ -1475,7 +1542,13 @@ export class MangaListComponent implements OnInit {
|
||||
const manga = this.editingManga();
|
||||
if (!manga || !this.editManga.title || !this.editManga.author || !this.editManga.status) return;
|
||||
|
||||
this.mangaService.updateManga(manga.id, this.editManga).subscribe(() => {
|
||||
const updateData = {
|
||||
...this.editManga,
|
||||
dateStarted: this.editManga.dateStarted ? new Date(this.editManga.dateStarted) : undefined,
|
||||
dateFinished: this.editManga.dateFinished ? new Date(this.editManga.dateFinished) : undefined,
|
||||
};
|
||||
|
||||
this.mangaService.updateManga(manga.id, updateData).subscribe(() => {
|
||||
this.loadManga();
|
||||
this.cancelEdit();
|
||||
});
|
||||
@@ -1670,7 +1743,7 @@ export class MangaListComponent implements OnInit {
|
||||
|
||||
try {
|
||||
await this.suggestionService.createSuggestion({
|
||||
entityType: SuggestionEntity.MANGA,
|
||||
entityType: SuggestionEntity.manga,
|
||||
title: this.suggestedManga.title,
|
||||
author: this.suggestedManga.author,
|
||||
notes: this.suggestedManga.notes,
|
||||
|
||||
@@ -80,6 +80,26 @@ import { Music, MusicStatus, MusicType, CreateMusicDto, UpdateMusicDto, Comment,
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="dateStarted">Date Started</label>
|
||||
<input
|
||||
type="date"
|
||||
id="dateStarted"
|
||||
[(ngModel)]="newMusic.dateStarted"
|
||||
name="dateStarted"
|
||||
>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="dateFinished">Date Finished</label>
|
||||
<input
|
||||
type="date"
|
||||
id="dateFinished"
|
||||
[(ngModel)]="newMusic.dateFinished"
|
||||
name="dateFinished"
|
||||
>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="rating">Rating (1-10)</label>
|
||||
<input
|
||||
@@ -219,6 +239,26 @@ import { Music, MusicStatus, MusicType, CreateMusicDto, UpdateMusicDto, Comment,
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="edit-dateStarted">Date Started</label>
|
||||
<input
|
||||
type="date"
|
||||
id="edit-dateStarted"
|
||||
[(ngModel)]="editMusicData.dateStarted"
|
||||
name="dateStarted"
|
||||
>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="edit-dateFinished">Date Finished</label>
|
||||
<input
|
||||
type="date"
|
||||
id="edit-dateFinished"
|
||||
[(ngModel)]="editMusicData.dateFinished"
|
||||
name="dateFinished"
|
||||
>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="edit-rating">Rating (1-10)</label>
|
||||
<input
|
||||
@@ -577,9 +617,15 @@ import { Music, MusicStatus, MusicType, CreateMusicDto, UpdateMusicDto, Comment,
|
||||
</div>
|
||||
}
|
||||
|
||||
@if (music.dateCompleted) {
|
||||
<p class="date-completed">
|
||||
Completed: {{ formatDate(music.dateCompleted) }}
|
||||
@if (music.dateStarted) {
|
||||
<p class="date-started">
|
||||
Started: {{ formatDate(music.dateStarted) }}
|
||||
</p>
|
||||
}
|
||||
|
||||
@if (music.dateFinished) {
|
||||
<p class="date-finished">
|
||||
Finished: {{ formatDate(music.dateFinished) }}
|
||||
</p>
|
||||
}
|
||||
|
||||
@@ -1013,7 +1059,8 @@ import { Music, MusicStatus, MusicType, CreateMusicDto, UpdateMusicDto, Comment,
|
||||
margin: 0.5rem 0;
|
||||
}
|
||||
|
||||
.date-completed {
|
||||
.date-started,
|
||||
.date-finished {
|
||||
font-size: 0.85rem;
|
||||
color: var(--witch-plum);
|
||||
margin-top: 0.5rem;
|
||||
@@ -1484,11 +1531,13 @@ export class MusicListComponent implements OnInit {
|
||||
|
||||
totalFilteredMusic = computed(() => this.filteredMusic().length);
|
||||
|
||||
newMusic: Partial<CreateMusicDto> = {
|
||||
newMusic: Partial<CreateMusicDto> & { dateStarted?: Date; dateFinished?: Date } = {
|
||||
title: '',
|
||||
artist: '',
|
||||
type: MusicType.album,
|
||||
status: MusicStatus.wantToListen,
|
||||
dateStarted: undefined,
|
||||
dateFinished: undefined,
|
||||
rating: undefined,
|
||||
notes: '',
|
||||
tags: [],
|
||||
@@ -1595,6 +1644,8 @@ export class MusicListComponent implements OnInit {
|
||||
artist: '',
|
||||
type: MusicType.album,
|
||||
status: MusicStatus.wantToListen,
|
||||
dateStarted: undefined,
|
||||
dateFinished: undefined,
|
||||
rating: undefined,
|
||||
notes: '',
|
||||
coverArt: undefined,
|
||||
@@ -1661,6 +1712,8 @@ export class MusicListComponent implements OnInit {
|
||||
artist: this.newMusic.artist,
|
||||
type: this.newMusic.type,
|
||||
status: this.newMusic.status,
|
||||
dateStarted: this.newMusic.dateStarted ? new Date(this.newMusic.dateStarted) : undefined,
|
||||
dateFinished: this.newMusic.dateFinished ? new Date(this.newMusic.dateFinished) : undefined,
|
||||
rating: this.newMusic.rating,
|
||||
notes: this.newMusic.notes,
|
||||
coverArt: this.newMusic.coverArt,
|
||||
@@ -1689,6 +1742,8 @@ export class MusicListComponent implements OnInit {
|
||||
artist: music.artist,
|
||||
type: music.type,
|
||||
status: music.status,
|
||||
dateStarted: music.dateStarted,
|
||||
dateFinished: music.dateFinished,
|
||||
rating: music.rating,
|
||||
notes: music.notes,
|
||||
coverArt: music.coverArt,
|
||||
@@ -1717,7 +1772,13 @@ export class MusicListComponent implements OnInit {
|
||||
const music = this.editingMusic();
|
||||
if (!music || !this.editMusicData.title || !this.editMusicData.artist || !this.editMusicData.type || !this.editMusicData.status) return;
|
||||
|
||||
this.musicService.updateMusic(music.id, this.editMusicData).subscribe(() => {
|
||||
const updateData = {
|
||||
...this.editMusicData,
|
||||
dateStarted: this.editMusicData.dateStarted ? new Date(this.editMusicData.dateStarted) : undefined,
|
||||
dateFinished: this.editMusicData.dateFinished ? new Date(this.editMusicData.dateFinished) : undefined,
|
||||
};
|
||||
|
||||
this.musicService.updateMusic(music.id, updateData).subscribe(() => {
|
||||
this.loadMusic();
|
||||
this.cancelEdit();
|
||||
});
|
||||
@@ -1915,7 +1976,7 @@ export class MusicListComponent implements OnInit {
|
||||
|
||||
try {
|
||||
await this.suggestionService.createSuggestion({
|
||||
entityType: SuggestionEntity.MUSIC,
|
||||
entityType: SuggestionEntity.music,
|
||||
title: this.suggestedMusic.title,
|
||||
artist: this.suggestedMusic.artist,
|
||||
type: this.suggestedMusic.type,
|
||||
|
||||
@@ -373,12 +373,12 @@ export class MyLikesComponent implements OnInit {
|
||||
}
|
||||
|
||||
getItemTitle(likedItem: LikedItemDto): string {
|
||||
const item = likedItem.item;
|
||||
const item = likedItem.item as any;
|
||||
return item.title || item.name || 'Untitled';
|
||||
}
|
||||
|
||||
getItemSubtitle(likedItem: LikedItemDto): string {
|
||||
const item = likedItem.item;
|
||||
const item = likedItem.item as any;
|
||||
const type = likedItem.like.entityType;
|
||||
|
||||
switch (type) {
|
||||
@@ -400,7 +400,7 @@ export class MyLikesComponent implements OnInit {
|
||||
}
|
||||
|
||||
getItemImage(likedItem: LikedItemDto): string | null {
|
||||
const item = likedItem.item;
|
||||
const item = likedItem.item as any;
|
||||
return item.coverImage || item.imageUrl || null;
|
||||
}
|
||||
|
||||
|
||||
@@ -43,22 +43,22 @@ import { Suggestion, SuggestionStatus, SuggestionEntity } from '@library/shared-
|
||||
All ({{ suggestions().length }})
|
||||
</button>
|
||||
<button
|
||||
(click)="setFilter(SuggestionStatus.UNREVIEWED)"
|
||||
[class.active]="statusFilter() === SuggestionStatus.UNREVIEWED"
|
||||
(click)="setFilter(SuggestionStatus.unreviewed)"
|
||||
[class.active]="statusFilter() === SuggestionStatus.unreviewed"
|
||||
class="filter-btn pending"
|
||||
>
|
||||
Pending ({{ unreviewedCount() }})
|
||||
</button>
|
||||
<button
|
||||
(click)="setFilter(SuggestionStatus.ACCEPTED)"
|
||||
[class.active]="statusFilter() === SuggestionStatus.ACCEPTED"
|
||||
(click)="setFilter(SuggestionStatus.accepted)"
|
||||
[class.active]="statusFilter() === SuggestionStatus.accepted"
|
||||
class="filter-btn accepted"
|
||||
>
|
||||
Accepted ({{ acceptedCount() }})
|
||||
</button>
|
||||
<button
|
||||
(click)="setFilter(SuggestionStatus.DECLINED)"
|
||||
[class.active]="statusFilter() === SuggestionStatus.DECLINED"
|
||||
(click)="setFilter(SuggestionStatus.declined)"
|
||||
[class.active]="statusFilter() === SuggestionStatus.declined"
|
||||
class="filter-btn declined"
|
||||
>
|
||||
Declined ({{ declinedCount() }})
|
||||
@@ -120,7 +120,7 @@ import { Suggestion, SuggestionStatus, SuggestionEntity } from '@library/shared-
|
||||
}
|
||||
</div>
|
||||
|
||||
@if (suggestion.status === SuggestionStatus.DECLINED && suggestion.declineReason) {
|
||||
@if (suggestion.status === SuggestionStatus.declined && suggestion.declineReason) {
|
||||
<div class="decline-reason">
|
||||
<strong>Reason:</strong> {{ suggestion.declineReason }}
|
||||
</div>
|
||||
@@ -337,9 +337,9 @@ export class MySuggestionsComponent implements OnInit {
|
||||
|
||||
SuggestionStatus = SuggestionStatus;
|
||||
|
||||
unreviewedCount = () => this.suggestions().filter(s => s.status === SuggestionStatus.UNREVIEWED).length;
|
||||
acceptedCount = () => this.suggestions().filter(s => s.status === SuggestionStatus.ACCEPTED).length;
|
||||
declinedCount = () => this.suggestions().filter(s => s.status === SuggestionStatus.DECLINED).length;
|
||||
unreviewedCount = () => this.suggestions().filter(s => s.status === SuggestionStatus.unreviewed).length;
|
||||
acceptedCount = () => this.suggestions().filter(s => s.status === SuggestionStatus.accepted).length;
|
||||
declinedCount = () => this.suggestions().filter(s => s.status === SuggestionStatus.declined).length;
|
||||
|
||||
filteredSuggestions = computed(() => {
|
||||
const filter = this.statusFilter();
|
||||
@@ -397,20 +397,20 @@ export class MySuggestionsComponent implements OnInit {
|
||||
|
||||
getStatusLabel(status: SuggestionStatus): string {
|
||||
switch (status) {
|
||||
case SuggestionStatus.UNREVIEWED: return 'Pending Review';
|
||||
case SuggestionStatus.ACCEPTED: return 'Accepted';
|
||||
case SuggestionStatus.DECLINED: return 'Declined';
|
||||
case SuggestionStatus.unreviewed: return 'Pending Review';
|
||||
case SuggestionStatus.accepted: return 'Accepted';
|
||||
case SuggestionStatus.declined: return 'Declined';
|
||||
}
|
||||
}
|
||||
|
||||
getEntityIcon(entityType: SuggestionEntity): string {
|
||||
switch (entityType) {
|
||||
case SuggestionEntity.GAME: return '🎮';
|
||||
case SuggestionEntity.BOOK: return '📚';
|
||||
case SuggestionEntity.MUSIC: return '🎵';
|
||||
case SuggestionEntity.MANGA: return '📖';
|
||||
case SuggestionEntity.SHOW: return '📺';
|
||||
case SuggestionEntity.ART: return '🎨';
|
||||
case SuggestionEntity.game: return '🎮';
|
||||
case SuggestionEntity.book: return '📚';
|
||||
case SuggestionEntity.music: return '🎵';
|
||||
case SuggestionEntity.manga: return '📖';
|
||||
case SuggestionEntity.show: return '📺';
|
||||
case SuggestionEntity.art: return '🎨';
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -69,6 +69,26 @@ import { Show, ShowStatus, ShowType, CreateShowDto, UpdateShowDto, Comment, Sugg
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="dateStarted">Date Started</label>
|
||||
<input
|
||||
type="date"
|
||||
id="dateStarted"
|
||||
[(ngModel)]="newShow.dateStarted"
|
||||
name="dateStarted"
|
||||
>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="dateFinished">Date Finished</label>
|
||||
<input
|
||||
type="date"
|
||||
id="dateFinished"
|
||||
[(ngModel)]="newShow.dateFinished"
|
||||
name="dateFinished"
|
||||
>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="rating">Rating (1-10)</label>
|
||||
<input
|
||||
@@ -197,6 +217,26 @@ import { Show, ShowStatus, ShowType, CreateShowDto, UpdateShowDto, Comment, Sugg
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="edit-dateStarted">Date Started</label>
|
||||
<input
|
||||
type="date"
|
||||
id="edit-dateStarted"
|
||||
[(ngModel)]="editShow.dateStarted"
|
||||
name="dateStarted"
|
||||
>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="edit-dateFinished">Date Finished</label>
|
||||
<input
|
||||
type="date"
|
||||
id="edit-dateFinished"
|
||||
[(ngModel)]="editShow.dateFinished"
|
||||
name="dateFinished"
|
||||
>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="edit-rating">Rating (1-10)</label>
|
||||
<input
|
||||
@@ -495,6 +535,18 @@ import { Show, ShowStatus, ShowType, CreateShowDto, UpdateShowDto, Comment, Sugg
|
||||
</div>
|
||||
}
|
||||
|
||||
@if (show.dateStarted) {
|
||||
<p class="date-started">
|
||||
Started: {{ formatDate(show.dateStarted) }}
|
||||
</p>
|
||||
}
|
||||
|
||||
@if (show.dateFinished) {
|
||||
<p class="date-finished">
|
||||
Finished: {{ formatDate(show.dateFinished) }}
|
||||
</p>
|
||||
}
|
||||
|
||||
@if (authService.isAdmin()) {
|
||||
<div class="actions">
|
||||
<button (click)="startEdit(show)" class="btn btn-secondary btn-sm">
|
||||
@@ -850,6 +902,13 @@ import { Show, ShowStatus, ShowType, CreateShowDto, UpdateShowDto, Comment, Sugg
|
||||
margin: 0.5rem 0;
|
||||
}
|
||||
|
||||
.date-started,
|
||||
.date-finished {
|
||||
font-size: 0.85rem;
|
||||
color: #4b5563;
|
||||
margin-top: 0.5rem;
|
||||
}
|
||||
|
||||
.actions {
|
||||
margin-top: 1rem;
|
||||
}
|
||||
@@ -1254,12 +1313,14 @@ export class ShowsListComponent implements OnInit {
|
||||
|
||||
totalFilteredShows = computed(() => this.filteredShows().length);
|
||||
|
||||
newShow: Partial<CreateShowDto> = {
|
||||
newShow: Partial<CreateShowDto> & { dateStarted?: Date; dateFinished?: Date } = {
|
||||
title: '',
|
||||
type: ShowType.tvSeries,
|
||||
status: ShowStatus.wantToWatch,
|
||||
rating: undefined,
|
||||
notes: '',
|
||||
dateStarted: undefined,
|
||||
dateFinished: undefined,
|
||||
tags: [],
|
||||
links: []
|
||||
};
|
||||
@@ -1361,6 +1422,8 @@ export class ShowsListComponent implements OnInit {
|
||||
rating: undefined,
|
||||
notes: '',
|
||||
coverImage: undefined,
|
||||
dateStarted: undefined,
|
||||
dateFinished: undefined,
|
||||
tags: [],
|
||||
links: []
|
||||
};
|
||||
@@ -1423,6 +1486,8 @@ export class ShowsListComponent implements OnInit {
|
||||
title: this.newShow.title,
|
||||
type: this.newShow.type,
|
||||
status: this.newShow.status,
|
||||
dateStarted: this.newShow.dateStarted ? new Date(this.newShow.dateStarted) : undefined,
|
||||
dateFinished: this.newShow.dateFinished ? new Date(this.newShow.dateFinished) : undefined,
|
||||
rating: this.newShow.rating,
|
||||
notes: this.newShow.notes,
|
||||
coverImage: this.newShow.coverImage,
|
||||
@@ -1450,6 +1515,8 @@ export class ShowsListComponent implements OnInit {
|
||||
title: show.title,
|
||||
type: show.type,
|
||||
status: show.status,
|
||||
dateStarted: show.dateStarted,
|
||||
dateFinished: show.dateFinished,
|
||||
rating: show.rating,
|
||||
notes: show.notes,
|
||||
coverImage: show.coverImage,
|
||||
@@ -1478,7 +1545,13 @@ export class ShowsListComponent implements OnInit {
|
||||
const show = this.editingShow();
|
||||
if (!show || !this.editShow.title || !this.editShow.type || !this.editShow.status) return;
|
||||
|
||||
this.showsService.updateShow(show.id, this.editShow).subscribe(() => {
|
||||
const updateData = {
|
||||
...this.editShow,
|
||||
dateStarted: this.editShow.dateStarted ? new Date(this.editShow.dateStarted) : undefined,
|
||||
dateFinished: this.editShow.dateFinished ? new Date(this.editShow.dateFinished) : undefined,
|
||||
};
|
||||
|
||||
this.showsService.updateShow(show.id, updateData).subscribe(() => {
|
||||
this.loadShows();
|
||||
this.cancelEdit();
|
||||
});
|
||||
@@ -1673,7 +1746,7 @@ export class ShowsListComponent implements OnInit {
|
||||
|
||||
try {
|
||||
await this.suggestionService.createSuggestion({
|
||||
entityType: SuggestionEntity.SHOW,
|
||||
entityType: SuggestionEntity.show,
|
||||
title: this.suggestedShow.title,
|
||||
type: this.suggestedShow.type,
|
||||
notes: this.suggestedShow.notes,
|
||||
|
||||
Reference in New Issue
Block a user