Teren-app/resources/js/Components/ui/chart/ChartTooltipContent.vue

86 lines
3.4 KiB
Vue

<script setup>
// Advanced tooltip component (original implementation) inspired by external patterns.
import { computed } from 'vue';
const props = defineProps({
hideLabel: { type: Boolean, default: false },
hideIndicator: { type: Boolean, default: false },
indicator: { type: String, default: 'dot' }, // 'dot' | 'line' | 'dashed'
nameKey: { type: String, required: false },
labelKey: { type: String, required: false },
labelFormatter: { type: Function, required: false },
payload: { type: Object, required: false, default: () => ({}) },
config: { type: Object, required: false, default: () => ({}) },
class: { type: [String, Array, Object], required: false },
color: { type: String, required: false },
x: { type: [Number, Date, String], required: false },
});
// Build array of entries referencing config for label & color
const entries = computed(() => {
return Object.entries(props.payload)
.map(([key, value]) => {
const seriesKey = props.nameKey || key;
const itemConfig = props.config[seriesKey] || props.config[key] || {};
const indicatorColor = itemConfig.color || props.color;
return { key, value, itemConfig, indicatorColor };
})
.filter(e => e.itemConfig && (e.itemConfig.label || e.value !== undefined));
});
const singleSeries = computed(() => entries.value.length === 1 && props.indicator !== 'dot');
const formattedLabel = computed(() => {
if (props.hideLabel) return null;
if (props.labelFormatter && props.x !== undefined) {
return props.labelFormatter(props.x);
}
if (props.labelKey) {
const cfg = props.config[props.labelKey];
return cfg?.label || props.payload[props.labelKey];
}
return props.x instanceof Date ? props.x.toLocaleDateString() : props.x;
});
function formatValue(v) {
if (v == null) return '';
if (typeof v === 'number') return v.toLocaleString();
return v;
}
</script>
<template>
<div :class="['border border-border/50 bg-background min-w-32 rounded-lg px-2.5 py-1.5 text-xs shadow-xl', props.class]">
<div v-if="!singleSeries && formattedLabel" class="font-medium mb-1">{{ formattedLabel }}</div>
<div class="grid gap-1.5">
<div
v-for="{ key, value, itemConfig, indicatorColor } in entries"
:key="key"
class="flex w-full flex-wrap items-stretch gap-2"
:class="indicator === 'dot' ? 'items-center' : 'items-start'"
>
<!-- Indicator -->
<template v-if="!hideIndicator">
<div
:class="[
'shrink-0 rounded-[2px] border-border',
indicator === 'dot' && 'h-2.5 w-2.5',
indicator === 'line' && 'w-1 h-4',
indicator === 'dashed' && 'w-0 h-4 border-[1.5px] border-dashed bg-transparent',
singleSeries && indicator === 'dashed' && 'my-0.5'
]"
:style="{ '--color-bg': indicatorColor, '--color-border': indicatorColor }"
/>
</template>
<div :class="['flex flex-1 justify-between leading-none', singleSeries ? 'items-end' : 'items-center']">
<div class="grid gap-1.5">
<div v-if="singleSeries && formattedLabel" class="font-medium">{{ formattedLabel }}</div>
<span class="text-muted-foreground">{{ itemConfig.label || formatValue(value) }}</span>
</div>
<span v-if="value !== undefined" class="font-mono font-medium tabular-nums">{{ formatValue(value) }}</span>
</div>
</div>
</div>
</div>
</template>