designtopack/public/site/plugins/analytics/src/components/AnalyticsDashboard.vue

394 lines
8.4 KiB
Vue
Raw Normal View History

<template>
<div class="k-analytics-dashboard">
<div class="k-analytics-filters">
<div class="k-analytics-date-filter">
<header class="k-field-header">
<label class="k-label k-field-label" title="Filtrer par dates">
<span class="k-label-text">Filtrer par dates </span>
</label>
</header>
<div class="k-date-inputs-wrapper">
<label>
Du
<input type="date" v-model="startDate" @change="fetchData" />
</label>
<label>
Au
<input type="date" v-model="endDate" @change="fetchData" />
</label>
</div>
</div>
<div class="k-analytics-user-filter">
<k-multiselect-field
:options="userOptions"
:value="selectedEmails"
label="Filtrer par utilisateur(s)"
search="true"
name="user"
@input="onUserSelectionChange"
/>
</div>
</div>
<div v-if="!hasData" class="k-analytics-empty">
<p>Aucune donnée à afficher</p>
</div>
<template v-else>
<div class="k-analytics-grid">
<div class="k-analytics-card">
<h3>Sessions uniques</h3>
<div class="k-analytics-metric">{{ data.uniqueSessions }}</div>
</div>
<div class="k-analytics-card">
<h3>Pages vues</h3>
<div class="k-analytics-metric">{{ data.totalVisits }}</div>
</div>
<div class="k-analytics-card">
<h3>Pages / session</h3>
<div class="k-analytics-metric">{{ pagesPerSession }}</div>
</div>
</div>
<div class="k-analytics-chart-container">
<h3>Visites par jour</h3>
<canvas ref="chartCanvas"></canvas>
</div>
<div
class="k-analytics-card"
v-if="data.visitsByPage && Object.keys(data.visitsByPage).length"
>
<h3>Pages les plus visitées</h3>
<ul class="k-analytics-list">
<li v-for="(count, page) in data.visitsByPage" :key="page">
<span class="k-analytics-list-label">{{ page }}</span>
<span class="k-analytics-list-value">{{ count }}</span>
</li>
</ul>
</div>
</template>
</div>
</template>
<script>
import {
Chart,
LineController,
LineElement,
PointElement,
LinearScale,
CategoryScale,
Filler,
Tooltip,
} from 'chart.js';
Chart.register(
LineController,
LineElement,
PointElement,
LinearScale,
CategoryScale,
Filler,
Tooltip
);
export default {
props: {
analyticsData: {
type: Object,
default: () => ({}),
},
},
data() {
return {
startDate: '',
endDate: '',
data: this.analyticsData || {},
chart: null,
users: [],
selectedEmails: [],
};
},
computed: {
hasData() {
return this.data && this.data.totalVisits > 0;
},
pagesPerSession() {
if (!this.data?.uniqueSessions) return '0';
return (this.data.totalVisits / this.data.uniqueSessions).toFixed(1);
},
userOptions() {
return this.users.map((u) => ({ value: u.email, text: u.label }));
},
},
watch: {
analyticsData(newVal) {
this.data = newVal || {};
this.renderChart();
},
},
mounted() {
this.setDefaultDates();
this.fetchData();
},
beforeUnmount() {
this.destroyChart();
},
methods: {
setDefaultDates() {
const now = new Date();
const thirtyDaysAgo = new Date(now);
thirtyDaysAgo.setDate(thirtyDaysAgo.getDate() - 30);
this.endDate = now.toISOString().split('T')[0];
this.startDate = thirtyDaysAgo.toISOString().split('T')[0];
},
onUserSelectionChange(emails) {
this.selectedEmails = emails;
this.fetchData();
},
async fetchData() {
const params = new URLSearchParams();
if (this.startDate) params.set('startDate', this.startDate);
if (this.endDate) params.set('endDate', this.endDate);
if (this.selectedEmails.length) {
params.set('emails', this.selectedEmails.join(','));
}
try {
const response = await fetch(`/analytics-data.json?${params}`);
const json = await response.json();
console.log(json.data);
if (json.status === 'success') {
this.data = json.data;
}
if (json.users && !this.users.length) {
this.users = json.users;
}
this.renderChart();
} catch (e) {
console.error('Analytics fetch error:', e);
this.renderChart();
}
},
destroyChart() {
if (this.chart) {
this.chart.destroy();
this.chart = null;
}
},
formatDateFR(isoDate) {
const [y, m, d] = isoDate.split('-');
return `${d}/${m}/${y}`;
},
renderChart() {
this.destroyChart();
const canvas = this.$refs.chartCanvas;
if (!canvas || !this.data?.visitsByDay) return;
const labels = Object.keys(this.data.visitsByDay).map((d) =>
this.formatDateFR(d)
);
const values = Object.values(this.data.visitsByDay);
if (!labels.length) return;
const maxValue = Math.max(...values);
this.chart = new Chart(canvas, {
type: 'line',
data: {
labels,
datasets: [
{
label: 'Visites',
data: values,
borderColor: '#4271ae',
backgroundColor: 'rgba(66, 113, 174, 0.1)',
fill: true,
tension: 0.3,
pointRadius: 3,
pointHoverRadius: 5,
},
],
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
tooltip: {
mode: 'index',
intersect: false,
},
},
scales: {
y: {
beginAtZero: true,
max: maxValue + 3,
ticks: { precision: 0 },
},
},
},
});
},
},
};
</script>
<style>
.k-analytics-dashboard {
padding: 1.5rem 0;
}
.k-analytics-filters {
display: flex;
gap: 1rem;
margin-bottom: 1.5rem;
}
.k-analytics-filters label {
display: flex;
align-items: center;
gap: 0.5rem;
font-size: 0.875rem;
color: var(--color-text-light);
}
.k-date-inputs-wrapper {
display: flex;
column-gap: 1rem;
}
.k-analytics-filters input[type='date'] {
padding: 0.375rem 0.5rem;
border: 1px solid var(--color-border);
border-radius: var(--rounded);
font-size: 0.875rem;
background: var(--color-background);
}
.k-analytics-grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 1.5rem;
margin-bottom: 1.5rem;
}
.k-analytics-user-filter {
display: flex;
align-items: center;
gap: 0.5rem;
font-size: 0.875rem;
color: var(--color-text-light);
margin-left: 2rem;
}
.k-field-name-user {
min-width: 15rem;
}
.k-analytics-card {
background: var(--color-background);
border-radius: var(--rounded);
padding: 1.5rem;
box-shadow: var(--shadow);
}
.k-analytics-card h3 {
font-size: 0.75rem;
font-weight: 600;
color: var(--color-text-light);
margin: 0 0 0.5rem 0;
text-transform: uppercase;
letter-spacing: 0.5px;
}
.k-analytics-metric {
font-size: 2.5rem;
font-weight: 700;
color: var(--color-text);
line-height: 1;
}
.k-analytics-chart-container {
background: var(--color-background);
border-radius: var(--rounded);
padding: 1.5rem;
margin-bottom: 1.5rem;
box-shadow: var(--shadow);
}
.k-analytics-chart-container h3 {
font-size: 0.875rem;
font-weight: 600;
color: var(--color-text);
margin: 0 0 1rem 0;
}
.k-analytics-chart-container canvas {
max-height: 300px;
}
.k-analytics-empty {
background: var(--color-background);
border-radius: var(--rounded);
padding: 3rem;
text-align: center;
box-shadow: var(--shadow);
}
.k-analytics-empty p {
margin: 0;
color: var(--color-text-light);
}
.k-analytics-list {
list-style: none;
margin: 0;
padding: 0;
}
.k-analytics-list li {
display: flex;
justify-content: space-between;
padding: 0.375rem 0;
border-bottom: 1px solid var(--color-border);
font-size: 0.875rem;
}
.k-analytics-list li:last-child {
border-bottom: none;
}
.k-analytics-list-label {
color: var(--color-text);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
margin-right: 1rem;
}
.k-analytics-list-value {
font-weight: 600;
color: var(--color-text);
flex-shrink: 0;
}
</style>