@goplusvn/core 0.1.0 → 0.1.2

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 (591) hide show
  1. package/package.json +31 -175
  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
  370. package/dist/audit/index.d.mts +0 -115
  371. package/dist/audit/index.d.ts +0 -115
  372. package/dist/audit/index.js +0 -204
  373. package/dist/audit/index.js.map +0 -1
  374. package/dist/audit/index.mjs +0 -200
  375. package/dist/audit/index.mjs.map +0 -1
  376. package/dist/auth/index.d.mts +0 -86
  377. package/dist/auth/index.d.ts +0 -86
  378. package/dist/auth/index.js +0 -210
  379. package/dist/auth/index.js.map +0 -1
  380. package/dist/auth/index.mjs +0 -198
  381. package/dist/auth/index.mjs.map +0 -1
  382. package/dist/button-1dWvP9Ib.d.mts +0 -30
  383. package/dist/button-1dWvP9Ib.d.ts +0 -30
  384. package/dist/calendar-2QzdEo1z.d.mts +0 -20
  385. package/dist/calendar-2QzdEo1z.d.ts +0 -20
  386. package/dist/code-generation/index.d.mts +0 -30
  387. package/dist/code-generation/index.d.ts +0 -30
  388. package/dist/code-generation/index.js +0 -31
  389. package/dist/code-generation/index.js.map +0 -1
  390. package/dist/code-generation/index.mjs +0 -28
  391. package/dist/code-generation/index.mjs.map +0 -1
  392. package/dist/configs/index.d.mts +0 -175
  393. package/dist/configs/index.d.ts +0 -175
  394. package/dist/configs/index.js +0 -254
  395. package/dist/configs/index.js.map +0 -1
  396. package/dist/configs/index.mjs +0 -233
  397. package/dist/configs/index.mjs.map +0 -1
  398. package/dist/crud/index.d.mts +0 -646
  399. package/dist/crud/index.d.ts +0 -646
  400. package/dist/crud/index.js +0 -11772
  401. package/dist/crud/index.js.map +0 -1
  402. package/dist/crud/index.mjs +0 -11665
  403. package/dist/crud/index.mjs.map +0 -1
  404. package/dist/crud/server.d.mts +0 -20
  405. package/dist/crud/server.d.ts +0 -20
  406. package/dist/crud/server.js +0 -123
  407. package/dist/crud/server.js.map +0 -1
  408. package/dist/crud/server.mjs +0 -120
  409. package/dist/crud/server.mjs.map +0 -1
  410. package/dist/data-table-skeleton-12NA8Mjx.d.mts +0 -39
  411. package/dist/data-table-skeleton-12NA8Mjx.d.ts +0 -39
  412. package/dist/dialog-bKfjZMTd.d.mts +0 -22
  413. package/dist/dialog-bKfjZMTd.d.ts +0 -22
  414. package/dist/dynamic-icon-DrGIiu2N.d.mts +0 -10
  415. package/dist/dynamic-icon-DrGIiu2N.d.ts +0 -10
  416. package/dist/home/index.d.mts +0 -269
  417. package/dist/home/index.d.ts +0 -269
  418. package/dist/home/index.js +0 -1678
  419. package/dist/home/index.js.map +0 -1
  420. package/dist/home/index.mjs +0 -1635
  421. package/dist/home/index.mjs.map +0 -1
  422. package/dist/hooks/index.d.mts +0 -7
  423. package/dist/hooks/index.d.ts +0 -7
  424. package/dist/hooks/index.js +0 -8316
  425. package/dist/hooks/index.js.map +0 -1
  426. package/dist/hooks/index.mjs +0 -8255
  427. package/dist/hooks/index.mjs.map +0 -1
  428. package/dist/index-50hpiPrV.d.ts +0 -116
  429. package/dist/index-B9zQVEVi.d.mts +0 -116
  430. package/dist/index.d.mts +0 -5
  431. package/dist/index.d.ts +0 -5
  432. package/dist/index.js +0 -123
  433. package/dist/index.js.map +0 -1
  434. package/dist/index.mjs +0 -118
  435. package/dist/index.mjs.map +0 -1
  436. package/dist/infrastructure/index.d.mts +0 -423
  437. package/dist/infrastructure/index.d.ts +0 -423
  438. package/dist/infrastructure/index.js +0 -633
  439. package/dist/infrastructure/index.js.map +0 -1
  440. package/dist/infrastructure/index.mjs +0 -619
  441. package/dist/infrastructure/index.mjs.map +0 -1
  442. package/dist/label-DWTEkNPo.d.ts +0 -226
  443. package/dist/label-LPpdcoBx.d.mts +0 -226
  444. package/dist/layout/index.d.mts +0 -48
  445. package/dist/layout/index.d.ts +0 -48
  446. package/dist/layout/index.js +0 -117
  447. package/dist/layout/index.js.map +0 -1
  448. package/dist/layout/index.mjs +0 -90
  449. package/dist/layout/index.mjs.map +0 -1
  450. package/dist/navigation/index.d.mts +0 -16
  451. package/dist/navigation/index.d.ts +0 -16
  452. package/dist/navigation/index.js +0 -53
  453. package/dist/navigation/index.js.map +0 -1
  454. package/dist/navigation/index.mjs +0 -50
  455. package/dist/navigation/index.mjs.map +0 -1
  456. package/dist/notification/index.d.mts +0 -105
  457. package/dist/notification/index.d.ts +0 -105
  458. package/dist/notification/index.js +0 -278
  459. package/dist/notification/index.js.map +0 -1
  460. package/dist/notification/index.mjs +0 -274
  461. package/dist/notification/index.mjs.map +0 -1
  462. package/dist/organization/index.d.mts +0 -99
  463. package/dist/organization/index.d.ts +0 -99
  464. package/dist/organization/index.js +0 -360
  465. package/dist/organization/index.js.map +0 -1
  466. package/dist/organization/index.mjs +0 -352
  467. package/dist/organization/index.mjs.map +0 -1
  468. package/dist/plugin/index.d.mts +0 -83
  469. package/dist/plugin/index.d.ts +0 -83
  470. package/dist/plugin/index.js +0 -86
  471. package/dist/plugin/index.js.map +0 -1
  472. package/dist/plugin/index.mjs +0 -84
  473. package/dist/plugin/index.mjs.map +0 -1
  474. package/dist/providers/index.d.mts +0 -25
  475. package/dist/providers/index.d.ts +0 -25
  476. package/dist/providers/index.js +0 -84
  477. package/dist/providers/index.js.map +0 -1
  478. package/dist/providers/index.mjs +0 -77
  479. package/dist/providers/index.mjs.map +0 -1
  480. package/dist/rbac/index.d.mts +0 -226
  481. package/dist/rbac/index.d.ts +0 -226
  482. package/dist/rbac/index.js +0 -4784
  483. package/dist/rbac/index.js.map +0 -1
  484. package/dist/rbac/index.mjs +0 -4722
  485. package/dist/rbac/index.mjs.map +0 -1
  486. package/dist/rbac/permissions.d.mts +0 -26
  487. package/dist/rbac/permissions.d.ts +0 -26
  488. package/dist/rbac/permissions.js +0 -94
  489. package/dist/rbac/permissions.js.map +0 -1
  490. package/dist/rbac/permissions.mjs +0 -90
  491. package/dist/rbac/permissions.mjs.map +0 -1
  492. package/dist/rbac/server.d.mts +0 -1
  493. package/dist/rbac/server.d.ts +0 -1
  494. package/dist/rbac/server.js +0 -128
  495. package/dist/rbac/server.js.map +0 -1
  496. package/dist/rbac/server.mjs +0 -124
  497. package/dist/rbac/server.mjs.map +0 -1
  498. package/dist/schemas/index.d.mts +0 -1257
  499. package/dist/schemas/index.d.ts +0 -1257
  500. package/dist/schemas/index.js +0 -572
  501. package/dist/schemas/index.js.map +0 -1
  502. package/dist/schemas/index.mjs +0 -523
  503. package/dist/schemas/index.mjs.map +0 -1
  504. package/dist/server-QuYCTa89.d.mts +0 -83
  505. package/dist/server-QuYCTa89.d.ts +0 -83
  506. package/dist/sonner-C74GlRDQ.d.mts +0 -71
  507. package/dist/sonner-C74GlRDQ.d.ts +0 -71
  508. package/dist/status-BOXZgIqX.d.mts +0 -12
  509. package/dist/status-BOXZgIqX.d.ts +0 -12
  510. package/dist/system/index.d.mts +0 -77
  511. package/dist/system/index.d.ts +0 -77
  512. package/dist/system/index.js +0 -102
  513. package/dist/system/index.js.map +0 -1
  514. package/dist/system/index.mjs +0 -100
  515. package/dist/system/index.mjs.map +0 -1
  516. package/dist/tabs-C6FfBwPY.d.mts +0 -18
  517. package/dist/tabs-C6FfBwPY.d.ts +0 -18
  518. package/dist/tenant-provider-B8eC_Wpb.d.mts +0 -27
  519. package/dist/tenant-provider-B8eC_Wpb.d.ts +0 -27
  520. package/dist/types/index.d.mts +0 -469
  521. package/dist/types/index.d.ts +0 -469
  522. package/dist/types/index.js +0 -25
  523. package/dist/types/index.js.map +0 -1
  524. package/dist/types/index.mjs +0 -21
  525. package/dist/types/index.mjs.map +0 -1
  526. package/dist/ui/auth.d.mts +0 -39
  527. package/dist/ui/auth.d.ts +0 -39
  528. package/dist/ui/auth.js +0 -4941
  529. package/dist/ui/auth.js.map +0 -1
  530. package/dist/ui/auth.mjs +0 -4896
  531. package/dist/ui/auth.mjs.map +0 -1
  532. package/dist/ui/crud.d.mts +0 -2
  533. package/dist/ui/crud.d.ts +0 -2
  534. package/dist/ui/crud.js +0 -4
  535. package/dist/ui/crud.js.map +0 -1
  536. package/dist/ui/crud.mjs +0 -3
  537. package/dist/ui/crud.mjs.map +0 -1
  538. package/dist/ui/data-display.d.mts +0 -596
  539. package/dist/ui/data-display.d.ts +0 -596
  540. package/dist/ui/data-display.js +0 -5307
  541. package/dist/ui/data-display.js.map +0 -1
  542. package/dist/ui/data-display.mjs +0 -5212
  543. package/dist/ui/data-display.mjs.map +0 -1
  544. package/dist/ui/feedback.d.mts +0 -55
  545. package/dist/ui/feedback.d.ts +0 -55
  546. package/dist/ui/feedback.js +0 -2608
  547. package/dist/ui/feedback.js.map +0 -1
  548. package/dist/ui/feedback.mjs +0 -2526
  549. package/dist/ui/feedback.mjs.map +0 -1
  550. package/dist/ui/forms.d.mts +0 -309
  551. package/dist/ui/forms.d.ts +0 -309
  552. package/dist/ui/forms.js +0 -4656
  553. package/dist/ui/forms.js.map +0 -1
  554. package/dist/ui/forms.mjs +0 -4571
  555. package/dist/ui/forms.mjs.map +0 -1
  556. package/dist/ui/index.d.mts +0 -331
  557. package/dist/ui/index.d.ts +0 -331
  558. package/dist/ui/index.js +0 -16953
  559. package/dist/ui/index.js.map +0 -1
  560. package/dist/ui/index.mjs +0 -16598
  561. package/dist/ui/index.mjs.map +0 -1
  562. package/dist/ui/primitives/client.d.mts +0 -61
  563. package/dist/ui/primitives/client.d.ts +0 -61
  564. package/dist/ui/primitives/client.js +0 -3408
  565. package/dist/ui/primitives/client.js.map +0 -1
  566. package/dist/ui/primitives/client.mjs +0 -3256
  567. package/dist/ui/primitives/client.mjs.map +0 -1
  568. package/dist/ui/primitives.d.mts +0 -113
  569. package/dist/ui/primitives.d.ts +0 -113
  570. package/dist/ui/primitives.js +0 -3356
  571. package/dist/ui/primitives.js.map +0 -1
  572. package/dist/ui/primitives.mjs +0 -3227
  573. package/dist/ui/primitives.mjs.map +0 -1
  574. package/dist/user/index.d.mts +0 -228
  575. package/dist/user/index.d.ts +0 -228
  576. package/dist/user/index.js +0 -4306
  577. package/dist/user/index.js.map +0 -1
  578. package/dist/user/index.mjs +0 -4260
  579. package/dist/user/index.mjs.map +0 -1
  580. package/dist/utils/index.d.mts +0 -205
  581. package/dist/utils/index.d.ts +0 -205
  582. package/dist/utils/index.js +0 -574
  583. package/dist/utils/index.js.map +0 -1
  584. package/dist/utils/index.mjs +0 -514
  585. package/dist/utils/index.mjs.map +0 -1
  586. package/dist/workflow/index.d.mts +0 -40
  587. package/dist/workflow/index.d.ts +0 -40
  588. package/dist/workflow/index.js +0 -3710
  589. package/dist/workflow/index.js.map +0 -1
  590. package/dist/workflow/index.mjs +0 -3677
  591. package/dist/workflow/index.mjs.map +0 -1
