Skip to main content

Reports

Generate and download water-data reports. Pick a report, configure the date, interval, units, and format, then download or preview. The report itself is rendered by the server — the frontend only ever builds a URL.

At a glance

Route/reportsReportsV2Page + subpages (libs/reportsV2/)
Available inproduction demo
Sidebar gateThe DISABLE_REPORTS permission; individual reports are gated per service and per permission
Route guardNone — the route element is not wrapped
EndpointGET {baseUrl}reportone endpoint for everything
Client librariesNo charting, PDF or Excel library — rendering is entirely server-side
Deliverywindow.open for files, an <iframe> for previews
Unit list sourceYour stored login response, not an API
What Reports does NOT do
  • It does not render anything. No charting, PDF, or spreadsheet library is installed for this feature — the server produces the document.
  • It does not use apiClient. The URL is assembled by hand and handed to window.open or an <iframe>, which is why the JWT travels in the query string.
  • It does not fetch the unit list. Units come from your stored login response.
  • It does not report server errors. Nothing reads the response, so a failure appears as a blank tab or a broken preview.
  • legacyReports is not a fallback — the directory contains no source files.

Which reports library is which

LibraryStatusWhere it runsWhat it is
reportsV2Currentproduction, demoRouted multi-page generator — this page
reportsStandalone appsrwi, uwms, lakepulseOlder single-page tabbed generator against the same backend
legacyReportsDeadNowhereContains no source files at all

The standalone reports lib builds its URL in ReportController.js and opens it the same way, but gates on NOT_GENERAL_REPORT / EXECUTIVE_SUMMARY_REPORT — keys reportsV2 never uses — and adds format=v2 conditionally rather than always.


Three ways in

Summary: Every route converges on one URL builder. The only thing that distinguishes a download from a preview is the reportFormat value.

PathSubpageWhat it does
/reportsReportMainSelectionPageThe chooser — frequent cards, executive cards, "create new"
/reports/frequent_reports/:reportIdFrequentReportsA preset with inline-editable fields
/reports/summary_reportSummaryReportPageDaily / Weekly / Monthly summary — PDF only
/reports/water_balanceWaterBalanceReportPageDaily / Monthly water-balance report — Excel
/reports/new_reportNewReportPageThe four-step wizard

The new-report wizard

Summary: Four steps, each validated before you can continue. Rain Water selects every unit automatically and skips the unit step entirely.

StepChoicesNotes
1. Report typeFlowmeter · Stock · GW Level · Quality · Rain Water · EnergyFiltered by your services; Rain Water additionally needs RWI
2. Time & dateEvery 5 Minutes · Every Hour · Every Day · Every Month, then single or multiple datesEach interval carries its own range cap
3. Categories & unitsSearchable unit picker, or select-allSkipped for Rain Water
4. Format & exportExcel or PDF, plus a live previewPreview goes stale if you change params

A breadcrumb of chips (StepSummaryChips) shows the chosen service, interval, units, and format as you go. Continue runs validateParams, which returns the list of missing field labels and renders them as an inline error — unitIds counts as satisfied when "select all" is on.


Parameter vocabulary

service — the report family
KeyValue
WATERwater
WATER_CONSOLIDATEDconsolidated
QUALITYquality
LEVELlevel
SUMMARYsummary
WATER_BALANCEwater_balance
BOREWELLborewell
RAIN_WATERrain_water
ENERGYenergy
ENERGY_CONSOLIDATEDenergy_consolidated
EXECUTIVE_SUMMARYexecutive
reportType and reportFormat
reportTypeValue
GRANULARgranular
HOURLYhourly
DAILYdaily
DAILY_RANGEdaily_range
MONTHLYmonthly
MONTHLY_RANGEmonthly_range
WEEKLYcustom — the one key whose value doesn't match its name
reportFormatValueBehaviour
EXCELxlsxDownload
PDFpdfDownload
PREVIEWhtmlRendered in an <iframe>

Only Excel and PDF are offered to the user; html is internal to the preview pane.

Interval to report type and range cap
IntervalRange cap (days/months)Single dateMultiple dates
Every 5 Minutes6granular
Every Hour6hourly
Every Day30dailydaily_range
Every Month12monthlymonthly_range
Preset and executive report defaults

Frequent reports

PresetreportTypeFormatDefault rangeExtra gate
Flowmeter ReportgranularExcelToday
Rain Water ReportmonthlyExcelMonth to dateRWI
Water Quality ReportdailyExcelToday
Energy ReportmonthlyExcelWeek to date

