Files
panel/resources/js/views/dashboards/ecommerce/EcommerceGeneratedLeads.vue

334 lines
9.0 KiB
Vue
Raw Normal View History

2025-08-04 16:33:07 +03:30
<script setup>
import { useDisplay, useTheme } from 'vuetify'
import { hexToRgb } from '@layouts/utils'
2025-09-15 15:46:14 +03:30
import { computed, onMounted, onUnmounted, ref, nextTick } from 'vue'
2025-08-04 16:33:07 +03:30
const props = defineProps({
donutColors: {
type: Array,
default: () => []
},
progress: {
type: Number,
default: 75
}
})
const vuetifyTheme = useTheme()
const display = useDisplay()
const cardRef = ref(null)
2025-09-15 15:46:14 +03:30
const cardWidth = ref(300)
2025-08-04 16:33:07 +03:30
const updateCardWidth = () => {
if (cardRef.value) {
2025-09-15 15:46:14 +03:30
const rect = cardRef.value.getBoundingClientRect()
const newWidth = rect.width
if (newWidth > 0 && Math.abs(newWidth - cardWidth.value) > 5) {
cardWidth.value = newWidth
}
2025-08-04 16:33:07 +03:30
}
}
2025-09-15 15:46:14 +03:30
onMounted(async () => {
await nextTick()
2025-08-04 16:33:07 +03:30
updateCardWidth()
window.addEventListener('resize', updateCardWidth)
})
onUnmounted(() => {
window.removeEventListener('resize', updateCardWidth)
})
const chartSeries = computed(() => [props.progress, 100 - props.progress])
const processColors = (colors) => {
const currentTheme = vuetifyTheme.current.value.colors
return colors.map(color => {
if (typeof color === 'string' && currentTheme[color]) {
return currentTheme[color]
}
return color
})
}
const chartSize = computed(() => {
2025-09-15 15:46:14 +03:30
const width = cardWidth.value
const breakpoints = {
xs: display.xs.value,
sm: display.sm.value,
md: display.md.value
}
2025-08-04 16:33:07 +03:30
2025-09-15 15:46:14 +03:30
let size = 120
if (breakpoints.xs) {
size = Math.min(width * 0.35, 120)
} else if (breakpoints.sm) {
size = Math.min(width * 0.4, 150)
} else if (width <= 400) {
size = Math.min(width * 0.4, 160)
} else if (width <= 600) {
size = Math.min(width * 0.35, 200)
2025-08-04 16:33:07 +03:30
} else {
2025-09-15 15:46:14 +03:30
size = Math.min(width * 0.3, 240)
2025-08-04 16:33:07 +03:30
}
2025-09-15 15:46:14 +03:30
size = Math.max(size, 100)
2025-08-04 16:33:07 +03:30
return { width: size, height: size }
})
const textSizes = computed(() => {
2025-09-15 15:46:14 +03:30
const width = cardWidth.value
2025-08-04 16:33:07 +03:30
2025-09-15 15:46:14 +03:30
if (display.xs.value || width < 300) {
2025-08-04 16:33:07 +03:30
return {
title: 'text-body-2',
subtitle: 'text-caption',
mainNumber: 'text-h6',
2025-09-15 15:46:14 +03:30
percentage: 'text-caption'
2025-08-04 16:33:07 +03:30
}
2025-09-15 15:46:14 +03:30
} else if (display.sm.value || width < 450) {
2025-08-04 16:33:07 +03:30
return {
title: 'text-body-1',
subtitle: 'text-body-2',
mainNumber: 'text-h5',
percentage: 'text-body-2'
}
2025-09-15 15:46:14 +03:30
} else if (width < 600) {
2025-08-04 16:33:07 +03:30
return {
title: 'text-h6',
subtitle: 'text-body-2',
mainNumber: 'text-h4',
percentage: 'text-body-1'
}
} else {
return {
2025-09-15 15:46:14 +03:30
title: 'text-h5',
2025-08-04 16:33:07 +03:30
subtitle: 'text-body-1',
2025-09-15 15:46:14 +03:30
mainNumber: 'text-h3',
2025-08-04 16:33:07 +03:30
percentage: 'text-body-1'
}
}
})
const chartOptions = computed(() => {
const currentTheme = vuetifyTheme.current.value.colors
const variableTheme = vuetifyTheme.current.value.variables
2025-09-15 15:46:14 +03:30
const width = cardWidth.value
const chartSizeValue = chartSize.value.width
2025-08-04 16:33:07 +03:30
2025-09-15 15:46:14 +03:30
const defaultDonutColors = [currentTheme.success]
2025-08-04 16:33:07 +03:30
const usedDonutColors = props.donutColors.length
? processColors(props.donutColors)
: defaultDonutColors
const headingColor = `rgba(${hexToRgb(currentTheme['on-background'])},${variableTheme['high-emphasis-opacity']})`
const backgroundCircleColor = `rgba(${hexToRgb(usedDonutColors[0])}, 0.15)`
2025-09-15 15:46:14 +03:30
const valueFontSize = chartSizeValue < 120 ? '0.9rem' :
chartSizeValue < 160 ? '1.2rem' :
chartSizeValue < 200 ? '1.4rem' : '1.6rem'
2025-08-04 16:33:07 +03:30
2025-09-15 15:46:14 +03:30
const labelFontSize = chartSizeValue < 120 ? '0.7rem' :
chartSizeValue < 160 ? '0.8rem' :
chartSizeValue < 200 ? '0.9rem' : '1rem'
2025-08-04 16:33:07 +03:30
2025-09-15 15:46:14 +03:30
const donutSize = chartSizeValue < 130 ? '60%' :
chartSizeValue < 180 ? '65%' : '70%'
2025-08-04 16:33:07 +03:30
return {
chart: {
parentHeightOffset: 0,
type: 'donut',
2025-09-15 15:46:14 +03:30
sparkline: { enabled: false },
animations: {
enabled: true,
easing: 'easeinout',
speed: 800,
dynamicAnimation: {
enabled: true,
speed: 350
}
}
2025-08-04 16:33:07 +03:30
},
labels: ['Progress', 'Remaining'],
colors: [...usedDonutColors, backgroundCircleColor],
2025-09-15 15:46:14 +03:30
stroke: { width: 0 },
dataLabels: { enabled: false },
legend: { show: false },
2025-08-04 16:33:07 +03:30
tooltip: {
enabled: true,
2025-09-15 15:46:14 +03:30
theme: vuetifyTheme.current.value.dark ? 'dark' : 'light',
2025-08-04 16:33:07 +03:30
style: {
2025-09-15 15:46:14 +03:30
fontSize: chartSizeValue < 140 ? '11px' : '13px'
2025-08-04 16:33:07 +03:30
}
},
grid: {
2025-09-15 15:46:14 +03:30
padding: { top: 0, bottom: 0, right: 0, left: 0 }
2025-08-04 16:33:07 +03:30
},
states: {
hover: {
2025-09-15 15:46:14 +03:30
filter: { type: 'none' }
2025-08-04 16:33:07 +03:30
}
},
plotOptions: {
pie: {
startAngle: -90,
endAngle: 270,
donut: {
size: donutSize,
background: 'transparent',
labels: {
show: true,
value: {
show: true,
fontSize: valueFontSize,
fontFamily: 'Public Sans',
color: headingColor,
fontWeight: 600,
2025-09-15 15:46:14 +03:30
offsetY: chartSizeValue < 130 ? -1 : 0,
2025-08-04 16:33:07 +03:30
formatter(val) {
return `${Math.round(val)}%`
},
},
2025-09-15 15:46:14 +03:30
name: { show: false },
2025-08-04 16:33:07 +03:30
total: {
show: true,
showAlways: true,
color: usedDonutColors[0],
fontSize: labelFontSize,
label: 'Progress',
fontFamily: 'Public Sans',
fontWeight: 500,
formatter() {
return `${props.progress}%`
},
},
},
},
},
},
2025-09-15 15:46:14 +03:30
responsive: []
2025-08-04 16:33:07 +03:30
}
})
const cardBackgroundStyle = computed(() => {
2025-09-15 15:46:14 +03:30
const defaultDonutColors = [vuetifyTheme.current.value.colors.success]
2025-08-04 16:33:07 +03:30
const colors = props.donutColors.length ? processColors(props.donutColors) : defaultDonutColors
const createGradientColor = (color, opacity = 0.08) => {
if (color.includes('rgba')) {
return color.replace(/[\d\.]+\)$/g, `${opacity})`)
}
if (color.startsWith('#')) {
return `rgba(${hexToRgb(color)}, ${opacity})`
}
if (color.includes('rgb')) {
return color.replace('rgb', 'rgba').replace(')', `, ${opacity})`)
}
return `rgba(${hexToRgb(color)}, ${opacity})`
}
const gradientColors = colors.map((color, index) =>
2025-09-15 15:46:14 +03:30
createGradientColor(color, index === 0 ? 0.1 : 0.03)
2025-08-04 16:33:07 +03:30
)
return {
background: `linear-gradient(135deg,
${gradientColors[0]} 0%,
2025-09-15 15:46:14 +03:30
${gradientColors[1] || createGradientColor(colors[0], 0.05)} 50%,
${gradientColors[2] || createGradientColor(colors[0], 0.02)} 100%)`
2025-08-04 16:33:07 +03:30
}
})
const layoutClasses = computed(() => {
2025-09-15 15:46:14 +03:30
const width = cardWidth.value
2025-08-04 16:33:07 +03:30
return {
2025-09-15 15:46:14 +03:30
cardPadding: display.xs.value ? 'pa-3' :
display.sm.value ? 'pa-4' :
width < 400 ? 'pa-4' : 'pa-5',
textSpacing: display.xs.value ? 'me-2' :
display.sm.value ? 'me-3' :
width < 400 ? 'me-3' : 'me-4',
iconSize: display.xs.value ? 14 :
display.sm.value ? 16 :
width < 400 ? 16 : 18,
itemSpacing: display.xs.value ? 'mb-2' :
width < 400 ? 'mb-3' : 'mb-3'
2025-08-04 16:33:07 +03:30
}
})
</script>
<template>
<VCard
ref="cardRef"
class="overflow-visible"
:style="cardBackgroundStyle"
>
<VCardText
class="d-flex align-center justify-space-between"
2025-09-15 15:46:14 +03:30
:class="layoutClasses.cardPadding"
2025-08-04 16:33:07 +03:30
>
<div
2025-09-15 15:46:14 +03:30
class="d-flex flex-column flex-grow-1 flex-shrink-1"
:class="layoutClasses.textSpacing"
style="min-width: 0;"
2025-08-04 16:33:07 +03:30
>
2025-09-15 15:46:14 +03:30
<div :class="layoutClasses.itemSpacing">
2025-08-04 16:33:07 +03:30
<h5
2025-09-15 15:46:14 +03:30
class="text-no-wrap font-weight-medium text-truncate"
2025-08-04 16:33:07 +03:30
:class="textSizes.title"
style="line-height: 1.2;"
>
Generated Leads
</h5>
<div
2025-09-15 15:46:14 +03:30
class="text-medium-emphasis text-truncate"
2025-08-04 16:33:07 +03:30
:class="textSizes.subtitle"
style="line-height: 1.1;"
>
Monthly Report
</div>
</div>
<div>
<h3
class="font-weight-bold"
2025-09-15 15:46:14 +03:30
:class="[textSizes.mainNumber, layoutClasses.itemSpacing]"
2025-08-04 16:33:07 +03:30
style="line-height: 1.1;"
>
4,350
</h3>
<div class="d-flex align-center">
<VIcon
icon="tabler-chevron-up"
:color="(props.donutColors.length ? processColors(props.donutColors) : [vuetifyTheme.current.value.colors.success])[0]"
:size="layoutClasses.iconSize"
2025-09-15 15:46:14 +03:30
class="me-1 flex-shrink-0"
2025-08-04 16:33:07 +03:30
/>
<span
2025-09-15 15:46:14 +03:30
class="font-weight-medium text-no-wrap"
2025-08-04 16:33:07 +03:30
:class="textSizes.percentage"
:style="{ color: (props.donutColors.length ? processColors(props.donutColors) : [vuetifyTheme.current.value.colors.success])[0] }"
>
15.8%
</span>
</div>
</div>
</div>
<div
2025-09-15 15:46:14 +03:30
class="d-flex align-center justify-center flex-shrink-0"
2025-08-04 16:33:07 +03:30
>
<VueApexCharts
:options="chartOptions"
:series="chartSeries"
:height="chartSize.height"
:width="chartSize.width"
/>
</div>
</VCardText>
</VCard>
2025-09-15 15:46:14 +03:30
</template>