namirasoft-site-react 1.5.22 → 1.5.23

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