@burdenoff/microfe-ecoimpacthub 2026.720.3 → 2026.720.4

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 (482) hide show
  1. package/dist/_virtual/_rolldown/runtime.js +1 -1
  2. package/dist/ecoimpacthub/EcoImpactHubRoot.js.map +1 -1
  3. package/dist/ecoimpacthub/EcoImpactHubRoutes.js.map +1 -1
  4. package/dist/ecoimpacthub/components/DataTable.js.map +1 -1
  5. package/dist/ecoimpacthub/components/EcoDiscussion.js.map +1 -1
  6. package/dist/ecoimpacthub/components/EcoTags.js.map +1 -1
  7. package/dist/ecoimpacthub/components/EmptyState.js.map +1 -1
  8. package/dist/ecoimpacthub/components/EvidenceUpload.js.map +1 -1
  9. package/dist/ecoimpacthub/components/FeatureFlagGate.js.map +1 -1
  10. package/dist/ecoimpacthub/components/FileLink.js.map +1 -1
  11. package/dist/ecoimpacthub/components/FilterTabs.js.map +1 -1
  12. package/dist/ecoimpacthub/components/MetricsCard.js.map +1 -1
  13. package/dist/ecoimpacthub/components/MiniBar.js.map +1 -1
  14. package/dist/ecoimpacthub/components/PageLayout.js.map +1 -1
  15. package/dist/ecoimpacthub/components/Pagination.js.map +1 -1
  16. package/dist/ecoimpacthub/components/StateViews.js.map +1 -1
  17. package/dist/ecoimpacthub/components/StatusBadge.js.map +1 -1
  18. package/dist/ecoimpacthub/components/Tabs.js.map +1 -1
  19. package/dist/ecoimpacthub/context/EcoImpactHubContext.js.map +1 -1
  20. package/dist/ecoimpacthub/hooks/useCursorPager.js.map +1 -1
  21. package/dist/ecoimpacthub/hooks/useEcoComments.js.map +1 -1
  22. package/dist/ecoimpacthub/hooks/useEcoFiles.js.map +1 -1
  23. package/dist/ecoimpacthub/hooks/useEcoImpactHubApi.js.map +1 -1
  24. package/dist/ecoimpacthub/hooks/useEcoTags.js.map +1 -1
  25. package/dist/ecoimpacthub/pages/DashboardPage.js.map +1 -1
  26. package/dist/ecoimpacthub/pages/_shared/ConservationBadge.js.map +1 -1
  27. package/dist/ecoimpacthub/pages/_shared/EvidenceThumb.js.map +1 -1
  28. package/dist/ecoimpacthub/pages/_shared/MethodBadge.js.map +1 -1
  29. package/dist/ecoimpacthub/pages/_shared/TaxonChip.js.map +1 -1
  30. package/dist/ecoimpacthub/pages/badges.js.map +1 -1
  31. package/dist/ecoimpacthub/pages/carbon/CarbonPage.js.map +1 -1
  32. package/dist/ecoimpacthub/pages/carbonVisuals.js.map +1 -1
  33. package/dist/ecoimpacthub/pages/habitat/HabitatProjectDetailPage.js.map +1 -1
  34. package/dist/ecoimpacthub/pages/habitat/HabitatProjectsPage.js.map +1 -1
  35. package/dist/ecoimpacthub/pages/habitat/LifecycleProgress.js.map +1 -1
  36. package/dist/ecoimpacthub/pages/monitoring/MonitoringPage.js.map +1 -1
  37. package/dist/ecoimpacthub/pages/observations/ObservationsPage.js.map +1 -1
  38. package/dist/ecoimpacthub/pages/reports/ReportDetailPage.js.map +1 -1
  39. package/dist/ecoimpacthub/pages/reports/ReportsPage.js.map +1 -1
  40. package/dist/ecoimpacthub/pages/sites/SiteDetailPage.js.map +1 -1
  41. package/dist/ecoimpacthub/pages/sites/SitesPage.js.map +1 -1
  42. package/dist/ecoimpacthub/pages/sites/siteTypeVisuals.js.map +1 -1
  43. package/dist/ecoimpacthub/pages/species/SpeciesDetailPage.js.map +1 -1
  44. package/dist/ecoimpacthub/pages/species/SpeciesPage.js.map +1 -1
  45. package/dist/ecoimpacthub/services/createSandboxAssistantTransport.d.ts.map +1 -1
  46. package/dist/ecoimpacthub/services/createSandboxAssistantTransport.js.map +1 -1
  47. package/dist/ecoimpacthub/types/index.d.ts.map +1 -1
  48. package/dist/ecoimpacthub/utils/cn.js.map +1 -1
  49. package/dist/ecoimpacthub/utils/formatters.js.map +1 -1
  50. package/dist/ecoimpacthub/utils/navigation.js.map +1 -1
  51. package/dist/node_modules/clsx/dist/clsx.js.map +1 -1
  52. package/dist/node_modules/d3-array/src/ascending.js.map +1 -1
  53. package/dist/node_modules/d3-array/src/bisect.js.map +1 -1
  54. package/dist/node_modules/d3-array/src/bisector.js.map +1 -1
  55. package/dist/node_modules/d3-array/src/descending.js.map +1 -1
  56. package/dist/node_modules/d3-array/src/max.js.map +1 -1
  57. package/dist/node_modules/d3-array/src/min.js.map +1 -1
  58. package/dist/node_modules/d3-array/src/number.js.map +1 -1
  59. package/dist/node_modules/d3-array/src/quantile.js.map +1 -1
  60. package/dist/node_modules/d3-array/src/quickselect.js.map +1 -1
  61. package/dist/node_modules/d3-array/src/range.js.map +1 -1
  62. package/dist/node_modules/d3-array/src/sort.js.map +1 -1
  63. package/dist/node_modules/d3-array/src/ticks.js.map +1 -1
  64. package/dist/node_modules/d3-color/src/color.js.map +1 -1
  65. package/dist/node_modules/d3-color/src/define.js.map +1 -1
  66. package/dist/node_modules/d3-format/src/defaultLocale.js.map +1 -1
  67. package/dist/node_modules/d3-format/src/exponent.js.map +1 -1
  68. package/dist/node_modules/d3-format/src/formatDecimal.js.map +1 -1
  69. package/dist/node_modules/d3-format/src/formatGroup.js.map +1 -1
  70. package/dist/node_modules/d3-format/src/formatNumerals.js.map +1 -1
  71. package/dist/node_modules/d3-format/src/formatPrefixAuto.js.map +1 -1
  72. package/dist/node_modules/d3-format/src/formatRounded.js.map +1 -1
  73. package/dist/node_modules/d3-format/src/formatSpecifier.js.map +1 -1
  74. package/dist/node_modules/d3-format/src/formatTrim.js.map +1 -1
  75. package/dist/node_modules/d3-format/src/formatTypes.js.map +1 -1
  76. package/dist/node_modules/d3-format/src/identity.js.map +1 -1
  77. package/dist/node_modules/d3-format/src/locale.js.map +1 -1
  78. package/dist/node_modules/d3-format/src/precisionFixed.js.map +1 -1
  79. package/dist/node_modules/d3-format/src/precisionPrefix.js.map +1 -1
  80. package/dist/node_modules/d3-format/src/precisionRound.js.map +1 -1
  81. package/dist/node_modules/d3-interpolate/src/array.js.map +1 -1
  82. package/dist/node_modules/d3-interpolate/src/basis.js.map +1 -1
  83. package/dist/node_modules/d3-interpolate/src/basisClosed.js.map +1 -1
  84. package/dist/node_modules/d3-interpolate/src/color.js.map +1 -1
  85. package/dist/node_modules/d3-interpolate/src/date.js.map +1 -1
  86. package/dist/node_modules/d3-interpolate/src/number.js.map +1 -1
  87. package/dist/node_modules/d3-interpolate/src/numberArray.js.map +1 -1
  88. package/dist/node_modules/d3-interpolate/src/object.js.map +1 -1
  89. package/dist/node_modules/d3-interpolate/src/piecewise.js.map +1 -1
  90. package/dist/node_modules/d3-interpolate/src/rgb.js.map +1 -1
  91. package/dist/node_modules/d3-interpolate/src/round.js.map +1 -1
  92. package/dist/node_modules/d3-interpolate/src/string.js.map +1 -1
  93. package/dist/node_modules/d3-interpolate/src/value.js.map +1 -1
  94. package/dist/node_modules/d3-path/src/path.js.map +1 -1
  95. package/dist/node_modules/d3-scale/src/band.js.map +1 -1
  96. package/dist/node_modules/d3-scale/src/constant.js.map +1 -1
  97. package/dist/node_modules/d3-scale/src/continuous.js.map +1 -1
  98. package/dist/node_modules/d3-scale/src/diverging.js.map +1 -1
  99. package/dist/node_modules/d3-scale/src/identity.js.map +1 -1
  100. package/dist/node_modules/d3-scale/src/init.js.map +1 -1
  101. package/dist/node_modules/d3-scale/src/linear.js.map +1 -1
  102. package/dist/node_modules/d3-scale/src/log.js.map +1 -1
  103. package/dist/node_modules/d3-scale/src/nice.js.map +1 -1
  104. package/dist/node_modules/d3-scale/src/number.js.map +1 -1
  105. package/dist/node_modules/d3-scale/src/ordinal.js.map +1 -1
  106. package/dist/node_modules/d3-scale/src/pow.js.map +1 -1
  107. package/dist/node_modules/d3-scale/src/quantile.js.map +1 -1
  108. package/dist/node_modules/d3-scale/src/quantize.js.map +1 -1
  109. package/dist/node_modules/d3-scale/src/radial.js.map +1 -1
  110. package/dist/node_modules/d3-scale/src/sequential.js.map +1 -1
  111. package/dist/node_modules/d3-scale/src/sequentialQuantile.js.map +1 -1
  112. package/dist/node_modules/d3-scale/src/symlog.js.map +1 -1
  113. package/dist/node_modules/d3-scale/src/threshold.js.map +1 -1
  114. package/dist/node_modules/d3-scale/src/tickFormat.js.map +1 -1
  115. package/dist/node_modules/d3-scale/src/time.js.map +1 -1
  116. package/dist/node_modules/d3-scale/src/utcTime.js.map +1 -1
  117. package/dist/node_modules/d3-shape/src/area.js.map +1 -1
  118. package/dist/node_modules/d3-shape/src/array.js.map +1 -1
  119. package/dist/node_modules/d3-shape/src/constant.js.map +1 -1
  120. package/dist/node_modules/d3-shape/src/curve/basis.js.map +1 -1
  121. package/dist/node_modules/d3-shape/src/curve/basisClosed.js.map +1 -1
  122. package/dist/node_modules/d3-shape/src/curve/basisOpen.js.map +1 -1
  123. package/dist/node_modules/d3-shape/src/curve/bump.js.map +1 -1
  124. package/dist/node_modules/d3-shape/src/curve/linear.js.map +1 -1
  125. package/dist/node_modules/d3-shape/src/curve/linearClosed.js.map +1 -1
  126. package/dist/node_modules/d3-shape/src/curve/monotone.js.map +1 -1
  127. package/dist/node_modules/d3-shape/src/curve/natural.js.map +1 -1
  128. package/dist/node_modules/d3-shape/src/curve/step.js.map +1 -1
  129. package/dist/node_modules/d3-shape/src/line.js.map +1 -1
  130. package/dist/node_modules/d3-shape/src/noop.js.map +1 -1
  131. package/dist/node_modules/d3-shape/src/offset/expand.js.map +1 -1
  132. package/dist/node_modules/d3-shape/src/offset/none.js.map +1 -1
  133. package/dist/node_modules/d3-shape/src/offset/silhouette.js.map +1 -1
  134. package/dist/node_modules/d3-shape/src/offset/wiggle.js.map +1 -1
  135. package/dist/node_modules/d3-shape/src/order/none.js.map +1 -1
  136. package/dist/node_modules/d3-shape/src/path.js.map +1 -1
  137. package/dist/node_modules/d3-shape/src/point.js.map +1 -1
  138. package/dist/node_modules/d3-shape/src/stack.js.map +1 -1
  139. package/dist/node_modules/d3-shape/src/symbol/circle.js.map +1 -1
  140. package/dist/node_modules/d3-shape/src/symbol/cross.js.map +1 -1
  141. package/dist/node_modules/d3-shape/src/symbol/diamond.js.map +1 -1
  142. package/dist/node_modules/d3-shape/src/symbol/square.js.map +1 -1
  143. package/dist/node_modules/d3-shape/src/symbol/star.js.map +1 -1
  144. package/dist/node_modules/d3-shape/src/symbol/triangle.js.map +1 -1
  145. package/dist/node_modules/d3-shape/src/symbol/wye.js.map +1 -1
  146. package/dist/node_modules/d3-shape/src/symbol.js.map +1 -1
  147. package/dist/node_modules/d3-time/src/day.js.map +1 -1
  148. package/dist/node_modules/d3-time/src/hour.js.map +1 -1
  149. package/dist/node_modules/d3-time/src/interval.js.map +1 -1
  150. package/dist/node_modules/d3-time/src/millisecond.js.map +1 -1
  151. package/dist/node_modules/d3-time/src/minute.js.map +1 -1
  152. package/dist/node_modules/d3-time/src/month.js.map +1 -1
  153. package/dist/node_modules/d3-time/src/second.js.map +1 -1
  154. package/dist/node_modules/d3-time/src/ticks.js.map +1 -1
  155. package/dist/node_modules/d3-time/src/week.js.map +1 -1
  156. package/dist/node_modules/d3-time/src/year.js.map +1 -1
  157. package/dist/node_modules/d3-time-format/src/defaultLocale.js.map +1 -1
  158. package/dist/node_modules/d3-time-format/src/locale.js.map +1 -1
  159. package/dist/node_modules/decimal.js-light/decimal.js.map +1 -1
  160. package/dist/node_modules/eventemitter3/index.js.map +1 -1
  161. package/dist/node_modules/fast-equals/dist/es/index.js.map +1 -1
  162. package/dist/node_modules/internmap/src/index.js.map +1 -1
  163. package/dist/node_modules/lodash/_DataView.js.map +1 -1
  164. package/dist/node_modules/lodash/_Hash.js.map +1 -1
  165. package/dist/node_modules/lodash/_ListCache.js.map +1 -1
  166. package/dist/node_modules/lodash/_Map.js.map +1 -1
  167. package/dist/node_modules/lodash/_MapCache.js.map +1 -1
  168. package/dist/node_modules/lodash/_Promise.js.map +1 -1
  169. package/dist/node_modules/lodash/_Set.js.map +1 -1
  170. package/dist/node_modules/lodash/_SetCache.js.map +1 -1
  171. package/dist/node_modules/lodash/_Stack.js +2 -1
  172. package/dist/node_modules/lodash/_Stack.js.map +1 -1
  173. package/dist/node_modules/lodash/_Symbol.js.map +1 -1
  174. package/dist/node_modules/lodash/_Uint8Array.js.map +1 -1
  175. package/dist/node_modules/lodash/_WeakMap.js.map +1 -1
  176. package/dist/node_modules/lodash/_apply.js.map +1 -1
  177. package/dist/node_modules/lodash/_arrayEvery.js.map +1 -1
  178. package/dist/node_modules/lodash/_arrayFilter.js.map +1 -1
  179. package/dist/node_modules/lodash/_arrayIncludes.js.map +1 -1
  180. package/dist/node_modules/lodash/_arrayIncludesWith.js.map +1 -1
  181. package/dist/node_modules/lodash/_arrayLikeKeys.js.map +1 -1
  182. package/dist/node_modules/lodash/_arrayMap.js.map +1 -1
  183. package/dist/node_modules/lodash/_arrayPush.js.map +1 -1
  184. package/dist/node_modules/lodash/_arraySome.js.map +1 -1
  185. package/dist/node_modules/lodash/_asciiToArray.js.map +1 -1
  186. package/dist/node_modules/lodash/_assocIndexOf.js.map +1 -1
  187. package/dist/node_modules/lodash/_baseAssignValue.js.map +1 -1
  188. package/dist/node_modules/lodash/_baseEach.js.map +1 -1
  189. package/dist/node_modules/lodash/_baseEvery.js.map +1 -1
  190. package/dist/node_modules/lodash/_baseExtremum.js.map +1 -1
  191. package/dist/node_modules/lodash/_baseFindIndex.js.map +1 -1
  192. package/dist/node_modules/lodash/_baseFlatten.js.map +1 -1
  193. package/dist/node_modules/lodash/_baseFor.js.map +1 -1
  194. package/dist/node_modules/lodash/_baseForOwn.js.map +1 -1
  195. package/dist/node_modules/lodash/_baseGet.js.map +1 -1
  196. package/dist/node_modules/lodash/_baseGetAllKeys.js.map +1 -1
  197. package/dist/node_modules/lodash/_baseGetTag.js.map +1 -1
  198. package/dist/node_modules/lodash/_baseGt.js.map +1 -1
  199. package/dist/node_modules/lodash/_baseHasIn.js.map +1 -1
  200. package/dist/node_modules/lodash/_baseIndexOf.js.map +1 -1
  201. package/dist/node_modules/lodash/_baseIsArguments.js.map +1 -1
  202. package/dist/node_modules/lodash/_baseIsEqual.js.map +1 -1
  203. package/dist/node_modules/lodash/_baseIsEqualDeep.js.map +1 -1
  204. package/dist/node_modules/lodash/_baseIsMatch.js.map +1 -1
  205. package/dist/node_modules/lodash/_baseIsNaN.js.map +1 -1
  206. package/dist/node_modules/lodash/_baseIsNative.js.map +1 -1
  207. package/dist/node_modules/lodash/_baseIsTypedArray.js.map +1 -1
  208. package/dist/node_modules/lodash/_baseIteratee.js.map +1 -1
  209. package/dist/node_modules/lodash/_baseKeys.js.map +1 -1
  210. package/dist/node_modules/lodash/_baseLt.js.map +1 -1
  211. package/dist/node_modules/lodash/_baseMap.js.map +1 -1
  212. package/dist/node_modules/lodash/_baseMatches.js.map +1 -1
  213. package/dist/node_modules/lodash/_baseMatchesProperty.js.map +1 -1
  214. package/dist/node_modules/lodash/_baseOrderBy.js.map +1 -1
  215. package/dist/node_modules/lodash/_baseProperty.js.map +1 -1
  216. package/dist/node_modules/lodash/_basePropertyDeep.js.map +1 -1
  217. package/dist/node_modules/lodash/_baseRange.js.map +1 -1
  218. package/dist/node_modules/lodash/_baseRest.js.map +1 -1
  219. package/dist/node_modules/lodash/_baseSetToString.js.map +1 -1
  220. package/dist/node_modules/lodash/_baseSlice.js.map +1 -1
  221. package/dist/node_modules/lodash/_baseSome.js.map +1 -1
  222. package/dist/node_modules/lodash/_baseSortBy.js.map +1 -1
  223. package/dist/node_modules/lodash/_baseTimes.js.map +1 -1
  224. package/dist/node_modules/lodash/_baseToString.js.map +1 -1
  225. package/dist/node_modules/lodash/_baseTrim.js.map +1 -1
  226. package/dist/node_modules/lodash/_baseUnary.js.map +1 -1
  227. package/dist/node_modules/lodash/_baseUniq.js.map +1 -1
  228. package/dist/node_modules/lodash/_cacheHas.js.map +1 -1
  229. package/dist/node_modules/lodash/_castPath.js.map +1 -1
  230. package/dist/node_modules/lodash/_castSlice.js.map +1 -1
  231. package/dist/node_modules/lodash/_compareAscending.js.map +1 -1
  232. package/dist/node_modules/lodash/_compareMultiple.js.map +1 -1
  233. package/dist/node_modules/lodash/_coreJsData.js.map +1 -1
  234. package/dist/node_modules/lodash/_createBaseEach.js.map +1 -1
  235. package/dist/node_modules/lodash/_createBaseFor.js.map +1 -1
  236. package/dist/node_modules/lodash/_createCaseFirst.js.map +1 -1
  237. package/dist/node_modules/lodash/_createFind.js.map +1 -1
  238. package/dist/node_modules/lodash/_createRange.js.map +1 -1
  239. package/dist/node_modules/lodash/_createSet.js.map +1 -1
  240. package/dist/node_modules/lodash/_defineProperty.js.map +1 -1
  241. package/dist/node_modules/lodash/_equalArrays.js.map +1 -1
  242. package/dist/node_modules/lodash/_equalByTag.js.map +1 -1
  243. package/dist/node_modules/lodash/_equalObjects.js.map +1 -1
  244. package/dist/node_modules/lodash/_freeGlobal.js.map +1 -1
  245. package/dist/node_modules/lodash/_getAllKeys.js.map +1 -1
  246. package/dist/node_modules/lodash/_getMapData.js.map +1 -1
  247. package/dist/node_modules/lodash/_getMatchData.js.map +1 -1
  248. package/dist/node_modules/lodash/_getNative.js.map +1 -1
  249. package/dist/node_modules/lodash/_getPrototype.js.map +1 -1
  250. package/dist/node_modules/lodash/_getRawTag.js.map +1 -1
  251. package/dist/node_modules/lodash/_getSymbols.js.map +1 -1
  252. package/dist/node_modules/lodash/_getTag.js.map +1 -1
  253. package/dist/node_modules/lodash/_getValue.js.map +1 -1
  254. package/dist/node_modules/lodash/_hasPath.js.map +1 -1
  255. package/dist/node_modules/lodash/_hasUnicode.js.map +1 -1
  256. package/dist/node_modules/lodash/_hashClear.js.map +1 -1
  257. package/dist/node_modules/lodash/_hashDelete.js.map +1 -1
  258. package/dist/node_modules/lodash/_hashGet.js.map +1 -1
  259. package/dist/node_modules/lodash/_hashHas.js.map +1 -1
  260. package/dist/node_modules/lodash/_hashSet.js.map +1 -1
  261. package/dist/node_modules/lodash/_isFlattenable.js.map +1 -1
  262. package/dist/node_modules/lodash/_isIndex.js.map +1 -1
  263. package/dist/node_modules/lodash/_isIterateeCall.js.map +1 -1
  264. package/dist/node_modules/lodash/_isKey.js.map +1 -1
  265. package/dist/node_modules/lodash/_isKeyable.js.map +1 -1
  266. package/dist/node_modules/lodash/_isMasked.js.map +1 -1
  267. package/dist/node_modules/lodash/_isPrototype.js.map +1 -1
  268. package/dist/node_modules/lodash/_isStrictComparable.js.map +1 -1
  269. package/dist/node_modules/lodash/_listCacheClear.js.map +1 -1
  270. package/dist/node_modules/lodash/_listCacheDelete.js.map +1 -1
  271. package/dist/node_modules/lodash/_listCacheGet.js.map +1 -1
  272. package/dist/node_modules/lodash/_listCacheHas.js.map +1 -1
  273. package/dist/node_modules/lodash/_listCacheSet.js.map +1 -1
  274. package/dist/node_modules/lodash/_mapCacheClear.js.map +1 -1
  275. package/dist/node_modules/lodash/_mapCacheDelete.js.map +1 -1
  276. package/dist/node_modules/lodash/_mapCacheGet.js.map +1 -1
  277. package/dist/node_modules/lodash/_mapCacheHas.js.map +1 -1
  278. package/dist/node_modules/lodash/_mapCacheSet.js.map +1 -1
  279. package/dist/node_modules/lodash/_mapToArray.js.map +1 -1
  280. package/dist/node_modules/lodash/_matchesStrictComparable.js.map +1 -1
  281. package/dist/node_modules/lodash/_memoizeCapped.js.map +1 -1
  282. package/dist/node_modules/lodash/_nativeCreate.js.map +1 -1
  283. package/dist/node_modules/lodash/_nativeKeys.js.map +1 -1
  284. package/dist/node_modules/lodash/_nodeUtil.js.map +1 -1
  285. package/dist/node_modules/lodash/_objectToString.js.map +1 -1
  286. package/dist/node_modules/lodash/_overArg.js.map +1 -1
  287. package/dist/node_modules/lodash/_overRest.js.map +1 -1
  288. package/dist/node_modules/lodash/_root.js.map +1 -1
  289. package/dist/node_modules/lodash/_setCacheAdd.js.map +1 -1
  290. package/dist/node_modules/lodash/_setCacheHas.js.map +1 -1
  291. package/dist/node_modules/lodash/_setToArray.js.map +1 -1
  292. package/dist/node_modules/lodash/_setToString.js.map +1 -1
  293. package/dist/node_modules/lodash/_shortOut.js.map +1 -1
  294. package/dist/node_modules/lodash/_stackClear.js.map +1 -1
  295. package/dist/node_modules/lodash/_stackDelete.js.map +1 -1
  296. package/dist/node_modules/lodash/_stackGet.js.map +1 -1
  297. package/dist/node_modules/lodash/_stackHas.js.map +1 -1
  298. package/dist/node_modules/lodash/_stackSet.js.map +1 -1
  299. package/dist/node_modules/lodash/_strictIndexOf.js.map +1 -1
  300. package/dist/node_modules/lodash/_stringToArray.js.map +1 -1
  301. package/dist/node_modules/lodash/_stringToPath.js.map +1 -1
  302. package/dist/node_modules/lodash/_toKey.js.map +1 -1
  303. package/dist/node_modules/lodash/_toSource.js.map +1 -1
  304. package/dist/node_modules/lodash/_trimmedEndIndex.js.map +1 -1
  305. package/dist/node_modules/lodash/_unicodeToArray.js.map +1 -1
  306. package/dist/node_modules/lodash/constant.js.map +1 -1
  307. package/dist/node_modules/lodash/debounce.js.map +1 -1
  308. package/dist/node_modules/lodash/eq.js.map +1 -1
  309. package/dist/node_modules/lodash/every.js.map +1 -1
  310. package/dist/node_modules/lodash/find.js.map +1 -1
  311. package/dist/node_modules/lodash/findIndex.js.map +1 -1
  312. package/dist/node_modules/lodash/flatMap.js.map +1 -1
  313. package/dist/node_modules/lodash/get.js.map +1 -1
  314. package/dist/node_modules/lodash/hasIn.js.map +1 -1
  315. package/dist/node_modules/lodash/identity.js.map +1 -1
  316. package/dist/node_modules/lodash/isArguments.js.map +1 -1
  317. package/dist/node_modules/lodash/isArray.js.map +1 -1
  318. package/dist/node_modules/lodash/isArrayLike.js.map +1 -1
  319. package/dist/node_modules/lodash/isBoolean.js.map +1 -1
  320. package/dist/node_modules/lodash/isBuffer.js.map +1 -1
  321. package/dist/node_modules/lodash/isEqual.js.map +1 -1
  322. package/dist/node_modules/lodash/isFunction.js.map +1 -1
  323. package/dist/node_modules/lodash/isLength.js.map +1 -1
  324. package/dist/node_modules/lodash/isNaN.js.map +1 -1
  325. package/dist/node_modules/lodash/isNil.js.map +1 -1
  326. package/dist/node_modules/lodash/isNumber.js.map +1 -1
  327. package/dist/node_modules/lodash/isObject.js.map +1 -1
  328. package/dist/node_modules/lodash/isObjectLike.js.map +1 -1
  329. package/dist/node_modules/lodash/isPlainObject.js.map +1 -1
  330. package/dist/node_modules/lodash/isString.js.map +1 -1
  331. package/dist/node_modules/lodash/isSymbol.js.map +1 -1
  332. package/dist/node_modules/lodash/isTypedArray.js.map +1 -1
  333. package/dist/node_modules/lodash/keys.js.map +1 -1
  334. package/dist/node_modules/lodash/last.js.map +1 -1
  335. package/dist/node_modules/lodash/map.js.map +1 -1
  336. package/dist/node_modules/lodash/mapValues.js.map +1 -1
  337. package/dist/node_modules/lodash/max.js.map +1 -1
  338. package/dist/node_modules/lodash/memoize.js.map +1 -1
  339. package/dist/node_modules/lodash/min.js.map +1 -1
  340. package/dist/node_modules/lodash/noop.js.map +1 -1
  341. package/dist/node_modules/lodash/now.js.map +1 -1
  342. package/dist/node_modules/lodash/property.js.map +1 -1
  343. package/dist/node_modules/lodash/range.js.map +1 -1
  344. package/dist/node_modules/lodash/some.js.map +1 -1
  345. package/dist/node_modules/lodash/sortBy.js.map +1 -1
  346. package/dist/node_modules/lodash/stubArray.js.map +1 -1
  347. package/dist/node_modules/lodash/stubFalse.js.map +1 -1
  348. package/dist/node_modules/lodash/throttle.js.map +1 -1
  349. package/dist/node_modules/lodash/toFinite.js.map +1 -1
  350. package/dist/node_modules/lodash/toInteger.js.map +1 -1
  351. package/dist/node_modules/lodash/toNumber.js.map +1 -1
  352. package/dist/node_modules/lodash/toString.js.map +1 -1
  353. package/dist/node_modules/lodash/uniqBy.js.map +1 -1
  354. package/dist/node_modules/lodash/upperFirst.js.map +1 -1
  355. package/dist/node_modules/object-assign/index.js.map +1 -1
  356. package/dist/node_modules/prop-types/checkPropTypes.js.map +1 -1
  357. package/dist/node_modules/prop-types/factoryWithThrowingShims.js.map +1 -1
  358. package/dist/node_modules/prop-types/factoryWithTypeCheckers.js.map +1 -1
  359. package/dist/node_modules/prop-types/index.js.map +1 -1
  360. package/dist/node_modules/prop-types/lib/ReactPropTypesSecret.js.map +1 -1
  361. package/dist/node_modules/prop-types/lib/has.js.map +1 -1
  362. package/dist/node_modules/prop-types/node_modules/react-is/cjs/react-is.development.js.map +1 -1
  363. package/dist/node_modules/prop-types/node_modules/react-is/cjs/react-is.production.min.js.map +1 -1
  364. package/dist/node_modules/prop-types/node_modules/react-is/index.js.map +1 -1
  365. package/dist/node_modules/react-is/cjs/react-is.development.js.map +1 -1
  366. package/dist/node_modules/react-is/cjs/react-is.production.min.js.map +1 -1
  367. package/dist/node_modules/react-is/index.js.map +1 -1
  368. package/dist/node_modules/react-smooth/es6/Animate.js +2 -2
  369. package/dist/node_modules/react-smooth/es6/Animate.js.map +1 -1
  370. package/dist/node_modules/react-smooth/es6/AnimateManager.js.map +1 -1
  371. package/dist/node_modules/react-smooth/es6/configUpdate.js.map +1 -1
  372. package/dist/node_modules/react-smooth/es6/easing.js.map +1 -1
  373. package/dist/node_modules/react-smooth/es6/setRafTimeout.js.map +1 -1
  374. package/dist/node_modules/react-smooth/es6/util.js.map +1 -1
  375. package/dist/node_modules/recharts/es6/cartesian/Bar.js +9 -9
  376. package/dist/node_modules/recharts/es6/cartesian/Bar.js.map +1 -1
  377. package/dist/node_modules/recharts/es6/cartesian/Brush.js +12 -12
  378. package/dist/node_modules/recharts/es6/cartesian/Brush.js.map +1 -1
  379. package/dist/node_modules/recharts/es6/cartesian/CartesianAxis.js +7 -7
  380. package/dist/node_modules/recharts/es6/cartesian/CartesianAxis.js.map +1 -1
  381. package/dist/node_modules/recharts/es6/cartesian/CartesianGrid.js +13 -13
  382. package/dist/node_modules/recharts/es6/cartesian/CartesianGrid.js.map +1 -1
  383. package/dist/node_modules/recharts/es6/cartesian/ErrorBar.js +4 -4
  384. package/dist/node_modules/recharts/es6/cartesian/ErrorBar.js.map +1 -1
  385. package/dist/node_modules/recharts/es6/cartesian/ReferenceArea.js +3 -3
  386. package/dist/node_modules/recharts/es6/cartesian/ReferenceArea.js.map +1 -1
  387. package/dist/node_modules/recharts/es6/cartesian/ReferenceDot.js +3 -3
  388. package/dist/node_modules/recharts/es6/cartesian/ReferenceDot.js.map +1 -1
  389. package/dist/node_modules/recharts/es6/cartesian/ReferenceLine.js +4 -4
  390. package/dist/node_modules/recharts/es6/cartesian/ReferenceLine.js.map +1 -1
  391. package/dist/node_modules/recharts/es6/cartesian/XAxis.js +3 -3
  392. package/dist/node_modules/recharts/es6/cartesian/XAxis.js.map +1 -1
  393. package/dist/node_modules/recharts/es6/cartesian/YAxis.js +3 -3
  394. package/dist/node_modules/recharts/es6/cartesian/YAxis.js.map +1 -1
  395. package/dist/node_modules/recharts/es6/cartesian/getEquidistantTicks.js.map +1 -1
  396. package/dist/node_modules/recharts/es6/cartesian/getTicks.js.map +1 -1
  397. package/dist/node_modules/recharts/es6/chart/AccessibilityManager.js +1 -1
  398. package/dist/node_modules/recharts/es6/chart/AccessibilityManager.js.map +1 -1
  399. package/dist/node_modules/recharts/es6/chart/BarChart.js.map +1 -1
  400. package/dist/node_modules/recharts/es6/chart/generateCategoricalChart.js +22 -22
  401. package/dist/node_modules/recharts/es6/chart/generateCategoricalChart.js.map +1 -1
  402. package/dist/node_modules/recharts/es6/component/Cell.js.map +1 -1
  403. package/dist/node_modules/recharts/es6/component/Cursor.js +1 -1
  404. package/dist/node_modules/recharts/es6/component/Cursor.js.map +1 -1
  405. package/dist/node_modules/recharts/es6/component/DefaultLegendContent.js +11 -11
  406. package/dist/node_modules/recharts/es6/component/DefaultLegendContent.js.map +1 -1
  407. package/dist/node_modules/recharts/es6/component/DefaultTooltipContent.js +6 -6
  408. package/dist/node_modules/recharts/es6/component/DefaultTooltipContent.js.map +1 -1
  409. package/dist/node_modules/recharts/es6/component/Label.js +14 -14
  410. package/dist/node_modules/recharts/es6/component/Label.js.map +1 -1
  411. package/dist/node_modules/recharts/es6/component/LabelList.js +6 -6
  412. package/dist/node_modules/recharts/es6/component/LabelList.js.map +1 -1
  413. package/dist/node_modules/recharts/es6/component/Legend.js +5 -5
  414. package/dist/node_modules/recharts/es6/component/Legend.js.map +1 -1
  415. package/dist/node_modules/recharts/es6/component/ResponsiveContainer.js +3 -3
  416. package/dist/node_modules/recharts/es6/component/ResponsiveContainer.js.map +1 -1
  417. package/dist/node_modules/recharts/es6/component/Text.js +2 -2
  418. package/dist/node_modules/recharts/es6/component/Text.js.map +1 -1
  419. package/dist/node_modules/recharts/es6/component/Tooltip.js +3 -3
  420. package/dist/node_modules/recharts/es6/component/Tooltip.js.map +1 -1
  421. package/dist/node_modules/recharts/es6/component/TooltipBoundingBox.js +2 -2
  422. package/dist/node_modules/recharts/es6/component/TooltipBoundingBox.js.map +1 -1
  423. package/dist/node_modules/recharts/es6/container/Layer.js +2 -2
  424. package/dist/node_modules/recharts/es6/container/Layer.js.map +1 -1
  425. package/dist/node_modules/recharts/es6/container/Surface.js +2 -2
  426. package/dist/node_modules/recharts/es6/container/Surface.js.map +1 -1
  427. package/dist/node_modules/recharts/es6/context/chartLayoutContext.js +2 -2
  428. package/dist/node_modules/recharts/es6/context/chartLayoutContext.js.map +1 -1
  429. package/dist/node_modules/recharts/es6/shape/Cross.js +1 -1
  430. package/dist/node_modules/recharts/es6/shape/Cross.js.map +1 -1
  431. package/dist/node_modules/recharts/es6/shape/Curve.js +1 -1
  432. package/dist/node_modules/recharts/es6/shape/Curve.js.map +1 -1
  433. package/dist/node_modules/recharts/es6/shape/Dot.js +1 -1
  434. package/dist/node_modules/recharts/es6/shape/Dot.js.map +1 -1
  435. package/dist/node_modules/recharts/es6/shape/Rectangle.js +4 -4
  436. package/dist/node_modules/recharts/es6/shape/Rectangle.js.map +1 -1
  437. package/dist/node_modules/recharts/es6/shape/Sector.js +1 -1
  438. package/dist/node_modules/recharts/es6/shape/Sector.js.map +1 -1
  439. package/dist/node_modules/recharts/es6/shape/Symbols.js +1 -1
  440. package/dist/node_modules/recharts/es6/shape/Symbols.js.map +1 -1
  441. package/dist/node_modules/recharts/es6/shape/Trapezoid.js +4 -4
  442. package/dist/node_modules/recharts/es6/shape/Trapezoid.js.map +1 -1
  443. package/dist/node_modules/recharts/es6/util/ActiveShapeUtils.js +9 -9
  444. package/dist/node_modules/recharts/es6/util/ActiveShapeUtils.js.map +1 -1
  445. package/dist/node_modules/recharts/es6/util/BarUtils.js +1 -1
  446. package/dist/node_modules/recharts/es6/util/BarUtils.js.map +1 -1
  447. package/dist/node_modules/recharts/es6/util/CartesianUtils.js +1 -1
  448. package/dist/node_modules/recharts/es6/util/CartesianUtils.js.map +1 -1
  449. package/dist/node_modules/recharts/es6/util/ChartUtils.js.map +1 -1
  450. package/dist/node_modules/recharts/es6/util/CssPrefixUtils.js.map +1 -1
  451. package/dist/node_modules/recharts/es6/util/DOMUtils.js.map +1 -1
  452. package/dist/node_modules/recharts/es6/util/DataUtils.js.map +1 -1
  453. package/dist/node_modules/recharts/es6/util/DetectReferenceElementsDomain.js.map +1 -1
  454. package/dist/node_modules/recharts/es6/util/Events.js.map +1 -1
  455. package/dist/node_modules/recharts/es6/util/Global.js.map +1 -1
  456. package/dist/node_modules/recharts/es6/util/IfOverflowMatches.js.map +1 -1
  457. package/dist/node_modules/recharts/es6/util/LogUtils.js.map +1 -1
  458. package/dist/node_modules/recharts/es6/util/PolarUtils.js.map +1 -1
  459. package/dist/node_modules/recharts/es6/util/ReactUtils.js +1 -1
  460. package/dist/node_modules/recharts/es6/util/ReactUtils.js.map +1 -1
  461. package/dist/node_modules/recharts/es6/util/ReduceCSSCalc.js +1 -1
  462. package/dist/node_modules/recharts/es6/util/ReduceCSSCalc.js.map +1 -1
  463. package/dist/node_modules/recharts/es6/util/ShallowEqual.js.map +1 -1
  464. package/dist/node_modules/recharts/es6/util/TickUtils.js.map +1 -1
  465. package/dist/node_modules/recharts/es6/util/calculateViewBox.js.map +1 -1
  466. package/dist/node_modules/recharts/es6/util/cursor/getCursorPoints.js.map +1 -1
  467. package/dist/node_modules/recharts/es6/util/cursor/getCursorRectangle.js.map +1 -1
  468. package/dist/node_modules/recharts/es6/util/cursor/getRadialCursorPoints.js.map +1 -1
  469. package/dist/node_modules/recharts/es6/util/getEveryNthWithCondition.js.map +1 -1
  470. package/dist/node_modules/recharts/es6/util/getLegendProps.js.map +1 -1
  471. package/dist/node_modules/recharts/es6/util/isDomainSpecifiedByUser.js.map +1 -1
  472. package/dist/node_modules/recharts/es6/util/payload/getUniqPayload.js.map +1 -1
  473. package/dist/node_modules/recharts/es6/util/tooltip/translate.js.map +1 -1
  474. package/dist/node_modules/recharts/es6/util/types.js +1 -1
  475. package/dist/node_modules/recharts/es6/util/types.js.map +1 -1
  476. package/dist/node_modules/recharts-scale/es6/getNiceTickValues.js.map +1 -1
  477. package/dist/node_modules/recharts-scale/es6/util/arithmetic.js.map +1 -1
  478. package/dist/node_modules/recharts-scale/es6/util/utils.js.map +1 -1
  479. package/dist/node_modules/tailwind-merge/dist/bundle-mjs.js +1 -1
  480. package/dist/node_modules/tailwind-merge/dist/bundle-mjs.js.map +1 -1
  481. package/dist/node_modules/tiny-invariant/dist/esm/tiny-invariant.js.map +1 -1
  482. package/package.json +2 -2
