Files
boursecu_user/src/views/base/home/composables/usePriceChart.ts
T
safavimohadeseh78andCursor a5e458ed25 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>
2026-06-14 10:08:46 +03:30

226 lines
6.2 KiB
TypeScript

import { ref, watch } from 'vue'
import { getAllPrices } from '@/api/home'
import type { Product } from '@/models/products'
import { getProductsCatalog } from '@/composables/useProductsCatalog'
import { getHomeLayoutId } from '../layouts/registry'
import { getProductBuyPrice, resolvePriceOffsetRatio } from '@/utils/productPrice'
import { isKatodMesGroup, isMaftolMesGroup } from '@/utils/productDealMethod'
import { getTradeBuyPrice, lastPrice } from '@/utils/price'
export type ChartMetalType = 'katod' | 'maftol'
type ChartPoint = [number, number]
/** Module-level chart state — one dataset shared by mobile and desktop PriceChart instances. */
const priceChartSeries = ref<{ name: string; data: ChartPoint[] }[]>([
{ name: 'قیمت', data: [] },
])
const rawChartPoints = ref<ChartPoint[]>([])
const chartSelection = ref('DAILY')
const chartLoading = ref(false)
const selectedChartMetal = ref<ChartMetalType>('katod')
const maftolChartRatio = ref(0)
let chartFetchPromise: Promise<void> | null = null
let chartInitialized = false
let maftolOffsetPromise: Promise<void> | null = null
let maftolOffsetLoaded = false
function findKatodProduct(products: Product[]) {
return (
products.find((product) => isKatodMesGroup(product.group_type))
?? products.find((product) => (product.title ?? '').includes('کاتد'))
)
}
function findMaftolProduct(products: Product[]) {
return (
products.find((product) => isMaftolMesGroup(product.group_type))
?? products.find((product) => (product.title ?? '').includes('مفتول'))
)
}
function applyChartRatio(basePrice: number, ratio: number) {
if (!ratio) {
return basePrice
}
return Math.round(basePrice + basePrice * ratio)
}
function deriveMaftolChartRatio(maftolProduct?: Product, katodProduct?: Product) {
const offsetRatio = resolvePriceOffsetRatio(maftolProduct?.price_offset_percent)
if (offsetRatio) {
return offsetRatio
}
if (!maftolProduct) {
return 0
}
const basePrice = getTradeBuyPrice(lastPrice.value)
const katodPrice = katodProduct ? getProductBuyPrice(katodProduct) : basePrice
const maftolPrice = getProductBuyPrice(maftolProduct)
const referencePrice = katodPrice || basePrice
if (!referencePrice || !maftolPrice || referencePrice === maftolPrice) {
return 0
}
return (maftolPrice - referencePrice) / referencePrice
}
function normalizeChartTimestamp(timestamp: number) {
return timestamp < 1_000_000_000_000 ? timestamp * 1000 : timestamp
}
function resolvePricesMap(response: unknown): Record<string, Array<{ timestamp: number; value: number }>> | null {
if (!response || typeof response !== 'object') {
return null
}
const body = response as Record<string, unknown>
const nested = body.data
if (nested && typeof nested === 'object' && 'prices' in nested) {
return (nested as { prices: Record<string, Array<{ timestamp: number; value: number }>> }).prices
}
if ('prices' in body) {
return body.prices as Record<string, Array<{ timestamp: number; value: number }>>
}
return null
}
function buildChartPoints(items: Array<{ timestamp: number; value: number }>): ChartPoint[] {
return items.map((item) => [
normalizeChartTimestamp(item.timestamp),
item.value - 1,
])
}
function syncChartSeries() {
const data =
getHomeLayoutId() === 'boursecu' && selectedChartMetal.value === 'maftol'
? rawChartPoints.value.map(
([timestamp, value]) =>
[timestamp, applyChartRatio(value, maftolChartRatio.value)] as ChartPoint
)
: rawChartPoints.value
priceChartSeries.value[0].data = data
}
watch([selectedChartMetal, maftolChartRatio], () => {
syncChartSeries()
})
async function fetchPrices(force = false) {
if (!force && chartInitialized && rawChartPoints.value.length > 0) {
syncChartSeries()
return
}
if (!force && chartFetchPromise) {
return chartFetchPromise
}
chartFetchPromise = (async () => {
try {
chartLoading.value = true
const response = await getAllPrices()
const data = resolvePricesMap(response)
const selectedData = data?.[chartSelection.value] ?? []
rawChartPoints.value = buildChartPoints(selectedData)
syncChartSeries()
chartInitialized = true
} catch (error) {
console.error('Error fetching prices:', error)
} finally {
chartLoading.value = false
chartFetchPromise = null
}
})()
return chartFetchPromise
}
async function loadMaftolOffset(force = false) {
if (getHomeLayoutId() !== 'boursecu') {
maftolChartRatio.value = 0
syncChartSeries()
return
}
if (!force && maftolOffsetLoaded) {
return
}
if (!force && maftolOffsetPromise) {
return maftolOffsetPromise
}
maftolOffsetPromise = (async () => {
try {
const products = await getProductsCatalog(force)
const katodProduct = findKatodProduct(products)
const maftolProduct = findMaftolProduct(products)
maftolChartRatio.value = deriveMaftolChartRatio(maftolProduct, katodProduct)
syncChartSeries()
maftolOffsetLoaded = true
} catch (error) {
console.error('Error loading maftol chart ratio:', error)
maftolChartRatio.value = 0
syncChartSeries()
} finally {
maftolOffsetPromise = null
}
})()
return maftolOffsetPromise
}
function updateChartData(timeline: string) {
chartSelection.value = timeline
void fetchPrices(true)
}
function setChartMetal(metal: ChartMetalType) {
selectedChartMetal.value = metal
}
export function usePriceChart() {
return {
priceChartSeries,
chartSelection,
chartLoading,
selectedChartMetal,
maftolChartRatio,
updateChartData,
fetchPrices,
loadMaftolOffset,
setChartMetal,
}
}
export function fetchHomeChartPrices(force = false) {
return fetchPrices(force)
}
export function loadHomeMaftolOffset(force = false) {
return loadMaftolOffset(force)
}
export function resetHomePriceChart() {
priceChartSeries.value = [{ name: 'قیمت', data: [] }]
rawChartPoints.value = []
chartSelection.value = 'DAILY'
selectedChartMetal.value = 'katod'
maftolChartRatio.value = 0
chartInitialized = false
chartFetchPromise = null
maftolOffsetLoaded = false
maftolOffsetPromise = null
}