Appearance
Insights ​
Overview ​
Insights surfaces in-app usage graphs for workspace members, pulling content-consumption data from the Laravel backend rather than from Amplitude. There are two surfaces:
- Dashboard Insights — a "Last 7 Days" bar chart widget on the workspace dashboard showing viewed, added, downloaded, and shared asset counts. Rendered by
InsightsTabGraphwith tab navigation across the four metrics. - Asset / Folder Detail Insights — the same chart embedded in the asset detail panel as a tab, scoped to a single asset. Rendered by
InsightsGraph, which shows all metrics as separate bar cards stacked vertically.
Both chart surfaces share the same Chart.js configuration, bar plugin, and weekday-label logic from the useInsightsChart composable. Insights data is not the same as Amplitude event tracking or portal analytics — see the comparison table below.
Architecture ​
Chart.js Bar components (from vue-chartjs) are used for rendering. Both InsightsGraph and InsightsTabGraph receive pre-built bar_data objects; they do not call APIs themselves. Data loading is handled upstream by page-level composables (useDashboard via useDashboardQuery, useAssetDetail for asset-scoped data).
The useInsightsChart composable is the single source of truth for all chart configuration. It computes real weekday initials for the trailing 7-day window using dayjs, defines the zero-bar-plugin that draws a 2px baseline under every bar, and exports buildInsightBarData() for constructing the two-dataset structure (bar + hover-dot scatter).
Date range filtering (start date, end date) and display state are managed by useAnalyticStore, a Pinia Options store. Store actions are the only way to update state — components call setter actions rather than writing directly to store properties.
File Structure ​
Store Files ​
app/stores/analytics.ts—useAnalyticStorePinia Options store- Date range state:
dStartDate,dEndDate,lastDate,activeDate - Transaction state:
InsightsTransaction,InsightsTransactionShare - UI modal flags:
isMoveModal,isCopyModal,isMoveFolderModal,isNewFolderModal - Misc state:
status,statusList,getEmail,userAgentAndLocation - All 12 setter actions (one per state field)
- Date range state:
Composable Files ​
app/composables/core/common/useInsightsChart.ts— chart configuration composableinsightsDayInitials— weekday letter array (S/M/T/W/T/F/S) for the actual trailing 7 daysinsightBarPlugin— Chart.js plugin that draws a 2px baseline under each bar (purple for zero-day, bar color for non-zero)insightsBarOptions— full Chart.js options object (tooltips, scales, hover, animation)buildInsightBarData(labels, values, cardLabel)— builds two-dataset bar+scatter structure
Component Files ​
app/components/insights/InsightsTabGraph.vue— tabbed insights chartv-tabs+v-windownavigation across insight types- Tab order enforced: viewed → added → downloaded → shared (with remaining metrics appended)
- Plan-gated metrics show as disabled tabs with an upgrade tooltip (
code: 402) activeTabandchartKeyare reactive;chartKeyincrements on tab switch to force re-render- Used on the DAM dashboard
app/components/insights/InsightsGraph.vue— stacked insights bar chart- Renders each insight metric as a separate
v-cardwith aBarchart - No tab navigation — all metrics shown simultaneously
- Used in asset / folder detail panels
- Renders each insight metric as a separate
Page / Query Files ​
app/composables/queries/useDashboardQuery.ts— TanStack Query for dashboard datainsightsQuery— fetches weekly insights for the workspace dashboard- Returns
weeklyInsightLoadingwhenisPending || isFetching
app/composables/core/pages/useDashboard.ts— dashboard page composableweeklyInsightLoading— derived frominsightsQuery.isPending || insightsQuery.isFetchinginsightsQuery.datafeedsInsightsTabGraphasweeklyInsightList
Style Files ​
app/assets/scss/pages/_insights.scss— insights-specific styles.weekly-insights,.weekly-insights-list,.weekly-insights-chart-skeleton- Chart skeleton loaders, tab layout
InsightsTabGraph ​
File: app/components/insights/InsightsTabGraph.vue
Tabbed bar chart for the dashboard. Handles plan-gated metric tabs, enforces metric display order, and re-renders charts on tab switch via a reactive chartKey.
Props ​
typescript
interface Props {
mainClass?: string // root v-card CSS class (default: 'boxview')
heading?: string // card title (default: 'Last 7 Days')
dataLoading?: boolean // shows skeleton when true
weeklyInsightList?: Record<string, InsightItem> // keyed insight data from backend
barOptions?: Record<string, unknown> // Chart.js options override
barPlugin?: Plugin<'bar'>[] // Chart.js plugins array
}Internal State ​
typescript
const activeTab = ref(0) // current v-tabs selection
const chartKey = ref(0) // incremented on tab change to force chart re-render
// Enforced tab order — remaining keys appended after these four
const INSIGHT_ORDER = ['viewed', 'added', 'downloaded', 'shared']Usage Example ​
vue
<template>
<InsightsTabGraph
heading="Last 7 Days"
:data-loading="weeklyInsightLoading"
:weekly-insight-list="insightsData"
:bar-options="insightsBarOptions"
:bar-plugin="[insightBarPlugin]"
/>
</template>
<script setup lang="ts">
const { insightsBarOptions, insightBarPlugin } = useInsightsChart()
const { insightsQuery, weeklyInsightLoading } = useDashboardQuery()
const insightsData = computed(() => insightsQuery.data.value ?? {})
</script>InsightsGraph ​
File: app/components/insights/InsightsGraph.vue
Stacked chart variant — renders all insight metrics simultaneously as separate bar cards. Used in asset and folder detail panels.
Props ​
typescript
interface Props {
mainClass?: string // additional CSS classes
heading?: string // section heading (default: 'Last 7 Days')
dataLoading?: boolean // shows skeleton loader when true
weeklyInsightList?: Record<string, InsightItem> // keyed insight data
barOptions?: Record<string, unknown> // Chart.js options
barPlugin?: Plugin<'bar'>[] // Chart.js plugins
}Usage Example ​
vue
<template>
<InsightsGraph
heading="Asset Insights"
:data-loading="assetInsightsLoading"
:weekly-insight-list="assetInsights"
:bar-options="insightsBarOptions"
:bar-plugin="[insightBarPlugin]"
/>
</template>
<script setup lang="ts">
const { insightsBarOptions, insightBarPlugin } = useInsightsChart()
const assetInsightsLoading = ref(false)
const assetInsights = ref({})
</script>useInsightsChart ​
File: app/composables/core/common/useInsightsChart.ts
Single source of truth for bar chart configuration used by both dashboard and asset-detail insight charts.
Returned Values ​
typescript
{
insightsDayInitials: string[]
// Weekday single letters (S/M/T/W/T/F/S) for the actual last 7 calendar days, oldest → today
insightBarPlugin: { id: 'zero-bar-plugin', beforeDraw(chart): void }
// Chart.js plugin: draws 2px baseline under each bar
// Zero-value bars: light lavender (#E9E9FB); non-zero bars: bar color (#A8A8F0)
insightsBarOptions: Record<string, unknown>
// Full Chart.js options: no legend, custom external tooltip, hidden Y axis,
// weekday-letter X-axis ticks, hover mode 'index'
buildInsightBarData: (labels: string[], values: number[], cardLabel: string) => ChartData
// Builds bar+scatter two-dataset structure
// Bar color: #A8A8F0 (purple), hover: #075850 (dark green)
// Scatter: hover-dot at points where value ≤ 20% of max (including zero)
}Usage Example ​
vue
<script setup lang="ts">
const { insightsBarOptions, insightBarPlugin, buildInsightBarData, insightsDayInitials } = useInsightsChart()
// Build chart data from API response
const chartData = buildInsightBarData(
insightsDayInitials, // ['S', 'M', 'T', 'W', 'T', 'F', 'S']
[12, 0, 4, 7, 0, 23, 8], // daily counts, oldest → today
'viewed'
)
// Pass to Bar component
// <Bar :chart-data="chartData" :options="insightsBarOptions" :plugins="[insightBarPlugin]" />
</script>useAnalyticStore ​
File: app/stores/analytics.ts
Pinia Options store for insights date-range and UI state.
State ​
typescript
interface AnalyticsState {
lastDate: string // rolling window size in days (default: '29')
status: string // current status filter
dStartDate: string // start date 'YYYY-MM-DD' (default: today)
dEndDate: string // end date 'YYYY-MM-DD' (default: today)
activeDate: string // currently highlighted date 'YYYY-MM-DD'
statusList: unknown[] // list of available status values
getEmail: string // email filter
isMoveModal: boolean // move asset dialog open
isCopyModal: boolean // copy asset dialog open
isMoveFolderModal: boolean // move folder dialog open
isNewFolderModal: boolean // new folder dialog open
InsightsTransaction: Record<string, unknown> // per-asset insight transaction data
InsightsTransactionShare: Record<string, unknown> // sharing insight transaction data
userAgentAndLocation: Record<string, unknown> // browser/location data for support
}Actions ​
typescript
setLastDate(payload: string): void // update rolling window
setStartDate(payload: string): void // update dStartDate
setEndDate(payload: string): void // update dEndDate
setStatus(payload: string): void // update status filter
setStatusList(payload: unknown[]): void // update status options
setActiveDate(payload: string): void // update highlighted date
setEmail(payload: string): void // update email filter
setMoveModal(payload: boolean): void // toggle move dialog
setCopyModal(payload: boolean): void // toggle copy dialog
setMoveFolderModal(payload: boolean): void // toggle folder move dialog
setNewFolderModal(payload: boolean): void // toggle new folder dialog
setUserAgentAndLocation(payload: UserAgentAndLocation | null): voidUsage Example ​
vue
<script setup lang="ts">
const analyticStore = useAnalyticStore()
// Update date range for insights query
analyticStore.setStartDate('2025-07-01')
analyticStore.setEndDate('2025-07-31')
analyticStore.setLastDate('30')
// Read state
const dateRange = computed(() => ({
from: analyticStore.dStartDate,
to: analyticStore.dEndDate,
}))
</script>Workflows ​
Dashboard Insights Load ​
useDashboard composable mounts
→ useDashboardQuery() returns insightsQuery
→ insightsQuery enabled when workspaceId resolved
→ fetches weekly insight data from Laravel backend
→ weeklyInsightLoading = isPending || isFetching
↓
InsightsTabGraph receives weeklyInsightList
→ orderedInsightList computed: viewed → added → downloaded → shared
→ plan-gated metrics (code: 402) rendered as disabled tabs with upgrade tooltip
→ v-tabs → v-window renders Bar chart per active tab
→ useInsightsChart.insightsBarOptions applied
→ zero-bar-plugin draws baselines
→ external tooltip: "N assets viewed on Mon"Asset Insights Load ​
AssetDetailPanel opens asset
→ useAssetDetail composable fetches asset data
→ insight data returned alongside asset detail
↓
InsightsGraph rendered in Insights tab
→ all metrics rendered as stacked v-card bar charts
→ same chart config from useInsightsChart
→ no tab navigation — all metrics visible simultaneouslyChart Data Structure ​
The buildInsightBarData function returns a two-dataset structure:
typescript
{
labels: string[], // weekday initials: ['S', 'M', 'T', 'W', 'T', 'F', 'S']
datasets: [
{
label: 'Graph Data',
type: 'bar',
data: number[], // daily counts
backgroundColor: '#A8A8F0', // lavender purple
hoverBackgroundColor: '#075850', // dark green on hover
borderWidth: 0,
card_label: 'viewed', // used by tooltip callback
},
{
type: 'scatter',
label: 'Points',
data: [{ x: 0, y: 12, show: false }, ...],
pointHoverRadius: (ctx) => pointData?.show ? 5 : 0,
// hover dot appears only for bars ≤ 20% of max value (including zero)
}
]
}Data Source Comparison ​
| Surface | Data source | Scope | Visible to |
|---|---|---|---|
| Insights (dashboard, asset detail) | Laravel backend counters | Workspace + individual assets | Workspace members (any role) |
| Portal Analytics | Laravel backend | External portal visitor activity | Workspace members on portal detail |
| Amplitude | Client-side event tracking | UI interactions | Internal team only (Amplitude dashboard) |
Insights counts authenticated workspace-member views and downloads. Portal Analytics counts external visitor activity. Amplitude tracks product interaction events for internal analysis.
Component Integration ​
Dashboard (app/pages/[workspace_id]/dam/dashboard/index.vue) ​
All insight state comes from useDashboard(). The composable builds weeklyInsightList by processing the raw insightsQuery data through buildInsightBarData() from useInsightsChart(). The page receives InsightBarPlugin as a single object and casts it to the Chart.js plugin array shape that InsightsTabGraph expects.
vue
<template>
<InsightsTabGraph
main-class="boxview weekly-insights h-auto mt-4 mt-sm-6 mt-lg-0 mb-4 mb-sm-6"
heading="Last 7 Days"
:data-loading="weeklyInsightLoading"
:weekly-insight-list="weeklyInsightList"
:bar-options="InsightsBarOptions"
:bar-plugin="insightBarPlugins"
/>
</template>
<script setup lang="ts">
const {
weeklyInsightLoading,
weeklyInsightList,
InsightsBarOptions,
InsightBarPlugin,
} = useDashboard()
const insightBarPlugins = [InsightBarPlugin] as unknown as import('chart.js').Plugin<'bar'>[]
</script>Asset Detail (app/pages/[workspace_id]/dam/files/[_id]/index.vue) ​
The Insights tab in the asset detail panel renders InsightsGraph (stacked variant, no tab navigation). State comes from useAssetDetail(). The page casts weeklyInsightList to the InsightItem-keyed shape that InsightsGraph expects, and applies the same plugin cast as the dashboard.
vue
<template>
<InsightsGraph
heading="Last 7 Days"
:data-loading="weeklyInsightLoading"
:weekly-insight-list="weeklyInsightListForChart"
:bar-options="InsightsBarOptions"
:bar-plugin="insightBarPlugins"
/>
</template>
<script setup lang="ts">
const {
weeklyInsightLoading,
weeklyInsightList,
InsightsBarOptions,
InsightBarPlugin,
} = useAssetDetail()
const weeklyInsightListForChart = computed(
() => weeklyInsightList.value as Record<string, { card_label?: string; bar_data?: unknown; code?: number }>
)
const insightBarPlugins = [InsightBarPlugin] as unknown as import('chart.js').Plugin<'bar'>[]
</script>Related Documentation ​
- Analytics — Amplitude + portal stats
- Dashboard — dashboard layout and query structure
- Portals — Portal Analytics
- Composables — useAnalyticStore
- Subscription & Plans — plan gating for insight metrics