namirasoft-site-react 1.5.45 → 1.5.47

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 (485) hide show
  1. package/.dockerignore +86 -86
  2. package/.env.template +10 -10
  3. package/Dockerfile +18 -18
  4. package/SKILL.md +307 -307
  5. package/config-overrides.js +72 -72
  6. package/dist/App.css +33 -33
  7. package/dist/App.d.ts +2 -2
  8. package/dist/NSBoxBuilder.d.ts +25 -25
  9. package/dist/components/NSBanner.d.ts +1 -1
  10. package/dist/components/NSBanner.module.css +47 -47
  11. package/dist/components/NSBarAction.d.ts +1 -1
  12. package/dist/components/NSBarAction.module.css +91 -91
  13. package/dist/components/NSBarAlert.d.ts +1 -1
  14. package/dist/components/NSBarAlert.module.css +79 -79
  15. package/dist/components/NSBarNotification.d.ts +1 -1
  16. package/dist/components/NSBarNotification.module.css +34 -34
  17. package/dist/components/NSBarTitle.d.ts +1 -1
  18. package/dist/components/NSBarTitle.module.css +9 -9
  19. package/dist/components/NSBox.module.css +87 -87
  20. package/dist/components/NSBoxBaseCombo.d.ts +1 -1
  21. package/dist/components/NSBoxBaseCombo.module.css +24 -24
  22. package/dist/components/NSBoxBaseLayout.d.ts +1 -1
  23. package/dist/components/NSBoxBaseLayoutRecursive.d.ts +1 -1
  24. package/dist/components/NSBoxBoolean.d.ts +1 -1
  25. package/dist/components/NSBoxBoolean.module.css +73 -73
  26. package/dist/components/NSBoxBooleans.d.ts +1 -1
  27. package/dist/components/NSBoxColor.d.ts +1 -1
  28. package/dist/components/NSBoxCombo.d.ts +1 -1
  29. package/dist/components/NSBoxDate.d.ts +1 -1
  30. package/dist/components/NSBoxDate.module.css +4 -4
  31. package/dist/components/NSBoxDateRange.d.ts +1 -1
  32. package/dist/components/NSBoxDateRangeBase.d.ts +1 -1
  33. package/dist/components/NSBoxDateRangeBase.module.css +73 -73
  34. package/dist/components/NSBoxDateTime.d.ts +1 -1
  35. package/dist/components/NSBoxDateTime.module.css +4 -4
  36. package/dist/components/NSBoxDateTimeRange.d.ts +1 -1
  37. package/dist/components/NSBoxDouble.d.ts +1 -1
  38. package/dist/components/NSBoxDuration.d.ts +1 -1
  39. package/dist/components/NSBoxDuration.module.css +4 -4
  40. package/dist/components/NSBoxDynamic.d.ts +1 -1
  41. package/dist/components/NSBoxDynamics.d.ts +1 -1
  42. package/dist/components/NSBoxEmail.d.ts +1 -1
  43. package/dist/components/NSBoxEntity.d.ts +1 -1
  44. package/dist/components/NSBoxEnum.d.ts +1 -1
  45. package/dist/components/NSBoxFile.d.ts +1 -1
  46. package/dist/components/NSBoxFile.module.css +10 -10
  47. package/dist/components/NSBoxFilePath.d.ts +1 -1
  48. package/dist/components/NSBoxFont.d.ts +1 -1
  49. package/dist/components/NSBoxIPV4.d.ts +1 -1
  50. package/dist/components/NSBoxIPV4Range.d.ts +1 -1
  51. package/dist/components/NSBoxIPV6.d.ts +1 -1
  52. package/dist/components/NSBoxIPV6Range.d.ts +1 -1
  53. package/dist/components/NSBoxInteger.d.ts +1 -1
  54. package/dist/components/NSBoxInteger.module.css +16 -16
  55. package/dist/components/NSBoxMoney.d.ts +1 -1
  56. package/dist/components/NSBoxOptions.d.ts +1 -1
  57. package/dist/components/NSBoxOptions.module.css +53 -53
  58. package/dist/components/NSBoxPhone.d.ts +1 -1
  59. package/dist/components/NSBoxPhone.module.css +35 -35
  60. package/dist/components/NSBoxRadio.d.ts +1 -1
  61. package/dist/components/NSBoxRadio.module.css +33 -33
  62. package/dist/components/NSBoxSchemaBase.d.ts +1 -1
  63. package/dist/components/NSBoxSchemaBase.module.css +17 -17
  64. package/dist/components/NSBoxSchemaVariable.d.ts +1 -1
  65. package/dist/components/NSBoxSchemaVariable.module.css +16 -16
  66. package/dist/components/NSBoxSearch.d.ts +1 -1
  67. package/dist/components/NSBoxSensitive.d.ts +1 -1
  68. package/dist/components/NSBoxString.d.ts +4 -1
  69. package/dist/components/NSBoxString.js +37 -7
  70. package/dist/components/NSBoxString.js.map +1 -1
  71. package/dist/components/NSBoxTextArea.d.ts +1 -1
  72. package/dist/components/NSBoxTextArea.module.css +12 -12
  73. package/dist/components/NSBoxTime.d.ts +1 -1
  74. package/dist/components/NSBoxTime.module.css +4 -4
  75. package/dist/components/NSBoxTimeRange.d.ts +1 -1
  76. package/dist/components/NSBoxTimeZone.d.ts +1 -1
  77. package/dist/components/NSBoxURL.d.ts +1 -1
  78. package/dist/components/NSBoxVersion.d.ts +1 -1
  79. package/dist/components/NSButton.module.css +57 -57
  80. package/dist/components/NSButtonBlue.d.ts +1 -1
  81. package/dist/components/NSButtonBlue.module.css +4 -4
  82. package/dist/components/NSButtonGreen.d.ts +1 -1
  83. package/dist/components/NSButtonGreen.module.css +4 -4
  84. package/dist/components/NSButtonRed.d.ts +1 -1
  85. package/dist/components/NSButtonRed.module.css +4 -4
  86. package/dist/components/NSCard.d.ts +1 -1
  87. package/dist/components/NSCard.module.css +122 -122
  88. package/dist/components/NSCardScreenshot.d.ts +1 -1
  89. package/dist/components/NSCardScreenshot.module.css +41 -41
  90. package/dist/components/NSChartBar.d.ts +1 -1
  91. package/dist/components/NSChartDoughnut.d.ts +1 -1
  92. package/dist/components/NSChartLine.d.ts +1 -1
  93. package/dist/components/NSChartPie.d.ts +1 -1
  94. package/dist/components/NSChartRange.d.ts +1 -1
  95. package/dist/components/NSColumn.d.ts +1 -1
  96. package/dist/components/NSColumn.module.css +16 -16
  97. package/dist/components/NSCopy.d.ts +1 -1
  98. package/dist/components/NSCopyBox.d.ts +1 -1
  99. package/dist/components/NSCopyBox.module.css +39 -39
  100. package/dist/components/NSDialog.d.ts +1 -1
  101. package/dist/components/NSDialog.module.css +105 -105
  102. package/dist/components/NSDialogBox.d.ts +1 -1
  103. package/dist/components/NSDialogBoxDate.d.ts +1 -1
  104. package/dist/components/NSDialogBoxDateTime.d.ts +1 -1
  105. package/dist/components/NSDialogBoxPhone.d.ts +1 -1
  106. package/dist/components/NSDialogBoxString.d.ts +1 -1
  107. package/dist/components/NSDialogDelete.d.ts +1 -1
  108. package/dist/components/NSDialogInfo.d.ts +1 -1
  109. package/dist/components/NSDialogPageSelection.d.ts +2 -2
  110. package/dist/components/NSDialogPageSelection.module.css +103 -103
  111. package/dist/components/NSDownTimer.d.ts +1 -1
  112. package/dist/components/NSDownTimer.module.css +55 -55
  113. package/dist/components/NSDownload.d.ts +1 -1
  114. package/dist/components/NSDownload.module.css +46 -46
  115. package/dist/components/NSElectronicCard.d.ts +1 -1
  116. package/dist/components/NSElectronicCard.js +1 -1
  117. package/dist/components/NSElectronicCard.js.map +1 -1
  118. package/dist/components/NSElectronicCard.module.css +66 -60
  119. package/dist/components/NSEntityCardBackground.d.ts +1 -1
  120. package/dist/components/NSEntityCardBackground.module.css +27 -27
  121. package/dist/components/NSFilterBox.d.ts +2 -2
  122. package/dist/components/NSFilterBox.module.css +27 -27
  123. package/dist/components/NSFilterBoxItems.d.ts +1 -1
  124. package/dist/components/NSFilterBoxItems.module.css +15 -15
  125. package/dist/components/NSFilterItem.d.ts +1 -1
  126. package/dist/components/NSFilterItem.module.css +24 -24
  127. package/dist/components/NSFooter.d.ts +1 -1
  128. package/dist/components/NSFooter.module.css +140 -140
  129. package/dist/components/NSGauge.d.ts +1 -1
  130. package/dist/components/NSGauge.module.css +67 -67
  131. package/dist/components/NSGaugeDate.d.ts +1 -1
  132. package/dist/components/NSGaugeDate.module.css +57 -57
  133. package/dist/components/NSGaugeNumber.d.ts +1 -1
  134. package/dist/components/NSGaugeNumber.module.css +66 -66
  135. package/dist/components/NSHeader.d.ts +1 -1
  136. package/dist/components/NSHeader.module.css +355 -355
  137. package/dist/components/NSHeaderScreenshot.d.ts +1 -1
  138. package/dist/components/NSHeaderScreenshot.module.css +35 -35
  139. package/dist/components/NSID.d.ts +1 -1
  140. package/dist/components/NSInfo.d.ts +1 -1
  141. package/dist/components/NSLabel.d.ts +1 -1
  142. package/dist/components/NSLabel.module.css +25 -25
  143. package/dist/components/NSLabelErrorNotifier.d.ts +1 -1
  144. package/dist/components/NSLabelErrorNotifier.module.css +2 -2
  145. package/dist/components/NSLabelSensitive.d.ts +1 -1
  146. package/dist/components/NSLabelSensitive.module.css +9 -9
  147. package/dist/components/NSLayout.d.ts +1 -1
  148. package/dist/components/NSLayout.module.css +22 -22
  149. package/dist/components/NSLine.d.ts +1 -1
  150. package/dist/components/NSLine.module.css +15 -15
  151. package/dist/components/NSLineText.d.ts +1 -1
  152. package/dist/components/NSLineText.module.css +38 -38
  153. package/dist/components/NSLink.d.ts +1 -1
  154. package/dist/components/NSLinkBlue.d.ts +1 -1
  155. package/dist/components/NSLinkGreen.d.ts +1 -1
  156. package/dist/components/NSLinkRed.d.ts +1 -1
  157. package/dist/components/NSListGrouped.d.ts +1 -1
  158. package/dist/components/NSListGrouped.module.css +69 -69
  159. package/dist/components/NSListProduct.d.ts +1 -1
  160. package/dist/components/NSLoading.d.ts +1 -1
  161. package/dist/components/NSLoading.module.css +10 -10
  162. package/dist/components/NSMarkdown.d.ts +1 -1
  163. package/dist/components/NSMegaMenu.d.ts +1 -1
  164. package/dist/components/NSMegaMenu.module.css +133 -133
  165. package/dist/components/NSMenuAction.d.ts +1 -1
  166. package/dist/components/NSMenuAction.module.css +88 -88
  167. package/dist/components/NSMenuButton.d.ts +1 -1
  168. package/dist/components/NSMenuButton.module.css +44 -44
  169. package/dist/components/NSNoData.d.ts +1 -1
  170. package/dist/components/NSNoData.module.css +11 -11
  171. package/dist/components/NSPagination.d.ts +1 -1
  172. package/dist/components/NSPagination.module.css +94 -94
  173. package/dist/components/NSPanel.d.ts +1 -1
  174. package/dist/components/NSPanel.module.css +28 -28
  175. package/dist/components/NSPanelAccordion.d.ts +1 -1
  176. package/dist/components/NSPanelAccordion.module.css +4 -4
  177. package/dist/components/NSProductSearch.d.ts +1 -1
  178. package/dist/components/NSProductSearch.module.css +73 -73
  179. package/dist/components/NSProgressBar.d.ts +1 -1
  180. package/dist/components/NSProgressBar.module.css +38 -38
  181. package/dist/components/NSRange.d.ts +1 -1
  182. package/dist/components/NSRange.module.css +66 -66
  183. package/dist/components/NSReorderDialog.d.ts +1 -1
  184. package/dist/components/NSReorderDialog.module.css +53 -53
  185. package/dist/components/NSRepeater.d.ts +1 -1
  186. package/dist/components/NSRepeaterNSBoxSchemaVariable.d.ts +1 -1
  187. package/dist/components/NSRepeaterNSTag.d.ts +1 -1
  188. package/dist/components/NSRow.d.ts +1 -1
  189. package/dist/components/NSRow.module.css +17 -17
  190. package/dist/components/NSSection.d.ts +1 -1
  191. package/dist/components/NSSection.module.css +4 -4
  192. package/dist/components/NSSectionCards.d.ts +1 -1
  193. package/dist/components/NSSectionCards.module.css +38 -38
  194. package/dist/components/NSSectionTiles.d.ts +1 -1
  195. package/dist/components/NSSectionTiles.module.css +10 -10
  196. package/dist/components/NSSectionTitle.d.ts +1 -1
  197. package/dist/components/NSSortDialog.d.ts +1 -1
  198. package/dist/components/NSSpace.d.ts +1 -1
  199. package/dist/components/NSSplitter.d.ts +1 -1
  200. package/dist/components/NSSplitter.module.css +59 -59
  201. package/dist/components/NSTabPage.d.ts +1 -1
  202. package/dist/components/NSTabPage.module.css +59 -59
  203. package/dist/components/NSTable.d.ts +1 -1
  204. package/dist/components/NSTable.js +23 -23
  205. package/dist/components/NSTable.module.css +376 -376
  206. package/dist/components/NSTableChart.d.ts +1 -1
  207. package/dist/components/NSTableChart.module.css +119 -119
  208. package/dist/components/NSTag.d.ts +1 -1
  209. package/dist/components/NSTile.d.ts +1 -1
  210. package/dist/components/NSTile.module.css +76 -76
  211. package/dist/components/NSTimelineMonthly.d.ts +1 -1
  212. package/dist/components/NSTimelineMonthly.module.css +71 -71
  213. package/dist/components/NSTitle.d.ts +1 -1
  214. package/dist/components/NSTitle.module.css +15 -15
  215. package/dist/formatter/MoneyFormatter.js +14 -3
  216. package/dist/formatter/MoneyFormatter.js.map +1 -1
  217. package/dist/pages/NSImplementing.d.ts +1 -1
  218. package/dist/pages/NSImplementing.module.css +35 -35
  219. package/dist/pages/NSNotFoundPage.d.ts +1 -1
  220. package/dist/pages/NSNotFoundPage.module.css +18 -18
  221. package/dist/pages/NSUpdating.d.ts +1 -1
  222. package/dist/pages/NSUpdating.module.css +35 -35
  223. package/dist/routing/NSRouterMaker.d.ts +0 -1
  224. package/dist/routing/NSRouterMaker.js +0 -3
  225. package/dist/routing/NSRouterMaker.js.map +1 -1
  226. package/package.json +86 -86
  227. package/public/index.html +38 -38
  228. package/src/App.css +33 -33
  229. package/src/App.tsx +926 -926
  230. package/src/BrowserDetector.ts +109 -109
  231. package/src/Color.tsx +10 -10
  232. package/src/CopyToClipboard.ts +6 -6
  233. package/src/EncryptionOperation.ts +98 -98
  234. package/src/KeyOperation.ts +113 -113
  235. package/src/LinkOperation.ts +101 -101
  236. package/src/NSBoxBuilder.tsx +923 -923
  237. package/src/ProductCacheService.ts +36 -36
  238. package/src/Validator.ts +199 -199
  239. package/src/components/INSBox.tsx +7 -7
  240. package/src/components/NSBanner.module.css +47 -47
  241. package/src/components/NSBanner.tsx +80 -80
  242. package/src/components/NSBarAction.module.css +91 -91
  243. package/src/components/NSBarAction.tsx +95 -95
  244. package/src/components/NSBarAlert.module.css +79 -79
  245. package/src/components/NSBarAlert.tsx +35 -35
  246. package/src/components/NSBarNotification.module.css +34 -34
  247. package/src/components/NSBarNotification.tsx +89 -89
  248. package/src/components/NSBarTitle.module.css +9 -9
  249. package/src/components/NSBarTitle.tsx +23 -23
  250. package/src/components/NSBox.module.css +87 -87
  251. package/src/components/NSBox.tsx +19 -19
  252. package/src/components/NSBoxBaseCombo.module.css +24 -24
  253. package/src/components/NSBoxBaseCombo.tsx +522 -522
  254. package/src/components/NSBoxBaseLayout.tsx +163 -163
  255. package/src/components/NSBoxBaseLayoutRecursive.tsx +59 -59
  256. package/src/components/NSBoxBoolean.module.css +73 -73
  257. package/src/components/NSBoxBoolean.tsx +152 -152
  258. package/src/components/NSBoxBooleans.tsx +181 -181
  259. package/src/components/NSBoxColor.tsx +107 -107
  260. package/src/components/NSBoxCombo.tsx +95 -95
  261. package/src/components/NSBoxDate.module.css +4 -4
  262. package/src/components/NSBoxDate.tsx +144 -144
  263. package/src/components/NSBoxDateRange.tsx +88 -88
  264. package/src/components/NSBoxDateRangeBase.module.css +73 -73
  265. package/src/components/NSBoxDateRangeBase.tsx +236 -236
  266. package/src/components/NSBoxDateTime.module.css +4 -4
  267. package/src/components/NSBoxDateTime.tsx +136 -136
  268. package/src/components/NSBoxDateTimeRange.tsx +66 -66
  269. package/src/components/NSBoxDouble.tsx +110 -110
  270. package/src/components/NSBoxDuration.module.css +4 -4
  271. package/src/components/NSBoxDuration.tsx +107 -107
  272. package/src/components/NSBoxDynamic.tsx +73 -73
  273. package/src/components/NSBoxDynamics.tsx +68 -68
  274. package/src/components/NSBoxEmail.tsx +117 -117
  275. package/src/components/NSBoxEntity.tsx +181 -181
  276. package/src/components/NSBoxEnum.tsx +113 -113
  277. package/src/components/NSBoxFile.module.css +10 -10
  278. package/src/components/NSBoxFile.tsx +106 -106
  279. package/src/components/NSBoxFilePath.tsx +106 -106
  280. package/src/components/NSBoxFont.tsx +107 -107
  281. package/src/components/NSBoxIPV4.tsx +107 -107
  282. package/src/components/NSBoxIPV4Range.tsx +107 -107
  283. package/src/components/NSBoxIPV6.tsx +104 -104
  284. package/src/components/NSBoxIPV6Range.tsx +107 -107
  285. package/src/components/NSBoxInteger.module.css +16 -16
  286. package/src/components/NSBoxInteger.tsx +126 -126
  287. package/src/components/NSBoxMoney.tsx +115 -115
  288. package/src/components/NSBoxOptions.module.css +53 -53
  289. package/src/components/NSBoxOptions.tsx +182 -182
  290. package/src/components/NSBoxPhone.module.css +35 -35
  291. package/src/components/NSBoxPhone.tsx +120 -120
  292. package/src/components/NSBoxRadio.module.css +33 -33
  293. package/src/components/NSBoxRadio.tsx +104 -104
  294. package/src/components/NSBoxSchemaBase.module.css +17 -17
  295. package/src/components/NSBoxSchemaBase.tsx +562 -562
  296. package/src/components/NSBoxSchemaVariable.module.css +16 -16
  297. package/src/components/NSBoxSchemaVariable.tsx +262 -262
  298. package/src/components/NSBoxSearch.tsx +112 -112
  299. package/src/components/NSBoxSensitive.tsx +133 -133
  300. package/src/components/NSBoxString.tsx +164 -106
  301. package/src/components/NSBoxTextArea.module.css +12 -12
  302. package/src/components/NSBoxTextArea.tsx +136 -136
  303. package/src/components/NSBoxTime.module.css +4 -4
  304. package/src/components/NSBoxTime.tsx +106 -106
  305. package/src/components/NSBoxTimeRange.tsx +220 -220
  306. package/src/components/NSBoxTimeZone.tsx +122 -122
  307. package/src/components/NSBoxURL.tsx +117 -117
  308. package/src/components/NSBoxVersion.tsx +107 -107
  309. package/src/components/NSButton.module.css +57 -57
  310. package/src/components/NSButton.tsx +74 -74
  311. package/src/components/NSButtonBlue.module.css +4 -4
  312. package/src/components/NSButtonBlue.tsx +29 -29
  313. package/src/components/NSButtonGreen.module.css +4 -4
  314. package/src/components/NSButtonGreen.tsx +29 -29
  315. package/src/components/NSButtonRed.module.css +4 -4
  316. package/src/components/NSButtonRed.tsx +29 -29
  317. package/src/components/NSCard.module.css +122 -122
  318. package/src/components/NSCard.tsx +67 -67
  319. package/src/components/NSCardScreenshot.module.css +41 -41
  320. package/src/components/NSCardScreenshot.tsx +31 -31
  321. package/src/components/NSChartBar.tsx +134 -134
  322. package/src/components/NSChartDoughnut.tsx +114 -114
  323. package/src/components/NSChartLine.tsx +110 -110
  324. package/src/components/NSChartPie.tsx +105 -105
  325. package/src/components/NSChartRange.tsx +14 -14
  326. package/src/components/NSColumn.module.css +16 -16
  327. package/src/components/NSColumn.tsx +24 -24
  328. package/src/components/NSCopy.tsx +95 -95
  329. package/src/components/NSCopyBox.module.css +39 -39
  330. package/src/components/NSCopyBox.tsx +53 -53
  331. package/src/components/NSDialog.module.css +105 -105
  332. package/src/components/NSDialog.tsx +72 -72
  333. package/src/components/NSDialogBox.tsx +38 -38
  334. package/src/components/NSDialogBoxDate.tsx +28 -28
  335. package/src/components/NSDialogBoxDateTime.tsx +28 -28
  336. package/src/components/NSDialogBoxPhone.tsx +28 -28
  337. package/src/components/NSDialogBoxString.tsx +28 -28
  338. package/src/components/NSDialogDelete.tsx +36 -36
  339. package/src/components/NSDialogInfo.tsx +49 -49
  340. package/src/components/NSDialogPageSelection.module.css +103 -103
  341. package/src/components/NSDialogPageSelection.tsx +233 -233
  342. package/src/components/NSDownTimer.module.css +55 -55
  343. package/src/components/NSDownTimer.tsx +91 -91
  344. package/src/components/NSDownload.module.css +46 -46
  345. package/src/components/NSDownload.tsx +69 -69
  346. package/src/components/NSElectronicCard.module.css +66 -60
  347. package/src/components/NSElectronicCard.tsx +46 -46
  348. package/src/components/NSEntityCardBackground.module.css +27 -27
  349. package/src/components/NSEntityCardBackground.tsx +36 -36
  350. package/src/components/NSFilterBox.module.css +27 -27
  351. package/src/components/NSFilterBox.tsx +477 -477
  352. package/src/components/NSFilterBoxItems.module.css +15 -15
  353. package/src/components/NSFilterBoxItems.tsx +52 -52
  354. package/src/components/NSFilterItem.module.css +24 -24
  355. package/src/components/NSFilterItem.tsx +27 -27
  356. package/src/components/NSFooter.module.css +140 -140
  357. package/src/components/NSFooter.tsx +322 -322
  358. package/src/components/NSGauge.module.css +67 -67
  359. package/src/components/NSGauge.tsx +52 -52
  360. package/src/components/NSGaugeDate.module.css +57 -57
  361. package/src/components/NSGaugeDate.tsx +130 -130
  362. package/src/components/NSGaugeNumber.module.css +66 -66
  363. package/src/components/NSGaugeNumber.tsx +96 -96
  364. package/src/components/NSHeader.module.css +355 -355
  365. package/src/components/NSHeader.tsx +340 -340
  366. package/src/components/NSHeaderScreenshot.module.css +35 -35
  367. package/src/components/NSHeaderScreenshot.tsx +39 -39
  368. package/src/components/NSID.tsx +180 -180
  369. package/src/components/NSInfo.tsx +50 -50
  370. package/src/components/NSLabel.module.css +25 -25
  371. package/src/components/NSLabel.tsx +47 -47
  372. package/src/components/NSLabelErrorNotifier.module.css +2 -2
  373. package/src/components/NSLabelErrorNotifier.tsx +35 -35
  374. package/src/components/NSLabelSensitive.module.css +9 -9
  375. package/src/components/NSLabelSensitive.tsx +101 -101
  376. package/src/components/NSLayout.module.css +22 -22
  377. package/src/components/NSLayout.tsx +143 -143
  378. package/src/components/NSLine.module.css +15 -15
  379. package/src/components/NSLine.tsx +13 -13
  380. package/src/components/NSLineText.module.css +38 -38
  381. package/src/components/NSLineText.tsx +17 -17
  382. package/src/components/NSListGrouped.module.css +69 -69
  383. package/src/components/NSListGrouped.tsx +106 -106
  384. package/src/components/NSListProduct.tsx +79 -79
  385. package/src/components/NSLoading.module.css +10 -10
  386. package/src/components/NSLoading.tsx +43 -43
  387. package/src/components/NSMegaMenu.module.css +133 -133
  388. package/src/components/NSMegaMenu.tsx +190 -190
  389. package/src/components/NSMenuAction.module.css +88 -88
  390. package/src/components/NSMenuAction.tsx +106 -106
  391. package/src/components/NSMenuButton.module.css +44 -44
  392. package/src/components/NSMenuButton.tsx +198 -198
  393. package/src/components/NSNoData.module.css +11 -11
  394. package/src/components/NSNoData.tsx +24 -24
  395. package/src/components/NSPagination.module.css +94 -94
  396. package/src/components/NSPagination.tsx +182 -182
  397. package/src/components/NSPanel.module.css +28 -28
  398. package/src/components/NSPanel.tsx +26 -26
  399. package/src/components/NSPanelAccordion.module.css +4 -4
  400. package/src/components/NSPanelAccordion.tsx +51 -51
  401. package/src/components/NSProductSearch.module.css +73 -73
  402. package/src/components/NSProductSearch.tsx +193 -193
  403. package/src/components/NSProgressBar.module.css +38 -38
  404. package/src/components/NSProgressBar.tsx +29 -29
  405. package/src/components/NSRange.module.css +66 -66
  406. package/src/components/NSRange.tsx +83 -83
  407. package/src/components/NSReorderDialog.module.css +53 -53
  408. package/src/components/NSReorderDialog.tsx +203 -203
  409. package/src/components/NSRepeater.tsx +335 -335
  410. package/src/components/NSRepeaterNSBoxSchemaVariable.tsx +96 -96
  411. package/src/components/NSRepeaterNSTag.tsx +86 -86
  412. package/src/components/NSRow.module.css +17 -17
  413. package/src/components/NSRow.tsx +24 -24
  414. package/src/components/NSSection.module.css +4 -4
  415. package/src/components/NSSection.tsx +26 -26
  416. package/src/components/NSSectionCards.module.css +38 -38
  417. package/src/components/NSSectionCards.tsx +51 -51
  418. package/src/components/NSSectionTiles.module.css +10 -10
  419. package/src/components/NSSectionTiles.tsx +25 -25
  420. package/src/components/NSSectionTitle.tsx +19 -19
  421. package/src/components/NSSortDialog.tsx +124 -124
  422. package/src/components/NSSpace.tsx +28 -28
  423. package/src/components/NSSplitter.module.css +59 -59
  424. package/src/components/NSSplitter.tsx +244 -244
  425. package/src/components/NSTabPage.module.css +59 -59
  426. package/src/components/NSTabPage.tsx +122 -122
  427. package/src/components/NSTable.module.css +376 -376
  428. package/src/components/NSTable.tsx +1451 -1451
  429. package/src/components/NSTableChart.module.css +119 -119
  430. package/src/components/NSTableChart.tsx +760 -760
  431. package/src/components/NSTag.tsx +74 -74
  432. package/src/components/NSTile.module.css +76 -76
  433. package/src/components/NSTile.tsx +27 -27
  434. package/src/components/NSTimelineMonthly.module.css +71 -71
  435. package/src/components/NSTimelineMonthly.tsx +44 -44
  436. package/src/components/NSTitle.module.css +15 -15
  437. package/src/components/NSTitle.tsx +19 -19
  438. package/src/css.d.ts +13 -13
  439. package/src/formatter/BackColorFormatter.tsx +23 -23
  440. package/src/formatter/BaseColumnFormatter.ts +86 -86
  441. package/src/formatter/BaseURLImageFormatter.tsx +37 -37
  442. package/src/formatter/BooleanFormatter.ts +26 -26
  443. package/src/formatter/BytesFormatter.tsx +19 -19
  444. package/src/formatter/DateFormatter.ts +26 -26
  445. package/src/formatter/DateTimeFormatter.ts +26 -26
  446. package/src/formatter/DurationFormatter.ts +17 -17
  447. package/src/formatter/EmailFormatter.tsx +25 -25
  448. package/src/formatter/EnumFormatter.ts +17 -17
  449. package/src/formatter/FloatFormatter.ts +27 -27
  450. package/src/formatter/ForeColorFormatter.tsx +24 -24
  451. package/src/formatter/IDFormatter.tsx +58 -58
  452. package/src/formatter/IPFormatter.ts +39 -39
  453. package/src/formatter/IntegerFormatter.ts +27 -27
  454. package/src/formatter/JsonFormatter.tsx +75 -75
  455. package/src/formatter/MoneyFormatter.ts +52 -39
  456. package/src/formatter/PercentFormatter.tsx +43 -43
  457. package/src/formatter/PhoneFormatter.tsx +25 -25
  458. package/src/formatter/SensitiveFormatter.tsx +40 -40
  459. package/src/formatter/StringFormatter.tsx +49 -49
  460. package/src/formatter/TimeFormatter.ts +25 -25
  461. package/src/formatter/URLFormatter.tsx +28 -28
  462. package/src/formatter/UnknowFormatter.ts +22 -22
  463. package/src/index.tsx +7 -7
  464. package/src/main.ts +266 -266
  465. package/src/pages/NSImplementing.module.css +35 -35
  466. package/src/pages/NSImplementing.tsx +32 -32
  467. package/src/pages/NSNotFoundPage.module.css +18 -18
  468. package/src/pages/NSNotFoundPage.tsx +14 -14
  469. package/src/pages/NSUpdating.module.css +35 -35
  470. package/src/pages/NSUpdating.tsx +32 -32
  471. package/src/props/IBackgroundProps.ts +5 -5
  472. package/src/props/IBaseComponentProps.ts +8 -8
  473. package/src/props/IHeaderIconProps.ts +10 -10
  474. package/src/props/IHeaderLeftProps.ts +6 -6
  475. package/src/props/IHeaderRightProps.ts +8 -8
  476. package/src/props/IImageProps.ts +4 -4
  477. package/src/props/ILinkProps.ts +4 -4
  478. package/src/props/IValidationNumberProps.ts +4 -4
  479. package/src/props/IValidationPrecisionProps.ts +3 -3
  480. package/src/props/IValidationProps.ts +9 -9
  481. package/src/props/IValidationRegexProps.ts +4 -4
  482. package/src/props/IValidationStringProps.ts +4 -4
  483. package/src/routing/NSNotifier.ts +114 -114
  484. package/src/routing/NSRouterMaker.tsx +24 -28
  485. package/tsconfig.json +43 -43
