Water Stock Levels
Visual monitoring of water storage tanks with animated water levels, capacity tracking, raw/recycled water distinction, and clickable tanks that open detailed stock-variation graphs.
Overview
The Water Stock feature (internally the stock_category / STOCK_CATEGORY) renders animated tank visuals per category and unit, computes fill percentages against maxCapacity, distinguishes raw vs recycled water (with a gradient tank for mixed categories), and opens a granular stock graph on click.
Location: libs/monitoring/src/pages/stock/
Route: monitoring/stock_category/:categoryId (nested under the monitoring parent route).
Registered in: production, demo, rwi, uwms. Not registered in lakepulse.
Permission required: the Water Stock sidebar entry is gated by the STOCK_CATEGORY service tag; the parent Monitoring menu is gated by WATER_MONITORING (see Visibility Parameters).
- Code Reference:
apps/production/src/routes/routes.js,apps/demo/src/routes/routes.js,apps/rwi/src/routes/routes.js,apps/uwms/src/routes/routes.js
Key Features
1. Visual Water Tank Display
Animated WaterTank per unit/category showing display name, current value with SI unit, online/offline state, fill percentage, and capacity label.
Color coding by water type (stock.jsx):
- Raw water — wave
#40BFEF, border#BDE4F3 - Recycled water — wave
#C289E8, border#E4BBFF - Default / mixed unit — wave
#46B2D9, border#BDE4F3 - Offline category — wave & border
#B0B0B0(stock.jsx)
2. Raw vs Recycled Tracking
Water type is read from unit.meta.stockCategoryType, compared against the StockCategoryType enum:
// libs/shared/src/enums/categoryType.js
const StockCategoryType = {
RAW: 'ID_RAW_WATER_STOCK',
RECYCLED: 'ID_RECYCLED_WATER_STOCK',
};
const type = unit.meta?.stockCategoryType || [];
const isRaw = type.includes(StockCategoryType.RAW);
const isRecycled = type.includes(StockCategoryType.RECYCLED);
- Code Reference:
stock.jsx,234-237
3. Multi-Level Hierarchy
- Category tank (left) — aggregate
total1, tank count badge, gradient fill for mixed categories, click opens category graph. - Unit tanks (right) — one tank per unit in a horizontally scrollable row; click opens unit graph.
- Code Reference:
stock.jsx(BuildAllCategoryAndUnits,BuildUnitsView)
4. Interactive Stock Graphs
Clicking a tank sets selected and renders StockGraphDialog. Category clicks recompute maxCapacity as the sum of unit capacities before opening.
- Code Reference:
stock.jsx
5. Date-Based Analysis
AppDatePickerSelection (single-day DatepickerEnum.PickerType.DAY) updates params.date1; changing the date sets loading and re-fetches.
- Code Reference:
stock.jsx
Architecture
Data Flow
- Code Reference:
LevelScreenDataProvider.jsx,MonitoringStore.js,dataSource/monitoring.js
Key Components
1. LevelScreenDataProvider (LevelDataContext)
- Location:
libs/monitoring/src/dataProvider/LevelScreenDataProvider.jsx - Props:
categoryId,children - State:
levelData({ data, totals }),params({ date1, category, type: 'HOUR' }),isLoading,levelGraphData - Exposes:
levelData,params,setParams,isLoading,setIsLoading,getGranularCategoryData(subCategoryId),getGranularUnitData(unitId, date1),levelGraphData,setLevelGraphData - Note: the provider renders its own loader and "Data Not Found" state internally before exposing the context (
:128-168).
2. StockCategoryPage (default export named StockCategoryPage)
- Location:
libs/monitoring/src/pages/stock/stock.jsx - Structure:
SubPageWrapper→LevelScreenDataProvider→LevelDataContext.Consumer→StockCategoryPageInner→FixedBar(bar=SubHeadingView)+BuildAllCategoryAndUnits
3. BuildUnitsView — renders each unit tank, online/offline handling, per-unit colors (stock.jsx)
4. BuildAllCategoryAndUnits — category summary tank + scrollable unit list + StockGraphDialog (stock.jsx)
5. StockGraphDialog
- Location:
libs/monitoring/src/pages/stock/StockGraphDialog.jsx - Opened via
selectedCategoryOrUnit,handleClose,mainValue,siUnit
6. WaterTank
- Location:
libs/components/src/waterTank/ - Props include
maxHeight,maxWidth,percentage,siUnit,waveColor,borderWaveColor,useGradient,gradientId,rawRatio,recycledRatio,tankReading
API Integration
Endpoints route through apiClient with keys from libs/shared/src/services/api/urls.js.
Category (level) data
GET deviceData/ // Urls.categoryData (urls.js)
Params: { category: 'STOCK_CATEGORY', type: 'HOUR', date1: '25/02/2026' }
- Note: stock uses
getCategoryData(V1,deviceData/), not the V2deviceDataV2endpoint used by flow (MonitoringStore.js,dataSource/monitoring.js).
Granular category graph
GET /granular/category // Urls.granularCategoryData (urls.js)
Params: { date1, categoryId, subCategoryId }
Granular unit graph
GET /granular/unit // Urls.granularUnitData (urls.js)
Params: { date1, unitId, summation: false }
- Code Reference:
LevelScreenDataProvider.jsx,MonitoringStore.js
Usage Examples
Initialize stock monitoring
import { LevelScreenDataProvider } from '@aquagen-mf-webapp/monitoring';
function StockApp() {
const { categoryId } = useParams();
return (
<LevelScreenDataProvider categoryId={categoryId}>
<StockContent />
</LevelScreenDataProvider>
);
}
Access stock data
import { useContext } from 'react';
import { LevelDataContext } from '@aquagen-mf-webapp/monitoring';
function StockSummary() {
const { levelData } = useContext(LevelDataContext);
const totalStock = levelData?.data?.total1?.toFixed(2);
const totalCapacity = levelData?.totals?.totalCapacity;
// ...
}
Total calculation (as implemented)
// LevelScreenDataProvider.jsx
function calculateTotals(data) {
let totalValue = 0;
let totalCapacity = 0;
data?.subCategories?.forEach((category) => {
category.units.forEach((unit) => {
totalValue += unit.value1 || 0;
totalCapacity += unit['meta']['maxCapacity'] || 0;
});
});
return { totalValue, totalCapacity };
}
Handle tank click
// stock.jsx
const handleTankClick = (item) => {
if (item.units) {
const totalCapacity = calcMaxCapacity(item.units); // sum of unit maxCapacity
setSelected({ ...item, meta: { ...item.meta, maxCapacity: totalCapacity } });
} else {
setSelected(item); // individual unit
}
};
Visual Components
Unit tank
<WaterTank
maxHeight={170}
maxWidth={150}
siUnit={categoryData?.siUnit || ''}
percentage={((unit.value1 ?? 0) / unit.meta.maxCapacity) * 100}
percentageFontSize={18}
waveColor={waveColor} // #40BFEF raw · #C289E8 recycled · #46B2D9 default
borderWaveColor={borderWaveColor}
tankReading={false}
/>
- Code Reference:
stock.jsx
Mixed-water gradient (category tank)
<WaterTank
useGradient={isMixed}
gradientId={`gradient-${category.id}`}
rawRatio={rawCount / totalUnits}
recycledRatio={recycledCount / totalUnits}
waveColor={waveColor} // undefined when isMixed
borderWaveColor={borderWaveColor}
tankReading="category"
/>
- Code Reference:
stock.jsx(isMixed = !isOnlyRaw && !isOnlyRecycled)
Edge Cases
| Scenario | Behavior | Reference |
|---|---|---|
| Loading | Provider returns a centered CustomLoader inside Expanded (height: 90vh) while isLoading | LevelScreenDataProvider.jsx |
| Empty / no data | When not loading and levelData has no keys, renders GenericInfo (dataNotFound lottie, "Data Not Found") | LevelScreenDataProvider.jsx |
| Offline unit | Tank opacity drops to 0.5; online badge replaced with Last Update: {lastUpdatedAt ?? '--'} | stock.jsx |
| Offline category | Category wave/border forced to grey #B0B0B0; opacity 0.5 | stock.jsx |
| Null current value | Unit value rendered as unit.value1?.toFixed(1) ?? '--'; percentage uses unit.value1 ?? 0 | stock.jsx |
| Division by zero (maxCapacity=0) | Percentage can be Infinity/NaN; guard with maxCapacity > 0 ? ... : 0 (see Troubleshooting) | stock.jsx |
| Missing stockCategoryType | Falls back to [] → default colors (#46B2D9 / #BDE4F3) | stock.jsx |
| Category total capacity | On category-tank click, maxCapacity is recomputed as the rounded sum of unit capacities | stock.jsx |
| Deep-link auto-open | ?unitId=…&showGraph=true auto-opens that unit's graph on mount | stock.jsx |
| navDate/navType cleanup | navDate/navType query params are stripped from the URL after mount | stock.jsx |
| Refresh during date change | Interval refresh re-runs init(); changing params resets the effect (loader shown) | LevelScreenDataProvider.jsx |
| UWMS / Rainwater context | recycledOnly (UWMS) / rawOnly (Rainwater) filters visible units; a ContextViewingBanner is shown | stock.jsx |
| Permission denied | Sidebar entry renders locked and routes to /feature_locked/MONITORING/STOCK | see Visibility Parameters |
Dependencies
Shared store / services
AppStoreContext—loginData.services,constantDate,isFromRainwater,isFromUwms,isUwmsContextActive,isRwiNativeApp,setWaterContextMonitoringStoreContext— shared cache +getCategoryData(libs/monitoring/src/store/MonitoringStore.js)apiClient,Urls(@aquagen-mf-webapp/shared/services)AnalyticsService,AnalyticEvents
Controllers / data sources / helpers
MonitoringController.getCategoryData / getCategoryGranularData / getUnitGranularDataMonitoringDataSource(dataSource/monitoring.js)StockHelper(default params,libs/monitoring/src/helper/stockHelper.js)useQueryParams(@aquagen-mf-webapp/shared/hooks/useQueryParams)
Shared enums / assets
StockCategoryType,StandardCategoryTypeUppercase,DatepickerEnum(@aquagen-mf-webapp/shared/enums)assets(colors, lotties:dataNotFound) (@aquagen-mf-webapp/shared/assets/assets)
Component libs (@aquagen-mf-webapp/components)
WaterTank,FixedBar,HighlightView,If/IfNot,OnlineView,SearchComponent,AppDatePickerSelection,GenericInfo,CustomLoader,Expanded,SubPageWrapper
External npm
react19,react-router-dom(useParams,useSearchParams),@mui/material+@mui/icons-material(Add,DragHandle),lodash,moment,@iconify/react
Visibility Parameters
Which apps register the feature: production, demo, rwi, uwms. Not lakepulse.
Permission tags
- Parent Monitoring menu (
METRICS):permissionId: 'WATER_MONITORING'(navHelperInstance.js).PermissionController.isPermittedreturnstrueforWATER_MONITORINGunconditionally (PermissionController.js), so the parent menu is effectively always visible. - Water Stock sidebar entry (
STOCK_PAGE):permissionId: StandardCategoryTypeUppercase.STOCK_CATEGORY→'STOCK_CATEGORY',lockedPath: '/feature_locked/MONITORING/STOCK'(navHelperInstance.js).
How permission is derived: getAllUserPermissions() pushes each loginData.services[].categoryId into the permission list (PermissionController.js). A user whose account has a STOCK_CATEGORY service is therefore permitted for the Water Stock entry.
Sidebar lock state (SidebarAccess)
STOCK_PAGEspecifies no explicitaccess, so it defaults toLOCKED— shown locked when the permission is missing; super users bypass (navHelperInstance.js).LOCKEDitems route to theirlockedPath(/feature_locked/MONITORING/STOCK→FeatureLockedProductPage,apps/*/routes.jsfeature_locked/:pageKey/:featureId).
Super-user exception: SUPER_USER grants every tag except DISABLE_*, ACCOUNT_SETTINGS, and UWI_DASHBOARD (PermissionController.js).
PermissionWrapper usage: PermissionWrapper (libs/components/src/permissionWrapper/PermissionWrapper.jsx) gates children via PermissionController.isPermitted(tag, appStore.loginData, ignoreSuperUser), supporting negate and ignoreSuperUser.
isDemo / subscription: stock pages are not directly gated by constants.isDemo; the nav entry sets isDemoOption: false. Demo behavior comes from the demo app registering the same routes.
Analytics Integration
import { AnalyticsService } from '@aquagen-mf-webapp/shared/services';
import { AnalyticEvents } from '@aquagen-mf-webapp/shared/enums';
// LevelScreenDataProvider.jsx
useEffect(() => {
AnalyticsService.sendEvent(AnalyticEvents.PAGE_VIEW, {}, true);
}, []);
- Event:
PAGE_VIEW(page_view) —libs/shared/src/enums/analyticsEnum.js
Troubleshooting
Tank colors not showing
stockCategoryType is unset; the code falls back to default colors. Verify backend meta.stockCategoryType (stock.jsx).
Percentage shows NaN / Infinity
maxCapacity is 0 or missing. Guard the division:
const percentage = maxCapacity > 0 ? (currentValue / maxCapacity) * 100 : 0;
Graph dialog not opening
selected is not set. Ensure handleTankClick fires and StockGraphDialog is rendered under <If condition={selected}> (stock.jsx).
"Data Not Found" on a valid account
levelData resolved to an empty object for the selected date. Confirm a STOCK_CATEGORY service exists and try another date (LevelScreenDataProvider.jsx).
Related Documentation
- Water Flow Monitoring - Flow consumption tracking
- Water Quality Monitoring - Quality parameter monitoring
- Routes - Monitoring route configuration
- Permissions - Access control model
- API & Services - API integration
Last Updated: July 2026
Module Location: libs/monitoring/src/pages/stock/