Remove summary from list responses, calculate client-side
- Remove summary field from MyResidencesResponse and TaskColumnsResponse - Update HomeScreen and ResidencesScreen to observe DataManager.totalSummary - Load tasks when residence views appear to ensure accurate summary - Add pull-to-refresh for tasks on ResidencesScreen - Update iOS views to use client-side calculated summary Summary is now calculated via refreshSummaryFromKanban() from cached kanban data. This ensures summary is always up-to-date after CRUD operations and when residence views are shown. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -285,20 +285,17 @@ object DataManager {
|
||||
|
||||
fun setMyResidences(response: MyResidencesResponse) {
|
||||
_myResidences.value = response
|
||||
// Also update totalSummary from myResidences response
|
||||
_totalSummary.value = response.summary
|
||||
myResidencesCacheTime = currentTimeMs()
|
||||
summaryCacheTime = currentTimeMs()
|
||||
updateLastSyncTime()
|
||||
// Calculate summary from cached kanban data (API no longer returns summary stats)
|
||||
// This ensures summary is always up-to-date when residence view is shown
|
||||
refreshSummaryFromKanban()
|
||||
persistToDisk()
|
||||
}
|
||||
|
||||
fun setTotalSummary(summary: TotalSummary) {
|
||||
_totalSummary.value = summary
|
||||
// Also update the summary in myResidences if it exists
|
||||
_myResidences.value?.let { current ->
|
||||
_myResidences.value = current.copy(summary = summary)
|
||||
}
|
||||
summaryCacheTime = currentTimeMs()
|
||||
persistToDisk()
|
||||
}
|
||||
@@ -356,10 +353,6 @@ object DataManager {
|
||||
fun refreshSummaryFromKanban() {
|
||||
val calculatedSummary = calculateSummaryFromKanban()
|
||||
_totalSummary.value = calculatedSummary
|
||||
// Also update myResidences summary if present
|
||||
_myResidences.value?.let { current ->
|
||||
_myResidences.value = current.copy(summary = calculatedSummary)
|
||||
}
|
||||
}
|
||||
|
||||
fun setResidenceSummary(residenceId: Int, summary: ResidenceSummaryResponse) {
|
||||
@@ -431,8 +424,7 @@ object DataManager {
|
||||
return TaskColumnsResponse(
|
||||
columns = filteredColumns,
|
||||
daysThreshold = allTasksData.daysThreshold,
|
||||
residenceId = residenceId.toString(),
|
||||
summary = null // Summary is global; residence-specific not available client-side
|
||||
residenceId = residenceId.toString()
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -178,13 +178,13 @@ data class TaskColumn(
|
||||
|
||||
/**
|
||||
* Kanban board response matching Go API KanbanBoardResponse
|
||||
* NOTE: Summary statistics are calculated client-side from kanban data
|
||||
*/
|
||||
@Serializable
|
||||
data class TaskColumnsResponse(
|
||||
val columns: List<TaskColumn>,
|
||||
@SerialName("days_threshold") val daysThreshold: Int,
|
||||
@SerialName("residence_id") val residenceId: String,
|
||||
val summary: TotalSummary? = null
|
||||
@SerialName("residence_id") val residenceId: String
|
||||
)
|
||||
|
||||
/**
|
||||
|
||||
@@ -200,12 +200,11 @@ data class WithSummaryResponse<T>(
|
||||
|
||||
/**
|
||||
* My residences response - list of user's residences
|
||||
* Go API returns array directly, this wraps for consistency
|
||||
* NOTE: Summary statistics are calculated client-side from kanban data
|
||||
*/
|
||||
@Serializable
|
||||
data class MyResidencesResponse(
|
||||
val residences: List<ResidenceResponse>,
|
||||
val summary: TotalSummary = TotalSummary()
|
||||
val residences: List<ResidenceResponse>
|
||||
)
|
||||
|
||||
/**
|
||||
|
||||
@@ -18,7 +18,9 @@ import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import com.example.casera.ui.components.HandleErrors
|
||||
import com.example.casera.ui.theme.AppRadius
|
||||
import com.example.casera.viewmodel.ResidenceViewModel
|
||||
import com.example.casera.viewmodel.TaskViewModel
|
||||
import com.example.casera.network.ApiResult
|
||||
import com.example.casera.data.DataManager
|
||||
import casera.composeapp.generated.resources.*
|
||||
import org.jetbrains.compose.resources.stringResource
|
||||
|
||||
@@ -28,12 +30,16 @@ fun HomeScreen(
|
||||
onNavigateToResidences: () -> Unit,
|
||||
onNavigateToTasks: () -> Unit,
|
||||
onLogout: () -> Unit,
|
||||
viewModel: ResidenceViewModel = viewModel { ResidenceViewModel() }
|
||||
viewModel: ResidenceViewModel = viewModel { ResidenceViewModel() },
|
||||
taskViewModel: TaskViewModel = viewModel { TaskViewModel() }
|
||||
) {
|
||||
val summaryState by viewModel.myResidencesState.collectAsState()
|
||||
val totalSummary by DataManager.totalSummary.collectAsState()
|
||||
|
||||
LaunchedEffect(Unit) {
|
||||
viewModel.loadMyResidences()
|
||||
// Also load tasks to ensure summary can be calculated from kanban data
|
||||
taskViewModel.loadTasks()
|
||||
}
|
||||
|
||||
// Handle errors for loading summary
|
||||
@@ -159,7 +165,7 @@ fun HomeScreen(
|
||||
color = MaterialTheme.colorScheme.outlineVariant
|
||||
)
|
||||
StatItem(
|
||||
value = "${summary.summary.totalTasks}",
|
||||
value = "${totalSummary?.totalTasks ?: 0}",
|
||||
label = stringResource(Res.string.home_total_tasks),
|
||||
color = Color(0xFF8B5CF6)
|
||||
)
|
||||
@@ -170,7 +176,7 @@ fun HomeScreen(
|
||||
color = MaterialTheme.colorScheme.outlineVariant
|
||||
)
|
||||
StatItem(
|
||||
value = "${summary.summary.totalPending}",
|
||||
value = "${totalSummary?.totalPending ?: 0}",
|
||||
label = stringResource(Res.string.home_pending),
|
||||
color = Color(0xFFF59E0B)
|
||||
)
|
||||
|
||||
@@ -29,12 +29,14 @@ import com.example.casera.ui.components.JoinResidenceDialog
|
||||
import com.example.casera.ui.components.common.StatItem
|
||||
import com.example.casera.ui.components.residence.TaskStatChip
|
||||
import com.example.casera.viewmodel.ResidenceViewModel
|
||||
import com.example.casera.viewmodel.TaskViewModel
|
||||
import com.example.casera.network.ApiResult
|
||||
import com.example.casera.utils.SubscriptionHelper
|
||||
import com.example.casera.ui.subscription.UpgradePromptDialog
|
||||
import com.example.casera.cache.SubscriptionCache
|
||||
import com.example.casera.analytics.PostHogAnalytics
|
||||
import com.example.casera.analytics.AnalyticsEvents
|
||||
import com.example.casera.data.DataManager
|
||||
import casera.composeapp.generated.resources.*
|
||||
import org.jetbrains.compose.resources.stringResource
|
||||
|
||||
@@ -46,9 +48,11 @@ fun ResidencesScreen(
|
||||
onLogout: () -> Unit,
|
||||
onNavigateToProfile: () -> Unit = {},
|
||||
shouldRefresh: Boolean = false,
|
||||
viewModel: ResidenceViewModel = viewModel { ResidenceViewModel() }
|
||||
viewModel: ResidenceViewModel = viewModel { ResidenceViewModel() },
|
||||
taskViewModel: TaskViewModel = viewModel { TaskViewModel() }
|
||||
) {
|
||||
val myResidencesState by viewModel.myResidencesState.collectAsState()
|
||||
val totalSummary by DataManager.totalSummary.collectAsState()
|
||||
var showJoinDialog by remember { mutableStateOf(false) }
|
||||
var isRefreshing by remember { mutableStateOf(false) }
|
||||
var showUpgradePrompt by remember { mutableStateOf(false) }
|
||||
@@ -65,16 +69,19 @@ fun ResidencesScreen(
|
||||
return Pair(check.allowed, check.triggerKey)
|
||||
}
|
||||
|
||||
// Track screen view
|
||||
// Track screen view and load data
|
||||
LaunchedEffect(Unit) {
|
||||
PostHogAnalytics.screen(AnalyticsEvents.RESIDENCE_SCREEN_SHOWN)
|
||||
viewModel.loadMyResidences()
|
||||
// Also load tasks to ensure summary can be calculated from kanban data
|
||||
taskViewModel.loadTasks()
|
||||
}
|
||||
|
||||
// Refresh when shouldRefresh flag changes
|
||||
LaunchedEffect(shouldRefresh) {
|
||||
if (shouldRefresh) {
|
||||
viewModel.loadMyResidences(forceRefresh = true)
|
||||
taskViewModel.loadTasks(forceRefresh = true)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -327,7 +334,8 @@ fun ResidencesScreen(
|
||||
isRefreshing = isRefreshing,
|
||||
onRefresh = {
|
||||
isRefreshing = true
|
||||
viewModel.loadMyResidences()
|
||||
viewModel.loadMyResidences(forceRefresh = true)
|
||||
taskViewModel.loadTasks(forceRefresh = true)
|
||||
},
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
@@ -383,12 +391,12 @@ fun ResidencesScreen(
|
||||
) {
|
||||
StatItem(
|
||||
icon = Icons.Default.Home,
|
||||
value = "${response.summary.totalResidences}",
|
||||
value = "${totalSummary?.totalResidences ?: response.residences.size}",
|
||||
label = stringResource(Res.string.home_properties)
|
||||
)
|
||||
StatItem(
|
||||
icon = Icons.Default.Assignment,
|
||||
value = "${response.summary.totalTasks}",
|
||||
value = "${totalSummary?.totalTasks ?: 0}",
|
||||
label = stringResource(Res.string.home_total_tasks)
|
||||
)
|
||||
}
|
||||
@@ -403,18 +411,18 @@ fun ResidencesScreen(
|
||||
) {
|
||||
StatItem(
|
||||
icon = Icons.Default.Warning,
|
||||
value = "${response.summary.totalOverdue}",
|
||||
value = "${totalSummary?.totalOverdue ?: 0}",
|
||||
label = stringResource(Res.string.home_overdue),
|
||||
valueColor = if (response.summary.totalOverdue > 0) MaterialTheme.colorScheme.error else null
|
||||
valueColor = if ((totalSummary?.totalOverdue ?: 0) > 0) MaterialTheme.colorScheme.error else null
|
||||
)
|
||||
StatItem(
|
||||
icon = Icons.Default.CalendarToday,
|
||||
value = "${response.summary.tasksDueNextWeek}",
|
||||
value = "${totalSummary?.tasksDueNextWeek ?: 0}",
|
||||
label = stringResource(Res.string.home_due_this_week)
|
||||
)
|
||||
StatItem(
|
||||
icon = Icons.Default.Event,
|
||||
value = "${response.summary.tasksDueNextMonth}",
|
||||
value = "${totalSummary?.tasksDueNextMonth ?: 0}",
|
||||
label = stringResource(Res.string.home_next_30_days)
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user