Alerts & Notifications
A full-page view of monitoring alerts — list, filter, sort, search, resolve, assign, and download. Alerts split into two tabs: General and Device Status (offline).
- Route:
/alerts/:categoryId→AlertsPage(libs/alerts/src/AlertsPage.jsx) - Permission:
ALERTS(gates the sidebar entry; the route isn't wrapped — see Permissions) - Library:
libs/alerts/is UI only; state/logic live in shared (AlertsStore,alertsController,alertsDataSource)
:categoryIdselects water vs energy alerts — see Energy alerts.
Layout
Summary: Two tabs over the alert types; a left panel (month picker, a pie of this-month stats, a unit filter); and a list grouped into Today and the selected month. Clicking an alert opens the relevant monitoring page and marks it read.
| Area | Component | What it shows | On click |
|---|---|---|---|
| Tabs | AlertsToggleTypeHeader | General / Device Status, each with an unread badge; plus search + sort | Switch tab → refetch; search filters client-side |
| Sort | AlertsSortList | Sort by Date / Unresolved; a "Show Read" toggle | Reorders / reveals read alerts |
| Left panel | AlertsLeftComponent | Month picker, a pie of this-month stats, and a Download button | Month → refetch; Download → export (see below) |
| Filter | AlertFilterComponent | A dialog of units (from your login data) to filter by | Pick units → filters the list |
| List | AlertListComponent | Alerts grouped Today + month | Row → opens monitoring (new tab) + marks read; chevron expands details |
| Row actions | (expanded row, General only) | Resolve and Assign | Resolve → confirm snackbar; Assign → pick a user |
APIs
State lives in the shared AlertsStore; calls go through apiClient:
| Action | Method | Path | Trigger |
|---|---|---|---|
| List alerts | GET | alerts | Page load, tab/month change, after read/assign ({ date, alert_type, energyEnabled }) |
| Today's unread (poll) | GET | alerts | Every 5 min ({ date, type: daily }) |
| Mark read | PATCH | notification/updateRead | Clicking an alert (and inside resolve) |
| Assign to user | POST | /notification/assignee | The ⋮ menu on a general alert |
| Resolve | PATCH | /notification/assignee | Resolve snackbar (assign-to-self + mark read) |
| Download report | GET | report?… (window.open) | The Download button |
Resolving an alert is implemented as assign-to-yourself + mark-read — there is no separate resolve flag. The download opens a report URL with the JWT in the query string.
Analytics: PAGE_VIEW, ALERTS_CLICK, ALERTS_DOWNLOAD, ALERT_ASSIGNED.
Energy alerts
The :categoryId route param chooses which alert domain the page loads. It sets
exactly one store flag — energyEnabled = (categoryId === 'ENERGY_ALERTS') — and
nothing else.
categoryId | Reached from | Loads |
|---|---|---|
WATER_ALERTS (default) | The main / standalone app sidebars (/alerts/WATER_ALERTS) | Water alerts |
ENERGY_ALERTS | The energy monitoring nav (energyNavigationHelper → /alerts/ENERGY_ALERTS) | Energy alerts |
API: it's the same GET alerts endpoint — energyEnabled just rides along
in the params ({ date, alert_type, energyEnabled }). true returns energy alerts,
false water alerts.
What's shown vs. hidden in energy mode — almost nothing differs; the flag only swaps the dataset:
| Aspect | In energy mode |
|---|---|
| Layout, tabs, filters, pie | Identical — same General / Device Status tabs and left panel |
| Alert dataset | Energy alerts (backend swaps on energyEnabled: true) |
| URL persistence | energyEnabled=true is re-applied on every tab switch, so you stay in energy mode |
| Alert click | An energy alert (standardCategoryId = ENERGY_CATEGORY) opens the energy monitoring page (/monitoring/energy_category/…) instead of a water page |
Only the list call passes energyEnabled. The 5-minute today-poll
({ date, type: daily }) and the download (service=alert) do not — so the
tab unread badges and the exported report are not energy-scoped even in energy
mode.
Edge cases & guards
| Case | Handling |
|---|---|
| Loading | A loader in the list and left-panel pie |
| No alerts | "Congratulations! You have no alerts." in the Today section |
| Today section | Only shown when the selected month is the current month |
| No data returned | Falls back to a PageNotFound panel |
| Filter / sort / search | All applied client-side on the fetched month (no pagination) |
| Deep link | ?alertId=<id> auto-expands and scrolls to that alert |
| API failure | ⚠️ The loader can hang — init() has no try/catch, so a failed fetch never clears the spinner |
Underlying libraries
| Purpose | Library |
|---|---|
| Stats pie | PieChartHover (Recharts-based shared component) |
| Date picker | AppDatePickerSelection (month mode) |
| Dates | moment |
| UI / utils | @mui/material (MUI 7), @iconify/react, lodash (debounced search) |
Code reference
| File | Role |
|---|---|
AlertsPage.jsx | Page shell, tabs, search/sort header |
components/AlertsLeftComponent.jsx | Month picker, stats pie, download |
components/AlertTabView.jsx | Layout + resolve snackbar |
components/AlertListComponent.jsx | The grouped list + row-click navigation |
components/AlertFilterComponent.jsx · AlertsSortList.jsx | Unit filter + sort menu |
libs/shared/src/store/AlertsStore.js | Alert state, polling, API actions |
Related
- Water Monitoring — where an alert click lands
- AquaGPT — ask the assistant about your alerts
- Application Routes · Permissions