feat: polish mobile detail tabs and persist active tab
This commit is contained in:
parent
9387e4166e
commit
0ef1392b19
|
|
@ -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 ===== */
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -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 %}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue