175 lines
5.2 KiB
Swift
175 lines
5.2 KiB
Swift
//
|
|
// TodayView.swift
|
|
// PlantGuide
|
|
//
|
|
// Created for PlantGuide plant identification app.
|
|
//
|
|
|
|
import SwiftUI
|
|
|
|
// MARK: - TodayView
|
|
|
|
/// Main Today View dashboard showing overdue tasks and today's tasks grouped by room.
|
|
/// This view replaces the Care tab as the primary task management interface.
|
|
@MainActor
|
|
struct TodayView: View {
|
|
// MARK: - Properties
|
|
|
|
@State private var viewModel: TodayViewModel
|
|
|
|
// MARK: - Initialization
|
|
|
|
init() {
|
|
_viewModel = State(initialValue: DIContainer.shared.makeTodayViewModel())
|
|
}
|
|
|
|
// MARK: - Computed Properties
|
|
|
|
/// Rooms sorted by their sort order for consistent display
|
|
private var sortedRooms: [Room] {
|
|
viewModel.todayTasksByRoom.keys.sorted { $0.sortOrder < $1.sortOrder }
|
|
}
|
|
|
|
// MARK: - Body
|
|
|
|
var body: some View {
|
|
NavigationStack {
|
|
Group {
|
|
if viewModel.isLoading {
|
|
ProgressView("Loading tasks...")
|
|
} else {
|
|
taskContent
|
|
}
|
|
}
|
|
.navigationTitle(viewModel.greeting)
|
|
.toolbar {
|
|
ToolbarItem(placement: .topBarTrailing) {
|
|
Button(viewModel.isEditMode ? "Done" : "Edit") {
|
|
viewModel.toggleEditMode()
|
|
}
|
|
}
|
|
}
|
|
.overlay {
|
|
emptyStateOverlay
|
|
}
|
|
.overlay(alignment: .bottom) {
|
|
if viewModel.isEditMode && viewModel.hasSelection {
|
|
BatchActionBar(
|
|
selectedCount: viewModel.selectedCount,
|
|
onComplete: {
|
|
Task { await viewModel.batchCompleteSelected() }
|
|
},
|
|
onCancel: {
|
|
viewModel.clearSelection()
|
|
}
|
|
)
|
|
.padding()
|
|
}
|
|
}
|
|
.alert("Complete \(viewModel.selectedCount) tasks?", isPresented: $viewModel.showBatchConfirmation) {
|
|
Button("Complete All", role: .destructive) {
|
|
Task { await viewModel.performBatchComplete() }
|
|
}
|
|
Button("Cancel", role: .cancel) {}
|
|
} message: {
|
|
Text("This will mark all selected tasks as completed.")
|
|
}
|
|
.task {
|
|
await viewModel.loadTasks()
|
|
}
|
|
.onAppear {
|
|
Task { await viewModel.loadTasks() }
|
|
}
|
|
.refreshable {
|
|
await viewModel.loadTasks()
|
|
}
|
|
}
|
|
}
|
|
|
|
// MARK: - Task Content
|
|
|
|
@ViewBuilder
|
|
private var taskContent: some View {
|
|
ScrollView {
|
|
VStack(spacing: 20) {
|
|
// Greeting + Stats
|
|
QuickStatsBar(
|
|
completedCount: viewModel.completedTodayCount,
|
|
totalCount: viewModel.totalTodayCount
|
|
)
|
|
|
|
// Overdue section (if any)
|
|
if !viewModel.overdueTasks.isEmpty {
|
|
TaskSection(
|
|
title: "OVERDUE",
|
|
tasks: viewModel.overdueTasks,
|
|
isOverdue: true,
|
|
plantName: viewModel.plantName,
|
|
onComplete: { task in
|
|
Task { await viewModel.markComplete(task) }
|
|
}
|
|
)
|
|
}
|
|
|
|
// Today's tasks grouped by room
|
|
if !viewModel.todayTasks.isEmpty {
|
|
todayTasksSection
|
|
}
|
|
}
|
|
.padding()
|
|
}
|
|
}
|
|
|
|
// MARK: - Today Tasks Section
|
|
|
|
@ViewBuilder
|
|
private var todayTasksSection: some View {
|
|
VStack(alignment: .leading, spacing: 16) {
|
|
// Section header
|
|
Text("TODAY")
|
|
.font(.subheadline)
|
|
.fontWeight(.semibold)
|
|
.foregroundStyle(.secondary)
|
|
.padding(.horizontal, 4)
|
|
|
|
// Tasks grouped by room
|
|
ForEach(sortedRooms, id: \.id) { room in
|
|
if let tasks = viewModel.todayTasksByRoom[room] {
|
|
RoomTaskGroup(
|
|
room: room,
|
|
tasks: tasks,
|
|
plantName: viewModel.plantName,
|
|
isEditMode: viewModel.isEditMode,
|
|
selectedTaskIDs: viewModel.selectedTaskIDs,
|
|
onComplete: { task in
|
|
Task { await viewModel.markComplete(task) }
|
|
},
|
|
onToggleSelection: { taskID in
|
|
viewModel.toggleSelection(for: taskID)
|
|
}
|
|
)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// MARK: - Empty State
|
|
|
|
@ViewBuilder
|
|
private var emptyStateOverlay: some View {
|
|
if viewModel.allTasksEmpty && !viewModel.isLoading {
|
|
ContentUnavailableView(
|
|
"All caught up!",
|
|
systemImage: "checkmark.circle",
|
|
description: Text("You have no tasks for today")
|
|
)
|
|
}
|
|
}
|
|
}
|
|
|
|
// MARK: - Preview
|
|
|
|
#Preview {
|
|
TodayView()
|
|
}
|