@@ -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-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 +1 @@
1
- {"version":3,"file":"ReportsPage.js","names":[],"sources":["../../../../src/ecoimpacthub/pages/reports/ReportsPage.tsx"],"sourcesContent":["/**\n * ESG Reports — list, filter, and draft sustainability disclosures.\n * @module ecoimpacthub/pages/reports\n *\n * Reads `ecoListReports`; writes `ecoCreateReport`.\n */\n\nimport { useMemo, useState } from 'react';\nimport { Plus, FileText } 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 { StatusBadge } from '../../components/StatusBadge';\nimport { DataTable, type Column } from '../../components/DataTable';\nimport { FilterTabs, type FilterOption } from '../../components/FilterTabs';\nimport { FrameworkBadge } from '../badges';\nimport { LoadingView, ErrorView } from '../../components/StateViews';\nimport { EmptyState } from '../../components/EmptyState';\nimport { Pagination } from '../../components/Pagination';\nimport { useEcoImpactHub } from '../../context';\nimport { useEcoReports, useEcoCreateReport, useCursorPager } from '../../hooks';\nimport { formatDate, formatEnumLabel, formatRelativeTime } from '../../utils/formatters';\nimport type { EcoReport, EcoReportStatus, EcoReportFramework } from '../../types';\n\nconst STATUS_FILTERS: FilterOption<EcoReportStatus | 'ALL'>[] = [\n { value: 'ALL', label: 'All' },\n { value: 'DRAFT', label: 'Draft' },\n { value: 'IN_REVIEW', label: 'In review' },\n { value: 'PUBLISHED', label: 'Published' },\n { value: 'ARCHIVED', label: 'Archived' },\n];\n\nconst FRAMEWORKS: EcoReportFramework[] = [\n 'GHG_PROTOCOL',\n 'GRI',\n 'TCFD',\n 'CDP',\n 'SASB',\n 'CSRD',\n 'ISSB',\n 'EU_TAXONOMY',\n 'CUSTOM',\n];\n\nconst PAGE_SIZE = 25;\n\ninterface CreateFormState {\n title: string;\n framework: EcoReportFramework;\n periodStart: string;\n periodEnd: string;\n summary: string;\n}\n\nconst EMPTY_FORM: CreateFormState = {\n title: '',\n framework: 'GHG_PROTOCOL',\n periodStart: '',\n periodEnd: '',\n summary: '',\n};\n\nexport function ReportsPage() {\n const { navigateTo } = useEcoImpactHub();\n const [status, setStatus] = useState<EcoReportStatus | 'ALL'>('ALL');\n const pager = useCursorPager();\n const [dialogOpen, setDialogOpen] = useState(false);\n const [form, setForm] = useState<CreateFormState>(EMPTY_FORM);\n const [formError, setFormError] = useState<string | null>(null);\n\n const { data, isLoading, isError, refetch } = useEcoReports(\n status === 'ALL' ? undefined : status,\n { limit: PAGE_SIZE, cursor: pager.cursor }\n );\n const createReport = useEcoCreateReport();\n\n const rows = useMemo(() => data?.items ?? [], [data]);\n\n const columns: Column<EcoReport>[] = [\n {\n key: 'title',\n header: 'Report',\n render: (r) => <p className=\"font-semibold text-text-primary\">{r.title}</p>,\n },\n {\n key: 'framework',\n header: 'Framework',\n hideOnMobile: true,\n render: (r) => <FrameworkBadge framework={r.framework} />,\n },\n {\n key: 'status',\n header: 'Status',\n render: (r) => <StatusBadge status={r.status} />,\n },\n {\n key: 'period',\n header: 'Period',\n hideOnMobile: true,\n render: (r) => (\n <span className=\"whitespace-nowrap text-text-secondary tabular-nums\">\n {formatDate(r.periodStart)} – {formatDate(r.periodEnd)}\n </span>\n ),\n },\n {\n key: 'published',\n header: 'Published',\n hideOnMobile: true,\n render: (r) =>\n r.publishedAt ? (\n <span className=\"whitespace-nowrap text-text-secondary\">{formatDate(r.publishedAt)}</span>\n ) : (\n <span className=\"text-text-tertiary\">—</span>\n ),\n },\n {\n key: 'updated',\n header: 'Updated',\n align: 'right',\n hideOnMobile: true,\n render: (r) => <span className=\"text-text-tertiary\">{formatRelativeTime(r.updatedAt)}</span>,\n },\n ];\n\n const resetForm = () => {\n setForm(EMPTY_FORM);\n setFormError(null);\n };\n\n const handleCreate = async () => {\n if (!form.title.trim()) {\n setFormError('Report title is required.');\n return;\n }\n if (!form.periodStart || !form.periodEnd) {\n setFormError('Reporting period start and end are required.');\n return;\n }\n try {\n const created = await createReport.mutateAsync({\n title: form.title.trim(),\n framework: form.framework,\n periodStart: form.periodStart,\n periodEnd: form.periodEnd,\n summary: form.summary.trim() || null,\n });\n setDialogOpen(false);\n resetForm();\n navigateTo(`reports/${created.id}`);\n } catch (err) {\n setFormError(err instanceof Error ? err.message : 'Failed to create report.');\n }\n };\n\n return (\n <PageLayout\n title=\"ESG Reports\"\n description=\"Sustainability disclosures — draft, review & publish GHG Protocol, GRI, TCFD, CDP & SASB reports.\"\n actions={\n <Button onClick={() => setDialogOpen(true)}>\n <Plus className=\"mr-1.5 h-4 w-4\" /> New report\n </Button>\n }\n >\n <div className=\"mb-5 flex flex-wrap items-center gap-3\">\n <FilterTabs\n options={STATUS_FILTERS}\n value={status}\n onChange={(v) => {\n setStatus(v);\n pager.reset();\n }}\n />\n </div>\n\n {isLoading ? (\n <LoadingView label=\"Loading reports…\" />\n ) : isError ? (\n <ErrorView message=\"Couldn't load reports.\" onRetry={() => refetch()} />\n ) : rows.length === 0 ? (\n <EmptyState\n illustration=\"empty-data\"\n icon={<FileText className=\"h-10 w-10\" />}\n title=\"No reports yet\"\n description={\n status === 'ALL'\n ? 'Draft your first ESG report to compile disclosures against a reporting framework.'\n : 'No reports match this filter.'\n }\n action={\n <Button onClick={() => setDialogOpen(true)}>\n <Plus className=\"mr-1.5 h-4 w-4\" /> New report\n </Button>\n }\n />\n ) : (\n <>\n <DataTable\n columns={columns}\n rows={rows}\n rowKey={(r) => r.id}\n onRowClick={(r) => navigateTo(`reports/${r.id}`)}\n />\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>New report</ResponsiveDialogTitle>\n <ResponsiveDialogDescription>\n Start a sustainability disclosure for a reporting period and framework.\n </ResponsiveDialogDescription>\n </ResponsiveDialogHeader>\n\n <div className=\"grid gap-4 py-2\">\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"title\">Title *</Label>\n <Input\n id=\"title\"\n value={form.title}\n onChange={(e) => setForm({ ...form, title: e.target.value })}\n placeholder=\"FY2026 Sustainability Report\"\n />\n </div>\n\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"framework\">Framework</Label>\n <Select\n value={form.framework}\n onValueChange={(v) => setForm({ ...form, framework: v as EcoReportFramework })}\n >\n <SelectTrigger id=\"framework\">\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n {FRAMEWORKS.map((f) => (\n <SelectItem key={f} value={f}>\n {formatEnumLabel(f)}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </div>\n\n <div className=\"grid gap-2 sm:grid-cols-2\">\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"periodStart\">Period start *</Label>\n <Input\n id=\"periodStart\"\n type=\"date\"\n value={form.periodStart}\n onChange={(e) => setForm({ ...form, periodStart: e.target.value })}\n />\n </div>\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"periodEnd\">Period end *</Label>\n <Input\n id=\"periodEnd\"\n type=\"date\"\n value={form.periodEnd}\n onChange={(e) => setForm({ ...form, periodEnd: e.target.value })}\n />\n </div>\n </div>\n\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"summary\">Summary</Label>\n <Textarea\n id=\"summary\"\n value={form.summary}\n onChange={(e) => setForm({ ...form, summary: e.target.value })}\n placeholder=\"Optional executive summary or scope note for this report.\"\n rows={3}\n />\n </div>\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={handleCreate} disabled={createReport.isPending}>\n {createReport.isPending ? 'Creating…' : 'Create report'}\n </Button>\n </ResponsiveDialogFooter>\n </ResponsiveDialogContent>\n </ResponsiveDialog>\n </PageLayout>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"ReportsPage.js","names":[],"sources":["../../../../src/ecoimpacthub/pages/reports/ReportsPage.tsx"],"sourcesContent":["/**\n * ESG Reports — list, filter, and draft sustainability disclosures.\n * @module ecoimpacthub/pages/reports\n *\n * Reads `ecoListReports`; writes `ecoCreateReport`.\n */\n\nimport { useMemo, useState } from 'react';\nimport { Plus, FileText } 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 { StatusBadge } from '../../components/StatusBadge';\nimport { DataTable, type Column } from '../../components/DataTable';\nimport { FilterTabs, type FilterOption } from '../../components/FilterTabs';\nimport { FrameworkBadge } from '../badges';\nimport { LoadingView, ErrorView } from '../../components/StateViews';\nimport { EmptyState } from '../../components/EmptyState';\nimport { Pagination } from '../../components/Pagination';\nimport { useEcoImpactHub } from '../../context';\nimport { useEcoReports, useEcoCreateReport, useCursorPager } from '../../hooks';\nimport { formatDate, formatEnumLabel, formatRelativeTime } from '../../utils/formatters';\nimport type { EcoReport, EcoReportStatus, EcoReportFramework } from '../../types';\n\nconst STATUS_FILTERS: FilterOption<EcoReportStatus | 'ALL'>[] = [\n { value: 'ALL', label: 'All' },\n { value: 'DRAFT', label: 'Draft' },\n { value: 'IN_REVIEW', label: 'In review' },\n { value: 'PUBLISHED', label: 'Published' },\n { value: 'ARCHIVED', label: 'Archived' },\n];\n\nconst FRAMEWORKS: EcoReportFramework[] = [\n 'GHG_PROTOCOL',\n 'GRI',\n 'TCFD',\n 'CDP',\n 'SASB',\n 'CSRD',\n 'ISSB',\n 'EU_TAXONOMY',\n 'CUSTOM',\n];\n\nconst PAGE_SIZE = 25;\n\ninterface CreateFormState {\n title: string;\n framework: EcoReportFramework;\n periodStart: string;\n periodEnd: string;\n summary: string;\n}\n\nconst EMPTY_FORM: CreateFormState = {\n title: '',\n framework: 'GHG_PROTOCOL',\n periodStart: '',\n periodEnd: '',\n summary: '',\n};\n\nexport function ReportsPage() {\n const { navigateTo } = useEcoImpactHub();\n const [status, setStatus] = useState<EcoReportStatus | 'ALL'>('ALL');\n const pager = useCursorPager();\n const [dialogOpen, setDialogOpen] = useState(false);\n const [form, setForm] = useState<CreateFormState>(EMPTY_FORM);\n const [formError, setFormError] = useState<string | null>(null);\n\n const { data, isLoading, isError, refetch } = useEcoReports(\n status === 'ALL' ? undefined : status,\n { limit: PAGE_SIZE, cursor: pager.cursor }\n );\n const createReport = useEcoCreateReport();\n\n const rows = useMemo(() => data?.items ?? [], [data]);\n\n const columns: Column<EcoReport>[] = [\n {\n key: 'title',\n header: 'Report',\n render: (r) => <p className=\"font-semibold text-text-primary\">{r.title}</p>,\n },\n {\n key: 'framework',\n header: 'Framework',\n hideOnMobile: true,\n render: (r) => <FrameworkBadge framework={r.framework} />,\n },\n {\n key: 'status',\n header: 'Status',\n render: (r) => <StatusBadge status={r.status} />,\n },\n {\n key: 'period',\n header: 'Period',\n hideOnMobile: true,\n render: (r) => (\n <span className=\"whitespace-nowrap text-text-secondary tabular-nums\">\n {formatDate(r.periodStart)} – {formatDate(r.periodEnd)}\n </span>\n ),\n },\n {\n key: 'published',\n header: 'Published',\n hideOnMobile: true,\n render: (r) =>\n r.publishedAt ? (\n <span className=\"whitespace-nowrap text-text-secondary\">{formatDate(r.publishedAt)}</span>\n ) : (\n <span className=\"text-text-tertiary\">—</span>\n ),\n },\n {\n key: 'updated',\n header: 'Updated',\n align: 'right',\n hideOnMobile: true,\n render: (r) => <span className=\"text-text-tertiary\">{formatRelativeTime(r.updatedAt)}</span>,\n },\n ];\n\n const resetForm = () => {\n setForm(EMPTY_FORM);\n setFormError(null);\n };\n\n const handleCreate = async () => {\n if (!form.title.trim()) {\n setFormError('Report title is required.');\n return;\n }\n if (!form.periodStart || !form.periodEnd) {\n setFormError('Reporting period start and end are required.');\n return;\n }\n try {\n const created = await createReport.mutateAsync({\n title: form.title.trim(),\n framework: form.framework,\n periodStart: form.periodStart,\n periodEnd: form.periodEnd,\n summary: form.summary.trim() || null,\n });\n setDialogOpen(false);\n resetForm();\n navigateTo(`reports/${created.id}`);\n } catch (err) {\n setFormError(err instanceof Error ? err.message : 'Failed to create report.');\n }\n };\n\n return (\n <PageLayout\n title=\"ESG Reports\"\n description=\"Sustainability disclosures — draft, review & publish GHG Protocol, GRI, TCFD, CDP & SASB reports.\"\n actions={\n <Button onClick={() => setDialogOpen(true)}>\n <Plus className=\"mr-1.5 h-4 w-4\" /> New report\n </Button>\n }\n >\n <div className=\"mb-5 flex flex-wrap items-center gap-3\">\n <FilterTabs\n options={STATUS_FILTERS}\n value={status}\n onChange={(v) => {\n setStatus(v);\n pager.reset();\n }}\n />\n </div>\n\n {isLoading ? (\n <LoadingView label=\"Loading reports…\" />\n ) : isError ? (\n <ErrorView message=\"Couldn't load reports.\" onRetry={() => refetch()} />\n ) : rows.length === 0 ? (\n <EmptyState\n illustration=\"empty-data\"\n icon={<FileText className=\"h-10 w-10\" />}\n title=\"No reports yet\"\n description={\n status === 'ALL'\n ? 'Draft your first ESG report to compile disclosures against a reporting framework.'\n : 'No reports match this filter.'\n }\n action={\n <Button onClick={() => setDialogOpen(true)}>\n <Plus className=\"mr-1.5 h-4 w-4\" /> New report\n </Button>\n }\n />\n ) : (\n <>\n <DataTable\n columns={columns}\n rows={rows}\n rowKey={(r) => r.id}\n onRowClick={(r) => navigateTo(`reports/${r.id}`)}\n />\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>New report</ResponsiveDialogTitle>\n <ResponsiveDialogDescription>\n Start a sustainability disclosure for a reporting period and framework.\n </ResponsiveDialogDescription>\n </ResponsiveDialogHeader>\n\n <div className=\"grid gap-4 py-2\">\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"title\">Title *</Label>\n <Input\n id=\"title\"\n value={form.title}\n onChange={(e) => setForm({ ...form, title: e.target.value })}\n placeholder=\"FY2026 Sustainability Report\"\n />\n </div>\n\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"framework\">Framework</Label>\n <Select\n value={form.framework}\n onValueChange={(v) => setForm({ ...form, framework: v as EcoReportFramework })}\n >\n <SelectTrigger id=\"framework\">\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n {FRAMEWORKS.map((f) => (\n <SelectItem key={f} value={f}>\n {formatEnumLabel(f)}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </div>\n\n <div className=\"grid gap-2 sm:grid-cols-2\">\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"periodStart\">Period start *</Label>\n <Input\n id=\"periodStart\"\n type=\"date\"\n value={form.periodStart}\n onChange={(e) => setForm({ ...form, periodStart: e.target.value })}\n />\n </div>\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"periodEnd\">Period end *</Label>\n <Input\n id=\"periodEnd\"\n type=\"date\"\n value={form.periodEnd}\n onChange={(e) => setForm({ ...form, periodEnd: e.target.value })}\n />\n </div>\n </div>\n\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"summary\">Summary</Label>\n <Textarea\n id=\"summary\"\n value={form.summary}\n onChange={(e) => setForm({ ...form, summary: e.target.value })}\n placeholder=\"Optional executive summary or scope note for this report.\"\n rows={3}\n />\n </div>\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={handleCreate} disabled={createReport.isPending}>\n {createReport.isPending ? 'Creating…' : 'Create report'}\n </Button>\n </ResponsiveDialogFooter>\n </ResponsiveDialogContent>\n </ResponsiveDialog>\n </PageLayout>\n );\n}\n"],"mappings":"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"}
@@ -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-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":";;;;;;;;;;;;;;;;;;;AAiEA,IAAM,KAAgC;CACpC;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;AACF,GACM,KAAqC;CACzC;CACA;CACA;CACA;CACA;AACF,GACM,KAAgD;CAAC;CAAS;CAAS;AAAe,GAClF,KAA+C;CAAC;CAAQ;CAAW;AAAU;AAEnF,SAAgB,IAAiB;CAC/B,IAAM,EAAE,UAAO,GAA0B,GACnC,EAAE,kBAAe,EAAgB,GACjC,EAAE,MAAM,GAAM,eAAW,aAAS,gBAAY,GAAW,CAAE,GAC3D,IAAe,EAAmB;EAAE,QAAQ;EAAI,YAAY;GAAE,OAAO;GAAI,QAAQ;EAAE;CAAE,CAAC,GACtF,KAAe,EAAmB;EACtC,QAAQ;EACR,cAAc;EACd,YAAY;GAAE,OAAO;GAAG,QAAQ;EAAE;CACpC,CAAC,GAEK,IAAe,GAAmB,GAClC,KAAkB,GAAsB,GACxC,IAAkB,GAAsB,GACxC,KAAqB,GAAyB,GAC9C,IAAoB,GAAwB,GAC5C,IAAc,GAAkB,GAEhC,CAAC,IAAY,KAAiB,EAAS,EAAK,GAC5C,CAAC,GAAY,KAAiB,EAAS;EAC3C,MAAM;EACN,MAAM;EACN,MAAM;EACN,YAAY;CACd,CAAC,GACK,CAAC,IAAe,KAAoB,EAAS,EAAK,GAClD,CAAC,GAAe,KAAoB,EAAS;EACjD,WAAW;EACX,MAAM;EACN,WAAW;EACX,UAAU;EACV,YAAY;EACZ,UAAU;EACV,UAAU;CACZ,CAAC,GACK,CAAC,IAAa,KAAkB,EAAS,EAAK,GAC9C,CAAC,GAAa,KAAkB,EAAS;EAC7C,WAAW;EACX,OAAO;EACP,MAAM;EACN,UAAU;CACZ,CAAC,GACK,CAAC,GAAO,KAAY,EAAwB,IAAI;CAEtD,IAAI,IACF,OACE,kBAAC,GAAD;EAAY,OAAM;YAChB,kBAAC,IAAD,CAAc,CAAA;CACJ,CAAA;CAGhB,IAAI,MAAW,CAAC,GACd,OACE,kBAAC,GAAD;EAAY,OAAM;YAChB,kBAAC,IAAD;GAAW,SAAQ;GAAiC,eAAe,GAAQ;EAAI,CAAA;CACrE,CAAA;CAIhB,IAAM,IAAU,EAAK,WAAW,CAAC,GAC3B,IAAa,EAAK,cAAc,CAAC,GACjC,IAAkB,EAAa,MAAM,SAAS,CAAC,GAG/C,IAAgB,EAAQ,QAAQ,MAAM,EAAE,WAAW,QAAQ,EAAE,QAC7D,IAAmB,EAAQ,QAAQ,MACvC;EAAC;EAAW;EAAY;CAAa,EAAE,SAAS,EAAE,MAAM,CAC1D,EAAE,QACI,KAAmB,EAAW,QAAQ,MAAM,EAAE,QAAQ,EAAE,QACxD,IAAkB,GAAa,MAAM,QAAQ,MAAM;CA+MzD,OACE,kBAAC,GAAD;EACE,OAAO,EAAK;EACZ,aAAa,CAAC,EAAK,MAAM,EAAgB,EAAK,IAAI,CAAC,EAAE,OAAO,OAAO,EAAE,KAAK,KAAK;EAC/E,SACE,kBAAA,IAAA,EAAA,UAAA,CACE,kBAAC,GAAD,EAAa,QAAQ,EAAK,OAAS,CAAA,GACnC,kBAAC,GAAD;GAAQ,SAAQ;GAAQ,eAAe,EAAW,OAAO;aAAzD,CACE,kBAAC,IAAD,EAAW,WAAU,iBAAkB,CAAA,GAAC,OAClC;IACR,EAAA,CAAA;YATN;GAaE,kBAAC,OAAD;IAAK,WAAU;cAAf,CAEE,kBAAC,GAAD;KAAW,WAAU;KAAQ,WAAU;eAAvC;MACE,kBAAC,MAAD;OAAI,WAAU;iBAAd;QACE,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,MAAD;SAAI,WAAU;mBAA6B;QAAQ,CAAA,GACnD,kBAAC,MAAD;SAAI,WAAU;mBACZ,kBAAC,IAAD,EAAe,MAAM,EAAK,KAAO,CAAA;QAC/B,CAAA,CACD,EAAA,CAAA;QACL,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,MAAD;SAAI,WAAU;mBAA6B;QAAU,CAAA,GACrD,kBAAC,MAAD;SAAI,WAAU;mBACX,CAAC,EAAK,QAAQ,EAAK,OAAO,EAAE,OAAO,OAAO,EAAE,KAAK,IAAI,KAAK;QACzD,CAAA,CACD,EAAA,CAAA;QACL,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,MAAD;SAAI,WAAU;mBAA6B;QAAQ,CAAA,GACnD,kBAAC,MAAD;SAAI,WAAU;mBACX,EAAK,gBAAgB,OAAiD,MAA1C,GAAG,EAAa,EAAK,YAAY,EAAE;QAC9D,CAAA,CACD,EAAA,CAAA;QACL,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,MAAD;SAAI,WAAU;mBAA6B;QAAe,CAAA,GAC1D,kBAAC,MAAD;SAAI,WAAU;mBACX,GAAa,EAAK,UAAU,EAAK,SAAS;QACzC,CAAA,CACD,EAAA,CAAA;QACL,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,MAAD;SAAI,WAAU;mBAA6B;QAAW,CAAA,GACtD,kBAAC,MAAD;SAAI,WAAU;mBAAiD,EAAQ;QAAW,CAAA,CAC/E,EAAA,CAAA;QACL,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,MAAD;SAAI,WAAU;mBAA6B;QAAc,CAAA,GACzD,kBAAC,MAAD;SAAI,WAAU;mBAAiD,EAAW;QAAW,CAAA,CAClF,EAAA,CAAA;OACH;;MACH,EAAK,eACJ,kBAAC,KAAD;OAAG,WAAU;iBACV,EAAK;MACL,CAAA;MAEL,kBAAC,OAAD;OAAK,WAAU;iBACb,kBAAC,IAAD;QAAS,YAAW;QAAoB,UAAU,EAAK;OAAK,CAAA;MACzD,CAAA;KACI;QAGX,kBAAC,OAAD;KAAK,WAAU;eAAf,CACE,kBAAC,GAAD;MAAW,WAAU;MAAQ,WAAU;gBAAvC,CACE,kBAAC,OAAD;OAAK,WAAU;iBAAf,CACE,kBAAC,IAAD,EAAK,WAAU,sBAAuB,CAAA,GACtC,kBAAC,MAAD;QAAI,WAAU;kBAA4C;OAAiB,CAAA,CACxE;UACL,kBAAC,OAAD;OAAK,WAAU;iBAAf;QACE,kBAAC,GAAD;SAAQ,eAAe,EAAe,EAAI;mBAA1C,CACE,kBAAC,IAAD,EAAU,WAAU,iBAAkB,CAAA,GAAC,qBACjC;;QACR,kBAAC,GAAD;SAAQ,SAAQ;SAAU,eAAe,EAAc,EAAI;mBAA3D,CACE,kBAAC,GAAD,EAAO,WAAU,iBAAkB,CAAA,GAAC,aAC9B;;QACR,kBAAC,GAAD;SAAQ,SAAQ;SAAU,eAAe,EAAiB,EAAI;mBAA9D,CACE,kBAAC,IAAD,EAAO,WAAU,iBAAkB,CAAA,GAAC,gBAC9B;;QACP,EAAK,WAAW,cACf,kBAAC,GAAD;SACE,SAAQ;SACR,SAAS,YAjNS;UAC3B,OACL;aAAS,IAAI;WACb,IAAI;YAEF,AADA,MAAM,EAAY,YAAY,CAAE,GAChC,EAAW,OAAO;WACpB,SAAS,GAAK;YACZ,EAAS,aAAe,QAAQ,EAAI,UAAU,yBAAyB;WACzE;UANa;SAOf;SAyMgB,UAAU,EAAY;SACtB,WAAU;mBAJZ,CAME,kBAAC,IAAD,EAAS,WAAU,iBAAkB,CAAA,GAAC,eAChC;;OAEP;QACI;SAEX,kBAAC,GAAD;MAAW,WAAU;MAAQ,WAAU;gBAAvC;OACE,kBAAC,OAAD;QAAK,WAAU;kBAAf,CACE,kBAAC,GAAD,EAAO,WAAU,sBAAuB,CAAA,GACxC,kBAAC,MAAD;SAAI,WAAU;mBAA4C;QAAsB,CAAA,CAC7E;;OACL,kBAAC,OAAD;QAAK,WAAU;kBAAf,CACE,kBAAC,OAAD;SAAK,WAAU;mBAAf;UACE,kBAAC,KAAD;WAAG,WAAU;qBAAb,CACG,GACD,kBAAC,QAAD;YAAM,WAAU;sBAAhB,CAA+C,KAAE,EAAQ,MAAa;aACrE;;UACH,kBAAC,KAAD;WAAG,WAAU;qBAAqC;UAAiB,CAAA;UACnE,kBAAC,IAAD;WACE,WAAU;WACV,OAAO,EAAQ,SAAU,IAAgB,EAAQ,SAAU,MAAM;WACjE,cAAa;UACd,CAAA;SACE;YACL,kBAAC,OAAD;SAAK,WAAU;mBAAf;UACE,kBAAC,KAAD;WAAG,WAAU;qBAAb,CACG,IACD,kBAAC,QAAD;YAAM,WAAU;sBAAhB,CAA+C,KAAE,EAAW,MAAa;aACxE;;UACH,kBAAC,KAAD;WAAG,WAAU;qBAAqC;UAAoB,CAAA;UACtE,kBAAC,KAAD;WACE,WACE,IAAmB,IACf,0CACA;qBAGL,IAAmB,IAChB,GAAG,EAAiB,SAAS,IAAmB,IAAI,MAAM,GAAG,mBAC7D;UACH,CAAA;SACA;UACF;;OACL,kBAAC,OAAD;QAAK,WAAU;kBAAf,CACE,kBAAC,KAAD;SAAG,WAAU;mBAAkC;QAAgB,CAAA,GAC9D,IACC,kBAAC,OAAD;SAAK,WAAU;mBAAf,CACE,kBAAC,OAAD;UAAK,WAAU;oBAAf,CACE,kBAAC,KAAD;WAAG,WAAU;qBACV,EAAgB;UAChB,CAAA,GACH,kBAAC,KAAD;WAAG,WAAU;qBAAb;YACG,EAAa,EAAgB,KAAK;YAAE;YAAE,EAAgB;YAAK;YAAG;YAC9D,GAAmB,EAAgB,UAAU;WAC7C;YACA;aACL,kBAAC,GAAD;UAAa,QAAO;UAAS,WAAU;SAAsB,CAAA,CAC1D;aAEL,kBAAC,OAAD;SAAK,WAAU;mBAAf,CACE,kBAAC,IAAD,EAAa,WAAU,4CAA6C,CAAA,GAAC,sBAElE;UAEJ;;MACI;OACR;MACF;;GAEJ,KAAS,kBAAC,KAAD;IAAG,WAAU;cAAuC;GAAS,CAAA;GAGvE,kBAAC,GAAD;IACE,OAAM;IACN,aAAY;IACZ,QACE,kBAAC,GAAD;KAAQ,MAAK;KAAK,eAAe,EAAc,EAAI;eAAnD,CACE,kBAAC,GAAD,EAAM,WAAU,iBAAkB,CAAA,GAAC,aAC7B;;cAGT,EAAQ,WAAW,IAClB,kBAAC,GAAD;KACE,cAAa;KACb,OAAM;KACN,aAAY;IACb,CAAA,IAED,kBAAC,GAAD;KAAW,SAAS;MAjS1B;OACE,KAAK;OACL,QAAQ;OACR,SAAS,MACP,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,KAAD;QAAG,WAAU;kBAAiC,EAAE;OAAQ,CAAA,GACxD,kBAAC,KAAD;QAAG,WAAU;kBAA8B,EAAgB,EAAE,IAAI;OAAK,CAAA,CACnE,EAAA,CAAA;MAET;MACA;OACE,KAAK;OACL,QAAQ;OACR,cAAc;OACd,SAAS,MAAM,kBAAC,QAAD;QAAM,WAAU;kBAAuB,EAAE,QAAQ;OAAU,CAAA;MAC5E;MACA;OAAE,KAAK;OAAU,QAAQ;OAAU,SAAS,MAAM,kBAAC,GAAD,EAAa,QAAQ,EAAE,OAAS,CAAA;MAAE;MACpF;OACE,KAAK;OACL,QAAQ;OACR,OAAO;OACP,SAAS,MACP,kBAAC,GAAD;QACE,OAAO,EAAE;QACT,gBAAgB,MAAM,GAAgB,OAAO;SAAE,IAAI,EAAE;SAAI,QAAQ;QAAqB,CAAC;kBAFzF,CAIE,kBAAC,GAAD;SAAe,WAAU;mBACvB,kBAAC,GAAD,CAAc,CAAA;QACD,CAAA,GACf,kBAAC,GAAD,EAAA,UACG,GAAgB,KAAK,MACpB,kBAAC,GAAD;SAAqB,OAAO;mBACzB,EAAgB,CAAE;QACT,GAFK,CAEL,CACb,EACY,CAAA,CACT;;MAEZ;KA2P0B;KAAe,MAAM;KAAS,SAAS,MAAM,EAAE;IAAK,CAAA;GAEnE,CAAA;GAGT,kBAAC,GAAD;IACE,OAAM;IACN,aAAY;IACZ,QACE,kBAAC,GAAD;KAAQ,MAAK;KAAK,eAAe,EAAiB,EAAI;eAAtD,CACE,kBAAC,GAAD,EAAM,WAAU,iBAAkB,CAAA,GAAC,gBAC7B;;cAGT,EAAW,WAAW,IACrB,kBAAC,GAAD;KACE,cAAa;KACb,OAAM;KACN,aAAY;IACb,CAAA,IAED,kBAAC,GAAD;KAAW,SAAS;MA5Q1B;OACE,KAAK;OACL,QAAQ;OACR,SAAS,MAAM,kBAAC,QAAD;QAAM,WAAU;kBAAiC,EAAE;OAAgB,CAAA;MACpF;MACA;OACE,KAAK;OACL,QAAQ;OACR,SAAS,MACP,kBAAC,QAAD;QAAM,WAAU;kBACb,EAAE,cAAc,kBACb,GAAG,EAAa,EAAE,QAAQ,EAAE,GAAG,EAAa,EAAE,QAAQ,EAAE,GAAG,EAAE,SAC7D,GAAG,EAAgB,EAAE,SAAS,EAAE,GAAG,EAAa,EAAE,UAAU,EAAE,GAAG,EAAE;OACnE,CAAA;MAEV;MACA;OAAE,KAAK;OAAY,QAAQ;OAAY,SAAS,MAAM,kBAAC,GAAD,EAAa,QAAQ,EAAE,SAAW,CAAA;MAAE;MAC1F;OACE,KAAK;OACL,QAAQ;OACR,OAAO;OACP,SAAS,MACP,kBAAC,GAAD;QACE,MAAK;QACL,SAAS,EAAE,WAAW,YAAY;QAClC,eAAe,GAAmB,OAAO;SAAE,IAAI,EAAE;SAAI,UAAU,CAAC,EAAE;QAAS,CAAC;kBAE3E,EAAE,WAAW,WAAW;OACnB,CAAA;MAEZ;KA8O0B;KAAkB,MAAM;KAAY,SAAS,MAAM,EAAE;IAAK,CAAA;GAEzE,CAAA;GAGT,kBAAC,GAAD;IAAS,OAAM;IAAsB,aAAY;cAC9C,EAAgB,WAAW,IAC1B,kBAAC,GAAD;KACE,cAAa;KACb,OAAM;KACN,aAAY;IACb,CAAA,IAED,kBAAC,GAAD;KAAW,SAAS;MAvP1B;OACE,KAAK;OACL,QAAQ;OACR,SAAS,MAAM,kBAAC,QAAD;QAAM,WAAU;kBAAiC,EAAE;OAAgB,CAAA;MACpF;MACA;OACE,KAAK;OACL,QAAQ;OACR,OAAO;OACP,SAAS,MACP,kBAAC,QAAD;QAAM,WAAU;kBAAhB;SACG,EAAa,EAAE,KAAK;SAAE;SAAE,EAAE;QACvB;;MAEV;MACA;OACE,KAAK;OACL,QAAQ;OACR,cAAc;OACd,SAAS,MAAM,kBAAC,QAAD;QAAM,WAAU;kBAAuB,EAAgB,EAAE,MAAM;OAAQ,CAAA;MACxF;MACA;OACE,KAAK;OACL,QAAQ;OACR,SAAS,MACP,EAAE,WACA,kBAAC,GAAD;QAAa,QAAO;QAAS,WAAU;OAAa,CAAA,IAEpD,kBAAC,GAAD;QAAa,QAAO;QAAK,WAAU;OAAa,CAAA;MAEtD;MACA;OACE,KAAK;OACL,QAAQ;OACR,OAAO;OACP,cAAc;OACd,SAAS,MAAM,kBAAC,QAAD;QAAM,WAAU;kBAAsB,GAAe,EAAE,UAAU;OAAQ,CAAA;MAC1F;KAkN0B;KAAoB,MAAM;KAAiB,SAAS,MAAM,EAAE;IAAK,CAAA;GAEhF,CAAA;GAGT,kBAAC,GAAD;IAAkB,MAAM;IAAY,cAAc;cAChD,kBAAC,GAAD,EAAA,UAAA;KACE,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,GAAD,EAAA,UAAuB,aAAiC,CAAA,GACxD,kBAAC,GAAD,EAAA,UAAA;MAA6B;MACL,EAAK;MAAK;KACL,EAAA,CAAA,CACP,EAAA,CAAA;KACxB,kBAAC,OAAD;MAAK,WAAU;gBAAf;OACE,kBAAC,OAAD;QAAK,WAAU;kBAAf,CACE,kBAAC,GAAD;SAAO,SAAQ;mBAAa;QAAa,CAAA,GACzC,kBAAC,GAAD;SACE,IAAG;SACH,OAAO,EAAW;SAClB,WAAW,MAAM,EAAc;UAAE,GAAG;UAAY,MAAM,EAAE,OAAO;SAAM,CAAC;SACtE,aAAY;QACb,CAAA,CACE;;OACL,kBAAC,OAAD;QAAK,WAAU;kBAAf,CACE,kBAAC,OAAD;SAAK,WAAU;mBAAf,CACE,kBAAC,GAAD;UAAO,SAAQ;oBAAa;SAAW,CAAA,GACvC,kBAAC,GAAD;UACE,OAAO,EAAW;UAClB,gBAAgB,MAAM,EAAc;WAAE,GAAG;WAAY,MAAM;UAAmB,CAAC;oBAFjF,CAIE,kBAAC,GAAD;WAAe,IAAG;qBAChB,kBAAC,GAAD,CAAc,CAAA;UACD,CAAA,GACf,kBAAC,GAAD,EAAA,UACG,GAAa,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;oBAAa;SAAW,CAAA,GACvC,kBAAC,GAAD;UACE,IAAG;UACH,OAAO,EAAW;UAClB,WAAW,MAAM,EAAc;WAAE,GAAG;WAAY,MAAM,EAAE,OAAO;UAAM,CAAC;UACtE,aAAY;SACb,CAAA,CACE;UACF;;OACL,kBAAC,OAAD;QAAK,WAAU;kBAAf,CACE,kBAAC,GAAD;SAAO,SAAQ;mBAAW;QAAkB,CAAA,GAC5C,kBAAC,GAAD;SACE,IAAG;SACH,OAAO,EAAW;SAClB,WAAW,MAAM,EAAc;UAAE,GAAG;UAAY,YAAY,EAAE,OAAO;SAAM,CAAC;SAC5E,aAAY;QACb,CAAA,CACE;;MACF;;KACL,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,GAAD;MAAQ,SAAQ;MAAU,eAAe,EAAc,EAAK;gBAAG;KAEvD,CAAA,GACR,kBAAC,GAAD;MAAQ,SAAS,YA9dS;OAClC,IAAI,CAAC,KAAM,CAAC,EAAW,KAAK,KAAK,GAAG;QAClC,EAAS,0BAA0B;QACnC;OACF;OACA,EAAS,IAAI;OACb,IAAI;QASF,AARA,MAAM,EAAa,YAAY;SAC7B,MAAM,EAAW,KAAK,KAAK;SAC3B,QAAQ;SACR,MAAM,EAAW;SACjB,MAAM,EAAW,KAAK,KAAK,KAAK;SAChC,YAAY,EAAW,WAAW,KAAK,KAAK;QAC9C,CAAC,GACD,EAAc,EAAK,GACnB,EAAc;SAAE,MAAM;SAAI,MAAM;SAAe,MAAM;SAAI,YAAY;QAAG,CAAC;OAC3E,SAAS,GAAK;QACZ,EAAS,aAAe,QAAQ,EAAI,UAAU,uBAAuB;OACvE;MACF;MA2c4C,UAAU,EAAa;gBACtD,EAAa,YAAY,YAAY;KAChC,CAAA,CACc,EAAA,CAAA;IACD,EAAA,CAAA;GACT,CAAA;GAGlB,kBAAC,GAAD;IAAkB,MAAM;IAAe,cAAc;cACnD,kBAAC,GAAD,EAAA,UAAA;KACE,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,GAAD,EAAA,UAAuB,sBAA0C,CAAA,GACjE,kBAAC,GAAD,EAAA,UAA6B,uDAEA,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;oBAAU;SAAkB,CAAA,GAC3C,kBAAC,GAAD;UACE,IAAG;UACH,OAAO,EAAc;UACrB,WAAW,MACT,EAAiB;WAAE,GAAG;WAAe,WAAW,EAAE,OAAO;UAAM,CAAC;UAElE,aAAY;SACb,CAAA,CACE;YACL,kBAAC,OAAD;SAAK,WAAU;mBAAf,CACE,kBAAC,GAAD;UAAO,SAAQ;oBAAS;SAAa,CAAA,GACrC,kBAAC,GAAD;UACE,IAAG;UACH,OAAO,EAAc;UACrB,WAAW,MAAM,EAAiB;WAAE,GAAG;WAAe,MAAM,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;oBAAQ;SAAgB,CAAA,GACvC,kBAAC,GAAD;UACE,OAAO,EAAc;UACrB,gBAAgB,MACd,EAAiB;WAAE,GAAG;WAAe,WAAW;UAA2B,CAAC;oBAHhF,CAME,kBAAC,GAAD;WAAe,IAAG;qBAChB,kBAAC,GAAD,CAAc,CAAA;UACD,CAAA,GACf,kBAAC,GAAD,EAAA,UACG,GAAqB,KAAK,MACzB,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;oBAAQ;SAAe,CAAA,GACtC,kBAAC,GAAD;UACE,OAAO,EAAc;UACrB,gBAAgB,MACd,EAAiB;WAAE,GAAG;WAAe,UAAU;UAA0B,CAAC;oBAH9E,CAME,kBAAC,GAAD;WAAe,IAAG;qBAChB,kBAAC,GAAD,CAAc,CAAA;UACD,CAAA,GACf,kBAAC,GAAD,EAAA,UACG,GAAqB,KAAK,MACzB,kBAAC,GAAD;WAAoB,OAAO;qBACxB,EAAgB,CAAC;UACR,GAFK,CAEL,CACb,EACY,CAAA,CACT;WACL;UACF;;OACJ,EAAc,cAAc,kBAC3B,kBAAC,OAAD;QAAK,WAAU;kBAAf,CACE,kBAAC,OAAD;SAAK,WAAU;mBAAf,CACE,kBAAC,GAAD;UAAO,SAAQ;oBAAQ;SAAgB,CAAA,GACvC,kBAAC,GAAD;UACE,IAAG;UACH,MAAK;UACL,MAAK;UACL,OAAO,EAAc;UACrB,WAAW,MACT,EAAiB;WAAE,GAAG;WAAe,UAAU,EAAE,OAAO;UAAM,CAAC;SAElE,CAAA,CACE;YACL,kBAAC,OAAD;SAAK,WAAU;mBAAf,CACE,kBAAC,GAAD;UAAO,SAAQ;oBAAQ;SAAgB,CAAA,GACvC,kBAAC,GAAD;UACE,IAAG;UACH,MAAK;UACL,MAAK;UACL,OAAO,EAAc;UACrB,WAAW,MACT,EAAiB;WAAE,GAAG;WAAe,UAAU,EAAE,OAAO;UAAM,CAAC;SAElE,CAAA,CACE;UACF;YAEL,kBAAC,OAAD;QAAK,WAAU;kBAAf,CACE,kBAAC,GAAD;SAAO,SAAQ;mBAAU;QAAkB,CAAA,GAC3C,kBAAC,GAAD;SACE,IAAG;SACH,MAAK;SACL,MAAK;SACL,OAAO,EAAc;SACrB,WAAW,MACT,EAAiB;UAAE,GAAG;UAAe,YAAY,EAAE,OAAO;SAAM,CAAC;SAEnE,aAAY;QACb,CAAA,CACE;;MAEJ;;KACL,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,GAAD;MAAQ,SAAQ;MAAU,eAAe,EAAiB,EAAK;gBAAG;KAE1D,CAAA,GACR,kBAAC,GAAD;MAAQ,SAAS,YAzkBY;OACrC,IAAI,CAAC,KAAM,CAAC,EAAc,UAAU,KAAK,KAAK,CAAC,EAAc,KAAK,KAAK,GAAG;QACxE,EAAS,kCAAkC;QAC3C;OACF;OACA,EAAS,IAAI;OACb,IAAI;QAYF,AAXA,MAAM,EAAgB,YAAY;SAChC,QAAQ;SACR,WAAW,EAAc,UAAU,KAAK;SACxC,MAAM,EAAc,KAAK,KAAK;SAC9B,WAAW,EAAc;SACzB,UAAU,EAAc;SACxB,YAAY,EAAc,aAAa,OAAO,EAAc,UAAU,IAAI;SAC1E,UAAU,EAAc,WAAW,OAAO,EAAc,QAAQ,IAAI;SACpE,UAAU,EAAc,WAAW,OAAO,EAAc,QAAQ,IAAI;QACtE,CAAC,GACD,EAAiB,EAAK,GACtB,EAAiB;SACf,WAAW;SACX,MAAM;SACN,WAAW;SACX,UAAU;SACV,YAAY;SACZ,UAAU;SACV,UAAU;QACZ,CAAC;OACH,SAAS,GAAK;QACZ,EAAS,aAAe,QAAQ,EAAI,UAAU,0BAA0B;OAC1E;MACF;MA2iB+C,UAAU,EAAgB;gBAC5D,EAAgB,YAAY,YAAY;KACnC,CAAA,CACc,EAAA,CAAA;IACD,EAAA,CAAA;GACT,CAAA;GAGlB,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,UAA6B,qFAEA,CAAA,CACP,EAAA,CAAA;KACxB,kBAAC,OAAD;MAAK,WAAU;gBAAf;OACE,kBAAC,OAAD;QAAK,WAAU;kBAAf;SACE,kBAAC,OAAD;UAAK,WAAU;oBAAf,CACE,kBAAC,GAAD;WAAO,SAAQ;qBAAS;UAAkB,CAAA,GAC1C,kBAAC,GAAD;WACE,IAAG;WACH,OAAO,EAAY;WACnB,WAAW,MAAM,EAAe;YAAE,GAAG;YAAa,WAAW,EAAE,OAAO;WAAM,CAAC;WAC7E,aAAY;UACb,CAAA,CACE;;SACL,kBAAC,OAAD;UAAK,WAAU;oBAAf,CACE,kBAAC,GAAD;WAAO,SAAQ;qBAAS;UAAc,CAAA,GACtC,kBAAC,GAAD;WACE,IAAG;WACH,MAAK;WACL,MAAK;WACL,OAAO,EAAY;WACnB,WAAW,MAAM,EAAe;YAAE,GAAG;YAAa,OAAO,EAAE,OAAO;WAAM,CAAC;WACzE,aAAY;UACb,CAAA,CACE;;SACL,kBAAC,OAAD;UAAK,WAAU;oBAAf,CACE,kBAAC,GAAD;WAAO,SAAQ;qBAAQ;UAAa,CAAA,GACpC,kBAAC,GAAD;WACE,IAAG;WACH,OAAO,EAAY;WACnB,WAAW,MAAM,EAAe;YAAE,GAAG;YAAa,MAAM,EAAE,OAAO;WAAM,CAAC;WACxE,aAAY;UACb,CAAA,CACE;;QACF;;OACJ,EAAQ,SAAS,KAChB,kBAAC,OAAD;QAAK,WAAU;kBAAf;SACE,kBAAC,GAAD;UAAO,SAAQ;oBAAU;SAAwB,CAAA;SACjD,kBAAC,GAAD;UACE,OAAO,EAAY;UACnB,gBAAgB,MAAM,EAAe;WAAE,GAAG;WAAa,UAAU;UAAE,CAAC;oBAFtE,CAIE,kBAAC,GAAD;WAAe,IAAG;qBAChB,kBAAC,GAAD,EAAa,aAAY,eAAgB,CAAA;UAC5B,CAAA,GACf,kBAAC,GAAD,EAAA,UACG,EAAQ,KAAK,MACZ,kBAAC,GAAD;WAAuB,OAAO,EAAE;qBAC7B,EAAE;UACO,GAFK,EAAE,EAEP,CACb,EACY,CAAA,CACT;;SACR,kBAAC,KAAD;UAAG,WAAU;oBAA6B;SAEvC,CAAA;QACA;;OAEN,KAAS,kBAAC,KAAD;QAAG,WAAU;kBAAkC;OAAS,CAAA;MAC/D;;KACL,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,GAAD;MAAQ,SAAQ;MAAU,eAAe,EAAe,EAAK;gBAAG;KAExD,CAAA,GACR,kBAAC,GAAD;MAAQ,SAAS,YAtnBiB;OAC1C,IAAI,CAAC,KAAM,CAAC,EAAY,UAAU,KAAK,KAAK,CAAC,EAAY,KAAK,KAAK,KAAK,CAAC,EAAY,OAAO;QAC1F,EAAS,yCAAyC;QAClD;OACF;OACA,EAAS,IAAI;OACb,IAAI;QAYF,AAXA,MAAM,EAAkB,YAAY;SAClC,QAAQ;SACR,WAAW,EAAY,UAAU,KAAK;SACtC,OAAO,OAAO,EAAY,KAAK;SAC/B,MAAM,EAAY,KAAK,KAAK;SAC5B,6BAAY,IAAI,KAAK,GAAE,YAAY;SACnC,UAAU,EAAY,YAAY;SAClC,QAAQ;QACV,CAAC,GACD,EAAe,EAAK,GACpB,EAAe;SAAE,WAAW;SAAI,OAAO;SAAI,MAAM;SAAI,UAAU;QAAG,CAAC,GACnE,EAAa,QAAQ;OACvB,SAAS,GAAK;QACZ,EAAS,aAAe,QAAQ,EAAI,UAAU,+BAA+B;OAC/E;MACF;MAgmBoD,UAAU,EAAkB;gBACnE,EAAkB,YAAY,eAAe;KACxC,CAAA,CACc,EAAA,CAAA;IACD,EAAA,CAAA;GACT,CAAA;EACR;;AAEhB"}
@@ -1 +1 @@
1
- {"version":3,"file":"SitesPage.js","names":[],"sources":["../../../../src/ecoimpacthub/pages/sites/SitesPage.tsx"],"sourcesContent":["/**\n * Sites — list, filter, and create monitored locations.\n * @module ecoimpacthub/pages/sites\n *\n * Reads `ecoListSites`; writes `ecoCreateSite`.\n */\n\nimport { useMemo, useState } from 'react';\nimport { Plus, MapPin } 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 { StatusBadge } from '../../components/StatusBadge';\nimport { DataTable, type Column } from '../../components/DataTable';\nimport { FilterTabs, type FilterOption } from '../../components/FilterTabs';\nimport { LoadingView, ErrorView } from '../../components/StateViews';\nimport { EmptyState } from '../../components/EmptyState';\nimport { Pagination } from '../../components/Pagination';\nimport { SiteTypeBadge, SiteTypeIcon } from './siteTypeVisuals';\nimport { useEcoImpactHub } from '../../context';\nimport { useEcoSites, useEcoCreateSite, useCursorPager } from '../../hooks';\nimport { formatEnumLabel, formatNumber, formatRelativeTime } from '../../utils/formatters';\nimport type { EcoSite, EcoSiteStatus, EcoSiteType } from '../../types';\n\nconst STATUS_FILTERS: FilterOption<EcoSiteStatus | 'ALL'>[] = [\n { value: 'ALL', label: 'All' },\n { value: 'ACTIVE', label: 'Active' },\n { value: 'MONITORING_PAUSED', label: 'Paused' },\n { value: 'ARCHIVED', label: 'Archived' },\n];\n\nconst SITE_TYPES: EcoSiteType[] = [\n 'FACILITY',\n 'WATERSHED',\n 'FOREST',\n 'WETLAND',\n 'FARM',\n 'PROTECTED_AREA',\n 'AIRSHED',\n 'COASTAL',\n 'OTHER',\n];\n\nconst PAGE_SIZE = 25;\n\ninterface CreateFormState {\n name: string;\n code: string;\n type: EcoSiteType;\n region: string;\n country: string;\n areaHectares: string;\n latitude: string;\n longitude: string;\n description: string;\n}\n\nconst EMPTY_FORM: CreateFormState = {\n name: '',\n code: '',\n type: 'FACILITY',\n region: '',\n country: '',\n areaHectares: '',\n latitude: '',\n longitude: '',\n description: '',\n};\n\nexport function SitesPage() {\n const { navigateTo } = useEcoImpactHub();\n const [status, setStatus] = useState<EcoSiteStatus | 'ALL'>('ALL');\n const [type, setType] = useState<EcoSiteType | 'ALL'>('ALL');\n const pager = useCursorPager();\n const [dialogOpen, setDialogOpen] = useState(false);\n const [form, setForm] = useState<CreateFormState>(EMPTY_FORM);\n const [formError, setFormError] = useState<string | null>(null);\n\n const { data, isLoading, isError, refetch } = useEcoSites({\n status: status === 'ALL' ? undefined : status,\n type: type === 'ALL' ? undefined : type,\n pagination: { limit: PAGE_SIZE, cursor: pager.cursor },\n });\n const createSite = useEcoCreateSite();\n\n const rows = useMemo(() => data?.items ?? [], [data]);\n\n const columns: Column<EcoSite>[] = [\n {\n key: 'name',\n header: 'Site',\n render: (s) => (\n <div className=\"flex items-center gap-3\">\n <SiteTypeIcon type={s.type} />\n <div className=\"min-w-0\">\n <p className=\"truncate font-semibold text-text-primary\">{s.name}</p>\n {s.code && <p className=\"truncate text-xs text-text-tertiary\">{s.code}</p>}\n </div>\n </div>\n ),\n },\n {\n key: 'type',\n header: 'Type',\n hideOnMobile: true,\n render: (s) => <SiteTypeBadge type={s.type} />,\n },\n {\n key: 'location',\n header: 'Location',\n hideOnMobile: true,\n render: (s) => {\n const loc = [s.region, s.country].filter(Boolean).join(', ');\n return loc ? (\n <span className=\"inline-flex items-center gap-1.5 text-text-secondary\">\n <MapPin className=\"h-3.5 w-3.5 shrink-0 text-text-tertiary\" />\n {loc}\n </span>\n ) : (\n <span className=\"text-text-tertiary\">—</span>\n );\n },\n },\n {\n key: 'area',\n header: 'Area',\n align: 'right',\n hideOnMobile: true,\n sortValue: (s) => s.areaHectares ?? 0,\n render: (s) =>\n s.areaHectares != null ? (\n <span className=\"tabular-nums text-text-secondary\">\n {formatNumber(s.areaHectares)} ha\n </span>\n ) : (\n <span className=\"text-text-tertiary\">—</span>\n ),\n },\n {\n key: 'status',\n header: 'Status',\n render: (s) => <StatusBadge status={s.status} />,\n },\n {\n key: 'updated',\n header: 'Updated',\n align: 'right',\n hideOnMobile: true,\n render: (s) => <span className=\"text-text-tertiary\">{formatRelativeTime(s.updatedAt)}</span>,\n },\n ];\n\n const resetForm = () => {\n setForm(EMPTY_FORM);\n setFormError(null);\n };\n\n const handleCreate = async () => {\n if (!form.name.trim()) {\n setFormError('Site name is required.');\n return;\n }\n try {\n const created = await createSite.mutateAsync({\n name: form.name.trim(),\n code: form.code.trim() || null,\n type: form.type,\n region: form.region.trim() || null,\n country: form.country.trim() || null,\n areaHectares: form.areaHectares ? Number(form.areaHectares) : null,\n latitude: form.latitude ? Number(form.latitude) : null,\n longitude: form.longitude ? Number(form.longitude) : null,\n description: form.description.trim() || null,\n });\n setDialogOpen(false);\n resetForm();\n navigateTo(`sites/${created.id}`);\n } catch (err) {\n setFormError(err instanceof Error ? err.message : 'Failed to create site.');\n }\n };\n\n return (\n <PageLayout\n title=\"Sites\"\n description=\"Monitored locations — facilities, watersheds, forests, wetlands, farms & protected areas.\"\n actions={\n <Button onClick={() => setDialogOpen(true)}>\n <Plus className=\"mr-1.5 h-4 w-4\" /> New site\n </Button>\n }\n >\n <div className=\"mb-5 flex flex-wrap items-center gap-3\">\n <FilterTabs\n options={STATUS_FILTERS}\n value={status}\n onChange={(v) => {\n setStatus(v);\n pager.reset();\n }}\n />\n <Select\n value={type}\n onValueChange={(v) => {\n setType(v as EcoSiteType | 'ALL');\n pager.reset();\n }}\n >\n <SelectTrigger className=\"w-44\">\n <SelectValue placeholder=\"All types\" />\n </SelectTrigger>\n <SelectContent>\n <SelectItem value=\"ALL\">All types</SelectItem>\n {SITE_TYPES.map((t) => (\n <SelectItem key={t} value={t}>\n {formatEnumLabel(t)}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </div>\n\n {isLoading ? (\n <LoadingView label=\"Loading sites…\" />\n ) : isError ? (\n <ErrorView message=\"Couldn't load sites.\" onRetry={() => refetch()} />\n ) : rows.length === 0 ? (\n <EmptyState\n illustration=\"empty-data\"\n icon={<MapPin className=\"h-10 w-10\" />}\n title=\"No sites yet\"\n description={\n status === 'ALL' && type === 'ALL'\n ? 'Create your first monitored site to start collecting environmental data.'\n : 'No sites match these filters.'\n }\n action={\n <Button onClick={() => setDialogOpen(true)}>\n <Plus className=\"mr-1.5 h-4 w-4\" /> New site\n </Button>\n }\n />\n ) : (\n <>\n <DataTable\n columns={columns}\n rows={rows}\n rowKey={(s) => s.id}\n onRowClick={(s) => navigateTo(`sites/${s.id}`)}\n />\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>New site</ResponsiveDialogTitle>\n <ResponsiveDialogDescription>\n Register a location to monitor and attribute environmental data to.\n </ResponsiveDialogDescription>\n </ResponsiveDialogHeader>\n\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=\"name\">Name *</Label>\n <Input\n id=\"name\"\n value={form.name}\n onChange={(e) => setForm({ ...form, name: e.target.value })}\n placeholder=\"Palar Wetland Reserve\"\n />\n </div>\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"code\">Code</Label>\n <Input\n id=\"code\"\n value={form.code}\n onChange={(e) => setForm({ ...form, code: e.target.value })}\n placeholder=\"SITE-PWR\"\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=\"type\">Type</Label>\n <Select\n value={form.type}\n onValueChange={(v) => setForm({ ...form, type: v as EcoSiteType })}\n >\n <SelectTrigger id=\"type\">\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n {SITE_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=\"area\">Area (hectares)</Label>\n <Input\n id=\"area\"\n type=\"number\"\n min=\"0\"\n step=\"any\"\n value={form.areaHectares}\n onChange={(e) => setForm({ ...form, areaHectares: e.target.value })}\n placeholder=\"42.5\"\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=\"region\">Region</Label>\n <Input\n id=\"region\"\n value={form.region}\n onChange={(e) => setForm({ ...form, region: e.target.value })}\n placeholder=\"Tamil Nadu\"\n />\n </div>\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"country\">Country</Label>\n <Input\n id=\"country\"\n value={form.country}\n onChange={(e) => setForm({ ...form, country: e.target.value })}\n placeholder=\"India\"\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=\"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=\"description\">Description</Label>\n <Textarea\n id=\"description\"\n value={form.description}\n onChange={(e) => setForm({ ...form, description: e.target.value })}\n placeholder=\"Optional context about this site.\"\n rows={2}\n />\n </div>\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={handleCreate} disabled={createSite.isPending}>\n {createSite.isPending ? 'Creating…' : 'Create site'}\n </Button>\n </ResponsiveDialogFooter>\n </ResponsiveDialogContent>\n </ResponsiveDialog>\n </PageLayout>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"SitesPage.js","names":[],"sources":["../../../../src/ecoimpacthub/pages/sites/SitesPage.tsx"],"sourcesContent":["/**\n * Sites — list, filter, and create monitored locations.\n * @module ecoimpacthub/pages/sites\n *\n * Reads `ecoListSites`; writes `ecoCreateSite`.\n */\n\nimport { useMemo, useState } from 'react';\nimport { Plus, MapPin } 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 { StatusBadge } from '../../components/StatusBadge';\nimport { DataTable, type Column } from '../../components/DataTable';\nimport { FilterTabs, type FilterOption } from '../../components/FilterTabs';\nimport { LoadingView, ErrorView } from '../../components/StateViews';\nimport { EmptyState } from '../../components/EmptyState';\nimport { Pagination } from '../../components/Pagination';\nimport { SiteTypeBadge, SiteTypeIcon } from './siteTypeVisuals';\nimport { useEcoImpactHub } from '../../context';\nimport { useEcoSites, useEcoCreateSite, useCursorPager } from '../../hooks';\nimport { formatEnumLabel, formatNumber, formatRelativeTime } from '../../utils/formatters';\nimport type { EcoSite, EcoSiteStatus, EcoSiteType } from '../../types';\n\nconst STATUS_FILTERS: FilterOption<EcoSiteStatus | 'ALL'>[] = [\n { value: 'ALL', label: 'All' },\n { value: 'ACTIVE', label: 'Active' },\n { value: 'MONITORING_PAUSED', label: 'Paused' },\n { value: 'ARCHIVED', label: 'Archived' },\n];\n\nconst SITE_TYPES: EcoSiteType[] = [\n 'FACILITY',\n 'WATERSHED',\n 'FOREST',\n 'WETLAND',\n 'FARM',\n 'PROTECTED_AREA',\n 'AIRSHED',\n 'COASTAL',\n 'OTHER',\n];\n\nconst PAGE_SIZE = 25;\n\ninterface CreateFormState {\n name: string;\n code: string;\n type: EcoSiteType;\n region: string;\n country: string;\n areaHectares: string;\n latitude: string;\n longitude: string;\n description: string;\n}\n\nconst EMPTY_FORM: CreateFormState = {\n name: '',\n code: '',\n type: 'FACILITY',\n region: '',\n country: '',\n areaHectares: '',\n latitude: '',\n longitude: '',\n description: '',\n};\n\nexport function SitesPage() {\n const { navigateTo } = useEcoImpactHub();\n const [status, setStatus] = useState<EcoSiteStatus | 'ALL'>('ALL');\n const [type, setType] = useState<EcoSiteType | 'ALL'>('ALL');\n const pager = useCursorPager();\n const [dialogOpen, setDialogOpen] = useState(false);\n const [form, setForm] = useState<CreateFormState>(EMPTY_FORM);\n const [formError, setFormError] = useState<string | null>(null);\n\n const { data, isLoading, isError, refetch } = useEcoSites({\n status: status === 'ALL' ? undefined : status,\n type: type === 'ALL' ? undefined : type,\n pagination: { limit: PAGE_SIZE, cursor: pager.cursor },\n });\n const createSite = useEcoCreateSite();\n\n const rows = useMemo(() => data?.items ?? [], [data]);\n\n const columns: Column<EcoSite>[] = [\n {\n key: 'name',\n header: 'Site',\n render: (s) => (\n <div className=\"flex items-center gap-3\">\n <SiteTypeIcon type={s.type} />\n <div className=\"min-w-0\">\n <p className=\"truncate font-semibold text-text-primary\">{s.name}</p>\n {s.code && <p className=\"truncate text-xs text-text-tertiary\">{s.code}</p>}\n </div>\n </div>\n ),\n },\n {\n key: 'type',\n header: 'Type',\n hideOnMobile: true,\n render: (s) => <SiteTypeBadge type={s.type} />,\n },\n {\n key: 'location',\n header: 'Location',\n hideOnMobile: true,\n render: (s) => {\n const loc = [s.region, s.country].filter(Boolean).join(', ');\n return loc ? (\n <span className=\"inline-flex items-center gap-1.5 text-text-secondary\">\n <MapPin className=\"h-3.5 w-3.5 shrink-0 text-text-tertiary\" />\n {loc}\n </span>\n ) : (\n <span className=\"text-text-tertiary\">—</span>\n );\n },\n },\n {\n key: 'area',\n header: 'Area',\n align: 'right',\n hideOnMobile: true,\n sortValue: (s) => s.areaHectares ?? 0,\n render: (s) =>\n s.areaHectares != null ? (\n <span className=\"tabular-nums text-text-secondary\">\n {formatNumber(s.areaHectares)} ha\n </span>\n ) : (\n <span className=\"text-text-tertiary\">—</span>\n ),\n },\n {\n key: 'status',\n header: 'Status',\n render: (s) => <StatusBadge status={s.status} />,\n },\n {\n key: 'updated',\n header: 'Updated',\n align: 'right',\n hideOnMobile: true,\n render: (s) => <span className=\"text-text-tertiary\">{formatRelativeTime(s.updatedAt)}</span>,\n },\n ];\n\n const resetForm = () => {\n setForm(EMPTY_FORM);\n setFormError(null);\n };\n\n const handleCreate = async () => {\n if (!form.name.trim()) {\n setFormError('Site name is required.');\n return;\n }\n try {\n const created = await createSite.mutateAsync({\n name: form.name.trim(),\n code: form.code.trim() || null,\n type: form.type,\n region: form.region.trim() || null,\n country: form.country.trim() || null,\n areaHectares: form.areaHectares ? Number(form.areaHectares) : null,\n latitude: form.latitude ? Number(form.latitude) : null,\n longitude: form.longitude ? Number(form.longitude) : null,\n description: form.description.trim() || null,\n });\n setDialogOpen(false);\n resetForm();\n navigateTo(`sites/${created.id}`);\n } catch (err) {\n setFormError(err instanceof Error ? err.message : 'Failed to create site.');\n }\n };\n\n return (\n <PageLayout\n title=\"Sites\"\n description=\"Monitored locations — facilities, watersheds, forests, wetlands, farms & protected areas.\"\n actions={\n <Button onClick={() => setDialogOpen(true)}>\n <Plus className=\"mr-1.5 h-4 w-4\" /> New site\n </Button>\n }\n >\n <div className=\"mb-5 flex flex-wrap items-center gap-3\">\n <FilterTabs\n options={STATUS_FILTERS}\n value={status}\n onChange={(v) => {\n setStatus(v);\n pager.reset();\n }}\n />\n <Select\n value={type}\n onValueChange={(v) => {\n setType(v as EcoSiteType | 'ALL');\n pager.reset();\n }}\n >\n <SelectTrigger className=\"w-44\">\n <SelectValue placeholder=\"All types\" />\n </SelectTrigger>\n <SelectContent>\n <SelectItem value=\"ALL\">All types</SelectItem>\n {SITE_TYPES.map((t) => (\n <SelectItem key={t} value={t}>\n {formatEnumLabel(t)}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </div>\n\n {isLoading ? (\n <LoadingView label=\"Loading sites…\" />\n ) : isError ? (\n <ErrorView message=\"Couldn't load sites.\" onRetry={() => refetch()} />\n ) : rows.length === 0 ? (\n <EmptyState\n illustration=\"empty-data\"\n icon={<MapPin className=\"h-10 w-10\" />}\n title=\"No sites yet\"\n description={\n status === 'ALL' && type === 'ALL'\n ? 'Create your first monitored site to start collecting environmental data.'\n : 'No sites match these filters.'\n }\n action={\n <Button onClick={() => setDialogOpen(true)}>\n <Plus className=\"mr-1.5 h-4 w-4\" /> New site\n </Button>\n }\n />\n ) : (\n <>\n <DataTable\n columns={columns}\n rows={rows}\n rowKey={(s) => s.id}\n onRowClick={(s) => navigateTo(`sites/${s.id}`)}\n />\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>New site</ResponsiveDialogTitle>\n <ResponsiveDialogDescription>\n Register a location to monitor and attribute environmental data to.\n </ResponsiveDialogDescription>\n </ResponsiveDialogHeader>\n\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=\"name\">Name *</Label>\n <Input\n id=\"name\"\n value={form.name}\n onChange={(e) => setForm({ ...form, name: e.target.value })}\n placeholder=\"Palar Wetland Reserve\"\n />\n </div>\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"code\">Code</Label>\n <Input\n id=\"code\"\n value={form.code}\n onChange={(e) => setForm({ ...form, code: e.target.value })}\n placeholder=\"SITE-PWR\"\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=\"type\">Type</Label>\n <Select\n value={form.type}\n onValueChange={(v) => setForm({ ...form, type: v as EcoSiteType })}\n >\n <SelectTrigger id=\"type\">\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n {SITE_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=\"area\">Area (hectares)</Label>\n <Input\n id=\"area\"\n type=\"number\"\n min=\"0\"\n step=\"any\"\n value={form.areaHectares}\n onChange={(e) => setForm({ ...form, areaHectares: e.target.value })}\n placeholder=\"42.5\"\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=\"region\">Region</Label>\n <Input\n id=\"region\"\n value={form.region}\n onChange={(e) => setForm({ ...form, region: e.target.value })}\n placeholder=\"Tamil Nadu\"\n />\n </div>\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"country\">Country</Label>\n <Input\n id=\"country\"\n value={form.country}\n onChange={(e) => setForm({ ...form, country: e.target.value })}\n placeholder=\"India\"\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=\"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=\"description\">Description</Label>\n <Textarea\n id=\"description\"\n value={form.description}\n onChange={(e) => setForm({ ...form, description: e.target.value })}\n placeholder=\"Optional context about this site.\"\n rows={2}\n />\n </div>\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={handleCreate} disabled={createSite.isPending}>\n {createSite.isPending ? 'Creating…' : 'Create site'}\n </Button>\n </ResponsiveDialogFooter>\n </ResponsiveDialogContent>\n </ResponsiveDialog>\n </PageLayout>\n );\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"siteTypeVisuals.js","names":[],"sources":["../../../../src/ecoimpacthub/pages/sites/siteTypeVisuals.tsx"],"sourcesContent":["/**\n * Per-site-type visual language — a lucide icon + a semantic-token colour tone\n * that makes the Sites list & Site detail scannable at a glance.\n * @module ecoimpacthub/pages/sites\n *\n * Tokens only (CSS variables) — no raw colours. Hue tones map to the fe-libs\n * `accent-*` / `status-*` families so each site type reads distinctly while\n * still inheriting the active product theme.\n */\n\nimport type { FC } from 'react';\nimport {\n Factory,\n Waves,\n Trees,\n Droplets,\n Sprout,\n ShieldCheck,\n Wind,\n Sailboat,\n MapPin,\n type LucideIcon,\n} from 'lucide-react';\nimport { cn } from '../../utils/cn';\nimport { formatEnumLabel } from '../../utils/formatters';\nimport type { EcoSiteType } from '../../types';\n\ninterface TypeVisual {\n Icon: LucideIcon;\n /** Shared bg + text classes for both the pill and the leading icon tile. */\n tone: string;\n}\n\nconst NEUTRAL = 'bg-bg-sunken text-text-secondary';\n\nconst VISUALS: Record<EcoSiteType, TypeVisual> = {\n FACILITY: { Icon: Factory, tone: NEUTRAL },\n WATERSHED: {\n Icon: Waves,\n tone: 'bg-[var(--color-accent-teal-subtle)] text-[var(--color-accent-teal)]',\n },\n FOREST: {\n Icon: Trees,\n tone: 'bg-status-success-bg-subtle text-status-success-text',\n },\n WETLAND: {\n Icon: Droplets,\n tone: 'bg-[var(--color-accent-blue-subtle)] text-[var(--color-accent-blue)]',\n },\n FARM: {\n Icon: Sprout,\n tone: 'bg-status-warning-bg-subtle text-status-warning-text',\n },\n PROTECTED_AREA: {\n Icon: ShieldCheck,\n tone: 'bg-[var(--color-accent-purple-subtle)] text-[var(--color-accent-purple)]',\n },\n AIRSHED: { Icon: Wind, tone: NEUTRAL },\n COASTAL: {\n Icon: Sailboat,\n tone: 'bg-[var(--color-accent-indigo-subtle)] text-[var(--color-accent-indigo)]',\n },\n OTHER: { Icon: MapPin, tone: NEUTRAL },\n};\n\nexport function siteTypeVisual(type: EcoSiteType): TypeVisual {\n return VISUALS[type] ?? VISUALS.OTHER;\n}\n\n/** A leading rounded icon tile keyed to the site type. */\nexport const SiteTypeIcon: FC<{ type: EcoSiteType; className?: string }> = ({\n type,\n className,\n}) => {\n const { Icon, tone } = siteTypeVisual(type);\n return (\n <span\n className={cn(\n 'inline-flex h-9 w-9 shrink-0 items-center justify-center rounded-lg',\n tone,\n className\n )}\n aria-hidden=\"true\"\n >\n <Icon className=\"h-[1.15rem] w-[1.15rem]\" />\n </span>\n );\n};\n\n/** A coloured pill: icon + human label for a site type. */\nexport const SiteTypeBadge: FC<{ type: EcoSiteType; className?: string }> = ({\n type,\n className,\n}) => {\n const { Icon, tone } = siteTypeVisual(type);\n return (\n <span\n className={cn(\n 'inline-flex items-center gap-1.5 rounded-full px-2.5 py-0.5 text-xs font-medium',\n tone,\n className\n )}\n >\n <Icon className=\"h-3.5 w-3.5\" />\n {formatEnumLabel(type)}\n </span>\n );\n};\n"],"mappings":";;;;;AAiCA,IAAM,IAAU,oCAEV,IAA2C;CAC/C,UAAU;EAAE,MAAM;EAAS,MAAM;EAAS;CAC1C,WAAW;EACT,MAAM;EACN,MAAM;EACP;CACD,QAAQ;EACN,MAAM;EACN,MAAM;EACP;CACD,SAAS;EACP,MAAM;EACN,MAAM;EACP;CACD,MAAM;EACJ,MAAM;EACN,MAAM;EACP;CACD,gBAAgB;EACd,MAAM;EACN,MAAM;EACP;CACD,SAAS;EAAE,MAAM;EAAM,MAAM;EAAS;CACtC,SAAS;EACP,MAAM;EACN,MAAM;EACP;CACD,OAAO;EAAE,MAAM;EAAQ,MAAM;EAAS;CACvC;AAED,SAAgB,EAAe,GAA+B;AAC5D,QAAO,EAAQ,MAAS,EAAQ;;AAIlC,IAAa,KAA+D,EAC1E,SACA,mBACI;CACJ,IAAM,EAAE,SAAM,YAAS,EAAe,EAAK;AAC3C,QACE,kBAAC,QAAD;EACE,WAAW,EACT,uEACA,GACA,EACD;EACD,eAAY;YAEZ,kBAAC,GAAD,EAAM,WAAU,2BAA4B,CAAA;EACvC,CAAA;GAKE,KAAgE,EAC3E,SACA,mBACI;CACJ,IAAM,EAAE,SAAM,YAAS,EAAe,EAAK;AAC3C,QACE,kBAAC,QAAD;EACE,WAAW,EACT,mFACA,GACA,EACD;YALH,CAOE,kBAAC,GAAD,EAAM,WAAU,eAAgB,CAAA,EAC/B,EAAgB,EAAK,CACjB"}
1
+ {"version":3,"file":"siteTypeVisuals.js","names":[],"sources":["../../../../src/ecoimpacthub/pages/sites/siteTypeVisuals.tsx"],"sourcesContent":["/**\n * Per-site-type visual language — a lucide icon + a semantic-token colour tone\n * that makes the Sites list & Site detail scannable at a glance.\n * @module ecoimpacthub/pages/sites\n *\n * Tokens only (CSS variables) — no raw colours. Hue tones map to the fe-libs\n * `accent-*` / `status-*` families so each site type reads distinctly while\n * still inheriting the active product theme.\n */\n\nimport type { FC } from 'react';\nimport {\n Factory,\n Waves,\n Trees,\n Droplets,\n Sprout,\n ShieldCheck,\n Wind,\n Sailboat,\n MapPin,\n type LucideIcon,\n} from 'lucide-react';\nimport { cn } from '../../utils/cn';\nimport { formatEnumLabel } from '../../utils/formatters';\nimport type { EcoSiteType } from '../../types';\n\ninterface TypeVisual {\n Icon: LucideIcon;\n /** Shared bg + text classes for both the pill and the leading icon tile. */\n tone: string;\n}\n\nconst NEUTRAL = 'bg-bg-sunken text-text-secondary';\n\nconst VISUALS: Record<EcoSiteType, TypeVisual> = {\n FACILITY: { Icon: Factory, tone: NEUTRAL },\n WATERSHED: {\n Icon: Waves,\n tone: 'bg-[var(--color-accent-teal-subtle)] text-[var(--color-accent-teal)]',\n },\n FOREST: {\n Icon: Trees,\n tone: 'bg-status-success-bg-subtle text-status-success-text',\n },\n WETLAND: {\n Icon: Droplets,\n tone: 'bg-[var(--color-accent-blue-subtle)] text-[var(--color-accent-blue)]',\n },\n FARM: {\n Icon: Sprout,\n tone: 'bg-status-warning-bg-subtle text-status-warning-text',\n },\n PROTECTED_AREA: {\n Icon: ShieldCheck,\n tone: 'bg-[var(--color-accent-purple-subtle)] text-[var(--color-accent-purple)]',\n },\n AIRSHED: { Icon: Wind, tone: NEUTRAL },\n COASTAL: {\n Icon: Sailboat,\n tone: 'bg-[var(--color-accent-indigo-subtle)] text-[var(--color-accent-indigo)]',\n },\n OTHER: { Icon: MapPin, tone: NEUTRAL },\n};\n\nexport function siteTypeVisual(type: EcoSiteType): TypeVisual {\n return VISUALS[type] ?? VISUALS.OTHER;\n}\n\n/** A leading rounded icon tile keyed to the site type. */\nexport const SiteTypeIcon: FC<{ type: EcoSiteType; className?: string }> = ({\n type,\n className,\n}) => {\n const { Icon, tone } = siteTypeVisual(type);\n return (\n <span\n className={cn(\n 'inline-flex h-9 w-9 shrink-0 items-center justify-center rounded-lg',\n tone,\n className\n )}\n aria-hidden=\"true\"\n >\n <Icon className=\"h-[1.15rem] w-[1.15rem]\" />\n </span>\n );\n};\n\n/** A coloured pill: icon + human label for a site type. */\nexport const SiteTypeBadge: FC<{ type: EcoSiteType; className?: string }> = ({\n type,\n className,\n}) => {\n const { Icon, tone } = siteTypeVisual(type);\n return (\n <span\n className={cn(\n 'inline-flex items-center gap-1.5 rounded-full px-2.5 py-0.5 text-xs font-medium',\n tone,\n className\n )}\n >\n <Icon className=\"h-3.5 w-3.5\" />\n {formatEnumLabel(type)}\n </span>\n );\n};\n"],"mappings":";;;;;AAiCA,IAAM,IAAU,oCAEV,IAA2C;CAC/C,UAAU;EAAE,MAAM;EAAS,MAAM;CAAQ;CACzC,WAAW;EACT,MAAM;EACN,MAAM;CACR;CACA,QAAQ;EACN,MAAM;EACN,MAAM;CACR;CACA,SAAS;EACP,MAAM;EACN,MAAM;CACR;CACA,MAAM;EACJ,MAAM;EACN,MAAM;CACR;CACA,gBAAgB;EACd,MAAM;EACN,MAAM;CACR;CACA,SAAS;EAAE,MAAM;EAAM,MAAM;CAAQ;CACrC,SAAS;EACP,MAAM;EACN,MAAM;CACR;CACA,OAAO;EAAE,MAAM;EAAQ,MAAM;CAAQ;AACvC;AAEA,SAAgB,EAAe,GAA+B;CAC5D,OAAO,EAAQ,MAAS,EAAQ;AAClC;AAGA,IAAa,KAA+D,EAC1E,SACA,mBACI;CACJ,IAAM,EAAE,SAAM,YAAS,EAAe,CAAI;CAC1C,OACE,kBAAC,QAAD;EACE,WAAW,EACT,uEACA,GACA,CACF;EACA,eAAY;YAEZ,kBAAC,GAAD,EAAM,WAAU,0BAA2B,CAAA;CACvC,CAAA;AAEV,GAGa,KAAgE,EAC3E,SACA,mBACI;CACJ,IAAM,EAAE,SAAM,YAAS,EAAe,CAAI;CAC1C,OACE,kBAAC,QAAD;EACE,WAAW,EACT,mFACA,GACA,CACF;YALF,CAOE,kBAAC,GAAD,EAAM,WAAU,cAAe,CAAA,GAC9B,EAAgB,CAAI,CACjB;;AAEV"}