Compare commits
	
		
			No commits in common. "0db65d190f9261ebc1931000a4e23f7a45963a2d" and "795edadfe3118dc25437ed03530f29c1b9343a4a" have entirely different histories.
		
	
	
		
			0db65d190f
			...
			795edadfe3
		
	
		
| @ -46,20 +46,20 @@ | ||||
|         @click="openMovePopup(item)"> | ||||
|         <!-- Gambar & Info Produk --> | ||||
|         <div class="flex items-center gap-3"> | ||||
|           <img v-if="item.produk?.foto?.length" :src="item.produk?.foto[0].url"  | ||||
|           <img v-if="item.produk.foto?.length" :src="item.produk.foto[0].url"  | ||||
|                class="size-12 object-cover rounded"  | ||||
|                @error="handleImageError" /> | ||||
|           <div class="size-12 bg-gray-200 rounded flex items-center justify-center" v-else> | ||||
|             <i class="fas fa-image text-gray-400"></i> | ||||
|           </div> | ||||
|           <div> | ||||
|             <p class="font-semibold text-D">{{ item.produk?.nama }}</p> | ||||
|             <p class="font-semibold text-D">{{ item.produk.nama }}</p> | ||||
|             <p class="text-sm text-gray-500 font-semibold">{{ item.kode_item }}</p> | ||||
|           </div> | ||||
|         </div> | ||||
| 
 | ||||
|         <!-- Berat --> | ||||
|         <span class="font-medium text-D">{{ item.produk?.berat }}g</span> | ||||
|         <span class="font-medium text-D">{{ item.produk.berat }}g</span> | ||||
|       </div> | ||||
|     </div> | ||||
| 
 | ||||
| @ -191,9 +191,7 @@ const qrCodeUrl = computed(() => { | ||||
| 
 | ||||
| // Computed untuk statistik | ||||
| const totalWeight = computed(() => { | ||||
|   const total = filteredItems.value.reduce((sum, item) => { | ||||
|     return sum + (item?.produk?.berat || 0); | ||||
|   }, 0); | ||||
|   const total = filteredItems.value.reduce((sum, item) => sum + (item.produk.berat || 0), 0); | ||||
|   return total.toFixed(2); | ||||
| }); | ||||
| 
 | ||||
| @ -333,7 +331,8 @@ const refreshData = async () => { | ||||
|      | ||||
|     // Filter hanya item yang ada di brankas (id_nampan = null atau tidak ada) | ||||
|     items.value = itemRes.data.filter(item => !item.id_nampan); | ||||
|     trays.value = trayRes.data;  | ||||
|     trays.value = trayRes.data; | ||||
|      | ||||
|   } catch (err) { | ||||
|     console.error("Error fetching data:", err); | ||||
|     alert.value = { error: err.response?.data?.message || "Gagal mengambil data" }; | ||||
|  | ||||
| @ -31,14 +31,7 @@ | ||||
|         <!-- Input Harga Jual --> | ||||
|         <div> | ||||
|           <label class="block text-sm font-medium text-D">Harga Jual</label> | ||||
|           <input  | ||||
|             type="text"  | ||||
|             v-model="hargaJualFormatted"  | ||||
|             @input="formatHargaInput" | ||||
|             @keypress="onlyNumbers" | ||||
|             placeholder="Masukkan Harga Jual" | ||||
|             class="bg-A focus:outline-none focus:border-C focus:ring focus:ring-D focus:ring-opacity-50 p-2 w-full rounded-md border border-B shadow-sm sm:text-sm" | ||||
|           /> | ||||
|           <InputField v-model="hargaJual" type="number" placeholder="Masukkan Harga Jual" /> | ||||
|         </div> | ||||
| 
 | ||||
|         <!-- Tombol Aksi --> | ||||
| @ -127,7 +120,6 @@ const kodeItem = ref(""); | ||||
| const info = ref(""); | ||||
| const error = ref(""); | ||||
| const hargaJual = ref(null); | ||||
| const hargaJualFormatted = ref(""); | ||||
| const item = ref(null); | ||||
| const loadingItem = ref(false); | ||||
| const pesanan = ref([]); | ||||
| @ -139,45 +131,6 @@ const showStruk = ref(false); | ||||
| 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, ""); | ||||
|   const number = parseInt(cleaned); | ||||
|   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); | ||||
|   } else { | ||||
|     hargaJualFormatted.value = ""; | ||||
|     hargaJual.value = null; | ||||
|   } | ||||
| }; | ||||
| 
 | ||||
| // Hanya izinkan angka saat mengetik | ||||
| const onlyNumbers = (event) => { | ||||
|   const char = String.fromCharCode(event.which); | ||||
|   if (!/[0-9]/.test(char)) { | ||||
|     event.preventDefault(); | ||||
|   } | ||||
| }; | ||||
| 
 | ||||
