feat: refine mobile detail tabs and editor layouts

This commit is contained in:
Sean McElwain 2026-04-26 16:04:04 -05:00
parent 98acc6a59b
commit 9387e4166e
3 changed files with 772 additions and 78 deletions

View File

@ -4613,3 +4613,667 @@ table {
} }
} }
/* ===== end tighten metadata panel on mobile (real classes) ===== */ /* ===== end tighten metadata panel on mobile (real classes) ===== */
/* ===== tighten non-OCR detail tabs on mobile ===== */
@media (max-width: 900px) {
.tab-panel[data-panel="extracted-fields"],
.tab-panel[data-panel="additional-fields"],
.tab-panel[data-panel="line-items"],
.tab-panel[data-panel="versions"],
.tab-panel[data-panel="raw-ocr"],
.tab-panel[data-panel="source-options"] {
font-size: 0.8rem !important;
line-height: 1.22 !important;
}
.tab-panel[data-panel="extracted-fields"] .card-title,
.tab-panel[data-panel="additional-fields"] .card-title,
.tab-panel[data-panel="line-items"] .card-title,
.tab-panel[data-panel="versions"] .card-title,
.tab-panel[data-panel="raw-ocr"] .card-title,
.tab-panel[data-panel="source-options"] .card-title {
font-size: 0.98rem !important;
line-height: 1.08 !important;
margin: 0 0 0.3rem 0 !important;
}
.tab-panel[data-panel="extracted-fields"] > p,
.tab-panel[data-panel="additional-fields"] > p,
.tab-panel[data-panel="line-items"] > p,
.tab-panel[data-panel="versions"] > p,
.tab-panel[data-panel="raw-ocr"] > p,
.tab-panel[data-panel="source-options"] > p {
font-size: 0.76rem !important;
line-height: 1.15 !important;
margin: 0.08rem 0 !important;
}
.tab-panel[data-panel="extracted-fields"] .form-grid,
.tab-panel[data-panel="additional-fields"] .form-grid {
gap: 0.42rem !important;
}
.tab-panel[data-panel="extracted-fields"] .form-field,
.tab-panel[data-panel="additional-fields"] .form-field {
margin: 0 !important;
}
.tab-panel[data-panel="extracted-fields"] .form-field label,
.tab-panel[data-panel="additional-fields"] .form-field label,
.tab-panel[data-panel="line-items"] th,
.tab-panel[data-panel="versions"] th,
.tab-panel[data-panel="source-options"] .meta-label {
font-size: 0.72rem !important;
line-height: 1.1 !important;
margin-bottom: 0.14rem !important;
}
.tab-panel[data-panel="extracted-fields"] input,
.tab-panel[data-panel="extracted-fields"] textarea,
.tab-panel[data-panel="extracted-fields"] select,
.tab-panel[data-panel="additional-fields"] input,
.tab-panel[data-panel="additional-fields"] textarea,
.tab-panel[data-panel="additional-fields"] select,
.tab-panel[data-panel="line-items"] input,
.tab-panel[data-panel="line-items"] textarea,
.tab-panel[data-panel="line-items"] select {
font-size: 0.8rem !important;
line-height: 1.2 !important;
padding-top: 0.42rem !important;
padding-bottom: 0.42rem !important;
}
.tab-panel[data-panel="extracted-fields"] textarea,
.tab-panel[data-panel="additional-fields"] textarea {
min-height: 5.5rem !important;
}
.tab-panel[data-panel="extracted-fields"] .button-row,
.tab-panel[data-panel="additional-fields"] .button-row,
.tab-panel[data-panel="line-items"] .button-row,
.tab-panel[data-panel="versions"] .button-row,
.tab-panel[data-panel="raw-ocr"] .button-row,
.tab-panel[data-panel="source-options"] .button-row {
margin-top: 0.35rem !important;
margin-bottom: 0.35rem !important;
gap: 0.24rem !important;
}
.tab-panel[data-panel="extracted-fields"] .button-row button,
.tab-panel[data-panel="additional-fields"] .button-row button,
.tab-panel[data-panel="line-items"] .button-row button,
.tab-panel[data-panel="versions"] .button-row button,
.tab-panel[data-panel="raw-ocr"] .button-row button,
.tab-panel[data-panel="source-options"] .button-row button,
.tab-panel[data-panel="additional-fields"] .button-row .button-link,
.tab-panel[data-panel="source-options"] .button-row .button-link {
font-size: 0.74rem !important;
line-height: 1 !important;
min-height: 1.6rem !important;
height: 1.6rem !important;
padding: 0 0.42rem !important;
border-radius: 999px !important;
}
.tab-panel[data-panel="line-items"] table th,
.tab-panel[data-panel="line-items"] table td,
.tab-panel[data-panel="versions"] table th,
.tab-panel[data-panel="versions"] table td {
padding: 0.22rem !important;
font-size: 0.72rem !important;
line-height: 1.15 !important;
}
.tab-panel[data-panel="raw-ocr"] pre,
.tab-panel[data-panel="raw-ocr"] textarea {
font-size: 0.78rem !important;
line-height: 1.2 !important;
}
.tab-panel[data-panel="source-options"] .meta-grid {
gap: 0.34rem !important;
}
.tab-panel[data-panel="source-options"] .meta-item {
padding: 0.38rem 0.48rem !important;
border-radius: 0.85rem !important;
}
.tab-panel[data-panel="source-options"] .meta-label {
font-size: 0.7rem !important;
margin-bottom: 0.12rem !important;
}
.tab-panel[data-panel="source-options"] .meta-item,
.tab-panel[data-panel="source-options"] .meta-item div,
.tab-panel[data-panel="source-options"] .meta-item span:not(.meta-label) {
font-size: 0.76rem !important;
line-height: 1.18 !important;
}
}
/* ===== end tighten non-OCR detail tabs on mobile ===== */
/* ===== extracted fields header inline action ===== */
@media (max-width: 900px) {
.extracted-fields-header-row {
display: grid !important;
grid-template-columns: minmax(0, 1fr) auto !important;
align-items: center !important;
column-gap: 0.35rem !important;
margin-bottom: 0.18rem !important;
}
.extracted-fields-header-row .card-title {
margin: 0 !important;
white-space: nowrap !important;
overflow: hidden !important;
text-overflow: ellipsis !important;
min-width: 0 !important;
font-size: 0.98rem !important;
line-height: 1.05 !important;
}
.extracted-autofill-inline-form {
margin: 0 !important;
justify-self: end !important;
}
.extracted-autofill-inline-form button {
display: inline-flex !important;
align-items: center !important;
justify-content: center !important;
white-space: nowrap !important;
font-size: 0.72rem !important;
line-height: 1 !important;
font-weight: 500 !important;
min-height: 1.5rem !important;
height: 1.5rem !important;
padding: 0 0.34rem !important;
border-radius: 999px !important;
margin: 0 !important;
}
}
/* ===== end extracted fields header inline action ===== */
/* ===== extracted version line spacing ===== */
@media (max-width: 900px) {
.tab-panel[data-panel="extracted-fields"] > p:first-of-type {
margin-top: 0.16rem !important;
margin-bottom: 0.28rem !important;
line-height: 1.18 !important;
}
.extracted-fields-header-row {
margin-bottom: 0.12rem !important;
}
}
/* ===== end extracted version line spacing ===== */
/* ===== line items header inline action ===== */
@media (max-width: 900px) {
.line-items-header-row {
display: grid !important;
grid-template-columns: minmax(0, 1fr) auto !important;
align-items: center !important;
column-gap: 0.35rem !important;
margin-bottom: 0.12rem !important;
}
.line-items-header-row .card-title {
margin: 0 !important;
white-space: nowrap !important;
overflow: hidden !important;
text-overflow: ellipsis !important;
min-width: 0 !important;
font-size: 0.98rem !important;
line-height: 1.05 !important;
}
.line-items-regenerate-inline-form {
margin: 0 !important;
justify-self: end !important;
}
.line-items-regenerate-inline-form button {
display: inline-flex !important;
align-items: center !important;
justify-content: center !important;
white-space: nowrap !important;
font-size: 0.72rem !important;
line-height: 1 !important;
font-weight: 500 !important;
min-height: 1.5rem !important;
height: 1.5rem !important;
padding: 0 0.34rem !important;
border-radius: 999px !important;
margin: 0 !important;
}
.tab-panel[data-panel="line-items"] > p:first-of-type {
margin-top: 0.16rem !important;
margin-bottom: 0.28rem !important;
line-height: 1.18 !important;
}
}
/* ===== end line items header inline action ===== */
/* ===== additional fields preset controls one row ===== */
@media (max-width: 900px) {
.tab-panel[data-panel="additional-fields"] form[style*="margin-bottom: 1rem;"] .button-row {
display: grid !important;
grid-template-columns: minmax(0, 1fr) auto auto !important;
align-items: center !important;
gap: 0.22rem !important;
margin: 0.18rem 0 0.35rem 0 !important;
}
.tab-panel[data-panel="additional-fields"] form[style*="margin-bottom: 1rem;"] .button-row select {
min-width: 0 !important;
width: 100% !important;
font-size: 0.78rem !important;
line-height: 1.1 !important;
height: 1.7rem !important;
padding: 0 0.45rem !important;
}
.tab-panel[data-panel="additional-fields"] form[style*="margin-bottom: 1rem;"] .button-row button,
.tab-panel[data-panel="additional-fields"] form[style*="margin-bottom: 1rem;"] .button-row .button-link {
display: inline-flex !important;
align-items: center !important;
justify-content: center !important;
white-space: nowrap !important;
font-size: 0.72rem !important;
line-height: 1 !important;
min-height: 1.7rem !important;
height: 1.7rem !important;
padding: 0 0.42rem !important;
border-radius: 999px !important;
margin: 0 !important;
}
}
/* ===== end additional fields preset controls one row ===== */
/* ===== source options dropdown + tighter mobile layout ===== */
@media (max-width: 900px) {
.tab-panel[data-panel="source-options"] .card-title {
margin-bottom: 0.22rem !important;
}
.tab-panel[data-panel="source-options"] > form {
gap: 0.34rem !important;
}
.tab-panel[data-panel="source-options"] .card {
padding: 0.56rem !important;
border-radius: 0.9rem !important;
}
.tab-panel[data-panel="source-options"] h3 {
margin: 0 0 0.2rem 0 !important;
font-size: 0.88rem !important;
line-height: 1.05 !important;
}
.source-options-select-row {
display: grid !important;
grid-template-columns: 4.2rem minmax(0, 1fr) !important;
gap: 0.18rem !important;
align-items: center !important;
}
.source-options-inline-label {
font-size: 0.7rem !important;
line-height: 1.05 !important;
color: #6b7280 !important;
margin: 0 !important;
}
.source-options-select-row select,
.tab-panel[data-panel="source-options"] select {
font-size: 0.74rem !important;
line-height: 1.05 !important;
min-height: 1.6rem !important;
height: 1.6rem !important;
padding: 0 0.4rem !important;
}
.tab-panel[data-panel="source-options"] .button-row {
margin-top: 0.18rem !important;
margin-bottom: 0 !important;
gap: 0.18rem !important;
}
.tab-panel[data-panel="source-options"] .button-row button,
.tab-panel[data-panel="source-options"] .button-row .button-link {
font-size: 0.7rem !important;
line-height: 1 !important;
min-height: 1.5rem !important;
height: 1.5rem !important;
padding: 0 0.36rem !important;
border-radius: 999px !important;
}
}
/* ===== end source options dropdown + tighter mobile layout ===== */
/* ===== metadata card matches source-options styling ===== */
@media (max-width: 900px) {
.meta-grid {
gap: 0.34rem !important;
}
.meta-item {
padding: 0.56rem !important;
border-radius: 0.9rem !important;
}
.meta-label {
font-size: 0.7rem !important;
line-height: 1.05 !important;
margin-bottom: 0.14rem !important;
color: #6b7280 !important;
}
.meta-item,
.meta-item div,
.meta-item span:not(.meta-label) {
font-size: 0.74rem !important;
line-height: 1.18 !important;
}
.meta-grid + .card-title,
h2.card-title + .meta-grid,
.card-title {
margin-bottom: 0.22rem !important;
}
}
/* ===== end metadata card matches source-options styling ===== */
/* ===== metadata card matches source-options styling v2 ===== */
@media (max-width: 900px) {
.tab-panel[data-panel="source-options"] > h2.card-title:last-of-type {
font-size: 0.98rem !important;
line-height: 1.08 !important;
margin: 0.1rem 0 0.22rem 0 !important;
}
.tab-panel[data-panel="source-options"] > .meta-grid {
gap: 0.34rem !important;
margin: 0 !important;
}
.tab-panel[data-panel="source-options"] > .meta-grid > .meta-item {
padding: 0.56rem !important;
border-radius: 0.9rem !important;
margin: 0 !important;
}
.tab-panel[data-panel="source-options"] > .meta-grid > .meta-item > .meta-label {
display: block !important;
font-size: 0.7rem !important;
line-height: 1.05 !important;
margin-bottom: 0.14rem !important;
color: #6b7280 !important;
}
.tab-panel[data-panel="source-options"] > .meta-grid > .meta-item,
.tab-panel[data-panel="source-options"] > .meta-grid > .meta-item > span:not(.meta-label),
.tab-panel[data-panel="source-options"] > .meta-grid > .meta-item > div {
font-size: 0.74rem !important;
line-height: 1.18 !important;
}
}
/* ===== end metadata card matches source-options styling v2 ===== */
/* ===== data reset matches file source box ===== */
@media (max-width: 900px) {
.tab-panel[data-panel="source-options"] .card h3 {
margin: 0 0 0.18rem 0 !important;
font-size: 0.88rem !important;
line-height: 1.05 !important;
}
.tab-panel[data-panel="source-options"] .card:has(select[name="ocr_action"]) {
padding: 0.56rem !important;
}
.tab-panel[data-panel="source-options"] .card:has(select[name="ocr_action"]) > div[style*="display:grid"] {
display: grid !important;
grid-template-columns: 1fr !important;
gap: 0.22rem !important;
}
.tab-panel[data-panel="source-options"] .card:has(select[name="ocr_action"]) > div[style*="display:grid"] > div {
display: grid !important;
grid-template-columns: 7.2rem minmax(0, 1fr) !important;
align-items: center !important;
gap: 0.28rem !important;
margin: 0 !important;
}
.tab-panel[data-panel="source-options"] .card:has(select[name="ocr_action"]) label {
font-size: 0.74rem !important;
line-height: 1.1 !important;
margin: 0 !important;
color: #111827 !important;
font-weight: 600 !important;
}
.tab-panel[data-panel="source-options"] .card:has(select[name="ocr_action"]) select {
width: 100% !important;
min-width: 0 !important;
font-size: 0.74rem !important;
line-height: 1.05 !important;
min-height: 1.6rem !important;
height: 1.6rem !important;
padding: 0 0.4rem !important;
}
}
/* ===== end data reset matches file source box ===== */
/* ===== data reset relayout ===== */
@media (max-width: 900px) {
.data-reset-grid {
display: grid !important;
grid-template-columns: 1fr !important;
gap: 0.22rem !important;
}
.data-reset-row {
display: grid !important;
grid-template-columns: 7.2rem minmax(0, 1fr) !important;
align-items: center !important;
gap: 0.28rem !important;
margin: 0 !important;
}
.data-reset-row label {
font-size: 0.74rem !important;
line-height: 1.1 !important;
margin: 0 !important;
color: #111827 !important;
font-weight: 600 !important;
}
.data-reset-row select {
width: 100% !important;
min-width: 0 !important;
font-size: 0.74rem !important;
line-height: 1.05 !important;
min-height: 1.6rem !important;
height: 1.6rem !important;
padding: 0 0.4rem !important;
}
}
/* ===== end data reset relayout ===== */
/* ===== source-options data reset matches file source v3 ===== */
@media (max-width: 900px) {
.tab-panel[data-panel="source-options"] > form > .card:nth-of-type(2) {
padding: 0.56rem !important;
border-radius: 0.9rem !important;
}
.tab-panel[data-panel="source-options"] > form > .card:nth-of-type(2) > h3 {
margin: 0 0 0.18rem 0 !important;
font-size: 0.88rem !important;
line-height: 1.05 !important;
}
.tab-panel[data-panel="source-options"] > form > .card:nth-of-type(2) .data-reset-grid {
display: grid !important;
grid-template-columns: 1fr !important;
gap: 0.22rem !important;
margin: 0 !important;
}
.tab-panel[data-panel="source-options"] > form > .card:nth-of-type(2) .data-reset-row {
display: grid !important;
grid-template-columns: 6.8rem minmax(0, 1fr) !important;
align-items: center !important;
gap: 0.22rem !important;
margin: 0 !important;
}
.tab-panel[data-panel="source-options"] > form > .card:nth-of-type(2) .data-reset-row label {
font-size: 0.74rem !important;
line-height: 1.08 !important;
font-weight: 500 !important;
margin: 0 !important;
color: #6b7280 !important;
}
.tab-panel[data-panel="source-options"] > form > .card:nth-of-type(2) .data-reset-row select {
width: 100% !important;
min-width: 0 !important;
font-size: 0.74rem !important;
line-height: 1.05 !important;
min-height: 1.6rem !important;
height: 1.6rem !important;
padding: 0 0.4rem !important;
margin: 0 !important;
}
.tab-panel[data-panel="source-options"] > form > div:last-child {
margin-top: 0.18rem !important;
}
.tab-panel[data-panel="source-options"] > form > div:last-child .btn {
font-size: 0.72rem !important;
line-height: 1 !important;
min-height: 1.5rem !important;
height: 1.5rem !important;
padding: 0 0.42rem !important;
border-radius: 999px !important;
}
}
/* ===== end source-options data reset matches file source v3 ===== */
/* ===== extra json matches OCR editor ===== */
@media (max-width: 900px) {
.tab-panel[data-panel="extracted-fields"] .extra-json-editor-field {
margin-top: 0.12rem !important;
}
.tab-panel[data-panel="extracted-fields"] .extra-json-editor-wrap {
display: block !important;
background: #f8fafc !important;
border: 1px solid #d7dce5 !important;
border-radius: 1rem !important;
overflow: hidden !important;
margin-top: 0.08rem !important;
}
.tab-panel[data-panel="extracted-fields"] .extra-json-editor-wrap textarea {
display: block !important;
width: 100% !important;
min-height: 10rem !important;
border: 0 !important;
border-radius: 0 !important;
background: transparent !important;
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace !important;
font-size: 0.88rem !important;
line-height: 1.35 !important;
padding: 0.6rem 0.7rem !important;
resize: vertical !important;
box-sizing: border-box !important;
}
.tab-panel[data-panel="extracted-fields"] .extra-json-editor-wrap textarea:focus {
outline: none !important;
box-shadow: none !important;
}
}
/* ===== end extra json matches OCR editor ===== */
/* ===== extra json matches OCR editor with line numbers ===== */
@media (max-width: 900px) {
.tab-panel[data-panel="extracted-fields"] .extra-json-editor-wrap {
display: grid !important;
grid-template-columns: 2.1rem minmax(0, 1fr) !important;
column-gap: 0 !important;
background: #f8fafc !important;
border: 1px solid #d7dce5 !important;
border-radius: 1rem !important;
overflow: hidden !important;
margin-top: 0.08rem !important;
}
.tab-panel[data-panel="extracted-fields"] .extra-json-editor-wrap .line-numbers {
width: 2.1rem !important;
min-width: 2.1rem !important;
margin: 0 !important;
padding: 0.6rem 0.22rem 0.6rem 0.18rem !important;
text-align: right !important;
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace !important;
font-size: 0.88rem !important;
line-height: 1.35 !important;
color: #6b7280 !important;
background: #f3f4f6 !important;
border-right: 1px solid #d7dce5 !important;
overflow: hidden !important;
user-select: none !important;
box-sizing: border-box !important;
}
.tab-panel[data-panel="extracted-fields"] .extra-json-editor-wrap textarea {
display: block !important;
width: 100% !important;
min-height: 10rem !important;
margin: 0 !important;
border: 0 !important;
border-radius: 0 !important;
background: transparent !important;
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace !important;
font-size: 0.88rem !important;
line-height: 1.35 !important;
padding: 0.6rem 0.7rem !important;
resize: vertical !important;
box-sizing: border-box !important;
overflow: auto !important;
white-space: pre !important;
overflow-wrap: normal !important;
}
.tab-panel[data-panel="extracted-fields"] .extra-json-editor-wrap textarea:focus {
outline: none !important;
box-shadow: none !important;
}
}
/* ===== end extra json matches OCR editor with line numbers ===== */

View File

@ -4,7 +4,7 @@
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
<title>{% block title %}Document Processor{% endblock %}</title> <title>{% block title %}Document Processor{% endblock %}</title>
<link rel="stylesheet" href="/static/app.css?v=131"> <link rel="stylesheet" href="/static/app.css?v=146">
<link rel="stylesheet" href="/static/app-shell.css?v=66"> <link rel="stylesheet" href="/static/app-shell.css?v=66">
</head> </head>
<body> <body>

View File

@ -1,5 +1,27 @@
{% extends "base.html" %} {% extends "base.html" %}
{% block title %}Document Detail{% endblock %} {% block title %}Document Detail
<script>
document.addEventListener("DOMContentLoaded", () => {
const ta = document.getElementById("extra_json");
const nums = document.getElementById("extra-json-line-numbers");
if (!ta || !nums) return;
const syncNums = () => {
const count = Math.max(1, ta.value.split("\n").length);
nums.textContent = Array.from({ length: count }, (_, i) => String(i + 1)).join("\n");
nums.scrollTop = ta.scrollTop;
};
ta.addEventListener("input", syncNums);
ta.addEventListener("scroll", () => {
nums.scrollTop = ta.scrollTop;
});
syncNums();
});
</script>
{% endblock %}
{% block content %} {% block content %}
{% if error == "line_count_mismatch" %} {% if error == "line_count_mismatch" %}
<div class="error-box"> <div class="error-box">
@ -223,7 +245,14 @@
</div> </div>
<div class="tab-panel{% if active_tab == 'extracted-fields' %} active{% endif %}" data-panel="extracted-fields"> <div class="tab-panel{% if active_tab == 'extracted-fields' %} active{% endif %}" data-panel="extracted-fields">
<div class="extracted-fields-header-row">
<h2 class="card-title">Extracted fields</h2> <h2 class="card-title">Extracted fields</h2>
<form method="get" action="/documents/{{ document.document_id }}" class="extracted-autofill-inline-form">
<input type="hidden" name="autofill_extracted" value="1">
<input type="hidden" name="tab" value="extracted-fields">
<button type="submit">Auto-extract fields</button>
</form>
</div>
{% if current_extracted_version_number %} {% if current_extracted_version_number %}
{% set current_extracted_meta = ( {% set current_extracted_meta = (
extracted_version_options extracted_version_options
@ -243,13 +272,7 @@
<p class="empty-state">No extracted fields saved yet.</p> <p class="empty-state">No extracted fields saved yet.</p>
{% endif %} {% 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;"> <form method="post" action="/documents/{{ document.document_id }}/save-extracted-fields" style="margin-top: 1rem;">
<div class="form-grid"> <div class="form-grid">
@ -265,7 +288,13 @@
<div class="form-field"><label>Reference number</label><input type="text" name="receipt_number" value="{{ extracted_form.receipt_number }}"></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>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>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 class="form-field full extra-json-editor-field">
<label for="extra_json">Extra JSON</label>
<div class="editor-wrap extra-json-editor-wrap">
<pre class="line-numbers" id="extra-json-line-numbers">1</pre>
<textarea id="extra_json" name="extra_json" rows="8" spellcheck="false">{{ extracted_form.extra_json }}</textarea>
</div>
</div>
</div> </div>
<div class="button-row" style="margin-top: 1rem;"> <div class="button-row" style="margin-top: 1rem;">
@ -376,19 +405,19 @@
<div class="tab-panel{% if active_tab == 'line-items' %} active{% endif %}" data-panel="line-items"> <div class="tab-panel{% if active_tab == 'line-items' %} active{% endif %}" data-panel="line-items">
<div class="line-items-header-row">
<h2 class="card-title">Line Items</h2> <h2 class="card-title">Line Items</h2>
<form method="post" action="/documents/{{ document.document_id }}/regenerate-line-items" class="line-items-regenerate-inline-form">
<button type="submit">Regenerate Line Items</button>
</form>
</div>
{% if current_line_item_version %} {% if current_line_item_version %}
<p>Current line item version: v{{ current_line_item_version.version_number }} — {{ current_line_item_version.created_at }}</p> <p>Current line item version: v{{ current_line_item_version.version_number }} — {{ current_line_item_version.created_at }}</p>
{% else %} {% else %}
<p class="empty-state">No line items saved yet.</p> <p class="empty-state">No line items saved yet.</p>
{% endif %} {% endif %}
<div class="button-row" style="margin-bottom: 0.75rem;">
<form method="post" action="/documents/{{ document.document_id }}/regenerate-line-items" style="display:inline;">
<button type="submit">Regenerate Line Items</button>
</form>
</div>
<form method="post" action="/documents/{{ document.document_id }}/save-line-items"> <form method="post" action="/documents/{{ document.document_id }}/save-line-items">
{% set base_count = line_items|length %} {% set base_count = line_items|length %}
@ -514,66 +543,45 @@ function addRow() {
<form method="post" action="/documents/{{ document.document_id }}/source-options" style="display:flex; flex-direction:column; gap:1rem;"> <form method="post" action="/documents/{{ document.document_id }}/source-options" style="display:flex; flex-direction:column; gap:1rem;">
<div class="card" style="padding:1rem;"> <div class="card" style="padding:1rem;">
<h3 style="margin-top:0;">File Source</h3> <div class="source-options-select-row">
<div style="display:flex; flex-direction:column; gap:0.75rem;"> <label for="file_action_select" class="source-options-inline-label">File source</label>
<label style="display:flex; align-items:center; gap:0.5rem;"> <select id="file_action_select" name="file_action">
<input type="radio" name="file_action" value="none" checked> <option value="none" selected>No file change</option>
<span>No file change</span> <option value="revert_original">Revert to original file</option>
</label> <option value="revert_current_saved">Revert to current saved version</option>
<label style="display:flex; align-items:center; gap:0.5rem;"> </select>
<input type="radio" name="file_action" value="revert_original">
<span>Revert to original file</span>
</label>
<label style="display:flex; align-items:center; gap:0.5rem;">
<input type="radio" name="file_action" value="revert_current_version">
<span>Revert to current saved version</span>
</label>
</div> </div>
</div> </div>
<div class="card" style="padding:1rem;"> <div class="card" style="padding:1rem;">
<h3 style="margin-top:0;">Data Reset</h3> <h3 style="margin-top:0;">Data Reset</h3>
<div style="display:grid; grid-template-columns: 180px 1fr; gap:0.75rem; align-items:center; margin-bottom:0.75rem;"> <div class="data-reset-grid">
<strong>OCR</strong> <div class="data-reset-row">
<select name="ocr_restore_choice"> <label for="ocr_action">OCR</label>
<option value="none" selected>No change</option> <select id="ocr_action" name="ocr_action">
<option value="original">Original</option> <option value="none" {% if selected_ocr_action == "none" %}selected{% endif %}>No change</option>
{% for version_number, version_type, created_at in ocr_version_options %} <option value="reset" {% if selected_ocr_action == "reset" %}selected{% endif %}>Reset</option>
<option value="version:{{ version_number }}">v{{ version_number }} — {{ version_type }} — {{ created_at }}</option>
{% endfor %}
</select> </select>
</div> </div>
<div class="data-reset-row">
<div style="display:grid; grid-template-columns: 180px 1fr; gap:0.75rem; align-items:center; margin-bottom:0.75rem;"> <label for="extracted_action">Extracted fields</label>
<strong>Extracted fields</strong> <select id="extracted_action" name="extracted_action">
<select name="extracted_restore_choice"> <option value="none" {% if selected_extracted_action == "none" %}selected{% endif %}>No change</option>
<option value="none" selected>No change</option> <option value="reset" {% if selected_extracted_action == "reset" %}selected{% endif %}>Reset</option>
<option value="original">Original</option>
{% for version_number, created_at in extracted_version_options %}
<option value="version:{{ version_number }}">v{{ version_number }} — {{ created_at }}</option>
{% endfor %}
</select> </select>
</div> </div>
<div class="data-reset-row">
<div style="display:grid; grid-template-columns: 180px 1fr; gap:0.75rem; align-items:center; margin-bottom:0.75rem;"> <label for="additional_action">Additional fields</label>
<strong>Additional fields</strong> <select id="additional_action" name="additional_action">
<select name="additional_restore_choice"> <option value="none" {% if selected_additional_action == "none" %}selected{% endif %}>No change</option>
<option value="none" selected>No change</option> <option value="reset" {% if selected_additional_action == "reset" %}selected{% endif %}>Reset</option>
<option value="original">Original</option>
{% for version_number, created_at in additional_version_options %}
<option value="version:{{ version_number }}">v{{ version_number }} — {{ created_at }}</option>
{% endfor %}
</select> </select>
</div> </div>
<div class="data-reset-row">
<div style="display:grid; grid-template-columns: 180px 1fr; gap:0.75rem; align-items:center;"> <label for="line_items_action">Line items</label>
<strong>Line items</strong> <select id="line_items_action" name="line_items_action">
<select name="line_item_restore_choice"> <option value="none" {% if selected_line_items_action == "none" %}selected{% endif %}>No change</option>
<option value="none" selected>No change</option> <option value="reset" {% if selected_line_items_action == "reset" %}selected{% endif %}>Reset</option>
<option value="clear">Clear</option>
{% for version in document.line_item_set_versions %}
<option value="version:{{ version.version_number }}">v{{ version.version_number }} — {{ version.created_at }}</option>
{% endfor %}
</select> </select>
</div> </div>
</div> </div>
@ -622,4 +630,26 @@ function addRow() {
<div class="meta-item"><span class="meta-label">Updated at</span>{{ document.updated_at }}</div> <div class="meta-item"><span class="meta-label">Updated at</span>{{ document.updated_at }}</div>
</div> </div>
</div> </div>
<script>
document.addEventListener("DOMContentLoaded", () => {
const ta = document.getElementById("extra_json");
const nums = document.getElementById("extra-json-line-numbers");
if (!ta || !nums) return;
const syncNums = () => {
const count = Math.max(1, ta.value.split("\n").length);
nums.textContent = Array.from({ length: count }, (_, i) => String(i + 1)).join("\n");
nums.scrollTop = ta.scrollTop;
};
ta.addEventListener("input", syncNums);
ta.addEventListener("scroll", () => {
nums.scrollTop = ta.scrollTop;
});
syncNums();
});
</script>
{% endblock %} {% endblock %}