import React, { useRef, useState, useEffect } from 'react'; import { WizardState } from '../types'; import { UploadCloud, FileText, X, Image as ImageIcon, Sparkles, Loader2, MapPin, Navigation, Plus, Trash2, Flag, Target, AlertCircle, CheckCircle2, Car, Footprints } from 'lucide-react'; import { processFile } from '../utils/fileUtils'; // --- HELPER COMPONENT: PLACE AUTOCOMPLETE INPUT (WIDGET VERSION) --- interface PlaceAutocompleteInputProps { value: string; onChange: (val: string, preview?: string) => void; placeholder: string; icon: React.ReactNode; scriptLoaded: boolean; disabled?: boolean; onError?: (msg: string) => void; addressPreview?: string; } const PlaceAutocompleteInput: React.FC = ({ value, onChange, placeholder, icon, scriptLoaded, disabled, onError, addressPreview }) => { const inputRef = useRef(null); const autocompleteRef = useRef(null); // Initialize Google Autocomplete Widget useEffect(() => { if (!scriptLoaded || !inputRef.current || !(window as any).google || autocompleteRef.current) return; try { const google = (window as any).google; // Use the standard Autocomplete widget attached to the input const autocomplete = new google.maps.places.Autocomplete(inputRef.current, { fields: ["place_id", "geometry", "name", "formatted_address"], types: ["geocode", "establishment"] }); autocompleteRef.current = autocomplete; autocomplete.addListener("place_changed", () => { const place = autocomplete.getPlace(); if (!place.geometry) { return; } // FIX: Use formatted_address as fallback if name is empty/missing const name = place.name || place.formatted_address || ""; const address = place.formatted_address; // Update parent state onChange(name, address); }); } catch (e) { console.error("Autocomplete init error", e); if(onError) onError("Błąd inicjalizacji widgetu Google Maps."); } }, [scriptLoaded, onError, onChange]); return (
{icon}
onChange(e.target.value)} disabled={disabled} className="w-full pl-9 p-3 border border-gray-300 rounded-md focus:border-[#EA4420] outline-none font-medium disabled:bg-gray-100 disabled:text-gray-400 transition-colors" placeholder={placeholder} autoComplete="off" /> {/* Address Confirmation Hint */} {addressPreview && (
{addressPreview}
)}
); }; // --- MAIN COMPONENT --- interface StepDetailsProps { data: WizardState; // Update Type Definition to allow functional updates updateData: (updates: Partial | ((prev: WizardState) => Partial)) => void; onGenerate: () => void; isGenerating: boolean; } const StepDetails: React.FC = ({ data, updateData, onGenerate, isGenerating }) => { const fileInputRef = useRef(null); const [error, setError] = useState(null); // Specific Error State const [mapError, setMapError] = useState<{title: string, msg: string} | null>(null); const [scriptLoaded, setScriptLoaded] = useState(false); // --- HARDCODED FALLBACK KEY --- const AUTO_PASTE_KEY = 'AIzaSyAq9IgZswt5j7GGfH2s-ESenHmfvWFCFCg'; const getEffectiveKey = () => { if (data.tripData?.googleMapsKey) return data.tripData.googleMapsKey; // @ts-ignore if (import.meta.env && import.meta.env.VITE_GOOGLE_MAPS_KEY) return import.meta.env.VITE_GOOGLE_MAPS_KEY; if (process.env.GOOGLE_MAPS_KEY) return process.env.GOOGLE_MAPS_KEY; return AUTO_PASTE_KEY; }; const effectiveKey = getEffectiveKey(); const isEnvKeyMissing = !process.env.GOOGLE_MAPS_KEY && // @ts-ignore !import.meta.env?.VITE_GOOGLE_MAPS_KEY && data.tripData?.googleMapsKey !== AUTO_PASTE_KEY; // --- GOOGLE MAPS LOADING --- const loadMapsScript = (apiKey: string) => { if (!apiKey) { setMapError({ title: "Brak klucza API", msg: "System nie mógł znaleźć klucza. Skontaktuj się z administratorem." }); return; } if ((window as any).google?.maps?.places) { setScriptLoaded(true); return; } const existingScript = document.querySelector(`script[src*="maps.googleapis.com/maps/api/js"]`); if (existingScript) { const interval = setInterval(() => { if ((window as any).google?.maps?.places) { setScriptLoaded(true); setMapError(null); clearInterval(interval); } }, 500); return; } const script = document.createElement('script'); script.src = `https://maps.googleapis.com/maps/api/js?key=${apiKey}&libraries=places&loading=async&v=weekly`; script.async = true; script.onload = () => { setTimeout(() => { if ((window as any).google?.maps?.places) { setScriptLoaded(true); setMapError(null); } }, 200); }; script.onerror = () => { setMapError({ title: "Błąd sieci", msg: "Nie udało się pobrać skryptu Google Maps." }); }; document.head.appendChild(script); }; useEffect(() => { if (data.eventType === 'trip') { (window as any).gm_authFailure = () => { setMapError({ title: "Klucz odrzucony przez Google", msg: "Podany klucz jest niepoprawny." }); setScriptLoaded(false); }; if (effectiveKey) loadMapsScript(effectiveKey); } }, [data.eventType, effectiveKey]); // Initialize Trip Data if missing useEffect(() => { if (data.eventType === 'trip') { if (!data.tripData) { updateData({ tripData: { startPoint: { place: '', description: '' }, endPoint: { place: '', description: '' }, stops: [{ id: crypto.randomUUID(), place: '', description: '' }], travelMode: null, googleMapsKey: AUTO_PASTE_KEY } }); } else if (!data.tripData.googleMapsKey) { updateData(prev => ({ tripData: { ...prev.tripData!, googleMapsKey: AUTO_PASTE_KEY } })); } } }, [data.eventType, updateData]); // Removed data.tripData dependency to avoid loops, handled by logic const handleFileChange = async (e: React.ChangeEvent) => { const newFiles = Array.from(e.target.files || []); if (newFiles.length === 0) return; if (data.files.length + newFiles.length > 3) { setError('Maksymalnie 3 pliki.'); return; } setError(null); const processedFiles = await Promise.all(newFiles.map(processFile)); updateData(prev => ({ files: [...prev.files, ...processedFiles] })); }; const removeFile = (id: string) => { updateData(prev => ({ files: prev.files.filter(f => f.id !== id) })); }; // --- TRIP DATA HELPERS (UPDATED TO USE FUNCTIONAL STATE UPDATES) --- const updateApiKey = (val: string) => { updateData(prev => ({ tripData: prev.tripData ? { ...prev.tripData, googleMapsKey: val } : prev.tripData })); if (val.length > 10) setMapError(null); }; const updatePoint = (pointType: 'startPoint' | 'endPoint', field: 'place' | 'description' | 'addressPreview', value: string) => { updateData(prev => { if (!prev.tripData) return {}; return { tripData: { ...prev.tripData, [pointType]: { ...prev.tripData[pointType], [field]: value } } }; }); }; const updateStop = (id: string, field: 'place' | 'description' | 'addressPreview', value: string) => { updateData(prev => { if (!prev.tripData) return {}; const newStops = prev.tripData.stops.map(s => s.id === id ? { ...s, [field]: value } : s); return { tripData: { ...prev.tripData, stops: newStops } }; }); }; const addStop = () => { updateData(prev => { if (!prev.tripData) return {}; return { tripData: { ...prev.tripData, stops: [...prev.tripData.stops, { id: crypto.randomUUID(), place: '', description: '' }] } }; }); }; const removeStop = (id: string) => { updateData(prev => { if (!prev.tripData) return {}; return { tripData: { ...prev.tripData, stops: prev.tripData.stops.filter(s => s.id !== id) } }; }); }; const setTravelMode = (mode: 'DRIVING' | 'WALKING') => { updateData(prev => { if (!prev.tripData) return {}; return { tripData: { ...prev.tripData, travelMode: mode } }; }); }; // Validation Check const isTripModeValid = data.eventType !== 'trip' || (data.tripData && data.tripData.travelMode !== null); const isReadyToGenerate = data.title && isTripModeValid; return (

