Compare commits
5 Commits
86307a66c8
...
8c790fbce0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8c790fbce0 | ||
|
|
f97834291d | ||
|
|
0f0db0dcf5 | ||
|
|
99ce18a435 | ||
|
|
342ff6a237 |
56
AGENTS.md
56
AGENTS.md
@@ -527,3 +527,59 @@ GET /properties/{propertyId}/bookings/{bookingId}/balance
|
||||
## 7) Compose Notes
|
||||
|
||||
- Use `androidx.compose.foundation.text.KeyboardOptions` for keyboard options imports.
|
||||
|
||||
---
|
||||
|
||||
## 8) Engineering Structure & Anti-Boilerplate Rules
|
||||
|
||||
### Non-negotiable coding rules
|
||||
|
||||
- Never add duplicate business logic in multiple files.
|
||||
- Never copy-paste permission checks, navigation decisions, mapping logic, or API call patterns.
|
||||
- If similar logic appears 2+ times, extract shared function/class immediately.
|
||||
- Prefer typed models/enums over raw strings for roles/status/flags.
|
||||
- Keep files small and purpose-driven; split before a file becomes hard to scan.
|
||||
|
||||
### Required project structure (current baseline)
|
||||
|
||||
- `core/` -> cross-cutting business primitives/policies (e.g., auth policy, role enum).
|
||||
- `data/api/core/` -> API client, constants, token providers, aggregated API service.
|
||||
- `data/api/service/` -> Retrofit endpoint interfaces only.
|
||||
- `data/api/model/` -> DTO/request/response models.
|
||||
- `ui/navigation/` -> route model, navigation orchestrators, back-navigation rules.
|
||||
- `ui/<feature>/` -> screen + state + viewmodel for that feature.
|
||||
|
||||
### How to implement future logic (mandatory workflow)
|
||||
|
||||
1. Define/extend domain type first (enum/data model/policy) instead of raw literals.
|
||||
2. Add/extend API contract in `data/api/service` and models in `data/api/model`.
|
||||
3. Add shared logic once (policy/helper/mapper) in `core` or feature-common layer.
|
||||
4. Keep ViewModel thin: orchestrate calls, state, and errors only.
|
||||
5. Keep UI dumb: consume state and callbacks; avoid business rules in composables.
|
||||
6. If navigation changes, update `ui/navigation` only (single source of truth).
|
||||
7. Before finishing, remove any newly introduced duplication and compile-check.
|
||||
|
||||
### PR/refactor acceptance checklist
|
||||
|
||||
- No repeated role/permission checks across screens.
|
||||
- No repeated model mapping blocks (extract mapper/helper).
|
||||
- No giant god-file when it can be split by domain responsibility.
|
||||
- Imports/packages follow the structure above.
|
||||
- Build passes: `./gradlew :app:compileDebugKotlin`.
|
||||
|
||||
### Guest Documents Authorization (mandatory)
|
||||
|
||||
- View access: `ADMIN`, `MANAGER` (and super admin).
|
||||
- Modify access (upload/delete): allowed only when booking status is `OPEN` or `CHECKED_IN`.
|
||||
- For `CHECKED_OUT`, `CANCELLED`, `NO_SHOW`: documents are read-only.
|
||||
- Never couple guest document permissions with Razorpay/settings permissions.
|
||||
|
||||
### Permission design guardrail
|
||||
|
||||
- Do not reuse one feature's permission gate for another unrelated feature.
|
||||
- Add explicit policy methods in `core/auth/AuthzPolicy` for each feature capability.
|
||||
|
||||
### Refactor safety rule
|
||||
|
||||
- Any package/file movement must include import updates in same change.
|
||||
- After refactor, compile check is mandatory: `./gradlew :app:compileDebugKotlin`.
|
||||
|
||||
@@ -3,52 +3,15 @@ package com.android.trisolarispms
|
||||
import android.os.Bundle
|
||||
import androidx.activity.ComponentActivity
|
||||
import androidx.activity.compose.setContent
|
||||
import androidx.activity.compose.BackHandler
|
||||
import androidx.activity.enableEdgeToEdge
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import com.android.trisolarispms.ui.AppRoute
|
||||
import com.android.trisolarispms.ui.auth.AuthScreen
|
||||
import com.android.trisolarispms.ui.auth.AuthViewModel
|
||||
import com.android.trisolarispms.ui.auth.NameScreen
|
||||
import com.android.trisolarispms.ui.auth.UnauthorizedScreen
|
||||
import com.android.trisolarispms.ui.booking.BookingCreateScreen
|
||||
import com.android.trisolarispms.ui.guest.GuestInfoScreen
|
||||
import com.android.trisolarispms.ui.guest.GuestSignatureScreen
|
||||
import com.android.trisolarispms.ui.booking.BookingExpectedDatesScreen
|
||||
import com.android.trisolarispms.ui.roomstay.ManageRoomStayRatesScreen
|
||||
import com.android.trisolarispms.ui.roomstay.ManageRoomStaySelectScreen
|
||||
import com.android.trisolarispms.ui.roomstay.ManageRoomStaySelection
|
||||
import com.android.trisolarispms.ui.roomstay.BookingRoomStaysScreen
|
||||
import com.android.trisolarispms.ui.roomstay.BookingDetailsTabsScreen
|
||||
import com.android.trisolarispms.ui.home.HomeScreen
|
||||
import com.android.trisolarispms.ui.payment.BookingPaymentsScreen
|
||||
import com.android.trisolarispms.ui.property.AddPropertyScreen
|
||||
import com.android.trisolarispms.ui.room.RoomFormScreen
|
||||
import com.android.trisolarispms.ui.room.RoomsScreen
|
||||
import com.android.trisolarispms.ui.card.IssueTemporaryCardScreen
|
||||
import com.android.trisolarispms.ui.card.CardInfoScreen
|
||||
import com.android.trisolarispms.ui.roomimage.RoomImagesScreen
|
||||
import com.android.trisolarispms.ui.roomimage.ImageTagsScreen
|
||||
import com.android.trisolarispms.ui.roomimage.AddImageTagScreen
|
||||
import com.android.trisolarispms.ui.roomimage.EditImageTagScreen
|
||||
import com.android.trisolarispms.ui.roomstay.ActiveRoomStaysScreen
|
||||
import com.android.trisolarispms.ui.razorpay.RazorpaySettingsScreen
|
||||
import com.android.trisolarispms.ui.razorpay.RazorpayQrScreen
|
||||
import com.android.trisolarispms.ui.users.PropertyUsersScreen
|
||||
import com.android.trisolarispms.ui.users.PropertyAccessCodeScreen
|
||||
import com.android.trisolarispms.ui.users.SuperAdminUserDirectoryScreen
|
||||
import com.android.trisolarispms.ui.roomtype.AddAmenityScreen
|
||||
import com.android.trisolarispms.ui.roomtype.AddRoomTypeScreen
|
||||
import com.android.trisolarispms.ui.roomtype.AmenitiesScreen
|
||||
import com.android.trisolarispms.ui.roomtype.EditAmenityScreen
|
||||
import com.android.trisolarispms.ui.roomtype.EditRoomTypeScreen
|
||||
import com.android.trisolarispms.ui.roomtype.RatePlanCalendarScreen
|
||||
import com.android.trisolarispms.ui.roomtype.RoomTypesScreen
|
||||
import com.android.trisolarispms.ui.navigation.MainRouteContent
|
||||
import com.android.trisolarispms.ui.theme.TrisolarisPMSTheme
|
||||
|
||||
class MainActivity : ComponentActivity() {
|
||||
@@ -60,709 +23,17 @@ class MainActivity : ComponentActivity() {
|
||||
val authViewModel: AuthViewModel = viewModel()
|
||||
val state by authViewModel.state.collectAsState()
|
||||
|
||||
if (state.unauthorized) {
|
||||
UnauthorizedScreen(
|
||||
when {
|
||||
state.unauthorized -> UnauthorizedScreen(
|
||||
message = state.error ?: "Not authorized. Contact admin.",
|
||||
onSignOut = authViewModel::signOut
|
||||
)
|
||||
} else if (state.apiVerified && state.needsName) {
|
||||
NameScreen(viewModel = authViewModel)
|
||||
} else if (state.apiVerified) {
|
||||
val route = remember { mutableStateOf<AppRoute>(AppRoute.Home) }
|
||||
val refreshKey = remember { mutableStateOf(0) }
|
||||
val selectedPropertyId = remember { mutableStateOf<String?>(null) }
|
||||
val selectedPropertyName = remember { mutableStateOf<String?>(null) }
|
||||
val selectedRoom = remember { mutableStateOf<com.android.trisolarispms.data.api.model.RoomDto?>(null) }
|
||||
val selectedRoomType = remember { mutableStateOf<com.android.trisolarispms.data.api.model.RoomTypeDto?>(null) }
|
||||
val selectedAmenity = remember { mutableStateOf<com.android.trisolarispms.data.api.model.AmenityDto?>(null) }
|
||||
val selectedGuest = remember { mutableStateOf<com.android.trisolarispms.data.api.model.GuestDto?>(null) }
|
||||
val selectedGuestPhone = remember { mutableStateOf<String?>(null) }
|
||||
val selectedImageTag = remember { mutableStateOf<com.android.trisolarispms.data.api.model.RoomImageTagDto?>(null) }
|
||||
val selectedManageRooms = remember { mutableStateOf<List<ManageRoomStaySelection>>(emptyList()) }
|
||||
val roomFormKey = remember { mutableStateOf(0) }
|
||||
val amenitiesReturnRoute = remember { mutableStateOf<AppRoute>(AppRoute.Home) }
|
||||
val currentRoute = route.value
|
||||
val singlePropertyId = state.propertyRoles.keys.firstOrNull()
|
||||
val singlePropertyIsAdmin = singlePropertyId?.let {
|
||||
state.propertyRoles[it]?.contains("ADMIN") == true
|
||||
} ?: false
|
||||
val canManageProperty: (String) -> Boolean = { propertyId ->
|
||||
state.isSuperAdmin || (state.propertyRoles[propertyId]?.contains("ADMIN") == true)
|
||||
}
|
||||
val canIssueTemporaryCard: (String) -> Boolean = { propertyId ->
|
||||
state.isSuperAdmin || state.propertyRoles[propertyId]?.any {
|
||||
it == "ADMIN" || it == "MANAGER"
|
||||
} == true
|
||||
}
|
||||
val canViewCardInfo: (String) -> Boolean = { propertyId ->
|
||||
state.isSuperAdmin || state.propertyRoles[propertyId]?.any {
|
||||
it == "ADMIN" || it == "MANAGER" || it == "STAFF"
|
||||
} == true
|
||||
}
|
||||
val canManageRazorpaySettings: (String) -> Boolean = { propertyId ->
|
||||
state.isSuperAdmin || state.propertyRoles[propertyId]?.contains("ADMIN") == true
|
||||
}
|
||||
val canDeleteCashPayment: (String) -> Boolean = { propertyId ->
|
||||
state.isSuperAdmin || state.propertyRoles[propertyId]?.contains("ADMIN") == true
|
||||
}
|
||||
val canManagePropertyUsers: (String) -> Boolean = { propertyId ->
|
||||
state.isSuperAdmin || state.propertyRoles[propertyId]?.contains("ADMIN") == true
|
||||
}
|
||||
val canCreateBookingFor: (String) -> Boolean = { propertyId ->
|
||||
state.isSuperAdmin || state.propertyRoles[propertyId]?.any {
|
||||
it == "ADMIN" || it == "MANAGER" || it == "STAFF"
|
||||
} == true
|
||||
}
|
||||
val allowedAccessCodeRoles: (String) -> List<String> = { propertyId ->
|
||||
if (state.isSuperAdmin) {
|
||||
listOf("MANAGER", "STAFF", "AGENT")
|
||||
} else {
|
||||
val roles = state.propertyRoles[propertyId].orEmpty()
|
||||
when {
|
||||
roles.contains("ADMIN") -> listOf("MANAGER", "STAFF", "AGENT")
|
||||
roles.contains("MANAGER") -> listOf("STAFF", "AGENT")
|
||||
else -> emptyList()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
BackHandler(enabled = currentRoute != AppRoute.Home) {
|
||||
when (currentRoute) {
|
||||
AppRoute.Home -> Unit
|
||||
AppRoute.AddProperty -> route.value = AppRoute.Home
|
||||
AppRoute.SuperAdminUsers -> route.value = AppRoute.Home
|
||||
is AppRoute.ActiveRoomStays -> {
|
||||
val isAdmin = state.propertyRoles[currentRoute.propertyId]?.contains("ADMIN") == true
|
||||
val blockBack = !state.isSuperAdmin && !isAdmin && state.propertyRoles.size == 1
|
||||
if (!blockBack) {
|
||||
route.value = AppRoute.Home
|
||||
}
|
||||
}
|
||||
is AppRoute.PropertyUsers -> route.value = AppRoute.ActiveRoomStays(
|
||||
currentRoute.propertyId,
|
||||
selectedPropertyName.value ?: "Property"
|
||||
)
|
||||
is AppRoute.PropertyAccessCode -> route.value = AppRoute.PropertyUsers(
|
||||
currentRoute.propertyId
|
||||
)
|
||||
is AppRoute.Rooms -> route.value = AppRoute.ActiveRoomStays(
|
||||
currentRoute.propertyId,
|
||||
selectedPropertyName.value ?: "Property"
|
||||
)
|
||||
is AppRoute.AddRoom -> route.value = AppRoute.Rooms(currentRoute.propertyId)
|
||||
is AppRoute.EditRoom -> route.value = AppRoute.Rooms(currentRoute.propertyId)
|
||||
is AppRoute.RoomTypes -> route.value = AppRoute.Rooms(currentRoute.propertyId)
|
||||
is AppRoute.AddRoomType -> route.value = AppRoute.RoomTypes(currentRoute.propertyId)
|
||||
is AppRoute.EditRoomType -> route.value = AppRoute.RoomTypes(currentRoute.propertyId)
|
||||
AppRoute.Amenities -> route.value = amenitiesReturnRoute.value
|
||||
AppRoute.AddAmenity -> route.value = AppRoute.Amenities
|
||||
is AppRoute.EditAmenity -> route.value = AppRoute.Amenities
|
||||
AppRoute.ImageTags -> route.value = AppRoute.Home
|
||||
AppRoute.AddImageTag -> route.value = AppRoute.ImageTags
|
||||
is AppRoute.EditImageTag -> route.value = AppRoute.ImageTags
|
||||
is AppRoute.RoomImages -> route.value = AppRoute.EditRoom(
|
||||
currentRoute.propertyId,
|
||||
currentRoute.roomId
|
||||
)
|
||||
is AppRoute.IssueTemporaryCard -> route.value = AppRoute.Rooms(currentRoute.propertyId)
|
||||
is AppRoute.CardInfo -> route.value = AppRoute.Rooms(currentRoute.propertyId)
|
||||
is AppRoute.RatePlanCalendar -> route.value = AppRoute.EditRoomType(
|
||||
currentRoute.propertyId,
|
||||
currentRoute.roomTypeId
|
||||
)
|
||||
is AppRoute.RazorpaySettings -> route.value = AppRoute.ActiveRoomStays(
|
||||
currentRoute.propertyId,
|
||||
selectedPropertyName.value ?: "Property"
|
||||
)
|
||||
is AppRoute.RazorpayQr -> route.value = AppRoute.BookingDetailsTabs(
|
||||
currentRoute.propertyId,
|
||||
currentRoute.bookingId,
|
||||
null
|
||||
)
|
||||
is AppRoute.CreateBooking -> route.value = AppRoute.ActiveRoomStays(
|
||||
currentRoute.propertyId,
|
||||
selectedPropertyName.value ?: "Property"
|
||||
)
|
||||
is AppRoute.GuestInfo -> route.value = AppRoute.Home
|
||||
is AppRoute.GuestSignature -> route.value = AppRoute.GuestInfo(
|
||||
currentRoute.propertyId,
|
||||
currentRoute.bookingId,
|
||||
currentRoute.guestId
|
||||
)
|
||||
is AppRoute.ManageRoomStaySelect -> route.value = AppRoute.ActiveRoomStays(
|
||||
currentRoute.propertyId,
|
||||
selectedPropertyName.value ?: "Property"
|
||||
)
|
||||
is AppRoute.ManageRoomStayRates -> route.value = AppRoute.ManageRoomStaySelect(
|
||||
currentRoute.propertyId,
|
||||
currentRoute.bookingId,
|
||||
currentRoute.fromAt,
|
||||
currentRoute.toAt
|
||||
)
|
||||
is AppRoute.ManageRoomStaySelectFromBooking -> route.value = AppRoute.ActiveRoomStays(
|
||||
currentRoute.propertyId,
|
||||
selectedPropertyName.value ?: "Property"
|
||||
)
|
||||
is AppRoute.ManageRoomStayRatesFromBooking -> route.value = AppRoute.ManageRoomStaySelectFromBooking(
|
||||
currentRoute.propertyId,
|
||||
currentRoute.bookingId,
|
||||
currentRoute.guestId,
|
||||
currentRoute.fromAt,
|
||||
currentRoute.toAt
|
||||
)
|
||||
is AppRoute.BookingRoomStays -> route.value = AppRoute.ActiveRoomStays(
|
||||
currentRoute.propertyId,
|
||||
selectedPropertyName.value ?: "Property"
|
||||
)
|
||||
is AppRoute.BookingExpectedDates -> route.value = AppRoute.ActiveRoomStays(
|
||||
currentRoute.propertyId,
|
||||
selectedPropertyName.value ?: "Property"
|
||||
)
|
||||
is AppRoute.BookingDetailsTabs -> route.value = AppRoute.ActiveRoomStays(
|
||||
currentRoute.propertyId,
|
||||
selectedPropertyName.value ?: "Property"
|
||||
)
|
||||
is AppRoute.BookingPayments -> route.value = AppRoute.BookingDetailsTabs(
|
||||
currentRoute.propertyId,
|
||||
currentRoute.bookingId,
|
||||
null
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
if (currentRoute == AppRoute.Home &&
|
||||
!state.isSuperAdmin &&
|
||||
!singlePropertyIsAdmin &&
|
||||
state.propertyRoles.size == 1 &&
|
||||
singlePropertyId != null
|
||||
) {
|
||||
LaunchedEffect(singlePropertyId) {
|
||||
selectedPropertyId.value = singlePropertyId
|
||||
route.value = AppRoute.ActiveRoomStays(
|
||||
singlePropertyId,
|
||||
selectedPropertyName.value ?: "Property"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
when (currentRoute) {
|
||||
AppRoute.Home -> HomeScreen(
|
||||
userId = state.userId,
|
||||
userName = state.userName,
|
||||
isSuperAdmin = state.isSuperAdmin,
|
||||
onUserDirectory = { route.value = AppRoute.SuperAdminUsers },
|
||||
onLogout = authViewModel::signOut,
|
||||
onAddProperty = { route.value = AppRoute.AddProperty },
|
||||
onAmenities = {
|
||||
amenitiesReturnRoute.value = AppRoute.Home
|
||||
route.value = AppRoute.Amenities
|
||||
},
|
||||
onImageTags = { route.value = AppRoute.ImageTags },
|
||||
refreshKey = refreshKey.value,
|
||||
selectedPropertyId = selectedPropertyId.value,
|
||||
onSelectProperty = { id, name ->
|
||||
selectedPropertyId.value = id
|
||||
selectedPropertyName.value = name
|
||||
route.value = AppRoute.ActiveRoomStays(id, name)
|
||||
},
|
||||
onRefreshProfile = authViewModel::refreshMe,
|
||||
showJoinProperty = state.propertyRoles.isEmpty() && !state.isSuperAdmin,
|
||||
onJoinPropertySuccess = { joinedPropertyId, joinedRoles ->
|
||||
refreshKey.value++
|
||||
authViewModel.refreshMe()
|
||||
val isAdmin = joinedRoles.contains("ADMIN")
|
||||
val shouldAutoOpen = !state.isSuperAdmin && !isAdmin && state.propertyRoles.size <= 1
|
||||
if (shouldAutoOpen) {
|
||||
route.value = AppRoute.ActiveRoomStays(
|
||||
joinedPropertyId,
|
||||
selectedPropertyName.value ?: "Property"
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
AppRoute.AddProperty -> AddPropertyScreen(
|
||||
onBack = { route.value = AppRoute.Home },
|
||||
onCreated = {
|
||||
refreshKey.value++
|
||||
route.value = AppRoute.Home
|
||||
}
|
||||
)
|
||||
is AppRoute.CreateBooking -> BookingCreateScreen(
|
||||
propertyId = currentRoute.propertyId,
|
||||
onBack = {
|
||||
route.value = AppRoute.ActiveRoomStays(
|
||||
currentRoute.propertyId,
|
||||
selectedPropertyName.value ?: "Property"
|
||||
)
|
||||
},
|
||||
onCreated = { response, guest, phone ->
|
||||
val bookingId = response.id.orEmpty()
|
||||
val guestId = (guest?.id ?: response.guestId).orEmpty()
|
||||
selectedGuest.value = guest
|
||||
selectedGuestPhone.value = phone
|
||||
if (bookingId.isNotBlank() && guestId.isNotBlank()) {
|
||||
val fromAt = response.checkInAt?.takeIf { it.isNotBlank() }
|
||||
?: response.expectedCheckInAt.orEmpty()
|
||||
val toAt = response.expectedCheckOutAt?.takeIf { it.isNotBlank() }
|
||||
route.value = AppRoute.ManageRoomStaySelectFromBooking(
|
||||
propertyId = currentRoute.propertyId,
|
||||
bookingId = bookingId,
|
||||
guestId = guestId,
|
||||
fromAt = fromAt,
|
||||
toAt = toAt
|
||||
)
|
||||
} else {
|
||||
route.value = AppRoute.Home
|
||||
}
|
||||
}
|
||||
)
|
||||
is AppRoute.GuestInfo -> GuestInfoScreen(
|
||||
propertyId = currentRoute.propertyId,
|
||||
guestId = currentRoute.guestId,
|
||||
initialGuest = selectedGuest.value,
|
||||
initialPhone = selectedGuestPhone.value,
|
||||
onBack = { route.value = AppRoute.Home },
|
||||
onSave = {
|
||||
route.value = AppRoute.GuestSignature(
|
||||
currentRoute.propertyId,
|
||||
currentRoute.bookingId,
|
||||
currentRoute.guestId
|
||||
)
|
||||
}
|
||||
)
|
||||
is AppRoute.GuestSignature -> GuestSignatureScreen(
|
||||
propertyId = currentRoute.propertyId,
|
||||
guestId = currentRoute.guestId,
|
||||
onBack = {
|
||||
route.value = AppRoute.GuestInfo(
|
||||
currentRoute.propertyId,
|
||||
currentRoute.bookingId,
|
||||
currentRoute.guestId
|
||||
)
|
||||
},
|
||||
onDone = {
|
||||
route.value = AppRoute.ActiveRoomStays(
|
||||
currentRoute.propertyId,
|
||||
selectedPropertyName.value ?: "Property"
|
||||
)
|
||||
}
|
||||
)
|
||||
is AppRoute.ActiveRoomStays -> ActiveRoomStaysScreen(
|
||||
propertyId = currentRoute.propertyId,
|
||||
propertyName = currentRoute.propertyName,
|
||||
onBack = {
|
||||
val isAdmin = state.propertyRoles[currentRoute.propertyId]?.contains("ADMIN") == true
|
||||
val blockBack = !state.isSuperAdmin && !isAdmin && state.propertyRoles.size == 1
|
||||
if (!blockBack) {
|
||||
route.value = AppRoute.Home
|
||||
}
|
||||
},
|
||||
showBack = run {
|
||||
val isAdmin = state.propertyRoles[currentRoute.propertyId]?.contains("ADMIN") == true
|
||||
val blockBack = !state.isSuperAdmin && !isAdmin && state.propertyRoles.size == 1
|
||||
!blockBack
|
||||
},
|
||||
onViewRooms = { route.value = AppRoute.Rooms(currentRoute.propertyId) },
|
||||
onCreateBooking = { route.value = AppRoute.CreateBooking(currentRoute.propertyId) },
|
||||
canCreateBooking = canCreateBookingFor(currentRoute.propertyId),
|
||||
showRazorpaySettings = canManageRazorpaySettings(currentRoute.propertyId),
|
||||
onRazorpaySettings = { route.value = AppRoute.RazorpaySettings(currentRoute.propertyId) },
|
||||
showUserAdmin = canManagePropertyUsers(currentRoute.propertyId),
|
||||
onUserAdmin = { route.value = AppRoute.PropertyUsers(currentRoute.propertyId) },
|
||||
onLogout = authViewModel::signOut,
|
||||
onManageRoomStay = { booking ->
|
||||
val fromAt = booking.checkInAt?.takeIf { it.isNotBlank() }
|
||||
?: booking.expectedCheckInAt.orEmpty()
|
||||
val toAt = booking.expectedCheckOutAt?.takeIf { it.isNotBlank() }
|
||||
?: booking.checkOutAt?.takeIf { it.isNotBlank() }
|
||||
if (fromAt.isNotBlank()) {
|
||||
route.value = AppRoute.ManageRoomStaySelect(
|
||||
propertyId = currentRoute.propertyId,
|
||||
bookingId = booking.id.orEmpty(),
|
||||
fromAt = fromAt,
|
||||
toAt = toAt
|
||||
)
|
||||
}
|
||||
},
|
||||
onViewBookingStays = { booking ->
|
||||
route.value = AppRoute.BookingRoomStays(
|
||||
propertyId = currentRoute.propertyId,
|
||||
bookingId = booking.id.orEmpty()
|
||||
)
|
||||
},
|
||||
onOpenBookingDetails = { booking ->
|
||||
route.value = AppRoute.BookingDetailsTabs(
|
||||
propertyId = currentRoute.propertyId,
|
||||
bookingId = booking.id.orEmpty(),
|
||||
guestId = booking.guestId
|
||||
)
|
||||
}
|
||||
)
|
||||
is AppRoute.RazorpaySettings -> RazorpaySettingsScreen(
|
||||
propertyId = currentRoute.propertyId,
|
||||
onBack = {
|
||||
route.value = AppRoute.ActiveRoomStays(
|
||||
currentRoute.propertyId,
|
||||
selectedPropertyName.value ?: "Property"
|
||||
)
|
||||
}
|
||||
)
|
||||
is AppRoute.RazorpayQr -> RazorpayQrScreen(
|
||||
propertyId = currentRoute.propertyId,
|
||||
bookingId = currentRoute.bookingId,
|
||||
pendingAmount = currentRoute.pendingAmount,
|
||||
guestPhone = currentRoute.guestPhone,
|
||||
onBack = {
|
||||
route.value = AppRoute.BookingDetailsTabs(
|
||||
currentRoute.propertyId,
|
||||
currentRoute.bookingId,
|
||||
null
|
||||
)
|
||||
}
|
||||
)
|
||||
is AppRoute.ManageRoomStaySelect -> ManageRoomStaySelectScreen(
|
||||
propertyId = currentRoute.propertyId,
|
||||
bookingFromAt = currentRoute.fromAt,
|
||||
bookingToAt = currentRoute.toAt,
|
||||
onBack = {
|
||||
route.value = AppRoute.ActiveRoomStays(
|
||||
currentRoute.propertyId,
|
||||
selectedPropertyName.value ?: "Property"
|
||||
)
|
||||
},
|
||||
onNext = { rooms ->
|
||||
selectedManageRooms.value = rooms
|
||||
route.value = AppRoute.ManageRoomStayRates(
|
||||
propertyId = currentRoute.propertyId,
|
||||
bookingId = currentRoute.bookingId,
|
||||
fromAt = currentRoute.fromAt,
|
||||
toAt = currentRoute.toAt
|
||||
)
|
||||
}
|
||||
)
|
||||
is AppRoute.ManageRoomStaySelectFromBooking -> ManageRoomStaySelectScreen(
|
||||
propertyId = currentRoute.propertyId,
|
||||
bookingFromAt = currentRoute.fromAt,
|
||||
bookingToAt = currentRoute.toAt,
|
||||
onBack = {
|
||||
route.value = AppRoute.ActiveRoomStays(
|
||||
currentRoute.propertyId,
|
||||
selectedPropertyName.value ?: "Property"
|
||||
)
|
||||
},
|
||||
onNext = { rooms ->
|
||||
selectedManageRooms.value = rooms
|
||||
route.value = AppRoute.ManageRoomStayRatesFromBooking(
|
||||
propertyId = currentRoute.propertyId,
|
||||
bookingId = currentRoute.bookingId,
|
||||
guestId = currentRoute.guestId,
|
||||
fromAt = currentRoute.fromAt,
|
||||
toAt = currentRoute.toAt
|
||||
)
|
||||
}
|
||||
)
|
||||
is AppRoute.ManageRoomStayRates -> ManageRoomStayRatesScreen(
|
||||
propertyId = currentRoute.propertyId,
|
||||
bookingId = currentRoute.bookingId,
|
||||
checkInAt = currentRoute.fromAt,
|
||||
checkOutAt = currentRoute.toAt,
|
||||
selectedRooms = selectedManageRooms.value,
|
||||
onBack = {
|
||||
route.value = AppRoute.ManageRoomStaySelect(
|
||||
currentRoute.propertyId,
|
||||
currentRoute.bookingId,
|
||||
currentRoute.fromAt,
|
||||
currentRoute.toAt
|
||||
)
|
||||
},
|
||||
onDone = {
|
||||
route.value = AppRoute.ActiveRoomStays(
|
||||
currentRoute.propertyId,
|
||||
selectedPropertyName.value ?: "Property"
|
||||
)
|
||||
}
|
||||
)
|
||||
is AppRoute.ManageRoomStayRatesFromBooking -> ManageRoomStayRatesScreen(
|
||||
propertyId = currentRoute.propertyId,
|
||||
bookingId = currentRoute.bookingId,
|
||||
checkInAt = currentRoute.fromAt,
|
||||
checkOutAt = currentRoute.toAt,
|
||||
selectedRooms = selectedManageRooms.value,
|
||||
onBack = {
|
||||
route.value = AppRoute.ManageRoomStaySelectFromBooking(
|
||||
currentRoute.propertyId,
|
||||
currentRoute.bookingId,
|
||||
currentRoute.guestId,
|
||||
currentRoute.fromAt,
|
||||
currentRoute.toAt
|
||||
)
|
||||
},
|
||||
onDone = {
|
||||
route.value = AppRoute.GuestInfo(
|
||||
currentRoute.propertyId,
|
||||
currentRoute.bookingId,
|
||||
currentRoute.guestId
|
||||
)
|
||||
}
|
||||
)
|
||||
is AppRoute.BookingRoomStays -> BookingRoomStaysScreen(
|
||||
propertyId = currentRoute.propertyId,
|
||||
bookingId = currentRoute.bookingId,
|
||||
onBack = {
|
||||
route.value = AppRoute.ActiveRoomStays(
|
||||
currentRoute.propertyId,
|
||||
selectedPropertyName.value ?: "Property"
|
||||
)
|
||||
}
|
||||
)
|
||||
is AppRoute.BookingExpectedDates -> BookingExpectedDatesScreen(
|
||||
propertyId = currentRoute.propertyId,
|
||||
bookingId = currentRoute.bookingId,
|
||||
status = currentRoute.status,
|
||||
expectedCheckInAt = currentRoute.expectedCheckInAt,
|
||||
expectedCheckOutAt = currentRoute.expectedCheckOutAt,
|
||||
onBack = {
|
||||
route.value = AppRoute.ActiveRoomStays(
|
||||
currentRoute.propertyId,
|
||||
selectedPropertyName.value ?: "Property"
|
||||
)
|
||||
},
|
||||
onDone = {
|
||||
route.value = AppRoute.ActiveRoomStays(
|
||||
currentRoute.propertyId,
|
||||
selectedPropertyName.value ?: "Property"
|
||||
)
|
||||
}
|
||||
)
|
||||
is AppRoute.BookingDetailsTabs -> BookingDetailsTabsScreen(
|
||||
propertyId = currentRoute.propertyId,
|
||||
bookingId = currentRoute.bookingId,
|
||||
guestId = currentRoute.guestId,
|
||||
onBack = {
|
||||
route.value = AppRoute.ActiveRoomStays(
|
||||
currentRoute.propertyId,
|
||||
selectedPropertyName.value ?: "Property"
|
||||
)
|
||||
},
|
||||
onEditCheckout = { expectedCheckInAt, expectedCheckOutAt ->
|
||||
route.value = AppRoute.BookingExpectedDates(
|
||||
propertyId = currentRoute.propertyId,
|
||||
bookingId = currentRoute.bookingId,
|
||||
status = "CHECKED_IN",
|
||||
expectedCheckInAt = expectedCheckInAt,
|
||||
expectedCheckOutAt = expectedCheckOutAt
|
||||
)
|
||||
},
|
||||
onEditSignature = { guestId ->
|
||||
route.value = AppRoute.GuestSignature(
|
||||
currentRoute.propertyId,
|
||||
currentRoute.bookingId,
|
||||
guestId
|
||||
)
|
||||
},
|
||||
onOpenRazorpayQr = { pendingAmount, guestPhone ->
|
||||
route.value = AppRoute.RazorpayQr(
|
||||
propertyId = currentRoute.propertyId,
|
||||
bookingId = currentRoute.bookingId,
|
||||
pendingAmount = pendingAmount,
|
||||
guestPhone = guestPhone
|
||||
)
|
||||
},
|
||||
onOpenPayments = {
|
||||
route.value = AppRoute.BookingPayments(
|
||||
propertyId = currentRoute.propertyId,
|
||||
bookingId = currentRoute.bookingId
|
||||
)
|
||||
},
|
||||
canManageDocuments = canManageRazorpaySettings(currentRoute.propertyId)
|
||||
)
|
||||
is AppRoute.BookingPayments -> BookingPaymentsScreen(
|
||||
propertyId = currentRoute.propertyId,
|
||||
bookingId = currentRoute.bookingId,
|
||||
canAddCash = canManageRazorpaySettings(currentRoute.propertyId),
|
||||
canDeleteCash = canDeleteCashPayment(currentRoute.propertyId),
|
||||
canRefund = canManageRazorpaySettings(currentRoute.propertyId),
|
||||
onBack = {
|
||||
route.value = AppRoute.BookingDetailsTabs(
|
||||
currentRoute.propertyId,
|
||||
currentRoute.bookingId,
|
||||
null
|
||||
)
|
||||
}
|
||||
)
|
||||
AppRoute.SuperAdminUsers -> SuperAdminUserDirectoryScreen(
|
||||
onBack = { route.value = AppRoute.Home }
|
||||
)
|
||||
is AppRoute.PropertyUsers -> PropertyUsersScreen(
|
||||
propertyId = currentRoute.propertyId,
|
||||
allowedRoleAssignments = when {
|
||||
state.isSuperAdmin -> listOf("ADMIN", "MANAGER", "STAFF", "AGENT")
|
||||
state.propertyRoles[currentRoute.propertyId]?.contains("ADMIN") == true ->
|
||||
listOf("ADMIN", "MANAGER", "STAFF", "AGENT")
|
||||
state.propertyRoles[currentRoute.propertyId]?.contains("MANAGER") == true ->
|
||||
listOf("STAFF", "AGENT")
|
||||
else -> emptyList()
|
||||
},
|
||||
canDisableAdmin = state.isSuperAdmin ||
|
||||
state.propertyRoles[currentRoute.propertyId]?.contains("ADMIN") == true,
|
||||
canDisableManager = state.propertyRoles[currentRoute.propertyId]?.contains("MANAGER") == true,
|
||||
onBack = {
|
||||
route.value = AppRoute.ActiveRoomStays(
|
||||
currentRoute.propertyId,
|
||||
selectedPropertyName.value ?: "Property"
|
||||
)
|
||||
},
|
||||
onOpenAccessCode = {
|
||||
route.value = AppRoute.PropertyAccessCode(currentRoute.propertyId)
|
||||
}
|
||||
)
|
||||
is AppRoute.PropertyAccessCode -> PropertyAccessCodeScreen(
|
||||
propertyId = currentRoute.propertyId,
|
||||
allowedRoles = allowedAccessCodeRoles(currentRoute.propertyId),
|
||||
onBack = { route.value = AppRoute.PropertyUsers(currentRoute.propertyId) }
|
||||
)
|
||||
is AppRoute.Rooms -> RoomsScreen(
|
||||
propertyId = currentRoute.propertyId,
|
||||
onBack = {
|
||||
route.value = AppRoute.ActiveRoomStays(
|
||||
currentRoute.propertyId,
|
||||
selectedPropertyName.value ?: "Property"
|
||||
)
|
||||
},
|
||||
onAddRoom = {
|
||||
roomFormKey.value++
|
||||
route.value = AppRoute.AddRoom(currentRoute.propertyId)
|
||||
},
|
||||
onViewRoomTypes = { route.value = AppRoute.RoomTypes(currentRoute.propertyId) },
|
||||
onViewCardInfo = { route.value = AppRoute.CardInfo(currentRoute.propertyId) },
|
||||
canManageRooms = canManageProperty(currentRoute.propertyId),
|
||||
canViewCardInfo = canViewCardInfo(currentRoute.propertyId),
|
||||
canIssueTemporaryCard = canIssueTemporaryCard(currentRoute.propertyId),
|
||||
onEditRoom = {
|
||||
selectedRoom.value = it
|
||||
roomFormKey.value++
|
||||
route.value = AppRoute.EditRoom(currentRoute.propertyId, it.id ?: "")
|
||||
},
|
||||
onIssueTemporaryCard = {
|
||||
if (it.id != null && canIssueTemporaryCard(currentRoute.propertyId)) {
|
||||
selectedRoom.value = it
|
||||
route.value = AppRoute.IssueTemporaryCard(currentRoute.propertyId, it.id)
|
||||
}
|
||||
}
|
||||
)
|
||||
is AppRoute.RoomTypes -> RoomTypesScreen(
|
||||
propertyId = currentRoute.propertyId,
|
||||
onBack = { route.value = AppRoute.Rooms(currentRoute.propertyId) },
|
||||
onAdd = { route.value = AppRoute.AddRoomType(currentRoute.propertyId) },
|
||||
canManageRoomTypes = canManageProperty(currentRoute.propertyId),
|
||||
onEdit = {
|
||||
selectedRoomType.value = it
|
||||
route.value = AppRoute.EditRoomType(currentRoute.propertyId, it.id ?: "")
|
||||
}
|
||||
)
|
||||
is AppRoute.AddRoomType -> AddRoomTypeScreen(
|
||||
propertyId = currentRoute.propertyId,
|
||||
onBack = { route.value = AppRoute.RoomTypes(currentRoute.propertyId) },
|
||||
onSave = { route.value = AppRoute.RoomTypes(currentRoute.propertyId) }
|
||||
)
|
||||
is AppRoute.EditRoomType -> EditRoomTypeScreen(
|
||||
propertyId = currentRoute.propertyId,
|
||||
roomType = selectedRoomType.value
|
||||
?: com.android.trisolarispms.data.api.model.RoomTypeDto(id = currentRoute.roomTypeId, code = "", name = ""),
|
||||
onBack = { route.value = AppRoute.RoomTypes(currentRoute.propertyId) },
|
||||
onSave = { route.value = AppRoute.RoomTypes(currentRoute.propertyId) },
|
||||
onOpenRatePlanCalendar = { ratePlanId, ratePlanCode ->
|
||||
route.value = AppRoute.RatePlanCalendar(
|
||||
currentRoute.propertyId,
|
||||
currentRoute.roomTypeId,
|
||||
ratePlanId,
|
||||
ratePlanCode
|
||||
)
|
||||
}
|
||||
)
|
||||
AppRoute.Amenities -> AmenitiesScreen(
|
||||
onBack = { route.value = amenitiesReturnRoute.value },
|
||||
onAdd = { route.value = AppRoute.AddAmenity },
|
||||
canManageAmenities = state.isSuperAdmin,
|
||||
onEdit = {
|
||||
selectedAmenity.value = it
|
||||
route.value = AppRoute.EditAmenity(it.id ?: "")
|
||||
}
|
||||
)
|
||||
AppRoute.AddAmenity -> AddAmenityScreen(
|
||||
onBack = { route.value = AppRoute.Amenities },
|
||||
onSave = { route.value = AppRoute.Amenities }
|
||||
)
|
||||
is AppRoute.EditAmenity -> EditAmenityScreen(
|
||||
amenity = selectedAmenity.value
|
||||
?: com.android.trisolarispms.data.api.model.AmenityDto(id = currentRoute.amenityId, name = ""),
|
||||
onBack = { route.value = AppRoute.Amenities },
|
||||
onSave = { route.value = AppRoute.Amenities }
|
||||
)
|
||||
AppRoute.ImageTags -> ImageTagsScreen(
|
||||
onBack = { route.value = AppRoute.Home },
|
||||
onAdd = { route.value = AppRoute.AddImageTag },
|
||||
onEdit = {
|
||||
selectedImageTag.value = it
|
||||
route.value = AppRoute.EditImageTag(it.id ?: "")
|
||||
}
|
||||
)
|
||||
AppRoute.AddImageTag -> AddImageTagScreen(
|
||||
onBack = { route.value = AppRoute.ImageTags },
|
||||
onSave = { route.value = AppRoute.ImageTags }
|
||||
)
|
||||
is AppRoute.EditImageTag -> EditImageTagScreen(
|
||||
tag = selectedImageTag.value
|
||||
?: com.android.trisolarispms.data.api.model.RoomImageTagDto(id = currentRoute.tagId, name = ""),
|
||||
onBack = { route.value = AppRoute.ImageTags },
|
||||
onSave = { route.value = AppRoute.ImageTags }
|
||||
)
|
||||
is AppRoute.AddRoom -> RoomFormScreen(
|
||||
title = "Add Room",
|
||||
propertyId = currentRoute.propertyId,
|
||||
roomId = null,
|
||||
roomData = null,
|
||||
formKey = roomFormKey.value,
|
||||
onBack = { route.value = AppRoute.Rooms(currentRoute.propertyId) },
|
||||
onSave = { route.value = AppRoute.Rooms(currentRoute.propertyId) },
|
||||
onViewImages = { }
|
||||
)
|
||||
is AppRoute.EditRoom -> RoomFormScreen(
|
||||
title = "Modify Room",
|
||||
propertyId = currentRoute.propertyId,
|
||||
roomId = currentRoute.roomId,
|
||||
roomData = selectedRoom.value,
|
||||
formKey = roomFormKey.value,
|
||||
onBack = { route.value = AppRoute.Rooms(currentRoute.propertyId) },
|
||||
onSave = { route.value = AppRoute.Rooms(currentRoute.propertyId) },
|
||||
onViewImages = { roomId ->
|
||||
route.value = AppRoute.RoomImages(currentRoute.propertyId, roomId)
|
||||
}
|
||||
)
|
||||
is AppRoute.IssueTemporaryCard -> IssueTemporaryCardScreen(
|
||||
propertyId = currentRoute.propertyId,
|
||||
roomId = currentRoute.roomId,
|
||||
roomNumber = selectedRoom.value?.roomNumber?.toString(),
|
||||
onBack = { route.value = AppRoute.Rooms(currentRoute.propertyId) }
|
||||
)
|
||||
is AppRoute.CardInfo -> CardInfoScreen(
|
||||
propertyId = currentRoute.propertyId,
|
||||
onBack = { route.value = AppRoute.Rooms(currentRoute.propertyId) }
|
||||
)
|
||||
is AppRoute.RatePlanCalendar -> RatePlanCalendarScreen(
|
||||
propertyId = currentRoute.propertyId,
|
||||
ratePlanId = currentRoute.ratePlanId,
|
||||
ratePlanCode = currentRoute.ratePlanCode,
|
||||
onBack = { route.value = AppRoute.EditRoomType(currentRoute.propertyId, currentRoute.roomTypeId) }
|
||||
)
|
||||
is AppRoute.RoomImages -> RoomImagesScreen(
|
||||
propertyId = currentRoute.propertyId,
|
||||
roomId = currentRoute.roomId,
|
||||
onBack = { route.value = AppRoute.EditRoom(currentRoute.propertyId, currentRoute.roomId) }
|
||||
)
|
||||
}
|
||||
} else {
|
||||
AuthScreen(viewModel = authViewModel)
|
||||
state.apiVerified && state.needsName -> NameScreen(viewModel = authViewModel)
|
||||
state.apiVerified -> MainRouteContent(
|
||||
state = state,
|
||||
authViewModel = authViewModel
|
||||
)
|
||||
else -> AuthScreen(viewModel = authViewModel)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
package com.android.trisolarispms.core.auth
|
||||
|
||||
class AuthzPolicy(
|
||||
private val isSuperAdmin: Boolean,
|
||||
propertyRoles: Map<String, List<Role>>
|
||||
) {
|
||||
private val rolesByProperty: Map<String, Set<Role>> = propertyRoles.mapValues { it.value.toSet() }
|
||||
|
||||
fun isPropertyAdmin(propertyId: String): Boolean = hasRole(propertyId, Role.ADMIN)
|
||||
|
||||
fun canManageProperty(propertyId: String): Boolean = hasRole(propertyId, Role.ADMIN)
|
||||
|
||||
fun canIssueTemporaryCard(propertyId: String): Boolean =
|
||||
hasAnyRole(propertyId, Role.ADMIN, Role.MANAGER)
|
||||
|
||||
fun canViewCardInfo(propertyId: String): Boolean =
|
||||
hasAnyRole(propertyId, Role.ADMIN, Role.MANAGER, Role.STAFF)
|
||||
|
||||
fun canManageRazorpaySettings(propertyId: String): Boolean = hasRole(propertyId, Role.ADMIN)
|
||||
|
||||
fun canDeleteCashPayment(propertyId: String): Boolean = hasRole(propertyId, Role.ADMIN)
|
||||
|
||||
fun canAddBookingPayment(propertyId: String): Boolean =
|
||||
hasAnyRole(propertyId, Role.ADMIN, Role.MANAGER, Role.STAFF)
|
||||
|
||||
fun canRefundBookingPayment(propertyId: String): Boolean =
|
||||
hasAnyRole(propertyId, Role.ADMIN, Role.MANAGER)
|
||||
|
||||
fun canManageGuestDocuments(propertyId: String): Boolean =
|
||||
hasAnyRole(propertyId, Role.ADMIN, Role.MANAGER)
|
||||
|
||||
fun canManagePropertyUsers(propertyId: String): Boolean = hasRole(propertyId, Role.ADMIN)
|
||||
|
||||
fun canCreateBookingFor(propertyId: String): Boolean =
|
||||
hasAnyRole(propertyId, Role.ADMIN, Role.MANAGER, Role.STAFF)
|
||||
|
||||
fun canDisableAdmin(propertyId: String): Boolean = hasRole(propertyId, Role.ADMIN)
|
||||
|
||||
fun canDisableManager(propertyId: String): Boolean = hasRole(propertyId, Role.MANAGER)
|
||||
|
||||
fun allowedAccessCodeRoles(propertyId: String): List<Role> = when {
|
||||
isSuperAdmin || hasRole(propertyId, Role.ADMIN) -> listOf(Role.MANAGER, Role.STAFF, Role.AGENT)
|
||||
hasRole(propertyId, Role.MANAGER) -> listOf(Role.STAFF, Role.AGENT)
|
||||
else -> emptyList()
|
||||
}
|
||||
|
||||
fun allowedRoleAssignments(propertyId: String): List<Role> = when {
|
||||
isSuperAdmin || hasRole(propertyId, Role.ADMIN) ->
|
||||
listOf(Role.ADMIN, Role.MANAGER, Role.STAFF, Role.AGENT)
|
||||
hasRole(propertyId, Role.MANAGER) -> listOf(Role.STAFF, Role.AGENT)
|
||||
else -> emptyList()
|
||||
}
|
||||
|
||||
fun shouldBlockBackToHome(propertyId: String, propertyCount: Int): Boolean =
|
||||
!isSuperAdmin && !isPropertyAdmin(propertyId) && propertyCount == 1
|
||||
|
||||
private fun hasRole(propertyId: String, requiredRole: Role): Boolean =
|
||||
isSuperAdmin || (rolesByProperty[propertyId]?.contains(requiredRole) == true)
|
||||
|
||||
private fun hasAnyRole(propertyId: String, vararg requiredRoles: Role): Boolean =
|
||||
isSuperAdmin || (rolesByProperty[propertyId]?.any { role -> role in requiredRoles } == true)
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package com.android.trisolarispms.core.auth
|
||||
|
||||
import java.util.Locale
|
||||
|
||||
enum class Role {
|
||||
ADMIN,
|
||||
MANAGER,
|
||||
STAFF,
|
||||
HOUSEKEEPING,
|
||||
FINANCE,
|
||||
GUIDE,
|
||||
SUPERVISOR,
|
||||
AGENT;
|
||||
|
||||
companion object {
|
||||
fun from(value: String?): Role? {
|
||||
val normalized = value?.trim()?.uppercase(Locale.US) ?: return null
|
||||
return entries.firstOrNull { it.name == normalized }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun Collection<String>?.toRoles(): List<Role> = this.orEmpty().mapNotNull(Role::from)
|
||||
|
||||
fun Collection<String>?.toRoleSet(): Set<Role> = toRoles().toSet()
|
||||
|
||||
fun Collection<Role>.toRoleNameList(): List<String> = map { it.name }
|
||||
@@ -1,20 +0,0 @@
|
||||
package com.android.trisolarispms.data.api
|
||||
|
||||
interface ApiService :
|
||||
AuthApi,
|
||||
PropertyApi,
|
||||
RoomTypeApi,
|
||||
RoomApi,
|
||||
RoomImageApi,
|
||||
ImageTagApi,
|
||||
BookingApi,
|
||||
RoomStayApi,
|
||||
CardApi,
|
||||
GuestApi,
|
||||
GuestDocumentApi,
|
||||
TransportApi,
|
||||
InboundEmailApi,
|
||||
AmenityApi,
|
||||
RatePlanApi,
|
||||
RazorpaySettingsApi,
|
||||
UserAdminApi
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.android.trisolarispms.data.api
|
||||
package com.android.trisolarispms.data.api.core
|
||||
|
||||
import com.google.firebase.auth.FirebaseAuth
|
||||
import okhttp3.Authenticator
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.android.trisolarispms.data.api
|
||||
package com.android.trisolarispms.data.api.core
|
||||
|
||||
object ApiConstants {
|
||||
const val BASE_URL = "https://api.hoteltrisolaris.in/"
|
||||
@@ -0,0 +1,38 @@
|
||||
package com.android.trisolarispms.data.api.core
|
||||
|
||||
import com.android.trisolarispms.data.api.service.AmenityApi
|
||||
import com.android.trisolarispms.data.api.service.AuthApi
|
||||
import com.android.trisolarispms.data.api.service.BookingApi
|
||||
import com.android.trisolarispms.data.api.service.CardApi
|
||||
import com.android.trisolarispms.data.api.service.GuestApi
|
||||
import com.android.trisolarispms.data.api.service.GuestDocumentApi
|
||||
import com.android.trisolarispms.data.api.service.ImageTagApi
|
||||
import com.android.trisolarispms.data.api.service.InboundEmailApi
|
||||
import com.android.trisolarispms.data.api.service.PropertyApi
|
||||
import com.android.trisolarispms.data.api.service.RatePlanApi
|
||||
import com.android.trisolarispms.data.api.service.RazorpaySettingsApi
|
||||
import com.android.trisolarispms.data.api.service.RoomApi
|
||||
import com.android.trisolarispms.data.api.service.RoomImageApi
|
||||
import com.android.trisolarispms.data.api.service.RoomStayApi
|
||||
import com.android.trisolarispms.data.api.service.RoomTypeApi
|
||||
import com.android.trisolarispms.data.api.service.TransportApi
|
||||
import com.android.trisolarispms.data.api.service.UserAdminApi
|
||||
|
||||
interface ApiService :
|
||||
AuthApi,
|
||||
PropertyApi,
|
||||
RoomTypeApi,
|
||||
RoomApi,
|
||||
RoomImageApi,
|
||||
ImageTagApi,
|
||||
BookingApi,
|
||||
RoomStayApi,
|
||||
CardApi,
|
||||
GuestApi,
|
||||
GuestDocumentApi,
|
||||
TransportApi,
|
||||
InboundEmailApi,
|
||||
AmenityApi,
|
||||
RatePlanApi,
|
||||
RazorpaySettingsApi,
|
||||
UserAdminApi
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.android.trisolarispms.data.api
|
||||
package com.android.trisolarispms.data.api.core
|
||||
|
||||
interface AuthTokenProvider {
|
||||
suspend fun token(forceRefresh: Boolean = false): String?
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.android.trisolarispms.data.api
|
||||
package com.android.trisolarispms.data.api.core
|
||||
|
||||
import com.google.firebase.auth.FirebaseAuth
|
||||
import kotlinx.coroutines.tasks.await
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.android.trisolarispms.data.api
|
||||
package com.android.trisolarispms.data.api.service
|
||||
|
||||
import com.android.trisolarispms.data.api.model.AmenityCreateRequest
|
||||
import com.android.trisolarispms.data.api.model.AmenityDto
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.android.trisolarispms.data.api
|
||||
package com.android.trisolarispms.data.api.service
|
||||
|
||||
import com.android.trisolarispms.data.api.model.AuthVerifyResponse
|
||||
import com.android.trisolarispms.data.api.model.AuthMeUpdateRequest
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.android.trisolarispms.data.api
|
||||
package com.android.trisolarispms.data.api.service
|
||||
|
||||
import com.android.trisolarispms.data.api.model.ActionResponse
|
||||
import com.android.trisolarispms.data.api.model.BookingCancelRequest
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.android.trisolarispms.data.api
|
||||
package com.android.trisolarispms.data.api.service
|
||||
|
||||
import com.android.trisolarispms.data.api.model.CardPrepareRequest
|
||||
import com.android.trisolarispms.data.api.model.CardPrepareResponse
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.android.trisolarispms.data.api
|
||||
package com.android.trisolarispms.data.api.service
|
||||
|
||||
import com.android.trisolarispms.data.api.model.GuestDto
|
||||
import com.android.trisolarispms.data.api.model.GuestCreateRequest
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.android.trisolarispms.data.api
|
||||
package com.android.trisolarispms.data.api.service
|
||||
|
||||
import com.android.trisolarispms.data.api.model.GuestDocumentDto
|
||||
import okhttp3.MultipartBody
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.android.trisolarispms.data.api
|
||||
package com.android.trisolarispms.data.api.service
|
||||
|
||||
import com.android.trisolarispms.data.api.model.RoomImageTagDto
|
||||
import retrofit2.Response
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.android.trisolarispms.data.api
|
||||
package com.android.trisolarispms.data.api.service
|
||||
|
||||
import com.android.trisolarispms.data.api.model.ActionResponse
|
||||
import okhttp3.MultipartBody
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.android.trisolarispms.data.api
|
||||
package com.android.trisolarispms.data.api.service
|
||||
|
||||
import com.android.trisolarispms.data.api.model.ActionResponse
|
||||
import com.android.trisolarispms.data.api.model.PropertyCreateRequest
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.android.trisolarispms.data.api
|
||||
package com.android.trisolarispms.data.api.service
|
||||
|
||||
import com.android.trisolarispms.data.api.model.RatePlanCalendarEntry
|
||||
import com.android.trisolarispms.data.api.model.RatePlanCalendarUpsertRequest
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.android.trisolarispms.data.api
|
||||
package com.android.trisolarispms.data.api.service
|
||||
|
||||
import com.android.trisolarispms.data.api.model.RazorpaySettingsRequest
|
||||
import com.android.trisolarispms.data.api.model.RazorpaySettingsResponse
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.android.trisolarispms.data.api
|
||||
package com.android.trisolarispms.data.api.service
|
||||
|
||||
import com.android.trisolarispms.data.api.model.RoomAvailabilityRangeResponse
|
||||
import com.android.trisolarispms.data.api.model.RoomAvailabilityResponse
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.android.trisolarispms.data.api
|
||||
package com.android.trisolarispms.data.api.service
|
||||
|
||||
import com.android.trisolarispms.data.api.model.ImageDto
|
||||
import okhttp3.MultipartBody
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.android.trisolarispms.data.api
|
||||
package com.android.trisolarispms.data.api.service
|
||||
|
||||
import com.android.trisolarispms.data.api.model.RoomChangeRequest
|
||||
import com.android.trisolarispms.data.api.model.RoomChangeResponse
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.android.trisolarispms.data.api
|
||||
package com.android.trisolarispms.data.api.service
|
||||
|
||||
import com.android.trisolarispms.data.api.model.ActionResponse
|
||||
import com.android.trisolarispms.data.api.model.RoomTypeCreateRequest
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.android.trisolarispms.data.api
|
||||
package com.android.trisolarispms.data.api.service
|
||||
|
||||
import com.android.trisolarispms.data.api.model.TransportModeDto
|
||||
import retrofit2.Response
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.android.trisolarispms.data.api
|
||||
package com.android.trisolarispms.data.api.service
|
||||
|
||||
import com.android.trisolarispms.data.api.model.AppUserSummaryResponse
|
||||
import com.android.trisolarispms.data.api.model.PropertyAccessCodeCreateRequest
|
||||
@@ -1,5 +1,7 @@
|
||||
package com.android.trisolarispms.ui.auth
|
||||
|
||||
import com.android.trisolarispms.core.auth.Role
|
||||
|
||||
data class AuthUiState(
|
||||
val countryCode: String = "+91",
|
||||
val phoneCountryCode: String = "IN",
|
||||
@@ -19,5 +21,5 @@ data class AuthUiState(
|
||||
val nameInput: String = "",
|
||||
val needsName: Boolean = false,
|
||||
val unauthorized: Boolean = false,
|
||||
val propertyRoles: Map<String, List<String>> = emptyMap()
|
||||
val propertyRoles: Map<String, List<Role>> = emptyMap()
|
||||
)
|
||||
|
||||
@@ -3,7 +3,9 @@ package com.android.trisolarispms.ui.auth
|
||||
import androidx.activity.ComponentActivity
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.android.trisolarispms.data.api.ApiClient
|
||||
import com.android.trisolarispms.core.auth.Role
|
||||
import com.android.trisolarispms.core.auth.toRoles
|
||||
import com.android.trisolarispms.data.api.core.ApiClient
|
||||
import com.google.firebase.FirebaseException
|
||||
import com.google.firebase.auth.FirebaseAuth
|
||||
import com.google.firebase.auth.PhoneAuthCredential
|
||||
@@ -208,50 +210,25 @@ class AuthViewModel(
|
||||
response: retrofit2.Response<com.android.trisolarispms.data.api.model.AuthVerifyResponse>
|
||||
) {
|
||||
val body = response.body()
|
||||
val status = body?.status
|
||||
val userName = body?.user?.name
|
||||
val isSuperAdmin = body?.user?.superAdmin == true
|
||||
val propertyRoles = body?.properties
|
||||
.orEmpty()
|
||||
.mapNotNull { entry ->
|
||||
val id = entry.propertyId
|
||||
id?.let { it to entry.roles.orEmpty() }
|
||||
id?.let { it to entry.roles.toRoles() }
|
||||
}
|
||||
.toMap()
|
||||
when {
|
||||
response.isSuccessful && (body?.status == "OK" || body?.status == "SUPER_ADMIN") -> {
|
||||
_state.update {
|
||||
it.copy(
|
||||
isLoading = false,
|
||||
userId = userId,
|
||||
apiVerified = true,
|
||||
needsName = userName.isNullOrBlank(),
|
||||
nameInput = userName ?: "",
|
||||
userName = userName,
|
||||
isSuperAdmin = isSuperAdmin,
|
||||
noProperties = body?.status == "NO_PROPERTIES",
|
||||
unauthorized = false,
|
||||
propertyRoles = propertyRoles,
|
||||
error = null
|
||||
)
|
||||
}
|
||||
}
|
||||
response.isSuccessful && body?.status == "NO_PROPERTIES" -> {
|
||||
_state.update {
|
||||
it.copy(
|
||||
isLoading = false,
|
||||
userId = userId,
|
||||
apiVerified = true,
|
||||
needsName = userName.isNullOrBlank(),
|
||||
nameInput = userName ?: "",
|
||||
userName = userName,
|
||||
isSuperAdmin = isSuperAdmin,
|
||||
noProperties = true,
|
||||
unauthorized = false,
|
||||
propertyRoles = propertyRoles,
|
||||
error = null
|
||||
)
|
||||
}
|
||||
}
|
||||
response.isSuccessful && (status == "OK" || status == "SUPER_ADMIN" || status == "NO_PROPERTIES") ->
|
||||
setVerifiedState(
|
||||
userId = userId,
|
||||
userName = userName,
|
||||
isSuperAdmin = isSuperAdmin,
|
||||
propertyRoles = propertyRoles,
|
||||
noProperties = status == "NO_PROPERTIES"
|
||||
)
|
||||
response.code() == 401 -> {
|
||||
_state.update {
|
||||
it.copy(
|
||||
@@ -281,6 +258,30 @@ class AuthViewModel(
|
||||
}
|
||||
}
|
||||
|
||||
private fun setVerifiedState(
|
||||
userId: String?,
|
||||
userName: String?,
|
||||
isSuperAdmin: Boolean,
|
||||
propertyRoles: Map<String, List<Role>>,
|
||||
noProperties: Boolean
|
||||
) {
|
||||
_state.update {
|
||||
it.copy(
|
||||
isLoading = false,
|
||||
userId = userId,
|
||||
apiVerified = true,
|
||||
needsName = userName.isNullOrBlank(),
|
||||
nameInput = userName ?: "",
|
||||
userName = userName,
|
||||
isSuperAdmin = isSuperAdmin,
|
||||
noProperties = noProperties,
|
||||
unauthorized = false,
|
||||
propertyRoles = propertyRoles,
|
||||
error = null
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun signOut() {
|
||||
auth.signOut()
|
||||
_state.update { AuthUiState() }
|
||||
@@ -335,5 +336,4 @@ class AuthViewModel(
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ package com.android.trisolarispms.ui.booking
|
||||
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.android.trisolarispms.data.api.ApiClient
|
||||
import com.android.trisolarispms.data.api.core.ApiClient
|
||||
import com.android.trisolarispms.data.api.model.BookingCreateRequest
|
||||
import com.android.trisolarispms.data.api.model.BookingCreateResponse
|
||||
import com.android.trisolarispms.data.api.model.GuestDto
|
||||
|
||||
@@ -37,7 +37,7 @@ import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import kotlinx.coroutines.launch
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import com.android.trisolarispms.data.api.ApiClient
|
||||
import com.android.trisolarispms.data.api.core.ApiClient
|
||||
import com.android.trisolarispms.data.api.model.BookingExpectedDatesRequest
|
||||
import com.kizitonwose.calendar.compose.HorizontalCalendar
|
||||
import com.kizitonwose.calendar.compose.rememberCalendarState
|
||||
|
||||
@@ -38,7 +38,7 @@ import com.airbnb.lottie.compose.LottieCompositionSpec
|
||||
import com.airbnb.lottie.compose.LottieConstants
|
||||
import com.airbnb.lottie.compose.animateLottieCompositionAsState
|
||||
import com.airbnb.lottie.compose.rememberLottieComposition
|
||||
import com.android.trisolarispms.data.api.ApiClient
|
||||
import com.android.trisolarispms.data.api.core.ApiClient
|
||||
import java.util.Calendar
|
||||
import java.util.Date
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
@@ -5,7 +5,7 @@ import android.nfc.tech.MifareClassic
|
||||
import android.util.Log
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.android.trisolarispms.data.api.ApiClient
|
||||
import com.android.trisolarispms.data.api.core.ApiClient
|
||||
import com.android.trisolarispms.data.api.model.CardPrepareRequest
|
||||
import com.android.trisolarispms.data.api.model.IssueCardRequest
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
|
||||
@@ -2,7 +2,7 @@ package com.android.trisolarispms.ui.guest
|
||||
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.android.trisolarispms.data.api.ApiClient
|
||||
import com.android.trisolarispms.data.api.core.ApiClient
|
||||
import com.android.trisolarispms.data.api.model.GuestDto
|
||||
import com.android.trisolarispms.data.api.model.GuestUpdateRequest
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
|
||||
@@ -2,7 +2,7 @@ package com.android.trisolarispms.ui.guest
|
||||
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.android.trisolarispms.data.api.ApiClient
|
||||
import com.android.trisolarispms.data.api.core.ApiClient
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.update
|
||||
|
||||
@@ -48,8 +48,8 @@ import coil.ImageLoader
|
||||
import coil.compose.SubcomposeAsyncImage
|
||||
import coil.decode.SvgDecoder
|
||||
import coil.request.ImageRequest
|
||||
import com.android.trisolarispms.data.api.ApiConstants
|
||||
import com.android.trisolarispms.data.api.FirebaseAuthTokenProvider
|
||||
import com.android.trisolarispms.data.api.core.ApiConstants
|
||||
import com.android.trisolarispms.data.api.core.FirebaseAuthTokenProvider
|
||||
import com.android.trisolarispms.data.api.model.GuestDocumentDto
|
||||
import com.google.firebase.auth.FirebaseAuth
|
||||
import okhttp3.Interceptor
|
||||
@@ -71,6 +71,7 @@ fun GuestDocumentsTab(
|
||||
guestId: String,
|
||||
bookingId: String,
|
||||
canManageDocuments: Boolean,
|
||||
canModifyDocuments: Boolean,
|
||||
viewModel: GuestDocumentsViewModel = viewModel(key = "guestDocs:$propertyId:$guestId")
|
||||
) {
|
||||
val state by viewModel.state.collectAsState()
|
||||
@@ -164,6 +165,13 @@ fun GuestDocumentsTab(
|
||||
Text(text = "You don't have access to view documents.")
|
||||
return@Column
|
||||
}
|
||||
if (!canModifyDocuments) {
|
||||
Text(
|
||||
text = "Read-only: documents can be modified only when booking is OPEN or CHECKED_IN.",
|
||||
style = MaterialTheme.typography.bodySmall
|
||||
)
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
}
|
||||
if (!state.isLoading && state.documents.isEmpty()) {
|
||||
Text(text = "No documents yet")
|
||||
}
|
||||
@@ -190,7 +198,7 @@ fun GuestDocumentsTab(
|
||||
guestId = guestId,
|
||||
doc = doc,
|
||||
imageLoader = imageLoader,
|
||||
canDelete = canManageDocuments,
|
||||
canDelete = canModifyDocuments,
|
||||
onDelete = { documentId ->
|
||||
viewModel.deleteDocument(propertyId, guestId, documentId)
|
||||
}
|
||||
@@ -199,7 +207,7 @@ fun GuestDocumentsTab(
|
||||
}
|
||||
}
|
||||
|
||||
if (canManageDocuments) {
|
||||
if (canModifyDocuments) {
|
||||
FloatingActionButton(
|
||||
onClick = { showPicker.value = true },
|
||||
modifier = Modifier
|
||||
@@ -214,7 +222,7 @@ fun GuestDocumentsTab(
|
||||
}
|
||||
}
|
||||
|
||||
if (showPicker.value) {
|
||||
if (showPicker.value && canModifyDocuments) {
|
||||
AlertDialog(
|
||||
onDismissRequest = { showPicker.value = false },
|
||||
title = { Text("Add document") },
|
||||
|
||||
@@ -5,8 +5,8 @@ import android.content.Context
|
||||
import android.net.Uri
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.android.trisolarispms.data.api.ApiClient
|
||||
import com.android.trisolarispms.data.api.ApiConstants
|
||||
import com.android.trisolarispms.data.api.core.ApiClient
|
||||
import com.android.trisolarispms.data.api.core.ApiConstants
|
||||
import com.android.trisolarispms.data.api.model.GuestDocumentDto
|
||||
import com.google.gson.Gson
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
|
||||
@@ -2,7 +2,9 @@ package com.android.trisolarispms.ui.home
|
||||
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.android.trisolarispms.data.api.ApiClient
|
||||
import com.android.trisolarispms.core.auth.Role
|
||||
import com.android.trisolarispms.core.auth.toRoles
|
||||
import com.android.trisolarispms.data.api.core.ApiClient
|
||||
import com.android.trisolarispms.data.api.model.PropertyAccessCodeJoinRequest
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
@@ -14,7 +16,7 @@ data class HomeJoinPropertyState(
|
||||
val error: String? = null,
|
||||
val message: String? = null,
|
||||
val joinedPropertyId: String? = null,
|
||||
val joinedRoles: List<String> = emptyList()
|
||||
val joinedRoles: List<Role> = emptyList()
|
||||
)
|
||||
|
||||
class HomeJoinPropertyViewModel : ViewModel() {
|
||||
@@ -49,7 +51,7 @@ class HomeJoinPropertyViewModel : ViewModel() {
|
||||
message = "Joined property",
|
||||
error = null,
|
||||
joinedPropertyId = body.propertyId ?: trimmedPropertyId,
|
||||
joinedRoles = body.roles
|
||||
joinedRoles = body.roles.toRoles()
|
||||
)
|
||||
}
|
||||
} else {
|
||||
|
||||
@@ -33,6 +33,7 @@ import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.text.input.KeyboardType
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import com.android.trisolarispms.core.auth.Role
|
||||
import com.android.trisolarispms.ui.property.PropertyListViewModel
|
||||
import androidx.compose.foundation.text.KeyboardOptions
|
||||
|
||||
@@ -52,7 +53,7 @@ fun HomeScreen(
|
||||
onSelectProperty: (String, String) -> Unit,
|
||||
onRefreshProfile: () -> Unit,
|
||||
showJoinProperty: Boolean,
|
||||
onJoinPropertySuccess: (String, List<String>) -> Unit,
|
||||
onJoinPropertySuccess: (String, List<Role>) -> Unit,
|
||||
viewModel: PropertyListViewModel = viewModel(),
|
||||
joinViewModel: HomeJoinPropertyViewModel = viewModel()
|
||||
) {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.android.trisolarispms.ui
|
||||
package com.android.trisolarispms.ui.navigation
|
||||
|
||||
sealed interface AppRoute {
|
||||
data object Home : AppRoute
|
||||
@@ -0,0 +1,82 @@
|
||||
package com.android.trisolarispms.ui.navigation
|
||||
|
||||
import com.android.trisolarispms.core.auth.AuthzPolicy
|
||||
import com.android.trisolarispms.ui.navigation.AppRoute
|
||||
|
||||
internal fun handleBackNavigation(
|
||||
refs: MainUiRefs,
|
||||
authz: AuthzPolicy,
|
||||
propertyCount: Int
|
||||
) {
|
||||
when (val currentRoute = refs.currentRoute) {
|
||||
AppRoute.Home -> Unit
|
||||
AppRoute.AddProperty -> refs.route.value = AppRoute.Home
|
||||
AppRoute.SuperAdminUsers -> refs.route.value = AppRoute.Home
|
||||
is AppRoute.ActiveRoomStays -> {
|
||||
val blockBack = authz.shouldBlockBackToHome(
|
||||
propertyId = currentRoute.propertyId,
|
||||
propertyCount = propertyCount
|
||||
)
|
||||
if (!blockBack) {
|
||||
refs.route.value = AppRoute.Home
|
||||
}
|
||||
}
|
||||
is AppRoute.PropertyUsers -> refs.openActiveRoomStays(currentRoute.propertyId)
|
||||
is AppRoute.PropertyAccessCode -> refs.route.value = AppRoute.PropertyUsers(currentRoute.propertyId)
|
||||
is AppRoute.Rooms -> refs.openActiveRoomStays(currentRoute.propertyId)
|
||||
is AppRoute.AddRoom -> refs.route.value = AppRoute.Rooms(currentRoute.propertyId)
|
||||
is AppRoute.EditRoom -> refs.route.value = AppRoute.Rooms(currentRoute.propertyId)
|
||||
is AppRoute.RoomTypes -> refs.route.value = AppRoute.Rooms(currentRoute.propertyId)
|
||||
is AppRoute.AddRoomType -> refs.route.value = AppRoute.RoomTypes(currentRoute.propertyId)
|
||||
is AppRoute.EditRoomType -> refs.route.value = AppRoute.RoomTypes(currentRoute.propertyId)
|
||||
AppRoute.Amenities -> refs.route.value = refs.amenitiesReturnRoute.value
|
||||
AppRoute.AddAmenity -> refs.route.value = AppRoute.Amenities
|
||||
is AppRoute.EditAmenity -> refs.route.value = AppRoute.Amenities
|
||||
AppRoute.ImageTags -> refs.route.value = AppRoute.Home
|
||||
AppRoute.AddImageTag -> refs.route.value = AppRoute.ImageTags
|
||||
is AppRoute.EditImageTag -> refs.route.value = AppRoute.ImageTags
|
||||
is AppRoute.RoomImages -> refs.route.value = AppRoute.EditRoom(currentRoute.propertyId, currentRoute.roomId)
|
||||
is AppRoute.IssueTemporaryCard -> refs.route.value = AppRoute.Rooms(currentRoute.propertyId)
|
||||
is AppRoute.CardInfo -> refs.route.value = AppRoute.Rooms(currentRoute.propertyId)
|
||||
is AppRoute.RatePlanCalendar -> refs.route.value = AppRoute.EditRoomType(
|
||||
currentRoute.propertyId,
|
||||
currentRoute.roomTypeId
|
||||
)
|
||||
is AppRoute.RazorpaySettings -> refs.openActiveRoomStays(currentRoute.propertyId)
|
||||
is AppRoute.RazorpayQr -> refs.route.value = AppRoute.BookingDetailsTabs(
|
||||
currentRoute.propertyId,
|
||||
currentRoute.bookingId,
|
||||
null
|
||||
)
|
||||
is AppRoute.CreateBooking -> refs.openActiveRoomStays(currentRoute.propertyId)
|
||||
is AppRoute.GuestInfo -> refs.route.value = AppRoute.Home
|
||||
is AppRoute.GuestSignature -> refs.route.value = AppRoute.GuestInfo(
|
||||
currentRoute.propertyId,
|
||||
currentRoute.bookingId,
|
||||
currentRoute.guestId
|
||||
)
|
||||
is AppRoute.ManageRoomStaySelect -> refs.openActiveRoomStays(currentRoute.propertyId)
|
||||
is AppRoute.ManageRoomStayRates -> refs.route.value = AppRoute.ManageRoomStaySelect(
|
||||
currentRoute.propertyId,
|
||||
currentRoute.bookingId,
|
||||
currentRoute.fromAt,
|
||||
currentRoute.toAt
|
||||
)
|
||||
is AppRoute.ManageRoomStaySelectFromBooking -> refs.openActiveRoomStays(currentRoute.propertyId)
|
||||
is AppRoute.ManageRoomStayRatesFromBooking -> refs.route.value = AppRoute.ManageRoomStaySelectFromBooking(
|
||||
currentRoute.propertyId,
|
||||
currentRoute.bookingId,
|
||||
currentRoute.guestId,
|
||||
currentRoute.fromAt,
|
||||
currentRoute.toAt
|
||||
)
|
||||
is AppRoute.BookingRoomStays -> refs.openActiveRoomStays(currentRoute.propertyId)
|
||||
is AppRoute.BookingExpectedDates -> refs.openActiveRoomStays(currentRoute.propertyId)
|
||||
is AppRoute.BookingDetailsTabs -> refs.openActiveRoomStays(currentRoute.propertyId)
|
||||
is AppRoute.BookingPayments -> refs.route.value = AppRoute.BookingDetailsTabs(
|
||||
currentRoute.propertyId,
|
||||
currentRoute.bookingId,
|
||||
null
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
package com.android.trisolarispms.ui.navigation
|
||||
|
||||
import androidx.activity.compose.BackHandler
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import com.android.trisolarispms.core.auth.AuthzPolicy
|
||||
import com.android.trisolarispms.data.api.model.AmenityDto
|
||||
import com.android.trisolarispms.data.api.model.GuestDto
|
||||
import com.android.trisolarispms.data.api.model.RoomDto
|
||||
import com.android.trisolarispms.data.api.model.RoomImageTagDto
|
||||
import com.android.trisolarispms.data.api.model.RoomTypeDto
|
||||
import com.android.trisolarispms.ui.navigation.AppRoute
|
||||
import com.android.trisolarispms.ui.auth.AuthUiState
|
||||
import com.android.trisolarispms.ui.auth.AuthViewModel
|
||||
import com.android.trisolarispms.ui.roomstay.ManageRoomStaySelection
|
||||
|
||||
@Composable
|
||||
internal fun MainRouteContent(
|
||||
state: AuthUiState,
|
||||
authViewModel: AuthViewModel
|
||||
) {
|
||||
val refs = remember {
|
||||
MainUiRefs(
|
||||
route = mutableStateOf<AppRoute>(AppRoute.Home),
|
||||
refreshKey = mutableStateOf(0),
|
||||
selectedPropertyId = mutableStateOf<String?>(null),
|
||||
selectedPropertyName = mutableStateOf<String?>(null),
|
||||
selectedRoom = mutableStateOf<RoomDto?>(null),
|
||||
selectedRoomType = mutableStateOf<RoomTypeDto?>(null),
|
||||
selectedAmenity = mutableStateOf<AmenityDto?>(null),
|
||||
selectedGuest = mutableStateOf<GuestDto?>(null),
|
||||
selectedGuestPhone = mutableStateOf<String?>(null),
|
||||
selectedImageTag = mutableStateOf<RoomImageTagDto?>(null),
|
||||
selectedManageRooms = mutableStateOf<List<ManageRoomStaySelection>>(emptyList()),
|
||||
roomFormKey = mutableStateOf(0),
|
||||
amenitiesReturnRoute = mutableStateOf<AppRoute>(AppRoute.Home)
|
||||
)
|
||||
}
|
||||
|
||||
val authz = remember(state.isSuperAdmin, state.propertyRoles) {
|
||||
AuthzPolicy(
|
||||
isSuperAdmin = state.isSuperAdmin,
|
||||
propertyRoles = state.propertyRoles
|
||||
)
|
||||
}
|
||||
|
||||
val singlePropertyId = state.propertyRoles.keys.firstOrNull()
|
||||
val singlePropertyIsAdmin = singlePropertyId?.let(authz::isPropertyAdmin) ?: false
|
||||
|
||||
BackHandler(enabled = refs.currentRoute != AppRoute.Home) {
|
||||
handleBackNavigation(
|
||||
refs = refs,
|
||||
authz = authz,
|
||||
propertyCount = state.propertyRoles.size
|
||||
)
|
||||
}
|
||||
|
||||
if (
|
||||
refs.currentRoute == AppRoute.Home &&
|
||||
!state.isSuperAdmin &&
|
||||
!singlePropertyIsAdmin &&
|
||||
state.propertyRoles.size == 1 &&
|
||||
singlePropertyId != null
|
||||
) {
|
||||
LaunchedEffect(singlePropertyId) {
|
||||
refs.selectedPropertyId.value = singlePropertyId
|
||||
refs.openActiveRoomStays(singlePropertyId)
|
||||
}
|
||||
}
|
||||
|
||||
when {
|
||||
renderHomeGuestRoutes(
|
||||
refs = refs,
|
||||
state = state,
|
||||
authViewModel = authViewModel
|
||||
) -> Unit
|
||||
|
||||
renderStayFlowRoutes(
|
||||
refs = refs,
|
||||
state = state,
|
||||
authViewModel = authViewModel,
|
||||
authz = authz
|
||||
) -> Unit
|
||||
|
||||
renderBookingRoutes(
|
||||
refs = refs,
|
||||
authz = authz
|
||||
) -> Unit
|
||||
|
||||
renderManagementRoutes(
|
||||
refs = refs,
|
||||
stateIsSuperAdmin = state.isSuperAdmin,
|
||||
authz = authz
|
||||
) -> Unit
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
package com.android.trisolarispms.ui.navigation
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import com.android.trisolarispms.core.auth.AuthzPolicy
|
||||
import com.android.trisolarispms.ui.navigation.AppRoute
|
||||
import com.android.trisolarispms.ui.booking.BookingExpectedDatesScreen
|
||||
import com.android.trisolarispms.ui.payment.BookingPaymentsScreen
|
||||
import com.android.trisolarispms.ui.roomstay.BookingDetailsTabsScreen
|
||||
import com.android.trisolarispms.ui.roomstay.BookingRoomStaysScreen
|
||||
|
||||
@Composable
|
||||
internal fun renderBookingRoutes(
|
||||
refs: MainUiRefs,
|
||||
authz: AuthzPolicy
|
||||
): Boolean {
|
||||
when (val currentRoute = refs.currentRoute) {
|
||||
is AppRoute.BookingRoomStays -> BookingRoomStaysScreen(
|
||||
propertyId = currentRoute.propertyId,
|
||||
bookingId = currentRoute.bookingId,
|
||||
onBack = { refs.openActiveRoomStays(currentRoute.propertyId) }
|
||||
)
|
||||
|
||||
is AppRoute.BookingExpectedDates -> BookingExpectedDatesScreen(
|
||||
propertyId = currentRoute.propertyId,
|
||||
bookingId = currentRoute.bookingId,
|
||||
status = currentRoute.status,
|
||||
expectedCheckInAt = currentRoute.expectedCheckInAt,
|
||||
expectedCheckOutAt = currentRoute.expectedCheckOutAt,
|
||||
onBack = { refs.openActiveRoomStays(currentRoute.propertyId) },
|
||||
onDone = { refs.openActiveRoomStays(currentRoute.propertyId) }
|
||||
)
|
||||
|
||||
is AppRoute.BookingDetailsTabs -> BookingDetailsTabsScreen(
|
||||
propertyId = currentRoute.propertyId,
|
||||
bookingId = currentRoute.bookingId,
|
||||
guestId = currentRoute.guestId,
|
||||
onBack = { refs.openActiveRoomStays(currentRoute.propertyId) },
|
||||
onEditCheckout = { expectedCheckInAt, expectedCheckOutAt ->
|
||||
refs.route.value = AppRoute.BookingExpectedDates(
|
||||
propertyId = currentRoute.propertyId,
|
||||
bookingId = currentRoute.bookingId,
|
||||
status = "CHECKED_IN",
|
||||
expectedCheckInAt = expectedCheckInAt,
|
||||
expectedCheckOutAt = expectedCheckOutAt
|
||||
)
|
||||
},
|
||||
onEditSignature = { guestId ->
|
||||
refs.route.value = AppRoute.GuestSignature(
|
||||
currentRoute.propertyId,
|
||||
currentRoute.bookingId,
|
||||
guestId
|
||||
)
|
||||
},
|
||||
onOpenRazorpayQr = { pendingAmount, guestPhone ->
|
||||
refs.route.value = AppRoute.RazorpayQr(
|
||||
propertyId = currentRoute.propertyId,
|
||||
bookingId = currentRoute.bookingId,
|
||||
pendingAmount = pendingAmount,
|
||||
guestPhone = guestPhone
|
||||
)
|
||||
},
|
||||
onOpenPayments = {
|
||||
refs.route.value = AppRoute.BookingPayments(
|
||||
propertyId = currentRoute.propertyId,
|
||||
bookingId = currentRoute.bookingId
|
||||
)
|
||||
},
|
||||
canManageDocuments = authz.canManageGuestDocuments(currentRoute.propertyId)
|
||||
)
|
||||
|
||||
is AppRoute.BookingPayments -> BookingPaymentsScreen(
|
||||
propertyId = currentRoute.propertyId,
|
||||
bookingId = currentRoute.bookingId,
|
||||
canAddCash = authz.canAddBookingPayment(currentRoute.propertyId),
|
||||
canDeleteCash = authz.canDeleteCashPayment(currentRoute.propertyId),
|
||||
canRefund = authz.canRefundBookingPayment(currentRoute.propertyId),
|
||||
onBack = {
|
||||
refs.route.value = AppRoute.BookingDetailsTabs(
|
||||
currentRoute.propertyId,
|
||||
currentRoute.bookingId,
|
||||
null
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
else -> return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
@@ -0,0 +1,117 @@
|
||||
package com.android.trisolarispms.ui.navigation
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import com.android.trisolarispms.core.auth.Role
|
||||
import com.android.trisolarispms.ui.navigation.AppRoute
|
||||
import com.android.trisolarispms.ui.auth.AuthUiState
|
||||
import com.android.trisolarispms.ui.auth.AuthViewModel
|
||||
import com.android.trisolarispms.ui.booking.BookingCreateScreen
|
||||
import com.android.trisolarispms.ui.guest.GuestInfoScreen
|
||||
import com.android.trisolarispms.ui.guest.GuestSignatureScreen
|
||||
import com.android.trisolarispms.ui.home.HomeScreen
|
||||
import com.android.trisolarispms.ui.property.AddPropertyScreen
|
||||
|
||||
@Composable
|
||||
internal fun renderHomeGuestRoutes(
|
||||
refs: MainUiRefs,
|
||||
state: AuthUiState,
|
||||
authViewModel: AuthViewModel
|
||||
): Boolean {
|
||||
when (val currentRoute = refs.currentRoute) {
|
||||
AppRoute.Home -> HomeScreen(
|
||||
userId = state.userId,
|
||||
userName = state.userName,
|
||||
isSuperAdmin = state.isSuperAdmin,
|
||||
onUserDirectory = { refs.route.value = AppRoute.SuperAdminUsers },
|
||||
onLogout = authViewModel::signOut,
|
||||
onAddProperty = { refs.route.value = AppRoute.AddProperty },
|
||||
onAmenities = {
|
||||
refs.amenitiesReturnRoute.value = AppRoute.Home
|
||||
refs.route.value = AppRoute.Amenities
|
||||
},
|
||||
onImageTags = { refs.route.value = AppRoute.ImageTags },
|
||||
refreshKey = refs.refreshKey.value,
|
||||
selectedPropertyId = refs.selectedPropertyId.value,
|
||||
onSelectProperty = { id, name ->
|
||||
refs.selectedPropertyId.value = id
|
||||
refs.selectedPropertyName.value = name
|
||||
refs.route.value = AppRoute.ActiveRoomStays(id, name)
|
||||
},
|
||||
onRefreshProfile = authViewModel::refreshMe,
|
||||
showJoinProperty = state.propertyRoles.isEmpty() && !state.isSuperAdmin,
|
||||
onJoinPropertySuccess = { joinedPropertyId, joinedRoles ->
|
||||
refs.refreshKey.value++
|
||||
authViewModel.refreshMe()
|
||||
val isAdmin = joinedRoles.contains(Role.ADMIN)
|
||||
val shouldAutoOpen = !state.isSuperAdmin && !isAdmin && state.propertyRoles.size <= 1
|
||||
if (shouldAutoOpen) {
|
||||
refs.openActiveRoomStays(joinedPropertyId)
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
AppRoute.AddProperty -> AddPropertyScreen(
|
||||
onBack = { refs.route.value = AppRoute.Home },
|
||||
onCreated = {
|
||||
refs.refreshKey.value++
|
||||
refs.route.value = AppRoute.Home
|
||||
}
|
||||
)
|
||||
|
||||
is AppRoute.CreateBooking -> BookingCreateScreen(
|
||||
propertyId = currentRoute.propertyId,
|
||||
onBack = { refs.openActiveRoomStays(currentRoute.propertyId) },
|
||||
onCreated = { response, guest, phone ->
|
||||
val bookingId = response.id.orEmpty()
|
||||
val guestId = (guest?.id ?: response.guestId).orEmpty()
|
||||
refs.selectedGuest.value = guest
|
||||
refs.selectedGuestPhone.value = phone
|
||||
if (bookingId.isNotBlank() && guestId.isNotBlank()) {
|
||||
val fromAt = response.checkInAt?.takeIf { it.isNotBlank() }
|
||||
?: response.expectedCheckInAt.orEmpty()
|
||||
val toAt = response.expectedCheckOutAt?.takeIf { it.isNotBlank() }
|
||||
refs.route.value = AppRoute.ManageRoomStaySelectFromBooking(
|
||||
propertyId = currentRoute.propertyId,
|
||||
bookingId = bookingId,
|
||||
guestId = guestId,
|
||||
fromAt = fromAt,
|
||||
toAt = toAt
|
||||
)
|
||||
} else {
|
||||
refs.route.value = AppRoute.Home
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
is AppRoute.GuestInfo -> GuestInfoScreen(
|
||||
propertyId = currentRoute.propertyId,
|
||||
guestId = currentRoute.guestId,
|
||||
initialGuest = refs.selectedGuest.value,
|
||||
initialPhone = refs.selectedGuestPhone.value,
|
||||
onBack = { refs.route.value = AppRoute.Home },
|
||||
onSave = {
|
||||
refs.route.value = AppRoute.GuestSignature(
|
||||
currentRoute.propertyId,
|
||||
currentRoute.bookingId,
|
||||
currentRoute.guestId
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
is AppRoute.GuestSignature -> GuestSignatureScreen(
|
||||
propertyId = currentRoute.propertyId,
|
||||
guestId = currentRoute.guestId,
|
||||
onBack = {
|
||||
refs.route.value = AppRoute.GuestInfo(
|
||||
currentRoute.propertyId,
|
||||
currentRoute.bookingId,
|
||||
currentRoute.guestId
|
||||
)
|
||||
},
|
||||
onDone = { refs.openActiveRoomStays(currentRoute.propertyId) }
|
||||
)
|
||||
|
||||
else -> return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
@@ -0,0 +1,210 @@
|
||||
package com.android.trisolarispms.ui.navigation
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import com.android.trisolarispms.core.auth.AuthzPolicy
|
||||
import com.android.trisolarispms.core.auth.toRoleNameList
|
||||
import com.android.trisolarispms.data.api.model.AmenityDto
|
||||
import com.android.trisolarispms.data.api.model.RoomImageTagDto
|
||||
import com.android.trisolarispms.data.api.model.RoomTypeDto
|
||||
import com.android.trisolarispms.ui.navigation.AppRoute
|
||||
import com.android.trisolarispms.ui.card.CardInfoScreen
|
||||
import com.android.trisolarispms.ui.card.IssueTemporaryCardScreen
|
||||
import com.android.trisolarispms.ui.room.RoomFormScreen
|
||||
import com.android.trisolarispms.ui.room.RoomsScreen
|
||||
import com.android.trisolarispms.ui.roomimage.AddImageTagScreen
|
||||
import com.android.trisolarispms.ui.roomimage.EditImageTagScreen
|
||||
import com.android.trisolarispms.ui.roomimage.ImageTagsScreen
|
||||
import com.android.trisolarispms.ui.roomimage.RoomImagesScreen
|
||||
import com.android.trisolarispms.ui.roomtype.AddAmenityScreen
|
||||
import com.android.trisolarispms.ui.roomtype.AddRoomTypeScreen
|
||||
import com.android.trisolarispms.ui.roomtype.AmenitiesScreen
|
||||
import com.android.trisolarispms.ui.roomtype.EditAmenityScreen
|
||||
import com.android.trisolarispms.ui.roomtype.EditRoomTypeScreen
|
||||
import com.android.trisolarispms.ui.roomtype.RatePlanCalendarScreen
|
||||
import com.android.trisolarispms.ui.roomtype.RoomTypesScreen
|
||||
import com.android.trisolarispms.ui.users.PropertyAccessCodeScreen
|
||||
import com.android.trisolarispms.ui.users.PropertyUsersScreen
|
||||
import com.android.trisolarispms.ui.users.SuperAdminUserDirectoryScreen
|
||||
|
||||
@Composable
|
||||
internal fun renderManagementRoutes(
|
||||
refs: MainUiRefs,
|
||||
stateIsSuperAdmin: Boolean,
|
||||
authz: AuthzPolicy
|
||||
): Boolean {
|
||||
when (val currentRoute = refs.currentRoute) {
|
||||
AppRoute.SuperAdminUsers -> SuperAdminUserDirectoryScreen(
|
||||
onBack = { refs.route.value = AppRoute.Home }
|
||||
)
|
||||
|
||||
is AppRoute.PropertyUsers -> PropertyUsersScreen(
|
||||
propertyId = currentRoute.propertyId,
|
||||
allowedRoleAssignments = authz
|
||||
.allowedRoleAssignments(currentRoute.propertyId)
|
||||
.toRoleNameList(),
|
||||
canDisableAdmin = authz.canDisableAdmin(currentRoute.propertyId),
|
||||
canDisableManager = authz.canDisableManager(currentRoute.propertyId),
|
||||
onBack = { refs.openActiveRoomStays(currentRoute.propertyId) },
|
||||
onOpenAccessCode = {
|
||||
refs.route.value = AppRoute.PropertyAccessCode(currentRoute.propertyId)
|
||||
}
|
||||
)
|
||||
|
||||
is AppRoute.PropertyAccessCode -> PropertyAccessCodeScreen(
|
||||
propertyId = currentRoute.propertyId,
|
||||
allowedRoles = authz.allowedAccessCodeRoles(currentRoute.propertyId).toRoleNameList(),
|
||||
onBack = { refs.route.value = AppRoute.PropertyUsers(currentRoute.propertyId) }
|
||||
)
|
||||
|
||||
is AppRoute.Rooms -> RoomsScreen(
|
||||
propertyId = currentRoute.propertyId,
|
||||
onBack = { refs.openActiveRoomStays(currentRoute.propertyId) },
|
||||
onAddRoom = {
|
||||
refs.roomFormKey.value++
|
||||
refs.route.value = AppRoute.AddRoom(currentRoute.propertyId)
|
||||
},
|
||||
onViewRoomTypes = { refs.route.value = AppRoute.RoomTypes(currentRoute.propertyId) },
|
||||
onViewCardInfo = { refs.route.value = AppRoute.CardInfo(currentRoute.propertyId) },
|
||||
canManageRooms = authz.canManageProperty(currentRoute.propertyId),
|
||||
canViewCardInfo = authz.canViewCardInfo(currentRoute.propertyId),
|
||||
canIssueTemporaryCard = authz.canIssueTemporaryCard(currentRoute.propertyId),
|
||||
onEditRoom = {
|
||||
refs.selectedRoom.value = it
|
||||
refs.roomFormKey.value++
|
||||
refs.route.value = AppRoute.EditRoom(currentRoute.propertyId, it.id ?: "")
|
||||
},
|
||||
onIssueTemporaryCard = {
|
||||
if (it.id != null && authz.canIssueTemporaryCard(currentRoute.propertyId)) {
|
||||
refs.selectedRoom.value = it
|
||||
refs.route.value = AppRoute.IssueTemporaryCard(currentRoute.propertyId, it.id)
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
is AppRoute.RoomTypes -> RoomTypesScreen(
|
||||
propertyId = currentRoute.propertyId,
|
||||
onBack = { refs.route.value = AppRoute.Rooms(currentRoute.propertyId) },
|
||||
onAdd = { refs.route.value = AppRoute.AddRoomType(currentRoute.propertyId) },
|
||||
canManageRoomTypes = authz.canManageProperty(currentRoute.propertyId),
|
||||
onEdit = {
|
||||
refs.selectedRoomType.value = it
|
||||
refs.route.value = AppRoute.EditRoomType(currentRoute.propertyId, it.id ?: "")
|
||||
}
|
||||
)
|
||||
|
||||
is AppRoute.AddRoomType -> AddRoomTypeScreen(
|
||||
propertyId = currentRoute.propertyId,
|
||||
onBack = { refs.route.value = AppRoute.RoomTypes(currentRoute.propertyId) },
|
||||
onSave = { refs.route.value = AppRoute.RoomTypes(currentRoute.propertyId) }
|
||||
)
|
||||
|
||||
is AppRoute.EditRoomType -> EditRoomTypeScreen(
|
||||
propertyId = currentRoute.propertyId,
|
||||
roomType = refs.selectedRoomType.value
|
||||
?: RoomTypeDto(id = currentRoute.roomTypeId, code = "", name = ""),
|
||||
onBack = { refs.route.value = AppRoute.RoomTypes(currentRoute.propertyId) },
|
||||
onSave = { refs.route.value = AppRoute.RoomTypes(currentRoute.propertyId) },
|
||||
onOpenRatePlanCalendar = { ratePlanId, ratePlanCode ->
|
||||
refs.route.value = AppRoute.RatePlanCalendar(
|
||||
currentRoute.propertyId,
|
||||
currentRoute.roomTypeId,
|
||||
ratePlanId,
|
||||
ratePlanCode
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
AppRoute.Amenities -> AmenitiesScreen(
|
||||
onBack = { refs.route.value = refs.amenitiesReturnRoute.value },
|
||||
onAdd = { refs.route.value = AppRoute.AddAmenity },
|
||||
canManageAmenities = stateIsSuperAdmin,
|
||||
onEdit = {
|
||||
refs.selectedAmenity.value = it
|
||||
refs.route.value = AppRoute.EditAmenity(it.id ?: "")
|
||||
}
|
||||
)
|
||||
|
||||
AppRoute.AddAmenity -> AddAmenityScreen(
|
||||
onBack = { refs.route.value = AppRoute.Amenities },
|
||||
onSave = { refs.route.value = AppRoute.Amenities }
|
||||
)
|
||||
|
||||
is AppRoute.EditAmenity -> EditAmenityScreen(
|
||||
amenity = refs.selectedAmenity.value ?: AmenityDto(id = currentRoute.amenityId, name = ""),
|
||||
onBack = { refs.route.value = AppRoute.Amenities },
|
||||
onSave = { refs.route.value = AppRoute.Amenities }
|
||||
)
|
||||
|
||||
AppRoute.ImageTags -> ImageTagsScreen(
|
||||
onBack = { refs.route.value = AppRoute.Home },
|
||||
onAdd = { refs.route.value = AppRoute.AddImageTag },
|
||||
onEdit = {
|
||||
refs.selectedImageTag.value = it
|
||||
refs.route.value = AppRoute.EditImageTag(it.id ?: "")
|
||||
}
|
||||
)
|
||||
|
||||
AppRoute.AddImageTag -> AddImageTagScreen(
|
||||
onBack = { refs.route.value = AppRoute.ImageTags },
|
||||
onSave = { refs.route.value = AppRoute.ImageTags }
|
||||
)
|
||||
|
||||
is AppRoute.EditImageTag -> EditImageTagScreen(
|
||||
tag = refs.selectedImageTag.value ?: RoomImageTagDto(id = currentRoute.tagId, name = ""),
|
||||
onBack = { refs.route.value = AppRoute.ImageTags },
|
||||
onSave = { refs.route.value = AppRoute.ImageTags }
|
||||
)
|
||||
|
||||
is AppRoute.AddRoom -> RoomFormScreen(
|
||||
title = "Add Room",
|
||||
propertyId = currentRoute.propertyId,
|
||||
roomId = null,
|
||||
roomData = null,
|
||||
formKey = refs.roomFormKey.value,
|
||||
onBack = { refs.route.value = AppRoute.Rooms(currentRoute.propertyId) },
|
||||
onSave = { refs.route.value = AppRoute.Rooms(currentRoute.propertyId) },
|
||||
onViewImages = { }
|
||||
)
|
||||
|
||||
is AppRoute.EditRoom -> RoomFormScreen(
|
||||
title = "Modify Room",
|
||||
propertyId = currentRoute.propertyId,
|
||||
roomId = currentRoute.roomId,
|
||||
roomData = refs.selectedRoom.value,
|
||||
formKey = refs.roomFormKey.value,
|
||||
onBack = { refs.route.value = AppRoute.Rooms(currentRoute.propertyId) },
|
||||
onSave = { refs.route.value = AppRoute.Rooms(currentRoute.propertyId) },
|
||||
onViewImages = { roomId ->
|
||||
refs.route.value = AppRoute.RoomImages(currentRoute.propertyId, roomId)
|
||||
}
|
||||
)
|
||||
|
||||
is AppRoute.IssueTemporaryCard -> IssueTemporaryCardScreen(
|
||||
propertyId = currentRoute.propertyId,
|
||||
roomId = currentRoute.roomId,
|
||||
roomNumber = refs.selectedRoom.value?.roomNumber?.toString(),
|
||||
onBack = { refs.route.value = AppRoute.Rooms(currentRoute.propertyId) }
|
||||
)
|
||||
|
||||
is AppRoute.CardInfo -> CardInfoScreen(
|
||||
propertyId = currentRoute.propertyId,
|
||||
onBack = { refs.route.value = AppRoute.Rooms(currentRoute.propertyId) }
|
||||
)
|
||||
|
||||
is AppRoute.RatePlanCalendar -> RatePlanCalendarScreen(
|
||||
propertyId = currentRoute.propertyId,
|
||||
ratePlanId = currentRoute.ratePlanId,
|
||||
ratePlanCode = currentRoute.ratePlanCode,
|
||||
onBack = { refs.route.value = AppRoute.EditRoomType(currentRoute.propertyId, currentRoute.roomTypeId) }
|
||||
)
|
||||
|
||||
is AppRoute.RoomImages -> RoomImagesScreen(
|
||||
propertyId = currentRoute.propertyId,
|
||||
roomId = currentRoute.roomId,
|
||||
onBack = { refs.route.value = AppRoute.EditRoom(currentRoute.propertyId, currentRoute.roomId) }
|
||||
)
|
||||
|
||||
else -> return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
@@ -0,0 +1,171 @@
|
||||
package com.android.trisolarispms.ui.navigation
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import com.android.trisolarispms.core.auth.AuthzPolicy
|
||||
import com.android.trisolarispms.ui.navigation.AppRoute
|
||||
import com.android.trisolarispms.ui.auth.AuthUiState
|
||||
import com.android.trisolarispms.ui.auth.AuthViewModel
|
||||
import com.android.trisolarispms.ui.razorpay.RazorpayQrScreen
|
||||
import com.android.trisolarispms.ui.razorpay.RazorpaySettingsScreen
|
||||
import com.android.trisolarispms.ui.roomstay.ActiveRoomStaysScreen
|
||||
import com.android.trisolarispms.ui.roomstay.ManageRoomStayRatesScreen
|
||||
import com.android.trisolarispms.ui.roomstay.ManageRoomStaySelectScreen
|
||||
|
||||
@Composable
|
||||
internal fun renderStayFlowRoutes(
|
||||
refs: MainUiRefs,
|
||||
state: AuthUiState,
|
||||
authViewModel: AuthViewModel,
|
||||
authz: AuthzPolicy
|
||||
): Boolean {
|
||||
when (val currentRoute = refs.currentRoute) {
|
||||
is AppRoute.ActiveRoomStays -> ActiveRoomStaysScreen(
|
||||
propertyId = currentRoute.propertyId,
|
||||
propertyName = currentRoute.propertyName,
|
||||
onBack = {
|
||||
val blockBack = authz.shouldBlockBackToHome(
|
||||
propertyId = currentRoute.propertyId,
|
||||
propertyCount = state.propertyRoles.size
|
||||
)
|
||||
if (!blockBack) {
|
||||
refs.route.value = AppRoute.Home
|
||||
}
|
||||
},
|
||||
showBack = !authz.shouldBlockBackToHome(
|
||||
propertyId = currentRoute.propertyId,
|
||||
propertyCount = state.propertyRoles.size
|
||||
),
|
||||
onViewRooms = { refs.route.value = AppRoute.Rooms(currentRoute.propertyId) },
|
||||
onCreateBooking = { refs.route.value = AppRoute.CreateBooking(currentRoute.propertyId) },
|
||||
canCreateBooking = authz.canCreateBookingFor(currentRoute.propertyId),
|
||||
showRazorpaySettings = authz.canManageRazorpaySettings(currentRoute.propertyId),
|
||||
onRazorpaySettings = { refs.route.value = AppRoute.RazorpaySettings(currentRoute.propertyId) },
|
||||
showUserAdmin = authz.canManagePropertyUsers(currentRoute.propertyId),
|
||||
onUserAdmin = { refs.route.value = AppRoute.PropertyUsers(currentRoute.propertyId) },
|
||||
onLogout = authViewModel::signOut,
|
||||
onManageRoomStay = { booking ->
|
||||
val fromAt = booking.checkInAt?.takeIf { it.isNotBlank() }
|
||||
?: booking.expectedCheckInAt.orEmpty()
|
||||
val toAt = booking.expectedCheckOutAt?.takeIf { it.isNotBlank() }
|
||||
?: booking.checkOutAt?.takeIf { it.isNotBlank() }
|
||||
if (fromAt.isNotBlank()) {
|
||||
refs.route.value = AppRoute.ManageRoomStaySelect(
|
||||
propertyId = currentRoute.propertyId,
|
||||
bookingId = booking.id.orEmpty(),
|
||||
fromAt = fromAt,
|
||||
toAt = toAt
|
||||
)
|
||||
}
|
||||
},
|
||||
onViewBookingStays = { booking ->
|
||||
refs.route.value = AppRoute.BookingRoomStays(
|
||||
propertyId = currentRoute.propertyId,
|
||||
bookingId = booking.id.orEmpty()
|
||||
)
|
||||
},
|
||||
onOpenBookingDetails = { booking ->
|
||||
refs.route.value = AppRoute.BookingDetailsTabs(
|
||||
propertyId = currentRoute.propertyId,
|
||||
bookingId = booking.id.orEmpty(),
|
||||
guestId = booking.guestId
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
is AppRoute.RazorpaySettings -> RazorpaySettingsScreen(
|
||||
propertyId = currentRoute.propertyId,
|
||||
onBack = { refs.openActiveRoomStays(currentRoute.propertyId) }
|
||||
)
|
||||
|
||||
is AppRoute.RazorpayQr -> RazorpayQrScreen(
|
||||
propertyId = currentRoute.propertyId,
|
||||
bookingId = currentRoute.bookingId,
|
||||
pendingAmount = currentRoute.pendingAmount,
|
||||
guestPhone = currentRoute.guestPhone,
|
||||
onBack = {
|
||||
refs.route.value = AppRoute.BookingDetailsTabs(
|
||||
currentRoute.propertyId,
|
||||
currentRoute.bookingId,
|
||||
null
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
is AppRoute.ManageRoomStaySelect -> ManageRoomStaySelectScreen(
|
||||
propertyId = currentRoute.propertyId,
|
||||
bookingFromAt = currentRoute.fromAt,
|
||||
bookingToAt = currentRoute.toAt,
|
||||
onBack = { refs.openActiveRoomStays(currentRoute.propertyId) },
|
||||
onNext = { rooms ->
|
||||
refs.selectedManageRooms.value = rooms
|
||||
refs.route.value = AppRoute.ManageRoomStayRates(
|
||||
propertyId = currentRoute.propertyId,
|
||||
bookingId = currentRoute.bookingId,
|
||||
fromAt = currentRoute.fromAt,
|
||||
toAt = currentRoute.toAt
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
is AppRoute.ManageRoomStaySelectFromBooking -> ManageRoomStaySelectScreen(
|
||||
propertyId = currentRoute.propertyId,
|
||||
bookingFromAt = currentRoute.fromAt,
|
||||
bookingToAt = currentRoute.toAt,
|
||||
onBack = { refs.openActiveRoomStays(currentRoute.propertyId) },
|
||||
onNext = { rooms ->
|
||||
refs.selectedManageRooms.value = rooms
|
||||
refs.route.value = AppRoute.ManageRoomStayRatesFromBooking(
|
||||
propertyId = currentRoute.propertyId,
|
||||
bookingId = currentRoute.bookingId,
|
||||
guestId = currentRoute.guestId,
|
||||
fromAt = currentRoute.fromAt,
|
||||
toAt = currentRoute.toAt
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
is AppRoute.ManageRoomStayRates -> ManageRoomStayRatesScreen(
|
||||
propertyId = currentRoute.propertyId,
|
||||
bookingId = currentRoute.bookingId,
|
||||
checkInAt = currentRoute.fromAt,
|
||||
checkOutAt = currentRoute.toAt,
|
||||
selectedRooms = refs.selectedManageRooms.value,
|
||||
onBack = {
|
||||
refs.route.value = AppRoute.ManageRoomStaySelect(
|
||||
currentRoute.propertyId,
|
||||
currentRoute.bookingId,
|
||||
currentRoute.fromAt,
|
||||
currentRoute.toAt
|
||||
)
|
||||
},
|
||||
onDone = { refs.openActiveRoomStays(currentRoute.propertyId) }
|
||||
)
|
||||
|
||||
is AppRoute.ManageRoomStayRatesFromBooking -> ManageRoomStayRatesScreen(
|
||||
propertyId = currentRoute.propertyId,
|
||||
bookingId = currentRoute.bookingId,
|
||||
checkInAt = currentRoute.fromAt,
|
||||
checkOutAt = currentRoute.toAt,
|
||||
selectedRooms = refs.selectedManageRooms.value,
|
||||
onBack = {
|
||||
refs.route.value = AppRoute.ManageRoomStaySelectFromBooking(
|
||||
currentRoute.propertyId,
|
||||
currentRoute.bookingId,
|
||||
currentRoute.guestId,
|
||||
currentRoute.fromAt,
|
||||
currentRoute.toAt
|
||||
)
|
||||
},
|
||||
onDone = {
|
||||
refs.route.value = AppRoute.GuestInfo(
|
||||
currentRoute.propertyId,
|
||||
currentRoute.bookingId,
|
||||
currentRoute.guestId
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
else -> return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package com.android.trisolarispms.ui.navigation
|
||||
|
||||
import androidx.compose.runtime.MutableState
|
||||
import com.android.trisolarispms.data.api.model.AmenityDto
|
||||
import com.android.trisolarispms.data.api.model.GuestDto
|
||||
import com.android.trisolarispms.data.api.model.RoomDto
|
||||
import com.android.trisolarispms.data.api.model.RoomImageTagDto
|
||||
import com.android.trisolarispms.data.api.model.RoomTypeDto
|
||||
import com.android.trisolarispms.ui.navigation.AppRoute
|
||||
import com.android.trisolarispms.ui.roomstay.ManageRoomStaySelection
|
||||
|
||||
internal data class MainUiRefs(
|
||||
val route: MutableState<AppRoute>,
|
||||
val refreshKey: MutableState<Int>,
|
||||
val selectedPropertyId: MutableState<String?>,
|
||||
val selectedPropertyName: MutableState<String?>,
|
||||
val selectedRoom: MutableState<RoomDto?>,
|
||||
val selectedRoomType: MutableState<RoomTypeDto?>,
|
||||
val selectedAmenity: MutableState<AmenityDto?>,
|
||||
val selectedGuest: MutableState<GuestDto?>,
|
||||
val selectedGuestPhone: MutableState<String?>,
|
||||
val selectedImageTag: MutableState<RoomImageTagDto?>,
|
||||
val selectedManageRooms: MutableState<List<ManageRoomStaySelection>>,
|
||||
val roomFormKey: MutableState<Int>,
|
||||
val amenitiesReturnRoute: MutableState<AppRoute>
|
||||
) {
|
||||
val currentRoute: AppRoute
|
||||
get() = route.value
|
||||
|
||||
fun openActiveRoomStays(propertyId: String) {
|
||||
route.value = AppRoute.ActiveRoomStays(propertyId, selectedPropertyName.value ?: "Property")
|
||||
}
|
||||
}
|
||||
@@ -240,7 +240,12 @@ private fun PaymentCard(
|
||||
payment.currency?.let { append(" $it") }
|
||||
}
|
||||
Text(text = amountText, style = MaterialTheme.typography.titleMedium)
|
||||
if (canRefund && (!payment.id.isNullOrBlank() || !payment.gatewayPaymentId.isNullOrBlank())) {
|
||||
val hasRefundableAmount = (payment.amount ?: 0L) > 0L
|
||||
if (
|
||||
canRefund &&
|
||||
hasRefundableAmount &&
|
||||
(!payment.id.isNullOrBlank() || !payment.gatewayPaymentId.isNullOrBlank())
|
||||
) {
|
||||
TextButton(onClick = { onRefund(payment) }) {
|
||||
Text("Refund")
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ package com.android.trisolarispms.ui.payment
|
||||
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.android.trisolarispms.data.api.ApiClient
|
||||
import com.android.trisolarispms.data.api.core.ApiClient
|
||||
import com.android.trisolarispms.data.api.model.RazorpayRefundRequest
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
|
||||
@@ -2,7 +2,7 @@ package com.android.trisolarispms.ui.property
|
||||
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.android.trisolarispms.data.api.ApiClient
|
||||
import com.android.trisolarispms.data.api.core.ApiClient
|
||||
import com.android.trisolarispms.data.api.model.PropertyCreateRequest
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
|
||||
@@ -2,7 +2,7 @@ package com.android.trisolarispms.ui.property
|
||||
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.android.trisolarispms.data.api.ApiClient
|
||||
import com.android.trisolarispms.data.api.core.ApiClient
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.update
|
||||
|
||||
@@ -2,8 +2,8 @@ package com.android.trisolarispms.ui.razorpay
|
||||
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.android.trisolarispms.data.api.ApiClient
|
||||
import com.android.trisolarispms.data.api.ApiConstants
|
||||
import com.android.trisolarispms.data.api.core.ApiClient
|
||||
import com.android.trisolarispms.data.api.core.ApiConstants
|
||||
import com.android.trisolarispms.data.api.model.RazorpayCloseRequest
|
||||
import com.android.trisolarispms.data.api.model.RazorpayQrEventDto
|
||||
import com.android.trisolarispms.data.api.model.RazorpayRequestListItemDto
|
||||
|
||||
@@ -2,7 +2,7 @@ package com.android.trisolarispms.ui.razorpay
|
||||
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.android.trisolarispms.data.api.ApiClient
|
||||
import com.android.trisolarispms.data.api.core.ApiClient
|
||||
import com.android.trisolarispms.data.api.model.RazorpaySettingsRequest
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
|
||||
@@ -2,7 +2,7 @@ package com.android.trisolarispms.ui.room
|
||||
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.android.trisolarispms.data.api.ApiClient
|
||||
import com.android.trisolarispms.data.api.core.ApiClient
|
||||
import com.android.trisolarispms.data.api.model.RoomCreateRequest
|
||||
import com.android.trisolarispms.data.api.model.RoomUpdateRequest
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
|
||||
@@ -2,7 +2,7 @@ package com.android.trisolarispms.ui.room
|
||||
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.android.trisolarispms.data.api.ApiClient
|
||||
import com.android.trisolarispms.data.api.core.ApiClient
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.update
|
||||
|
||||
@@ -2,7 +2,7 @@ package com.android.trisolarispms.ui.roomimage
|
||||
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.android.trisolarispms.data.api.ApiClient
|
||||
import com.android.trisolarispms.data.api.core.ApiClient
|
||||
import com.android.trisolarispms.data.api.model.RoomImageTagDto
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
|
||||
@@ -2,7 +2,7 @@ package com.android.trisolarispms.ui.roomimage
|
||||
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.android.trisolarispms.data.api.ApiClient
|
||||
import com.android.trisolarispms.data.api.core.ApiClient
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.update
|
||||
|
||||
@@ -2,7 +2,7 @@ package com.android.trisolarispms.ui.roomimage
|
||||
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.android.trisolarispms.data.api.ApiClient
|
||||
import com.android.trisolarispms.data.api.core.ApiClient
|
||||
import com.android.trisolarispms.data.api.model.ImageDto
|
||||
import com.android.trisolarispms.data.api.model.RoomImageReorderRequest
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
|
||||
@@ -2,7 +2,7 @@ package com.android.trisolarispms.ui.roomstay
|
||||
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.android.trisolarispms.data.api.ApiClient
|
||||
import com.android.trisolarispms.data.api.core.ApiClient
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.update
|
||||
|
||||
@@ -53,8 +53,8 @@ import coil.ImageLoader
|
||||
import coil.compose.AsyncImage
|
||||
import coil.decode.SvgDecoder
|
||||
import coil.request.ImageRequest
|
||||
import com.android.trisolarispms.data.api.ApiConstants
|
||||
import com.android.trisolarispms.data.api.FirebaseAuthTokenProvider
|
||||
import com.android.trisolarispms.data.api.core.ApiConstants
|
||||
import com.android.trisolarispms.data.api.core.FirebaseAuthTokenProvider
|
||||
import com.android.trisolarispms.data.api.model.BookingDetailsResponse
|
||||
import com.android.trisolarispms.ui.guestdocs.GuestDocumentsTab
|
||||
import com.google.firebase.auth.FirebaseAuth
|
||||
@@ -87,6 +87,10 @@ fun BookingDetailsTabsScreen(
|
||||
val scope = rememberCoroutineScope()
|
||||
val staysState by staysViewModel.state.collectAsState()
|
||||
val detailsState by detailsViewModel.state.collectAsState()
|
||||
val canModifyDocuments = canManageDocuments && when (detailsState.details?.status) {
|
||||
"OPEN", "CHECKED_IN" -> true
|
||||
else -> false
|
||||
}
|
||||
|
||||
LaunchedEffect(propertyId, bookingId, guestId) {
|
||||
staysViewModel.load(propertyId, bookingId)
|
||||
@@ -164,7 +168,8 @@ fun BookingDetailsTabsScreen(
|
||||
propertyId = propertyId,
|
||||
guestId = resolvedGuestId,
|
||||
bookingId = bookingId,
|
||||
canManageDocuments = canManageDocuments
|
||||
canManageDocuments = canManageDocuments,
|
||||
canModifyDocuments = canModifyDocuments
|
||||
)
|
||||
} else {
|
||||
Box(
|
||||
|
||||
@@ -2,8 +2,8 @@ package com.android.trisolarispms.ui.roomstay
|
||||
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.android.trisolarispms.data.api.ApiClient
|
||||
import com.android.trisolarispms.data.api.ApiConstants
|
||||
import com.android.trisolarispms.data.api.core.ApiClient
|
||||
import com.android.trisolarispms.data.api.core.ApiConstants
|
||||
import com.android.trisolarispms.data.api.model.BookingDetailsResponse
|
||||
import com.google.gson.Gson
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
|
||||
@@ -2,7 +2,7 @@ package com.android.trisolarispms.ui.roomstay
|
||||
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.android.trisolarispms.data.api.ApiClient
|
||||
import com.android.trisolarispms.data.api.core.ApiClient
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.update
|
||||
|
||||
@@ -2,7 +2,7 @@ package com.android.trisolarispms.ui.roomstay
|
||||
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.android.trisolarispms.data.api.ApiClient
|
||||
import com.android.trisolarispms.data.api.core.ApiClient
|
||||
import com.android.trisolarispms.data.api.model.BookingBulkCheckInRequest
|
||||
import com.android.trisolarispms.data.api.model.BookingBulkCheckInStayRequest
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
|
||||
@@ -2,7 +2,7 @@ package com.android.trisolarispms.ui.roomstay
|
||||
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.android.trisolarispms.data.api.ApiClient
|
||||
import com.android.trisolarispms.data.api.core.ApiClient
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.update
|
||||
|
||||
@@ -33,7 +33,7 @@ import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import com.android.trisolarispms.data.api.model.AmenityDto
|
||||
import com.android.trisolarispms.data.api.ApiConstants
|
||||
import com.android.trisolarispms.data.api.core.ApiConstants
|
||||
import coil.compose.AsyncImage
|
||||
|
||||
@Composable
|
||||
|
||||
@@ -43,7 +43,7 @@ import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.window.PopupProperties
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import coil.compose.AsyncImage
|
||||
import com.android.trisolarispms.data.api.ApiConstants
|
||||
import com.android.trisolarispms.data.api.core.ApiConstants
|
||||
|
||||
@Composable
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
|
||||
@@ -2,7 +2,7 @@ package com.android.trisolarispms.ui.roomtype
|
||||
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.android.trisolarispms.data.api.ApiClient
|
||||
import com.android.trisolarispms.data.api.core.ApiClient
|
||||
import com.android.trisolarispms.data.api.model.AmenityCreateRequest
|
||||
import com.android.trisolarispms.data.api.model.AmenityUpdateRequest
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
|
||||
@@ -2,7 +2,7 @@ package com.android.trisolarispms.ui.roomtype
|
||||
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.android.trisolarispms.data.api.ApiClient
|
||||
import com.android.trisolarispms.data.api.core.ApiClient
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.update
|
||||
|
||||
@@ -2,7 +2,7 @@ package com.android.trisolarispms.ui.roomtype
|
||||
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.android.trisolarispms.data.api.ApiClient
|
||||
import com.android.trisolarispms.data.api.core.ApiClient
|
||||
import com.android.trisolarispms.data.api.model.RatePlanCalendarEntry
|
||||
import com.android.trisolarispms.data.api.model.RatePlanCalendarUpsertRequest
|
||||
import com.android.trisolarispms.data.api.model.RatePlanRequest
|
||||
|
||||
@@ -44,7 +44,7 @@ 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.core.ApiConstants
|
||||
|
||||
@Composable
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
|
||||
@@ -2,7 +2,7 @@ package com.android.trisolarispms.ui.roomtype
|
||||
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.android.trisolarispms.data.api.ApiClient
|
||||
import com.android.trisolarispms.data.api.core.ApiClient
|
||||
import com.android.trisolarispms.data.api.model.RoomTypeCreateRequest
|
||||
import com.android.trisolarispms.data.api.model.RoomTypeUpdateRequest
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
|
||||
@@ -2,7 +2,7 @@ package com.android.trisolarispms.ui.roomtype
|
||||
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.android.trisolarispms.data.api.ApiClient
|
||||
import com.android.trisolarispms.data.api.core.ApiClient
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.update
|
||||
|
||||
@@ -2,7 +2,7 @@ package com.android.trisolarispms.ui.users
|
||||
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.android.trisolarispms.data.api.ApiClient
|
||||
import com.android.trisolarispms.data.api.core.ApiClient
|
||||
import com.android.trisolarispms.data.api.model.PropertyAccessCodeCreateRequest
|
||||
import com.android.trisolarispms.data.api.model.PropertyAccessCodeResponse
|
||||
import com.android.trisolarispms.data.api.model.PropertyUserResponse
|
||||
|
||||
@@ -14,6 +14,8 @@ import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.android.trisolarispms.core.auth.Role
|
||||
import com.android.trisolarispms.core.auth.toRoles
|
||||
|
||||
@Composable
|
||||
fun PropertyUserCard(
|
||||
@@ -73,6 +75,14 @@ fun canDisableUser(
|
||||
if (user.userId.isNullOrBlank()) return false
|
||||
if (canDisableAdmin) return true
|
||||
if (!canDisableManager) return false
|
||||
val allowed = setOf("STAFF", "AGENT", "HOUSEKEEPING", "FINANCE", "GUIDE", "SUPERVISOR")
|
||||
return user.roles.all { allowed.contains(it) }
|
||||
val allowed = setOf(
|
||||
Role.STAFF,
|
||||
Role.AGENT,
|
||||
Role.HOUSEKEEPING,
|
||||
Role.FINANCE,
|
||||
Role.GUIDE,
|
||||
Role.SUPERVISOR
|
||||
)
|
||||
val parsedRoles = user.roles.toRoles()
|
||||
return parsedRoles.size == user.roles.size && parsedRoles.all { it in allowed }
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ package com.android.trisolarispms.ui.users
|
||||
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.android.trisolarispms.data.api.ApiClient
|
||||
import com.android.trisolarispms.data.api.core.ApiClient
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.update
|
||||
@@ -32,18 +32,9 @@ class UserDirectoryViewModel : ViewModel() {
|
||||
when (mode) {
|
||||
UserDirectoryMode.SuperAdmin -> {
|
||||
val response = api.listUsers(null)
|
||||
val body = response.body()
|
||||
if (response.isSuccessful && body != null) {
|
||||
val mapped = body.map {
|
||||
PropertyUserUi(
|
||||
userId = it.id,
|
||||
name = it.name,
|
||||
phoneE164 = it.phoneE164,
|
||||
disabled = it.disabled,
|
||||
superAdmin = it.superAdmin
|
||||
)
|
||||
}
|
||||
_state.update { it.copy(isLoading = false, users = mapped, error = null) }
|
||||
val users = response.body()?.toSuperAdminUsers()
|
||||
if (response.isSuccessful && users != null) {
|
||||
_state.update { it.copy(isLoading = false, users = users, error = null) }
|
||||
} else {
|
||||
_state.update { it.copy(isLoading = false, error = "Load failed: ${response.code()}") }
|
||||
}
|
||||
@@ -53,19 +44,9 @@ class UserDirectoryViewModel : ViewModel() {
|
||||
propertyId = mode.propertyId,
|
||||
phone = null
|
||||
)
|
||||
val body = response.body()
|
||||
if (response.isSuccessful && body != null) {
|
||||
val mapped = body.map {
|
||||
PropertyUserUi(
|
||||
userId = it.userId,
|
||||
roles = it.roles,
|
||||
name = it.name,
|
||||
phoneE164 = it.phoneE164,
|
||||
disabled = it.disabled,
|
||||
superAdmin = it.superAdmin
|
||||
)
|
||||
}
|
||||
_state.update { it.copy(isLoading = false, users = mapped, error = null) }
|
||||
val users = response.body()?.toPropertyUsers()
|
||||
if (response.isSuccessful && users != null) {
|
||||
_state.update { it.copy(isLoading = false, users = users, error = null) }
|
||||
} else {
|
||||
_state.update { it.copy(isLoading = false, error = "Load failed: ${response.code()}") }
|
||||
}
|
||||
@@ -90,18 +71,9 @@ class UserDirectoryViewModel : ViewModel() {
|
||||
when (mode) {
|
||||
UserDirectoryMode.SuperAdmin -> {
|
||||
val response = api.listUsers(digits)
|
||||
val body = response.body()
|
||||
if (response.isSuccessful && body != null) {
|
||||
val mapped = body.map {
|
||||
PropertyUserUi(
|
||||
userId = it.id,
|
||||
name = it.name,
|
||||
phoneE164 = it.phoneE164,
|
||||
disabled = it.disabled,
|
||||
superAdmin = it.superAdmin
|
||||
)
|
||||
}
|
||||
_state.update { it.copy(isLoading = false, users = mapped, error = null) }
|
||||
val users = response.body()?.toSuperAdminUsers()
|
||||
if (response.isSuccessful && users != null) {
|
||||
_state.update { it.copy(isLoading = false, users = users, error = null) }
|
||||
} else {
|
||||
_state.update { it.copy(isLoading = false, error = "Search failed: ${response.code()}") }
|
||||
}
|
||||
@@ -111,19 +83,9 @@ class UserDirectoryViewModel : ViewModel() {
|
||||
propertyId = mode.propertyId,
|
||||
phone = digits
|
||||
)
|
||||
val body = response.body()
|
||||
if (response.isSuccessful && body != null) {
|
||||
val mapped = body.map {
|
||||
PropertyUserUi(
|
||||
userId = it.userId,
|
||||
roles = it.roles,
|
||||
name = it.name,
|
||||
phoneE164 = it.phoneE164,
|
||||
disabled = it.disabled,
|
||||
superAdmin = it.superAdmin
|
||||
)
|
||||
}
|
||||
_state.update { it.copy(isLoading = false, users = mapped, error = null) }
|
||||
val users = response.body()?.toPropertyUsers()
|
||||
if (response.isSuccessful && users != null) {
|
||||
_state.update { it.copy(isLoading = false, users = users, error = null) }
|
||||
} else {
|
||||
_state.update { it.copy(isLoading = false, error = "Search failed: ${response.code()}") }
|
||||
}
|
||||
@@ -134,4 +96,28 @@ class UserDirectoryViewModel : ViewModel() {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun List<com.android.trisolarispms.data.api.model.AppUserSummaryResponse>.toSuperAdminUsers():
|
||||
List<PropertyUserUi> =
|
||||
map {
|
||||
PropertyUserUi(
|
||||
userId = it.id,
|
||||
name = it.name,
|
||||
phoneE164 = it.phoneE164,
|
||||
disabled = it.disabled,
|
||||
superAdmin = it.superAdmin
|
||||
)
|
||||
}
|
||||
|
||||
private fun List<com.android.trisolarispms.data.api.model.PropertyUserDetailsResponse>.toPropertyUsers():
|
||||
List<PropertyUserUi> = map {
|
||||
PropertyUserUi(
|
||||
userId = it.userId,
|
||||
roles = it.roles,
|
||||
name = it.name,
|
||||
phoneE164 = it.phoneE164,
|
||||
disabled = it.disabled,
|
||||
superAdmin = it.superAdmin
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user