@@ -1,761 +1,761 @@
1
- "use client";
2
- import { IStorageLocal, TimeOperation } from "namirasoft-core";
3
- import { Component, createRef } from "react";
4
- import { IBaseComponentProps } from "../props/IBaseComponentProps";
5
- import { NSBoxBoolean } from "./NSBoxBoolean";
6
- import { NSBoxCombo } from "./NSBoxCombo";
7
- import { NSBoxOptions } from "./NSBoxOptions";
8
- import { NSButton } from "./NSButton";
9
- import { NSButtonBlue } from "./NSButtonBlue";
10
- import { NSChartBar, NSChartBarMode } from "./NSChartBar";
11
- import { NSChartDoughnut } from "./NSChartDoughnut";
12
- import { NSChartLine } from "./NSChartLine";
13
- import { NSColumn } from "./NSColumn";
14
- import { NSNoData } from "./NSNoData";
15
- import { TableColumnInfo } from "./NSTable";
16
- import Styles from "./NSTableChart.module.css";
17
-
18
- export type NSTableChartType = "bar" | "hbar" | "line" | "area" | "doughnut";
19
- export type NSTableChartRowSource = "page" | "selected";
20
- export type NSTableChartAggregate = "sum" | "average" | "count" | "min" | "max";
21
- export type NSTableChartDateGroup = "exact" | "hourly" | "daily" | "weekly" | "monthly" | "yearly";
22
-
23
- const CHART_TYPES: { type: NSTableChartType, text: string }[] = [
24
- { type: "bar", text: "Bar" },
25
- { type: "hbar", text: "Horizontal Bar" },
26
- { type: "line", text: "Line" },
27
- { type: "area", text: "Area" },
28
- { type: "doughnut", text: "Doughnut" },
29
- ];
30
-
31
- const BAR_MODES: { mode: NSChartBarMode, text: string }[] = [
32
- { mode: "grouped", text: "Grouped" },
33
- { mode: "stacked", text: "Stacked" },
34
- { mode: "percent", text: "Percent" },
35
- ];
36
-
37
- const AGGREGATES: { value: NSTableChartAggregate, text: string }[] = [
38
- { value: "sum", text: "Sum" },
39
- { value: "average", text: "Average" },
40
- { value: "count", text: "Count" },
41
- { value: "min", text: "Min" },
42
- { value: "max", text: "Max" },
43
- ];
44
-
45
- const DATE_GROUPS: { value: NSTableChartDateGroup, text: string }[] = [
46
- { value: "exact", text: "Exact" },
47
- { value: "hourly", text: "Hourly" },
48
- { value: "daily", text: "Daily" },
49
- { value: "weekly", text: "Weekly" },
50
- { value: "monthly", text: "Monthly" },
51
- { value: "yearly", text: "Yearly" },
52
- ];
53
-
54
- const NUMERIC_TYPES = ["number", "integer", "int", "float", "double", "decimal", "long", "money", "percent", "percentage", "bytes", "duration"];
55
-
56
- export interface NSTableChartProps<RowType> extends IBaseComponentProps
57
- {
58
- name: string;
59
- columns: TableColumnInfo[];
60
- rows: RowType[];
61
- selectedRows: RowType[];
62
- onClose: () => void;
63
- }
64
-
65
- interface NSTableChartState
66
- {
67
- chartType: NSTableChartType;
68
- barMode: NSChartBarMode;
69
- aggregate: NSTableChartAggregate;
70
- dateGroup: NSTableChartDateGroup;
71
- rowSource: NSTableChartRowSource;
72
- xColumn: string;
73
- yColumns: string[];
74
- includeUndatedRows: boolean;
75
- }
76
-
77
- interface NSTableChartSettings
78
- {
79
- chartType?: NSTableChartType;
80
- barMode?: NSChartBarMode;
81
- aggregate?: NSTableChartAggregate;
82
- dateGroup?: NSTableChartDateGroup;
83
- rowSource?: NSTableChartRowSource;
84
- xColumn?: string;
85
- yColumns?: string[];
86
- includeUndatedRows?: boolean;
87
- }
88
-
89
- export class NSTableChart<RowType> extends Component<NSTableChartProps<RowType>, NSTableChartState>
90
- {
91
- private chartContainer = createRef<HTMLDivElement>();
92
- private previousBodyOverflow = "";
93
- private Storage = new IStorageLocal();
94
-
95
- constructor(props: NSTableChartProps<RowType>)
96
- {
97
- super(props);
98
- let numeric = this.getNumericColumns();
99
- let nonNumeric = props.columns.filter(column => !this.isNumericColumn(column));
100
- let defaultX = (nonNumeric[0] ?? props.columns[0])?.name ?? "";
101
- let defaultY = numeric.length > 0 ? [numeric[0].name] : [];
102
-
103
- let saved = this.loadSettings();
104
- let chartType: NSTableChartType = CHART_TYPES.some(item => item.type === saved?.chartType) ? saved!.chartType! : "bar";
105
- let barMode: NSChartBarMode = BAR_MODES.some(item => item.mode === saved?.barMode) ? saved!.barMode! : "grouped";
106
- let aggregate: NSTableChartAggregate = AGGREGATES.some(item => item.value === saved?.aggregate) ? saved!.aggregate! : "sum";
107
- let dateGroup: NSTableChartDateGroup = DATE_GROUPS.some(item => item.value === saved?.dateGroup) ? saved!.dateGroup! : "exact";
108
- let rowSource: NSTableChartRowSource = saved?.rowSource === "selected" ? "selected" : "page";
109
- let xColumn = (saved?.xColumn && this.getColumn(saved.xColumn)) ? saved.xColumn : defaultX;
110
- let savedY = (saved?.yColumns ?? []).filter((name: string) => numeric.some(column => column.name === name));
111
- let yColumns = savedY.length > 0 ? savedY : defaultY;
112
- if (chartType === "doughnut" && yColumns.length > 1)
113
- yColumns = [yColumns[0]];
114
-
115
- let includeUndatedRows = typeof saved?.includeUndatedRows === "boolean" ? saved.includeUndatedRows : true;
116
-
117
- this.state = {
118
- chartType,
119
- barMode,
120
- aggregate,
121
- dateGroup,
122
- rowSource,
123
- xColumn,
124
- yColumns,
125
- includeUndatedRows,
126
- };
127
- this.download = this.download.bind(this);
128
- }
129
-
130
- override componentDidMount()
131
- {
132
- this.previousBodyOverflow = document.body.style.overflow;
133
- document.body.style.overflow = "hidden";
134
- }
135
-
136
- override componentWillUnmount()
137
- {
138
- document.body.style.overflow = this.previousBodyOverflow;
139
- }
140
-
141
- override componentDidUpdate(_prevProps: NSTableChartProps<RowType>, prevState: NSTableChartState)
142
- {
143
- if (
144
- prevState.chartType !== this.state.chartType ||
145
- prevState.barMode !== this.state.barMode ||
146
- prevState.aggregate !== this.state.aggregate ||
147
- prevState.dateGroup !== this.state.dateGroup ||
148
- prevState.rowSource !== this.state.rowSource ||
149
- prevState.xColumn !== this.state.xColumn ||
150
- prevState.yColumns !== this.state.yColumns ||
151
- prevState.includeUndatedRows !== this.state.includeUndatedRows
152
- )
153
- this.saveSettings();
154
- }
155
-
156
- private settingsKey(): string
157
- {
158
- return `ns_table_chart_${this.props.name}`;
159
- }
160
-
161
- private loadSettings(): NSTableChartSettings | null
162
- {
163
- try
164
- {
165
- let raw = this.Storage.get(this.settingsKey(), "");
166
- return raw ? JSON.parse(raw) as NSTableChartSettings : null;
167
- }
168
- catch (e)
169
- {
170
- return null;
171
- }
172
- }
173
-
174
- private saveSettings(): void
175
- {
176
- try
177
- {
178
- let { chartType, barMode, aggregate, dateGroup, rowSource, xColumn, yColumns, includeUndatedRows } = this.state;
179
- this.Storage.set(this.settingsKey(), JSON.stringify({ chartType, barMode, aggregate, dateGroup, rowSource, xColumn, yColumns, includeUndatedRows }));
180
- }
181
- catch (e) { }
182
- }
183
-
184
- private isNumericColumn(column: TableColumnInfo): boolean
185
- {
186
- return NUMERIC_TYPES.includes((column.type ?? "").toLowerCase());
187
- }
188
-
189
- private isDateColumn(column: TableColumnInfo | undefined): boolean
190
- {
191
- return !!column && /date/.test((column.type ?? "").toLowerCase());
192
- }
193
-
194
- private isDateTimeColumn(column: TableColumnInfo | undefined): boolean
195
- {
196
- return !!column && (column.type ?? "").toLowerCase().includes("datetime");
197
- }
198
-
199
- private dateBucket(date: Date, group: NSTableChartDateGroup): Date
200
- {
201
- switch (group)
202
- {
203
- case "hourly": return TimeOperation.beginningOfHour(date);
204
- case "daily": return TimeOperation.beginningOfDay(date);
205
- case "weekly":
206
- {
207
- let start = TimeOperation.beginningOfDay(date);
208
- let offset = (start.getDay() + 6) % 7;
209
- return TimeOperation.daysAgo(offset, start);
210
- }
211
- case "monthly": return TimeOperation.beginningOfMonth(date);
212
- case "yearly": return TimeOperation.beginningOfYear(date);
213
- default: return date;
214
- }
215
- }
216
-
217
- private formatBucket(date: Date, group: NSTableChartDateGroup): string
218
- {
219
- switch (group)
220
- {
221
- case "hourly": return TimeOperation.format(date, "YYYY/MM/DD HH:00");
222
- case "daily": return TimeOperation.format(date, "YYYY/MM/DD");
223
- case "weekly":
224
- {
225
- let { year, week } = this.isoWeek(date);
226
- return `${year}-W${String(week).padStart(2, "0")}`;
227
- }
228
- case "monthly": return TimeOperation.format(date, "YYYY/MM");
229
- case "yearly": return TimeOperation.format(date, "YYYY");
230
- default: return TimeOperation.format(date, "YYYY/MM/DD HH:mm:ss");
231
- }
232
- }
233
-
234
- private isoWeek(monday: Date): { year: number, week: number }
235
- {
236
- let thursday = new Date(monday.getTime());
237
- thursday.setDate(thursday.getDate() + 3);
238
- let year = thursday.getFullYear();
239
- let firstThursday = new Date(year, 0, 4);
240
- let firstThursdayOffset = (firstThursday.getDay() + 6) % 7;
241
- firstThursday.setDate(firstThursday.getDate() - firstThursdayOffset + 3);
242
- let week = 1 + Math.round((thursday.getTime() - firstThursday.getTime()) / (7 * 86400000));
243
- return { year, week };
244
- }
245
-
246
- private getLabelParts(row: RowType, xCol: TableColumnInfo | undefined): { key: string, display: string } | null
247
- {
248
- if (this.isDateColumn(xCol))
249
- {
250
- let raw = (row as any)[xCol!.name];
251
- let date = raw !== null && raw !== undefined && raw !== "" ? new Date(raw) : null;
252
- let valid = date !== null && !isNaN(date.getTime());
253
- if (!valid)
254
- return this.state.includeUndatedRows ? { key: "__undated__", display: "(No Date)" } : null;
255
- if (this.state.dateGroup !== "exact")
256
- {
257
- let bucket = this.dateBucket(date!, this.state.dateGroup);
258
- return { key: String(bucket.getTime()), display: this.formatBucket(bucket, this.state.dateGroup) };
259
- }
260
- }
261
- return { key: this.labelOf(row, xCol), display: this.formatLabel(row, xCol) };
262
- }
263
-
264
- private getNumericColumns(): TableColumnInfo[]
265
- {
266
- return this.props.columns.filter(column => this.isNumericColumn(column));
267
- }
268
-
269
- private getColumn(name: string): TableColumnInfo | undefined
270
- {
271
- return this.props.columns.find(column => column.name === name);
272
- }
273
-
274
- private getTableTitle(): string
275
- {
276
- return this.props.columns[0]?.table?.text ?? this.props.name;
277
- }
278
-
279
- private getActiveRows(): RowType[]
280
- {
281
- return this.state.rowSource === "selected" ? this.props.selectedRows : this.props.rows;
282
- }
283
-
284
- private toNumber(value: any): number
285
- {
286
- let n = Number(value);
287
- return isFinite(n) ? n : 0;
288
- }
289
-
290
- private labelOf(row: RowType, column: TableColumnInfo | undefined): string
291
- {
292
- if (!column)
293
- return "";
294
- let value = (row as any)[column.name];
295
- if (value === null || value === undefined)
296
- return "";
297
- return String(value);
298
- }
299
-
300
- private aggregate(rows: RowType[], xCol: TableColumnInfo | undefined, yCols: TableColumnInfo[]): { labels: string[], series: number[][] }
301
- {
302
- let keys: string[] = [];
303
- let displays: string[] = [];
304
- let indexByKey = new Map<string, number>();
305
- let stats: { sum: number, count: number, min: number, max: number }[][] = yCols.map(() => []);
306
- rows.forEach(row =>
307
- {
308
- let parts = this.getLabelParts(row, xCol);
309
- if (parts === null)
310
- return;
311
- let { key, display } = parts;
312
- let index = indexByKey.get(key);
313
- if (index === undefined)
314
- {
315
- index = keys.length;
316
- indexByKey.set(key, index);
317
- keys.push(key);
318
- displays.push(display);
319
- stats.forEach(serie => serie[index as number] = { sum: 0, count: 0, min: Infinity, max: -Infinity });
320
- }
321
- yCols.forEach((column, s) =>
322
- {
323
- let raw = (row as any)[column.name];
324
- if (raw === null || raw === undefined || raw === "")
325
- return;
326
- let value = this.toNumber(raw);
327
- let cell = stats[s][index as number];
328
- cell.sum += value;
329
- cell.count += 1;
330
- cell.min = Math.min(cell.min, value);
331
- cell.max = Math.max(cell.max, value);
332
- });
333
- });
334
-
335
- let series = stats.map(serie => serie.map(cell => this.applyAggregate(cell)));
336
-
337
- let order = keys.map((_, index) => index);
338
- order.sort((a, b) => this.compareLabels(keys[a], keys[b], xCol));
339
- return {
340
- labels: order.map(index => displays[index]),
341
- series: series.map(serie => order.map(index => serie[index])),
342
- };
343
- }
344
-
345
- private formatLabel(row: RowType, column: TableColumnInfo | undefined): string
346
- {
347
- if (!column)
348
- return "";
349
- let value = (row as any)[column.name];
350
- if (value === null || value === undefined || value === "")
351
- return "";
352
- try
353
- {
354
- let out = (column.formatter as any)?.format(value);
355
- if (typeof out === "string")
356
- return out;
357
- }
358
- catch (e)
359
- {
360
- }
361
- return String(value);
362
- }
363
-
364
- private applyAggregate(cell: { sum: number, count: number, min: number, max: number }): number
365
- {
366
- switch (this.state.aggregate)
367
- {
368
- case "sum":
369
- return cell.sum;
370
- case "count":
371
- return cell.count;
372
- case "average":
373
- return cell.count ? cell.sum / cell.count : 0;
374
- case "min":
375
- return cell.count ? cell.min : 0;
376
- case "max":
377
- return cell.count ? cell.max : 0;
378
- default:
379
- return cell.sum;
380
- }
381
- }
382
-
383
- private formatValueFor(yCols: TableColumnInfo[]): (value: number) => string
384
- {
385
- let aggregate = this.state.aggregate;
386
- let formatter = yCols[0]?.formatter;
387
- return (value: number) =>
388
- {
389
- if (aggregate === "count")
390
- return Math.round(value).toLocaleString();
391
- try
392
- {
393
- let out = (formatter as any)?.format(value);
394
- if (typeof out === "string")
395
- return out;
396
- }
397
- catch (e) { }
398
- return value.toLocaleString();
399
- };
400
- }
401
-
402
- private compareLabels(a: string, b: string, xCol: TableColumnInfo | undefined): number
403
- {
404
- if (xCol && this.isNumericColumn(xCol))
405
- return this.toNumber(a) - this.toNumber(b);
406
- if (xCol && /(date|time)/.test((xCol.type ?? "").toLowerCase()))
407
- {
408
- let da = Date.parse(a);
409
- let db = Date.parse(b);
410
- if (!isNaN(da) && !isNaN(db))
411
- return da - db;
412
- }
413
- return a.localeCompare(b, undefined, { numeric: true, sensitivity: "base" });
414
- }
415
-
416
- private download()
417
- {
418
- let container = this.chartContainer.current;
419
- if (!container)
420
- return;
421
- let canvas = container.querySelector("canvas");
422
- if (!canvas)
423
- return;
424
- let link = document.createElement("a");
425
- link.href = canvas.toDataURL("image/png");
426
- link.download = this.props.name + "_chart.png";
427
- link.click();
428
- }
429
-
430
- private renderChart()
431
- {
432
- let { chartType, xColumn, yColumns } = this.state;
433
- let xCol = this.getColumn(xColumn);
434
- let yCols = yColumns.map(name => this.getColumn(name)).filter(Boolean) as TableColumnInfo[];
435
-
436
- let rows = this.getActiveRows();
437
-
438
- if (rows.length === 0)
439
- return <NSNoData lable={this.state.rowSource === "selected" ? "No selected rows" : "No Data"} />;
440
- if (yCols.length === 0)
441
- return <div className={Styles.ns_chart_message}>Select at least one numeric series for the Y-axis.</div>;
442
-
443
- let { labels, series } = this.aggregate(rows, xCol, yCols);
444
-
445
- let dataset = yCols.map((column, s) => ({ label: column.text, data: series[s] }));
446
- let formatValue = this.formatValueFor(yCols);
447
-
448
- if (chartType === "bar" || chartType === "hbar")
449
- {
450
- let mode: NSChartBarMode = yCols.length > 1 ? this.state.barMode : "grouped";
451
- let max = 0;
452
- labels.forEach((_, index) =>
453
- {
454
- if (mode === "stacked")
455
- {
456
- let sum = 0;
457
- dataset.forEach(item => sum += item.data[index] ?? 0);
458
- max = Math.max(max, sum);
459
- }
460
- else
461
- dataset.forEach(item => max = Math.max(max, item.data[index] ?? 0));
462
- });
463
- max = max <= 0 ? 1 : Math.ceil(max * 1.1);
464
- return (
465
- <NSChartBar
466
- labels={labels}
467
- dataset={dataset}
468
- range={{ min: 0, max }}
469
- mode={mode}
470
- horizontal={chartType === "hbar"}
471
- xTitle={xCol?.text}
472
- formatValue={formatValue}
473
- yGrid
474
- style={{ width: "100%" }}
475
- />
476
- );
477
- }
478
-
479
- if (chartType === "line" || chartType === "area")
480
- {
481
- let max = 0;
482
- dataset.forEach(item => item.data.forEach(value => max = Math.max(max, value)));
483
- max = max <= 0 ? 1 : Math.ceil(max * 1.1);
484
- return (
485
- <NSChartLine
486
- labels={labels}
487
- dataset={dataset}
488
- range={{ min: 0, max }}
489
- area={chartType === "area"}
490
- xTitle={xCol?.text}
491
- formatValue={formatValue}
492
- yGrid
493
- style={{ width: "100%" }}
494
- />
495
- );
496
- }
497
-
498
- let datas = series[0];
499
-
500
- return (
501
- <NSChartDoughnut
502
- title={yCols[0]?.text ?? ""}
503
- labels={labels}
504
- datas={datas}
505
- labelTitle={xCol?.text}
506
- valueTitle={yCols[0]?.text}
507
- formatValue={formatValue}
508
- style={{ width: "100%", height: "100%", display: "flex", justifyContent: "center" }}
509
- />
510
- );
511
- }
512
-
513
- override render()
514
- {
515
- let { chartType, barMode, dateGroup, xColumn, yColumns } = this.state;
516
- let numericColumns = this.getNumericColumns();
517
- let isSingleSeries = chartType === "doughnut";
518
- let xCol = this.getColumn(xColumn);
519
- let xColIsDate = this.isDateColumn(xCol);
520
- let xColIsDateTime = this.isDateTimeColumn(xCol);
521
- let dateGroupOptions = DATE_GROUPS.filter(item => item.value !== "hourly" || xColIsDateTime);
522
-
523
- let labelAxisTitle = chartType === "doughnut" ? "Category" : chartType === "hbar" ? "Label (Y-Axis)" : "Label (X-Axis)";
524
- let seriesAxisTitle = chartType === "doughnut" ? "Value" : chartType === "hbar" ? "Series (X-Axis)" : "Series (Y-Axis)";
525
-
526
- return (
527
- <div
528
- id={this.props.id}
529
- className={[Styles.ns_chart_overlay, ...(this.props.classList ?? [])].join(" ")}
530
- style={this.props.style}
531
- >
532
- <div className={Styles.ns_chart_modal}>
533
- <div className={Styles.ns_chart_header}>
534
- <span className={Styles.ns_chart_title}>
535
- Chart{this.getTableTitle() ? ` - ${this.getTableTitle()}` : ""}
536
- </span>
537
- <NSButton
538
- icon={{ src: "https://static.namirasoft.com/image/concept/close/blue.svg", alt: "close" }}
539
- onClick={{
540
- action: this.props.onClose,
541
- showLoading: false
542
- }}
543
- style={{ width: 24, height: 24, boxShadow: "none" }}
544
- />
545
- </div>
546
- <div className={Styles.ns_chart_body}>
547
- <div className={Styles.ns_chart_canvas} ref={this.chartContainer}>
548
- {this.renderChart()}
549
- </div>
550
- <NSColumn classList={[Styles.ns_chart_panel]}>
551
- <NSBoxCombo
552
- title="Chart Type"
553
- required={false}
554
- multiple={false}
555
- searchable={false}
556
- clearable={false}
557
- reloadable={false}
558
- defaultValue={chartType}
559
- getOptions={() => CHART_TYPES.map(item => ({ value: item.type, title: item.text }))}
560
- onChanged={(box) =>
561
- {
562
- let value = box.getValue(false) as string | null;
563
- if (!value)
564
- return;
565
- let nextType = value as NSTableChartType;
566
- let nextY = nextType === "doughnut" && yColumns.length > 1
567
- ? [yColumns[0]]
568
- : yColumns;
569
- this.setState({ chartType: nextType, yColumns: nextY });
570
- }}
571
- menu={{
572
- builtin: {
573
- copy: { enabled: false, getValue: () => "" },
574
- delete: { enabled: false, onDelete: () => { } },
575
- },
576
- items: []
577
- }}
578
- />
579
-
580
- {
581
- (chartType === "bar" || chartType === "hbar") && yColumns.length > 1 &&
582
- <NSBoxOptions
583
- key="bar-mode"
584
- title=""
585
- hideHeader={true}
586
- multiple={false}
587
- required={false}
588
- defaultValue={barMode}
589
- options={BAR_MODES.map(item => ({ value: item.mode, title: item.text }))}
590
- onChanged={(box) =>
591
- {
592
- let value = box.getValue(false) as string | null;
593
- if (value)
594
- this.setState({ barMode: value as NSChartBarMode });
595
- }}
596
- menu={{
597
- builtin: {
598
- copy: { enabled: false, getValue: () => "" },
599
- delete: { enabled: false, onDelete: () => { } },
600
- },
601
- items: []
602
- }}
603
- input={{ style: { minHeight: "auto", border: "none", padding: "0" } }}
604
- />
605
- }
606
-
607
- <NSBoxCombo
608
- title={labelAxisTitle}
609
- multiple={false}
610
- required={false}
611
- reloadable={false}
612
- clearable={false}
613
- defaultValue={xColumn}
614
- getOptions={() => this.props.columns.map(column => ({ value: column.name, title: column.text }))}
615
- onChanged={(box) =>
616
- {
617
- let value = box.getValue(false) as string | null;
618
- if (value)
619
- this.setState({ xColumn: value });
620
- }}
621
- menu={{
622
- builtin: {
623
- copy: { enabled: false, getValue: () => "" },
624
- delete: { enabled: false, onDelete: () => { } },
625
- },
626
- items: []
627
- }}
628
- />
629
-
630
- {
631
- xColIsDate &&
632
- <NSBoxOptions
633
- key="date-group"
634
- title=""
635
- hideHeader={true}
636
- multiple={false}
637
- required={false}
638
- defaultValue={dateGroup}
639
- options={dateGroupOptions.map(item => ({ value: item.value, title: item.text }))}
640
- onChanged={(box) =>
641
- {
642
- let value = box.getValue(false) as string | null;
643
- if (value)
644
- this.setState({ dateGroup: value as NSTableChartDateGroup });
645
- }}
646
- menu={{
647
- builtin: {
648
- copy: { enabled: false, getValue: () => "" },
649
- delete: { enabled: false, onDelete: () => { } },
650
- },
651
- items: []
652
- }}
653
- input={{ style: { minHeight: "auto", border: "none", padding: "0" } }}
654
- />
655
- }
656
-
657
- <NSBoxCombo
658
- key={isSingleSeries ? "y-single" : "y-multi"}
659
- title={seriesAxisTitle}
660
- multiple={!isSingleSeries}
661
- required={false}
662
- clearable={false}
663
- reloadable={false}
664
- defaultValue={isSingleSeries ? yColumns[0] : yColumns}
665
- getOptions={() => numericColumns.map(column => ({ value: column.name, title: column.text }))}
666
- onChanged={(box) =>
667
- {
668
- let value = box.getValue(false);
669
- let next = Array.isArray(value) ? value : (value ? [value] : []);
670
- this.setState({ yColumns: next });
671
- }}
672
- menu={{
673
- builtin: {
674
- copy: { enabled: false, getValue: () => "" },
675
- delete: { enabled: false, onDelete: () => { } },
676
- },
677
- items: []
678
- }}
679
- />
680
-
681
- <NSBoxOptions
682
- title="Aggregate"
683
- multiple={false}
684
- required={false}
685
- defaultValue={this.state.aggregate}
686
- options={AGGREGATES.map(item => ({ value: item.value, title: item.text }))}
687
- onChanged={(box) =>
688
- {
689
- let value = box.getValue(false) as string | null;
690
- if (value)
691
- this.setState({ aggregate: value as NSTableChartAggregate });
692
- }}
693
- menu={{
694
- builtin: {
695
- copy: { enabled: false, getValue: () => "" },
696
- delete: { enabled: false, onDelete: () => { } },
697
- },
698
- items: []
699
- }}
700
- input={{ style: { border: "none", padding: "8px 0 0" } }}
701
- />
702
-
703
- {
704
- numericColumns.length === 0 &&
705
- <div className={Styles.ns_chart_hint}>No numeric columns available.</div>
706
- }
707
-
708
- {
709
- isSingleSeries &&
710
- <div className={Styles.ns_chart_hint}>Doughnut charts use a single series.</div>
711
- }
712
-
713
- <NSBoxBoolean
714
- title="Only selected rows "
715
- required={false}
716
- defaultValue={false}
717
- hideHeader={true}
718
- onChanged={(box) =>
719
- {
720
- let nextSource: NSTableChartRowSource = box.getValue(false) === true ? "selected" : "page";
721
- if (nextSource !== this.state.rowSource)
722
- this.setState({ rowSource: nextSource });
723
- }}
724
- />
725
-
726
- {
727
- xColIsDate &&
728
- <NSBoxBoolean
729
- key="include-undated"
730
- title="Include rows without a date"
731
- required={false}
732
- defaultValue={this.state.includeUndatedRows}
733
- hideHeader={true}
734
- onChanged={(box) =>
735
- {
736
- let next = box.getValue(false) === true;
737
- if (next !== this.state.includeUndatedRows)
738
- this.setState({ includeUndatedRows: next });
739
- }}
740
- />
741
- }
742
-
743
- <NSButtonBlue
744
- title="Download PNG"
745
- onClick={{
746
- action: (onFinished) =>
747
- {
748
- this.download();
749
- onFinished();
750
- },
751
- showLoading: false,
752
- }}
753
- style={{ minHeight: 48, marginTop: "auto" }}
754
- />
755
- </NSColumn>
756
- </div>
757
- </div>
758
- </div>
759
- );
760
- }
1
+ "use client";
2
+ import { IStorageLocal, TimeOperation } from "namirasoft-core";
3
+ import { Component, createRef } from "react";
4
+ import { IBaseComponentProps } from "../props/IBaseComponentProps";
5
+ import { NSBoxBoolean } from "./NSBoxBoolean";
6
+ import { NSBoxCombo } from "./NSBoxCombo";
7
+ import { NSBoxOptions } from "./NSBoxOptions";
8
+ import { NSButton } from "./NSButton";
9
+ import { NSButtonBlue } from "./NSButtonBlue";
10
+ import { NSChartBar, NSChartBarMode } from "./NSChartBar";
11
+ import { NSChartDoughnut } from "./NSChartDoughnut";
12
+ import { NSChartLine } from "./NSChartLine";
13
+ import { NSColumn } from "./NSColumn";
14
+ import { NSNoData } from "./NSNoData";
15
+ import { TableColumnInfo } from "./NSTable";
16
+ import Styles from "./NSTableChart.module.css";
17
+
18
+ export type NSTableChartType = "bar" | "hbar" | "line" | "area" | "doughnut";
19
+ export type NSTableChartRowSource = "page" | "selected";
20
+ export type NSTableChartAggregate = "sum" | "average" | "count" | "min" | "max";
21
+ export type NSTableChartDateGroup = "exact" | "hourly" | "daily" | "weekly" | "monthly" | "yearly";
22
+
23
+ const CHART_TYPES: { type: NSTableChartType, text: string }[] = [
24
+ { type: "bar", text: "Bar" },
25
+ { type: "hbar", text: "Horizontal Bar" },
26
+ { type: "line", text: "Line" },
27
+ { type: "area", text: "Area" },
28
+ { type: "doughnut", text: "Doughnut" },
29
+ ];
30
+
31
+ const BAR_MODES: { mode: NSChartBarMode, text: string }[] = [
32
+ { mode: "grouped", text: "Grouped" },
33
+ { mode: "stacked", text: "Stacked" },
34
+ { mode: "percent", text: "Percent" },
35
+ ];
36
+
37
+ const AGGREGATES: { value: NSTableChartAggregate, text: string }[] = [
38
+ { value: "sum", text: "Sum" },
39
+ { value: "average", text: "Average" },
40
+ { value: "count", text: "Count" },
41
+ { value: "min", text: "Min" },
42
+ { value: "max", text: "Max" },
43
+ ];
44
+
45
+ const DATE_GROUPS: { value: NSTableChartDateGroup, text: string }[] = [
46
+ { value: "exact", text: "Exact" },
47
+ { value: "hourly", text: "Hourly" },
48
+ { value: "daily", text: "Daily" },
49
+ { value: "weekly", text: "Weekly" },
50
+ { value: "monthly", text: "Monthly" },
51
+ { value: "yearly", text: "Yearly" },
52
+ ];
53
+
54
+ const NUMERIC_TYPES = ["number", "integer", "int", "float", "double", "decimal", "long", "money", "percent", "percentage", "bytes", "duration"];
55
+
56
+ export interface NSTableChartProps<RowType> extends IBaseComponentProps
57
+ {
58
+ name: string;
59
+ columns: TableColumnInfo[];
60
+ rows: RowType[];
61
+ selectedRows: RowType[];
62
+ onClose: () => void;
63
+ }
64
+
65
+ interface NSTableChartState
66
+ {
67
+ chartType: NSTableChartType;
68
+ barMode: NSChartBarMode;
69
+ aggregate: NSTableChartAggregate;
70
+ dateGroup: NSTableChartDateGroup;
71
+ rowSource: NSTableChartRowSource;
72
+ xColumn: string;
73
+ yColumns: string[];
74
+ includeUndatedRows: boolean;
75
+ }
76
+
77
+ interface NSTableChartSettings
78
+ {
79
+ chartType?: NSTableChartType;
80
+ barMode?: NSChartBarMode;
81
+ aggregate?: NSTableChartAggregate;
82
+ dateGroup?: NSTableChartDateGroup;
83
+ rowSource?: NSTableChartRowSource;
84
+ xColumn?: string;
85
+ yColumns?: string[];
86
+ includeUndatedRows?: boolean;
87
+ }
88
+
89
+ export class NSTableChart<RowType> extends Component<NSTableChartProps<RowType>, NSTableChartState>
90
+ {
91
+ private chartContainer = createRef<HTMLDivElement>();
92
+ private previousBodyOverflow = "";
93
+ private Storage = new IStorageLocal();
94
+
95
+ constructor(props: NSTableChartProps<RowType>)
96
+ {
97
+ super(props);
98
+ let numeric = this.getNumericColumns();
99
+ let nonNumeric = props.columns.filter(column => !this.isNumericColumn(column));
100
+ let defaultX = (nonNumeric[0] ?? props.columns[0])?.name ?? "";
101
+ let defaultY = numeric.length > 0 ? [numeric[0].name] : [];
102
+
103
+ let saved = this.loadSettings();
104
+ let chartType: NSTableChartType = CHART_TYPES.some(item => item.type === saved?.chartType) ? saved!.chartType! : "bar";
105
+ let barMode: NSChartBarMode = BAR_MODES.some(item => item.mode === saved?.barMode) ? saved!.barMode! : "grouped";
106
+ let aggregate: NSTableChartAggregate = AGGREGATES.some(item => item.value === saved?.aggregate) ? saved!.aggregate! : "sum";
107
+ let dateGroup: NSTableChartDateGroup = DATE_GROUPS.some(item => item.value === saved?.dateGroup) ? saved!.dateGroup! : "exact";
108
+ let rowSource: NSTableChartRowSource = saved?.rowSource === "selected" ? "selected" : "page";
109
+ let xColumn = (saved?.xColumn && this.getColumn(saved.xColumn)) ? saved.xColumn : defaultX;
110
+ let savedY = (saved?.yColumns ?? []).filter((name: string) => numeric.some(column => column.name === name));
111
+ let yColumns = savedY.length > 0 ? savedY : defaultY;
112
+ if (chartType === "doughnut" && yColumns.length > 1)
113
+ yColumns = [yColumns[0]];
114
+
115
+ let includeUndatedRows = typeof saved?.includeUndatedRows === "boolean" ? saved.includeUndatedRows : true;
116
+
117
+ this.state = {
118
+ chartType,
119
+ barMode,
120
+ aggregate,
121
+ dateGroup,
122
+ rowSource,
123
+ xColumn,
124
+ yColumns,
125
+ includeUndatedRows,
126
+ };
127
+ this.download = this.download.bind(this);
128
+ }
129
+
130
+ override componentDidMount()
131
+ {
132
+ this.previousBodyOverflow = document.body.style.overflow;
133
+ document.body.style.overflow = "hidden";
134
+ }
135
+
136
+ override componentWillUnmount()
137
+ {
138
+ document.body.style.overflow = this.previousBodyOverflow;
139
+ }
140
+
141
+ override componentDidUpdate(_prevProps: NSTableChartProps<RowType>, prevState: NSTableChartState)
142
+ {
143
+ if (
144
+ prevState.chartType !== this.state.chartType ||
145
+ prevState.barMode !== this.state.barMode ||
146
+ prevState.aggregate !== this.state.aggregate ||
147
+ prevState.dateGroup !== this.state.dateGroup ||
148
+ prevState.rowSource !== this.state.rowSource ||
149
+ prevState.xColumn !== this.state.xColumn ||
150
+ prevState.yColumns !== this.state.yColumns ||
151
+ prevState.includeUndatedRows !== this.state.includeUndatedRows
152
+ )
153
+ this.saveSettings();
154
+ }
155
+
156
+ private settingsKey(): string
157
+ {
158
+ return `ns_table_chart_${this.props.name}`;
159
+ }
160
+
161
+ private loadSettings(): NSTableChartSettings | null
162
+ {
163
+ try
164
+ {
165
+ let raw = this.Storage.get(this.settingsKey(), "");
166
+ return raw ? JSON.parse(raw) as NSTableChartSettings : null;
167
+ }
168
+ catch (e)
169
+ {
170
+ return null;
171
+ }
172
+ }
173
+
174
+ private saveSettings(): void
175
+ {
176
+ try
177
+ {
178
+ let { chartType, barMode, aggregate, dateGroup, rowSource, xColumn, yColumns, includeUndatedRows } = this.state;
179
+ this.Storage.set(this.settingsKey(), JSON.stringify({ chartType, barMode, aggregate, dateGroup, rowSource, xColumn, yColumns, includeUndatedRows }));
180
+ }
181
+ catch (e) { }
182
+ }
183
+
184
+ private isNumericColumn(column: TableColumnInfo): boolean
185
+ {
186
+ return NUMERIC_TYPES.includes((column.type ?? "").toLowerCase());
187
+ }
188
+
189
+ private isDateColumn(column: TableColumnInfo | undefined): boolean
190
+ {
191
+ return !!column && /date/.test((column.type ?? "").toLowerCase());
192
+ }
193
+
194
+ private isDateTimeColumn(column: TableColumnInfo | undefined): boolean
195
+ {
196
+ return !!column && (column.type ?? "").toLowerCase().includes("datetime");
197
+ }
198
+
199
+ private dateBucket(date: Date, group: NSTableChartDateGroup): Date
200
+ {
201
+ switch (group)
202
+ {
203
+ case "hourly": return TimeOperation.beginningOfHour(date);
204
+ case "daily": return TimeOperation.beginningOfDay(date);
205
+ case "weekly":
206
+ {
207
+ let start = TimeOperation.beginningOfDay(date);
208
+ let offset = (start.getDay() + 6) % 7;
209
+ return TimeOperation.daysAgo(offset, start);
210
+ }
211
+ case "monthly": return TimeOperation.beginningOfMonth(date);
212
+ case "yearly": return TimeOperation.beginningOfYear(date);
213
+ default: return date;
214
+ }
215
+ }
216
+
217
+ private formatBucket(date: Date, group: NSTableChartDateGroup): string
218
+ {
219
+ switch (group)
220
+ {
221
+ case "hourly": return TimeOperation.format(date, "YYYY/MM/DD HH:00");
222
+ case "daily": return TimeOperation.format(date, "YYYY/MM/DD");
223
+ case "weekly":
224
+ {
225
+ let { year, week } = this.isoWeek(date);
226
+ return `${year}-W${String(week).padStart(2, "0")}`;
227
+ }
228
+ case "monthly": return TimeOperation.format(date, "YYYY/MM");
229
+ case "yearly": return TimeOperation.format(date, "YYYY");
230
+ default: return TimeOperation.format(date, "YYYY/MM/DD HH:mm:ss");
231
+ }
232
+ }
233
+
234
+ private isoWeek(monday: Date): { year: number, week: number }
235
+ {
236
+ let thursday = new Date(monday.getTime());
237
+ thursday.setDate(thursday.getDate() + 3);
238
+ let year = thursday.getFullYear();
239
+ let firstThursday = new Date(year, 0, 4);
240
+ let firstThursdayOffset = (firstThursday.getDay() + 6) % 7;
241
+ firstThursday.setDate(firstThursday.getDate() - firstThursdayOffset + 3);
242
+ let week = 1 + Math.round((thursday.getTime() - firstThursday.getTime()) / (7 * 86400000));
243
+ return { year, week };
244
+ }
245
+
246
+ private getLabelParts(row: RowType, xCol: TableColumnInfo | undefined): { key: string, display: string } | null
247
+ {
248
+ if (this.isDateColumn(xCol))
249
+ {
250
+ let raw = (row as any)[xCol!.name];
251
+ let date = raw !== null && raw !== undefined && raw !== "" ? new Date(raw) : null;
252
+ let valid = date !== null && !isNaN(date.getTime());
253
+ if (!valid)
254
+ return this.state.includeUndatedRows ? { key: "__undated__", display: "(No Date)" } : null;
255
+ if (this.state.dateGroup !== "exact")
256
+ {
257
+ let bucket = this.dateBucket(date!, this.state.dateGroup);
258
+ return { key: String(bucket.getTime()), display: this.formatBucket(bucket, this.state.dateGroup) };
259
+ }
260
+ }
261
+ return { key: this.labelOf(row, xCol), display: this.formatLabel(row, xCol) };
262
+ }
263
+
264
+ private getNumericColumns(): TableColumnInfo[]
265
+ {
266
+ return this.props.columns.filter(column => this.isNumericColumn(column));
267
+ }
268
+
269
+ private getColumn(name: string): TableColumnInfo | undefined
270
+ {
271
+ return this.props.columns.find(column => column.name === name);
272
+ }
273
+
274
+ private getTableTitle(): string
275
+ {
276
+ return this.props.columns[0]?.table?.text ?? this.props.name;
277
+ }
278
+
279
+ private getActiveRows(): RowType[]
280
+ {
281
+ return this.state.rowSource === "selected" ? this.props.selectedRows : this.props.rows;
282
+ }
283
+
284
+ private toNumber(value: any): number
285
+ {
286
+ let n = Number(value);
287
+ return isFinite(n) ? n : 0;
288
+ }
289
+
290
+ private labelOf(row: RowType, column: TableColumnInfo | undefined): string
291
+ {
292
+ if (!column)
293
+ return "";
294
+ let value = (row as any)[column.name];
295
+ if (value === null || value === undefined)
296
+ return "";
297
+ return String(value);
298
+ }
299
+
300
+ private aggregate(rows: RowType[], xCol: TableColumnInfo | undefined, yCols: TableColumnInfo[]): { labels: string[], series: number[][] }
301
+ {
302
+ let keys: string[] = [];
303
+ let displays: string[] = [];
304
+ let indexByKey = new Map<string, number>();
305
+ let stats: { sum: number, count: number, min: number, max: number }[][] = yCols.map(() => []);
306
+ rows.forEach(row =>
307
+ {
308
+ let parts = this.getLabelParts(row, xCol);
309
+ if (parts === null)
310
+ return;
311
+ let { key, display } = parts;
312
+ let index = indexByKey.get(key);
313
+ if (index === undefined)
314
+ {
315
+ index = keys.length;
316
+ indexByKey.set(key, index);
317
+ keys.push(key);
318
+ displays.push(display);
319
+ stats.forEach(serie => serie[index as number] = { sum: 0, count: 0, min: Infinity, max: -Infinity });
320
+ }
321
+ yCols.forEach((column, s) =>
322
+ {
323
+ let raw = (row as any)[column.name];
324
+ if (raw === null || raw === undefined || raw === "")
325
+ return;
326
+ let value = this.toNumber(raw);
327
+ let cell = stats[s][index as number];
328
+ cell.sum += value;
329
+ cell.count += 1;
330
+ cell.min = Math.min(cell.min, value);
331
+ cell.max = Math.max(cell.max, value);
332
+ });
333
+ });
334
+
335
+ let series = stats.map(serie => serie.map(cell => this.applyAggregate(cell)));
336
+
337
+ let order = keys.map((_, index) => index);
338
+ order.sort((a, b) => this.compareLabels(keys[a], keys[b], xCol));
339
+ return {
340
+ labels: order.map(index => displays[index]),
341
+ series: series.map(serie => order.map(index => serie[index])),
342
+ };
343
+ }
344
+
345
+ private formatLabel(row: RowType, column: TableColumnInfo | undefined): string
346
+ {
347
+ if (!column)
348
+ return "";
349
+ let value = (row as any)[column.name];
350
+ if (value === null || value === undefined || value === "")
351
+ return "";
352
+ try
353
+ {
354
+ let out = (column.formatter as any)?.format(value);
355
+ if (typeof out === "string")
356
+ return out;
357
+ }
358
+ catch (e)
359
+ {
360
+ }
361
+ return String(value);
362
+ }
363
+
364
+ private applyAggregate(cell: { sum: number, count: number, min: number, max: number }): number
365
+ {
366
+ switch (this.state.aggregate)
367
+ {
368
+ case "sum":
369
+ return cell.sum;
370
+ case "count":
371
+ return cell.count;
372
+ case "average":
373
+ return cell.count ? cell.sum / cell.count : 0;
374
+ case "min":
375
+ return cell.count ? cell.min : 0;
376
+ case "max":
377
+ return cell.count ? cell.max : 0;
378
+ default:
379
+ return cell.sum;
380
+ }
381
+ }
382
+
383
+ private formatValueFor(yCols: TableColumnInfo[]): (value: number) => string
384
+ {
385
+ let aggregate = this.state.aggregate;
386
+ let formatter = yCols[0]?.formatter;
387
+ return (value: number) =>
388
+ {
389
+ if (aggregate === "count")
390
+ return Math.round(value).toLocaleString();
391
+ try
392
+ {
393
+ let out = (formatter as any)?.format(value);
394
+ if (typeof out === "string")
395
+ return out;
396
+ }
397
+ catch (e) { }
398
+ return value.toLocaleString();
399
+ };
400
+ }
401
+
402
+ private compareLabels(a: string, b: string, xCol: TableColumnInfo | undefined): number
403
+ {
404
+ if (xCol && this.isNumericColumn(xCol))
405
+ return this.toNumber(a) - this.toNumber(b);
406
+ if (xCol && /(date|time)/.test((xCol.type ?? "").toLowerCase()))
407
+ {
408
+ let da = Date.parse(a);
409
+ let db = Date.parse(b);
410
+ if (!isNaN(da) && !isNaN(db))
411
+ return da - db;
412
+ }
413
+ return a.localeCompare(b, undefined, { numeric: true, sensitivity: "base" });
414
+ }
415
+
416
+ private download()
417
+ {
418
+ let container = this.chartContainer.current;
419
+ if (!container)
420
+ return;
421
+ let canvas = container.querySelector("canvas");
422
+ if (!canvas)
423
+ return;
424
+ let link = document.createElement("a");
425
+ link.href = canvas.toDataURL("image/png");
426
+ link.download = this.props.name + "_chart.png";
427
+ link.click();
428
+ }
429
+
430
+ private renderChart()
431
+ {
432
+ let { chartType, xColumn, yColumns } = this.state;
433
+ let xCol = this.getColumn(xColumn);
434
+ let yCols = yColumns.map(name => this.getColumn(name)).filter(Boolean) as TableColumnInfo[];
435
+
436
+ let rows = this.getActiveRows();
437
+
438
+ if (rows.length === 0)
439
+ return <NSNoData lable={this.state.rowSource === "selected" ? "No selected rows" : "No Data"} />;
440
+ if (yCols.length === 0)
441
+ return <div className={Styles.ns_chart_message}>Select at least one numeric series for the Y-axis.</div>;
442
+
443
+ let { labels, series } = this.aggregate(rows, xCol, yCols);
444
+
445
+ let dataset = yCols.map((column, s) => ({ label: column.text, data: series[s] }));
446
+ let formatValue = this.formatValueFor(yCols);
447
+
448
+ if (chartType === "bar" || chartType === "hbar")
449
+ {
450
+ let mode: NSChartBarMode = yCols.length > 1 ? this.state.barMode : "grouped";
451
+ let max = 0;
452
+ labels.forEach((_, index) =>
453
+ {
454
+ if (mode === "stacked")
455
+ {
456
+ let sum = 0;
457
+ dataset.forEach(item => sum += item.data[index] ?? 0);
458
+ max = Math.max(max, sum);
459
+ }
460
+ else
461
+ dataset.forEach(item => max = Math.max(max, item.data[index] ?? 0));
462
+ });
463
+ max = max <= 0 ? 1 : Math.ceil(max * 1.1);
464
+ return (
465
+ <NSChartBar
466
+ labels={labels}
467
+ dataset={dataset}
468
+ range={{ min: 0, max }}
469
+ mode={mode}
470
+ horizontal={chartType === "hbar"}
471
+ xTitle={xCol?.text}
472
+ formatValue={formatValue}
473
+ yGrid
474
+ style={{ width: "100%" }}
475
+ />
476
+ );
477
+ }
478
+
479
+ if (chartType === "line" || chartType === "area")
480
+ {
481
+ let max = 0;
482
+ dataset.forEach(item => item.data.forEach(value => max = Math.max(max, value)));
483
+ max = max <= 0 ? 1 : Math.ceil(max * 1.1);
484
+ return (
485
+ <NSChartLine
486
+ labels={labels}
487
+ dataset={dataset}
488
+ range={{ min: 0, max }}
489
+ area={chartType === "area"}
490
+ xTitle={xCol?.text}
491
+ formatValue={formatValue}
492
+ yGrid
493
+ style={{ width: "100%" }}
494
+ />
495
+ );
496
+ }
497
+
498
+ let datas = series[0];
499
+
500
+ return (
501
+ <NSChartDoughnut
502
+ title={yCols[0]?.text ?? ""}
503
+ labels={labels}
504
+ datas={datas}
505
+ labelTitle={xCol?.text}
506
+ valueTitle={yCols[0]?.text}
507
+ formatValue={formatValue}
508
+ style={{ width: "100%", height: "100%", display: "flex", justifyContent: "center" }}
509
+ />
510
+ );
511
+ }
512
+
513
+ override render()
514
+ {
515
+ let { chartType, barMode, dateGroup, xColumn, yColumns } = this.state;
516
+ let numericColumns = this.getNumericColumns();
517
+ let isSingleSeries = chartType === "doughnut";
518
+ let xCol = this.getColumn(xColumn);
519
+ let xColIsDate = this.isDateColumn(xCol);
520
+ let xColIsDateTime = this.isDateTimeColumn(xCol);
521
+ let dateGroupOptions = DATE_GROUPS.filter(item => item.value !== "hourly" || xColIsDateTime);
522
+
523
+ let labelAxisTitle = chartType === "doughnut" ? "Category" : chartType === "hbar" ? "Label (Y-Axis)" : "Label (X-Axis)";
524
+ let seriesAxisTitle = chartType === "doughnut" ? "Value" : chartType === "hbar" ? "Series (X-Axis)" : "Series (Y-Axis)";
525
+
526
+ return (
527
+ <div
528
+ id={this.props.id}
529
+ className={[Styles.ns_chart_overlay, ...(this.props.classList ?? [])].join(" ")}
530
+ style={this.props.style}
531
+ >
532
+ <div className={Styles.ns_chart_modal}>
533
+ <div className={Styles.ns_chart_header}>
534
+ <span className={Styles.ns_chart_title}>
535
+ Chart{this.getTableTitle() ? ` - ${this.getTableTitle()}` : ""}
536
+ </span>
537
+ <NSButton
538
+ icon={{ src: "https://static.namirasoft.com/image/concept/close/blue.svg", alt: "close" }}
539
+ onClick={{
540
+ action: this.props.onClose,
541
+ showLoading: false
542
+ }}
543
+ style={{ width: 24, height: 24, boxShadow: "none" }}
544
+ />
545
+ </div>
546
+ <div className={Styles.ns_chart_body}>
547
+ <div className={Styles.ns_chart_canvas} ref={this.chartContainer}>
548
+ {this.renderChart()}
549
+ </div>
550
+ <NSColumn classList={[Styles.ns_chart_panel]}>
551
+ <NSBoxCombo
552
+ title="Chart Type"
553
+ required={false}
554
+ multiple={false}
555
+ searchable={false}
556
+ clearable={false}
557
+ reloadable={false}
558
+ defaultValue={chartType}
559
+ getOptions={() => CHART_TYPES.map(item => ({ value: item.type, title: item.text }))}
560
+ onChanged={(box) =>
561
+ {
562
+ let value = box.getValue(false) as string | null;
563
+ if (!value)
564
+ return;
565
+ let nextType = value as NSTableChartType;
566
+ let nextY = nextType === "doughnut" && yColumns.length > 1
567
+ ? [yColumns[0]]
568
+ : yColumns;
569
+ this.setState({ chartType: nextType, yColumns: nextY });
570
+ }}
571
+ menu={{
572
+ builtin: {
573
+ copy: { enabled: false, getValue: () => "" },
574
+ delete: { enabled: false, onDelete: () => { } },
575
+ },
576
+ items: []
577
+ }}
578
+ />
579
+
580
+ {
581
+ (chartType === "bar" || chartType === "hbar") && yColumns.length > 1 &&
582
+ <NSBoxOptions
583
+ key="bar-mode"
584
+ title=""
585
+ hideHeader={true}
586
+ multiple={false}
587
+ required={false}
588
+ defaultValue={barMode}
589
+ options={BAR_MODES.map(item => ({ value: item.mode, title: item.text }))}
590
+ onChanged={(box) =>
591
+ {
592
+ let value = box.getValue(false) as string | null;
593
+ if (value)
594
+ this.setState({ barMode: value as NSChartBarMode });
595
+ }}
596
+ menu={{
597
+ builtin: {
598
+ copy: { enabled: false, getValue: () => "" },
599
+ delete: { enabled: false, onDelete: () => { } },
600
+ },
601
+ items: []
602
+ }}
603
+ input={{ style: { minHeight: "auto", border: "none", padding: "0" } }}
604
+ />
605
+ }
606
+
607
+ <NSBoxCombo
608
+ title={labelAxisTitle}
609
+ multiple={false}
610
+ required={false}
611
+ reloadable={false}
612
+ clearable={false}
613
+ defaultValue={xColumn}
614
+ getOptions={() => this.props.columns.map(column => ({ value: column.name, title: column.text }))}
615
+ onChanged={(box) =>
616
+ {
617
+ let value = box.getValue(false) as string | null;
618
+ if (value)
619
+ this.setState({ xColumn: value });
620
+ }}
621
+ menu={{
622
+ builtin: {
623
+ copy: { enabled: false, getValue: () => "" },
624
+ delete: { enabled: false, onDelete: () => { } },
625
+ },
626
+ items: []
627
+ }}
628
+ />
629
+
630
+ {
631
+ xColIsDate &&
632
+ <NSBoxOptions
633
+ key="date-group"
634
+ title=""
635
+ hideHeader={true}
636
+ multiple={false}
637
+ required={false}
638
+ defaultValue={dateGroup}
639
+ options={dateGroupOptions.map(item => ({ value: item.value, title: item.text }))}
640
+ onChanged={(box) =>
641
+ {
642
+ let value = box.getValue(false) as string | null;
643
+ if (value)
644
+ this.setState({ dateGroup: value as NSTableChartDateGroup });
645
+ }}
646
+ menu={{
647
+ builtin: {
648
+ copy: { enabled: false, getValue: () => "" },
649
+ delete: { enabled: false, onDelete: () => { } },
650
+ },
651
+ items: []
652
+ }}
653
+ input={{ style: { minHeight: "auto", border: "none", padding: "0" } }}
654
+ />
655
+ }
656
+
657
+ <NSBoxCombo
658
+ key={isSingleSeries ? "y-single" : "y-multi"}
659
+ title={seriesAxisTitle}
660
+ multiple={!isSingleSeries}
661
+ required={false}
662
+ clearable={false}
663
+ reloadable={false}
664
+ defaultValue={isSingleSeries ? yColumns[0] : yColumns}
665
+ getOptions={() => numericColumns.map(column => ({ value: column.name, title: column.text }))}
666
+ onChanged={(box) =>
667
+ {
668
+ let value = box.getValue(false);
669
+ let next = Array.isArray(value) ? value : (value ? [value] : []);
670
+ this.setState({ yColumns: next });
671
+ }}
672
+ menu={{
673
+ builtin: {
674
+ copy: { enabled: false, getValue: () => "" },
675
+ delete: { enabled: false, onDelete: () => { } },
676
+ },
677
+ items: []
678
+ }}
679
+ />
680
+
681
+ <NSBoxOptions
682
+ title="Aggregate"
683
+ multiple={false}
684
+ required={false}
685
+ defaultValue={this.state.aggregate}
686
+ options={AGGREGATES.map(item => ({ value: item.value, title: item.text }))}
687
+ onChanged={(box) =>
688
+ {
689
+ let value = box.getValue(false) as string | null;
690
+ if (value)
691
+ this.setState({ aggregate: value as NSTableChartAggregate });
692
+ }}
693
+ menu={{
694
+ builtin: {
695
+ copy: { enabled: false, getValue: () => "" },
696
+ delete: { enabled: false, onDelete: () => { } },
697
+ },
698
+ items: []
699
+ }}
700
+ input={{ style: { border: "none", padding: "8px 0 0" } }}
701
+ />
702
+
703
+ {
704
+ numericColumns.length === 0 &&
705
+ <div className={Styles.ns_chart_hint}>No numeric columns available.</div>
706
+ }
707
+
708
+ {
709
+ isSingleSeries &&
710
+ <div className={Styles.ns_chart_hint}>Doughnut charts use a single series.</div>
711
+ }
712
+
713
+ <NSBoxBoolean
714
+ title="Only selected rows "
715
+ required={false}
716
+ defaultValue={false}
717
+ hideHeader={true}
718
+ onChanged={(box) =>
719
+ {
720
+ let nextSource: NSTableChartRowSource = box.getValue(false) === true ? "selected" : "page";
721
+ if (nextSource !== this.state.rowSource)
722
+ this.setState({ rowSource: nextSource });
723
+ }}
724
+ />
725
+
726
+ {
727
+ xColIsDate &&
728
+ <NSBoxBoolean
729
+ key="include-undated"
730
+ title="Include rows without a date"
731
+ required={false}
732
+ defaultValue={this.state.includeUndatedRows}
733
+ hideHeader={true}
734
+ onChanged={(box) =>
735
+ {
736
+ let next = box.getValue(false) === true;
737
+ if (next !== this.state.includeUndatedRows)
738
+ this.setState({ includeUndatedRows: next });
739
+ }}
740
+ />
741
+ }
742
+
743
+ <NSButtonBlue
744
+ title="Download PNG"
745
+ onClick={{
746
+ action: (onFinished) =>
747
+ {
748
+ this.download();
749
+ onFinished();
750
+ },
751
+ showLoading: false,
752
+ }}
753
+ style={{ minHeight: 48, marginTop: "auto" }}
754
+ />
755
+ </NSColumn>
756
+ </div>
757
+ </div>
758
+ </div>
759
+ );
760
+ }
761
761
  }