diff --git a/app/src/main/java/com/android/trisolarispms/data/api/model/RoomTypeModels.kt b/app/src/main/java/com/android/trisolarispms/data/api/model/RoomTypeModels.kt index 0ac4249..8bfa3cf 100644 --- a/app/src/main/java/com/android/trisolarispms/data/api/model/RoomTypeModels.kt +++ b/app/src/main/java/com/android/trisolarispms/data/api/model/RoomTypeModels.kt @@ -17,6 +17,7 @@ data class RoomTypeCreateRequest( data class RoomTypeUpdateRequest( val code: String? = null, val name: String? = null, + val active: Boolean? = null, val baseOccupancy: Int? = null, val maxOccupancy: Int? = null, val sqFeet: Int? = null, @@ -30,6 +31,7 @@ data class RoomTypeDto( val propertyId: String? = null, val code: String? = null, val name: String? = null, + val active: Boolean? = null, val baseOccupancy: Int? = null, val maxOccupancy: Int? = null, val sqFeet: Int? = null, diff --git a/app/src/main/java/com/android/trisolarispms/ui/room/RoomsScreen.kt b/app/src/main/java/com/android/trisolarispms/ui/room/RoomsScreen.kt index 101f0b2..69b994b 100644 --- a/app/src/main/java/com/android/trisolarispms/ui/room/RoomsScreen.kt +++ b/app/src/main/java/com/android/trisolarispms/ui/room/RoomsScreen.kt @@ -8,12 +8,10 @@ import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding -import androidx.compose.ui.draw.alpha import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.ArrowBack +import androidx.compose.material.icons.automirrored.filled.ArrowBack import androidx.compose.material.icons.filled.Add import androidx.compose.material.icons.filled.Category -import androidx.compose.material3.Button import androidx.compose.material3.CircularProgressIndicator import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.Icon @@ -28,6 +26,7 @@ import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.alpha import androidx.compose.ui.unit.dp import androidx.lifecycle.viewmodel.compose.viewModel @@ -54,7 +53,7 @@ fun RoomsScreen( title = { Text("Available Rooms") }, navigationIcon = { IconButton(onClick = onBack) { - Icon(Icons.Default.ArrowBack, contentDescription = "Back") + Icon(Icons.AutoMirrored.Default.ArrowBack, contentDescription = "Back") } }, actions = { diff --git a/app/src/main/java/com/android/trisolarispms/ui/roomimage/ImagePreviewDialog.kt b/app/src/main/java/com/android/trisolarispms/ui/roomimage/ImagePreviewDialog.kt new file mode 100644 index 0000000..c125579 --- /dev/null +++ b/app/src/main/java/com/android/trisolarispms/ui/roomimage/ImagePreviewDialog.kt @@ -0,0 +1,35 @@ +package com.android.trisolarispms.ui.roomimage + +import androidx.compose.material3.AlertDialog +import androidx.compose.material3.Text +import androidx.compose.material3.TextButton +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.unit.dp +import androidx.compose.foundation.layout.fillMaxWidth +import coil.compose.AsyncImage + +@Composable +fun ImagePreviewDialog( + imageUrl: String, + onDismiss: () -> Unit +) { + AlertDialog( + onDismissRequest = onDismiss, + title = { Text("Preview") }, + text = { + AsyncImage( + model = imageUrl, + contentDescription = "Image preview", + modifier = Modifier.fillMaxWidth(), + contentScale = ContentScale.Fit + ) + }, + confirmButton = { + TextButton(onClick = onDismiss) { + Text("Close") + } + } + ) +} diff --git a/app/src/main/java/com/android/trisolarispms/ui/roomimage/RoomImageGridItem.kt b/app/src/main/java/com/android/trisolarispms/ui/roomimage/RoomImageGridItem.kt index f817e69..f82b052 100644 --- a/app/src/main/java/com/android/trisolarispms/ui/roomimage/RoomImageGridItem.kt +++ b/app/src/main/java/com/android/trisolarispms/ui/roomimage/RoomImageGridItem.kt @@ -4,15 +4,12 @@ import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.aspectRatio -import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.width -import androidx.compose.foundation.layout.height import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.automirrored.filled.Label import androidx.compose.material.icons.filled.Delete import androidx.compose.material.icons.filled.DragIndicator -import androidx.compose.material.icons.filled.Label import androidx.compose.material3.Icon import androidx.compose.material3.IconButton import androidx.compose.material3.MaterialTheme @@ -85,7 +82,7 @@ fun RoomImageGridItem( } if (onEditTags != null) { IconButton(onClick = onEditTags, enabled = hasId) { - Icon(Icons.Default.Label, contentDescription = "Edit Tags") + Icon(Icons.AutoMirrored.Filled.Label, contentDescription = "Edit Tags") } } if (onDelete != null) { diff --git a/app/src/main/java/com/android/trisolarispms/ui/roomimage/RoomImagesScreen.kt b/app/src/main/java/com/android/trisolarispms/ui/roomimage/RoomImagesScreen.kt index c461983..cc885b4 100644 --- a/app/src/main/java/com/android/trisolarispms/ui/roomimage/RoomImagesScreen.kt +++ b/app/src/main/java/com/android/trisolarispms/ui/roomimage/RoomImagesScreen.kt @@ -42,7 +42,6 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.unit.dp import androidx.lifecycle.viewmodel.compose.viewModel -import coil.compose.AsyncImage import okhttp3.MediaType.Companion.toMediaType import okhttp3.MultipartBody import okhttp3.RequestBody.Companion.toRequestBody @@ -251,21 +250,9 @@ fun RoomImagesScreen( val preview = previewUrl.value if (!preview.isNullOrBlank()) { - AlertDialog( - onDismissRequest = { previewUrl.value = null }, - title = { Text("Preview") }, - text = { - AsyncImage( - model = preview, - contentDescription = "Image preview", - modifier = Modifier.fillMaxWidth() - ) - }, - confirmButton = { - TextButton(onClick = { previewUrl.value = null }) { - Text("Close") - } - } + ImagePreviewDialog( + imageUrl = preview, + onDismiss = { previewUrl.value = null } ) } diff --git a/app/src/main/java/com/android/trisolarispms/ui/roomtype/EditRoomTypeScreen.kt b/app/src/main/java/com/android/trisolarispms/ui/roomtype/EditRoomTypeScreen.kt index 59ecc47..ebbb31c 100644 --- a/app/src/main/java/com/android/trisolarispms/ui/roomtype/EditRoomTypeScreen.kt +++ b/app/src/main/java/com/android/trisolarispms/ui/roomtype/EditRoomTypeScreen.kt @@ -11,9 +11,12 @@ import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width import androidx.compose.foundation.layout.wrapContentHeight import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.verticalScroll +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.items import androidx.compose.foundation.lazy.grid.rememberLazyGridState import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.material.icons.filled.DragIndicator @@ -45,6 +48,8 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.text.input.KeyboardType import androidx.compose.ui.unit.dp import androidx.lifecycle.viewmodel.compose.viewModel +import coil.compose.AsyncImage +import com.android.trisolarispms.data.api.ApiConstants import com.android.trisolarispms.data.api.model.RoomTypeDto import com.android.trisolarispms.ui.roomimage.RoomImageGridItem import com.android.trisolarispms.ui.roomimage.ReorderableImageGrid @@ -67,6 +72,9 @@ fun EditRoomTypeScreen( val showDeleteConfirm = remember { mutableStateOf(false) } val orderedImages = remember { mutableStateOf>(emptyList()) } val originalOrderIds = remember { mutableStateOf>(emptyList()) } + val previewUrl = remember { mutableStateOf(null) } + val showAmenityDialog = remember { mutableStateOf(false) } + val amenitySearch = remember { mutableStateOf("") } val gridState = rememberLazyGridState() val saveRoomTypeImageOrder = remember(roomType.code, orderedImages.value) { { @@ -199,25 +207,21 @@ fun EditRoomTypeScreen( ) Spacer(modifier = Modifier.height(12.dp)) - Text(text = "Amenities", style = MaterialTheme.typography.titleSmall) - if (amenityState.items.isEmpty()) { - Text(text = "No amenities available", style = MaterialTheme.typography.bodySmall) - } else { - amenityState.items.forEach { amenity -> - Row( - modifier = Modifier - .fillMaxWidth() - .wrapContentHeight() - .padding(vertical = 4.dp) - ) { - Checkbox( - checked = state.amenityIds.contains(amenity.id), - onCheckedChange = { amenity.id?.let { viewModel.onAmenityToggle(it) } } - ) - Text(text = amenity.name ?: "", modifier = Modifier.padding(start = 8.dp)) - } + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically + ) { + Text(text = "Amenities", style = MaterialTheme.typography.titleSmall) + Button(onClick = { showAmenityDialog.value = true }) { + Text("Edit") } } + val selectedCount = state.amenityIds.size + Text( + text = if (selectedCount == 0) "No amenities selected" else "$selectedCount selected", + style = MaterialTheme.typography.bodySmall + ) Spacer(modifier = Modifier.height(16.dp)) Row( @@ -256,6 +260,7 @@ fun EditRoomTypeScreen( RoomImageGridItem( image = image, modifier = dragHandleModifier, + onPreview = { previewUrl.value = image.url ?: image.thumbnailUrl }, showTags = true ) } @@ -287,4 +292,93 @@ fun EditRoomTypeScreen( } ) } + + if (showAmenityDialog.value) { + val query = amenitySearch.value.trim() + val filtered = if (query.isBlank()) { + amenityState.items + } else { + amenityState.items.filter { + val name = it.name.orEmpty() + val category = it.category.orEmpty() + name.contains(query, ignoreCase = true) || category.contains(query, ignoreCase = true) + } + } + AlertDialog( + onDismissRequest = { showAmenityDialog.value = false }, + title = { Text("Select Amenities") }, + text = { + Column { + OutlinedTextField( + value = amenitySearch.value, + onValueChange = { amenitySearch.value = it }, + label = { Text("Search") }, + modifier = Modifier.fillMaxWidth() + ) + Spacer(modifier = Modifier.height(12.dp)) + if (amenityState.items.isEmpty()) { + Text(text = "No amenities available", style = MaterialTheme.typography.bodySmall) + } else if (filtered.isEmpty()) { + Text(text = "No matches", style = MaterialTheme.typography.bodySmall) + } else { + LazyColumn( + modifier = Modifier + .fillMaxWidth() + .height(320.dp) + ) { + items(filtered) { amenity -> + val id = amenity.id ?: return@items + val iconKey = amenity.iconKey.orEmpty().removeSuffix(".png") + Row( + modifier = Modifier + .fillMaxWidth() + .padding(vertical = 6.dp), + verticalAlignment = Alignment.CenterVertically + ) { + Checkbox( + checked = state.amenityIds.contains(id), + onCheckedChange = { viewModel.onAmenityToggle(id) } + ) + if (iconKey.isNotBlank()) { + AsyncImage( + model = "${ApiConstants.BASE_URL}icons/png/$iconKey.png", + contentDescription = amenity.name ?: iconKey, + modifier = Modifier + .size(24.dp) + .padding(end = 8.dp) + ) + } else { + Spacer(modifier = Modifier.width(8.dp)) + } + Column(modifier = Modifier.weight(1f)) { + Text(text = amenity.name.orEmpty()) + if (!amenity.category.isNullOrBlank()) { + Text( + text = amenity.category.orEmpty(), + style = MaterialTheme.typography.bodySmall, + color = MaterialTheme.colorScheme.onSurfaceVariant + ) + } + } + } + } + } + } + } + }, + confirmButton = { + TextButton(onClick = { showAmenityDialog.value = false }) { + Text("Done") + } + } + ) + } + + val preview = previewUrl.value + if (!preview.isNullOrBlank()) { + com.android.trisolarispms.ui.roomimage.ImagePreviewDialog( + imageUrl = preview, + onDismiss = { previewUrl.value = null } + ) + } }