generated from nhcarrigan/template
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:
@@ -105,6 +105,34 @@ import { Manga, MangaStatus, CreateMangaDto, UpdateMangaDto, Comment, Suggestion
|
||||
>
|
||||
</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">
|
||||
<label for="notes">Notes</label>
|
||||
<textarea
|
||||
@@ -256,6 +284,34 @@ import { Manga, MangaStatus, CreateMangaDto, UpdateMangaDto, Comment, Suggestion
|
||||
>
|
||||
</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">
|
||||
<label for="edit-notes">Notes</label>
|
||||
<textarea
|
||||
@@ -524,6 +580,12 @@ import { Manga, MangaStatus, CreateMangaDto, UpdateMangaDto, Comment, Suggestion
|
||||
</div>
|
||||
}
|
||||
|
||||
@if (manga.timeSpent) {
|
||||
<p class="time-spent">
|
||||
📚 Reading Time: {{ formatTimeSpent(manga.timeSpent) }}
|
||||
</p>
|
||||
}
|
||||
|
||||
<app-like-button
|
||||
entityType="manga"
|
||||
[entityId]="manga.id"
|
||||
@@ -691,6 +753,13 @@ import { Manga, MangaStatus, CreateMangaDto, UpdateMangaDto, Comment, Suggestion
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.form-row {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 1rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.form-actions {
|
||||
display: flex;
|
||||
gap: 1rem;
|
||||
@@ -886,6 +955,13 @@ import { Manga, MangaStatus, CreateMangaDto, UpdateMangaDto, Comment, Suggestion
|
||||
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,
|
||||
@@ -1313,6 +1389,12 @@ export class MangaListComponent implements OnInit {
|
||||
|
||||
editManga: Partial<UpdateMangaDto> = {};
|
||||
|
||||
// Time tracking state
|
||||
newMangaTimeHours = 0;
|
||||
newMangaTimeMinutes = 0;
|
||||
editMangaTimeHours = 0;
|
||||
editMangaTimeMinutes = 0;
|
||||
|
||||
// Tags and links input state
|
||||
newTagInput = '';
|
||||
editTagInput = '';
|
||||
@@ -1405,6 +1487,8 @@ export class MangaListComponent implements OnInit {
|
||||
tags: [],
|
||||
links: []
|
||||
};
|
||||
this.newMangaTimeHours = 0;
|
||||
this.newMangaTimeMinutes = 0;
|
||||
this.newMangaImagePreview.set(null);
|
||||
this.imageError.set(null);
|
||||
this.newTagInput = '';
|
||||
@@ -1412,6 +1496,16 @@ export class MangaListComponent implements OnInit {
|
||||
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') {
|
||||
const input = target === 'new' ? this.newTagInput.trim() : this.editTagInput.trim();
|
||||
if (!input) return;
|
||||
@@ -1499,8 +1593,17 @@ export class MangaListComponent implements OnInit {
|
||||
notes: manga.notes,
|
||||
coverImage: manga.coverImage,
|
||||
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.showAddForm.set(false);
|
||||
this.imageError.set(null);
|
||||
@@ -1590,6 +1693,19 @@ export class MangaListComponent 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(mangaId: string) {
|
||||
const expanded = this.expandedComments();
|
||||
const isCurrentlyExpanded = expanded[mangaId];
|
||||
|
||||
Reference in New Issue
Block a user