Files
library/apps/frontend/src/app/components/home/home.component.ts
T

450 lines
12 KiB
TypeScript

/**
* @copyright 2026 NHCarrigan
* @license Naomi's Public License
* @author Naomi Carrigan
*/
import { Component, OnInit, inject, signal } from '@angular/core';
import { CommonModule } from '@angular/common';
import { RouterModule } from '@angular/router';
import { GamesService } from '../../services/games.service';
import { BooksService } from '../../services/books.service';
import { MusicService } from '../../services/music.service';
import { MangaService } from '../../services/manga.service';
import { ShowsService } from '../../services/shows.service';
import { ArtService } from '../../services/art.service';
import { Game, GameStatus, Book, BookStatus, Music, MusicType, Manga, MangaStatus, Show, ShowStatus, ShowType, Art } from '@library/shared-types';
@Component({
selector: 'app-home',
standalone: true,
imports: [CommonModule, RouterModule],
template: `
<div class="container">
<div class="hero">
<h1>Welcome to Naomi's Library</h1>
<p class="tagline">A personal collection of games, books, music, manga, shows, and art</p>
</div>
<div class="stats-grid">
<a routerLink="/games" class="stat-card games-card">
<div class="icon">🎮</div>
<div class="stat-info">
<h3>Games</h3>
<div class="count">{{ gamesCount() }}</div>
<p>{{ currentlyPlayingCount() }} currently playing</p>
</div>
</a>
<a routerLink="/books" class="stat-card books-card">
<div class="icon">📚</div>
<div class="stat-info">
<h3>Books</h3>
<div class="count">{{ booksCount() }}</div>
<p>{{ currentlyReadingCount() }} currently reading</p>
</div>
</a>
<a routerLink="/music" class="stat-card music-card">
<div class="icon">🎵</div>
<div class="stat-info">
<h3>Music</h3>
<div class="count">{{ musicCount() }}</div>
<p>{{ albumsCount() }} albums, {{ singlesCount() }} singles</p>
</div>
</a>
<a routerLink="/manga" class="stat-card manga-card">
<div class="icon">📖</div>
<div class="stat-info">
<h3>Manga</h3>
<div class="count">{{ mangaCount() }}</div>
<p>{{ currentlyReadingMangaCount() }} currently reading</p>
</div>
</a>
<a routerLink="/shows" class="stat-card shows-card">
<div class="icon">📺</div>
<div class="stat-info">
<h3>Shows</h3>
<div class="count">{{ showsCount() }}</div>
<p>{{ animeCount() }} anime, {{ filmsCount() }} films</p>
</div>
</a>
<a routerLink="/art" class="stat-card art-card">
<div class="icon">🎨</div>
<div class="stat-info">
<h3>Art</h3>
<div class="count">{{ artCount() }}</div>
<p>commissioned pieces</p>
</div>
</a>
</div>
<div class="recent-section">
<h2>Recent Additions</h2>
<div class="recent-grid">
@if (recentGames().length > 0) {
<div class="recent-category">
<h3>🎮 Latest Games</h3>
<ul class="recent-list">
@for (game of recentGames(); track game.id) {
<li>
<a routerLink="/games">{{ game.title }}</a>
@if (game.platform) {
<span class="platform">({{ game.platform }})</span>
}
</li>
}
</ul>
</div>
}
@if (recentBooks().length > 0) {
<div class="recent-category">
<h3>📚 Latest Books</h3>
<ul class="recent-list">
@for (book of recentBooks(); track book.id) {
<li>
<a routerLink="/books">{{ book.title }}</a>
<span class="author">by {{ book.author }}</span>
</li>
}
</ul>
</div>
}
@if (recentMusic().length > 0) {
<div class="recent-category">
<h3>🎵 Latest Music</h3>
<ul class="recent-list">
@for (music of recentMusic(); track music.id) {
<li>
<a routerLink="/music">{{ music.title }}</a>
<span class="artist">by {{ music.artist }}</span>
</li>
}
</ul>
</div>
}
@if (recentManga().length > 0) {
<div class="recent-category">
<h3>📖 Latest Manga</h3>
<ul class="recent-list">
@for (manga of recentManga(); track manga.id) {
<li>
<a routerLink="/manga">{{ manga.title }}</a>
<span class="author">by {{ manga.author }}</span>
</li>
}
</ul>
</div>
}
@if (recentShows().length > 0) {
<div class="recent-category">
<h3>📺 Latest Shows</h3>
<ul class="recent-list">
@for (show of recentShows(); track show.id) {
<li>
<a routerLink="/shows">{{ show.title }}</a>
<span class="show-type">{{ formatShowType(show.type) }}</span>
</li>
}
</ul>
</div>
}
@if (recentArt().length > 0) {
<div class="recent-category">
<h3>🎨 Latest Art</h3>
<ul class="recent-list">
@for (art of recentArt(); track art.id) {
<li>
<a routerLink="/art">{{ art.title }}</a>
<span class="artist">by {{ art.artist }}</span>
</li>
}
</ul>
</div>
}
</div>
</div>
</div>
`,
styles: [`
.container {
max-width: 1200px;
margin: 0 auto;
padding: 2rem;
}
.hero {
text-align: center;
padding: 3rem 0;
margin-bottom: 3rem;
}
.hero h1 {
font-size: 2.5rem;
margin-bottom: 0.5rem;
color: var(--witch-purple);
}
.tagline {
font-size: 1.2rem;
color: var(--witch-plum);
}
.stats-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
gap: 1.5rem;
margin-bottom: 3rem;
}
.stat-card {
display: flex;
align-items: center;
gap: 1.5rem;
padding: 1.5rem;
background: rgba(255, 255, 255, 0.95);
border: 2px solid var(--witch-mauve);
border-radius: 12px;
text-decoration: none;
color: inherit;
transition: all 0.3s;
backdrop-filter: blur(10px);
}
.stat-card:hover {
transform: translateY(-4px);
box-shadow: 0 8px 16px var(--witch-shadow);
background: var(--witch-moon);
}
.games-card:hover { border-color: #ff6b6b; }
.books-card:hover { border-color: #8b6f47; }
.music-card:hover { border-color: #74b9ff; }
.manga-card:hover { border-color: #00b894; }
.shows-card:hover { border-color: #e84393; }
.art-card:hover { border-color: #fdcb6e; }
.icon {
font-size: 3rem;
flex-shrink: 0;
}
.stat-info h3 {
margin: 0 0 0.5rem 0;
font-size: 1.1rem;
color: var(--witch-plum);
}
.count {
font-size: 2.5rem;
font-weight: bold;
line-height: 1;
margin-bottom: 0.5rem;
}
.games-card .count { color: #ff6b6b; }
.books-card .count { color: #8b6f47; }
.music-card .count { color: #74b9ff; }
.manga-card .count { color: #00b894; }
.shows-card .count { color: #e84393; }
.art-card .count { color: #fdcb6e; }
.stat-info p {
margin: 0;
color: var(--witch-plum);
font-size: 0.9rem;
}
.recent-section {
background: rgba(255, 255, 255, 0.95);
padding: 2rem;
border-radius: 12px;
border: 2px solid var(--witch-lavender);
backdrop-filter: blur(10px);
}
.recent-section h2 {
margin: 0 0 1.5rem 0;
font-size: 1.5rem;
}
.recent-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: 2rem;
}
.recent-category h3 {
margin: 0 0 1rem 0;
font-size: 1.1rem;
color: var(--witch-purple);
}
.recent-list {
list-style: none;
padding: 0;
margin: 0;
}
.recent-list li {
padding: 0.5rem 0;
border-bottom: 1px solid var(--witch-lavender);
}
.recent-list li:last-child {
border-bottom: none;
}
.recent-list a {
color: var(--witch-rose);
text-decoration: none;
font-weight: 500;
}
.recent-list a:hover {
color: var(--witch-plum);
text-decoration: underline;
}
.platform,
.author,
.artist,
.show-type {
display: block;
font-size: 0.85rem;
color: var(--witch-plum);
margin-top: 0.25rem;
}
`]
})
export class HomeComponent implements OnInit {
gamesService = inject(GamesService);
booksService = inject(BooksService);
musicService = inject(MusicService);
mangaService = inject(MangaService);
showsService = inject(ShowsService);
artService = inject(ArtService);
games = signal<Game[]>([]);
books = signal<Book[]>([]);
music = signal<Music[]>([]);
manga = signal<Manga[]>([]);
shows = signal<Show[]>([]);
art = signal<Art[]>([]);
ngOnInit() {
// Load all data
this.gamesService.getAllGames().subscribe(games => this.games.set(games));
this.booksService.getAllBooks().subscribe(books => this.books.set(books));
this.musicService.getAllMusic().subscribe(music => this.music.set(music));
this.mangaService.getAllManga().subscribe(manga => this.manga.set(manga));
this.showsService.getAllShows().subscribe(shows => this.shows.set(shows));
this.artService.getAllArt().subscribe(art => this.art.set(art));
}
// Games stats
gamesCount() {
return this.games().length;
}
currentlyPlayingCount() {
return this.games().filter(g => g.status === GameStatus.playing).length;
}
recentGames() {
return this.games().slice(0, 5);
}
// Books stats
booksCount() {
return this.books().length;
}
currentlyReadingCount() {
return this.books().filter(b => b.status === BookStatus.reading).length;
}
recentBooks() {
return this.books().slice(0, 5);
}
// Music stats
musicCount() {
return this.music().length;
}
albumsCount() {
return this.music().filter(m => m.type === MusicType.album).length;
}
singlesCount() {
return this.music().filter(m => m.type === MusicType.single).length;
}
recentMusic() {
return this.music().slice(0, 5);
}
// Manga stats
mangaCount() {
return this.manga().length;
}
currentlyReadingMangaCount() {
return this.manga().filter(m => m.status === MangaStatus.reading).length;
}
recentManga() {
return this.manga().slice(0, 5);
}
// Shows stats
showsCount() {
return this.shows().length;
}
animeCount() {
return this.shows().filter(s => s.type === ShowType.anime).length;
}
filmsCount() {
return this.shows().filter(s => s.type === ShowType.film).length;
}
recentShows() {
return this.shows().slice(0, 5);
}
formatShowType(type: ShowType): string {
switch (type) {
case ShowType.tvSeries:
return 'TV Series';
case ShowType.anime:
return 'Anime';
case ShowType.film:
return 'Film';
case ShowType.documentary:
return 'Documentary';
default:
return type;
}
}
// Art stats
artCount() {
return this.art().length;
}
recentArt() {
return this.art().slice(0, 5);
}
}