Fix task stats consistency and improve residence card UI

- Compute task stats locally from kanban data for both summary card and residence cards
- Filter out completed_tasks and cancelled_tasks columns from counts
- Use startOfDay for accurate date comparisons (overdue, due this week, next 30 days)
- Add parseDate helper to DateUtils
- Make address tappable to open in Apple Maps
- Remove navigation title from residences list
- Update CLAUDE.md with Go backend references and DataManager architecture

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Trey t
2025-12-16 23:57:01 -06:00
parent 7f3ed69574
commit c3a9494b0f
6 changed files with 378 additions and 354 deletions

129
CLAUDE.md
View File

@@ -10,12 +10,12 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
## Project Overview
MyCrib is a Kotlin Multiplatform Mobile (KMM) property management application with shared business logic and platform-specific UI implementations. The backend is a Django REST Framework API (located in the sibling `myCribAPI` directory).
MyCrib (Casera) is a Kotlin Multiplatform Mobile (KMM) property management application with shared business logic and platform-specific UI implementations. The backend is a Go REST API with PostgreSQL (located in the sibling `myCribAPI-go` directory).
**Tech Stack:**
- **Shared (Kotlin)**: Compose Multiplatform for Android, networking layer, ViewModels, models
- **iOS**: SwiftUI with Kotlin shared layer integration via SKIE
- **Backend**: Django REST Framework (separate repository at `../myCribAPI`)
- **Backend**: Go REST API with PostgreSQL (separate directory at `../myCribAPI-go`)
## Build Commands
@@ -56,42 +56,64 @@ open iosApp/iosApp.xcodeproj
## Architecture
### Shared Kotlin Layer (`composeApp/src/commonMain/kotlin/com/example/mycrib/`)
### Shared Kotlin Layer (`composeApp/src/commonMain/kotlin/com/example/casera/`)
**Core Components:**
1. **APILayer** (`network/APILayer.kt`)
- **Single entry point for all API calls**
- Manages caching via DataCache
- Handles automatic cache updates on mutations (create/update/delete)
- Pattern: Cache-first reads with optional `forceRefresh` parameter
- Returns `ApiResult<T>` (Success/Error/Loading states)
1. **DataManager** (`data/DataManager.kt`) - **Single Source of Truth**
- Unified cache for ALL app data (auth, residences, tasks, lookups, etc.)
- All data is exposed via `StateFlow` for reactive UI updates
- Automatic cache timeout validation (1 hour default)
- Persists data to disk for offline access
- Platform-specific initialization (TokenManager, ThemeStorage, PersistenceManager)
- O(1) lookup helpers: `getTaskPriority(id)`, `getTaskCategory(id)`, etc.
2. **DataCache** (`cache/DataCache.kt`)
- In-memory cache for lookup data (residence types, task categories, priorities, statuses, etc.)
- Must be initialized via `APILayer.initializeLookups()` after login
- Stores `MutableState` objects that UI can observe directly
- Cleared on logout
2. **APILayer** (`network/APILayer.kt`) - **Single Entry Point for Network Calls**
- Every API response immediately updates DataManager
- All screens observe DataManager StateFlows directly
- Handles cache-first reads with `forceRefresh` parameter
- ETag-based conditional fetching for lookups (304 Not Modified support)
- Guards against concurrent initialization/prefetch calls
- Returns `ApiResult<T>` (Success/Error/Loading/Idle states)
3. **TokenStorage** (`storage/TokenStorage.kt`)
- Platform-specific secure token storage
- Android: EncryptedSharedPreferences
- iOS: Keychain
- All API calls automatically include token from TokenStorage
3. **API Clients** (`network/*Api.kt`)
- Domain-specific API clients: `ResidenceApi`, `TaskApi`, `ContractorApi`, etc.
- Low-level HTTP calls using Ktor
- Error parsing and response handling
4. **ViewModels** (`viewmodel/`)
- Shared ViewModels expose StateFlow for UI observation
- Pattern: ViewModel calls APILayer → APILayer manages cache + network → ViewModel emits ApiResult states
- ViewModels: `ResidenceViewModel`, `TaskViewModel`, `AuthViewModel`, `ContractorViewModel`, etc.
4. **PersistenceManager** (`data/PersistenceManager.kt`)
- Platform-specific disk persistence (expect/actual pattern)
- Stores serialized JSON for offline access
- Loads cached data on app startup
5. **Navigation** (`navigation/`)
- Type-safe navigation using kotlinx.serialization
- Routes defined as `@Serializable` data classes
- Shared between Android Compose Navigation
5. **ViewModels** (`viewmodel/`)
- Thin wrappers that call APILayer methods
- Expose loading/error states for UI feedback
- ViewModels: `ResidenceViewModel`, `TaskViewModel`, `AuthViewModel`, etc.
**Data Flow:**
```
UI → ViewModel → APILayer → (Cache Check) → Network API → Update Cache → Return to ViewModel → UI observes StateFlow
User Action → ViewModel → APILayer → API Client → Server Response
DataManager Updated (cache + disk)
All Screens React (StateFlow observers)
```
**Cache Architecture:**
```kotlin
// DataManager exposes StateFlows that UI observes directly
DataManager.residences: StateFlow<List<Residence>>
DataManager.myResidences: StateFlow<MyResidencesResponse?>
DataManager.allTasks: StateFlow<TaskColumnsResponse?>
DataManager.taskCategories: StateFlow<List<TaskCategory>>
// Cache validation (1 hour timeout)
DataManager.isCacheValid(DataManager.residencesCacheTime)
// O(1) lookups for IDs
DataManager.getTaskPriority(task.priorityId) // Returns TaskPriority?
DataManager.getTaskCategory(task.categoryId) // Returns TaskCategory?
```
### iOS Layer (`iosApp/iosApp/`)
@@ -855,7 +877,7 @@ fun ThemePickerDialog(
## Environment Configuration
**API Environment Toggle** (`composeApp/src/commonMain/kotlin/com/example/mycrib/network/ApiConfig.kt`):
**API Environment Toggle** (`composeApp/src/commonMain/kotlin/com/example/casera/network/ApiConfig.kt`):
```kotlin
val CURRENT_ENV = Environment.DEV // or Environment.LOCAL
@@ -864,7 +886,7 @@ val CURRENT_ENV = Environment.DEV // or Environment.LOCAL
- `Environment.LOCAL`: Points to `http://10.0.2.2:8000/api` (Android emulator) or `http://127.0.0.1:8000/api` (iOS simulator)
- `Environment.DEV`: Points to `https://mycrib.treytartt.com/api`
**Change this to switch between local Django backend and production server.**
**Change this to switch between local Go backend and production server.**
## Common Development Patterns
@@ -953,20 +975,34 @@ Currently tests are minimal. When adding tests:
### Committing Changes
When committing changes that span both iOS and Android, commit them together in the KMM repository. If backend changes are needed, commit separately in the `myCribAPI` repository.
When committing changes that span both iOS and Android, commit them together in the KMM repository. If backend changes are needed, commit separately in the `myCribAPI-go` repository.
### Data Cache Initialization
### DataManager Initialization
**Critical**: After user login, call `APILayer.initializeLookups()` to populate DataCache with reference data. Without this, dropdowns and pickers will be empty.
**Critical**: DataManager must be initialized at app startup with platform-specific managers:
```kotlin
// In Application.onCreate() or equivalent
DataManager.initialize(
tokenMgr = TokenManager(context),
themeMgr = ThemeStorageManager(context),
persistenceMgr = PersistenceManager(context)
)
```
After user login, call `APILayer.initializeLookups()` to populate DataManager with reference data. This uses ETag-based caching - if data hasn't changed, server returns 304 Not Modified.
```kotlin
// After successful login
val initResult = APILayer.initializeLookups()
if (initResult is ApiResult.Success) {
// Navigate to main screen
// Now safe to navigate to main screen
// Lookups are cached in DataManager and persisted to disk
}
```
Without this, dropdowns and pickers will be empty.
### iOS Build Issues
If iOS build fails with type mismatch errors:
@@ -989,19 +1025,19 @@ Without `forceRefresh`, APILayer returns cached data.
MyCribKMM/
├── composeApp/
│ └── src/
│ ├── commonMain/kotlin/com/example/mycrib/
│ │ ├── cache/ # DataCache
│ │ ├── models/ # Shared data models
│ │ ├── network/ # APILayer, API clients
│ │ ├── repository/ # Additional data repositories
│ │ ├── storage/ # TokenStorage
│ ├── commonMain/kotlin/com/example/casera/
│ │ ├── data/ # DataManager, PersistenceManager
│ │ ├── models/ # Shared data models (kotlinx.serialization)
│ │ ├── network/ # APILayer, *Api clients, ApiConfig
│ │ ├── storage/ # TokenManager, ThemeStorageManager
│ │ ├── util/ # DateUtils, helpers
│ │ ├── ui/ # Compose UI (Android)
│ │ │ ├── components/ # Reusable components
│ │ │ ├── screens/ # Screen composables
│ │ │ └── theme/ # Material theme
│ │ │ └── theme/ # Material theme, ThemeManager
│ │ ├── viewmodel/ # Shared ViewModels
│ │ └── App.kt # Android navigation
│ ├── androidMain/ # Android-specific code
│ ├── androidMain/ # Android-specific (TokenManager, etc.)
│ ├── iosMain/ # iOS-specific Kotlin code
│ └── commonTest/ # Shared tests
@@ -1009,9 +1045,9 @@ MyCribKMM/
│ ├── *ViewModel.swift # Swift wrappers for Kotlin VMs
│ ├── *View.swift # SwiftUI screens
│ ├── Components/ # Reusable SwiftUI components
│ ├── Design/ # Design system (spacing, colors)
│ ├── Design/ # Design system (DesignSystem.swift, OrganicDesign.swift)
│ ├── Extensions/ # Swift extensions
│ ├── Helpers/ # Utility helpers
│ ├── Helpers/ # Utility helpers (DateUtils, etc.)
│ ├── PushNotifications/ # APNs integration
│ └── [Feature]/ # Feature-grouped files
│ ├── Task/
@@ -1024,6 +1060,5 @@ MyCribKMM/
## Related Repositories
- **Backend API**: `../myCribAPI` - Django REST Framework backend
- **Load Testing**: `../myCribAPI/locust` - Locust load testing scripts
- **Documentation**: `../myCribAPI/docs` - Server configuration guides
- **Backend API**: `../myCribAPI-go` - Go REST API with PostgreSQL
- **Documentation**: `../myCribAPI-go/docs` - Server configuration and API docs