From 799c0b44b9f66220b822f1377b3e3f480d707c2b Mon Sep 17 00:00:00 2001 From: androidlover5842 Date: Thu, 29 Jan 2026 09:39:17 +0530 Subject: [PATCH] create booking : improve form and ui further --- .../com/android/trisolarispms/MainActivity.kt | 7 +- .../trisolarispms/data/api/GuestApi.kt | 7 ++ .../data/api/model/BookingModels.kt | 6 +- .../data/api/model/GuestModels.kt | 5 ++ .../ui/booking/BookingCreateScreen.kt | 90 ++++++++++++++++--- .../ui/booking/BookingCreateState.kt | 9 +- .../ui/booking/BookingCreateViewModel.kt | 75 +++++++++++++--- 7 files changed, 172 insertions(+), 27 deletions(-) diff --git a/app/src/main/java/com/android/trisolarispms/MainActivity.kt b/app/src/main/java/com/android/trisolarispms/MainActivity.kt index d1a1b86..3b72888 100644 --- a/app/src/main/java/com/android/trisolarispms/MainActivity.kt +++ b/app/src/main/java/com/android/trisolarispms/MainActivity.kt @@ -140,7 +140,12 @@ class MainActivity : ComponentActivity() { ) is AppRoute.CreateBooking -> BookingCreateScreen( propertyId = currentRoute.propertyId, - onBack = { route.value = AppRoute.Home }, + onBack = { + route.value = AppRoute.ActiveRoomStays( + currentRoute.propertyId, + selectedPropertyName.value ?: "Property" + ) + }, onCreated = { response, guest, phone -> val bookingId = response.id.orEmpty() val guestId = (guest?.id ?: response.guestId).orEmpty() diff --git a/app/src/main/java/com/android/trisolarispms/data/api/GuestApi.kt b/app/src/main/java/com/android/trisolarispms/data/api/GuestApi.kt index c925e8f..e450c8e 100644 --- a/app/src/main/java/com/android/trisolarispms/data/api/GuestApi.kt +++ b/app/src/main/java/com/android/trisolarispms/data/api/GuestApi.kt @@ -5,6 +5,7 @@ import com.android.trisolarispms.data.api.model.GuestCreateRequest import com.android.trisolarispms.data.api.model.GuestRatingDto import com.android.trisolarispms.data.api.model.GuestRatingRequest import com.android.trisolarispms.data.api.model.GuestUpdateRequest +import com.android.trisolarispms.data.api.model.GuestVisitCountResponse import com.android.trisolarispms.data.api.model.GuestVehicleDto import com.android.trisolarispms.data.api.model.GuestVehicleRequest import retrofit2.Response @@ -36,6 +37,12 @@ interface GuestApi { @Query("vehicleNumber") vehicleNumber: String? = null ): Response> + @GET("properties/{propertyId}/guests/visit-count") + suspend fun getGuestVisitCount( + @Path("propertyId") propertyId: String, + @Query("phone") phone: String + ): Response + @GET("properties/{propertyId}/guests/{guestId}") suspend fun getGuest( @Path("propertyId") propertyId: String, diff --git a/app/src/main/java/com/android/trisolarispms/data/api/model/BookingModels.kt b/app/src/main/java/com/android/trisolarispms/data/api/model/BookingModels.kt index 6ee18db..1bcc1fa 100644 --- a/app/src/main/java/com/android/trisolarispms/data/api/model/BookingModels.kt +++ b/app/src/main/java/com/android/trisolarispms/data/api/model/BookingModels.kt @@ -14,8 +14,10 @@ data class BookingCreateRequest( val source: String? = null, val guestPhoneE164: String? = null, val transportMode: String? = null, - val adultCount: Int? = null, - val totalGuestCount: Int? = null, + val childCount: Int? = null, + val maleCount: Int? = null, + val femaleCount: Int? = null, + val expectedGuestCount: Int? = null, val notes: String? = null ) diff --git a/app/src/main/java/com/android/trisolarispms/data/api/model/GuestModels.kt b/app/src/main/java/com/android/trisolarispms/data/api/model/GuestModels.kt index 6e24230..0573348 100644 --- a/app/src/main/java/com/android/trisolarispms/data/api/model/GuestModels.kt +++ b/app/src/main/java/com/android/trisolarispms/data/api/model/GuestModels.kt @@ -64,3 +64,8 @@ data class GuestDocumentDto( val extractedData: Map? = null, val extractedAt: String? = null ) + +data class GuestVisitCountResponse( + val guestId: String? = null, + val bookingCount: Int? = null +) diff --git a/app/src/main/java/com/android/trisolarispms/ui/booking/BookingCreateScreen.kt b/app/src/main/java/com/android/trisolarispms/ui/booking/BookingCreateScreen.kt index 64bedbc..9d7a223 100644 --- a/app/src/main/java/com/android/trisolarispms/ui/booking/BookingCreateScreen.kt +++ b/app/src/main/java/com/android/trisolarispms/ui/booking/BookingCreateScreen.kt @@ -174,9 +174,51 @@ fun BookingCreateScreen( modifier = Modifier.fillMaxWidth() ) Spacer(modifier = Modifier.height(12.dp)) - Text(text = "Guest Phone (optional)", style = MaterialTheme.typography.titleSmall) - Spacer(modifier = Modifier.height(6.dp)) val selectedCountry = findPhoneCountryOption(state.phoneCountryCode) + val phoneDigitsLength = state.phoneNationalNumber.length + val phoneIsComplete = phoneDigitsLength == selectedCountry.maxLength + val phoneE164 = if (phoneIsComplete) { + "+${selectedCountry.dialCode}${state.phoneNationalNumber}" + } else { + null + } + LaunchedEffect(propertyId, phoneE164) { + if (phoneE164 != null) { + viewModel.fetchPhoneVisitCount(propertyId, phoneE164) + } else { + viewModel.clearPhoneVisitCount() + } + } + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.spacedBy(8.dp), + verticalAlignment = Alignment.CenterVertically + ) { + Text(text = "Guest Phone (optional)", style = MaterialTheme.typography.titleSmall) + if (phoneIsComplete && state.phoneVisitCountLoading) { + Row(horizontalArrangement = Arrangement.spacedBy(6.dp)) { + Text(text = "|", style = MaterialTheme.typography.titleSmall) + Text( + text = "checking...", + color = MaterialTheme.colorScheme.onSurfaceVariant, + style = MaterialTheme.typography.titleSmall + ) + } + } else { + val count = state.phoneVisitCount ?: 0 + if (count > 0) { + Row(horizontalArrangement = Arrangement.spacedBy(6.dp)) { + Text(text = "|", style = MaterialTheme.typography.titleSmall) + Text( + text = "${count} times visited", + color = Color(0xFF2E7D32), + style = MaterialTheme.typography.titleSmall + ) + } + } + } + } + Spacer(modifier = Modifier.height(6.dp)) Row( modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.spacedBy(8.dp), @@ -307,20 +349,44 @@ fun BookingCreateScreen( } Spacer(modifier = Modifier.height(12.dp)) OutlinedTextField( - value = state.adultCount, - onValueChange = viewModel::onAdultCountChange, - label = { Text("Adult Count (optional)") }, + value = state.childCount, + onValueChange = viewModel::onChildCountChange, + label = { Text("Child Count (optional)") }, keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number), modifier = Modifier.fillMaxWidth() ) Spacer(modifier = Modifier.height(12.dp)) - OutlinedTextField( - value = state.totalGuestCount, - onValueChange = viewModel::onTotalGuestCountChange, - label = { Text("Total Guest Count (optional)") }, - keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number), - modifier = Modifier.fillMaxWidth() - ) + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.spacedBy(8.dp), + verticalAlignment = Alignment.CenterVertically + ) { + OutlinedTextField( + value = state.maleCount, + onValueChange = viewModel::onMaleCountChange, + label = { Text("Male Count (optional)") }, + keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number), + modifier = Modifier.weight(1f) + ) + OutlinedTextField( + value = state.femaleCount, + onValueChange = viewModel::onFemaleCountChange, + label = { Text("Female Count (optional)") }, + keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number), + modifier = Modifier.weight(1f) + ) + } + val showExpectedGuestCount = state.maleCount.isBlank() && state.femaleCount.isBlank() + if (showExpectedGuestCount) { + Spacer(modifier = Modifier.height(12.dp)) + OutlinedTextField( + value = state.expectedGuestCount, + onValueChange = viewModel::onExpectedGuestCountChange, + label = { Text("Expected Guest Count (optional)") }, + keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number), + modifier = Modifier.fillMaxWidth() + ) + } Spacer(modifier = Modifier.height(12.dp)) OutlinedTextField( value = state.notes, diff --git a/app/src/main/java/com/android/trisolarispms/ui/booking/BookingCreateState.kt b/app/src/main/java/com/android/trisolarispms/ui/booking/BookingCreateState.kt index 2d674f3..075de30 100644 --- a/app/src/main/java/com/android/trisolarispms/ui/booking/BookingCreateState.kt +++ b/app/src/main/java/com/android/trisolarispms/ui/booking/BookingCreateState.kt @@ -3,12 +3,17 @@ package com.android.trisolarispms.ui.booking data class BookingCreateState( val phoneCountryCode: String = "IN", val phoneNationalNumber: String = "", + val phoneVisitCount: Int? = null, + val phoneVisitCountLoading: Boolean = false, + val phoneVisitCountPhone: String? = null, val expectedCheckInAt: String = "", val expectedCheckOutAt: String = "", val source: String = "WALKIN", val transportMode: String = "CAR", - val adultCount: String = "", - val totalGuestCount: String = "", + val childCount: String = "", + val maleCount: String = "", + val femaleCount: String = "", + val expectedGuestCount: String = "", val notes: String = "", val isLoading: Boolean = false, val error: String? = null diff --git a/app/src/main/java/com/android/trisolarispms/ui/booking/BookingCreateViewModel.kt b/app/src/main/java/com/android/trisolarispms/ui/booking/BookingCreateViewModel.kt index b2448ae..0aa72b0 100644 --- a/app/src/main/java/com/android/trisolarispms/ui/booking/BookingCreateViewModel.kt +++ b/app/src/main/java/com/android/trisolarispms/ui/booking/BookingCreateViewModel.kt @@ -31,14 +31,57 @@ class BookingCreateViewModel : ViewModel() { val option = findPhoneCountryOption(value) _state.update { current -> val trimmed = current.phoneNationalNumber.filter { it.isDigit() }.take(option.maxLength) - current.copy(phoneCountryCode = value, phoneNationalNumber = trimmed, error = null) + current.copy( + phoneCountryCode = value, + phoneNationalNumber = trimmed, + phoneVisitCount = if (trimmed.length == option.maxLength) current.phoneVisitCount else null, + phoneVisitCountPhone = null, + phoneVisitCountLoading = false, + error = null + ) } } fun onPhoneNationalNumberChange(value: String) { val option = findPhoneCountryOption(_state.value.phoneCountryCode) val trimmed = value.filter { it.isDigit() }.take(option.maxLength) - _state.update { it.copy(phoneNationalNumber = trimmed, error = null) } + _state.update { + it.copy( + phoneNationalNumber = trimmed, + phoneVisitCount = if (trimmed.length == option.maxLength) it.phoneVisitCount else null, + error = null + ) + } + } + + fun clearPhoneVisitCount() { + _state.update { it.copy(phoneVisitCount = null, phoneVisitCountPhone = null, phoneVisitCountLoading = false) } + } + + fun fetchPhoneVisitCount(propertyId: String, phoneE164: String) { + val current = _state.value + if (current.phoneVisitCountLoading || current.phoneVisitCountPhone == phoneE164) return + viewModelScope.launch { + _state.update { it.copy(phoneVisitCountLoading = true, phoneVisitCountPhone = phoneE164, error = null) } + try { + val api = ApiClient.create() + val response = api.getGuestVisitCount(propertyId = propertyId, phone = phoneE164) + val body = response.body() + if (response.isSuccessful && body != null) { + _state.update { + it.copy( + phoneVisitCount = body.bookingCount ?: 0, + phoneVisitCountLoading = false, + error = null + ) + } + } else { + _state.update { it.copy(phoneVisitCountLoading = false) } + } + } catch (_: Exception) { + _state.update { it.copy(phoneVisitCountLoading = false) } + } + } } fun onSourceChange(value: String) { @@ -49,12 +92,20 @@ class BookingCreateViewModel : ViewModel() { _state.update { it.copy(transportMode = value, error = null) } } - fun onAdultCountChange(value: String) { - _state.update { it.copy(adultCount = value.filter { it.isDigit() }, error = null) } + fun onChildCountChange(value: String) { + _state.update { it.copy(childCount = value.filter { it.isDigit() }, error = null) } } - fun onTotalGuestCountChange(value: String) { - _state.update { it.copy(totalGuestCount = value.filter { it.isDigit() }, error = null) } + fun onMaleCountChange(value: String) { + _state.update { it.copy(maleCount = value.filter { it.isDigit() }, error = null) } + } + + fun onFemaleCountChange(value: String) { + _state.update { it.copy(femaleCount = value.filter { it.isDigit() }, error = null) } + } + + fun onExpectedGuestCountChange(value: String) { + _state.update { it.copy(expectedGuestCount = value.filter { it.isDigit() }, error = null) } } fun onNotesChange(value: String) { @@ -69,8 +120,10 @@ class BookingCreateViewModel : ViewModel() { _state.update { it.copy(error = "Check-in and check-out are required") } return } - val adultCount = current.adultCount.toIntOrNull() - val totalGuestCount = current.totalGuestCount.toIntOrNull() + val childCount = current.childCount.toIntOrNull() + val maleCount = current.maleCount.toIntOrNull() + val femaleCount = current.femaleCount.toIntOrNull() + val expectedGuestCount = current.expectedGuestCount.toIntOrNull() viewModelScope.launch { _state.update { it.copy(isLoading = true, error = null) } try { @@ -90,8 +143,10 @@ class BookingCreateViewModel : ViewModel() { source = current.source.trim().ifBlank { null }, guestPhoneE164 = phone, transportMode = current.transportMode.trim().ifBlank { null }, - adultCount = adultCount, - totalGuestCount = totalGuestCount, + childCount = childCount, + maleCount = maleCount, + femaleCount = femaleCount, + expectedGuestCount = expectedGuestCount, notes = current.notes.trim().ifBlank { null } ) )