Compare commits
4
Commits
dadcf97bec
...
55c2628c75
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
55c2628c75 | ||
|
|
75d1d5f7fa | ||
|
|
2622d9f01e | ||
|
|
a7407b8e06 |
@@ -0,0 +1,110 @@
|
|||||||
|
import log from "loglevel";
|
||||||
|
import {getAccessToken} from "@/auth/token";
|
||||||
|
import {mammonUrl, refreshAccessToken} from "@/mammon";
|
||||||
|
|
||||||
|
const STREAM_URL = mammonUrl + "activities/processed";
|
||||||
|
const RECONNECT_DELAY_MILLIS = 3_000;
|
||||||
|
const FRAME_SEPARATOR = "\n\n";
|
||||||
|
const PROCESSED_EVENT = "event:processed";
|
||||||
|
|
||||||
|
const listeners = new Set<() => void>();
|
||||||
|
|
||||||
|
let controller: AbortController | undefined;
|
||||||
|
let reconnectTimer: ReturnType<typeof setTimeout> | undefined;
|
||||||
|
|
||||||
|
const notifyProcessed = () => listeners.forEach(listener => listener());
|
||||||
|
|
||||||
|
const scheduleReconnect = () => {
|
||||||
|
if (listeners.size === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
reconnectTimer = setTimeout(() => connect(), RECONNECT_DELAY_MILLIS);
|
||||||
|
};
|
||||||
|
|
||||||
|
const consume = async (body: ReadableStream<Uint8Array>) => {
|
||||||
|
const reader = body.getReader();
|
||||||
|
const decoder = new TextDecoder();
|
||||||
|
let buffer = "";
|
||||||
|
|
||||||
|
for (; ;) {
|
||||||
|
const {value, done} = await reader.read();
|
||||||
|
|
||||||
|
if (done) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
buffer += decoder.decode(value, {stream: true});
|
||||||
|
|
||||||
|
let boundary = buffer.indexOf(FRAME_SEPARATOR);
|
||||||
|
|
||||||
|
while (boundary !== -1) {
|
||||||
|
const frame = buffer.slice(0, boundary);
|
||||||
|
buffer = buffer.slice(boundary + FRAME_SEPARATOR.length);
|
||||||
|
|
||||||
|
if (frame.split("\n").some(line => line.trim() === PROCESSED_EVENT)) {
|
||||||
|
notifyProcessed();
|
||||||
|
}
|
||||||
|
boundary = buffer.indexOf(FRAME_SEPARATOR);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const connect = async (retried = false): Promise<void> => {
|
||||||
|
if (listeners.size === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const signal = controller?.signal;
|
||||||
|
const token = getAccessToken();
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(STREAM_URL, {
|
||||||
|
headers: {
|
||||||
|
Accept: "text/event-stream",
|
||||||
|
...(token ? {Authorization: `Bearer ${token}`} : {}),
|
||||||
|
},
|
||||||
|
signal,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (response.status === 401) {
|
||||||
|
if (!retried && await refreshAccessToken() && listeners.size > 0) {
|
||||||
|
return connect(true);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!response.ok || !response.body) {
|
||||||
|
throw new Error(`Activity processing SSE stream failed with ${response.status}`);
|
||||||
|
}
|
||||||
|
await consume(response.body);
|
||||||
|
scheduleReconnect();
|
||||||
|
} catch (error) {
|
||||||
|
if (listeners.size === 0 || signal?.aborted) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
log.debug("Activity processing SSE error, reconnecting", error);
|
||||||
|
scheduleReconnect();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const close = () => {
|
||||||
|
if (reconnectTimer) {
|
||||||
|
clearTimeout(reconnectTimer);
|
||||||
|
reconnectTimer = undefined;
|
||||||
|
}
|
||||||
|
controller?.abort();
|
||||||
|
controller = undefined;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const onActivitiesProcessed = (listener: () => void): (() => void) => {
|
||||||
|
listeners.add(listener);
|
||||||
|
|
||||||
|
if (!controller) {
|
||||||
|
controller = new AbortController();
|
||||||
|
connect();
|
||||||
|
}
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
if (listeners.delete(listener) && listeners.size === 0) {
|
||||||
|
close();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
@@ -1 +1,4 @@
|
|||||||
export {default as SourceLabel} from './SourceLabel.vue';
|
export {default as SourceLabel} from './SourceLabel.vue';
|
||||||
|
export {onActivitiesProcessed} from './activitiesProcessed';
|
||||||
|
export {useActivitiesProcessed} from './useActivitiesProcessed';
|
||||||
|
export {useProcessedResource} from './useProcessedResource';
|
||||||
|
|||||||
@@ -0,0 +1,8 @@
|
|||||||
|
import {onUnmounted} from "vue";
|
||||||
|
import {onActivitiesProcessed} from "./activitiesProcessed";
|
||||||
|
|
||||||
|
export const useActivitiesProcessed = (listener: () => void): (() => void) => {
|
||||||
|
const unsubscribe = onActivitiesProcessed(listener);
|
||||||
|
onUnmounted(unsubscribe);
|
||||||
|
return unsubscribe;
|
||||||
|
};
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
import {ref, type Ref, watchEffect} from "vue";
|
||||||
|
import {useActivitiesProcessed} from "./useActivitiesProcessed";
|
||||||
|
|
||||||
|
export const useProcessedResource = <T>(loader: () => Promise<T>, initial: T): Ref<T> => {
|
||||||
|
const data = ref(initial) as Ref<T>;
|
||||||
|
|
||||||
|
const load = async () => {
|
||||||
|
data.value = await loader();
|
||||||
|
};
|
||||||
|
|
||||||
|
watchEffect(load);
|
||||||
|
useActivitiesProcessed(load);
|
||||||
|
|
||||||
|
return data;
|
||||||
|
};
|
||||||
@@ -24,5 +24,5 @@ export const useCharactersStore = defineStore('characters', () => {
|
|||||||
|
|
||||||
refresh();
|
refresh();
|
||||||
|
|
||||||
return {characters, findById, reloadActivities, refresh};
|
return {characters, findById, reloadActivities};
|
||||||
})
|
})
|
||||||
@@ -37,7 +37,7 @@ describe('useSort', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
test('Hides ignored columns', () => {
|
test('Hides ignored columns', () => {
|
||||||
const { showColumn } = useSort(array, { ignoredColums: ['key1'] })
|
const { showColumn } = useSort(array, { ignoredColumns: ['key1'] })
|
||||||
|
|
||||||
expect(showColumn('key1')).toBe(false)
|
expect(showColumn('key1')).toBe(false)
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ export type SortDirection = "asc" | "desc";
|
|||||||
export type UseSortOptions = {
|
export type UseSortOptions = {
|
||||||
defaultSortKey?: string;
|
defaultSortKey?: string;
|
||||||
defaultSortDirection?: SortDirection;
|
defaultSortDirection?: SortDirection;
|
||||||
ignoredColums?: MaybeRefOrGetter<string[]>;
|
ignoredColumns?: MaybeRefOrGetter<string[]>;
|
||||||
headerComponent?: HeaderComponent;
|
headerComponent?: HeaderComponent;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -16,7 +16,7 @@ export const useSort = <T>(array: MaybeRefOrGetter<T[]>, options?: UseSortOption
|
|||||||
sortKey.value = key;
|
sortKey.value = key;
|
||||||
sortDirection.value = direction;
|
sortDirection.value = direction;
|
||||||
};
|
};
|
||||||
const showColumn = (sortKey: string) => !toValue(options?.ignoredColums)?.includes(sortKey);
|
const showColumn = (sortKey: string) => !toValue(options?.ignoredColumns)?.includes(sortKey);
|
||||||
const headerProps = computed(() => ({
|
const headerProps = computed(() => ({
|
||||||
onSort: sortBy,
|
onSort: sortBy,
|
||||||
showColumn,
|
showColumn,
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import {defineStore} from "pinia";
|
|||||||
import {computed, ref, triggerRef} from "vue";
|
import {computed, ref, triggerRef} from "vue";
|
||||||
import {ledgerApi, transactionApi} from "@/mammon";
|
import {ledgerApi, transactionApi} from "@/mammon";
|
||||||
import {useRouteParams} from "@vueuse/router";
|
import {useRouteParams} from "@vueuse/router";
|
||||||
|
import {onActivitiesProcessed} from "@/activity";
|
||||||
|
|
||||||
export const LedgerTypes = {
|
export const LedgerTypes = {
|
||||||
Main: 'MAIN',
|
Main: 'MAIN',
|
||||||
@@ -71,7 +72,9 @@ export const useLedgersStore = defineStore('ledgers', () => {
|
|||||||
|
|
||||||
refresh();
|
refresh();
|
||||||
|
|
||||||
return {ledgers, findById, findAllById, createMain, createCombined, updateMain, updateCombined, refresh};
|
onActivitiesProcessed(refresh);
|
||||||
|
|
||||||
|
return {ledgers, findById, findAllById, createMain, createCombined, updateMain, updateCombined};
|
||||||
})
|
})
|
||||||
|
|
||||||
const getLedgerId = (ledger: Ledger | string): string => typeof ledger == 'string' ? ledger : ledger.ledgerId;
|
const getLedgerId = (ledger: Ledger | string): string => typeof ledger == 'string' ? ledger : ledger.ledgerId;
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import {SliderCheckbox} from '@/components';
|
import {SliderCheckbox} from '@/components';
|
||||||
import {SortableHeader, useSort, VirtualScrollTable} from '@/components/table';
|
import {SortableHeader, useSort, VirtualScrollTable} from '@/components/table';
|
||||||
import {MarketType, MarketTypeLabel, TaxInput, useMarketOrdersStore, useMarketTaxStore} from "@/market";
|
import {getListedSellTypeIds, MarketType, MarketTypeLabel, TaxInput, useMarketTaxStore} from "@/market";
|
||||||
import {MinusIcon, PlusIcon} from '@heroicons/vue/24/outline';
|
import {MinusIcon, PlusIcon} from '@heroicons/vue/24/outline';
|
||||||
import {useStorage} from '@vueuse/core';
|
import {computedAsync, useStorage} from '@vueuse/core';
|
||||||
import {computed, ref} from 'vue';
|
import {computed, ref} from 'vue';
|
||||||
import {AcquiredType} from './AcquiredType';
|
import {AcquiredType} from './AcquiredType';
|
||||||
import AcquisitionQuartilesTooltip from './AcquisitionQuartilesTooltip.vue';
|
import AcquisitionQuartilesTooltip from './AcquisitionQuartilesTooltip.vue';
|
||||||
@@ -31,7 +31,7 @@ interface Props {
|
|||||||
items?: AcquiredType[];
|
items?: AcquiredType[];
|
||||||
infoOnly?: boolean;
|
infoOnly?: boolean;
|
||||||
showAll?: boolean;
|
showAll?: boolean;
|
||||||
ignoredColums?: string[] | string;
|
ignoredColumns?: string[] | string;
|
||||||
defaultSortKey?: string;
|
defaultSortKey?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -44,36 +44,37 @@ const props = withDefaults(defineProps<Props>(), {
|
|||||||
items: () => [],
|
items: () => [],
|
||||||
infoOnly: false,
|
infoOnly: false,
|
||||||
showAll: false,
|
showAll: false,
|
||||||
ignoredColums: () => [],
|
ignoredColumns: () => [],
|
||||||
defaultSortKey: 'precentProfit',
|
defaultSortKey: 'precentProfit',
|
||||||
});
|
});
|
||||||
defineEmits<Emits>();
|
defineEmits<Emits>();
|
||||||
|
|
||||||
const columnsToIgnore = computed(() => {
|
const columnsToIgnore = computed(() => {
|
||||||
const ignoredColums = typeof props.ignoredColums === 'string' ? [props.ignoredColums] : [...props.ignoredColums];
|
const ignoredColumns = typeof props.ignoredColumns === 'string' ? [props.ignoredColumns] : [...props.ignoredColumns];
|
||||||
|
|
||||||
if (props.infoOnly && !ignoredColums.includes('buttons')) {
|
if (props.infoOnly && !ignoredColumns.includes('buttons')) {
|
||||||
ignoredColums.push('buttons');
|
ignoredColumns.push('buttons');
|
||||||
}
|
}
|
||||||
if (!props.showAll && !ignoredColums.includes('ledger')) {
|
if (!props.showAll && !ignoredColumns.includes('ledger')) {
|
||||||
ignoredColums.push('ledger');
|
ignoredColumns.push('ledger');
|
||||||
}
|
}
|
||||||
if (ignoredColums.includes('ledger')) {
|
if (ignoredColumns.includes('ledger')) {
|
||||||
ignoredColums.push('ledgerName');
|
ignoredColumns.push('ledgerName');
|
||||||
}
|
}
|
||||||
return ignoredColums;
|
return ignoredColumns;
|
||||||
});
|
});
|
||||||
|
|
||||||
const marketTaxStore = useMarketTaxStore();
|
const marketTaxStore = useMarketTaxStore();
|
||||||
const ledgersStore = useLedgersStore();
|
const ledgersStore = useLedgersStore();
|
||||||
const marketOrdersStore = useMarketOrdersStore();
|
|
||||||
|
const listedTypeIds = computedAsync(getListedSellTypeIds, new Set<number>());
|
||||||
|
|
||||||
const threshold = useStorage('market-acquisition-threshold', 10);
|
const threshold = useStorage('market-acquisition-threshold', 10);
|
||||||
const filter = ref("");
|
const filter = ref("");
|
||||||
const unlistedOnly = ref(false);
|
const unlistedOnly = ref(false);
|
||||||
const matchesFilter = (r: AcquiredType) =>
|
const matchesFilter = (r: AcquiredType) =>
|
||||||
r.type.name.toLowerCase().includes(filter.value.toLowerCase())
|
r.type.name.toLowerCase().includes(filter.value.toLowerCase())
|
||||||
&& (!unlistedOnly.value || !marketOrdersStore.listedTypeIds.has(r.type.id));
|
&& (!unlistedOnly.value || !listedTypeIds.value.has(r.type.id));
|
||||||
const { sortedArray, headerProps, showColumn } = useSort<Result>(computed(() => {
|
const { sortedArray, headerProps, showColumn } = useSort<Result>(computed(() => {
|
||||||
const filteredItems = props.items.filter(matchesFilter);
|
const filteredItems = props.items.filter(matchesFilter);
|
||||||
|
|
||||||
@@ -135,7 +136,7 @@ const { sortedArray, headerProps, showColumn } = useSort<Result>(computed(() =>
|
|||||||
}), {
|
}), {
|
||||||
defaultSortKey: props.defaultSortKey,
|
defaultSortKey: props.defaultSortKey,
|
||||||
defaultSortDirection: 'desc',
|
defaultSortDirection: 'desc',
|
||||||
ignoredColums: columnsToIgnore
|
ignoredColumns: columnsToIgnore
|
||||||
})
|
})
|
||||||
const getLineColor = (result: Result) => {
|
const getLineColor = (result: Result) => {
|
||||||
if (result.precentProfit >= (threshold.value / 100)) {
|
if (result.precentProfit >= (threshold.value / 100)) {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import {getMarketTypes, MarketTypePrice, useApraisalStore} from "@/market";
|
import {getMarketTypes, MarketTypePrice, useAppraisalStore} from "@/market";
|
||||||
import {ref, watch} from 'vue';
|
import {ref, watch} from 'vue';
|
||||||
import {AcquiredType} from './AcquiredType';
|
import {AcquiredType} from './AcquiredType';
|
||||||
import {RawAcquiredType} from './acquisition';
|
import {RawAcquiredType} from './acquisition';
|
||||||
@@ -9,7 +9,7 @@ import SellModal from './SellModal.vue';
|
|||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
items: RawAcquiredType[];
|
items: RawAcquiredType[];
|
||||||
ignoredColums?: string[] | string;
|
ignoredColumns?: string[] | string;
|
||||||
ledgerId?: string;
|
ledgerId?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -18,17 +18,17 @@ const props = defineProps<Props>();
|
|||||||
const buyModal = ref<typeof BuyModal>();
|
const buyModal = ref<typeof BuyModal>();
|
||||||
const sellModal = ref<typeof SellModal>();
|
const sellModal = ref<typeof SellModal>();
|
||||||
|
|
||||||
const apraisalStore = useApraisalStore();
|
const appraisalStore = useAppraisalStore();
|
||||||
const enriched = ref<AcquiredType[]>([]);
|
const enriched = ref<AcquiredType[]>([]);
|
||||||
|
|
||||||
watch(() => props.items, async itms => {
|
const refresh = async (itms: RawAcquiredType[] = props.items) => {
|
||||||
if (itms.length === 0) {
|
if (itms.length === 0) {
|
||||||
enriched.value = [];
|
enriched.value = [];
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const types = await getMarketTypes([...new Set(itms.map(i => i.type))]);
|
const types = await getMarketTypes([...new Set(itms.map(i => i.type))]);
|
||||||
const prices = await apraisalStore.getPrices(types);
|
const prices = await appraisalStore.getPrices(types);
|
||||||
|
|
||||||
enriched.value = itms.map(i => {
|
enriched.value = itms.map(i => {
|
||||||
const price = prices.find(p => p.type.id === i.type) as MarketTypePrice;
|
const price = prices.find(p => p.type.id === i.type) as MarketTypePrice;
|
||||||
@@ -40,12 +40,16 @@ watch(() => props.items, async itms => {
|
|||||||
sell: price.sell
|
sell: price.sell
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
}, {immediate: true});
|
};
|
||||||
|
|
||||||
|
watch(() => props.items, refresh, {immediate: true});
|
||||||
|
|
||||||
|
defineExpose({refresh});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<template v-if="enriched.length > 0">
|
<template v-if="enriched.length > 0">
|
||||||
<AcquisitionResultTable :items="enriched" :ignoredColums="ignoredColums" @buy="(types, price, buy, sell) => buyModal?.open(types[0].type, { 'Price': price, 'Buy': buy, 'Sell': sell }, props.ledgerId)" @sell="types => sellModal?.open(types, props.ledgerId)" />
|
<AcquisitionResultTable :items="enriched" :ignoredColumns="ignoredColumns" @buy="(types, price, buy, sell) => buyModal?.open(types[0].type, { 'Price': price, 'Buy': buy, 'Sell': sell }, props.ledgerId)" @sell="types => sellModal?.open(types, props.ledgerId)" />
|
||||||
<BuyModal ref="buyModal" />
|
<BuyModal ref="buyModal" />
|
||||||
<SellModal ref="sellModal" />
|
<SellModal ref="sellModal" />
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import {defineStore} from "pinia";
|
|||||||
import {computed, ref} from "vue";
|
import {computed, ref} from "vue";
|
||||||
import {acquisitionApi, activityApi} from "@/mammon";
|
import {acquisitionApi, activityApi} from "@/mammon";
|
||||||
import {AcquisitionResponse, ActivitySourceResponse} from "@/generated/mammon";
|
import {AcquisitionResponse, ActivitySourceResponse} from "@/generated/mammon";
|
||||||
|
import {onActivitiesProcessed} from "@/activity";
|
||||||
|
|
||||||
export type RawAcquiredType = {
|
export type RawAcquiredType = {
|
||||||
id: string;
|
id: string;
|
||||||
@@ -66,5 +67,7 @@ export const useAcquiredTypesStore = defineStore('market-acquisition', () => {
|
|||||||
|
|
||||||
refresh();
|
refresh();
|
||||||
|
|
||||||
return { acquiredTypes: types, addAcquiredType, removeAcquiredType, processNewActivities, refresh };
|
onActivitiesProcessed(refresh);
|
||||||
|
|
||||||
|
return { acquiredTypes: types, addAcquiredType, removeAcquiredType, processNewActivities };
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import {getMammonPrices} from './mammon';
|
|||||||
const CACHE_DURATION = 1000 * 60 * 5; // 5 minutes
|
const CACHE_DURATION = 1000 * 60 * 5; // 5 minutes
|
||||||
const BATCH_SIZE = 100;
|
const BATCH_SIZE = 100;
|
||||||
|
|
||||||
export const useApraisalStore = defineStore('appraisal', () => {
|
export const useAppraisalStore = defineStore('appraisal', () => {
|
||||||
const cache: RegionalMarketCache<MarketTypePrice> = new RegionalMarketCache(CACHE_DURATION);
|
const cache: RegionalMarketCache<MarketTypePrice> = new RegionalMarketCache(CACHE_DURATION);
|
||||||
|
|
||||||
const getPricesUncached = getMammonPrices;
|
const getPricesUncached = getMammonPrices;
|
||||||
|
|||||||
@@ -1,21 +1,7 @@
|
|||||||
import {defineStore} from "pinia";
|
|
||||||
import {computed, ref} from "vue";
|
|
||||||
import {marketApi} from "@/mammon";
|
import {marketApi} from "@/mammon";
|
||||||
import {MarketOrderResponse, MarketOrderResponseDirectionEnum} from "@/generated/mammon";
|
import {MarketOrderResponseDirectionEnum} from "@/generated/mammon";
|
||||||
|
|
||||||
export const useMarketOrdersStore = defineStore('market-orders', () => {
|
export const getListedSellTypeIds = (): Promise<Set<number>> => marketApi.findAllMarketOrders()
|
||||||
const orders = ref<MarketOrderResponse[]>([]);
|
.then(response => new Set(response.data
|
||||||
|
|
||||||
const listedTypeIds = computed(() => new Set(
|
|
||||||
orders.value
|
|
||||||
.filter(o => o.direction === MarketOrderResponseDirectionEnum.Sell)
|
.filter(o => o.direction === MarketOrderResponseDirectionEnum.Sell)
|
||||||
.map(o => o.marketTypeId)
|
.map(o => o.marketTypeId)));
|
||||||
));
|
|
||||||
|
|
||||||
const refresh = () => marketApi.findAllMarketOrders()
|
|
||||||
.then(response => orders.value = response.data);
|
|
||||||
|
|
||||||
refresh();
|
|
||||||
|
|
||||||
return {orders, listedTypeIds, refresh};
|
|
||||||
});
|
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ type Result = {
|
|||||||
interface Props {
|
interface Props {
|
||||||
items?: ScanResult[];
|
items?: ScanResult[];
|
||||||
infoOnly?: boolean;
|
infoOnly?: boolean;
|
||||||
ignoredColums?: string[] | string;
|
ignoredColumns?: string[] | string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const scoreFormater = new Intl.NumberFormat("en-US", {
|
const scoreFormater = new Intl.NumberFormat("en-US", {
|
||||||
@@ -40,7 +40,7 @@ const volumeFormater = new Intl.NumberFormat("en-US", {
|
|||||||
const props = withDefaults(defineProps<Props>(), {
|
const props = withDefaults(defineProps<Props>(), {
|
||||||
items: () => [],
|
items: () => [],
|
||||||
infoOnly: false,
|
infoOnly: false,
|
||||||
ignoredColums: () => []
|
ignoredColumns: () => []
|
||||||
});
|
});
|
||||||
|
|
||||||
const acquiredTypesStore = useAcquiredTypesStore();
|
const acquiredTypesStore = useAcquiredTypesStore();
|
||||||
@@ -54,7 +54,7 @@ const trackedTrends = useStorage<MarketTrend[]>('market-scan-trends', [
|
|||||||
MarketTrends.Down,
|
MarketTrends.Down,
|
||||||
]);
|
]);
|
||||||
const columnsToIgnore = computed(() =>
|
const columnsToIgnore = computed(() =>
|
||||||
typeof props.ignoredColums === 'string' ? [props.ignoredColums] : props.ignoredColums
|
typeof props.ignoredColumns === 'string' ? [props.ignoredColumns] : props.ignoredColumns
|
||||||
);
|
);
|
||||||
const { sortedArray, headerProps, showColumn } = useSort<Result>(computed(() => props.items
|
const { sortedArray, headerProps, showColumn } = useSort<Result>(computed(() => props.items
|
||||||
.filter(r => r.type.name.toLowerCase().includes(filter.value.toLowerCase()))
|
.filter(r => r.type.name.toLowerCase().includes(filter.value.toLowerCase()))
|
||||||
@@ -82,7 +82,7 @@ const { sortedArray, headerProps, showColumn } = useSort<Result>(computed(() =>
|
|||||||
}).filter(r => !onlyCheap.value || (r.buy <= r.q1 && r.profit >= (threshold.value / 100)))), {
|
}).filter(r => !onlyCheap.value || (r.buy <= r.q1 && r.profit >= (threshold.value / 100)))), {
|
||||||
defaultSortKey: 'score',
|
defaultSortKey: 'score',
|
||||||
defaultSortDirection: 'desc',
|
defaultSortDirection: 'desc',
|
||||||
ignoredColums: columnsToIgnore
|
ignoredColumns: columnsToIgnore
|
||||||
})
|
})
|
||||||
const getLineColor = (result: Result) => {
|
const getLineColor = (result: Result) => {
|
||||||
if (props.infoOnly) {
|
if (props.infoOnly) {
|
||||||
|
|||||||
@@ -7,19 +7,17 @@ import {IskLabel} from "@/market";
|
|||||||
import {SortableHeader, useSort, VirtualScrollTable} from "@/components/table";
|
import {SortableHeader, useSort, VirtualScrollTable} from "@/components/table";
|
||||||
import {TransferList, TransferTypes} from "@/transaction";
|
import {TransferList, TransferTypes} from "@/transaction";
|
||||||
import {Dropdown} from "@/components";
|
import {Dropdown} from "@/components";
|
||||||
import {SourceLabel} from "@/activity";
|
import {SourceLabel, useProcessedResource} from "@/activity";
|
||||||
import {useCharactersStore} from "@/characters";
|
import {useCharactersStore} from "@/characters";
|
||||||
import {formatEveDate} from "@/formaters.ts";
|
import {formatEveDate} from "@/formaters.ts";
|
||||||
|
|
||||||
const {ledgerId} = useLedgerParam();
|
const {ledgerId} = useLedgerParam();
|
||||||
const charactersStore = useCharactersStore();
|
const charactersStore = useCharactersStore();
|
||||||
|
|
||||||
const transactions = computedAsync<TransactionResponse[]>(async () => {
|
const transactions = useProcessedResource<TransactionResponse[]>(
|
||||||
if (ledgerId.value) {
|
() => ledgerId.value ? findAllTransactionInLeger(ledgerId.value) : Promise.resolve([]),
|
||||||
return await findAllTransactionInLeger(ledgerId.value);
|
[],
|
||||||
}
|
);
|
||||||
return [];
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const { sortedArray, headerProps } = useSort(computedAsync(() => Promise.all(transactions.value.map(async transaction => {
|
const { sortedArray, headerProps } = useSort(computedAsync(() => Promise.all(transactions.value.map(async transaction => {
|
||||||
const source = transaction.source;
|
const source = transaction.source;
|
||||||
|
|||||||
@@ -14,7 +14,6 @@ const editLedgerModal = ref<typeof EditLedgerModal>();
|
|||||||
const processActivities = async () => {
|
const processActivities = async () => {
|
||||||
await activityApi.fetchAllNewActivities();
|
await activityApi.fetchAllNewActivities();
|
||||||
await activityApi.processNewActivities();
|
await activityApi.processNewActivities();
|
||||||
await ledgersStore.refresh();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const { sortedArray, headerProps } = useSort<Ledger>(() => ledgersStore.ledgers);
|
const { sortedArray, headerProps } = useSort<Ledger>(() => ledgersStore.ledgers);
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import {AcquisitionsPanel, toAcquiredType} from '@/market/acquisition';
|
import {AcquisitionsPanel, RawAcquiredType, toAcquiredType} from '@/market/acquisition';
|
||||||
import {useLedgerParam} from "@/ledger";
|
import {useLedgerParam} from "@/ledger";
|
||||||
import {acquisitionApi} from "@/mammon";
|
import {acquisitionApi} from "@/mammon";
|
||||||
import {computedAsync} from "@vueuse/core";
|
import {useProcessedResource} from "@/activity";
|
||||||
|
|
||||||
const {ledgerId} = useLedgerParam();
|
const {ledgerId} = useLedgerParam();
|
||||||
|
|
||||||
const items = computedAsync(async () => {
|
const items = useProcessedResource<RawAcquiredType[]>(async () => {
|
||||||
if (!ledgerId.value) {
|
if (!ledgerId.value) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
@@ -19,6 +19,6 @@ const items = computedAsync(async () => {
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="mt-4">
|
<div class="mt-4">
|
||||||
<AcquisitionsPanel :items="items" :ledgerId="ledgerId" :ignoredColums="['date', 'ledger']" />
|
<AcquisitionsPanel :items="items" :ledgerId="ledgerId" :ignoredColumns="['date', 'ledger']" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@@ -4,15 +4,14 @@ import {computedAsync} from "@vueuse/core";
|
|||||||
import {BalanceResponse} from "@/generated/mammon";
|
import {BalanceResponse} from "@/generated/mammon";
|
||||||
import {getMarketType, MarketTypeLabel} from "@/market";
|
import {getMarketType, MarketTypeLabel} from "@/market";
|
||||||
import {SortableHeader, useSort, VirtualScrollTable} from "@/components/table";
|
import {SortableHeader, useSort, VirtualScrollTable} from "@/components/table";
|
||||||
|
import {useProcessedResource} from "@/activity";
|
||||||
|
|
||||||
const {ledgerId} = useLedgerParam();
|
const {ledgerId} = useLedgerParam();
|
||||||
|
|
||||||
const balance = computedAsync<BalanceResponse>(async () => {
|
const balance = useProcessedResource<BalanceResponse | undefined>(
|
||||||
if (ledgerId.value) {
|
() => ledgerId.value ? getLedgerBalance(ledgerId.value) : Promise.resolve(undefined),
|
||||||
return await getLedgerBalance(ledgerId.value);
|
undefined,
|
||||||
}
|
);
|
||||||
return undefined;
|
|
||||||
});
|
|
||||||
|
|
||||||
const { sortedArray, headerProps } = useSort(computedAsync(async () => {
|
const { sortedArray, headerProps } = useSort(computedAsync(async () => {
|
||||||
const itemBalances = balance.value?.itemBalances;
|
const itemBalances = balance.value?.itemBalances;
|
||||||
|
|||||||
@@ -2,11 +2,13 @@
|
|||||||
import {AcquisitionsPanel, useAcquiredTypesStore} from '@/market/acquisition';
|
import {AcquisitionsPanel, useAcquiredTypesStore} from '@/market/acquisition';
|
||||||
import {ArrowPathIcon} from '@heroicons/vue/24/outline';
|
import {ArrowPathIcon} from '@heroicons/vue/24/outline';
|
||||||
import {useAutoRefresh} from '@/composables';
|
import {useAutoRefresh} from '@/composables';
|
||||||
|
import {ref} from 'vue';
|
||||||
|
|
||||||
const acquiredTypesStore = useAcquiredTypesStore();
|
const acquiredTypesStore = useAcquiredTypesStore();
|
||||||
|
const panel = ref<InstanceType<typeof AcquisitionsPanel>>();
|
||||||
|
|
||||||
const refresh = async () => {
|
const refresh = async () => {
|
||||||
await acquiredTypesStore.refresh();
|
await panel.value?.refresh();
|
||||||
}
|
}
|
||||||
|
|
||||||
const {active: autoRefresh, toggle: toggleAutoRefresh} = useAutoRefresh(refresh, 5 * 60 * 1000);
|
const {active: autoRefresh, toggle: toggleAutoRefresh} = useAutoRefresh(refresh, 5 * 60 * 1000);
|
||||||
@@ -22,6 +24,6 @@ const {active: autoRefresh, toggle: toggleAutoRefresh} = useAutoRefresh(refresh,
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<AcquisitionsPanel :items="acquiredTypesStore.acquiredTypes" ignoredColums="date" />
|
<AcquisitionsPanel ref="panel" :items="acquiredTypesStore.acquiredTypes" ignoredColumns="date" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import {ClipboardButton} from '@/components';
|
import {ClipboardButton} from '@/components';
|
||||||
import {getMarketType, MarketType, MarketTypeInput, useApraisalStore, useMarketTaxStore} from "@/market";
|
import {getMarketType, MarketType, MarketTypeInput, useAppraisalStore, useMarketTaxStore} from "@/market";
|
||||||
import {AcquisitionResultTable, BuyModal} from '@/market/acquisition';
|
import {AcquisitionResultTable, BuyModal} from '@/market/acquisition';
|
||||||
import {ScanResultTable, toScanResult} from '@/market/scan';
|
import {ScanResultTable, toScanResult} from '@/market/scan';
|
||||||
import {acquisitionApi, marketApi} from "@/mammon";
|
import {acquisitionApi, marketApi} from "@/mammon";
|
||||||
@@ -18,10 +18,10 @@ const router = useRouter();
|
|||||||
const item = ref<MarketType>();
|
const item = ref<MarketType>();
|
||||||
const inputItem = ref<MarketType>();
|
const inputItem = ref<MarketType>();
|
||||||
|
|
||||||
const apraisalStore = useApraisalStore();
|
const appraisalStore = useAppraisalStore();
|
||||||
const marketTaxStore = useMarketTaxStore();
|
const marketTaxStore = useMarketTaxStore();
|
||||||
const days = useStorage('market-scan-days', 365);
|
const days = useStorage('market-scan-days', 365);
|
||||||
const price = computedAsync(() => item.value ? apraisalStore.getPrice(item.value) : undefined);
|
const price = computedAsync(() => item.value ? appraisalStore.getPrice(item.value) : undefined);
|
||||||
const result = computedAsync(async () => {
|
const result = computedAsync(async () => {
|
||||||
if (!item.value) {
|
if (!item.value) {
|
||||||
return undefined;
|
return undefined;
|
||||||
@@ -107,11 +107,11 @@ watch(useRoute(), async route => {
|
|||||||
</div>
|
</div>
|
||||||
<div v-if="result" class="mb-4">
|
<div v-if="result" class="mb-4">
|
||||||
<span>Market Info:</span>
|
<span>Market Info:</span>
|
||||||
<ScanResultTable :items="[result]" infoOnly :ignoredColums="['name', 'acquisitions']" />
|
<ScanResultTable :items="[result]" infoOnly :ignoredColumns="['name', 'acquisitions']" />
|
||||||
</div>
|
</div>
|
||||||
<div v-if="acquisitions && acquisitions.length > 0">
|
<div v-if="acquisitions && acquisitions.length > 0">
|
||||||
<span>Acquisitions:</span>
|
<span>Acquisitions:</span>
|
||||||
<AcquisitionResultTable :items="acquisitions" infoOnly showAll :ignoredColums="['name', 'buy', 'sell']" defaultSortKey="date"/>
|
<AcquisitionResultTable :items="acquisitions" infoOnly showAll :ignoredColumns="['name', 'buy', 'sell']" defaultSortKey="date"/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<BuyModal ref="buyModal" />
|
<BuyModal ref="buyModal" />
|
||||||
|
|||||||
+1
-1
@@ -24,7 +24,7 @@ export const useRuleBookStore = defineStore('rule-book', () => {
|
|||||||
|
|
||||||
refresh();
|
refresh();
|
||||||
|
|
||||||
return {ruleBook, refresh, update};
|
return {ruleBook, update};
|
||||||
})
|
})
|
||||||
|
|
||||||
export const fetchScriptDefinitions = (): Promise<string> =>
|
export const fetchScriptDefinitions = (): Promise<string> =>
|
||||||
|
|||||||
Reference in New Issue
Block a user