Skip to content

Dashboard ​

Overview ​

  1. Entry point for the DAM module: The dashboard is the first screen a user sees after entering a workspace. It gives a quick view of the most important content — recent collages, recent folders, and recently uploaded assets.

  2. Quick-action shortcuts: Workspace-permission-aware buttons for "New Asset", "New Collage", and "New Folder" appear at the top. Each button is hidden when the authenticated user's role does not grant the relevant permission.

  3. Recents panel with tabs: The left card shows two tabs — Collages and Folders — each with card/list items linking directly into the relevant detail page.

  4. Right rail widgets: A "Last 7 Days" bar chart (weekly insights) sits above a "Recently Added" asset list that shows thumbnails, filenames, folders, and file-type chips.

  5. Admin alerts: The page reads localStorage and emits EventBus events to trigger a persistent header banner when uncategorized assets exist or when white-label SMTP is not configured.

  6. Storage gate: If the workspace storage is at or above 100%, the "New Asset" quick-action card becomes non-interactive, preventing new uploads until storage is freed.

Architecture ​

The dashboard is a self-contained Nuxt page (pages/_workspace_id/dam/dashboard/index.vue). It uses the damLayout layout and is protected by five middleware guards: authCheck, check-workspace-access, checkWorkspace, can-access-dam-module, and check-if-suspended.

All data loading happens sequentially in mounted(). The page makes five independent API calls in order: common overview data, recent collages, recent folders, recently uploaded assets, and weekly insight metrics. Each call manages its own loading flag and renders skeleton loaders while its data is in flight.

Permission checks are computed properties that reach into this.$auth.user.accessibleWorkspaces for the current workspace object and call plugin-level helpers ($canCreateCollages, $canShareDownload, etc.). This keeps the template free of inline permission logic.

The weekly insights bar chart uses Chart.js 2 via a wrapper component (InsightsTabGraph.vue). Chart data is assembled in fetchWeeklyInsights(), which deep-clones a template dataset object and attaches each insight category's graph values. A custom beforeDraw plugin renders thin placeholder lines for zero-height bars.

Collage actions (rename, delete, share, permissions) are handled directly on the dashboard via a set of lazy-loaded dialogs. The Vuex store handles the actual mutations — dam/deleteCollection and dam/renameCollection — keeping backend communication out of the page component.

EventBus is used in two directions: the page listens for alertClose (to dismiss the header banner) and syncDashboard (to refresh collages or folders after external changes), and it emits alertMsgonHeader and open-add-folder-dailog / openCreateCollage to drive sibling layout components.

File Structure ​

JavaScript Files (.js) ​

  • mixins/fileType.js — File type detection mixin

    • $isAudio(fileType) — returns true for audio MIME types
    • $isVideo(fileType) — returns true for video MIME types
    • getSrcPath(file) — returns the correct thumbnail or preview URL for any asset type
  • mixins/commonFunctions.js — Shared utility mixin

    • changedFields(pairs) — diffs old/new values and returns which fields changed
    • $getWorkspaceId() — reads workspace ID from route or session
    • $toQueryString(obj) — serialises an object to a URL query string
  • mixins/imageStyle.js — Dynamic image class mixin

    • assetsListOnloadSetStyle(file, stylesObj, refPrefix) — sets landscape/portrait class after image load
    • dashboardCollageOnloadSetStyle(image, stylesObj, ref) — same, scoped to collage card images
  • mixins/amplitude-analytics.js — Analytics mixin

    • dispatchAnalytics(body) — fires an Amplitude event
    • trackActivity(body, extra) — enriches and fires an activity event
  • store/dam.js — Vuex DAM module

    • state.storage — { used, total, percentage, available, image, audio, video }
    • state.newFolderCount — counter reset by resetNewFolderCount mutation on dashboard load
    • Action deleteCollection(collectionId) — calls DELETE digital-assets/collection/delete/:id
    • Action renameCollection({ oldCollection, newName }) — commits setCollectionName locally
    • Action downloadMultipleFiles({ files, folders, collection_id, download_name }) — triggers download

