Reisen: Wohnorte mit Datum, Anreise je Besuch, Umzüge in der Kilometer-Rechnung

- Wohnorte als Liste mit "seit"-Datum (Tabelle travel_homes, ersetzt den
  einzelnen Wohnort; Migration Schritt 5 übernimmt ihn): Reisen starten
  beim damals gültigen Wohnort, Wechsel zählen als Umzug, Tage am
  Wohnort nicht als Reisetage
- "In dieser Zeit hier gewohnt" im Besuch: legt Wohnort ab Beginn und den
  vorherigen wieder ab Ende + 1 Tag an
- Anreise je Besuch (automatisch / von zuhause / vom vorherigen Ort):
  Rundreisen auch mit mehreren Tagen zwischen den Orten
- Fix: Besuche bleiben bearbeitbar, auch wenn eine Fotodatei fehlt;
  Speicherfehler erscheinen im Dialog statt verdeckt dahinter

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
guyg1474andClaude Opus 5.5 committed 2026-10-15 20:40:40 +02:00
1 parent 56c4f38ef0
commit 1d7497bead
11 files changed
+343 -113

No files matched your search

+25 -3
View File
@@ -6,7 +6,8 @@ import { imgUrl, uploadImage } from '../../api.js';
* Fotos werden beim Hochladen auf dem Server von Metadaten (GPS usw.) befreit.
* onSave(body) / onDelete() liefern bei Erfolg den neuen Stand (truthy).
*/
export default function VisitDialog({ ref, city, visit, onSave, onDelete, busy }) {
// pageError: Fehlermeldung der Seite (z. B. vom Speichern) – im Dialog anzeigen, sonst läge sie verdeckt dahinter
export default function VisitDialog({ ref, city, visit, onSave, onDelete, onLivedThere, busy, pageError }) {
const today = new Date().toISOString().slice(0, 10);
const [form, setForm] = useState(() => ({
dateFrom: visit?.dateFrom || today,
@@ -14,6 +15,8 @@ export default function VisitDialog({ ref, city, visit, onSave, onDelete, busy }
companions: visit?.companions || '',
notes: visit?.notes || '',
photos: visit?.photos || [],
// Anreise: '' automatisch, 'home' von zuhause, 'continued' vom vorherigen Ort weitergereist (Rundreise)
arrival: visit?.continued == null ? '' : visit.continued ? 'continued' : 'home',
}));
const [uploading, setUploading] = useState(0);
const [error, setError] = useState(null);
@@ -37,7 +40,9 @@ export default function VisitDialog({ ref, city, visit, onSave, onDelete, busy }
async function submit(e) {
e.preventDefault();
if (await onSave({ ...form, dateTo: form.dateTo || null })) ref.current.close();
const { arrival, ...rest } = form;
const continued = arrival === '' ? null : arrival === 'continued';
if (await onSave({ ...rest, dateTo: form.dateTo || null, continued })) ref.current.close();
}
async function remove() {
@@ -60,6 +65,14 @@ export default function VisitDialog({ ref, city, visit, onSave, onDelete, busy }
</label>
</div>
<p className="hint visit-hint" id="bis-hint">„Bis“ leer lassen für einen einzelnen Tag.</p>
<label>
Anreise
<select {...field('arrival')}>
<option value="">Automatisch (höchstens 1 Tag nach dem vorigen Besuch = Weiterreise)</option>
<option value="home">Von zuhause</option>
<option value="continued">Vom vorherigen Ort weitergereist (Rundreise)</option>
</select>
</label>
<label>
Begleitung <small>(optional)</small>
<input {...field('companions')} maxLength={200} placeholder="z. B. Anna, Ben" />
@@ -87,7 +100,16 @@ export default function VisitDialog({ ref, city, visit, onSave, onDelete, busy }
</button>
<p className="hint">Standort, Kamera und Aufnahmezeit werden beim Hochladen aus den Fotos entfernt.</p>
</fieldset>
{error && <p className="error">{error}</p>}
{visit?.dateTo && visit.dateTo !== visit.dateFrom && (
<p className="hint visit-lived">
Längerer Aufenthalt, z. B. Auslandssemester?{' '}
<button className="link-btn" type="button" disabled={busy} onClick={async () => { if (await onLivedThere(visit)) ref.current.close(); }}>
⌂ In dieser Zeit hier gewohnt
</button>
{' '}– macht {city.name} für diesen Zeitraum zum Wohnort (Strecken als Umzug, keine Reisetage).
</p>
)}
{(error || pageError) && <p className="error" role="alert">{error || pageError}</p>}
<div className="form-actions">
{visit && <button className="link-btn danger task-delete" type="button" disabled={busy} onClick={remove}>Löschen</button>}
<button className="btn ghost" type="button" onClick={() => ref.current.close()}>Abbrechen</button>