mirror of
https://github.com/Freika/dawarich.git
synced 2026-01-11 09:41:40 -05:00
Update importing process
This commit is contained in:
parent
8ad0b20d3d
commit
aeac8262df
7 changed files with 158 additions and 1126 deletions
|
|
@ -1,283 +0,0 @@
|
||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
||||||
<title>European Grand Tour - Trip Details</title>
|
|
||||||
<script src="https://cdn.tailwindcss.com"></script>
|
|
||||||
</head>
|
|
||||||
<body class="bg-gray-50 text-black min-h-screen">
|
|
||||||
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
|
|
||||||
<!-- Trip Header -->
|
|
||||||
<header class="mb-8">
|
|
||||||
<h1 class="text-3xl sm:text-4xl lg:text-5xl font-light tracking-tight text-black mb-4">
|
|
||||||
European Grand Tour
|
|
||||||
</h1>
|
|
||||||
<p class="text-lg text-gray-600 max-w-2xl">
|
|
||||||
A 21-day journey through the heart of Europe, discovering historic cities, stunning landscapes, and rich cultural heritage.
|
|
||||||
</p>
|
|
||||||
</header>
|
|
||||||
|
|
||||||
<!-- Main Content Grid -->
|
|
||||||
<div class="grid grid-cols-1 lg:grid-cols-3 gap-8">
|
|
||||||
<!-- Map Area - Hero Element -->
|
|
||||||
<div class="lg:col-span-2">
|
|
||||||
<div class="bg-white rounded-lg shadow-sm border border-gray-200 overflow-hidden">
|
|
||||||
<div class="h-96 lg:h-[500px] bg-gray-100 relative flex items-center justify-center">
|
|
||||||
<!-- Map Placeholder -->
|
|
||||||
<div class="text-center">
|
|
||||||
<div class="w-16 h-16 bg-gray-300 rounded-full mx-auto mb-4"></div>
|
|
||||||
<p class="text-gray-500 text-sm">Interactive Map</p>
|
|
||||||
<p class="text-gray-400 text-xs mt-1">Route visualization would appear here</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Route indicators -->
|
|
||||||
<div class="absolute top-4 left-4">
|
|
||||||
<div class="bg-black text-white px-3 py-1 rounded-full text-xs font-medium">
|
|
||||||
Start: Amsterdam
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="absolute bottom-4 right-4">
|
|
||||||
<div class="bg-gray-800 text-white px-3 py-1 rounded-full text-xs font-medium">
|
|
||||||
End: Rome
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Stats Section -->
|
|
||||||
<div class="space-y-8">
|
|
||||||
<!-- Trip Statistics -->
|
|
||||||
<div class="bg-white rounded-lg shadow-sm border border-gray-200 p-6">
|
|
||||||
<h2 class="text-xl font-medium text-black mb-6">Trip Statistics</h2>
|
|
||||||
|
|
||||||
<div class="space-y-6">
|
|
||||||
<div class="border-b border-gray-100 pb-4">
|
|
||||||
<div class="text-2xl font-light text-black">3,247 km</div>
|
|
||||||
<div class="text-sm text-gray-600 mt-1">Total Distance</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="border-b border-gray-100 pb-4">
|
|
||||||
<div class="text-2xl font-light text-black">21 days</div>
|
|
||||||
<div class="text-sm text-gray-600 mt-1">Duration</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<div class="text-2xl font-light text-black">7 countries</div>
|
|
||||||
<div class="text-sm text-gray-600 mt-1">Countries Visited</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Countries List -->
|
|
||||||
<div class="bg-white rounded-lg shadow-sm border border-gray-200 p-6">
|
|
||||||
<h3 class="text-lg font-medium text-black mb-4">Countries Visited</h3>
|
|
||||||
<div class="space-y-3">
|
|
||||||
<div class="flex justify-between items-center">
|
|
||||||
<span class="text-gray-900">Netherlands</span>
|
|
||||||
<span class="text-xs text-gray-500">3 days</span>
|
|
||||||
</div>
|
|
||||||
<div class="flex justify-between items-center">
|
|
||||||
<span class="text-gray-900">Germany</span>
|
|
||||||
<span class="text-xs text-gray-500">4 days</span>
|
|
||||||
</div>
|
|
||||||
<div class="flex justify-between items-center">
|
|
||||||
<span class="text-gray-900">Austria</span>
|
|
||||||
<span class="text-xs text-gray-500">2 days</span>
|
|
||||||
</div>
|
|
||||||
<div class="flex justify-between items-center">
|
|
||||||
<span class="text-gray-900">Switzerland</span>
|
|
||||||
<span class="text-xs text-gray-500">3 days</span>
|
|
||||||
</div>
|
|
||||||
<div class="flex justify-between items-center">
|
|
||||||
<span class="text-gray-900">France</span>
|
|
||||||
<span class="text-xs text-gray-500">4 days</span>
|
|
||||||
</div>
|
|
||||||
<div class="flex justify-between items-center">
|
|
||||||
<span class="text-gray-900">Monaco</span>
|
|
||||||
<span class="text-xs text-gray-500">1 day</span>
|
|
||||||
</div>
|
|
||||||
<div class="flex justify-between items-center">
|
|
||||||
<span class="text-gray-900">Italy</span>
|
|
||||||
<span class="text-xs text-gray-500">4 days</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Photos Section -->
|
|
||||||
<section class="mt-16">
|
|
||||||
<div class="flex items-center justify-between mb-8">
|
|
||||||
<h2 class="text-2xl sm:text-3xl font-light text-black">Trip Photos</h2>
|
|
||||||
<div class="text-sm text-gray-600">147 photos</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Photo Grid -->
|
|
||||||
<div class="grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-4 xl:grid-cols-6 gap-4">
|
|
||||||
<!-- Photo placeholders -->
|
|
||||||
<div class="bg-white rounded-lg shadow-sm border border-gray-200 overflow-hidden">
|
|
||||||
<div class="aspect-square bg-gray-100 relative">
|
|
||||||
<div class="absolute inset-0 flex items-center justify-center">
|
|
||||||
<div class="w-8 h-8 bg-gray-300 rounded"></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="p-3">
|
|
||||||
<p class="text-xs text-gray-600">Amsterdam Canal</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="bg-white rounded-lg shadow-sm border border-gray-200 overflow-hidden">
|
|
||||||
<div class="aspect-square bg-gray-100 relative">
|
|
||||||
<div class="absolute inset-0 flex items-center justify-center">
|
|
||||||
<div class="w-8 h-8 bg-gray-300 rounded"></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="p-3">
|
|
||||||
<p class="text-xs text-gray-600">Berlin Wall</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="bg-white rounded-lg shadow-sm border border-gray-200 overflow-hidden">
|
|
||||||
<div class="aspect-square bg-gray-100 relative">
|
|
||||||
<div class="absolute inset-0 flex items-center justify-center">
|
|
||||||
<div class="w-8 h-8 bg-gray-300 rounded"></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="p-3">
|
|
||||||
<p class="text-xs text-gray-600">Alpine Vista</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="bg-white rounded-lg shadow-sm border border-gray-200 overflow-hidden">
|
|
||||||
<div class="aspect-square bg-gray-100 relative">
|
|
||||||
<div class="absolute inset-0 flex items-center justify-center">
|
|
||||||
<div class="w-8 h-8 bg-gray-300 rounded"></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="p-3">
|
|
||||||
<p class="text-xs text-gray-600">Swiss Mountains</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="bg-white rounded-lg shadow-sm border border-gray-200 overflow-hidden">
|
|
||||||
<div class="aspect-square bg-gray-100 relative">
|
|
||||||
<div class="absolute inset-0 flex items-center justify-center">
|
|
||||||
<div class="w-8 h-8 bg-gray-300 rounded"></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="p-3">
|
|
||||||
<p class="text-xs text-gray-600">Eiffel Tower</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="bg-white rounded-lg shadow-sm border border-gray-200 overflow-hidden">
|
|
||||||
<div class="aspect-square bg-gray-100 relative">
|
|
||||||
<div class="absolute inset-0 flex items-center justify-center">
|
|
||||||
<div class="w-8 h-8 bg-gray-300 rounded"></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="p-3">
|
|
||||||
<p class="text-xs text-gray-600">Monaco Harbor</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="bg-white rounded-lg shadow-sm border border-gray-200 overflow-hidden">
|
|
||||||
<div class="aspect-square bg-gray-100 relative">
|
|
||||||
<div class="absolute inset-0 flex items-center justify-center">
|
|
||||||
<div class="w-8 h-8 bg-gray-300 rounded"></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="p-3">
|
|
||||||
<p class="text-xs text-gray-600">Colosseum</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="bg-white rounded-lg shadow-sm border border-gray-200 overflow-hidden">
|
|
||||||
<div class="aspect-square bg-gray-100 relative">
|
|
||||||
<div class="absolute inset-0 flex items-center justify-center">
|
|
||||||
<div class="w-8 h-8 bg-gray-300 rounded"></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="p-3">
|
|
||||||
<p class="text-xs text-gray-600">Roman Forum</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Load more indicator -->
|
|
||||||
<div class="col-span-2 sm:col-span-3 lg:col-span-4 xl:col-span-6 mt-8">
|
|
||||||
<button class="w-full py-3 px-4 bg-white border border-gray-200 rounded-lg text-gray-600 hover:bg-gray-50 transition-colors text-sm font-medium">
|
|
||||||
Load More Photos
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<!-- Trip Timeline (Additional Context) -->
|
|
||||||
<section class="mt-16">
|
|
||||||
<h2 class="text-2xl sm:text-3xl font-light text-black mb-8">Trip Timeline</h2>
|
|
||||||
|
|
||||||
<div class="bg-white rounded-lg shadow-sm border border-gray-200 p-6">
|
|
||||||
<div class="space-y-6">
|
|
||||||
<div class="flex items-start space-x-4">
|
|
||||||
<div class="w-2 h-2 bg-black rounded-full mt-2 flex-shrink-0"></div>
|
|
||||||
<div>
|
|
||||||
<div class="text-sm font-medium text-black">Day 1-3: Amsterdam, Netherlands</div>
|
|
||||||
<div class="text-sm text-gray-600 mt-1">Explored canals, visited museums, experienced local culture</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="flex items-start space-x-4">
|
|
||||||
<div class="w-2 h-2 bg-gray-400 rounded-full mt-2 flex-shrink-0"></div>
|
|
||||||
<div>
|
|
||||||
<div class="text-sm font-medium text-black">Day 4-7: Berlin & Munich, Germany</div>
|
|
||||||
<div class="text-sm text-gray-600 mt-1">Historical sites, traditional cuisine, alpine preparation</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="flex items-start space-x-4">
|
|
||||||
<div class="w-2 h-2 bg-gray-400 rounded-full mt-2 flex-shrink-0"></div>
|
|
||||||
<div>
|
|
||||||
<div class="text-sm font-medium text-black">Day 8-9: Salzburg, Austria</div>
|
|
||||||
<div class="text-sm text-gray-600 mt-1">Mozart's birthplace, stunning architecture</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="flex items-start space-x-4">
|
|
||||||
<div class="w-2 h-2 bg-gray-400 rounded-full mt-2 flex-shrink-0"></div>
|
|
||||||
<div>
|
|
||||||
<div class="text-sm font-medium text-black">Day 10-12: Zurich & Alps, Switzerland</div>
|
|
||||||
<div class="text-sm text-gray-600 mt-1">Mountain adventures, pristine lakes, scenic drives</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="flex items-start space-x-4">
|
|
||||||
<div class="w-2 h-2 bg-gray-400 rounded-full mt-2 flex-shrink-0"></div>
|
|
||||||
<div>
|
|
||||||
<div class="text-sm font-medium text-black">Day 13-16: Paris & Lyon, France</div>
|
|
||||||
<div class="text-sm text-gray-600 mt-1">Art, cuisine, romance, and French countryside</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="flex items-start space-x-4">
|
|
||||||
<div class="w-2 h-2 bg-gray-400 rounded-full mt-2 flex-shrink-0"></div>
|
|
||||||
<div>
|
|
||||||
<div class="text-sm font-medium text-black">Day 17: Monaco</div>
|
|
||||||
<div class="text-sm text-gray-600 mt-1">Luxury, casinos, and Mediterranean coastline</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="flex items-start space-x-4">
|
|
||||||
<div class="w-2 h-2 bg-gray-400 rounded-full mt-2 flex-shrink-0"></div>
|
|
||||||
<div>
|
|
||||||
<div class="text-sm font-medium text-black">Day 18-21: Rome, Italy</div>
|
|
||||||
<div class="text-sm text-gray-600 mt-1">Ancient history, incredible food, perfect ending</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
</div>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
|
|
@ -1,238 +0,0 @@
|
||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
||||||
<title>Asian Adventure - Trip Details</title>
|
|
||||||
<script src="https://cdn.tailwindcss.com"></script>
|
|
||||||
</head>
|
|
||||||
<body class="bg-white text-black min-h-screen">
|
|
||||||
<!-- Main Container -->
|
|
||||||
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
|
|
||||||
|
|
||||||
<!-- Trip Header -->
|
|
||||||
<header class="mb-8">
|
|
||||||
<h1 class="text-3xl sm:text-4xl lg:text-5xl font-light tracking-tight mb-2">
|
|
||||||
Asian Adventure
|
|
||||||
</h1>
|
|
||||||
<p class="text-lg text-gray-600 font-light">
|
|
||||||
A journey through Southeast Asia's cultural treasures
|
|
||||||
</p>
|
|
||||||
</header>
|
|
||||||
|
|
||||||
<!-- Main Content Grid -->
|
|
||||||
<div class="grid grid-cols-1 lg:grid-cols-4 gap-8">
|
|
||||||
|
|
||||||
<!-- Map Section (Takes up 3/4 on desktop) -->
|
|
||||||
<div class="lg:col-span-3 order-2 lg:order-1">
|
|
||||||
<div class="bg-gray-100 border border-gray-200 rounded-lg aspect-[4/3] flex items-center justify-center">
|
|
||||||
<div class="text-center">
|
|
||||||
<div class="w-16 h-16 bg-gray-300 rounded-full mx-auto mb-4"></div>
|
|
||||||
<p class="text-gray-500 font-light">Interactive Map</p>
|
|
||||||
<p class="text-sm text-gray-400 mt-2">Route visualization</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Sidebar (Stats and Info) -->
|
|
||||||
<div class="lg:col-span-1 order-1 lg:order-2 space-y-8">
|
|
||||||
|
|
||||||
<!-- Trip Stats -->
|
|
||||||
<div class="bg-gray-50 border border-gray-200 rounded-lg p-6">
|
|
||||||
<h2 class="text-lg font-medium mb-6 tracking-tight">Trip Statistics</h2>
|
|
||||||
|
|
||||||
<div class="space-y-6">
|
|
||||||
<div>
|
|
||||||
<div class="text-2xl font-light text-black mb-1">2,847 km</div>
|
|
||||||
<div class="text-sm text-gray-600">Total Distance</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<div class="text-2xl font-light text-black mb-1">18 days</div>
|
|
||||||
<div class="text-sm text-gray-600">Duration</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<div class="text-2xl font-light text-black mb-1">5 countries</div>
|
|
||||||
<div class="text-sm text-gray-600">Countries Visited</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Countries List -->
|
|
||||||
<div class="bg-white border border-gray-200 rounded-lg p-6">
|
|
||||||
<h3 class="text-lg font-medium mb-4 tracking-tight">Countries</h3>
|
|
||||||
<div class="space-y-3">
|
|
||||||
<div class="flex justify-between items-center py-2 border-b border-gray-100 last:border-b-0">
|
|
||||||
<span class="font-light">Thailand</span>
|
|
||||||
<span class="text-sm text-gray-500">6 days</span>
|
|
||||||
</div>
|
|
||||||
<div class="flex justify-between items-center py-2 border-b border-gray-100 last:border-b-0">
|
|
||||||
<span class="font-light">Vietnam</span>
|
|
||||||
<span class="text-sm text-gray-500">4 days</span>
|
|
||||||
</div>
|
|
||||||
<div class="flex justify-between items-center py-2 border-b border-gray-100 last:border-b-0">
|
|
||||||
<span class="font-light">Cambodia</span>
|
|
||||||
<span class="text-sm text-gray-500">3 days</span>
|
|
||||||
</div>
|
|
||||||
<div class="flex justify-between items-center py-2 border-b border-gray-100 last:border-b-0">
|
|
||||||
<span class="font-light">Laos</span>
|
|
||||||
<span class="text-sm text-gray-500">3 days</span>
|
|
||||||
</div>
|
|
||||||
<div class="flex justify-between items-center py-2 border-b border-gray-100 last:border-b-0">
|
|
||||||
<span class="font-light">Myanmar</span>
|
|
||||||
<span class="text-sm text-gray-500">2 days</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Quick Stats -->
|
|
||||||
<div class="bg-white border border-gray-200 rounded-lg p-6">
|
|
||||||
<h3 class="text-lg font-medium mb-4 tracking-tight">Highlights</h3>
|
|
||||||
<div class="space-y-4">
|
|
||||||
<div class="flex items-center space-x-3">
|
|
||||||
<div class="w-2 h-2 bg-black rounded-full"></div>
|
|
||||||
<span class="text-sm font-light">12 temples visited</span>
|
|
||||||
</div>
|
|
||||||
<div class="flex items-center space-x-3">
|
|
||||||
<div class="w-2 h-2 bg-black rounded-full"></div>
|
|
||||||
<span class="text-sm font-light">4 cooking classes</span>
|
|
||||||
</div>
|
|
||||||
<div class="flex items-center space-x-3">
|
|
||||||
<div class="w-2 h-2 bg-black rounded-full"></div>
|
|
||||||
<span class="text-sm font-light">8 markets explored</span>
|
|
||||||
</div>
|
|
||||||
<div class="flex items-center space-x-3">
|
|
||||||
<div class="w-2 h-2 bg-black rounded-full"></div>
|
|
||||||
<span class="text-sm font-light">3 boat rides</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Photos Section -->
|
|
||||||
<section class="mt-16">
|
|
||||||
<div class="flex items-center justify-between mb-8">
|
|
||||||
<h2 class="text-2xl sm:text-3xl font-light tracking-tight">Trip Photos</h2>
|
|
||||||
<span class="text-sm text-gray-500 font-light">247 photos</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Photo Grid -->
|
|
||||||
<div class="grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-4 xl:grid-cols-6 gap-4">
|
|
||||||
<!-- Photo placeholders with varying aspect ratios -->
|
|
||||||
<div class="bg-gray-200 rounded-lg aspect-square flex items-center justify-center">
|
|
||||||
<div class="w-8 h-8 bg-gray-400 rounded"></div>
|
|
||||||
</div>
|
|
||||||
<div class="bg-gray-200 rounded-lg aspect-[4/3] flex items-center justify-center">
|
|
||||||
<div class="w-8 h-6 bg-gray-400 rounded"></div>
|
|
||||||
</div>
|
|
||||||
<div class="bg-gray-200 rounded-lg aspect-square flex items-center justify-center">
|
|
||||||
<div class="w-8 h-8 bg-gray-400 rounded"></div>
|
|
||||||
</div>
|
|
||||||
<div class="bg-gray-200 rounded-lg aspect-[3/4] flex items-center justify-center">
|
|
||||||
<div class="w-6 h-8 bg-gray-400 rounded"></div>
|
|
||||||
</div>
|
|
||||||
<div class="bg-gray-200 rounded-lg aspect-square flex items-center justify-center">
|
|
||||||
<div class="w-8 h-8 bg-gray-400 rounded"></div>
|
|
||||||
</div>
|
|
||||||
<div class="bg-gray-200 rounded-lg aspect-[4/3] flex items-center justify-center">
|
|
||||||
<div class="w-8 h-6 bg-gray-400 rounded"></div>
|
|
||||||
</div>
|
|
||||||
<div class="bg-gray-200 rounded-lg aspect-square flex items-center justify-center">
|
|
||||||
<div class="w-8 h-8 bg-gray-400 rounded"></div>
|
|
||||||
</div>
|
|
||||||
<div class="bg-gray-200 rounded-lg aspect-[3/4] flex items-center justify-center">
|
|
||||||
<div class="w-6 h-8 bg-gray-400 rounded"></div>
|
|
||||||
</div>
|
|
||||||
<div class="bg-gray-200 rounded-lg aspect-square flex items-center justify-center">
|
|
||||||
<div class="w-8 h-8 bg-gray-400 rounded"></div>
|
|
||||||
</div>
|
|
||||||
<div class="bg-gray-200 rounded-lg aspect-[4/3] flex items-center justify-center">
|
|
||||||
<div class="w-8 h-6 bg-gray-400 rounded"></div>
|
|
||||||
</div>
|
|
||||||
<div class="bg-gray-200 rounded-lg aspect-square flex items-center justify-center">
|
|
||||||
<div class="w-8 h-8 bg-gray-400 rounded"></div>
|
|
||||||
</div>
|
|
||||||
<div class="bg-gray-200 rounded-lg aspect-[3/4] flex items-center justify-center">
|
|
||||||
<div class="w-6 h-8 bg-gray-400 rounded"></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Load More Button -->
|
|
||||||
<div class="flex justify-center mt-8">
|
|
||||||
<button class="px-8 py-3 border border-gray-300 rounded-lg text-sm font-light hover:bg-gray-50 transition-colors">
|
|
||||||
Load More Photos
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<!-- Trip Timeline (Additional Section) -->
|
|
||||||
<section class="mt-16 border-t border-gray-200 pt-16">
|
|
||||||
<h2 class="text-2xl sm:text-3xl font-light tracking-tight mb-8">Trip Timeline</h2>
|
|
||||||
|
|
||||||
<div class="space-y-8">
|
|
||||||
<div class="flex flex-col sm:flex-row sm:items-start gap-4">
|
|
||||||
<div class="flex-shrink-0 sm:w-24">
|
|
||||||
<span class="text-sm text-gray-500 font-light">Day 1-6</span>
|
|
||||||
</div>
|
|
||||||
<div class="flex-grow">
|
|
||||||
<h3 class="font-medium mb-2">Bangkok & Northern Thailand</h3>
|
|
||||||
<p class="text-gray-600 font-light text-sm leading-relaxed">
|
|
||||||
Explored the bustling streets of Bangkok, visited ancient temples, and trekked through the mountains of Chiang Mai.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="flex flex-col sm:flex-row sm:items-start gap-4">
|
|
||||||
<div class="flex-shrink-0 sm:w-24">
|
|
||||||
<span class="text-sm text-gray-500 font-light">Day 7-10</span>
|
|
||||||
</div>
|
|
||||||
<div class="flex-grow">
|
|
||||||
<h3 class="font-medium mb-2">Ho Chi Minh City & Hanoi</h3>
|
|
||||||
<p class="text-gray-600 font-light text-sm leading-relaxed">
|
|
||||||
Discovered Vietnamese culture, cuisine, and history across the country's two major cities.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="flex flex-col sm:flex-row sm:items-start gap-4">
|
|
||||||
<div class="flex-shrink-0 sm:w-24">
|
|
||||||
<span class="text-sm text-gray-500 font-light">Day 11-13</span>
|
|
||||||
</div>
|
|
||||||
<div class="flex-grow">
|
|
||||||
<h3 class="font-medium mb-2">Siem Reap, Cambodia</h3>
|
|
||||||
<p class="text-gray-600 font-light text-sm leading-relaxed">
|
|
||||||
Marveled at the ancient temples of Angkor Wat and experienced traditional Khmer culture.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="flex flex-col sm:flex-row sm:items-start gap-4">
|
|
||||||
<div class="flex-shrink-0 sm:w-24">
|
|
||||||
<span class="text-sm text-gray-500 font-light">Day 14-16</span>
|
|
||||||
</div>
|
|
||||||
<div class="flex-grow">
|
|
||||||
<h3 class="font-medium mb-2">Luang Prabang, Laos</h3>
|
|
||||||
<p class="text-gray-600 font-light text-sm leading-relaxed">
|
|
||||||
Experienced the peaceful atmosphere of this UNESCO World Heritage city along the Mekong River.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="flex flex-col sm:flex-row sm:items-start gap-4">
|
|
||||||
<div class="flex-shrink-0 sm:w-24">
|
|
||||||
<span class="text-sm text-gray-500 font-light">Day 17-18</span>
|
|
||||||
</div>
|
|
||||||
<div class="flex-grow">
|
|
||||||
<h3 class="font-medium mb-2">Yangon, Myanmar</h3>
|
|
||||||
<p class="text-gray-600 font-light text-sm leading-relaxed">
|
|
||||||
Concluded the journey with visits to golden pagodas and local markets in Myanmar's largest city.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
</div>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
|
|
@ -1,316 +0,0 @@
|
||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
||||||
<title>Coast to Coast Adventure - Trip Details</title>
|
|
||||||
<script src="https://cdn.tailwindcss.com"></script>
|
|
||||||
<style>
|
|
||||||
.map-placeholder {
|
|
||||||
background: linear-gradient(45deg, #f3f4f6 25%, transparent 25%),
|
|
||||||
linear-gradient(-45deg, #f3f4f6 25%, transparent 25%),
|
|
||||||
linear-gradient(45deg, transparent 75%, #f3f4f6 75%),
|
|
||||||
linear-gradient(-45deg, transparent 75%, #f3f4f6 75%);
|
|
||||||
background-size: 20px 20px;
|
|
||||||
background-position: 0 0, 0 10px, 10px -10px, -10px 0px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.photo-placeholder {
|
|
||||||
background: linear-gradient(135deg, #e5e7eb 0%, #f9fafb 50%, #e5e7eb 100%);
|
|
||||||
}
|
|
||||||
|
|
||||||
.stat-card {
|
|
||||||
transition: transform 0.2s ease-in-out;
|
|
||||||
}
|
|
||||||
|
|
||||||
.stat-card:hover {
|
|
||||||
transform: translateY(-2px);
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body class="bg-white text-black font-sans antialiased">
|
|
||||||
<!-- Main Container -->
|
|
||||||
<div class="min-h-screen p-4 md:p-8">
|
|
||||||
<div class="max-w-7xl mx-auto">
|
|
||||||
|
|
||||||
<!-- Trip Header -->
|
|
||||||
<header class="mb-8">
|
|
||||||
<h1 class="text-4xl md:text-5xl lg:text-6xl font-light tracking-tight mb-2">
|
|
||||||
Coast to Coast Adventure
|
|
||||||
</h1>
|
|
||||||
<p class="text-lg md:text-xl text-gray-600 font-light">
|
|
||||||
New York City to San Francisco • October 2024
|
|
||||||
</p>
|
|
||||||
</header>
|
|
||||||
|
|
||||||
<!-- Main Grid Layout -->
|
|
||||||
<div class="grid grid-cols-1 lg:grid-cols-2 gap-8 mb-8">
|
|
||||||
|
|
||||||
<!-- Map Section -->
|
|
||||||
<div class="lg:col-span-2">
|
|
||||||
<div class="bg-white border border-gray-200 rounded-lg overflow-hidden">
|
|
||||||
<div class="p-6 border-b border-gray-100">
|
|
||||||
<h2 class="text-2xl font-light mb-2">Route Overview</h2>
|
|
||||||
<p class="text-gray-600">Interactive journey across America</p>
|
|
||||||
</div>
|
|
||||||
<div class="map-placeholder h-96 md:h-[500px] flex items-center justify-center">
|
|
||||||
<div class="text-center">
|
|
||||||
<div class="w-16 h-16 mx-auto mb-4 bg-gray-300 rounded-full flex items-center justify-center">
|
|
||||||
<svg class="w-8 h-8 text-gray-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17.657 16.657L13.414 20.9a1.998 1.998 0 01-2.827 0l-4.244-4.243a8 8 0 1111.314 0z"></path>
|
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 11a3 3 0 11-6 0 3 3 0 016 0z"></path>
|
|
||||||
</svg>
|
|
||||||
</div>
|
|
||||||
<p class="text-gray-500 font-light">Interactive Map</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Stats Section -->
|
|
||||||
<div>
|
|
||||||
<div class="bg-white border border-gray-200 rounded-lg p-6">
|
|
||||||
<h2 class="text-2xl font-light mb-6">Trip Statistics</h2>
|
|
||||||
|
|
||||||
<div class="space-y-6">
|
|
||||||
<!-- Distance -->
|
|
||||||
<div class="stat-card bg-gray-50 p-4 rounded-lg">
|
|
||||||
<div class="flex items-center justify-between">
|
|
||||||
<div>
|
|
||||||
<p class="text-sm text-gray-600 mb-1">Total Distance</p>
|
|
||||||
<p class="text-3xl font-light">2,908 mi</p>
|
|
||||||
</div>
|
|
||||||
<div class="w-12 h-12 bg-white rounded-full flex items-center justify-center">
|
|
||||||
<svg class="w-6 h-6 text-gray-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 7h8m0 0v8m0-8l-8 8-4-4-6 6"></path>
|
|
||||||
</svg>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Duration -->
|
|
||||||
<div class="stat-card bg-gray-50 p-4 rounded-lg">
|
|
||||||
<div class="flex items-center justify-between">
|
|
||||||
<div>
|
|
||||||
<p class="text-sm text-gray-600 mb-1">Duration</p>
|
|
||||||
<p class="text-3xl font-light">14 days</p>
|
|
||||||
</div>
|
|
||||||
<div class="w-12 h-12 bg-white rounded-full flex items-center justify-center">
|
|
||||||
<svg class="w-6 h-6 text-gray-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z"></path>
|
|
||||||
</svg>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Countries -->
|
|
||||||
<div class="stat-card bg-gray-50 p-4 rounded-lg">
|
|
||||||
<div class="flex items-center justify-between">
|
|
||||||
<div>
|
|
||||||
<p class="text-sm text-gray-600 mb-1">States Visited</p>
|
|
||||||
<p class="text-3xl font-light">12</p>
|
|
||||||
</div>
|
|
||||||
<div class="w-12 h-12 bg-white rounded-full flex items-center justify-center">
|
|
||||||
<svg class="w-6 h-6 text-gray-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3.055 11H5a2 2 0 012 2v1a2 2 0 002 2 2 2 0 012 2v2.945M8 3.935V5.5A2.5 2.5 0 0010.5 8h.5a2 2 0 012 2 2 2 0 104 0 2 2 0 012-2h1.064M15 20.488V18a2 2 0 012-2h3.064"></path>
|
|
||||||
</svg>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- State List -->
|
|
||||||
<div class="mt-6 pt-6 border-t border-gray-100">
|
|
||||||
<h3 class="text-lg font-light mb-4">States Crossed</h3>
|
|
||||||
<div class="grid grid-cols-2 gap-2 text-sm">
|
|
||||||
<div class="flex items-center space-x-2">
|
|
||||||
<div class="w-2 h-2 bg-gray-400 rounded-full"></div>
|
|
||||||
<span>New York</span>
|
|
||||||
</div>
|
|
||||||
<div class="flex items-center space-x-2">
|
|
||||||
<div class="w-2 h-2 bg-gray-400 rounded-full"></div>
|
|
||||||
<span>Pennsylvania</span>
|
|
||||||
</div>
|
|
||||||
<div class="flex items-center space-x-2">
|
|
||||||
<div class="w-2 h-2 bg-gray-400 rounded-full"></div>
|
|
||||||
<span>Ohio</span>
|
|
||||||
</div>
|
|
||||||
<div class="flex items-center space-x-2">
|
|
||||||
<div class="w-2 h-2 bg-gray-400 rounded-full"></div>
|
|
||||||
<span>Indiana</span>
|
|
||||||
</div>
|
|
||||||
<div class="flex items-center space-x-2">
|
|
||||||
<div class="w-2 h-2 bg-gray-400 rounded-full"></div>
|
|
||||||
<span>Illinois</span>
|
|
||||||
</div>
|
|
||||||
<div class="flex items-center space-x-2">
|
|
||||||
<div class="w-2 h-2 bg-gray-400 rounded-full"></div>
|
|
||||||
<span>Iowa</span>
|
|
||||||
</div>
|
|
||||||
<div class="flex items-center space-x-2">
|
|
||||||
<div class="w-2 h-2 bg-gray-400 rounded-full"></div>
|
|
||||||
<span>Nebraska</span>
|
|
||||||
</div>
|
|
||||||
<div class="flex items-center space-x-2">
|
|
||||||
<div class="w-2 h-2 bg-gray-400 rounded-full"></div>
|
|
||||||
<span>Colorado</span>
|
|
||||||
</div>
|
|
||||||
<div class="flex items-center space-x-2">
|
|
||||||
<div class="w-2 h-2 bg-gray-400 rounded-full"></div>
|
|
||||||
<span>Utah</span>
|
|
||||||
</div>
|
|
||||||
<div class="flex items-center space-x-2">
|
|
||||||
<div class="w-2 h-2 bg-gray-400 rounded-full"></div>
|
|
||||||
<span>Nevada</span>
|
|
||||||
</div>
|
|
||||||
<div class="flex items-center space-x-2">
|
|
||||||
<div class="w-2 h-2 bg-gray-400 rounded-full"></div>
|
|
||||||
<span>California</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Photos Section -->
|
|
||||||
<div>
|
|
||||||
<div class="bg-white border border-gray-200 rounded-lg p-6">
|
|
||||||
<h2 class="text-2xl font-light mb-6">Trip Highlights</h2>
|
|
||||||
|
|
||||||
<div class="grid grid-cols-2 gap-4 mb-6">
|
|
||||||
<!-- Featured Photo -->
|
|
||||||
<div class="col-span-2">
|
|
||||||
<div class="photo-placeholder h-48 rounded-lg flex items-center justify-center">
|
|
||||||
<div class="text-center">
|
|
||||||
<div class="w-12 h-12 mx-auto mb-2 bg-gray-400 rounded-lg flex items-center justify-center">
|
|
||||||
<svg class="w-6 h-6 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 16l4.586-4.586a2 2 0 012.828 0L16 16m-2-2l1.586-1.586a2 2 0 012.828 0L20 14m-6-6h.01M6 20h12a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v12a2 2 0 002 2z"></path>
|
|
||||||
</svg>
|
|
||||||
</div>
|
|
||||||
<p class="text-sm text-gray-500">Golden Gate Bridge</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Small Photos -->
|
|
||||||
<div class="photo-placeholder h-24 rounded-lg flex items-center justify-center">
|
|
||||||
<div class="text-center">
|
|
||||||
<div class="w-8 h-8 mx-auto mb-1 bg-gray-400 rounded flex items-center justify-center">
|
|
||||||
<svg class="w-4 h-4 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 16l4.586-4.586a2 2 0 012.828 0L16 16m-2-2l1.586-1.586a2 2 0 012.828 0L20 14m-6-6h.01M6 20h12a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v12a2 2 0 002 2z"></path>
|
|
||||||
</svg>
|
|
||||||
</div>
|
|
||||||
<p class="text-xs text-gray-500">Chicago Skyline</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="photo-placeholder h-24 rounded-lg flex items-center justify-center">
|
|
||||||
<div class="text-center">
|
|
||||||
<div class="w-8 h-8 mx-auto mb-1 bg-gray-400 rounded flex items-center justify-center">
|
|
||||||
<svg class="w-4 h-4 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 16l4.586-4.586a2 2 0 012.828 0L16 16m-2-2l1.586-1.586a2 2 0 012.828 0L20 14m-6-6h.01M6 20h12a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v12a2 2 0 002 2z"></path>
|
|
||||||
</svg>
|
|
||||||
</div>
|
|
||||||
<p class="text-xs text-gray-500">Rocky Mountains</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="photo-placeholder h-24 rounded-lg flex items-center justify-center">
|
|
||||||
<div class="text-center">
|
|
||||||
<div class="w-8 h-8 mx-auto mb-1 bg-gray-400 rounded flex items-center justify-center">
|
|
||||||
<svg class="w-4 h-4 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 16l4.586-4.586a2 2 0 012.828 0L16 16m-2-2l1.586-1.586a2 2 0 012.828 0L20 14m-6-6h.01M6 20h12a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v12a2 2 0 002 2z"></path>
|
|
||||||
</svg>
|
|
||||||
</div>
|
|
||||||
<p class="text-xs text-gray-500">Monument Valley</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="photo-placeholder h-24 rounded-lg flex items-center justify-center">
|
|
||||||
<div class="text-center">
|
|
||||||
<div class="w-8 h-8 mx-auto mb-1 bg-gray-400 rounded flex items-center justify-center">
|
|
||||||
<svg class="w-4 h-4 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 16l4.586-4.586a2 2 0 012.828 0L16 16m-2-2l1.586-1.586a2 2 0 012.828 0L20 14m-6-6h.01M6 20h12a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v12a2 2 0 002 2z"></path>
|
|
||||||
</svg>
|
|
||||||
</div>
|
|
||||||
<p class="text-xs text-gray-500">Route 66</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Photo Counter -->
|
|
||||||
<div class="text-center">
|
|
||||||
<button class="text-sm text-gray-600 hover:text-black transition-colors duration-200 border border-gray-200 px-4 py-2 rounded-lg hover:border-gray-300">
|
|
||||||
View all 247 photos
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Additional Trip Details -->
|
|
||||||
<div class="grid grid-cols-1 md:grid-cols-3 gap-8">
|
|
||||||
<!-- Key Stops -->
|
|
||||||
<div class="bg-white border border-gray-200 rounded-lg p-6">
|
|
||||||
<h3 class="text-xl font-light mb-4">Key Stops</h3>
|
|
||||||
<div class="space-y-3">
|
|
||||||
<div class="flex items-center justify-between py-2 border-b border-gray-100 last:border-b-0">
|
|
||||||
<span class="text-sm">Times Square, NYC</span>
|
|
||||||
<span class="text-sm text-gray-500">Day 1</span>
|
|
||||||
</div>
|
|
||||||
<div class="flex items-center justify-between py-2 border-b border-gray-100 last:border-b-0">
|
|
||||||
<span class="text-sm">Millennium Park, Chicago</span>
|
|
||||||
<span class="text-sm text-gray-500">Day 4</span>
|
|
||||||
</div>
|
|
||||||
<div class="flex items-center justify-between py-2 border-b border-gray-100 last:border-b-0">
|
|
||||||
<span class="text-sm">Rocky Mountain National Park</span>
|
|
||||||
<span class="text-sm text-gray-500">Day 8</span>
|
|
||||||
</div>
|
|
||||||
<div class="flex items-center justify-between py-2 border-b border-gray-100 last:border-b-0">
|
|
||||||
<span class="text-sm">Arches National Park</span>
|
|
||||||
<span class="text-sm text-gray-500">Day 10</span>
|
|
||||||
</div>
|
|
||||||
<div class="flex items-center justify-between py-2 border-b border-gray-100 last:border-b-0">
|
|
||||||
<span class="text-sm">Golden Gate Bridge, SF</span>
|
|
||||||
<span class="text-sm text-gray-500">Day 14</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Weather Summary -->
|
|
||||||
<div class="bg-white border border-gray-200 rounded-lg p-6">
|
|
||||||
<h3 class="text-xl font-light mb-4">Weather Summary</h3>
|
|
||||||
<div class="space-y-4">
|
|
||||||
<div class="flex items-center justify-between">
|
|
||||||
<span class="text-sm">Average Temperature</span>
|
|
||||||
<span class="text-sm font-medium">68°F</span>
|
|
||||||
</div>
|
|
||||||
<div class="flex items-center justify-between">
|
|
||||||
<span class="text-sm">Sunny Days</span>
|
|
||||||
<span class="text-sm font-medium">11 of 14</span>
|
|
||||||
</div>
|
|
||||||
<div class="flex items-center justify-between">
|
|
||||||
<span class="text-sm">Rain Days</span>
|
|
||||||
<span class="text-sm font-medium">2 of 14</span>
|
|
||||||
</div>
|
|
||||||
<div class="flex items-center justify-between">
|
|
||||||
<span class="text-sm">Best Weather</span>
|
|
||||||
<span class="text-sm font-medium">Utah, Nevada</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Trip Notes -->
|
|
||||||
<div class="bg-white border border-gray-200 rounded-lg p-6">
|
|
||||||
<h3 class="text-xl font-light mb-4">Trip Notes</h3>
|
|
||||||
<div class="space-y-3 text-sm text-gray-700">
|
|
||||||
<p>Perfect timing for fall foliage in the Midwest. Colorado mountains were breathtaking with early snow caps.</p>
|
|
||||||
<p>Route 66 sections in Illinois and Missouri provided authentic American road trip experience.</p>
|
|
||||||
<p>Utah's landscape diversity exceeded expectations - from desert to mountain passes.</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
|
|
@ -1,189 +0,0 @@
|
||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
||||||
<title>Coast to Coast Adventure - Trip Details</title>
|
|
||||||
<script src="https://cdn.tailwindcss.com"></script>
|
|
||||||
<style>
|
|
||||||
.map-placeholder {
|
|
||||||
background: linear-gradient(45deg, #f3f4f6 25%, transparent 25%),
|
|
||||||
linear-gradient(-45deg, #f3f4f6 25%, transparent 25%),
|
|
||||||
linear-gradient(45deg, transparent 75%, #f3f4f6 75%),
|
|
||||||
linear-gradient(-45deg, transparent 75%, #f3f4f6 75%);
|
|
||||||
background-size: 20px 20px;
|
|
||||||
background-position: 0 0, 0 10px, 10px -10px, -10px 0px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.photo-placeholder {
|
|
||||||
background: linear-gradient(135deg, #e5e7eb 0%, #f9fafb 50%, #e5e7eb 100%);
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body class="bg-gray-50 text-black font-sans antialiased">
|
|
||||||
<!-- Main Container -->
|
|
||||||
<div class="min-h-screen p-4">
|
|
||||||
<div class="max-w-4xl mx-auto">
|
|
||||||
|
|
||||||
<!-- Compact Header -->
|
|
||||||
<header class="mb-6 bg-white rounded-lg p-4 border border-gray-200">
|
|
||||||
<div class="flex items-center justify-between">
|
|
||||||
<div>
|
|
||||||
<h1 class="text-2xl font-light tracking-tight">Coast to Coast Adventure</h1>
|
|
||||||
<p class="text-sm text-gray-600">NYC → SF • Oct 2024</p>
|
|
||||||
</div>
|
|
||||||
<div class="text-right text-sm">
|
|
||||||
<div class="text-lg font-light">2,908 mi</div>
|
|
||||||
<div class="text-gray-600">14 days</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</header>
|
|
||||||
|
|
||||||
<!-- Main Layout -->
|
|
||||||
<div class="grid grid-cols-1 lg:grid-cols-3 gap-4">
|
|
||||||
|
|
||||||
<!-- Map Section -->
|
|
||||||
<div class="lg:col-span-2">
|
|
||||||
<div class="bg-white border border-gray-200 rounded-lg overflow-hidden">
|
|
||||||
<div class="map-placeholder h-64 lg:h-80 flex items-center justify-center">
|
|
||||||
<div class="text-center">
|
|
||||||
<div class="w-12 h-12 mx-auto mb-2 bg-gray-300 rounded-full flex items-center justify-center">
|
|
||||||
<svg class="w-6 h-6 text-gray-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17.657 16.657L13.414 20.9a1.998 1.998 0 01-2.827 0l-4.244-4.243a8 8 0 1111.314 0z"></path>
|
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 11a3 3 0 11-6 0 3 3 0 016 0z"></path>
|
|
||||||
</svg>
|
|
||||||
</div>
|
|
||||||
<p class="text-xs text-gray-500">Route Map</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Compact Data Panel -->
|
|
||||||
<div class="space-y-4">
|
|
||||||
<!-- Stats -->
|
|
||||||
<div class="bg-white border border-gray-200 rounded-lg p-4">
|
|
||||||
<h3 class="text-sm font-medium mb-3 text-gray-800">Trip Stats</h3>
|
|
||||||
<div class="space-y-2 text-sm">
|
|
||||||
<div class="flex justify-between">
|
|
||||||
<span class="text-gray-600">Distance</span>
|
|
||||||
<span class="font-medium">2,908 mi</span>
|
|
||||||
</div>
|
|
||||||
<div class="flex justify-between">
|
|
||||||
<span class="text-gray-600">Duration</span>
|
|
||||||
<span class="font-medium">14 days</span>
|
|
||||||
</div>
|
|
||||||
<div class="flex justify-between">
|
|
||||||
<span class="text-gray-600">States</span>
|
|
||||||
<span class="font-medium">12</span>
|
|
||||||
</div>
|
|
||||||
<div class="flex justify-between">
|
|
||||||
<span class="text-gray-600">Photos</span>
|
|
||||||
<span class="font-medium">247</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Compact States List -->
|
|
||||||
<div class="bg-white border border-gray-200 rounded-lg p-4">
|
|
||||||
<h3 class="text-sm font-medium mb-3 text-gray-800">Route</h3>
|
|
||||||
<div class="text-xs text-gray-600 leading-relaxed">
|
|
||||||
NY → PA → OH → IN → IL → IA → NE → CO → UT → NV → CA
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Compact Photos -->
|
|
||||||
<div class="bg-white border border-gray-200 rounded-lg p-4">
|
|
||||||
<h3 class="text-sm font-medium mb-3 text-gray-800">Highlights</h3>
|
|
||||||
<div class="grid grid-cols-2 gap-2">
|
|
||||||
<div class="photo-placeholder h-16 rounded flex items-center justify-center">
|
|
||||||
<svg class="w-4 h-4 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 16l4.586-4.586a2 2 0 012.828 0L16 16m-2-2l1.586-1.586a2 2 0 012.828 0L20 14m-6-6h.01M6 20h12a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v12a2 2 0 002 2z"></path>
|
|
||||||
</svg>
|
|
||||||
</div>
|
|
||||||
<div class="photo-placeholder h-16 rounded flex items-center justify-center">
|
|
||||||
<svg class="w-4 h-4 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 16l4.586-4.586a2 2 0 012.828 0L16 16m-2-2l1.586-1.586a2 2 0 012.828 0L20 14m-6-6h.01M6 20h12a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v12a2 2 0 002 2z"></path>
|
|
||||||
</svg>
|
|
||||||
</div>
|
|
||||||
<div class="photo-placeholder h-16 rounded flex items-center justify-center">
|
|
||||||
<svg class="w-4 h-4 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 16l4.586-4.586a2 2 0 012.828 0L16 16m-2-2l1.586-1.586a2 2 0 012.828 0L20 14m-6-6h.01M6 20h12a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v12a2 2 0 002 2z"></path>
|
|
||||||
</svg>
|
|
||||||
</div>
|
|
||||||
<div class="photo-placeholder h-16 rounded flex items-center justify-center">
|
|
||||||
<svg class="w-4 h-4 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 16l4.586-4.586a2 2 0 012.828 0L16 16m-2-2l1.586-1.586a2 2 0 012.828 0L20 14m-6-6h.01M6 20h12a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v12a2 2 0 002 2z"></path>
|
|
||||||
</svg>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Compact Additional Details -->
|
|
||||||
<div class="grid grid-cols-1 md:grid-cols-3 gap-4 mt-4">
|
|
||||||
<!-- Key Stops -->
|
|
||||||
<div class="bg-white border border-gray-200 rounded-lg p-4">
|
|
||||||
<h3 class="text-sm font-medium mb-3 text-gray-800">Key Stops</h3>
|
|
||||||
<div class="space-y-1 text-xs">
|
|
||||||
<div class="flex justify-between py-1">
|
|
||||||
<span>Times Square</span>
|
|
||||||
<span class="text-gray-500">Day 1</span>
|
|
||||||
</div>
|
|
||||||
<div class="flex justify-between py-1">
|
|
||||||
<span>Chicago</span>
|
|
||||||
<span class="text-gray-500">Day 4</span>
|
|
||||||
</div>
|
|
||||||
<div class="flex justify-between py-1">
|
|
||||||
<span>Rocky Mountains</span>
|
|
||||||
<span class="text-gray-500">Day 8</span>
|
|
||||||
</div>
|
|
||||||
<div class="flex justify-between py-1">
|
|
||||||
<span>Arches NP</span>
|
|
||||||
<span class="text-gray-500">Day 10</span>
|
|
||||||
</div>
|
|
||||||
<div class="flex justify-between py-1">
|
|
||||||
<span>Golden Gate</span>
|
|
||||||
<span class="text-gray-500">Day 14</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Weather -->
|
|
||||||
<div class="bg-white border border-gray-200 rounded-lg p-4">
|
|
||||||
<h3 class="text-sm font-medium mb-3 text-gray-800">Weather</h3>
|
|
||||||
<div class="space-y-1 text-xs">
|
|
||||||
<div class="flex justify-between py-1">
|
|
||||||
<span>Avg Temp</span>
|
|
||||||
<span class="font-medium">68°F</span>
|
|
||||||
</div>
|
|
||||||
<div class="flex justify-between py-1">
|
|
||||||
<span>Sunny Days</span>
|
|
||||||
<span class="font-medium">11/14</span>
|
|
||||||
</div>
|
|
||||||
<div class="flex justify-between py-1">
|
|
||||||
<span>Rain Days</span>
|
|
||||||
<span class="font-medium">2/14</span>
|
|
||||||
</div>
|
|
||||||
<div class="flex justify-between py-1">
|
|
||||||
<span>Best</span>
|
|
||||||
<span class="font-medium">Utah, Nevada</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Quick Notes -->
|
|
||||||
<div class="bg-white border border-gray-200 rounded-lg p-4">
|
|
||||||
<h3 class="text-sm font-medium mb-3 text-gray-800">Notes</h3>
|
|
||||||
<div class="text-xs text-gray-700 space-y-2">
|
|
||||||
<p>Fall foliage in Midwest was perfect timing.</p>
|
|
||||||
<p>Route 66 sections provided authentic experience.</p>
|
|
||||||
<p>Utah landscape diversity exceeded expectations.</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
|
|
@ -21,6 +21,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
|
||||||
|
|
||||||
- [ ] In the User Settings, you can now import your user data from a zip file. It will import all the data from the zip file, listed above. It will also start stats recalculation.
|
- [ ] In the User Settings, you can now import your user data from a zip file. It will import all the data from the zip file, listed above. It will also start stats recalculation.
|
||||||
- [ ] User can select to override settings or not.
|
- [ ] User can select to override settings or not.
|
||||||
|
- [ ] Check distance units if they are correct
|
||||||
|
- [ ] Why import creates more points than the original?
|
||||||
|
|
||||||
- Export file size is now displayed in the exports and imports lists.
|
- Export file size is now displayed in the exports and imports lists.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -29,7 +29,7 @@ class Point < ApplicationRecord
|
||||||
scope :visited, -> { where.not(visit_id: nil) }
|
scope :visited, -> { where.not(visit_id: nil) }
|
||||||
scope :not_visited, -> { where(visit_id: nil) }
|
scope :not_visited, -> { where(visit_id: nil) }
|
||||||
|
|
||||||
after_create :async_reverse_geocode, if: -> { DawarichSettings.store_geodata? }
|
after_create :async_reverse_geocode, if: -> { DawarichSettings.store_geodata? && !reverse_geocoded? }
|
||||||
after_create :set_country
|
after_create :set_country
|
||||||
after_create_commit :broadcast_coordinates
|
after_create_commit :broadcast_coordinates
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,8 @@
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
class Users::ImportData::Points
|
class Users::ImportData::Points
|
||||||
|
BATCH_SIZE = 1000
|
||||||
|
|
||||||
def initialize(user, points_data)
|
def initialize(user, points_data)
|
||||||
@user = user
|
@user = user
|
||||||
@points_data = points_data
|
@points_data = points_data
|
||||||
|
|
@ -11,83 +13,81 @@ class Users::ImportData::Points
|
||||||
|
|
||||||
Rails.logger.info "Importing #{points_data.size} points for user: #{user.email}"
|
Rails.logger.info "Importing #{points_data.size} points for user: #{user.email}"
|
||||||
|
|
||||||
points_created = 0
|
# Pre-load reference data for efficient bulk processing
|
||||||
skipped_invalid = 0
|
preload_reference_data
|
||||||
|
|
||||||
|
# Filter valid points and prepare for bulk import
|
||||||
|
valid_points = filter_and_prepare_points
|
||||||
|
|
||||||
|
if valid_points.empty?
|
||||||
|
Rails.logger.info "No valid points to import"
|
||||||
|
return 0
|
||||||
|
end
|
||||||
|
|
||||||
|
# Remove duplicates based on unique constraint
|
||||||
|
deduplicated_points = deduplicate_points(valid_points)
|
||||||
|
|
||||||
|
Rails.logger.info "Prepared #{deduplicated_points.size} unique valid points (#{points_data.size - deduplicated_points.size} duplicates/invalid skipped)"
|
||||||
|
|
||||||
|
# Bulk import in batches
|
||||||
|
total_created = bulk_import_points(deduplicated_points)
|
||||||
|
|
||||||
|
Rails.logger.info "Points import completed. Created: #{total_created}"
|
||||||
|
total_created
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
attr_reader :user, :points_data, :imports_lookup, :countries_lookup, :visits_lookup
|
||||||
|
|
||||||
|
def preload_reference_data
|
||||||
|
# Pre-load imports for this user
|
||||||
|
@imports_lookup = user.imports.index_by { |import|
|
||||||
|
[import.name, import.source, import.created_at.to_s]
|
||||||
|
}
|
||||||
|
|
||||||
|
# Pre-load all countries for efficient lookup
|
||||||
|
@countries_lookup = {}
|
||||||
|
Country.all.each do |country|
|
||||||
|
# Index by all possible lookup keys
|
||||||
|
@countries_lookup[[country.name, country.iso_a2, country.iso_a3]] = country
|
||||||
|
@countries_lookup[country.name] = country
|
||||||
|
end
|
||||||
|
|
||||||
|
# Pre-load visits for this user
|
||||||
|
@visits_lookup = user.visits.index_by { |visit|
|
||||||
|
[visit.name, visit.started_at.to_s, visit.ended_at.to_s]
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
def filter_and_prepare_points
|
||||||
|
valid_points = []
|
||||||
|
skipped_count = 0
|
||||||
|
|
||||||
points_data.each do |point_data|
|
points_data.each do |point_data|
|
||||||
next unless point_data.is_a?(Hash)
|
next unless point_data.is_a?(Hash)
|
||||||
|
|
||||||
# Skip points with invalid or missing required data
|
# Skip points with invalid or missing required data
|
||||||
unless valid_point_data?(point_data)
|
unless valid_point_data?(point_data)
|
||||||
skipped_invalid += 1
|
skipped_count += 1
|
||||||
next
|
next
|
||||||
end
|
end
|
||||||
|
|
||||||
# Check if point already exists (match by coordinates, timestamp, and user)
|
# Prepare point attributes for bulk insert
|
||||||
if point_exists?(point_data)
|
prepared_attributes = prepare_point_attributes(point_data)
|
||||||
|
unless prepared_attributes
|
||||||
|
skipped_count += 1
|
||||||
next
|
next
|
||||||
end
|
end
|
||||||
|
|
||||||
# Create new point
|
valid_points << prepared_attributes
|
||||||
point_record = create_point_record(point_data)
|
|
||||||
points_created += 1 if point_record
|
|
||||||
|
|
||||||
if points_created % 1000 == 0
|
|
||||||
Rails.logger.debug "Imported #{points_created} points..."
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
if skipped_invalid > 0
|
if skipped_count > 0
|
||||||
Rails.logger.warn "Skipped #{skipped_invalid} points with invalid or missing required data"
|
Rails.logger.warn "Skipped #{skipped_count} points with invalid or missing required data"
|
||||||
end
|
end
|
||||||
|
|
||||||
Rails.logger.info "Points import completed. Created: #{points_created}"
|
valid_points
|
||||||
points_created
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
attr_reader :user, :points_data
|
|
||||||
|
|
||||||
def point_exists?(point_data)
|
|
||||||
return false unless point_data['lonlat'].present? && point_data['timestamp'].present?
|
|
||||||
|
|
||||||
Point.exists?(
|
|
||||||
lonlat: point_data['lonlat'],
|
|
||||||
timestamp: point_data['timestamp'],
|
|
||||||
user_id: user.id
|
|
||||||
)
|
|
||||||
rescue StandardError => e
|
|
||||||
Rails.logger.debug "Error checking if point exists: #{e.message}"
|
|
||||||
false
|
|
||||||
end
|
|
||||||
|
|
||||||
def create_point_record(point_data)
|
|
||||||
point_attributes = prepare_point_attributes(point_data)
|
|
||||||
|
|
||||||
begin
|
|
||||||
# Create point and skip the automatic country assignment callback since we're handling it manually
|
|
||||||
point = Point.create!(point_attributes)
|
|
||||||
|
|
||||||
# If we have a country assigned via country_info, update the point to set it
|
|
||||||
if point_attributes[:country].present?
|
|
||||||
point.update_column(:country_id, point_attributes[:country].id)
|
|
||||||
point.reload
|
|
||||||
end
|
|
||||||
|
|
||||||
point
|
|
||||||
rescue ActiveRecord::RecordInvalid => e
|
|
||||||
Rails.logger.error "Failed to create point: #{e.message}"
|
|
||||||
Rails.logger.error "Point data: #{point_data.inspect}"
|
|
||||||
Rails.logger.error "Prepared attributes: #{point_attributes.inspect}"
|
|
||||||
nil
|
|
||||||
rescue StandardError => e
|
|
||||||
Rails.logger.error "Unexpected error creating point: #{e.message}"
|
|
||||||
Rails.logger.error "Point data: #{point_data.inspect}"
|
|
||||||
Rails.logger.error "Prepared attributes: #{point_attributes.inspect}"
|
|
||||||
Rails.logger.error "Backtrace: #{e.backtrace.first(5).join('\n')}"
|
|
||||||
nil
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def prepare_point_attributes(point_data)
|
def prepare_point_attributes(point_data)
|
||||||
|
|
@ -99,68 +99,124 @@ class Users::ImportData::Points
|
||||||
'country_info',
|
'country_info',
|
||||||
'visit_reference',
|
'visit_reference',
|
||||||
'country' # Exclude the string country field - handled via country_info relationship
|
'country' # Exclude the string country field - handled via country_info relationship
|
||||||
).merge(user: user)
|
)
|
||||||
|
|
||||||
# Handle lonlat reconstruction if missing (for backward compatibility)
|
# Handle lonlat reconstruction if missing (for backward compatibility)
|
||||||
ensure_lonlat_field(attributes, point_data)
|
ensure_lonlat_field(attributes, point_data)
|
||||||
|
|
||||||
# Find and assign related records
|
# Remove longitude/latitude after lonlat reconstruction to ensure consistent keys
|
||||||
assign_import_reference(attributes, point_data['import_reference'])
|
attributes.delete('longitude')
|
||||||
assign_country_reference(attributes, point_data['country_info'])
|
attributes.delete('latitude')
|
||||||
assign_visit_reference(attributes, point_data['visit_reference'])
|
|
||||||
|
|
||||||
attributes
|
# Add required attributes for bulk insert
|
||||||
|
attributes['user_id'] = user.id
|
||||||
|
attributes['created_at'] = Time.current
|
||||||
|
attributes['updated_at'] = Time.current
|
||||||
|
|
||||||
|
# Resolve foreign key relationships
|
||||||
|
resolve_import_reference(attributes, point_data['import_reference'])
|
||||||
|
resolve_country_reference(attributes, point_data['country_info'])
|
||||||
|
resolve_visit_reference(attributes, point_data['visit_reference'])
|
||||||
|
|
||||||
|
# Convert string keys to symbols for consistency with Point model
|
||||||
|
attributes.symbolize_keys
|
||||||
|
rescue StandardError => e
|
||||||
|
Rails.logger.error "Failed to prepare point attributes: #{e.message}"
|
||||||
|
Rails.logger.error "Point data: #{point_data.inspect}"
|
||||||
|
nil
|
||||||
end
|
end
|
||||||
|
|
||||||
def assign_import_reference(attributes, import_reference)
|
def resolve_import_reference(attributes, import_reference)
|
||||||
return unless import_reference.is_a?(Hash)
|
return unless import_reference.is_a?(Hash)
|
||||||
|
|
||||||
import = user.imports.find_by(
|
import_key = [
|
||||||
name: import_reference['name'],
|
import_reference['name'],
|
||||||
source: import_reference['source'],
|
import_reference['source'],
|
||||||
created_at: import_reference['created_at']
|
import_reference['created_at']
|
||||||
)
|
]
|
||||||
|
|
||||||
attributes[:import] = import if import
|
import = imports_lookup[import_key]
|
||||||
|
attributes['import_id'] = import.id if import
|
||||||
end
|
end
|
||||||
|
|
||||||
def assign_country_reference(attributes, country_info)
|
def resolve_country_reference(attributes, country_info)
|
||||||
return unless country_info.is_a?(Hash)
|
return unless country_info.is_a?(Hash)
|
||||||
|
|
||||||
# Try to find country by all attributes first
|
# Try to find country by all attributes first
|
||||||
country = Country.find_by(
|
country_key = [country_info['name'], country_info['iso_a2'], country_info['iso_a3']]
|
||||||
name: country_info['name'],
|
country = countries_lookup[country_key]
|
||||||
iso_a2: country_info['iso_a2'],
|
|
||||||
iso_a3: country_info['iso_a3']
|
|
||||||
)
|
|
||||||
|
|
||||||
# If not found by all attributes, try to find by name only
|
# If not found by all attributes, try to find by name only
|
||||||
if country.nil? && country_info['name'].present?
|
if country.nil? && country_info['name'].present?
|
||||||
country = Country.find_by(name: country_info['name'])
|
country = countries_lookup[country_info['name']]
|
||||||
end
|
end
|
||||||
|
|
||||||
# If still not found, create a new country record with minimal data
|
# If still not found, create a new country record
|
||||||
if country.nil? && country_info['name'].present?
|
if country.nil? && country_info['name'].present?
|
||||||
country = Country.find_or_create_by(name: country_info['name']) do |new_country|
|
country = create_missing_country(country_info)
|
||||||
|
# Add to lookup cache for subsequent points
|
||||||
|
@countries_lookup[country_info['name']] = country
|
||||||
|
@countries_lookup[[country.name, country.iso_a2, country.iso_a3]] = country
|
||||||
|
end
|
||||||
|
|
||||||
|
attributes['country_id'] = country.id if country
|
||||||
|
end
|
||||||
|
|
||||||
|
def create_missing_country(country_info)
|
||||||
|
Country.find_or_create_by(name: country_info['name']) do |new_country|
|
||||||
new_country.iso_a2 = country_info['iso_a2'] || country_info['name'][0..1].upcase
|
new_country.iso_a2 = country_info['iso_a2'] || country_info['name'][0..1].upcase
|
||||||
new_country.iso_a3 = country_info['iso_a3'] || country_info['name'][0..2].upcase
|
new_country.iso_a3 = country_info['iso_a3'] || country_info['name'][0..2].upcase
|
||||||
new_country.geom = "MULTIPOLYGON (((0 0, 1 0, 1 1, 0 1, 0 0)))" # Default geometry
|
new_country.geom = "MULTIPOLYGON (((0 0, 1 0, 1 1, 0 1, 0 0)))" # Default geometry
|
||||||
end
|
end
|
||||||
|
rescue StandardError => e
|
||||||
|
Rails.logger.error "Failed to create missing country: #{e.message}"
|
||||||
|
nil
|
||||||
end
|
end
|
||||||
|
|
||||||
attributes[:country] = country if country
|
def resolve_visit_reference(attributes, visit_reference)
|
||||||
end
|
|
||||||
|
|
||||||
def assign_visit_reference(attributes, visit_reference)
|
|
||||||
return unless visit_reference.is_a?(Hash)
|
return unless visit_reference.is_a?(Hash)
|
||||||
|
|
||||||
visit = user.visits.find_by(
|
visit_key = [
|
||||||
name: visit_reference['name'],
|
visit_reference['name'],
|
||||||
started_at: visit_reference['started_at'],
|
visit_reference['started_at'],
|
||||||
ended_at: visit_reference['ended_at']
|
visit_reference['ended_at']
|
||||||
|
]
|
||||||
|
|
||||||
|
visit = visits_lookup[visit_key]
|
||||||
|
attributes['visit_id'] = visit.id if visit
|
||||||
|
end
|
||||||
|
|
||||||
|
def deduplicate_points(points)
|
||||||
|
points.uniq { |point| [point[:lonlat], point[:timestamp], point[:user_id]] }
|
||||||
|
end
|
||||||
|
|
||||||
|
def bulk_import_points(points)
|
||||||
|
total_created = 0
|
||||||
|
|
||||||
|
points.each_slice(BATCH_SIZE) do |batch|
|
||||||
|
begin
|
||||||
|
# Use upsert_all to efficiently bulk insert/update points
|
||||||
|
result = Point.upsert_all(
|
||||||
|
batch,
|
||||||
|
unique_by: %i[lonlat timestamp user_id],
|
||||||
|
returning: %w[id],
|
||||||
|
on_duplicate: :skip
|
||||||
)
|
)
|
||||||
|
|
||||||
attributes[:visit] = visit if visit
|
batch_created = result.count
|
||||||
|
total_created += batch_created
|
||||||
|
|
||||||
|
Rails.logger.debug "Processed batch of #{batch.size} points, created #{batch_created}, total created: #{total_created}"
|
||||||
|
|
||||||
|
rescue StandardError => e
|
||||||
|
Rails.logger.error "Failed to process point batch: #{e.message}"
|
||||||
|
Rails.logger.error "Batch size: #{batch.size}"
|
||||||
|
Rails.logger.error "Backtrace: #{e.backtrace.first(3).join('\n')}"
|
||||||
|
# Continue with next batch instead of failing completely
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
total_created
|
||||||
end
|
end
|
||||||
|
|
||||||
def valid_point_data?(point_data)
|
def valid_point_data?(point_data)
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue