ability to cancel future bookings
This commit is contained in:
@@ -18,11 +18,15 @@ import androidx.compose.foundation.pager.rememberPagerState
|
|||||||
import androidx.compose.foundation.rememberScrollState
|
import androidx.compose.foundation.rememberScrollState
|
||||||
import androidx.compose.foundation.verticalScroll
|
import androidx.compose.foundation.verticalScroll
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.filled.Error
|
||||||
|
import androidx.compose.material.icons.filled.MoreVert
|
||||||
import androidx.compose.material.icons.filled.QrCode
|
import androidx.compose.material.icons.filled.QrCode
|
||||||
import androidx.compose.material.icons.filled.ReceiptLong
|
import androidx.compose.material.icons.filled.ReceiptLong
|
||||||
import androidx.compose.material3.Card
|
import androidx.compose.material3.Card
|
||||||
import androidx.compose.material3.CardDefaults
|
import androidx.compose.material3.CardDefaults
|
||||||
import androidx.compose.material3.CircularProgressIndicator
|
import androidx.compose.material3.CircularProgressIndicator
|
||||||
|
import androidx.compose.material3.DropdownMenu
|
||||||
|
import androidx.compose.material3.DropdownMenuItem
|
||||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.IconButton
|
import androidx.compose.material3.IconButton
|
||||||
@@ -31,6 +35,7 @@ import androidx.compose.material3.Tab
|
|||||||
import androidx.compose.material3.TabRow
|
import androidx.compose.material3.TabRow
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.material3.TextButton
|
import androidx.compose.material3.TextButton
|
||||||
|
import androidx.compose.material3.AlertDialog
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.DisposableEffect
|
import androidx.compose.runtime.DisposableEffect
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
@@ -53,6 +58,7 @@ 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.core.FirebaseAuthTokenProvider
|
import com.android.trisolarispms.data.api.core.FirebaseAuthTokenProvider
|
||||||
import com.android.trisolarispms.data.api.model.BookingBillingMode
|
import com.android.trisolarispms.data.api.model.BookingBillingMode
|
||||||
|
import com.android.trisolarispms.data.api.model.BookingCancelRequest
|
||||||
import com.android.trisolarispms.data.api.model.BookingDetailsResponse
|
import com.android.trisolarispms.data.api.model.BookingDetailsResponse
|
||||||
import com.android.trisolarispms.data.api.model.BookingExpectedDatesRequest
|
import com.android.trisolarispms.data.api.model.BookingExpectedDatesRequest
|
||||||
import com.android.trisolarispms.ui.booking.BookingDatePickerDialog
|
import com.android.trisolarispms.ui.booking.BookingDatePickerDialog
|
||||||
@@ -91,10 +97,15 @@ fun BookingDetailsTabsScreen(
|
|||||||
val scope = rememberCoroutineScope()
|
val scope = rememberCoroutineScope()
|
||||||
val staysState by staysViewModel.state.collectAsState()
|
val staysState by staysViewModel.state.collectAsState()
|
||||||
val detailsState by detailsViewModel.state.collectAsState()
|
val detailsState by detailsViewModel.state.collectAsState()
|
||||||
|
val actionsMenuExpanded = remember { mutableStateOf(false) }
|
||||||
|
val showCancelConfirm = remember { mutableStateOf(false) }
|
||||||
|
val cancelLoading = remember { mutableStateOf(false) }
|
||||||
|
val cancelError = remember { mutableStateOf<String?>(null) }
|
||||||
val canModifyDocuments = canManageDocuments && when (detailsState.details?.status) {
|
val canModifyDocuments = canManageDocuments && when (detailsState.details?.status) {
|
||||||
"OPEN", "CHECKED_IN" -> true
|
"OPEN", "CHECKED_IN" -> true
|
||||||
else -> false
|
else -> false
|
||||||
}
|
}
|
||||||
|
val canCancelBooking = detailsState.details?.status?.uppercase() == "OPEN"
|
||||||
|
|
||||||
LaunchedEffect(propertyId, bookingId, guestId) {
|
LaunchedEffect(propertyId, bookingId, guestId) {
|
||||||
staysViewModel.load(propertyId, bookingId)
|
staysViewModel.load(propertyId, bookingId)
|
||||||
@@ -107,7 +118,34 @@ fun BookingDetailsTabsScreen(
|
|||||||
|
|
||||||
BackTopBarScaffold(
|
BackTopBarScaffold(
|
||||||
title = "Details",
|
title = "Details",
|
||||||
onBack = onBack
|
onBack = onBack,
|
||||||
|
actions = {
|
||||||
|
if (canCancelBooking) {
|
||||||
|
IconButton(onClick = { actionsMenuExpanded.value = true }) {
|
||||||
|
Icon(Icons.Default.MoreVert, contentDescription = "More options")
|
||||||
|
}
|
||||||
|
DropdownMenu(
|
||||||
|
expanded = actionsMenuExpanded.value,
|
||||||
|
onDismissRequest = { actionsMenuExpanded.value = false }
|
||||||
|
) {
|
||||||
|
DropdownMenuItem(
|
||||||
|
text = { Text("Cancel Booking") },
|
||||||
|
leadingIcon = {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Default.Error,
|
||||||
|
contentDescription = null,
|
||||||
|
tint = MaterialTheme.colorScheme.error
|
||||||
|
)
|
||||||
|
},
|
||||||
|
onClick = {
|
||||||
|
actionsMenuExpanded.value = false
|
||||||
|
cancelError.value = null
|
||||||
|
showCancelConfirm.value = true
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
) { padding ->
|
) { padding ->
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
@@ -179,6 +217,74 @@ fun BookingDetailsTabsScreen(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (showCancelConfirm.value && canCancelBooking) {
|
||||||
|
AlertDialog(
|
||||||
|
onDismissRequest = {
|
||||||
|
if (!cancelLoading.value) {
|
||||||
|
showCancelConfirm.value = false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
icon = {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Default.Error,
|
||||||
|
contentDescription = null,
|
||||||
|
tint = MaterialTheme.colorScheme.error
|
||||||
|
)
|
||||||
|
},
|
||||||
|
title = { Text("Cancel booking?") },
|
||||||
|
text = {
|
||||||
|
Column(verticalArrangement = Arrangement.spacedBy(8.dp)) {
|
||||||
|
Text("This will cancel the OPEN booking.")
|
||||||
|
cancelError.value?.let { message ->
|
||||||
|
Text(
|
||||||
|
text = message,
|
||||||
|
color = MaterialTheme.colorScheme.error,
|
||||||
|
style = MaterialTheme.typography.bodySmall
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
confirmButton = {
|
||||||
|
TextButton(
|
||||||
|
enabled = !cancelLoading.value,
|
||||||
|
onClick = {
|
||||||
|
scope.launch {
|
||||||
|
cancelLoading.value = true
|
||||||
|
cancelError.value = null
|
||||||
|
try {
|
||||||
|
val response = ApiClient.create().cancelBooking(
|
||||||
|
propertyId = propertyId,
|
||||||
|
bookingId = bookingId,
|
||||||
|
body = BookingCancelRequest()
|
||||||
|
)
|
||||||
|
if (response.isSuccessful) {
|
||||||
|
showCancelConfirm.value = false
|
||||||
|
onBack()
|
||||||
|
} else {
|
||||||
|
cancelError.value = "Cancel failed: ${response.code()}"
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
cancelError.value = e.localizedMessage ?: "Cancel failed"
|
||||||
|
} finally {
|
||||||
|
cancelLoading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
Text(if (cancelLoading.value) "Cancelling..." else "Cancel Booking")
|
||||||
|
}
|
||||||
|
},
|
||||||
|
dismissButton = {
|
||||||
|
TextButton(
|
||||||
|
enabled = !cancelLoading.value,
|
||||||
|
onClick = { showCancelConfirm.value = false }
|
||||||
|
) {
|
||||||
|
Text("Keep Booking")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
|
|||||||
Reference in New Issue
Block a user