@burdenoff/microfe-ecoimpacthub 2026.720.2 → 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 (484) 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.d.ts.map +1 -1
  18. package/dist/ecoimpacthub/components/StatusBadge.js +3 -0
  19. package/dist/ecoimpacthub/components/StatusBadge.js.map +1 -1
  20. package/dist/ecoimpacthub/components/Tabs.js.map +1 -1
  21. package/dist/ecoimpacthub/context/EcoImpactHubContext.js.map +1 -1
  22. package/dist/ecoimpacthub/hooks/useCursorPager.js.map +1 -1
  23. package/dist/ecoimpacthub/hooks/useEcoComments.js.map +1 -1
  24. package/dist/ecoimpacthub/hooks/useEcoFiles.js.map +1 -1
  25. package/dist/ecoimpacthub/hooks/useEcoImpactHubApi.js.map +1 -1
  26. package/dist/ecoimpacthub/hooks/useEcoTags.js.map +1 -1
  27. package/dist/ecoimpacthub/pages/DashboardPage.js.map +1 -1
  28. package/dist/ecoimpacthub/pages/_shared/ConservationBadge.js.map +1 -1
  29. package/dist/ecoimpacthub/pages/_shared/EvidenceThumb.js.map +1 -1
  30. package/dist/ecoimpacthub/pages/_shared/MethodBadge.js.map +1 -1
  31. package/dist/ecoimpacthub/pages/_shared/TaxonChip.js.map +1 -1
  32. package/dist/ecoimpacthub/pages/badges.js.map +1 -1
  33. package/dist/ecoimpacthub/pages/carbon/CarbonPage.js.map +1 -1
  34. package/dist/ecoimpacthub/pages/carbonVisuals.js.map +1 -1
  35. package/dist/ecoimpacthub/pages/habitat/HabitatProjectDetailPage.js.map +1 -1
  36. package/dist/ecoimpacthub/pages/habitat/HabitatProjectsPage.js.map +1 -1
  37. package/dist/ecoimpacthub/pages/habitat/LifecycleProgress.js.map +1 -1
  38. package/dist/ecoimpacthub/pages/monitoring/MonitoringPage.js.map +1 -1
  39. package/dist/ecoimpacthub/pages/observations/ObservationsPage.js.map +1 -1
  40. package/dist/ecoimpacthub/pages/reports/ReportDetailPage.js.map +1 -1
  41. package/dist/ecoimpacthub/pages/reports/ReportsPage.js.map +1 -1
  42. package/dist/ecoimpacthub/pages/sites/SiteDetailPage.js.map +1 -1
  43. package/dist/ecoimpacthub/pages/sites/SitesPage.js.map +1 -1
  44. package/dist/ecoimpacthub/pages/sites/siteTypeVisuals.js.map +1 -1
  45. package/dist/ecoimpacthub/pages/species/SpeciesDetailPage.js.map +1 -1
  46. package/dist/ecoimpacthub/pages/species/SpeciesPage.js.map +1 -1
  47. package/dist/ecoimpacthub/services/createSandboxAssistantTransport.d.ts.map +1 -1
  48. package/dist/ecoimpacthub/services/createSandboxAssistantTransport.js.map +1 -1
  49. package/dist/ecoimpacthub/types/index.d.ts.map +1 -1
  50. package/dist/ecoimpacthub/utils/cn.js.map +1 -1
  51. package/dist/ecoimpacthub/utils/formatters.js.map +1 -1
  52. package/dist/ecoimpacthub/utils/navigation.js.map +1 -1
  53. package/dist/node_modules/clsx/dist/clsx.js.map +1 -1
  54. package/dist/node_modules/d3-array/src/ascending.js.map +1 -1
  55. package/dist/node_modules/d3-array/src/bisect.js.map +1 -1
  56. package/dist/node_modules/d3-array/src/bisector.js.map +1 -1
  57. package/dist/node_modules/d3-array/src/descending.js.map +1 -1
  58. package/dist/node_modules/d3-array/src/max.js.map +1 -1
  59. package/dist/node_modules/d3-array/src/min.js.map +1 -1
  60. package/dist/node_modules/d3-array/src/number.js.map +1 -1
  61. package/dist/node_modules/d3-array/src/quantile.js.map +1 -1
  62. package/dist/node_modules/d3-array/src/quickselect.js.map +1 -1
  63. package/dist/node_modules/d3-array/src/range.js.map +1 -1
  64. package/dist/node_modules/d3-array/src/sort.js.map +1 -1
  65. package/dist/node_modules/d3-array/src/ticks.js.map +1 -1
  66. package/dist/node_modules/d3-color/src/color.js.map +1 -1
  67. package/dist/node_modules/d3-color/src/define.js.map +1 -1
  68. package/dist/node_modules/d3-format/src/defaultLocale.js.map +1 -1
  69. package/dist/node_modules/d3-format/src/exponent.js.map +1 -1
  70. package/dist/node_modules/d3-format/src/formatDecimal.js.map +1 -1
  71. package/dist/node_modules/d3-format/src/formatGroup.js.map +1 -1
  72. package/dist/node_modules/d3-format/src/formatNumerals.js.map +1 -1
  73. package/dist/node_modules/d3-format/src/formatPrefixAuto.js.map +1 -1
  74. package/dist/node_modules/d3-format/src/formatRounded.js.map +1 -1
  75. package/dist/node_modules/d3-format/src/formatSpecifier.js.map +1 -1
  76. package/dist/node_modules/d3-format/src/formatTrim.js.map +1 -1
  77. package/dist/node_modules/d3-format/src/formatTypes.js.map +1 -1
  78. package/dist/node_modules/d3-format/src/identity.js.map +1 -1
  79. package/dist/node_modules/d3-format/src/locale.js.map +1 -1
  80. package/dist/node_modules/d3-format/src/precisionFixed.js.map +1 -1
  81. package/dist/node_modules/d3-format/src/precisionPrefix.js.map +1 -1
  82. package/dist/node_modules/d3-format/src/precisionRound.js.map +1 -1
  83. package/dist/node_modules/d3-interpolate/src/array.js.map +1 -1
  84. package/dist/node_modules/d3-interpolate/src/basis.js.map +1 -1
  85. package/dist/node_modules/d3-interpolate/src/basisClosed.js.map +1 -1
  86. package/dist/node_modules/d3-interpolate/src/color.js.map +1 -1
  87. package/dist/node_modules/d3-interpolate/src/date.js.map +1 -1
  88. package/dist/node_modules/d3-interpolate/src/number.js.map +1 -1
  89. package/dist/node_modules/d3-interpolate/src/numberArray.js.map +1 -1
  90. package/dist/node_modules/d3-interpolate/src/object.js.map +1 -1
  91. package/dist/node_modules/d3-interpolate/src/piecewise.js.map +1 -1
  92. package/dist/node_modules/d3-interpolate/src/rgb.js.map +1 -1
  93. package/dist/node_modules/d3-interpolate/src/round.js.map +1 -1
  94. package/dist/node_modules/d3-interpolate/src/string.js.map +1 -1
  95. package/dist/node_modules/d3-interpolate/src/value.js.map +1 -1
  96. package/dist/node_modules/d3-path/src/path.js.map +1 -1
  97. package/dist/node_modules/d3-scale/src/band.js.map +1 -1
  98. package/dist/node_modules/d3-scale/src/constant.js.map +1 -1
  99. package/dist/node_modules/d3-scale/src/continuous.js.map +1 -1
  100. package/dist/node_modules/d3-scale/src/diverging.js.map +1 -1
  101. package/dist/node_modules/d3-scale/src/identity.js.map +1 -1
  102. package/dist/node_modules/d3-scale/src/init.js.map +1 -1
  103. package/dist/node_modules/d3-scale/src/linear.js.map +1 -1
  104. package/dist/node_modules/d3-scale/src/log.js.map +1 -1
  105. package/dist/node_modules/d3-scale/src/nice.js.map +1 -1
  106. package/dist/node_modules/d3-scale/src/number.js.map +1 -1
  107. package/dist/node_modules/d3-scale/src/ordinal.js.map +1 -1
  108. package/dist/node_modules/d3-scale/src/pow.js.map +1 -1
  109. package/dist/node_modules/d3-scale/src/quantile.js.map +1 -1
  110. package/dist/node_modules/d3-scale/src/quantize.js.map +1 -1
  111. package/dist/node_modules/d3-scale/src/radial.js.map +1 -1
  112. package/dist/node_modules/d3-scale/src/sequential.js.map +1 -1
  113. package/dist/node_modules/d3-scale/src/sequentialQuantile.js.map +1 -1
  114. package/dist/node_modules/d3-scale/src/symlog.js.map +1 -1
  115. package/dist/node_modules/d3-scale/src/threshold.js.map +1 -1
  116. package/dist/node_modules/d3-scale/src/tickFormat.js.map +1 -1
  117. package/dist/node_modules/d3-scale/src/time.js.map +1 -1
  118. package/dist/node_modules/d3-scale/src/utcTime.js.map +1 -1
  119. package/dist/node_modules/d3-shape/src/area.js.map +1 -1
  120. package/dist/node_modules/d3-shape/src/array.js.map +1 -1
  121. package/dist/node_modules/d3-shape/src/constant.js.map +1 -1
  122. package/dist/node_modules/d3-shape/src/curve/basis.js.map +1 -1
  123. package/dist/node_modules/d3-shape/src/curve/basisClosed.js.map +1 -1
  124. package/dist/node_modules/d3-shape/src/curve/basisOpen.js.map +1 -1
  125. package/dist/node_modules/d3-shape/src/curve/bump.js.map +1 -1
  126. package/dist/node_modules/d3-shape/src/curve/linear.js.map +1 -1
  127. package/dist/node_modules/d3-shape/src/curve/linearClosed.js.map +1 -1
  128. package/dist/node_modules/d3-shape/src/curve/monotone.js.map +1 -1
  129. package/dist/node_modules/d3-shape/src/curve/natural.js.map +1 -1
  130. package/dist/node_modules/d3-shape/src/curve/step.js.map +1 -1
  131. package/dist/node_modules/d3-shape/src/line.js.map +1 -1
  132. package/dist/node_modules/d3-shape/src/noop.js.map +1 -1
  133. package/dist/node_modules/d3-shape/src/offset/expand.js.map +1 -1
  134. package/dist/node_modules/d3-shape/src/offset/none.js.map +1 -1
  135. package/dist/node_modules/d3-shape/src/offset/silhouette.js.map +1 -1
  136. package/dist/node_modules/d3-shape/src/offset/wiggle.js.map +1 -1
  137. package/dist/node_modules/d3-shape/src/order/none.js.map +1 -1
  138. package/dist/node_modules/d3-shape/src/path.js.map +1 -1
  139. package/dist/node_modules/d3-shape/src/point.js.map +1 -1
  140. package/dist/node_modules/d3-shape/src/stack.js.map +1 -1
  141. package/dist/node_modules/d3-shape/src/symbol/circle.js.map +1 -1
  142. package/dist/node_modules/d3-shape/src/symbol/cross.js.map +1 -1
  143. package/dist/node_modules/d3-shape/src/symbol/diamond.js.map +1 -1
  144. package/dist/node_modules/d3-shape/src/symbol/square.js.map +1 -1
  145. package/dist/node_modules/d3-shape/src/symbol/star.js.map +1 -1
  146. package/dist/node_modules/d3-shape/src/symbol/triangle.js.map +1 -1
  147. package/dist/node_modules/d3-shape/src/symbol/wye.js.map +1 -1
  148. package/dist/node_modules/d3-shape/src/symbol.js.map +1 -1
  149. package/dist/node_modules/d3-time/src/day.js.map +1 -1
  150. package/dist/node_modules/d3-time/src/hour.js.map +1 -1
  151. package/dist/node_modules/d3-time/src/interval.js.map +1 -1
  152. package/dist/node_modules/d3-time/src/millisecond.js.map +1 -1
  153. package/dist/node_modules/d3-time/src/minute.js.map +1 -1
  154. package/dist/node_modules/d3-time/src/month.js.map +1 -1
  155. package/dist/node_modules/d3-time/src/second.js.map +1 -1
  156. package/dist/node_modules/d3-time/src/ticks.js.map +1 -1
  157. package/dist/node_modules/d3-time/src/week.js.map +1 -1
  158. package/dist/node_modules/d3-time/src/year.js.map +1 -1
  159. package/dist/node_modules/d3-time-format/src/defaultLocale.js.map +1 -1
  160. package/dist/node_modules/d3-time-format/src/locale.js.map +1 -1
  161. package/dist/node_modules/decimal.js-light/decimal.js.map +1 -1
  162. package/dist/node_modules/eventemitter3/index.js.map +1 -1
  163. package/dist/node_modules/fast-equals/dist/es/index.js.map +1 -1
  164. package/dist/node_modules/internmap/src/index.js.map +1 -1
  165. package/dist/node_modules/lodash/_DataView.js.map +1 -1
  166. package/dist/node_modules/lodash/_Hash.js.map +1 -1
  167. package/dist/node_modules/lodash/_ListCache.js.map +1 -1
  168. package/dist/node_modules/lodash/_Map.js.map +1 -1
  169. package/dist/node_modules/lodash/_MapCache.js.map +1 -1
  170. package/dist/node_modules/lodash/_Promise.js.map +1 -1
  171. package/dist/node_modules/lodash/_Set.js.map +1 -1
  172. package/dist/node_modules/lodash/_SetCache.js.map +1 -1
  173. package/dist/node_modules/lodash/_Stack.js +2 -1
  174. package/dist/node_modules/lodash/_Stack.js.map +1 -1
  175. package/dist/node_modules/lodash/_Symbol.js.map +1 -1
  176. package/dist/node_modules/lodash/_Uint8Array.js.map +1 -1
  177. package/dist/node_modules/lodash/_WeakMap.js.map +1 -1
  178. package/dist/node_modules/lodash/_apply.js.map +1 -1
  179. package/dist/node_modules/lodash/_arrayEvery.js.map +1 -1
  180. package/dist/node_modules/lodash/_arrayFilter.js.map +1 -1
  181. package/dist/node_modules/lodash/_arrayIncludes.js.map +1 -1
  182. package/dist/node_modules/lodash/_arrayIncludesWith.js.map +1 -1
  183. package/dist/node_modules/lodash/_arrayLikeKeys.js.map +1 -1
  184. package/dist/node_modules/lodash/_arrayMap.js.map +1 -1
  185. package/dist/node_modules/lodash/_arrayPush.js.map +1 -1
  186. package/dist/node_modules/lodash/_arraySome.js.map +1 -1
  187. package/dist/node_modules/lodash/_asciiToArray.js.map +1 -1
  188. package/dist/node_modules/lodash/_assocIndexOf.js.map +1 -1
  189. package/dist/node_modules/lodash/_baseAssignValue.js.map +1 -1
  190. package/dist/node_modules/lodash/_baseEach.js.map +1 -1
  191. package/dist/node_modules/lodash/_baseEvery.js.map +1 -1
  192. package/dist/node_modules/lodash/_baseExtremum.js.map +1 -1
  193. package/dist/node_modules/lodash/_baseFindIndex.js.map +1 -1
  194. package/dist/node_modules/lodash/_baseFlatten.js.map +1 -1
  195. package/dist/node_modules/lodash/_baseFor.js.map +1 -1
  196. package/dist/node_modules/lodash/_baseForOwn.js.map +1 -1
  197. package/dist/node_modules/lodash/_baseGet.js.map +1 -1
  198. package/dist/node_modules/lodash/_baseGetAllKeys.js.map +1 -1
  199. package/dist/node_modules/lodash/_baseGetTag.js.map +1 -1
  200. package/dist/node_modules/lodash/_baseGt.js.map +1 -1
  201. package/dist/node_modules/lodash/_baseHasIn.js.map +1 -1
  202. package/dist/node_modules/lodash/_baseIndexOf.js.map +1 -1
  203. package/dist/node_modules/lodash/_baseIsArguments.js.map +1 -1
  204. package/dist/node_modules/lodash/_baseIsEqual.js.map +1 -1
  205. package/dist/node_modules/lodash/_baseIsEqualDeep.js.map +1 -1
  206. package/dist/node_modules/lodash/_baseIsMatch.js.map +1 -1
  207. package/dist/node_modules/lodash/_baseIsNaN.js.map +1 -1
  208. package/dist/node_modules/lodash/_baseIsNative.js.map +1 -1
  209. package/dist/node_modules/lodash/_baseIsTypedArray.js.map +1 -1
  210. package/dist/node_modules/lodash/_baseIteratee.js.map +1 -1
  211. package/dist/node_modules/lodash/_baseKeys.js.map +1 -1
  212. package/dist/node_modules/lodash/_baseLt.js.map +1 -1
  213. package/dist/node_modules/lodash/_baseMap.js.map +1 -1
  214. package/dist/node_modules/lodash/_baseMatches.js.map +1 -1
  215. package/dist/node_modules/lodash/_baseMatchesProperty.js.map +1 -1
  216. package/dist/node_modules/lodash/_baseOrderBy.js.map +1 -1
  217. package/dist/node_modules/lodash/_baseProperty.js.map +1 -1
  218. package/dist/node_modules/lodash/_basePropertyDeep.js.map +1 -1
  219. package/dist/node_modules/lodash/_baseRange.js.map +1 -1
  220. package/dist/node_modules/lodash/_baseRest.js.map +1 -1
  221. package/dist/node_modules/lodash/_baseSetToString.js.map +1 -1
  222. package/dist/node_modules/lodash/_baseSlice.js.map +1 -1
  223. package/dist/node_modules/lodash/_baseSome.js.map +1 -1
  224. package/dist/node_modules/lodash/_baseSortBy.js.map +1 -1
  225. package/dist/node_modules/lodash/_baseTimes.js.map +1 -1
  226. package/dist/node_modules/lodash/_baseToString.js.map +1 -1
  227. package/dist/node_modules/lodash/_baseTrim.js.map +1 -1
  228. package/dist/node_modules/lodash/_baseUnary.js.map +1 -1
  229. package/dist/node_modules/lodash/_baseUniq.js.map +1 -1
  230. package/dist/node_modules/lodash/_cacheHas.js.map +1 -1
  231. package/dist/node_modules/lodash/_castPath.js.map +1 -1
  232. package/dist/node_modules/lodash/_castSlice.js.map +1 -1
  233. package/dist/node_modules/lodash/_compareAscending.js.map +1 -1
  234. package/dist/node_modules/lodash/_compareMultiple.js.map +1 -1
  235. package/dist/node_modules/lodash/_coreJsData.js.map +1 -1
  236. package/dist/node_modules/lodash/_createBaseEach.js.map +1 -1
  237. package/dist/node_modules/lodash/_createBaseFor.js.map +1 -1
  238. package/dist/node_modules/lodash/_createCaseFirst.js.map +1 -1
  239. package/dist/node_modules/lodash/_createFind.js.map +1 -1
  240. package/dist/node_modules/lodash/_createRange.js.map +1 -1
  241. package/dist/node_modules/lodash/_createSet.js.map +1 -1
  242. package/dist/node_modules/lodash/_defineProperty.js.map +1 -1
  243. package/dist/node_modules/lodash/_equalArrays.js.map +1 -1
  244. package/dist/node_modules/lodash/_equalByTag.js.map +1 -1
  245. package/dist/node_modules/lodash/_equalObjects.js.map +1 -1
  246. package/dist/node_modules/lodash/_freeGlobal.js.map +1 -1
  247. package/dist/node_modules/lodash/_getAllKeys.js.map +1 -1
  248. package/dist/node_modules/lodash/_getMapData.js.map +1 -1
  249. package/dist/node_modules/lodash/_getMatchData.js.map +1 -1
  250. package/dist/node_modules/lodash/_getNative.js.map +1 -1
  251. package/dist/node_modules/lodash/_getPrototype.js.map +1 -1
  252. package/dist/node_modules/lodash/_getRawTag.js.map +1 -1
  253. package/dist/node_modules/lodash/_getSymbols.js.map +1 -1
  254. package/dist/node_modules/lodash/_getTag.js.map +1 -1
  255. package/dist/node_modules/lodash/_getValue.js.map +1 -1
  256. package/dist/node_modules/lodash/_hasPath.js.map +1 -1
  257. package/dist/node_modules/lodash/_hasUnicode.js.map +1 -1
  258. package/dist/node_modules/lodash/_hashClear.js.map +1 -1
  259. package/dist/node_modules/lodash/_hashDelete.js.map +1 -1
  260. package/dist/node_modules/lodash/_hashGet.js.map +1 -1
  261. package/dist/node_modules/lodash/_hashHas.js.map +1 -1
  262. package/dist/node_modules/lodash/_hashSet.js.map +1 -1
  263. package/dist/node_modules/lodash/_isFlattenable.js.map +1 -1
  264. package/dist/node_modules/lodash/_isIndex.js.map +1 -1
  265. package/dist/node_modules/lodash/_isIterateeCall.js.map +1 -1
  266. package/dist/node_modules/lodash/_isKey.js.map +1 -1
  267. package/dist/node_modules/lodash/_isKeyable.js.map +1 -1
  268. package/dist/node_modules/lodash/_isMasked.js.map +1 -1
  269. package/dist/node_modules/lodash/_isPrototype.js.map +1 -1
  270. package/dist/node_modules/lodash/_isStrictComparable.js.map +1 -1
  271. package/dist/node_modules/lodash/_listCacheClear.js.map +1 -1
  272. package/dist/node_modules/lodash/_listCacheDelete.js.map +1 -1
  273. package/dist/node_modules/lodash/_listCacheGet.js.map +1 -1
  274. package/dist/node_modules/lodash/_listCacheHas.js.map +1 -1
  275. package/dist/node_modules/lodash/_listCacheSet.js.map +1 -1
  276. package/dist/node_modules/lodash/_mapCacheClear.js.map +1 -1
  277. package/dist/node_modules/lodash/_mapCacheDelete.js.map +1 -1
  278. package/dist/node_modules/lodash/_mapCacheGet.js.map +1 -1
  279. package/dist/node_modules/lodash/_mapCacheHas.js.map +1 -1
  280. package/dist/node_modules/lodash/_mapCacheSet.js.map +1 -1
  281. package/dist/node_modules/lodash/_mapToArray.js.map +1 -1
  282. package/dist/node_modules/lodash/_matchesStrictComparable.js.map +1 -1
  283. package/dist/node_modules/lodash/_memoizeCapped.js.map +1 -1
  284. package/dist/node_modules/lodash/_nativeCreate.js.map +1 -1
  285. package/dist/node_modules/lodash/_nativeKeys.js.map +1 -1
  286. package/dist/node_modules/lodash/_nodeUtil.js.map +1 -1
  287. package/dist/node_modules/lodash/_objectToString.js.map +1 -1
  288. package/dist/node_modules/lodash/_overArg.js.map +1 -1
  289. package/dist/node_modules/lodash/_overRest.js.map +1 -1
  290. package/dist/node_modules/lodash/_root.js.map +1 -1
  291. package/dist/node_modules/lodash/_setCacheAdd.js.map +1 -1
  292. package/dist/node_modules/lodash/_setCacheHas.js.map +1 -1
  293. package/dist/node_modules/lodash/_setToArray.js.map +1 -1
  294. package/dist/node_modules/lodash/_setToString.js.map +1 -1
  295. package/dist/node_modules/lodash/_shortOut.js.map +1 -1
  296. package/dist/node_modules/lodash/_stackClear.js.map +1 -1
  297. package/dist/node_modules/lodash/_stackDelete.js.map +1 -1
  298. package/dist/node_modules/lodash/_stackGet.js.map +1 -1
  299. package/dist/node_modules/lodash/_stackHas.js.map +1 -1
  300. package/dist/node_modules/lodash/_stackSet.js.map +1 -1
  301. package/dist/node_modules/lodash/_strictIndexOf.js.map +1 -1
  302. package/dist/node_modules/lodash/_stringToArray.js.map +1 -1
  303. package/dist/node_modules/lodash/_stringToPath.js.map +1 -1
  304. package/dist/node_modules/lodash/_toKey.js.map +1 -1
  305. package/dist/node_modules/lodash/_toSource.js.map +1 -1
  306. package/dist/node_modules/lodash/_trimmedEndIndex.js.map +1 -1
  307. package/dist/node_modules/lodash/_unicodeToArray.js.map +1 -1
  308. package/dist/node_modules/lodash/constant.js.map +1 -1
  309. package/dist/node_modules/lodash/debounce.js.map +1 -1
  310. package/dist/node_modules/lodash/eq.js.map +1 -1
  311. package/dist/node_modules/lodash/every.js.map +1 -1
  312. package/dist/node_modules/lodash/find.js.map +1 -1
  313. package/dist/node_modules/lodash/findIndex.js.map +1 -1
  314. package/dist/node_modules/lodash/flatMap.js.map +1 -1
  315. package/dist/node_modules/lodash/get.js.map +1 -1
  316. package/dist/node_modules/lodash/hasIn.js.map +1 -1
  317. package/dist/node_modules/lodash/identity.js.map +1 -1
  318. package/dist/node_modules/lodash/isArguments.js.map +1 -1
  319. package/dist/node_modules/lodash/isArray.js.map +1 -1
  320. package/dist/node_modules/lodash/isArrayLike.js.map +1 -1
  321. package/dist/node_modules/lodash/isBoolean.js.map +1 -1
  322. package/dist/node_modules/lodash/isBuffer.js.map +1 -1
  323. package/dist/node_modules/lodash/isEqual.js.map +1 -1
  324. package/dist/node_modules/lodash/isFunction.js.map +1 -1
  325. package/dist/node_modules/lodash/isLength.js.map +1 -1
  326. package/dist/node_modules/lodash/isNaN.js.map +1 -1
  327. package/dist/node_modules/lodash/isNil.js.map +1 -1
  328. package/dist/node_modules/lodash/isNumber.js.map +1 -1
  329. package/dist/node_modules/lodash/isObject.js.map +1 -1
  330. package/dist/node_modules/lodash/isObjectLike.js.map +1 -1
  331. package/dist/node_modules/lodash/isPlainObject.js.map +1 -1
  332. package/dist/node_modules/lodash/isString.js.map +1 -1
  333. package/dist/node_modules/lodash/isSymbol.js.map +1 -1
  334. package/dist/node_modules/lodash/isTypedArray.js.map +1 -1
  335. package/dist/node_modules/lodash/keys.js.map +1 -1
  336. package/dist/node_modules/lodash/last.js.map +1 -1
  337. package/dist/node_modules/lodash/map.js.map +1 -1
  338. package/dist/node_modules/lodash/mapValues.js.map +1 -1
  339. package/dist/node_modules/lodash/max.js.map +1 -1
  340. package/dist/node_modules/lodash/memoize.js.map +1 -1
  341. package/dist/node_modules/lodash/min.js.map +1 -1
  342. package/dist/node_modules/lodash/noop.js.map +1 -1
  343. package/dist/node_modules/lodash/now.js.map +1 -1
  344. package/dist/node_modules/lodash/property.js.map +1 -1
  345. package/dist/node_modules/lodash/range.js.map +1 -1
  346. package/dist/node_modules/lodash/some.js.map +1 -1
  347. package/dist/node_modules/lodash/sortBy.js.map +1 -1
  348. package/dist/node_modules/lodash/stubArray.js.map +1 -1
  349. package/dist/node_modules/lodash/stubFalse.js.map +1 -1
  350. package/dist/node_modules/lodash/throttle.js.map +1 -1
  351. package/dist/node_modules/lodash/toFinite.js.map +1 -1
  352. package/dist/node_modules/lodash/toInteger.js.map +1 -1
  353. package/dist/node_modules/lodash/toNumber.js.map +1 -1
  354. package/dist/node_modules/lodash/toString.js.map +1 -1
  355. package/dist/node_modules/lodash/uniqBy.js.map +1 -1
  356. package/dist/node_modules/lodash/upperFirst.js.map +1 -1
  357. package/dist/node_modules/object-assign/index.js.map +1 -1
  358. package/dist/node_modules/prop-types/checkPropTypes.js.map +1 -1
  359. package/dist/node_modules/prop-types/factoryWithThrowingShims.js.map +1 -1
  360. package/dist/node_modules/prop-types/factoryWithTypeCheckers.js.map +1 -1
  361. package/dist/node_modules/prop-types/index.js.map +1 -1
  362. package/dist/node_modules/prop-types/lib/ReactPropTypesSecret.js.map +1 -1
  363. package/dist/node_modules/prop-types/lib/has.js.map +1 -1
  364. package/dist/node_modules/prop-types/node_modules/react-is/cjs/react-is.development.js.map +1 -1
  365. package/dist/node_modules/prop-types/node_modules/react-is/cjs/react-is.production.min.js.map +1 -1
  366. package/dist/node_modules/prop-types/node_modules/react-is/index.js.map +1 -1
  367. package/dist/node_modules/react-is/cjs/react-is.development.js.map +1 -1
  368. package/dist/node_modules/react-is/cjs/react-is.production.min.js.map +1 -1
  369. package/dist/node_modules/react-is/index.js.map +1 -1
  370. package/dist/node_modules/react-smooth/es6/Animate.js +2 -2
  371. package/dist/node_modules/react-smooth/es6/Animate.js.map +1 -1
  372. package/dist/node_modules/react-smooth/es6/AnimateManager.js.map +1 -1
  373. package/dist/node_modules/react-smooth/es6/configUpdate.js.map +1 -1
  374. package/dist/node_modules/react-smooth/es6/easing.js.map +1 -1
  375. package/dist/node_modules/react-smooth/es6/setRafTimeout.js.map +1 -1
  376. package/dist/node_modules/react-smooth/es6/util.js.map +1 -1
  377. package/dist/node_modules/recharts/es6/cartesian/Bar.js +9 -9
  378. package/dist/node_modules/recharts/es6/cartesian/Bar.js.map +1 -1
  379. package/dist/node_modules/recharts/es6/cartesian/Brush.js +12 -12
  380. package/dist/node_modules/recharts/es6/cartesian/Brush.js.map +1 -1
  381. package/dist/node_modules/recharts/es6/cartesian/CartesianAxis.js +7 -7
  382. package/dist/node_modules/recharts/es6/cartesian/CartesianAxis.js.map +1 -1
  383. package/dist/node_modules/recharts/es6/cartesian/CartesianGrid.js +13 -13
  384. package/dist/node_modules/recharts/es6/cartesian/CartesianGrid.js.map +1 -1
  385. package/dist/node_modules/recharts/es6/cartesian/ErrorBar.js +4 -4
  386. package/dist/node_modules/recharts/es6/cartesian/ErrorBar.js.map +1 -1
  387. package/dist/node_modules/recharts/es6/cartesian/ReferenceArea.js +3 -3
  388. package/dist/node_modules/recharts/es6/cartesian/ReferenceArea.js.map +1 -1
  389. package/dist/node_modules/recharts/es6/cartesian/ReferenceDot.js +3 -3
  390. package/dist/node_modules/recharts/es6/cartesian/ReferenceDot.js.map +1 -1
  391. package/dist/node_modules/recharts/es6/cartesian/ReferenceLine.js +4 -4
  392. package/dist/node_modules/recharts/es6/cartesian/ReferenceLine.js.map +1 -1
  393. package/dist/node_modules/recharts/es6/cartesian/XAxis.js +3 -3
  394. package/dist/node_modules/recharts/es6/cartesian/XAxis.js.map +1 -1
  395. package/dist/node_modules/recharts/es6/cartesian/YAxis.js +3 -3
  396. package/dist/node_modules/recharts/es6/cartesian/YAxis.js.map +1 -1
  397. package/dist/node_modules/recharts/es6/cartesian/getEquidistantTicks.js.map +1 -1
  398. package/dist/node_modules/recharts/es6/cartesian/getTicks.js.map +1 -1
  399. package/dist/node_modules/recharts/es6/chart/AccessibilityManager.js +1 -1
  400. package/dist/node_modules/recharts/es6/chart/AccessibilityManager.js.map +1 -1
  401. package/dist/node_modules/recharts/es6/chart/BarChart.js.map +1 -1
  402. package/dist/node_modules/recharts/es6/chart/generateCategoricalChart.js +22 -22
  403. package/dist/node_modules/recharts/es6/chart/generateCategoricalChart.js.map +1 -1
  404. package/dist/node_modules/recharts/es6/component/Cell.js.map +1 -1
  405. package/dist/node_modules/recharts/es6/component/Cursor.js +1 -1
  406. package/dist/node_modules/recharts/es6/component/Cursor.js.map +1 -1
  407. package/dist/node_modules/recharts/es6/component/DefaultLegendContent.js +11 -11
  408. package/dist/node_modules/recharts/es6/component/DefaultLegendContent.js.map +1 -1
  409. package/dist/node_modules/recharts/es6/component/DefaultTooltipContent.js +6 -6
  410. package/dist/node_modules/recharts/es6/component/DefaultTooltipContent.js.map +1 -1
  411. package/dist/node_modules/recharts/es6/component/Label.js +14 -14
  412. package/dist/node_modules/recharts/es6/component/Label.js.map +1 -1
  413. package/dist/node_modules/recharts/es6/component/LabelList.js +6 -6
  414. package/dist/node_modules/recharts/es6/component/LabelList.js.map +1 -1
  415. package/dist/node_modules/recharts/es6/component/Legend.js +5 -5
  416. package/dist/node_modules/recharts/es6/component/Legend.js.map +1 -1
  417. package/dist/node_modules/recharts/es6/component/ResponsiveContainer.js +3 -3
  418. package/dist/node_modules/recharts/es6/component/ResponsiveContainer.js.map +1 -1
  419. package/dist/node_modules/recharts/es6/component/Text.js +2 -2
  420. package/dist/node_modules/recharts/es6/component/Text.js.map +1 -1
  421. package/dist/node_modules/recharts/es6/component/Tooltip.js +3 -3
  422. package/dist/node_modules/recharts/es6/component/Tooltip.js.map +1 -1
  423. package/dist/node_modules/recharts/es6/component/TooltipBoundingBox.js +2 -2
  424. package/dist/node_modules/recharts/es6/component/TooltipBoundingBox.js.map +1 -1
  425. package/dist/node_modules/recharts/es6/container/Layer.js +2 -2
  426. package/dist/node_modules/recharts/es6/container/Layer.js.map +1 -1
  427. package/dist/node_modules/recharts/es6/container/Surface.js +2 -2
  428. package/dist/node_modules/recharts/es6/container/Surface.js.map +1 -1
  429. package/dist/node_modules/recharts/es6/context/chartLayoutContext.js +2 -2
  430. package/dist/node_modules/recharts/es6/context/chartLayoutContext.js.map +1 -1
  431. package/dist/node_modules/recharts/es6/shape/Cross.js +1 -1
  432. package/dist/node_modules/recharts/es6/shape/Cross.js.map +1 -1
  433. package/dist/node_modules/recharts/es6/shape/Curve.js +1 -1
  434. package/dist/node_modules/recharts/es6/shape/Curve.js.map +1 -1
  435. package/dist/node_modules/recharts/es6/shape/Dot.js +1 -1
  436. package/dist/node_modules/recharts/es6/shape/Dot.js.map +1 -1
  437. package/dist/node_modules/recharts/es6/shape/Rectangle.js +4 -4
  438. package/dist/node_modules/recharts/es6/shape/Rectangle.js.map +1 -1
  439. package/dist/node_modules/recharts/es6/shape/Sector.js +1 -1
  440. package/dist/node_modules/recharts/es6/shape/Sector.js.map +1 -1
  441. package/dist/node_modules/recharts/es6/shape/Symbols.js +1 -1
  442. package/dist/node_modules/recharts/es6/shape/Symbols.js.map +1 -1
  443. package/dist/node_modules/recharts/es6/shape/Trapezoid.js +4 -4
  444. package/dist/node_modules/recharts/es6/shape/Trapezoid.js.map +1 -1
  445. package/dist/node_modules/recharts/es6/util/ActiveShapeUtils.js +9 -9
  446. package/dist/node_modules/recharts/es6/util/ActiveShapeUtils.js.map +1 -1
  447. package/dist/node_modules/recharts/es6/util/BarUtils.js +1 -1
  448. package/dist/node_modules/recharts/es6/util/BarUtils.js.map +1 -1
  449. package/dist/node_modules/recharts/es6/util/CartesianUtils.js +1 -1
  450. package/dist/node_modules/recharts/es6/util/CartesianUtils.js.map +1 -1
  451. package/dist/node_modules/recharts/es6/util/ChartUtils.js.map +1 -1
  452. package/dist/node_modules/recharts/es6/util/CssPrefixUtils.js.map +1 -1
  453. package/dist/node_modules/recharts/es6/util/DOMUtils.js.map +1 -1
  454. package/dist/node_modules/recharts/es6/util/DataUtils.js.map +1 -1
  455. package/dist/node_modules/recharts/es6/util/DetectReferenceElementsDomain.js.map +1 -1
  456. package/dist/node_modules/recharts/es6/util/Events.js.map +1 -1
  457. package/dist/node_modules/recharts/es6/util/Global.js.map +1 -1
  458. package/dist/node_modules/recharts/es6/util/IfOverflowMatches.js.map +1 -1
  459. package/dist/node_modules/recharts/es6/util/LogUtils.js.map +1 -1
  460. package/dist/node_modules/recharts/es6/util/PolarUtils.js.map +1 -1
  461. package/dist/node_modules/recharts/es6/util/ReactUtils.js +1 -1
  462. package/dist/node_modules/recharts/es6/util/ReactUtils.js.map +1 -1
  463. package/dist/node_modules/recharts/es6/util/ReduceCSSCalc.js +1 -1
  464. package/dist/node_modules/recharts/es6/util/ReduceCSSCalc.js.map +1 -1
  465. package/dist/node_modules/recharts/es6/util/ShallowEqual.js.map +1 -1
  466. package/dist/node_modules/recharts/es6/util/TickUtils.js.map +1 -1
  467. package/dist/node_modules/recharts/es6/util/calculateViewBox.js.map +1 -1
  468. package/dist/node_modules/recharts/es6/util/cursor/getCursorPoints.js.map +1 -1
  469. package/dist/node_modules/recharts/es6/util/cursor/getCursorRectangle.js.map +1 -1
  470. package/dist/node_modules/recharts/es6/util/cursor/getRadialCursorPoints.js.map +1 -1
  471. package/dist/node_modules/recharts/es6/util/getEveryNthWithCondition.js.map +1 -1
  472. package/dist/node_modules/recharts/es6/util/getLegendProps.js.map +1 -1
  473. package/dist/node_modules/recharts/es6/util/isDomainSpecifiedByUser.js.map +1 -1
  474. package/dist/node_modules/recharts/es6/util/payload/getUniqPayload.js.map +1 -1
  475. package/dist/node_modules/recharts/es6/util/tooltip/translate.js.map +1 -1
  476. package/dist/node_modules/recharts/es6/util/types.js +1 -1
  477. package/dist/node_modules/recharts/es6/util/types.js.map +1 -1
  478. package/dist/node_modules/recharts-scale/es6/getNiceTickValues.js.map +1 -1
  479. package/dist/node_modules/recharts-scale/es6/util/arithmetic.js.map +1 -1
  480. package/dist/node_modules/recharts-scale/es6/util/utils.js.map +1 -1
  481. package/dist/node_modules/tailwind-merge/dist/bundle-mjs.js +1 -1
  482. package/dist/node_modules/tailwind-merge/dist/bundle-mjs.js.map +1 -1
  483. package/dist/node_modules/tiny-invariant/dist/esm/tiny-invariant.js.map +1 -1
  484. package/package.json +2 -2
