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 totalsparams- Query parameters (date, category)isLoading- Loading statelevelGraphData- 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 pixelsmaxWidth- Tank width in pixelspercentage- Fill percentage (0-100)siUnit- Unit label (kL, L, m³)waveColor- Water wave colorborderWaveColor- Wave border coloruseGradient- Enable gradient for mixed waterrawRatio- 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);
}, []);
Related Documentation
- Water Flow Monitoring - Flow consumption tracking
- Water Quality Monitoring - Quality parameter monitoring
- Ground Water Level - Borewell level monitoring
- API & Services - API integration
- Components - WaterTank component reference
Last Updated: February 2026
Module Location: libs/monitoring/src/pages/stock/