166 lines
6.9 KiB
Swift
166 lines
6.9 KiB
Swift
import SwiftUI
|
|
import CoreData
|
|
|
|
struct FuelLogListView: View {
|
|
@Environment(\.managedObjectContext) private var viewContext
|
|
|
|
// All fuel logs (sorted by date descending)
|
|
@FetchRequest(
|
|
sortDescriptors: [NSSortDescriptor(keyPath: \FuelLog.date, ascending: false)],
|
|
animation: .default)
|
|
private var fuelLogs: FetchedResults<FuelLog>
|
|
|
|
// All vehicles (sorted by make)
|
|
@FetchRequest(
|
|
sortDescriptors: [NSSortDescriptor(keyPath: \Vehicle.make, ascending: true)],
|
|
animation: .default)
|
|
private var vehicles: FetchedResults<Vehicle>
|
|
|
|
@State private var showingAddFuelLog = false
|
|
@State private var selectedVehicleID: UUID? = nil
|
|
@State private var showAddVehicleSheet = false
|
|
|
|
// For tracking the previous odometer reading
|
|
@State private var previousOdometer: Double? = nil
|
|
@State private var showOdometerAlert = false
|
|
@State private var isUpdatingCalculation = false
|
|
|
|
// Computed property for formatted previous odometer value
|
|
private var previousOdometerString: String {
|
|
previousOdometer.map { String(format: "%.0f", $0) } ?? "N/A"
|
|
}
|
|
|
|
// Filter fuel logs by the selected vehicle.
|
|
private var filteredFuelLogs: [FuelLog] {
|
|
if let vehicleID = selectedVehicleID {
|
|
return fuelLogs.filter { log in
|
|
if let logVehicleID = log.vehicle?.id {
|
|
return logVehicleID == vehicleID
|
|
}
|
|
return false
|
|
}
|
|
} else {
|
|
return Array(fuelLogs)
|
|
}
|
|
}
|
|
|
|
var body: some View {
|
|
NavigationView {
|
|
if vehicles.isEmpty {
|
|
// Empty state: no vehicles exist.
|
|
VStack(spacing: 20) {
|
|
Text("No vehicles found.")
|
|
.font(.headline)
|
|
Text("Please add a vehicle before logging fuel records.")
|
|
.multilineTextAlignment(.center)
|
|
.padding(.horizontal)
|
|
Button("Add Vehicle") {
|
|
showAddVehicleSheet = true
|
|
}
|
|
.padding()
|
|
.background(Color.blue)
|
|
.foregroundColor(.white)
|
|
.cornerRadius(8)
|
|
}
|
|
.navigationTitle("Fuel Logs")
|
|
.sheet(isPresented: $showAddVehicleSheet) {
|
|
AddVehicleView().environment(\.managedObjectContext, viewContext)
|
|
}
|
|
} else {
|
|
List {
|
|
// Vehicle Picker Section
|
|
Section {
|
|
Picker("Vehicle", selection: $selectedVehicleID) {
|
|
ForEach(vehicles, id: \.objectID) { vehicle in
|
|
// Only tag if vehicle.id is not nil.
|
|
if let vid = vehicle.id {
|
|
Text("\(vehicle.year) \(vehicle.make ?? "") \(vehicle.model ?? "")")
|
|
.tag(vid as UUID?)
|
|
} else {
|
|
// If id is nil, you can still show the row but tag with nil.
|
|
Text("\(vehicle.year) \(vehicle.make ?? "") \(vehicle.model ?? "")")
|
|
.tag(nil as UUID?)
|
|
}
|
|
}
|
|
}
|
|
.pickerStyle(MenuPickerStyle())
|
|
}
|
|
|
|
// Fuel Logs Section (filtered by selected vehicle)
|
|
ForEach(filteredFuelLogs.indices, id: \.self) { index in
|
|
let log = filteredFuelLogs[index]
|
|
let distance: Double? = {
|
|
guard index < filteredFuelLogs.count - 1 else { return nil }
|
|
let previousLog = filteredFuelLogs[index + 1]
|
|
let milesDriven = log.odometer - previousLog.odometer
|
|
return milesDriven > 0 ? milesDriven : nil
|
|
}()
|
|
let mpg: Double? = {
|
|
guard index < filteredFuelLogs.count - 1 else { return nil }
|
|
let previousLog = filteredFuelLogs[index + 1]
|
|
let milesDriven = log.odometer - previousLog.odometer
|
|
guard log.fuelVolume > 0, milesDriven > 0 else { return nil }
|
|
return milesDriven / log.fuelVolume
|
|
}()
|
|
|
|
NavigationLink(destination: FuelLogDetailView(fuelLog: log)) {
|
|
FuelLogSummaryView(log: log, mpg: mpg, distanceSincePrevious: distance)
|
|
}
|
|
}
|
|
.onDelete(perform: deleteFuelLogs)
|
|
}
|
|
.listStyle(InsetGroupedListStyle())
|
|
.navigationTitle("Fuel Logs")
|
|
.toolbar {
|
|
ToolbarItem(placement: .navigationBarTrailing) {
|
|
Button {
|
|
showingAddFuelLog = true
|
|
} label: {
|
|
Label("Add Fuel Log", systemImage: "plus")
|
|
}
|
|
}
|
|
ToolbarItem(placement: .navigationBarLeading) {
|
|
EditButton()
|
|
}
|
|
}
|
|
.sheet(isPresented: $showingAddFuelLog) {
|
|
AddFuelLogView().environment(\.managedObjectContext, viewContext)
|
|
}
|
|
.onAppear {
|
|
print("Total fuel logs: \(fuelLogs.count)")
|
|
setDefaultVehicle()
|
|
}
|
|
}
|
|
}
|
|
.alert(isPresented: $showOdometerAlert) {
|
|
Alert(title: Text("Odometer Reading Error"),
|
|
message: Text("Odometer reading must be greater than the previous record (\(previousOdometerString))."),
|
|
dismissButton: .default(Text("OK")))
|
|
}
|
|
}
|
|
|
|
// Set default vehicle selection on appear.
|
|
private func setDefaultVehicle() {
|
|
if selectedVehicleID == nil {
|
|
if let lastFuelLog = fuelLogs.first, let vehicle = lastFuelLog.vehicle {
|
|
selectedVehicleID = vehicle.id
|
|
} else {
|
|
selectedVehicleID = vehicles.first?.id
|
|
}
|
|
}
|
|
}
|
|
|
|
private func deleteFuelLogs(offsets: IndexSet) {
|
|
withAnimation {
|
|
let logsToDelete = offsets.map { filteredFuelLogs[$0] }
|
|
logsToDelete.forEach { viewContext.delete($0) }
|
|
do {
|
|
try viewContext.save()
|
|
} catch {
|
|
let nsError = error as NSError
|
|
fatalError("Unresolved error \(nsError), \(nsError.userInfo)")
|
|
}
|
|
}
|
|
}
|
|
}
|