Update amenities API and category suggestions
This commit is contained in:
@@ -65,10 +65,8 @@ class MainActivity : ComponentActivity() {
|
|||||||
isSuperAdmin = state.isSuperAdmin,
|
isSuperAdmin = state.isSuperAdmin,
|
||||||
onAddProperty = { route.value = AppRoute.AddProperty },
|
onAddProperty = { route.value = AppRoute.AddProperty },
|
||||||
onAmenities = {
|
onAmenities = {
|
||||||
selectedPropertyId.value?.let { propertyId ->
|
amenitiesReturnRoute.value = AppRoute.Home
|
||||||
amenitiesReturnRoute.value = AppRoute.Home
|
route.value = AppRoute.Amenities
|
||||||
route.value = AppRoute.Amenities(propertyId)
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
refreshKey = refreshKey.value,
|
refreshKey = refreshKey.value,
|
||||||
selectedPropertyId = selectedPropertyId.value,
|
selectedPropertyId = selectedPropertyId.value,
|
||||||
@@ -134,27 +132,24 @@ class MainActivity : ComponentActivity() {
|
|||||||
onBack = { route.value = AppRoute.RoomTypes(currentRoute.propertyId) },
|
onBack = { route.value = AppRoute.RoomTypes(currentRoute.propertyId) },
|
||||||
onSave = { route.value = AppRoute.RoomTypes(currentRoute.propertyId) }
|
onSave = { route.value = AppRoute.RoomTypes(currentRoute.propertyId) }
|
||||||
)
|
)
|
||||||
is AppRoute.Amenities -> AmenitiesScreen(
|
AppRoute.Amenities -> AmenitiesScreen(
|
||||||
propertyId = currentRoute.propertyId,
|
|
||||||
onBack = { route.value = amenitiesReturnRoute.value },
|
onBack = { route.value = amenitiesReturnRoute.value },
|
||||||
onAdd = { route.value = AppRoute.AddAmenity(currentRoute.propertyId) },
|
onAdd = { route.value = AppRoute.AddAmenity },
|
||||||
canManageAmenities = state.isSuperAdmin,
|
canManageAmenities = state.isSuperAdmin,
|
||||||
onEdit = {
|
onEdit = {
|
||||||
selectedAmenity.value = it
|
selectedAmenity.value = it
|
||||||
route.value = AppRoute.EditAmenity(currentRoute.propertyId, it.id ?: "")
|
route.value = AppRoute.EditAmenity(it.id ?: "")
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
is AppRoute.AddAmenity -> AddAmenityScreen(
|
AppRoute.AddAmenity -> AddAmenityScreen(
|
||||||
propertyId = currentRoute.propertyId,
|
onBack = { route.value = AppRoute.Amenities },
|
||||||
onBack = { route.value = AppRoute.Amenities(currentRoute.propertyId) },
|
onSave = { route.value = AppRoute.Amenities }
|
||||||
onSave = { route.value = AppRoute.Amenities(currentRoute.propertyId) }
|
|
||||||
)
|
)
|
||||||
is AppRoute.EditAmenity -> EditAmenityScreen(
|
is AppRoute.EditAmenity -> EditAmenityScreen(
|
||||||
propertyId = currentRoute.propertyId,
|
|
||||||
amenity = selectedAmenity.value
|
amenity = selectedAmenity.value
|
||||||
?: com.android.trisolarispms.data.api.model.AmenityDto(id = currentRoute.amenityId, name = ""),
|
?: com.android.trisolarispms.data.api.model.AmenityDto(id = currentRoute.amenityId, name = ""),
|
||||||
onBack = { route.value = AppRoute.Amenities(currentRoute.propertyId) },
|
onBack = { route.value = AppRoute.Amenities },
|
||||||
onSave = { route.value = AppRoute.Amenities(currentRoute.propertyId) }
|
onSave = { route.value = AppRoute.Amenities }
|
||||||
)
|
)
|
||||||
is AppRoute.AddRoom -> RoomFormScreen(
|
is AppRoute.AddRoom -> RoomFormScreen(
|
||||||
title = "Add Room",
|
title = "Add Room",
|
||||||
|
|||||||
@@ -12,25 +12,22 @@ import retrofit2.http.PUT
|
|||||||
import retrofit2.http.Path
|
import retrofit2.http.Path
|
||||||
|
|
||||||
interface AmenityApi {
|
interface AmenityApi {
|
||||||
@GET("properties/{propertyId}/amenities")
|
@GET("amenities")
|
||||||
suspend fun listAmenities(@Path("propertyId") propertyId: String): Response<List<AmenityDto>>
|
suspend fun listAmenities(): Response<List<AmenityDto>>
|
||||||
|
|
||||||
@POST("properties/{propertyId}/amenities")
|
@POST("amenities")
|
||||||
suspend fun createAmenity(
|
suspend fun createAmenity(
|
||||||
@Path("propertyId") propertyId: String,
|
|
||||||
@Body body: AmenityCreateRequest
|
@Body body: AmenityCreateRequest
|
||||||
): Response<AmenityDto>
|
): Response<AmenityDto>
|
||||||
|
|
||||||
@PUT("properties/{propertyId}/amenities/{amenityId}")
|
@PUT("amenities/{amenityId}")
|
||||||
suspend fun updateAmenity(
|
suspend fun updateAmenity(
|
||||||
@Path("propertyId") propertyId: String,
|
|
||||||
@Path("amenityId") amenityId: String,
|
@Path("amenityId") amenityId: String,
|
||||||
@Body body: AmenityUpdateRequest
|
@Body body: AmenityUpdateRequest
|
||||||
): Response<AmenityDto>
|
): Response<AmenityDto>
|
||||||
|
|
||||||
@DELETE("properties/{propertyId}/amenities/{amenityId}")
|
@DELETE("amenities/{amenityId}")
|
||||||
suspend fun deleteAmenity(
|
suspend fun deleteAmenity(
|
||||||
@Path("propertyId") propertyId: String,
|
|
||||||
@Path("amenityId") amenityId: String
|
@Path("amenityId") amenityId: String
|
||||||
): Response<Unit>
|
): Response<Unit>
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,13 +2,19 @@ package com.android.trisolarispms.data.api.model
|
|||||||
|
|
||||||
data class AmenityDto(
|
data class AmenityDto(
|
||||||
val id: String? = null,
|
val id: String? = null,
|
||||||
val name: String? = null
|
val name: String? = null,
|
||||||
|
val category: String? = null,
|
||||||
|
val iconKey: String? = null
|
||||||
)
|
)
|
||||||
|
|
||||||
data class AmenityCreateRequest(
|
data class AmenityCreateRequest(
|
||||||
val name: String
|
val name: String,
|
||||||
|
val category: String? = null,
|
||||||
|
val iconKey: String? = null
|
||||||
)
|
)
|
||||||
|
|
||||||
data class AmenityUpdateRequest(
|
data class AmenityUpdateRequest(
|
||||||
val name: String
|
val name: String,
|
||||||
|
val category: String? = null,
|
||||||
|
val iconKey: String? = null
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ sealed interface AppRoute {
|
|||||||
data class RoomTypes(val propertyId: String) : AppRoute
|
data class RoomTypes(val propertyId: String) : AppRoute
|
||||||
data class AddRoomType(val propertyId: String) : AppRoute
|
data class AddRoomType(val propertyId: String) : AppRoute
|
||||||
data class EditRoomType(val propertyId: String, val roomTypeId: String) : AppRoute
|
data class EditRoomType(val propertyId: String, val roomTypeId: String) : AppRoute
|
||||||
data class Amenities(val propertyId: String) : AppRoute
|
data object Amenities : AppRoute
|
||||||
data class AddAmenity(val propertyId: String) : AppRoute
|
data object AddAmenity : AppRoute
|
||||||
data class EditAmenity(val propertyId: String, val amenityId: String) : AppRoute
|
data class EditAmenity(val amenityId: String) : AppRoute
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -78,7 +78,7 @@ fun HomeScreen(
|
|||||||
onAddProperty()
|
onAddProperty()
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
if (isSuperAdmin && selectedPropertyId != null) {
|
if (isSuperAdmin) {
|
||||||
DropdownMenuItem(
|
DropdownMenuItem(
|
||||||
text = { Text("Modify Amenities") },
|
text = { Text("Modify Amenities") },
|
||||||
onClick = {
|
onClick = {
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package com.android.trisolarispms.ui.roomtype
|
package com.android.trisolarispms.ui.roomtype
|
||||||
|
|
||||||
|
import androidx.compose.foundation.clickable
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.Spacer
|
import androidx.compose.foundation.layout.Spacer
|
||||||
@@ -20,8 +21,10 @@ import androidx.compose.material3.Text
|
|||||||
import androidx.compose.material3.TopAppBar
|
import androidx.compose.material3.TopAppBar
|
||||||
import androidx.compose.material3.TopAppBarDefaults
|
import androidx.compose.material3.TopAppBarDefaults
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
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.runtime.remember
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||||
@@ -29,12 +32,26 @@ import androidx.lifecycle.viewmodel.compose.viewModel
|
|||||||
@Composable
|
@Composable
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
fun AddAmenityScreen(
|
fun AddAmenityScreen(
|
||||||
propertyId: String,
|
|
||||||
onBack: () -> Unit,
|
onBack: () -> Unit,
|
||||||
onSave: () -> Unit,
|
onSave: () -> Unit,
|
||||||
viewModel: AmenityFormViewModel = viewModel()
|
viewModel: AmenityFormViewModel = viewModel(),
|
||||||
|
amenityListViewModel: AmenityListViewModel = viewModel()
|
||||||
) {
|
) {
|
||||||
val state by viewModel.state.collectAsState()
|
val state by viewModel.state.collectAsState()
|
||||||
|
val amenityState by amenityListViewModel.state.collectAsState()
|
||||||
|
|
||||||
|
LaunchedEffect(Unit) {
|
||||||
|
amenityListViewModel.load()
|
||||||
|
}
|
||||||
|
|
||||||
|
val categorySuggestions = remember(state.category, amenityState.items) {
|
||||||
|
val all = amenityState.items.mapNotNull { it.category?.trim() }
|
||||||
|
.filter { it.isNotBlank() }
|
||||||
|
.distinct()
|
||||||
|
.sorted()
|
||||||
|
if (state.category.isBlank()) all
|
||||||
|
else all.filter { it.contains(state.category, ignoreCase = true) }
|
||||||
|
}
|
||||||
|
|
||||||
Scaffold(
|
Scaffold(
|
||||||
topBar = {
|
topBar = {
|
||||||
@@ -46,7 +63,7 @@ fun AddAmenityScreen(
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
actions = {
|
actions = {
|
||||||
IconButton(onClick = { viewModel.submitCreate(propertyId, onSave) }) {
|
IconButton(onClick = { viewModel.submitCreate(onSave) }) {
|
||||||
Icon(Icons.Default.Done, contentDescription = "Save")
|
Icon(Icons.Default.Done, contentDescription = "Save")
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -67,6 +84,35 @@ fun AddAmenityScreen(
|
|||||||
label = { Text("Name") },
|
label = { Text("Name") },
|
||||||
modifier = Modifier.fillMaxWidth()
|
modifier = Modifier.fillMaxWidth()
|
||||||
)
|
)
|
||||||
|
Spacer(modifier = Modifier.height(12.dp))
|
||||||
|
|
||||||
|
OutlinedTextField(
|
||||||
|
value = state.category,
|
||||||
|
onValueChange = viewModel::onCategoryChange,
|
||||||
|
label = { Text("Category") },
|
||||||
|
modifier = Modifier.fillMaxWidth()
|
||||||
|
)
|
||||||
|
if (categorySuggestions.isNotEmpty()) {
|
||||||
|
Spacer(modifier = Modifier.height(6.dp))
|
||||||
|
categorySuggestions.take(5).forEach { suggestion ->
|
||||||
|
Text(
|
||||||
|
text = suggestion,
|
||||||
|
style = MaterialTheme.typography.labelLarge,
|
||||||
|
color = MaterialTheme.colorScheme.primary,
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(vertical = 2.dp)
|
||||||
|
.clickable { viewModel.onCategoryChange(suggestion) }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Spacer(modifier = Modifier.height(12.dp))
|
||||||
|
|
||||||
|
OutlinedTextField(
|
||||||
|
value = state.iconKey,
|
||||||
|
onValueChange = viewModel::onIconKeyChange,
|
||||||
|
label = { Text("Icon Key") },
|
||||||
|
modifier = Modifier.fillMaxWidth()
|
||||||
|
)
|
||||||
state.error?.let {
|
state.error?.let {
|
||||||
Spacer(modifier = Modifier.height(12.dp))
|
Spacer(modifier = Modifier.height(12.dp))
|
||||||
Text(text = it, color = MaterialTheme.colorScheme.error)
|
Text(text = it, color = MaterialTheme.colorScheme.error)
|
||||||
|
|||||||
@@ -44,8 +44,8 @@ fun AddRoomTypeScreen(
|
|||||||
val state by viewModel.state.collectAsState()
|
val state by viewModel.state.collectAsState()
|
||||||
val amenityState by amenityViewModel.state.collectAsState()
|
val amenityState by amenityViewModel.state.collectAsState()
|
||||||
|
|
||||||
LaunchedEffect(propertyId) {
|
LaunchedEffect(Unit) {
|
||||||
amenityViewModel.load(propertyId)
|
amenityViewModel.load()
|
||||||
}
|
}
|
||||||
|
|
||||||
Scaffold(
|
Scaffold(
|
||||||
|
|||||||
@@ -33,7 +33,6 @@ import com.android.trisolarispms.data.api.model.AmenityDto
|
|||||||
@Composable
|
@Composable
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
fun AmenitiesScreen(
|
fun AmenitiesScreen(
|
||||||
propertyId: String,
|
|
||||||
onBack: () -> Unit,
|
onBack: () -> Unit,
|
||||||
onAdd: () -> Unit,
|
onAdd: () -> Unit,
|
||||||
canManageAmenities: Boolean,
|
canManageAmenities: Boolean,
|
||||||
@@ -42,8 +41,8 @@ fun AmenitiesScreen(
|
|||||||
) {
|
) {
|
||||||
val state by viewModel.state.collectAsState()
|
val state by viewModel.state.collectAsState()
|
||||||
|
|
||||||
LaunchedEffect(propertyId) {
|
LaunchedEffect(Unit) {
|
||||||
viewModel.load(propertyId)
|
viewModel.load()
|
||||||
}
|
}
|
||||||
|
|
||||||
Scaffold(
|
Scaffold(
|
||||||
@@ -100,11 +99,15 @@ fun AmenitiesScreen(
|
|||||||
.clickable(enabled = canManageAmenities && item.id != null) { onEdit(item) }
|
.clickable(enabled = canManageAmenities && item.id != null) { onEdit(item) }
|
||||||
)
|
)
|
||||||
if (canManageAmenities && item.id != null) {
|
if (canManageAmenities && item.id != null) {
|
||||||
IconButton(onClick = { viewModel.deleteAmenity(propertyId, item.id) }) {
|
IconButton(onClick = { viewModel.deleteAmenity(item.id) }) {
|
||||||
Icon(Icons.Default.Delete, contentDescription = "Delete Amenity")
|
Icon(Icons.Default.Delete, contentDescription = "Delete Amenity")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
val meta = listOfNotNull(item.category, item.iconKey).joinToString(" • ")
|
||||||
|
if (meta.isNotBlank()) {
|
||||||
|
Text(text = meta, style = MaterialTheme.typography.bodySmall)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,8 @@ package com.android.trisolarispms.ui.roomtype
|
|||||||
|
|
||||||
data class AmenityFormState(
|
data class AmenityFormState(
|
||||||
val name: String = "",
|
val name: String = "",
|
||||||
|
val category: String = "",
|
||||||
|
val iconKey: String = "",
|
||||||
val isLoading: Boolean = false,
|
val isLoading: Boolean = false,
|
||||||
val error: String? = null,
|
val error: String? = null,
|
||||||
val success: Boolean = false
|
val success: Boolean = false
|
||||||
|
|||||||
@@ -14,15 +14,30 @@ class AmenityFormViewModel : ViewModel() {
|
|||||||
private val _state = MutableStateFlow(AmenityFormState())
|
private val _state = MutableStateFlow(AmenityFormState())
|
||||||
val state: StateFlow<AmenityFormState> = _state
|
val state: StateFlow<AmenityFormState> = _state
|
||||||
|
|
||||||
fun setAmenityName(name: String) {
|
fun setAmenity(amenity: com.android.trisolarispms.data.api.model.AmenityDto) {
|
||||||
_state.update { it.copy(name = name, error = null) }
|
_state.update {
|
||||||
|
it.copy(
|
||||||
|
name = amenity.name.orEmpty(),
|
||||||
|
category = amenity.category.orEmpty(),
|
||||||
|
iconKey = amenity.iconKey.orEmpty(),
|
||||||
|
error = null
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun onNameChange(value: String) {
|
fun onNameChange(value: String) {
|
||||||
_state.update { it.copy(name = value, error = null) }
|
_state.update { it.copy(name = value, error = null) }
|
||||||
}
|
}
|
||||||
|
|
||||||
fun submitCreate(propertyId: String, onDone: () -> Unit) {
|
fun onCategoryChange(value: String) {
|
||||||
|
_state.update { it.copy(category = value, error = null) }
|
||||||
|
}
|
||||||
|
|
||||||
|
fun onIconKeyChange(value: String) {
|
||||||
|
_state.update { it.copy(iconKey = value, error = null) }
|
||||||
|
}
|
||||||
|
|
||||||
|
fun submitCreate(onDone: () -> Unit) {
|
||||||
val name = state.value.name.trim()
|
val name = state.value.name.trim()
|
||||||
if (name.isBlank()) {
|
if (name.isBlank()) {
|
||||||
_state.update { it.copy(error = "Name is required") }
|
_state.update { it.copy(error = "Name is required") }
|
||||||
@@ -32,7 +47,13 @@ class AmenityFormViewModel : ViewModel() {
|
|||||||
_state.update { it.copy(isLoading = true, error = null) }
|
_state.update { it.copy(isLoading = true, error = null) }
|
||||||
try {
|
try {
|
||||||
val api = ApiClient.create()
|
val api = ApiClient.create()
|
||||||
val response = api.createAmenity(propertyId, AmenityCreateRequest(name))
|
val response = api.createAmenity(
|
||||||
|
AmenityCreateRequest(
|
||||||
|
name = name,
|
||||||
|
category = state.value.category.trim().ifBlank { null },
|
||||||
|
iconKey = state.value.iconKey.trim().ifBlank { null }
|
||||||
|
)
|
||||||
|
)
|
||||||
if (response.isSuccessful) {
|
if (response.isSuccessful) {
|
||||||
_state.update { it.copy(isLoading = false, success = true) }
|
_state.update { it.copy(isLoading = false, success = true) }
|
||||||
onDone()
|
onDone()
|
||||||
@@ -45,7 +66,7 @@ class AmenityFormViewModel : ViewModel() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun submitUpdate(propertyId: String, amenityId: String, onDone: () -> Unit) {
|
fun submitUpdate(amenityId: String, onDone: () -> Unit) {
|
||||||
val name = state.value.name.trim()
|
val name = state.value.name.trim()
|
||||||
if (name.isBlank()) {
|
if (name.isBlank()) {
|
||||||
_state.update { it.copy(error = "Name is required") }
|
_state.update { it.copy(error = "Name is required") }
|
||||||
@@ -55,7 +76,14 @@ class AmenityFormViewModel : ViewModel() {
|
|||||||
_state.update { it.copy(isLoading = true, error = null) }
|
_state.update { it.copy(isLoading = true, error = null) }
|
||||||
try {
|
try {
|
||||||
val api = ApiClient.create()
|
val api = ApiClient.create()
|
||||||
val response = api.updateAmenity(propertyId, amenityId, AmenityUpdateRequest(name))
|
val response = api.updateAmenity(
|
||||||
|
amenityId,
|
||||||
|
AmenityUpdateRequest(
|
||||||
|
name = name,
|
||||||
|
category = state.value.category.trim().ifBlank { null },
|
||||||
|
iconKey = state.value.iconKey.trim().ifBlank { null }
|
||||||
|
)
|
||||||
|
)
|
||||||
if (response.isSuccessful) {
|
if (response.isSuccessful) {
|
||||||
_state.update { it.copy(isLoading = false, success = true) }
|
_state.update { it.copy(isLoading = false, success = true) }
|
||||||
onDone()
|
onDone()
|
||||||
|
|||||||
@@ -12,13 +12,12 @@ class AmenityListViewModel : ViewModel() {
|
|||||||
private val _state = MutableStateFlow(AmenityListState())
|
private val _state = MutableStateFlow(AmenityListState())
|
||||||
val state: StateFlow<AmenityListState> = _state
|
val state: StateFlow<AmenityListState> = _state
|
||||||
|
|
||||||
fun load(propertyId: String) {
|
fun load() {
|
||||||
if (propertyId.isBlank()) return
|
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
_state.update { it.copy(isLoading = true, error = null) }
|
_state.update { it.copy(isLoading = true, error = null) }
|
||||||
try {
|
try {
|
||||||
val api = ApiClient.create()
|
val api = ApiClient.create()
|
||||||
val response = api.listAmenities(propertyId)
|
val response = api.listAmenities()
|
||||||
if (response.isSuccessful) {
|
if (response.isSuccessful) {
|
||||||
_state.update {
|
_state.update {
|
||||||
it.copy(
|
it.copy(
|
||||||
@@ -36,13 +35,13 @@ class AmenityListViewModel : ViewModel() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun deleteAmenity(propertyId: String, amenityId: String) {
|
fun deleteAmenity(amenityId: String) {
|
||||||
if (propertyId.isBlank() || amenityId.isBlank()) return
|
if (amenityId.isBlank()) return
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
_state.update { it.copy(isLoading = true, error = null) }
|
_state.update { it.copy(isLoading = true, error = null) }
|
||||||
try {
|
try {
|
||||||
val api = ApiClient.create()
|
val api = ApiClient.create()
|
||||||
val response = api.deleteAmenity(propertyId, amenityId)
|
val response = api.deleteAmenity(amenityId)
|
||||||
if (response.isSuccessful) {
|
if (response.isSuccessful) {
|
||||||
_state.update { current ->
|
_state.update { current ->
|
||||||
current.copy(
|
current.copy(
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package com.android.trisolarispms.ui.roomtype
|
package com.android.trisolarispms.ui.roomtype
|
||||||
|
|
||||||
|
import androidx.compose.foundation.clickable
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.Spacer
|
import androidx.compose.foundation.layout.Spacer
|
||||||
@@ -23,6 +24,7 @@ import androidx.compose.runtime.Composable
|
|||||||
import androidx.compose.runtime.LaunchedEffect
|
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.runtime.remember
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||||
@@ -31,16 +33,29 @@ import com.android.trisolarispms.data.api.model.AmenityDto
|
|||||||
@Composable
|
@Composable
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
fun EditAmenityScreen(
|
fun EditAmenityScreen(
|
||||||
propertyId: String,
|
|
||||||
amenity: AmenityDto,
|
amenity: AmenityDto,
|
||||||
onBack: () -> Unit,
|
onBack: () -> Unit,
|
||||||
onSave: () -> Unit,
|
onSave: () -> Unit,
|
||||||
viewModel: AmenityFormViewModel = viewModel()
|
viewModel: AmenityFormViewModel = viewModel(),
|
||||||
|
amenityListViewModel: AmenityListViewModel = viewModel()
|
||||||
) {
|
) {
|
||||||
val state by viewModel.state.collectAsState()
|
val state by viewModel.state.collectAsState()
|
||||||
|
val amenityState by amenityListViewModel.state.collectAsState()
|
||||||
|
|
||||||
LaunchedEffect(amenity.id) {
|
LaunchedEffect(amenity.id) {
|
||||||
viewModel.setAmenityName(amenity.name.orEmpty())
|
viewModel.setAmenity(amenity)
|
||||||
|
}
|
||||||
|
LaunchedEffect(Unit) {
|
||||||
|
amenityListViewModel.load()
|
||||||
|
}
|
||||||
|
|
||||||
|
val categorySuggestions = remember(state.category, amenityState.items) {
|
||||||
|
val all = amenityState.items.mapNotNull { it.category?.trim() }
|
||||||
|
.filter { it.isNotBlank() }
|
||||||
|
.distinct()
|
||||||
|
.sorted()
|
||||||
|
if (state.category.isBlank()) all
|
||||||
|
else all.filter { it.contains(state.category, ignoreCase = true) }
|
||||||
}
|
}
|
||||||
|
|
||||||
Scaffold(
|
Scaffold(
|
||||||
@@ -55,7 +70,7 @@ fun EditAmenityScreen(
|
|||||||
actions = {
|
actions = {
|
||||||
IconButton(onClick = {
|
IconButton(onClick = {
|
||||||
val id = amenity.id.orEmpty()
|
val id = amenity.id.orEmpty()
|
||||||
viewModel.submitUpdate(propertyId, id, onSave)
|
viewModel.submitUpdate(id, onSave)
|
||||||
}) {
|
}) {
|
||||||
Icon(Icons.Default.Done, contentDescription = "Save")
|
Icon(Icons.Default.Done, contentDescription = "Save")
|
||||||
}
|
}
|
||||||
@@ -77,6 +92,35 @@ fun EditAmenityScreen(
|
|||||||
label = { Text("Name") },
|
label = { Text("Name") },
|
||||||
modifier = Modifier.fillMaxWidth()
|
modifier = Modifier.fillMaxWidth()
|
||||||
)
|
)
|
||||||
|
Spacer(modifier = Modifier.height(12.dp))
|
||||||
|
|
||||||
|
OutlinedTextField(
|
||||||
|
value = state.category,
|
||||||
|
onValueChange = viewModel::onCategoryChange,
|
||||||
|
label = { Text("Category") },
|
||||||
|
modifier = Modifier.fillMaxWidth()
|
||||||
|
)
|
||||||
|
if (categorySuggestions.isNotEmpty()) {
|
||||||
|
Spacer(modifier = Modifier.height(6.dp))
|
||||||
|
categorySuggestions.take(5).forEach { suggestion ->
|
||||||
|
Text(
|
||||||
|
text = suggestion,
|
||||||
|
style = MaterialTheme.typography.labelLarge,
|
||||||
|
color = MaterialTheme.colorScheme.primary,
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(vertical = 2.dp)
|
||||||
|
.clickable { viewModel.onCategoryChange(suggestion) }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Spacer(modifier = Modifier.height(12.dp))
|
||||||
|
|
||||||
|
OutlinedTextField(
|
||||||
|
value = state.iconKey,
|
||||||
|
onValueChange = viewModel::onIconKeyChange,
|
||||||
|
label = { Text("Icon Key") },
|
||||||
|
modifier = Modifier.fillMaxWidth()
|
||||||
|
)
|
||||||
state.error?.let {
|
state.error?.let {
|
||||||
Spacer(modifier = Modifier.height(12.dp))
|
Spacer(modifier = Modifier.height(12.dp))
|
||||||
Text(text = it, color = MaterialTheme.colorScheme.error)
|
Text(text = it, color = MaterialTheme.colorScheme.error)
|
||||||
|
|||||||
@@ -49,8 +49,8 @@ fun EditRoomTypeScreen(
|
|||||||
LaunchedEffect(roomType.id) {
|
LaunchedEffect(roomType.id) {
|
||||||
viewModel.setRoomType(roomType)
|
viewModel.setRoomType(roomType)
|
||||||
}
|
}
|
||||||
LaunchedEffect(propertyId) {
|
LaunchedEffect(Unit) {
|
||||||
amenityViewModel.load(propertyId)
|
amenityViewModel.load()
|
||||||
}
|
}
|
||||||
|
|
||||||
Scaffold(
|
Scaffold(
|
||||||
|
|||||||
Reference in New Issue
Block a user