Compare commits
4 Commits
3a90aa848d
...
d69ed60a6e
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d69ed60a6e | ||
|
|
56f13f5e79 | ||
|
|
9555ae2e40 | ||
|
|
9d942d6411 |
@@ -56,6 +56,7 @@ data class BookingListItem(
|
|||||||
val guestId: String? = null,
|
val guestId: String? = null,
|
||||||
val guestName: String? = null,
|
val guestName: String? = null,
|
||||||
val guestPhone: String? = null,
|
val guestPhone: String? = null,
|
||||||
|
val vehicleNumbers: List<String> = emptyList(),
|
||||||
val roomNumbers: List<Int> = emptyList(),
|
val roomNumbers: List<Int> = emptyList(),
|
||||||
val source: String? = null,
|
val source: String? = null,
|
||||||
val fromCity: String? = null,
|
val fromCity: String? = null,
|
||||||
@@ -112,6 +113,22 @@ data class BookingBillableNightsResponse(
|
|||||||
val billableNights: Long? = null
|
val billableNights: Long? = null
|
||||||
)
|
)
|
||||||
|
|
||||||
|
data class BookingExpectedCheckoutPreviewRequest(
|
||||||
|
val checkInAt: String,
|
||||||
|
val billableNights: Int? = null,
|
||||||
|
val billingMode: BookingBillingMode? = null,
|
||||||
|
val billingCheckinTime: String? = null,
|
||||||
|
val billingCheckoutTime: String? = null
|
||||||
|
)
|
||||||
|
|
||||||
|
data class BookingExpectedCheckoutPreviewResponse(
|
||||||
|
val expectedCheckOutAt: String? = null,
|
||||||
|
val billableNights: Int? = null,
|
||||||
|
val billingMode: BookingBillingMode? = null,
|
||||||
|
val billingCheckinTime: String? = null,
|
||||||
|
val billingCheckoutTime: String? = null
|
||||||
|
)
|
||||||
|
|
||||||
data class BookingDetailsResponse(
|
data class BookingDetailsResponse(
|
||||||
val id: String? = null,
|
val id: String? = null,
|
||||||
val status: String? = null,
|
val status: String? = null,
|
||||||
|
|||||||
@@ -13,6 +13,8 @@ import com.android.trisolarispms.data.api.model.BookingListItem
|
|||||||
import com.android.trisolarispms.data.api.model.BookingBulkCheckInRequest
|
import com.android.trisolarispms.data.api.model.BookingBulkCheckInRequest
|
||||||
import com.android.trisolarispms.data.api.model.BookingBillableNightsRequest
|
import com.android.trisolarispms.data.api.model.BookingBillableNightsRequest
|
||||||
import com.android.trisolarispms.data.api.model.BookingBillableNightsResponse
|
import com.android.trisolarispms.data.api.model.BookingBillableNightsResponse
|
||||||
|
import com.android.trisolarispms.data.api.model.BookingExpectedCheckoutPreviewRequest
|
||||||
|
import com.android.trisolarispms.data.api.model.BookingExpectedCheckoutPreviewResponse
|
||||||
import com.android.trisolarispms.data.api.model.BookingExpectedDatesRequest
|
import com.android.trisolarispms.data.api.model.BookingExpectedDatesRequest
|
||||||
import com.android.trisolarispms.data.api.model.BookingDetailsResponse
|
import com.android.trisolarispms.data.api.model.BookingDetailsResponse
|
||||||
import com.android.trisolarispms.data.api.model.BookingBalanceResponse
|
import com.android.trisolarispms.data.api.model.BookingBalanceResponse
|
||||||
@@ -69,6 +71,12 @@ interface BookingApi {
|
|||||||
@Body body: BookingBillableNightsRequest
|
@Body body: BookingBillableNightsRequest
|
||||||
): Response<BookingBillableNightsResponse>
|
): Response<BookingBillableNightsResponse>
|
||||||
|
|
||||||
|
@POST("properties/{propertyId}/bookings/expected-checkout-preview")
|
||||||
|
suspend fun previewExpectedCheckout(
|
||||||
|
@Path("propertyId") propertyId: String,
|
||||||
|
@Body body: BookingExpectedCheckoutPreviewRequest
|
||||||
|
): Response<BookingExpectedCheckoutPreviewResponse>
|
||||||
|
|
||||||
@POST("properties/{propertyId}/bookings/{bookingId}/billing-policy")
|
@POST("properties/{propertyId}/bookings/{bookingId}/billing-policy")
|
||||||
suspend fun updateBookingBillingPolicy(
|
suspend fun updateBookingBillingPolicy(
|
||||||
@Path("propertyId") propertyId: String,
|
@Path("propertyId") propertyId: String,
|
||||||
|
|||||||
@@ -59,31 +59,43 @@ fun BookingCreateScreen(
|
|||||||
val checkOutTime = remember { mutableStateOf("11:00") }
|
val checkOutTime = remember { mutableStateOf("11:00") }
|
||||||
val checkInNow = remember { mutableStateOf(true) }
|
val checkInNow = remember { mutableStateOf(true) }
|
||||||
val sourceMenuExpanded = remember { mutableStateOf(false) }
|
val sourceMenuExpanded = remember { mutableStateOf(false) }
|
||||||
val sourceOptions = listOf("WALKIN", "OTA", "AGENT")
|
val sourceOptions = listOf("DIRECT", "AGENT")
|
||||||
val relationMenuExpanded = remember { mutableStateOf(false) }
|
val relationMenuExpanded = remember { mutableStateOf(false) }
|
||||||
val relationOptions = listOf("FRIENDS", "FAMILY", "GROUP", "ALONE")
|
val relationOptions = listOf("FRIENDS", "FAMILY", "GROUP", "ALONE")
|
||||||
val transportMenuExpanded = remember { mutableStateOf(false) }
|
val transportMenuExpanded = remember { mutableStateOf(false) }
|
||||||
val transportOptions = listOf("CAR", "BIKE", "TRAIN", "PLANE", "BUS", "FOOT", "CYCLE", "OTHER")
|
val transportOptions = listOf("CAR", "BIKE", "TRAIN", "PLANE", "BUS", "FOOT", "CYCLE", "OTHER")
|
||||||
val billingModeMenuExpanded = remember { mutableStateOf(false) }
|
val billingModeMenuExpanded = remember { mutableStateOf(false) }
|
||||||
val displayFormatter = remember { DateTimeFormatter.ofPattern("dd-MM-yy HH:mm") }
|
val displayFormatter = remember { DateTimeFormatter.ofPattern("dd-MM-yy HH:mm") }
|
||||||
|
val timeFormatter = remember { DateTimeFormatter.ofPattern("HH:mm") }
|
||||||
val phoneCountryMenuExpanded = remember { mutableStateOf(false) }
|
val phoneCountryMenuExpanded = remember { mutableStateOf(false) }
|
||||||
val phoneCountries = remember { phoneCountryOptions() }
|
val phoneCountries = remember { phoneCountryOptions() }
|
||||||
val phoneCountrySearch = remember { mutableStateOf("") }
|
val phoneCountrySearch = remember { mutableStateOf("") }
|
||||||
|
|
||||||
|
val applyCheckInSelection: (LocalDate, String) -> Unit = { date, time ->
|
||||||
|
checkInDate.value = date
|
||||||
|
checkInTime.value = time
|
||||||
|
val checkInAt = formatBookingIso(date, time)
|
||||||
|
viewModel.onExpectedCheckInAtChange(checkInAt)
|
||||||
|
viewModel.autoSetBillingFromCheckIn(checkInAt)
|
||||||
|
viewModel.refreshExpectedCheckoutPreview(propertyId)
|
||||||
|
}
|
||||||
|
|
||||||
LaunchedEffect(propertyId) {
|
LaunchedEffect(propertyId) {
|
||||||
viewModel.reset()
|
viewModel.reset()
|
||||||
viewModel.loadBillingPolicy(propertyId)
|
viewModel.loadBillingPolicy(propertyId)
|
||||||
val now = OffsetDateTime.now()
|
val now = OffsetDateTime.now()
|
||||||
checkInDate.value = now.toLocalDate()
|
|
||||||
checkInTime.value = now.format(DateTimeFormatter.ofPattern("HH:mm"))
|
|
||||||
val nowIso = now.format(DateTimeFormatter.ISO_OFFSET_DATE_TIME)
|
|
||||||
viewModel.onExpectedCheckInAtChange(nowIso)
|
|
||||||
viewModel.autoSetBillingFromCheckIn(nowIso)
|
|
||||||
checkInNow.value = true
|
checkInNow.value = true
|
||||||
val defaultCheckoutDate = now.toLocalDate().plusDays(1)
|
val defaultCheckoutDate = now.toLocalDate().plusDays(1)
|
||||||
checkOutDate.value = defaultCheckoutDate
|
checkOutDate.value = defaultCheckoutDate
|
||||||
checkOutTime.value = "11:00"
|
checkOutTime.value = "11:00"
|
||||||
viewModel.onExpectedCheckOutAtChange(formatBookingIso(defaultCheckoutDate, checkOutTime.value))
|
viewModel.onExpectedCheckOutAtChange(formatBookingIso(defaultCheckoutDate, checkOutTime.value))
|
||||||
|
applyCheckInSelection(now.toLocalDate(), now.format(timeFormatter))
|
||||||
|
}
|
||||||
|
|
||||||
|
LaunchedEffect(state.expectedCheckOutAt) {
|
||||||
|
val parsed = runCatching { OffsetDateTime.parse(state.expectedCheckOutAt) }.getOrNull() ?: return@LaunchedEffect
|
||||||
|
checkOutDate.value = parsed.toLocalDate()
|
||||||
|
checkOutTime.value = parsed.format(timeFormatter)
|
||||||
}
|
}
|
||||||
|
|
||||||
SaveTopBarScaffold(
|
SaveTopBarScaffold(
|
||||||
@@ -111,11 +123,7 @@ fun BookingCreateScreen(
|
|||||||
checkInNow.value = enabled
|
checkInNow.value = enabled
|
||||||
if (enabled) {
|
if (enabled) {
|
||||||
val now = OffsetDateTime.now()
|
val now = OffsetDateTime.now()
|
||||||
checkInDate.value = now.toLocalDate()
|
applyCheckInSelection(now.toLocalDate(), now.format(timeFormatter))
|
||||||
checkInTime.value = now.format(DateTimeFormatter.ofPattern("HH:mm"))
|
|
||||||
val nowIso = now.format(DateTimeFormatter.ISO_OFFSET_DATE_TIME)
|
|
||||||
viewModel.onExpectedCheckInAtChange(nowIso)
|
|
||||||
viewModel.autoSetBillingFromCheckIn(nowIso)
|
|
||||||
} else {
|
} else {
|
||||||
viewModel.onExpectedCheckInAtChange("")
|
viewModel.onExpectedCheckInAtChange("")
|
||||||
}
|
}
|
||||||
@@ -181,6 +189,7 @@ fun BookingCreateScreen(
|
|||||||
onClick = {
|
onClick = {
|
||||||
billingModeMenuExpanded.value = false
|
billingModeMenuExpanded.value = false
|
||||||
viewModel.onBillingModeChange(mode)
|
viewModel.onBillingModeChange(mode)
|
||||||
|
viewModel.refreshExpectedCheckoutPreview(propertyId)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -191,7 +200,10 @@ fun BookingCreateScreen(
|
|||||||
BookingTimePickerTextField(
|
BookingTimePickerTextField(
|
||||||
value = state.billingCheckoutTime,
|
value = state.billingCheckoutTime,
|
||||||
label = { Text("Billing check-out (HH:mm)") },
|
label = { Text("Billing check-out (HH:mm)") },
|
||||||
onTimeSelected = viewModel::onBillingCheckoutTimeChange,
|
onTimeSelected = { selectedTime ->
|
||||||
|
viewModel.onBillingCheckoutTimeChange(selectedTime)
|
||||||
|
viewModel.refreshExpectedCheckoutPreview(propertyId)
|
||||||
|
},
|
||||||
modifier = Modifier.fillMaxWidth()
|
modifier = Modifier.fillMaxWidth()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -481,10 +493,7 @@ fun BookingCreateScreen(
|
|||||||
minDate = LocalDate.now(),
|
minDate = LocalDate.now(),
|
||||||
onDismiss = { showCheckInPicker.value = false },
|
onDismiss = { showCheckInPicker.value = false },
|
||||||
onConfirm = { date, time ->
|
onConfirm = { date, time ->
|
||||||
checkInDate.value = date
|
applyCheckInSelection(date, time)
|
||||||
checkInTime.value = time
|
|
||||||
val formatted = formatBookingIso(date, time)
|
|
||||||
viewModel.onExpectedCheckInAtChange(formatted)
|
|
||||||
showCheckInPicker.value = false
|
showCheckInPicker.value = false
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ data class BookingCreateState(
|
|||||||
val expectedCheckOutAt: String = "",
|
val expectedCheckOutAt: String = "",
|
||||||
val billingMode: BookingBillingMode = BookingBillingMode.PROPERTY_POLICY,
|
val billingMode: BookingBillingMode = BookingBillingMode.PROPERTY_POLICY,
|
||||||
val billingCheckoutTime: String = "",
|
val billingCheckoutTime: String = "",
|
||||||
val source: String = "WALKIN",
|
val source: String = "DIRECT",
|
||||||
val fromCity: String = "",
|
val fromCity: String = "",
|
||||||
val toCity: String = "",
|
val toCity: String = "",
|
||||||
val memberRelation: String = "",
|
val memberRelation: String = "",
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import com.android.trisolarispms.data.api.core.ApiClient
|
|||||||
import com.android.trisolarispms.data.api.model.BookingBillingMode
|
import com.android.trisolarispms.data.api.model.BookingBillingMode
|
||||||
import com.android.trisolarispms.data.api.model.BookingCreateRequest
|
import com.android.trisolarispms.data.api.model.BookingCreateRequest
|
||||||
import com.android.trisolarispms.data.api.model.BookingCreateResponse
|
import com.android.trisolarispms.data.api.model.BookingCreateResponse
|
||||||
|
import com.android.trisolarispms.data.api.model.BookingExpectedCheckoutPreviewRequest
|
||||||
import com.android.trisolarispms.data.api.model.GuestDto
|
import com.android.trisolarispms.data.api.model.GuestDto
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.StateFlow
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
@@ -14,10 +15,16 @@ import kotlinx.coroutines.launch
|
|||||||
import java.time.OffsetDateTime
|
import java.time.OffsetDateTime
|
||||||
|
|
||||||
class BookingCreateViewModel : ViewModel() {
|
class BookingCreateViewModel : ViewModel() {
|
||||||
|
private companion object {
|
||||||
|
const val DEFAULT_PREVIEW_BILLABLE_NIGHTS = 1
|
||||||
|
}
|
||||||
|
|
||||||
private val _state = MutableStateFlow(BookingCreateState())
|
private val _state = MutableStateFlow(BookingCreateState())
|
||||||
val state: StateFlow<BookingCreateState> = _state
|
val state: StateFlow<BookingCreateState> = _state
|
||||||
|
private var expectedCheckoutPreviewRequestId: Long = 0
|
||||||
|
|
||||||
fun reset() {
|
fun reset() {
|
||||||
|
expectedCheckoutPreviewRequestId = 0
|
||||||
_state.value = BookingCreateState()
|
_state.value = BookingCreateState()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -76,6 +83,50 @@ class BookingCreateViewModel : ViewModel() {
|
|||||||
_state.update { it.copy(billingCheckoutTime = value, error = null) }
|
_state.update { it.copy(billingCheckoutTime = value, error = null) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun refreshExpectedCheckoutPreview(propertyId: String) {
|
||||||
|
if (propertyId.isBlank()) return
|
||||||
|
val requestBody = buildExpectedCheckoutPreviewRequest(_state.value) ?: return
|
||||||
|
val requestId = ++expectedCheckoutPreviewRequestId
|
||||||
|
viewModelScope.launch {
|
||||||
|
try {
|
||||||
|
val api = ApiClient.create()
|
||||||
|
val response = api.previewExpectedCheckout(
|
||||||
|
propertyId = propertyId,
|
||||||
|
body = requestBody
|
||||||
|
)
|
||||||
|
val expectedCheckOutAt = response.body()?.expectedCheckOutAt?.trim().orEmpty()
|
||||||
|
if (!response.isSuccessful || expectedCheckOutAt.isBlank() || requestId != expectedCheckoutPreviewRequestId) {
|
||||||
|
return@launch
|
||||||
|
}
|
||||||
|
_state.update { current ->
|
||||||
|
if (requestId != expectedCheckoutPreviewRequestId) {
|
||||||
|
current
|
||||||
|
} else {
|
||||||
|
current.copy(expectedCheckOutAt = expectedCheckOutAt, error = null)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (_: Exception) {
|
||||||
|
// Keep user-entered check-out on preview failures.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun buildExpectedCheckoutPreviewRequest(state: BookingCreateState): BookingExpectedCheckoutPreviewRequest? {
|
||||||
|
val expectedCheckInAt = state.expectedCheckInAt.trim()
|
||||||
|
if (expectedCheckInAt.isBlank()) return null
|
||||||
|
val customBillingCheckoutTime = state.billingCheckoutTime.trim().ifBlank { null }
|
||||||
|
return BookingExpectedCheckoutPreviewRequest(
|
||||||
|
checkInAt = expectedCheckInAt,
|
||||||
|
billableNights = DEFAULT_PREVIEW_BILLABLE_NIGHTS,
|
||||||
|
billingMode = state.billingMode,
|
||||||
|
billingCheckoutTime = if (state.billingMode == BookingBillingMode.CUSTOM_WINDOW) {
|
||||||
|
customBillingCheckoutTime
|
||||||
|
} else {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
fun onPhoneCountryChange(value: String) {
|
fun onPhoneCountryChange(value: String) {
|
||||||
val option = findPhoneCountryOption(value)
|
val option = findPhoneCountryOption(value)
|
||||||
_state.update { current ->
|
_state.update { current ->
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package com.android.trisolarispms.ui.roomstay
|
package com.android.trisolarispms.ui.roomstay
|
||||||
|
|
||||||
|
import androidx.activity.compose.BackHandler
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.Spacer
|
import androidx.compose.foundation.layout.Spacer
|
||||||
@@ -12,6 +13,8 @@ import androidx.compose.foundation.lazy.grid.items
|
|||||||
import androidx.compose.foundation.combinedClickable
|
import androidx.compose.foundation.combinedClickable
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.filled.Add
|
import androidx.compose.material.icons.filled.Add
|
||||||
|
import androidx.compose.material.icons.filled.CalendarMonth
|
||||||
|
import androidx.compose.material.icons.filled.MoreVert
|
||||||
import androidx.compose.material.icons.filled.People
|
import androidx.compose.material.icons.filled.People
|
||||||
import androidx.compose.material.icons.filled.MeetingRoom
|
import androidx.compose.material.icons.filled.MeetingRoom
|
||||||
import androidx.compose.material.icons.filled.Payment
|
import androidx.compose.material.icons.filled.Payment
|
||||||
@@ -21,6 +24,8 @@ import androidx.compose.material3.Card
|
|||||||
import androidx.compose.material3.CardDefaults
|
import androidx.compose.material3.CardDefaults
|
||||||
import androidx.compose.material3.FloatingActionButton
|
import androidx.compose.material3.FloatingActionButton
|
||||||
import androidx.compose.material3.CircularProgressIndicator
|
import androidx.compose.material3.CircularProgressIndicator
|
||||||
|
import androidx.compose.material3.DropdownMenu
|
||||||
|
import androidx.compose.material3.DropdownMenuItem
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.IconButton
|
import androidx.compose.material3.IconButton
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
@@ -63,6 +68,11 @@ fun ActiveRoomStaysScreen(
|
|||||||
) {
|
) {
|
||||||
val state by viewModel.state.collectAsState()
|
val state by viewModel.state.collectAsState()
|
||||||
val selectedBooking = remember { mutableStateOf<BookingListItem?>(null) }
|
val selectedBooking = remember { mutableStateOf<BookingListItem?>(null) }
|
||||||
|
val menuExpanded = remember { mutableStateOf(false) }
|
||||||
|
|
||||||
|
BackHandler(enabled = state.showOpenBookings) {
|
||||||
|
viewModel.hideOpenBookings()
|
||||||
|
}
|
||||||
|
|
||||||
LaunchedEffect(propertyId) {
|
LaunchedEffect(propertyId) {
|
||||||
viewModel.load(propertyId)
|
viewModel.load(propertyId)
|
||||||
@@ -70,23 +80,63 @@ fun ActiveRoomStaysScreen(
|
|||||||
|
|
||||||
BackTopBarScaffold(
|
BackTopBarScaffold(
|
||||||
title = propertyName,
|
title = propertyName,
|
||||||
onBack = onBack,
|
onBack = {
|
||||||
|
if (state.showOpenBookings) {
|
||||||
|
viewModel.hideOpenBookings()
|
||||||
|
} else {
|
||||||
|
onBack()
|
||||||
|
}
|
||||||
|
},
|
||||||
showBack = showBack,
|
showBack = showBack,
|
||||||
actions = {
|
actions = {
|
||||||
IconButton(onClick = onViewRooms) {
|
IconButton(onClick = onViewRooms) {
|
||||||
Icon(Icons.Default.MeetingRoom, contentDescription = "Available Rooms")
|
Icon(Icons.Default.MeetingRoom, contentDescription = "Available Rooms")
|
||||||
}
|
}
|
||||||
IconButton(onClick = onOpenSettings) {
|
IconButton(onClick = viewModel::toggleShowOpenBookings) {
|
||||||
Icon(Icons.Default.Settings, contentDescription = "Settings")
|
Icon(
|
||||||
|
Icons.Default.CalendarMonth,
|
||||||
|
contentDescription = "Show Open Bookings",
|
||||||
|
tint = if (state.showOpenBookings) {
|
||||||
|
MaterialTheme.colorScheme.primary
|
||||||
|
} else {
|
||||||
|
MaterialTheme.colorScheme.onSurfaceVariant
|
||||||
}
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
IconButton(onClick = { menuExpanded.value = true }) {
|
||||||
|
Icon(Icons.Default.MoreVert, contentDescription = "Menu")
|
||||||
|
}
|
||||||
|
DropdownMenu(
|
||||||
|
expanded = menuExpanded.value,
|
||||||
|
onDismissRequest = { menuExpanded.value = false }
|
||||||
|
) {
|
||||||
|
DropdownMenuItem(
|
||||||
|
text = { Text("Settings") },
|
||||||
|
leadingIcon = { Icon(Icons.Default.Settings, contentDescription = null) },
|
||||||
|
onClick = {
|
||||||
|
menuExpanded.value = false
|
||||||
|
onOpenSettings()
|
||||||
|
}
|
||||||
|
)
|
||||||
if (showRazorpaySettings) {
|
if (showRazorpaySettings) {
|
||||||
IconButton(onClick = onRazorpaySettings) {
|
DropdownMenuItem(
|
||||||
Icon(Icons.Default.Payment, contentDescription = "Razorpay Settings")
|
text = { Text("Razorpay Settings") },
|
||||||
|
leadingIcon = { Icon(Icons.Default.Payment, contentDescription = null) },
|
||||||
|
onClick = {
|
||||||
|
menuExpanded.value = false
|
||||||
|
onRazorpaySettings()
|
||||||
}
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
if (showUserAdmin) {
|
if (showUserAdmin) {
|
||||||
IconButton(onClick = onUserAdmin) {
|
DropdownMenuItem(
|
||||||
Icon(Icons.Default.People, contentDescription = "Property Users")
|
text = { Text("Property Users") },
|
||||||
|
leadingIcon = { Icon(Icons.Default.People, contentDescription = null) },
|
||||||
|
onClick = {
|
||||||
|
menuExpanded.value = false
|
||||||
|
onUserAdmin()
|
||||||
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -112,8 +162,18 @@ fun ActiveRoomStaysScreen(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!state.isLoading && state.error == null) {
|
if (!state.isLoading && state.error == null) {
|
||||||
if (state.checkedInBookings.isNotEmpty()) {
|
val shownBookings = if (state.showOpenBookings) {
|
||||||
Text(text = "Checked-in bookings", style = MaterialTheme.typography.titleMedium)
|
state.openBookings
|
||||||
|
} else {
|
||||||
|
state.checkedInBookings
|
||||||
|
}
|
||||||
|
if (shownBookings.isNotEmpty()) {
|
||||||
|
val sectionTitle = if (state.showOpenBookings) {
|
||||||
|
"Open bookings"
|
||||||
|
} else {
|
||||||
|
"Checked-in bookings"
|
||||||
|
}
|
||||||
|
Text(text = sectionTitle, style = MaterialTheme.typography.titleMedium)
|
||||||
Spacer(modifier = Modifier.height(8.dp))
|
Spacer(modifier = Modifier.height(8.dp))
|
||||||
LazyVerticalGrid(
|
LazyVerticalGrid(
|
||||||
columns = GridCells.Fixed(2),
|
columns = GridCells.Fixed(2),
|
||||||
@@ -121,7 +181,7 @@ fun ActiveRoomStaysScreen(
|
|||||||
horizontalArrangement = Arrangement.spacedBy(12.dp),
|
horizontalArrangement = Arrangement.spacedBy(12.dp),
|
||||||
verticalArrangement = Arrangement.spacedBy(12.dp)
|
verticalArrangement = Arrangement.spacedBy(12.dp)
|
||||||
) {
|
) {
|
||||||
items(state.checkedInBookings) { booking ->
|
items(shownBookings) { booking ->
|
||||||
CheckedInBookingCard(
|
CheckedInBookingCard(
|
||||||
booking = booking,
|
booking = booking,
|
||||||
onClick = { onOpenBookingDetails(booking) })
|
onClick = { onOpenBookingDetails(booking) })
|
||||||
@@ -129,7 +189,12 @@ fun ActiveRoomStaysScreen(
|
|||||||
}
|
}
|
||||||
Spacer(modifier = Modifier.height(16.dp))
|
Spacer(modifier = Modifier.height(16.dp))
|
||||||
} else {
|
} else {
|
||||||
Text(text = "No checked-in bookings")
|
val emptyLabel = if (state.showOpenBookings) {
|
||||||
|
"No open bookings"
|
||||||
|
} else {
|
||||||
|
"No checked-in bookings"
|
||||||
|
}
|
||||||
|
Text(text = emptyLabel)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -188,10 +253,6 @@ private fun CheckedInBookingCard(
|
|||||||
color = MaterialTheme.colorScheme.onSurfaceVariant
|
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
val source = booking.source?.takeIf { it.isNotBlank() }
|
|
||||||
if (source != null) {
|
|
||||||
Text(text = source, style = MaterialTheme.typography.bodySmall)
|
|
||||||
}
|
|
||||||
val expectedCount = booking.expectedGuestCount
|
val expectedCount = booking.expectedGuestCount
|
||||||
val totalCount = booking.totalGuestCount
|
val totalCount = booking.totalGuestCount
|
||||||
val countLine = when {
|
val countLine = when {
|
||||||
@@ -216,6 +277,14 @@ private fun CheckedInBookingCard(
|
|||||||
color = MaterialTheme.colorScheme.error
|
color = MaterialTheme.colorScheme.error
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
val vehicleNumbers = booking.vehicleNumbers.filter { it.isNotBlank() }
|
||||||
|
if (vehicleNumbers.isNotEmpty()) {
|
||||||
|
Spacer(modifier = Modifier.height(6.dp))
|
||||||
|
Text(
|
||||||
|
text = vehicleNumbers.joinToString(", "),
|
||||||
|
style = MaterialTheme.typography.bodySmall
|
||||||
|
)
|
||||||
|
}
|
||||||
val checkInAt = booking.checkInAt?.takeIf { it.isNotBlank() }
|
val checkInAt = booking.checkInAt?.takeIf { it.isNotBlank() }
|
||||||
val checkOutAt = booking.expectedCheckOutAt?.takeIf { it.isNotBlank() }
|
val checkOutAt = booking.expectedCheckOutAt?.takeIf { it.isNotBlank() }
|
||||||
if (checkInAt != null && checkOutAt != null) {
|
if (checkInAt != null && checkOutAt != null) {
|
||||||
|
|||||||
@@ -7,5 +7,7 @@ data class ActiveRoomStaysState(
|
|||||||
val isLoading: Boolean = false,
|
val isLoading: Boolean = false,
|
||||||
val error: String? = null,
|
val error: String? = null,
|
||||||
val items: List<ActiveRoomStayDto> = emptyList(),
|
val items: List<ActiveRoomStayDto> = emptyList(),
|
||||||
val checkedInBookings: List<BookingListItem> = emptyList()
|
val checkedInBookings: List<BookingListItem> = emptyList(),
|
||||||
|
val openBookings: List<BookingListItem> = emptyList(),
|
||||||
|
val showOpenBookings: Boolean = false
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -11,6 +11,14 @@ class ActiveRoomStaysViewModel : ViewModel() {
|
|||||||
private val _state = MutableStateFlow(ActiveRoomStaysState())
|
private val _state = MutableStateFlow(ActiveRoomStaysState())
|
||||||
val state: StateFlow<ActiveRoomStaysState> = _state
|
val state: StateFlow<ActiveRoomStaysState> = _state
|
||||||
|
|
||||||
|
fun toggleShowOpenBookings() {
|
||||||
|
_state.update { it.copy(showOpenBookings = !it.showOpenBookings) }
|
||||||
|
}
|
||||||
|
|
||||||
|
fun hideOpenBookings() {
|
||||||
|
_state.update { it.copy(showOpenBookings = false) }
|
||||||
|
}
|
||||||
|
|
||||||
fun load(propertyId: String) {
|
fun load(propertyId: String) {
|
||||||
if (propertyId.isBlank()) return
|
if (propertyId.isBlank()) return
|
||||||
launchRequest(
|
launchRequest(
|
||||||
@@ -22,12 +30,14 @@ class ActiveRoomStaysViewModel : ViewModel() {
|
|||||||
val api = ApiClient.create()
|
val api = ApiClient.create()
|
||||||
val activeResponse = api.listActiveRoomStays(propertyId)
|
val activeResponse = api.listActiveRoomStays(propertyId)
|
||||||
val bookingsResponse = api.listBookings(propertyId, status = "CHECKED_IN")
|
val bookingsResponse = api.listBookings(propertyId, status = "CHECKED_IN")
|
||||||
|
val openBookingsResponse = api.listBookings(propertyId, status = "OPEN")
|
||||||
if (activeResponse.isSuccessful) {
|
if (activeResponse.isSuccessful) {
|
||||||
_state.update {
|
_state.update {
|
||||||
it.copy(
|
it.copy(
|
||||||
isLoading = false,
|
isLoading = false,
|
||||||
items = activeResponse.body().orEmpty(),
|
items = activeResponse.body().orEmpty(),
|
||||||
checkedInBookings = bookingsResponse.body().orEmpty(),
|
checkedInBookings = bookingsResponse.body().orEmpty(),
|
||||||
|
openBookings = openBookingsResponse.body().orEmpty(),
|
||||||
error = null
|
error = null
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user