Compare commits

...

2 Commits

Author SHA1 Message Date
Sirttas 5c12a8af43 dropdown floating 2026-06-01 22:29:06 +02:00
Sirttas 05210fea4b transfer list 2026-06-01 22:19:26 +02:00
8 changed files with 155 additions and 29 deletions
+36 -9
View File
@@ -1,8 +1,8 @@
<script setup lang="ts">
import {ChevronDownIcon, ChevronUpIcon} from '@heroicons/vue/24/outline';
import {vOnClickOutside} from '@vueuse/components';
import {useEventListener} from '@vueuse/core';
import {ref} from 'vue';
import {useElementBounding, useEventListener} from '@vueuse/core';
import {computed, ref} from 'vue';
interface Props {
inline?: boolean;
@@ -15,6 +15,16 @@ const props = withDefaults(defineProps<Props>(), {
})
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) {
@@ -30,8 +40,8 @@ useEventListener('keyup', e => {
</script>
<template>
<div class="dropdown" :class="{'dropdown-open': isOpen, 'dropdown-close': !isOpen}" v-on-click-outside="doAutoClose">
<button @click="isOpen = !isOpen">
<div ref="root" class="dropdown" :class="{'dropdown-open': isOpen, 'dropdown-close': !isOpen}" v-on-click-outside="[doAutoClose, { ignore: [floating] }]">
<button @click="isOpen = !isOpen" class="cursor-pointer">
<Transition
enter-active-class="transition-transform"
enter-from-class="rotate-180"
@@ -51,12 +61,21 @@ useEventListener('keyup', e => {
<div v-if="inline && isOpen">
<slot />
</div>
<div v-else-if="isOpen" class="relative">
<div class="z-10 divide-y rounded-b-md absolute">
<slot />
</div>
</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 />
</div>
</div>
</Transition>
</Teleport>
</div>
</template>
@@ -66,4 +85,12 @@ useEventListener('keyup', e => {
.chevron {
@apply w-4 h-4 me-1;
}
.dropdown-floating {
@apply fixed z-10;
}
.dropdown-floating > div {
@apply bg-slate-800 rounded-b-md shadow-lg;
}
</style>
+5 -5
View File
@@ -1,8 +1,8 @@
<script setup lang="ts">
import { vElementHover } from '@vueuse/components';
import { useElementBounding } from '@vueuse/core';
import { computed, ref } from 'vue';
import { useSharedWindowSize } from './tooltip';
import {vElementHover} from '@vueuse/components';
import {useElementBounding} from '@vueuse/core';
import {computed, ref} from 'vue';
import {useSharedWindowSize} from './tooltip';
const open = defineModel('open', { default: false });
@@ -25,7 +25,7 @@ const positions = computed(() => {
</script>
<template>
<div ref="mainDiv" clas="flex flex-col items-center justify-center" :class="{
<div ref="mainDiv" class="flex flex-col items-center justify-center" :class="{
'open': open,
'tooltip-top': positions.includes('top'),
'tooltip-bottom': positions.includes('bottom'),
-3
View File
@@ -8,7 +8,6 @@ import {
MainLedgerResponse,
MainLedgerResponseTypeEnum,
TransactionResponse,
TransferResponseTypeEnum,
UpdateCombinedLedgerRequest,
UpdateMainLedgerRequest
} from "@/generated/mammon";
@@ -24,8 +23,6 @@ export type MainLedger = MainLedgerResponse & {type: MainLedgerResponseTypeEnum}
export type CombinedLedger = CombinedLedgerResponse & {type: CombinedLedgerResponseTypeEnum}
export type Ledger = MainLedger | CombinedLedger;
export const TransferTypes = TransferResponseTypeEnum;
export const systemLedgerRef = 'system';
export const systemLedger = {
type: LedgerTypes.Main,
+30 -4
View File
@@ -1,11 +1,13 @@
<script setup lang="ts">
import {findAllTransactionInLeger, TransferTypes, useLedgerParam} from "@/ledger";
import {findAllTransactionInLeger, useLedgerParam} from "@/ledger";
import {computedAsync} from "@vueuse/core";
import {TransactionResponse} from "@/generated/mammon";
import {formatEveDate} from "@/formaters.ts";
import {IskLabel} from "@/market";
import {SortableHeader, useSort, VirtualScrollTable} from "@/components/table";
import {TransferList, TransferTypes} from "@/transaction";
import {Dropdown} from "@/components";
const {ledgerId} = useLedgerParam();
@@ -21,7 +23,8 @@ const { sortedArray, headerProps } = useSort(() => transactions.value.map(transa
transactionId: transaction.transactionId,
description: transaction.description,
date: new Date(transaction.datetime),
balance: getIskBalance(transaction)
balance: getIskBalance(transaction),
transfers: transaction.transfers
}
}));
@@ -47,7 +50,7 @@ const getIskBalance = (transaction: TransactionResponse) => {
<template>
<div class="mt-4">
<VirtualScrollTable :list="sortedArray" :itemHeight="33" bottom="1rem">
<VirtualScrollTable :list="sortedArray" :itemHeight="33" bottom="1rem">
<template #default="{ list }">
<thead>
<tr>
@@ -58,7 +61,14 @@ const getIskBalance = (transaction: TransactionResponse) => {
</thead>
<tbody>
<tr v-for="t in list" :key="t.data.transactionId">
<td>{{formatEveDate(t.data.date)}}</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>
@@ -69,3 +79,19 @@ const getIskBalance = (transaction: TransactionResponse) => {
</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>
-8
View File
@@ -63,14 +63,6 @@ const logout = async () => {
@apply w-full;
}
.user-dropdown :deep(>div) {
@apply w-full;
> div {
@apply w-full bg-slate-800;
}
}
.user-dropdown :deep(>button) {
@apply bg-slate-700 hover:bg-slate-800 border-none flex items-center w-full;
}
+76
View File
@@ -0,0 +1,76 @@
<script setup lang="ts">
import {Transfer, TransferTypes} from "@/transaction/transaction.ts";
import {LedgerLabel, systemLedger, useLedgersStore} from "@/ledger";
import {getMarketType, IskLabel, MarketTypeLabel} from "@/market";
import {computedAsync} from "@vueuse/core";
type TransferWithValue = Transfer & { marketTypeId: number; };
interface Props {
transfers?: Transfer[]
}
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) => {
const fromLedger = findById(transfer.fromLedgerId) ?? systemLedger
const toLedger = findById(transfer.toLedgerId) ?? systemLedger
const item = transfer.marketTypeId ? await getMarketType(transfer.marketTypeId) : undefined;
return {
...transfer,
fromLedger,
toLedger,
itemName: item ? item.name : '',
fromLedgerName: fromLedger.name,
toLedgerName: toLedger.name
}
}))
}, []);
</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" />
</td>
</template>
</tr>
</tbody>
</table>
</div>
</template>
+3
View File
@@ -0,0 +1,3 @@
export * from './transaction'
export {default as TransferList} from './TransferList.vue';
+5
View File
@@ -0,0 +1,5 @@
import {TransactionResponseTransfersInner, TransferResponseTypeEnum} from "@/generated/mammon";
export const TransferTypes = TransferResponseTypeEnum;
export type TransferType = TransferResponseTypeEnum;
export type Transfer = TransactionResponseTransfersInner;