diff --git a/app/src/main/java/com/alya/ecommerce_serang/data/api/dto/Product.kt b/app/src/main/java/com/alya/ecommerce_serang/data/api/dto/Product.kt index 888105f..ae2c979 100644 --- a/app/src/main/java/com/alya/ecommerce_serang/data/api/dto/Product.kt +++ b/app/src/main/java/com/alya/ecommerce_serang/data/api/dto/Product.kt @@ -10,6 +10,9 @@ data class Product( @field:SerializedName("image") val image: String? = null, + @field:SerializedName("is_wholesale") + val isWholesale: Boolean? = null, + @field:SerializedName("sppirt") val sppirt: String? = null, diff --git a/app/src/main/java/com/alya/ecommerce_serang/data/api/dto/Wholesale.kt b/app/src/main/java/com/alya/ecommerce_serang/data/api/dto/Wholesale.kt new file mode 100644 index 0000000..7dd2f33 --- /dev/null +++ b/app/src/main/java/com/alya/ecommerce_serang/data/api/dto/Wholesale.kt @@ -0,0 +1,18 @@ +package com.alya.ecommerce_serang.data.api.dto + +import com.google.gson.annotations.SerializedName + +data class Wholesale( + + @field:SerializedName("product_id") + val productId: Int? = null, + + @field:SerializedName("wholesale_price") + val wholesalePrice: String? = null, + + @field:SerializedName("id") + val id: Int? = null, + + @field:SerializedName("min_item") + val minItem: Int? = null +) diff --git a/app/src/main/java/com/alya/ecommerce_serang/data/api/response/store/product/CreateProductResponse.kt b/app/src/main/java/com/alya/ecommerce_serang/data/api/response/store/product/CreateProductResponse.kt index 53d6bf6..f7d3381 100644 --- a/app/src/main/java/com/alya/ecommerce_serang/data/api/response/store/product/CreateProductResponse.kt +++ b/app/src/main/java/com/alya/ecommerce_serang/data/api/response/store/product/CreateProductResponse.kt @@ -2,6 +2,7 @@ package com.alya.ecommerce_serang.data.api.response.store.product import com.alya.ecommerce_serang.data.api.dto.Preorder import com.alya.ecommerce_serang.data.api.dto.Product +import com.alya.ecommerce_serang.data.api.dto.Wholesale import com.google.gson.annotations.SerializedName data class CreateProductResponse( @@ -9,9 +10,12 @@ data class CreateProductResponse( @field:SerializedName("product") val product: Product? = null, + @field:SerializedName("wholesale") + val wholesale: Wholesale? = null, + @field:SerializedName("message") val message: String? = null, @field:SerializedName("preorder") val preorder: Preorder? = null -) +) \ No newline at end of file diff --git a/app/src/main/java/com/alya/ecommerce_serang/data/api/response/store/product/UpdateProductResponse.kt b/app/src/main/java/com/alya/ecommerce_serang/data/api/response/store/product/UpdateProductResponse.kt index b20fe20..907a1cb 100644 --- a/app/src/main/java/com/alya/ecommerce_serang/data/api/response/store/product/UpdateProductResponse.kt +++ b/app/src/main/java/com/alya/ecommerce_serang/data/api/response/store/product/UpdateProductResponse.kt @@ -1,6 +1,8 @@ package com.alya.ecommerce_serang.data.api.response.store.product +import com.alya.ecommerce_serang.data.api.dto.Preorder import com.alya.ecommerce_serang.data.api.dto.Product +import com.alya.ecommerce_serang.data.api.dto.Wholesale import com.google.gson.annotations.SerializedName data class UpdateProductResponse( @@ -8,6 +10,12 @@ data class UpdateProductResponse( @field:SerializedName("product") val product: Product? = null, + @field:SerializedName("wholesale") + val wholesale: Wholesale? = null, + @field:SerializedName("message") - val message: String? = null + val message: String? = null, + + @field:SerializedName("preorder") + val preorder: Preorder? = null ) diff --git a/app/src/main/java/com/alya/ecommerce_serang/data/api/retrofit/ApiService.kt b/app/src/main/java/com/alya/ecommerce_serang/data/api/retrofit/ApiService.kt index 2e1e579..c889c3c 100644 --- a/app/src/main/java/com/alya/ecommerce_serang/data/api/retrofit/ApiService.kt +++ b/app/src/main/java/com/alya/ecommerce_serang/data/api/retrofit/ApiService.kt @@ -263,6 +263,9 @@ interface ApiService { @Part("weight") weight: RequestBody, @Part("is_pre_order") isPreOrder: RequestBody, @Part("duration") duration: RequestBody, + @Part("is_wholesale") isWholesale: RequestBody, + @Part("wholesale_min_item") minItemWholesale: RequestBody, + @Part("wholesale_price") wholesalePrice: RequestBody, @Part("category_id") categoryId: RequestBody, @Part("status") status: RequestBody, @Part("condition") condition: RequestBody, @@ -271,10 +274,13 @@ interface ApiService { @Part halal: MultipartBody.Part? ): Response - @PUT("store/editproduct/{id}") + @Multipart + @PUT("store/editproduct") suspend fun updateProduct( - @Path("id") productId: Int?, - @Body updatedProduct: Map + @PartMap data: Map, + @Part productImage: MultipartBody.Part?, + @Part halal: MultipartBody.Part?, + @Part sppirt: MultipartBody.Part? ): Response @DELETE("store/deleteproduct/{id}") diff --git a/app/src/main/java/com/alya/ecommerce_serang/data/repository/ProductRepository.kt b/app/src/main/java/com/alya/ecommerce_serang/data/repository/ProductRepository.kt index 85aae9e..a3a1358 100644 --- a/app/src/main/java/com/alya/ecommerce_serang/data/repository/ProductRepository.kt +++ b/app/src/main/java/com/alya/ecommerce_serang/data/repository/ProductRepository.kt @@ -6,13 +6,13 @@ import com.alya.ecommerce_serang.data.api.dto.CategoryItem import com.alya.ecommerce_serang.data.api.dto.Preorder import com.alya.ecommerce_serang.data.api.dto.ProductsItem import com.alya.ecommerce_serang.data.api.dto.SearchRequest +import com.alya.ecommerce_serang.data.api.dto.Wholesale +import com.alya.ecommerce_serang.data.api.response.store.product.CreateProductResponse import com.alya.ecommerce_serang.data.api.response.customer.cart.AddCartResponse import com.alya.ecommerce_serang.data.api.response.customer.product.ProductResponse import com.alya.ecommerce_serang.data.api.response.customer.product.ReviewsItem import com.alya.ecommerce_serang.data.api.response.customer.product.StoreProduct import com.alya.ecommerce_serang.data.api.response.product.Search -import com.alya.ecommerce_serang.data.api.response.store.product.CreateProductResponse -import com.alya.ecommerce_serang.data.api.response.store.product.UpdateProductResponse import com.alya.ecommerce_serang.data.api.retrofit.ApiService import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext @@ -161,6 +161,8 @@ class ProductRepository(private val apiService: ApiService) { weight: Int, isPreOrder: Boolean, preorder: Preorder, + isWholesale: Boolean, + wholesale: Wholesale, categoryId: Int, status: String, condition: String, @@ -178,6 +180,9 @@ class ProductRepository(private val apiService: ApiService) { weight = RequestBody.create("text/plain".toMediaTypeOrNull(), weight.toString()), isPreOrder = RequestBody.create("text/plain".toMediaTypeOrNull(), isPreOrder.toString()), duration = RequestBody.create("text/plain".toMediaTypeOrNull(), preorder.duration.toString()), + isWholesale = RequestBody.create("text/plain".toMediaTypeOrNull(), isWholesale.toString()), + minItemWholesale = RequestBody.create("text/plain".toMediaTypeOrNull(), wholesale.minItem.toString()), + wholesalePrice = RequestBody.create("text/plain".toMediaTypeOrNull(), wholesale.wholesalePrice.toString()), categoryId = RequestBody.create("text/plain".toMediaTypeOrNull(), categoryId.toString()), status = RequestBody.create("text/plain".toMediaTypeOrNull(), status), condition = RequestBody.create("text/plain".toMediaTypeOrNull(), condition), @@ -260,16 +265,13 @@ class ProductRepository(private val apiService: ApiService) { } } - suspend fun updateProduct(productId: Int?, updatedProduct: Map) : UpdateProductResponse { - // Build the request with the updated fields - val response = apiService.updateProduct(productId, updatedProduct) - if (response.isSuccessful) { - return response.body()!! - } else { - throw Exception("Gagal memperbarui produk: ${response.code()}") - } - } - + suspend fun updateProduct( + productId: Int?, + data: Map, + image: MultipartBody.Part?, + halal: MultipartBody.Part?, + sppirt: MultipartBody.Part? + ) = apiService.updateProduct(data, image, halal, sppirt) suspend fun deleteProduct(productId: Int): Result { return withContext(Dispatchers.IO) { diff --git a/app/src/main/java/com/alya/ecommerce_serang/ui/profile/mystore/product/DetailStoreProductActivity.kt b/app/src/main/java/com/alya/ecommerce_serang/ui/profile/mystore/product/DetailStoreProductActivity.kt index 0e5fa50..cb7385d 100644 --- a/app/src/main/java/com/alya/ecommerce_serang/ui/profile/mystore/product/DetailStoreProductActivity.kt +++ b/app/src/main/java/com/alya/ecommerce_serang/ui/profile/mystore/product/DetailStoreProductActivity.kt @@ -3,8 +3,10 @@ package com.alya.ecommerce_serang.ui.profile.mystore.product import android.app.Activity import android.content.Context import android.content.Intent +import android.database.Cursor import android.net.Uri import android.os.Bundle +import android.provider.OpenableColumns import android.util.Log import android.view.View import android.widget.ArrayAdapter @@ -16,7 +18,6 @@ import androidx.appcompat.app.AppCompatActivity import androidx.core.content.ContextCompat import com.alya.ecommerce_serang.data.api.dto.CategoryItem import com.alya.ecommerce_serang.data.api.dto.Preorder -import com.alya.ecommerce_serang.data.api.dto.Product import com.alya.ecommerce_serang.data.api.retrofit.ApiConfig import com.alya.ecommerce_serang.data.repository.ProductRepository import com.alya.ecommerce_serang.data.repository.Result @@ -32,6 +33,9 @@ import java.io.File import java.io.FileOutputStream import kotlin.getValue import androidx.core.net.toUri +import androidx.core.widget.doAfterTextChanged +import com.alya.ecommerce_serang.BuildConfig.BASE_URL +import com.alya.ecommerce_serang.data.api.dto.Wholesale class DetailStoreProductActivity : AppCompatActivity() { @@ -42,6 +46,7 @@ class DetailStoreProductActivity : AppCompatActivity() { private var sppirtUri: Uri? = null private var halalUri: Uri? = null private var productId: Int? = null + private var hasImage: Boolean = false private val viewModel: ProductViewModel by viewModels { BaseViewModelFactory { @@ -58,6 +63,7 @@ class DetailStoreProductActivity : AppCompatActivity() { imageUri?.let { binding.ivPreviewFoto.setImageURI(it) binding.switcherFotoProduk.showNext() + hasImage = true } validateForm() } @@ -89,26 +95,39 @@ class DetailStoreProductActivity : AppCompatActivity() { binding.header.headerTitle.text = if (isEditing) "Ubah Produk" else "Tambah Produk" -// if (isEditing && productId != null) { -// viewModel.productDetail.observe(this) { product -> -// product?.let { -// populateForm(it) -// } -// } -// viewModel.loadProductDetail(productId!!) -// } + if (isEditing && productId != null && productId != -1) { + viewModel.loadProductDetail(productId!!) + viewModel.productDetail.observe(this) { product -> + product?.let { populateForm(it) } + } + } setupCategorySpinner() - setupImagePickers() + setupFilePickers() var conditionList = listOf("Baru", "Pernah Dipakai") val adapterCondition = ArrayAdapter(this, android.R.layout.simple_spinner_item, conditionList) adapterCondition.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item) binding.spinnerKondisiProduk.adapter = adapterCondition - // Setup Pre-Order visibility - binding.switchIsPreOrder.setOnCheckedChangeListener { _, isChecked -> - binding.layoutDurasi.visibility = if (isChecked) View.VISIBLE else View.GONE + with(binding) { + edtNamaProduk.doAfterTextChanged { validateForm() } + edtDeskripsiProduk.doAfterTextChanged { validateForm() } + edtHargaProduk.doAfterTextChanged { validateForm() } + edtStokProduk.doAfterTextChanged { validateForm() } + edtMinOrder.doAfterTextChanged { validateForm() } + edtBeratProduk.doAfterTextChanged { validateForm() } + edtDurasi.doAfterTextChanged { validateForm() } + edtMinPesanGrosir.doAfterTextChanged { validateForm() } + edtHargaGrosir.doAfterTextChanged { validateForm() } + switchIsPreOrder.setOnCheckedChangeListener { _, isChecked -> + togglePreOrderVisibility(isChecked) + validateForm() + } + switchIsWholesale.setOnCheckedChangeListener { _, isChecked -> + toggleWholesaleVisibility(isChecked) + validateForm() + } } validateForm() @@ -138,7 +157,7 @@ class DetailStoreProductActivity : AppCompatActivity() { } } - private fun setupImagePickers() { + private fun setupFilePickers() { binding.tvTambahFoto.setOnClickListener { val intent = Intent(Intent.ACTION_PICK).apply { type = "image/*" } imagePickerLauncher.launch(intent) @@ -151,6 +170,7 @@ class DetailStoreProductActivity : AppCompatActivity() { binding.btnRemoveFoto.setOnClickListener { imageUri = null + hasImage = false binding.switcherFotoProduk.showPrevious() validateForm() } @@ -168,33 +188,56 @@ class DetailStoreProductActivity : AppCompatActivity() { } } - private fun populateForm(product: Product?) { - binding.edtNamaProduk.setText(product?.name) - binding.edtDeskripsiProduk.setText(product?.description) - binding.edtHargaProduk.setText(product?.price.toString()) - binding.edtStokProduk.setText(product?.stock.toString()) - binding.edtMinOrder.setText(product?.minOrder.toString()) - binding.edtBeratProduk.setText(product?.weight.toString()) - binding.switchIsPreOrder.isChecked = product?.isPreOrder == false -// binding.switchIsWholesale.isChecked = product?.isWholesale == false -// binding.edtMinPesanGrosir.setText(product?.minOrderWholesale.toString()) -// binding.edtHargaGrosir.setText(product?.priceWholesale.toString()) - binding.switchIsActive.isChecked = product?.status == "active" - binding.spinnerKondisiProduk.setSelection(if (product?.condition == "Baru") 0 else 1) + private fun populateForm(product: com.alya.ecommerce_serang.data.api.response.customer.product.Product) { + binding.edtNamaProduk.setText(product.productName) + binding.edtDeskripsiProduk.setText(product.description) + binding.edtHargaProduk.setText(product.price.toString()) + binding.edtStokProduk.setText(product.stock.toString()) + binding.edtMinOrder.setText(product.minOrder.toString()) + binding.edtBeratProduk.setText(product.weight.toString()) + binding.spinnerKondisiProduk.setSelection(if (product.condition == "Baru") 0 else 1) - product?.categoryId?.let { - binding.spinnerKategoriProduk.setSelection(categoryList.indexOfFirst { it.id == product.categoryId }) + // Category selection + product.categoryId.let { categoryId -> + val index = categoryList.indexOfFirst { it.id == categoryId } + if (index != -1) binding.spinnerKategoriProduk.setSelection(index) } - Glide.with(this).load(product?.image).into(binding.ivPreviewFoto) - binding.switcherFotoProduk.showNext() + // Pre-order + val isPreOrder = product.isPreOrder == true + binding.switchIsPreOrder.jumpDrawablesToCurrentState() + binding.switchIsPreOrder.isChecked = isPreOrder + togglePreOrderVisibility(isPreOrder) + if (isPreOrder) { + binding.edtDurasi.setText(product.preorderDuration?.toString() ?: "") + } - product?.sppirt?.let { + // Wholesale + val isWholesale = product.isWholesale == true + binding.switchIsWholesale.jumpDrawablesToCurrentState() + binding.switchIsWholesale.isChecked = isWholesale + toggleWholesaleVisibility(isWholesale) + if (isWholesale) { + binding.edtMinPesanGrosir.setText(product.wholesaleMinItem?.toString() ?: "") + binding.edtHargaGrosir.setText(product.wholesalePrice?.toString() ?: "") + } + + // Product image + val imageUrl = if (product.image.startsWith("/")) { + BASE_URL + product.image.removePrefix("/") + } else product.image + Glide.with(this).load(imageUrl).into(binding.ivPreviewFoto) + binding.switcherFotoProduk.showNext() + hasImage = true + + // SPPIRT + product.sppirt?.let { binding.tvSppirtName.text = getFileName(it.toUri()) binding.switcherSppirt.showNext() } - product?.halal?.let { + // Halal + product.halal?.let { binding.tvHalalName.text = getFileName(it.toUri()) binding.switcherHalal.showNext() } @@ -202,13 +245,42 @@ class DetailStoreProductActivity : AppCompatActivity() { validateForm() } + private fun togglePreOrderVisibility(isChecked: Boolean) { + Log.d("DEBUG", "togglePreOrderVisibility: $isChecked") + binding.layoutDurasi.visibility = if (isChecked) View.VISIBLE else View.GONE + } + + private fun toggleWholesaleVisibility(isChecked: Boolean) { + Log.d("DEBUG", "toggleWholesaleVisibility: $isChecked") + binding.layoutMinPesanGrosir.visibility = if (isChecked) View.VISIBLE else View.GONE + binding.layoutHargaGrosir.visibility = if (isChecked) View.VISIBLE else View.GONE + } + private fun isValidFile(uri: Uri): Boolean { val mimeType = contentResolver.getType(uri) ?: return false return listOf("application/pdf", "image/jpeg", "image/png", "image/jpg").contains(mimeType) } private fun getFileName(uri: Uri): String { - return uri.lastPathSegment?.split("/")?.last() ?: "unknown_file" + var name = "unknown_file" + val cursor = contentResolver.query(uri, null, null, null, null) + cursor?.use { + if (it.moveToFirst()) { + val nameIndex = it.getColumnIndexOpenableColumnsDisplayName() + if (nameIndex != -1) { + name = it.getString(nameIndex) + } + } + } + return name + } + + private fun Cursor.getColumnIndexOpenableColumnsDisplayName(): Int { + return try { + getColumnIndexOrThrow(OpenableColumns.DISPLAY_NAME) + } catch (e: Exception) { + -1 + } } private fun uriToNamedFile(uri: Uri, context: Context, prefix: String): File { @@ -242,36 +314,63 @@ class DetailStoreProductActivity : AppCompatActivity() { } private fun validateForm() { - val valid = binding.edtNamaProduk.text.isNotBlank() && - binding.edtDeskripsiProduk.text.isNotBlank() && - binding.edtHargaProduk.text.isNotBlank() && - binding.edtStokProduk.text.isNotBlank() && - binding.edtMinOrder.text.isNotBlank() && - binding.edtBeratProduk.text.isNotBlank() && - (!binding.switchIsPreOrder.isChecked || binding.edtDurasi.text.isNotBlank()) && - imageUri != null + val name = binding.edtNamaProduk.text.toString().trim() + val description = binding.edtDeskripsiProduk.text.toString().trim() + val price = binding.edtHargaProduk.text.toString().trim() + val stock = binding.edtStokProduk.text.toString().trim() + val minOrder = binding.edtMinOrder.text.toString().trim() + val weight = binding.edtBeratProduk.text.toString().trim() + val duration = binding.edtDurasi.text.toString().trim() + val wholesaleMinItem = binding.edtMinPesanGrosir.text.toString().trim() + val wholesalePrice = binding.edtHargaGrosir.text.toString().trim() + val category = binding.spinnerKategoriProduk.selectedItemPosition != -1 + val isPreOrderChecked = binding.switchIsPreOrder.isChecked + val isWholesaleChecked = binding.switchIsWholesale.isChecked + + val valid = name.isNotEmpty() && + description.isNotEmpty() && + price.isNotEmpty() && + stock.isNotEmpty() && + minOrder.isNotEmpty() && + weight.isNotEmpty() && + (!isPreOrderChecked || duration.isNotEmpty()) && + (!isWholesaleChecked || (wholesaleMinItem.isNotEmpty() && wholesalePrice.isNotEmpty())) && + category && + hasImage binding.btnSaveProduct.isEnabled = valid binding.btnSaveProduct.setTextColor( - if (valid) ContextCompat.getColor(this, R.color.white) else ContextCompat.getColor(this, R.color.black_300) + if (valid) ContextCompat.getColor(this, R.color.white) + else ContextCompat.getColor(this, R.color.black_300) ) binding.btnSaveProduct.setBackgroundResource( - if (valid) R.drawable.bg_button_active else R.drawable.bg_button_disabled + if (valid) R.drawable.bg_button_active + else R.drawable.bg_button_disabled ) } private fun addProduct() { - val name = binding.edtNamaProduk.text.toString() - val description = binding.edtDeskripsiProduk.text.toString() - val price = binding.edtHargaProduk.text.toString().toInt() - val stock = binding.edtStokProduk.text.toString().toInt() - val minOrder = binding.edtMinOrder.text.toString().toInt() - val weight = binding.edtBeratProduk.text.toString().toInt() + val name = binding.edtNamaProduk.text.toString().trim() + val description = binding.edtDeskripsiProduk.text.toString().trim() + val price = binding.edtHargaProduk.text.toString().toIntOrNull() ?: return showInputError("Harga produk") + val stock = binding.edtStokProduk.text.toString().toIntOrNull() ?: return showInputError("Stok produk") + val minOrder = binding.edtMinOrder.text.toString().toIntOrNull() ?: return showInputError("Minimal order") + val weight = binding.edtBeratProduk.text.toString().toIntOrNull() ?: return showInputError("Berat produk") + val isPreOrder = binding.switchIsPreOrder.isChecked - val duration = if (isPreOrder) binding.edtDurasi.text.toString().toInt() else 0 + val duration = if (isPreOrder) { + binding.edtDurasi.text.toString().toIntOrNull() ?: return showInputError("Durasi pre-order") + } else 0 + val isWholesale = binding.switchIsWholesale.isChecked - val minOrderWholesale = binding.edtMinPesanGrosir.text.toString().toInt() - val priceWholesale = binding.edtHargaGrosir.text.toString().toInt() + val minOrderWholesale = if (isWholesale) { + binding.edtMinPesanGrosir.text.toString().toIntOrNull() ?: return showInputError("Min. grosir") + } else 0 + + val priceWholesale = if (isWholesale) { + binding.edtHargaGrosir.text.toString().toIntOrNull() ?: return showInputError("Harga grosir") + } else 0 + val status = if (binding.switchIsActive.isChecked) "active" else "inactive" val condition = binding.spinnerKondisiProduk.selectedItem.toString() val categoryId = categoryList.getOrNull(binding.spinnerKategoriProduk.selectedItemPosition)?.id ?: 0 @@ -288,9 +387,14 @@ class DetailStoreProductActivity : AppCompatActivity() { val halalPart = halalFile?.let { createPartFromFile("halal", it) } val preorder = Preorder(productId = productId, duration = duration) + val wholesale = Wholesale(productId = productId, minItem = minOrderWholesale, wholesalePrice = priceWholesale.toString()) viewModel.addProduct( - name, description, price, stock, minOrder, weight, isPreOrder, preorder, categoryId, status, condition, imagePart, sppirtPart, halalPart + name, description, price, stock, minOrder, weight, + isPreOrder, preorder, + isWholesale, wholesale, + categoryId, status, condition, + imagePart, sppirtPart, halalPart ) viewModel.productCreationResult.observe(this) { result -> @@ -309,24 +413,84 @@ class DetailStoreProductActivity : AppCompatActivity() { } } + private fun showInputError(fieldName: String): Nothing { + Toast.makeText(this, "$fieldName tidak boleh kosong dan harus berupa angka.", Toast.LENGTH_SHORT).show() + return throw IllegalArgumentException("$fieldName invalid") + } + private fun updateProduct(productId: Int?) { - val updatedProduct = mapOf( - "name" to binding.edtNamaProduk.text.toString(), - "description" to binding.edtDeskripsiProduk.text.toString(), - "price" to binding.edtHargaProduk.text.toString(), - "stock" to binding.edtStokProduk.text.toString().toInt(), - "min_order" to binding.edtMinOrder.text.toString().toInt(), - "weight" to binding.edtBeratProduk.text.toString().toInt(), - "is_pre_order" to binding.switchIsPreOrder.isChecked, - "duration" to binding.edtDurasi.text.toString().toInt(), - "category_id" to categoryList[binding.spinnerKategoriProduk.selectedItemPosition].id, - "status" to if (binding.switchIsActive.isChecked) "active" else "inactive", - "condition" to binding.spinnerKondisiProduk.selectedItem.toString(), - "productimg" to imageUri?.path, - "sppirt" to sppirtUri?.path, - "halal" to halalUri?.path + if (productId == null) return + + val imageFile = imageUri?.let { uriToNamedFile(it, this, "productimg") } + val sppirtFile = sppirtUri?.let { uriToNamedFile(it, this, "sppirt") } + val halalFile = halalUri?.let { uriToNamedFile(it, this, "halal") } + + val imagePart = createPartFromFile("productimg", imageFile) + val sppirtPart = createPartFromFile("sppirt", sppirtFile) + val halalPart = createPartFromFile("halal", halalFile) + + val data = mutableMapOf( + "product_id" to toRequestBody(productId.toString()), + "name" to toRequestBody(binding.edtNamaProduk.text.toString()), + "description" to toRequestBody(binding.edtDeskripsiProduk.text.toString()), + "price" to toRequestBody(binding.edtHargaProduk.text.toString()), + "stock" to toRequestBody(binding.edtStokProduk.text.toString()), + "min_order" to toRequestBody(binding.edtMinOrder.text.toString()), + "weight" to toRequestBody(binding.edtBeratProduk.text.toString()), + "is_pre_order" to toRequestBody(binding.switchIsPreOrder.isChecked.toString()), + "preorder_duration" to toRequestBody( + if (binding.switchIsPreOrder.isChecked) + binding.edtDurasi.text.toString() + else + "0" + ), + "is_wholesale" to toRequestBody(binding.switchIsWholesale.isChecked.toString()), + "wholesale_min_item" to toRequestBody( + if (binding.switchIsWholesale.isChecked) + binding.edtMinPesanGrosir.text.toString() + else + "0" + ), + "wholesale_price" to toRequestBody( + if (binding.switchIsWholesale.isChecked) + binding.edtHargaGrosir.text.toString() + else + "0" + ), + "category_id" to toRequestBody( + categoryList[binding.spinnerKategoriProduk.selectedItemPosition].id.toString() + ), + "status" to toRequestBody( + if (binding.switchIsActive.isChecked) "active" else "inactive" + ), + "condition" to toRequestBody(binding.spinnerKondisiProduk.selectedItem.toString()) ) - viewModel.updateProduct(productId, updatedProduct) + viewModel.updateProduct( + productId, + data, + imagePart, + halalPart, + sppirtPart + ) + + viewModel.productUpdateResult.observe(this) { result -> + when (result) { + is Result.Loading -> binding.btnSaveProduct.isEnabled = false + is Result.Success -> { + val product = result.data.product + Toast.makeText(this, "Produk berhasil diubah: ${product?.name}", Toast.LENGTH_SHORT).show() + setResult(Activity.RESULT_OK) + finish() + } + is Result.Error -> { + Log.e("ProductDetailActivity", "Error: ${result.exception.message}") + binding.btnSaveProduct.isEnabled = true + } + } + } } + + private fun toRequestBody(value: String): RequestBody = + RequestBody.create("text/plain".toMediaTypeOrNull(), value) } \ No newline at end of file diff --git a/app/src/main/java/com/alya/ecommerce_serang/ui/profile/mystore/product/ProductActivity.kt b/app/src/main/java/com/alya/ecommerce_serang/ui/profile/mystore/product/ProductActivity.kt index 3a40ba1..9c4aee5 100644 --- a/app/src/main/java/com/alya/ecommerce_serang/ui/profile/mystore/product/ProductActivity.kt +++ b/app/src/main/java/com/alya/ecommerce_serang/ui/profile/mystore/product/ProductActivity.kt @@ -43,6 +43,11 @@ class ProductActivity : AppCompatActivity() { } + override fun onResume() { + super.onResume() + viewModel.loadMyStoreProducts() + } + private fun observeViewModel() { viewModel.productList.observe(this) { result -> when (result) { diff --git a/app/src/main/java/com/alya/ecommerce_serang/ui/profile/mystore/product/ProductAdapter.kt b/app/src/main/java/com/alya/ecommerce_serang/ui/profile/mystore/product/ProductAdapter.kt index ec831be..ba9dc2f 100644 --- a/app/src/main/java/com/alya/ecommerce_serang/ui/profile/mystore/product/ProductAdapter.kt +++ b/app/src/main/java/com/alya/ecommerce_serang/ui/profile/mystore/product/ProductAdapter.kt @@ -10,6 +10,7 @@ import android.widget.Toast import androidx.core.content.ContextCompat import androidx.fragment.app.FragmentActivity import androidx.recyclerview.widget.RecyclerView +import com.alya.ecommerce_serang.BuildConfig.BASE_URL import com.alya.ecommerce_serang.R import com.alya.ecommerce_serang.data.api.dto.Product import com.alya.ecommerce_serang.data.api.dto.ProductsItem @@ -43,8 +44,13 @@ class ProductAdapter( tvProductStatus.background = ContextCompat.getDrawable(itemView.context, R.drawable.bg_product_inactive) } + val imageUrl = if (product.image.startsWith("/")) { + BASE_URL + product.image.removePrefix("/") // Append base URL if the path starts with "/" + } else { + product.image // Use as is if it's already a full URL + } Glide.with(itemView.context) - .load(product.image) + .load(imageUrl) .placeholder(R.drawable.placeholder_image) .into(ivProduct) diff --git a/app/src/main/java/com/alya/ecommerce_serang/ui/profile/mystore/product/ProductOptionsBottomSheetFragment.kt b/app/src/main/java/com/alya/ecommerce_serang/ui/profile/mystore/product/ProductOptionsBottomSheetFragment.kt index 7c990f1..b80f73d 100644 --- a/app/src/main/java/com/alya/ecommerce_serang/ui/profile/mystore/product/ProductOptionsBottomSheetFragment.kt +++ b/app/src/main/java/com/alya/ecommerce_serang/ui/profile/mystore/product/ProductOptionsBottomSheetFragment.kt @@ -42,10 +42,14 @@ class ProductOptionsBottomSheetFragment(private val product: ProductsItem) : Bot .setTitle("Hapus Produk?") .setMessage("Produk yang dihapus tidak dapat dikembalikan.") .setPositiveButton("Ya, Hapus") { _, _ -> - val viewModel = ViewModelProvider(this).get(ProductViewModel::class.java) + val viewModel = ViewModelProvider(requireActivity())[ProductViewModel::class.java] viewModel.deleteProduct(product.id) Toast.makeText(context, "Produk berhasil dihapus", Toast.LENGTH_SHORT).show() dismiss() + activity?.run { + finish() + startActivity(Intent(this, ProductActivity::class.java)) + } } .setNegativeButton("Batalkan", null) .show() diff --git a/app/src/main/java/com/alya/ecommerce_serang/utils/viewmodel/ProductViewModel.kt b/app/src/main/java/com/alya/ecommerce_serang/utils/viewmodel/ProductViewModel.kt index 703db21..e2d5611 100644 --- a/app/src/main/java/com/alya/ecommerce_serang/utils/viewmodel/ProductViewModel.kt +++ b/app/src/main/java/com/alya/ecommerce_serang/utils/viewmodel/ProductViewModel.kt @@ -7,6 +7,7 @@ import androidx.lifecycle.viewModelScope import com.alya.ecommerce_serang.data.api.dto.CategoryItem import com.alya.ecommerce_serang.data.api.dto.Preorder import com.alya.ecommerce_serang.data.api.dto.ProductsItem +import com.alya.ecommerce_serang.data.api.dto.Wholesale import com.alya.ecommerce_serang.data.api.response.store.product.CreateProductResponse import com.alya.ecommerce_serang.data.api.response.customer.product.Product import com.alya.ecommerce_serang.data.api.response.customer.product.ReviewsItem @@ -16,6 +17,7 @@ import com.alya.ecommerce_serang.data.repository.ProductRepository import com.alya.ecommerce_serang.data.repository.Result import kotlinx.coroutines.launch import okhttp3.MultipartBody +import okhttp3.RequestBody class ProductViewModel(private val repository: ProductRepository) : ViewModel() { @@ -88,6 +90,8 @@ class ProductViewModel(private val repository: ProductRepository) : ViewModel() weight: Int, isPreOrder: Boolean, preorder: Preorder, + isWholesale: Boolean, + wholesale: Wholesale, categoryId: Int, status: String, condition: String, @@ -98,20 +102,26 @@ class ProductViewModel(private val repository: ProductRepository) : ViewModel() _productCreationResult.value = Result.Loading viewModelScope.launch { val result = repository.addProduct( - name, description, price, stock, minOrder, weight, isPreOrder, preorder, categoryId, status, condition, imagePart, sppirtPart, halalPart + name, description, price, stock, minOrder, weight, isPreOrder, preorder, isWholesale, wholesale, categoryId, status, condition, imagePart, sppirtPart, halalPart ) _productCreationResult.value = result } } - fun updateProduct(productId: Int?, updatedProduct: Map) { - _productUpdateResult.value = Result.Loading + fun updateProduct( + productId: Int?, + data: Map, + image: MultipartBody.Part?, + halal: MultipartBody.Part?, + sppirt: MultipartBody.Part? + ) { viewModelScope.launch { + _productUpdateResult.postValue(Result.Loading) try { - val response = repository.updateProduct(productId, updatedProduct) - _productUpdateResult.value = Result.Success(response) + val response = repository.updateProduct(productId, data, image, halal, sppirt) + _productUpdateResult.postValue(Result.Success(response.body()!!)) } catch (e: Exception) { - _productUpdateResult.value = Result.Error(e) + _productUpdateResult.postValue(Result.Error(e)) } } } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 7ee4510..5b78d69 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,5 +1,5 @@ [versions] -agp = "8.9.2" +agp = "8.10.0" glide = "4.16.0" gson = "2.11.0" hiltAndroid = "2.56.2" # Updated from 2.44 for better compatibility