[update] dari struoverlay langsung ke strukVIew
This commit is contained in:
parent
878e652630
commit
d442e2e691
@ -2,9 +2,23 @@
|
||||
<ConfirmDeleteModal v-if="showDeleteModal" :isOpen="showDeleteModal" title="Konfirmasi"
|
||||
message="Yakin ingin menghapus item ini?" @confirm="hapusPesanan" @cancel="closeDeleteModal" />
|
||||
|
||||
<!-- ==== TAMBAHAN: Struk Overlay ==== -->
|
||||
<StrukOverlay v-if="showStruk" :isOpen="showStruk" :pesanan="pesanan" :total="total" @close="closeStruk" />
|
||||
<!-- ==== END TAMBAHAN ==== -->
|
||||
<!-- Struk Input Overlay -->
|
||||
<StrukOverlay
|
||||
v-if="showStruk"
|
||||
:isOpen="showStruk"
|
||||
:pesanan="pesanan"
|
||||
:total="total"
|
||||
@close="closeStruk"
|
||||
@transaksi-saved="handleTransaksiSaved"
|
||||
/>
|
||||
|
||||
<!-- Struk View (Print) Overlay -->
|
||||
<StrukView
|
||||
v-if="showStrukView"
|
||||
:isOpen="showStrukView"
|
||||
:transaksi="savedTransaksi"
|
||||
@close="closeStrukView"
|
||||
/>
|
||||
|
||||
<div class="p-2 sm:p-4">
|
||||
<!-- Grid Form & Total -->
|
||||
@ -115,13 +129,15 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
<script setup>
|
||||
import { ref, computed } from "vue";
|
||||
import InputField from "./InputField.vue";
|
||||
import axios from "axios";
|
||||
import ConfirmDeleteModal from "./ConfirmDeleteModal.vue";
|
||||
import StrukOverlay from "./StrukOverlay.vue";
|
||||
import StrukView from "./StrukView.vue";
|
||||
|
||||
// Emit untuk komunikasi dengan parent
|
||||
const emit = defineEmits(['transaksi-saved']);
|
||||
|
||||
const kodeItem = ref("");
|
||||
const info = ref("");
|
||||
@ -131,21 +147,21 @@ const hargaJualFormatted = ref("");
|
||||
const item = ref(null);
|
||||
const loadingItem = ref(false);
|
||||
const pesanan = ref([]);
|
||||
const showDeleteModal = ref(false)
|
||||
const deleteIndex = ref(null)
|
||||
const showDeleteModal = ref(false);
|
||||
const deleteIndex = ref(null);
|
||||
|
||||
const showStruk = ref(false);
|
||||
const showStrukView = ref(false);
|
||||
const savedTransaksi = ref(null);
|
||||
|
||||
let errorTimeout = null;
|
||||
let infoTimeout = null;
|
||||
|
||||
// Format angka dengan pemisah ribuan
|
||||
const formatNumber = (num) => {
|
||||
if (!num) return "";
|
||||
return num.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ".");
|
||||
};
|
||||
|
||||
// Menghapus format dan mengambil angka asli
|
||||
const unformatNumber = (str) => {
|
||||
if (!str) return null;
|
||||
const cleaned = str.replace(/\./g, "");
|
||||
@ -153,14 +169,11 @@ const unformatNumber = (str) => {
|
||||
return isNaN(number) ? null : number;
|
||||
};
|
||||
|
||||
// Handler untuk format input harga
|
||||
const formatHargaInput = (event) => {
|
||||
const value = event.target.value;
|
||||
// Hapus semua karakter selain angka
|
||||
const cleanValue = value.replace(/\D/g, "");
|
||||
|
||||
if (cleanValue) {
|
||||
// Format dengan pemisah ribuan
|
||||
const formatted = formatNumber(cleanValue);
|
||||
hargaJualFormatted.value = formatted;
|
||||
hargaJual.value = parseInt(cleanValue);
|
||||
@ -170,7 +183,6 @@ const formatHargaInput = (event) => {
|
||||
}
|
||||
};
|
||||
|
||||
// Hanya izinkan angka saat mengetik
|
||||
const onlyNumbers = (event) => {
|
||||
const char = String.fromCharCode(event.which);
|
||||
if (!/[0-9]/.test(char)) {
|
||||
@ -196,11 +208,8 @@ const inputItem = async () => {
|
||||
});
|
||||
item.value = response.data;
|
||||
hargaJual.value = item.value.produk.harga_jual;
|
||||
// Format harga untuk tampilan
|
||||
hargaJualFormatted.value = formatNumber(item.value.produk.harga_jual);
|
||||
|
||||
// console.log(item.value);
|
||||
|
||||
if (item.value.is_sold) {
|
||||
throw new Error("Item sudah terjual");
|
||||
}
|
||||
@ -231,8 +240,7 @@ const tambahItem = () => {
|
||||
if (!item.value || !hargaJual.value) {
|
||||
error.value = "Scan atau masukkan kode item untuk dijual.";
|
||||
if (kodeItem.value) {
|
||||
error.value =
|
||||
"Masukkan harga jual, atau input dari kode item lagi.";
|
||||
error.value = "Masukkan harga jual, atau input dari kode item lagi.";
|
||||
}
|
||||
clearTimeout(errorTimeout);
|
||||
errorTimeout = setTimeout(() => {
|
||||
@ -241,14 +249,12 @@ const tambahItem = () => {
|
||||
return;
|
||||
}
|
||||
|
||||
// harga deal
|
||||
item.value.kode_item = Number(kodeItem.value);
|
||||
item.value.harga_deal = Number(hargaJual.value);
|
||||
item.value.posisi = item.value.nampan ? item.value.nampan.nama : "Brankas";
|
||||
|
||||
pesanan.value.push(item.value);
|
||||
|
||||
// Reset input fields
|
||||
kodeItem.value = "";
|
||||
hargaJual.value = null;
|
||||
hargaJualFormatted.value = "";
|
||||
@ -258,23 +264,22 @@ const tambahItem = () => {
|
||||
};
|
||||
|
||||
const openDeleteModal = (index) => {
|
||||
deleteIndex.value = index
|
||||
showDeleteModal.value = true
|
||||
}
|
||||
deleteIndex.value = index;
|
||||
showDeleteModal.value = true;
|
||||
};
|
||||
|
||||
const closeDeleteModal = () => {
|
||||
showDeleteModal.value = false
|
||||
deleteIndex.value = null
|
||||
}
|
||||
showDeleteModal.value = false;
|
||||
deleteIndex.value = null;
|
||||
};
|
||||
|
||||
const hapusPesanan = () => {
|
||||
if (deleteIndex.value !== null) {
|
||||
pesanan.value.splice(deleteIndex.value, 1)
|
||||
pesanan.value.splice(deleteIndex.value, 1);
|
||||
}
|
||||
closeDeleteModal()
|
||||
}
|
||||
closeDeleteModal();
|
||||
};
|
||||
|
||||
// ==== MODIFIKASI: konfirmasiPenjualan sekarang menampilkan struk ====
|
||||
const konfirmasiPenjualan = () => {
|
||||
if (pesanan.value.length === 0) {
|
||||
error.value = "Belum ada item yang dipesan.";
|
||||
@ -284,17 +289,37 @@ const konfirmasiPenjualan = () => {
|
||||
}, 5000);
|
||||
return;
|
||||
}
|
||||
console.log(pesanan.value)
|
||||
// Tampilkan struk overlay
|
||||
showStruk.value = true;
|
||||
};
|
||||
// ==== END MODIFIKASI ====
|
||||
|
||||
// ==== TAMBAHAN: Fungsi untuk menutup struk ====
|
||||
const closeStruk = () => {
|
||||
showStruk.value = false;
|
||||
};
|
||||
// ==== END TAMBAHAN ====
|
||||
|
||||
const closeStrukView = () => {
|
||||
showStrukView.value = false;
|
||||
savedTransaksi.value = null;
|
||||
|
||||
// Reset pesanan setelah menutup struk view
|
||||
pesanan.value = [];
|
||||
};
|
||||
|
||||
// Handler ketika transaksi berhasil disimpan
|
||||
const handleTransaksiSaved = (transaksiData) => {
|
||||
// Tutup StrukOverlay
|
||||
showStruk.value = false;
|
||||
|
||||
// Simpan data transaksi
|
||||
savedTransaksi.value = transaksiData;
|
||||
|
||||
// Emit ke parent (Kasir.vue)
|
||||
emit('transaksi-saved', transaksiData);
|
||||
|
||||
// Buka StrukView untuk print
|
||||
setTimeout(() => {
|
||||
showStrukView.value = true;
|
||||
}, 300);
|
||||
};
|
||||
|
||||
const total = computed(() => {
|
||||
let sum = 0;
|
||||
|
||||
@ -64,7 +64,6 @@
|
||||
</thead>
|
||||
|
||||
<tbody>
|
||||
<!-- Item rows dengan dynamic height -->
|
||||
<tr v-for="(item, index) in props.pesanan" :key="index"
|
||||
class="text-center"
|
||||
:style="getRowStyle()">
|
||||
@ -95,9 +94,7 @@
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<!-- Bagian bawah -->
|
||||
<div class="flex text-sm mt-2">
|
||||
<!-- PERHATIAN -->
|
||||
<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">
|
||||
@ -109,17 +106,14 @@
|
||||
</ol>
|
||||
</div>
|
||||
|
||||
<!-- SALES -->
|
||||
<div class="w-[20%] p-2 flex flex-col items-center justify-center">
|
||||
<p><strong>Hormat Kami</strong></p>
|
||||
<inputSelect v-model="selectedSales" :options="salesOptions"
|
||||
class="mt-16 text-sm rounded bg-B cursor-pointer !w-[160px] text-center [option]:text-left" />
|
||||
</div>
|
||||
|
||||
<!-- ONGKOS & TOTAL -->
|
||||
<div class="ml-auto w-[25%] p-2 flex flex-col justify-between">
|
||||
<div class="space-y-4">
|
||||
<!-- Ongkos bikin -->
|
||||
<div class="flex items-start justify-between ">
|
||||
<div class="flex flex-col ">
|
||||
<p class="font-semibold">Ongkos bikin</p>
|
||||
@ -132,7 +126,6 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Total -->
|
||||
<div class="flex items-center justify-between -mt-4">
|
||||
<p class="font-semibold">Total Harga</p>
|
||||
<div class="flex items-center w-40">
|
||||
@ -144,13 +137,12 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Tombol -->
|
||||
<div class="flex justify-end gap-2 mt-4">
|
||||
<button @click="$emit('close')" class="bg-gray-400 text-white px-6 py-2 rounded">
|
||||
Batal
|
||||
</button>
|
||||
<button @click="handleSimpan" class="bg-C text-white px-6 py-2 rounded">
|
||||
Simpan
|
||||
<button @click="handleSimpan" :disabled="isSaving" class="bg-C text-white px-6 py-2 rounded disabled:opacity-50">
|
||||
{{ isSaving ? 'Menyimpan...' : 'Simpan' }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@ -162,13 +154,11 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Simple Toast Alert -->
|
||||
<div v-if="showToast"
|
||||
class="fixed top-4 left-1/2 transform -translate-x-1/2 z-[10001]
|
||||
transition-all duration-300 ease-in-out"
|
||||
:class="toastClasses">
|
||||
<div class="flex items-center gap-2 px-4 py-3 rounded-lg shadow-lg max-w-sm">
|
||||
<!-- Icon -->
|
||||
<div class="flex-shrink-0">
|
||||
<svg v-if="toastType === 'error'" class="w-5 h-5" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zM8.707 7.293a1 1 0 00-1.414 1.414L8.586 10l-1.293 1.293a1 1 0 101.414 1.414L10 11.414l1.293 1.293a1 1 0 001.414-1.414L11.414 10l1.293-1.293a1 1 0 00-1.414-1.414L10 8.586 8.707 7.293z" clip-rule="evenodd" />
|
||||
@ -180,8 +170,6 @@
|
||||
<path fill-rule="evenodd" d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7-4a1 1 0 11-2 0 1 1 0 012 0zM9 9a1 1 0 000 2v3a1 1 0 001 1h1a1 1 0 100-2v-3a1 1 0 00-1-1H9z" clip-rule="evenodd" />
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
<!-- Message -->
|
||||
<p class="text-sm font-medium">{{ toastMessage }}</p>
|
||||
</div>
|
||||
</div>
|
||||
@ -198,7 +186,6 @@ import logo_visa from '@/../images/logo_visa.png'
|
||||
import logo_mandiri from '@/../images/logo_mandiri.png'
|
||||
import inputField from '@/components/InputField.vue'
|
||||
import inputSelect from '@/components/InputSelect.vue'
|
||||
|
||||
import axios from 'axios'
|
||||
|
||||
const props = defineProps({
|
||||
@ -216,7 +203,7 @@ const props = defineProps({
|
||||
}
|
||||
})
|
||||
|
||||
const emit = defineEmits(['close', 'confirm'])
|
||||
const emit = defineEmits(['close', 'confirm', 'transaksi-saved'])
|
||||
|
||||
const namaPembeli = ref('')
|
||||
const nomorTelepon = ref('')
|
||||
@ -225,10 +212,10 @@ const ongkosBikin = ref(0)
|
||||
const selectedSales = ref(null)
|
||||
const salesOptions = ref([])
|
||||
const ongkosBikinFormatted = ref("")
|
||||
const isSaving = ref(false)
|
||||
|
||||
// Simple Toast State
|
||||
const showToast = ref(false)
|
||||
const toastType = ref('error') // 'error', 'success', 'info'
|
||||
const toastType = ref('error')
|
||||
const toastMessage = ref('')
|
||||
|
||||
const toastClasses = computed(() => {
|
||||
@ -245,30 +232,25 @@ const grandTotal = computed(() => {
|
||||
return props.total + (ongkosBikin.value || 0)
|
||||
})
|
||||
|
||||
// Fungsi untuk menentukan style row berdasarkan jumlah item
|
||||
const getRowStyle = () => {
|
||||
if (props.pesanan.length === 1) {
|
||||
return { height: '126px' } // 2x lipat dari tinggi normal (48px)
|
||||
return { height: '126px' }
|
||||
}
|
||||
return { height: '63px' } // Tinggi normal
|
||||
return { height: '63px' }
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Fungsi untuk menentukan class gambar berdasarkan jumlah item
|
||||
const getImageClass = () => {
|
||||
if (props.pesanan.length === 1) {
|
||||
return 'w-25 h-25' // 2x lipat dari ukuran normal (w-10 h-10)
|
||||
return 'w-25 h-25'
|
||||
}
|
||||
return 'w-12 h-12' // Ukuran normal
|
||||
return 'w-12 h-12'
|
||||
}
|
||||
|
||||
// Fungsi untuk menentukan class text berdasarkan jumlah item
|
||||
const getTextClass = () => {
|
||||
if (props.pesanan.length === 1) {
|
||||
return 'text-lg font-medium' // Text lebih besar untuk single item
|
||||
return 'text-lg font-medium'
|
||||
}
|
||||
return 'text-sm' // Text normal
|
||||
return 'text-sm'
|
||||
}
|
||||
|
||||
const getCurrentDate = () => {
|
||||
@ -290,7 +272,6 @@ const generateTransactionCode = () => {
|
||||
return `TRS-${timestamp}`
|
||||
}
|
||||
|
||||
// Simple Toast Function
|
||||
const showSimpleToast = (type, message, duration = 3000) => {
|
||||
toastType.value = type
|
||||
toastMessage.value = message
|
||||
@ -348,14 +329,16 @@ const handleSimpan = () => {
|
||||
nama_pembeli: namaPembeli.value,
|
||||
no_hp: nomorTelepon.value,
|
||||
alamat: alamat.value,
|
||||
ongkos_bikin: ongkosBikin.value || 0, // Pastikan nama field benar
|
||||
ongkos_bikin: ongkosBikin.value || 0,
|
||||
total_harga: grandTotal.value,
|
||||
items: props.pesanan
|
||||
})
|
||||
}
|
||||
|
||||
const simpanTransaksi = async (dataTransaksi) => {
|
||||
// console.log('Data transaksi yang akan disimpan:', dataTransaksi);
|
||||
if (isSaving.value) return
|
||||
|
||||
isSaving.value = true
|
||||
|
||||
try {
|
||||
const response = await axios.post('/api/transaksi', dataTransaksi, {
|
||||
@ -366,16 +349,18 @@ const simpanTransaksi = async (dataTransaksi) => {
|
||||
|
||||
showSimpleToast('success', 'Transaksi berhasil disimpan!', 2000)
|
||||
|
||||
// Delay untuk memberikan waktu user membaca notifikasi
|
||||
// Emit event dengan data transaksi yang sudah disimpan
|
||||
setTimeout(() => {
|
||||
emit('transaksi-saved', response.data);
|
||||
emit('close');
|
||||
window.location.reload();
|
||||
}, 2200);
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error saving transaksi:', error);
|
||||
const errorMessage = error.response?.data?.message || error.message || 'Terjadi kesalahan saat menyimpan transaksi';
|
||||
showSimpleToast('error', `Error: ${errorMessage}`, 4000);
|
||||
} finally {
|
||||
isSaving.value = false
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@ -4,7 +4,6 @@
|
||||
<div
|
||||
class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-5 gap-3 sm:gap-2 mx-auto min-h-[75vh]"
|
||||
>
|
||||
|
||||
<div class="lg:col-span-3">
|
||||
<div
|
||||
class="bg-white rounded-xl shadow-lg border border-gray-200 overflow-hidden h-auto lg:h-full"
|
||||
@ -15,13 +14,11 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="lg:col-span-2">
|
||||
<div
|
||||
class="bg-white rounded-xl shadow-lg border border-gray-200 overflow-hidden lg:h-fit sticky top-4 max-h-[70vh] overflow-y-auto"
|
||||
>
|
||||
<div class="p-3 sm:p-4 md:p-6">
|
||||
|
||||
<KasirTransaksiList
|
||||
:transaksi="transaksi.data || []"
|
||||
:loading="loading"
|
||||
@ -34,7 +31,6 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<ModalConfirm
|
||||
v-if="showConfirm"
|
||||
title="Konfirmasi"
|
||||
@ -66,13 +62,11 @@ const showConfirm = ref(false);
|
||||
const confirmMessage = ref("Apakah kamu yakin?");
|
||||
let lastTransaksi = null;
|
||||
|
||||
|
||||
const handleConfirm = () => {
|
||||
showConfirm.value = false;
|
||||
console.log("User konfirmasi, cetak struk di sini...", lastTransaksi);
|
||||
};
|
||||
|
||||
|
||||
const fetchTransaksiHariIni = async (page = 1) => {
|
||||
try {
|
||||
loading.value = true;
|
||||
@ -105,47 +99,54 @@ const fetchTransaksiHariIni = async (page = 1) => {
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
const handlePageChange = (page) => {
|
||||
if (page >= 1 && page <= (transaksi.value.pagination?.last_page || 1)) {
|
||||
fetchTransaksiHariIni(page);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
const handleTransaksiSaved = async (newTransaksi) => {
|
||||
// Format data transaksi baru
|
||||
const formattedNewTransaksi = {
|
||||
id: newTransaksi.id,
|
||||
kode_transaksi: newTransaksi.kode_transaksi,
|
||||
created_at: newTransaksi.created_at,
|
||||
total_harga: newTransaksi.total_harga || 0,
|
||||
itemTransaksi: newTransaksi.itemTransaksi || [],
|
||||
itemTransaksi: newTransaksi.itemTransaksi || newTransaksi.items || [],
|
||||
pendapatan: newTransaksi.total_harga || 0,
|
||||
total_items: newTransaksi.itemTransaksi?.length || 0,
|
||||
tanggal: new Date(newTransaksi.created_at).toLocaleDateString('id-ID')
|
||||
total_items: (newTransaksi.itemTransaksi || newTransaksi.items || []).length,
|
||||
tanggal: new Date(newTransaksi.created_at).toLocaleDateString('id-ID'),
|
||||
nama_pembeli: newTransaksi.nama_pembeli,
|
||||
alamat: newTransaksi.alamat,
|
||||
no_hp: newTransaksi.no_hp,
|
||||
ongkos_bikin: newTransaksi.ongkos_bikin,
|
||||
nama_sales: newTransaksi.nama_sales
|
||||
};
|
||||
|
||||
// Tambahkan ke awal list
|
||||
transaksi.value.data.unshift(formattedNewTransaksi);
|
||||
lastTransaksi = formattedNewTransaksi;
|
||||
|
||||
// Update pagination
|
||||
if (transaksi.value.pagination) {
|
||||
transaksi.value.pagination.total += 1;
|
||||
// Hapus item terakhir jika melebihi limit
|
||||
if (transaksi.value.data.length > limit) {
|
||||
transaksi.value.data.pop();
|
||||
}
|
||||
}
|
||||
|
||||
confirmMessage.value = "Transaksi berhasil disimpan. Cetak struk sekarang?";
|
||||
showConfirm.value = true;
|
||||
// Tidak perlu modal konfirmasi lagi karena sudah ada StrukView
|
||||
// confirmMessage.value = "Transaksi berhasil disimpan. Cetak struk sekarang?";
|
||||
// showConfirm.value = true;
|
||||
};
|
||||
|
||||
|
||||
let refreshInterval = null;
|
||||
const startAutoRefresh = () => {
|
||||
if (refreshInterval) clearInterval(refreshInterval);
|
||||
refreshInterval = setInterval(() => {
|
||||
fetchTransaksiHariIni(currentPage.value);
|
||||
}, 10000);
|
||||
}, 30000); // Refresh setiap 30 detik (sebelumnya 10 detik)
|
||||
};
|
||||
|
||||
const stopAutoRefresh = () => {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user