Не подтверждена Коммит fb0d66d3 создал по автору Vitaly.Smirnov's avatar Vitaly.Smirnov Зафиксировано автором GitHub
Просмотр файлов

fix: Prevent past time bookings in BookingEditor (#340)



Co-authored-by: default avatarRadch-enko <stanislav.radchenko@effective.band>
владелец 960d50d8
......@@ -6,4 +6,7 @@
<string name="update_button">Изменить</string>
<string name="delete_button">Удалить бронь</string>
<string name="error">Произошла ошибка</string>
<string name="is_time_in_past_error">Ошибка, выбрано прошедшее время</string>
<string name="error_creating_event">Error creating event</string>
<string name="error_deleting_event">Error deleting event"</string>
</resources>
\ Нет новой строки в конце файла
......@@ -11,10 +11,17 @@ import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Snackbar
import androidx.compose.material3.SnackbarDuration
import androidx.compose.material3.SnackbarHost
import androidx.compose.material3.SnackbarHostState
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
......@@ -40,6 +47,9 @@ import band.effective.office.tablet.feature.bookingEditor.booking_view_title
import band.effective.office.tablet.feature.bookingEditor.create_view_title
import band.effective.office.tablet.feature.bookingEditor.delete_button
import band.effective.office.tablet.feature.bookingEditor.error
import band.effective.office.tablet.feature.bookingEditor.error_creating_event
import band.effective.office.tablet.feature.bookingEditor.error_deleting_event
import band.effective.office.tablet.feature.bookingEditor.is_time_in_past_error
import band.effective.office.tablet.feature.bookingEditor.presentation.datetimepicker.DateTimePickerModalView
import band.effective.office.tablet.feature.bookingEditor.update_button
import com.arkivanov.decompose.extensions.compose.stack.Children
......@@ -109,6 +119,7 @@ fun BookingEditor(
start = state.event.startTime.format(timeFormatter),
finish = state.event.finishTime.format(timeFormatter),
room = component.roomName,
isTimeInPastError = state.isTimeInPastError
)
}
}
......@@ -149,9 +160,20 @@ private fun BookingEditor(
isNewEvent: Boolean,
start: String,
finish: String,
room: String
room: String,
isTimeInPastError: Boolean
) {
val snackbarHostState = remember { SnackbarHostState() }
val timeInPastErrorMessage = stringResource(Res.string.is_time_in_past_error)
LaunchedEffect(isTimeInPastError) {
if (isTimeInPastError) {
snackbarHostState.showSnackbar(
message = timeInPastErrorMessage,
duration = SnackbarDuration.Short
)
}
}
Box {
Column(
modifier = Modifier
......@@ -212,7 +234,7 @@ private fun BookingEditor(
when {
isCreateLoad -> Loader()
isCreateError -> Text(
text = "Error creating event", // Ideally, this should be a string resource
text = stringResource(Res.string.error_creating_event),
style = MaterialTheme.typography.h6
)
else -> Text(
......@@ -247,7 +269,7 @@ private fun BookingEditor(
when {
isDeleteLoad -> Loader()
isDeleteError -> Text(
text = "Error deleting event", // Ideally, this should be a string resource
text = stringResource(Res.string.error_deleting_event),
style = MaterialTheme.typography.h6
)
......@@ -266,5 +288,22 @@ private fun BookingEditor(
.fillMaxWidth().padding(35.dp),
onDismissRequest = onDismissRequest
)
SnackbarHost(
hostState = snackbarHostState,
modifier = Modifier
.align(Alignment.BottomCenter)
.padding(bottom = 32.dp)
) { snackbarData ->
Snackbar(
modifier = Modifier.padding(16.dp),
containerColor = MaterialTheme.colorScheme.errorContainer,
contentColor = MaterialTheme.colorScheme.onErrorContainer
) {
Text(
text = snackbarData.visuals.message,
style = MaterialTheme.typography.bodyMedium
)
}
}
}
}
......@@ -11,8 +11,6 @@ import band.effective.office.tablet.core.domain.useCase.OrganizersInfoUseCase
import band.effective.office.tablet.core.domain.useCase.UpdateBookingUseCase
import band.effective.office.tablet.core.domain.util.asInstant
import band.effective.office.tablet.core.domain.util.asLocalDateTime
import band.effective.office.tablet.core.domain.util.currentLocalDateTime
import band.effective.office.tablet.core.domain.util.defaultTimeZone
import band.effective.office.tablet.core.ui.common.ModalWindow
import band.effective.office.tablet.core.ui.utils.componentCoroutineScope
import band.effective.office.tablet.feature.bookingEditor.presentation.datetimepicker.DateTimePickerComponent
......@@ -31,8 +29,10 @@ import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import kotlinx.datetime.Clock
import kotlinx.datetime.LocalDateTime
import kotlinx.datetime.atStartOfDayIn
import kotlinx.datetime.TimeZone
import kotlinx.datetime.toLocalDateTime
import kotlinx.serialization.Serializable
import org.koin.core.component.KoinComponent
import org.koin.core.component.inject
......@@ -216,7 +216,7 @@ class BookingEditorComponent(
inputError: Boolean,
busyEvent: Boolean
) = mutableState.update {
it.copy(enableUpdateButton = !inputError && !busyEvent)
it.copy(enableUpdateButton = !inputError && !busyEvent && !it.isTimeInPastError)
}
/**
......@@ -229,12 +229,14 @@ class BookingEditorComponent(
duration = duration,
organizer = selectOrganizer
)
val isTimeInPast = newDate <= getCurrentTime()
updateStateWithNewEventDetails(
newDate = newDate,
newDuration = duration,
newOrganizer = selectOrganizer,
busyEvents = busyEvents
busyEvents = busyEvents,
isTimeInPast = isTimeInPast
)
if (selectOrganizer != Organizer.default) {
......@@ -260,45 +262,33 @@ class BookingEditorComponent(
val resolvedOrganizer = organizers.firstOrNull {
it.fullName == newOrganizer.fullName
} ?: event.organizer
val isTimeInPast = newDate <= getCurrentTime()
val busyEvents = checkForBusyEvents(
date = newDate,
duration = newDuration,
organizer = resolvedOrganizer
)
if (isValidEventTime(newDate, newDuration)) {
updateStateWithNewEventDetails(
newDate = newDate,
newDuration = newDuration,
newOrganizer = resolvedOrganizer,
busyEvents = busyEvents
)
updateStateWithNewEventDetails(
newDate = newDate,
newDuration = newDuration,
newOrganizer = resolvedOrganizer,
busyEvents = busyEvents,
isTimeInPast = isTimeInPast
)
updateButtonState(
inputError = !organizers.contains(resolvedOrganizer),
busyEvent = busyEvents.isNotEmpty()
)
}
updateButtonState(
inputError = !organizers.contains(resolvedOrganizer),
busyEvent = busyEvents.isNotEmpty()
)
}
}
/**
* Checks if the event time is valid
*/
private fun isValidEventTime(date: LocalDateTime, duration: Int): Boolean {
val today = getTodayStartTime()
val officeEndTime = OfficeTime.finishWorkTime(date.date)
val eventEndTime = date.asInstant.plus(duration.minutes).asLocalDateTime
return duration > 0 && date > today && eventEndTime < officeEndTime
}
/**
* Gets the start time of today
* Gets the current time
*/
private fun getTodayStartTime(): LocalDateTime =
currentLocalDateTime.date.atStartOfDayIn(defaultTimeZone).asLocalDateTime
private fun getCurrentTime(): LocalDateTime =
Clock.System.now().toLocalDateTime(TimeZone.currentSystemDefault())
/**
* Checks for busy events that conflict with the given parameters
......@@ -343,7 +333,8 @@ class BookingEditorComponent(
newDate: LocalDateTime,
newDuration: Int,
newOrganizer: Organizer,
busyEvents: List<EventInfo>
busyEvents: List<EventInfo>,
isTimeInPast: Boolean
) {
val updatedEvent = createEventInfo(
id = state.value.event.id,
......@@ -358,7 +349,8 @@ class BookingEditorComponent(
duration = newDuration,
selectOrganizer = newOrganizer,
event = updatedEvent,
isBusyEvent = busyEvents.isNotEmpty()
isBusyEvent = busyEvents.isNotEmpty(),
isTimeInPastError = isTimeInPast
)
}
}
......
......@@ -23,7 +23,8 @@ data class State(
val isErrorCreate: Boolean,
val showSelectDate: Boolean,
val enableUpdateButton: Boolean,
val isBusyEvent: Boolean
val isBusyEvent: Boolean,
val isTimeInPastError: Boolean
) {
companion object {
val defaultValue = State(
......@@ -44,7 +45,8 @@ data class State(
isErrorCreate = false,
showSelectDate = false,
enableUpdateButton = false,
isBusyEvent = false
isBusyEvent = false,
isTimeInPastError = false
)
}
......
Поддерживает Markdown
0% или .
You are about to add 0 people to the discussion. Proceed with caution.
Сначала завершите редактирование этого сообщения!
Пожалуйста, зарегистрируйтесь или чтобы прокомментировать