From 81250953ebda2982c9cc54c1dc1d98e7cc31e6da Mon Sep 17 00:00:00 2001 From: Sirttas Date: Thu, 9 Jul 2026 19:06:53 +0200 Subject: [PATCH] feat(#17): Add an appraisal compare-by-location page --- src/components/Dropdown.vue | 2 +- src/mammon/mammonService.ts | 6 +- src/market/appraisal/CompareResultTable.vue | 128 ++++++++++++++++++++ src/market/appraisal/MarketTypePrice.ts | 2 +- src/market/appraisal/appraisal.ts | 12 +- src/market/appraisal/compare.ts | 66 ++++++++++ src/market/appraisal/index.ts | 2 + src/market/appraisal/mammon.ts | 4 +- src/market/index.ts | 1 + src/market/location/MarketLocation.ts | 15 +++ src/market/location/MarketLocationInput.vue | 51 ++++++++ src/market/location/index.ts | 2 + src/market/market.ts | 2 +- src/pages/Market.vue | 3 + src/pages/market/Appraise.vue | 21 ++-- src/pages/market/CompareAppraisal.vue | 63 ++++++++++ src/routes.ts | 2 + 17 files changed, 361 insertions(+), 21 deletions(-) create mode 100644 src/market/appraisal/CompareResultTable.vue create mode 100644 src/market/appraisal/compare.ts create mode 100644 src/market/location/MarketLocation.ts create mode 100644 src/market/location/MarketLocationInput.vue create mode 100644 src/market/location/index.ts create mode 100644 src/pages/market/CompareAppraisal.vue diff --git a/src/components/Dropdown.vue b/src/components/Dropdown.vue index 580ce6e..ce68a8e 100644 --- a/src/components/Dropdown.vue +++ b/src/components/Dropdown.vue @@ -14,7 +14,7 @@ const props = withDefaults(defineProps(), { autoClose: true }) -const isOpen = ref(false); +const isOpen = defineModel('open', {default: false}); const root = ref(null); const floating = ref(null); diff --git a/src/mammon/mammonService.ts b/src/mammon/mammonService.ts index a5dce62..8316e53 100644 --- a/src/mammon/mammonService.ts +++ b/src/mammon/mammonService.ts @@ -12,7 +12,8 @@ import { ReprocessingApi, RuleBookApi, RuleScriptApi, - TransactionApi + TransactionApi, + UniverseApi } from "@/generated/mammon"; export const mammonUrl = import.meta.env.VITE_MAMMON_URL; @@ -105,4 +106,5 @@ export const ruleScriptApi = new RuleScriptApi(undefined, mammonUrl, mammonAxios export const activityApi = new ActivityApi(undefined, mammonUrl, mammonAxiosInstance); export const acquisitionApi = new AcquisitionApi(undefined, mammonUrl, mammonAxiosInstance); export const marketApi = new MarketApi(undefined, mammonUrl, mammonAxiosInstance); -export const reprocessingApi = new ReprocessingApi(undefined, mammonUrl, mammonAxiosInstance); \ No newline at end of file +export const reprocessingApi = new ReprocessingApi(undefined, mammonUrl, mammonAxiosInstance); +export const universeApi = new UniverseApi(undefined, mammonUrl, mammonAxiosInstance); \ No newline at end of file diff --git a/src/market/appraisal/CompareResultTable.vue b/src/market/appraisal/CompareResultTable.vue new file mode 100644 index 0000000..584beeb --- /dev/null +++ b/src/market/appraisal/CompareResultTable.vue @@ -0,0 +1,128 @@ + + + diff --git a/src/market/appraisal/MarketTypePrice.ts b/src/market/appraisal/MarketTypePrice.ts index 1ed52c2..0998dba 100644 --- a/src/market/appraisal/MarketTypePrice.ts +++ b/src/market/appraisal/MarketTypePrice.ts @@ -8,4 +8,4 @@ export type MarketTypePrice = { orderCount: number; }; -export type PriceGetter = (types: MarketType[]) => Promise; +export type PriceGetter = (types: MarketType[], locationId?: number) => Promise; diff --git a/src/market/appraisal/appraisal.ts b/src/market/appraisal/appraisal.ts index 2f9c2a0..1ae4d3e 100644 --- a/src/market/appraisal/appraisal.ts +++ b/src/market/appraisal/appraisal.ts @@ -13,14 +13,14 @@ export const useAppraisalStore = defineStore('appraisal', () => { const getPricesUncached = getMammonPrices; - const getPrice = async (type: MarketType, regionId?: number): Promise => (await getPrices([type], regionId))[0]; - const getPrices = async (types: MarketType[], regionId?: number): Promise => { + const getPrice = async (type: MarketType, locationId?: number): Promise => (await getPrices([type], locationId))[0]; + const getPrices = async (types: MarketType[], locationId?: number): Promise => { const cached: MarketTypePrice[] = []; const uncached: MarketType[] = []; - const rId = regionId ?? jitaId; + const lId = locationId ?? jitaId; types.forEach(t => { - const cachedPrice = cache.get(rId, t.id); + const cachedPrice = cache.get(lId, t.id); if (cachedPrice) { cached.push(cachedPrice); @@ -33,12 +33,12 @@ export const useAppraisalStore = defineStore('appraisal', () => { const batches: Promise[] = []; for (let i = 0; i < uncached.length; i += BATCH_SIZE) { - batches.push(getPricesUncached(uncached.slice(i, i + BATCH_SIZE))); + batches.push(getPricesUncached(uncached.slice(i, i + BATCH_SIZE), lId)); } const prices = (await Promise.all(batches)).flat(); - prices.forEach(p => cache.set(rId, p.type.id, p)); + prices.forEach(p => cache.set(lId, p.type.id, p)); return [ ...cached, ...prices ]; } return cached; diff --git a/src/market/appraisal/compare.ts b/src/market/appraisal/compare.ts new file mode 100644 index 0000000..5a6397d --- /dev/null +++ b/src/market/appraisal/compare.ts @@ -0,0 +1,66 @@ +import {marketApi} from '@/mammon'; +import {getMarketTypes, MarketType} from '../type'; +import {MarketTypePrice} from './MarketTypePrice'; + +export type LocationPrice = { + buyUnit: number; + sellUnit: number; + buy: number; + sell: number; +}; + +export type ComparedItem = { + typeId: number; + name: string; + quantity: number; + volume: number; + prices: Record; +}; + +export type PricesForLocation = (types: MarketType[], locationId: number) => Promise; + +export const compareAppraisal = async ( + raw: string, + locationIds: number[], + getPrices: PricesForLocation +): Promise => { + if (!raw?.trim() || locationIds.length === 0) { + return []; + } + + const stacks = await marketApi.parseStacks(raw).then(r => r.data); + + if (stacks.length === 0) { + return []; + } + + const quantities = new Map(stacks.map(s => [s.marketTypeId, s.quantity])); + const types = await getMarketTypes(stacks.map(s => s.marketTypeId)); + + const rows = new Map(types.map(t => [t.id, { + typeId: t.id, + name: t.name, + quantity: quantities.get(t.id) ?? 0, + volume: t.volume, + prices: {}, + }])); + + const pricesByLocation = await Promise.all( + locationIds.map(async locationId => [locationId, await getPrices(types, locationId)] as const) + ); + + pricesByLocation.forEach(([locationId, prices]) => prices.forEach(p => { + const row = rows.get(p.type.id); + + if (row) { + row.prices[locationId] = { + buyUnit: p.buy, + sellUnit: p.sell, + buy: p.buy * row.quantity, + sell: p.sell * row.quantity, + }; + } + })); + + return [...rows.values()]; +}; diff --git a/src/market/appraisal/index.ts b/src/market/appraisal/index.ts index 3635217..b1c63b8 100644 --- a/src/market/appraisal/index.ts +++ b/src/market/appraisal/index.ts @@ -1,5 +1,7 @@ export * from './MarketTypePrice'; export * from './appraisal'; export * from './appraise'; +export * from './compare'; export { default as AppraisalResultTable } from './AppraisalResultTable.vue'; +export { default as CompareResultTable } from './CompareResultTable.vue'; diff --git a/src/market/appraisal/mammon.ts b/src/market/appraisal/mammon.ts index dbb6d4b..983c229 100644 --- a/src/market/appraisal/mammon.ts +++ b/src/market/appraisal/mammon.ts @@ -1,13 +1,13 @@ import {marketApi} from '@/mammon/mammonService'; import {MarketTypePrice, PriceGetter} from './MarketTypePrice'; -export const getMammonPrices: PriceGetter = async types => { +export const getMammonPrices: PriceGetter = async (types, locationId) => { if (types.length === 0) { return []; } const typesById = new Map(types.map(t => [t.id, t])); - const response = await marketApi.currentPrices(types.map(t => t.id)); + const response = await marketApi.currentPrices(types.map(t => t.id), locationId); return response.data.reduce((prices, p) => { const type = typesById.get(p.marketTypeId); diff --git a/src/market/index.ts b/src/market/index.ts index 54e1b81..723a9f6 100644 --- a/src/market/index.ts +++ b/src/market/index.ts @@ -1,5 +1,6 @@ export * from './RegionalMarketCache'; export * from './history'; +export * from './location'; export * from './order'; export * from './tax'; export * from './type'; diff --git a/src/market/location/MarketLocation.ts b/src/market/location/MarketLocation.ts new file mode 100644 index 0000000..a3dcb09 --- /dev/null +++ b/src/market/location/MarketLocation.ts @@ -0,0 +1,15 @@ +import {universeApi} from '@/mammon/mammonService'; +import type {MarketLocationResponse} from '@/generated/mammon'; + +export type MarketLocation = MarketLocationResponse; + +const cache = new Map(); + +export const searchMarketLocations = async (search: string): Promise => { + if (search.length === 0) { + return []; + } + const locations = await universeApi.searchLocations(search).then(r => r.data); + locations.forEach(l => cache.set(l.id, l)); + return locations; +}; diff --git a/src/market/location/MarketLocationInput.vue b/src/market/location/MarketLocationInput.vue new file mode 100644 index 0000000..21a8fc2 --- /dev/null +++ b/src/market/location/MarketLocationInput.vue @@ -0,0 +1,51 @@ + + + diff --git a/src/market/location/index.ts b/src/market/location/index.ts new file mode 100644 index 0000000..bdbb095 --- /dev/null +++ b/src/market/location/index.ts @@ -0,0 +1,2 @@ +export * from './MarketLocation'; +export { default as MarketLocationInput } from './MarketLocationInput.vue'; diff --git a/src/market/market.ts b/src/market/market.ts index 53a3215..1f20986 100644 --- a/src/market/market.ts +++ b/src/market/market.ts @@ -1 +1 @@ -export const jitaId = 10000002; +export const jitaId = 60003760; \ No newline at end of file diff --git a/src/pages/Market.vue b/src/pages/Market.vue index ab60362..244022d 100644 --- a/src/pages/Market.vue +++ b/src/pages/Market.vue @@ -18,6 +18,9 @@ import {routeNames} from '@/routes'; Appraisal + + Compare + diff --git a/src/pages/market/Appraise.vue b/src/pages/market/Appraise.vue index ba87410..e3c9301 100644 --- a/src/pages/market/Appraise.vue +++ b/src/pages/market/Appraise.vue @@ -1,8 +1,10 @@