| const inputItem = async () => { | ||||
|   if (!kodeItem.value) return; | ||||
| 
 | ||||
| @ -196,8 +149,6 @@ 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); | ||||
| 
 | ||||
| @ -213,10 +164,13 @@ const inputItem = async () => { | ||||
|       info.value = ""; | ||||
|     }, 3000); | ||||
|   } catch (err) { | ||||
|     error.value = "Item tidak ditemukan"; | ||||
|     if (err == "") { | ||||
|       error.value = "Error: Item tidak ditemukan"; | ||||
|     } else { | ||||
|       error.value = err; | ||||
|     } | ||||
|     info.value = ""; | ||||
|     hargaJual.value = null; | ||||
|     hargaJualFormatted.value = ""; | ||||
|     item.value = null; | ||||
| 
 | ||||
|     errorTimeout = setTimeout(() => { | ||||
| @ -251,7 +205,6 @@ const tambahItem = () => { | ||||
|   // Reset input fields | ||||
|   kodeItem.value = ""; | ||||
|   hargaJual.value = null; | ||||
|   hargaJualFormatted.value = ""; | ||||
|   item.value = null; | ||||
|   info.value = ""; | ||||
|   clearTimeout(infoTimeout); | ||||
|  | ||||
| @ -2,9 +2,9 @@ | ||||
|   <div v-if="isOpen" | ||||
|     class="text-D pt-serif-regular-italic fixed inset-0 bg-black/75 flex items-center justify-center z-[9999]"> | ||||
|     <div class="bg-white w-[1224px] h-[528px] rounded-md shadow-lg relative overflow-hidden"> | ||||
|       <div class="bg-yellow-500 h-8 w-full"> | ||||
|         <div class="bg-D h-6 w-full"></div> | ||||
|       </div> | ||||
|         <div class="bg-yellow-500 h-8 w-full"> | ||||
|             <div class="bg-D h-6 w-full"></div> | ||||
|         </div> | ||||
| 
 | ||||
|       <div class="p-6 text-sm flex flex-col h-full relative"> | ||||
|         <div class="relative flex items-center justify-between top-0 pb-1 mb-2"> | ||||
| @ -19,26 +19,37 @@ | ||||
|               <i class="fab fa-whatsapp text-green-500 text-xl"></i> 08158851178 | ||||
|             </p> | ||||
|             <p class=" text-sm">{{ generateTransactionCode() }}</p> | ||||
| 
 | ||||
|           </div> | ||||
| 
 | ||||
| 
 | ||||
|           <div class="absolute inset-x-0 top-[-48px] flex flex-col items-center"> | ||||
|             <img :src="logo" alt="Logo" class="h-40" /> | ||||
|           </div> | ||||
| 
 | ||||
| 
 | ||||
|           <div class="grid grid-cols-[130px_1fr] gap-y-0 text-xs items-center  -mt-5 relative z-10"> | ||||
|             <div class="text-right font-semibold pr-3">Tanggal :</div> | ||||
| 
 | ||||
|             <p class="mt-1 text-left pl-2">{{ getCurrentDate() }}</p> | ||||
| 
 | ||||
|             <div class="text-right font-semibold pr-3">Nama :</div> | ||||
| 
 | ||||
|             <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> | ||||
| 
 | ||||
|             <inputField v-model="alamat" class="h-7 px-2 text-sm rounded bg-blue-200 w-full" /> | ||||
| 
 | ||||
|             <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" /> | ||||
|           </div> | ||||
|         </div> | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
|         <div class="flex  mb-1 gap-179"> | ||||
|             <div class="flex gap-4"> | ||||
|                 <img :src="logo_bca" alt="Logo_bca" class="h-5" /> | ||||
| @ -51,7 +62,7 @@ | ||||
|                 <img :src="logo_mandiri" alt="Logo_mandiri" class="h-5" /> | ||||
|             </div> | ||||
|         </div> | ||||
|         <table class="w-full border-D  text-sm table-fixed border-b"> | ||||
|             <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> | ||||
| @ -77,7 +88,7 @@ | ||||
|                 </template> | ||||
|                 {{ item.produk?.nama || '' }} | ||||
|               </td> | ||||
|               <td class="border-r border-D">{{ item.produk.nama ? (item.nampan?.nama || 'Brankas') : '' }}</td> | ||||
|               <td class="border-r border-D">{{ item.nampan?.nama || 'Brankas' }}</td> | ||||
|               <td class="border-r border-D"> | ||||
|                 <span v-if="item.produk?.berat">{{ item.produk.berat }}g</span> | ||||
|               </td> | ||||
| @ -121,10 +132,15 @@ | ||||
|                   <p class="text-red-500 text-xs">diluar harga jual</p> | ||||
|                 </div> | ||||
|                 <div class="flex items-center w-40"> | ||||
|                   <p>Rp</p> | ||||
|                   <input type="text" v-model="ongkosBikinFormatted" @input="formatInput" | ||||
|                     class="h-7 px-2 text-sm rounded bg-blue-200 text-left w-full" /> | ||||
|                 </div> | ||||
|   <p>Rp</p> | ||||
|   <input | ||||
|     type="text" | ||||
|     v-model="ongkosBikinFormatted" | ||||
|     @input="formatInput" | ||||
|     class="h-7 px-2 text-sm rounded bg-blue-200 text-left w-full" | ||||
|   /> | ||||
| </div> | ||||
| 
 | ||||
|               </div> | ||||
| 
 | ||||
|               <!-- Total --> | ||||
| @ -154,30 +170,7 @@ | ||||
|       <p class="absolute p-8 bottom-0 left-0 w-full text-left text-xs bg-D text-white py-1"> | ||||
|         Terima kasih sudah berbelanja dengan kami | ||||
|       </p> | ||||
|     </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" /> | ||||
|         </svg> | ||||
|         <svg v-else-if="toastType === 'success'" 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 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clip-rule="evenodd" /> | ||||
|         </svg> | ||||
|         <svg v-else class="w-5 h-5" fill="currentColor" viewBox="0 0 20 20"> | ||||
|           <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> | ||||
| </template> | ||||
| @ -211,6 +204,7 @@ const props = defineProps({ | ||||
|   } | ||||
| }) | ||||
| 
 | ||||
| 
 | ||||
| const emit = defineEmits(['close', 'confirm']) | ||||
| 
 | ||||
| const namaPembeli = ref('') | ||||
| @ -219,22 +213,7 @@ const alamat = ref('') | ||||
| const ongkosBikin = ref(0) | ||||
| const selectedSales = ref(null) | ||||
| const salesOptions = ref([]) | ||||
| const ongkosBikinFormatted = ref("") | ||||
| 
 | ||||
| // Simple Toast State | ||||
| const showToast = ref(false) | ||||
| const toastType = ref('error') // 'error', 'success', 'info' | ||||
| const toastMessage = ref('') | ||||
| 
 | ||||
| const toastClasses = computed(() => { | ||||
|   const baseClasses = 'text-white' | ||||
|   const typeClasses = { | ||||
|     error: 'bg-red-500', | ||||
|     success: 'bg-green-500',  | ||||
|     info: 'bg-blue-500' | ||||
|   } | ||||
|   return `${baseClasses} ${typeClasses[toastType.value]}` | ||||
| }) | ||||
| const ongkosBikinFormatted = ref(""); | ||||
| 
 | ||||
| const grandTotal = computed(() => { | ||||
|   return props.total + (ongkosBikin.value || 0) | ||||
| @ -259,17 +238,6 @@ const generateTransactionCode = () => { | ||||
|   return `TRS-${timestamp}` | ||||
| } | ||||
| 
 | ||||
| // Simple Toast Function | ||||
| const showSimpleToast = (type, message, duration = 3000) => { | ||||
|   toastType.value = type | ||||
|   toastMessage.value = message | ||||
|   showToast.value = true | ||||
|    | ||||
|   setTimeout(() => { | ||||
|     showToast.value = false | ||||
|   }, duration) | ||||
| } | ||||
| 
 | ||||
| const fetchSales = async () => { | ||||
|   try { | ||||
|     const response = await axios.get('/api/sales', { | ||||
| @ -283,6 +251,7 @@ const fetchSales = async () => { | ||||
|       label: sales.nama | ||||
|     })) | ||||
| 
 | ||||
| 
 | ||||
|     if (salesOptions.value.length > 0) { | ||||
|       selectedSales.value = salesOptions.value[0].value | ||||
|     } | ||||
| @ -292,26 +261,28 @@ const fetchSales = async () => { | ||||
| } | ||||
| 
 | ||||
| const handleSimpan = () => { | ||||
| 
 | ||||
|   if (!namaPembeli.value.trim()) { | ||||
|     showSimpleToast('error', 'Nama pembeli harus diisi!') | ||||
|     alert('Nama pembeli harus diisi!') | ||||
|     return | ||||
|   } | ||||
| 
 | ||||
|   if (!nomorTelepon.value.trim()) { | ||||
|     showSimpleToast('error', 'Nomor telepon harus diisi!') | ||||
|     alert('Nomor telepon harus diisi!') | ||||
|     return | ||||
|   } | ||||
| 
 | ||||
|   if (!alamat.value.trim()) { | ||||
|     showSimpleToast('error', 'Alamat harus diisi!') | ||||
|     alert('Alamat harus diisi!') | ||||
|     return | ||||
|   } | ||||
| 
 | ||||
|   if (!selectedSales.value) { | ||||
|     showSimpleToast('error', 'Sales harus dipilih!') | ||||
|     alert('Sales harus dipilih!') | ||||
|     return | ||||
|   } | ||||
| 
 | ||||
| 
 | ||||
|   simpanTransaksi({ | ||||
|     id_sales: selectedSales.value, | ||||
|     nama_pembeli: namaPembeli.value, | ||||
| @ -323,6 +294,7 @@ const handleSimpan = () => { | ||||
|   }) | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| const simpanTransaksi = async (dataTransaksi) => { | ||||
|   console.log('Data transaksi yang akan disimpan:', dataTransaksi); | ||||
| 
 | ||||
| @ -333,19 +305,16 @@ const simpanTransaksi = async (dataTransaksi) => { | ||||
|       }, | ||||
|     }); | ||||
| 
 | ||||
|     showSimpleToast('success', 'Transaksi berhasil disimpan!', 2000) | ||||
|      | ||||
|     // Delay untuk memberikan waktu user membaca notifikasi | ||||
|     setTimeout(() => { | ||||
|       props.pesanan.value = []; | ||||
|       props.isOpen = false; | ||||
|       window.location.reload(); | ||||
|     }, 2200); | ||||
| 
 | ||||
|     props.pesanan.value = []; | ||||
|     props.isOpen = false; | ||||
| 
 | ||||
|     alert('Transaksi berhasil disimpan!'); | ||||
|     window.location.reload(); | ||||
| 
 | ||||
|   } 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); | ||||
|     alert('Error menyimpan transaksi: ' + (error.response?.data?.message || error.message)); | ||||
|   } | ||||
| }; | ||||
| 
 | ||||
| @ -364,12 +333,18 @@ const pesananMinimal = computed(() => { | ||||
| }) | ||||
| 
 | ||||
| function formatInput(e) { | ||||
| 
 | ||||
|   let value = e.target.value.replace(/\D/g, ""); | ||||
| 
 | ||||
| 
 | ||||
|   ongkosBikin.value = value ? parseInt(value, 10) : null; | ||||
| 
 | ||||
| 
 | ||||
|   ongkosBikinFormatted.value = value | ||||
|     ? new Intl.NumberFormat("id-ID").format(value) | ||||
|     : ""; | ||||
| } | ||||
| 
 | ||||
| </script> | ||||
| 
 | ||||
| <style scoped> | ||||
| @ -379,4 +354,4 @@ function formatInput(e) { | ||||
|   font-family: "PT Serif", serif; | ||||
|   font-weight: 400; | ||||
| } | ||||
| </style> | ||||
| </style> | ||||
|  | ||||
| @ -61,18 +61,18 @@ | ||||
|   > | ||||
|     <div class="flex items-center gap-3"> | ||||
|       <img | ||||
|         v-if="item.produk?.foto && item.produk?.foto.length > 0" | ||||
|         :src="item.produk?.foto[0].url" | ||||
|         v-if="item.produk.foto && item.produk.foto.length > 0" | ||||
|         :src="item.produk.foto[0].url" | ||||
|         alt="foto produk" | ||||
|         class="size-12 object-cover rounded" | ||||
|       /> | ||||
|       <div class="text-D"> | ||||
|         <p class="text-sm">{{ item.produk?.nama }}</p> | ||||
|         <p class="text-sm">{{ item.produk.nama }}</p> | ||||
|         <p class="text-sm font-medium">{{ item.kode_item }}</p> | ||||
|       </div> | ||||
|     </div> | ||||
|     <div class="flex items-center gap-2"> | ||||
|       <span class="font-medium">{{ item.produk?.berat }}g</span> | ||||
|       <span class="font-medium">{{ item.produk.berat }}g</span> | ||||
|     </div> | ||||
|   </div> | ||||
| </div> | ||||
| @ -107,7 +107,7 @@ | ||||
|         {{ selectedItem.kode_item }} | ||||
|       </div> | ||||
|       <div class="text-center text-gray-700 font-medium mb-3"> | ||||
|         {{ selectedItem.produk?.nama }} | ||||
|         {{ selectedItem.produk.nama }} | ||||
|       </div> | ||||
| 
 | ||||
|       <div class="flex justify-center mb-4"> | ||||
| @ -248,7 +248,7 @@ const saveMove = async () => { | ||||
| // Hitung total berat | ||||
| const totalWeight = (tray) => { | ||||
|   if (!tray.items) return 0; | ||||
|   const total = tray.items.reduce((sum, item) => sum + (item.produk?.berat || 0), 0); | ||||
|   const total = tray.items.reduce((sum, item) => sum + (item.produk.berat || 0), 0); | ||||
|   return total.toFixed(2); | ||||
| }; | ||||
| 
 | ||||
|  | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user