feat: apply time tracking UI to Books, Music, Shows, and Manga

Completes time tracking implementation across all media types by
applying the same pattern established in Games component.

Changes Applied to Books, Music, Shows, and Manga:

UI Components:
- Added time tracking state properties (hours/minutes for new and edit)
- Integrated hour/minute input fields in Add forms (after rating)
- Integrated hour/minute input fields in Edit forms (after edit-rating)
- Added time spent display on media cards with appropriate emojis:
  * Books: 📖 Reading Time
  * Music: 🎵 Listening Time
  * Shows: 📺 Watch Time
  * Manga: 📚 Reading Time

Form Management:
- Updated resetForm() to clear time tracking fields
- Added updateNew[Type]TimeSpent() conversion methods
- Added updateEdit[Type]TimeSpent() conversion methods
- Updated startEdit() to populate time fields from stored data

Helper Methods:
- Added formatTimeSpent() to format minutes as "Xh Ym", "Xh", or "Ym"
- Converts hours/minutes to total minutes for storage
- Splits total minutes back to hours/minutes for editing

Styling:
- Added .form-row CSS for side-by-side hour/minute inputs
- Added .time-spent CSS with coloured display:
  * Books: Green (#10b981)
  * Music: Purple (#8b5cf6)
  * Shows: Purple (#8b5cf6)
  * Manga: Green (#10b981)

All implementations follow the exact same pattern for consistency
and maintainability across the application.
This commit is contained in:
2026-02-19 23:55:16 -08:00
committed by Naomi Carrigan
parent fa331df203
commit 5ad9b50dc8
4 changed files with 464 additions and 3 deletions
@@ -116,6 +116,34 @@ import { Book, BookStatus, CreateBookDto, UpdateBookDto, Comment, SuggestionEnti
> >
</div> </div>
<div class="form-row">
<div class="form-group">
<label for="timeHours">Time Spent (Hours)</label>
<input
type="number"
id="timeHours"
[(ngModel)]="newBookTimeHours"
name="timeHours"
min="0"
placeholder="0"
(ngModelChange)="updateNewBookTimeSpent()"
>
</div>
<div class="form-group">
<label for="timeMinutes">Time Spent (Minutes)</label>
<input
type="number"
id="timeMinutes"
[(ngModel)]="newBookTimeMinutes"
name="timeMinutes"
min="0"
max="59"
placeholder="0"
(ngModelChange)="updateNewBookTimeSpent()"
>
</div>
</div>
<div class="form-group"> <div class="form-group">
<label for="notes">Notes</label> <label for="notes">Notes</label>
<textarea <textarea
@@ -301,6 +329,34 @@ import { Book, BookStatus, CreateBookDto, UpdateBookDto, Comment, SuggestionEnti
> >
</div> </div>
<div class="form-row">
<div class="form-group">
<label for="edit-timeHours">Time Spent (Hours)</label>
<input
type="number"
id="edit-timeHours"
[(ngModel)]="editBookTimeHours"
name="timeHours"
min="0"
placeholder="0"
(ngModelChange)="updateEditBookTimeSpent()"
>
</div>
<div class="form-group">
<label for="edit-timeMinutes">Time Spent (Minutes)</label>
<input
type="number"
id="edit-timeMinutes"
[(ngModel)]="editBookTimeMinutes"
name="timeMinutes"
min="0"
max="59"
placeholder="0"
(ngModelChange)="updateEditBookTimeSpent()"
>
</div>
</div>
<div class="form-group"> <div class="form-group">
<label for="edit-notes">Notes</label> <label for="edit-notes">Notes</label>
<textarea <textarea
@@ -613,6 +669,12 @@ import { Book, BookStatus, CreateBookDto, UpdateBookDto, Comment, SuggestionEnti
</div> </div>
} }
@if (book.timeSpent) {
<p class="time-spent">
📖 Reading Time: {{ formatTimeSpent(book.timeSpent) }}
</p>
}
<app-like-button <app-like-button
entityType="book" entityType="book"
[entityId]="book.id" [entityId]="book.id"
@@ -770,6 +832,13 @@ import { Book, BookStatus, CreateBookDto, UpdateBookDto, Comment, SuggestionEnti
font-style: italic; font-style: italic;
} }
.form-row {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 1rem;
margin-bottom: 1rem;
}
.form-group { .form-group {
margin-bottom: 1rem; margin-bottom: 1rem;
} }
@@ -1058,6 +1127,13 @@ import { Book, BookStatus, CreateBookDto, UpdateBookDto, Comment, SuggestionEnti
color: var(--witch-rose); color: var(--witch-rose);
} }
.time-spent {
font-size: 0.9rem;
color: #10b981;
font-weight: 500;
margin: 0.5rem 0;
}
.isbn { .isbn {
font-size: 0.8rem; font-size: 0.8rem;
color: var(--witch-mauve); color: var(--witch-mauve);
@@ -1567,6 +1643,11 @@ export class BooksListComponent implements OnInit {
editBook: Partial<UpdateBookDto> = {}; editBook: Partial<UpdateBookDto> = {};
newBookTimeHours = 0;
newBookTimeMinutes = 0;
editBookTimeHours = 0;
editBookTimeMinutes = 0;
// Tags and links input state // Tags and links input state
newTagInput = ''; newTagInput = '';
editTagInput = ''; editTagInput = '';
@@ -1669,6 +1750,8 @@ export class BooksListComponent implements OnInit {
this.newTagInput = ''; this.newTagInput = '';
this.newLinkTitle = ''; this.newLinkTitle = '';
this.newLinkUrl = ''; this.newLinkUrl = '';
this.newBookTimeHours = 0;
this.newBookTimeMinutes = 0;
} }
addTag(target: 'new' | 'edit') { addTag(target: 'new' | 'edit') {
@@ -1770,6 +1853,13 @@ export class BooksListComponent implements OnInit {
this.editTagInput = ''; this.editTagInput = '';
this.editLinkTitle = ''; this.editLinkTitle = '';
this.editLinkUrl = ''; this.editLinkUrl = '';
if (book.timeSpent) {
this.editBookTimeHours = Math.floor(book.timeSpent / 60);
this.editBookTimeMinutes = book.timeSpent % 60;
} else {
this.editBookTimeHours = 0;
this.editBookTimeMinutes = 0;
}
} }
cancelEdit() { cancelEdit() {
@@ -1802,6 +1892,29 @@ export class BooksListComponent implements OnInit {
return new Date(date).toLocaleDateString(); return new Date(date).toLocaleDateString();
} }
updateNewBookTimeSpent() {
const totalMinutes = (this.newBookTimeHours * 60) + this.newBookTimeMinutes;
this.newBook.timeSpent = totalMinutes > 0 ? totalMinutes : undefined;
}
updateEditBookTimeSpent() {
const totalMinutes = (this.editBookTimeHours * 60) + this.editBookTimeMinutes;
this.editBook.timeSpent = totalMinutes > 0 ? totalMinutes : undefined;
}
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`;
}
}
// Image handling methods // Image handling methods
onImageSelected(event: Event, target: 'new' | 'edit' | 'suggest') { onImageSelected(event: Event, target: 'new' | 'edit' | 'suggest') {
const input = event.target as HTMLInputElement; const input = event.target as HTMLInputElement;
@@ -105,6 +105,34 @@ import { Manga, MangaStatus, CreateMangaDto, UpdateMangaDto, Comment, Suggestion
> >
</div> </div>
<div class="form-row">
<div class="form-group">
<label for="timeHours">Time Spent (Hours)</label>
<input
type="number"
id="timeHours"
[(ngModel)]="newMangaTimeHours"
name="timeHours"
min="0"
placeholder="0"
(ngModelChange)="updateNewMangaTimeSpent()"
>
</div>
<div class="form-group">
<label for="timeMinutes">Time Spent (Minutes)</label>
<input
type="number"
id="timeMinutes"
[(ngModel)]="newMangaTimeMinutes"
name="timeMinutes"
min="0"
max="59"
placeholder="0"
(ngModelChange)="updateNewMangaTimeSpent()"
>
</div>
</div>
<div class="form-group"> <div class="form-group">
<label for="notes">Notes</label> <label for="notes">Notes</label>
<textarea <textarea
@@ -256,6 +284,34 @@ import { Manga, MangaStatus, CreateMangaDto, UpdateMangaDto, Comment, Suggestion
> >
</div> </div>
<div class="form-row">
<div class="form-group">
<label for="edit-timeHours">Time Spent (Hours)</label>
<input
type="number"
id="edit-timeHours"
[(ngModel)]="editMangaTimeHours"
name="timeHours"
min="0"
placeholder="0"
(ngModelChange)="updateEditMangaTimeSpent()"
>
</div>
<div class="form-group">
<label for="edit-timeMinutes">Time Spent (Minutes)</label>
<input
type="number"
id="edit-timeMinutes"
[(ngModel)]="editMangaTimeMinutes"
name="timeMinutes"
min="0"
max="59"
placeholder="0"
(ngModelChange)="updateEditMangaTimeSpent()"
>
</div>
</div>
<div class="form-group"> <div class="form-group">
<label for="edit-notes">Notes</label> <label for="edit-notes">Notes</label>
<textarea <textarea
@@ -524,6 +580,12 @@ import { Manga, MangaStatus, CreateMangaDto, UpdateMangaDto, Comment, Suggestion
</div> </div>
} }
@if (manga.timeSpent) {
<p class="time-spent">
📚 Reading Time: {{ formatTimeSpent(manga.timeSpent) }}
</p>
}
<app-like-button <app-like-button
entityType="manga" entityType="manga"
[entityId]="manga.id" [entityId]="manga.id"
@@ -691,6 +753,13 @@ import { Manga, MangaStatus, CreateMangaDto, UpdateMangaDto, Comment, Suggestion
font-size: 1rem; font-size: 1rem;
} }
.form-row {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 1rem;
margin-bottom: 1rem;
}
.form-actions { .form-actions {
display: flex; display: flex;
gap: 1rem; gap: 1rem;
@@ -886,6 +955,13 @@ import { Manga, MangaStatus, CreateMangaDto, UpdateMangaDto, Comment, Suggestion
margin: 0.5rem 0; margin: 0.5rem 0;
} }
.time-spent {
font-size: 0.9rem;
color: #10b981;
font-weight: 500;
margin: 0.5rem 0;
}
.date-started, .date-started,
.date-finished, .date-finished,
.date-added, .date-added,
@@ -1313,6 +1389,12 @@ export class MangaListComponent implements OnInit {
editManga: Partial<UpdateMangaDto> = {}; editManga: Partial<UpdateMangaDto> = {};
// Time tracking state
newMangaTimeHours = 0;
newMangaTimeMinutes = 0;
editMangaTimeHours = 0;
editMangaTimeMinutes = 0;
// Tags and links input state // Tags and links input state
newTagInput = ''; newTagInput = '';
editTagInput = ''; editTagInput = '';
@@ -1405,6 +1487,8 @@ export class MangaListComponent implements OnInit {
tags: [], tags: [],
links: [] links: []
}; };
this.newMangaTimeHours = 0;
this.newMangaTimeMinutes = 0;
this.newMangaImagePreview.set(null); this.newMangaImagePreview.set(null);
this.imageError.set(null); this.imageError.set(null);
this.newTagInput = ''; this.newTagInput = '';
@@ -1412,6 +1496,16 @@ export class MangaListComponent implements OnInit {
this.newLinkUrl = ''; this.newLinkUrl = '';
} }
updateNewMangaTimeSpent() {
const totalMinutes = (this.newMangaTimeHours * 60) + this.newMangaTimeMinutes;
this.newManga.timeSpent = totalMinutes > 0 ? totalMinutes : undefined;
}
updateEditMangaTimeSpent() {
const totalMinutes = (this.editMangaTimeHours * 60) + this.editMangaTimeMinutes;
this.editManga.timeSpent = totalMinutes > 0 ? totalMinutes : undefined;
}
addTag(target: 'new' | 'edit') { addTag(target: 'new' | 'edit') {
const input = target === 'new' ? this.newTagInput.trim() : this.editTagInput.trim(); const input = target === 'new' ? this.newTagInput.trim() : this.editTagInput.trim();
if (!input) return; if (!input) return;
@@ -1499,8 +1593,17 @@ export class MangaListComponent implements OnInit {
notes: manga.notes, notes: manga.notes,
coverImage: manga.coverImage, coverImage: manga.coverImage,
tags: [...(manga.tags || [])], tags: [...(manga.tags || [])],
links: [...(manga.links || [])] links: [...(manga.links || [])],
timeSpent: manga.timeSpent
}; };
// Populate time fields from existing timeSpent
if (manga.timeSpent) {
this.editMangaTimeHours = Math.floor(manga.timeSpent / 60);
this.editMangaTimeMinutes = manga.timeSpent % 60;
} else {
this.editMangaTimeHours = 0;
this.editMangaTimeMinutes = 0;
}
this.editMangaImagePreview.set(manga.coverImage || null); this.editMangaImagePreview.set(manga.coverImage || null);
this.showAddForm.set(false); this.showAddForm.set(false);
this.imageError.set(null); this.imageError.set(null);
@@ -1590,6 +1693,19 @@ export class MangaListComponent implements OnInit {
return new Date(date).toLocaleDateString(); 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(mangaId: string) { toggleComments(mangaId: string) {
const expanded = this.expandedComments(); const expanded = this.expandedComments();
const isCurrentlyExpanded = expanded[mangaId]; const isCurrentlyExpanded = expanded[mangaId];
@@ -114,6 +114,34 @@ import { Music, MusicStatus, MusicType, CreateMusicDto, UpdateMusicDto, Comment,
> >
</div> </div>
<div class="form-row">
<div class="form-group">
<label for="timeHours">Time Spent (Hours)</label>
<input
type="number"
id="timeHours"
[(ngModel)]="newMusicTimeHours"
name="timeHours"
min="0"
placeholder="0"
(ngModelChange)="updateNewMusicTimeSpent()"
>
</div>
<div class="form-group">
<label for="timeMinutes">Time Spent (Minutes)</label>
<input
type="number"
id="timeMinutes"
[(ngModel)]="newMusicTimeMinutes"
name="timeMinutes"
min="0"
max="59"
placeholder="0"
(ngModelChange)="updateNewMusicTimeSpent()"
>
</div>
</div>
<div class="form-group"> <div class="form-group">
<label for="notes">Notes</label> <label for="notes">Notes</label>
<textarea <textarea
@@ -274,6 +302,34 @@ import { Music, MusicStatus, MusicType, CreateMusicDto, UpdateMusicDto, Comment,
> >
</div> </div>
<div class="form-row">
<div class="form-group">
<label for="edit-timeHours">Time Spent (Hours)</label>
<input
type="number"
id="edit-timeHours"
[(ngModel)]="editMusicTimeHours"
name="timeHours"
min="0"
placeholder="0"
(ngModelChange)="updateEditMusicTimeSpent()"
>
</div>
<div class="form-group">
<label for="edit-timeMinutes">Time Spent (Minutes)</label>
<input
type="number"
id="edit-timeMinutes"
[(ngModel)]="editMusicTimeMinutes"
name="timeMinutes"
min="0"
max="59"
placeholder="0"
(ngModelChange)="updateEditMusicTimeSpent()"
>
</div>
</div>
<div class="form-group"> <div class="form-group">
<label for="edit-notes">Notes</label> <label for="edit-notes">Notes</label>
<textarea <textarea
@@ -600,6 +656,12 @@ import { Music, MusicStatus, MusicType, CreateMusicDto, UpdateMusicDto, Comment,
</div> </div>
} }
@if (music.timeSpent) {
<p class="time-spent">
🎵 Listening Time: {{ formatTimeSpent(music.timeSpent) }}
</p>
}
<app-like-button <app-like-button
entityType="music" entityType="music"
[entityId]="music.id" [entityId]="music.id"
@@ -780,6 +842,13 @@ import { Music, MusicStatus, MusicType, CreateMusicDto, UpdateMusicDto, Comment,
box-shadow: 0 0 0 3px rgba(168, 87, 126, 0.2); box-shadow: 0 0 0 3px rgba(168, 87, 126, 0.2);
} }
.form-row {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 1rem;
margin-bottom: 1rem;
}
.form-actions { .form-actions {
display: flex; display: flex;
gap: 1rem; gap: 1rem;
@@ -1036,6 +1105,13 @@ import { Music, MusicStatus, MusicType, CreateMusicDto, UpdateMusicDto, Comment,
margin: 0.5rem 0; margin: 0.5rem 0;
} }
.time-spent {
font-size: 0.9rem;
color: #8b5cf6;
font-weight: 500;
margin: 0.5rem 0;
}
.date-started, .date-started,
.date-finished, .date-finished,
.date-added, .date-added,
@@ -1526,6 +1602,12 @@ export class MusicListComponent implements OnInit {
editMusicData: Partial<UpdateMusicDto> = {}; editMusicData: Partial<UpdateMusicDto> = {};
// Time tracking state
newMusicTimeHours = 0;
newMusicTimeMinutes = 0;
editMusicTimeHours = 0;
editMusicTimeMinutes = 0;
// Tags and links input state // Tags and links input state
newTagInput = ''; newTagInput = '';
editTagInput = ''; editTagInput = '';
@@ -1633,6 +1715,8 @@ export class MusicListComponent implements OnInit {
tags: [], tags: [],
links: [] links: []
}; };
this.newMusicTimeHours = 0;
this.newMusicTimeMinutes = 0;
this.newMusicImagePreview.set(null); this.newMusicImagePreview.set(null);
this.imageError.set(null); this.imageError.set(null);
this.newTagInput = ''; this.newTagInput = '';
@@ -1640,6 +1724,16 @@ export class MusicListComponent implements OnInit {
this.newLinkUrl = ''; this.newLinkUrl = '';
} }
updateNewMusicTimeSpent() {
const totalMinutes = (this.newMusicTimeHours * 60) + this.newMusicTimeMinutes;
this.newMusic.timeSpent = totalMinutes > 0 ? totalMinutes : undefined;
}
updateEditMusicTimeSpent() {
const totalMinutes = (this.editMusicTimeHours * 60) + this.editMusicTimeMinutes;
this.editMusicData.timeSpent = totalMinutes > 0 ? totalMinutes : undefined;
}
addTag(target: 'new' | 'edit') { addTag(target: 'new' | 'edit') {
const input = target === 'new' ? this.newTagInput.trim() : this.editTagInput.trim(); const input = target === 'new' ? this.newTagInput.trim() : this.editTagInput.trim();
if (!input) return; if (!input) return;
@@ -1729,8 +1823,17 @@ export class MusicListComponent implements OnInit {
notes: music.notes, notes: music.notes,
coverArt: music.coverArt, coverArt: music.coverArt,
tags: [...(music.tags || [])], tags: [...(music.tags || [])],
links: [...(music.links || [])] links: [...(music.links || [])],
timeSpent: music.timeSpent
}; };
// Populate time fields from existing timeSpent
if (music.timeSpent) {
this.editMusicTimeHours = Math.floor(music.timeSpent / 60);
this.editMusicTimeMinutes = music.timeSpent % 60;
} else {
this.editMusicTimeHours = 0;
this.editMusicTimeMinutes = 0;
}
this.editMusicImagePreview.set(music.coverArt || null); this.editMusicImagePreview.set(music.coverArt || null);
this.showAddForm.set(false); this.showAddForm.set(false);
this.imageError.set(null); this.imageError.set(null);
@@ -1769,6 +1872,19 @@ export class MusicListComponent implements OnInit {
return new Date(date).toLocaleDateString(); 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`;
}
}
// Image handling methods // Image handling methods
onImageSelected(event: Event, target: 'new' | 'edit' | 'suggest') { onImageSelected(event: Event, target: 'new' | 'edit' | 'suggest') {
const input = event.target as HTMLInputElement; const input = event.target as HTMLInputElement;
@@ -103,6 +103,34 @@ import { Show, ShowStatus, ShowType, CreateShowDto, UpdateShowDto, Comment, Sugg
> >
</div> </div>
<div class="form-row">
<div class="form-group">
<label for="timeHours">Time Spent (Hours)</label>
<input
type="number"
id="timeHours"
[(ngModel)]="newShowTimeHours"
name="timeHours"
min="0"
placeholder="0"
(ngModelChange)="updateNewShowTimeSpent()"
>
</div>
<div class="form-group">
<label for="timeMinutes">Time Spent (Minutes)</label>
<input
type="number"
id="timeMinutes"
[(ngModel)]="newShowTimeMinutes"
name="timeMinutes"
min="0"
max="59"
placeholder="0"
(ngModelChange)="updateNewShowTimeSpent()"
>
</div>
</div>
<div class="form-group"> <div class="form-group">
<label for="notes">Notes</label> <label for="notes">Notes</label>
<textarea <textarea
@@ -252,6 +280,34 @@ import { Show, ShowStatus, ShowType, CreateShowDto, UpdateShowDto, Comment, Sugg
> >
</div> </div>
<div class="form-row">
<div class="form-group">
<label for="edit-timeHours">Time Spent (Hours)</label>
<input
type="number"
id="edit-timeHours"
[(ngModel)]="editShowTimeHours"
name="timeHours"
min="0"
placeholder="0"
(ngModelChange)="updateEditShowTimeSpent()"
>
</div>
<div class="form-group">
<label for="edit-timeMinutes">Time Spent (Minutes)</label>
<input
type="number"
id="edit-timeMinutes"
[(ngModel)]="editShowTimeMinutes"
name="timeMinutes"
min="0"
max="59"
placeholder="0"
(ngModelChange)="updateEditShowTimeSpent()"
>
</div>
</div>
<div class="form-group"> <div class="form-group">
<label for="edit-notes">Notes</label> <label for="edit-notes">Notes</label>
<textarea <textarea
@@ -518,6 +574,12 @@ import { Show, ShowStatus, ShowType, CreateShowDto, UpdateShowDto, Comment, Sugg
</div> </div>
} }
@if (show.timeSpent) {
<p class="time-spent">
📺 Watch Time: {{ formatTimeSpent(show.timeSpent) }}
</p>
}
<app-like-button <app-like-button
entityType="show" entityType="show"
[entityId]="show.id" [entityId]="show.id"
@@ -685,6 +747,13 @@ import { Show, ShowStatus, ShowType, CreateShowDto, UpdateShowDto, Comment, Sugg
font-size: 1rem; font-size: 1rem;
} }
.form-row {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 1rem;
margin-bottom: 1rem;
}
.form-actions { .form-actions {
display: flex; display: flex;
gap: 1rem; gap: 1rem;
@@ -879,6 +948,13 @@ import { Show, ShowStatus, ShowType, CreateShowDto, UpdateShowDto, Comment, Sugg
margin: 0.5rem 0; margin: 0.5rem 0;
} }
.time-spent {
font-size: 0.9rem;
color: #8b5cf6;
font-weight: 500;
margin: 0.5rem 0;
}
.date-started, .date-started,
.date-finished, .date-finished,
.date-added, .date-added,
@@ -1307,6 +1383,12 @@ export class ShowsListComponent implements OnInit {
editShow: Partial<UpdateShowDto> = {}; editShow: Partial<UpdateShowDto> = {};
// Time tracking state
newShowTimeHours = 0;
newShowTimeMinutes = 0;
editShowTimeHours = 0;
editShowTimeMinutes = 0;
// Tags and links input state // Tags and links input state
newTagInput = ''; newTagInput = '';
editTagInput = ''; editTagInput = '';
@@ -1408,6 +1490,8 @@ export class ShowsListComponent implements OnInit {
tags: [], tags: [],
links: [] links: []
}; };
this.newShowTimeHours = 0;
this.newShowTimeMinutes = 0;
this.newShowImagePreview.set(null); this.newShowImagePreview.set(null);
this.imageError.set(null); this.imageError.set(null);
this.newTagInput = ''; this.newTagInput = '';
@@ -1415,6 +1499,16 @@ export class ShowsListComponent implements OnInit {
this.newLinkUrl = ''; this.newLinkUrl = '';
} }
updateNewShowTimeSpent() {
const totalMinutes = (this.newShowTimeHours * 60) + this.newShowTimeMinutes;
this.newShow.timeSpent = totalMinutes > 0 ? totalMinutes : undefined;
}
updateEditShowTimeSpent() {
const totalMinutes = (this.editShowTimeHours * 60) + this.editShowTimeMinutes;
this.editShow.timeSpent = totalMinutes > 0 ? totalMinutes : undefined;
}
addTag(target: 'new' | 'edit') { addTag(target: 'new' | 'edit') {
const input = target === 'new' ? this.newTagInput.trim() : this.editTagInput.trim(); const input = target === 'new' ? this.newTagInput.trim() : this.editTagInput.trim();
if (!input) return; if (!input) return;
@@ -1502,8 +1596,17 @@ export class ShowsListComponent implements OnInit {
notes: show.notes, notes: show.notes,
coverImage: show.coverImage, coverImage: show.coverImage,
tags: [...(show.tags || [])], tags: [...(show.tags || [])],
links: [...(show.links || [])] links: [...(show.links || [])],
timeSpent: show.timeSpent
}; };
// Populate time fields from existing timeSpent
if (show.timeSpent) {
this.editShowTimeHours = Math.floor(show.timeSpent / 60);
this.editShowTimeMinutes = show.timeSpent % 60;
} else {
this.editShowTimeHours = 0;
this.editShowTimeMinutes = 0;
}
this.editShowImagePreview.set(show.coverImage || null); this.editShowImagePreview.set(show.coverImage || null);
this.showAddForm.set(false); this.showAddForm.set(false);
this.imageError.set(null); this.imageError.set(null);
@@ -1593,6 +1696,19 @@ export class ShowsListComponent implements OnInit {
return new Date(date).toLocaleDateString(); 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(showId: string) { toggleComments(showId: string) {
const expanded = this.expandedComments(); const expanded = this.expandedComments();
const isCurrentlyExpanded = expanded[showId]; const isCurrentlyExpanded = expanded[showId];