@ -1,66 +1,51 @@
< template >
< template >
< div >
< div >
<!-- Loading -- >
< div v-if ="loading" class="text-center py-6" > Loading... < / div >
< div v-if ="loading" class="text-center py-6" > Loading... < / div >
<!-- Error -- >
< div v -else -if = " error " class = "text-center text-red-500 py-6" > { { error } } < / div >
< div v -else -if = " error " class = "text-center text-red-500 py-6" > { { error } } < / div >
<!-- Kalau hasil search kosong -- >
< div v -else -if = " filteredTrays.length = = = 0 " class = "text-center text-gray-500 py-6" >
< div
v - else - if = "filteredTrays.length === 0"
class = "text-center text-gray-500 py-6"
>
Nampan tidak ditemukan .
Nampan tidak ditemukan .
< / div >
< / div >
<!-- Grid nampan -- >
< div v -else class = "grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-4" >
< div
v - else
class = "grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-4"
>
< div
< div
v - for = "tray in filteredTrays"
v - for = "tray in filteredTrays"
: key = "tray.id"
: key = "tray.id"
class = "border rounded-lg p-4 shadow-sm hover:shadow-md transition"
class = "border rounded-lg p-4 shadow-sm hover:shadow-md transition"
>
>
<!-- Header Nampan -- >
< div class = "flex justify-between items-center mb-3" >
< div class = "flex justify-between items-center mb-3" >
< h2 class = "font-bold text-lg" style = "color: #102C57;" > { { tray . nama } } < / h2 >
< h2 class = "font-bold text-lg" style = "color: #102C57;" > { { tray . nama } } < / h2 >
< div class = "flex gap-2" >
< div class = "flex gap-2" >
< button
< button
class = "p-2 rounded bg-yellow-400 hover:bg-yellow-500"
class = "p-2 rounded bg-yellow-400 hover:bg-yellow-500"
@ click = "emit('edit', tray)"
@ click = "emit('edit', tray)"
>
>
✏ ️
✏ ️
< / button >
< / button >
< button
< button
class = "bg-red-500 text-white p-1 rounded"
class = "bg-red-500 text-white p-1 rounded"
@ click = "emit('delete', tray.id)"
@ click = "emit('delete', tray.id)"
>
>
🗑 ️
🗑 ️
< / button >
< / button >
< / div >
< / div >
< / div >
< / div >
<!-- Isi Nampan -- >
< div v-if ="tray.items && tray.items.length > 0" class="space-y-2 max-h-64 overflow-y-auto pr-2" >
< div
< div
v - if = "tray.items && tray.items.length > 0"
v - for = "item in tray.items"
class = "space-y-2 max-h-64 overflow-y-auto pr-2"
: key = "item.id"
class = "flex justify-between items-center border rounded-lg p-2"
@ click = "openMovePopup(item)"
>
>
< div
v - for = "item in tray.items"
: key = "item.id"
class = "flex justify-between items-center border rounded-lg p-2"
>
<!-- Gambar + Info -- >
< div class = "flex items-center gap-3" >
< div class = "flex items-center gap-3" >
< img
< img
v - if = "item.produk.foto && item.produk.foto.length > 0"
v - if = "item.produk.foto && item.produk.foto.length > 0"
: src = "item.produk.foto[0].url"
: src = "item.produk.foto[0].url"
alt = "foto produk"
alt = "foto produk"
class = "w-12 h-12 object-cover rounded"
class = "w-12 h-12 object-cover rounded"
/ >
/ >
< div >
< div >
< p class = "text-sm" style = "color: #102C57;" > { { item . produk . nama } } < / p >
< p class = "text-sm" style = "color: #102C57;" > { { item . produk . nama } } < / p >
@ -68,88 +53,179 @@
< p class = "text-sm" style = "color: #102C57;" > { { item . produk . harga _jual . toLocaleString ( ) } } < / p >
< p class = "text-sm" style = "color: #102C57;" > { { item . produk . harga _jual . toLocaleString ( ) } } < / p >
< / div >
< / div >
< / div >
< / div >
<!-- Berat -- >
< 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 >
< / div >
< / div >
<!-- Kalau nampan kosong -- >
< div v -else class = "text-gray-400 text-center py-4" >
< div v -else class = "text-gray-400 text-center py-4" >
Nampan kosong . < br / >
Nampan kosong . < br / >
Masuk ke menu < b > Brankas < / b > untuk memindahkan item ke nampan .
Masuk ke menu < b > Brankas < / b > untuk memindahkan item ke nampan .
< / div >
< / div >
<!-- Total Berat -- >
< div class = "border-t mt-3 pt-2 text-right font-semibold" >
< div class = "border-t mt-3 pt-2 text-right font-semibold" >
Berat Total : { { totalWeight ( tray ) } } g
Berat Total : { { totalWeight ( tray ) } } g
< / div >
< / div >
< / div >
< / div >
< / div >
< / div >
< / div >
< / div >
< / template >
<!-- Pop - up pindah item -- >
< div v-if ="isPopupVisible" class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center p-4 z-50" >
< div class = "bg-white rounded-lg shadow-lg max-w-sm w-full p-6 relative" >
< div class = "flex justify-center mb-4" >
< div class = "p-2 border border-gray-300 rounded-lg" >
< img :src ="qrCodeUrl" alt = "QR Code" class = "w-36 h-36" / >
< / div >
< / div >
< div class = "text-center text-gray-700 font-medium mb-1" > { { selectedItem . produk . nama } } < / div >
< div class = "text-center text-gray-500 text-sm mb-4" > { { selectedItem . produk . kategori } } < / div >
< div class = "flex justify-center mb-4" >
< button class = "bg-blue-500 text-white px-4 py-2 rounded hover:bg-blue-600 transition" >
Cetak
< / button >
< / div >
< div class = "mb-4" >
< label for = "tray-select" class = "block text-sm font-medium text-gray-700 mb-1" > Nama Nampan < / label >
< select
id = "tray-select"
v - model = "selectedTrayId"
class = "block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-300 focus:ring focus:ring-indigo-200 focus:ring-opacity-50"
>
< option value = "" disabled > Pilih Nampan < / option >
< option v-for ="tray in availableTrays" :key="tray.id" :value="tray.id" >
{ { tray . nama } }
< / option >
< / select >
< / div >
< div class = "flex justify-end gap-2" >
< button
@ click = "closePopup"
class = "px-4 py-2 rounded border border-gray-300 text-gray-700 hover:bg-gray-100 transition"
>
Batal
< / button >
< button
@ click = "saveMove"
: disabled = "!selectedTrayId"
class = "px-4 py-2 rounded text-white transition"
: class = "selectedTrayId ? 'bg-orange-500 hover:bg-orange-600' : 'bg-gray-400 cursor-not-allowed'"
>
Simpan
< / button >
< / div >
< / div >
< / div >
< / template >
< script setup >
< script setup >
import { ref , onMounted , computed } from "vue" ;
import { ref , onMounted , computed } from "vue" ;
import axios from "axios" ;
import axios from "axios" ;
/ / t e r i m a s e a r c h d a r i p a r e n t
const props = defineProps ( {
const props = defineProps ( {
search : {
search : {
type : String ,
type : String ,
default : "" ,
default : "" ,
} ,
} ,
} ) ;
} ) ;
const emit = defineEmits ( [ "edit" , "delete" ] ) ;
const emit = defineEmits ( [ "edit" , "delete" ] )
const trays = ref ( [ ] ) ;
const trays = ref ( [ ] ) ;
const loading = ref ( true ) ;
const loading = ref ( true ) ;
const error = ref ( null ) ;
const error = ref ( null ) ;
/ / h i t u n g t o t a l b e r a t
/ / - - - S t a t e u n t u k P o p - u p - - -
const totalWeight = ( tray ) => {
const isPopupVisible = ref ( false ) ;
if ( ! tray . items ) return 0 ;
const selectedItem = ref ( null ) ;
return tray . items . reduce ( ( sum , item ) => sum + ( item . produk . berat || 0 ) , 0 ) ;
const selectedTrayId = ref ( "" ) ;
} ;
/ / c o n s t t o t a l W e i g h t = ( t r a y ) = > {
/ / i f ( ! t r a y . i t e m s ) r e t u r n 0 ;
/ / c o n s t t o t a l = t r a y . i t e m s . r e d u c e ( ( s u m , i t e m ) = > s u m + ( i t e m . p r o d u k . b e r a t | | 0 ) , 0 ) ;
/ / r e t u r n t o t a l . t o F i x e d ( 2 ) ; / / h a s i l s t r i n g " 1 2 . 3 4 "
/ / } ;
/ / a m b i l d a t a d a r i b a c k e n d
/ / Q R C o d e g e n e r a t o r
onMounted ( async ( ) => {
const qrCodeUrl = computed ( ( ) => {
if ( selectedItem . value ) {
const data = ` ITM- ${ selectedItem . value . id } - ${ selectedItem . value . produk . nama . replace ( /\s/g , '' ) } ` ;
return ` https://api.qrserver.com/v1/create-qr-code/?size=150x150&data= ${ encodeURIComponent ( data ) } ` ;
}
return '' ;
} ) ;
/ / - - - F u n g s i P o p - u p - - -
const openMovePopup = ( item ) => {
selectedItem . value = item ;
selectedTrayId . value = "" ;
isPopupVisible . value = true ;
} ;
const closePopup = ( ) => {
isPopupVisible . value = false ;
selectedItem . value = null ;
selectedTrayId . value = "" ;
} ;
const saveMove = async ( ) => {
if ( ! selectedTrayId . value || ! selectedItem . value ) return ;
try {
await axios . put ( ` /api/item/ ${ selectedItem . value . id } ` , {
id _nampan : selectedTrayId . value ,
id _produk : selectedItem . value . id _produk , / / i k u t k a n i d _ p r o d u k k a r e n a A P I m i n t a
} ) ;
await refreshData ( ) ;
closePopup ( ) ;
} catch ( err ) {
console . error ( "Gagal memindahkan item:" , err . response ? . data || err ) ;
alert ( "Gagal memindahkan item. Silakan coba lagi." ) ;
}
} ;
/ / - - - A m b i l d a t a n a m p a n + i t e m - - -
const refreshData = async ( ) => {
try {
try {
const [ nampanRes , itemRes ] = await Promise . all ( [
const [ nampanRes , itemRes ] = await Promise . all ( [
axios . get ( "/api/nampan" ) ,
axios . get ( "/api/nampan" ) ,
axios . get ( "/api/item" )
axios . get ( "/api/item" ) ,
] ) ;
] ) ;
const nampans = nampanRes . data ;
const nampans = nampanRes . data ;
const items = itemRes . data ;
const items = itemRes . data ;
/ / m a p p i n g i t e m s k e n a m p a n
trays . value = nampans . map ( ( tray ) => {
trays . value = nampans . map ( tray => {
return {
return {
... tray ,
... tray ,
items : items . filter ( item => item . id _nampan === tray . id )
/ / p a s t i k a n t i p e s a m a ( s t r i n g / n u m b e r )
items : items . filter ( ( item ) => Number ( item . id _nampan ) === Number ( tray . id ) ) ,
} ;
} ;
} ) ;
} ) ;
console . log ( "Nampan dengan items:" , trays . value ) ;
} catch ( err ) {
} catch ( err ) {
error . value = err . message || "Gagal mengambil data" ;
error . value = err . message || "Gagal mengambil data" ;
} finally {
} finally {
loading . value = false ;
loading . value = false ;
}
}
} ) ;
} ;
/ / f i l t e r b e r d a s a r k a n n a m a n a m p a n
/ / H i t u n g t o t a l b e r a t
const totalWeight = ( tray ) => {
if ( ! tray . items ) return 0 ;
const total = tray . items . reduce ( ( sum , item ) => sum + ( item . produk . berat || 0 ) , 0 ) ;
return total . toFixed ( 2 ) ;
} ;
/ / F i l t e r n a m p a n b e r d a s a r k a n p e n c a r i a n
const filteredTrays = computed ( ( ) => {
const filteredTrays = computed ( ( ) => {
if ( ! props . search ) return trays . value ;
if ( ! props . search ) return trays . value ;
return trays . value . filter ( ( tray ) =>
return trays . value . filter ( ( tray ) =>
tray . nama . toLowerCase ( ) . includes ( props . search . toLowerCase ( ) )
tray . nama . toLowerCase ( ) . includes ( props . search . toLowerCase ( ) )
) ;
) ;
} ) ;
} ) ;
< / script >
/ / D a f t a r n a m p a n l a i n ( s e l a i n t e m p a t i t e m s a a t i n i )
const availableTrays = computed ( ( ) => {
if ( ! selectedItem . value || ! trays . value ) return [ ] ;
return trays . value . filter (
( tray ) => Number ( tray . id ) !== Number ( selectedItem . value . id _nampan )
) ;
} ) ;
onMounted ( ( ) => {
refreshData ( ) ;
} ) ;
< / script >