182 lines
6.4 KiB
JavaScript
182 lines
6.4 KiB
JavaScript
// resources/js/composables/usePrintStruk.js
|
|
import { computed, nextTick } from "vue";
|
|
|
|
export function usePrintStruk(
|
|
transaksi,
|
|
itemsWithMinimal,
|
|
formatDate,
|
|
formatNumber
|
|
) {
|
|
const handlePrint = async () => {
|
|
const printWindow = window.open("", "_blank", "width=900,height=600");
|
|
|
|
// Filter item yang valid
|
|
const items = itemsWithMinimal.value.filter((i) => i.harga_deal > 0);
|
|
const imageUrls = items
|
|
.map((i) => i.produk?.foto?.[0]?.url)
|
|
.filter(Boolean);
|
|
|
|
// 1. Kompresi & encode semua gambar ke base64 (40x40, JPEG 80%)
|
|
const compressedImages = await Promise.all(
|
|
imageUrls.map((url) => compressImageToBase64(url, 40, 40, 0.8))
|
|
);
|
|
|
|
// Ganti URL dengan base64 (fallback ke placeholder jika gagal)
|
|
const imageMap = {};
|
|
imageUrls.forEach((url, i) => {
|
|
imageMap[url] =
|
|
compressedImages[i] ||
|
|
"https://via.placeholder.com/40x40?text=No+Img";
|
|
});
|
|
|
|
const item = itemsWithMinimal.value[0]
|
|
const imgSrc = imageMap[item.produk?.foto?.[0]?.url] || 'https://via.placeholder.com/100x100?text=No+Img'
|
|
|
|
const printContent = `
|
|
<!DOCTYPE html>
|
|
<html lang="id">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<title>Struk #${transaksi.value.kode_transaksi || 'N/A'}</title>
|
|
<style>
|
|
@page { size: var(--page-w, 210mm) var(--page-h, 99mm) portrait; margin:0; }
|
|
*,*::before,*::after{box-sizing:border-box;}
|
|
|
|
:root{
|
|
/* ==== SESUAIKAN DI SINI ==== */
|
|
--page-w: 210mm; /* lebar kertas */
|
|
--page-h: 99mm; /* tinggi kertas */
|
|
--pad: 8mm; /* padding luar */
|
|
--font: 10pt; /* font dasar */
|
|
--img-w: 100px; /* lebar gambar (single-item) */
|
|
--img-h: 100px; /* tinggi gambar */
|
|
--item-h: 126px; /* tinggi baris item (single) */
|
|
--gap: 12px; /* jarak antar elemen */
|
|
--label-w: 60px; /* lebar label pelanggan */
|
|
}
|
|
|
|
body{margin:0;padding:0;font-family:Arial,sans-serif;font-size:var(--font);line-height:1.4;}
|
|
.c{width:var(--page-w);height:var(--page-h);padding:var(--pad);position:relative;}
|
|
|
|
/* ==== KODE TRANSAKSI ==== */
|
|
.kode{position:absolute;top:var(--pad);left:var(--pad);font-weight:bold;font-size:calc(var(--font)*0.9);}
|
|
|
|
/* ==== DATA PELANGGAN ==== */
|
|
.pelanggan{position:absolute;top:var(--pad);right:var(--pad);font-size:calc(var(--font)*0.9);line-height:1.5;}
|
|
.pelanggan .l{display:inline-block;width:var(--label-w);}
|
|
|
|
/* ==== SINGLE ITEM (gambar besar di tengah) ==== */
|
|
.item-box{
|
|
position:absolute;top:35mm;left:var(--pad);right:var(--pad);
|
|
height:var(--item-h);display:flex;align-items:center;
|
|
gap:var(--gap);font-size:calc(var(--font)*0.9);
|
|
}
|
|
.jumlah{font-weight:bold;font-size:1.5em;width:40px;text-align:center;}
|
|
.item-img{
|
|
width:var(--img-w);height:var(--img-h);object-fit:cover;
|
|
border-radius:4px;flex-shrink:0;border:1px solid #ddd;
|
|
}
|
|
.item-info{flex:1;display:flex;justify-content:space-between;align-items:center;}
|
|
.item-name{font-weight:600;max-width:48%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}
|
|
.item-details{display:flex;gap:12px;font-size:calc(var(--font)*0.8);color:#444;}
|
|
|
|
/* ==== SALES ==== */
|
|
.sales{position:absolute;bottom:var(--pad);left:var(--pad);text-align:center;font-size:calc(var(--font)*0.9);}
|
|
.sales .val{font-weight:bold;margin-top:20px;}
|
|
|
|
/* ==== HARGA ==== */
|
|
.harga{position:absolute;bottom:var(--pad);right:var(--pad);text-align:right;font-size:var(--font);}
|
|
.hr{display:flex;justify-content:flex-end;gap:15px;margin-bottom:5px;align-items:center;}
|
|
.hv{font-weight:bold;min-width:110px;text-align:right;}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div class="c">
|
|
|
|
<!-- KODE TRANSAKSI -->
|
|
<div class="kode">${escapeHtml(transaksi.value.kode_transaksi || 'N/A')}</div>
|
|
|
|
<!-- DATA PELANGGAN -->
|
|
<div class="pelanggan">
|
|
<div>${formatDate(transaksi.value.created_at)}</div>
|
|
<div>${escapeHtml(transaksi.value.nama_pembeli || '-')}</div>
|
|
<div>${escapeHtml(transaksi.value.alamat || '-')}</div>
|
|
<div>${escapeHtml(transaksi.value.no_hp || '-')}</div>
|
|
</div>
|
|
|
|
<!-- SINGLE ITEM + JUMLAH -->
|
|
<div class="item-box">
|
|
<div class="jumlah">1</div>
|
|
<img src="${imgSrc}" class="item-img" alt="Produk" />
|
|
<div class="item-info">
|
|
<span class="item-name">${escapeHtml(item.produk?.nama || '')}</span>
|
|
<div class="item-details">
|
|
<span>${escapeHtml(item.posisi_asal || 'Brankas')}</span>
|
|
<span>${item.produk?.berat ? formatNumber(item.produk.berat)+'g' : ''}</span>
|
|
<span>${item.produk?.kadar ? item.produk.kadar+'k' : ''}</span>
|
|
<span>Rp${formatNumber(item.harga_deal)}</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- SALES -->
|
|
<div class="sales">
|
|
<div class="val">${escapeHtml(transaksi.value.nama_sales || '-')}</div>
|
|
</div>
|
|
|
|
<!-- HARGA -->
|
|
<div class="harga">
|
|
<div class="hr"><span class="hv">${(transaksi.value.ongkos_bikin||0).toLocaleString()},-</span></div>
|
|
<div class="hr"><span class="hv">${(transaksi.value.total_harga||0).toLocaleString()},-</span></div>
|
|
</div>
|
|
|
|
</div>
|
|
</body>
|
|
</html>
|
|
`;
|
|
|
|
printWindow.document.write(printContent);
|
|
printWindow.document.close();
|
|
|
|
printWindow.onload = () => {
|
|
setTimeout(() => {
|
|
printWindow.focus();
|
|
printWindow.print();
|
|
printWindow.close();
|
|
}, 300);
|
|
};
|
|
};
|
|
|
|
return { handlePrint };
|
|
}
|
|
|
|
// === Helper: Kompresi Gambar ke Base64 ===
|
|
async function compressImageToBase64(url, width, height, quality = 0.8) {
|
|
return new Promise((resolve) => {
|
|
const img = new Image();
|
|
img.crossOrigin = "anonymous";
|
|
|
|
img.onload = () => {
|
|
const canvas = document.createElement("canvas");
|
|
canvas.width = width;
|
|
canvas.height = height;
|
|
const ctx = canvas.getContext("2d");
|
|
|
|
ctx.drawImage(img, 0, 0, width, height);
|
|
|
|
const base64 = canvas.toDataURL("image/jpeg", quality);
|
|
resolve(base64);
|
|
};
|
|
|
|
img.onerror = () => resolve(null);
|
|
img.src = url + (url.includes("?") ? "&" : "?") + "t=" + Date.now();
|
|
});
|
|
}
|
|
|
|
function escapeHtml(text) {
|
|
if (!text) return "";
|
|
const div = document.createElement("div");
|
|
div.textContent = text;
|
|
return div.innerHTML;
|
|
}
|