chore: remove dead PDF artifact imports and clean backup artifacts

This commit is contained in:
Sean McElwain 2026-04-28 22:48:37 -05:00
parent 0617ab58c4
commit c19753ec04
9 changed files with 0 additions and 1449 deletions

View File

@ -20,8 +20,6 @@ from pypdf import PdfReader
from app.core.storage_settings import get_default_save_root from app.core.storage_settings import get_default_save_root
from app.db.deps import get_db from app.db.deps import get_db
from app.logic.document_outputs import ( from app.logic.document_outputs import (
create_field_enriched_pdf_version,
create_ocr_corrected_pdf_version,
save_field_enriched_pdf_current, save_field_enriched_pdf_current,
save_ocr_corrected_pdf_current, save_ocr_corrected_pdf_current,
) )

View File

@ -1,383 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>{{ document.document_id }}</title>
<style>
body { font-family: sans-serif; margin: 1rem; }
textarea { font-family: monospace; }
input[type="text"], input[type="date"] { padding: 0.25rem; }
.editor-wrap {
display: flex;
align-items: flex-start;
gap: 0.5rem;
}
.line-numbers {
font-family: monospace;
white-space: pre;
text-align: right;
color: #666;
user-select: none;
padding-top: 2px;
min-width: 3rem;
}
.line-warning {
color: #8a5a00;
font-weight: 600;
}
.error-box {
background: #ffe8e8;
color: #8b0000;
padding: 0.75rem;
border: 1px solid #cc9999;
margin-bottom: 1rem;
}
.split-layout {
display: flex;
align-items: flex-start;
gap: 1.5rem;
}
.preview-pane {
flex: 1 1 52%;
min-width: 420px;
}
.editor-pane {
flex: 1 1 48%;
min-width: 420px;
}
.preview-box {
border: 1px solid #ccc;
padding: 0.5rem;
background: #fafafa;
}
.preview-box iframe,
.preview-box img {
width: 100%;
height: 900px;
border: none;
background: white;
}
.section-box {
margin-bottom: 1.5rem;
}
table { border-collapse: collapse; width: 100%; margin-bottom: 1rem; }
th, td { border: 1px solid #ccc; padding: 0.4rem; text-align: left; vertical-align: top; }
th { background: #f3f3f3; }
@media (max-width: 1200px) {
.split-layout {
flex-direction: column;
}
.preview-pane,
.editor-pane {
min-width: 0;
width: 100%;
}
.preview-box iframe,
.preview-box img {
height: 700px;
}
}
</style>
</head>
<body>
<p>
<a href="/documents/">Back to documents</a> |
<a href="/queue/">Open review queue</a> |
<a href="/trash/">Open trash</a>
</p>
<h1>{{ document.document_id }}</h1>
{% if error == "line_count_mismatch" %}
<div class="error-box">
Could not save reviewed OCR because line count did not match OCR layout.
Expected {{ error_expected }}, got {{ error_actual }}.
</div>
{% elif error == "save_ocr_corrected_failed" %}
<div class="error-box">
Could not save OCR-corrected PDF. Check that reviewed OCR line count matches raw OCR line count.
</div>
{% elif error == "rerun_ocr_failed" %}
<div class="error-box">
OCR rerun failed.
</div>
{% elif error == "save_field_enriched_failed" %}
<div class="error-box">
Could not save field-enriched PDF.
</div>
{% endif %}
<form method="post" action="/documents/{{ document.document_id }}/move-to-trash" style="margin-bottom: 1rem;">
<button type="submit">Move to trash</button>
</form>
<div class="section-box">
<h2>Document metadata</h2>
<ul>
<li>Type: {{ document.document_type }}</li>
<li>Source path: {{ document.source_path }}</li>
<li>Current path: {{ document.current_path }}</li>
<li>Share path: {{ document.share_path or "" }}</li>
<li>App URL: <a href="{{ app_url }}">{{ app_url }}</a></li>
<li>Original filename: {{ document.original_filename }}</li>
<li>Canonical filename: {{ document.canonical_filename }}</li>
<li>MIME type: {{ document.mime_type }}</li>
<li>File size: {{ document.file_size }}</li>
<li>Page count: {{ document.page_count }}</li>
<li>Storage status: {{ document.storage_status }}</li>
<li>Review status: {{ document.review_status }}</li>
<li>Created at: {{ document.created_at }}</li>
<li>Updated at: {{ document.updated_at }}</li>
</ul>
</div>
<div class="section-box">
<h2>Saved PDF outputs</h2>
<form method="post" action="/documents/{{ document.document_id }}/save-ocr-corrected-pdf" style="display:inline;">
<button type="submit">Save OCR-corrected PDF</button>
</form>
<form method="post" action="/documents/{{ document.document_id }}/save-field-enriched-pdf" style="display:inline; margin-left: 1rem;">
<button type="submit">Save field-enriched PDF</button>
</form>
</div>
<div class="split-layout">
<div class="preview-pane">
<div class="section-box">
<h2>Document preview</h2>
<div class="preview-box">
{% if file_url %}
{% if document.mime_type == "application/pdf" %}
<iframe src="{{ file_url }}"></iframe>
{% elif document.mime_type in ["image/jpeg", "image/png"] %}
<img src="{{ file_url }}" alt="Document image">
{% else %}
<p><a href="{{ file_url }}" target="_blank">Open file</a></p>
{% endif %}
{% else %}
<p>No preview available.</p>
{% endif %}
</div>
</div>
<div class="section-box">
<h2>Document versions</h2>
{% if document.versions %}
<table>
<thead>
<tr>
<th>Version</th>
<th>Type</th>
<th>Path</th>
<th>Created</th>
<th>Notes</th>
</tr>
</thead>
<tbody>
{% for version in document.versions %}
<tr>
<td>v{{ version.version_number }}</td>
<td>{{ version.version_type }}</td>
<td>{{ version.file_path }}</td>
<td>{{ version.created_at }}</td>
<td>{{ version.notes or "" }}</td>
</tr>
{% endfor %}
</tbody>
</table>
{% else %}
<p>No versions found.</p>
{% endif %}
</div>
</div>
<div class="editor-pane">
<div class="section-box">
<h2>Raw OCR</h2>
<form method="post" action="/documents/{{ document.document_id }}/rerun-ocr">
<button type="submit">Re-run OCR</button>
</form>
{% if raw_ocr %}
<p>
<strong>Text version:</strong> v{{ raw_ocr.version_number }}<br>
<strong>OCR engine:</strong> {{ raw_ocr.ocr_engine or "unknown" }}<br>
<strong>OCR engine version:</strong> {{ raw_ocr.ocr_engine_version or "unknown" }}<br>
<strong>Rerun source:</strong> {{ raw_ocr.rerun_source or "unknown" }}<br>
<strong>Quality score:</strong> {{ raw_ocr.quality_score if raw_ocr.quality_score is not none else "not scored yet" }}<br>
<strong>Quality flags:</strong> {{ raw_ocr.quality_flags if raw_ocr.quality_flags else [] }}<br>
<strong>Quality note:</strong> {{ raw_ocr.quality_note or "" }}
</p>
{% else %}
<p>No raw OCR text found.</p>
{% endif %}
</div>
<div class="section-box">
<h2>Reviewed OCR</h2>
{% if reviewed_ocr %}
<p>
Current reviewed version saved at {{ reviewed_ocr.created_at }} —
v{{ reviewed_ocr.version_number }}
</p>
{% else %}
<p>No reviewed OCR saved yet.</p>
{% endif %}
<p>
Expected OCR lines: <span id="expected-lines">{{ expected_line_count }}</span><br>
Current editor lines: <span id="actual-lines">{{ actual_line_count }}</span>
<br><span id="line-warning" class="line-warning" {% if expected_line_count == actual_line_count %}style="display:none;"{% endif %}>
Line count mismatch may affect corrected PDF layout.
</span>
</p>
<form method="post" action="/documents/{{ document.document_id }}/review-text">
<div>
<label for="reviewed_text">Edit reviewed OCR text (one line per OCR line):</label>
</div>
<div class="editor-wrap">
<div class="line-numbers" id="line-numbers">{% for n in line_numbers %}{{ n }}
{% endfor %}</div>
<textarea id="reviewed_text" name="reviewed_text" rows="{{ [actual_line_count + 2, 20]|max }}" cols="90">{{ review_text_value }}</textarea>
</div>
<h3>Quality flags</h3>
<div>
{% for flag in quality_flag_options %}
<label style="display:block;">
<input
type="checkbox"
name="quality_flags"
value="{{ flag }}"
{% if flag in current_quality_flags %}checked{% endif %}
>
{{ flag }}
</label>
{% endfor %}
</div>
<h3>Quality note</h3>
<div>
<textarea id="quality_note" name="quality_note" rows="4" cols="90">{{ current_quality_note }}</textarea>
</div>
<div style="margin-top: 1rem;">
<button type="submit" id="save-reviewed-btn">Save reviewed OCR</button>
</div>
</form>
</div>
<div class="section-box">
<h2>Extracted fields</h2>
{% if current_extracted %}
<p>Current extracted fields last updated at {{ current_extracted.updated_at }}</p>
{% else %}
<p>No extracted fields saved yet.</p>
{% endif %}
<form method="get" action="/documents/{{ document.document_id }}">
<input type="hidden" name="autofill_extracted" value="1">
<button type="submit">Auto-extract fields</button>
</form>
<form method="post" action="/documents/{{ document.document_id }}/save-extracted-fields" style="margin-top: 1rem;">
<div>
<label>Merchant raw:</label><br>
<input type="text" name="merchant_raw" value="{{ extracted_form.merchant_raw }}" size="80">
</div>
<div>
<label>Merchant normalized:</label><br>
<input type="text" name="merchant_normalized" value="{{ extracted_form.merchant_normalized }}" size="80">
</div>
<div>
<label>Transaction date:</label><br>
<input type="date" name="transaction_date" value="{{ extracted_form.transaction_date }}">
</div>
<div>
<label>Transaction time:</label><br>
<input type="text" name="transaction_time" value="{{ extracted_form.transaction_time }}" size="20">
</div>
<div>
<label>Subtotal:</label><br>
<input type="text" name="subtotal" value="{{ extracted_form.subtotal }}" size="20">
</div>
<div>
<label>Tax:</label><br>
<input type="text" name="tax" value="{{ extracted_form.tax }}" size="20">
</div>
<div>
<label>Total:</label><br>
<input type="text" name="total" value="{{ extracted_form.total }}" size="20">
</div>
<div>
<label>Currency:</label><br>
<input type="text" name="currency" value="{{ extracted_form.currency }}" size="10">
</div>
<div>
<label>Payment method:</label><br>
<input type="text" name="payment_method" value="{{ extracted_form.payment_method }}" size="40">
</div>
<div>
<label>Receipt number:</label><br>
<input type="text" name="receipt_number" value="{{ extracted_form.receipt_number }}" size="40">
</div>
<div>
<label>Location:</label><br>
<input type="text" name="location" value="{{ extracted_form.location }}" size="80">
</div>
<div>
<label>Counterparty:</label><br>
<input type="text" name="counterparty" value="{{ extracted_form.counterparty }}" size="80">
</div>
<div>
<label>Extra JSON:</label><br>
<textarea name="extra_json" rows="8" cols="90">{{ extracted_form.extra_json }}</textarea>
</div>
<div style="margin-top: 1rem;">
<button type="submit">Save extracted fields</button>
</div>
</form>
</div>
</div>
</div>
<script>
const textarea = document.getElementById("reviewed_text");
const expectedLines = parseInt(document.getElementById("expected-lines").textContent || "0", 10);
const actualLinesEl = document.getElementById("actual-lines");
const warningEl = document.getElementById("line-warning");
const saveBtn = document.getElementById("save-reviewed-btn");
const lineNumbersEl = document.getElementById("line-numbers");
function countLines(text) {
if (text.length === 0) return 0;
return text.split('\\n').length;
}
function rebuildLineNumbers(lineCount) {
let nums = "";
for (let i = 1; i <= lineCount; i++) {
nums += i + "\\n";
}
lineNumbersEl.textContent = nums;
}
function updateEditorState() {
const actual = countLines(textarea.value);
actualLinesEl.textContent = actual.toString();
rebuildLineNumbers(Math.max(actual, expectedLines));
const mismatch = expectedLines > 0 && actual !== expectedLines;
warningEl.style.display = mismatch ? "inline" : "none";
saveBtn.disabled = mismatch;
}
textarea.addEventListener("input", updateEditorState);
updateEditorState();
</script>
</body>
</html>

View File

@ -1,362 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>{{ document.document_id }}</title>
<link rel="stylesheet" href="/static/app.css">
</head>
<body>
<div class="app-shell" id="app-shell">
<aside class="sidebar">
<div class="sidebar-top">
<div class="sidebar-toggle" id="menu-toggle" aria-label="Toggle navigation" role="button" tabindex="0">
<span></span><span></span><span></span>
</div>
<div class="brand">Document Processor</div>
</div>
<div class="sidebar-section-title">Workspace</div>
<nav class="nav-list">
<a class="nav-link active" href="/documents/" title="Documents"><span class="nav-link-short">D</span><span class="nav-link-text">Documents</span></a>
<a class="nav-link" href="/line-items/" title="Line Items"><span class="nav-link-short">L</span><span class="nav-link-text">Line Items</span></a>
<a class="nav-link" href="/queue/" title="Review Queue"><span class="nav-link-short">Q</span><span class="nav-link-text">Review Queue</span></a>
<a class="nav-link" href="/trash/" title="Trash"><span class="nav-link-short">T</span><span class="nav-link-text">Trash</span></a>
<a class="nav-link" href="/ingest/" title="Ingest"><span class="nav-link-short">I</span><span class="nav-link-text">Ingest</span></a>
</nav>
</aside>
<main class="main">
{% if error == "line_count_mismatch" %}
<div class="error-box">
Could not save reviewed OCR because line count did not match OCR layout.
Expected {{ error_expected }}, got {{ error_actual }}.
</div>
{% elif error == "save_ocr_corrected_failed" %}
<div class="error-box">
Could not save OCR-corrected PDF. Check that reviewed OCR line count matches raw OCR line count.
</div>
{% elif error == "rerun_ocr_failed" %}
<div class="error-box">OCR rerun failed.</div>
{% elif error == "save_field_enriched_failed" %}
<div class="error-box">Could not save field-enriched PDF.</div>
{% endif %}
<div class="detail-sticky-header">
<div class="topbar">
<div>
<h1 class="page-title">{{ document.document_id }}</h1>
<p class="page-subtitle">{{ document.original_filename or document.canonical_filename or document.document_type }}</p>
</div>
<div class="badges">
<span class="badge {% if document.review_status == 'reviewed' %}reviewed{% else %}pending{% endif %}">{{ document.review_status }}</span>
<span class="badge">{{ document.document_type }}</span>
<span class="badge">{{ document.mime_type }}</span>
</div>
</div>
<div class="card" style="margin-bottom: 0;">
<div class="button-row">
<form method="post" action="/documents/{{ document.document_id }}/rerun-ocr">
<button type="submit">Re-run OCR</button>
</form>
<form method="post" action="/documents/{{ document.document_id }}/save-ocr-corrected-pdf">
<button class="primary" type="submit">Save OCR-corrected PDF</button>
</form>
<form method="post" action="/documents/{{ document.document_id }}/save-field-enriched-pdf">
<button type="submit">Save field-enriched PDF</button>
</form>
<form method="post" action="/documents/{{ document.document_id }}/move-to-trash">
<button class="danger" type="submit">Move to trash</button>
</form>
</div>
<div class="queue-nav-row">
{% if prev_doc %}
<a class="button-link" href="/documents/{{ prev_doc.document_id }}">← Previous</a>
{% endif %}
{% if next_doc %}
<a class="button-link" href="/documents/{{ next_doc.document_id }}">Next →</a>
{% endif %}
{% if next_ocr_doc %}
<a class="button-link" href="/documents/{{ next_ocr_doc.document_id }}">Next OCR review</a>
{% endif %}
{% if next_fields_doc %}
<a class="button-link" href="/documents/{{ next_fields_doc.document_id }}">Next field extraction</a>
{% endif %}
</div>
</div>
</div>
<div class="workspace-grid">
<section>
<div class="card preview-card">
<h2 class="card-title">Document preview</h2>
{% if file_url %}
{% if document.mime_type == "application/pdf" %}
<iframe class="preview-frame" src="{{ file_url }}"></iframe>
{% elif document.mime_type in ["image/jpeg", "image/png"] %}
<img class="preview-image" src="{{ file_url }}" alt="Document image">
{% else %}
<p><a href="{{ file_url }}" target="_blank">Open file</a></p>
{% endif %}
{% else %}
<p class="empty-state">No preview available.</p>
{% endif %}
</div>
</section>
<section>
<div class="card">
<div class="right-pane-tabs">
<button class="tab-button{% if active_tab == 'ocr-review' %} active{% endif %}" type="button" data-tab="ocr-review">OCR Review</button>
<button class="tab-button{% if active_tab == 'extracted-fields' %} active{% endif %}" type="button" data-tab="extracted-fields">Extracted Fields</button>
<button class="tab-button{% if active_tab == 'versions' %} active{% endif %}" type="button" data-tab="versions">Versions</button>
<button class="tab-button{% if active_tab == 'raw-ocr' %} active{% endif %}" type="button" data-tab="raw-ocr">Raw OCR</button>
</div>
<div class="tab-panel{% if active_tab == 'ocr-review' %} active{% endif %}" data-panel="ocr-review">
<h2 class="card-title">Reviewed OCR</h2>
{% if reviewed_ocr %}
<p>Current reviewed version saved at {{ reviewed_ocr.created_at }} — v{{ reviewed_ocr.version_number }}</p>
{% else %}
<p class="empty-state">No reviewed OCR saved yet.</p>
{% endif %}
<p>
Expected OCR lines: <span id="expected-lines">{{ expected_line_count }}</span><br>
Current editor lines: <span id="actual-lines">{{ actual_line_count }}</span><br>
<span id="line-warning" class="line-warning" {% if expected_line_count == actual_line_count %}style="display:none;"{% endif %}>
Line count mismatch may affect corrected PDF layout.
</span>
</p>
<form method="post" action="/documents/{{ document.document_id }}/review-text">
<div class="form-field full">
<label for="reviewed_text">Edit reviewed OCR text (one line per OCR line)</label>
<div class="editor-wrap">
<pre class="line-numbers" id="line-numbers">{% for n in line_numbers %}{{ n }}
{% endfor %}</pre>
<textarea id="reviewed_text" name="reviewed_text" rows="34" spellcheck="false">{{ review_text_value }}</textarea>
</div>
</div>
<div class="form-field full">
<label>Quality flags</label>
<div>
{% for flag in quality_flag_options %}
<label style="display:block; margin-bottom: 0.25rem;">
<input type="checkbox" name="quality_flags" value="{{ flag }}" {% if flag in current_quality_flags %}checked{% endif %}>
{{ flag }}
</label>
{% endfor %}
</div>
</div>
<div class="form-field full">
<label for="quality_note">Quality note</label>
<textarea id="quality_note" name="quality_note" rows="4">{{ current_quality_note }}</textarea>
</div>
<div class="button-row">
<button class="primary" type="submit" id="save-reviewed-btn">Save reviewed OCR</button>
</div>
</form>
</div>
<div class="tab-panel{% if active_tab == 'extracted-fields' %} active{% endif %}" data-panel="extracted-fields">
<h2 class="card-title">Extracted fields</h2>
{% if current_extracted %}
<p>Current extracted fields last updated at {{ current_extracted.updated_at }}</p>
{% else %}
<p class="empty-state">No extracted fields saved yet.</p>
{% endif %}
<form method="get" action="/documents/{{ document.document_id }}">
<input type="hidden" name="autofill_extracted" value="1">
<input type="hidden" name="tab" value="extracted-fields">
<div class="button-row">
<button type="submit">Auto-extract fields</button>
</div>
</form>
<form method="post" action="/documents/{{ document.document_id }}/save-extracted-fields" style="margin-top: 1rem;">
<div class="form-grid">
<div class="form-field"><label>Merchant raw</label><input type="text" name="merchant_raw" value="{{ extracted_form.merchant_raw }}"></div>
<div class="form-field"><label>Merchant normalized</label><input type="text" name="merchant_normalized" value="{{ extracted_form.merchant_normalized }}"></div>
<div class="form-field"><label>Transaction date</label><input type="date" name="transaction_date" value="{{ extracted_form.transaction_date }}"></div>
<div class="form-field"><label>Transaction time</label><input type="text" name="transaction_time" value="{{ extracted_form.transaction_time }}"></div>
<div class="form-field"><label>Subtotal</label><input type="text" name="subtotal" value="{{ extracted_form.subtotal }}"></div>
<div class="form-field"><label>Tax</label><input type="text" name="tax" value="{{ extracted_form.tax }}"></div>
<div class="form-field"><label>Total</label><input type="text" name="total" value="{{ extracted_form.total }}"></div>
<div class="form-field"><label>Currency</label><input type="text" name="currency" value="{{ extracted_form.currency }}"></div>
<div class="form-field"><label>Payment method</label><input type="text" name="payment_method" value="{{ extracted_form.payment_method }}"></div>
<div class="form-field"><label>Reference number</label><input type="text" name="receipt_number" value="{{ extracted_form.receipt_number }}"></div>
<div class="form-field full"><label>Location</label><input type="text" name="location" value="{{ extracted_form.location }}"></div>
<div class="form-field full"><label>Counterparty</label><input type="text" name="counterparty" value="{{ extracted_form.counterparty }}"></div>
<div class="form-field full"><label>Extra JSON</label><textarea name="extra_json" rows="8">{{ extracted_form.extra_json }}</textarea></div>
</div>
<div class="button-row" style="margin-top: 1rem;">
<button class="primary" type="submit">Save extracted fields</button>
</div>
</form>
</div>
<div class="tab-panel{% if active_tab == 'versions' %} active{% endif %}" data-panel="versions">
<h2 class="card-title">Document versions</h2>
{% if document.versions %}
<div class="table-wrap">
<table>
<thead>
<tr>
<th>Version</th>
<th>Type</th>
<th>Path</th>
<th>Created</th>
<th>Notes</th>
</tr>
</thead>
<tbody>
{% for version in document.versions %}
<tr>
<td>v{{ version.version_number }}</td>
<td>{{ version.version_type }}</td>
<td>{{ version.file_path }}</td>
<td>{{ version.created_at }}</td>
<td>{{ version.notes or "" }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% else %}
<p class="empty-state">No versions found.</p>
{% endif %}
</div>
<div class="tab-panel{% if active_tab == 'raw-ocr' %} active{% endif %}" data-panel="raw-ocr">
<h2 class="card-title">Raw OCR</h2>
{% if raw_ocr %}
<div class="meta-grid">
<div class="meta-item"><span class="meta-label">Text version</span>v{{ raw_ocr.version_number }}</div>
<div class="meta-item"><span class="meta-label">OCR engine</span>{{ raw_ocr.ocr_engine or "unknown" }}</div>
<div class="meta-item"><span class="meta-label">Engine version</span>{{ raw_ocr.ocr_engine_version or "unknown" }}</div>
<div class="meta-item"><span class="meta-label">Rerun source</span>{{ raw_ocr.rerun_source or "unknown" }}</div>
<div class="meta-item"><span class="meta-label">Quality score</span>{{ raw_ocr.quality_score if raw_ocr.quality_score is not none else "not scored yet" }}</div>
<div class="meta-item"><span class="meta-label">Quality note</span>{{ raw_ocr.quality_note or "" }}</div>
</div>
<p><strong>Quality flags:</strong> {{ raw_ocr.quality_flags if raw_ocr and raw_ocr.quality_flags else [] }}</p>
<pre class="codeblock">{{ raw_ocr.text_content }}</pre>
{% else %}
<p class="empty-state">No raw OCR text found.</p>
{% endif %}
</div>
</div>
</section>
</div>
<div class="card">
<h2 class="card-title">Metadata</h2>
<div class="meta-grid">
<div class="meta-item"><span class="meta-label">Type</span>{{ document.document_type }}</div>
<div class="meta-item"><span class="meta-label">Review status</span>{{ document.review_status }}</div>
<div class="meta-item"><span class="meta-label">Source path</span>{{ document.source_path }}</div>
<div class="meta-item"><span class="meta-label">Current path</span>{{ document.current_path }}</div>
<div class="meta-item"><span class="meta-label">Original filename</span>{{ document.original_filename }}</div>
<div class="meta-item"><span class="meta-label">Canonical filename</span>{{ document.canonical_filename }}</div>
<div class="meta-item"><span class="meta-label">MIME type</span>{{ document.mime_type }}</div>
<div class="meta-item"><span class="meta-label">File size</span>{{ document.file_size }}</div>
<div class="meta-item"><span class="meta-label">Page count</span>{{ document.page_count }}</div>
<div class="meta-item"><span class="meta-label">Share path</span>{{ document.share_path or "" }}</div>
<div class="meta-item"><span class="meta-label">Created at</span>{{ document.created_at }}</div>
<div class="meta-item"><span class="meta-label">Updated at</span>{{ document.updated_at }}</div>
</div>
</div>
</main>
</div>
<script>
(function () {
const appShell = document.getElementById("app-shell");
const menuToggle = document.getElementById("menu-toggle");
if (appShell && menuToggle) {
menuToggle.addEventListener("click", function () {
appShell.classList.toggle("nav-open");
});
menuToggle.addEventListener("keydown", function (e) {
if (e.key === "Enter" || e.key === " ") {
e.preventDefault();
appShell.classList.toggle("nav-open");
}
});
}
const tabButtons = document.querySelectorAll("[data-tab]");
const tabPanels = document.querySelectorAll("[data-panel]");
function activateTab(target) {
tabButtons.forEach(function (b) {
b.classList.toggle("active", b.getAttribute("data-tab") === target);
});
tabPanels.forEach(function (p) {
p.classList.toggle("active", p.getAttribute("data-panel") === target);
});
}
tabButtons.forEach(function (btn) {
btn.addEventListener("click", function () {
const target = btn.getAttribute("data-tab");
activateTab(target);
const url = new URL(window.location.href);
url.searchParams.set("tab", target);
window.history.replaceState({}, "", url.toString());
});
});
const textarea = document.getElementById("reviewed_text");
const expectedLinesEl = document.getElementById("expected-lines");
const actualLinesEl = document.getElementById("actual-lines");
const warningEl = document.getElementById("line-warning");
const saveBtn = document.getElementById("save-reviewed-btn");
const lineNumbersEl = document.getElementById("line-numbers");
if (textarea && expectedLinesEl && actualLinesEl && warningEl && saveBtn && lineNumbersEl) {
const expectedLines = parseInt(expectedLinesEl.textContent || "0", 10);
function countLines(text) {
if (text.length === 0) return 0;
return text.split('\n').length;
}
function rebuildLineNumbers(lineCount) {
let nums = "";
for (let i = 1; i <= lineCount; i++) nums += i + "\n";
lineNumbersEl.textContent = nums;
}
function syncScroll() {
lineNumbersEl.scrollTop = textarea.scrollTop;
}
function updateEditorState() {
const actual = countLines(textarea.value);
actualLinesEl.textContent = actual.toString();
rebuildLineNumbers(Math.max(actual, expectedLines));
const mismatch = expectedLines > 0 && actual !== expectedLines;
warningEl.style.display = mismatch ? "inline" : "none";
saveBtn.disabled = mismatch;
syncScroll();
}
textarea.addEventListener("input", updateEditorState);
textarea.addEventListener("scroll", syncScroll);
updateEditorState();
syncScroll();
}
})();
</script>
</body>
</html>

View File

@ -1,95 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Documents</title>
<link rel="stylesheet" href="/static/app.css">
</head>
<body>
<div class="app-shell" id="app-shell">
<aside class="sidebar">
<div class="sidebar-top">
<div class="sidebar-toggle" id="menu-toggle" aria-label="Toggle navigation" role="button" tabindex="0">
<span></span><span></span><span></span>
</div>
<div class="brand">Document Processor</div>
</div>
<div class="sidebar-section-title">Workspace</div>
<nav class="nav-list">
<a class="nav-link active" href="/documents/" title="Documents"><span class="nav-link-short">D</span><span class="nav-link-text">Documents</span></a>
<a class="nav-link" href="/line-items/" title="Line Items"><span class="nav-link-short">L</span><span class="nav-link-text">Line Items</span></a>
<a class="nav-link" href="/queue/" title="Review Queue"><span class="nav-link-short">Q</span><span class="nav-link-text">Review Queue</span></a>
<a class="nav-link" href="/trash/" title="Trash"><span class="nav-link-short">T</span><span class="nav-link-text">Trash</span></a>
<a class="nav-link" href="/ingest/" title="Ingest"><span class="nav-link-short">I</span><span class="nav-link-text">Ingest</span></a>
</nav>
</aside>
<main class="main">
<div class="topbar">
<div>
<h1 class="page-title">Documents</h1>
<p class="page-subtitle">Active documents available for review and processing.</p>
</div>
</div>
<div class="card">
<div class="button-row">
<a class="button-link primary" href="/ingest/">Open ingest</a>
<a class="button-link" href="/queue/">Open review queue</a>
<a class="button-link" href="/trash/">Open trash</a>
</div>
</div>
<div class="card">
<h2 class="card-title">All documents</h2>
{% if documents %}
<div class="table-wrap">
<table>
<thead>
<tr>
<th>Document</th>
<th>Type</th>
<th>Review status</th>
<th>Current path</th>
<th>Updated</th>
</tr>
</thead>
<tbody>
{% for doc in documents %}
<tr>
<td><a href="/documents/{{ doc.document_id }}">{{ doc.document_id }}</a></td>
<td>{{ doc.document_type }}</td>
<td><span class="badge {% if doc.review_status == 'reviewed' %}reviewed{% else %}pending{% endif %}">{{ doc.review_status }}</span></td>
<td>{{ doc.current_path }}</td>
<td>{{ doc.updated_at }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% else %}
<p class="empty-state">No documents found.</p>
{% endif %}
</div>
</main>
</div>
<script>
(function () {
const appShell = document.getElementById("app-shell");
const menuToggle = document.getElementById("menu-toggle");
if (!appShell || !menuToggle) return;
menuToggle.addEventListener("click", function () {
appShell.classList.toggle("nav-open");
});
menuToggle.addEventListener("keydown", function (e) {
if (e.key === "Enter" || e.key === " ") {
e.preventDefault();
appShell.classList.toggle("nav-open");
}
});
})();
</script>
</body>
</html>

View File

@ -1,105 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Ingest</title>
<link rel="stylesheet" href="/static/app.css">
</head>
<body>
<div class="app-shell" id="app-shell">
<aside class="sidebar">
<div class="sidebar-top">
<div class="sidebar-toggle" id="menu-toggle" aria-label="Toggle navigation" role="button" tabindex="0">
<span></span><span></span><span></span>
</div>
<div class="brand">Document Processor</div>
</div>
<div class="sidebar-section-title">Workspace</div>
<nav class="nav-list">
<a class="nav-link" href="/documents/" title="Documents"><span class="nav-link-short">D</span><span class="nav-link-text">Documents</span></a>
<a class="nav-link" href="/line-items/" title="Line Items"><span class="nav-link-short">L</span><span class="nav-link-text">Line Items</span></a>
<a class="nav-link" href="/queue/" title="Review Queue"><span class="nav-link-short">Q</span><span class="nav-link-text">Review Queue</span></a>
<a class="nav-link" href="/trash/" title="Trash"><span class="nav-link-short">T</span><span class="nav-link-text">Trash</span></a>
<a class="nav-link active" href="/ingest/" title="Ingest"><span class="nav-link-short">I</span><span class="nav-link-text">Ingest</span></a>
</nav>
</aside>
<main class="main">
<div class="topbar">
<div>
<h1 class="page-title">Ingest</h1>
<p class="page-subtitle">Upload files or ingest from server-side paths.</p>
</div>
</div>
<div class="card">
<h2 class="card-title">Upload files</h2>
<form method="post" action="/ingest/upload-files" enctype="multipart/form-data">
<div class="form-field full">
<label>Select file(s)</label>
<input type="file" name="uploaded_files" multiple required>
</div>
<div class="button-row" style="margin-top: 1rem;">
<button class="primary" type="submit">Upload and ingest</button>
</div>
</form>
</div>
<div class="card">
<h2 class="card-title">Server-side ingest</h2>
<form method="post" action="/ingest/server-file" style="margin-bottom: 1.25rem;">
<div class="form-field full">
<label>Ingest one server file</label>
<input type="text" name="file_path" placeholder="/mnt/storage/.../file.pdf" required>
</div>
<div class="button-row" style="margin-top: 1rem;">
<button type="submit">Ingest file</button>
</div>
</form>
<form method="post" action="/ingest/server-directory" style="margin-bottom: 1.25rem;">
<div class="form-field full">
<label>Ingest one server directory</label>
<input type="text" name="directory_path" placeholder="/mnt/storage/.../incoming" required>
</div>
<div class="form-field">
<label><input type="checkbox" name="recursive" value="1"> Recursive</label>
</div>
<div class="button-row" style="margin-top: 1rem;">
<button type="submit">Ingest directory</button>
</div>
</form>
<form method="post" action="/ingest/inbox">
<div class="form-field full">
<label>Inbox root</label>
<input type="text" value="{{ inbox_root }}" readonly>
</div>
<div class="button-row" style="margin-top: 1rem;">
<button type="submit">Ingest inbox</button>
</div>
</form>
</div>
</main>
</div>
<script>
(function () {
const appShell = document.getElementById("app-shell");
const menuToggle = document.getElementById("menu-toggle");
if (!appShell || !menuToggle) return;
menuToggle.addEventListener("click", function () {
appShell.classList.toggle("nav-open");
});
menuToggle.addEventListener("keydown", function (e) {
if (e.key === "Enter" || e.key === " ") {
e.preventDefault();
appShell.classList.toggle("nav-open");
}
});
})();
</script>
</body>
</html>

View File

@ -1,161 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Line Items</title>
<link rel="stylesheet" href="/static/app.css">
</head>
<body>
<div class="app-shell" id="app-shell">
<aside class="sidebar">
<div class="sidebar-top">
<div class="sidebar-toggle" id="menu-toggle" aria-label="Toggle navigation" role="button" tabindex="0">
<span></span><span></span><span></span>
</div>
<div class="brand">Document Processor</div>
</div>
<div class="sidebar-section-title">Workspace</div>
<nav class="nav-list">
<a class="nav-link" href="/documents/" title="Documents"><span class="nav-link-short">D</span><span class="nav-link-text">Documents</span></a>
<a class="nav-link active" href="/line-items/" title="Line Items"><span class="nav-link-short">L</span><span class="nav-link-text">Line Items</span></a>
<a class="nav-link" href="/line-items/summary" title="Line Item Summary"><span class="nav-link-short">S</span><span class="nav-link-text">Line Item Summary</span></a>
<a class="nav-link" href="/queue/" title="Review Queue"><span class="nav-link-short">Q</span><span class="nav-link-text">Review Queue</span></a>
<a class="nav-link" href="/trash/" title="Trash"><span class="nav-link-short">T</span><span class="nav-link-text">Trash</span></a>
<a class="nav-link" href="/ingest/" title="Ingest"><span class="nav-link-short">I</span><span class="nav-link-text">Ingest</span></a>
</nav>
</aside>
<main class="main">
<div class="topbar">
<div>
<h1 class="page-title">Line Items</h1>
<p class="page-subtitle">Search extracted purchase lines across documents</p>
</div>
</div>
<div class="card">
<form method="get" action="/line-items/">
<div class="form-grid">
<div class="form-field">
<label for="q">Item contains</label>
<input id="q" type="text" name="q" value="{{ q }}" placeholder="margarita">
</div>
<div class="form-field">
<label for="merchant">Merchant contains</label>
<input id="merchant" type="text" name="merchant" value="{{ merchant }}" placeholder="El Canelo">
</div>
<div class="form-field">
<label for="category">Category</label>
<input id="category" type="text" name="category" value="{{ category }}" placeholder="cocktail">
</div>
<div class="form-field">
<label for="date_from">Date from</label>
<input id="date_from" type="date" name="date_from" value="{{ date_from }}">
</div>
<div class="form-field">
<label for="date_to">Date to</label>
<input id="date_to" type="date" name="date_to" value="{{ date_to }}">
</div>
<div class="form-field">
<label for="rating_min">Min rating</label>
<input id="rating_min" type="text" name="rating_min" value="{{ rating_min }}" placeholder="7">
</div>
<div class="form-field">
<label for="rating_max">Max rating</label>
<input id="rating_max" type="text" name="rating_max" value="{{ rating_max }}" placeholder="10">
</div>
</div>
<div class="button-row" style="margin-top: 1rem;">
<button class="primary" type="submit">Search</button>
<a class="button-link" href="/line-items/">Clear</a>
<a class="button-link" href="/line-items/summary?q={{ q }}">Summary view</a>
</div>
</form>
</div>
<div class="card">
<h2 class="card-title">Results</h2>
{% if rows %}
{% for row in rows %}
<div class="card" style="margin-bottom: 1rem;">
<div class="topbar" style="margin-bottom: 0.75rem;">
<div>
<div class="page-subtitle">{{ row.transaction_date }} · {{ row.merchant }}</div>
<h3 class="card-title" style="margin: 0.2rem 0 0 0;">{{ row.description }}</h3>
<div class="page-subtitle">{{ row.raw_description }}</div>
</div>
<div class="badges">
{% if row.category %}
<span class="badge">{{ row.category }}</span>
{% endif %}
{% if row.quantity %}
<span class="badge">Qty {{ row.quantity }}</span>
{% endif %}
{% if row.line_total %}
<span class="badge">${{ row.line_total }}</span>
{% endif %}
{% if row.confidence %}
<span class="badge">Conf {{ row.confidence }}</span>
{% endif %}
{% if row.quality_rating %}
<span class="badge reviewed">Rating {{ row.quality_rating }}</span>
{% endif %}
</div>
</div>
<form method="post" action="/line-items/{{ row.line_item_id }}/review">
<input type="hidden" name="q" value="{{ q }}">
<input type="hidden" name="merchant" value="{{ merchant }}">
<input type="hidden" name="category" value="{{ category }}">
<input type="hidden" name="date_from" value="{{ date_from }}">
<input type="hidden" name="date_to" value="{{ date_to }}">
<input type="hidden" name="rating_min" value="{{ rating_min }}">
<input type="hidden" name="rating_max" value="{{ rating_max }}">
<div class="form-grid">
<div class="form-field">
<label for="quality_rating_{{ row.line_item_id }}">Quality rating</label>
<input id="quality_rating_{{ row.line_item_id }}" type="text" name="quality_rating" value="{{ row.quality_rating }}" placeholder="e.g. 8.5 or 4/5">
</div>
<div class="form-field full">
<label for="quality_note_{{ row.line_item_id }}">Quality note</label>
<textarea id="quality_note_{{ row.line_item_id }}" name="quality_note" rows="3" placeholder="Taste, portion, texture, service notes...">{{ row.quality_note }}</textarea>
</div>
</div>
<div class="button-row" style="margin-top: 1rem;">
<button class="primary" type="submit">Save rating/note</button>
<a class="button-link" href="/documents/{{ row.document_id }}?tab=extracted-fields">Open document</a>
</div>
</form>
</div>
{% endfor %}
{% else %}
<p class="empty-state">No line items found for the current filters.</p>
{% endif %}
</div>
</main>
</div>
<script>
(function () {
const appShell = document.getElementById("app-shell");
const menuToggle = document.getElementById("menu-toggle");
if (appShell && menuToggle) {
menuToggle.addEventListener("click", function () {
appShell.classList.toggle("nav-open");
});
menuToggle.addEventListener("keydown", function (e) {
if (e.key === "Enter" || e.key === " ") {
e.preventDefault();
appShell.classList.toggle("nav-open");
}
});
}
})();
</script>
</body>
</html>

View File

@ -1,110 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Line Item Summary</title>
<link rel="stylesheet" href="/static/app.css">
</head>
<body>
<div class="app-shell" id="app-shell">
<aside class="sidebar">
<div class="sidebar-top">
<div class="sidebar-toggle" id="menu-toggle" aria-label="Toggle navigation" role="button" tabindex="0">
<span></span><span></span><span></span>
</div>
<div class="brand">Document Processor</div>
</div>
<div class="sidebar-section-title">Workspace</div>
<nav class="nav-list">
<a class="nav-link" href="/documents/" title="Documents"><span class="nav-link-short">D</span><span class="nav-link-text">Documents</span></a>
<a class="nav-link" href="/line-items/" title="Line Items"><span class="nav-link-short">L</span><span class="nav-link-text">Line Items</span></a>
<a class="nav-link active" href="/line-items/summary" title="Line Item Summary"><span class="nav-link-short">S</span><span class="nav-link-text">Line Item Summary</span></a>
<a class="nav-link" href="/queue/" title="Review Queue"><span class="nav-link-short">Q</span><span class="nav-link-text">Review Queue</span></a>
<a class="nav-link" href="/trash/" title="Trash"><span class="nav-link-short">T</span><span class="nav-link-text">Trash</span></a>
<a class="nav-link" href="/ingest/" title="Ingest"><span class="nav-link-short">I</span><span class="nav-link-text">Ingest</span></a>
</nav>
</aside>
<main class="main">
<div class="topbar">
<div>
<h1 class="page-title">Line Item Summary</h1>
<p class="page-subtitle">Aggregate prices and ratings across extracted line items</p>
</div>
</div>
<div class="card">
<form method="get" action="/line-items/summary">
<div class="form-grid">
<div class="form-field">
<label for="q">Item contains</label>
<input id="q" name="q" value="{{ q }}" placeholder="margarita">
</div>
</div>
<div class="button-row" style="margin-top: 1rem;">
<button class="primary" type="submit">Search</button>
<a class="button-link" href="/line-items/summary">Clear</a>
<a class="button-link" href="/line-items/?q={{ q }}">Detailed view</a>
</div>
</form>
</div>
<div class="card">
<h2 class="card-title">Summary Results</h2>
{% if rows %}
<div class="table-wrap">
<table>
<thead>
<tr>
<th>Item</th>
<th>Count</th>
<th>Avg Price</th>
<th>Min Price</th>
<th>Max Price</th>
<th>Rated Count</th>
<th>Avg Rating</th>
</tr>
</thead>
<tbody>
{% for row in rows %}
<tr>
<td>{{ row.item }}</td>
<td>{{ row.count }}</td>
<td>{{ row.avg_price }}</td>
<td>{{ row.min_price }}</td>
<td>{{ row.max_price }}</td>
<td>{{ row.rated_count }}</td>
<td>{{ row.avg_rating }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% else %}
<p class="empty-state">No summary rows found for the current search.</p>
{% endif %}
</div>
</main>
</div>
<script>
(function () {
const appShell = document.getElementById("app-shell");
const menuToggle = document.getElementById("menu-toggle");
if (appShell && menuToggle) {
menuToggle.addEventListener("click", function () {
appShell.classList.toggle("nav-open");
});
menuToggle.addEventListener("keydown", function (e) {
if (e.key === "Enter" || e.key === " ") {
e.preventDefault();
appShell.classList.toggle("nav-open");
}
});
}
})();
</script>
</body>
</html>

View File

@ -1,134 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Review Queue</title>
<link rel="stylesheet" href="/static/app.css">
</head>
<body>
<div class="app-shell" id="app-shell">
<aside class="sidebar">
<div class="sidebar-top">
<div class="sidebar-toggle" id="menu-toggle" aria-label="Toggle navigation" role="button" tabindex="0">
<span></span><span></span><span></span>
</div>
<div class="brand">Document Processor</div>
</div>
<div class="sidebar-section-title">Workspace</div>
<nav class="nav-list">
<a class="nav-link" href="/documents/" title="Documents"><span class="nav-link-short">D</span><span class="nav-link-text">Documents</span></a>
<a class="nav-link" href="/line-items/" title="Line Items"><span class="nav-link-short">L</span><span class="nav-link-text">Line Items</span></a>
<a class="nav-link active" href="/queue/" title="Review Queue"><span class="nav-link-short">Q</span><span class="nav-link-text">Review Queue</span></a>
<a class="nav-link" href="/trash/" title="Trash"><span class="nav-link-short">T</span><span class="nav-link-text">Trash</span></a>
<a class="nav-link" href="/ingest/" title="Ingest"><span class="nav-link-short">I</span><span class="nav-link-text">Ingest</span></a>
</nav>
</aside>
<main class="main">
<div class="topbar">
<div>
<h1 class="page-title">Review Queue</h1>
<p class="page-subtitle">Work through OCR review and field extraction in order.</p>
</div>
</div>
<div class="card">
<div class="button-row">
{% if next_ocr %}
<a class="button-link primary" href="/documents/{{ next_ocr.document_id }}?queue=ocr">Next needing OCR review</a>
{% endif %}
{% if next_fields %}
<a class="button-link" href="/documents/{{ next_fields.document_id }}?queue=fields">Next needing field extraction</a>
{% endif %}
</div>
</div>
<div class="card">
<h2 class="card-title">Needs OCR review ({{ needs_ocr_review|length }})</h2>
{% if needs_ocr_review %}
<div class="table-wrap">
<table>
<thead><tr><th>Document</th><th>Type</th><th>Review status</th><th>Updated</th></tr></thead>
<tbody>
{% for doc in needs_ocr_review %}
<tr>
<td><a href="/documents/{{ doc.document_id }}?queue=ocr">{{ doc.document_id }}</a></td>
<td>{{ doc.document_type }}</td>
<td><span class="badge pending">{{ doc.review_status }}</span></td>
<td>{{ doc.updated_at }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% else %}
<p class="empty-state">No documents currently need OCR review.</p>
{% endif %}
</div>
<div class="card">
<h2 class="card-title">Needs field extraction ({{ needs_field_extraction|length }})</h2>
{% if needs_field_extraction %}
<div class="table-wrap">
<table>
<thead><tr><th>Document</th><th>Type</th><th>Review status</th><th>Updated</th></tr></thead>
<tbody>
{% for doc in needs_field_extraction %}
<tr>
<td><a href="/documents/{{ doc.document_id }}?queue=fields">{{ doc.document_id }}</a></td>
<td>{{ doc.document_type }}</td>
<td><span class="badge reviewed">{{ doc.review_status }}</span></td>
<td>{{ doc.updated_at }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% else %}
<p class="empty-state">No reviewed documents are waiting on field extraction.</p>
{% endif %}
</div>
<div class="card">
<h2 class="card-title">Recently updated</h2>
{% if recently_updated %}
<div class="table-wrap">
<table>
<thead><tr><th>Document</th><th>Type</th><th>Review status</th><th>Current path</th><th>Updated</th></tr></thead>
<tbody>
{% for doc in recently_updated %}
<tr>
<td><a href="/documents/{{ doc.document_id }}?queue=recent">{{ doc.document_id }}</a></td>
<td>{{ doc.document_type }}</td>
<td><span class="badge {% if doc.review_status == 'reviewed' %}reviewed{% else %}pending{% endif %}">{{ doc.review_status }}</span></td>
<td>{{ doc.current_path }}</td>
<td>{{ doc.updated_at }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% endif %}
</div>
</main>
</div>
<script>
(function () {
const appShell = document.getElementById("app-shell");
const menuToggle = document.getElementById("menu-toggle");
if (!appShell || !menuToggle) return;
menuToggle.addEventListener("click", function () {
appShell.classList.toggle("nav-open");
});
menuToggle.addEventListener("keydown", function (e) {
if (e.key === "Enter" || e.key === " ") {
e.preventDefault();
appShell.classList.toggle("nav-open");
}
});
})();
</script>
</body>
</html>

View File

@ -1,97 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Trash</title>
<link rel="stylesheet" href="/static/app.css">
</head>
<body>
<div class="app-shell" id="app-shell">
<aside class="sidebar">
<div class="sidebar-top">
<div class="sidebar-toggle" id="menu-toggle" aria-label="Toggle navigation" role="button" tabindex="0">
<span></span><span></span><span></span>
</div>
<div class="brand">Document Processor</div>
</div>
<div class="sidebar-section-title">Workspace</div>
<nav class="nav-list">
<a class="nav-link" href="/documents/" title="Documents"><span class="nav-link-short">D</span><span class="nav-link-text">Documents</span></a>
<a class="nav-link" href="/line-items/" title="Line Items"><span class="nav-link-short">L</span><span class="nav-link-text">Line Items</span></a>
<a class="nav-link" href="/queue/" title="Review Queue"><span class="nav-link-short">Q</span><span class="nav-link-text">Review Queue</span></a>
<a class="nav-link active" href="/trash/" title="Trash"><span class="nav-link-short">T</span><span class="nav-link-text">Trash</span></a>
<a class="nav-link" href="/ingest/" title="Ingest"><span class="nav-link-short">I</span><span class="nav-link-text">Ingest</span></a>
</nav>
</aside>
<main class="main">
<div class="topbar">
<div>
<h1 class="page-title">Trash</h1>
<p class="page-subtitle">Soft-deleted documents can be restored or removed permanently.</p>
</div>
</div>
<div class="card">
{% if documents %}
<div class="table-wrap">
<table>
<thead>
<tr>
<th>Document</th>
<th>Type</th>
<th>Review status</th>
<th>Trashed at</th>
<th>Current path</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
{% for doc in documents %}
<tr>
<td><a href="/documents/{{ doc.document_id }}">{{ doc.document_id }}</a></td>
<td>{{ doc.document_type }}</td>
<td><span class="badge trashed">{{ doc.review_status }}</span></td>
<td>{{ doc.trashed_at }}</td>
<td>{{ doc.current_path }}</td>
<td>
<div class="button-row">
<form method="post" action="/trash/{{ doc.document_id }}/restore">
<button type="submit">Restore</button>
</form>
<form method="post" action="/trash/{{ doc.document_id }}/delete">
<button class="danger" type="submit">Delete permanently</button>
</form>
</div>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% else %}
<p class="empty-state">Trash is empty.</p>
{% endif %}
</div>
</main>
</div>
<script>
(function () {
const appShell = document.getElementById("app-shell");
const menuToggle = document.getElementById("menu-toggle");
if (!appShell || !menuToggle) return;
menuToggle.addEventListener("click", function () {
appShell.classList.toggle("nav-open");
});
menuToggle.addEventListener("keydown", function (e) {
if (e.key === "Enter" || e.key === " ") {
e.preventDefault();
appShell.classList.toggle("nav-open");
}
});
})();
</script>
</body>
</html>