Vue Component Files (.vue) ​

  • pages/_workspace_id/dam/dashboard/index.vue — Dashboard page

    • Renders quick-action cards, recents tabs, weekly insights, and recently uploaded
    • Loads all data in mounted() and cleans up EventBus listeners in beforeDestroy
    • Delegates collage CRUD to Vuex actions; renders confirm/rename/share dialogs inline
  • components/dam/Insights/InsightsTabGraph.vue — Weekly insights widget

    • Tabbed Chart.js bar chart, one tab per insight category (uploads, downloads, etc.)
    • Accepts a fully assembled bar_data Chart.js dataset object per tab
    • Shows a 402 upgrade tooltip when a category is gated by subscription plan
  • components/dam/Dialogs/ShareAssetDialog.vue — Share collage dialog

    • Used on dashboard to share an individual collage to external recipients
    • Receives files, collection, and collectionAssetsId as props
  • components/theme/global/Dialog/ConfirmationDialog.vue — Generic confirm dialog

    • Used for delete-collage confirmation
    • Emits confirm and cancel
  • components/dam/Dialogs/PermissionDialog.vue — Collage permission dialog

    • Manages which portals can see a given collage (instance_visibility)
    • Emits permission-updated with the updated permission object
  • components/dam/Dialogs/CreateCollageDialog.vue — Create/rename collage dialog

    • Dual-purpose: create new collages and edit name/description of existing ones
    • Emits submit with { name, description }

SVG Icon Components ​

  • components/svg/CollageUploadIcon.vue — Upload icon for "New Asset" quick action
  • components/svg/CollageGridIcon.vue — Grid icon for "New Collage" quick action
  • components/svg/CollageFolderIcon.vue — Folder icon for "New Folder" quick action and recent asset folder link
  • components/svg/CollageEmptyIcon.vue — Illustration shown in empty Collages/Folders/Recently Added states
  • components/svg/CollageFolderLargeIcon.vue — Large folder icon in the Folders tab list view
  • components/svg/CollageShareIcon.vue — Share option in collage context menu
  • components/svg/CollageEditIcon.vue — Edit option in collage context menu
  • components/svg/CollageDeleteIcon.vue — Delete option in collage context menu
  • components/svg/CollageLockIcon.vue — Lock badge on restricted collages/folders
  • components/svg/CollagePortalIcon.vue — Portals option in collage context menu

WeeklyInsights Component ​

File: components/dam/Insights/InsightsTabGraph.vue

Renders a tabbed bar chart for the last 7 days of workspace activity. Each tab corresponds to one insight category returned by the API (e.g. uploaded, downloaded). Categories gated by a plan restriction show a disabled tab with an upgrade tooltip.

Features ​

  • One v-tab per insight category from the API response
  • Skeleton loaders while data is loading or when the insight object is empty
  • Chart.js bar chart with a custom beforeDraw plugin that renders thin placeholder lines for zero-value bars
  • Upgrade messaging (402 plan gate) shown inline per tab instead of globally hiding the widget

Props ​

javascript
{
  mainClass: {
    type: String,
    required: true         // CSS class(es) applied to the outer v-card
  },
  heading: {
    type: String,
    required: true         // Card title text, e.g. 'Last 7 Days'
  },
  dataLoading: {
    type: Boolean,
    required: true         // Shows skeleton when true
  },
  weeklyInsightList: {
    type: Object,
    required: true         // Keyed by category name; each value is an insight object
                           // with bar_data (Chart.js dataset), card_label, code, message
  },
  barOptions: {
    type: Object,
    required: true         // Chart.js options object (axes, tooltips, hover)
  },
  barPlugin: {
    type: Array,
    required: true         // Chart.js plugin array (zero-bar-plugin)
  }
}

Usage ​

vue
<template>
  <WeeklyInsights
    mainClass="boxview collageTabs weekly-insights-tab h-auto"
    heading="Last 7 Days"
    :dataLoading="weeklyInsightLoading"
    :weeklyInsightList="weeklyInsightList"
    :barOptions="InsightsBarOptions"
    :barPlugin="InsightBarPlugin"
  />
</template>

<script>
import { cloneDeep } from 'lodash'

export default {
  components: {
    WeeklyInsights: () => import('~/components/dam/Insights/InsightsTabGraph.vue'),
  },
  data() {
    return {
      weeklyInsightLoading: true,
      weeklyInsightList: {},
      InsightsBarOptions: { /* Chart.js options */ },
      InsightBarPlugin: [{ id: 'zero-bar-plugin', beforeDraw(chart) { /* ... */ } }],
    }
  },
}
</script>

Dashboard Page ​

File: pages/_workspace_id/dam/dashboard/index.vue

Route: /:workspace_id/dam/dashboard

Features ​

  • Quick-action cards for New Asset, New Collage, and New Folder — hidden per role
  • Storage full gate: disables the New Asset card when storage.percentage >= 100
  • Recents panel with Collages and Folders tabs, each with grid card / list row items
  • Collage context menu: Share, Edit (rename/description), Portals (permissions), Delete
  • Folder visibility lock badge when instance_visibility is empty
  • Right-rail Recently Added list with thumbnail, name, date, folder link, and file-type chip
  • Weekly insights widget with Chart.js bar chart
  • EventBus-driven uncategorized assets banner and SMTP configuration warning

