Reisen: Wohnort und nachvollziehbare Kilometer-Rechnung
- Wohnort pro Konto (Tabelle travel_home), im Tab "Statistik" über die Städtesuche festlegen, auf der Karte als ⌂ - Kilometer (route.js): jede Reise mit Hin- und Heimweg ab Wohnort; Reisen mit höchstens einem Tag Abstand als Rundreise; Besuche während eines längeren Aufenthalts als Ausflug hin und zurück; ohne Wohnort werden Reisen direkt verbunden - "Wie gerechnet?" listet alle Teilstrecken; Reisetage zählen jeden Tag nur einmal (Ausflüge im Aufenthalt nicht doppelt) - Wohnort-Suche mit eigenen IDs (zwei Suchfelder auf der Seite) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
fdbb31065f
commit
56c4f38ef0
10 files changed
+210
-22
No files matched your search
@@ -1,5 +1,7 @@
|
||||
import LAENDER from '../../data/laender.json';
|
||||
import { BY_CODE, CONTINENTS, continentOf, countryName, distanceKm, num } from './laender.js';
|
||||
import CitySearch from './CitySearch.jsx';
|
||||
import { BY_CODE, CONTINENTS, continentOf, countryName, num } from './laender.js';
|
||||
import { travelDays, travelRoute } from './route.js';
|
||||
|
||||
const pct = (part, total) => (total ? (part / total) * 100 : 0);
|
||||
const fmtPct = (p) => `${p.toLocaleString('de-DE', { maximumFractionDigits: p < 10 ? 1 : 0 })} %`;
|
||||
@@ -9,7 +11,7 @@ const TOTAL_AREA = LAENDER.reduce((s, c) => s + (c.area || 0), 0);
|
||||
* Statistik: Länder (Welt und je Kontinent), Fläche, äußerste Punkte, Kilometer entlang der Zeitleiste.
|
||||
* Grundlage für "Welt": alle Länder und Gebiete der Länderdaten (world-countries).
|
||||
*/
|
||||
export default function TravelStats({ visited, cities, visits, wishes, onSelectCity }) {
|
||||
export default function TravelStats({ visited, cities, visits, wishes, home, onSetHome, onRemoveHome, busy, onSelectCity }) {
|
||||
const codes = [...visited].filter((c) => BY_CODE.has(c));
|
||||
const area = codes.reduce((s, c) => s + (BY_CODE.get(c).area || 0), 0);
|
||||
const continents = CONTINENTS.map((name) => {
|
||||
@@ -27,12 +29,14 @@ export default function TravelStats({ visited, cities, visits, wishes, onSelectC
|
||||
['Westlichster Ort', extreme((c) => -c.lon), (c) => `${Math.abs(c.lon).toFixed(1).replace('.', ',')}° ${c.lon < 0 ? 'W' : 'O'}`],
|
||||
];
|
||||
|
||||
// Kilometer: Besuche in zeitlicher Reihenfolge, Luftlinie von Ort zu Ort
|
||||
const byId = new Map(cities.map((c) => [c.id, c]));
|
||||
const route = [...visits].sort((a, b) => a.dateFrom.localeCompare(b.dateFrom)).map((v) => byId.get(v.cityId)).filter(Boolean);
|
||||
let km = 0;
|
||||
for (let i = 1; i < route.length; i += 1) if (route[i].id !== route[i - 1].id) km += distanceKm(route[i - 1], route[i]);
|
||||
const days = visits.reduce((s, v) => s + 1 + (v.dateTo ? Math.round((new Date(v.dateTo) - new Date(v.dateFrom)) / 864e5) : 0), 0);
|
||||
// Kilometer: Reisen ab Wohnort, Rundreisen und Ausflüge (siehe route.js)
|
||||
const route = travelRoute(visits, cities, home);
|
||||
const days = travelDays(visits);
|
||||
const summary = [
|
||||
`${route.trips} ${route.trips === 1 ? 'Reise' : 'Reisen'}${home ? ` ab ${home.name}` : ''}`,
|
||||
route.excursions && `${route.excursions} ${route.excursions === 1 ? 'Ausflug' : 'Ausflüge'}`,
|
||||
`${days} Reisetage`,
|
||||
].filter(Boolean).join(' · ');
|
||||
|
||||
return (
|
||||
<div className="travel-stats">
|
||||
@@ -41,11 +45,39 @@ export default function TravelStats({ visited, cities, visits, wishes, onSelectC
|
||||
<div className="stat-tile"><strong>{fmtPct(pct(area, TOTAL_AREA))}</strong><span>der Landfläche ({num(Math.round(area))} km²)</span></div>
|
||||
<div className="stat-tile"><strong>{cities.length}</strong><span>{cities.length === 1 ? 'Stadt' : 'Städte'} besucht · {wishes} auf der Wunschliste</span></div>
|
||||
<div className="stat-tile">
|
||||
<strong>{route.length > 1 ? `${num(Math.round(km))} km` : '–'}</strong>
|
||||
<span>{route.length > 1 ? `Luftlinie entlang ${visits.length} Besuchen (${days} Reisetage)` : 'Besuche mit Datum eintragen, um Kilometer zu sehen'}</span>
|
||||
<strong>{route.legs.length ? `${num(Math.round(route.km))} km` : '–'}</strong>
|
||||
<span>{route.legs.length ? `Luftlinie · ${summary}` : 'Besuche mit Datum eintragen, um Kilometer zu sehen'}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<section className="stat-home" aria-labelledby="stat-wohnort">
|
||||
<h2 className="section-title" id="stat-wohnort">Wohnort</h2>
|
||||
{home ? (
|
||||
<p className="stat-home-current">
|
||||
⌂ <strong>{home.name}</strong> <small>{countryName(home.country)}</small>
|
||||
<button className="link-btn danger" type="button" disabled={busy} onClick={onRemoveHome}>Entfernen</button>
|
||||
</p>
|
||||
) : (
|
||||
<p className="hint">Mit Wohnort zählt jede Reise als Hin- und Rückweg ab zuhause.</p>
|
||||
)}
|
||||
<CitySearch mode="home" cities={[]} onAdd={onSetHome} busy={busy} />
|
||||
{route.legs.length > 0 && (
|
||||
<details className="stat-route">
|
||||
<summary>Wie gerechnet? ({route.legs.length} Teilstrecken)</summary>
|
||||
<ol>
|
||||
{route.legs.map((l, i) => (
|
||||
<li key={i}>
|
||||
<span>{l.from} → {l.to}</span>
|
||||
<small>{l.kind}</small>
|
||||
<strong>{num(Math.round(l.km))} km</strong>
|
||||
</li>
|
||||
))}
|
||||
</ol>
|
||||
{!home && <p className="hint">Ohne Wohnort fehlen Hin- und Heimwege; die Reisen werden direkt nacheinander verbunden.</p>}
|
||||
</details>
|
||||
)}
|
||||
</section>
|
||||
|
||||
<section className="stat-continents" aria-labelledby="stat-kontinente">
|
||||
<h2 className="section-title" id="stat-kontinente">Kontinente</h2>
|
||||
{continents.map((c) => (
|
||||
|
||||
Reference in new issue
Block a user