sells detail

This commit is contained in:
Gracia Hotmauli
2025-06-08 17:36:20 +07:00
parent 51882ff634
commit aa8229f0a0
13 changed files with 448 additions and 198 deletions

View File

@ -0,0 +1,36 @@
package com.alya.ecommerce_serang.data.api.dto
import com.google.gson.annotations.SerializedName
data class OrderItemsItem(
@field:SerializedName("order_item_id")
val orderItemId: Int? = null,
@field:SerializedName("review_id")
val reviewId: Int? = null,
@field:SerializedName("quantity")
val quantity: Int? = null,
@field:SerializedName("price")
val price: Int? = null,
@field:SerializedName("subtotal")
val subtotal: Int? = null,
@field:SerializedName("product_image")
val productImage: String? = null,
@field:SerializedName("product_id")
val productId: Int? = null,
@field:SerializedName("store_name")
val storeName: String? = null,
@field:SerializedName("product_price")
val productPrice: Int? = null,
@field:SerializedName("product_name")
val productName: String? = null
)

View File

@ -1,5 +1,6 @@
package com.alya.ecommerce_serang.data.api.response.store.sells package com.alya.ecommerce_serang.data.api.response.store.sells
import com.alya.ecommerce_serang.data.api.dto.OrderItemsItem
import com.google.gson.annotations.SerializedName import com.google.gson.annotations.SerializedName
data class OrderDetailResponse( data class OrderDetailResponse(
@ -17,7 +18,7 @@ data class Orders(
val receiptNum: Any? = null, val receiptNum: Any? = null,
@field:SerializedName("payment_upload_at") @field:SerializedName("payment_upload_at")
val paymentUploadAt: Any? = null, val paymentUploadAt: String? = null,
@field:SerializedName("latitude") @field:SerializedName("latitude")
val latitude: Any? = null, val latitude: Any? = null,
@ -44,10 +45,10 @@ data class Orders(
val street: String? = null, val street: String? = null,
@field:SerializedName("cancel_date") @field:SerializedName("cancel_date")
val cancelDate: Any? = null, val cancelDate: String? = null,
@field:SerializedName("payment_evidence") @field:SerializedName("payment_evidence")
val paymentEvidence: Any? = null, val paymentEvidence: String? = null,
@field:SerializedName("longitude") @field:SerializedName("longitude")
val longitude: Any? = null, val longitude: Any? = null,
@ -71,16 +72,16 @@ data class Orders(
val voucherName: Any? = null, val voucherName: Any? = null,
@field:SerializedName("payment_status") @field:SerializedName("payment_status")
val paymentStatus: Any? = null, val paymentStatus: String? = null,
@field:SerializedName("address_id") @field:SerializedName("address_id")
val addressId: Int? = null, val addressId: Int? = null,
@field:SerializedName("payment_amount") @field:SerializedName("payment_amount")
val paymentAmount: Any? = null, val paymentAmount: String? = null,
@field:SerializedName("cancel_reason") @field:SerializedName("cancel_reason")
val cancelReason: Any? = null, val cancelReason: String? = null,
@field:SerializedName("total_amount") @field:SerializedName("total_amount")
val totalAmount: String? = null, val totalAmount: String? = null,
@ -88,6 +89,9 @@ data class Orders(
@field:SerializedName("user_id") @field:SerializedName("user_id")
val userId: Int? = null, val userId: Int? = null,
@field:SerializedName("phone")
val phone: String? = null,
@field:SerializedName("province_id") @field:SerializedName("province_id")
val provinceId: Int? = null, val provinceId: Int? = null,
@ -103,6 +107,9 @@ data class Orders(
@field:SerializedName("pay_info_num") @field:SerializedName("pay_info_num")
val payInfoNum: String? = null, val payInfoNum: String? = null,
@field:SerializedName("recipient")
val recipient: String? = null,
@field:SerializedName("shipment_price") @field:SerializedName("shipment_price")
val shipmentPrice: String? = null, val shipmentPrice: String? = null,
@ -126,4 +133,4 @@ data class Orders(
@field:SerializedName("city_id") @field:SerializedName("city_id")
val cityId: Int? = null val cityId: Int? = null
) )

View File

@ -1,5 +1,6 @@
package com.alya.ecommerce_serang.data.api.response.store.sells package com.alya.ecommerce_serang.data.api.response.store.sells
import com.alya.ecommerce_serang.data.api.dto.OrderItemsItem
import com.google.gson.annotations.SerializedName import com.google.gson.annotations.SerializedName
data class OrderListResponse( data class OrderListResponse(
@ -11,39 +12,6 @@ data class OrderListResponse(
val message: String? = null val message: String? = null
) )
data class OrderItemsItem(
@field:SerializedName("order_item_id")
val orderItemId: Int? = null,
@field:SerializedName("review_id")
val reviewId: Any? = null,
@field:SerializedName("quantity")
val quantity: Int? = null,
@field:SerializedName("price")
val price: Int? = null,
@field:SerializedName("subtotal")
val subtotal: Int? = null,
@field:SerializedName("product_image")
val productImage: String? = null,
@field:SerializedName("product_id")
val productId: Int? = null,
@field:SerializedName("store_name")
val storeName: String? = null,
@field:SerializedName("product_price")
val productPrice: Int? = null,
@field:SerializedName("product_name")
val productName: String? = null
)
data class OrdersItem( data class OrdersItem(
@field:SerializedName("receipt_num") @field:SerializedName("receipt_num")

View File

@ -327,6 +327,11 @@ interface ApiService {
@Path("status") status: String @Path("status") status: String
): Response<com.alya.ecommerce_serang.data.api.response.store.sells.OrderListResponse> ): Response<com.alya.ecommerce_serang.data.api.response.store.sells.OrderListResponse>
@GET("order/detail/{id}")
suspend fun getSellDetail(
@Path("id") orderId: Int
): Response<com.alya.ecommerce_serang.data.api.response.store.sells.OrderDetailResponse>
@PUT("store/order/update") @PUT("store/order/update")
suspend fun confirmOrder( suspend fun confirmOrder(
@Body confirmOrder : CompletedOrderRequest @Body confirmOrder : CompletedOrderRequest

View File

@ -2,6 +2,7 @@ package com.alya.ecommerce_serang.data.repository
import android.util.Log import android.util.Log
import com.alya.ecommerce_serang.data.api.dto.PaymentConfirmRequest import com.alya.ecommerce_serang.data.api.dto.PaymentConfirmRequest
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.OrderListResponse
import com.alya.ecommerce_serang.data.api.response.store.sells.PaymentConfirmationResponse import com.alya.ecommerce_serang.data.api.response.store.sells.PaymentConfirmationResponse
import com.alya.ecommerce_serang.data.api.retrofit.ApiService import com.alya.ecommerce_serang.data.api.retrofit.ApiService
@ -32,6 +33,16 @@ class SellsRepository(private val apiService: ApiService) {
} }
} }
suspend fun getSellDetails(orderId: Int): OrderDetailResponse? {
return try {
val response = apiService.getSellDetail(orderId)
if (response.isSuccessful) response.body() else null
} catch (e: Exception) {
Log.e("SellsRepository", "Error getting order details", e)
null
}
}
suspend fun updateOrderStatus(orderId: Int?, status: String) { suspend fun updateOrderStatus(orderId: Int?, status: String) {
try { try {
val response = apiService.updateOrder(orderId, status) val response = apiService.updateOrder(orderId, status)

View File

@ -2,15 +2,20 @@ package com.alya.ecommerce_serang.ui.profile.mystore.sells
import android.os.Bundle import android.os.Bundle
import android.util.Log import android.util.Log
import android.widget.Toast
import androidx.activity.viewModels import androidx.activity.viewModels
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import com.alya.ecommerce_serang.data.api.response.store.sells.Orders import com.alya.ecommerce_serang.data.api.response.store.sells.Orders
import com.alya.ecommerce_serang.data.api.response.store.sells.OrdersItem
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.AddressRepository
import com.alya.ecommerce_serang.data.repository.SellsRepository import com.alya.ecommerce_serang.data.repository.SellsRepository
import com.alya.ecommerce_serang.databinding.ActivityDetailSellsBinding import com.alya.ecommerce_serang.databinding.ActivityDetailSellsBinding
import com.alya.ecommerce_serang.ui.profile.mystore.sells.shipment.DetailShipmentActivity
import com.alya.ecommerce_serang.utils.BaseViewModelFactory import com.alya.ecommerce_serang.utils.BaseViewModelFactory
import com.alya.ecommerce_serang.utils.SessionManager import com.alya.ecommerce_serang.utils.SessionManager
import com.alya.ecommerce_serang.utils.viewmodel.AddressViewModel
import com.alya.ecommerce_serang.utils.viewmodel.SellsViewModel import com.alya.ecommerce_serang.utils.viewmodel.SellsViewModel
import com.google.gson.Gson import com.google.gson.Gson
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
@ -28,6 +33,7 @@ class DetailSellsActivity : AppCompatActivity() {
private val viewModel: SellsViewModel by viewModels { private val viewModel: SellsViewModel by viewModels {
BaseViewModelFactory { BaseViewModelFactory {
sessionManager = SessionManager(this)
val apiService = ApiConfig.getApiService(sessionManager) val apiService = ApiConfig.getApiService(sessionManager)
val sellsRepository = SellsRepository(apiService) val sellsRepository = SellsRepository(apiService)
SellsViewModel(sellsRepository) SellsViewModel(sellsRepository)
@ -51,48 +57,88 @@ class DetailSellsActivity : AppCompatActivity() {
layoutManager = LinearLayoutManager(this@DetailSellsActivity) layoutManager = LinearLayoutManager(this@DetailSellsActivity)
} }
val sellsJson = intent.getStringExtra("sells") val sellsJson = intent.getStringExtra("sells_data")
if (sellsJson != null) { if (sellsJson != null) {
try { try {
sells = Gson().fromJson(sellsJson, Orders::class.java) val basicOrder = Gson().fromJson(sellsJson, OrdersItem::class.java)
showOrderDetails() basicOrder.orderId.let {
viewModel.getSellDetails(it)
}
} catch (e: Exception) { } catch (e: Exception) {
Log.e("DetailSellsActivity", "Failed to parse order data", e) Log.e("DetailSellsActivity", "Failed to parse order data", e)
} }
} else { } else {
Log.e("DetailSellsActivity", "Order data is missing") Log.e("DetailSellsActivity", "No order passed in intent")
}
observeOrderDetails()
}
private fun observeOrderDetails() {
viewModel.sellDetails.observe(this) { order ->
if (order != null) {
sells = order
showOrderDetails()
} else {
Log.e("DetailSellsActivity", "❌ Failed to retrieve order details")
}
} }
} }
private fun showOrderDetails() { private fun showOrderDetails() = with(binding) {
sells?.let { sell -> sells?.let { sell ->
when (sell.orderStatus) { when (sell.orderStatus) {
"pending", "unpaid" -> { "pending", "unpaid" -> tvOrderSellsTitle.text = "Pesanan Belum Dibayar"
binding.tvOrderSellsTitle.text = "Pesanan Belum Dibayar" "shipped" -> tvOrderSellsTitle.text = "Pesanan Telah Dikirim"
} "delivered" -> tvOrderSellsTitle.text = "Pesanan Telah Sampai"
"shipped" -> { "completed" -> tvOrderSellsTitle.text = "Pesanan Selesai"
binding.tvOrderSellsTitle.text = "Pesanan Telah Dikirim" "canceled" -> tvOrderSellsTitle.text = "Pesanan Dibatalkan"
} else -> tvOrderSellsTitle.text = "Status Tidak Diketahui"
"delivered" -> {
binding.tvOrderSellsTitle.text = "Pesanan Telah Sampai"
}
"completed" -> {
binding.tvOrderSellsTitle.text = "Pesanan Selesai"
}
"canceled" -> {
binding.tvOrderSellsTitle.text = "Pesanan Dibatalkan"
}
} }
binding.tvOrderNumber.text = sell.orderId.toString() tvOrderNumber.text = sell.orderId.toString()
binding.tvOrderCustomer.text = sell.username tvOrderCustomer.text = sell.username
binding.tvOrderDate.text = formatDate(sell.updatedAt.toString()) tvOrderDate.text = formatDate(sell.updatedAt.toString())
binding.tvOrderTotalProduct.text = "(${sell.orderItems?.size} Barang)" tvOrderTotalProduct.text = "(${sell.orderItems?.size ?: 0} Barang)"
binding.tvOrderSubtotal.text = formatPrice(sell.totalAmount.toString()) tvOrderSubtotal.text = formatPrice(sell.totalAmount.toString())
binding.tvOrderShipPrice.text = formatPrice(sell.shipmentPrice.toString()) tvOrderShipPrice.text = formatPrice(sell.shipmentPrice.toString())
binding.tvOrderPrice.text = formatPrice(sell.totalAmount.toString()) tvOrderPrice.text = formatPrice(sell.totalAmount.toString())
binding.tvOrderRecipient.text = sell.username tvOrderRecipient.text = sell.recipient ?: "-"
binding.tvOrderRecipientNum.text = sell.receiptNum.toString() tvOrderRecipientNum.text = sell.receiptNum?.toString() ?: "-"
val cityId = sell.cityId?.toString()
val provinceId = sell.provinceId?.toString()
if (cityId != null && provinceId != null) {
val viewModelAddress: AddressViewModel by viewModels {
BaseViewModelFactory {
val apiService = ApiConfig.getApiService(sessionManager)
val addressRepository = AddressRepository(apiService)
AddressViewModel(addressRepository)
}
}
viewModelAddress.fetchCities(provinceId)
viewModelAddress.fetchProvinces()
viewModelAddress.cities.observe(this@DetailSellsActivity) { cities ->
val cityName = cities.find { it.cityId == cityId }?.cityName
viewModelAddress.provinces.observe(this@DetailSellsActivity) { provinces ->
val provinceName = provinces.find { it.provinceId == provinceId }?.provinceName
val fullAddress = listOfNotNull(
sell.street,
sell.subdistrict,
cityName,
provinceName
).joinToString(", ")
tvOrderRecipientAddress.text = fullAddress
}
}
} else {
tvOrderRecipientAddress.text = "-"
}
sell.orderItems?.let { sell.orderItems?.let {
productAdapter.submitList(it.filterNotNull()) productAdapter.submitList(it.filterNotNull())
@ -106,13 +152,11 @@ class DetailSellsActivity : AppCompatActivity() {
inputFormat.timeZone = TimeZone.getTimeZone("UTC") inputFormat.timeZone = TimeZone.getTimeZone("UTC")
val outputFormat = SimpleDateFormat("dd MMM yyyy, HH.mm", Locale("id", "ID")) val outputFormat = SimpleDateFormat("dd MMM yyyy, HH.mm", Locale("id", "ID"))
val date = inputFormat.parse(dateString) val date = inputFormat.parse(dateString)
date?.let { date?.let {
val calendar = Calendar.getInstance() val calendar = Calendar.getInstance()
calendar.time = it calendar.time = it
outputFormat.format(calendar.time) outputFormat.format(calendar.time)
} ?: dateString } ?: dateString
} catch (e: Exception) { } catch (e: Exception) {
@ -123,7 +167,6 @@ class DetailSellsActivity : AppCompatActivity() {
private fun formatPrice(price: String): String { private fun formatPrice(price: String): String {
val priceDouble = price.toDoubleOrNull() ?: 0.0 val priceDouble = price.toDoubleOrNull() ?: 0.0
val formattedPrice = String.format(Locale("id", "ID"), "Rp%,.0f", priceDouble) return String.format(Locale("id", "ID"), "Rp%,.0f", priceDouble)
return formattedPrice
} }
} }

View File

@ -6,8 +6,9 @@ import android.view.ViewGroup
import android.widget.ImageView import android.widget.ImageView
import android.widget.TextView import android.widget.TextView
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.response.store.sells.OrderItemsItem import com.alya.ecommerce_serang.data.api.dto.OrderItemsItem
import com.bumptech.glide.Glide import com.bumptech.glide.Glide
import java.util.Locale import java.util.Locale
@ -33,9 +34,18 @@ class SellsProductAdapter : RecyclerView.Adapter<SellsProductAdapter.ProductView
tvQty.text = "${item.quantity} x " tvQty.text = "${item.quantity} x "
tvPrice.text = formatPrice(item.price.toString()) tvPrice.text = formatPrice(item.price.toString())
tvTotal.text = formatPrice(item.subtotal.toString()) tvTotal.text = formatPrice(item.subtotal.toString())
val fullImageUrl = when (val img = item.productImage) {
is String -> {
if (img.startsWith("/")) BASE_URL + img.substring(1) else img
}
else -> R.drawable.placeholder_image
}
Glide.with(ivProduct.context) Glide.with(ivProduct.context)
.load(item.productImage) .load(fullImageUrl)
.placeholder(R.drawable.placeholder_image) .placeholder(R.drawable.placeholder_image)
.error(R.drawable.placeholder_image)
.into(ivProduct) .into(ivProduct)
} }
} }

View File

@ -5,6 +5,7 @@ import android.graphics.Color
import android.graphics.drawable.ColorDrawable import android.graphics.drawable.ColorDrawable
import android.graphics.drawable.Drawable import android.graphics.drawable.Drawable
import android.os.Bundle import android.os.Bundle
import android.util.Log
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.view.Window import android.view.Window
@ -18,32 +19,38 @@ import androidx.appcompat.app.AppCompatActivity
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import com.alya.ecommerce_serang.BuildConfig.BASE_URL 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.response.store.sells.Orders
import com.alya.ecommerce_serang.data.api.response.store.sells.OrdersItem import com.alya.ecommerce_serang.data.api.response.store.sells.OrdersItem
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.AddressRepository
import com.alya.ecommerce_serang.data.repository.SellsRepository import com.alya.ecommerce_serang.data.repository.SellsRepository
import com.alya.ecommerce_serang.databinding.ActivityDetailPaymentBinding 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.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.BaseViewModelFactory
import com.alya.ecommerce_serang.utils.SessionManager import com.alya.ecommerce_serang.utils.SessionManager
import com.alya.ecommerce_serang.utils.viewmodel.AddressViewModel
import com.alya.ecommerce_serang.utils.viewmodel.SellsViewModel import com.alya.ecommerce_serang.utils.viewmodel.SellsViewModel
import com.bumptech.glide.Glide import com.bumptech.glide.Glide
import com.bumptech.glide.load.engine.DiskCacheStrategy import com.bumptech.glide.load.engine.DiskCacheStrategy
import com.bumptech.glide.request.target.CustomTarget import com.bumptech.glide.request.target.CustomTarget
import com.bumptech.glide.request.transition.Transition
import com.google.gson.Gson import com.google.gson.Gson
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
import java.util.Calendar
import java.util.Locale import java.util.Locale
import java.util.TimeZone import java.util.TimeZone
class DetailPaymentActivity : AppCompatActivity() { class DetailPaymentActivity : AppCompatActivity() {
private lateinit var binding: ActivityDetailPaymentBinding private lateinit var binding: ActivityDetailPaymentBinding
private lateinit var sells: OrdersItem private var sells: Orders? = null
private lateinit var productAdapter: SellsProductAdapter private lateinit var productAdapter: SellsProductAdapter
private lateinit var sessionManager: SessionManager private lateinit var sessionManager: SessionManager
private val viewModel: SellsViewModel by viewModels { private val viewModel: SellsViewModel by viewModels {
BaseViewModelFactory { BaseViewModelFactory {
sessionManager = SessionManager(this)
val apiService = ApiConfig.getApiService(sessionManager) val apiService = ApiConfig.getApiService(sessionManager)
val sellsRepository = SellsRepository(apiService) val sellsRepository = SellsRepository(apiService)
SellsViewModel(sellsRepository) SellsViewModel(sellsRepository)
@ -55,59 +62,120 @@ class DetailPaymentActivity : AppCompatActivity() {
binding = ActivityDetailPaymentBinding.inflate(layoutInflater) binding = ActivityDetailPaymentBinding.inflate(layoutInflater)
setContentView(binding.root) setContentView(binding.root)
sessionManager = SessionManager(this) binding.header.headerTitle.text = "Detail Pesanan"
val orderJson = intent.getStringExtra("sells_data")
sells = Gson().fromJson(orderJson, OrdersItem::class.java)
binding.header.headerLeftIcon.setOnClickListener { binding.header.headerLeftIcon.setOnClickListener {
// onBackPressed() onBackPressed()
finish() finish()
} }
setupRecyclerView() val sellsJson = intent.getStringExtra("sells_data")
bindOrderDetails() if (sellsJson != null) {
setupPaymentEvidenceViewer() try {
val basicOrder = Gson().fromJson(sellsJson, OrdersItem::class.java)
basicOrder.orderId.let {
viewModel.getSellDetails(it)
}
} catch (e: Exception) {
Log.e("DetailSellsActivity", "Failed to parse order data", e)
}
} else {
Log.e("DetailSellsActivity", "No order passed in intent")
}
observeOrderDetails()
} }
private fun bindOrderDetails() = with(binding) { private fun observeOrderDetails() {
tvOrderNumber.text = sells.orderId.toString() viewModel.sellDetails.observe(this) { order ->
tvOrderCustomer.text = sells.username if (order != null) {
tvOrderDate.text = formatDate(sells.createdAt) sells = order
tvOrderDue.text = formatDate(sells.updatedAt) showOrderDetails()
} else {
tvOrderTotalProduct.text = "(${sells.orderItems?.size ?: 0} Barang)" Log.e("DetailShipmentActivity", "❌ Failed to retrieve order details")
tvOrderSubtotal.text = "Rp${sells.totalAmount}" }
tvOrderPrice.text = "Rp${sells.totalAmount}"
tvOrderShipPrice.text = "Rp${sells.shipmentPrice}"
tvOrderRecipient.text = sells.username
// tvOrderRecipientNum.text = sells.phone
tvOrderRecipientAddress.text = sells.street
// sells.paymentEvidence
binding.btnConfirmPayment.setOnClickListener{
viewModel.confirmPayment(sells.orderId, "confirmed")
finish()
} }
} }
private fun setupRecyclerView() { private fun showOrderDetails() = with(binding) {
productAdapter = SellsProductAdapter() sells?.let { sell ->
tvOrderNumber.text = sell.orderId.toString()
tvOrderCustomer.text = sell.username
tvOrderDate.text = formatDate(sell.updatedAt.toString())
tvOrderTotalProduct.text = "(${sell.orderItems?.size ?: 0} Barang)"
tvOrderSubtotal.text = formatPrice(sell.totalAmount.toString())
tvOrderShipPrice.text = formatPrice(sell.shipmentPrice.toString())
tvOrderPrice.text = formatPrice(sell.totalAmount.toString())
tvOrderRecipient.text = sell.recipient ?: "-"
tvOrderRecipientNum.text = sell.receiptNum?.toString() ?: "-"
binding.rvProductItems.apply { val cityId = sell.cityId?.toString()
layoutManager = LinearLayoutManager(this@DetailPaymentActivity) val provinceId = sell.provinceId?.toString()
adapter = productAdapter
if (cityId != null && provinceId != null) {
val viewModelAddress: AddressViewModel by viewModels {
BaseViewModelFactory {
val apiService = ApiConfig.getApiService(sessionManager)
val addressRepository = AddressRepository(apiService)
AddressViewModel(addressRepository)
}
}
viewModelAddress.fetchCities(provinceId)
viewModelAddress.fetchProvinces()
viewModelAddress.cities.observe(this@DetailPaymentActivity) { cities ->
val cityName = cities.find { it.cityId == cityId }?.cityName
viewModelAddress.provinces.observe(this@DetailPaymentActivity) { provinces ->
val provinceName = provinces.find { it.provinceId == provinceId }?.provinceName
val fullAddress = listOfNotNull(
sell.street,
sell.subdistrict,
cityName,
provinceName
).joinToString(", ")
tvOrderRecipientAddress.text = fullAddress
}
}
} else {
tvOrderRecipientAddress.text = "-"
}
sell.orderItems?.let {
productAdapter.submitList(it.filterNotNull())
}
} }
}
// Submit the order items to the adapter private fun formatDate(dateString: String): String {
productAdapter.submitList(sells.orderItems ?: emptyList()) return try {
val inputFormat = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", Locale.getDefault())
inputFormat.timeZone = TimeZone.getTimeZone("UTC")
val outputFormat = SimpleDateFormat("dd MMM yyyy, HH.mm", Locale("id", "ID"))
val date = inputFormat.parse(dateString)
date?.let {
val calendar = Calendar.getInstance()
calendar.time = it
outputFormat.format(calendar.time)
} ?: dateString
} catch (e: Exception) {
Log.e("DateFormatting", "Error formatting date: ${e.message}")
dateString
}
}
private fun formatPrice(price: String): String {
val priceDouble = price.toDoubleOrNull() ?: 0.0
return String.format(Locale("id", "ID"), "Rp%,.0f", priceDouble)
} }
private fun setupPaymentEvidenceViewer() { private fun setupPaymentEvidenceViewer() {
binding.tvOrderSellsDesc.setOnClickListener { binding.tvOrderSellsDesc.setOnClickListener {
val paymentEvidence = sells.paymentEvidence val paymentEvidence = sells?.paymentEvidence
if (!paymentEvidence.isNullOrEmpty()) { if (!paymentEvidence.isNullOrEmpty()) {
showPaymentEvidenceDialog(paymentEvidence) showPaymentEvidenceDialog(paymentEvidence)
} else { } else {
@ -154,7 +222,7 @@ class DetailPaymentActivity : AppCompatActivity() {
.error(R.drawable.placeholder_image) .error(R.drawable.placeholder_image)
.diskCacheStrategy(DiskCacheStrategy.ALL) .diskCacheStrategy(DiskCacheStrategy.ALL)
.into(object : CustomTarget<Drawable>() { .into(object : CustomTarget<Drawable>() {
override fun onResourceReady(resource: Drawable, transition: com.bumptech.glide.request.transition.Transition<in Drawable>?) { override fun onResourceReady(resource: Drawable, transition: Transition<in Drawable>?) {
progressBar.visibility = View.GONE progressBar.visibility = View.GONE
imageView.setImageDrawable(resource) imageView.setImageDrawable(resource)
} }
@ -184,17 +252,4 @@ class DetailPaymentActivity : AppCompatActivity() {
dialog.show() dialog.show()
} }
private fun formatDate(dateStr: String?): String {
return try {
val inputFormat = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", Locale.getDefault())
inputFormat.timeZone = TimeZone.getTimeZone("UTC")
val outputFormat = SimpleDateFormat("dd MMM yyyy, HH:mm", Locale("id", "ID"))
val date = inputFormat.parse(dateStr ?: "")
outputFormat.format(date!!)
} catch (e: Exception) {
"-"
}
}
} }

View File

@ -1,71 +1,155 @@
package com.alya.ecommerce_serang.ui.profile.mystore.sells.shipment package com.alya.ecommerce_serang.ui.profile.mystore.sells.shipment
import android.os.Bundle import android.os.Bundle
import android.util.Log
import androidx.activity.viewModels
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.recyclerview.widget.LinearLayoutManager import com.alya.ecommerce_serang.data.api.response.store.sells.Orders
import com.alya.ecommerce_serang.data.api.response.store.sells.OrdersItem import com.alya.ecommerce_serang.data.api.response.store.sells.OrdersItem
import com.alya.ecommerce_serang.data.api.retrofit.ApiConfig
import com.alya.ecommerce_serang.data.repository.AddressRepository
import com.alya.ecommerce_serang.data.repository.SellsRepository
import com.alya.ecommerce_serang.databinding.ActivityDetailShipmentBinding import com.alya.ecommerce_serang.databinding.ActivityDetailShipmentBinding
import com.alya.ecommerce_serang.ui.profile.mystore.sells.SellsProductAdapter import com.alya.ecommerce_serang.ui.profile.mystore.sells.SellsProductAdapter
import com.alya.ecommerce_serang.utils.BaseViewModelFactory
import com.alya.ecommerce_serang.utils.SessionManager
import com.alya.ecommerce_serang.utils.viewmodel.AddressViewModel
import com.alya.ecommerce_serang.utils.viewmodel.SellsViewModel
import com.google.gson.Gson import com.google.gson.Gson
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
import java.util.Calendar
import java.util.Locale import java.util.Locale
import java.util.TimeZone import java.util.TimeZone
import kotlin.getValue
class DetailShipmentActivity : AppCompatActivity() { class DetailShipmentActivity : AppCompatActivity() {
private lateinit var binding: ActivityDetailShipmentBinding private lateinit var binding: ActivityDetailShipmentBinding
private lateinit var sells: OrdersItem private lateinit var sessionManager: SessionManager
private var sells: Orders? = null
private lateinit var productAdapter: SellsProductAdapter private lateinit var productAdapter: SellsProductAdapter
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?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
binding = ActivityDetailShipmentBinding.inflate(layoutInflater) binding = ActivityDetailShipmentBinding.inflate(layoutInflater)
setContentView(binding.root) setContentView(binding.root)
val orderJson = intent.getStringExtra("sells_data") binding.header.headerTitle.text = "Detail Pesanan"
sells = Gson().fromJson(orderJson, OrdersItem::class.java) binding.header.headerLeftIcon.setOnClickListener {
onBackPressed()
setupRecyclerView() finish()
bindOrderDetails()
}
private fun bindOrderDetails() = with(binding) {
tvOrderNumber.text = sells.orderId.toString()
tvOrderCustomer.text = sells.username
tvOrderDate.text = formatDate(sells.createdAt)
tvOrderDue.text = formatDate(sells.updatedAt)
tvOrderTotalProduct.text = "(${sells.orderItems?.size ?: 0} Barang)"
tvOrderSubtotal.text = "Rp${sells.totalAmount}"
tvOrderPrice.text = "Rp${sells.totalAmount}"
tvOrderShipPrice.text = "Rp${sells.shipmentPrice}"
tvOrderRecipient.text = sells.username
// tvOrderRecipientNum.text = sells.phone
tvOrderRecipientAddress.text = sells.street
}
private fun setupRecyclerView() {
productAdapter = SellsProductAdapter()
binding.rvProductItems.apply {
layoutManager = LinearLayoutManager(this@DetailShipmentActivity)
adapter = productAdapter
} }
// Submit the order items to the adapter val sellsJson = intent.getStringExtra("sells_data")
productAdapter.submitList(sells.orderItems ?: emptyList()) if (sellsJson != null) {
try {
val basicOrder = Gson().fromJson(sellsJson, OrdersItem::class.java)
basicOrder.orderId.let {
viewModel.getSellDetails(it)
}
} catch (e: Exception) {
Log.e("DetailSellsActivity", "Failed to parse order data", e)
}
} else {
Log.e("DetailSellsActivity", "No order passed in intent")
}
observeOrderDetails()
} }
private fun formatDate(dateStr: String?): String { private fun observeOrderDetails() {
viewModel.sellDetails.observe(this) { order ->
if (order != null) {
sells = order
showOrderDetails()
} else {
Log.e("DetailShipmentActivity", "❌ Failed to retrieve order details")
}
}
}
private fun showOrderDetails() = with(binding) {
sells?.let { sell ->
tvOrderNumber.text = sell.orderId.toString()
tvOrderCustomer.text = sell.username
tvOrderDate.text = formatDate(sell.updatedAt.toString())
tvOrderTotalProduct.text = "(${sell.orderItems?.size ?: 0} Barang)"
tvOrderSubtotal.text = formatPrice(sell.totalAmount.toString())
tvOrderShipPrice.text = formatPrice(sell.shipmentPrice.toString())
tvOrderPrice.text = formatPrice(sell.totalAmount.toString())
tvOrderRecipient.text = sell.recipient ?: "-"
tvOrderRecipientNum.text = sell.receiptNum?.toString() ?: "-"
val cityId = sell.cityId?.toString()
val provinceId = sell.provinceId?.toString()
if (cityId != null && provinceId != null) {
val viewModelAddress: AddressViewModel by viewModels {
BaseViewModelFactory {
val apiService = ApiConfig.getApiService(sessionManager)
val addressRepository = AddressRepository(apiService)
AddressViewModel(addressRepository)
}
}
viewModelAddress.fetchCities(provinceId)
viewModelAddress.fetchProvinces()
viewModelAddress.cities.observe(this@DetailShipmentActivity) { cities ->
val cityName = cities.find { it.cityId == cityId }?.cityName
viewModelAddress.provinces.observe(this@DetailShipmentActivity) { provinces ->
val provinceName = provinces.find { it.provinceId == provinceId }?.provinceName
val fullAddress = listOfNotNull(
sell.street,
sell.subdistrict,
cityName,
provinceName
).joinToString(", ")
tvOrderRecipientAddress.text = fullAddress
}
}
} else {
tvOrderRecipientAddress.text = "-"
}
sell.orderItems?.let {
productAdapter.submitList(it.filterNotNull())
}
}
}
private fun formatDate(dateString: String): String {
return try { return try {
val inputFormat = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", Locale.getDefault()) val inputFormat = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", Locale.getDefault())
inputFormat.timeZone = TimeZone.getTimeZone("UTC") inputFormat.timeZone = TimeZone.getTimeZone("UTC")
val outputFormat = SimpleDateFormat("dd MMM yyyy, HH:mm", Locale("id", "ID"))
val date = inputFormat.parse(dateStr ?: "") val outputFormat = SimpleDateFormat("dd MMM yyyy, HH.mm", Locale("id", "ID"))
outputFormat.format(date!!) val date = inputFormat.parse(dateString)
date?.let {
val calendar = Calendar.getInstance()
calendar.time = it
outputFormat.format(calendar.time)
} ?: dateString
} catch (e: Exception) { } catch (e: Exception) {
"-" Log.e("DateFormatting", "Error formatting date: ${e.message}")
dateString
} }
} }
private fun formatPrice(price: String): String {
val priceDouble = price.toDoubleOrNull() ?: 0.0
return String.format(Locale("id", "ID"), "Rp%,.0f", priceDouble)
}
} }

View File

@ -5,12 +5,15 @@ import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import com.alya.ecommerce_serang.data.api.dto.OrderItemsItem
import com.alya.ecommerce_serang.data.api.dto.PaymentConfirmRequest import com.alya.ecommerce_serang.data.api.dto.PaymentConfirmRequest
import com.alya.ecommerce_serang.data.api.response.store.sells.Orders
import com.alya.ecommerce_serang.data.api.response.store.sells.OrdersItem import com.alya.ecommerce_serang.data.api.response.store.sells.OrdersItem
import com.alya.ecommerce_serang.data.api.response.store.sells.PaymentConfirmationResponse import com.alya.ecommerce_serang.data.api.response.store.sells.PaymentConfirmationResponse
import com.alya.ecommerce_serang.data.repository.Result import com.alya.ecommerce_serang.data.repository.Result
import com.alya.ecommerce_serang.data.repository.SellsRepository import com.alya.ecommerce_serang.data.repository.SellsRepository
import com.alya.ecommerce_serang.ui.order.address.ViewState import com.alya.ecommerce_serang.ui.order.address.ViewState
import com.alya.ecommerce_serang.ui.order.history.HistoryViewModel
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
class SellsViewModel(private val repository: SellsRepository) : ViewModel() { class SellsViewModel(private val repository: SellsRepository) : ViewModel() {
@ -22,9 +25,28 @@ class SellsViewModel(private val repository: SellsRepository) : ViewModel() {
private val _sells = MutableLiveData<ViewState<List<OrdersItem>>>() private val _sells = MutableLiveData<ViewState<List<OrdersItem>>>()
val sells: LiveData<ViewState<List<OrdersItem>>> = _sells val sells: LiveData<ViewState<List<OrdersItem>>> = _sells
private val _sellDetails = MutableLiveData<Orders?>()
val sellDetails: LiveData<Orders?> get() = _sellDetails
private val _confirmPaymentStore = MutableLiveData<Result<PaymentConfirmationResponse>>() private val _confirmPaymentStore = MutableLiveData<Result<PaymentConfirmationResponse>>()
val confirmPaymentStore: LiveData<Result<PaymentConfirmationResponse>> = _confirmPaymentStore val confirmPaymentStore: LiveData<Result<PaymentConfirmationResponse>> = _confirmPaymentStore
// LiveData untuk OrderItems
private val _orderItems = MutableLiveData<List<OrderItemsItem>?>()
val orderItems: LiveData<List<OrderItemsItem>?> get() = _orderItems
private val _isLoading = MutableLiveData<Boolean>()
val isLoading: LiveData<Boolean> = _isLoading
private val _message = MutableLiveData<String>()
val message: LiveData<String> = _message
private val _isSuccess = MutableLiveData<Boolean>()
val isSuccess: LiveData<Boolean> = _isSuccess
private val _error = MutableLiveData<String>()
val error: LiveData<String> get() = _error
fun getSellList(status: String) { fun getSellList(status: String) {
Log.d(TAG, "========== Starting getSellList ==========") Log.d(TAG, "========== Starting getSellList ==========")
Log.d(TAG, "Requested status: '$status'") Log.d(TAG, "Requested status: '$status'")
@ -48,15 +70,15 @@ class SellsViewModel(private val repository: SellsRepository) : ViewModel() {
// Log the entire result data structure // Log the entire result data structure
Log.d(TAG, "Raw result data: ${result.data}") Log.d(TAG, "Raw result data: ${result.data}")
Log.d(TAG, "Result data class: ${result.data?.javaClass?.simpleName}") Log.d(TAG, "Result data class: ${result.data.javaClass.simpleName}")
val orders = result.data.orders val orders = result.data.orders
Log.d(TAG, "Extracted orders list: $orders") Log.d(TAG, "Extracted orders list: $orders")
Log.d(TAG, "Orders list class: ${orders?.javaClass?.simpleName}") Log.d(TAG, "Orders list class: ${orders.javaClass.simpleName}")
Log.d(TAG, "Orders count: ${orders?.size ?: 0}") Log.d(TAG, "Orders count: ${orders.size}")
// Check if orders list is null or empty // Check if orders list is null or empty
if (orders == null) { if (false) {
Log.w(TAG, "⚠️ Orders list is NULL") Log.w(TAG, "⚠️ Orders list is NULL")
} else if (orders.isEmpty()) { } else if (orders.isEmpty()) {
Log.w(TAG, "⚠️ Orders list is EMPTY") Log.w(TAG, "⚠️ Orders list is EMPTY")
@ -67,17 +89,17 @@ class SellsViewModel(private val repository: SellsRepository) : ViewModel() {
orders.forEachIndexed { index, order -> orders.forEachIndexed { index, order ->
Log.d(TAG, "--- Order ${index + 1}/${orders.size} ---") Log.d(TAG, "--- Order ${index + 1}/${orders.size} ---")
Log.d(TAG, " Order object: $order") Log.d(TAG, " Order object: $order")
Log.d(TAG, " Order class: ${order?.javaClass?.simpleName}") Log.d(TAG, " Order class: ${order.javaClass.simpleName}")
Log.d(TAG, " - ID: ${order?.orderId}") Log.d(TAG, " - ID: ${order.orderId}")
Log.d(TAG, " - Status: '${order?.status}'") Log.d(TAG, " - Status: '${order.status}'")
Log.d(TAG, " - Customer: '${order?.username}'") Log.d(TAG, " - Customer: '${order.username}'")
Log.d(TAG, " - Total: ${order?.totalAmount}") Log.d(TAG, " - Total: ${order.totalAmount}")
Log.d(TAG, " - Items count: ${order?.orderItems?.size ?: 0}") Log.d(TAG, " - Items count: ${order.orderItems?.size ?: 0}")
Log.d(TAG, " - Created at: ${order?.createdAt}") Log.d(TAG, " - Created at: ${order.createdAt}")
Log.d(TAG, " - Updated at: ${order?.updatedAt}") Log.d(TAG, " - Updated at: ${order.updatedAt}")
// Log order items if available // Log order items if available
order?.orderItems?.let { items -> order.orderItems?.let { items ->
Log.d(TAG, " Order items:") Log.d(TAG, " Order items:")
items.forEachIndexed { itemIndex, item -> items.forEachIndexed { itemIndex, item ->
Log.d(TAG, " Item ${itemIndex + 1}: ${item?.productName} (Qty: ${item?.quantity})") Log.d(TAG, " Item ${itemIndex + 1}: ${item?.productName} (Qty: ${item?.quantity})")
@ -87,8 +109,8 @@ class SellsViewModel(private val repository: SellsRepository) : ViewModel() {
} }
// Set the ViewState to Success // Set the ViewState to Success
_sells.value = ViewState.Success(orders ?: emptyList()) _sells.value = ViewState.Success(orders)
Log.d(TAG, "✅ ViewState.Success set with ${orders?.size ?: 0} orders") Log.d(TAG, "✅ ViewState.Success set with ${orders.size} orders")
} }
is Result.Error -> { is Result.Error -> {
@ -125,6 +147,28 @@ class SellsViewModel(private val repository: SellsRepository) : ViewModel() {
Log.d(TAG, "========== getSellList method completed ==========") Log.d(TAG, "========== getSellList method completed ==========")
} }
fun getSellDetails(orderId: Int) {
Log.d(TAG, "========== Starting getSellDetails ==========")
Log.d(TAG, "Fetching details for order ID: $orderId")
_isLoading.value = true
viewModelScope.launch {
try {
val response = repository.getSellDetails(orderId)
if (response != null) {
_sellDetails.value = response.orders
_orderItems.value = response.orders?.orderItems?.filterNotNull()
} else {
_error.value = "Gagal memuat detail pesanan"
}
} catch (e: Exception) {
_error.value = "Terjadi kesalahan: ${e.message}"
Log.e(SellsViewModel.Companion.TAG, "Error fetching order details", e)
} finally {
_isLoading.value = false
}
}
}
fun updateOrderStatus(orderId: Int?, status: String) { fun updateOrderStatus(orderId: Int?, status: String) {
Log.d(TAG, "========== Starting updateOrderStatus ==========") Log.d(TAG, "========== Starting updateOrderStatus ==========")
Log.d(TAG, "Updating order status: orderId=$orderId, status='$status'") Log.d(TAG, "Updating order status: orderId=$orderId, status='$status'")

View File

@ -46,8 +46,7 @@
android:layout_marginStart="12dp" android:layout_marginStart="12dp"
app:layout_constraintStart_toStartOf="@id/shape_payment_order_title" app:layout_constraintStart_toStartOf="@id/shape_payment_order_title"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent">
android:gravity="center">
<TextView <TextView
android:id="@+id/tv_order_sells_title" android:id="@+id/tv_order_sells_title"
@ -70,7 +69,8 @@
android:textAlignment="textEnd" android:textAlignment="textEnd"
android:textColor="@color/blue_500" android:textColor="@color/blue_500"
android:clickable="true" android:clickable="true"
android:layout_marginEnd="16dp"/> android:layout_marginEnd="16dp"
android:visibility="gone"/>
</RelativeLayout> </RelativeLayout>
@ -377,19 +377,4 @@
</ScrollView> </ScrollView>
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="16dp"
android:orientation="horizontal"
android:gravity="bottom">
<Button
android:id="@+id/btn_confirm_payment"
style="@style/button.large.active.long"
android:text="Kirim Pesanan"
android:layout_alignParentEnd="true"/>
</RelativeLayout>
</LinearLayout> </LinearLayout>

View File

@ -2,6 +2,7 @@
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:fitsSystemWindows="true"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:background="@android:color/white" android:background="@android:color/white"

View File

@ -2,6 +2,7 @@
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:fitsSystemWindows="true"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:background="@android:color/white" android:background="@android:color/white"