'Please Select', 1 => 'Economy', 2 => 'Business', 3 => 'First', 4 => 'Premium Economy', 5 => 'Private', ]; const array SEAT_TYPES = [ 0 => 'Please Select', 1 => 'Window', 2 => 'Middle', 3 => 'Aisle', ]; const array FLIGHT_REASONS = [ 0 => 'Please Select', 1 => 'Pleasure', 2 => 'Business', 3 => 'Crew', 4 => 'Other', ]; private function formatTime(?string $time): string { if (!$time) return '00:00'; [$hours, $minutes] = explode(':', $time); return str_pad($hours, 2, '0', STR_PAD_LEFT) . ':' . $minutes; } public function getPossibleAircraft(string $aircraftQuery) { preg_match('/\((\w+)\)/', $aircraftQuery, $matches); $designator = $matches[1] ?? null; if(!$designator){ $aircraft = []; } else { $aircraft = Aircraft::when($designator, fn($query) => $query->where('designator', 'ilike', $designator)) ->orderBy('model_full_name') ->limit(10) ->get(['id', 'manufacturer_code', 'model_full_name', 'designator']) ->map(fn($a) => [ 'value' => $a->id, 'title' => "{$a->manufacturer_code} {$a->model_full_name} ({$a->designator})", ]) ->values() ->toArray(); } return $aircraft; } public function getPossibleAirports(string $airportQuery) { preg_match('/\((\w{3})\/(\w{4})\)/', $airportQuery, $matches); $iata = $matches[1] ?? null; $icao = $matches[2] ?? null; if (!$iata && !$icao) { return []; } $airports = Airport::with('region.country') ->where(function ($q) use ($iata, $icao) { $q->where('iata_code', 'ilike', $iata) ->orWhere('icao_code', 'ilike', $icao); }) ->orderByRaw(" CASE WHEN iata_code = ? AND icao_code = ? THEN 0 WHEN iata_code = ? THEN 1 WHEN icao_code = ? THEN 2 ELSE 3 END ", [$iata, $icao, $iata, $icao]) ->limit(10) ->get(['id', 'name', 'municipality', 'iata_code', 'icao_code', 'region_id']) ->map(fn($a) => [ 'value' => $a->id, 'title' => "{$a->name} ({$a->iata_code}/{$a->icao_code})", 'country_code' => strtolower($a?->region->country->code ?? ''), ]) ->values() ->toArray(); return $airports; } public function getPossibleAirlines(string $airlineQuery) { preg_match('/\((\w{2,3})\/(\w{3,4})\)/', $airlineQuery, $matches); $iata = $matches[1] ?? null; $icao = $matches[2] ?? null; $airlines = Airline::when($iata || $icao, function ($query) use ($iata, $icao) { $query->orderByRaw(" CASE WHEN \"IATA_code\" = ? AND \"ICAO_code\" = ? THEN 0 WHEN \"IATA_code\" = ? THEN 1 WHEN \"ICAO_code\" = ? THEN 2 ELSE 3 END ", [$iata, $icao, $iata, $icao]) ->where(function ($q) use ($iata, $icao) { $q->where('IATA_code', $iata) ->orWhere('ICAO_code', $icao); }); }) ->orderByDesc('active') ->limit(10) ->get(['id', 'name', 'IATA_code', 'ICAO_code']) ->map(fn($a) => [ 'value' => $a->id, 'title' => "{$a->name} ({$a->IATA_code}/{$a->ICAO_code})", ]) ->values() ->toArray(); return $airlines; } public function reconcile(Request $request) { $user = Auth::user(); $flightToReconcile = ImportedFlight::where('user_id', $user->id)->orderBy('id')->first(); if (!$flightToReconcile) { return null; } $date = null; if ($flightToReconcile->date) { $date = Carbon::createFromFormat('m-d-y', $flightToReconcile->date)->format('Y-m-d'); } return [ 'flight_classes' => collect(self::FLIGHT_CLASSES)->map(fn($title, $value) => [ 'value' => $value, 'title' => $title, ])->values(), 'flight_reasons' => collect(self::FLIGHT_REASONS)->map(fn($title, $value) => [ 'value' => $value, 'title' => $title, ])->values(), 'seat_types' => collect(self::SEAT_TYPES)->map(fn($title, $value) => [ 'value' => $value, 'title' => $title, ])->values(), 'flight_number' => $flightToReconcile->flight_number ?? '', 'date' => $date ?? '', 'dep_time' => $this->formatTime($flightToReconcile->dep_time), 'arr_time' => $this->formatTime($flightToReconcile->arr_time), 'duration' => $this->formatTime($flightToReconcile->duration), 'registration' => $flightToReconcile->registration ?? '', 'note' => $flightToReconcile->note ?? '', 'seat_number' => $flightToReconcile->seat_number ?? '', 'flight_class' => $flightToReconcile->flight_class ?? '', 'seat_type' => $flightToReconcile->seat_type ?? '', 'flight_reason' => $flightToReconcile->flight_reason ?? '', 'airline_options' => $this->getPossibleAirlines($flightToReconcile->airline ?? ''), 'to_options' => $this->getPossibleAirports($flightToReconcile->to ?? ''), 'from_options' => $this->getPossibleAirports($flightToReconcile->from ?? ''), 'aircraft_options' => $this->getPossibleAircraft($flightToReconcile->aircraft ?? ''), ]; } public function save(Request $request){ $validated = $request->validate([ 'date' => 'required|date', 'flight_number' => 'required|string', 'from_id' => 'required|integer|exists:airports,id', 'to_id' => 'required|integer|exists:airports,id', 'dep_time' => 'nullable|date_format:H:i', 'arr_time' => 'nullable|date_format:H:i', 'duration' => 'nullable|date_format:H:i', 'airline_id' => 'nullable|integer|exists:airlines,id', 'aircraft_id' => 'nullable|integer|exists:aircraft,id', 'registration' => 'nullable|string', 'seat_number' => 'nullable|string', 'seat_type_id' => 'nullable|integer', 'flight_class_id' => 'nullable|integer', 'flight_reason_id' => 'nullable|integer', 'note' => 'nullable|string', ],[ 'from_id.required' => 'Please select a departure airport.', 'to_id.required' => 'Please select an arrival airport.', 'dep_time.date_format' => 'Departure time must be in HH:MM format, i.e: 01:25', 'arr_time.date_format' => 'Arrival time must be in HH:MM format, i.e: 12:25', 'duration.date_format' => 'Must be in HH:MM format, e.g: 03:37', ]); } public function store(Request $request) { try { $request->validate([ 'csv' => ['required', 'file', 'mimes:csv,txt', 'max:10240'], ]); $path = $request->file('csv')->getRealPath(); $handle = fopen($path, 'r'); fgetcsv($handle); // Read and normalise header row $headers = array_map( fn($h) => strtolower(trim(str_replace(' ', '_', $h))), fgetcsv($handle) ); \Log::debug('CSV headers', $headers); $firstRow = fgetcsv($handle); \Log::debug('First row', $firstRow ?? ['empty']); \Log::debug('Header count: ' . count($headers) . ', Row count: ' . count($firstRow ?? [])); $map = [ 'date' => 'date', 'flight_number' => 'flight_number', 'from' => 'from', 'to' => 'to', 'dep_time' => 'dep_time', 'arr_time' => 'arr_time', 'duration' => 'duration', 'airline' => 'airline', 'aircraft' => 'aircraft', 'registration' => 'registration', 'seat_number' => 'seat_number', 'seat_type' => 'seat_type', 'flight_class' => 'flight_class', 'flight_reason' => 'flight_reason', 'note' => 'note', ]; $userId = Auth::id(); $imported = 0; while (($row = fgetcsv($handle)) !== false) { if (count($row) !== count($headers)) { continue; // skip malformed rows } $raw = array_combine($headers, $row); $data = ['user_id' => $userId]; foreach ($map as $csvKey => $column) { $data[$column] = $raw[$csvKey] ?? null; } ImportedFlight::create($data); $imported++; } fclose($handle); return response()->json(['imported' => $imported]); } catch (\Exception $e) { return response()->json(['message' => $e->getMessage()], 500); } } }