generated from nhcarrigan/template
feat: Multiple Features, Accessibility, Security, and UX Improvements #59
@@ -34,6 +34,7 @@ model Game {
|
||||
links Link[]
|
||||
series String?
|
||||
seriesOrder Int? @db.Int
|
||||
timeSpent Int? @db.Int
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
comments Comment[]
|
||||
@@ -62,6 +63,7 @@ model Book {
|
||||
links Link[]
|
||||
series String?
|
||||
seriesOrder Int? @db.Int
|
||||
timeSpent Int? @db.Int
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
comments Comment[]
|
||||
@@ -89,6 +91,7 @@ model Music {
|
||||
coverArt String?
|
||||
tags String[]
|
||||
links Link[]
|
||||
timeSpent Int? @db.Int
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
comments Comment[]
|
||||
@@ -135,6 +138,7 @@ model Show {
|
||||
coverImage String?
|
||||
tags String[]
|
||||
links Link[]
|
||||
timeSpent Int? @db.Int
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
comments Comment[]
|
||||
@@ -168,6 +172,7 @@ model Manga {
|
||||
coverImage String?
|
||||
tags String[]
|
||||
links Link[]
|
||||
timeSpent Int? @db.Int
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
comments Comment[]
|
||||
|
||||
@@ -104,6 +104,34 @@ import { Game, GameStatus, CreateGameDto, UpdateGameDto, Comment, SuggestionEnti
|
||||
>
|
||||
</div>
|
||||
|
||||
<div class="form-row">
|
||||
<div class="form-group">
|
||||
<label for="timeHours">Time Spent (Hours)</label>
|
||||
<input
|
||||
type="number"
|
||||
id="timeHours"
|
||||
[(ngModel)]="newGameTimeHours"
|
||||
name="timeHours"
|
||||
min="0"
|
||||
placeholder="0"
|
||||
(ngModelChange)="updateNewGameTimeSpent()"
|
||||
>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="timeMinutes">Time Spent (Minutes)</label>
|
||||
<input
|
||||
type="number"
|
||||
id="timeMinutes"
|
||||
[(ngModel)]="newGameTimeMinutes"
|
||||
name="timeMinutes"
|
||||
min="0"
|
||||
max="59"
|
||||
placeholder="0"
|
||||
(ngModelChange)="updateNewGameTimeSpent()"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="notes">Notes</label>
|
||||
<textarea
|
||||
@@ -277,6 +305,34 @@ import { Game, GameStatus, CreateGameDto, UpdateGameDto, Comment, SuggestionEnti
|
||||
>
|
||||
</div>
|
||||
|
||||
<div class="form-row">
|
||||
<div class="form-group">
|
||||
<label for="edit-timeHours">Time Spent (Hours)</label>
|
||||
<input
|
||||
type="number"
|
||||
id="edit-timeHours"
|
||||
[(ngModel)]="editGameTimeHours"
|
||||
name="timeHours"
|
||||
min="0"
|
||||
placeholder="0"
|
||||
(ngModelChange)="updateEditGameTimeSpent()"
|
||||
>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="edit-timeMinutes">Time Spent (Minutes)</label>
|
||||
<input
|
||||
type="number"
|
||||
id="edit-timeMinutes"
|
||||
[(ngModel)]="editGameTimeMinutes"
|
||||
name="timeMinutes"
|
||||
min="0"
|
||||
max="59"
|
||||
placeholder="0"
|
||||
(ngModelChange)="updateEditGameTimeSpent()"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="edit-notes">Notes</label>
|
||||
<textarea
|
||||
@@ -574,6 +630,12 @@ import { Game, GameStatus, CreateGameDto, UpdateGameDto, Comment, SuggestionEnti
|
||||
</div>
|
||||
}
|
||||
|
||||
@if (game.timeSpent) {
|
||||
<p class="time-spent">
|
||||
⏱️ Time Played: {{ formatTimeSpent(game.timeSpent) }}
|
||||
</p>
|
||||
}
|
||||
|
||||
<app-like-button
|
||||
entityType="game"
|
||||
[entityId]="game.id"
|
||||
@@ -741,6 +803,13 @@ import { Game, GameStatus, CreateGameDto, UpdateGameDto, Comment, SuggestionEnti
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.form-row {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 1rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.form-actions {
|
||||
display: flex;
|
||||
gap: 1rem;
|
||||
@@ -942,6 +1011,13 @@ import { Game, GameStatus, CreateGameDto, UpdateGameDto, Comment, SuggestionEnti
|
||||
margin: 0.5rem 0;
|
||||
}
|
||||
|
||||
.time-spent {
|
||||
font-size: 0.9rem;
|
||||
color: #10b981;
|
||||
font-weight: 500;
|
||||
margin: 0.5rem 0;
|
||||
}
|
||||
|
||||
.date-started,
|
||||
.date-finished,
|
||||
.date-added,
|
||||
@@ -1369,6 +1445,12 @@ export class GamesListComponent implements OnInit {
|
||||
|
||||
editGame: Partial<UpdateGameDto> = {};
|
||||
|
||||
// Time tracking state
|
||||
newGameTimeHours = 0;
|
||||
newGameTimeMinutes = 0;
|
||||
editGameTimeHours = 0;
|
||||
editGameTimeMinutes = 0;
|
||||
|
||||
// Tags and links input state
|
||||
newTagInput = '';
|
||||
editTagInput = '';
|
||||
@@ -1461,6 +1543,8 @@ export class GamesListComponent implements OnInit {
|
||||
tags: [],
|
||||
links: []
|
||||
};
|
||||
this.newGameTimeHours = 0;
|
||||
this.newGameTimeMinutes = 0;
|
||||
this.newGameImagePreview.set(null);
|
||||
this.imageError.set(null);
|
||||
this.newTagInput = '';
|
||||
@@ -1468,6 +1552,16 @@ export class GamesListComponent implements OnInit {
|
||||
this.newLinkUrl = '';
|
||||
}
|
||||
|
||||
updateNewGameTimeSpent() {
|
||||
const totalMinutes = (this.newGameTimeHours * 60) + this.newGameTimeMinutes;
|
||||
this.newGame.timeSpent = totalMinutes > 0 ? totalMinutes : undefined;
|
||||
}
|
||||
|
||||
updateEditGameTimeSpent() {
|
||||
const totalMinutes = (this.editGameTimeHours * 60) + this.editGameTimeMinutes;
|
||||
this.editGame.timeSpent = totalMinutes > 0 ? totalMinutes : undefined;
|
||||
}
|
||||
|
||||
addTag(target: 'new' | 'edit') {
|
||||
const input = target === 'new' ? this.newTagInput.trim() : this.editTagInput.trim();
|
||||
if (!input) return;
|
||||
@@ -1557,8 +1651,17 @@ export class GamesListComponent implements OnInit {
|
||||
tags: [...(game.tags || [])],
|
||||
links: [...(game.links || [])],
|
||||
series: game.series,
|
||||
seriesOrder: game.seriesOrder
|
||||
seriesOrder: game.seriesOrder,
|
||||
timeSpent: game.timeSpent
|
||||
};
|
||||
// Populate time fields from existing timeSpent
|
||||
if (game.timeSpent) {
|
||||
this.editGameTimeHours = Math.floor(game.timeSpent / 60);
|
||||
this.editGameTimeMinutes = game.timeSpent % 60;
|
||||
} else {
|
||||
this.editGameTimeHours = 0;
|
||||
this.editGameTimeMinutes = 0;
|
||||
}
|
||||
this.editGameImagePreview.set(game.coverImage || null);
|
||||
this.showAddForm.set(false);
|
||||
this.imageError.set(null);
|
||||
@@ -1650,6 +1753,19 @@ export class GamesListComponent implements OnInit {
|
||||
return new Date(date).toLocaleDateString();
|
||||
}
|
||||
|
||||
formatTimeSpent(minutes: number): string {
|
||||
const hours = Math.floor(minutes / 60);
|
||||
const mins = minutes % 60;
|
||||
|
||||
if (hours === 0) {
|
||||
return `${mins}m`;
|
||||
} else if (mins === 0) {
|
||||
return `${hours}h`;
|
||||
} else {
|
||||
return `${hours}h ${mins}m`;
|
||||
}
|
||||
}
|
||||
|
||||
toggleComments(gameId: string) {
|
||||
const expanded = this.expandedComments();
|
||||
const isCurrentlyExpanded = expanded[gameId];
|
||||
|
||||
@@ -29,6 +29,7 @@ interface Book {
|
||||
links: Array<Link>;
|
||||
series?: string;
|
||||
seriesOrder?: number;
|
||||
timeSpent?: number;
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
}
|
||||
@@ -47,6 +48,7 @@ interface CreateBookDto {
|
||||
links?: Array<Link>;
|
||||
series?: string;
|
||||
seriesOrder?: number;
|
||||
timeSpent?: number;
|
||||
}
|
||||
|
||||
interface UpdateBookDto extends Partial<CreateBookDto> {
|
||||
|
||||
@@ -29,6 +29,7 @@ interface Game {
|
||||
links: Array<Link>;
|
||||
series?: string;
|
||||
seriesOrder?: number;
|
||||
timeSpent?: number;
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
}
|
||||
@@ -46,6 +47,7 @@ interface CreateGameDto {
|
||||
links?: Array<Link>;
|
||||
series?: string;
|
||||
seriesOrder?: number;
|
||||
timeSpent?: number;
|
||||
}
|
||||
|
||||
interface UpdateGameDto extends Partial<CreateGameDto> {
|
||||
|
||||
@@ -27,6 +27,7 @@ interface Manga {
|
||||
coverImage?: string;
|
||||
tags: Array<string>;
|
||||
links: Array<Link>;
|
||||
timeSpent?: number;
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
}
|
||||
@@ -42,6 +43,7 @@ interface CreateMangaDto {
|
||||
coverImage?: string;
|
||||
tags?: Array<string>;
|
||||
links?: Array<Link>;
|
||||
timeSpent?: number;
|
||||
}
|
||||
|
||||
interface UpdateMangaDto extends Partial<CreateMangaDto> {
|
||||
|
||||
@@ -34,6 +34,7 @@ interface Music {
|
||||
coverArt?: string;
|
||||
tags: Array<string>;
|
||||
links: Array<Link>;
|
||||
timeSpent?: number;
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
}
|
||||
@@ -50,6 +51,7 @@ interface CreateMusicDto {
|
||||
coverArt?: string;
|
||||
tags?: Array<string>;
|
||||
links?: Array<Link>;
|
||||
timeSpent?: number;
|
||||
}
|
||||
|
||||
interface UpdateMusicDto extends Partial<CreateMusicDto> {
|
||||
|
||||
@@ -34,6 +34,7 @@ interface Show {
|
||||
coverImage?: string;
|
||||
tags: Array<string>;
|
||||
links: Array<Link>;
|
||||
timeSpent?: number;
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
}
|
||||
@@ -49,6 +50,7 @@ interface CreateShowDto {
|
||||
coverImage?: string;
|
||||
tags?: Array<string>;
|
||||
links?: Array<Link>;
|
||||
timeSpent?: number;
|
||||
}
|
||||
|
||||
interface UpdateShowDto extends Partial<CreateShowDto> {
|
||||
|
||||
Reference in New Issue
Block a user