Consistently format dates and times across all templates and API handlers using MM-DD-YYYY with 12-hour clock (03:04 PM): - analytics.go: date keys, lastSync, lastRead timestamps - progress.go: lastUpdated timestamp in GetAllProgress - book_detail.templ: LastReadAt, DatePublished - book_detail_modals.templ: progress sync timestamps, LastReadAt - devices.templ: LastSync, LastSeen - conflicts.templ: CreatedAt - admin_users.templ: user CreatedAt date
160 lines
5.7 KiB
Templ
160 lines
5.7 KiB
Templ
package templates
|
|
|
|
import (
|
|
"bookhoard/internal/handlers"
|
|
"fmt"
|
|
)
|
|
|
|
// ProgressSyncModal shows progress from all devices for manual review
|
|
templ ProgressSyncModal(book handlers.MediaDetail) {
|
|
<div
|
|
id="progress-sync-modal"
|
|
class="hidden fixed inset-0 z-50 flex items-center justify-center overflow-y-auto"
|
|
style="background-color: rgba(0, 0, 0, 0.7);"
|
|
>
|
|
<div
|
|
class="card rounded-lg p-6 w-full max-w-4xl mx-4 my-8"
|
|
style="background-color: var(--bg-secondary); border-color: var(--border);"
|
|
>
|
|
<div class="flex justify-between items-center mb-6">
|
|
<div>
|
|
<h2 class="text-2xl font-bold" style="color: var(--text-primary)">Sync Progress</h2>
|
|
<p class="text-sm" style="color: var(--text-secondary);">{ book.Title }</p>
|
|
</div>
|
|
<button
|
|
@click="hideProgressSyncModal()"
|
|
class="p-2 hover:opacity-80 rounded-lg"
|
|
style="color: var(--text-primary); background-color: var(--bg-primary);"
|
|
>
|
|
✕
|
|
</button>
|
|
</div>
|
|
if book.ActiveConflict != nil {
|
|
<!-- Conflict Detected - Show All Sources -->
|
|
<div
|
|
class="mb-6 p-4 rounded-lg border"
|
|
style="background-color: #f59e0b20; border-color: #f59e0b;"
|
|
>
|
|
<p class="font-semibold mb-2" style="color: var(--text-primary);">
|
|
⚠️ Conflict Detected
|
|
</p>
|
|
<p class="text-sm" style="color: var(--text-secondary);">
|
|
Progress differs between devices. Review the options below and manually resolve via the Conflicts page.
|
|
</p>
|
|
<a
|
|
href="/conflicts"
|
|
class="inline-block mt-3 px-4 py-2 rounded-lg text-sm font-semibold"
|
|
style="background-color: #f59e0b; color: white; text-decoration: none;"
|
|
>
|
|
Go to Conflicts Page →
|
|
</a>
|
|
</div>
|
|
<!-- Display all device progress side-by-side -->
|
|
<div class="space-y-4">
|
|
for source, data := range book.ActiveConflict.ConflictData {
|
|
<div
|
|
class="card p-4 rounded-lg border"
|
|
style="background-color: var(--bg-primary); border-color: var(--border);"
|
|
>
|
|
<div class="flex justify-between items-start mb-3">
|
|
<div>
|
|
<span
|
|
class="inline-block px-2 py-1 rounded text-xs font-semibold capitalize mb-2"
|
|
style="background-color: var(--accent); color: white;"
|
|
>
|
|
{ source }
|
|
</span>
|
|
<p class="text-xs" style="color: var(--text-secondary);">
|
|
{ data.Timestamp.Format("01-02-2006 03:04 PM:05") }
|
|
</p>
|
|
</div>
|
|
<div class="text-right">
|
|
<div class="text-3xl font-bold" style="color: var(--accent);">
|
|
{ fmt.Sprintf("%.1f", data.Data["percentage"].(float64)) }%
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<!-- Additional progress details if available -->
|
|
if data.Data["current_page"] != nil {
|
|
<div class="text-sm" style="color: var(--text-secondary);">
|
|
Page: { fmt.Sprintf("%.0f", data.Data["current_page"].(float64)) } / { fmt.Sprintf("%.0f", data.Data["total_pages"].(float64)) }
|
|
</div>
|
|
}
|
|
if data.Data["epubcfi"] != nil {
|
|
<div class="text-xs mt-1" style="color: var(--text-secondary);">
|
|
CFI: { data.Data["epubcfi"].(string) }
|
|
</div>
|
|
}
|
|
</div>
|
|
}
|
|
</div>
|
|
} else if book.ReadingProgress != nil {
|
|
<!-- No Conflict - Show Current Progress -->
|
|
<div class="mb-4" style="color: var(--text-secondary);">
|
|
<p>No conflicts detected. Current progress from <strong>{ book.ReadingProgress.LastSyncSource.String }</strong>:</p>
|
|
</div>
|
|
<div
|
|
class="card p-6 rounded-lg border text-center"
|
|
style="background-color: var(--bg-primary); border-color: var(--border);"
|
|
>
|
|
<div class="text-5xl font-bold mb-4" style="color: var(--accent);">
|
|
{ fmt.Sprintf("%.1f", book.ReadingProgress.Percentage.Float64 * 100) }%
|
|
</div>
|
|
<p class="capitalize text-lg mb-2" style="color: var(--text-primary);">
|
|
{ book.ReadingProgress.LastSyncSource.String }
|
|
</p>
|
|
if book.ReadingProgress.CurrentPage.Valid && book.ReadingProgress.TotalPages.Valid {
|
|
<p style="color: var(--text-secondary);">
|
|
Page { book.ReadingProgress.CurrentPage.Int32 } of { book.ReadingProgress.TotalPages.Int32 }
|
|
</p>
|
|
}
|
|
if book.ReadingProgress.LastReadAt.Valid {
|
|
<p class="text-sm mt-2" style="color: var(--text-secondary);">
|
|
Last read: { book.ReadingProgress.LastReadAt.Time.Format("01-02-2006 03:04 PM") }
|
|
</p>
|
|
}
|
|
</div>
|
|
}
|
|
<div class="flex justify-end space-x-3 mt-6">
|
|
<button
|
|
@click="hideProgressSyncModal()"
|
|
class="px-4 py-2 rounded-lg"
|
|
style="background-color: var(--bg-primary); color: var(--text-primary); border: 1px solid var(--border);"
|
|
>
|
|
Close
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
}
|
|
|
|
// NotesHighlightsModal - Placeholder for future feature
|
|
templ NotesHighlightsModal(book handlers.MediaDetail) {
|
|
<div
|
|
id="notes-modal"
|
|
class="hidden fixed inset-0 z-50 flex items-center justify-center"
|
|
style="background-color: rgba(0, 0, 0, 0.7);"
|
|
>
|
|
<div
|
|
class="card rounded-lg p-8 w-full max-w-2xl mx-4 text-center"
|
|
style="background-color: var(--bg-secondary); border-color: var(--border);"
|
|
>
|
|
<div class="text-6xl mb-4">📝</div>
|
|
<h2 class="text-2xl font-bold mb-2" style="color: var(--text-primary)">Notes & Highlights</h2>
|
|
<p class="mb-2" style="color: var(--text-secondary);">
|
|
This book has <strong>{ book.NotesCount }</strong> notes and <strong>{ book.HighlightsCount }</strong> highlights.
|
|
</p>
|
|
<p class="mb-6" style="color: var(--text-secondary);">Feature coming soon!</p>
|
|
<div>
|
|
<button
|
|
@click="hideNotesModal()"
|
|
class="px-6 py-2 rounded-lg font-semibold"
|
|
style="background-color: var(--accent); color: white;"
|
|
>
|
|
Close
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
}
|