Files
boursecu_user/src/views/base/home/composables/usePriceChart.ts
T

226 lines
6.3 KiB
TypeScript
Raw Normal View History

import { computed, ref, watch } from 'vue'
2026-05-14 13:44:43 +03:30
import { getAllPrices } from '@/api/home'
import { getProductsCatalog, getProductsCatalogRef } from '@/composables/useProductsCatalog'
2026-07-21 15:39:03 +02:00
import { getTradeBuyPrice, getTradeSellPrice } from '@/utils/price'
2026-06-23 16:02:41 +03:30
export type ChartMetalType = 'katod' | 'maftol' | 'shamsh'
export type ChartTimeline = 'DAILY' | 'WEEKLY' | 'MONTHLY'
type ChartPoint = [number, number]
type PricesMap = Record<string, Array<{ timestamp: number; value: number }>>
2026-07-21 15:39:03 +02:00
/**
* مارک‌آپ ثابت روی قیمت بورس کالا برای نمودار:
* کاتد +۳٪ ، مفتول +۶٪ ، شمش +۱۲٪
*/
export const CHART_METAL_MARKUP_RATIO: Record<ChartMetalType, number> = {
katod: 0.03,
maftol: 0.06,
shamsh: 0.12,
}
/** 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 cachedPrices = ref<PricesMap | null>(null)
const chartSelection = ref<ChartTimeline>('DAILY')
const chartLoading = ref(false)
const selectedChartMetal = ref<ChartMetalType>('katod')
const catalogProducts = getProductsCatalogRef()
2026-07-21 15:39:03 +02:00
const maftolChartRatio = computed(() => CHART_METAL_MARKUP_RATIO.maftol)
const shamshChartRatio = computed(() => CHART_METAL_MARKUP_RATIO.shamsh)
2026-07-21 15:39:03 +02:00
const selectedMetalChartRatio = computed(
() => CHART_METAL_MARKUP_RATIO[selectedChartMetal.value],
)
let chartFetchPromise: Promise<void> | null = null
let chartInitialized = false
2026-06-23 16:02:41 +03:30
let metalOffsetPromise: Promise<void> | null = null
let metalOffsetLoaded = false
function applyChartRatio(basePrice: number, ratio: number) {
if (!ratio) {
return basePrice
}
return Math.round(basePrice + basePrice * ratio)
}
2026-07-21 15:39:03 +02:00
/** بالاترین/پایین‌ترین امروز — همان مارک‌آپ ثابت فلز روی قیمت بورس کالا */
export function resolveMetalDayRangePrices(options: {
price: { up?: { buy?: number; sell?: number }; buy?: number; sell?: number; price_last?: number } | null | undefined
metal: ChartMetalType
2026-06-23 16:02:41 +03:30
metalRatio: number
}) {
const baseHigh = options.price?.up?.buy ?? getTradeBuyPrice(options.price as Parameters<typeof getTradeBuyPrice>[0])
const baseLow = options.price?.up?.sell ?? getTradeSellPrice(options.price as Parameters<typeof getTradeSellPrice>[0])
2026-07-21 15:39:03 +02:00
const ratio = options.metalRatio || CHART_METAL_MARKUP_RATIO[options.metal]
return {
2026-07-21 15:39:03 +02:00
high: applyChartRatio(baseHigh, ratio),
low: applyChartRatio(baseLow, ratio),
}
}
function normalizeChartTimestamp(timestamp: number) {
return timestamp < 1_000_000_000_000 ? timestamp * 1000 : timestamp
}
function resolvePricesMap(response: unknown): PricesMap | 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: PricesMap }).prices
}
if ('prices' in body) {
return body.prices as PricesMap
}
return null
}
function buildChartPoints(items: Array<{ timestamp: number; value: number }>): ChartPoint[] {
return items.map((item) => [
normalizeChartTimestamp(item.timestamp),
item.value - 1,
])
}
function syncChartSeries() {
2026-06-23 16:02:41 +03:30
const ratio = selectedMetalChartRatio.value
// New array so Vue invalidates parent computeds (nested .data mutation did not).
priceChartSeries.value = [
{
name: 'قیمت',
data: rawChartPoints.value.map(
([timestamp, value]) => [timestamp, applyChartRatio(value, ratio)] as ChartPoint,
),
},
]
}
function applyTimelineFromCache(timeline: ChartTimeline = chartSelection.value) {
const selectedData = cachedPrices.value?.[timeline] ?? []
rawChartPoints.value = buildChartPoints(selectedData)
syncChartSeries()
}
2026-06-23 16:02:41 +03:30
watch([selectedChartMetal, maftolChartRatio, shamshChartRatio], () => {
syncChartSeries()
})
async function fetchPrices(force = false) {
if (!force && chartInitialized && cachedPrices.value) {
applyTimelineFromCache()
return
}
if (!force && chartFetchPromise) {
return chartFetchPromise
}
chartFetchPromise = (async () => {
try {
chartLoading.value = true
const response = await getAllPrices()
const data = resolvePricesMap(response)
cachedPrices.value = data
applyTimelineFromCache()
chartInitialized = Boolean(data)
} catch (error) {
console.error('Error fetching prices:', error)
} finally {
chartLoading.value = false
chartFetchPromise = null
}
})()
return chartFetchPromise
}
2026-06-23 16:02:41 +03:30
async function loadMetalChartOffsets(force = false) {
if (!force && metalOffsetLoaded && catalogProducts.value.length > 0) {
return
}
2026-06-23 16:02:41 +03:30
if (!force && metalOffsetPromise) {
return metalOffsetPromise
}
2026-06-23 16:02:41 +03:30
metalOffsetPromise = (async () => {
try {
await getProductsCatalog(force)
syncChartSeries()
2026-06-23 16:02:41 +03:30
metalOffsetLoaded = true
} catch (error) {
2026-06-23 16:02:41 +03:30
console.error('Error loading boursecu chart metal ratios:', error)
syncChartSeries()
} finally {
2026-06-23 16:02:41 +03:30
metalOffsetPromise = null
}
})()
2026-06-23 16:02:41 +03:30
return metalOffsetPromise
}
function updateChartData(timeline: string) {
chartSelection.value = timeline as ChartTimeline
if (cachedPrices.value) {
applyTimelineFromCache(chartSelection.value)
return
}
void fetchPrices(true)
}
function setChartMetal(metal: ChartMetalType) {
selectedChartMetal.value = metal
2026-07-21 15:39:03 +02:00
syncChartSeries()
}
2026-05-14 13:44:43 +03:30
export function usePriceChart() {
return {
priceChartSeries,
chartSelection,
chartLoading,
selectedChartMetal,
maftolChartRatio,
2026-06-23 16:02:41 +03:30
shamshChartRatio,
selectedMetalChartRatio,
updateChartData,
fetchPrices,
2026-06-23 16:02:41 +03:30
loadMaftolOffset: loadMetalChartOffsets,
loadMetalChartOffsets,
setChartMetal,
}
}
2026-05-14 13:44:43 +03:30
export function fetchHomeChartPrices(force = false) {
return fetchPrices(force)
}
2026-05-14 13:44:43 +03:30
export function loadHomeMaftolOffset(force = false) {
2026-06-23 16:02:41 +03:30
return loadMetalChartOffsets(force)
}
2026-05-14 13:44:43 +03:30
export function resetHomePriceChart() {
priceChartSeries.value = [{ name: 'قیمت', data: [] }]
rawChartPoints.value = []
cachedPrices.value = null
chartSelection.value = 'DAILY'
selectedChartMetal.value = 'katod'
chartInitialized = false
chartFetchPromise = null
2026-06-23 16:02:41 +03:30
metalOffsetLoaded = false
metalOffsetPromise = null
}