@@ -1 +1 @@
1
- {"version":3,"file":"useEcoComments.js","names":[],"sources":["../../../src/ecoimpacthub/hooks/useEcoComments.ts"],"sourcesContent":["/**\n * EcoImpactHub — wspace-conversations-svc EntityComment REUSE hooks (Integration B).\n * @module ecoimpacthub/hooks/useEcoComments\n *\n * Adds a discussion surface to eco entities WITHOUT any eco-svc change by\n * reusing the generic EntityComment API composed into the WORKSPACE supergraph.\n * Comments attach to any entity by `(entityType, entityId)`:\n * - `getEntityComments(input): EntityCommentConnection!`\n * - `createEntityComment(input): EntityComment!`\n *\n * Transport reuses `wspaceFetch` from `useEcoFiles` (workspace gateway +\n * workspace token). Lives in a separate file; `useEcoImpactHubApi.ts` is\n * untouched.\n */\n\nimport { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';\n\nimport { useEcoImpactHub } from '../context';\nimport { wspaceFetch } from './useEcoFiles';\n\nexport interface EcoComment {\n id: string;\n authorId: string;\n authorType: string;\n content: {\n text: string | null;\n markdown: string | null;\n };\n createdAt: string;\n edited: boolean;\n parentCommentId: string | null;\n replyCount: number;\n}\n\nconst commentsKey = (entityType: string, entityId: string) =>\n ['ecoimpacthub', 'comments', entityType, entityId] as const;\n\n/** Top-level (root) comments for an eco entity, oldest first. */\nexport const useEcoComments = (entityType: string, entityId: string | undefined) => {\n const { apiGatewayUrl } = useEcoImpactHub();\n return useQuery({\n queryKey: commentsKey(entityType, entityId ?? ''),\n enabled: Boolean(entityId),\n queryFn: async () => {\n const data = await wspaceFetch<{\n getEntityComments: { items: EcoComment[]; total: number };\n }>(\n `query EcoGetEntityComments($input: GetCommentsInput!) {\n getEntityComments(input: $input) {\n total\n items {\n id\n authorId\n authorType\n content { text markdown }\n createdAt\n edited\n parentCommentId\n replyCount\n }\n }\n }`,\n {\n input: {\n entityType,\n entityId,\n take: 100,\n parentCommentId: null,\n },\n },\n apiGatewayUrl\n );\n // Render root comments oldest-first (server order is not guaranteed).\n const roots = data.getEntityComments.items.filter((c) => !c.parentCommentId);\n roots.sort((a, b) => new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime());\n return { items: roots, total: data.getEntityComments.total };\n },\n });\n};\n\nexport const useEcoCreateComment = (entityType: string, entityId: string) => {\n const { apiGatewayUrl } = useEcoImpactHub();\n const qc = useQueryClient();\n return useMutation({\n mutationFn: async (text: string) => {\n const data = await wspaceFetch<{ createEntityComment: { id: string } }>(\n `mutation EcoCreateEntityComment($input: CreateEntityCommentInput!) {\n createEntityComment(input: $input) { id }\n }`,\n {\n input: {\n entityType,\n entityId,\n content: { text },\n },\n },\n apiGatewayUrl\n );\n return data.createEntityComment;\n },\n onSuccess: () => {\n qc.invalidateQueries({ queryKey: commentsKey(entityType, entityId) });\n },\n });\n};\n"],"mappings":";;;;;AAkCA,IAAM,KAAe,GAAoB,MACvC;CAAC;CAAgB;CAAY;CAAY;CAAS,EAGvC,KAAkB,GAAoB,MAAiC;CAClF,IAAM,EAAE,qBAAkB,GAAiB;AAC3C,QAAO,EAAS;EACd,UAAU,EAAY,GAAY,KAAY,GAAG;EACjD,SAAS,EAAQ;EACjB,SAAS,YAAY;GACnB,IAAM,IAAO,MAAM,EAGjB,6YAeA,EACE,OAAO;IACL;IACA;IACA,MAAM;IACN,iBAAiB;IAClB,EACF,EACD,EACD,EAEK,IAAQ,EAAK,kBAAkB,MAAM,QAAQ,MAAM,CAAC,EAAE,gBAAgB;AAE5E,UADA,EAAM,MAAM,GAAG,MAAM,IAAI,KAAK,EAAE,UAAU,CAAC,SAAS,GAAG,IAAI,KAAK,EAAE,UAAU,CAAC,SAAS,CAAC,EAChF;IAAE,OAAO;IAAO,OAAO,EAAK,kBAAkB;IAAO;;EAE/D,CAAC;GAGS,KAAuB,GAAoB,MAAqB;CAC3E,IAAM,EAAE,qBAAkB,GAAiB,EACrC,IAAK,GAAgB;AAC3B,QAAO,EAAY;EACjB,YAAY,OAAO,OACJ,MAAM,EACjB,wIAGA,EACE,OAAO;GACL;GACA;GACA,SAAS,EAAE,SAAM;GAClB,EACF,EACD,EACD,EACW;EAEd,iBAAiB;AACf,KAAG,kBAAkB,EAAE,UAAU,EAAY,GAAY,EAAS,EAAE,CAAC;;EAExE,CAAC"}
1
+ {"version":3,"file":"useEcoComments.js","names":[],"sources":["../../../src/ecoimpacthub/hooks/useEcoComments.ts"],"sourcesContent":["/**\n * EcoImpactHub — wspace-conversations-svc EntityComment REUSE hooks (Integration B).\n * @module ecoimpacthub/hooks/useEcoComments\n *\n * Adds a discussion surface to eco entities WITHOUT any eco-svc change by\n * reusing the generic EntityComment API composed into the WORKSPACE supergraph.\n * Comments attach to any entity by `(entityType, entityId)`:\n * - `getEntityComments(input): EntityCommentConnection!`\n * - `createEntityComment(input): EntityComment!`\n *\n * Transport reuses `wspaceFetch` from `useEcoFiles` (workspace gateway +\n * workspace token). Lives in a separate file; `useEcoImpactHubApi.ts` is\n * untouched.\n */\n\nimport { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';\n\nimport { useEcoImpactHub } from '../context';\nimport { wspaceFetch } from './useEcoFiles';\n\nexport interface EcoComment {\n id: string;\n authorId: string;\n authorType: string;\n content: {\n text: string | null;\n markdown: string | null;\n };\n createdAt: string;\n edited: boolean;\n parentCommentId: string | null;\n replyCount: number;\n}\n\nconst commentsKey = (entityType: string, entityId: string) =>\n ['ecoimpacthub', 'comments', entityType, entityId] as const;\n\n/** Top-level (root) comments for an eco entity, oldest first. */\nexport const useEcoComments = (entityType: string, entityId: string | undefined) => {\n const { apiGatewayUrl } = useEcoImpactHub();\n return useQuery({\n queryKey: commentsKey(entityType, entityId ?? ''),\n enabled: Boolean(entityId),\n queryFn: async () => {\n const data = await wspaceFetch<{\n getEntityComments: { items: EcoComment[]; total: number };\n }>(\n `query EcoGetEntityComments($input: GetCommentsInput!) {\n getEntityComments(input: $input) {\n total\n items {\n id\n authorId\n authorType\n content { text markdown }\n createdAt\n edited\n parentCommentId\n replyCount\n }\n }\n }`,\n {\n input: {\n entityType,\n entityId,\n take: 100,\n parentCommentId: null,\n },\n },\n apiGatewayUrl\n );\n // Render root comments oldest-first (server order is not guaranteed).\n const roots = data.getEntityComments.items.filter((c) => !c.parentCommentId);\n roots.sort((a, b) => new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime());\n return { items: roots, total: data.getEntityComments.total };\n },\n });\n};\n\nexport const useEcoCreateComment = (entityType: string, entityId: string) => {\n const { apiGatewayUrl } = useEcoImpactHub();\n const qc = useQueryClient();\n return useMutation({\n mutationFn: async (text: string) => {\n const data = await wspaceFetch<{ createEntityComment: { id: string } }>(\n `mutation EcoCreateEntityComment($input: CreateEntityCommentInput!) {\n createEntityComment(input: $input) { id }\n }`,\n {\n input: {\n entityType,\n entityId,\n content: { text },\n },\n },\n apiGatewayUrl\n );\n return data.createEntityComment;\n },\n onSuccess: () => {\n qc.invalidateQueries({ queryKey: commentsKey(entityType, entityId) });\n },\n });\n};\n"],"mappings":";;;;;AAkCA,IAAM,KAAe,GAAoB,MACvC;CAAC;CAAgB;CAAY;CAAY;AAAQ,GAGtC,KAAkB,GAAoB,MAAiC;CAClF,IAAM,EAAE,qBAAkB,EAAgB;CAC1C,OAAO,EAAS;EACd,UAAU,EAAY,GAAY,KAAY,EAAE;EAChD,SAAS,EAAQ;EACjB,SAAS,YAAY;GACnB,IAAM,IAAO,MAAM,EAGjB,6YAeA,EACE,OAAO;IACL;IACA;IACA,MAAM;IACN,iBAAiB;GACnB,EACF,GACA,CACF,GAEM,IAAQ,EAAK,kBAAkB,MAAM,QAAQ,MAAM,CAAC,EAAE,eAAe;GAE3E,OADA,EAAM,MAAM,GAAG,MAAM,IAAI,KAAK,EAAE,SAAS,EAAE,QAAQ,IAAI,IAAI,KAAK,EAAE,SAAS,EAAE,QAAQ,CAAC,GAC/E;IAAE,OAAO;IAAO,OAAO,EAAK,kBAAkB;GAAM;EAC7D;CACF,CAAC;AACH,GAEa,KAAuB,GAAoB,MAAqB;CAC3E,IAAM,EAAE,qBAAkB,EAAgB,GACpC,IAAK,EAAe;CAC1B,OAAO,EAAY;EACjB,YAAY,OAAO,OAcV,MAbY,EACjB,wIAGA,EACE,OAAO;GACL;GACA;GACA,SAAS,EAAE,QAAK;EAClB,EACF,GACA,CACF,GACY;EAEd,iBAAiB;GACf,EAAG,kBAAkB,EAAE,UAAU,EAAY,GAAY,CAAQ,EAAE,CAAC;EACtE;CACF,CAAC;AACH"}
@@ -1 +1 @@
1
- {"version":3,"file":"useEcoFiles.js","names":[],"sources":["../../../src/ecoimpacthub/hooks/useEcoFiles.ts"],"sourcesContent":["/**\n * EcoImpactHub — wspace-files-svc REUSE hooks (Integration A).\n * @module ecoimpacthub/hooks/useEcoFiles\n *\n * Reuses the platform file store (`wspace-files-svc`) WITHOUT any eco-svc\n * change: the eco schema already exposes `EcoObservation.evidenceFileId` /\n * `EcoReport.fileId`; this module lets the UI actually produce a `fileId` via\n * the canonical 3-step upload flow composed into the WORKSPACE supergraph:\n * 1. `initiateUpload(input)` → `{ uploadUrl, session { id } }`\n * 2. HTTP `PUT` the bytes to the returned signed `uploadUrl`\n * 3. `completeUpload(input: { sessionId })` → `{ fileId }`\n * plus `getFile(id)` to resolve an existing evidence/report file's name + URL.\n *\n * Transport mirrors `useEcoImpactHubApi` exactly — `graphqlFetch({ gateway:\n * 'workspace', workspaceToken: true })` — so no ApolloProvider is required.\n * These hooks live in a SEPARATE file by design; `useEcoImpactHubApi.ts` is\n * left untouched.\n */\n\nimport { useCallback, useState } from 'react';\nimport { useQuery } from '@tanstack/react-query';\nimport { graphqlFetch } from '@burdenoff/fe-libs/shared/graphql';\n\nimport { useEcoImpactHub } from '../context';\n\n// ============================================================================\n// Shared workspace-gateway transport (reused by the comments + tags hooks too)\n// ============================================================================\n\ntype GraphqlReadCacheGlobals = {\n __graphqlFetchResponseCache?: { clear: () => void };\n __graphqlFetchPendingResponses?: { clear: () => void };\n __boffGraphQLResponseCache?: { clear: () => void };\n};\n\n/**\n * fe-libs `graphqlFetch` coalesces `query` responses in a short-lived in-memory\n * cache. After a write we drop those caches so the invalidation refetch that\n * follows reads fresh data (same remedy `useEcoImpactHubApi` applies).\n */\nconst clearGraphqlReadCaches = (): void => {\n const g = globalThis as GraphqlReadCacheGlobals;\n g.__graphqlFetchResponseCache?.clear();\n g.__graphqlFetchPendingResponses?.clear();\n g.__boffGraphQLResponseCache?.clear();\n};\n\n/** Execute a workspace-gateway GraphQL operation with the workspace token. */\nexport const wspaceFetch = async <TData>(\n query: string,\n variables: Record<string, unknown>,\n baseUrl?: string\n): Promise<TData> => {\n const response = await graphqlFetch<TData>({\n gateway: 'workspace',\n baseUrl,\n query,\n variables,\n workspaceToken: true,\n });\n\n if (response.errors?.length) {\n throw new Error(response.errors[0]?.message ?? 'GraphQL request failed');\n }\n if (!response.data) {\n throw new Error('GraphQL response did not include data');\n }\n if (/^\\s*mutation\\b/.test(query)) {\n clearGraphqlReadCaches();\n }\n return response.data;\n};\n\n// ============================================================================\n// File upload (initiate → PUT → complete)\n// ============================================================================\n\nexport interface UploadedEcoFile {\n fileId: string;\n fileName: string;\n}\n\n/** `PUT` the blob to the signed URL, reporting byte-progress via `onProgress`. */\nfunction putWithProgress(\n url: string,\n file: File,\n contentType: string,\n onProgress: (percent: number) => void\n): Promise<void> {\n return new Promise<void>((resolve, reject) => {\n const xhr = new XMLHttpRequest();\n xhr.open('PUT', url);\n xhr.setRequestHeader('Content-Type', contentType);\n xhr.upload.onprogress = (event) => {\n if (event.lengthComputable) {\n onProgress(Math.round((event.loaded / event.total) * 100));\n }\n };\n xhr.onload = () => {\n if (xhr.status >= 200 && xhr.status < 300) {\n resolve();\n } else {\n reject(new Error(`File storage rejected the upload (HTTP ${xhr.status}).`));\n }\n };\n xhr.onerror = () => reject(new Error('Network error while uploading the file.'));\n xhr.onabort = () => reject(new Error('The upload was cancelled.'));\n xhr.send(file);\n });\n}\n\nexport interface UseEcoFileUploadResult {\n upload: (file: File) => Promise<UploadedEcoFile>;\n isUploading: boolean;\n /** 0–100 byte-progress of the signed-URL PUT. */\n progress: number;\n error: string | null;\n reset: () => void;\n}\n\n/**\n * Imperative upload hook: `await upload(file)` runs the platform 3-step flow and\n * resolves to `{ fileId, fileName }`. `fileId` is what the eco mutations accept\n * (`ecoRecordObservation.evidenceFileId`, `ecoPublishReport.fileId`).\n */\nexport const useEcoFileUpload = (): UseEcoFileUploadResult => {\n const { apiGatewayUrl } = useEcoImpactHub();\n const [isUploading, setIsUploading] = useState(false);\n const [progress, setProgress] = useState(0);\n const [error, setError] = useState<string | null>(null);\n\n const reset = useCallback(() => {\n setIsUploading(false);\n setProgress(0);\n setError(null);\n }, []);\n\n const upload = useCallback(\n async (file: File): Promise<UploadedEcoFile> => {\n setIsUploading(true);\n setProgress(0);\n setError(null);\n try {\n const dotIndex = file.name.lastIndexOf('.');\n const extension = dotIndex > -1 ? file.name.slice(dotIndex + 1) : '';\n const mimeType = file.type || 'application/octet-stream';\n\n // 1. initiate\n const initiated = await wspaceFetch<{\n initiateUpload: { uploadUrl: string | null; session: { id: string } };\n }>(\n `mutation EcoInitiateUpload($input: InitiateUploadInput!) {\n initiateUpload(input: $input) {\n uploadUrl\n session { id }\n }\n }`,\n {\n input: {\n fileName: file.name,\n extension,\n mimeType,\n fileSize: String(file.size),\n visibility: 'WORKSPACE',\n },\n },\n apiGatewayUrl\n );\n\n const { uploadUrl } = initiated.initiateUpload;\n const sessionId = initiated.initiateUpload.session.id;\n if (!uploadUrl) {\n throw new Error('The file service did not return an upload URL.');\n }\n\n // 2. PUT the bytes\n await putWithProgress(uploadUrl, file, mimeType, setProgress);\n\n // 3. complete\n const completed = await wspaceFetch<{\n completeUpload: { fileId: string; file: { id: string; name: string } | null };\n }>(\n `mutation EcoCompleteUpload($input: CompleteUploadInput!) {\n completeUpload(input: $input) {\n fileId\n file { id name }\n }\n }`,\n { input: { sessionId } },\n apiGatewayUrl\n );\n\n setProgress(100);\n return {\n fileId: completed.completeUpload.fileId,\n fileName: completed.completeUpload.file?.name ?? file.name,\n };\n } catch (err) {\n const message = err instanceof Error ? err.message : 'The file could not be uploaded.';\n setError(message);\n throw err instanceof Error ? err : new Error(message);\n } finally {\n setIsUploading(false);\n }\n },\n [apiGatewayUrl]\n );\n\n return { upload, isUploading, progress, error, reset };\n};\n\n// ============================================================================\n// Resolve an existing file (name + download URL) for rendering evidence links\n// ============================================================================\n\nexport interface EcoFileMeta {\n id: string;\n name: string;\n downloadUrl: string | null;\n mimeType: string;\n size: string;\n}\n\nexport const useEcoFile = (id: string | null | undefined) => {\n const { apiGatewayUrl } = useEcoImpactHub();\n return useQuery({\n queryKey: ['ecoimpacthub', 'file', id ?? ''],\n enabled: Boolean(id),\n queryFn: async () => {\n const data = await wspaceFetch<{ getFile: EcoFileMeta | null }>(\n `query EcoGetFile($id: ID!) {\n getFile(id: $id) {\n id\n name\n downloadUrl\n mimeType\n size\n }\n }`,\n { id },\n apiGatewayUrl\n );\n return data.getFile;\n },\n });\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"useEcoFiles.js","names":[],"sources":["../../../src/ecoimpacthub/hooks/useEcoFiles.ts"],"sourcesContent":["/**\n * EcoImpactHub — wspace-files-svc REUSE hooks (Integration A).\n * @module ecoimpacthub/hooks/useEcoFiles\n *\n * Reuses the platform file store (`wspace-files-svc`) WITHOUT any eco-svc\n * change: the eco schema already exposes `EcoObservation.evidenceFileId` /\n * `EcoReport.fileId`; this module lets the UI actually produce a `fileId` via\n * the canonical 3-step upload flow composed into the WORKSPACE supergraph:\n * 1. `initiateUpload(input)` → `{ uploadUrl, session { id } }`\n * 2. HTTP `PUT` the bytes to the returned signed `uploadUrl`\n * 3. `completeUpload(input: { sessionId })` → `{ fileId }`\n * plus `getFile(id)` to resolve an existing evidence/report file's name + URL.\n *\n * Transport mirrors `useEcoImpactHubApi` exactly — `graphqlFetch({ gateway:\n * 'workspace', workspaceToken: true })` — so no ApolloProvider is required.\n * These hooks live in a SEPARATE file by design; `useEcoImpactHubApi.ts` is\n * left untouched.\n */\n\nimport { useCallback, useState } from 'react';\nimport { useQuery } from '@tanstack/react-query';\nimport { graphqlFetch } from '@burdenoff/fe-libs/shared/graphql';\n\nimport { useEcoImpactHub } from '../context';\n\n// ============================================================================\n// Shared workspace-gateway transport (reused by the comments + tags hooks too)\n// ============================================================================\n\ntype GraphqlReadCacheGlobals = {\n __graphqlFetchResponseCache?: { clear: () => void };\n __graphqlFetchPendingResponses?: { clear: () => void };\n __boffGraphQLResponseCache?: { clear: () => void };\n};\n\n/**\n * fe-libs `graphqlFetch` coalesces `query` responses in a short-lived in-memory\n * cache. After a write we drop those caches so the invalidation refetch that\n * follows reads fresh data (same remedy `useEcoImpactHubApi` applies).\n */\nconst clearGraphqlReadCaches = (): void => {\n const g = globalThis as GraphqlReadCacheGlobals;\n g.__graphqlFetchResponseCache?.clear();\n g.__graphqlFetchPendingResponses?.clear();\n g.__boffGraphQLResponseCache?.clear();\n};\n\n/** Execute a workspace-gateway GraphQL operation with the workspace token. */\nexport const wspaceFetch = async <TData>(\n query: string,\n variables: Record<string, unknown>,\n baseUrl?: string\n): Promise<TData> => {\n const response = await graphqlFetch<TData>({\n gateway: 'workspace',\n baseUrl,\n query,\n variables,\n workspaceToken: true,\n });\n\n if (response.errors?.length) {\n throw new Error(response.errors[0]?.message ?? 'GraphQL request failed');\n }\n if (!response.data) {\n throw new Error('GraphQL response did not include data');\n }\n if (/^\\s*mutation\\b/.test(query)) {\n clearGraphqlReadCaches();\n }\n return response.data;\n};\n\n// ============================================================================\n// File upload (initiate → PUT → complete)\n// ============================================================================\n\nexport interface UploadedEcoFile {\n fileId: string;\n fileName: string;\n}\n\n/** `PUT` the blob to the signed URL, reporting byte-progress via `onProgress`. */\nfunction putWithProgress(\n url: string,\n file: File,\n contentType: string,\n onProgress: (percent: number) => void\n): Promise<void> {\n return new Promise<void>((resolve, reject) => {\n const xhr = new XMLHttpRequest();\n xhr.open('PUT', url);\n xhr.setRequestHeader('Content-Type', contentType);\n xhr.upload.onprogress = (event) => {\n if (event.lengthComputable) {\n onProgress(Math.round((event.loaded / event.total) * 100));\n }\n };\n xhr.onload = () => {\n if (xhr.status >= 200 && xhr.status < 300) {\n resolve();\n } else {\n reject(new Error(`File storage rejected the upload (HTTP ${xhr.status}).`));\n }\n };\n xhr.onerror = () => reject(new Error('Network error while uploading the file.'));\n xhr.onabort = () => reject(new Error('The upload was cancelled.'));\n xhr.send(file);\n });\n}\n\nexport interface UseEcoFileUploadResult {\n upload: (file: File) => Promise<UploadedEcoFile>;\n isUploading: boolean;\n /** 0–100 byte-progress of the signed-URL PUT. */\n progress: number;\n error: string | null;\n reset: () => void;\n}\n\n/**\n * Imperative upload hook: `await upload(file)` runs the platform 3-step flow and\n * resolves to `{ fileId, fileName }`. `fileId` is what the eco mutations accept\n * (`ecoRecordObservation.evidenceFileId`, `ecoPublishReport.fileId`).\n */\nexport const useEcoFileUpload = (): UseEcoFileUploadResult => {\n const { apiGatewayUrl } = useEcoImpactHub();\n const [isUploading, setIsUploading] = useState(false);\n const [progress, setProgress] = useState(0);\n const [error, setError] = useState<string | null>(null);\n\n const reset = useCallback(() => {\n setIsUploading(false);\n setProgress(0);\n setError(null);\n }, []);\n\n const upload = useCallback(\n async (file: File): Promise<UploadedEcoFile> => {\n setIsUploading(true);\n setProgress(0);\n setError(null);\n try {\n const dotIndex = file.name.lastIndexOf('.');\n const extension = dotIndex > -1 ? file.name.slice(dotIndex + 1) : '';\n const mimeType = file.type || 'application/octet-stream';\n\n // 1. initiate\n const initiated = await wspaceFetch<{\n initiateUpload: { uploadUrl: string | null; session: { id: string } };\n }>(\n `mutation EcoInitiateUpload($input: InitiateUploadInput!) {\n initiateUpload(input: $input) {\n uploadUrl\n session { id }\n }\n }`,\n {\n input: {\n fileName: file.name,\n extension,\n mimeType,\n fileSize: String(file.size),\n visibility: 'WORKSPACE',\n },\n },\n apiGatewayUrl\n );\n\n const { uploadUrl } = initiated.initiateUpload;\n const sessionId = initiated.initiateUpload.session.id;\n if (!uploadUrl) {\n throw new Error('The file service did not return an upload URL.');\n }\n\n // 2. PUT the bytes\n await putWithProgress(uploadUrl, file, mimeType, setProgress);\n\n // 3. complete\n const completed = await wspaceFetch<{\n completeUpload: { fileId: string; file: { id: string; name: string } | null };\n }>(\n `mutation EcoCompleteUpload($input: CompleteUploadInput!) {\n completeUpload(input: $input) {\n fileId\n file { id name }\n }\n }`,\n { input: { sessionId } },\n apiGatewayUrl\n );\n\n setProgress(100);\n return {\n fileId: completed.completeUpload.fileId,\n fileName: completed.completeUpload.file?.name ?? file.name,\n };\n } catch (err) {\n const message = err instanceof Error ? err.message : 'The file could not be uploaded.';\n setError(message);\n throw err instanceof Error ? err : new Error(message);\n } finally {\n setIsUploading(false);\n }\n },\n [apiGatewayUrl]\n );\n\n return { upload, isUploading, progress, error, reset };\n};\n\n// ============================================================================\n// Resolve an existing file (name + download URL) for rendering evidence links\n// ============================================================================\n\nexport interface EcoFileMeta {\n id: string;\n name: string;\n downloadUrl: string | null;\n mimeType: string;\n size: string;\n}\n\nexport const useEcoFile = (id: string | null | undefined) => {\n const { apiGatewayUrl } = useEcoImpactHub();\n return useQuery({\n queryKey: ['ecoimpacthub', 'file', id ?? ''],\n enabled: Boolean(id),\n queryFn: async () => {\n const data = await wspaceFetch<{ getFile: EcoFileMeta | null }>(\n `query EcoGetFile($id: ID!) {\n getFile(id: $id) {\n id\n name\n downloadUrl\n mimeType\n size\n }\n }`,\n { id },\n apiGatewayUrl\n );\n return data.getFile;\n },\n });\n};\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"useEcoImpactHubApi.js","names":[],"sources":["../../../src/ecoimpacthub/hooks/useEcoImpactHubApi.ts"],"sourcesContent":["/**\n * EcoImpactHub API hooks\n * @module ecoimpacthub/hooks\n *\n * React-Query hooks wired DIRECTLY to the real `wspace-ecoimpacthub-svc`\n * federated GraphQL contract (the `eco*` operations). Every query/mutation\n * below uses an inline gql string whose fields match the svc\n * `.hive-schema.graphql` exactly. No mock data.\n *\n * Transport: `graphqlFetch({ gateway: 'workspace', workspaceToken: true })` —\n * the host shell supplies the gateway base URL + a workspace-scoped token.\n */\n\nimport { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';\nimport { graphqlFetch } from '@burdenoff/fe-libs/shared/graphql';\n\nimport { useEcoImpactHub } from '../context';\nimport type {\n EcoConnection,\n EcoSite,\n EcoSiteStatus,\n EcoSiteType,\n EcoSensor,\n EcoSensorKind,\n EcoSensorStatus,\n EcoMeasurement,\n EcoAlertThreshold,\n EcoSpecies,\n EcoTaxonGroup,\n EcoConservationStatus,\n EcoObservation,\n EcoHabitatProject,\n EcoHabitatProjectStatus,\n EcoRestorationActivity,\n EcoEmissionFactor,\n EcoEmissionScope,\n EcoCarbonEntry,\n EcoCarbonEntryStatus,\n EcoReductionTarget,\n EcoReductionTargetStatus,\n EcoReport,\n EcoReportStatus,\n EcoReportLineItem,\n EcoCreateSiteInput,\n EcoUpdateSiteInput,\n EcoCreateSensorInput,\n EcoRecordMeasurementInput,\n EcoCreateThresholdInput,\n EcoCreateSpeciesInput,\n EcoUpdateSpeciesInput,\n EcoRecordObservationInput,\n EcoCreateHabitatProjectInput,\n EcoLogRestorationActivityInput,\n EcoCreateEmissionFactorInput,\n EcoRecordCarbonEntryInput,\n EcoCreateReductionTargetInput,\n EcoCreateReportInput,\n EcoAddReportLineItemInput,\n PaginationInput,\n} from '../types';\n\n// ============================================================================\n// Transport\n// ============================================================================\n\n/**\n * fe-libs `graphqlFetch` coalesces `query` responses in an in-memory cache keyed\n * by operationName+variables for ~15s (mutations are never cached). A read\n * issued right after a write (e.g. React-Query refetch triggered by our\n * `invalidateQueries`) would otherwise return the PRE-mutation response until the\n * TTL expires — making just-created/updated rows invisible until a full reload.\n * fe-libs' documented remedy is that write-performing callers clear those caches;\n * we do exactly that (the same maps `clearGraphqlFetchResponseCache` clears)\n * after every eco mutation so every follow-up read is fresh.\n */\ntype GraphqlReadCacheGlobals = {\n __graphqlFetchResponseCache?: { clear: () => void };\n __graphqlFetchPendingResponses?: { clear: () => void };\n __boffGraphQLResponseCache?: { clear: () => void };\n};\n\nconst clearGraphqlReadCaches = (): void => {\n const g = globalThis as GraphqlReadCacheGlobals;\n g.__graphqlFetchResponseCache?.clear();\n g.__graphqlFetchPendingResponses?.clear();\n g.__boffGraphQLResponseCache?.clear();\n};\n\nconst ecoQuery = async <TData>(\n query: string,\n variables: Record<string, unknown>,\n baseUrl?: string\n): Promise<TData> => {\n const response = await graphqlFetch<TData>({\n gateway: 'workspace',\n baseUrl,\n query,\n variables,\n workspaceToken: true,\n });\n\n if (response.errors?.length) {\n throw new Error(response.errors[0]?.message ?? 'GraphQL request failed');\n }\n if (!response.data) {\n throw new Error('GraphQL response did not include data');\n }\n // After a write, drop fe-libs' short-lived read caches so the invalidation\n // refetch that follows reads fresh data instead of the pre-mutation snapshot.\n if (/^\\s*mutation\\b/.test(query)) {\n clearGraphqlReadCaches();\n }\n return response.data;\n};\n\n// React-Query key roots (one namespace per domain entity).\nconst KEY = {\n sites: ['ecoimpacthub', 'sites'] as const,\n site: (id: string) => ['ecoimpacthub', 'site', id] as const,\n sensors: ['ecoimpacthub', 'sensors'] as const,\n measurements: ['ecoimpacthub', 'measurements'] as const,\n thresholds: ['ecoimpacthub', 'thresholds'] as const,\n species: ['ecoimpacthub', 'species'] as const,\n speciesOne: (id: string) => ['ecoimpacthub', 'species', id] as const,\n observations: ['ecoimpacthub', 'observations'] as const,\n habitatProjects: ['ecoimpacthub', 'habitat-projects'] as const,\n habitatProject: (id: string) => ['ecoimpacthub', 'habitat-project', id] as const,\n emissionFactors: ['ecoimpacthub', 'emission-factors'] as const,\n carbonEntries: ['ecoimpacthub', 'carbon-entries'] as const,\n carbonTotals: ['ecoimpacthub', 'carbon-totals'] as const,\n reductionTargets: ['ecoimpacthub', 'reduction-targets'] as const,\n reports: ['ecoimpacthub', 'reports'] as const,\n report: (id: string) => ['ecoimpacthub', 'report', id] as const,\n};\n\nconst DEFAULT_PAGE: PaginationInput = { limit: 50, offset: 0 };\n\n// ============================================================================\n// Reusable gql field selections (codegen-free strictness)\n// ============================================================================\n\nconst SITE_FIELDS = `\n id workspaceId name code type status description latitude longitude\n areaHectares country region createdAt updatedAt\n`;\n\nconst SENSOR_FIELDS = `\n id workspaceId siteId name kind status hardwareId unit lastReadingAt installedAt\n createdAt updatedAt\n`;\n\nconst MEASUREMENT_FIELDS = `\n id workspaceId siteId sensorId parameter value unit source recordedAt isBreach notes\n createdAt updatedAt\n`;\n\nconst THRESHOLD_FIELDS = `\n id workspaceId siteId parameter direction severity limitValue minValue maxValue unit\n isActive createdAt updatedAt\n`;\n\nconst SPECIES_FIELDS = `\n id workspaceId commonName scientificName taxonGroup conservationStatus isPriority\n description createdAt updatedAt\n`;\n\nconst OBSERVATION_FIELDS = `\n id workspaceId speciesId siteId method count observedAt latitude longitude observerId\n notes evidence { id } createdAt updatedAt\n`;\n\nconst HABITAT_FIELDS = `\n id workspaceId name status description siteId targetHectares budgetAmount currency\n managerId startDate endDate completedAt createdAt updatedAt\n`;\n\nconst ACTIVITY_FIELDS = `\n id workspaceId projectId type summary details quantity unit performedAt createdAt updatedAt\n`;\n\nconst FACTOR_FIELDS = `\n id workspaceId name scope activityUnit factorKgco2e source validYear isActive\n createdAt updatedAt\n`;\n\nconst CARBON_FIELDS = `\n id workspaceId siteId factorId scope status category activityValue activityUnit co2eKg\n periodStart notes createdAt updatedAt\n`;\n\nconst TARGET_FIELDS = `\n id workspaceId name status metric baselineYear baselineValue targetYear targetValue unit\n description createdAt updatedAt\n`;\n\nconst REPORT_FIELDS = `\n id workspaceId title framework status periodStart periodEnd summary publishedAt\n file { id } createdAt updatedAt\n`;\n\nconst LINE_ITEM_FIELDS = `\n id workspaceId reportId carbonEntryId label value unit category createdAt updatedAt\n`;\n\n// ============================================================================\n// Sites (Environmental Monitoring)\n// ============================================================================\n\nexport interface SiteFilters {\n status?: EcoSiteStatus;\n type?: EcoSiteType;\n pagination?: PaginationInput;\n}\n\nexport const useEcoSites = (filters: SiteFilters = {}) => {\n const { apiGatewayUrl } = useEcoImpactHub();\n return useQuery({\n queryKey: [...KEY.sites, filters],\n queryFn: async () => {\n const result = await ecoQuery<{ ecoListSites: EcoConnection<EcoSite> }>(\n `query EcoListSites($status: EcoSiteStatus, $type: EcoSiteType, $pagination: PaginationInput) {\n ecoListSites(status: $status, type: $type, pagination: $pagination) {\n items { ${SITE_FIELDS} }\n pageInfo { cursor hasMore }\n }\n }`,\n {\n status: filters.status,\n type: filters.type,\n pagination: filters.pagination ?? DEFAULT_PAGE,\n },\n apiGatewayUrl\n );\n return result.ecoListSites;\n },\n });\n};\n\nexport const useEcoSite = (id: string | undefined) => {\n const { apiGatewayUrl } = useEcoImpactHub();\n return useQuery({\n queryKey: KEY.site(id ?? ''),\n enabled: Boolean(id),\n queryFn: async () => {\n const result = await ecoQuery<{ ecoSite: EcoSite | null }>(\n `query EcoSite($id: ID!) {\n ecoSite(id: $id) {\n ${SITE_FIELDS}\n sensors { ${SENSOR_FIELDS} }\n thresholds { ${THRESHOLD_FIELDS} }\n }\n }`,\n { id },\n apiGatewayUrl\n );\n return result.ecoSite;\n },\n });\n};\n\nexport const useEcoCreateSite = () => {\n const { apiGatewayUrl } = useEcoImpactHub();\n const qc = useQueryClient();\n return useMutation({\n mutationFn: async (input: EcoCreateSiteInput) => {\n const result = await ecoQuery<{ ecoCreateSite: EcoSite }>(\n `mutation EcoCreateSite($input: EcoCreateSiteInput!) {\n ecoCreateSite(input: $input) { ${SITE_FIELDS} }\n }`,\n { input },\n apiGatewayUrl\n );\n return result.ecoCreateSite;\n },\n onSuccess: () => qc.invalidateQueries({ queryKey: KEY.sites }),\n });\n};\n\nexport const useEcoUpdateSite = () => {\n const { apiGatewayUrl } = useEcoImpactHub();\n const qc = useQueryClient();\n return useMutation({\n mutationFn: async (vars: { id: string; input: EcoUpdateSiteInput }) => {\n const result = await ecoQuery<{ ecoUpdateSite: EcoSite }>(\n `mutation EcoUpdateSite($id: ID!, $input: EcoUpdateSiteInput!) {\n ecoUpdateSite(id: $id, input: $input) { ${SITE_FIELDS} }\n }`,\n vars,\n apiGatewayUrl\n );\n return result.ecoUpdateSite;\n },\n onSuccess: (data) => {\n qc.invalidateQueries({ queryKey: KEY.sites });\n qc.invalidateQueries({ queryKey: KEY.site(data.id) });\n },\n });\n};\n\nexport const useEcoArchiveSite = () => {\n const { apiGatewayUrl } = useEcoImpactHub();\n const qc = useQueryClient();\n return useMutation({\n mutationFn: async (id: string) => {\n const result = await ecoQuery<{ ecoArchiveSite: boolean }>(\n `mutation EcoArchiveSite($id: ID!) { ecoArchiveSite(id: $id) }`,\n { id },\n apiGatewayUrl\n );\n return result.ecoArchiveSite;\n },\n onSuccess: () => qc.invalidateQueries({ queryKey: KEY.sites }),\n });\n};\n\n// ============================================================================\n// Sensors / Measurements / Thresholds (Environmental Monitoring)\n// ============================================================================\n\nexport interface SensorFilters {\n siteId?: string;\n kind?: EcoSensorKind;\n status?: EcoSensorStatus;\n}\n\nexport const useEcoSensors = (filters: SensorFilters = {}) => {\n const { apiGatewayUrl } = useEcoImpactHub();\n return useQuery({\n queryKey: [...KEY.sensors, filters],\n queryFn: async () => {\n const result = await ecoQuery<{ ecoListSensors: EcoConnection<EcoSensor> }>(\n `query EcoListSensors($siteId: ID, $kind: EcoSensorKind, $status: EcoSensorStatus) {\n ecoListSensors(siteId: $siteId, kind: $kind, status: $status) {\n items { ${SENSOR_FIELDS} }\n pageInfo { cursor hasMore }\n }\n }`,\n { siteId: filters.siteId, kind: filters.kind, status: filters.status },\n apiGatewayUrl\n );\n return result.ecoListSensors;\n },\n });\n};\n\nexport const useEcoCreateSensor = () => {\n const { apiGatewayUrl } = useEcoImpactHub();\n const qc = useQueryClient();\n return useMutation({\n mutationFn: async (input: EcoCreateSensorInput) => {\n const result = await ecoQuery<{ ecoCreateSensor: EcoSensor }>(\n `mutation EcoCreateSensor($input: EcoCreateSensorInput!) {\n ecoCreateSensor(input: $input) { ${SENSOR_FIELDS} }\n }`,\n { input },\n apiGatewayUrl\n );\n return result.ecoCreateSensor;\n },\n onSuccess: (data) => {\n qc.invalidateQueries({ queryKey: KEY.sensors });\n qc.invalidateQueries({ queryKey: KEY.site(data.siteId) });\n },\n });\n};\n\nexport const useEcoSetSensorStatus = () => {\n const { apiGatewayUrl } = useEcoImpactHub();\n const qc = useQueryClient();\n return useMutation({\n mutationFn: async (vars: { id: string; status: EcoSensorStatus }) => {\n const result = await ecoQuery<{ ecoSetSensorStatus: EcoSensor }>(\n `mutation EcoSetSensorStatus($id: ID!, $status: EcoSensorStatus!) {\n ecoSetSensorStatus(id: $id, status: $status) { ${SENSOR_FIELDS} }\n }`,\n vars,\n apiGatewayUrl\n );\n return result.ecoSetSensorStatus;\n },\n onSuccess: (data) => {\n qc.invalidateQueries({ queryKey: KEY.sensors });\n qc.invalidateQueries({ queryKey: KEY.site(data.siteId) });\n },\n });\n};\n\nexport interface MeasurementFilters {\n siteId?: string;\n sensorId?: string;\n parameter?: string;\n breachesOnly?: boolean;\n pagination?: PaginationInput;\n}\n\nexport const useEcoMeasurements = (filters: MeasurementFilters = {}) => {\n const { apiGatewayUrl } = useEcoImpactHub();\n return useQuery({\n queryKey: [...KEY.measurements, filters],\n queryFn: async () => {\n const result = await ecoQuery<{ ecoListMeasurements: EcoConnection<EcoMeasurement> }>(\n `query EcoListMeasurements($siteId: ID, $sensorId: ID, $parameter: String, $breachesOnly: Boolean, $pagination: PaginationInput) {\n ecoListMeasurements(siteId: $siteId, sensorId: $sensorId, parameter: $parameter, breachesOnly: $breachesOnly, pagination: $pagination) {\n items { ${MEASUREMENT_FIELDS} }\n pageInfo { cursor hasMore }\n }\n }`,\n {\n siteId: filters.siteId,\n sensorId: filters.sensorId,\n parameter: filters.parameter,\n breachesOnly: filters.breachesOnly,\n pagination: filters.pagination ?? DEFAULT_PAGE,\n },\n apiGatewayUrl\n );\n return result.ecoListMeasurements;\n },\n });\n};\n\nexport const useEcoRecordMeasurement = () => {\n const { apiGatewayUrl } = useEcoImpactHub();\n const qc = useQueryClient();\n return useMutation({\n mutationFn: async (input: EcoRecordMeasurementInput) => {\n const result = await ecoQuery<{ ecoRecordMeasurement: EcoMeasurement }>(\n `mutation EcoRecordMeasurement($input: EcoRecordMeasurementInput!) {\n ecoRecordMeasurement(input: $input) { ${MEASUREMENT_FIELDS} }\n }`,\n { input },\n apiGatewayUrl\n );\n return result.ecoRecordMeasurement;\n },\n onSuccess: () => qc.invalidateQueries({ queryKey: KEY.measurements }),\n });\n};\n\nexport const useEcoThresholds = (siteId?: string) => {\n const { apiGatewayUrl } = useEcoImpactHub();\n return useQuery({\n queryKey: [...KEY.thresholds, siteId ?? 'all'],\n queryFn: async () => {\n const result = await ecoQuery<{ ecoListThresholds: EcoConnection<EcoAlertThreshold> }>(\n `query EcoListThresholds($siteId: ID) {\n ecoListThresholds(siteId: $siteId) {\n items { ${THRESHOLD_FIELDS} }\n pageInfo { cursor hasMore }\n }\n }`,\n { siteId },\n apiGatewayUrl\n );\n return result.ecoListThresholds;\n },\n });\n};\n\nexport const useEcoCreateThreshold = () => {\n const { apiGatewayUrl } = useEcoImpactHub();\n const qc = useQueryClient();\n return useMutation({\n mutationFn: async (input: EcoCreateThresholdInput) => {\n const result = await ecoQuery<{ ecoCreateThreshold: EcoAlertThreshold }>(\n `mutation EcoCreateThreshold($input: EcoCreateThresholdInput!) {\n ecoCreateThreshold(input: $input) { ${THRESHOLD_FIELDS} }\n }`,\n { input },\n apiGatewayUrl\n );\n return result.ecoCreateThreshold;\n },\n onSuccess: (data) => {\n qc.invalidateQueries({ queryKey: KEY.thresholds });\n qc.invalidateQueries({ queryKey: KEY.site(data.siteId) });\n },\n });\n};\n\nexport const useEcoSetThresholdActive = () => {\n const { apiGatewayUrl } = useEcoImpactHub();\n const qc = useQueryClient();\n return useMutation({\n mutationFn: async (vars: { id: string; isActive: boolean }) => {\n const result = await ecoQuery<{ ecoSetThresholdActive: EcoAlertThreshold }>(\n `mutation EcoSetThresholdActive($id: ID!, $isActive: Boolean!) {\n ecoSetThresholdActive(id: $id, isActive: $isActive) { ${THRESHOLD_FIELDS} }\n }`,\n vars,\n apiGatewayUrl\n );\n return result.ecoSetThresholdActive;\n },\n onSuccess: () => qc.invalidateQueries({ queryKey: KEY.thresholds }),\n });\n};\n\n// ============================================================================\n// Species / Observations / Habitat (Conservation)\n// ============================================================================\n\nexport interface SpeciesFilters {\n taxonGroup?: EcoTaxonGroup;\n conservationStatus?: EcoConservationStatus;\n priorityOnly?: boolean;\n pagination?: PaginationInput;\n}\n\nexport const useEcoSpecies = (filters: SpeciesFilters = {}) => {\n const { apiGatewayUrl } = useEcoImpactHub();\n return useQuery({\n queryKey: [...KEY.species, filters],\n queryFn: async () => {\n const result = await ecoQuery<{ ecoListSpecies: EcoConnection<EcoSpecies> }>(\n `query EcoListSpecies($taxonGroup: EcoTaxonGroup, $conservationStatus: EcoConservationStatus, $priorityOnly: Boolean, $pagination: PaginationInput) {\n ecoListSpecies(taxonGroup: $taxonGroup, conservationStatus: $conservationStatus, priorityOnly: $priorityOnly, pagination: $pagination) {\n items { ${SPECIES_FIELDS} }\n pageInfo { cursor hasMore }\n }\n }`,\n {\n taxonGroup: filters.taxonGroup,\n conservationStatus: filters.conservationStatus,\n priorityOnly: filters.priorityOnly,\n pagination: filters.pagination ?? DEFAULT_PAGE,\n },\n apiGatewayUrl\n );\n return result.ecoListSpecies;\n },\n });\n};\n\nexport const useEcoSpeciesDetail = (id: string | undefined) => {\n const { apiGatewayUrl } = useEcoImpactHub();\n return useQuery({\n queryKey: KEY.speciesOne(id ?? ''),\n enabled: Boolean(id),\n queryFn: async () => {\n const result = await ecoQuery<{ ecoSpecies: EcoSpecies | null }>(\n `query EcoSpeciesDetail($id: ID!) {\n ecoSpecies(id: $id) {\n ${SPECIES_FIELDS}\n observations { ${OBSERVATION_FIELDS} }\n }\n }`,\n { id },\n apiGatewayUrl\n );\n return result.ecoSpecies;\n },\n });\n};\n\nexport const useEcoCreateSpecies = () => {\n const { apiGatewayUrl } = useEcoImpactHub();\n const qc = useQueryClient();\n return useMutation({\n mutationFn: async (input: EcoCreateSpeciesInput) => {\n const result = await ecoQuery<{ ecoCreateSpecies: EcoSpecies }>(\n `mutation EcoCreateSpecies($input: EcoCreateSpeciesInput!) {\n ecoCreateSpecies(input: $input) { ${SPECIES_FIELDS} }\n }`,\n { input },\n apiGatewayUrl\n );\n return result.ecoCreateSpecies;\n },\n onSuccess: () => qc.invalidateQueries({ queryKey: KEY.species }),\n });\n};\n\nexport const useEcoUpdateSpecies = () => {\n const { apiGatewayUrl } = useEcoImpactHub();\n const qc = useQueryClient();\n return useMutation({\n mutationFn: async (vars: { id: string; input: EcoUpdateSpeciesInput }) => {\n const result = await ecoQuery<{ ecoUpdateSpecies: EcoSpecies }>(\n `mutation EcoUpdateSpecies($id: ID!, $input: EcoUpdateSpeciesInput!) {\n ecoUpdateSpecies(id: $id, input: $input) { ${SPECIES_FIELDS} }\n }`,\n vars,\n apiGatewayUrl\n );\n return result.ecoUpdateSpecies;\n },\n onSuccess: (data) => {\n qc.invalidateQueries({ queryKey: KEY.species });\n qc.invalidateQueries({ queryKey: KEY.speciesOne(data.id) });\n },\n });\n};\n\nexport const useEcoArchiveSpecies = () => {\n const { apiGatewayUrl } = useEcoImpactHub();\n const qc = useQueryClient();\n return useMutation({\n mutationFn: async (id: string) => {\n const result = await ecoQuery<{ ecoArchiveSpecies: boolean }>(\n `mutation EcoArchiveSpecies($id: ID!) { ecoArchiveSpecies(id: $id) }`,\n { id },\n apiGatewayUrl\n );\n return result.ecoArchiveSpecies;\n },\n onSuccess: () => qc.invalidateQueries({ queryKey: KEY.species }),\n });\n};\n\nexport interface ObservationFilters {\n speciesId?: string;\n siteId?: string;\n pagination?: PaginationInput;\n}\n\nexport const useEcoObservations = (filters: ObservationFilters = {}) => {\n const { apiGatewayUrl } = useEcoImpactHub();\n return useQuery({\n queryKey: [...KEY.observations, filters],\n queryFn: async () => {\n const result = await ecoQuery<{ ecoListObservations: EcoConnection<EcoObservation> }>(\n `query EcoListObservations($speciesId: ID, $siteId: ID, $pagination: PaginationInput) {\n ecoListObservations(speciesId: $speciesId, siteId: $siteId, pagination: $pagination) {\n items { ${OBSERVATION_FIELDS} species { id commonName scientificName taxonGroup } }\n pageInfo { cursor hasMore }\n }\n }`,\n {\n speciesId: filters.speciesId,\n siteId: filters.siteId,\n pagination: filters.pagination ?? DEFAULT_PAGE,\n },\n apiGatewayUrl\n );\n return result.ecoListObservations;\n },\n });\n};\n\nexport const useEcoRecordObservation = () => {\n const { apiGatewayUrl } = useEcoImpactHub();\n const qc = useQueryClient();\n return useMutation({\n mutationFn: async (input: EcoRecordObservationInput) => {\n const result = await ecoQuery<{ ecoRecordObservation: EcoObservation }>(\n `mutation EcoRecordObservation($input: EcoRecordObservationInput!) {\n ecoRecordObservation(input: $input) { ${OBSERVATION_FIELDS} }\n }`,\n { input },\n apiGatewayUrl\n );\n return result.ecoRecordObservation;\n },\n onSuccess: (data) => {\n qc.invalidateQueries({ queryKey: KEY.observations });\n qc.invalidateQueries({ queryKey: KEY.speciesOne(data.speciesId) });\n },\n });\n};\n\nexport const useEcoHabitatProjects = (status?: EcoHabitatProjectStatus) => {\n const { apiGatewayUrl } = useEcoImpactHub();\n return useQuery({\n queryKey: [...KEY.habitatProjects, status ?? 'all'],\n queryFn: async () => {\n const result = await ecoQuery<{ ecoListHabitatProjects: EcoConnection<EcoHabitatProject> }>(\n `query EcoListHabitatProjects($status: EcoHabitatProjectStatus) {\n ecoListHabitatProjects(status: $status) {\n items { ${HABITAT_FIELDS} }\n pageInfo { cursor hasMore }\n }\n }`,\n { status },\n apiGatewayUrl\n );\n return result.ecoListHabitatProjects;\n },\n });\n};\n\nexport const useEcoHabitatProject = (id: string | undefined) => {\n const { apiGatewayUrl } = useEcoImpactHub();\n return useQuery({\n queryKey: KEY.habitatProject(id ?? ''),\n enabled: Boolean(id),\n queryFn: async () => {\n const result = await ecoQuery<{ ecoHabitatProject: EcoHabitatProject | null }>(\n `query EcoHabitatProject($id: ID!) {\n ecoHabitatProject(id: $id) {\n ${HABITAT_FIELDS}\n activities { ${ACTIVITY_FIELDS} }\n }\n }`,\n { id },\n apiGatewayUrl\n );\n return result.ecoHabitatProject;\n },\n });\n};\n\nexport const useEcoCreateHabitatProject = () => {\n const { apiGatewayUrl } = useEcoImpactHub();\n const qc = useQueryClient();\n return useMutation({\n mutationFn: async (input: EcoCreateHabitatProjectInput) => {\n const result = await ecoQuery<{ ecoCreateHabitatProject: EcoHabitatProject }>(\n `mutation EcoCreateHabitatProject($input: EcoCreateHabitatProjectInput!) {\n ecoCreateHabitatProject(input: $input) { ${HABITAT_FIELDS} }\n }`,\n { input },\n apiGatewayUrl\n );\n return result.ecoCreateHabitatProject;\n },\n onSuccess: () => qc.invalidateQueries({ queryKey: KEY.habitatProjects }),\n });\n};\n\nexport const useEcoTransitionHabitatProject = () => {\n const { apiGatewayUrl } = useEcoImpactHub();\n const qc = useQueryClient();\n return useMutation({\n mutationFn: async (vars: { id: string; status: EcoHabitatProjectStatus }) => {\n const result = await ecoQuery<{ ecoTransitionHabitatProject: EcoHabitatProject }>(\n `mutation EcoTransitionHabitatProject($id: ID!, $status: EcoHabitatProjectStatus!) {\n ecoTransitionHabitatProject(id: $id, status: $status) { ${HABITAT_FIELDS} }\n }`,\n vars,\n apiGatewayUrl\n );\n return result.ecoTransitionHabitatProject;\n },\n onSuccess: (data) => {\n qc.invalidateQueries({ queryKey: KEY.habitatProjects });\n qc.invalidateQueries({ queryKey: KEY.habitatProject(data.id) });\n },\n });\n};\n\nexport const useEcoLogRestorationActivity = () => {\n const { apiGatewayUrl } = useEcoImpactHub();\n const qc = useQueryClient();\n return useMutation({\n mutationFn: async (input: EcoLogRestorationActivityInput) => {\n const result = await ecoQuery<{ ecoLogRestorationActivity: EcoRestorationActivity }>(\n `mutation EcoLogRestorationActivity($input: EcoLogRestorationActivityInput!) {\n ecoLogRestorationActivity(input: $input) { ${ACTIVITY_FIELDS} }\n }`,\n { input },\n apiGatewayUrl\n );\n return result.ecoLogRestorationActivity;\n },\n onSuccess: (data) => qc.invalidateQueries({ queryKey: KEY.habitatProject(data.projectId) }),\n });\n};\n\n// ============================================================================\n// Carbon ledger / Emission factors / Reduction targets (Sustainability)\n// ============================================================================\n\nexport const useEcoEmissionFactors = (scope?: EcoEmissionScope, activeOnly?: boolean) => {\n const { apiGatewayUrl } = useEcoImpactHub();\n return useQuery({\n queryKey: [...KEY.emissionFactors, scope ?? 'all', activeOnly ?? false],\n queryFn: async () => {\n const result = await ecoQuery<{ ecoListEmissionFactors: EcoConnection<EcoEmissionFactor> }>(\n `query EcoListEmissionFactors($scope: EcoEmissionScope, $activeOnly: Boolean) {\n ecoListEmissionFactors(scope: $scope, activeOnly: $activeOnly) {\n items { ${FACTOR_FIELDS} }\n pageInfo { cursor hasMore }\n }\n }`,\n { scope, activeOnly },\n apiGatewayUrl\n );\n return result.ecoListEmissionFactors;\n },\n });\n};\n\nexport const useEcoCreateEmissionFactor = () => {\n const { apiGatewayUrl } = useEcoImpactHub();\n const qc = useQueryClient();\n return useMutation({\n mutationFn: async (input: EcoCreateEmissionFactorInput) => {\n const result = await ecoQuery<{ ecoCreateEmissionFactor: EcoEmissionFactor }>(\n `mutation EcoCreateEmissionFactor($input: EcoCreateEmissionFactorInput!) {\n ecoCreateEmissionFactor(input: $input) { ${FACTOR_FIELDS} }\n }`,\n { input },\n apiGatewayUrl\n );\n return result.ecoCreateEmissionFactor;\n },\n onSuccess: () => qc.invalidateQueries({ queryKey: KEY.emissionFactors }),\n });\n};\n\nexport interface CarbonEntryFilters {\n scope?: EcoEmissionScope;\n siteId?: string;\n status?: EcoCarbonEntryStatus;\n pagination?: PaginationInput;\n}\n\nexport const useEcoCarbonEntries = (filters: CarbonEntryFilters = {}) => {\n const { apiGatewayUrl } = useEcoImpactHub();\n return useQuery({\n queryKey: [...KEY.carbonEntries, filters],\n queryFn: async () => {\n const result = await ecoQuery<{ ecoListCarbonEntries: EcoConnection<EcoCarbonEntry> }>(\n `query EcoListCarbonEntries($scope: EcoEmissionScope, $siteId: ID, $status: EcoCarbonEntryStatus, $pagination: PaginationInput) {\n ecoListCarbonEntries(scope: $scope, siteId: $siteId, status: $status, pagination: $pagination) {\n items { ${CARBON_FIELDS} factor { id name factorKgco2e activityUnit } }\n pageInfo { cursor hasMore }\n }\n }`,\n {\n scope: filters.scope,\n siteId: filters.siteId,\n status: filters.status,\n pagination: filters.pagination ?? DEFAULT_PAGE,\n },\n apiGatewayUrl\n );\n return result.ecoListCarbonEntries;\n },\n });\n};\n\n/** Total CO2e by scope + grand total, in one aliased query. */\nexport interface EcoCarbonTotals {\n total: number;\n scope1: number;\n scope2: number;\n scope3: number;\n}\n\nexport const useEcoCarbonTotals = (siteId?: string) => {\n const { apiGatewayUrl } = useEcoImpactHub();\n return useQuery({\n queryKey: [...KEY.carbonTotals, siteId ?? 'all'],\n queryFn: async (): Promise<EcoCarbonTotals> => {\n const result = await ecoQuery<{\n total: number;\n scope1: number;\n scope2: number;\n scope3: number;\n }>(\n `query EcoCarbonTotals($siteId: ID) {\n total: ecoCarbonTotalKg(siteId: $siteId)\n scope1: ecoCarbonTotalKg(scope: SCOPE_1, siteId: $siteId)\n scope2: ecoCarbonTotalKg(scope: SCOPE_2, siteId: $siteId)\n scope3: ecoCarbonTotalKg(scope: SCOPE_3, siteId: $siteId)\n }`,\n { siteId },\n apiGatewayUrl\n );\n return result;\n },\n });\n};\n\nexport const useEcoRecordCarbonEntry = () => {\n const { apiGatewayUrl } = useEcoImpactHub();\n const qc = useQueryClient();\n return useMutation({\n mutationFn: async (input: EcoRecordCarbonEntryInput) => {\n const result = await ecoQuery<{ ecoRecordCarbonEntry: EcoCarbonEntry }>(\n `mutation EcoRecordCarbonEntry($input: EcoRecordCarbonEntryInput!) {\n ecoRecordCarbonEntry(input: $input) { ${CARBON_FIELDS} }\n }`,\n { input },\n apiGatewayUrl\n );\n return result.ecoRecordCarbonEntry;\n },\n onSuccess: () => {\n qc.invalidateQueries({ queryKey: KEY.carbonEntries });\n qc.invalidateQueries({ queryKey: KEY.carbonTotals });\n },\n });\n};\n\nexport const useEcoVerifyCarbonEntry = () => {\n const { apiGatewayUrl } = useEcoImpactHub();\n const qc = useQueryClient();\n return useMutation({\n mutationFn: async (id: string) => {\n const result = await ecoQuery<{ ecoVerifyCarbonEntry: EcoCarbonEntry }>(\n `mutation EcoVerifyCarbonEntry($id: ID!) {\n ecoVerifyCarbonEntry(id: $id) { ${CARBON_FIELDS} }\n }`,\n { id },\n apiGatewayUrl\n );\n return result.ecoVerifyCarbonEntry;\n },\n onSuccess: () => qc.invalidateQueries({ queryKey: KEY.carbonEntries }),\n });\n};\n\nexport const useEcoReductionTargets = (status?: EcoReductionTargetStatus) => {\n const { apiGatewayUrl } = useEcoImpactHub();\n return useQuery({\n queryKey: [...KEY.reductionTargets, status ?? 'all'],\n queryFn: async () => {\n const result = await ecoQuery<{ ecoListReductionTargets: EcoConnection<EcoReductionTarget> }>(\n `query EcoListReductionTargets($status: EcoReductionTargetStatus) {\n ecoListReductionTargets(status: $status) {\n items { ${TARGET_FIELDS} }\n pageInfo { cursor hasMore }\n }\n }`,\n { status },\n apiGatewayUrl\n );\n return result.ecoListReductionTargets;\n },\n });\n};\n\nexport const useEcoCreateReductionTarget = () => {\n const { apiGatewayUrl } = useEcoImpactHub();\n const qc = useQueryClient();\n return useMutation({\n mutationFn: async (input: EcoCreateReductionTargetInput) => {\n const result = await ecoQuery<{ ecoCreateReductionTarget: EcoReductionTarget }>(\n `mutation EcoCreateReductionTarget($input: EcoCreateReductionTargetInput!) {\n ecoCreateReductionTarget(input: $input) { ${TARGET_FIELDS} }\n }`,\n { input },\n apiGatewayUrl\n );\n return result.ecoCreateReductionTarget;\n },\n onSuccess: () => qc.invalidateQueries({ queryKey: KEY.reductionTargets }),\n });\n};\n\nexport const useEcoSetReductionTargetStatus = () => {\n const { apiGatewayUrl } = useEcoImpactHub();\n const qc = useQueryClient();\n return useMutation({\n mutationFn: async (vars: { id: string; status: EcoReductionTargetStatus }) => {\n const result = await ecoQuery<{ ecoSetReductionTargetStatus: EcoReductionTarget }>(\n `mutation EcoSetReductionTargetStatus($id: ID!, $status: EcoReductionTargetStatus!) {\n ecoSetReductionTargetStatus(id: $id, status: $status) { ${TARGET_FIELDS} }\n }`,\n vars,\n apiGatewayUrl\n );\n return result.ecoSetReductionTargetStatus;\n },\n onSuccess: () => qc.invalidateQueries({ queryKey: KEY.reductionTargets }),\n });\n};\n\n// ============================================================================\n// Reports (Sustainability & Reporting)\n// ============================================================================\n\nexport const useEcoReports = (status?: EcoReportStatus, pagination?: PaginationInput) => {\n const { apiGatewayUrl } = useEcoImpactHub();\n return useQuery({\n queryKey: [...KEY.reports, status ?? 'all', pagination ?? DEFAULT_PAGE],\n queryFn: async () => {\n const result = await ecoQuery<{ ecoListReports: EcoConnection<EcoReport> }>(\n `query EcoListReports($status: EcoReportStatus, $pagination: PaginationInput) {\n ecoListReports(status: $status, pagination: $pagination) {\n items { ${REPORT_FIELDS} }\n pageInfo { cursor hasMore }\n }\n }`,\n { status, pagination: pagination ?? DEFAULT_PAGE },\n apiGatewayUrl\n );\n return result.ecoListReports;\n },\n });\n};\n\nexport const useEcoReport = (id: string | undefined) => {\n const { apiGatewayUrl } = useEcoImpactHub();\n return useQuery({\n queryKey: KEY.report(id ?? ''),\n enabled: Boolean(id),\n queryFn: async () => {\n const result = await ecoQuery<{ ecoReport: EcoReport | null }>(\n `query EcoReport($id: ID!) {\n ecoReport(id: $id) {\n ${REPORT_FIELDS}\n lineItems { ${LINE_ITEM_FIELDS} }\n }\n }`,\n { id },\n apiGatewayUrl\n );\n return result.ecoReport;\n },\n });\n};\n\nexport const useEcoCreateReport = () => {\n const { apiGatewayUrl } = useEcoImpactHub();\n const qc = useQueryClient();\n return useMutation({\n mutationFn: async (input: EcoCreateReportInput) => {\n const result = await ecoQuery<{ ecoCreateReport: EcoReport }>(\n `mutation EcoCreateReport($input: EcoCreateReportInput!) {\n ecoCreateReport(input: $input) { ${REPORT_FIELDS} }\n }`,\n { input },\n apiGatewayUrl\n );\n return result.ecoCreateReport;\n },\n onSuccess: () => qc.invalidateQueries({ queryKey: KEY.reports }),\n });\n};\n\nexport const useEcoAddReportLineItem = () => {\n const { apiGatewayUrl } = useEcoImpactHub();\n const qc = useQueryClient();\n return useMutation({\n mutationFn: async (input: EcoAddReportLineItemInput) => {\n const result = await ecoQuery<{ ecoAddReportLineItem: EcoReportLineItem }>(\n `mutation EcoAddReportLineItem($input: EcoAddReportLineItemInput!) {\n ecoAddReportLineItem(input: $input) { ${LINE_ITEM_FIELDS} }\n }`,\n { input },\n apiGatewayUrl\n );\n return result.ecoAddReportLineItem;\n },\n onSuccess: (data) => {\n qc.invalidateQueries({ queryKey: KEY.reports });\n qc.invalidateQueries({ queryKey: KEY.report(data.reportId) });\n },\n });\n};\n\nexport const useEcoPublishReport = () => {\n const { apiGatewayUrl } = useEcoImpactHub();\n const qc = useQueryClient();\n return useMutation({\n mutationFn: async (vars: { id: string; fileId?: string | null }) => {\n const result = await ecoQuery<{ ecoPublishReport: EcoReport }>(\n `mutation EcoPublishReport($id: ID!, $fileId: ID) {\n ecoPublishReport(id: $id, fileId: $fileId) { ${REPORT_FIELDS} }\n }`,\n vars,\n apiGatewayUrl\n );\n return result.ecoPublishReport;\n },\n onSuccess: (data) => {\n qc.invalidateQueries({ queryKey: KEY.reports });\n qc.invalidateQueries({ queryKey: KEY.report(data.id) });\n },\n });\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"useEcoImpactHubApi.js","names":[],"sources":["../../../src/ecoimpacthub/hooks/useEcoImpactHubApi.ts"],"sourcesContent":["/**\n * EcoImpactHub API hooks\n * @module ecoimpacthub/hooks\n *\n * React-Query hooks wired DIRECTLY to the real `wspace-ecoimpacthub-svc`\n * federated GraphQL contract (the `eco*` operations). Every query/mutation\n * below uses an inline gql string whose fields match the svc\n * `.hive-schema.graphql` exactly. No mock data.\n *\n * Transport: `graphqlFetch({ gateway: 'workspace', workspaceToken: true })` —\n * the host shell supplies the gateway base URL + a workspace-scoped token.\n */\n\nimport { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';\nimport { graphqlFetch } from '@burdenoff/fe-libs/shared/graphql';\n\nimport { useEcoImpactHub } from '../context';\nimport type {\n EcoConnection,\n EcoSite,\n EcoSiteStatus,\n EcoSiteType,\n EcoSensor,\n EcoSensorKind,\n EcoSensorStatus,\n EcoMeasurement,\n EcoAlertThreshold,\n EcoSpecies,\n EcoTaxonGroup,\n EcoConservationStatus,\n EcoObservation,\n EcoHabitatProject,\n EcoHabitatProjectStatus,\n EcoRestorationActivity,\n EcoEmissionFactor,\n EcoEmissionScope,\n EcoCarbonEntry,\n EcoCarbonEntryStatus,\n EcoReductionTarget,\n EcoReductionTargetStatus,\n EcoReport,\n EcoReportStatus,\n EcoReportLineItem,\n EcoCreateSiteInput,\n EcoUpdateSiteInput,\n EcoCreateSensorInput,\n EcoRecordMeasurementInput,\n EcoCreateThresholdInput,\n EcoCreateSpeciesInput,\n EcoUpdateSpeciesInput,\n EcoRecordObservationInput,\n EcoCreateHabitatProjectInput,\n EcoLogRestorationActivityInput,\n EcoCreateEmissionFactorInput,\n EcoRecordCarbonEntryInput,\n EcoCreateReductionTargetInput,\n EcoCreateReportInput,\n EcoAddReportLineItemInput,\n PaginationInput,\n} from '../types';\n\n// ============================================================================\n// Transport\n// ============================================================================\n\n/**\n * fe-libs `graphqlFetch` coalesces `query` responses in an in-memory cache keyed\n * by operationName+variables for ~15s (mutations are never cached). A read\n * issued right after a write (e.g. React-Query refetch triggered by our\n * `invalidateQueries`) would otherwise return the PRE-mutation response until the\n * TTL expires — making just-created/updated rows invisible until a full reload.\n * fe-libs' documented remedy is that write-performing callers clear those caches;\n * we do exactly that (the same maps `clearGraphqlFetchResponseCache` clears)\n * after every eco mutation so every follow-up read is fresh.\n */\ntype GraphqlReadCacheGlobals = {\n __graphqlFetchResponseCache?: { clear: () => void };\n __graphqlFetchPendingResponses?: { clear: () => void };\n __boffGraphQLResponseCache?: { clear: () => void };\n};\n\nconst clearGraphqlReadCaches = (): void => {\n const g = globalThis as GraphqlReadCacheGlobals;\n g.__graphqlFetchResponseCache?.clear();\n g.__graphqlFetchPendingResponses?.clear();\n g.__boffGraphQLResponseCache?.clear();\n};\n\nconst ecoQuery = async <TData>(\n query: string,\n variables: Record<string, unknown>,\n baseUrl?: string\n): Promise<TData> => {\n const response = await graphqlFetch<TData>({\n gateway: 'workspace',\n baseUrl,\n query,\n variables,\n workspaceToken: true,\n });\n\n if (response.errors?.length) {\n throw new Error(response.errors[0]?.message ?? 'GraphQL request failed');\n }\n if (!response.data) {\n throw new Error('GraphQL response did not include data');\n }\n // After a write, drop fe-libs' short-lived read caches so the invalidation\n // refetch that follows reads fresh data instead of the pre-mutation snapshot.\n if (/^\\s*mutation\\b/.test(query)) {\n clearGraphqlReadCaches();\n }\n return response.data;\n};\n\n// React-Query key roots (one namespace per domain entity).\nconst KEY = {\n sites: ['ecoimpacthub', 'sites'] as const,\n site: (id: string) => ['ecoimpacthub', 'site', id] as const,\n sensors: ['ecoimpacthub', 'sensors'] as const,\n measurements: ['ecoimpacthub', 'measurements'] as const,\n thresholds: ['ecoimpacthub', 'thresholds'] as const,\n species: ['ecoimpacthub', 'species'] as const,\n speciesOne: (id: string) => ['ecoimpacthub', 'species', id] as const,\n observations: ['ecoimpacthub', 'observations'] as const,\n habitatProjects: ['ecoimpacthub', 'habitat-projects'] as const,\n habitatProject: (id: string) => ['ecoimpacthub', 'habitat-project', id] as const,\n emissionFactors: ['ecoimpacthub', 'emission-factors'] as const,\n carbonEntries: ['ecoimpacthub', 'carbon-entries'] as const,\n carbonTotals: ['ecoimpacthub', 'carbon-totals'] as const,\n reductionTargets: ['ecoimpacthub', 'reduction-targets'] as const,\n reports: ['ecoimpacthub', 'reports'] as const,\n report: (id: string) => ['ecoimpacthub', 'report', id] as const,\n};\n\nconst DEFAULT_PAGE: PaginationInput = { limit: 50, offset: 0 };\n\n// ============================================================================\n// Reusable gql field selections (codegen-free strictness)\n// ============================================================================\n\nconst SITE_FIELDS = `\n id workspaceId name code type status description latitude longitude\n areaHectares country region createdAt updatedAt\n`;\n\nconst SENSOR_FIELDS = `\n id workspaceId siteId name kind status hardwareId unit lastReadingAt installedAt\n createdAt updatedAt\n`;\n\nconst MEASUREMENT_FIELDS = `\n id workspaceId siteId sensorId parameter value unit source recordedAt isBreach notes\n createdAt updatedAt\n`;\n\nconst THRESHOLD_FIELDS = `\n id workspaceId siteId parameter direction severity limitValue minValue maxValue unit\n isActive createdAt updatedAt\n`;\n\nconst SPECIES_FIELDS = `\n id workspaceId commonName scientificName taxonGroup conservationStatus isPriority\n description createdAt updatedAt\n`;\n\nconst OBSERVATION_FIELDS = `\n id workspaceId speciesId siteId method count observedAt latitude longitude observerId\n notes evidence { id } createdAt updatedAt\n`;\n\nconst HABITAT_FIELDS = `\n id workspaceId name status description siteId targetHectares budgetAmount currency\n managerId startDate endDate completedAt createdAt updatedAt\n`;\n\nconst ACTIVITY_FIELDS = `\n id workspaceId projectId type summary details quantity unit performedAt createdAt updatedAt\n`;\n\nconst FACTOR_FIELDS = `\n id workspaceId name scope activityUnit factorKgco2e source validYear isActive\n createdAt updatedAt\n`;\n\nconst CARBON_FIELDS = `\n id workspaceId siteId factorId scope status category activityValue activityUnit co2eKg\n periodStart notes createdAt updatedAt\n`;\n\nconst TARGET_FIELDS = `\n id workspaceId name status metric baselineYear baselineValue targetYear targetValue unit\n description createdAt updatedAt\n`;\n\nconst REPORT_FIELDS = `\n id workspaceId title framework status periodStart periodEnd summary publishedAt\n file { id } createdAt updatedAt\n`;\n\nconst LINE_ITEM_FIELDS = `\n id workspaceId reportId carbonEntryId label value unit category createdAt updatedAt\n`;\n\n// ============================================================================\n// Sites (Environmental Monitoring)\n// ============================================================================\n\nexport interface SiteFilters {\n status?: EcoSiteStatus;\n type?: EcoSiteType;\n pagination?: PaginationInput;\n}\n\nexport const useEcoSites = (filters: SiteFilters = {}) => {\n const { apiGatewayUrl } = useEcoImpactHub();\n return useQuery({\n queryKey: [...KEY.sites, filters],\n queryFn: async () => {\n const result = await ecoQuery<{ ecoListSites: EcoConnection<EcoSite> }>(\n `query EcoListSites($status: EcoSiteStatus, $type: EcoSiteType, $pagination: PaginationInput) {\n ecoListSites(status: $status, type: $type, pagination: $pagination) {\n items { ${SITE_FIELDS} }\n pageInfo { cursor hasMore }\n }\n }`,\n {\n status: filters.status,\n type: filters.type,\n pagination: filters.pagination ?? DEFAULT_PAGE,\n },\n apiGatewayUrl\n );\n return result.ecoListSites;\n },\n });\n};\n\nexport const useEcoSite = (id: string | undefined) => {\n const { apiGatewayUrl } = useEcoImpactHub();\n return useQuery({\n queryKey: KEY.site(id ?? ''),\n enabled: Boolean(id),\n queryFn: async () => {\n const result = await ecoQuery<{ ecoSite: EcoSite | null }>(\n `query EcoSite($id: ID!) {\n ecoSite(id: $id) {\n ${SITE_FIELDS}\n sensors { ${SENSOR_FIELDS} }\n thresholds { ${THRESHOLD_FIELDS} }\n }\n }`,\n { id },\n apiGatewayUrl\n );\n return result.ecoSite;\n },\n });\n};\n\nexport const useEcoCreateSite = () => {\n const { apiGatewayUrl } = useEcoImpactHub();\n const qc = useQueryClient();\n return useMutation({\n mutationFn: async (input: EcoCreateSiteInput) => {\n const result = await ecoQuery<{ ecoCreateSite: EcoSite }>(\n `mutation EcoCreateSite($input: EcoCreateSiteInput!) {\n ecoCreateSite(input: $input) { ${SITE_FIELDS} }\n }`,\n { input },\n apiGatewayUrl\n );\n return result.ecoCreateSite;\n },\n onSuccess: () => qc.invalidateQueries({ queryKey: KEY.sites }),\n });\n};\n\nexport const useEcoUpdateSite = () => {\n const { apiGatewayUrl } = useEcoImpactHub();\n const qc = useQueryClient();\n return useMutation({\n mutationFn: async (vars: { id: string; input: EcoUpdateSiteInput }) => {\n const result = await ecoQuery<{ ecoUpdateSite: EcoSite }>(\n `mutation EcoUpdateSite($id: ID!, $input: EcoUpdateSiteInput!) {\n ecoUpdateSite(id: $id, input: $input) { ${SITE_FIELDS} }\n }`,\n vars,\n apiGatewayUrl\n );\n return result.ecoUpdateSite;\n },\n onSuccess: (data) => {\n qc.invalidateQueries({ queryKey: KEY.sites });\n qc.invalidateQueries({ queryKey: KEY.site(data.id) });\n },\n });\n};\n\nexport const useEcoArchiveSite = () => {\n const { apiGatewayUrl } = useEcoImpactHub();\n const qc = useQueryClient();\n return useMutation({\n mutationFn: async (id: string) => {\n const result = await ecoQuery<{ ecoArchiveSite: boolean }>(\n `mutation EcoArchiveSite($id: ID!) { ecoArchiveSite(id: $id) }`,\n { id },\n apiGatewayUrl\n );\n return result.ecoArchiveSite;\n },\n onSuccess: () => qc.invalidateQueries({ queryKey: KEY.sites }),\n });\n};\n\n// ============================================================================\n// Sensors / Measurements / Thresholds (Environmental Monitoring)\n// ============================================================================\n\nexport interface SensorFilters {\n siteId?: string;\n kind?: EcoSensorKind;\n status?: EcoSensorStatus;\n}\n\nexport const useEcoSensors = (filters: SensorFilters = {}) => {\n const { apiGatewayUrl } = useEcoImpactHub();\n return useQuery({\n queryKey: [...KEY.sensors, filters],\n queryFn: async () => {\n const result = await ecoQuery<{ ecoListSensors: EcoConnection<EcoSensor> }>(\n `query EcoListSensors($siteId: ID, $kind: EcoSensorKind, $status: EcoSensorStatus) {\n ecoListSensors(siteId: $siteId, kind: $kind, status: $status) {\n items { ${SENSOR_FIELDS} }\n pageInfo { cursor hasMore }\n }\n }`,\n { siteId: filters.siteId, kind: filters.kind, status: filters.status },\n apiGatewayUrl\n );\n return result.ecoListSensors;\n },\n });\n};\n\nexport const useEcoCreateSensor = () => {\n const { apiGatewayUrl } = useEcoImpactHub();\n const qc = useQueryClient();\n return useMutation({\n mutationFn: async (input: EcoCreateSensorInput) => {\n const result = await ecoQuery<{ ecoCreateSensor: EcoSensor }>(\n `mutation EcoCreateSensor($input: EcoCreateSensorInput!) {\n ecoCreateSensor(input: $input) { ${SENSOR_FIELDS} }\n }`,\n { input },\n apiGatewayUrl\n );\n return result.ecoCreateSensor;\n },\n onSuccess: (data) => {\n qc.invalidateQueries({ queryKey: KEY.sensors });\n qc.invalidateQueries({ queryKey: KEY.site(data.siteId) });\n },\n });\n};\n\nexport const useEcoSetSensorStatus = () => {\n const { apiGatewayUrl } = useEcoImpactHub();\n const qc = useQueryClient();\n return useMutation({\n mutationFn: async (vars: { id: string; status: EcoSensorStatus }) => {\n const result = await ecoQuery<{ ecoSetSensorStatus: EcoSensor }>(\n `mutation EcoSetSensorStatus($id: ID!, $status: EcoSensorStatus!) {\n ecoSetSensorStatus(id: $id, status: $status) { ${SENSOR_FIELDS} }\n }`,\n vars,\n apiGatewayUrl\n );\n return result.ecoSetSensorStatus;\n },\n onSuccess: (data) => {\n qc.invalidateQueries({ queryKey: KEY.sensors });\n qc.invalidateQueries({ queryKey: KEY.site(data.siteId) });\n },\n });\n};\n\nexport interface MeasurementFilters {\n siteId?: string;\n sensorId?: string;\n parameter?: string;\n breachesOnly?: boolean;\n pagination?: PaginationInput;\n}\n\nexport const useEcoMeasurements = (filters: MeasurementFilters = {}) => {\n const { apiGatewayUrl } = useEcoImpactHub();\n return useQuery({\n queryKey: [...KEY.measurements, filters],\n queryFn: async () => {\n const result = await ecoQuery<{ ecoListMeasurements: EcoConnection<EcoMeasurement> }>(\n `query EcoListMeasurements($siteId: ID, $sensorId: ID, $parameter: String, $breachesOnly: Boolean, $pagination: PaginationInput) {\n ecoListMeasurements(siteId: $siteId, sensorId: $sensorId, parameter: $parameter, breachesOnly: $breachesOnly, pagination: $pagination) {\n items { ${MEASUREMENT_FIELDS} }\n pageInfo { cursor hasMore }\n }\n }`,\n {\n siteId: filters.siteId,\n sensorId: filters.sensorId,\n parameter: filters.parameter,\n breachesOnly: filters.breachesOnly,\n pagination: filters.pagination ?? DEFAULT_PAGE,\n },\n apiGatewayUrl\n );\n return result.ecoListMeasurements;\n },\n });\n};\n\nexport const useEcoRecordMeasurement = () => {\n const { apiGatewayUrl } = useEcoImpactHub();\n const qc = useQueryClient();\n return useMutation({\n mutationFn: async (input: EcoRecordMeasurementInput) => {\n const result = await ecoQuery<{ ecoRecordMeasurement: EcoMeasurement }>(\n `mutation EcoRecordMeasurement($input: EcoRecordMeasurementInput!) {\n ecoRecordMeasurement(input: $input) { ${MEASUREMENT_FIELDS} }\n }`,\n { input },\n apiGatewayUrl\n );\n return result.ecoRecordMeasurement;\n },\n onSuccess: () => qc.invalidateQueries({ queryKey: KEY.measurements }),\n });\n};\n\nexport const useEcoThresholds = (siteId?: string) => {\n const { apiGatewayUrl } = useEcoImpactHub();\n return useQuery({\n queryKey: [...KEY.thresholds, siteId ?? 'all'],\n queryFn: async () => {\n const result = await ecoQuery<{ ecoListThresholds: EcoConnection<EcoAlertThreshold> }>(\n `query EcoListThresholds($siteId: ID) {\n ecoListThresholds(siteId: $siteId) {\n items { ${THRESHOLD_FIELDS} }\n pageInfo { cursor hasMore }\n }\n }`,\n { siteId },\n apiGatewayUrl\n );\n return result.ecoListThresholds;\n },\n });\n};\n\nexport const useEcoCreateThreshold = () => {\n const { apiGatewayUrl } = useEcoImpactHub();\n const qc = useQueryClient();\n return useMutation({\n mutationFn: async (input: EcoCreateThresholdInput) => {\n const result = await ecoQuery<{ ecoCreateThreshold: EcoAlertThreshold }>(\n `mutation EcoCreateThreshold($input: EcoCreateThresholdInput!) {\n ecoCreateThreshold(input: $input) { ${THRESHOLD_FIELDS} }\n }`,\n { input },\n apiGatewayUrl\n );\n return result.ecoCreateThreshold;\n },\n onSuccess: (data) => {\n qc.invalidateQueries({ queryKey: KEY.thresholds });\n qc.invalidateQueries({ queryKey: KEY.site(data.siteId) });\n },\n });\n};\n\nexport const useEcoSetThresholdActive = () => {\n const { apiGatewayUrl } = useEcoImpactHub();\n const qc = useQueryClient();\n return useMutation({\n mutationFn: async (vars: { id: string; isActive: boolean }) => {\n const result = await ecoQuery<{ ecoSetThresholdActive: EcoAlertThreshold }>(\n `mutation EcoSetThresholdActive($id: ID!, $isActive: Boolean!) {\n ecoSetThresholdActive(id: $id, isActive: $isActive) { ${THRESHOLD_FIELDS} }\n }`,\n vars,\n apiGatewayUrl\n );\n return result.ecoSetThresholdActive;\n },\n onSuccess: () => qc.invalidateQueries({ queryKey: KEY.thresholds }),\n });\n};\n\n// ============================================================================\n// Species / Observations / Habitat (Conservation)\n// ============================================================================\n\nexport interface SpeciesFilters {\n taxonGroup?: EcoTaxonGroup;\n conservationStatus?: EcoConservationStatus;\n priorityOnly?: boolean;\n pagination?: PaginationInput;\n}\n\nexport const useEcoSpecies = (filters: SpeciesFilters = {}) => {\n const { apiGatewayUrl } = useEcoImpactHub();\n return useQuery({\n queryKey: [...KEY.species, filters],\n queryFn: async () => {\n const result = await ecoQuery<{ ecoListSpecies: EcoConnection<EcoSpecies> }>(\n `query EcoListSpecies($taxonGroup: EcoTaxonGroup, $conservationStatus: EcoConservationStatus, $priorityOnly: Boolean, $pagination: PaginationInput) {\n ecoListSpecies(taxonGroup: $taxonGroup, conservationStatus: $conservationStatus, priorityOnly: $priorityOnly, pagination: $pagination) {\n items { ${SPECIES_FIELDS} }\n pageInfo { cursor hasMore }\n }\n }`,\n {\n taxonGroup: filters.taxonGroup,\n conservationStatus: filters.conservationStatus,\n priorityOnly: filters.priorityOnly,\n pagination: filters.pagination ?? DEFAULT_PAGE,\n },\n apiGatewayUrl\n );\n return result.ecoListSpecies;\n },\n });\n};\n\nexport const useEcoSpeciesDetail = (id: string | undefined) => {\n const { apiGatewayUrl } = useEcoImpactHub();\n return useQuery({\n queryKey: KEY.speciesOne(id ?? ''),\n enabled: Boolean(id),\n queryFn: async () => {\n const result = await ecoQuery<{ ecoSpecies: EcoSpecies | null }>(\n `query EcoSpeciesDetail($id: ID!) {\n ecoSpecies(id: $id) {\n ${SPECIES_FIELDS}\n observations { ${OBSERVATION_FIELDS} }\n }\n }`,\n { id },\n apiGatewayUrl\n );\n return result.ecoSpecies;\n },\n });\n};\n\nexport const useEcoCreateSpecies = () => {\n const { apiGatewayUrl } = useEcoImpactHub();\n const qc = useQueryClient();\n return useMutation({\n mutationFn: async (input: EcoCreateSpeciesInput) => {\n const result = await ecoQuery<{ ecoCreateSpecies: EcoSpecies }>(\n `mutation EcoCreateSpecies($input: EcoCreateSpeciesInput!) {\n ecoCreateSpecies(input: $input) { ${SPECIES_FIELDS} }\n }`,\n { input },\n apiGatewayUrl\n );\n return result.ecoCreateSpecies;\n },\n onSuccess: () => qc.invalidateQueries({ queryKey: KEY.species }),\n });\n};\n\nexport const useEcoUpdateSpecies = () => {\n const { apiGatewayUrl } = useEcoImpactHub();\n const qc = useQueryClient();\n return useMutation({\n mutationFn: async (vars: { id: string; input: EcoUpdateSpeciesInput }) => {\n const result = await ecoQuery<{ ecoUpdateSpecies: EcoSpecies }>(\n `mutation EcoUpdateSpecies($id: ID!, $input: EcoUpdateSpeciesInput!) {\n ecoUpdateSpecies(id: $id, input: $input) { ${SPECIES_FIELDS} }\n }`,\n vars,\n apiGatewayUrl\n );\n return result.ecoUpdateSpecies;\n },\n onSuccess: (data) => {\n qc.invalidateQueries({ queryKey: KEY.species });\n qc.invalidateQueries({ queryKey: KEY.speciesOne(data.id) });\n },\n });\n};\n\nexport const useEcoArchiveSpecies = () => {\n const { apiGatewayUrl } = useEcoImpactHub();\n const qc = useQueryClient();\n return useMutation({\n mutationFn: async (id: string) => {\n const result = await ecoQuery<{ ecoArchiveSpecies: boolean }>(\n `mutation EcoArchiveSpecies($id: ID!) { ecoArchiveSpecies(id: $id) }`,\n { id },\n apiGatewayUrl\n );\n return result.ecoArchiveSpecies;\n },\n onSuccess: () => qc.invalidateQueries({ queryKey: KEY.species }),\n });\n};\n\nexport interface ObservationFilters {\n speciesId?: string;\n siteId?: string;\n pagination?: PaginationInput;\n}\n\nexport const useEcoObservations = (filters: ObservationFilters = {}) => {\n const { apiGatewayUrl } = useEcoImpactHub();\n return useQuery({\n queryKey: [...KEY.observations, filters],\n queryFn: async () => {\n const result = await ecoQuery<{ ecoListObservations: EcoConnection<EcoObservation> }>(\n `query EcoListObservations($speciesId: ID, $siteId: ID, $pagination: PaginationInput) {\n ecoListObservations(speciesId: $speciesId, siteId: $siteId, pagination: $pagination) {\n items { ${OBSERVATION_FIELDS} species { id commonName scientificName taxonGroup } }\n pageInfo { cursor hasMore }\n }\n }`,\n {\n speciesId: filters.speciesId,\n siteId: filters.siteId,\n pagination: filters.pagination ?? DEFAULT_PAGE,\n },\n apiGatewayUrl\n );\n return result.ecoListObservations;\n },\n });\n};\n\nexport const useEcoRecordObservation = () => {\n const { apiGatewayUrl } = useEcoImpactHub();\n const qc = useQueryClient();\n return useMutation({\n mutationFn: async (input: EcoRecordObservationInput) => {\n const result = await ecoQuery<{ ecoRecordObservation: EcoObservation }>(\n `mutation EcoRecordObservation($input: EcoRecordObservationInput!) {\n ecoRecordObservation(input: $input) { ${OBSERVATION_FIELDS} }\n }`,\n { input },\n apiGatewayUrl\n );\n return result.ecoRecordObservation;\n },\n onSuccess: (data) => {\n qc.invalidateQueries({ queryKey: KEY.observations });\n qc.invalidateQueries({ queryKey: KEY.speciesOne(data.speciesId) });\n },\n });\n};\n\nexport const useEcoHabitatProjects = (status?: EcoHabitatProjectStatus) => {\n const { apiGatewayUrl } = useEcoImpactHub();\n return useQuery({\n queryKey: [...KEY.habitatProjects, status ?? 'all'],\n queryFn: async () => {\n const result = await ecoQuery<{ ecoListHabitatProjects: EcoConnection<EcoHabitatProject> }>(\n `query EcoListHabitatProjects($status: EcoHabitatProjectStatus) {\n ecoListHabitatProjects(status: $status) {\n items { ${HABITAT_FIELDS} }\n pageInfo { cursor hasMore }\n }\n }`,\n { status },\n apiGatewayUrl\n );\n return result.ecoListHabitatProjects;\n },\n });\n};\n\nexport const useEcoHabitatProject = (id: string | undefined) => {\n const { apiGatewayUrl } = useEcoImpactHub();\n return useQuery({\n queryKey: KEY.habitatProject(id ?? ''),\n enabled: Boolean(id),\n queryFn: async () => {\n const result = await ecoQuery<{ ecoHabitatProject: EcoHabitatProject | null }>(\n `query EcoHabitatProject($id: ID!) {\n ecoHabitatProject(id: $id) {\n ${HABITAT_FIELDS}\n activities { ${ACTIVITY_FIELDS} }\n }\n }`,\n { id },\n apiGatewayUrl\n );\n return result.ecoHabitatProject;\n },\n });\n};\n\nexport const useEcoCreateHabitatProject = () => {\n const { apiGatewayUrl } = useEcoImpactHub();\n const qc = useQueryClient();\n return useMutation({\n mutationFn: async (input: EcoCreateHabitatProjectInput) => {\n const result = await ecoQuery<{ ecoCreateHabitatProject: EcoHabitatProject }>(\n `mutation EcoCreateHabitatProject($input: EcoCreateHabitatProjectInput!) {\n ecoCreateHabitatProject(input: $input) { ${HABITAT_FIELDS} }\n }`,\n { input },\n apiGatewayUrl\n );\n return result.ecoCreateHabitatProject;\n },\n onSuccess: () => qc.invalidateQueries({ queryKey: KEY.habitatProjects }),\n });\n};\n\nexport const useEcoTransitionHabitatProject = () => {\n const { apiGatewayUrl } = useEcoImpactHub();\n const qc = useQueryClient();\n return useMutation({\n mutationFn: async (vars: { id: string; status: EcoHabitatProjectStatus }) => {\n const result = await ecoQuery<{ ecoTransitionHabitatProject: EcoHabitatProject }>(\n `mutation EcoTransitionHabitatProject($id: ID!, $status: EcoHabitatProjectStatus!) {\n ecoTransitionHabitatProject(id: $id, status: $status) { ${HABITAT_FIELDS} }\n }`,\n vars,\n apiGatewayUrl\n );\n return result.ecoTransitionHabitatProject;\n },\n onSuccess: (data) => {\n qc.invalidateQueries({ queryKey: KEY.habitatProjects });\n qc.invalidateQueries({ queryKey: KEY.habitatProject(data.id) });\n },\n });\n};\n\nexport const useEcoLogRestorationActivity = () => {\n const { apiGatewayUrl } = useEcoImpactHub();\n const qc = useQueryClient();\n return useMutation({\n mutationFn: async (input: EcoLogRestorationActivityInput) => {\n const result = await ecoQuery<{ ecoLogRestorationActivity: EcoRestorationActivity }>(\n `mutation EcoLogRestorationActivity($input: EcoLogRestorationActivityInput!) {\n ecoLogRestorationActivity(input: $input) { ${ACTIVITY_FIELDS} }\n }`,\n { input },\n apiGatewayUrl\n );\n return result.ecoLogRestorationActivity;\n },\n onSuccess: (data) => qc.invalidateQueries({ queryKey: KEY.habitatProject(data.projectId) }),\n });\n};\n\n// ============================================================================\n// Carbon ledger / Emission factors / Reduction targets (Sustainability)\n// ============================================================================\n\nexport const useEcoEmissionFactors = (scope?: EcoEmissionScope, activeOnly?: boolean) => {\n const { apiGatewayUrl } = useEcoImpactHub();\n return useQuery({\n queryKey: [...KEY.emissionFactors, scope ?? 'all', activeOnly ?? false],\n queryFn: async () => {\n const result = await ecoQuery<{ ecoListEmissionFactors: EcoConnection<EcoEmissionFactor> }>(\n `query EcoListEmissionFactors($scope: EcoEmissionScope, $activeOnly: Boolean) {\n ecoListEmissionFactors(scope: $scope, activeOnly: $activeOnly) {\n items { ${FACTOR_FIELDS} }\n pageInfo { cursor hasMore }\n }\n }`,\n { scope, activeOnly },\n apiGatewayUrl\n );\n return result.ecoListEmissionFactors;\n },\n });\n};\n\nexport const useEcoCreateEmissionFactor = () => {\n const { apiGatewayUrl } = useEcoImpactHub();\n const qc = useQueryClient();\n return useMutation({\n mutationFn: async (input: EcoCreateEmissionFactorInput) => {\n const result = await ecoQuery<{ ecoCreateEmissionFactor: EcoEmissionFactor }>(\n `mutation EcoCreateEmissionFactor($input: EcoCreateEmissionFactorInput!) {\n ecoCreateEmissionFactor(input: $input) { ${FACTOR_FIELDS} }\n }`,\n { input },\n apiGatewayUrl\n );\n return result.ecoCreateEmissionFactor;\n },\n onSuccess: () => qc.invalidateQueries({ queryKey: KEY.emissionFactors }),\n });\n};\n\nexport interface CarbonEntryFilters {\n scope?: EcoEmissionScope;\n siteId?: string;\n status?: EcoCarbonEntryStatus;\n pagination?: PaginationInput;\n}\n\nexport const useEcoCarbonEntries = (filters: CarbonEntryFilters = {}) => {\n const { apiGatewayUrl } = useEcoImpactHub();\n return useQuery({\n queryKey: [...KEY.carbonEntries, filters],\n queryFn: async () => {\n const result = await ecoQuery<{ ecoListCarbonEntries: EcoConnection<EcoCarbonEntry> }>(\n `query EcoListCarbonEntries($scope: EcoEmissionScope, $siteId: ID, $status: EcoCarbonEntryStatus, $pagination: PaginationInput) {\n ecoListCarbonEntries(scope: $scope, siteId: $siteId, status: $status, pagination: $pagination) {\n items { ${CARBON_FIELDS} factor { id name factorKgco2e activityUnit } }\n pageInfo { cursor hasMore }\n }\n }`,\n {\n scope: filters.scope,\n siteId: filters.siteId,\n status: filters.status,\n pagination: filters.pagination ?? DEFAULT_PAGE,\n },\n apiGatewayUrl\n );\n return result.ecoListCarbonEntries;\n },\n });\n};\n\n/** Total CO2e by scope + grand total, in one aliased query. */\nexport interface EcoCarbonTotals {\n total: number;\n scope1: number;\n scope2: number;\n scope3: number;\n}\n\nexport const useEcoCarbonTotals = (siteId?: string) => {\n const { apiGatewayUrl } = useEcoImpactHub();\n return useQuery({\n queryKey: [...KEY.carbonTotals, siteId ?? 'all'],\n queryFn: async (): Promise<EcoCarbonTotals> => {\n const result = await ecoQuery<{\n total: number;\n scope1: number;\n scope2: number;\n scope3: number;\n }>(\n `query EcoCarbonTotals($siteId: ID) {\n total: ecoCarbonTotalKg(siteId: $siteId)\n scope1: ecoCarbonTotalKg(scope: SCOPE_1, siteId: $siteId)\n scope2: ecoCarbonTotalKg(scope: SCOPE_2, siteId: $siteId)\n scope3: ecoCarbonTotalKg(scope: SCOPE_3, siteId: $siteId)\n }`,\n { siteId },\n apiGatewayUrl\n );\n return result;\n },\n });\n};\n\nexport const useEcoRecordCarbonEntry = () => {\n const { apiGatewayUrl } = useEcoImpactHub();\n const qc = useQueryClient();\n return useMutation({\n mutationFn: async (input: EcoRecordCarbonEntryInput) => {\n const result = await ecoQuery<{ ecoRecordCarbonEntry: EcoCarbonEntry }>(\n `mutation EcoRecordCarbonEntry($input: EcoRecordCarbonEntryInput!) {\n ecoRecordCarbonEntry(input: $input) { ${CARBON_FIELDS} }\n }`,\n { input },\n apiGatewayUrl\n );\n return result.ecoRecordCarbonEntry;\n },\n onSuccess: () => {\n qc.invalidateQueries({ queryKey: KEY.carbonEntries });\n qc.invalidateQueries({ queryKey: KEY.carbonTotals });\n },\n });\n};\n\nexport const useEcoVerifyCarbonEntry = () => {\n const { apiGatewayUrl } = useEcoImpactHub();\n const qc = useQueryClient();\n return useMutation({\n mutationFn: async (id: string) => {\n const result = await ecoQuery<{ ecoVerifyCarbonEntry: EcoCarbonEntry }>(\n `mutation EcoVerifyCarbonEntry($id: ID!) {\n ecoVerifyCarbonEntry(id: $id) { ${CARBON_FIELDS} }\n }`,\n { id },\n apiGatewayUrl\n );\n return result.ecoVerifyCarbonEntry;\n },\n onSuccess: () => qc.invalidateQueries({ queryKey: KEY.carbonEntries }),\n });\n};\n\nexport const useEcoReductionTargets = (status?: EcoReductionTargetStatus) => {\n const { apiGatewayUrl } = useEcoImpactHub();\n return useQuery({\n queryKey: [...KEY.reductionTargets, status ?? 'all'],\n queryFn: async () => {\n const result = await ecoQuery<{ ecoListReductionTargets: EcoConnection<EcoReductionTarget> }>(\n `query EcoListReductionTargets($status: EcoReductionTargetStatus) {\n ecoListReductionTargets(status: $status) {\n items { ${TARGET_FIELDS} }\n pageInfo { cursor hasMore }\n }\n }`,\n { status },\n apiGatewayUrl\n );\n return result.ecoListReductionTargets;\n },\n });\n};\n\nexport const useEcoCreateReductionTarget = () => {\n const { apiGatewayUrl } = useEcoImpactHub();\n const qc = useQueryClient();\n return useMutation({\n mutationFn: async (input: EcoCreateReductionTargetInput) => {\n const result = await ecoQuery<{ ecoCreateReductionTarget: EcoReductionTarget }>(\n `mutation EcoCreateReductionTarget($input: EcoCreateReductionTargetInput!) {\n ecoCreateReductionTarget(input: $input) { ${TARGET_FIELDS} }\n }`,\n { input },\n apiGatewayUrl\n );\n return result.ecoCreateReductionTarget;\n },\n onSuccess: () => qc.invalidateQueries({ queryKey: KEY.reductionTargets }),\n });\n};\n\nexport const useEcoSetReductionTargetStatus = () => {\n const { apiGatewayUrl } = useEcoImpactHub();\n const qc = useQueryClient();\n return useMutation({\n mutationFn: async (vars: { id: string; status: EcoReductionTargetStatus }) => {\n const result = await ecoQuery<{ ecoSetReductionTargetStatus: EcoReductionTarget }>(\n `mutation EcoSetReductionTargetStatus($id: ID!, $status: EcoReductionTargetStatus!) {\n ecoSetReductionTargetStatus(id: $id, status: $status) { ${TARGET_FIELDS} }\n }`,\n vars,\n apiGatewayUrl\n );\n return result.ecoSetReductionTargetStatus;\n },\n onSuccess: () => qc.invalidateQueries({ queryKey: KEY.reductionTargets }),\n });\n};\n\n// ============================================================================\n// Reports (Sustainability & Reporting)\n// ============================================================================\n\nexport const useEcoReports = (status?: EcoReportStatus, pagination?: PaginationInput) => {\n const { apiGatewayUrl } = useEcoImpactHub();\n return useQuery({\n queryKey: [...KEY.reports, status ?? 'all', pagination ?? DEFAULT_PAGE],\n queryFn: async () => {\n const result = await ecoQuery<{ ecoListReports: EcoConnection<EcoReport> }>(\n `query EcoListReports($status: EcoReportStatus, $pagination: PaginationInput) {\n ecoListReports(status: $status, pagination: $pagination) {\n items { ${REPORT_FIELDS} }\n pageInfo { cursor hasMore }\n }\n }`,\n { status, pagination: pagination ?? DEFAULT_PAGE },\n apiGatewayUrl\n );\n return result.ecoListReports;\n },\n });\n};\n\nexport const useEcoReport = (id: string | undefined) => {\n const { apiGatewayUrl } = useEcoImpactHub();\n return useQuery({\n queryKey: KEY.report(id ?? ''),\n enabled: Boolean(id),\n queryFn: async () => {\n const result = await ecoQuery<{ ecoReport: EcoReport | null }>(\n `query EcoReport($id: ID!) {\n ecoReport(id: $id) {\n ${REPORT_FIELDS}\n lineItems { ${LINE_ITEM_FIELDS} }\n }\n }`,\n { id },\n apiGatewayUrl\n );\n return result.ecoReport;\n },\n });\n};\n\nexport const useEcoCreateReport = () => {\n const { apiGatewayUrl } = useEcoImpactHub();\n const qc = useQueryClient();\n return useMutation({\n mutationFn: async (input: EcoCreateReportInput) => {\n const result = await ecoQuery<{ ecoCreateReport: EcoReport }>(\n `mutation EcoCreateReport($input: EcoCreateReportInput!) {\n ecoCreateReport(input: $input) { ${REPORT_FIELDS} }\n }`,\n { input },\n apiGatewayUrl\n );\n return result.ecoCreateReport;\n },\n onSuccess: () => qc.invalidateQueries({ queryKey: KEY.reports }),\n });\n};\n\nexport const useEcoAddReportLineItem = () => {\n const { apiGatewayUrl } = useEcoImpactHub();\n const qc = useQueryClient();\n return useMutation({\n mutationFn: async (input: EcoAddReportLineItemInput) => {\n const result = await ecoQuery<{ ecoAddReportLineItem: EcoReportLineItem }>(\n `mutation EcoAddReportLineItem($input: EcoAddReportLineItemInput!) {\n ecoAddReportLineItem(input: $input) { ${LINE_ITEM_FIELDS} }\n }`,\n { input },\n apiGatewayUrl\n );\n return result.ecoAddReportLineItem;\n },\n onSuccess: (data) => {\n qc.invalidateQueries({ queryKey: KEY.reports });\n qc.invalidateQueries({ queryKey: KEY.report(data.reportId) });\n },\n });\n};\n\nexport const useEcoPublishReport = () => {\n const { apiGatewayUrl } = useEcoImpactHub();\n const qc = useQueryClient();\n return useMutation({\n mutationFn: async (vars: { id: string; fileId?: string | null }) => {\n const result = await ecoQuery<{ ecoPublishReport: EcoReport }>(\n `mutation EcoPublishReport($id: ID!, $fileId: ID) {\n ecoPublishReport(id: $id, fileId: $fileId) { ${REPORT_FIELDS} }\n }`,\n vars,\n apiGatewayUrl\n );\n return result.ecoPublishReport;\n },\n onSuccess: (data) => {\n qc.invalidateQueries({ queryKey: KEY.reports });\n qc.invalidateQueries({ queryKey: KEY.report(data.id) });\n },\n });\n};\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"useEcoTags.js","names":[],"sources":["../../../src/ecoimpacthub/hooks/useEcoTags.ts"],"sourcesContent":["/**\n * EcoImpactHub — wspace-tags-svc UniversalTag REUSE hooks (Integration C).\n * @module ecoimpacthub/hooks/useEcoTags\n *\n * The eco svc does NOT expose a tags field, so we drive the platform tag\n * assignment store directly (approach (b) in the reuse audit) — no eco-svc\n * change. All operations are composed into the WORKSPACE supergraph:\n * - `getEntityTags(entityType, entityId)` → current assignments (display)\n * - `searchTags(query)` → pick an existing tag\n * - `createTag(input)` → create a new tag (needs a vocabulary)\n * - `assignTag(input)` → link tag ↔ entity\n * - `removeTagAssignment(id)` → unlink\n *\n * `createTag` requires a `vocabularyId`; we resolve a stable, workspace-scoped\n * \"ecoimpacthub\" vocabulary (`getVocabularyByKey`, creating it on first use).\n *\n * Transport reuses `wspaceFetch` from `useEcoFiles`. Lives in a separate file;\n * `useEcoImpactHubApi.ts` is untouched.\n */\n\nimport { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';\n\nimport { useEcoImpactHub } from '../context';\nimport { wspaceFetch } from './useEcoFiles';\n\nexport interface EcoTag {\n id: string;\n label: string;\n color: string | null;\n key: string;\n}\n\nexport interface EcoTagAssignment {\n id: string;\n tag: EcoTag;\n}\n\nconst ECO_VOCABULARY_KEY = 'ecoimpacthub';\n\nconst entityTagsKey = (entityType: string, entityId: string) =>\n ['ecoimpacthub', 'entity-tags', entityType, entityId] as const;\n\nconst TAG_FIELDS = 'id label color key';\n\nconst slugify = (value: string): string =>\n value\n .toLowerCase()\n .trim()\n .replace(/[^a-z0-9]+/g, '-')\n .replace(/^-+|-+$/g, '')\n .slice(0, 48) || 'tag';\n\n/** Current tag assignments for an eco entity. */\nexport const useEcoEntityTags = (entityType: string, entityId: string | undefined) => {\n const { apiGatewayUrl } = useEcoImpactHub();\n return useQuery({\n queryKey: entityTagsKey(entityType, entityId ?? ''),\n enabled: Boolean(entityId),\n queryFn: async () => {\n const data = await wspaceFetch<{ getEntityTags: EcoTagAssignment[] }>(\n `query EcoGetEntityTags($entityType: String!, $entityId: ID!) {\n getEntityTags(entityType: $entityType, entityId: $entityId) {\n id\n tag { ${TAG_FIELDS} }\n }\n }`,\n { entityType, entityId },\n apiGatewayUrl\n );\n return data.getEntityTags;\n },\n });\n};\n\n/** Search existing tags (enabled only for a non-empty query). */\nexport const useEcoTagSearch = (query: string) => {\n const { apiGatewayUrl } = useEcoImpactHub();\n const trimmed = query.trim();\n return useQuery({\n queryKey: ['ecoimpacthub', 'tag-search', trimmed],\n enabled: trimmed.length > 0,\n queryFn: async () => {\n const data = await wspaceFetch<{ searchTags: EcoTag[] }>(\n `query EcoSearchTags($query: String!, $limit: Int) {\n searchTags(query: $query, limit: $limit) { ${TAG_FIELDS} }\n }`,\n { query: trimmed, limit: 8 },\n apiGatewayUrl\n );\n return data.searchTags;\n },\n });\n};\n\n/** Assign an existing tag to the entity. */\nexport const useEcoAssignTag = (entityType: string, entityId: string) => {\n const { apiGatewayUrl } = useEcoImpactHub();\n const qc = useQueryClient();\n return useMutation({\n mutationFn: async (tagId: string) => {\n const data = await wspaceFetch<{ assignTag: { id: string } }>(\n `mutation EcoAssignTag($input: AssignTagInput!) {\n assignTag(input: $input) { id }\n }`,\n { input: { entityType, entityId, tagId } },\n apiGatewayUrl\n );\n return data.assignTag;\n },\n onSuccess: () => qc.invalidateQueries({ queryKey: entityTagsKey(entityType, entityId) }),\n });\n};\n\n/** Remove a tag assignment by its assignment id. */\nexport const useEcoRemoveTagAssignment = (entityType: string, entityId: string) => {\n const { apiGatewayUrl } = useEcoImpactHub();\n const qc = useQueryClient();\n return useMutation({\n mutationFn: async (assignmentId: string) => {\n const data = await wspaceFetch<{ removeTagAssignment: boolean }>(\n `mutation EcoRemoveTagAssignment($id: ID!) {\n removeTagAssignment(id: $id)\n }`,\n { id: assignmentId },\n apiGatewayUrl\n );\n return data.removeTagAssignment;\n },\n onSuccess: () => qc.invalidateQueries({ queryKey: entityTagsKey(entityType, entityId) }),\n });\n};\n\n/** Resolve (creating on first use) the workspace-scoped \"ecoimpacthub\" vocabulary. */\nconst resolveEcoVocabularyId = async (baseUrl?: string): Promise<string> => {\n const existing = await wspaceFetch<{ getVocabularyByKey: { id: string } | null }>(\n `query EcoGetVocabulary($key: String!) {\n getVocabularyByKey(key: $key) { id }\n }`,\n { key: ECO_VOCABULARY_KEY },\n baseUrl\n );\n if (existing.getVocabularyByKey?.id) {\n return existing.getVocabularyByKey.id;\n }\n const created = await wspaceFetch<{ createVocabulary: { id: string } }>(\n `mutation EcoCreateVocabulary($input: CreateVocabularyInput!) {\n createVocabulary(input: $input) { id }\n }`,\n {\n input: {\n key: ECO_VOCABULARY_KEY,\n name: 'EcoImpactHub',\n description: 'Tags for EcoImpactHub sites, species and other records.',\n allowCustomTags: true,\n },\n },\n baseUrl\n );\n return created.createVocabulary.id;\n};\n\n/** Create a new tag from a free-text label and assign it to the entity. */\nexport const useEcoCreateAndAssignTag = (entityType: string, entityId: string) => {\n const { apiGatewayUrl } = useEcoImpactHub();\n const qc = useQueryClient();\n return useMutation({\n mutationFn: async (label: string) => {\n const trimmed = label.trim();\n if (!trimmed) {\n throw new Error('A tag label is required.');\n }\n const vocabularyId = await resolveEcoVocabularyId(apiGatewayUrl);\n // A unique key avoids collisions with existing tags in the vocabulary;\n // the (non-unique) label is what users see.\n const key = `${slugify(trimmed)}-${Math.random().toString(36).slice(2, 6)}`;\n const created = await wspaceFetch<{ createTag: { id: string } }>(\n `mutation EcoCreateTag($input: CreateTagInput!) {\n createTag(input: $input) { id }\n }`,\n { input: { vocabularyId, key, label: trimmed } },\n apiGatewayUrl\n );\n await wspaceFetch<{ assignTag: { id: string } }>(\n `mutation EcoAssignCreatedTag($input: AssignTagInput!) {\n assignTag(input: $input) { id }\n }`,\n { input: { entityType, entityId, tagId: created.createTag.id } },\n apiGatewayUrl\n );\n return created.createTag;\n },\n onSuccess: () => qc.invalidateQueries({ queryKey: entityTagsKey(entityType, entityId) }),\n });\n};\n"],"mappings":";;;;;AAqCA,IAAM,IAAqB,gBAErB,KAAiB,GAAoB,MACzC;CAAC;CAAgB;CAAe;CAAY;CAAS,EAEjD,IAAa,sBAEb,KAAW,MACf,EACG,aAAa,CACb,MAAM,CACN,QAAQ,eAAe,IAAI,CAC3B,QAAQ,YAAY,GAAG,CACvB,MAAM,GAAG,GAAG,IAAI,OAGR,KAAoB,GAAoB,MAAiC;CACpF,IAAM,EAAE,qBAAkB,GAAiB;AAC3C,QAAO,EAAS;EACd,UAAU,EAAc,GAAY,KAAY,GAAG;EACnD,SAAS,EAAQ;EACjB,SAAS,aACM,MAAM,EACjB;;;oBAGY,EAAW;;YAGvB;GAAE;GAAY;GAAU,EACxB,EACD,EACW;EAEf,CAAC;GAIS,KAAmB,MAAkB;CAChD,IAAM,EAAE,qBAAkB,GAAiB,EACrC,IAAU,EAAM,MAAM;AAC5B,QAAO,EAAS;EACd,UAAU;GAAC;GAAgB;GAAc;GAAQ;EACjD,SAAS,EAAQ,SAAS;EAC1B,SAAS,aACM,MAAM,EACjB;uDAC+C,EAAW;YAE1D;GAAE,OAAO;GAAS,OAAO;GAAG,EAC5B,EACD,EACW;EAEf,CAAC;GAIS,KAAmB,GAAoB,MAAqB;CACvE,IAAM,EAAE,qBAAkB,GAAiB,EACrC,IAAK,GAAgB;AAC3B,QAAO,EAAY;EACjB,YAAY,OAAO,OACJ,MAAM,EACjB,0GAGA,EAAE,OAAO;GAAE;GAAY;GAAU;GAAO,EAAE,EAC1C,EACD,EACW;EAEd,iBAAiB,EAAG,kBAAkB,EAAE,UAAU,EAAc,GAAY,EAAS,EAAE,CAAC;EACzF,CAAC;GAIS,KAA6B,GAAoB,MAAqB;CACjF,IAAM,EAAE,qBAAkB,GAAiB,EACrC,IAAK,GAAgB;AAC3B,QAAO,EAAY;EACjB,YAAY,OAAO,OACJ,MAAM,EACjB,kGAGA,EAAE,IAAI,GAAc,EACpB,EACD,EACW;EAEd,iBAAiB,EAAG,kBAAkB,EAAE,UAAU,EAAc,GAAY,EAAS,EAAE,CAAC;EACzF,CAAC;GAIE,IAAyB,OAAO,MAAsC;CAC1E,IAAM,IAAW,MAAM,EACrB,8FAGA,EAAE,KAAK,GAAoB,EAC3B,EACD;AAkBD,QAjBI,EAAS,oBAAoB,KACxB,EAAS,mBAAmB,MAErB,MAAM,EACpB,uHAGA,EACE,OAAO;EACL,KAAK;EACL,MAAM;EACN,aAAa;EACb,iBAAiB;EAClB,EACF,EACD,EACD,EACc,iBAAiB;GAIrB,KAA4B,GAAoB,MAAqB;CAChF,IAAM,EAAE,qBAAkB,GAAiB,EACrC,IAAK,GAAgB;AAC3B,QAAO,EAAY;EACjB,YAAY,OAAO,MAAkB;GACnC,IAAM,IAAU,EAAM,MAAM;AAC5B,OAAI,CAAC,EACH,OAAU,MAAM,2BAA2B;GAM7C,IAAM,IAAU,MAAM,EACpB,0GAGA,EAAE,OAAO;IAAE,cARQ,MAAM,EAAuB,EAAc;IAQrC,KALf,GAAG,EAAQ,EAAQ,CAAC,GAAG,KAAK,QAAQ,CAAC,SAAS,GAAG,CAAC,MAAM,GAAG,EAAE;IAKzC,OAAO;IAAS,EAAE,EAChD,EACD;AAQD,UAPA,MAAM,EACJ,iHAGA,EAAE,OAAO;IAAE;IAAY;IAAU,OAAO,EAAQ,UAAU;IAAI,EAAE,EAChE,EACD,EACM,EAAQ;;EAEjB,iBAAiB,EAAG,kBAAkB,EAAE,UAAU,EAAc,GAAY,EAAS,EAAE,CAAC;EACzF,CAAC"}
1
+ {"version":3,"file":"useEcoTags.js","names":[],"sources":["../../../src/ecoimpacthub/hooks/useEcoTags.ts"],"sourcesContent":["/**\n * EcoImpactHub — wspace-tags-svc UniversalTag REUSE hooks (Integration C).\n * @module ecoimpacthub/hooks/useEcoTags\n *\n * The eco svc does NOT expose a tags field, so we drive the platform tag\n * assignment store directly (approach (b) in the reuse audit) — no eco-svc\n * change. All operations are composed into the WORKSPACE supergraph:\n * - `getEntityTags(entityType, entityId)` → current assignments (display)\n * - `searchTags(query)` → pick an existing tag\n * - `createTag(input)` → create a new tag (needs a vocabulary)\n * - `assignTag(input)` → link tag ↔ entity\n * - `removeTagAssignment(id)` → unlink\n *\n * `createTag` requires a `vocabularyId`; we resolve a stable, workspace-scoped\n * \"ecoimpacthub\" vocabulary (`getVocabularyByKey`, creating it on first use).\n *\n * Transport reuses `wspaceFetch` from `useEcoFiles`. Lives in a separate file;\n * `useEcoImpactHubApi.ts` is untouched.\n */\n\nimport { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';\n\nimport { useEcoImpactHub } from '../context';\nimport { wspaceFetch } from './useEcoFiles';\n\nexport interface EcoTag {\n id: string;\n label: string;\n color: string | null;\n key: string;\n}\n\nexport interface EcoTagAssignment {\n id: string;\n tag: EcoTag;\n}\n\nconst ECO_VOCABULARY_KEY = 'ecoimpacthub';\n\nconst entityTagsKey = (entityType: string, entityId: string) =>\n ['ecoimpacthub', 'entity-tags', entityType, entityId] as const;\n\nconst TAG_FIELDS = 'id label color key';\n\nconst slugify = (value: string): string =>\n value\n .toLowerCase()\n .trim()\n .replace(/[^a-z0-9]+/g, '-')\n .replace(/^-+|-+$/g, '')\n .slice(0, 48) || 'tag';\n\n/** Current tag assignments for an eco entity. */\nexport const useEcoEntityTags = (entityType: string, entityId: string | undefined) => {\n const { apiGatewayUrl } = useEcoImpactHub();\n return useQuery({\n queryKey: entityTagsKey(entityType, entityId ?? ''),\n enabled: Boolean(entityId),\n queryFn: async () => {\n const data = await wspaceFetch<{ getEntityTags: EcoTagAssignment[] }>(\n `query EcoGetEntityTags($entityType: String!, $entityId: ID!) {\n getEntityTags(entityType: $entityType, entityId: $entityId) {\n id\n tag { ${TAG_FIELDS} }\n }\n }`,\n { entityType, entityId },\n apiGatewayUrl\n );\n return data.getEntityTags;\n },\n });\n};\n\n/** Search existing tags (enabled only for a non-empty query). */\nexport const useEcoTagSearch = (query: string) => {\n const { apiGatewayUrl } = useEcoImpactHub();\n const trimmed = query.trim();\n return useQuery({\n queryKey: ['ecoimpacthub', 'tag-search', trimmed],\n enabled: trimmed.length > 0,\n queryFn: async () => {\n const data = await wspaceFetch<{ searchTags: EcoTag[] }>(\n `query EcoSearchTags($query: String!, $limit: Int) {\n searchTags(query: $query, limit: $limit) { ${TAG_FIELDS} }\n }`,\n { query: trimmed, limit: 8 },\n apiGatewayUrl\n );\n return data.searchTags;\n },\n });\n};\n\n/** Assign an existing tag to the entity. */\nexport const useEcoAssignTag = (entityType: string, entityId: string) => {\n const { apiGatewayUrl } = useEcoImpactHub();\n const qc = useQueryClient();\n return useMutation({\n mutationFn: async (tagId: string) => {\n const data = await wspaceFetch<{ assignTag: { id: string } }>(\n `mutation EcoAssignTag($input: AssignTagInput!) {\n assignTag(input: $input) { id }\n }`,\n { input: { entityType, entityId, tagId } },\n apiGatewayUrl\n );\n return data.assignTag;\n },\n onSuccess: () => qc.invalidateQueries({ queryKey: entityTagsKey(entityType, entityId) }),\n });\n};\n\n/** Remove a tag assignment by its assignment id. */\nexport const useEcoRemoveTagAssignment = (entityType: string, entityId: string) => {\n const { apiGatewayUrl } = useEcoImpactHub();\n const qc = useQueryClient();\n return useMutation({\n mutationFn: async (assignmentId: string) => {\n const data = await wspaceFetch<{ removeTagAssignment: boolean }>(\n `mutation EcoRemoveTagAssignment($id: ID!) {\n removeTagAssignment(id: $id)\n }`,\n { id: assignmentId },\n apiGatewayUrl\n );\n return data.removeTagAssignment;\n },\n onSuccess: () => qc.invalidateQueries({ queryKey: entityTagsKey(entityType, entityId) }),\n });\n};\n\n/** Resolve (creating on first use) the workspace-scoped \"ecoimpacthub\" vocabulary. */\nconst resolveEcoVocabularyId = async (baseUrl?: string): Promise<string> => {\n const existing = await wspaceFetch<{ getVocabularyByKey: { id: string } | null }>(\n `query EcoGetVocabulary($key: String!) {\n getVocabularyByKey(key: $key) { id }\n }`,\n { key: ECO_VOCABULARY_KEY },\n baseUrl\n );\n if (existing.getVocabularyByKey?.id) {\n return existing.getVocabularyByKey.id;\n }\n const created = await wspaceFetch<{ createVocabulary: { id: string } }>(\n `mutation EcoCreateVocabulary($input: CreateVocabularyInput!) {\n createVocabulary(input: $input) { id }\n }`,\n {\n input: {\n key: ECO_VOCABULARY_KEY,\n name: 'EcoImpactHub',\n description: 'Tags for EcoImpactHub sites, species and other records.',\n allowCustomTags: true,\n },\n },\n baseUrl\n );\n return created.createVocabulary.id;\n};\n\n/** Create a new tag from a free-text label and assign it to the entity. */\nexport const useEcoCreateAndAssignTag = (entityType: string, entityId: string) => {\n const { apiGatewayUrl } = useEcoImpactHub();\n const qc = useQueryClient();\n return useMutation({\n mutationFn: async (label: string) => {\n const trimmed = label.trim();\n if (!trimmed) {\n throw new Error('A tag label is required.');\n }\n const vocabularyId = await resolveEcoVocabularyId(apiGatewayUrl);\n // A unique key avoids collisions with existing tags in the vocabulary;\n // the (non-unique) label is what users see.\n const key = `${slugify(trimmed)}-${Math.random().toString(36).slice(2, 6)}`;\n const created = await wspaceFetch<{ createTag: { id: string } }>(\n `mutation EcoCreateTag($input: CreateTagInput!) {\n createTag(input: $input) { id }\n }`,\n { input: { vocabularyId, key, label: trimmed } },\n apiGatewayUrl\n );\n await wspaceFetch<{ assignTag: { id: string } }>(\n `mutation EcoAssignCreatedTag($input: AssignTagInput!) {\n assignTag(input: $input) { id }\n }`,\n { input: { entityType, entityId, tagId: created.createTag.id } },\n apiGatewayUrl\n );\n return created.createTag;\n },\n onSuccess: () => qc.invalidateQueries({ queryKey: entityTagsKey(entityType, entityId) }),\n });\n};\n"],"mappings":";;;;;AAqCA,IAAM,IAAqB,gBAErB,KAAiB,GAAoB,MACzC;CAAC;CAAgB;CAAe;CAAY;AAAQ,GAEhD,IAAa,sBAEb,KAAW,MACf,EACG,YAAY,EACZ,KAAK,EACL,QAAQ,eAAe,GAAG,EAC1B,QAAQ,YAAY,EAAE,EACtB,MAAM,GAAG,EAAE,KAAK,OAGR,KAAoB,GAAoB,MAAiC;CACpF,IAAM,EAAE,qBAAkB,EAAgB;CAC1C,OAAO,EAAS;EACd,UAAU,EAAc,GAAY,KAAY,EAAE;EAClD,SAAS,EAAQ;EACjB,SAAS,aAWA,MAVY,EACjB;;;oBAGY,EAAW;;YAGvB;GAAE;GAAY;EAAS,GACvB,CACF,GACY;CAEhB,CAAC;AACH,GAGa,KAAmB,MAAkB;CAChD,IAAM,EAAE,qBAAkB,EAAgB,GACpC,IAAU,EAAM,KAAK;CAC3B,OAAO,EAAS;EACd,UAAU;GAAC;GAAgB;GAAc;EAAO;EAChD,SAAS,EAAQ,SAAS;EAC1B,SAAS,aAQA,MAPY,EACjB;uDAC+C,EAAW;YAE1D;GAAE,OAAO;GAAS,OAAO;EAAE,GAC3B,CACF,GACY;CAEhB,CAAC;AACH,GAGa,KAAmB,GAAoB,MAAqB;CACvE,IAAM,EAAE,qBAAkB,EAAgB,GACpC,IAAK,EAAe;CAC1B,OAAO,EAAY;EACjB,YAAY,OAAO,OAQV,MAPY,EACjB,0GAGA,EAAE,OAAO;GAAE;GAAY;GAAU;EAAM,EAAE,GACzC,CACF,GACY;EAEd,iBAAiB,EAAG,kBAAkB,EAAE,UAAU,EAAc,GAAY,CAAQ,EAAE,CAAC;CACzF,CAAC;AACH,GAGa,KAA6B,GAAoB,MAAqB;CACjF,IAAM,EAAE,qBAAkB,EAAgB,GACpC,IAAK,EAAe;CAC1B,OAAO,EAAY;EACjB,YAAY,OAAO,OAQV,MAPY,EACjB,kGAGA,EAAE,IAAI,EAAa,GACnB,CACF,GACY;EAEd,iBAAiB,EAAG,kBAAkB,EAAE,UAAU,EAAc,GAAY,CAAQ,EAAE,CAAC;CACzF,CAAC;AACH,GAGM,IAAyB,OAAO,MAAsC;CAC1E,IAAM,IAAW,MAAM,EACrB,8FAGA,EAAE,KAAK,EAAmB,GAC1B,CACF;CAkBA,OAjBI,EAAS,oBAAoB,KACxB,EAAS,mBAAmB,MAgB9B,MAde,EACpB,uHAGA,EACE,OAAO;EACL,KAAK;EACL,MAAM;EACN,aAAa;EACb,iBAAiB;CACnB,EACF,GACA,CACF,GACe,iBAAiB;AAClC,GAGa,KAA4B,GAAoB,MAAqB;CAChF,IAAM,EAAE,qBAAkB,EAAgB,GACpC,IAAK,EAAe;CAC1B,OAAO,EAAY;EACjB,YAAY,OAAO,MAAkB;GACnC,IAAM,IAAU,EAAM,KAAK;GAC3B,IAAI,CAAC,GACH,MAAU,MAAM,0BAA0B;GAM5C,IAAM,IAAU,MAAM,EACpB,0GAGA,EAAE,OAAO;IAAE,cAAA,MARc,EAAuB,CAAa;IAQpC,KAAA,GALZ,EAAQ,CAAO,EAAE,GAAG,KAAK,OAAO,EAAE,SAAS,EAAE,EAAE,MAAM,GAAG,CAAC;IAKxC,OAAO;GAAQ,EAAE,GAC/C,CACF;GAQA,OAPA,MAAM,EACJ,iHAGA,EAAE,OAAO;IAAE;IAAY;IAAU,OAAO,EAAQ,UAAU;GAAG,EAAE,GAC/D,CACF,GACO,EAAQ;EACjB;EACA,iBAAiB,EAAG,kBAAkB,EAAE,UAAU,EAAc,GAAY,CAAQ,EAAE,CAAC;CACzF,CAAC;AACH"}
@@ -1 +1 @@
1
- {"version":3,"file":"DashboardPage.js","names":[],"sources":["../../../src/ecoimpacthub/pages/DashboardPage.tsx"],"sourcesContent":["/**\n * Dashboard — EcoImpactHub environment, conservation & sustainability overview.\n * @module ecoimpacthub/pages\n *\n * KPIs are aggregated client-side from the real `eco*` list endpoints plus the\n * `ecoCarbonTotalKg` aggregate (by scope). Widgets show recent field\n * observations and open threshold breaches. No mock data.\n */\n\nimport { useMemo } from 'react';\nimport {\n MapPin,\n PawPrint,\n Trees,\n Factory,\n TriangleAlert,\n Binoculars,\n ShieldCheck,\n ArrowRight,\n Radio,\n} from 'lucide-react';\nimport { GlassCard } from '@burdenoff/fe-libs/ui';\nimport {\n BarChart,\n Bar,\n XAxis,\n YAxis,\n CartesianGrid,\n ResponsiveContainer,\n Tooltip,\n Cell,\n LabelList,\n} from 'recharts';\nimport { PageLayout } from '../components/PageLayout';\nimport { MetricsCard } from '../components/MetricsCard';\nimport { StatusBadge } from '../components/StatusBadge';\nimport { MiniBar } from '../components/MiniBar';\nimport { LoadingView, Section } from '../components/StateViews';\nimport { EmptyState } from '../components/EmptyState';\nimport { ScopeSplitBar, ScopeLegend, Meter, SCOPE_HUE } from './carbonVisuals';\nimport { useEcoImpactHub } from '../context';\nimport {\n useEcoSites,\n useEcoSpecies,\n useEcoHabitatProjects,\n useEcoCarbonTotals,\n useEcoMeasurements,\n useEcoObservations,\n useEcoSensors,\n} from '../hooks';\nimport {\n formatNumber,\n formatRelativeTime,\n formatCo2e,\n formatEnumLabel,\n formatPercent,\n} from '../utils/formatters';\nimport type { EcoConservationStatus, EcoEmissionScope } from '../types';\n\nconst ACTIVE_PROJECT_STATUSES = new Set(['PLANNED', 'IN_PROGRESS', 'MONITORING']);\n\n/** Conservation-status → meter tint (semantic, IUCN-severity ordered). */\nconst CONSERVATION_TONE: Record<EcoConservationStatus, string> = {\n LEAST_CONCERN: 'var(--color-status-success-text)',\n NEAR_THREATENED: 'var(--color-accent-teal)',\n VULNERABLE: 'var(--color-status-warning-text)',\n ENDANGERED: 'var(--color-status-error-text)',\n CRITICALLY_ENDANGERED: 'var(--color-status-error-text)',\n EXTINCT_IN_WILD: 'var(--color-text-tertiary)',\n DATA_DEFICIENT: 'var(--color-text-tertiary)',\n NOT_EVALUATED: 'var(--color-text-tertiary)',\n};\n\nexport function DashboardPage() {\n const { navigateTo, currentUser } = useEcoImpactHub();\n\n const sites = useEcoSites({ pagination: { limit: 100, offset: 0 } });\n const species = useEcoSpecies({ pagination: { limit: 100, offset: 0 } });\n const projects = useEcoHabitatProjects();\n const carbon = useEcoCarbonTotals();\n const breaches = useEcoMeasurements({ breachesOnly: true, pagination: { limit: 6, offset: 0 } });\n const observations = useEcoObservations({ pagination: { limit: 6, offset: 0 } });\n const sensors = useEcoSensors();\n\n const siteRows = sites.data?.items ?? [];\n const speciesRows = species.data?.items ?? [];\n const projectRows = projects.data?.items ?? [];\n const sensorRows = sensors.data?.items ?? [];\n\n const activeSites = siteRows.filter((s) => s.status === 'ACTIVE').length;\n const priorityCount = speciesRows.filter((s) => s.isPriority).length;\n const threatened = speciesRows.filter((s) =>\n ['VULNERABLE', 'ENDANGERED', 'CRITICALLY_ENDANGERED'].includes(s.conservationStatus)\n ).length;\n const activeProjects = projectRows.filter((p) => ACTIVE_PROJECT_STATUSES.has(p.status)).length;\n const onlineSensors = sensorRows.filter((s) => s.status === 'ONLINE').length;\n const offlineSensors = sensorRows.filter((s) =>\n ['OFFLINE', 'DEGRADED', 'MAINTENANCE'].includes(s.status)\n ).length;\n\n const carbonData = useMemo<{ scope: EcoEmissionScope; label: string; kg: number }[]>(() => {\n const c = carbon.data;\n if (!c) return [];\n return [\n { scope: 'SCOPE_1', label: 'Scope 1', kg: c.scope1 },\n { scope: 'SCOPE_2', label: 'Scope 2', kg: c.scope2 },\n { scope: 'SCOPE_3', label: 'Scope 3', kg: c.scope3 },\n ];\n }, [carbon.data]);\n\n const isInitialLoading =\n sites.isLoading && species.isLoading && projects.isLoading && carbon.isLoading;\n\n if (isInitialLoading) {\n return (\n <PageLayout title=\"EcoImpactHub\" titleGradient aurora>\n <LoadingView label=\"Loading your impact overview…\" />\n </PageLayout>\n );\n }\n\n const greetingName = currentUser?.firstName || currentUser?.name || 'there';\n const breachRows = breaches.data?.items ?? [];\n const observationRows = observations.data?.items ?? [];\n\n return (\n <PageLayout\n title=\"EcoImpactHub\"\n titleGradient\n aurora\n description={`Welcome back, ${greetingName} — your environment, conservation & sustainability control room.`}\n >\n {/* KPI grid */}\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-6\">\n <MetricsCard\n icon={<MapPin className=\"h-5 w-5\" />}\n value={\n sites.data?.pageInfo?.hasMore ? `${siteRows.length}+` : formatNumber(siteRows.length)\n }\n label=\"Monitored sites\"\n footer={\n <div className=\"space-y-2\">\n <MiniBar\n value={siteRows.length ? (activeSites / siteRows.length) * 100 : 0}\n height={4}\n barClassName=\"bg-action-primary-bg\"\n />\n <p className=\"text-xs text-text-tertiary\">\n <span className=\"tabular-nums text-text-secondary\">{activeSites}</span> active\n </p>\n </div>\n }\n />\n <MetricsCard\n icon={<PawPrint className=\"h-5 w-5\" />}\n value={\n species.data?.pageInfo?.hasMore\n ? `${speciesRows.length}+`\n : formatNumber(speciesRows.length)\n }\n label=\"Species tracked\"\n accent={threatened > 0 ? 'warning' : undefined}\n footer={\n <div className=\"space-y-2\">\n <MiniBar\n value={speciesRows.length ? (threatened / speciesRows.length) * 100 : 0}\n height={4}\n barClassName=\"bg-status-warning-text\"\n />\n <p className=\"text-xs text-text-tertiary\">\n <span className=\"tabular-nums text-text-secondary\">{priorityCount}</span> priority ·{' '}\n <span className=\"tabular-nums text-text-secondary\">{threatened}</span> threatened\n </p>\n </div>\n }\n />\n <MetricsCard\n icon={<Trees className=\"h-5 w-5\" />}\n value={formatNumber(activeProjects)}\n label=\"Active habitat projects\"\n footer={\n <div className=\"space-y-2\">\n <MiniBar\n value={projectRows.length ? (activeProjects / projectRows.length) * 100 : 0}\n height={4}\n barClassName=\"bg-action-primary-bg\"\n />\n <p className=\"text-xs text-text-tertiary\">\n <span className=\"tabular-nums text-text-secondary\">\n {formatNumber(projectRows.length)}\n </span>{' '}\n total\n </p>\n </div>\n }\n />\n <MetricsCard\n icon={<Factory className=\"h-5 w-5\" />}\n value={carbon.data ? formatCo2e(carbon.data.total) : '—'}\n label=\"Total footprint\"\n featured\n footer={\n carbon.data && carbon.data.total > 0 ? (\n <div className=\"space-y-2\">\n <ScopeSplitBar\n scope1={carbon.data.scope1}\n scope2={carbon.data.scope2}\n scope3={carbon.data.scope3}\n height={6}\n />\n <p className=\"text-xs text-text-tertiary\">Scope 1 · 2 · 3 split</p>\n </div>\n ) : undefined\n }\n />\n <MetricsCard\n icon={<Radio className=\"h-5 w-5\" />}\n value={formatNumber(onlineSensors)}\n label=\"Sensors online\"\n accent={offlineSensors > 0 ? 'warning' : 'success'}\n footer={\n <div className=\"space-y-2\">\n <MiniBar\n value={sensorRows.length ? (onlineSensors / sensorRows.length) * 100 : 0}\n height={4}\n barClassName=\"bg-status-success-text\"\n />\n <p className=\"text-xs text-text-tertiary\">\n <span className=\"tabular-nums text-text-secondary\">{offlineSensors}</span> need\n attention\n </p>\n </div>\n }\n />\n <MetricsCard\n icon={<TriangleAlert className=\"h-5 w-5\" />}\n value={\n breaches.data?.pageInfo?.hasMore\n ? `${breachRows.length}+`\n : formatNumber(breachRows.length)\n }\n label=\"Open breaches\"\n accent={breachRows.length > 0 ? 'error' : 'success'}\n footer={\n <p\n className={\n breachRows.length > 0\n ? 'text-xs font-medium text-status-error-text'\n : 'text-xs text-text-tertiary'\n }\n >\n {breachRows.length > 0 ? 'Needs attention' : 'All parameters in range'}\n </p>\n }\n />\n </div>\n\n <div className=\"mt-6 grid grid-cols-1 gap-6 lg:grid-cols-3\">\n {/* Carbon by scope */}\n <GlassCard treatment=\"glass\" className=\"p-6 lg:col-span-2\">\n <div className=\"mb-4 flex flex-wrap items-start justify-between gap-3\">\n <div>\n <h2 className=\"text-base font-semibold text-text-primary\">\n Carbon footprint by scope\n </h2>\n <p className=\"mt-0.5 text-sm text-text-secondary\">\n GHG Protocol Scope 1 / 2 / 3 emissions (CO₂e).\n </p>\n </div>\n <div className=\"flex items-center gap-4\">\n {carbon.data && carbon.data.total > 0 && (\n <div className=\"text-right\">\n <p className=\"text-lg font-semibold tabular-nums text-text-primary\">\n {formatCo2e(carbon.data.total)}\n </p>\n <p className=\"text-xs text-text-tertiary\">total</p>\n </div>\n )}\n <button\n type=\"button\"\n onClick={() => navigateTo('carbon')}\n className=\"inline-flex items-center gap-1 text-sm font-medium text-accent hover:underline\"\n >\n Ledger <ArrowRight className=\"h-4 w-4\" />\n </button>\n </div>\n </div>\n {carbonData.every((d) => d.kg === 0) ? (\n <EmptyState\n illustration=\"empty-data\"\n icon={<Factory className=\"h-10 w-10\" />}\n title=\"No carbon entries yet\"\n description=\"Record activity in the carbon ledger to see your Scope 1–3 footprint here.\"\n />\n ) : (\n <>\n <div className=\"h-56\">\n <ResponsiveContainer width=\"100%\" height=\"100%\">\n <BarChart data={carbonData} margin={{ top: 20, right: 8, left: 8, bottom: 0 }}>\n <defs>\n {carbonData.map((d) => (\n <linearGradient\n key={d.scope}\n id={`ecoScopeGrad-${d.scope}`}\n x1=\"0\"\n y1=\"0\"\n x2=\"0\"\n y2=\"1\"\n >\n <stop\n offset=\"0%\"\n style={{ stopColor: SCOPE_HUE[d.scope], stopOpacity: 1 }}\n />\n <stop\n offset=\"100%\"\n style={{ stopColor: SCOPE_HUE[d.scope], stopOpacity: 0.55 }}\n />\n </linearGradient>\n ))}\n </defs>\n <CartesianGrid\n vertical={false}\n stroke=\"var(--color-border-seam)\"\n strokeOpacity={0.6}\n strokeDasharray=\"3 4\"\n />\n <XAxis\n dataKey=\"label\"\n tick={{ fill: 'var(--color-text-secondary)', fontSize: 12 }}\n axisLine={{ stroke: 'var(--color-border-seam)' }}\n tickLine={false}\n dy={4}\n />\n <YAxis\n tick={{ fill: 'var(--color-text-tertiary)', fontSize: 11 }}\n axisLine={false}\n tickLine={false}\n width={48}\n tickFormatter={(v: number) =>\n v >= 1000 ? `${Math.round(v / 1000)}t` : `${v}`\n }\n />\n <Tooltip\n cursor={{ fill: 'var(--color-bg-sunken)', opacity: 0.4 }}\n contentStyle={{\n background: 'var(--color-bg-surface)',\n border: '1px solid var(--color-border-seam)',\n borderRadius: 12,\n color: 'var(--color-text-primary)',\n }}\n formatter={(value: number) => [formatCo2e(value), 'Emissions']}\n />\n <Bar dataKey=\"kg\" radius={[8, 8, 2, 2]} maxBarSize={72}>\n {carbonData.map((d) => (\n <Cell key={d.scope} fill={`url(#ecoScopeGrad-${d.scope})`} />\n ))}\n <LabelList\n dataKey=\"kg\"\n position=\"top\"\n formatter={(v: number) => (v > 0 ? formatCo2e(v) : '')}\n style={{\n fill: 'var(--color-text-secondary)',\n fontSize: 11,\n fontWeight: 600,\n }}\n />\n </Bar>\n </BarChart>\n </ResponsiveContainer>\n </div>\n <ScopeLegend data={carbonData} className=\"mt-4 border-t border-border-seam pt-4\" />\n </>\n )}\n </GlassCard>\n\n {/* Recent breaches */}\n <GlassCard treatment=\"glass\" className=\"p-6\">\n <div className=\"mb-4 flex items-center justify-between gap-3\">\n <div className=\"flex items-center gap-2\">\n <h2 className=\"text-base font-semibold text-text-primary\">Recent breaches</h2>\n {breachRows.length > 0 && (\n <span className=\"rounded-full bg-status-error-bg-subtle px-2 py-0.5 text-xs font-medium tabular-nums text-status-error-text\">\n {breaches.data?.pageInfo?.hasMore ? `${breachRows.length}+` : breachRows.length}\n </span>\n )}\n </div>\n <button\n type=\"button\"\n onClick={() => navigateTo('monitoring')}\n className=\"inline-flex items-center gap-1 text-sm font-medium text-accent hover:underline\"\n >\n Monitoring <ArrowRight className=\"h-4 w-4\" />\n </button>\n </div>\n {breachRows.length === 0 ? (\n <div className=\"flex flex-col items-center justify-center rounded-xl border border-dashed border-border-seam py-10 text-center\">\n <span className=\"flex size-12 items-center justify-center rounded-full bg-status-success-bg-subtle\">\n <ShieldCheck className=\"h-6 w-6 text-status-success-text\" />\n </span>\n <p className=\"mt-3 text-sm font-medium text-text-primary\">All parameters in range</p>\n <p className=\"mt-1 text-xs text-text-tertiary\">No open threshold breaches.</p>\n </div>\n ) : (\n <ul className=\"space-y-2.5\">\n {breachRows.map((m) => (\n <li\n key={m.id}\n className=\"flex items-start justify-between gap-3 rounded-xl bg-bg-sunken/50 p-3\"\n >\n <div className=\"flex min-w-0 items-start gap-2.5\">\n <span\n aria-hidden=\"true\"\n className=\"mt-1.5 size-2 shrink-0 rounded-full bg-status-error-text motion-safe:animate-vc-glow-pulse\"\n />\n <div className=\"min-w-0\">\n <p className=\"truncate text-sm font-medium text-text-primary\">\n {m.parameter}\n </p>\n <p className=\"text-xs text-text-tertiary\">\n <span className=\"tabular-nums\">\n {formatNumber(m.value)} {m.unit}\n </span>{' '}\n · {formatRelativeTime(m.recordedAt)}\n </p>\n </div>\n </div>\n <StatusBadge status=\"BREACH\" />\n </li>\n ))}\n </ul>\n )}\n </GlassCard>\n </div>\n\n {/* Recent observations */}\n <Section\n title=\"Recent field observations\"\n description=\"Latest wildlife & biodiversity sightings recorded across your sites.\"\n action={\n <button\n type=\"button\"\n onClick={() => navigateTo('observations')}\n className=\"inline-flex items-center gap-1 text-sm font-medium text-accent hover:underline\"\n >\n All observations <ArrowRight className=\"h-4 w-4\" />\n </button>\n }\n >\n {observationRows.length === 0 ? (\n <EmptyState\n illustration=\"empty-data\"\n icon={<Binoculars className=\"h-10 w-10\" />}\n title=\"No observations yet\"\n description=\"Record your first field observation to build your biodiversity dataset.\"\n />\n ) : (\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3\">\n {observationRows.map((o) => (\n <GlassCard\n key={o.id}\n treatment=\"plain\"\n className=\"p-4 transition-shadow hover:shadow-float\"\n >\n <div className=\"flex items-start justify-between gap-2\">\n <div className=\"flex min-w-0 items-start gap-2.5\">\n <span className=\"mt-0.5 flex size-8 shrink-0 items-center justify-center rounded-lg bg-[var(--color-accent-soft)] text-accent\">\n <Binoculars className=\"h-4 w-4\" />\n </span>\n <div className=\"min-w-0\">\n <p className=\"truncate text-sm font-semibold text-text-primary\">\n {o.species?.commonName ?? 'Unknown species'}\n </p>\n {o.species?.scientificName && (\n <p className=\"truncate text-xs italic text-text-tertiary\">\n {o.species.scientificName}\n </p>\n )}\n </div>\n </div>\n <span className=\"shrink-0 rounded-full bg-[var(--color-accent-soft)] px-2 py-0.5 text-xs font-medium tabular-nums text-text-primary\">\n ×{formatNumber(o.count)}\n </span>\n </div>\n <div className=\"mt-3 flex items-center justify-between border-t border-border-seam/70 pt-2.5\">\n <span className=\"rounded-full bg-bg-sunken px-2 py-0.5 text-xs text-text-secondary\">\n {formatEnumLabel(o.method)}\n </span>\n <span className=\"text-xs text-text-tertiary\">\n {formatRelativeTime(o.observedAt)}\n </span>\n </div>\n </GlassCard>\n ))}\n </div>\n )}\n </Section>\n\n {/* Conservation status snapshot */}\n {speciesRows.length > 0 && (\n <Section\n title=\"Conservation status\"\n description=\"Distribution of tracked species by IUCN-style status.\"\n >\n <GlassCard treatment=\"plain\" className=\"p-6\">\n <div className=\"space-y-3.5\">\n {(() => {\n const counts = new Map<string, number>();\n for (const s of speciesRows) {\n counts.set(s.conservationStatus, (counts.get(s.conservationStatus) ?? 0) + 1);\n }\n const total = speciesRows.length;\n return [...counts.entries()]\n .sort((a, b) => b[1] - a[1])\n .map(([status, count]) => {\n const pct = (count / total) * 100;\n const tone =\n CONSERVATION_TONE[status as EcoConservationStatus] ??\n 'var(--color-text-tertiary)';\n return (\n <div key={status} className=\"flex items-center gap-3 sm:gap-4\">\n <div className=\"w-36 shrink-0 sm:w-48\">\n <StatusBadge status={status} />\n </div>\n <Meter value={pct} color={tone} className=\"flex-1\" />\n <span className=\"w-9 shrink-0 text-right text-sm font-medium tabular-nums text-text-primary\">\n {count}\n </span>\n <span className=\"hidden w-12 shrink-0 text-right text-xs tabular-nums text-text-tertiary sm:inline\">\n {formatPercent(pct)}\n </span>\n </div>\n );\n });\n })()}\n </div>\n </GlassCard>\n </Section>\n )}\n </PageLayout>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"DashboardPage.js","names":[],"sources":["../../../src/ecoimpacthub/pages/DashboardPage.tsx"],"sourcesContent":["/**\n * Dashboard — EcoImpactHub environment, conservation & sustainability overview.\n * @module ecoimpacthub/pages\n *\n * KPIs are aggregated client-side from the real `eco*` list endpoints plus the\n * `ecoCarbonTotalKg` aggregate (by scope). Widgets show recent field\n * observations and open threshold breaches. No mock data.\n */\n\nimport { useMemo } from 'react';\nimport {\n MapPin,\n PawPrint,\n Trees,\n Factory,\n TriangleAlert,\n Binoculars,\n ShieldCheck,\n ArrowRight,\n Radio,\n} from 'lucide-react';\nimport { GlassCard } from '@burdenoff/fe-libs/ui';\nimport {\n BarChart,\n Bar,\n XAxis,\n YAxis,\n CartesianGrid,\n ResponsiveContainer,\n Tooltip,\n Cell,\n LabelList,\n} from 'recharts';\nimport { PageLayout } from '../components/PageLayout';\nimport { MetricsCard } from '../components/MetricsCard';\nimport { StatusBadge } from '../components/StatusBadge';\nimport { MiniBar } from '../components/MiniBar';\nimport { LoadingView, Section } from '../components/StateViews';\nimport { EmptyState } from '../components/EmptyState';\nimport { ScopeSplitBar, ScopeLegend, Meter, SCOPE_HUE } from './carbonVisuals';\nimport { useEcoImpactHub } from '../context';\nimport {\n useEcoSites,\n useEcoSpecies,\n useEcoHabitatProjects,\n useEcoCarbonTotals,\n useEcoMeasurements,\n useEcoObservations,\n useEcoSensors,\n} from '../hooks';\nimport {\n formatNumber,\n formatRelativeTime,\n formatCo2e,\n formatEnumLabel,\n formatPercent,\n} from '../utils/formatters';\nimport type { EcoConservationStatus, EcoEmissionScope } from '../types';\n\nconst ACTIVE_PROJECT_STATUSES = new Set(['PLANNED', 'IN_PROGRESS', 'MONITORING']);\n\n/** Conservation-status → meter tint (semantic, IUCN-severity ordered). */\nconst CONSERVATION_TONE: Record<EcoConservationStatus, string> = {\n LEAST_CONCERN: 'var(--color-status-success-text)',\n NEAR_THREATENED: 'var(--color-accent-teal)',\n VULNERABLE: 'var(--color-status-warning-text)',\n ENDANGERED: 'var(--color-status-error-text)',\n CRITICALLY_ENDANGERED: 'var(--color-status-error-text)',\n EXTINCT_IN_WILD: 'var(--color-text-tertiary)',\n DATA_DEFICIENT: 'var(--color-text-tertiary)',\n NOT_EVALUATED: 'var(--color-text-tertiary)',\n};\n\nexport function DashboardPage() {\n const { navigateTo, currentUser } = useEcoImpactHub();\n\n const sites = useEcoSites({ pagination: { limit: 100, offset: 0 } });\n const species = useEcoSpecies({ pagination: { limit: 100, offset: 0 } });\n const projects = useEcoHabitatProjects();\n const carbon = useEcoCarbonTotals();\n const breaches = useEcoMeasurements({ breachesOnly: true, pagination: { limit: 6, offset: 0 } });\n const observations = useEcoObservations({ pagination: { limit: 6, offset: 0 } });\n const sensors = useEcoSensors();\n\n const siteRows = sites.data?.items ?? [];\n const speciesRows = species.data?.items ?? [];\n const projectRows = projects.data?.items ?? [];\n const sensorRows = sensors.data?.items ?? [];\n\n const activeSites = siteRows.filter((s) => s.status === 'ACTIVE').length;\n const priorityCount = speciesRows.filter((s) => s.isPriority).length;\n const threatened = speciesRows.filter((s) =>\n ['VULNERABLE', 'ENDANGERED', 'CRITICALLY_ENDANGERED'].includes(s.conservationStatus)\n ).length;\n const activeProjects = projectRows.filter((p) => ACTIVE_PROJECT_STATUSES.has(p.status)).length;\n const onlineSensors = sensorRows.filter((s) => s.status === 'ONLINE').length;\n const offlineSensors = sensorRows.filter((s) =>\n ['OFFLINE', 'DEGRADED', 'MAINTENANCE'].includes(s.status)\n ).length;\n\n const carbonData = useMemo<{ scope: EcoEmissionScope; label: string; kg: number }[]>(() => {\n const c = carbon.data;\n if (!c) return [];\n return [\n { scope: 'SCOPE_1', label: 'Scope 1', kg: c.scope1 },\n { scope: 'SCOPE_2', label: 'Scope 2', kg: c.scope2 },\n { scope: 'SCOPE_3', label: 'Scope 3', kg: c.scope3 },\n ];\n }, [carbon.data]);\n\n const isInitialLoading =\n sites.isLoading && species.isLoading && projects.isLoading && carbon.isLoading;\n\n if (isInitialLoading) {\n return (\n <PageLayout title=\"EcoImpactHub\" titleGradient aurora>\n <LoadingView label=\"Loading your impact overview…\" />\n </PageLayout>\n );\n }\n\n const greetingName = currentUser?.firstName || currentUser?.name || 'there';\n const breachRows = breaches.data?.items ?? [];\n const observationRows = observations.data?.items ?? [];\n\n return (\n <PageLayout\n title=\"EcoImpactHub\"\n titleGradient\n aurora\n description={`Welcome back, ${greetingName} — your environment, conservation & sustainability control room.`}\n >\n {/* KPI grid */}\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-6\">\n <MetricsCard\n icon={<MapPin className=\"h-5 w-5\" />}\n value={\n sites.data?.pageInfo?.hasMore ? `${siteRows.length}+` : formatNumber(siteRows.length)\n }\n label=\"Monitored sites\"\n footer={\n <div className=\"space-y-2\">\n <MiniBar\n value={siteRows.length ? (activeSites / siteRows.length) * 100 : 0}\n height={4}\n barClassName=\"bg-action-primary-bg\"\n />\n <p className=\"text-xs text-text-tertiary\">\n <span className=\"tabular-nums text-text-secondary\">{activeSites}</span> active\n </p>\n </div>\n }\n />\n <MetricsCard\n icon={<PawPrint className=\"h-5 w-5\" />}\n value={\n species.data?.pageInfo?.hasMore\n ? `${speciesRows.length}+`\n : formatNumber(speciesRows.length)\n }\n label=\"Species tracked\"\n accent={threatened > 0 ? 'warning' : undefined}\n footer={\n <div className=\"space-y-2\">\n <MiniBar\n value={speciesRows.length ? (threatened / speciesRows.length) * 100 : 0}\n height={4}\n barClassName=\"bg-status-warning-text\"\n />\n <p className=\"text-xs text-text-tertiary\">\n <span className=\"tabular-nums text-text-secondary\">{priorityCount}</span> priority ·{' '}\n <span className=\"tabular-nums text-text-secondary\">{threatened}</span> threatened\n </p>\n </div>\n }\n />\n <MetricsCard\n icon={<Trees className=\"h-5 w-5\" />}\n value={formatNumber(activeProjects)}\n label=\"Active habitat projects\"\n footer={\n <div className=\"space-y-2\">\n <MiniBar\n value={projectRows.length ? (activeProjects / projectRows.length) * 100 : 0}\n height={4}\n barClassName=\"bg-action-primary-bg\"\n />\n <p className=\"text-xs text-text-tertiary\">\n <span className=\"tabular-nums text-text-secondary\">\n {formatNumber(projectRows.length)}\n </span>{' '}\n total\n </p>\n </div>\n }\n />\n <MetricsCard\n icon={<Factory className=\"h-5 w-5\" />}\n value={carbon.data ? formatCo2e(carbon.data.total) : '—'}\n label=\"Total footprint\"\n featured\n footer={\n carbon.data && carbon.data.total > 0 ? (\n <div className=\"space-y-2\">\n <ScopeSplitBar\n scope1={carbon.data.scope1}\n scope2={carbon.data.scope2}\n scope3={carbon.data.scope3}\n height={6}\n />\n <p className=\"text-xs text-text-tertiary\">Scope 1 · 2 · 3 split</p>\n </div>\n ) : undefined\n }\n />\n <MetricsCard\n icon={<Radio className=\"h-5 w-5\" />}\n value={formatNumber(onlineSensors)}\n label=\"Sensors online\"\n accent={offlineSensors > 0 ? 'warning' : 'success'}\n footer={\n <div className=\"space-y-2\">\n <MiniBar\n value={sensorRows.length ? (onlineSensors / sensorRows.length) * 100 : 0}\n height={4}\n barClassName=\"bg-status-success-text\"\n />\n <p className=\"text-xs text-text-tertiary\">\n <span className=\"tabular-nums text-text-secondary\">{offlineSensors}</span> need\n attention\n </p>\n </div>\n }\n />\n <MetricsCard\n icon={<TriangleAlert className=\"h-5 w-5\" />}\n value={\n breaches.data?.pageInfo?.hasMore\n ? `${breachRows.length}+`\n : formatNumber(breachRows.length)\n }\n label=\"Open breaches\"\n accent={breachRows.length > 0 ? 'error' : 'success'}\n footer={\n <p\n className={\n breachRows.length > 0\n ? 'text-xs font-medium text-status-error-text'\n : 'text-xs text-text-tertiary'\n }\n >\n {breachRows.length > 0 ? 'Needs attention' : 'All parameters in range'}\n </p>\n }\n />\n </div>\n\n <div className=\"mt-6 grid grid-cols-1 gap-6 lg:grid-cols-3\">\n {/* Carbon by scope */}\n <GlassCard treatment=\"glass\" className=\"p-6 lg:col-span-2\">\n <div className=\"mb-4 flex flex-wrap items-start justify-between gap-3\">\n <div>\n <h2 className=\"text-base font-semibold text-text-primary\">\n Carbon footprint by scope\n </h2>\n <p className=\"mt-0.5 text-sm text-text-secondary\">\n GHG Protocol Scope 1 / 2 / 3 emissions (CO₂e).\n </p>\n </div>\n <div className=\"flex items-center gap-4\">\n {carbon.data && carbon.data.total > 0 && (\n <div className=\"text-right\">\n <p className=\"text-lg font-semibold tabular-nums text-text-primary\">\n {formatCo2e(carbon.data.total)}\n </p>\n <p className=\"text-xs text-text-tertiary\">total</p>\n </div>\n )}\n <button\n type=\"button\"\n onClick={() => navigateTo('carbon')}\n className=\"inline-flex items-center gap-1 text-sm font-medium text-accent hover:underline\"\n >\n Ledger <ArrowRight className=\"h-4 w-4\" />\n </button>\n </div>\n </div>\n {carbonData.every((d) => d.kg === 0) ? (\n <EmptyState\n illustration=\"empty-data\"\n icon={<Factory className=\"h-10 w-10\" />}\n title=\"No carbon entries yet\"\n description=\"Record activity in the carbon ledger to see your Scope 1–3 footprint here.\"\n />\n ) : (\n <>\n <div className=\"h-56\">\n <ResponsiveContainer width=\"100%\" height=\"100%\">\n <BarChart data={carbonData} margin={{ top: 20, right: 8, left: 8, bottom: 0 }}>\n <defs>\n {carbonData.map((d) => (\n <linearGradient\n key={d.scope}\n id={`ecoScopeGrad-${d.scope}`}\n x1=\"0\"\n y1=\"0\"\n x2=\"0\"\n y2=\"1\"\n >\n <stop\n offset=\"0%\"\n style={{ stopColor: SCOPE_HUE[d.scope], stopOpacity: 1 }}\n />\n <stop\n offset=\"100%\"\n style={{ stopColor: SCOPE_HUE[d.scope], stopOpacity: 0.55 }}\n />\n </linearGradient>\n ))}\n </defs>\n <CartesianGrid\n vertical={false}\n stroke=\"var(--color-border-seam)\"\n strokeOpacity={0.6}\n strokeDasharray=\"3 4\"\n />\n <XAxis\n dataKey=\"label\"\n tick={{ fill: 'var(--color-text-secondary)', fontSize: 12 }}\n axisLine={{ stroke: 'var(--color-border-seam)' }}\n tickLine={false}\n dy={4}\n />\n <YAxis\n tick={{ fill: 'var(--color-text-tertiary)', fontSize: 11 }}\n axisLine={false}\n tickLine={false}\n width={48}\n tickFormatter={(v: number) =>\n v >= 1000 ? `${Math.round(v / 1000)}t` : `${v}`\n }\n />\n <Tooltip\n cursor={{ fill: 'var(--color-bg-sunken)', opacity: 0.4 }}\n contentStyle={{\n background: 'var(--color-bg-surface)',\n border: '1px solid var(--color-border-seam)',\n borderRadius: 12,\n color: 'var(--color-text-primary)',\n }}\n formatter={(value: number) => [formatCo2e(value), 'Emissions']}\n />\n <Bar dataKey=\"kg\" radius={[8, 8, 2, 2]} maxBarSize={72}>\n {carbonData.map((d) => (\n <Cell key={d.scope} fill={`url(#ecoScopeGrad-${d.scope})`} />\n ))}\n <LabelList\n dataKey=\"kg\"\n position=\"top\"\n formatter={(v: number) => (v > 0 ? formatCo2e(v) : '')}\n style={{\n fill: 'var(--color-text-secondary)',\n fontSize: 11,\n fontWeight: 600,\n }}\n />\n </Bar>\n </BarChart>\n </ResponsiveContainer>\n </div>\n <ScopeLegend data={carbonData} className=\"mt-4 border-t border-border-seam pt-4\" />\n </>\n )}\n </GlassCard>\n\n {/* Recent breaches */}\n <GlassCard treatment=\"glass\" className=\"p-6\">\n <div className=\"mb-4 flex items-center justify-between gap-3\">\n <div className=\"flex items-center gap-2\">\n <h2 className=\"text-base font-semibold text-text-primary\">Recent breaches</h2>\n {breachRows.length > 0 && (\n <span className=\"rounded-full bg-status-error-bg-subtle px-2 py-0.5 text-xs font-medium tabular-nums text-status-error-text\">\n {breaches.data?.pageInfo?.hasMore ? `${breachRows.length}+` : breachRows.length}\n </span>\n )}\n </div>\n <button\n type=\"button\"\n onClick={() => navigateTo('monitoring')}\n className=\"inline-flex items-center gap-1 text-sm font-medium text-accent hover:underline\"\n >\n Monitoring <ArrowRight className=\"h-4 w-4\" />\n </button>\n </div>\n {breachRows.length === 0 ? (\n <div className=\"flex flex-col items-center justify-center rounded-xl border border-dashed border-border-seam py-10 text-center\">\n <span className=\"flex size-12 items-center justify-center rounded-full bg-status-success-bg-subtle\">\n <ShieldCheck className=\"h-6 w-6 text-status-success-text\" />\n </span>\n <p className=\"mt-3 text-sm font-medium text-text-primary\">All parameters in range</p>\n <p className=\"mt-1 text-xs text-text-tertiary\">No open threshold breaches.</p>\n </div>\n ) : (\n <ul className=\"space-y-2.5\">\n {breachRows.map((m) => (\n <li\n key={m.id}\n className=\"flex items-start justify-between gap-3 rounded-xl bg-bg-sunken/50 p-3\"\n >\n <div className=\"flex min-w-0 items-start gap-2.5\">\n <span\n aria-hidden=\"true\"\n className=\"mt-1.5 size-2 shrink-0 rounded-full bg-status-error-text motion-safe:animate-vc-glow-pulse\"\n />\n <div className=\"min-w-0\">\n <p className=\"truncate text-sm font-medium text-text-primary\">\n {m.parameter}\n </p>\n <p className=\"text-xs text-text-tertiary\">\n <span className=\"tabular-nums\">\n {formatNumber(m.value)} {m.unit}\n </span>{' '}\n · {formatRelativeTime(m.recordedAt)}\n </p>\n </div>\n </div>\n <StatusBadge status=\"BREACH\" />\n </li>\n ))}\n </ul>\n )}\n </GlassCard>\n </div>\n\n {/* Recent observations */}\n <Section\n title=\"Recent field observations\"\n description=\"Latest wildlife & biodiversity sightings recorded across your sites.\"\n action={\n <button\n type=\"button\"\n onClick={() => navigateTo('observations')}\n className=\"inline-flex items-center gap-1 text-sm font-medium text-accent hover:underline\"\n >\n All observations <ArrowRight className=\"h-4 w-4\" />\n </button>\n }\n >\n {observationRows.length === 0 ? (\n <EmptyState\n illustration=\"empty-data\"\n icon={<Binoculars className=\"h-10 w-10\" />}\n title=\"No observations yet\"\n description=\"Record your first field observation to build your biodiversity dataset.\"\n />\n ) : (\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3\">\n {observationRows.map((o) => (\n <GlassCard\n key={o.id}\n treatment=\"plain\"\n className=\"p-4 transition-shadow hover:shadow-float\"\n >\n <div className=\"flex items-start justify-between gap-2\">\n <div className=\"flex min-w-0 items-start gap-2.5\">\n <span className=\"mt-0.5 flex size-8 shrink-0 items-center justify-center rounded-lg bg-[var(--color-accent-soft)] text-accent\">\n <Binoculars className=\"h-4 w-4\" />\n </span>\n <div className=\"min-w-0\">\n <p className=\"truncate text-sm font-semibold text-text-primary\">\n {o.species?.commonName ?? 'Unknown species'}\n </p>\n {o.species?.scientificName && (\n <p className=\"truncate text-xs italic text-text-tertiary\">\n {o.species.scientificName}\n </p>\n )}\n </div>\n </div>\n <span className=\"shrink-0 rounded-full bg-[var(--color-accent-soft)] px-2 py-0.5 text-xs font-medium tabular-nums text-text-primary\">\n ×{formatNumber(o.count)}\n </span>\n </div>\n <div className=\"mt-3 flex items-center justify-between border-t border-border-seam/70 pt-2.5\">\n <span className=\"rounded-full bg-bg-sunken px-2 py-0.5 text-xs text-text-secondary\">\n {formatEnumLabel(o.method)}\n </span>\n <span className=\"text-xs text-text-tertiary\">\n {formatRelativeTime(o.observedAt)}\n </span>\n </div>\n </GlassCard>\n ))}\n </div>\n )}\n </Section>\n\n {/* Conservation status snapshot */}\n {speciesRows.length > 0 && (\n <Section\n title=\"Conservation status\"\n description=\"Distribution of tracked species by IUCN-style status.\"\n >\n <GlassCard treatment=\"plain\" className=\"p-6\">\n <div className=\"space-y-3.5\">\n {(() => {\n const counts = new Map<string, number>();\n for (const s of speciesRows) {\n counts.set(s.conservationStatus, (counts.get(s.conservationStatus) ?? 0) + 1);\n }\n const total = speciesRows.length;\n return [...counts.entries()]\n .sort((a, b) => b[1] - a[1])\n .map(([status, count]) => {\n const pct = (count / total) * 100;\n const tone =\n CONSERVATION_TONE[status as EcoConservationStatus] ??\n 'var(--color-text-tertiary)';\n return (\n <div key={status} className=\"flex items-center gap-3 sm:gap-4\">\n <div className=\"w-36 shrink-0 sm:w-48\">\n <StatusBadge status={status} />\n </div>\n <Meter value={pct} color={tone} className=\"flex-1\" />\n <span className=\"w-9 shrink-0 text-right text-sm font-medium tabular-nums text-text-primary\">\n {count}\n </span>\n <span className=\"hidden w-12 shrink-0 text-right text-xs tabular-nums text-text-tertiary sm:inline\">\n {formatPercent(pct)}\n </span>\n </div>\n );\n });\n })()}\n </div>\n </GlassCard>\n </Section>\n )}\n </PageLayout>\n );\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"ConservationBadge.js","names":[],"sources":["../../../../src/ecoimpacthub/pages/_shared/ConservationBadge.tsx"],"sourcesContent":["/**\n * ConservationBadge — a rich IUCN conservation-status indicator.\n * @module ecoimpacthub/pages/_shared\n *\n * Wraps the shared {@link StatusBadge} (which already maps every IUCN status to\n * the correct semantic tone — Endangered/Critically Endangered = destructive,\n * Vulnerable = warning, Near Threatened = info, Least Concern = success, Data\n * Deficient/Not Evaluated = neutral) and prefixes a tone-matched threat-tier\n * icon so the badge reads at a glance in a dense catalog. Tokens only.\n */\n\nimport type { FC } from 'react';\nimport {\n CircleHelp,\n Eye,\n ShieldAlert,\n ShieldCheck,\n ShieldOff,\n TriangleAlert,\n type LucideIcon,\n} from 'lucide-react';\nimport { StatusBadge } from '../../components/StatusBadge';\nimport { cn } from '../../utils/cn';\n\ninterface Tier {\n Icon: LucideIcon;\n iconClass: string;\n}\n\nconst TIER: Record<string, Tier> = {\n CRITICALLY_ENDANGERED: { Icon: ShieldAlert, iconClass: 'text-status-error-text' },\n ENDANGERED: { Icon: ShieldAlert, iconClass: 'text-status-error-text' },\n VULNERABLE: { Icon: TriangleAlert, iconClass: 'text-status-warning-text' },\n NEAR_THREATENED: { Icon: Eye, iconClass: 'text-accent' },\n LEAST_CONCERN: { Icon: ShieldCheck, iconClass: 'text-status-success-text' },\n EXTINCT_IN_WILD: { Icon: ShieldOff, iconClass: 'text-text-secondary' },\n DATA_DEFICIENT: { Icon: CircleHelp, iconClass: 'text-text-tertiary' },\n NOT_EVALUATED: { Icon: CircleHelp, iconClass: 'text-text-tertiary' },\n};\n\nconst FALLBACK: Tier = { Icon: CircleHelp, iconClass: 'text-text-tertiary' };\n\ninterface ConservationBadgeProps {\n status: string;\n className?: string;\n}\n\nexport const ConservationBadge: FC<ConservationBadgeProps> = ({ status, className }) => {\n const tier = TIER[status.toUpperCase()] ?? FALLBACK;\n const { Icon, iconClass } = tier;\n return (\n <span className={cn('inline-flex items-center gap-1.5', className)}>\n <Icon className={cn('h-4 w-4 shrink-0', iconClass)} aria-hidden=\"true\" />\n <StatusBadge status={status} className=\"font-semibold\" />\n </span>\n );\n};\n"],"mappings":";;;;;AA6BA,IAAM,IAA6B;CACjC,uBAAuB;EAAE,MAAM;EAAa,WAAW;EAA0B;CACjF,YAAY;EAAE,MAAM;EAAa,WAAW;EAA0B;CACtE,YAAY;EAAE,MAAM;EAAe,WAAW;EAA4B;CAC1E,iBAAiB;EAAE,MAAM;EAAK,WAAW;EAAe;CACxD,eAAe;EAAE,MAAM;EAAa,WAAW;EAA4B;CAC3E,iBAAiB;EAAE,MAAM;EAAW,WAAW;EAAuB;CACtE,gBAAgB;EAAE,MAAM;EAAY,WAAW;EAAsB;CACrE,eAAe;EAAE,MAAM;EAAY,WAAW;EAAsB;CACrE,EAEK,IAAiB;CAAE,MAAM;CAAY,WAAW;CAAsB,EAO/D,KAAiD,EAAE,WAAQ,mBAAgB;CAEtF,IAAM,EAAE,SAAM,iBADD,EAAK,EAAO,aAAa,KAAK;AAE3C,QACE,kBAAC,QAAD;EAAM,WAAW,EAAG,oCAAoC,EAAU;YAAlE,CACE,kBAAC,GAAD;GAAM,WAAW,EAAG,oBAAoB,EAAU;GAAE,eAAY;GAAS,CAAA,EACzE,kBAAC,GAAD;GAAqB;GAAQ,WAAU;GAAkB,CAAA,CACpD"}
1
+ {"version":3,"file":"ConservationBadge.js","names":[],"sources":["../../../../src/ecoimpacthub/pages/_shared/ConservationBadge.tsx"],"sourcesContent":["/**\n * ConservationBadge — a rich IUCN conservation-status indicator.\n * @module ecoimpacthub/pages/_shared\n *\n * Wraps the shared {@link StatusBadge} (which already maps every IUCN status to\n * the correct semantic tone — Endangered/Critically Endangered = destructive,\n * Vulnerable = warning, Near Threatened = info, Least Concern = success, Data\n * Deficient/Not Evaluated = neutral) and prefixes a tone-matched threat-tier\n * icon so the badge reads at a glance in a dense catalog. Tokens only.\n */\n\nimport type { FC } from 'react';\nimport {\n CircleHelp,\n Eye,\n ShieldAlert,\n ShieldCheck,\n ShieldOff,\n TriangleAlert,\n type LucideIcon,\n} from 'lucide-react';\nimport { StatusBadge } from '../../components/StatusBadge';\nimport { cn } from '../../utils/cn';\n\ninterface Tier {\n Icon: LucideIcon;\n iconClass: string;\n}\n\nconst TIER: Record<string, Tier> = {\n CRITICALLY_ENDANGERED: { Icon: ShieldAlert, iconClass: 'text-status-error-text' },\n ENDANGERED: { Icon: ShieldAlert, iconClass: 'text-status-error-text' },\n VULNERABLE: { Icon: TriangleAlert, iconClass: 'text-status-warning-text' },\n NEAR_THREATENED: { Icon: Eye, iconClass: 'text-accent' },\n LEAST_CONCERN: { Icon: ShieldCheck, iconClass: 'text-status-success-text' },\n EXTINCT_IN_WILD: { Icon: ShieldOff, iconClass: 'text-text-secondary' },\n DATA_DEFICIENT: { Icon: CircleHelp, iconClass: 'text-text-tertiary' },\n NOT_EVALUATED: { Icon: CircleHelp, iconClass: 'text-text-tertiary' },\n};\n\nconst FALLBACK: Tier = { Icon: CircleHelp, iconClass: 'text-text-tertiary' };\n\ninterface ConservationBadgeProps {\n status: string;\n className?: string;\n}\n\nexport const ConservationBadge: FC<ConservationBadgeProps> = ({ status, className }) => {\n const tier = TIER[status.toUpperCase()] ?? FALLBACK;\n const { Icon, iconClass } = tier;\n return (\n <span className={cn('inline-flex items-center gap-1.5', className)}>\n <Icon className={cn('h-4 w-4 shrink-0', iconClass)} aria-hidden=\"true\" />\n <StatusBadge status={status} className=\"font-semibold\" />\n </span>\n );\n};\n"],"mappings":";;;;;AA6BA,IAAM,IAA6B;CACjC,uBAAuB;EAAE,MAAM;EAAa,WAAW;CAAyB;CAChF,YAAY;EAAE,MAAM;EAAa,WAAW;CAAyB;CACrE,YAAY;EAAE,MAAM;EAAe,WAAW;CAA2B;CACzE,iBAAiB;EAAE,MAAM;EAAK,WAAW;CAAc;CACvD,eAAe;EAAE,MAAM;EAAa,WAAW;CAA2B;CAC1E,iBAAiB;EAAE,MAAM;EAAW,WAAW;CAAsB;CACrE,gBAAgB;EAAE,MAAM;EAAY,WAAW;CAAqB;CACpE,eAAe;EAAE,MAAM;EAAY,WAAW;CAAqB;AACrE,GAEM,IAAiB;CAAE,MAAM;CAAY,WAAW;AAAqB,GAO9D,KAAiD,EAAE,WAAQ,mBAAgB;CAEtF,IAAM,EAAE,SAAM,iBADD,EAAK,EAAO,YAAY,MAAM;CAE3C,OACE,kBAAC,QAAD;EAAM,WAAW,EAAG,oCAAoC,CAAS;YAAjE,CACE,kBAAC,GAAD;GAAM,WAAW,EAAG,oBAAoB,CAAS;GAAG,eAAY;EAAQ,CAAA,GACxE,kBAAC,GAAD;GAAqB;GAAQ,WAAU;EAAiB,CAAA,CACpD;;AAEV"}
@@ -1 +1 @@
1
- {"version":3,"file":"EvidenceThumb.js","names":[],"sources":["../../../../src/ecoimpacthub/pages/_shared/EvidenceThumb.tsx"],"sourcesContent":["/**\n * EvidenceThumb — an inline evidence-photo thumbnail for a field observation.\n * @module ecoimpacthub/pages/_shared\n *\n * Observation queries only select `evidence { id }`, so this resolves the\n * platform File via `useEcoFile(id)` (wspace-files-svc `getFile`, Integration A)\n * to obtain a signed `downloadUrl` + `mimeType`, then renders:\n * - an actual <img> thumbnail (linked to the full asset) for image evidence,\n * - an audio / document glyph tile (linked) for non-image evidence,\n * - and graceful, tone-muted fallback tiles for: no evidence, still loading,\n * unresolvable file, or a broken image URL.\n *\n * The `useEcoFile` query is disabled when there is no `fileId`, so only rows\n * that actually carry evidence trigger a fetch (and React Query dedupes by id).\n */\n\nimport { useState } from 'react';\nimport { AudioLines, FileText, ImageOff, Loader2 } from 'lucide-react';\nimport { useEcoFile } from '../../hooks';\nimport { cn } from '../../utils/cn';\n\nconst TILE =\n 'flex h-11 w-11 shrink-0 items-center justify-center overflow-hidden rounded-lg border border-border-seam bg-bg-sunken';\n\ninterface EvidenceThumbProps {\n fileId?: string | null;\n /** Descriptive alt text (e.g. the species name) for the rendered image. */\n alt?: string;\n}\n\nexport function EvidenceThumb({ fileId, alt }: EvidenceThumbProps) {\n const { data, isLoading, isError } = useEcoFile(fileId);\n const [imgBroken, setImgBroken] = useState(false);\n\n // No evidence attached to this observation.\n if (!fileId) {\n return (\n <span className={cn(TILE, 'text-text-tertiary')} aria-label=\"No evidence\" title=\"No evidence\">\n <ImageOff className=\"h-4 w-4\" aria-hidden=\"true\" />\n </span>\n );\n }\n\n // Resolving the file record.\n if (isLoading) {\n return (\n <span className={cn(TILE, 'text-text-tertiary')} aria-label=\"Loading evidence\">\n <Loader2 className=\"h-4 w-4 animate-spin\" aria-hidden=\"true\" />\n </span>\n );\n }\n\n // File record missing / no usable URL.\n if (isError || !data || !data.downloadUrl) {\n return (\n <span\n className={cn(TILE, 'text-text-tertiary')}\n aria-label=\"Evidence unavailable\"\n title={data?.name ?? 'Evidence unavailable'}\n >\n <FileText className=\"h-4 w-4\" aria-hidden=\"true\" />\n </span>\n );\n }\n\n const mimeType = data.mimeType ?? '';\n const isImage = mimeType.startsWith('image/');\n const isAudio = mimeType.startsWith('audio/');\n\n // Image evidence → real thumbnail, linked to the full asset.\n if (isImage && !imgBroken) {\n return (\n <a\n href={data.downloadUrl}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n onClick={(e) => e.stopPropagation()}\n className={cn(\n TILE,\n 'p-0 transition-shadow hover:ring-2 hover:ring-accent focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-accent'\n )}\n title={data.name}\n aria-label={`Open evidence photo${alt ? `: ${alt}` : ''}`}\n >\n <img\n src={data.downloadUrl}\n alt={alt ?? data.name}\n loading=\"lazy\"\n onError={() => setImgBroken(true)}\n className=\"h-full w-full object-cover\"\n />\n </a>\n );\n }\n\n // Non-image (or broken image) evidence → a linked glyph tile.\n return (\n <a\n href={data.downloadUrl}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n onClick={(e) => e.stopPropagation()}\n className={cn(\n TILE,\n 'text-text-secondary transition-colors hover:border-accent hover:text-accent focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-accent'\n )}\n title={data.name}\n aria-label={`Open evidence: ${data.name}`}\n >\n {isAudio ? (\n <AudioLines className=\"h-4 w-4\" aria-hidden=\"true\" />\n ) : (\n <FileText className=\"h-4 w-4\" aria-hidden=\"true\" />\n )}\n </a>\n );\n}\n"],"mappings":";;;;;;;AAqBA,IAAM,IACJ;AAQF,SAAgB,EAAc,EAAE,WAAQ,UAA2B;CACjE,IAAM,EAAE,SAAM,cAAW,eAAY,EAAW,EAAO,EACjD,CAAC,GAAW,KAAgB,EAAS,GAAM;AAGjD,KAAI,CAAC,EACH,QACE,kBAAC,QAAD;EAAM,WAAW,EAAG,GAAM,qBAAqB;EAAE,cAAW;EAAc,OAAM;YAC9E,kBAAC,GAAD;GAAU,WAAU;GAAU,eAAY;GAAS,CAAA;EAC9C,CAAA;AAKX,KAAI,EACF,QACE,kBAAC,QAAD;EAAM,WAAW,EAAG,GAAM,qBAAqB;EAAE,cAAW;YAC1D,kBAAC,GAAD;GAAS,WAAU;GAAuB,eAAY;GAAS,CAAA;EAC1D,CAAA;AAKX,KAAI,KAAW,CAAC,KAAQ,CAAC,EAAK,YAC5B,QACE,kBAAC,QAAD;EACE,WAAW,EAAG,GAAM,qBAAqB;EACzC,cAAW;EACX,OAAO,GAAM,QAAQ;YAErB,kBAAC,GAAD;GAAU,WAAU;GAAU,eAAY;GAAS,CAAA;EAC9C,CAAA;CAIX,IAAM,IAAW,EAAK,YAAY,IAC5B,IAAU,EAAS,WAAW,SAAS,EACvC,IAAU,EAAS,WAAW,SAAS;AA6B7C,QA1BI,KAAW,CAAC,IAEZ,kBAAC,KAAD;EACE,MAAM,EAAK;EACX,QAAO;EACP,KAAI;EACJ,UAAU,MAAM,EAAE,iBAAiB;EACnC,WAAW,EACT,GACA,iIACD;EACD,OAAO,EAAK;EACZ,cAAY,sBAAsB,IAAM,KAAK,MAAQ;YAErD,kBAAC,OAAD;GACE,KAAK,EAAK;GACV,KAAK,KAAO,EAAK;GACjB,SAAQ;GACR,eAAe,EAAa,GAAK;GACjC,WAAU;GACV,CAAA;EACA,CAAA,GAMN,kBAAC,KAAD;EACE,MAAM,EAAK;EACX,QAAO;EACP,KAAI;EACJ,UAAU,MAAM,EAAE,iBAAiB;EACnC,WAAW,EACT,GACA,wJACD;EACD,OAAO,EAAK;EACZ,cAAY,kBAAkB,EAAK;YAGjC,EADD,IACE,IAEA,GAFD;GAAY,WAAU;GAAU,eAAY;GAAS,CAEF;EAEnD,CAAA"}
1
+ {"version":3,"file":"EvidenceThumb.js","names":[],"sources":["../../../../src/ecoimpacthub/pages/_shared/EvidenceThumb.tsx"],"sourcesContent":["/**\n * EvidenceThumb — an inline evidence-photo thumbnail for a field observation.\n * @module ecoimpacthub/pages/_shared\n *\n * Observation queries only select `evidence { id }`, so this resolves the\n * platform File via `useEcoFile(id)` (wspace-files-svc `getFile`, Integration A)\n * to obtain a signed `downloadUrl` + `mimeType`, then renders:\n * - an actual <img> thumbnail (linked to the full asset) for image evidence,\n * - an audio / document glyph tile (linked) for non-image evidence,\n * - and graceful, tone-muted fallback tiles for: no evidence, still loading,\n * unresolvable file, or a broken image URL.\n *\n * The `useEcoFile` query is disabled when there is no `fileId`, so only rows\n * that actually carry evidence trigger a fetch (and React Query dedupes by id).\n */\n\nimport { useState } from 'react';\nimport { AudioLines, FileText, ImageOff, Loader2 } from 'lucide-react';\nimport { useEcoFile } from '../../hooks';\nimport { cn } from '../../utils/cn';\n\nconst TILE =\n 'flex h-11 w-11 shrink-0 items-center justify-center overflow-hidden rounded-lg border border-border-seam bg-bg-sunken';\n\ninterface EvidenceThumbProps {\n fileId?: string | null;\n /** Descriptive alt text (e.g. the species name) for the rendered image. */\n alt?: string;\n}\n\nexport function EvidenceThumb({ fileId, alt }: EvidenceThumbProps) {\n const { data, isLoading, isError } = useEcoFile(fileId);\n const [imgBroken, setImgBroken] = useState(false);\n\n // No evidence attached to this observation.\n if (!fileId) {\n return (\n <span className={cn(TILE, 'text-text-tertiary')} aria-label=\"No evidence\" title=\"No evidence\">\n <ImageOff className=\"h-4 w-4\" aria-hidden=\"true\" />\n </span>\n );\n }\n\n // Resolving the file record.\n if (isLoading) {\n return (\n <span className={cn(TILE, 'text-text-tertiary')} aria-label=\"Loading evidence\">\n <Loader2 className=\"h-4 w-4 animate-spin\" aria-hidden=\"true\" />\n </span>\n );\n }\n\n // File record missing / no usable URL.\n if (isError || !data || !data.downloadUrl) {\n return (\n <span\n className={cn(TILE, 'text-text-tertiary')}\n aria-label=\"Evidence unavailable\"\n title={data?.name ?? 'Evidence unavailable'}\n >\n <FileText className=\"h-4 w-4\" aria-hidden=\"true\" />\n </span>\n );\n }\n\n const mimeType = data.mimeType ?? '';\n const isImage = mimeType.startsWith('image/');\n const isAudio = mimeType.startsWith('audio/');\n\n // Image evidence → real thumbnail, linked to the full asset.\n if (isImage && !imgBroken) {\n return (\n <a\n href={data.downloadUrl}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n onClick={(e) => e.stopPropagation()}\n className={cn(\n TILE,\n 'p-0 transition-shadow hover:ring-2 hover:ring-accent focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-accent'\n )}\n title={data.name}\n aria-label={`Open evidence photo${alt ? `: ${alt}` : ''}`}\n >\n <img\n src={data.downloadUrl}\n alt={alt ?? data.name}\n loading=\"lazy\"\n onError={() => setImgBroken(true)}\n className=\"h-full w-full object-cover\"\n />\n </a>\n );\n }\n\n // Non-image (or broken image) evidence → a linked glyph tile.\n return (\n <a\n href={data.downloadUrl}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n onClick={(e) => e.stopPropagation()}\n className={cn(\n TILE,\n 'text-text-secondary transition-colors hover:border-accent hover:text-accent focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-accent'\n )}\n title={data.name}\n aria-label={`Open evidence: ${data.name}`}\n >\n {isAudio ? (\n <AudioLines className=\"h-4 w-4\" aria-hidden=\"true\" />\n ) : (\n <FileText className=\"h-4 w-4\" aria-hidden=\"true\" />\n )}\n </a>\n );\n}\n"],"mappings":";;;;;;;AAqBA,IAAM,IACJ;AAQF,SAAgB,EAAc,EAAE,WAAQ,UAA2B;CACjE,IAAM,EAAE,SAAM,cAAW,eAAY,EAAW,CAAM,GAChD,CAAC,GAAW,KAAgB,EAAS,EAAK;CAGhD,IAAI,CAAC,GACH,OACE,kBAAC,QAAD;EAAM,WAAW,EAAG,GAAM,oBAAoB;EAAG,cAAW;EAAc,OAAM;YAC9E,kBAAC,GAAD;GAAU,WAAU;GAAU,eAAY;EAAQ,CAAA;CAC9C,CAAA;CAKV,IAAI,GACF,OACE,kBAAC,QAAD;EAAM,WAAW,EAAG,GAAM,oBAAoB;EAAG,cAAW;YAC1D,kBAAC,GAAD;GAAS,WAAU;GAAuB,eAAY;EAAQ,CAAA;CAC1D,CAAA;CAKV,IAAI,KAAW,CAAC,KAAQ,CAAC,EAAK,aAC5B,OACE,kBAAC,QAAD;EACE,WAAW,EAAG,GAAM,oBAAoB;EACxC,cAAW;EACX,OAAO,GAAM,QAAQ;YAErB,kBAAC,GAAD;GAAU,WAAU;GAAU,eAAY;EAAQ,CAAA;CAC9C,CAAA;CAIV,IAAM,IAAW,EAAK,YAAY,IAC5B,IAAU,EAAS,WAAW,QAAQ,GACtC,IAAU,EAAS,WAAW,QAAQ;CA6B5C,OA1BI,KAAW,CAAC,IAEZ,kBAAC,KAAD;EACE,MAAM,EAAK;EACX,QAAO;EACP,KAAI;EACJ,UAAU,MAAM,EAAE,gBAAgB;EAClC,WAAW,EACT,GACA,gIACF;EACA,OAAO,EAAK;EACZ,cAAY,sBAAsB,IAAM,KAAK,MAAQ;YAErD,kBAAC,OAAD;GACE,KAAK,EAAK;GACV,KAAK,KAAO,EAAK;GACjB,SAAQ;GACR,eAAe,EAAa,EAAI;GAChC,WAAU;EACX,CAAA;CACA,CAAA,IAML,kBAAC,KAAD;EACE,MAAM,EAAK;EACX,QAAO;EACP,KAAI;EACJ,UAAU,MAAM,EAAE,gBAAgB;EAClC,WAAW,EACT,GACA,uJACF;EACA,OAAO,EAAK;EACZ,cAAY,kBAAkB,EAAK;YAGjC,EADD,IACE,IAEA,GAFD;GAAY,WAAU;GAAU,eAAY;EAAQ,CAEF;CAEnD,CAAA;AAEP"}