stats_view.js 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235
  1. const { div, h2, p, section, button, form, input, ul, li, a, h3, span, strong, table, tr, td, th } = require("../server/node_modules/hyperaxe");
  2. const { template, i18n } = require('./main_views');
  3. const C = (stats, t) => Number((stats && stats.content && stats.content[t]) || 0);
  4. const O = (stats, t) => Number((stats && stats.opinions && stats.opinions[t]) || 0);
  5. exports.statsView = (stats, filter) => {
  6. const title = i18n.statsTitle;
  7. const description = i18n.statsDescription;
  8. const modes = ['ALL', 'MINE', 'TOMBSTONE'];
  9. const types = [
  10. 'bookmark', 'event', 'task', 'votes', 'report', 'feed', 'project',
  11. 'image', 'audio', 'video', 'document', 'transfer', 'post', 'tribe',
  12. 'market', 'forum', 'job', 'aiExchange', 'karmaScore'
  13. ];
  14. const totalContent = types.reduce((sum, t) => sum + C(stats, t), 0);
  15. const totalOpinions = types.reduce((sum, t) => sum + O(stats, t), 0);
  16. const blockStyle = 'padding:16px;border:1px solid #ddd;border-radius:8px;margin-bottom:24px;';
  17. const headerStyle = 'background-color:#f8f9fa; padding:24px; border-radius:8px; border:1px solid #e0e0e0; box-shadow:0 2px 8px rgba(0,0,0,0.1);';
  18. return template(
  19. title,
  20. section(
  21. div({ class: 'tags-header' },
  22. h2(title),
  23. p(description)
  24. ),
  25. div({ class: 'mode-buttons', style: 'display:grid;grid-template-columns:repeat(auto-fit,minmax(120px,1fr));gap:16px;margin-bottom:24px;' },
  26. modes.map(m =>
  27. form({ method: 'GET', action: '/stats' },
  28. input({ type: 'hidden', name: 'filter', value: m }),
  29. button({ type: 'submit', class: filter === m ? 'filter-btn active' : 'filter-btn' }, i18n[m + 'Button'])
  30. )
  31. )
  32. ),
  33. section(
  34. div({ style: headerStyle },
  35. h3({ style: 'font-size:18px; color:#555; margin:8px 0;' }, `${i18n.statsCreatedAt}: `, span({ style: 'color:#888;' }, stats.createdAt)),
  36. h3({ style: 'font-size:18px; color:#555; margin:8px 0; font-weight:600;' },
  37. a({ class: "user-link", href: `/author/${encodeURIComponent(stats.id)}`, style: 'color:#007bff; text-decoration:none;' }, stats.id)
  38. ),
  39. div({ style: 'margin-bottom:16px;' },
  40. ul({ style: 'list-style-type:none; padding:0; margin:0;' },
  41. li({ style: 'font-size:18px; color:#555; margin:8px 0;' },
  42. `${i18n.statsBlobsSize}: `,
  43. span({ style: 'color:#888;' }, stats.statsBlobsSize)
  44. ),
  45. li({ style: 'font-size:18px; color:#555; margin:8px 0;' },
  46. `${i18n.statsBlockchainSize}: `,
  47. span({ style: 'color:#888;' }, stats.statsBlockchainSize)
  48. ),
  49. li({ style: 'font-size:18px; color:#555; margin:8px 0;' },
  50. strong(`${i18n.statsSize}: `, span({ style: 'color:#888;' }, span({ style: 'color:#555;' }, stats.folderSize)))
  51. )
  52. )
  53. )
  54. ),
  55. div({ style: headerStyle },
  56. h3({ style: 'font-size:18px; color:#555; margin:8px 0; font-weight:600;' }, i18n.statsBankingTitle),
  57. ul({ style: 'list-style-type:none; padding:0; margin:0;' },
  58. li({ style: 'font-size:18px; color:#555; margin:8px 0;' },
  59. `${i18n.statsEcoWalletLabel}: `,
  60. a(
  61. {
  62. href: '/wallet',
  63. style: 'color:#007bff; text-decoration:none; word-break:break-all;'
  64. },
  65. stats?.banking?.myAddress || i18n.statsEcoWalletNotConfigured
  66. )
  67. ),
  68. li({ style: 'font-size:18px; color:#555; margin:8px 0;' },
  69. `${i18n.statsTotalEcoAddresses}: `,
  70. span({ style: 'color:#888;' }, String(stats?.banking?.totalAddresses || 0))
  71. )
  72. )
  73. ),
  74. div({ style: headerStyle },
  75. h3({ style: 'font-size:18px; color:#555; margin:8px 0; font-weight:600;' }, i18n.statsAITraining),
  76. ul({ style: 'list-style-type:none; padding:0; margin:0;' },
  77. li({ style: 'font-size:18px; color:#555; margin:8px 0;' },
  78. `${i18n.statsAIExchanges}: `,
  79. span({ style: 'color:#888;' }, String(C(stats, 'aiExchange') || 0))
  80. )
  81. )
  82. ),
  83. filter === 'ALL'
  84. ? div({ class: 'stats-container' }, [
  85. div({ style: blockStyle }, h2(`${i18n.bankingUserEngagementScore}: ${C(stats, 'karmaScore')}`)),
  86. div({ style: blockStyle },
  87. h2(i18n.statsActivity7d),
  88. table({ style: 'width:100%; border-collapse: collapse;' },
  89. tr(th(i18n.day), th(i18n.messages)),
  90. ...(Array.isArray(stats.activity?.daily7) ? stats.activity.daily7 : []).map(row =>
  91. tr(td(row.day), td(String(row.count)))
  92. )
  93. ),
  94. p(`${i18n.statsActivity7dTotal}: ${stats.activity?.daily7Total || 0}`),
  95. p(`${i18n.statsActivity30dTotal}: ${stats.activity?.daily30Total || 0}`)
  96. ),
  97. div({ style: blockStyle },
  98. h2(`${i18n.statsDiscoveredTribes}: ${stats.memberTribes.length}`),
  99. table({ style: 'width:100%; border-collapse: collapse; margin-top: 8px;' },
  100. tr(th(i18n.typeTribe || 'Tribe')),
  101. ...stats.memberTribes.map(name => tr(td(name)))
  102. )
  103. ),
  104. div({ style: blockStyle }, h2(`${i18n.statsUsersTitle}: ${stats.usersKPIs?.totalInhabitants || stats.inhabitants || 0}`)),
  105. div({ style: blockStyle }, h2(`${i18n.statsDiscoveredForum}: ${C(stats, 'forum')}`)),
  106. div({ style: blockStyle }, h2(`${i18n.statsDiscoveredTransfer}: ${C(stats, 'transfer')}`)),
  107. div({ style: blockStyle },
  108. h2(i18n.statsMarketTitle),
  109. ul([
  110. li(`${i18n.statsMarketTotal}: ${stats.marketKPIs?.total || 0}`),
  111. li(`${i18n.statsMarketForSale}: ${stats.marketKPIs?.forSale || 0}`),
  112. li(`${i18n.statsMarketReserved}: ${stats.marketKPIs?.reserved || 0}`),
  113. li(`${i18n.statsMarketClosed}: ${stats.marketKPIs?.closed || 0}`),
  114. li(`${i18n.statsMarketSold}: ${stats.marketKPIs?.sold || 0}`),
  115. li(`${i18n.statsMarketRevenue}: ${((stats.marketKPIs?.revenueECO || 0)).toFixed(6)} ECO`),
  116. li(`${i18n.statsMarketAvgSoldPrice}: ${((stats.marketKPIs?.avgSoldPrice || 0)).toFixed(6)} ECO`)
  117. ])
  118. ),
  119. div({ style: blockStyle },
  120. h2(i18n.statsProjectsTitle),
  121. ul([
  122. li(`${i18n.statsProjectsTotal}: ${stats.projectsKPIs?.total || 0}`),
  123. li(`${i18n.statsProjectsActive}: ${stats.projectsKPIs?.active || 0}`),
  124. li(`${i18n.statsProjectsCompleted}: ${stats.projectsKPIs?.completed || 0}`),
  125. li(`${i18n.statsProjectsPaused}: ${stats.projectsKPIs?.paused || 0}`),
  126. li(`${i18n.statsProjectsCancelled}: ${stats.projectsKPIs?.cancelled || 0}`),
  127. li(`${i18n.statsProjectsGoalTotal}: ${(stats.projectsKPIs?.ecoGoalTotal || 0)} ECO`),
  128. li(`${i18n.statsProjectsPledgedTotal}: ${(stats.projectsKPIs?.ecoPledgedTotal || 0)} ECO`),
  129. li(`${i18n.statsProjectsSuccessRate}: ${((stats.projectsKPIs?.successRate || 0)).toFixed(1)}%`),
  130. li(`${i18n.statsProjectsAvgProgress}: ${((stats.projectsKPIs?.avgProgress || 0)).toFixed(1)}%`),
  131. li(`${i18n.statsProjectsMedianProgress}: ${((stats.projectsKPIs?.medianProgress || 0)).toFixed(1)}%`),
  132. li(`${i18n.statsProjectsActiveFundingAvg}: ${((stats.projectsKPIs?.activeFundingAvg || 0)).toFixed(1)}%`)
  133. ])
  134. ),
  135. div({ style: blockStyle },
  136. h2(i18n.statsJobsTitle),
  137. ul([
  138. li(`${i18n.statsJobsTotal}: ${stats.jobsKPIs?.total || 0}`),
  139. li(`${i18n.statsJobsOpen}: ${stats.jobsKPIs?.open || 0}`),
  140. li(`${i18n.statsJobsClosed}: ${stats.jobsKPIs?.closed || 0}`),
  141. li(`${i18n.statsJobsOpenVacants}: ${stats.jobsKPIs?.openVacants || 0}`),
  142. li(`${i18n.statsJobsSubscribersTotal}: ${stats.jobsKPIs?.subscribersTotal || 0}`),
  143. li(`${i18n.statsJobsAvgSalary}: ${((stats.jobsKPIs?.avgSalary || 0)).toFixed(2)} ECO`),
  144. li(`${i18n.statsJobsMedianSalary}: ${((stats.jobsKPIs?.medianSalary || 0)).toFixed(2)} ECO`)
  145. ])
  146. ),
  147. div({ style: blockStyle },
  148. h2(`${i18n.statsNetworkOpinions}: ${totalOpinions}`),
  149. ul(types.map(t => O(stats, t) > 0 ? li(`${i18n[`stats${t.charAt(0).toUpperCase() + t.slice(1)}`]}: ${O(stats, t)}`) : null).filter(Boolean))
  150. ),
  151. div({ style: blockStyle },
  152. h2(`${i18n.statsNetworkContent}: ${totalContent}`),
  153. ul(types.map(t => C(stats, t) > 0 ? li(`${i18n[`stats${t.charAt(0).toUpperCase() + t.slice(1)}`]}: ${C(stats, t)}`) : null).filter(Boolean))
  154. )
  155. ])
  156. : filter === 'MINE'
  157. ? div({ class: 'stats-container' }, [
  158. div({ style: blockStyle }, h2(`${i18n.bankingUserEngagementScore}: ${C(stats, 'karmaScore')}`)),
  159. div({ style: blockStyle },
  160. h2(i18n.statsActivity7d),
  161. table({ style: 'width:100%; border-collapse: collapse;' },
  162. tr(th(i18n.day), th(i18n.messages)),
  163. ...(Array.isArray(stats.activity?.daily7) ? stats.activity.daily7 : []).map(row =>
  164. tr(td(row.day), td(String(row.count)))
  165. )
  166. ),
  167. p(`${i18n.statsActivity7dTotal}: ${stats.activity?.daily7Total || 0}`),
  168. p(`${i18n.statsActivity30dTotal}: ${stats.activity?.daily30Total || 0}`)
  169. ),
  170. div({ style: blockStyle },
  171. h2(`${i18n.statsDiscoveredTribes}: ${stats.memberTribes.length}`),
  172. table({ style: 'width:100%; border-collapse: collapse; margin-top: 8px;' },
  173. tr(th(i18n.typeTribe || 'Tribe')),
  174. ...stats.memberTribes.map(name => tr(td(name)))
  175. )
  176. ),
  177. div({ style: blockStyle }, h2(`${i18n.statsYourForum}: ${C(stats, 'forum')}`)),
  178. div({ style: blockStyle }, h2(`${i18n.statsYourTransfer}: ${C(stats, 'transfer')}`)),
  179. div({ style: blockStyle },
  180. h2(i18n.statsMarketTitle),
  181. ul([
  182. li(`${i18n.statsMarketTotal}: ${stats.marketKPIs?.total || 0}`),
  183. li(`${i18n.statsMarketForSale}: ${stats.marketKPIs?.forSale || 0}`),
  184. li(`${i18n.statsMarketReserved}: ${stats.marketKPIs?.reserved || 0}`),
  185. li(`${i18n.statsMarketClosed}: ${stats.marketKPIs?.closed || 0}`),
  186. li(`${i18n.statsMarketSold}: ${stats.marketKPIs?.sold || 0}`),
  187. li(`${i18n.statsMarketRevenue}: ${((stats.marketKPIs?.revenueECO || 0)).toFixed(6)} ECO`),
  188. li(`${i18n.statsMarketAvgSoldPrice}: ${((stats.marketKPIs?.avgSoldPrice || 0)).toFixed(6)} ECO`)
  189. ])
  190. ),
  191. div({ style: blockStyle },
  192. h2(i18n.statsProjectsTitle),
  193. ul([
  194. li(`${i18n.statsProjectsTotal}: ${stats.projectsKPIs?.total || 0}`),
  195. li(`${i18n.statsProjectsActive}: ${stats.projectsKPIs?.active || 0}`),
  196. li(`${i18n.statsProjectsCompleted}: ${stats.projectsKPIs?.completed || 0}`),
  197. li(`${i18n.statsProjectsPaused}: ${stats.projectsKPIs?.paused || 0}`),
  198. li(`${i18n.statsProjectsCancelled}: ${stats.projectsKPIs?.cancelled || 0}`),
  199. li(`${i18n.statsProjectsGoalTotal}: ${(stats.projectsKPIs?.ecoGoalTotal || 0)} ECO`),
  200. li(`${i18n.statsProjectsPledgedTotal}: ${(stats.projectsKPIs?.ecoPledgedTotal || 0)} ECO`),
  201. li(`${i18n.statsProjectsSuccessRate}: ${((stats.projectsKPIs?.successRate || 0)).toFixed(1)}%`),
  202. li(`${i18n.statsProjectsAvgProgress}: ${((stats.projectsKPIs?.avgProgress || 0)).toFixed(1)}%`),
  203. li(`${i18n.statsProjectsMedianProgress}: ${((stats.projectsKPIs?.medianProgress || 0)).toFixed(1)}%`),
  204. li(`${i18n.statsProjectsActiveFundingAvg}: ${((stats.projectsKPIs?.activeFundingAvg || 0)).toFixed(1)}%`)
  205. ])
  206. ),
  207. div({ style: blockStyle },
  208. h2(`${i18n.statsYourOpinions}: ${totalOpinions}`),
  209. ul(types.map(t => O(stats, t) > 0 ? li(`${i18n[`stats${t.charAt(0).toUpperCase() + t.slice(1)}`]}: ${O(stats, t)}`) : null).filter(Boolean))
  210. ),
  211. div({ style: blockStyle },
  212. h2(`${i18n.statsYourContent}: ${totalContent}`),
  213. ul(types.map(t => C(stats, t) > 0 ? li(`${i18n[`stats${t.charAt(0).toUpperCase() + t.slice(1)}`]}: ${C(stats, t)}`) : null).filter(Boolean))
  214. )
  215. ])
  216. : div({ class: 'stats-container' }, [
  217. div({ style: blockStyle },
  218. h2(`${i18n.TOMBSTONEButton}: ${stats.userTombstoneCount}`),
  219. h2(`${i18n.statsTombstoneRatio.toUpperCase()}: ${((stats.tombstoneKPIs?.ratio || 0)).toFixed(2)}%`)
  220. )
  221. ])
  222. )
  223. )
  224. );
  225. };