payfrit-works-ios/PayfritWorks/Views/TaskListScreen.swift
2026-02-01 23:38:34 -08:00

219 lines
7.3 KiB
Swift

import SwiftUI
struct TaskListScreen: View {
var businessName: String = ""
@EnvironmentObject var appState: AppState
@State private var tasks: [WorkTask] = []
@State private var isLoading = true
@State private var error: String?
@State private var lastRefresh = Date()
@State private var selectedTask: WorkTask?
@State private var showingMyTasks = false
private let refreshTimer = Timer.publish(every: 2, on: .main, in: .common).autoconnect()
var body: some View {
ZStack(alignment: .bottomTrailing) {
Group {
if isLoading {
ProgressView()
} else if let error = error {
errorView(error)
} else if tasks.isEmpty {
emptyView
} else {
taskListView
}
}
.frame(maxWidth: .infinity, maxHeight: .infinity)
Button { showingMyTasks = true } label: {
Image(systemName: "checkmark.circle.fill")
.font(.title3)
.padding(12)
.background(Color.payfritGreen)
.foregroundColor(.white)
.clipShape(Circle())
.shadow(radius: 4)
}
.padding(.trailing, 16)
.padding(.bottom, 16)
}
.navigationTitle(businessName.isEmpty ? "Available Tasks" : businessName)
.toolbar {
ToolbarItem(placement: .navigationBarTrailing) {
Button { loadTasks() } label: {
Image(systemName: "arrow.clockwise")
}
}
ToolbarItem(placement: .navigationBarTrailing) {
Menu {
Label(appState.userName ?? "Worker", systemImage: "person.circle")
.disabled(true)
Divider()
Button { showingMyTasks = true } label: {
Label("My Tasks", systemImage: "checkmark.circle")
}
Button(role: .destructive) { logout() } label: {
Label("Logout", systemImage: "rectangle.portrait.and.arrow.right")
}
} label: {
Image(systemName: "ellipsis.circle")
}
}
}
.navigationDestination(isPresented: $showingMyTasks) {
MyTasksScreen()
}
.task { loadTasks() }
.onReceive(refreshTimer) { _ in loadTasks(silent: true) }
}
// MARK: - List
private var taskListView: some View {
List(tasks) { task in
NavigationLink {
TaskDetailScreen(task: task, showAcceptButton: true)
} label: {
taskRow(task)
}
}
.listStyle(.plain)
.refreshable { loadTasks() }
}
private func taskRow(_ task: WorkTask) -> some View {
HStack(spacing: 12) {
// Left color bar
Rectangle()
.fill(task.color)
.frame(width: 4)
.cornerRadius(2)
// Chat icon or category icon
ZStack {
RoundedRectangle(cornerRadius: 10)
.fill(task.isChat ? Color.payfritGreen.opacity(0.15) : task.color.opacity(0.15))
.frame(width: 40, height: 40)
Image(systemName: task.isChat ? "bubble.left.and.bubble.right.fill" : "doc.text.fill")
.foregroundColor(task.isChat ? .payfritGreen : task.color)
.font(.subheadline)
}
VStack(alignment: .leading, spacing: 4) {
Text(task.title)
.font(.callout.weight(.semibold))
if !task.locationDisplay.isEmpty {
HStack(spacing: 4) {
Image(systemName: task.deliveryAddress.isEmpty ? "mappin.circle" : "bicycle")
.font(.caption2)
.foregroundColor(.payfritGreen)
Text(task.locationDisplay)
.font(.caption)
.foregroundColor(.secondary)
.lineLimit(1)
}
} else if !task.details.isEmpty {
Text(task.details)
.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)
if task.isChat {
Text("Chat")
.font(.caption2)
.fontWeight(.medium)
.foregroundColor(.payfritGreen)
.padding(.horizontal, 6)
.padding(.vertical, 2)
.background(Color.payfritGreen.opacity(0.15))
.cornerRadius(8)
}
Text(task.timeAgo)
.font(.caption2)
.foregroundColor(.secondary)
}
}
Spacer()
Image(systemName: task.isChat ? "arrow.right" : "chevron.right")
.foregroundColor(.secondary)
.font(.caption)
}
.padding(.vertical, 4)
}
// MARK: - Empty / Error
private var emptyView: some View {
VStack(spacing: 16) {
Image(systemName: "checkmark.circle")
.font(.system(size: 64))
.foregroundColor(.secondary)
Text("No pending tasks")
.font(.title3)
.foregroundColor(.secondary)
Text("Check back soon!")
.foregroundColor(.secondary)
}
}
private func errorView(_ message: String) -> some View {
VStack(spacing: 16) {
Image(systemName: "exclamationmark.circle")
.font(.system(size: 48))
.foregroundColor(.red)
Text(message)
.multilineTextAlignment(.center)
Button("Retry") { loadTasks() }
.buttonStyle(.borderedProminent)
}
.padding()
}
// MARK: - Actions
private func loadTasks(silent: Bool = false) {
if !silent {
isLoading = true
error = nil
}
Task {
do {
let result = try await APIService.shared.listPendingTasks()
tasks = result
isLoading = false
lastRefresh = Date()
} catch {
if !silent {
self.error = error.localizedDescription
isLoading = false
}
}
}
}
private func logout() {
Task {
await APIService.shared.logout()
await AuthStorage.shared.clearAuth()
appState.clearAuth()
}
}
}