Skip to content

Commit 16a52ec

Browse files
committed
refactor: consolidate event filter hooks, UI components, and shared utilities
- Extract shared useEventFilters hook replacing 3 duplicated filter hooks - Create EventsFilterBar and EventsMobileFiltersModal shared components - Add mobile filter validation for tick range and date range - Centralize TickRangeValue type and toTickRangeValue helper - Compose formatRangeLabel inside getAmountRangeLabel - Extract AddressCell component for highlighted address pattern - Centralize buildTickFilter and parseTickRange utilities
1 parent 55391a3 commit 16a52ec

28 files changed

+1359
-533
lines changed

src/pages/network/address/AddressPage.tsx

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,13 @@ import { useGetSmartContractsQuery } from '@app/store/apis/qubic-static'
2020
import { clsxTwMerge, formatEllipsis, formatString, isValidQubicAddress } from '@app/utils'
2121
import { useGetAddressName } from '@app/hooks'
2222
import { HomeLink } from '../components'
23-
import { AddressDetails, ContractOverview, OwnedAssets, TransactionsOverview } from './components'
23+
import {
24+
AddressDetails,
25+
AddressEvents,
26+
ContractOverview,
27+
OwnedAssets,
28+
TransactionsOverview
29+
} from './components'
2430

2531
function AddressPage() {
2632
const { t } = useTranslation('network-page')
@@ -80,13 +86,14 @@ function AddressPage() {
8086
const tabParam = searchParams.get('tab')
8187

8288
const selectedTabIndex = useMemo(() => {
83-
if (tabParam === 'contract' && isSmartContract) return 1
89+
if (tabParam === 'events') return 1
90+
if (tabParam === 'contract' && isSmartContract) return 2
8491
return 0
8592
}, [tabParam, isSmartContract])
8693

8794
// Normalize invalid tab params so URL always reflects the visible tab
8895
useEffect(() => {
89-
const isValidTab = tabParam === 'contract' && isSmartContract
96+
const isValidTab = tabParam === 'events' || (tabParam === 'contract' && isSmartContract)
9097
if (tabParam && !isValidTab) {
9198
setSearchParams(
9299
(prev) => {
@@ -103,6 +110,8 @@ function AddressPage() {
103110
setSearchParams(
104111
(prev) => {
105112
if (index === 1) {
113+
prev.set('tab', 'events')
114+
} else if (index === 2) {
106115
prev.set('tab', 'contract')
107116
} else {
108117
prev.delete('tab')
@@ -219,12 +228,16 @@ function AddressPage() {
219228
>
220229
<Tabs.List>
221230
<Tabs.Tab>{t('transactions')}</Tabs.Tab>
231+
<Tabs.Tab>{t('events')}</Tabs.Tab>
222232
{isSmartContract && <Tabs.Tab>{t('contract')}</Tabs.Tab>}
223233
</Tabs.List>
224234
<Tabs.Panels>
225235
<Tabs.Panel>
226236
<TransactionsOverview addressId={addressId} />
227237
</Tabs.Panel>
238+
<Tabs.Panel>
239+
<AddressEvents addressId={addressId} />
240+
</Tabs.Panel>
228241
{isSmartContract && smartContractDetails && (
229242
<Tabs.Panel>
230243
<ContractOverview
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
import { useTranslation } from 'react-i18next'
2+
3+
import { Alert } from '@app/components/ui'
4+
import BetaBanner from '../../components/BetaBanner'
5+
import { EventsFilterBar } from '../../components/filters'
6+
import TransactionEvents from '../../components/TxItem/TransactionEvents'
7+
import { useEventFilters } from '../../hooks'
8+
import { useAddressEvents } from '../hooks'
9+
10+
type Props = Readonly<{
11+
addressId: string
12+
}>
13+
14+
export default function AddressEvents({ addressId }: Props) {
15+
const { t } = useTranslation('network-page')
16+
const {
17+
events,
18+
total,
19+
eventType,
20+
tickStart,
21+
tickEnd,
22+
dateRange,
23+
sourceFilter,
24+
destinationFilter,
25+
isLoading,
26+
hasError
27+
} = useAddressEvents(addressId)
28+
29+
const filters = useEventFilters({
30+
tickStart,
31+
tickEnd,
32+
eventType,
33+
dateRange,
34+
sourceFilter,
35+
destinationFilter
36+
})
37+
38+
return (
39+
<div className="flex flex-col gap-16">
40+
<BetaBanner />
41+
42+
<EventsFilterBar
43+
filters={filters}
44+
eventType={eventType}
45+
tickStart={tickStart}
46+
tickEnd={tickEnd}
47+
dateRange={dateRange}
48+
sourceFilter={sourceFilter}
49+
destinationFilter={destinationFilter}
50+
idPrefix="addr-events"
51+
/>
52+
53+
{hasError ? (
54+
<Alert variant="error">{t('eventsLoadFailed')}</Alert>
55+
) : (
56+
<TransactionEvents
57+
events={events}
58+
total={total}
59+
isLoading={isLoading}
60+
paginated
61+
showTxId
62+
showTickAndTimestamp
63+
showBetaBanner={false}
64+
highlightAddress={addressId}
65+
/>
66+
)}
67+
</div>
68+
)
69+
}

src/pages/network/address/components/TransactionsOverview/TransactionFiltersBar.tsx

Lines changed: 15 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,12 @@ import {
1515
RangeFilterContent,
1616
ResetFiltersButton
1717
} from '../../../components/filters'
18-
import { formatRangeLabel, useAmountPresetHandler, useClearFilterHandler } from '../../../hooks'
18+
import {
19+
formatRangeLabel,
20+
getAmountRangeLabel,
21+
useAmountPresetHandler,
22+
useClearFilterHandler
23+
} from '../../../hooks'
1924
import type {
2025
AddressFilter,
2126
TransactionDirection,
@@ -271,21 +276,13 @@ export default function TransactionFiltersBar({
271276
return `${t('destination')}: ${prefix}${destinationAddresses.length}`
272277
}
273278

274-
const getAmountLabel = () => {
275-
if (!isAmountActive) return t('amount')
276-
const { start, end, presetKey } = activeFilters.amountRange || {}
277-
278-
if (presetKey) {
279-
const preset = AMOUNT_PRESETS.find((p) => p.labelKey === presetKey)
280-
if (preset) return `${t('amount')}: ${t(preset.labelKey)}`
281-
}
282-
283-
if (start && end)
284-
return `${t('amount')}: ${formatAmountShort(start, t)} - ${formatAmountShort(end, t)}`
285-
if (start) return `${t('amount')}: >= ${formatAmountShort(start, t)}`
286-
if (end) return `${t('amount')}: <= ${formatAmountShort(end, t)}`
287-
return t('amount')
288-
}
279+
const amountLabel = getAmountRangeLabel(
280+
t('amount'),
281+
activeFilters.amountRange,
282+
AMOUNT_PRESETS,
283+
t,
284+
formatAmountShort
285+
)
289286

290287
const getDateLabel = () => {
291288
if (!isDateActive) return t('date')
@@ -339,9 +336,7 @@ export default function TransactionFiltersBar({
339336
{isDestinationActive && (
340337
<ActiveFilterChip label={getDestinationLabel()} onClear={clearDestinationFilter} />
341338
)}
342-
{isAmountActive && (
343-
<ActiveFilterChip label={getAmountLabel()} onClear={clearAmountFilter} />
344-
)}
339+
{isAmountActive && <ActiveFilterChip label={amountLabel} onClear={clearAmountFilter} />}
345340
{isDateActive && <ActiveFilterChip label={getDateLabel()} onClear={clearDateFilter} />}
346341
{isTickActive && <ActiveFilterChip label={getTickLabel()} onClear={clearTickFilter} />}
347342
{isInputTypeActive && (
@@ -432,7 +427,7 @@ export default function TransactionFiltersBar({
432427

433428
{/* Amount Filter */}
434429
<FilterDropdown
435-
label={getAmountLabel()}
430+
label={amountLabel}
436431
isActive={isAmountActive}
437432
show={openDropdown === 'amount'}
438433
onToggle={() => handleToggle('amount')}

src/pages/network/address/components/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
export { default as AddressDetails } from './AddressDetails'
2+
export { default as AddressEvents } from './AddressEvents'
23
export { default as ContractOverview } from './ContractOverview'
34
export { default as OwnedAssets } from './OwnedAssets'
45
export { default as LatestTransactions } from './TransactionsOverview/LatestTransactions'
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
1+
export { default as useAddressEvents } from './useAddressEvents'
12
export { default as useLatestTransactions } from './useLatestTransactions'
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
import { useMemo } from 'react'
2+
import { useSearchParams } from 'react-router-dom'
3+
4+
import {
5+
usePageAutoCorrect,
6+
useSanitizedEventType,
7+
useValidatedPage,
8+
useValidatedPageSize
9+
} from '@app/hooks'
10+
import { type ShouldFilter, type TransactionEvent, useGetEventsQuery } from '@app/store/apis/events'
11+
import type { AddressFilter } from '../components/TransactionsOverview/filterUtils'
12+
import {
13+
buildEventAddressFilter,
14+
buildTickFilter,
15+
buildTimestampRange,
16+
type DateRangeValue,
17+
parseAddressFilter,
18+
parseDateRange,
19+
parseTickRange
20+
} from '../../utils/eventFilterUtils'
21+
22+
export default function useAddressEvents(addressId: string): {
23+
events: TransactionEvent[]
24+
total: number
25+
eventType: number | undefined
26+
tickStart: string | undefined
27+
tickEnd: string | undefined
28+
dateRange: DateRangeValue | undefined
29+
sourceFilter: AddressFilter | undefined
30+
destinationFilter: AddressFilter | undefined
31+
isLoading: boolean
32+
hasError: boolean
33+
} {
34+
const [searchParams] = useSearchParams()
35+
36+
const { start: tickStart, end: tickEnd } = parseTickRange(searchParams)
37+
38+
const dateRange = parseDateRange(searchParams)
39+
const sourceFilter = parseAddressFilter(searchParams, 'source', 'sourceMode')
40+
const destinationFilter = parseAddressFilter(searchParams, 'destination', 'destMode')
41+
42+
const page = useValidatedPage()
43+
const pageSize = useValidatedPageSize()
44+
const offset = (page - 1) * pageSize
45+
46+
const eventType = useSanitizedEventType()
47+
48+
const should = useMemo<ShouldFilter[]>(
49+
() => [{ terms: { source: addressId, destination: addressId } }],
50+
[addressId]
51+
)
52+
53+
const { tickNumber, tickRange } = buildTickFilter(tickStart, tickEnd)
54+
55+
const timestampRange = buildTimestampRange(dateRange)
56+
const sourceResult = buildEventAddressFilter(sourceFilter)
57+
const destResult = buildEventAddressFilter(destinationFilter)
58+
59+
const { data, isFetching, isError } = useGetEventsQuery(
60+
{
61+
should,
62+
tickNumber,
63+
tickRange,
64+
timestampRange,
65+
offset,
66+
size: pageSize,
67+
logType: eventType,
68+
source: sourceResult.include,
69+
excludeSource: sourceResult.exclude,
70+
destination: destResult.include,
71+
excludeDestination: destResult.exclude
72+
},
73+
{ skip: !addressId }
74+
)
75+
76+
const total = data?.total ?? 0
77+
78+
usePageAutoCorrect(!!data, total, pageSize)
79+
80+
return {
81+
events: data?.events ?? [],
82+
total,
83+
eventType,
84+
tickStart,
85+
tickEnd,
86+
dateRange,
87+
sourceFilter,
88+
destinationFilter,
89+
isLoading: isFetching,
90+
hasError: isError
91+
}
92+
}

src/pages/network/blockchain/events/EventsMobileFiltersModal.tsx

Lines changed: 0 additions & 77 deletions
This file was deleted.

0 commit comments

Comments
 (0)