Making Dashboard Stat Cards Interactive: Linking UI Components to Tab Navigation in a Static S3-Hosted Dashboard
What Was Done
The JADA maintenance hub dashboard had a "Total Tasks" stat card displaying 82 pending items, but it was purely informative—clicking it did nothing. Users had to manually navigate to the Systems tab to see the task list. This session involved making that card clickable to instantly switch users to the Tasks view, improving UX and reducing navigation friction.
The core change was minimal but required understanding the full deployment pipeline: identify the card in the S3-hosted HTML, add click handling via JavaScript's existing tab-switch logic, apply CSS hover states, push to S3, and invalidate CloudFront cache.
Technical Details
File Location and Structure
The maintenance hub dashboard lives in an S3 bucket (not locally), accessible via CloudFront. The actual file path is:
s3://[maintenance-hub-bucket]/index.html
Downloaded locally to /tmp/maintenance_index.html for editing. The file is a single-page application (SPA) with embedded CSS and JavaScript—no build step, no bundler. This is a deliberate choice for simplicity and fast deployments.
The Total Tasks Card HTML
The stat card was initially a static <div> with class info-card:
<div class="info-card">
<h3>Total Tasks</h3>
<p class="stat-value">82</p>
</div>
This rendered as a styled box but had no interactivity. The issue: no onclick handler and no visual affordance (cursor, hover state) suggesting it was clickable.
Tab Navigation Logic
The dashboard uses a simple tab-switching function already in the codebase:
function switchTab(tabName) {
// Hide all tabs
document.querySelectorAll('[data-tab]').forEach(el => {
el.classList.remove('active');
});
// Show selected tab
document.querySelector('[data-tab="' + tabName + '"]').classList.add('active');
}
The Tasks table is rendered inside a tab with data-tab="systems". Calling switchTab('systems') activates that tab and hides others via CSS class toggling.
The Solution
Three changes were needed:
- Add onclick handler: Wrap the card or add
onclick="switchTab('systems')"directly. - Add cursor styling: CSS class
info-card-linkwithcursor: pointerto signal interactivity. - Add hover state: Visual feedback when users hover, using
transform: scale(1.02)or shadow elevation.
The updated card:
<div class="info-card info-card-link" onclick="switchTab('systems')">
<h3>Total Tasks</h3>
<p class="stat-value">82</p>
</div>
New CSS added to the <style> block:
.info-card-link {
cursor: pointer;
transition: all 0.2s ease;
}
.info-card-link:hover {
transform: scale(1.02);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
}
This leverages the existing transition property on .info-card for smooth animation.
Infrastructure and Deployment
S3 Bucket Setup
The maintenance hub is hosted in S3 with public read access for CloudFront. The bucket is configured for static website hosting with index.html as the default document.
CloudFront Distribution
A CloudFront distribution sits in front of the S3 bucket, caching index.html with a default TTL (likely 24–86400 seconds depending on configuration). This provides geographic distribution and fast edge delivery.
Critical step: After uploading the modified index.html to S3, the CloudFront cache must be invalidated. Otherwise, users see the old version for up to 24 hours.
Invalidation command:
aws cloudfront create-invalidation \
--distribution-id [DIST_ID] \
--paths "/index.html"
The distribution ID is a string like E1A2B3C4D5E6F. This was identified by listing all CloudFront distributions and matching the origin domain to the S3 bucket name.
Deployment Workflow
- Download: Pull
index.htmlfrom S3 to local environment. - Edit: Modify HTML and CSS in a text editor or IDE.
- Test locally: Open the file in a browser to validate the click handler and CSS.
- Upload: Push the modified file back to S3 using the AWS CLI or console.
- Invalidate: Clear the CloudFront cache for that object.
- Verify: Check the live site (e.g.,
maintenance.sailjada.com) to confirm the change is live.
Key Decisions
Why onclick Instead of Event Listeners?
In a small, embedded JavaScript context (no framework), inline onclick is acceptable and easier to reason about than adding event listeners dynamically. For larger SPAs, event delegation would be preferred, but the maintenance hub is kept intentionally simple.
Why Transform Scale for Hover?
transform: scale(1.02) is GPU-accelerated and smooth, unlike width or height` changes, which trigger reflow. A 2% scale is subtle enough to feel polished without being jarring. The shadow elevation adds depth and reinforces the "clickable" affordance.
Why CloudFront Invalidation?
S3 alone doesn't serve assets with cache headers. CloudFront adds caching at the edge for performance, but this introduces a trade-off: updates aren't immediately visible without invalidation. By invalidating specifically the changed object (not a wildcard), we minimize cache miss penalties and control costs.
What's Next
This pattern can be extended to other stat cards if they should navigate elsewhere:
- Active Maintenance Tasks: Could link to a filtered view or status page.
- Engine Hours: Could link to a haul-out or service schedule tab.
- System Health: Could pop up a drill-down modal or log view.
As interactivity grows, consider moving to a lightweight framework (Vue, React, or htmx) to avoid callback hell and manage