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
This commit is contained in:
@@ -0,0 +1,68 @@
|
||||
import { router } from '@inertiajs/vue3'
|
||||
|
||||
const CONSENT_KEY = 'analytics_consent'
|
||||
const VISITOR_KEY = '_vid'
|
||||
|
||||
function getVisitorId() {
|
||||
let vid = localStorage.getItem(VISITOR_KEY)
|
||||
if (!vid) {
|
||||
vid = crypto.randomUUID()
|
||||
localStorage.setItem(VISITOR_KEY, vid)
|
||||
}
|
||||
return vid
|
||||
}
|
||||
|
||||
function isExcludedPath() {
|
||||
const path = window.location.pathname
|
||||
return path.startsWith('/dashboard') || path.startsWith('/admin')
|
||||
}
|
||||
|
||||
function sendPayload(payload) {
|
||||
const blob = new Blob([JSON.stringify(payload)], { type: 'application/json' })
|
||||
|
||||
if (navigator.sendBeacon) {
|
||||
const sent = navigator.sendBeacon('/api/track/hit', blob)
|
||||
if (sent) return
|
||||
}
|
||||
|
||||
fetch('/api/track/hit', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify(payload),
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
keepalive: true,
|
||||
}).catch(() => {})
|
||||
}
|
||||
|
||||
export function getConsent() {
|
||||
return localStorage.getItem(CONSENT_KEY)
|
||||
}
|
||||
|
||||
export function trackCurrentPage() {
|
||||
if (typeof window === 'undefined') return
|
||||
if (getConsent() !== 'granted') return
|
||||
if (isExcludedPath()) return
|
||||
|
||||
sendPayload({
|
||||
visitor_id: getVisitorId(),
|
||||
url: window.location.pathname + window.location.search,
|
||||
referrer: document.referrer || null,
|
||||
screen_resolution: `${window.screen.width}x${window.screen.height}`,
|
||||
title: document.title,
|
||||
timestamp: Date.now(),
|
||||
})
|
||||
}
|
||||
|
||||
let listenerRegistered = false
|
||||
|
||||
export function initAnalytics() {
|
||||
if (typeof window === 'undefined') return
|
||||
|
||||
if (!listenerRegistered) {
|
||||
listenerRegistered = true
|
||||
router.on('navigate', () => {
|
||||
setTimeout(trackCurrentPage, 100)
|
||||
})
|
||||
}
|
||||
|
||||
window._ntspiTrackHit = trackCurrentPage
|
||||
}
|
||||
Reference in New Issue
Block a user