Implement fully dynamic task summary UI from API data

Updated both iOS and Android to build residence task summary UI entirely
from API response data, with no hardcoded categories, icons, colors, or labels.

**Changes:**

**Backend Integration:**
- Updated TaskSummary model to use dynamic categories list instead of static fields
- Added TaskColumnCategory and TaskColumnIcon models for metadata
- Categories now include: name, displayName, icons (ios/android/web), color, count

**Android (ResidencesScreen.kt):**
- Removed hardcoded category extraction (overdue_tasks, current_tasks, in_progress_tasks)
- Now dynamically iterates over first 3 categories from API
- Added getIconForCategory() helper to map icon names to Material Icons
- Added parseHexColor() helper that works in commonMain (no Android-specific code)
- Uses category.displayName, category.icons.android, category.color from API

**iOS (ResidenceCard.swift):**
- Removed hardcoded category extraction and SF Symbol names
- Now dynamically iterates over first 3 categories using ForEach
- Uses category.displayName, category.icons.ios, category.color from API
- Leverages existing Color(hex:) extension for color parsing

**Component Organization:**
- Moved TaskSummaryCard.kt from commonMain to androidMain (uses Android-specific APIs)
- Created TaskSummaryCard.swift for iOS with dynamic category rendering

**Benefits:**
 Backend controls all category metadata (icons, colors, display names)
 Apps automatically reflect backend changes without redeployment
 No platform-specific hardcoded values
 Single source of truth in task/constants.py TASK_COLUMNS

**Files Changed:**
- Residence.kt: Added TaskColumnCategory, TaskColumnIcon models
- ResidencesScreen.kt: Dynamic category rendering with helpers
- ResidenceCard.swift: Dynamic category rendering with ForEach
- TaskSummaryCard.kt: Moved to androidMain
- TaskSummaryCard.swift: New iOS dynamic component

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

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Trey t
2025-11-15 11:13:37 -06:00
parent 7f6f77d95a
commit bb5664c954
5 changed files with 406 additions and 46 deletions

View File

@@ -0,0 +1,135 @@
package com.mycrib.android.ui.components
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.*
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import com.mycrib.shared.models.TaskSummary
import com.mycrib.shared.models.TaskColumnCategory
/**
* Displays a task summary with dynamic categories from the backend.
*
* The backend provides icons, colors, and counts for each category,
* allowing the UI to be built entirely from API data.
*
* @param taskSummary The task summary data from the API
* @param modifier Optional modifier for the card
* @param visibleCategories Optional list of category names to show (default: show all)
*/
@Composable
fun TaskSummaryCard(
taskSummary: TaskSummary,
modifier: Modifier = Modifier,
visibleCategories: List<String>? = null
) {
Card(
modifier = modifier.fillMaxWidth(),
elevation = CardDefaults.cardElevation(defaultElevation = 2.dp)
) {
Column(
modifier = Modifier
.fillMaxWidth()
.padding(16.dp)
) {
Text(
text = "Tasks",
style = MaterialTheme.typography.titleMedium,
fontWeight = FontWeight.Bold
)
Spacer(modifier = Modifier.height(12.dp))
// Filter categories if visibleCategories is provided
val categories = if (visibleCategories != null) {
taskSummary.categories.filter { it.name in visibleCategories }
} else {
taskSummary.categories
}
// Display each category
categories.forEach { category ->
TaskCategoryItem(category = category)
Spacer(modifier = Modifier.height(8.dp))
}
}
}
}
/**
* Displays a single task category with icon, name, and count
*/
@Composable
private fun TaskCategoryItem(
category: TaskColumnCategory,
modifier: Modifier = Modifier
) {
Row(
modifier = modifier
.fillMaxWidth()
.background(
color = parseHexColor(category.color).copy(alpha = 0.1f),
shape = RoundedCornerShape(8.dp)
)
.padding(12.dp),
verticalAlignment = Alignment.CenterVertically
) {
// Icon placeholder (using colored circle for now, can be replaced with actual icons)
Box(
modifier = Modifier
.size(32.dp)
.background(
color = parseHexColor(category.color),
shape = RoundedCornerShape(16.dp)
),
contentAlignment = Alignment.Center
) {
Text(
text = category.displayName.first().toString(),
color = Color.White,
style = MaterialTheme.typography.labelLarge,
fontWeight = FontWeight.Bold
)
}
Spacer(modifier = Modifier.width(12.dp))
// Category name
Text(
text = category.displayName,
style = MaterialTheme.typography.bodyLarge,
modifier = Modifier.weight(1f)
)
// Count badge
Surface(
color = parseHexColor(category.color),
shape = RoundedCornerShape(12.dp)
) {
Text(
text = category.count.toString(),
color = Color.White,
style = MaterialTheme.typography.labelLarge,
fontWeight = FontWeight.Bold,
modifier = Modifier.padding(horizontal = 12.dp, vertical = 4.dp)
)
}
}
}
/**
* Parse hex color string to Compose Color
*/
private fun parseHexColor(hex: String): Color {
return try {
Color(android.graphics.Color.parseColor(hex))
} catch (e: Exception) {
Color.Gray // Fallback color
}
}