Computed Properties ​

javascript
{
  storageFull()            // true when storage.percentage >= 100
  storageUsedPercentage()  // formatted percentage string for display
  BACKEND_URL()            // this.$config.backendUrl — used to detect SVG-type assets
  visibleTopBtnRoutes()    // filters topBtnRoutes by canUpload/canCreateCollages/canCreateFolders
  canShareDownload()       // $canShareDownload(workspace)
  canCreateCollages()      // $canCreateCollages(workspace)
  canCreateFolders()       // $canCreateFolders(workspace)
  canDeleteContent()       // $canDeleteContent(workspace)
  canDeleteCollage()       // $canDeleteCollage(workspace)
  canEditCollageNameDescription()  // $canEditCollageNameDescription(workspace)
  canManageCollagePermission()     // feature flag: subscription_features.collection_permission_customisation.enable
  canUpdateCollagePermission()     // $canManageCollagePermission(workspace)
}

Key Methods ​

javascript
{
  // Data loading
  getOverviewData()         // GET /digital-assets/dashboard/common-data — uncategorized count + SMTP state
  RecentsCollages()         // GET /digital-assets/new-dashboard/recently-collection
  RecentsFolders()          // GET /digital-assets/new-dashboard/recently-folders
  loadRecentUploads()       // GET /digital-assets/new-dashboard/recently-uploaded-assets
  fetchWeeklyInsights()     // POST /digital-assets/new-dashboard/weekly-insights

  // Navigation
  navigateRoute(topBtn)     // Handles New Asset route push; delegates Collage/Folder to EventBus
  openCollageFolder(event, flag, item_id, new_tab, item) // Resolves route for collage or folder; respects Ctrl/Cmd click
  openAsset(asset_id, new_tab)  // Resolves asset detail route; fires analytics; handles PWA standalone mode

  // Collage CRUD
  deleteCollection()        // Dispatches dam/deleteCollection; removes item from local collages array
  changeCollectionName(payload) // POST collection update; dispatches dam/renameCollection
  shareCollection(item)     // Opens ShareAssetDialog for the given collage
  confirmDeleteCollection(item) // Sets collection ref and opens delete confirm dialog
  openPermissions(item)     // Opens PermissionDialog for the given collage
  openRenameDialog(item)    // Opens CreateCollageDialog in rename mode

  // EventBus helpers
  addCollage()              // EventBus.$emit('openCreateCollage', true)
  addFolderDialog()         // EventBus.$emit('open-add-folder-dailog')

  // Alert management
  emitUncategoryAlert()     // EventBus.$emit('alertMsgonHeader', { type: 'uncategoryAlert' })
  emitSmtpAlert()           // EventBus.$emit('alertMsgonHeader', { type: 'smtpAlert' })
  closeAlert()              // Persists dismiss=true to localStorage; hides uncategorized banner
  closeSmtpAlert()          // Persists dismiss=true to localStorage for SMTP alert

  // Image helpers (from imageStyle mixin)
  getImageClassAndDynamicStyle(file, dynamicStylesCard)  // Returns CSS class for fitted image display
  dashboardCollageOnloadSetStyle(image, stylesObj, ref)  // Sets style after collage preview image loads

  // Formatting
  formatDate(date)          // Returns 'Jan 01, 2024' locale string
  removeFileExtension(filename) // Strips last dot-extension from display name
}

Workflows ​

Dashboard Load Workflow ​

1. User navigates to dashboard
   Route: /:workspace_id/dam/dashboard
   Middleware: authCheck → check-workspace-access → checkWorkspace
             → can-access-dam-module → check-if-suspended
   Layout: damLayout
   ↓
2. mounted() begins — contentLoad: true shows skeleton loaders for top buttons
   $nextTick sets contentLoad: false to render actual quick-action cards
   ↓
3. getOverviewData()
   GET /digital-assets/dashboard/common-data?workspace_id={id}
   → Reads/writes localStorage key `{workspace_id}` for uncategory alert state
   → Reads/writes localStorage key `{workspace_id}_smtp` for SMTP alert state
   → Commits dam/resetNewFolderCount
   → Sets uncategoryCount, uncategoryAlert, smtpAlert
   → emitUncategoryAlert() / emitSmtpAlert() if conditions met
   ↓
