Merge pull request #31

gracia
This commit is contained in:
Gracia Hotmauli
2025-06-11 00:03:31 +07:00
committed by GitHub
10 changed files with 223 additions and 177 deletions

View File

@ -1,4 +1,4 @@
package com.alya.ecommerce_serang.data.api.response.store.profile
package com.alya.ecommerce_serang.data.api.response.store
import com.google.gson.annotations.SerializedName
@ -8,4 +8,4 @@ data class GenericResponse(
@SerializedName("success")
val success: Boolean = true
)
)

View File

@ -71,7 +71,7 @@ import com.alya.ecommerce_serang.data.api.response.store.product.CreateProductRe
import com.alya.ecommerce_serang.data.api.response.store.product.DeleteProductResponse
import com.alya.ecommerce_serang.data.api.response.store.product.UpdateProductResponse
import com.alya.ecommerce_serang.data.api.response.store.product.ViewStoreProductsResponse
import com.alya.ecommerce_serang.data.api.response.store.profile.GenericResponse
import com.alya.ecommerce_serang.data.api.response.store.GenericResponse
import com.alya.ecommerce_serang.data.api.response.store.profile.StoreDataResponse
import com.alya.ecommerce_serang.data.api.response.store.topup.BalanceTopUpResponse
import com.alya.ecommerce_serang.data.api.response.store.topup.TopUpResponse
@ -339,10 +339,22 @@ interface ApiService {
@PUT("store/order/update")
suspend fun updateOrder(
@Query("order_id") orderId: Int?,
@Query("status") status: String
@Part("order_id") orderId: Int?,
@Part("status") status: String
): Response<com.alya.ecommerce_serang.data.api.response.store.sells.UpdateOrderItemResponse>
@PUT("store/payment/update")
suspend fun confirmPayment(
@Part("order_id") orderId: Int?,
@Part("status") status: String
): Response<GenericResponse>
@PUT("store/shipping/receiptnum")
suspend fun confirmShipment(
@Part("receipt_num") receiptNum: String,
@Part("order_id") orderId: Int?
): Response<GenericResponse>
@Multipart
@POST("addcomplaint")
suspend fun addComplaint(

View File

@ -2,10 +2,12 @@ package com.alya.ecommerce_serang.data.repository
import android.util.Log
import com.alya.ecommerce_serang.data.api.dto.PaymentConfirmRequest
import com.alya.ecommerce_serang.data.api.response.store.GenericResponse
import com.alya.ecommerce_serang.data.api.response.store.sells.OrderDetailResponse
import com.alya.ecommerce_serang.data.api.response.store.sells.OrderListResponse
import com.alya.ecommerce_serang.data.api.response.store.sells.PaymentConfirmationResponse
import com.alya.ecommerce_serang.data.api.retrofit.ApiService
import retrofit2.Response
class SellsRepository(private val apiService: ApiService) {
suspend fun getSellList(status: String): Result<OrderListResponse> {
@ -43,40 +45,25 @@ class SellsRepository(private val apiService: ApiService) {
}
}
suspend fun updateOrderStatus(orderId: Int?, status: String) {
try {
val response = apiService.updateOrder(orderId, status)
if (response.isSuccessful) {
Log.d("SellsRepository", "Order status updated successfully: orderId=$orderId, status=$status")
} else {
Log.e("SellsRepository", "Error updating order status: orderId=$orderId, status=$status")
}
} catch (e: Exception) {
Log.e("SellsRepository", "Exception updating order status", e)
}
}
suspend fun confirmPaymentStore(request: PaymentConfirmRequest): Result<PaymentConfirmationResponse> {
suspend fun confirmPayment(orderId: Int, status: String): Response<GenericResponse> {
return try {
Log.d("SellsRepository", "Conforming order request completed: $request")
val response = apiService.paymentConfirmation(request)
if(response.isSuccessful) {
val paymentConfirmResponse = response.body()
if (paymentConfirmResponse != null) {
Log.d("SellsRepository", "Order confirmed successfully: ${paymentConfirmResponse.message}")
Result.Success(paymentConfirmResponse)
} else {
Log.e("SellsRepository", "Response body was null")
Result.Error(Exception("Empty response from server"))
}
} else {
val errorBody = response.errorBody()?.string() ?: "Unknown Error"
Log.e("SellsRepository", "Error confirming order: $errorBody")
Result.Error(Exception(errorBody))
}
} catch (e: Exception){
Result.Error(e)
Log.d("SellsRepository", "Calling confirmPayment with orderId=$orderId, status=$status")
apiService.confirmPayment(orderId, status)
} catch (e: Exception) {
Log.e("SellsRepository", "Error during confirmPayment", e)
throw e
}
}
suspend fun confirmShipment(orderId: Int, receiptNum: String): Response<GenericResponse> {
return try {
apiService.confirmShipment(
receiptNum = receiptNum,
orderId = orderId
)
} catch (e: Exception) {
throw e
}
}
}

View File

@ -16,7 +16,6 @@ import android.widget.TextView
import android.widget.Toast
import androidx.activity.viewModels
import androidx.appcompat.app.AppCompatActivity
import androidx.recyclerview.widget.LinearLayoutManager
import com.alya.ecommerce_serang.BuildConfig.BASE_URL
import com.alya.ecommerce_serang.R
import com.alya.ecommerce_serang.data.api.response.store.sells.Orders
@ -26,7 +25,6 @@ import com.alya.ecommerce_serang.data.repository.AddressRepository
import com.alya.ecommerce_serang.data.repository.SellsRepository
import com.alya.ecommerce_serang.databinding.ActivityDetailPaymentBinding
import com.alya.ecommerce_serang.ui.profile.mystore.sells.SellsProductAdapter
import com.alya.ecommerce_serang.ui.profile.mystore.sells.shipment.DetailShipmentActivity
import com.alya.ecommerce_serang.utils.BaseViewModelFactory
import com.alya.ecommerce_serang.utils.SessionManager
import com.alya.ecommerce_serang.utils.viewmodel.AddressViewModel
@ -40,6 +38,7 @@ import java.text.SimpleDateFormat
import java.util.Calendar
import java.util.Locale
import java.util.TimeZone
import androidx.core.graphics.drawable.toDrawable
class DetailPaymentActivity : AppCompatActivity() {
@ -84,6 +83,32 @@ class DetailPaymentActivity : AppCompatActivity() {
observeOrderDetails()
binding.tvOrderSellsDesc.setOnClickListener {
val paymentEvidence = sells?.paymentEvidence
if (!paymentEvidence.isNullOrEmpty()) {
showPaymentEvidenceDialog(paymentEvidence)
} else {
Toast.makeText(this, "Bukti pembayaran tidak tersedia", Toast.LENGTH_SHORT).show()
}
}
binding.btnHoldPayment.setOnClickListener {
sells?.orderId?.let {
viewModel.confirmPayment(it, "onhold")
Toast.makeText(this, "Otomatis pembayaran dinonaktifkan", Toast.LENGTH_SHORT).show()
} ?: run {
Log.e("DetailPaymentActivity", "No order passed in intent")
}
}
binding.btnConfirmPayment.setOnClickListener {
sells?.orderId?.let {
viewModel.confirmPayment(it, "confirmed")
Toast.makeText(this, "Pembayaran dikonfirmasi", Toast.LENGTH_SHORT).show()
} ?: run {
Log.e("DetailPaymentActivity", "No order passed in intent")
}
}
}
private fun observeOrderDetails() {
@ -173,17 +198,6 @@ class DetailPaymentActivity : AppCompatActivity() {
return String.format(Locale("id", "ID"), "Rp%,.0f", priceDouble)
}
private fun setupPaymentEvidenceViewer() {
binding.tvOrderSellsDesc.setOnClickListener {
val paymentEvidence = sells?.paymentEvidence
if (!paymentEvidence.isNullOrEmpty()) {
showPaymentEvidenceDialog(paymentEvidence)
} else {
Toast.makeText(this, "Bukti pembayaran tidak tersedia", Toast.LENGTH_SHORT).show()
}
}
}
private fun showPaymentEvidenceDialog(paymentEvidence: String) {
val dialog = Dialog(this)
dialog.requestWindowFeature(Window.FEATURE_NO_TITLE)
@ -193,7 +207,7 @@ class DetailPaymentActivity : AppCompatActivity() {
// Set dialog to fullscreen
val window = dialog.window
window?.setLayout(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)
window?.setBackgroundDrawable(ColorDrawable(Color.BLACK))
window?.setBackgroundDrawable(Color.WHITE.toDrawable())
// Get views from dialog
val imageView = dialog.findViewById<ImageView>(R.id.iv_payment_evidence)
@ -201,21 +215,13 @@ class DetailPaymentActivity : AppCompatActivity() {
val tvTitle = dialog.findViewById<TextView>(R.id.tv_title)
val progressBar = dialog.findViewById<ProgressBar>(R.id.progress_bar)
// Set title
tvTitle.text = "Bukti Pembayaran"
val fullImageUrl =
if (paymentEvidence.startsWith("/")) BASE_URL + paymentEvidence.substring(1)
else paymentEvidence
// Build image URL
val fullImageUrl = when (val img = paymentEvidence) {
is String -> {
if (img.startsWith("/")) BASE_URL + img.substring(1) else img
}
else -> R.drawable.placeholder_image // Default image for null
}
// Show progress bar while loading
progressBar.visibility = View.VISIBLE
// Load image with Glide
Glide.with(this)
.load(fullImageUrl)
.placeholder(R.drawable.placeholder_image)
@ -239,16 +245,8 @@ class DetailPaymentActivity : AppCompatActivity() {
}
})
// Close button click listener
btnClose.setOnClickListener {
dialog.dismiss()
}
// Click outside to close
imageView.setOnClickListener {
dialog.dismiss()
}
btnClose.setOnClickListener { dialog.dismiss() }
imageView.setOnClickListener { dialog.dismiss() }
dialog.show()
}

View File

@ -1,7 +1,9 @@
package com.alya.ecommerce_serang.ui.profile.mystore.sells.shipment
import android.content.Intent
import android.os.Bundle
import android.util.Log
import android.widget.Toast
import androidx.activity.viewModels
import androidx.appcompat.app.AppCompatActivity
import com.alya.ecommerce_serang.data.api.response.store.sells.Orders
@ -64,6 +66,16 @@ class DetailShipmentActivity : AppCompatActivity() {
}
observeOrderDetails()
binding.btnConfirmShipment.setOnClickListener {
sells?.let {
val intent = Intent(this, ShipmentConfirmationActivity::class.java)
intent.putExtra("sells_data", Gson().toJson(it))
startActivity(intent)
} ?: run {
Toast.makeText(this, "Data pesanan tidak tersedia", Toast.LENGTH_SHORT).show()
}
}
}
private fun observeOrderDetails() {

View File

@ -1,21 +1,71 @@
package com.alya.ecommerce_serang.ui.profile.mystore.sells.shipment
import android.os.Bundle
import androidx.activity.enableEdgeToEdge
import android.widget.Toast
import androidx.activity.viewModels
import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat
import com.alya.ecommerce_serang.R
import com.alya.ecommerce_serang.data.api.response.store.sells.Orders
import com.alya.ecommerce_serang.data.api.retrofit.ApiConfig
import com.alya.ecommerce_serang.data.repository.SellsRepository
import com.alya.ecommerce_serang.databinding.ActivityShipmentConfirmationBinding
import com.alya.ecommerce_serang.utils.BaseViewModelFactory
import com.alya.ecommerce_serang.utils.SessionManager
import com.alya.ecommerce_serang.utils.viewmodel.SellsViewModel
import com.google.gson.Gson
import kotlin.getValue
class ShipmentConfirmationActivity : AppCompatActivity() {
private lateinit var binding: ActivityShipmentConfirmationBinding
private var sells: Orders? = null
private lateinit var sessionManager: SessionManager
private val viewModel: SellsViewModel by viewModels {
BaseViewModelFactory {
sessionManager = SessionManager(this)
val apiService = ApiConfig.getApiService(sessionManager)
val sellsRepository = SellsRepository(apiService)
SellsViewModel(sellsRepository)
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
setContentView(R.layout.activity_shipment_confirmation)
ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main)) { v, insets ->
val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars())
v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom)
insets
binding = ActivityShipmentConfirmationBinding.inflate(layoutInflater)
setContentView(binding.root)
binding.header.headerTitle.text = "Konfirmasi Pengiriman"
binding.header.headerLeftIcon.setOnClickListener {
onBackPressed()
finish()
}
val sellsJson = intent.getStringExtra("sells_data")
sells = Gson().fromJson(sellsJson, Orders::class.java)
// Pre-fill fields
binding.edtNoPesanan.setText(sells?.orderId?.toString())
binding.edtKurir.setText(sells?.courier ?: "")
binding.edtLayananKirim.setText(sells?.service ?: "")
binding.btnConfirm.setOnClickListener {
val receiptNum = binding.edtNoResi.text.toString().trim()
val orderId = sells?.orderId
if (receiptNum.isEmpty()) {
Toast.makeText(this, "Nomor resi tidak boleh kosong", Toast.LENGTH_SHORT).show()
} else if (orderId == null) {
Toast.makeText(this, "Order ID tidak ditemukan", Toast.LENGTH_SHORT).show()
} else {
viewModel.confirmShipment(orderId, receiptNum)
}
}
viewModel.message.observe(this) {
Toast.makeText(this, it, Toast.LENGTH_SHORT).show()
}
viewModel.isSuccess.observe(this) { success ->
if (success) finish()
}
}
}

View File

@ -169,42 +169,53 @@ class SellsViewModel(private val repository: SellsRepository) : ViewModel() {
}
}
fun updateOrderStatus(orderId: Int?, status: String) {
Log.d(TAG, "========== Starting updateOrderStatus ==========")
Log.d(TAG, "Updating order status: orderId=$orderId, status='$status'")
fun confirmPayment(orderId: Int, status: String) {
Log.d(TAG, "Confirming order completed: orderId=$orderId, status=$status")
_isLoading.value = true
viewModelScope.launch {
try {
Log.d(TAG, "Calling repository.updateOrderStatus")
val startTime = System.currentTimeMillis()
repository.updateOrderStatus(orderId, status)
val endTime = System.currentTimeMillis()
Log.d(TAG, "✅ Order status updated successfully in ${endTime - startTime}ms")
Log.d(TAG, "Updated orderId=$orderId to status='$status'")
val response = repository.confirmPayment(orderId, status)
if (response.isSuccessful) {
val body = response.body()
Log.d(TAG, "confirmPayment success: ${body?.message}")
_message.value = body?.message ?: "Status berhasil diperbarui"
_isSuccess.value = true
} else {
val errorMsg = response.errorBody()?.string() ?: "Unknown error"
Log.e(TAG, "confirmPayment failed: $errorMsg")
_error.value = "Gagal memperbarui status: $errorMsg"
_isSuccess.value = false
}
} catch (e: Exception) {
Log.e(TAG, "❌ Error updating order status")
Log.e(TAG, "Exception type: ${e.javaClass.simpleName}")
Log.e(TAG, "Exception message: ${e.message}")
Log.e(TAG, "Exception stack trace:", e)
Log.e(TAG, "Exception in confirmPayment", e)
_error.value = "Terjadi kesalahan: ${e.message}"
_isSuccess.value = false
} finally {
_isLoading.value = false
}
}
Log.d(TAG, "========== updateOrderStatus method completed ==========")
}
fun confirmPayment(orderId: Int, status: String) {
Log.d(TAG, "Confirming order completed: orderId=$orderId, status=$status")
fun confirmShipment(orderId: Int, receiptNum: String) {
_isLoading.value = true
viewModelScope.launch {
_confirmPaymentStore.value = Result.Loading
val request = PaymentConfirmRequest(orderId, status)
Log.d(TAG, "Sending order completion request: $request")
val result = repository.confirmPaymentStore(request)
Log.d(TAG, "Order completion result: $result")
_confirmPaymentStore.value = result
try {
val response = repository.confirmShipment(orderId, receiptNum)
if (response.isSuccessful) {
_message.value = response.body()?.message ?: "Berhasil mengonfirmasi pengiriman"
_isSuccess.value = true
} else {
val errorMsg = response.errorBody()?.string() ?: "Gagal konfirmasi pengiriman"
_error.value = errorMsg
_isSuccess.value = false
}
} catch (e: Exception) {
_error.value = "Terjadi kesalahan: ${e.message}"
_isSuccess.value = false
} finally {
_isLoading.value = false
}
}
}

View File

@ -31,7 +31,7 @@
<!-- Payment Header -->
<View
android:id="@+id/shape_payment_order_title"
android:id="@+id/shape_shipment_order_title"
android:layout_width="4dp"
android:layout_height="10dp"
android:layout_marginTop="3dp"
@ -44,7 +44,7 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="12dp"
app:layout_constraintStart_toStartOf="@id/shape_payment_order_title"
app:layout_constraintStart_toStartOf="@id/shape_shipment_order_title"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
android:gravity="center">
@ -278,7 +278,7 @@
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintStart_toStartOf="@id/shape_payment_order_title"
app:layout_constraintStart_toStartOf="@id/shape_shipment_order_title"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
android:gravity="center">
@ -409,7 +409,7 @@
android:gravity="bottom">
<Button
android:id="@+id/btn_confirm_payment"
android:id="@+id/btn_confirm_shipment"
style="@style/button.large.active.long"
android:text="Kirim Pesanan"
android:layout_alignParentEnd="true"/>

View File

@ -59,7 +59,7 @@
android:orientation="vertical"
android:layout_marginBottom="24dp">
<!-- Label Kurir Pengiriman -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
@ -72,47 +72,37 @@
style="@style/body_medium"
android:layout_marginEnd="4dp"/>
</LinearLayout>
<!-- Spinner Dropdown dengan Chevron -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:background="@drawable/bg_text_field_disabled"
android:gravity="center_vertical"
android:layout_marginTop="10dp">
<Spinner
android:id="@+id/spinner_kurir"
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:padding="8dp"
style="@style/body_small"
android:background="@null"
android:clickable="false"/>
<!-- Chevron Down Icon -->
<ImageView
android:layout_width="16dp"
android:layout_height="16dp"
android:src="@drawable/ic_down"
android:layout_marginEnd="8dp"
android:contentDescription="Chevron Down" />
android:text="*"
style="@style/body_medium"
android:textColor="@color/red_required"
android:layout_gravity="end"/>
</LinearLayout>
<EditText
android:id="@+id/edt_kurir"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/bg_text_field"
android:hint="Isi kurir pengiriman di sini"
android:padding="8dp"
style="@style/body_small"
android:layout_marginTop="4dp"/>
</LinearLayout>
<!-- Paket Pengiriman -->
<!-- Layanan Pengiriman -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:layout_marginBottom="24dp">
<!-- Label Kondisi Produk -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
@ -121,43 +111,32 @@
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Paket Pengiriman"
android:text="Layanan Pengiriman"
style="@style/body_medium"
android:layout_marginEnd="4dp"/>
</LinearLayout>
<!-- Spinner Dropdown dengan Chevron -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:background="@drawable/bg_text_field_disabled"
android:gravity="center_vertical"
android:layout_marginTop="10dp">
<Spinner
android:id="@+id/spinner_paket_kirim"
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:padding="8dp"
style="@style/body_small"
android:background="@null"
android:clickable="false"/>
<!-- Chevron Down Icon -->
<ImageView
android:layout_width="16dp"
android:layout_height="16dp"
android:src="@drawable/ic_down"
android:layout_marginEnd="8dp"
android:contentDescription="Chevron Down" />
android:text="*"
style="@style/body_medium"
android:textColor="@color/red_required"
android:layout_gravity="end"/>
</LinearLayout>
</LinearLayout>
<EditText
android:id="@+id/edt_layanan_kirim"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/bg_text_field"
android:hint="Isi layanan pengiriman dari kurir di sini"
android:padding="8dp"
style="@style/body_small"
android:layout_marginTop="4dp"/>
</LinearLayout>
<!-- Nomor Resi -->
<LinearLayout

View File

@ -3,7 +3,7 @@
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@android:color/black">
android:background="@color/white">
<!-- Header with title and close button -->
<LinearLayout
@ -23,18 +23,15 @@
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="Bukti Pembayaran"
android:textColor="@android:color/white"
android:textSize="18sp"
android:textStyle="bold" />
style="@style/title_large" />
<ImageButton
android:id="@+id/btn_close"
android:layout_width="48dp"
android:layout_height="48dp"
android:layout_width="24dp"
android:layout_height="24dp"
android:background="?attr/selectableItemBackgroundBorderless"
android:src="@drawable/ic_close"
android:contentDescription="Close"
app:tint="@android:color/white" />
android:contentDescription="Close" />
</LinearLayout>