Szczegóły

{data.eventType === 'trip' ? 'Zaplanuj trasę i opisz przebieg podróży.' : 'Uzupełnij informacje o wydarzeniu.'}

{/* SEKCJA DLA WYCIECZEK (TRIP) */} {data.eventType === 'trip' && data.tripData && (
{/* Fallback Input */} {(!data.tripData.googleMapsKey && isEnvKeyMissing) && (

Nie wykryto klucza w .env

System automatycznie wklei klucz zapasowy. Jeśli to nie nastąpiło, wklej go poniżej.

updateApiKey(e.target.value)} placeholder="Wklej klucz Google Maps API (AIza...)" className="w-full p-2 text-sm border border-yellow-300 rounded bg-white focus:border-[#EA4420] outline-none" />
)} {/* Detailed Error Banner for Maps */} {mapError && (

{mapError.title}

{mapError.msg}

)}

Plan Podróży

{scriptLoaded && !mapError && ( API OK )}
{/* BIG TRAVEL MODE SELECTOR */}
{/* Validation Message if missing */} {!data.tripData.travelMode && (

* Wybór rodzaju trasy jest wymagany

)}
{/* START POINT */}
{ updatePoint('startPoint', 'place', val); if(preview) updatePoint('startPoint', 'addressPreview', preview); }} addressPreview={data.tripData.startPoint.addressPreview} placeholder="Punkt Startowy (np. Kraków)" icon={} scriptLoaded={scriptLoaded} onError={(msg) => setMapError({title: "Błąd API Places", msg})} />
updatePoint('startPoint', 'description', e.target.value)} className="w-full p-3 border border-gray-300 rounded-md focus:border-[#EA4420] outline-none" placeholder="Opis startu (np. Zbiórka o 6:00)" />
{/* Placeholder for alignment */}
{/* STOPS */} {data.tripData.stops.map((stop, index) => (
{ updateStop(stop.id, 'place', val); if(preview) updateStop(stop.id, 'addressPreview', preview); }} addressPreview={stop.addressPreview} placeholder={`Przystanek ${index + 1}`} icon={} scriptLoaded={scriptLoaded} onError={(msg) => setMapError({title: "Błąd API Places", msg})} />
updateStop(stop.id, 'description', e.target.value)} className="w-full p-3 border border-gray-200 rounded-md focus:border-[#EA4420] outline-none" placeholder="Co tam robiliście?" />
))}
{/* END POINT */}
{ updatePoint('endPoint', 'place', val); if(preview) updatePoint('endPoint', 'addressPreview', preview); }} addressPreview={data.tripData.endPoint.addressPreview} placeholder="Punkt Końcowy (np. Zakopane)" icon={} scriptLoaded={scriptLoaded} onError={(msg) => setMapError({title: "Błąd API Places", msg})} />
updatePoint('endPoint', 'description', e.target.value)} className="w-full p-3 border border-gray-300 rounded-md focus:border-[#EA4420] outline-none" placeholder="Opis końca (np. Nareszcie piwo)" />
{/* Placeholder for alignment */}
)} {/* STANDARDOWE POLA */}
updateData({ title: e.target.value })} className="w-full p-4 border border-gray-200 rounded-md focus:ring-1 focus:ring-[#EA4420] focus:border-[#EA4420] outline-none transition-all font-medium text-gray-900 placeholder-gray-300" placeholder="np. Roadtrip po Bałkanach" />