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, Eye } from 'lucide-react'; import { processFile } from '../utils/fileUtils'; import { parseGpxFile } from '../utils/gpxUtils'; import { getEnvVar } from '../utils/envUtils'; import { UI_TEXT } from '../_EDITABLE_CONFIG/ui_text'; // --- 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); useEffect(() => { if (!scriptLoaded || !inputRef.current || !(window as any).google || autocompleteRef.current) return; try { const google = (window as any).google; 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; const name = place.name || place.formatted_address || ""; const address = place.formatted_address; 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" /> {addressPreview && (
{addressPreview}
)}
); }; interface StepDetailsProps { data: WizardState; 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); const [mapError, setMapError] = useState<{title: string, msg: string} | null>(null); const [scriptLoaded, setScriptLoaded] = useState(false); const [isParsingGpx, setIsParsingGpx] = useState(false); // State for GPX Text Preview const [showGpxPreview, setShowGpxPreview] = useState(false); const getEffectiveKey = () => { if (data.tripData?.googleMapsKey) return data.tripData.googleMapsKey; return getEnvVar('VITE_GOOGLE_MAPS_KEY'); }; const effectiveKey = getEffectiveKey(); const isKeyMissing = !effectiveKey; const loadMapsScript = (apiKey: string) => { if (!apiKey) 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 lub domena nie jest autoryzowana." }); setScriptLoaded(false); }; if (effectiveKey) loadMapsScript(effectiveKey); } }, [data.eventType, effectiveKey]); 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: '' } }); } } }, [data.eventType, updateData]); 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); // 1. Process files for display/Gemini upload const processedFiles = await Promise.all(newFiles.map(processFile)); updateData(prev => ({ files: [...prev.files, ...processedFiles] })); // 2. Scan for GPX to parse stats const gpxFile = newFiles.find(f => f.name.toLowerCase().endsWith('.gpx')); if (gpxFile) { setIsParsingGpx(true); try { const result = await parseGpxFile(gpxFile); updateData(prev => ({ ...prev, stats: result.stats, gpxSummary: result.richSummary })); } catch (gpxErr) { console.error(gpxErr); setError("Wgrano plik, ale nie udało się odczytać statystyk GPX. Sprawdź format pliku."); } finally { setIsParsingGpx(false); } } }; const removeFile = (id: string) => { updateData(prev => ({ files: prev.files.filter(f => f.id !== id) })); }; 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 } }; }); }; const isTripModeValid = data.eventType !== 'trip' || (data.tripData && data.tripData.travelMode !== null); const isReadyToGenerate = data.title && isTripModeValid; // Decide what text to show in preview const previewText = data.gpxSummary ? data.gpxSummary : `DYSTANS: ${data.stats.distance || "0"}\nCZAS TRWANIA: ${data.stats.duration || "0"}\nPRZEWYŻSZENIA: ${data.stats.elevation || "0"}`; return (
{/* GPX PREVIEW MODAL */} {showGpxPreview && (

Podgląd danych dla AI (Context)

Poniższa treść zostanie dołączona do promptu:

                            {previewText}
                        
)}

{UI_TEXT.stepDetails.title}

{data.eventType === 'trip' ? UI_TEXT.stepDetails.subtitleTrip : UI_TEXT.stepDetails.subtitleEvent}

{/* TRIP SECTION */} {data.eventType === 'trip' && data.tripData && (
{isKeyMissing && !data.tripData.googleMapsKey && (

{UI_TEXT.stepDetails.tripSection.apiKeyMissing}

{UI_TEXT.stepDetails.tripSection.apiKeyMissingDesc}

updateApiKey(e.target.value)} placeholder={UI_TEXT.stepDetails.tripSection.apiKeyPlaceholder} className="w-full p-2 text-sm border border-yellow-300 rounded bg-white focus:border-[#EA4420] outline-none" />
)} {mapError && (

{mapError.title}

{mapError.msg}

)}

{UI_TEXT.stepDetails.tripSection.title}

{scriptLoaded && !mapError && ( API OK )}
{!data.tripData.travelMode && (

{UI_TEXT.stepDetails.tripSection.modeRequired}

)}
{ updatePoint('startPoint', 'place', val); if(preview) updatePoint('startPoint', 'addressPreview', preview); }} addressPreview={data.tripData.startPoint.addressPreview} placeholder={UI_TEXT.stepDetails.tripSection.startPoint} 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={UI_TEXT.stepDetails.tripSection.startDesc} />
{data.tripData.stops.map((stop, index) => (
{ updateStop(stop.id, 'place', val); if(preview) updateStop(stop.id, 'addressPreview', preview); }} addressPreview={stop.addressPreview} placeholder={`${UI_TEXT.stepDetails.tripSection.stopPlaceholder} ${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={UI_TEXT.stepDetails.tripSection.stopDescPlaceholder} />
))}
{ updatePoint('endPoint', 'place', val); if(preview) updatePoint('endPoint', 'addressPreview', preview); }} addressPreview={data.tripData.endPoint.addressPreview} placeholder={UI_TEXT.stepDetails.tripSection.endPoint} 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={UI_TEXT.stepDetails.tripSection.endDesc} />
)} {/* Standard Fields */}
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={UI_TEXT.stepDetails.fields.titlePlaceholder} />