Case Study
MRT Jakarta Dashboard
Apr–May 2026 · Full-stack · Deployed
Overview
PT MRT Jakarta needed a centralized platform to manage 15 stations simultaneously — coordinating incident response, tracking real-time operational data, and enabling different staff roles to act on live information. The existing approach relied on fragmented tools and manual coordination, creating delays in incident escalation and limited visibility across the network.
I designed and built the full platform as a contract engagement: frontend design system, API architecture, authentication, authorization model, real-time data layer, and deployment pipeline — 15 pages shipping as v2.18.0 with 127 Playwright E2E tests passing in CI.
Architecture
Frontend on Vercel (Next.js 15 / React 19), API on Fly.io (Express.js / Node.js), PostgreSQL as the persistent store. Real-time station updates are pushed via Server-Sent Events — the frontend maintains a persistent SSE connection to the API, receiving events as station state changes.
VERCEL
Next.js 15 / React 19
FLY.IO
Express.js / Node.js
POSTGRESQL
Database / Persistent
SSE (real-time) · JWT + Google OAuth · Docker (local dev) · GitHub Actions (CI)
Design System
Built “Operations Terminal” — a purpose-built dark ops-board design system. No component library. Every token, component, and motion primitive was defined from scratch: CSS custom properties for color, typography, and spacing; a component library including OpsCard, StatTile, TerminalBlock, LEDDot, and MonoChip; and a motion grammar (LED pulse, count-up animation, hover transitions) with full reduced-motion support.
The system is portable — this portfolio was built on a variant of it. Design ownership is the claim; the shared tokens between the dashboard and the portfolio are the proof.
Key Challenges
SSE Reconnection Under Load
PROBLEMServer-Sent Events drop silently when the server restarts or the connection times out. Station controllers saw stale data without knowing their connection had dropped.
SOLUTIONImplemented an automatic exponential-backoff reconnection strategy on the client with a visible connection-state indicator in the UI. The API sends a heartbeat event every 15 seconds; the client treats silence beyond 20 seconds as a disconnect and reconnects automatically.
Zero reported stale-data incidents after rollout. Connection health is now visible to the operator at all times.
Multi-role RBAC with Spatie
PROBLEMDifferent staff roles (station controller, supervisor, admin, auditor) needed different views and action sets across the same 15 pages — not just route guards, but per-component permission checks.
SOLUTIONUsed Laravel Spatie Permission on the API to define role-permission pairs, then exposed a permissions payload in the JWT. The Next.js frontend reads the payload on mount and conditionally renders action buttons, form fields, and entire panels based on the decoded permissions — no second round-trip.
Role enforcement is consistent across all 15 pages with a single shared permissions hook. Adding a new role requires one API change and no frontend changes.
Interactive Map with 15+ Concurrent Stations
PROBLEMThe station map needed to show live status for all 15 stations simultaneously — color-coded by incident state — while remaining performant enough to update in real time without full re-renders.
SOLUTIONStation state is held in a Zustand store updated by the SSE stream. The map renders 15 station markers as individual React components keyed by station ID, each subscribing only to its own slice of the store. React renders only the markers whose state changes.
Map updates are immediate and smooth at 60fps. No full page re-renders on station state changes.
Testing
127 Playwright E2E tests cover the full application: authentication flows (JWT + Google OAuth), all 15 pages, incident lifecycle (create → escalate → resolve), RBAC (each role's permitted and forbidden actions), and real-time update reception.
E2E was chosen over unit tests for this domain because the critical behaviors are interaction sequences across multiple pages and real API responses — not isolated functions. The test suite runs on every push via GitHub Actions and the HTML report is published to GitHub Pages.