Dashboard
The app's landing page — a single-fetch snapshot of the facility's water system for a chosen day, laid out as cards. Almost every card is also a shortcut that deep-links into the matching monitoring page.
At a glance
| Route | /dashboard → DashboardPage (libs/dashboard/src/DashboardPage.jsx) |
| Permission | LANDING_PAGE gates the sidebar entry; the route itself is not wrapped |
| Variant split | UWI_DASHBOARD — see below |
| Library | libs/dashboard/ — standard dataSource → controller → store → components |
| Main endpoint | GET /landingPage/userData — feeds every standard card |
| Extra endpoints | GET uwms/stats (STP section only) · mark-alert-read via the shared alerts controller |
| Refresh | Every 5 min, unconditionally |
| Date control | No date picker — a Today / Yesterday toggle |
| Charts | recharts donut + shared PieChartHover, DashboardLineGraph, WaterTank + a hand-rolled SVG gauge |
- It does not fetch per card. One
/landingPage/userDataresponse feeds every standard card, sliced client-side. Only the STP section fetches separately. - It does not offer a date picker. There is only a Today / Yesterday toggle, and Yesterday reverts itself after 60 seconds.
- It does not distinguish a backend failure from an unmapped account — both render
PageNotFound. - A "Not Available" card is not a permission failure; it means the service is absent from your login data.
- The
UWI_DASHBOARDvariant does not use a different endpoint./deviceDataV2belongs tolibs/uwi, a different feature.
The page renders BuildDashboardPage inside a negated PermissionWrapper and
BuildUWIDashboardPage inside a positive one, both keyed on UWI_DASHBOARD. So
users with that permission get the slimmer UWI dashboard and everyone else
gets the standard one. UWI_DASHBOARD is excluded from the super-user auto-grant,
so even super users see the standard view unless it is explicitly assigned.
One fetch, many cards
Summary: One call feeds every standard card, sliced client-side. Only the STP section fetches separately. The alert list is a controller-side merge of the general and offline "today" buckets, not a separate request.
What each card renders
| Card | Reads | Service gate | Empty copy | On click |
|---|---|---|---|---|
| Water Intake | graphData.intake | SOURCE_CATEGORY | "No Intake" | Source monitoring; a legend row opens that unit |
| Water Used | graphData.consumption | SOURCE_CATEGORY | "No Consumption" | Source monitoring; a legend row opens that unit |
| Water Recycled | graphData.recycledWater | SOURCE_CATEGORY | "No Recycled Water" | Source monitoring; a legend row opens that unit |
| Real-Time Water Graph | graphData (whole) | receives checkServiceExist | — | — |
| Water Stock | graphData via StockSummary | STOCK_CATEGORY | — | Stock monitoring |
| Water Quality Index | graphData.quality | QUALITY_CATEGORY | — | Quality monitoring |
| Energy Consumption | graphData.energy | ENERGY_CATEGORY | — | Energy monitoring (energy nav helper) |
| Groundwater Level | store | GROUND_WATER_LEVEL | "No Data Found" | Ground-water monitoring |
| Rainfall | store | RWI permission, not a service | — | /rwi |
| STP | uwms/stats | — (hides itself if empty) | — | UWMS plant view — same tab |
| Alerts | dashboardData.formattedAlertsArray | — | "No alerts found!" | Per-category monitoring deep link and marks the alert read |
| Insights | dummy data | demo builds only (constants.isDemo) | — | — |
The three pie cards share one "View Breakup" / "Collapse Breakup" toggle — it expands all three at once.
Standard layout, region by region
- Left column
- A bordered group holding Water Intake · Water Used · Water Recycled plus the shared breakup toggle
- Real-Time Water Graph (≈63%) beside Water Stock (≈32%)
- Water Quality Index · Energy Consumption · a stacked pair of Groundwater and Rainfall
- The STP section
- Right rail (fixed 300 px from
lgup){Today|Yesterday}'s Alerts- Insights — only in demo builds
UWI dashboard variant
Driven by the same /landingPage/userData response, using UWI-specific
components (DashboardUWIGraphSummary, DashboardUWIQualityDetails,
DashboardUWIEnergyCard): Real-Time Water Graph, a groundwater strip, current
water quality, energy, Stock, {day}'s Intake, {day}'s Consumption, and Alerts.
The UWI dashboard does not hit /deviceDataV2 — that endpoint belongs to
libs/uwi, a different feature.
Card availability
Two independent mechanisms decide what a card shows:
Summary:
PermissionWrapperandcheckServiceExistare separate gates. A missing service produces a sales prompt; a missing permission produces a locked card. Neither is an error state.
| Mechanism | How it works | Result when it fails |
|---|---|---|
PermissionWrapper | Splits the whole page on UWI_DASHBOARD; wraps the Rainfall card on RWI | The negated branch renders — for Rainfall, a dedicated locked card |
checkServiceExist(id) | loginData.services.some(s => s.standardCategoryId === id) | useContactSales renders "Not Available" + a "Get More Details" link and the contact-sales overlay |
Day selection
There is no date picker. A single switcher drives params.date.
Summary: Yesterday is deliberately temporary — a 60-second countdown snaps the page back to Today, and the countdown restarts whenever a refresh lands.
The header reads Viewing {Today's|Yesterday's} Summary (ddd, MMM D); the
countdown carries the tooltip "Automatically reverts to today's summary". Every
switch sets isLoadingGraphData, which dims the page.
Data & APIs
| Action | Method | Path | Trigger |
|---|---|---|---|
| Dashboard data | GET | /landingPage/userData | Mount, day switch, and every 5 min |
| STP stats | GET | uwms/stats | The STP section, on mount / day change |
| Mark alert read | — | shared alerts controller (updateNotificationRead) | Clicking a dashboard alert |
Request params and how the response is sliced
Params sent on every dashboard fetch:
| Param | Value |
|---|---|
date | DD/MM/YYYY |
targetIndustryId | industryId from the stored login response |
alertsEnabled | true |
pastThreeMonthsEnabled | false — and the day switch re-sets it to false |
summaryData | true |
type | MONTH |
How the store maps the response:
| Store field | Source |
|---|---|
dashboardData | the whole data object |
graphData | data.summaryData — the source of nearly every card |
siUnit | data.siUnit.SOURCE_CATEGORY |
monthsData | data.pastThreeMonths.cardData |
formattedAlertsArray | injected by the controller: the general + offline today alert arrays concatenated |
isYesterday | derived by comparing params.date with yesterday |
graphData keys consumed by cards: intake, consumption, recycledWater,
quality, energy.
Alert click routing
Each row routes by standardCategoryId, always carrying the alert's own date
(converted from DD-MM-YYYY to DD/MM/YYYY) and opening in a new tab:
| Category | Path |
|---|---|
SOURCE_CATEGORY | /monitoring/source_category/{categoryId}/{subCategoryId}/{unitId} |
QUALITY_CATEGORY | /monitoring/quality_category/{categoryId}/?unitId={unitId} plus ¶m= when meta.param exists |
| anything else | /monitoring/{standardCategoryId lowercased}/{categoryId}/?unitId={unitId} |
After navigating it calls updateNotificationRead with { id, date: date_key, type }.
The card header's icon opens /alerts instead. See Alerts.
Analytics
| Event | Payload |
|---|---|
PAGE_VIEW | — |
DASHBOARD_DATE_CHANGE | dashboardTrendGraphType: Today or Yesterday |
DASHBOARD_CARD_CLICK | page: the GraphCardType (Intake, Consumption, Recycled, Rainwater, Quality, Stock, Borewell) |
DASHBOARD_DETAILS_CLICK | fired from a legend/unit row |
DASHBOARD_ALERT_CLICK | categoryId, clickedTime, alertType, importanceLevel |
LEAKAGE_LEARN_MORE_CLICK | — |
Card internals worth knowing
Water Quality Index
| Health score | round(safe / (safe + unSafe) * 100), or 0 when there are no parameters |
| Bands | 100 Excellent · ≥ 80 Good · ≥ 60 Fair · below that Poor |
| Gauge | Hand-rolled inline SVG — a 70 px circle, 8 px stroke, rotated -90deg, progress via strokeDashoffset. No chart library |
| Unit ordering | Units with breached parameters first, then offline units before online ones |
| Legend | Within Safe Range · Crossed Safe Range · Offline |
Energy Consumption
Total (with siUnit, defaulting to kWh), a daily average, and a recharts
donut built from only the top three entries of energy.subCategoryTotal.
STP section
Three tiles from uwms/stats — Water Treated (water_treated_today),
Energy Consumed (energy_consumed_today) and Total Energy Saved
(total_energy_saved) — each with a trend arrow from its matching
*_change_percent. The card also carries an "Energy Savings Formula" explainer
(actual power derived from energy over time, energy saved as the gap against a
reference power). If the fetch returns nothing, the whole section unmounts.
Leakage banner
When loginData.leakageEnabled is set, a green "Water Loss Detection enabled."
strip appears under the title on md+ screens; "Learn more" opens
LeakageDetectionDialog.
Render states
| State | What the user sees |
|---|---|
| First load | Centred loader at 80vh |
| Mid-refresh | Backdrop loader, the page greyscaled to 70% with pointer events disabled |
No data, or needsMapping set | The whole page falls back to PageNotFound |
| Fetch failed | Same as above — the data source swallows the error and logs it |
| Per card | Empty copy, "Not Available" sales prompt, or a locked variant (see above) |
| STP with no data | The section removes itself |
| Small screens | Cards stack; search moves below the title |
Visibility & access
Which apps register the route
/dashboard → DashboardPage. The page itself is app-agnostic; the variant is
chosen by permission rather than by app.
Route-level gating: none
The route element is unwrapped. LANDING_PAGE gates only the sidebar entry, so
the URL renders for anyone who reaches it.
The four independent gates
| Gate | Mechanism | Effect |
|---|---|---|
| Sidebar entry | LANDING_PAGE permission | Hides the nav item |
| Page variant | PermissionWrapper on UWI_DASHBOARD, once negated and once not | Standard vs UWI layout |
| Rainfall card | PermissionWrapper on RWI | Falls back to a dedicated locked card |
| Per-card data | checkServiceExist(category) against loginData.services | "Not Available" + contact-sales prompt |
UWI_DASHBOARD is excluded from the super-user auto-grant. Subscription expiry is
handled by the global overlay described in Permissions.
Dependencies
Shared store & services
| Import | Used for |
|---|---|
AppStore | loginData (services, leakageEnabled), setMenuIconEnabled, setSelectedCategory |
apiClient · Urls | /landingPage/userData |
AlertsController | updateNotificationRead when an alert row is clicked |
AlertsHelper | Resolving the general / offline response keys the controller merges |
UwiDashboardController | uwms/stats for the STP section |
AnalyticsService · AnalyticEvents | The six dashboard events |
NavigationHelper · EnergyNavigationHelper · UwmsNavigationHelper | Card click-through |
useNavigateSearchParams | New-tab navigation carrying the selected date |
LocalDBInstance · LocalDBKeys | industryId for the request params |
constants | refreshDuration, isDemo |
Component library
SubPageWrapper, FixedBar, Container, If, Expanded, CustomLoader,
BackdropLoader, PageNotFound, GenericInfo, useContactSales +
ContactSalesOverlay, PermissionWrapper, SearchComponent,
LeakageDetectionDialog, PieChartHover, DashboardLineGraph, WaterTank.
Lib-internal
DashboardDataContext / DashboardDataProvider, DashboardEnum (card types, pie
palettes, quality legend, day enum), and the Build* / Dashboard* card components.
External npm
recharts (the energy donut only), moment, lodash, @mui/material,
@iconify/react. The quality gauge is hand-rolled inline SVG with no chart
library; empty states use Lottie animations through GenericInfo.
Usage examples
Read a card's slice of the single response
import { useContext } from 'react';
import { DashboardDataContext } from '@aquagen-mf-webapp/dashboard/store/DashboardStore';
function IntakeTotal() {
const { graphData, siUnit, isLoading } = useContext(DashboardDataContext);
if (isLoading) return null;
// every standard card reads from graphData, which is response.data.summaryData
return <span>{graphData.intake?.total} {siUnit}</span>;
}
Switch the day, the way the header does
const { params, setParams, isYesterday, setIsLoadingGraphData } = useContext(
DashboardDataContext
);
import { DashboardEnum } from '@aquagen-mf-webapp/dashboard/enum/dashboardEnums';
setIsLoadingGraphData(true); // dims the page while the refetch lands
setParams({
...params,
date: isYesterday
? DashboardEnum.GraphDateType.TODAY.date
: DashboardEnum.GraphDateType.YESTERDAY.date,
pastThreeMonthsEnabled: false,
alertsEnabled: true,
});
Check whether a card should render its data
import { StandardCategoryTypeUppercase } from '@aquagen-mf-webapp/shared/enums';
const checkServiceExist = (id) =>
appStore.loginData.services.some((s) => s.standardCategoryId === id);
const canShowStock = checkServiceExist(
StandardCategoryTypeUppercase.STOCK_CATEGORY
);
Fire the dashboard analytics events
AnalyticsService.sendEvent(AnalyticEvents.PAGE_VIEW, {}, true);
AnalyticsService.sendEvent(AnalyticEvents.DASHBOARD_DATE_CHANGE, {
dashboardTrendGraphType: 'Yesterday',
});
AnalyticsService.sendEvent(AnalyticEvents.DASHBOARD_CARD_CLICK, {
page: DashboardEnum.GraphCardType.STOCK,
});
Troubleshooting
The whole page shows "not found" but the account is fine
BuildDashboardPage falls back to PageNotFound when dashboardData is missing
or when dashboardData.needsMapping is set. A swallowed fetch error produces the
identical screen, so check the console for Error Fetching Dashboard Data before
concluding the account is unmapped.
A card says "Not Available" for a customer who has that feature
That is the service gate, not a permission. The category must appear in
loginData.services as standardCategoryId. PermissionWrapper is not involved.
The UWI Consumption card is permanently locked
Known defect — the UWI layout reuses the shared PieChartSummary without passing
hasPermission, and the component treats falsy as "no access".
The page keeps reloading while I read yesterday's data
The 5-minute interval has no date check, so it refetches regardless of the selected day. Yesterday additionally auto-reverts after 60 seconds.
Dates look wrong on a long-lived tab
GraphDateType.TODAY.date and .YESTERDAY.date are evaluated once when the enum
module loads. A tab open across midnight compares against a stale date.
The STP section vanished
It unmounts itself when uwms/stats returns nothing — that is deliberate, not a
failure.
The page crashed during load
The store reads data.pastThreeMonths.cardData and data.siUnit.SOURCE_CATEGORY
without optional chaining, while always requesting pastThreeMonthsEnabled: false.
A response omitting either block throws inside init.
Known issues & gotchas
| Issue | Where | Impact |
|---|---|---|
| Unguarded response access | The store reads data.pastThreeMonths.cardData and data.siUnit.SOURCE_CATEGORY with no optional chaining — while pastThreeMonthsEnabled is always sent as false | If the backend omits either block the store throws during init, taking the page down |
| Unguarded session parse | params is initialised with JSON.parse(loginResponse).industryId at provider construction | Throws if the session is missing rather than redirecting |
| UWI Consumption card is always locked | The UWI page reuses the shared PieChartSummary but never passes hasPermission, and the component treats falsy as "no access" | That card permanently renders the "Not Available" sales state |
| "Today" is frozen at page load | GraphDateType.TODAY.date / .YESTERDAY.date are computed once when the enum module is evaluated | A tab left open across midnight compares against a stale date |
| The 5-minute refresh is unconditional | The store's interval has no date check | It keeps refetching even while you are looking at Yesterday — unlike Water Balance, which only polls on today |
| Errors collapse into "not found" | The data source catches and logs | A backend failure is indistinguishable from an unmapped account |
| Misleading affordance | DashboardUWIEnergyCard sets cursor: pointer but defines no click handler | Looks clickable, does nothing |
| Dead code | DashboardSummaryCards.jsx is never imported; the store exposes activePieIndex that nothing consumes; DashboardEnum.DatePickerType is unused and its month entry has a datepPickerType typo | Noise when navigating the lib |
| Fragile double mapping | checkServiceExist re-maps its argument through StandardCategoryTypeUppercase, and callers already pass a mapped value | Harmless only because that enum's keys equal its values — a non-identity entry would silently break gating |
Code reference
| File | Role |
|---|---|
DashboardPage.jsx | Shell, provider, header, leakage banner, day switcher, variant split |
dataSource/dashboard.js | The /landingPage/userData call |
controller/dashboardController.js | Merges the general + offline today alerts |
store/DashboardStore.js | Context state, params, 5-min interval |
enum/dashboardEnums.js | Card types, pie palettes, quality legend, day enum |
components/BuildDashboardPage.jsx | Standard layout + service gating |
components/BuildUWIDashboardPage.jsx | UWI layout |
components/DashboardGraphSummary.jsx | PieChartSummary + StockSummary, incl. the locked state |
components/DashboardQualityDetails.jsx | Health-score gauge + unit rows |
components/DashboardEnergyCard.jsx | Totals + recharts donut |
components/DashboardTrendGraph.jsx | Real-time line trend |
components/DashboardGroundwaterCard.jsx · DashboardRainwaterCard.jsx | The stacked pair |
components/DashboardUWMSSection.jsx | STP tiles from uwms/stats |
components/DashboardAlertList.jsx | Right-rail alerts + click routing |
components/DashboardInsightsList.jsx | Demo-only insights |
Related
- Water Monitoring · Water Quality · Water Stock · Ground Water Level — where the cards link
- Alerts & Notifications — the right-rail list and the mark-read call
- Energy Monitoring — the energy card's target
- Water Balance — contrast its conditional refresh with this page's unconditional one
- Application Routes · Permissions