transaction list display

This commit is contained in:
Sirttas
2026-05-31 23:10:50 +02:00
parent 47ee14319d
commit 3235cf21ba
11 changed files with 126 additions and 18 deletions
+1 -1
View File
@@ -6,7 +6,7 @@ import {ref} from 'vue';
interface Props { interface Props {
inline?: boolean; inline?: boolean;
autoClose: boolean; autoClose?: boolean;
} }
const props = withDefaults(defineProps<Props>(), { const props = withDefaults(defineProps<Props>(), {
+5 -1
View File
@@ -2,9 +2,12 @@
import {isCombined, Ledger} from "@/ledger/ledger.ts"; import {isCombined, Ledger} from "@/ledger/ledger.ts";
import {FolderOpenIcon} from '@heroicons/vue/24/outline'; import {FolderOpenIcon} from '@heroicons/vue/24/outline';
import {RouterLink} from "vue-router";
import {routeNames} from "@/routes.ts";
interface Props { interface Props {
ledger: Ledger; ledger: Ledger;
link?: boolean;
} }
const props = defineProps<Props>(); const props = defineProps<Props>();
@@ -14,6 +17,7 @@ const props = defineProps<Props>();
<div class="flex"> <div class="flex">
<FolderOpenIcon v-if="isCombined(ledger)" class="w-4 me-1" /> <FolderOpenIcon v-if="isCombined(ledger)" class="w-4 me-1" />
<div v-else class="w-4 me-1"/> <div v-else class="w-4 me-1"/>
<span>{{ ledger.name }}</span> <RouterLink v-if="link" :to="{name: routeNames.listLedgerTransactions, params: {ledgerId: ledger.ledgerId}}">{{ ledger.name }}</RouterLink>
<span v-else>{{ ledger.name }}</span>
</div> </div>
</template> </template>
+6 -1
View File
@@ -6,12 +6,14 @@ import {
LedgerResponseTypeEnum, LedgerResponseTypeEnum,
MainLedgerResponse, MainLedgerResponse,
MainLedgerResponseTypeEnum, MainLedgerResponseTypeEnum,
TransactionResponse,
TransferResponseTypeEnum,
UpdateCombinedLedgerRequest, UpdateCombinedLedgerRequest,
UpdateMainLedgerRequest UpdateMainLedgerRequest
} from "@/generated/mammon"; } from "@/generated/mammon";
import {defineStore} from "pinia"; import {defineStore} from "pinia";
import {ref, triggerRef} from "vue"; import {ref, triggerRef} from "vue";
import {ledgerApi} from "@/mammon"; import {ledgerApi, transactionApi} from "@/mammon";
export const LedgerTypes = LedgerResponseTypeEnum; export const LedgerTypes = LedgerResponseTypeEnum;
@@ -20,6 +22,7 @@ export type MainLedger = MainLedgerResponse & {type: MainLedgerResponseTypeEnum}
export type CombinedLedger = CombinedLedgerResponse & {type: CombinedLedgerResponseTypeEnum} export type CombinedLedger = CombinedLedgerResponse & {type: CombinedLedgerResponseTypeEnum}
export type Ledger = MainLedger | CombinedLedger; export type Ledger = MainLedger | CombinedLedger;
export const TransferTypes = TransferResponseTypeEnum;
export const systemLedgerRef = 'system'; export const systemLedgerRef = 'system';
export const systemLedger = { export const systemLedger = {
@@ -71,3 +74,5 @@ export const useLedgersStore = defineStore('ledgers', () => {
return {ledgers, findById, findAllById, createMain, createCombined, updateMain, updateCombined, refresh}; return {ledgers, findById, findAllById, createMain, createCombined, updateMain, updateCombined, refresh};
}) })
export const findAllTransactionInLeger = (ledger: Ledger | string): Promise<TransactionResponse[]> => transactionApi.finAllTransactionsInLedger(typeof ledger == 'string' ? ledger : ledger.ledgerId).then(response => response.data)
+3 -1
View File
@@ -6,7 +6,8 @@ import {
CharacterRuleBookApi, CharacterRuleBookApi,
LedgerApi, LedgerApi,
ProcessingApi, ProcessingApi,
RuleBookApi RuleBookApi,
TransactionApi
} from "@/generated/mammon"; } from "@/generated/mammon";
export const mammonUrl = import.meta.env.VITE_MAMMON_URL; export const mammonUrl = import.meta.env.VITE_MAMMON_URL;
@@ -22,6 +23,7 @@ const mammonAxiosInstance = axios.create({
logResource(mammonAxiosInstance) logResource(mammonAxiosInstance)
export const ledgerApi = new LedgerApi(undefined, mammonUrl, 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 characterApi = new CharacterApi(undefined, mammonUrl, mammonAxiosInstance);
export const ruleBookApi = new RuleBookApi(undefined, mammonUrl, mammonAxiosInstance); export const ruleBookApi = new RuleBookApi(undefined, mammonUrl, mammonAxiosInstance);
export const characterRuleBookApi = new CharacterRuleBookApi(undefined, mammonUrl, mammonAxiosInstance); export const characterRuleBookApi = new CharacterRuleBookApi(undefined, mammonUrl, mammonAxiosInstance);
+13
View File
@@ -0,0 +1,13 @@
<script setup lang="ts">
import {formatIsk} from "@/formaters";
interface Props {
amount: number;
}
const { amount } = defineProps<Props>();
</script>
<template>
<span :class="amount >= 0 ? 'text-emerald-400' : 'text-amber-700'">{{ formatIsk(amount) }}</span>
</template>
+2
View File
@@ -6,3 +6,5 @@ export * from './type';
export * from './appraisal'; export * from './appraisal';
export * from './market'; export * from './market';
export { default as IskLabel } from './IskLabel.vue';
+21 -8
View File
@@ -1,27 +1,40 @@
import {esiAxiosInstance} from '@/service';
export type MarketType = { export type MarketType = {
id: number; type_id: number;
group_id: number; group_id: number;
marketgroup_id: number; market_group_id: number;
name: string; name: string;
published: boolean; published: boolean;
description: string; description: string;
basePrice: number; base_price: number;
icon_id: number; icon_id: number;
volume: number; volume: number;
portionSize: number; portion_size: 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<MarketType>(`/universe/types/${id}/`).then(r => {
cache.set(id, r.data);
return r.data;
});
};
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 [];
} }
return [] const ids = types.filter((t): t is number => typeof t === 'number');
return Promise.all(ids.map(fetchType));
} }
const blueprintMarketGrous = [ // TODO add all groups const blueprintMarketGroups = [ // TODO add all groups
2, 2,
2157, 2157,
2159, 2159,
@@ -0,0 +1,67 @@
<script setup lang="ts">
import {ref, watch} from "vue";
import {useRoute} from "vue-router";
import log from "loglevel";
import {findAllTransactionInLeger, Ledger, TransferTypes, useLedgersStore} from "@/ledger";
import {computedAsync} from "@vueuse/core";
import {TransactionResponse} from "@/generated/mammon";
import {formatEveDate} from "@/formaters.ts";
import {IskLabel} from "@/market";
const {findById, refresh} = useLedgersStore();
const ledger = ref<Ledger>();
const transactions = computedAsync<TransactionResponse[]>(async () => {
if (ledger.value) {
return await findAllTransactionInLeger(ledger.value.ledgerId);
}
return [];
}, []);
const getIskBalance = (transaction: TransactionResponse) => {
const ledgerId = ledger.value?.ledgerId;
if (!ledgerId) {
return 0;
}
let balance = 0;
for (const transfer of transaction.transfers) {
if (transfer.type === TransferTypes.Isk) {
if (transfer.toLedgerId === ledgerId) {
balance += transfer.amount;
} else if (transfer.fromLedgerId === ledgerId) {
balance -= transfer.amount;
}
}
}
return balance;
}
watch(useRoute(), async route => {
if (route.params.ledgerId) {
const id = typeof route.params.ledgerId === 'string' ? route.params.ledgerId : route.params.ledgerId[0];
await refresh() // FIXME
ledger.value = findById(id)
log.info('Loaded ledger:', ledger.value);
} else {
ledger.value = undefined;
log.info('No ledger to load');
}
}, { immediate: true })
</script>
<template>
<div class="mt-4">
<div class="flex items-end gap-2 mt-2" v-for="transaction in transactions" :key="transaction.transactionId">
<span>{{formatEveDate(new Date(transaction.datetime))}}</span>
<IskLabel :amount="getIskBalance(transaction)" />
<span>{{transaction.description}}</span>
</div>
</div>
</template>
+3 -3
View File
@@ -4,7 +4,7 @@ import {EditLedgerModal, LedgerLabel, useLedgersStore} from "@/ledger";
import {storeToRefs} from "pinia"; import {storeToRefs} from "pinia";
import {nextTick, ref} from "vue"; import {nextTick, ref} from "vue";
import {PencilSquareIcon} from "@heroicons/vue/24/outline"; import {PencilSquareIcon} from "@heroicons/vue/24/outline";
import {formatIsk} from "@/formaters.ts"; import {IskLabel} from "@/market";
const {ledgers} = storeToRefs(useLedgersStore()); const {ledgers} = storeToRefs(useLedgersStore());
@@ -22,9 +22,9 @@ const openEdit = async (ledgerId: string) => {
<template> <template>
<div class="mt-4"> <div class="mt-4">
<div v-for="ledger in ledgers" :key="ledger.ledgerId" class="flex items-center mb-2"> <div v-for="ledger in ledgers" :key="ledger.ledgerId" class="flex items-center mb-2">
<LedgerLabel :ledger="ledger" /> <LedgerLabel :ledger="ledger" :link="true" />
<div class="flex grow"> <div class="flex grow">
<span class="ms-2">{{ formatIsk(ledger.balance) }}</span> <IskLabel class="ms-2" :amount="ledger.balance" />
</div> </div>
<button class="btn-icon ms-2" @click="openEdit(ledger.ledgerId)"><PencilSquareIcon /></button> <button class="btn-icon ms-2" @click="openEdit(ledger.ledgerId)"><PencilSquareIcon /></button>
</div> </div>
+1 -1
View File
@@ -12,7 +12,7 @@ const {characters} = storeToRefs(useCharactersStore());
<div class="grid mb-2 mt-4"> <div class="grid mb-2 mt-4">
<div v-for="character in characters" :key="character.characterId" class="flex items-center mb-2"> <div v-for="character in characters" :key="character.characterId" class="flex items-center mb-2">
<CharacterLabel class="flex grow" :character="character" /> <CharacterLabel class="flex grow" :character="character" />
<RouterLink class="btn-icon ms-2" :to="{ name: routeNames.characterRulebook, params: { characterId: character.characterId } }"><PencilSquareIcon /></RouterLink> <RouterLink class="btn-icon ms-2" :to="{ name: routeNames.editCharacterRulebook, params: { characterId: character.characterId } }"><PencilSquareIcon /></RouterLink>
</div> </div>
</div> </div>
</template> </template>
+4 -2
View File
@@ -3,10 +3,11 @@ import {RouteRecordRaw} from 'vue-router';
export const routeNames = { export const routeNames = {
home: 'home', home: 'home',
callback: 'callback', callback: 'callback',
listLedgerTransactions: 'list-ledger-tTransactions',
listRuleBooks: 'list-rule-books', listRuleBooks: 'list-rule-books',
newRuleBook: 'new-rule-book', newRuleBook: 'new-rule-book',
editRuleBook: 'edit-rule-book', editRuleBook: 'edit-rule-book',
characterRulebook: 'character-rulebook', editCharacterRulebook: 'edit-character-rule-book',
marketTypes: 'market-types', marketTypes: 'market-types',
about: 'about', about: 'about',
} as const; } as const;
@@ -17,6 +18,7 @@ export const routes: RouteRecordRaw[] = [
{path: '/ledgers', component: () => import('@/pages/Ledgers.vue'), children: [ {path: '/ledgers', component: () => import('@/pages/Ledgers.vue'), children: [
{path: '', component: () => import('@/pages/ledger/ListLedgers.vue')}, {path: '', component: () => import('@/pages/ledger/ListLedgers.vue')},
{path: ':ledgerId/transactions', name: routeNames.listLedgerTransactions, component: () => import('@/pages/ledger/ListLedgerTransactions.vue')},
]}, ]},
{path: '/rules', component: () => import('@/pages/Rules.vue'), children: [ {path: '/rules', component: () => import('@/pages/Rules.vue'), children: [
@@ -28,7 +30,7 @@ export const routes: RouteRecordRaw[] = [
]}, ]},
{path: '/characters/rules', children: [ {path: '/characters/rules', children: [
{path: '', component: () => import('@/pages/rules/ListCharacterRuleBooks.vue')}, {path: '', component: () => import('@/pages/rules/ListCharacterRuleBooks.vue')},
{path: '/characters/:characterId/rules', name: routeNames.characterRulebook, component: () => import('@/pages/rules/EditCharacterRuleBook.vue')}, {path: '/characters/:characterId/rules', name: routeNames.editCharacterRulebook, component: () => import('@/pages/rules/EditCharacterRuleBook.vue')},
]} ]}
]}, ]},