File size: 4,027 Bytes
9d2d895 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 | /**
* Land tab — filters bypass options to `type === 'land_bridge'` only.
* Excludes proposed and unavailable corridors from the primary list but
* shows them in a secondary "other corridors" section with honest labels.
* Empty state when no land-bridge corridors exist for this lane.
*/
import type {
BypassCorridorOption,
GetRouteExplorerLaneResponse,
} from '@/generated/server/worldmonitor/supply_chain/v1/service_server';
import { renderRouteCard } from '../components/RouteCard';
import { setTrustedHtml, trustedHtml } from '@/utils/dom-utils';
export interface LandTabOptions {
onSelectBypass: (option: BypassCorridorOption) => void;
}
export class LandTab {
public readonly element: HTMLDivElement;
private opts: LandTabOptions;
constructor(opts: LandTabOptions) {
this.opts = opts;
this.element = document.createElement('div');
this.element.className = 're-tab re-tab--land';
this.element.setAttribute('role', 'tabpanel');
this.renderEmpty();
}
public update(data: GetRouteExplorerLaneResponse | null): void {
if (!data || data.noModeledLane) {
this.renderNoLane();
return;
}
const landBridges = data.bypassOptions.filter((o) => o.type === 'land_bridge');
const active = landBridges.filter(
(o) => o.status !== 'CORRIDOR_STATUS_PROPOSED' && o.status !== 'CORRIDOR_STATUS_UNAVAILABLE',
);
const other = landBridges.filter(
(o) => o.status === 'CORRIDOR_STATUS_PROPOSED' || o.status === 'CORRIDOR_STATUS_UNAVAILABLE',
);
if (landBridges.length === 0) {
this.renderEmptyLand();
return;
}
this.renderList(active, other);
}
private renderEmpty(): void {
setTrustedHtml(this.element, trustedHtml('<div class="re-tab__placeholder">Pick a country pair and product to see land corridors.</div>', "legacy direct innerHTML migration"));
}
private renderNoLane(): void {
setTrustedHtml(this.element, trustedHtml('<div class="re-tab__empty"><p>No modeled lane. Land corridors require a primary route context.</p></div>', "legacy direct innerHTML migration"));
}
private renderEmptyLand(): void {
setTrustedHtml(this.element, trustedHtml('<div class="re-tab__empty">' +
'<h3>No overland alternatives</h3>' +
'<p>No land-bridge corridors are modeled for this lane\'s primary chokepoint. ' +
'Only 5 land corridors are currently in the dataset (Aqaba, Djibouti-Addis, ' +
'Baku-Tbilisi-Batumi, US Rail, Ukraine Rail).</p>' +
'</div>', "legacy direct innerHTML migration"));
}
private renderList(active: BypassCorridorOption[], other: BypassCorridorOption[]): void {
setTrustedHtml(this.element, trustedHtml('', "legacy direct innerHTML migration"));
if (active.length > 0) {
const header = document.createElement('h3');
header.className = 're-land__header';
header.textContent = 'Land corridors';
this.element.append(header);
const listEl = document.createElement('div');
listEl.className = 're-land__list';
listEl.setAttribute('role', 'listbox');
active.forEach((option, idx) => {
listEl.append(
renderRouteCard({
option,
index: idx,
isActive: false,
onSelect: (o) => this.opts.onSelectBypass(o),
}),
);
});
this.element.append(listEl);
}
if (other.length > 0) {
const otherHeader = document.createElement('h4');
otherHeader.className = 're-land__other-header';
otherHeader.textContent = 'Other corridors (not currently usable)';
this.element.append(otherHeader);
const otherEl = document.createElement('div');
otherEl.className = 're-land__other';
other.forEach((option, idx) => {
otherEl.append(
renderRouteCard({
option,
index: active.length + idx,
isActive: false,
onSelect: () => {},
}),
);
});
this.element.append(otherEl);
}
}
}
|