feat(frontend): add transaction link in latest transactions (#3375)

* fix(frontend): post migration fixes

* fix(frontend): align with stylelint

* fix(frontend): fix tests and dashboard layout

* feat(frontend): add link to transactions

* feat(frontend): remove unused code

* feat(frontend): let dynamic keys in translations

* feat(frontend): fix stylelint

* feat(frontend): add missing styles for breadcrumb

---------

Co-authored-by: einhornimmond <dario.rekowski@gmx.de>
This commit is contained in:
MateuszMichalowski 2024-10-15 16:06:34 +02:00 committed by GitHub
parent ec964020c4
commit b69d2273ae
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
13 changed files with 275 additions and 58 deletions

View File

@ -50,7 +50,6 @@ module.exports = {
'vue/v-on-event-hyphenation': 0, // TODO remove at the end of migration and fix
'vue/require-default-prop': 0, // TODO remove at the end of migration and fix
'vue/no-computed-properties-in-data': 0, // TODO remove at the end of migration and fix
'@intlify/vue-i18n/no-dynamic-keys': 'error',
'@intlify/vue-i18n/no-missing-keys': 0, // TODO remove at the end of migration and fix
'@intlify/vue-i18n/no-unused-keys': [
'error',

View File

@ -1,5 +1,5 @@
<template>
<div class="breadcrumb bg-transparent">
<div class="page-breadcrumb breadcrumb bg-transparent">
<h1>{{ pageTitle }}</h1>
</div>
</template>
@ -17,3 +17,10 @@ export default {
},
}
</script>
<style scoped>
.page-breadcrumb {
margin-bottom: 1rem;
padding: 0.75rem 1rem;
}
</style>

View File

@ -24,27 +24,18 @@
</transaction-list-item>
</div>
<div class="mt-3">
<div v-for="({ id, typeId }, index) in transactions" :key="`l2-` + id">
<div v-for="transaction in transactions" :key="`l2-` + transaction.id">
<transaction-list-item
v-if="typeId !== 'DECAY'"
:type-id="typeId"
v-if="transaction.typeId !== 'DECAY'"
:type-id="transaction.typeId"
class="pointer mb-3 bg-white app-box-shadow gradido-border-radius p-3 test-list-group-item"
>
<template #SEND>
<transaction-send v-bind="transactions[index]" />
<template v-if="transaction.typeId !== 'LINK_SUMMARY'" #item>
<gdd-transaction :transaction="transaction" />
</template>
<template #RECEIVE>
<transaction-receive v-bind="transactions[index]" />
</template>
<template #CREATION>
<transaction-creation v-bind="transactions[index]" />
</template>
<template #LINK_SUMMARY>
<template v-else #LINK_SUMMARY>
<transaction-link-summary
v-bind="transactions[index]"
v-bind="transaction"
:transaction-link-count="transactionLinkCount"
@update-transactions="updateTransactions"
/>
@ -75,19 +66,15 @@
<script>
import TransactionListItem from '@/components/TransactionListItem'
import TransactionDecay from '@/components/Transactions/TransactionDecay'
import TransactionSend from '@/components/Transactions/TransactionSend'
import TransactionReceive from '@/components/Transactions/TransactionReceive'
import TransactionCreation from '@/components/Transactions/TransactionCreation'
import TransactionLinkSummary from '@/components/Transactions/TransactionLinkSummary'
import GddTransaction from '@/components/Transactions/GddTransaction.vue'
export default {
name: 'GddTransactionList',
components: {
GddTransaction,
TransactionListItem,
TransactionDecay,
TransactionSend,
TransactionReceive,
TransactionCreation,
TransactionLinkSummary,
},
props: {

View File

@ -140,3 +140,9 @@ button.navbar-toggler > span.navbar-toggler-icon {
}
}
</style>
<style scoped>
:deep(.container-fluid) {
padding: 0 !important;
}
</style>

View File

@ -4,17 +4,8 @@
<BCol class="h3">{{ $t('transaction.lastTransactions') }}</BCol>
</BRow>
<div v-for="(transaction, index) in transactions" :key="transaction.id">
<BRow
v-if="
index <= 8 &&
transaction.typeId !== 'DECAY' &&
transaction.typeId !== 'LINK_SUMMARY' &&
transaction.typeId !== 'CREATION'
"
align-v="center"
class="mb-4"
>
<div v-for="transaction in filteredTransactions" :key="transaction.id">
<BRow align-v="center" class="mb-4">
<BCol cols="auto">
<div class="align-items-center">
<avatar
@ -31,14 +22,19 @@
<div class="fw-bold">
<name :linked-user="transaction.linkedUser" font-color="text-dark" />
</div>
<div class="d-flex mt-3">
<div class="small">
<button
class="transaction-details-link d-flex mt-3"
role="link"
:data-href="`/transactions#transaction-${transaction.id}`"
@click="handleRedirect(transaction.id)"
>
<span class="small">
{{ $filters.GDD(transaction.amount) }}
</div>
<div class="small ms-3 text-end">
</span>
<span class="small ms-3 text-end">
{{ $d(new Date(transaction.balanceDate), 'short') }}
</div>
</div>
</span>
</button>
</BCol>
</BRow>
</BCol>
@ -46,23 +42,50 @@
</div>
</div>
</template>
<script>
<script setup>
import Avatar from 'vue-avatar'
import Name from '@/components/TransactionRows/Name'
import { useRoute, useRouter } from 'vue-router'
import { useStore } from 'vuex'
import { computed } from 'vue'
const props = defineProps({
transactions: {
default: () => [],
type: Array,
},
})
export default {
name: 'LastTransactions',
components: {
Avatar,
Name,
},
props: {
transactions: {
default: () => [],
type: Array,
},
transactionCount: { type: Number, default: 0 },
transactionLinkCount: { type: Number, default: 0 },
},
const router = useRouter()
const route = useRoute()
const store = useStore()
const handleRedirect = (id) => {
store.dispatch('changeTransactionToHighlightId', id)
if (route.name !== 'Transactions') router.replace({ name: 'Transactions' })
}
const filteredTransactions = computed(() => {
return props.transactions
.filter(
(transaction) =>
transaction.typeId !== 'DECAY' &&
transaction.typeId !== 'LINK_SUMMARY' &&
transaction.typeId !== 'CREATION',
)
.slice(0, 8)
})
</script>
<style scoped lang="scss">
.transaction-details-link {
color: var(--bs-body-color) !important;
border: none;
background-color: transparent;
border-bottom: 1px solid transparent;
transition: border-bottom-color 0.15s ease-in-out;
}
.transaction-details-link:hover {
border-color: #383838;
}
</style>

View File

@ -245,6 +245,7 @@ import { BAvatar, BCol, BCollapse, BRow } from 'bootstrap-vue-next'
import TransactionCollapse from '@/components/TransactionCollapse.vue'
import { GdtEntryType } from '@/graphql/enums'
import VariantIcon from '@/components/VariantIcon.vue'
import { createStore } from 'vuex'
const mockToastError = vi.fn()
vi.mock('@/composables/useToast', () => ({
@ -268,6 +269,13 @@ describe('Transaction', () => {
const Wrapper = () => {
return mount(Transaction, {
global: {
plugins: [
createStore({
state: {
transactionToHighlightId: '',
},
}),
],
mocks: {
$d: (value) => value?.toString() ?? '',
$n: (value) => value?.toString() ?? '',

View File

@ -49,11 +49,12 @@
</template>
<script setup>
import { ref, computed, onMounted, getCurrentInstance } from 'vue'
import { ref, computed, onMounted, getCurrentInstance, watch } from 'vue'
import { useI18n } from 'vue-i18n'
import CollapseIcon from './TransactionRows/CollapseIcon'
import TransactionCollapse from './TransactionCollapse'
import { GdtEntryType } from '../graphql/enums'
import { useStore } from 'vuex'
const props = defineProps({
amount: Number,
@ -71,10 +72,14 @@ const props = defineProps({
const collapseStatus = ref([])
const visible = ref(false)
const store = useStore()
const { t, n } = useI18n()
const collapseId = computed(() => 'gdt-collapse-' + String(props.id))
const transactionToHighlightId = computed(() => store.state.transactionToHighlightId)
const getLinesByType = computed(() => {
switch (props.gdtEntryType) {
case GdtEntryType.FORM:
@ -116,6 +121,12 @@ const getLinesByType = computed(() => {
}
})
watch(transactionToHighlightId, () => {
if (parseInt(transactionToHighlightId.value) === props.id) {
visible.value = true
}
})
// onMounted(() => {
// // Note: This event listener setup might need to be adjusted for Vue 3
// const root = getCurrentInstance().appContext.config.globalProperties

View File

@ -1,5 +1,6 @@
<template>
<div>
<slot name="item" />
<slot :name="typeId"></slot>
</div>
</template>

View File

@ -0,0 +1,167 @@
<template>
<div
:id="`transaction-${props.transaction.id}`"
ref="gddTransaction"
:class="`transaction-slot-${props.transaction.type}`"
:data-transaction-id="`transaction-${props.transaction.id}`"
@click="toggleVisible"
>
<BRow class="align-items-center">
<BCol cols="3" lg="2" md="2">
<component :is="avatarComponent" v-bind="avatarProps">
<variant-icon v-if="isCreationType" icon="gift" variant="white" />
</component>
</BCol>
<BCol>
<div>
<component :is="nameComponent" v-bind="nameProps" />
</div>
<span class="small">{{ $d(new Date(props.transaction.balanceDate), 'short') }}</span>
<span class="ms-4 small">{{ $d(new Date(props.transaction.balanceDate), 'time') }}</span>
</BCol>
<BCol cols="8" lg="3" md="3" sm="8" offset="3" offset-md="0" offset-lg="0">
<div class="small mb-2">
{{ $t(`decay.types.${props.transaction.typeId.toLowerCase()}`) }}
</div>
<div
:class="[
'fw-bold',
{
'gradido-global-color-accent': props.transaction.typeId === 'RECEIVE',
'text-140': props.transaction.typeId === 'SEND',
},
]"
data-test="transaction-amount"
>
{{ $filters.GDD(props.transaction.amount) }}
</div>
<div v-if="props.transaction.linkId" class="small">
{{ $t('via_link') }}
<variant-icon icon="link45deg" variant="muted" class="m-mb-1" />
</div>
</BCol>
<BCol cols="12" md="1" lg="1" class="text-end">
<collapse-icon class="text-end" :visible="visible" />
</BCol>
</BRow>
<BCollapse :model-value="visible" class="pb-4 pt-lg-3">
<decay-information
:type-id="props.transaction.typeId"
:decay="props.transaction.decay"
:amount="props.transaction.amount"
:memo="props.transaction.memo"
:balance="props.transaction.balance"
:previous-balance="props.transaction.previousBalance"
/>
</BCollapse>
</div>
</template>
<script setup>
import { ref, computed, watch } from 'vue'
import { useStore } from 'vuex'
import Avatar from 'vue-avatar'
import CollapseIcon from '../TransactionRows/CollapseIcon'
import Name from '../TransactionRows/Name'
import DecayInformation from '../DecayInformations/DecayInformation'
import { BAvatar, BRow } from 'bootstrap-vue-next'
const props = defineProps({
transaction: {
type: Object,
required: true,
},
})
const gddTransaction = ref(null)
const store = useStore()
const visible = ref(false)
const toggleVisible = () => {
visible.value = !visible.value
}
const username = computed(() => ({
username: `${props.transaction?.linkedUser?.firstName} ${props.transaction?.linkedUser?.lastName}`,
initials: `${props.transaction?.linkedUser?.firstName[0]}${props.transaction.linkedUser?.lastName[0]}`,
}))
const isCreationType = computed(() => {
return props.transaction.typeId === 'CREATION'
})
const avatarComponent = computed(() => {
return isCreationType.value ? BAvatar : Avatar
})
const avatarProps = computed(() => {
if (isCreationType.value) {
return {
size: 42,
rounded: 'lg',
variant: 'success',
}
} else {
return {
username: username.value.username,
initials: username.value.initials,
color: '#fff',
size: 42,
}
}
})
const nameComponent = computed(() => {
return isCreationType.value ? 'div' : Name
})
const nameProps = computed(() => {
if (isCreationType.value) {
return {
class: 'fw-bold',
}
} else {
return {
class: 'fw-bold',
amount: props.transaction.amount,
linkedUser: props.transaction.linkedUser,
linkId: props.transaction.linkId,
}
}
})
const handleOpenAfterScroll = (scrollY) => {
const handleScrollEnd = () => {
window.removeEventListener('scrollend', handleScrollEnd)
}
window.addEventListener('scrollend', handleScrollEnd)
window.scrollTo(0, scrollY)
}
const transactionToHighlightId = computed(() => store.state.transactionToHighlightId)
watch(
transactionToHighlightId,
async (newValue) => {
if (parseInt(newValue) === props.transaction.id) {
visible.value = true
setTimeout(() => {
const element = document.getElementById(`transaction-${props.transaction.id}`)
const yVal = element.getBoundingClientRect().top + window.pageYOffset - 16
handleOpenAfterScroll(yVal)
}, 300)
await store.dispatch('changeTransactionToHighlightId', '')
}
},
{ immediate: true },
)
</script>
<style lang="scss" scoped>
:deep(.b-avatar-custom > svg) {
height: 2em;
width: 2em;
}
</style>

View File

@ -309,6 +309,7 @@ const setVisible = (bool) => {
<style>
.breadcrumb {
background-color: transparent;
padding: 0.75rem 1rem;
}
.main-page {

View File

@ -48,7 +48,6 @@ const transactionsGdt = ref([])
const transactionGdtCount = ref(0)
const currentPage = ref(1)
const pageSize = ref(25)
// const tabIndex = ref(0)
const { toastError } = useAppToast()

View File

@ -38,6 +38,7 @@ const routes = [
// },
// },
{
name: 'Transactions',
path: '/transactions',
component: () => import('@/pages/Transactions'),
props: { gdt: false },

View File

@ -75,6 +75,9 @@ export const mutations = {
redirectPath: (state, redirectPath) => {
state.redirectPath = redirectPath || '/overview'
},
setTransactionToHighlightId: (state, id) => {
state.transactionToHighlightId = id
},
}
export const actions = {
@ -119,6 +122,9 @@ export const actions = {
commit('redirectPath', '/overview')
localStorage.clear()
},
changeTransactionToHighlightId({ commit }, id) {
commit('setTransactionToHighlightId', id)
},
}
let store
@ -153,6 +159,7 @@ try {
email: '',
darkMode: false,
redirectPath: '/overview',
transactionToHighlightId: '',
},
getters: {},
// Synchronous mutation of the state