@burdenoff/microfe-ecoimpacthub 2026.731.1 → 2026.801.1
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/EcoImpactHubRoutes.js +1 -1
- package/dist/ecoimpacthub/EcoImpactHubRoutes.js.map +1 -1
- package/dist/ecoimpacthub/context/EcoImpactHubContext.js +1 -1
- package/dist/ecoimpacthub/context/EcoImpactHubContext.js.map +1 -1
- package/dist/ecoimpacthub/pages/habitat/HabitatProjectDetailPage.js +1 -1
- package/dist/ecoimpacthub/pages/habitat/HabitatProjectDetailPage.js.map +1 -1
- package/dist/ecoimpacthub/pages/reports/ReportDetailPage.js +1 -1
- package/dist/ecoimpacthub/pages/reports/ReportDetailPage.js.map +1 -1
- package/dist/ecoimpacthub/pages/sites/SiteDetailPage.js +1 -1
- package/dist/ecoimpacthub/pages/sites/SiteDetailPage.js.map +1 -1
- package/dist/ecoimpacthub/pages/species/SpeciesDetailPage.js +1 -1
- package/dist/ecoimpacthub/pages/species/SpeciesDetailPage.js.map +1 -1
- package/package.json +3 -3
|
@@ -2,7 +2,7 @@ import { useEcoImpactHub as e } from "./context/EcoImpactHubContext.js";
|
|
|
2
2
|
import { FeatureFlagGate as t } from "./components/FeatureFlagGate.js";
|
|
3
3
|
import { DashboardPage as n } from "./pages/DashboardPage.js";
|
|
4
4
|
import { Suspense as r, lazy as i } from "react";
|
|
5
|
-
import { Navigate as a, Route as o, Routes as s, useLocation as c } from "react-router
|
|
5
|
+
import { Navigate as a, Route as o, Routes as s, useLocation as c } from "react-router";
|
|
6
6
|
import { jsx as l, jsxs as u } from "react/jsx-runtime";
|
|
7
7
|
import { Spinner as d } from "@burdenoff/fe-libs/ui";
|
|
8
8
|
//#region src/ecoimpacthub/EcoImpactHubRoutes.tsx
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"EcoImpactHubRoutes.js","names":[],"sources":["../../src/ecoimpacthub/EcoImpactHubRoutes.tsx"],"sourcesContent":["/**\n * EcoImpactHub Routes\n * @module ecoimpacthub\n *\n * Domain routes. The dashboard is eager-loaded (primary landing); all other\n * pages are lazy-loaded. Every route maps to a real `eco*` backend capability.\n */\n\nimport { type FC, lazy, Suspense, type ReactNode } from 'react';\nimport { Routes, Route, Navigate, useLocation } from 'react-router
|
|
1
|
+
{"version":3,"file":"EcoImpactHubRoutes.js","names":[],"sources":["../../src/ecoimpacthub/EcoImpactHubRoutes.tsx"],"sourcesContent":["/**\n * EcoImpactHub Routes\n * @module ecoimpacthub\n *\n * Domain routes. The dashboard is eager-loaded (primary landing); all other\n * pages are lazy-loaded. Every route maps to a real `eco*` backend capability.\n */\n\nimport { type FC, lazy, Suspense, type ReactNode } from 'react';\nimport { Routes, Route, Navigate, useLocation } from 'react-router';\nimport { Spinner } from '@burdenoff/fe-libs/ui';\nimport { useEcoImpactHub } from './context/EcoImpactHubContext';\nimport { FeatureFlagGate } from './components/FeatureFlagGate';\n\n// Eager — primary landing\nimport { DashboardPage } from './pages/DashboardPage';\n\n// Sites (Environmental Monitoring)\nconst SitesPage = lazy(() =>\n import('./pages/sites/SitesPage').then((m) => ({ default: m.SitesPage }))\n);\nconst SiteDetailPage = lazy(() =>\n import('./pages/sites/SiteDetailPage').then((m) => ({ default: m.SiteDetailPage }))\n);\n\n// Monitoring (sensors / measurements / thresholds)\nconst MonitoringPage = lazy(() =>\n import('./pages/monitoring/MonitoringPage').then((m) => ({ default: m.MonitoringPage }))\n);\n\n// Species (Conservation)\nconst SpeciesPage = lazy(() =>\n import('./pages/species/SpeciesPage').then((m) => ({ default: m.SpeciesPage }))\n);\nconst SpeciesDetailPage = lazy(() =>\n import('./pages/species/SpeciesDetailPage').then((m) => ({ default: m.SpeciesDetailPage }))\n);\n\n// Observations (citizen science / field data)\nconst ObservationsPage = lazy(() =>\n import('./pages/observations/ObservationsPage').then((m) => ({ default: m.ObservationsPage }))\n);\n\n// Habitat projects & restoration\nconst HabitatProjectsPage = lazy(() =>\n import('./pages/habitat/HabitatProjectsPage').then((m) => ({ default: m.HabitatProjectsPage }))\n);\nconst HabitatProjectDetailPage = lazy(() =>\n import('./pages/habitat/HabitatProjectDetailPage').then((m) => ({\n default: m.HabitatProjectDetailPage,\n }))\n);\n\n// Carbon ledger / emission factors / reduction targets (Sustainability)\nconst CarbonPage = lazy(() =>\n import('./pages/carbon/CarbonPage').then((m) => ({ default: m.CarbonPage }))\n);\n\n// ESG Reports\nconst ReportsPage = lazy(() =>\n import('./pages/reports/ReportsPage').then((m) => ({ default: m.ReportsPage }))\n);\nconst ReportDetailPage = lazy(() =>\n import('./pages/reports/ReportDetailPage').then((m) => ({ default: m.ReportDetailPage }))\n);\n\nconst PageLoader: FC = () => (\n <div className=\"flex min-h-[400px] items-center justify-center\">\n <Spinner size=\"lg\" />\n </div>\n);\n\nfunction gated(flag: string, element: ReactNode): ReactNode {\n return <FeatureFlagGate flag={flag}>{element}</FeatureFlagGate>;\n}\n\nexport const EcoImpactHubRoutes: FC = () => {\n const { basePath, isStandalone } = useEcoImpactHub();\n const location = useLocation();\n\n const normalizedBasePath = basePath === '/' ? '' : (basePath ?? '').replace(/\\/$/, '');\n\n // Guard: only render if the current path matches basePath (integrated mode).\n if (normalizedBasePath && !isStandalone) {\n const pathMatches =\n location.pathname === normalizedBasePath ||\n location.pathname.startsWith(normalizedBasePath + '/');\n if (!pathMatches) {\n return null;\n }\n }\n\n return (\n <div className=\"flex min-h-full flex-col\">\n <Suspense fallback={<PageLoader />}>\n <Routes>\n <Route index element={gated('ecoimpacthub.page.dashboard', <DashboardPage />)} />\n\n <Route path=\"sites\" element={gated('ecoimpacthub.page.sites', <SitesPage />)} />\n <Route path=\"sites/:id\" element={gated('ecoimpacthub.page.sites', <SiteDetailPage />)} />\n\n <Route\n path=\"monitoring\"\n element={gated('ecoimpacthub.page.monitoring', <MonitoringPage />)}\n />\n\n <Route path=\"species\" element={gated('ecoimpacthub.page.species', <SpeciesPage />)} />\n <Route\n path=\"species/:id\"\n element={gated('ecoimpacthub.page.species', <SpeciesDetailPage />)}\n />\n\n <Route\n path=\"observations\"\n element={gated('ecoimpacthub.page.observations', <ObservationsPage />)}\n />\n\n <Route\n path=\"habitat\"\n element={gated('ecoimpacthub.page.habitat', <HabitatProjectsPage />)}\n />\n <Route\n path=\"habitat/:id\"\n element={gated('ecoimpacthub.page.habitat', <HabitatProjectDetailPage />)}\n />\n\n <Route path=\"carbon\" element={gated('ecoimpacthub.page.carbon', <CarbonPage />)} />\n\n <Route path=\"reports\" element={gated('ecoimpacthub.page.reports', <ReportsPage />)} />\n <Route\n path=\"reports/:id\"\n element={gated('ecoimpacthub.page.reports', <ReportDetailPage />)}\n />\n\n <Route path=\"*\" element={<Navigate to=\"\" replace />} />\n </Routes>\n </Suspense>\n </div>\n );\n};\n"],"mappings":"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"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { joinPath as e } from "../utils/navigation.js";
|
|
2
2
|
import { createContext as t, useCallback as n, useContext as r, useMemo as i } from "react";
|
|
3
|
-
import { MemoryRouter as a, useNavigate as o } from "react-router
|
|
3
|
+
import { MemoryRouter as a, useNavigate as o } from "react-router";
|
|
4
4
|
import { jsx as s } from "react/jsx-runtime";
|
|
5
5
|
//#region src/ecoimpacthub/context/EcoImpactHubContext.tsx
|
|
6
6
|
var c = t(null);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"EcoImpactHubContext.js","names":[],"sources":["../../../src/ecoimpacthub/context/EcoImpactHubContext.tsx"],"sourcesContent":["/**\n * EcoImpactHub Context Provider\n * @module ecoimpacthub/context\n *\n * Splits standalone mode (wraps its own MemoryRouter + useNavigate) from\n * integrated mode (the host shell supplies `navigate`). Exposes gateway/auth\n * context + relative navigation helpers to every page.\n */\n\nimport type { FC, PropsWithChildren } from 'react';\nimport { createContext, useContext, useMemo, useCallback } from 'react';\nimport { MemoryRouter, useNavigate as useRouterNavigate } from 'react-router
|
|
1
|
+
{"version":3,"file":"EcoImpactHubContext.js","names":[],"sources":["../../../src/ecoimpacthub/context/EcoImpactHubContext.tsx"],"sourcesContent":["/**\n * EcoImpactHub Context Provider\n * @module ecoimpacthub/context\n *\n * Splits standalone mode (wraps its own MemoryRouter + useNavigate) from\n * integrated mode (the host shell supplies `navigate`). Exposes gateway/auth\n * context + relative navigation helpers to every page.\n */\n\nimport type { FC, PropsWithChildren } from 'react';\nimport { createContext, useContext, useMemo, useCallback } from 'react';\nimport { MemoryRouter, useNavigate as useRouterNavigate } from 'react-router';\nimport type { EcoImpactHubRootProps } from '../types';\nimport { joinPath } from '../utils/navigation';\n\ninterface EcoImpactHubContextValue extends Omit<\n EcoImpactHubRootProps,\n 'registerNavItems' | 'registerFooterItems' | 'navigate'\n> {\n isStandalone: boolean;\n /** Shell's navigate function (only available in integrated mode). */\n navigate?: (path: string) => void;\n /** Navigate to a path relative to basePath. */\n navigateTo: (relativePath: string) => void;\n /** Get the full path for a relative path. */\n getPath: (relativePath: string) => string;\n}\n\nconst EcoImpactHubContext = createContext<EcoImpactHubContextValue | null>(null);\n\nexport function useEcoImpactHub(): EcoImpactHubContextValue {\n const context = useContext(EcoImpactHubContext);\n if (!context) {\n throw new Error('useEcoImpactHub must be used within an EcoImpactHubProvider');\n }\n return context;\n}\n\ntype ProviderProps = Omit<EcoImpactHubRootProps, 'registerNavItems' | 'registerFooterItems'> & {\n isStandalone: boolean;\n};\n\nconst StandaloneProviderInner: FC<PropsWithChildren<ProviderProps>> = ({\n children,\n basePath,\n currentUser,\n tenantId,\n workspaceId,\n apiGatewayUrl,\n authToken,\n}) => {\n const routerNavigate = useRouterNavigate();\n const effectiveBasePath = basePath || '/';\n\n const getPath = useCallback(\n (relativePath: string) => joinPath(effectiveBasePath, relativePath),\n [effectiveBasePath]\n );\n\n const navigateTo = useCallback(\n (relativePath: string) => {\n routerNavigate(joinPath(effectiveBasePath, relativePath));\n },\n [effectiveBasePath, routerNavigate]\n );\n\n const contextValue = useMemo<EcoImpactHubContextValue>(\n () => ({\n basePath: effectiveBasePath,\n navigate: undefined,\n currentUser,\n tenantId,\n workspaceId,\n apiGatewayUrl,\n authToken,\n isStandalone: true,\n navigateTo,\n getPath,\n }),\n [\n effectiveBasePath,\n currentUser,\n tenantId,\n workspaceId,\n apiGatewayUrl,\n authToken,\n navigateTo,\n getPath,\n ]\n );\n\n return (\n <EcoImpactHubContext.Provider value={contextValue}>{children}</EcoImpactHubContext.Provider>\n );\n};\n\nconst IntegratedProviderInner: FC<\n PropsWithChildren<ProviderProps & { navigate: (path: string) => void }>\n> = ({\n children,\n basePath,\n navigate,\n currentUser,\n tenantId,\n workspaceId,\n apiGatewayUrl,\n authToken,\n}) => {\n const effectiveBasePath = basePath || '/';\n\n const getPath = useCallback(\n (relativePath: string) => joinPath(effectiveBasePath, relativePath),\n [effectiveBasePath]\n );\n\n const navigateTo = useCallback(\n (relativePath: string) => {\n navigate(joinPath(effectiveBasePath, relativePath));\n },\n [effectiveBasePath, navigate]\n );\n\n const contextValue = useMemo<EcoImpactHubContextValue>(\n () => ({\n basePath: effectiveBasePath,\n navigate,\n currentUser,\n tenantId,\n workspaceId,\n apiGatewayUrl,\n authToken,\n isStandalone: false,\n navigateTo,\n getPath,\n }),\n [\n effectiveBasePath,\n navigate,\n currentUser,\n tenantId,\n workspaceId,\n apiGatewayUrl,\n authToken,\n navigateTo,\n getPath,\n ]\n );\n\n return (\n <EcoImpactHubContext.Provider value={contextValue}>{children}</EcoImpactHubContext.Provider>\n );\n};\n\nexport const EcoImpactHubProvider: FC<PropsWithChildren<EcoImpactHubRootProps>> = (props) => {\n const { children, navigate } = props;\n const isStandalone = !navigate;\n\n if (isStandalone) {\n return (\n <MemoryRouter>\n <StandaloneProviderInner {...props} isStandalone={true}>\n {children}\n </StandaloneProviderInner>\n </MemoryRouter>\n );\n }\n\n return (\n <IntegratedProviderInner {...props} navigate={navigate} isStandalone={false}>\n {children}\n </IntegratedProviderInner>\n );\n};\n"],"mappings":";;;;;AA4BA,IAAM,IAAsB,EAA+C,IAAI;AAE/E,SAAgB,IAA4C;CAC1D,IAAM,IAAU,EAAW,CAAmB;CAC9C,IAAI,CAAC,GACH,MAAU,MAAM,6DAA6D;CAE/E,OAAO;AACT;AAMA,IAAM,KAAiE,EACrE,aACA,aACA,gBACA,aACA,gBACA,kBACA,mBACI;CACJ,IAAM,IAAiB,EAAkB,GACnC,IAAoB,KAAY,KAEhC,IAAU,GACb,MAAyB,EAAS,GAAmB,CAAY,GAClE,CAAC,CAAiB,CACpB,GAEM,IAAa,GAChB,MAAyB;EACxB,EAAe,EAAS,GAAmB,CAAY,CAAC;CAC1D,GACA,CAAC,GAAmB,CAAc,CACpC,GAEM,IAAe,SACZ;EACL,UAAU;EACV,UAAU,KAAA;EACV;EACA;EACA;EACA;EACA;EACA,cAAc;EACd;EACA;CACF,IACA;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;CACF,CACF;CAEA,OACE,kBAAC,EAAoB,UAArB;EAA8B,OAAO;EAAe;CAAuC,CAAA;AAE/F,GAEM,KAED,EACH,aACA,aACA,aACA,gBACA,aACA,gBACA,kBACA,mBACI;CACJ,IAAM,IAAoB,KAAY,KAEhC,IAAU,GACb,MAAyB,EAAS,GAAmB,CAAY,GAClE,CAAC,CAAiB,CACpB,GAEM,IAAa,GAChB,MAAyB;EACxB,EAAS,EAAS,GAAmB,CAAY,CAAC;CACpD,GACA,CAAC,GAAmB,CAAQ,CAC9B,GAEM,IAAe,SACZ;EACL,UAAU;EACV;EACA;EACA;EACA;EACA;EACA;EACA,cAAc;EACd;EACA;CACF,IACA;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;CACF,CACF;CAEA,OACE,kBAAC,EAAoB,UAArB;EAA8B,OAAO;EAAe;CAAuC,CAAA;AAE/F,GAEa,KAAsE,MAAU;CAC3F,IAAM,EAAE,aAAU,gBAAa;CAa/B,OAZsB,IAapB,kBAAC,GAAD;EAAyB,GAAI;EAAiB;EAAU,cAAc;EACnE;CACsB,CAAA,IAXvB,kBAAC,GAAD,EAAA,UACE,kBAAC,GAAD;EAAyB,GAAI;EAAO,cAAc;EAC/C;CACsB,CAAA,EACb,CAAA;AASpB"}
|
|
@@ -12,7 +12,7 @@ import { ActivityTypeBadge as s, LifecycleBadge as c } from "../badges.js";
|
|
|
12
12
|
import { LifecycleProgress as le } from "./LifecycleProgress.js";
|
|
13
13
|
import { useMemo as l, useState as u } from "react";
|
|
14
14
|
import { ArrowLeft as ue, ArrowRight as d, Plus as f, Sprout as de } from "lucide-react";
|
|
15
|
-
import { useParams as fe } from "react-router
|
|
15
|
+
import { useParams as fe } from "react-router";
|
|
16
16
|
import { jsx as p, jsxs as m } from "react/jsx-runtime";
|
|
17
17
|
import { Button as h, GlassCard as g, Input as _, Label as v, ResponsiveDialog as y, ResponsiveDialogContent as b, ResponsiveDialogDescription as x, ResponsiveDialogFooter as S, ResponsiveDialogHeader as C, ResponsiveDialogTitle as w, Select as T, SelectContent as E, SelectItem as D, SelectTrigger as O, SelectValue as k, Textarea as A } from "@burdenoff/fe-libs/ui";
|
|
18
18
|
//#region src/ecoimpacthub/pages/habitat/HabitatProjectDetailPage.tsx
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"HabitatProjectDetailPage.js","names":[],"sources":["../../../../src/ecoimpacthub/pages/habitat/HabitatProjectDetailPage.tsx"],"sourcesContent":["/**\n * Habitat project detail — overview, lifecycle-progress, transitions & activities.\n * @module ecoimpacthub/pages/habitat\n *\n * Reads `ecoHabitatProject` (+ nested activities); writes\n * `ecoTransitionHabitatProject`, `ecoLogRestorationActivity`. The hero is\n * balanced into three tidy cards — overview, lifecycle-progress (a status-derived\n * radial ring + stepper, since the schema has no numeric progress) and\n * advance-project. Tags are driven via the platform tag store (no eco-svc change).\n */\n\nimport { useMemo, useState, type ReactNode } from 'react';\nimport { useParams } from 'react-router-dom';\nimport { ArrowLeft, ArrowRight, Plus, Sprout } from 'lucide-react';\nimport {\n Button,\n Input,\n Label,\n Textarea,\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 { 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 useEcoHabitatProject,\n useEcoTransitionHabitatProject,\n useEcoLogRestorationActivity,\n useEcoSites,\n} from '../../hooks';\nimport { formatDate, formatDateTime, formatEnumLabel, formatNumber } from '../../utils/formatters';\nimport type {\n EcoHabitatProjectStatus,\n EcoRestorationActivity,\n EcoRestorationActivityType,\n} from '../../types';\nimport { ActivityTypeBadge, LifecycleBadge } from '../badges';\nimport { LifecycleProgress } from './LifecycleProgress';\n\n/** Allowed forward lifecycle transitions per current status. */\nconst NEXT_STATUSES: Record<EcoHabitatProjectStatus, EcoHabitatProjectStatus[]> = {\n PLANNED: ['IN_PROGRESS', 'ON_HOLD', 'CANCELLED'],\n IN_PROGRESS: ['MONITORING', 'ON_HOLD', 'COMPLETED', 'CANCELLED'],\n MONITORING: ['COMPLETED', 'IN_PROGRESS'],\n ON_HOLD: ['IN_PROGRESS', 'CANCELLED'],\n COMPLETED: [],\n CANCELLED: [],\n};\n\nconst ACTIVITY_TYPES: EcoRestorationActivityType[] = [\n 'PLANTING',\n 'INVASIVE_REMOVAL',\n 'REWILDING',\n 'WATER_MANAGEMENT',\n 'SOIL_REMEDIATION',\n 'COMMUNITY_ENGAGEMENT',\n 'SURVEY',\n];\n\n/** Local `YYYY-MM-DDTHH:mm` string for a datetime-local default of \"now\". */\nconst nowLocal = (): string => {\n const d = new Date();\n const local = new Date(d.getTime() - d.getTimezoneOffset() * 60000);\n return local.toISOString().slice(0, 16);\n};\n\ninterface ActivityFormState {\n type: EcoRestorationActivityType;\n summary: string;\n details: string;\n quantity: string;\n unit: string;\n performedAt: string;\n}\n\nconst emptyActivityForm = (): ActivityFormState => ({\n type: 'PLANTING',\n summary: '',\n details: '',\n quantity: '',\n unit: '',\n performedAt: nowLocal(),\n});\n\n/** A labelled key/value cell for the overview grid. */\nfunction Fact({ label, children }: { label: string; children: ReactNode }) {\n return (\n <div>\n <dt className=\"text-xs text-text-tertiary\">{label}</dt>\n <dd className=\"mt-1 text-sm text-text-primary\">{children}</dd>\n </div>\n );\n}\n\nexport function HabitatProjectDetailPage() {\n const { id } = useParams<{ id: string }>();\n const { navigateTo } = useEcoImpactHub();\n const { data: project, isLoading, isError, refetch } = useEcoHabitatProject(id);\n const sites = useEcoSites({ pagination: { limit: 100, offset: 0 } });\n\n const transitionProject = useEcoTransitionHabitatProject();\n const logActivity = useEcoLogRestorationActivity();\n\n const [activityOpen, setActivityOpen] = useState(false);\n const [activityForm, setActivityForm] = useState<ActivityFormState>(emptyActivityForm);\n const [error, setError] = useState<string | null>(null);\n\n const projectSiteId = project?.siteId ?? null;\n const siteItems = sites.data?.items;\n const siteName = useMemo(\n () => (projectSiteId ? (siteItems?.find((s) => s.id === projectSiteId)?.name ?? null) : null),\n [projectSiteId, siteItems]\n );\n\n if (isLoading) {\n return (\n <PageLayout title=\"Habitat project\">\n <LoadingView />\n </PageLayout>\n );\n }\n if (isError || !project) {\n return (\n <PageLayout title=\"Habitat project\">\n <ErrorView message=\"This project could not be loaded.\" onRetry={() => refetch()} />\n </PageLayout>\n );\n }\n\n const activities = project.activities ?? [];\n const nextStatuses = NEXT_STATUSES[project.status];\n const timeline = [formatDate(project.startDate), formatDate(project.endDate)].join(' – ');\n const subtitle = [siteName, timeline].filter((v) => v && v !== '— – —').join(' · ');\n\n const handleTransition = async (status: EcoHabitatProjectStatus) => {\n if (!id) return;\n setError(null);\n try {\n await transitionProject.mutateAsync({ id, status });\n } catch (err) {\n setError(err instanceof Error ? err.message : 'Failed to update project status.');\n }\n };\n\n const handleLogActivity = async () => {\n if (!id || !activityForm.summary.trim()) {\n setError('Activity summary is required.');\n return;\n }\n setError(null);\n try {\n await logActivity.mutateAsync({\n projectId: id,\n type: activityForm.type,\n summary: activityForm.summary.trim(),\n details: activityForm.details.trim() || null,\n quantity: activityForm.quantity ? Number(activityForm.quantity) : null,\n unit: activityForm.unit.trim() || null,\n performedAt: activityForm.performedAt\n ? new Date(activityForm.performedAt).toISOString()\n : new Date().toISOString(),\n });\n setActivityOpen(false);\n setActivityForm(emptyActivityForm());\n } catch (err) {\n setError(err instanceof Error ? err.message : 'Failed to log activity.');\n }\n };\n\n const activityColumns: Column<EcoRestorationActivity>[] = [\n {\n key: 'type',\n header: 'Type',\n render: (a) => <ActivityTypeBadge type={a.type} />,\n },\n {\n key: 'summary',\n header: 'Summary',\n render: (a) => (\n <div className=\"min-w-0\">\n <p className=\"text-text-primary\">{a.summary}</p>\n {a.details && (\n <p className=\"mt-0.5 max-w-md truncate text-xs text-text-tertiary\">{a.details}</p>\n )}\n </div>\n ),\n },\n {\n key: 'quantity',\n header: 'Quantity',\n align: 'right',\n hideOnMobile: true,\n sortValue: (a) => a.quantity ?? 0,\n render: (a) =>\n a.quantity != null ? (\n <span className=\"tabular-nums text-text-secondary\">\n {formatNumber(a.quantity)}\n {a.unit ? ` ${a.unit}` : ''}\n </span>\n ) : (\n <span className=\"text-text-tertiary\">—</span>\n ),\n },\n {\n key: 'performedAt',\n header: 'Performed',\n align: 'right',\n hideOnMobile: true,\n sortValue: (a) => a.performedAt,\n render: (a) => <span className=\"text-text-tertiary\">{formatDateTime(a.performedAt)}</span>,\n },\n ];\n\n return (\n <PageLayout\n title={\n <span className=\"flex flex-wrap items-center gap-x-3 gap-y-2\">\n <span className=\"text-text-primary\">{project.name}</span>\n <LifecycleBadge status={project.status} />\n </span>\n }\n description={subtitle || undefined}\n actions={\n <Button variant=\"ghost\" onClick={() => navigateTo('habitat')}>\n <ArrowLeft className=\"mr-1.5 h-4 w-4\" /> Back\n </Button>\n }\n >\n {/* Hero — overview · lifecycle-progress · advance-project */}\n <div className=\"grid grid-cols-1 gap-4 lg:grid-cols-4\">\n {/* Overview */}\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 <Fact label=\"Status\">\n <LifecycleBadge status={project.status} />\n </Fact>\n <Fact label=\"Target area\">\n <span className=\"tabular-nums\">\n {project.targetHectares != null\n ? `${formatNumber(project.targetHectares)} ha`\n : '—'}\n </span>\n </Fact>\n <Fact label=\"Budget\">\n <span className=\"tabular-nums\">\n {project.budgetAmount > 0\n ? `${formatNumber(project.budgetAmount)} ${project.currency}`\n : '—'}\n </span>\n </Fact>\n <Fact label=\"Start\">{formatDate(project.startDate)}</Fact>\n <Fact label=\"End\">{formatDate(project.endDate)}</Fact>\n <Fact label={project.completedAt ? 'Completed' : 'Site'}>\n {project.completedAt ? formatDate(project.completedAt) : (siteName ?? '—')}\n </Fact>\n {project.completedAt && <Fact label=\"Site\">{siteName ?? '—'}</Fact>}\n </dl>\n\n {project.description && (\n <p className=\"mt-5 rounded-lg bg-bg-sunken/60 p-3 text-sm text-text-secondary\">\n {project.description}\n </p>\n )}\n\n <div className=\"mt-5 border-t border-border-seam pt-4\">\n <EcoTags entityType=\"ecoimpacthub-habitat\" entityId={project.id} />\n </div>\n </GlassCard>\n\n {/* Lifecycle progress */}\n <GlassCard treatment=\"glass\" className=\"flex flex-col p-6\">\n <p className=\"mb-2 text-xs font-medium uppercase tracking-wide text-text-tertiary\">\n Lifecycle progress\n </p>\n <div className=\"flex flex-1 items-center justify-center\">\n <LifecycleProgress status={project.status} />\n </div>\n </GlassCard>\n\n {/* Advance project */}\n <GlassCard treatment=\"glass\" className=\"flex flex-col gap-3 p-6\">\n <p className=\"text-xs font-medium uppercase tracking-wide text-text-tertiary\">\n Advance project\n </p>\n {nextStatuses.length === 0 ? (\n <p className=\"text-sm text-text-secondary\">\n This project is {formatEnumLabel(project.status).toLowerCase()} — no further\n transitions.\n </p>\n ) : (\n nextStatuses.map((next) => (\n <Button\n key={next}\n variant=\"outline\"\n onClick={() => handleTransition(next)}\n disabled={transitionProject.isPending}\n className=\"justify-between\"\n >\n {formatEnumLabel(next)}\n <ArrowRight className=\"h-4 w-4 opacity-60\" />\n </Button>\n ))\n )}\n <Button className=\"mt-auto\" onClick={() => setActivityOpen(true)}>\n <Plus className=\"mr-1.5 h-4 w-4\" /> Log activity\n </Button>\n </GlassCard>\n </div>\n\n {error && <p className=\"mt-4 text-sm text-status-error-text\">{error}</p>}\n\n {/* Restoration activities */}\n <Section\n title=\"Restoration activities\"\n description=\"Work logged against this project — planting, removals, surveys & engagement.\"\n action={\n <Button size=\"sm\" onClick={() => setActivityOpen(true)}>\n <Plus className=\"mr-1.5 h-4 w-4\" /> Log activity\n </Button>\n }\n >\n {activities.length === 0 ? (\n <EmptyState\n illustration=\"empty-data\"\n icon={<Sprout className=\"h-10 w-10\" />}\n title=\"No activities yet\"\n description=\"Log restoration work to build this project's field record.\"\n action={\n <Button size=\"sm\" onClick={() => setActivityOpen(true)}>\n <Plus className=\"mr-1.5 h-4 w-4\" /> Log activity\n </Button>\n }\n />\n ) : (\n <DataTable\n columns={activityColumns}\n rows={activities}\n rowKey={(a) => a.id}\n defaultSort={{ key: 'performedAt', dir: 'desc' }}\n />\n )}\n </Section>\n\n {/* Log activity dialog */}\n <ResponsiveDialog open={activityOpen} onOpenChange={setActivityOpen}>\n <ResponsiveDialogContent>\n <ResponsiveDialogHeader>\n <ResponsiveDialogTitle>Log restoration activity</ResponsiveDialogTitle>\n <ResponsiveDialogDescription>\n Record a piece of work performed for {project.name}.\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=\"actType\">Type</Label>\n <Select\n value={activityForm.type}\n onValueChange={(v) =>\n setActivityForm({ ...activityForm, type: v as EcoRestorationActivityType })\n }\n >\n <SelectTrigger id=\"actType\">\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n {ACTIVITY_TYPES.map((t) => (\n <SelectItem key={t} value={t}>\n {formatEnumLabel(t)}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </div>\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"actPerformedAt\">Performed at</Label>\n <Input\n id=\"actPerformedAt\"\n type=\"datetime-local\"\n value={activityForm.performedAt}\n onChange={(e) =>\n setActivityForm({ ...activityForm, performedAt: e.target.value })\n }\n />\n </div>\n </div>\n\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"actSummary\">Summary *</Label>\n <Input\n id=\"actSummary\"\n value={activityForm.summary}\n onChange={(e) => setActivityForm({ ...activityForm, summary: e.target.value })}\n placeholder=\"Planted 500 mangrove saplings\"\n />\n </div>\n\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"actDetails\">Details</Label>\n <Textarea\n id=\"actDetails\"\n value={activityForm.details}\n onChange={(e) => setActivityForm({ ...activityForm, details: e.target.value })}\n placeholder=\"Optional notes about the work.\"\n rows={2}\n />\n </div>\n\n <div className=\"grid gap-2 sm:grid-cols-2\">\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"actQuantity\">Quantity</Label>\n <Input\n id=\"actQuantity\"\n type=\"number\"\n step=\"any\"\n value={activityForm.quantity}\n onChange={(e) => setActivityForm({ ...activityForm, quantity: e.target.value })}\n placeholder=\"500\"\n />\n </div>\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"actUnit\">Unit</Label>\n <Input\n id=\"actUnit\"\n value={activityForm.unit}\n onChange={(e) => setActivityForm({ ...activityForm, unit: e.target.value })}\n placeholder=\"saplings\"\n />\n </div>\n </div>\n </div>\n <ResponsiveDialogFooter>\n <Button variant=\"outline\" onClick={() => setActivityOpen(false)}>\n Cancel\n </Button>\n <Button onClick={handleLogActivity} disabled={logActivity.isPending}>\n {logActivity.isPending ? 'Logging…' : 'Log activity'}\n </Button>\n </ResponsiveDialogFooter>\n </ResponsiveDialogContent>\n </ResponsiveDialog>\n </PageLayout>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"HabitatProjectDetailPage.js","names":[],"sources":["../../../../src/ecoimpacthub/pages/habitat/HabitatProjectDetailPage.tsx"],"sourcesContent":["/**\n * Habitat project detail — overview, lifecycle-progress, transitions & activities.\n * @module ecoimpacthub/pages/habitat\n *\n * Reads `ecoHabitatProject` (+ nested activities); writes\n * `ecoTransitionHabitatProject`, `ecoLogRestorationActivity`. The hero is\n * balanced into three tidy cards — overview, lifecycle-progress (a status-derived\n * radial ring + stepper, since the schema has no numeric progress) and\n * advance-project. Tags are driven via the platform tag store (no eco-svc change).\n */\n\nimport { useMemo, useState, type ReactNode } from 'react';\nimport { useParams } from 'react-router';\nimport { ArrowLeft, ArrowRight, Plus, Sprout } from 'lucide-react';\nimport {\n Button,\n Input,\n Label,\n Textarea,\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 { 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 useEcoHabitatProject,\n useEcoTransitionHabitatProject,\n useEcoLogRestorationActivity,\n useEcoSites,\n} from '../../hooks';\nimport { formatDate, formatDateTime, formatEnumLabel, formatNumber } from '../../utils/formatters';\nimport type {\n EcoHabitatProjectStatus,\n EcoRestorationActivity,\n EcoRestorationActivityType,\n} from '../../types';\nimport { ActivityTypeBadge, LifecycleBadge } from '../badges';\nimport { LifecycleProgress } from './LifecycleProgress';\n\n/** Allowed forward lifecycle transitions per current status. */\nconst NEXT_STATUSES: Record<EcoHabitatProjectStatus, EcoHabitatProjectStatus[]> = {\n PLANNED: ['IN_PROGRESS', 'ON_HOLD', 'CANCELLED'],\n IN_PROGRESS: ['MONITORING', 'ON_HOLD', 'COMPLETED', 'CANCELLED'],\n MONITORING: ['COMPLETED', 'IN_PROGRESS'],\n ON_HOLD: ['IN_PROGRESS', 'CANCELLED'],\n COMPLETED: [],\n CANCELLED: [],\n};\n\nconst ACTIVITY_TYPES: EcoRestorationActivityType[] = [\n 'PLANTING',\n 'INVASIVE_REMOVAL',\n 'REWILDING',\n 'WATER_MANAGEMENT',\n 'SOIL_REMEDIATION',\n 'COMMUNITY_ENGAGEMENT',\n 'SURVEY',\n];\n\n/** Local `YYYY-MM-DDTHH:mm` string for a datetime-local default of \"now\". */\nconst nowLocal = (): string => {\n const d = new Date();\n const local = new Date(d.getTime() - d.getTimezoneOffset() * 60000);\n return local.toISOString().slice(0, 16);\n};\n\ninterface ActivityFormState {\n type: EcoRestorationActivityType;\n summary: string;\n details: string;\n quantity: string;\n unit: string;\n performedAt: string;\n}\n\nconst emptyActivityForm = (): ActivityFormState => ({\n type: 'PLANTING',\n summary: '',\n details: '',\n quantity: '',\n unit: '',\n performedAt: nowLocal(),\n});\n\n/** A labelled key/value cell for the overview grid. */\nfunction Fact({ label, children }: { label: string; children: ReactNode }) {\n return (\n <div>\n <dt className=\"text-xs text-text-tertiary\">{label}</dt>\n <dd className=\"mt-1 text-sm text-text-primary\">{children}</dd>\n </div>\n );\n}\n\nexport function HabitatProjectDetailPage() {\n const { id } = useParams<{ id: string }>();\n const { navigateTo } = useEcoImpactHub();\n const { data: project, isLoading, isError, refetch } = useEcoHabitatProject(id);\n const sites = useEcoSites({ pagination: { limit: 100, offset: 0 } });\n\n const transitionProject = useEcoTransitionHabitatProject();\n const logActivity = useEcoLogRestorationActivity();\n\n const [activityOpen, setActivityOpen] = useState(false);\n const [activityForm, setActivityForm] = useState<ActivityFormState>(emptyActivityForm);\n const [error, setError] = useState<string | null>(null);\n\n const projectSiteId = project?.siteId ?? null;\n const siteItems = sites.data?.items;\n const siteName = useMemo(\n () => (projectSiteId ? (siteItems?.find((s) => s.id === projectSiteId)?.name ?? null) : null),\n [projectSiteId, siteItems]\n );\n\n if (isLoading) {\n return (\n <PageLayout title=\"Habitat project\">\n <LoadingView />\n </PageLayout>\n );\n }\n if (isError || !project) {\n return (\n <PageLayout title=\"Habitat project\">\n <ErrorView message=\"This project could not be loaded.\" onRetry={() => refetch()} />\n </PageLayout>\n );\n }\n\n const activities = project.activities ?? [];\n const nextStatuses = NEXT_STATUSES[project.status];\n const timeline = [formatDate(project.startDate), formatDate(project.endDate)].join(' – ');\n const subtitle = [siteName, timeline].filter((v) => v && v !== '— – —').join(' · ');\n\n const handleTransition = async (status: EcoHabitatProjectStatus) => {\n if (!id) return;\n setError(null);\n try {\n await transitionProject.mutateAsync({ id, status });\n } catch (err) {\n setError(err instanceof Error ? err.message : 'Failed to update project status.');\n }\n };\n\n const handleLogActivity = async () => {\n if (!id || !activityForm.summary.trim()) {\n setError('Activity summary is required.');\n return;\n }\n setError(null);\n try {\n await logActivity.mutateAsync({\n projectId: id,\n type: activityForm.type,\n summary: activityForm.summary.trim(),\n details: activityForm.details.trim() || null,\n quantity: activityForm.quantity ? Number(activityForm.quantity) : null,\n unit: activityForm.unit.trim() || null,\n performedAt: activityForm.performedAt\n ? new Date(activityForm.performedAt).toISOString()\n : new Date().toISOString(),\n });\n setActivityOpen(false);\n setActivityForm(emptyActivityForm());\n } catch (err) {\n setError(err instanceof Error ? err.message : 'Failed to log activity.');\n }\n };\n\n const activityColumns: Column<EcoRestorationActivity>[] = [\n {\n key: 'type',\n header: 'Type',\n render: (a) => <ActivityTypeBadge type={a.type} />,\n },\n {\n key: 'summary',\n header: 'Summary',\n render: (a) => (\n <div className=\"min-w-0\">\n <p className=\"text-text-primary\">{a.summary}</p>\n {a.details && (\n <p className=\"mt-0.5 max-w-md truncate text-xs text-text-tertiary\">{a.details}</p>\n )}\n </div>\n ),\n },\n {\n key: 'quantity',\n header: 'Quantity',\n align: 'right',\n hideOnMobile: true,\n sortValue: (a) => a.quantity ?? 0,\n render: (a) =>\n a.quantity != null ? (\n <span className=\"tabular-nums text-text-secondary\">\n {formatNumber(a.quantity)}\n {a.unit ? ` ${a.unit}` : ''}\n </span>\n ) : (\n <span className=\"text-text-tertiary\">—</span>\n ),\n },\n {\n key: 'performedAt',\n header: 'Performed',\n align: 'right',\n hideOnMobile: true,\n sortValue: (a) => a.performedAt,\n render: (a) => <span className=\"text-text-tertiary\">{formatDateTime(a.performedAt)}</span>,\n },\n ];\n\n return (\n <PageLayout\n title={\n <span className=\"flex flex-wrap items-center gap-x-3 gap-y-2\">\n <span className=\"text-text-primary\">{project.name}</span>\n <LifecycleBadge status={project.status} />\n </span>\n }\n description={subtitle || undefined}\n actions={\n <Button variant=\"ghost\" onClick={() => navigateTo('habitat')}>\n <ArrowLeft className=\"mr-1.5 h-4 w-4\" /> Back\n </Button>\n }\n >\n {/* Hero — overview · lifecycle-progress · advance-project */}\n <div className=\"grid grid-cols-1 gap-4 lg:grid-cols-4\">\n {/* Overview */}\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 <Fact label=\"Status\">\n <LifecycleBadge status={project.status} />\n </Fact>\n <Fact label=\"Target area\">\n <span className=\"tabular-nums\">\n {project.targetHectares != null\n ? `${formatNumber(project.targetHectares)} ha`\n : '—'}\n </span>\n </Fact>\n <Fact label=\"Budget\">\n <span className=\"tabular-nums\">\n {project.budgetAmount > 0\n ? `${formatNumber(project.budgetAmount)} ${project.currency}`\n : '—'}\n </span>\n </Fact>\n <Fact label=\"Start\">{formatDate(project.startDate)}</Fact>\n <Fact label=\"End\">{formatDate(project.endDate)}</Fact>\n <Fact label={project.completedAt ? 'Completed' : 'Site'}>\n {project.completedAt ? formatDate(project.completedAt) : (siteName ?? '—')}\n </Fact>\n {project.completedAt && <Fact label=\"Site\">{siteName ?? '—'}</Fact>}\n </dl>\n\n {project.description && (\n <p className=\"mt-5 rounded-lg bg-bg-sunken/60 p-3 text-sm text-text-secondary\">\n {project.description}\n </p>\n )}\n\n <div className=\"mt-5 border-t border-border-seam pt-4\">\n <EcoTags entityType=\"ecoimpacthub-habitat\" entityId={project.id} />\n </div>\n </GlassCard>\n\n {/* Lifecycle progress */}\n <GlassCard treatment=\"glass\" className=\"flex flex-col p-6\">\n <p className=\"mb-2 text-xs font-medium uppercase tracking-wide text-text-tertiary\">\n Lifecycle progress\n </p>\n <div className=\"flex flex-1 items-center justify-center\">\n <LifecycleProgress status={project.status} />\n </div>\n </GlassCard>\n\n {/* Advance project */}\n <GlassCard treatment=\"glass\" className=\"flex flex-col gap-3 p-6\">\n <p className=\"text-xs font-medium uppercase tracking-wide text-text-tertiary\">\n Advance project\n </p>\n {nextStatuses.length === 0 ? (\n <p className=\"text-sm text-text-secondary\">\n This project is {formatEnumLabel(project.status).toLowerCase()} — no further\n transitions.\n </p>\n ) : (\n nextStatuses.map((next) => (\n <Button\n key={next}\n variant=\"outline\"\n onClick={() => handleTransition(next)}\n disabled={transitionProject.isPending}\n className=\"justify-between\"\n >\n {formatEnumLabel(next)}\n <ArrowRight className=\"h-4 w-4 opacity-60\" />\n </Button>\n ))\n )}\n <Button className=\"mt-auto\" onClick={() => setActivityOpen(true)}>\n <Plus className=\"mr-1.5 h-4 w-4\" /> Log activity\n </Button>\n </GlassCard>\n </div>\n\n {error && <p className=\"mt-4 text-sm text-status-error-text\">{error}</p>}\n\n {/* Restoration activities */}\n <Section\n title=\"Restoration activities\"\n description=\"Work logged against this project — planting, removals, surveys & engagement.\"\n action={\n <Button size=\"sm\" onClick={() => setActivityOpen(true)}>\n <Plus className=\"mr-1.5 h-4 w-4\" /> Log activity\n </Button>\n }\n >\n {activities.length === 0 ? (\n <EmptyState\n illustration=\"empty-data\"\n icon={<Sprout className=\"h-10 w-10\" />}\n title=\"No activities yet\"\n description=\"Log restoration work to build this project's field record.\"\n action={\n <Button size=\"sm\" onClick={() => setActivityOpen(true)}>\n <Plus className=\"mr-1.5 h-4 w-4\" /> Log activity\n </Button>\n }\n />\n ) : (\n <DataTable\n columns={activityColumns}\n rows={activities}\n rowKey={(a) => a.id}\n defaultSort={{ key: 'performedAt', dir: 'desc' }}\n />\n )}\n </Section>\n\n {/* Log activity dialog */}\n <ResponsiveDialog open={activityOpen} onOpenChange={setActivityOpen}>\n <ResponsiveDialogContent>\n <ResponsiveDialogHeader>\n <ResponsiveDialogTitle>Log restoration activity</ResponsiveDialogTitle>\n <ResponsiveDialogDescription>\n Record a piece of work performed for {project.name}.\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=\"actType\">Type</Label>\n <Select\n value={activityForm.type}\n onValueChange={(v) =>\n setActivityForm({ ...activityForm, type: v as EcoRestorationActivityType })\n }\n >\n <SelectTrigger id=\"actType\">\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n {ACTIVITY_TYPES.map((t) => (\n <SelectItem key={t} value={t}>\n {formatEnumLabel(t)}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </div>\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"actPerformedAt\">Performed at</Label>\n <Input\n id=\"actPerformedAt\"\n type=\"datetime-local\"\n value={activityForm.performedAt}\n onChange={(e) =>\n setActivityForm({ ...activityForm, performedAt: e.target.value })\n }\n />\n </div>\n </div>\n\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"actSummary\">Summary *</Label>\n <Input\n id=\"actSummary\"\n value={activityForm.summary}\n onChange={(e) => setActivityForm({ ...activityForm, summary: e.target.value })}\n placeholder=\"Planted 500 mangrove saplings\"\n />\n </div>\n\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"actDetails\">Details</Label>\n <Textarea\n id=\"actDetails\"\n value={activityForm.details}\n onChange={(e) => setActivityForm({ ...activityForm, details: e.target.value })}\n placeholder=\"Optional notes about the work.\"\n rows={2}\n />\n </div>\n\n <div className=\"grid gap-2 sm:grid-cols-2\">\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"actQuantity\">Quantity</Label>\n <Input\n id=\"actQuantity\"\n type=\"number\"\n step=\"any\"\n value={activityForm.quantity}\n onChange={(e) => setActivityForm({ ...activityForm, quantity: e.target.value })}\n placeholder=\"500\"\n />\n </div>\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"actUnit\">Unit</Label>\n <Input\n id=\"actUnit\"\n value={activityForm.unit}\n onChange={(e) => setActivityForm({ ...activityForm, unit: e.target.value })}\n placeholder=\"saplings\"\n />\n </div>\n </div>\n </div>\n <ResponsiveDialogFooter>\n <Button variant=\"outline\" onClick={() => setActivityOpen(false)}>\n Cancel\n </Button>\n <Button onClick={handleLogActivity} disabled={logActivity.isPending}>\n {logActivity.isPending ? 'Logging…' : 'Log activity'}\n </Button>\n </ResponsiveDialogFooter>\n </ResponsiveDialogContent>\n </ResponsiveDialog>\n </PageLayout>\n );\n}\n"],"mappings":"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"}
|
|
@@ -13,7 +13,7 @@ import { FileLink as m } from "../../components/FileLink.js";
|
|
|
13
13
|
import { EcoDiscussion as h } from "../../components/EcoDiscussion.js";
|
|
14
14
|
import { useMemo as g, useState as _ } from "react";
|
|
15
15
|
import { ArrowLeft as v, ListPlus as y, Send as b } from "lucide-react";
|
|
16
|
-
import { useParams as x } from "react-router
|
|
16
|
+
import { useParams as x } from "react-router";
|
|
17
17
|
import { Fragment as S, jsx as C, jsxs as w } from "react/jsx-runtime";
|
|
18
18
|
import { Button as T, GlassCard as E, Input as D, Label as O, ResponsiveDialog as k, ResponsiveDialogContent as ee, ResponsiveDialogDescription as A, ResponsiveDialogFooter as j, ResponsiveDialogHeader as M, ResponsiveDialogTitle as N } from "@burdenoff/fe-libs/ui";
|
|
19
19
|
//#region src/ecoimpacthub/pages/reports/ReportDetailPage.tsx
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ReportDetailPage.js","names":[],"sources":["../../../../src/ecoimpacthub/pages/reports/ReportDetailPage.tsx"],"sourcesContent":["/**\n * Report detail — overview, line items (with total) & disclosure actions.\n * @module ecoimpacthub/pages/reports\n *\n * Reads `ecoReport` (+ nested lineItems); writes `ecoAddReportLineItem`,\n * `ecoPublishReport`. The hero pairs a fact-rich overview with an actions card;\n * line items render as a crisp table whose footer carries the running TOTAL, and\n * a discussion thread (platform EntityComment API) sits below.\n */\n\nimport { useMemo, useState, type ReactNode } from 'react';\nimport { useParams } from 'react-router-dom';\nimport { ArrowLeft, ListPlus, Send } from 'lucide-react';\nimport {\n Button,\n Input,\n Label,\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 { LoadingView, ErrorView, Section } from '../../components/StateViews';\nimport { EmptyState } from '../../components/EmptyState';\nimport { EvidenceUpload } from '../../components/EvidenceUpload';\nimport { FileLink } from '../../components/FileLink';\nimport { EcoDiscussion } from '../../components/EcoDiscussion';\nimport { useEcoImpactHub } from '../../context';\nimport {\n useEcoReport,\n useEcoAddReportLineItem,\n useEcoPublishReport,\n type UploadedEcoFile,\n} from '../../hooks';\nimport { formatDate, formatNumber } from '../../utils/formatters';\nimport { FrameworkBadge } from '../badges';\n\n/** A labelled key/value cell for the overview grid. */\nfunction Fact({ label, children }: { label: string; children: ReactNode }) {\n return (\n <div>\n <dt className=\"text-xs text-text-tertiary\">{label}</dt>\n <dd className=\"mt-1 text-sm text-text-primary\">{children}</dd>\n </div>\n );\n}\n\nexport function ReportDetailPage() {\n const { id } = useParams<{ id: string }>();\n const { navigateTo } = useEcoImpactHub();\n const { data: report, isLoading, isError, refetch } = useEcoReport(id);\n\n const addLineItem = useEcoAddReportLineItem();\n const publishReport = useEcoPublishReport();\n\n const [lineItemOpen, setLineItemOpen] = useState(false);\n const [lineItemForm, setLineItemForm] = useState({\n label: '',\n value: '',\n unit: '',\n category: '',\n carbonEntryId: '',\n });\n const [error, setError] = useState<string | null>(null);\n const [attachment, setAttachment] = useState<UploadedEcoFile | null>(null);\n\n const lineItems = useMemo(() => report?.lineItems ?? [], [report]);\n const total = useMemo(() => lineItems.reduce((sum, li) => sum + li.value, 0), [lineItems]);\n const totalUnit = useMemo(() => {\n const units = new Set(lineItems.map((li) => li.unit));\n return units.size === 1 ? [...units][0] : null;\n }, [lineItems]);\n\n if (isLoading) {\n return (\n <PageLayout title=\"Report\">\n <LoadingView />\n </PageLayout>\n );\n }\n if (isError || !report) {\n return (\n <PageLayout title=\"Report\">\n <ErrorView message=\"This report could not be loaded.\" onRetry={() => refetch()} />\n </PageLayout>\n );\n }\n\n const isPublished = report.status === 'PUBLISHED';\n\n const handleAddLineItem = async () => {\n if (!id) return;\n if (!lineItemForm.label.trim() || !lineItemForm.value || !lineItemForm.unit.trim()) {\n setError('Label, value and unit are required.');\n return;\n }\n setError(null);\n try {\n await addLineItem.mutateAsync({\n reportId: id,\n label: lineItemForm.label.trim(),\n value: Number(lineItemForm.value),\n unit: lineItemForm.unit.trim(),\n category: lineItemForm.category.trim() || null,\n carbonEntryId: lineItemForm.carbonEntryId.trim() || null,\n });\n setLineItemOpen(false);\n setLineItemForm({ label: '', value: '', unit: '', category: '', carbonEntryId: '' });\n } catch (err) {\n setError(err instanceof Error ? err.message : 'Failed to add line item.');\n }\n };\n\n const handlePublish = async () => {\n if (!id) return;\n setError(null);\n try {\n await publishReport.mutateAsync({ id, fileId: attachment?.fileId ?? null });\n setAttachment(null);\n } catch (err) {\n setError(err instanceof Error ? err.message : 'Failed to publish report.');\n }\n };\n\n return (\n <PageLayout\n title={\n <span className=\"flex flex-wrap items-center gap-x-3 gap-y-2\">\n <span className=\"text-text-primary\">{report.title}</span>\n <FrameworkBadge framework={report.framework} />\n </span>\n }\n description={`Reporting period ${formatDate(report.periodStart)} – ${formatDate(report.periodEnd)}`}\n actions={\n <>\n <StatusBadge status={report.status} />\n <Button variant=\"ghost\" onClick={() => navigateTo('reports')}>\n <ArrowLeft className=\"mr-1.5 h-4 w-4\" /> Back\n </Button>\n </>\n }\n >\n {/* Overview + actions */}\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 <Fact label=\"Framework\">\n <FrameworkBadge framework={report.framework} />\n </Fact>\n <Fact label=\"Status\">\n <StatusBadge status={report.status} />\n </Fact>\n <Fact label=\"Period\">\n <span className=\"tabular-nums\">\n {formatDate(report.periodStart)} – {formatDate(report.periodEnd)}\n </span>\n </Fact>\n <Fact label=\"Published\">{formatDate(report.publishedAt)}</Fact>\n <Fact label=\"Line items\">\n <span className=\"tabular-nums\">{lineItems.length}</span>\n </Fact>\n <Fact label=\"Attachment\">\n {report.file?.id ? (\n <FileLink fileId={report.file.id} />\n ) : (\n <span className=\"text-text-tertiary\">—</span>\n )}\n </Fact>\n </dl>\n {report.summary && (\n <p className=\"mt-5 rounded-lg bg-bg-sunken/60 p-3 text-sm text-text-secondary\">\n {report.summary}\n </p>\n )}\n </GlassCard>\n\n <GlassCard treatment=\"glass\" className=\"flex flex-col gap-3 p-6\">\n <p className=\"text-xs font-medium uppercase tracking-wide text-text-tertiary\">Actions</p>\n <Button onClick={() => setLineItemOpen(true)}>\n <ListPlus className=\"mr-1.5 h-4 w-4\" /> Add line item\n </Button>\n {isPublished ? (\n <p className=\"mt-1 text-sm text-text-secondary\">\n Published {formatDate(report.publishedAt)}. This disclosure is finalised.\n </p>\n ) : (\n <>\n <EvidenceUpload\n id=\"reportAttachment\"\n label=\"Attach file (optional)\"\n value={attachment}\n onChange={setAttachment}\n hint=\"Attach the signed report PDF or supporting document, then publish.\"\n />\n <Button variant=\"outline\" onClick={handlePublish} disabled={publishReport.isPending}>\n <Send className=\"mr-1.5 h-4 w-4\" />\n {publishReport.isPending ? 'Publishing…' : 'Publish report'}\n </Button>\n </>\n )}\n </GlassCard>\n </div>\n\n {error && <p className=\"mt-4 text-sm text-status-error-text\">{error}</p>}\n\n {/* Line items */}\n <Section\n title=\"Line items\"\n description=\"Quantified disclosures that make up this report.\"\n action={\n <Button size=\"sm\" onClick={() => setLineItemOpen(true)}>\n <ListPlus className=\"mr-1.5 h-4 w-4\" /> Add line item\n </Button>\n }\n >\n {lineItems.length === 0 ? (\n <EmptyState\n illustration=\"empty-data\"\n title=\"No line items\"\n description=\"Add a line item to record a quantified disclosure for this report.\"\n action={\n <Button size=\"sm\" onClick={() => setLineItemOpen(true)}>\n <ListPlus className=\"mr-1.5 h-4 w-4\" /> Add line item\n </Button>\n }\n />\n ) : (\n <GlassCard treatment=\"plain\" className=\"overflow-hidden\">\n <div className=\"overflow-x-auto\">\n <table className=\"w-full border-collapse text-sm\">\n <thead>\n <tr className=\"border-b border-border-seam text-left\">\n <th className=\"px-4 py-3 font-medium text-text-secondary\">Line item</th>\n <th className=\"hidden px-4 py-3 font-medium text-text-secondary md:table-cell\">\n Category\n </th>\n <th className=\"px-4 py-3 text-right font-medium text-text-secondary\">Value</th>\n </tr>\n </thead>\n <tbody>\n {lineItems.map((li) => (\n <tr key={li.id} className=\"border-b border-border-seam/60\">\n <td className=\"px-4 py-3 font-medium text-text-primary\">{li.label}</td>\n <td className=\"hidden px-4 py-3 text-text-secondary md:table-cell\">\n {li.category ?? '—'}\n </td>\n <td className=\"px-4 py-3 text-right text-text-primary tabular-nums\">\n {formatNumber(li.value)} {li.unit}\n </td>\n </tr>\n ))}\n </tbody>\n <tfoot>\n <tr className=\"border-t-2 border-border-seam bg-bg-sunken/40\">\n <td className=\"px-4 py-3 text-sm font-semibold text-text-primary\" colSpan={2}>\n Total\n </td>\n <td className=\"px-4 py-3 text-right text-base font-bold text-text-primary tabular-nums\">\n {formatNumber(total)}\n {totalUnit ? ` ${totalUnit}` : ''}\n </td>\n </tr>\n </tfoot>\n </table>\n </div>\n </GlassCard>\n )}\n </Section>\n\n {/* Discussion — reuses the platform EntityComment API (no eco-svc change) */}\n <EcoDiscussion\n entityType=\"ecoimpacthub-report\"\n entityId={report.id}\n description=\"Coordinate review and sign-off on this disclosure with your team.\"\n />\n\n {/* Add line item dialog */}\n <ResponsiveDialog open={lineItemOpen} onOpenChange={setLineItemOpen}>\n <ResponsiveDialogContent>\n <ResponsiveDialogHeader>\n <ResponsiveDialogTitle>Add line item</ResponsiveDialogTitle>\n <ResponsiveDialogDescription>\n Record a quantified disclosure for {report.title}.\n </ResponsiveDialogDescription>\n </ResponsiveDialogHeader>\n <div className=\"grid gap-4 py-2\">\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"liLabel\">Label *</Label>\n <Input\n id=\"liLabel\"\n value={lineItemForm.label}\n onChange={(e) => setLineItemForm({ ...lineItemForm, label: e.target.value })}\n placeholder=\"Scope 1 emissions\"\n />\n </div>\n <div className=\"grid gap-2 sm:grid-cols-2\">\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"liValue\">Value *</Label>\n <Input\n id=\"liValue\"\n type=\"number\"\n step=\"any\"\n value={lineItemForm.value}\n onChange={(e) => setLineItemForm({ ...lineItemForm, value: e.target.value })}\n placeholder=\"1250\"\n />\n </div>\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"liUnit\">Unit *</Label>\n <Input\n id=\"liUnit\"\n value={lineItemForm.unit}\n onChange={(e) => setLineItemForm({ ...lineItemForm, unit: e.target.value })}\n placeholder=\"t CO₂e\"\n />\n </div>\n </div>\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"liCategory\">Category</Label>\n <Input\n id=\"liCategory\"\n value={lineItemForm.category}\n onChange={(e) => setLineItemForm({ ...lineItemForm, category: e.target.value })}\n placeholder=\"Optional grouping, e.g. Energy\"\n />\n </div>\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"liCarbonEntry\">Carbon entry ID</Label>\n <Input\n id=\"liCarbonEntry\"\n value={lineItemForm.carbonEntryId}\n onChange={(e) =>\n setLineItemForm({ ...lineItemForm, carbonEntryId: e.target.value })\n }\n placeholder=\"Optional linked carbon-ledger entry\"\n />\n </div>\n </div>\n <ResponsiveDialogFooter>\n <Button variant=\"outline\" onClick={() => setLineItemOpen(false)}>\n Cancel\n </Button>\n <Button onClick={handleAddLineItem} disabled={addLineItem.isPending}>\n {addLineItem.isPending ? 'Adding…' : 'Add line item'}\n </Button>\n </ResponsiveDialogFooter>\n </ResponsiveDialogContent>\n </ResponsiveDialog>\n </PageLayout>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"ReportDetailPage.js","names":[],"sources":["../../../../src/ecoimpacthub/pages/reports/ReportDetailPage.tsx"],"sourcesContent":["/**\n * Report detail — overview, line items (with total) & disclosure actions.\n * @module ecoimpacthub/pages/reports\n *\n * Reads `ecoReport` (+ nested lineItems); writes `ecoAddReportLineItem`,\n * `ecoPublishReport`. The hero pairs a fact-rich overview with an actions card;\n * line items render as a crisp table whose footer carries the running TOTAL, and\n * a discussion thread (platform EntityComment API) sits below.\n */\n\nimport { useMemo, useState, type ReactNode } from 'react';\nimport { useParams } from 'react-router';\nimport { ArrowLeft, ListPlus, Send } from 'lucide-react';\nimport {\n Button,\n Input,\n Label,\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 { LoadingView, ErrorView, Section } from '../../components/StateViews';\nimport { EmptyState } from '../../components/EmptyState';\nimport { EvidenceUpload } from '../../components/EvidenceUpload';\nimport { FileLink } from '../../components/FileLink';\nimport { EcoDiscussion } from '../../components/EcoDiscussion';\nimport { useEcoImpactHub } from '../../context';\nimport {\n useEcoReport,\n useEcoAddReportLineItem,\n useEcoPublishReport,\n type UploadedEcoFile,\n} from '../../hooks';\nimport { formatDate, formatNumber } from '../../utils/formatters';\nimport { FrameworkBadge } from '../badges';\n\n/** A labelled key/value cell for the overview grid. */\nfunction Fact({ label, children }: { label: string; children: ReactNode }) {\n return (\n <div>\n <dt className=\"text-xs text-text-tertiary\">{label}</dt>\n <dd className=\"mt-1 text-sm text-text-primary\">{children}</dd>\n </div>\n );\n}\n\nexport function ReportDetailPage() {\n const { id } = useParams<{ id: string }>();\n const { navigateTo } = useEcoImpactHub();\n const { data: report, isLoading, isError, refetch } = useEcoReport(id);\n\n const addLineItem = useEcoAddReportLineItem();\n const publishReport = useEcoPublishReport();\n\n const [lineItemOpen, setLineItemOpen] = useState(false);\n const [lineItemForm, setLineItemForm] = useState({\n label: '',\n value: '',\n unit: '',\n category: '',\n carbonEntryId: '',\n });\n const [error, setError] = useState<string | null>(null);\n const [attachment, setAttachment] = useState<UploadedEcoFile | null>(null);\n\n const lineItems = useMemo(() => report?.lineItems ?? [], [report]);\n const total = useMemo(() => lineItems.reduce((sum, li) => sum + li.value, 0), [lineItems]);\n const totalUnit = useMemo(() => {\n const units = new Set(lineItems.map((li) => li.unit));\n return units.size === 1 ? [...units][0] : null;\n }, [lineItems]);\n\n if (isLoading) {\n return (\n <PageLayout title=\"Report\">\n <LoadingView />\n </PageLayout>\n );\n }\n if (isError || !report) {\n return (\n <PageLayout title=\"Report\">\n <ErrorView message=\"This report could not be loaded.\" onRetry={() => refetch()} />\n </PageLayout>\n );\n }\n\n const isPublished = report.status === 'PUBLISHED';\n\n const handleAddLineItem = async () => {\n if (!id) return;\n if (!lineItemForm.label.trim() || !lineItemForm.value || !lineItemForm.unit.trim()) {\n setError('Label, value and unit are required.');\n return;\n }\n setError(null);\n try {\n await addLineItem.mutateAsync({\n reportId: id,\n label: lineItemForm.label.trim(),\n value: Number(lineItemForm.value),\n unit: lineItemForm.unit.trim(),\n category: lineItemForm.category.trim() || null,\n carbonEntryId: lineItemForm.carbonEntryId.trim() || null,\n });\n setLineItemOpen(false);\n setLineItemForm({ label: '', value: '', unit: '', category: '', carbonEntryId: '' });\n } catch (err) {\n setError(err instanceof Error ? err.message : 'Failed to add line item.');\n }\n };\n\n const handlePublish = async () => {\n if (!id) return;\n setError(null);\n try {\n await publishReport.mutateAsync({ id, fileId: attachment?.fileId ?? null });\n setAttachment(null);\n } catch (err) {\n setError(err instanceof Error ? err.message : 'Failed to publish report.');\n }\n };\n\n return (\n <PageLayout\n title={\n <span className=\"flex flex-wrap items-center gap-x-3 gap-y-2\">\n <span className=\"text-text-primary\">{report.title}</span>\n <FrameworkBadge framework={report.framework} />\n </span>\n }\n description={`Reporting period ${formatDate(report.periodStart)} – ${formatDate(report.periodEnd)}`}\n actions={\n <>\n <StatusBadge status={report.status} />\n <Button variant=\"ghost\" onClick={() => navigateTo('reports')}>\n <ArrowLeft className=\"mr-1.5 h-4 w-4\" /> Back\n </Button>\n </>\n }\n >\n {/* Overview + actions */}\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 <Fact label=\"Framework\">\n <FrameworkBadge framework={report.framework} />\n </Fact>\n <Fact label=\"Status\">\n <StatusBadge status={report.status} />\n </Fact>\n <Fact label=\"Period\">\n <span className=\"tabular-nums\">\n {formatDate(report.periodStart)} – {formatDate(report.periodEnd)}\n </span>\n </Fact>\n <Fact label=\"Published\">{formatDate(report.publishedAt)}</Fact>\n <Fact label=\"Line items\">\n <span className=\"tabular-nums\">{lineItems.length}</span>\n </Fact>\n <Fact label=\"Attachment\">\n {report.file?.id ? (\n <FileLink fileId={report.file.id} />\n ) : (\n <span className=\"text-text-tertiary\">—</span>\n )}\n </Fact>\n </dl>\n {report.summary && (\n <p className=\"mt-5 rounded-lg bg-bg-sunken/60 p-3 text-sm text-text-secondary\">\n {report.summary}\n </p>\n )}\n </GlassCard>\n\n <GlassCard treatment=\"glass\" className=\"flex flex-col gap-3 p-6\">\n <p className=\"text-xs font-medium uppercase tracking-wide text-text-tertiary\">Actions</p>\n <Button onClick={() => setLineItemOpen(true)}>\n <ListPlus className=\"mr-1.5 h-4 w-4\" /> Add line item\n </Button>\n {isPublished ? (\n <p className=\"mt-1 text-sm text-text-secondary\">\n Published {formatDate(report.publishedAt)}. This disclosure is finalised.\n </p>\n ) : (\n <>\n <EvidenceUpload\n id=\"reportAttachment\"\n label=\"Attach file (optional)\"\n value={attachment}\n onChange={setAttachment}\n hint=\"Attach the signed report PDF or supporting document, then publish.\"\n />\n <Button variant=\"outline\" onClick={handlePublish} disabled={publishReport.isPending}>\n <Send className=\"mr-1.5 h-4 w-4\" />\n {publishReport.isPending ? 'Publishing…' : 'Publish report'}\n </Button>\n </>\n )}\n </GlassCard>\n </div>\n\n {error && <p className=\"mt-4 text-sm text-status-error-text\">{error}</p>}\n\n {/* Line items */}\n <Section\n title=\"Line items\"\n description=\"Quantified disclosures that make up this report.\"\n action={\n <Button size=\"sm\" onClick={() => setLineItemOpen(true)}>\n <ListPlus className=\"mr-1.5 h-4 w-4\" /> Add line item\n </Button>\n }\n >\n {lineItems.length === 0 ? (\n <EmptyState\n illustration=\"empty-data\"\n title=\"No line items\"\n description=\"Add a line item to record a quantified disclosure for this report.\"\n action={\n <Button size=\"sm\" onClick={() => setLineItemOpen(true)}>\n <ListPlus className=\"mr-1.5 h-4 w-4\" /> Add line item\n </Button>\n }\n />\n ) : (\n <GlassCard treatment=\"plain\" className=\"overflow-hidden\">\n <div className=\"overflow-x-auto\">\n <table className=\"w-full border-collapse text-sm\">\n <thead>\n <tr className=\"border-b border-border-seam text-left\">\n <th className=\"px-4 py-3 font-medium text-text-secondary\">Line item</th>\n <th className=\"hidden px-4 py-3 font-medium text-text-secondary md:table-cell\">\n Category\n </th>\n <th className=\"px-4 py-3 text-right font-medium text-text-secondary\">Value</th>\n </tr>\n </thead>\n <tbody>\n {lineItems.map((li) => (\n <tr key={li.id} className=\"border-b border-border-seam/60\">\n <td className=\"px-4 py-3 font-medium text-text-primary\">{li.label}</td>\n <td className=\"hidden px-4 py-3 text-text-secondary md:table-cell\">\n {li.category ?? '—'}\n </td>\n <td className=\"px-4 py-3 text-right text-text-primary tabular-nums\">\n {formatNumber(li.value)} {li.unit}\n </td>\n </tr>\n ))}\n </tbody>\n <tfoot>\n <tr className=\"border-t-2 border-border-seam bg-bg-sunken/40\">\n <td className=\"px-4 py-3 text-sm font-semibold text-text-primary\" colSpan={2}>\n Total\n </td>\n <td className=\"px-4 py-3 text-right text-base font-bold text-text-primary tabular-nums\">\n {formatNumber(total)}\n {totalUnit ? ` ${totalUnit}` : ''}\n </td>\n </tr>\n </tfoot>\n </table>\n </div>\n </GlassCard>\n )}\n </Section>\n\n {/* Discussion — reuses the platform EntityComment API (no eco-svc change) */}\n <EcoDiscussion\n entityType=\"ecoimpacthub-report\"\n entityId={report.id}\n description=\"Coordinate review and sign-off on this disclosure with your team.\"\n />\n\n {/* Add line item dialog */}\n <ResponsiveDialog open={lineItemOpen} onOpenChange={setLineItemOpen}>\n <ResponsiveDialogContent>\n <ResponsiveDialogHeader>\n <ResponsiveDialogTitle>Add line item</ResponsiveDialogTitle>\n <ResponsiveDialogDescription>\n Record a quantified disclosure for {report.title}.\n </ResponsiveDialogDescription>\n </ResponsiveDialogHeader>\n <div className=\"grid gap-4 py-2\">\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"liLabel\">Label *</Label>\n <Input\n id=\"liLabel\"\n value={lineItemForm.label}\n onChange={(e) => setLineItemForm({ ...lineItemForm, label: e.target.value })}\n placeholder=\"Scope 1 emissions\"\n />\n </div>\n <div className=\"grid gap-2 sm:grid-cols-2\">\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"liValue\">Value *</Label>\n <Input\n id=\"liValue\"\n type=\"number\"\n step=\"any\"\n value={lineItemForm.value}\n onChange={(e) => setLineItemForm({ ...lineItemForm, value: e.target.value })}\n placeholder=\"1250\"\n />\n </div>\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"liUnit\">Unit *</Label>\n <Input\n id=\"liUnit\"\n value={lineItemForm.unit}\n onChange={(e) => setLineItemForm({ ...lineItemForm, unit: e.target.value })}\n placeholder=\"t CO₂e\"\n />\n </div>\n </div>\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"liCategory\">Category</Label>\n <Input\n id=\"liCategory\"\n value={lineItemForm.category}\n onChange={(e) => setLineItemForm({ ...lineItemForm, category: e.target.value })}\n placeholder=\"Optional grouping, e.g. Energy\"\n />\n </div>\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"liCarbonEntry\">Carbon entry ID</Label>\n <Input\n id=\"liCarbonEntry\"\n value={lineItemForm.carbonEntryId}\n onChange={(e) =>\n setLineItemForm({ ...lineItemForm, carbonEntryId: e.target.value })\n }\n placeholder=\"Optional linked carbon-ledger entry\"\n />\n </div>\n </div>\n <ResponsiveDialogFooter>\n <Button variant=\"outline\" onClick={() => setLineItemOpen(false)}>\n Cancel\n </Button>\n <Button onClick={handleAddLineItem} disabled={addLineItem.isPending}>\n {addLineItem.isPending ? 'Adding…' : 'Add line item'}\n </Button>\n </ResponsiveDialogFooter>\n </ResponsiveDialogContent>\n </ResponsiveDialog>\n </PageLayout>\n );\n}\n"],"mappings":"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"}
|
|
@@ -13,7 +13,7 @@ import { EcoTags as pe } from "../../components/EcoTags.js";
|
|
|
13
13
|
import { SiteTypeBadge as me } from "./siteTypeVisuals.js";
|
|
14
14
|
import { useState as l } from "react";
|
|
15
15
|
import { Activity as he, Archive as ge, ArrowLeft as _e, Gauge as ve, Plus as u, Radio as d, ShieldCheck as ye, Zap as be } from "lucide-react";
|
|
16
|
-
import { useParams as xe } from "react-router
|
|
16
|
+
import { useParams as xe } from "react-router";
|
|
17
17
|
import { Fragment as Se, jsx as f, jsxs as p } from "react/jsx-runtime";
|
|
18
18
|
import { Button as m, GlassCard as h, Input as g, Label as _, ResponsiveDialog as v, ResponsiveDialogContent as y, ResponsiveDialogDescription as b, ResponsiveDialogFooter as x, ResponsiveDialogHeader as S, ResponsiveDialogTitle as C, Select as w, SelectContent as T, SelectItem as E, SelectTrigger as D, SelectValue as O } from "@burdenoff/fe-libs/ui";
|
|
19
19
|
//#region src/ecoimpacthub/pages/sites/SiteDetailPage.tsx
|
|
@@ -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, Zap, ShieldCheck } 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 { MiniBar } from '../../components/MiniBar';\nimport { SiteTypeBadge } from './siteTypeVisuals';\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 formatRelativeTime,\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 const latestBreach = useEcoMeasurements({\n siteId: id,\n breachesOnly: true,\n pagination: { limit: 1, offset: 0 },\n });\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 // ── Monitoring summary (derived from already-loaded site data) ─────────────\n const onlineSensors = sensors.filter((s) => s.status === 'ONLINE').length;\n const attentionSensors = sensors.filter((s) =>\n ['OFFLINE', 'DEGRADED', 'MAINTENANCE'].includes(s.status)\n ).length;\n const activeThresholds = thresholds.filter((t) => t.isActive).length;\n const latestBreachRow = latestBreach.data?.items?.[0] ?? null;\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) =>\n m.isBreach ? (\n <StatusBadge status=\"BREACH\" className=\"uppercase\" />\n ) : (\n <StatusBadge status=\"OK\" className=\"uppercase\" />\n ),\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 {/* Hero: overview · quick actions + monitoring summary */}\n <div className=\"grid grid-cols-1 gap-4 lg:grid-cols-3\">\n {/* Overview */}\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-1\">\n <SiteTypeBadge type={site.type} />\n </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 {/* Right rail: quick actions + monitoring summary */}\n <div className=\"flex flex-col gap-4\">\n <GlassCard treatment=\"glass\" className=\"p-6\">\n <div className=\"mb-3 flex items-center gap-2\">\n <Zap className=\"h-4 w-4 text-accent\" />\n <h2 className=\"text-base font-semibold text-text-primary\">Quick actions</h2>\n </div>\n <div className=\"flex flex-col gap-2.5\">\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 </div>\n </GlassCard>\n\n <GlassCard treatment=\"glass\" className=\"p-6\">\n <div className=\"mb-4 flex items-center gap-2\">\n <Radio className=\"h-4 w-4 text-accent\" />\n <h2 className=\"text-base font-semibold text-text-primary\">Monitoring summary</h2>\n </div>\n <div className=\"grid grid-cols-2 gap-3\">\n <div className=\"rounded-xl bg-bg-sunken/60 p-3\">\n <p className=\"text-2xl font-semibold text-text-primary tabular-nums\">\n {onlineSensors}\n <span className=\"text-base text-text-tertiary\">/{sensors.length}</span>\n </p>\n <p className=\"mt-0.5 text-xs text-text-secondary\">Sensors online</p>\n <MiniBar\n className=\"mt-2\"\n value={sensors.length ? (onlineSensors / sensors.length) * 100 : 0}\n barClassName=\"bg-[var(--color-status-success-text)]\"\n />\n </div>\n <div className=\"rounded-xl bg-bg-sunken/60 p-3\">\n <p className=\"text-2xl font-semibold text-text-primary tabular-nums\">\n {activeThresholds}\n <span className=\"text-base text-text-tertiary\">/{thresholds.length}</span>\n </p>\n <p className=\"mt-0.5 text-xs text-text-secondary\">Active thresholds</p>\n <p\n className={\n attentionSensors > 0\n ? 'mt-2 text-xs text-status-warning-text'\n : 'mt-2 text-xs text-text-tertiary'\n }\n >\n {attentionSensors > 0\n ? `${attentionSensors} sensor${attentionSensors > 1 ? 's' : ''} need attention`\n : 'All sensors healthy'}\n </p>\n </div>\n </div>\n <div className=\"mt-4 border-t border-border-seam pt-4\">\n <p className=\"mb-2 text-xs text-text-tertiary\">Latest breach</p>\n {latestBreachRow ? (\n <div className=\"flex items-center justify-between gap-3\">\n <div className=\"min-w-0\">\n <p className=\"truncate text-sm font-medium text-text-primary\">\n {latestBreachRow.parameter}\n </p>\n <p className=\"text-xs text-text-tertiary tabular-nums\">\n {formatNumber(latestBreachRow.value)} {latestBreachRow.unit} ·{' '}\n {formatRelativeTime(latestBreachRow.recordedAt)}\n </p>\n </div>\n <StatusBadge status=\"BREACH\" className=\"shrink-0 uppercase\" />\n </div>\n ) : (\n <div className=\"flex items-center gap-2 text-sm text-text-secondary\">\n <ShieldCheck className=\"h-4 w-4 shrink-0 text-status-success-text\" />\n No breaches recorded\n </div>\n )}\n </div>\n </GlassCard>\n </div>\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 <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 </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';\nimport { ArrowLeft, Plus, Radio, Gauge, Activity, Archive, Zap, ShieldCheck } 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 { MiniBar } from '../../components/MiniBar';\nimport { SiteTypeBadge } from './siteTypeVisuals';\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 formatRelativeTime,\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 const latestBreach = useEcoMeasurements({\n siteId: id,\n breachesOnly: true,\n pagination: { limit: 1, offset: 0 },\n });\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 // ── Monitoring summary (derived from already-loaded site data) ─────────────\n const onlineSensors = sensors.filter((s) => s.status === 'ONLINE').length;\n const attentionSensors = sensors.filter((s) =>\n ['OFFLINE', 'DEGRADED', 'MAINTENANCE'].includes(s.status)\n ).length;\n const activeThresholds = thresholds.filter((t) => t.isActive).length;\n const latestBreachRow = latestBreach.data?.items?.[0] ?? null;\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) =>\n m.isBreach ? (\n <StatusBadge status=\"BREACH\" className=\"uppercase\" />\n ) : (\n <StatusBadge status=\"OK\" className=\"uppercase\" />\n ),\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 {/* Hero: overview · quick actions + monitoring summary */}\n <div className=\"grid grid-cols-1 gap-4 lg:grid-cols-3\">\n {/* Overview */}\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-1\">\n <SiteTypeBadge type={site.type} />\n </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 {/* Right rail: quick actions + monitoring summary */}\n <div className=\"flex flex-col gap-4\">\n <GlassCard treatment=\"glass\" className=\"p-6\">\n <div className=\"mb-3 flex items-center gap-2\">\n <Zap className=\"h-4 w-4 text-accent\" />\n <h2 className=\"text-base font-semibold text-text-primary\">Quick actions</h2>\n </div>\n <div className=\"flex flex-col gap-2.5\">\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 </div>\n </GlassCard>\n\n <GlassCard treatment=\"glass\" className=\"p-6\">\n <div className=\"mb-4 flex items-center gap-2\">\n <Radio className=\"h-4 w-4 text-accent\" />\n <h2 className=\"text-base font-semibold text-text-primary\">Monitoring summary</h2>\n </div>\n <div className=\"grid grid-cols-2 gap-3\">\n <div className=\"rounded-xl bg-bg-sunken/60 p-3\">\n <p className=\"text-2xl font-semibold text-text-primary tabular-nums\">\n {onlineSensors}\n <span className=\"text-base text-text-tertiary\">/{sensors.length}</span>\n </p>\n <p className=\"mt-0.5 text-xs text-text-secondary\">Sensors online</p>\n <MiniBar\n className=\"mt-2\"\n value={sensors.length ? (onlineSensors / sensors.length) * 100 : 0}\n barClassName=\"bg-[var(--color-status-success-text)]\"\n />\n </div>\n <div className=\"rounded-xl bg-bg-sunken/60 p-3\">\n <p className=\"text-2xl font-semibold text-text-primary tabular-nums\">\n {activeThresholds}\n <span className=\"text-base text-text-tertiary\">/{thresholds.length}</span>\n </p>\n <p className=\"mt-0.5 text-xs text-text-secondary\">Active thresholds</p>\n <p\n className={\n attentionSensors > 0\n ? 'mt-2 text-xs text-status-warning-text'\n : 'mt-2 text-xs text-text-tertiary'\n }\n >\n {attentionSensors > 0\n ? `${attentionSensors} sensor${attentionSensors > 1 ? 's' : ''} need attention`\n : 'All sensors healthy'}\n </p>\n </div>\n </div>\n <div className=\"mt-4 border-t border-border-seam pt-4\">\n <p className=\"mb-2 text-xs text-text-tertiary\">Latest breach</p>\n {latestBreachRow ? (\n <div className=\"flex items-center justify-between gap-3\">\n <div className=\"min-w-0\">\n <p className=\"truncate text-sm font-medium text-text-primary\">\n {latestBreachRow.parameter}\n </p>\n <p className=\"text-xs text-text-tertiary tabular-nums\">\n {formatNumber(latestBreachRow.value)} {latestBreachRow.unit} ·{' '}\n {formatRelativeTime(latestBreachRow.recordedAt)}\n </p>\n </div>\n <StatusBadge status=\"BREACH\" className=\"shrink-0 uppercase\" />\n </div>\n ) : (\n <div className=\"flex items-center gap-2 text-sm text-text-secondary\">\n <ShieldCheck className=\"h-4 w-4 shrink-0 text-status-success-text\" />\n No breaches recorded\n </div>\n )}\n </div>\n </GlassCard>\n </div>\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 <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 </PageLayout>\n );\n}\n"],"mappings":"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"}
|
|
@@ -15,7 +15,7 @@ import { ConservationBadge as c } from "../_shared/ConservationBadge.js";
|
|
|
15
15
|
import { TaxonChip as de } from "../_shared/TaxonChip.js";
|
|
16
16
|
import { useState as l } from "react";
|
|
17
17
|
import { Archive as fe, ArrowLeft as pe, Binoculars as u, Pencil as me, Star as d } from "lucide-react";
|
|
18
|
-
import { useParams as f } from "react-router
|
|
18
|
+
import { useParams as f } from "react-router";
|
|
19
19
|
import { Fragment as p, jsx as m, jsxs as h } from "react/jsx-runtime";
|
|
20
20
|
import { Button as g, GlassCard as _, Input as v, Label as y, ResponsiveDialog as b, ResponsiveDialogContent as x, ResponsiveDialogDescription as S, ResponsiveDialogFooter as C, ResponsiveDialogHeader as w, ResponsiveDialogTitle as T, Select as E, SelectContent as D, SelectItem as O, SelectTrigger as k, SelectValue as A, Textarea as j } from "@burdenoff/fe-libs/ui";
|
|
21
21
|
//#region src/ecoimpacthub/pages/species/SpeciesDetailPage.tsx
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SpeciesDetailPage.js","names":[],"sources":["../../../../src/ecoimpacthub/pages/species/SpeciesDetailPage.tsx"],"sourcesContent":["/**\n * Species detail — overview, field observations & species actions.\n * @module ecoimpacthub/pages/species\n *\n * Reads `ecoSpecies` (+ nested observations); writes `ecoRecordObservation`,\n * `ecoUpdateSpecies`, `ecoArchiveSpecies`.\n */\n\nimport { useState } from 'react';\nimport { useParams } from 'react-router-dom';\nimport { ArrowLeft, Binoculars, Pencil, Archive, Star } from 'lucide-react';\nimport {\n Button,\n Input,\n Label,\n Textarea,\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 { DataTable, type Column } from '../../components/DataTable';\nimport { LoadingView, ErrorView, Section } from '../../components/StateViews';\nimport { EmptyState } from '../../components/EmptyState';\nimport { EvidenceUpload } from '../../components/EvidenceUpload';\nimport { EcoTags } from '../../components/EcoTags';\nimport { ConservationBadge } from '../_shared/ConservationBadge';\nimport { TaxonChip } from '../_shared/TaxonChip';\nimport { MethodBadge } from '../_shared/MethodBadge';\nimport { EvidenceThumb } from '../_shared/EvidenceThumb';\nimport { useEcoImpactHub } from '../../context';\nimport {\n useEcoSpeciesDetail,\n useEcoSites,\n useEcoRecordObservation,\n useEcoUpdateSpecies,\n useEcoArchiveSpecies,\n type UploadedEcoFile,\n} from '../../hooks';\nimport { formatDateTime, formatEnumLabel, formatNumber } from '../../utils/formatters';\nimport type {\n EcoObservation,\n EcoObservationMethod,\n EcoTaxonGroup,\n EcoConservationStatus,\n} from '../../types';\n\nconst OBSERVATION_METHODS: EcoObservationMethod[] = [\n 'DIRECT_SIGHTING',\n 'CAMERA_TRAP',\n 'ACOUSTIC',\n 'TRACKS_SIGNS',\n 'EDNA',\n 'TELEMETRY',\n 'COMMUNITY_REPORT',\n];\n\nconst TAXON_GROUPS: EcoTaxonGroup[] = [\n 'MAMMAL',\n 'BIRD',\n 'REPTILE',\n 'AMPHIBIAN',\n 'FISH',\n 'INSECT',\n 'PLANT',\n 'FUNGI',\n 'OTHER',\n];\n\nconst CONSERVATION_STATUSES: EcoConservationStatus[] = [\n 'LEAST_CONCERN',\n 'NEAR_THREATENED',\n 'VULNERABLE',\n 'ENDANGERED',\n 'CRITICALLY_ENDANGERED',\n 'EXTINCT_IN_WILD',\n 'DATA_DEFICIENT',\n 'NOT_EVALUATED',\n];\n\n/** Format a Date into the `YYYY-MM-DDTHH:mm` value a datetime-local input expects. */\nconst toLocalInputValue = (d: Date): string => {\n const pad = (n: number) => String(n).padStart(2, '0');\n return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}T${pad(\n d.getHours()\n )}:${pad(d.getMinutes())}`;\n};\n\ninterface EditFormState {\n commonName: string;\n scientificName: string;\n taxonGroup: EcoTaxonGroup;\n conservationStatus: EcoConservationStatus;\n isPriority: boolean;\n description: string;\n}\n\nexport function SpeciesDetailPage() {\n const { id } = useParams<{ id: string }>();\n const { navigateTo } = useEcoImpactHub();\n const { data: species, isLoading, isError, refetch } = useEcoSpeciesDetail(id);\n const sites = useEcoSites({ pagination: { limit: 100, offset: 0 } });\n\n const recordObservation = useEcoRecordObservation();\n const updateSpecies = useEcoUpdateSpecies();\n const archiveSpecies = useEcoArchiveSpecies();\n\n const [observeOpen, setObserveOpen] = useState(false);\n const [observeForm, setObserveForm] = useState<{\n method: EcoObservationMethod;\n count: string;\n observedAt: string;\n siteId: string;\n notes: string;\n evidence: UploadedEcoFile | null;\n }>({\n method: 'DIRECT_SIGHTING',\n count: '1',\n observedAt: toLocalInputValue(new Date()),\n siteId: '',\n notes: '',\n evidence: null,\n });\n const [editOpen, setEditOpen] = useState(false);\n const [editForm, setEditForm] = useState<EditFormState>({\n commonName: '',\n scientificName: '',\n taxonGroup: 'MAMMAL',\n conservationStatus: 'NOT_EVALUATED',\n isPriority: false,\n description: '',\n });\n const [error, setError] = useState<string | null>(null);\n\n if (isLoading) {\n return (\n <PageLayout title=\"Species\">\n <LoadingView />\n </PageLayout>\n );\n }\n if (isError || !species) {\n return (\n <PageLayout title=\"Species\">\n <ErrorView message=\"This species could not be loaded.\" onRetry={() => refetch()} />\n </PageLayout>\n );\n }\n\n const observations = species.observations ?? [];\n const siteRows = sites.data?.items ?? [];\n const siteNameById = new Map<string, string>();\n for (const s of siteRows) siteNameById.set(s.id, s.name);\n\n const openObserve = () => {\n setObserveForm({\n method: 'DIRECT_SIGHTING',\n count: '1',\n observedAt: toLocalInputValue(new Date()),\n siteId: '',\n notes: '',\n evidence: null,\n });\n setError(null);\n setObserveOpen(true);\n };\n\n const openEdit = () => {\n setEditForm({\n commonName: species.commonName,\n scientificName: species.scientificName ?? '',\n taxonGroup: species.taxonGroup,\n conservationStatus: species.conservationStatus,\n isPriority: species.isPriority,\n description: species.description ?? '',\n });\n setError(null);\n setEditOpen(true);\n };\n\n const handleRecordObservation = async () => {\n if (!id) return;\n setError(null);\n try {\n await recordObservation.mutateAsync({\n speciesId: id,\n method: observeForm.method,\n count: observeForm.count ? Number(observeForm.count) : 1,\n observedAt: new Date(observeForm.observedAt).toISOString(),\n siteId: observeForm.siteId || null,\n notes: observeForm.notes.trim() || null,\n evidenceFileId: observeForm.evidence?.fileId ?? null,\n });\n setObserveOpen(false);\n } catch (err) {\n setError(err instanceof Error ? err.message : 'Failed to record observation.');\n }\n };\n\n const handleUpdate = async () => {\n if (!id || !editForm.commonName.trim()) {\n setError('Common name is required.');\n return;\n }\n setError(null);\n try {\n await updateSpecies.mutateAsync({\n id,\n input: {\n commonName: editForm.commonName.trim(),\n scientificName: editForm.scientificName.trim() || null,\n taxonGroup: editForm.taxonGroup,\n conservationStatus: editForm.conservationStatus,\n isPriority: editForm.isPriority,\n description: editForm.description.trim() || null,\n },\n });\n setEditOpen(false);\n } catch (err) {\n setError(err instanceof Error ? err.message : 'Failed to update species.');\n }\n };\n\n const handleArchive = async () => {\n if (!id) return;\n setError(null);\n try {\n await archiveSpecies.mutateAsync(id);\n navigateTo('species');\n } catch (err) {\n setError(err instanceof Error ? err.message : 'Failed to archive species.');\n }\n };\n\n const observationColumns: Column<EcoObservation>[] = [\n {\n key: 'evidence',\n header: 'Evidence',\n render: (o) => <EvidenceThumb fileId={o.evidence?.id} alt={species.commonName} />,\n },\n {\n key: 'method',\n header: 'Method',\n render: (o) => <MethodBadge method={o.method} />,\n },\n {\n key: 'count',\n header: 'Count',\n align: 'right',\n sortValue: (o) => o.count,\n render: (o) => (\n <span className=\"tabular-nums font-medium text-text-primary\">×{formatNumber(o.count)}</span>\n ),\n },\n {\n key: 'observedAt',\n header: 'Observed',\n hideOnMobile: true,\n render: (o) => (\n <span className=\"whitespace-nowrap text-text-secondary\">\n {formatDateTime(o.observedAt)}\n </span>\n ),\n },\n {\n key: 'site',\n header: 'Site',\n hideOnMobile: true,\n render: (o) => {\n const name = o.siteId ? siteNameById.get(o.siteId) : undefined;\n return name ? (\n <span className=\"text-text-secondary\">{name}</span>\n ) : (\n <span className=\"text-text-tertiary\">—</span>\n );\n },\n },\n {\n key: 'notes',\n header: 'Notes',\n hideOnMobile: true,\n render: (o) =>\n o.notes ? (\n <span className=\"block max-w-xs truncate text-text-secondary\" title={o.notes}>\n {o.notes}\n </span>\n ) : (\n <span className=\"text-text-tertiary\">—</span>\n ),\n },\n ];\n\n return (\n <PageLayout\n title={species.commonName}\n description={[species.scientificName, formatEnumLabel(species.taxonGroup)]\n .filter(Boolean)\n .join(' · ')}\n actions={\n <>\n <ConservationBadge status={species.conservationStatus} />\n {species.isPriority && (\n <span\n className=\"inline-flex items-center gap-1 rounded-full bg-status-warning-bg-subtle px-2.5 py-0.5 text-xs font-semibold text-status-warning-text\"\n title=\"Priority species\"\n >\n <Star className=\"h-3.5 w-3.5 fill-current\" /> Priority\n </span>\n )}\n <Button variant=\"ghost\" onClick={() => navigateTo('species')}>\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\">Scientific name</dt>\n <dd className=\"mt-0.5 text-sm italic text-text-primary\">\n {species.scientificName ?? '—'}\n </dd>\n </div>\n <div>\n <dt className=\"text-xs text-text-tertiary\">Taxon group</dt>\n <dd className=\"mt-1\">\n <TaxonChip group={species.taxonGroup} />\n </dd>\n </div>\n <div>\n <dt className=\"text-xs text-text-tertiary\">Conservation</dt>\n <dd className=\"mt-1\">\n <ConservationBadge status={species.conservationStatus} />\n </dd>\n </div>\n <div>\n <dt className=\"text-xs text-text-tertiary\">Priority</dt>\n <dd className=\"mt-0.5 text-sm text-text-primary\">\n {species.isPriority ? (\n <span className=\"inline-flex items-center gap-1 text-status-warning-text\">\n <Star className=\"h-4 w-4 fill-current\" /> Priority\n </span>\n ) : (\n '—'\n )}\n </dd>\n </div>\n <div>\n <dt className=\"text-xs text-text-tertiary\">Observations</dt>\n <dd className=\"mt-0.5 text-sm text-text-primary tabular-nums\">\n {observations.length}\n </dd>\n </div>\n </dl>\n {species.description && (\n <p className=\"mt-5 rounded-lg bg-bg-sunken/60 p-3 text-sm text-text-secondary\">\n {species.description}\n </p>\n )}\n <div className=\"mt-5 border-t border-border-seam pt-4\">\n <EcoTags entityType=\"ecoimpacthub-species\" entityId={species.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={openObserve}>\n <Binoculars className=\"mr-1.5 h-4 w-4\" /> Record observation\n </Button>\n <Button variant=\"outline\" onClick={openEdit}>\n <Pencil className=\"mr-1.5 h-4 w-4\" /> Edit species\n </Button>\n <Button\n variant=\"ghost\"\n onClick={handleArchive}\n disabled={archiveSpecies.isPending}\n className=\"text-status-error-text hover:text-status-error-text\"\n >\n <Archive className=\"mr-1.5 h-4 w-4\" /> Archive\n </Button>\n </GlassCard>\n </div>\n\n {error && <p className=\"mt-4 text-sm text-status-error-text\">{error}</p>}\n\n {/* Observations */}\n <Section\n title=\"Observations\"\n description=\"Field records logged against this species.\"\n action={\n <Button size=\"sm\" onClick={openObserve}>\n <Binoculars className=\"mr-1.5 h-4 w-4\" /> Record observation\n </Button>\n }\n >\n {observations.length === 0 ? (\n <EmptyState\n illustration=\"empty-data\"\n title=\"No observations yet\"\n description=\"Record a field observation to see it here.\"\n />\n ) : (\n <DataTable columns={observationColumns} rows={observations} rowKey={(o) => o.id} />\n )}\n </Section>\n\n {/* Record observation dialog */}\n <ResponsiveDialog open={observeOpen} onOpenChange={setObserveOpen}>\n <ResponsiveDialogContent>\n <ResponsiveDialogHeader>\n <ResponsiveDialogTitle>Record observation</ResponsiveDialogTitle>\n <ResponsiveDialogDescription>\n Log a field sighting or detection of {species.commonName}.\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=\"obsMethod\">Method</Label>\n <Select\n value={observeForm.method}\n onValueChange={(v) =>\n setObserveForm({ ...observeForm, method: v as EcoObservationMethod })\n }\n >\n <SelectTrigger id=\"obsMethod\">\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n {OBSERVATION_METHODS.map((m) => (\n <SelectItem key={m} value={m}>\n {formatEnumLabel(m)}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </div>\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"obsCount\">Count</Label>\n <Input\n id=\"obsCount\"\n type=\"number\"\n min=\"1\"\n step=\"1\"\n value={observeForm.count}\n onChange={(e) => setObserveForm({ ...observeForm, count: e.target.value })}\n placeholder=\"1\"\n />\n </div>\n </div>\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"obsAt\">Observed at</Label>\n <Input\n id=\"obsAt\"\n type=\"datetime-local\"\n value={observeForm.observedAt}\n onChange={(e) => setObserveForm({ ...observeForm, observedAt: e.target.value })}\n />\n </div>\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"obsSite\">Site (optional)</Label>\n <Select\n value={observeForm.siteId}\n onValueChange={(v) => setObserveForm({ ...observeForm, siteId: v })}\n >\n <SelectTrigger id=\"obsSite\">\n <SelectValue placeholder=\"No site\" />\n </SelectTrigger>\n <SelectContent>\n {siteRows.map((s) => (\n <SelectItem key={s.id} value={s.id}>\n {s.name}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </div>\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"obsNotes\">Notes</Label>\n <Textarea\n id=\"obsNotes\"\n value={observeForm.notes}\n onChange={(e) => setObserveForm({ ...observeForm, notes: e.target.value })}\n placeholder=\"Optional context — behaviour, conditions, evidence.\"\n rows={2}\n />\n </div>\n <EvidenceUpload\n id=\"speciesObsEvidence\"\n label=\"Evidence photo (optional)\"\n value={observeForm.evidence}\n onChange={(evidence) => setObserveForm({ ...observeForm, evidence })}\n hint=\"Attach a photo, audio clip or PDF as supporting evidence.\"\n />\n </div>\n <ResponsiveDialogFooter>\n <Button variant=\"outline\" onClick={() => setObserveOpen(false)}>\n Cancel\n </Button>\n <Button onClick={handleRecordObservation} disabled={recordObservation.isPending}>\n {recordObservation.isPending ? 'Recording…' : 'Record'}\n </Button>\n </ResponsiveDialogFooter>\n </ResponsiveDialogContent>\n </ResponsiveDialog>\n\n {/* Edit species dialog */}\n <ResponsiveDialog open={editOpen} onOpenChange={setEditOpen}>\n <ResponsiveDialogContent>\n <ResponsiveDialogHeader>\n <ResponsiveDialogTitle>Edit species</ResponsiveDialogTitle>\n <ResponsiveDialogDescription>\n Update naming, classification and priority for this species.\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=\"editCommon\">Common name *</Label>\n <Input\n id=\"editCommon\"\n value={editForm.commonName}\n onChange={(e) => setEditForm({ ...editForm, commonName: e.target.value })}\n placeholder=\"Indian Pangolin\"\n />\n </div>\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"editScientific\">Scientific name</Label>\n <Input\n id=\"editScientific\"\n value={editForm.scientificName}\n onChange={(e) => setEditForm({ ...editForm, scientificName: e.target.value })}\n placeholder=\"Manis crassicaudata\"\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=\"editTaxon\">Taxon group</Label>\n <Select\n value={editForm.taxonGroup}\n onValueChange={(v) =>\n setEditForm({ ...editForm, taxonGroup: v as EcoTaxonGroup })\n }\n >\n <SelectTrigger id=\"editTaxon\">\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n {TAXON_GROUPS.map((t) => (\n <SelectItem key={t} value={t}>\n {formatEnumLabel(t)}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </div>\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"editConservation\">Conservation status</Label>\n <Select\n value={editForm.conservationStatus}\n onValueChange={(v) =>\n setEditForm({ ...editForm, conservationStatus: v as EcoConservationStatus })\n }\n >\n <SelectTrigger id=\"editConservation\">\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n {CONSERVATION_STATUSES.map((c) => (\n <SelectItem key={c} value={c}>\n {formatEnumLabel(c)}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </div>\n </div>\n <label className=\"flex items-center gap-2 text-sm text-text-primary\">\n <input\n type=\"checkbox\"\n checked={editForm.isPriority}\n onChange={(e) => setEditForm({ ...editForm, isPriority: e.target.checked })}\n className=\"h-4 w-4 rounded border-border-seam accent-action-primary-bg\"\n />\n Mark as priority species\n </label>\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"editDescription\">Description</Label>\n <Textarea\n id=\"editDescription\"\n value={editForm.description}\n onChange={(e) => setEditForm({ ...editForm, description: e.target.value })}\n placeholder=\"Optional notes on habitat, range or identification.\"\n rows={2}\n />\n </div>\n </div>\n <ResponsiveDialogFooter>\n <Button variant=\"outline\" onClick={() => setEditOpen(false)}>\n Cancel\n </Button>\n <Button onClick={handleUpdate} disabled={updateSpecies.isPending}>\n {updateSpecies.isPending ? 'Saving…' : 'Save changes'}\n </Button>\n </ResponsiveDialogFooter>\n </ResponsiveDialogContent>\n </ResponsiveDialog>\n </PageLayout>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"SpeciesDetailPage.js","names":[],"sources":["../../../../src/ecoimpacthub/pages/species/SpeciesDetailPage.tsx"],"sourcesContent":["/**\n * Species detail — overview, field observations & species actions.\n * @module ecoimpacthub/pages/species\n *\n * Reads `ecoSpecies` (+ nested observations); writes `ecoRecordObservation`,\n * `ecoUpdateSpecies`, `ecoArchiveSpecies`.\n */\n\nimport { useState } from 'react';\nimport { useParams } from 'react-router';\nimport { ArrowLeft, Binoculars, Pencil, Archive, Star } from 'lucide-react';\nimport {\n Button,\n Input,\n Label,\n Textarea,\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 { DataTable, type Column } from '../../components/DataTable';\nimport { LoadingView, ErrorView, Section } from '../../components/StateViews';\nimport { EmptyState } from '../../components/EmptyState';\nimport { EvidenceUpload } from '../../components/EvidenceUpload';\nimport { EcoTags } from '../../components/EcoTags';\nimport { ConservationBadge } from '../_shared/ConservationBadge';\nimport { TaxonChip } from '../_shared/TaxonChip';\nimport { MethodBadge } from '../_shared/MethodBadge';\nimport { EvidenceThumb } from '../_shared/EvidenceThumb';\nimport { useEcoImpactHub } from '../../context';\nimport {\n useEcoSpeciesDetail,\n useEcoSites,\n useEcoRecordObservation,\n useEcoUpdateSpecies,\n useEcoArchiveSpecies,\n type UploadedEcoFile,\n} from '../../hooks';\nimport { formatDateTime, formatEnumLabel, formatNumber } from '../../utils/formatters';\nimport type {\n EcoObservation,\n EcoObservationMethod,\n EcoTaxonGroup,\n EcoConservationStatus,\n} from '../../types';\n\nconst OBSERVATION_METHODS: EcoObservationMethod[] = [\n 'DIRECT_SIGHTING',\n 'CAMERA_TRAP',\n 'ACOUSTIC',\n 'TRACKS_SIGNS',\n 'EDNA',\n 'TELEMETRY',\n 'COMMUNITY_REPORT',\n];\n\nconst TAXON_GROUPS: EcoTaxonGroup[] = [\n 'MAMMAL',\n 'BIRD',\n 'REPTILE',\n 'AMPHIBIAN',\n 'FISH',\n 'INSECT',\n 'PLANT',\n 'FUNGI',\n 'OTHER',\n];\n\nconst CONSERVATION_STATUSES: EcoConservationStatus[] = [\n 'LEAST_CONCERN',\n 'NEAR_THREATENED',\n 'VULNERABLE',\n 'ENDANGERED',\n 'CRITICALLY_ENDANGERED',\n 'EXTINCT_IN_WILD',\n 'DATA_DEFICIENT',\n 'NOT_EVALUATED',\n];\n\n/** Format a Date into the `YYYY-MM-DDTHH:mm` value a datetime-local input expects. */\nconst toLocalInputValue = (d: Date): string => {\n const pad = (n: number) => String(n).padStart(2, '0');\n return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}T${pad(\n d.getHours()\n )}:${pad(d.getMinutes())}`;\n};\n\ninterface EditFormState {\n commonName: string;\n scientificName: string;\n taxonGroup: EcoTaxonGroup;\n conservationStatus: EcoConservationStatus;\n isPriority: boolean;\n description: string;\n}\n\nexport function SpeciesDetailPage() {\n const { id } = useParams<{ id: string }>();\n const { navigateTo } = useEcoImpactHub();\n const { data: species, isLoading, isError, refetch } = useEcoSpeciesDetail(id);\n const sites = useEcoSites({ pagination: { limit: 100, offset: 0 } });\n\n const recordObservation = useEcoRecordObservation();\n const updateSpecies = useEcoUpdateSpecies();\n const archiveSpecies = useEcoArchiveSpecies();\n\n const [observeOpen, setObserveOpen] = useState(false);\n const [observeForm, setObserveForm] = useState<{\n method: EcoObservationMethod;\n count: string;\n observedAt: string;\n siteId: string;\n notes: string;\n evidence: UploadedEcoFile | null;\n }>({\n method: 'DIRECT_SIGHTING',\n count: '1',\n observedAt: toLocalInputValue(new Date()),\n siteId: '',\n notes: '',\n evidence: null,\n });\n const [editOpen, setEditOpen] = useState(false);\n const [editForm, setEditForm] = useState<EditFormState>({\n commonName: '',\n scientificName: '',\n taxonGroup: 'MAMMAL',\n conservationStatus: 'NOT_EVALUATED',\n isPriority: false,\n description: '',\n });\n const [error, setError] = useState<string | null>(null);\n\n if (isLoading) {\n return (\n <PageLayout title=\"Species\">\n <LoadingView />\n </PageLayout>\n );\n }\n if (isError || !species) {\n return (\n <PageLayout title=\"Species\">\n <ErrorView message=\"This species could not be loaded.\" onRetry={() => refetch()} />\n </PageLayout>\n );\n }\n\n const observations = species.observations ?? [];\n const siteRows = sites.data?.items ?? [];\n const siteNameById = new Map<string, string>();\n for (const s of siteRows) siteNameById.set(s.id, s.name);\n\n const openObserve = () => {\n setObserveForm({\n method: 'DIRECT_SIGHTING',\n count: '1',\n observedAt: toLocalInputValue(new Date()),\n siteId: '',\n notes: '',\n evidence: null,\n });\n setError(null);\n setObserveOpen(true);\n };\n\n const openEdit = () => {\n setEditForm({\n commonName: species.commonName,\n scientificName: species.scientificName ?? '',\n taxonGroup: species.taxonGroup,\n conservationStatus: species.conservationStatus,\n isPriority: species.isPriority,\n description: species.description ?? '',\n });\n setError(null);\n setEditOpen(true);\n };\n\n const handleRecordObservation = async () => {\n if (!id) return;\n setError(null);\n try {\n await recordObservation.mutateAsync({\n speciesId: id,\n method: observeForm.method,\n count: observeForm.count ? Number(observeForm.count) : 1,\n observedAt: new Date(observeForm.observedAt).toISOString(),\n siteId: observeForm.siteId || null,\n notes: observeForm.notes.trim() || null,\n evidenceFileId: observeForm.evidence?.fileId ?? null,\n });\n setObserveOpen(false);\n } catch (err) {\n setError(err instanceof Error ? err.message : 'Failed to record observation.');\n }\n };\n\n const handleUpdate = async () => {\n if (!id || !editForm.commonName.trim()) {\n setError('Common name is required.');\n return;\n }\n setError(null);\n try {\n await updateSpecies.mutateAsync({\n id,\n input: {\n commonName: editForm.commonName.trim(),\n scientificName: editForm.scientificName.trim() || null,\n taxonGroup: editForm.taxonGroup,\n conservationStatus: editForm.conservationStatus,\n isPriority: editForm.isPriority,\n description: editForm.description.trim() || null,\n },\n });\n setEditOpen(false);\n } catch (err) {\n setError(err instanceof Error ? err.message : 'Failed to update species.');\n }\n };\n\n const handleArchive = async () => {\n if (!id) return;\n setError(null);\n try {\n await archiveSpecies.mutateAsync(id);\n navigateTo('species');\n } catch (err) {\n setError(err instanceof Error ? err.message : 'Failed to archive species.');\n }\n };\n\n const observationColumns: Column<EcoObservation>[] = [\n {\n key: 'evidence',\n header: 'Evidence',\n render: (o) => <EvidenceThumb fileId={o.evidence?.id} alt={species.commonName} />,\n },\n {\n key: 'method',\n header: 'Method',\n render: (o) => <MethodBadge method={o.method} />,\n },\n {\n key: 'count',\n header: 'Count',\n align: 'right',\n sortValue: (o) => o.count,\n render: (o) => (\n <span className=\"tabular-nums font-medium text-text-primary\">×{formatNumber(o.count)}</span>\n ),\n },\n {\n key: 'observedAt',\n header: 'Observed',\n hideOnMobile: true,\n render: (o) => (\n <span className=\"whitespace-nowrap text-text-secondary\">\n {formatDateTime(o.observedAt)}\n </span>\n ),\n },\n {\n key: 'site',\n header: 'Site',\n hideOnMobile: true,\n render: (o) => {\n const name = o.siteId ? siteNameById.get(o.siteId) : undefined;\n return name ? (\n <span className=\"text-text-secondary\">{name}</span>\n ) : (\n <span className=\"text-text-tertiary\">—</span>\n );\n },\n },\n {\n key: 'notes',\n header: 'Notes',\n hideOnMobile: true,\n render: (o) =>\n o.notes ? (\n <span className=\"block max-w-xs truncate text-text-secondary\" title={o.notes}>\n {o.notes}\n </span>\n ) : (\n <span className=\"text-text-tertiary\">—</span>\n ),\n },\n ];\n\n return (\n <PageLayout\n title={species.commonName}\n description={[species.scientificName, formatEnumLabel(species.taxonGroup)]\n .filter(Boolean)\n .join(' · ')}\n actions={\n <>\n <ConservationBadge status={species.conservationStatus} />\n {species.isPriority && (\n <span\n className=\"inline-flex items-center gap-1 rounded-full bg-status-warning-bg-subtle px-2.5 py-0.5 text-xs font-semibold text-status-warning-text\"\n title=\"Priority species\"\n >\n <Star className=\"h-3.5 w-3.5 fill-current\" /> Priority\n </span>\n )}\n <Button variant=\"ghost\" onClick={() => navigateTo('species')}>\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\">Scientific name</dt>\n <dd className=\"mt-0.5 text-sm italic text-text-primary\">\n {species.scientificName ?? '—'}\n </dd>\n </div>\n <div>\n <dt className=\"text-xs text-text-tertiary\">Taxon group</dt>\n <dd className=\"mt-1\">\n <TaxonChip group={species.taxonGroup} />\n </dd>\n </div>\n <div>\n <dt className=\"text-xs text-text-tertiary\">Conservation</dt>\n <dd className=\"mt-1\">\n <ConservationBadge status={species.conservationStatus} />\n </dd>\n </div>\n <div>\n <dt className=\"text-xs text-text-tertiary\">Priority</dt>\n <dd className=\"mt-0.5 text-sm text-text-primary\">\n {species.isPriority ? (\n <span className=\"inline-flex items-center gap-1 text-status-warning-text\">\n <Star className=\"h-4 w-4 fill-current\" /> Priority\n </span>\n ) : (\n '—'\n )}\n </dd>\n </div>\n <div>\n <dt className=\"text-xs text-text-tertiary\">Observations</dt>\n <dd className=\"mt-0.5 text-sm text-text-primary tabular-nums\">\n {observations.length}\n </dd>\n </div>\n </dl>\n {species.description && (\n <p className=\"mt-5 rounded-lg bg-bg-sunken/60 p-3 text-sm text-text-secondary\">\n {species.description}\n </p>\n )}\n <div className=\"mt-5 border-t border-border-seam pt-4\">\n <EcoTags entityType=\"ecoimpacthub-species\" entityId={species.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={openObserve}>\n <Binoculars className=\"mr-1.5 h-4 w-4\" /> Record observation\n </Button>\n <Button variant=\"outline\" onClick={openEdit}>\n <Pencil className=\"mr-1.5 h-4 w-4\" /> Edit species\n </Button>\n <Button\n variant=\"ghost\"\n onClick={handleArchive}\n disabled={archiveSpecies.isPending}\n className=\"text-status-error-text hover:text-status-error-text\"\n >\n <Archive className=\"mr-1.5 h-4 w-4\" /> Archive\n </Button>\n </GlassCard>\n </div>\n\n {error && <p className=\"mt-4 text-sm text-status-error-text\">{error}</p>}\n\n {/* Observations */}\n <Section\n title=\"Observations\"\n description=\"Field records logged against this species.\"\n action={\n <Button size=\"sm\" onClick={openObserve}>\n <Binoculars className=\"mr-1.5 h-4 w-4\" /> Record observation\n </Button>\n }\n >\n {observations.length === 0 ? (\n <EmptyState\n illustration=\"empty-data\"\n title=\"No observations yet\"\n description=\"Record a field observation to see it here.\"\n />\n ) : (\n <DataTable columns={observationColumns} rows={observations} rowKey={(o) => o.id} />\n )}\n </Section>\n\n {/* Record observation dialog */}\n <ResponsiveDialog open={observeOpen} onOpenChange={setObserveOpen}>\n <ResponsiveDialogContent>\n <ResponsiveDialogHeader>\n <ResponsiveDialogTitle>Record observation</ResponsiveDialogTitle>\n <ResponsiveDialogDescription>\n Log a field sighting or detection of {species.commonName}.\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=\"obsMethod\">Method</Label>\n <Select\n value={observeForm.method}\n onValueChange={(v) =>\n setObserveForm({ ...observeForm, method: v as EcoObservationMethod })\n }\n >\n <SelectTrigger id=\"obsMethod\">\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n {OBSERVATION_METHODS.map((m) => (\n <SelectItem key={m} value={m}>\n {formatEnumLabel(m)}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </div>\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"obsCount\">Count</Label>\n <Input\n id=\"obsCount\"\n type=\"number\"\n min=\"1\"\n step=\"1\"\n value={observeForm.count}\n onChange={(e) => setObserveForm({ ...observeForm, count: e.target.value })}\n placeholder=\"1\"\n />\n </div>\n </div>\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"obsAt\">Observed at</Label>\n <Input\n id=\"obsAt\"\n type=\"datetime-local\"\n value={observeForm.observedAt}\n onChange={(e) => setObserveForm({ ...observeForm, observedAt: e.target.value })}\n />\n </div>\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"obsSite\">Site (optional)</Label>\n <Select\n value={observeForm.siteId}\n onValueChange={(v) => setObserveForm({ ...observeForm, siteId: v })}\n >\n <SelectTrigger id=\"obsSite\">\n <SelectValue placeholder=\"No site\" />\n </SelectTrigger>\n <SelectContent>\n {siteRows.map((s) => (\n <SelectItem key={s.id} value={s.id}>\n {s.name}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </div>\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"obsNotes\">Notes</Label>\n <Textarea\n id=\"obsNotes\"\n value={observeForm.notes}\n onChange={(e) => setObserveForm({ ...observeForm, notes: e.target.value })}\n placeholder=\"Optional context — behaviour, conditions, evidence.\"\n rows={2}\n />\n </div>\n <EvidenceUpload\n id=\"speciesObsEvidence\"\n label=\"Evidence photo (optional)\"\n value={observeForm.evidence}\n onChange={(evidence) => setObserveForm({ ...observeForm, evidence })}\n hint=\"Attach a photo, audio clip or PDF as supporting evidence.\"\n />\n </div>\n <ResponsiveDialogFooter>\n <Button variant=\"outline\" onClick={() => setObserveOpen(false)}>\n Cancel\n </Button>\n <Button onClick={handleRecordObservation} disabled={recordObservation.isPending}>\n {recordObservation.isPending ? 'Recording…' : 'Record'}\n </Button>\n </ResponsiveDialogFooter>\n </ResponsiveDialogContent>\n </ResponsiveDialog>\n\n {/* Edit species dialog */}\n <ResponsiveDialog open={editOpen} onOpenChange={setEditOpen}>\n <ResponsiveDialogContent>\n <ResponsiveDialogHeader>\n <ResponsiveDialogTitle>Edit species</ResponsiveDialogTitle>\n <ResponsiveDialogDescription>\n Update naming, classification and priority for this species.\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=\"editCommon\">Common name *</Label>\n <Input\n id=\"editCommon\"\n value={editForm.commonName}\n onChange={(e) => setEditForm({ ...editForm, commonName: e.target.value })}\n placeholder=\"Indian Pangolin\"\n />\n </div>\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"editScientific\">Scientific name</Label>\n <Input\n id=\"editScientific\"\n value={editForm.scientificName}\n onChange={(e) => setEditForm({ ...editForm, scientificName: e.target.value })}\n placeholder=\"Manis crassicaudata\"\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=\"editTaxon\">Taxon group</Label>\n <Select\n value={editForm.taxonGroup}\n onValueChange={(v) =>\n setEditForm({ ...editForm, taxonGroup: v as EcoTaxonGroup })\n }\n >\n <SelectTrigger id=\"editTaxon\">\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n {TAXON_GROUPS.map((t) => (\n <SelectItem key={t} value={t}>\n {formatEnumLabel(t)}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </div>\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"editConservation\">Conservation status</Label>\n <Select\n value={editForm.conservationStatus}\n onValueChange={(v) =>\n setEditForm({ ...editForm, conservationStatus: v as EcoConservationStatus })\n }\n >\n <SelectTrigger id=\"editConservation\">\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n {CONSERVATION_STATUSES.map((c) => (\n <SelectItem key={c} value={c}>\n {formatEnumLabel(c)}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </div>\n </div>\n <label className=\"flex items-center gap-2 text-sm text-text-primary\">\n <input\n type=\"checkbox\"\n checked={editForm.isPriority}\n onChange={(e) => setEditForm({ ...editForm, isPriority: e.target.checked })}\n className=\"h-4 w-4 rounded border-border-seam accent-action-primary-bg\"\n />\n Mark as priority species\n </label>\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"editDescription\">Description</Label>\n <Textarea\n id=\"editDescription\"\n value={editForm.description}\n onChange={(e) => setEditForm({ ...editForm, description: e.target.value })}\n placeholder=\"Optional notes on habitat, range or identification.\"\n rows={2}\n />\n </div>\n </div>\n <ResponsiveDialogFooter>\n <Button variant=\"outline\" onClick={() => setEditOpen(false)}>\n Cancel\n </Button>\n <Button onClick={handleUpdate} disabled={updateSpecies.isPending}>\n {updateSpecies.isPending ? 'Saving…' : 'Save changes'}\n </Button>\n </ResponsiveDialogFooter>\n </ResponsiveDialogContent>\n </ResponsiveDialog>\n </PageLayout>\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;AAwDA,IAAM,IAA8C;CAClD;CACA;CACA;CACA;CACA;CACA;CACA;AACF,GAEM,IAAgC;CACpC;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;AACF,GAEM,IAAiD;CACrD;CACA;CACA;CACA;CACA;CACA;CACA;CACA;AACF,GAGM,KAAqB,MAAoB;CAC7C,IAAM,KAAO,MAAc,OAAO,CAAC,EAAE,SAAS,GAAG,GAAG;CACpD,OAAO,GAAG,EAAE,YAAY,EAAE,GAAG,EAAI,EAAE,SAAS,IAAI,CAAC,EAAE,GAAG,EAAI,EAAE,QAAQ,CAAC,EAAE,GAAG,EACxE,EAAE,SAAS,CACb,EAAE,GAAG,EAAI,EAAE,WAAW,CAAC;AACzB;AAWA,SAAgB,IAAoB;CAClC,IAAM,EAAE,UAAO,EAA0B,GACnC,EAAE,kBAAe,EAAgB,GACjC,EAAE,MAAM,GAAS,eAAW,aAAS,gBAAY,GAAoB,CAAE,GACvE,KAAQ,GAAY,EAAE,YAAY;EAAE,OAAO;EAAK,QAAQ;CAAE,EAAE,CAAC,GAE7D,IAAoB,GAAwB,GAC5C,IAAgB,GAAoB,GACpC,IAAiB,GAAqB,GAEtC,CAAC,IAAa,KAAkB,EAAS,EAAK,GAC9C,CAAC,GAAa,KAAkB,EAOnC;EACD,QAAQ;EACR,OAAO;EACP,YAAY,kBAAkB,IAAI,KAAK,CAAC;EACxC,QAAQ;EACR,OAAO;EACP,UAAU;CACZ,CAAC,GACK,CAAC,IAAU,KAAe,EAAS,EAAK,GACxC,CAAC,GAAU,KAAe,EAAwB;EACtD,YAAY;EACZ,gBAAgB;EAChB,YAAY;EACZ,oBAAoB;EACpB,YAAY;EACZ,aAAa;CACf,CAAC,GACK,CAAC,GAAO,KAAY,EAAwB,IAAI;CAEtD,IAAI,IACF,OACE,kBAAC,GAAD;EAAY,OAAM;YAChB,kBAAC,GAAD,CAAc,CAAA;CACJ,CAAA;CAGhB,IAAI,MAAW,CAAC,GACd,OACE,kBAAC,GAAD;EAAY,OAAM;YAChB,kBAAC,GAAD;GAAW,SAAQ;GAAoC,eAAe,GAAQ;EAAI,CAAA;CACxE,CAAA;CAIhB,IAAM,IAAe,EAAQ,gBAAgB,CAAC,GACxC,IAAW,GAAM,MAAM,SAAS,CAAC,GACjC,oBAAe,IAAI,IAAoB;CAC7C,KAAK,IAAM,KAAK,GAAU,EAAa,IAAI,EAAE,IAAI,EAAE,IAAI;CAEvD,IAAM,UAAoB;EAUxB,AATA,EAAe;GACb,QAAQ;GACR,OAAO;GACP,YAAY,kBAAkB,IAAI,KAAK,CAAC;GACxC,QAAQ;GACR,OAAO;GACP,UAAU;EACZ,CAAC,GACD,EAAS,IAAI,GACb,EAAe,EAAI;CACrB;CA+HA,OACE,kBAAC,GAAD;EACE,OAAO,EAAQ;EACf,aAAa,CAAC,EAAQ,gBAAgB,EAAgB,EAAQ,UAAU,CAAC,EACtE,OAAO,OAAO,EACd,KAAK,KAAK;EACb,SACE,kBAAA,GAAA,EAAA,UAAA;GACE,kBAAC,GAAD,EAAmB,QAAQ,EAAQ,mBAAqB,CAAA;GACvD,EAAQ,cACP,kBAAC,QAAD;IACE,WAAU;IACV,OAAM;cAFR,CAIE,kBAAC,GAAD,EAAM,WAAU,2BAA4B,CAAA,GAAC,WACzC;;GAER,kBAAC,GAAD;IAAQ,SAAQ;IAAQ,eAAe,EAAW,SAAS;cAA3D,CACE,kBAAC,IAAD,EAAW,WAAU,iBAAkB,CAAA,GAAC,OAClC;;EACR,EAAA,CAAA;YAnBN;GAuBE,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,GAAD;KAAW,WAAU;KAAQ,WAAU;eAAvC;MACE,kBAAC,MAAD;OAAI,WAAU;iBAAd;QACE,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,MAAD;SAAI,WAAU;mBAA6B;QAAmB,CAAA,GAC9D,kBAAC,MAAD;SAAI,WAAU;mBACX,EAAQ,kBAAkB;QACzB,CAAA,CACD,EAAA,CAAA;QACL,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,MAAD;SAAI,WAAU;mBAA6B;QAAe,CAAA,GAC1D,kBAAC,MAAD;SAAI,WAAU;mBACZ,kBAAC,IAAD,EAAW,OAAO,EAAQ,WAAa,CAAA;QACrC,CAAA,CACD,EAAA,CAAA;QACL,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,MAAD;SAAI,WAAU;mBAA6B;QAAgB,CAAA,GAC3D,kBAAC,MAAD;SAAI,WAAU;mBACZ,kBAAC,GAAD,EAAmB,QAAQ,EAAQ,mBAAqB,CAAA;QACtD,CAAA,CACD,EAAA,CAAA;QACL,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,MAAD;SAAI,WAAU;mBAA6B;QAAY,CAAA,GACvD,kBAAC,MAAD;SAAI,WAAU;mBACX,EAAQ,aACP,kBAAC,QAAD;UAAM,WAAU;oBAAhB,CACE,kBAAC,GAAD,EAAM,WAAU,uBAAwB,CAAA,GAAC,WACrC;cAEN;QAEA,CAAA,CACD,EAAA,CAAA;QACL,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,MAAD;SAAI,WAAU;mBAA6B;QAAgB,CAAA,GAC3D,kBAAC,MAAD;SAAI,WAAU;mBACX,EAAa;QACZ,CAAA,CACD,EAAA,CAAA;OACH;;MACH,EAAQ,eACP,kBAAC,KAAD;OAAG,WAAU;iBACV,EAAQ;MACR,CAAA;MAEL,kBAAC,OAAD;OAAK,WAAU;iBACb,kBAAC,GAAD;QAAS,YAAW;QAAuB,UAAU,EAAQ;OAAK,CAAA;MAC/D,CAAA;KACI;QAEX,kBAAC,GAAD;KAAW,WAAU;KAAQ,WAAU;eAAvC;MACE,kBAAC,KAAD;OAAG,WAAU;iBAA6B;MAAgB,CAAA;MAC1D,kBAAC,GAAD;OAAQ,SAAS;iBAAjB,CACE,kBAAC,GAAD,EAAY,WAAU,iBAAkB,CAAA,GAAC,qBACnC;;MACR,kBAAC,GAAD;OAAQ,SAAQ;OAAU,eA5MX;QAUrB,AATA,EAAY;SACV,YAAY,EAAQ;SACpB,gBAAgB,EAAQ,kBAAkB;SAC1C,YAAY,EAAQ;SACpB,oBAAoB,EAAQ;SAC5B,YAAY,EAAQ;SACpB,aAAa,EAAQ,eAAe;QACtC,CAAC,GACD,EAAS,IAAI,GACb,EAAY,EAAI;OAClB;iBAiMQ,CACE,kBAAC,IAAD,EAAQ,WAAU,iBAAkB,CAAA,GAAC,eAC/B;;MACR,kBAAC,GAAD;OACE,SAAQ;OACR,SAAS,YAzJe;QAC3B,OACL;WAAS,IAAI;SACb,IAAI;UAEF,AADA,MAAM,EAAe,YAAY,CAAE,GACnC,EAAW,SAAS;SACtB,SAAS,GAAK;UACZ,EAAS,aAAe,QAAQ,EAAI,UAAU,4BAA4B;SAC5E;QANa;OAOf;OAiJU,UAAU,EAAe;OACzB,WAAU;iBAJZ,CAME,kBAAC,IAAD,EAAS,WAAU,iBAAkB,CAAA,GAAC,UAChC;;KACC;MACR;;GAEJ,KAAS,kBAAC,KAAD;IAAG,WAAU;cAAuC;GAAS,CAAA;GAGvE,kBAAC,IAAD;IACE,OAAM;IACN,aAAY;IACZ,QACE,kBAAC,GAAD;KAAQ,MAAK;KAAK,SAAS;eAA3B,CACE,kBAAC,GAAD,EAAY,WAAU,iBAAkB,CAAA,GAAC,qBACnC;;cAGT,EAAa,WAAW,IACvB,kBAAC,IAAD;KACE,cAAa;KACb,OAAM;KACN,aAAY;IACb,CAAA,IAED,kBAAC,IAAD;KAAW,SAAS;MAzK1B;OACE,KAAK;OACL,QAAQ;OACR,SAAS,MAAM,kBAAC,IAAD;QAAe,QAAQ,EAAE,UAAU;QAAI,KAAK,EAAQ;OAAa,CAAA;MAClF;MACA;OACE,KAAK;OACL,QAAQ;OACR,SAAS,MAAM,kBAAC,IAAD,EAAa,QAAQ,EAAE,OAAS,CAAA;MACjD;MACA;OACE,KAAK;OACL,QAAQ;OACR,OAAO;OACP,YAAY,MAAM,EAAE;OACpB,SAAS,MACP,kBAAC,QAAD;QAAM,WAAU;kBAAhB,CAA6D,KAAE,EAAa,EAAE,KAAK,CAAQ;;MAE/F;MACA;OACE,KAAK;OACL,QAAQ;OACR,cAAc;OACd,SAAS,MACP,kBAAC,QAAD;QAAM,WAAU;kBACb,GAAe,EAAE,UAAU;OACxB,CAAA;MAEV;MACA;OACE,KAAK;OACL,QAAQ;OACR,cAAc;OACd,SAAS,MAAM;QACb,IAAM,IAAO,EAAE,SAAS,EAAa,IAAI,EAAE,MAAM,IAAI,KAAA;QACrD,OAAO,IACL,kBAAC,QAAD;SAAM,WAAU;mBAAuB;QAAW,CAAA,IAElD,kBAAC,QAAD;SAAM,WAAU;mBAAqB;QAAO,CAAA;OAEhD;MACF;MACA;OACE,KAAK;OACL,QAAQ;OACR,cAAc;OACd,SAAS,MACP,EAAE,QACA,kBAAC,QAAD;QAAM,WAAU;QAA8C,OAAO,EAAE;kBACpE,EAAE;OACC,CAAA,IAEN,kBAAC,QAAD;QAAM,WAAU;kBAAqB;OAAO,CAAA;MAElD;KAmH0B;KAAoB,MAAM;KAAc,SAAS,MAAM,EAAE;IAAK,CAAA;GAE7E,CAAA;GAGT,kBAAC,GAAD;IAAkB,MAAM;IAAa,cAAc;cACjD,kBAAC,GAAD,EAAA,UAAA;KACE,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,GAAD,EAAA,UAAuB,qBAAyC,CAAA,GAChE,kBAAC,GAAD,EAAA,UAAA;MAA6B;MACW,EAAQ;MAAW;KAC9B,EAAA,CAAA,CACP,EAAA,CAAA;KACxB,kBAAC,OAAD;MAAK,WAAU;gBAAf;OACE,kBAAC,OAAD;QAAK,WAAU;kBAAf,CACE,kBAAC,OAAD;SAAK,WAAU;mBAAf,CACE,kBAAC,GAAD;UAAO,SAAQ;oBAAY;SAAa,CAAA,GACxC,kBAAC,GAAD;UACE,OAAO,EAAY;UACnB,gBAAgB,MACd,EAAe;WAAE,GAAG;WAAa,QAAQ;UAA0B,CAAC;oBAHxE,CAME,kBAAC,GAAD;WAAe,IAAG;qBAChB,kBAAC,GAAD,CAAc,CAAA;UACD,CAAA,GACf,kBAAC,GAAD,EAAA,UACG,EAAoB,KAAK,MACxB,kBAAC,GAAD;WAAoB,OAAO;qBACxB,EAAgB,CAAC;UACR,GAFK,CAEL,CACb,EACY,CAAA,CACT;WACL;YACL,kBAAC,OAAD;SAAK,WAAU;mBAAf,CACE,kBAAC,GAAD;UAAO,SAAQ;oBAAW;SAAY,CAAA,GACtC,kBAAC,GAAD;UACE,IAAG;UACH,MAAK;UACL,KAAI;UACJ,MAAK;UACL,OAAO,EAAY;UACnB,WAAW,MAAM,EAAe;WAAE,GAAG;WAAa,OAAO,EAAE,OAAO;UAAM,CAAC;UACzE,aAAY;SACb,CAAA,CACE;UACF;;OACL,kBAAC,OAAD;QAAK,WAAU;kBAAf,CACE,kBAAC,GAAD;SAAO,SAAQ;mBAAQ;QAAkB,CAAA,GACzC,kBAAC,GAAD;SACE,IAAG;SACH,MAAK;SACL,OAAO,EAAY;SACnB,WAAW,MAAM,EAAe;UAAE,GAAG;UAAa,YAAY,EAAE,OAAO;SAAM,CAAC;QAC/E,CAAA,CACE;;OACL,kBAAC,OAAD;QAAK,WAAU;kBAAf,CACE,kBAAC,GAAD;SAAO,SAAQ;mBAAU;QAAsB,CAAA,GAC/C,kBAAC,GAAD;SACE,OAAO,EAAY;SACnB,gBAAgB,MAAM,EAAe;UAAE,GAAG;UAAa,QAAQ;SAAE,CAAC;mBAFpE,CAIE,kBAAC,GAAD;UAAe,IAAG;oBAChB,kBAAC,GAAD,EAAa,aAAY,UAAW,CAAA;SACvB,CAAA,GACf,kBAAC,GAAD,EAAA,UACG,EAAS,KAAK,MACb,kBAAC,GAAD;UAAuB,OAAO,EAAE;oBAC7B,EAAE;SACO,GAFK,EAAE,EAEP,CACb,EACY,CAAA,CACT;UACL;;OACL,kBAAC,OAAD;QAAK,WAAU;kBAAf,CACE,kBAAC,GAAD;SAAO,SAAQ;mBAAW;QAAY,CAAA,GACtC,kBAAC,GAAD;SACE,IAAG;SACH,OAAO,EAAY;SACnB,WAAW,MAAM,EAAe;UAAE,GAAG;UAAa,OAAO,EAAE,OAAO;SAAM,CAAC;SACzE,aAAY;SACZ,MAAM;QACP,CAAA,CACE;;OACL,kBAAC,GAAD;QACE,IAAG;QACH,OAAM;QACN,OAAO,EAAY;QACnB,WAAW,MAAa,EAAe;SAAE,GAAG;SAAa;QAAS,CAAC;QACnE,MAAK;OACN,CAAA;MACE;;KACL,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,GAAD;MAAQ,SAAQ;MAAU,eAAe,EAAe,EAAK;gBAAG;KAExD,CAAA,GACR,kBAAC,GAAD;MAAQ,SAAS,YAjUiB;OACrC,OACL;UAAS,IAAI;QACb,IAAI;SAUF,AATA,MAAM,EAAkB,YAAY;UAClC,WAAW;UACX,QAAQ,EAAY;UACpB,OAAO,EAAY,QAAQ,OAAO,EAAY,KAAK,IAAI;UACvD,YAAY,IAAI,KAAK,EAAY,UAAU,EAAE,YAAY;UACzD,QAAQ,EAAY,UAAU;UAC9B,OAAO,EAAY,MAAM,KAAK,KAAK;UACnC,gBAAgB,EAAY,UAAU,UAAU;SAClD,CAAC,GACD,EAAe,EAAK;QACtB,SAAS,GAAK;SACZ,EAAS,aAAe,QAAQ,EAAI,UAAU,+BAA+B;QAC/E;OAda;MAef;MAgToD,UAAU,EAAkB;gBACnE,EAAkB,YAAY,eAAe;KACxC,CAAA,CACc,EAAA,CAAA;IACD,EAAA,CAAA;GACT,CAAA;GAGlB,kBAAC,GAAD;IAAkB,MAAM;IAAU,cAAc;cAC9C,kBAAC,GAAD,EAAA,UAAA;KACE,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,GAAD,EAAA,UAAuB,eAAmC,CAAA,GAC1D,kBAAC,GAAD,EAAA,UAA6B,+DAEA,CAAA,CACP,EAAA,CAAA;KACxB,kBAAC,OAAD;MAAK,WAAU;gBAAf;OACE,kBAAC,OAAD;QAAK,WAAU;kBAAf,CACE,kBAAC,OAAD;SAAK,WAAU;mBAAf,CACE,kBAAC,GAAD;UAAO,SAAQ;oBAAa;SAAoB,CAAA,GAChD,kBAAC,GAAD;UACE,IAAG;UACH,OAAO,EAAS;UAChB,WAAW,MAAM,EAAY;WAAE,GAAG;WAAU,YAAY,EAAE,OAAO;UAAM,CAAC;UACxE,aAAY;SACb,CAAA,CACE;YACL,kBAAC,OAAD;SAAK,WAAU;mBAAf,CACE,kBAAC,GAAD;UAAO,SAAQ;oBAAiB;SAAsB,CAAA,GACtD,kBAAC,GAAD;UACE,IAAG;UACH,OAAO,EAAS;UAChB,WAAW,MAAM,EAAY;WAAE,GAAG;WAAU,gBAAgB,EAAE,OAAO;UAAM,CAAC;UAC5E,aAAY;SACb,CAAA,CACE;UACF;;OACL,kBAAC,OAAD;QAAK,WAAU;kBAAf,CACE,kBAAC,OAAD;SAAK,WAAU;mBAAf,CACE,kBAAC,GAAD;UAAO,SAAQ;oBAAY;SAAkB,CAAA,GAC7C,kBAAC,GAAD;UACE,OAAO,EAAS;UAChB,gBAAgB,MACd,EAAY;WAAE,GAAG;WAAU,YAAY;UAAmB,CAAC;oBAH/D,CAME,kBAAC,GAAD;WAAe,IAAG;qBAChB,kBAAC,GAAD,CAAc,CAAA;UACD,CAAA,GACf,kBAAC,GAAD,EAAA,UACG,EAAa,KAAK,MACjB,kBAAC,GAAD;WAAoB,OAAO;qBACxB,EAAgB,CAAC;UACR,GAFK,CAEL,CACb,EACY,CAAA,CACT;WACL;YACL,kBAAC,OAAD;SAAK,WAAU;mBAAf,CACE,kBAAC,GAAD;UAAO,SAAQ;oBAAmB;SAA0B,CAAA,GAC5D,kBAAC,GAAD;UACE,OAAO,EAAS;UAChB,gBAAgB,MACd,EAAY;WAAE,GAAG;WAAU,oBAAoB;UAA2B,CAAC;oBAH/E,CAME,kBAAC,GAAD;WAAe,IAAG;qBAChB,kBAAC,GAAD,CAAc,CAAA;UACD,CAAA,GACf,kBAAC,GAAD,EAAA,UACG,EAAsB,KAAK,MAC1B,kBAAC,GAAD;WAAoB,OAAO;qBACxB,EAAgB,CAAC;UACR,GAFK,CAEL,CACb,EACY,CAAA,CACT;WACL;UACF;;OACL,kBAAC,SAAD;QAAO,WAAU;kBAAjB,CACE,kBAAC,SAAD;SACE,MAAK;SACL,SAAS,EAAS;SAClB,WAAW,MAAM,EAAY;UAAE,GAAG;UAAU,YAAY,EAAE,OAAO;SAAQ,CAAC;SAC1E,WAAU;QACX,CAAA,GAAC,0BAEG;;OACP,kBAAC,OAAD;QAAK,WAAU;kBAAf,CACE,kBAAC,GAAD;SAAO,SAAQ;mBAAkB;QAAkB,CAAA,GACnD,kBAAC,GAAD;SACE,IAAG;SACH,OAAO,EAAS;SAChB,WAAW,MAAM,EAAY;UAAE,GAAG;UAAU,aAAa,EAAE,OAAO;SAAM,CAAC;SACzE,aAAY;SACZ,MAAM;QACP,CAAA,CACE;;MACF;;KACL,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,GAAD;MAAQ,SAAQ;MAAU,eAAe,EAAY,EAAK;gBAAG;KAErD,CAAA,GACR,kBAAC,GAAD;MAAQ,SAAS,YArZM;OAC/B,IAAI,CAAC,KAAM,CAAC,EAAS,WAAW,KAAK,GAAG;QACtC,EAAS,0BAA0B;QACnC;OACF;OACA,EAAS,IAAI;OACb,IAAI;QAYF,AAXA,MAAM,EAAc,YAAY;SAC9B;SACA,OAAO;UACL,YAAY,EAAS,WAAW,KAAK;UACrC,gBAAgB,EAAS,eAAe,KAAK,KAAK;UAClD,YAAY,EAAS;UACrB,oBAAoB,EAAS;UAC7B,YAAY,EAAS;UACrB,aAAa,EAAS,YAAY,KAAK,KAAK;SAC9C;QACF,CAAC,GACD,EAAY,EAAK;OACnB,SAAS,GAAK;QACZ,EAAS,aAAe,QAAQ,EAAI,UAAU,2BAA2B;OAC3E;MACF;MA+XyC,UAAU,EAAc;gBACpD,EAAc,YAAY,YAAY;KACjC,CAAA,CACc,EAAA,CAAA;IACD,EAAA,CAAA;GACT,CAAA;EACR;;AAEhB"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@burdenoff/microfe-ecoimpacthub",
|
|
3
|
-
"version": "2026.
|
|
3
|
+
"version": "2026.801.1",
|
|
4
4
|
"description": "Burdenoff EcoImpactHub Microfrontend",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"files": [
|
|
@@ -46,14 +46,14 @@
|
|
|
46
46
|
},
|
|
47
47
|
"dependencies": {
|
|
48
48
|
"@apollo/client": "^4.0.11",
|
|
49
|
-
"@burdenoff/fe-libs": "2026.
|
|
49
|
+
"@burdenoff/fe-libs": "2026.801.1",
|
|
50
50
|
"@tanstack/react-query": "^5.90.16",
|
|
51
51
|
"clsx": "^2.1.1",
|
|
52
52
|
"graphql": "^16.10.0",
|
|
53
53
|
"lucide-react": "^0.468.0",
|
|
54
54
|
"react": "^19.0.0",
|
|
55
55
|
"react-dom": "^19.0.0",
|
|
56
|
-
"react-router
|
|
56
|
+
"react-router": "^8.3.0",
|
|
57
57
|
"tailwind-merge": "^3.4.0",
|
|
58
58
|
"zustand": "^5.0.3",
|
|
59
59
|
"recharts": "^2.15.0",
|