@burdenoff/microfe-ecoimpacthub 2026.704.1 → 2026.704.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.
- package/dist/ecoimpacthub/components/EcoDiscussion.js +109 -0
- package/dist/ecoimpacthub/components/EcoDiscussion.js.map +1 -0
- package/dist/ecoimpacthub/components/EcoTags.js +149 -0
- package/dist/ecoimpacthub/components/EcoTags.js.map +1 -0
- package/dist/ecoimpacthub/components/EvidenceUpload.js +93 -0
- package/dist/ecoimpacthub/components/EvidenceUpload.js.map +1 -0
- package/dist/ecoimpacthub/components/FileLink.js +36 -0
- package/dist/ecoimpacthub/components/FileLink.js.map +1 -0
- package/dist/ecoimpacthub/components/PageLayout.js +2 -2
- package/dist/ecoimpacthub/components/PageLayout.js.map +1 -1
- package/dist/ecoimpacthub/hooks/index.js +3 -0
- package/dist/ecoimpacthub/hooks/useCursorPager.js +1 -1
- package/dist/ecoimpacthub/hooks/useCursorPager.js.map +1 -1
- package/dist/ecoimpacthub/hooks/useEcoComments.js +45 -0
- package/dist/ecoimpacthub/hooks/useEcoComments.js.map +1 -0
- package/dist/ecoimpacthub/hooks/useEcoFiles.js +81 -0
- package/dist/ecoimpacthub/hooks/useEcoFiles.js.map +1 -0
- package/dist/ecoimpacthub/hooks/useEcoImpactHubApi.js +169 -166
- package/dist/ecoimpacthub/hooks/useEcoImpactHubApi.js.map +1 -1
- package/dist/ecoimpacthub/hooks/useEcoTags.js +89 -0
- package/dist/ecoimpacthub/hooks/useEcoTags.js.map +1 -0
- package/dist/ecoimpacthub/pages/observations/ObservationsPage.js +124 -101
- package/dist/ecoimpacthub/pages/observations/ObservationsPage.js.map +1 -1
- package/dist/ecoimpacthub/pages/reports/ReportDetailPage.js +153 -126
- package/dist/ecoimpacthub/pages/reports/ReportDetailPage.js.map +1 -1
- package/dist/ecoimpacthub/pages/sites/SiteDetailPage.js +241 -230
- package/dist/ecoimpacthub/pages/sites/SiteDetailPage.js.map +1 -1
- package/dist/ecoimpacthub/pages/species/SpeciesDetailPage.js +260 -225
- package/dist/ecoimpacthub/pages/species/SpeciesDetailPage.js.map +1 -1
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SiteDetailPage.js","names":[],"sources":["../../../../src/ecoimpacthub/pages/sites/SiteDetailPage.tsx"],"sourcesContent":["/**\n * Site detail — sensors, alert thresholds, recent measurements & site actions.\n * @module ecoimpacthub/pages/sites\n *\n * Reads `ecoSite` (+ nested sensors/thresholds) and `ecoListMeasurements`;\n * writes `ecoCreateSensor`, `ecoSetSensorStatus`, `ecoCreateThreshold`,\n * `ecoSetThresholdActive`, `ecoRecordMeasurement`, `ecoArchiveSite`.\n */\n\nimport { useState } from 'react';\nimport { useParams } from 'react-router-dom';\nimport { ArrowLeft, Plus, Radio, Gauge, Activity, Archive } from 'lucide-react';\nimport {\n Button,\n Input,\n Label,\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n GlassCard,\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 { LoadingView, ErrorView, Section } from '../../components/StateViews';\nimport { EmptyState } from '../../components/EmptyState';\nimport { useEcoImpactHub } from '../../context';\nimport {\n useEcoSite,\n useEcoMeasurements,\n useEcoCreateSensor,\n useEcoSetSensorStatus,\n useEcoCreateThreshold,\n useEcoSetThresholdActive,\n useEcoRecordMeasurement,\n useEcoArchiveSite,\n} from '../../hooks';\nimport {\n formatDateTime,\n formatEnumLabel,\n formatNumber,\n formatCoords,\n} from '../../utils/formatters';\nimport type {\n EcoSensor,\n EcoSensorKind,\n EcoSensorStatus,\n EcoAlertThreshold,\n EcoMeasurement,\n EcoThresholdDirection,\n EcoThresholdSeverity,\n} from '../../types';\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\nexport function SiteDetailPage() {\n const { id } = useParams<{ id: string }>();\n const { navigateTo } = useEcoImpactHub();\n const { data: site, isLoading, isError, refetch } = useEcoSite(id);\n const measurements = useEcoMeasurements({ siteId: id, pagination: { limit: 10, offset: 0 } });\n\n const createSensor = useEcoCreateSensor();\n const setSensorStatus = useEcoSetSensorStatus();\n const createThreshold = useEcoCreateThreshold();\n const setThresholdActive = useEcoSetThresholdActive();\n const recordMeasurement = useEcoRecordMeasurement();\n const archiveSite = useEcoArchiveSite();\n\n const [sensorOpen, setSensorOpen] = useState(false);\n const [sensorForm, setSensorForm] = useState({\n name: '',\n kind: 'AIR_QUALITY' as EcoSensorKind,\n unit: '',\n hardwareId: '',\n });\n const [thresholdOpen, setThresholdOpen] = useState(false);\n const [thresholdForm, setThresholdForm] = useState({\n parameter: '',\n unit: '',\n direction: 'ABOVE' as EcoThresholdDirection,\n severity: 'WARNING' as EcoThresholdSeverity,\n limitValue: '',\n minValue: '',\n maxValue: '',\n });\n const [measureOpen, setMeasureOpen] = useState(false);\n const [measureForm, setMeasureForm] = useState({\n parameter: '',\n value: '',\n unit: '',\n sensorId: '',\n });\n const [error, setError] = useState<string | null>(null);\n\n if (isLoading) {\n return (\n <PageLayout title=\"Site\">\n <LoadingView />\n </PageLayout>\n );\n }\n if (isError || !site) {\n return (\n <PageLayout title=\"Site\">\n <ErrorView message=\"This site could not be loaded.\" onRetry={() => refetch()} />\n </PageLayout>\n );\n }\n\n const sensors = site.sensors ?? [];\n const thresholds = site.thresholds ?? [];\n const measurementRows = measurements.data?.items ?? [];\n\n const handleAddSensor = async () => {\n if (!id || !sensorForm.name.trim()) {\n setError('Sensor name is required.');\n return;\n }\n setError(null);\n try {\n await createSensor.mutateAsync({\n name: sensorForm.name.trim(),\n siteId: id,\n kind: sensorForm.kind,\n unit: sensorForm.unit.trim() || null,\n hardwareId: sensorForm.hardwareId.trim() || null,\n });\n setSensorOpen(false);\n setSensorForm({ name: '', kind: 'AIR_QUALITY', unit: '', hardwareId: '' });\n } catch (err) {\n setError(err instanceof Error ? err.message : 'Failed to add sensor.');\n }\n };\n\n const handleAddThreshold = async () => {\n if (!id || !thresholdForm.parameter.trim() || !thresholdForm.unit.trim()) {\n setError('Parameter and unit are required.');\n return;\n }\n setError(null);\n try {\n await createThreshold.mutateAsync({\n siteId: id,\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 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 const handleRecordMeasurement = async () => {\n if (!id || !measureForm.parameter.trim() || !measureForm.unit.trim() || !measureForm.value) {\n setError('Parameter, value and unit are required.');\n return;\n }\n setError(null);\n try {\n await recordMeasurement.mutateAsync({\n siteId: id,\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({ parameter: '', value: '', unit: '', sensorId: '' });\n measurements.refetch();\n } catch (err) {\n setError(err instanceof Error ? err.message : 'Failed to record measurement.');\n }\n };\n\n const handleArchive = async () => {\n if (!id) return;\n setError(null);\n try {\n await archiveSite.mutateAsync(id);\n navigateTo('sites');\n } catch (err) {\n setError(err instanceof Error ? err.message : 'Failed to archive site.');\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: '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: '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 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) => (m.isBreach ? <StatusBadge status=\"BREACH\" /> : <StatusBadge status=\"OK\" />),\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 return (\n <PageLayout\n title={site.name}\n description={[site.code, formatEnumLabel(site.type)].filter(Boolean).join(' · ')}\n actions={\n <>\n <StatusBadge status={site.status} />\n <Button variant=\"ghost\" onClick={() => navigateTo('sites')}>\n <ArrowLeft className=\"mr-1.5 h-4 w-4\" /> Back\n </Button>\n </>\n }\n >\n {/* Overview */}\n <div className=\"grid grid-cols-1 gap-4 lg:grid-cols-3\">\n <GlassCard treatment=\"glass\" className=\"p-6 lg:col-span-2\">\n <dl className=\"grid grid-cols-2 gap-x-6 gap-y-4 sm:grid-cols-3\">\n <div>\n <dt className=\"text-xs text-text-tertiary\">Type</dt>\n <dd className=\"mt-0.5 text-sm text-text-primary\">{formatEnumLabel(site.type)}</dd>\n </div>\n <div>\n <dt className=\"text-xs text-text-tertiary\">Region</dt>\n <dd className=\"mt-0.5 text-sm text-text-primary\">\n {[site.region, site.country].filter(Boolean).join(', ') || '—'}\n </dd>\n </div>\n <div>\n <dt className=\"text-xs text-text-tertiary\">Area</dt>\n <dd className=\"mt-0.5 text-sm text-text-primary tabular-nums\">\n {site.areaHectares != null ? `${formatNumber(site.areaHectares)} ha` : '—'}\n </dd>\n </div>\n <div>\n <dt className=\"text-xs text-text-tertiary\">Coordinates</dt>\n <dd className=\"mt-0.5 text-sm text-text-primary tabular-nums\">\n {formatCoords(site.latitude, site.longitude)}\n </dd>\n </div>\n <div>\n <dt className=\"text-xs text-text-tertiary\">Sensors</dt>\n <dd className=\"mt-0.5 text-sm text-text-primary tabular-nums\">{sensors.length}</dd>\n </div>\n <div>\n <dt className=\"text-xs text-text-tertiary\">Thresholds</dt>\n <dd className=\"mt-0.5 text-sm text-text-primary tabular-nums\">{thresholds.length}</dd>\n </div>\n </dl>\n {site.description && (\n <p className=\"mt-5 rounded-lg bg-bg-sunken/60 p-3 text-sm text-text-secondary\">\n {site.description}\n </p>\n )}\n </GlassCard>\n\n <GlassCard treatment=\"glass\" className=\"flex flex-col gap-3 p-6\">\n <p className=\"text-xs text-text-tertiary\">Quick actions</p>\n <Button onClick={() => setMeasureOpen(true)}>\n <Activity className=\"mr-1.5 h-4 w-4\" /> Record measurement\n </Button>\n <Button variant=\"outline\" onClick={() => setSensorOpen(true)}>\n <Radio className=\"mr-1.5 h-4 w-4\" /> Add sensor\n </Button>\n <Button variant=\"outline\" onClick={() => setThresholdOpen(true)}>\n <Gauge className=\"mr-1.5 h-4 w-4\" /> Add threshold\n </Button>\n {site.status !== 'ARCHIVED' && (\n <Button\n variant=\"ghost\"\n onClick={handleArchive}\n disabled={archiveSite.isPending}\n className=\"text-status-error-text hover:text-status-error-text\"\n >\n <Archive className=\"mr-1.5 h-4 w-4\" /> Archive site\n </Button>\n )}\n </GlassCard>\n </div>\n\n {error && <p className=\"mt-4 text-sm text-status-error-text\">{error}</p>}\n\n {/* Sensors */}\n <Section\n title=\"Sensors\"\n description=\"Instruments streaming or recording readings for this site.\"\n action={\n <Button size=\"sm\" onClick={() => setSensorOpen(true)}>\n <Plus className=\"mr-1.5 h-4 w-4\" /> Add sensor\n </Button>\n }\n >\n {sensors.length === 0 ? (\n <EmptyState\n illustration=\"empty-data\"\n title=\"No sensors\"\n description=\"Add a sensor to begin monitoring.\"\n />\n ) : (\n <DataTable columns={sensorColumns} rows={sensors} rowKey={(s) => s.id} />\n )}\n </Section>\n\n {/* Thresholds */}\n <Section\n title=\"Alert thresholds\"\n description=\"Rules that flag measurements as breaches when values drift out of range.\"\n action={\n <Button size=\"sm\" onClick={() => setThresholdOpen(true)}>\n <Plus className=\"mr-1.5 h-4 w-4\" /> Add threshold\n </Button>\n }\n >\n {thresholds.length === 0 ? (\n <EmptyState\n illustration=\"empty-data\"\n title=\"No thresholds\"\n description=\"Define a threshold to flag breaches.\"\n />\n ) : (\n <DataTable columns={thresholdColumns} rows={thresholds} rowKey={(t) => t.id} />\n )}\n </Section>\n\n {/* Recent measurements */}\n <Section title=\"Recent measurements\" description=\"The latest readings recorded at this site.\">\n {measurementRows.length === 0 ? (\n <EmptyState\n illustration=\"empty-data\"\n title=\"No measurements yet\"\n description=\"Record a measurement to see it here.\"\n />\n ) : (\n <DataTable columns={measurementColumns} rows={measurementRows} rowKey={(m) => m.id} />\n )}\n </Section>\n\n {/* Add sensor dialog */}\n <ResponsiveDialog open={sensorOpen} onOpenChange={setSensorOpen}>\n <ResponsiveDialogContent>\n <ResponsiveDialogHeader>\n <ResponsiveDialogTitle>Add sensor</ResponsiveDialogTitle>\n <ResponsiveDialogDescription>\n Register a sensor at {site.name}.\n </ResponsiveDialogDescription>\n </ResponsiveDialogHeader>\n <div className=\"grid gap-4 py-2\">\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"sensorName\">Name *</Label>\n <Input\n id=\"sensorName\"\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=\"sensorKind\">Kind</Label>\n <Select\n value={sensorForm.kind}\n onValueChange={(v) => setSensorForm({ ...sensorForm, kind: v as EcoSensorKind })}\n >\n <SelectTrigger id=\"sensorKind\">\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=\"sensorUnit\">Unit</Label>\n <Input\n id=\"sensorUnit\"\n value={sensorForm.unit}\n onChange={(e) => setSensorForm({ ...sensorForm, unit: e.target.value })}\n placeholder=\"µg/m³\"\n />\n </div>\n </div>\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"sensorHw\">Hardware ID</Label>\n <Input\n id=\"sensorHw\"\n value={sensorForm.hardwareId}\n onChange={(e) => setSensorForm({ ...sensorForm, hardwareId: e.target.value })}\n placeholder=\"Optional device identifier\"\n />\n </div>\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 open={thresholdOpen} onOpenChange={setThresholdOpen}>\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-2 sm:grid-cols-2\">\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"thParam\">Parameter *</Label>\n <Input\n id=\"thParam\"\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=\"thUnit\">Unit *</Label>\n <Input\n id=\"thUnit\"\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=\"thDir\">Direction</Label>\n <Select\n value={thresholdForm.direction}\n onValueChange={(v) =>\n setThresholdForm({ ...thresholdForm, direction: v as EcoThresholdDirection })\n }\n >\n <SelectTrigger id=\"thDir\">\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=\"thSev\">Severity</Label>\n <Select\n value={thresholdForm.severity}\n onValueChange={(v) =>\n setThresholdForm({ ...thresholdForm, severity: v as EcoThresholdSeverity })\n }\n >\n <SelectTrigger id=\"thSev\">\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=\"thMin\">Min value</Label>\n <Input\n id=\"thMin\"\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=\"thMax\">Max value</Label>\n <Input\n id=\"thMax\"\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=\"thLimit\">Limit value</Label>\n <Input\n id=\"thLimit\"\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 </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\n {/* Record measurement dialog */}\n <ResponsiveDialog open={measureOpen} onOpenChange={setMeasureOpen}>\n <ResponsiveDialogContent>\n <ResponsiveDialogHeader>\n <ResponsiveDialogTitle>Record measurement</ResponsiveDialogTitle>\n <ResponsiveDialogDescription>\n Log a reading. It is checked against active thresholds and flagged if it breaches.\n </ResponsiveDialogDescription>\n </ResponsiveDialogHeader>\n <div className=\"grid gap-4 py-2\">\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 {sensors.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 {sensors.map((s) => (\n <SelectItem key={s.id} value={s.id}>\n {s.name}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </div>\n )}\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 </PageLayout>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"SiteDetailPage.js","names":[],"sources":["../../../../src/ecoimpacthub/pages/sites/SiteDetailPage.tsx"],"sourcesContent":["/**\n * Site detail — sensors, alert thresholds, recent measurements & site actions.\n * @module ecoimpacthub/pages/sites\n *\n * Reads `ecoSite` (+ nested sensors/thresholds) and `ecoListMeasurements`;\n * writes `ecoCreateSensor`, `ecoSetSensorStatus`, `ecoCreateThreshold`,\n * `ecoSetThresholdActive`, `ecoRecordMeasurement`, `ecoArchiveSite`.\n */\n\nimport { useState } from 'react';\nimport { useParams } from 'react-router-dom';\nimport { ArrowLeft, Plus, Radio, Gauge, Activity, Archive } from 'lucide-react';\nimport {\n Button,\n Input,\n Label,\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n GlassCard,\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 { LoadingView, ErrorView, Section } from '../../components/StateViews';\nimport { EmptyState } from '../../components/EmptyState';\nimport { EcoTags } from '../../components/EcoTags';\nimport { useEcoImpactHub } from '../../context';\nimport {\n useEcoSite,\n useEcoMeasurements,\n useEcoCreateSensor,\n useEcoSetSensorStatus,\n useEcoCreateThreshold,\n useEcoSetThresholdActive,\n useEcoRecordMeasurement,\n useEcoArchiveSite,\n} from '../../hooks';\nimport {\n formatDateTime,\n formatEnumLabel,\n formatNumber,\n formatCoords,\n} from '../../utils/formatters';\nimport type {\n EcoSensor,\n EcoSensorKind,\n EcoSensorStatus,\n EcoAlertThreshold,\n EcoMeasurement,\n EcoThresholdDirection,\n EcoThresholdSeverity,\n} from '../../types';\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\nexport function SiteDetailPage() {\n const { id } = useParams<{ id: string }>();\n const { navigateTo } = useEcoImpactHub();\n const { data: site, isLoading, isError, refetch } = useEcoSite(id);\n const measurements = useEcoMeasurements({ siteId: id, pagination: { limit: 10, offset: 0 } });\n\n const createSensor = useEcoCreateSensor();\n const setSensorStatus = useEcoSetSensorStatus();\n const createThreshold = useEcoCreateThreshold();\n const setThresholdActive = useEcoSetThresholdActive();\n const recordMeasurement = useEcoRecordMeasurement();\n const archiveSite = useEcoArchiveSite();\n\n const [sensorOpen, setSensorOpen] = useState(false);\n const [sensorForm, setSensorForm] = useState({\n name: '',\n kind: 'AIR_QUALITY' as EcoSensorKind,\n unit: '',\n hardwareId: '',\n });\n const [thresholdOpen, setThresholdOpen] = useState(false);\n const [thresholdForm, setThresholdForm] = useState({\n parameter: '',\n unit: '',\n direction: 'ABOVE' as EcoThresholdDirection,\n severity: 'WARNING' as EcoThresholdSeverity,\n limitValue: '',\n minValue: '',\n maxValue: '',\n });\n const [measureOpen, setMeasureOpen] = useState(false);\n const [measureForm, setMeasureForm] = useState({\n parameter: '',\n value: '',\n unit: '',\n sensorId: '',\n });\n const [error, setError] = useState<string | null>(null);\n\n if (isLoading) {\n return (\n <PageLayout title=\"Site\">\n <LoadingView />\n </PageLayout>\n );\n }\n if (isError || !site) {\n return (\n <PageLayout title=\"Site\">\n <ErrorView message=\"This site could not be loaded.\" onRetry={() => refetch()} />\n </PageLayout>\n );\n }\n\n const sensors = site.sensors ?? [];\n const thresholds = site.thresholds ?? [];\n const measurementRows = measurements.data?.items ?? [];\n\n const handleAddSensor = async () => {\n if (!id || !sensorForm.name.trim()) {\n setError('Sensor name is required.');\n return;\n }\n setError(null);\n try {\n await createSensor.mutateAsync({\n name: sensorForm.name.trim(),\n siteId: id,\n kind: sensorForm.kind,\n unit: sensorForm.unit.trim() || null,\n hardwareId: sensorForm.hardwareId.trim() || null,\n });\n setSensorOpen(false);\n setSensorForm({ name: '', kind: 'AIR_QUALITY', unit: '', hardwareId: '' });\n } catch (err) {\n setError(err instanceof Error ? err.message : 'Failed to add sensor.');\n }\n };\n\n const handleAddThreshold = async () => {\n if (!id || !thresholdForm.parameter.trim() || !thresholdForm.unit.trim()) {\n setError('Parameter and unit are required.');\n return;\n }\n setError(null);\n try {\n await createThreshold.mutateAsync({\n siteId: id,\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 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 const handleRecordMeasurement = async () => {\n if (!id || !measureForm.parameter.trim() || !measureForm.unit.trim() || !measureForm.value) {\n setError('Parameter, value and unit are required.');\n return;\n }\n setError(null);\n try {\n await recordMeasurement.mutateAsync({\n siteId: id,\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({ parameter: '', value: '', unit: '', sensorId: '' });\n measurements.refetch();\n } catch (err) {\n setError(err instanceof Error ? err.message : 'Failed to record measurement.');\n }\n };\n\n const handleArchive = async () => {\n if (!id) return;\n setError(null);\n try {\n await archiveSite.mutateAsync(id);\n navigateTo('sites');\n } catch (err) {\n setError(err instanceof Error ? err.message : 'Failed to archive site.');\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: '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: '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 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) => (m.isBreach ? <StatusBadge status=\"BREACH\" /> : <StatusBadge status=\"OK\" />),\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 return (\n <PageLayout\n title={site.name}\n description={[site.code, formatEnumLabel(site.type)].filter(Boolean).join(' · ')}\n actions={\n <>\n <StatusBadge status={site.status} />\n <Button variant=\"ghost\" onClick={() => navigateTo('sites')}>\n <ArrowLeft className=\"mr-1.5 h-4 w-4\" /> Back\n </Button>\n </>\n }\n >\n {/* Overview */}\n <div className=\"grid grid-cols-1 gap-4 lg:grid-cols-3\">\n <GlassCard treatment=\"glass\" className=\"p-6 lg:col-span-2\">\n <dl className=\"grid grid-cols-2 gap-x-6 gap-y-4 sm:grid-cols-3\">\n <div>\n <dt className=\"text-xs text-text-tertiary\">Type</dt>\n <dd className=\"mt-0.5 text-sm text-text-primary\">{formatEnumLabel(site.type)}</dd>\n </div>\n <div>\n <dt className=\"text-xs text-text-tertiary\">Region</dt>\n <dd className=\"mt-0.5 text-sm text-text-primary\">\n {[site.region, site.country].filter(Boolean).join(', ') || '—'}\n </dd>\n </div>\n <div>\n <dt className=\"text-xs text-text-tertiary\">Area</dt>\n <dd className=\"mt-0.5 text-sm text-text-primary tabular-nums\">\n {site.areaHectares != null ? `${formatNumber(site.areaHectares)} ha` : '—'}\n </dd>\n </div>\n <div>\n <dt className=\"text-xs text-text-tertiary\">Coordinates</dt>\n <dd className=\"mt-0.5 text-sm text-text-primary tabular-nums\">\n {formatCoords(site.latitude, site.longitude)}\n </dd>\n </div>\n <div>\n <dt className=\"text-xs text-text-tertiary\">Sensors</dt>\n <dd className=\"mt-0.5 text-sm text-text-primary tabular-nums\">{sensors.length}</dd>\n </div>\n <div>\n <dt className=\"text-xs text-text-tertiary\">Thresholds</dt>\n <dd className=\"mt-0.5 text-sm text-text-primary tabular-nums\">{thresholds.length}</dd>\n </div>\n </dl>\n {site.description && (\n <p className=\"mt-5 rounded-lg bg-bg-sunken/60 p-3 text-sm text-text-secondary\">\n {site.description}\n </p>\n )}\n <div className=\"mt-5 border-t border-border-seam pt-4\">\n <EcoTags entityType=\"ecoimpacthub-site\" entityId={site.id} />\n </div>\n </GlassCard>\n\n <GlassCard treatment=\"glass\" className=\"flex flex-col gap-3 p-6\">\n <p className=\"text-xs text-text-tertiary\">Quick actions</p>\n <Button onClick={() => setMeasureOpen(true)}>\n <Activity className=\"mr-1.5 h-4 w-4\" /> Record measurement\n </Button>\n <Button variant=\"outline\" onClick={() => setSensorOpen(true)}>\n <Radio className=\"mr-1.5 h-4 w-4\" /> Add sensor\n </Button>\n <Button variant=\"outline\" onClick={() => setThresholdOpen(true)}>\n <Gauge className=\"mr-1.5 h-4 w-4\" /> Add threshold\n </Button>\n {site.status !== 'ARCHIVED' && (\n <Button\n variant=\"ghost\"\n onClick={handleArchive}\n disabled={archiveSite.isPending}\n className=\"text-status-error-text hover:text-status-error-text\"\n >\n <Archive className=\"mr-1.5 h-4 w-4\" /> Archive site\n </Button>\n )}\n </GlassCard>\n </div>\n\n {error && <p className=\"mt-4 text-sm text-status-error-text\">{error}</p>}\n\n {/* Sensors */}\n <Section\n title=\"Sensors\"\n description=\"Instruments streaming or recording readings for this site.\"\n action={\n <Button size=\"sm\" onClick={() => setSensorOpen(true)}>\n <Plus className=\"mr-1.5 h-4 w-4\" /> Add sensor\n </Button>\n }\n >\n {sensors.length === 0 ? (\n <EmptyState\n illustration=\"empty-data\"\n title=\"No sensors\"\n description=\"Add a sensor to begin monitoring.\"\n />\n ) : (\n <DataTable columns={sensorColumns} rows={sensors} rowKey={(s) => s.id} />\n )}\n </Section>\n\n {/* Thresholds */}\n <Section\n title=\"Alert thresholds\"\n description=\"Rules that flag measurements as breaches when values drift out of range.\"\n action={\n <Button size=\"sm\" onClick={() => setThresholdOpen(true)}>\n <Plus className=\"mr-1.5 h-4 w-4\" /> Add threshold\n </Button>\n }\n >\n {thresholds.length === 0 ? (\n <EmptyState\n illustration=\"empty-data\"\n title=\"No thresholds\"\n description=\"Define a threshold to flag breaches.\"\n />\n ) : (\n <DataTable columns={thresholdColumns} rows={thresholds} rowKey={(t) => t.id} />\n )}\n </Section>\n\n {/* Recent measurements */}\n <Section title=\"Recent measurements\" description=\"The latest readings recorded at this site.\">\n {measurementRows.length === 0 ? (\n <EmptyState\n illustration=\"empty-data\"\n title=\"No measurements yet\"\n description=\"Record a measurement to see it here.\"\n />\n ) : (\n <DataTable columns={measurementColumns} rows={measurementRows} rowKey={(m) => m.id} />\n )}\n </Section>\n\n {/* Add sensor dialog */}\n <ResponsiveDialog open={sensorOpen} onOpenChange={setSensorOpen}>\n <ResponsiveDialogContent>\n <ResponsiveDialogHeader>\n <ResponsiveDialogTitle>Add sensor</ResponsiveDialogTitle>\n <ResponsiveDialogDescription>\n Register a sensor at {site.name}.\n </ResponsiveDialogDescription>\n </ResponsiveDialogHeader>\n <div className=\"grid gap-4 py-2\">\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"sensorName\">Name *</Label>\n <Input\n id=\"sensorName\"\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=\"sensorKind\">Kind</Label>\n <Select\n value={sensorForm.kind}\n onValueChange={(v) => setSensorForm({ ...sensorForm, kind: v as EcoSensorKind })}\n >\n <SelectTrigger id=\"sensorKind\">\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=\"sensorUnit\">Unit</Label>\n <Input\n id=\"sensorUnit\"\n value={sensorForm.unit}\n onChange={(e) => setSensorForm({ ...sensorForm, unit: e.target.value })}\n placeholder=\"µg/m³\"\n />\n </div>\n </div>\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"sensorHw\">Hardware ID</Label>\n <Input\n id=\"sensorHw\"\n value={sensorForm.hardwareId}\n onChange={(e) => setSensorForm({ ...sensorForm, hardwareId: e.target.value })}\n placeholder=\"Optional device identifier\"\n />\n </div>\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 open={thresholdOpen} onOpenChange={setThresholdOpen}>\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-2 sm:grid-cols-2\">\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"thParam\">Parameter *</Label>\n <Input\n id=\"thParam\"\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=\"thUnit\">Unit *</Label>\n <Input\n id=\"thUnit\"\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=\"thDir\">Direction</Label>\n <Select\n value={thresholdForm.direction}\n onValueChange={(v) =>\n setThresholdForm({ ...thresholdForm, direction: v as EcoThresholdDirection })\n }\n >\n <SelectTrigger id=\"thDir\">\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=\"thSev\">Severity</Label>\n <Select\n value={thresholdForm.severity}\n onValueChange={(v) =>\n setThresholdForm({ ...thresholdForm, severity: v as EcoThresholdSeverity })\n }\n >\n <SelectTrigger id=\"thSev\">\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=\"thMin\">Min value</Label>\n <Input\n id=\"thMin\"\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=\"thMax\">Max value</Label>\n <Input\n id=\"thMax\"\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=\"thLimit\">Limit value</Label>\n <Input\n id=\"thLimit\"\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 </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\n {/* Record measurement dialog */}\n <ResponsiveDialog open={measureOpen} onOpenChange={setMeasureOpen}>\n <ResponsiveDialogContent>\n <ResponsiveDialogHeader>\n <ResponsiveDialogTitle>Record measurement</ResponsiveDialogTitle>\n <ResponsiveDialogDescription>\n Log a reading. It is checked against active thresholds and flagged if it breaches.\n </ResponsiveDialogDescription>\n </ResponsiveDialogHeader>\n <div className=\"grid gap-4 py-2\">\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 {sensors.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 {sensors.map((s) => (\n <SelectItem key={s.id} value={s.id}>\n {s.name}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </div>\n )}\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 </PageLayout>\n );\n}\n"],"mappings":"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"}
|