frontend/src/components/ChartBlock.vue
2025-10-06 21:27:09 +03:00

205 lines
4.5 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<script setup>
import { ref, watch } from 'vue'
import {
Chart as ChartJS,
Title,
Tooltip,
Legend,
LineElement,
PointElement,
CategoryScale,
TimeScale,
LinearScale,
} from 'chart.js'
import { Line } from 'vue-chartjs'
import 'chartjs-adapter-date-fns'
ChartJS.register(
Title,
Tooltip,
Legend,
LineElement,
PointElement,
CategoryScale,
TimeScale,
LinearScale,
)
const props = defineProps({
chartsData: {
type: [Object, null],
required: true,
default: null,
},
})
const originColors = {
surugaya: '#2d3081',
mandarake: '#924646',
}
const chartData = ref({
datasets: [],
})
const showPlaceholder = ref(false)
const placeholderMessage = ref('')
function transformToChartJSSeries(payload) {
const datasets = []
for (const origin of payload.origins) {
if (!origin.Prices || origin.Prices.length === 0) continue
const data = origin.Prices.filter((p) => p.value != null)
.map((p) => {
let y = p.value
if (typeof y === 'string') {
y = parseFloat(y)
}
const x =
typeof p.created_at === 'number' ? p.created_at * 1000 : new Date(p.created_at).getTime()
return {
x,
y: typeof y === 'number' && !isNaN(y) ? y : null,
}
})
.filter((p) => p.y !== null)
.sort((a, b) => a.x - b.x)
if (data.length === 0) continue
const color = originColors[origin.origin] || '#000000'
datasets.push({
label: origin.origin,
data,
borderColor: color,
backgroundColor: color + '20',
fill: false,
pointRadius: 4,
pointHoverRadius: 6,
type: 'line',
})
}
return datasets
}
const chartOptions = {
responsive: true,
maintainAspectRatio: false,
animation: false,
plugins: {
legend: { display: true },
tooltip: {
mode: 'index',
intersect: false,
callbacks: {
title: (context) => {
const date = new Date(context[0].parsed.x)
return date.toLocaleString('ru-RU', {
year: 'numeric',
month: '2-digit',
day: '2-digit',
hour: '2-digit',
minute: '2-digit',
})
},
},
},
},
scales: {
x: {
type: 'time',
time: {
tooltipFormat: 'dd.MM.yyyy HH:mm',
unit: 'day',
displayFormats: { day: 'dd MMM' },
},
title: { display: true, text: 'Date' },
},
y: {
title: { display: true, text: 'Price' },
ticks: { precision: 0 },
},
},
elements: {
line: { borderWidth: 2, tension: 0, spanGaps: true },
point: { radius: 4, hoverRadius: 6 },
},
interaction: {
mode: 'nearest',
axis: 'x',
intersect: false,
},
}
watch(
() => props.chartsData,
(newData) => {
// Сброс состояния
showPlaceholder.value = false
placeholderMessage.value = ''
// Случай 1: null или undefined
if (newData === null || newData === undefined) {
showPlaceholder.value = true
placeholderMessage.value = 'No prices found'
chartData.value = { datasets: [] }
return
}
// Случай 2: явная ошибка в объекте
if (typeof newData === 'object' && newData.error) {
showPlaceholder.value = true
placeholderMessage.value = newData.error
chartData.value = { datasets: [] }
return
}
// Случай 3: нормальный объект — пробуем преобразовать
let datasets = []
if (newData.origins && Array.isArray(newData.origins)) {
datasets = transformToChartJSSeries(newData)
}
// 🔥 Ключевое изменение: проверяем, есть ли хоть один датасет
if (datasets.length === 0) {
showPlaceholder.value = true
placeholderMessage.value = 'No prices found'
chartData.value = { datasets: [] }
} else {
showPlaceholder.value = false
chartData.value = { datasets }
}
},
{ immediate: true }
)
</script>
<template>
<div class="chart-container" style="height: 100%; width: 100%">
<div v-if="showPlaceholder" class="error-placeholder">
{{ placeholderMessage }}
</div>
<Line v-else :data="chartData" :options="chartOptions" style="height: 100%; width: 100%" />
</div>
</template>
<style scoped>
.chart-container {
position: relative;
min-width: 80%;
}
.error-placeholder {
display: flex;
align-items: center;
justify-content: center;
height: 100%;
width: 100%;
color: #666;
font-size: 1.2rem;
text-align: center;
}
</style>