ai removed boilerplate and orgnized code even more
This commit is contained in:
@@ -0,0 +1,25 @@
|
|||||||
|
package com.android.trisolarispms.core.viewmodel
|
||||||
|
|
||||||
|
import androidx.lifecycle.ViewModel
|
||||||
|
import androidx.lifecycle.viewModelScope
|
||||||
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
import kotlinx.coroutines.flow.update
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
|
internal fun <S> ViewModel.launchRequest(
|
||||||
|
state: MutableStateFlow<S>,
|
||||||
|
setLoading: (S) -> S,
|
||||||
|
setError: (S, String) -> S,
|
||||||
|
defaultError: String,
|
||||||
|
block: suspend () -> Unit
|
||||||
|
) {
|
||||||
|
viewModelScope.launch {
|
||||||
|
state.update(setLoading)
|
||||||
|
try {
|
||||||
|
block()
|
||||||
|
} catch (e: Exception) {
|
||||||
|
val message = e.localizedMessage ?: defaultError
|
||||||
|
state.update { current -> setError(current, message) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,12 +1,11 @@
|
|||||||
package com.android.trisolarispms.ui.guest
|
package com.android.trisolarispms.ui.guest
|
||||||
|
|
||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
import androidx.lifecycle.viewModelScope
|
|
||||||
import com.android.trisolarispms.data.api.core.ApiClient
|
import com.android.trisolarispms.data.api.core.ApiClient
|
||||||
|
import com.android.trisolarispms.core.viewmodel.launchRequest
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.StateFlow
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
import kotlinx.coroutines.flow.update
|
import kotlinx.coroutines.flow.update
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
import okhttp3.MediaType.Companion.toMediaType
|
import okhttp3.MediaType.Companion.toMediaType
|
||||||
import okhttp3.MultipartBody
|
import okhttp3.MultipartBody
|
||||||
import okhttp3.RequestBody.Companion.toRequestBody
|
import okhttp3.RequestBody.Companion.toRequestBody
|
||||||
@@ -21,25 +20,25 @@ class GuestSignatureViewModel : ViewModel() {
|
|||||||
|
|
||||||
fun uploadSignature(propertyId: String, guestId: String, svg: String, onDone: () -> Unit) {
|
fun uploadSignature(propertyId: String, guestId: String, svg: String, onDone: () -> Unit) {
|
||||||
if (propertyId.isBlank() || guestId.isBlank()) return
|
if (propertyId.isBlank() || guestId.isBlank()) return
|
||||||
viewModelScope.launch {
|
launchRequest(
|
||||||
_state.update { it.copy(isLoading = true, error = null) }
|
state = _state,
|
||||||
try {
|
setLoading = { it.copy(isLoading = true, error = null) },
|
||||||
val api = ApiClient.create()
|
setError = { current, message -> current.copy(isLoading = false, error = message) },
|
||||||
val requestBody = svg.toRequestBody("image/svg+xml".toMediaType())
|
defaultError = "Upload failed"
|
||||||
val part = MultipartBody.Part.createFormData(
|
) {
|
||||||
name = "file",
|
val api = ApiClient.create()
|
||||||
filename = "signature.svg",
|
val requestBody = svg.toRequestBody("image/svg+xml".toMediaType())
|
||||||
body = requestBody
|
val part = MultipartBody.Part.createFormData(
|
||||||
)
|
name = "file",
|
||||||
val response = api.uploadSignature(propertyId = propertyId, guestId = guestId, file = part)
|
filename = "signature.svg",
|
||||||
if (response.isSuccessful) {
|
body = requestBody
|
||||||
_state.update { it.copy(isLoading = false, error = null) }
|
)
|
||||||
onDone()
|
val response = api.uploadSignature(propertyId = propertyId, guestId = guestId, file = part)
|
||||||
} else {
|
if (response.isSuccessful) {
|
||||||
_state.update { it.copy(isLoading = false, error = "Upload failed: ${response.code()}") }
|
_state.update { it.copy(isLoading = false, error = null) }
|
||||||
}
|
onDone()
|
||||||
} catch (e: Exception) {
|
} else {
|
||||||
_state.update { it.copy(isLoading = false, error = e.localizedMessage ?: "Upload failed") }
|
_state.update { it.copy(isLoading = false, error = "Upload failed: ${response.code()}") }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,15 +1,14 @@
|
|||||||
package com.android.trisolarispms.ui.home
|
package com.android.trisolarispms.ui.home
|
||||||
|
|
||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
import androidx.lifecycle.viewModelScope
|
|
||||||
import com.android.trisolarispms.core.auth.Role
|
import com.android.trisolarispms.core.auth.Role
|
||||||
import com.android.trisolarispms.core.auth.toRoles
|
import com.android.trisolarispms.core.auth.toRoles
|
||||||
import com.android.trisolarispms.data.api.core.ApiClient
|
import com.android.trisolarispms.data.api.core.ApiClient
|
||||||
import com.android.trisolarispms.data.api.model.PropertyAccessCodeJoinRequest
|
import com.android.trisolarispms.data.api.model.PropertyAccessCodeJoinRequest
|
||||||
|
import com.android.trisolarispms.core.viewmodel.launchRequest
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.StateFlow
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
import kotlinx.coroutines.flow.update
|
import kotlinx.coroutines.flow.update
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
|
|
||||||
data class HomeJoinPropertyState(
|
data class HomeJoinPropertyState(
|
||||||
val isLoading: Boolean = false,
|
val isLoading: Boolean = false,
|
||||||
@@ -34,31 +33,31 @@ class HomeJoinPropertyViewModel : ViewModel() {
|
|||||||
_state.update { it.copy(error = "Code must be 6 digits", message = null) }
|
_state.update { it.copy(error = "Code must be 6 digits", message = null) }
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
viewModelScope.launch {
|
launchRequest(
|
||||||
_state.update { it.copy(isLoading = true, error = null, message = null) }
|
state = _state,
|
||||||
try {
|
setLoading = { it.copy(isLoading = true, error = null, message = null) },
|
||||||
val response = ApiClient.create().joinAccessCode(
|
setError = { current, message -> current.copy(isLoading = false, error = message, message = null) },
|
||||||
PropertyAccessCodeJoinRequest(
|
defaultError = "Join failed"
|
||||||
propertyId = trimmedPropertyId,
|
) {
|
||||||
code = digits
|
val response = ApiClient.create().joinAccessCode(
|
||||||
)
|
PropertyAccessCodeJoinRequest(
|
||||||
|
propertyId = trimmedPropertyId,
|
||||||
|
code = digits
|
||||||
)
|
)
|
||||||
val body = response.body()
|
)
|
||||||
if (response.isSuccessful && body != null) {
|
val body = response.body()
|
||||||
_state.update {
|
if (response.isSuccessful && body != null) {
|
||||||
it.copy(
|
_state.update {
|
||||||
isLoading = false,
|
it.copy(
|
||||||
message = "Joined property",
|
isLoading = false,
|
||||||
error = null,
|
message = "Joined property",
|
||||||
joinedPropertyId = body.propertyId ?: trimmedPropertyId,
|
error = null,
|
||||||
joinedRoles = body.roles.toRoles()
|
joinedPropertyId = body.propertyId ?: trimmedPropertyId,
|
||||||
)
|
joinedRoles = body.roles.toRoles()
|
||||||
}
|
)
|
||||||
} else {
|
|
||||||
_state.update { it.copy(isLoading = false, error = "Join failed: ${response.code()}") }
|
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} else {
|
||||||
_state.update { it.copy(isLoading = false, error = e.localizedMessage ?: "Join failed") }
|
_state.update { it.copy(isLoading = false, error = "Join failed: ${response.code()}") }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import com.android.trisolarispms.ui.auth.AuthViewModel
|
|||||||
import com.android.trisolarispms.ui.razorpay.RazorpayQrScreen
|
import com.android.trisolarispms.ui.razorpay.RazorpayQrScreen
|
||||||
import com.android.trisolarispms.ui.razorpay.RazorpaySettingsScreen
|
import com.android.trisolarispms.ui.razorpay.RazorpaySettingsScreen
|
||||||
import com.android.trisolarispms.ui.roomstay.ActiveRoomStaysScreen
|
import com.android.trisolarispms.ui.roomstay.ActiveRoomStaysScreen
|
||||||
|
import com.android.trisolarispms.ui.roomstay.ManageRoomStaySelection
|
||||||
import com.android.trisolarispms.ui.roomstay.ManageRoomStayRatesScreen
|
import com.android.trisolarispms.ui.roomstay.ManageRoomStayRatesScreen
|
||||||
import com.android.trisolarispms.ui.roomstay.ManageRoomStaySelectScreen
|
import com.android.trisolarispms.ui.roomstay.ManageRoomStaySelectScreen
|
||||||
|
|
||||||
@@ -18,23 +19,56 @@ internal fun renderStayFlowRoutes(
|
|||||||
authViewModel: AuthViewModel,
|
authViewModel: AuthViewModel,
|
||||||
authz: AuthzPolicy
|
authz: AuthzPolicy
|
||||||
): Boolean {
|
): Boolean {
|
||||||
|
@Composable
|
||||||
|
fun renderManageRoomStaySelectRoute(
|
||||||
|
propertyId: String,
|
||||||
|
fromAt: String,
|
||||||
|
toAt: String?,
|
||||||
|
onNext: (List<ManageRoomStaySelection>) -> Unit
|
||||||
|
) {
|
||||||
|
ManageRoomStaySelectScreen(
|
||||||
|
propertyId = propertyId,
|
||||||
|
bookingFromAt = fromAt,
|
||||||
|
bookingToAt = toAt,
|
||||||
|
onBack = { refs.openActiveRoomStays(propertyId) },
|
||||||
|
onNext = { rooms ->
|
||||||
|
refs.selectedManageRooms.value = rooms
|
||||||
|
onNext(rooms)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun renderManageRoomStayRatesRoute(
|
||||||
|
propertyId: String,
|
||||||
|
bookingId: String,
|
||||||
|
fromAt: String,
|
||||||
|
toAt: String?,
|
||||||
|
onBack: () -> Unit,
|
||||||
|
onDone: () -> Unit
|
||||||
|
) {
|
||||||
|
ManageRoomStayRatesScreen(
|
||||||
|
propertyId = propertyId,
|
||||||
|
bookingId = bookingId,
|
||||||
|
checkInAt = fromAt,
|
||||||
|
checkOutAt = toAt,
|
||||||
|
selectedRooms = refs.selectedManageRooms.value,
|
||||||
|
onBack = onBack,
|
||||||
|
onDone = onDone
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
when (val currentRoute = refs.currentRoute) {
|
when (val currentRoute = refs.currentRoute) {
|
||||||
is AppRoute.ActiveRoomStays -> ActiveRoomStaysScreen(
|
is AppRoute.ActiveRoomStays -> ActiveRoomStaysScreen(
|
||||||
propertyId = currentRoute.propertyId,
|
propertyId = currentRoute.propertyId,
|
||||||
propertyName = currentRoute.propertyName,
|
propertyName = currentRoute.propertyName,
|
||||||
onBack = {
|
onBack = {
|
||||||
val blockBack = authz.shouldBlockBackToHome(
|
val blockBack = shouldBlockHomeBack(authz, state, currentRoute.propertyId)
|
||||||
propertyId = currentRoute.propertyId,
|
|
||||||
propertyCount = state.propertyRoles.size
|
|
||||||
)
|
|
||||||
if (!blockBack) {
|
if (!blockBack) {
|
||||||
refs.route.value = AppRoute.Home
|
refs.route.value = AppRoute.Home
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
showBack = !authz.shouldBlockBackToHome(
|
showBack = !shouldBlockHomeBack(authz, state, currentRoute.propertyId),
|
||||||
propertyId = currentRoute.propertyId,
|
|
||||||
propertyCount = state.propertyRoles.size
|
|
||||||
),
|
|
||||||
onViewRooms = { refs.route.value = AppRoute.Rooms(currentRoute.propertyId) },
|
onViewRooms = { refs.route.value = AppRoute.Rooms(currentRoute.propertyId) },
|
||||||
onCreateBooking = { refs.route.value = AppRoute.CreateBooking(currentRoute.propertyId) },
|
onCreateBooking = { refs.route.value = AppRoute.CreateBooking(currentRoute.propertyId) },
|
||||||
canCreateBooking = authz.canCreateBookingFor(currentRoute.propertyId),
|
canCreateBooking = authz.canCreateBookingFor(currentRoute.propertyId),
|
||||||
@@ -91,62 +125,54 @@ internal fun renderStayFlowRoutes(
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
is AppRoute.ManageRoomStaySelect -> ManageRoomStaySelectScreen(
|
is AppRoute.ManageRoomStaySelect -> renderManageRoomStaySelectRoute(
|
||||||
propertyId = currentRoute.propertyId,
|
propertyId = currentRoute.propertyId,
|
||||||
bookingFromAt = currentRoute.fromAt,
|
fromAt = currentRoute.fromAt,
|
||||||
bookingToAt = currentRoute.toAt,
|
toAt = currentRoute.toAt
|
||||||
onBack = { refs.openActiveRoomStays(currentRoute.propertyId) },
|
) {
|
||||||
onNext = { rooms ->
|
refs.route.value = AppRoute.ManageRoomStayRates(
|
||||||
refs.selectedManageRooms.value = rooms
|
propertyId = currentRoute.propertyId,
|
||||||
refs.route.value = AppRoute.ManageRoomStayRates(
|
bookingId = currentRoute.bookingId,
|
||||||
propertyId = currentRoute.propertyId,
|
fromAt = currentRoute.fromAt,
|
||||||
bookingId = currentRoute.bookingId,
|
toAt = currentRoute.toAt
|
||||||
fromAt = currentRoute.fromAt,
|
)
|
||||||
toAt = currentRoute.toAt
|
}
|
||||||
)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
is AppRoute.ManageRoomStaySelectFromBooking -> ManageRoomStaySelectScreen(
|
is AppRoute.ManageRoomStaySelectFromBooking -> renderManageRoomStaySelectRoute(
|
||||||
propertyId = currentRoute.propertyId,
|
propertyId = currentRoute.propertyId,
|
||||||
bookingFromAt = currentRoute.fromAt,
|
fromAt = currentRoute.fromAt,
|
||||||
bookingToAt = currentRoute.toAt,
|
toAt = currentRoute.toAt
|
||||||
onBack = { refs.openActiveRoomStays(currentRoute.propertyId) },
|
) {
|
||||||
onNext = { rooms ->
|
refs.route.value = AppRoute.ManageRoomStayRatesFromBooking(
|
||||||
refs.selectedManageRooms.value = rooms
|
propertyId = currentRoute.propertyId,
|
||||||
refs.route.value = AppRoute.ManageRoomStayRatesFromBooking(
|
bookingId = currentRoute.bookingId,
|
||||||
propertyId = currentRoute.propertyId,
|
guestId = currentRoute.guestId,
|
||||||
bookingId = currentRoute.bookingId,
|
fromAt = currentRoute.fromAt,
|
||||||
guestId = currentRoute.guestId,
|
toAt = currentRoute.toAt
|
||||||
fromAt = currentRoute.fromAt,
|
)
|
||||||
toAt = currentRoute.toAt
|
}
|
||||||
)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
is AppRoute.ManageRoomStayRates -> ManageRoomStayRatesScreen(
|
is AppRoute.ManageRoomStayRates -> renderManageRoomStayRatesRoute(
|
||||||
propertyId = currentRoute.propertyId,
|
propertyId = currentRoute.propertyId,
|
||||||
bookingId = currentRoute.bookingId,
|
bookingId = currentRoute.bookingId,
|
||||||
checkInAt = currentRoute.fromAt,
|
fromAt = currentRoute.fromAt,
|
||||||
checkOutAt = currentRoute.toAt,
|
toAt = currentRoute.toAt,
|
||||||
selectedRooms = refs.selectedManageRooms.value,
|
|
||||||
onBack = {
|
onBack = {
|
||||||
refs.route.value = AppRoute.ManageRoomStaySelect(
|
refs.route.value = AppRoute.ManageRoomStaySelect(
|
||||||
currentRoute.propertyId,
|
propertyId = currentRoute.propertyId,
|
||||||
currentRoute.bookingId,
|
bookingId = currentRoute.bookingId,
|
||||||
currentRoute.fromAt,
|
fromAt = currentRoute.fromAt,
|
||||||
currentRoute.toAt
|
toAt = currentRoute.toAt
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
onDone = { refs.openActiveRoomStays(currentRoute.propertyId) }
|
onDone = { refs.openActiveRoomStays(currentRoute.propertyId) }
|
||||||
)
|
)
|
||||||
|
|
||||||
is AppRoute.ManageRoomStayRatesFromBooking -> ManageRoomStayRatesScreen(
|
is AppRoute.ManageRoomStayRatesFromBooking -> renderManageRoomStayRatesRoute(
|
||||||
propertyId = currentRoute.propertyId,
|
propertyId = currentRoute.propertyId,
|
||||||
bookingId = currentRoute.bookingId,
|
bookingId = currentRoute.bookingId,
|
||||||
checkInAt = currentRoute.fromAt,
|
fromAt = currentRoute.fromAt,
|
||||||
checkOutAt = currentRoute.toAt,
|
toAt = currentRoute.toAt,
|
||||||
selectedRooms = refs.selectedManageRooms.value,
|
|
||||||
onBack = {
|
onBack = {
|
||||||
refs.route.value = AppRoute.ManageRoomStaySelectFromBooking(
|
refs.route.value = AppRoute.ManageRoomStaySelectFromBooking(
|
||||||
currentRoute.propertyId,
|
currentRoute.propertyId,
|
||||||
@@ -169,3 +195,9 @@ internal fun renderStayFlowRoutes(
|
|||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun shouldBlockHomeBack(authz: AuthzPolicy, state: AuthUiState, propertyId: String): Boolean =
|
||||||
|
authz.shouldBlockBackToHome(
|
||||||
|
propertyId = propertyId,
|
||||||
|
propertyCount = state.propertyRoles.size
|
||||||
|
)
|
||||||
|
|||||||
@@ -3,49 +3,33 @@ package com.android.trisolarispms.ui.payment
|
|||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
import com.android.trisolarispms.data.api.core.ApiClient
|
import com.android.trisolarispms.data.api.core.ApiClient
|
||||||
|
import com.android.trisolarispms.data.api.core.ApiService
|
||||||
import com.android.trisolarispms.data.api.model.RazorpayRefundRequest
|
import com.android.trisolarispms.data.api.model.RazorpayRefundRequest
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.StateFlow
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
import kotlinx.coroutines.flow.update
|
import kotlinx.coroutines.flow.update
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
import retrofit2.Response
|
||||||
|
|
||||||
class BookingPaymentsViewModel : ViewModel() {
|
class BookingPaymentsViewModel : ViewModel() {
|
||||||
private val _state = MutableStateFlow(BookingPaymentsState())
|
private val _state = MutableStateFlow(BookingPaymentsState())
|
||||||
val state: StateFlow<BookingPaymentsState> = _state
|
val state: StateFlow<BookingPaymentsState> = _state
|
||||||
|
|
||||||
fun load(propertyId: String, bookingId: String) {
|
fun load(propertyId: String, bookingId: String) {
|
||||||
viewModelScope.launch {
|
runPaymentAction(defaultError = "Load failed") { api ->
|
||||||
_state.update { it.copy(isLoading = true, error = null, message = null) }
|
val response = api.listPayments(propertyId, bookingId)
|
||||||
try {
|
val body = response.body()
|
||||||
val api = ApiClient.create()
|
if (response.isSuccessful && body != null) {
|
||||||
val response = api.listPayments(propertyId, bookingId)
|
|
||||||
val body = response.body()
|
|
||||||
if (response.isSuccessful && body != null) {
|
|
||||||
_state.update {
|
|
||||||
it.copy(
|
|
||||||
isLoading = false,
|
|
||||||
payments = body,
|
|
||||||
error = null,
|
|
||||||
message = null
|
|
||||||
)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
_state.update {
|
|
||||||
it.copy(
|
|
||||||
isLoading = false,
|
|
||||||
error = "Load failed: ${response.code()}",
|
|
||||||
message = null
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (e: Exception) {
|
|
||||||
_state.update {
|
_state.update {
|
||||||
it.copy(
|
it.copy(
|
||||||
isLoading = false,
|
isLoading = false,
|
||||||
error = e.localizedMessage ?: "Load failed",
|
payments = body,
|
||||||
|
error = null,
|
||||||
message = null
|
message = null
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
setActionFailure("Load", response)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -55,82 +39,46 @@ class BookingPaymentsViewModel : ViewModel() {
|
|||||||
_state.update { it.copy(error = "Amount must be greater than 0", message = null) }
|
_state.update { it.copy(error = "Amount must be greater than 0", message = null) }
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
viewModelScope.launch {
|
runPaymentAction(defaultError = "Create failed") { api ->
|
||||||
_state.update { it.copy(isLoading = true, error = null, message = null) }
|
val response = api.createPayment(
|
||||||
try {
|
propertyId = propertyId,
|
||||||
val api = ApiClient.create()
|
bookingId = bookingId,
|
||||||
val response = api.createPayment(
|
body = com.android.trisolarispms.data.api.model.PaymentCreateRequest(amount = amount)
|
||||||
propertyId = propertyId,
|
)
|
||||||
bookingId = bookingId,
|
val body = response.body()
|
||||||
body = com.android.trisolarispms.data.api.model.PaymentCreateRequest(amount = amount)
|
if (response.isSuccessful && body != null) {
|
||||||
)
|
_state.update { current ->
|
||||||
val body = response.body()
|
current.copy(
|
||||||
if (response.isSuccessful && body != null) {
|
|
||||||
_state.update { current ->
|
|
||||||
current.copy(
|
|
||||||
isLoading = false,
|
|
||||||
payments = listOf(body) + current.payments,
|
|
||||||
error = null,
|
|
||||||
message = "Cash payment added"
|
|
||||||
)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
_state.update {
|
|
||||||
it.copy(
|
|
||||||
isLoading = false,
|
|
||||||
error = "Create failed: ${response.code()}",
|
|
||||||
message = null
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (e: Exception) {
|
|
||||||
_state.update {
|
|
||||||
it.copy(
|
|
||||||
isLoading = false,
|
isLoading = false,
|
||||||
error = e.localizedMessage ?: "Create failed",
|
payments = listOf(body) + current.payments,
|
||||||
message = null
|
error = null,
|
||||||
|
message = "Cash payment added"
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
setActionFailure("Create", response)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun deleteCashPayment(propertyId: String, bookingId: String, paymentId: String) {
|
fun deleteCashPayment(propertyId: String, bookingId: String, paymentId: String) {
|
||||||
viewModelScope.launch {
|
runPaymentAction(defaultError = "Delete failed") { api ->
|
||||||
_state.update { it.copy(isLoading = true, error = null, message = null) }
|
val response = api.deletePayment(
|
||||||
try {
|
propertyId = propertyId,
|
||||||
val api = ApiClient.create()
|
bookingId = bookingId,
|
||||||
val response = api.deletePayment(
|
paymentId = paymentId
|
||||||
propertyId = propertyId,
|
)
|
||||||
bookingId = bookingId,
|
if (response.isSuccessful) {
|
||||||
paymentId = paymentId
|
_state.update { current ->
|
||||||
)
|
current.copy(
|
||||||
if (response.isSuccessful) {
|
|
||||||
_state.update { current ->
|
|
||||||
current.copy(
|
|
||||||
isLoading = false,
|
|
||||||
payments = current.payments.filterNot { it.id == paymentId },
|
|
||||||
error = null,
|
|
||||||
message = "Cash payment deleted"
|
|
||||||
)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
_state.update {
|
|
||||||
it.copy(
|
|
||||||
isLoading = false,
|
|
||||||
error = "Delete failed: ${response.code()}",
|
|
||||||
message = null
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (e: Exception) {
|
|
||||||
_state.update {
|
|
||||||
it.copy(
|
|
||||||
isLoading = false,
|
isLoading = false,
|
||||||
error = e.localizedMessage ?: "Delete failed",
|
payments = current.payments.filterNot { it.id == paymentId },
|
||||||
message = null
|
error = null,
|
||||||
|
message = "Cash payment deleted"
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
setActionFailure("Delete", response)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -151,48 +99,60 @@ class BookingPaymentsViewModel : ViewModel() {
|
|||||||
_state.update { it.copy(error = "Missing payment ID", message = null) }
|
_state.update { it.copy(error = "Missing payment ID", message = null) }
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
runPaymentAction(defaultError = "Refund failed") { api ->
|
||||||
|
val response = api.refundRazorpayPayment(
|
||||||
|
propertyId = propertyId,
|
||||||
|
bookingId = bookingId,
|
||||||
|
body = RazorpayRefundRequest(
|
||||||
|
paymentId = paymentId,
|
||||||
|
razorpayPaymentId = razorpayPaymentId,
|
||||||
|
amount = amount,
|
||||||
|
notes = notes?.takeIf { it.isNotBlank() }
|
||||||
|
)
|
||||||
|
)
|
||||||
|
val body = response.body()
|
||||||
|
if (response.isSuccessful && body != null) {
|
||||||
|
_state.update {
|
||||||
|
it.copy(
|
||||||
|
isLoading = false,
|
||||||
|
error = null,
|
||||||
|
message = "Refund processed"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
load(propertyId, bookingId)
|
||||||
|
} else {
|
||||||
|
setActionFailure("Refund", response)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun runPaymentAction(
|
||||||
|
defaultError: String,
|
||||||
|
block: suspend (ApiService) -> Unit
|
||||||
|
) {
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
_state.update { it.copy(isLoading = true, error = null, message = null) }
|
_state.update { it.copy(isLoading = true, error = null, message = null) }
|
||||||
try {
|
try {
|
||||||
val api = ApiClient.create()
|
block(ApiClient.create())
|
||||||
val response = api.refundRazorpayPayment(
|
|
||||||
propertyId = propertyId,
|
|
||||||
bookingId = bookingId,
|
|
||||||
body = RazorpayRefundRequest(
|
|
||||||
paymentId = paymentId,
|
|
||||||
razorpayPaymentId = razorpayPaymentId,
|
|
||||||
amount = amount,
|
|
||||||
notes = notes?.takeIf { it.isNotBlank() }
|
|
||||||
)
|
|
||||||
)
|
|
||||||
val body = response.body()
|
|
||||||
if (response.isSuccessful && body != null) {
|
|
||||||
_state.update {
|
|
||||||
it.copy(
|
|
||||||
isLoading = false,
|
|
||||||
error = null,
|
|
||||||
message = "Refund processed"
|
|
||||||
)
|
|
||||||
}
|
|
||||||
load(propertyId, bookingId)
|
|
||||||
} else {
|
|
||||||
_state.update {
|
|
||||||
it.copy(
|
|
||||||
isLoading = false,
|
|
||||||
error = "Refund failed: ${response.code()}",
|
|
||||||
message = null
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
_state.update {
|
_state.update {
|
||||||
it.copy(
|
it.copy(
|
||||||
isLoading = false,
|
isLoading = false,
|
||||||
error = e.localizedMessage ?: "Refund failed",
|
error = e.localizedMessage ?: defaultError,
|
||||||
message = null
|
message = null
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun setActionFailure(action: String, response: Response<*>) {
|
||||||
|
_state.update {
|
||||||
|
it.copy(
|
||||||
|
isLoading = false,
|
||||||
|
error = "$action failed: ${response.code()}",
|
||||||
|
message = null
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,13 +1,12 @@
|
|||||||
package com.android.trisolarispms.ui.property
|
package com.android.trisolarispms.ui.property
|
||||||
|
|
||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
import androidx.lifecycle.viewModelScope
|
|
||||||
import com.android.trisolarispms.data.api.core.ApiClient
|
import com.android.trisolarispms.data.api.core.ApiClient
|
||||||
import com.android.trisolarispms.data.api.model.PropertyCreateRequest
|
import com.android.trisolarispms.data.api.model.PropertyCreateRequest
|
||||||
|
import com.android.trisolarispms.core.viewmodel.launchRequest
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.StateFlow
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
import kotlinx.coroutines.flow.update
|
import kotlinx.coroutines.flow.update
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
|
|
||||||
class AddPropertyViewModel : ViewModel() {
|
class AddPropertyViewModel : ViewModel() {
|
||||||
private val _state = MutableStateFlow(AddPropertyState())
|
private val _state = MutableStateFlow(AddPropertyState())
|
||||||
@@ -56,31 +55,31 @@ class AddPropertyViewModel : ViewModel() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
viewModelScope.launch {
|
launchRequest(
|
||||||
_state.update { it.copy(isLoading = true, error = null) }
|
state = _state,
|
||||||
try {
|
setLoading = { it.copy(isLoading = true, error = null) },
|
||||||
val api = ApiClient.create()
|
setError = { current, message -> current.copy(isLoading = false, error = message) },
|
||||||
val body = PropertyCreateRequest(
|
defaultError = "Create failed"
|
||||||
code = null,
|
) {
|
||||||
name = name,
|
val api = ApiClient.create()
|
||||||
addressText = state.value.addressText.takeIf { it.isNotBlank() },
|
val body = PropertyCreateRequest(
|
||||||
timezone = state.value.timezone.takeIf { it.isNotBlank() },
|
code = null,
|
||||||
currency = state.value.currency.takeIf { it.isNotBlank() }
|
name = name,
|
||||||
)
|
addressText = state.value.addressText.takeIf { it.isNotBlank() },
|
||||||
val response = api.createProperty(body)
|
timezone = state.value.timezone.takeIf { it.isNotBlank() },
|
||||||
if (response.isSuccessful) {
|
currency = state.value.currency.takeIf { it.isNotBlank() }
|
||||||
_state.update {
|
)
|
||||||
it.copy(
|
val response = api.createProperty(body)
|
||||||
isLoading = false,
|
if (response.isSuccessful) {
|
||||||
createdPropertyId = response.body()?.id,
|
_state.update {
|
||||||
error = null
|
it.copy(
|
||||||
)
|
isLoading = false,
|
||||||
}
|
createdPropertyId = response.body()?.id,
|
||||||
} else {
|
error = null
|
||||||
_state.update { it.copy(isLoading = false, error = "Create failed: ${response.code()}") }
|
)
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} else {
|
||||||
_state.update { it.copy(isLoading = false, error = e.localizedMessage ?: "Create failed") }
|
_state.update { it.copy(isLoading = false, error = "Create failed: ${response.code()}") }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,36 +1,35 @@
|
|||||||
package com.android.trisolarispms.ui.property
|
package com.android.trisolarispms.ui.property
|
||||||
|
|
||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
import androidx.lifecycle.viewModelScope
|
|
||||||
import com.android.trisolarispms.data.api.core.ApiClient
|
import com.android.trisolarispms.data.api.core.ApiClient
|
||||||
|
import com.android.trisolarispms.core.viewmodel.launchRequest
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.StateFlow
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
import kotlinx.coroutines.flow.update
|
import kotlinx.coroutines.flow.update
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
|
|
||||||
class PropertyListViewModel : ViewModel() {
|
class PropertyListViewModel : ViewModel() {
|
||||||
private val _state = MutableStateFlow(PropertyListState())
|
private val _state = MutableStateFlow(PropertyListState())
|
||||||
val state: StateFlow<PropertyListState> = _state
|
val state: StateFlow<PropertyListState> = _state
|
||||||
|
|
||||||
fun refresh() {
|
fun refresh() {
|
||||||
viewModelScope.launch {
|
launchRequest(
|
||||||
_state.update { it.copy(isLoading = true, error = null) }
|
state = _state,
|
||||||
try {
|
setLoading = { it.copy(isLoading = true, error = null) },
|
||||||
val api = ApiClient.create()
|
setError = { current, message -> current.copy(isLoading = false, error = message) },
|
||||||
val response = api.listProperties()
|
defaultError = "Load failed"
|
||||||
if (response.isSuccessful) {
|
) {
|
||||||
_state.update {
|
val api = ApiClient.create()
|
||||||
it.copy(
|
val response = api.listProperties()
|
||||||
isLoading = false,
|
if (response.isSuccessful) {
|
||||||
properties = response.body().orEmpty(),
|
_state.update {
|
||||||
error = null
|
it.copy(
|
||||||
)
|
isLoading = false,
|
||||||
}
|
properties = response.body().orEmpty(),
|
||||||
} else {
|
error = null
|
||||||
_state.update { it.copy(isLoading = false, error = "Load failed: ${response.code()}") }
|
)
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} else {
|
||||||
_state.update { it.copy(isLoading = false, error = e.localizedMessage ?: "Load failed") }
|
_state.update { it.copy(isLoading = false, error = "Load failed: ${response.code()}") }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,12 +3,14 @@ package com.android.trisolarispms.ui.room
|
|||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
import com.android.trisolarispms.data.api.core.ApiClient
|
import com.android.trisolarispms.data.api.core.ApiClient
|
||||||
|
import com.android.trisolarispms.data.api.core.ApiService
|
||||||
import com.android.trisolarispms.data.api.model.RoomCreateRequest
|
import com.android.trisolarispms.data.api.model.RoomCreateRequest
|
||||||
import com.android.trisolarispms.data.api.model.RoomUpdateRequest
|
import com.android.trisolarispms.data.api.model.RoomUpdateRequest
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.StateFlow
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
import kotlinx.coroutines.flow.update
|
import kotlinx.coroutines.flow.update
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
import retrofit2.Response
|
||||||
|
|
||||||
class RoomFormViewModel : ViewModel() {
|
class RoomFormViewModel : ViewModel() {
|
||||||
private val _state = MutableStateFlow(RoomFormState())
|
private val _state = MutableStateFlow(RoomFormState())
|
||||||
@@ -58,70 +60,22 @@ class RoomFormViewModel : ViewModel() {
|
|||||||
fun onNotesChange(value: String) = _state.update { it.copy(notes = value, error = null) }
|
fun onNotesChange(value: String) = _state.update { it.copy(notes = value, error = null) }
|
||||||
|
|
||||||
fun submitCreate(propertyId: String, onDone: () -> Unit) {
|
fun submitCreate(propertyId: String, onDone: () -> Unit) {
|
||||||
val roomNumberText = state.value.roomNumber.trim()
|
val input = readValidatedInput() ?: return
|
||||||
val roomTypeCode = state.value.roomTypeCode.trim()
|
submitMutation(
|
||||||
val roomNumber = roomNumberText.toIntOrNull()
|
action = "Create",
|
||||||
if (roomNumber == null || roomTypeCode.isBlank()) {
|
onDone = onDone
|
||||||
_state.update { it.copy(error = "Room number must be a number and room type is required") }
|
) { api ->
|
||||||
return
|
api.createRoom(propertyId, input.toCreateRequest())
|
||||||
}
|
|
||||||
viewModelScope.launch {
|
|
||||||
_state.update { it.copy(isLoading = true, error = null) }
|
|
||||||
try {
|
|
||||||
val api = ApiClient.create()
|
|
||||||
val body = RoomCreateRequest(
|
|
||||||
roomNumber = roomNumber,
|
|
||||||
floor = state.value.floor.trim().toIntOrNull(),
|
|
||||||
roomTypeCode = roomTypeCode,
|
|
||||||
hasNfc = state.value.hasNfc,
|
|
||||||
active = state.value.active,
|
|
||||||
maintenance = state.value.maintenance,
|
|
||||||
notes = state.value.notes.takeIf { it.isNotBlank() }
|
|
||||||
)
|
|
||||||
val response = api.createRoom(propertyId, body)
|
|
||||||
if (response.isSuccessful) {
|
|
||||||
_state.update { it.copy(isLoading = false, success = true) }
|
|
||||||
onDone()
|
|
||||||
} else {
|
|
||||||
_state.update { it.copy(isLoading = false, error = "Create failed: ${response.code()}") }
|
|
||||||
}
|
|
||||||
} catch (e: Exception) {
|
|
||||||
_state.update { it.copy(isLoading = false, error = e.localizedMessage ?: "Create failed") }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun submitUpdate(propertyId: String, roomId: String, onDone: () -> Unit) {
|
fun submitUpdate(propertyId: String, roomId: String, onDone: () -> Unit) {
|
||||||
val roomNumberText = state.value.roomNumber.trim()
|
val input = readValidatedInput() ?: return
|
||||||
val roomTypeCode = state.value.roomTypeCode.trim()
|
submitMutation(
|
||||||
val roomNumber = roomNumberText.toIntOrNull()
|
action = "Update",
|
||||||
if (roomNumber == null || roomTypeCode.isBlank()) {
|
onDone = onDone
|
||||||
_state.update { it.copy(error = "Room number must be a number and room type is required") }
|
) { api ->
|
||||||
return
|
api.updateRoom(propertyId, roomId, input.toUpdateRequest())
|
||||||
}
|
|
||||||
viewModelScope.launch {
|
|
||||||
_state.update { it.copy(isLoading = true, error = null) }
|
|
||||||
try {
|
|
||||||
val api = ApiClient.create()
|
|
||||||
val body = RoomUpdateRequest(
|
|
||||||
roomNumber = roomNumber,
|
|
||||||
floor = state.value.floor.trim().toIntOrNull(),
|
|
||||||
roomTypeCode = roomTypeCode,
|
|
||||||
hasNfc = state.value.hasNfc,
|
|
||||||
active = state.value.active,
|
|
||||||
maintenance = state.value.maintenance,
|
|
||||||
notes = state.value.notes.takeIf { it.isNotBlank() }
|
|
||||||
)
|
|
||||||
val response = api.updateRoom(propertyId, roomId, body)
|
|
||||||
if (response.isSuccessful) {
|
|
||||||
_state.update { it.copy(isLoading = false, success = true) }
|
|
||||||
onDone()
|
|
||||||
} else {
|
|
||||||
_state.update { it.copy(isLoading = false, error = "Update failed: ${response.code()}") }
|
|
||||||
}
|
|
||||||
} catch (e: Exception) {
|
|
||||||
_state.update { it.copy(isLoading = false, error = e.localizedMessage ?: "Update failed") }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -130,19 +84,80 @@ class RoomFormViewModel : ViewModel() {
|
|||||||
_state.update { it.copy(error = "Room ID is missing") }
|
_state.update { it.copy(error = "Room ID is missing") }
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
submitMutation(
|
||||||
|
action = "Delete",
|
||||||
|
onDone = onDone
|
||||||
|
) { api ->
|
||||||
|
api.deleteRoom(propertyId, roomId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private data class ValidRoomInput(
|
||||||
|
val roomNumber: Int,
|
||||||
|
val floor: Int?,
|
||||||
|
val roomTypeCode: String,
|
||||||
|
val hasNfc: Boolean,
|
||||||
|
val active: Boolean,
|
||||||
|
val maintenance: Boolean,
|
||||||
|
val notes: String?
|
||||||
|
) {
|
||||||
|
fun toCreateRequest(): RoomCreateRequest = RoomCreateRequest(
|
||||||
|
roomNumber = roomNumber,
|
||||||
|
floor = floor,
|
||||||
|
roomTypeCode = roomTypeCode,
|
||||||
|
hasNfc = hasNfc,
|
||||||
|
active = active,
|
||||||
|
maintenance = maintenance,
|
||||||
|
notes = notes
|
||||||
|
)
|
||||||
|
|
||||||
|
fun toUpdateRequest(): RoomUpdateRequest = RoomUpdateRequest(
|
||||||
|
roomNumber = roomNumber,
|
||||||
|
floor = floor,
|
||||||
|
roomTypeCode = roomTypeCode,
|
||||||
|
hasNfc = hasNfc,
|
||||||
|
active = active,
|
||||||
|
maintenance = maintenance,
|
||||||
|
notes = notes
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun readValidatedInput(): ValidRoomInput? {
|
||||||
|
val current = state.value
|
||||||
|
val roomNumber = current.roomNumber.trim().toIntOrNull()
|
||||||
|
val roomTypeCode = current.roomTypeCode.trim()
|
||||||
|
if (roomNumber == null || roomTypeCode.isBlank()) {
|
||||||
|
_state.update { it.copy(error = "Room number must be a number and room type is required") }
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
return ValidRoomInput(
|
||||||
|
roomNumber = roomNumber,
|
||||||
|
floor = current.floor.trim().toIntOrNull(),
|
||||||
|
roomTypeCode = roomTypeCode,
|
||||||
|
hasNfc = current.hasNfc,
|
||||||
|
active = current.active,
|
||||||
|
maintenance = current.maintenance,
|
||||||
|
notes = current.notes.takeIf { it.isNotBlank() }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun submitMutation(
|
||||||
|
action: String,
|
||||||
|
onDone: () -> Unit,
|
||||||
|
call: suspend (ApiService) -> Response<*>
|
||||||
|
) {
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
_state.update { it.copy(isLoading = true, error = null) }
|
_state.update { it.copy(isLoading = true, error = null) }
|
||||||
try {
|
try {
|
||||||
val api = ApiClient.create()
|
val response = call(ApiClient.create())
|
||||||
val response = api.deleteRoom(propertyId, roomId)
|
|
||||||
if (response.isSuccessful) {
|
if (response.isSuccessful) {
|
||||||
_state.update { it.copy(isLoading = false, success = true) }
|
_state.update { it.copy(isLoading = false, success = true) }
|
||||||
onDone()
|
onDone()
|
||||||
} else {
|
} else {
|
||||||
_state.update { it.copy(isLoading = false, error = "Delete failed: ${response.code()}") }
|
_state.update { it.copy(isLoading = false, error = "$action failed: ${response.code()}") }
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
_state.update { it.copy(isLoading = false, error = e.localizedMessage ?: "Delete failed") }
|
_state.update { it.copy(isLoading = false, error = e.localizedMessage ?: "$action failed") }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,11 @@
|
|||||||
package com.android.trisolarispms.ui.room
|
package com.android.trisolarispms.ui.room
|
||||||
|
|
||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
import androidx.lifecycle.viewModelScope
|
|
||||||
import com.android.trisolarispms.data.api.core.ApiClient
|
import com.android.trisolarispms.data.api.core.ApiClient
|
||||||
|
import com.android.trisolarispms.core.viewmodel.launchRequest
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.StateFlow
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
import kotlinx.coroutines.flow.update
|
import kotlinx.coroutines.flow.update
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
|
|
||||||
class RoomListViewModel : ViewModel() {
|
class RoomListViewModel : ViewModel() {
|
||||||
private val _state = MutableStateFlow(RoomListState())
|
private val _state = MutableStateFlow(RoomListState())
|
||||||
@@ -14,35 +13,35 @@ class RoomListViewModel : ViewModel() {
|
|||||||
|
|
||||||
fun load(propertyId: String, showAll: Boolean = false, roomTypeCode: String? = null) {
|
fun load(propertyId: String, showAll: Boolean = false, roomTypeCode: String? = null) {
|
||||||
if (propertyId.isBlank()) return
|
if (propertyId.isBlank()) return
|
||||||
viewModelScope.launch {
|
launchRequest(
|
||||||
_state.update { it.copy(isLoading = true, error = null, showAll = showAll, roomTypeCode = roomTypeCode) }
|
state = _state,
|
||||||
try {
|
setLoading = { it.copy(isLoading = true, error = null, showAll = showAll, roomTypeCode = roomTypeCode) },
|
||||||
val api = ApiClient.create()
|
setError = { current, message -> current.copy(isLoading = false, error = message) },
|
||||||
val trimmedCode = roomTypeCode?.trim().orEmpty()
|
defaultError = "Load failed"
|
||||||
val response = if (trimmedCode.isNotBlank()) {
|
) {
|
||||||
api.listRoomsByType(
|
val api = ApiClient.create()
|
||||||
propertyId = propertyId,
|
val trimmedCode = roomTypeCode?.trim().orEmpty()
|
||||||
roomTypeCode = trimmedCode,
|
val response = if (trimmedCode.isNotBlank()) {
|
||||||
availableOnly = if (showAll) false else true
|
api.listRoomsByType(
|
||||||
|
propertyId = propertyId,
|
||||||
|
roomTypeCode = trimmedCode,
|
||||||
|
availableOnly = !showAll
|
||||||
|
)
|
||||||
|
} else if (showAll) {
|
||||||
|
api.listRooms(propertyId)
|
||||||
|
} else {
|
||||||
|
api.listAvailableRooms(propertyId)
|
||||||
|
}
|
||||||
|
if (response.isSuccessful) {
|
||||||
|
_state.update {
|
||||||
|
it.copy(
|
||||||
|
isLoading = false,
|
||||||
|
rooms = response.body().orEmpty(),
|
||||||
|
error = null
|
||||||
)
|
)
|
||||||
} else if (showAll) {
|
|
||||||
api.listRooms(propertyId)
|
|
||||||
} else {
|
|
||||||
api.listAvailableRooms(propertyId)
|
|
||||||
}
|
}
|
||||||
if (response.isSuccessful) {
|
} else {
|
||||||
_state.update {
|
_state.update { it.copy(isLoading = false, error = "Load failed: ${response.code()}") }
|
||||||
it.copy(
|
|
||||||
isLoading = false,
|
|
||||||
rooms = response.body().orEmpty(),
|
|
||||||
error = null
|
|
||||||
)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
_state.update { it.copy(isLoading = false, error = "Load failed: ${response.code()}") }
|
|
||||||
}
|
|
||||||
} catch (e: Exception) {
|
|
||||||
_state.update { it.copy(isLoading = false, error = e.localizedMessage ?: "Load failed") }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,34 +1,12 @@
|
|||||||
package com.android.trisolarispms.ui.roomimage
|
package com.android.trisolarispms.ui.roomimage
|
||||||
|
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
|
||||||
import androidx.compose.foundation.layout.Column
|
|
||||||
import androidx.compose.foundation.layout.Spacer
|
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
|
||||||
import androidx.compose.foundation.layout.height
|
|
||||||
import androidx.compose.foundation.layout.padding
|
|
||||||
import androidx.compose.material.icons.Icons
|
|
||||||
import androidx.compose.material.icons.automirrored.filled.ArrowBack
|
|
||||||
import androidx.compose.material.icons.filled.Done
|
|
||||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
|
||||||
import androidx.compose.material3.Icon
|
|
||||||
import androidx.compose.material3.IconButton
|
|
||||||
import androidx.compose.material3.MaterialTheme
|
|
||||||
import androidx.compose.material3.OutlinedTextField
|
|
||||||
import androidx.compose.material3.Scaffold
|
|
||||||
import androidx.compose.material3.Text
|
|
||||||
import androidx.compose.material3.TopAppBar
|
|
||||||
import androidx.compose.material3.TopAppBarDefaults
|
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.collectAsState
|
import androidx.compose.runtime.collectAsState
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
|
||||||
import androidx.compose.ui.Modifier
|
|
||||||
import androidx.compose.ui.unit.dp
|
|
||||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
|
||||||
fun AddImageTagScreen(
|
fun AddImageTagScreen(
|
||||||
onBack: () -> Unit,
|
onBack: () -> Unit,
|
||||||
onSave: () -> Unit,
|
onSave: () -> Unit,
|
||||||
@@ -40,41 +18,12 @@ fun AddImageTagScreen(
|
|||||||
viewModel.reset()
|
viewModel.reset()
|
||||||
}
|
}
|
||||||
|
|
||||||
Scaffold(
|
ImageTagFormScreen(
|
||||||
topBar = {
|
title = "Add Tag",
|
||||||
TopAppBar(
|
name = state.name,
|
||||||
title = { Text("Add Tag") },
|
error = state.error,
|
||||||
navigationIcon = {
|
onNameChange = viewModel::onNameChange,
|
||||||
IconButton(onClick = onBack) {
|
onBack = onBack,
|
||||||
Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = "Back")
|
onSave = { viewModel.submitCreate(onSave) }
|
||||||
}
|
)
|
||||||
},
|
|
||||||
actions = {
|
|
||||||
IconButton(onClick = { viewModel.submitCreate(onSave) }) {
|
|
||||||
Icon(Icons.Default.Done, contentDescription = "Save")
|
|
||||||
}
|
|
||||||
},
|
|
||||||
colors = TopAppBarDefaults.topAppBarColors()
|
|
||||||
)
|
|
||||||
}
|
|
||||||
) { padding ->
|
|
||||||
Column(
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxSize()
|
|
||||||
.padding(padding)
|
|
||||||
.padding(24.dp),
|
|
||||||
verticalArrangement = Arrangement.Top
|
|
||||||
) {
|
|
||||||
OutlinedTextField(
|
|
||||||
value = state.name,
|
|
||||||
onValueChange = viewModel::onNameChange,
|
|
||||||
label = { Text("Name") },
|
|
||||||
modifier = Modifier.fillMaxWidth()
|
|
||||||
)
|
|
||||||
state.error?.let {
|
|
||||||
Spacer(modifier = Modifier.height(12.dp))
|
|
||||||
Text(text = it, color = MaterialTheme.colorScheme.error)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,35 +1,13 @@
|
|||||||
package com.android.trisolarispms.ui.roomimage
|
package com.android.trisolarispms.ui.roomimage
|
||||||
|
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
|
||||||
import androidx.compose.foundation.layout.Column
|
|
||||||
import androidx.compose.foundation.layout.Spacer
|
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
|
||||||
import androidx.compose.foundation.layout.height
|
|
||||||
import androidx.compose.foundation.layout.padding
|
|
||||||
import androidx.compose.material.icons.Icons
|
|
||||||
import androidx.compose.material.icons.filled.ArrowBack
|
|
||||||
import androidx.compose.material.icons.filled.Done
|
|
||||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
|
||||||
import androidx.compose.material3.Icon
|
|
||||||
import androidx.compose.material3.IconButton
|
|
||||||
import androidx.compose.material3.MaterialTheme
|
|
||||||
import androidx.compose.material3.OutlinedTextField
|
|
||||||
import androidx.compose.material3.Scaffold
|
|
||||||
import androidx.compose.material3.Text
|
|
||||||
import androidx.compose.material3.TopAppBar
|
|
||||||
import androidx.compose.material3.TopAppBarDefaults
|
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.collectAsState
|
import androidx.compose.runtime.collectAsState
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.ui.Modifier
|
|
||||||
import androidx.compose.ui.unit.dp
|
|
||||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||||
import com.android.trisolarispms.data.api.model.RoomImageTagDto
|
import com.android.trisolarispms.data.api.model.RoomImageTagDto
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
|
||||||
fun EditImageTagScreen(
|
fun EditImageTagScreen(
|
||||||
tag: RoomImageTagDto,
|
tag: RoomImageTagDto,
|
||||||
onBack: () -> Unit,
|
onBack: () -> Unit,
|
||||||
@@ -42,41 +20,12 @@ fun EditImageTagScreen(
|
|||||||
viewModel.setTag(tag)
|
viewModel.setTag(tag)
|
||||||
}
|
}
|
||||||
|
|
||||||
Scaffold(
|
ImageTagFormScreen(
|
||||||
topBar = {
|
title = "Edit Tag",
|
||||||
TopAppBar(
|
name = state.name,
|
||||||
title = { Text("Edit Tag") },
|
error = state.error,
|
||||||
navigationIcon = {
|
onNameChange = viewModel::onNameChange,
|
||||||
IconButton(onClick = onBack) {
|
onBack = onBack,
|
||||||
Icon(Icons.Default.ArrowBack, contentDescription = "Back")
|
onSave = { viewModel.submitUpdate(tag.id.orEmpty(), onSave) }
|
||||||
}
|
)
|
||||||
},
|
|
||||||
actions = {
|
|
||||||
IconButton(onClick = { viewModel.submitUpdate(tag.id.orEmpty(), onSave) }) {
|
|
||||||
Icon(Icons.Default.Done, contentDescription = "Save")
|
|
||||||
}
|
|
||||||
},
|
|
||||||
colors = TopAppBarDefaults.topAppBarColors()
|
|
||||||
)
|
|
||||||
}
|
|
||||||
) { padding ->
|
|
||||||
Column(
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxSize()
|
|
||||||
.padding(padding)
|
|
||||||
.padding(24.dp),
|
|
||||||
verticalArrangement = Arrangement.Top
|
|
||||||
) {
|
|
||||||
OutlinedTextField(
|
|
||||||
value = state.name,
|
|
||||||
onValueChange = viewModel::onNameChange,
|
|
||||||
label = { Text("Name") },
|
|
||||||
modifier = Modifier.fillMaxWidth()
|
|
||||||
)
|
|
||||||
state.error?.let {
|
|
||||||
Spacer(modifier = Modifier.height(12.dp))
|
|
||||||
Text(text = it, color = MaterialTheme.colorScheme.error)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,73 @@
|
|||||||
|
package com.android.trisolarispms.ui.roomimage
|
||||||
|
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.Spacer
|
||||||
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.height
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.automirrored.filled.ArrowBack
|
||||||
|
import androidx.compose.material.icons.filled.Done
|
||||||
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
|
import androidx.compose.material3.Icon
|
||||||
|
import androidx.compose.material3.IconButton
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.OutlinedTextField
|
||||||
|
import androidx.compose.material3.Scaffold
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.material3.TopAppBar
|
||||||
|
import androidx.compose.material3.TopAppBarDefaults
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
|
internal fun ImageTagFormScreen(
|
||||||
|
title: String,
|
||||||
|
name: String,
|
||||||
|
error: String?,
|
||||||
|
onNameChange: (String) -> Unit,
|
||||||
|
onBack: () -> Unit,
|
||||||
|
onSave: () -> Unit
|
||||||
|
) {
|
||||||
|
Scaffold(
|
||||||
|
topBar = {
|
||||||
|
TopAppBar(
|
||||||
|
title = { Text(title) },
|
||||||
|
navigationIcon = {
|
||||||
|
IconButton(onClick = onBack) {
|
||||||
|
Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = "Back")
|
||||||
|
}
|
||||||
|
},
|
||||||
|
actions = {
|
||||||
|
IconButton(onClick = onSave) {
|
||||||
|
Icon(Icons.Default.Done, contentDescription = "Save")
|
||||||
|
}
|
||||||
|
},
|
||||||
|
colors = TopAppBarDefaults.topAppBarColors()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
) { padding ->
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.padding(padding)
|
||||||
|
.padding(24.dp),
|
||||||
|
verticalArrangement = Arrangement.Top
|
||||||
|
) {
|
||||||
|
OutlinedTextField(
|
||||||
|
value = name,
|
||||||
|
onValueChange = onNameChange,
|
||||||
|
label = { Text("Name") },
|
||||||
|
modifier = Modifier.fillMaxWidth()
|
||||||
|
)
|
||||||
|
error?.let {
|
||||||
|
Spacer(modifier = Modifier.height(12.dp))
|
||||||
|
Text(text = it, color = MaterialTheme.colorScheme.error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,11 +3,13 @@ package com.android.trisolarispms.ui.roomimage
|
|||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
import com.android.trisolarispms.data.api.core.ApiClient
|
import com.android.trisolarispms.data.api.core.ApiClient
|
||||||
|
import com.android.trisolarispms.data.api.core.ApiService
|
||||||
import com.android.trisolarispms.data.api.model.RoomImageTagDto
|
import com.android.trisolarispms.data.api.model.RoomImageTagDto
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.StateFlow
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
import kotlinx.coroutines.flow.update
|
import kotlinx.coroutines.flow.update
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
import retrofit2.Response
|
||||||
|
|
||||||
class ImageTagFormViewModel : ViewModel() {
|
class ImageTagFormViewModel : ViewModel() {
|
||||||
private val _state = MutableStateFlow(ImageTagFormState())
|
private val _state = MutableStateFlow(ImageTagFormState())
|
||||||
@@ -26,47 +28,57 @@ class ImageTagFormViewModel : ViewModel() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun submitCreate(onDone: () -> Unit) {
|
fun submitCreate(onDone: () -> Unit) {
|
||||||
val name = state.value.name.trim()
|
val payload = readValidatedPayload() ?: return
|
||||||
if (name.isBlank()) {
|
submitMutation(
|
||||||
_state.update { it.copy(error = "Name is required") }
|
action = "Create",
|
||||||
return
|
onDone = onDone,
|
||||||
}
|
resetOnSuccess = true
|
||||||
viewModelScope.launch {
|
) { api ->
|
||||||
_state.update { it.copy(isLoading = true, error = null) }
|
api.createImageTag(payload)
|
||||||
try {
|
|
||||||
val api = ApiClient.create()
|
|
||||||
val response = api.createImageTag(RoomImageTagDto(name = name))
|
|
||||||
if (response.isSuccessful) {
|
|
||||||
_state.update { ImageTagFormState(success = true) }
|
|
||||||
onDone()
|
|
||||||
} else {
|
|
||||||
_state.update { it.copy(isLoading = false, error = "Create failed: ${response.code()}") }
|
|
||||||
}
|
|
||||||
} catch (e: Exception) {
|
|
||||||
_state.update { it.copy(isLoading = false, error = e.localizedMessage ?: "Create failed") }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun submitUpdate(tagId: String, onDone: () -> Unit) {
|
fun submitUpdate(tagId: String, onDone: () -> Unit) {
|
||||||
|
val payload = readValidatedPayload() ?: return
|
||||||
|
submitMutation(
|
||||||
|
action = "Update",
|
||||||
|
onDone = onDone,
|
||||||
|
resetOnSuccess = false
|
||||||
|
) { api ->
|
||||||
|
api.updateImageTag(tagId, payload)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun readValidatedPayload(): RoomImageTagDto? {
|
||||||
val name = state.value.name.trim()
|
val name = state.value.name.trim()
|
||||||
if (name.isBlank()) {
|
if (name.isBlank()) {
|
||||||
_state.update { it.copy(error = "Name is required") }
|
_state.update { it.copy(error = "Name is required") }
|
||||||
return
|
return null
|
||||||
}
|
}
|
||||||
|
return RoomImageTagDto(name = name)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun submitMutation(
|
||||||
|
action: String,
|
||||||
|
onDone: () -> Unit,
|
||||||
|
resetOnSuccess: Boolean,
|
||||||
|
call: suspend (ApiService) -> Response<*>
|
||||||
|
) {
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
_state.update { it.copy(isLoading = true, error = null) }
|
_state.update { it.copy(isLoading = true, error = null) }
|
||||||
try {
|
try {
|
||||||
val api = ApiClient.create()
|
val response = call(ApiClient.create())
|
||||||
val response = api.updateImageTag(tagId, RoomImageTagDto(name = name))
|
|
||||||
if (response.isSuccessful) {
|
if (response.isSuccessful) {
|
||||||
_state.update { it.copy(isLoading = false, success = true) }
|
_state.update {
|
||||||
|
if (resetOnSuccess) ImageTagFormState(success = true)
|
||||||
|
else it.copy(isLoading = false, success = true)
|
||||||
|
}
|
||||||
onDone()
|
onDone()
|
||||||
} else {
|
} else {
|
||||||
_state.update { it.copy(isLoading = false, error = "Update failed: ${response.code()}") }
|
_state.update { it.copy(isLoading = false, error = "$action failed: ${response.code()}") }
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
_state.update { it.copy(isLoading = false, error = e.localizedMessage ?: "Update failed") }
|
_state.update { it.copy(isLoading = false, error = e.localizedMessage ?: "$action failed") }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,60 +1,59 @@
|
|||||||
package com.android.trisolarispms.ui.roomimage
|
package com.android.trisolarispms.ui.roomimage
|
||||||
|
|
||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
import androidx.lifecycle.viewModelScope
|
|
||||||
import com.android.trisolarispms.data.api.core.ApiClient
|
import com.android.trisolarispms.data.api.core.ApiClient
|
||||||
|
import com.android.trisolarispms.core.viewmodel.launchRequest
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.StateFlow
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
import kotlinx.coroutines.flow.update
|
import kotlinx.coroutines.flow.update
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
|
|
||||||
class ImageTagViewModel : ViewModel() {
|
class ImageTagViewModel : ViewModel() {
|
||||||
private val _state = MutableStateFlow(ImageTagState())
|
private val _state = MutableStateFlow(ImageTagState())
|
||||||
val state: StateFlow<ImageTagState> = _state
|
val state: StateFlow<ImageTagState> = _state
|
||||||
|
|
||||||
fun load() {
|
fun load() {
|
||||||
viewModelScope.launch {
|
launchRequest(
|
||||||
_state.update { it.copy(isLoading = true, error = null) }
|
state = _state,
|
||||||
try {
|
setLoading = { it.copy(isLoading = true, error = null) },
|
||||||
val api = ApiClient.create()
|
setError = { current, message -> current.copy(isLoading = false, error = message) },
|
||||||
val response = api.listImageTags()
|
defaultError = "Load failed"
|
||||||
if (response.isSuccessful) {
|
) {
|
||||||
_state.update {
|
val api = ApiClient.create()
|
||||||
it.copy(
|
val response = api.listImageTags()
|
||||||
isLoading = false,
|
if (response.isSuccessful) {
|
||||||
tags = response.body().orEmpty(),
|
_state.update {
|
||||||
error = null
|
it.copy(
|
||||||
)
|
isLoading = false,
|
||||||
}
|
tags = response.body().orEmpty(),
|
||||||
} else {
|
error = null
|
||||||
_state.update { it.copy(isLoading = false, error = "Load failed: ${response.code()}") }
|
)
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} else {
|
||||||
_state.update { it.copy(isLoading = false, error = e.localizedMessage ?: "Load failed") }
|
_state.update { it.copy(isLoading = false, error = "Load failed: ${response.code()}") }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun delete(tagId: String) {
|
fun delete(tagId: String) {
|
||||||
if (tagId.isBlank()) return
|
if (tagId.isBlank()) return
|
||||||
viewModelScope.launch {
|
launchRequest(
|
||||||
_state.update { it.copy(isLoading = true, error = null) }
|
state = _state,
|
||||||
try {
|
setLoading = { it.copy(isLoading = true, error = null) },
|
||||||
val api = ApiClient.create()
|
setError = { current, message -> current.copy(isLoading = false, error = message) },
|
||||||
val response = api.deleteImageTag(tagId)
|
defaultError = "Delete failed"
|
||||||
if (response.isSuccessful) {
|
) {
|
||||||
_state.update { current ->
|
val api = ApiClient.create()
|
||||||
current.copy(
|
val response = api.deleteImageTag(tagId)
|
||||||
isLoading = false,
|
if (response.isSuccessful) {
|
||||||
tags = current.tags.filterNot { it.id == tagId },
|
_state.update { current ->
|
||||||
error = null
|
current.copy(
|
||||||
)
|
isLoading = false,
|
||||||
}
|
tags = current.tags.filterNot { it.id == tagId },
|
||||||
} else {
|
error = null
|
||||||
_state.update { it.copy(isLoading = false, error = "Delete failed: ${response.code()}") }
|
)
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} else {
|
||||||
_state.update { it.copy(isLoading = false, error = e.localizedMessage ?: "Delete failed") }
|
_state.update { it.copy(isLoading = false, error = "Delete failed: ${response.code()}") }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,11 @@
|
|||||||
package com.android.trisolarispms.ui.roomstay
|
package com.android.trisolarispms.ui.roomstay
|
||||||
|
|
||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
import androidx.lifecycle.viewModelScope
|
|
||||||
import com.android.trisolarispms.data.api.core.ApiClient
|
import com.android.trisolarispms.data.api.core.ApiClient
|
||||||
|
import com.android.trisolarispms.core.viewmodel.launchRequest
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.StateFlow
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
import kotlinx.coroutines.flow.update
|
import kotlinx.coroutines.flow.update
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
|
|
||||||
class ActiveRoomStaysViewModel : ViewModel() {
|
class ActiveRoomStaysViewModel : ViewModel() {
|
||||||
private val _state = MutableStateFlow(ActiveRoomStaysState())
|
private val _state = MutableStateFlow(ActiveRoomStaysState())
|
||||||
@@ -14,26 +13,26 @@ class ActiveRoomStaysViewModel : ViewModel() {
|
|||||||
|
|
||||||
fun load(propertyId: String) {
|
fun load(propertyId: String) {
|
||||||
if (propertyId.isBlank()) return
|
if (propertyId.isBlank()) return
|
||||||
viewModelScope.launch {
|
launchRequest(
|
||||||
_state.update { it.copy(isLoading = true, error = null) }
|
state = _state,
|
||||||
try {
|
setLoading = { it.copy(isLoading = true, error = null) },
|
||||||
val api = ApiClient.create()
|
setError = { current, message -> current.copy(isLoading = false, error = message) },
|
||||||
val activeResponse = api.listActiveRoomStays(propertyId)
|
defaultError = "Load failed"
|
||||||
val bookingsResponse = api.listBookings(propertyId, status = "CHECKED_IN")
|
) {
|
||||||
if (activeResponse.isSuccessful) {
|
val api = ApiClient.create()
|
||||||
_state.update {
|
val activeResponse = api.listActiveRoomStays(propertyId)
|
||||||
it.copy(
|
val bookingsResponse = api.listBookings(propertyId, status = "CHECKED_IN")
|
||||||
isLoading = false,
|
if (activeResponse.isSuccessful) {
|
||||||
items = activeResponse.body().orEmpty(),
|
_state.update {
|
||||||
checkedInBookings = bookingsResponse.body().orEmpty(),
|
it.copy(
|
||||||
error = null
|
isLoading = false,
|
||||||
)
|
items = activeResponse.body().orEmpty(),
|
||||||
}
|
checkedInBookings = bookingsResponse.body().orEmpty(),
|
||||||
} else {
|
error = null
|
||||||
_state.update { it.copy(isLoading = false, error = "Load failed: ${activeResponse.code()}") }
|
)
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} else {
|
||||||
_state.update { it.copy(isLoading = false, error = e.localizedMessage ?: "Load failed") }
|
_state.update { it.copy(isLoading = false, error = "Load failed: ${activeResponse.code()}") }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,13 +5,14 @@ import androidx.lifecycle.viewModelScope
|
|||||||
import com.android.trisolarispms.data.api.core.ApiClient
|
import com.android.trisolarispms.data.api.core.ApiClient
|
||||||
import com.android.trisolarispms.data.api.core.ApiConstants
|
import com.android.trisolarispms.data.api.core.ApiConstants
|
||||||
import com.android.trisolarispms.data.api.model.BookingDetailsResponse
|
import com.android.trisolarispms.data.api.model.BookingDetailsResponse
|
||||||
|
import com.android.trisolarispms.core.viewmodel.launchRequest
|
||||||
import com.google.gson.Gson
|
import com.google.gson.Gson
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.StateFlow
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
import kotlinx.coroutines.flow.update
|
import kotlinx.coroutines.flow.update
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
import kotlinx.coroutines.Job
|
import kotlinx.coroutines.Job
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
import okhttp3.Request
|
import okhttp3.Request
|
||||||
import okhttp3.sse.EventSource
|
import okhttp3.sse.EventSource
|
||||||
import okhttp3.sse.EventSourceListener
|
import okhttp3.sse.EventSourceListener
|
||||||
@@ -30,24 +31,24 @@ class BookingDetailsViewModel : ViewModel() {
|
|||||||
|
|
||||||
fun load(propertyId: String, bookingId: String) {
|
fun load(propertyId: String, bookingId: String) {
|
||||||
if (propertyId.isBlank() || bookingId.isBlank()) return
|
if (propertyId.isBlank() || bookingId.isBlank()) return
|
||||||
viewModelScope.launch {
|
launchRequest(
|
||||||
_state.update { it.copy(isLoading = true, error = null) }
|
state = _state,
|
||||||
try {
|
setLoading = { it.copy(isLoading = true, error = null) },
|
||||||
val api = ApiClient.create()
|
setError = { current, message -> current.copy(isLoading = false, error = message) },
|
||||||
val response = api.getBookingDetails(propertyId, bookingId)
|
defaultError = "Load failed"
|
||||||
if (response.isSuccessful) {
|
) {
|
||||||
_state.update {
|
val api = ApiClient.create()
|
||||||
it.copy(
|
val response = api.getBookingDetails(propertyId, bookingId)
|
||||||
isLoading = false,
|
if (response.isSuccessful) {
|
||||||
details = response.body(),
|
_state.update {
|
||||||
error = null
|
it.copy(
|
||||||
)
|
isLoading = false,
|
||||||
}
|
details = response.body(),
|
||||||
} else {
|
error = null
|
||||||
_state.update { it.copy(isLoading = false, error = "Load failed: ${response.code()}") }
|
)
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} else {
|
||||||
_state.update { it.copy(isLoading = false, error = e.localizedMessage ?: "Load failed") }
|
_state.update { it.copy(isLoading = false, error = "Load failed: ${response.code()}") }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,11 @@
|
|||||||
package com.android.trisolarispms.ui.roomstay
|
package com.android.trisolarispms.ui.roomstay
|
||||||
|
|
||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
import androidx.lifecycle.viewModelScope
|
|
||||||
import com.android.trisolarispms.data.api.core.ApiClient
|
import com.android.trisolarispms.data.api.core.ApiClient
|
||||||
|
import com.android.trisolarispms.core.viewmodel.launchRequest
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.StateFlow
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
import kotlinx.coroutines.flow.update
|
import kotlinx.coroutines.flow.update
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
|
|
||||||
class BookingRoomStaysViewModel : ViewModel() {
|
class BookingRoomStaysViewModel : ViewModel() {
|
||||||
private val _state = MutableStateFlow(BookingRoomStaysState())
|
private val _state = MutableStateFlow(BookingRoomStaysState())
|
||||||
@@ -18,25 +17,25 @@ class BookingRoomStaysViewModel : ViewModel() {
|
|||||||
|
|
||||||
fun load(propertyId: String, bookingId: String) {
|
fun load(propertyId: String, bookingId: String) {
|
||||||
if (propertyId.isBlank() || bookingId.isBlank()) return
|
if (propertyId.isBlank() || bookingId.isBlank()) return
|
||||||
viewModelScope.launch {
|
launchRequest(
|
||||||
_state.update { it.copy(isLoading = true, error = null) }
|
state = _state,
|
||||||
try {
|
setLoading = { it.copy(isLoading = true, error = null) },
|
||||||
val api = ApiClient.create()
|
setError = { current, message -> current.copy(isLoading = false, error = message) },
|
||||||
val response = api.listActiveRoomStays(propertyId)
|
defaultError = "Load failed"
|
||||||
if (response.isSuccessful) {
|
) {
|
||||||
val filtered = response.body().orEmpty().filter { it.bookingId == bookingId }
|
val api = ApiClient.create()
|
||||||
_state.update {
|
val response = api.listActiveRoomStays(propertyId)
|
||||||
it.copy(
|
if (response.isSuccessful) {
|
||||||
isLoading = false,
|
val filtered = response.body().orEmpty().filter { it.bookingId == bookingId }
|
||||||
stays = filtered,
|
_state.update {
|
||||||
error = null
|
it.copy(
|
||||||
)
|
isLoading = false,
|
||||||
}
|
stays = filtered,
|
||||||
} else {
|
error = null
|
||||||
_state.update { it.copy(isLoading = false, error = "Load failed: ${response.code()}") }
|
)
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} else {
|
||||||
_state.update { it.copy(isLoading = false, error = e.localizedMessage ?: "Load failed") }
|
_state.update { it.copy(isLoading = false, error = "Load failed: ${response.code()}") }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,14 +1,13 @@
|
|||||||
package com.android.trisolarispms.ui.roomstay
|
package com.android.trisolarispms.ui.roomstay
|
||||||
|
|
||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
import androidx.lifecycle.viewModelScope
|
|
||||||
import com.android.trisolarispms.data.api.core.ApiClient
|
import com.android.trisolarispms.data.api.core.ApiClient
|
||||||
import com.android.trisolarispms.data.api.model.BookingBulkCheckInRequest
|
import com.android.trisolarispms.data.api.model.BookingBulkCheckInRequest
|
||||||
import com.android.trisolarispms.data.api.model.BookingBulkCheckInStayRequest
|
import com.android.trisolarispms.data.api.model.BookingBulkCheckInStayRequest
|
||||||
|
import com.android.trisolarispms.core.viewmodel.launchRequest
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.StateFlow
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
import kotlinx.coroutines.flow.update
|
import kotlinx.coroutines.flow.update
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
|
|
||||||
class ManageRoomStayRatesViewModel : ViewModel() {
|
class ManageRoomStayRatesViewModel : ViewModel() {
|
||||||
private val _state = MutableStateFlow(ManageRoomStayRatesState())
|
private val _state = MutableStateFlow(ManageRoomStayRatesState())
|
||||||
@@ -79,34 +78,34 @@ class ManageRoomStayRatesViewModel : ViewModel() {
|
|||||||
if (propertyId.isBlank() || bookingId.isBlank() || checkInAt.isBlank()) return
|
if (propertyId.isBlank() || bookingId.isBlank() || checkInAt.isBlank()) return
|
||||||
val items = _state.value.items
|
val items = _state.value.items
|
||||||
if (items.isEmpty()) return
|
if (items.isEmpty()) return
|
||||||
viewModelScope.launch {
|
launchRequest(
|
||||||
_state.update { it.copy(isLoading = true, error = null) }
|
state = _state,
|
||||||
try {
|
setLoading = { it.copy(isLoading = true, error = null) },
|
||||||
val api = ApiClient.create()
|
setError = { current, message -> current.copy(isLoading = false, error = message) },
|
||||||
val stays = items.map { item ->
|
defaultError = "Update failed"
|
||||||
BookingBulkCheckInStayRequest(
|
) {
|
||||||
roomId = item.roomId,
|
val api = ApiClient.create()
|
||||||
checkInAt = checkInAt,
|
val stays = items.map { item ->
|
||||||
checkOutAt = checkOutAt,
|
BookingBulkCheckInStayRequest(
|
||||||
nightlyRate = item.nightlyRate,
|
roomId = item.roomId,
|
||||||
rateSource = "MANUAL",
|
checkInAt = checkInAt,
|
||||||
ratePlanCode = item.ratePlanCode,
|
checkOutAt = checkOutAt,
|
||||||
currency = item.currency
|
nightlyRate = item.nightlyRate,
|
||||||
)
|
rateSource = "MANUAL",
|
||||||
}
|
ratePlanCode = item.ratePlanCode,
|
||||||
val response = api.bulkCheckIn(
|
currency = item.currency
|
||||||
propertyId = propertyId,
|
|
||||||
bookingId = bookingId,
|
|
||||||
body = BookingBulkCheckInRequest(stays = stays)
|
|
||||||
)
|
)
|
||||||
if (response.isSuccessful) {
|
}
|
||||||
_state.update { it.copy(isLoading = false, error = null) }
|
val response = api.bulkCheckIn(
|
||||||
onDone()
|
propertyId = propertyId,
|
||||||
} else {
|
bookingId = bookingId,
|
||||||
_state.update { it.copy(isLoading = false, error = "Update failed: ${response.code()}") }
|
body = BookingBulkCheckInRequest(stays = stays)
|
||||||
}
|
)
|
||||||
} catch (e: Exception) {
|
if (response.isSuccessful) {
|
||||||
_state.update { it.copy(isLoading = false, error = e.localizedMessage ?: "Update failed") }
|
_state.update { it.copy(isLoading = false, error = null) }
|
||||||
|
onDone()
|
||||||
|
} else {
|
||||||
|
_state.update { it.copy(isLoading = false, error = "Update failed: ${response.code()}") }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,11 @@
|
|||||||
package com.android.trisolarispms.ui.roomstay
|
package com.android.trisolarispms.ui.roomstay
|
||||||
|
|
||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
import androidx.lifecycle.viewModelScope
|
|
||||||
import com.android.trisolarispms.data.api.core.ApiClient
|
import com.android.trisolarispms.data.api.core.ApiClient
|
||||||
|
import com.android.trisolarispms.core.viewmodel.launchRequest
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.StateFlow
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
import kotlinx.coroutines.flow.update
|
import kotlinx.coroutines.flow.update
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
|
|
||||||
class ManageRoomStaySelectViewModel : ViewModel() {
|
class ManageRoomStaySelectViewModel : ViewModel() {
|
||||||
private val _state = MutableStateFlow(ManageRoomStaySelectState())
|
private val _state = MutableStateFlow(ManageRoomStaySelectState())
|
||||||
@@ -14,24 +13,24 @@ class ManageRoomStaySelectViewModel : ViewModel() {
|
|||||||
|
|
||||||
fun load(propertyId: String, from: String, to: String) {
|
fun load(propertyId: String, from: String, to: String) {
|
||||||
if (propertyId.isBlank() || from.isBlank() || to.isBlank()) return
|
if (propertyId.isBlank() || from.isBlank() || to.isBlank()) return
|
||||||
viewModelScope.launch {
|
launchRequest(
|
||||||
_state.update { it.copy(isLoading = true, error = null) }
|
state = _state,
|
||||||
try {
|
setLoading = { it.copy(isLoading = true, error = null) },
|
||||||
val api = ApiClient.create()
|
setError = { current, message -> current.copy(isLoading = false, error = message) },
|
||||||
val response = api.listAvailableRoomsWithRate(propertyId, from = from, to = to)
|
defaultError = "Load failed"
|
||||||
if (response.isSuccessful) {
|
) {
|
||||||
_state.update {
|
val api = ApiClient.create()
|
||||||
it.copy(
|
val response = api.listAvailableRoomsWithRate(propertyId, from = from, to = to)
|
||||||
isLoading = false,
|
if (response.isSuccessful) {
|
||||||
rooms = response.body().orEmpty(),
|
_state.update {
|
||||||
error = null
|
it.copy(
|
||||||
)
|
isLoading = false,
|
||||||
}
|
rooms = response.body().orEmpty(),
|
||||||
} else {
|
error = null
|
||||||
_state.update { it.copy(isLoading = false, error = "Load failed: ${response.code()}") }
|
)
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} else {
|
||||||
_state.update { it.copy(isLoading = false, error = e.localizedMessage ?: "Load failed") }
|
_state.update { it.copy(isLoading = false, error = "Load failed: ${response.code()}") }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,13 +10,12 @@ fun AddAmenityScreen(
|
|||||||
viewModel: AmenityFormViewModel = viewModel(),
|
viewModel: AmenityFormViewModel = viewModel(),
|
||||||
amenityListViewModel: AmenityListViewModel = viewModel()
|
amenityListViewModel: AmenityListViewModel = viewModel()
|
||||||
) {
|
) {
|
||||||
androidx.compose.runtime.LaunchedEffect(Unit) {
|
AmenityEditorScreen(
|
||||||
viewModel.resetForm(preserveOptions = true)
|
|
||||||
}
|
|
||||||
AmenityFormScreen(
|
|
||||||
title = "Add Amenity",
|
title = "Add Amenity",
|
||||||
|
setupKey = Unit,
|
||||||
onBack = onBack,
|
onBack = onBack,
|
||||||
onSaveClick = { viewModel.submitCreate(onSave) },
|
onSaveClick = { viewModel.submitCreate(onSave) },
|
||||||
|
setupForm = { viewModel.resetForm(preserveOptions = true) },
|
||||||
viewModel = viewModel,
|
viewModel = viewModel,
|
||||||
amenityListViewModel = amenityListViewModel
|
amenityListViewModel = amenityListViewModel
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -0,0 +1,27 @@
|
|||||||
|
package com.android.trisolarispms.ui.roomtype
|
||||||
|
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
|
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
internal fun AmenityEditorScreen(
|
||||||
|
title: String,
|
||||||
|
setupKey: Any?,
|
||||||
|
onBack: () -> Unit,
|
||||||
|
onSaveClick: () -> Unit,
|
||||||
|
setupForm: () -> Unit,
|
||||||
|
viewModel: AmenityFormViewModel = viewModel(),
|
||||||
|
amenityListViewModel: AmenityListViewModel = viewModel()
|
||||||
|
) {
|
||||||
|
LaunchedEffect(setupKey) {
|
||||||
|
setupForm()
|
||||||
|
}
|
||||||
|
AmenityFormScreen(
|
||||||
|
title = title,
|
||||||
|
onBack = onBack,
|
||||||
|
onSaveClick = onSaveClick,
|
||||||
|
viewModel = viewModel,
|
||||||
|
amenityListViewModel = amenityListViewModel
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -3,12 +3,14 @@ package com.android.trisolarispms.ui.roomtype
|
|||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
import com.android.trisolarispms.data.api.core.ApiClient
|
import com.android.trisolarispms.data.api.core.ApiClient
|
||||||
|
import com.android.trisolarispms.data.api.core.ApiService
|
||||||
import com.android.trisolarispms.data.api.model.AmenityCreateRequest
|
import com.android.trisolarispms.data.api.model.AmenityCreateRequest
|
||||||
import com.android.trisolarispms.data.api.model.AmenityUpdateRequest
|
import com.android.trisolarispms.data.api.model.AmenityUpdateRequest
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.StateFlow
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
import kotlinx.coroutines.flow.update
|
import kotlinx.coroutines.flow.update
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
import retrofit2.Response
|
||||||
|
|
||||||
class AmenityFormViewModel : ViewModel() {
|
class AmenityFormViewModel : ViewModel() {
|
||||||
private val _state = MutableStateFlow(AmenityFormState())
|
private val _state = MutableStateFlow(AmenityFormState())
|
||||||
@@ -88,62 +90,75 @@ class AmenityFormViewModel : ViewModel() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun submitCreate(onDone: () -> Unit) {
|
fun submitCreate(onDone: () -> Unit) {
|
||||||
val name = state.value.name.trim()
|
val payload = readValidatedPayload() ?: return
|
||||||
if (name.isBlank()) {
|
submitMutation(
|
||||||
_state.update { it.copy(error = "Name is required") }
|
action = "Create",
|
||||||
return
|
onDone = onDone
|
||||||
}
|
) { api ->
|
||||||
viewModelScope.launch {
|
api.createAmenity(payload.toCreateRequest())
|
||||||
_state.update { it.copy(isLoading = true, error = null) }
|
|
||||||
try {
|
|
||||||
val iconKey = state.value.iconKey.trim().removeSuffix(".png").removeSuffix(".PNG")
|
|
||||||
val api = ApiClient.create()
|
|
||||||
val response = api.createAmenity(
|
|
||||||
AmenityCreateRequest(
|
|
||||||
name = name,
|
|
||||||
category = state.value.category.trim().ifBlank { null },
|
|
||||||
iconKey = iconKey.ifBlank { null }
|
|
||||||
)
|
|
||||||
)
|
|
||||||
if (response.isSuccessful) {
|
|
||||||
_state.update { it.copy(isLoading = false, success = true) }
|
|
||||||
onDone()
|
|
||||||
} else {
|
|
||||||
_state.update { it.copy(isLoading = false, error = "Create failed: ${response.code()}") }
|
|
||||||
}
|
|
||||||
} catch (e: Exception) {
|
|
||||||
_state.update { it.copy(isLoading = false, error = e.localizedMessage ?: "Create failed") }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun submitUpdate(amenityId: String, onDone: () -> Unit) {
|
fun submitUpdate(amenityId: String, onDone: () -> Unit) {
|
||||||
val name = state.value.name.trim()
|
val payload = readValidatedPayload() ?: return
|
||||||
|
submitMutation(
|
||||||
|
action = "Update",
|
||||||
|
onDone = onDone
|
||||||
|
) { api ->
|
||||||
|
api.updateAmenity(amenityId, payload.toUpdateRequest())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private data class AmenityPayload(
|
||||||
|
val name: String,
|
||||||
|
val category: String?,
|
||||||
|
val iconKey: String?
|
||||||
|
) {
|
||||||
|
fun toCreateRequest(): AmenityCreateRequest = AmenityCreateRequest(
|
||||||
|
name = name,
|
||||||
|
category = category,
|
||||||
|
iconKey = iconKey
|
||||||
|
)
|
||||||
|
|
||||||
|
fun toUpdateRequest(): AmenityUpdateRequest = AmenityUpdateRequest(
|
||||||
|
name = name,
|
||||||
|
category = category,
|
||||||
|
iconKey = iconKey
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun readValidatedPayload(): AmenityPayload? {
|
||||||
|
val current = state.value
|
||||||
|
val name = current.name.trim()
|
||||||
if (name.isBlank()) {
|
if (name.isBlank()) {
|
||||||
_state.update { it.copy(error = "Name is required") }
|
_state.update { it.copy(error = "Name is required") }
|
||||||
return
|
return null
|
||||||
}
|
}
|
||||||
|
val iconKey = current.iconKey.trim().removeSuffix(".png").removeSuffix(".PNG")
|
||||||
|
return AmenityPayload(
|
||||||
|
name = name,
|
||||||
|
category = current.category.trim().ifBlank { null },
|
||||||
|
iconKey = iconKey.ifBlank { null }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun submitMutation(
|
||||||
|
action: String,
|
||||||
|
onDone: () -> Unit,
|
||||||
|
call: suspend (ApiService) -> Response<*>
|
||||||
|
) {
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
_state.update { it.copy(isLoading = true, error = null) }
|
_state.update { it.copy(isLoading = true, error = null) }
|
||||||
try {
|
try {
|
||||||
val iconKey = state.value.iconKey.trim().removeSuffix(".png").removeSuffix(".PNG")
|
val response = call(ApiClient.create())
|
||||||
val api = ApiClient.create()
|
|
||||||
val response = api.updateAmenity(
|
|
||||||
amenityId,
|
|
||||||
AmenityUpdateRequest(
|
|
||||||
name = name,
|
|
||||||
category = state.value.category.trim().ifBlank { null },
|
|
||||||
iconKey = iconKey.ifBlank { null }
|
|
||||||
)
|
|
||||||
)
|
|
||||||
if (response.isSuccessful) {
|
if (response.isSuccessful) {
|
||||||
_state.update { it.copy(isLoading = false, success = true) }
|
_state.update { it.copy(isLoading = false, success = true) }
|
||||||
onDone()
|
onDone()
|
||||||
} else {
|
} else {
|
||||||
_state.update { it.copy(isLoading = false, error = "Update failed: ${response.code()}") }
|
_state.update { it.copy(isLoading = false, error = "$action failed: ${response.code()}") }
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
_state.update { it.copy(isLoading = false, error = e.localizedMessage ?: "Update failed") }
|
_state.update { it.copy(isLoading = false, error = e.localizedMessage ?: "$action failed") }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,60 +1,59 @@
|
|||||||
package com.android.trisolarispms.ui.roomtype
|
package com.android.trisolarispms.ui.roomtype
|
||||||
|
|
||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
import androidx.lifecycle.viewModelScope
|
|
||||||
import com.android.trisolarispms.data.api.core.ApiClient
|
import com.android.trisolarispms.data.api.core.ApiClient
|
||||||
|
import com.android.trisolarispms.core.viewmodel.launchRequest
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.StateFlow
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
import kotlinx.coroutines.flow.update
|
import kotlinx.coroutines.flow.update
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
|
|
||||||
class AmenityListViewModel : ViewModel() {
|
class AmenityListViewModel : ViewModel() {
|
||||||
private val _state = MutableStateFlow(AmenityListState())
|
private val _state = MutableStateFlow(AmenityListState())
|
||||||
val state: StateFlow<AmenityListState> = _state
|
val state: StateFlow<AmenityListState> = _state
|
||||||
|
|
||||||
fun load() {
|
fun load() {
|
||||||
viewModelScope.launch {
|
launchRequest(
|
||||||
_state.update { it.copy(isLoading = true, error = null) }
|
state = _state,
|
||||||
try {
|
setLoading = { it.copy(isLoading = true, error = null) },
|
||||||
val api = ApiClient.create()
|
setError = { current, message -> current.copy(isLoading = false, error = message) },
|
||||||
val response = api.listAmenities()
|
defaultError = "Load failed"
|
||||||
if (response.isSuccessful) {
|
) {
|
||||||
_state.update {
|
val api = ApiClient.create()
|
||||||
it.copy(
|
val response = api.listAmenities()
|
||||||
isLoading = false,
|
if (response.isSuccessful) {
|
||||||
items = response.body().orEmpty(),
|
_state.update {
|
||||||
error = null
|
it.copy(
|
||||||
)
|
isLoading = false,
|
||||||
}
|
items = response.body().orEmpty(),
|
||||||
} else {
|
error = null
|
||||||
_state.update { it.copy(isLoading = false, error = "Load failed: ${response.code()}") }
|
)
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} else {
|
||||||
_state.update { it.copy(isLoading = false, error = e.localizedMessage ?: "Load failed") }
|
_state.update { it.copy(isLoading = false, error = "Load failed: ${response.code()}") }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun deleteAmenity(amenityId: String) {
|
fun deleteAmenity(amenityId: String) {
|
||||||
if (amenityId.isBlank()) return
|
if (amenityId.isBlank()) return
|
||||||
viewModelScope.launch {
|
launchRequest(
|
||||||
_state.update { it.copy(isLoading = true, error = null) }
|
state = _state,
|
||||||
try {
|
setLoading = { it.copy(isLoading = true, error = null) },
|
||||||
val api = ApiClient.create()
|
setError = { current, message -> current.copy(isLoading = false, error = message) },
|
||||||
val response = api.deleteAmenity(amenityId)
|
defaultError = "Delete failed"
|
||||||
if (response.isSuccessful) {
|
) {
|
||||||
_state.update { current ->
|
val api = ApiClient.create()
|
||||||
current.copy(
|
val response = api.deleteAmenity(amenityId)
|
||||||
isLoading = false,
|
if (response.isSuccessful) {
|
||||||
items = current.items.filterNot { it.id == amenityId },
|
_state.update { current ->
|
||||||
error = null
|
current.copy(
|
||||||
)
|
isLoading = false,
|
||||||
}
|
items = current.items.filterNot { it.id == amenityId },
|
||||||
} else {
|
error = null
|
||||||
_state.update { it.copy(isLoading = false, error = "Delete failed: ${response.code()}") }
|
)
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} else {
|
||||||
_state.update { it.copy(isLoading = false, error = e.localizedMessage ?: "Delete failed") }
|
_state.update { it.copy(isLoading = false, error = "Delete failed: ${response.code()}") }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
package com.android.trisolarispms.ui.roomtype
|
package com.android.trisolarispms.ui.roomtype
|
||||||
|
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
|
||||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||||
import com.android.trisolarispms.data.api.model.AmenityDto
|
import com.android.trisolarispms.data.api.model.AmenityDto
|
||||||
|
|
||||||
@@ -13,16 +12,15 @@ fun EditAmenityScreen(
|
|||||||
viewModel: AmenityFormViewModel = viewModel(),
|
viewModel: AmenityFormViewModel = viewModel(),
|
||||||
amenityListViewModel: AmenityListViewModel = viewModel()
|
amenityListViewModel: AmenityListViewModel = viewModel()
|
||||||
) {
|
) {
|
||||||
LaunchedEffect(amenity.id) {
|
AmenityEditorScreen(
|
||||||
viewModel.setAmenity(amenity)
|
|
||||||
}
|
|
||||||
AmenityFormScreen(
|
|
||||||
title = "Edit Amenity",
|
title = "Edit Amenity",
|
||||||
|
setupKey = amenity.id,
|
||||||
onBack = onBack,
|
onBack = onBack,
|
||||||
onSaveClick = {
|
onSaveClick = {
|
||||||
val id = amenity.id.orEmpty()
|
val id = amenity.id.orEmpty()
|
||||||
viewModel.submitUpdate(id, onSave)
|
viewModel.submitUpdate(id, onSave)
|
||||||
},
|
},
|
||||||
|
setupForm = { viewModel.setAmenity(amenity) },
|
||||||
viewModel = viewModel,
|
viewModel = viewModel,
|
||||||
amenityListViewModel = amenityListViewModel
|
amenityListViewModel = amenityListViewModel
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -3,12 +3,14 @@ package com.android.trisolarispms.ui.roomtype
|
|||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
import com.android.trisolarispms.data.api.core.ApiClient
|
import com.android.trisolarispms.data.api.core.ApiClient
|
||||||
|
import com.android.trisolarispms.data.api.core.ApiService
|
||||||
import com.android.trisolarispms.data.api.model.RoomTypeCreateRequest
|
import com.android.trisolarispms.data.api.model.RoomTypeCreateRequest
|
||||||
import com.android.trisolarispms.data.api.model.RoomTypeUpdateRequest
|
import com.android.trisolarispms.data.api.model.RoomTypeUpdateRequest
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.StateFlow
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
import kotlinx.coroutines.flow.update
|
import kotlinx.coroutines.flow.update
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
import retrofit2.Response
|
||||||
|
|
||||||
class RoomTypeFormViewModel : ViewModel() {
|
class RoomTypeFormViewModel : ViewModel() {
|
||||||
private val _state = MutableStateFlow(RoomTypeFormState())
|
private val _state = MutableStateFlow(RoomTypeFormState())
|
||||||
@@ -48,70 +50,22 @@ class RoomTypeFormViewModel : ViewModel() {
|
|||||||
fun onAliasesChange(value: String) = _state.update { it.copy(otaAliases = value, error = null) }
|
fun onAliasesChange(value: String) = _state.update { it.copy(otaAliases = value, error = null) }
|
||||||
|
|
||||||
fun submit(propertyId: String, onDone: () -> Unit) {
|
fun submit(propertyId: String, onDone: () -> Unit) {
|
||||||
val code = state.value.code.trim()
|
val payload = readValidatedPayload() ?: return
|
||||||
val name = state.value.name.trim()
|
submitMutation(
|
||||||
if (code.isBlank() || name.isBlank()) {
|
action = "Create",
|
||||||
_state.update { it.copy(error = "Code and name are required") }
|
onDone = onDone
|
||||||
return
|
) { api ->
|
||||||
}
|
api.createRoomType(propertyId, payload.toCreateRequest())
|
||||||
viewModelScope.launch {
|
|
||||||
_state.update { it.copy(isLoading = true, error = null) }
|
|
||||||
try {
|
|
||||||
val api = ApiClient.create()
|
|
||||||
val body = RoomTypeCreateRequest(
|
|
||||||
code = code,
|
|
||||||
name = name,
|
|
||||||
baseOccupancy = state.value.baseOccupancy.toIntOrNull(),
|
|
||||||
maxOccupancy = state.value.maxOccupancy.toIntOrNull(),
|
|
||||||
sqFeet = state.value.sqFeet.toIntOrNull(),
|
|
||||||
bathroomSqFeet = state.value.bathroomSqFeet.toIntOrNull(),
|
|
||||||
amenityIds = state.value.amenityIds.toList().ifEmpty { null },
|
|
||||||
otaAliases = state.value.otaAliases.split(',').map { it.trim() }.filter { it.isNotBlank() }.ifEmpty { null }
|
|
||||||
)
|
|
||||||
val response = api.createRoomType(propertyId, body)
|
|
||||||
if (response.isSuccessful) {
|
|
||||||
_state.update { it.copy(isLoading = false, success = true) }
|
|
||||||
onDone()
|
|
||||||
} else {
|
|
||||||
_state.update { it.copy(isLoading = false, error = "Create failed: ${response.code()}") }
|
|
||||||
}
|
|
||||||
} catch (e: Exception) {
|
|
||||||
_state.update { it.copy(isLoading = false, error = e.localizedMessage ?: "Create failed") }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun submitUpdate(propertyId: String, roomTypeId: String, onDone: () -> Unit) {
|
fun submitUpdate(propertyId: String, roomTypeId: String, onDone: () -> Unit) {
|
||||||
val code = state.value.code.trim()
|
val payload = readValidatedPayload() ?: return
|
||||||
val name = state.value.name.trim()
|
submitMutation(
|
||||||
if (code.isBlank() || name.isBlank()) {
|
action = "Update",
|
||||||
_state.update { it.copy(error = "Code and name are required") }
|
onDone = onDone
|
||||||
return
|
) { api ->
|
||||||
}
|
api.updateRoomType(propertyId, roomTypeId, payload.toUpdateRequest())
|
||||||
viewModelScope.launch {
|
|
||||||
_state.update { it.copy(isLoading = true, error = null) }
|
|
||||||
try {
|
|
||||||
val api = ApiClient.create()
|
|
||||||
val body = RoomTypeUpdateRequest(
|
|
||||||
code = code,
|
|
||||||
name = name,
|
|
||||||
baseOccupancy = state.value.baseOccupancy.toIntOrNull(),
|
|
||||||
maxOccupancy = state.value.maxOccupancy.toIntOrNull(),
|
|
||||||
sqFeet = state.value.sqFeet.toIntOrNull(),
|
|
||||||
bathroomSqFeet = state.value.bathroomSqFeet.toIntOrNull(),
|
|
||||||
amenityIds = state.value.amenityIds.toList().ifEmpty { null },
|
|
||||||
otaAliases = state.value.otaAliases.split(',').map { it.trim() }.filter { it.isNotBlank() }.ifEmpty { null }
|
|
||||||
)
|
|
||||||
val response = api.updateRoomType(propertyId, roomTypeId, body)
|
|
||||||
if (response.isSuccessful) {
|
|
||||||
_state.update { it.copy(isLoading = false, success = true) }
|
|
||||||
onDone()
|
|
||||||
} else {
|
|
||||||
_state.update { it.copy(isLoading = false, error = "Update failed: ${response.code()}") }
|
|
||||||
}
|
|
||||||
} catch (e: Exception) {
|
|
||||||
_state.update { it.copy(isLoading = false, error = e.localizedMessage ?: "Update failed") }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -120,19 +74,88 @@ class RoomTypeFormViewModel : ViewModel() {
|
|||||||
_state.update { it.copy(error = "Room type ID is missing") }
|
_state.update { it.copy(error = "Room type ID is missing") }
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
submitMutation(
|
||||||
|
action = "Delete",
|
||||||
|
onDone = onDone
|
||||||
|
) { api ->
|
||||||
|
api.deleteRoomType(propertyId, roomTypeId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private data class RoomTypePayload(
|
||||||
|
val code: String,
|
||||||
|
val name: String,
|
||||||
|
val baseOccupancy: Int?,
|
||||||
|
val maxOccupancy: Int?,
|
||||||
|
val sqFeet: Int?,
|
||||||
|
val bathroomSqFeet: Int?,
|
||||||
|
val amenityIds: List<String>?,
|
||||||
|
val otaAliases: List<String>?
|
||||||
|
) {
|
||||||
|
fun toCreateRequest(): RoomTypeCreateRequest = RoomTypeCreateRequest(
|
||||||
|
code = code,
|
||||||
|
name = name,
|
||||||
|
baseOccupancy = baseOccupancy,
|
||||||
|
maxOccupancy = maxOccupancy,
|
||||||
|
sqFeet = sqFeet,
|
||||||
|
bathroomSqFeet = bathroomSqFeet,
|
||||||
|
amenityIds = amenityIds,
|
||||||
|
otaAliases = otaAliases
|
||||||
|
)
|
||||||
|
|
||||||
|
fun toUpdateRequest(): RoomTypeUpdateRequest = RoomTypeUpdateRequest(
|
||||||
|
code = code,
|
||||||
|
name = name,
|
||||||
|
baseOccupancy = baseOccupancy,
|
||||||
|
maxOccupancy = maxOccupancy,
|
||||||
|
sqFeet = sqFeet,
|
||||||
|
bathroomSqFeet = bathroomSqFeet,
|
||||||
|
amenityIds = amenityIds,
|
||||||
|
otaAliases = otaAliases
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun readValidatedPayload(): RoomTypePayload? {
|
||||||
|
val current = state.value
|
||||||
|
val code = current.code.trim()
|
||||||
|
val name = current.name.trim()
|
||||||
|
if (code.isBlank() || name.isBlank()) {
|
||||||
|
_state.update { it.copy(error = "Code and name are required") }
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
return RoomTypePayload(
|
||||||
|
code = code,
|
||||||
|
name = name,
|
||||||
|
baseOccupancy = current.baseOccupancy.toIntOrNull(),
|
||||||
|
maxOccupancy = current.maxOccupancy.toIntOrNull(),
|
||||||
|
sqFeet = current.sqFeet.toIntOrNull(),
|
||||||
|
bathroomSqFeet = current.bathroomSqFeet.toIntOrNull(),
|
||||||
|
amenityIds = current.amenityIds.toList().ifEmpty { null },
|
||||||
|
otaAliases = current.otaAliases
|
||||||
|
.split(',')
|
||||||
|
.map { it.trim() }
|
||||||
|
.filter { it.isNotBlank() }
|
||||||
|
.ifEmpty { null }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun submitMutation(
|
||||||
|
action: String,
|
||||||
|
onDone: () -> Unit,
|
||||||
|
call: suspend (ApiService) -> Response<*>
|
||||||
|
) {
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
_state.update { it.copy(isLoading = true, error = null) }
|
_state.update { it.copy(isLoading = true, error = null) }
|
||||||
try {
|
try {
|
||||||
val api = ApiClient.create()
|
val response = call(ApiClient.create())
|
||||||
val response = api.deleteRoomType(propertyId, roomTypeId)
|
|
||||||
if (response.isSuccessful) {
|
if (response.isSuccessful) {
|
||||||
_state.update { it.copy(isLoading = false, success = true) }
|
_state.update { it.copy(isLoading = false, success = true) }
|
||||||
onDone()
|
onDone()
|
||||||
} else {
|
} else {
|
||||||
_state.update { it.copy(isLoading = false, error = "Delete failed: ${response.code()}") }
|
_state.update { it.copy(isLoading = false, error = "$action failed: ${response.code()}") }
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
_state.update { it.copy(isLoading = false, error = e.localizedMessage ?: "Delete failed") }
|
_state.update { it.copy(isLoading = false, error = e.localizedMessage ?: "$action failed") }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,9 @@ package com.android.trisolarispms.ui.roomtype
|
|||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
import com.android.trisolarispms.data.api.core.ApiClient
|
import com.android.trisolarispms.data.api.core.ApiClient
|
||||||
|
import com.android.trisolarispms.data.api.core.ApiService
|
||||||
|
import com.android.trisolarispms.data.api.model.RoomTypeDto
|
||||||
|
import com.android.trisolarispms.core.viewmodel.launchRequest
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.StateFlow
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
import kotlinx.coroutines.flow.update
|
import kotlinx.coroutines.flow.update
|
||||||
@@ -14,76 +17,81 @@ class RoomTypeListViewModel : ViewModel() {
|
|||||||
|
|
||||||
fun load(propertyId: String) {
|
fun load(propertyId: String) {
|
||||||
if (propertyId.isBlank()) return
|
if (propertyId.isBlank()) return
|
||||||
viewModelScope.launch {
|
launchRequest(
|
||||||
_state.update { it.copy(isLoading = true, error = null) }
|
state = _state,
|
||||||
try {
|
setLoading = { it.copy(isLoading = true, error = null) },
|
||||||
val api = ApiClient.create()
|
setError = { current, message -> current.copy(isLoading = false, error = message) },
|
||||||
val response = api.listRoomTypes(propertyId)
|
defaultError = "Load failed"
|
||||||
if (response.isSuccessful) {
|
) {
|
||||||
val items = response.body().orEmpty()
|
val api = ApiClient.create()
|
||||||
_state.update {
|
val response = api.listRoomTypes(propertyId)
|
||||||
it.copy(
|
if (response.isSuccessful) {
|
||||||
isLoading = false,
|
val items = response.body().orEmpty()
|
||||||
items = items,
|
_state.update {
|
||||||
error = null
|
it.copy(
|
||||||
)
|
isLoading = false,
|
||||||
}
|
items = items,
|
||||||
loadRoomTypeImages(propertyId, items)
|
error = null
|
||||||
loadRoomTypeAvailableCounts(propertyId, items)
|
)
|
||||||
} else {
|
|
||||||
_state.update { it.copy(isLoading = false, error = "Load failed: ${response.code()}") }
|
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
loadRoomTypeImages(propertyId, items)
|
||||||
_state.update { it.copy(isLoading = false, error = e.localizedMessage ?: "Load failed") }
|
loadRoomTypeAvailableCounts(propertyId, items)
|
||||||
|
} else {
|
||||||
|
_state.update { it.copy(isLoading = false, error = "Load failed: ${response.code()}") }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun loadRoomTypeImages(propertyId: String, items: List<com.android.trisolarispms.data.api.model.RoomTypeDto>) {
|
private fun loadRoomTypeImages(propertyId: String, items: List<RoomTypeDto>) {
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
val api = ApiClient.create()
|
val updates = buildByTypeCodeMap(
|
||||||
val updates = mutableMapOf<String, com.android.trisolarispms.data.api.model.ImageDto?>()
|
items = items,
|
||||||
for (item in items) {
|
fetch = { api, code ->
|
||||||
val code = item.code?.trim().orEmpty()
|
|
||||||
if (code.isBlank()) continue
|
|
||||||
try {
|
|
||||||
val response = api.listRoomTypeImages(propertyId, code)
|
val response = api.listRoomTypeImages(propertyId, code)
|
||||||
if (response.isSuccessful) {
|
if (response.isSuccessful) response.body().orEmpty().firstOrNull() else null
|
||||||
updates[code] = response.body().orEmpty().firstOrNull()
|
|
||||||
}
|
|
||||||
} catch (_: Exception) {
|
|
||||||
// Ignore per-item failures to avoid blocking the list.
|
|
||||||
}
|
}
|
||||||
}
|
)
|
||||||
if (updates.isNotEmpty()) {
|
if (updates.isNotEmpty()) {
|
||||||
_state.update { it.copy(imageByTypeCode = it.imageByTypeCode + updates) }
|
_state.update { it.copy(imageByTypeCode = it.imageByTypeCode + updates) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun loadRoomTypeAvailableCounts(propertyId: String, items: List<com.android.trisolarispms.data.api.model.RoomTypeDto>) {
|
private fun loadRoomTypeAvailableCounts(propertyId: String, items: List<RoomTypeDto>) {
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
val api = ApiClient.create()
|
val updates = buildByTypeCodeMap(
|
||||||
val updates = mutableMapOf<String, Int>()
|
items = items,
|
||||||
for (item in items) {
|
fetch = { api, code ->
|
||||||
val code = item.code?.trim().orEmpty()
|
|
||||||
if (code.isBlank()) continue
|
|
||||||
try {
|
|
||||||
val response = api.listRoomsByType(
|
val response = api.listRoomsByType(
|
||||||
propertyId = propertyId,
|
propertyId = propertyId,
|
||||||
roomTypeCode = code,
|
roomTypeCode = code,
|
||||||
availableOnly = true
|
availableOnly = true
|
||||||
)
|
)
|
||||||
if (response.isSuccessful) {
|
if (response.isSuccessful) {
|
||||||
updates[code] = response.body().orEmpty().size
|
response.body().orEmpty().size
|
||||||
|
} else {
|
||||||
|
null
|
||||||
}
|
}
|
||||||
} catch (_: Exception) {
|
|
||||||
// Ignore per-item failures.
|
|
||||||
}
|
}
|
||||||
}
|
)
|
||||||
if (updates.isNotEmpty()) {
|
if (updates.isNotEmpty()) {
|
||||||
_state.update { it.copy(availableCountByTypeCode = it.availableCountByTypeCode + updates) }
|
_state.update { it.copy(availableCountByTypeCode = it.availableCountByTypeCode + updates) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private suspend fun <T> buildByTypeCodeMap(
|
||||||
|
items: List<RoomTypeDto>,
|
||||||
|
fetch: suspend (ApiService, String) -> T?
|
||||||
|
): Map<String, T> {
|
||||||
|
val api = ApiClient.create()
|
||||||
|
val updates = mutableMapOf<String, T>()
|
||||||
|
for (item in items) {
|
||||||
|
val code = item.code?.trim().orEmpty()
|
||||||
|
if (code.isBlank()) continue
|
||||||
|
val value = runCatching { fetch(api, code) }.getOrNull() ?: continue
|
||||||
|
updates[code] = value
|
||||||
|
}
|
||||||
|
return updates
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,9 +3,10 @@ package com.android.trisolarispms.ui.users
|
|||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
import com.android.trisolarispms.data.api.core.ApiClient
|
import com.android.trisolarispms.data.api.core.ApiClient
|
||||||
|
import com.android.trisolarispms.data.api.core.ApiService
|
||||||
import com.android.trisolarispms.data.api.model.PropertyAccessCodeCreateRequest
|
import com.android.trisolarispms.data.api.model.PropertyAccessCodeCreateRequest
|
||||||
import com.android.trisolarispms.data.api.model.PropertyAccessCodeResponse
|
import com.android.trisolarispms.data.api.model.PropertyAccessCodeResponse
|
||||||
import com.android.trisolarispms.data.api.model.PropertyUserResponse
|
import com.android.trisolarispms.data.api.model.PropertyUserDetailsResponse
|
||||||
import com.android.trisolarispms.data.api.model.PropertyAccessCodeJoinRequest
|
import com.android.trisolarispms.data.api.model.PropertyAccessCodeJoinRequest
|
||||||
import com.android.trisolarispms.data.api.model.UserRolesUpdateRequest
|
import com.android.trisolarispms.data.api.model.UserRolesUpdateRequest
|
||||||
import com.android.trisolarispms.data.api.model.PropertyUserDisabledRequest
|
import com.android.trisolarispms.data.api.model.PropertyUserDisabledRequest
|
||||||
@@ -28,33 +29,7 @@ class PropertyUsersViewModel : ViewModel() {
|
|||||||
val state: StateFlow<PropertyUsersState> = _state
|
val state: StateFlow<PropertyUsersState> = _state
|
||||||
|
|
||||||
fun loadAll(propertyId: String) {
|
fun loadAll(propertyId: String) {
|
||||||
viewModelScope.launch {
|
fetchUsers(propertyId = propertyId, phone = null, action = "Load")
|
||||||
_state.update { it.copy(isLoading = true, error = null, message = null) }
|
|
||||||
try {
|
|
||||||
val response = ApiClient.create().searchPropertyUsers(
|
|
||||||
propertyId = propertyId,
|
|
||||||
phone = null
|
|
||||||
)
|
|
||||||
val body = response.body()
|
|
||||||
if (response.isSuccessful && body != null) {
|
|
||||||
val mapped = body.map {
|
|
||||||
PropertyUserUi(
|
|
||||||
userId = it.userId,
|
|
||||||
roles = it.roles,
|
|
||||||
name = it.name,
|
|
||||||
phoneE164 = it.phoneE164,
|
|
||||||
disabled = it.disabled,
|
|
||||||
superAdmin = it.superAdmin
|
|
||||||
)
|
|
||||||
}
|
|
||||||
_state.update { it.copy(isLoading = false, users = mapped, error = null) }
|
|
||||||
} else {
|
|
||||||
_state.update { it.copy(isLoading = false, error = "Load failed: ${response.code()}") }
|
|
||||||
}
|
|
||||||
} catch (e: Exception) {
|
|
||||||
_state.update { it.copy(isLoading = false, error = e.localizedMessage ?: "Load failed") }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun searchUsers(propertyId: String, phoneInput: String) {
|
fun searchUsers(propertyId: String, phoneInput: String) {
|
||||||
@@ -63,33 +38,7 @@ class PropertyUsersViewModel : ViewModel() {
|
|||||||
_state.update { it.copy(users = emptyList(), error = null, isLoading = false, message = null) }
|
_state.update { it.copy(users = emptyList(), error = null, isLoading = false, message = null) }
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
viewModelScope.launch {
|
fetchUsers(propertyId = propertyId, phone = digits, action = "Search")
|
||||||
_state.update { it.copy(isLoading = true, error = null, message = null) }
|
|
||||||
try {
|
|
||||||
val response = ApiClient.create().searchPropertyUsers(
|
|
||||||
propertyId = propertyId,
|
|
||||||
phone = digits
|
|
||||||
)
|
|
||||||
val body = response.body()
|
|
||||||
if (response.isSuccessful && body != null) {
|
|
||||||
val mapped = body.map {
|
|
||||||
PropertyUserUi(
|
|
||||||
userId = it.userId,
|
|
||||||
roles = it.roles,
|
|
||||||
name = it.name,
|
|
||||||
phoneE164 = it.phoneE164,
|
|
||||||
disabled = it.disabled,
|
|
||||||
superAdmin = it.superAdmin
|
|
||||||
)
|
|
||||||
}
|
|
||||||
_state.update { it.copy(isLoading = false, users = mapped, error = null) }
|
|
||||||
} else {
|
|
||||||
_state.update { it.copy(isLoading = false, error = "Search failed: ${response.code()}") }
|
|
||||||
}
|
|
||||||
} catch (e: Exception) {
|
|
||||||
_state.update { it.copy(isLoading = false, error = e.localizedMessage ?: "Search failed") }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun createAccessCode(propertyId: String, roles: List<String>) {
|
fun createAccessCode(propertyId: String, roles: List<String>) {
|
||||||
@@ -97,27 +46,22 @@ class PropertyUsersViewModel : ViewModel() {
|
|||||||
_state.update { it.copy(error = "Select at least one role", message = null) }
|
_state.update { it.copy(error = "Select at least one role", message = null) }
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
viewModelScope.launch {
|
runAction(defaultError = "Create failed") { api ->
|
||||||
_state.update { it.copy(isLoading = true, error = null, message = null) }
|
val response = api.createAccessCode(
|
||||||
try {
|
propertyId = propertyId,
|
||||||
val response = ApiClient.create().createAccessCode(
|
body = PropertyAccessCodeCreateRequest(roles = roles)
|
||||||
propertyId = propertyId,
|
)
|
||||||
body = PropertyAccessCodeCreateRequest(roles = roles)
|
val body = response.body()
|
||||||
)
|
if (response.isSuccessful && body != null) {
|
||||||
val body = response.body()
|
_state.update {
|
||||||
if (response.isSuccessful && body != null) {
|
it.copy(
|
||||||
_state.update {
|
isLoading = false,
|
||||||
it.copy(
|
accessCode = body,
|
||||||
isLoading = false,
|
message = "Access code created"
|
||||||
accessCode = body,
|
)
|
||||||
message = "Access code created"
|
|
||||||
)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
_state.update { it.copy(isLoading = false, error = "Create failed: ${response.code()}") }
|
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} else {
|
||||||
_state.update { it.copy(isLoading = false, error = e.localizedMessage ?: "Create failed") }
|
_state.update { it.copy(isLoading = false, error = "Create failed: ${response.code()}") }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -128,26 +72,21 @@ class PropertyUsersViewModel : ViewModel() {
|
|||||||
_state.update { it.copy(error = "Code must be 6 digits", message = null) }
|
_state.update { it.copy(error = "Code must be 6 digits", message = null) }
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
viewModelScope.launch {
|
runAction(defaultError = "Join failed") { api ->
|
||||||
_state.update { it.copy(isLoading = true, error = null, message = null) }
|
val response = api.joinAccessCode(
|
||||||
try {
|
PropertyAccessCodeJoinRequest(propertyId = propertyId, code = digits)
|
||||||
val response = ApiClient.create().joinAccessCode(
|
)
|
||||||
PropertyAccessCodeJoinRequest(propertyId = propertyId, code = digits)
|
val body = response.body()
|
||||||
)
|
if (response.isSuccessful && body != null) {
|
||||||
val body = response.body()
|
_state.update {
|
||||||
if (response.isSuccessful && body != null) {
|
it.copy(
|
||||||
_state.update {
|
isLoading = false,
|
||||||
it.copy(
|
message = "Joined property",
|
||||||
isLoading = false,
|
error = null
|
||||||
message = "Joined property",
|
)
|
||||||
error = null
|
|
||||||
)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
_state.update { it.copy(isLoading = false, error = "Join failed: ${response.code()}") }
|
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} else {
|
||||||
_state.update { it.copy(isLoading = false, error = e.localizedMessage ?: "Join failed") }
|
_state.update { it.copy(isLoading = false, error = "Join failed: ${response.code()}") }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -157,64 +96,50 @@ class PropertyUsersViewModel : ViewModel() {
|
|||||||
_state.update { it.copy(error = "Select at least one role", message = null) }
|
_state.update { it.copy(error = "Select at least one role", message = null) }
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
viewModelScope.launch {
|
runAction(defaultError = "Update failed") { api ->
|
||||||
_state.update { it.copy(isLoading = true, error = null, message = null) }
|
val response = api.updateUserRoles(
|
||||||
try {
|
propertyId = propertyId,
|
||||||
val response = ApiClient.create().updateUserRoles(
|
userId = userId,
|
||||||
propertyId = propertyId,
|
body = UserRolesUpdateRequest(roles = roles)
|
||||||
userId = userId,
|
)
|
||||||
body = UserRolesUpdateRequest(roles = roles)
|
val body = response.body()
|
||||||
)
|
if (response.isSuccessful && body != null) {
|
||||||
val body = response.body()
|
_state.update { current ->
|
||||||
if (response.isSuccessful && body != null) {
|
val updated = current.users.map { user ->
|
||||||
_state.update { current ->
|
if (user.userId == userId) user.copy(roles = body.roles) else user
|
||||||
val updated = current.users.map { user ->
|
|
||||||
if (user.userId == userId) {
|
|
||||||
user.copy(roles = body.roles)
|
|
||||||
} else user
|
|
||||||
}
|
|
||||||
current.copy(
|
|
||||||
isLoading = false,
|
|
||||||
users = updated,
|
|
||||||
message = "Roles updated"
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
} else {
|
current.copy(
|
||||||
_state.update { it.copy(isLoading = false, error = "Update failed: ${response.code()}") }
|
isLoading = false,
|
||||||
|
users = updated,
|
||||||
|
message = "Roles updated"
|
||||||
|
)
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} else {
|
||||||
_state.update { it.copy(isLoading = false, error = e.localizedMessage ?: "Update failed") }
|
_state.update { it.copy(isLoading = false, error = "Update failed: ${response.code()}") }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun updateDisabled(propertyId: String, userId: String, disabled: Boolean) {
|
fun updateDisabled(propertyId: String, userId: String, disabled: Boolean) {
|
||||||
viewModelScope.launch {
|
runAction(defaultError = "Update failed") { api ->
|
||||||
_state.update { it.copy(isLoading = true, error = null, message = null) }
|
val response = api.updateUserDisabled(
|
||||||
try {
|
propertyId = propertyId,
|
||||||
val response = ApiClient.create().updateUserDisabled(
|
userId = userId,
|
||||||
propertyId = propertyId,
|
body = PropertyUserDisabledRequest(disabled = disabled)
|
||||||
userId = userId,
|
)
|
||||||
body = PropertyUserDisabledRequest(disabled = disabled)
|
if (response.isSuccessful) {
|
||||||
)
|
_state.update { current ->
|
||||||
if (response.isSuccessful) {
|
val updated = current.users.map { user ->
|
||||||
_state.update { current ->
|
if (user.userId == userId) user.copy(disabled = disabled) else user
|
||||||
val updated = current.users.map { user ->
|
|
||||||
if (user.userId == userId) {
|
|
||||||
user.copy(disabled = disabled)
|
|
||||||
} else user
|
|
||||||
}
|
|
||||||
current.copy(
|
|
||||||
isLoading = false,
|
|
||||||
users = updated,
|
|
||||||
message = if (disabled) "User disabled" else "User enabled"
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
} else {
|
current.copy(
|
||||||
_state.update { it.copy(isLoading = false, error = "Update failed: ${response.code()}") }
|
isLoading = false,
|
||||||
|
users = updated,
|
||||||
|
message = if (disabled) "User disabled" else "User enabled"
|
||||||
|
)
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} else {
|
||||||
_state.update { it.copy(isLoading = false, error = e.localizedMessage ?: "Update failed") }
|
_state.update { it.copy(isLoading = false, error = "Update failed: ${response.code()}") }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -232,4 +157,41 @@ class PropertyUsersViewModel : ViewModel() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun fetchUsers(propertyId: String, phone: String?, action: String) {
|
||||||
|
runAction(defaultError = "$action failed") { api ->
|
||||||
|
val response = api.searchPropertyUsers(propertyId = propertyId, phone = phone)
|
||||||
|
val body = response.body()
|
||||||
|
if (response.isSuccessful && body != null) {
|
||||||
|
_state.update { it.copy(isLoading = false, users = mapPropertyUsers(body), error = null) }
|
||||||
|
} else {
|
||||||
|
_state.update { it.copy(isLoading = false, error = "$action failed: ${response.code()}") }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun mapPropertyUsers(body: List<PropertyUserDetailsResponse>): List<PropertyUserUi> = body.map {
|
||||||
|
PropertyUserUi(
|
||||||
|
userId = it.userId,
|
||||||
|
roles = it.roles,
|
||||||
|
name = it.name,
|
||||||
|
phoneE164 = it.phoneE164,
|
||||||
|
disabled = it.disabled,
|
||||||
|
superAdmin = it.superAdmin
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun runAction(
|
||||||
|
defaultError: String,
|
||||||
|
block: suspend (ApiService) -> Unit
|
||||||
|
) {
|
||||||
|
viewModelScope.launch {
|
||||||
|
_state.update { it.copy(isLoading = true, error = null, message = null) }
|
||||||
|
try {
|
||||||
|
block(ApiClient.create())
|
||||||
|
} catch (e: Exception) {
|
||||||
|
_state.update { it.copy(isLoading = false, error = e.localizedMessage ?: defaultError) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,8 @@ package com.android.trisolarispms.ui.users
|
|||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
import com.android.trisolarispms.data.api.core.ApiClient
|
import com.android.trisolarispms.data.api.core.ApiClient
|
||||||
|
import com.android.trisolarispms.data.api.core.ApiService
|
||||||
|
import com.android.trisolarispms.core.viewmodel.launchRequest
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.StateFlow
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
import kotlinx.coroutines.flow.update
|
import kotlinx.coroutines.flow.update
|
||||||
@@ -25,37 +27,11 @@ class UserDirectoryViewModel : ViewModel() {
|
|||||||
val state: StateFlow<UserDirectoryState> = _state
|
val state: StateFlow<UserDirectoryState> = _state
|
||||||
|
|
||||||
fun loadAll(mode: UserDirectoryMode) {
|
fun loadAll(mode: UserDirectoryMode) {
|
||||||
viewModelScope.launch {
|
runDirectoryQuery(
|
||||||
_state.update { it.copy(isLoading = true, error = null) }
|
mode = mode,
|
||||||
try {
|
phone = null,
|
||||||
val api = ApiClient.create()
|
action = "Load"
|
||||||
when (mode) {
|
)
|
||||||
UserDirectoryMode.SuperAdmin -> {
|
|
||||||
val response = api.listUsers(null)
|
|
||||||
val users = response.body()?.toSuperAdminUsers()
|
|
||||||
if (response.isSuccessful && users != null) {
|
|
||||||
_state.update { it.copy(isLoading = false, users = users, error = null) }
|
|
||||||
} else {
|
|
||||||
_state.update { it.copy(isLoading = false, error = "Load failed: ${response.code()}") }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
is UserDirectoryMode.Property -> {
|
|
||||||
val response = api.searchPropertyUsers(
|
|
||||||
propertyId = mode.propertyId,
|
|
||||||
phone = null
|
|
||||||
)
|
|
||||||
val users = response.body()?.toPropertyUsers()
|
|
||||||
if (response.isSuccessful && users != null) {
|
|
||||||
_state.update { it.copy(isLoading = false, users = users, error = null) }
|
|
||||||
} else {
|
|
||||||
_state.update { it.copy(isLoading = false, error = "Load failed: ${response.code()}") }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (e: Exception) {
|
|
||||||
_state.update { it.copy(isLoading = false, error = e.localizedMessage ?: "Load failed") }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun search(mode: UserDirectoryMode, phoneInput: String) {
|
fun search(mode: UserDirectoryMode, phoneInput: String) {
|
||||||
@@ -64,39 +40,65 @@ class UserDirectoryViewModel : ViewModel() {
|
|||||||
_state.update { it.copy(users = emptyList(), error = null, isLoading = false) }
|
_state.update { it.copy(users = emptyList(), error = null, isLoading = false) }
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
viewModelScope.launch {
|
runDirectoryQuery(
|
||||||
_state.update { it.copy(isLoading = true, error = null) }
|
mode = mode,
|
||||||
try {
|
phone = digits,
|
||||||
val api = ApiClient.create()
|
action = "Search"
|
||||||
when (mode) {
|
)
|
||||||
UserDirectoryMode.SuperAdmin -> {
|
}
|
||||||
val response = api.listUsers(digits)
|
|
||||||
val users = response.body()?.toSuperAdminUsers()
|
private data class UserQueryResult(
|
||||||
if (response.isSuccessful && users != null) {
|
val users: List<PropertyUserUi>?,
|
||||||
_state.update { it.copy(isLoading = false, users = users, error = null) }
|
val code: Int,
|
||||||
} else {
|
val isSuccessful: Boolean
|
||||||
_state.update { it.copy(isLoading = false, error = "Search failed: ${response.code()}") }
|
)
|
||||||
}
|
|
||||||
}
|
private fun runDirectoryQuery(
|
||||||
is UserDirectoryMode.Property -> {
|
mode: UserDirectoryMode,
|
||||||
val response = api.searchPropertyUsers(
|
phone: String?,
|
||||||
propertyId = mode.propertyId,
|
action: String
|
||||||
phone = digits
|
) {
|
||||||
)
|
launchRequest(
|
||||||
val users = response.body()?.toPropertyUsers()
|
state = _state,
|
||||||
if (response.isSuccessful && users != null) {
|
setLoading = { it.copy(isLoading = true, error = null) },
|
||||||
_state.update { it.copy(isLoading = false, users = users, error = null) }
|
setError = { current, message -> current.copy(isLoading = false, error = message) },
|
||||||
} else {
|
defaultError = "$action failed"
|
||||||
_state.update { it.copy(isLoading = false, error = "Search failed: ${response.code()}") }
|
) {
|
||||||
}
|
val result = queryUsers(ApiClient.create(), mode, phone)
|
||||||
}
|
if (result.isSuccessful && result.users != null) {
|
||||||
}
|
_state.update { it.copy(isLoading = false, users = result.users, error = null) }
|
||||||
} catch (e: Exception) {
|
} else {
|
||||||
_state.update { it.copy(isLoading = false, error = e.localizedMessage ?: "Search failed") }
|
_state.update { it.copy(isLoading = false, error = "$action failed: ${result.code}") }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private suspend fun queryUsers(
|
||||||
|
api: ApiService,
|
||||||
|
mode: UserDirectoryMode,
|
||||||
|
phone: String?
|
||||||
|
): UserQueryResult = when (mode) {
|
||||||
|
UserDirectoryMode.SuperAdmin -> {
|
||||||
|
val response = api.listUsers(phone)
|
||||||
|
UserQueryResult(
|
||||||
|
users = response.body()?.toSuperAdminUsers(),
|
||||||
|
code = response.code(),
|
||||||
|
isSuccessful = response.isSuccessful
|
||||||
|
)
|
||||||
|
}
|
||||||
|
is UserDirectoryMode.Property -> {
|
||||||
|
val response = api.searchPropertyUsers(
|
||||||
|
propertyId = mode.propertyId,
|
||||||
|
phone = phone
|
||||||
|
)
|
||||||
|
UserQueryResult(
|
||||||
|
users = response.body()?.toPropertyUsers(),
|
||||||
|
code = response.code(),
|
||||||
|
isSuccessful = response.isSuccessful
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun List<com.android.trisolarispms.data.api.model.AppUserSummaryResponse>.toSuperAdminUsers():
|
private fun List<com.android.trisolarispms.data.api.model.AppUserSummaryResponse>.toSuperAdminUsers():
|
||||||
List<PropertyUserUi> =
|
List<PropertyUserUi> =
|
||||||
map {
|
map {
|
||||||
|
|||||||
Reference in New Issue
Block a user