API Integration: - Fix logHabit: send "date" instead of "completed_at" - Fix FinanceCategory: "icon" → "emoji" to match API - Fix task priorities: remove level 4, keep 1-3 matching API - Fix habit frequencies: map monthly/interval → "custom" for API - Add token refresh (401 → auto retry with new token) - Add proper error handling (remove try? in save functions, show errors in UI) - Add date field to savings transactions - Add MonthlyPaymentDetail and OverduePayment models - Fix habit completedToday: compute on client from logs (API doesn't return it) - Filter habits by day of week on client (daily/weekly/monthly/interval) Design System (glassmorphism): - New DesignSystem.swift: Theme colors, GlassCard modifier, GlowIcon, GlowStatCard - Custom tab bar with per-tab glow colors (VStack layout, not ZStack overlay) - Deep dark background #06060f across all views - Glass cards with gradient fill + stroke throughout app - App icon: glassmorphism style with teal glow Health Dashboard: - Compact ReadinessBanner with recommendation text - 8 metric tiles: sleep, HR, HRV, steps, SpO2, respiratory rate, energy, distance - Each tile with status indicator (good/ok/bad) and hint text - Heart rate card (min/avg/max) - Weekly trends card (averages) - Recovery score (weighted: 40% sleep, 35% HRV, 25% RHR) - Tips card with actionable recommendations - Sleep detail view with hypnogram (step chart of phases) - Sleep segments timeline from HealthKit (deep/rem/core/awake with exact times) - Line chart replacing bar chart for weekly data - Collect respiratory_rate and sleep phases with timestamps from HealthKit - Background sync every ~30min via BGProcessingTask Notifications: - NotificationService for local push notifications - Morning/evening reminders with native DatePicker (wheel) - Payment reminders: 5 days, 1 day, and day-of for recurring savings - Notification settings in Settings tab UI Fixes: - Fix color picker overflow: HStack → LazyVGrid 5 columns - Fix sheet headers: shorter text, proper padding - Fix task/habit toggle: separate tap zones (checkbox vs edit) - Fix deprecated onChange syntax for iOS 17+ - Savings overview: real monthly payments and detailed overdues from API - Settings: timezone as Menu picker, removed Telegram/server notifications sections - All sheets use .presentationDetents([.large]) Config: - project.yml: real DEVELOPMENT_TEAM, HealthKit + BackgroundModes capabilities - Info.plist: BGTaskScheduler + UIBackgroundModes - Assets.xcassets with AppIcon - CLAUDE.md project documentation Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
99 lines
3.8 KiB
Swift
99 lines
3.8 KiB
Swift
import SwiftUI
|
||
|
||
struct TasksView: View {
|
||
@EnvironmentObject var authManager: AuthManager
|
||
@State private var tasks: [PulseTask] = []
|
||
@State private var isLoading = true
|
||
@State private var showAddTask = false
|
||
@State private var filter: TaskFilter = .pending
|
||
|
||
enum TaskFilter: String, CaseIterable {
|
||
case pending = "Активные"
|
||
case completed = "Выполненные"
|
||
case all = "Все"
|
||
}
|
||
|
||
var filteredTasks: [PulseTask] {
|
||
switch filter {
|
||
case .pending: return tasks.filter { !$0.completed }
|
||
case .completed: return tasks.filter { $0.completed }
|
||
case .all: return tasks
|
||
}
|
||
}
|
||
|
||
var body: some View {
|
||
ZStack {
|
||
Color(hex: "06060f").ignoresSafeArea()
|
||
VStack(spacing: 0) {
|
||
// Header
|
||
HStack {
|
||
Text("Задачи").font(.title.bold()).foregroundColor(.white)
|
||
Spacer()
|
||
Button(action: { showAddTask = true }) {
|
||
Image(systemName: "plus.circle.fill").font(.title2).foregroundColor(Color(hex: "00d4aa"))
|
||
}
|
||
}.padding()
|
||
|
||
// Filter
|
||
Picker("", selection: $filter) {
|
||
ForEach(TaskFilter.allCases, id: \.self) { f in Text(f.rawValue).tag(f) }
|
||
}
|
||
.pickerStyle(.segmented)
|
||
.padding(.horizontal)
|
||
.padding(.bottom, 8)
|
||
|
||
if isLoading {
|
||
ProgressView().tint(Color(hex: "00d4aa")).padding(.top, 40)
|
||
Spacer()
|
||
} else if filteredTasks.isEmpty {
|
||
VStack(spacing: 12) {
|
||
Text("✅").font(.system(size: 50))
|
||
Text(filter == .pending ? "Нет активных задач" : "Нет задач")
|
||
.foregroundColor(Color(hex: "8888aa"))
|
||
}.padding(.top, 60)
|
||
Spacer()
|
||
} else {
|
||
List {
|
||
ForEach(filteredTasks) { task in
|
||
TaskRowView(task: task) { await completeTask(task) }
|
||
.listRowBackground(Color.clear)
|
||
.listRowSeparator(.hidden)
|
||
}
|
||
.onDelete { indices in
|
||
let tasksToDelete = indices.map { filteredTasks[$0] }
|
||
Task {
|
||
for task in tasksToDelete {
|
||
try? await APIService.shared.deleteTask(token: authManager.token, id: task.id)
|
||
}
|
||
await loadTasks()
|
||
}
|
||
}
|
||
}
|
||
.listStyle(.plain)
|
||
.scrollContentBackground(.hidden)
|
||
}
|
||
}
|
||
}
|
||
.sheet(isPresented: $showAddTask) {
|
||
AddTaskView(isPresented: $showAddTask) { await loadTasks() }
|
||
.presentationDetents([.large])
|
||
.presentationDragIndicator(.visible)
|
||
.presentationBackground(Color(hex: "06060f"))
|
||
}
|
||
.task { await loadTasks() }
|
||
.refreshable { await loadTasks(refresh: true) }
|
||
}
|
||
|
||
func loadTasks(refresh: Bool = false) async {
|
||
if !refresh { isLoading = true }
|
||
tasks = (try? await APIService.shared.getTasks(token: authManager.token)) ?? []
|
||
isLoading = false
|
||
}
|
||
|
||
func completeTask(_ task: PulseTask) async {
|
||
try? await APIService.shared.completeTask(token: authManager.token, id: task.id)
|
||
await loadTasks()
|
||
UIImpactFeedbackGenerator(style: .light).impactOccurred()
|
||
}
|
||
}
|