Compare commits
2 Commits
cba0c5a43e
...
5a28ed5254
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5a28ed5254 | ||
|
|
f6161cf308 |
@ -33,7 +33,7 @@
|
|||||||
<inputField v-model="namaPembeli" class="h-7 text-sm rounded bg-blue-200 w-full" />
|
<inputField v-model="namaPembeli" class="h-7 text-sm rounded bg-blue-200 w-full" />
|
||||||
|
|
||||||
<div class="text-right font-semibold pr-3">Alamat :</div>
|
<div class="text-right font-semibold pr-3">Alamat :</div>
|
||||||
<inputField v-model="alamat" class="h-7 px-2 text-sm rounded bg-blue-200 w-full" />
|
<inputField v-model="alamat" maxlength="50" class="h-7 px-2 text-sm rounded bg-blue-200 w-full" />
|
||||||
|
|
||||||
<div class="text-right font-semibold pr-3">No.Hp :</div>
|
<div class="text-right font-semibold pr-3">No.Hp :</div>
|
||||||
<inputField v-model="nomorTelepon" class="h-7 px-2 text-sm rounded bg-blue-200 w-full" />
|
<inputField v-model="nomorTelepon" class="h-7 px-2 text-sm rounded bg-blue-200 w-full" />
|
||||||
|
|||||||
@ -1,178 +1,128 @@
|
|||||||
<template>
|
<template>
|
||||||
<div v-if="isOpen"
|
<teleport to="body">
|
||||||
class="text-D pt-serif-regular-italic fixed inset-0 bg-black/75 flex items-center justify-center z-[9999]">
|
<div v-if="isOpen" class="fixed inset-0 bg-black/75 flex items-center justify-center z-[9999] pt-serif-regular-italic text-D">
|
||||||
|
<div class="print-area bg-white w-[1224px] h-[528px] shadow-lg relative overflow-hidden">
|
||||||
|
<!-- Header Kuning -->
|
||||||
|
<div class="bg-yellow-500 h-8 w-full"><div class="bg-D h-6 w-full"></div></div>
|
||||||
|
|
||||||
<!-- print-area untuk fokus saat print -->
|
<div class="p-6 text-sm flex flex-col h-full">
|
||||||
<div class="print-area bg-white w-[1224px] h-[528px] shadow-lg relative overflow-hidden">
|
<!-- Info Toko + Logo + Data Pembeli -->
|
||||||
<div class="bg-yellow-500 h-8 w-full">
|
<div class="relative flex justify-between pb-1 mb-2">
|
||||||
<div class="bg-D h-6 w-full"></div>
|
<div class="flex flex-col gap-2 -mt-5">
|
||||||
</div>
|
<p class="flex items-center gap-2"><i class="fab fa-instagram text-pink-600 text-xl"></i> tokomas_Jakartacitayam</p>
|
||||||
|
<p class="flex items-center gap-2"><i class="fab fa-tiktok text-black text-xl"></i> tokomas_Jakartacitayam</p>
|
||||||
|
<p class="flex items-center gap-2"><i class="fab fa-whatsapp text-green-500 text-xl"></i> 08158851178</p>
|
||||||
|
<p class="text-sm">{{ transaksi.kode_transaksi || 'N/A' }}</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="p-6 text-sm flex flex-col h-full relative">
|
<div class="absolute inset-x-0 top-[-48px] flex justify-center">
|
||||||
<div class="relative flex items-center justify-between top-0 pb-1 mb-2">
|
<img :src="logo" alt="Logo" class="h-40" />
|
||||||
<div class="flex flex-col gap-2 -mt-5">
|
</div>
|
||||||
<p class="flex items-center gap-2">
|
|
||||||
<i class="fab fa-instagram text-pink-600 text-xl"></i> tokomas_Jakartacitayam
|
|
||||||
</p>
|
|
||||||
<p class="flex items-center gap-2">
|
|
||||||
<i class="fab fa-tiktok text-black text-xl"></i> tokomas_Jakartacitayam
|
|
||||||
</p>
|
|
||||||
<p class="flex items-center gap-2">
|
|
||||||
<i class="fab fa-whatsapp text-green-500 text-xl"></i> 08158851178
|
|
||||||
</p>
|
|
||||||
<p class=" text-sm">{{ transaksi.kode_transaksi || 'N/A' }}</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="absolute inset-x-0 top-[-48px] flex flex-col items-center">
|
<div class="grid grid-cols-[130px_1fr] gap-y-1 text-xs items-center -mt-5 z-10">
|
||||||
<img :src="logo" alt="Logo" class="h-40" />
|
<div class="text-right font-semibold pr-3">Tanggal :</div><div>{{ formatDate(transaksi.created_at) }}</div>
|
||||||
</div>
|
<div class="text-right font-semibold pr-3">Nama :</div><div>{{ transaksi.nama_pembeli || '-' }}</div>
|
||||||
|
<div class="text-right font-semibold pr-3">Alamat :</div><div>{{ transaksi.alamat || '-' }}</div>
|
||||||
<div class="grid grid-cols-[130px_1fr] gap-y-1 text-xs items-center -mt-5 relative z-10">
|
<div class="text-right font-semibold pr-3">No.Hp :</div><div>{{ transaksi.no_hp || '-' }}</div>
|
||||||
<div class="text-right font-semibold pr-3">Tanggal :</div>
|
|
||||||
<p class="text-left pl-2">{{ formatDate(transaksi.created_at) }}</p>
|
|
||||||
|
|
||||||
<div class="text-right font-semibold pr-3">Nama :</div>
|
|
||||||
<p class="text-left pl-2">{{ transaksi.nama_pembeli || '-' }}</p>
|
|
||||||
|
|
||||||
<div class="text-right font-semibold pr-3">Alamat :</div>
|
|
||||||
<p class="text-left pl-2">{{ transaksi.alamat || '-' }}</p>
|
|
||||||
|
|
||||||
<div class="text-right font-semibold pr-3">No.Hp :</div>
|
|
||||||
<p class="text-left pl-2">{{ transaksi.no_hp || '-' }}</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="flex mb-1 gap-179">
|
|
||||||
<div class="flex gap-4">
|
|
||||||
<img :src="logo_bca" alt="Logo_bca" class="h-5" />
|
|
||||||
<img :src="logo_bri" alt="Logo_bri" class="h-5" />
|
|
||||||
<img :src="logo_bni" alt="Logo_bni" class="h-5" />
|
|
||||||
</div>
|
|
||||||
<div class="flex gap-4">
|
|
||||||
<img :src="logo_mastercard" alt="Logo_mastercard" class="h-5" />
|
|
||||||
<img :src="logo_visa" alt="Logo_visa" class="h-5" />
|
|
||||||
<img :src="logo_mandiri" alt="Logo_mandiri" class="h-5" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<table class="w-full border-D text-sm table-fixed border-b">
|
|
||||||
<thead>
|
|
||||||
<tr class="border-b border-t border-D">
|
|
||||||
<th class="w-[40px] border-r text-lg border-D">Jml</th>
|
|
||||||
<th class="w-[425px] py-2 text-lg border-r border-D">Item</th>
|
|
||||||
<th class="w-[70px] border-r text-lg border-D">Posisi</th>
|
|
||||||
<th class="w-[40px] border-r text-lg border-D">Berat</th>
|
|
||||||
<th class="w-[40px] border-r text-lg border-D">Kadar</th>
|
|
||||||
<th class="w-[175px] text-lg">Harga</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
|
|
||||||
<tbody>
|
|
||||||
<tr v-for="(item, index) in itemsWithMinimal" :key="index" class="text-center" :style="getRowStyle()">
|
|
||||||
<td class="border-r border-D">
|
|
||||||
<span v-if="item.harga_deal && item.harga_deal > 0">1</span>
|
|
||||||
</td>
|
|
||||||
<td class="flex items-center gap-2 p-2 border-r border-D" :style="getRowStyle()">
|
|
||||||
<img
|
|
||||||
:src="item.produk?.foto?.[0]?.url || 'https://via.placeholder.com/50x50?text=No+Img'"
|
|
||||||
:class="getImageClass()"
|
|
||||||
class="object-cover rounded"
|
|
||||||
/>
|
|
||||||
|
|
||||||
|
|
||||||
<span :class="getTextClass()">{{ item.produk?.nama || '' }}</span>
|
|
||||||
</td>
|
|
||||||
|
|
||||||
<td class="border-r border-D">
|
|
||||||
<span v-if="item.produk?.nama">{{ item.posisi_asal || 'Brankas' }}</span>
|
|
||||||
</td>
|
|
||||||
<td class="border-r border-D">
|
|
||||||
<span v-if="item.produk?.berat">{{ formatNumber(item.produk.berat) }}g</span>
|
|
||||||
</td>
|
|
||||||
<td class="border-r border-D">
|
|
||||||
<span v-if="item.produk?.kadar">{{ item.produk.kadar }}k</span>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<span v-if="item.harga_deal && item.harga_deal > 0">
|
|
||||||
Rp{{ formatNumber(item.harga_deal) }}
|
|
||||||
</span>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
|
|
||||||
|
|
||||||
<div class="flex text-sm mt-2">
|
|
||||||
|
|
||||||
<div class="w-[40%] p-2 text-left">
|
|
||||||
<p class="font-semibold">PERHATIAN</p>
|
|
||||||
<ol class="list-decimal ml-4 text-xs space-y-1">
|
|
||||||
<li>Berat barang telah ditimbang dan disaksikan oleh pembeli.</li>
|
|
||||||
<li>Barang yang dikembalikan menurut harga pasaran dan dipotong ongkos bikin, barang rusak lain harga.</li>
|
|
||||||
<li>Barang yang sudah dibeli berarti sudah diperiksa dan disetujui.</li>
|
|
||||||
<li class="text-red-500">Surat ini harap dibawa pada saat menjual kembali.</li>
|
|
||||||
</ol>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
<div class="w-[20%] p-2 flex flex-col items-center justify-center">
|
|
||||||
<p><strong>Hormat Kami</strong></p>
|
|
||||||
<div class="mt-16 text-sm text-center">
|
|
||||||
<p class="font-semibold">{{ transaksi.nama_sales || '-' }}</p>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Logo Bank -->
|
||||||
|
<div class="flex justify-between mb-1">
|
||||||
|
<div class="flex gap-4">
|
||||||
|
<img :src="logo_bca" class="h-5" /><img :src="logo_bri" class="h-5" /><img :src="logo_bni" class="h-5" />
|
||||||
|
</div>
|
||||||
|
<div class="flex gap-4">
|
||||||
|
<img :src="logo_mastercard" class="h-5" /><img :src="logo_visa" class="h-5" /><img :src="logo_mandiri" class="h-5" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="ml-auto w-[25%] p-2 flex flex-col justify-between">
|
<!-- Tabel Item -->
|
||||||
<div class="space-y-4">
|
<table class="w-full border-D text-sm table-fixed border-b">
|
||||||
|
<thead>
|
||||||
|
<tr class="border-b border-t border-D">
|
||||||
|
<th class="w-[40px] border-r border-D text-lg">Jml</th>
|
||||||
|
<th class="w-[425px] py-2 border-r border-D text-lg">Item</th>
|
||||||
|
<th class="w-[70px] border-r border-D text-lg">Posisi</th>
|
||||||
|
<th class="w-[40px] border-r border-D text-lg">Berat</th>
|
||||||
|
<th class="w-[40px] border-r border-D text-lg">Kadar</th>
|
||||||
|
<th class="w-[175px] text-lg">Harga</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr v-for="item in items" :key="item.id" class="text-center" :style="rowStyle">
|
||||||
|
<td class="border-r border-D"><span v-if="item.harga_deal">1</span></td>
|
||||||
|
<td class="flex items-center gap-2 p-2 border-r border-D" :style="rowStyle">
|
||||||
|
<img :src="item.produk?.foto?.[0]?.url || placeholder" :class="imgClass" class="object-cover rounded" />
|
||||||
|
<span :class="textClass">{{ item.produk?.nama || '' }}</span>
|
||||||
|
</td>
|
||||||
|
<td class="border-r border-D">{{ item.posisi_asal || 'Brankas' }}</td>
|
||||||
|
<td class="border-r border-D">{{ item.produk?.berat ? formatNumber(item.produk.berat) + 'g' : '' }}</td>
|
||||||
|
<td class="border-r border-D">{{ item.produk?.kadar ? item.produk.kadar + 'k' : '' }}</td>
|
||||||
|
<td>Rp{{ item.harga_deal ? formatNumber(item.harga_deal) : '' }}</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
<div class="flex items-start justify-between">
|
<!-- Footer: Perhatian + Sales + Harga + Tombol -->
|
||||||
<div class="flex flex-col">
|
<div class="flex text-sm mt-2">
|
||||||
<p class="font-semibold">Ongkos bikin</p>
|
<div class="w-[40%] p-2 text-left">
|
||||||
<p class="text-red-500 text-xs">diluar harga jual</p>
|
<p class="font-semibold">PERHATIAN</p>
|
||||||
|
<ol class="list-decimal ml-4 text-xs space-y-1">
|
||||||
|
<li>Berat barang telah ditimbang dan disaksikan oleh pembeli.</li>
|
||||||
|
<li>Barang yang dikembalikan menurut harga pasaran dan dipotong ongkos bikin, barang rusak lain harga.</li>
|
||||||
|
<li>Barang yang sudah dibeli berarti sudah diperiksa dan disetujui.</li>
|
||||||
|
<li class="text-red-500">Surat ini harap dibawa pada saat menjual kembali.</li>
|
||||||
|
</ol>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="w-[20%] p-2 flex flex-col items-center justify-center">
|
||||||
|
<p><strong>Hormat Kami</strong></p>
|
||||||
|
<p class="mt-16 font-semibold">{{ transaksi.nama_sales || '-' }}</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="ml-auto w-[25%] p-2 flex flex-col justify-between">
|
||||||
|
<div class="space-y-4">
|
||||||
|
<div class="flex items-start justify-between">
|
||||||
|
<div>
|
||||||
|
<p class="font-semibold">Ongkos bikin</p>
|
||||||
|
<p class="text-red-500 text-xs">diluar harga jual</p>
|
||||||
|
</div>
|
||||||
|
<div class="flex p-1 items-center w-40 bg-B rounded-sm text-sm">
|
||||||
|
<span>Rp</span>
|
||||||
|
<span class="w-full text-left pl-1">{{ (transaksi.ongkos_bikin || 0).toLocaleString() }},-</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex p-1 items-center w-40 bg-B rounded-sm">
|
<div class="flex items-center justify-between -mt-4">
|
||||||
<p>Rp</p>
|
<p class="font-semibold">Total Harga</p>
|
||||||
<p class="px-2 pl-0 text-left text-sm w-full">
|
<div class="flex items-center w-40 text-sm">
|
||||||
{{ (transaksi.ongkos_bikin || 0).toLocaleString() }},-
|
<span>Rp</span>
|
||||||
</p>
|
<span class="w-full text-left pl-1 py-1">{{ (transaksi.total_harga || 0).toLocaleString() }},-</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Total -->
|
<div class="flex justify-end gap-2 mt-4 no-print">
|
||||||
<div class="flex items-center justify-between -mt-4">
|
<button @click="$emit('close')" class="bg-gray-400 text-white px-6 py-2 rounded">Tutup</button>
|
||||||
<p class="font-semibold">Total Harga</p>
|
<button @click="handlePrint" class="bg-C text-white px-6 py-2 rounded">Print</button>
|
||||||
<div class="flex items-center w-40">
|
|
||||||
<p>Rp</p>
|
|
||||||
<p class="px-3 pl-0 py-1 text-left text-sm w-full">
|
|
||||||
{{ (transaksi.total_harga || 0).toLocaleString() }},-
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Tombol -->
|
|
||||||
<div class="flex justify-end gap-2 mt-4 no-print">
|
|
||||||
<button @click="$emit('close')" class="bg-gray-400 text-white px-6 py-2 rounded">
|
|
||||||
Tutup
|
|
||||||
</button>
|
|
||||||
<button @click="handlePrint" class="bg-C text-white px-6 py-2 rounded">
|
|
||||||
Print
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<p class="absolute p-8 bottom-0 left-0 w-full text-left text-xs bg-D text-white py-1">
|
<p class="absolute bottom-0 left-0 w-full bg-D text-white text-xs py-1 pl-8">
|
||||||
Terima kasih sudah berbelanja dengan kami
|
Terima kasih sudah berbelanja dengan kami
|
||||||
</p>
|
</p>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</teleport>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { computed } from 'vue'
|
import { computed } from 'vue'
|
||||||
|
import { usePrintStruk } from '@/composables/usePrintStruk'
|
||||||
|
|
||||||
|
// === Assets ===
|
||||||
import logo from '@/../images/logo.png'
|
import logo from '@/../images/logo.png'
|
||||||
import logo_bca from '@/../images/logo_bca.png'
|
import logo_bca from '@/../images/logo_bca.png'
|
||||||
import logo_bri from '@/../images/logo_bri.png'
|
import logo_bri from '@/../images/logo_bri.png'
|
||||||
@ -180,123 +130,62 @@ import logo_bni from '@/../images/logo_bni.png'
|
|||||||
import logo_mastercard from '@/../images/logo_mastercard.png'
|
import logo_mastercard from '@/../images/logo_mastercard.png'
|
||||||
import logo_visa from '@/../images/logo_visa.png'
|
import logo_visa from '@/../images/logo_visa.png'
|
||||||
import logo_mandiri from '@/../images/logo_mandiri.png'
|
import logo_mandiri from '@/../images/logo_mandiri.png'
|
||||||
|
const placeholder = 'https://via.placeholder.com/50x50?text=No+Img'
|
||||||
|
|
||||||
|
// === Props & Emits ===
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
isOpen: {
|
isOpen: Boolean,
|
||||||
type: Boolean,
|
transaksi: { type: Object, default: () => ({}) }
|
||||||
default: false,
|
|
||||||
},
|
|
||||||
transaksi: {
|
|
||||||
type: Object,
|
|
||||||
default: () => ({})
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
|
|
||||||
const emit = defineEmits(['close'])
|
const emit = defineEmits(['close'])
|
||||||
|
|
||||||
const formatDate = (dateString) => {
|
// === Helpers ===
|
||||||
if (!dateString) return '-'
|
const formatDate = (d) => {
|
||||||
|
if (!d) return '-'
|
||||||
|
const date = new Date(d)
|
||||||
const days = ['Minggu','Senin','Selasa','Rabu','Kamis','Jumat','Sabtu']
|
const days = ['Minggu','Senin','Selasa','Rabu','Kamis','Jumat','Sabtu']
|
||||||
const months = ['01','02','03','04','05','06','07','08','09','10','11','12']
|
const months = ['01','02','03','04','05','06','07','08','09','10','11','12']
|
||||||
const date = new Date(dateString)
|
return `${days[date.getDay()]}, ${String(date.getDate()).padStart(2,'0')}-${months[date.getMonth()]}-${date.getFullYear()}`
|
||||||
const dayName = days[date.getDay()]
|
|
||||||
const day = String(date.getDate()).padStart(2, '0')
|
|
||||||
const month = months[date.getMonth()]
|
|
||||||
const year = date.getFullYear()
|
|
||||||
return `${dayName}, ${day}-${month}-${year}`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const itemsWithMinimal = computed(() => {
|
const formatNumber = (n) => n ? parseFloat(n).toLocaleString('id-ID') : ''
|
||||||
const items = props.transaksi.itemTransaksi ||
|
|
||||||
props.transaksi.items ||
|
|
||||||
props.transaksi.item_transaksi ||
|
|
||||||
[]
|
|
||||||
const arr = [...items]
|
|
||||||
|
|
||||||
if (arr.length === 0) arr.push({ produk: {}, harga_deal: 0, posisi_asal: '' })
|
// === Items ===
|
||||||
return arr
|
const items = computed(() => {
|
||||||
|
const raw = props.transaksi.itemTransaksi ||
|
||||||
|
props.transaksi.items ||
|
||||||
|
props.transaksi.item_transaksi || []
|
||||||
|
const arr = [...raw].filter(i => i.harga_deal > 0)
|
||||||
|
return arr.length ? arr : [{ produk: {}, harga_deal: 0, posisi_asal: '' }]
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// === Dynamic Styles ===
|
||||||
|
const isSingle = computed(() => items.value.length === 1)
|
||||||
|
const rowStyle = computed(() => ({ height: isSingle.value ? '126px' : '63px' }))
|
||||||
|
const imgClass = computed(() => isSingle.value ? 'w-25 h-25' : 'w-12 h-12')
|
||||||
|
const textClass = computed(() => isSingle.value ? 'text-lg font-medium' : 'text-sm')
|
||||||
|
|
||||||
const getRowStyle = () => {
|
// === Print ===
|
||||||
if (itemsWithMinimal.value.length === 1) {
|
const { handlePrint } = usePrintStruk(
|
||||||
return { height: '126px' }
|
computed(() => props.transaksi),
|
||||||
}
|
items,
|
||||||
return { height: '63px' }
|
formatDate,
|
||||||
}
|
formatNumber
|
||||||
const getImageClass = () => {
|
)
|
||||||
if (itemsWithMinimal.value.length === 1) {
|
|
||||||
return 'w-25 h-25'
|
|
||||||
}
|
|
||||||
return 'w-12 h-12'
|
|
||||||
}
|
|
||||||
const getTextClass = () => {
|
|
||||||
if (itemsWithMinimal.value.length === 1) {
|
|
||||||
return 'text-lg font-medium'
|
|
||||||
}
|
|
||||||
return 'text-sm'
|
|
||||||
}
|
|
||||||
|
|
||||||
const handlePrint = () => {
|
|
||||||
window.print()
|
|
||||||
}
|
|
||||||
|
|
||||||
const formatNumber = (number) => {
|
|
||||||
if (!number) return 0
|
|
||||||
return parseFloat(number).toLocaleString('id-ID')
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
@import url('https://fonts.googleapis.com/css2?family=PT+Serif:ital,wght@0,400;0,700;1,400;1,700&display=swap');
|
@import url('https://fonts.googleapis.com/css2?family=PT+Serif:ital,wght@0,400;0,700;1,400;1,700&display=swap');
|
||||||
|
.pt-serif-regular-italic { font-family: "PT Serif", serif; font-weight: 400; }
|
||||||
.pt-serif-regular-italic {
|
|
||||||
font-family: "PT Serif", serif;
|
|
||||||
font-weight: 400;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@media print {
|
@media print {
|
||||||
@page {
|
@page { size: A4; margin: 0; }
|
||||||
size: A4;
|
* { visibility: hidden !important; }
|
||||||
margin: 0;
|
.print-area, .print-area * { visibility: visible !important; -webkit-print-color-adjust: exact; print-color-adjust: exact; }
|
||||||
}
|
|
||||||
|
|
||||||
html, body {
|
|
||||||
height: 100%;
|
|
||||||
overflow: hidden;
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Sembunyikan semua elemen di luar print-area */
|
|
||||||
body * {
|
|
||||||
visibility: hidden !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.print-area * {
|
|
||||||
visibility: visible !important;
|
|
||||||
-webkit-print-color-adjust: exact !important;
|
|
||||||
print-color-adjust: exact !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.print-area {
|
.print-area {
|
||||||
position: fixed !important;
|
position: fixed !important; top: 0 !important; left: 0 !important;
|
||||||
top: 0 !important;
|
width: 1224px; height: 528px; transform: scale(0.6673); transform-origin: top left;
|
||||||
left: 0 !important;
|
|
||||||
width: 1224px;
|
|
||||||
height: 528px;
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
transform: scale(0.6673);
|
|
||||||
transform-origin: top left;
|
|
||||||
page-break-after: avoid !important;
|
|
||||||
page-break-inside: avoid !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Hilangkan tombol tutup & print */
|
|
||||||
.no-print {
|
|
||||||
display: none !important;
|
|
||||||
}
|
}
|
||||||
|
.no-print { display: none !important; }
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
181
resources/js/composables/usePrintStruk.js
Normal file
181
resources/js/composables/usePrintStruk.js
Normal file
@ -0,0 +1,181 @@
|
|||||||
|
// 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;
|
||||||
|
}
|
||||||
@ -1,27 +1,61 @@
|
|||||||
<template>
|
<template>
|
||||||
<mainLayout>
|
<div class="min-h-screen bg-gray-50 p-8">
|
||||||
<div class="home p-6">
|
<div class="max-w-4xl mx-auto">
|
||||||
<h1 class="text-3xl font-bold text-D mb-4">Contoh penggunaan halaman</h1>
|
<h1 class="text-3xl font-bold mb-6 text-center">Test Print Struk</h1>
|
||||||
|
|
||||||
<!-- Komponen Struk -->
|
<StrukView
|
||||||
<StrukOverlay :isOpen="true" />
|
:is-open="true"
|
||||||
|
:transaksi="dummyTransaksi"
|
||||||
<hr class="my-6 border-D" />
|
@close="isOpen = false"
|
||||||
<h1 class="text-xl font-bold text-D mb-4">Contoh grid</h1>
|
/>
|
||||||
<div class="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 gap-4">
|
|
||||||
<div v-for="value in data" :key="value" class="p-5 shadow-lg rounded-lg shadow-C bg-A text-D text-lg">
|
|
||||||
Item: {{ value }}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</mainLayout>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref } from 'vue'
|
import { ref } from 'vue'
|
||||||
import mainLayout from '../layouts/mainLayout.vue'
|
import StrukView from '../components/StrukView.vue'
|
||||||
import StrukOverlay from '../components/StrukOverlay.vue' // pastikan path sesuai
|
|
||||||
|
|
||||||
const message = ref("Style dan message dari script dan style di dalam halaman")
|
// === Dummy Data ===
|
||||||
const data = ref([1, 2, 3, 4, 5])
|
const dummyTransaksi = {
|
||||||
</script>
|
kode_transaksi: 'TRX20250001',
|
||||||
|
created_at: '2025-10-28T10:30:00',
|
||||||
|
nama_pembeli: 'Budi Santoso',
|
||||||
|
alamat: 'Jl. Merdeka',
|
||||||
|
no_hp: '08123456789',
|
||||||
|
nama_sales: 'Rina',
|
||||||
|
ongkos_bikin: 100000,
|
||||||
|
total_harga: 2600000,
|
||||||
|
itemTransaksi: [
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
harga_deal: 2500000,
|
||||||
|
posisi_asal: 'Etalase Depan',
|
||||||
|
produk: {
|
||||||
|
nama: 'Gelang rantai 5 buah clover merah',
|
||||||
|
berat: 5.2,
|
||||||
|
kadar: 18,
|
||||||
|
foto: [
|
||||||
|
{ url: 'http://localhost:8000/storage/foto/hVmf9TF2AfnsNUELXtW3F7q30QVCb0S9L1zWnicY.jpg'}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// {
|
||||||
|
// id: 2,
|
||||||
|
// harga_deal: 100000,
|
||||||
|
// posisi_asal: 'Brankas',
|
||||||
|
// produk: {
|
||||||
|
// nama: 'Gelang serut daun shimmer mp (mas putih)',
|
||||||
|
// berat: 1.0,
|
||||||
|
// kadar: 24,
|
||||||
|
// foto: [
|
||||||
|
// { url: 'http://localhost:8000/storage/foto/dF0UWskzLYnuOH8ch8DEjz7ZeBWa8wXbyhtYRjRy.jpg'}
|
||||||
|
// ]
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
// === Modal State ===
|
||||||
|
const isOpen = ref(false)
|
||||||
|
</script>
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user