@burdenoff/microfe-ecoimpacthub 2026.704.4 → 2026.704.5

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.
Files changed (47) hide show
  1. package/dist/ecoimpacthub/pages/DashboardPage.js +345 -189
  2. package/dist/ecoimpacthub/pages/DashboardPage.js.map +1 -1
  3. package/dist/ecoimpacthub/pages/_shared/ConservationBadge.js +58 -0
  4. package/dist/ecoimpacthub/pages/_shared/ConservationBadge.js.map +1 -0
  5. package/dist/ecoimpacthub/pages/_shared/EvidenceThumb.js +70 -0
  6. package/dist/ecoimpacthub/pages/_shared/EvidenceThumb.js.map +1 -0
  7. package/dist/ecoimpacthub/pages/_shared/MethodBadge.js +51 -0
  8. package/dist/ecoimpacthub/pages/_shared/MethodBadge.js.map +1 -0
  9. package/dist/ecoimpacthub/pages/_shared/TaxonChip.js +29 -0
  10. package/dist/ecoimpacthub/pages/_shared/TaxonChip.js.map +1 -0
  11. package/dist/ecoimpacthub/pages/badges.js +71 -0
  12. package/dist/ecoimpacthub/pages/badges.js.map +1 -0
  13. package/dist/ecoimpacthub/pages/carbon/CarbonPage.js +337 -300
  14. package/dist/ecoimpacthub/pages/carbon/CarbonPage.js.map +1 -1
  15. package/dist/ecoimpacthub/pages/carbonVisuals.js +89 -0
  16. package/dist/ecoimpacthub/pages/carbonVisuals.js.map +1 -0
  17. package/dist/ecoimpacthub/pages/habitat/HabitatProjectDetailPage.js +252 -218
  18. package/dist/ecoimpacthub/pages/habitat/HabitatProjectDetailPage.js.map +1 -1
  19. package/dist/ecoimpacthub/pages/habitat/HabitatProjectsPage.js +15 -12
  20. package/dist/ecoimpacthub/pages/habitat/HabitatProjectsPage.js.map +1 -1
  21. package/dist/ecoimpacthub/pages/habitat/LifecycleProgress.js +168 -0
  22. package/dist/ecoimpacthub/pages/habitat/LifecycleProgress.js.map +1 -0
  23. package/dist/ecoimpacthub/pages/monitoring/MonitoringPage.js +30 -17
  24. package/dist/ecoimpacthub/pages/monitoring/MonitoringPage.js.map +1 -1
  25. package/dist/ecoimpacthub/pages/observations/ObservationsPage.js +236 -219
  26. package/dist/ecoimpacthub/pages/observations/ObservationsPage.js.map +1 -1
  27. package/dist/ecoimpacthub/pages/reports/ReportDetailPage.js +248 -212
  28. package/dist/ecoimpacthub/pages/reports/ReportDetailPage.js.map +1 -1
  29. package/dist/ecoimpacthub/pages/reports/ReportsPage.js +119 -118
  30. package/dist/ecoimpacthub/pages/reports/ReportsPage.js.map +1 -1
  31. package/dist/ecoimpacthub/pages/sites/SiteDetailPage.js +498 -370
  32. package/dist/ecoimpacthub/pages/sites/SiteDetailPage.js.map +1 -1
  33. package/dist/ecoimpacthub/pages/sites/SitesPage.js +109 -99
  34. package/dist/ecoimpacthub/pages/sites/SitesPage.js.map +1 -1
  35. package/dist/ecoimpacthub/pages/sites/siteTypeVisuals.js +64 -0
  36. package/dist/ecoimpacthub/pages/sites/siteTypeVisuals.js.map +1 -0
  37. package/dist/ecoimpacthub/pages/species/SpeciesDetailPage.js +275 -259
  38. package/dist/ecoimpacthub/pages/species/SpeciesDetailPage.js.map +1 -1
  39. package/dist/ecoimpacthub/pages/species/SpeciesPage.js +235 -177
  40. package/dist/ecoimpacthub/pages/species/SpeciesPage.js.map +1 -1
  41. package/dist/node_modules/recharts/es6/cartesian/CartesianGrid.js +305 -0
  42. package/dist/node_modules/recharts/es6/cartesian/CartesianGrid.js.map +1 -0
  43. package/dist/node_modules/recharts/es6/context/chartLayoutContext.js +44 -33
  44. package/dist/node_modules/recharts/es6/context/chartLayoutContext.js.map +1 -1
  45. package/dist/node_modules/recharts/es6/util/ChartUtils.js +31 -23
  46. package/dist/node_modules/recharts/es6/util/ChartUtils.js.map +1 -1
  47. package/package.json +1 -1
