Refactor iOS forms and integrate notification API with APILayer

- Refactored ContractorFormSheet to follow SwiftUI best practices
  - Moved Field enum outside struct and renamed to ContractorFormField
  - Extracted body into computed properties for better readability
  - Replaced deprecated NavigationView with NavigationStack
  - Fixed input field contrast in light mode by adding borders
  - Fixed force cast in loadContractorSpecialties

- Refactored TaskFormView to eliminate screen flickering
  - Moved Field enum outside struct and renamed to TaskFormField
  - Fixed conditional view structure that caused flicker on load
  - Used ZStack with overlay instead of if/else for loading state
  - Changed to .task modifier for proper async initialization
  - Made loadLookups properly async and fixed force casts
  - Replaced deprecated NavigationView with NavigationStack

- Integrated PushNotificationManager with APILayer
  - Updated registerDeviceWithBackend to use APILayer.shared.registerDevice()
  - Updated updateNotificationPreferences to use APILayer
  - Updated getNotificationPreferences to use APILayer
  - Added proper error handling with try-catch pattern

- Added notification operations to APILayer
  - Added NotificationApi instance
  - Implemented registerDevice, unregisterDevice
  - Implemented getNotificationPreferences, updateNotificationPreferences
  - Implemented getNotificationHistory, markNotificationAsRead
  - Implemented markAllNotificationsAsRead, getUnreadCount
  - All methods follow consistent pattern with auth token handling

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Trey t
2025-11-13 13:12:54 -06:00
parent 230eb013dd
commit 2b95c3b9c1
4 changed files with 458 additions and 349 deletions

View File

@@ -1,12 +1,18 @@
import SwiftUI
import ComposeApp
// MARK: - Field Focus Enum
enum TaskFormField {
case title, description, intervalDays, estimatedCost
}
// MARK: - Task Form View
struct TaskFormView: View {
let residenceId: Int32?
let residences: [Residence]?
@Binding var isPresented: Bool
@StateObject private var viewModel = TaskViewModel()
@FocusState private var focusedField: Field?
@FocusState private var focusedField: TaskFormField?
private var needsResidenceSelection: Bool {
residenceId == nil
@@ -17,7 +23,7 @@ struct TaskFormView: View {
@State private var taskFrequencies: [TaskFrequency] = []
@State private var taskPriorities: [TaskPriority] = []
@State private var taskStatuses: [TaskStatus] = []
@State private var isLoadingLookups: Bool = false
@State private var isLoadingLookups: Bool = true
// Form fields
@State private var selectedResidence: Residence?
@@ -35,19 +41,9 @@ struct TaskFormView: View {
@State private var titleError: String = ""
@State private var residenceError: String = ""
enum Field {
case title, description, intervalDays, estimatedCost
}
var body: some View {
NavigationView {
if isLoadingLookups {
VStack(spacing: 16) {
ProgressView()
Text("Loading...")
.foregroundColor(.secondary)
}
} else {
NavigationStack {
ZStack {
Form {
// Residence Picker (only if needed)
if needsResidenceSelection, let residences = residences {
@@ -138,49 +134,68 @@ struct TaskFormView: View {
}
}
}
.navigationTitle("Add Task")
.navigationBarTitleDisplayMode(.inline)
.toolbar {
ToolbarItem(placement: .navigationBarLeading) {
Button("Cancel") {
isPresented = false
}
}
.disabled(isLoadingLookups)
.blur(radius: isLoadingLookups ? 3 : 0)
ToolbarItem(placement: .navigationBarTrailing) {
Button("Save") {
submitForm()
}
.disabled(viewModel.isLoading)
if isLoadingLookups {
VStack(spacing: 16) {
ProgressView()
.scaleEffect(1.5)
Text("Loading...")
.foregroundColor(.secondary)
}
.frame(maxWidth: .infinity, maxHeight: .infinity)
.background(Color(uiColor: .systemBackground).opacity(0.8))
}
.onAppear {
loadLookups()
}
.onChange(of: viewModel.taskCreated) { created in
if created {
}
.navigationTitle("Add Task")
.navigationBarTitleDisplayMode(.inline)
.toolbar {
ToolbarItem(placement: .navigationBarLeading) {
Button("Cancel") {
isPresented = false
}
.disabled(isLoadingLookups)
}
ToolbarItem(placement: .navigationBarTrailing) {
Button("Save") {
submitForm()
}
.disabled(viewModel.isLoading || isLoadingLookups)
}
}
.task {
await loadLookups()
}
.onChange(of: viewModel.taskCreated) { created in
if created {
isPresented = false
}
}
}
}
private func loadLookups() {
Task {
isLoadingLookups = true
// Load all lookups from DataCache
await MainActor.run {
self.taskCategories = DataCache.shared.taskCategories.value as! [TaskCategory]
self.taskFrequencies = DataCache.shared.taskFrequencies.value as! [TaskFrequency]
self.taskPriorities = DataCache.shared.taskPriorities.value as! [TaskPriority]
self.taskStatuses = DataCache.shared.taskStatuses.value as! [TaskStatus]
self.isLoadingLookups = false
}
setDefaults()
private func loadLookups() async {
// Load all lookups from DataCache
if let categories = DataCache.shared.taskCategories.value as? [TaskCategory] {
taskCategories = categories
}
if let frequencies = DataCache.shared.taskFrequencies.value as? [TaskFrequency] {
taskFrequencies = frequencies
}
if let priorities = DataCache.shared.taskPriorities.value as? [TaskPriority] {
taskPriorities = priorities
}
if let statuses = DataCache.shared.taskStatuses.value as? [TaskStatus] {
taskStatuses = statuses
}
setDefaults()
isLoadingLookups = false
}
private func setDefaults() {