Making Dashboard Cards Interactive: Linking the Total Tasks Widget to the Systems Tab

During this development session, we tackled a UX improvement on the JADA Maintenance Hub: making the "Total Tasks" summary card clickable so users can navigate directly to the detailed task list. What started as a simple UI enhancement revealed important lessons about state management, CloudFront caching, and the architecture of our serverless dashboard infrastructure.

The Problem

The maintenance dashboard displays a summary card showing "Total Tasks: 82" in the header area, but clicking it did nothing. Users had to manually navigate to the Systems tab to see the full task list. For a tool designed to reduce friction in maintenance workflows, this was friction we could eliminate.

Architecture Context

The JADA Maintenance Hub is a static site hosted on AWS S3 with CloudFront distribution caching. The core application lives at:

  • S3 bucket: jada-maintenance-hub
  • Primary file: index.html
  • CloudFront distribution: maintenance.jadaledger.io

The dashboard uses vanilla JavaScript with a tab-switching architecture: multiple content sections are hidden/shown based on the active tab. The Systems tab (accessed via data-tab="systems") contains the detailed task table populated from our Lambda API.

Technical Implementation

Step 1: Understanding the Tab Navigation Pattern

First, we downloaded the current production index.html from S3 to understand the existing tab logic:

aws s3 cp s3://jada-maintenance-hub/index.html ./maintenance_index.html

The navigation uses a switchTab(tabName) function that:

  • Hides all tab content sections
  • Shows the requested tab based on matching data-tab attributes
  • Updates button/card active states for visual feedback

The Systems tab is identified by data-tab="systems", making our target invocation simply switchTab('systems').

Step 2: Locating the Total Tasks Card

We located the Total Tasks card in the dashboard header section. The original markup was a static <div class="info-card"> element with no interaction handlers. The card displayed aggregated task count from the dashboard state but had no affordance indicating it was interactive.

Step 3: CSS and Interaction Styling

To signal interactivity to users, we added a new CSS class info-card-link with the following properties:

.info-card-link {
  cursor: pointer;
  transition: all 0.3s ease;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}

.info-card-link:hover {
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2);
  transform: translateY(-2px);
}

.info-card-link:active {
  transform: translateY(0);
}

This follows modern UI patterns: pointer cursor indicates clickability, shadow lift on hover provides feedback, and the active state prevents the jarring effect of transform changes during click.

Step 4: Wiring the Click Handler

We modified the Total Tasks card markup to include the new class and an onclick handler:

<div class="info-card info-card-link" onclick="switchTab('systems')">
  <div class="card-value">82</div>
  <div class="card-label">Total Tasks</div>
</div>

The inline onclick is appropriate here because:

  • The card is rendered during page load (not dynamically inserted)
  • The target function is already in the global scope
  • It's a simple, single-action interaction with no complex event delegation
  • For a dashboard used by boat crews in variable conditions, minimal JavaScript overhead is desirable

Deployment and Cache Invalidation

Static site changes require careful cache management. Our deployment process:

aws s3 cp ./maintenance_index.html s3://jada-maintenance-hub/index.html --content-type text/html

Then invalidate the CloudFront distribution cache:

aws cloudfront create-invalidation --distribution-id [DISTRIBUTION_ID] --paths "/*"

Why full invalidation: We changed the HTML structure (added onclick), not just CSS. CloudFront caches both, but HTML changes are more critical — a stale CSS file gracefully degrades, while a stale HTML file with missing event handlers fails silently.

The invalidation typically propagates to edge locations within 60 seconds, but we verify by checking a staging environment that points to the same S3 bucket without CloudFront in front (useful for testing before cache invalidation).

Related Infrastructure Updates

During this session, we also made updates to the Apps Script backend:

  • JadaLedger.gs: New financial tracking module for expense logging
  • ExpenseTracker.gs: Modified to integrate with the new ledger backend
  • appsscript.json: Updated manifest with new API scopes and dependencies

These were pushed to the Apps Script project using clasp:

cd /Users/cb/Documents/repos/sites/queenofsandiego.com/
npx clasp push

The .claspignore file is configured to exclude development files and only sync production code. This separation ensures that dashboard deployments (S3/CloudFront) and backend updates (Apps Script) can be managed independently.

Testing and Validation

After deployment, we validated:

  • Click responsiveness: Clicking the Total Tasks card immediately switches to the Systems tab
  • Visual feedback: Hover and active states trigger as expected
  • Mobile compatibility: Touch devices trigger the onclick handler consistently
  • Cache behavior: Refreshing the page shows the updated HTML without manual cache clearing

Key Decisions and Rationale

  • Inline onclick vs. event listeners: For a static, pre-rendered card, inline handlers reduce JavaScript complexity. Event delegation would be overkill.
  • CSS class approach: Using a dedicated class (info-card-link) makes the intent clear in markup and allows future styling rules without modifying HTML.
  • Shadow/transform effects: Subtle animations (0.3s transitions) provide feedback without distraction — important for users in motion or rough seas.
  • Full CloudFront invalidation: While we could have invalidated just index.html, the wildcard path ensures any edge-case dependencies are refreshed.

What's Next

This pattern can be extended to other summary cards (systems count, pending tasks, etc.) using the same approach. We should monitor CloudFront metrics to ensure invalidation times remain under 60 seconds, and consider implementing a service worker