New themes issues - Megathread

@Ealfardan Please can you help with your css code

Hey everyone,

Ever since the latest update, we’ve been facing a few major issues with the Sales Invoice feature:

-We’re no longer able to save or print Sales Invoices.

-When we try to print, the invoice shows up with a scroll bar and each single invoice is showing up as 40–80 pages long, with the footer pushed all the way to the last page.

-Also, the new Theme Enhancer feature looks broken on our side. We can’t scroll down to reach it properly — the page keeps extending endlessly and the scroll bar keeps moving up on its own, even after 10+ minutes of being on the invoice tab.

-For context, we’re using a custom theme for our invoices.

Another thing we noticed — in Sales Quotes, the custom fields that used to appear at the top are now showing up at the bottom of the document.

Is anyone else facing the same issues? Has anyone figured out a fix or workaround yet?

Appreciate any help!

I have merged multiple topics into this one and changed the topic accordingly.

I will also pin this topic to the top in order to help new reports find their way.

The idea of ​​using AI in theme design is brilliant, but:

  • It’s necessary to preserve the old themes and the default theme.
  • When generating a theme using AI, there must be a way to modify or improve the same theme, as modifications and improvements are limited to the default theme only.
  • How can it be applied to other modules?

Even after restarting the server to force upgrade it’s showingthe same with the scrollbar

it is the same issue persistent @lubos @Mabaega

same with me

hello i will like to my invoices to display fully on my screen without the scrollbar but i dont seem to have the code right please someone help.


I will like the view to extent to the empty space on the right

do you use a theme?
you have to edit the display css code for it

To remove the scrollbar, temporarily try setting the margin to none, this worked in some of my experiments.

@NCHIA1 & @alizali, I have merged your topic to this one.

Please continue here.

where i can do this?

In your browser’s print dialogue menu. It might be folded under More settings

Or try use my costum theme (modified from default custom theme), this work with Margin Default

<!DOCTYPE html>
<html>
<head>
	<meta charset='UTF-8' />
	<meta name='viewport' content='width=device-width, initial-scale=1.0' />
	<!-- Styling for consistent look and feel -->
	<style>
		/* Print-specific fixes only */
		@media print {
			@page {
				margin: 0.5in;
			}
			body {
				margin: 0 !important;
			}
		}

		body {
			margin: 30px;
			font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
			color: #171717;
			font-size: 12px;
			line-height: 1.428571429;
		}

		table {
			font-size: 12px;
		}

		tr#table-headers th {
			font-weight: bold;
			padding: 5px 10px;
			border: 1px solid #000;
			text-align: start
		}

		tbody#table-rows td {
			padding: 5px 10px;
			border-left: 1px solid #000;
			border-right: 1px solid #000;
			text-align: start;
			vertical-align: top
		}

		tbody#table-rows tr:last-child td {
			padding-bottom: 30px;
			border-bottom: 1px solid #000;
		}
	</style>