4. RecentsCollages()
   GET /digital-assets/new-dashboard/recently-collection?workspace_id={id}
   → Sets collages[] with preview_images[], each image gets a generated UUID
   → collagesLoading: false
   ↓
5. RecentsFolders()
   GET /digital-assets/new-dashboard/recently-folders?workspace_id={id}
   → Sets folderList[]
   → foldersLoading: false
   ↓
6. loadRecentUploads()
   GET /digital-assets/new-dashboard/recently-uploaded-assets?workspace_id={id}
   → Sets recentUploadList[]
   → recentLoading: false
   ↓
7. fetchWeeklyInsights()
   POST /digital-assets/new-dashboard/weekly-insights
   Body: { date_type: 'week', from_date: 7 days ago, to_date: today }
   → Sets weeklyInsightList{} — one key per category
   → For each category: clones InsightsBarData template, attaches labels + datasets
   → weeklyInsightLoading: false
   ↓
8. EventBus listeners registered
   alertClose → closeAlert() or closeSmtpAlert()
   syncDashboard('collage') → RecentsCollages()
   syncDashboard('folders') → RecentsFolders()
   $nuxt.$on('update-overview-data') → getOverviewData() if >1 second since last fetch
   $nuxt.$on('update-uncategory') → getOverviewData() after 2 second delay

Collage Delete Workflow ​

1. User clicks Delete in collage context menu
   Component: Dashboard page
   Method: confirmDeleteCollection(item)
   → Sets collection = item, deleteDialog = true
   ↓
2. ConfirmationDialog shown
   Heading: 'Confirm Delete'
   Message: 'Are you sure you want to delete this <b>{name}</b> collage?'
   ↓
3. User confirms
   Method: deleteCollection()
   Vuex: dam/deleteCollection(collectionId)
   API: DELETE digital-assets/collection/delete/{id}?workspace_id={id}
   ↓
4. On success
   snackbar.success(message)
   removeCollection(collectionId) — filters collages[] in place
   RecentsCollages() — re-fetches to sync order
   Amplitude + trackActivity events fired

Collage Rename Workflow ​

1. User clicks Edit in collage context menu
   Method: openRenameDialog(item)
   → Sets collection = item, renameDialog = true
   ↓
2. CreateCollageDialog shown with existing name and description
   ↓
3. User submits
   Method: changeCollectionName({ name, description })
   API: POST digital-assets/collection/update/{id}
   Body: { workspace_id, name, description }
   ↓
4. On success
   collection.name and collection.description updated locally
   Vuex: dam/renameCollection({ oldCollection, newName }) — local commit only
   snackbar.success(message)
   Amplitude + trackActivity events fired

API Integration ​

Dashboard Endpoints ​

MethodEndpointDescription
GET/digital-assets/dashboard/common-dataOverview: uncategorized count, SMTP status
GET/digital-assets/new-dashboard/recently-collectionRecent collages with preview images
GET/digital-assets/new-dashboard/recently-foldersRecent folders with asset counts
GET/digital-assets/new-dashboard/recently-uploaded-assetsRecent assets with thumbnails
POST/digital-assets/new-dashboard/weekly-insights7-day activity metrics by category
POST/digital-assets/collection/update/:idRename or update collage description

Request and Response Examples ​

GET /digital-assets/dashboard/common-data ​

javascript
// Request
GET /digital-assets/dashboard/common-data?workspace_id=42

// Response
{
  "data": {
    "total_uncategorized": 3,
    "white_label_enabled": true,
    "smtp_configured": false,
    "use_storage": 2147483648,
    "total_storage": 10737418240,
    "storage_percentage": 20.0
  }
}

POST /digital-assets/new-dashboard/weekly-insights ​

javascript
// Request
POST /digital-assets/new-dashboard/weekly-insights
{
  "date_type": "week",
  "from_date": "2024-01-08",
  "to_date": "2024-01-14"
}

// Response
{
  "data": {
    "uploaded": {
      "graph_data": {
        "2024-01-08": 4,
        "2024-01-09": 2,
        "2024-01-10": 0,
        "2024-01-11": 7,
        "2024-01-12": 1,
        "2024-01-13": 3,
        "2024-01-14": 5
      }
    },
    "downloaded": {
      "original": { "code": 402, "message": "Upgrade to access download analytics" }
    }
  }
}

GET /digital-assets/new-dashboard/recently-collection ​

javascript
// Request
GET /digital-assets/new-dashboard/recently-collection?workspace_id=42&url_workspace_id=42

