@ -1,266 +0,0 @@
< template >
< mainLayout >
<!-- Modal Buat Item -- >
< CreateItemModal
: isOpen = "openItemModal"
: product = "editedProduct"
@ close = "closeItemModal"
/ >
< div class = "p-6" >
< p class = "font-serif italic text-[25px] text-D" > Edit Produk < / p >
< div class = "flex flex-col md:flex-row mt-5 gap-6" >
<!-- Form Section -- >
< div class = "flex-1" >
< div class = "mb-3" >
< label class = "block text-D mb-1" > Nama Produk < / label >
< InputField v -model = " form.nama " type = "text" placeholder = "Masukkan nama produk" / >
< / div >
< div class = "mb-3" >
< label class = "block text-D mb-1" > Kategori < / label >
< InputSelect v -model = " form.id_kategori " :options ="category" placeholder = "Pilih kategori" / >
< / div >
< div class = "mb-3 flex flex-row w-full gap-3" >
< div class = "flex-1" >
< label class = "block text-D mb-1" > Berat ( g ) < / label >
< InputField v -model = " form.berat " type = "number" step = "0.01" placeholder = "Masukkan berat" @input ="calculateHargaJual" / >
< / div >
< div class = "flex-1" >
< label class = "block text-D mb-1" > Kadar ( K ) < / label >
< InputField v -model = " form.kadar " type = "number" placeholder = "Masukkan kadar" / >
< / div >
< / div >
< div class = "mb-3 flex flex-row w-full gap-3" >
< div class = "flex-1" >
< label class = "block text-D mb-1" > Harga per Gram < / label >
< InputField v -model = " form.harga_per_gram " type = "number" step = "0.01" placeholder = "Masukkan harga per gram" @input ="calculateHargaJual" / >
< / div >
< div class = "flex-1" >
< label class = "block text-D mb-1" > Harga Jual < / label >
< InputField v -model = " form.harga_jual " type = "number" step = "0.01" placeholder = "Masukkan harga jual" / >
< / div >
< / div >
< / div >
<!-- Image Upload Section -- >
< div class = "flex-1" >
< label class = "block text-D mb-1" > Foto < / label >
< div class = "grid grid-cols-3 gap-3" >
<!-- Existing Images -- >
< div v-for ="(image, index) in uploadedImages" :key="`img-${image.id}`" class="relative group aspect-square" >
< div class = "w-full h-full bg-gray-100 rounded-lg border-2 border-gray-200 overflow-hidden" >
< img :src ="image.url" : alt = "`Foto ${index + 1}`" class = "w-full h-full object-cover" / >
< button @click ="removeImage(image.id)" :disabled ="uploadLoading"
class = "absolute -top-2 -right-2 bg-red-500 text-white rounded-full w-6 h-6 flex items-center justify-center text-sm font-bold shadow-lg hover:bg-red-600 transition-colors disabled:bg-gray-400" >
×
< / button >
< / div >
< / div >
<!-- Upload Button -- >
< div v -if = " uploadedImages.length < 6 " @drop ="handleDrop" @ dragover.prevent
@ dragenter . prevent = "isDragging = true" @ dragleave . prevent = "isDragging = false" @ click = "triggerFileInput"
class = "aspect-square bg-gray-50 border-2 border-dashed border-gray-300 rounded-lg flex flex-col items-center justify-center cursor-pointer hover:border-D hover:bg-blue-50 transition-colors group"
: class = "{ 'border-blue-400 bg-blue-50': isDragging, 'cursor-not-allowed opacity-50': uploadLoading }" >
< div class = "text-center" >
< div v -if = " ! uploadLoading "
class = "w-12 h-12 bg-D rounded-lg flex items-center justify-center mx-auto mb-2 group-hover:bg-D transition-colors" >
< svg class = "w-6 h-6 text-white" fill = "none" stroke = "currentColor" viewBox = "0 0 24 24" >
< path stroke -linecap = " round " stroke -linejoin = " round " stroke -width = " 2 "
d = "M12 6v6m0 0v6m0-6h6m-6 0H6" > < / path >
< / svg >
< / div >
< div v -else class = "w-12 h-12 bg-D rounded-lg flex items-center justify-center mx-auto mb-2" >
< svg class = "animate-spin w-6 h-6 text-white" fill = "none" viewBox = "0 0 24 24" >
< circle class = "opacity-25" cx = "12" cy = "12" r = "10" stroke = "currentColor" stroke -width = " 4 " > < / circle >
< path class = "opacity-75" fill = "currentColor"
d = "m4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z" >
< / path >
< / svg >
< / div >
< p class = "text-xs text-gray-600 font-medium" v-html ="uploadLoading ? 'Uploading...' : 'Unggah<br/>Foto'" > < / p >
< / div >
< / div >
< / div >
< input ref = "fileInput" type = "file" multiple accept = "image/jpeg,image/jpg,image/png" @change ="handleFileSelect" class = "hidden" / >
< p class = "text-xs text-gray-500 mt-2" > Format : JPG , JPEG , PNG ( Max : 2 MB per file , Max : 6 foto ) < / p >
< div v-if ="uploadError" class="mt-2 p-2 bg-red-50 border border-red-200 rounded text-sm text-red-600" >
{ { uploadError } }
< / div >
< / div >
< / div >
< div class = "mt-6 flex justify-end flex-row gap-3" >
< button @click ="back" class = "px-6 py-2 rounded-md bg-gray-400 hover:bg-gray-500 text-white" > Batal < / button >
< button @click ="submitForm" : disabled = "loading || !isFormValid"
class = "bg-C text-D px-6 py-2 rounded-md hover:bg-B disabled:bg-B disabled:text-white disabled:cursor-not-allowed" >
{ { loading ? 'Menyimpan...' : 'Simpan Perubahan' } }
< / button >
< / div >
< / div >
< / mainLayout >
< / template >
< script setup >
import { ref , computed , onMounted } from "vue" ;
import { useRoute , useRouter } from "vue-router" ;
import axios from "axios" ;
import mainLayout from "../layouts/mainLayout.vue" ;
import InputField from "../components/InputField.vue" ;
import InputSelect from "../components/InputSelect.vue" ;
import CreateItemModal from "../components/CreateItemModal.vue" ;
const route = useRoute ( ) ;
const router = useRouter ( ) ;
const productId = route . params . id ;
const form = ref ( {
nama : "" ,
id _kategori : null ,
berat : 0 ,
kadar : 0 ,
harga _per _gram : 0 ,
harga _jual : 0 ,
} ) ;
const category = ref ( [ ] ) ;
const uploadedImages = ref ( [ ] ) ;
const loading = ref ( false ) ;
const uploadLoading = ref ( false ) ;
const uploadError = ref ( "" ) ;
const isDragging = ref ( false ) ;
const fileInput = ref ( null ) ;
const openItemModal = ref ( false ) ;
const editedProduct = ref ( null ) ;
const userId = ref ( 1 ) ; / / T O D O : a m b i l d a r i a u t h
const isFormValid = computed ( ( ) => {
return (
form . value . nama &&
form . value . id _kategori &&
form . value . berat > 0 &&
form . value . kadar > 0 &&
form . value . harga _per _gram > 0 &&
form . value . harga _jual > 0
) ;
} ) ;
const calculateHargaJual = ( ) => {
const berat = parseFloat ( form . value . berat ) || 0 ;
const hargaPerGram = parseFloat ( form . value . harga _per _gram ) || 0 ;
if ( berat > 0 && hargaPerGram > 0 ) {
form . value . harga _jual = berat * hargaPerGram ;
}
} ;
const loadKategori = async ( ) => {
const response = await axios . get ( "/api/kategori" ) ;
category . value = response . data . map ( ( c ) => ( { value : c . id , label : c . nama } ) ) ;
} ;
const loadProduk = async ( ) => {
const response = await axios . get ( ` /api/produk/ ${ productId } ` ) ;
const produk = response . data ;
form . value = {
nama : produk . nama ,
id _kategori : produk . id _kategori ,
berat : produk . berat ,
kadar : produk . kadar ,
harga _per _gram : produk . harga _per _gram ,
harga _jual : produk . harga _jual ,
} ;
uploadedImages . value = produk . foto || [ ] ;
} ;
const triggerFileInput = ( ) => {
if ( ! uploadLoading . value && uploadedImages . value . length < 6 ) {
fileInput . value ? . click ( ) ;
}
} ;
const handleFileSelect = ( e ) => {
const files = Array . from ( e . target . files ) ;
uploadFiles ( files ) ;
} ;
const handleDrop = ( e ) => {
e . preventDefault ( ) ;
isDragging . value = false ;
if ( uploadLoading . value || uploadedImages . value . length >= 6 ) return ;
const files = Array . from ( e . dataTransfer . files ) ;
uploadFiles ( files ) ;
} ;
const uploadFiles = async ( files ) => {
uploadError . value = "" ;
const validFiles = files . filter (
( file ) =>
[ "image/jpeg" , "image/jpg" , "image/png" ] . includes ( file . type ) &&
file . size <= 2 * 1024 * 1024
) ;
if ( ! validFiles . length ) return ;
uploadLoading . value = true ;
try {
for ( const file of validFiles ) {
const formData = new FormData ( ) ;
formData . append ( "foto" , file ) ;
formData . append ( "id_user" , userId . value ) ;
const res = await axios . post ( "/api/foto/upload" , formData , {
headers : { "Content-Type" : "multipart/form-data" } ,
} ) ;
uploadedImages . value . push ( res . data ) ;
}
} finally {
uploadLoading . value = false ;
}
} ;
const removeImage = async ( id ) => {
try {
await axios . delete ( ` /api/foto/hapus/ ${ id } ` ) ;
uploadedImages . value = uploadedImages . value . filter ( ( i ) => i . id !== id ) ;
} catch {
uploadError . value = "Gagal menghapus foto" ;
}
} ;
const submitForm = async ( ) => {
loading . value = true ;
try {
await axios . put ( ` /api/produk/ ${ productId } ` , {
... form . value ,
id _user : userId . value ,
} ) ;
alert ( "Produk berhasil diupdate!" ) ;
router . push ( "/produk" ) ;
} catch ( err ) {
alert ( "Gagal update produk!" ) ;
console . error ( err ) ;
} finally {
loading . value = false ;
}
} ;
const closeItemModal = ( ) => {
openItemModal . value = false ;
} ;
const back = ( ) => {
router . push ( "/produk" ) ;
} ;
onMounted ( ( ) => {
loadKategori ( ) ;
loadProduk ( ) ;
} ) ;
< / script >