@@ -1 +1 @@
1
- {"version":3,"file":"ObservationsPage.js","names":[],"sources":["../../../../src/ecoimpacthub/pages/observations/ObservationsPage.tsx"],"sourcesContent":["/**\n * Observations — a cross-species / cross-site field-observation feed.\n * @module ecoimpacthub/pages/observations\n *\n * Reads `ecoListObservations` (which selects the related species inline, so\n * `o.species?.commonName` is available per item); writes `ecoRecordObservation`.\n * Site & species name lookups are built from the `ecoListSites` /\n * `ecoListSpecies` connections. No mock data.\n */\n\nimport { useMemo, useState } from 'react';\nimport { Plus, Binoculars } from 'lucide-react';\nimport {\n Button,\n Input,\n Label,\n Textarea,\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n ResponsiveDialog,\n ResponsiveDialogContent,\n ResponsiveDialogHeader,\n ResponsiveDialogTitle,\n ResponsiveDialogDescription,\n ResponsiveDialogFooter,\n} from '@burdenoff/fe-libs/ui';\nimport { PageLayout } from '../../components/PageLayout';\nimport { DataTable, type Column } from '../../components/DataTable';\nimport { LoadingView, ErrorView } from '../../components/StateViews';\nimport { EmptyState } from '../../components/EmptyState';\nimport { Pagination } from '../../components/Pagination';\nimport { EvidenceUpload } from '../../components/EvidenceUpload';\nimport { FileLink } from '../../components/FileLink';\nimport {\n useEcoObservations,\n useEcoRecordObservation,\n useEcoSites,\n useEcoSpecies,\n useCursorPager,\n type UploadedEcoFile,\n} from '../../hooks';\nimport {\n formatCoords,\n formatDateTime,\n formatEnumLabel,\n formatNumber,\n} from '../../utils/formatters';\nimport { cn } from '../../utils/cn';\nimport type { EcoObservation, EcoObservationMethod } 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 PAGE_SIZE = 25;\nconst LOOKUP_PAGE = { limit: 100, offset: 0 };\n/** Sentinel for the \"no site\" option (Radix Select items can't use an empty value). */\nconst NO_SITE = 'NONE';\n\ninterface RecordFormState {\n speciesId: string;\n observedAt: string;\n count: string;\n method: EcoObservationMethod;\n siteId: string;\n latitude: string;\n longitude: string;\n notes: string;\n evidence: UploadedEcoFile | null;\n}\n\n/** A datetime-local value (`YYYY-MM-DDTHH:mm`) for \"now\" in the viewer's timezone. */\nfunction nowLocalInputValue(): string {\n const now = new Date();\n const local = new Date(now.getTime() - now.getTimezoneOffset() * 60000);\n return local.toISOString().slice(0, 16);\n}\n\nfunction makeEmptyForm(): RecordFormState {\n return {\n speciesId: '',\n observedAt: nowLocalInputValue(),\n count: '1',\n method: 'DIRECT_SIGHTING',\n siteId: NO_SITE,\n latitude: '',\n longitude: '',\n notes: '',\n evidence: null,\n };\n}\n\nexport function ObservationsPage() {\n const [speciesFilter, setSpeciesFilter] = useState<string>('ALL');\n const [siteFilter, setSiteFilter] = useState<string>('ALL');\n const pager = useCursorPager();\n const [dialogOpen, setDialogOpen] = useState(false);\n const [form, setForm] = useState<RecordFormState>(makeEmptyForm);\n const [formError, setFormError] = useState<string | null>(null);\n\n const sites = useEcoSites({ pagination: LOOKUP_PAGE });\n const species = useEcoSpecies({ pagination: LOOKUP_PAGE });\n\n const { data, isLoading, isError, refetch } = useEcoObservations({\n speciesId: speciesFilter === 'ALL' ? undefined : speciesFilter,\n siteId: siteFilter === 'ALL' ? undefined : siteFilter,\n pagination: { limit: PAGE_SIZE, cursor: pager.cursor },\n });\n const recordObservation = useEcoRecordObservation();\n\n const siteRows = useMemo(() => sites.data?.items ?? [], [sites.data]);\n const speciesRows = useMemo(() => species.data?.items ?? [], [species.data]);\n const rows = useMemo(() => data?.items ?? [], [data]);\n\n const siteNameById = useMemo(() => {\n const map = new Map<string, string>();\n for (const s of siteRows) map.set(s.id, s.name);\n return map;\n }, [siteRows]);\n\n const speciesNameById = useMemo(() => {\n const map = new Map<string, string>();\n for (const s of speciesRows) map.set(s.id, s.commonName);\n return map;\n }, [speciesRows]);\n\n const columns: Column<EcoObservation>[] = [\n {\n key: 'species',\n header: 'Species',\n render: (o) => (\n <div className=\"min-w-0\">\n <p className=\"font-semibold text-text-primary\">\n {o.species?.commonName ?? speciesNameById.get(o.speciesId) ?? 'Unknown species'}\n </p>\n {o.species?.scientificName && (\n <p className=\"truncate text-xs italic text-text-tertiary\">{o.species.scientificName}</p>\n )}\n </div>\n ),\n },\n {\n key: 'method',\n header: 'Method',\n render: (o) => (\n <span className=\"inline-flex rounded-full bg-bg-sunken px-2 py-0.5 text-xs text-text-secondary\">\n {formatEnumLabel(o.method)}\n </span>\n ),\n },\n {\n key: 'count',\n header: 'Count',\n align: 'right',\n sortValue: (o) => o.count,\n render: (o) => (\n <span className=\"tabular-nums text-text-secondary\">×{formatNumber(o.count)}</span>\n ),\n },\n {\n key: 'observedAt',\n header: 'Observed',\n align: 'right',\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: 'coords',\n header: 'Coordinates',\n hideOnMobile: true,\n render: (o) => {\n const coords = formatCoords(o.latitude, o.longitude);\n return (\n <span\n className={cn(\n 'tabular-nums',\n coords === '—' ? 'text-text-tertiary' : 'text-text-secondary'\n )}\n >\n {coords}\n </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 key: 'evidence',\n header: 'Evidence',\n hideOnMobile: true,\n render: (o) =>\n o.evidence?.id ? (\n <FileLink fileId={o.evidence.id} />\n ) : (\n <span className=\"text-text-tertiary\">—</span>\n ),\n },\n ];\n\n const openDialog = () => {\n setForm(makeEmptyForm());\n setFormError(null);\n setDialogOpen(true);\n };\n\n const resetForm = () => {\n setForm(makeEmptyForm());\n setFormError(null);\n };\n\n const handleRecord = async () => {\n if (!form.speciesId) {\n setFormError('Species is required.');\n return;\n }\n try {\n await recordObservation.mutateAsync({\n speciesId: form.speciesId,\n observedAt: form.observedAt\n ? new Date(form.observedAt).toISOString()\n : new Date().toISOString(),\n count: form.count ? Number(form.count) : 1,\n method: form.method,\n siteId: form.siteId && form.siteId !== NO_SITE ? form.siteId : null,\n latitude: form.latitude ? Number(form.latitude) : null,\n longitude: form.longitude ? Number(form.longitude) : null,\n notes: form.notes.trim() || null,\n evidenceFileId: form.evidence?.fileId ?? null,\n });\n setDialogOpen(false);\n resetForm();\n } catch (err) {\n setFormError(err instanceof Error ? err.message : 'Failed to record observation.');\n }\n };\n\n const filtersActive = speciesFilter !== 'ALL' || siteFilter !== 'ALL';\n\n return (\n <PageLayout\n title=\"Field Observations\"\n description=\"Wildlife & biodiversity sightings across every species and site — your citizen-science field log.\"\n actions={\n <Button onClick={openDialog}>\n <Plus className=\"mr-1.5 h-4 w-4\" /> Record observation\n </Button>\n }\n >\n <div className=\"mb-5 flex flex-wrap items-center gap-3\">\n <Select\n value={speciesFilter}\n onValueChange={(v) => {\n setSpeciesFilter(v);\n pager.reset();\n }}\n >\n <SelectTrigger className=\"w-52\">\n <SelectValue placeholder=\"All species\" />\n </SelectTrigger>\n <SelectContent>\n <SelectItem value=\"ALL\">All species</SelectItem>\n {speciesRows.map((s) => (\n <SelectItem key={s.id} value={s.id}>\n {s.commonName}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n <Select\n value={siteFilter}\n onValueChange={(v) => {\n setSiteFilter(v);\n pager.reset();\n }}\n >\n <SelectTrigger className=\"w-52\">\n <SelectValue placeholder=\"All sites\" />\n </SelectTrigger>\n <SelectContent>\n <SelectItem value=\"ALL\">All sites</SelectItem>\n {siteRows.map((s) => (\n <SelectItem key={s.id} value={s.id}>\n {s.name}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </div>\n\n {isLoading ? (\n <LoadingView label=\"Loading observations…\" />\n ) : isError ? (\n <ErrorView message=\"Couldn't load observations.\" onRetry={() => refetch()} />\n ) : rows.length === 0 ? (\n <EmptyState\n illustration=\"empty-data\"\n icon={<Binoculars className=\"h-10 w-10\" />}\n title=\"No observations yet\"\n description={\n filtersActive\n ? 'No observations match these filters.'\n : 'Record your first field observation to start building your biodiversity dataset.'\n }\n action={\n <Button onClick={openDialog}>\n <Plus className=\"mr-1.5 h-4 w-4\" /> Record observation\n </Button>\n }\n />\n ) : (\n <>\n <DataTable columns={columns} rows={rows} rowKey={(o) => o.id} />\n <Pagination\n page={pager.page}\n pageSize={PAGE_SIZE}\n itemCount={rows.length}\n hasMore={data?.pageInfo?.hasMore ?? false}\n onPageChange={(target) => pager.onPageChange(target, data?.pageInfo?.cursor)}\n />\n </>\n )}\n\n <ResponsiveDialog\n open={dialogOpen}\n onOpenChange={(open) => {\n setDialogOpen(open);\n if (!open) resetForm();\n }}\n >\n <ResponsiveDialogContent>\n <ResponsiveDialogHeader>\n <ResponsiveDialogTitle>Record observation</ResponsiveDialogTitle>\n <ResponsiveDialogDescription>\n Log a wildlife or biodiversity sighting in the field.\n </ResponsiveDialogDescription>\n </ResponsiveDialogHeader>\n\n <div className=\"grid gap-4 py-2\">\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"species\">Species *</Label>\n <Select\n value={form.speciesId}\n onValueChange={(v) => setForm({ ...form, speciesId: v })}\n >\n <SelectTrigger id=\"species\">\n <SelectValue placeholder=\"Select a species\" />\n </SelectTrigger>\n <SelectContent>\n {speciesRows.map((s) => (\n <SelectItem key={s.id} value={s.id}>\n {s.commonName}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n {speciesRows.length === 0 && (\n <p className=\"text-xs text-text-tertiary\">\n No species defined yet — add one in the Species section first.\n </p>\n )}\n </div>\n\n <div className=\"grid gap-2 sm:grid-cols-2\">\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"observedAt\">Observed at</Label>\n <Input\n id=\"observedAt\"\n type=\"datetime-local\"\n value={form.observedAt}\n onChange={(e) => setForm({ ...form, observedAt: e.target.value })}\n />\n </div>\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"count\">Count</Label>\n <Input\n id=\"count\"\n type=\"number\"\n min=\"1\"\n step=\"1\"\n value={form.count}\n onChange={(e) => setForm({ ...form, count: e.target.value })}\n placeholder=\"1\"\n />\n </div>\n </div>\n\n <div className=\"grid gap-2 sm:grid-cols-2\">\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"method\">Method</Label>\n <Select\n value={form.method}\n onValueChange={(v) => setForm({ ...form, method: v as EcoObservationMethod })}\n >\n <SelectTrigger id=\"method\">\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=\"site\">Site</Label>\n <Select value={form.siteId} onValueChange={(v) => setForm({ ...form, siteId: v })}>\n <SelectTrigger id=\"site\">\n <SelectValue placeholder=\"No site\" />\n </SelectTrigger>\n <SelectContent>\n <SelectItem value={NO_SITE}>No site</SelectItem>\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>\n\n <div className=\"grid gap-2 sm:grid-cols-2\">\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"lat\">Latitude</Label>\n <Input\n id=\"lat\"\n type=\"number\"\n step=\"any\"\n value={form.latitude}\n onChange={(e) => setForm({ ...form, latitude: e.target.value })}\n placeholder=\"12.9716\"\n />\n </div>\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"lng\">Longitude</Label>\n <Input\n id=\"lng\"\n type=\"number\"\n step=\"any\"\n value={form.longitude}\n onChange={(e) => setForm({ ...form, longitude: e.target.value })}\n placeholder=\"77.5946\"\n />\n </div>\n </div>\n\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"notes\">Notes</Label>\n <Textarea\n id=\"notes\"\n value={form.notes}\n onChange={(e) => setForm({ ...form, notes: e.target.value })}\n placeholder=\"Behaviour, habitat, weather or other field context.\"\n rows={2}\n />\n </div>\n\n <EvidenceUpload\n id=\"obsEvidence\"\n label=\"Evidence photo (optional)\"\n value={form.evidence}\n onChange={(evidence) => setForm({ ...form, evidence })}\n hint=\"Attach a photo, audio clip or PDF as supporting evidence.\"\n />\n\n {formError && <p className=\"text-sm text-status-error-text\">{formError}</p>}\n </div>\n\n <ResponsiveDialogFooter>\n <Button variant=\"outline\" onClick={() => setDialogOpen(false)}>\n Cancel\n </Button>\n <Button onClick={handleRecord} disabled={recordObservation.isPending}>\n {recordObservation.isPending ? 'Recording…' : 'Record observation'}\n </Button>\n </ResponsiveDialogFooter>\n </ResponsiveDialogContent>\n </ResponsiveDialog>\n </PageLayout>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"ObservationsPage.js","names":[],"sources":["../../../../src/ecoimpacthub/pages/observations/ObservationsPage.tsx"],"sourcesContent":["/**\n * Observations — a cross-species / cross-site field-observation feed.\n * @module ecoimpacthub/pages/observations\n *\n * Reads `ecoListObservations` (which selects the related species inline, so\n * `o.species?.commonName` is available per item); writes `ecoRecordObservation`.\n * Site & species name lookups are built from the `ecoListSites` /\n * `ecoListSpecies` connections. No mock data.\n */\n\nimport { useMemo, useState } from 'react';\nimport { Plus, Binoculars, MapPin } from 'lucide-react';\nimport {\n Button,\n GlassCard,\n Input,\n Label,\n Textarea,\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n ResponsiveDialog,\n ResponsiveDialogContent,\n ResponsiveDialogHeader,\n ResponsiveDialogTitle,\n ResponsiveDialogDescription,\n ResponsiveDialogFooter,\n} from '@burdenoff/fe-libs/ui';\nimport { PageLayout } from '../../components/PageLayout';\nimport { DataTable, type Column } from '../../components/DataTable';\nimport { LoadingView, ErrorView } from '../../components/StateViews';\nimport { EmptyState } from '../../components/EmptyState';\nimport { Pagination } from '../../components/Pagination';\nimport { EvidenceUpload } from '../../components/EvidenceUpload';\nimport { MethodBadge } from '../_shared/MethodBadge';\nimport { EvidenceThumb } from '../_shared/EvidenceThumb';\nimport {\n useEcoObservations,\n useEcoRecordObservation,\n useEcoSites,\n useEcoSpecies,\n useCursorPager,\n type UploadedEcoFile,\n} from '../../hooks';\nimport {\n formatCoords,\n formatDateTime,\n formatEnumLabel,\n formatNumber,\n} from '../../utils/formatters';\nimport type { EcoObservation, EcoObservationMethod } 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 PAGE_SIZE = 25;\nconst LOOKUP_PAGE = { limit: 100, offset: 0 };\n/** Sentinel for the \"no site\" option (Radix Select items can't use an empty value). */\nconst NO_SITE = 'NONE';\n\ninterface RecordFormState {\n speciesId: string;\n observedAt: string;\n count: string;\n method: EcoObservationMethod;\n siteId: string;\n latitude: string;\n longitude: string;\n notes: string;\n evidence: UploadedEcoFile | null;\n}\n\n/** A datetime-local value (`YYYY-MM-DDTHH:mm`) for \"now\" in the viewer's timezone. */\nfunction nowLocalInputValue(): string {\n const now = new Date();\n const local = new Date(now.getTime() - now.getTimezoneOffset() * 60000);\n return local.toISOString().slice(0, 16);\n}\n\nfunction makeEmptyForm(): RecordFormState {\n return {\n speciesId: '',\n observedAt: nowLocalInputValue(),\n count: '1',\n method: 'DIRECT_SIGHTING',\n siteId: NO_SITE,\n latitude: '',\n longitude: '',\n notes: '',\n evidence: null,\n };\n}\n\nexport function ObservationsPage() {\n const [speciesFilter, setSpeciesFilter] = useState<string>('ALL');\n const [siteFilter, setSiteFilter] = useState<string>('ALL');\n const pager = useCursorPager();\n const [dialogOpen, setDialogOpen] = useState(false);\n const [form, setForm] = useState<RecordFormState>(makeEmptyForm);\n const [formError, setFormError] = useState<string | null>(null);\n\n const sites = useEcoSites({ pagination: LOOKUP_PAGE });\n const species = useEcoSpecies({ pagination: LOOKUP_PAGE });\n\n const { data, isLoading, isError, refetch } = useEcoObservations({\n speciesId: speciesFilter === 'ALL' ? undefined : speciesFilter,\n siteId: siteFilter === 'ALL' ? undefined : siteFilter,\n pagination: { limit: PAGE_SIZE, cursor: pager.cursor },\n });\n const recordObservation = useEcoRecordObservation();\n\n const siteRows = useMemo(() => sites.data?.items ?? [], [sites.data]);\n const speciesRows = useMemo(() => species.data?.items ?? [], [species.data]);\n const rows = useMemo(() => data?.items ?? [], [data]);\n\n const siteNameById = useMemo(() => {\n const map = new Map<string, string>();\n for (const s of siteRows) map.set(s.id, s.name);\n return map;\n }, [siteRows]);\n\n const speciesNameById = useMemo(() => {\n const map = new Map<string, string>();\n for (const s of speciesRows) map.set(s.id, s.commonName);\n return map;\n }, [speciesRows]);\n\n const columns: Column<EcoObservation>[] = [\n {\n key: 'evidence',\n header: 'Evidence',\n render: (o) => (\n <EvidenceThumb\n fileId={o.evidence?.id}\n alt={o.species?.commonName ?? speciesNameById.get(o.speciesId)}\n />\n ),\n },\n {\n key: 'species',\n header: 'Species',\n render: (o) => (\n <div className=\"min-w-0\">\n <p className=\"font-semibold text-text-primary\">\n {o.species?.commonName ?? speciesNameById.get(o.speciesId) ?? 'Unknown species'}\n </p>\n {o.species?.scientificName && (\n <p className=\"truncate text-xs italic text-text-tertiary\">{o.species.scientificName}</p>\n )}\n </div>\n ),\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: 'coords',\n header: 'Coords',\n hideOnMobile: true,\n render: (o) => {\n const coords = formatCoords(o.latitude, o.longitude);\n if (coords === '—') return <span className=\"text-text-tertiary\">—</span>;\n return (\n <span className=\"inline-flex items-center gap-1 tabular-nums text-text-secondary\">\n <MapPin className=\"h-3.5 w-3.5 shrink-0 text-text-tertiary\" aria-hidden=\"true\" />\n {coords}\n </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 const openDialog = () => {\n setForm(makeEmptyForm());\n setFormError(null);\n setDialogOpen(true);\n };\n\n const resetForm = () => {\n setForm(makeEmptyForm());\n setFormError(null);\n };\n\n const handleRecord = async () => {\n if (!form.speciesId) {\n setFormError('Species is required.');\n return;\n }\n try {\n await recordObservation.mutateAsync({\n speciesId: form.speciesId,\n observedAt: form.observedAt\n ? new Date(form.observedAt).toISOString()\n : new Date().toISOString(),\n count: form.count ? Number(form.count) : 1,\n method: form.method,\n siteId: form.siteId && form.siteId !== NO_SITE ? form.siteId : null,\n latitude: form.latitude ? Number(form.latitude) : null,\n longitude: form.longitude ? Number(form.longitude) : null,\n notes: form.notes.trim() || null,\n evidenceFileId: form.evidence?.fileId ?? null,\n });\n setDialogOpen(false);\n resetForm();\n } catch (err) {\n setFormError(err instanceof Error ? err.message : 'Failed to record observation.');\n }\n };\n\n const filtersActive = speciesFilter !== 'ALL' || siteFilter !== 'ALL';\n\n return (\n <PageLayout\n title=\"Field Observations\"\n description=\"Wildlife & biodiversity sightings across every species and site — your citizen-science field log.\"\n actions={\n <Button onClick={openDialog}>\n <Plus className=\"mr-1.5 h-4 w-4\" /> Record observation\n </Button>\n }\n >\n <GlassCard treatment=\"plain\" className=\"mb-5 p-4\">\n <div className=\"flex flex-col gap-3 sm:flex-row sm:flex-wrap sm:items-end\">\n <div className=\"grid gap-1.5\">\n <span className=\"text-xs font-medium uppercase tracking-wide text-text-tertiary\">\n Species\n </span>\n <Select\n value={speciesFilter}\n onValueChange={(v) => {\n setSpeciesFilter(v);\n pager.reset();\n }}\n >\n <SelectTrigger className=\"w-full sm:w-52\">\n <SelectValue placeholder=\"All species\" />\n </SelectTrigger>\n <SelectContent>\n <SelectItem value=\"ALL\">All species</SelectItem>\n {speciesRows.map((s) => (\n <SelectItem key={s.id} value={s.id}>\n {s.commonName}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </div>\n <div className=\"grid gap-1.5\">\n <span className=\"text-xs font-medium uppercase tracking-wide text-text-tertiary\">\n Site\n </span>\n <Select\n value={siteFilter}\n onValueChange={(v) => {\n setSiteFilter(v);\n pager.reset();\n }}\n >\n <SelectTrigger className=\"w-full sm:w-52\">\n <SelectValue placeholder=\"All sites\" />\n </SelectTrigger>\n <SelectContent>\n <SelectItem value=\"ALL\">All sites</SelectItem>\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>\n </GlassCard>\n\n {isLoading ? (\n <LoadingView label=\"Loading observations…\" />\n ) : isError ? (\n <ErrorView message=\"Couldn't load observations.\" onRetry={() => refetch()} />\n ) : rows.length === 0 ? (\n <EmptyState\n illustration=\"empty-data\"\n icon={<Binoculars className=\"h-10 w-10\" />}\n title=\"No observations yet\"\n description={\n filtersActive\n ? 'No observations match these filters.'\n : 'Record your first field observation to start building your biodiversity dataset.'\n }\n action={\n <Button onClick={openDialog}>\n <Plus className=\"mr-1.5 h-4 w-4\" /> Record observation\n </Button>\n }\n />\n ) : (\n <>\n <DataTable columns={columns} rows={rows} rowKey={(o) => o.id} />\n <Pagination\n page={pager.page}\n pageSize={PAGE_SIZE}\n itemCount={rows.length}\n hasMore={data?.pageInfo?.hasMore ?? false}\n onPageChange={(target) => pager.onPageChange(target, data?.pageInfo?.cursor)}\n />\n </>\n )}\n\n <ResponsiveDialog\n open={dialogOpen}\n onOpenChange={(open) => {\n setDialogOpen(open);\n if (!open) resetForm();\n }}\n >\n <ResponsiveDialogContent>\n <ResponsiveDialogHeader>\n <ResponsiveDialogTitle>Record observation</ResponsiveDialogTitle>\n <ResponsiveDialogDescription>\n Log a wildlife or biodiversity sighting in the field.\n </ResponsiveDialogDescription>\n </ResponsiveDialogHeader>\n\n <div className=\"grid gap-4 py-2\">\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"species\">Species *</Label>\n <Select\n value={form.speciesId}\n onValueChange={(v) => setForm({ ...form, speciesId: v })}\n >\n <SelectTrigger id=\"species\">\n <SelectValue placeholder=\"Select a species\" />\n </SelectTrigger>\n <SelectContent>\n {speciesRows.map((s) => (\n <SelectItem key={s.id} value={s.id}>\n {s.commonName}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n {speciesRows.length === 0 && (\n <p className=\"text-xs text-text-tertiary\">\n No species defined yet — add one in the Species section first.\n </p>\n )}\n </div>\n\n <div className=\"grid gap-2 sm:grid-cols-2\">\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"observedAt\">Observed at</Label>\n <Input\n id=\"observedAt\"\n type=\"datetime-local\"\n value={form.observedAt}\n onChange={(e) => setForm({ ...form, observedAt: e.target.value })}\n />\n </div>\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"count\">Count</Label>\n <Input\n id=\"count\"\n type=\"number\"\n min=\"1\"\n step=\"1\"\n value={form.count}\n onChange={(e) => setForm({ ...form, count: e.target.value })}\n placeholder=\"1\"\n />\n </div>\n </div>\n\n <div className=\"grid gap-2 sm:grid-cols-2\">\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"method\">Method</Label>\n <Select\n value={form.method}\n onValueChange={(v) => setForm({ ...form, method: v as EcoObservationMethod })}\n >\n <SelectTrigger id=\"method\">\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=\"site\">Site</Label>\n <Select value={form.siteId} onValueChange={(v) => setForm({ ...form, siteId: v })}>\n <SelectTrigger id=\"site\">\n <SelectValue placeholder=\"No site\" />\n </SelectTrigger>\n <SelectContent>\n <SelectItem value={NO_SITE}>No site</SelectItem>\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>\n\n <div className=\"grid gap-2 sm:grid-cols-2\">\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"lat\">Latitude</Label>\n <Input\n id=\"lat\"\n type=\"number\"\n step=\"any\"\n value={form.latitude}\n onChange={(e) => setForm({ ...form, latitude: e.target.value })}\n placeholder=\"12.9716\"\n />\n </div>\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"lng\">Longitude</Label>\n <Input\n id=\"lng\"\n type=\"number\"\n step=\"any\"\n value={form.longitude}\n onChange={(e) => setForm({ ...form, longitude: e.target.value })}\n placeholder=\"77.5946\"\n />\n </div>\n </div>\n\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"notes\">Notes</Label>\n <Textarea\n id=\"notes\"\n value={form.notes}\n onChange={(e) => setForm({ ...form, notes: e.target.value })}\n placeholder=\"Behaviour, habitat, weather or other field context.\"\n rows={2}\n />\n </div>\n\n <EvidenceUpload\n id=\"obsEvidence\"\n label=\"Evidence photo (optional)\"\n value={form.evidence}\n onChange={(evidence) => setForm({ ...form, evidence })}\n hint=\"Attach a photo, audio clip or PDF as supporting evidence.\"\n />\n\n {formError && <p className=\"text-sm text-status-error-text\">{formError}</p>}\n </div>\n\n <ResponsiveDialogFooter>\n <Button variant=\"outline\" onClick={() => setDialogOpen(false)}>\n Cancel\n </Button>\n <Button onClick={handleRecord} disabled={recordObservation.isPending}>\n {recordObservation.isPending ? 'Recording…' : 'Record observation'}\n </Button>\n </ResponsiveDialogFooter>\n </ResponsiveDialogContent>\n </ResponsiveDialog>\n </PageLayout>\n );\n}\n"],"mappings":"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"}