Fixed template function call mismatches: - conflicts.templ: Renamed showConflictModal→showResolveModal, hideConflictModal→hideResolveModal, handleResolveConflict→handleResolveSubmit - collections.templ: Fixed case mismatch addSelectedBooks→addbooksToAdd - queue.templ: Removed dead /static/queue.js reference - progress.templ: Removed vestigial x-data="progress" attribute Added missing queue functions: - showQueueItemModal() - Shows queue item detail modal - hideQueueItemModal() - Hides queue item modal - filterQueue() - Filters queue by status (stubbed) Fixed build errors: - main.ts: Removed imports for deleted themeDropdown.ts and woodPaneling.ts files All fixes are minimal and conservative. No breaking changes to existing functionality. TypeScript builds successfully (168KB main.js). Templates regenerate successfully.
246 lines
12 KiB
Templ
246 lines
12 KiB
Templ
package templates
|
|
|
|
import "bookhoard/internal/handlers"
|
|
|
|
templ Conflicts(user User, conflicts []handlers.ConflictDetailResponse, total int, unresolved int, errorMessage string) {
|
|
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8"/>
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
|
<title>Sync Conflicts - Bookhoard</title>
|
|
<script src="/static/htmx.min.js"></script>
|
|
<script src="/static/main.js"></script>
|
|
<link href="/static/style.css" rel="stylesheet"/>
|
|
</head>
|
|
<body x-data="conflicts" class="theme-{ user.Theme }">
|
|
@Header(user, "/conflicts")
|
|
<div class="w-full px-4 sm:px-6 lg:px-8 py-8">
|
|
<div class="mb-8">
|
|
<div class="flex justify-between items-center">
|
|
<div>
|
|
<h1 class="text-3xl font-bold" style="color: var(--text-primary)">Sync Conflicts</h1>
|
|
<p style="color: var(--text-secondary)">Resolve conflicts when reading progress differs across devices</p>
|
|
</div>
|
|
<div class="flex space-x-3">
|
|
<select
|
|
id="status-filter"
|
|
onchange="filterConflicts()"
|
|
class="px-4 py-2 border rounded-lg"
|
|
style="background-color: var(--bg-secondary); color: var(--text-primary); border-color: var(--border);"
|
|
>
|
|
<option value="unresolved">Unresolved Only</option>
|
|
<option value="all">All Conflicts</option>
|
|
<option value="resolved">Resolved Only</option>
|
|
</select>
|
|
<button @click="dismissAllResolved()" class="btn-secondary px-4 py-2 rounded-lg">
|
|
Dismiss All Resolved
|
|
</button>
|
|
</div>
|
|
</div>
|
|
<div id="stats-bar" class="mt-4 flex space-x-6 text-sm">
|
|
<span style="color: var(--text-secondary)">Total: <strong style="color: var(--text-primary)">{ total }</strong></span>
|
|
<span style="color: var(--text-secondary)">Unresolved: <strong style="color: #f59e0b;">{ unresolved }</strong></span>
|
|
<span style="color: var(--text-secondary)">Resolved: <strong style="color: #10b981;">{ total - unresolved }</strong></span>
|
|
</div>
|
|
<div id="bulk-actions" class="mt-4 hidden">
|
|
<div class="flex items-center justify-between p-4 rounded-lg border" style="background-color: var(--bg-secondary); border-color: var(--border);">
|
|
<div class="flex items-center space-x-4">
|
|
<span class="text-sm font-semibold" style="color: var(--text-primary);">
|
|
<span id="selected-count">0</span> conflicts selected
|
|
</span>
|
|
<select
|
|
id="bulk-strategy"
|
|
class="px-3 py-2 border rounded-lg text-sm"
|
|
style="background-color: var(--bg-primary); color: var(--text-primary); border-color: var(--border);"
|
|
>
|
|
<option value="most_recent">Most Recent</option>
|
|
<option value="highest_progress">Highest Progress</option>
|
|
</select>
|
|
</div>
|
|
<div class="flex space-x-2">
|
|
<button @click="bulkResolve()" class="btn-primary px-4 py-2 rounded-lg text-sm">
|
|
✅ Resolve Selected
|
|
</button>
|
|
<button @click="bulkDismiss()" class="btn-danger px-4 py-2 rounded-lg text-sm">
|
|
❌ Dismiss Selected
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div id="loading" class="hidden text-center py-16" style="color: var(--text-secondary)">
|
|
<div class="loading-spinner mx-auto mb-4"></div>
|
|
<p>Loading conflicts...</p>
|
|
</div>
|
|
<div id="empty-state" class="hidden text-center py-16" style="color: var(--text-secondary)">
|
|
<div class="text-6xl mb-4">✅</div>
|
|
<h3 class="text-xl font-semibold mb-2" style="color: var(--text-primary)">No Conflicts</h3>
|
|
<p>Your devices are in sync! No conflicts to resolve.</p>
|
|
</div>
|
|
<div id="conflicts-list" class="space-y-6">
|
|
<div id="select-all-container" class="mb-4 hidden">
|
|
<label class="flex items-center space-x-2 cursor-pointer">
|
|
<input type="checkbox" id="select-all-conflicts" class="w-5 h-5 rounded" onchange="toggleAllConflicts()"/>
|
|
<span class="text-sm font-semibold" style="color: var(--text-primary);">Select All</span>
|
|
</label>
|
|
</div>
|
|
if len(conflicts) == 0 {
|
|
<div class="text-center py-16" style="color: var(--text-secondary)">
|
|
<div class="text-6xl mb-4">✅</div>
|
|
<h3 class="text-xl font-semibold mb-2" style="color: var(--text-primary)">No Conflicts</h3>
|
|
<p>Your devices are in sync! No conflicts to resolve.</p>
|
|
</div>
|
|
}
|
|
for _, conflict := range conflicts {
|
|
<div class="conflict-item card p-6 rounded-lg border" data-conflict-id="{ conflict.ID }" style="background-color: var(--bg-secondary); border-color: var(--border); border-left-width: 4px; border-left-style: solid; border-left-color: rgb(245, 158, 11);">
|
|
<div class="flex justify-between items-start mb-4">
|
|
<div class="flex items-start space-x-4">
|
|
<input
|
|
type="checkbox"
|
|
class="conflict-checkbox w-5 h-5 rounded mt-1"
|
|
data-conflict-id="{ conflict.ID }"
|
|
onchange="updateBulkActions()"
|
|
/>
|
|
<div>
|
|
<h3 class="text-lg font-semibold" style="color: var(--text-primary)">{ conflict.MediaItemTitle }</h3>
|
|
<p class="text-sm" style="color: var(--text-secondary)">Type: { conflict.ConflictType }</p>
|
|
</div>
|
|
</div>
|
|
<button @click="showResolveModal('{ conflict.ID }')" class="btn-primary px-4 py-2 rounded-lg">
|
|
Resolve
|
|
</button>
|
|
</div>
|
|
<div class="text-sm" style="color: var(--text-secondary)">
|
|
Created: { conflict.CreatedAt.Format("2006-01-02 15:04:05") }
|
|
if conflict.ResolvedBy != "" {
|
|
| Resolved by: { conflict.ResolvedBy }
|
|
}
|
|
</div>
|
|
</div>
|
|
}
|
|
</div>
|
|
<div id="conflict-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)">Resolve Conflict</h2>
|
|
<p class="text-sm" id="conflict-book-title" style="color: var(--text-secondary);">Book Title</p>
|
|
</div>
|
|
<button @click="hideResolveModal()" class="p-2 hover:opacity-80 rounded-lg" style="color: var(--text-primary); background-color: var(--bg-primary);">
|
|
X
|
|
</button>
|
|
</div>
|
|
<form id="conflict-resolution-form" @submit="handleResolveSubmit($event)">
|
|
<input type="hidden" id="conflict-id"/>
|
|
<div id="conflict-details" class="mb-6"></div>
|
|
<div class="card p-4 rounded-lg border mb-6" style="background-color: var(--bg-primary); border-color: var(--border);">
|
|
<h3 class="font-semibold mb-4" style="color: var(--text-primary)">Resolution Options</h3>
|
|
<div class="space-y-3 mb-6">
|
|
<label class="flex items-center p-3 border rounded-lg cursor-pointer hover:opacity-80" style="border-color: var(--border);">
|
|
<input type="radio" name="winner" value="koreader" class="mr-3" required/>
|
|
<div class="flex-1">
|
|
<span class="font-semibold" style="color: var(--text-primary);">Keep KOReader Progress</span>
|
|
<p class="text-xs" style="color: var(--text-secondary);">Use progress from your KOReader device</p>
|
|
</div>
|
|
</label>
|
|
<label class="flex items-center p-3 border rounded-lg cursor-pointer hover:opacity-80" style="border-color: var(--border);">
|
|
<input type="radio" name="winner" value="kobo" class="mr-3"/>
|
|
<div class="flex-1">
|
|
<span class="font-semibold" style="color: var(--text-primary);">Keep Kobo Progress</span>
|
|
<p class="text-xs" style="color: var(--text-secondary);">Use progress from your Kobo device</p>
|
|
</div>
|
|
</label>
|
|
<label class="flex items-center p-3 border rounded-lg cursor-pointer hover:opacity-80" style="border-color: var(--border);">
|
|
<input type="radio" name="winner" value="web" class="mr-3"/>
|
|
<div class="flex-1">
|
|
<span class="font-semibold" style="color: var(--text-primary);">Keep Web Progress</span>
|
|
<p class="text-xs" style="color: var(--text-secondary);">Use progress from the web interface</p>
|
|
</div>
|
|
</label>
|
|
<label class="flex items-center p-3 border rounded-lg cursor-pointer hover:opacity-80" style="border-color: var(--border);">
|
|
<input type="radio" name="winner" value="manual" class="mr-3"/>
|
|
<div class="flex-1">
|
|
<span class="font-semibold" style="color: var(--text-primary);">Manual Override</span>
|
|
<p class="text-xs" style="color: var(--text-secondary);">Specify a custom progress value</p>
|
|
</div>
|
|
</label>
|
|
</div>
|
|
<div id="manual-override" class="hidden mb-4 p-4 border rounded-lg" style="background-color: var(--bg-secondary); border-color: var(--border);">
|
|
<h4 class="font-semibold mb-3" style="color: var(--text-primary);">Manual Progress</h4>
|
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
|
<div>
|
|
<label class="block text-sm font-medium mb-2" style="color: var(--text-secondary);">Percentage (0-100)</label>
|
|
<input
|
|
type="number"
|
|
id="manual-percentage"
|
|
min="0"
|
|
max="100"
|
|
step="0.01"
|
|
class="w-full px-4 py-2 border rounded-lg"
|
|
style="background-color: var(--bg-primary); color: var(--text-primary); border-color: var(--border);"
|
|
/>
|
|
</div>
|
|
<div>
|
|
<label class="block text-sm font-medium mb-2" style="color: var(--text-secondary);">Page Number</label>
|
|
<input
|
|
type="number"
|
|
id="manual-page"
|
|
min="1"
|
|
class="w-full px-4 py-2 border rounded-lg"
|
|
style="background-color: var(--bg-primary); color: var(--text-primary); border-color: var(--border);"
|
|
/>
|
|
</div>
|
|
<div>
|
|
<label class="block text-sm font-medium mb-2" style="color: var(--text-secondary);">EPUB CFI</label>
|
|
<input
|
|
type="text"
|
|
id="manual-epubcfi"
|
|
class="w-full px-4 py-2 border rounded-lg"
|
|
style="background-color: var(--bg-primary); color: var(--text-primary); border-color: var(--border);"
|
|
/>
|
|
</div>
|
|
<div>
|
|
<label class="block text-sm font-medium mb-2" style="color: var(--text-secondary);">Chapter</label>
|
|
<input
|
|
type="number"
|
|
id="manual-chapter"
|
|
min="1"
|
|
class="w-full px-4 py-2 border rounded-lg"
|
|
style="background-color: var(--bg-primary); color: var(--text-primary); border-color: var(--border);"
|
|
/>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="mb-6">
|
|
<label class="flex items-center space-x-3 cursor-pointer">
|
|
<input type="checkbox" id="apply-to-all" class="w-5 h-5 rounded"/>
|
|
<span style="color: var(--text-primary);">Apply this resolution to all future conflicts</span>
|
|
</label>
|
|
</div>
|
|
<div class="mb-6">
|
|
<label class="block text-sm font-medium mb-2" style="color: var(--text-secondary);">Resolution Reason (optional)</label>
|
|
<input
|
|
type="text"
|
|
id="resolution-reason"
|
|
class="w-full px-4 py-2 border rounded-lg"
|
|
style="background-color: var(--bg-primary); color: var(--text-primary); border-color: var(--border);"
|
|
placeholder="e.g., Device was offline, using most recent progress"
|
|
/>
|
|
</div>
|
|
</div>
|
|
<div class="flex justify-end space-x-3">
|
|
<button type="button" @click="hideResolveModal()" class="btn-secondary px-4 py-2 rounded-lg">Cancel</button>
|
|
<button type="button" @click="deleteConflict()" class="btn-danger px-4 py-2 rounded-lg">Dismiss</button>
|
|
<button type="submit" class="btn-primary px-6 py-2 rounded-lg">Resolve Conflict</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<script src="/static/conflicts.js"></script>
|
|
@ErrorToast(errorMessage)
|
|
</body>
|
|
</html>
|
|
}
|