442 lines
12 KiB
Vue
442 lines
12 KiB
Vue
|
|
<template>
|
||
|
|
<VCard class="h-100 overflow-visible position-relative" :style="cardBackgroundStyle">
|
||
|
|
<VCardText class="pa-6 h-100 d-flex flex-column">
|
||
|
|
<div class="d-flex justify-center align-center mb-4 flex-wrap gap-2">
|
||
|
|
<VChip color="primary" variant="tonal" size="small">
|
||
|
|
<VIcon icon="tabler-calendar" size="14" class="me-1" />
|
||
|
|
{{ getCurrentDate() }}
|
||
|
|
</VChip>
|
||
|
|
<VChip color="secondary" variant="outlined" size="small" v-if="chartName">
|
||
|
|
<VIcon icon="tabler-chart-line" size="14" class="me-1" />
|
||
|
|
{{ chartName }}
|
||
|
|
</VChip>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div class="text-center mb-6">
|
||
|
|
<div class="progress-circle-container mb-4">
|
||
|
|
<VProgressCircular
|
||
|
|
:model-value="completionPercent"
|
||
|
|
:size="120"
|
||
|
|
:width="8"
|
||
|
|
color="success"
|
||
|
|
class="progress-main"
|
||
|
|
>
|
||
|
|
<div class="text-center">
|
||
|
|
<h3 class="text-h3 font-weight-bold">{{ completionPercent }}%</h3>
|
||
|
|
<span class="text-caption text-medium-emphasis">Completed</span>
|
||
|
|
</div>
|
||
|
|
</VProgressCircular>
|
||
|
|
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div class="mb-4">
|
||
|
|
<VBtn
|
||
|
|
block
|
||
|
|
color="primary"
|
||
|
|
variant="elevated"
|
||
|
|
size="large"
|
||
|
|
class="btn-analyze"
|
||
|
|
:loading="loading"
|
||
|
|
@click="onAnalyzeClick"
|
||
|
|
>
|
||
|
|
<VIcon
|
||
|
|
:icon="loading ? 'tabler-loader-2' : 'tabler-chart-bar'"
|
||
|
|
class="me-2"
|
||
|
|
:class="{ 'rotating': loading }"
|
||
|
|
/>
|
||
|
|
{{ loading ? "Analyzing..." : "Analyze" }}
|
||
|
|
</VBtn>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div class="flex-grow-1 d-flex flex-column" style="min-height: 0; height: 250px;">
|
||
|
|
<VCard
|
||
|
|
variant="tonal"
|
||
|
|
color="surface"
|
||
|
|
class="analysis-container d-flex flex-column h-100"
|
||
|
|
>
|
||
|
|
<VCardText class="pa-4 d-flex flex-column h-100" style="min-height: 0;">
|
||
|
|
<div class="d-flex align-center mb-3">
|
||
|
|
<VIcon icon="tabler-report-analytics" class="me-2" />
|
||
|
|
<h6 class="text-h6">Analysis Results {{ chartName ? `- ${chartName}` : '' }}</h6>
|
||
|
|
</div>
|
||
|
|
<div
|
||
|
|
class="analysis-content custom-scrollbar"
|
||
|
|
style="overflow-y: auto; height: 1px; flex: 1; color: white;"
|
||
|
|
v-html="analysisResult"
|
||
|
|
></div>
|
||
|
|
</VCardText>
|
||
|
|
</VCard>
|
||
|
|
</div>
|
||
|
|
</VCardText>
|
||
|
|
</VCard>
|
||
|
|
</template>
|
||
|
|
|
||
|
|
<script setup>
|
||
|
|
import { ref, computed } from 'vue'
|
||
|
|
import { useTheme } from 'vuetify'
|
||
|
|
import { hexToRgb } from '@layouts/utils'
|
||
|
|
|
||
|
|
// Define props
|
||
|
|
const props = defineProps({
|
||
|
|
chartName: {
|
||
|
|
type: String,
|
||
|
|
default: '',
|
||
|
|
required: false
|
||
|
|
}
|
||
|
|
})
|
||
|
|
|
||
|
|
const vuetifyTheme = useTheme()
|
||
|
|
const loading = ref(false)
|
||
|
|
const completionPercent = ref(25)
|
||
|
|
const analysisResult = ref(`
|
||
|
|
<div class="text-center text-medium-emphasis py-8">
|
||
|
|
<div class="mb-3">
|
||
|
|
<svg width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5">
|
||
|
|
<path d="M3 3v18h18"/>
|
||
|
|
<path d="M18.7 8l-5.1 5.2-2.8-2.7L7 14.3"/>
|
||
|
|
</svg>
|
||
|
|
</div>
|
||
|
|
<p class="mb-0">Click 'Analyze ${props.chartName || 'Projects'}' to generate detailed insights</p>
|
||
|
|
</div>
|
||
|
|
`)
|
||
|
|
|
||
|
|
const getCurrentDate = () => {
|
||
|
|
const today = new Date()
|
||
|
|
const options = {
|
||
|
|
weekday: 'long',
|
||
|
|
year: 'numeric',
|
||
|
|
month: 'long',
|
||
|
|
day: 'numeric'
|
||
|
|
}
|
||
|
|
return today.toLocaleDateString('en-US', options)
|
||
|
|
}
|
||
|
|
|
||
|
|
const cardBackgroundStyle = computed(() => {
|
||
|
|
const currentTheme = vuetifyTheme.current.value.colors
|
||
|
|
const createGradientColor = (color, opacity = 0.08) =>
|
||
|
|
color.startsWith('#') ? `rgba(${hexToRgb(color)}, ${opacity})` : `rgba(${hexToRgb(color)}, ${opacity})`
|
||
|
|
|
||
|
|
return {
|
||
|
|
background: `linear-gradient(135deg,
|
||
|
|
${createGradientColor(currentTheme.primary, 0.12)} 0%,
|
||
|
|
${createGradientColor(currentTheme.success, 0.06)} 50%,
|
||
|
|
${createGradientColor(currentTheme.primary, 0.04)} 100%)`
|
||
|
|
}
|
||
|
|
})
|
||
|
|
|
||
|
|
function onAnalyzeClick() {
|
||
|
|
loading.value = true
|
||
|
|
analysisResult.value = `
|
||
|
|
<div class="text-center py-4">
|
||
|
|
<div class="mb-3">
|
||
|
|
<svg class="rotating" width="32" height="32" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||
|
|
<path d="M21 12a9 9 0 11-6.219-8.56"/>
|
||
|
|
</svg>
|
||
|
|
</div>
|
||
|
|
<p class="text-medium-emphasis">Analyzing ${props.chartName || 'project'} data...</p>
|
||
|
|
</div>
|
||
|
|
`
|
||
|
|
setTimeout(() => {
|
||
|
|
loading.value = false
|
||
|
|
analysisResult.value = `
|
||
|
|
<div class="analysis-results">
|
||
|
|
<div class="result-section mb-4">
|
||
|
|
<h6 class="text-subtitle-1 mb-3 d-flex align-center">
|
||
|
|
<span class="result-icon success me-2">📊</span>
|
||
|
|
${props.chartName || 'Project'} Overview
|
||
|
|
</h6>
|
||
|
|
<div class="result-grid">
|
||
|
|
<div class="result-item">
|
||
|
|
<span class="result-label">Avg. Completion Time</span>
|
||
|
|
<span class="result-value text-primary">14 days</span>
|
||
|
|
</div>
|
||
|
|
<div class="result-item">
|
||
|
|
<span class="result-label">Success Rate</span>
|
||
|
|
<span class="result-value text-success">85%</span>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
<div class="result-section mb-4">
|
||
|
|
<h6 class="text-subtitle-1 mb-3 d-flex align-center">
|
||
|
|
<span class="result-icon warning me-2">📈</span>
|
||
|
|
Current Status
|
||
|
|
</h6>
|
||
|
|
<div class="status-list">
|
||
|
|
<div class="status-item">
|
||
|
|
<div class="status-dot success"></div>
|
||
|
|
<span>Completed ${props.chartName ? props.chartName.toLowerCase() : 'projects'}: <strong>1</strong></span>
|
||
|
|
</div>
|
||
|
|
<div class="status-item">
|
||
|
|
<div class="status-dot warning"></div>
|
||
|
|
<span>Pending ${props.chartName ? props.chartName.toLowerCase() : 'projects'}: <strong>2</strong></span>
|
||
|
|
</div>
|
||
|
|
<div class="status-item">
|
||
|
|
<div class="status-dot primary"></div>
|
||
|
|
<span>Average Progress: <strong>40%</strong></span>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
<div class="result-section mb-4">
|
||
|
|
<h6 class="text-subtitle-1 mb-3 d-flex align-center">
|
||
|
|
<span class="result-icon info me-2">💡</span>
|
||
|
|
Recommendations
|
||
|
|
</h6>
|
||
|
|
<ul class="recommendation-list">
|
||
|
|
<li>Focus on completing pending ${props.chartName ? props.chartName.toLowerCase() : 'projects'}</li>
|
||
|
|
<li>Optimize resource allocation for better efficiency</li>
|
||
|
|
<li>Set up automated progress tracking</li>
|
||
|
|
<li>Review ${props.chartName ? props.chartName.toLowerCase() : 'project'} timelines and deadlines</li>
|
||
|
|
<li>Implement better team communication</li>
|
||
|
|
<li>Consider hiring additional resources</li>
|
||
|
|
</ul>
|
||
|
|
</div>
|
||
|
|
<div class="result-section mb-4">
|
||
|
|
<h6 class="text-subtitle-1 mb-3 d-flex align-center">
|
||
|
|
<span class="result-icon me-2">📅</span>
|
||
|
|
Detailed Timeline
|
||
|
|
</h6>
|
||
|
|
<div class="timeline-items">
|
||
|
|
<div class="timeline-item">
|
||
|
|
<div class="timeline-dot"></div>
|
||
|
|
<div class="timeline-content">
|
||
|
|
<strong>Alpha ${props.chartName || 'Project'}</strong> - Started 10 days ago, 80% complete
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
<div class="timeline-item">
|
||
|
|
<div class="timeline-dot"></div>
|
||
|
|
<div class="timeline-content">
|
||
|
|
<strong>Beta ${props.chartName || 'Project'}</strong> - Started 5 days ago, 30% complete
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
<div class="timeline-item">
|
||
|
|
<div class="timeline-dot"></div>
|
||
|
|
<div class="timeline-content">
|
||
|
|
<strong>Gamma ${props.chartName || 'Project'}</strong> - Starting next week, 0% complete
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
<div class="result-section">
|
||
|
|
<h6 class="text-subtitle-1 mb-3 d-flex align-center">
|
||
|
|
<span class="result-icon me-2">⚡</span>
|
||
|
|
Performance Metrics
|
||
|
|
</h6>
|
||
|
|
<div class="metrics-grid">
|
||
|
|
<div class="metric-item">
|
||
|
|
<span class="metric-label">Tasks per Day</span>
|
||
|
|
<span class="metric-value">3.2</span>
|
||
|
|
</div>
|
||
|
|
<div class="metric-item">
|
||
|
|
<span class="metric-label">Efficiency Score</span>
|
||
|
|
<span class="metric-value">92%</span>
|
||
|
|
</div>
|
||
|
|
<div class="metric-item">
|
||
|
|
<span class="metric-label">Team Velocity</span>
|
||
|
|
<span class="metric-value">High</span>
|
||
|
|
</div>
|
||
|
|
<div class="metric-item">
|
||
|
|
<span class="metric-label">Quality Score</span>
|
||
|
|
<span class="metric-value">8.7/10</span>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
`
|
||
|
|
}, 2000)
|
||
|
|
}
|
||
|
|
</script>
|
||
|
|
|
||
|
|
<style scoped>
|
||
|
|
.progress-circle-container {
|
||
|
|
position: relative;
|
||
|
|
display: inline-block;
|
||
|
|
}
|
||
|
|
|
||
|
|
.floating-stats {
|
||
|
|
position: absolute;
|
||
|
|
top: -10px;
|
||
|
|
left: 50%;
|
||
|
|
transform: translateX(-50%);
|
||
|
|
display: flex;
|
||
|
|
gap: 8px;
|
||
|
|
}
|
||
|
|
|
||
|
|
.stat-chip {
|
||
|
|
backdrop-filter: blur(10px);
|
||
|
|
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
|
||
|
|
}
|
||
|
|
|
||
|
|
.btn-analyze {
|
||
|
|
box-shadow: 0 4px 12px rgba(var(--v-theme-primary), 0.3);
|
||
|
|
transition: all 0.3s ease;
|
||
|
|
}
|
||
|
|
|
||
|
|
.btn-analyze:hover {
|
||
|
|
transform: translateY(-2px);
|
||
|
|
box-shadow: 0 6px 16px rgba(var(--v-theme-primary), 0.4);
|
||
|
|
}
|
||
|
|
|
||
|
|
.analysis-container {
|
||
|
|
backdrop-filter: blur(10px);
|
||
|
|
border: 1px solid rgba(var(--v-border-color), 0.1);
|
||
|
|
}
|
||
|
|
|
||
|
|
.analysis-content {
|
||
|
|
scrollbar-width: thin;
|
||
|
|
scrollbar-color: rgba(var(--v-theme-primary), 0.3) transparent;
|
||
|
|
color: white !important;
|
||
|
|
}
|
||
|
|
|
||
|
|
.analysis-content * {
|
||
|
|
color: white !important;
|
||
|
|
}
|
||
|
|
|
||
|
|
.analysis-content .text-medium-emphasis {
|
||
|
|
color: rgba(255, 255, 255, 0.7) !important;
|
||
|
|
}
|
||
|
|
|
||
|
|
.analysis-content .text-primary {
|
||
|
|
color: rgb(var(--v-theme-primary)) !important;
|
||
|
|
}
|
||
|
|
|
||
|
|
.analysis-content .text-success {
|
||
|
|
color: rgb(var(--v-theme-success)) !important;
|
||
|
|
}
|
||
|
|
|
||
|
|
.custom-scrollbar::-webkit-scrollbar {
|
||
|
|
width: 6px;
|
||
|
|
}
|
||
|
|
|
||
|
|
.custom-scrollbar::-webkit-scrollbar-track {
|
||
|
|
background: transparent;
|
||
|
|
}
|
||
|
|
|
||
|
|
.custom-scrollbar::-webkit-scrollbar-thumb {
|
||
|
|
background-color: rgba(var(--v-theme-primary), 0.3);
|
||
|
|
border-radius: 4px;
|
||
|
|
}
|
||
|
|
|
||
|
|
.rotating {
|
||
|
|
animation: spin 1s linear infinite;
|
||
|
|
}
|
||
|
|
|
||
|
|
@keyframes spin {
|
||
|
|
from { transform: rotate(0deg); }
|
||
|
|
to { transform: rotate(360deg); }
|
||
|
|
}
|
||
|
|
|
||
|
|
.result-section {
|
||
|
|
background: rgba(var(--v-theme-surface), 0.5);
|
||
|
|
border-radius: 8px;
|
||
|
|
padding: 16px;
|
||
|
|
border: 1px solid rgba(var(--v-border-color), 0.1);
|
||
|
|
}
|
||
|
|
|
||
|
|
.result-grid {
|
||
|
|
display: grid;
|
||
|
|
grid-template-columns: 1fr 1fr;
|
||
|
|
gap: 12px;
|
||
|
|
}
|
||
|
|
|
||
|
|
.result-item {
|
||
|
|
display: flex;
|
||
|
|
flex-direction: column;
|
||
|
|
gap: 4px;
|
||
|
|
}
|
||
|
|
|
||
|
|
.result-label {
|
||
|
|
font-size: 0.75rem;
|
||
|
|
opacity: 0.7;
|
||
|
|
}
|
||
|
|
|
||
|
|
.result-value {
|
||
|
|
font-weight: 600;
|
||
|
|
font-size: 0.875rem;
|
||
|
|
}
|
||
|
|
|
||
|
|
.status-list {
|
||
|
|
display: flex;
|
||
|
|
flex-direction: column;
|
||
|
|
gap: 8px;
|
||
|
|
}
|
||
|
|
|
||
|
|
.status-item {
|
||
|
|
display: flex;
|
||
|
|
align-items: center;
|
||
|
|
gap: 8px;
|
||
|
|
font-size: 0.875rem;
|
||
|
|
}
|
||
|
|
|
||
|
|
.status-dot {
|
||
|
|
width: 8px;
|
||
|
|
height: 8px;
|
||
|
|
border-radius: 50%;
|
||
|
|
}
|
||
|
|
|
||
|
|
.status-dot.success { background-color: rgb(var(--v-theme-success)); }
|
||
|
|
.status-dot.warning { background-color: rgb(var(--v-theme-warning)); }
|
||
|
|
.status-dot.primary { background-color: rgb(var(--v-theme-primary)); }
|
||
|
|
|
||
|
|
.result-icon {
|
||
|
|
font-size: 1.2rem;
|
||
|
|
}
|
||
|
|
|
||
|
|
.recommendation-list {
|
||
|
|
margin: 0;
|
||
|
|
padding-left: 16px;
|
||
|
|
}
|
||
|
|
|
||
|
|
.recommendation-list li {
|
||
|
|
margin-bottom: 6px;
|
||
|
|
font-size: 0.875rem;
|
||
|
|
opacity: 0.8;
|
||
|
|
}
|
||
|
|
|
||
|
|
.timeline-items {
|
||
|
|
display: flex;
|
||
|
|
flex-direction: column;
|
||
|
|
gap: 12px;
|
||
|
|
}
|
||
|
|
|
||
|
|
.timeline-item {
|
||
|
|
display: flex;
|
||
|
|
align-items: center;
|
||
|
|
gap: 12px;
|
||
|
|
}
|
||
|
|
|
||
|
|
.timeline-dot {
|
||
|
|
width: 8px;
|
||
|
|
height: 8px;
|
||
|
|
border-radius: 50%;
|
||
|
|
background-color: rgb(var(--v-theme-primary));
|
||
|
|
flex-shrink: 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
.timeline-content {
|
||
|
|
font-size: 0.875rem;
|
||
|
|
}
|
||
|
|
|
||
|
|
.metrics-grid {
|
||
|
|
display: grid;
|
||
|
|
grid-template-columns: 1fr 1fr;
|
||
|
|
gap: 12px;
|
||
|
|
}
|
||
|
|
|
||
|
|
.metric-item {
|
||
|
|
display: flex;
|
||
|
|
flex-direction: column;
|
||
|
|
gap: 4px;
|
||
|
|
}
|
||
|
|
|
||
|
|
.metric-label {
|
||
|
|
font-size: 0.75rem;
|
||
|
|
opacity: 0.7;
|
||
|
|
}
|
||
|
|
|
||
|
|
.metric-value {
|
||
|
|
font-weight: 600;
|
||
|
|
font-size: 0.875rem;
|
||
|
|
color: rgb(var(--v-theme-primary));
|
||
|
|
}
|
||
|
|
</style>
|