New framework: - AccessibilityLabels.swift: centralized A11y struct with VoiceOver strings - AccessibilityModifiers.swift: reusable .a11yHeader, .a11yDecorative, .a11yButton, .a11yCard, .a11yStatValue View extensions Shared components: decorative elements hidden, stat views combined, status/priority badges labeled, error views announced, empty states grouped Cards: ResidenceCard, TaskCard, DynamicTaskCard, ContractorCard, DocumentCard, WarrantyCard — all grouped with combined labels, chevrons hidden, action buttons labeled Main screens: Login, Register, Residences, Tasks, Contractors, Documents — toolbar buttons labeled, section headers marked, form field hints added Onboarding: all 10 views — header traits, button hints, task selection state, progress indicator, decorative backgrounds hidden Profile/Subscription: toggle hints, theme selection state, feature comparison table accessibility, subscription button labels iOS build verified: BUILD SUCCEEDED
166 lines
5.1 KiB
Swift
166 lines
5.1 KiB
Swift
//
|
|
// TaskSummaryCard.swift
|
|
// iosApp
|
|
//
|
|
// Displays a dynamic task summary with categories from the backend.
|
|
// The backend provides icons, colors, and counts, making the UI fully data-driven.
|
|
//
|
|
|
|
import SwiftUI
|
|
import ComposeApp
|
|
|
|
/// Displays a task summary with dynamic categories from the backend
|
|
struct TaskSummaryCard: View {
|
|
let taskSummary: ResidenceTaskSummary
|
|
var visibleCategories: [String]? = nil
|
|
|
|
private var filteredCategories: [TaskCategorySummary] {
|
|
if let visible = visibleCategories {
|
|
return taskSummary.categories.filter { visible.contains($0.name) }
|
|
}
|
|
return taskSummary.categories
|
|
}
|
|
|
|
var body: some View {
|
|
VStack(alignment: .leading, spacing: 12) {
|
|
Text("Tasks")
|
|
.font(.headline)
|
|
.fontWeight(.bold)
|
|
.foregroundColor(Color.appTextPrimary)
|
|
.accessibilityAddTraits(.isHeader)
|
|
|
|
ForEach(filteredCategories, id: \.name) { category in
|
|
TaskCategoryRow(category: category)
|
|
}
|
|
}
|
|
.padding(16)
|
|
.background(Color.appBackgroundSecondary)
|
|
.cornerRadius(12)
|
|
.shadow(color: Color.black.opacity(0.1), radius: 4, x: 0, y: 2)
|
|
}
|
|
}
|
|
|
|
/// Displays a single task category with icon, name, and count
|
|
struct TaskCategoryRow: View {
|
|
let category: TaskCategorySummary
|
|
|
|
private var categoryColor: Color {
|
|
Color(hex: category.color) ?? .gray
|
|
}
|
|
|
|
var body: some View {
|
|
HStack(spacing: 12) {
|
|
// Icon with colored background
|
|
ZStack {
|
|
Circle()
|
|
.fill(categoryColor)
|
|
.frame(width: 32, height: 32)
|
|
|
|
Image(systemName: category.icons.ios)
|
|
.foregroundColor(Color.appTextOnPrimary)
|
|
.font(.system(size: 14, weight: .semibold))
|
|
}
|
|
|
|
// Category name
|
|
Text(category.displayName)
|
|
.font(.body)
|
|
.foregroundColor(Color.appTextPrimary)
|
|
|
|
Spacer()
|
|
|
|
// Count badge
|
|
Text("\(category.count)")
|
|
.font(.subheadline)
|
|
.fontWeight(.bold)
|
|
.foregroundColor(Color.appTextOnPrimary)
|
|
.padding(.horizontal, 12)
|
|
.padding(.vertical, 4)
|
|
.background(categoryColor)
|
|
.cornerRadius(12)
|
|
}
|
|
.padding(12)
|
|
.background(categoryColor.opacity(0.1))
|
|
.cornerRadius(8)
|
|
.accessibilityElement(children: .combine)
|
|
}
|
|
}
|
|
|
|
// MARK: - Preview
|
|
|
|
#if DEBUG
|
|
struct TaskSummaryCard_Previews: PreviewProvider {
|
|
static var previews: some View {
|
|
VStack(spacing: 20) {
|
|
// Preview with all categories
|
|
TaskSummaryCard(taskSummary: mockTaskSummary)
|
|
.padding()
|
|
|
|
// Preview with filtered categories
|
|
TaskSummaryCard(
|
|
taskSummary: mockTaskSummary,
|
|
visibleCategories: ["overdue_tasks", "current_tasks", "in_progress_tasks"]
|
|
)
|
|
.padding()
|
|
}
|
|
.background(Color(.systemGroupedBackground))
|
|
}
|
|
|
|
static var mockTaskSummary: ResidenceTaskSummary {
|
|
ResidenceTaskSummary(
|
|
categories: [
|
|
TaskCategorySummary(
|
|
name: "overdue_tasks",
|
|
displayName: "Overdue",
|
|
icons: TaskCategoryIcons(
|
|
android: "Warning",
|
|
ios: "exclamationmark.triangle"
|
|
),
|
|
color: "#FF3B30",
|
|
count: 3
|
|
),
|
|
TaskCategorySummary(
|
|
name: "current_tasks",
|
|
displayName: "Current",
|
|
icons: TaskCategoryIcons(
|
|
android: "CalendarToday",
|
|
ios: "calendar"
|
|
),
|
|
color: "#007AFF",
|
|
count: 8
|
|
),
|
|
TaskCategorySummary(
|
|
name: "in_progress_tasks",
|
|
displayName: "In Progress",
|
|
icons: TaskCategoryIcons(
|
|
android: "PlayCircle",
|
|
ios: "play.circle"
|
|
),
|
|
color: "#FF9500",
|
|
count: 2
|
|
),
|
|
TaskCategorySummary(
|
|
name: "backlog_tasks",
|
|
displayName: "Backlog",
|
|
icons: TaskCategoryIcons(
|
|
android: "Inbox",
|
|
ios: "tray"
|
|
),
|
|
color: "#5856D6",
|
|
count: 7
|
|
),
|
|
TaskCategorySummary(
|
|
name: "done_tasks",
|
|
displayName: "Done",
|
|
icons: TaskCategoryIcons(
|
|
android: "CheckCircle",
|
|
ios: "checkmark.circle"
|
|
),
|
|
color: "#34C759",
|
|
count: 5
|
|
)
|
|
]
|
|
)
|
|
}
|
|
}
|
|
#endif
|