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:
@@ -1,7 +1,8 @@
|
||||
// Calendar grid for the trip's date range. Real weeks as rows (Mon–Sun
|
||||
// columns); days outside the range are greyed. Each in-range day shows its
|
||||
// entries as compact, type-coloured chips. Clicking a day opens the editor.
|
||||
import { el } from '../dom.js';
|
||||
import { el, toast } from '../dom.js';
|
||||
import { api } from '../api.js';
|
||||
import {
|
||||
ENTRY_TYPES,
|
||||
typeInfo,
|
||||
@@ -68,9 +69,14 @@ export function renderCalendar(tctx) {
|
||||
{ class: 'card calendar-section' },
|
||||
el(
|
||||
'div',
|
||||
{ class: 'section-head' },
|
||||
el('h2', {}, 'Calendar'),
|
||||
el('p', { class: 'muted' }, 'Click a day to add or edit entries.'),
|
||||
{ class: 'section-head cal-section-head' },
|
||||
el(
|
||||
'div',
|
||||
{},
|
||||
el('h2', {}, 'Calendar'),
|
||||
el('p', { class: 'muted' }, 'Click a day to add or edit entries.'),
|
||||
),
|
||||
syncTransportsButton(tctx),
|
||||
),
|
||||
legend(),
|
||||
);
|
||||
@@ -97,7 +103,7 @@ export function renderCalendar(tctx) {
|
||||
for (const week of weeks) {
|
||||
// Each week is a stay-bands strip (all-day bars) above a row of day cells.
|
||||
const weekEl = el('div', { class: 'cal-week' });
|
||||
const bands = renderWeekBands(week.map(ymd), stayLayout, tctx);
|
||||
const bands = renderWeekBands(week.map(ymd), stayLayout, entriesById, tctx);
|
||||
if (bands) weekEl.appendChild(bands);
|
||||
const daysRow = el('div', { class: 'cal-week-days' });
|
||||
for (const date of week) {
|
||||
@@ -111,12 +117,39 @@ export function renderCalendar(tctx) {
|
||||
// which bubbles up here — sweep any lingering drop highlight.
|
||||
cal.addEventListener('dragend', () => {
|
||||
for (const n of cal.querySelectorAll('.cal-day-drop')) n.classList.remove('cal-day-drop');
|
||||
for (const n of cal.querySelectorAll('.cal-bands-drop')) n.classList.remove('cal-bands-drop');
|
||||
});
|
||||
|
||||
section.appendChild(cal);
|
||||
return section;
|
||||
}
|
||||
|
||||
// "↻ Sync transports" — reconciles auto-created transport entries after stays
|
||||
// have been dragged/reshuffled (see docs/API.md, "Regenerating auto-transports").
|
||||
function syncTransportsButton(tctx) {
|
||||
const btn = el('button', { class: 'btn btn-sm btn-ghost', type: 'button' }, '↻ Sync transports');
|
||||
btn.addEventListener('click', async () => {
|
||||
btn.disabled = true;
|
||||
const original = btn.textContent;
|
||||
btn.textContent = 'Syncing…';
|
||||
try {
|
||||
const res = await api.trips.regenerateTransports(tctx.tripId);
|
||||
const parts = [];
|
||||
if (res.created) parts.push(`${res.created} added`);
|
||||
if (res.updated) parts.push(`${res.updated} updated`);
|
||||
if (res.deleted) parts.push(`${res.deleted} removed`);
|
||||
toast(parts.length ? `Transports synced: ${parts.join(', ')}` : 'Transports already in sync', 'success');
|
||||
await tctx.refreshTrip();
|
||||
} catch (err) {
|
||||
toast(err.message);
|
||||
} finally {
|
||||
btn.disabled = false;
|
||||
btn.textContent = original;
|
||||
}
|
||||
});
|
||||
return btn;
|
||||
}
|
||||
|
||||
function dayCell(date, rangeStart, rangeEnd, byDate, contByDate, dropoffByDate, entriesById, tctx, currency) {
|
||||
const key = ymd(date);
|
||||
const inRange = date >= rangeStart && date <= rangeEnd;
|
||||
|
||||
Reference in New Issue
Block a user