88 lines
3.0 KiB
Vue
88 lines
3.0 KiB
Vue
<template>
|
|
<section class="feature-page__section">
|
|
<div class="feature-grid feature-grid--cards">
|
|
<StatCard v-for="card in cards" :key="card.label" :label="card.label" :value="card.value" :hint="card.hint" />
|
|
</div>
|
|
|
|
<div class="feature-grid feature-grid--dashboard">
|
|
<article class="feature-panel app-panel">
|
|
<h3>Recent tasks</h3>
|
|
<div v-for="task in tasks" :key="task.id" class="row-card">
|
|
<div>
|
|
<strong>{{ task.name }}</strong>
|
|
<div class="muted">{{ task.processedFiles }} / {{ task.totalFiles }} files</div>
|
|
</div>
|
|
<StatusBadge :label="task.status" :tone="mapTaskStatusTone(task.status)" />
|
|
</div>
|
|
</article>
|
|
|
|
<article class="feature-panel app-panel">
|
|
<h3>Processing chart</h3>
|
|
<div class="muted">Chart total {{ chartTotal }}</div>
|
|
<div ref="chartRef" class="echart-panel" aria-label="Processing chart"></div>
|
|
</article>
|
|
|
|
<article class="feature-panel app-panel">
|
|
<h3>Quick entries</h3>
|
|
<div v-for="entry in quickEntries" :key="entry.title" class="row-card row-card--stacked">
|
|
<strong>{{ entry.title }}</strong>
|
|
<div>{{ entry.value }}</div>
|
|
<div class="muted">{{ entry.hint }}</div>
|
|
<RouterLink :to="entry.to" class="quick-link">{{ entry.actionLabel }}</RouterLink>
|
|
</div>
|
|
</article>
|
|
</div>
|
|
</section>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import { computed, onBeforeUnmount, onMounted, ref, watch } from 'vue'
|
|
import { RouterLink } from 'vue-router'
|
|
import * as echarts from 'echarts'
|
|
import StatCard from '../common/StatCard.vue'
|
|
import StatusBadge from '../common/StatusBadge.vue'
|
|
import type { DashboardQuickEntry } from '../../types/dashboard'
|
|
import type { TaskSummary } from '../../types/task'
|
|
import { mapTaskStatusTone } from '../../utils/task'
|
|
|
|
const props = defineProps<{
|
|
cards: Array<{ label: string; value: string; hint: string }>
|
|
tasks: TaskSummary[]
|
|
quickEntries: DashboardQuickEntry[]
|
|
chartBars: Array<{ label: string; value: number; raw: number }>
|
|
}>()
|
|
|
|
const chartRef = ref<HTMLDivElement | null>(null)
|
|
let chartInstance: echarts.ECharts | null = null
|
|
const chartTotal = computed(() => props.chartBars.reduce((sum, item) => sum + item.raw, 0))
|
|
|
|
function renderChart() {
|
|
if (!chartRef.value) return
|
|
if (typeof HTMLCanvasElement === 'undefined') return
|
|
if (!HTMLCanvasElement.prototype.getContext) return
|
|
if (chartRef.value.clientWidth === 0 || chartRef.value.clientHeight === 0) return
|
|
|
|
chartInstance?.dispose()
|
|
|
|
chartInstance = echarts.init(chartRef.value)
|
|
chartInstance.setOption({
|
|
tooltip: { trigger: 'item' },
|
|
series: [
|
|
{
|
|
type: 'pie',
|
|
radius: ['45%', '70%'],
|
|
label: { color: '#e8eef8' },
|
|
data: props.chartBars.map((bar) => ({ name: bar.label, value: bar.raw }))
|
|
}
|
|
]
|
|
})
|
|
}
|
|
|
|
onMounted(renderChart)
|
|
watch(() => props.chartBars, renderChart, { deep: true })
|
|
onBeforeUnmount(() => {
|
|
chartInstance?.dispose()
|
|
chartInstance = null
|
|
})
|
|
</script>
|