59 lines
1.6 KiB
TypeScript
59 lines
1.6 KiB
TypeScript
import type { Event, Trip, Node, VoyageInfo } from "~/types"
|
|
import { byStart } from "./byStart"
|
|
import { eventToNode } from "./eventToNode"
|
|
import { formatDateRange } from "./formatDateRange"
|
|
|
|
/** Sentinel id for the synthetic voyage-root event; never sent to the API. */
|
|
const ROOT_EVENT_ID = -1
|
|
|
|
/** Build the unified node tree (voyage = root) from flat events + trips. */
|
|
export const buildVoyageTree = (
|
|
info: VoyageInfo,
|
|
events: Event[],
|
|
trips: Trip[],
|
|
): Node => {
|
|
const tripByStart = new Map<number, Trip>()
|
|
for (const t of trips) if (!tripByStart.has(t.eventStart)) tripByStart.set(t.eventStart, t)
|
|
|
|
const byParent = new Map<number | null, Event[]>()
|
|
for (const e of events) {
|
|
const p = e.parent ?? null
|
|
const arr = byParent.get(p) ?? []
|
|
arr.push(e)
|
|
byParent.set(p, arr)
|
|
}
|
|
|
|
const roots = (byParent.get(null) ?? [])
|
|
.slice()
|
|
.sort(byStart)
|
|
.map((e) => eventToNode(e, byParent, tripByStart))
|
|
|
|
const dated = roots.filter((n) => n.event.startDate)
|
|
const dateLabel = dated.length
|
|
? formatDateRange(
|
|
dated[0]!.event.startDate as string,
|
|
dated[dated.length - 1]!.event.startDate as string,
|
|
)
|
|
: undefined
|
|
|
|
// Synthetic root: gives the voyage a uniform `node.event` (so components read
|
|
// `node.event.title` at every level) without addressing a real event.
|
|
const rootEvent: Event = {
|
|
id: ROOT_EVENT_ID,
|
|
title: info.title,
|
|
startDate: null,
|
|
endDate: null,
|
|
location: null,
|
|
parent: null,
|
|
}
|
|
|
|
return {
|
|
id: "__voyage",
|
|
root: true,
|
|
event: rootEvent,
|
|
trip: null,
|
|
children: roots,
|
|
dateLabel,
|
|
lutins: info.lutins ?? [],
|
|
}
|
|
}
|