feat(game): 优化界面组件
- 在国际化文件中添加钱包流水相关翻译项 - 在用户个人资料页面添加复制邀请链接功能 - 优化桌面端动物组件的视觉效果和动画参数 - 添加虚拟滚动功能到财务记录标签页提升性能 - 为桌面端控制面板添加投注数量调节按钮 - 更新消息模态框为通知列表和详情展示 - 在头部余额显示旁添加充值图标入口
This commit is contained in:
@@ -54,6 +54,9 @@ export function useAnimalVm(
|
||||
)
|
||||
const setModalOpen = useModalStore((state) => state.setModalOpen)
|
||||
const activeChipId = useGameRoundStore((state) => state.activeChipId)
|
||||
const activeBetQuantity = useGameRoundStore(
|
||||
(state) => state.activeBetQuantity,
|
||||
)
|
||||
const chips = useGameRoundStore((state) => state.chips)
|
||||
const clearSelections = useGameRoundStore((state) => state.clearSelections)
|
||||
const roundId = useGameRoundStore((state) => state.round.id)
|
||||
@@ -188,7 +191,9 @@ export function useAnimalVm(
|
||||
return
|
||||
}
|
||||
|
||||
if (totalBetAmount + (activeChip?.amount ?? 0) > balance) {
|
||||
const nextBetAmount = (activeChip?.amount ?? 0) * activeBetQuantity
|
||||
|
||||
if (totalBetAmount + nextBetAmount > balance) {
|
||||
setCellWarning({
|
||||
cellId: animalId,
|
||||
type: 'balance',
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useEffect, useRef, useState } from 'react'
|
||||
import { useEffect, useRef } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
|
||||
import { placeGameBet } from '@/features/game'
|
||||
@@ -44,32 +44,40 @@ function toBetId(chipId: string) {
|
||||
return Number.isInteger(betId) && betId >= 1 && betId <= 6 ? betId : null
|
||||
}
|
||||
|
||||
function formatBetAmount(amount: number) {
|
||||
if (Number.isInteger(amount)) {
|
||||
return String(amount)
|
||||
}
|
||||
|
||||
return amount.toFixed(2).replace(/\.?0+$/, '')
|
||||
}
|
||||
|
||||
function groupSelections(selections: BetSelection[]) {
|
||||
return selections.reduce<Map<string, { betId: number; numbers: number[] }>>(
|
||||
(accumulator, selection) => {
|
||||
const betId = toBetId(selection.chipId)
|
||||
|
||||
if (betId === null) {
|
||||
return accumulator
|
||||
}
|
||||
|
||||
const groupKey = String(betId)
|
||||
const current = accumulator.get(groupKey)
|
||||
|
||||
if (current) {
|
||||
current.numbers.push(selection.cellId)
|
||||
return accumulator
|
||||
}
|
||||
|
||||
accumulator.set(groupKey, {
|
||||
betId,
|
||||
numbers: [selection.cellId],
|
||||
})
|
||||
return selections.reduce<
|
||||
Map<string, { amount: number; betId: number; numbers: number[] }>
|
||||
>((accumulator, selection) => {
|
||||
const betId = toBetId(selection.chipId)
|
||||
|
||||
if (betId === null) {
|
||||
return accumulator
|
||||
},
|
||||
new Map(),
|
||||
)
|
||||
}
|
||||
|
||||
const groupKey = `${betId}:${selection.amount}`
|
||||
const current = accumulator.get(groupKey)
|
||||
|
||||
if (current) {
|
||||
current.numbers.push(selection.cellId)
|
||||
return accumulator
|
||||
}
|
||||
|
||||
accumulator.set(groupKey, {
|
||||
amount: selection.amount,
|
||||
betId,
|
||||
numbers: [selection.cellId],
|
||||
})
|
||||
|
||||
return accumulator
|
||||
}, new Map())
|
||||
}
|
||||
|
||||
export function useAutoHostingRunner() {
|
||||
@@ -79,9 +87,10 @@ export function useAutoHostingRunner() {
|
||||
const setCurrentUser = useAuthStore((state) => state.setCurrentUser)
|
||||
const round = useGameRoundStore((state) => state.round)
|
||||
const clearSelections = useGameRoundStore((state) => state.clearSelections)
|
||||
const balanceAfterBet = useGameAutoHostingStore(
|
||||
(state) => state.balanceAfterBet,
|
||||
const lastSingleWinAmount = useGameAutoHostingStore(
|
||||
(state) => state.lastSingleWinAmount,
|
||||
)
|
||||
const lastIsJackpot = useGameAutoHostingStore((state) => state.lastIsJackpot)
|
||||
const isHosting = useGameAutoHostingStore((state) => state.isHosting)
|
||||
const lastSubmittedRoundId = useGameAutoHostingStore(
|
||||
(state) => state.lastSubmittedRoundId,
|
||||
@@ -92,14 +101,10 @@ export function useAutoHostingRunner() {
|
||||
(state) => state.markRoundSubmitted,
|
||||
)
|
||||
const stopHosting = useGameAutoHostingStore((state) => state.stopHosting)
|
||||
const [isSubmitting, setIsSubmitting] = useState(false)
|
||||
const previousJackpotRef = useRef(currentUser?.isJackpot === true)
|
||||
const inFlightRoundIdRef = useRef<string | null>(null)
|
||||
|
||||
useEffect(() => {
|
||||
const isJackpot = currentUser?.isJackpot === true
|
||||
|
||||
if (!isHosting) {
|
||||
previousJackpotRef.current = isJackpot
|
||||
return
|
||||
}
|
||||
|
||||
@@ -116,30 +121,24 @@ export function useAutoHostingRunner() {
|
||||
|
||||
if (
|
||||
rules.stopIfSingleWinAbove.enabled &&
|
||||
balanceAfterBet !== null &&
|
||||
balance - balanceAfterBet > rules.stopIfSingleWinAbove.amount
|
||||
lastSingleWinAmount !== null &&
|
||||
lastSingleWinAmount > rules.stopIfSingleWinAbove.amount
|
||||
) {
|
||||
stopHosting()
|
||||
notify.success(t('commonUi.toast.autoHostingStoppedWin'))
|
||||
return
|
||||
}
|
||||
|
||||
if (
|
||||
rules.stopOnJackpot &&
|
||||
isJackpot &&
|
||||
previousJackpotRef.current === false
|
||||
) {
|
||||
if (rules.stopOnJackpot && lastIsJackpot === true) {
|
||||
stopHosting()
|
||||
notify.success(t('commonUi.toast.autoHostingStoppedJackpot'))
|
||||
return
|
||||
}
|
||||
|
||||
previousJackpotRef.current = isJackpot
|
||||
}, [
|
||||
balanceAfterBet,
|
||||
currentUser?.coin,
|
||||
currentUser?.isJackpot,
|
||||
isHosting,
|
||||
lastIsJackpot,
|
||||
lastSingleWinAmount,
|
||||
rules,
|
||||
stopHosting,
|
||||
t,
|
||||
@@ -148,7 +147,7 @@ export function useAutoHostingRunner() {
|
||||
useEffect(() => {
|
||||
if (
|
||||
!isHosting ||
|
||||
isSubmitting ||
|
||||
inFlightRoundIdRef.current !== null ||
|
||||
authStatus !== 'authenticated' ||
|
||||
!currentUser ||
|
||||
round.phase !== 'betting' ||
|
||||
@@ -178,11 +177,10 @@ export function useAutoHostingRunner() {
|
||||
return
|
||||
}
|
||||
|
||||
let cancelled = false
|
||||
const submittingRoundId = round.id
|
||||
inFlightRoundIdRef.current = submittingRoundId
|
||||
|
||||
const submitAutoBet = async () => {
|
||||
setIsSubmitting(true)
|
||||
|
||||
try {
|
||||
let latestBalance = currentUser.coin ?? '0'
|
||||
|
||||
@@ -190,11 +188,14 @@ export function useAutoHostingRunner() {
|
||||
const uniqueNumbers = [...new Set(group.numbers)].sort(
|
||||
(left, right) => left - right,
|
||||
)
|
||||
const formattedSingleBetAmount = formatBetAmount(group.amount)
|
||||
const result = await placeGameBet({
|
||||
bet_amount: formattedSingleBetAmount,
|
||||
bet_id: group.betId,
|
||||
idempotency_key: createIdempotencyKey(),
|
||||
numbers: uniqueNumbers.join(','),
|
||||
period_no: round.id,
|
||||
single_bet_amount: formattedSingleBetAmount,
|
||||
})
|
||||
|
||||
if (result.status !== 'accepted') {
|
||||
@@ -204,42 +205,44 @@ export function useAutoHostingRunner() {
|
||||
latestBalance = result.balance_after
|
||||
}
|
||||
|
||||
if (cancelled) {
|
||||
const latestHostingState = useGameAutoHostingStore.getState()
|
||||
const latestUser = useAuthStore.getState().currentUser
|
||||
|
||||
if (
|
||||
!latestHostingState.isHosting ||
|
||||
latestHostingState.lastSubmittedRoundId === submittingRoundId ||
|
||||
!latestUser
|
||||
) {
|
||||
return
|
||||
}
|
||||
|
||||
setCurrentUser({
|
||||
...currentUser,
|
||||
...latestUser,
|
||||
coin: latestBalance,
|
||||
lastBetPeriodNo: round.id,
|
||||
lastBetPeriodNo: submittingRoundId,
|
||||
})
|
||||
markRoundSubmitted(round.id, parseBalance(latestBalance))
|
||||
markRoundSubmitted(submittingRoundId, parseBalance(latestBalance))
|
||||
clearSelections()
|
||||
} catch (error) {
|
||||
if (!cancelled) {
|
||||
if (useGameAutoHostingStore.getState().isHosting) {
|
||||
stopHosting()
|
||||
notify.error(t('commonUi.toast.autoHostingSubmitFailed'), {
|
||||
description: error instanceof Error ? error.message : undefined,
|
||||
})
|
||||
}
|
||||
} finally {
|
||||
if (!cancelled) {
|
||||
setIsSubmitting(false)
|
||||
if (inFlightRoundIdRef.current === submittingRoundId) {
|
||||
inFlightRoundIdRef.current = null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void submitAutoBet()
|
||||
|
||||
return () => {
|
||||
cancelled = true
|
||||
}
|
||||
}, [
|
||||
authStatus,
|
||||
clearSelections,
|
||||
currentUser,
|
||||
isHosting,
|
||||
isSubmitting,
|
||||
lastSubmittedRoundId,
|
||||
markRoundSubmitted,
|
||||
round.id,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useQuery } from '@tanstack/react-query'
|
||||
import { useInfiniteQuery } from '@tanstack/react-query'
|
||||
import { useCallback, useEffect, useMemo, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
|
||||
@@ -38,25 +38,33 @@ export function useFinanceRecordsVm({ enabled }: { enabled: boolean }) {
|
||||
const { i18n, t } = useTranslation()
|
||||
const locale = i18n.resolvedLanguage ?? i18n.language ?? 'en-US'
|
||||
const [recordType, setRecordType] = useState<FinanceRecordType>('deposit')
|
||||
const [page, setPage] = useState(1)
|
||||
|
||||
const query = useQuery({
|
||||
queryKey: ['finance', 'user-info-order-list', recordType, page],
|
||||
queryFn: () =>
|
||||
const query = useInfiniteQuery({
|
||||
queryKey: ['finance', 'user-info-order-list', recordType],
|
||||
initialPageParam: 1,
|
||||
queryFn: ({ pageParam }) =>
|
||||
recordType === 'deposit'
|
||||
? getDepositOrderList({
|
||||
page,
|
||||
page: pageParam,
|
||||
pageSize: FINANCE_RECORD_PAGE_SIZE,
|
||||
})
|
||||
: getWithdrawOrderList({
|
||||
page,
|
||||
page: pageParam,
|
||||
pageSize: FINANCE_RECORD_PAGE_SIZE,
|
||||
}),
|
||||
enabled,
|
||||
getNextPageParam: (lastPage) => {
|
||||
const nextPage = lastPage.pagination.page + 1
|
||||
const loadedCount =
|
||||
lastPage.pagination.page * lastPage.pagination.page_size
|
||||
|
||||
return loadedCount < lastPage.pagination.total ? nextPage : undefined
|
||||
},
|
||||
})
|
||||
|
||||
const pagination = query.data?.pagination
|
||||
const total = pagination?.total ?? 0
|
||||
const lastPage = query.data?.pages.at(-1)
|
||||
const total = lastPage?.pagination.total ?? 0
|
||||
const loadedPage = lastPage?.pagination.page ?? 1
|
||||
|
||||
const recordTypes = useMemo(
|
||||
() =>
|
||||
@@ -69,58 +77,46 @@ export function useFinanceRecordsVm({ enabled }: { enabled: boolean }) {
|
||||
|
||||
const items = useMemo(
|
||||
() =>
|
||||
(query.data?.list ?? []).map((item) => ({
|
||||
amountLabel: formatFinanceAmount(item.amount, locale),
|
||||
bonusAmountLabel: formatFinanceAmount(item.bonusAmount, locale),
|
||||
id: item.orderNo,
|
||||
orderNoLabel: item.orderNo || '--',
|
||||
})),
|
||||
[locale, query.data?.list],
|
||||
(query.data?.pages ?? []).flatMap((page) =>
|
||||
page.list.map((item, index) => ({
|
||||
amountLabel: formatFinanceAmount(item.amount, locale),
|
||||
bonusAmountLabel: formatFinanceAmount(item.bonusAmount, locale),
|
||||
id: item.orderNo || `${page.pagination.page}-${index}`,
|
||||
orderNoLabel: item.orderNo || '--',
|
||||
})),
|
||||
),
|
||||
[locale, query.data?.pages],
|
||||
)
|
||||
|
||||
const selectRecordType = useCallback((type: FinanceRecordType) => {
|
||||
setRecordType(type)
|
||||
setPage(1)
|
||||
}, [])
|
||||
|
||||
const goPreviousPage = useCallback(() => {
|
||||
setPage((currentPage) => Math.max(1, currentPage - 1))
|
||||
}, [])
|
||||
|
||||
const goNextPage = useCallback(() => {
|
||||
setPage((currentPage) => currentPage + 1)
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
if (!enabled) {
|
||||
setRecordType('deposit')
|
||||
setPage(1)
|
||||
}
|
||||
}, [enabled])
|
||||
|
||||
return {
|
||||
canGoNextPage: page * FINANCE_RECORD_PAGE_SIZE < total,
|
||||
canGoPreviousPage: page > 1,
|
||||
emptyText: t('game.modals.userInfo.financeRecords.empty'),
|
||||
goNextPage,
|
||||
goPreviousPage,
|
||||
fetchNextPage: query.fetchNextPage,
|
||||
hasNextPage: query.hasNextPage,
|
||||
headers: {
|
||||
amount: t('game.modals.userInfo.financeRecords.amount'),
|
||||
bonusAmount: t('game.modals.userInfo.financeRecords.bonusAmount'),
|
||||
orderNo: t('game.modals.userInfo.financeRecords.orderNo'),
|
||||
},
|
||||
isError: query.isError,
|
||||
isFetching: query.isFetching,
|
||||
isFetchingNextPage: query.isFetchingNextPage,
|
||||
isLoading: query.isLoading,
|
||||
items,
|
||||
loadFailedText: t('game.modals.userInfo.financeRecords.loadFailed'),
|
||||
loadingText: t('game.modals.userInfo.financeRecords.loading'),
|
||||
pageLabel: t('game.modals.userInfo.financeRecords.page', {
|
||||
page: pagination?.page ?? page,
|
||||
page: loadedPage,
|
||||
total,
|
||||
}),
|
||||
nextText: t('game.modals.userInfo.financeRecords.next'),
|
||||
previousText: t('game.modals.userInfo.financeRecords.previous'),
|
||||
recordType,
|
||||
recordTypes,
|
||||
selectRecordType,
|
||||
|
||||
@@ -62,10 +62,16 @@ export function useGameControlVm() {
|
||||
const { t } = useTranslation()
|
||||
const chips = useGameRoundStore((state) => state.chips)
|
||||
const activeChipId = useGameRoundStore((state) => state.activeChipId)
|
||||
const activeBetQuantity = useGameRoundStore(
|
||||
(state) => state.activeBetQuantity,
|
||||
)
|
||||
const round = useGameRoundStore((state) => state.round)
|
||||
const maxSelectionCount = useGameRoundStore(
|
||||
(state) => state.maxSelectionCount,
|
||||
)
|
||||
const adjustBetQuantity = useGameRoundStore(
|
||||
(state) => state.adjustBetQuantity,
|
||||
)
|
||||
const selections = useGameRoundStore((state) => state.selections)
|
||||
const clearSelections = useGameRoundStore((state) => state.clearSelections)
|
||||
const restoreRecentSuccessfulSelections = useGameRoundStore(
|
||||
@@ -151,32 +157,10 @@ export function useGameControlVm() {
|
||||
return
|
||||
}
|
||||
|
||||
const groupedSelections = selections.reduce<
|
||||
Map<string, { betId: number; numbers: number[] }>
|
||||
>((accumulator, selection) => {
|
||||
const betId = toBetId(selection.chipId)
|
||||
const betId = toBetId(selections[0]?.chipId ?? activeChipId)
|
||||
const singleBetAmount = selections[0]?.amount ?? selectedChip?.amount ?? 0
|
||||
|
||||
if (betId === null) {
|
||||
return accumulator
|
||||
}
|
||||
|
||||
const groupKey = String(betId)
|
||||
const current = accumulator.get(groupKey)
|
||||
|
||||
if (current) {
|
||||
current.numbers.push(selection.cellId)
|
||||
return accumulator
|
||||
}
|
||||
|
||||
accumulator.set(groupKey, {
|
||||
betId,
|
||||
numbers: [selection.cellId],
|
||||
})
|
||||
|
||||
return accumulator
|
||||
}, new Map())
|
||||
|
||||
if (groupedSelections.size === 0) {
|
||||
if (betId === null || singleBetAmount <= 0) {
|
||||
notify.warning(t('commonUi.toast.betUnavailable'))
|
||||
return
|
||||
}
|
||||
@@ -186,24 +170,25 @@ export function useGameControlVm() {
|
||||
try {
|
||||
let latestBalance = currentUser?.coin ?? '0'
|
||||
|
||||
for (const group of groupedSelections.values()) {
|
||||
const uniqueNumbers = [...new Set(group.numbers)].sort(
|
||||
(left, right) => left - right,
|
||||
)
|
||||
const result = await placeGameBet({
|
||||
bet_id: group.betId,
|
||||
idempotency_key: createIdempotencyKey(),
|
||||
numbers: uniqueNumbers.join(','),
|
||||
period_no: round.id,
|
||||
})
|
||||
const uniqueNumbers = [
|
||||
...new Set(selections.map((item) => item.cellId)),
|
||||
].sort((left, right) => left - right)
|
||||
const formattedSingleBetAmount = formatChipDisplayValue(singleBetAmount)
|
||||
const result = await placeGameBet({
|
||||
bet_amount: formattedSingleBetAmount,
|
||||
bet_id: betId,
|
||||
idempotency_key: createIdempotencyKey(),
|
||||
numbers: uniqueNumbers.join(','),
|
||||
period_no: round.id,
|
||||
single_bet_amount: formattedSingleBetAmount,
|
||||
})
|
||||
|
||||
if (result.status !== 'accepted') {
|
||||
throw new Error(t('commonUi.toast.betRejected'))
|
||||
}
|
||||
|
||||
latestBalance = result.balance_after
|
||||
if (result.status !== 'accepted') {
|
||||
throw new Error(t('commonUi.toast.betRejected'))
|
||||
}
|
||||
|
||||
latestBalance = result.balance_after
|
||||
|
||||
if (currentUser) {
|
||||
setCurrentUser({
|
||||
...currentUser,
|
||||
@@ -223,6 +208,7 @@ export function useGameControlVm() {
|
||||
setIsSubmitting(false)
|
||||
}
|
||||
}, [
|
||||
activeChipId,
|
||||
authStatus,
|
||||
clearSelections,
|
||||
confirmState,
|
||||
@@ -233,6 +219,7 @@ export function useGameControlVm() {
|
||||
round.id,
|
||||
round.phase,
|
||||
selections,
|
||||
selectedChip?.amount,
|
||||
setRecentSuccessfulSelections,
|
||||
setCurrentUser,
|
||||
setModalOpen,
|
||||
@@ -282,6 +269,7 @@ export function useGameControlVm() {
|
||||
round.phase === 'betting' &&
|
||||
!hasSubmittedCurrentRound &&
|
||||
!isAutoHosting,
|
||||
canDecreaseBetQuantity: activeBetQuantity > 1,
|
||||
confirmLabel:
|
||||
confirmState === 'idle'
|
||||
? t('gameDesktop.control.selectNumbers')
|
||||
@@ -293,12 +281,14 @@ export function useGameControlVm() {
|
||||
confirmState,
|
||||
isConfirmClickable: confirmState === 'ready' && !isAutoHosting,
|
||||
onChipSelect: selectChip,
|
||||
onDecreaseBetQuantity: () => adjustBetQuantity(-1),
|
||||
onIncreaseBetQuantity: () => adjustBetQuantity(1),
|
||||
onConfirm: handleConfirm,
|
||||
onClearSelections: clearSelections,
|
||||
onOpenAutoSetting: handleOpenAutoSetting,
|
||||
onRepeatSelections: handleRepeatSelections,
|
||||
maxSelectionCountLabel: maxSelectionCount,
|
||||
selectedChipAmountLabel: selectedChip?.valueLabel ?? '--',
|
||||
selectedBetQuantityLabel: activeBetQuantity,
|
||||
selectedChipId: activeChipId,
|
||||
selectedCountLabel: selections.length,
|
||||
totalBetAmountLabel: formatChipDisplayValue(totalBetAmount),
|
||||
|
||||
@@ -13,7 +13,11 @@ import {
|
||||
type GameSocketMessage,
|
||||
} from '@/lib/ws/game-socket-client'
|
||||
import { getAuthDeviceId, useAuthStore } from '@/store/auth'
|
||||
import { useGameRoundStore, useGameSessionStore } from '@/store/game'
|
||||
import {
|
||||
useGameAutoHostingStore,
|
||||
useGameRoundStore,
|
||||
useGameSessionStore,
|
||||
} from '@/store/game'
|
||||
import { getGameLobbyInit, normalizePeriodTickRound } from '../api/game-api'
|
||||
import type {
|
||||
BetWinEventDataDto,
|
||||
@@ -606,6 +610,10 @@ function applyBetWinMessage(message: GameSocketMessage) {
|
||||
totalWin: betWinData.total_win,
|
||||
winningCellId: betWinData.result_number,
|
||||
})
|
||||
useGameAutoHostingStore.getState().recordBetWin({
|
||||
isJackpot: betWinData.is_jackpot,
|
||||
singleWinAmount: toOptionalNumber(betWinData.total_win) ?? null,
|
||||
})
|
||||
|
||||
if (!currentUser) {
|
||||
return
|
||||
|
||||
106
src/features/game/hooks/use-wallet-records-vm.ts
Normal file
106
src/features/game/hooks/use-wallet-records-vm.ts
Normal file
@@ -0,0 +1,106 @@
|
||||
import { useInfiniteQuery } from '@tanstack/react-query'
|
||||
import dayjs from 'dayjs'
|
||||
import { useMemo } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
|
||||
import { getWalletRecordList } from '@/features/game/api'
|
||||
|
||||
const WALLET_RECORD_PAGE_SIZE = 20
|
||||
const WALLET_RECORD_TYPE = 'payout'
|
||||
|
||||
function formatWalletAmount(value: string, locale: string) {
|
||||
const numberValue = Number(value)
|
||||
|
||||
if (!Number.isFinite(numberValue)) {
|
||||
return value || '--'
|
||||
}
|
||||
|
||||
return new Intl.NumberFormat(locale, {
|
||||
maximumFractionDigits: 4,
|
||||
}).format(numberValue)
|
||||
}
|
||||
|
||||
function formatWalletRecordTime(value: number | string | null) {
|
||||
if (value === null || value === '') {
|
||||
return '--'
|
||||
}
|
||||
|
||||
const numericValue = Number(value)
|
||||
const timestamp =
|
||||
Number.isFinite(numericValue) && numericValue > 0
|
||||
? numericValue < 10_000_000_000
|
||||
? numericValue * 1000
|
||||
: numericValue
|
||||
: value
|
||||
const formatted = dayjs(timestamp).format('YYYY-MM-DD HH:mm:ss')
|
||||
|
||||
return formatted === 'Invalid Date' ? String(value) : formatted
|
||||
}
|
||||
|
||||
export function useWalletRecordsVm({ enabled }: { enabled: boolean }) {
|
||||
const { i18n, t } = useTranslation()
|
||||
const locale = i18n.resolvedLanguage ?? i18n.language ?? 'en-US'
|
||||
|
||||
const query = useInfiniteQuery({
|
||||
queryKey: ['finance', 'wallet-record-list', WALLET_RECORD_TYPE],
|
||||
initialPageParam: 1,
|
||||
queryFn: ({ pageParam }) =>
|
||||
getWalletRecordList({
|
||||
page: pageParam,
|
||||
pageSize: WALLET_RECORD_PAGE_SIZE,
|
||||
type: WALLET_RECORD_TYPE,
|
||||
}),
|
||||
enabled,
|
||||
getNextPageParam: (lastPage) => {
|
||||
const nextPage = lastPage.pagination.page + 1
|
||||
const loadedCount =
|
||||
lastPage.pagination.page * lastPage.pagination.page_size
|
||||
|
||||
return loadedCount < lastPage.pagination.total ? nextPage : undefined
|
||||
},
|
||||
})
|
||||
|
||||
const lastPage = query.data?.pages.at(-1)
|
||||
const total = lastPage?.pagination.total ?? 0
|
||||
const loadedPage = lastPage?.pagination.page ?? 1
|
||||
|
||||
const items = useMemo(
|
||||
() =>
|
||||
(query.data?.pages ?? []).flatMap((page) =>
|
||||
page.list.map((item, index) => ({
|
||||
amountLabel: formatWalletAmount(item.amount, locale),
|
||||
balanceAfterLabel: formatWalletAmount(item.balanceAfter, locale),
|
||||
balanceBeforeLabel: formatWalletAmount(item.balanceBefore, locale),
|
||||
id: item.id || `${page.pagination.page}-${index}`,
|
||||
remarkLabel: item.remark || '--',
|
||||
timeLabel: formatWalletRecordTime(item.createdAt),
|
||||
typeLabel: item.type || WALLET_RECORD_TYPE,
|
||||
})),
|
||||
),
|
||||
[locale, query.data?.pages],
|
||||
)
|
||||
|
||||
return {
|
||||
emptyText: t('game.modals.userInfo.walletRecords.empty'),
|
||||
fetchNextPage: query.fetchNextPage,
|
||||
hasNextPage: query.hasNextPage,
|
||||
headers: {
|
||||
amount: t('game.modals.userInfo.walletRecords.amount'),
|
||||
balanceAfter: t('game.modals.userInfo.walletRecords.balanceAfter'),
|
||||
balanceBefore: t('game.modals.userInfo.walletRecords.balanceBefore'),
|
||||
remark: t('game.modals.userInfo.walletRecords.remark'),
|
||||
time: t('game.modals.userInfo.walletRecords.time'),
|
||||
type: t('game.modals.userInfo.walletRecords.type'),
|
||||
},
|
||||
isError: query.isError,
|
||||
isFetchingNextPage: query.isFetchingNextPage,
|
||||
isLoading: query.isLoading,
|
||||
items,
|
||||
loadFailedText: t('game.modals.userInfo.walletRecords.loadFailed'),
|
||||
loadingText: t('game.modals.userInfo.walletRecords.loading'),
|
||||
pageLabel: t('game.modals.userInfo.walletRecords.page', {
|
||||
page: loadedPage,
|
||||
total,
|
||||
}),
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user