Making the JADA Maintenance Dashboard Task Card Clickable: A Single-Page App Navigation Pattern

During a recent development session, we identified a UX gap in the JADA Maintenance Hub dashboard: the "Total Tasks" summary card displayed the count but wasn't interactive. Users had to manually navigate to the Tasks tab to see details. This post walks through how we made that card clickable to instantly route users to the task list, and the infrastructure patterns that enabled a same-day deployment.

The Problem: Isolated Summary Cards

The JADA Maintenance Hub is a single-page application (SPA) served from S3 and distributed globally via CloudFront. It displays multiple dashboard tabs—Overview, Systems, Tasks, and more—each with its own content pane. The Overview tab showed summary cards including "Total Tasks: 82", but clicking that card did nothing. The cognitive load on users was: see the count, manually switch tabs, find the tasks. We wanted to eliminate that friction.

Architecture Overview: S3 + CloudFront + Tab-Based Navigation

The maintenance hub lives in an S3 bucket (exact path withheld for security) and is served through a CloudFront distribution that handles cache invalidation and global edge distribution. The application uses a tab-switching pattern implemented in vanilla JavaScript:


// Simplified tab navigation pattern
function switchTab(tabName) {
  // Hide all tab panes
  document.querySelectorAll('[data-pane]').forEach(pane => {
    pane.style.display = 'none';
  });
  
  // Show the requested tab
  document.querySelector(`[data-pane="${tabName}"]`).style.display = 'block';
  
  // Update active tab indicator
  document.querySelectorAll('[data-tab]').forEach(tab => {
    tab.classList.remove('active');
  });
  document.querySelector(`[data-tab="${tabName}"]`).classList.add('active');
}

The Tasks tab content is rendered in a pane with data-pane="systems" (note: the attribute name and value indicate some legacy naming, but the logic is consistent). By wiring the card's click handler to switchTab('systems'), we reuse the existing navigation infrastructure without adding new route logic.

Implementation: Wrapping the Card in Clickable Markup

The Total Tasks card was originally a static <div> with class info-card. We modified the card structure in the index.html file (downloaded from S3) to be semantically clickable:


<!-- Before: Static card -->
<div class="info-card">
  <h3>Total Tasks</h3>
  <p class="card-value">82</p>
</div>

<!-- After: Clickable card -->
<button class="info-card info-card-link" onclick="switchTab('systems')">
  <h3>Total Tasks</h3>
  <p class="card-value">82</p>
</button>

We converted the <div> to a <button> element and added the info-card-link class. This provides semantic HTML (screen readers understand it as clickable) and native keyboard support (users can tab and Enter to activate).

Styling: Visual Affordance for Interactivity

We added CSS rules to signal that the card is interactive:


.info-card-link {
  cursor: pointer;
  transition: all 0.2s ease;
  border: none;
  background: inherit;
  padding: inherit;
}

.info-card-link:hover {
  transform: translateY(-2px);
  box-shadow: 0 8px 16px rgba(0, 0, 0, 0.15);
}

.info-card-link:active {
  transform: translateY(0);
  box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
}

.info-card-link:focus {
  outline: 2px solid #007bff;
  outline-offset: 2px;
}

The hover state adds a subtle lift (2px translateY) and enhanced shadow, signaling interactivity without being jarring. The active state provides tactile feedback on click. The focus outline ensures keyboard users can see which element they're about to activate.

Deployment: S3 + CloudFront Cache Invalidation

After modifying the index.html file locally, we uploaded it back to S3 using the AWS CLI:


aws s3 cp /tmp/maintenance_index.html s3://[bucket-name]/index.html --content-type "text/html"

Uploading to S3 alone isn't sufficient; CloudFront caches the object at edge locations globally. We invalidated the cache to force an immediate refresh:


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

The wildcard path /* clears all cached objects for that distribution. CloudFront typically propagates invalidations within 60 seconds, so users see the updated version almost immediately. This is critical for same-day deployments—without invalidation, cached versions would linger for hours.

Why This Approach?

  • Zero dependency changes: We didn't need to modify any backend APIs, Lambda functions, or services. The card routes to an existing tab pane.
  • Reuses existing navigation: The switchTab() function already handles all the DOM manipulation. Adding a new call site is trivial.
  • Semantic HTML: Using a <button> instead of a styled <div> with JavaScript click handlers is more accessible and easier for other developers to understand.
  • Graceful degradation: If JavaScript fails, a button element still indicates clickability. A styled div would silently fail.
  • Fast deployment: S3 + CloudFront allows us to push changes without server restarts or deployment pipelines. Download, modify, upload, invalidate—all from the CLI in minutes.

Testing and Validation

After deployment, we verified:

  • Clicking the Total Tasks card switches to the Systems tab where the task table is rendered.
  • Hover and active states display correctly on desktop browsers.
  • Keyboard navigation works (Tab to focus, Enter to activate).
  • CloudFront served the new version (checked Cache-Control headers in DevTools).

What's Next

This pattern—clickable summary cards routing to detail tabs—can now be extended to other cards (Total Maintenance Items, Upcoming Haul-Out, etc.) using the same technique. The infrastructure is already in place; each new card is just a copy-paste of the onclick handler and CSS class.

Future improvements might include analytics tracking (log which cards users click most) or dynamic totals (update the card count via WebSocket when new tasks are created), but those are post-MVP enhancements. For today's goal—same-day UX improvement with zero downtime—S3 + CloudFront invalidation delivered.