@@ -0,0 +1,290 @@
1
+ import { describe, it, expect, vi } from "vitest";
2
+ import {
3
+ parseCSV,
4
+ parseJSON,
5
+ validateImportData,
6
+ exportToCSV,
7
+ exportToJSON,
8
+ exportToXLSX,
9
+ } from "./import-export-service";
10
+ import type { EntityConfig } from "../../types";
11
+ import * as XLSX from "xlsx";
12
+
13
+ // Polyfill File.text() if missing or just mock it on instances
14
+ function createFile(content: string, name: string, type: string) {
15
+ const file = new File([content], name, { type });
16
+ // Mock text method
17
+ Object.defineProperty(file, "text", {
18
+ value: () => Promise.resolve(content),
19
+ writable: true,
20
+ });
21
+ // Mock arrayBuffer
22
+ Object.defineProperty(file, "arrayBuffer", {
23
+ value: () => Promise.resolve(new TextEncoder().encode(content)),
24
+ writable: true,
25
+ });
26
+ return file;
27
+ }
28
+
29
+ // Mock xlsx
30
+ vi.mock("xlsx", () => ({
31
+ read: vi.fn(() => ({
32
+ SheetNames: ["Sheet1"],
33
+ Sheets: {
34
+ Sheet1: {},
35
+ },
36
+ })),
37
+ utils: {
38
+ sheet_to_json: vi.fn(() => [{ name: "Alice", age: 30 }]),
39
+ book_new: vi.fn(() => ({})),
40
+ aoa_to_sheet: vi.fn(),
41
+ book_append_sheet: vi.fn(),
42
+ json_to_sheet: vi.fn(),
43
+ },
44
+ write: vi.fn(() => new ArrayBuffer(8)),
45
+ }));
46
+
47
+ describe("ImportExportService", () => {
48
+ describe("parseCSV", () => {
49
+ it("should parse valid CSV", async () => {
50
+ const csvContent = "name,age\nAlice,30\nBob,25";
51
+ const file = createFile(csvContent, "test.csv", "text/csv");
52
+ const result = await parseCSV(file);
53
+ expect(result).toHaveLength(2);
54
+ expect(result[0]).toEqual({ name: "Alice", age: "30" });
55
+ expect(result[1]).toEqual({ name: "Bob", age: "25" });
56
+ });
57
+
58
+ it("should handle empty file", async () => {
59
+ const file = createFile("", "empty.csv", "text/csv");
60
+ const result = await parseCSV(file);
61
+ expect(result).toEqual([]);
62
+ });
63
+ });
64
+
65
+ describe("parseJSON", () => {
66
+ it("should parse valid JSON array", async () => {
67
+ const jsonContent = JSON.stringify([{ name: "Alice", age: 30 }]);
68
+ const file = createFile(jsonContent, "test.json", "application/json");
69
+ const result = await parseJSON(file);
70
+ expect(result).toHaveLength(1);
71
+ expect(result[0]).toEqual({ name: "Alice", age: 30 });
72
+ });
73
+
74
+ it("should parse single JSON object", async () => {
75
+ const jsonContent = JSON.stringify({ name: "Alice", age: 30 });
76
+ const file = createFile(jsonContent, "test.json", "application/json");
77
+ const result = await parseJSON(file);
78
+ expect(result).toHaveLength(1);
79
+ expect(result[0]).toEqual({ name: "Alice", age: 30 });
80
+ });
81
+ });
82
+
83
+ describe("validateImportData", () => {
84
+ const mockConfig: EntityConfig = {
85
+ fields: [
86
+ { name: "name", label: "Name", type: "text", required: true },
87
+ { name: "age", label: "Age", type: "number" },
88
+ { name: "email", label: "Email", type: "email" },
89
+ ],
90
+ apiEndpoint: "/api/test",
91
+ name: "test",
92
+ label: "Test",
93
+ pluralLabel: "Tests",
94
+ idField: "id",
95
+ displayField: "name",
96
+ } as unknown as EntityConfig; // Partial mock
97
+
98
+ it("should validate valid data", () => {
99
+ const data = [{ name: "Alice", age: "30", email: "alice@example.com" }];
100
+ const result = validateImportData(data, mockConfig);
101
+ expect(result.success).toBe(true);
102
+ expect(result.errors).toHaveLength(0);
103
+ });
104
+
105
+ it("should catch required field errors", () => {
106
+ const data = [{ age: "30" }]; // missing name
107
+ const result = validateImportData(data, mockConfig);
108
+ expect(result.success).toBe(false);
109
+ expect(result.errors[0].message).toContain("Name là bắt buộc");
110
+ });
111
+
112
+ it("should catch type errors", () => {
113
+ const data = [
114
+ { name: "Bob", age: "invalid" },
115
+ { name: "Charlie", email: "not-an-email" },
116
+ ];
117
+ const result = validateImportData(data, mockConfig);
118
+ expect(result.success).toBe(false);
119
+ expect(result.errors).toHaveLength(2);
120
+ });
121
+
122
+ it("should validate case-insensitive options", () => {
123
+ const configWithOptions: EntityConfig = {
124
+ ...mockConfig,
125
+ fields: [
126
+ ...mockConfig.fields,
127
+ {
128
+ name: "status",
129
+ label: "Status",
130
+ type: "select",
131
+ options: [
132
+ { label: "Active", value: "active" },
133
+ { label: "Inactive", value: "inactive" },
134
+ ],
135
+ },
136
+ ],
137
+ };
138
+
139
+ const data = [
140
+ { name: "User1", status: "Active" },
141
+ { name: "User2", status: "ACTIVE" },
142
+ { name: "User3", status: "active" },
143
+ ];
144
+ const result = validateImportData(data, configWithOptions);
145
+ expect(result.success).toBe(true);
146
+ expect(result.errors).toHaveLength(0);
147
+ });
148
+
149
+ it("should reject invalid options", () => {
150
+ const configWithOptions: EntityConfig = {
151
+ ...mockConfig,
152
+ fields: [
153
+ ...mockConfig.fields,
154
+ {
155
+ name: "status",
156
+ label: "Status",
157
+ type: "select",
158
+ options: ["red", "blue"],
159
+ },
160
+ ],
161
+ };
162
+
163
+ const data = [{ name: "User1", status: "green" }];
164
+ const result = validateImportData(data, configWithOptions);
165
+ expect(result.success).toBe(false);
166
+ expect(result.errors[0].message).toContain("Status không hợp lệ");
167
+ });
168
+ });
169
+
170
+ describe("exportToCSV", () => {
171
+ it("should export data to CSV", () => {
172
+ const data = [{ name: "Alice", age: 30 }];
173
+ const result = exportToCSV(data);
174
+ expect(result).toContain("name,age");
175
+ expect(result).toContain("Alice,30");
176
+ });
177
+
178
+ it("should handle specific fields", () => {
179
+ const data = [{ name: "Alice", age: 30, hidden: "secret" }];
180
+ const result = exportToCSV(data, ["name"]);
181
+ expect(result).toContain("name");
182
+ expect(result).not.toContain("age");
183
+ expect(result).toContain("Alice");
184
+ });
185
+ });
186
+
187
+ describe("exportToJSON", () => {
188
+ it("should export data to JSON", () => {
189
+ const data = [{ name: "Alice", age: 30 }];
190
+ const result = exportToJSON(data);
191
+ expect(JSON.parse(result)).toEqual(data);
192
+ });
193
+
194
+ it("should handle specific fields", () => {
195
+ const data = [{ name: "Alice", age: 30, hidden: "secret" }];
196
+ const result = exportToJSON(data, ["name"]);
197
+ const parsed = JSON.parse(result);
198
+ expect(parsed[0]).toHaveProperty("name");
199
+ expect(parsed[0]).not.toHaveProperty("age");
200
+ });
201
+ });
202
+
203
+ // Since we mocked xlsx, we can test using those mocks
204
+ describe("XLSX functions", () => {
205
+ it("should parse XLSX", async () => {
206
+ const { parseXLSX } = await import("./import-export-service");
207
+ const file = createFile(
208
+ "dummy",
209
+ "test.xlsx",
210
+ "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
211
+ );
212
+ const result = await parseXLSX(file);
213
+ expect(result).toEqual([{ name: "Alice", age: 30 }]);
214
+ });
215
+
216
+ it("should generate XLSX template", async () => {
217
+ const { generateXLSXTemplate } = await import("./import-export-service");
218
+ const mockConfig: EntityConfig = {
219
+ fields: [{ name: "name", label: "Name", type: "text" }],
220
+ apiEndpoint: "/api",
221
+ name: "test",
222
+ label: "Test",
223
+ pluralLabel: "Tests",
224
+ idField: "id",
225
+ displayField: "name",
226
+ } as any;
227
+ const blob = await generateXLSXTemplate(mockConfig);
228
+ expect(blob).toBeInstanceOf(Blob);
229
+ });
230
+
231
+ it("should export to XLSX", async () => {
232
+ const { exportToXLSX } = await import("./import-export-service");
233
+ const data = [{ name: "Alice" }];
234
+ const blob = await exportToXLSX(data);
235
+ expect(blob).toBeInstanceOf(Blob);
236
+ });
237
+
238
+ it("should export to XLSX with specific fields", async () => {
239
+ const { exportToXLSX } = await import("./import-export-service");
240
+ const data = [{ name: "Alice", age: 30 }];
241
+ const blob = await exportToXLSX(data, ["name"]);
242
+ expect(blob).toBeInstanceOf(Blob);
243
+ // Verify filtered data passed to xlsx (via mock)
244
+ const XLSX = await import("xlsx");
245
+ expect(XLSX.utils.json_to_sheet).toHaveBeenCalledWith(
246
+ expect.arrayContaining([expect.objectContaining({ name: "Alice" })]),
247
+ );
248
+ // Should NOT check for age absence strictly on the mock call args because json_to_sheet receives the mapped array
249
+ // But we can check expected structure if we spy carefully.
250
+ // In this mock, we just check call happened.
251
+ });
252
+ });
253
+
254
+ describe("downloadFile", () => {
255
+ it("should trigger download", async () => {
256
+ const { downloadFile } = await import("./import-export-service");
257
+ const blob = new Blob(["test"]);
258
+
259
+ // Mock DOM
260
+ const link = {
261
+ href: "",
262
+ download: "",
263
+ click: vi.fn(),
264
+ style: {},
265
+ };
266
+ const createElementSpy = vi
267
+ .spyOn(document, "createElement")
268
+ .mockReturnValue(link as any);
269
+ const appendChildSpy = vi
270
+ .spyOn(document.body, "appendChild")
271
+ .mockImplementation(() => link as any);
272
+ const removeChildSpy = vi
273
+ .spyOn(document.body, "removeChild")
274
+ .mockImplementation(() => link as any);
275
+
276
+ // Mock URL
277
+ global.URL.createObjectURL = vi.fn(() => "blob:url");
278
+ global.URL.revokeObjectURL = vi.fn();
279
+
280
+ downloadFile(blob, "test.csv");
281
+
282
+ expect(createElementSpy).toHaveBeenCalledWith("a");
283
+ expect(link.download).toBe("test.csv");
284
+ expect(link.click).toHaveBeenCalled();
285
+ expect(appendChildSpy).toHaveBeenCalled();
286
+ expect(removeChildSpy).toHaveBeenCalled();
287
+ expect(global.URL.revokeObjectURL).toHaveBeenCalledWith("blob:url");
288
+ });
289
+ });
290
+ });
@@ -0,0 +1,352 @@
1
+ import type { EntityConfig, ImportOptions, ImportResult } from "../../types";
2
+
3
+ function getRowValue(
4
+ row: Record<string, unknown>,
5
+ field: { name: string; label: string },
6
+ ): unknown {
7
+ // Prefer machine key (field.name), but support human header (field.label)
8
+ // for backward compatibility with older templates.
9
+ if (Object.prototype.hasOwnProperty.call(row, field.name)) {
10
+ return row[field.name];
11
+ }
12
+ if (Object.prototype.hasOwnProperty.call(row, field.label)) {
13
+ return row[field.label];
14
+ }
15
+ return undefined;
16
+ }
17
+
18
+ /**
19
+ * Parse CSV file
20
+ */
21
+ export async function parseCSV(file: File): Promise<Record<string, unknown>[]> {
22
+ const text = await file.text();
23
+ const lines = text.split("\n").filter((line) => line.trim());
24
+ if (lines.length === 0) return [];
25
+
26
+ const headers = lines[0].split(",").map((h) => h.trim());
27
+ const rows: Record<string, unknown>[] = [];
28
+
29
+ for (let i = 1; i < lines.length; i++) {
30
+ const values = lines[i].split(",").map((v) => v.trim());
31
+ const row: Record<string, unknown> = {};
32
+ headers.forEach((header, index) => {
33
+ row[header] = values[index] || "";
34
+ });
35
+ rows.push(row);
36
+ }
37
+
38
+ return rows;
39
+ }
40
+
41
+ /**
42
+ * Parse JSON file
43
+ */
44
+ export async function parseJSON(
45
+ file: File,
46
+ ): Promise<Record<string, unknown>[]> {
47
+ const text = await file.text();
48
+ const data = JSON.parse(text);
49
+ return Array.isArray(data) ? data : [data];
50
+ }
51
+
52
+ /**
53
+ * Parse XLSX file (requires xlsx library)
54
+ */
55
+ export async function parseXLSX(
56
+ file: File,
57
+ ): Promise<Record<string, unknown>[]> {
58
+ // Dynamic import to avoid bundling xlsx if not needed
59
+ const XLSX = await import("xlsx");
60
+ const arrayBuffer = await file.arrayBuffer();
61
+ const workbook = XLSX.read(arrayBuffer, { type: "array" });
62
+ const firstSheet = workbook.Sheets[workbook.SheetNames[0]];
63
+ return XLSX.utils.sheet_to_json(firstSheet);
64
+ }
65
+
66
+ /**
67
+ * Parse file based on format
68
+ */
69
+ export async function parseFile(
70
+ file: File,
71
+ format: ImportOptions["format"],
72
+ ): Promise<Record<string, unknown>[]> {
73
+ switch (format) {
74
+ case "csv":
75
+ return parseCSV(file);
76
+ case "json":
77
+ return parseJSON(file);
78
+ case "xlsx":
79
+ return parseXLSX(file);
80
+ default:
81
+ throw new Error(`Unsupported format: ${format}`);
82
+ }
83
+ }
84
+
85
+ /**
86
+ * Validate import data against field configs
87
+ */
88
+ export function validateImportData(
89
+ data: Record<string, unknown>[],
90
+ config: EntityConfig,
91
+ options: Partial<ImportOptions> = {},
92
+ ): ImportResult {
93
+ const errors: ImportResult["errors"] = [];
94
+ let imported = 0;
95
+ let failed = 0;
96
+
97
+ data.forEach((row, index) => {
98
+ const rowErrors: string[] = [];
99
+
100
+ config.fields.forEach((field) => {
101
+ if (field.hideInForm) return;
102
+
103
+ const value = getRowValue(row, { name: field.name, label: field.label });
104
+
105
+ // Required check
106
+ if (
107
+ field.required &&
108
+ (value === null || value === undefined || value === "")
109
+ ) {
110
+ rowErrors.push(`${field.label} là bắt buộc`);
111
+ }
112
+
113
+ // List validation (check if value is in options)
114
+ if (
115
+ value !== null &&
116
+ value !== undefined &&
117
+ value !== "" &&
118
+ field.options &&
119
+ field.options.length > 0
120
+ ) {
121
+ const stringValue = String(value).toLowerCase();
122
+ const validOption = field.options.some((opt) => {
123
+ const isObject = typeof opt === "object" && opt !== null;
124
+ const optValue = isObject ? opt.value : opt;
125
+ return String(optValue).toLowerCase() === stringValue;
126
+ });
127
+
128
+ if (!validOption) {
129
+ // Collect valid labels for error message
130
+ const validLabels = field.options
131
+ .map((opt) => {
132
+ const isObject = typeof opt === "object" && opt !== null;
133
+ return isObject ? (opt as any).label : String(opt);
134
+ })
135
+ .join(", ");
136
+ rowErrors.push(
137
+ `${field.label} không hợp lệ. Chỉ chấp nhận: ${validLabels}`,
138
+ );
139
+ }
140
+ } else if (value !== null && value !== undefined && value !== "") {
141
+ // Type validation (only if not a list field or validated above)
142
+ switch (field.type) {
143
+ case "email":
144
+ if (
145
+ typeof value === "string" &&
146
+ !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value)
147
+ ) {
148
+ rowErrors.push(`${field.label} phải là email hợp lệ`);
149
+ }
150
+ break;
151
+ case "number":
152
+ case "integer":
153
+ if (isNaN(Number(value))) {
154
+ rowErrors.push(`${field.label} phải là số`);
155
+ }
156
+ break;
157
+ }
158
+ }
159
+ // End validation
160
+ });
161
+
162
+ if (rowErrors.length > 0) {
163
+ failed++;
164
+ rowErrors.forEach((error) => {
165
+ errors.push({
166
+ row: index + 1,
167
+ field: "",
168
+ message: error,
169
+ });
170
+ });
171
+ } else {
172
+ imported++;
173
+ }
174
+ });
175
+
176
+ return {
177
+ success: failed === 0 || options.skipErrors === true,
178
+ imported,
179
+ failed,
180
+ errors,
181
+ };
182
+ }
183
+
184
+ /**
185
+ * Generate CSV template from config
186
+ */
187
+ export function generateCSVTemplate(config: EntityConfig): string {
188
+ const headers = config.fields
189
+ .filter((field) => !field.hideInForm || field.showInImport)
190
+ .map((field) => field.label);
191
+
192
+ return headers.join(",") + "\n";
193
+ }
194
+
195
+ /**
196
+ * Generate JSON template from config
197
+ */
198
+ export function generateJSONTemplate(
199
+ config: EntityConfig,
200
+ ): Record<string, unknown> {
201
+ const template: Record<string, unknown> = {};
202
+
203
+ config.fields
204
+ .filter((field) => !field.hideInForm || field.showInImport)
205
+ .forEach((field) => {
206
+ // For JSON, we still use technical names as keys because JSON is developer-centric
207
+ // and usually requires strict structure.
208
+ template[field.name] = field.defaultValue || "";
209
+ });
210
+
211
+ return template;
212
+ }
213
+
214
+ /**
215
+ * Generate XLSX template (requires xlsx library)
216
+ */
217
+ export async function generateXLSXTemplate(
218
+ config: EntityConfig,
219
+ ): Promise<Blob> {
220
+ const XLSX = await import("xlsx");
221
+
222
+ const importHeaders = config.fields
223
+ .filter((field) => !field.hideInForm || field.showInImport)
224
+ .map((field) => field.label);
225
+
226
+ const workbook = XLSX.utils.book_new();
227
+
228
+ // Sheet 1: actual import template (machine headers)
229
+ const templateSheet = XLSX.utils.aoa_to_sheet([importHeaders]);
230
+ XLSX.utils.book_append_sheet(workbook, templateSheet, "Import");
231
+
232
+ // Sheet 2: guide for humans (doesn't affect parsing because parser reads first sheet)
233
+ const guideRows = [
234
+ ["field", "label", "type", "required"],
235
+ ...config.fields
236
+ .filter((field) => !field.hideInForm || field.showInImport)
237
+ .map((field) => [
238
+ field.name,
239
+ field.label,
240
+ field.type,
241
+ field.required ? "yes" : "no",
242
+ ]),
243
+ ];
244
+ const guideSheet = XLSX.utils.aoa_to_sheet(guideRows);
245
+ XLSX.utils.book_append_sheet(workbook, guideSheet, "Guide");
246
+
247
+ const buffer = XLSX.write(workbook, { type: "array", bookType: "xlsx" });
248
+ return new Blob([buffer], {
249
+ type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
250
+ });
251
+ }
252
+
253
+ /**
254
+ * Export data to CSV
255
+ */
256
+ export function exportToCSV(
257
+ data: Record<string, unknown>[],
258
+ fields?: string[],
259
+ ): string {
260
+ if (data.length === 0) return "";
261
+
262
+ const keys = fields || Object.keys(data[0]);
263
+ const headers = keys.join(",");
264
+ const rows = data.map((row) =>
265
+ keys.map((key) => String(row[key] || "")).join(","),
266
+ );
267
+
268
+ return [headers, ...rows].join("\n");
269
+ }
270
+
271
+ /**
272
+ * Export data to JSON
273
+ */
274
+ export function exportToJSON(
275
+ data: Record<string, unknown>[],
276
+ fields?: string[],
277
+ ): string {
278
+ if (fields) {
279
+ const filtered = data.map((row) => {
280
+ const filteredRow: Record<string, unknown> = {};
281
+ fields.forEach((field) => {
282
+ filteredRow[field] = row[field];
283
+ });
284
+ return filteredRow;
285
+ });
286
+ return JSON.stringify(filtered, null, 2);
287
+ }
288
+
289
+ return JSON.stringify(data, null, 2);
290
+ }
291
+
292
+ /**
293
+ * Export data to XLSX (requires xlsx library)
294
+ */
295
+ export async function exportToXLSX(
296
+ data: Record<string, unknown>[],
297
+ fields?: string[],
298
+ ): Promise<Blob> {
299
+ const XLSX = await import("xlsx");
300
+
301
+ let exportData: Record<string, unknown>[];
302
+ if (fields) {
303
+ exportData = data.map((row) => {
304
+ const filteredRow: Record<string, unknown> = {};
305
+ fields.forEach((field) => {
306
+ const value = row[field];
307
+ filteredRow[field] = Array.isArray(value)
308
+ ? value.join(", ")
309
+ : typeof value === "object" && value !== null
310
+ ? JSON.stringify(value)
311
+ : value;
312
+ });
313
+ return filteredRow;
314
+ });
315
+ } else {
316
+ exportData = data.map((row) => {
317
+ const newRow: Record<string, unknown> = {};
318
+ Object.keys(row).forEach((key) => {
319
+ const value = row[key];
320
+ newRow[key] = Array.isArray(value)
321
+ ? value.join(", ")
322
+ : typeof value === "object" && value !== null
323
+ ? JSON.stringify(value)
324
+ : value;
325
+ });
326
+ return newRow;
327
+ });
328
+ }
329
+
330
+ const worksheet = XLSX.utils.json_to_sheet(exportData);
331
+ const workbook = XLSX.utils.book_new();
332
+ XLSX.utils.book_append_sheet(workbook, worksheet, "Data");
333
+
334
+ const buffer = XLSX.write(workbook, { type: "array", bookType: "xlsx" });
335
+ return new Blob([buffer], {
336
+ type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
337
+ });
338
+ }
339
+
340
+ /**
341
+ * Download file
342
+ */
343
+ export function downloadFile(blob: Blob, filename: string): void {
344
+ const url = URL.createObjectURL(blob);
345
+ const link = document.createElement("a");
346
+ link.href = url;
347
+ link.download = filename;
348
+ document.body.appendChild(link);
349
+ link.click();
350
+ document.body.removeChild(link);
351
+ URL.revokeObjectURL(url);
352
+ }