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 graphs for detailed stock variation analysis.


Overview

The Water Stock Levels feature provides real-time visualization of water storage tanks across multiple categories and units, with animated water tank visuals, capacity calculations, and interactive graphs showing stock variations over time.

Location: libs/monitoring/src/pages/stock/

Route: /monitoring/stock/:categoryId

Permission Required: WATER_MONITORING


Key Features

1. Visual Water Tank Display

Animated water tank visualization with:

Tank Components:

  • Animated Wave - Realistic water wave animation
  • Percentage Fill - Visual fill level based on current/max capacity
  • Current Level - Numeric reading with SI unit
  • Capacity Label - Maximum tank capacity

Color Coding by Water Type:

  • Raw Water - Blue (#40BFEF wave, #BDE4F3 border)
  • Recycled Water - Purple (#C289E8 wave, #E4BBFF border)
  • Mixed - Gradient combining both colors based on ratio
  • Offline - Grey (#B0B0B0)

2. Raw vs Recycled Water Tracking

Each tank/category can contain:

  • Raw Water Only - Source water (blue tanks)
  • Recycled Water Only - Treated/reused water (purple tanks)
  • Mixed - Combination with gradient visualization

Configuration:

unit.meta = {
stockCategoryType: [
'ID_RAW_WATER_STOCK', // Raw water
'ID_RECYCLED_WATER_STOCK', // Recycled water
],
maxCapacity: 1000, // kL or L
};

3. Multi-Level Hierarchy

Category View:

  • Summary tank showing total across all units
  • Tank count badge
  • Total stock value
  • Aggregate capacity
  • Click to view detailed graph

Unit View:

  • Individual tank for each unit
  • Current stock level
  • Online/offline status
  • Last update timestamp
  • Click to view unit graph

4. Interactive Stock Graphs

Click on any tank to open a detailed dialog showing:

  • Historical stock variation graph
  • Date picker for time range selection
  • Granular data visualization
  • Export options

5. Date-Based Analysis

Date Picker:

  • Single day selection
  • Today button for quick access
  • Persistent date selection
  • Real-time updates for selected date

Architecture

Data Flow

Key Components

1. LevelScreenDataProvider

  • Purpose: State management for stock levels
  • Location: libs/monitoring/src/dataProvider/LevelScreenDataProvider.jsx
  • State Variables:
    • levelData - Stock level data with totals
    • params - Query parameters (date, category)
    • isLoading - Loading state
    • levelGraphData - Granular graph data for selected tank

Context Value:

{
levelData: {
data: {
total1: 2450.5, // Total stock across all units
subCategories: [
{
id: 'CAT_1',
displayName: 'Overhead Tanks',
online: true,
total1: 1200.0, // Category total
units: [
{
unitId: 'UNIT_1',
displayName: 'OHT 1',
value1: 800.5, // Current stock
online: true,
lastUpdatedAt: '25/02/2026 10:30 AM',
meta: {
maxCapacity: 1000,
stockCategoryType: ['ID_RAW_WATER_STOCK']
}
},
{
unitId: 'UNIT_2',
displayName: 'OHT 2',
value1: 399.5,
online: true,
meta: {
maxCapacity: 500,
stockCategoryType: ['ID_RECYCLED_WATER_STOCK']
}
}
]
}
]
},
totals: {
totalValue: 2450.5,
totalCapacity: 5000
}
},
params: { date1: '25/02/2026', category: 'STOCK_CATEGORY', type: 'HOUR' },
isLoading: false,
setParams: (params) => {},
getGranularCategoryData: (subCategoryId) => {},
getGranularUnitData: (unitId, date1) => {},
levelGraphData: { ... }
}

2. StockCategoryPage

  • Purpose: Main stock monitoring page
  • Location: libs/monitoring/src/pages/stock/stock.jsx
  • Features:
    • Fixed header with date picker
    • Current stock summary
    • Raw/Recycled water legend
    • Category and unit tanks
    • Stock graph dialog

3. BuildUnitsView

  • Purpose: Render individual unit tanks
  • Features:
    • Animated water tank visualization
    • Color coding by water type
    • Online/offline status
    • Click handler for detailed graph

4. BuildAllCategoryAndUnits

  • Purpose: Render all categories with nested units
  • Features:
    • Category summary tank (left)
    • Horizontal divider with drag handle
    • Scrollable unit list (right)
    • Mixed water gradient support

5. StockGraphDialog

  • Purpose: Modal dialog showing stock variation graph
  • Location: libs/monitoring/src/pages/stock/StockGraphDialog.jsx
  • Features:
    • Date range selector
    • Line/area chart showing stock trends
    • Zoom and pan controls
    • Export to CSV/PNG

6. WaterTank Component

  • Purpose: Reusable animated water tank visualization
  • Location: libs/components/src/waterTank/
  • Props:
    • maxHeight - Tank height in pixels
    • maxWidth - Tank width in pixels
    • percentage - Fill percentage (0-100)
    • siUnit - Unit label (kL, L, m³)
    • waveColor - Water wave color
    • borderWaveColor - Wave border color
    • useGradient - Enable gradient for mixed water
    • rawRatio - Raw water percentage (for gradient)
    • recycledRatio - Recycled water percentage (for gradient)

API Integration

Get Category Data

GET /categoryData
Params: {
category: 'STOCK_CATEGORY',
type: 'HOUR',
date1: '25/02/2026'
}

Response: {
siUnit: 'kL',
total1: 2450.5,
subCategories: [
{
id: 'CAT_1',
displayName: 'Overhead Tanks',
online: true,
total1: 1200.0,
units: [
{
unitId: 'UNIT_1',
displayName: 'OHT 1',
value1: 800.5,
online: true,
lastUpdatedAt: '25/02/2026 10:30 AM',
meta: {
maxCapacity: 1000,
stockCategoryType: ['ID_RAW_WATER_STOCK']
}
}
]
}
]
}

Get Granular Category Data

GET /granular/category
Params: {
date1: '25/02/2026',
categoryId: 'STOCK_CATEGORY',
subCategoryId: 'CAT_1'
}

Response: {
graph: [
{ x: '00:00', y: 750.2 },
{ x: '01:00', y: 755.8 },
{ x: '02:00', y: 760.5 },
// ... hourly data
]
}

Get Granular Unit Data

GET /granular/unit
Params: {
date1: '25/02/2026',
unitId: 'UNIT_1',
summation: false
}

Response: {
graph: [
{ x: '00:00', y: 800.0 },
{ x: '01:00', y: 798.5 },
{ x: '02:00', y: 801.2 },
// ... hourly stock readings
]
}

Usage Examples

1. Initialize Stock Monitoring

import { LevelScreenDataProvider } from '@aquagen-mf-webapp/monitoring';

function StockApp() {
const { categoryId } = useParams();

return (
<LevelScreenDataProvider categoryId={categoryId}>
<StockContent />
</LevelScreenDataProvider>
);
}

2. Access Stock Data

import { useContext } from 'react';
import { LevelDataContext } from '@aquagen-mf-webapp/monitoring';

function StockSummary() {
const levelStore = useContext(LevelDataContext);
const { levelData } = levelStore;

const totalStock = levelData?.data?.total1?.toFixed(2);
const totalCapacity = levelData?.totals?.totalCapacity;

return (
<div>
<h3>Current Stock: {totalStock} kL</h3>
<h4>Total Capacity: {totalCapacity} kL</h4>
<p>Fill Level: {((totalStock / totalCapacity) * 100).toFixed(1)}%</p>
</div>
);
}

3. Calculate Water Type Ratios

function calculateWaterTypeRatios(units) {
const totalUnits = units.length;

const rawCount = units.filter((u) => u.meta?.stockCategoryType?.[0] === 'ID_RAW_WATER_STOCK').length;

const recycledCount = units.filter((u) => u.meta?.stockCategoryType?.[0] === 'ID_RECYCLED_WATER_STOCK').length;

return {
isOnlyRaw: rawCount === totalUnits,
isOnlyRecycled: recycledCount === totalUnits,
isMixed: rawCount > 0 && recycledCount > 0,
rawRatio: rawCount / totalUnits,
recycledRatio: recycledCount / totalUnits,
};
}

4. Determine Tank Colors

function getTankColors(unit) {
const type = unit.meta?.stockCategoryType || [];
const isRaw = type.includes('ID_RAW_WATER_STOCK');
const isRecycled = type.includes('ID_RECYCLED_WATER_STOCK');

const waveColor = isRaw
? '#40BFEF' // Blue for raw water
: isRecycled
? '#C289E8' // Purple for recycled
: '#46B2D9'; // Default blue

const borderWaveColor = isRaw
? '#BDE4F3' // Light blue
: isRecycled
? '#E4BBFF' // Light purple
: '#BDE4F3'; // Default light blue

return { waveColor, borderWaveColor };
}

5. Handle Tank Click

function handleTankClick(item) {
if (item.units) {
// Category tank clicked - calculate total capacity
const totalCapacity = item.units.reduce((sum, u) => sum + (u.meta?.maxCapacity || 0), 0);

setSelected({
...item,
meta: { ...item.meta, maxCapacity: totalCapacity },
});
} else {
// Individual unit tank clicked
setSelected(item);
}

// Open graph dialog
}

6. Change Date

function handleDateChange(newDate) {
const levelStore = useContext(LevelDataContext);

levelStore.setIsLoading(true);
levelStore.setParams({
...levelStore.params,
date1: newDate.format('DD/MM/YYYY'),
});
}

function handleTodayClick() {
const today = moment(new Date()).format('DD/MM/YYYY');
handleDateChange(moment(today, 'DD/MM/YYYY'));
}

Visual Components

Water Tank Visualization

<WaterTank
maxHeight={170}
maxWidth={150}
text={`${unit.value1.toFixed(1)} ${siUnit}`}
fontSize={22}
siUnit={siUnit}
maxCapacity={unit.meta.maxCapacity}
percentage={(unit.value1 / unit.meta.maxCapacity) * 100}
percentageFontSize={18}
waveColor="#40BFEF" // Blue for raw water
borderWaveColor="#BDE4F3"
tankReading={false} // Hide additional reading
/>

Mixed Water Gradient Tank

<WaterTank
maxHeight={170}
maxWidth={150}
percentage={75}
useGradient={true}
gradientId={`gradient-${category.id}`}
rawRatio={0.6} // 60% raw water
recycledRatio={0.4} // 40% recycled water
waveColor={undefined} // Not used with gradient
borderWaveColor={undefined} // Not used with gradient
/>

Gradient Definition:

<defs>
<linearGradient id="gradient-CAT_1" x1="0%" y1="100%" x2="0%" y2="0%">
<stop offset="0%" stopColor="#40BFEF" stopOpacity="1" />
<stop offset="60%" stopColor="#40BFEF" stopOpacity="1" />
<stop offset="60%" stopColor="#C289E8" stopOpacity="1" />
<stop offset="100%" stopColor="#C289E8" stopOpacity="1" />
</linearGradient>
</defs>

Calculations

Total Capacity Calculation

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 };
}

Fill Percentage

function calculatePercentage(currentValue, maxCapacity) {
if (!maxCapacity || maxCapacity === 0) return 0;
return ((currentValue || 0) / maxCapacity) * 100;
}

Round to One Decimal

const rounded = Math.round(value * 10) / 10;

Auto-Refresh

Stock data auto-refreshes at configured intervals:

useEffect(() => {
setIsLoading(true);
init();

const interval = setInterval(() => {
init();
}, constants.refreshDuration);

return () => clearInterval(interval);
}, [params]);

Note: Refresh pauses when params change to avoid duplicate loading.


Responsive Design

Desktop (md+):

  • Horizontal layout: Category tank | Divider | Unit tanks
  • Scrollable horizontal unit list
  • Vertical divider with drag handle icon

Mobile (xs-sm):

  • Stacked vertical layout
  • Horizontal dividers between sections
  • Full-width tanks

URL Parameters

Stock page supports navigation parameters:

// From dashboard navigation
/monitoring/stock/STOCK_CATEGORY?navDate=25/02/2026&navType=DAY&unitId=UNIT_1&showGraph=true

// Auto-open graph for specific unit
useEffect(() => {
if (searchParams.has('unitId') && searchParams.get('showGraph') === 'true') {
const unit = units.find(u => u.unitId === searchParams.get('unitId'));
if (unit) handleTankClick(unit);
}
}, []);

// Clean up params after use
useEffect(() => {
if (searchParams.has('navDate') || searchParams.has('navType')) {
const newParams = new URLSearchParams(searchParams);
newParams.delete('navDate');
newParams.delete('navType');
setSearchParams(newParams, { replace: true });
}
}, []);

Water Type Legend

Header displays color legend:

<Box sx={{ display: 'flex', gap: 4, alignItems: 'center' }}>
{/* Raw Water */}
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
<Icon icon="mdi:drop-outline" color="#3BB5E3" width={26} height={26} />
<Typography fontWeight={500}>Raw Water</Typography>
</Box>

{/* Recycled Water */}
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
<Icon icon="tabler:recycle" color="#9901FF" width={26} height={26} />
<Typography fontWeight={500}>Recycled Water</Typography>
</Box>
</Box>

Best Practices

1. Handle Offline Tanks

// Reduce opacity for offline tanks
sx={{ opacity: unit.online ? 1 : 0.5 }}

// Show last update instead of online badge
{unit.online ? (
<OnlineView />
) : (
<Typography fontSize={11} color="#B0B0B0">
Last Update: {unit.lastUpdatedAt ?? '--'}
</Typography>
)}

2. Use Optional Chaining

// Safe access to nested properties
const capacity = unit.meta?.maxCapacity || 0;
const stockType = unit.meta?.stockCategoryType?.[0];

3. Round Capacity Values

// Always round to 1 decimal place
const displayCapacity = Math.round(unit.meta.maxCapacity * 10) / 10;

4. Handle Empty Categories

if (isLoading) return <Loader />;
if (!levelData || !Object.keys(levelData).length) {
return <GenericInfo lottieData={dataNotFound} subTitle="Data Not Found" />;
}

Troubleshooting

Tank Colors Not Showing

Cause: stockCategoryType not set in unit metadata Solution: Check backend configuration, use default color

const type = unit.meta?.stockCategoryType || [];
const waveColor = type.includes('ID_RAW_WATER_STOCK') ? '#40BFEF' : '#46B2D9';

Percentage Calculation Error

Cause: Division by zero (maxCapacity = 0) Solution: Add safety check

const percentage = maxCapacity > 0 ? (currentValue / maxCapacity) * 100 : 0;

Graph Not Opening

Cause: Missing handleTankClick or dialog state Solution: Ensure click handler and dialog state are properly set up

const [selected, setSelected] = useState(null);
const handleTankClick = (item) => setSelected(item);
const handleCloseDialog = () => setSelected(null);

Analytics Integration

import { AnalyticsService } from '@aquagen-mf-webapp/shared/services';
import { AnalyticEvents } from '@aquagen-mf-webapp/shared/enums';

useEffect(() => {
AnalyticsService.sendEvent(AnalyticEvents.PAGE_VIEW, {}, true);
}, []);


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