From 59e2e6a43bb0f09494a93372f23c189b54dc2825 Mon Sep 17 00:00:00 2001 From: Gracia Hotmauli <95269134+hotmauligracia@users.noreply.github.com> Date: Sat, 24 May 2025 18:05:13 +0700 Subject: [PATCH] product create, update, delete --- .../ecommerce_serang/data/api/dto/Product.kt | 3 + .../data/api/dto/Wholesale.kt | 18 ++ .../store/product/CreateProductResponse.kt | 6 +- .../store/product/UpdateProductResponse.kt | 10 +- .../data/api/retrofit/ApiService.kt | 3 + .../data/repository/ProductRepository.kt | 11 +- .../product/DetailStoreProductActivity.kt | 238 ++++++++++++++---- .../mystore/product/ProductActivity.kt | 5 + .../profile/mystore/product/ProductAdapter.kt | 8 +- .../ProductOptionsBottomSheetFragment.kt | 6 +- .../utils/viewmodel/ProductViewModel.kt | 5 +- gradle/libs.versions.toml | 2 +- 12 files changed, 254 insertions(+), 61 deletions(-) create mode 100644 app/src/main/java/com/alya/ecommerce_serang/data/api/dto/Wholesale.kt 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..34e4a86 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, 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..7022d8b 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,14 +6,16 @@ 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.response.product.Search import com.alya.ecommerce_serang.data.api.retrofit.ApiService +import com.alya.ecommerce_serang.utils.SessionManager import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext import okhttp3.MediaType.Companion.toMediaTypeOrNull @@ -161,6 +163,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 +182,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), 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..66ef9e7 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,17 +95,15 @@ 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) @@ -108,7 +112,27 @@ class DetailStoreProductActivity : AppCompatActivity() { // Setup Pre-Order visibility binding.switchIsPreOrder.setOnCheckedChangeListener { _, isChecked -> - binding.layoutDurasi.visibility = if (isChecked) View.VISIBLE else View.GONE + togglePreOrderVisibility(isChecked) + validateForm() + } + + binding.switchIsWholesale.setOnCheckedChangeListener { _, isChecked -> + toggleWholesaleVisibility(isChecked) + validateForm() + } + + 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 { _, _ -> validateForm() } + switchIsWholesale.setOnCheckedChangeListener { _, _ -> validateForm() } } validateForm() @@ -138,7 +162,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 +175,7 @@ class DetailStoreProductActivity : AppCompatActivity() { binding.btnRemoveFoto.setOnClickListener { imageUri = null + hasImage = false binding.switcherFotoProduk.showPrevious() validateForm() } @@ -168,33 +193,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 +250,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 +319,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 +392,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,6 +418,11 @@ 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(), @@ -318,7 +432,10 @@ class DetailStoreProductActivity : AppCompatActivity() { "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(), + "duration" to (binding.edtDurasi.text.toString().toIntOrNull() ?: 0), + "is_wholesale" to binding.switchIsWholesale.isChecked, + "wholesale_min_item" to (binding.edtMinPesanGrosir.text.toString().toIntOrNull() ?: 0), + "wholesale_price" to (binding.edtHargaGrosir.text.toString().toIntOrNull() ?: 0), "category_id" to categoryList[binding.spinnerKategoriProduk.selectedItemPosition].id, "status" to if (binding.switchIsActive.isChecked) "active" else "inactive", "condition" to binding.spinnerKondisiProduk.selectedItem.toString(), @@ -328,5 +445,20 @@ class DetailStoreProductActivity : AppCompatActivity() { ) viewModel.updateProduct(productId, updatedProduct) + 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 + } + } + } } } \ 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..3f80593 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 @@ -88,6 +89,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,7 +101,7 @@ 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 } 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