Files
PlantGuide/PlantGuide/Presentation/Scenes/TodayView/TodayView.swift
treyt 064d73ba03 fix: resolve issue #13 - Today tab
Automated fix by Tony CI.
Closes #13

Co-Authored-By: Claude <noreply@anthropic.com>
2026-02-16 22:33:31 -06:00

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()
}