// Response
{
  "data": [
    {
      "id": 101,
      "name": "Spring Campaign",
      "description": "Brand assets for Q1",
      "assets_count": 12,
      "instance_visibility": [1],
      "preview_images": [
        { "url": "https://cdn.example.com/thumb1.jpg", "file_type": "jpg" },
        { "url": "https://cdn.example.com/thumb2.jpg", "file_type": "png" }
      ]
    }
  ]
}

Component Integration ​

Embedding Dashboard Data in a Custom Component ​

vue
<template>
  <div>
    <!-- Weekly insights widget -->
    <WeeklyInsights
      mainClass="boxview collageTabs h-auto"
      heading="Last 7 Days"
      :dataLoading="weeklyInsightLoading"
      :weeklyInsightList="weeklyInsightList"
      :barOptions="chartOptions"
      :barPlugin="chartPlugins"
    />

    <!-- Recently uploaded list -->
    <v-card outlined class="boxview mt-4">
      <v-card-title>
        <h5>Recently Added</h5>
      </v-card-title>
      <v-card-text class="py-0">
        <v-list dense class="py-0">
          <v-list-item
            v-for="asset in recentUploadList"
            :key="asset.id"
            class="recently-uploaded-list"
            :ripple="false"
          >
            <v-list-item-avatar tile size="64" class="my-0 mr-0">
              <v-img :src="getSrcPath(asset)" :alt="asset.display_file_name" />
            </v-list-item-avatar>
            <v-list-item-content class="py-0 mx-6">
              <v-list-item-subtitle class="recent-date">
                {{ formatDate(asset.created_at) }}
              </v-list-item-subtitle>
              <v-list-item-title>
                {{ removeFileExtension(asset.display_file_name) }}
              </v-list-item-title>
            </v-list-item-content>
            <v-list-item-action class="my-0 ml-0">
              <v-chip class="tags" label>{{ asset.file_type }}</v-chip>
            </v-list-item-action>
          </v-list-item>
        </v-list>
      </v-card-text>
    </v-card>
  </div>
</template>

<script>
import { cloneDeep } from 'lodash'
import moment from 'moment'
import fileType from '~/mixins/fileType'

export default {
  name: 'DashboardSummary',
  mixins: [fileType],
  components: {
    WeeklyInsights: () => import('~/components/dam/Insights/InsightsTabGraph.vue'),
  },
  data() {
    return {
      weeklyInsightLoading: true,
      weeklyInsightList: {},
      recentUploadList: [],
      chartOptions: {
        maintainAspectRatio: false,
        responsive: true,
        legend: { display: false },
        scales: {
          xAxes: [{ gridLines: { display: false }, ticks: { display: false } }],
          yAxes: [{ gridLines: { display: false }, ticks: { beginAtZero: true, display: false } }],
        },
      },
      chartPlugins: [],
      insightsBarTemplate: {
        labels: [],
        datasets: [
          {
            label: 'Graph Data',
            data: [],
            backgroundColor: '#A8A8F0',
            borderColor: '#A8A8F0',
          },
        ],
      },
    }
  },
  async mounted() {
    await this.loadData()
  },
  methods: {
    async loadData() {
      const workspaceId = this.$route.params.workspace_id || this.$getWorkspaceId()
      try {
        const pastDate = new Date()
        pastDate.setDate(pastDate.getDate() - 6)
        const { data: insights } = await this.$axios.$post(
          '/digital-assets/new-dashboard/weekly-insights',
          {
            date_type: 'week',
            from_date: moment(pastDate).format('YYYY-MM-DD'),
            to_date: moment(new Date()).format('YYYY-MM-DD'),
          }
        )
        Object.keys(insights).forEach((key) => {
          const insight = insights[key]
          if (!insight.original) {
            insight.card_label = key
            insight.bar_data = cloneDeep(this.insightsBarTemplate)
            insight.bar_data.labels = Object.keys(insight.graph_data)
            insight.bar_data.datasets[0].data = Object.values(insight.graph_data)
          }
        })
        this.weeklyInsightList = insights
      } finally {
        this.weeklyInsightLoading = false
      }

      const { data: recent } = await this.$axios.$get(
        '/digital-assets/new-dashboard/recently-uploaded-assets',
        { params: { workspace_id: workspaceId, url_workspace_id: workspaceId } }
      )
      this.recentUploadList = recent
    },
    formatDate(date) {
      return new Date(date).toLocaleDateString('en-US', {
        year: 'numeric',
        month: 'short',
        day: '2-digit',
      })
    },
    removeFileExtension(filename) {
      const i = filename.lastIndexOf('.')
      return i !== -1 ? filename.substring(0, i) : filename
    },
  },
}
</script>