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,1452 +1,1452 @@
1
- "use client";
2
- import { BaseMetaColumn, BaseMetaTable, IStorageLocal, SortItem } from "namirasoft-core";
3
- import { Component, createRef, ReactNode } from "react";
4
- import { utils, writeFile } from 'xlsx';
5
- import { BaseColumnFormatter } from '../formatter/BaseColumnFormatter';
6
- import { IBaseComponentProps } from "../props/IBaseComponentProps";
7
- import { NSButton, NSButtonProps } from './NSButton';
8
- import { NSDialogInfo } from './NSDialogInfo';
9
- import { NSDialogPageSelection } from './NSDialogPageSelection';
10
- import { NSLoading } from './NSLoading';
11
- import { NSNoData } from './NSNoData';
12
- import { NSPagination } from './NSPagination';
13
- import { NSReorderDialog } from './NSReorderDialog';
14
- import { NSSortDialog } from './NSSortDialog';
15
- import { NSTableChart } from './NSTableChart';
16
- import Styles from './NSTable.module.css';
17
-
18
- export interface TableInfo
19
- {
20
- name: string;
21
- text: string;
22
- }
23
-
24
- export interface TableColumnInfo
25
- {
26
- table: TableInfo;
27
- index: number;
28
- type: string;
29
- name: string;
30
- text: string;
31
- formatter: BaseColumnFormatter;
32
- group?: string;
33
- defaults?: {
34
- hidden?: boolean;
35
- };
36
- }
37
-
38
- export interface TableRowInfo<RowType>
39
- {
40
- index: number;
41
- value: RowType;
42
- }
43
-
44
- export interface TableCellInfo<RowType>
45
- {
46
- column: TableColumnInfo;
47
- row: TableRowInfo<RowType>;
48
- value: any;
49
- formatted: any;
50
- }
51
-
52
- interface PrintableRow<RowType>
53
- {
54
- value: RowType;
55
- index: number;
56
- depth: number;
57
- ancestorHasNext: boolean[];
58
- isLastChild: boolean;
59
- }
60
-
61
- export interface NSTableProps<RowType> extends IBaseComponentProps
62
- {
63
- name: string;
64
- checkbox: boolean;
65
- columns: TableColumnInfo[];
66
- getRows: (page: number, size: number, sorts: SortItem[]) => Promise<{ rows: RowType[], count: number }>;
67
- getRowKey: (row: TableRowInfo<RowType>) => string;
68
- getRowAttributes?: (row_value: RowType, row_index: number) => { [key: string]: any };
69
- getColumnAttributes?: (column: TableColumnInfo) => { [key: string]: any };
70
- getCellFormattedValue?: (value: any, column: TableColumnInfo, row: TableRowInfo<RowType>, printable: boolean) => any;
71
- onColumnClick?: (e: React.MouseEvent<HTMLTableCellElement, MouseEvent>, column: TableColumnInfo) => void;
72
- onRowClick?: (e: React.MouseEvent<HTMLTableRowElement, MouseEvent>, row: TableRowInfo<RowType>) => void;
73
- onCellClick?: (e: React.MouseEvent<HTMLTableCellElement, MouseEvent>, cell: TableCellInfo<RowType>) => boolean | undefined;
74
- getRowCheckboxDisabled?: (row: TableRowInfo<RowType>) => boolean;
75
- buttons?: NSButtonProps[];
76
- children?: ReactNode;
77
- headerChildren?: ReactNode;
78
- hierarchy?: { id: string, parent: string, group?: string[] };
79
- defaultExpanded?: boolean;
80
- onSelectedIDsChanged?: () => void;
81
- onSortChanged?: (items: SortItem[] | null) => void;
82
- onVisibleColumnsChanged?: (visible_columns: { table: string, column: string }[]) => void;
83
- sort?: NSTableSortConfig;
84
- ui?: {
85
- pagination?: boolean;
86
- button_panel?: boolean | NSTableButtonPanelConfig;
87
- grow?: boolean;
88
- };
89
- }
90
-
91
- export interface NSTableSortConfig
92
- {
93
- enabled?: boolean;
94
- onSort?: (items: SortItem[] | null) => void;
95
- }
96
-
97
- export type NSTableButtonKey = "reorder" | "sort" | "fullscreen" | "chart" | "export" | "print" | "refresh" | "expand";
98
- export type NSTableButtonPanelConfig = { [key in NSTableButtonKey]?: boolean };
99
-
100
- interface NSTableState<RowType>
101
- {
102
- columns: TableColumnInfo[];
103
- visible_columns: { table: string, column: string }[];
104
- totalItems: number;
105
- rows: RowType[] | null;
106
- title?: string;
107
- model: {
108
- show: boolean;
109
- description?: string;
110
- };
111
- selectedIDs: string[];
112
- selectedRows: RowType[];
113
- expandedIDs: string[];
114
- pageSelectionMode: PageSelectionMode;
115
- sortItems: SortItem[] | null;
116
- columnWidths: { [key: string]: number };
117
- fullscreen: boolean;
118
- showChart: boolean;
119
- showSortDialog: boolean;
120
- showReorderDialog: boolean;
121
- }
122
-
123
- enum PageSelectionMode
124
- {
125
- Hidden = "Hidden",
126
- Print = "Print",
127
- CSV = "CSV"
128
- }
129
-
130
- export class NSTable<RowType> extends Component<NSTableProps<RowType>, NSTableState<RowType>>
131
- {
132
- private Table_Ref = createRef<HTMLTableElement>();
133
- NSPagination = createRef<NSPagination>();
134
-
135
- private LastSelectedRowID: number | null = null;
136
- private resizeMoveHandler: ((e: MouseEvent) => void) | null = null;
137
- private resizeUpHandler: (() => void) | null = null;
138
-
139
- constructor(props: NSTableProps<RowType>)
140
- {
141
- super(props);
142
- this.state = {
143
- columns: props.columns,
144
- visible_columns: this.getVisibleColumns(),
145
- rows: null,
146
- totalItems: 0,
147
- model: { show: false },
148
- selectedIDs: [],
149
- selectedRows: [],
150
- expandedIDs: [],
151
- pageSelectionMode: PageSelectionMode.Hidden,
152
- sortItems: this.getSortItems(),
153
- columnWidths: this.loadColumnWidths(),
154
- fullscreen: false,
155
- showChart: false,
156
- showSortDialog: false,
157
- showReorderDialog: false,
158
- };
159
- this.getSortItemsKey = this.getSortItemsKey.bind(this);
160
- this.getSortItems = this.getSortItems.bind(this);
161
- this.setSortItems = this.setSortItems.bind(this);
162
- this.getVisibleColumnsKey = this.getVisibleColumnsKey.bind(this);
163
- this.getVisibleColumns = this.getVisibleColumns.bind(this);
164
- this.setVisibleColumns = this.setVisibleColumns.bind(this);
165
- this.setColumns = this.setColumns.bind(this);
166
- this.setColumns = this.setColumns.bind(this);
167
- this.setRows = this.setRows.bind(this);
168
- this.foreachColumn = this.foreachColumn.bind(this);
169
- this.getColumns = this.getColumns.bind(this);
170
- this.showModal = this.showModal.bind(this);
171
- this.hideModal = this.hideModal.bind(this);
172
- this.onPageChange = this.onPageChange.bind(this);
173
- this.isSomeSelected = this.isSomeSelected.bind(this);
174
- this.toggleAllSelections = this.toggleAllSelections.bind(this);
175
- this.print = this.print.bind(this);
176
- this.exportCSV = this.exportCSV.bind(this);
177
- this.isAllSelected = this.isAllSelected.bind(this);
178
- this.reload = this.reload.bind(this);
179
- this.toggleFullscreen = this.toggleFullscreen.bind(this);
180
- this.toggleExpandAll = this.toggleExpandAll.bind(this);
181
- }
182
- private toggleFullscreen()
183
- {
184
- this.setState({ fullscreen: !this.state.fullscreen });
185
- }
186
- private getSortItemsKey(): string
187
- {
188
- return "ns_table_" + this.props.name + "_sort_items";
189
- }
190
- getSortItems(): SortItem[]
191
- {
192
- let storage = new IStorageLocal();
193
- let item = storage.get(this.getSortItemsKey(), "");
194
- try
195
- {
196
- return SortItem.parse(item);
197
- } catch (error)
198
- {
199
- }
200
- return [];
201
- }
202
- isSortEnabled(): boolean
203
- {
204
- return this.props.sort?.enabled ?? true;
205
- }
206
- private hasCustomSort(): boolean
207
- {
208
- return !!this.props.sort?.onSort;
209
- }
210
- setSortItems(items: SortItem[] | null)
211
- {
212
- if (!this.isSortEnabled())
213
- return;
214
- let storage = new IStorageLocal();
215
- storage.set(this.getSortItemsKey(), SortItem.stringify(items));
216
- this.setState({ sortItems: items }, () =>
217
- {
218
- this.props.onSortChanged?.(items);
219
- this.props.sort?.onSort?.(items);
220
- });
221
- }
222
- private compareRowsBySortItems(a: RowType, b: RowType, items: SortItem[]): number
223
- {
224
- for (let item of items)
225
- {
226
- let column = this.state.columns.find(c => c.table.name === item.table.name && c.name === item.column.name);
227
- let va = (a as any)[item.column.name];
228
- let vb = (b as any)[item.column.name];
229
- let cmp = column ? column.formatter.compare(va, vb) : BaseColumnFormatter.smartCompare(va, vb);
230
- if (cmp !== 0)
231
- return item.ascending ? cmp : -cmp;
232
- }
233
- return 0;
234
- }
235
- private sortRowsClient(rows: RowType[]): RowType[]
236
- {
237
- let items = this.state.sortItems;
238
- if (!items || items.length === 0)
239
- return rows;
240
- let decorated = rows.map((row, index) => ({ row, index }));
241
- decorated.sort((x, y) =>
242
- {
243
- let cmp = this.compareRowsBySortItems(x.row, y.row, items!);
244
- return cmp !== 0 ? cmp : x.index - y.index;
245
- });
246
- return decorated.map(d => d.row);
247
- }
248
- private getDisplayRows(): RowType[] | null
249
- {
250
- let rows = this.state.rows;
251
- if (!rows || !this.isSortEnabled() || this.hasCustomSort())
252
- return rows;
253
- return this.sortRowsClient(rows);
254
- }
255
- private getVisibleColumnsKey(): string
256
- {
257
- return "ns_table_" + this.props.name + "_visible_columns";
258
- }
259
- getVisibleColumns(): { table: string, column: string }[]
260
- {
261
- let storage = new IStorageLocal();
262
- let item = storage.get(this.getVisibleColumnsKey(), "");
263
- try
264
- {
265
- return JSON.parse(item);
266
- } catch (error)
267
- {
268
- }
269
- return [];
270
- }
271
- setVisibleColumns(visible_columns: { table: string, column: string }[])
272
- {
273
- let storage = new IStorageLocal();
274
- storage.set(this.getVisibleColumnsKey(), JSON.stringify(visible_columns));
275
- this.setState({ visible_columns }, () =>
276
- {
277
- this.props.onVisibleColumnsChanged?.(visible_columns);
278
- });
279
- }
280
- setColumns(columns: TableColumnInfo[])
281
- {
282
- this.setState({ columns });
283
- }
284
- setRows(rows: RowType[] | null, totalItems: number)
285
- {
286
- this.setState({ rows, totalItems });
287
- }
288
- foreachColumn(visible: boolean | null, handler: (column: TableColumnInfo) => void)
289
- {
290
- let vs = this.getVisibleColumns();
291
- let shouldBeVisible = (c: TableColumnInfo) =>
292
- {
293
- if (vs.length === 0)
294
- return !c.defaults?.hidden;
295
- return vs.filter(v => v.table === c.table.name && v.column === c.name).length > 0;
296
- };
297
- this.state.columns.forEach(c =>
298
- {
299
- if (visible === null || shouldBeVisible(c) === visible)
300
- handler(c);
301
- })
302
- }
303
- getColumns(visible: boolean | null): TableColumnInfo[]
304
- {
305
- let columns: TableColumnInfo[] = [];
306
- this.foreachColumn(visible, c => columns.push(c));
307
- if (visible === true)
308
- {
309
- let vs = this.getVisibleColumns();
310
- if (vs.length > 0)
311
- columns.sort((a, b) =>
312
- {
313
- let ai = vs.findIndex(v => v.table === a.table.name && v.column === a.name);
314
- let bi = vs.findIndex(v => v.table === b.table.name && v.column === b.name);
315
- return ai - bi;
316
- });
317
- }
318
- return columns;
319
- }
320
- private showModal(description: string, title?: string)
321
- {
322
- this.setState({ model: { show: true, description }, title });
323
- }
324
- private hideModal()
325
- {
326
- this.setState({ model: { show: false } });
327
- }
328
- private onPageChange(page: number, size: number)
329
- {
330
- this.reload(page, size);
331
- }
332
- public async reload(page: number | null, size: number | null)
333
- {
334
- this.setRows(null, 0);
335
- if (page === null)
336
- page = this.NSPagination.current?.getCurrentPage() ?? 0;
337
- if (size === null)
338
- size = this.NSPagination.current?.getPageSize() ?? 0;
339
- try
340
- {
341
- let sorts = this.getSortItems();
342
- let res = await this.props.getRows(page, size, sorts);
343
- this.setRows(res.rows, res.count);
344
- } catch (error)
345
- {
346
- }
347
- }
348
- private toggleAllSelections()
349
- {
350
- let selectedIDs: string[] = [];
351
- let selectedRows: RowType[] = [];
352
- this.LastSelectedRowID = null;
353
-
354
- if (this.getSelectedIDs().length === 0)
355
- {
356
- this.state.rows?.forEach((row, rowIndex) =>
357
- {
358
- let rowInfo = { index: rowIndex, value: row };
359
- if (this.props.getRowCheckboxDisabled?.(rowInfo))
360
- return;
361
- let id = this.props.getRowKey(rowInfo);
362
- selectedIDs.push(id);
363
- selectedRows.push(row);
364
- })
365
- }
366
-
367
- this.setSelectedIDs(selectedIDs, selectedRows);
368
- }
369
- private escapeHtml(value: any): string
370
- {
371
- if (value === null || value === undefined)
372
- return "";
373
- let text = typeof value === "object" ? JSON.stringify(value) : String(value);
374
- return text
375
- .replace(/&/g, "&amp;")
376
- .replace(/</g, "&lt;")
377
- .replace(/>/g, "&gt;")
378
- .replace(/"/g, "&quot;")
379
- .replace(/'/g, "&#39;");
380
- }
381
- private getPrintableRows(rows: RowType[], expandAll: boolean): PrintableRow<RowType>[]
382
- {
383
- let result: PrintableRow<RowType>[] = [];
384
- if (!this.props.hierarchy)
385
- {
386
- rows.forEach((value, index) => result.push({ value, index, depth: 0, ancestorHasNext: [], isLastChild: true }));
387
- return result;
388
- }
389
- let childrenMap = this.buildChildrenMap(rows);
390
- let indexOfRow = new Map<RowType, number>();
391
- rows.forEach((row, index) => indexOfRow.set(row, index));
392
- let parentCol = this.props.hierarchy.parent;
393
- let ids = new Set<string>();
394
- rows.forEach(row => ids.add(this.getRowIdValue(row) + "" + this.getGroupKey(row)));
395
- let topLevel = rows.filter(row =>
396
- {
397
- let pid = (row as any)[parentCol];
398
- let pidKey = pid === null || pid === undefined ? "" : String(pid);
399
- if (pidKey === "")
400
- return true;
401
- return !ids.has(pidKey + "" + this.getGroupKey(row));
402
- });
403
- let walk = (value: RowType, depth: number, ancestorHasNext: boolean[], isLastChild: boolean) =>
404
- {
405
- let index = indexOfRow.get(value) ?? 0;
406
- result.push({ value, index, depth, ancestorHasNext, isLastChild });
407
- let rowKey = this.props.getRowKey({ index, value });
408
- if (!expandAll && !this.isExpanded(rowKey))
409
- return;
410
- let children = childrenMap?.get(this.getRowChildLookupKey(value)) ?? [];
411
- let childAncestors = [...ancestorHasNext, !isLastChild];
412
- children.forEach((child, childIndex) => walk(child, depth + 1, childAncestors, childIndex === children.length - 1));
413
- };
414
- topLevel.forEach((row, index) => walk(row, 0, [], index === topLevel.length - 1));
415
- return result;
416
- }
417
- private buildTreeGuides(entry: PrintableRow<RowType>): string
418
- {
419
- if (entry.depth === 0)
420
- return "";
421
- let indent = 16;
422
- let color = "#000";
423
- let overhang = 8;
424
- let guides = "";
425
- for (let i = 0; i < entry.depth; i++)
426
- {
427
- let center = indent / 2;
428
- let lines = "";
429
- if (i < entry.depth - 1)
430
- {
431
- if (entry.ancestorHasNext[i])
432
- lines += `<span style="position: absolute; inset-inline-start: ${center}px; top: 0; height: 100%; width: 1px; background: ${color};"></span>`;
433
- }
434
- else
435
- {
436
- lines += `<span style="position: absolute; inset-inline-start: ${center}px; top: -${overhang}px; height: calc(50% + ${overhang}px); width: 1px; background: ${color};"></span>`;
437
- if (!entry.isLastChild)
438
- lines += `<span style="position: absolute; inset-inline-start: ${center}px; top: 50%; height: 50%; width: 1px; background: ${color};"></span>`;
439
- lines += `<span style="position: absolute; inset-inline-start: ${center}px; top: 50%; width: ${center - 2}px; height: 1px; background: ${color};"></span>`;
440
- }
441
- guides += `<span style="position: relative; display: inline-block; width: ${indent}px; align-self: stretch; flex: 0 0 auto;">${lines}</span>`;
442
- }
443
- return guides;
444
- }
445
- private buildPrintDocument(rows: RowType[], expandAll: boolean): string
446
- {
447
- let columns = this.getColumns(true);
448
- let totalWidth = columns.reduce((sum, column) => sum + this.getColumnWidth(column), 0) || 1;
449
- let colgroup = columns
450
- .map(column => `<col style="width: ${(this.getColumnWidth(column) / totalWidth * 100).toFixed(4)}%" />`)
451
- .join("");
452
- let head = columns
453
- .map(column => `<th>${this.escapeHtml(column.text)}</th>`)
454
- .join("");
455
- let printable = this.getPrintableRows(rows, expandAll);
456
- let body = printable
457
- .map((entry, entryIndex) =>
458
- {
459
- let row = { index: entry.index, value: entry.value };
460
- let next = printable[entryIndex + 1];
461
- let previous = printable[entryIndex - 1];
462
- let removeTop = entry.depth > 0 && !!previous;
463
- let removeBottom = !!next && next.depth > 0;
464
- let edgeStyle = `${removeTop ? "border-top: none;" : ""}${removeBottom ? "border-bottom: none;" : ""}`;
465
- let cells = columns
466
- .map((column, columnIndex) =>
467
- {
468
- let text = this.escapeHtml(this.getCellTableInfo(column, row, true).formatted);
469
- if (columnIndex === 0 && this.props.hierarchy && entry.depth > 0)
470
- {
471
- let guides = this.buildTreeGuides(entry);
472
- let overlay = `<span style="position: absolute; top: 0; bottom: 0; inset-inline-start: 8px; display: flex; align-items: stretch;">${guides}</span>`;
473
- return `<td style="position: relative; padding-inline-start: ${8 + entry.depth * 16}px;${edgeStyle}">${overlay}${text}</td>`;
474
- }
475
- if (columnIndex === 0 && edgeStyle)
476
- return `<td style="${edgeStyle}">${text}</td>`;
477
- return `<td>${text}</td>`;
478
- })
479
- .join("");
480
- return `<tr>${cells}</tr>`;
481
- })
482
- .join("");
483
- let direction = document.documentElement.dir || "ltr";
484
- return `
485
- <!DOCTYPE html>
486
- <html dir="${direction}">
487
- <head>
488
- <meta charset="utf-8" />
489
- <title>${this.escapeHtml(this.props.name)}</title>
490
- <style>
491
- * { -webkit-print-color-adjust: exact; print-color-adjust: exact; }
492
- body { font-family: sans-serif; margin: 16px; }
493
- table { width: 100%; border-collapse: collapse; table-layout: fixed; }
494
- th, td { border: 1px solid #000; padding: 8px; text-align: start; overflow-wrap: break-word; word-break: break-word; }
495
- thead { display: table-header-group; }
496
- tr { page-break-inside: avoid; }
497
- </style>
498
- </head>
499
- <body>
500
- <table>
501
- <colgroup>${colgroup}</colgroup>
502
- <thead><tr>${head}</tr></thead>
503
- <tbody>${body}</tbody>
504
- </table>
505
- </body>
506
- </html>
507
- `;
508
- }
509
- print(rows: RowType[], expandAll: boolean = false)
510
- {
511
- let blob = new Blob([this.buildPrintDocument(rows, expandAll)], { type: "text/html" });
512
- let url = URL.createObjectURL(blob);
513
- let printWindow = window.open(url, "_blank", "height=800,width=800");
514
- if (!printWindow)
515
- {
516
- URL.revokeObjectURL(url);
517
- this.showModal("Please allow pop-ups to print this table.", this.props.name);
518
- return;
519
- }
520
- printWindow.onload = () =>
521
- {
522
- printWindow!.focus();
523
- printWindow!.onafterprint = () =>
524
- {
525
- printWindow!.close();
526
- URL.revokeObjectURL(url);
527
- };
528
- printWindow!.print();
529
- };
530
- }
531
- exportCSV(rows: RowType[])
532
- {
533
- let columns = this.getColumns(true);
534
- let header = columns.map(column => column.text);
535
- let data = (rows || []).map((rowValue, rowIndex) =>
536
- {
537
- let row = { index: rowIndex, value: rowValue };
538
- return columns.map(column =>
539
- {
540
- let formatted = this.getCellTableInfo(column, row, true).formatted;
541
- if (formatted === null || formatted === undefined)
542
- return "";
543
- return typeof formatted === "object" ? JSON.stringify(formatted) : formatted;
544
- });
545
- });
546
- let worksheet = utils.aoa_to_sheet([header, ...data]);
547
- worksheet["!cols"] = columns.map(column => ({ wpx: this.getColumnWidth(column) }));
548
- let workbook = utils.book_new();
549
- utils.book_append_sheet(workbook, worksheet, this.props.name);
550
- writeFile(workbook, this.props.name + ".xlsx");
551
- }
552
-
553
- handleSelectRow(row: TableRowInfo<RowType>, selected: boolean, ctrlKey: boolean, shiftKey: boolean)
554
- {
555
- let selectedIDs = this.state.selectedIDs;
556
- let selectedRows = this.state.selectedRows;
557
- let rowID = this.props.getRowKey(row);
558
-
559
- if (shiftKey)
560
- {
561
- selectedRows = this.getRowsInRange(this.LastSelectedRowID ?? 0, row.index);
562
- selectedIDs = selectedRows.map(row => this.props.getRowKey({ index: -1, value: row }));
563
- }
564
- else if (selected)
565
- {
566
- if (this.isMultipleSelected() && !ctrlKey)
567
- {
568
- selectedIDs = [rowID];
569
- selectedRows = [row.value];
570
- this.LastSelectedRowID = row.index;
571
- }
572
- else
573
- {
574
- selectedIDs = selectedIDs.filter(x => x !== rowID);
575
- selectedRows = selectedRows.filter(x => this.props.getRowKey({ index: -1, value: x }) !== rowID);
576
- this.LastSelectedRowID = ctrlKey ? row.index : null;
577
- }
578
- }
579
- else
580
- {
581
- if (ctrlKey)
582
- {
583
- selectedIDs.push(rowID);
584
- selectedRows.push(row.value);
585
- }
586
- else
587
- {
588
- selectedIDs = [rowID];
589
- selectedRows = [row.value];
590
- }
591
- this.LastSelectedRowID = row.index;
592
- }
593
-
594
- this.setSelectedIDs(selectedIDs, selectedRows);
595
- }
596
- getRowsInRange(fromIndex: number, toIndex: number)
597
- {
598
- let rows = this.getDisplayRows();
599
- if (!rows)
600
- return [];
601
-
602
- let start = Math.min(fromIndex, toIndex);
603
- let end = Math.max(fromIndex, toIndex);
604
-
605
- return rows.slice(start, end + 1);
606
- }
607
- getSelectedIDs(): string[]
608
- {
609
- return this.state.selectedIDs;
610
- }
611
- getSelectedRows(): RowType[]
612
- {
613
- return this.state.selectedRows;
614
- }
615
- setSelectedIDs(ids: string[], rows: RowType[], callback?: () => void)
616
- {
617
- this.setState({ selectedIDs: ids, selectedRows: rows }, () =>
618
- {
619
- if (this.props.onSelectedIDsChanged)
620
- this.props.onSelectedIDsChanged();
621
- if (callback)
622
- callback();
623
- });
624
- }
625
- private getGroupKey(row: RowType): string
626
- {
627
- let h = this.props.hierarchy;
628
- if (!h || !h.group || h.group.length === 0)
629
- return "";
630
- return h.group.map(g =>
631
- {
632
- let v = (row as any)[g];
633
- return v === null || v === undefined ? "" : String(v);
634
- }).join("");
635
- }
636
- private buildChildrenMap(rows: RowType[] | null): Map<string, RowType[]> | null
637
- {
638
- let h = this.props.hierarchy;
639
- if (!h || !rows)
640
- return null;
641
- let map = new Map<string, RowType[]>();
642
- for (let r of rows)
643
- {
644
- let pid = (r as any)[h.parent];
645
- let pidKey = pid === null || pid === undefined ? "" : String(pid);
646
- let key = pidKey + "" + this.getGroupKey(r);
647
- let arr = map.get(key);
648
- if (!arr) { arr = []; map.set(key, arr); }
649
- arr.push(r);
650
- }
651
- return map;
652
- }
653
- private getRowIdValue(row: RowType): string
654
- {
655
- let h = this.props.hierarchy;
656
- if (!h) return "";
657
- let v = (row as any)[h.id];
658
- return v === null || v === undefined ? "" : String(v);
659
- }
660
- private getRowChildLookupKey(row: RowType): string
661
- {
662
- return this.getRowIdValue(row) + "" + this.getGroupKey(row);
663
- }
664
- private isExpanded(rowKey: string): boolean
665
- {
666
- let inList = this.state.expandedIDs.includes(rowKey);
667
- return this.props.defaultExpanded ? !inList : inList;
668
- }
669
- private toggleExpanded(rowKey: string)
670
- {
671
- let ids = this.state.expandedIDs;
672
- if (ids.includes(rowKey))
673
- ids = ids.filter(x => x !== rowKey);
674
- else
675
- ids = [...ids, rowKey];
676
- this.setState({ expandedIDs: ids });
677
- }
678
- private getExpandableRowKeys(): string[]
679
- {
680
- let rows = this.state.rows;
681
- if (!this.props.hierarchy || !rows)
682
- return [];
683
- let childrenMap = this.buildChildrenMap(rows);
684
- if (!childrenMap)
685
- return [];
686
- let keys: string[] = [];
687
- rows.forEach((value, index) =>
688
- {
689
- let children = childrenMap.get(this.getRowChildLookupKey(value)) ?? [];
690
- if (children.length > 0)
691
- keys.push(this.props.getRowKey({ index, value }));
692
- });
693
- return keys;
694
- }
695
- private isAnyExpanded(): boolean
696
- {
697
- return this.getExpandableRowKeys().some(key => this.isExpanded(key));
698
- }
699
- private toggleExpandAll()
700
- {
701
- let keys = this.getExpandableRowKeys();
702
- let expand = !this.isAnyExpanded();
703
- let inList = this.props.defaultExpanded ? !expand : expand;
704
- this.setState({ expandedIDs: inList ? keys : [] });
705
- }
706
- private isAllSelected(): boolean
707
- {
708
- if (!this.state.rows || this.state.rows.length === 0)
709
- return false;
710
- const selectableCount = this.state.rows.filter((row, i) =>
711
- !this.props.getRowCheckboxDisabled?.({ index: i, value: row })
712
- ).length;
713
- return selectableCount > 0 && this.state.selectedIDs.length === selectableCount;
714
- }
715
- private isSomeSelected(): boolean
716
- {
717
- return this.state.selectedIDs.length > 0 && !this.isAllSelected();
718
- }
719
- private isMultipleSelected(): boolean
720
- {
721
- return this.state.selectedIDs.length > 1;
722
- }
723
- private getCellTableInfo(column: TableColumnInfo, row: TableRowInfo<RowType>, printable: boolean)
724
- {
725
- let value = (row.value as any)[column.name];
726
- let formatted = this.getCellFormattedValue(value, column, row, printable);
727
- return { column, row, value, formatted };
728
- }
729
- private getCellFormattedValue(value: any, column: TableColumnInfo, row: TableRowInfo<RowType>, printable: boolean)
730
- {
731
- if (this.props.getCellFormattedValue)
732
- return this.props.getCellFormattedValue(value, column, row, printable);
733
- return column.formatter.format(value, column, row, printable);
734
- }
735
- private getColumnWidthsKey(): string
736
- {
737
- return "ns_table_" + this.props.name + "_column_widths";
738
- }
739
- private loadColumnWidths(): { [key: string]: number }
740
- {
741
- let storage = new IStorageLocal();
742
- let item = storage.get(this.getColumnWidthsKey(), "");
743
- try
744
- {
745
- return JSON.parse(item);
746
- }
747
- catch
748
- {
749
- return {};
750
- }
751
- }
752
- private saveColumnWidths(widths: { [key: string]: number })
753
- {
754
- let storage = new IStorageLocal();
755
- storage.set(this.getColumnWidthsKey(), JSON.stringify(widths));
756
- }
757
- private getColumnKey(column: TableColumnInfo): string
758
- {
759
- return column.table.name + "_" + column.name;
760
- }
761
- private getColumnWidth(column: TableColumnInfo): number
762
- {
763
- let key = this.getColumnKey(column);
764
- if (this.state.columnWidths[key] !== undefined)
765
- return this.state.columnWidths[key];
766
- let w = column.formatter.width;
767
- if (typeof w === 'number') return w;
768
- if (typeof w === 'string')
769
- {
770
- let parsed = parseInt(w, 10);
771
- if (!isNaN(parsed)) return parsed;
772
- }
773
- return 120;
774
- }
775
- private getGridTemplateColumns(): string
776
- {
777
- let parts: string[] = [];
778
- if (this.props.checkbox)
779
- parts.push("40px");
780
- this.getColumns(true).forEach(column =>
781
- {
782
- parts.push(this.getColumnWidth(column) + "px");
783
- });
784
- return parts.join(" ");
785
- }
786
- private startResize(e: React.MouseEvent, column: TableColumnInfo)
787
- {
788
- e.preventDefault();
789
- e.stopPropagation();
790
- let key = this.getColumnKey(column);
791
- let startX = e.clientX;
792
- let startWidth = this.getColumnWidth(column);
793
- let currentWidths = { ...this.state.columnWidths };
794
- document.body.style.cursor = 'col-resize';
795
- document.body.style.userSelect = 'none';
796
- const onMouseMove = (ev: MouseEvent) =>
797
- {
798
- let delta = ev.clientX - startX;
799
- let newWidth = Math.max(80, startWidth + delta);
800
- currentWidths = { ...currentWidths, [key]: newWidth };
801
- this.setState({ columnWidths: currentWidths });
802
- };
803
- const onMouseUp = () =>
804
- {
805
- document.body.style.cursor = '';
806
- document.body.style.userSelect = '';
807
- document.removeEventListener('mousemove', onMouseMove);
808
- document.removeEventListener('mouseup', onMouseUp);
809
- this.resizeMoveHandler = null;
810
- this.resizeUpHandler = null;
811
- this.saveColumnWidths(currentWidths);
812
- };
813
- this.resizeMoveHandler = onMouseMove;
814
- this.resizeUpHandler = onMouseUp;
815
- document.addEventListener('mousemove', onMouseMove);
816
- document.addEventListener('mouseup', onMouseUp);
817
- }
818
- override componentDidMount(): void
819
- {
820
- if (!this.NSPagination.current)
821
- this.reload(null, null);
822
- }
823
- override componentWillUnmount(): void
824
- {
825
- if (this.resizeMoveHandler)
826
- document.removeEventListener('mousemove', this.resizeMoveHandler);
827
- if (this.resizeUpHandler)
828
- document.removeEventListener('mouseup', this.resizeUpHandler);
829
- }
830
- override render()
831
- {
832
-
833
- let show_pagination = this.props.ui?.pagination ?? true;
834
- let button_panel_config = this.props.ui?.button_panel;
835
- let show_button_panel = button_panel_config !== false;
836
- let isButtonEnabled = (key: NSTableButtonKey): boolean =>
837
- {
838
- if (button_panel_config === undefined || typeof button_panel_config === "boolean")
839
- return true;
840
- return button_panel_config[key] ?? true;
841
- };
842
- let getRowAttributes = (row_value: RowType, row_index: number) =>
843
- {
844
- if (this.props.getRowAttributes)
845
- return this.props.getRowAttributes(row_value, row_index);
846
- return {};
847
- }
848
- let getColumnAttributes = (column: TableColumnInfo) =>
849
- {
850
- if (this.props.getColumnAttributes)
851
- return this.props.getColumnAttributes(column);
852
- return {};
853
- }
854
- let onColumnClick = (e: React.MouseEvent<HTMLTableCellElement, MouseEvent>, column: TableColumnInfo) =>
855
- {
856
- if ((e.target as HTMLElement).tagName !== "SPAN")
857
- return;
858
-
859
- if (this.props.onColumnClick)
860
- this.props.onColumnClick(e, column);
861
- }
862
- let onRowClick = (e: React.MouseEvent<HTMLTableRowElement, MouseEvent>, row: TableRowInfo<RowType>, selected: boolean) =>
863
- {
864
- const target = e.target as HTMLElement;
865
-
866
- if (target.tagName === "TR" || target.tagName === "TD" || target.closest("td")?.dataset.cellType === "checkbox")
867
- this.handleSelectRow(row, selected, e.ctrlKey, e.shiftKey);
868
-
869
- if (this.props.onRowClick)
870
- this.props.onRowClick(e, row);
871
- };
872
- let onCellClick = (e: React.MouseEvent<HTMLTableCellElement, MouseEvent>, cell: TableCellInfo<RowType>) =>
873
- {
874
- if ((e.target as HTMLElement).tagName !== "SPAN")
875
- return;
876
-
877
- let overided = cell.column.formatter.onclick_overrided;
878
- if (this.props.onCellClick)
879
- overided = this.props.onCellClick(e, cell) ?? true;
880
- if (!overided)
881
- this.showModal(cell.formatted, cell.column.text);
882
- };
883
- let gridTemplateColumns = this.getGridTemplateColumns();
884
- let content;
885
- if (this.state.rows === null)
886
- {
887
- content = (
888
- <tr className="d-flex justify-content-center align-items-center p-0">
889
- <td className="w-100 h-100 m-0 mh-100">
890
- <NSLoading
891
- title="Loading table rows ..."
892
- />
893
- </td>
894
- </tr>
895
- );
896
- }
897
- else if (this.state.rows.length === 0)
898
- {
899
- content = (
900
- <tr className="d-flex justify-content-center align-items-center p-0">
901
- <td className="w-100 h-100 m-0 mh-100">
902
- <NSNoData lable='No Data' classList={["p-3"]} />
903
- </td>
904
- </tr>
905
- );
906
- }
907
- else
908
- {
909
- let displayRows = this.getDisplayRows() ?? [];
910
- let hierarchical = !!this.props.hierarchy;
911
- let childrenMap = this.buildChildrenMap(displayRows);
912
- let indexOfRow = new Map<RowType, number>();
913
- displayRows.forEach((r, i) => indexOfRow.set(r, i));
914
- let INDENT = 16;
915
- let lineColor = "rgba(20, 27, 92, 0.35)";
916
- let renderRow = (rowValue: RowType, rowIndex: number, depth: number, parentKey: string, ancestorHasNext: boolean[], isLastChild: boolean): React.ReactNode[] =>
917
- {
918
- let row = { index: rowIndex, value: rowValue };
919
- let rowKey = this.props.getRowKey(row);
920
- let reactKey = parentKey ? `${parentKey}>${rowKey}` : rowKey;
921
- let isSelected = this.state.selectedIDs.includes(rowKey);
922
- let isLastSelected = this.LastSelectedRowID === row.index;
923
- let isCheckboxDisabled = this.props.getRowCheckboxDisabled?.(row) ?? false;
924
- let children: RowType[] = [];
925
- if (hierarchical && childrenMap)
926
- {
927
- let lookupKey = this.getRowChildLookupKey(rowValue);
928
- children = childrenMap.get(lookupKey) ?? [];
929
- }
930
- let hasChildren = children.length > 0;
931
- let expanded = this.isExpanded(rowKey);
932
- let rowAttrs = getRowAttributes(rowValue, rowIndex);
933
- let nodes: React.ReactNode[] = [];
934
- nodes.push(
935
- <tr
936
- key={reactKey}
937
- {...rowAttrs}
938
- style={{ ...(rowAttrs.style || {}), gridTemplateColumns }}
939
- className={`${isSelected ? Styles.ns_selected : ""} ${isLastSelected ? Styles.ns_last_selected : ""}`.trim()}
940
- onClick={(e) => !isCheckboxDisabled && onRowClick(e, row, isSelected)}
941
- >
942
- {
943
- this.props.checkbox &&
944
- <td data-cell-type="checkbox">
945
- <label
946
- htmlFor={`checkbox_device_${rowKey}`}
947
- className={Styles.ns_check_box_label}
948
- >
949
- <input
950
- id={`checkbox_device_${rowKey}`}
951
- type="checkbox"
952
- checked={isSelected}
953
- disabled={isCheckboxDisabled}
954
- className={Styles.ns_check_box}
955
- onChange={() => { }}
956
- />
957
- </label>
958
- </td>
959
- }
960
- {
961
- this.getColumns(true).map((column, index) =>
962
- {
963
- column.index = index + (this.props.checkbox ? 1 : 0);
964
- let cell = this.getCellTableInfo(column, row, false);
965
- let isFirstDataColumn = index === 0;
966
- return (
967
- <td
968
- key={column.index}
969
- {...getColumnAttributes(column)}
970
- data-label={`${column.text}: `}
971
- width={column.formatter.width}
972
- className=''
973
- style={isFirstDataColumn && hierarchical ? { overflow: "visible" } : undefined}
974
- onClick={(e) =>
975
- {
976
- onColumnClick(e, column);
977
- onCellClick(e, cell);
978
- }}
979
- >
980
- {isFirstDataColumn && hierarchical ? (
981
- <div
982
- style={{
983
- display: "flex",
984
- alignItems: "center",
985
- }}
986
- >
987
- {Array.from({ length: depth }).map((_, i) =>
988
- {
989
- let isLastLevel = i === depth - 1;
990
- let drawVerticalTop = i < depth - 1 ? ancestorHasNext[i] : true;
991
- let drawVerticalBottom = i < depth - 1 ? ancestorHasNext[i] : !isLastChild;
992
- let drawHorizontal = isLastLevel;
993
- return (
994
- <span
995
- key={i}
996
- style={{
997
- position: "relative",
998
- display: "inline-block",
999
- width: INDENT,
1000
- alignSelf: "stretch",
1001
- flex: "0 0 auto",
1002
- }}
1003
- >
1004
- {drawVerticalTop && (
1005
- <span style={{
1006
- position: "absolute",
1007
- left: INDENT / 2,
1008
- top: -24,
1009
- height: "calc(50% + 24px)",
1010
- width: 1,
1011
- background: lineColor,
1012
- }} />
1013
- )}
1014
- {drawVerticalBottom && (
1015
- <span style={{
1016
- position: "absolute",
1017
- left: INDENT / 2,
1018
- top: "50%",
1019
- height: "calc(50% + 24px)",
1020
- width: 1,
1021
- background: lineColor,
1022
- }} />
1023
- )}
1024
- {drawHorizontal && (
1025
- <span style={{
1026
- position: "absolute",
1027
- left: INDENT / 2,
1028
- top: "50%",
1029
- width: INDENT / 2,
1030
- height: 1,
1031
- background: lineColor,
1032
- }} />
1033
- )}
1034
- </span>
1035
- );
1036
- })}
1037
- <span
1038
- data-cell-type="tree-toggle"
1039
- style={{
1040
- display: "inline-flex",
1041
- alignItems: "center",
1042
- justifyContent: "center",
1043
- marginRight: 6,
1044
- }}
1045
- onClick={(e) => e.stopPropagation()}
1046
- >
1047
- {hasChildren ? (
1048
- <button
1049
- type="button"
1050
- onClick={(e) =>
1051
- {
1052
- e.stopPropagation();
1053
- this.toggleExpanded(rowKey);
1054
- }}
1055
- style={{
1056
- width: 18,
1057
- height: 18,
1058
- lineHeight: "16px",
1059
- padding: 0,
1060
- border: "1px solid rgba(20, 27, 92, 0.4)",
1061
- borderRadius: 3,
1062
- background: "#fff",
1063
- cursor: "pointer",
1064
- fontSize: 14,
1065
- fontWeight: "bold",
1066
- color: "rgba(20, 27, 92, 1)",
1067
- }}
1068
- aria-label={expanded ? "Collapse" : "Expand"}
1069
- >
1070
- {expanded ? "−" : "+"}
1071
- </button>
1072
- ) : (
1073
- <span style={{ display: "inline-block", width: 18, height: 18 }} />
1074
- )}
1075
- </span>
1076
- {cell.formatted ?? ""}
1077
- </div>
1078
- ) : (
1079
- <>
1080
- {cell.formatted ?? ""}
1081
- </>
1082
- )}
1083
- </td>
1084
- );
1085
- })
1086
- }
1087
- </tr>
1088
- );
1089
- if (hierarchical && hasChildren && expanded)
1090
- {
1091
- let childAncestors = [...ancestorHasNext, !isLastChild];
1092
- children.forEach((child, ci2) =>
1093
- {
1094
- let ci = indexOfRow.get(child) ?? -1;
1095
- let lastChild = ci2 === children.length - 1;
1096
- renderRow(child, ci, depth + 1, reactKey, childAncestors, lastChild).forEach(n => nodes.push(n));
1097
- });
1098
- }
1099
- return nodes;
1100
- };
1101
- let topLevel: RowType[];
1102
- if (hierarchical && this.props.hierarchy)
1103
- {
1104
- let parentCol = this.props.hierarchy.parent;
1105
- let ids = new Set<string>();
1106
- displayRows.forEach(r => ids.add(this.getRowIdValue(r) + "" + this.getGroupKey(r)));
1107
- topLevel = displayRows.filter(r =>
1108
- {
1109
- let pid = (r as any)[parentCol];
1110
- let pidKey = pid === null || pid === undefined ? "" : String(pid);
1111
- if (pidKey === "")
1112
- return true;
1113
- return !ids.has(pidKey + "" + this.getGroupKey(r));
1114
- });
1115
- }
1116
- else
1117
- {
1118
- topLevel = displayRows;
1119
- }
1120
- content = topLevel.flatMap((rowValue, i) => renderRow(rowValue, indexOfRow.get(rowValue) ?? 0, 0, "", [], i === topLevel.length - 1));
1121
- }
1122
-
1123
- let getSortSign = (column: TableColumnInfo) =>
1124
- {
1125
- if (this.state.sortItems)
1126
- {
1127
- let c = this.state.sortItems.filter(c => c.table.name === column.table.name && c.column.name === column.name)[0]
1128
- if (c)
1129
- {
1130
- if (c.ascending)
1131
- return "↑"
1132
- return "↓";
1133
- }
1134
- }
1135
- return "";
1136
- };
1137
-
1138
- let buttonPanel = show_button_panel && (
1139
- <div className={Styles.ns_button_panel}>
1140
- {
1141
- this.state.pageSelectionMode !== PageSelectionMode.Hidden &&
1142
- <NSDialogPageSelection<RowType>
1143
- icon={
1144
- this.state.pageSelectionMode === PageSelectionMode.Print ? "https://static.namirasoft.com/image/concept/print/blue.svg" :
1145
- this.state.pageSelectionMode === PageSelectionMode.CSV ? "https://static.namirasoft.com/image/concept/export/blue.svg" : ""
1146
- }
1147
- current_page={this.NSPagination.current?.getCurrentPage() ?? 1}
1148
- current_size={this.NSPagination.current?.getPageSize() ?? 0}
1149
- max_page={this.state.totalItems}
1150
- getItems={async (page, size, selected) =>
1151
- {
1152
- if (selected)
1153
- {
1154
- let rows: RowType[] = [];
1155
- if (this.state.rows)
1156
- for (let i = 0; i < this.state.rows.length; i++)
1157
- {
1158
- const row = { index: i, value: this.state.rows[i] };
1159
- let id = this.props.getRowKey(row);
1160
- if (this.state.selectedIDs.includes(id))
1161
- rows.push(row.value);
1162
- }
1163
- return rows;
1164
- }
1165
- let res = await this.props.getRows(page, size, this.getSortItems());
1166
- return res.rows;
1167
- }}
1168
- onFinish={async (items: RowType[], expandAll: boolean) =>
1169
- {
1170
- if (this.state.pageSelectionMode === PageSelectionMode.Print)
1171
- this.print(items, expandAll);
1172
- else if (this.state.pageSelectionMode === PageSelectionMode.CSV)
1173
- this.exportCSV(items);
1174
- }}
1175
- showExpandAll={this.state.pageSelectionMode === PageSelectionMode.Print && !!this.props.hierarchy}
1176
- onClose={() => this.setState({ pageSelectionMode: PageSelectionMode.Hidden })}
1177
- />
1178
- }
1179
- {
1180
- this.props.hierarchy && isButtonEnabled("expand") &&
1181
- <NSButton
1182
- attributes={{ title: this.isAnyExpanded() ? 'Collapse All' : 'Expand All' }}
1183
- icon={{ src: "https://static.namirasoft.com/image/concept/hierarchy/blue.svg", alt: "expand" }}
1184
- onClick={{
1185
- action: this.toggleExpandAll,
1186
- showLoading: false
1187
- }}
1188
- classList={[Styles.ns_table_button]}
1189
- />
1190
- }
1191
- {
1192
- isButtonEnabled("reorder") &&
1193
- <NSButton
1194
- attributes={{ title: 'Reorder' }}
1195
- icon={{ src: "https://static.namirasoft.com/image/concept/grip-vertical/blue.svg", alt: "reorder" }}
1196
- onClick={{
1197
- action: () => this.setState({ showReorderDialog: true }),
1198
- showLoading: false
1199
- }}
1200
- classList={[Styles.ns_table_button]}
1201
- />
1202
- }
1203
- {
1204
- isButtonEnabled("sort") && this.isSortEnabled() &&
1205
- <NSButton
1206
- attributes={{ title: 'Sort' }}
1207
- icon={{ src: "https://static.namirasoft.com/image/concept/arrow-down-short-wide/blue.svg", alt: "sort" }}
1208
- onClick={{
1209
- action: () => this.setState({ showSortDialog: true }),
1210
- showLoading: false
1211
- }}
1212
- classList={[Styles.ns_table_button]}
1213
- />
1214
- }
1215
- {
1216
- this.props.buttons?.map((button, index) => (
1217
- <NSButton
1218
- key={index}
1219
- {...button}
1220
- classList={[Styles.ns_table_button, ...(button.classList ?? [])]}
1221
- />
1222
- ))
1223
- }
1224
- {
1225
- isButtonEnabled("fullscreen") &&
1226
- <NSButton
1227
- icon={{ src: `https://static.namirasoft.com/image/concept/full-screen/blue.svg` }}
1228
- classList={[Styles.ns_table_button, Styles.ns_fullscreen_button]}
1229
- onClick={{
1230
- action: this.toggleFullscreen,
1231
- showLoading: false
1232
- }}
1233
- attributes={{ title: this.state.fullscreen ? 'Minimize' : 'Maximize' }}
1234
- />
1235
- }
1236
- {
1237
- isButtonEnabled("chart") &&
1238
- <NSButton
1239
- attributes={{ title: 'Chart' }}
1240
- icon={{ src: "https://static.namirasoft.com/image/concept/chart/blue.svg" }}
1241
- onClick={{
1242
- action: () =>
1243
- {
1244
- if (!this.state.rows || this.state.rows.length === 0)
1245
- this.showModal(" ", "There is no data to display as a chart.");
1246
- else
1247
- this.setState({ showChart: true });
1248
- },
1249
- showLoading: false
1250
- }}
1251
- classList={[Styles.ns_table_button]}
1252
- />
1253
- }
1254
- {
1255
- isButtonEnabled("export") &&
1256
- <NSButton
1257
- icon={{ src: "https://static.namirasoft.com/image/concept/export/blue.svg" }}
1258
- onClick={{
1259
- action: () => { this.setState({ pageSelectionMode: PageSelectionMode.CSV }); },
1260
- showLoading: false
1261
- }}
1262
- classList={[Styles.ns_table_button]}
1263
- attributes={{ title: 'Export' }}
1264
- />
1265
- }
1266
- {
1267
- isButtonEnabled("print") &&
1268
- <NSButton
1269
- attributes={{ title: 'Print' }}
1270
- icon={{ src: "https://static.namirasoft.com/image/concept/print/blue.svg" }}
1271
- onClick={{
1272
- action: () => { this.setState({ pageSelectionMode: PageSelectionMode.Print }); },
1273
- showLoading: false
1274
- }}
1275
- classList={[Styles.ns_table_button]}
1276
- />
1277
- }
1278
- {
1279
- isButtonEnabled("refresh") &&
1280
- <NSButton
1281
- attributes={{ title: 'Refresh' }}
1282
- icon={{ src: "https://static.namirasoft.com/image/concept/refresh/blue.svg" }}
1283
- onClick={{
1284
- action: async (onFinished) =>
1285
- {
1286
- this.reload(null, null).then(onFinished).catch(onFinished);
1287
- }
1288
- }}
1289
- classList={[Styles.ns_table_button]}
1290
- style={{ border: '1px solid rgb(3, 119, 255)' }}
1291
- />
1292
- }
1293
- </div>
1294
- );
1295
-
1296
- return (
1297
- <div
1298
- id={this.props.id}
1299
- className={[...this.props.classList ?? [], Styles.ns_table_wrapper, this.state.fullscreen ? Styles.ns_fullscreen : ""].filter(Boolean).join(" ")}
1300
- style={{ ...this.props.style }}
1301
- >
1302
- {
1303
- ((!this.state.fullscreen && this.props.headerChildren) || show_pagination || buttonPanel) &&
1304
- <div className={Styles.ns_table_topbar}>
1305
- <div className={Styles.ns_table_topbar_left}>
1306
- {
1307
- !this.state.fullscreen &&
1308
- this.props.headerChildren
1309
- }
1310
- {
1311
- show_pagination &&
1312
- <NSPagination
1313
- ref={this.NSPagination}
1314
- totalItems={this.state.totalItems}
1315
- onPageChange={this.onPageChange}
1316
- />
1317
- }
1318
- </div>
1319
- {buttonPanel}
1320
- </div>
1321
- }
1322
- {
1323
- !this.state.fullscreen && this.props.children &&
1324
- <div className={Styles.ns_table_children}>
1325
- {this.props.children}
1326
- </div>
1327
- }
1328
- <table
1329
- ref={this.Table_Ref}
1330
- className={Styles.ns_table}
1331
- style={this.props.ui?.grow ? { maxHeight: "none", height: "auto", overflowY: "visible", overflowX: "auto" } : undefined}
1332
- >
1333
- <thead>
1334
- <tr style={{ gridTemplateColumns }}>
1335
- {
1336
- this.props.checkbox &&
1337
- <th data-cell-type="checkbox" className="d-flex">
1338
- <label
1339
- htmlFor='checkbox'
1340
- className={Styles.ns_checkbox_label}
1341
- >
1342
- <input
1343
- id="checkbox"
1344
- type="checkbox"
1345
- className={`${Styles.ns_check_box} ${this.isSomeSelected() ? Styles.ns_indeterminate : ""}`}
1346
- checked={this.getSelectedIDs().length > 0}
1347
- onChange={this.toggleAllSelections}
1348
- ></input>
1349
- </label>
1350
- </th>
1351
- }
1352
- {
1353
- this.getColumns(true).map(column =>
1354
- <th
1355
- key={column.name}
1356
- scope="col"
1357
- onMouseDown={(e) =>
1358
- {
1359
- if ((e.target as HTMLElement).closest('[data-resize-handle]'))
1360
- return;
1361
- if (!this.isSortEnabled())
1362
- return;
1363
- let t = new BaseMetaTable(null, column.table.name, column.table.text);
1364
- let c = new BaseMetaColumn(t, column.name, column.text, "", false);
1365
- let items = this.getSortItems();
1366
- if (!items || items.length === 0 || items[0].table.name !== t.name || items[0].column.name !== c.name)
1367
- this.setSortItems([new SortItem(t, c, true)]);
1368
- else
1369
- {
1370
- if (items[0].ascending)
1371
- this.setSortItems([new SortItem(t, c, false)]);
1372
- else
1373
- this.setSortItems(null);
1374
- }
1375
- }}
1376
- style={{ width: this.getColumnWidth(column) }}
1377
- >
1378
- <span className={Styles.ns_th_title}>
1379
- {column.text + " " + getSortSign(column)}
1380
- </span>
1381
- <div
1382
- data-resize-handle="true"
1383
- className={Styles.ns_resize_handle}
1384
- onMouseDown={(e) => this.startResize(e, column)}
1385
- onClick={(e) => { e.stopPropagation() }}
1386
- />
1387
- </th>
1388
- )
1389
- }
1390
- </tr>
1391
- </thead>
1392
-
1393
- <tbody className={Styles.ns_tbody}>
1394
- {content}
1395
- </tbody>
1396
- </table>
1397
-
1398
- {
1399
- this.state.model.show &&
1400
- <NSDialogInfo
1401
- description={this.state.model.description}
1402
- onClose={() => { this.hideModal(); }}
1403
- title={this.state.title}
1404
- show_as_code={true}
1405
- >
1406
- <></>
1407
- </NSDialogInfo>
1408
- }
1409
- {
1410
- this.state.showChart &&
1411
- <NSTableChart<RowType>
1412
- name={this.props.name}
1413
- columns={this.getColumns(true)}
1414
- rows={this.state.rows ?? []}
1415
- selectedRows={this.state.selectedRows}
1416
- onClose={() => this.setState({ showChart: false })}
1417
- />
1418
- }
1419
- {
1420
- this.state.showSortDialog && this.isSortEnabled() &&
1421
- <NSSortDialog
1422
- sorts={this.getSortItems()}
1423
- getColumns={() => this.getColumns(null)}
1424
- onCancel={() => this.setState({ showSortDialog: false })}
1425
- onApply={sorts =>
1426
- {
1427
- this.setSortItems(sorts);
1428
- this.setState({ showSortDialog: false });
1429
- }}
1430
- />
1431
- }
1432
- {
1433
- this.state.showReorderDialog &&
1434
- <NSReorderDialog
1435
- visibleColumnKeys={this.getVisibleColumns().map(c => c.table + "." + c.column)}
1436
- getColumns={() => this.getColumns(null)}
1437
- onCancel={() => this.setState({ showReorderDialog: false })}
1438
- onApply={orderedKeys =>
1439
- {
1440
- const all = this.getColumns(null);
1441
- const cs = orderedKeys
1442
- .map(k => all.find(c => c.table.name + "." + c.name === k))
1443
- .filter((c): c is TableColumnInfo => !!c);
1444
- this.setVisibleColumns(cs.map(c => ({ table: c.table.name, column: c.name })));
1445
- this.setState({ showReorderDialog: false });
1446
- }}
1447
- />
1448
- }
1449
- </div>
1450
- );
1451
- }
1
+ "use client";
2
+ import { BaseMetaColumn, BaseMetaTable, IStorageLocal, SortItem } from "namirasoft-core";
3
+ import { Component, createRef, ReactNode } from "react";
4
+ import { utils, writeFile } from 'xlsx';
5
+ import { BaseColumnFormatter } from '../formatter/BaseColumnFormatter';
6
+ import { IBaseComponentProps } from "../props/IBaseComponentProps";
7
+ import { NSButton, NSButtonProps } from './NSButton';
8
+ import { NSDialogInfo } from './NSDialogInfo';
9
+ import { NSDialogPageSelection } from './NSDialogPageSelection';
10
+ import { NSLoading } from './NSLoading';
11
+ import { NSNoData } from './NSNoData';
12
+ import { NSPagination } from './NSPagination';
13
+ import { NSReorderDialog } from './NSReorderDialog';
14
+ import { NSSortDialog } from './NSSortDialog';
15
+ import { NSTableChart } from './NSTableChart';
16
+ import Styles from './NSTable.module.css';
17
+
18
+ export interface TableInfo
19
+ {
20
+ name: string;
21
+ text: string;
22
+ }
23
+
24
+ export interface TableColumnInfo
25
+ {
26
+ table: TableInfo;
27
+ index: number;
28
+ type: string;
29
+ name: string;
30
+ text: string;
31
+ formatter: BaseColumnFormatter;
32
+ group?: string;
33
+ defaults?: {
34
+ hidden?: boolean;
35
+ };
36
+ }
37
+
38
+ export interface TableRowInfo<RowType>
39
+ {
40
+ index: number;
41
+ value: RowType;
42
+ }
43
+
44
+ export interface TableCellInfo<RowType>
45
+ {
46
+ column: TableColumnInfo;
47
+ row: TableRowInfo<RowType>;
48
+ value: any;
49
+ formatted: any;
50
+ }
51
+
52
+ interface PrintableRow<RowType>
53
+ {
54
+ value: RowType;
55
+ index: number;
56
+ depth: number;
57
+ ancestorHasNext: boolean[];
58
+ isLastChild: boolean;
59
+ }
60
+
61
+ export interface NSTableProps<RowType> extends IBaseComponentProps
62
+ {
63
+ name: string;
64
+ checkbox: boolean;
65
+ columns: TableColumnInfo[];
66
+ getRows: (page: number, size: number, sorts: SortItem[]) => Promise<{ rows: RowType[], count: number }>;
67
+ getRowKey: (row: TableRowInfo<RowType>) => string;
68
+ getRowAttributes?: (row_value: RowType, row_index: number) => { [key: string]: any };
69
+ getColumnAttributes?: (column: TableColumnInfo) => { [key: string]: any };
70
+ getCellFormattedValue?: (value: any, column: TableColumnInfo, row: TableRowInfo<RowType>, printable: boolean) => any;
71
+ onColumnClick?: (e: React.MouseEvent<HTMLTableCellElement, MouseEvent>, column: TableColumnInfo) => void;
72
+ onRowClick?: (e: React.MouseEvent<HTMLTableRowElement, MouseEvent>, row: TableRowInfo<RowType>) => void;
73
+ onCellClick?: (e: React.MouseEvent<HTMLTableCellElement, MouseEvent>, cell: TableCellInfo<RowType>) => boolean | undefined;
74
+ getRowCheckboxDisabled?: (row: TableRowInfo<RowType>) => boolean;
75
+ buttons?: NSButtonProps[];
76
+ children?: ReactNode;
77
+ headerChildren?: ReactNode;
78
+ hierarchy?: { id: string, parent: string, group?: string[] };
79
+ defaultExpanded?: boolean;
80
+ onSelectedIDsChanged?: () => void;
81
+ onSortChanged?: (items: SortItem[] | null) => void;
82
+ onVisibleColumnsChanged?: (visible_columns: { table: string, column: string }[]) => void;
83
+ sort?: NSTableSortConfig;
84
+ ui?: {
85
+ pagination?: boolean;
86
+ button_panel?: boolean | NSTableButtonPanelConfig;
87
+ grow?: boolean;
88
+ };
89
+ }
90
+
91
+ export interface NSTableSortConfig
92
+ {
93
+ enabled?: boolean;
94
+ onSort?: (items: SortItem[] | null) => void;
95
+ }
96
+
97
+ export type NSTableButtonKey = "reorder" | "sort" | "fullscreen" | "chart" | "export" | "print" | "refresh" | "expand";
98
+ export type NSTableButtonPanelConfig = { [key in NSTableButtonKey]?: boolean };
99
+
100
+ interface NSTableState<RowType>
101
+ {
102
+ columns: TableColumnInfo[];
103
+ visible_columns: { table: string, column: string }[];
104
+ totalItems: number;
105
+ rows: RowType[] | null;
106
+ title?: string;
107
+ model: {
108
+ show: boolean;
109
+ description?: string;
110
+ };
111
+ selectedIDs: string[];
112
+ selectedRows: RowType[];
113
+ expandedIDs: string[];
114
+ pageSelectionMode: PageSelectionMode;
115
+ sortItems: SortItem[] | null;
116
+ columnWidths: { [key: string]: number };
117
+ fullscreen: boolean;
118
+ showChart: boolean;
119
+ showSortDialog: boolean;
120
+ showReorderDialog: boolean;
121
+ }
122
+
123
+ enum PageSelectionMode
124
+ {
125
+ Hidden = "Hidden",
126
+ Print = "Print",
127
+ CSV = "CSV"
128
+ }
129
+
130
+ export class NSTable<RowType> extends Component<NSTableProps<RowType>, NSTableState<RowType>>
131
+ {
132
+ private Table_Ref = createRef<HTMLTableElement>();
133
+ NSPagination = createRef<NSPagination>();
134
+
135
+ private LastSelectedRowID: number | null = null;
136
+ private resizeMoveHandler: ((e: MouseEvent) => void) | null = null;
137
+ private resizeUpHandler: (() => void) | null = null;
138
+
139
+ constructor(props: NSTableProps<RowType>)
140
+ {
141
+ super(props);
142
+ this.state = {
143
+ columns: props.columns,
144
+ visible_columns: this.getVisibleColumns(),
145
+ rows: null,
146
+ totalItems: 0,
147
+ model: { show: false },
148
+ selectedIDs: [],
149
+ selectedRows: [],
150
+ expandedIDs: [],
151
+ pageSelectionMode: PageSelectionMode.Hidden,
152
+ sortItems: this.getSortItems(),
153
+ columnWidths: this.loadColumnWidths(),
154
+ fullscreen: false,
155
+ showChart: false,
156
+ showSortDialog: false,
157
+ showReorderDialog: false,
158
+ };
159
+ this.getSortItemsKey = this.getSortItemsKey.bind(this);
160
+ this.getSortItems = this.getSortItems.bind(this);
161
+ this.setSortItems = this.setSortItems.bind(this);
162
+ this.getVisibleColumnsKey = this.getVisibleColumnsKey.bind(this);
163
+ this.getVisibleColumns = this.getVisibleColumns.bind(this);
164
+ this.setVisibleColumns = this.setVisibleColumns.bind(this);
165
+ this.setColumns = this.setColumns.bind(this);
166
+ this.setColumns = this.setColumns.bind(this);
167
+ this.setRows = this.setRows.bind(this);
168
+ this.foreachColumn = this.foreachColumn.bind(this);
169
+ this.getColumns = this.getColumns.bind(this);
170
+ this.showModal = this.showModal.bind(this);
171
+ this.hideModal = this.hideModal.bind(this);
172
+ this.onPageChange = this.onPageChange.bind(this);
173
+ this.isSomeSelected = this.isSomeSelected.bind(this);
174
+ this.toggleAllSelections = this.toggleAllSelections.bind(this);
175
+ this.print = this.print.bind(this);
176
+ this.exportCSV = this.exportCSV.bind(this);
177
+ this.isAllSelected = this.isAllSelected.bind(this);
178
+ this.reload = this.reload.bind(this);
179
+ this.toggleFullscreen = this.toggleFullscreen.bind(this);
180
+ this.toggleExpandAll = this.toggleExpandAll.bind(this);
181
+ }
182
+ private toggleFullscreen()
183
+ {
184
+ this.setState({ fullscreen: !this.state.fullscreen });
185
+ }
186
+ private getSortItemsKey(): string
187
+ {
188
+ return "ns_table_" + this.props.name + "_sort_items";
189
+ }
190
+ getSortItems(): SortItem[]
191
+ {
192
+ let storage = new IStorageLocal();
193
+ let item = storage.get(this.getSortItemsKey(), "");
194
+ try
195
+ {
196
+ return SortItem.parse(item);
197
+ } catch (error)
198
+ {
199
+ }
200
+ return [];
201
+ }
202
+ isSortEnabled(): boolean
203
+ {
204
+ return this.props.sort?.enabled ?? true;
205
+ }
206
+ private hasCustomSort(): boolean
207
+ {
208
+ return !!this.props.sort?.onSort;
209
+ }
210
+ setSortItems(items: SortItem[] | null)
211
+ {
212
+ if (!this.isSortEnabled())
213
+ return;
214
+ let storage = new IStorageLocal();
215
+ storage.set(this.getSortItemsKey(), SortItem.stringify(items));
216
+ this.setState({ sortItems: items }, () =>
217
+ {
218
+ this.props.onSortChanged?.(items);
219
+ this.props.sort?.onSort?.(items);
220
+ });
221
+ }
222
+ private compareRowsBySortItems(a: RowType, b: RowType, items: SortItem[]): number
223
+ {
224
+ for (let item of items)
225
+ {
226
+ let column = this.state.columns.find(c => c.table.name === item.table.name && c.name === item.column.name);
227
+ let va = (a as any)[item.column.name];
228
+ let vb = (b as any)[item.column.name];
229
+ let cmp = column ? column.formatter.compare(va, vb) : BaseColumnFormatter.smartCompare(va, vb);
230
+ if (cmp !== 0)
231
+ return item.ascending ? cmp : -cmp;
232
+ }
233
+ return 0;
234
+ }
235
+ private sortRowsClient(rows: RowType[]): RowType[]
236
+ {
237
+ let items = this.state.sortItems;
238
+ if (!items || items.length === 0)
239
+ return rows;
240
+ let decorated = rows.map((row, index) => ({ row, index }));
241
+ decorated.sort((x, y) =>
242
+ {
243
+ let cmp = this.compareRowsBySortItems(x.row, y.row, items!);
244
+ return cmp !== 0 ? cmp : x.index - y.index;
245
+ });
246
+ return decorated.map(d => d.row);
247
+ }
248
+ private getDisplayRows(): RowType[] | null
249
+ {
250
+ let rows = this.state.rows;
251
+ if (!rows || !this.isSortEnabled() || this.hasCustomSort())
252
+ return rows;
253
+ return this.sortRowsClient(rows);
254
+ }
255
+ private getVisibleColumnsKey(): string
256
+ {
257
+ return "ns_table_" + this.props.name + "_visible_columns";
258
+ }
259
+ getVisibleColumns(): { table: string, column: string }[]
260
+ {
261
+ let storage = new IStorageLocal();
262
+ let item = storage.get(this.getVisibleColumnsKey(), "");
263
+ try
264
+ {
265
+ return JSON.parse(item);
266
+ } catch (error)
267
+ {
268
+ }
269
+ return [];
270
+ }
271
+ setVisibleColumns(visible_columns: { table: string, column: string }[])
272
+ {
273
+ let storage = new IStorageLocal();
274
+ storage.set(this.getVisibleColumnsKey(), JSON.stringify(visible_columns));
275
+ this.setState({ visible_columns }, () =>
276
+ {
277
+ this.props.onVisibleColumnsChanged?.(visible_columns);
278
+ });
279
+ }
280
+ setColumns(columns: TableColumnInfo[])
281
+ {
282
+ this.setState({ columns });
283
+ }
284
+ setRows(rows: RowType[] | null, totalItems: number)
285
+ {
286
+ this.setState({ rows, totalItems });
287
+ }
288
+ foreachColumn(visible: boolean | null, handler: (column: TableColumnInfo) => void)
289
+ {
290
+ let vs = this.getVisibleColumns();
291
+ let shouldBeVisible = (c: TableColumnInfo) =>
292
+ {
293
+ if (vs.length === 0)
294
+ return !c.defaults?.hidden;
295
+ return vs.filter(v => v.table === c.table.name && v.column === c.name).length > 0;
296
+ };
297
+ this.state.columns.forEach(c =>
298
+ {
299
+ if (visible === null || shouldBeVisible(c) === visible)
300
+ handler(c);
301
+ })
302
+ }
303
+ getColumns(visible: boolean | null): TableColumnInfo[]
304
+ {
305
+ let columns: TableColumnInfo[] = [];
306
+ this.foreachColumn(visible, c => columns.push(c));
307
+ if (visible === true)
308
+ {
309
+ let vs = this.getVisibleColumns();
310
+ if (vs.length > 0)
311
+ columns.sort((a, b) =>
312
+ {
313
+ let ai = vs.findIndex(v => v.table === a.table.name && v.column === a.name);
314
+ let bi = vs.findIndex(v => v.table === b.table.name && v.column === b.name);
315
+ return ai - bi;
316
+ });
317
+ }
318
+ return columns;
319
+ }
320
+ private showModal(description: string, title?: string)
321
+ {
322
+ this.setState({ model: { show: true, description }, title });
323
+ }
324
+ private hideModal()
325
+ {
326
+ this.setState({ model: { show: false } });
327
+ }
328
+ private onPageChange(page: number, size: number)
329
+ {
330
+ this.reload(page, size);
331
+ }
332
+ public async reload(page: number | null, size: number | null)
333
+ {
334
+ this.setRows(null, 0);
335
+ if (page === null)
336
+ page = this.NSPagination.current?.getCurrentPage() ?? 0;
337
+ if (size === null)
338
+ size = this.NSPagination.current?.getPageSize() ?? 0;
339
+ try
340
+ {
341
+ let sorts = this.getSortItems();
342
+ let res = await this.props.getRows(page, size, sorts);
343
+ this.setRows(res.rows, res.count);
344
+ } catch (error)
345
+ {
346
+ }
347
+ }
348
+ private toggleAllSelections()
349
+ {
350
+ let selectedIDs: string[] = [];
351
+ let selectedRows: RowType[] = [];
352
+ this.LastSelectedRowID = null;
353
+
354
+ if (this.getSelectedIDs().length === 0)
355
+ {
356
+ this.state.rows?.forEach((row, rowIndex) =>
357
+ {
358
+ let rowInfo = { index: rowIndex, value: row };
359
+ if (this.props.getRowCheckboxDisabled?.(rowInfo))
360
+ return;
361
+ let id = this.props.getRowKey(rowInfo);
362
+ selectedIDs.push(id);
363
+ selectedRows.push(row);
364
+ })
365
+ }
366
+
367
+ this.setSelectedIDs(selectedIDs, selectedRows);
368
+ }
369
+ private escapeHtml(value: any): string
370
+ {
371
+ if (value === null || value === undefined)
372
+ return "";
373
+ let text = typeof value === "object" ? JSON.stringify(value) : String(value);
374
+ return text
375
+ .replace(/&/g, "&amp;")
376
+ .replace(/</g, "&lt;")
377
+ .replace(/>/g, "&gt;")
378
+ .replace(/"/g, "&quot;")
379
+ .replace(/'/g, "&#39;");
380
+ }
381
+ private getPrintableRows(rows: RowType[], expandAll: boolean): PrintableRow<RowType>[]
382
+ {
383
+ let result: PrintableRow<RowType>[] = [];
384
+ if (!this.props.hierarchy)
385
+ {
386
+ rows.forEach((value, index) => result.push({ value, index, depth: 0, ancestorHasNext: [], isLastChild: true }));
387
+ return result;
388
+ }
389
+ let childrenMap = this.buildChildrenMap(rows);
390
+ let indexOfRow = new Map<RowType, number>();
391
+ rows.forEach((row, index) => indexOfRow.set(row, index));
392
+ let parentCol = this.props.hierarchy.parent;
393
+ let ids = new Set<string>();
394
+ rows.forEach(row => ids.add(this.getRowIdValue(row) + "" + this.getGroupKey(row)));
395
+ let topLevel = rows.filter(row =>
396
+ {
397
+ let pid = (row as any)[parentCol];
398
+ let pidKey = pid === null || pid === undefined ? "" : String(pid);
399
+ if (pidKey === "")
400
+ return true;
401
+ return !ids.has(pidKey + "" + this.getGroupKey(row));
402
+ });
403
+ let walk = (value: RowType, depth: number, ancestorHasNext: boolean[], isLastChild: boolean) =>
404
+ {
405
+ let index = indexOfRow.get(value) ?? 0;
406
+ result.push({ value, index, depth, ancestorHasNext, isLastChild });
407
+ let rowKey = this.props.getRowKey({ index, value });
408
+ if (!expandAll && !this.isExpanded(rowKey))
409
+ return;
410
+ let children = childrenMap?.get(this.getRowChildLookupKey(value)) ?? [];
411
+ let childAncestors = [...ancestorHasNext, !isLastChild];
412
+ children.forEach((child, childIndex) => walk(child, depth + 1, childAncestors, childIndex === children.length - 1));
413
+ };
414
+ topLevel.forEach((row, index) => walk(row, 0, [], index === topLevel.length - 1));
415
+ return result;
416
+ }
417
+ private buildTreeGuides(entry: PrintableRow<RowType>): string
418
+ {
419
+ if (entry.depth === 0)
420
+ return "";
421
+ let indent = 16;
422
+ let color = "#000";
423
+ let overhang = 8;
424
+ let guides = "";
425
+ for (let i = 0; i < entry.depth; i++)
426
+ {
427
+ let center = indent / 2;
428
+ let lines = "";
429
+ if (i < entry.depth - 1)
430
+ {
431
+ if (entry.ancestorHasNext[i])
432
+ lines += `<span style="position: absolute; inset-inline-start: ${center}px; top: 0; height: 100%; width: 1px; background: ${color};"></span>`;
433
+ }
434
+ else
435
+ {
436
+ lines += `<span style="position: absolute; inset-inline-start: ${center}px; top: -${overhang}px; height: calc(50% + ${overhang}px); width: 1px; background: ${color};"></span>`;
437
+ if (!entry.isLastChild)
438
+ lines += `<span style="position: absolute; inset-inline-start: ${center}px; top: 50%; height: 50%; width: 1px; background: ${color};"></span>`;
439
+ lines += `<span style="position: absolute; inset-inline-start: ${center}px; top: 50%; width: ${center - 2}px; height: 1px; background: ${color};"></span>`;
440
+ }
441
+ guides += `<span style="position: relative; display: inline-block; width: ${indent}px; align-self: stretch; flex: 0 0 auto;">${lines}</span>`;
442
+ }
443
+ return guides;
444
+ }
445
+ private buildPrintDocument(rows: RowType[], expandAll: boolean): string
446
+ {
447
+ let columns = this.getColumns(true);
448
+ let totalWidth = columns.reduce((sum, column) => sum + this.getColumnWidth(column), 0) || 1;
449
+ let colgroup = columns
450
+ .map(column => `<col style="width: ${(this.getColumnWidth(column) / totalWidth * 100).toFixed(4)}%" />`)
451
+ .join("");
452
+ let head = columns
453
+ .map(column => `<th>${this.escapeHtml(column.text)}</th>`)
454
+ .join("");
455
+ let printable = this.getPrintableRows(rows, expandAll);
456
+ let body = printable
457
+ .map((entry, entryIndex) =>
458
+ {
459
+ let row = { index: entry.index, value: entry.value };
460
+ let next = printable[entryIndex + 1];
461
+ let previous = printable[entryIndex - 1];
462
+ let removeTop = entry.depth > 0 && !!previous;
463
+ let removeBottom = !!next && next.depth > 0;
464
+ let edgeStyle = `${removeTop ? "border-top: none;" : ""}${removeBottom ? "border-bottom: none;" : ""}`;
465
+ let cells = columns
466
+ .map((column, columnIndex) =>
467
+ {
468
+ let text = this.escapeHtml(this.getCellTableInfo(column, row, true).formatted);
469
+ if (columnIndex === 0 && this.props.hierarchy && entry.depth > 0)
470
+ {
471
+ let guides = this.buildTreeGuides(entry);
472
+ let overlay = `<span style="position: absolute; top: 0; bottom: 0; inset-inline-start: 8px; display: flex; align-items: stretch;">${guides}</span>`;
473
+ return `<td style="position: relative; padding-inline-start: ${8 + entry.depth * 16}px;${edgeStyle}">${overlay}${text}</td>`;
474
+ }
475
+ if (columnIndex === 0 && edgeStyle)
476
+ return `<td style="${edgeStyle}">${text}</td>`;
477
+ return `<td>${text}</td>`;
478
+ })
479
+ .join("");
480
+ return `<tr>${cells}</tr>`;
481
+ })
482
+ .join("");
483
+ let direction = document.documentElement.dir || "ltr";
484
+ return `
485
+ <!DOCTYPE html>
486
+ <html dir="${direction}">
487
+ <head>
488
+ <meta charset="utf-8" />
489
+ <title>${this.escapeHtml(this.props.name)}</title>
490
+ <style>
491
+ * { -webkit-print-color-adjust: exact; print-color-adjust: exact; }
492
+ body { font-family: sans-serif; margin: 16px; }
493
+ table { width: 100%; border-collapse: collapse; table-layout: fixed; }
494
+ th, td { border: 1px solid #000; padding: 8px; text-align: start; overflow-wrap: break-word; word-break: break-word; }
495
+ thead { display: table-header-group; }
496
+ tr { page-break-inside: avoid; }
497
+ </style>
498
+ </head>
499
+ <body>
500
+ <table>
501
+ <colgroup>${colgroup}</colgroup>
502
+ <thead><tr>${head}</tr></thead>
503
+ <tbody>${body}</tbody>
504
+ </table>
505
+ </body>
506
+ </html>
507
+ `;
508
+ }
509
+ print(rows: RowType[], expandAll: boolean = false)
510
+ {
511
+ let blob = new Blob([this.buildPrintDocument(rows, expandAll)], { type: "text/html" });
512
+ let url = URL.createObjectURL(blob);
513
+ let printWindow = window.open(url, "_blank", "height=800,width=800");
514
+ if (!printWindow)
515
+ {
516
+ URL.revokeObjectURL(url);
517
+ this.showModal("Please allow pop-ups to print this table.", this.props.name);
518
+ return;
519
+ }
520
+ printWindow.onload = () =>
521
+ {
522
+ printWindow!.focus();
523
+ printWindow!.onafterprint = () =>
524
+ {
525
+ printWindow!.close();
526
+ URL.revokeObjectURL(url);
527
+ };
528
+ printWindow!.print();
529
+ };
530
+ }
531
+ exportCSV(rows: RowType[])
532
+ {
533
+ let columns = this.getColumns(true);
534
+ let header = columns.map(column => column.text);
535
+ let data = (rows || []).map((rowValue, rowIndex) =>
536
+ {
537
+ let row = { index: rowIndex, value: rowValue };
538
+ return columns.map(column =>
539
+ {
540
+ let formatted = this.getCellTableInfo(column, row, true).formatted;
541
+ if (formatted === null || formatted === undefined)
542
+ return "";
543
+ return typeof formatted === "object" ? JSON.stringify(formatted) : formatted;
544
+ });
545
+ });
546
+ let worksheet = utils.aoa_to_sheet([header, ...data]);
547
+ worksheet["!cols"] = columns.map(column => ({ wpx: this.getColumnWidth(column) }));
548
+ let workbook = utils.book_new();
549
+ utils.book_append_sheet(workbook, worksheet, this.props.name);
550
+ writeFile(workbook, this.props.name + ".xlsx");
551
+ }
552
+
553
+ handleSelectRow(row: TableRowInfo<RowType>, selected: boolean, ctrlKey: boolean, shiftKey: boolean)
554
+ {
555
+ let selectedIDs = this.state.selectedIDs;
556
+ let selectedRows = this.state.selectedRows;
557
+ let rowID = this.props.getRowKey(row);
558
+
559
+ if (shiftKey)
560
+ {
561
+ selectedRows = this.getRowsInRange(this.LastSelectedRowID ?? 0, row.index);
562
+ selectedIDs = selectedRows.map(row => this.props.getRowKey({ index: -1, value: row }));
563
+ }
564
+ else if (selected)
565
+ {
566
+ if (this.isMultipleSelected() && !ctrlKey)
567
+ {
568
+ selectedIDs = [rowID];
569
+ selectedRows = [row.value];
570
+ this.LastSelectedRowID = row.index;
571
+ }
572
+ else
573
+ {
574
+ selectedIDs = selectedIDs.filter(x => x !== rowID);
575
+ selectedRows = selectedRows.filter(x => this.props.getRowKey({ index: -1, value: x }) !== rowID);
576
+ this.LastSelectedRowID = ctrlKey ? row.index : null;
577
+ }
578
+ }
579
+ else
580
+ {
581
+ if (ctrlKey)
582
+ {
583
+ selectedIDs.push(rowID);
584
+ selectedRows.push(row.value);
585
+ }
586
+ else
587
+ {
588
+ selectedIDs = [rowID];
589
+ selectedRows = [row.value];
590
+ }
591
+ this.LastSelectedRowID = row.index;
592
+ }
593
+
594
+ this.setSelectedIDs(selectedIDs, selectedRows);
595
+ }
596
+ getRowsInRange(fromIndex: number, toIndex: number)
597
+ {
598
+ let rows = this.getDisplayRows();
599
+ if (!rows)
600
+ return [];
601
+
602
+ let start = Math.min(fromIndex, toIndex);
603
+ let end = Math.max(fromIndex, toIndex);
604
+
605
+ return rows.slice(start, end + 1);
606
+ }
607
+ getSelectedIDs(): string[]
608
+ {
609
+ return this.state.selectedIDs;
610
+ }
611
+ getSelectedRows(): RowType[]
612
+ {
613
+ return this.state.selectedRows;
614
+ }
615
+ setSelectedIDs(ids: string[], rows: RowType[], callback?: () => void)
616
+ {
617
+ this.setState({ selectedIDs: ids, selectedRows: rows }, () =>
618
+ {
619
+ if (this.props.onSelectedIDsChanged)
620
+ this.props.onSelectedIDsChanged();
621
+ if (callback)
622
+ callback();
623
+ });
624
+ }
625
+ private getGroupKey(row: RowType): string
626
+ {
627
+ let h = this.props.hierarchy;
628
+ if (!h || !h.group || h.group.length === 0)
629
+ return "";
630
+ return h.group.map(g =>
631
+ {
632
+ let v = (row as any)[g];
633
+ return v === null || v === undefined ? "" : String(v);
634
+ }).join("");
635
+ }
636
+ private buildChildrenMap(rows: RowType[] | null): Map<string, RowType[]> | null
637
+ {
638
+ let h = this.props.hierarchy;
639
+ if (!h || !rows)
640
+ return null;
641
+ let map = new Map<string, RowType[]>();
642
+ for (let r of rows)
643
+ {
644
+ let pid = (r as any)[h.parent];
645
+ let pidKey = pid === null || pid === undefined ? "" : String(pid);
646
+ let key = pidKey + "" + this.getGroupKey(r);
647
+ let arr = map.get(key);
648
+ if (!arr) { arr = []; map.set(key, arr); }
649
+ arr.push(r);
650
+ }
651
+ return map;
652
+ }
653
+ private getRowIdValue(row: RowType): string
654
+ {
655
+ let h = this.props.hierarchy;
656
+ if (!h) return "";
657
+ let v = (row as any)[h.id];
658
+ return v === null || v === undefined ? "" : String(v);
659
+ }
660
+ private getRowChildLookupKey(row: RowType): string
661
+ {
662
+ return this.getRowIdValue(row) + "" + this.getGroupKey(row);
663
+ }
664
+ private isExpanded(rowKey: string): boolean
665
+ {
666
+ let inList = this.state.expandedIDs.includes(rowKey);
667
+ return this.props.defaultExpanded ? !inList : inList;
668
+ }
669
+ private toggleExpanded(rowKey: string)
670
+ {
671
+ let ids = this.state.expandedIDs;
672
+ if (ids.includes(rowKey))
673
+ ids = ids.filter(x => x !== rowKey);
674
+ else
675
+ ids = [...ids, rowKey];
676
+ this.setState({ expandedIDs: ids });
677
+ }
678
+ private getExpandableRowKeys(): string[]
679
+ {
680
+ let rows = this.state.rows;
681
+ if (!this.props.hierarchy || !rows)
682
+ return [];
683
+ let childrenMap = this.buildChildrenMap(rows);
684
+ if (!childrenMap)
685
+ return [];
686
+ let keys: string[] = [];
687
+ rows.forEach((value, index) =>
688
+ {
689
+ let children = childrenMap.get(this.getRowChildLookupKey(value)) ?? [];
690
+ if (children.length > 0)
691
+ keys.push(this.props.getRowKey({ index, value }));
692
+ });
693
+ return keys;
694
+ }
695
+ private isAnyExpanded(): boolean
696
+ {
697
+ return this.getExpandableRowKeys().some(key => this.isExpanded(key));
698
+ }
699
+ private toggleExpandAll()
700
+ {
701
+ let keys = this.getExpandableRowKeys();
702
+ let expand = !this.isAnyExpanded();
703
+ let inList = this.props.defaultExpanded ? !expand : expand;
704
+ this.setState({ expandedIDs: inList ? keys : [] });
705
+ }
706
+ private isAllSelected(): boolean
707
+ {
708
+ if (!this.state.rows || this.state.rows.length === 0)
709
+ return false;
710
+ const selectableCount = this.state.rows.filter((row, i) =>
711
+ !this.props.getRowCheckboxDisabled?.({ index: i, value: row })
712
+ ).length;
713
+ return selectableCount > 0 && this.state.selectedIDs.length === selectableCount;
714
+ }
715
+ private isSomeSelected(): boolean
716
+ {
717
+ return this.state.selectedIDs.length > 0 && !this.isAllSelected();
718
+ }
719
+ private isMultipleSelected(): boolean
720
+ {
721
+ return this.state.selectedIDs.length > 1;
722
+ }
723
+ private getCellTableInfo(column: TableColumnInfo, row: TableRowInfo<RowType>, printable: boolean)
724
+ {
725
+ let value = (row.value as any)[column.name];
726
+ let formatted = this.getCellFormattedValue(value, column, row, printable);
727
+ return { column, row, value, formatted };
728
+ }
729
+ private getCellFormattedValue(value: any, column: TableColumnInfo, row: TableRowInfo<RowType>, printable: boolean)
730
+ {
731
+ if (this.props.getCellFormattedValue)
732
+ return this.props.getCellFormattedValue(value, column, row, printable);
733
+ return column.formatter.format(value, column, row, printable);
734
+ }
735
+ private getColumnWidthsKey(): string
736
+ {
737
+ return "ns_table_" + this.props.name + "_column_widths";
738
+ }
739
+ private loadColumnWidths(): { [key: string]: number }
740
+ {
741
+ let storage = new IStorageLocal();
742
+ let item = storage.get(this.getColumnWidthsKey(), "");
743
+ try
744
+ {
745
+ return JSON.parse(item);
746
+ }
747
+ catch
748
+ {
749
+ return {};
750
+ }
751
+ }
752
+ private saveColumnWidths(widths: { [key: string]: number })
753
+ {
754
+ let storage = new IStorageLocal();
755
+ storage.set(this.getColumnWidthsKey(), JSON.stringify(widths));
756
+ }
757
+ private getColumnKey(column: TableColumnInfo): string
758
+ {
759
+ return column.table.name + "_" + column.name;
760
+ }
761
+ private getColumnWidth(column: TableColumnInfo): number
762
+ {
763
+ let key = this.getColumnKey(column);
764
+ if (this.state.columnWidths[key] !== undefined)
765
+ return this.state.columnWidths[key];
766
+ let w = column.formatter.width;
767
+ if (typeof w === 'number') return w;
768
+ if (typeof w === 'string')
769
+ {
770
+ let parsed = parseInt(w, 10);
771
+ if (!isNaN(parsed)) return parsed;
772
+ }
773
+ return 120;
774
+ }
775
+ private getGridTemplateColumns(): string
776
+ {
777
+ let parts: string[] = [];
778
+ if (this.props.checkbox)
779
+ parts.push("40px");
780
+ this.getColumns(true).forEach(column =>
781
+ {
782
+ parts.push(this.getColumnWidth(column) + "px");
783
+ });
784
+ return parts.join(" ");
785
+ }
786
+ private startResize(e: React.MouseEvent, column: TableColumnInfo)
787
+ {
788
+ e.preventDefault();
789
+ e.stopPropagation();
790
+ let key = this.getColumnKey(column);
791
+ let startX = e.clientX;
792
+ let startWidth = this.getColumnWidth(column);
793
+ let currentWidths = { ...this.state.columnWidths };
794
+ document.body.style.cursor = 'col-resize';
795
+ document.body.style.userSelect = 'none';
796
+ const onMouseMove = (ev: MouseEvent) =>
797
+ {
798
+ let delta = ev.clientX - startX;
799
+ let newWidth = Math.max(80, startWidth + delta);
800
+ currentWidths = { ...currentWidths, [key]: newWidth };
801
+ this.setState({ columnWidths: currentWidths });
802
+ };
803
+ const onMouseUp = () =>
804
+ {
805
+ document.body.style.cursor = '';
806
+ document.body.style.userSelect = '';
807
+ document.removeEventListener('mousemove', onMouseMove);
808
+ document.removeEventListener('mouseup', onMouseUp);
809
+ this.resizeMoveHandler = null;
810
+ this.resizeUpHandler = null;
811
+ this.saveColumnWidths(currentWidths);
812
+ };
813
+ this.resizeMoveHandler = onMouseMove;
814
+ this.resizeUpHandler = onMouseUp;
815
+ document.addEventListener('mousemove', onMouseMove);
816
+ document.addEventListener('mouseup', onMouseUp);
817
+ }
818
+ override componentDidMount(): void
819
+ {
820
+ if (!this.NSPagination.current)
821
+ this.reload(null, null);
822
+ }
823
+ override componentWillUnmount(): void
824
+ {
825
+ if (this.resizeMoveHandler)
826
+ document.removeEventListener('mousemove', this.resizeMoveHandler);
827
+ if (this.resizeUpHandler)
828
+ document.removeEventListener('mouseup', this.resizeUpHandler);
829
+ }
830
+ override render()
831
+ {
832
+
833
+ let show_pagination = this.props.ui?.pagination ?? true;
834
+ let button_panel_config = this.props.ui?.button_panel;
835
+ let show_button_panel = button_panel_config !== false;
836
+ let isButtonEnabled = (key: NSTableButtonKey): boolean =>
837
+ {
838
+ if (button_panel_config === undefined || typeof button_panel_config === "boolean")
839
+ return true;
840
+ return button_panel_config[key] ?? true;
841
+ };
842
+ let getRowAttributes = (row_value: RowType, row_index: number) =>
843
+ {
844
+ if (this.props.getRowAttributes)
845
+ return this.props.getRowAttributes(row_value, row_index);
846
+ return {};
847
+ }
848
+ let getColumnAttributes = (column: TableColumnInfo) =>
849
+ {
850
+ if (this.props.getColumnAttributes)
851
+ return this.props.getColumnAttributes(column);
852
+ return {};
853
+ }
854
+ let onColumnClick = (e: React.MouseEvent<HTMLTableCellElement, MouseEvent>, column: TableColumnInfo) =>
855
+ {
856
+ if ((e.target as HTMLElement).tagName !== "SPAN")
857
+ return;
858
+
859
+ if (this.props.onColumnClick)
860
+ this.props.onColumnClick(e, column);
861
+ }
862
+ let onRowClick = (e: React.MouseEvent<HTMLTableRowElement, MouseEvent>, row: TableRowInfo<RowType>, selected: boolean) =>
863
+ {
864
+ const target = e.target as HTMLElement;
865
+
866
+ if (target.tagName === "TR" || target.tagName === "TD" || target.closest("td")?.dataset.cellType === "checkbox")
867
+ this.handleSelectRow(row, selected, e.ctrlKey, e.shiftKey);
868
+
869
+ if (this.props.onRowClick)
870
+ this.props.onRowClick(e, row);
871
+ };
872
+ let onCellClick = (e: React.MouseEvent<HTMLTableCellElement, MouseEvent>, cell: TableCellInfo<RowType>) =>
873
+ {
874
+ if ((e.target as HTMLElement).tagName !== "SPAN")
875
+ return;
876
+
877
+ let overided = cell.column.formatter.onclick_overrided;
878
+ if (this.props.onCellClick)
879
+ overided = this.props.onCellClick(e, cell) ?? true;
880
+ if (!overided)
881
+ this.showModal(cell.formatted, cell.column.text);
882
+ };
883
+ let gridTemplateColumns = this.getGridTemplateColumns();
884
+ let content;
885
+ if (this.state.rows === null)
886
+ {
887
+ content = (
888
+ <tr className="d-flex justify-content-center align-items-center p-0">
889
+ <td className="w-100 h-100 m-0 mh-100">
890
+ <NSLoading
891
+ title="Loading table rows ..."
892
+ />
893
+ </td>
894
+ </tr>
895
+ );
896
+ }
897
+ else if (this.state.rows.length === 0)
898
+ {
899
+ content = (
900
+ <tr className="d-flex justify-content-center align-items-center p-0">
901
+ <td className="w-100 h-100 m-0 mh-100">
902
+ <NSNoData lable='No Data' classList={["p-3"]} />
903
+ </td>
904
+ </tr>
905
+ );
906
+ }
907
+ else
908
+ {
909
+ let displayRows = this.getDisplayRows() ?? [];
910
+ let hierarchical = !!this.props.hierarchy;
911
+ let childrenMap = this.buildChildrenMap(displayRows);
912
+ let indexOfRow = new Map<RowType, number>();
913
+ displayRows.forEach((r, i) => indexOfRow.set(r, i));
914
+ let INDENT = 16;
915
+ let lineColor = "rgba(20, 27, 92, 0.35)";
916
+ let renderRow = (rowValue: RowType, rowIndex: number, depth: number, parentKey: string, ancestorHasNext: boolean[], isLastChild: boolean): React.ReactNode[] =>
917
+ {
918
+ let row = { index: rowIndex, value: rowValue };
919
+ let rowKey = this.props.getRowKey(row);
920
+ let reactKey = parentKey ? `${parentKey}>${rowKey}` : rowKey;
921
+ let isSelected = this.state.selectedIDs.includes(rowKey);
922
+ let isLastSelected = this.LastSelectedRowID === row.index;
923
+ let isCheckboxDisabled = this.props.getRowCheckboxDisabled?.(row) ?? false;
924
+ let children: RowType[] = [];
925
+ if (hierarchical && childrenMap)
926
+ {
927
+ let lookupKey = this.getRowChildLookupKey(rowValue);
928
+ children = childrenMap.get(lookupKey) ?? [];
929
+ }
930
+ let hasChildren = children.length > 0;
931
+ let expanded = this.isExpanded(rowKey);
932
+ let rowAttrs = getRowAttributes(rowValue, rowIndex);
933
+ let nodes: React.ReactNode[] = [];
934
+ nodes.push(
935
+ <tr
936
+ key={reactKey}
937
+ {...rowAttrs}
938
+ style={{ ...(rowAttrs.style || {}), gridTemplateColumns }}
939
+ className={`${isSelected ? Styles.ns_selected : ""} ${isLastSelected ? Styles.ns_last_selected : ""}`.trim()}
940
+ onClick={(e) => !isCheckboxDisabled && onRowClick(e, row, isSelected)}
941
+ >
942
+ {
943
+ this.props.checkbox &&
944
+ <td data-cell-type="checkbox">
945
+ <label
946
+ htmlFor={`checkbox_device_${rowKey}`}
947
+ className={Styles.ns_check_box_label}
948
+ >
949
+ <input
950
+ id={`checkbox_device_${rowKey}`}
951
+ type="checkbox"
952
+ checked={isSelected}
953
+ disabled={isCheckboxDisabled}
954
+ className={Styles.ns_check_box}
955
+ onChange={() => { }}
956
+ />
957
+ </label>
958
+ </td>
959
+ }
960
+ {
961
+ this.getColumns(true).map((column, index) =>
962
+ {
963
+ column.index = index + (this.props.checkbox ? 1 : 0);
964
+ let cell = this.getCellTableInfo(column, row, false);
965
+ let isFirstDataColumn = index === 0;
966
+ return (
967
+ <td
968
+ key={column.index}
969
+ {...getColumnAttributes(column)}
970
+ data-label={`${column.text}: `}
971
+ width={column.formatter.width}
972
+ className=''
973
+ style={isFirstDataColumn && hierarchical ? { overflow: "visible" } : undefined}
974
+ onClick={(e) =>
975
+ {
976
+ onColumnClick(e, column);
977
+ onCellClick(e, cell);
978
+ }}
979
+ >
980
+ {isFirstDataColumn && hierarchical ? (
981
+ <div
982
+ style={{
983
+ display: "flex",
984
+ alignItems: "center",
985
+ }}
986
+ >
987
+ {Array.from({ length: depth }).map((_, i) =>
988
+ {
989
+ let isLastLevel = i === depth - 1;
990
+ let drawVerticalTop = i < depth - 1 ? ancestorHasNext[i] : true;
991
+ let drawVerticalBottom = i < depth - 1 ? ancestorHasNext[i] : !isLastChild;
992
+ let drawHorizontal = isLastLevel;
993
+ return (
994
+ <span
995
+ key={i}
996
+ style={{
997
+ position: "relative",
998
+ display: "inline-block",
999
+ width: INDENT,
1000
+ alignSelf: "stretch",
1001
+ flex: "0 0 auto",
1002
+ }}
1003
+ >
1004
+ {drawVerticalTop && (
1005
+ <span style={{
1006
+ position: "absolute",
1007
+ left: INDENT / 2,
1008
+ top: -24,
1009
+ height: "calc(50% + 24px)",
1010
+ width: 1,
1011
+ background: lineColor,
1012
+ }} />
1013
+ )}
1014
+ {drawVerticalBottom && (
1015
+ <span style={{
1016
+ position: "absolute",
1017
+ left: INDENT / 2,
1018
+ top: "50%",
1019
+ height: "calc(50% + 24px)",
1020
+ width: 1,
1021
+ background: lineColor,
1022
+ }} />
1023
+ )}
1024
+ {drawHorizontal && (
1025
+ <span style={{
1026
+ position: "absolute",
1027
+ left: INDENT / 2,
1028
+ top: "50%",
1029
+ width: INDENT / 2,
1030
+ height: 1,
1031
+ background: lineColor,
1032
+ }} />
1033
+ )}
1034
+ </span>
1035
+ );
1036
+ })}
1037
+ <span
1038
+ data-cell-type="tree-toggle"
1039
+ style={{
1040
+ display: "inline-flex",
1041
+ alignItems: "center",
1042
+ justifyContent: "center",
1043
+ marginRight: 6,
1044
+ }}
1045
+ onClick={(e) => e.stopPropagation()}
1046
+ >
1047
+ {hasChildren ? (
1048
+ <button
1049
+ type="button"
1050
+ onClick={(e) =>
1051
+ {
1052
+ e.stopPropagation();
1053
+ this.toggleExpanded(rowKey);
1054
+ }}
1055
+ style={{
1056
+ width: 18,
1057
+ height: 18,
1058
+ lineHeight: "16px",
1059
+ padding: 0,
1060
+ border: "1px solid rgba(20, 27, 92, 0.4)",
1061
+ borderRadius: 3,
1062
+ background: "#fff",
1063
+ cursor: "pointer",
1064
+ fontSize: 14,
1065
+ fontWeight: "bold",
1066
+ color: "rgba(20, 27, 92, 1)",
1067
+ }}
1068
+ aria-label={expanded ? "Collapse" : "Expand"}
1069
+ >
1070
+ {expanded ? "−" : "+"}
1071
+ </button>
1072
+ ) : (
1073
+ <span style={{ display: "inline-block", width: 18, height: 18 }} />
1074
+ )}
1075
+ </span>
1076
+ {cell.formatted ?? ""}
1077
+ </div>
1078
+ ) : (
1079
+ <>
1080
+ {cell.formatted ?? ""}
1081
+ </>
1082
+ )}
1083
+ </td>
1084
+ );
1085
+ })
1086
+ }
1087
+ </tr>
1088
+ );
1089
+ if (hierarchical && hasChildren && expanded)
1090
+ {
1091
+ let childAncestors = [...ancestorHasNext, !isLastChild];
1092
+ children.forEach((child, ci2) =>
1093
+ {
1094
+ let ci = indexOfRow.get(child) ?? -1;
1095
+ let lastChild = ci2 === children.length - 1;
1096
+ renderRow(child, ci, depth + 1, reactKey, childAncestors, lastChild).forEach(n => nodes.push(n));
1097
+ });
1098
+ }
1099
+ return nodes;
1100
+ };
1101
+ let topLevel: RowType[];
1102
+ if (hierarchical && this.props.hierarchy)
1103
+ {
1104
+ let parentCol = this.props.hierarchy.parent;
1105
+ let ids = new Set<string>();
1106
+ displayRows.forEach(r => ids.add(this.getRowIdValue(r) + "" + this.getGroupKey(r)));
1107
+ topLevel = displayRows.filter(r =>
1108
+ {
1109
+ let pid = (r as any)[parentCol];
1110
+ let pidKey = pid === null || pid === undefined ? "" : String(pid);
1111
+ if (pidKey === "")
1112
+ return true;
1113
+ return !ids.has(pidKey + "" + this.getGroupKey(r));
1114
+ });
1115
+ }
1116
+ else
1117
+ {
1118
+ topLevel = displayRows;
1119
+ }
1120
+ content = topLevel.flatMap((rowValue, i) => renderRow(rowValue, indexOfRow.get(rowValue) ?? 0, 0, "", [], i === topLevel.length - 1));
1121
+ }
1122
+
1123
+ let getSortSign = (column: TableColumnInfo) =>
1124
+ {
1125
+ if (this.state.sortItems)
1126
+ {
1127
+ let c = this.state.sortItems.filter(c => c.table.name === column.table.name && c.column.name === column.name)[0]
1128
+ if (c)
1129
+ {
1130
+ if (c.ascending)
1131
+ return "↑"
1132
+ return "↓";
1133
+ }
1134
+ }
1135
+ return "";
1136
+ };
1137
+
1138
+ let buttonPanel = show_button_panel && (
1139
+ <div className={Styles.ns_button_panel}>
1140
+ {
1141
+ this.state.pageSelectionMode !== PageSelectionMode.Hidden &&
1142
+ <NSDialogPageSelection<RowType>
1143
+ icon={
1144
+ this.state.pageSelectionMode === PageSelectionMode.Print ? "https://static.namirasoft.com/image/concept/print/blue.svg" :
1145
+ this.state.pageSelectionMode === PageSelectionMode.CSV ? "https://static.namirasoft.com/image/concept/export/blue.svg" : ""
1146
+ }
1147
+ current_page={this.NSPagination.current?.getCurrentPage() ?? 1}
1148
+ current_size={this.NSPagination.current?.getPageSize() ?? 0}
1149
+ max_page={this.state.totalItems}
1150
+ getItems={async (page, size, selected) =>
1151
+ {
1152
+ if (selected)
1153
+ {
1154
+ let rows: RowType[] = [];
1155
+ if (this.state.rows)
1156
+ for (let i = 0; i < this.state.rows.length; i++)
1157
+ {
1158
+ const row = { index: i, value: this.state.rows[i] };
1159
+ let id = this.props.getRowKey(row);
1160
+ if (this.state.selectedIDs.includes(id))
1161
+ rows.push(row.value);
1162
+ }
1163
+ return rows;
1164
+ }
1165
+ let res = await this.props.getRows(page, size, this.getSortItems());
1166
+ return res.rows;
1167
+ }}
1168
+ onFinish={async (items: RowType[], expandAll: boolean) =>
1169
+ {
1170
+ if (this.state.pageSelectionMode === PageSelectionMode.Print)
1171
+ this.print(items, expandAll);
1172
+ else if (this.state.pageSelectionMode === PageSelectionMode.CSV)
1173
+ this.exportCSV(items);
1174
+ }}
1175
+ showExpandAll={this.state.pageSelectionMode === PageSelectionMode.Print && !!this.props.hierarchy}
1176
+ onClose={() => this.setState({ pageSelectionMode: PageSelectionMode.Hidden })}
1177
+ />
1178
+ }
1179
+ {
1180
+ this.props.hierarchy && isButtonEnabled("expand") &&
1181
+ <NSButton
1182
+ attributes={{ title: this.isAnyExpanded() ? 'Collapse All' : 'Expand All' }}
1183
+ icon={{ src: "https://static.namirasoft.com/image/concept/hierarchy/blue.svg", alt: "expand" }}
1184
+ onClick={{
1185
+ action: this.toggleExpandAll,
1186
+ showLoading: false
1187
+ }}
1188
+ classList={[Styles.ns_table_button]}
1189
+ />
1190
+ }
1191
+ {
1192
+ isButtonEnabled("reorder") &&
1193
+ <NSButton
1194
+ attributes={{ title: 'Reorder' }}
1195
+ icon={{ src: "https://static.namirasoft.com/image/concept/grip-vertical/blue.svg", alt: "reorder" }}
1196
+ onClick={{
1197
+ action: () => this.setState({ showReorderDialog: true }),
1198
+ showLoading: false
1199
+ }}
1200
+ classList={[Styles.ns_table_button]}
1201
+ />
1202
+ }
1203
+ {
1204
+ isButtonEnabled("sort") && this.isSortEnabled() &&
1205
+ <NSButton
1206
+ attributes={{ title: 'Sort' }}
1207
+ icon={{ src: "https://static.namirasoft.com/image/concept/arrow-down-short-wide/blue.svg", alt: "sort" }}
1208
+ onClick={{
1209
+ action: () => this.setState({ showSortDialog: true }),
1210
+ showLoading: false
1211
+ }}
1212
+ classList={[Styles.ns_table_button]}
1213
+ />
1214
+ }
1215
+ {
1216
+ this.props.buttons?.map((button, index) => (
1217
+ <NSButton
1218
+ key={index}
1219
+ {...button}
1220
+ classList={[Styles.ns_table_button, ...(button.classList ?? [])]}
1221
+ />
1222
+ ))
1223
+ }
1224
+ {
1225
+ isButtonEnabled("fullscreen") &&
1226
+ <NSButton
1227
+ icon={{ src: `https://static.namirasoft.com/image/concept/full-screen/blue.svg` }}
1228
+ classList={[Styles.ns_table_button, Styles.ns_fullscreen_button]}
1229
+ onClick={{
1230
+ action: this.toggleFullscreen,
1231
+ showLoading: false
1232
+ }}
1233
+ attributes={{ title: this.state.fullscreen ? 'Minimize' : 'Maximize' }}
1234
+ />
1235
+ }
1236
+ {
1237
+ isButtonEnabled("chart") &&
1238
+ <NSButton
1239
+ attributes={{ title: 'Chart' }}
1240
+ icon={{ src: "https://static.namirasoft.com/image/concept/chart/blue.svg" }}
1241
+ onClick={{
1242
+ action: () =>
1243
+ {
1244
+ if (!this.state.rows || this.state.rows.length === 0)
1245
+ this.showModal(" ", "There is no data to display as a chart.");
1246
+ else
1247
+ this.setState({ showChart: true });
1248
+ },
1249
+ showLoading: false
1250
+ }}
1251
+ classList={[Styles.ns_table_button]}
1252
+ />
1253
+ }
1254
+ {
1255
+ isButtonEnabled("export") &&
1256
+ <NSButton
1257
+ icon={{ src: "https://static.namirasoft.com/image/concept/export/blue.svg" }}
1258
+ onClick={{
1259
+ action: () => { this.setState({ pageSelectionMode: PageSelectionMode.CSV }); },
1260
+ showLoading: false
1261
+ }}
1262
+ classList={[Styles.ns_table_button]}
1263
+ attributes={{ title: 'Export' }}
1264
+ />
1265
+ }
1266
+ {
1267
+ isButtonEnabled("print") &&
1268
+ <NSButton
1269
+ attributes={{ title: 'Print' }}
1270
+ icon={{ src: "https://static.namirasoft.com/image/concept/print/blue.svg" }}
1271
+ onClick={{
1272
+ action: () => { this.setState({ pageSelectionMode: PageSelectionMode.Print }); },
1273
+ showLoading: false
1274
+ }}
1275
+ classList={[Styles.ns_table_button]}
1276
+ />
1277
+ }
1278
+ {
1279
+ isButtonEnabled("refresh") &&
1280
+ <NSButton
1281
+ attributes={{ title: 'Refresh' }}
1282
+ icon={{ src: "https://static.namirasoft.com/image/concept/refresh/blue.svg" }}
1283
+ onClick={{
1284
+ action: async (onFinished) =>
1285
+ {
1286
+ this.reload(null, null).then(onFinished).catch(onFinished);
1287
+ }
1288
+ }}
1289
+ classList={[Styles.ns_table_button]}
1290
+ style={{ border: '1px solid rgb(3, 119, 255)' }}
1291
+ />
1292
+ }
1293
+ </div>
1294
+ );
1295
+
1296
+ return (
1297
+ <div
1298
+ id={this.props.id}
1299
+ className={[...this.props.classList ?? [], Styles.ns_table_wrapper, this.state.fullscreen ? Styles.ns_fullscreen : ""].filter(Boolean).join(" ")}
1300
+ style={{ ...this.props.style }}
1301
+ >
1302
+ {
1303
+ ((!this.state.fullscreen && this.props.headerChildren) || show_pagination || buttonPanel) &&
1304
+ <div className={Styles.ns_table_topbar}>
1305
+ <div className={Styles.ns_table_topbar_left}>
1306
+ {
1307
+ !this.state.fullscreen &&
1308
+ this.props.headerChildren
1309
+ }
1310
+ {
1311
+ show_pagination &&
1312
+ <NSPagination
1313
+ ref={this.NSPagination}
1314
+ totalItems={this.state.totalItems}
1315
+ onPageChange={this.onPageChange}
1316
+ />
1317
+ }
1318
+ </div>
1319
+ {buttonPanel}
1320
+ </div>
1321
+ }
1322
+ {
1323
+ !this.state.fullscreen && this.props.children &&
1324
+ <div className={Styles.ns_table_children}>
1325
+ {this.props.children}
1326
+ </div>
1327
+ }
1328
+ <table
1329
+ ref={this.Table_Ref}
1330
+ className={Styles.ns_table}
1331
+ style={this.props.ui?.grow ? { maxHeight: "none", height: "auto", overflowY: "visible", overflowX: "auto" } : undefined}
1332
+ >
1333
+ <thead>
1334
+ <tr style={{ gridTemplateColumns }}>
1335
+ {
1336
+ this.props.checkbox &&
1337
+ <th data-cell-type="checkbox" className="d-flex">
1338
+ <label
1339
+ htmlFor='checkbox'
1340
+ className={Styles.ns_checkbox_label}
1341
+ >
1342
+ <input
1343
+ id="checkbox"
1344
+ type="checkbox"
1345
+ className={`${Styles.ns_check_box} ${this.isSomeSelected() ? Styles.ns_indeterminate : ""}`}
1346
+ checked={this.getSelectedIDs().length > 0}
1347
+ onChange={this.toggleAllSelections}
1348
+ ></input>
1349
+ </label>
1350
+ </th>
1351
+ }
1352
+ {
1353
+ this.getColumns(true).map(column =>
1354
+ <th
1355
+ key={column.name}
1356
+ scope="col"
1357
+ onMouseDown={(e) =>
1358
+ {
1359
+ if ((e.target as HTMLElement).closest('[data-resize-handle]'))
1360
+ return;
1361
+ if (!this.isSortEnabled())
1362
+ return;
1363
+ let t = new BaseMetaTable(null, column.table.name, column.table.text);
1364
+ let c = new BaseMetaColumn(t, column.name, column.text, "", false);
1365
+ let items = this.getSortItems();
1366
+ if (!items || items.length === 0 || items[0].table.name !== t.name || items[0].column.name !== c.name)
1367
+ this.setSortItems([new SortItem(t, c, true)]);
1368
+ else
1369
+ {
1370
+ if (items[0].ascending)
1371
+ this.setSortItems([new SortItem(t, c, false)]);
1372
+ else
1373
+ this.setSortItems(null);
1374
+ }
1375
+ }}
1376
+ style={{ width: this.getColumnWidth(column) }}
1377
+ >
1378
+ <span className={Styles.ns_th_title}>
1379
+ {column.text + " " + getSortSign(column)}
1380
+ </span>
1381
+ <div
1382
+ data-resize-handle="true"
1383
+ className={Styles.ns_resize_handle}
1384
+ onMouseDown={(e) => this.startResize(e, column)}
1385
+ onClick={(e) => { e.stopPropagation() }}
1386
+ />
1387
+ </th>
1388
+ )
1389
+ }
1390
+ </tr>
1391
+ </thead>
1392
+
1393
+ <tbody className={Styles.ns_tbody}>
1394
+ {content}
1395
+ </tbody>
1396
+ </table>
1397
+
1398
+ {
1399
+ this.state.model.show &&
1400
+ <NSDialogInfo
1401
+ description={this.state.model.description}
1402
+ onClose={() => { this.hideModal(); }}
1403
+ title={this.state.title}
1404
+ show_as_code={true}
1405
+ >
1406
+ <></>
1407
+ </NSDialogInfo>
1408
+ }
1409
+ {
1410
+ this.state.showChart &&
1411
+ <NSTableChart<RowType>
1412
+ name={this.props.name}
1413
+ columns={this.getColumns(true)}
1414
+ rows={this.state.rows ?? []}
1415
+ selectedRows={this.state.selectedRows}
1416
+ onClose={() => this.setState({ showChart: false })}
1417
+ />
1418
+ }
1419
+ {
1420
+ this.state.showSortDialog && this.isSortEnabled() &&
1421
+ <NSSortDialog
1422
+ sorts={this.getSortItems()}
1423
+ getColumns={() => this.getColumns(null)}
1424
+ onCancel={() => this.setState({ showSortDialog: false })}
1425
+ onApply={sorts =>
1426
+ {
1427
+ this.setSortItems(sorts);
1428
+ this.setState({ showSortDialog: false });
1429
+ }}
1430
+ />
1431
+ }
1432
+ {
1433
+ this.state.showReorderDialog &&
1434
+ <NSReorderDialog
1435
+ visibleColumnKeys={this.getVisibleColumns().map(c => c.table + "." + c.column)}
1436
+ getColumns={() => this.getColumns(null)}
1437
+ onCancel={() => this.setState({ showReorderDialog: false })}
1438
+ onApply={orderedKeys =>
1439
+ {
1440
+ const all = this.getColumns(null);
1441
+ const cs = orderedKeys
1442
+ .map(k => all.find(c => c.table.name + "." + c.name === k))
1443
+ .filter((c): c is TableColumnInfo => !!c);
1444
+ this.setVisibleColumns(cs.map(c => ({ table: c.table.name, column: c.name })));
1445
+ this.setState({ showReorderDialog: false });
1446
+ }}
1447
+ />
1448
+ }
1449
+ </div>
1450
+ );
1451
+ }
1452
1452
  }