</head>
<body>

	<!-- Top section: document title and optional business logo -->
	<table style='margin-bottom: 20px; width: 100%'>
		<tbody>
			<tr>
				<td style='vertical-align: top'>
					<div style='font-size: 32px; line-height: 32px; font-weight: bold' id='title'>Invoice</div>
				</td>
				<td style='text-align: end' id='business-logo'>
				</td>
			</tr>
		</tbody>
	</table>

	<!-- Second section: recipient info, document fields (like date, invoice #), and business info -->
	<table style='margin-bottom: 20px; width: 100%'>
		<tbody>
			<tr>
				<td style='vertical-align: top' id='recipient-info'></td>
				<td style='text-align: end; vertical-align: top' id='fields'></td>
				<td style='width: 20px'></td>
				<td style='width: 1px; border-left-width: 1px; border-left-color: #000; border-left-style: solid'></td>
				<td style='width: 20px'></td>
				<td style='width: 1px; white-space: nowrap; vertical-align: top' id='business-info'></td>
			</tr>
		</tbody>
	</table>

	<!-- Description block -->
	<table  style='border-collapse: collapse; width: 100%'>
	    <tr><td><div style='font-weight: bold; font-size: 14px; margin-bottom: 20px' id='description'></div></td></tr>
	</table>
	
	<!-- Main table containing column headers, line items, and totals -->
	<table style='border-collapse: collapse; width: 100%'>
		<thead>
			<tr id='table-headers'></tr>
		</thead>
		<tbody id='table-rows'>
		</tbody>
		<tfoot id='totals'>
		</tfoot>
	</table>
    
    <script src='resources/qrcode/qrcode.js'></script>
    
	<table  style='border-collapse: collapse; width: 100%'>
	    <tr><td><div id='qrcode' style='margin-bottom: 20px'></div></td></tr>

	    <!-- Section for any additional custom fields -->
	    <tr><td><div id='custom-fields'  style=' word-break: break-all; overflow-wrap: break-word; white-space: normal;'></div></td></tr>

	    <!-- Section for footers -->
	    <tr><td><div id='footers'  style='word-break: break-all; overflow-wrap: break-word; white-space: normal;'></div></td></tr>
    </table>

	<!-- Status div -->
	<div id='status'></div>
    
	<script>
		// Listen for messages sent from the parent frame (via postMessage API)
		window.addEventListener('message', (event) => {

			if (event.source !== window.parent) return; // Only accept messages from parent
			if (event.data.type !== 'context-response') return; // Ignore irrelevant messages

			// Extract the main data object sent from parent
			const data = event.data.body;

			// Set text direction (LTR or RTL) for the whole document
			document.documentElement.dir = data.direction;

			// Populate title and description if provided
			document.getElementById('title').innerHTML = data.title || 'No title';
			document.getElementById('description').innerHTML = data.description || '';

			// Inject business logo image if available
			var businessLogoTd = document.getElementById('business-logo');
			if (data.business.logo) {
				const img = document.createElement('img');
				img.src = data.business.logo;
				img.style = 'max-height: 150px; max-width: 300px; display: inline';
				businessLogoTd.appendChild(img);
			}

			// Populate business info section with name and address (convert line breaks)
			const business = data.business || {};
			document.getElementById('business-info').innerHTML = `<strong id="BusinessName">${business.name || ''}</strong><br>${business.address ? business.address.replace(/\n/g, '<br>') : ''}`;

			// Populate recipient info section with name and address (convert line breaks)
			const recipient = data.recipient || {};
			document.getElementById('recipient-info').innerHTML = `<strong>${recipient.name || ''}</strong><br>${recipient.address ? recipient.address.replace(/\n/g, '<br>') : ''}`;

			// Insert fields (e.g. issue date, due date) into right-hand side
			const fieldsDiv = document.getElementById('fields');
			fieldsDiv.innerHTML = '';
			(data.fields || []).forEach(f => {
				const div = document.createElement('div');
				div.innerHTML = `<strong>${f.label}</strong><br />${f.text || ''}<br /><br />`;
				fieldsDiv.appendChild(div);
			});

			// Build table headers dynamically based on `columns` definition
			const headersRow = document.getElementById('table-headers');
			headersRow.innerHTML = '';
			(data.table.columns || []).forEach(col => {
				const th = document.createElement('th');
				th.innerHTML = col.label;
				th.style.textAlign = col.align;
				if (col.minWidth) {
					th.style.whiteSpace = 'nowrap';
					th.style.width = '1px';
				}
				else if (col.nowrap) {
					th.style.whiteSpace = 'nowrap';
					th.style.width = '80px';
				}
				headersRow.appendChild(th);
			});

			// Populate main table body with rows and alignments based on column definitions
			const rowsBody = document.getElementById('table-rows');
			rowsBody.innerHTML = '';
			(data.table.rows || []).forEach(row => {
				const tr = document.createElement('tr');
				row.cells.forEach((cell, i) => {
					var col = data.table.columns[i];
					const td = document.createElement('td');
					td.innerHTML = (cell.text || '').split('\n').join('<br />');
					td.style.textAlign = col.align;
					if (col.minWidth) {
						td.style.whiteSpace = 'nowrap';
						td.style.width = '1px';
					}
					else if (col.nowrap) {
						td.style.whiteSpace = 'nowrap';
						td.style.width = '80px';
					}
					tr.appendChild(td);
				});
				rowsBody.appendChild(tr);
			});

			// Populate totals section with subtotals, taxes, grand total, etc.
			const totalsBody = document.getElementById('totals');
			totalsBody.innerHTML = '';
			(data.table.totals || []).forEach(total => {
				const tr = document.createElement('tr');
				const tdLabel = document.createElement('td');
				const tdValue = document.createElement('td');
				tdLabel.innerHTML = total.label;
				tdLabel.colSpan = data.table.columns.length - 1;
				tdLabel.style = 'padding: 5px 10px; text-align: end; vertical-align: top';
				tdValue.innerHTML = total.text;
				tdValue.id = total.key;
				tdValue.dataset.value = total.number;
				tdValue.style = 'padding: 5px 10px; border: 1px solid #000; text-align: right; white-space: nowrap; vertical-align: top';

				// Bold totals if marked as 'emphasis'
				if (total.emphasis) {
					tdLabel.style.fontWeight = 'bold';
					tdValue.style.fontWeight = 'bold';
				}
				tr.appendChild(tdLabel);
				tr.appendChild(tdValue);
				totalsBody.appendChild(tr);
			});

			// Render custom fields section below the table (e.g. notes, terms)
			const customFieldsDiv = document.getElementById('custom-fields');
			customFieldsDiv.innerHTML = '';
			
			let qrcodetext = '';
			(data.custom_fields || []).forEach(f => {
                if (f.label === 'Base64 QRCode') {
                    console.log('Found Base64 QRCode:', f.text);
                    qrcodetext = f.text;
                } else {
                    console.log(f.label + ':', f.text);
                    const div = document.createElement('div');
                    div.innerHTML = `<strong>${f.label}</strong><br />${(f.text || '').split('\n').join('<br />')}<br /><br />`;
                    customFieldsDiv.appendChild(div);
                }
            });

			// Render footers section
			const footersDiv = document.getElementById('footers');
			footersDiv.innerHTML = '';
			(data.footers || []).forEach(f => {
				const div = document.createElement('div');
				div.style = 'margin-top: 20px';
				div.innerHTML = f;
				footersDiv.appendChild(div);

				// Find and execute any script tags
				const scripts = div.querySelectorAll('script');
				scripts.forEach(script => {
					const newScript = document.createElement('script');

					// Copy script attributes if needed
					for (const attr of script.attributes) {
						newScript.setAttribute(attr.name, attr.value);
					}

					// Inline script handling
					if (script.textContent) {
						newScript.textContent = script.textContent;
					}

					// Replace the old <script> with the new one so it executes
					script.parentNode.replaceChild(newScript, script);
				});
			});
			
			// Display status label (e.g. PAID, CANCELLED) with colored border based on status type
			const statusDiv = document.getElementById('status');
			if (data.emphasis?.text != null) {
				statusDiv.style.marginTop = '40px';
				const span = document.createElement('span');
				span.style = 'border-width: 5px; border-color: #FF0000; border-style: solid; padding: 10px; font-size: 20px; text-transform: uppercase';
				if (data.emphasis.positive) {
					span.style.color = 'green';
					span.style.borderColor = 'green';
				}
				if (data.emphasis.negative) {
					span.style.color = 'red';
					span.style.borderColor = 'red';
				}
				span.innerHTML = data.emphasis.text;
				statusDiv.appendChild(span);
			}
            
            const qrcodeDiv = document.getElementById('qrcode');
            // This handles QR code Phase II on invoices for Saudi Arabia
            if (qrcodetext && qrcodetext.length > 10 && qrcodeDiv) {
            	qrcodeDiv.innerHTML = '';
            	renderQRCode(qrcodetext, 'qrcode'); 
			} else {
				// This handles QR code on invoices for Saudi Arabia - this is here just temporarily. Better way will be implemented.
    			if (data.type === 'salesinvoice') {
    				if (data.business.country === 'ar-SA' || data.business.country === 'en-SA') {
    
    					function appendTLV(tag, text, byteList) {
    						const encoded = new TextEncoder().encode(text);
    						byteList.push(tag);
    						byteList.push(encoded.length);
    						for (let b of encoded) byteList.push(b);
    					}
    
    					const byteList = [];
    
    					let businessName = 'No name';
    					if (data.business) businessName = data.business.name;
    
    					let vatNumber = '0000000000000';
    					let vatField = data.business.custom_fields.find(item => item.key === 'd96d97e8-c857-42c6-8360-443c06a13de9');
    					if (vatField) vatNumber = vatField.text;
    
    					let timestamp = new Date((data.timestamp - 621355968000000000) / 10000).toISOString();
    
    					let total = 0;
    					let totalElement = document.getElementById('Total');
    					if (totalElement != null) total = parseFloat(totalElement.getAttribute('data-value'));
    
    					let vat = 0;
    					let taxAmounts = document.getElementsByClassName('taxAmount');
    					for (let i = 0; i < taxAmounts.length; i++) {
    						vat += parseFloat(taxAmounts[i].getAttribute('data-value'));
    					}
    
    					appendTLV(1, businessName, byteList);
    					appendTLV(2, vatNumber, byteList);
    					appendTLV(3, timestamp, byteList);
    					appendTLV(4, total.toFixed(2), byteList);
    					appendTLV(5, vat.toFixed(2), byteList);
    
    					// Convert to Uint8Array
    					const tlvBytes = Uint8Array.from(byteList);
    
    					// Convert to Base64
    					const qrData = btoa(String.fromCharCode(...tlvBytes));
    
    					console.log(qrData);
    
    					new QRCode(document.getElementById('qrcode'), {
    						text: qrData,
    						width: 128,
    						height: 128,
    						colorDark: '#000000',
    						colorLight: '#ffffff',
    						correctLevel: QRCode.CorrectLevel.L
    					});
    				}
    			}
			}
        
            function renderQRCode(content, elementId) {
                new QRCode(document.getElementById(elementId), {
                    text: content,
                    width: 160,
                    height: 160,
                    colorDark: '#000000',
                    colorLight: '#fafafa',
                    correctLevel: QRCode.CorrectLevel.L
                });
        
                // Tambahan opsional: parsing isi QR untuk tooltip
                let details = parseQRCodeContent(content);
                if (details.size > 0) {
                    let title = Array.from(details)
                        .map(([tag, value]) => `Tag ${tag} : ${value.join(', ')}`)
                        .join('\n');
                    const qrCodeDiv = document.getElementById(elementId);
                    if (qrCodeDiv) qrCodeDiv.title = title.trim();
                }
            }
        
            function parseQRCodeContent(qrCodeBase64) {
                let details = new Map();
                try {
                    let data = atob(qrCodeBase64.replace(/\+/g, '+'));
                    let index = 0;
                    while (index < data.length) {
                        let tag = data.charCodeAt(index++);
                        let length = data.charCodeAt(index++);
                        let value = data.substr(index, length);
                        index += length;
                        let decodedValue = (tag === 8 || tag === 9)
                            ? [...value].map(c => c.charCodeAt(0).toString(16).padStart(2, '0')).join(' ').toUpperCase()
                            : new TextDecoder('utf-8').decode(new Uint8Array([...value].map(c => c.charCodeAt(0))));
                        details.set(tag, [decodedValue]);
                    }
                } catch (ex) {
                    console.error('Error decoding QR code: ' + ex.message);
                }
                return details;
            }

		}, false);

		// Request context data from parent frame when page loads
		window.addEventListener('load', () =>
			window.parent.postMessage({ type: 'context-request' }, '*')
		);
	</script>
	
</body>
</html>

@AYUB the issue is that your document has a lot of columns and it doesn’t fit horizontally. Please check the latest version (25.6.24.2437) where this should be fixed.

Same problem here. Only way I could temporarily override this is by going to “more settings” in the print menu and selecting the scale as custom and then putting the scale at more or less 87. @lubos fix for this in near future?

I have the same issue…

The problem is sorted with fewer columns, but it’s still there in the document with more columns


Also, this scroll bar is appearing in print preview
Before these problems were not there, an even document had more columns

I can see the issue here. The View screen now assumes A4 portrait orientation by default and whatever doesn’t fit that orientation is overflowed.

@lubos, I wonder if the new View screens using iframe could be made print page agnostic like before?