Skip to main content

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: SubPageWrapperLevelScreenDataProviderLevelDataContext.ConsumerStockCategoryPageInnerFixedBar(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 V2 deviceDataV2 endpoint 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

ScenarioBehaviorReference
LoadingProvider returns a centered CustomLoader inside Expanded (height: 90vh) while isLoadingLevelScreenDataProvider.jsx
Empty / no dataWhen not loading and levelData has no keys, renders GenericInfo (dataNotFound lottie, "Data Not Found")LevelScreenDataProvider.jsx
Offline unitTank opacity drops to 0.5; online badge replaced with Last Update: {lastUpdatedAt ?? '--'}stock.jsx
Offline categoryCategory wave/border forced to grey #B0B0B0; opacity 0.5stock.jsx
Null current valueUnit value rendered as unit.value1?.toFixed(1) ?? '--'; percentage uses unit.value1 ?? 0stock.jsx
Division by zero (maxCapacity=0)Percentage can be Infinity/NaN; guard with maxCapacity > 0 ? ... : 0 (see Troubleshooting)stock.jsx
Missing stockCategoryTypeFalls back to [] → default colors (#46B2D9 / #BDE4F3)stock.jsx
Category total capacityOn category-tank click, maxCapacity is recomputed as the rounded sum of unit capacitiesstock.jsx
Deep-link auto-open?unitId=…&showGraph=true auto-opens that unit's graph on mountstock.jsx
navDate/navType cleanupnavDate/navType query params are stripped from the URL after mountstock.jsx
Refresh during date changeInterval refresh re-runs init(); changing params resets the effect (loader shown)LevelScreenDataProvider.jsx
UWMS / Rainwater contextrecycledOnly (UWMS) / rawOnly (Rainwater) filters visible units; a ContextViewingBanner is shownstock.jsx
Permission deniedSidebar entry renders locked and routes to /feature_locked/MONITORING/STOCKsee Visibility Parameters

Dependencies

Shared store / services

  • AppStoreContextloginData.services, constantDate, isFromRainwater, isFromUwms, isUwmsContextActive, isRwiNativeApp, setWaterContext
  • MonitoringStoreContext — 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 / getUnitGranularData
  • MonitoringDataSource (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

  • react 19, 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.isPermitted returns true for WATER_MONITORING unconditionally (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_PAGE specifies no explicit access, so it defaults to LOCKED — shown locked when the permission is missing; super users bypass (navHelperInstance.js).
  • LOCKED items route to their lockedPath (/feature_locked/MONITORING/STOCKFeatureLockedProductPage, apps/*/routes.js feature_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).



Last Updated: July 2026 Module Location: libs/monitoring/src/pages/stock/