- 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
69 lines
1.7 KiB
JavaScript
69 lines
1.7 KiB
JavaScript
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
|
|
}
|