/** * @copyright 2026 NHCarrigan * @license Naomi's Public License * @author Naomi Carrigan */ import { Component, OnInit, inject, signal, computed } from '@angular/core'; import { CommonModule } from '@angular/common'; import { SuggestionService } from '../../services/suggestion.service'; import { AuthService } from '../../services/auth.service'; import { PaginationComponent } from '../shared/pagination.component'; import { Suggestion, SuggestionStatus, SuggestionEntity } from '@library/shared-types'; @Component({ selector: 'app-my-suggestions', standalone: true, imports: [CommonModule, PaginationComponent], template: `

My Suggestions

Track the status of items you've suggested to Naomi

@if (!authService.isAuthenticated()) {

Please log in to view your suggestions.

} @else if (loading()) {
Loading your suggestions...
} @else if (suggestions().length === 0) {

You haven't made any suggestions yet!

Visit any collection page and click "Suggest a..." to recommend something to Naomi.

} @else {
@for (suggestion of paginatedSuggestions(); track suggestion.id) {
{{ getEntityIcon(suggestion.entityType) }} {{ suggestion.entityType }} {{ getStatusLabel(suggestion.status) }}

{{ suggestion.title }}

@if (suggestion.gameData) { @if (suggestion.gameData.platform) {

Platform: {{ suggestion.gameData.platform }}

} } @if (suggestion.bookData) { @if (suggestion.bookData.author) {

Author: {{ suggestion.bookData.author }}

} } @if (suggestion.musicData) { @if (suggestion.musicData.artist) {

Artist: {{ suggestion.musicData.artist }}

} } @if (suggestion.artData) { @if (suggestion.artData.artist) {

Artist: {{ suggestion.artData.artist }}

} } @if (suggestion.showData) { @if (suggestion.showData.type) {

Type: {{ suggestion.showData.type }}

} } @if (suggestion.mangaData) { @if (suggestion.mangaData.author) {

Author: {{ suggestion.mangaData.author }}

} }
@if (suggestion.status === SuggestionStatus.DECLINED && suggestion.declineReason) {
Reason: {{ suggestion.declineReason }}
}
}
}
`, styles: [` .container { max-width: 900px; margin: 0 auto; padding: 2rem 1rem; } .header-section { margin-bottom: 2rem; } .header-section h2 { margin: 0 0 0.5rem 0; } .subtitle { color: #666; margin: 0; } .not-authenticated, .loading, .empty-state { text-align: center; padding: 3rem; color: #666; background: #f8f9fa; border-radius: 8px; } .hint { font-size: 0.9rem; color: #888; } .filters { display: flex; gap: 0.75rem; margin-bottom: 2rem; flex-wrap: wrap; } .filter-btn { padding: 0.5rem 1rem; background: #e5e7eb; border: none; border-radius: 4px; cursor: pointer; transition: all 0.3s; } .filter-btn:hover { background: #d1d5db; } .filter-btn.active { color: white; } .filter-btn.active, .filter-btn.active.pending { background: #f59e0b; } .filter-btn.active.accepted { background: #10b981; } .filter-btn.active.declined { background: #ef4444; } .suggestions-list { display: flex; flex-direction: column; gap: 1rem; } .suggestion-card { background: white; border: 1px solid #e5e7eb; border-radius: 8px; padding: 1.25rem; transition: box-shadow 0.3s; } .suggestion-card:hover { box-shadow: 0 4px 12px rgba(0,0,0,0.1); } .suggestion-card.status-accepted { border-left: 4px solid #10b981; } .suggestion-card.status-declined { border-left: 4px solid #ef4444; } .suggestion-card.status-unreviewed { border-left: 4px solid #f59e0b; } .suggestion-header { display: flex; gap: 0.75rem; margin-bottom: 0.75rem; } .entity-badge, .status-badge { padding: 0.25rem 0.5rem; border-radius: 4px; font-size: 0.75rem; font-weight: 600; text-transform: uppercase; } .entity-badge { background: #e5e7eb; color: #374151; } .entity-badge.entity-game { background: #fff1f1; color: #dc2626; } .entity-badge.entity-book { background: #fdf4ed; color: #8b6f47; } .entity-badge.entity-music { background: #eff6ff; color: #2563eb; } .entity-badge.entity-manga { background: #ecfdf5; color: #059669; } .entity-badge.entity-show { background: #fdf2f8; color: #db2777; } .entity-badge.entity-art { background: #fefce8; color: #ca8a04; } .status-badge.status-unreviewed { background: #fef3c7; color: #92400e; } .status-badge.status-accepted { background: #d1fae5; color: #065f46; } .status-badge.status-declined { background: #fee2e2; color: #991b1b; } .suggestion-title { margin: 0 0 0.75rem 0; font-size: 1.1rem; } .suggestion-details { font-size: 0.9rem; color: #4b5563; } .suggestion-details p { margin: 0.25rem 0; } .decline-reason { background: #fee2e2; border: 1px solid #fecaca; border-radius: 4px; padding: 0.75rem; margin-top: 0.75rem; font-size: 0.9rem; color: #991b1b; } .suggestion-footer { margin-top: 0.75rem; padding-top: 0.75rem; border-top: 1px solid #e5e7eb; } .date { font-size: 0.8rem; color: #9ca3af; } `] }) export class MySuggestionsComponent implements OnInit { suggestionService = inject(SuggestionService); authService = inject(AuthService); suggestions = signal([]); loading = signal(true); statusFilter = signal<'all' | SuggestionStatus>('all'); // Pagination state currentPage = signal(1); pageSize = signal(25); 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; filteredSuggestions = computed(() => { const filter = this.statusFilter(); if (filter === 'all') { return this.suggestions(); } return this.suggestions().filter(s => s.status === filter); }); paginatedSuggestions = computed(() => { const suggestions = this.filteredSuggestions(); const start = (this.currentPage() - 1) * this.pageSize(); const end = start + this.pageSize(); return suggestions.slice(start, end); }); totalFilteredSuggestions = computed(() => this.filteredSuggestions().length); ngOnInit() { if (this.authService.isAuthenticated()) { this.loadSuggestions(); } else { this.loading.set(false); } } async loadSuggestions() { this.loading.set(true); try { const suggestions = await this.suggestionService.getMySuggestions(); this.suggestions.set(suggestions); } catch { // Handle error silently } finally { this.loading.set(false); } } setFilter(filter: 'all' | SuggestionStatus) { this.statusFilter.set(filter); this.currentPage.set(1); // Reset to first page when filter changes } onPageChange(page: number) { this.currentPage.set(page); } onPageSizeChange(pageSize: number) { this.pageSize.set(pageSize); // Calculate new current page to stay on approximately the same content const firstItemIndex = (this.currentPage() - 1) * this.pageSize(); const newPage = Math.floor(firstItemIndex / pageSize) + 1; this.currentPage.set(newPage); } getStatusLabel(status: SuggestionStatus): string { switch (status) { 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 '🎨'; } } formatDate(date: Date | string): string { return new Date(date).toLocaleDateString(); } }