From 9273e01324a13d97e5708f642720caaf6079233b Mon Sep 17 00:00:00 2001 From: Gracia Hotmauli <95269134+hotmauligracia@users.noreply.github.com> Date: Tue, 26 Aug 2025 03:26:58 +0700 Subject: [PATCH] fix register store and product --- .../data/api/dto/PaymentUpdate.kt | 21 + .../data/api/retrofit/ApiService.kt | 74 ++- .../data/repository/MyStoreRepository.kt | 186 +++++++ .../profile/mystore/RegisterStoreActivity.kt | 474 ++++++++++-------- .../product/DetailStoreProductActivity.kt | 22 +- .../utils/viewmodel/MyStoreViewModel.kt | 80 ++- .../layout/activity_detail_store_product.xml | 3 + .../res/layout/activity_register_store.xml | 261 ++++------ 8 files changed, 717 insertions(+), 404 deletions(-) create mode 100644 app/src/main/java/com/alya/ecommerce_serang/data/api/dto/PaymentUpdate.kt diff --git a/app/src/main/java/com/alya/ecommerce_serang/data/api/dto/PaymentUpdate.kt b/app/src/main/java/com/alya/ecommerce_serang/data/api/dto/PaymentUpdate.kt new file mode 100644 index 0000000..4c2001b --- /dev/null +++ b/app/src/main/java/com/alya/ecommerce_serang/data/api/dto/PaymentUpdate.kt @@ -0,0 +1,21 @@ +package com.alya.ecommerce_serang.data.api.dto + +import com.google.gson.annotations.SerializedName +import java.io.File + +data class PaymentUpdate( + @field:SerializedName("id") + val id: Int? = null, + + @field:SerializedName("bank_name") + val bankName: String, + + @field:SerializedName("bank_num") + val bankNum: String, + + @field:SerializedName("account_name") + val accountName: String, + + @field:SerializedName("qris_image") + val qrisImage: File? = 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 c2c83b6..9316122 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 @@ -1,7 +1,6 @@ package com.alya.ecommerce_serang.data.api.retrofit -import com.alya.ecommerce_serang.data.api.dto.AddEvidenceRequest import com.alya.ecommerce_serang.data.api.dto.AddPaymentInfoResponse import com.alya.ecommerce_serang.data.api.dto.CancelOrderReq import com.alya.ecommerce_serang.data.api.dto.CartItem @@ -17,7 +16,6 @@ import com.alya.ecommerce_serang.data.api.dto.LoginRequest import com.alya.ecommerce_serang.data.api.dto.OrderRequest import com.alya.ecommerce_serang.data.api.dto.OrderRequestBuy import com.alya.ecommerce_serang.data.api.dto.OtpRequest -import com.alya.ecommerce_serang.data.api.dto.PaymentConfirmRequest import com.alya.ecommerce_serang.data.api.dto.ProvinceResponse import com.alya.ecommerce_serang.data.api.dto.RegisterRequest import com.alya.ecommerce_serang.data.api.dto.ResetPassReq @@ -29,7 +27,6 @@ import com.alya.ecommerce_serang.data.api.dto.UpdateCart import com.alya.ecommerce_serang.data.api.dto.UpdateChatRequest import com.alya.ecommerce_serang.data.api.dto.VerifRegisReq import com.alya.ecommerce_serang.data.api.response.auth.ChangePassResponse -import com.alya.ecommerce_serang.data.api.response.auth.CheckStoreResponse import com.alya.ecommerce_serang.data.api.response.auth.FcmTokenResponse import com.alya.ecommerce_serang.data.api.response.auth.HasStoreResponse import com.alya.ecommerce_serang.data.api.response.auth.ListNotifResponse @@ -83,12 +80,10 @@ import com.alya.ecommerce_serang.data.api.response.store.product.UpdateProductRe import com.alya.ecommerce_serang.data.api.response.store.product.ViewStoreProductsResponse import com.alya.ecommerce_serang.data.api.response.store.profile.StoreDataResponse import com.alya.ecommerce_serang.data.api.response.store.review.ProductReviewResponse -import com.alya.ecommerce_serang.data.api.response.store.sells.PaymentConfirmationResponse import com.alya.ecommerce_serang.data.api.response.store.topup.BalanceTopUpResponse import com.alya.ecommerce_serang.data.api.response.store.topup.TopUpResponse import okhttp3.MultipartBody import okhttp3.RequestBody -import retrofit2.Call import retrofit2.Response import retrofit2.http.Body import retrofit2.http.DELETE @@ -99,7 +94,6 @@ import retrofit2.http.PUT import retrofit2.http.Part import retrofit2.http.PartMap import retrofit2.http.Path -import retrofit2.http.Query interface ApiService { @POST("registeruser") @@ -112,9 +106,6 @@ interface ApiService { @Body verifRegisReq: VerifRegisReq ):VerifRegisterResponse - @GET("checkstore") - suspend fun checkStore (): Response - @Multipart @POST("registerstore") suspend fun registerStore( @@ -204,11 +195,6 @@ interface ApiService { @Path("id") orderId: Int ): Response - @POST("order/addevidence") - suspend fun addEvidence( - @Body request : AddEvidenceRequest, - ): Response - @Multipart @POST("order/addevidence") suspend fun addEvidenceMultipart( @@ -256,15 +242,9 @@ interface ApiService { @GET("mystore") suspend fun getMyStoreData(): Response - @GET("mystore") - suspend fun getStoreAddress(): Response - @GET("mystore/product") // Replace with actual endpoint suspend fun getStoreProduct(): Response - @GET("category") - fun getCategories(): Call - @Multipart @POST("store/createproduct") suspend fun addProduct( @@ -374,9 +354,6 @@ interface ApiService { @GET("store/topup") suspend fun getTopUpHistory(): Response - @GET("store/topup") - suspend fun getFilteredTopUpHistory(@Query("date") date: String): Response - @Multipart @POST("store/createtopup") suspend fun addBalanceTopUp( @@ -388,11 +365,6 @@ interface ApiService { @Part("bank_num") bankNum: RequestBody ): Response - @PUT("store/payment/update") - suspend fun paymentConfirmation( - @Body confirmPaymentReq : PaymentConfirmRequest - ): Response - @Multipart @PUT("mystore/edit") suspend fun updateStoreProfileMultipart( @@ -404,13 +376,26 @@ interface ApiService { ): Response @Multipart - @POST("mystore/payment/add") - suspend fun addPaymentInfo( - @Part("bank_name") bankName: RequestBody, - @Part("bank_num") bankNum: RequestBody, - @Part("account_name") accountName: RequestBody, - @Part qris: MultipartBody.Part? - ): Response + @PUT("mystore/edit") + suspend fun updateStoreApprovalMultipart( + @Part("store_name") storeName: RequestBody, + @Part("store_description") storeDescription: RequestBody, + @Part("store_type_id") storeTypeId: RequestBody, + @Part("latitude") storeLatitude: RequestBody, + @Part("longitude") storeLongitude: RequestBody, + @Part("province_id") storeProvince: RequestBody, + @Part("city_id") storeCity: RequestBody, + @Part("subdistrict") storeSubdistrict: RequestBody, + @Part("village_id") storeVillage: RequestBody, + @Part("street") storeStreet: RequestBody, + @Part("postal_code") storePostalCode: RequestBody, + @Part("detail") storeAddressDetail: RequestBody, + @Part("user_phone") storeUserPhone: RequestBody, + @Part storeimg: MultipartBody.Part?, + @Part ktp: MultipartBody.Part?, + @Part npwp: MultipartBody.Part?, + @Part nib: MultipartBody.Part? + ): Response @Multipart @POST("mystore/payment/add") @@ -421,6 +406,16 @@ interface ApiService { @Part qris: MultipartBody.Part? ): Response + @Multipart + @PUT("mystore/payment/edit") + suspend fun updatePaymentInfo( + @Part("payment_info_id") paymentInfoId: RequestBody, + @Part("account_name") accountName: RequestBody, + @Part("bank_name") bankName: RequestBody, + @Part("bank_num") bankNum: RequestBody, + @Part qris: MultipartBody.Part? = null + ): Response + @DELETE("mystore/payment/delete/{id}") suspend fun deletePaymentInfo( @Path("id") paymentMethodId: Int @@ -464,15 +459,6 @@ interface ApiService { @GET("search") suspend fun getSearchHistory(): Response - @Multipart - @POST("sendchat") - suspend fun sendChatLine( - @Part("store_id") storeId: RequestBody, - @Part("message") message: RequestBody, - @Part("product_id") productId: RequestBody?, - @Part chatimg: MultipartBody.Part? - ): Response - @Multipart @POST("store/sendchat") suspend fun sendChatMessageStore( diff --git a/app/src/main/java/com/alya/ecommerce_serang/data/repository/MyStoreRepository.kt b/app/src/main/java/com/alya/ecommerce_serang/data/repository/MyStoreRepository.kt index 87c1bf9..a11c181 100644 --- a/app/src/main/java/com/alya/ecommerce_serang/data/repository/MyStoreRepository.kt +++ b/app/src/main/java/com/alya/ecommerce_serang/data/repository/MyStoreRepository.kt @@ -1,17 +1,23 @@ package com.alya.ecommerce_serang.data.repository import android.util.Log +import com.alya.ecommerce_serang.data.api.dto.PaymentUpdate import com.alya.ecommerce_serang.data.api.dto.ProductsItem +import com.alya.ecommerce_serang.data.api.dto.ShippingServiceRequest import com.alya.ecommerce_serang.data.api.dto.Store import com.alya.ecommerce_serang.data.api.response.auth.ListStoreTypeResponse import com.alya.ecommerce_serang.data.api.response.store.StoreResponse import com.alya.ecommerce_serang.data.api.response.store.profile.StoreDataResponse import com.alya.ecommerce_serang.data.api.response.store.sells.OrderListResponse import com.alya.ecommerce_serang.data.api.retrofit.ApiService +import okhttp3.MediaType.Companion.toMediaTypeOrNull import okhttp3.MultipartBody import okhttp3.RequestBody +import okhttp3.RequestBody.Companion.asRequestBody +import okhttp3.RequestBody.Companion.toRequestBody import retrofit2.HttpException import retrofit2.Response +import java.io.File import java.io.IOException class MyStoreRepository(private val apiService: ApiService) { @@ -139,6 +145,186 @@ class MyStoreRepository(private val apiService: ApiService) { } } + suspend fun updateStoreApproval( + storeName: RequestBody, + description: RequestBody, + storeType: RequestBody, + latitude: RequestBody, + longitude: RequestBody, + storeProvince: RequestBody, + storeCity: RequestBody, + storeSubdistrict: RequestBody, + storeVillage: RequestBody, + storeStreet: RequestBody, + storePostalCode: RequestBody, + storeAddressDetail: RequestBody, + userPhone: RequestBody, + paymentsToUpdate: List = emptyList(), + paymentIdToDelete: List = emptyList(), + storeCourier: List? = null, + storeImage: MultipartBody.Part?, + ktpImage: MultipartBody.Part?, + npwpDocument: MultipartBody.Part?, + nibDocument: MultipartBody.Part? + ): Response? { + return try { + Log.d(TAG, "Updating store profile & address for approval...") + + val profileResp = apiService.updateStoreApprovalMultipart( + storeName = storeName, + storeDescription = description, + storeTypeId = storeType, + storeLatitude = latitude, + storeLongitude = longitude, + storeProvince = storeProvince, + storeCity = storeCity, + storeSubdistrict = storeSubdistrict, + storeVillage = storeVillage, + storeStreet = storeStreet, + storePostalCode = storePostalCode, + storeAddressDetail = storeAddressDetail, + storeUserPhone = userPhone, + storeimg = storeImage, + ktp = ktpImage, + npwp = npwpDocument, + nib = nibDocument + ) + + if (!profileResp.isSuccessful) { + Log.e(TAG, "Profile update failed: ${profileResp.code()} ${profileResp.errorBody()?.string()}") + return profileResp // short-circuit; let caller inspect the failure + } + + // 2) Payments: delete, then upsert (safer if you’re changing accounts) + if (paymentIdToDelete.isNotEmpty() || paymentsToUpdate.isNotEmpty()) { + Log.d(TAG, "Synchronizing payments: delete=${paymentIdToDelete.size}, upsert=${paymentsToUpdate.size}") + } + + // 2a) Delete payments + paymentIdToDelete.forEach { id -> + runCatching { + apiService.deletePaymentInfo(id) + }.onSuccess { + if (!it.isSuccessful) { + Log.e(TAG, "Delete payment $id failed: ${it.code()} ${it.errorBody()?.string()}") + } else { + Log.d(TAG, "Deleted payment $id") + } + }.onFailure { e -> + Log.e(TAG, "Delete payment $id exception", e) + } + } + + // 2b) Upsert payments (add if id==null, else update) + paymentsToUpdate.forEach { item -> + runCatching { + // --- CHANGE HERE if your PaymentUpdate field names differ --- + val id = item.id // Int? (null => add) + val bankName = item.bankName // String + val bankNum = item.bankNum // String + val accountName = item.accountName // String + val qrisImage = item.qrisImage // File? (Optional) + // ----------------------------------------------------------- + + if (id == null) { + // ADD + val resp = apiService.addPaymentInfoDirect( + bankName = bankName.toPlain(), + bankNum = bankNum.toPlain(), + accountName = accountName.toPlain(), + qris = createQrisPartOrNull(qrisImage) + ) + if (!resp.isSuccessful) { + Log.e(TAG, "Add payment failed: ${resp.code()} ${resp.errorBody()?.string()}") + } else { + Log.d(TAG, "Added payment: $bankName/$bankNum") + } + } else { + // UPDATE + val resp = apiService.updatePaymentInfo( + paymentInfoId = id.toString().toPlain(), + accountName = accountName.toPlain(), + bankName = bankName.toPlain(), + bankNum = bankNum.toPlain(), + qris = createQrisPartOrNull(qrisImage) + ) + if (!resp.isSuccessful) { + Log.e(TAG, "Update payment $id failed: ${resp.code()} ${resp.errorBody()?.string()}") + } else { + Log.d(TAG, "Updated payment $id: $bankName/$bankNum") + } + } + }.onFailure { e -> + Log.e(TAG, "Upsert payment exception", e) + } + } + + // 3) Shipping: sync to desiredCouriers (if provided) + storeCourier?.let { desired -> + try { + val current = apiService.getStoreData().let { resp -> + if (resp.isSuccessful) { + resp.body()?.shipping?.mapNotNull { it.courier } ?: emptyList() + } else { + Log.e(TAG, "Failed to read current shipping: ${resp.code()} ${resp.errorBody()?.string()}") + emptyList() + } + } + + val desiredSet = desired.toSet() + val currentSet = current.toSet() + + val toAdd = (desiredSet - currentSet).toList() + val toDel = (currentSet - desiredSet).toList() + + if (toAdd.isNotEmpty()) { + val addResp = apiService.addShippingService(ShippingServiceRequest(couriers = toAdd)) + if (!addResp.isSuccessful) { + Log.e(TAG, "Add couriers failed: ${addResp.code()} ${addResp.errorBody()?.string()}") + } else { + Log.d(TAG, "Added couriers: $toAdd") + } + } + + if (toDel.isNotEmpty()) { + val delResp = apiService.deleteShippingService(ShippingServiceRequest(couriers = toDel)) + if (!delResp.isSuccessful) { + Log.e(TAG, "Delete couriers failed: ${delResp.code()} ${delResp.errorBody()?.string()}") + } else { + Log.d(TAG, "Deleted couriers: $toDel") + } + } + } catch (e: Exception) { + Log.e(TAG, "Sync shipping exception", e) + } + } + + // Return the profile response (already successful here) + profileResp + } catch (e: Exception) { + Log.e(TAG, "Error updating store approval flow", e) + null + } + } + + private fun String.toPlain(): RequestBody = + this.toRequestBody("text/plain".toMediaTypeOrNull()) + + private fun createQrisPartOrNull(file: File?): MultipartBody.Part? = + file?.let { + val mime = when (it.extension.lowercase()) { + "jpg", "jpeg" -> "image/jpeg" + "png" -> "image/png" + else -> "application/octet-stream" + }.toMediaTypeOrNull() + + MultipartBody.Part.createFormData( + "qris", + it.name, + it.asRequestBody(mime) + ) + } + companion object { private var TAG = "MyStoreRepository" } diff --git a/app/src/main/java/com/alya/ecommerce_serang/ui/profile/mystore/RegisterStoreActivity.kt b/app/src/main/java/com/alya/ecommerce_serang/ui/profile/mystore/RegisterStoreActivity.kt index 5524c82..2a3368c 100644 --- a/app/src/main/java/com/alya/ecommerce_serang/ui/profile/mystore/RegisterStoreActivity.kt +++ b/app/src/main/java/com/alya/ecommerce_serang/ui/profile/mystore/RegisterStoreActivity.kt @@ -47,6 +47,7 @@ import okhttp3.RequestBody import okhttp3.RequestBody.Companion.toRequestBody import java.io.File import androidx.core.net.toUri +import com.alya.ecommerce_serang.data.api.dto.PaymentUpdate class RegisterStoreActivity : AppCompatActivity() { @@ -58,6 +59,21 @@ class RegisterStoreActivity : AppCompatActivity() { private lateinit var subdistrictAdapter: SubdsitrictAdapter private lateinit var bankAdapter: BankAdapter + // pending values (filled from myStoreProfile once) + private var wantedProvinceId: Int? = null + private var wantedCityId: String? = null + private var wantedSubdistrictId: String? = null + private var wantedBankName: String? = null + + // one-shot guards so we don't re-apply repeatedly + private var provinceApplied = false + private var cityApplied = false + private var subdistrictApplied = false + private var bankApplied = false + + // avoid clearing/overriding while restoring + private var isRestoringSelections = false + // Request codes for file picking private val PICK_STORE_IMAGE_REQUEST = 1001 private val PICK_KTP_REQUEST = 1002 @@ -123,6 +139,10 @@ class RegisterStoreActivity : AppCompatActivity() { setupSpinners() // Location spinners Log.d(TAG, "onCreate: Spinners setup completed") + binding.checkboxApprove.setOnCheckedChangeListener { _, _ -> + validateRequiredFields() + } + // Setup observers setupStoreTypesObserver() // Store type observer setupObservers() @@ -209,12 +229,27 @@ class RegisterStoreActivity : AppCompatActivity() { // Prefill spinner for store types preselectStoreType(store.storeTypeId) - // Prefill province, city, and subdistrict - preselectProvinceCitySubdistrict( - provinceId = store.provinceId, - cityId = store.cityId, - subdistrictId = store.subdistrict - ) + // Cache what we want to select later (after data arrives) + wantedProvinceId = store.provinceId + wantedCityId = store.cityId + wantedSubdistrictId = store.subdistrict + wantedBankName = storeResponse.payment.firstOrNull()?.bankName + + // Cache what we want to select later (after data arrives) + wantedProvinceId = store.provinceId + wantedCityId = store.cityId + wantedSubdistrictId = store.subdistrict + wantedBankName = storeResponse.payment.firstOrNull()?.bankName + + // Mark restoring flow on + isRestoringSelections = true + + // Try to apply immediately (if adapters already have data), otherwise + // observers below will apply when data is ready. + tryApplyProvince() + tryApplyCity() + tryApplySubdistrict() + tryApplyBank() validateRequiredFields() } @@ -229,64 +264,78 @@ class RegisterStoreActivity : AppCompatActivity() { else Toast.makeText(this, "Harap lengkapi semua field yang wajib diisi", Toast.LENGTH_SHORT).show() } } + validateRequiredFields() } private fun preselectStoreType(storeTypeId: Int) { // The adapter is created in setupStoreTypeSpinner(...) - val adapter = binding.spinnerStoreType.adapter - if (adapter != null) { - val count = adapter.count - for (i in 0 until count) { - val item = adapter.getItem(i) as? StoreTypesItem - if (item?.id == storeTypeId) { - binding.spinnerStoreType.setSelection(i, false) - break - } + val adapter = binding.spinnerStoreType.adapter ?: return + for (i in 0 until adapter.count) { + val item = adapter.getItem(i) as? StoreTypesItem + if (item?.id == storeTypeId) { + binding.spinnerStoreType.setSelection(i, false) + viewModel.storeTypeId.value = storeTypeId + validateRequiredFields() + break } } } - private fun preselectProvinceCitySubdistrict( - provinceId: Int, - cityId: String, - subdistrictId: String - ) { - // Province first (this will trigger cities fetch) - val provCount = provinceAdapter.count - for (i in 0 until provCount) { - if (provinceAdapter.getProvinceId(i) == provinceId) { + private fun tryApplyProvince() { + if (provinceApplied) return + val target = wantedProvinceId ?: return + val count = provinceAdapter.count + for (i in 0 until count) { + if (provinceAdapter.getProvinceId(i) == target) { binding.spinnerProvince.setSelection(i, false) - break + provinceApplied = true + maybeFinishRestoring() + return } } + } - // When cities arrive, select the city, then load subdistricts - viewModel.citiesState.observe(this) { state -> - if (state is Result.Success) { - val cityCount = cityAdapter.count - for (i in 0 until cityCount) { - if (cityAdapter.getCityId(i) == cityId) { - binding.spinnerCity.setSelection(i, false) - break - } - } + private fun tryApplyCity() { + if (cityApplied) return + val target = wantedCityId ?: return + val count = cityAdapter.count + for (i in 0 until count) { + if (cityAdapter.getCityId(i) == target) { + binding.spinnerCity.setSelection(i, false) + cityApplied = true + maybeFinishRestoring() + return } } + } - // When subdistricts arrive, select the subdistrict - viewModel.subdistrictState.observe(this) { state -> - if (state is Result.Success) { - val subCount = subdistrictAdapter.count - for (i in 0 until subCount) { - if (subdistrictAdapter.getSubdistrictId(i) == subdistrictId) { - binding.spinnerSubdistrict.setSelection(i, false) - break - } - } + private fun tryApplySubdistrict() { + if (subdistrictApplied) return + val target = wantedSubdistrictId ?: return + val count = subdistrictAdapter.count + for (i in 0 until count) { + if (subdistrictAdapter.getSubdistrictId(i) == target) { + binding.spinnerSubdistrict.setSelection(i, false) + subdistrictApplied = true + maybeFinishRestoring() + return } } } + private fun tryApplyBank() { + if (bankApplied) return + val targetName = wantedBankName ?: return + val pos = bankAdapter.findPositionByName(targetName) + if (pos >= 0) { + binding.spinnerBankName.setSelection(pos, false) + viewModel.bankName.value = targetName + viewModel.selectedBankName = targetName + validateRequiredFields() + bankApplied = true + } + } + private fun setupHeader() { binding.header.main.background = ContextCompat.getColor(this, R.color.blue_500).toDrawable() binding.header.headerTitle.visibility = View.GONE @@ -301,31 +350,42 @@ class RegisterStoreActivity : AppCompatActivity() { } private fun validateRequiredFields() { - val isFormValid = !viewModel.storeName.value.isNullOrBlank() && - !viewModel.street.value.isNullOrBlank() && - (viewModel.postalCode.value ?: 0) > 0 && - !viewModel.subdistrict.value.isNullOrBlank() && - !viewModel.bankName.value.isNullOrBlank() && - (viewModel.bankNumber.value ?: 0) > 0 && - (viewModel.provinceId.value ?: 0) > 0 && - !viewModel.cityId.value.isNullOrBlank() && - (viewModel.storeTypeId.value ?: 0) > 0 && - viewModel.ktpUri != null && - viewModel.nibUri != null && - viewModel.npwpUri != null && - viewModel.selectedCouriers.isNotEmpty() && - !viewModel.accountName.value.isNullOrBlank() + val bankName = viewModel.bankName.value?.trim().orEmpty() + val bankSelected = bankName.isNotEmpty() && !bankName.equals("Pilih Bank", ignoreCase = true) - binding.btnRegister.isEnabled = true - if (isFormValid) { - binding.btnRegister.setBackgroundResource(R.drawable.bg_button_active) - binding.btnRegister.setTextColor(ContextCompat.getColor(this, R.color.white)) - binding.btnRegister.isEnabled = true - } else { - binding.btnRegister.setBackgroundResource(R.drawable.bg_button_disabled) - binding.btnRegister.setTextColor(ContextCompat.getColor(this, R.color.black_300)) - binding.btnRegister.isEnabled = false + val provinceSelected = viewModel.provinceId.value != null + val citySelected = !viewModel.cityId.value.isNullOrBlank() + val subdistrictSelected = !viewModel.subdistrict.value.isNullOrBlank() + + val currentStoreType = binding.spinnerStoreType.selectedItem as? StoreTypesItem + val storeTypeSelected = when { + currentStoreType != null -> currentStoreType.id != 0 && + !currentStoreType.name.equals("Pilih Jenis UMKM", true) + else -> (viewModel.storeTypeId.value ?: -1) > 0 } + + val isFormValid = + !viewModel.storeName.value.isNullOrBlank() && + !viewModel.street.value.isNullOrBlank() && + (viewModel.postalCode.value ?: 0) > 0 && + provinceSelected && citySelected && subdistrictSelected && + storeTypeSelected && + bankSelected && + (viewModel.bankNumber.value ?: 0) > 0 && + viewModel.ktpUri != null && + viewModel.nibUri != null && + viewModel.npwpUri != null && + viewModel.selectedCouriers.isNotEmpty() && + !viewModel.accountName.value.isNullOrBlank() && + binding.checkboxApprove.isChecked + + binding.btnRegister.isEnabled = isFormValid + binding.btnRegister.setBackgroundResource( + if (isFormValid) R.drawable.bg_button_active else R.drawable.bg_button_disabled + ) + binding.btnRegister.setTextColor( + ContextCompat.getColor(this, if (isFormValid) R.color.white else R.color.black_300) + ) } private fun setupObservers() { @@ -340,12 +400,10 @@ class RegisterStoreActivity : AppCompatActivity() { binding.spinnerProvince.isEnabled = false } is Result.Success -> { - Log.d(TAG, "setupObservers: Provinces loaded successfully: ${state.data.size} provinces") binding.provinceProgressBar.visibility = View.GONE binding.spinnerProvince.isEnabled = true - - // Update adapter with data provinceAdapter.updateData(state.data) + tryApplyProvince() } is Result.Error -> { Log.e(TAG, "setupObservers: Error loading provinces: ${state.exception.message}") @@ -364,12 +422,10 @@ class RegisterStoreActivity : AppCompatActivity() { binding.spinnerCity.isEnabled = false } is Result.Success -> { - Log.d(TAG, "setupObservers: Cities loaded successfully: ${state.data.size} cities") binding.cityProgressBar.visibility = View.GONE binding.spinnerCity.isEnabled = true - - // Update adapter with data cityAdapter.updateData(state.data) + tryApplyCity() } is Result.Error -> { Log.e(TAG, "setupObservers: Error loading cities: ${state.exception.message}") @@ -387,11 +443,20 @@ class RegisterStoreActivity : AppCompatActivity() { binding.spinnerSubdistrict.isEnabled = false } is Result.Success -> { - Log.d(TAG, "setupobservers: Subdistrict loaded successfullti: ${state.data.size} subdistrict") binding.subdistrictProgressBar.visibility = View.GONE binding.spinnerSubdistrict.isEnabled = true - subdistrictAdapter.updateData(state.data) + + // If you’re not restoring a specific subdistrict, select the first real item + if (!isRestoringSelections && state.data.isNotEmpty()) { + binding.spinnerSubdistrict.setSelection(0, false) + val id0 = subdistrictAdapter.getSubdistrictId(0) + viewModel.subdistrict.value = id0 ?: "" + viewModel.selectedSubdistrict = id0 + validateRequiredFields() + } + + tryApplySubdistrict() } is Result.Error -> { Log.e(TAG, "setupObservers: Error loading subdistrict: ${state.exception.message}") @@ -466,6 +531,7 @@ class RegisterStoreActivity : AppCompatActivity() { // Setup spinner with API data setupStoreTypeSpinner(displayList) + tryApplyBank() } else { Log.w(TAG, "setupStoreTypesObserver: Received empty store types list") } @@ -510,17 +576,10 @@ class RegisterStoreActivity : AppCompatActivity() { // Set item selection listener binding.spinnerStoreType.onItemSelectedListener = object : AdapterView.OnItemSelectedListener { - override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) { - val selectedItem = adapter.getItem(position) - Log.d(TAG, "Store type selected: position=$position, item=${selectedItem?.name}, id=${selectedItem?.id}") - - if (selectedItem != null && selectedItem.id > 0) { - // Store the actual ID from the API, not just position - viewModel.storeTypeId.value = selectedItem.id - Log.d(TAG, "Set storeTypeId to ${selectedItem.id}") - } else { - Log.d(TAG, "Default or null store type selected, not setting storeTypeId") - } + override fun onItemSelected(parent: AdapterView<*>?, view: View?, pos: Int, id: Long) { + val item = (binding.spinnerStoreType.adapter.getItem(pos) as? StoreTypesItem) + if (item != null && item.id > 0) viewModel.storeTypeId.value = item.id + validateRequiredFields() } override fun onNothingSelected(parent: AdapterView<*>?) { @@ -539,22 +598,12 @@ class RegisterStoreActivity : AppCompatActivity() { // Setup province spinner binding.spinnerProvince.adapter = provinceAdapter binding.spinnerProvince.onItemSelectedListener = object : AdapterView.OnItemSelectedListener { - override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) { - Log.d(TAG, "Province selected at position: $position") - val provinceId = provinceAdapter.getProvinceId(position) - if (provinceId != null) { - Log.d(TAG, "Setting province ID: $provinceId") - viewModel.provinceId.value = provinceId - Log.d(TAG, "Fetching cities for province ID: $provinceId") - viewModel.getCities(provinceId) - - // Reset city selection when province changes - Log.d(TAG, "Clearing city adapter for new province selection") - cityAdapter.clear() - binding.spinnerCity.setSelection(0) - } else { - Log.e(TAG, "Invalid province ID for position: $position") + override fun onItemSelected(parent: AdapterView<*>?, view: View?, pos: Int, id: Long) { + provinceAdapter.getProvinceId(pos)?.let { + viewModel.provinceId.value = it + viewModel.getCities(it) } + validateRequiredFields() } override fun onNothingSelected(parent: AdapterView<*>?) { @@ -565,21 +614,13 @@ class RegisterStoreActivity : AppCompatActivity() { // Setup city spinner binding.spinnerCity.adapter = cityAdapter binding.spinnerCity.onItemSelectedListener = object : AdapterView.OnItemSelectedListener { - override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) { - Log.d(TAG, "City selected at position: $position") - val cityId = cityAdapter.getCityId(position) - if (cityId != null) { - Log.d(TAG, "Setting city ID: $cityId") - viewModel.cityId.value = cityId - Log.d(TAG, "Fetching subdistrict for city ID: $cityId") - viewModel.getSubdistrict(cityId) - - subdistrictAdapter.clear() - binding.spinnerSubdistrict.setSelection(0) - viewModel.selectedCityId = cityId - } else { - Log.e(TAG, "Invalid city ID for position: $position") + override fun onItemSelected(p: AdapterView<*>?, v: View?, pos: Int, id: Long) { + cityAdapter.getCityId(pos)?.let { + viewModel.cityId.value = it + viewModel.getSubdistrict(it) + viewModel.selectedCityId = it } + validateRequiredFields() } override fun onNothingSelected(parent: AdapterView<*>?) { @@ -590,16 +631,11 @@ class RegisterStoreActivity : AppCompatActivity() { //Setup Subdistrict spinner binding.spinnerSubdistrict.adapter = subdistrictAdapter binding.spinnerSubdistrict.onItemSelectedListener = object : AdapterView.OnItemSelectedListener { - override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) { - Log.d(TAG, "Subdistrict selected at position: $position") - val subdistrictId = subdistrictAdapter.getSubdistrictId(position) - if (subdistrictId != null) { - Log.d(TAG, "Setting subdistrict ID: $subdistrictId") - viewModel.subdistrict.value = subdistrictId - viewModel.selectedSubdistrict = subdistrictId - } else { - Log.e(TAG, "Invalid subdistrict ID for position: $position") - } + override fun onItemSelected(p: AdapterView<*>?, v: View?, pos: Int, id: Long) { + val selectedId = subdistrictAdapter.getSubdistrictId(pos) + viewModel.subdistrict.value = selectedId ?: "" // empty => not selected + viewModel.selectedSubdistrict = selectedId + validateRequiredFields() } override fun onNothingSelected(parent: AdapterView<*>?) { @@ -609,63 +645,31 @@ class RegisterStoreActivity : AppCompatActivity() { binding.spinnerBankName.adapter = bankAdapter binding.spinnerBankName.onItemSelectedListener = object : AdapterView.OnItemSelectedListener { - override fun onItemSelected( - parent: AdapterView<*>?, - view: View?, - position: Int, - id: Long - ) { - Log.d(TAG, "Bank selected at position: $position") - val bankName = bankAdapter.getBankName(position) - if (bankName != null) { - Log.d(TAG, "Setting bank name: $bankName") - viewModel.bankName.value = bankName - viewModel.selectedBankName = bankName - - // Optional: Log the selected bank details - val selectedBank = bankAdapter.getBankItem(position) - selectedBank?.let { - Log.d(TAG, "Selected bank: ${it.bankName} (Code: ${it.bankCode})") - } - - // Hide progress bar if it was showing - binding.bankNameProgressBar.visibility = View.GONE - - } else { - Log.e(TAG, "Invalid bank name for position: $position") - } - } - - override fun onNothingSelected(parent: AdapterView<*>?) { - Log.d(TAG, "No bank selected") - viewModel.selectedBankName = null + override fun onItemSelected(p: AdapterView<*>?, v: View?, pos: Int, id: Long) { + val bankName = bankAdapter.getBankName(pos) + viewModel.bankName.value = bankName + viewModel.selectedBankName = bankName + validateRequiredFields() } + override fun onNothingSelected(parent: AdapterView<*>?) { /* no-op */ } } + tryApplyBank() - // Add initial hints to the spinners - if (provinceAdapter.isEmpty) { - Log.d(TAG, "Adding default province hint") - provinceAdapter.add("Pilih Provinsi") - } - - if (cityAdapter.isEmpty) { - Log.d(TAG, "Adding default city hint") - cityAdapter.add("Pilih Kabupaten/Kota") - } - - if (subdistrictAdapter.isEmpty) { - Log.d(TAG, "Adding default kecamatan hint") - subdistrictAdapter.add("Pilih Kecamatan") - } - - if (bankAdapter.isEmpty) { - Log.d(TAG, "Adding default bank hint") - bankAdapter.add("Pilih Bank") - } +// // Add initial hints to the spinners +// if (provinceAdapter.isEmpty) provinceAdapter.add("Pilih Provinsi") +// if (cityAdapter.isEmpty) cityAdapter.add("Pilih Kabupaten/Kota") +// if (subdistrictAdapter.isEmpty) subdistrictAdapter.add("Pilih Kecamatan") +// if (bankAdapter.isEmpty) bankAdapter.add("Pilih Bank") Log.d(TAG, "setupSpinners: Province and city spinners setup completed") } + private fun maybeFinishRestoring() { + if (provinceApplied && cityApplied && subdistrictApplied) { + isRestoringSelections = false + } + } + private fun setupDocumentUploads() { Log.d(TAG, "setupDocumentUploads: Setting up document upload buttons") @@ -805,6 +809,7 @@ class RegisterStoreActivity : AppCompatActivity() { override fun afterTextChanged(s: Editable?) { viewModel.storeDescription.value = s.toString() Log.d(TAG, "Store description updated: ${s.toString().take(20)}${if ((s?.length ?: 0) > 20) "..." else ""}") + validateRequiredFields() } }) @@ -822,14 +827,8 @@ class RegisterStoreActivity : AppCompatActivity() { override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {} override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {} override fun afterTextChanged(s: Editable?) { - try { - viewModel.postalCode.value = s.toString().toInt() - Log.d(TAG, "Postal code updated: ${s.toString()}") - } catch (e: NumberFormatException) { - // Handle invalid input - Log.e(TAG, "Invalid postal code input: ${s.toString()}, error: $e") - validateRequiredFields() - } + viewModel.postalCode.value = s.toString().toIntOrNull() ?: 0 + validateRequiredFields() } }) @@ -839,6 +838,7 @@ class RegisterStoreActivity : AppCompatActivity() { override fun afterTextChanged(s: Editable?) { viewModel.addressDetail.value = s.toString() Log.d(TAG, "Address detail updated: ${s.toString()}") + validateRequiredFields() } }) @@ -993,30 +993,41 @@ class RegisterStoreActivity : AppCompatActivity() { } private fun doUpdateStoreProfile() { - val nameBody: RequestBody = (viewModel.storeName.value ?: "") - .toRequestBody("text/plain".toMediaTypeOrNull()) - val typeBody: RequestBody = ((viewModel.storeTypeId.value ?: 0).toString()) - .toRequestBody("text/plain".toMediaTypeOrNull()) - val descBody: RequestBody = (viewModel.storeDescription.value ?: "") - .toRequestBody("text/plain".toMediaTypeOrNull()) - val onLeaveBody: RequestBody = "false" - .toRequestBody("text/plain".toMediaTypeOrNull()) + // --- Text parts --- + val nameBody: RequestBody = (viewModel.storeName.value ?: "").toRequestBody("text/plain".toMediaTypeOrNull()) + val typeBody: RequestBody = ((viewModel.storeTypeId.value ?: 0).toString()).toRequestBody("text/plain".toMediaTypeOrNull()) + val descBody: RequestBody = (viewModel.storeDescription.value ?: "").toRequestBody("text/plain".toMediaTypeOrNull()) + // NOTE: is_on_leave is NOT part of approval multipart; keep separate if needed - // --- Build Multipart for store image (optional) --- - // Prefer compressing images to keep payload small; fall back to raw copy if needed. + val latBody: RequestBody = (viewModel.latitude.value ?: "").toRequestBody("text/plain".toMediaTypeOrNull()) + val longBody: RequestBody = (viewModel.longitude.value ?: "").toRequestBody("text/plain".toMediaTypeOrNull()) + val provBody: RequestBody = ((viewModel.provinceId.value ?: 0).toString()).toRequestBody("text/plain".toMediaTypeOrNull()) + val cityBody: RequestBody = (viewModel.cityId.value ?: "").toRequestBody("text/plain".toMediaTypeOrNull()) + val subdistrictBody: RequestBody = (viewModel.selectedSubdistrict ?: viewModel.subdistrict.value ?: "").toRequestBody("text/plain".toMediaTypeOrNull()) + + // If you don't have village picker yet, send empty string or reuse subdistrict + val villageBody: RequestBody = "".toRequestBody("text/plain".toMediaTypeOrNull()) + + val streetBody: RequestBody = (viewModel.street.value ?: "").toRequestBody("text/plain".toMediaTypeOrNull()) + val postalBody: RequestBody = ((viewModel.postalCode.value ?: 0).toString()).toRequestBody("text/plain".toMediaTypeOrNull()) + val detailBody: RequestBody = (viewModel.addressDetail.value ?: "").toRequestBody("text/plain".toMediaTypeOrNull()) + + // You can read user phone from current store profile when reapply + val currentPhone = myStoreViewModel.myStoreProfile.value?.store?.userPhone ?: "" + val userPhoneBody: RequestBody = currentPhone.toRequestBody("text/plain".toMediaTypeOrNull()) + + // --- Multipart images/docs (safe compress/copy) --- val storeImgPart: MultipartBody.Part? = viewModel.storeImageUri?.let { uri -> try { - // (A) Optional safety check: only allow jpg/png/webp val allowed = Regex("^(jpg|jpeg|png|webp)$", RegexOption.IGNORE_CASE) if (!ImageUtils.isAllowedFileType(this, uri, allowed)) { Toast.makeText(this, "Format gambar tidak didukung", Toast.LENGTH_SHORT).show() null } else { - // (B) Compress for upload (ke cacheDir), then build multipart val compressed: File = ImageUtils.compressImage( context = this, uri = uri, - filename = "storeimg", // prefix + filename = "storeimg", maxWidth = 1024, maxHeight = 1024, quality = 80 @@ -1024,18 +1035,76 @@ class RegisterStoreActivity : AppCompatActivity() { FileUtils.createMultipartFromFile("storeimg", compressed) } } catch (e: Exception) { - // If compression fails, try raw copy as fallback val rawFile = FileUtils.createTempFileFromUri(this, uri) rawFile?.let { FileUtils.createMultipartFromFile("storeimg", it) } } } - myStoreViewModel.updateStoreProfile( + val ktpPart: MultipartBody.Part? = viewModel.ktpUri?.let { uri -> + val file = FileUtils.createTempFileFromUri(this, uri) + file?.let { FileUtils.createMultipartFromFile("ktp", it) } + } + + val npwpPart: MultipartBody.Part? = viewModel.npwpUri?.let { uri -> + val file = FileUtils.createTempFileFromUri(this, uri) + file?.let { FileUtils.createMultipartFromFile("npwp", it) } + } + + val nibPart: MultipartBody.Part? = viewModel.nibUri?.let { uri -> + val file = FileUtils.createTempFileFromUri(this, uri) + file?.let { FileUtils.createMultipartFromFile("nib", it) } + } + + // --- Couriers desired (sync to exactly this set) --- + val desiredCouriers = viewModel.selectedCouriers.toList() + + // --- (Optional) Payment upsert from UI fields --- + // If you want to send the bank from the form during re-apply: + val paymentsToUpsert = buildList { + val bankName = viewModel.bankName.value + val bankNum = viewModel.bankNumber.value?.toString() + val accName = viewModel.accountName.value + + if (!bankName.isNullOrBlank() && !bankNum.isNullOrBlank() && !accName.isNullOrBlank()) { + // If you want to update the first existing payment instead of adding new: + val existingId = myStoreViewModel.payment.value?.firstOrNull()?.id + add( + PaymentUpdate( + id = existingId, // null => add; id!=null => update + bankName = bankName, + bankNum = bankNum, + accountName = accName, + qrisImage = null // attach File if you have new QRIS to upload + ) + ) + } + } + + // --- Delete list (empty if none) --- + val paymentIdToDelete = emptyList() + + // --- Fire the update --- + myStoreViewModel.updateStoreApproval( storeName = nameBody, - storeType = typeBody, description = descBody, - isOnLeave = onLeaveBody, - storeImage = storeImgPart + storeType = typeBody, + latitude = latBody, + longitude = longBody, + storeProvince = provBody, + storeCity = cityBody, + storeSubdistrict = subdistrictBody, + storeVillage = villageBody, + storeStreet = streetBody, + storePostalCode = postalBody, + storeAddressDetail = detailBody, + userPhone = userPhoneBody, + paymentsToUpdate = paymentsToUpsert, + paymentIdToDelete = paymentIdToDelete, + storeCourier = desiredCouriers, + storeImage = storeImgPart, + ktpImage = ktpPart, + npwpDocument = npwpPart, + nibDocument = nibPart ) myStoreViewModel.updateStoreProfileResult.observe(this) { @@ -1049,6 +1118,7 @@ class RegisterStoreActivity : AppCompatActivity() { } } + companion object { private const val TAG = "RegisterStoreActivity" } 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 1ced252..5ab478d 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 @@ -49,6 +49,9 @@ class DetailStoreProductActivity : AppCompatActivity() { private var productId: Int? = null private var hasImage: Boolean = false + private var isEditing = false + private var hasExistingImage = false + private val viewModel: ProductViewModel by viewModels { BaseViewModelFactory { sessionManager = SessionManager(this) @@ -98,7 +101,7 @@ class DetailStoreProductActivity : AppCompatActivity() { binding = ActivityDetailStoreProductBinding.inflate(layoutInflater) setContentView(binding.root) - val isEditing = intent.getBooleanExtra("is_editing", false) + isEditing = intent.getBooleanExtra("is_editing", false) productId = intent.getIntExtra("product_id", -1) binding.headerStoreProduct.headerTitle.text = if (isEditing) "Ubah Produk" else "Tambah Produk" @@ -179,6 +182,7 @@ class DetailStoreProductActivity : AppCompatActivity() { binding.btnRemoveFoto.setOnClickListener { imageUri = null hasImage = false + hasExistingImage = false binding.switcherFotoProduk.showPrevious() validateForm() } @@ -236,7 +240,9 @@ class DetailStoreProductActivity : AppCompatActivity() { } else product.image Glide.with(this).load(imageUrl).into(binding.ivPreviewFoto) binding.switcherFotoProduk.showNext() + hasImage = true + hasExistingImage = true // SPPIRT product.sppirt?.let { @@ -331,10 +337,18 @@ class DetailStoreProductActivity : AppCompatActivity() { 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 categorySelected = binding.spinnerKategoriProduk.selectedItemPosition != -1 val isPreOrderChecked = binding.switchIsPreOrder.isChecked val isWholesaleChecked = binding.switchIsWholesale.isChecked + val hasRequiredImage = if (isEditing) { + // In edit mode: allow existing server image OR newly picked image + hasImage || hasExistingImage + } else { + // In create mode: must have a picked image + hasImage + } + val valid = name.isNotEmpty() && description.isNotEmpty() && price.isNotEmpty() && @@ -343,8 +357,8 @@ class DetailStoreProductActivity : AppCompatActivity() { weight.isNotEmpty() && (!isPreOrderChecked || duration.isNotEmpty()) && (!isWholesaleChecked || (wholesaleMinItem.isNotEmpty() && wholesalePrice.isNotEmpty())) && - category && - hasImage + categorySelected && + hasRequiredImage binding.btnSaveProduct.isEnabled = valid binding.btnSaveProduct.setTextColor( diff --git a/app/src/main/java/com/alya/ecommerce_serang/utils/viewmodel/MyStoreViewModel.kt b/app/src/main/java/com/alya/ecommerce_serang/utils/viewmodel/MyStoreViewModel.kt index 6355c74..5d0663f 100644 --- a/app/src/main/java/com/alya/ecommerce_serang/utils/viewmodel/MyStoreViewModel.kt +++ b/app/src/main/java/com/alya/ecommerce_serang/utils/viewmodel/MyStoreViewModel.kt @@ -6,6 +6,7 @@ import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel import androidx.lifecycle.map import androidx.lifecycle.viewModelScope +import com.alya.ecommerce_serang.data.api.dto.PaymentUpdate import com.alya.ecommerce_serang.data.api.dto.ProductsItem import com.alya.ecommerce_serang.data.api.dto.Store import com.alya.ecommerce_serang.data.api.response.auth.StoreTypesItem @@ -19,6 +20,7 @@ import kotlinx.coroutines.launch import okhttp3.MediaType.Companion.toMediaTypeOrNull import okhttp3.MultipartBody import okhttp3.RequestBody +import okhttp3.RequestBody.Companion.toRequestBody import java.text.NumberFormat import java.util.Locale @@ -199,6 +201,80 @@ class MyStoreViewModel(private val repository: MyStoreRepository): ViewModel() { } } - private fun String.toRequestBody(): RequestBody = - RequestBody.create("text/plain".toMediaTypeOrNull(), this) + private fun String.toPlain(): RequestBody = + this.toRequestBody("text/plain".toMediaTypeOrNull()) + + fun updateStoreApproval( + storeName: RequestBody, + description: RequestBody, + storeType: RequestBody, + latitude: RequestBody, + longitude: RequestBody, + storeProvince: RequestBody, + storeCity: RequestBody, + storeSubdistrict: RequestBody, + storeVillage: RequestBody, + storeStreet: RequestBody, + storePostalCode: RequestBody, + storeAddressDetail: RequestBody, + userPhone: RequestBody, + paymentsToUpdate: List = emptyList(), + paymentIdToDelete: List = emptyList(), + storeCourier: List? = null, + storeImage: MultipartBody.Part?, + ktpImage: MultipartBody.Part?, + npwpDocument: MultipartBody.Part?, + nibDocument: MultipartBody.Part? + ) { + viewModelScope.launch { + try { + val store = myStoreProfile.value + + if (store == null) { + _errorMessage.postValue("Data toko tidak tersedia") + Log.e(TAG, "Store data is null") + return@launch + } + + val response = repository.updateStoreApproval( + storeName = storeName, + description = description, + storeType = storeType, + latitude = latitude, + longitude = longitude, + storeProvince = storeProvince, + storeCity = storeCity, + storeSubdistrict = storeSubdistrict, + storeVillage = storeVillage, + storeStreet = storeStreet, + storePostalCode = storePostalCode, + storeAddressDetail = storeAddressDetail, + userPhone = userPhone, + paymentsToUpdate = paymentsToUpdate, + paymentIdToDelete = paymentIdToDelete, + storeCourier = storeCourier, + storeImage = storeImage, + ktpImage = ktpImage, + npwpDocument = npwpDocument, + nibDocument = nibDocument + ) + + if (response != null) { + if (response.isSuccessful) { + _updateStoreProfileResult.postValue(response.body()) + Log.d(TAG, "Update successful: ${response.body()}") + } else { + _errorMessage.postValue("Gagal memperbarui profil") + Log.e(TAG, "Update failed: ${response.errorBody()?.string()}") + } + } else { + _errorMessage.postValue("Terjadi kesalahan jaringan atau server") + Log.e(TAG, "Repository returned null response") + } + } catch (e: Exception) { + _errorMessage.postValue(e.message ?: "Unexpected error") + Log.e(TAG, "Exception updating store profile", e) + } + } + } } \ No newline at end of file diff --git a/app/src/main/res/layout/activity_detail_store_product.xml b/app/src/main/res/layout/activity_detail_store_product.xml index 0e8d3ff..fcd6ad2 100644 --- a/app/src/main/res/layout/activity_detail_store_product.xml +++ b/app/src/main/res/layout/activity_detail_store_product.xml @@ -371,6 +371,7 @@ android:background="@drawable/bg_text_field" android:hint="Isi stok produk di sini" android:padding="8dp" + android:inputType="number" style="@style/body_small" /> @@ -413,6 +414,7 @@ android:hint="Isi minimum pemesanan produk di sini" android:padding="8dp" style="@style/body_small" + android:inputType="number" android:layout_marginTop="10dp" /> @@ -711,6 +713,7 @@ android:background="@drawable/bg_text_field" android:hint="Isi minimum produk untuk mendapatkan harga grosir di sini" android:padding="8dp" + android:inputType="number" style="@style/body_small" /> diff --git a/app/src/main/res/layout/activity_register_store.xml b/app/src/main/res/layout/activity_register_store.xml index cb21ad8..9634f7e 100644 --- a/app/src/main/res/layout/activity_register_store.xml +++ b/app/src/main/res/layout/activity_register_store.xml @@ -543,49 +543,6 @@ - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + +