{% extends "layout.html.tera" %}
{% block title %}Review queue — {{ entity_plural | capitalize }} — {{ app_display }}{% endblock title %}
{% block content %}
<nav class="breadcrumb-nav" aria-label="Breadcrumb">
<ol class="breadcrumb-list" aria-label="Breadcrumb trail">
<li class="breadcrumb-list-item"><a href="/">Home</a></li>
<li class="breadcrumb-list-item"><a href="/{{ entity_plural }}">{{ entity_plural | capitalize }}</a></li>
<li class="breadcrumb-list-item" aria-current="page">Review queue</li>
</ol>
</nav>
<section aria-label="Duplicate review queue">
<h2>Duplicate review queue</h2>
<p>
<span class="hint" aria-label="Queue summary">
{{ stats.pending }} pending ·
{{ stats.confirmed }} confirmed ·
{{ stats.rejected }} rejected ·
{{ stats.auto_merged }} auto-merged
</span>
</p>
<p>
<a class="button"
href="/{{ entity_plural }}/review-queue/kanban"
aria-label="Switch to kanban board view">Kanban view</a>
<a class="button"
href="/{{ entity_plural }}/deduplicate"
aria-label="Run a batch deduplication scan">Run dedup scan</a>
</p>
<form class="form"
aria-label="Filter review queue"
hx-get="/{{ entity_plural }}/review-queue"
hx-target="#review-queue-tbody"
hx-select="#review-queue-tbody"
hx-swap="outerHTML"
hx-trigger="change">
<div class="field" aria-label="Status filter field">
<label class="label" for="status-filter">Status</label>
<select class="select"
id="status-filter"
name="status"
aria-label="Filter by review status">
<option class="option" value="Pending" {% if filter_status == "Pending" %}selected{% endif %}>Pending</option>
<option class="option" value="Confirmed" {% if filter_status == "Confirmed" %}selected{% endif %}>Confirmed</option>
<option class="option" value="Rejected" {% if filter_status == "Rejected" %}selected{% endif %}>Rejected</option>
<option class="option" value="AutoMerged" {% if filter_status == "AutoMerged" %}selected{% endif %}>Auto-merged</option>
</select>
</div>
<div class="field" aria-label="Quality filter field">
<label class="label" for="quality-filter">Match quality</label>
<select class="select"
id="quality-filter"
name="quality"
aria-label="Filter by match quality">
<option class="option" value="">Any</option>
<option class="option" value="definite" {% if filter_quality == "definite" %}selected{% endif %}>Definite</option>
<option class="option" value="probable" {% if filter_quality == "probable" %}selected{% endif %}>Probable</option>
<option class="option" value="possible" {% if filter_quality == "possible" %}selected{% endif %}>Possible</option>
<option class="option" value="unlikely" {% if filter_quality == "unlikely" %}selected{% endif %}>Unlikely</option>
</select>
</div>
</form>
{% if items | length == 0 %}
<div class="alert" role="alert" aria-label="Empty review queue" data-type="info">
No review items match the current filter.
</div>
{% else %}
<table class="data-table" aria-label="Review queue items">
<thead class="data-table-head">
<tr class="data-table-row">
<th class="data-table-th" scope="col">Candidate A</th>
<th class="data-table-th" scope="col">Candidate B</th>
<th class="data-table-th" scope="col">Score</th>
<th class="data-table-th" scope="col">Quality</th>
<th class="data-table-th" scope="col">Detected</th>
<th class="data-table-th" scope="col">Actions</th>
</tr>
</thead>
<tbody class="data-table-body" id="review-queue-tbody">
{% for item in items %}
<tr class="data-table-row">
<td class="data-table-td">
<a href="/{{ entity_plural }}/{{ item.a_id }}">{{ item.a_label }}</a>
</td>
<td class="data-table-td">
<a href="/{{ entity_plural }}/{{ item.b_id }}">{{ item.b_label }}</a>
</td>
<td class="data-table-td">
<meter class="meter"
aria-label="Match score {{ item.score_pct }} percent"
min="0"
max="100"
low="50"
high="85"
optimum="100"
value="{{ item.score_pct }}">{{ item.score_pct }}%</meter>
<div><strong>{{ item.score }}</strong></div>
{% if item.breakdown %}
<details class="details" aria-label="Score breakdown">
<summary>Breakdown</summary>
<ol class="summary-list" aria-label="Per-component scores">
{% for k, v in item.breakdown %}
<li class="summary-list-item">
<dl>
<dt>{{ k }}</dt>
<dd>{{ v }}</dd>
</dl>
</li>
{% endfor %}
</ol>
</details>
{% endif %}
</td>
<td class="data-table-td">
{% if item.quality == "definite" %}
<span class="badge" data-type="success" aria-label="Definite match">Definite</span>
{% elif item.quality == "probable" %}
<span class="badge" data-type="warning" aria-label="Probable match">Probable</span>
{% elif item.quality == "possible" %}
<span class="badge" data-type="info" aria-label="Possible match">Possible</span>
{% else %}
<span class="badge" data-type="error" aria-label="Unlikely match">Unlikely</span>
{% endif %}
</td>
<td class="data-table-td">
<span class="hint" aria-label="Detection method">{{ item.detection_method }}</span>
<br>
<time datetime="{{ item.created_at }}">{{ item.created_at }}</time>
</td>
<td class="data-table-td">
<a class="button"
href="/{{ entity_plural }}/compare?a={{ item.a_id }}&b={{ item.b_id }}&review_id={{ item.id }}"
aria-label="Open side-by-side comparison">Compare</a>
<button class="button"
type="button"
aria-label="Merge candidates"
onclick="document.getElementById('merge-confirm-{{ item.id }}').showModal()">
Merge
</button>
<button class="action-bar-button"
type="button"
aria-label="Reject as not a duplicate"
hx-post="/api/{{ entity_plural }}/review-queue/{{ item.id }}/reject"
hx-target="closest tr"
hx-swap="outerHTML"
onclick="lily.toast('Marked as not a duplicate', 'info')">
Reject
</button>
<dialog id="merge-confirm-{{ item.id }}"
class="alert-dialog"
role="alertdialog"
aria-modal="true"
aria-labelledby="merge-confirm-{{ item.id }}-title"
aria-describedby="merge-confirm-{{ item.id }}-body">
<h2 id="merge-confirm-{{ item.id }}-title">Merge these {{ entity_plural }}?</h2>
<p id="merge-confirm-{{ item.id }}-body">
This will merge <strong>{{ item.b_label }}</strong> into
<strong>{{ item.a_label }}</strong> as the surviving record.
Match score: <strong>{{ item.score }}</strong> ({{ item.quality }}).
This action is reversible from the audit log.
</p>
<form method="dialog" class="form" aria-label="Confirm merge">
<button class="button" type="submit" value="cancel" aria-label="Cancel merge">Cancel</button>
<button class="button"
type="submit"
value="confirm"
aria-label="Confirm merge"
hx-post="/api/{{ entity_plural }}/merge"
hx-vals='{"main_id": "{{ item.a_id }}", "duplicate_id": "{{ item.b_id }}", "review_id": "{{ item.id }}"}'
hx-target="closest tr"
hx-swap="outerHTML"
onclick="lily.toast('Merge completed', 'success')">Merge</button>
</form>
</dialog>
</td>
</tr>
{% endfor %}
</tbody>
</table>
{% if pagination and pagination.total_pages > 1 %}
<nav class="pagination-nav" aria-label="Review queue pagination">
<ol class="pagination-list" aria-label="Page links">
{% if pagination.has_prev %}
<li class="pagination-list-item">
<a class="pagination-link"
href="/{{ entity_plural }}/review-queue?page={{ pagination.page - 1 }}"
aria-label="Previous page">Previous</a>
</li>
{% endif %}
{% for p in pagination.pages %}
<li class="pagination-list-item">
{% if p == pagination.page %}
<span aria-current="page" aria-label="Page {{ p }}, current">{{ p }}</span>
{% else %}
<a class="pagination-link"
href="/{{ entity_plural }}/review-queue?page={{ p }}"
aria-label="Page {{ p }}">{{ p }}</a>
{% endif %}
</li>
{% endfor %}
{% if pagination.has_next %}
<li class="pagination-list-item">
<a class="pagination-link"
href="/{{ entity_plural }}/review-queue?page={{ pagination.page + 1 }}"
aria-label="Next page">Next</a>
</li>
{% endif %}
</ol>
</nav>
{% endif %}
{% endif %}
</section>
{% endblock content %}