Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 2d57345634 |
@@ -23,5 +23,3 @@ docker-compose.yml
|
|||||||
*.njsproj
|
*.njsproj
|
||||||
*.sln
|
*.sln
|
||||||
*.sw?
|
*.sw?
|
||||||
|
|
||||||
generated/mammon/
|
|
||||||
@@ -1,782 +0,0 @@
|
|||||||
openapi: 3.1.0
|
|
||||||
info:
|
|
||||||
title: OpenAPI definition
|
|
||||||
version: v0
|
|
||||||
servers:
|
|
||||||
- url: http://localhost:8080
|
|
||||||
description: Generated server url
|
|
||||||
paths:
|
|
||||||
/rule-books/{ruleBookId}:
|
|
||||||
get:
|
|
||||||
tags:
|
|
||||||
- rule-book
|
|
||||||
summary: Find a rule book by its id
|
|
||||||
operationId: findRuleBookById
|
|
||||||
parameters:
|
|
||||||
- name: ruleBookId
|
|
||||||
in: path
|
|
||||||
description: Id of the rule book
|
|
||||||
required: true
|
|
||||||
schema:
|
|
||||||
type: string
|
|
||||||
format: uuid
|
|
||||||
responses:
|
|
||||||
"200":
|
|
||||||
description: The rule book
|
|
||||||
content:
|
|
||||||
'*/*':
|
|
||||||
schema:
|
|
||||||
$ref: "#/components/schemas/RuleBookResponse"
|
|
||||||
"404":
|
|
||||||
description: No rule book with this id
|
|
||||||
put:
|
|
||||||
tags:
|
|
||||||
- rule-book
|
|
||||||
summary: Update a rule book
|
|
||||||
operationId: updateRuleBook
|
|
||||||
parameters:
|
|
||||||
- name: ruleBookId
|
|
||||||
in: path
|
|
||||||
description: Id of the rule book
|
|
||||||
required: true
|
|
||||||
schema:
|
|
||||||
type: string
|
|
||||||
format: uuid
|
|
||||||
requestBody:
|
|
||||||
description: New state of the rule book
|
|
||||||
content:
|
|
||||||
application/json:
|
|
||||||
schema:
|
|
||||||
$ref: "#/components/schemas/UpdateRuleBookRequest"
|
|
||||||
required: true
|
|
||||||
responses:
|
|
||||||
"200":
|
|
||||||
description: The updated rule book
|
|
||||||
content:
|
|
||||||
'*/*':
|
|
||||||
schema:
|
|
||||||
$ref: "#/components/schemas/RuleBookResponse"
|
|
||||||
"400":
|
|
||||||
description: Invalid request (e.g. blank name)
|
|
||||||
delete:
|
|
||||||
tags:
|
|
||||||
- rule-book
|
|
||||||
summary: Delete a rule book
|
|
||||||
operationId: deleteRuleBook
|
|
||||||
parameters:
|
|
||||||
- name: ruleBookId
|
|
||||||
in: path
|
|
||||||
description: Id of the rule book
|
|
||||||
required: true
|
|
||||||
schema:
|
|
||||||
type: string
|
|
||||||
format: uuid
|
|
||||||
responses:
|
|
||||||
"204":
|
|
||||||
description: The rule book was deleted
|
|
||||||
"400":
|
|
||||||
description: The rule book is associated to a character
|
|
||||||
/ledgers/main/{ledgerId}:
|
|
||||||
put:
|
|
||||||
tags:
|
|
||||||
- ledger
|
|
||||||
summary: Update a main ledger
|
|
||||||
operationId: updateMainLedger
|
|
||||||
parameters:
|
|
||||||
- name: ledgerId
|
|
||||||
in: path
|
|
||||||
description: Id of the main ledger
|
|
||||||
required: true
|
|
||||||
schema:
|
|
||||||
type: string
|
|
||||||
format: uuid
|
|
||||||
requestBody:
|
|
||||||
description: New state of the main ledger
|
|
||||||
content:
|
|
||||||
application/json:
|
|
||||||
schema:
|
|
||||||
$ref: "#/components/schemas/UpdateMainLedgerRequest"
|
|
||||||
required: true
|
|
||||||
responses:
|
|
||||||
"200":
|
|
||||||
description: The updated main ledger
|
|
||||||
content:
|
|
||||||
'*/*':
|
|
||||||
schema:
|
|
||||||
$ref: "#/components/schemas/MainLedgerResponse"
|
|
||||||
"400":
|
|
||||||
description: "The ledger is not a main ledger, or the request is invalid"
|
|
||||||
"404":
|
|
||||||
description: No ledger with this id
|
|
||||||
/ledgers/combined/{ledgerId}:
|
|
||||||
put:
|
|
||||||
tags:
|
|
||||||
- ledger
|
|
||||||
summary: Update a combined ledger
|
|
||||||
operationId: updateCombinedLedger
|
|
||||||
parameters:
|
|
||||||
- name: ledgerId
|
|
||||||
in: path
|
|
||||||
description: Id of the combined ledger
|
|
||||||
required: true
|
|
||||||
schema:
|
|
||||||
type: string
|
|
||||||
format: uuid
|
|
||||||
requestBody:
|
|
||||||
description: New state of the combined ledger
|
|
||||||
content:
|
|
||||||
application/json:
|
|
||||||
schema:
|
|
||||||
$ref: "#/components/schemas/UpdateCombinedLedgerRequest"
|
|
||||||
required: true
|
|
||||||
responses:
|
|
||||||
"200":
|
|
||||||
description: The updated combined ledger
|
|
||||||
content:
|
|
||||||
'*/*':
|
|
||||||
schema:
|
|
||||||
$ref: "#/components/schemas/CombinedLedgerResponse"
|
|
||||||
"400":
|
|
||||||
description: "The ledger is not a combined ledger, or the request is invalid"
|
|
||||||
"404":
|
|
||||||
description: No ledger with this id
|
|
||||||
/characters/{characterId}/rule-book:
|
|
||||||
get:
|
|
||||||
tags:
|
|
||||||
- character-rule-book
|
|
||||||
summary: Find the rule book assigned to a character
|
|
||||||
operationId: findCharacterRuleBookByCharacterId
|
|
||||||
parameters:
|
|
||||||
- name: characterId
|
|
||||||
in: path
|
|
||||||
description: Id of the character
|
|
||||||
required: true
|
|
||||||
schema:
|
|
||||||
type: integer
|
|
||||||
format: int64
|
|
||||||
responses:
|
|
||||||
"200":
|
|
||||||
description: The rule book assignment of the character
|
|
||||||
content:
|
|
||||||
'*/*':
|
|
||||||
schema:
|
|
||||||
$ref: "#/components/schemas/CharacterRuleBookResponse"
|
|
||||||
"400":
|
|
||||||
description: Invalid character id
|
|
||||||
"404":
|
|
||||||
description: No rule book assigned to this character
|
|
||||||
put:
|
|
||||||
tags:
|
|
||||||
- character-rule-book
|
|
||||||
summary: Assign a rule book to a character
|
|
||||||
operationId: setCharacterRuleBookForCharacter
|
|
||||||
parameters:
|
|
||||||
- name: characterId
|
|
||||||
in: path
|
|
||||||
description: Id of the character
|
|
||||||
required: true
|
|
||||||
schema:
|
|
||||||
type: integer
|
|
||||||
format: int64
|
|
||||||
requestBody:
|
|
||||||
description: Rule book and ledger bindings to assign
|
|
||||||
content:
|
|
||||||
application/json:
|
|
||||||
schema:
|
|
||||||
$ref: "#/components/schemas/SetCharacterRuleBookRequest"
|
|
||||||
required: true
|
|
||||||
responses:
|
|
||||||
"200":
|
|
||||||
description: The rule book assignment of the character
|
|
||||||
content:
|
|
||||||
'*/*':
|
|
||||||
schema:
|
|
||||||
$ref: "#/components/schemas/CharacterRuleBookResponse"
|
|
||||||
"400":
|
|
||||||
description: "The referenced rule book or a bound ledger does not exist,\
|
|
||||||
\ or a ledger binding is missing"
|
|
||||||
/rule-books:
|
|
||||||
get:
|
|
||||||
tags:
|
|
||||||
- rule-book
|
|
||||||
summary: Find all rule books
|
|
||||||
operationId: findAllRuleBooks
|
|
||||||
responses:
|
|
||||||
"200":
|
|
||||||
description: All rule books
|
|
||||||
content:
|
|
||||||
'*/*':
|
|
||||||
schema:
|
|
||||||
type: array
|
|
||||||
items:
|
|
||||||
$ref: "#/components/schemas/RuleBookResponse"
|
|
||||||
post:
|
|
||||||
tags:
|
|
||||||
- rule-book
|
|
||||||
summary: Create a rule book
|
|
||||||
operationId: createRuleBook
|
|
||||||
requestBody:
|
|
||||||
description: Rule book to create
|
|
||||||
content:
|
|
||||||
application/json:
|
|
||||||
schema:
|
|
||||||
$ref: "#/components/schemas/CreateRuleBookRequest"
|
|
||||||
required: true
|
|
||||||
responses:
|
|
||||||
"201":
|
|
||||||
description: The created rule book
|
|
||||||
content:
|
|
||||||
'*/*':
|
|
||||||
schema:
|
|
||||||
$ref: "#/components/schemas/RuleBookResponse"
|
|
||||||
"400":
|
|
||||||
description: Invalid request (e.g. blank name)
|
|
||||||
/process-activities:
|
|
||||||
post:
|
|
||||||
tags:
|
|
||||||
- processing
|
|
||||||
summary: Process new activities for all characters with a usable token
|
|
||||||
operationId: processNewActivities
|
|
||||||
responses:
|
|
||||||
"200":
|
|
||||||
description: New activities processed
|
|
||||||
/ledgers/main:
|
|
||||||
post:
|
|
||||||
tags:
|
|
||||||
- ledger
|
|
||||||
summary: Create a main ledger
|
|
||||||
operationId: createMainLedger
|
|
||||||
requestBody:
|
|
||||||
description: Main ledger to create
|
|
||||||
content:
|
|
||||||
application/json:
|
|
||||||
schema:
|
|
||||||
$ref: "#/components/schemas/CreateMainLedgerRequest"
|
|
||||||
required: true
|
|
||||||
responses:
|
|
||||||
"201":
|
|
||||||
description: The created main ledger
|
|
||||||
content:
|
|
||||||
'*/*':
|
|
||||||
schema:
|
|
||||||
$ref: "#/components/schemas/MainLedgerResponse"
|
|
||||||
"400":
|
|
||||||
description: Invalid request (e.g. blank name)
|
|
||||||
/ledgers/combined:
|
|
||||||
post:
|
|
||||||
tags:
|
|
||||||
- ledger
|
|
||||||
summary: Create a combined ledger
|
|
||||||
operationId: createCombinedLedger
|
|
||||||
requestBody:
|
|
||||||
description: Combined ledger to create
|
|
||||||
content:
|
|
||||||
application/json:
|
|
||||||
schema:
|
|
||||||
$ref: "#/components/schemas/CreateCombinedLedgerRequest"
|
|
||||||
required: true
|
|
||||||
responses:
|
|
||||||
"201":
|
|
||||||
description: The created combined ledger
|
|
||||||
content:
|
|
||||||
'*/*':
|
|
||||||
schema:
|
|
||||||
$ref: "#/components/schemas/CombinedLedgerResponse"
|
|
||||||
"400":
|
|
||||||
description: "Invalid request (e.g. blank name, a member ledger missing\
|
|
||||||
\ or already contained)"
|
|
||||||
/activity/fetch:
|
|
||||||
post:
|
|
||||||
tags:
|
|
||||||
- activity
|
|
||||||
summary: Fetch all new activities for all characters from the EVE API
|
|
||||||
operationId: fetchAllNewActivities
|
|
||||||
responses:
|
|
||||||
"200":
|
|
||||||
description: New activities fetched and stored
|
|
||||||
/activity/fetch/{characterId}:
|
|
||||||
post:
|
|
||||||
tags:
|
|
||||||
- activity
|
|
||||||
summary: Fetch new activities for a character from the EVE API
|
|
||||||
operationId: fetchNewActivitiesForCharacter
|
|
||||||
parameters:
|
|
||||||
- name: characterId
|
|
||||||
in: path
|
|
||||||
description: Id of the character
|
|
||||||
required: true
|
|
||||||
schema:
|
|
||||||
type: integer
|
|
||||||
format: int64
|
|
||||||
responses:
|
|
||||||
"200":
|
|
||||||
description: New activities fetched and stored
|
|
||||||
"400":
|
|
||||||
description: No character with this id
|
|
||||||
/rule-books/script-definitions:
|
|
||||||
get:
|
|
||||||
tags:
|
|
||||||
- rule-book
|
|
||||||
summary: Download the TypeScript definitions for the rule script runtime
|
|
||||||
operationId: getScriptDefinitions
|
|
||||||
responses:
|
|
||||||
"200":
|
|
||||||
description: The rule-runner.d.ts type definitions
|
|
||||||
content:
|
|
||||||
text/plain:
|
|
||||||
schema:
|
|
||||||
type: string
|
|
||||||
/ledgers:
|
|
||||||
get:
|
|
||||||
tags:
|
|
||||||
- ledger
|
|
||||||
summary: Find all ledgers
|
|
||||||
operationId: findAllLedgers
|
|
||||||
responses:
|
|
||||||
"200":
|
|
||||||
description: All ledgers
|
|
||||||
content:
|
|
||||||
'*/*':
|
|
||||||
schema:
|
|
||||||
type: array
|
|
||||||
items:
|
|
||||||
$ref: "#/components/schemas/LedgerResponse"
|
|
||||||
/ledgers/{ledgerId}:
|
|
||||||
get:
|
|
||||||
tags:
|
|
||||||
- ledger
|
|
||||||
summary: Find a ledger by its id
|
|
||||||
operationId: findLedgerById
|
|
||||||
parameters:
|
|
||||||
- name: ledgerId
|
|
||||||
in: path
|
|
||||||
description: Id of the ledger
|
|
||||||
required: true
|
|
||||||
schema:
|
|
||||||
type: string
|
|
||||||
format: uuid
|
|
||||||
responses:
|
|
||||||
"200":
|
|
||||||
description: The ledger
|
|
||||||
content:
|
|
||||||
'*/*':
|
|
||||||
schema:
|
|
||||||
$ref: "#/components/schemas/LedgerResponse"
|
|
||||||
"400":
|
|
||||||
description: The ledger cannot be exposed (system ledger)
|
|
||||||
"404":
|
|
||||||
description: No ledger with this id
|
|
||||||
/ledgers/{ledgerId}/transactions:
|
|
||||||
get:
|
|
||||||
tags:
|
|
||||||
- transaction
|
|
||||||
summary: Find all transactions in a ledger
|
|
||||||
operationId: finAllTransactionsInLedger
|
|
||||||
parameters:
|
|
||||||
- name: ledgerId
|
|
||||||
in: path
|
|
||||||
description: Id of the ledger
|
|
||||||
required: true
|
|
||||||
schema:
|
|
||||||
type: string
|
|
||||||
format: uuid
|
|
||||||
responses:
|
|
||||||
"200":
|
|
||||||
description: All transactions in the ledger
|
|
||||||
content:
|
|
||||||
'*/*':
|
|
||||||
schema:
|
|
||||||
type: array
|
|
||||||
items:
|
|
||||||
$ref: "#/components/schemas/TransactionResponse"
|
|
||||||
"404":
|
|
||||||
description: No ledger with this id
|
|
||||||
/ledgers/{ledgerId}/balance:
|
|
||||||
get:
|
|
||||||
tags:
|
|
||||||
- ledger
|
|
||||||
summary: Find the balance of a ledger
|
|
||||||
operationId: findBalanceByLedgerId
|
|
||||||
parameters:
|
|
||||||
- name: ledgerId
|
|
||||||
in: path
|
|
||||||
description: Id of the ledger
|
|
||||||
required: true
|
|
||||||
schema:
|
|
||||||
type: string
|
|
||||||
format: uuid
|
|
||||||
responses:
|
|
||||||
"200":
|
|
||||||
description: The balance of the ledger
|
|
||||||
content:
|
|
||||||
'*/*':
|
|
||||||
schema:
|
|
||||||
$ref: "#/components/schemas/BalanceResponse"
|
|
||||||
"404":
|
|
||||||
description: No ledger with this id
|
|
||||||
/characters:
|
|
||||||
get:
|
|
||||||
tags:
|
|
||||||
- character
|
|
||||||
summary: Find all characters with a usable token
|
|
||||||
operationId: findAllCharacters
|
|
||||||
responses:
|
|
||||||
"200":
|
|
||||||
description: All characters with a usable token
|
|
||||||
content:
|
|
||||||
'*/*':
|
|
||||||
schema:
|
|
||||||
type: array
|
|
||||||
items:
|
|
||||||
$ref: "#/components/schemas/CharacterResponse"
|
|
||||||
/acquisitions:
|
|
||||||
get:
|
|
||||||
tags:
|
|
||||||
- acquisition
|
|
||||||
summary: Find all acquisitions that still have remaining stock
|
|
||||||
operationId: findAllAcquisitions
|
|
||||||
responses:
|
|
||||||
"200":
|
|
||||||
description: The acquisitions with remaining stock
|
|
||||||
content:
|
|
||||||
'*/*':
|
|
||||||
schema:
|
|
||||||
type: array
|
|
||||||
items:
|
|
||||||
$ref: "#/components/schemas/AcquisitionResponse"
|
|
||||||
components:
|
|
||||||
schemas:
|
|
||||||
UpdateRuleBookRequest:
|
|
||||||
type: object
|
|
||||||
properties:
|
|
||||||
name:
|
|
||||||
type: string
|
|
||||||
usedForAcquisitions:
|
|
||||||
type: boolean
|
|
||||||
ledgerRefs:
|
|
||||||
type: array
|
|
||||||
items:
|
|
||||||
type: string
|
|
||||||
pattern: "[a-z][a-zA-Z0-9]*"
|
|
||||||
script:
|
|
||||||
type: string
|
|
||||||
required:
|
|
||||||
- ledgerRefs
|
|
||||||
- name
|
|
||||||
- script
|
|
||||||
- usedForAcquisitions
|
|
||||||
RuleBookResponse:
|
|
||||||
type: object
|
|
||||||
properties:
|
|
||||||
ruleBookId:
|
|
||||||
type: string
|
|
||||||
format: uuid
|
|
||||||
name:
|
|
||||||
type: string
|
|
||||||
usedForAcquisitions:
|
|
||||||
type: boolean
|
|
||||||
ledgerRefs:
|
|
||||||
type: array
|
|
||||||
items:
|
|
||||||
type: string
|
|
||||||
pattern: "[a-z][a-zA-Z0-9]*"
|
|
||||||
script:
|
|
||||||
type: string
|
|
||||||
required:
|
|
||||||
- ledgerRefs
|
|
||||||
- name
|
|
||||||
- ruleBookId
|
|
||||||
- script
|
|
||||||
- usedForAcquisitions
|
|
||||||
UpdateMainLedgerRequest:
|
|
||||||
type: object
|
|
||||||
properties:
|
|
||||||
name:
|
|
||||||
type: string
|
|
||||||
required:
|
|
||||||
- name
|
|
||||||
MainLedgerResponse:
|
|
||||||
allOf:
|
|
||||||
- $ref: "#/components/schemas/LedgerResponse"
|
|
||||||
- type: object
|
|
||||||
properties:
|
|
||||||
ledgerId:
|
|
||||||
type: string
|
|
||||||
format: uuid
|
|
||||||
name:
|
|
||||||
type: string
|
|
||||||
balance:
|
|
||||||
type: number
|
|
||||||
required:
|
|
||||||
- balance
|
|
||||||
- ledgerId
|
|
||||||
- name
|
|
||||||
UpdateCombinedLedgerRequest:
|
|
||||||
type: object
|
|
||||||
properties:
|
|
||||||
name:
|
|
||||||
type: string
|
|
||||||
memberLedgerIds:
|
|
||||||
type: array
|
|
||||||
items:
|
|
||||||
type: string
|
|
||||||
format: uuid
|
|
||||||
required:
|
|
||||||
- memberLedgerIds
|
|
||||||
- name
|
|
||||||
CombinedLedgerResponse:
|
|
||||||
allOf:
|
|
||||||
- $ref: "#/components/schemas/LedgerResponse"
|
|
||||||
- type: object
|
|
||||||
properties:
|
|
||||||
ledgerId:
|
|
||||||
type: string
|
|
||||||
format: uuid
|
|
||||||
name:
|
|
||||||
type: string
|
|
||||||
balance:
|
|
||||||
type: number
|
|
||||||
memberLedgerIds:
|
|
||||||
type: array
|
|
||||||
items:
|
|
||||||
type: string
|
|
||||||
format: uuid
|
|
||||||
required:
|
|
||||||
- balance
|
|
||||||
- ledgerId
|
|
||||||
- memberLedgerIds
|
|
||||||
- name
|
|
||||||
SetCharacterRuleBookRequest:
|
|
||||||
type: object
|
|
||||||
properties:
|
|
||||||
ruleBookId:
|
|
||||||
type: string
|
|
||||||
format: uuid
|
|
||||||
bindings:
|
|
||||||
type: object
|
|
||||||
additionalProperties:
|
|
||||||
type: string
|
|
||||||
format: uuid
|
|
||||||
required:
|
|
||||||
- bindings
|
|
||||||
- ruleBookId
|
|
||||||
CharacterRuleBookResponse:
|
|
||||||
type: object
|
|
||||||
properties:
|
|
||||||
characterId:
|
|
||||||
type: integer
|
|
||||||
format: int64
|
|
||||||
ruleBookId:
|
|
||||||
type: string
|
|
||||||
format: uuid
|
|
||||||
bindings:
|
|
||||||
type: object
|
|
||||||
additionalProperties:
|
|
||||||
type: string
|
|
||||||
format: uuid
|
|
||||||
required:
|
|
||||||
- bindings
|
|
||||||
- characterId
|
|
||||||
- ruleBookId
|
|
||||||
CreateRuleBookRequest:
|
|
||||||
type: object
|
|
||||||
properties:
|
|
||||||
name:
|
|
||||||
type: string
|
|
||||||
usedForAcquisitions:
|
|
||||||
type: boolean
|
|
||||||
ledgerRefs:
|
|
||||||
type: array
|
|
||||||
items:
|
|
||||||
type: string
|
|
||||||
pattern: "[a-z][a-zA-Z0-9]*"
|
|
||||||
script:
|
|
||||||
type: string
|
|
||||||
required:
|
|
||||||
- ledgerRefs
|
|
||||||
- name
|
|
||||||
- script
|
|
||||||
- usedForAcquisitions
|
|
||||||
CreateMainLedgerRequest:
|
|
||||||
type: object
|
|
||||||
properties:
|
|
||||||
name:
|
|
||||||
type: string
|
|
||||||
required:
|
|
||||||
- name
|
|
||||||
CreateCombinedLedgerRequest:
|
|
||||||
type: object
|
|
||||||
properties:
|
|
||||||
name:
|
|
||||||
type: string
|
|
||||||
memberLedgerIds:
|
|
||||||
type: array
|
|
||||||
items:
|
|
||||||
type: string
|
|
||||||
format: uuid
|
|
||||||
required:
|
|
||||||
- memberLedgerIds
|
|
||||||
- name
|
|
||||||
LedgerResponse:
|
|
||||||
discriminator:
|
|
||||||
propertyName: type
|
|
||||||
mapping:
|
|
||||||
MAIN: "#/components/schemas/MainLedgerResponse"
|
|
||||||
COMBINED: "#/components/schemas/CombinedLedgerResponse"
|
|
||||||
oneOf:
|
|
||||||
- $ref: "#/components/schemas/MainLedgerResponse"
|
|
||||||
- $ref: "#/components/schemas/CombinedLedgerResponse"
|
|
||||||
properties:
|
|
||||||
type:
|
|
||||||
type: string
|
|
||||||
required:
|
|
||||||
- type
|
|
||||||
IskTransferResponse:
|
|
||||||
allOf:
|
|
||||||
- $ref: "#/components/schemas/TransferResponse"
|
|
||||||
- type: object
|
|
||||||
properties:
|
|
||||||
fromLedgerId:
|
|
||||||
type: string
|
|
||||||
format: uuid
|
|
||||||
toLedgerId:
|
|
||||||
type: string
|
|
||||||
format: uuid
|
|
||||||
amount:
|
|
||||||
type: number
|
|
||||||
required:
|
|
||||||
- amount
|
|
||||||
- fromLedgerId
|
|
||||||
- toLedgerId
|
|
||||||
ItemTransferResponse:
|
|
||||||
allOf:
|
|
||||||
- $ref: "#/components/schemas/TransferResponse"
|
|
||||||
- type: object
|
|
||||||
properties:
|
|
||||||
fromLedgerId:
|
|
||||||
type: string
|
|
||||||
format: uuid
|
|
||||||
toLedgerId:
|
|
||||||
type: string
|
|
||||||
format: uuid
|
|
||||||
marketTypeId:
|
|
||||||
type: integer
|
|
||||||
format: int64
|
|
||||||
quantity:
|
|
||||||
type: integer
|
|
||||||
format: int64
|
|
||||||
required:
|
|
||||||
- fromLedgerId
|
|
||||||
- marketTypeId
|
|
||||||
- quantity
|
|
||||||
- toLedgerId
|
|
||||||
TransactionResponse:
|
|
||||||
type: object
|
|
||||||
properties:
|
|
||||||
transactionId:
|
|
||||||
type: string
|
|
||||||
format: uuid
|
|
||||||
characterId:
|
|
||||||
type: integer
|
|
||||||
format: int64
|
|
||||||
datetime:
|
|
||||||
type: string
|
|
||||||
format: date-time
|
|
||||||
description:
|
|
||||||
type: string
|
|
||||||
transfers:
|
|
||||||
type: array
|
|
||||||
items:
|
|
||||||
$ref: "#/components/schemas/TransferResponse"
|
|
||||||
required:
|
|
||||||
- characterId
|
|
||||||
- datetime
|
|
||||||
- description
|
|
||||||
- transactionId
|
|
||||||
- transfers
|
|
||||||
TransferResponse:
|
|
||||||
discriminator:
|
|
||||||
propertyName: type
|
|
||||||
mapping:
|
|
||||||
ISK: "#/components/schemas/IskTransferResponse"
|
|
||||||
ITEM: "#/components/schemas/ItemTransferResponse"
|
|
||||||
oneOf:
|
|
||||||
- $ref: "#/components/schemas/IskTransferResponse"
|
|
||||||
- $ref: "#/components/schemas/ItemTransferResponse"
|
|
||||||
properties:
|
|
||||||
type:
|
|
||||||
type: string
|
|
||||||
required:
|
|
||||||
- type
|
|
||||||
BalanceResponse:
|
|
||||||
type: object
|
|
||||||
properties:
|
|
||||||
iskBalance:
|
|
||||||
type: number
|
|
||||||
itemBalances:
|
|
||||||
type: array
|
|
||||||
items:
|
|
||||||
$ref: "#/components/schemas/ItemBalanceResponse"
|
|
||||||
required:
|
|
||||||
- iskBalance
|
|
||||||
- itemBalances
|
|
||||||
ItemBalanceResponse:
|
|
||||||
type: object
|
|
||||||
properties:
|
|
||||||
typeId:
|
|
||||||
type: integer
|
|
||||||
format: int64
|
|
||||||
quantity:
|
|
||||||
type: integer
|
|
||||||
format: int64
|
|
||||||
required:
|
|
||||||
- quantity
|
|
||||||
- typeId
|
|
||||||
CharacterResponse:
|
|
||||||
type: object
|
|
||||||
properties:
|
|
||||||
characterId:
|
|
||||||
type: integer
|
|
||||||
format: int64
|
|
||||||
name:
|
|
||||||
type: string
|
|
||||||
required:
|
|
||||||
- characterId
|
|
||||||
- name
|
|
||||||
AcquisitionResponse:
|
|
||||||
type: object
|
|
||||||
properties:
|
|
||||||
acquisitionId:
|
|
||||||
type: string
|
|
||||||
format: uuid
|
|
||||||
characterId:
|
|
||||||
type: integer
|
|
||||||
format: int64
|
|
||||||
marketTypeId:
|
|
||||||
type: integer
|
|
||||||
format: int64
|
|
||||||
source:
|
|
||||||
type: string
|
|
||||||
enum:
|
|
||||||
- BOUGHT
|
|
||||||
- MANUAL
|
|
||||||
datetime:
|
|
||||||
type: string
|
|
||||||
format: date-time
|
|
||||||
quantity:
|
|
||||||
type: integer
|
|
||||||
format: int64
|
|
||||||
remaining:
|
|
||||||
type: integer
|
|
||||||
format: int64
|
|
||||||
unitCost:
|
|
||||||
type: number
|
|
||||||
required:
|
|
||||||
- acquisitionId
|
|
||||||
- characterId
|
|
||||||
- datetime
|
|
||||||
- marketTypeId
|
|
||||||
- quantity
|
|
||||||
- remaining
|
|
||||||
- source
|
|
||||||
- unitCost
|
|
||||||
Generated
+2764
-2211
File diff suppressed because it is too large
Load Diff
+16
-18
@@ -11,30 +11,28 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@heroicons/vue": "^2.0.18",
|
"@heroicons/vue": "^2.0.18",
|
||||||
"@vueuse/components": "^14.3.0",
|
"@vueuse/components": "^10.5.0",
|
||||||
"@vueuse/core": "^14.3.0",
|
"@vueuse/core": "^10.2.1",
|
||||||
"@vueuse/integrations": "^14.3.0",
|
"@vueuse/integrations": "^10.2.1",
|
||||||
"@vueuse/router": "^14.3.0",
|
|
||||||
"axios": "^1.4.0",
|
"axios": "^1.4.0",
|
||||||
"axios-rate-limit": "^1.3.1",
|
"axios-rate-limit": "^1.3.1",
|
||||||
"gemory": "file:",
|
|
||||||
"loglevel": "^1.8.1",
|
"loglevel": "^1.8.1",
|
||||||
"loglevel-plugin-prefix": "^0.8.4",
|
"loglevel-plugin-prefix": "^0.8.4",
|
||||||
"monaco-editor": "^0.55.1",
|
"oidc-client-ts": "^3.0.1",
|
||||||
"pinia": "^3.0.4",
|
"pinia": "^2.1.6",
|
||||||
"sortablejs": "^1.15.7",
|
|
||||||
"vue": "^3.3.4",
|
"vue": "^3.3.4",
|
||||||
"vue-router": "^5.0.7"
|
"vue-router": "^4.2.4"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@tailwindcss/vite": "^4.3.0",
|
"@types/node": "^20.4.5",
|
||||||
"@types/node": "^25.8.0",
|
"@vitejs/plugin-vue": "^5.0.4",
|
||||||
"@vitejs/plugin-vue": "^6.0.7",
|
"autoprefixer": "^10.4.14",
|
||||||
"tailwindcss": "^4.3.0",
|
"postcss": "^8.4.27",
|
||||||
"typescript": "^6.0.3",
|
"tailwindcss": "^3.3.3",
|
||||||
"vite": "^8.0.13",
|
"typescript": "^5.0.2",
|
||||||
"vite-plugin-runtime-env": "^1.0.0",
|
"vite": "^5.2.11",
|
||||||
"vitest": "^4.1.6",
|
"vite-plugin-runtime-env": "^0.1.1",
|
||||||
"vue-tsc": "^3.2.9"
|
"vitest": "^1.6.0",
|
||||||
|
"vue-tsc": "^2.0.18"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,8 @@
|
|||||||
|
export default {
|
||||||
|
plugins: {
|
||||||
|
'postcss-import': {},
|
||||||
|
'tailwindcss/nesting': {},
|
||||||
|
tailwindcss: {},
|
||||||
|
autoprefixer: {},
|
||||||
|
},
|
||||||
|
}
|
||||||
+7
-10
@@ -1,14 +1,14 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import {computed} from 'vue';
|
import { useAuthStore } from '@/auth';
|
||||||
import {RouterView, useRoute} from 'vue-router';
|
import { computed } from 'vue';
|
||||||
import {Sidebar} from './sidebar';
|
import { RouterView, useRoute } from 'vue-router';
|
||||||
import {ConfirmModal} from '@/confirm';
|
import { Sidebar } from './sidebar';
|
||||||
import {routeNames} from '@/routes';
|
|
||||||
|
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
|
const authStore = useAuthStore();
|
||||||
|
|
||||||
const hideSidebar = computed(() => {
|
const hideSidebar = computed(() => {
|
||||||
return route.name === routeNames.callback || route.name === routeNames.about;
|
return !authStore.isLoggedIn || route.name === 'callback' || route.name === 'about';
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -22,12 +22,9 @@ const hideSidebar = computed(() => {
|
|||||||
<RouterView />
|
<RouterView />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<ConfirmModal />
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped lang="postcss">
|
||||||
@reference "@/style.css";
|
|
||||||
|
|
||||||
div.main-container {
|
div.main-container {
|
||||||
@apply px-4 sm:ml-64;
|
@apply px-4 sm:ml-64;
|
||||||
}
|
}
|
||||||
|
|||||||
+49
@@ -0,0 +1,49 @@
|
|||||||
|
import log from "loglevel";
|
||||||
|
import { Log, User, UserManager, WebStorageStateStore } from "oidc-client-ts";
|
||||||
|
import { defineStore } from "pinia";
|
||||||
|
import { computed, ref } from "vue";
|
||||||
|
|
||||||
|
Log.setLogger(log);
|
||||||
|
|
||||||
|
export const useAuthStore = defineStore('auth', () => {
|
||||||
|
const userManager = new UserManager({
|
||||||
|
authority: import.meta.env.VITE_AUTH_AUTHORITY,
|
||||||
|
client_id: import.meta.env.VITE_AUTH_CLIENT_ID,
|
||||||
|
client_secret: import.meta.env.VITE_AUTH_CLIENT_SECRET,
|
||||||
|
redirect_uri: import.meta.env.VITE_AUTH_REDIRECT_URI,
|
||||||
|
scope: import.meta.env.VITE_AUTH_SCOPE,
|
||||||
|
stateStore: new WebStorageStateStore({ store: window.localStorage }),
|
||||||
|
userStore: new WebStorageStateStore({ store: window.localStorage })
|
||||||
|
});
|
||||||
|
|
||||||
|
const user = ref<User>();
|
||||||
|
const isLoggedIn = computed(() => !!user.value);
|
||||||
|
const accessToken = computed(() => user.value?.access_token);
|
||||||
|
const username = computed(() => user.value?.profile.name ?? "");
|
||||||
|
const userId = computed(() => user.value?.profile.sub ?? "");
|
||||||
|
|
||||||
|
const redirect = async () => {
|
||||||
|
await userManager.signinRedirect();
|
||||||
|
log.info("Redirecting to login page");
|
||||||
|
}
|
||||||
|
const login = async () => {
|
||||||
|
await userManager.signinCallback();
|
||||||
|
log.debug("Logged in");
|
||||||
|
}
|
||||||
|
const logout = async () => {
|
||||||
|
await userManager.signoutRedirect();
|
||||||
|
log.debug("Logged out");
|
||||||
|
}
|
||||||
|
|
||||||
|
const setUser = (u?: User | null) => {
|
||||||
|
if (u) {
|
||||||
|
user.value = u;
|
||||||
|
log.debug("User loaded", u.profile.name);
|
||||||
|
} else {
|
||||||
|
user.value = undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
userManager.events.addUserLoaded(setUser);
|
||||||
|
userManager.getUser().then(setUser);
|
||||||
|
return { redirect, login, logout, isLoggedIn, accessToken, username, userId };
|
||||||
|
});
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
<script setup lang="ts">
|
|
||||||
|
|
||||||
import {Character} from "./chartacters.ts";
|
|
||||||
|
|
||||||
interface Props {
|
|
||||||
character: Character;
|
|
||||||
size?: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
const props = withDefaults(defineProps<Props>(), {
|
|
||||||
size: 32
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<div class="flex">
|
|
||||||
<img class="me-2" :src="`https://images.evetech.net/characters/${character.characterId}/portrait?size=${size}`" />
|
|
||||||
<span>{{ character.name }}</span>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
import {activityApi, characterApi} from "@/mammon";
|
|
||||||
import {defineStore} from "pinia";
|
|
||||||
import {ref} from "vue";
|
|
||||||
import {CharacterResponse} from "@/generated/mammon";
|
|
||||||
|
|
||||||
export type Character = CharacterResponse
|
|
||||||
|
|
||||||
export const useCharactersStore = defineStore('characters', () => {
|
|
||||||
const characters = ref<Character[]>([]);
|
|
||||||
|
|
||||||
const findById = async (characterId: number): Promise<Character | undefined> => {
|
|
||||||
let character = characters.value.find(c => c.characterId === characterId);
|
|
||||||
|
|
||||||
if (!character) {
|
|
||||||
await refresh(); // TODO call api instead of refresh
|
|
||||||
character = characters.value.find(c => c.characterId === characterId);
|
|
||||||
}
|
|
||||||
return character;
|
|
||||||
}
|
|
||||||
|
|
||||||
const reloadActivities = (characterId: number): Promise<void> => activityApi.fetchNewActivitiesForCharacter(characterId) as Promise<void>;
|
|
||||||
|
|
||||||
const refresh = () => characterApi.findAllCharacters().then(response => characters.value = response.data);
|
|
||||||
|
|
||||||
refresh();
|
|
||||||
|
|
||||||
return {characters, findById, reloadActivities, refresh};
|
|
||||||
})
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
export * from './chartacters.ts'
|
|
||||||
|
|
||||||
export {default as CharacterLabel} from './CharacterLabel.vue';
|
|
||||||
+29
-66
@@ -1,96 +1,59 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import {ChevronDownIcon, ChevronUpIcon} from '@heroicons/vue/24/outline';
|
import { ChevronDownIcon, ChevronUpIcon } from '@heroicons/vue/24/outline';
|
||||||
import {vOnClickOutside} from '@vueuse/components';
|
import { vOnClickOutside } from '@vueuse/components';
|
||||||
import {useElementBounding, useEventListener} from '@vueuse/core';
|
import { useEventListener } from '@vueuse/core';
|
||||||
import {computed, ref} from 'vue';
|
import { ref } from 'vue';
|
||||||
|
|
||||||
interface Props {
|
|
||||||
inline?: boolean;
|
|
||||||
autoClose?: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
const props = withDefaults(defineProps<Props>(), {
|
|
||||||
inline: false,
|
|
||||||
autoClose: true
|
|
||||||
})
|
|
||||||
|
|
||||||
const isOpen = ref(false);
|
const isOpen = ref(false);
|
||||||
const root = ref<HTMLElement | null>(null);
|
|
||||||
const floating = ref<HTMLElement | null>(null);
|
|
||||||
|
|
||||||
const { left, bottom, width } = useElementBounding(root);
|
|
||||||
|
|
||||||
const floatingStyle = computed(() => ({
|
|
||||||
left: `${left.value}px`,
|
|
||||||
top: `${bottom.value}px`,
|
|
||||||
minWidth: `${width.value}px`,
|
|
||||||
}));
|
|
||||||
|
|
||||||
const doAutoClose = () => {
|
|
||||||
if (props.autoClose) {
|
|
||||||
isOpen.value = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
useEventListener('keyup', e => {
|
useEventListener('keyup', e => {
|
||||||
if (e.key === 'Escape') {
|
if (e.key === 'Escape') {
|
||||||
doAutoClose();
|
isOpen.value = false;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div ref="root" class="dropdown" :class="{'dropdown-open': isOpen, 'dropdown-close': !isOpen}" v-on-click-outside="[doAutoClose, { ignore: [floating] }]">
|
<div class="dropdown" :class="{'dropdown-open': isOpen, 'dropdown-close': !isOpen}" v-on-click-outside="() => isOpen = false">
|
||||||
<button @click="isOpen = !isOpen" class="cursor-pointer">
|
<button @click="isOpen = !isOpen">
|
||||||
<Transition
|
<slot name="button" />
|
||||||
enter-active-class="transition-transform"
|
<Transition name="flip">
|
||||||
enter-from-class="rotate-180"
|
|
||||||
leave-active-class="hidden"
|
|
||||||
leave-to-class="rotate-180">
|
|
||||||
<ChevronDownIcon v-if="!isOpen" class="chevron" />
|
<ChevronDownIcon v-if="!isOpen" class="chevron" />
|
||||||
<ChevronUpIcon v-else class="chevron" />
|
<ChevronUpIcon v-else class="chevron" />
|
||||||
</Transition>
|
</Transition>
|
||||||
<slot name="button" />
|
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<Transition
|
<Transition name="fade">
|
||||||
enter-active-class="transition-opacity"
|
<div v-if="isOpen" class="relative">
|
||||||
enter-from-class="opacity-0"
|
<div class="z-10 divide-y rounded-b-md absolute">
|
||||||
leave-from-class="transition-opacity"
|
|
||||||
leave-to-class="opacity-0">
|
|
||||||
<div v-if="inline && isOpen">
|
|
||||||
<slot />
|
|
||||||
</div>
|
|
||||||
</Transition>
|
|
||||||
|
|
||||||
<Teleport to="body">
|
|
||||||
<Transition
|
|
||||||
enter-active-class="transition-opacity"
|
|
||||||
enter-from-class="opacity-0"
|
|
||||||
leave-from-class="transition-opacity"
|
|
||||||
leave-to-class="opacity-0">
|
|
||||||
<div v-if="!inline && isOpen" ref="floating" class="dropdown-floating" :style="floatingStyle">
|
|
||||||
<div class="divide-y rounded-b-md">
|
|
||||||
<slot />
|
<slot />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Transition>
|
</Transition>
|
||||||
</Teleport>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped lang="postcss">
|
||||||
@reference "@/style.css";
|
|
||||||
|
|
||||||
.chevron {
|
.chevron {
|
||||||
@apply w-4 h-4 me-1;
|
@apply w-4 h-4 ms-1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dropdown-floating {
|
.fade-enter-from, .fade-leave-to {
|
||||||
@apply fixed z-10;
|
opacity: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dropdown-floating > div {
|
.fade-enter-active, .fade-leave-active {
|
||||||
@apply bg-slate-800 rounded-b-md shadow-lg;
|
@apply transition-opacity;
|
||||||
|
}
|
||||||
|
|
||||||
|
.flip-enter-from, .flip-leave-to {
|
||||||
|
transform: rotate(180deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.flip-enter-active {
|
||||||
|
@apply transition-transform;
|
||||||
|
}
|
||||||
|
.flip-leave-active {
|
||||||
|
display: none;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import {vOnClickOutside} from '@vueuse/components';
|
import { vOnClickOutside } from '@vueuse/components';
|
||||||
import {useEventListener} from '@vueuse/core';
|
import { useEventListener } from '@vueuse/core';
|
||||||
import {watch} from 'vue';
|
import { watch } from 'vue';
|
||||||
|
|
||||||
const open = defineModel('open', { default: false });
|
const open = defineModel('open', { default: false });
|
||||||
|
|
||||||
@@ -34,8 +34,7 @@ useEventListener('keyup', e => {
|
|||||||
</Transition>
|
</Transition>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped lang="postcss">
|
||||||
@reference "@/style.css";
|
|
||||||
.fade-enter-from, .fade-leave-to {
|
.fade-enter-from, .fade-leave-to {
|
||||||
@apply opacity-0;
|
@apply opacity-0;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,19 +0,0 @@
|
|||||||
<script setup lang="ts">
|
|
||||||
import { computed } from 'vue';
|
|
||||||
|
|
||||||
|
|
||||||
interface Props {
|
|
||||||
value: number;
|
|
||||||
total: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
const props = defineProps<Props>();
|
|
||||||
|
|
||||||
const percentage = computed(() => (props.value / props.total) * 100);
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<div class="w-full bg-gray-600 rounded-full h-2.5">
|
|
||||||
<div class="bg-emerald-600 h-2.5 rounded-full" :style="{ width: percentage + '%'}" />
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
@@ -1,71 +0,0 @@
|
|||||||
<script setup lang="ts" generic="T">
|
|
||||||
import {vOnClickOutside} from '@vueuse/components';
|
|
||||||
import {useVirtualList} from '@vueuse/core';
|
|
||||||
import {computed, ref} from 'vue';
|
|
||||||
|
|
||||||
interface Props {
|
|
||||||
items: T[];
|
|
||||||
itemHeight?: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
const props = withDefaults(defineProps<Props>(), {
|
|
||||||
itemHeight: 24,
|
|
||||||
});
|
|
||||||
|
|
||||||
const modelValue = defineModel<T>();
|
|
||||||
|
|
||||||
const isOpen = ref(false);
|
|
||||||
const currentIndex = ref(-1);
|
|
||||||
const {list, scrollTo, containerProps, wrapperProps} = useVirtualList(computed(() => props.items), {
|
|
||||||
itemHeight: () => props.itemHeight,
|
|
||||||
overscan: 3,
|
|
||||||
});
|
|
||||||
|
|
||||||
const moveDown = () => {
|
|
||||||
currentIndex.value = currentIndex.value >= props.items.length - 1 ? 0 : currentIndex.value + 1;
|
|
||||||
scrollTo(currentIndex.value);
|
|
||||||
};
|
|
||||||
const moveUp = () => {
|
|
||||||
currentIndex.value = currentIndex.value <= 0 ? props.items.length - 1 : currentIndex.value - 1;
|
|
||||||
scrollTo(currentIndex.value);
|
|
||||||
};
|
|
||||||
const select = (item?: T) => {
|
|
||||||
modelValue.value = item;
|
|
||||||
currentIndex.value = -1;
|
|
||||||
isOpen.value = false;
|
|
||||||
};
|
|
||||||
const submit = () => {
|
|
||||||
if (currentIndex.value >= 0 && currentIndex.value < props.items.length) {
|
|
||||||
select(props.items[currentIndex.value]);
|
|
||||||
} else if (modelValue.value === undefined && props.items.length > 0) {
|
|
||||||
select(props.items[0]);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<div @click="isOpen = true" v-on-click-outside="() => isOpen = false">
|
|
||||||
<div class="fake-input" @keyup.enter="submit" @keyup.down="moveDown" @keyup.up="moveUp">
|
|
||||||
<slot name="input" :value="modelValue" />
|
|
||||||
</div>
|
|
||||||
<div v-if="isOpen && items.length" class="z-20 absolute">
|
|
||||||
<div v-bind="containerProps" class="rounded-b" style="height: 300px">
|
|
||||||
<div v-bind="wrapperProps">
|
|
||||||
<div v-for="s in list" :key="s.index"
|
|
||||||
class="hover:bg-slate-700 cursor-pointer"
|
|
||||||
:class="s.index === currentIndex ? 'bg-emerald-500' : 'bg-slate-500'"
|
|
||||||
@click.stop="select(s.data)">
|
|
||||||
<slot name="item" :item="s.data" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
@reference "@/style.css";
|
|
||||||
.fake-input {
|
|
||||||
@apply flex border bg-slate-500 rounded px-1 py-0.5;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -10,10 +10,8 @@ const modelValue = defineModel({ default: false });
|
|||||||
</label>
|
</label>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped lang="postcss">
|
||||||
@reference "@/style.css";
|
input:checked ~ span:last-child {
|
||||||
|
--tw-translate-x: 1.25rem;
|
||||||
input:checked ~ span:last-child {
|
}
|
||||||
transform: translateX(1.25rem);
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
@@ -2,8 +2,6 @@ export { default as ClipboardButton } from './ClipboardButton.vue';
|
|||||||
export { default as Dropdown } from './Dropdown.vue';
|
export { default as Dropdown } from './Dropdown.vue';
|
||||||
export { default as LoadingSpinner } from './LoadingSpinner.vue';
|
export { default as LoadingSpinner } from './LoadingSpinner.vue';
|
||||||
export { default as Modal } from './Modal.vue';
|
export { default as Modal } from './Modal.vue';
|
||||||
export { default as ProgressBar } from './ProgressBar.vue';
|
|
||||||
export { default as SelectInput } from './SelectInput.vue';
|
|
||||||
export { default as SliderCheckbox } from './SliderCheckbox.vue';
|
export { default as SliderCheckbox } from './SliderCheckbox.vue';
|
||||||
export { default as Tooltip } from './tooltip/Tooltip.vue';
|
export { default as Tooltip } from './tooltip/Tooltip.vue';
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import {HeaderComponent, SortDirection} from './sort';
|
import { HeaderComponent, SortDirection } from './sort';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
currentSortKey: string | null;
|
currentSortKey: string | null;
|
||||||
@@ -32,8 +32,7 @@ const emit = defineEmits<Emit>();
|
|||||||
</component>
|
</component>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped lang="postcss">
|
||||||
@reference "@/style.css";
|
|
||||||
.sort-header {
|
.sort-header {
|
||||||
@apply relative h-8 pe-3;
|
@apply relative h-8 pe-3;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,13 +1,12 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import {useElementBounding, useVirtualList} from '@vueuse/core';
|
import { useElementBounding, useVirtualList } from '@vueuse/core';
|
||||||
import {computed, ref} from 'vue';
|
import { computed, ref } from 'vue';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
list?: any[];
|
list?: any[];
|
||||||
itemHeight: number;
|
itemHeight: number;
|
||||||
headerHeight?: number;
|
headerHeight?: number;
|
||||||
footerHeight?: number;
|
bottom?: string;
|
||||||
bottom?: string; // FIXME: use css variable
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -36,16 +35,11 @@ const computedHeaderHeight = computed(() => {
|
|||||||
|
|
||||||
return h + 'px';
|
return h + 'px';
|
||||||
})
|
})
|
||||||
const computedFooterHeight = computed(() => {
|
|
||||||
const h = props.footerHeight ?? 0;
|
|
||||||
|
|
||||||
return h + 'px';
|
|
||||||
})
|
|
||||||
const computedWrapperProps = computed(() => ({
|
const computedWrapperProps = computed(() => ({
|
||||||
...wrapperProps.value,
|
...wrapperProps.value,
|
||||||
style: {
|
style: {
|
||||||
...wrapperProps.value.style,
|
...wrapperProps.value.style,
|
||||||
height: `calc(${wrapperProps.value.style.height} + ${computedHeaderHeight.value} + ${computedFooterHeight.value} + 1px)`
|
height: `calc(${wrapperProps.value.style.height} + ${computedHeaderHeight.value} + 1px)`
|
||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
const itemHeightStyle = computed(() => {
|
const itemHeightStyle = computed(() => {
|
||||||
@@ -67,32 +61,24 @@ const itemHeightStyle = computed(() => {
|
|||||||
<slot v-else name="empty" />
|
<slot v-else name="empty" />
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped lang="postcss">
|
||||||
@reference "@/style.css";
|
|
||||||
|
|
||||||
div.table-container {
|
div.table-container {
|
||||||
@apply bg-slate-600;
|
@apply bg-slate-600;
|
||||||
max-height: calc(100vh - v-bind(ypx));
|
max-height: calc(100vh - v-bind(ypx));
|
||||||
|
:deep(>div) {
|
||||||
&::-webkit-scrollbar-track {
|
|
||||||
margin-top: v-bind(computedHeaderHeight);
|
|
||||||
margin-bottom: v-bind(computedFooterHeight);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
div.table-container:deep(>div) {
|
|
||||||
@apply bg-slate-800;
|
@apply bg-slate-800;
|
||||||
>table {
|
>table {
|
||||||
>thead {
|
>thead {
|
||||||
@apply sticky z-10;
|
@apply sticky z-10;
|
||||||
top: -1px;
|
top: -1px;
|
||||||
}
|
}
|
||||||
>tfoot {
|
|
||||||
@apply bg-slate-600 sticky z-10;
|
|
||||||
bottom: -1px;
|
|
||||||
}
|
|
||||||
>*>tr, >*>tr>td {
|
>*>tr, >*>tr>td {
|
||||||
height: v-bind(itemHeightStyle);
|
height: v-bind(itemHeightStyle);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
&::-webkit-scrollbar-track {
|
||||||
|
margin-top: v-bind(computedHeaderHeight);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import {vElementHover} from '@vueuse/components';
|
import { vElementHover } from '@vueuse/components';
|
||||||
import {useElementBounding} from '@vueuse/core';
|
import { useElementBounding } from '@vueuse/core';
|
||||||
import {computed, ref} from 'vue';
|
import { computed, ref } from 'vue';
|
||||||
import {useSharedWindowSize} from './tooltip';
|
import { useSharedWindowSize } from './tooltip';
|
||||||
|
|
||||||
const open = defineModel('open', { default: false });
|
const open = defineModel('open', { default: false });
|
||||||
|
|
||||||
@@ -25,7 +25,7 @@ const positions = computed(() => {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div ref="mainDiv" class="flex flex-col items-center justify-center" :class="{
|
<div ref="mainDiv" clas="flex flex-col items-center justify-center" :class="{
|
||||||
'open': open,
|
'open': open,
|
||||||
'tooltip-top': positions.includes('top'),
|
'tooltip-top': positions.includes('top'),
|
||||||
'tooltip-bottom': positions.includes('bottom'),
|
'tooltip-bottom': positions.includes('bottom'),
|
||||||
|
|||||||
@@ -1,45 +0,0 @@
|
|||||||
<script setup lang="ts">
|
|
||||||
import {computed} from "vue";
|
|
||||||
import {storeToRefs} from "pinia";
|
|
||||||
import {Modal} from "@/components";
|
|
||||||
import {useConfirmStore} from "./useConfirm";
|
|
||||||
|
|
||||||
const confirmStore = useConfirmStore();
|
|
||||||
const {open, options} = storeToRefs(confirmStore);
|
|
||||||
const {accept, cancel} = confirmStore;
|
|
||||||
|
|
||||||
const modalOpen = computed({
|
|
||||||
get: () => open.value,
|
|
||||||
set: value => {
|
|
||||||
if (!value) {
|
|
||||||
cancel();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<Modal v-model:open="modalOpen">
|
|
||||||
<div class="bg-slate-800 rounded pb-4 w-96">
|
|
||||||
<span class="m-2">{{ options.title ?? "Confirm" }}</span>
|
|
||||||
<hr />
|
|
||||||
<div class="m-4">{{ options.message }}</div>
|
|
||||||
<div class="flex justify-end">
|
|
||||||
<button class="me-2" @click="cancel">{{ options.cancelLabel ?? "Cancel" }}</button>
|
|
||||||
<button class="confirm me-4" :class="options.danger ? 'danger' : ''" @click="accept">{{ options.confirmLabel ?? "Confirm" }}</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</Modal>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
@reference "@/style.css";
|
|
||||||
|
|
||||||
button.confirm {
|
|
||||||
@apply border-emerald-500 bg-emerald-500 hover:bg-emerald-600;
|
|
||||||
|
|
||||||
&.danger {
|
|
||||||
@apply border-amber-900 bg-amber-900 hover:bg-amber-800;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
export { default as ConfirmModal } from './ConfirmModal.vue';
|
|
||||||
export { confirm, useConfirmStore } from './useConfirm';
|
|
||||||
export type { ConfirmOptions } from './useConfirm';
|
|
||||||
@@ -1,37 +0,0 @@
|
|||||||
import {defineStore} from "pinia";
|
|
||||||
import {ref} from "vue";
|
|
||||||
|
|
||||||
export interface ConfirmOptions {
|
|
||||||
title?: string;
|
|
||||||
message: string;
|
|
||||||
confirmLabel?: string;
|
|
||||||
cancelLabel?: string;
|
|
||||||
danger?: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const useConfirmStore = defineStore('confirm', () => {
|
|
||||||
const open = ref(false);
|
|
||||||
const options = ref<ConfirmOptions>({message: ""});
|
|
||||||
let resolver: ((value: boolean) => void) | undefined;
|
|
||||||
|
|
||||||
const settle = (value: boolean) => {
|
|
||||||
open.value = false;
|
|
||||||
resolver?.(value);
|
|
||||||
resolver = undefined;
|
|
||||||
};
|
|
||||||
|
|
||||||
const confirm = (opts: ConfirmOptions | string): Promise<boolean> => {
|
|
||||||
options.value = typeof opts === "string" ? {message: opts} : opts;
|
|
||||||
open.value = true;
|
|
||||||
return new Promise<boolean>(resolve => {
|
|
||||||
resolver = resolve;
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const accept = () => settle(true);
|
|
||||||
const cancel = () => settle(false);
|
|
||||||
|
|
||||||
return {open, options, confirm, accept, cancel};
|
|
||||||
});
|
|
||||||
|
|
||||||
export const confirm = (opts: ConfirmOptions | string): Promise<boolean> => useConfirmStore().confirm(opts);
|
|
||||||
@@ -11,6 +11,7 @@ export const percentFormater = new Intl.NumberFormat("en-US", {
|
|||||||
maximumFractionDigits: 0
|
maximumFractionDigits: 0
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
const timeFormat = new Intl.NumberFormat("en-US", {
|
const timeFormat = new Intl.NumberFormat("en-US", {
|
||||||
minimumFractionDigits: 0,
|
minimumFractionDigits: 0,
|
||||||
maximumFractionDigits: 0,
|
maximumFractionDigits: 0,
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -1,62 +0,0 @@
|
|||||||
/* tslint:disable */
|
|
||||||
/* eslint-disable */
|
|
||||||
/**
|
|
||||||
* OpenAPI definition
|
|
||||||
* No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)
|
|
||||||
*
|
|
||||||
* The version of the OpenAPI document: v0
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
|
|
||||||
* https://openapi-generator.tech
|
|
||||||
* Do not edit the class manually.
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
|
||||||
import type { Configuration } from './configuration';
|
|
||||||
// Some imports not used depending on template conditions
|
|
||||||
// @ts-ignore
|
|
||||||
import type { AxiosPromise, AxiosInstance, RawAxiosRequestConfig } from 'axios';
|
|
||||||
import globalAxios from 'axios';
|
|
||||||
|
|
||||||
export const BASE_PATH = "http://localhost:8080".replace(/\/+$/, "");
|
|
||||||
|
|
||||||
export const COLLECTION_FORMATS = {
|
|
||||||
csv: ",",
|
|
||||||
ssv: " ",
|
|
||||||
tsv: "\t",
|
|
||||||
pipes: "|",
|
|
||||||
};
|
|
||||||
|
|
||||||
export interface RequestArgs {
|
|
||||||
url: string;
|
|
||||||
options: RawAxiosRequestConfig;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class BaseAPI {
|
|
||||||
protected configuration: Configuration | undefined;
|
|
||||||
|
|
||||||
constructor(configuration?: Configuration, protected basePath: string = BASE_PATH, protected axios: AxiosInstance = globalAxios) {
|
|
||||||
if (configuration) {
|
|
||||||
this.configuration = configuration;
|
|
||||||
this.basePath = configuration.basePath ?? basePath;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export class RequiredError extends Error {
|
|
||||||
constructor(public field: string, msg?: string) {
|
|
||||||
super(msg);
|
|
||||||
this.name = "RequiredError"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
interface ServerMap {
|
|
||||||
[key: string]: {
|
|
||||||
url: string,
|
|
||||||
description: string,
|
|
||||||
}[];
|
|
||||||
}
|
|
||||||
|
|
||||||
export const operationServerMap: ServerMap = {
|
|
||||||
}
|
|
||||||
@@ -1,127 +0,0 @@
|
|||||||
/* tslint:disable */
|
|
||||||
/* eslint-disable */
|
|
||||||
/**
|
|
||||||
* OpenAPI definition
|
|
||||||
* No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)
|
|
||||||
*
|
|
||||||
* The version of the OpenAPI document: v0
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
|
|
||||||
* https://openapi-generator.tech
|
|
||||||
* Do not edit the class manually.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import type { Configuration } from "./configuration";
|
|
||||||
import type { RequestArgs } from "./base";
|
|
||||||
import type { AxiosInstance, AxiosResponse } from 'axios';
|
|
||||||
import { RequiredError } from "./base";
|
|
||||||
|
|
||||||
export const DUMMY_BASE_URL = 'https://example.com'
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @throws {RequiredError}
|
|
||||||
*/
|
|
||||||
export const assertParamExists = function (functionName: string, paramName: string, paramValue: unknown) {
|
|
||||||
if (paramValue === null || paramValue === undefined) {
|
|
||||||
throw new RequiredError(paramName, `Required parameter ${paramName} was null or undefined when calling ${functionName}.`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export const setApiKeyToObject = async function (object: any, keyParamName: string, configuration?: Configuration) {
|
|
||||||
if (configuration && configuration.apiKey) {
|
|
||||||
const localVarApiKeyValue = typeof configuration.apiKey === 'function'
|
|
||||||
? await configuration.apiKey(keyParamName)
|
|
||||||
: await configuration.apiKey;
|
|
||||||
object[keyParamName] = localVarApiKeyValue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export const setBasicAuthToObject = function (object: any, configuration?: Configuration) {
|
|
||||||
if (configuration && (configuration.username || configuration.password)) {
|
|
||||||
object["auth"] = { username: configuration.username, password: configuration.password };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export const setBearerAuthToObject = async function (object: any, configuration?: Configuration) {
|
|
||||||
if (configuration && configuration.accessToken) {
|
|
||||||
const accessToken = typeof configuration.accessToken === 'function'
|
|
||||||
? await configuration.accessToken()
|
|
||||||
: await configuration.accessToken;
|
|
||||||
object["Authorization"] = "Bearer " + accessToken;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export const setOAuthToObject = async function (object: any, name: string, scopes: string[], configuration?: Configuration) {
|
|
||||||
if (configuration && configuration.accessToken) {
|
|
||||||
const localVarAccessTokenValue = typeof configuration.accessToken === 'function'
|
|
||||||
? await configuration.accessToken(name, scopes)
|
|
||||||
: await configuration.accessToken;
|
|
||||||
object["Authorization"] = "Bearer " + localVarAccessTokenValue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
function setFlattenedQueryParams(urlSearchParams: URLSearchParams, parameter: any, key: string = ""): void {
|
|
||||||
if (parameter == null) return;
|
|
||||||
if (typeof parameter === "object") {
|
|
||||||
if (Array.isArray(parameter) || parameter instanceof Set) {
|
|
||||||
(parameter as any[]).forEach(item => setFlattenedQueryParams(urlSearchParams, item, key));
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
Object.keys(parameter).forEach(currentKey =>
|
|
||||||
setFlattenedQueryParams(urlSearchParams, parameter[currentKey], `${key}${key !== '' ? '.' : ''}${currentKey}`)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
if (urlSearchParams.has(key)) {
|
|
||||||
urlSearchParams.append(key, parameter);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
urlSearchParams.set(key, parameter);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export const setSearchParams = function (url: URL, ...objects: any[]) {
|
|
||||||
const searchParams = new URLSearchParams(url.search);
|
|
||||||
setFlattenedQueryParams(searchParams, objects);
|
|
||||||
url.search = searchParams.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* JSON serialization helper function which replaces instances of unserializable types with serializable ones.
|
|
||||||
* This function will run for every key-value pair encountered by JSON.stringify while traversing an object.
|
|
||||||
* Converting a set to a string will return an empty object, so an intermediate conversion to an array is required.
|
|
||||||
*/
|
|
||||||
// @ts-ignore
|
|
||||||
export const replaceWithSerializableTypeIfNeeded = function(key: string, value: any) {
|
|
||||||
if (value instanceof Set) {
|
|
||||||
return Array.from(value);
|
|
||||||
} else {
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export const serializeDataIfNeeded = function (value: any, requestOptions: any, configuration?: Configuration) {
|
|
||||||
const nonString = typeof value !== 'string';
|
|
||||||
const needsSerialization = nonString && configuration && configuration.isJsonMime
|
|
||||||
? configuration.isJsonMime(requestOptions.headers['Content-Type'])
|
|
||||||
: nonString;
|
|
||||||
return needsSerialization
|
|
||||||
? JSON.stringify(value !== undefined ? value : {}, replaceWithSerializableTypeIfNeeded)
|
|
||||||
: (value || "");
|
|
||||||
}
|
|
||||||
|
|
||||||
export const toPathString = function (url: URL) {
|
|
||||||
return url.pathname + url.search + url.hash
|
|
||||||
}
|
|
||||||
|
|
||||||
export const createRequestFunction = function (axiosArgs: RequestArgs, globalAxios: AxiosInstance, BASE_PATH: string, configuration?: Configuration) {
|
|
||||||
return <T = unknown, R = AxiosResponse<T>>(axios: AxiosInstance = globalAxios, basePath: string = BASE_PATH) => {
|
|
||||||
const axiosRequestArgs = {...axiosArgs.options, url: (axios.defaults.baseURL ? '' : configuration?.basePath ?? basePath) + axiosArgs.url};
|
|
||||||
return axios.request<T, R>(axiosRequestArgs);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@@ -1,121 +0,0 @@
|
|||||||
/* tslint:disable */
|
|
||||||
/**
|
|
||||||
* OpenAPI definition
|
|
||||||
* No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)
|
|
||||||
*
|
|
||||||
* The version of the OpenAPI document: v0
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
|
|
||||||
* https://openapi-generator.tech
|
|
||||||
* Do not edit the class manually.
|
|
||||||
*/
|
|
||||||
|
|
||||||
interface AWSv4Configuration {
|
|
||||||
options?: {
|
|
||||||
region?: string
|
|
||||||
service?: string
|
|
||||||
}
|
|
||||||
credentials?: {
|
|
||||||
accessKeyId?: string
|
|
||||||
secretAccessKey?: string,
|
|
||||||
sessionToken?: string
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ConfigurationParameters {
|
|
||||||
apiKey?: string | Promise<string> | ((name: string) => string) | ((name: string) => Promise<string>);
|
|
||||||
username?: string;
|
|
||||||
password?: string;
|
|
||||||
accessToken?: string | Promise<string> | ((name?: string, scopes?: string[]) => string) | ((name?: string, scopes?: string[]) => Promise<string>);
|
|
||||||
awsv4?: AWSv4Configuration;
|
|
||||||
basePath?: string;
|
|
||||||
serverIndex?: number;
|
|
||||||
baseOptions?: any;
|
|
||||||
formDataCtor?: new () => any;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class Configuration {
|
|
||||||
/**
|
|
||||||
* parameter for apiKey security
|
|
||||||
* @param name security name
|
|
||||||
*/
|
|
||||||
apiKey?: string | Promise<string> | ((name: string) => string) | ((name: string) => Promise<string>);
|
|
||||||
/**
|
|
||||||
* parameter for basic security
|
|
||||||
*/
|
|
||||||
username?: string;
|
|
||||||
/**
|
|
||||||
* parameter for basic security
|
|
||||||
*/
|
|
||||||
password?: string;
|
|
||||||
/**
|
|
||||||
* parameter for oauth2 security
|
|
||||||
* @param name security name
|
|
||||||
* @param scopes oauth2 scope
|
|
||||||
*/
|
|
||||||
accessToken?: string | Promise<string> | ((name?: string, scopes?: string[]) => string) | ((name?: string, scopes?: string[]) => Promise<string>);
|
|
||||||
/**
|
|
||||||
* parameter for aws4 signature security
|
|
||||||
* @param {Object} AWS4Signature - AWS4 Signature security
|
|
||||||
* @param {string} options.region - aws region
|
|
||||||
* @param {string} options.service - name of the service.
|
|
||||||
* @param {string} credentials.accessKeyId - aws access key id
|
|
||||||
* @param {string} credentials.secretAccessKey - aws access key
|
|
||||||
* @param {string} credentials.sessionToken - aws session token
|
|
||||||
* @memberof Configuration
|
|
||||||
*/
|
|
||||||
awsv4?: AWSv4Configuration;
|
|
||||||
/**
|
|
||||||
* override base path
|
|
||||||
*/
|
|
||||||
basePath?: string;
|
|
||||||
/**
|
|
||||||
* override server index
|
|
||||||
*/
|
|
||||||
serverIndex?: number;
|
|
||||||
/**
|
|
||||||
* base options for axios calls
|
|
||||||
*/
|
|
||||||
baseOptions?: any;
|
|
||||||
/**
|
|
||||||
* The FormData constructor that will be used to create multipart form data
|
|
||||||
* requests. You can inject this here so that execution environments that
|
|
||||||
* do not support the FormData class can still run the generated client.
|
|
||||||
*
|
|
||||||
* @type {new () => FormData}
|
|
||||||
*/
|
|
||||||
formDataCtor?: new () => any;
|
|
||||||
|
|
||||||
constructor(param: ConfigurationParameters = {}) {
|
|
||||||
this.apiKey = param.apiKey;
|
|
||||||
this.username = param.username;
|
|
||||||
this.password = param.password;
|
|
||||||
this.accessToken = param.accessToken;
|
|
||||||
this.awsv4 = param.awsv4;
|
|
||||||
this.basePath = param.basePath;
|
|
||||||
this.serverIndex = param.serverIndex;
|
|
||||||
this.baseOptions = {
|
|
||||||
...param.baseOptions,
|
|
||||||
headers: {
|
|
||||||
...param.baseOptions?.headers,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
this.formDataCtor = param.formDataCtor;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check if the given MIME is a JSON MIME.
|
|
||||||
* JSON MIME examples:
|
|
||||||
* application/json
|
|
||||||
* application/json; charset=UTF8
|
|
||||||
* APPLICATION/JSON
|
|
||||||
* application/vnd.company+json
|
|
||||||
* @param mime - MIME (Multipurpose Internet Mail Extensions)
|
|
||||||
* @return True if the given MIME is JSON, false otherwise.
|
|
||||||
*/
|
|
||||||
public isJsonMime(mime: string): boolean {
|
|
||||||
const jsonMime: RegExp = /^(application\/json|[^;/ \t]+\/[^;/ \t]+[+]json)[ \t]*(;.*)?$/i;
|
|
||||||
return mime !== null && jsonMime.test(mime);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
/* tslint:disable */
|
|
||||||
/* eslint-disable */
|
|
||||||
/**
|
|
||||||
* OpenAPI definition
|
|
||||||
* No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)
|
|
||||||
*
|
|
||||||
* The version of the OpenAPI document: v0
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
|
|
||||||
* https://openapi-generator.tech
|
|
||||||
* Do not edit the class manually.
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
|
||||||
export * from "./api";
|
|
||||||
export * from "./configuration";
|
|
||||||
|
|
||||||
@@ -1,147 +0,0 @@
|
|||||||
<script setup lang="ts">
|
|
||||||
|
|
||||||
import {computed, ref} from "vue";
|
|
||||||
import {storeToRefs} from "pinia";
|
|
||||||
import {isCombined, Ledger, LedgerType, LedgerTypes, useLedgersStore} from "./ledger";
|
|
||||||
import {Modal} from "@/components";
|
|
||||||
import LedgerLabel from "./LedgerLabel.vue";
|
|
||||||
import {PlusIcon, TrashIcon} from '@heroicons/vue/24/outline';
|
|
||||||
import LedgerSelect from "@/ledger/LedgerSelect.vue";
|
|
||||||
|
|
||||||
interface Props {
|
|
||||||
ledgerId?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
const props = defineProps<Props>();
|
|
||||||
|
|
||||||
const ledgersStore = useLedgersStore();
|
|
||||||
const {ledgers} = storeToRefs(ledgersStore);
|
|
||||||
const {findById, findAllById, createMain, createCombined, updateMain, updateCombined} = ledgersStore;
|
|
||||||
|
|
||||||
const modalOpen = ref<boolean>(false);
|
|
||||||
|
|
||||||
const type = ref<LedgerType>(LedgerTypes.Main);
|
|
||||||
const name = ref("");
|
|
||||||
const members = ref<Ledger[]>([]);
|
|
||||||
const selectedLedger = ref<Ledger>();
|
|
||||||
const availableLedgers = computed(() => ledgers.value
|
|
||||||
.filter(l => l.ledgerId !== props.ledgerId)
|
|
||||||
.filter(l => !members.value.includes(l)));
|
|
||||||
|
|
||||||
|
|
||||||
const addMember = () => {
|
|
||||||
if (selectedLedger.value && !members.value.includes(selectedLedger.value)) {
|
|
||||||
members.value = [...members.value, selectedLedger.value];
|
|
||||||
selectedLedger.value = undefined;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const open = () => {
|
|
||||||
const ledger = isCreating.value ? undefined : findById(props.ledgerId);
|
|
||||||
|
|
||||||
if (ledger) {
|
|
||||||
type.value = ledger.type;
|
|
||||||
name.value = ledger.name;
|
|
||||||
members.value = isCombined(ledger) ? findAllById(ledger.memberLedgerIds) : [];
|
|
||||||
} else {
|
|
||||||
type.value = LedgerTypes.Main;
|
|
||||||
name.value = "";
|
|
||||||
members.value = [];
|
|
||||||
}
|
|
||||||
modalOpen.value = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
const canSave = computed(() => name.value.trim().length > 0);
|
|
||||||
const isCreating = computed(() => props.ledgerId === undefined || props.ledgerId.length === 0);
|
|
||||||
const title = computed(() => {
|
|
||||||
if (isCreating.value) {
|
|
||||||
return `Creating ${type.value === LedgerTypes.Main ? 'Main' : 'Combined'} Ledger`
|
|
||||||
}
|
|
||||||
return `Updating ${name.value}`
|
|
||||||
})
|
|
||||||
|
|
||||||
const create = () => {
|
|
||||||
if (type.value === LedgerTypes.Main) {
|
|
||||||
createMain({name: name.value})
|
|
||||||
} else {
|
|
||||||
createCombined({name: name.value, memberLedgerIds: members.value.map(l => l.ledgerId)})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const update = () => {
|
|
||||||
if (type.value === LedgerTypes.Main) {
|
|
||||||
updateMain(props.ledgerId, {name: name.value})
|
|
||||||
} else {
|
|
||||||
updateCombined(props.ledgerId, {name: name.value, memberLedgerIds: members.value.map(l => l.ledgerId)})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const save = () => {
|
|
||||||
if (!canSave.value) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isCreating.value) {
|
|
||||||
create();
|
|
||||||
} else {
|
|
||||||
update();
|
|
||||||
}
|
|
||||||
modalOpen.value = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
defineExpose({ open });
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<Modal v-model:open="modalOpen">
|
|
||||||
<div class="bg-slate-800 rounded pb-4 w-96">
|
|
||||||
<span class="m-2">{{ title }}</span>
|
|
||||||
<hr />
|
|
||||||
<div class="mt-4">
|
|
||||||
<div v-if="!ledgerId" class="flex justify-center">
|
|
||||||
<div class="flex bg-slate-600 rounded-s-md p-1">
|
|
||||||
<button class="switch" :class="{active: type === LedgerTypes.Main}" @click="type = LedgerTypes.Main">Main</button>
|
|
||||||
</div>
|
|
||||||
<div class="switch flex bg-slate-600 rounded-e-md p-1">
|
|
||||||
<button class="switch" :class="{active: type === LedgerTypes.Combined}" @click="type = LedgerTypes.Combined">Combined</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="m-4">
|
|
||||||
Name:
|
|
||||||
<div class="flex">
|
|
||||||
<input type="text" class="flex grow" v-model="name" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div v-if="type === LedgerTypes.Combined" class="ms-4 mb-4">
|
|
||||||
Member Ledgers:
|
|
||||||
<div v-for="ledger in members" :key="ledger.ledgerId" class="flex">
|
|
||||||
<LedgerLabel class="flex grow mb-2" :ledger="ledger" />
|
|
||||||
<div class="flex justify-end me-4 ms-2">
|
|
||||||
<button class="btn-icon" @click="members = members.filter(m => m !== ledger)"><TrashIcon /></button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div v-if="availableLedgers.length" class="flex">
|
|
||||||
<LedgerSelect v-model="selectedLedger" class="grow" :ledgers="availableLedgers" />
|
|
||||||
<div class="flex justify-end me-4 ms-2">
|
|
||||||
<button class="btn-icon" @click="addMember"><PlusIcon /></button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="flex justify-end">
|
|
||||||
<button class="me-4" @click="save" :disabled="!canSave">Save</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</Modal>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
@reference "@/style.css";
|
|
||||||
|
|
||||||
button.switch {
|
|
||||||
@apply flex items-center px-4 rounded-md bg-slate-600;
|
|
||||||
&.active {
|
|
||||||
@apply bg-emerald-500;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
<script setup lang="ts">
|
|
||||||
|
|
||||||
import {isCombined, Ledger, systemLedger} from "@/ledger/ledger.ts";
|
|
||||||
import {FolderOpenIcon} from '@heroicons/vue/24/outline';
|
|
||||||
import {RouterLink} from "vue-router";
|
|
||||||
import {routeNames} from "@/routes";
|
|
||||||
|
|
||||||
interface Props {
|
|
||||||
ledger: Ledger;
|
|
||||||
link?: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
const props = defineProps<Props>();
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<div class="flex">
|
|
||||||
<FolderOpenIcon v-if="isCombined(ledger)" class="w-4 me-1" />
|
|
||||||
<div v-else class="w-4 me-1"/>
|
|
||||||
<RouterLink v-if="link" :to="{name: routeNames.viewLedger, params: {ledgerId: ledger.ledgerId}}">{{ ledger.name }}</RouterLink>
|
|
||||||
<span v-else :class="{'system-ledger': ledger === systemLedger}">{{ ledger.name }}</span>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
@reference "@/style.css";
|
|
||||||
|
|
||||||
.system-ledger {
|
|
||||||
@apply text-emerald-400;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -1,34 +0,0 @@
|
|||||||
<script setup lang="ts">
|
|
||||||
import {Ledger, systemLedger, useLedgersStore} from "@/ledger/ledger.ts";
|
|
||||||
import {storeToRefs} from "pinia";
|
|
||||||
import {computed} from "vue";
|
|
||||||
|
|
||||||
interface Props {
|
|
||||||
ledgers?: Ledger[];
|
|
||||||
}
|
|
||||||
|
|
||||||
const props = defineProps<Props>()
|
|
||||||
const ledger = defineModel<Ledger>();
|
|
||||||
const {ledgers: allLedgers} = storeToRefs(useLedgersStore());
|
|
||||||
|
|
||||||
const ledgersToUse = computed(() => props.ledgers || allLedgers);
|
|
||||||
const ledgerId = computed({
|
|
||||||
get: () => ledger.value?.ledgerId,
|
|
||||||
set: value => ledger.value = ledgersToUse.value.find(l => l.ledgerId === value)
|
|
||||||
})
|
|
||||||
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<select v-model="ledgerId" :class="{'system-ledger': ledger === systemLedger}">
|
|
||||||
<option v-for="l in ledgersToUse" :key="l.ledgerId" :value="l.ledgerId" :class="{'system-ledger': l === systemLedger}">{{ l.name }}</option>
|
|
||||||
</select>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
@reference "@/style.css";
|
|
||||||
|
|
||||||
.system-ledger {
|
|
||||||
@apply text-emerald-400;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
export * from './ledger';
|
|
||||||
|
|
||||||
export {default as LedgerLabel} from './LedgerLabel.vue';
|
|
||||||
export {default as LedgerSelect} from './LedgerSelect.vue';
|
|
||||||
export {default as EditLedgerModal} from './EditLedgerModal.vue';
|
|
||||||
@@ -1,88 +0,0 @@
|
|||||||
import {
|
|
||||||
BalanceResponse,
|
|
||||||
CombinedLedgerResponse,
|
|
||||||
CreateCombinedLedgerRequest,
|
|
||||||
CreateMainLedgerRequest,
|
|
||||||
LedgerResponse,
|
|
||||||
MainLedgerResponse,
|
|
||||||
TransactionResponse,
|
|
||||||
UpdateCombinedLedgerRequest,
|
|
||||||
UpdateMainLedgerRequest
|
|
||||||
} from "@/generated/mammon";
|
|
||||||
import {defineStore} from "pinia";
|
|
||||||
import {computed, ref, triggerRef} from "vue";
|
|
||||||
import {ledgerApi, transactionApi} from "@/mammon";
|
|
||||||
import {useRouteParams} from "@vueuse/router";
|
|
||||||
|
|
||||||
export const LedgerTypes = {
|
|
||||||
Main: 'MAIN',
|
|
||||||
Combined: 'COMBINED',
|
|
||||||
};
|
|
||||||
|
|
||||||
export type LedgerType = LedgerResponse['type'];
|
|
||||||
export type MainLedger = MainLedgerResponse
|
|
||||||
export type CombinedLedger = CombinedLedgerResponse
|
|
||||||
export type Ledger = MainLedger | CombinedLedger;
|
|
||||||
|
|
||||||
export const systemLedgerRef = 'system';
|
|
||||||
export const systemLedger = {
|
|
||||||
type: LedgerTypes.Main,
|
|
||||||
ledgerId: "00000000-0000-0000-0000-000000000001",
|
|
||||||
name: "Eve Economy",
|
|
||||||
balance: 0,
|
|
||||||
_system: true
|
|
||||||
} as MainLedger;
|
|
||||||
|
|
||||||
export const isMain = (ledger: Ledger): ledger is MainLedger => {
|
|
||||||
return ledger.type === LedgerTypes.Main;
|
|
||||||
}
|
|
||||||
export const isCombined = (ledger: Ledger): ledger is CombinedLedger => {
|
|
||||||
return ledger.type === LedgerTypes.Combined;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const useLedgersStore = defineStore('ledgers', () => {
|
|
||||||
const ledgers = ref<Ledger[]>([]);
|
|
||||||
|
|
||||||
const addLedger = (ledger: Ledger) => {
|
|
||||||
ledgers.value.push(ledger);
|
|
||||||
triggerRef(ledgers);
|
|
||||||
return ledger;
|
|
||||||
};
|
|
||||||
|
|
||||||
const replaceLedger = (ledger: Ledger) => {
|
|
||||||
const index = ledgers.value.findIndex(l => l.ledgerId === ledger.ledgerId);
|
|
||||||
|
|
||||||
if (index !== -1) {
|
|
||||||
ledgers.value[index] = ledger;
|
|
||||||
}
|
|
||||||
triggerRef(ledgers);
|
|
||||||
return ledger;
|
|
||||||
};
|
|
||||||
|
|
||||||
const findById = (ledgerId: string): Ledger | undefined => ledgers.value.find(l => l.ledgerId === ledgerId);
|
|
||||||
const findAllById = (ledgerIds: string[]): Ledger[] => ledgerIds.map(findById).filter((x): x is Ledger => x !== undefined)
|
|
||||||
|
|
||||||
const createMain = (ledger: CreateMainLedgerRequest) => ledgerApi.createMainLedger(ledger).then(response => addLedger(response.data as Ledger));
|
|
||||||
const createCombined = (ledger: CreateCombinedLedgerRequest) => ledgerApi.createCombinedLedger(ledger).then(response => addLedger(response.data as Ledger));
|
|
||||||
const updateMain = (ledgerId: string, ledger: UpdateMainLedgerRequest) => ledgerApi.updateMainLedger(ledgerId, ledger).then(response => replaceLedger(response.data as Ledger));
|
|
||||||
const updateCombined = (ledgerId: string, ledger: UpdateCombinedLedgerRequest) => ledgerApi.updateCombinedLedger(ledgerId, ledger).then(response => replaceLedger(response.data as Ledger));
|
|
||||||
|
|
||||||
const refresh = () => ledgerApi.findAllLedgers().then(response => ledgers.value = response.data as Ledger[]);
|
|
||||||
|
|
||||||
refresh();
|
|
||||||
|
|
||||||
return {ledgers, findById, findAllById, createMain, createCombined, updateMain, updateCombined, refresh};
|
|
||||||
})
|
|
||||||
|
|
||||||
const getLedgerId = (ledger: Ledger | string): string => typeof ledger == 'string' ? ledger : ledger.ledgerId;
|
|
||||||
|
|
||||||
export const findAllTransactionInLeger = (ledger: Ledger | string): Promise<TransactionResponse[]> => transactionApi.finAllTransactionsInLedger(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 = () => {
|
|
||||||
const {findById} = useLedgersStore();
|
|
||||||
const ledgerId = useRouteParams<string, string>('ledgerId', '', { transform: v => typeof v === 'string' ? v : v[0]});
|
|
||||||
const ledger = computed(() => findById(ledgerId.value))
|
|
||||||
|
|
||||||
return {ledgerId, ledger};
|
|
||||||
}
|
|
||||||
+13
@@ -1,3 +1,4 @@
|
|||||||
|
import { useAuthStore } from "@/auth";
|
||||||
import { createPinia } from 'pinia';
|
import { createPinia } from 'pinia';
|
||||||
import { createApp } from 'vue';
|
import { createApp } from 'vue';
|
||||||
import { createRouter, createWebHistory } from 'vue-router';
|
import { createRouter, createWebHistory } from 'vue-router';
|
||||||
@@ -16,6 +17,18 @@ const router = createRouter({
|
|||||||
});
|
});
|
||||||
|
|
||||||
app.use(pinia);
|
app.use(pinia);
|
||||||
|
|
||||||
|
const authStore = useAuthStore();
|
||||||
|
|
||||||
|
router.beforeEach(async to => {
|
||||||
|
if (to.name === 'callback') {
|
||||||
|
await authStore.login();
|
||||||
|
return { name: 'home' };
|
||||||
|
} else if (!authStore.isLoggedIn) {
|
||||||
|
await authStore.redirect();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
app.use(router);
|
app.use(router);
|
||||||
|
|
||||||
app.mount('#app');
|
app.mount('#app');
|
||||||
|
|||||||
@@ -1 +0,0 @@
|
|||||||
export * from './mammonService'
|
|
||||||
@@ -1,33 +0,0 @@
|
|||||||
import {logResource} from "@/service";
|
|
||||||
import axios from "axios";
|
|
||||||
import {
|
|
||||||
AcquisitionApi,
|
|
||||||
ActivityApi,
|
|
||||||
CharacterApi,
|
|
||||||
CharacterRuleBookApi,
|
|
||||||
LedgerApi,
|
|
||||||
ProcessingApi,
|
|
||||||
RuleBookApi,
|
|
||||||
TransactionApi
|
|
||||||
} from "@/generated/mammon";
|
|
||||||
|
|
||||||
export const mammonUrl = import.meta.env.VITE_MAMMON_URL;
|
|
||||||
export const mammonAddCharacterUrl = mammonUrl + "oauth2/authorization/esi"
|
|
||||||
|
|
||||||
const mammonAxiosInstance = axios.create({
|
|
||||||
baseURL: mammonUrl,
|
|
||||||
headers: {
|
|
||||||
'Accept': 'application/json',
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
},
|
|
||||||
})
|
|
||||||
logResource(mammonAxiosInstance)
|
|
||||||
|
|
||||||
export const ledgerApi = new LedgerApi(undefined, mammonUrl, mammonAxiosInstance);
|
|
||||||
export const transactionApi = new TransactionApi(undefined, mammonUrl, mammonAxiosInstance);
|
|
||||||
export const characterApi = new CharacterApi(undefined, mammonUrl, mammonAxiosInstance);
|
|
||||||
export const ruleBookApi = new RuleBookApi(undefined, mammonUrl, mammonAxiosInstance);
|
|
||||||
export const characterRuleBookApi = new CharacterRuleBookApi(undefined, mammonUrl, mammonAxiosInstance);
|
|
||||||
export const activityApi = new ActivityApi(undefined, mammonUrl, mammonAxiosInstance);
|
|
||||||
export const processingApi = new ProcessingApi(undefined, mammonUrl, mammonAxiosInstance);
|
|
||||||
export const acquisitionApi = new AcquisitionApi(undefined, mammonUrl, mammonAxiosInstance);
|
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
export type MarbasObject = {
|
||||||
|
id: number;
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
export * from './MarbasObject';
|
||||||
|
export * from './marbasService';
|
||||||
|
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
import { useAuthStore } from "@/auth";
|
||||||
|
import { logResource } from "@/service";
|
||||||
|
import axios from "axios";
|
||||||
|
|
||||||
|
export const marbasAxiosInstance = axios.create({
|
||||||
|
baseURL: import.meta.env.VITE_MARBAS_URL,
|
||||||
|
headers: {
|
||||||
|
'Accept': 'application/json',
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
marbasAxiosInstance.interceptors.request.use(r => {
|
||||||
|
const authStore = useAuthStore();
|
||||||
|
|
||||||
|
if (!authStore.isLoggedIn) {
|
||||||
|
throw new Error("Not logged in");
|
||||||
|
}
|
||||||
|
|
||||||
|
const accessToken = authStore.accessToken;
|
||||||
|
|
||||||
|
if (accessToken) {
|
||||||
|
r.headers.Authorization = `Bearer ${accessToken}`;
|
||||||
|
}
|
||||||
|
if (!r.params?.page_size) {
|
||||||
|
r.params = { ...r.params, page_size: 250 };
|
||||||
|
}
|
||||||
|
return r;
|
||||||
|
})
|
||||||
|
logResource(marbasAxiosInstance)
|
||||||
|
marbasAxiosInstance.interceptors.response.use(async r => {
|
||||||
|
let next: string = r.data?.next;
|
||||||
|
let results = r.data?.results;
|
||||||
|
|
||||||
|
if (next) {
|
||||||
|
if (!next.startsWith(import.meta.env.VITE_MARBAS_URL)) { // FIME remove once the API is fixed
|
||||||
|
next = import.meta.env.VITE_MARBAS_URL + next.replace(/http(s)?:\/\/[^/]+\//g, '');
|
||||||
|
}
|
||||||
|
|
||||||
|
results = results.concat((await marbasAxiosInstance.request({
|
||||||
|
...r.config,
|
||||||
|
url: next,
|
||||||
|
baseURL: '',
|
||||||
|
})).data);
|
||||||
|
}
|
||||||
|
if (results) {
|
||||||
|
r.data = results;
|
||||||
|
}
|
||||||
|
return r;
|
||||||
|
})
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
<script setup lang="ts">
|
|
||||||
import {formatIsk} from "@/formaters";
|
|
||||||
import {computed} from "vue";
|
|
||||||
|
|
||||||
interface Props {
|
|
||||||
amount: number;
|
|
||||||
colored?: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
const props = withDefaults(defineProps<Props>(), {
|
|
||||||
colored: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
const computedClass = computed(() => {
|
|
||||||
if (!props.colored) {
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
|
|
||||||
return props.amount >= 0 ? 'text-emerald-400' : 'text-amber-700';
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<span :class="computedClass">{{ formatIsk(amount) }}</span>
|
|
||||||
</template>
|
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
import { MarketType } from "..";
|
import { MarketType } from "..";
|
||||||
import { RawAcquiredType } from "./acquisition";
|
import { MarbasAcquiredType } from "./acquisition";
|
||||||
|
|
||||||
export type AcquiredType = Omit<RawAcquiredType, 'type'> & {
|
export type AcquiredType = Omit<MarbasAcquiredType, 'type'> & {
|
||||||
type: MarketType,
|
type: MarketType,
|
||||||
buy: number,
|
buy: number,
|
||||||
sell: number
|
sell: number
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import {LoadingSpinner, Tooltip} from '@/components';
|
import { LoadingSpinner, Tooltip } from '@/components';
|
||||||
import {formatIsk} from '@/formaters';
|
import { formatIsk } from '@/formaters';
|
||||||
import {getHistory, getHistoryQuartils} from '@/market';
|
import { getHistory, getHistoryQuartils } from '@/market';
|
||||||
import {ArrowTrendingDownIcon, ArrowTrendingUpIcon} from '@heroicons/vue/24/outline';
|
import { ArrowTrendingDownIcon, ArrowTrendingUpIcon } from '@heroicons/vue/24/outline';
|
||||||
import {computedAsync} from '@vueuse/core';
|
import { computedAsync } from '@vueuse/core';
|
||||||
import {ref, watchEffect} from 'vue';
|
import { ref, watchEffect } from 'vue';
|
||||||
|
|
||||||
const trendingScale = 3;
|
const trendingScale = 3;
|
||||||
|
|
||||||
@@ -74,9 +74,7 @@ watchEffect(async () => {
|
|||||||
</Tooltip>
|
</Tooltip>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped lang="postcss">
|
||||||
@reference "@/style.css";
|
|
||||||
|
|
||||||
.tooltip {
|
.tooltip {
|
||||||
@apply ms-auto;
|
@apply ms-auto;
|
||||||
>:deep(div.header) {
|
>:deep(div.header) {
|
||||||
|
|||||||
@@ -1,15 +1,15 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import {SortableHeader, useSort, VirtualScrollTable} from '@/components/table';
|
import { SortableHeader, useSort, VirtualScrollTable } from '@/components/table';
|
||||||
import {MarketType, MarketTypeLabel, TaxInput, useMarketTaxStore} from "@/market";
|
import { formatEveDate, formatIsk, percentFormater } from '@/formaters';
|
||||||
import {MinusIcon, PlusIcon} from '@heroicons/vue/24/outline';
|
import { MarketType, MarketTypeLabel, TaxInput, useMarketTaxStore } from "@/market";
|
||||||
import {useStorage} from '@vueuse/core';
|
import { MinusIcon, PlusIcon } from '@heroicons/vue/24/outline';
|
||||||
import {computed, ref} from 'vue';
|
import { useStorage } from '@vueuse/core';
|
||||||
import {AcquiredType} from './AcquiredType';
|
import { computed, ref } from 'vue';
|
||||||
|
import { AcquiredType } from './AcquiredType';
|
||||||
import AcquisitionQuantilsTooltip from './AcquisitionQuantilsTooltip.vue';
|
import AcquisitionQuantilsTooltip from './AcquisitionQuantilsTooltip.vue';
|
||||||
import {formatEveDate, formatIsk, percentFormater} from "@/formaters.ts";
|
|
||||||
|
|
||||||
type Result = {
|
type Result = {
|
||||||
id: string;
|
id: number;
|
||||||
type: MarketType;
|
type: MarketType;
|
||||||
name: string;
|
name: string;
|
||||||
buy: number;
|
buy: number;
|
||||||
@@ -98,7 +98,7 @@ const { sortedArray, headerProps, showColumn } = useSort<Result>(computed(() =>
|
|||||||
const precentProfit = marketTaxStore.calculateProfit(price, first.sell);
|
const precentProfit = marketTaxStore.calculateProfit(price, first.sell);
|
||||||
|
|
||||||
list.push({
|
list.push({
|
||||||
id: typeID.toString(),
|
id: typeID,
|
||||||
type: first.type,
|
type: first.type,
|
||||||
name: first.type.name,
|
name: first.type.name,
|
||||||
buy: first.buy,
|
buy: first.buy,
|
||||||
@@ -126,33 +126,6 @@ const getLineColor = (result: Result) => {
|
|||||||
}
|
}
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
const total = computed(() => {
|
|
||||||
if (sortedArray.value.length <= 1) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
const first = sortedArray.value[0];
|
|
||||||
|
|
||||||
if (!first) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
const sameItem = sortedArray.value.every(r => r.type.id === first.type.id);
|
|
||||||
const quantity = sameItem ? sortedArray.value.reduce((acc, r) => acc + r.quantity, 0) : 0;
|
|
||||||
const totalRemaining = sameItem ? sortedArray.value.reduce((acc, r) => acc + r.remaining, 0) : 0;
|
|
||||||
const price = sortedArray.value.reduce((acc, r) => acc + r.price * r.remaining, 0) / totalRemaining;
|
|
||||||
const precentProfit = marketTaxStore.calculateProfit(price, first.sell);
|
|
||||||
const iskProfit = sortedArray.value.reduce((acc, r) => acc + r.iskProfit, 0);
|
|
||||||
|
|
||||||
return {
|
|
||||||
sameItem,
|
|
||||||
price,
|
|
||||||
remaining: totalRemaining,
|
|
||||||
quantity,
|
|
||||||
precentProfit,
|
|
||||||
iskProfit
|
|
||||||
};
|
|
||||||
});
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@@ -169,7 +142,7 @@ const total = computed(() => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<VirtualScrollTable :list="sortedArray" :itemHeight="33" :footerHeight="!!total ? 33 : 0" bottom="1rem">
|
<VirtualScrollTable :list="sortedArray" :itemHeight="33" bottom="1rem">
|
||||||
<template #default="{ list }">
|
<template #default="{ list }">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
@@ -205,37 +178,6 @@ const total = computed(() => {
|
|||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
<tfoot v-if="!!total">
|
|
||||||
<tr>
|
|
||||||
<td v-if="showColumn('name')">Total</td>
|
|
||||||
<td v-if="showColumn('buy')">
|
|
||||||
<template v-if="!showColumn('name')">Total</template>
|
|
||||||
</td>
|
|
||||||
<td v-if="showColumn('sell')">
|
|
||||||
<template v-if="!showColumn('name') && !showColumn('buy')">Total</template>
|
|
||||||
</td>
|
|
||||||
<td v-if="showColumn('date')">
|
|
||||||
<template v-if="!showColumn('name') && !showColumn('buy') && !showColumn('sell')">Total</template>
|
|
||||||
</td>
|
|
||||||
<td v-if="showColumn('price')" class="text-right">
|
|
||||||
<template v-if="total.sameItem">
|
|
||||||
{{ formatIsk(total.price) }}
|
|
||||||
</template>
|
|
||||||
</td>
|
|
||||||
<td v-if="showColumn('remaining')" class="text-right">
|
|
||||||
<template v-if="total.sameItem">
|
|
||||||
{{ total.remaining }}/{{ total.quantity }}
|
|
||||||
</template>
|
|
||||||
</td>
|
|
||||||
<td v-if="showColumn('precentProfit')" class="text-right">
|
|
||||||
<template v-if="total.sameItem">
|
|
||||||
{{ percentFormater.format(total.precentProfit) }}
|
|
||||||
</template>
|
|
||||||
</td>
|
|
||||||
<td v-if="showColumn('iskProfit')" class="text-right">{{ formatIsk(total.iskProfit) }}</td>
|
|
||||||
<td v-if="showColumn('buttons')" />
|
|
||||||
</tr>
|
|
||||||
</tfoot>
|
|
||||||
</template>
|
</template>
|
||||||
<template #empty>
|
<template #empty>
|
||||||
<div class="text-center mt-4">
|
<div class="text-center mt-4">
|
||||||
@@ -245,8 +187,7 @@ const total = computed(() => {
|
|||||||
</VirtualScrollTable>
|
</VirtualScrollTable>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped lang="postcss">
|
||||||
@reference "@/style.css";
|
|
||||||
div.end {
|
div.end {
|
||||||
@apply justify-self-end ms-2;
|
@apply justify-self-end ms-2;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import {Modal} from '@/components';
|
import { Modal } from '@/components';
|
||||||
import {formatIsk} from '@/formaters';
|
import { formatIsk } from '@/formaters';
|
||||||
import {MarketType, MarketTypeLabel} from '@/market';
|
import { MarketType, MarketTypeLabel } from '@/market';
|
||||||
import {ref} from 'vue';
|
import { ref } from 'vue';
|
||||||
import {useAcquiredTypesStore} from './acquisition';
|
import { useAcquiredTypesStore } from './acquisition';
|
||||||
|
|
||||||
|
|
||||||
const acquiredTypesStore = useAcquiredTypesStore();
|
const acquiredTypesStore = useAcquiredTypesStore();
|
||||||
|
|
||||||
|
|||||||
@@ -1,43 +1,74 @@
|
|||||||
import {defineStore} from "pinia";
|
import { marbasAxiosInstance, MarbasObject } from "@/marbas";
|
||||||
import {computed, ref} from "vue";
|
import { AxiosResponse } from "axios";
|
||||||
import {acquisitionApi} from "@/mammon";
|
import log from "loglevel";
|
||||||
import {AcquisitionResponse, AcquisitionResponseSourceEnum} from "@/generated/mammon";
|
import { defineStore } from "pinia";
|
||||||
|
import { computed, ref } from "vue";
|
||||||
|
|
||||||
export type AcquiredTypeSource = 'bo' | 'so' | 'prod' | 'misc';
|
export type AcquiredTypeSource = 'bo' | 'so' | 'prod' | 'misc';
|
||||||
|
|
||||||
export type RawAcquiredType = {
|
export type MarbasAcquiredType = MarbasObject & {
|
||||||
id: string;
|
|
||||||
type: number;
|
type: number;
|
||||||
quantity: number;
|
quantity: number;
|
||||||
remaining: number;
|
remaining: number;
|
||||||
price: number;
|
price: number;
|
||||||
date: Date;
|
date: Date;
|
||||||
source: AcquisitionResponseSourceEnum;
|
source: AcquiredTypeSource;
|
||||||
|
user: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
const toAcquiredType = (a: AcquisitionResponse): RawAcquiredType => ({
|
type RawMarbasAcquiredType = Omit<MarbasAcquiredType, 'date'> & {
|
||||||
id: a.acquisitionId,
|
date: string;
|
||||||
type: a.marketTypeId,
|
}
|
||||||
quantity: a.quantity,
|
|
||||||
remaining: a.remaining,
|
type InsertableRawMarbasAcquiredType = Omit<MarbasAcquiredType, 'id' | 'user' | 'date'>;
|
||||||
price: a.unitCost,
|
|
||||||
date: new Date(a.datetime),
|
const mapRawMarbasAcquiredType = (raw: RawMarbasAcquiredType): MarbasAcquiredType => ({
|
||||||
source: a.source,
|
...raw,
|
||||||
|
date: raw.date ? new Date(raw.date) : new Date()
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const endpoint = '/api/acquisitions/';
|
||||||
|
|
||||||
export const useAcquiredTypesStore = defineStore('market-acquisition', () => {
|
export const useAcquiredTypesStore = defineStore('market-acquisition', () => {
|
||||||
const acquiredTypes = ref<RawAcquiredType[]>([]);
|
const acquiredTypes = ref<MarbasAcquiredType[]>([]);
|
||||||
|
|
||||||
const types = computed(() => acquiredTypes.value.filter(item => item.remaining > 0));
|
const types = computed(() => acquiredTypes.value.filter(item => item.remaining > 0));
|
||||||
|
const addAcquiredType = async (type: number, quantity: number, price: number, source?: AcquiredTypeSource) => {
|
||||||
|
const newItem = mapRawMarbasAcquiredType((await marbasAxiosInstance.post<RawMarbasAcquiredType, AxiosResponse<RawMarbasAcquiredType>, InsertableRawMarbasAcquiredType>(endpoint, {
|
||||||
|
type: type,
|
||||||
|
quantity: quantity,
|
||||||
|
remaining: quantity,
|
||||||
|
price: price,
|
||||||
|
source: source ?? 'misc',
|
||||||
|
})).data);
|
||||||
|
|
||||||
// Display-only: the backend exposes no write endpoint yet, so buy/sell are no-ops.
|
acquiredTypes.value = [...acquiredTypes.value, newItem];
|
||||||
const addAcquiredType = async (_type: number, _quantity: number, _price: number, _source?: AcquiredTypeSource) => {};
|
log.info(`Acquired type ${newItem.id} with quantity ${newItem.quantity} and price ${newItem.price}`, newItem);
|
||||||
const removeAcquiredType = async (_id: string, _quantity: number) => {};
|
};
|
||||||
|
const removeAcquiredType = async (id: number, quantity: number) => {
|
||||||
|
const found = acquiredTypes.value.find(t => t.id === id);
|
||||||
|
|
||||||
const refresh = () => acquisitionApi.findAllAcquisitions()
|
if (!found) {
|
||||||
.then(response => acquiredTypes.value = response.data.map(toAcquiredType));
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
refresh();
|
const item = {
|
||||||
|
...found,
|
||||||
|
remaining: Math.max(0, found.remaining - quantity)
|
||||||
|
};
|
||||||
|
|
||||||
return { acquiredTypes: types, addAcquiredType, removeAcquiredType, refresh };
|
acquiredTypes.value = acquiredTypes.value.map(i => {
|
||||||
|
if (i.id === item.id) {
|
||||||
|
return item;
|
||||||
|
} else {
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
await marbasAxiosInstance.put(`${endpoint}${item.id}/`, item);
|
||||||
|
log.info(`Acquired type ${item.id} remaining: ${item.remaining}`, item);
|
||||||
|
};
|
||||||
|
|
||||||
|
marbasAxiosInstance.get<RawMarbasAcquiredType[]>(endpoint).then(res => acquiredTypes.value = res.data.map(mapRawMarbasAcquiredType));
|
||||||
|
|
||||||
|
return { acquiredTypes: types, addAcquiredType, removeAcquiredType };
|
||||||
});
|
});
|
||||||
@@ -23,8 +23,8 @@ const historyCache: RegionalMarketCache<EsiMarketOrderHistory[]> = new RegionalM
|
|||||||
return date;
|
return date;
|
||||||
});
|
});
|
||||||
|
|
||||||
export const getHistory = async (typeId: number, regionId?: number): Promise<EsiMarketOrderHistory[]> => {
|
export const getHistory = async (tyeId: number, regionId?: number): Promise<EsiMarketOrderHistory[]> => {
|
||||||
const rId = regionId ?? jitaId;
|
const rId = regionId ?? jitaId;
|
||||||
|
|
||||||
return historyCache.computeIfAbsent(rId, typeId, async () => (await esiAxiosInstance.get(`/markets/${rId}/history/`, { params: { type_id: typeId } })).data);
|
return historyCache.computeIfAbsent(rId, tyeId, async () => (await esiAxiosInstance.get(`/markets/${rId}/history/`, { params: { type_id: tyeId } })).data);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,5 +6,3 @@ export * from './type';
|
|||||||
export * from './appraisal';
|
export * from './appraisal';
|
||||||
export * from './market';
|
export * from './market';
|
||||||
|
|
||||||
export { default as IskLabel } from './IskLabel.vue';
|
|
||||||
|
|
||||||
|
|||||||
@@ -1 +1,3 @@
|
|||||||
|
|
||||||
export const jitaId = 10000002;
|
export const jitaId = 10000002;
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import {storeToRefs} from "pinia";
|
import { storeToRefs } from "pinia";
|
||||||
import {useMarketTaxStore} from "./tax";
|
import { useMarketTaxStore } from "./tax";
|
||||||
|
|
||||||
const { brokerFee, scc } = storeToRefs(useMarketTaxStore());
|
const { brokerFee, scc } = storeToRefs(useMarketTaxStore());
|
||||||
|
|
||||||
@@ -17,8 +17,7 @@ const { brokerFee, scc } = storeToRefs(useMarketTaxStore());
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped lang="postcss">
|
||||||
@reference "@/style.css";
|
|
||||||
div.end {
|
div.end {
|
||||||
@apply justify-self-end ms-2;
|
@apply justify-self-end ms-2;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
<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 { getHistoryQuartils, MarketType, MarketTypeLabel, TaxInput, useMarketTaxStore } from "@/market";
|
||||||
import {useStorage} from '@vueuse/core';
|
import { BookmarkSlashIcon, ShoppingCartIcon } from '@heroicons/vue/24/outline';
|
||||||
import {computed, ref} from 'vue';
|
import { useStorage } from '@vueuse/core';
|
||||||
import {useAcquiredTypesStore} from '../acquisition';
|
import { computed, ref } from 'vue';
|
||||||
import {TrackingResult} from './tracking';
|
import { TrackingResult } from './tracking';
|
||||||
|
|
||||||
type Result = {
|
type Result = {
|
||||||
type: MarketType;
|
type: MarketType;
|
||||||
@@ -17,7 +17,6 @@ type Result = {
|
|||||||
q1: number;
|
q1: number;
|
||||||
median: number;
|
median: number;
|
||||||
q3: number;
|
q3: number;
|
||||||
acquisitions: number;
|
|
||||||
profit: number;
|
profit: number;
|
||||||
score: number;
|
score: number;
|
||||||
}
|
}
|
||||||
@@ -45,7 +44,6 @@ const props = withDefaults(defineProps<Props>(), {
|
|||||||
defineEmits<Emits>();
|
defineEmits<Emits>();
|
||||||
|
|
||||||
const marketTaxStore = useMarketTaxStore();
|
const marketTaxStore = useMarketTaxStore();
|
||||||
const acquiredTypesStore = useAcquiredTypesStore();
|
|
||||||
|
|
||||||
const days = useStorage('market-tracking-days', 365);
|
const days = useStorage('market-tracking-days', 365);
|
||||||
const threshold = useStorage('market-tracking-threshold', 10);
|
const threshold = useStorage('market-tracking-threshold', 10);
|
||||||
@@ -65,9 +63,6 @@ const { sortedArray, headerProps, showColumn } = useSort<Result>(computed(() =>
|
|||||||
const quartils = getHistoryQuartils(r.history, days.value);
|
const quartils = getHistoryQuartils(r.history, days.value);
|
||||||
const profit = quartils.q1 === 0 || quartils.q3 === 0 ? 0 : marketTaxStore.calculateProfit(quartils.q1, quartils.q3);
|
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 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
|
|
||||||
.filter(t => t.type === r.type.id)
|
|
||||||
.reduce((a, b) => a + b.remaining, 0);
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
type: r.type,
|
type: r.type,
|
||||||
@@ -78,7 +73,6 @@ const { sortedArray, headerProps, showColumn } = useSort<Result>(computed(() =>
|
|||||||
q1: quartils.q1,
|
q1: quartils.q1,
|
||||||
median: quartils.median,
|
median: quartils.median,
|
||||||
q3: quartils.q3,
|
q3: quartils.q3,
|
||||||
acquisitions,
|
|
||||||
profit,
|
profit,
|
||||||
score
|
score
|
||||||
};
|
};
|
||||||
@@ -134,7 +128,6 @@ const getLineColor = (result: Result) => {
|
|||||||
<SortableHeader v-bind="headerProps" sortKey="q3">Q3</SortableHeader>
|
<SortableHeader v-bind="headerProps" sortKey="q3">Q3</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="buttons" unsortable />
|
<SortableHeader v-bind="headerProps" sortKey="buttons" unsortable />
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
@@ -150,7 +143,6 @@ const getLineColor = (result: Result) => {
|
|||||||
<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('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('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>
|
<button class="btn-icon me-1" title="Untrack" @click="$emit('remove', r.data.type)"><BookmarkSlashIcon /></button>
|
||||||
@@ -166,8 +158,7 @@ const getLineColor = (result: Result) => {
|
|||||||
</VirtualScrollTable>
|
</VirtualScrollTable>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped lang="postcss">
|
||||||
@reference "@/style.css";
|
|
||||||
div.end {
|
div.end {
|
||||||
@apply justify-self-end ms-2;
|
@apply justify-self-end ms-2;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { marbasAxiosInstance, MarbasObject } from "@/marbas";
|
||||||
import { EsiMarketOrderHistory, getHistory, MarketType, MarketTypePrice } from "@/market";
|
import { EsiMarketOrderHistory, getHistory, MarketType, MarketTypePrice } from "@/market";
|
||||||
import log from "loglevel";
|
import log from "loglevel";
|
||||||
import { defineStore } from "pinia";
|
import { defineStore } from "pinia";
|
||||||
@@ -11,16 +12,21 @@ export type TrackingResult = {
|
|||||||
orderCount: number,
|
orderCount: number,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type MarbasTrackedType = MarbasObject & {
|
||||||
|
type: number
|
||||||
|
};
|
||||||
|
|
||||||
const endpoint = '/api/types_tracking/';
|
const endpoint = '/api/types_tracking/';
|
||||||
|
|
||||||
export const useMarketTrackingStore = defineStore('marketTracking', () => {
|
export const useMarketTrackingStore = defineStore('marketTracking', () => {
|
||||||
const trackedTypes = ref<any[]>([]); // TODO
|
const trackedTypes = ref<MarbasTrackedType[]>([]);
|
||||||
|
|
||||||
const types = computed(() => trackedTypes.value.map(item => item.type) ?? []);
|
const types = computed(() => trackedTypes.value.map(item => item.type) ?? []);
|
||||||
const addType = async (type: number) => {
|
const addType = async (type: number) => {
|
||||||
const found = trackedTypes.value.find(item => item.type === type);
|
const found = trackedTypes.value.find(item => item.type === type);
|
||||||
|
|
||||||
if (!found) {
|
if (!found) {
|
||||||
|
trackedTypes.value = [...trackedTypes.value, (await marbasAxiosInstance.post<MarbasTrackedType>(endpoint, { type })).data];
|
||||||
log.info(`Tracking type ${type}`);
|
log.info(`Tracking type ${type}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -32,8 +38,12 @@ export const useMarketTrackingStore = defineStore('marketTracking', () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
trackedTypes.value = trackedTypes.value.filter(t => t.id !== found.id);
|
trackedTypes.value = trackedTypes.value.filter(t => t.id !== found.id);
|
||||||
|
await marbasAxiosInstance.delete(`${endpoint}${found.id}`);
|
||||||
|
log.info(`Stopped tracking type ${type}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
marbasAxiosInstance.get<MarbasTrackedType[]>(endpoint).then(res => trackedTypes.value = res.data);
|
||||||
|
|
||||||
return { types, addType, removeType };
|
return { types, addType, removeType };
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -1,42 +1,35 @@
|
|||||||
import {esiAxiosInstance} from '@/service';
|
import { marbasAxiosInstance } from "@/marbas";
|
||||||
|
|
||||||
export type MarketType = {
|
export type MarketType = {
|
||||||
id: number;
|
id: number;
|
||||||
group_id: number;
|
group_id: number;
|
||||||
market_group_id: number;
|
marketgroup_id: number;
|
||||||
name: string;
|
name: string;
|
||||||
published: boolean;
|
published: boolean;
|
||||||
description: string;
|
description: string;
|
||||||
base_price: number;
|
basePrice: number;
|
||||||
icon_id: number;
|
icon_id: number;
|
||||||
volume: number;
|
volume: number;
|
||||||
portion_size: number;
|
portionSize: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
const cache = new Map<number, MarketType>(); // TODO move to pinia store
|
|
||||||
|
|
||||||
const fetchType = (id: number): Promise<MarketType> => {
|
|
||||||
if (cache.has(id)) {
|
|
||||||
return Promise.resolve(cache.get(id)!);
|
|
||||||
}
|
|
||||||
return esiAxiosInstance.get<Omit<MarketType, 'id'> & { type_id: number }>(`/universe/types/${id}/`).then(r => {
|
|
||||||
const { type_id, ...rest } = r.data;
|
|
||||||
const marketType: MarketType = { id: type_id, ...rest };
|
|
||||||
cache.set(id, marketType);
|
|
||||||
return marketType;
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
export const getMarketType = async (type: string | number): Promise<MarketType> => (await getMarketTypes([type]))[0];
|
export const getMarketType = async (type: string | number): Promise<MarketType> => (await getMarketTypes([type]))[0];
|
||||||
export const getMarketTypes = async (types: (string | number)[]): Promise<MarketType[]> => {
|
export const getMarketTypes = async (types: (string | number)[]): Promise<MarketType[]> => {
|
||||||
if (types.length === 0) {
|
if (types.length === 0) {
|
||||||
return [];
|
return [];
|
||||||
|
} else if (types.length === 1 && typeof types[0] === "number") {
|
||||||
|
return [(await marbasAxiosInstance.get<MarketType>(`/sde/types/${types[0]}/`)).data];
|
||||||
}
|
}
|
||||||
const ids = types.filter((t): t is number => typeof t === 'number');
|
return (await marbasAxiosInstance.post<MarketType[]>("/api/types/search", types.map(t => {
|
||||||
return Promise.all(ids.map(fetchType));
|
if (typeof t === "number") {
|
||||||
|
return { id: t };
|
||||||
|
} else {
|
||||||
|
return { name: t };
|
||||||
|
}
|
||||||
|
}))).data;
|
||||||
}
|
}
|
||||||
|
|
||||||
const blueprintMarketGroups = [ // TODO add all groups
|
const blueprintMarketGrous = [ // TODO add all groups
|
||||||
2,
|
2,
|
||||||
2157,
|
2157,
|
||||||
2159,
|
2159,
|
||||||
@@ -56,5 +49,9 @@ const blueprintMarketGroups = [ // TODO add all groups
|
|||||||
]
|
]
|
||||||
|
|
||||||
export const searchMarketTypes = async (search: string): Promise<MarketType[]> => {
|
export const searchMarketTypes = async (search: string): Promise<MarketType[]> => {
|
||||||
return []
|
return (await marbasAxiosInstance.post<MarketType[]>("/api/types/search", [{
|
||||||
|
name__icontains: search,
|
||||||
|
marketgroup_id___not: null,
|
||||||
|
marketgroup_id__in___not: blueprintMarketGrous,
|
||||||
|
}])).data;
|
||||||
}
|
}
|
||||||
@@ -1,9 +1,9 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import {vOnClickOutside} from '@vueuse/components';
|
import { vOnClickOutside } from '@vueuse/components';
|
||||||
import {useVirtualList} from '@vueuse/core';
|
import { useVirtualList } from '@vueuse/core';
|
||||||
import log from 'loglevel';
|
import log from 'loglevel';
|
||||||
import {nextTick, ref, watch, watchEffect} from 'vue';
|
import { nextTick, ref, watch, watchEffect } from 'vue';
|
||||||
import {MarketType, searchMarketTypes} from './MarketType';
|
import { MarketType, searchMarketTypes } from './MarketType';
|
||||||
import MarketTypeLabel from "./MarketTypeLabel.vue";
|
import MarketTypeLabel from "./MarketTypeLabel.vue";
|
||||||
|
|
||||||
interface Emits {
|
interface Emits {
|
||||||
@@ -89,7 +89,7 @@ watchEffect(async () => {
|
|||||||
<template>
|
<template>
|
||||||
<div @click="() => isOpen = true" v-on-click-outside="() => isOpen = false">
|
<div @click="() => isOpen = true" v-on-click-outside="() => isOpen = false">
|
||||||
<div class="fake-input">
|
<div class="fake-input">
|
||||||
<img v-if="modelValue?.type_id" :src="`https://images.evetech.net/types/${modelValue.type_id}/icon?size=32`" alt="" />
|
<img v-if="modelValue?.id" :src="`https://images.evetech.net/types/${modelValue.id}/icon?size=32`" alt="" />
|
||||||
<input type="text" v-model="name" @keyup.enter="submit" @keyup.down="moveDown" @keyup.up="moveUp" />
|
<input type="text" v-model="name" @keyup.enter="submit" @keyup.down="moveDown" @keyup.up="moveUp" />
|
||||||
</div>
|
</div>
|
||||||
<div v-if="suggestions.length > 1" class="z-20 absolute w-96">
|
<div v-if="suggestions.length > 1" class="z-20 absolute w-96">
|
||||||
@@ -104,8 +104,7 @@ watchEffect(async () => {
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped lang="postcss">
|
||||||
@reference "@/style.css";
|
|
||||||
.fake-input {
|
.fake-input {
|
||||||
@apply w-96 flex border bg-slate-500 rounded px-1 py-0.5;
|
@apply w-96 flex border bg-slate-500 rounded px-1 py-0.5;
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,6 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import {ClipboardButton} from '@/components';
|
import { ClipboardButton } from '@/components';
|
||||||
import {InformationCircleIcon} from '@heroicons/vue/24/outline';
|
import { InformationCircleIcon } from '@heroicons/vue/24/outline';
|
||||||
import {routeNames} from '@/routes';
|
|
||||||
import {computedAsync} from "@vueuse/core";
|
|
||||||
import {getMarketType} from "@/market";
|
|
||||||
|
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
@@ -12,39 +9,28 @@ interface Props {
|
|||||||
hideCopy?: boolean;
|
hideCopy?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const props = withDefaults(defineProps<Props>(), {
|
withDefaults(defineProps<Props>(), {
|
||||||
name: "",
|
name: "",
|
||||||
id: 0,
|
id: 0,
|
||||||
hideCopy: false
|
hideCopy: false
|
||||||
});
|
});
|
||||||
|
|
||||||
const computedName = computedAsync<string>(async () => {
|
|
||||||
if (props.name) {
|
|
||||||
return props.name;
|
|
||||||
} else if (props.id) {
|
|
||||||
return await getMarketType(props.id).then(marketType => marketType.name);
|
|
||||||
}
|
|
||||||
return "";
|
|
||||||
}, "");
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div v-if="id || computedName" class="flex flex-row">
|
<div v-if="id || name" class="flex flex-row">
|
||||||
<img v-if="id" :src="`https://images.evetech.net/types/${id}/icon?size=32`" class="inline-block w-5 h-5 me-1 mt-1" alt="" />
|
<img v-if="id" :src="`https://images.evetech.net/types/${id}/icon?size=32`" class="inline-block w-5 h-5 me-1 mt-1" alt="" />
|
||||||
<template v-if="computedName">
|
<template v-if="name">
|
||||||
{{ computedName }}
|
{{ name }}
|
||||||
<RouterLink v-if="id" :to="{ name: routeNames.marketTypes, params: { type: id } }" class="btn-icon ms-1 me-1 mt-1" title="Show item info">
|
<RouterLink v-if="id" :to="{ name: 'market-types', params: { type: id } }" class="button btn-icon ms-1 me-1 mt-1" title="Show item info">
|
||||||
<InformationCircleIcon />
|
<InformationCircleIcon />
|
||||||
</RouterLink>
|
</RouterLink>
|
||||||
<ClipboardButton v-if="!hideCopy" :value="computedName" />
|
<ClipboardButton v-if="!hideCopy" :value="name" />
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped lang="postcss">
|
||||||
@reference "@/style.css";
|
button:deep(>svg), .button:deep(>svg) {
|
||||||
|
|
||||||
button:deep(>svg), .btn-icon:deep(>svg) {
|
|
||||||
@apply !w-4 !h-4;
|
@apply !w-4 !h-4;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
@@ -1,27 +1,11 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
|
||||||
import {mammonAddCharacterUrl} from "@/mammon";
|
|
||||||
import {storeToRefs} from "pinia";
|
|
||||||
import {CharacterLabel, useCharactersStore} from "@/characters";
|
|
||||||
import {ArrowPathIcon} from '@heroicons/vue/24/outline';
|
|
||||||
|
|
||||||
const charactersStore = useCharactersStore()
|
|
||||||
const {characters} = storeToRefs(charactersStore);
|
|
||||||
const {reloadActivities} = charactersStore;
|
|
||||||
|
|
||||||
const addCharacter = () => {
|
const addCharacter = () => {
|
||||||
window.location.replace(mammonAddCharacterUrl);
|
// TODO
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="grid mb-2 mt-4">
|
<div class="grid mb-2 mt-4">
|
||||||
<div class="mb-4 border-b-1 flex justify-end">
|
<button class="justify-self-end" @click="addCharacter">Add chacarcter</button>
|
||||||
<button class="mb-2" @click="addCharacter">Add chacarcter</button>
|
|
||||||
</div>
|
|
||||||
<div v-for="character in characters" :key="character.characterId" class="flex items-center mb-2">
|
|
||||||
<CharacterLabel class="grow" :character="character" />
|
|
||||||
<button class="btn-icon" @click="reloadActivities(character.characterId)"><ArrowPathIcon /></button>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
<script setup lang="ts">
|
|
||||||
import {RouterView} from 'vue-router';
|
|
||||||
import {EditLedgerModal, useLedgersStore} from "@/ledger";
|
|
||||||
import {ref} from "vue";
|
|
||||||
import {activityApi, processingApi} from "@/mammon";
|
|
||||||
|
|
||||||
const {refresh} = useLedgersStore();
|
|
||||||
|
|
||||||
const editLedgerModal = ref<typeof EditLedgerModal>();
|
|
||||||
|
|
||||||
const processActivities = async () => {
|
|
||||||
await activityApi.fetchAllNewActivities();
|
|
||||||
await processingApi.processNewActivities();
|
|
||||||
await refresh();
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<div class="mt-4">
|
|
||||||
<div class="mb-4 border-b-1 flex justify-end">
|
|
||||||
<button class="mb-2 ms-2" @click="processActivities">Process Activities</button>
|
|
||||||
<button class="mb-2 ms-2" @click="editLedgerModal?.open()">New Ledger</button>
|
|
||||||
</div>
|
|
||||||
<EditLedgerModal ref="editLedgerModal" />
|
|
||||||
<RouterView />
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
+12
-3
@@ -1,12 +1,12 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import {RouterLink, RouterView} from 'vue-router';
|
import { RouterLink, RouterView } from 'vue-router';
|
||||||
import {routeNames} from '@/routes';
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="mt-4">
|
<div class="mt-4">
|
||||||
<div class="flex border-b-2 border-emerald-500">
|
<div class="flex border-b-2 border-emerald-500">
|
||||||
<RouterLink :to="{name: routeNames.marketTypes}" class="tab">
|
<RouterLink :to="{name: 'market-types'}" class="tab">
|
||||||
<span>Item Info</span>
|
<span>Item Info</span>
|
||||||
</RouterLink>
|
</RouterLink>
|
||||||
<RouterLink to="/market/tracking" class="tab">
|
<RouterLink to="/market/tracking" class="tab">
|
||||||
@@ -19,3 +19,12 @@ import {routeNames} from '@/routes';
|
|||||||
<RouterView />
|
<RouterView />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
<style scoped lang="postcss">
|
||||||
|
a.tab {
|
||||||
|
@apply flex items-center px-4 me-2 rounded-t-md bg-slate-600 hover:bg-slate-700;
|
||||||
|
&.router-link-active {
|
||||||
|
@apply bg-emerald-500 hover:bg-emerald-700;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
<script setup lang="ts">
|
|
||||||
import {RouterLink, RouterView} from "vue-router";
|
|
||||||
import {routeNames} from '@/routes';
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<div class="mt-4">
|
|
||||||
<div class="flex border-b-2 border-emerald-500">
|
|
||||||
<RouterLink :to="{ name: routeNames.listRuleBooks }" class="tab">
|
|
||||||
<span>Rule Books</span>
|
|
||||||
</RouterLink>
|
|
||||||
<RouterLink to="/characters/rules" class="tab">
|
|
||||||
<span>Characters Rules</span>
|
|
||||||
</RouterLink>
|
|
||||||
</div>
|
|
||||||
<RouterView />
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
@@ -1,106 +0,0 @@
|
|||||||
<script setup lang="ts">
|
|
||||||
|
|
||||||
import {findAllTransactionInLeger, useLedgerParam} from "@/ledger";
|
|
||||||
import {computedAsync} from "@vueuse/core";
|
|
||||||
import {TransactionResponse} from "@/generated/mammon";
|
|
||||||
import {IskLabel} from "@/market";
|
|
||||||
import {SortableHeader, useSort, VirtualScrollTable} from "@/components/table";
|
|
||||||
import {TransferList, TransferTypes} from "@/transaction";
|
|
||||||
import {Dropdown} from "@/components";
|
|
||||||
import {CharacterLabel, useCharactersStore} from "@/characters";
|
|
||||||
import {formatEveDate} from "@/formaters.ts";
|
|
||||||
|
|
||||||
const {ledgerId} = useLedgerParam();
|
|
||||||
const {findById} = useCharactersStore();
|
|
||||||
|
|
||||||
const transactions = computedAsync<TransactionResponse[]>(async () => {
|
|
||||||
if (ledgerId.value) {
|
|
||||||
return await findAllTransactionInLeger(ledgerId.value);
|
|
||||||
}
|
|
||||||
return [];
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const { sortedArray, headerProps } = useSort(computedAsync(() => Promise.all(transactions.value.map(async transaction => {
|
|
||||||
const character = await findById(transaction.characterId);
|
|
||||||
return {
|
|
||||||
character,
|
|
||||||
characterName: character?.name ?? "",
|
|
||||||
transactionId: transaction.transactionId,
|
|
||||||
description: transaction.description,
|
|
||||||
date: new Date(transaction.datetime),
|
|
||||||
balance: getIskBalance(transaction),
|
|
||||||
transfers: transaction.transfers
|
|
||||||
}
|
|
||||||
})), []), { defaultSortKey: 'date', defaultSortDirection: 'desc' });
|
|
||||||
|
|
||||||
const getIskBalance = (transaction: TransactionResponse) => {
|
|
||||||
if (!ledgerId.value) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
let balance = 0;
|
|
||||||
|
|
||||||
for (const transfer of transaction.transfers) {
|
|
||||||
if (transfer.type === TransferTypes.Isk) {
|
|
||||||
if (transfer.toLedgerId === ledgerId.value) {
|
|
||||||
balance += transfer.amount;
|
|
||||||
} else if (transfer.fromLedgerId === ledgerId.value) {
|
|
||||||
balance -= transfer.amount;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return balance;
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<div class="mt-4">
|
|
||||||
<VirtualScrollTable :list="sortedArray" :itemHeight="33" bottom="1rem">
|
|
||||||
<template #default="{ list }">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<SortableHeader v-bind="headerProps" sortKey="characterName">Character</SortableHeader>
|
|
||||||
<SortableHeader v-bind="headerProps" sortKey="date">Date</SortableHeader>
|
|
||||||
<SortableHeader v-bind="headerProps" sortKey="balance">Isk Change</SortableHeader>
|
|
||||||
<SortableHeader v-bind="headerProps" sortKey="description">Description</SortableHeader>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
<tr v-for="t in list" :key="t.data.transactionId">
|
|
||||||
<td>
|
|
||||||
<CharacterLabel v-if="t.data.character" :character="t.data.character" />
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<Dropdown class="transfer-dropdown">
|
|
||||||
<template #button>
|
|
||||||
{{formatEveDate(t.data.date)}}
|
|
||||||
</template>
|
|
||||||
<TransferList :transfers="t.data.transfers" />
|
|
||||||
</Dropdown>
|
|
||||||
</td>
|
|
||||||
<td class="text-right">
|
|
||||||
<IskLabel :amount="t.data.balance" />
|
|
||||||
</td>
|
|
||||||
<td>{{t.data.description}}</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</template>
|
|
||||||
</VirtualScrollTable>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
@reference "@/style.css";
|
|
||||||
|
|
||||||
tr:hover>td>.transfer-dropdown :deep(>button) {
|
|
||||||
@apply bg-slate-900;
|
|
||||||
}
|
|
||||||
|
|
||||||
.transfer-dropdown :deep(>button) {
|
|
||||||
@apply bg-slate-800 hover:bg-slate-900 border-none flex items-center w-full;
|
|
||||||
}
|
|
||||||
|
|
||||||
.transfer-dropdown.dropdown-open :deep(>button) {
|
|
||||||
@apply bg-slate-800 rounded-b-none;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -1,58 +0,0 @@
|
|||||||
<script setup lang="ts">
|
|
||||||
|
|
||||||
import {EditLedgerModal, Ledger, LedgerLabel, useLedgersStore} from "@/ledger";
|
|
||||||
import {storeToRefs} from "pinia";
|
|
||||||
import {nextTick, ref} from "vue";
|
|
||||||
import {PencilSquareIcon} from "@heroicons/vue/24/outline";
|
|
||||||
import {IskLabel} from "@/market";
|
|
||||||
import {SortableHeader, useSort, VirtualScrollTable} from "@/components/table";
|
|
||||||
|
|
||||||
const {ledgers} = storeToRefs(useLedgersStore());
|
|
||||||
|
|
||||||
const { sortedArray, headerProps } = useSort<Ledger>(ledgers);
|
|
||||||
|
|
||||||
const editModal = ref<typeof EditLedgerModal>();
|
|
||||||
const editingLedgerId = ref("");
|
|
||||||
|
|
||||||
const openEdit = async (ledgerId: string) => {
|
|
||||||
editingLedgerId.value = ledgerId;
|
|
||||||
await nextTick();
|
|
||||||
editModal.value?.open();
|
|
||||||
};
|
|
||||||
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<div class="mt-4">
|
|
||||||
<VirtualScrollTable :list="sortedArray" :itemHeight="33" bottom="1rem">
|
|
||||||
<template #default="{ list }">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<SortableHeader v-bind="headerProps" sortKey="name">Ledger</SortableHeader>
|
|
||||||
<SortableHeader v-bind="headerProps" sortKey="balance">Isk Balance</SortableHeader>
|
|
||||||
<SortableHeader v-bind="headerProps" sortKey="buttons" unsortable />
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
<tr v-for="l in list" :key="l.data.ledgerId">
|
|
||||||
<td>
|
|
||||||
<LedgerLabel :ledger="l.data" :link="true" />
|
|
||||||
</td>
|
|
||||||
<td class="text-right">
|
|
||||||
<IskLabel class="ms-2" :amount="l.data.balance" />
|
|
||||||
</td>
|
|
||||||
<td class="text-right">
|
|
||||||
<button class="btn-icon ms-2" @click="openEdit(l.data.ledgerId)"><PencilSquareIcon /></button>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</template>
|
|
||||||
<template #empty>
|
|
||||||
<div class="text-center mt-4">
|
|
||||||
<span>No ledgers found</span>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
</VirtualScrollTable>
|
|
||||||
</div>
|
|
||||||
<EditLedgerModal ref="editModal" :ledger-id="editingLedgerId" />
|
|
||||||
</template>
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
<script setup lang="ts">
|
|
||||||
import {RouterLink, RouterView} from 'vue-router';
|
|
||||||
import {isMain, useLedgerParam} from "@/ledger";
|
|
||||||
import {routeNames} from "@/routes.ts";
|
|
||||||
|
|
||||||
const {ledgerId, ledger} = useLedgerParam();
|
|
||||||
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<div v-if="ledger" class="mt-4">
|
|
||||||
<div class="flex border-b-2 border-emerald-500">
|
|
||||||
<RouterLink :to="{name: routeNames.viewLedgerBalance}" class="tab">
|
|
||||||
<span>Balance</span>
|
|
||||||
</RouterLink>
|
|
||||||
<RouterLink v-if="isMain(ledger)" :to="{name: routeNames.listLedgerTransactions}" class="tab">
|
|
||||||
<span>Transactions</span>
|
|
||||||
</RouterLink>
|
|
||||||
</div>
|
|
||||||
<RouterView />
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
@@ -1,60 +0,0 @@
|
|||||||
<script setup lang="ts">
|
|
||||||
import {getLedgerBalance, useLedgerParam} from "@/ledger";
|
|
||||||
import {computedAsync} from "@vueuse/core";
|
|
||||||
import {BalanceResponse} from "@/generated/mammon";
|
|
||||||
import {getMarketType, IskLabel, MarketTypeLabel} from "@/market";
|
|
||||||
import {SortableHeader, useSort, VirtualScrollTable} from "@/components/table";
|
|
||||||
|
|
||||||
const {ledgerId} = useLedgerParam();
|
|
||||||
|
|
||||||
const balance = computedAsync<BalanceResponse>(async () => {
|
|
||||||
if (ledgerId.value) {
|
|
||||||
return await getLedgerBalance(ledgerId.value);
|
|
||||||
}
|
|
||||||
return undefined;
|
|
||||||
});
|
|
||||||
|
|
||||||
const { sortedArray, headerProps } = useSort(computedAsync(async () => {
|
|
||||||
const itemBalances = balance.value?.itemBalances;
|
|
||||||
|
|
||||||
if (!itemBalances) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
return await Promise.all(itemBalances.map(async i => {
|
|
||||||
const item = await getMarketType(i.typeId);
|
|
||||||
|
|
||||||
return {
|
|
||||||
...i,
|
|
||||||
name: item.name
|
|
||||||
};
|
|
||||||
}));
|
|
||||||
}, []));
|
|
||||||
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<div v-if="balance" class="mt-4">
|
|
||||||
<div class="flex justify-end w-full">
|
|
||||||
<IskLabel class="mb-2" :amount="balance.iskBalance" />
|
|
||||||
</div>
|
|
||||||
<VirtualScrollTable :list="sortedArray" :itemHeight="33" bottom="1rem">
|
|
||||||
<template #default="{ list }">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<SortableHeader v-bind="headerProps" sortKey="name">Item</SortableHeader>
|
|
||||||
<SortableHeader v-bind="headerProps" sortKey="quantity">Balance</SortableHeader>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
<tr v-for="i in list" :key="i.data.typeId">
|
|
||||||
<td>
|
|
||||||
<MarketTypeLabel :id="i.data.typeId" :name="i.data.name" />
|
|
||||||
</td>
|
|
||||||
<td class="text-right">{{i.data.quantity}}</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</template>
|
|
||||||
</VirtualScrollTable>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
@@ -1,22 +1,16 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import {getMarketTypes, MarketTypePrice, useApraisalStore} from "@/market";
|
import { MarketTypePrice, getMarketTypes, useApraisalStore } from "@/market";
|
||||||
import {AcquiredType, AcquisitionResultTable, BuyModal, SellModal, useAcquiredTypesStore} from '@/market/acquisition';
|
import { AcquiredType, AcquisitionResultTable, BuyModal, SellModal, useAcquiredTypesStore } from '@/market/acquisition';
|
||||||
import {ref, watch} from 'vue';
|
import { ref, watch } from 'vue';
|
||||||
import {activityApi, processingApi} from "@/mammon";
|
|
||||||
|
|
||||||
const buyModal = ref<typeof BuyModal>();
|
const buyModal = ref<typeof BuyModal>();
|
||||||
const sellModal = ref<typeof SellModal>();
|
const sellModal = ref<typeof SellModal>();
|
||||||
|
|
||||||
|
|
||||||
const apraisalStore = useApraisalStore();
|
const apraisalStore = useApraisalStore();
|
||||||
const acquiredTypesStore = useAcquiredTypesStore();
|
const acquiredTypesStore = useAcquiredTypesStore();
|
||||||
const items = ref<AcquiredType[]>([]);
|
const items = ref<AcquiredType[]>([]);
|
||||||
|
|
||||||
const refresh = async () => {
|
|
||||||
await activityApi.fetchAllNewActivities();
|
|
||||||
await processingApi.processNewActivities();
|
|
||||||
await acquiredTypesStore.refresh();
|
|
||||||
}
|
|
||||||
|
|
||||||
watch(() => acquiredTypesStore.acquiredTypes, async itms => {
|
watch(() => acquiredTypesStore.acquiredTypes, async itms => {
|
||||||
if (itms.length === 0) {
|
if (itms.length === 0) {
|
||||||
return;
|
return;
|
||||||
@@ -40,9 +34,6 @@ watch(() => acquiredTypesStore.acquiredTypes, async itms => {
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="mt-4">
|
<div class="mt-4">
|
||||||
<div class="flex">
|
|
||||||
<button class="ms-auto" @click="refresh">Refresh</button>
|
|
||||||
</div>
|
|
||||||
<template v-if="items.length > 0">
|
<template v-if="items.length > 0">
|
||||||
<AcquisitionResultTable :items="items" @buy="(types, price, buy, sell) => buyModal?.open(types[0].type, { 'Price': price, 'Buy': buy, 'Sell': sell })" @sell="types => sellModal?.open(types)" ignoredColums="date" />
|
<AcquisitionResultTable :items="items" @buy="(types, price, buy, sell) => buyModal?.open(types[0].type, { 'Price': price, 'Buy': buy, 'Sell': sell })" @sell="types => sellModal?.open(types)" ignoredColums="date" />
|
||||||
<BuyModal ref="buyModal" />
|
<BuyModal ref="buyModal" />
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { Modal, ProgressBar } from "@/components";
|
|
||||||
import { MarketType, MarketTypeInput, MarketTypePrice, getHistory, getMarketTypes, useApraisalStore } from "@/market";
|
import { MarketType, MarketTypeInput, MarketTypePrice, getHistory, getMarketTypes, useApraisalStore } from "@/market";
|
||||||
import { BuyModal } from '@/market/acquisition';
|
import { BuyModal } from '@/market/acquisition';
|
||||||
import { TrackingResult, TrackingResultTable, createResult, useMarketTrackingStore } from '@/market/tracking';
|
import { TrackingResult, TrackingResultTable, createResult, useMarketTrackingStore } from '@/market/tracking';
|
||||||
@@ -57,6 +56,10 @@ watch(() => marketTrackingStore.types, async t => {
|
|||||||
|
|
||||||
const prices = await apraisalStore.getPrices(await getMarketTypes(typesToLoad));
|
const prices = await apraisalStore.getPrices(await getMarketTypes(typesToLoad));
|
||||||
|
|
||||||
|
items.value = [
|
||||||
|
...items.value
|
||||||
|
];
|
||||||
|
|
||||||
typesToLoad.forEach(async i => items.value.push(await createResult(i, prices.find(p => p.type.id === i) as MarketTypePrice)));
|
typesToLoad.forEach(async i => items.value.push(await createResult(i, prices.find(p => p.type.id === i) as MarketTypePrice)));
|
||||||
}, { immediate: true });
|
}, { immediate: true });
|
||||||
</script>
|
</script>
|
||||||
@@ -73,10 +76,5 @@ watch(() => marketTrackingStore.types, async t => {
|
|||||||
<hr />
|
<hr />
|
||||||
<TrackingResultTable :items="items" @buy="(type, buy, sell) => buyModal?.open(type, { 'Buy': buy, 'Sell': sell })" @remove="removeItem" />
|
<TrackingResultTable :items="items" @buy="(type, buy, sell) => buyModal?.open(type, { 'Buy': buy, 'Sell': sell })" @remove="removeItem" />
|
||||||
<BuyModal ref="buyModal" />
|
<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>
|
||||||
</template>
|
</template>
|
||||||
@@ -1,14 +1,13 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import {ClipboardButton} from '@/components';
|
import { ClipboardButton } from '@/components';
|
||||||
import {getMarketType, MarketType, MarketTypeInput, useApraisalStore} from "@/market";
|
import { MarketType, MarketTypeInput, getMarketType, useApraisalStore } from "@/market";
|
||||||
import {AcquisitionResultTable, BuyModal, useAcquiredTypesStore} from '@/market/acquisition';
|
import { AcquisitionResultTable, BuyModal, useAcquiredTypesStore } from '@/market/acquisition';
|
||||||
import {createResult, TrackingResultTable, useMarketTrackingStore} from '@/market/tracking';
|
import { TrackingResultTable, createResult, useMarketTrackingStore } from '@/market/tracking';
|
||||||
import {BookmarkIcon, BookmarkSlashIcon, ShoppingCartIcon} from '@heroicons/vue/24/outline';
|
import { BookmarkIcon, BookmarkSlashIcon, ShoppingCartIcon } from '@heroicons/vue/24/outline';
|
||||||
|
import { computedAsync } from '@vueuse/core/index.cjs';
|
||||||
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 {computedAsync} from "@vueuse/core";
|
|
||||||
|
|
||||||
const buyModal = ref<typeof BuyModal>();
|
const buyModal = ref<typeof BuyModal>();
|
||||||
|
|
||||||
@@ -53,7 +52,7 @@ const view = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
router.push({
|
router.push({
|
||||||
name: routeNames.marketTypes,
|
name: 'market-types',
|
||||||
params: {
|
params: {
|
||||||
type: inputItem.value.id
|
type: inputItem.value.id
|
||||||
}
|
}
|
||||||
@@ -104,7 +103,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']" />
|
<TrackingResultTable :items="[result]" infoOnly ignoredColums="name" />
|
||||||
</div>
|
</div>
|
||||||
<div v-if="acquisitions && acquisitions.length > 0">
|
<div v-if="acquisitions && acquisitions.length > 0">
|
||||||
<span>Acquisitions:</span>
|
<span>Acquisitions:</span>
|
||||||
@@ -114,9 +113,7 @@ watch(useRoute(), async route => {
|
|||||||
<BuyModal ref="buyModal" />
|
<BuyModal ref="buyModal" />
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped lang="postcss">
|
||||||
@reference "@/style.css";
|
|
||||||
|
|
||||||
img.type-image {
|
img.type-image {
|
||||||
width: 64px;
|
width: 64px;
|
||||||
height: 64px;
|
height: 64px;
|
||||||
|
|||||||
@@ -1,96 +0,0 @@
|
|||||||
<script setup lang="ts">
|
|
||||||
import {Character, CharacterLabel, useCharactersStore} from "@/characters";
|
|
||||||
import {useRoute} from "vue-router";
|
|
||||||
import {computed, ref, watch, watchEffect} from "vue";
|
|
||||||
import log from "loglevel";
|
|
||||||
import {
|
|
||||||
findCharacterRuleBookByCharacterId,
|
|
||||||
RuleBook,
|
|
||||||
setCharacterRuleBookForCharacter,
|
|
||||||
useRuleBooksStore
|
|
||||||
} from "@/rules";
|
|
||||||
import {storeToRefs} from "pinia";
|
|
||||||
import {isMain, Ledger, LedgerSelect, systemLedger, useLedgersStore} from "@/ledger";
|
|
||||||
|
|
||||||
type Bindings = { [key: string]: Ledger; };
|
|
||||||
|
|
||||||
const ruleBookStore = useRuleBooksStore();
|
|
||||||
const {findById: findRuleBookById} = ruleBookStore;
|
|
||||||
const {ruleBooks} = storeToRefs(ruleBookStore);
|
|
||||||
const {findById: findCharacterById} = useCharactersStore();
|
|
||||||
const {ledgers} = storeToRefs(useLedgersStore());
|
|
||||||
|
|
||||||
const ledgersToUse = computed(() => [systemLedger, ...ledgers.value.filter(isMain)]);
|
|
||||||
|
|
||||||
const character = ref<Character>();
|
|
||||||
const ruleBook = ref<RuleBook>();
|
|
||||||
const bindings = ref<Bindings>({});
|
|
||||||
const ledgerRefs = computed<string[]>(() => ruleBook.value?.ledgerRefs ?? [])
|
|
||||||
|
|
||||||
watchEffect(async () => {
|
|
||||||
const characterId = character.value?.characterId;
|
|
||||||
|
|
||||||
if (characterId) {
|
|
||||||
const characterRuleBook = await findCharacterRuleBookByCharacterId(characterId);
|
|
||||||
|
|
||||||
ruleBook.value = findRuleBookById(characterRuleBook.ruleBookId);
|
|
||||||
bindings.value = Object.fromEntries(
|
|
||||||
Object.entries(characterRuleBook.bindings)
|
|
||||||
.map(([key, id]) => [key, ledgersToUse.value.find(l => l.ledgerId === id) ?? systemLedger])
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const save = () => {
|
|
||||||
const characterId = character.value?.characterId;
|
|
||||||
const ruleBookId = ruleBook.value?.ruleBookId;
|
|
||||||
|
|
||||||
if (characterId && ruleBookId) {
|
|
||||||
setCharacterRuleBookForCharacter(characterId, {
|
|
||||||
ruleBookId,
|
|
||||||
bindings: Object.fromEntries(
|
|
||||||
Object.entries(bindings.value)
|
|
||||||
.map(([key, ledger]) => [key, ledger.ledgerId])
|
|
||||||
)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
watch(useRoute(), async route => {
|
|
||||||
if (route.params.characterId) {
|
|
||||||
const id = parseInt(typeof route.params.characterId === 'string' ? route.params.characterId : route.params.characterId[0]);
|
|
||||||
|
|
||||||
character.value = await findCharacterById(id);
|
|
||||||
log.info('Loaded character:', character.value);
|
|
||||||
} else {
|
|
||||||
character.value = undefined;
|
|
||||||
log.info('No character to load');
|
|
||||||
}
|
|
||||||
}, { immediate: true })
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<div v-if="character" class="grid mb-2 mt-4">
|
|
||||||
<div class="mb-2 border-b-1 flex">
|
|
||||||
<CharacterLabel class="flex grow mb-2" :character="character" :size="64" />
|
|
||||||
<div>
|
|
||||||
<button @click="save">Save</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="flex-col border-b-1">
|
|
||||||
Rule Book:
|
|
||||||
<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>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
<div class="flex-col border-b-1">
|
|
||||||
Ledger Bindings:
|
|
||||||
<div class="flex flex-wrap items-center mb-2 mt-2">
|
|
||||||
<div class="me-2" v-for="ref in ledgerRefs" :ref="ref">
|
|
||||||
<span class="me-1">{{ref}}:</span>
|
|
||||||
<LedgerSelect :ledgers="ledgersToUse" :modelValue="bindings[ref] ?? systemLedger" @update:modelValue="value => { if (value) bindings[ref] = value }" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
@@ -1,119 +0,0 @@
|
|||||||
<script setup lang="ts">
|
|
||||||
import {useRoute, useRouter} from "vue-router";
|
|
||||||
import {ref, watch} from "vue";
|
|
||||||
import {useDebounceFn, useEventListener} from "@vueuse/core";
|
|
||||||
import log from "loglevel";
|
|
||||||
import {ScriptEditor, useRuleBooksStore} from "@/rules";
|
|
||||||
import {PlusIcon, TrashIcon} from "@heroicons/vue/24/outline";
|
|
||||||
import {routeNames} from "@/routes";
|
|
||||||
import {SliderCheckbox} from "@/components";
|
|
||||||
|
|
||||||
const ruleBookId = ref<string>();
|
|
||||||
const name = ref<string>('');
|
|
||||||
const usedForAcquisitions = ref<boolean>(false);
|
|
||||||
const ledgerRefs = ref<string[]>([]);
|
|
||||||
const script = ref<string>('');
|
|
||||||
|
|
||||||
const {findById, create, update, refresh} = useRuleBooksStore();
|
|
||||||
const router = useRouter();
|
|
||||||
|
|
||||||
const save = async () => {
|
|
||||||
if (!ruleBookId.value) {
|
|
||||||
const created = await create({
|
|
||||||
name: name.value,
|
|
||||||
usedForAcquisitions: usedForAcquisitions.value,
|
|
||||||
ledgerRefs: ledgerRefs.value,
|
|
||||||
script: script.value
|
|
||||||
})
|
|
||||||
await router.push({ name: routeNames.editRuleBook, params: {ruleBookId: created.ruleBookId}})
|
|
||||||
|
|
||||||
} else {
|
|
||||||
await update(ruleBookId.value, {
|
|
||||||
name: name.value,
|
|
||||||
usedForAcquisitions: usedForAcquisitions.value,
|
|
||||||
ledgerRefs: ledgerRefs.value,
|
|
||||||
script: script.value
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
useEventListener(window, 'keydown', (event: KeyboardEvent) => {
|
|
||||||
if ((event.metaKey || event.ctrlKey) && event.key.toLowerCase() === 's') {
|
|
||||||
event.preventDefault();
|
|
||||||
save();
|
|
||||||
}
|
|
||||||
}, {capture: true});
|
|
||||||
|
|
||||||
const addLedgerRef = () => {
|
|
||||||
ledgerRefs.value = [...ledgerRefs.value, '']
|
|
||||||
}
|
|
||||||
const updateLedgerRef = useDebounceFn((index: number, value: string) => {
|
|
||||||
ledgerRefs.value[index] = value;
|
|
||||||
}, 500);
|
|
||||||
|
|
||||||
const removeLedgerRef = (index: number) => {
|
|
||||||
ledgerRefs.value = ledgerRefs.value.toSpliced(index, 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
watch(useRoute(), async route => {
|
|
||||||
if (route.params.ruleBookId) {
|
|
||||||
const promise = refresh(); // FIXME don't call refresh
|
|
||||||
|
|
||||||
const id = typeof route.params.ruleBookId === 'string' ? route.params.ruleBookId : route.params.ruleBookId[0];
|
|
||||||
|
|
||||||
await promise;
|
|
||||||
|
|
||||||
const ruleBook = findById(id);
|
|
||||||
|
|
||||||
ruleBookId.value = id;
|
|
||||||
name.value = ruleBook?.name ?? '';
|
|
||||||
usedForAcquisitions.value = ruleBook?.usedForAcquisitions ?? false;
|
|
||||||
ledgerRefs.value = [...(ruleBook?.ledgerRefs ?? [])];
|
|
||||||
script.value = ruleBook?.script ?? '';
|
|
||||||
log.info('Loaded rule book:', ruleBook);
|
|
||||||
} else {
|
|
||||||
ruleBookId.value = undefined;
|
|
||||||
name.value = '';
|
|
||||||
usedForAcquisitions.value = false;
|
|
||||||
ledgerRefs.value = [];
|
|
||||||
script.value = '';
|
|
||||||
log.info('No rule book to load');
|
|
||||||
}
|
|
||||||
}, { immediate: true })
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<div class="flex flex-col mb-2 mt-4 h-[calc(100vh-4.5rem)]">
|
|
||||||
<div class="flex flex-col grow min-h-0">
|
|
||||||
<div class="flex grow border-b-1">
|
|
||||||
Name:
|
|
||||||
<input class="mb-2 ms-2" type="text" v-model="name" />
|
|
||||||
<label class="flex items-center ms-2 mb-2">
|
|
||||||
<SliderCheckbox class="me-2" v-model="usedForAcquisitions" />
|
|
||||||
Used for acquisitions
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
<div class="border-b-1">
|
|
||||||
Ledgers References:
|
|
||||||
<div class="flex flex-wrap items-center mt-2">
|
|
||||||
<div class="flex items-center mb-2" v-for="(ledgerRef, index) in ledgerRefs" :key="index">
|
|
||||||
<input class="me-1" type="text" :value="ledgerRef" @input="updateLedgerRef(index, ($event.target as HTMLInputElement).value)" />
|
|
||||||
<button class="btn-icon me-2" @click="removeLedgerRef(index)"><TrashIcon /></button>
|
|
||||||
</div>
|
|
||||||
<div class="flex items-center mb-2">
|
|
||||||
<button class="btn-icon" @click="addLedgerRef"><PlusIcon /></button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="flex flex-col grow min-h-0 border-b-1">
|
|
||||||
Script:
|
|
||||||
<ScriptEditor class="mt-2 mb-2" v-model="script" :ledgerRefs="ledgerRefs" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="mt-2 justify-end flex">
|
|
||||||
<div>
|
|
||||||
<button @click="save">Save</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
@@ -1,57 +0,0 @@
|
|||||||
<script setup lang="ts">
|
|
||||||
import {storeToRefs} from "pinia";
|
|
||||||
import {Character, CharacterLabel, useCharactersStore} from "@/characters";
|
|
||||||
import {PencilSquareIcon} from "@heroicons/vue/24/outline";
|
|
||||||
import {findCharacterRuleBookByCharacterId, useRuleBooksStore} from "@/rules";
|
|
||||||
import {computedAsync} from "@vueuse/core";
|
|
||||||
import {routeNames} from "@/routes.ts";
|
|
||||||
import {SortableHeader, useSort} from "@/components/table";
|
|
||||||
|
|
||||||
type CharacterRuleBookView = {
|
|
||||||
character: Character;
|
|
||||||
characterName: string;
|
|
||||||
characterId: number;
|
|
||||||
ruleBookName: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
const {characters} = storeToRefs(useCharactersStore());
|
|
||||||
const {findById: findRuleBookById} = useRuleBooksStore();
|
|
||||||
|
|
||||||
|
|
||||||
const { sortedArray, headerProps } = useSort(computedAsync<CharacterRuleBookView[]>(async () => await Promise.all(characters.value.map(async (character: Character): Promise<CharacterRuleBookView> => {
|
|
||||||
const characterRuleBook = await findCharacterRuleBookByCharacterId(character.characterId);
|
|
||||||
const ruleBook = findRuleBookById(characterRuleBook.ruleBookId);
|
|
||||||
|
|
||||||
return {
|
|
||||||
character,
|
|
||||||
characterName: character.name,
|
|
||||||
characterId: character.characterId,
|
|
||||||
ruleBookName: ruleBook?.name ?? ''
|
|
||||||
}
|
|
||||||
})), []))
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<div class="grid mb-2 mt-4">
|
|
||||||
<table>
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<SortableHeader v-bind="headerProps" sortKey="characterName">Character</SortableHeader>
|
|
||||||
<SortableHeader v-bind="headerProps" sortKey="ruleBookName">Rule Book</SortableHeader>
|
|
||||||
<SortableHeader v-bind="headerProps" sortKey="buttons" unsortable />
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
<tr v-for="characterRuleBookView in sortedArray" :key="characterRuleBookView.characterId" >
|
|
||||||
<td>
|
|
||||||
<CharacterLabel :character="characterRuleBookView.character" />
|
|
||||||
</td>
|
|
||||||
<td>{{characterRuleBookView.ruleBookName}}</td>
|
|
||||||
<td class="text-right">
|
|
||||||
<RouterLink class="btn-icon" :to="{ name: routeNames.editCharacterRulebook, params: { characterId: characterRuleBookView.characterId } }"><PencilSquareIcon /></RouterLink>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
@@ -1,37 +0,0 @@
|
|||||||
<script setup lang="ts">
|
|
||||||
import {storeToRefs} from "pinia";
|
|
||||||
import {DocumentDuplicateIcon, PencilSquareIcon, TrashIcon} from "@heroicons/vue/24/outline";
|
|
||||||
import {confirm} from "@/confirm";
|
|
||||||
import {RuleBook, useRuleBooksStore} from "@/rules";
|
|
||||||
import {routeNames} from "@/routes";
|
|
||||||
|
|
||||||
const ruleBooksStore = useRuleBooksStore();
|
|
||||||
const {ruleBooks} = storeToRefs(ruleBooksStore);
|
|
||||||
|
|
||||||
const duplicate = async (ruleBook: RuleBook) => {
|
|
||||||
if (await confirm({title: "Duplicate Rule Book", message: `Duplicate ${ruleBook.name}?`, confirmLabel: "Duplicate"})) {
|
|
||||||
await ruleBooksStore.duplicate(ruleBook);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const remove = async (ruleBook: RuleBook) => {
|
|
||||||
if (await confirm({title: "Delete Rule Book", message: `Delete ${ruleBook.name}?`, confirmLabel: "Delete", danger: true})) {
|
|
||||||
await ruleBooksStore.remove(ruleBook.ruleBookId);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<div class="grid mb-2 mt-4">
|
|
||||||
<div class="flex justify-end border-b-1">
|
|
||||||
<RouterLink class="button mb-2 ms-2" :to="{ name: routeNames.newRuleBook}">New Rule Book</RouterLink>
|
|
||||||
</div>
|
|
||||||
<div v-for="ruleBook in ruleBooks" :key="ruleBook.ruleBookId" class="flex items-center mt-2">
|
|
||||||
<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>
|
|
||||||
<button class="btn-icon me-1" @click="duplicate(ruleBook)"><DocumentDuplicateIcon /></button>
|
|
||||||
<button class="btn-icon text-amber-700 hover:text-amber-600" @click="remove(ruleBook)"><TrashIcon /></button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
export class Preference<T> {
|
||||||
|
private key: string;
|
||||||
|
private description: string;
|
||||||
|
private value?: T;
|
||||||
|
private defaultValue?: T;
|
||||||
|
|
||||||
|
constructor(key: string, description: string, defaultValue?: T) {
|
||||||
|
this.key = key;
|
||||||
|
this.description = description;
|
||||||
|
this.defaultValue = defaultValue;
|
||||||
|
this.value = this.load();
|
||||||
|
}
|
||||||
|
|
||||||
|
private load() {
|
||||||
|
const value = localStorage.getItem(this.key);
|
||||||
|
|
||||||
|
if (value) {
|
||||||
|
return JSON.parse(value);
|
||||||
|
}
|
||||||
|
return this.defaultValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -12,8 +12,7 @@ const modelValue = defineModel({ default: false });
|
|||||||
</label>
|
</label>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped lang="postcss">
|
||||||
@reference "@/style.css";
|
|
||||||
input:checked ~ span:last-child {
|
input:checked ~ span:last-child {
|
||||||
--tw-translate-x: 1.75rem;
|
--tw-translate-x: 1.75rem;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import { marbasAxiosInstance } from "@/marbas";
|
||||||
|
|
||||||
export type ReprocessItemValues = {
|
export type ReprocessItemValues = {
|
||||||
typeID: number;
|
typeID: number;
|
||||||
name: string;
|
name: string;
|
||||||
@@ -20,5 +22,7 @@ export const reprocess = async (items: string, minerals: string, efficiency?: nu
|
|||||||
};
|
};
|
||||||
const source = JSON.stringify(sourceJson);
|
const source = JSON.stringify(sourceJson);
|
||||||
|
|
||||||
return []
|
const response = await marbasAxiosInstance.post('/reprocess/', source, {params: {efficiency: efficiency ?? 0.55}});
|
||||||
|
|
||||||
|
return response.data;
|
||||||
};
|
};
|
||||||
|
|||||||
+14
-54
@@ -1,56 +1,16 @@
|
|||||||
import {RouteRecordRaw} from 'vue-router';
|
import { RouteRecordRaw } from 'vue-router';
|
||||||
|
|
||||||
export const routeNames = {
|
|
||||||
home: 'home',
|
|
||||||
callback: 'callback',
|
|
||||||
viewLedger: 'view-ledger',
|
|
||||||
viewLedgerBalance: 'view-ledger-balance',
|
|
||||||
listLedgerTransactions: 'list-ledger-transactions',
|
|
||||||
listRuleBooks: 'list-rule-books',
|
|
||||||
newRuleBook: 'new-rule-book',
|
|
||||||
editRuleBook: 'edit-rule-book',
|
|
||||||
editCharacterRulebook: 'edit-character-rule-book',
|
|
||||||
marketTypes: 'market-types',
|
|
||||||
about: 'about',
|
|
||||||
} as const;
|
|
||||||
|
|
||||||
export const routes: RouteRecordRaw[] = [
|
export const routes: RouteRecordRaw[] = [
|
||||||
{path: '/', name: routeNames.home, component: () => import('@/pages/Index.vue')},
|
{ path: '/', name: 'home', component: () => import('@/pages/Index.vue') },
|
||||||
{path: '/callback', name: routeNames.callback, component: () => import('@/pages/Index.vue')},
|
{ path: '/callback', name: 'callback', component: () => import('@/pages/Index.vue') },
|
||||||
|
{ path: '/reprocess', component: () => import('@/pages/Reprocess.vue') },
|
||||||
{path: '/ledgers', component: () => import('@/pages/Ledgers.vue'), children: [
|
{ path: '/market', component: () => import('@/pages/Market.vue'), children: [
|
||||||
{path: '', component: () => import('@/pages/ledger/ListLedgers.vue')},
|
{ path: '', redirect: '/market/types' },
|
||||||
{path: ':ledgerId', component: () => import('./pages/ledger/ViewLedger.vue'), children: [
|
{ path: 'types/:type?', name: 'market-types', component: () => import('@/pages/market/TypeInfo.vue') },
|
||||||
{path: '', name: routeNames.viewLedger, redirect: {name: routeNames.viewLedgerBalance}},
|
{ path: 'tracking', component: () => import('@/pages/market/Tracking.vue') },
|
||||||
{path: 'balance', name: routeNames.viewLedgerBalance, component: () => import('@/pages/ledger/ViewLedgerBalance.vue')},
|
{ path: 'acquisitions', component: () => import('@/pages/market/Acquisitions.vue') },
|
||||||
{path: 'transactions', name: routeNames.listLedgerTransactions, component: () => import('@/pages/ledger/ListLedgerTransactions.vue')},
|
] },
|
||||||
]},
|
{ path: '/tools', component: () => import('@/pages/Tools.vue') },
|
||||||
]},
|
{ path: '/characters', component: () => import('@/pages/Characters.vue') },
|
||||||
|
{ path: '/about', name: 'about', component: () => import('@/pages/About.vue') },
|
||||||
{path: '/rules', component: () => import('@/pages/Rules.vue'), children: [
|
];
|
||||||
{path: '', redirect: {name: routeNames.listRuleBooks}},
|
|
||||||
{path: '/rule-books', children: [
|
|
||||||
{path: '', name: routeNames.listRuleBooks, component: () => import('@/pages/rules/ListRuleBooks.vue')},
|
|
||||||
{path: 'new', name: routeNames.newRuleBook, component: () => import('@/pages/rules/EditRuleBook.vue')},
|
|
||||||
{path: ':ruleBookId', name: routeNames.editRuleBook, component: () => import('@/pages/rules/EditRuleBook.vue')},
|
|
||||||
]},
|
|
||||||
{path: '/characters/rules', children: [
|
|
||||||
{path: '', component: () => import('@/pages/rules/ListCharacterRuleBooks.vue')},
|
|
||||||
{path: '/characters/:characterId/rules', name: routeNames.editCharacterRulebook, component: () => import('@/pages/rules/EditCharacterRuleBook.vue')},
|
|
||||||
]}
|
|
||||||
]},
|
|
||||||
|
|
||||||
{path: '/market', component: () => import('@/pages/Market.vue'), children: [
|
|
||||||
{path: '', redirect: {name: routeNames.marketTypes}},
|
|
||||||
{path: 'types/:type?', name: routeNames.marketTypes, component: () => import('@/pages/market/TypeInfo.vue')},
|
|
||||||
{path: 'tracking', component: () => import('@/pages/market/Tracking.vue')},
|
|
||||||
{path: 'acquisitions', component: () => import('@/pages/market/Acquisitions.vue')},
|
|
||||||
]},
|
|
||||||
|
|
||||||
{path: '/reprocess', component: () => import('@/pages/Reprocess.vue')},
|
|
||||||
|
|
||||||
{path: '/tools', component: () => import('@/pages/Tools.vue')},
|
|
||||||
|
|
||||||
{path: '/characters', component: () => import('@/pages/Characters.vue')},
|
|
||||||
{path: '/about', name: routeNames.about, component: () => import('@/pages/About.vue')},
|
|
||||||
] as const;
|
|
||||||
@@ -1,104 +0,0 @@
|
|||||||
<script setup lang="ts">
|
|
||||||
import * as monaco from 'monaco-editor';
|
|
||||||
import editorWorker from 'monaco-editor/esm/vs/editor/editor.worker?worker';
|
|
||||||
import tsWorker from 'monaco-editor/esm/vs/language/typescript/ts.worker?worker';
|
|
||||||
import {onBeforeUnmount, onMounted, ref, watch} from 'vue';
|
|
||||||
import {fetchScriptDefinitions} from './rules';
|
|
||||||
|
|
||||||
(self as unknown as { MonacoEnvironment: { getWorker(workerId: string, label: string): Worker } }).MonacoEnvironment = {
|
|
||||||
getWorker(_workerId: string, label: string) {
|
|
||||||
if (label === 'typescript' || label === 'javascript') {
|
|
||||||
return new tsWorker();
|
|
||||||
}
|
|
||||||
return new editorWorker();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let extraLibLoaded = false;
|
|
||||||
|
|
||||||
const loadScriptDefinitions = async () => {
|
|
||||||
if (extraLibLoaded) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
const definitions = await fetchScriptDefinitions();
|
|
||||||
monaco.typescript.javascriptDefaults.addExtraLib(definitions, 'ts:rule-runner.d.ts');
|
|
||||||
extraLibLoaded = true;
|
|
||||||
} catch {
|
|
||||||
// type definitions are optional — the editor still works without autocomplete
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const props = defineProps<{ ledgerRefs?: string[] }>();
|
|
||||||
|
|
||||||
let ledgersLib: monaco.IDisposable | undefined;
|
|
||||||
|
|
||||||
const updateLedgerRefs = (refs: readonly string[]) => {
|
|
||||||
ledgersLib?.dispose();
|
|
||||||
const members = refs
|
|
||||||
.filter(ref => ref && ref !== 'system')
|
|
||||||
.map(ref => ` readonly ${JSON.stringify(ref)}: Ledger;`)
|
|
||||||
.join('\n');
|
|
||||||
ledgersLib = monaco.typescript.javascriptDefaults.addExtraLib(
|
|
||||||
`declare interface Ledgers {\n${members}\n}\n`,
|
|
||||||
'ts:rule-runner.ledgers.d.ts'
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const model = defineModel<string>({default: ''});
|
|
||||||
const container = ref<HTMLElement>();
|
|
||||||
let editor: monaco.editor.IStandaloneCodeEditor | undefined;
|
|
||||||
|
|
||||||
onMounted(async () => {
|
|
||||||
await loadScriptDefinitions();
|
|
||||||
updateLedgerRefs(props.ledgerRefs ?? []);
|
|
||||||
|
|
||||||
if (!container.value) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
editor = monaco.editor.create(container.value, {
|
|
||||||
value: model.value,
|
|
||||||
language: 'javascript',
|
|
||||||
theme: 'vs-dark',
|
|
||||||
automaticLayout: true,
|
|
||||||
minimap: {enabled: false},
|
|
||||||
scrollBeyondLastLine: false,
|
|
||||||
fontSize: 13,
|
|
||||||
tabSize: 2,
|
|
||||||
});
|
|
||||||
|
|
||||||
editor.onDidChangeModelContent(() => {
|
|
||||||
const value = editor!.getValue();
|
|
||||||
|
|
||||||
if (value !== model.value) {
|
|
||||||
model.value = value;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
watch(model, value => {
|
|
||||||
if (editor && value !== editor.getValue()) {
|
|
||||||
editor.setValue(value ?? '');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
watch(() => props.ledgerRefs, refs => updateLedgerRefs(refs ?? []), {deep: true});
|
|
||||||
|
|
||||||
onBeforeUnmount(() => {
|
|
||||||
editor?.dispose();
|
|
||||||
ledgersLib?.dispose();
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<div ref="container" class="script-editor"></div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
.script-editor {
|
|
||||||
width: 100%;
|
|
||||||
flex: 1 1 auto;
|
|
||||||
min-height: 12rem;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
export * from "./rules";
|
|
||||||
|
|
||||||
export {default as ScriptEditor} from './ScriptEditor.vue';
|
|
||||||
@@ -1,61 +0,0 @@
|
|||||||
import {characterRuleBookApi, ruleBookApi} from "@/mammon";
|
|
||||||
import {
|
|
||||||
CharacterRuleBookResponse,
|
|
||||||
CreateRuleBookRequest,
|
|
||||||
RuleBookResponse,
|
|
||||||
SetCharacterRuleBookRequest
|
|
||||||
} from "@/generated/mammon";
|
|
||||||
import {defineStore} from "pinia";
|
|
||||||
import {ref, triggerRef} from "vue";
|
|
||||||
|
|
||||||
export type RuleBook = RuleBookResponse;
|
|
||||||
|
|
||||||
export const useRuleBooksStore = defineStore('rule-books', () => {
|
|
||||||
const ruleBooks = ref<RuleBook[]>([]);
|
|
||||||
|
|
||||||
const addRuleBook = (ruleBook: RuleBook) => {
|
|
||||||
ruleBooks.value.push(ruleBook);
|
|
||||||
triggerRef(ruleBooks);
|
|
||||||
return ruleBook;
|
|
||||||
};
|
|
||||||
|
|
||||||
const replaceRuleBook = (ruleBook: RuleBook) => {
|
|
||||||
const index = ruleBooks.value.findIndex(rb => rb.ruleBookId === ruleBook.ruleBookId);
|
|
||||||
|
|
||||||
if (index !== -1) {
|
|
||||||
ruleBooks.value[index] = ruleBook;
|
|
||||||
}
|
|
||||||
triggerRef(ruleBooks);
|
|
||||||
return ruleBook;
|
|
||||||
};
|
|
||||||
|
|
||||||
const findById = (ruleBookId: string): RuleBook | undefined => ruleBooks.value.find(rb => rb.ruleBookId === ruleBookId);
|
|
||||||
const create = (ruleBook: CreateRuleBookRequest) => ruleBookApi.createRuleBook(ruleBook).then(response => addRuleBook(response.data));
|
|
||||||
const update = (ruleBookId: string, ruleBook: CreateRuleBookRequest) => ruleBookApi.updateRuleBook(ruleBookId, ruleBook).then(response => replaceRuleBook(response.data));
|
|
||||||
const duplicate = (ruleBook: RuleBook) => create({
|
|
||||||
name: `${ruleBook.name} (copy)`,
|
|
||||||
usedForAcquisitions: ruleBook.usedForAcquisitions,
|
|
||||||
ledgerRefs: [...ruleBook.ledgerRefs],
|
|
||||||
script: ruleBook.script,
|
|
||||||
});
|
|
||||||
|
|
||||||
const remove = (ruleBookId: string) => ruleBookApi.deleteRuleBook(ruleBookId).then(() => {
|
|
||||||
ruleBooks.value = ruleBooks.value.filter(rb => rb.ruleBookId !== ruleBookId);
|
|
||||||
});
|
|
||||||
|
|
||||||
const refresh = () => ruleBookApi.findAllRuleBooks().then(response => ruleBooks.value = response.data);
|
|
||||||
|
|
||||||
refresh();
|
|
||||||
|
|
||||||
return {ruleBooks, findById, create, update, duplicate, remove, refresh};
|
|
||||||
})
|
|
||||||
|
|
||||||
export const findCharacterRuleBookByCharacterId = (characterId: number): Promise<CharacterRuleBookResponse> => characterRuleBookApi.findCharacterRuleBookByCharacterId(characterId)
|
|
||||||
.then(response => response.data)
|
|
||||||
.catch(() => ({characterId, ruleBookId: '', bindings: {}}));
|
|
||||||
|
|
||||||
export const setCharacterRuleBookForCharacter = (characterId: number, ruleBook: SetCharacterRuleBookRequest): Promise<CharacterRuleBookResponse> => characterRuleBookApi.setCharacterRuleBookForCharacter(characterId, ruleBook)
|
|
||||||
.then(response => response.data);
|
|
||||||
|
|
||||||
export const fetchScriptDefinitions = (): Promise<string> =>
|
|
||||||
ruleBookApi.getScriptDefinitions({responseType: 'text'}).then(response => response.data);
|
|
||||||
+21
-21
@@ -1,18 +1,18 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import {Dropdown} from '@/components';
|
import { useAuthStore } from '@/auth';
|
||||||
import {RouterLink} from 'vue-router';
|
import { Dropdown } from '@/components';
|
||||||
import {routeNames} from '@/routes';
|
import { RouterLink } from 'vue-router';
|
||||||
|
|
||||||
const links = [
|
const links = [
|
||||||
{name: "Market", path: "/market"},
|
{ name: "Market", path: "/market" },
|
||||||
{name: "Ledger", path: "/ledgers"},
|
{ name: "Reprocess", path: "/reprocess" },
|
||||||
{name: "Rules", path: "/rules"},
|
{ name: "Tools", path: "/tools" }
|
||||||
{name: "Reprocess", path: "/reprocess"},
|
|
||||||
{name: "Tools", path: "/tools"}
|
|
||||||
];
|
];
|
||||||
|
|
||||||
|
const authStore = useAuthStore();
|
||||||
|
|
||||||
const logout = async () => {
|
const logout = async () => {
|
||||||
|
await authStore.logout();
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -22,14 +22,14 @@ const logout = async () => {
|
|||||||
<div class="mb-2 border-b-2 border-emerald-500">
|
<div class="mb-2 border-b-2 border-emerald-500">
|
||||||
<Dropdown class="mb-2 user-dropdown">
|
<Dropdown class="mb-2 user-dropdown">
|
||||||
<template #button>
|
<template #button>
|
||||||
<span>NAME</span>
|
<span>{{ authStore.username }}</span>
|
||||||
</template>
|
</template>
|
||||||
<ul>
|
<ul>
|
||||||
<li>
|
<li>
|
||||||
<RouterLink class="sidebar-button py-0.5 px-2" to="/characters">Characters</RouterLink>
|
<RouterLink class="sidebar-button py-0.5 px-2" to="/characters">Characters</RouterLink>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<RouterLink class="sidebar-button py-0.5 px-2" :to="{name: routeNames.about}">About EVE Online</RouterLink>
|
<RouterLink class="sidebar-button py-0.5 px-2" :to="{name: 'about'}">About EVE Online</RouterLink>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a class="sidebar-button py-0.5 px-2 text-amber-700" @click="logout">Logout</a>
|
<a class="sidebar-button py-0.5 px-2 text-amber-700" @click="logout">Logout</a>
|
||||||
@@ -48,26 +48,26 @@ const logout = async () => {
|
|||||||
</aside>
|
</aside>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped lang="postcss">
|
||||||
@reference "@/style.css";
|
|
||||||
|
|
||||||
.sidebar-button {
|
.sidebar-button {
|
||||||
@apply flex items-center rounded-md hover:bg-slate-800 cursor-pointer;
|
@apply flex items-center rounded-md hover:bg-slate-800 cursor-pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
.router-link-active {
|
.router-link-active {
|
||||||
@apply bg-emerald-500 hover:bg-emerald-700;
|
@apply bg-emerald-500 hover:bg-emerald-700;
|
||||||
}
|
}
|
||||||
|
|
||||||
.user-dropdown {
|
.user-dropdown {
|
||||||
@apply w-full;
|
@apply w-full;
|
||||||
}
|
:deep(>div) {
|
||||||
|
@apply w-full;
|
||||||
.user-dropdown :deep(>button) {
|
>div {
|
||||||
|
@apply w-full bg-slate-800;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
:deep(>button) {
|
||||||
@apply bg-slate-700 hover:bg-slate-800 border-none flex items-center w-full;
|
@apply bg-slate-700 hover:bg-slate-800 border-none flex items-center w-full;
|
||||||
}
|
}
|
||||||
|
&.dropdown-open:deep(>button) {
|
||||||
.user-dropdown.dropdown-open :deep(>button) {
|
|
||||||
@apply bg-slate-800 rounded-b-none;
|
@apply bg-slate-800 rounded-b-none;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
+9
-26
@@ -1,15 +1,9 @@
|
|||||||
@import "tailwindcss";
|
@tailwind base;
|
||||||
|
@tailwind components;
|
||||||
|
@tailwind utilities;
|
||||||
|
|
||||||
@custom-variant search-cancel (&::-webkit-search-cancel-button);
|
|
||||||
|
|
||||||
@utility btn-icon {
|
|
||||||
@apply p-0 border-none bg-transparent hover:text-slate-400 hover:bg-transparent cursor-pointer;
|
|
||||||
> svg {
|
|
||||||
@apply w-6 h-6;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@layer base {
|
@layer base {
|
||||||
span, table, input, th, tr, td, button, a.button, div, hr {
|
span, table, input, th, tr, td, button, div, hr {
|
||||||
@apply border-slate-600 text-slate-100 placeholder-slate-400;
|
@apply border-slate-600 text-slate-100 placeholder-slate-400;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -17,15 +11,12 @@
|
|||||||
@apply bg-slate-800;
|
@apply bg-slate-800;
|
||||||
}
|
}
|
||||||
|
|
||||||
button, a.button {
|
button {
|
||||||
@apply py-0.5 px-2 border rounded bg-slate-600 hover:bg-slate-700;
|
@apply py-0.5 px-2 border rounded bg-slate-600 hover:bg-slate-700;
|
||||||
}
|
}
|
||||||
input, select {
|
input {
|
||||||
@apply border bg-slate-500 rounded px-1;
|
@apply border bg-slate-500 rounded px-1;
|
||||||
}
|
}
|
||||||
option {
|
|
||||||
@apply bg-slate-500;
|
|
||||||
}
|
|
||||||
textarea {
|
textarea {
|
||||||
@apply border rounded bg-slate-500 w-full;
|
@apply border rounded bg-slate-500 w-full;
|
||||||
}
|
}
|
||||||
@@ -58,9 +49,6 @@
|
|||||||
@apply bg-emerald-500 hover:bg-emerald-600;
|
@apply bg-emerald-500 hover:bg-emerald-600;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
tfoot>tr>td {
|
|
||||||
@apply font-semibold;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
::-webkit-scrollbar {
|
::-webkit-scrollbar {
|
||||||
@@ -78,14 +66,9 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.btn-icon {
|
.btn-icon {
|
||||||
@apply btn-icon;
|
@apply p-0 border-none bg-transparent hover:text-slate-400 hover:bg-transparent;
|
||||||
}
|
> svg {
|
||||||
|
@apply w-6 h-6;
|
||||||
a.tab {
|
|
||||||
@apply flex items-center px-4 me-2 rounded-t-md bg-slate-600 hover:bg-slate-700;
|
|
||||||
|
|
||||||
&.router-link-active {
|
|
||||||
@apply bg-emerald-500 hover:bg-emerald-700;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,78 +0,0 @@
|
|||||||
<script setup lang="ts">
|
|
||||||
|
|
||||||
import {TransferTypes} from "@/transaction/transaction.ts";
|
|
||||||
import {LedgerLabel, systemLedger, useLedgersStore} from "@/ledger";
|
|
||||||
import {getMarketType, IskLabel, MarketTypeLabel} from "@/market";
|
|
||||||
import {computedAsync} from "@vueuse/core";
|
|
||||||
import {TransferResponse} from "@/generated/mammon";
|
|
||||||
|
|
||||||
type TransferWithValue = TransferResponse & { marketTypeId: number; };
|
|
||||||
|
|
||||||
interface Props {
|
|
||||||
transfers?: TransferResponse[]
|
|
||||||
}
|
|
||||||
|
|
||||||
const props = defineProps<Props>();
|
|
||||||
|
|
||||||
const {findById} = useLedgersStore();
|
|
||||||
|
|
||||||
const sortedArray = computedAsync(async () => {
|
|
||||||
if (!props.transfers) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
return (await Promise.all(props.transfers.map(async (transfer: TransferWithValue, index) => {
|
|
||||||
const fromLedger = findById(transfer.fromLedgerId) ?? systemLedger
|
|
||||||
const toLedger = findById(transfer.toLedgerId) ?? systemLedger
|
|
||||||
|
|
||||||
const item = transfer.marketTypeId ? await getMarketType(transfer.marketTypeId) : undefined;
|
|
||||||
|
|
||||||
return {
|
|
||||||
...transfer,
|
|
||||||
order: index,
|
|
||||||
fromLedger,
|
|
||||||
toLedger,
|
|
||||||
itemName: item ? item.name : '',
|
|
||||||
fromLedgerName: fromLedger.name,
|
|
||||||
toLedgerName: toLedger.name
|
|
||||||
}
|
|
||||||
}))).sort((a, b) => a.order - b.order)
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<div>
|
|
||||||
<table>
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th>From</th>
|
|
||||||
<th>To</th>
|
|
||||||
<th>Item</th>
|
|
||||||
<th>Quantity/Amount</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
<tr v-for="t in sortedArray">
|
|
||||||
<td>
|
|
||||||
<LedgerLabel :ledger="t.fromLedger" />
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<LedgerLabel :ledger="t.toLedger" />
|
|
||||||
</td>
|
|
||||||
<template v-if="t.type === TransferTypes.Item">
|
|
||||||
<td>
|
|
||||||
<MarketTypeLabel :id="t.marketTypeId" :name="t.itemName" />
|
|
||||||
</td>
|
|
||||||
<td class="text-right">{{ t.quantity }}</td>
|
|
||||||
</template>
|
|
||||||
<template v-else-if="t.type === TransferTypes.Isk">
|
|
||||||
<td colspan="2" class="text-right">
|
|
||||||
<IskLabel :amount="t.amount" :colored="false" />
|
|
||||||
</td>
|
|
||||||
</template>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
export * from './transaction'
|
|
||||||
|
|
||||||
export {default as TransferList} from './TransferList.vue';
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
import {TransferResponse} from "@/generated/mammon";
|
|
||||||
|
|
||||||
export const TransferTypes = {
|
|
||||||
Isk: 'ISK',
|
|
||||||
Item: 'ITEM',
|
|
||||||
} as const;
|
|
||||||
export type TransferType = TransferResponse['type'];
|
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
/** @type {import('tailwindcss').Config} */
|
||||||
|
export default {
|
||||||
|
content: [
|
||||||
|
"./index.html",
|
||||||
|
"./src/**/*.{vue,js,ts,jsx,tsx}",
|
||||||
|
],
|
||||||
|
theme: {
|
||||||
|
extend: {},
|
||||||
|
},
|
||||||
|
plugins: [
|
||||||
|
require('tailwindcss/plugin')(({ addVariant }) => {
|
||||||
|
addVariant('search-cancel', '&::-webkit-search-cancel-button');
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
||||||
+1
-2
@@ -26,8 +26,7 @@
|
|||||||
"src/*": [
|
"src/*": [
|
||||||
"./src/*"
|
"./src/*"
|
||||||
]
|
]
|
||||||
},
|
}
|
||||||
"allowSyntheticDefaultImports": true
|
|
||||||
},
|
},
|
||||||
"include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"],
|
"include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"],
|
||||||
"references": [{ "path": "./tsconfig.node.json" }]
|
"references": [{ "path": "./tsconfig.node.json" }]
|
||||||
|
|||||||
@@ -2,13 +2,11 @@ import vue from '@vitejs/plugin-vue';
|
|||||||
import * as path from "path";
|
import * as path from "path";
|
||||||
import { defineConfig } from 'vite';
|
import { defineConfig } from 'vite';
|
||||||
import runtimeEnv from 'vite-plugin-runtime-env';
|
import runtimeEnv from 'vite-plugin-runtime-env';
|
||||||
import tailwindcss from '@tailwindcss/vite'
|
|
||||||
|
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
plugins: [
|
plugins: [
|
||||||
runtimeEnv(),
|
runtimeEnv(),
|
||||||
vue(),
|
vue(),
|
||||||
tailwindcss(),
|
|
||||||
],
|
],
|
||||||
resolve: {
|
resolve: {
|
||||||
alias: {
|
alias: {
|
||||||
|
|||||||
Reference in New Issue
Block a user