Tự động format hóa đơn, đánh số, inject CSS, thêm nút in cho AppSheet trên mobile
// ==UserScript==
// @name AppSheet Bill Formatter & Print (Mobile)
// @namespace http://tampermonkey.net/
// @version 2.3
// @description Tự động format hóa đơn, đánh số, inject CSS, thêm nút in cho AppSheet trên mobile
// @match https://*.appsheet.com/*
// @grant none
// ==/UserScript==
(function() {
'use strict';
// Inject CSS cho hóa đơn (có mobile responsive)
function injectBillCss() {
if (document.getElementById('bill-css-style')) return;
const style = document.createElement('style');
style.id = 'bill-css-style';
style.textContent = `
.CommonLongTextRichTextDisplay {
width: 100% !important;
font-family: monospace !important;
max-width: 100% !important;
margin: 0 auto !important;
padding: 10px !important;
font-size: 12px !important;
display: block !important;
}
@media print {
.CommonLongTextRichTextDisplay {
margin: 0 !important;
padding: 0 !important;
}
}
.CommonLongTextRichTextDisplay table,
.CommonLongTextRichTextDisplay h1,
.CommonLongTextRichTextDisplay h3,
.CommonLongTextRichTextDisplay td,
.CommonLongTextRichTextDisplay th,
.CommonLongTextRichTextDisplay tr {
margin: 0 !important;
padding: 2px !important;
border: none !important;
border-collapse: collapse !important;
}
.CommonLongTextRichTextDisplay table {
width: 100% !important;
margin: 0 auto !important;
table-layout: fixed !important;
}
.CommonLongTextRichTextDisplay table:first-of-type {
margin-bottom: 4px !important;
text-align: center !important;
border: none !important;
}
.CommonLongTextRichTextDisplay table:first-of-type td {
text-align: center !important;
border: none !important;
}
.CommonLongTextRichTextDisplay table:first-of-type h1 {
font-size: 20px !important;
font-weight: 700 !important;
letter-spacing: 1px !important;
line-height: 1.2 !important;
text-align: center !important;
}
.CommonLongTextRichTextDisplay table:first-of-type h3 {
font-size: 11px !important;
font-weight: normal !important;
line-height: 1.2 !important;
text-align: center !important;
}
.CommonLongTextRichTextDisplay table:nth-of-type(2) {
margin: 4px 0 !important;
border: none !important;
}
.CommonLongTextRichTextDisplay table:nth-of-type(2) td {
border: none !important;
}
.CommonLongTextRichTextDisplay table:nth-of-type(2) td:first-child {
width: 80px !important;
font-weight: 700 !important;
}
.CommonLongTextRichTextDisplay table:nth-of-type(3) {
margin: 15px 0 !important;
border: 1px solid black !important;
}
.CommonLongTextRichTextDisplay table:nth-of-type(3) th,
.CommonLongTextRichTextDisplay table:nth-of-type(3) td {
border: 1px solid black !important;
padding: 8px !important;
text-align: center !important;
font-weight: normal !important;
}
.CommonLongTextRichTextDisplay table:nth-of-type(3) tr td {
font-weight: normal !important;
}
.CommonLongTextRichTextDisplay table:nth-of-type(3) td:nth-child(3) {
text-align: left !important;
}
.CommonLongTextRichTextDisplay table:nth-of-type(3) td:first-child,
.CommonLongTextRichTextDisplay table:nth-of-type(3) th:first-child {
width: 50px !important;
}
.CommonLongTextRichTextDisplay table:nth-of-type(3) td:nth-child(2),
.CommonLongTextRichTextDisplay table:nth-of-type(3) th:nth-child(2) {
width: 120px !important;
}
.CommonLongTextRichTextDisplay table:nth-of-type(3) td:nth-child(4),
.CommonLongTextRichTextDisplay table:nth-of-type(3) th:nth-child(4) {
width: 80px !important;
}
.CommonLongTextRichTextDisplay table:nth-of-type(3) tr:last-child td {
border: 1px solid #000 !important;
font-weight: normal !important;
}
.CommonLongTextRichTextDisplay table:nth-of-type(3) tr:last-child td:first-child {
text-align: center !important;
padding-left: 8px !important;
border-right: none !important;
}
.CommonLongTextRichTextDisplay table:nth-of-type(3) tr:last-child td:nth-child(5) {
width: 100px !important;
border: 1px solid #000 !important;
}
.CommonLongTextRichTextDisplay table:nth-of-type(3) tr:last-child td:nth-child(6) {
border: 1px solid #000 !important;
background: none !important;
}
.CommonLongTextRichTextDisplay table:nth-of-type(3) tr:last-child td:nth-child(2),
.CommonLongTextRichTextDisplay table:nth-of-type(3) tr:last-child td:nth-child(3),
.CommonLongTextRichTextDisplay table:nth-of-type(3) tr:last-child td:nth-child(4) {
border: none !important;
background: none !important;
}
.CommonLongTextRichTextDisplay table:nth-of-type(3) tr:last-child td:first-child[colspan=\"3\"] ~ td {
display: none !important;
}
.CommonLongTextRichTextDisplay table:nth-of-type(4) {
margin-top: 5px !important;
page-break-inside: avoid !important;
}
.CommonLongTextRichTextDisplay table:nth-of-type(4) td {
text-align: center !important;
width: 25% !important;
padding: 0 !important;
vertical-align: top !important;
}
.CommonLongTextRichTextDisplay table:nth-of-type(4) strong {
display: block !important;
margin-bottom: 0px !important;
font-weight: normal !important;
}
.CommonLongTextRichTextDisplay table:nth-of-type(4) p,
.CommonLongTextRichTextDisplay table:nth-of-type(4) i {
font-style: italic !important;
margin: 0 !important;
padding: 0 !important;
}
@media (max-width: 600px) {
.CommonLongTextRichTextDisplay table {
display: block !important;
width: 100% !important;
overflow-x: auto !important;
box-sizing: border-box !important;
}
.CommonLongTextRichTextDisplay th,
.CommonLongTextRichTextDisplay td {
font-size: 11px !important;
padding: 4px !important;
word-break: break-word !important;
}
}
`;
document.head.appendChild(style);
}
// Đánh số thứ tự và xử lý dòng tổng cộng
function numberRows() {
const tableContainer = document.querySelector('.CommonLongTextRichTextDisplay');
if (!tableContainer) return;
const tables = tableContainer.getElementsByTagName('table');
if (tables.length < 3) return;
const productTable = tables[2];
if (!productTable) return;
const rows = productTable.rows;
if (rows.length < 2) return;
// Đánh số thứ tự từ 1
let stt = 1;
for (let i = 1; i < rows.length - 1; i++) {
const row = rows[i];
if (!row || !row.cells || row.cells.length < 2) continue;
const sttCell = row.cells[0];
const codeCell = row.cells[1];
// Chỉ đánh số nếu có mã hàng
if (codeCell && codeCell.textContent.trim() !== '') {
sttCell.textContent = stt++;
sttCell.style.textAlign = 'center';
}
}
// Xử lý dòng tổng cộng
const lastRow = rows[rows.length - 1];
if (lastRow && lastRow.cells.length > 0) {
const firstCell = lastRow.cells[0];
if (firstCell.textContent.trim().toLowerCase().includes('tổng')) {
firstCell.setAttribute('colspan', '4');
firstCell.style.textAlign = 'left';
firstCell.style.paddingLeft = '8px';
firstCell.style.borderRight = 'none';
lastRow.cells[1].style.display = 'none';
lastRow.cells[2].style.display = 'none';
lastRow.cells[3].style.display = 'none';
}
}
}
// Thêm nút in vào đầu action bar
function addPrintButton() {
var actionBar = document.querySelector('.SlideshowPage__action-bar');
if (!actionBar || document.getElementById('bookmarklet-print-btn')) return;
// Tạo nút in giống các action khác, chỉ icon, dùng đúng class
var btn = document.createElement('span');
btn.id = 'bookmarklet-print-btn';
btn.className = 'ASTappable GenericActionButton CSSRolloutContainer OutsideReactRootRollout GenericActionButton--large-mode GenericActionButton--max-height ASTappable--pointer';
btn.setAttribute('tabindex', '0');
btn.setAttribute('role', 'button');
btn.setAttribute('title', 'In hóa đơn');
btn.style = 'margin-left:10px;';
// Sử dụng SVG icon giống action mặc định (nếu AppSheet dùng SVG)
btn.innerHTML = `
<div class="GenericActionButton__paddington" style="display:flex;align-items:center;justify-content:center;">
<svg width="28" height="28" viewBox="0 0 24 24" fill="none">
<rect x="3" y="7" width="18" height="13" rx="2" fill="#1976d2"/>
<rect x="6" y="3" width="12" height="4" rx="1" fill="#1976d2"/>
<rect x="8" y="15" width="8" height="2" rx="1" fill="white"/>
<rect x="8" y="11" width="8" height="2" rx="1" fill="white"/>
</svg>
<span class="sr-only">In hóa đơn</span>
</div>
`;
// Hiệu ứng hover
btn.onmouseover = function() { btn.querySelector('rect').setAttribute('fill', '#1251a3'); };
btn.onmouseout = function() { btn.querySelector('rect').setAttribute('fill', '#1976d2'); };
btn.onclick = function() {
var el = document.querySelector('.CommonLongTextRichTextDisplay');
if (!el) { alert('Không tìm thấy hóa đơn!'); return; }
var clone = el.cloneNode(true);
var w = window.open('', '', 'width=1122,height=793');
w.document.write('<html><head><title>In hóa đơn</title><style>' + document.getElementById('bill-css-style').textContent + '</style></head><body>' + clone.outerHTML + '</body></html>');
w.document.close();
setTimeout(function () { w.print(); w.close(); }, 500);
};
var wrap = document.createElement('div');
wrap.className = 'SlideshowPage__header-action';
wrap.appendChild(btn);
actionBar.insertBefore(wrap, actionBar.firstChild);
}
// Hàm khởi tạo
function initAutoBillFormatter() {
injectBillCss();
setTimeout(() => {
numberRows();
addPrintButton();
}, 500);
// Theo dõi thay đổi DOM để tự động format lại khi cần
const observer = new MutationObserver(() => {
setTimeout(() => {
numberRows();
addPrintButton();
}, 100);
});
observer.observe(document.body, { childList: true, subtree: true });
}
// Khởi động khi trang load
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', initAutoBillFormatter);
} else {
initAutoBillFormatter();
}
})();