feat: polish mobile detail tabs and persist active tab

This commit is contained in:
Sean McElwain 2026-04-26 16:59:23 -05:00
parent 9387e4166e
commit 0ef1392b19
3 changed files with 214 additions and 5 deletions

View File

@ -5277,3 +5277,93 @@ table {
} }
} }
/* ===== end extra json matches OCR editor with line numbers ===== */ /* ===== end extra json matches OCR editor with line numbers ===== */
/* ===== line items mobile final sizing ===== */
@media (max-width: 900px) {
.tab-panel[data-panel="line-items"] table {
min-width: 68rem !important;
}
/* # */
.tab-panel[data-panel="line-items"] table th:nth-child(1),
.tab-panel[data-panel="line-items"] table td:nth-child(1) {
min-width: 2.4rem !important;
width: 2.4rem !important;
}
/* Date */
.tab-panel[data-panel="line-items"] table th:nth-child(2),
.tab-panel[data-panel="line-items"] table td:nth-child(2) {
min-width: 6.2rem !important;
width: 6.2rem !important;
}
/* Description - larger */
.tab-panel[data-panel="line-items"] table th:nth-child(3),
.tab-panel[data-panel="line-items"] table td:nth-child(3) {
min-width: 13rem !important;
width: 13rem !important;
}
/* Qty */
.tab-panel[data-panel="line-items"] table th:nth-child(4),
.tab-panel[data-panel="line-items"] table td:nth-child(4) {
min-width: 3rem !important;
width: 3rem !important;
}
/* Unit */
.tab-panel[data-panel="line-items"] table th:nth-child(5),
.tab-panel[data-panel="line-items"] table td:nth-child(5) {
min-width: 4rem !important;
width: 4rem !important;
}
/* Total - larger */
.tab-panel[data-panel="line-items"] table th:nth-child(6),
.tab-panel[data-panel="line-items"] table td:nth-child(6) {
min-width: 6rem !important;
width: 6rem !important;
}
/* Tax - smaller */
.tab-panel[data-panel="line-items"] table th:nth-child(7),
.tab-panel[data-panel="line-items"] table td:nth-child(7) {
min-width: 2.8rem !important;
width: 2.8rem !important;
}
/* Category */
.tab-panel[data-panel="line-items"] table th:nth-child(8),
.tab-panel[data-panel="line-items"] table td:nth-child(8) {
min-width: 7rem !important;
width: 7rem !important;
}
/* Notes */
.tab-panel[data-panel="line-items"] table th:nth-child(9),
.tab-panel[data-panel="line-items"] table td:nth-child(9) {
min-width: 7rem !important;
width: 7rem !important;
}
.tab-panel[data-panel="line-items"] input {
min-width: 100% !important;
width: 100% !important;
font-size: 0.82rem !important;
padding-left: 0.45rem !important;
padding-right: 0.45rem !important;
}
/* numeric columns align right */
.tab-panel[data-panel="line-items"] table td:nth-child(4) input,
.tab-panel[data-panel="line-items"] table td:nth-child(5) input,
.tab-panel[data-panel="line-items"] table td:nth-child(6) input,
.tab-panel[data-panel="line-items"] table td:nth-child(7) input {
text-align: right !important;
font-variant-numeric: tabular-nums !important;
}
}
/* ===== end line items mobile final sizing ===== */

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=146"> <link rel="stylesheet" href="/static/app.css?v=153">
<link rel="stylesheet" href="/static/app-shell.css?v=66"> <link rel="stylesheet" href="/static/app-shell.css?v=66">
</head> </head>
<body> <body>
@ -97,5 +97,50 @@ document.addEventListener("DOMContentLoaded", () => {
}); });
</script> </script>
<script>
document.addEventListener("DOMContentLoaded", () => {
const tabButtons = document.querySelectorAll(".right-pane-tabs [data-tab]");
const tabPanels = document.querySelectorAll(".tab-panel[data-panel]");
if (!(tabButtons.length && tabPanels.length)) return;
const tabKey = "documentDetailActiveTab";
const applyTab = (tabName) => {
tabButtons.forEach((btn) => {
btn.classList.toggle("active", btn.dataset.tab === tabName);
});
tabPanels.forEach((panel) => {
panel.classList.toggle("active", panel.dataset.panel === tabName);
});
localStorage.setItem(tabKey, tabName);
};
const url = new URL(window.location.href);
const urlTab = url.searchParams.get("tab");
const savedTab = localStorage.getItem(tabKey);
const defaultTab =
urlTab ||
savedTab ||
document.querySelector(".right-pane-tabs .tab-button.active")?.dataset.tab ||
tabButtons[0]?.dataset.tab;
if (defaultTab) {
applyTab(defaultTab);
}
tabButtons.forEach((btn) => {
btn.addEventListener("click", () => {
const nextTab = btn.dataset.tab;
applyTab(nextTab);
const nextUrl = new URL(window.location.href);
nextUrl.searchParams.set("tab", nextTab);
window.history.replaceState({}, "", nextUrl.toString());
});
});
});
</script>
</body> </body>
</html> </html>

View File

