feat(matching): match results as a scoped templ partial + enriched matches
- BookMatch now carries title/author/cover (the fields the old client renderer always assumed but the API never sent — cards would have shown 'undefined'); filled during the family-lens pass in QueryBooks - new partial route GET /unlinked-books/matches renders the scoped match cascade server-side; searchMatches swaps it via htmx.ajax instead of rebuilding cards from JSON - NOTE: the unlinked-books surface is currently dark end-to-end — the templ page has no route, the live linking.ts renderer targets a container no template has, and its Link button calls a window.showMatchModal that does not exist. This commit ships the scoped backend + partial; wiring the UI is a product decision.
This commit is contained in:
1 parent
a31da89df5
commit
327fc2c0f9
5 files changed
+383
-160
No files matched your search
@@ -1,5 +1,53 @@
|
||||
package templates
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"bookhoard/internal/services"
|
||||
)
|
||||
|
||||
|
||||
// UnlinkedMatchCards renders the match candidates for one device file's
|
||||
// "find matches" reveal — the /unlinked-books/matches partial response.
|
||||
// Cards are clicked through the delegated data-action handler (rendering
|
||||
// ownership: templ renders, JS only swaps and handles the click).
|
||||
templ UnlinkedMatchCards(progressId string, matches []services.BookMatch) {
|
||||
if len(matches) == 0 {
|
||||
<p class="text-sm text-center p-4" style="color: var(--text-secondary)">No matches found. Try manual linking.</p>
|
||||
} else {
|
||||
for _, match := range matches {
|
||||
<div class="card p-4 rounded-lg border cursor-pointer hover:shadow-lg transition-shadow"
|
||||
style="background-color: var(--bg-primary); border-color: var(--border);"
|
||||
data-action="auto-link"
|
||||
data-progress-id={ progressId }
|
||||
data-media-item-id={ match.MediaItemID.String() }
|
||||
data-confidence={ fmt.Sprintf("%v", match.Confidence) }
|
||||
>
|
||||
<div class="flex gap-4">
|
||||
<img
|
||||
src={ match.CoverImagePath }
|
||||
alt="Cover"
|
||||
class="w-16 h-24 object-cover rounded"
|
||||
onerror="this.src='/static/placeholder-book.svg'"
|
||||
/>
|
||||
<div>
|
||||
<h5 class="font-semibold" style="color: var(--text-primary)">{ match.Title }</h5>
|
||||
if match.Author != "" {
|
||||
<p class="text-sm" style="color: var(--text-secondary)">by { match.Author }</p>
|
||||
}
|
||||
<div class="mt-2 flex items-center gap-2">
|
||||
<span class="text-xs px-2 py-1 rounded" style="background-color: var(--accent); color: var(--bg-primary);">
|
||||
{ fmt.Sprintf("%.0f%% confidence", match.Confidence*100) }
|
||||
</span>
|
||||
<span class="text-xs" style="color: var(--text-secondary)">{ match.MatchMethod }</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
templ UnlinkedBooks(user User, unlinkedBooks []UnlinkedBookData) {
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
Reference in new issue
Block a user