Split desktop and mobile layouts with shared page data so APIs fetch once per context.
Add layout shells, desktop views, module-level page caches, and UI fixes for auth, home, wallet, transactions, address, and related pages. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,188 @@
|
||||
import { computed, inject, onMounted, provide, ref, type ComponentPublicInstance, type InjectionKey } from 'vue'
|
||||
import { useIntersectionObserver } from '@vueuse/core'
|
||||
import { useDesktopBreakpoint } from '@/layouts/composables/useAppLayout'
|
||||
import { loading, loadingMore } from '@/utils'
|
||||
import {
|
||||
BOURSECU_TRANSACTION_TABS,
|
||||
type BoursecuTradeSide,
|
||||
type BoursecuTransactionTab,
|
||||
type BoursecuTransactionTabId,
|
||||
} from '../config/boursecuTabs'
|
||||
import {
|
||||
getActiveBoursecuTab,
|
||||
initTransactionsPageData,
|
||||
loadMoreTransactions,
|
||||
reloadTransactionsTable,
|
||||
resetTransactionsPagination,
|
||||
transactionActiveTabId,
|
||||
transactionDateFilter,
|
||||
transactionItems,
|
||||
transactionPagination,
|
||||
transactionStatusFilter,
|
||||
transactionTableOutput,
|
||||
transactionTradeSide,
|
||||
transactionsBoursecuMode,
|
||||
} from './useTransactionsPageData'
|
||||
|
||||
export interface TransactionsPageContext {
|
||||
pagination: typeof transactionPagination
|
||||
items: typeof transactionItems
|
||||
tableOutput: typeof transactionTableOutput
|
||||
showFilter: ReturnType<typeof ref<boolean>>
|
||||
dateFilter: typeof transactionDateFilter
|
||||
statusFilter: typeof transactionStatusFilter
|
||||
mobileObserverEl: ReturnType<typeof ref<HTMLElement | null>>
|
||||
desktopObserverEl: ReturnType<typeof ref<HTMLElement | null>>
|
||||
activeTabId: typeof transactionActiveTabId
|
||||
tradeSide: typeof transactionTradeSide
|
||||
activeTab: ReturnType<typeof computed<BoursecuTransactionTab>>
|
||||
emptyText: ReturnType<typeof computed<string>>
|
||||
resetPagination: typeof resetTransactionsPagination
|
||||
initTable: () => Promise<void>
|
||||
reloadTable: typeof reloadTransactionsTable
|
||||
applyFilter: () => Promise<void>
|
||||
setActiveTab: (tabId: BoursecuTransactionTabId) => Promise<void>
|
||||
setTradeSide: (side: BoursecuTradeSide) => Promise<void>
|
||||
applyInlineFilters: () => Promise<void>
|
||||
bindMobileObserver: (el: Element | ComponentPublicInstance | null) => void
|
||||
bindDesktopObserver: (el: Element | ComponentPublicInstance | null) => void
|
||||
}
|
||||
|
||||
export const TRANSACTIONS_PAGE_KEY: InjectionKey<TransactionsPageContext> =
|
||||
Symbol('transactionsPage')
|
||||
|
||||
export function useTransactionsPageProvider(options?: { boursecu?: boolean }) {
|
||||
const isBoursecu = options?.boursecu ?? false
|
||||
const isDesktop = useDesktopBreakpoint()
|
||||
const showFilter = ref(false)
|
||||
const mobileObserverEl = ref<HTMLElement | null>(null)
|
||||
const desktopObserverEl = ref<HTMLElement | null>(null)
|
||||
|
||||
const activeObserverEl = computed(() =>
|
||||
isDesktop.value ? desktopObserverEl.value : mobileObserverEl.value,
|
||||
)
|
||||
|
||||
const activeTab = computed<BoursecuTransactionTab>(
|
||||
() =>
|
||||
BOURSECU_TRANSACTION_TABS.find((tab) => tab.id === transactionActiveTabId.value) ??
|
||||
BOURSECU_TRANSACTION_TABS[0],
|
||||
)
|
||||
|
||||
const emptyText = computed(() => {
|
||||
if (!transactionsBoursecuMode.value) {
|
||||
return 'تراکنشی موجود نیست'
|
||||
}
|
||||
|
||||
if (activeTab.value.id === 'trades' && transactionTradeSide.value === 'sell') {
|
||||
return 'معامله فروشی برای شما به ثبت نرسیده است'
|
||||
}
|
||||
|
||||
return activeTab.value.emptyText
|
||||
})
|
||||
|
||||
async function initTable() {
|
||||
await reloadTransactionsTable()
|
||||
}
|
||||
|
||||
async function applyFilter() {
|
||||
await reloadTransactionsTable()
|
||||
showFilter.value = false
|
||||
}
|
||||
|
||||
async function setActiveTab(tabId: BoursecuTransactionTabId) {
|
||||
if (transactionActiveTabId.value === tabId) {
|
||||
return
|
||||
}
|
||||
|
||||
transactionActiveTabId.value = tabId
|
||||
await reloadTransactionsTable()
|
||||
}
|
||||
|
||||
async function setTradeSide(side: BoursecuTradeSide) {
|
||||
if (transactionTradeSide.value === side) {
|
||||
return
|
||||
}
|
||||
|
||||
transactionTradeSide.value = side
|
||||
if (getActiveBoursecuTab().showTradeToggle) {
|
||||
await reloadTransactionsTable()
|
||||
}
|
||||
}
|
||||
|
||||
async function applyInlineFilters() {
|
||||
await reloadTransactionsTable()
|
||||
}
|
||||
|
||||
function resolveObserverElement(el: Element | ComponentPublicInstance | null) {
|
||||
if (!el) return null
|
||||
if (el instanceof HTMLElement) return el
|
||||
const root = (el as ComponentPublicInstance).$el
|
||||
return root instanceof HTMLElement ? root : null
|
||||
}
|
||||
|
||||
function bindMobileObserver(el: Element | ComponentPublicInstance | null) {
|
||||
mobileObserverEl.value = resolveObserverElement(el)
|
||||
}
|
||||
|
||||
function bindDesktopObserver(el: Element | ComponentPublicInstance | null) {
|
||||
desktopObserverEl.value = resolveObserverElement(el)
|
||||
}
|
||||
|
||||
useIntersectionObserver(
|
||||
activeObserverEl,
|
||||
async ([entry]) => {
|
||||
if (entry?.isIntersecting) {
|
||||
await loadMoreTransactions()
|
||||
}
|
||||
},
|
||||
{
|
||||
root: null,
|
||||
rootMargin: '300px',
|
||||
threshold: 0.01,
|
||||
},
|
||||
)
|
||||
|
||||
onMounted(async () => {
|
||||
await initTransactionsPageData(isBoursecu)
|
||||
})
|
||||
|
||||
const context: TransactionsPageContext = {
|
||||
pagination: transactionPagination,
|
||||
items: transactionItems,
|
||||
tableOutput: transactionTableOutput,
|
||||
showFilter,
|
||||
dateFilter: transactionDateFilter,
|
||||
statusFilter: transactionStatusFilter,
|
||||
mobileObserverEl,
|
||||
desktopObserverEl,
|
||||
activeTabId: transactionActiveTabId,
|
||||
tradeSide: transactionTradeSide,
|
||||
activeTab,
|
||||
emptyText,
|
||||
resetPagination: resetTransactionsPagination,
|
||||
initTable,
|
||||
reloadTable: reloadTransactionsTable,
|
||||
applyFilter,
|
||||
setActiveTab,
|
||||
setTradeSide,
|
||||
applyInlineFilters,
|
||||
bindMobileObserver,
|
||||
bindDesktopObserver,
|
||||
}
|
||||
|
||||
provide(TRANSACTIONS_PAGE_KEY, context)
|
||||
return context
|
||||
}
|
||||
|
||||
export function useTransactionsPageContext() {
|
||||
const context = inject(TRANSACTIONS_PAGE_KEY)
|
||||
if (!context) {
|
||||
throw new Error('useTransactionsPageContext must be used within useTransactionsPageProvider')
|
||||
}
|
||||
return context
|
||||
}
|
||||
|
||||
/** @deprecated Use useTransactionsPageProvider in index + useTransactionsPageContext in views. */
|
||||
export function useTransactionsPage(options?: { boursecu?: boolean }) {
|
||||
return useTransactionsPageProvider(options)
|
||||
}
|
||||
@@ -0,0 +1,157 @@
|
||||
import { ref } from 'vue'
|
||||
import { getTransactionTable, getTransactionTable2 } from '@/api/transaction'
|
||||
import type { TableEngineInput, TableEngineOutput } from '@/models/table'
|
||||
import type { Transaction } from '@/models/transaction'
|
||||
import { loading, loadingMore } from '@/utils'
|
||||
import {
|
||||
BOURSECU_TRANSACTION_TABS,
|
||||
buildBoursecuTransactionPagination,
|
||||
type BoursecuTradeSide,
|
||||
type BoursecuTransactionTab,
|
||||
type BoursecuTransactionTabId,
|
||||
} from '../config/boursecuTabs'
|
||||
|
||||
/** Shared transactions datasets — mobile/desktop views read this module state only. */
|
||||
let initPromise: Promise<void> | null = null
|
||||
const initialized = ref(false)
|
||||
|
||||
export const transactionsBoursecuMode = ref(false)
|
||||
export const transactionPagination = ref<TableEngineInput>({ filters: [], limit: 7, page: 0 })
|
||||
export const transactionItems = ref<Transaction[]>([])
|
||||
export const transactionTableOutput = ref<TableEngineOutput<Transaction> | null>(null)
|
||||
export const transactionDateFilter = ref<string>()
|
||||
export const transactionStatusFilter = ref<string>()
|
||||
export const transactionActiveTabId = ref<BoursecuTransactionTabId>('trades')
|
||||
export const transactionTradeSide = ref<BoursecuTradeSide>('buy')
|
||||
|
||||
export function isTransactionsPageDataInitialized() {
|
||||
return initialized.value
|
||||
}
|
||||
|
||||
export function getActiveBoursecuTab(): BoursecuTransactionTab {
|
||||
return (
|
||||
BOURSECU_TRANSACTION_TABS.find((tab) => tab.id === transactionActiveTabId.value) ??
|
||||
BOURSECU_TRANSACTION_TABS[0]
|
||||
)
|
||||
}
|
||||
|
||||
export function resetTransactionsPagination() {
|
||||
if (transactionsBoursecuMode.value) {
|
||||
transactionPagination.value = buildBoursecuTransactionPagination(
|
||||
getActiveBoursecuTab(),
|
||||
transactionTradeSide.value,
|
||||
transactionDateFilter.value,
|
||||
transactionStatusFilter.value,
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
transactionPagination.value = { filters: [], limit: 7, page: 0 }
|
||||
if (transactionDateFilter.value) {
|
||||
transactionPagination.value.filters = [
|
||||
{
|
||||
field: 'created_at',
|
||||
cr: 'and',
|
||||
type: 'date',
|
||||
op: 'eq',
|
||||
value: transactionDateFilter.value,
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
async function fetchTransactionsPage(append: boolean) {
|
||||
const isFirstPage = transactionPagination.value.page === 0 && !append
|
||||
|
||||
if (isFirstPage) {
|
||||
loading.value = true
|
||||
} else {
|
||||
loadingMore.value = true
|
||||
}
|
||||
|
||||
try {
|
||||
const activeTab = getActiveBoursecuTab()
|
||||
const fetcher =
|
||||
transactionsBoursecuMode.value && activeTab.fetchMode === 'deposit'
|
||||
? getTransactionTable2
|
||||
: getTransactionTable
|
||||
|
||||
transactionTableOutput.value = await fetcher(transactionPagination.value)
|
||||
const rows = transactionTableOutput.value.rows ?? []
|
||||
|
||||
if (isFirstPage) {
|
||||
transactionItems.value = [...rows]
|
||||
} else {
|
||||
transactionItems.value.push(...rows)
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error fetching transactions:', error)
|
||||
} finally {
|
||||
loading.value = false
|
||||
loadingMore.value = false
|
||||
}
|
||||
}
|
||||
|
||||
export async function reloadTransactionsTable() {
|
||||
transactionPagination.value.page = 0
|
||||
transactionItems.value = []
|
||||
resetTransactionsPagination()
|
||||
await fetchTransactionsPage(false)
|
||||
}
|
||||
|
||||
export async function loadMoreTransactions() {
|
||||
if (
|
||||
!transactionTableOutput.value
|
||||
|| (transactionPagination.value.page + 1) * transactionPagination.value.limit
|
||||
>= transactionTableOutput.value.query.total
|
||||
) {
|
||||
return
|
||||
}
|
||||
|
||||
transactionPagination.value.page++
|
||||
await fetchTransactionsPage(true)
|
||||
}
|
||||
|
||||
export async function initTransactionsPageData(boursecu: boolean, force = false) {
|
||||
if (!force && initialized.value && transactionsBoursecuMode.value === boursecu) {
|
||||
return
|
||||
}
|
||||
|
||||
if (!force && initPromise) {
|
||||
return initPromise
|
||||
}
|
||||
|
||||
initPromise = (async () => {
|
||||
try {
|
||||
if (force) {
|
||||
resetTransactionsPageData()
|
||||
}
|
||||
|
||||
transactionsBoursecuMode.value = boursecu
|
||||
transactionPagination.value.page = 0
|
||||
transactionItems.value = []
|
||||
resetTransactionsPagination()
|
||||
await fetchTransactionsPage(false)
|
||||
initialized.value = true
|
||||
} catch (error) {
|
||||
console.error('Transactions page initialization error:', error)
|
||||
} finally {
|
||||
initPromise = null
|
||||
}
|
||||
})()
|
||||
|
||||
return initPromise
|
||||
}
|
||||
|
||||
export function resetTransactionsPageData() {
|
||||
initialized.value = false
|
||||
initPromise = null
|
||||
transactionsBoursecuMode.value = false
|
||||
transactionPagination.value = { filters: [], limit: 7, page: 0 }
|
||||
transactionItems.value = []
|
||||
transactionTableOutput.value = null
|
||||
transactionDateFilter.value = undefined
|
||||
transactionStatusFilter.value = undefined
|
||||
transactionActiveTabId.value = 'trades'
|
||||
transactionTradeSide.value = 'buy'
|
||||
}
|
||||
Reference in New Issue
Block a user