Files
ntspi-app/resources/js/Pages/Dashboard/Components/shared/AnalyticsOverview.vue
T
F4ilji d6c0f48d48 feat(analytics): add site analytics with grouped navigation sections
- Add Analytics container: models, tasks, jobs, controllers, routes, services
- Add analytics config with section groups (structure, institute, content, services)
- Add database tables: analytics_sessions, analytics_hits, analytics_daily_stats
- Add frontend: Analytics page, section detail, chart, overview, navigation components
- Add consent banner and JS tracking service (sendBeacon + Inertia listener)
- Add permission sync for view_analytics, view_any_analytic
- Fix trailing slash URL matching in GetNavigationSectionsTask
- Fix event propagation on expand arrows in navigation sections
- Fix RecordVisitJob to not depend on dropped analytics_geo_cache table
- Replace plain text views/visitors with badge-style icons
- Group navigation sections by category with separate blocks
- Add config-driven non-CMS sections (faculties, divisions, DPO, journals)
- Remove Events, TV, Sveden from analytics sections
2026-09-18 22:18:04 +05:00

114 lines
3.5 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<div class="grid grid-cols-2 sm:grid-cols-4 gap-4">
<div
v-for="card in statCards"
:key="card.label"
class="bg-layer border border-layer-line rounded-lg p-5 shadow-xs"
>
<div class="flex items-center justify-between mb-3">
<div class="w-10 h-10 rounded-lg flex items-center justify-center" :class="card.bgClass">
<DashboardIcon :name="card.icon" size="5" :class="card.iconClass" />
</div>
<span
v-if="card.change.direction !== 'neutral'"
class="inline-flex items-center px-1.5 py-0.5 rounded-full text-[10px] font-medium"
:class="badgeClass(card)"
>
{{ card.change.direction === 'up' ? '↑' : '↓' }}
{{ formatChange(card) }}
</span>
</div>
<div class="space-y-1">
<p class="text-2xl font-semibold text-foreground">{{ formatValue(card.value, card.format) }}</p>
<p class="text-xs text-muted-foreground-1">{{ card.label }}</p>
</div>
</div>
</div>
</template>
<script>
import DashboardIcon from '../DashboardIcon.vue';
export default {
name: 'AnalyticsOverview',
components: { DashboardIcon },
props: {
overview: { type: Object, required: true },
},
computed: {
statCards() {
return [
{
icon: 'users',
bgClass: 'bg-blue-500/10',
iconClass: 'text-blue-600',
label: 'Уникальные посетители',
value: this.overview.unique_visitors.value,
change: this.overview.unique_visitors,
changeSuffix: '%',
format: 'number',
},
{
icon: 'eye',
bgClass: 'bg-emerald-500/10',
iconClass: 'text-emerald-600',
label: 'Просмотры страниц',
value: this.overview.page_views.value,
change: this.overview.page_views,
changeSuffix: '%',
format: 'number',
},
{
icon: 'clock',
bgClass: 'bg-violet-500/10',
iconClass: 'text-violet-600',
label: 'Среднее время',
value: this.overview.avg_time.value,
change: this.overview.avg_time,
changeSuffix: 'с',
format: 'duration',
},
{
icon: 'arrow-uturn-left',
bgClass: 'bg-rose-500/10',
iconClass: 'text-rose-600',
label: 'Отказы (Bounce Rate)',
value: this.overview.bounce_rate.value,
change: this.overview.bounce_rate,
changeSuffix: '%',
format: 'percent',
invertColor: true,
},
];
},
},
methods: {
formatValue(value, format) {
if (format === 'duration') {
const min = Math.floor(value / 60);
const sec = value % 60;
return min > 0 ? `${min}м ${sec}с` : `${sec}с`;
}
if (format === 'percent') return `${value}%`;
return new Intl.NumberFormat('ru-RU').format(value);
},
formatChange(card) {
const val = Math.abs(card.change.change);
if (card.format === 'duration') {
const min = Math.floor(val / 60);
const sec = val % 60;
return min > 0 ? `${min}м ${sec}с` : `${val}с`;
}
return `${val}${card.changeSuffix}`;
},
badgeClass(card) {
const isUp = card.change.direction === 'up';
const bad = card.invertColor ? isUp : !isUp;
return bad
? 'bg-red-500/10 text-red-600'
: 'bg-emerald-500/10 text-emerald-600';
},
},
};
</script>