Making the JADA Maintenance Hub Dashboard Interactive: Clickable Task Cards and Tab Navigation
During this development session, we tackled a critical usability issue on the JADA Maintenance Hub: the "Total Tasks" card displayed a count but wasn't actionable. Users had to manually navigate to the Tasks tab to see their work queue. This post documents how we made the card clickable, wired it to tab navigation, and deployed the changes across our S3 + CloudFront infrastructure in under an hour.
The Problem: Passive Dashboard Cards
The maintenance hub dashboard (hosted at maintenance.jada.queenofsandiego.com) displays key metrics in card widgets, including engine hours, haul-out status, and total task count. The "Total Tasks 82" card was purely informational—clicking it did nothing. Users expecting that card to be a navigation shortcut to their task list had to scroll down and click the Tasks tab manually. For a cockpit-focused tool where crews are often standing or moving, removing friction matters.
Technical Details: Card Clickability and Tab Switching
Our maintenance hub uses vanilla JavaScript tab navigation with a switchTab(tabName) function that manages visibility and state. The challenge was identifying where the task table lived (spoiler: the Systems tab, not Tasks) and linking the card to the correct function call.
Step 1: Locating the File and Understanding Tab Structure
The maintenance hub's HTML lives in an S3 bucket (not in our local repository). We downloaded it directly:
aws s3 cp s3://[maintenance-bucket]/index.html ./maintenance_index.html
Inspecting the file revealed the tab navigation structure. Each tab is defined with a data-tab attribute, and the switchTab() function uses this to toggle visibility. We searched for the task rendering logic and found it populated in the Systems tab—not a dedicated Tasks tab. This is an important detail: the task list is contextual within the broader systems maintenance view.
Step 2: Identifying the Total Tasks Card
The card markup followed a consistent pattern:
<div class="info-card">
<div class="card-label">Total Tasks</div>
<div class="card-value">82</div>
</div>
The card was static HTML with no event handlers. We needed to:
- Wrap the card in a clickable element (or add
onclickdirectly) - Add CSS to indicate interactivity (cursor, hover state)
- Wire the click to
switchTab('systems')
Step 3: Implementation
We added an info-card-link CSS class that applies a pointer cursor and subtle hover background change—visual feedback that the card is interactive without cluttering the design:
.info-card-link {
cursor: pointer;
transition: background-color 0.2s ease;
}
.info-card-link:hover {
background-color: rgba(0, 0, 0, 0.05);
}
Then we updated the HTML markup:
<div class="info-card info-card-link" onclick="switchTab('systems')">
<div class="card-label">Total Tasks</div>
<div class="card-value">82</div>
</div>
The switchTab('systems') call uses the existing tab management function, ensuring consistency with other navigation patterns in the app. This is a zero-risk change—we're not introducing new state logic, just connecting an existing function to a new trigger point.
Infrastructure: S3 and CloudFront Deployment
Once the HTML was updated, we faced a deployment question: how do we get this live without downtime? The maintenance hub is served through CloudFront (a CDN) in front of S3, which adds a layer of caching complexity.
S3 Upload
We uploaded the updated index.html directly to the S3 bucket:
aws s3 cp ./maintenance_index.html s3://[maintenance-bucket]/index.html --acl public-read
The --acl public-read flag ensures the object is readable by CloudFront's origin access policies.
CloudFront Cache Invalidation
S3 is now updated, but CloudFront still serves the old cached version. We needed to invalidate the cache path:
aws cloudfront create-invalidation --distribution-id [MAINTENANCE_DIST_ID] --paths "/*"
This tells CloudFront to drop its cached version of all objects under the maintenance subdomain. The next request fetches the fresh version from S3. Invalidation is nearly instantaneous (typically propagates within 1–5 seconds across CloudFront's edge locations).
Why not just update S3? Without invalidation, users hitting CloudFront's edge servers would still receive the old cached HTML until the object's TTL (Time To Live) expired. For a change like this—where we want immediate user impact—invalidation is essential.
Why This Approach?
We chose direct S3 + CloudFront over a redeployment from source control because:
- Speed: This change had no code dependencies; it was pure HTML/CSS. Pushing through CI/CD would add 5–10 minutes of unnecessary overhead.
- Minimal risk: We're not touching logic, only adding an onclick handler to an existing element. No JavaScript function changes, no state mutations.
- Separation of concerns: The maintenance hub's HTML lives in S3 because it's a static asset. Our Google Apps Script projects (JadaLedger.gs, ExpenseTracker.gs) handle dynamic data. Keeping them separate reduces coupling.
- Traceability: CloudFront invalidation logs are queryable; we have a clear audit trail of when the change went live.
Testing and Verification
After invalidation propagated, we verified the change by:
- Hard-refreshing the maintenance hub dashboard (
Cmd+Shift+Ron macOS to bypass local cache) - Confirming the "Total Tasks" card now shows a pointer cursor on hover
- Clicking the card and verifying the Systems tab becomes active and the task table is visible
The change was live within 30 seconds of the invalidation request.
What's Next
This pattern—making dashboard cards actionable shortcuts—could extend to other cards (e.g., "Engine Hours" → Systems tab with engine-specific section, "Haul-Out Status" → Calendar tab). We should also consider adding keyboard navigation (e.g., Tab and Enter keys) to make the cards accessible to users on screen readers.
The lesson here is architectural: when serving static assets from S3 behind CloudFront, keep the invalidation command close at hand. It's the fastest way to push updates to production without triggering a full CI/CD pipeline.