128 lines
4.7 KiB
TypeScript
128 lines
4.7 KiB
TypeScript
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<WizardState>) => void;
|
|
}
|
|
|
|
const StepData: React.FC<StepDataProps> = ({ data, updateData }) => {
|
|
const fileInputRef = useRef<HTMLInputElement>(null);
|
|
const [error, setError] = useState<string | null>(null);
|
|
const [isParsing, setIsParsing] = useState(false);
|
|
|
|
const handleFileChange = async (e: React.ChangeEvent<HTMLInputElement>) => {
|
|
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 (
|
|
<div className="space-y-10 animate-fade-in">
|
|
<div>
|
|
<h2 className="text-3xl font-bold tracking-tight text-gray-900 mb-3">Dane Aktywności</h2>
|
|
<p className="text-gray-500 mb-8 text-lg">Wgraj plik GPX lub wpisz dane ręcznie.</p>
|
|
</div>
|
|
|
|
{/* Upload Zone */}
|
|
<div
|
|
className={`border-2 border-dashed rounded-md p-10 flex flex-col items-center justify-center text-center transition-all cursor-pointer group ${
|
|
error ? 'border-red-300 bg-red-50' : 'border-gray-200 hover:border-[#EA4420] hover:bg-[#EA4420]/5'
|
|
}`}
|
|
onClick={() => fileInputRef.current?.click()}
|
|
>
|
|
<input
|
|
type="file"
|
|
ref={fileInputRef}
|
|
onChange={handleFileChange}
|
|
accept=".gpx"
|
|
className="hidden"
|
|
/>
|
|
|
|
{isParsing ? (
|
|
<div className="animate-pulse flex flex-col items-center">
|
|
<FileJson size={48} className="text-[#EA4420] mb-4 stroke-1" />
|
|
<p className="text-[#EA4420] font-semibold">Analizowanie pliku...</p>
|
|
</div>
|
|
) : (
|
|
<>
|
|
<UploadCloud size={48} className="text-gray-300 group-hover:text-[#EA4420] mb-4 stroke-1 transition-colors" />
|
|
<p className="text-gray-900 font-bold text-lg">Kliknij, aby wgrać plik GPX</p>
|
|
<p className="text-gray-500 text-sm mt-2">lub przeciągnij i upuść tutaj</p>
|
|
</>
|
|
)}
|
|
</div>
|
|
|
|
{error && (
|
|
<div className="flex items-center space-x-2 text-red-600 bg-red-50 p-4 rounded-md text-sm border border-red-100">
|
|
<AlertCircle size={18} />
|
|
<span className="font-medium">{error}</span>
|
|
</div>
|
|
)}
|
|
|
|
{/* Manual Override Inputs */}
|
|
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
|
|
<div>
|
|
<label className="block text-sm font-bold text-gray-700 mb-2">Dystans</label>
|
|
<input
|
|
type="text"
|
|
value={data.stats.distance}
|
|
onChange={(e) => 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"
|
|
/>
|
|
</div>
|
|
<div>
|
|
<label className="block text-sm font-bold text-gray-700 mb-2">Czas trwania</label>
|
|
<input
|
|
type="text"
|
|
value={data.stats.duration}
|
|
onChange={(e) => 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"
|
|
/>
|
|
</div>
|
|
<div>
|
|
<label className="block text-sm font-bold text-gray-700 mb-2">Przewyższenia</label>
|
|
<input
|
|
type="text"
|
|
value={data.stats.elevation}
|
|
onChange={(e) => 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"
|
|
/>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export default StepData; |