Compare commits
6
Commits
9cd0d5fb5e
...
2ab3f01d89
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2ab3f01d89 | ||
|
|
3981475c55 | ||
|
|
acde42b406 | ||
|
|
7ca38aee70 | ||
|
|
dd031551ca | ||
|
|
f3cb4798d5 |
+220
-21
@@ -57,7 +57,10 @@ paths:
|
|||||||
schema:
|
schema:
|
||||||
$ref: "#/components/schemas/RuleBookResponse"
|
$ref: "#/components/schemas/RuleBookResponse"
|
||||||
"400":
|
"400":
|
||||||
description: Invalid request (e.g. blank name)
|
description: |-
|
||||||
|
Returned when:
|
||||||
|
- the request is invalid (e.g. blank name)
|
||||||
|
- the rule book is the default rule book, which cannot be modified
|
||||||
delete:
|
delete:
|
||||||
tags:
|
tags:
|
||||||
- rule-book
|
- rule-book
|
||||||
@@ -75,7 +78,10 @@ paths:
|
|||||||
"204":
|
"204":
|
||||||
description: The rule book was deleted
|
description: The rule book was deleted
|
||||||
"400":
|
"400":
|
||||||
description: The rule book is associated to a character
|
description: |-
|
||||||
|
Returned when:
|
||||||
|
- the rule book is associated to a character
|
||||||
|
- the rule book is the default rule book, which cannot be deleted
|
||||||
/ledgers/main/{ledgerId}:
|
/ledgers/main/{ledgerId}:
|
||||||
put:
|
put:
|
||||||
tags:
|
tags:
|
||||||
@@ -105,7 +111,10 @@ paths:
|
|||||||
schema:
|
schema:
|
||||||
$ref: "#/components/schemas/MainLedgerResponse"
|
$ref: "#/components/schemas/MainLedgerResponse"
|
||||||
"400":
|
"400":
|
||||||
description: "The ledger is not a main ledger, or the request is invalid"
|
description: |-
|
||||||
|
Returned when:
|
||||||
|
- the ledger is not a main ledger
|
||||||
|
- the request is invalid
|
||||||
"404":
|
"404":
|
||||||
description: No ledger with this id
|
description: No ledger with this id
|
||||||
/ledgers/combined/{ledgerId}:
|
/ledgers/combined/{ledgerId}:
|
||||||
@@ -137,7 +146,10 @@ paths:
|
|||||||
schema:
|
schema:
|
||||||
$ref: "#/components/schemas/CombinedLedgerResponse"
|
$ref: "#/components/schemas/CombinedLedgerResponse"
|
||||||
"400":
|
"400":
|
||||||
description: "The ledger is not a combined ledger, or the request is invalid"
|
description: |-
|
||||||
|
Returned when:
|
||||||
|
- the ledger is not a combined ledger
|
||||||
|
- the request is invalid
|
||||||
"404":
|
"404":
|
||||||
description: No ledger with this id
|
description: No ledger with this id
|
||||||
/characters/{characterId}/rule-book:
|
/characters/{characterId}/rule-book:
|
||||||
@@ -193,8 +205,12 @@ paths:
|
|||||||
schema:
|
schema:
|
||||||
$ref: "#/components/schemas/CharacterRuleBookResponse"
|
$ref: "#/components/schemas/CharacterRuleBookResponse"
|
||||||
"400":
|
"400":
|
||||||
description: "The referenced rule book or a bound ledger does not exist,\
|
description: |-
|
||||||
\ or a ledger binding is missing"
|
Returned when:
|
||||||
|
- the referenced rule book does not exist
|
||||||
|
- a bound ledger does not exist
|
||||||
|
- a bound ledger is not a main or system ledger
|
||||||
|
- a required ledger binding is missing
|
||||||
/rule-books:
|
/rule-books:
|
||||||
get:
|
get:
|
||||||
tags:
|
tags:
|
||||||
@@ -225,6 +241,13 @@ paths:
|
|||||||
responses:
|
responses:
|
||||||
"201":
|
"201":
|
||||||
description: The created rule book
|
description: The created rule book
|
||||||
|
headers:
|
||||||
|
Location:
|
||||||
|
description: URL of the created rule book
|
||||||
|
style: simple
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
format: uri
|
||||||
content:
|
content:
|
||||||
'*/*':
|
'*/*':
|
||||||
schema:
|
schema:
|
||||||
@@ -256,6 +279,13 @@ paths:
|
|||||||
responses:
|
responses:
|
||||||
"201":
|
"201":
|
||||||
description: The created main ledger
|
description: The created main ledger
|
||||||
|
headers:
|
||||||
|
Location:
|
||||||
|
description: URL of the created main ledger
|
||||||
|
style: simple
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
format: uri
|
||||||
content:
|
content:
|
||||||
'*/*':
|
'*/*':
|
||||||
schema:
|
schema:
|
||||||
@@ -278,6 +308,13 @@ paths:
|
|||||||
responses:
|
responses:
|
||||||
"201":
|
"201":
|
||||||
description: The created combined ledger
|
description: The created combined ledger
|
||||||
|
headers:
|
||||||
|
Location:
|
||||||
|
description: URL of the created combined ledger
|
||||||
|
style: simple
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
format: uri
|
||||||
content:
|
content:
|
||||||
'*/*':
|
'*/*':
|
||||||
schema:
|
schema:
|
||||||
@@ -326,6 +363,88 @@ paths:
|
|||||||
text/plain:
|
text/plain:
|
||||||
schema:
|
schema:
|
||||||
type: string
|
type: string
|
||||||
|
/market/{marketTypeId}/history:
|
||||||
|
get:
|
||||||
|
tags:
|
||||||
|
- market
|
||||||
|
summary: "Find the market history of a type, most recent first"
|
||||||
|
operationId: findHistory
|
||||||
|
parameters:
|
||||||
|
- name: marketTypeId
|
||||||
|
in: path
|
||||||
|
description: Id of the market type
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
type: integer
|
||||||
|
format: int64
|
||||||
|
- name: days
|
||||||
|
in: query
|
||||||
|
description: Optional number of most recent days to return; omit for the full
|
||||||
|
history
|
||||||
|
required: false
|
||||||
|
schema:
|
||||||
|
type: integer
|
||||||
|
format: int32
|
||||||
|
minimum: 1
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: The market history of the type
|
||||||
|
content:
|
||||||
|
'*/*':
|
||||||
|
schema:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
$ref: "#/components/schemas/MarketHistoryResponse"
|
||||||
|
"400":
|
||||||
|
description: The days parameter is not greater than 0
|
||||||
|
/market/scan:
|
||||||
|
get:
|
||||||
|
tags:
|
||||||
|
- market
|
||||||
|
summary: "Scan every tracked market type, returning volume-weighted price quartiles\
|
||||||
|
\ for each"
|
||||||
|
operationId: scanMarket
|
||||||
|
parameters:
|
||||||
|
- name: days
|
||||||
|
in: query
|
||||||
|
description: Number of most recent days of history to analyse
|
||||||
|
required: false
|
||||||
|
schema:
|
||||||
|
type: integer
|
||||||
|
format: int32
|
||||||
|
default: 365
|
||||||
|
minimum: 1
|
||||||
|
- name: brokerFee
|
||||||
|
in: query
|
||||||
|
description: "Broker fee as a fraction (e.g. 0.015 for 1.5%), paid on both\
|
||||||
|
\ buy and sell orders"
|
||||||
|
required: false
|
||||||
|
schema:
|
||||||
|
type: number
|
||||||
|
default: 0.015
|
||||||
|
maximum: 1
|
||||||
|
minimum: 0
|
||||||
|
- name: salesTax
|
||||||
|
in: query
|
||||||
|
description: "Sales tax as a fraction (e.g. 0.036 for 3.6%), paid on sell\
|
||||||
|
\ orders"
|
||||||
|
required: false
|
||||||
|
schema:
|
||||||
|
type: number
|
||||||
|
default: 0.036
|
||||||
|
maximum: 1
|
||||||
|
minimum: 0
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: "The scan results, one entry per tracked market type"
|
||||||
|
content:
|
||||||
|
'*/*':
|
||||||
|
schema:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
$ref: "#/components/schemas/MarketScanResponse"
|
||||||
|
"400":
|
||||||
|
description: The days parameter is not greater than 0
|
||||||
/ledgers:
|
/ledgers:
|
||||||
get:
|
get:
|
||||||
tags:
|
tags:
|
||||||
@@ -429,6 +548,21 @@ paths:
|
|||||||
type: array
|
type: array
|
||||||
items:
|
items:
|
||||||
$ref: "#/components/schemas/CharacterResponse"
|
$ref: "#/components/schemas/CharacterResponse"
|
||||||
|
/characters/rule-books:
|
||||||
|
get:
|
||||||
|
tags:
|
||||||
|
- character-rule-book
|
||||||
|
summary: Find the rule books of all characters that have a token
|
||||||
|
operationId: findAllCharacterRuleBooks
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: Rule book assignments of all characters with a token
|
||||||
|
content:
|
||||||
|
'*/*':
|
||||||
|
schema:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
$ref: "#/components/schemas/CharacterRuleBookResponse"
|
||||||
/acquisitions:
|
/acquisitions:
|
||||||
get:
|
get:
|
||||||
tags:
|
tags:
|
||||||
@@ -560,15 +694,24 @@ components:
|
|||||||
required:
|
required:
|
||||||
- bindings
|
- bindings
|
||||||
- ruleBookId
|
- ruleBookId
|
||||||
CharacterRuleBookResponse:
|
CharacterResponse:
|
||||||
type: object
|
type: object
|
||||||
properties:
|
properties:
|
||||||
characterId:
|
characterId:
|
||||||
type: integer
|
type: integer
|
||||||
format: int64
|
format: int64
|
||||||
ruleBookId:
|
name:
|
||||||
type: string
|
type: string
|
||||||
format: uuid
|
required:
|
||||||
|
- characterId
|
||||||
|
- name
|
||||||
|
CharacterRuleBookResponse:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
character:
|
||||||
|
$ref: "#/components/schemas/CharacterResponse"
|
||||||
|
ruleBook:
|
||||||
|
$ref: "#/components/schemas/RuleBookSummaryResponse"
|
||||||
bindings:
|
bindings:
|
||||||
type: object
|
type: object
|
||||||
additionalProperties:
|
additionalProperties:
|
||||||
@@ -576,7 +719,18 @@ components:
|
|||||||
format: uuid
|
format: uuid
|
||||||
required:
|
required:
|
||||||
- bindings
|
- bindings
|
||||||
- characterId
|
- character
|
||||||
|
- ruleBook
|
||||||
|
RuleBookSummaryResponse:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
ruleBookId:
|
||||||
|
type: string
|
||||||
|
format: uuid
|
||||||
|
name:
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- name
|
||||||
- ruleBookId
|
- ruleBookId
|
||||||
CreateRuleBookRequest:
|
CreateRuleBookRequest:
|
||||||
type: object
|
type: object
|
||||||
@@ -617,6 +771,62 @@ components:
|
|||||||
required:
|
required:
|
||||||
- memberLedgerIds
|
- memberLedgerIds
|
||||||
- name
|
- name
|
||||||
|
MarketHistoryResponse:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
marketTypeId:
|
||||||
|
type: integer
|
||||||
|
format: int64
|
||||||
|
date:
|
||||||
|
type: string
|
||||||
|
format: date
|
||||||
|
average:
|
||||||
|
type: number
|
||||||
|
highest:
|
||||||
|
type: number
|
||||||
|
lowest:
|
||||||
|
type: number
|
||||||
|
orderCount:
|
||||||
|
type: integer
|
||||||
|
format: int64
|
||||||
|
volume:
|
||||||
|
type: integer
|
||||||
|
format: int64
|
||||||
|
required:
|
||||||
|
- average
|
||||||
|
- date
|
||||||
|
- highest
|
||||||
|
- lowest
|
||||||
|
- marketTypeId
|
||||||
|
- orderCount
|
||||||
|
- volume
|
||||||
|
MarketScanResponse:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
marketTypeId:
|
||||||
|
type: integer
|
||||||
|
format: int64
|
||||||
|
q1:
|
||||||
|
type: number
|
||||||
|
median:
|
||||||
|
type: number
|
||||||
|
q3:
|
||||||
|
type: number
|
||||||
|
totalVolume:
|
||||||
|
type: integer
|
||||||
|
format: int64
|
||||||
|
profit:
|
||||||
|
type: number
|
||||||
|
score:
|
||||||
|
type: number
|
||||||
|
required:
|
||||||
|
- marketTypeId
|
||||||
|
- median
|
||||||
|
- profit
|
||||||
|
- q1
|
||||||
|
- q3
|
||||||
|
- score
|
||||||
|
- totalVolume
|
||||||
LedgerResponse:
|
LedgerResponse:
|
||||||
discriminator:
|
discriminator:
|
||||||
propertyName: type
|
propertyName: type
|
||||||
@@ -732,17 +942,6 @@ components:
|
|||||||
required:
|
required:
|
||||||
- quantity
|
- quantity
|
||||||
- typeId
|
- typeId
|
||||||
CharacterResponse:
|
|
||||||
type: object
|
|
||||||
properties:
|
|
||||||
characterId:
|
|
||||||
type: integer
|
|
||||||
format: int64
|
|
||||||
name:
|
|
||||||
type: string
|
|
||||||
required:
|
|
||||||
- characterId
|
|
||||||
- name
|
|
||||||
AcquisitionResponse:
|
AcquisitionResponse:
|
||||||
type: object
|
type: object
|
||||||
properties:
|
properties:
|
||||||
|
|||||||
@@ -1,18 +1,15 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import {computed} from "vue";
|
import {computed} from "vue";
|
||||||
import {storeToRefs} from "pinia";
|
|
||||||
import {Modal} from "@/components";
|
import {Modal} from "@/components";
|
||||||
import {useConfirmStore} from "./useConfirm";
|
import {useConfirmStore} from "./useConfirm";
|
||||||
|
|
||||||
const confirmStore = useConfirmStore();
|
const confirmStore = useConfirmStore();
|
||||||
const {open, options} = storeToRefs(confirmStore);
|
|
||||||
const {accept, cancel} = confirmStore;
|
|
||||||
|
|
||||||
const modalOpen = computed({
|
const modalOpen = computed({
|
||||||
get: () => open.value,
|
get: () => confirmStore.open,
|
||||||
set: value => {
|
set: value => {
|
||||||
if (!value) {
|
if (!value) {
|
||||||
cancel();
|
confirmStore.cancel();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@@ -21,12 +18,12 @@ const modalOpen = computed({
|
|||||||
<template>
|
<template>
|
||||||
<Modal v-model:open="modalOpen">
|
<Modal v-model:open="modalOpen">
|
||||||
<div class="bg-slate-800 rounded pb-4 w-96">
|
<div class="bg-slate-800 rounded pb-4 w-96">
|
||||||
<span class="m-2">{{ options.title ?? "Confirm" }}</span>
|
<span class="m-2">{{ confirmStore.options.title ?? "Confirm" }}</span>
|
||||||
<hr />
|
<hr />
|
||||||
<div class="m-4">{{ options.message }}</div>
|
<div class="m-4">{{ confirmStore.options.message }}</div>
|
||||||
<div class="flex justify-end">
|
<div class="flex justify-end">
|
||||||
<button class="me-2" @click="cancel">{{ options.cancelLabel ?? "Cancel" }}</button>
|
<button class="me-2" @click="confirmStore.cancel">{{ confirmStore.options.cancelLabel ?? "Cancel" }}</button>
|
||||||
<button class="confirm me-4" :class="options.danger ? 'danger' : ''" @click="accept">{{ options.confirmLabel ?? "Confirm" }}</button>
|
<button class="confirm me-4" :class="confirmStore.options.danger ? 'danger' : ''" @click="confirmStore.accept">{{ confirmStore.options.confirmLabel ?? "Confirm" }}</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Modal>
|
</Modal>
|
||||||
|
|||||||
+279
-2
@@ -50,8 +50,8 @@ export interface CharacterResponse {
|
|||||||
'name': string;
|
'name': string;
|
||||||
}
|
}
|
||||||
export interface CharacterRuleBookResponse {
|
export interface CharacterRuleBookResponse {
|
||||||
'characterId': number;
|
'character': CharacterResponse;
|
||||||
'ruleBookId': string;
|
'ruleBook': RuleBookSummaryResponse;
|
||||||
'bindings': { [key: string]: string; };
|
'bindings': { [key: string]: string; };
|
||||||
}
|
}
|
||||||
export interface CombinedLedgerResponse extends LedgerResponse {
|
export interface CombinedLedgerResponse extends LedgerResponse {
|
||||||
@@ -98,6 +98,24 @@ export interface MainLedgerResponse extends LedgerResponse {
|
|||||||
'name': string;
|
'name': string;
|
||||||
'balance': number;
|
'balance': number;
|
||||||
}
|
}
|
||||||
|
export interface MarketHistoryResponse {
|
||||||
|
'marketTypeId': number;
|
||||||
|
'date': string;
|
||||||
|
'average': number;
|
||||||
|
'highest': number;
|
||||||
|
'lowest': number;
|
||||||
|
'orderCount': number;
|
||||||
|
'volume': number;
|
||||||
|
}
|
||||||
|
export interface MarketScanResponse {
|
||||||
|
'marketTypeId': number;
|
||||||
|
'q1': number;
|
||||||
|
'median': number;
|
||||||
|
'q3': number;
|
||||||
|
'totalVolume': number;
|
||||||
|
'profit': number;
|
||||||
|
'score': number;
|
||||||
|
}
|
||||||
export interface RuleBookResponse {
|
export interface RuleBookResponse {
|
||||||
'ruleBookId': string;
|
'ruleBookId': string;
|
||||||
'name': string;
|
'name': string;
|
||||||
@@ -105,6 +123,10 @@ export interface RuleBookResponse {
|
|||||||
'ledgerRefs': Array<string>;
|
'ledgerRefs': Array<string>;
|
||||||
'script': string;
|
'script': string;
|
||||||
}
|
}
|
||||||
|
export interface RuleBookSummaryResponse {
|
||||||
|
'ruleBookId': string;
|
||||||
|
'name': string;
|
||||||
|
}
|
||||||
export interface SetCharacterRuleBookRequest {
|
export interface SetCharacterRuleBookRequest {
|
||||||
'ruleBookId': string;
|
'ruleBookId': string;
|
||||||
'bindings': { [key: string]: string; };
|
'bindings': { [key: string]: string; };
|
||||||
@@ -488,6 +510,36 @@ export class CharacterApi extends BaseAPI {
|
|||||||
*/
|
*/
|
||||||
export const CharacterRuleBookApiAxiosParamCreator = function (configuration?: Configuration) {
|
export const CharacterRuleBookApiAxiosParamCreator = function (configuration?: Configuration) {
|
||||||
return {
|
return {
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @summary Find the rule books of all characters that have a token
|
||||||
|
* @param {*} [options] Override http request option.
|
||||||
|
* @throws {RequiredError}
|
||||||
|
*/
|
||||||
|
findAllCharacterRuleBooks: async (options: RawAxiosRequestConfig = {}): Promise<RequestArgs> => {
|
||||||
|
const localVarPath = `/characters/rule-books`;
|
||||||
|
// use dummy base URL string because the URL constructor only accepts absolute URLs.
|
||||||
|
const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);
|
||||||
|
let baseOptions;
|
||||||
|
if (configuration) {
|
||||||
|
baseOptions = configuration.baseOptions;
|
||||||
|
}
|
||||||
|
|
||||||
|
const localVarRequestOptions = { method: 'GET', ...baseOptions, ...options};
|
||||||
|
const localVarHeaderParameter = {} as any;
|
||||||
|
const localVarQueryParameter = {} as any;
|
||||||
|
|
||||||
|
localVarHeaderParameter['Accept'] = '*/*';
|
||||||
|
|
||||||
|
setSearchParams(localVarUrlObj, localVarQueryParameter);
|
||||||
|
let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
|
||||||
|
localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};
|
||||||
|
|
||||||
|
return {
|
||||||
|
url: toPathString(localVarUrlObj),
|
||||||
|
options: localVarRequestOptions,
|
||||||
|
};
|
||||||
|
},
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @summary Find the rule book assigned to a character
|
* @summary Find the rule book assigned to a character
|
||||||
@@ -570,6 +622,18 @@ export const CharacterRuleBookApiAxiosParamCreator = function (configuration?: C
|
|||||||
export const CharacterRuleBookApiFp = function(configuration?: Configuration) {
|
export const CharacterRuleBookApiFp = function(configuration?: Configuration) {
|
||||||
const localVarAxiosParamCreator = CharacterRuleBookApiAxiosParamCreator(configuration)
|
const localVarAxiosParamCreator = CharacterRuleBookApiAxiosParamCreator(configuration)
|
||||||
return {
|
return {
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @summary Find the rule books of all characters that have a token
|
||||||
|
* @param {*} [options] Override http request option.
|
||||||
|
* @throws {RequiredError}
|
||||||
|
*/
|
||||||
|
async findAllCharacterRuleBooks(options?: RawAxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<Array<CharacterRuleBookResponse>>> {
|
||||||
|
const localVarAxiosArgs = await localVarAxiosParamCreator.findAllCharacterRuleBooks(options);
|
||||||
|
const localVarOperationServerIndex = configuration?.serverIndex ?? 0;
|
||||||
|
const localVarOperationServerBasePath = operationServerMap['CharacterRuleBookApi.findAllCharacterRuleBooks']?.[localVarOperationServerIndex]?.url;
|
||||||
|
return (axios, basePath) => createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration)(axios, localVarOperationServerBasePath || basePath);
|
||||||
|
},
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @summary Find the rule book assigned to a character
|
* @summary Find the rule book assigned to a character
|
||||||
@@ -606,6 +670,15 @@ export const CharacterRuleBookApiFp = function(configuration?: Configuration) {
|
|||||||
export const CharacterRuleBookApiFactory = function (configuration?: Configuration, basePath?: string, axios?: AxiosInstance) {
|
export const CharacterRuleBookApiFactory = function (configuration?: Configuration, basePath?: string, axios?: AxiosInstance) {
|
||||||
const localVarFp = CharacterRuleBookApiFp(configuration)
|
const localVarFp = CharacterRuleBookApiFp(configuration)
|
||||||
return {
|
return {
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @summary Find the rule books of all characters that have a token
|
||||||
|
* @param {*} [options] Override http request option.
|
||||||
|
* @throws {RequiredError}
|
||||||
|
*/
|
||||||
|
findAllCharacterRuleBooks(options?: RawAxiosRequestConfig): AxiosPromise<Array<CharacterRuleBookResponse>> {
|
||||||
|
return localVarFp.findAllCharacterRuleBooks(options).then((request) => request(axios, basePath));
|
||||||
|
},
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @summary Find the rule book assigned to a character
|
* @summary Find the rule book assigned to a character
|
||||||
@@ -634,6 +707,16 @@ export const CharacterRuleBookApiFactory = function (configuration?: Configurati
|
|||||||
* CharacterRuleBookApi - object-oriented interface
|
* CharacterRuleBookApi - object-oriented interface
|
||||||
*/
|
*/
|
||||||
export class CharacterRuleBookApi extends BaseAPI {
|
export class CharacterRuleBookApi extends BaseAPI {
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @summary Find the rule books of all characters that have a token
|
||||||
|
* @param {*} [options] Override http request option.
|
||||||
|
* @throws {RequiredError}
|
||||||
|
*/
|
||||||
|
public findAllCharacterRuleBooks(options?: RawAxiosRequestConfig) {
|
||||||
|
return CharacterRuleBookApiFp(this.configuration).findAllCharacterRuleBooks(options).then((request) => request(this.axios, this.basePath));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @summary Find the rule book assigned to a character
|
* @summary Find the rule book assigned to a character
|
||||||
@@ -1180,6 +1263,200 @@ export class LedgerApi extends BaseAPI {
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* MarketApi - axios parameter creator
|
||||||
|
*/
|
||||||
|
export const MarketApiAxiosParamCreator = function (configuration?: Configuration) {
|
||||||
|
return {
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @summary Find the market history of a type, most recent first
|
||||||
|
* @param {number} marketTypeId Id of the market type
|
||||||
|
* @param {number} [days] Optional number of most recent days to return; omit for the full history
|
||||||
|
* @param {*} [options] Override http request option.
|
||||||
|
* @throws {RequiredError}
|
||||||
|
*/
|
||||||
|
findHistory: async (marketTypeId: number, days?: number, options: RawAxiosRequestConfig = {}): Promise<RequestArgs> => {
|
||||||
|
// verify required parameter 'marketTypeId' is not null or undefined
|
||||||
|
assertParamExists('findHistory', 'marketTypeId', marketTypeId)
|
||||||
|
const localVarPath = `/market/{marketTypeId}/history`
|
||||||
|
.replace('{marketTypeId}', encodeURIComponent(String(marketTypeId)));
|
||||||
|
// use dummy base URL string because the URL constructor only accepts absolute URLs.
|
||||||
|
const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);
|
||||||
|
let baseOptions;
|
||||||
|
if (configuration) {
|
||||||
|
baseOptions = configuration.baseOptions;
|
||||||
|
}
|
||||||
|
|
||||||
|
const localVarRequestOptions = { method: 'GET', ...baseOptions, ...options};
|
||||||
|
const localVarHeaderParameter = {} as any;
|
||||||
|
const localVarQueryParameter = {} as any;
|
||||||
|
|
||||||
|
if (days !== undefined) {
|
||||||
|
localVarQueryParameter['days'] = days;
|
||||||
|
}
|
||||||
|
|
||||||
|
localVarHeaderParameter['Accept'] = '*/*';
|
||||||
|
|
||||||
|
setSearchParams(localVarUrlObj, localVarQueryParameter);
|
||||||
|
let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
|
||||||
|
localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};
|
||||||
|
|
||||||
|
return {
|
||||||
|
url: toPathString(localVarUrlObj),
|
||||||
|
options: localVarRequestOptions,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @summary Scan every tracked market type, returning volume-weighted price quartiles for each
|
||||||
|
* @param {number} [days] Number of most recent days of history to analyse
|
||||||
|
* @param {number} [brokerFee] Broker fee as a fraction (e.g. 0.015 for 1.5%), paid on both buy and sell orders
|
||||||
|
* @param {number} [salesTax] Sales tax as a fraction (e.g. 0.036 for 3.6%), paid on sell orders
|
||||||
|
* @param {*} [options] Override http request option.
|
||||||
|
* @throws {RequiredError}
|
||||||
|
*/
|
||||||
|
scanMarket: async (days?: number, brokerFee?: number, salesTax?: number, options: RawAxiosRequestConfig = {}): Promise<RequestArgs> => {
|
||||||
|
const localVarPath = `/market/scan`;
|
||||||
|
// use dummy base URL string because the URL constructor only accepts absolute URLs.
|
||||||
|
const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);
|
||||||
|
let baseOptions;
|
||||||
|
if (configuration) {
|
||||||
|
baseOptions = configuration.baseOptions;
|
||||||
|
}
|
||||||
|
|
||||||
|
const localVarRequestOptions = { method: 'GET', ...baseOptions, ...options};
|
||||||
|
const localVarHeaderParameter = {} as any;
|
||||||
|
const localVarQueryParameter = {} as any;
|
||||||
|
|
||||||
|
if (days !== undefined) {
|
||||||
|
localVarQueryParameter['days'] = days;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (brokerFee !== undefined) {
|
||||||
|
localVarQueryParameter['brokerFee'] = brokerFee;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (salesTax !== undefined) {
|
||||||
|
localVarQueryParameter['salesTax'] = salesTax;
|
||||||
|
}
|
||||||
|
|
||||||
|
localVarHeaderParameter['Accept'] = '*/*';
|
||||||
|
|
||||||
|
setSearchParams(localVarUrlObj, localVarQueryParameter);
|
||||||
|
let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
|
||||||
|
localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};
|
||||||
|
|
||||||
|
return {
|
||||||
|
url: toPathString(localVarUrlObj),
|
||||||
|
options: localVarRequestOptions,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* MarketApi - functional programming interface
|
||||||
|
*/
|
||||||
|
export const MarketApiFp = function(configuration?: Configuration) {
|
||||||
|
const localVarAxiosParamCreator = MarketApiAxiosParamCreator(configuration)
|
||||||
|
return {
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @summary Find the market history of a type, most recent first
|
||||||
|
* @param {number} marketTypeId Id of the market type
|
||||||
|
* @param {number} [days] Optional number of most recent days to return; omit for the full history
|
||||||
|
* @param {*} [options] Override http request option.
|
||||||
|
* @throws {RequiredError}
|
||||||
|
*/
|
||||||
|
async findHistory(marketTypeId: number, days?: number, options?: RawAxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<Array<MarketHistoryResponse>>> {
|
||||||
|
const localVarAxiosArgs = await localVarAxiosParamCreator.findHistory(marketTypeId, days, options);
|
||||||
|
const localVarOperationServerIndex = configuration?.serverIndex ?? 0;
|
||||||
|
const localVarOperationServerBasePath = operationServerMap['MarketApi.findHistory']?.[localVarOperationServerIndex]?.url;
|
||||||
|
return (axios, basePath) => createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration)(axios, localVarOperationServerBasePath || basePath);
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @summary Scan every tracked market type, returning volume-weighted price quartiles for each
|
||||||
|
* @param {number} [days] Number of most recent days of history to analyse
|
||||||
|
* @param {number} [brokerFee] Broker fee as a fraction (e.g. 0.015 for 1.5%), paid on both buy and sell orders
|
||||||
|
* @param {number} [salesTax] Sales tax as a fraction (e.g. 0.036 for 3.6%), paid on sell orders
|
||||||
|
* @param {*} [options] Override http request option.
|
||||||
|
* @throws {RequiredError}
|
||||||
|
*/
|
||||||
|
async scanMarket(days?: number, brokerFee?: number, salesTax?: number, options?: RawAxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<Array<MarketScanResponse>>> {
|
||||||
|
const localVarAxiosArgs = await localVarAxiosParamCreator.scanMarket(days, brokerFee, salesTax, options);
|
||||||
|
const localVarOperationServerIndex = configuration?.serverIndex ?? 0;
|
||||||
|
const localVarOperationServerBasePath = operationServerMap['MarketApi.scanMarket']?.[localVarOperationServerIndex]?.url;
|
||||||
|
return (axios, basePath) => createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration)(axios, localVarOperationServerBasePath || basePath);
|
||||||
|
},
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* MarketApi - factory interface
|
||||||
|
*/
|
||||||
|
export const MarketApiFactory = function (configuration?: Configuration, basePath?: string, axios?: AxiosInstance) {
|
||||||
|
const localVarFp = MarketApiFp(configuration)
|
||||||
|
return {
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @summary Find the market history of a type, most recent first
|
||||||
|
* @param {number} marketTypeId Id of the market type
|
||||||
|
* @param {number} [days] Optional number of most recent days to return; omit for the full history
|
||||||
|
* @param {*} [options] Override http request option.
|
||||||
|
* @throws {RequiredError}
|
||||||
|
*/
|
||||||
|
findHistory(marketTypeId: number, days?: number, options?: RawAxiosRequestConfig): AxiosPromise<Array<MarketHistoryResponse>> {
|
||||||
|
return localVarFp.findHistory(marketTypeId, days, options).then((request) => request(axios, basePath));
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @summary Scan every tracked market type, returning volume-weighted price quartiles for each
|
||||||
|
* @param {number} [days] Number of most recent days of history to analyse
|
||||||
|
* @param {number} [brokerFee] Broker fee as a fraction (e.g. 0.015 for 1.5%), paid on both buy and sell orders
|
||||||
|
* @param {number} [salesTax] Sales tax as a fraction (e.g. 0.036 for 3.6%), paid on sell orders
|
||||||
|
* @param {*} [options] Override http request option.
|
||||||
|
* @throws {RequiredError}
|
||||||
|
*/
|
||||||
|
scanMarket(days?: number, brokerFee?: number, salesTax?: number, options?: RawAxiosRequestConfig): AxiosPromise<Array<MarketScanResponse>> {
|
||||||
|
return localVarFp.scanMarket(days, brokerFee, salesTax, options).then((request) => request(axios, basePath));
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* MarketApi - object-oriented interface
|
||||||
|
*/
|
||||||
|
export class MarketApi extends BaseAPI {
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @summary Find the market history of a type, most recent first
|
||||||
|
* @param {number} marketTypeId Id of the market type
|
||||||
|
* @param {number} [days] Optional number of most recent days to return; omit for the full history
|
||||||
|
* @param {*} [options] Override http request option.
|
||||||
|
* @throws {RequiredError}
|
||||||
|
*/
|
||||||
|
public findHistory(marketTypeId: number, days?: number, options?: RawAxiosRequestConfig) {
|
||||||
|
return MarketApiFp(this.configuration).findHistory(marketTypeId, days, options).then((request) => request(this.axios, this.basePath));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @summary Scan every tracked market type, returning volume-weighted price quartiles for each
|
||||||
|
* @param {number} [days] Number of most recent days of history to analyse
|
||||||
|
* @param {number} [brokerFee] Broker fee as a fraction (e.g. 0.015 for 1.5%), paid on both buy and sell orders
|
||||||
|
* @param {number} [salesTax] Sales tax as a fraction (e.g. 0.036 for 3.6%), paid on sell orders
|
||||||
|
* @param {*} [options] Override http request option.
|
||||||
|
* @throws {RequiredError}
|
||||||
|
*/
|
||||||
|
public scanMarket(days?: number, brokerFee?: number, salesTax?: number, options?: RawAxiosRequestConfig) {
|
||||||
|
return MarketApiFp(this.configuration).scanMarket(days, brokerFee, salesTax, options).then((request) => request(this.axios, this.basePath));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ProcessingApi - axios parameter creator
|
* ProcessingApi - axios parameter creator
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
|
||||||
import {computed, ref} from "vue";
|
import {computed, ref} from "vue";
|
||||||
import {storeToRefs} from "pinia";
|
|
||||||
import {isCombined, Ledger, LedgerType, LedgerTypes, useLedgersStore} from "./ledger";
|
import {isCombined, Ledger, LedgerType, LedgerTypes, useLedgersStore} from "./ledger";
|
||||||
import {Modal} from "@/components";
|
import {Modal} from "@/components";
|
||||||
import LedgerLabel from "./LedgerLabel.vue";
|
import LedgerLabel from "./LedgerLabel.vue";
|
||||||
@@ -15,8 +14,6 @@ interface Props {
|
|||||||
const props = defineProps<Props>();
|
const props = defineProps<Props>();
|
||||||
|
|
||||||
const ledgersStore = useLedgersStore();
|
const ledgersStore = useLedgersStore();
|
||||||
const {ledgers} = storeToRefs(ledgersStore);
|
|
||||||
const {findById, findAllById, createMain, createCombined, updateMain, updateCombined} = ledgersStore;
|
|
||||||
|
|
||||||
const modalOpen = ref<boolean>(false);
|
const modalOpen = ref<boolean>(false);
|
||||||
|
|
||||||
@@ -24,7 +21,7 @@ const type = ref<LedgerType>(LedgerTypes.Main);
|
|||||||
const name = ref("");
|
const name = ref("");
|
||||||
const members = ref<Ledger[]>([]);
|
const members = ref<Ledger[]>([]);
|
||||||
const selectedLedger = ref<Ledger>();
|
const selectedLedger = ref<Ledger>();
|
||||||
const availableLedgers = computed(() => ledgers.value
|
const availableLedgers = computed(() => ledgersStore.ledgers
|
||||||
.filter(l => l.ledgerId !== props.ledgerId)
|
.filter(l => l.ledgerId !== props.ledgerId)
|
||||||
.filter(l => !members.value.includes(l)));
|
.filter(l => !members.value.includes(l)));
|
||||||
|
|
||||||
@@ -37,12 +34,12 @@ const addMember = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const open = () => {
|
const open = () => {
|
||||||
const ledger = isCreating.value ? undefined : findById(props.ledgerId);
|
const ledger = isCreating.value ? undefined : ledgersStore.findById(props.ledgerId);
|
||||||
|
|
||||||
if (ledger) {
|
if (ledger) {
|
||||||
type.value = ledger.type;
|
type.value = ledger.type;
|
||||||
name.value = ledger.name;
|
name.value = ledger.name;
|
||||||
members.value = isCombined(ledger) ? findAllById(ledger.memberLedgerIds) : [];
|
members.value = isCombined(ledger) ? ledgersStore.findAllById(ledger.memberLedgerIds) : [];
|
||||||
} else {
|
} else {
|
||||||
type.value = LedgerTypes.Main;
|
type.value = LedgerTypes.Main;
|
||||||
name.value = "";
|
name.value = "";
|
||||||
@@ -62,17 +59,17 @@ const title = computed(() => {
|
|||||||
|
|
||||||
const create = () => {
|
const create = () => {
|
||||||
if (type.value === LedgerTypes.Main) {
|
if (type.value === LedgerTypes.Main) {
|
||||||
createMain({name: name.value})
|
ledgersStore.createMain({name: name.value})
|
||||||
} else {
|
} else {
|
||||||
createCombined({name: name.value, memberLedgerIds: members.value.map(l => l.ledgerId)})
|
ledgersStore.createCombined({name: name.value, memberLedgerIds: members.value.map(l => l.ledgerId)})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const update = () => {
|
const update = () => {
|
||||||
if (type.value === LedgerTypes.Main) {
|
if (type.value === LedgerTypes.Main) {
|
||||||
updateMain(props.ledgerId, {name: name.value})
|
ledgersStore.updateMain(props.ledgerId, {name: name.value})
|
||||||
} else {
|
} else {
|
||||||
updateCombined(props.ledgerId, {name: name.value, memberLedgerIds: members.value.map(l => l.ledgerId)})
|
ledgersStore.updateCombined(props.ledgerId, {name: name.value, memberLedgerIds: members.value.map(l => l.ledgerId)})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import {Ledger, systemLedger, useLedgersStore} from "@/ledger/ledger.ts";
|
import {Ledger, systemLedger, useLedgersStore} from "@/ledger/ledger.ts";
|
||||||
import {storeToRefs} from "pinia";
|
|
||||||
import {computed} from "vue";
|
import {computed} from "vue";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
@@ -9,9 +8,9 @@ interface Props {
|
|||||||
|
|
||||||
const props = defineProps<Props>()
|
const props = defineProps<Props>()
|
||||||
const ledger = defineModel<Ledger>();
|
const ledger = defineModel<Ledger>();
|
||||||
const {ledgers: allLedgers} = storeToRefs(useLedgersStore());
|
const ledgersStore = useLedgersStore();
|
||||||
|
|
||||||
const ledgersToUse = computed(() => props.ledgers || allLedgers);
|
const ledgersToUse = computed(() => props.ledgers || ledgersStore.ledgers);
|
||||||
const ledgerId = computed({
|
const ledgerId = computed({
|
||||||
get: () => ledger.value?.ledgerId,
|
get: () => ledger.value?.ledgerId,
|
||||||
set: value => ledger.value = ledgersToUse.value.find(l => l.ledgerId === value)
|
set: value => ledger.value = ledgersToUse.value.find(l => l.ledgerId === value)
|
||||||
|
|||||||
@@ -80,9 +80,9 @@ export const findAllTransactionInLeger = (ledger: Ledger | string): Promise<Tran
|
|||||||
export const getLedgerBalance = (ledger: Ledger | string): Promise<BalanceResponse> => ledgerApi.findBalanceByLedgerId(getLedgerId(ledger)).then(response => response.data)
|
export const getLedgerBalance = (ledger: Ledger | string): Promise<BalanceResponse> => ledgerApi.findBalanceByLedgerId(getLedgerId(ledger)).then(response => response.data)
|
||||||
|
|
||||||
export const useLedgerParam = () => {
|
export const useLedgerParam = () => {
|
||||||
const {findById} = useLedgersStore();
|
const ledgersStore = useLedgersStore();
|
||||||
const ledgerId = useRouteParams<string, string>('ledgerId', '', { transform: v => typeof v === 'string' ? v : v[0]});
|
const ledgerId = useRouteParams<string, string>('ledgerId', '', { transform: v => typeof v === 'string' ? v : v[0]});
|
||||||
const ledger = computed(() => findById(ledgerId.value))
|
const ledger = computed(() => ledgersStore.findById(ledgerId.value))
|
||||||
|
|
||||||
return {ledgerId, ledger};
|
return {ledgerId, ledger};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import {
|
|||||||
CharacterApi,
|
CharacterApi,
|
||||||
CharacterRuleBookApi,
|
CharacterRuleBookApi,
|
||||||
LedgerApi,
|
LedgerApi,
|
||||||
|
MarketApi,
|
||||||
ProcessingApi,
|
ProcessingApi,
|
||||||
RuleBookApi,
|
RuleBookApi,
|
||||||
TransactionApi
|
TransactionApi
|
||||||
@@ -31,3 +32,4 @@ export const characterRuleBookApi = new CharacterRuleBookApi(undefined, mammonUr
|
|||||||
export const activityApi = new ActivityApi(undefined, mammonUrl, mammonAxiosInstance);
|
export const activityApi = new ActivityApi(undefined, mammonUrl, mammonAxiosInstance);
|
||||||
export const processingApi = new ProcessingApi(undefined, mammonUrl, mammonAxiosInstance);
|
export const processingApi = new ProcessingApi(undefined, mammonUrl, mammonAxiosInstance);
|
||||||
export const acquisitionApi = new AcquisitionApi(undefined, mammonUrl, mammonAxiosInstance);
|
export const acquisitionApi = new AcquisitionApi(undefined, mammonUrl, mammonAxiosInstance);
|
||||||
|
export const marketApi = new MarketApi(undefined, mammonUrl, mammonAxiosInstance);
|
||||||
|
|||||||
@@ -1,30 +0,0 @@
|
|||||||
import { esiAxiosInstance } from "@/service";
|
|
||||||
import { RegionalMarketCache } from '../RegionalMarketCache';
|
|
||||||
import { jitaId } from "../market";
|
|
||||||
|
|
||||||
|
|
||||||
export type EsiMarketOrderHistory = {
|
|
||||||
average: number;
|
|
||||||
date: string;
|
|
||||||
highest: number;
|
|
||||||
lowest: number;
|
|
||||||
order_count: number;
|
|
||||||
volume: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO use pinia store
|
|
||||||
const historyCache: RegionalMarketCache<EsiMarketOrderHistory[]> = new RegionalMarketCache(() => {
|
|
||||||
const date = new Date();
|
|
||||||
|
|
||||||
if (date.getUTCHours() >= 11) {
|
|
||||||
date.setUTCDate(date.getUTCDate() + 1);
|
|
||||||
}
|
|
||||||
date.setUTCHours(11, 0, 0, 0);
|
|
||||||
return date;
|
|
||||||
});
|
|
||||||
|
|
||||||
export const getHistory = async (typeId: number, regionId?: number): Promise<EsiMarketOrderHistory[]> => {
|
|
||||||
const rId = regionId ?? jitaId;
|
|
||||||
|
|
||||||
return historyCache.computeIfAbsent(rId, typeId, async () => (await esiAxiosInstance.get(`/markets/${rId}/history/`, { params: { type_id: typeId } })).data);
|
|
||||||
}
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import { EsiMarketOrderHistory } from "@/market";
|
import { MarketHistory } from "@/market";
|
||||||
|
|
||||||
export type HistoryQuartils = {
|
export type HistoryQuartils = {
|
||||||
totalVolume: number,
|
totalVolume: number,
|
||||||
@@ -7,7 +7,7 @@ export type HistoryQuartils = {
|
|||||||
q3: number,
|
q3: number,
|
||||||
}
|
}
|
||||||
|
|
||||||
export const getHistoryQuartils = (history: EsiMarketOrderHistory[], days?: number): HistoryQuartils => {
|
export const getHistoryQuartils = (history: MarketHistory[], days?: number): HistoryQuartils => {
|
||||||
const now = Date.now();
|
const now = Date.now();
|
||||||
|
|
||||||
const volumes = history
|
const volumes = history
|
||||||
@@ -51,7 +51,7 @@ export const getHistoryQuartils = (history: EsiMarketOrderHistory[], days?: numb
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const estimateVolume = (history: EsiMarketOrderHistory): number => {
|
const estimateVolume = (history: MarketHistory): number => {
|
||||||
if (history.volume === 0) {
|
if (history.volume === 0) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,7 @@
|
|||||||
|
import { MarketHistoryResponse } from "@/generated/mammon";
|
||||||
|
import { marketApi } from "@/mammon";
|
||||||
|
|
||||||
|
export type MarketHistory = MarketHistoryResponse;
|
||||||
|
|
||||||
|
export const getHistory = async (typeId: number): Promise<MarketHistory[]> =>
|
||||||
|
(await marketApi.findHistory(typeId)).data;
|
||||||
@@ -1,2 +1,2 @@
|
|||||||
export * from './EsiMarketOrderHistory';
|
export * from './MarketHistory';
|
||||||
export * from './HistoryQuartils';
|
export * from './HistoryQuartils';
|
||||||
|
|||||||
@@ -1,12 +1,13 @@
|
|||||||
<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 {getHistoryQuartils, MarketType, MarketTypeLabel, TaxInput, useMarketTaxStore} from "@/market";
|
import {formatIsk, percentFormater} from "@/formaters";
|
||||||
import {BookmarkSlashIcon, ShoppingCartIcon} from '@heroicons/vue/24/outline';
|
import {MarketType, MarketTypeLabel} from "@/market";
|
||||||
|
import {ShoppingCartIcon} from '@heroicons/vue/24/outline';
|
||||||
import {useStorage} from '@vueuse/core';
|
import {useStorage} from '@vueuse/core';
|
||||||
import {computed, ref} from 'vue';
|
import {computed, ref} from 'vue';
|
||||||
import {useAcquiredTypesStore} from '../acquisition';
|
import {useAcquiredTypesStore} from '../acquisition';
|
||||||
import {TrackingResult} from './tracking';
|
import {ScanResult} from './scan';
|
||||||
|
|
||||||
type Result = {
|
type Result = {
|
||||||
type: MarketType;
|
type: MarketType;
|
||||||
@@ -17,25 +18,28 @@ type Result = {
|
|||||||
q1: number;
|
q1: number;
|
||||||
median: number;
|
median: number;
|
||||||
q3: number;
|
q3: number;
|
||||||
|
totalVolume: number;
|
||||||
acquisitions: number;
|
acquisitions: number;
|
||||||
profit: number;
|
profit: number;
|
||||||
score: number;
|
score: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
items?: TrackingResult[];
|
items?: ScanResult[];
|
||||||
infoOnly?: boolean;
|
infoOnly?: boolean;
|
||||||
ignoredColums?: string[] | string;
|
ignoredColums?: string[] | string;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Emits {
|
interface Emits {
|
||||||
(e: 'buy', type: MarketType, buy: number, sell: number): void;
|
(e: 'buy', type: MarketType, buy: number, sell: number): void;
|
||||||
(e: 'remove', type: MarketType): void;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const scoreFormater = new Intl.NumberFormat("en-US", {
|
const scoreFormater = new Intl.NumberFormat("en-US", {
|
||||||
maximumFractionDigits: 0
|
maximumFractionDigits: 0
|
||||||
});
|
});
|
||||||
|
const volumeFormater = new Intl.NumberFormat("en-US", {
|
||||||
|
maximumFractionDigits: 0
|
||||||
|
});
|
||||||
|
|
||||||
const props = withDefaults(defineProps<Props>(), {
|
const props = withDefaults(defineProps<Props>(), {
|
||||||
items: () => [],
|
items: () => [],
|
||||||
@@ -44,11 +48,9 @@ const props = withDefaults(defineProps<Props>(), {
|
|||||||
});
|
});
|
||||||
defineEmits<Emits>();
|
defineEmits<Emits>();
|
||||||
|
|
||||||
const marketTaxStore = useMarketTaxStore();
|
|
||||||
const acquiredTypesStore = useAcquiredTypesStore();
|
const acquiredTypesStore = useAcquiredTypesStore();
|
||||||
|
|
||||||
const days = useStorage('market-tracking-days', 365);
|
const threshold = useStorage('market-scan-threshold', 10);
|
||||||
const threshold = useStorage('market-tracking-threshold', 10);
|
|
||||||
const filter = ref("");
|
const filter = ref("");
|
||||||
const onlyCheap = ref(false);
|
const onlyCheap = ref(false);
|
||||||
const columnsToIgnore = computed(() => {
|
const columnsToIgnore = computed(() => {
|
||||||
@@ -62,9 +64,6 @@ const columnsToIgnore = computed(() => {
|
|||||||
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()))
|
||||||
.map(r => {
|
.map(r => {
|
||||||
const quartils = getHistoryQuartils(r.history, days.value);
|
|
||||||
const profit = quartils.q1 === 0 || quartils.q3 === 0 ? 0 : marketTaxStore.calculateProfit(quartils.q1, quartils.q3);
|
|
||||||
const score = profit <= 0 ? 0 : Math.sqrt((Math.pow(quartils.totalVolume, 1.1) * Math.pow(quartils.q1, 1.2) * Math.pow(profit, 0.5) * Math.pow(Math.max(1, r.orderCount), -0.7)) / days.value);
|
|
||||||
const acquisitions = columnsToIgnore.value.includes('acquisitions') ? 0 : acquiredTypesStore.acquiredTypes
|
const acquisitions = columnsToIgnore.value.includes('acquisitions') ? 0 : acquiredTypesStore.acquiredTypes
|
||||||
.filter(t => t.type === r.type.id)
|
.filter(t => t.type === r.type.id)
|
||||||
.reduce((a, b) => a + b.remaining, 0);
|
.reduce((a, b) => a + b.remaining, 0);
|
||||||
@@ -75,12 +74,13 @@ const { sortedArray, headerProps, showColumn } = useSort<Result>(computed(() =>
|
|||||||
name: r.type.name,
|
name: r.type.name,
|
||||||
buy: r.buy,
|
buy: r.buy,
|
||||||
sell: r.sell,
|
sell: r.sell,
|
||||||
q1: quartils.q1,
|
q1: r.q1,
|
||||||
median: quartils.median,
|
median: r.median,
|
||||||
q3: quartils.q3,
|
q3: r.q3,
|
||||||
|
totalVolume: r.totalVolume,
|
||||||
acquisitions,
|
acquisitions,
|
||||||
profit,
|
profit: r.profit,
|
||||||
score
|
score: r.score
|
||||||
};
|
};
|
||||||
}).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',
|
||||||
@@ -104,15 +104,10 @@ const getLineColor = (result: Result) => {
|
|||||||
<template>
|
<template>
|
||||||
<div v-if="!infoOnly" class="flex mb-2 mt-4">
|
<div v-if="!infoOnly" class="flex mb-2 mt-4">
|
||||||
<div class="flex justify-self-end ms-auto">
|
<div class="flex justify-self-end ms-auto">
|
||||||
<TaxInput />
|
|
||||||
<div class="end">
|
<div class="end">
|
||||||
<span>Profit Threshold: </span>
|
<span>Profit Threshold: </span>
|
||||||
<input type="number" min="0" max="1000" step="1" v-model="threshold" />
|
<input type="number" min="0" max="1000" step="1" v-model="threshold" />
|
||||||
</div>
|
</div>
|
||||||
<div class="end">
|
|
||||||
<span>Days: </span>
|
|
||||||
<input type="number" min="1" max="365" step="1" v-model="days" />
|
|
||||||
</div>
|
|
||||||
<div class="end flex">
|
<div class="end flex">
|
||||||
<SliderCheckbox class="me-1" v-model="onlyCheap" /> Show only cheap items
|
<SliderCheckbox class="me-1" v-model="onlyCheap" /> Show only cheap items
|
||||||
</div>
|
</div>
|
||||||
@@ -132,6 +127,7 @@ const getLineColor = (result: Result) => {
|
|||||||
<SortableHeader v-bind="headerProps" sortKey="q1">Q1</SortableHeader>
|
<SortableHeader v-bind="headerProps" sortKey="q1">Q1</SortableHeader>
|
||||||
<SortableHeader v-bind="headerProps" sortKey="median">Median</SortableHeader>
|
<SortableHeader v-bind="headerProps" sortKey="median">Median</SortableHeader>
|
||||||
<SortableHeader v-bind="headerProps" sortKey="q3">Q3</SortableHeader>
|
<SortableHeader v-bind="headerProps" sortKey="q3">Q3</SortableHeader>
|
||||||
|
<SortableHeader v-bind="headerProps" sortKey="totalVolume">Volume</SortableHeader>
|
||||||
<SortableHeader v-bind="headerProps" sortKey="profit">Profit</SortableHeader>
|
<SortableHeader v-bind="headerProps" sortKey="profit">Profit</SortableHeader>
|
||||||
<SortableHeader v-bind="headerProps" sortKey="score">Score</SortableHeader>
|
<SortableHeader v-bind="headerProps" sortKey="score">Score</SortableHeader>
|
||||||
<SortableHeader v-bind="headerProps" sortKey="acquisitions">Acquisitions</SortableHeader>
|
<SortableHeader v-bind="headerProps" sortKey="acquisitions">Acquisitions</SortableHeader>
|
||||||
@@ -148,12 +144,12 @@ const getLineColor = (result: Result) => {
|
|||||||
<td v-if="showColumn('q1')" class="text-right">{{ formatIsk(r.data.q1) }}</td>
|
<td v-if="showColumn('q1')" class="text-right">{{ formatIsk(r.data.q1) }}</td>
|
||||||
<td v-if="showColumn('median')" class="text-right">{{ formatIsk(r.data.median) }}</td>
|
<td v-if="showColumn('median')" class="text-right">{{ formatIsk(r.data.median) }}</td>
|
||||||
<td v-if="showColumn('q3')" class="text-right">{{ formatIsk(r.data.q3) }}</td>
|
<td v-if="showColumn('q3')" class="text-right">{{ formatIsk(r.data.q3) }}</td>
|
||||||
|
<td v-if="showColumn('totalVolume')" class="text-right">{{ volumeFormater.format(r.data.totalVolume) }}</td>
|
||||||
<td v-if="showColumn('profit')" class="text-right">{{ percentFormater.format(r.data.profit) }}</td>
|
<td v-if="showColumn('profit')" class="text-right">{{ percentFormater.format(r.data.profit) }}</td>
|
||||||
<td v-if="showColumn('score')" class="text-right">{{ scoreFormater.format(r.data.score) }}</td>
|
<td v-if="showColumn('score')" class="text-right">{{ scoreFormater.format(r.data.score) }}</td>
|
||||||
<td v-if="showColumn('acquisitions')" class="text-right">{{ r.data.acquisitions }}</td>
|
<td v-if="showColumn('acquisitions')" class="text-right">{{ r.data.acquisitions }}</td>
|
||||||
<td v-if="showColumn('buttons')" class="text-right">
|
<td v-if="showColumn('buttons')" class="text-right">
|
||||||
<button class="btn-icon me-1" title="Add acquisitions" @click="$emit('buy', r.data.type, r.data.buy, r.data.sell)"><ShoppingCartIcon /></button>
|
<button class="btn-icon me-1" title="Add acquisitions" @click="$emit('buy', r.data.type, r.data.buy, r.data.sell)"><ShoppingCartIcon /></button>
|
||||||
<button class="btn-icon me-1" title="Untrack" @click="$emit('remove', r.data.type)"><BookmarkSlashIcon /></button>
|
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
@@ -171,4 +167,4 @@ const getLineColor = (result: Result) => {
|
|||||||
div.end {
|
div.end {
|
||||||
@apply justify-self-end ms-2;
|
@apply justify-self-end ms-2;
|
||||||
}
|
}
|
||||||
</style>../history/HistoryQuartils
|
</style>
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
export * from './scan';
|
||||||
|
|
||||||
|
export { default as ScanResultTable } from './ScanResultTable.vue';
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
import { getHistory, getHistoryQuartils, HistoryQuartils, MarketType, MarketTypePrice } from "@/market";
|
||||||
|
import { MarketScanResponse } from "@/generated/mammon";
|
||||||
|
|
||||||
|
export type ScanResult = {
|
||||||
|
type: MarketType;
|
||||||
|
buy: number;
|
||||||
|
sell: number;
|
||||||
|
q1: number;
|
||||||
|
median: number;
|
||||||
|
q3: number;
|
||||||
|
totalVolume: number;
|
||||||
|
profit: number;
|
||||||
|
score: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mirrors mammon's MarketScoreCalculator so the client-side path matches the backend scan.
|
||||||
|
export const calculateScore = (quartils: HistoryQuartils, profit: number, orderCount: number, days: number): number => {
|
||||||
|
if (profit <= 0) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return Math.sqrt((Math.pow(quartils.totalVolume, 1.1) * Math.pow(quartils.q1, 1.2) * Math.pow(profit, 0.5) * Math.pow(Math.max(1, orderCount), -0.7)) / days);
|
||||||
|
}
|
||||||
|
|
||||||
|
export const toScanResult = (res: MarketScanResponse, type: MarketType, price: MarketTypePrice): ScanResult => ({
|
||||||
|
type,
|
||||||
|
buy: price.buy,
|
||||||
|
sell: price.sell,
|
||||||
|
q1: res.q1,
|
||||||
|
median: res.median,
|
||||||
|
q3: res.q3,
|
||||||
|
totalVolume: res.totalVolume,
|
||||||
|
profit: res.profit,
|
||||||
|
score: res.score,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Client-side scan result for a single type (used where the scan endpoint can't be queried per-type).
|
||||||
|
export const buildScanResult = async (price: MarketTypePrice, days: number, calculateProfit: (buy: number, sell: number) => number): Promise<ScanResult> => {
|
||||||
|
const history = await getHistory(price.type.id);
|
||||||
|
const quartils = getHistoryQuartils(history, days);
|
||||||
|
const profit = quartils.q1 === 0 || quartils.q3 === 0 ? 0 : calculateProfit(quartils.q1, quartils.q3);
|
||||||
|
|
||||||
|
return {
|
||||||
|
type: price.type,
|
||||||
|
buy: price.buy,
|
||||||
|
sell: price.sell,
|
||||||
|
q1: quartils.q1,
|
||||||
|
median: quartils.median,
|
||||||
|
q3: quartils.q3,
|
||||||
|
totalVolume: quartils.totalVolume,
|
||||||
|
profit,
|
||||||
|
score: calculateScore(quartils, profit, price.orderCount, days),
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -1,19 +1,18 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import {storeToRefs} from "pinia";
|
|
||||||
import {useMarketTaxStore} from "./tax";
|
import {useMarketTaxStore} from "./tax";
|
||||||
|
|
||||||
const { brokerFee, scc } = storeToRefs(useMarketTaxStore());
|
const marketTaxStore = useMarketTaxStore();
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="end">
|
<div class="end">
|
||||||
<span>Broker Fee: </span>
|
<span>Broker Fee: </span>
|
||||||
<input type="number" min="1" max="3" step="0.01" v-model="brokerFee" />
|
<input type="number" min="1" max="3" step="0.01" v-model="marketTaxStore.brokerFee" />
|
||||||
</div>
|
</div>
|
||||||
<div class="end">
|
<div class="end">
|
||||||
<span>SCC: </span>
|
<span>SCC: </span>
|
||||||
<input type="number" min="3.6" max="8" step="0.01" v-model="scc" >
|
<input type="number" min="3.6" max="8" step="0.01" v-model="marketTaxStore.scc" >
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +0,0 @@
|
|||||||
export * from './tracking';
|
|
||||||
|
|
||||||
export { default as TrackingResultTable } from './TrackingResultTable.vue';
|
|
||||||
|
|
||||||
@@ -1,40 +0,0 @@
|
|||||||
import { EsiMarketOrderHistory, getHistory, MarketType, MarketTypePrice } from "@/market";
|
|
||||||
import log from "loglevel";
|
|
||||||
import { defineStore } from "pinia";
|
|
||||||
import { computed, ref } from "vue";
|
|
||||||
|
|
||||||
export type TrackingResult = {
|
|
||||||
type: MarketType;
|
|
||||||
history: EsiMarketOrderHistory[];
|
|
||||||
buy: number,
|
|
||||||
sell: number,
|
|
||||||
orderCount: number,
|
|
||||||
}
|
|
||||||
|
|
||||||
const endpoint = '/api/types_tracking/';
|
|
||||||
|
|
||||||
export const useMarketTrackingStore = defineStore('marketTracking', () => {
|
|
||||||
const trackedTypes = ref<any[]>([]); // TODO
|
|
||||||
|
|
||||||
const types = computed(() => trackedTypes.value.map(item => item.type) ?? []);
|
|
||||||
const addType = async (type: number) => {
|
|
||||||
const found = trackedTypes.value.find(item => item.type === type);
|
|
||||||
|
|
||||||
if (!found) {
|
|
||||||
log.info(`Tracking type ${type}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const removeType = async (type: number) => {
|
|
||||||
const found = trackedTypes.value.find(item => item.type === type);
|
|
||||||
|
|
||||||
if (!found) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
trackedTypes.value = trackedTypes.value.filter(t => t.id !== found.id);
|
|
||||||
}
|
|
||||||
|
|
||||||
return { types, addType, removeType };
|
|
||||||
});
|
|
||||||
|
|
||||||
export const createResult = async (id: number, price: MarketTypePrice): Promise<TrackingResult> => ({ history: await getHistory(id), ...price });
|
|
||||||
@@ -1,13 +1,10 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
|
||||||
import {mammonAddCharacterUrl} from "@/mammon";
|
import {mammonAddCharacterUrl} from "@/mammon";
|
||||||
import {storeToRefs} from "pinia";
|
|
||||||
import {CharacterLabel, useCharactersStore} from "@/characters";
|
import {CharacterLabel, useCharactersStore} from "@/characters";
|
||||||
import {ArrowPathIcon} from '@heroicons/vue/24/outline';
|
import {ArrowPathIcon} from '@heroicons/vue/24/outline';
|
||||||
|
|
||||||
const charactersStore = useCharactersStore()
|
const charactersStore = useCharactersStore()
|
||||||
const {characters} = storeToRefs(charactersStore);
|
|
||||||
const {reloadActivities} = charactersStore;
|
|
||||||
|
|
||||||
const addCharacter = () => {
|
const addCharacter = () => {
|
||||||
window.location.replace(mammonAddCharacterUrl);
|
window.location.replace(mammonAddCharacterUrl);
|
||||||
@@ -19,9 +16,9 @@ const addCharacter = () => {
|
|||||||
<div class="mb-4 border-b-1 flex justify-end">
|
<div class="mb-4 border-b-1 flex justify-end">
|
||||||
<button class="mb-2" @click="addCharacter">Add chacarcter</button>
|
<button class="mb-2" @click="addCharacter">Add chacarcter</button>
|
||||||
</div>
|
</div>
|
||||||
<div v-for="character in characters" :key="character.characterId" class="flex items-center mb-2">
|
<div v-for="character in charactersStore.characters" :key="character.characterId" class="flex items-center mb-2">
|
||||||
<CharacterLabel class="grow" :character="character" />
|
<CharacterLabel class="grow" :character="character" />
|
||||||
<button class="btn-icon" @click="reloadActivities(character.characterId)"><ArrowPathIcon /></button>
|
<button class="btn-icon" @click="charactersStore.reloadActivities(character.characterId)"><ArrowPathIcon /></button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@@ -4,14 +4,14 @@ import {EditLedgerModal, useLedgersStore} from "@/ledger";
|
|||||||
import {ref} from "vue";
|
import {ref} from "vue";
|
||||||
import {activityApi, processingApi} from "@/mammon";
|
import {activityApi, processingApi} from "@/mammon";
|
||||||
|
|
||||||
const {refresh} = useLedgersStore();
|
const ledgersStore = useLedgersStore();
|
||||||
|
|
||||||
const editLedgerModal = ref<typeof EditLedgerModal>();
|
const editLedgerModal = ref<typeof EditLedgerModal>();
|
||||||
|
|
||||||
const processActivities = async () => {
|
const processActivities = async () => {
|
||||||
await activityApi.fetchAllNewActivities();
|
await activityApi.fetchAllNewActivities();
|
||||||
await processingApi.processNewActivities();
|
await processingApi.processNewActivities();
|
||||||
await refresh();
|
await ledgersStore.refresh();
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -9,8 +9,8 @@ import {routeNames} from '@/routes';
|
|||||||
<RouterLink :to="{name: routeNames.marketTypes}" class="tab">
|
<RouterLink :to="{name: routeNames.marketTypes}" class="tab">
|
||||||
<span>Item Info</span>
|
<span>Item Info</span>
|
||||||
</RouterLink>
|
</RouterLink>
|
||||||
<RouterLink to="/market/tracking" class="tab">
|
<RouterLink to="/market/scan" class="tab">
|
||||||
<span>Tracking</span>
|
<span>Scan</span>
|
||||||
</RouterLink>
|
</RouterLink>
|
||||||
<RouterLink to="/market/acquisitions" class="tab">
|
<RouterLink to="/market/acquisitions" class="tab">
|
||||||
<span>Acquisitions</span>
|
<span>Acquisitions</span>
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import {CharacterLabel, useCharactersStore} from "@/characters";
|
|||||||
import {formatEveDate} from "@/formaters.ts";
|
import {formatEveDate} from "@/formaters.ts";
|
||||||
|
|
||||||
const {ledgerId} = useLedgerParam();
|
const {ledgerId} = useLedgerParam();
|
||||||
const {findById} = useCharactersStore();
|
const charactersStore = useCharactersStore();
|
||||||
|
|
||||||
const transactions = computedAsync<TransactionResponse[]>(async () => {
|
const transactions = computedAsync<TransactionResponse[]>(async () => {
|
||||||
if (ledgerId.value) {
|
if (ledgerId.value) {
|
||||||
@@ -21,7 +21,7 @@ const transactions = computedAsync<TransactionResponse[]>(async () => {
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
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 character = await findById(transaction.characterId);
|
const character = await charactersStore.findById(transaction.characterId);
|
||||||
return {
|
return {
|
||||||
character,
|
character,
|
||||||
characterName: character?.name ?? "",
|
characterName: character?.name ?? "",
|
||||||
|
|||||||
@@ -1,15 +1,14 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
|
||||||
import {EditLedgerModal, Ledger, LedgerLabel, useLedgersStore} from "@/ledger";
|
import {EditLedgerModal, Ledger, LedgerLabel, useLedgersStore} from "@/ledger";
|
||||||
import {storeToRefs} from "pinia";
|
|
||||||
import {nextTick, ref} from "vue";
|
import {nextTick, ref} from "vue";
|
||||||
import {PencilSquareIcon} from "@heroicons/vue/24/outline";
|
import {PencilSquareIcon} from "@heroicons/vue/24/outline";
|
||||||
import {IskLabel} from "@/market";
|
import {IskLabel} from "@/market";
|
||||||
import {SortableHeader, useSort, VirtualScrollTable} from "@/components/table";
|
import {SortableHeader, useSort, VirtualScrollTable} from "@/components/table";
|
||||||
|
|
||||||
const {ledgers} = storeToRefs(useLedgersStore());
|
const ledgersStore = useLedgersStore();
|
||||||
|
|
||||||
const { sortedArray, headerProps } = useSort<Ledger>(ledgers);
|
const { sortedArray, headerProps } = useSort<Ledger>(() => ledgersStore.ledgers);
|
||||||
|
|
||||||
const editModal = ref<typeof EditLedgerModal>();
|
const editModal = ref<typeof EditLedgerModal>();
|
||||||
const editingLedgerId = ref("");
|
const editingLedgerId = ref("");
|
||||||
|
|||||||
@@ -0,0 +1,67 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import {getMarketTypes, TaxInput, useApraisalStore, useMarketTaxStore} from "@/market";
|
||||||
|
import {BuyModal} from '@/market/acquisition';
|
||||||
|
import {ScanResult, ScanResultTable, toScanResult} from '@/market/scan';
|
||||||
|
import {marketApi} from "@/mammon";
|
||||||
|
import {useStorage} from "@vueuse/core";
|
||||||
|
import {ref, watch} from 'vue';
|
||||||
|
|
||||||
|
const buyModal = ref<typeof BuyModal>();
|
||||||
|
|
||||||
|
const apraisalStore = useApraisalStore();
|
||||||
|
const marketTaxStore = useMarketTaxStore();
|
||||||
|
const days = useStorage('market-scan-days', 365);
|
||||||
|
const items = ref<ScanResult[]>([]);
|
||||||
|
const loading = ref(false);
|
||||||
|
|
||||||
|
const scan = async () => {
|
||||||
|
loading.value = true;
|
||||||
|
try {
|
||||||
|
const { data } = await marketApi.scanMarket(
|
||||||
|
days.value,
|
||||||
|
marketTaxStore.brokerFee / 100,
|
||||||
|
marketTaxStore.scc / 100
|
||||||
|
);
|
||||||
|
const types = await getMarketTypes(data.map(r => r.marketTypeId));
|
||||||
|
const prices = await apraisalStore.getPrices(types);
|
||||||
|
|
||||||
|
items.value = data.flatMap(r => {
|
||||||
|
const type = types.find(t => t.id === r.marketTypeId);
|
||||||
|
const price = prices.find(p => p.type.id === r.marketTypeId);
|
||||||
|
|
||||||
|
return type && price ? [toScanResult(r, type, price)] : [];
|
||||||
|
});
|
||||||
|
} finally {
|
||||||
|
loading.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
watch([days, () => marketTaxStore.brokerFee, () => marketTaxStore.scc], scan, { immediate: true });
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="flex mb-2 mt-4">
|
||||||
|
<div class="flex justify-self-end ms-auto">
|
||||||
|
<TaxInput />
|
||||||
|
<div class="end">
|
||||||
|
<span>Days: </span>
|
||||||
|
<input type="number" min="1" max="365" step="1" v-model="days" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<hr />
|
||||||
|
<div v-if="loading" class="text-center mt-4">
|
||||||
|
<span>Scanning market…</span>
|
||||||
|
</div>
|
||||||
|
<template v-else>
|
||||||
|
<ScanResultTable :items="items" @buy="(type, buy, sell) => buyModal?.open(type, { 'Buy': buy, 'Sell': sell })" />
|
||||||
|
<BuyModal ref="buyModal" />
|
||||||
|
</template>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
@reference "@/style.css";
|
||||||
|
div.end {
|
||||||
|
@apply justify-self-end ms-2;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -1,82 +0,0 @@
|
|||||||
<script setup lang="ts">
|
|
||||||
import { Modal, ProgressBar } from "@/components";
|
|
||||||
import { MarketType, MarketTypeInput, MarketTypePrice, getHistory, getMarketTypes, useApraisalStore } from "@/market";
|
|
||||||
import { BuyModal } from '@/market/acquisition';
|
|
||||||
import { TrackingResult, TrackingResultTable, createResult, useMarketTrackingStore } from '@/market/tracking';
|
|
||||||
import { ref, watch } from 'vue';
|
|
||||||
|
|
||||||
|
|
||||||
const buyModal = ref<typeof BuyModal>();
|
|
||||||
|
|
||||||
const item = ref<MarketType>();
|
|
||||||
|
|
||||||
const apraisalStore = useApraisalStore();
|
|
||||||
const marketTrackingStore = useMarketTrackingStore();
|
|
||||||
const items = ref<TrackingResult[]>([]);
|
|
||||||
const addOrRelaod = async (type: MarketType) => {
|
|
||||||
const typeID = type.id;
|
|
||||||
const [history, price] = await Promise.all([
|
|
||||||
getHistory(typeID),
|
|
||||||
apraisalStore.getPrice(type)
|
|
||||||
]);
|
|
||||||
const itm = {
|
|
||||||
type,
|
|
||||||
history,
|
|
||||||
buy: price.buy,
|
|
||||||
sell: price.sell,
|
|
||||||
orderCount: price.orderCount
|
|
||||||
};
|
|
||||||
|
|
||||||
if (items.value.some(i => i.type.id === typeID)) {
|
|
||||||
items.value = items.value.map(i => i.type.id === typeID ? itm : i);
|
|
||||||
} else {
|
|
||||||
items.value = [ ...items.value, itm];
|
|
||||||
marketTrackingStore.addType(typeID);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const addItem = async () => {
|
|
||||||
if (!item.value) {
|
|
||||||
// TODO error
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
addOrRelaod(item.value);
|
|
||||||
item.value = undefined;
|
|
||||||
}
|
|
||||||
const removeItem = (type: MarketType) => {
|
|
||||||
items.value = items.value.filter(i => i.type.id !== type.id);
|
|
||||||
marketTrackingStore.removeType(type.id);
|
|
||||||
}
|
|
||||||
|
|
||||||
watch(() => marketTrackingStore.types, async t => {
|
|
||||||
const typesToLoad = t.filter(t => !items.value.some(i => i.type.id === t));
|
|
||||||
|
|
||||||
if (typesToLoad.length === 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const prices = await apraisalStore.getPrices(await getMarketTypes(typesToLoad));
|
|
||||||
|
|
||||||
typesToLoad.forEach(async i => items.value.push(await createResult(i, prices.find(p => p.type.id === i) as MarketTypePrice)));
|
|
||||||
}, { immediate: true });
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<div class="grid mb-2 mt-4">
|
|
||||||
<div class="w-auto flex">
|
|
||||||
<span>Item: </span>
|
|
||||||
<MarketTypeInput class="ms-2" v-model="item" @submit="addItem"/>
|
|
||||||
<button class="justify-self-end ms-2" @click="addItem">Add</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<template v-if="items.length > 0">
|
|
||||||
<hr />
|
|
||||||
<TrackingResultTable :items="items" @buy="(type, buy, sell) => buyModal?.open(type, { 'Buy': buy, 'Sell': sell })" @remove="removeItem" />
|
|
||||||
<BuyModal ref="buyModal" />
|
|
||||||
<Modal :open="items.length > 0 && items.length < marketTrackingStore.types.length">
|
|
||||||
<div class="ms-auto me-auto mb-2 w-96">
|
|
||||||
<ProgressBar :value="items.length" :total="marketTrackingStore.types.length" />
|
|
||||||
</div>
|
|
||||||
</Modal>
|
|
||||||
</template>
|
|
||||||
</template>
|
|
||||||
@@ -1,14 +1,14 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import {ClipboardButton} from '@/components';
|
import {ClipboardButton} from '@/components';
|
||||||
import {getMarketType, MarketType, MarketTypeInput, useApraisalStore} from "@/market";
|
import {getMarketType, MarketType, MarketTypeInput, useApraisalStore, useMarketTaxStore} from "@/market";
|
||||||
import {AcquisitionResultTable, BuyModal, useAcquiredTypesStore} from '@/market/acquisition';
|
import {AcquisitionResultTable, BuyModal, useAcquiredTypesStore} from '@/market/acquisition';
|
||||||
import {createResult, TrackingResultTable, useMarketTrackingStore} from '@/market/tracking';
|
import {buildScanResult, ScanResultTable} from '@/market/scan';
|
||||||
import {BookmarkIcon, BookmarkSlashIcon, ShoppingCartIcon} from '@heroicons/vue/24/outline';
|
import {ShoppingCartIcon} from '@heroicons/vue/24/outline';
|
||||||
import log from "loglevel";
|
import log from "loglevel";
|
||||||
import {computed, ref, watch} from "vue";
|
import {computed, ref, watch} from "vue";
|
||||||
import {useRoute, useRouter} from "vue-router";
|
import {useRoute, useRouter} from "vue-router";
|
||||||
import {routeNames} from "@/routes";
|
import {routeNames} from "@/routes";
|
||||||
import {computedAsync} from "@vueuse/core";
|
import {computedAsync, useStorage} from "@vueuse/core";
|
||||||
|
|
||||||
const buyModal = ref<typeof BuyModal>();
|
const buyModal = ref<typeof BuyModal>();
|
||||||
|
|
||||||
@@ -18,12 +18,12 @@ const item = ref<MarketType>();
|
|||||||
const inputItem = ref<MarketType>();
|
const inputItem = ref<MarketType>();
|
||||||
|
|
||||||
const apraisalStore = useApraisalStore();
|
const apraisalStore = useApraisalStore();
|
||||||
|
const marketTaxStore = useMarketTaxStore();
|
||||||
|
const days = useStorage('market-scan-days', 365);
|
||||||
const price = computedAsync(() => item.value ? apraisalStore.getPrice(item.value) : undefined);
|
const price = computedAsync(() => item.value ? apraisalStore.getPrice(item.value) : undefined);
|
||||||
const marketTrackingStore = useMarketTrackingStore();
|
const result = computedAsync(async () => price.value ? await buildScanResult(price.value, days.value, marketTaxStore.calculateProfit) : undefined);
|
||||||
const result = computedAsync(async () => item.value && price.value ? await createResult(item.value?.id, price.value) : undefined);
|
|
||||||
const acquiredTypesStore = useAcquiredTypesStore();
|
const acquiredTypesStore = useAcquiredTypesStore();
|
||||||
|
|
||||||
const isTracked = computed(() => item.value ? marketTrackingStore.types.includes(item.value.id) : false);
|
|
||||||
const acquisitions = computed(() => {
|
const acquisitions = computed(() => {
|
||||||
const p = price.value;
|
const p = price.value;
|
||||||
|
|
||||||
@@ -36,17 +36,6 @@ const acquisitions = computed(() => {
|
|||||||
sell: p.sell
|
sell: p.sell
|
||||||
}));
|
}));
|
||||||
});
|
});
|
||||||
const toogleTracking = () => {
|
|
||||||
if (!item.value) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (isTracked.value) {
|
|
||||||
marketTrackingStore.removeType(item.value.id);
|
|
||||||
} else {
|
|
||||||
marketTrackingStore.addType(item.value.id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const view = () => {
|
const view = () => {
|
||||||
if (!inputItem.value) {
|
if (!inputItem.value) {
|
||||||
return;
|
return;
|
||||||
@@ -93,10 +82,6 @@ watch(useRoute(), async route => {
|
|||||||
<div class="ms-auto">
|
<div class="ms-auto">
|
||||||
<ClipboardButton class="ms-1" :value="item.name" />
|
<ClipboardButton class="ms-1" :value="item.name" />
|
||||||
<button v-if="price" class="btn-icon ms-1" title="Add acquisitions" @click="buyModal?.open(item, { 'Buy': price.buy, 'Sell': price.sell })"><ShoppingCartIcon /></button>
|
<button v-if="price" class="btn-icon ms-1" title="Add acquisitions" @click="buyModal?.open(item, { 'Buy': price.buy, 'Sell': price.sell })"><ShoppingCartIcon /></button>
|
||||||
<button class="btn-icon ms-1" :title="isTracked ? 'Untrack' : 'Track'" @click="toogleTracking">
|
|
||||||
<BookmarkSlashIcon v-if="isTracked" />
|
|
||||||
<BookmarkIcon v-else />
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<p v-if="item.description" class="text-sm">{{ item.description }}</p>
|
<p v-if="item.description" class="text-sm">{{ item.description }}</p>
|
||||||
@@ -104,7 +89,7 @@ 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>
|
||||||
<TrackingResultTable :items="[result]" infoOnly :ignoredColums="['name', 'acquisitions']" />
|
<ScanResultTable :items="[result]" infoOnly :ignoredColums="['name', 'acquisitions']" />
|
||||||
</div>
|
</div>
|
||||||
<div v-if="acquisitions && acquisitions.length > 0">
|
<div v-if="acquisitions && acquisitions.length > 0">
|
||||||
<span>Acquisitions:</span>
|
<span>Acquisitions:</span>
|
||||||
|
|||||||
@@ -4,23 +4,20 @@ import {useRoute} from "vue-router";
|
|||||||
import {computed, ref, watch, watchEffect} from "vue";
|
import {computed, ref, watch, watchEffect} from "vue";
|
||||||
import log from "loglevel";
|
import log from "loglevel";
|
||||||
import {
|
import {
|
||||||
findCharacterRuleBookByCharacterId,
|
|
||||||
RuleBook,
|
RuleBook,
|
||||||
setCharacterRuleBookForCharacter,
|
useCharacterRuleBooksStore,
|
||||||
useRuleBooksStore
|
useRuleBooksStore
|
||||||
} from "@/rules";
|
} from "@/rules";
|
||||||
import {storeToRefs} from "pinia";
|
|
||||||
import {isMain, Ledger, LedgerSelect, systemLedger, useLedgersStore} from "@/ledger";
|
import {isMain, Ledger, LedgerSelect, systemLedger, useLedgersStore} from "@/ledger";
|
||||||
|
|
||||||
type Bindings = { [key: string]: Ledger; };
|
type Bindings = { [key: string]: Ledger; };
|
||||||
|
|
||||||
const ruleBookStore = useRuleBooksStore();
|
const ruleBookStore = useRuleBooksStore();
|
||||||
const {findById: findRuleBookById} = ruleBookStore;
|
const characterRuleBooksStore = useCharacterRuleBooksStore();
|
||||||
const {ruleBooks} = storeToRefs(ruleBookStore);
|
const charactersStore = useCharactersStore();
|
||||||
const {findById: findCharacterById} = useCharactersStore();
|
const ledgersStore = useLedgersStore();
|
||||||
const {ledgers} = storeToRefs(useLedgersStore());
|
|
||||||
|
|
||||||
const ledgersToUse = computed(() => [systemLedger, ...ledgers.value.filter(isMain)]);
|
const ledgersToUse = computed(() => [systemLedger, ...ledgersStore.ledgers.filter(isMain)]);
|
||||||
|
|
||||||
const character = ref<Character>();
|
const character = ref<Character>();
|
||||||
const ruleBook = ref<RuleBook>();
|
const ruleBook = ref<RuleBook>();
|
||||||
@@ -31,11 +28,11 @@ watchEffect(async () => {
|
|||||||
const characterId = character.value?.characterId;
|
const characterId = character.value?.characterId;
|
||||||
|
|
||||||
if (characterId) {
|
if (characterId) {
|
||||||
const characterRuleBook = await findCharacterRuleBookByCharacterId(characterId);
|
const characterRuleBook = characterRuleBooksStore.findByCharacterId(characterId);
|
||||||
|
|
||||||
ruleBook.value = findRuleBookById(characterRuleBook.ruleBookId);
|
ruleBook.value = ruleBookStore.findById(characterRuleBook?.ruleBook.ruleBookId ?? '');
|
||||||
bindings.value = Object.fromEntries(
|
bindings.value = Object.fromEntries(
|
||||||
Object.entries(characterRuleBook.bindings)
|
Object.entries(characterRuleBook?.bindings ?? {})
|
||||||
.map(([key, id]) => [key, ledgersToUse.value.find(l => l.ledgerId === id) ?? systemLedger])
|
.map(([key, id]) => [key, ledgersToUse.value.find(l => l.ledgerId === id) ?? systemLedger])
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -46,7 +43,7 @@ const save = () => {
|
|||||||
const ruleBookId = ruleBook.value?.ruleBookId;
|
const ruleBookId = ruleBook.value?.ruleBookId;
|
||||||
|
|
||||||
if (characterId && ruleBookId) {
|
if (characterId && ruleBookId) {
|
||||||
setCharacterRuleBookForCharacter(characterId, {
|
characterRuleBooksStore.setForCharacter(characterId, {
|
||||||
ruleBookId,
|
ruleBookId,
|
||||||
bindings: Object.fromEntries(
|
bindings: Object.fromEntries(
|
||||||
Object.entries(bindings.value)
|
Object.entries(bindings.value)
|
||||||
@@ -60,7 +57,7 @@ watch(useRoute(), async route => {
|
|||||||
if (route.params.characterId) {
|
if (route.params.characterId) {
|
||||||
const id = parseInt(typeof route.params.characterId === 'string' ? route.params.characterId : route.params.characterId[0]);
|
const id = parseInt(typeof route.params.characterId === 'string' ? route.params.characterId : route.params.characterId[0]);
|
||||||
|
|
||||||
character.value = await findCharacterById(id);
|
character.value = await charactersStore.findById(id);
|
||||||
log.info('Loaded character:', character.value);
|
log.info('Loaded character:', character.value);
|
||||||
} else {
|
} else {
|
||||||
character.value = undefined;
|
character.value = undefined;
|
||||||
@@ -80,7 +77,7 @@ watch(useRoute(), async route => {
|
|||||||
<div class="flex-col border-b-1">
|
<div class="flex-col border-b-1">
|
||||||
Rule Book:
|
Rule Book:
|
||||||
<select class="me-2 mb-2 w-50" v-model="ruleBook">
|
<select class="me-2 mb-2 w-50" v-model="ruleBook">
|
||||||
<option v-for="rb in ruleBooks" :key="rb.ruleBookId" :value="rb">{{ rb.name }}</option>
|
<option v-for="rb in ruleBookStore.ruleBooks" :key="rb.ruleBookId" :value="rb">{{ rb.name }}</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex-col border-b-1">
|
<div class="flex-col border-b-1">
|
||||||
|
|||||||
@@ -14,12 +14,12 @@ const usedForAcquisitions = ref<boolean>(false);
|
|||||||
const ledgerRefs = ref<string[]>([]);
|
const ledgerRefs = ref<string[]>([]);
|
||||||
const script = ref<string>('');
|
const script = ref<string>('');
|
||||||
|
|
||||||
const {findById, create, update, refresh} = useRuleBooksStore();
|
const ruleBooksStore = useRuleBooksStore();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
const save = async () => {
|
const save = async () => {
|
||||||
if (!ruleBookId.value) {
|
if (!ruleBookId.value) {
|
||||||
const created = await create({
|
const created = await ruleBooksStore.create({
|
||||||
name: name.value,
|
name: name.value,
|
||||||
usedForAcquisitions: usedForAcquisitions.value,
|
usedForAcquisitions: usedForAcquisitions.value,
|
||||||
ledgerRefs: ledgerRefs.value,
|
ledgerRefs: ledgerRefs.value,
|
||||||
@@ -28,7 +28,7 @@ const save = async () => {
|
|||||||
await router.push({ name: routeNames.editRuleBook, params: {ruleBookId: created.ruleBookId}})
|
await router.push({ name: routeNames.editRuleBook, params: {ruleBookId: created.ruleBookId}})
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
await update(ruleBookId.value, {
|
await ruleBooksStore.update(ruleBookId.value, {
|
||||||
name: name.value,
|
name: name.value,
|
||||||
usedForAcquisitions: usedForAcquisitions.value,
|
usedForAcquisitions: usedForAcquisitions.value,
|
||||||
ledgerRefs: ledgerRefs.value,
|
ledgerRefs: ledgerRefs.value,
|
||||||
@@ -57,13 +57,13 @@ const removeLedgerRef = (index: number) => {
|
|||||||
|
|
||||||
watch(useRoute(), async route => {
|
watch(useRoute(), async route => {
|
||||||
if (route.params.ruleBookId) {
|
if (route.params.ruleBookId) {
|
||||||
const promise = refresh(); // FIXME don't call refresh
|
const promise = ruleBooksStore.refresh(); // FIXME don't call refresh
|
||||||
|
|
||||||
const id = typeof route.params.ruleBookId === 'string' ? route.params.ruleBookId : route.params.ruleBookId[0];
|
const id = typeof route.params.ruleBookId === 'string' ? route.params.ruleBookId : route.params.ruleBookId[0];
|
||||||
|
|
||||||
await promise;
|
await promise;
|
||||||
|
|
||||||
const ruleBook = findById(id);
|
const ruleBook = ruleBooksStore.findById(id);
|
||||||
|
|
||||||
ruleBookId.value = id;
|
ruleBookId.value = id;
|
||||||
name.value = ruleBook?.name ?? '';
|
name.value = ruleBook?.name ?? '';
|
||||||
|
|||||||
@@ -1,34 +1,23 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import {storeToRefs} from "pinia";
|
import {Character, CharacterLabel} from "@/characters";
|
||||||
import {Character, CharacterLabel, useCharactersStore} from "@/characters";
|
|
||||||
import {PencilSquareIcon} from "@heroicons/vue/24/outline";
|
import {PencilSquareIcon} from "@heroicons/vue/24/outline";
|
||||||
import {findCharacterRuleBookByCharacterId, useRuleBooksStore} from "@/rules";
|
import {CharacterRuleBook, useCharacterRuleBooksStore} from "@/rules";
|
||||||
import {computedAsync} from "@vueuse/core";
|
|
||||||
import {routeNames} from "@/routes.ts";
|
import {routeNames} from "@/routes.ts";
|
||||||
import {SortableHeader, useSort} from "@/components/table";
|
import {SortableHeader, useSort} from "@/components/table";
|
||||||
|
|
||||||
type CharacterRuleBookView = {
|
type CharacterRuleBookView = {
|
||||||
character: Character;
|
character: Character;
|
||||||
characterName: string;
|
characterName: string;
|
||||||
characterId: number;
|
|
||||||
ruleBookName: string;
|
ruleBookName: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const {characters} = storeToRefs(useCharactersStore());
|
const characterRuleBooksStore = useCharacterRuleBooksStore();
|
||||||
const {findById: findRuleBookById} = useRuleBooksStore();
|
|
||||||
|
|
||||||
|
const { sortedArray, headerProps } = useSort<CharacterRuleBookView>(() => characterRuleBooksStore.characterRuleBooks.map((characterRuleBook: CharacterRuleBook): CharacterRuleBookView => ({
|
||||||
const { sortedArray, headerProps } = useSort(computedAsync<CharacterRuleBookView[]>(async () => await Promise.all(characters.value.map(async (character: Character): Promise<CharacterRuleBookView> => {
|
character: characterRuleBook.character,
|
||||||
const characterRuleBook = await findCharacterRuleBookByCharacterId(character.characterId);
|
characterName: characterRuleBook.character.name,
|
||||||
const ruleBook = findRuleBookById(characterRuleBook.ruleBookId);
|
ruleBookName: characterRuleBook.ruleBook.name
|
||||||
|
})))
|
||||||
return {
|
|
||||||
character,
|
|
||||||
characterName: character.name,
|
|
||||||
characterId: character.characterId,
|
|
||||||
ruleBookName: ruleBook?.name ?? ''
|
|
||||||
}
|
|
||||||
})), []))
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@@ -42,13 +31,13 @@ const { sortedArray, headerProps } = useSort(computedAsync<CharacterRuleBookView
|
|||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr v-for="characterRuleBookView in sortedArray" :key="characterRuleBookView.characterId" >
|
<tr v-for="characterRuleBookView in sortedArray" :key="characterRuleBookView.character.characterId" >
|
||||||
<td>
|
<td>
|
||||||
<CharacterLabel :character="characterRuleBookView.character" />
|
<CharacterLabel :character="characterRuleBookView.character" />
|
||||||
</td>
|
</td>
|
||||||
<td>{{characterRuleBookView.ruleBookName}}</td>
|
<td>{{characterRuleBookView.ruleBookName}}</td>
|
||||||
<td class="text-right">
|
<td class="text-right">
|
||||||
<RouterLink class="btn-icon" :to="{ name: routeNames.editCharacterRulebook, params: { characterId: characterRuleBookView.characterId } }"><PencilSquareIcon /></RouterLink>
|
<RouterLink class="btn-icon" :to="{ name: routeNames.editCharacterRulebook, params: { characterId: characterRuleBookView.character.characterId } }"><PencilSquareIcon /></RouterLink>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
|
|||||||
@@ -1,12 +1,10 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import {storeToRefs} from "pinia";
|
|
||||||
import {DocumentDuplicateIcon, PencilSquareIcon, TrashIcon} from "@heroicons/vue/24/outline";
|
import {DocumentDuplicateIcon, PencilSquareIcon, TrashIcon} from "@heroicons/vue/24/outline";
|
||||||
import {confirm} from "@/confirm";
|
import {confirm} from "@/confirm";
|
||||||
import {RuleBook, useRuleBooksStore} from "@/rules";
|
import {RuleBook, useRuleBooksStore} from "@/rules";
|
||||||
import {routeNames} from "@/routes";
|
import {routeNames} from "@/routes";
|
||||||
|
|
||||||
const ruleBooksStore = useRuleBooksStore();
|
const ruleBooksStore = useRuleBooksStore();
|
||||||
const {ruleBooks} = storeToRefs(ruleBooksStore);
|
|
||||||
|
|
||||||
const duplicate = async (ruleBook: RuleBook) => {
|
const duplicate = async (ruleBook: RuleBook) => {
|
||||||
if (await confirm({title: "Duplicate Rule Book", message: `Duplicate ${ruleBook.name}?`, confirmLabel: "Duplicate"})) {
|
if (await confirm({title: "Duplicate Rule Book", message: `Duplicate ${ruleBook.name}?`, confirmLabel: "Duplicate"})) {
|
||||||
@@ -27,7 +25,7 @@ const remove = async (ruleBook: RuleBook) => {
|
|||||||
<div class="flex justify-end border-b-1">
|
<div class="flex justify-end border-b-1">
|
||||||
<RouterLink class="button mb-2 ms-2" :to="{ name: routeNames.newRuleBook}">New Rule Book</RouterLink>
|
<RouterLink class="button mb-2 ms-2" :to="{ name: routeNames.newRuleBook}">New Rule Book</RouterLink>
|
||||||
</div>
|
</div>
|
||||||
<div v-for="ruleBook in ruleBooks" :key="ruleBook.ruleBookId" class="flex items-center mt-2">
|
<div v-for="ruleBook in ruleBooksStore.ruleBooks" :key="ruleBook.ruleBookId" class="flex items-center mt-2">
|
||||||
<span class="flex grow me-2">{{ruleBook.name}}</span>
|
<span class="flex grow me-2">{{ruleBook.name}}</span>
|
||||||
<RouterLink class="btn-icon me-1" :to="{ name: routeNames.editRuleBook, params: { ruleBookId: ruleBook.ruleBookId } }"><PencilSquareIcon /></RouterLink>
|
<RouterLink class="btn-icon me-1" :to="{ name: routeNames.editRuleBook, params: { ruleBookId: ruleBook.ruleBookId } }"><PencilSquareIcon /></RouterLink>
|
||||||
<button class="btn-icon me-1" @click="duplicate(ruleBook)"><DocumentDuplicateIcon /></button>
|
<button class="btn-icon me-1" @click="duplicate(ruleBook)"><DocumentDuplicateIcon /></button>
|
||||||
|
|||||||
+1
-1
@@ -43,7 +43,7 @@ export const routes: RouteRecordRaw[] = [
|
|||||||
{path: '/market', component: () => import('@/pages/Market.vue'), children: [
|
{path: '/market', component: () => import('@/pages/Market.vue'), children: [
|
||||||
{path: '', redirect: {name: routeNames.marketTypes}},
|
{path: '', redirect: {name: routeNames.marketTypes}},
|
||||||
{path: 'types/:type?', name: routeNames.marketTypes, component: () => import('@/pages/market/TypeInfo.vue')},
|
{path: 'types/:type?', name: routeNames.marketTypes, component: () => import('@/pages/market/TypeInfo.vue')},
|
||||||
{path: 'tracking', component: () => import('@/pages/market/Tracking.vue')},
|
{path: 'scan', component: () => import('@/pages/market/Scan.vue')},
|
||||||
{path: 'acquisitions', component: () => import('@/pages/market/Acquisitions.vue')},
|
{path: 'acquisitions', component: () => import('@/pages/market/Acquisitions.vue')},
|
||||||
]},
|
]},
|
||||||
|
|
||||||
|
|||||||
+27
-5
@@ -50,12 +50,34 @@ export const useRuleBooksStore = defineStore('rule-books', () => {
|
|||||||
return {ruleBooks, findById, create, update, duplicate, remove, refresh};
|
return {ruleBooks, findById, create, update, duplicate, remove, refresh};
|
||||||
})
|
})
|
||||||
|
|
||||||
export const findCharacterRuleBookByCharacterId = (characterId: number): Promise<CharacterRuleBookResponse> => characterRuleBookApi.findCharacterRuleBookByCharacterId(characterId)
|
export type CharacterRuleBook = CharacterRuleBookResponse;
|
||||||
.then(response => response.data)
|
|
||||||
.catch(() => ({characterId, ruleBookId: '', bindings: {}}));
|
|
||||||
|
|
||||||
export const setCharacterRuleBookForCharacter = (characterId: number, ruleBook: SetCharacterRuleBookRequest): Promise<CharacterRuleBookResponse> => characterRuleBookApi.setCharacterRuleBookForCharacter(characterId, ruleBook)
|
export const useCharacterRuleBooksStore = defineStore('character-rule-books', () => {
|
||||||
.then(response => response.data);
|
const characterRuleBooks = ref<CharacterRuleBook[]>([]);
|
||||||
|
|
||||||
|
const replaceCharacterRuleBook = (characterRuleBook: CharacterRuleBook) => {
|
||||||
|
const index = characterRuleBooks.value.findIndex(crb => crb.character.characterId === characterRuleBook.character.characterId);
|
||||||
|
|
||||||
|
if (index !== -1) {
|
||||||
|
characterRuleBooks.value[index] = characterRuleBook;
|
||||||
|
} else {
|
||||||
|
characterRuleBooks.value.push(characterRuleBook);
|
||||||
|
}
|
||||||
|
triggerRef(characterRuleBooks);
|
||||||
|
return characterRuleBook;
|
||||||
|
};
|
||||||
|
|
||||||
|
const findByCharacterId = (characterId: number): CharacterRuleBook | undefined => characterRuleBooks.value.find(crb => crb.character.characterId === characterId);
|
||||||
|
|
||||||
|
const setForCharacter = (characterId: number, ruleBook: SetCharacterRuleBookRequest) => characterRuleBookApi.setCharacterRuleBookForCharacter(characterId, ruleBook)
|
||||||
|
.then(response => replaceCharacterRuleBook(response.data));
|
||||||
|
|
||||||
|
const refresh = () => characterRuleBookApi.findAllCharacterRuleBooks().then(response => characterRuleBooks.value = response.data);
|
||||||
|
|
||||||
|
refresh();
|
||||||
|
|
||||||
|
return {characterRuleBooks, findByCharacterId, setForCharacter, refresh};
|
||||||
|
})
|
||||||
|
|
||||||
export const fetchScriptDefinitions = (): Promise<string> =>
|
export const fetchScriptDefinitions = (): Promise<string> =>
|
||||||
ruleBookApi.getScriptDefinitions({responseType: 'text'}).then(response => response.data);
|
ruleBookApi.getScriptDefinitions({responseType: 'text'}).then(response => response.data);
|
||||||
@@ -14,7 +14,7 @@ interface Props {
|
|||||||
|
|
||||||
const props = defineProps<Props>();
|
const props = defineProps<Props>();
|
||||||
|
|
||||||
const {findById} = useLedgersStore();
|
const ledgersStore = useLedgersStore();
|
||||||
|
|
||||||
const sortedArray = computedAsync(async () => {
|
const sortedArray = computedAsync(async () => {
|
||||||
if (!props.transfers) {
|
if (!props.transfers) {
|
||||||
@@ -22,8 +22,8 @@ const sortedArray = computedAsync(async () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (await Promise.all(props.transfers.map(async (transfer: TransferWithValue, index) => {
|
return (await Promise.all(props.transfers.map(async (transfer: TransferWithValue, index) => {
|
||||||
const fromLedger = findById(transfer.fromLedgerId) ?? systemLedger
|
const fromLedger = ledgersStore.findById(transfer.fromLedgerId) ?? systemLedger
|
||||||
const toLedger = findById(transfer.toLedgerId) ?? systemLedger
|
const toLedger = ledgersStore.findById(transfer.toLedgerId) ?? systemLedger
|
||||||
|
|
||||||
const item = transfer.marketTypeId ? await getMarketType(transfer.marketTypeId) : undefined;
|
const item = transfer.marketTypeId ? await getMarketType(transfer.marketTypeId) : undefined;
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user