@burdenoff/microfe-ecoimpacthub 2026.720.1 → 2026.720.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- {"version":3,"file":"MonitoringPage.js","names":[],"sources":["../../../../src/ecoimpacthub/pages/monitoring/MonitoringPage.tsx"],"sourcesContent":["/**\n * Monitoring — a cross-site hub for environmental measurements, sensors &\n * alert thresholds.\n * @module ecoimpacthub/pages/monitoring\n *\n * Reads `ecoListMeasurements`, `ecoListSensors`, `ecoListThresholds` (and\n * `ecoListSites` for name lookups); writes `ecoRecordMeasurement`,\n * `ecoCreateSensor`, `ecoSetSensorStatus`, `ecoCreateThreshold`,\n * `ecoSetThresholdActive`.\n */\n\nimport { useMemo, useState } from 'react';\nimport { Plus, Activity, Radio, Gauge } from 'lucide-react';\nimport {\n Button,\n Input,\n Label,\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n ResponsiveDialog,\n ResponsiveDialogContent,\n ResponsiveDialogHeader,\n ResponsiveDialogTitle,\n ResponsiveDialogDescription,\n ResponsiveDialogFooter,\n} from '@burdenoff/fe-libs/ui';\nimport { PageLayout } from '../../components/PageLayout';\nimport { StatusBadge } from '../../components/StatusBadge';\nimport { DataTable, type Column } from '../../components/DataTable';\nimport { FilterTabs, type FilterOption } from '../../components/FilterTabs';\nimport { Tabs, type TabItem } from '../../components/Tabs';\nimport { LoadingView, ErrorView } from '../../components/StateViews';\nimport { EmptyState } from '../../components/EmptyState';\nimport { Pagination } from '../../components/Pagination';\nimport {\n useEcoSites,\n useEcoMeasurements,\n useEcoRecordMeasurement,\n useEcoSensors,\n useEcoCreateSensor,\n useEcoSetSensorStatus,\n useEcoThresholds,\n useEcoCreateThreshold,\n useEcoSetThresholdActive,\n useCursorPager,\n} from '../../hooks';\nimport { formatDateTime, formatEnumLabel, formatNumber } from '../../utils/formatters';\nimport type {\n EcoMeasurement,\n EcoSensor,\n EcoSensorKind,\n EcoSensorStatus,\n EcoAlertThreshold,\n EcoThresholdDirection,\n EcoThresholdSeverity,\n} from '../../types';\n\ntype MonitoringTab = 'measurements' | 'sensors' | 'thresholds';\n\nconst SENSOR_KINDS: EcoSensorKind[] = [\n 'AIR_QUALITY',\n 'WATER_QUALITY',\n 'WEATHER',\n 'SOIL',\n 'ENERGY_METER',\n 'FLOW',\n 'ACOUSTIC',\n 'CAMERA_TRAP',\n 'MANUAL',\n];\nconst SENSOR_STATUSES: EcoSensorStatus[] = [\n 'ONLINE',\n 'OFFLINE',\n 'DEGRADED',\n 'MAINTENANCE',\n 'RETIRED',\n];\nconst THRESHOLD_DIRECTIONS: EcoThresholdDirection[] = ['ABOVE', 'BELOW', 'OUTSIDE_RANGE'];\nconst THRESHOLD_SEVERITIES: EcoThresholdSeverity[] = ['INFO', 'WARNING', 'CRITICAL'];\n\nconst BREACH_FILTERS: FilterOption<'ALL' | 'BREACHES'>[] = [\n { value: 'ALL', label: 'All' },\n { value: 'BREACHES', label: 'Breaches only' },\n];\n\nconst ALL = 'ALL';\nconst PAGE_SIZE = 25;\n\nexport function MonitoringPage() {\n const [tab, setTab] = useState<MonitoringTab>('measurements');\n\n // Site directory — powers name lookups + the per-tab site filters/selects.\n const sitesQuery = useEcoSites({ pagination: { limit: 100, offset: 0 } });\n const allSites = useMemo(() => sitesQuery.data?.items ?? [], [sitesQuery.data]);\n const siteNameById = useMemo(() => {\n const map = new Map<string, string>();\n for (const s of allSites) map.set(s.id, s.name);\n return map;\n }, [allSites]);\n const siteName = (id?: string | null): string => (id ? (siteNameById.get(id) ?? '—') : '—');\n\n // ── Measurements tab state ────────────────────────────────────────────────\n const [measSiteId, setMeasSiteId] = useState<string>(ALL);\n const [breachFilter, setBreachFilter] = useState<'ALL' | 'BREACHES'>('ALL');\n const measPager = useCursorPager();\n\n const measurementsQuery = useEcoMeasurements({\n siteId: measSiteId === ALL ? undefined : measSiteId,\n breachesOnly: breachFilter === 'BREACHES' ? true : undefined,\n pagination: { limit: PAGE_SIZE, cursor: measPager.cursor },\n });\n const recordMeasurement = useEcoRecordMeasurement();\n\n const [measureOpen, setMeasureOpen] = useState(false);\n const [measureForm, setMeasureForm] = useState({\n siteId: '',\n parameter: '',\n value: '',\n unit: '',\n sensorId: '',\n });\n const dialogSensors = useEcoSensors({ siteId: measureForm.siteId || undefined });\n const dialogSensorList = dialogSensors.data ?? [];\n\n // ── Sensors tab state ─────────────────────────────────────────────────────\n const [sensorSiteId, setSensorSiteId] = useState<string>(ALL);\n const [sensorStatusFilter, setSensorStatusFilter] = useState<EcoSensorStatus | 'ALL'>(ALL);\n\n const sensorsQuery = useEcoSensors({\n siteId: sensorSiteId === ALL ? undefined : sensorSiteId,\n status: sensorStatusFilter === ALL ? undefined : sensorStatusFilter,\n });\n const sensors = sensorsQuery.data ?? [];\n const createSensor = useEcoCreateSensor();\n const setSensorStatus = useEcoSetSensorStatus();\n\n const [sensorOpen, setSensorOpen] = useState(false);\n const [sensorForm, setSensorForm] = useState({\n siteId: '',\n name: '',\n kind: 'AIR_QUALITY' as EcoSensorKind,\n unit: '',\n });\n\n // ── Thresholds tab state ──────────────────────────────────────────────────\n const [thresholdSiteId, setThresholdSiteId] = useState<string>(ALL);\n\n const thresholdsQuery = useEcoThresholds(thresholdSiteId === ALL ? undefined : thresholdSiteId);\n const thresholds = thresholdsQuery.data ?? [];\n const createThreshold = useEcoCreateThreshold();\n const setThresholdActive = useEcoSetThresholdActive();\n\n const [thresholdOpen, setThresholdOpen] = useState(false);\n const [thresholdForm, setThresholdForm] = useState({\n siteId: '',\n parameter: '',\n unit: '',\n direction: 'ABOVE' as EcoThresholdDirection,\n severity: 'WARNING' as EcoThresholdSeverity,\n limitValue: '',\n minValue: '',\n maxValue: '',\n });\n\n const [error, setError] = useState<string | null>(null);\n\n const tabItems: TabItem<MonitoringTab>[] = [\n { value: 'measurements', label: 'Measurements', icon: <Activity className=\"h-4 w-4\" /> },\n {\n value: 'sensors',\n label: 'Sensors',\n icon: <Radio className=\"h-4 w-4\" />,\n count: sensorsQuery.data?.length,\n },\n {\n value: 'thresholds',\n label: 'Thresholds',\n icon: <Gauge className=\"h-4 w-4\" />,\n count: thresholdsQuery.data?.length,\n },\n ];\n\n // ── Mutations ─────────────────────────────────────────────────────────────\n const handleRecordMeasurement = async () => {\n if (\n !measureForm.siteId ||\n !measureForm.parameter.trim() ||\n !measureForm.unit.trim() ||\n !measureForm.value\n ) {\n setError('Site, parameter, value and unit are required.');\n return;\n }\n setError(null);\n try {\n await recordMeasurement.mutateAsync({\n siteId: measureForm.siteId,\n parameter: measureForm.parameter.trim(),\n value: Number(measureForm.value),\n unit: measureForm.unit.trim(),\n recordedAt: new Date().toISOString(),\n sensorId: measureForm.sensorId || null,\n source: 'MANUAL',\n });\n setMeasureOpen(false);\n setMeasureForm({ siteId: '', parameter: '', value: '', unit: '', sensorId: '' });\n measurementsQuery.refetch();\n } catch (err) {\n setError(err instanceof Error ? err.message : 'Failed to record measurement.');\n }\n };\n\n const handleAddSensor = async () => {\n if (!sensorForm.siteId || !sensorForm.name.trim()) {\n setError('Site and sensor name are required.');\n return;\n }\n setError(null);\n try {\n await createSensor.mutateAsync({\n name: sensorForm.name.trim(),\n siteId: sensorForm.siteId,\n kind: sensorForm.kind,\n unit: sensorForm.unit.trim() || null,\n });\n setSensorOpen(false);\n setSensorForm({ siteId: '', name: '', kind: 'AIR_QUALITY', unit: '' });\n } catch (err) {\n setError(err instanceof Error ? err.message : 'Failed to add sensor.');\n }\n };\n\n const handleAddThreshold = async () => {\n if (!thresholdForm.siteId || !thresholdForm.parameter.trim() || !thresholdForm.unit.trim()) {\n setError('Site, parameter and unit are required.');\n return;\n }\n setError(null);\n try {\n await createThreshold.mutateAsync({\n siteId: thresholdForm.siteId,\n parameter: thresholdForm.parameter.trim(),\n unit: thresholdForm.unit.trim(),\n direction: thresholdForm.direction,\n severity: thresholdForm.severity,\n limitValue: thresholdForm.limitValue ? Number(thresholdForm.limitValue) : null,\n minValue: thresholdForm.minValue ? Number(thresholdForm.minValue) : null,\n maxValue: thresholdForm.maxValue ? Number(thresholdForm.maxValue) : null,\n });\n setThresholdOpen(false);\n setThresholdForm({\n siteId: '',\n parameter: '',\n unit: '',\n direction: 'ABOVE',\n severity: 'WARNING',\n limitValue: '',\n minValue: '',\n maxValue: '',\n });\n } catch (err) {\n setError(err instanceof Error ? err.message : 'Failed to add threshold.');\n }\n };\n\n // ── Columns ───────────────────────────────────────────────────────────────\n const measurementColumns: Column<EcoMeasurement>[] = [\n {\n key: 'parameter',\n header: 'Parameter',\n render: (m) => <span className=\"font-medium text-text-primary\">{m.parameter}</span>,\n },\n {\n key: 'value',\n header: 'Value',\n align: 'right',\n render: (m) => (\n <span className=\"tabular-nums text-text-primary\">\n {formatNumber(m.value)} {m.unit}\n </span>\n ),\n },\n {\n key: 'source',\n header: 'Source',\n hideOnMobile: true,\n render: (m) => <span className=\"text-text-secondary\">{formatEnumLabel(m.source)}</span>,\n },\n {\n key: 'breach',\n header: 'Status',\n render: (m) =>\n m.isBreach ? (\n <StatusBadge status=\"BREACH\" className=\"uppercase\" />\n ) : (\n <StatusBadge status=\"OK\" className=\"uppercase\" />\n ),\n },\n {\n key: 'site',\n header: 'Site',\n hideOnMobile: true,\n render: (m) => <span className=\"text-text-secondary\">{siteName(m.siteId)}</span>,\n },\n {\n key: 'recorded',\n header: 'Recorded',\n align: 'right',\n hideOnMobile: true,\n render: (m) => <span className=\"text-text-tertiary\">{formatDateTime(m.recordedAt)}</span>,\n },\n ];\n\n const sensorColumns: Column<EcoSensor>[] = [\n {\n key: 'name',\n header: 'Sensor',\n render: (s) => (\n <div>\n <p className=\"font-medium text-text-primary\">{s.name}</p>\n <p className=\"text-xs text-text-tertiary\">{formatEnumLabel(s.kind)}</p>\n </div>\n ),\n },\n {\n key: 'site',\n header: 'Site',\n hideOnMobile: true,\n render: (s) => <span className=\"text-text-secondary\">{siteName(s.siteId)}</span>,\n },\n {\n key: 'unit',\n header: 'Unit',\n hideOnMobile: true,\n render: (s) => <span className=\"text-text-secondary\">{s.unit ?? '—'}</span>,\n },\n { key: 'status', header: 'Status', render: (s) => <StatusBadge status={s.status} /> },\n {\n key: 'action',\n header: '',\n align: 'right',\n render: (s) => (\n <Select\n value={s.status}\n onValueChange={(v) => setSensorStatus.mutate({ id: s.id, status: v as EcoSensorStatus })}\n >\n <SelectTrigger className=\"h-8 w-36\">\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n {SENSOR_STATUSES.map((st) => (\n <SelectItem key={st} value={st}>\n {formatEnumLabel(st)}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n ),\n },\n ];\n\n const thresholdColumns: Column<EcoAlertThreshold>[] = [\n {\n key: 'parameter',\n header: 'Parameter',\n render: (t) => <span className=\"font-medium text-text-primary\">{t.parameter}</span>,\n },\n {\n key: 'site',\n header: 'Site',\n hideOnMobile: true,\n render: (t) => <span className=\"text-text-secondary\">{siteName(t.siteId)}</span>,\n },\n {\n key: 'rule',\n header: 'Rule',\n render: (t) => (\n <span className=\"text-text-secondary\">\n {t.direction === 'OUTSIDE_RANGE'\n ? `${formatNumber(t.minValue)}–${formatNumber(t.maxValue)} ${t.unit}`\n : `${formatEnumLabel(t.direction)} ${formatNumber(t.limitValue)} ${t.unit}`}\n </span>\n ),\n },\n { key: 'severity', header: 'Severity', render: (t) => <StatusBadge status={t.severity} /> },\n {\n key: 'active',\n header: 'Active',\n align: 'right',\n render: (t) => (\n <Button\n size=\"sm\"\n variant={t.isActive ? 'outline' : 'ghost'}\n onClick={() => setThresholdActive.mutate({ id: t.id, isActive: !t.isActive })}\n >\n {t.isActive ? 'Active' : 'Inactive'}\n </Button>\n ),\n },\n ];\n\n // ── Reusable site <Select> options ────────────────────────────────────────\n const siteOptions = allSites.map((s) => (\n <SelectItem key={s.id} value={s.id}>\n {s.name}\n </SelectItem>\n ));\n\n const measurementRows = measurementsQuery.data?.items ?? [];\n\n return (\n <PageLayout\n title=\"Monitoring\"\n description=\"Environmental sensors, readings and alert thresholds across every monitored site.\"\n actions={\n tab === 'measurements' ? (\n <Button\n onClick={() => {\n setError(null);\n setMeasureOpen(true);\n }}\n >\n <Plus className=\"mr-1.5 h-4 w-4\" /> Record measurement\n </Button>\n ) : tab === 'sensors' ? (\n <Button\n onClick={() => {\n setError(null);\n setSensorOpen(true);\n }}\n >\n <Plus className=\"mr-1.5 h-4 w-4\" /> Add sensor\n </Button>\n ) : (\n <Button\n onClick={() => {\n setError(null);\n setThresholdOpen(true);\n }}\n >\n <Plus className=\"mr-1.5 h-4 w-4\" /> Add threshold\n </Button>\n )\n }\n >\n <Tabs items={tabItems} value={tab} onChange={setTab} className=\"mb-5\" />\n\n {/* ── Measurements ─────────────────────────────────────────────────── */}\n {tab === 'measurements' && (\n <>\n <div className=\"mb-5 flex flex-wrap items-center gap-3\">\n <Select\n value={measSiteId}\n onValueChange={(v) => {\n setMeasSiteId(v);\n measPager.reset();\n }}\n >\n <SelectTrigger className=\"w-52\">\n <SelectValue placeholder=\"All sites\" />\n </SelectTrigger>\n <SelectContent>\n <SelectItem value={ALL}>All sites</SelectItem>\n {siteOptions}\n </SelectContent>\n </Select>\n <FilterTabs\n options={BREACH_FILTERS}\n value={breachFilter}\n onChange={(v) => {\n setBreachFilter(v);\n measPager.reset();\n }}\n />\n </div>\n\n {measurementsQuery.isLoading ? (\n <LoadingView label=\"Loading measurements…\" />\n ) : measurementsQuery.isError ? (\n <ErrorView\n message=\"Couldn't load measurements.\"\n onRetry={() => measurementsQuery.refetch()}\n />\n ) : measurementRows.length === 0 ? (\n <EmptyState\n illustration=\"empty-data\"\n icon={<Activity className=\"h-10 w-10\" />}\n title=\"No measurements\"\n description={\n measSiteId === ALL && breachFilter === 'ALL'\n ? 'Record a measurement to start building a reading history.'\n : 'No measurements match these filters.'\n }\n action={\n <Button\n onClick={() => {\n setError(null);\n setMeasureOpen(true);\n }}\n >\n <Plus className=\"mr-1.5 h-4 w-4\" /> Record measurement\n </Button>\n }\n />\n ) : (\n <>\n <DataTable columns={measurementColumns} rows={measurementRows} rowKey={(m) => m.id} />\n <Pagination\n page={measPager.page}\n pageSize={PAGE_SIZE}\n itemCount={measurementRows.length}\n hasMore={measurementsQuery.data?.pageInfo?.hasMore ?? false}\n onPageChange={(target) =>\n measPager.onPageChange(target, measurementsQuery.data?.pageInfo?.cursor)\n }\n />\n </>\n )}\n </>\n )}\n\n {/* ── Sensors ──────────────────────────────────────────────────────── */}\n {tab === 'sensors' && (\n <>\n <div className=\"mb-5 flex flex-wrap items-center gap-3\">\n <Select value={sensorSiteId} onValueChange={setSensorSiteId}>\n <SelectTrigger className=\"w-52\">\n <SelectValue placeholder=\"All sites\" />\n </SelectTrigger>\n <SelectContent>\n <SelectItem value={ALL}>All sites</SelectItem>\n {siteOptions}\n </SelectContent>\n </Select>\n <Select\n value={sensorStatusFilter}\n onValueChange={(v) => setSensorStatusFilter(v as EcoSensorStatus | 'ALL')}\n >\n <SelectTrigger className=\"w-44\">\n <SelectValue placeholder=\"All statuses\" />\n </SelectTrigger>\n <SelectContent>\n <SelectItem value={ALL}>All statuses</SelectItem>\n {SENSOR_STATUSES.map((st) => (\n <SelectItem key={st} value={st}>\n {formatEnumLabel(st)}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </div>\n\n {sensorsQuery.isLoading ? (\n <LoadingView label=\"Loading sensors…\" />\n ) : sensorsQuery.isError ? (\n <ErrorView message=\"Couldn't load sensors.\" onRetry={() => sensorsQuery.refetch()} />\n ) : sensors.length === 0 ? (\n <EmptyState\n illustration=\"empty-data\"\n icon={<Radio className=\"h-10 w-10\" />}\n title=\"No sensors\"\n description={\n sensorSiteId === ALL && sensorStatusFilter === ALL\n ? 'Add a sensor to begin streaming or logging readings.'\n : 'No sensors match these filters.'\n }\n action={\n <Button\n onClick={() => {\n setError(null);\n setSensorOpen(true);\n }}\n >\n <Plus className=\"mr-1.5 h-4 w-4\" /> Add sensor\n </Button>\n }\n />\n ) : (\n <DataTable columns={sensorColumns} rows={sensors} rowKey={(s) => s.id} />\n )}\n </>\n )}\n\n {/* ── Thresholds ───────────────────────────────────────────────────── */}\n {tab === 'thresholds' && (\n <>\n <div className=\"mb-5 flex flex-wrap items-center gap-3\">\n <Select value={thresholdSiteId} onValueChange={setThresholdSiteId}>\n <SelectTrigger className=\"w-52\">\n <SelectValue placeholder=\"All sites\" />\n </SelectTrigger>\n <SelectContent>\n <SelectItem value={ALL}>All sites</SelectItem>\n {siteOptions}\n </SelectContent>\n </Select>\n </div>\n\n {thresholdsQuery.isLoading ? (\n <LoadingView label=\"Loading thresholds…\" />\n ) : thresholdsQuery.isError ? (\n <ErrorView\n message=\"Couldn't load thresholds.\"\n onRetry={() => thresholdsQuery.refetch()}\n />\n ) : thresholds.length === 0 ? (\n <EmptyState\n illustration=\"empty-data\"\n icon={<Gauge className=\"h-10 w-10\" />}\n title=\"No thresholds\"\n description={\n thresholdSiteId === ALL\n ? 'Define a threshold to flag readings that drift out of range.'\n : 'This site has no thresholds yet.'\n }\n action={\n <Button\n onClick={() => {\n setError(null);\n setThresholdOpen(true);\n }}\n >\n <Plus className=\"mr-1.5 h-4 w-4\" /> Add threshold\n </Button>\n }\n />\n ) : (\n <DataTable columns={thresholdColumns} rows={thresholds} rowKey={(t) => t.id} />\n )}\n </>\n )}\n\n {/* ── Record measurement dialog ────────────────────────────────────── */}\n <ResponsiveDialog\n open={measureOpen}\n onOpenChange={(open) => {\n setMeasureOpen(open);\n if (!open) setError(null);\n }}\n >\n <ResponsiveDialogContent>\n <ResponsiveDialogHeader>\n <ResponsiveDialogTitle>Record measurement</ResponsiveDialogTitle>\n <ResponsiveDialogDescription>\n Log a reading. It is checked against the site&apos;s active thresholds and flagged if\n it breaches.\n </ResponsiveDialogDescription>\n </ResponsiveDialogHeader>\n <div className=\"grid gap-4 py-2\">\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"mSite\">Site *</Label>\n <Select\n value={measureForm.siteId}\n onValueChange={(v) => setMeasureForm({ ...measureForm, siteId: v, sensorId: '' })}\n >\n <SelectTrigger id=\"mSite\">\n <SelectValue placeholder=\"Select a site\" />\n </SelectTrigger>\n <SelectContent>{siteOptions}</SelectContent>\n </Select>\n </div>\n <div className=\"grid gap-2 sm:grid-cols-3\">\n <div className=\"grid gap-1.5 sm:col-span-1\">\n <Label htmlFor=\"mParam\">Parameter *</Label>\n <Input\n id=\"mParam\"\n value={measureForm.parameter}\n onChange={(e) => setMeasureForm({ ...measureForm, parameter: e.target.value })}\n placeholder=\"PM2.5\"\n />\n </div>\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"mValue\">Value *</Label>\n <Input\n id=\"mValue\"\n type=\"number\"\n step=\"any\"\n value={measureForm.value}\n onChange={(e) => setMeasureForm({ ...measureForm, value: e.target.value })}\n placeholder=\"42\"\n />\n </div>\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"mUnit\">Unit *</Label>\n <Input\n id=\"mUnit\"\n value={measureForm.unit}\n onChange={(e) => setMeasureForm({ ...measureForm, unit: e.target.value })}\n placeholder=\"µg/m³\"\n />\n </div>\n </div>\n {measureForm.siteId && dialogSensorList.length > 0 && (\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"mSensor\">Sensor (optional)</Label>\n <Select\n value={measureForm.sensorId}\n onValueChange={(v) => setMeasureForm({ ...measureForm, sensorId: v })}\n >\n <SelectTrigger id=\"mSensor\">\n <SelectValue placeholder=\"Manual entry\" />\n </SelectTrigger>\n <SelectContent>\n {dialogSensorList.map((s) => (\n <SelectItem key={s.id} value={s.id}>\n {s.name}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n <p className=\"text-xs text-text-tertiary\">\n Select a specific sensor for context, or leave blank for a site-level reading.\n </p>\n </div>\n )}\n {error && <p className=\"text-sm text-status-error-text\">{error}</p>}\n </div>\n <ResponsiveDialogFooter>\n <Button variant=\"outline\" onClick={() => setMeasureOpen(false)}>\n Cancel\n </Button>\n <Button onClick={handleRecordMeasurement} disabled={recordMeasurement.isPending}>\n {recordMeasurement.isPending ? 'Recording…' : 'Record'}\n </Button>\n </ResponsiveDialogFooter>\n </ResponsiveDialogContent>\n </ResponsiveDialog>\n\n {/* ── Add sensor dialog ────────────────────────────────────────────── */}\n <ResponsiveDialog\n open={sensorOpen}\n onOpenChange={(open) => {\n setSensorOpen(open);\n if (!open) setError(null);\n }}\n >\n <ResponsiveDialogContent>\n <ResponsiveDialogHeader>\n <ResponsiveDialogTitle>Add sensor</ResponsiveDialogTitle>\n <ResponsiveDialogDescription>\n Register a sensor and attribute it to a monitored site.\n </ResponsiveDialogDescription>\n </ResponsiveDialogHeader>\n <div className=\"grid gap-4 py-2\">\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"sSite\">Site *</Label>\n <Select\n value={sensorForm.siteId}\n onValueChange={(v) => setSensorForm({ ...sensorForm, siteId: v })}\n >\n <SelectTrigger id=\"sSite\">\n <SelectValue placeholder=\"Select a site\" />\n </SelectTrigger>\n <SelectContent>{siteOptions}</SelectContent>\n </Select>\n </div>\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"sName\">Name *</Label>\n <Input\n id=\"sName\"\n value={sensorForm.name}\n onChange={(e) => setSensorForm({ ...sensorForm, name: e.target.value })}\n placeholder=\"North gate PM2.5\"\n />\n </div>\n <div className=\"grid gap-2 sm:grid-cols-2\">\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"sKind\">Kind</Label>\n <Select\n value={sensorForm.kind}\n onValueChange={(v) => setSensorForm({ ...sensorForm, kind: v as EcoSensorKind })}\n >\n <SelectTrigger id=\"sKind\">\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n {SENSOR_KINDS.map((k) => (\n <SelectItem key={k} value={k}>\n {formatEnumLabel(k)}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </div>\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"sUnit\">Unit</Label>\n <Input\n id=\"sUnit\"\n value={sensorForm.unit}\n onChange={(e) => setSensorForm({ ...sensorForm, unit: e.target.value })}\n placeholder=\"µg/m³\"\n />\n </div>\n </div>\n {error && <p className=\"text-sm text-status-error-text\">{error}</p>}\n </div>\n <ResponsiveDialogFooter>\n <Button variant=\"outline\" onClick={() => setSensorOpen(false)}>\n Cancel\n </Button>\n <Button onClick={handleAddSensor} disabled={createSensor.isPending}>\n {createSensor.isPending ? 'Adding…' : 'Add sensor'}\n </Button>\n </ResponsiveDialogFooter>\n </ResponsiveDialogContent>\n </ResponsiveDialog>\n\n {/* ── Add threshold dialog ─────────────────────────────────────────── */}\n <ResponsiveDialog\n open={thresholdOpen}\n onOpenChange={(open) => {\n setThresholdOpen(open);\n if (!open) setError(null);\n }}\n >\n <ResponsiveDialogContent>\n <ResponsiveDialogHeader>\n <ResponsiveDialogTitle>Add alert threshold</ResponsiveDialogTitle>\n <ResponsiveDialogDescription>\n Flag readings that drift out of an acceptable range.\n </ResponsiveDialogDescription>\n </ResponsiveDialogHeader>\n <div className=\"grid gap-4 py-2\">\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"tSite\">Site *</Label>\n <Select\n value={thresholdForm.siteId}\n onValueChange={(v) => setThresholdForm({ ...thresholdForm, siteId: v })}\n >\n <SelectTrigger id=\"tSite\">\n <SelectValue placeholder=\"Select a site\" />\n </SelectTrigger>\n <SelectContent>{siteOptions}</SelectContent>\n </Select>\n </div>\n <div className=\"grid gap-2 sm:grid-cols-2\">\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"tParam\">Parameter *</Label>\n <Input\n id=\"tParam\"\n value={thresholdForm.parameter}\n onChange={(e) =>\n setThresholdForm({ ...thresholdForm, parameter: e.target.value })\n }\n placeholder=\"PM2.5\"\n />\n </div>\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"tUnit\">Unit *</Label>\n <Input\n id=\"tUnit\"\n value={thresholdForm.unit}\n onChange={(e) => setThresholdForm({ ...thresholdForm, unit: e.target.value })}\n placeholder=\"µg/m³\"\n />\n </div>\n </div>\n <div className=\"grid gap-2 sm:grid-cols-2\">\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"tDir\">Direction</Label>\n <Select\n value={thresholdForm.direction}\n onValueChange={(v) =>\n setThresholdForm({ ...thresholdForm, direction: v as EcoThresholdDirection })\n }\n >\n <SelectTrigger id=\"tDir\">\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n {THRESHOLD_DIRECTIONS.map((d) => (\n <SelectItem key={d} value={d}>\n {formatEnumLabel(d)}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </div>\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"tSev\">Severity</Label>\n <Select\n value={thresholdForm.severity}\n onValueChange={(v) =>\n setThresholdForm({ ...thresholdForm, severity: v as EcoThresholdSeverity })\n }\n >\n <SelectTrigger id=\"tSev\">\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n {THRESHOLD_SEVERITIES.map((s) => (\n <SelectItem key={s} value={s}>\n {formatEnumLabel(s)}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </div>\n </div>\n {thresholdForm.direction === 'OUTSIDE_RANGE' ? (\n <div className=\"grid gap-2 sm:grid-cols-2\">\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"tMin\">Min value</Label>\n <Input\n id=\"tMin\"\n type=\"number\"\n step=\"any\"\n value={thresholdForm.minValue}\n onChange={(e) =>\n setThresholdForm({ ...thresholdForm, minValue: e.target.value })\n }\n />\n </div>\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"tMax\">Max value</Label>\n <Input\n id=\"tMax\"\n type=\"number\"\n step=\"any\"\n value={thresholdForm.maxValue}\n onChange={(e) =>\n setThresholdForm({ ...thresholdForm, maxValue: e.target.value })\n }\n />\n </div>\n </div>\n ) : (\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"tLimit\">Limit value</Label>\n <Input\n id=\"tLimit\"\n type=\"number\"\n step=\"any\"\n value={thresholdForm.limitValue}\n onChange={(e) =>\n setThresholdForm({ ...thresholdForm, limitValue: e.target.value })\n }\n placeholder=\"35\"\n />\n </div>\n )}\n {error && <p className=\"text-sm text-status-error-text\">{error}</p>}\n </div>\n <ResponsiveDialogFooter>\n <Button variant=\"outline\" onClick={() => setThresholdOpen(false)}>\n Cancel\n </Button>\n <Button onClick={handleAddThreshold} disabled={createThreshold.isPending}>\n {createThreshold.isPending ? 'Adding…' : 'Add threshold'}\n </Button>\n </ResponsiveDialogFooter>\n </ResponsiveDialogContent>\n </ResponsiveDialog>\n </PageLayout>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"MonitoringPage.js","names":[],"sources":["../../../../src/ecoimpacthub/pages/monitoring/MonitoringPage.tsx"],"sourcesContent":["/**\n * Monitoring — a cross-site hub for environmental measurements, sensors &\n * alert thresholds.\n * @module ecoimpacthub/pages/monitoring\n *\n * Reads `ecoListMeasurements`, `ecoListSensors`, `ecoListThresholds` (and\n * `ecoListSites` for name lookups); writes `ecoRecordMeasurement`,\n * `ecoCreateSensor`, `ecoSetSensorStatus`, `ecoCreateThreshold`,\n * `ecoSetThresholdActive`.\n */\n\nimport { useMemo, useState } from 'react';\nimport { Plus, Activity, Radio, Gauge } from 'lucide-react';\nimport {\n Button,\n Input,\n Label,\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n ResponsiveDialog,\n ResponsiveDialogContent,\n ResponsiveDialogHeader,\n ResponsiveDialogTitle,\n ResponsiveDialogDescription,\n ResponsiveDialogFooter,\n} from '@burdenoff/fe-libs/ui';\nimport { PageLayout } from '../../components/PageLayout';\nimport { StatusBadge } from '../../components/StatusBadge';\nimport { DataTable, type Column } from '../../components/DataTable';\nimport { FilterTabs, type FilterOption } from '../../components/FilterTabs';\nimport { Tabs, type TabItem } from '../../components/Tabs';\nimport { LoadingView, ErrorView } from '../../components/StateViews';\nimport { EmptyState } from '../../components/EmptyState';\nimport { Pagination } from '../../components/Pagination';\nimport {\n useEcoSites,\n useEcoMeasurements,\n useEcoRecordMeasurement,\n useEcoSensors,\n useEcoCreateSensor,\n useEcoSetSensorStatus,\n useEcoThresholds,\n useEcoCreateThreshold,\n useEcoSetThresholdActive,\n useCursorPager,\n} from '../../hooks';\nimport { formatDateTime, formatEnumLabel, formatNumber } from '../../utils/formatters';\nimport type {\n EcoMeasurement,\n EcoSensor,\n EcoSensorKind,\n EcoSensorStatus,\n EcoAlertThreshold,\n EcoThresholdDirection,\n EcoThresholdSeverity,\n} from '../../types';\n\ntype MonitoringTab = 'measurements' | 'sensors' | 'thresholds';\n\nconst SENSOR_KINDS: EcoSensorKind[] = [\n 'AIR_QUALITY',\n 'WATER_QUALITY',\n 'WEATHER',\n 'SOIL',\n 'ENERGY_METER',\n 'FLOW',\n 'ACOUSTIC',\n 'CAMERA_TRAP',\n 'MANUAL',\n];\nconst SENSOR_STATUSES: EcoSensorStatus[] = [\n 'ONLINE',\n 'OFFLINE',\n 'DEGRADED',\n 'MAINTENANCE',\n 'RETIRED',\n];\nconst THRESHOLD_DIRECTIONS: EcoThresholdDirection[] = ['ABOVE', 'BELOW', 'OUTSIDE_RANGE'];\nconst THRESHOLD_SEVERITIES: EcoThresholdSeverity[] = ['INFO', 'WARNING', 'CRITICAL'];\n\nconst BREACH_FILTERS: FilterOption<'ALL' | 'BREACHES'>[] = [\n { value: 'ALL', label: 'All' },\n { value: 'BREACHES', label: 'Breaches only' },\n];\n\nconst ALL = 'ALL';\nconst PAGE_SIZE = 25;\n\nexport function MonitoringPage() {\n const [tab, setTab] = useState<MonitoringTab>('measurements');\n\n // Site directory — powers name lookups + the per-tab site filters/selects.\n const sitesQuery = useEcoSites({ pagination: { limit: 100, offset: 0 } });\n const allSites = useMemo(() => sitesQuery.data?.items ?? [], [sitesQuery.data]);\n const siteNameById = useMemo(() => {\n const map = new Map<string, string>();\n for (const s of allSites) map.set(s.id, s.name);\n return map;\n }, [allSites]);\n const siteName = (id?: string | null): string => (id ? (siteNameById.get(id) ?? '—') : '—');\n\n // ── Measurements tab state ────────────────────────────────────────────────\n const [measSiteId, setMeasSiteId] = useState<string>(ALL);\n const [breachFilter, setBreachFilter] = useState<'ALL' | 'BREACHES'>('ALL');\n const measPager = useCursorPager();\n\n const measurementsQuery = useEcoMeasurements({\n siteId: measSiteId === ALL ? undefined : measSiteId,\n breachesOnly: breachFilter === 'BREACHES' ? true : undefined,\n pagination: { limit: PAGE_SIZE, cursor: measPager.cursor },\n });\n const recordMeasurement = useEcoRecordMeasurement();\n\n const [measureOpen, setMeasureOpen] = useState(false);\n const [measureForm, setMeasureForm] = useState({\n siteId: '',\n parameter: '',\n value: '',\n unit: '',\n sensorId: '',\n });\n const dialogSensors = useEcoSensors({ siteId: measureForm.siteId || undefined });\n const dialogSensorList = dialogSensors.data?.items ?? [];\n\n // ── Sensors tab state ─────────────────────────────────────────────────────\n const [sensorSiteId, setSensorSiteId] = useState<string>(ALL);\n const [sensorStatusFilter, setSensorStatusFilter] = useState<EcoSensorStatus | 'ALL'>(ALL);\n\n const sensorsQuery = useEcoSensors({\n siteId: sensorSiteId === ALL ? undefined : sensorSiteId,\n status: sensorStatusFilter === ALL ? undefined : sensorStatusFilter,\n });\n const sensors = sensorsQuery.data?.items ?? [];\n const createSensor = useEcoCreateSensor();\n const setSensorStatus = useEcoSetSensorStatus();\n\n const [sensorOpen, setSensorOpen] = useState(false);\n const [sensorForm, setSensorForm] = useState({\n siteId: '',\n name: '',\n kind: 'AIR_QUALITY' as EcoSensorKind,\n unit: '',\n });\n\n // ── Thresholds tab state ──────────────────────────────────────────────────\n const [thresholdSiteId, setThresholdSiteId] = useState<string>(ALL);\n\n const thresholdsQuery = useEcoThresholds(thresholdSiteId === ALL ? undefined : thresholdSiteId);\n const thresholds = thresholdsQuery.data?.items ?? [];\n const createThreshold = useEcoCreateThreshold();\n const setThresholdActive = useEcoSetThresholdActive();\n\n const [thresholdOpen, setThresholdOpen] = useState(false);\n const [thresholdForm, setThresholdForm] = useState({\n siteId: '',\n parameter: '',\n unit: '',\n direction: 'ABOVE' as EcoThresholdDirection,\n severity: 'WARNING' as EcoThresholdSeverity,\n limitValue: '',\n minValue: '',\n maxValue: '',\n });\n\n const [error, setError] = useState<string | null>(null);\n\n const tabItems: TabItem<MonitoringTab>[] = [\n { value: 'measurements', label: 'Measurements', icon: <Activity className=\"h-4 w-4\" /> },\n {\n value: 'sensors',\n label: 'Sensors',\n icon: <Radio className=\"h-4 w-4\" />,\n count: sensorsQuery.data?.items?.length,\n },\n {\n value: 'thresholds',\n label: 'Thresholds',\n icon: <Gauge className=\"h-4 w-4\" />,\n count: thresholdsQuery.data?.items?.length,\n },\n ];\n\n // ── Mutations ─────────────────────────────────────────────────────────────\n const handleRecordMeasurement = async () => {\n if (\n !measureForm.siteId ||\n !measureForm.parameter.trim() ||\n !measureForm.unit.trim() ||\n !measureForm.value\n ) {\n setError('Site, parameter, value and unit are required.');\n return;\n }\n setError(null);\n try {\n await recordMeasurement.mutateAsync({\n siteId: measureForm.siteId,\n parameter: measureForm.parameter.trim(),\n value: Number(measureForm.value),\n unit: measureForm.unit.trim(),\n recordedAt: new Date().toISOString(),\n sensorId: measureForm.sensorId || null,\n source: 'MANUAL',\n });\n setMeasureOpen(false);\n setMeasureForm({ siteId: '', parameter: '', value: '', unit: '', sensorId: '' });\n measurementsQuery.refetch();\n } catch (err) {\n setError(err instanceof Error ? err.message : 'Failed to record measurement.');\n }\n };\n\n const handleAddSensor = async () => {\n if (!sensorForm.siteId || !sensorForm.name.trim()) {\n setError('Site and sensor name are required.');\n return;\n }\n setError(null);\n try {\n await createSensor.mutateAsync({\n name: sensorForm.name.trim(),\n siteId: sensorForm.siteId,\n kind: sensorForm.kind,\n unit: sensorForm.unit.trim() || null,\n });\n setSensorOpen(false);\n setSensorForm({ siteId: '', name: '', kind: 'AIR_QUALITY', unit: '' });\n } catch (err) {\n setError(err instanceof Error ? err.message : 'Failed to add sensor.');\n }\n };\n\n const handleAddThreshold = async () => {\n if (!thresholdForm.siteId || !thresholdForm.parameter.trim() || !thresholdForm.unit.trim()) {\n setError('Site, parameter and unit are required.');\n return;\n }\n setError(null);\n try {\n await createThreshold.mutateAsync({\n siteId: thresholdForm.siteId,\n parameter: thresholdForm.parameter.trim(),\n unit: thresholdForm.unit.trim(),\n direction: thresholdForm.direction,\n severity: thresholdForm.severity,\n limitValue: thresholdForm.limitValue ? Number(thresholdForm.limitValue) : null,\n minValue: thresholdForm.minValue ? Number(thresholdForm.minValue) : null,\n maxValue: thresholdForm.maxValue ? Number(thresholdForm.maxValue) : null,\n });\n setThresholdOpen(false);\n setThresholdForm({\n siteId: '',\n parameter: '',\n unit: '',\n direction: 'ABOVE',\n severity: 'WARNING',\n limitValue: '',\n minValue: '',\n maxValue: '',\n });\n } catch (err) {\n setError(err instanceof Error ? err.message : 'Failed to add threshold.');\n }\n };\n\n // ── Columns ───────────────────────────────────────────────────────────────\n const measurementColumns: Column<EcoMeasurement>[] = [\n {\n key: 'parameter',\n header: 'Parameter',\n render: (m) => <span className=\"font-medium text-text-primary\">{m.parameter}</span>,\n },\n {\n key: 'value',\n header: 'Value',\n align: 'right',\n render: (m) => (\n <span className=\"tabular-nums text-text-primary\">\n {formatNumber(m.value)} {m.unit}\n </span>\n ),\n },\n {\n key: 'source',\n header: 'Source',\n hideOnMobile: true,\n render: (m) => <span className=\"text-text-secondary\">{formatEnumLabel(m.source)}</span>,\n },\n {\n key: 'breach',\n header: 'Status',\n render: (m) =>\n m.isBreach ? (\n <StatusBadge status=\"BREACH\" className=\"uppercase\" />\n ) : (\n <StatusBadge status=\"OK\" className=\"uppercase\" />\n ),\n },\n {\n key: 'site',\n header: 'Site',\n hideOnMobile: true,\n render: (m) => <span className=\"text-text-secondary\">{siteName(m.siteId)}</span>,\n },\n {\n key: 'recorded',\n header: 'Recorded',\n align: 'right',\n hideOnMobile: true,\n render: (m) => <span className=\"text-text-tertiary\">{formatDateTime(m.recordedAt)}</span>,\n },\n ];\n\n const sensorColumns: Column<EcoSensor>[] = [\n {\n key: 'name',\n header: 'Sensor',\n render: (s) => (\n <div>\n <p className=\"font-medium text-text-primary\">{s.name}</p>\n <p className=\"text-xs text-text-tertiary\">{formatEnumLabel(s.kind)}</p>\n </div>\n ),\n },\n {\n key: 'site',\n header: 'Site',\n hideOnMobile: true,\n render: (s) => <span className=\"text-text-secondary\">{siteName(s.siteId)}</span>,\n },\n {\n key: 'unit',\n header: 'Unit',\n hideOnMobile: true,\n render: (s) => <span className=\"text-text-secondary\">{s.unit ?? '—'}</span>,\n },\n { key: 'status', header: 'Status', render: (s) => <StatusBadge status={s.status} /> },\n {\n key: 'action',\n header: '',\n align: 'right',\n render: (s) => (\n <Select\n value={s.status}\n onValueChange={(v) => setSensorStatus.mutate({ id: s.id, status: v as EcoSensorStatus })}\n >\n <SelectTrigger className=\"h-8 w-36\">\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n {SENSOR_STATUSES.map((st) => (\n <SelectItem key={st} value={st}>\n {formatEnumLabel(st)}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n ),\n },\n ];\n\n const thresholdColumns: Column<EcoAlertThreshold>[] = [\n {\n key: 'parameter',\n header: 'Parameter',\n render: (t) => <span className=\"font-medium text-text-primary\">{t.parameter}</span>,\n },\n {\n key: 'site',\n header: 'Site',\n hideOnMobile: true,\n render: (t) => <span className=\"text-text-secondary\">{siteName(t.siteId)}</span>,\n },\n {\n key: 'rule',\n header: 'Rule',\n render: (t) => (\n <span className=\"text-text-secondary\">\n {t.direction === 'OUTSIDE_RANGE'\n ? `${formatNumber(t.minValue)}–${formatNumber(t.maxValue)} ${t.unit}`\n : `${formatEnumLabel(t.direction)} ${formatNumber(t.limitValue)} ${t.unit}`}\n </span>\n ),\n },\n { key: 'severity', header: 'Severity', render: (t) => <StatusBadge status={t.severity} /> },\n {\n key: 'active',\n header: 'Active',\n align: 'right',\n render: (t) => (\n <Button\n size=\"sm\"\n variant={t.isActive ? 'outline' : 'ghost'}\n onClick={() => setThresholdActive.mutate({ id: t.id, isActive: !t.isActive })}\n >\n {t.isActive ? 'Active' : 'Inactive'}\n </Button>\n ),\n },\n ];\n\n // ── Reusable site <Select> options ────────────────────────────────────────\n const siteOptions = allSites.map((s) => (\n <SelectItem key={s.id} value={s.id}>\n {s.name}\n </SelectItem>\n ));\n\n const measurementRows = measurementsQuery.data?.items ?? [];\n\n return (\n <PageLayout\n title=\"Monitoring\"\n description=\"Environmental sensors, readings and alert thresholds across every monitored site.\"\n actions={\n tab === 'measurements' ? (\n <Button\n onClick={() => {\n setError(null);\n setMeasureOpen(true);\n }}\n >\n <Plus className=\"mr-1.5 h-4 w-4\" /> Record measurement\n </Button>\n ) : tab === 'sensors' ? (\n <Button\n onClick={() => {\n setError(null);\n setSensorOpen(true);\n }}\n >\n <Plus className=\"mr-1.5 h-4 w-4\" /> Add sensor\n </Button>\n ) : (\n <Button\n onClick={() => {\n setError(null);\n setThresholdOpen(true);\n }}\n >\n <Plus className=\"mr-1.5 h-4 w-4\" /> Add threshold\n </Button>\n )\n }\n >\n <Tabs items={tabItems} value={tab} onChange={setTab} className=\"mb-5\" />\n\n {/* ── Measurements ─────────────────────────────────────────────────── */}\n {tab === 'measurements' && (\n <>\n <div className=\"mb-5 flex flex-wrap items-center gap-3\">\n <Select\n value={measSiteId}\n onValueChange={(v) => {\n setMeasSiteId(v);\n measPager.reset();\n }}\n >\n <SelectTrigger className=\"w-52\">\n <SelectValue placeholder=\"All sites\" />\n </SelectTrigger>\n <SelectContent>\n <SelectItem value={ALL}>All sites</SelectItem>\n {siteOptions}\n </SelectContent>\n </Select>\n <FilterTabs\n options={BREACH_FILTERS}\n value={breachFilter}\n onChange={(v) => {\n setBreachFilter(v);\n measPager.reset();\n }}\n />\n </div>\n\n {measurementsQuery.isLoading ? (\n <LoadingView label=\"Loading measurements…\" />\n ) : measurementsQuery.isError ? (\n <ErrorView\n message=\"Couldn't load measurements.\"\n onRetry={() => measurementsQuery.refetch()}\n />\n ) : measurementRows.length === 0 ? (\n <EmptyState\n illustration=\"empty-data\"\n icon={<Activity className=\"h-10 w-10\" />}\n title=\"No measurements\"\n description={\n measSiteId === ALL && breachFilter === 'ALL'\n ? 'Record a measurement to start building a reading history.'\n : 'No measurements match these filters.'\n }\n action={\n <Button\n onClick={() => {\n setError(null);\n setMeasureOpen(true);\n }}\n >\n <Plus className=\"mr-1.5 h-4 w-4\" /> Record measurement\n </Button>\n }\n />\n ) : (\n <>\n <DataTable columns={measurementColumns} rows={measurementRows} rowKey={(m) => m.id} />\n <Pagination\n page={measPager.page}\n pageSize={PAGE_SIZE}\n itemCount={measurementRows.length}\n hasMore={measurementsQuery.data?.pageInfo?.hasMore ?? false}\n onPageChange={(target) =>\n measPager.onPageChange(target, measurementsQuery.data?.pageInfo?.cursor)\n }\n />\n </>\n )}\n </>\n )}\n\n {/* ── Sensors ──────────────────────────────────────────────────────── */}\n {tab === 'sensors' && (\n <>\n <div className=\"mb-5 flex flex-wrap items-center gap-3\">\n <Select value={sensorSiteId} onValueChange={setSensorSiteId}>\n <SelectTrigger className=\"w-52\">\n <SelectValue placeholder=\"All sites\" />\n </SelectTrigger>\n <SelectContent>\n <SelectItem value={ALL}>All sites</SelectItem>\n {siteOptions}\n </SelectContent>\n </Select>\n <Select\n value={sensorStatusFilter}\n onValueChange={(v) => setSensorStatusFilter(v as EcoSensorStatus | 'ALL')}\n >\n <SelectTrigger className=\"w-44\">\n <SelectValue placeholder=\"All statuses\" />\n </SelectTrigger>\n <SelectContent>\n <SelectItem value={ALL}>All statuses</SelectItem>\n {SENSOR_STATUSES.map((st) => (\n <SelectItem key={st} value={st}>\n {formatEnumLabel(st)}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </div>\n\n {sensorsQuery.isLoading ? (\n <LoadingView label=\"Loading sensors…\" />\n ) : sensorsQuery.isError ? (\n <ErrorView message=\"Couldn't load sensors.\" onRetry={() => sensorsQuery.refetch()} />\n ) : sensors.length === 0 ? (\n <EmptyState\n illustration=\"empty-data\"\n icon={<Radio className=\"h-10 w-10\" />}\n title=\"No sensors\"\n description={\n sensorSiteId === ALL && sensorStatusFilter === ALL\n ? 'Add a sensor to begin streaming or logging readings.'\n : 'No sensors match these filters.'\n }\n action={\n <Button\n onClick={() => {\n setError(null);\n setSensorOpen(true);\n }}\n >\n <Plus className=\"mr-1.5 h-4 w-4\" /> Add sensor\n </Button>\n }\n />\n ) : (\n <DataTable columns={sensorColumns} rows={sensors} rowKey={(s) => s.id} />\n )}\n </>\n )}\n\n {/* ── Thresholds ───────────────────────────────────────────────────── */}\n {tab === 'thresholds' && (\n <>\n <div className=\"mb-5 flex flex-wrap items-center gap-3\">\n <Select value={thresholdSiteId} onValueChange={setThresholdSiteId}>\n <SelectTrigger className=\"w-52\">\n <SelectValue placeholder=\"All sites\" />\n </SelectTrigger>\n <SelectContent>\n <SelectItem value={ALL}>All sites</SelectItem>\n {siteOptions}\n </SelectContent>\n </Select>\n </div>\n\n {thresholdsQuery.isLoading ? (\n <LoadingView label=\"Loading thresholds…\" />\n ) : thresholdsQuery.isError ? (\n <ErrorView\n message=\"Couldn't load thresholds.\"\n onRetry={() => thresholdsQuery.refetch()}\n />\n ) : thresholds.length === 0 ? (\n <EmptyState\n illustration=\"empty-data\"\n icon={<Gauge className=\"h-10 w-10\" />}\n title=\"No thresholds\"\n description={\n thresholdSiteId === ALL\n ? 'Define a threshold to flag readings that drift out of range.'\n : 'This site has no thresholds yet.'\n }\n action={\n <Button\n onClick={() => {\n setError(null);\n setThresholdOpen(true);\n }}\n >\n <Plus className=\"mr-1.5 h-4 w-4\" /> Add threshold\n </Button>\n }\n />\n ) : (\n <DataTable columns={thresholdColumns} rows={thresholds} rowKey={(t) => t.id} />\n )}\n </>\n )}\n\n {/* ── Record measurement dialog ────────────────────────────────────── */}\n <ResponsiveDialog\n open={measureOpen}\n onOpenChange={(open) => {\n setMeasureOpen(open);\n if (!open) setError(null);\n }}\n >\n <ResponsiveDialogContent>\n <ResponsiveDialogHeader>\n <ResponsiveDialogTitle>Record measurement</ResponsiveDialogTitle>\n <ResponsiveDialogDescription>\n Log a reading. It is checked against the site&apos;s active thresholds and flagged if\n it breaches.\n </ResponsiveDialogDescription>\n </ResponsiveDialogHeader>\n <div className=\"grid gap-4 py-2\">\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"mSite\">Site *</Label>\n <Select\n value={measureForm.siteId}\n onValueChange={(v) => setMeasureForm({ ...measureForm, siteId: v, sensorId: '' })}\n >\n <SelectTrigger id=\"mSite\">\n <SelectValue placeholder=\"Select a site\" />\n </SelectTrigger>\n <SelectContent>{siteOptions}</SelectContent>\n </Select>\n </div>\n <div className=\"grid gap-2 sm:grid-cols-3\">\n <div className=\"grid gap-1.5 sm:col-span-1\">\n <Label htmlFor=\"mParam\">Parameter *</Label>\n <Input\n id=\"mParam\"\n value={measureForm.parameter}\n onChange={(e) => setMeasureForm({ ...measureForm, parameter: e.target.value })}\n placeholder=\"PM2.5\"\n />\n </div>\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"mValue\">Value *</Label>\n <Input\n id=\"mValue\"\n type=\"number\"\n step=\"any\"\n value={measureForm.value}\n onChange={(e) => setMeasureForm({ ...measureForm, value: e.target.value })}\n placeholder=\"42\"\n />\n </div>\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"mUnit\">Unit *</Label>\n <Input\n id=\"mUnit\"\n value={measureForm.unit}\n onChange={(e) => setMeasureForm({ ...measureForm, unit: e.target.value })}\n placeholder=\"µg/m³\"\n />\n </div>\n </div>\n {measureForm.siteId && dialogSensorList.length > 0 && (\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"mSensor\">Sensor (optional)</Label>\n <Select\n value={measureForm.sensorId}\n onValueChange={(v) => setMeasureForm({ ...measureForm, sensorId: v })}\n >\n <SelectTrigger id=\"mSensor\">\n <SelectValue placeholder=\"Manual entry\" />\n </SelectTrigger>\n <SelectContent>\n {dialogSensorList.map((s) => (\n <SelectItem key={s.id} value={s.id}>\n {s.name}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n <p className=\"text-xs text-text-tertiary\">\n Select a specific sensor for context, or leave blank for a site-level reading.\n </p>\n </div>\n )}\n {error && <p className=\"text-sm text-status-error-text\">{error}</p>}\n </div>\n <ResponsiveDialogFooter>\n <Button variant=\"outline\" onClick={() => setMeasureOpen(false)}>\n Cancel\n </Button>\n <Button onClick={handleRecordMeasurement} disabled={recordMeasurement.isPending}>\n {recordMeasurement.isPending ? 'Recording…' : 'Record'}\n </Button>\n </ResponsiveDialogFooter>\n </ResponsiveDialogContent>\n </ResponsiveDialog>\n\n {/* ── Add sensor dialog ────────────────────────────────────────────── */}\n <ResponsiveDialog\n open={sensorOpen}\n onOpenChange={(open) => {\n setSensorOpen(open);\n if (!open) setError(null);\n }}\n >\n <ResponsiveDialogContent>\n <ResponsiveDialogHeader>\n <ResponsiveDialogTitle>Add sensor</ResponsiveDialogTitle>\n <ResponsiveDialogDescription>\n Register a sensor and attribute it to a monitored site.\n </ResponsiveDialogDescription>\n </ResponsiveDialogHeader>\n <div className=\"grid gap-4 py-2\">\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"sSite\">Site *</Label>\n <Select\n value={sensorForm.siteId}\n onValueChange={(v) => setSensorForm({ ...sensorForm, siteId: v })}\n >\n <SelectTrigger id=\"sSite\">\n <SelectValue placeholder=\"Select a site\" />\n </SelectTrigger>\n <SelectContent>{siteOptions}</SelectContent>\n </Select>\n </div>\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"sName\">Name *</Label>\n <Input\n id=\"sName\"\n value={sensorForm.name}\n onChange={(e) => setSensorForm({ ...sensorForm, name: e.target.value })}\n placeholder=\"North gate PM2.5\"\n />\n </div>\n <div className=\"grid gap-2 sm:grid-cols-2\">\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"sKind\">Kind</Label>\n <Select\n value={sensorForm.kind}\n onValueChange={(v) => setSensorForm({ ...sensorForm, kind: v as EcoSensorKind })}\n >\n <SelectTrigger id=\"sKind\">\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n {SENSOR_KINDS.map((k) => (\n <SelectItem key={k} value={k}>\n {formatEnumLabel(k)}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </div>\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"sUnit\">Unit</Label>\n <Input\n id=\"sUnit\"\n value={sensorForm.unit}\n onChange={(e) => setSensorForm({ ...sensorForm, unit: e.target.value })}\n placeholder=\"µg/m³\"\n />\n </div>\n </div>\n {error && <p className=\"text-sm text-status-error-text\">{error}</p>}\n </div>\n <ResponsiveDialogFooter>\n <Button variant=\"outline\" onClick={() => setSensorOpen(false)}>\n Cancel\n </Button>\n <Button onClick={handleAddSensor} disabled={createSensor.isPending}>\n {createSensor.isPending ? 'Adding…' : 'Add sensor'}\n </Button>\n </ResponsiveDialogFooter>\n </ResponsiveDialogContent>\n </ResponsiveDialog>\n\n {/* ── Add threshold dialog ─────────────────────────────────────────── */}\n <ResponsiveDialog\n open={thresholdOpen}\n onOpenChange={(open) => {\n setThresholdOpen(open);\n if (!open) setError(null);\n }}\n >\n <ResponsiveDialogContent>\n <ResponsiveDialogHeader>\n <ResponsiveDialogTitle>Add alert threshold</ResponsiveDialogTitle>\n <ResponsiveDialogDescription>\n Flag readings that drift out of an acceptable range.\n </ResponsiveDialogDescription>\n </ResponsiveDialogHeader>\n <div className=\"grid gap-4 py-2\">\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"tSite\">Site *</Label>\n <Select\n value={thresholdForm.siteId}\n onValueChange={(v) => setThresholdForm({ ...thresholdForm, siteId: v })}\n >\n <SelectTrigger id=\"tSite\">\n <SelectValue placeholder=\"Select a site\" />\n </SelectTrigger>\n <SelectContent>{siteOptions}</SelectContent>\n </Select>\n </div>\n <div className=\"grid gap-2 sm:grid-cols-2\">\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"tParam\">Parameter *</Label>\n <Input\n id=\"tParam\"\n value={thresholdForm.parameter}\n onChange={(e) =>\n setThresholdForm({ ...thresholdForm, parameter: e.target.value })\n }\n placeholder=\"PM2.5\"\n />\n </div>\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"tUnit\">Unit *</Label>\n <Input\n id=\"tUnit\"\n value={thresholdForm.unit}\n onChange={(e) => setThresholdForm({ ...thresholdForm, unit: e.target.value })}\n placeholder=\"µg/m³\"\n />\n </div>\n </div>\n <div className=\"grid gap-2 sm:grid-cols-2\">\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"tDir\">Direction</Label>\n <Select\n value={thresholdForm.direction}\n onValueChange={(v) =>\n setThresholdForm({ ...thresholdForm, direction: v as EcoThresholdDirection })\n }\n >\n <SelectTrigger id=\"tDir\">\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n {THRESHOLD_DIRECTIONS.map((d) => (\n <SelectItem key={d} value={d}>\n {formatEnumLabel(d)}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </div>\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"tSev\">Severity</Label>\n <Select\n value={thresholdForm.severity}\n onValueChange={(v) =>\n setThresholdForm({ ...thresholdForm, severity: v as EcoThresholdSeverity })\n }\n >\n <SelectTrigger id=\"tSev\">\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n {THRESHOLD_SEVERITIES.map((s) => (\n <SelectItem key={s} value={s}>\n {formatEnumLabel(s)}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </div>\n </div>\n {thresholdForm.direction === 'OUTSIDE_RANGE' ? (\n <div className=\"grid gap-2 sm:grid-cols-2\">\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"tMin\">Min value</Label>\n <Input\n id=\"tMin\"\n type=\"number\"\n step=\"any\"\n value={thresholdForm.minValue}\n onChange={(e) =>\n setThresholdForm({ ...thresholdForm, minValue: e.target.value })\n }\n />\n </div>\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"tMax\">Max value</Label>\n <Input\n id=\"tMax\"\n type=\"number\"\n step=\"any\"\n value={thresholdForm.maxValue}\n onChange={(e) =>\n setThresholdForm({ ...thresholdForm, maxValue: e.target.value })\n }\n />\n </div>\n </div>\n ) : (\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"tLimit\">Limit value</Label>\n <Input\n id=\"tLimit\"\n type=\"number\"\n step=\"any\"\n value={thresholdForm.limitValue}\n onChange={(e) =>\n setThresholdForm({ ...thresholdForm, limitValue: e.target.value })\n }\n placeholder=\"35\"\n />\n </div>\n )}\n {error && <p className=\"text-sm text-status-error-text\">{error}</p>}\n </div>\n <ResponsiveDialogFooter>\n <Button variant=\"outline\" onClick={() => setThresholdOpen(false)}>\n Cancel\n </Button>\n <Button onClick={handleAddThreshold} disabled={createThreshold.isPending}>\n {createThreshold.isPending ? 'Adding…' : 'Add threshold'}\n </Button>\n </ResponsiveDialogFooter>\n </ResponsiveDialogContent>\n </ResponsiveDialog>\n </PageLayout>\n );\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@burdenoff/microfe-ecoimpacthub",
3
- "version": "2026.720.1",
3
+ "version": "2026.720.3",
4
4
  "description": "Burdenoff EcoImpactHub Microfrontend",
5
5
  "type": "module",
6
6
  "files": [