import SwiftUI struct MyTasksScreen: View { @Environment(\.dismiss) var dismiss @State private var selectedFilter = "active" @State private var tasksByFilter: [String: [WorkTask]] = [ "active": [], "today": [], "week": [], "completed": [] ] @State private var loadingByFilter: [String: Bool] = [ "active": true, "today": true, "week": true, "completed": true ] @State private var errorByFilter: [String: String?] = [ "active": nil, "today": nil, "week": nil, "completed": nil ] private let filters = [ FilterTab(value: "active", label: "Active", icon: "play.fill"), FilterTab(value: "today", label: "Today", icon: "calendar"), FilterTab(value: "week", label: "This Week", icon: "calendar.badge.clock"), FilterTab(value: "completed", label: "Done", icon: "checkmark.circle.fill"), ] private let refreshTimer = Timer.publish(every: 2, on: .main, in: .common).autoconnect() var body: some View { VStack(spacing: 0) { // Tab bar HStack(spacing: 0) { ForEach(filters, id: \.value) { filter in Button { selectedFilter = filter.value loadTasks(filter.value) } label: { VStack(spacing: 4) { Image(systemName: filter.icon) .font(.caption) Text(filter.label) .font(.caption2) .lineLimit(1) .minimumScaleFactor(0.8) } .frame(maxWidth: .infinity) .padding(.vertical, 10) .foregroundColor(selectedFilter == filter.value ? .white : .white.opacity(0.7)) } } } .background(Color.payfritGreen) // Indicator GeometryReader { geo in let idx = filters.firstIndex(where: { $0.value == selectedFilter }) ?? 0 let width = geo.size.width / CGFloat(filters.count) Rectangle() .fill(Color.white) .frame(width: width, height: 3) .offset(x: width * CGFloat(idx)) .animation(.easeInOut(duration: 0.2), value: selectedFilter) } .frame(height: 3) .background(Color.payfritGreen) // Content TabView(selection: $selectedFilter) { ForEach(filters, id: \.value) { filter in taskListView(filter.value) .tag(filter.value) } } .tabViewStyle(.page(indexDisplayMode: .never)) } .navigationTitle("My Tasks") .toolbarBackground(Color.payfritGreen, for: .navigationBar) .toolbarBackground(.visible, for: .navigationBar) .toolbarColorScheme(.dark, for: .navigationBar) .toolbar { ToolbarItem(placement: .navigationBarTrailing) { Button { loadTasks(selectedFilter) } label: { Image(systemName: "arrow.clockwise") .foregroundColor(.white) } } } .task { loadTasks("active") } .onReceive(refreshTimer) { _ in loadTasks(selectedFilter, silent: true) } } // MARK: - Task List per Filter @ViewBuilder private func taskListView(_ filterType: String) -> some View { let isLoading = loadingByFilter[filterType] ?? true let error = errorByFilter[filterType] ?? nil let tasks = tasksByFilter[filterType] ?? [] if isLoading { ProgressView() } else if let error = error { VStack(spacing: 16) { Image(systemName: "exclamationmark.circle") .font(.system(size: 48)) .foregroundColor(.red) Text(error) .multilineTextAlignment(.center) Button("Retry") { loadTasks(filterType) } .buttonStyle(.borderedProminent) } .padding() } else if tasks.isEmpty { emptyView(filterType) } else { List(tasks) { task in let isCompleted = filterType == "completed" NavigationLink { TaskDetailScreen(task: task, showCompleteButton: !isCompleted) } label: { taskCard(task, isCompleted: isCompleted) } } .listStyle(.plain) .refreshable { loadTasks(filterType) } } } private func taskCard(_ task: WorkTask, isCompleted: Bool) -> some View { HStack { Rectangle() .fill(isCompleted ? Color.green : task.color) .frame(width: 4) .cornerRadius(2) if isCompleted { Image(systemName: "checkmark.circle.fill") .foregroundColor(.green) } VStack(alignment: .leading, spacing: 4) { Text(task.title) .font(.callout.weight(.semibold)) .strikethrough(isCompleted) .foregroundColor(isCompleted ? .secondary : .primary) if !task.locationDisplay.isEmpty { HStack(spacing: 4) { Image(systemName: task.deliveryAddress.isEmpty ? "mappin.circle" : "bicycle") .font(.caption2) .foregroundColor(task.color) Text(task.locationDisplay) .font(.caption) .foregroundColor(.secondary) .lineLimit(1) } } HStack(spacing: 8) { Text(task.categoryName) .font(.caption2) .fontWeight(.medium) .foregroundColor(task.color) .padding(.horizontal, 6) .padding(.vertical, 2) .background(task.color.opacity(0.2)) .cornerRadius(8) Text(task.timeAgo) .font(.caption2) .foregroundColor(.secondary) } } Spacer() if task.isChat { Image(systemName: "bubble.left.fill") .foregroundColor(task.color) .font(.footnote) } if !isCompleted { Text(task.statusName) .font(.caption2) .fontWeight(.medium) .foregroundColor(statusColor(task.statusId)) .padding(.horizontal, 8) .padding(.vertical, 4) .background(statusColor(task.statusId).opacity(0.1)) .cornerRadius(8) } if task.isChat { Image(systemName: "bubble.right.fill") .foregroundColor(task.color) .font(.body) } else { Image(systemName: "chevron.right") .foregroundColor(.secondary) .font(.caption) } } .padding(.vertical, 4) } // MARK: - Empty @ViewBuilder private func emptyView(_ filterType: String) -> some View { VStack(spacing: 16) { Image(systemName: emptyIcon(filterType)) .font(.system(size: 64)) .foregroundColor(.secondary) Text(emptyMessage(filterType)) .font(.title3) .foregroundColor(.secondary) Text(emptySubMessage(filterType)) .foregroundColor(.secondary) .multilineTextAlignment(.center) } .padding() } private func emptyIcon(_ f: String) -> String { switch f { case "active": return "doc.text" case "today": return "calendar" case "week": return "calendar.badge.clock" case "completed": return "checkmark.circle" default: return "tray" } } private func emptyMessage(_ f: String) -> String { switch f { case "active": return "No active tasks" case "today": return "No tasks today" case "week": return "No tasks this week" case "completed": return "No completed tasks" default: return "No tasks" } } private func emptySubMessage(_ f: String) -> String { switch f { case "active": return "Claim some tasks to get started!" case "today": return "You haven't worked on any tasks today" case "week": return "You haven't worked on any tasks this week" case "completed": return "Complete tasks to see them here" default: return "" } } // MARK: - Actions private func loadTasks(_ filterType: String, silent: Bool = false) { if !silent { loadingByFilter[filterType] = true errorByFilter[filterType] = nil } Task { do { let tasks = try await APIService.shared.listMyTasks(filterType: filterType) tasksByFilter[filterType] = tasks loadingByFilter[filterType] = false } catch { if !silent { errorByFilter[filterType] = error.localizedDescription loadingByFilter[filterType] = false } } } } private func statusColor(_ id: Int) -> Color { switch id { case 0: return .payfritGreen case 1: return .payfritGreen case 2: return .purple case 3: return .green default: return .gray } } } private struct FilterTab { let value: String let label: String let icon: String }