Add amenity picker and room type active flag

This commit is contained in:
androidlover5842
2026-01-28 00:52:30 +05:30
parent dbbcb6c4a6
commit d8a40e4c9a
6 changed files with 156 additions and 42 deletions

View File

@@ -17,6 +17,7 @@ data class RoomTypeCreateRequest(
data class RoomTypeUpdateRequest( data class RoomTypeUpdateRequest(
val code: String? = null, val code: String? = null,
val name: String? = null, val name: String? = null,
val active: Boolean? = null,
val baseOccupancy: Int? = null, val baseOccupancy: Int? = null,
val maxOccupancy: Int? = null, val maxOccupancy: Int? = null,
val sqFeet: Int? = null, val sqFeet: Int? = null,
@@ -30,6 +31,7 @@ data class RoomTypeDto(
val propertyId: String? = null, val propertyId: String? = null,
val code: String? = null, val code: String? = null,
val name: String? = null, val name: String? = null,
val active: Boolean? = null,
val baseOccupancy: Int? = null, val baseOccupancy: Int? = null,
val maxOccupancy: Int? = null, val maxOccupancy: Int? = null,
val sqFeet: Int? = null, val sqFeet: Int? = null,

View File

@@ -8,12 +8,10 @@ import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.ui.draw.alpha
import androidx.compose.material.icons.Icons 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.Add
import androidx.compose.material.icons.filled.Category import androidx.compose.material.icons.filled.Category
import androidx.compose.material3.Button
import androidx.compose.material3.CircularProgressIndicator import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
@@ -28,6 +26,7 @@ import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.alpha
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel import androidx.lifecycle.viewmodel.compose.viewModel
@@ -54,7 +53,7 @@ fun RoomsScreen(
title = { Text("Available Rooms") }, title = { Text("Available Rooms") },
navigationIcon = { navigationIcon = {
IconButton(onClick = onBack) { IconButton(onClick = onBack) {
Icon(Icons.Default.ArrowBack, contentDescription = "Back") Icon(Icons.AutoMirrored.Default.ArrowBack, contentDescription = "Back")
} }
}, },
actions = { actions = {

View File

@@ -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")
}
}
)
}

View File

@@ -4,15 +4,12 @@ import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.aspectRatio import androidx.compose.foundation.layout.aspectRatio
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding 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.Icons
import androidx.compose.material.icons.automirrored.filled.Label
import androidx.compose.material.icons.filled.Delete import androidx.compose.material.icons.filled.Delete
import androidx.compose.material.icons.filled.DragIndicator import androidx.compose.material.icons.filled.DragIndicator
import androidx.compose.material.icons.filled.Label
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
@@ -85,7 +82,7 @@ fun RoomImageGridItem(
} }
if (onEditTags != null) { if (onEditTags != null) {
IconButton(onClick = onEditTags, enabled = hasId) { IconButton(onClick = onEditTags, enabled = hasId) {
Icon(Icons.Default.Label, contentDescription = "Edit Tags") Icon(Icons.AutoMirrored.Filled.Label, contentDescription = "Edit Tags")
} }
} }
if (onDelete != null) { if (onDelete != null) {

View File

@@ -42,7 +42,6 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel import androidx.lifecycle.viewmodel.compose.viewModel
import coil.compose.AsyncImage
import okhttp3.MediaType.Companion.toMediaType import okhttp3.MediaType.Companion.toMediaType
import okhttp3.MultipartBody import okhttp3.MultipartBody
import okhttp3.RequestBody.Companion.toRequestBody import okhttp3.RequestBody.Companion.toRequestBody
@@ -251,21 +250,9 @@ fun RoomImagesScreen(
val preview = previewUrl.value val preview = previewUrl.value
if (!preview.isNullOrBlank()) { if (!preview.isNullOrBlank()) {
AlertDialog( ImagePreviewDialog(
onDismissRequest = { previewUrl.value = null }, imageUrl = preview,
title = { Text("Preview") }, onDismiss = { previewUrl.value = null }
text = {
AsyncImage(
model = preview,
contentDescription = "Image preview",
modifier = Modifier.fillMaxWidth()
)
},
confirmButton = {
TextButton(onClick = { previewUrl.value = null }) {
Text("Close")
}
}
) )
} }

View File

@@ -11,9 +11,12 @@ import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.layout.wrapContentHeight import androidx.compose.foundation.layout.wrapContentHeight
import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll 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.lazy.grid.rememberLazyGridState
import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material.icons.filled.DragIndicator 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.text.input.KeyboardType
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel 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.data.api.model.RoomTypeDto
import com.android.trisolarispms.ui.roomimage.RoomImageGridItem import com.android.trisolarispms.ui.roomimage.RoomImageGridItem
import com.android.trisolarispms.ui.roomimage.ReorderableImageGrid import com.android.trisolarispms.ui.roomimage.ReorderableImageGrid
@@ -67,6 +72,9 @@ fun EditRoomTypeScreen(
val showDeleteConfirm = remember { mutableStateOf(false) } val showDeleteConfirm = remember { mutableStateOf(false) }
val orderedImages = remember { mutableStateOf<List<com.android.trisolarispms.data.api.model.ImageDto>>(emptyList()) } val orderedImages = remember { mutableStateOf<List<com.android.trisolarispms.data.api.model.ImageDto>>(emptyList()) }
val originalOrderIds = remember { mutableStateOf<List<String>>(emptyList()) } val originalOrderIds = remember { mutableStateOf<List<String>>(emptyList()) }
val previewUrl = remember { mutableStateOf<String?>(null) }
val showAmenityDialog = remember { mutableStateOf(false) }
val amenitySearch = remember { mutableStateOf("") }
val gridState = rememberLazyGridState() val gridState = rememberLazyGridState()
val saveRoomTypeImageOrder = remember(roomType.code, orderedImages.value) { val saveRoomTypeImageOrder = remember(roomType.code, orderedImages.value) {
{ {
@@ -199,25 +207,21 @@ fun EditRoomTypeScreen(
) )
Spacer(modifier = Modifier.height(12.dp)) Spacer(modifier = Modifier.height(12.dp))
Text(text = "Amenities", style = MaterialTheme.typography.titleSmall) Row(
if (amenityState.items.isEmpty()) { modifier = Modifier.fillMaxWidth(),
Text(text = "No amenities available", style = MaterialTheme.typography.bodySmall) horizontalArrangement = Arrangement.SpaceBetween,
} else { verticalAlignment = Alignment.CenterVertically
amenityState.items.forEach { amenity -> ) {
Row( Text(text = "Amenities", style = MaterialTheme.typography.titleSmall)
modifier = Modifier Button(onClick = { showAmenityDialog.value = true }) {
.fillMaxWidth() Text("Edit")
.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))
}
} }
} }
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)) Spacer(modifier = Modifier.height(16.dp))
Row( Row(
@@ -256,6 +260,7 @@ fun EditRoomTypeScreen(
RoomImageGridItem( RoomImageGridItem(
image = image, image = image,
modifier = dragHandleModifier, modifier = dragHandleModifier,
onPreview = { previewUrl.value = image.url ?: image.thumbnailUrl },
showTags = true 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 }
)
}
} }