Executive reports

ReportserviceFormatMax date
Daily SummarysummaryPDFYesterday
Weekly SummaryexecutivePDFEnd of last week
Monthly SummaryexecutivePDFEnd of last month
Daily Water Balancewater_balanceExcelYesterday
Monthly Water Balancewater_balanceExcelEnd of last month

Note that Daily Summary uses the summary service while Weekly and Monthly use executive — three summary variants across two services.

The Summary card requires DAILY_SUMMARY; the Water Balance card requires WATER_BALANCE.


The one endpoint

buildReportUrl(format) in reportStore.jsx assembles the whole thing client-side — there is no apiClient call anywhere in this feature:

GET {baseUrl}report
?useShift=true
&reportType=<granular|hourly|daily|daily_range|monthly|monthly_range|custom>
&reportFormat=<pdf|xlsx|html>
&service=<water|quality|summary|water_balance|...>
&jwt=<token>
&startDate=<DD/MM/YYYY>
&format=v2
[&endDate=...] [&unitIds=a,b,c] [&startTime=...] [&endTime=...]

The token comes from loginData.token with any Bearer prefix stripped. The four trailing parameters are appended only when present, and unitIds is comma-joined.

The JWT travels in the query string

Because the report is fetched by window.open or an <iframe> rather than through apiClient, the token is passed as a jwt query parameter. It lands in browser history, and in the logs of anything between the browser and the server. The same pattern is used by the alerts export.


Visibility & access

Which apps register the route

AppLibraryShape
production · demoreportsV2/reports with nested subpage routes
rwi · uwms · lakepulsereports/reports as a single tabbed page

Both register /reports/water_balance — in the standalone apps that path is the only water-balance surface, since the diagram is not routed there.

Gating layers

LayerRule
/reports routeUnguarded
Sidebar entryDISABLE_REPORTS
Report type cardsShown when your services include that category
Rain WaterAdditionally requires RWI
Summary cardRequires DAILY_SUMMARY
Water Balance cardRequires WATER_BALANCE
Standalone reports libNOT_GENERAL_REPORT / EXECUTIVE_SUMMARY_REPORT

Cards that declare a permission are checked through PermissionController.isPermitted; the rest fall back to a service check.


Render states

StateWhat the user sees
Missing fieldsContinue is blocked and the missing field labels are listed inline
Rain Water selectedAll units auto-selected, unit step skipped
More than 5 unitsA non-blocking notice that the report will take longer
Preview requestedThe report renders in an <iframe>
Params changed after previewA "refresh preview" affordance appears
DownloadA new tab opens; the file arrives from the server
Server errorSurfaces inside the downloaded file or the iframe — there is no client-side error UI

Dependencies

Shared store & services

ImportUsed for
Urls.baseUrlThe root of the hand-assembled report URL
AppStoreloginData.token for the jwt parameter
PermissionControllerisPermitted for cards that declare a permission
Permissions · StandardCategoryTypeUppercaseGating vocabulary
DatepickerEnumPicker modes per interval
NavigationHelperRouting between the chooser and the report subpages

Notably absent: apiClient. Nothing in this feature makes an XHR — the report is fetched by the browser itself.

Component library

AppDatePickerSelection, plus MUI primitives for the wizard, cards, chips, and the preview frame.

Lib-internal

ReportDataProvider / ReportDataContext (params + buildReportUrl) and ReportHelper, which holds every enum, preset, interval, validation routine, and display helper in one 500-line module.

External npm

moment, @mui/material, @iconify/react. No charting, PDF, or spreadsheet library — rendering is entirely server-side, and delivery is window.open or an <iframe>.


Usage examples

Build a report URL

import { useContext } from 'react';
import { ReportDataContext } from '@aquagen-mf-webapp/reportsV2/store/reportStore';
import { ReportHelper } from '@aquagen-mf-webapp/reportsV2/helper/reportHelper';

function DownloadButton() {
const { buildReportUrl } = useContext(ReportDataContext);

const download = () =>
window.open(buildReportUrl(ReportHelper.ReportFormat.EXCEL), '_blank', 'noreferrer');

const preview = () => buildReportUrl(ReportHelper.ReportFormat.PREVIEW); // 'html'

return <button onClick={download}>Download</button>;
}

Configure params the way a preset does

const { params, setParams } = useContext(ReportDataContext);

