diff --git a/docs/mammon-api.yml b/docs/mammon-api.yml index 154e3ae..c09a8d6 100644 --- a/docs/mammon-api.yml +++ b/docs/mammon-api.yml @@ -904,6 +904,34 @@ paths: $ref: "#/components/schemas/TransactionResponse" "404": description: No ledger with this id + /ledgers/{ledgerId}/profit: + get: + tags: + - transaction + summary: Get the daily profit series for a single ledger + operationId: profitPerDayInLedger + parameters: + - name: ledgerId + in: path + description: Id of the ledger + required: true + schema: + type: string + format: uuid + responses: + "200": + description: "Daily profit for the ledger, ascending by date, only for days\ + \ with ISK activity" + content: + '*/*': + schema: + type: array + items: + $ref: "#/components/schemas/DailyProfitResponse" + "400": + description: The ledger is the system ledger or a combined ledger + "404": + description: No ledger with this id /ledgers/{ledgerId}/balance: get: tags: @@ -1682,7 +1710,7 @@ components: type: string format: date-time description: When the order was issued. - example: 2026-06-22T05:12:25Z + example: 2026-06-22T05:12:25 escrow: type: number description: ISK held in escrow for a buy order; null for sell orders. @@ -1910,6 +1938,32 @@ components: type: string required: - type + DailyProfitResponse: + type: object + description: "Profit for a single day: ISK in, ISK out, and their difference." + properties: + date: + type: string + format: date + description: The UTC (EVE) day this profit is for. + example: 2026-06-22 + iskIn: + type: number + description: Total ISK that entered the ledger on this day. + example: 1500000.0 + iskOut: + type: number + description: Total ISK that left the ledger on this day. + example: 500000.0 + profit: + type: number + description: "Profit for the day: iskIn minus iskOut." + example: 1000000.0 + required: + - date + - iskIn + - iskOut + - profit BalanceResponse: type: object description: "The balance of a ledger: its ISK total plus the quantity of each\ diff --git a/package-lock.json b/package-lock.json index e13944b..29a3248 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,6 +14,7 @@ "@vueuse/integrations": "^14.3.0", "@vueuse/router": "^14.3.0", "axios": "^1.4.0", + "chart.js": "^4.5.1", "gemory": "file:", "loglevel": "^1.8.1", "loglevel-plugin-prefix": "^0.8.4", @@ -21,6 +22,7 @@ "pinia": "^3.0.4", "sortablejs": "^1.15.7", "vue": "^3.3.4", + "vue-chartjs": "^5.3.4", "vue-router": "^5.0.7" }, "devDependencies": { @@ -229,6 +231,12 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "node_modules/@kurkle/color": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/@kurkle/color/-/color-0.3.4.tgz", + "integrity": "sha512-M5UknZPHRu3DEDWoipU6sE8PdkZ6Z/S+v4dD+Ke8IaNlpdSQah50lz1KtcFBa2vsdOnwbbnxJwVM4wty6udA5w==", + "license": "MIT" + }, "node_modules/@napi-rs/wasm-runtime": { "version": "1.1.5", "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.5.tgz", @@ -1483,6 +1491,18 @@ "node": ">=18" } }, + "node_modules/chart.js": { + "version": "4.5.1", + "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.5.1.tgz", + "integrity": "sha512-GIjfiT9dbmHRiYi6Nl2yFCq7kkwdkp1W/lp2J99rX0yo9tgJGn3lKQATztIjb5tVtevcBtIdICNWqlq5+E8/Pw==", + "license": "MIT", + "dependencies": { + "@kurkle/color": "^0.3.0" + }, + "engines": { + "pnpm": ">=8" + } + }, "node_modules/chokidar": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-5.0.0.tgz", @@ -2975,6 +2995,16 @@ } } }, + "node_modules/vue-chartjs": { + "version": "5.3.4", + "resolved": "https://registry.npmjs.org/vue-chartjs/-/vue-chartjs-5.3.4.tgz", + "integrity": "sha512-x3Fqob8RQvrTdssfi9ecsCzEkFOd8JPmNwSkSQzdfKj/uBsRJs/Y88cZcZIEcPsTVfMGwMo4MOoihoDG2DoE/g==", + "license": "MIT", + "peerDependencies": { + "chart.js": "^4.1.1", + "vue": "^3.0.0-0 || ^2.7.0" + } + }, "node_modules/vue-router": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/vue-router/-/vue-router-5.1.0.tgz", diff --git a/package.json b/package.json index 4f089f7..0db4132 100644 --- a/package.json +++ b/package.json @@ -16,6 +16,7 @@ "@vueuse/integrations": "^14.3.0", "@vueuse/router": "^14.3.0", "axios": "^1.4.0", + "chart.js": "^4.5.1", "gemory": "file:", "loglevel": "^1.8.1", "loglevel-plugin-prefix": "^0.8.4", @@ -23,6 +24,7 @@ "pinia": "^3.0.4", "sortablejs": "^1.15.7", "vue": "^3.3.4", + "vue-chartjs": "^5.3.4", "vue-router": "^5.0.7" }, "devDependencies": { diff --git a/src/components/DualRangeSlider.vue b/src/components/DualRangeSlider.vue new file mode 100644 index 0000000..8121adf --- /dev/null +++ b/src/components/DualRangeSlider.vue @@ -0,0 +1,87 @@ + + + + + + + + + + + + diff --git a/src/components/index.ts b/src/components/index.ts index 77b51e4..bb37933 100644 --- a/src/components/index.ts +++ b/src/components/index.ts @@ -1,5 +1,6 @@ export { default as ClipboardButton } from './ClipboardButton.vue'; export { default as Dropdown } from './Dropdown.vue'; +export { default as DualRangeSlider } from './DualRangeSlider.vue'; export { default as LoadingSpinner } from './LoadingSpinner.vue'; export { default as Modal } from './Modal.vue'; export { default as ProgressBar } from './ProgressBar.vue'; diff --git a/src/generated/mammon/api.ts b/src/generated/mammon/api.ts index 3e46ab4..0a313c1 100644 --- a/src/generated/mammon/api.ts +++ b/src/generated/mammon/api.ts @@ -190,6 +190,27 @@ export interface CreateMainLedgerRequest { */ 'name': string; } +/** + * Profit for a single day: ISK in, ISK out, and their difference. + */ +export interface DailyProfitResponse { + /** + * The UTC (EVE) day this profit is for. + */ + 'date': string; + /** + * Total ISK that entered the ledger on this day. + */ + 'iskIn': number; + /** + * Total ISK that left the ledger on this day. + */ + 'iskOut': number; + /** + * Profit for the day: iskIn minus iskOut. + */ + 'profit': number; +} /** * A market location (NPC station) with its containing solar system and region. */ @@ -3707,6 +3728,40 @@ export const TransactionApiAxiosParamCreator = function (configuration?: Configu let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {}; localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers}; + return { + url: toPathString(localVarUrlObj), + options: localVarRequestOptions, + }; + }, + /** + * + * @summary Get the daily profit series for a single ledger + * @param {string} ledgerId Id of the ledger + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + profitPerDayInLedger: async (ledgerId: string, options: RawAxiosRequestConfig = {}): Promise => { + // verify required parameter 'ledgerId' is not null or undefined + assertParamExists('profitPerDayInLedger', 'ledgerId', ledgerId) + const localVarPath = `/ledgers/{ledgerId}/profit` + .replace('{ledgerId}', encodeURIComponent(String(ledgerId))); + // 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, @@ -3734,6 +3789,19 @@ export const TransactionApiFp = function(configuration?: Configuration) { const localVarOperationServerBasePath = operationServerMap['TransactionApi.finAllTransactionsInLedger']?.[localVarOperationServerIndex]?.url; return (axios, basePath) => createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration)(axios, localVarOperationServerBasePath || basePath); }, + /** + * + * @summary Get the daily profit series for a single ledger + * @param {string} ledgerId Id of the ledger + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + async profitPerDayInLedger(ledgerId: string, options?: RawAxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise>> { + const localVarAxiosArgs = await localVarAxiosParamCreator.profitPerDayInLedger(ledgerId, options); + const localVarOperationServerIndex = configuration?.serverIndex ?? 0; + const localVarOperationServerBasePath = operationServerMap['TransactionApi.profitPerDayInLedger']?.[localVarOperationServerIndex]?.url; + return (axios, basePath) => createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration)(axios, localVarOperationServerBasePath || basePath); + }, } }; @@ -3753,6 +3821,16 @@ export const TransactionApiFactory = function (configuration?: Configuration, ba finAllTransactionsInLedger(ledgerId: string, options?: RawAxiosRequestConfig): AxiosPromise> { return localVarFp.finAllTransactionsInLedger(ledgerId, options).then((request) => request(axios, basePath)); }, + /** + * + * @summary Get the daily profit series for a single ledger + * @param {string} ledgerId Id of the ledger + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + profitPerDayInLedger(ledgerId: string, options?: RawAxiosRequestConfig): AxiosPromise> { + return localVarFp.profitPerDayInLedger(ledgerId, options).then((request) => request(axios, basePath)); + }, }; }; @@ -3770,6 +3848,17 @@ export class TransactionApi extends BaseAPI { public finAllTransactionsInLedger(ledgerId: string, options?: RawAxiosRequestConfig) { return TransactionApiFp(this.configuration).finAllTransactionsInLedger(ledgerId, options).then((request) => request(this.axios, this.basePath)); } + + /** + * + * @summary Get the daily profit series for a single ledger + * @param {string} ledgerId Id of the ledger + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + public profitPerDayInLedger(ledgerId: string, options?: RawAxiosRequestConfig) { + return TransactionApiFp(this.configuration).profitPerDayInLedger(ledgerId, options).then((request) => request(this.axios, this.basePath)); + } } diff --git a/src/pages/ledger/ViewLedger.vue b/src/pages/ledger/ViewLedger.vue index 7632ce2..4d8bd07 100644 --- a/src/pages/ledger/ViewLedger.vue +++ b/src/pages/ledger/ViewLedger.vue @@ -1,6 +1,6 @@ + + + + + Total profit: + + + + + No trade profit recorded for this ledger + + + + + + + + {{ profits[from]?.date }} + + {{ profits[to]?.date }} + + + + + diff --git a/src/routes.ts b/src/routes.ts index 8ee9e9a..cc4e780 100644 --- a/src/routes.ts +++ b/src/routes.ts @@ -13,6 +13,7 @@ export const routeNames = { viewLedgerBalance: 'view-ledger-balance', listLedgerTransactions: 'list-ledger-transactions', listLedgerAcquisitions: 'list-ledger-acquisitions', + viewLedgerStatistics: 'view-ledger-statistics', editRuleBook: 'edit-rule-book', marketTypes: 'market-types', appraise: 'appraise', @@ -31,6 +32,7 @@ export const routes: RouteRecordRaw[] = [ {path: 'balance', name: routeNames.viewLedgerBalance, component: () => import('@/pages/ledger/ViewLedgerBalance.vue')}, {path: 'transactions', name: routeNames.listLedgerTransactions, component: () => import('@/pages/ledger/ListLedgerTransactions.vue')}, {path: 'acquisitions', name: routeNames.listLedgerAcquisitions, component: () => import('@/pages/ledger/ViewLedgerAcquisitions.vue')}, + {path: 'statistics', name: routeNames.viewLedgerStatistics, component: () => import('@/pages/ledger/ViewLedgerStatistics.vue')}, ]}, ]},
+ No trade profit recorded for this ledger +