diff --git a/modules/rcal/components/folk-calendar-view.ts b/modules/rcal/components/folk-calendar-view.ts index eb55e52..472768b 100644 --- a/modules/rcal/components/folk-calendar-view.ts +++ b/modules/rcal/components/folk-calendar-view.ts @@ -106,7 +106,7 @@ import { calendarSchema, calendarDocId, type CalendarDoc } from "../schemas"; import type { DocumentId } from "../../../shared/local-first/document"; import { TourEngine } from "../../../shared/tour-engine"; import { startPresenceHeartbeat } from '../../../shared/collab-presence'; -import { generateBreadcrumb, CONTINENT_CENTROIDS } from "../geo-hierarchy"; +import { generateBreadcrumb } from "../geo-hierarchy"; // ── Component ── @@ -262,6 +262,7 @@ class FolkCalendarView extends HTMLElement { source_name: e.sourceName, source_color: e.sourceColor, location_name: e.locationName, location_breadcrumb: e.locationBreadcrumb ?? null, + booking_status: e.bookingStatus ?? null, location_lat: e.locationLat, location_lng: e.locationLng, latitude: e.locationLat ?? null, longitude: e.locationLng ?? null, @@ -479,7 +480,7 @@ class FolkCalendarView extends HTMLElement { const demoEvents: { start: Date; durationMin: number; title: string; source: number; desc: string; location: string | null; virtual: boolean; - lat?: number; lng?: number; breadcrumb?: string; likelihood?: number; + lat?: number; lng?: number; breadcrumb?: string; likelihood?: number; booked?: boolean; }[] = [ // ── LAST MONTH ── { start: rel(-1, 18, 10, 0), durationMin: 90, title: "Sprint 22 Review", source: 0, desc: "Sprint review with Berlin engineering team", location: "Factory Berlin", virtual: false, lat: 52.5030, lng: 13.3345, breadcrumb: "Earth > Europe > Germany > Berlin" }, @@ -514,11 +515,11 @@ class FolkCalendarView extends HTMLElement { { start: rel(0, 10, 15, 30), durationMin: 60, title: "Deploy Prep", source: 0, desc: "Pre-release checklist and staging verification", location: null, virtual: true }, { start: rel(0, 11, 7, 30), durationMin: 75, title: "Morning Yoga", source: 5, desc: "Vinyasa flow", location: "Yoga Studio Kreuzberg", virtual: false, breadcrumb: "Earth > Europe > Germany > Berlin > Kreuzberg" }, { start: rel(0, 11, 18, 0), durationMin: 120, title: "Workshop: Intro to CRDTs (1/4)", source: 4, desc: "Community workshop series on conflict-free replicated data types", location: "c-base, Berlin", virtual: false, lat: 52.5130, lng: 13.4200, breadcrumb: "Earth > Europe > Germany > Berlin > Mitte" }, - { start: rel(0, 12, 7, 15), durationMin: 390, title: "Train Berlin \u2192 Amsterdam", source: 1, desc: "ICE 643 Berlin Hbf \u2192 Amsterdam Centraal", location: "Berlin Hauptbahnhof", virtual: false, lat: 52.5251, lng: 13.3694, breadcrumb: "Earth > Europe > Germany > Berlin" }, - { start: rel(0, 12, 14, 30), durationMin: 30, title: "Hotel Check-in", source: 1, desc: "Hotel V Nesplein, Amsterdam", location: "Hotel V Nesplein", virtual: false, lat: 52.3667, lng: 4.8945, breadcrumb: "Earth > Europe > Netherlands > Amsterdam" }, - { start: rel(0, 13, 10, 0), durationMin: 180, title: "Partner Meeting", source: 0, desc: "On-site with Amsterdam design team", location: "WeWork Weteringschans", virtual: false, lat: 52.3603, lng: 4.8880, breadcrumb: "Earth > Europe > Netherlands > Amsterdam" }, + { start: rel(0, 12, 7, 15), durationMin: 390, title: "Train Berlin \u2192 Amsterdam", source: 1, desc: "ICE 643 Berlin Hbf \u2192 Amsterdam Centraal", location: "Berlin Hauptbahnhof", virtual: false, lat: 52.5251, lng: 13.3694, breadcrumb: "Earth > Europe > Germany > Berlin", booked: true }, + { start: rel(0, 12, 14, 30), durationMin: 30, title: "Hotel Check-in", source: 1, desc: "Hotel V Nesplein, Amsterdam", location: "Hotel V Nesplein", virtual: false, lat: 52.3667, lng: 4.8945, breadcrumb: "Earth > Europe > Netherlands > Amsterdam", booked: true }, + { start: rel(0, 13, 10, 0), durationMin: 180, title: "Partner Meeting", source: 0, desc: "On-site with Amsterdam design team", location: "WeWork Weteringschans", virtual: false, lat: 52.3603, lng: 4.8880, breadcrumb: "Earth > Europe > Netherlands > Amsterdam", booked: true }, { start: rel(0, 13, 15, 0), durationMin: 120, title: "Canal District Walk", source: 2, desc: "Afternoon along Prinsengracht and Jordaan", location: "Prinsengracht, Amsterdam", virtual: false, lat: 52.3738, lng: 4.8820, breadcrumb: "Earth > Europe > Netherlands > Amsterdam" }, - { start: rel(0, 14, 9, 30), durationMin: 390, title: "Return Train Amsterdam \u2192 Berlin", source: 1, desc: "ICE 148 Amsterdam \u2192 Berlin", location: "Amsterdam Centraal", virtual: false, lat: 52.3791, lng: 4.9003, breadcrumb: "Earth > Europe > Netherlands > Amsterdam" }, + { start: rel(0, 14, 9, 30), durationMin: 390, title: "Return Train Amsterdam \u2192 Berlin", source: 1, desc: "ICE 148 Amsterdam \u2192 Berlin", location: "Amsterdam Centraal", virtual: false, lat: 52.3791, lng: 4.9003, breadcrumb: "Earth > Europe > Netherlands > Amsterdam", booked: true }, { start: rel(0, 15, 9, 0), durationMin: 30, title: "Team Standup", source: 0, desc: "Post-travel sync", location: "Factory Berlin", virtual: false, breadcrumb: "Earth > Europe > Germany > Berlin" }, { start: rel(0, 15, 19, 30), durationMin: 120, title: "Dinner with Friends", source: 2, desc: "Birthday dinner for Mia", location: "Il Casolare, Kreuzberg", virtual: false, lat: 52.4900, lng: 13.4200, breadcrumb: "Earth > Europe > Germany > Berlin > Kreuzberg" }, { start: rel(0, 16, 7, 30), durationMin: 75, title: "Morning Yoga", source: 5, desc: "Vinyasa flow", location: "Yoga Studio Kreuzberg", virtual: false, breadcrumb: "Earth > Europe > Germany > Berlin > Kreuzberg" }, @@ -531,11 +532,11 @@ class FolkCalendarView extends HTMLElement { { start: rel(0, 19, 9, 0), durationMin: 45, title: "Dentist", source: 5, desc: "Regular checkup, Dr. Weber", location: "Torstr. 140, Berlin", virtual: false, lat: 52.5308, lng: 13.3970, breadcrumb: "Earth > Europe > Germany > Berlin > Mitte" }, { start: rel(0, 20, 19, 0), durationMin: 90, title: "Book Club", source: 4, desc: '"The Mushroom at the End of the World"', location: "Shakespeare & Sons, Berlin", virtual: false, lat: 52.4925, lng: 13.4310, breadcrumb: "Earth > Europe > Germany > Berlin > Kreuzberg" }, { start: rel(0, 21, 14, 0), durationMin: 60, title: "c-base Open Tuesday", source: 4, desc: "Weekly open hackerspace session", location: "c-base, Berlin", virtual: false, lat: 52.5130, lng: 13.4200, breadcrumb: "Earth > Europe > Germany > Berlin > Mitte" }, - { start: rel(0, 22, 6, 45), durationMin: 195, title: "Flight \u2192 Lisbon", source: 1, desc: "TAP TP 571 BER \u2192 LIS", location: "BER Airport", virtual: false, lat: 52.3667, lng: 13.5033, breadcrumb: "Earth > Europe > Germany > Berlin" }, - { start: rel(0, 23, 9, 0), durationMin: 540, title: "Web Summit Day 1", source: 3, desc: "Opening keynotes, startup pavilion", location: "Altice Arena, Lisbon", virtual: false, lat: 38.7685, lng: -9.0943, breadcrumb: "Earth > Europe > Portugal > Lisbon" }, - { start: rel(0, 24, 9, 0), durationMin: 540, title: "Web Summit Day 2", source: 3, desc: "Panel: Local-First Software", location: "Altice Arena, Lisbon", virtual: false, lat: 38.7685, lng: -9.0943, breadcrumb: "Earth > Europe > Portugal > Lisbon" }, + { start: rel(0, 22, 6, 45), durationMin: 195, title: "Flight \u2192 Lisbon", source: 1, desc: "TAP TP 571 BER \u2192 LIS", location: "BER Airport", virtual: false, lat: 52.3667, lng: 13.5033, breadcrumb: "Earth > Europe > Germany > Berlin", booked: true }, + { start: rel(0, 23, 9, 0), durationMin: 540, title: "Web Summit Day 1", source: 3, desc: "Opening keynotes, startup pavilion", location: "Altice Arena, Lisbon", virtual: false, lat: 38.7685, lng: -9.0943, breadcrumb: "Earth > Europe > Portugal > Lisbon", booked: true }, + { start: rel(0, 24, 9, 0), durationMin: 540, title: "Web Summit Day 2", source: 3, desc: "Panel: Local-First Software", location: "Altice Arena, Lisbon", virtual: false, lat: 38.7685, lng: -9.0943, breadcrumb: "Earth > Europe > Portugal > Lisbon", booked: true }, { start: rel(0, 25, 10, 0), durationMin: 360, title: "Lisbon City Tour", source: 2, desc: "Alfama, Tram 28, Past\u00e9is de Bel\u00e9m", location: "Alfama, Lisbon", virtual: false, lat: 38.7118, lng: -9.1300, breadcrumb: "Earth > Europe > Portugal > Lisbon" }, - { start: rel(0, 25, 19, 30), durationMin: 195, title: "Flight \u2192 Berlin", source: 1, desc: "TAP TP 572 LIS \u2192 BER", location: "Lisbon Airport", virtual: false, lat: 38.7756, lng: -9.1354, breadcrumb: "Earth > Europe > Portugal > Lisbon" }, + { start: rel(0, 25, 19, 30), durationMin: 195, title: "Flight \u2192 Berlin", source: 1, desc: "TAP TP 572 LIS \u2192 BER", location: "Lisbon Airport", virtual: false, lat: 38.7756, lng: -9.1354, breadcrumb: "Earth > Europe > Portugal > Lisbon", booked: true }, { start: rel(0, 25, 18, 0), durationMin: 120, title: "Workshop: CRDTs (3/4)", source: 4, desc: "Merging trees and documents", location: "c-base, Berlin", virtual: false, lat: 52.5130, lng: 13.4200, breadcrumb: "Earth > Europe > Germany > Berlin > Mitte" }, { start: rel(0, 26, 18, 0), durationMin: 180, title: "Hackathon \u2014 c-base", source: 4, desc: "Local-first data sync hackathon", location: "c-base, Berlin", virtual: false, lat: 52.5130, lng: 13.4200, breadcrumb: "Earth > Europe > Germany > Berlin > Mitte" }, { start: rel(0, 27, 9, 0), durationMin: 30, title: "Team Standup", source: 0, desc: "Post-travel sync", location: "Factory Berlin", virtual: false, breadcrumb: "Earth > Europe > Germany > Berlin" }, @@ -545,10 +546,10 @@ class FolkCalendarView extends HTMLElement { // ── NEXT MONTH (+1) ── { start: rel(1, 1, 18, 0), durationMin: 120, title: "Workshop: CRDTs (4/4)", source: 4, desc: "Final session: production patterns", location: "c-base, Berlin", virtual: false, lat: 52.5130, lng: 13.4200, breadcrumb: "Earth > Europe > Germany > Berlin > Mitte" }, { start: rel(1, 2, 10, 0), durationMin: 120, title: "Sprint 25 Planning", source: 0, desc: "Plan next sprint", location: "Factory Berlin", virtual: false, breadcrumb: "Earth > Europe > Germany > Berlin" }, - { start: rel(1, 3, 6, 0), durationMin: 180, title: "Flight \u2192 Barcelona", source: 1, desc: "VY 1862 BER \u2192 BCN", location: "BER Airport", virtual: false, lat: 52.3667, lng: 13.5033, breadcrumb: "Earth > Europe > Germany > Berlin" }, - { start: rel(1, 3, 14, 0), durationMin: 240, title: "Team Retreat Day 1", source: 0, desc: "Strategy offsite at Barcel\u00f3 Sants", location: "Barcel\u00f3 Sants, Barcelona", virtual: false, lat: 41.3795, lng: 2.1405, breadcrumb: "Earth > Europe > Spain > Barcelona" }, - { start: rel(1, 4, 9, 0), durationMin: 480, title: "Team Retreat Day 2", source: 0, desc: "Workshops + Sagrada Familia visit", location: "Barcelona", virtual: false, lat: 41.4036, lng: 2.1744, breadcrumb: "Earth > Europe > Spain > Barcelona" }, - { start: rel(1, 5, 15, 0), durationMin: 180, title: "Flight \u2192 Berlin", source: 1, desc: "VY 1863 BCN \u2192 BER", location: "BCN Airport", virtual: false, lat: 41.2974, lng: 2.0833, breadcrumb: "Earth > Europe > Spain > Barcelona" }, + { start: rel(1, 3, 6, 0), durationMin: 180, title: "Flight \u2192 Barcelona", source: 1, desc: "VY 1862 BER \u2192 BCN", location: "BER Airport", virtual: false, lat: 52.3667, lng: 13.5033, breadcrumb: "Earth > Europe > Germany > Berlin", booked: true }, + { start: rel(1, 3, 14, 0), durationMin: 240, title: "Team Retreat Day 1", source: 0, desc: "Strategy offsite at Barcel\u00f3 Sants", location: "Barcel\u00f3 Sants, Barcelona", virtual: false, lat: 41.3795, lng: 2.1405, breadcrumb: "Earth > Europe > Spain > Barcelona", booked: true }, + { start: rel(1, 4, 9, 0), durationMin: 480, title: "Team Retreat Day 2", source: 0, desc: "Workshops + Sagrada Familia visit", location: "Barcelona", virtual: false, lat: 41.4036, lng: 2.1744, breadcrumb: "Earth > Europe > Spain > Barcelona", booked: true }, + { start: rel(1, 5, 15, 0), durationMin: 180, title: "Flight \u2192 Berlin", source: 1, desc: "VY 1863 BCN \u2192 BER", location: "BCN Airport", virtual: false, lat: 41.2974, lng: 2.0833, breadcrumb: "Earth > Europe > Spain > Barcelona", booked: true }, { start: rel(1, 6, 7, 30), durationMin: 75, title: "Morning Yoga", source: 5, desc: "Vinyasa flow", location: "Yoga Studio Kreuzberg", virtual: false, breadcrumb: "Earth > Europe > Germany > Berlin > Kreuzberg" }, { start: rel(1, 8, 7, 30), durationMin: 75, title: "Morning Yoga", source: 5, desc: "Vinyasa flow", location: "Yoga Studio Kreuzberg", virtual: false, breadcrumb: "Earth > Europe > Germany > Berlin > Kreuzberg" }, { start: rel(1, 8, 19, 0), durationMin: 120, title: "Berlin Philharmonic", source: 2, desc: "Brahms Symphony No. 4", location: "Berliner Philharmonie", virtual: false, lat: 52.5103, lng: 13.3699, breadcrumb: "Earth > Europe > Germany > Berlin > Tiergarten" }, @@ -668,6 +669,7 @@ class FolkCalendarView extends HTMLElement { latitude: e.lat, longitude: e.lng, likelihood: e.likelihood ?? null, + booking_status: e.booked ? "booked" : null, }; }); @@ -2772,92 +2774,8 @@ class FolkCalendarView extends HTMLElement { this.mapMarkerLayer.clearLayers(); const located = this.getVisibleLocatedEvents(); - const spatialLevel = this.getEffectiveSpatialIndex(); - if (spatialLevel <= 1) { - // Planet / Continent — group by continent - this.renderAggregateMarkers(located, 1); - } else if (spatialLevel <= 3) { - // Bioregion / Country — group by country - this.renderAggregateMarkers(located, 3); - } else if (spatialLevel <= 5) { - // Region / City — group by city - this.renderAggregateMarkers(located, 5); - } else { - // Neighborhood / Address / Coordinates — individual markers - this.renderIndividualMarkers(located); - } - - // Auto-fit map to visible event bounds - if (this.zoomCoupled && located.length > 0) { - if (located.length === 1) { - this.leafletMap?.flyTo([located[0].latitude, located[0].longitude], this.getEffectiveLeafletZoom(), { duration: 0.8 }); - } else { - const bounds = L.latLngBounds(located.map((e: any) => [e.latitude, e.longitude])); - this.leafletMap?.flyToBounds(bounds, { padding: [40, 40], maxZoom: 16, duration: 0.8 }); - } - } - } - - /** Render aggregate cluster markers grouped by spatial label at the given level. */ - private renderAggregateMarkers(events: any[], level: number) { - const L = (window as any).L; - if (!L) return; - - // Group events by spatial label - const groups = new Map(); - for (const ev of events) { - const label = this.getSpatialLabel(ev.location_breadcrumb, level) || "Other"; - if (!groups.has(label)) groups.set(label, []); - groups.get(label)!.push(ev); - } - - for (const [label, evs] of groups) { - // Compute centroid — use stable continent centroids at level ≤ 1 - let lat: number, lng: number; - if (level <= 1 && CONTINENT_CENTROIDS[label]) { - [lat, lng] = CONTINENT_CENTROIDS[label]; - } else { - let sumLat = 0, sumLng = 0; - for (const e of evs) { sumLat += e.latitude; sumLng += e.longitude; } - lat = sumLat / evs.length; - lng = sumLng / evs.length; - } - - // Determine dominant source color for border - const colorCounts: Record = {}; - for (const e of evs) { - const c = e.source_color || "#6366f1"; - colorCounts[c] = (colorCounts[c] || 0) + 1; - } - const dominantColor = Object.entries(colorCounts).sort((a, b) => b[1] - a[1])[0]?.[0] || "#6366f1"; - - const icon = L.divIcon({ - className: "", - html: `
${this.esc(label)}${evs.length}
`, - iconSize: [0, 0], - iconAnchor: [0, 0], - }); - - const marker = L.marker([lat, lng], { icon }); - - // Popup listing events in this cluster (first 10) - const popupList = evs.slice(0, 10).map((e: any) => - `
${this.esc(e.title)}
${new Date(e.start_time).toLocaleDateString("default", { month: "short", day: "numeric" })}
` - ).join(""); - const overflow = evs.length > 10 ? `
+${evs.length - 10} more
` : ""; - marker.bindPopup(`
${this.esc(label)} (${evs.length})
${popupList}${overflow}
`); - - this.mapMarkerLayer.addLayer(marker); - } - } - - /** Render individual event markers (original behavior). */ - private renderIndividualMarkers(events: any[]) { - const L = (window as any).L; - if (!L) return; - - for (const ev of events) { + for (const ev of located) { const es = this.getEventStyles(ev); const marker = L.circleMarker([ev.latitude, ev.longitude], { radius: 6, color: ev.source_color || "#6366f1", @@ -2873,6 +2791,16 @@ class FolkCalendarView extends HTMLElement { `); this.mapMarkerLayer.addLayer(marker); } + + // Auto-fit map to visible event bounds + if (this.zoomCoupled && located.length > 0) { + if (located.length === 1) { + this.leafletMap?.flyTo([located[0].latitude, located[0].longitude], this.getEffectiveLeafletZoom(), { duration: 0.8 }); + } else { + const bounds = L.latLngBounds(located.map((e: any) => [e.latitude, e.longitude])); + this.leafletMap?.flyToBounds(bounds, { padding: [40, 40], maxZoom: 16, duration: 0.8 }); + } + } } private updateTransitLines() { @@ -2880,9 +2808,6 @@ class FolkCalendarView extends HTMLElement { if (!L || !this.transitLineLayer) return; this.transitLineLayer.clearLayers(); - // Skip transit lines at aggregate zoom levels (spatial < 6) - if (this.getEffectiveSpatialIndex() < 6) return; - const sorted = this.getVisibleLocatedEvents() .sort((a, b) => new Date(a.start_time).getTime() - new Date(b.start_time).getTime()); @@ -2892,14 +2817,16 @@ class FolkCalendarView extends HTMLElement { const curr = sorted[i], next = sorted[i + 1]; if (Math.abs(curr.latitude - next.latitude) < 0.01 && Math.abs(curr.longitude - next.longitude) < 0.01) continue; - const isTravel = curr.source_name === "Travel" || next.source_name === "Travel"; + // Line color based on booking status: green = booked, red = unbooked/unknown + const isBooked = next.booking_status === "booked"; + const color = isBooked ? "#22c55e" : "#ef4444"; + const label = isBooked ? "Booked" : "Not booked"; const line = L.polyline( [[curr.latitude, curr.longitude], [next.latitude, next.longitude]], - { color: isTravel ? "#f97316" : "#94a3b8", weight: isTravel ? 3 : 2, - opacity: isTravel ? 0.8 : 0.4, dashArray: isTravel ? "8, 8" : "4, 8" } + { color, weight: 3, opacity: 0.7, dashArray: isBooked ? undefined : "8, 6" } ); line.bindTooltip( - `${this.esc(curr.location_name || curr.title)} \u2192 ${this.esc(next.location_name || next.title)}`, + `${this.esc(curr.location_name || curr.title)} \u2192 ${this.esc(next.location_name || next.title)} [${label}]`, { sticky: true } ); this.transitLineLayer.addLayer(line); @@ -3008,10 +2935,6 @@ class FolkCalendarView extends HTMLElement { .map-fab { position: absolute; bottom: 16px; right: 16px; width: 44px; height: 44px; border-radius: 50%; border: 1px solid var(--rs-border-strong); background: var(--rs-bg-surface); color: var(--rs-text-primary); cursor: pointer; font-size: 20px; display: flex; align-items: center; justify-content: center; z-index: 100; box-shadow: var(--rs-shadow-md); transition: all 0.15s; } .map-fab:hover { border-color: var(--rs-primary-hover); background: var(--rs-bg-surface-raised); transform: scale(1.1); } - /* ── Map Cluster Labels (semantic zoom) ── */ - .map-cluster-label { background: var(--rs-bg-surface, #1e1e2e); border: 2px solid var(--rs-primary, #6366f1); border-radius: 16px; padding: 4px 10px; font-size: 12px; font-weight: 600; white-space: nowrap; box-shadow: 0 2px 8px rgba(0,0,0,0.2); color: var(--rs-text-primary, #e0e0e0); display: inline-flex; align-items: center; gap: 4px; transform: translate(-50%, -50%); pointer-events: auto; cursor: pointer; } - .map-cluster-count { font-size: 10px; opacity: 0.7; } - /* ── Synodic (reused in overlay) ── */ .synodic-section { margin: 0 0 8px; } .synodic-labels { display: flex; justify-content: space-between; font-size: 11px; color: var(--rs-text-muted); margin-bottom: 6px; } diff --git a/modules/rcal/local-first-client.ts b/modules/rcal/local-first-client.ts index 943449e..b098aba 100644 --- a/modules/rcal/local-first-client.ts +++ b/modules/rcal/local-first-client.ts @@ -70,7 +70,7 @@ export class CalLocalFirstClient { const docId = calendarDocId(this.#space) as DocumentId; this.#sync.change(docId, `Update event ${eventId}`, (d) => { if (!d.events[eventId]) { - d.events[eventId] = { id: eventId, title: '', description: '', startTime: 0, endTime: 0, allDay: false, timezone: null, rrule: null, status: null, visibility: null, sourceId: null, sourceName: null, sourceType: null, sourceColor: null, locationId: null, locationName: null, coordinates: null, locationGranularity: null, locationLat: null, locationLng: null, locationBreadcrumb: null, isVirtual: false, virtualUrl: null, virtualPlatform: null, rToolSource: null, rToolEntityId: null, attendees: [], attendeeCount: 0, metadata: null, createdAt: Date.now(), updatedAt: Date.now(), ...changes } as CalendarEvent; + d.events[eventId] = { id: eventId, title: '', description: '', startTime: 0, endTime: 0, allDay: false, timezone: null, rrule: null, status: null, visibility: null, sourceId: null, sourceName: null, sourceType: null, sourceColor: null, locationId: null, locationName: null, coordinates: null, locationGranularity: null, locationLat: null, locationLng: null, locationBreadcrumb: null, bookingStatus: null, isVirtual: false, virtualUrl: null, virtualPlatform: null, rToolSource: null, rToolEntityId: null, attendees: [], attendeeCount: 0, metadata: null, createdAt: Date.now(), updatedAt: Date.now(), ...changes } as CalendarEvent; } else { Object.assign(d.events[eventId], changes); d.events[eventId].updatedAt = Date.now(); diff --git a/modules/rcal/mod.ts b/modules/rcal/mod.ts index 4f069ee..c916040 100644 --- a/modules/rcal/mod.ts +++ b/modules/rcal/mod.ts @@ -88,6 +88,7 @@ function eventToRow(ev: CalendarEvent, sources: Record) location_lng: ev.locationLng, location_granularity: ev.locationGranularity, location_breadcrumb: ev.locationBreadcrumb, + booking_status: ev.bookingStatus, is_virtual: ev.isVirtual, virtual_url: ev.virtualUrl, virtual_platform: ev.virtualPlatform, @@ -256,6 +257,7 @@ function seedDemoIfEmpty(space: string) { locationLat: e.locationLat ?? null, locationLng: e.locationLng ?? null, locationBreadcrumb: null, + bookingStatus: null, isVirtual: e.isVirtual || false, virtualUrl: e.virtualUrl || null, virtualPlatform: e.virtualPlatform || null, @@ -413,6 +415,7 @@ routes.post("/api/events", async (c) => { locationLat: null, locationLng: null, locationBreadcrumb: null, + bookingStatus: null, isVirtual: is_virtual || false, virtualUrl: virtual_url || null, virtualPlatform: virtual_platform || null, @@ -544,6 +547,7 @@ routes.post("/api/import-ics", async (c) => { locationLat: null, locationLng: null, locationBreadcrumb: null, + bookingStatus: null, isVirtual: false, virtualUrl: null, virtualPlatform: null, diff --git a/modules/rcal/schemas.ts b/modules/rcal/schemas.ts index b685da7..95d0fa4 100644 --- a/modules/rcal/schemas.ts +++ b/modules/rcal/schemas.ts @@ -51,6 +51,7 @@ export interface CalendarEvent { rToolSource: string | null; rToolEntityId: string | null; locationBreadcrumb: string | null; // "Earth > Europe > Germany > Berlin" + bookingStatus: string | null; // "booked" | "unbooked" | null (placeholder for booking pipeline) attendees: unknown[]; attendeeCount: number; tags: string[] | null; diff --git a/modules/rschedule/mod.ts b/modules/rschedule/mod.ts index 443e387..1c895c6 100644 --- a/modules/rschedule/mod.ts +++ b/modules/rschedule/mod.ts @@ -260,6 +260,7 @@ async function executeCalendarEvent( locationLat: null, locationLng: null, locationBreadcrumb: null, + bookingStatus: null, isVirtual: false, virtualUrl: null, virtualPlatform: null, @@ -1087,6 +1088,7 @@ function syncReminderToCalendar(reminder: Reminder, space: string): string | nul locationLat: null, locationLng: null, locationBreadcrumb: null, + bookingStatus: null, isVirtual: false, virtualUrl: null, virtualPlatform: null, @@ -1698,6 +1700,7 @@ async function executeWorkflowNode( locationLat: null, locationLng: null, locationBreadcrumb: null, + bookingStatus: null, isVirtual: false, virtualUrl: null, virtualPlatform: null,