Files
panel/resources/js/components/ProjectActivityBarChart.vue

508 lines
11 KiB
Vue
Raw Normal View History

2025-08-04 16:33:07 +03:30
<template>
<VCard
class="overflow-visible position-relative"
:style="cardBackgroundStyle"
elevation="8"
>
<!-- Decorative elements -->
<div class="position-absolute decorative-circle-1"></div>
<div class="position-absolute decorative-circle-2"></div>
<div class="position-absolute decorative-dots"></div>
<VCardItem class="pb-4">
<div class="d-flex align-center justify-space-between">
<div>
<VCardTitle class="text-h5 font-weight-bold mb-1">
<VIcon icon="mdi-chart-line" class="me-2 text-primary"></VIcon>
Active Projects Progress
</VCardTitle>
<VCardSubtitle class="text-subtitle-1 opacity-80">This Week Overview</VCardSubtitle>
</div>
<VChip
:color="changePercent >= 0 ? 'success' : 'error'"
variant="tonal"
size="small"
class="font-weight-bold"
>
{{ formattedChange }}
</VChip>
</div>
</VCardItem>
<VCardText class="pb-2">
<!-- Progress Summary -->
<div class="progress-summary-card mb-6">
<div class="d-flex align-center justify-center mb-2">
<div class="progress-circle">
<h2 class="text-h2 font-weight-bold text-primary mb-0">{{ totalProgress }}%</h2>
</div>
</div>
<p class="text-center text-subtitle-2 mb-0 opacity-80">Average Progress</p>
</div>
<VueApexCharts
:options="chartOptions"
:series="series"
:height="330"
width="100%"
class="enhanced-chart"
/>
<!-- Project Stats -->
<!-- <div class="project-stats mt-4">
<div class="d-flex justify-space-between align-center mb-2">
<span class="text-subtitle-2 opacity-80">Projects Status</span>
<VChip size="x-small" color="primary" variant="text">
{{ values.length }} Total
</VChip>
</div>
<div class="d-flex flex-wrap gap-2">
<VChip
v-for="(value, index) in values"
:key="index"
:color="getProjectStatusColor(value)"
variant="tonal"
size="small"
class="font-weight-medium"
>
{{ labels[index] }}: {{ value }}%
</VChip>
</div>
</div> -->
</VCardText>
</VCard>
</template>
<script setup>
import { computed } from 'vue'
import { useTheme } from 'vuetify'
import { hexToRgb } from '@layouts/utils'
import VueApexCharts from 'vue3-apexcharts'
const vuetifyTheme = useTheme()
const labels = [
'Project Alpha',
'Project Beta',
'Project Gamma',
'Project Delta',
'Project Epsilon',
]
const values = [45, 70, 30, 85, 55]
const series = [
{
name: 'Progress Percentage',
data: values,
},
]
const totalProgress = computed(() =>
Math.round(values.reduce((sum, v) => sum + v, 0) / values.length)
)
const changePercent = computed(() => {
const last = values[values.length - 1]
const prev = values[values.length - 2] ?? last
return prev === 0 ? 0 : ((last - prev) / prev) * 100
})
const formattedChange = computed(() => {
const sign = changePercent.value >= 0 ? '+' : ''
return `${sign}${changePercent.value.toFixed(1)}%`
})
const getProjectStatusColor = (value) => {
if (value >= 80) return 'success'
if (value >= 60) return 'warning'
if (value >= 40) return 'info'
return 'error'
}
const cardBackgroundStyle = computed(() => {
const currentTheme = vuetifyTheme.current.value.colors
const primaryColor = currentTheme.primary
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 gradientColor1 = createGradientColor(primaryColor, 0.15)
const gradientColor2 = createGradientColor(primaryColor, 0.02)
const gradientColor3 = createGradientColor(primaryColor, 0.08)
return {
background: `linear-gradient(135deg,
${gradientColor1} 0%,
${gradientColor2} 25%,
${gradientColor3} 50%,
${gradientColor2} 75%,
${gradientColor1} 100%)`,
backdropFilter: 'blur(10px)',
border: `1px solid ${createGradientColor(primaryColor, 0.2)}`
}
})
const chartOptions = computed(() => ({
chart: {
type: 'bar',
parentHeightOffset: 0,
toolbar: { show: false },
background: 'transparent',
animations: {
enabled: true,
easing: 'easeinout',
speed: 800,
animateGradually: {
enabled: true,
delay: 150
},
dynamicAnimation: {
enabled: true,
speed: 350
}
}
},
plotOptions: {
bar: {
horizontal: false,
columnWidth: "35%",
borderRadius: 20,
borderRadiusApplication: 'end',
borderRadiusWhenStacked: 'last',
dataLabels: {
position: 'top'
}
},
},
colors: ['rgba(var(--v-theme-primary), 1)'],
grid: {
show: true,
borderColor: 'rgba(var(--v-theme-on-surface), 0.08)',
strokeDashArray: 3,
position: 'back',
xaxis: {
lines: {
show: false
}
},
yaxis: {
lines: {
show: true
}
},
row: {
colors: undefined,
opacity: 0.5
},
column: {
colors: undefined,
opacity: 0.5
},
padding: {
top: 0,
right: 10,
bottom: 0,
left: 10
}
},
dataLabels: {
enabled: true,
offsetY: -20,
style: {
fontSize: '12px',
fontWeight: 'bold',
colors: ['rgba(var(--v-theme-primary), 1)']
},
formatter: function (val) {
return val + '%'
}
},
legend: { show: false },
tooltip: {
enabled: true,
theme: 'dark',
style: {
fontSize: '12px',
fontFamily: 'Arial, sans-serif'
},
y: {
formatter: function (val) {
return val + '%'
}
},
marker: {
show: true
}
},
xaxis: {
categories: labels,
axisBorder: {
show: true,
color: 'rgba(var(--v-theme-on-surface), 0.12)'
},
axisTicks: {
show: true,
color: 'rgba(var(--v-theme-on-surface), 0.12)'
},
labels: {
show: true,
rotate: -45,
rotateAlways: false,
hideOverlappingLabels: true,
showDuplicates: false,
trim: false,
style: {
fontSize: '12px',
fontWeight: '500',
colors: 'rgba(var(--v-theme-on-surface), 0.7)',
fontFamily: 'Arial, sans-serif',
},
},
},
yaxis: {
labels: {
show: true,
style: {
fontSize: '11px',
colors: 'rgba(var(--v-theme-on-surface), 0.6)',
fontFamily: 'Arial, sans-serif',
},
formatter: function (val) {
return val + '%'
}
},
min: 0,
max: 100
},
responsive: [
{
breakpoint: 1441,
options: {
plotOptions: {
bar: {
columnWidth: '45%',
borderRadius: 18,
},
},
},
},
{
breakpoint: 1368,
options: {
plotOptions: {
bar: {
columnWidth: '40%',
borderRadius: 16,
}
},
},
},
{
breakpoint: 1264,
options: {
plotOptions: {
bar: {
columnWidth: '35%',
borderRadius: 20,
},
},
},
},
{
breakpoint: 960,
options: {
plotOptions: {
bar: {
columnWidth: '30%',
borderRadius: 18,
},
},
},
},
{
breakpoint: 883,
options: {
plotOptions: {
bar: {
columnWidth: '35%',
borderRadius: 16,
}
}
},
},
{
breakpoint: 768,
options: {
plotOptions: {
bar: {
columnWidth: '40%',
borderRadius: 14,
}
},
dataLabels: {
offsetY: -15
}
},
},
{
breakpoint: 600,
options: {
plotOptions: {
bar: {
columnWidth: '45%',
borderRadius: 16,
},
},
dataLabels: {
enabled: false
}
},
},
{
breakpoint: 479,
options: {
plotOptions: {
bar: {
borderRadius: 12,
},
},
grid: {
padding: { right: 5, left: 5, bottom: 10 },
},
dataLabels: {
enabled: false
}
},
},
{
breakpoint: 400,
options: {
plotOptions: {
bar: {
borderRadius: 10,
}
},
xaxis: {
labels: {
rotate: -90
}
}
},
},
],
}))
</script>
<style scoped>
.decorative-circle-1 {
top: -20px;
right: -20px;
width: 80px;
height: 80px;
background: radial-gradient(circle, rgba(var(--v-theme-primary), 0.1) 0%, transparent 70%);
border-radius: 50%;
z-index: 0;
}
.decorative-circle-2 {
bottom: -30px;
left: -30px;
width: 120px;
height: 120px;
background: radial-gradient(circle, rgba(var(--v-theme-secondary), 0.08) 0%, transparent 70%);
border-radius: 50%;
z-index: 0;
}
.decorative-dots {
top: 50%;
right: 20px;
width: 6px;
height: 40px;
background-image: radial-gradient(circle, rgba(var(--v-theme-primary), 0.3) 1px, transparent 1px);
background-size: 6px 8px;
z-index: 0;
}
.progress-summary-card {
background: rgba(var(--v-theme-surface), 0.6);
border-radius: 16px;
padding: 20px;
text-align: center;
backdrop-filter: blur(10px);
border: 1px solid rgba(var(--v-theme-primary), 0.1);
position: relative;
overflow: hidden;
}
.progress-summary-card::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
height: 2px;
background: linear-gradient(90deg,
rgba(var(--v-theme-primary), 0.3) 0%,
rgba(var(--v-theme-primary), 0.8) 50%,
rgba(var(--v-theme-primary), 0.3) 100%);
}
.progress-circle {
position: relative;
display: inline-block;
padding: 10px;
}
.progress-circle::after {
content: '';
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 80px;
height: 80px;
border: 3px solid rgba(var(--v-theme-primary), 0.2);
border-radius: 50%;
z-index: -1;
}
.project-stats {
background: rgba(var(--v-theme-surface), 0.4);
border-radius: 12px;
padding: 16px;
backdrop-filter: blur(5px);
border: 1px solid rgba(var(--v-theme-on-surface), 0.08);
}
.enhanced-chart {
position: relative;
z-index: 1;
}
/* Hover effects */
.v-card:hover {
transform: translateY(-2px);
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
}
.v-card:hover .decorative-circle-1 {
transform: scale(1.1);
transition: transform 0.3s ease;
}
.v-card:hover .decorative-circle-2 {
transform: scale(1.05);
transition: transform 0.3s ease;
}
</style>