setParams({
...params,
serviceType: ReportHelper.ServiceType.WATER, // 'water'
reportType: ReportHelper.ReportType.GRANULAR, // 'granular'
format: ReportHelper.ReportFormat.EXCEL, // 'xlsx'
startDate: '29/07/2026',
endDate: '29/07/2026',
unitIds: ['unit-a', 'unit-b'], // joined with commas in the URL
});

Validate a wizard step

const { selectAllUnits } = useContext(ReportDataContext);

const missing = ReportHelper.validateParams(
params,
['serviceType', 'reportType', 'unitIds', 'format'],
{ selectAllUnits } // 'unitIds' counts as satisfied when select-all is on
);
if (missing.length) return setStepErrors(missing); // array of human labels

Read the breadcrumb display values

ReportHelper.getServiceTypeDisplay(params); // 'Flowmeter'
ReportHelper.getIntervalDisplay(params); // 'Every Day'
ReportHelper.getFormatDisplay(params); // 'Excel'
ReportHelper.getUnitsDisplay(params, selectAllUnits); // 'All Units' or a joined list

Troubleshooting

A blank tab opens and nothing downloads

Nothing in the frontend reads the server's response, so a server-side failure looks exactly like this. Copy the opened URL and inspect the response directly.

getUnitsDisplay throws

It calls JSON.parse(localStorage.getItem('loginResponse')) with no null check, and uses the raw storage key rather than LocalDBInstance. It fails hard when the session is missing.

The Rain Water report skipped the unit step

Intentional — Rain Water auto-selects every unit and bypasses step 3.

A report type is missing from the wizard

Type cards are filtered by your services. Rain Water additionally requires RWI.

The Summary or Water Balance card is absent

They require DAILY_SUMMARY and WATER_BALANCE respectively.

The same report is gated differently in AquaRain

The standalone apps run libs/reports, which gates on NOT_GENERAL_REPORT / EXECUTIVE_SUMMARY_REPORT — keys reportsV2 never uses.

Backend logs show reportType=custom and I never chose that

ReportType.WEEKLY has the value custom. Weekly Summary is the only path that emits it.

The preview looks stale

Changing params after a preview does not re-fetch it; a refresh affordance appears instead.


Known issues & gotchas

Verified against the code
IssueWhereImpact
JWT in the URLbuildReportUrl, and the standalone lib's ReportControllerThe token is exposed in history and intermediary logs
Raw localStorage with a literal keygetUnitsDisplay does JSON.parse(localStorage.getItem('loginResponse')), bypassing LocalDBInstance, with no null checkThrows if the session is absent, and duplicates knowledge of the storage key
Non-aliased deep importreportStore.jsx imports Urls from src/services/api/urls instead of the @aquagen-mf-webapp/shared aliasFragile path that breaks if the shared lib moves
WEEKLY maps to customReportType.WEEKLY = 'custom'Easy to misread when correlating frontend and backend logs
Urls.reportUrl(...) is deadThe helper exists in urls.js but nothing calls it — both live libs build their URL inlineTwo competing sources of truth for the same URL shape
legacyReports is an empty shellThe lib directory contains no source filesDelete candidate; do not build on it
No error surfaceNothing inspects the server's response, because nothing reads itA failed report looks like a blank tab or a broken iframe
Two divergent gating vocabulariesreportsV2 uses services + DAILY_SUMMARY / WATER_BALANCE; reports uses NOT_GENERAL_REPORT / EXECUTIVE_SUMMARY_REPORTThe same product gates differently depending on which app you are in

Code reference

FileRole
ReportsV2Page.jsxLayout shell, <Outlet />, provider
store/reportStore.jsxParams + buildReportUrl
helper/reportHelper.jsEvery enum, preset, interval, validation, and display helper
subpages/ReportMainSelectionPage.jsxThe chooser
subpages/NewReportPage.jsx · NewReportStepContents.jsxThe wizard and its steps
subpages/SummaryReportPage.jsx · WaterBalanceReportPage.jsx · FrequentReports.jsxThe dedicated report pages
components/ReportUnitSelector.jsxUnit picker + the five-unit notice
components/Report{Interval,Format,ServiceType}Selector.jsxField selectors
components/ReportPreviewComponent.jsxThe iframe preview
components/StepSummaryChips.jsxThe breadcrumb chips
libs/reports/src/controller/ReportController.jsThe standalone apps' URL builder