diff --git a/.DS_Store b/.DS_Store index 2e5e93b..89d5114 100644 Binary files a/.DS_Store and b/.DS_Store differ diff --git a/App.tsx b/App.tsx index 9e76762..abba0e9 100644 --- a/App.tsx +++ b/App.tsx @@ -131,9 +131,9 @@ const App: React.FC = () => { const [isLoaded, setIsLoaded] = useState(false); const [errorMessage, setErrorMessage] = useState(null); - // LOGO & AVATAR STATE - const [logoLoaded, setLogoLoaded] = useState(false); - const [avatarLoaded, setAvatarLoaded] = useState(false); + // LOGO & AVATAR STATE: Default to false (no error), so we try to show image first. + const [logoError, setLogoError] = useState(false); + const [avatarError, setAvatarError] = useState(false); useEffect(() => { const storedAuth = localStorage.getItem(AUTH_KEY); @@ -245,14 +245,15 @@ const App: React.FC = () => {
- {/* LOGO LOGIC: Image is hidden by default. If it loads, it shows and text hides. */} - Logo setLogoLoaded(true)} - className={`h-10 object-contain ${logoLoaded ? 'block' : 'hidden'}`} - /> - {!logoLoaded && ( + {/* LOGO LOGIC: Try to show image. If error, fallback to icon/text. */} + {!logoError ? ( + Logo setLogoError(true)} + className="h-10 object-contain" + /> + ) : (

{UI_TEXT.header.appTitle}

@@ -326,13 +327,14 @@ const App: React.FC = () => {
{/* AVATAR LOGIC */} - {AUTHOR_CONFIG.name} setAvatarLoaded(true)} - className={`relative w-20 h-20 rounded-full object-cover border border-gray-100 bg-white ${avatarLoaded ? 'block' : 'hidden'}`} - /> - {!avatarLoaded && ( + {!avatarError ? ( + {AUTHOR_CONFIG.name} setAvatarError(true)} + className="relative w-20 h-20 rounded-full object-cover border border-gray-100 bg-white" + /> + ) : (
diff --git a/components/StepData.tsx b/components/StepData.tsx index b59dbf9..b9a7232 100644 --- a/components/StepData.tsx +++ b/components/StepData.tsx @@ -1,128 +1 @@ -import React, { useRef, useState } from 'react'; -import { WizardState } from '../types'; -import { UploadCloud, FileJson, AlertCircle } from 'lucide-react'; -import { parseGpxFile } from '../utils/gpxUtils'; - -interface StepDataProps { - data: WizardState; - updateData: (updates: Partial) => void; -} - -const StepData: React.FC = ({ data, updateData }) => { - const fileInputRef = useRef(null); - const [error, setError] = useState(null); - const [isParsing, setIsParsing] = useState(false); - - const handleFileChange = async (e: React.ChangeEvent) => { - const file = e.target.files?.[0]; - if (!file) return; - - if (!file.name.toLowerCase().endsWith('.gpx')) { - setError('Proszę wybrać plik .gpx'); - return; - } - - setError(null); - setIsParsing(true); - - try { - const stats = await parseGpxFile(file); - updateData({ stats }); - } catch (err) { - console.error(err); - setError('Błąd parsowania pliku GPX. Spróbuj innego pliku lub wpisz dane ręcznie.'); - } finally { - setIsParsing(false); - } - }; - - const handleStatsChange = (key: keyof typeof data.stats, value: string) => { - updateData({ - stats: { - ...data.stats, - [key]: value - } - }); - }; - - return ( -
-
-

Dane Aktywności

-

Wgraj plik GPX lub wpisz dane ręcznie.

-
- - {/* Upload Zone */} -
fileInputRef.current?.click()} - > - - - {isParsing ? ( -
- -

Analizowanie pliku...

-
- ) : ( - <> - -

Kliknij, aby wgrać plik GPX

-

lub przeciągnij i upuść tutaj

- - )} -
- - {error && ( -
- - {error} -
- )} - - {/* Manual Override Inputs */} -
-
- - handleStatsChange('distance', 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. 12.5 km" - /> -
-
- - handleStatsChange('duration', 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. 1h 45m" - /> -
-
- - handleStatsChange('elevation', 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. 350m" - /> -
-
-
- ); -}; - -export default StepData; \ No newline at end of file +// Unused. Logic moved to StepDetails.tsx \ No newline at end of file diff --git a/components/StepDetails.tsx b/components/StepDetails.tsx index bd0f831..53ee16c 100644 --- a/components/StepDetails.tsx +++ b/components/StepDetails.tsx @@ -3,6 +3,7 @@ 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'; @@ -82,6 +83,7 @@ const StepDetails: React.FC = ({ data, updateData, onGenerate, 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); @@ -162,8 +164,29 @@ const StepDetails: React.FC = ({ data, updateData, onGenerate, 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) => { @@ -218,34 +241,39 @@ const StepDetails: React.FC = ({ data, updateData, onGenerate, 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 kontekstu GPX + Podgląd danych dla AI (Context)

-
-

To widzi AI:

-
-

DYSTANS: {data.stats.distance || "0"}

-

CZAS TRWANIA: {data.stats.duration || "0"}

-

PRZEWYŻSZENIA: {data.stats.elevation || "0"}

+
+
+

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

+
+                            {previewText}
+                        
-
+
@@ -484,9 +512,18 @@ const StepDetails: React.FC = ({ data, updateData, onGenerate, accept=".gpx,.pdf,image/*" className="hidden" /> - -

{UI_TEXT.stepDetails.fields.filesDrop}

-

{UI_TEXT.stepDetails.fields.filesSub}

+ {isParsingGpx ? ( +
+ +

Analizowanie GPX...

+
+ ) : ( + <> + +

{UI_TEXT.stepDetails.fields.filesDrop}

+

{UI_TEXT.stepDetails.fields.filesSub}

+ + )}
{error &&

{error}

} @@ -514,7 +551,7 @@ const StepDetails: React.FC = ({ data, updateData, onGenerate,
{/* GPX Preview Button */} - {(data.stats.distance || data.stats.duration || data.files.some(f => f.file.name.endsWith('.gpx'))) && ( + {(data.gpxSummary || data.stats.distance || data.stats.duration) && (