const Templates = {
list: [],
loadAll() {
const custom = JSON.parse(localStorage.getItem('pro.customTemplates') || '[]');
const builtin = [
{
id: 'modern-80', name: 'โมเดิร์น 80mm', paperSize: '80mm', components: [
{type: 'logo', data: {url: '', width: '120px', align: 'center'}},
{type: 'store-info', data: {inline: true}},
{type: 'divider', data: {style: 'solid', color: '#333', thickness: '2px'}},
{type: 'receipt-info', data: {inline: true}},
{type: 'items-table', data: {}},
{type: 'divider', data: {style: 'dashed', color: '#ccc'}},
{type: 'summary', data: {}},
{type: 'payment-info', data: {method: 'เงินสด'}},
{type: 'footer', data: {text: 'ขอบคุณที่ใช้บริการ', align: 'center', size: '12px', inline: true}},
{type: 'promptpay', data: {useTotal: true}}
]
},
{
id: 'invoice-a4', name: 'Invoice A4', paperSize: 'a4', components: [
{type: 'logo', data: {url: '', width: '180px', align: 'center'}},
{type: 'heading', data: {text: 'INVOICE', align: 'right', size: '28px', inline: true}},
{type: 'divider', data: {style: 'solid', color: '#4a90e2'}},
{type: 'store-info', data: {inline: true}},
{type: 'receipt-info', data: {inline: true}},
{type: 'items-table', data: {}},
{type: 'summary', data: {}},
{type: 'footer', data: {text: 'ชำระเงินภายใน 7 วัน', align: 'left', size: '12px', inline: true}},
{type: 'barcode', data: {}}
]
}
];
this.list = [...builtin, ...custom];
},
saveCurrent(name, desc) {
const tpl = {
id: 'custom-' + Date.now(),
name, desc,
paperSize: document.getElementById('paper-size').value,
components: Pro.components.map(c => ({type: c.type, data: {...c.data}})),
// store snapshot as part of this custom template so template can include shop defaults
store: {...Pro.data.store},
vatRate: Pro.data.vatRate
};
const arr = JSON.parse(localStorage.getItem('pro.customTemplates') || '[]');
arr.push(tpl);
localStorage.setItem('pro.customTemplates', JSON.stringify(arr));
// Best-effort: also save template to server storage/templates/
try {
fetch('api/save_template.php', {method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify(tpl)}).then(r => r.json()).then(res => {
// Optionally: could store returned path in template metadata
}).catch(() => {});
} catch (e) {}
return tpl;
},
renderList() {
const el = document.getElementById('templates-list');
el.innerHTML = this.list.map(t => `
<div class="template-card" data-id="${t.id}">
<div class="template-name"><i class="fa-regular fa-file-lines"></i> ${t.name}</div>
<div class="template-desc">${t.paperSize.toUpperCase()}</div>
</div>
`).join('');
el.querySelectorAll('.template-card').forEach(card => {
card.onclick = () => loadTemplateById(card.dataset.id);
});
}
};
function loadTemplateById(id) {
const tpl = Templates.list.find(t => t.id === id);
if (!tpl) return;
clearCanvas(true);
document.getElementById('paper-size').value = tpl.paperSize;
changePaperSize();
Pro.components = (tpl.components || []).map(c => ({id: 'comp-' + (Pro.idCounter++), type: c.type, data: {...c.data}}));
document.getElementById('drop-zone').innerHTML = '';
Pro.components.forEach(renderComponent);
// If template contains store info, merge it into current store so templates load with shop defaults
if (tpl.store) {
// Merge but prefer existing non-empty values; only apply template store values that are non-empty
const safe = Object.fromEntries(Object.entries(tpl.store).filter(([k, v]) => v !== undefined && v !== ''));
Pro.data.store = {...Pro.data.store, ...safe};
try {localStorage.setItem('pro.store', JSON.stringify(Pro.data.store));} catch (e) {}
}
if (tpl.vatRate !== undefined) {Pro.data.vatRate = tpl.vatRate; try {localStorage.setItem('pro.vatRate', String(Pro.data.vatRate));} catch (e) {} }
fillDataForm();
setTimeout(() => {renderBarcodes(); renderQRCodes(); bindInlineEditors();}, 30);
localStorage.setItem('pro.lastTemplate', id);
}