feat: filtre utilisateurs analytics, améliorations dashboard + autres modifs
All checks were successful
Deploy Preprod / Build and Deploy to Preprod (push) Successful in 34s
All checks were successful
Deploy Preprod / Build and Deploy to Preprod (push) Successful in 34s
- Multiselect Kirby pour filtrer par utilisateur(s) - Données de test alignées sur les vrais comptes - Suppression bloc utilisateurs les plus actifs - Route get-data supporte le filtre emails - Améliorations UI filtres (layout dates + users) - Autres modifs : menu, router, dialog, deploy workflow Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
8a73da920f
commit
de104dc7dd
9 changed files with 243 additions and 42 deletions
0
public/site/cache/index.html
vendored
0
public/site/cache/index.html
vendored
|
|
@ -60,6 +60,16 @@ $menu = [
|
|||
],
|
||||
'-',
|
||||
'-',
|
||||
'analytics' => [
|
||||
'label' => 'Analytics',
|
||||
'icon' => 'chart',
|
||||
'link' => 'pages/analytics',
|
||||
'current' => function (string $current): bool {
|
||||
$path = Kirby\Cms\App::instance()->path();
|
||||
return Str::contains($path, 'pages/analytics');
|
||||
}
|
||||
],
|
||||
'-',
|
||||
'users',
|
||||
'system'
|
||||
];
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
.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:.5rem;font-size:.875rem;color:var(--color-text-light)}.k-analytics-filters input[type=date]{padding:.375rem .5rem;border:1px solid var(--color-border);border-radius:var(--rounded);font-size:.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-grid--2col{grid-template-columns:repeat(2,1fr)}.k-analytics-card{background:var(--color-background);border-radius:var(--rounded);padding:1.5rem;box-shadow:var(--shadow)}.k-analytics-card h3{font-size:.75rem;font-weight:600;color:var(--color-text-light);margin:0 0 .5rem;text-transform:uppercase;letter-spacing:.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:.875rem;font-weight:600;color:var(--color-text);margin:0 0 1rem}.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:.375rem 0;border-bottom:1px solid var(--color-border);font-size:.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}
|
||||
.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:.5rem;font-size:.875rem;color:var(--color-text-light)}.k-date-inputs-wrapper{display:flex;column-gap:1rem}.k-analytics-filters input[type=date]{padding:.375rem .5rem;border:1px solid var(--color-border);border-radius:var(--rounded);font-size:.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:.5rem;font-size:.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:.75rem;font-weight:600;color:var(--color-text-light);margin:0 0 .5rem;text-transform:uppercase;letter-spacing:.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:.875rem;font-weight:600;color:var(--color-text);margin:0 0 1rem}.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:.375rem 0;border-bottom:1px solid var(--color-border);font-size:.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}
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
|
|
@ -27,7 +27,6 @@ return [
|
|||
$request = $kirby->request();
|
||||
$filters = [];
|
||||
|
||||
// Récupérer les filtres depuis query params
|
||||
if ($startDate = $request->query()->get('startDate')) {
|
||||
$filters['startDate'] = $startDate;
|
||||
}
|
||||
|
|
@ -40,11 +39,38 @@ return [
|
|||
$filters['project'] = $project;
|
||||
}
|
||||
|
||||
if (!empty($_GET['emails'])) {
|
||||
$filters['emails'] = explode(',', $_GET['emails']);
|
||||
}
|
||||
|
||||
$data = $analyticsPage->getAnalyticsData($filters);
|
||||
|
||||
$users = [];
|
||||
foreach ($kirby->users() as $u) {
|
||||
$email = (string) $u->email();
|
||||
$name = $u->name()->isNotEmpty() ? (string) $u->name() : $email;
|
||||
$label = $name;
|
||||
|
||||
$clientField = $u->content()->get('client');
|
||||
if ($clientField && $clientField->isNotEmpty()) {
|
||||
$clientPage = $clientField->toPage();
|
||||
if ($clientPage) {
|
||||
$label .= ' (' . $clientPage->title() . ')';
|
||||
}
|
||||
}
|
||||
|
||||
$users[] = [
|
||||
'email' => $email,
|
||||
'label' => $label,
|
||||
];
|
||||
}
|
||||
|
||||
usort($users, fn($a, $b) => strcasecmp($a['label'], $b['label']));
|
||||
|
||||
return [
|
||||
'status' => 'success',
|
||||
'data' => $data
|
||||
'data' => $data,
|
||||
'users' => $users
|
||||
];
|
||||
}
|
||||
];
|
||||
|
|
|
|||
|
|
@ -1,14 +1,33 @@
|
|||
<template>
|
||||
<div class="k-analytics-dashboard">
|
||||
<div class="k-analytics-filters">
|
||||
<label>
|
||||
Du
|
||||
<input type="date" v-model="startDate" @change="fetchData" />
|
||||
</label>
|
||||
<label>
|
||||
Au
|
||||
<input type="date" v-model="endDate" @change="fetchData" />
|
||||
</label>
|
||||
<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">
|
||||
|
|
@ -36,31 +55,17 @@
|
|||
<canvas ref="chartCanvas"></canvas>
|
||||
</div>
|
||||
|
||||
<div class="k-analytics-grid k-analytics-grid--2col">
|
||||
<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>
|
||||
<div
|
||||
class="k-analytics-card"
|
||||
v-if="data.visitsByUser && Object.keys(data.visitsByUser).length"
|
||||
>
|
||||
<h3>Utilisateurs les plus actifs</h3>
|
||||
<ul class="k-analytics-list">
|
||||
<li v-for="(count, user) in data.visitsByUser" :key="user">
|
||||
<span class="k-analytics-list-label">{{ user }}</span>
|
||||
<span class="k-analytics-list-value">{{ count }}</span>
|
||||
</li>
|
||||
</ul>
|
||||
</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>
|
||||
|
|
@ -102,6 +107,8 @@ export default {
|
|||
endDate: '',
|
||||
data: this.analyticsData || {},
|
||||
chart: null,
|
||||
users: [],
|
||||
selectedEmails: [],
|
||||
};
|
||||
},
|
||||
|
||||
|
|
@ -113,6 +120,9 @@ export default {
|
|||
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: {
|
||||
|
|
@ -141,19 +151,33 @@ export default {
|
|||
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);
|
||||
|
|
@ -247,6 +271,11 @@ export default {
|
|||
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);
|
||||
|
|
@ -262,8 +291,17 @@ export default {
|
|||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
.k-analytics-grid--2col {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
.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 {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue