@goplusvn/core 0.1.0 → 0.1.1

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 (369) hide show
  1. package/package.json +2 -1
  2. package/src/assets/erp_wallpaper.png +0 -0
  3. package/src/assets/goeat_logo.png +0 -0
  4. package/src/audit/audit-manager.ts +139 -0
  5. package/src/audit/index.ts +11 -0
  6. package/src/audit/memory-audit-logger.ts +86 -0
  7. package/src/audit/types.ts +50 -0
  8. package/src/auth/auth-service.ts +97 -0
  9. package/src/auth/index.ts +266 -0
  10. package/src/code-generation/index.ts +69 -0
  11. package/src/configs/auth-routes.ts +17 -0
  12. package/src/configs/crud.ts +136 -0
  13. package/src/configs/data/navigations.ts +781 -0
  14. package/src/configs/data/oauth-links.ts +10 -0
  15. package/src/configs/entities/material-categories.config.ts +125 -0
  16. package/src/configs/i18n.ts +12 -0
  17. package/src/configs/index.ts +26 -0
  18. package/src/configs/status.ts +25 -0
  19. package/src/configs/themes.ts +100 -0
  20. package/src/crud/components/crud-bulk-actions.tsx +91 -0
  21. package/src/crud/components/crud-card-view.tsx +241 -0
  22. package/src/crud/components/crud-context.tsx +122 -0
  23. package/src/crud/components/crud-delete-dialog.tsx +145 -0
  24. package/src/crud/components/crud-dialog.tsx +406 -0
  25. package/src/crud/components/crud-empty-state.tsx +104 -0
  26. package/src/crud/components/crud-export-button.tsx +170 -0
  27. package/src/crud/components/crud-field-renderer.tsx +653 -0
  28. package/src/crud/components/crud-filter-chips.tsx +102 -0
  29. package/src/crud/components/crud-filters/checkbox-filter.tsx +97 -0
  30. package/src/crud/components/crud-filters/datetime-filter.tsx +83 -0
  31. package/src/crud/components/crud-filters/filter-builder.tsx +66 -0
  32. package/src/crud/components/crud-filters/index.tsx +76 -0
  33. package/src/crud/components/crud-filters/radio-filter.tsx +86 -0
  34. package/src/crud/components/crud-filters/select-filter.tsx +141 -0
  35. package/src/crud/components/crud-filters/text-filter.tsx +86 -0
  36. package/src/crud/components/crud-form.tsx +642 -0
  37. package/src/crud/components/crud-import-dialog.tsx +440 -0
  38. package/src/crud/components/crud-infinite-scroll.tsx +116 -0
  39. package/src/crud/components/crud-page.tsx +1017 -0
  40. package/src/crud/components/crud-provider.tsx +277 -0
  41. package/src/crud/components/crud-row-actions.tsx +189 -0
  42. package/src/crud/components/crud-search.tsx +82 -0
  43. package/src/crud/components/crud-sheet.tsx +336 -0
  44. package/src/crud/components/crud-table-skeleton.tsx +26 -0
  45. package/src/crud/components/crud-table-toolbar.tsx +91 -0
  46. package/src/crud/components/crud-table.tsx +352 -0
  47. package/src/crud/components/crud-virtual-table.tsx +55 -0
  48. package/src/crud/components/index.tsx +20 -0
  49. package/src/crud/crud-filters/checkbox-filter.tsx +87 -0
  50. package/src/crud/crud-filters/datetime-filter.tsx +82 -0
  51. package/src/crud/crud-filters/filter-builder.tsx +64 -0
  52. package/src/crud/crud-filters/index.tsx +78 -0
  53. package/src/crud/crud-filters/radio-filter.tsx +79 -0
  54. package/src/crud/crud-filters/select-filter.tsx +148 -0
  55. package/src/crud/crud-filters/text-filter.tsx +81 -0
  56. package/src/crud/index.ts +43 -0
  57. package/src/crud/lib/crud-service.test.ts +334 -0
  58. package/src/crud/lib/crud-service.ts +358 -0
  59. package/src/crud/lib/crud-utils.test.ts +354 -0
  60. package/src/crud/lib/crud-utils.ts +299 -0
  61. package/src/crud/lib/crud-validator.ts +247 -0
  62. package/src/crud/lib/data-loader.ts +234 -0
  63. package/src/crud/lib/field-calculator.ts +241 -0
  64. package/src/crud/lib/field-formatter.ts +240 -0
  65. package/src/crud/lib/import-export-service.test.ts +290 -0
  66. package/src/crud/lib/import-export-service.ts +352 -0
  67. package/src/crud/lib/import-server-utils.ts +109 -0
  68. package/src/crud/lib/lazy-loader.ts +241 -0
  69. package/src/crud/lib/parse-filters.ts +85 -0
  70. package/src/crud/lib/permissions.ts +52 -0
  71. package/src/crud/lib/serialize-config.ts +60 -0
  72. package/src/crud/lib/stream-loader.ts +145 -0
  73. package/src/crud/lib/translate-config.ts +335 -0
  74. package/src/crud/lib/types.ts +11 -0
  75. package/src/crud/pages/entity-crud-page.tsx +144 -0
  76. package/src/crud/server.ts +8 -0
  77. package/src/home/constants.tsx +142 -0
  78. package/src/home/feature-showcase.tsx +171 -0
  79. package/src/home/home-page.tsx +191 -0
  80. package/src/home/hooks/index.ts +1 -0
  81. package/src/home/hooks/useWidgetPreferences.ts +167 -0
  82. package/src/home/index.ts +33 -0
  83. package/src/home/quick-access-dialog.tsx +271 -0
  84. package/src/home/quick-access-menu.tsx +267 -0
  85. package/src/home/types.ts +140 -0
  86. package/src/home/welcome-card.tsx +92 -0
  87. package/src/home/widget-container.tsx +258 -0
  88. package/src/home/widgets/base-widget.tsx +200 -0
  89. package/src/home/widgets/customers-widget.tsx +74 -0
  90. package/src/home/widgets/index.ts +6 -0
  91. package/src/home/widgets/orders-widget.tsx +87 -0
  92. package/src/home/widgets/revenue-widget.tsx +71 -0
  93. package/src/home/widgets/stock-widget.tsx +109 -0
  94. package/src/hooks/index.tsx +598 -0
  95. package/src/hooks/use-tenant.test.tsx +30 -0
  96. package/src/hooks/use-tenant.ts +5 -0
  97. package/src/index.ts +17 -0
  98. package/src/infrastructure/__tests__/architecture-verification.spec.ts +103 -0
  99. package/src/infrastructure/api-service.ts +317 -0
  100. package/src/infrastructure/cache/cache-manager.ts +107 -0
  101. package/src/infrastructure/cache/cache.ts +120 -0
  102. package/src/infrastructure/cache/index.ts +8 -0
  103. package/src/infrastructure/cache/types.ts +48 -0
  104. package/src/infrastructure/cron/cron-manager.ts +239 -0
  105. package/src/infrastructure/cron/index.ts +6 -0
  106. package/src/infrastructure/cron/types.ts +41 -0
  107. package/src/infrastructure/event-bus/event-bus.ts +145 -0
  108. package/src/infrastructure/event-bus/index.ts +2 -0
  109. package/src/infrastructure/event-bus/types.ts +22 -0
  110. package/src/infrastructure/index.ts +32 -0
  111. package/src/infrastructure/lock/decorators.ts +67 -0
  112. package/src/infrastructure/lock/index.ts +2 -0
  113. package/src/infrastructure/lock/lock-manager.ts +33 -0
  114. package/src/infrastructure/logger/index.ts +2 -0
  115. package/src/infrastructure/logger/logger.ts +96 -0
  116. package/src/infrastructure/logger/types.ts +25 -0
  117. package/src/layout/index.tsx +185 -0
  118. package/src/navigation/index.ts +91 -0
  119. package/src/notification/index.ts +14 -0
  120. package/src/notification/notification-service.ts +120 -0
  121. package/src/notification/storage/in-memory.ts +56 -0
  122. package/src/notification/storage/index.ts +1 -0
  123. package/src/notification/types.ts +51 -0
  124. package/src/organization/branch-service.ts +299 -0
  125. package/src/organization/branches.config.ts +154 -0
  126. package/src/organization/index.ts +5 -0
  127. package/src/plugin/apps-registry.ts +97 -0
  128. package/src/plugin/index.ts +5 -0
  129. package/src/plugin/types.ts +41 -0
  130. package/src/providers/index.tsx +109 -0
  131. package/src/providers/tenant-provider.tsx +45 -0
  132. package/src/rbac/components/roles/role-card.tsx +158 -0
  133. package/src/rbac/components/roles/role-stats-cards.tsx +29 -0
  134. package/src/rbac/components/roles/role-toolbar.tsx +123 -0
  135. package/src/rbac/hooks/use-role-operations.ts +159 -0
  136. package/src/rbac/hooks/use-roles-data.ts +59 -0
  137. package/src/rbac/index.ts +297 -0
  138. package/src/rbac/lib/permission-helpers.ts +63 -0
  139. package/src/rbac/pages/action-list-page.tsx +25 -0
  140. package/src/rbac/pages/resource-list-page.tsx +25 -0
  141. package/src/rbac/pages/role-list-page.tsx +378 -0
  142. package/src/rbac/permission-service.ts +140 -0
  143. package/src/rbac/permissions.ts +135 -0
  144. package/src/rbac/resource-service.ts +115 -0
  145. package/src/rbac/resource-validator.ts +119 -0
  146. package/src/rbac/role-service.ts +165 -0
  147. package/src/rbac/server.ts +16 -0
  148. package/src/rbac/types.ts +38 -0
  149. package/src/schemas/action.schema.ts +66 -0
  150. package/src/schemas/branch.schema.ts +52 -0
  151. package/src/schemas/coming-soon-schema.ts +9 -0
  152. package/src/schemas/company.schema.ts +44 -0
  153. package/src/schemas/forgot-passward-schema.ts +9 -0
  154. package/src/schemas/index.ts +30 -0
  155. package/src/schemas/material-category.schema.ts +43 -0
  156. package/src/schemas/material-pricing.schema.ts +74 -0
  157. package/src/schemas/material.schema.ts +76 -0
  158. package/src/schemas/materials.ts +52 -0
  159. package/src/schemas/new-passward-schema.ts +15 -0
  160. package/src/schemas/partner-company.schema.ts +149 -0
  161. package/src/schemas/register-schema.ts +36 -0
  162. package/src/schemas/resource.schema.ts +133 -0
  163. package/src/schemas/role.schema.ts +11 -0
  164. package/src/schemas/sign-in-schema.ts +24 -0
  165. package/src/schemas/supplier-pricing.schema.ts +15 -0
  166. package/src/schemas/supplier.schema.ts +120 -0
  167. package/src/schemas/system-category-group.schema.ts +67 -0
  168. package/src/schemas/system-category.schema.ts +77 -0
  169. package/src/schemas/system-config.schema.ts +118 -0
  170. package/src/schemas/uom.schema.ts +75 -0
  171. package/src/schemas/user-supplier.schema.ts +179 -0
  172. package/src/schemas/user.schema.ts +18 -0
  173. package/src/schemas/verify-email-schema.ts +9 -0
  174. package/src/schemas/warehouse.schema.ts +49 -0
  175. package/src/system/components/categories/category-list.tsx +529 -0
  176. package/src/system/components/categories/category-manager.tsx +89 -0
  177. package/src/system/components/categories/group-sidebar.tsx +308 -0
  178. package/src/system/components/settings/setting-dialogs.tsx +197 -0
  179. package/src/system/components/settings/setting-field.tsx +291 -0
  180. package/src/system/components/settings/setting-form-dialog.tsx +308 -0
  181. package/src/system/components/settings/settings-groups.ts +80 -0
  182. package/src/system/components/settings/settings-search.tsx +71 -0
  183. package/src/system/components/settings/settings-section.tsx +74 -0
  184. package/src/system/components/settings/settings-sidebar.tsx +81 -0
  185. package/src/system/constants.ts +3 -0
  186. package/src/system/index.ts +150 -0
  187. package/src/system/job-manager.ts +176 -0
  188. package/src/system/pages/components/categories/category-list.tsx +537 -0
  189. package/src/system/pages/components/categories/category-manager.tsx +90 -0
  190. package/src/system/pages/components/categories/group-sidebar.tsx +311 -0
  191. package/src/system/pages/components/settings/sales-rules-settings.tsx +222 -0
  192. package/src/system/pages/components/settings/setting-dialogs.tsx +197 -0
  193. package/src/system/pages/components/settings/setting-field.tsx +292 -0
  194. package/src/system/pages/components/settings/setting-form-dialog.tsx +308 -0
  195. package/src/system/pages/components/settings/settings-groups.ts +87 -0
  196. package/src/system/pages/components/settings/settings-page.tsx +372 -0
  197. package/src/system/pages/components/settings/settings-search.tsx +71 -0
  198. package/src/system/pages/components/settings/settings-section.tsx +74 -0
  199. package/src/system/pages/components/settings/settings-sidebar.tsx +81 -0
  200. package/src/system/pages/components/settings/system-settings.tsx +244 -0
  201. package/src/system/pages/system-category-page.tsx +15 -0
  202. package/src/system/pages/system-settings-page.tsx +380 -0
  203. package/src/system/schemas/system-category-group.schema.ts +46 -0
  204. package/src/system/schemas/system-category.schema.ts +56 -0
  205. package/src/system/services/settings-service.ts +127 -0
  206. package/src/system/services/system-category-service.ts +63 -0
  207. package/src/system/types.ts +45 -0
  208. package/src/types/index.ts +703 -0
  209. package/src/ui/auth/auth-layout.tsx +135 -0
  210. package/src/ui/auth/forgot-password-form.tsx +98 -0
  211. package/src/ui/auth/index.tsx +7 -0
  212. package/src/ui/auth/new-password-form.tsx +107 -0
  213. package/src/ui/auth/oauth-links.tsx +30 -0
  214. package/src/ui/auth/register-form.tsx +202 -0
  215. package/src/ui/auth/sign-in-form.tsx +238 -0
  216. package/src/ui/auth/verify-email-form.tsx +104 -0
  217. package/src/ui/crud/index.tsx +10 -0
  218. package/src/ui/data-display/accordion.tsx +65 -0
  219. package/src/ui/data-display/aspect-ratio.tsx +11 -0
  220. package/src/ui/data-display/avatar.tsx +163 -0
  221. package/src/ui/data-display/bento-grid.tsx +77 -0
  222. package/src/ui/data-display/carousel.tsx +249 -0
  223. package/src/ui/data-display/chart.tsx +363 -0
  224. package/src/ui/data-display/code-block-highlight.tsx +54 -0
  225. package/src/ui/data-display/collapsible.tsx +42 -0
  226. package/src/ui/data-display/compact-stat-bar.tsx +149 -0
  227. package/src/ui/data-display/data-table/data-table-context.tsx +255 -0
  228. package/src/ui/data-display/data-table/data-table-empty-state.tsx +133 -0
  229. package/src/ui/data-display/data-table/data-table-skeleton.tsx +145 -0
  230. package/src/ui/data-display/data-table/data-table-toolbar.tsx +353 -0
  231. package/src/ui/data-display/data-table/data-table.tsx +597 -0
  232. package/src/ui/data-display/data-table/index.ts +44 -0
  233. package/src/ui/data-display/data-table-column-header.tsx +75 -0
  234. package/src/ui/data-display/data-table-pagination.tsx +130 -0
  235. package/src/ui/data-display/data-table-view-options.tsx +59 -0
  236. package/src/ui/data-display/formatted-number-input.tsx +210 -0
  237. package/src/ui/data-display/highlight.tsx +20 -0
  238. package/src/ui/data-display/hover-card.tsx +48 -0
  239. package/src/ui/data-display/index.tsx +50 -0
  240. package/src/ui/data-display/iphone-15-pro.tsx +114 -0
  241. package/src/ui/data-display/kanban/index.ts +4 -0
  242. package/src/ui/data-display/kanban/kanban-board.tsx +192 -0
  243. package/src/ui/data-display/kanban/kanban-column.tsx +74 -0
  244. package/src/ui/data-display/kanban/kanban-item.tsx +50 -0
  245. package/src/ui/data-display/kanban/kanban-types.ts +21 -0
  246. package/src/ui/data-display/kpi-card.tsx +68 -0
  247. package/src/ui/data-display/media-grid.tsx +110 -0
  248. package/src/ui/data-display/safari.tsx +175 -0
  249. package/src/ui/data-display/show-more-text.tsx +55 -0
  250. package/src/ui/data-display/tabs.tsx +68 -0
  251. package/src/ui/data-display/timeline.tsx +256 -0
  252. package/src/ui/feedback/alert.tsx +60 -0
  253. package/src/ui/feedback/context-menu.tsx +245 -0
  254. package/src/ui/feedback/drawer.tsx +132 -0
  255. package/src/ui/feedback/error-dialog.tsx +273 -0
  256. package/src/ui/feedback/index.tsx +183 -0
  257. package/src/ui/feedback/progress.tsx +32 -0
  258. package/src/ui/feedback/sheet.tsx +148 -0
  259. package/src/ui/feedback/sonner.tsx +36 -0
  260. package/src/ui/forms/command.tsx +157 -0
  261. package/src/ui/forms/date-picker.tsx +73 -0
  262. package/src/ui/forms/date-range-picker.tsx +76 -0
  263. package/src/ui/forms/date-time-picker.tsx +109 -0
  264. package/src/ui/forms/editor/editor-menu-bar.tsx +394 -0
  265. package/src/ui/forms/editor/index.tsx +130 -0
  266. package/src/ui/forms/editor/multi-select-example.tsx +1234 -0
  267. package/src/ui/forms/emoji-picker.tsx +109 -0
  268. package/src/ui/forms/file-dropzone.tsx +169 -0
  269. package/src/ui/forms/file-thumbnail.tsx +29 -0
  270. package/src/ui/forms/index.tsx +201 -0
  271. package/src/ui/forms/input-file.tsx +99 -0
  272. package/src/ui/forms/input-group.tsx +46 -0
  273. package/src/ui/forms/input-otp.tsx +81 -0
  274. package/src/ui/forms/input-phone.tsx +172 -0
  275. package/src/ui/forms/input-spin.tsx +116 -0
  276. package/src/ui/forms/input-tags.tsx +219 -0
  277. package/src/ui/forms/input-time.tsx +42 -0
  278. package/src/ui/forms/multi-select.tsx +629 -0
  279. package/src/ui/forms/multiple-date-picker.tsx +74 -0
  280. package/src/ui/forms/radio-group.tsx +42 -0
  281. package/src/ui/forms/rating.tsx +158 -0
  282. package/src/ui/forms/time-picker.tsx +57 -0
  283. package/src/ui/index.tsx +17 -0
  284. package/src/ui/layout/animated-list.tsx +77 -0
  285. package/src/ui/layout/animated-sidebar.tsx +294 -0
  286. package/src/ui/layout/command-menu.tsx +355 -0
  287. package/src/ui/layout/customizer.tsx +324 -0
  288. package/src/ui/layout/footer.tsx +43 -0
  289. package/src/ui/layout/full-screen-toggle.tsx +52 -0
  290. package/src/ui/layout/header-breadcrumb.tsx +77 -0
  291. package/src/ui/layout/horizontal-layout-header.tsx +83 -0
  292. package/src/ui/layout/horizontal-layout.tsx +50 -0
  293. package/src/ui/layout/index.tsx +25 -0
  294. package/src/ui/layout/language-dropdown.tsx +103 -0
  295. package/src/ui/layout/logo.tsx +63 -0
  296. package/src/ui/layout/main-layout.tsx +57 -0
  297. package/src/ui/layout/mode-dropdown.tsx +58 -0
  298. package/src/ui/layout/notification-dropdown.tsx +127 -0
  299. package/src/ui/layout/page-tabs.tsx +306 -0
  300. package/src/ui/layout/route-cache.tsx +214 -0
  301. package/src/ui/layout/sidebar-group-icon-menu.tsx +195 -0
  302. package/src/ui/layout/sidebar.tsx +279 -0
  303. package/src/ui/layout/tab-content-cache.tsx +201 -0
  304. package/src/ui/layout/tab-navigation-provider.tsx +536 -0
  305. package/src/ui/layout/toggle-mobile-sidebar.tsx +33 -0
  306. package/src/ui/layout/top-bar-header-menubar.tsx +412 -0
  307. package/src/ui/layout/user-dropdown.tsx +188 -0
  308. package/src/ui/layout/vertical-layout-header.tsx +65 -0
  309. package/src/ui/layout/vertical-layout.tsx +47 -0
  310. package/src/ui/management/audit-log-page.tsx +209 -0
  311. package/src/ui/management/cache-management.tsx +349 -0
  312. package/src/ui/management/index.ts +3 -0
  313. package/src/ui/management/job-management.tsx +308 -0
  314. package/src/ui/pages/not-found.tsx +30 -0
  315. package/src/ui/primitives/badge.tsx +66 -0
  316. package/src/ui/primitives/breadcrumb.tsx +103 -0
  317. package/src/ui/primitives/button.tsx +129 -0
  318. package/src/ui/primitives/calendar.tsx +74 -0
  319. package/src/ui/primitives/card.tsx +86 -0
  320. package/src/ui/primitives/checkbox.tsx +31 -0
  321. package/src/ui/primitives/client.ts +30 -0
  322. package/src/ui/primitives/combobox.tsx +290 -0
  323. package/src/ui/primitives/dialog.tsx +121 -0
  324. package/src/ui/primitives/dropdown-menu.tsx +239 -0
  325. package/src/ui/primitives/dynamic-icon.tsx +24 -0
  326. package/src/ui/primitives/index.tsx +134 -0
  327. package/src/ui/primitives/input-number.tsx +131 -0
  328. package/src/ui/primitives/input.tsx +22 -0
  329. package/src/ui/primitives/keyboard.tsx +23 -0
  330. package/src/ui/primitives/label.tsx +24 -0
  331. package/src/ui/primitives/menubar.tsx +262 -0
  332. package/src/ui/primitives/navigation-menu.tsx +157 -0
  333. package/src/ui/primitives/pagination.tsx +118 -0
  334. package/src/ui/primitives/popover.tsx +56 -0
  335. package/src/ui/primitives/prefetch-link.tsx +60 -0
  336. package/src/ui/primitives/resizable.tsx +59 -0
  337. package/src/ui/primitives/scroll-area.tsx +63 -0
  338. package/src/ui/primitives/select.tsx +172 -0
  339. package/src/ui/primitives/separator.tsx +51 -0
  340. package/src/ui/primitives/sidebar.tsx +844 -0
  341. package/src/ui/primitives/slider.tsx +27 -0
  342. package/src/ui/primitives/status-badge.tsx +47 -0
  343. package/src/ui/primitives/sticky-layout.tsx +50 -0
  344. package/src/ui/primitives/switch.tsx +29 -0
  345. package/src/ui/primitives/table.tsx +116 -0
  346. package/src/ui/primitives/tabs.tsx +55 -0
  347. package/src/ui/primitives/toggle-group.tsx +70 -0
  348. package/src/ui/primitives/toggle.tsx +47 -0
  349. package/src/ui/primitives/tooltip.tsx +59 -0
  350. package/src/user/components/dangerous-zone.tsx +34 -0
  351. package/src/user/components/delete-account-form.tsx +40 -0
  352. package/src/user/components/index.ts +4 -0
  353. package/src/user/components/profile-info-form.tsx +390 -0
  354. package/src/user/components/profile-info.tsx +32 -0
  355. package/src/user/components/unified-profile-dialog.tsx +1019 -0
  356. package/src/user/components/user-stats.tsx +27 -0
  357. package/src/user/components/user-toolbar.tsx +137 -0
  358. package/src/user/components/users-card-view.tsx +253 -0
  359. package/src/user/index.ts +11 -0
  360. package/src/user/pages/user-list-page.tsx +234 -0
  361. package/src/user/pages/users-client-page.tsx +385 -0
  362. package/src/user/profile-page.tsx +19 -0
  363. package/src/user/schemas.ts +68 -0
  364. package/src/user/types.ts +34 -0
  365. package/src/user/user-service.ts +538 -0
  366. package/src/utils/index.ts +906 -0
  367. package/src/workflow/activity-timeline.tsx +412 -0
  368. package/src/workflow/approval-workflow.tsx +31 -0
  369. package/src/workflow/index.ts +2 -0
@@ -0,0 +1,241 @@
1
+ /**
2
+ * Field Calculator - Handles field calculations based on dependencies
3
+ */
4
+
5
+ import type { FieldConfig } from "../../types";
6
+
7
+ /**
8
+ * Safe expression evaluator - replaces eval() to prevent code injection
9
+ * Supports: +, -, *, /, (), numbers
10
+ * Uses a simple recursive descent parser - NO eval() or Function()
11
+ */
12
+ function safeEvaluate(expression: string): number {
13
+ // Remove all whitespace
14
+ expression = expression.replace(/\s+/g, "");
15
+
16
+ // Validate expression only contains safe characters
17
+ // Allowed: numbers, operators (+, -, *, /), parentheses, and decimal points
18
+ if (!/^[0-9+\-*/().]+$/.test(expression)) {
19
+ throw new Error("Invalid characters in expression");
20
+ }
21
+
22
+ let index = 0;
23
+
24
+ // Parse a number
25
+ function parseNumber(): number {
26
+ let numStr = "";
27
+ while (index < expression.length && /[0-9.]/.test(expression[index])) {
28
+ numStr += expression[index];
29
+ index++;
30
+ }
31
+ const num = parseFloat(numStr);
32
+ if (isNaN(num)) {
33
+ throw new Error(`Invalid number: ${numStr}`);
34
+ }
35
+ return num;
36
+ }
37
+
38
+ // Parse expression with addition and subtraction
39
+ function parseExpression(): number {
40
+ let result = parseTerm();
41
+ while (index < expression.length) {
42
+ const op = expression[index];
43
+ if (op === "+") {
44
+ index++;
45
+ result += parseTerm();
46
+ } else if (op === "-") {
47
+ index++;
48
+ result -= parseTerm();
49
+ } else {
50
+ break;
51
+ }
52
+ }
53
+ return result;
54
+ }
55
+
56
+ // Parse term with multiplication and division
57
+ function parseTerm(): number {
58
+ let result = parseFactor();
59
+ while (index < expression.length) {
60
+ const op = expression[index];
61
+ if (op === "*") {
62
+ index++;
63
+ result *= parseFactor();
64
+ } else if (op === "/") {
65
+ index++;
66
+ const divisor = parseFactor();
67
+ if (divisor === 0) {
68
+ throw new Error("Division by zero");
69
+ }
70
+ result /= divisor;
71
+ } else {
72
+ break;
73
+ }
74
+ }
75
+ return result;
76
+ }
77
+
78
+ // Parse factor (number or parenthesized expression)
79
+ function parseFactor(): number {
80
+ if (index >= expression.length) {
81
+ throw new Error("Unexpected end of expression");
82
+ }
83
+
84
+ if (expression[index] === "(") {
85
+ index++; // skip '('
86
+ const result = parseExpression();
87
+ if (index >= expression.length || expression[index] !== ")") {
88
+ throw new Error("Missing closing parenthesis");
89
+ }
90
+ index++; // skip ')'
91
+ return result;
92
+ }
93
+
94
+ if (expression[index] === "-") {
95
+ index++; // unary minus
96
+ return -parseFactor();
97
+ }
98
+
99
+ if (expression[index] === "+") {
100
+ index++; // unary plus
101
+ return parseFactor();
102
+ }
103
+
104
+ return parseNumber();
105
+ }
106
+
107
+ try {
108
+ const result = parseExpression();
109
+ if (index < expression.length) {
110
+ throw new Error(
111
+ `Unexpected character: ${expression[index]} at position ${index}`,
112
+ );
113
+ }
114
+ return result;
115
+ } catch (error) {
116
+ throw new Error(
117
+ `Invalid expression: ${error instanceof Error ? error.message : String(error)}`,
118
+ );
119
+ }
120
+ }
121
+
122
+ /**
123
+ * Sanitize and validate field names
124
+ */
125
+ function isValidFieldName(name: string): boolean {
126
+ // Only allow alphanumeric and underscore, must start with letter or underscore
127
+ return /^[a-zA-Z_][a-zA-Z0-9_]*$/.test(name);
128
+ }
129
+
130
+ /**
131
+ * Evaluate a formula string or function
132
+ * SECURITY: No longer uses eval() - uses safe evaluator instead
133
+ */
134
+ export function evaluateFormula(
135
+ formula: string | ((values: Record<string, unknown>) => unknown),
136
+ values: Record<string, unknown>,
137
+ ): unknown {
138
+ if (typeof formula === "function") {
139
+ return formula(values);
140
+ }
141
+
142
+ // Simple formula parser for common operations
143
+ // Supports: +, -, *, /, ()
144
+ try {
145
+ // Replace field names with their values
146
+ let expression = formula.trim();
147
+ const fieldNames = Object.keys(values);
148
+
149
+ // Validate all field names first
150
+ fieldNames.forEach((fieldName) => {
151
+ if (!isValidFieldName(fieldName)) {
152
+ throw new Error(`Invalid field name: ${fieldName}`);
153
+ }
154
+ });
155
+
156
+ // Replace field names with their numeric values
157
+ fieldNames.forEach((fieldName) => {
158
+ const value = values[fieldName];
159
+ const numValue = Number(value);
160
+ if (!isNaN(numValue) && isFinite(numValue)) {
161
+ // Replace field name with its numeric value
162
+ // Use word boundary to avoid partial matches
163
+ expression = expression.replace(
164
+ new RegExp(`\\b${fieldName}\\b`, "g"),
165
+ String(numValue),
166
+ );
167
+ } else {
168
+ // If field value is not a valid number, throw error
169
+ throw new Error(
170
+ `Field "${fieldName}" must be a valid number for calculation`,
171
+ );
172
+ }
173
+ });
174
+
175
+ // Validate expression after replacement
176
+ // Check that all field names have been replaced
177
+ const remainingFieldNames = fieldNames.filter((fieldName) => {
178
+ return new RegExp(`\\b${fieldName}\\b`).test(expression);
179
+ });
180
+
181
+ if (remainingFieldNames.length > 0) {
182
+ throw new Error(
183
+ `Fields not found in values: ${remainingFieldNames.join(", ")}`,
184
+ );
185
+ }
186
+
187
+ // Evaluate the expression safely (no eval!)
188
+ return safeEvaluate(expression);
189
+ } catch (error) {
190
+ console.error("Error evaluating formula:", error);
191
+ return null;
192
+ }
193
+ }
194
+
195
+ /**
196
+ * Calculate field value based on dependencies
197
+ */
198
+ export function calculateFieldValue(
199
+ field: FieldConfig,
200
+ formValues: Record<string, unknown>,
201
+ ): unknown {
202
+ if (!field.calculate) return undefined;
203
+
204
+ const { dependsOn, formula } = field.calculate;
205
+
206
+ // Get values of dependent fields
207
+ const dependentValues: Record<string, unknown> = {};
208
+ dependsOn.forEach((depField) => {
209
+ dependentValues[depField] = formValues[depField];
210
+ });
211
+
212
+ // Check if all required fields have values
213
+ const hasAllValues = dependsOn.every(
214
+ (depField) =>
215
+ formValues[depField] !== undefined &&
216
+ formValues[depField] !== null &&
217
+ formValues[depField] !== "",
218
+ );
219
+
220
+ if (!hasAllValues) {
221
+ return undefined;
222
+ }
223
+
224
+ // Evaluate formula
225
+ return evaluateFormula(formula, dependentValues);
226
+ }
227
+
228
+ /**
229
+ * Get all fields that depend on a given field
230
+ */
231
+ export function getDependentFields(
232
+ fieldName: string,
233
+ allFields: FieldConfig[],
234
+ ): FieldConfig[] {
235
+ return allFields.filter(
236
+ (field) =>
237
+ field.calculate?.dependsOn?.includes(fieldName) ||
238
+ field.dependencies?.fields?.includes(fieldName) ||
239
+ field.cascade?.triggerField === fieldName,
240
+ );
241
+ }
@@ -0,0 +1,240 @@
1
+ /**
2
+ * Field Formatter - Handles field value formatting for display
3
+ */
4
+
5
+ import type { FieldConfig } from "../../types";
6
+
7
+ /**
8
+ * Format a value based on field configuration
9
+ */
10
+ export function formatFieldValue(value: unknown, field: FieldConfig): string {
11
+ if (value === undefined || value === null || value === "") {
12
+ return "";
13
+ }
14
+
15
+ // Apply output transformation if exists
16
+ if (field.transform?.output) {
17
+ value = field.transform.output(value);
18
+ }
19
+
20
+ // Apply formatting if exists
21
+ // Priority: Explicit format config
22
+ if (field.format) {
23
+ const { type, options = {} } = field.format;
24
+
25
+ switch (type) {
26
+ case "currency":
27
+ return formatCurrency(value, options);
28
+
29
+ case "percentage":
30
+ return formatPercentage(value, options);
31
+
32
+ case "number":
33
+ return formatNumber(value, options);
34
+
35
+ case "date":
36
+ return formatDate(value, options);
37
+
38
+ case "datetime":
39
+ return formatDateTime(value, options);
40
+
41
+ case "custom":
42
+ if (options.custom) {
43
+ return options.custom(value);
44
+ }
45
+ return String(value);
46
+
47
+ default:
48
+ return String(value);
49
+ }
50
+ }
51
+
52
+ // Fallback: Default formatting based on field type
53
+ switch (field.type) {
54
+ case "integer":
55
+ return formatNumber(value, { useGrouping: true, decimals: 0 });
56
+
57
+ case "number":
58
+ return formatNumber(value, { useGrouping: true });
59
+
60
+ case "date":
61
+ return formatDate(value, {});
62
+
63
+ case "datetime":
64
+ return formatDateTime(value, {});
65
+
66
+ case "boolean":
67
+ return value ? "Yes" : "No";
68
+
69
+ case "select":
70
+ case "multiselect":
71
+ if (field.options && Array.isArray(field.options)) {
72
+ // Handle multiselect
73
+ if (field.type === "multiselect" && Array.isArray(value)) {
74
+ return value
75
+ .map((val) => {
76
+ const option = field.options?.find((opt) => {
77
+ const optValue = typeof opt === "object" ? opt.value : opt;
78
+ return String(optValue) === String(val);
79
+ });
80
+ return option
81
+ ? typeof option === "object"
82
+ ? option.label
83
+ : String(option)
84
+ : String(val);
85
+ })
86
+ .join(", ");
87
+ }
88
+
89
+ // Handle single select
90
+ const option = field.options.find((opt) => {
91
+ const optValue = typeof opt === "object" ? opt.value : opt;
92
+ const valToCheck =
93
+ typeof value === "object" && value !== null
94
+ ? (value as Record<string, unknown>).value || value
95
+ : value;
96
+ return String(optValue) === String(valToCheck);
97
+ });
98
+
99
+ if (option) {
100
+ return typeof option === "object" ? option.label : String(option);
101
+ }
102
+ }
103
+ return String(value);
104
+ }
105
+
106
+ return String(value);
107
+ }
108
+
109
+ /**
110
+ * Format as currency
111
+ */
112
+ function formatCurrency(
113
+ value: unknown,
114
+ options: {
115
+ currency?: string;
116
+ locale?: string;
117
+ decimals?: number;
118
+ },
119
+ ): string {
120
+ const numValue = Number(value);
121
+ if (isNaN(numValue)) return String(value);
122
+
123
+ const { currency = "USD", locale = "en-US", decimals = 2 } = options;
124
+
125
+ return new Intl.NumberFormat(locale, {
126
+ style: "currency",
127
+ currency,
128
+ minimumFractionDigits: decimals,
129
+ maximumFractionDigits: decimals,
130
+ }).format(numValue);
131
+ }
132
+
133
+ /**
134
+ * Format as percentage
135
+ */
136
+ function formatPercentage(
137
+ value: unknown,
138
+ options: {
139
+ decimals?: number;
140
+ locale?: string;
141
+ },
142
+ ): string {
143
+ const numValue = Number(value);
144
+ if (isNaN(numValue)) return String(value);
145
+
146
+ const { decimals = 2, locale = "en-US" } = options;
147
+
148
+ return new Intl.NumberFormat(locale, {
149
+ style: "percent",
150
+ minimumFractionDigits: decimals,
151
+ maximumFractionDigits: decimals,
152
+ }).format(numValue / 100);
153
+ }
154
+
155
+ /**
156
+ * Format as number
157
+ */
158
+ function formatNumber(
159
+ value: unknown,
160
+ options: {
161
+ decimals?: number;
162
+ locale?: string;
163
+ useGrouping?: boolean;
164
+ },
165
+ ): string {
166
+ const numValue = Number(value);
167
+ if (isNaN(numValue)) return String(value);
168
+
169
+ const { decimals = 2, locale = "en-US", useGrouping = true } = options;
170
+
171
+ return new Intl.NumberFormat(locale, {
172
+ minimumFractionDigits: decimals,
173
+ maximumFractionDigits: decimals,
174
+ useGrouping,
175
+ }).format(numValue);
176
+ }
177
+
178
+ /**
179
+ * Format as date (Asia/Ho_Chi_Minh timezone)
180
+ */
181
+ function formatDate(
182
+ value: unknown,
183
+ options: {
184
+ dateFormat?: string;
185
+ locale?: string;
186
+ },
187
+ ): string {
188
+ if (!value) return "";
189
+
190
+ const date = value instanceof Date ? value : new Date(String(value));
191
+ if (isNaN(date.getTime())) return String(value);
192
+
193
+ const { dateFormat, locale = "en-US" } = options;
194
+
195
+ if (dateFormat) {
196
+ // Custom format using date-fns or similar
197
+ // For now, use Intl.DateTimeFormat
198
+ return new Intl.DateTimeFormat(locale, {
199
+ year: "numeric",
200
+ month: "2-digit",
201
+ day: "2-digit",
202
+ timeZone: "Asia/Ho_Chi_Minh",
203
+ }).format(date);
204
+ }
205
+
206
+ return new Intl.DateTimeFormat(locale, {
207
+ year: "numeric",
208
+ month: "2-digit",
209
+ day: "2-digit",
210
+ timeZone: "Asia/Ho_Chi_Minh",
211
+ }).format(date);
212
+ }
213
+
214
+ /**
215
+ * Format as datetime (Asia/Ho_Chi_Minh timezone)
216
+ */
217
+ function formatDateTime(
218
+ value: unknown,
219
+ options: {
220
+ dateFormat?: string;
221
+ locale?: string;
222
+ },
223
+ ): string {
224
+ if (!value) return "";
225
+
226
+ const date = value instanceof Date ? value : new Date(String(value));
227
+ if (isNaN(date.getTime())) return String(value);
228
+
229
+ const { dateFormat, locale = "en-US" } = options;
230
+
231
+ return new Intl.DateTimeFormat(locale, {
232
+ year: "numeric",
233
+ month: "2-digit",
234
+ day: "2-digit",
235
+ hour: "2-digit",
236
+ minute: "2-digit",
237
+ second: "2-digit",
238
+ timeZone: "Asia/Ho_Chi_Minh",
239
+ }).format(date);
240
+ }