169 lines
7.0 KiB
Swift
169 lines
7.0 KiB
Swift
import SwiftUI
|
||
import UniformTypeIdentifiers
|
||
import CoreData
|
||
|
||
struct FuelLogImporterView: View {
|
||
@Environment(\.managedObjectContext) private var viewContext
|
||
@Environment(\.dismiss) var dismiss
|
||
|
||
// Pass in the selected vehicle from FuelLogListView.
|
||
var selectedVehicle: Vehicle?
|
||
|
||
@State private var isFileImporterPresented = false
|
||
@State private var importError: String? = nil
|
||
|
||
var body: some View {
|
||
VStack(spacing: 20) {
|
||
Text("Import Fuel Logs from CSV")
|
||
.font(.headline)
|
||
Button("Select CSV File") {
|
||
isFileImporterPresented = true
|
||
}
|
||
.padding()
|
||
.background(Color.blue)
|
||
.foregroundColor(.white)
|
||
.cornerRadius(8)
|
||
|
||
if let error = importError {
|
||
Text("Error: \(error)")
|
||
.foregroundColor(.red)
|
||
}
|
||
}
|
||
.fileImporter(
|
||
isPresented: $isFileImporterPresented,
|
||
allowedContentTypes: [UTType.commaSeparatedText],
|
||
allowsMultipleSelection: false
|
||
) { result in
|
||
switch result {
|
||
case .success(let urls):
|
||
if let url = urls.first {
|
||
// Request access to the file URL.
|
||
guard url.startAccessingSecurityScopedResource() else {
|
||
importError = "Permission error: Unable to access the selected file."
|
||
return
|
||
}
|
||
defer { url.stopAccessingSecurityScopedResource() }
|
||
importCSV(from: url)
|
||
}
|
||
case .failure(let error):
|
||
importError = error.localizedDescription
|
||
}
|
||
}
|
||
.navigationTitle("Import Fuel Logs")
|
||
}
|
||
|
||
private func importCSV(from url: URL) {
|
||
do {
|
||
let data = try Data(contentsOf: url)
|
||
guard let csvString = String(data: data, encoding: .utf8) else {
|
||
importError = "Could not decode file."
|
||
return
|
||
}
|
||
// Assume the first line is a header.
|
||
let lines = csvString.components(separatedBy: "\n").filter { !$0.isEmpty }
|
||
guard lines.count > 1 else {
|
||
importError = "CSV file appears empty."
|
||
return
|
||
}
|
||
// For this example, assume the CSV columns are:
|
||
// date,odometer,fuelVolume,cost,locationCoordinates,locationName,octane,pricePerGalon,fullTank
|
||
let header = lines[0].components(separatedBy: ",")
|
||
for line in lines.dropFirst() {
|
||
let fields = line.components(separatedBy: ",")
|
||
if fields.count < header.count {
|
||
print("Skipping row: \(line) – not enough fields (found \(fields.count), expected \(header.count))")
|
||
continue
|
||
}
|
||
|
||
// Trim whitespace for each field
|
||
let dateStr = fields[0].trimmingCharacters(in: .whitespacesAndNewlines)
|
||
let odometerStr = fields[1].trimmingCharacters(in: .whitespacesAndNewlines)
|
||
let fuelVolumeStr = fields[2].trimmingCharacters(in: .whitespacesAndNewlines)
|
||
let costStr = fields[3].trimmingCharacters(in: .whitespacesAndNewlines)
|
||
let locationCoordinates = fields[4].trimmingCharacters(in: .whitespacesAndNewlines)
|
||
let locationName = fields[5].trimmingCharacters(in: .whitespacesAndNewlines)
|
||
let octaneStr = fields[6].trimmingCharacters(in: .whitespacesAndNewlines)
|
||
let pricePerGalonStr = fields[7].trimmingCharacters(in: .whitespacesAndNewlines)
|
||
let fullTankStr = fields[8].trimmingCharacters(in: .whitespacesAndNewlines)
|
||
|
||
// Debug prints for each field
|
||
print("Row: \(line)")
|
||
print("Date: \(dateStr), Odometer: \(odometerStr), Fuel Volume: \(fuelVolumeStr), Cost: \(costStr), Octane: \(octaneStr), Price/Gal: \(pricePerGalonStr), FullTank: \(fullTankStr)")
|
||
|
||
// Setup date formatter – adjust format as needed.
|
||
var parsedDate: Date?
|
||
let formats = ["M/d/yyyy", "yyyy-MM-dd HH:mm"]
|
||
for format in formats {
|
||
let formatter = DateFormatter()
|
||
formatter.dateFormat = format
|
||
formatter.locale = Locale(identifier: "en_US_POSIX")
|
||
if let date = formatter.date(from: dateStr) {
|
||
parsedDate = date
|
||
break
|
||
}
|
||
}
|
||
guard let date = parsedDate else {
|
||
print("Skipping row – invalid date: \(dateStr)")
|
||
continue
|
||
}
|
||
|
||
guard let odometer = Double(odometerStr) else {
|
||
print("Skipping row – invalid odometer: \(odometerStr)")
|
||
continue
|
||
}
|
||
|
||
guard let fuelVolume = Double(fuelVolumeStr) else {
|
||
print("Skipping row – invalid fuel volume: \(fuelVolumeStr)")
|
||
continue
|
||
}
|
||
|
||
guard let cost = Double(costStr) else {
|
||
print("Skipping row – invalid cost: \(costStr)")
|
||
continue
|
||
}
|
||
|
||
guard let octane = Int16(octaneStr) else {
|
||
print("Skipping row – invalid octane: \(octaneStr)")
|
||
continue
|
||
}
|
||
|
||
guard let pricePerGalon = Double(pricePerGalonStr) else {
|
||
print("Skipping row – invalid price per gallon: \(pricePerGalonStr)")
|
||
continue
|
||
}
|
||
|
||
// If all conversions succeed, create the fuel log.
|
||
let fullTank = (fullTankStr.lowercased() == "true" || fullTankStr == "1")
|
||
|
||
let newLog = FuelLog(context: viewContext)
|
||
newLog.id = UUID()
|
||
newLog.date = date
|
||
newLog.odometer = odometer
|
||
newLog.fuelVolume = fuelVolume
|
||
newLog.cost = cost
|
||
newLog.locationCoordinates = locationCoordinates
|
||
newLog.locationName = locationName
|
||
newLog.octane = octane
|
||
newLog.pricePerGalon = pricePerGalon
|
||
newLog.fullTank = fullTank
|
||
|
||
// Optionally assign vehicle if needed.
|
||
print("Assigning vehicle: \(selectedVehicle?.make ?? "none")")
|
||
newLog.vehicle = selectedVehicle
|
||
|
||
print("Imported row successfully.")
|
||
}
|
||
try viewContext.save()
|
||
dismiss()
|
||
} catch {
|
||
importError = error.localizedDescription
|
||
}
|
||
}
|
||
}
|
||
|
||
struct FuelLogImporterView_Previews: PreviewProvider {
|
||
static var previews: some View {
|
||
FuelLogImporterView(selectedVehicle: nil)
|
||
}
|
||
}
|