Merge pull request #30

gracia
This commit is contained in:
Gracia Hotmauli
2025-06-08 17:37:11 +07:00
committed by GitHub
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
import com.alya.ecommerce_serang.data.api.dto.OrderItemsItem
import com.google.gson.annotations.SerializedName
data class OrderDetailResponse(
@ -17,7 +18,7 @@ data class Orders(
val receiptNum: Any? = null,
@field:SerializedName("payment_upload_at")
val paymentUploadAt: Any? = null,
val paymentUploadAt: String? = null,
@field:SerializedName("latitude")
val latitude: Any? = null,
@ -44,10 +45,10 @@ data class Orders(
val street: String? = null,
@field:SerializedName("cancel_date")
val cancelDate: Any? = null,
val cancelDate: String? = null,
@field:SerializedName("payment_evidence")
val paymentEvidence: Any? = null,
val paymentEvidence: String? = null,
@field:SerializedName("longitude")
val longitude: Any? = null,
@ -71,16 +72,16 @@ data class Orders(
val voucherName: Any? = null,
@field:SerializedName("payment_status")
val paymentStatus: Any? = null,
val paymentStatus: String? = null,
@field:SerializedName("address_id")
val addressId: Int? = null,
@field:SerializedName("payment_amount")
val paymentAmount: Any? = null,
val paymentAmount: String? = null,
@field:SerializedName("cancel_reason")
val cancelReason: Any? = null,
val cancelReason: String? = null,
@field:SerializedName("total_amount")
val totalAmount: String? = null,
@ -88,6 +89,9 @@ data class Orders(
@field:SerializedName("user_id")
val userId: Int? = null,
@field:SerializedName("phone")
val phone: String? = null,
@field:SerializedName("province_id")
val provinceId: Int? = null,
@ -103,6 +107,9 @@ data class Orders(
@field:SerializedName("pay_info_num")
val payInfoNum: String? = null,
@field:SerializedName("recipient")
val recipient: String? = null,
@field:SerializedName("shipment_price")
val shipmentPrice: String? = null,
@ -126,4 +133,4 @@ data class Orders(
@field:SerializedName("city_id")
val cityId: Int? = null
)
)

View File

@ -1,5 +1,6 @@
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
data class OrderListResponse(
@ -11,39 +12,6 @@ data class OrderListResponse(
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(
@field:SerializedName("receipt_num")

View File

@ -327,6 +327,11 @@ interface ApiService {
@Path("status") status: String
): 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")
suspend fun confirmOrder(
@Body confirmOrder : CompletedOrderRequest

View File

@ -2,6 +2,7 @@ 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.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
@ -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) {
try {
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.util.Log
import android.widget.Toast
import androidx.activity.viewModels
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.retrofit.ApiConfig
import com.alya.ecommerce_serang.data.repository.AddressRepository
import com.alya.ecommerce_serang.data.repository.SellsRepository
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.SessionManager
import com.alya.ecommerce_serang.utils.viewmodel.AddressViewModel
import com.alya.ecommerce_serang.utils.viewmodel.SellsViewModel
import com.google.gson.Gson
import java.text.SimpleDateFormat
@ -28,6 +33,7 @@ class DetailSellsActivity : AppCompatActivity() {
private val viewModel: SellsViewModel by viewModels {
BaseViewModelFactory {
sessionManager = SessionManager(this)
val apiService = ApiConfig.getApiService(sessionManager)
val sellsRepository = SellsRepository(apiService)
SellsViewModel(sellsRepository)
@ -51,48 +57,88 @@ class DetailSellsActivity : AppCompatActivity() {
layoutManager = LinearLayoutManager(this@DetailSellsActivity)
}
val sellsJson = intent.getStringExtra("sells")
val sellsJson = intent.getStringExtra("sells_data")
if (sellsJson != null) {
try {
sells = Gson().fromJson(sellsJson, Orders::class.java)
showOrderDetails()
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", "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 ->
when (sell.orderStatus) {
"pending", "unpaid" -> {
binding.tvOrderSellsTitle.text = "Pesanan Belum Dibayar"
}
"shipped" -> {
binding.tvOrderSellsTitle.text = "Pesanan Telah Dikirim"
}
"delivered" -> {
binding.tvOrderSellsTitle.text = "Pesanan Telah Sampai"
}
"completed" -> {
binding.tvOrderSellsTitle.text = "Pesanan Selesai"
}
"canceled" -> {
binding.tvOrderSellsTitle.text = "Pesanan Dibatalkan"
}
"pending", "unpaid" -> tvOrderSellsTitle.text = "Pesanan Belum Dibayar"
"shipped" -> tvOrderSellsTitle.text = "Pesanan Telah Dikirim"
"delivered" -> tvOrderSellsTitle.text = "Pesanan Telah Sampai"
"completed" -> tvOrderSellsTitle.text = "Pesanan Selesai"
"canceled" -> tvOrderSellsTitle.text = "Pesanan Dibatalkan"
else -> tvOrderSellsTitle.text = "Status Tidak Diketahui"
}
binding.tvOrderNumber.text = sell.orderId.toString()
binding.tvOrderCustomer.text = sell.username
binding.tvOrderDate.text = formatDate(sell.updatedAt.toString())
binding.tvOrderTotalProduct.text = "(${sell.orderItems?.size} Barang)"
binding.tvOrderSubtotal.text = formatPrice(sell.totalAmount.toString())
binding.tvOrderShipPrice.text = formatPrice(sell.shipmentPrice.toString())
binding.tvOrderPrice.text = formatPrice(sell.totalAmount.toString())
binding.tvOrderRecipient.text = sell.username
binding.tvOrderRecipientNum.text = sell.receiptNum.toString()
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@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 {
productAdapter.submitList(it.filterNotNull())
@ -106,13 +152,11 @@ class DetailSellsActivity : AppCompatActivity() {
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) {
@ -123,7 +167,6 @@ class DetailSellsActivity : AppCompatActivity() {
private fun formatPrice(price: String): String {
val priceDouble = price.toDoubleOrNull() ?: 0.0
val formattedPrice = String.format(Locale("id", "ID"), "Rp%,.0f", priceDouble)
return formattedPrice
return String.format(Locale("id", "ID"), "Rp%,.0f", priceDouble)
}
}

View File

@ -6,8 +6,9 @@ import android.view.ViewGroup
import android.widget.ImageView
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView
import com.alya.ecommerce_serang.BuildConfig.BASE_URL
import com.alya.ecommerce_serang.R
import com.alya.ecommerce_serang.data.api.response.store.sells.OrderItemsItem
import com.alya.ecommerce_serang.data.api.dto.OrderItemsItem
import com.bumptech.glide.Glide
import java.util.Locale
@ -33,9 +34,18 @@ class SellsProductAdapter : RecyclerView.Adapter<SellsProductAdapter.ProductView
tvQty.text = "${item.quantity} x "
tvPrice.text = formatPrice(item.price.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)
.load(item.productImage)
.load(fullImageUrl)
.placeholder(R.drawable.placeholder_image)
.error(R.drawable.placeholder_image)
.into(ivProduct)
}
}

View File

@ -5,6 +5,7 @@ import android.graphics.Color
import android.graphics.drawable.ColorDrawable
import android.graphics.drawable.Drawable
import android.os.Bundle
import android.util.Log
import android.view.View
import android.view.ViewGroup
import android.view.Window
@ -18,32 +19,38 @@ 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
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.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
import com.alya.ecommerce_serang.utils.viewmodel.SellsViewModel
import com.bumptech.glide.Glide
import com.bumptech.glide.load.engine.DiskCacheStrategy
import com.bumptech.glide.request.target.CustomTarget
import com.bumptech.glide.request.transition.Transition
import com.google.gson.Gson
import java.text.SimpleDateFormat
import java.util.Calendar
import java.util.Locale
import java.util.TimeZone
class DetailPaymentActivity : AppCompatActivity() {
private lateinit var binding: ActivityDetailPaymentBinding
private lateinit var sells: OrdersItem
private var sells: Orders? = null
private lateinit var productAdapter: SellsProductAdapter
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)
@ -55,59 +62,120 @@ class DetailPaymentActivity : AppCompatActivity() {
binding = ActivityDetailPaymentBinding.inflate(layoutInflater)
setContentView(binding.root)
sessionManager = SessionManager(this)
val orderJson = intent.getStringExtra("sells_data")
sells = Gson().fromJson(orderJson, OrdersItem::class.java)
binding.header.headerTitle.text = "Detail Pesanan"
binding.header.headerLeftIcon.setOnClickListener {
// onBackPressed()
onBackPressed()
finish()
}
setupRecyclerView()
bindOrderDetails()
setupPaymentEvidenceViewer()
val sellsJson = intent.getStringExtra("sells_data")
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 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
// sells.paymentEvidence
binding.btnConfirmPayment.setOnClickListener{
viewModel.confirmPayment(sells.orderId, "confirmed")
finish()
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 setupRecyclerView() {
productAdapter = SellsProductAdapter()
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() ?: "-"
binding.rvProductItems.apply {
layoutManager = LinearLayoutManager(this@DetailPaymentActivity)
adapter = productAdapter
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@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
productAdapter.submitList(sells.orderItems ?: emptyList())
private fun formatDate(dateString: 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(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() {
binding.tvOrderSellsDesc.setOnClickListener {
val paymentEvidence = sells.paymentEvidence
val paymentEvidence = sells?.paymentEvidence
if (!paymentEvidence.isNullOrEmpty()) {
showPaymentEvidenceDialog(paymentEvidence)
} else {
@ -154,7 +222,7 @@ class DetailPaymentActivity : AppCompatActivity() {
.error(R.drawable.placeholder_image)
.diskCacheStrategy(DiskCacheStrategy.ALL)
.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
imageView.setImageDrawable(resource)
}
@ -184,17 +252,4 @@ class DetailPaymentActivity : AppCompatActivity() {
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
import android.os.Bundle
import android.util.Log
import androidx.activity.viewModels
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.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.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 java.text.SimpleDateFormat
import java.util.Calendar
import java.util.Locale
import java.util.TimeZone
import kotlin.getValue
class DetailShipmentActivity : AppCompatActivity() {
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 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)
binding = ActivityDetailShipmentBinding.inflate(layoutInflater)
setContentView(binding.root)
val orderJson = intent.getStringExtra("sells_data")
sells = Gson().fromJson(orderJson, OrdersItem::class.java)
setupRecyclerView()
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
binding.header.headerTitle.text = "Detail Pesanan"
binding.header.headerLeftIcon.setOnClickListener {
onBackPressed()
finish()
}
// Submit the order items to the adapter
productAdapter.submitList(sells.orderItems ?: emptyList())
val sellsJson = intent.getStringExtra("sells_data")
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 {
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!!)
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)
}
}

View File

@ -5,12 +5,15 @@ import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
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.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.PaymentConfirmationResponse
import com.alya.ecommerce_serang.data.repository.Result
import com.alya.ecommerce_serang.data.repository.SellsRepository
import com.alya.ecommerce_serang.ui.order.address.ViewState
import com.alya.ecommerce_serang.ui.order.history.HistoryViewModel
import kotlinx.coroutines.launch
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>>>()
val sells: LiveData<ViewState<List<OrdersItem>>> = _sells
private val _sellDetails = MutableLiveData<Orders?>()
val sellDetails: LiveData<Orders?> get() = _sellDetails
private val _confirmPaymentStore = MutableLiveData<Result<PaymentConfirmationResponse>>()
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) {
Log.d(TAG, "========== Starting getSellList ==========")
Log.d(TAG, "Requested status: '$status'")
@ -48,15 +70,15 @@ class SellsViewModel(private val repository: SellsRepository) : ViewModel() {
// Log the entire result data structure
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
Log.d(TAG, "Extracted orders list: $orders")
Log.d(TAG, "Orders list class: ${orders?.javaClass?.simpleName}")
Log.d(TAG, "Orders count: ${orders?.size ?: 0}")
Log.d(TAG, "Orders list class: ${orders.javaClass.simpleName}")
Log.d(TAG, "Orders count: ${orders.size}")
// Check if orders list is null or empty
if (orders == null) {
if (false) {
Log.w(TAG, "⚠️ Orders list is NULL")
} else if (orders.isEmpty()) {
Log.w(TAG, "⚠️ Orders list is EMPTY")
@ -67,17 +89,17 @@ class SellsViewModel(private val repository: SellsRepository) : ViewModel() {
orders.forEachIndexed { index, order ->
Log.d(TAG, "--- Order ${index + 1}/${orders.size} ---")
Log.d(TAG, " Order object: $order")
Log.d(TAG, " Order class: ${order?.javaClass?.simpleName}")
Log.d(TAG, " - ID: ${order?.orderId}")
Log.d(TAG, " - Status: '${order?.status}'")
Log.d(TAG, " - Customer: '${order?.username}'")
Log.d(TAG, " - Total: ${order?.totalAmount}")
Log.d(TAG, " - Items count: ${order?.orderItems?.size ?: 0}")
Log.d(TAG, " - Created at: ${order?.createdAt}")
Log.d(TAG, " - Updated at: ${order?.updatedAt}")
Log.d(TAG, " Order class: ${order.javaClass.simpleName}")
Log.d(TAG, " - ID: ${order.orderId}")
Log.d(TAG, " - Status: '${order.status}'")
Log.d(TAG, " - Customer: '${order.username}'")
Log.d(TAG, " - Total: ${order.totalAmount}")
Log.d(TAG, " - Items count: ${order.orderItems?.size ?: 0}")
Log.d(TAG, " - Created at: ${order.createdAt}")
Log.d(TAG, " - Updated at: ${order.updatedAt}")
// Log order items if available
order?.orderItems?.let { items ->
order.orderItems?.let { items ->
Log.d(TAG, " Order items:")
items.forEachIndexed { itemIndex, item ->
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
_sells.value = ViewState.Success(orders ?: emptyList())
Log.d(TAG, "✅ ViewState.Success set with ${orders?.size ?: 0} orders")
_sells.value = ViewState.Success(orders)
Log.d(TAG, "✅ ViewState.Success set with ${orders.size} orders")
}
is Result.Error -> {
@ -125,6 +147,28 @@ class SellsViewModel(private val repository: SellsRepository) : ViewModel() {
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) {
Log.d(TAG, "========== Starting updateOrderStatus ==========")
Log.d(TAG, "Updating order status: orderId=$orderId, status='$status'")

View File

@ -46,8 +46,7 @@
android:layout_marginStart="12dp"
app:layout_constraintStart_toStartOf="@id/shape_payment_order_title"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
android:gravity="center">
app:layout_constraintTop_toTopOf="parent">
<TextView
android:id="@+id/tv_order_sells_title"
@ -70,7 +69,8 @@
android:textAlignment="textEnd"
android:textColor="@color/blue_500"
android:clickable="true"
android:layout_marginEnd="16dp"/>
android:layout_marginEnd="16dp"
android:visibility="gone"/>
</RelativeLayout>
@ -377,19 +377,4 @@
</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>

View File

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

View File

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