diff --git a/server/src/travel/route.ts b/server/src/travel/route.ts index 2fbe961..39368d0 100644 --- a/server/src/travel/route.ts +++ b/server/src/travel/route.ts @@ -141,6 +141,8 @@ export interface HaulItem { weight: number; buyAt: number; // sell-order price in origin (you buy instantly) sellAt: number; // sell-order price in destination + buyAgeHours: number | null; // how stale the origin price is + sellAgeHours: number | null; // how stale the destination price is netPerUnit: number; // after taxes at destination unitsCarryable: number; tripProfit: number; @@ -177,6 +179,8 @@ export async function haulAdvisor(from: string, to: string, maxWeight: number, m weight: it.weight, buyAt: buy.price, sellAt: sell.price, + buyAgeHours: buy.ageHours, + sellAgeHours: sell.ageHours, netPerUnit, unitsCarryable, tripProfit: netPerUnit * unitsCarryable, diff --git a/web/src/pages/OpportunitiesPage.tsx b/web/src/pages/OpportunitiesPage.tsx index 0ba72f9..77adc35 100644 --- a/web/src/pages/OpportunitiesPage.tsx +++ b/web/src/pages/OpportunitiesPage.tsx @@ -41,6 +41,14 @@ const CITIES = ['Bridgewatch', 'Caerleon', 'Fort Sterling', 'Lymhurst', 'Martloc const silver = (n: number) => Math.round(n).toLocaleString('en-US'); +/** Derive a "T6.4" chip from an item id like "T6_SHOES_PLATE_SET1@4" (blank for non-tiered ids). */ +function tierEnchant(id: string): string { + const t = id.match(/^T(\d)_/); + if (!t) return ''; + const e = id.match(/@(\d)$/); + return `T${t[1]}${e ? `.${e[1]}` : ''}`; +} + function Age({ hours }: { hours: number | null }) { if (hours == null) return null; const label = hours < 1 ? `${Math.round(hours * 60)}m` : hours < 48 ? `${Math.round(hours)}h` : `${Math.round(hours / 24)}d`; @@ -218,7 +226,12 @@ export default function OpportunitiesPage() { {o.inputs.map((inp) => ( - {o.inputNames[inp.item] ?? inp.item} + + {o.inputNames[inp.item] ?? inp.item} + {tierEnchant(inp.item) && ( + {tierEnchant(inp.item)} + )} + {inp.count} {inp.effectiveCount.toFixed(2)} {silver(inp.unitCost)} diff --git a/web/src/pages/TravelPage.tsx b/web/src/pages/TravelPage.tsx index 84f60ef..41dbb5b 100644 --- a/web/src/pages/TravelPage.tsx +++ b/web/src/pages/TravelPage.tsx @@ -26,12 +26,22 @@ interface HaulItem { weight: number; buyAt: number; sellAt: number; + buyAgeHours: number | null; + sellAgeHours: number | null; netPerUnit: number; unitsCarryable: number; tripProfit: number; profitPerWeight: number; } +/** Color-coded price-age badge: green fresh, yellow aging, red stale. */ +function PriceAge({ hours }: { hours: number | null }) { + if (hours == null) return —; + const label = hours < 1 ? `${Math.round(hours * 60)}m` : hours < 48 ? `${Math.round(hours)}h` : `${Math.round(hours / 24)}d`; + const color = hours < 6 ? '#7fd18a' : hours < 24 ? '#e0c069' : '#e08869'; + return {label}; +} + interface Status { zones: number; connections: number; @@ -218,7 +228,7 @@ export default function TravelPage() { - + @@ -226,7 +236,9 @@ export default function TravelPage() { + +
ItemBuy at {from}Sell at {to}Net/unitUnitsTrip profitProfit/kgItemBuy at {from}ageSell at {to}ageNet/unitUnitsTrip profitProfit/kg
{h.name ?? h.item} T{h.tier} {silver(h.buyAt)} {silver(h.sellAt)} {silver(h.netPerUnit)} {h.unitsCarryable} {silver(h.tripProfit)}