product create, update, delete

This commit is contained in:
Gracia Hotmauli
2025-05-24 18:05:13 +07:00
parent fca21743d6
commit 59e2e6a43b
12 changed files with 254 additions and 61 deletions

View File

@ -10,6 +10,9 @@ data class Product(
@field:SerializedName("image") @field:SerializedName("image")
val image: String? = null, val image: String? = null,
@field:SerializedName("is_wholesale")
val isWholesale: Boolean? = null,
@field:SerializedName("sppirt") @field:SerializedName("sppirt")
val sppirt: String? = null, val sppirt: String? = null,

View File

@ -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
)

View File

@ -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.Preorder
import com.alya.ecommerce_serang.data.api.dto.Product import com.alya.ecommerce_serang.data.api.dto.Product
import com.alya.ecommerce_serang.data.api.dto.Wholesale
import com.google.gson.annotations.SerializedName import com.google.gson.annotations.SerializedName
data class CreateProductResponse( data class CreateProductResponse(
@ -9,9 +10,12 @@ data class CreateProductResponse(
@field:SerializedName("product") @field:SerializedName("product")
val product: Product? = null, val product: Product? = null,
@field:SerializedName("wholesale")
val wholesale: Wholesale? = null,
@field:SerializedName("message") @field:SerializedName("message")
val message: String? = null, val message: String? = null,
@field:SerializedName("preorder") @field:SerializedName("preorder")
val preorder: Preorder? = null val preorder: Preorder? = null
) )

View File

@ -1,6 +1,8 @@
package com.alya.ecommerce_serang.data.api.response.store.product 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.Product
import com.alya.ecommerce_serang.data.api.dto.Wholesale
import com.google.gson.annotations.SerializedName import com.google.gson.annotations.SerializedName
data class UpdateProductResponse( data class UpdateProductResponse(
@ -8,6 +10,12 @@ data class UpdateProductResponse(
@field:SerializedName("product") @field:SerializedName("product")
val product: Product? = null, val product: Product? = null,
@field:SerializedName("wholesale")
val wholesale: Wholesale? = null,
@field:SerializedName("message") @field:SerializedName("message")
val message: String? = null val message: String? = null,
@field:SerializedName("preorder")
val preorder: Preorder? = null
) )

View File

@ -263,6 +263,9 @@ interface ApiService {
@Part("weight") weight: RequestBody, @Part("weight") weight: RequestBody,
@Part("is_pre_order") isPreOrder: RequestBody, @Part("is_pre_order") isPreOrder: RequestBody,
@Part("duration") duration: 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("category_id") categoryId: RequestBody,
@Part("status") status: RequestBody, @Part("status") status: RequestBody,
@Part("condition") condition: RequestBody, @Part("condition") condition: RequestBody,

View File

@ -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.Preorder
import com.alya.ecommerce_serang.data.api.dto.ProductsItem 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.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.cart.AddCartResponse
import com.alya.ecommerce_serang.data.api.response.customer.product.ProductResponse 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.ReviewsItem
import com.alya.ecommerce_serang.data.api.response.customer.product.StoreProduct 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.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.data.api.retrofit.ApiService
import com.alya.ecommerce_serang.utils.SessionManager
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import okhttp3.MediaType.Companion.toMediaTypeOrNull import okhttp3.MediaType.Companion.toMediaTypeOrNull
@ -161,6 +163,8 @@ class ProductRepository(private val apiService: ApiService) {
weight: Int, weight: Int,
isPreOrder: Boolean, isPreOrder: Boolean,
preorder: Preorder, preorder: Preorder,
isWholesale: Boolean,
wholesale: Wholesale,
categoryId: Int, categoryId: Int,
status: String, status: String,
condition: String, condition: String,
@ -178,6 +182,9 @@ class ProductRepository(private val apiService: ApiService) {
weight = RequestBody.create("text/plain".toMediaTypeOrNull(), weight.toString()), weight = RequestBody.create("text/plain".toMediaTypeOrNull(), weight.toString()),
isPreOrder = RequestBody.create("text/plain".toMediaTypeOrNull(), isPreOrder.toString()), isPreOrder = RequestBody.create("text/plain".toMediaTypeOrNull(), isPreOrder.toString()),
duration = RequestBody.create("text/plain".toMediaTypeOrNull(), preorder.duration.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()), categoryId = RequestBody.create("text/plain".toMediaTypeOrNull(), categoryId.toString()),
status = RequestBody.create("text/plain".toMediaTypeOrNull(), status), status = RequestBody.create("text/plain".toMediaTypeOrNull(), status),
condition = RequestBody.create("text/plain".toMediaTypeOrNull(), condition), condition = RequestBody.create("text/plain".toMediaTypeOrNull(), condition),

View File

@ -3,8 +3,10 @@ package com.alya.ecommerce_serang.ui.profile.mystore.product
import android.app.Activity import android.app.Activity
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.database.Cursor
import android.net.Uri import android.net.Uri
import android.os.Bundle import android.os.Bundle
import android.provider.OpenableColumns
import android.util.Log import android.util.Log
import android.view.View import android.view.View
import android.widget.ArrayAdapter import android.widget.ArrayAdapter
@ -16,7 +18,6 @@ import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import com.alya.ecommerce_serang.data.api.dto.CategoryItem 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.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.api.retrofit.ApiConfig
import com.alya.ecommerce_serang.data.repository.ProductRepository import com.alya.ecommerce_serang.data.repository.ProductRepository
import com.alya.ecommerce_serang.data.repository.Result import com.alya.ecommerce_serang.data.repository.Result
@ -32,6 +33,9 @@ import java.io.File
import java.io.FileOutputStream import java.io.FileOutputStream
import kotlin.getValue import kotlin.getValue
import androidx.core.net.toUri 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() { class DetailStoreProductActivity : AppCompatActivity() {
@ -42,6 +46,7 @@ class DetailStoreProductActivity : AppCompatActivity() {
private var sppirtUri: Uri? = null private var sppirtUri: Uri? = null
private var halalUri: Uri? = null private var halalUri: Uri? = null
private var productId: Int? = null private var productId: Int? = null
private var hasImage: Boolean = false
private val viewModel: ProductViewModel by viewModels { private val viewModel: ProductViewModel by viewModels {
BaseViewModelFactory { BaseViewModelFactory {
@ -58,6 +63,7 @@ class DetailStoreProductActivity : AppCompatActivity() {
imageUri?.let { imageUri?.let {
binding.ivPreviewFoto.setImageURI(it) binding.ivPreviewFoto.setImageURI(it)
binding.switcherFotoProduk.showNext() binding.switcherFotoProduk.showNext()
hasImage = true
} }
validateForm() validateForm()
} }
@ -89,17 +95,15 @@ class DetailStoreProductActivity : AppCompatActivity() {
binding.header.headerTitle.text = if (isEditing) "Ubah Produk" else "Tambah Produk" binding.header.headerTitle.text = if (isEditing) "Ubah Produk" else "Tambah Produk"
// if (isEditing && productId != null) { if (isEditing && productId != null && productId != -1) {
// viewModel.productDetail.observe(this) { product -> viewModel.loadProductDetail(productId!!)
// product?.let { viewModel.productDetail.observe(this) { product ->
// populateForm(it) product?.let { populateForm(it) }
// } }
// } }
// viewModel.loadProductDetail(productId!!)
// }
setupCategorySpinner() setupCategorySpinner()
setupImagePickers() setupFilePickers()
var conditionList = listOf("Baru", "Pernah Dipakai") var conditionList = listOf("Baru", "Pernah Dipakai")
val adapterCondition = ArrayAdapter(this, android.R.layout.simple_spinner_item, conditionList) val adapterCondition = ArrayAdapter(this, android.R.layout.simple_spinner_item, conditionList)
@ -108,7 +112,27 @@ class DetailStoreProductActivity : AppCompatActivity() {
// Setup Pre-Order visibility // Setup Pre-Order visibility
binding.switchIsPreOrder.setOnCheckedChangeListener { _, isChecked -> 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() validateForm()
@ -138,7 +162,7 @@ class DetailStoreProductActivity : AppCompatActivity() {
} }
} }
private fun setupImagePickers() { private fun setupFilePickers() {
binding.tvTambahFoto.setOnClickListener { binding.tvTambahFoto.setOnClickListener {
val intent = Intent(Intent.ACTION_PICK).apply { type = "image/*" } val intent = Intent(Intent.ACTION_PICK).apply { type = "image/*" }
imagePickerLauncher.launch(intent) imagePickerLauncher.launch(intent)
@ -151,6 +175,7 @@ class DetailStoreProductActivity : AppCompatActivity() {
binding.btnRemoveFoto.setOnClickListener { binding.btnRemoveFoto.setOnClickListener {
imageUri = null imageUri = null
hasImage = false
binding.switcherFotoProduk.showPrevious() binding.switcherFotoProduk.showPrevious()
validateForm() validateForm()
} }
@ -168,33 +193,56 @@ class DetailStoreProductActivity : AppCompatActivity() {
} }
} }
private fun populateForm(product: Product?) { private fun populateForm(product: com.alya.ecommerce_serang.data.api.response.customer.product.Product) {
binding.edtNamaProduk.setText(product?.name) binding.edtNamaProduk.setText(product.productName)
binding.edtDeskripsiProduk.setText(product?.description) binding.edtDeskripsiProduk.setText(product.description)
binding.edtHargaProduk.setText(product?.price.toString()) binding.edtHargaProduk.setText(product.price.toString())
binding.edtStokProduk.setText(product?.stock.toString()) binding.edtStokProduk.setText(product.stock.toString())
binding.edtMinOrder.setText(product?.minOrder.toString()) binding.edtMinOrder.setText(product.minOrder.toString())
binding.edtBeratProduk.setText(product?.weight.toString()) binding.edtBeratProduk.setText(product.weight.toString())
binding.switchIsPreOrder.isChecked = product?.isPreOrder == false binding.spinnerKondisiProduk.setSelection(if (product.condition == "Baru") 0 else 1)
// 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)
product?.categoryId?.let { // Category selection
binding.spinnerKategoriProduk.setSelection(categoryList.indexOfFirst { it.id == product.categoryId }) 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) // Pre-order
binding.switcherFotoProduk.showNext() 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.tvSppirtName.text = getFileName(it.toUri())
binding.switcherSppirt.showNext() binding.switcherSppirt.showNext()
} }
product?.halal?.let { // Halal
product.halal?.let {
binding.tvHalalName.text = getFileName(it.toUri()) binding.tvHalalName.text = getFileName(it.toUri())
binding.switcherHalal.showNext() binding.switcherHalal.showNext()
} }
@ -202,13 +250,42 @@ class DetailStoreProductActivity : AppCompatActivity() {
validateForm() 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 { private fun isValidFile(uri: Uri): Boolean {
val mimeType = contentResolver.getType(uri) ?: return false val mimeType = contentResolver.getType(uri) ?: return false
return listOf("application/pdf", "image/jpeg", "image/png", "image/jpg").contains(mimeType) return listOf("application/pdf", "image/jpeg", "image/png", "image/jpg").contains(mimeType)
} }
private fun getFileName(uri: Uri): String { 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 { private fun uriToNamedFile(uri: Uri, context: Context, prefix: String): File {
@ -242,36 +319,63 @@ class DetailStoreProductActivity : AppCompatActivity() {
} }
private fun validateForm() { private fun validateForm() {
val valid = binding.edtNamaProduk.text.isNotBlank() && val name = binding.edtNamaProduk.text.toString().trim()
binding.edtDeskripsiProduk.text.isNotBlank() && val description = binding.edtDeskripsiProduk.text.toString().trim()
binding.edtHargaProduk.text.isNotBlank() && val price = binding.edtHargaProduk.text.toString().trim()
binding.edtStokProduk.text.isNotBlank() && val stock = binding.edtStokProduk.text.toString().trim()
binding.edtMinOrder.text.isNotBlank() && val minOrder = binding.edtMinOrder.text.toString().trim()
binding.edtBeratProduk.text.isNotBlank() && val weight = binding.edtBeratProduk.text.toString().trim()
(!binding.switchIsPreOrder.isChecked || binding.edtDurasi.text.isNotBlank()) && val duration = binding.edtDurasi.text.toString().trim()
imageUri != null 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.isEnabled = valid
binding.btnSaveProduct.setTextColor( 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( 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() { private fun addProduct() {
val name = binding.edtNamaProduk.text.toString() val name = binding.edtNamaProduk.text.toString().trim()
val description = binding.edtDeskripsiProduk.text.toString() val description = binding.edtDeskripsiProduk.text.toString().trim()
val price = binding.edtHargaProduk.text.toString().toInt() val price = binding.edtHargaProduk.text.toString().toIntOrNull() ?: return showInputError("Harga produk")
val stock = binding.edtStokProduk.text.toString().toInt() val stock = binding.edtStokProduk.text.toString().toIntOrNull() ?: return showInputError("Stok produk")
val minOrder = binding.edtMinOrder.text.toString().toInt() val minOrder = binding.edtMinOrder.text.toString().toIntOrNull() ?: return showInputError("Minimal order")
val weight = binding.edtBeratProduk.text.toString().toInt() val weight = binding.edtBeratProduk.text.toString().toIntOrNull() ?: return showInputError("Berat produk")
val isPreOrder = binding.switchIsPreOrder.isChecked 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 isWholesale = binding.switchIsWholesale.isChecked
val minOrderWholesale = binding.edtMinPesanGrosir.text.toString().toInt() val minOrderWholesale = if (isWholesale) {
val priceWholesale = binding.edtHargaGrosir.text.toString().toInt() 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 status = if (binding.switchIsActive.isChecked) "active" else "inactive"
val condition = binding.spinnerKondisiProduk.selectedItem.toString() val condition = binding.spinnerKondisiProduk.selectedItem.toString()
val categoryId = categoryList.getOrNull(binding.spinnerKategoriProduk.selectedItemPosition)?.id ?: 0 val categoryId = categoryList.getOrNull(binding.spinnerKategoriProduk.selectedItemPosition)?.id ?: 0
@ -288,9 +392,14 @@ class DetailStoreProductActivity : AppCompatActivity() {
val halalPart = halalFile?.let { createPartFromFile("halal", it) } val halalPart = halalFile?.let { createPartFromFile("halal", it) }
val preorder = Preorder(productId = productId, duration = duration) val preorder = Preorder(productId = productId, duration = duration)
val wholesale = Wholesale(productId = productId, minItem = minOrderWholesale, wholesalePrice = priceWholesale.toString())
viewModel.addProduct( 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 -> 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?) { private fun updateProduct(productId: Int?) {
val updatedProduct = mapOf( val updatedProduct = mapOf(
"name" to binding.edtNamaProduk.text.toString(), "name" to binding.edtNamaProduk.text.toString(),
@ -318,7 +432,10 @@ class DetailStoreProductActivity : AppCompatActivity() {
"min_order" to binding.edtMinOrder.text.toString().toInt(), "min_order" to binding.edtMinOrder.text.toString().toInt(),
"weight" to binding.edtBeratProduk.text.toString().toInt(), "weight" to binding.edtBeratProduk.text.toString().toInt(),
"is_pre_order" to binding.switchIsPreOrder.isChecked, "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, "category_id" to categoryList[binding.spinnerKategoriProduk.selectedItemPosition].id,
"status" to if (binding.switchIsActive.isChecked) "active" else "inactive", "status" to if (binding.switchIsActive.isChecked) "active" else "inactive",
"condition" to binding.spinnerKondisiProduk.selectedItem.toString(), "condition" to binding.spinnerKondisiProduk.selectedItem.toString(),
@ -328,5 +445,20 @@ class DetailStoreProductActivity : AppCompatActivity() {
) )
viewModel.updateProduct(productId, updatedProduct) 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
}
}
}
} }
} }

View File

@ -43,6 +43,11 @@ class ProductActivity : AppCompatActivity() {
} }
override fun onResume() {
super.onResume()
viewModel.loadMyStoreProducts()
}
private fun observeViewModel() { private fun observeViewModel() {
viewModel.productList.observe(this) { result -> viewModel.productList.observe(this) { result ->
when (result) { when (result) {

View File

@ -10,6 +10,7 @@ import android.widget.Toast
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.fragment.app.FragmentActivity import androidx.fragment.app.FragmentActivity
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import com.alya.ecommerce_serang.BuildConfig.BASE_URL
import com.alya.ecommerce_serang.R import com.alya.ecommerce_serang.R
import com.alya.ecommerce_serang.data.api.dto.Product import com.alya.ecommerce_serang.data.api.dto.Product
import com.alya.ecommerce_serang.data.api.dto.ProductsItem 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) 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) Glide.with(itemView.context)
.load(product.image) .load(imageUrl)
.placeholder(R.drawable.placeholder_image) .placeholder(R.drawable.placeholder_image)
.into(ivProduct) .into(ivProduct)

View File

@ -42,10 +42,14 @@ class ProductOptionsBottomSheetFragment(private val product: ProductsItem) : Bot
.setTitle("Hapus Produk?") .setTitle("Hapus Produk?")
.setMessage("Produk yang dihapus tidak dapat dikembalikan.") .setMessage("Produk yang dihapus tidak dapat dikembalikan.")
.setPositiveButton("Ya, Hapus") { _, _ -> .setPositiveButton("Ya, Hapus") { _, _ ->
val viewModel = ViewModelProvider(this).get(ProductViewModel::class.java) val viewModel = ViewModelProvider(requireActivity())[ProductViewModel::class.java]
viewModel.deleteProduct(product.id) viewModel.deleteProduct(product.id)
Toast.makeText(context, "Produk berhasil dihapus", Toast.LENGTH_SHORT).show() Toast.makeText(context, "Produk berhasil dihapus", Toast.LENGTH_SHORT).show()
dismiss() dismiss()
activity?.run {
finish()
startActivity(Intent(this, ProductActivity::class.java))
}
} }
.setNegativeButton("Batalkan", null) .setNegativeButton("Batalkan", null)
.show() .show()

View File

@ -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.CategoryItem
import com.alya.ecommerce_serang.data.api.dto.Preorder 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.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.store.product.CreateProductResponse
import com.alya.ecommerce_serang.data.api.response.customer.product.Product import com.alya.ecommerce_serang.data.api.response.customer.product.Product
import com.alya.ecommerce_serang.data.api.response.customer.product.ReviewsItem import com.alya.ecommerce_serang.data.api.response.customer.product.ReviewsItem
@ -88,6 +89,8 @@ class ProductViewModel(private val repository: ProductRepository) : ViewModel()
weight: Int, weight: Int,
isPreOrder: Boolean, isPreOrder: Boolean,
preorder: Preorder, preorder: Preorder,
isWholesale: Boolean,
wholesale: Wholesale,
categoryId: Int, categoryId: Int,
status: String, status: String,
condition: String, condition: String,
@ -98,7 +101,7 @@ class ProductViewModel(private val repository: ProductRepository) : ViewModel()
_productCreationResult.value = Result.Loading _productCreationResult.value = Result.Loading
viewModelScope.launch { viewModelScope.launch {
val result = repository.addProduct( 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 _productCreationResult.value = result
} }

View File

@ -1,5 +1,5 @@
[versions] [versions]
agp = "8.9.2" agp = "8.10.0"
glide = "4.16.0" glide = "4.16.0"
gson = "2.11.0" gson = "2.11.0"
hiltAndroid = "2.56.2" # Updated from 2.44 for better compatibility hiltAndroid = "2.56.2" # Updated from 2.44 for better compatibility