create booking : improve form and ui further
This commit is contained in:
@@ -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()
|
||||
|
||||
@@ -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<List<GuestDto>>
|
||||
|
||||
@GET("properties/{propertyId}/guests/visit-count")
|
||||
suspend fun getGuestVisitCount(
|
||||
@Path("propertyId") propertyId: String,
|
||||
@Query("phone") phone: String
|
||||
): Response<GuestVisitCountResponse>
|
||||
|
||||
@GET("properties/{propertyId}/guests/{guestId}")
|
||||
suspend fun getGuest(
|
||||
@Path("propertyId") propertyId: String,
|
||||
|
||||
@@ -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
|
||||
)
|
||||
|
||||
|
||||
@@ -64,3 +64,8 @@ data class GuestDocumentDto(
|
||||
val extractedData: Map<String, String>? = null,
|
||||
val extractedAt: String? = null
|
||||
)
|
||||
|
||||
data class GuestVisitCountResponse(
|
||||
val guestId: String? = null,
|
||||
val bookingCount: Int? = null
|
||||
)
|
||||
|
||||
@@ -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))
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
OutlinedTextField(
|
||||
value = state.totalGuestCount,
|
||||
onValueChange = viewModel::onTotalGuestCountChange,
|
||||
label = { Text("Total Guest Count (optional)") },
|
||||
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,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 }
|
||||
)
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user