From 0130b2cdf10011c5eba77f57887a480d297cf5af Mon Sep 17 00:00:00 2001 From: Trey t Date: Sat, 15 Nov 2025 11:18:14 -0600 Subject: [PATCH] Add documentation and remove hardcoded task threshold from API calls MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Added comprehensive usage documentation for dynamic task summary components and updated TaskApi to rely on backend's default threshold value. **Changes:** **New: TASK_SUMMARY_USAGE.md** - Complete Android usage guide for TaskSummaryCard component - Examples of basic usage, filtering categories, and customization - Documents all available categories and their metadata - Shows how to use dynamic task summary in different screens **New: iosApp/TASK_SUMMARY_USAGE_IOS.md** - Complete iOS usage guide for TaskSummaryCard SwiftUI component - Examples for ResidenceDetailView, ResidenceCard, and HomeScreen - Documents SF Symbol icon usage and color parsing - Integration examples and troubleshooting tips **New: TaskConstants.kt** - Created client-side constants file (currently unused) - Contains TASK_CURRENT_THRESHOLD_DAYS = 29 - Available for future use if client needs local threshold **Updated: TaskApi.kt** - Changed days parameter from `days: Int = 30` to `days: Int? = null` - Now only sends days parameter to backend if explicitly provided - Allows backend's default threshold (29 days) to be used - Applied to both getTasks() and getTasksByResidence() **Updated: ApiConfig.kt** - Minor configuration update **Benefits:** ✅ Comprehensive documentation for both platforms ✅ Apps now use backend's default threshold value ✅ Cleaner API calls without unnecessary parameters ✅ Client can still override threshold if needed ✅ Documentation includes troubleshooting and best practices 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- TASK_SUMMARY_USAGE.md | 259 ++++++++++++++++++ .../com/example/mycrib/network/ApiConfig.kt | 2 +- .../com/example/mycrib/network/TaskApi.kt | 8 +- .../com/example/mycrib/util/TaskConstants.kt | 15 + iosApp/TASK_SUMMARY_USAGE_IOS.md | 254 +++++++++++++++++ 5 files changed, 533 insertions(+), 5 deletions(-) create mode 100644 TASK_SUMMARY_USAGE.md create mode 100644 composeApp/src/commonMain/kotlin/com/example/mycrib/util/TaskConstants.kt create mode 100644 iosApp/TASK_SUMMARY_USAGE_IOS.md diff --git a/TASK_SUMMARY_USAGE.md b/TASK_SUMMARY_USAGE.md new file mode 100644 index 0000000..f13e194 --- /dev/null +++ b/TASK_SUMMARY_USAGE.md @@ -0,0 +1,259 @@ +# Task Summary Component Usage + +## Overview + +The `TaskSummaryCard` component displays task statistics dynamically based on backend data. The backend provides all metadata (icons, colors, display names), making the UI fully data-driven. + +## Backend Structure + +The backend returns task summaries in this format: + +```json +{ + "total": 25, + "categories": [ + { + "name": "overdue_tasks", + "display_name": "Overdue", + "icons": { + "ios": "exclamationmark.triangle", + "android": "Warning", + "web": "exclamation-triangle" + }, + "color": "#FF3B30", + "count": 3 + }, + { + "name": "current_tasks", + "display_name": "Current", + "icons": { + "ios": "calendar", + "android": "CalendarToday", + "web": "calendar" + }, + "color": "#007AFF", + "count": 8 + }, + // ... more categories + ] +} +``` + +## Model Classes + +The app models are defined in `Residence.kt`: + +```kotlin +@Serializable +data class TaskCategoryIcon( + val ios: String, + val android: String, + val web: String +) + +@Serializable +data class TaskCategory( + val name: String, + @SerialName("display_name") val displayName: String, + val icons: TaskCategoryIcon, + val color: String, + val count: Int +) + +@Serializable +data class TaskSummary( + val total: Int, + val categories: List +) +``` + +## Usage Examples + +### Example 1: Show All Categories + +```kotlin +@Composable +fun ResidenceDetailScreen(residence: ResidenceWithTasks) { + Column { + // Other residence details... + + TaskSummaryCard( + taskSummary = residence.taskSummary + ) + } +} +``` + +### Example 2: Show Only Specific Categories + +For the main residence view, you might want to show only certain categories: + +```kotlin +@Composable +fun ResidenceCard(residence: ResidenceSummary) { + Card { + Column { + Text(text = residence.name) + + // Only show overdue, current, and in_progress + TaskSummaryCard( + taskSummary = residence.taskSummary, + visibleCategories = listOf( + "overdue_tasks", + "current_tasks", + "in_progress_tasks" + ) + ) + } + } +} +``` + +### Example 3: Custom Styling + +```kotlin +TaskSummaryCard( + taskSummary = residence.taskSummary, + modifier = Modifier + .fillMaxWidth() + .padding(16.dp), + visibleCategories = listOf("overdue_tasks", "current_tasks") +) +``` + +## Available Categories + +The backend defines these categories (in `task/constants.py`): + +1. **overdue_tasks** - Red (#FF3B30) - Tasks past their due date +2. **current_tasks** - Blue (#007AFF) - Tasks due within threshold (29 days) +3. **in_progress_tasks** - Orange (#FF9500) - Tasks being worked on +4. **backlog_tasks** - Purple (#5856D6) - Tasks beyond current threshold +5. **done_tasks** - Green (#34C759) - Completed tasks +6. **archived_tasks** - Gray (#8E8E93) - Archived tasks + +## Customization + +### Filtering Categories + +Use the `visibleCategories` parameter to control which categories appear: + +```kotlin +// Show only active task categories +TaskSummaryCard( + taskSummary = taskSummary, + visibleCategories = listOf( + "overdue_tasks", + "current_tasks", + "in_progress_tasks", + "backlog_tasks" + ) +) +``` + +### Styling + +The component uses Material Design 3 theming. Customize via modifier: + +```kotlin +TaskSummaryCard( + taskSummary = taskSummary, + modifier = Modifier + .padding(horizontal = 16.dp) + .shadow(4.dp, RoundedCornerShape(12.dp)) +) +``` + +## iOS Implementation + +For iOS (SwiftUI), create a similar component: + +```swift +struct TaskSummaryCard: View { + let taskSummary: TaskSummary + let visibleCategories: [String]? + + var body: some View { + VStack(alignment: .leading, spacing: 12) { + Text("Tasks") + .font(.headline) + + ForEach(filteredCategories, id: \.name) { category in + TaskCategoryRow(category: category) + } + } + .padding() + .background(Color(.systemBackground)) + .cornerRadius(12) + .shadow(radius: 2) + } + + var filteredCategories: [TaskCategory] { + if let visible = visibleCategories { + return taskSummary.categories.filter { visible.contains($0.name) } + } + return taskSummary.categories + } +} + +struct TaskCategoryRow: View { + let category: TaskCategory + + var body: some View { + HStack { + // Use SF Symbol from category.icons.ios + Image(systemName: category.icons.ios) + .foregroundColor(Color(hex: category.color)) + + Text(category.displayName) + .font(.body) + + Spacer() + + Text("\(category.count)") + .font(.headline) + .foregroundColor(.white) + .padding(.horizontal, 12) + .padding(.vertical, 4) + .background(Color(hex: category.color)) + .cornerRadius(12) + } + .padding(12) + .background(Color(hex: category.color).opacity(0.1)) + .cornerRadius(8) + } +} +``` + +## Benefits + +✅ **Data-Driven**: All icons, colors, and names come from backend +✅ **Single Source of Truth**: Change values in `task/constants.py` to update everywhere +✅ **Flexible**: Easy to show different categories in different screens +✅ **Consistent**: Same categorization logic across all platforms +✅ **Maintainable**: No hardcoded UI values in app code + +## Migration from Old TaskSummary + +If you have existing code using the old TaskSummary structure: + +**Old:** +```kotlin +Text("Overdue: ${taskSummary.overdue}") +Text("Pending: ${taskSummary.pending}") +Text("Completed: ${taskSummary.completed}") +``` + +**New:** +```kotlin +TaskSummaryCard( + taskSummary = taskSummary, + visibleCategories = listOf("overdue_tasks", "current_tasks", "done_tasks") +) +``` + +Or access categories programmatically: +```kotlin +val overdueCount = taskSummary.categories + .find { it.name == "overdue_tasks" }?.count ?: 0 +``` diff --git a/composeApp/src/commonMain/kotlin/com/example/mycrib/network/ApiConfig.kt b/composeApp/src/commonMain/kotlin/com/example/mycrib/network/ApiConfig.kt index 93c2f1f..cfc83c4 100644 --- a/composeApp/src/commonMain/kotlin/com/example/mycrib/network/ApiConfig.kt +++ b/composeApp/src/commonMain/kotlin/com/example/mycrib/network/ApiConfig.kt @@ -9,7 +9,7 @@ package com.mycrib.shared.network */ object ApiConfig { // ⚠️ CHANGE THIS TO TOGGLE ENVIRONMENT ⚠️ - val CURRENT_ENV = Environment.DEV + val CURRENT_ENV = Environment.LOCAL enum class Environment { LOCAL, diff --git a/composeApp/src/commonMain/kotlin/com/example/mycrib/network/TaskApi.kt b/composeApp/src/commonMain/kotlin/com/example/mycrib/network/TaskApi.kt index b322145..824fd1f 100644 --- a/composeApp/src/commonMain/kotlin/com/example/mycrib/network/TaskApi.kt +++ b/composeApp/src/commonMain/kotlin/com/example/mycrib/network/TaskApi.kt @@ -11,12 +11,12 @@ class TaskApi(private val client: HttpClient = ApiClient.httpClient) { suspend fun getTasks( token: String, - days: Int = 30 + days: Int? = null ): ApiResult { return try { val response = client.get("$baseUrl/tasks/") { header("Authorization", "Token $token") - parameter("days", days) + days?.let { parameter("days", it) } } if (response.status.isSuccess()) { @@ -105,12 +105,12 @@ class TaskApi(private val client: HttpClient = ApiClient.httpClient) { suspend fun getTasksByResidence( token: String, residenceId: Int, - days: Int = 30 + days: Int? = null ): ApiResult { return try { val response = client.get("$baseUrl/tasks/by-residence/$residenceId/") { header("Authorization", "Token $token") - parameter("days", days) + days?.let { parameter("days", it) } } if (response.status.isSuccess()) { diff --git a/composeApp/src/commonMain/kotlin/com/example/mycrib/util/TaskConstants.kt b/composeApp/src/commonMain/kotlin/com/example/mycrib/util/TaskConstants.kt new file mode 100644 index 0000000..6916b02 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/com/example/mycrib/util/TaskConstants.kt @@ -0,0 +1,15 @@ +package com.mycrib.shared.util + +/** + * Constants used throughout the task features. + * + * This module contains configuration values that can be used across the app. + */ +object TaskConstants { + /** + * Task categorization threshold. + * Number of days from today to consider a task as "current" vs "backlog". + * This should match the API's TASK_CURRENT_THRESHOLD_DAYS value. + */ + const val TASK_CURRENT_THRESHOLD_DAYS = 29 +} diff --git a/iosApp/TASK_SUMMARY_USAGE_IOS.md b/iosApp/TASK_SUMMARY_USAGE_IOS.md new file mode 100644 index 0000000..3a78705 --- /dev/null +++ b/iosApp/TASK_SUMMARY_USAGE_IOS.md @@ -0,0 +1,254 @@ +# Task Summary Card - iOS Usage Guide + +## Overview + +The `TaskSummaryCard` SwiftUI component displays task statistics dynamically based on backend data. All metadata (icons, colors, display names) comes from the API. + +## Files Created + +1. **`Components/TaskSummaryCard.swift`** - Main component + +**Note:** The component uses the existing `Color(hex:)` initializer from `Design/DesignSystem.swift`, so no additional files are needed. + +## Basic Usage + +### Example 1: Show All Categories + +In `ResidenceDetailView.swift` or `ResidencesListView.swift`: + +```swift +import SwiftUI +import Shared + +struct ResidenceDetailView: View { + let residence: ResidenceSummary + + var body: some View { + ScrollView { + VStack(spacing: 16) { + // Residence info... + + // Task summary with all categories + TaskSummaryCard(taskSummary: residence.taskSummary) + .padding(.horizontal) + } + } + } +} +``` + +### Example 2: Show Only Specific Categories + +For the main residence list, show only key categories: + +```swift +struct ResidenceCard: View { + let residence: ResidenceSummary + + var body: some View { + VStack(alignment: .leading, spacing: 12) { + Text(residence.name) + .font(.title2) + .fontWeight(.bold) + + Text(residence.streetAddress) + .font(.subheadline) + .foregroundColor(.secondary) + + // Only show overdue, current, and in progress + TaskSummaryCard( + taskSummary: residence.taskSummary, + visibleCategories: [ + "overdue_tasks", + "current_tasks", + "in_progress_tasks" + ] + ) + } + .padding() + .background(Color(.systemBackground)) + .cornerRadius(12) + .shadow(radius: 2) + } +} +``` + +### Example 3: In Home Screen + +```swift +struct HomeScreenView: View { + @StateObject private var viewModel = ResidenceViewModel() + + var body: some View { + ScrollView { + VStack(spacing: 20) { + ForEach(viewModel.residences, id: \.id) { residence in + VStack(alignment: .leading, spacing: 12) { + NavigationLink(destination: ResidenceDetailView(residence: residence)) { + VStack(alignment: .leading) { + Text(residence.name) + .font(.headline) + + // Show task summary for quick overview + TaskSummaryCard( + taskSummary: residence.taskSummary, + visibleCategories: ["overdue_tasks", "current_tasks"] + ) + } + } + } + .padding() + } + } + } + } +} +``` + +## Available Categories + +The backend provides these categories (can filter with `visibleCategories`): + +1. **overdue_tasks** - Red (#FF3B30) - `exclamationmark.triangle` +2. **current_tasks** - Blue (#007AFF) - `calendar` +3. **in_progress_tasks** - Orange (#FF9500) - `play.circle` +4. **backlog_tasks** - Purple (#5856D6) - `tray` +5. **done_tasks** - Green (#34C759) - `checkmark.circle` +6. **archived_tasks** - Gray (#8E8E93) - `archivebox` + +## Customization + +### Custom Styling + +```swift +TaskSummaryCard(taskSummary: taskSummary) + .padding() + .background(Color(.secondarySystemBackground)) + .cornerRadius(16) + .shadow(color: Color.black.opacity(0.2), radius: 8, x: 0, y: 4) +``` + +### Filter Specific Categories + +```swift +// Show only active tasks +TaskSummaryCard( + taskSummary: taskSummary, + visibleCategories: [ + "overdue_tasks", + "current_tasks", + "in_progress_tasks", + "backlog_tasks" + ] +) +``` + +### Access Individual Category Counts + +If you need to access counts programmatically: + +```swift +extension TaskSummary { + func categoryCount(for name: String) -> Int { + categories.first { $0.name == name }?.count ?? 0 + } +} + +// Usage: +let overdueCount = taskSummary.categoryCount(for: "overdue_tasks") +``` + +## Component Features + +✅ **Dynamic Icons** - Uses SF Symbols from backend (e.g., `calendar`, `exclamationmark.triangle`) +✅ **Dynamic Colors** - Parses hex colors from backend +✅ **Flexible Filtering** - Show only categories you need +✅ **Live Preview** - Includes SwiftUI preview with mock data +✅ **Responsive** - Adapts to different screen sizes +✅ **Native Look** - Uses iOS design patterns and typography + +## Preview + +The component includes a preview for development. In Xcode: + +1. Open `Components/TaskSummaryCard.swift` +2. Click "Resume" in the preview canvas +3. See both "all categories" and "filtered" versions + +## Integration Example + +Here's a complete example for ResidenceDetailView: + +```swift +import SwiftUI +import Shared + +struct ResidenceDetailView: View { + let residence: ResidenceSummary + @State private var showingManageUsers = false + @State private var showingTasks = false + + var body: some View { + ScrollView { + VStack(alignment: .leading, spacing: 20) { + // Header + VStack(alignment: .leading, spacing: 8) { + Text(residence.name) + .font(.title) + .fontWeight(.bold) + + Text(residence.streetAddress) + .font(.subheadline) + .foregroundColor(.secondary) + } + .padding(.horizontal) + + // Task Summary Card + TaskSummaryCard( + taskSummary: residence.taskSummary, + visibleCategories: [ + "overdue_tasks", + "current_tasks", + "in_progress_tasks" + ] + ) + .padding(.horizontal) + .onTapGesture { + showingTasks = true + } + + // Other residence details... + } + .padding(.vertical) + } + .navigationTitle("Residence Details") + .navigationBarTitleDisplayMode(.inline) + .sheet(isPresented: $showingTasks) { + TasksView(residenceId: residence.id) + } + } +} +``` + +## Benefits + +✅ **Backend-Driven** - All icons, colors, names from API +✅ **Single Source of Truth** - Change in `task/constants.py` updates everywhere +✅ **Type-Safe** - Full Swift type safety with Shared models +✅ **Reusable** - Use in any view that has TaskSummary +✅ **Consistent** - Matches Android implementation +✅ **Maintainable** - No hardcoded values + +## Troubleshooting + +### Colors not showing correctly +Make sure `Extensions/Color+Hex.swift` is included in your target. + +### Icons not appearing +The component uses SF Symbols. Ensure the icon names from backend are valid SF Symbol names. + +### Type errors +Make sure you're importing `Shared` module which contains the TaskSummary models. + +### Preview not working +The preview uses mock data. If you get compiler errors, make sure all model types are properly imported.