@ -21,6 +21,43 @@ document.addEventListener("DOMContentLoaded", () => {
}); });
</script> </script>
<script>
document.addEventListener("DOMContentLoaded", () => {
const panel = document.querySelector('.tab-panel[data-panel="line-items"]');
if (!panel) return;
const moneyCols = [6, 7]; // total, tax
const qtyCols = [4, 5]; // qty, unit
const rows = panel.querySelectorAll("tbody tr");
rows.forEach((row) => {
const cells = row.querySelectorAll("td");
[...moneyCols, ...qtyCols].forEach((col) => {
const cell = cells[col - 1];
if (!cell) return;
const input = cell.querySelector("input");
if (!input) return;
const trimDecimals = () => {
const v = input.value.trim();
if (!v) return;
const n = Number(v.replace(/,/g, ""));
if (!Number.isFinite(n)) return;
if (moneyCols.includes(col)) {
input.value = n.toFixed(2);
} else {
input.value = Number.isInteger(n) ? String(n) : String(n);
}
};
input.addEventListener("blur", trimDecimals);
});
});
});
</script>
{% endblock %} {% endblock %}
{% block content %} {% block content %}
{% if error == "line_count_mismatch" %} {% if error == "line_count_mismatch" %}
@ -451,10 +488,10 @@ document.addEventListener("DOMContentLoaded", () => {
<td style="padding:0.35rem;">{{ i + 1 }}</td> <td style="padding:0.35rem;">{{ i + 1 }}</td>
<td style="padding:0.35rem;"><input type="date" name="entry_date_{{ i }}" value="{{ item.entry_date.isoformat() if item and item.entry_date else '' }}" style="width:100%;"></td> <td style="padding:0.35rem;"><input type="date" name="entry_date_{{ i }}" value="{{ item.entry_date.isoformat() if item and item.entry_date else '' }}" style="width:100%;"></td>
<td style="padding:0.35rem;"><input type="text" name="description_{{ i }}" value="{{ item.description if item else '' }}" style="width:100%;"></td> <td style="padding:0.35rem;"><input type="text" name="description_{{ i }}" value="{{ item.description if item else '' }}" style="width:100%;"></td>
<td style="padding:0.35rem;"><input type="text" name="quantity_{{ i }}" value="{{ item.quantity if item and item.quantity is not none else '' }}" style="width:100%;"></td> <td style="padding:0.35rem;"><input type="text" name="quantity_{{ i }}" value="{{ (item.quantity | string | replace('.0000', '') | replace('.00', '')) if item and item.quantity is not none else '' }}" style="width:100%;"></td>
<td style="padding:0.35rem;"><input type="text" name="unit_price_{{ i }}" value="{{ item.unit_price if item and item.unit_price is not none else '' }}" style="width:100%;"></td> <td style="padding:0.35rem;"><input type="text" name="unit_price_{{ i }}" value="{{ (item.unit_price | string | replace('.0000', '') | replace('.00', '')) if item and item.unit_price is not none else '' }}" style="width:100%;"></td>
<td style="padding:0.35rem;"><input type="text" name="line_total_{{ i }}" value="{{ item.line_total if item and item.line_total is not none else '' }}" style="width:100%;"></td> <td style="padding:0.35rem;"><input type="text" name="line_total_{{ i }}" value="{{ '%.2f'|format(item.line_total|float) if item and item.line_total is not none else '' }}" style="width:100%;"></td>
<td style="padding:0.35rem;"><input type="text" name="tax_amount_{{ i }}" value="{{ item.tax_amount if item and item.tax_amount is not none else '' }}" style="width:100%;"></td> <td style="padding:0.35rem;"><input type="text" name="tax_amount_{{ i }}" value="{{ '%.2f'|format(item.tax_amount|float) if item and item.tax_amount is not none else '' }}" style="width:100%;"></td>
<td style="padding:0.35rem;"><input type="text" name="category_{{ i }}" value="{{ item.category if item else '' }}" style="width:100%;"></td> <td style="padding:0.35rem;"><input type="text" name="category_{{ i }}" value="{{ item.category if item else '' }}" style="width:100%;"></td>
<td style="padding:0.35rem;"><input type="text" name="notes_{{ i }}" value="{{ item.notes if item else '' }}" style="width:100%;"></td> <td style="padding:0.35rem;"><input type="text" name="notes_{{ i }}" value="{{ item.notes if item else '' }}" style="width:100%;"></td>
</tr> </tr>
@ -652,4 +689,41 @@ document.addEventListener("DOMContentLoaded", () => {
}); });
</script> </script>
<script>
document.addEventListener("DOMContentLoaded", () => {
const panel = document.querySelector('.tab-panel[data-panel="line-items"]');
if (!panel) return;
const moneyCols = [6, 7]; // total, tax
const qtyCols = [4, 5]; // qty, unit
const rows = panel.querySelectorAll("tbody tr");
rows.forEach((row) => {
const cells = row.querySelectorAll("td");
[...moneyCols, ...qtyCols].forEach((col) => {
const cell = cells[col - 1];
if (!cell) return;
const input = cell.querySelector("input");
if (!input) return;
const trimDecimals = () => {
const v = input.value.trim();
if (!v) return;
const n = Number(v.replace(/,/g, ""));
if (!Number.isFinite(n)) return;
if (moneyCols.includes(col)) {
input.value = n.toFixed(2);
} else {
input.value = Number.isInteger(n) ? String(n) : String(n);
}
};
input.addEventListener("blur", trimDecimals);
});
});
});
</script>
{% endblock %} {% endblock %}