feat(#17): Add an appraisal compare-by-location page
This commit is contained in:
@@ -0,0 +1,128 @@
|
||||
<script setup lang="ts">
|
||||
import {SortableHeader, useSort, VirtualScrollTable} from '@/components/table';
|
||||
import {MarketTypeLabel} from '@/market/type';
|
||||
import {computed} from 'vue';
|
||||
import {MarketLocation} from '../location';
|
||||
import IskLabel from '../IskLabel.vue';
|
||||
import {ComparedItem} from './compare';
|
||||
|
||||
interface Props {
|
||||
result?: ComparedItem[];
|
||||
locations?: MarketLocation[];
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
result: () => [],
|
||||
locations: () => []
|
||||
});
|
||||
|
||||
const buyKey = (id: number): `buy:${number}` => `buy:${id}`;
|
||||
const sellKey = (id: number): `sell:${number}` => `sell:${id}`;
|
||||
|
||||
type SortableItem = ComparedItem & Record<`buy:${number}` | `sell:${number}`, number>;
|
||||
|
||||
const rows = computed<SortableItem[]>(() => props.result.map(item => {
|
||||
const sortable = {...item} as SortableItem;
|
||||
|
||||
props.locations.forEach(l => {
|
||||
const price = item.prices[l.id];
|
||||
sortable[buyKey(l.id)] = price?.buy ?? 0;
|
||||
sortable[sellKey(l.id)] = price?.sell ?? 0;
|
||||
});
|
||||
|
||||
return sortable;
|
||||
}));
|
||||
|
||||
const {sortedArray, headerProps} = useSort(rows, {
|
||||
defaultSortKey: 'name',
|
||||
defaultSortDirection: 'asc'
|
||||
});
|
||||
|
||||
const bestBuy = (row: ComparedItem): number | undefined => {
|
||||
let best: number | undefined;
|
||||
let bestValue = -Infinity;
|
||||
props.locations.forEach(l => {
|
||||
const cell = row.prices[l.id];
|
||||
if (cell && cell.buy > bestValue) {
|
||||
bestValue = cell.buy;
|
||||
best = l.id;
|
||||
}
|
||||
});
|
||||
return bestValue > 0 ? best : undefined;
|
||||
};
|
||||
|
||||
const bestSell = (row: ComparedItem): number | undefined => {
|
||||
let best: number | undefined;
|
||||
let bestValue = -Infinity;
|
||||
props.locations.forEach(l => {
|
||||
const cell = row.prices[l.id];
|
||||
if (cell && cell.sell > bestValue) {
|
||||
bestValue = cell.sell;
|
||||
best = l.id;
|
||||
}
|
||||
});
|
||||
return bestValue > 0 ? best : undefined;
|
||||
};
|
||||
|
||||
const totals = computed(() => new Map(props.locations.map(l => [l.id, props.result.reduce((acc, row) => {
|
||||
const cell = row.prices[l.id];
|
||||
return {
|
||||
buy: acc.buy + (cell?.buy ?? 0),
|
||||
sell: acc.sell + (cell?.sell ?? 0),
|
||||
};
|
||||
}, {buy: 0, sell: 0})])));
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<VirtualScrollTable :list="sortedArray" :itemHeight="33" :headerHeight="66" :footerHeight="33" bottom="1rem">
|
||||
<template #default="{ list }">
|
||||
<thead>
|
||||
<tr>
|
||||
<SortableHeader v-bind="headerProps" sortKey="name" rowspan="2">Item</SortableHeader>
|
||||
<SortableHeader v-bind="headerProps" sortKey="quantity" rowspan="2">Qty</SortableHeader>
|
||||
<th v-for="location in locations" :key="location.id" colspan="2" class="text-center border-l border-slate-500" :title="location.name">
|
||||
{{ location.systemName }}
|
||||
</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<template v-for="location in locations" :key="location.id">
|
||||
<SortableHeader v-bind="headerProps" :sortKey="buyKey(location.id)" class="border-l border-slate-500">Buy</SortableHeader>
|
||||
<SortableHeader v-bind="headerProps" :sortKey="sellKey(location.id)">Sell</SortableHeader>
|
||||
</template>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="r in list" :key="r.data.typeId">
|
||||
<td>
|
||||
<MarketTypeLabel :id="r.data.typeId" :name="r.data.name" />
|
||||
</td>
|
||||
<td class="text-right">{{ r.data.quantity.toLocaleString() }}</td>
|
||||
<template v-for="location in locations" :key="location.id">
|
||||
<td class="text-right border-l border-slate-500" :class="r.data.prices[location.id] ? (bestBuy(r.data) === location.id ? 'bg-emerald-500' : 'bg-amber-900') : ''">
|
||||
<IskLabel v-if="r.data.prices[location.id]" :amount="r.data.prices[location.id].buy" :colored="false" />
|
||||
<span v-else>-</span>
|
||||
</td>
|
||||
<td class="text-right" :class="r.data.prices[location.id] ? (bestSell(r.data) === location.id ? 'bg-emerald-500' : 'bg-amber-900') : ''">
|
||||
<IskLabel v-if="r.data.prices[location.id]" :amount="r.data.prices[location.id].sell" :colored="false" />
|
||||
<span v-else>-</span>
|
||||
</td>
|
||||
</template>
|
||||
</tr>
|
||||
</tbody>
|
||||
<tfoot>
|
||||
<tr>
|
||||
<td colspan="2" class="font-semibold">Total</td>
|
||||
<template v-for="location in locations" :key="location.id">
|
||||
<td class="text-right border-l border-slate-500"><IskLabel :amount="totals.get(location.id)?.buy ?? 0" :colored="false" /></td>
|
||||
<td class="text-right"><IskLabel :amount="totals.get(location.id)?.sell ?? 0" :colored="false" /></td>
|
||||
</template>
|
||||
</tr>
|
||||
</tfoot>
|
||||
</template>
|
||||
<template #empty>
|
||||
<div class="text-center mt-4">
|
||||
<span>No items found</span>
|
||||
</div>
|
||||
</template>
|
||||
</VirtualScrollTable>
|
||||
</template>
|
||||
Reference in New Issue
Block a user