Add transport sync button and allow dropping stays onto band-covered days

- Auto-created transports carry auto_ref {from,to} stay ids; new
  POST /api/trips/:id/transports/regenerate reconciles them against the
  current stay order (re-date/re-title kept bridges preserving mode and
  price, delete orphans, create missing) without touching manual
  transports or flight-covered gaps
- Sync transports button in the calendar header with result toast
- The week band strip is now a drop target resolving the day from the
  pointer position, so stays can be dropped onto spots covered by other
  stays; overlapping stays stack in band lanes
This commit is contained in:
2026-07-20 10:22:49 +07:00
parent 71a158aa51
commit 36c4e4f306
14 changed files with 425 additions and 38 deletions
+9 -10
View File
@@ -4,6 +4,7 @@ import { membership } from '../util/access.js';
import { ENTRY_COLUMNS, attachParticipants } from '../util/entrySerialize.js';
import { validateSegments } from '../util/segments.js';
import { validateRental } from '../util/rental.js';
import { autoTransportTitle } from '../util/autoTransport.js';
const ENTRY_TYPES = new Set(['flight', 'transport', 'activity', 'rental', 'stay', 'note']);
const TRANSPORT_MODES = new Set(['train', 'bus', 'ferry', 'taxi', 'drive', 'other']);
@@ -234,8 +235,8 @@ export default function entriesRoutes(db) {
AND date >= ? AND date <= ? LIMIT 1`
);
const insertAutoTransport = db.prepare(
`INSERT INTO entries (trip_id, date, type, title, details, sort_order, split_mode)
VALUES (?, ?, 'transport', ?, '', 0, 'equal')`
`INSERT INTO entries (trip_id, date, type, title, details, sort_order, split_mode, auto_ref)
VALUES (?, ?, 'transport', ?, '', 0, 'equal', ?)`
);
const memberIdSet = (tripId) => new Set(getMemberIds.all(tripId).map((r) => r.user_id));
@@ -247,12 +248,6 @@ export default function entriesRoutes(db) {
}
}
// Short name for an auto-transport title: first comma-segment of the stay's
// location_name, falling back to its title.
function shortName(stay) {
return (stay.location_name || stay.title).split(',')[0].trim();
}
// Auto-create a transport entry bridging `earlier` -> `later` (both stay
// rows) unless a transport/flight already covers the gap, or the window is
// inverted (overlapping stays).
@@ -261,8 +256,12 @@ export default function entriesRoutes(db) {
const windowEnd = later.date;
if (windowStart > windowEnd) return;
if (existsTransportOrFlightInWindow.get(tripId, windowStart, windowEnd)) return;
const title = `${shortName(earlier)}${shortName(later)}`;
insertAutoTransport.run(tripId, later.date, title);
insertAutoTransport.run(
tripId,
later.date,
autoTransportTitle(earlier, later),
JSON.stringify({ from: earlier.id, to: later.id })
);
}
// Fires once, on stay creation: bridges the new stay to its nearest