@@ -5,6 +5,8 @@ import { WorldMap } from './components/WorldMap';
55import { VisitList } from './components/VisitList' ;
66import { CalendarInput } from './components/CalendarInput' ;
77import { AddVisitForm } from './components/AddVisitForm' ;
8+ import { StatsPanel } from './components/StatsPanel' ;
9+ import { DateRangeFilter } from './components/DateRangeFilter' ;
810import { LegalPage } from './components/LegalPage' ;
911import {
1012 saveVisits ,
@@ -36,10 +38,18 @@ function App() {
3638 } ) ;
3739 const [ showAddForm , setShowAddForm ] = useState ( false ) ;
3840 const [ showCalendarInput , setShowCalendarInput ] = useState ( false ) ;
41+ const [ showStats , setShowStats ] = useState ( false ) ;
3942 const [ highlightedCountry , setHighlightedCountry ] = useState < string > ( ) ;
4043 const [ homeCountry , setHomeCountry ] = useState < string > ( ( ) => loadHomeCountry ( ) ) ;
44+ const [ flyHomeCounter , setFlyHomeCounter ] = useState ( 0 ) ;
4145 const [ page , setPage ] = useState < Page > ( ( ) => parseHash ( window . location . hash ) ) ;
4246
47+ const statsAvailable = dateRange . end . getFullYear ( ) - dateRange . start . getFullYear ( ) >= 2 ;
48+
49+ useEffect ( ( ) => {
50+ if ( ! statsAvailable ) setShowStats ( false ) ;
51+ } , [ statsAvailable ] ) ;
52+
4353 // Listen for hash changes
4454 useEffect ( ( ) => {
4555 const onHashChange = ( ) => setPage ( parseHash ( window . location . hash ) ) ;
@@ -180,7 +190,7 @@ function App() {
180190 < div className = "max-w-7xl mx-auto flex items-center justify-between" >
181191 < h1 className = "text-lg md:text-xl font-bold text-gray-900 dark:text-white" > tripm.app</ h1 >
182192 < p className = "text-xs md:text-sm text-gray-500 dark:text-gray-400" >
183- Extract travel history from your calendar
193+ Visualise your travel history
184194 </ p >
185195 </ div >
186196 </ header >
@@ -190,49 +200,64 @@ function App() {
190200 ) : (
191201 < main className = "flex-1 max-w-7xl mx-auto w-full p-4 space-y-4" >
192202 { /* Controls */ }
193- < div className = "bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-lg p-4" >
194- < div className = "flex flex-wrap items-center justify-between gap-4" >
195- < div className = "flex flex-wrap gap-2" >
196- < button
197- onClick = { ( ) => setShowCalendarInput ( ! showCalendarInput ) }
198- className = "px-4 py-2 bg-blue-600 text-white rounded-md hover:bg-blue-700 text-sm"
199- >
200- { showCalendarInput ? 'Cancel' : 'Import calendar' }
201- </ button >
202- < button
203- onClick = { ( ) => setShowAddForm ( ! showAddForm ) }
204- className = "px-4 py-2 bg-emerald-600 text-white rounded-md hover:bg-emerald-700 text-sm"
205- >
206- { showAddForm ? 'Cancel' : 'Add trip manually' }
207- </ button >
203+ < div className = "bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-lg" >
204+ { /* Row 1: Action buttons */ }
205+ < div className = "flex flex-wrap gap-2 p-4" >
206+ < button
207+ onClick = { ( ) => setShowCalendarInput ( ! showCalendarInput ) }
208+ className = "px-4 py-2 bg-blue-600 text-white rounded-md hover:bg-blue-700 text-sm"
209+ >
210+ { showCalendarInput ? 'Cancel' : 'Import calendar' }
211+ </ button >
212+ < button
213+ onClick = { ( ) => setShowAddForm ( ! showAddForm ) }
214+ className = "px-4 py-2 bg-emerald-600 text-white rounded-md hover:bg-emerald-700 text-sm"
215+ >
216+ { showAddForm ? 'Cancel' : 'Add trip' }
217+ </ button >
218+ < button
219+ onClick = { ( ) => setShowStats ( ! showStats ) }
220+ disabled = { ! statsAvailable || visits . length === 0 }
221+ className = "px-4 py-2 bg-gray-600 text-white rounded-md hover:bg-gray-700 text-sm disabled:opacity-40"
222+ >
223+ { showStats ? 'Hide stats' : 'Stats' }
224+ </ button >
225+ < button
226+ onClick = { handleExport }
227+ disabled = { visits . length === 0 }
228+ className = "px-4 py-2 bg-gray-600 text-white rounded-md hover:bg-gray-700 text-sm disabled:opacity-40"
229+ >
230+ Export
231+ </ button >
232+ < label className = "px-4 py-2 bg-gray-600 text-white rounded-md hover:bg-gray-700 text-sm cursor-pointer" >
233+ Import
234+ < input
235+ type = "file"
236+ accept = ".json"
237+ onChange = { handleImportJson }
238+ className = "hidden"
239+ />
240+ </ label >
241+ { visits . length > 0 && (
208242 < button
209- onClick = { handleExport }
210- disabled = { visits . length === 0 }
211- className = "px-4 py-2 bg-gray-600 text-white rounded-md hover:bg-gray-700 text-sm disabled:bg-gray-300 dark:disabled:bg-gray-600"
243+ onClick = { handleClearAll }
244+ className = "px-4 py-2 bg-red-700 text-white rounded-md hover:bg-red-800 text-sm"
212245 >
213- Export
246+ Reset
214247 </ button >
215- < label className = "px-4 py-2 bg-gray-600 text-white rounded-md hover:bg-gray-700 text-sm cursor-pointer" >
216- Import
217- < input
218- type = "file"
219- accept = ".json"
220- onChange = { handleImportJson }
221- className = "hidden"
222- />
223- </ label >
224- { visits . length > 0 && (
225- < button
226- onClick = { handleClearAll }
227- className = "px-4 py-2 bg-red-700 text-white rounded-md hover:bg-red-800 text-sm"
228- >
229- Reset
230- </ button >
231- ) }
232- </ div >
248+ ) }
249+ </ div >
250+
251+ { /* Row 2: Period filter, home selector, stats */ }
252+ < div className = "flex flex-wrap items-center justify-between gap-4 border-t border-gray-200 dark:border-gray-700 px-4 py-3" >
253+ < DateRangeFilter dateRange = { dateRange } onChange = { setDateRange } />
233254 < div className = "flex items-center gap-4" >
234255 < div className = "flex items-center gap-2" >
235- < label htmlFor = "home-country" className = "text-sm font-medium text-gray-700 dark:text-gray-300" >
256+ < label
257+ htmlFor = "home-country"
258+ className = { `text-sm text-gray-700 dark:text-gray-300 ${ homeCountry ? 'cursor-pointer hover:text-gray-900 dark:hover:text-white' : '' } ` }
259+ onClick = { ( ) => { if ( homeCountry ) setFlyHomeCounter ( ( c ) => c + 1 ) ; } }
260+ >
236261 Home
237262 </ label >
238263 < div className = "relative" >
@@ -287,14 +312,17 @@ function App() {
287312 />
288313 ) }
289314
315+ { /* Stats panel */ }
316+ { showStats && (
317+ < StatsPanel visits = { filteredVisits } />
318+ ) }
319+
290320 { /* List and map */ }
291321 < div className = "grid grid-cols-1 lg:grid-cols-3 gap-4 lg:h-[600px]" >
292322 { /* Visit list */ }
293323 < div className = "h-[400px] lg:h-auto bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-lg overflow-hidden" >
294324 < VisitList
295325 visits = { filteredVisits }
296- dateRange = { dateRange }
297- onDateRangeChange = { setDateRange }
298326 onDeleteVisit = { handleDeleteEntry }
299327 onDeleteCountry = { handleDeleteCountry }
300328 onEditEntry = { handleEditEntry }
@@ -307,6 +335,7 @@ function App() {
307335 < WorldMap
308336 visits = { visitsInDateRange }
309337 homeCountry = { homeCountry }
338+ flyHomeTrigger = { flyHomeCounter }
310339 onCountryClick = { setHighlightedCountry }
311340 />
312341 </ div >
0 commit comments