# How the Journey Journal Feature Aggregates and Displays Information Across Multiple Trips in TREK

> Discover how TREK's Journey Journal aggregates and displays multi-trip data. Learn about chronological timelines, synchronized views, and client-side grouping logic for a unified experience.

- Repository: [Maurice/TREK](https://github.com/mauriceboe/TREK)
- Tags: deep-dive
- Published: 2026-07-01

---

**The Journey Journal links multiple trips via a `trip_ids` array, aggregates entries from all linked trips into a unified chronological timeline using client-side grouping logic, and renders them through synchronized timeline, gallery, and map views.**

TREK is an open-source travel platform that implements a photo-first Journey Journal capable of spanning one or more trips. Unlike isolated trip logs, this feature aggregates entries from multiple excursions into a single chronological stream, allowing travelers to document extended journeys that cross multiple destinations or time periods.

## Data Model and Trip Linking

The aggregation architecture begins in the data layer, where journeys maintain explicit links to their constituent trips through a relational array structure.

### Creating Multi-Trip Journeys

Journeys are created with an optional `trip_ids` parameter that links existing trips to the new journal. According to the TREK source code in [`client/src/store/journeyStore.ts`](https://github.com/mauriceboe/TREK/blob/main/client/src/store/journeyStore.ts), the `createJourney` method accepts this array and triggers the backend to import those trips' places as location anchors for future entries.

```typescript
// From journeyStore.ts (lines 66-70)
const newJourney = await useJourneyStore.getState().createJourney({
  title: 'European Summer',
  subtitle: 'June‑August 2025',
  trip_ids: [12, 34, 57],   // IDs of existing trips to link
});

```

When the backend receives this request, it stores the relationships in a `JourneyTrip[]` structure and copies the linked trips' places into the journey's location suggestion pool. This allows users to tag entries with locations from any linked trip without affecting the display logic.

### Loading Aggregated Data

The `loadJourney` function in the same store (lines 50-56) retrieves the complete journal payload, including the `trips` array and all associated entries. Because entries are stored at the journey level rather than nested within individual trips, the API returns a flat list of entries regardless of which trip they originated from.

## Client-Side Aggregation Logic

Once the data reaches the client, the `useJourneyPublic` hook (for public views) and similar internal hooks transform the flat entry list into derived collections optimized for UI rendering.

### Grouping Entries Chronologically

The hook first aggregates entries by date using the pure helper `groupByDate`, defined in [`client/src/pages/journeyPublic/journeyPublicModel.ts`](https://github.com/mauriceboe/TREK/blob/main/client/src/pages/journeyPublic/journeyPublicModel.ts) (lines 44-51). This creates a `Map<string, Entry[]>` where keys are ISO date strings and values are arrays of entries for that day.

```typescript
// From journeyPublicModel.ts
const groupedEntries = useMemo(() => groupByDate(entries), [entries]);
const sortedDates = useMemo(() => [...groupedEntries.keys()].sort(), [groupedEntries]);

```

The `sortedDates` array (calculated in [`useJourneyPublic.ts`](https://github.com/mauriceboe/TREK/blob/main/useJourneyPublic.ts) lines 54-57) provides the chronological backbone for the entire UI, ensuring day headers appear in correct temporal order even when entries originate from different linked trips.

### Synchronizing Map Markers with Timeline Days

To maintain visual consistency between the timeline and map views, the hook generates `sidebarMapItems` with color coding derived from the global `sortedDates` list. As implemented in [`useJourneyPublic.ts`](https://github.com/mauriceboe/TREK/blob/main/useJourneyPublic.ts) (lines 63-84), this logic assigns each entry a `dayColor` based on its position in the sorted dates array, ensuring markers match their corresponding timeline day headers.

```typescript
// From useJourneyPublic.ts
const sidebarMapItems = useMemo(() => {
  const counters = new Map<string, number>();
  return entries
    .filter(e => e.location_lat && e.location_lng)
    .map(e => {
      const dayIdx = sortedDates.indexOf(e.entry_date);
      const dayLabel = (counters.get(e.entry_date) ?? 0) + 1;
      counters.set(e.entry_date, dayLabel);
      return {
        id: String(e.id),
        lat: e.location_lat!,
        lng: e.location_lng!,
        title: e.title ?? '',
        mood: e.mood,
        dayColor: DAY_COLORS[dayIdx % DAY_COLORS.length],
        dayLabel,
      };
    });
}, [entries, sortedDates]);

```

## Rendering Multi-Trip Views

The presentation layer in [`JourneyPublicPage.tsx`](https://github.com/mauriceboe/TREK/blob/main/JourneyPublicPage.tsx) consumes these aggregated data structures to render three interchangeable view modes that treat multi-trip content as a single coherent narrative.

### Timeline View with Day Headers

The timeline implementation (lines 97-107) iterates over `sortedDates`, rendering a colored day header for each date followed by all entries belonging to that day via `groupedEntries.get(date)`. This approach seamlessly interleaves entries from different linked trips when they occur on the same date.

```tsx
{sortedDates.map((date, dayIdx) => {
  const dayEntries = groupedEntries.get(date)!;
  const { weekday, month, day } = formatDate(date, locale);
  const dayColor = DAY_COLORS[dayIdx % DAY_COLORS.length];

  return (
    <div key={date}>
      {/* Day header with chronological color coding */}
      <div className="flex items-center gap-3 mb-4">
        <div className="w-10 h-10 rounded-xl flex items-center justify-center text-[14px] font-bold text-white"
             style={{ background: dayColor }}>
          {dayIdx + 1}
        </div>
        <div>
          <div className="text-[14px] font-semibold">{weekday}</div>
          <div className="text-[11px]">{month} {day}</div>
        </div>
      </div>

      {/* Entries aggregated from all linked trips */}
      {dayEntries.map(entry => (
        <EntryCard key={entry.id} entry={entry} token={token!} />
      ))}
    </div>
  );
})}

```

### Map and Gallery Integration

The **map view** (lines 109-118) receives the pre-computed `sidebarMapItems` array, rendering markers with their day-specific colors already assigned. This guarantees that hovering a timeline entry highlights the corresponding map marker with matching color coding.

The **gallery view** (lines 94-99) operates on `allPhotos`, a flat collection of every photo belonging to the journey regardless of source trip. This allows users to browse the entire visual narrative without trip boundaries.

## Summary

- **Multi-trip linking**: Journeys store references to multiple trips via the `trip_ids` array in [`journeyStore.ts`](https://github.com/mauriceboe/TREK/blob/main/journeyStore.ts), importing their places as location suggestions while keeping entries at the journey level.
- **Chronological aggregation**: The `groupByDate` helper in [`journeyPublicModel.ts`](https://github.com/mauriceboe/TREK/blob/main/journeyPublicModel.ts) and the `sortedDates` derivation in [`useJourneyPublic.ts`](https://github.com/mauriceboe/TREK/blob/main/useJourneyPublic.ts) create a unified timeline across all linked trips.
- **Synchronized visualization**: Map markers receive `dayColor` assignments based on the global sorted date list, ensuring consistency between timeline, map, and gallery views.
- **Entry storage model**: Entries belong to the journey, not individual trips, enabling seamless aggregation even when trips overlap or span different time zones.

## Frequently Asked Questions

### How does the Journey Journal link multiple trips in TREK?

The journal links trips through the `trip_ids` array passed to `createJourney` in [`client/src/store/journeyStore.ts`](https://github.com/mauriceboe/TREK/blob/main/client/src/store/journeyStore.ts) (lines 66-70). When a journey is created with this array, the backend establishes `JourneyTrip` relationships and copies the linked trips' places into the journey's location pool for tagging entries.

### Where are journal entries stored in relation to trips?

Entries are stored at the **journey level**, not nested within individual trips. This design allows the UI to treat all entries as a single chronological stream regardless of which linked trip they document. The trip links serve primarily as location sources during entry creation.

### How does the map view stay synchronized with the timeline day colors?

The `useJourneyPublic` hook generates map markers with `dayColor` values computed from the `sortedDates` array index (lines 63-84). Since both the timeline headers and map markers derive their colors from the same sorted date list, they remain visually synchronized even when days contain entries from multiple different trips.

### Can entries be added to a journey without linking a trip initially?

Yes. While the creation flow described in [`wiki/Journey-Journal.md`](https://github.com/mauriceboe/TREK/blob/main/wiki/Journey-Journal.md) (lines 21-22) allows selecting trips to import location anchors, entries can be created independently and tagged with any location. The linked trips primarily provide convenience suggestions during the authoring process.