@goplusvn/core 0.1.1 → 0.1.3

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 (223) hide show
  1. package/package.json +31 -175
  2. package/dist/audit/index.d.mts +0 -115
  3. package/dist/audit/index.d.ts +0 -115
  4. package/dist/audit/index.js +0 -204
  5. package/dist/audit/index.js.map +0 -1
  6. package/dist/audit/index.mjs +0 -200
  7. package/dist/audit/index.mjs.map +0 -1
  8. package/dist/auth/index.d.mts +0 -86
  9. package/dist/auth/index.d.ts +0 -86
  10. package/dist/auth/index.js +0 -210
  11. package/dist/auth/index.js.map +0 -1
  12. package/dist/auth/index.mjs +0 -198
  13. package/dist/auth/index.mjs.map +0 -1
  14. package/dist/button-1dWvP9Ib.d.mts +0 -30
  15. package/dist/button-1dWvP9Ib.d.ts +0 -30
  16. package/dist/calendar-2QzdEo1z.d.mts +0 -20
  17. package/dist/calendar-2QzdEo1z.d.ts +0 -20
  18. package/dist/code-generation/index.d.mts +0 -30
  19. package/dist/code-generation/index.d.ts +0 -30
  20. package/dist/code-generation/index.js +0 -31
  21. package/dist/code-generation/index.js.map +0 -1
  22. package/dist/code-generation/index.mjs +0 -28
  23. package/dist/code-generation/index.mjs.map +0 -1
  24. package/dist/configs/index.d.mts +0 -175
  25. package/dist/configs/index.d.ts +0 -175
  26. package/dist/configs/index.js +0 -254
  27. package/dist/configs/index.js.map +0 -1
  28. package/dist/configs/index.mjs +0 -233
  29. package/dist/configs/index.mjs.map +0 -1
  30. package/dist/crud/index.d.mts +0 -646
  31. package/dist/crud/index.d.ts +0 -646
  32. package/dist/crud/index.js +0 -11772
  33. package/dist/crud/index.js.map +0 -1
  34. package/dist/crud/index.mjs +0 -11665
  35. package/dist/crud/index.mjs.map +0 -1
  36. package/dist/crud/server.d.mts +0 -20
  37. package/dist/crud/server.d.ts +0 -20
  38. package/dist/crud/server.js +0 -123
  39. package/dist/crud/server.js.map +0 -1
  40. package/dist/crud/server.mjs +0 -120
  41. package/dist/crud/server.mjs.map +0 -1
  42. package/dist/data-table-skeleton-12NA8Mjx.d.mts +0 -39
  43. package/dist/data-table-skeleton-12NA8Mjx.d.ts +0 -39
  44. package/dist/dialog-bKfjZMTd.d.mts +0 -22
  45. package/dist/dialog-bKfjZMTd.d.ts +0 -22
  46. package/dist/dynamic-icon-DrGIiu2N.d.mts +0 -10
  47. package/dist/dynamic-icon-DrGIiu2N.d.ts +0 -10
  48. package/dist/home/index.d.mts +0 -269
  49. package/dist/home/index.d.ts +0 -269
  50. package/dist/home/index.js +0 -1678
  51. package/dist/home/index.js.map +0 -1
  52. package/dist/home/index.mjs +0 -1635
  53. package/dist/home/index.mjs.map +0 -1
  54. package/dist/hooks/index.d.mts +0 -7
  55. package/dist/hooks/index.d.ts +0 -7
  56. package/dist/hooks/index.js +0 -8316
  57. package/dist/hooks/index.js.map +0 -1
  58. package/dist/hooks/index.mjs +0 -8255
  59. package/dist/hooks/index.mjs.map +0 -1
  60. package/dist/index-50hpiPrV.d.ts +0 -116
  61. package/dist/index-B9zQVEVi.d.mts +0 -116
  62. package/dist/index.d.mts +0 -5
  63. package/dist/index.d.ts +0 -5
  64. package/dist/index.js +0 -123
  65. package/dist/index.js.map +0 -1
  66. package/dist/index.mjs +0 -118
  67. package/dist/index.mjs.map +0 -1
  68. package/dist/infrastructure/index.d.mts +0 -423
  69. package/dist/infrastructure/index.d.ts +0 -423
  70. package/dist/infrastructure/index.js +0 -633
  71. package/dist/infrastructure/index.js.map +0 -1
  72. package/dist/infrastructure/index.mjs +0 -619
  73. package/dist/infrastructure/index.mjs.map +0 -1
  74. package/dist/label-DWTEkNPo.d.ts +0 -226
  75. package/dist/label-LPpdcoBx.d.mts +0 -226
  76. package/dist/layout/index.d.mts +0 -48
  77. package/dist/layout/index.d.ts +0 -48
  78. package/dist/layout/index.js +0 -117
  79. package/dist/layout/index.js.map +0 -1
  80. package/dist/layout/index.mjs +0 -90
  81. package/dist/layout/index.mjs.map +0 -1
  82. package/dist/navigation/index.d.mts +0 -16
  83. package/dist/navigation/index.d.ts +0 -16
  84. package/dist/navigation/index.js +0 -53
  85. package/dist/navigation/index.js.map +0 -1
  86. package/dist/navigation/index.mjs +0 -50
  87. package/dist/navigation/index.mjs.map +0 -1
  88. package/dist/notification/index.d.mts +0 -105
  89. package/dist/notification/index.d.ts +0 -105
  90. package/dist/notification/index.js +0 -278
  91. package/dist/notification/index.js.map +0 -1
  92. package/dist/notification/index.mjs +0 -274
  93. package/dist/notification/index.mjs.map +0 -1
  94. package/dist/organization/index.d.mts +0 -99
  95. package/dist/organization/index.d.ts +0 -99
  96. package/dist/organization/index.js +0 -360
  97. package/dist/organization/index.js.map +0 -1
  98. package/dist/organization/index.mjs +0 -352
  99. package/dist/organization/index.mjs.map +0 -1
  100. package/dist/plugin/index.d.mts +0 -83
  101. package/dist/plugin/index.d.ts +0 -83
  102. package/dist/plugin/index.js +0 -86
  103. package/dist/plugin/index.js.map +0 -1
  104. package/dist/plugin/index.mjs +0 -84
  105. package/dist/plugin/index.mjs.map +0 -1
  106. package/dist/providers/index.d.mts +0 -25
  107. package/dist/providers/index.d.ts +0 -25
  108. package/dist/providers/index.js +0 -84
  109. package/dist/providers/index.js.map +0 -1
  110. package/dist/providers/index.mjs +0 -77
  111. package/dist/providers/index.mjs.map +0 -1
  112. package/dist/rbac/index.d.mts +0 -226
  113. package/dist/rbac/index.d.ts +0 -226
  114. package/dist/rbac/index.js +0 -4784
  115. package/dist/rbac/index.js.map +0 -1
  116. package/dist/rbac/index.mjs +0 -4722
  117. package/dist/rbac/index.mjs.map +0 -1
  118. package/dist/rbac/permissions.d.mts +0 -26
  119. package/dist/rbac/permissions.d.ts +0 -26
  120. package/dist/rbac/permissions.js +0 -94
  121. package/dist/rbac/permissions.js.map +0 -1
  122. package/dist/rbac/permissions.mjs +0 -90
  123. package/dist/rbac/permissions.mjs.map +0 -1
  124. package/dist/rbac/server.d.mts +0 -1
  125. package/dist/rbac/server.d.ts +0 -1
  126. package/dist/rbac/server.js +0 -128
  127. package/dist/rbac/server.js.map +0 -1
  128. package/dist/rbac/server.mjs +0 -124
  129. package/dist/rbac/server.mjs.map +0 -1
  130. package/dist/schemas/index.d.mts +0 -1257
  131. package/dist/schemas/index.d.ts +0 -1257
  132. package/dist/schemas/index.js +0 -572
  133. package/dist/schemas/index.js.map +0 -1
  134. package/dist/schemas/index.mjs +0 -523
  135. package/dist/schemas/index.mjs.map +0 -1
  136. package/dist/server-QuYCTa89.d.mts +0 -83
  137. package/dist/server-QuYCTa89.d.ts +0 -83
  138. package/dist/sonner-C74GlRDQ.d.mts +0 -71
  139. package/dist/sonner-C74GlRDQ.d.ts +0 -71
  140. package/dist/status-BOXZgIqX.d.mts +0 -12
  141. package/dist/status-BOXZgIqX.d.ts +0 -12
  142. package/dist/system/index.d.mts +0 -77
  143. package/dist/system/index.d.ts +0 -77
  144. package/dist/system/index.js +0 -102
  145. package/dist/system/index.js.map +0 -1
  146. package/dist/system/index.mjs +0 -100
  147. package/dist/system/index.mjs.map +0 -1
  148. package/dist/tabs-C6FfBwPY.d.mts +0 -18
  149. package/dist/tabs-C6FfBwPY.d.ts +0 -18
  150. package/dist/tenant-provider-B8eC_Wpb.d.mts +0 -27
  151. package/dist/tenant-provider-B8eC_Wpb.d.ts +0 -27
  152. package/dist/types/index.d.mts +0 -469
  153. package/dist/types/index.d.ts +0 -469
  154. package/dist/types/index.js +0 -25
  155. package/dist/types/index.js.map +0 -1
  156. package/dist/types/index.mjs +0 -21
  157. package/dist/types/index.mjs.map +0 -1
  158. package/dist/ui/auth.d.mts +0 -39
  159. package/dist/ui/auth.d.ts +0 -39
  160. package/dist/ui/auth.js +0 -4941
  161. package/dist/ui/auth.js.map +0 -1
  162. package/dist/ui/auth.mjs +0 -4896
  163. package/dist/ui/auth.mjs.map +0 -1
  164. package/dist/ui/crud.d.mts +0 -2
  165. package/dist/ui/crud.d.ts +0 -2
  166. package/dist/ui/crud.js +0 -4
  167. package/dist/ui/crud.js.map +0 -1
  168. package/dist/ui/crud.mjs +0 -3
  169. package/dist/ui/crud.mjs.map +0 -1
  170. package/dist/ui/data-display.d.mts +0 -596
  171. package/dist/ui/data-display.d.ts +0 -596
  172. package/dist/ui/data-display.js +0 -5307
  173. package/dist/ui/data-display.js.map +0 -1
  174. package/dist/ui/data-display.mjs +0 -5212
  175. package/dist/ui/data-display.mjs.map +0 -1
  176. package/dist/ui/feedback.d.mts +0 -55
  177. package/dist/ui/feedback.d.ts +0 -55
  178. package/dist/ui/feedback.js +0 -2608
  179. package/dist/ui/feedback.js.map +0 -1
  180. package/dist/ui/feedback.mjs +0 -2526
  181. package/dist/ui/feedback.mjs.map +0 -1
  182. package/dist/ui/forms.d.mts +0 -309
  183. package/dist/ui/forms.d.ts +0 -309
  184. package/dist/ui/forms.js +0 -4656
  185. package/dist/ui/forms.js.map +0 -1
  186. package/dist/ui/forms.mjs +0 -4571
  187. package/dist/ui/forms.mjs.map +0 -1
  188. package/dist/ui/index.d.mts +0 -331
  189. package/dist/ui/index.d.ts +0 -331
  190. package/dist/ui/index.js +0 -16953
  191. package/dist/ui/index.js.map +0 -1
  192. package/dist/ui/index.mjs +0 -16598
  193. package/dist/ui/index.mjs.map +0 -1
  194. package/dist/ui/primitives/client.d.mts +0 -61
  195. package/dist/ui/primitives/client.d.ts +0 -61
  196. package/dist/ui/primitives/client.js +0 -3408
  197. package/dist/ui/primitives/client.js.map +0 -1
  198. package/dist/ui/primitives/client.mjs +0 -3256
  199. package/dist/ui/primitives/client.mjs.map +0 -1
  200. package/dist/ui/primitives.d.mts +0 -113
  201. package/dist/ui/primitives.d.ts +0 -113
  202. package/dist/ui/primitives.js +0 -3356
  203. package/dist/ui/primitives.js.map +0 -1
  204. package/dist/ui/primitives.mjs +0 -3227
  205. package/dist/ui/primitives.mjs.map +0 -1
  206. package/dist/user/index.d.mts +0 -228
  207. package/dist/user/index.d.ts +0 -228
  208. package/dist/user/index.js +0 -4306
  209. package/dist/user/index.js.map +0 -1
  210. package/dist/user/index.mjs +0 -4260
  211. package/dist/user/index.mjs.map +0 -1
  212. package/dist/utils/index.d.mts +0 -205
  213. package/dist/utils/index.d.ts +0 -205
  214. package/dist/utils/index.js +0 -574
  215. package/dist/utils/index.js.map +0 -1
  216. package/dist/utils/index.mjs +0 -514
  217. package/dist/utils/index.mjs.map +0 -1
  218. package/dist/workflow/index.d.mts +0 -40
  219. package/dist/workflow/index.d.ts +0 -40
  220. package/dist/workflow/index.js +0 -3710
  221. package/dist/workflow/index.js.map +0 -1
  222. package/dist/workflow/index.mjs +0 -3677
  223. package/dist/workflow/index.mjs.map +0 -1
@@ -1,48 +0,0 @@
1
- import * as react_jsx_runtime from 'react/jsx-runtime';
2
- import * as React from 'react';
3
-
4
- interface AppShellProps {
5
- children: React.ReactNode;
6
- sidebar?: React.ReactNode;
7
- header?: React.ReactNode;
8
- footer?: React.ReactNode;
9
- className?: string;
10
- }
11
- declare function AppShell({ children, sidebar, header, footer, className, }: AppShellProps): react_jsx_runtime.JSX.Element;
12
- interface HeaderProps {
13
- children?: React.ReactNode;
14
- className?: string;
15
- sticky?: boolean;
16
- }
17
- declare function Header({ children, className, sticky }: HeaderProps): react_jsx_runtime.JSX.Element;
18
- interface SidebarProps {
19
- children?: React.ReactNode;
20
- className?: string;
21
- collapsed?: boolean;
22
- width?: string | number;
23
- collapsedWidth?: string | number;
24
- }
25
- declare function Sidebar({ children, className, collapsed, width, collapsedWidth, }: SidebarProps): react_jsx_runtime.JSX.Element;
26
- interface FooterProps {
27
- children?: React.ReactNode;
28
- className?: string;
29
- }
30
- declare function Footer({ children, className }: FooterProps): react_jsx_runtime.JSX.Element;
31
- interface PageContainerProps {
32
- children: React.ReactNode;
33
- className?: string;
34
- title?: string;
35
- description?: string;
36
- }
37
- declare function PageContainer({ children, className, title, description, }: PageContainerProps): react_jsx_runtime.JSX.Element;
38
- interface BreadcrumbItem {
39
- label: string;
40
- href?: string;
41
- }
42
- interface BreadcrumbProps {
43
- items: BreadcrumbItem[];
44
- className?: string;
45
- }
46
- declare function Breadcrumb({ items, className }: BreadcrumbProps): react_jsx_runtime.JSX.Element;
47
-
48
- export { AppShell, type AppShellProps, Breadcrumb, type BreadcrumbItem, type BreadcrumbProps, Footer, type FooterProps, Header, type HeaderProps, PageContainer, type PageContainerProps, Sidebar, type SidebarProps };
@@ -1,48 +0,0 @@
1
- import * as react_jsx_runtime from 'react/jsx-runtime';
2
- import * as React from 'react';
3
-
4
- interface AppShellProps {
5
- children: React.ReactNode;
6
- sidebar?: React.ReactNode;
7
- header?: React.ReactNode;
8
- footer?: React.ReactNode;
9
- className?: string;
10
- }
11
- declare function AppShell({ children, sidebar, header, footer, className, }: AppShellProps): react_jsx_runtime.JSX.Element;
12
- interface HeaderProps {
13
- children?: React.ReactNode;
14
- className?: string;
15
- sticky?: boolean;
16
- }
17
- declare function Header({ children, className, sticky }: HeaderProps): react_jsx_runtime.JSX.Element;
18
- interface SidebarProps {
19
- children?: React.ReactNode;
20
- className?: string;
21
- collapsed?: boolean;
22
- width?: string | number;
23
- collapsedWidth?: string | number;
24
- }
25
- declare function Sidebar({ children, className, collapsed, width, collapsedWidth, }: SidebarProps): react_jsx_runtime.JSX.Element;
26
- interface FooterProps {
27
- children?: React.ReactNode;
28
- className?: string;
29
- }
30
- declare function Footer({ children, className }: FooterProps): react_jsx_runtime.JSX.Element;
31
- interface PageContainerProps {
32
- children: React.ReactNode;
33
- className?: string;
34
- title?: string;
35
- description?: string;
36
- }
37
- declare function PageContainer({ children, className, title, description, }: PageContainerProps): react_jsx_runtime.JSX.Element;
38
- interface BreadcrumbItem {
39
- label: string;
40
- href?: string;
41
- }
42
- interface BreadcrumbProps {
43
- items: BreadcrumbItem[];
44
- className?: string;
45
- }
46
- declare function Breadcrumb({ items, className }: BreadcrumbProps): react_jsx_runtime.JSX.Element;
47
-
48
- export { AppShell, type AppShellProps, Breadcrumb, type BreadcrumbItem, type BreadcrumbProps, Footer, type FooterProps, Header, type HeaderProps, PageContainer, type PageContainerProps, Sidebar, type SidebarProps };
@@ -1,117 +0,0 @@
1
- 'use strict';
2
-
3
- var React = require('react');
4
- var clsx = require('clsx');
5
- var tailwindMerge = require('tailwind-merge');
6
- var jsxRuntime = require('react/jsx-runtime');
7
-
8
- function _interopNamespace(e) {
9
- if (e && e.__esModule) return e;
10
- var n = Object.create(null);
11
- if (e) {
12
- Object.keys(e).forEach(function (k) {
13
- if (k !== 'default') {
14
- var d = Object.getOwnPropertyDescriptor(e, k);
15
- Object.defineProperty(n, k, d.get ? d : {
16
- enumerable: true,
17
- get: function () { return e[k]; }
18
- });
19
- }
20
- });
21
- }
22
- n.default = e;
23
- return Object.freeze(n);
24
- }
25
-
26
- var React__namespace = /*#__PURE__*/_interopNamespace(React);
27
-
28
- function cn(...inputs) {
29
- return tailwindMerge.twMerge(clsx.clsx(inputs));
30
- }
31
- function AppShell({
32
- children,
33
- sidebar,
34
- header,
35
- footer,
36
- className
37
- }) {
38
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: cn("flex min-h-screen", className), children: [
39
- sidebar,
40
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-1 flex-col", children: [
41
- header,
42
- /* @__PURE__ */ jsxRuntime.jsx("main", { className: "flex-1", children }),
43
- footer
44
- ] })
45
- ] });
46
- }
47
- function Header({ children, className, sticky = true }) {
48
- return /* @__PURE__ */ jsxRuntime.jsx(
49
- "header",
50
- {
51
- className: cn(
52
- "border-b bg-background",
53
- sticky && "sticky top-0 z-50",
54
- className
55
- ),
56
- children
57
- }
58
- );
59
- }
60
- function Sidebar({
61
- children,
62
- className,
63
- collapsed = false,
64
- width = 280,
65
- collapsedWidth = 68
66
- }) {
67
- const sidebarWidth = collapsed ? collapsedWidth : width;
68
- return /* @__PURE__ */ jsxRuntime.jsx(
69
- "aside",
70
- {
71
- className: cn("border-r bg-background transition-all", className),
72
- style: {
73
- width: typeof sidebarWidth === "number" ? `${sidebarWidth}px` : sidebarWidth
74
- },
75
- children
76
- }
77
- );
78
- }
79
- function Footer({ children, className }) {
80
- return /* @__PURE__ */ jsxRuntime.jsx("footer", { className: cn("border-t bg-background", className), children });
81
- }
82
- function PageContainer({
83
- children,
84
- className,
85
- title,
86
- description
87
- }) {
88
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: cn("w-full px-4 md:px-6 lg:px-8 py-6", className), children: [
89
- (title || description) && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mb-6", children: [
90
- title && /* @__PURE__ */ jsxRuntime.jsx("h1", { className: "text-2xl font-bold", children: title }),
91
- description && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-muted-foreground", children: description })
92
- ] }),
93
- children
94
- ] });
95
- }
96
- function Breadcrumb({ items, className }) {
97
- return /* @__PURE__ */ jsxRuntime.jsx("nav", { className: cn("flex items-center space-x-2 text-sm", className), children: items.map((item, index) => /* @__PURE__ */ jsxRuntime.jsxs(React__namespace.Fragment, { children: [
98
- index > 0 && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-muted-foreground", children: "/" }),
99
- item.href ? /* @__PURE__ */ jsxRuntime.jsx(
100
- "a",
101
- {
102
- href: item.href,
103
- className: "text-muted-foreground hover:text-foreground",
104
- children: item.label
105
- }
106
- ) : /* @__PURE__ */ jsxRuntime.jsx("span", { className: "font-medium", children: item.label })
107
- ] }, index)) });
108
- }
109
-
110
- exports.AppShell = AppShell;
111
- exports.Breadcrumb = Breadcrumb;
112
- exports.Footer = Footer;
113
- exports.Header = Header;
114
- exports.PageContainer = PageContainer;
115
- exports.Sidebar = Sidebar;
116
- //# sourceMappingURL=index.js.map
117
- //# sourceMappingURL=index.js.map
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../../src/utils/index.ts","../../src/layout/index.tsx"],"names":["twMerge","clsx","jsxs","jsx","React"],"mappings":"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","file":"index.js","sourcesContent":["// @goerp/core/utils\n// Utility functions for GoERP platform\n\nimport { clsx } from \"clsx\";\nimport type { ClassValue } from \"clsx\";\nimport { twMerge } from \"tailwind-merge\";\n\n// Import types from core/types\nimport type { LocaleType, FormatStyleType } from \"../types\";\n\n// ============================================================================\n// Class Names\n// ============================================================================\n\n/**\n * Merge Tailwind CSS classes with clsx\n */\nexport function cn(...inputs: ClassValue[]) {\n return twMerge(clsx(inputs));\n}\n\n// ============================================================================\n// String Utilities\n// ============================================================================\n\n/**\n * Get initials from full name\n */\nexport function getInitials(fullName: string): string {\n if (fullName.length === 0) return \"\";\n const names = fullName.split(\" \");\n const initials = names.map((name) => name.charAt(0).toUpperCase()).join(\"\");\n return initials;\n}\n\n/**\n * Slugify string\n */\nexport function slugify(text: string): string {\n return text\n .toLowerCase()\n .normalize(\"NFD\")\n .replace(/[\\u0300-\\u036f]/g, \"\")\n .replace(/[^a-z0-9]+/g, \"-\")\n .replace(/(^-|-$)/g, \"\");\n}\n\n/**\n * Convert camelCase to Title Case\n */\nexport function camelCaseToTitleCase(camelCaseStr: string): string {\n return camelCaseStr\n .replace(/([A-Z])/g, \" $1\")\n .replace(/^./, (char) => char.toUpperCase());\n}\n\n/**\n * Convert Title Case to camelCase\n */\nexport function titleCaseToCamelCase(titleCaseStr: string): string {\n return titleCaseStr\n .toLowerCase()\n .replace(/[-_\\s]+(.)/g, (_, char) => char.toUpperCase())\n .replace(/[-_]/g, \"\");\n}\n\n// ============================================================================\n// Number Utilities\n// ============================================================================\n\nexport const isEven = (num: number) => num % 2 === 0;\nexport const isNonNegative = (num: number) => num >= 0;\n\n/**\n * Đọc số tiền thành chữ tiếng Việt\n * Dùng chung cho tất cả các phiếu in (hợp đồng, phiếu thu, phiếu chi, phiếu nhập/xuất kho, v.v.)\n * @param amount - Số tiền (VND)\n * @returns Chuỗi đọc bằng chữ, VD: \"Bảy mươi hai triệu một trăm năm mươi chín nghìn tám trăm hai mươi đồng\"\n */\nexport function readMoney(amount: number): string {\n if (!Number.isFinite(amount)) return \"Không đồng\"\n\n const isNegative = amount < 0\n\n // Bỏ phần thập phân - VND không có đơn vị lẻ\n amount = Math.floor(Math.abs(amount))\n\n if (amount === 0) return \"Không đồng\"\n\n const unit = [\"\", \"nghìn\", \"triệu\", \"tỷ\", \"nghìn tỷ\", \"triệu tỷ\"]\n const digit = [\n \"không\",\n \"một\",\n \"hai\",\n \"ba\",\n \"bốn\",\n \"năm\",\n \"sáu\",\n \"bảy\",\n \"tám\",\n \"chín\",\n ]\n\n let str = amount.toString()\n const groups: string[] = []\n while (str.length > 0) {\n groups.push(str.slice(-3))\n str = str.slice(0, -3)\n }\n\n let result = \"\"\n for (let i = 0; i < groups.length; i++) {\n const group = groups[i]\n if (group === \"000\") continue\n\n const [a, b, c] = group.padStart(3, \"0\").split(\"\").map(Number)\n let groupResult = \"\"\n\n const hasHundreds = a !== 0 || groups.length > 1\n\n if (hasHundreds) {\n groupResult += `${digit[a]} trăm `\n }\n\n if (b === 0 && c !== 0) {\n if (hasHundreds) groupResult += \"lẻ \"\n } else if (b === 1) {\n groupResult += \"mười \"\n } else if (b > 1) {\n groupResult += `${digit[b]} mươi `\n }\n\n if (c === 1 && b > 1) {\n groupResult += \"mốt \"\n } else if (c === 5 && b > 0) {\n groupResult += \"lăm \"\n } else if (c !== 0) {\n groupResult += `${digit[c]} `\n }\n\n if (i === groups.length - 1 && a === 0) {\n groupResult = groupResult.replace(\"không trăm \", \"\")\n if (b === 0) groupResult = groupResult.replace(\"lẻ \", \"\")\n }\n\n if (groupResult.trim() !== \"\") {\n result = `${groupResult.trim()} ${unit[i]} ${result}`\n }\n }\n\n result = result.trim() + \" đồng\"\n\n if (isNegative) {\n return \"Âm \" + result\n }\n\n return result.charAt(0).toUpperCase() + result.slice(1)\n}\n\n/**\n * Format currency with locale\n */\nexport function formatCurrency(\n value: number,\n locales: LocaleType = \"vi\",\n currency: string = \"VND\",\n): string {\n return new Intl.NumberFormat(locales === \"vi\" ? \"vi-VN\" : locales, {\n style: \"decimal\",\n maximumFractionDigits: 0,\n }).format(value);\n}\n\n/**\n * Format number with locale\n */\nexport function formatNumber(\n value: number,\n options?: {\n locale?: string;\n minimumFractionDigits?: number;\n maximumFractionDigits?: number;\n },\n): string {\n const {\n locale = \"vi-VN\",\n minimumFractionDigits = 0,\n maximumFractionDigits = 2,\n } = options || {};\n\n return new Intl.NumberFormat(locale, {\n minimumFractionDigits,\n maximumFractionDigits,\n }).format(value);\n}\n\n/**\n * Format percent\n */\nexport function formatPercent(\n value: number,\n locales: LocaleType = \"vi\",\n): string {\n return new Intl.NumberFormat(locales === \"vi\" ? \"vi-VN\" : locales, {\n style: \"percent\",\n maximumFractionDigits: 0,\n }).format(value);\n}\n\n/**\n * Format number to compact (e.g., 1K, 1M)\n */\nexport function formatNumberToCompact(\n value: number,\n locales: LocaleType = \"vi\",\n): string {\n return new Intl.NumberFormat(locales === \"vi\" ? \"vi-VN\" : locales, {\n notation: \"compact\",\n compactDisplay: \"short\",\n }).format(value);\n}\n\n/**\n * Format file size\n */\nexport function formatFileSize(bytes: number, decimals: number = 2): string {\n if (bytes === 0) return \"0 Bytes\";\n\n const k = 1000;\n const dm = decimals < 0 ? 0 : decimals;\n const sizes = [\"Bytes\", \"KB\", \"MB\", \"GB\", \"TB\", \"PB\"];\n\n const i = Math.floor(Math.log(bytes) / Math.log(k));\n\n return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + \" \" + sizes[i];\n}\n\n/**\n * Format unread count\n */\nexport function formatUnreadCount(unreadCount: number): string | number {\n return unreadCount >= 100 ? \"+99\" : unreadCount;\n}\n\n// ============================================================================\n// Date Utilities\n// ============================================================================\n\n/**\n * Format date with Vietnamese locale (Asia/Ho_Chi_Minh timezone)\n */\nexport function formatDate(\n date: Date | string,\n options?: {\n locale?: string;\n format?: \"short\" | \"medium\" | \"long\" | \"full\";\n },\n): string {\n const { locale = \"vi-VN\", format = \"medium\" } = options || {};\n const dateObj = typeof date === \"string\" ? new Date(date) : date;\n\n const formatOptions: Record<string, Intl.DateTimeFormatOptions> = {\n short: { day: \"2-digit\", month: \"2-digit\", year: \"numeric\", timeZone: \"Asia/Ho_Chi_Minh\" },\n medium: { day: \"2-digit\", month: \"short\", year: \"numeric\", timeZone: \"Asia/Ho_Chi_Minh\" },\n long: { day: \"numeric\", month: \"long\", year: \"numeric\", timeZone: \"Asia/Ho_Chi_Minh\" },\n full: { weekday: \"long\", day: \"numeric\", month: \"long\", year: \"numeric\", timeZone: \"Asia/Ho_Chi_Minh\" },\n };\n\n return new Intl.DateTimeFormat(locale, formatOptions[format]).format(dateObj);\n}\n\n/**\n * Format datetime with Vietnamese locale (Asia/Ho_Chi_Minh timezone)\n */\nexport function formatDateTime(\n date: Date | string,\n options?: {\n locale?: string;\n },\n): string {\n const { locale = \"vi-VN\" } = options || {};\n const dateObj = typeof date === \"string\" ? new Date(date) : date;\n\n return new Intl.DateTimeFormat(locale, {\n day: \"2-digit\",\n month: \"2-digit\",\n year: \"numeric\",\n hour: \"2-digit\",\n minute: \"2-digit\",\n timeZone: \"Asia/Ho_Chi_Minh\",\n }).format(dateObj);\n}\n\n/**\n * Format relative date (Today, Yesterday, or date) - Asia/Ho_Chi_Minh timezone\n */\nexport function formatRelativeDate(value?: string | number | Date): string {\n if (!value) return \"No Date\";\n\n const date = new Date(value);\n const today = new Date();\n const yesterday = new Date();\n yesterday.setDate(today.getDate() - 1);\n\n // Compare dates in Vietnam timezone\n const vnDateStr = new Intl.DateTimeFormat(\"en-CA\", { timeZone: \"Asia/Ho_Chi_Minh\", year: \"numeric\", month: \"2-digit\", day: \"2-digit\" }).format(date);\n const vnTodayStr = new Intl.DateTimeFormat(\"en-CA\", { timeZone: \"Asia/Ho_Chi_Minh\", year: \"numeric\", month: \"2-digit\", day: \"2-digit\" }).format(today);\n const vnYesterdayStr = new Intl.DateTimeFormat(\"en-CA\", { timeZone: \"Asia/Ho_Chi_Minh\", year: \"numeric\", month: \"2-digit\", day: \"2-digit\" }).format(yesterday);\n\n if (vnDateStr === vnTodayStr) return \"Today\";\n if (vnDateStr === vnYesterdayStr) return \"Yesterday\";\n\n return formatDate(date);\n}\n\n/**\n * Check if date is before today (Asia/Ho_Chi_Minh timezone)\n */\nexport function isBeforeToday(date: Date): boolean {\n const vnTodayStr = new Intl.DateTimeFormat(\"en-CA\", { timeZone: \"Asia/Ho_Chi_Minh\", year: \"numeric\", month: \"2-digit\", day: \"2-digit\" }).format(new Date());\n const vnDateStr = new Intl.DateTimeFormat(\"en-CA\", { timeZone: \"Asia/Ho_Chi_Minh\", year: \"numeric\", month: \"2-digit\", day: \"2-digit\" }).format(date);\n return vnDateStr < vnTodayStr;\n}\n\n// ============================================================================\n// Path Utilities\n// ============================================================================\n\n/**\n * Ensure path has prefix\n */\nexport function ensureWithPrefix(value: string, prefix: string): string {\n return value.startsWith(prefix) ? value : `${prefix}${value}`;\n}\n\n/**\n * Ensure path has suffix\n */\nexport function ensureWithSuffix(value: string, suffix: string): string {\n return value.endsWith(suffix) ? value : `${value}${suffix}`;\n}\n\n/**\n * Ensure path without prefix\n */\nexport function ensureWithoutPrefix(value: string, prefix: string): string {\n return value.startsWith(prefix) ? value.slice(prefix.length) : value;\n}\n\n/**\n * Ensure path without suffix\n */\nexport function ensureWithoutSuffix(value: string, suffix: string): string {\n return value.endsWith(suffix) ? value.slice(0, -suffix.length) : value;\n}\n\n/**\n * Check if pathname is active\n */\nexport function isActivePathname(\n basePathname: string,\n currentPathname: string,\n exactMatch: boolean = false,\n): boolean {\n if (typeof basePathname !== \"string\" || typeof currentPathname !== \"string\") {\n throw new Error(\"Both basePathname and currentPathname must be strings\");\n }\n\n if (exactMatch) {\n return basePathname === currentPathname;\n }\n\n return (\n currentPathname.startsWith(basePathname) &&\n (currentPathname.length === basePathname.length ||\n currentPathname[basePathname.length] === \"/\")\n );\n}\n\n// ============================================================================\n// General Utilities\n// ============================================================================\n\n/**\n * Wait/sleep function\n */\nexport function wait(ms: number = 250): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n\n/**\n * Debounce function\n */\nexport function debounce<T extends (...args: unknown[]) => unknown>(\n func: T,\n wait: number,\n): (...args: Parameters<T>) => void {\n let timeout: ReturnType<typeof setTimeout> | null = null;\n\n return (...args: Parameters<T>) => {\n if (timeout) {\n clearTimeout(timeout);\n }\n timeout = setTimeout(() => func(...args), wait);\n };\n}\n\n/**\n * Deep clone object\n */\nexport function deepClone<T>(obj: T): T {\n return JSON.parse(JSON.stringify(obj));\n}\n\n/**\n * Check if value is empty\n */\nexport function isEmpty(value: unknown): boolean {\n if (value === null || value === undefined) return true;\n if (typeof value === \"string\") return value.trim() === \"\";\n if (Array.isArray(value)) return value.length === 0;\n if (typeof value === \"object\") return Object.keys(value).length === 0;\n return false;\n}\n\n/**\n * Generate a random ID\n */\nexport function generateId(prefix?: string): string {\n const id = Math.random().toString(36).substring(2, 11);\n return prefix ? `${prefix}_${id}` : id;\n}\n\n/**\n * Get dictionary value safely\n */\nexport function getDictionaryValue(\n key: string,\n section: Record<string, unknown>,\n fallback?: string,\n): string {\n const value = section[key];\n\n if (typeof value !== \"string\") {\n if (fallback !== undefined) {\n return fallback;\n }\n\n const normalizedKey = key.replace(/[-_]/g, \"\");\n const normalizedValue = section[normalizedKey];\n\n if (typeof normalizedValue === \"string\") {\n return normalizedValue;\n }\n\n return key;\n }\n\n return value;\n}\n\n/**\n * Format overview card value based on style\n */\nexport function formatOverviewCardValue(\n value: number,\n formatStyle: FormatStyleType,\n): string | number {\n switch (formatStyle) {\n case \"percent\":\n return formatPercent(value);\n case \"currency\":\n return formatCurrency(value);\n default:\n return value.toLocaleString(\"vi-VN\", {\n maximumFractionDigits: 0,\n });\n }\n}\n\n// ============================================================================\n// Additional Utilities (migrated from shared-utils)\n// ============================================================================\n\n/**\n * Get credit card brand name from number\n */\nexport function getCreditCardBrandName(number: string): string {\n const re = {\n visa: /^4/,\n mastercard: /^5[1-5]/,\n amex: /^3[47]/,\n discover: /^6(?:011|5)/,\n };\n\n for (const [type, regex] of Object.entries(re)) {\n if (regex.test(number)) return type;\n }\n return \"unknown\";\n}\n\n/**\n * Convert rem to pixels\n */\nexport function remToPx(rem: number): number {\n if (typeof document === \"undefined\") return rem * 16;\n const rootFontSize = parseFloat(\n getComputedStyle(document.documentElement).fontSize,\n );\n return rem * rootFontSize;\n}\n\n/**\n * Check if string is a valid URL\n */\nexport function isUrl(text: string): boolean {\n try {\n new URL(text);\n return true;\n } catch {\n return false;\n }\n}\n\n/**\n * Rating to percentage string\n */\nexport function ratingToPercentage(\n rating: number,\n maxRating: number,\n fractionDigits: number = 0,\n): string {\n const value = ((rating / maxRating) * 100).toFixed(fractionDigits);\n return value + \"%\";\n}\n\n/**\n * Ensure redirect pathname with query params\n */\nexport function ensureRedirectPathname(\n basePathname: string,\n redirectPathname: string,\n): string {\n const searchParams = new URLSearchParams({\n redirectTo: ensureWithoutSuffix(redirectPathname, \"/\"),\n });\n\n return ensureWithSuffix(basePathname, \"?\" + searchParams.toString());\n}\n\n/**\n * Get discounted price\n */\nexport function getDiscountedPrice(\n price: number,\n discountRate: number,\n isAnnual: boolean = false,\n): number {\n if (isAnnual) {\n const annualPrice = price * 12;\n const discountedAnnualPrice = annualPrice * (1 - discountRate);\n return discountedAnnualPrice / 12;\n } else {\n return price * (1 - discountRate);\n }\n}\n\n/**\n * Convert time string to Date\n */\nexport function timeToDate(timeString: string, baseDate = new Date()): Date {\n if (!/^\\d{2}:\\d{2}$/.test(timeString)) {\n throw new Error(\"Invalid time format. Use 'HH:mm'.\");\n }\n\n const [hours, minutes] = timeString.split(\":\").map(Number);\n const date = new Date(baseDate);\n\n date.setHours(hours, minutes, 0, 0);\n\n return date;\n}\n\n/**\n * Format file type\n */\nexport function formatFileType(type: string): string {\n return type.slice(0, type.lastIndexOf(\"/\"));\n}\n\n/**\n * Format date with time (Asia/Ho_Chi_Minh timezone)\n */\nexport function formatDateWithTime(value: string | number | Date): string {\n const date = new Date(value);\n return new Intl.DateTimeFormat(\"vi-VN\", {\n day: \"2-digit\",\n month: \"2-digit\",\n year: \"numeric\",\n hour: \"2-digit\",\n minute: \"2-digit\",\n timeZone: \"Asia/Ho_Chi_Minh\",\n }).format(date);\n}\n\n/**\n * Format date short (MMM dd) - Asia/Ho_Chi_Minh timezone\n */\nexport function formatDateShort(value: string | number | Date): string {\n const date = new Date(value);\n return new Intl.DateTimeFormat(\"en-US\", {\n month: \"short\",\n day: \"2-digit\",\n timeZone: \"Asia/Ho_Chi_Minh\",\n }).format(date);\n}\n\n/**\n * Format time (Asia/Ho_Chi_Minh timezone)\n */\nexport function formatTime(value: string | number | Date): string {\n const date = new Date(value);\n return new Intl.DateTimeFormat(\"en-US\", {\n hour: \"numeric\",\n minute: \"2-digit\",\n hour12: true,\n timeZone: \"Asia/Ho_Chi_Minh\",\n }).format(date);\n}\n\n/**\n * Format duration from milliseconds\n */\nexport function formatDuration(value: string | number | Date): string {\n const numberValue = Number(value);\n const isNegative = numberValue < 0;\n const absoluteValue = Math.abs(numberValue);\n\n const hours = Math.floor(absoluteValue / 3600000);\n const minutes = Math.floor((absoluteValue % 3600000) / 60000);\n const seconds = Math.floor((absoluteValue % 60000) / 1000);\n\n const parts = [];\n if (hours) parts.push(`${hours}h`);\n if (minutes) parts.push(`${minutes}m`);\n if (seconds) parts.push(`${seconds}s`);\n\n const formattedDuration = parts.join(\" \") || \"0s\";\n\n return isNegative ? `-${formattedDuration}` : formattedDuration;\n}\n\n/**\n * Format distance to now\n */\nexport function formatDistance(value: string | number | Date): string {\n const date = new Date(value);\n const now = new Date();\n const diffMs = now.getTime() - date.getTime();\n const diffMins = Math.floor(diffMs / 60000);\n const diffHours = Math.floor(diffMs / 3600000);\n const diffDays = Math.floor(diffMs / 86400000);\n\n if (diffMins < 1) return \"just now\";\n if (diffMins < 60) return `${diffMins} mins ago`;\n if (diffHours < 24) return `${diffHours} hrs ago`;\n if (diffDays < 30) return `${diffDays} days ago`;\n if (diffDays < 365) return `${Math.floor(diffDays / 30)} months ago`;\n return `${Math.floor(diffDays / 365)} years ago`;\n}\n\n// Re-export status constants from configs\nexport {\n STATUS_COLORS,\n STATUS_ACTIVE,\n STATUS_INACTIVE,\n STATUS_OPTIONS,\n STATUS_VALUES,\n booleanToStatus,\n statusToBoolean,\n} from \"../configs/status\";\n\n// ============================================================================\n// Localization Utilities\n// ============================================================================\n\nconst LOCALE_LIST = [\"vi\", \"en\"];\n\nexport function isPathnameMissingLocale(pathname: string) {\n return !LOCALE_LIST.some((locale) => pathname.startsWith(`/${locale}`));\n}\n\nexport function getLocaleFromPathname(pathname: string) {\n return LOCALE_LIST.find((locale) => pathname.startsWith(`/${locale}`));\n}\n\nexport function ensureLocalizedPathname(pathname: string, locale: string) {\n if (!pathname || !locale)\n throw new Error(\"Pathname or Locale cannot be empty\");\n return isPathnameMissingLocale(pathname)\n ? `${ensureWithPrefix(locale, \"/\")}${ensureWithPrefix(pathname, \"/\")}`\n : pathname;\n}\n\nexport function relocalizePathname(pathname: string, locale: string) {\n if (!pathname || !locale)\n throw new Error(\"Pathname or Locale cannot be empty\");\n const segments = pathname.split(\"/\");\n segments[1] = locale;\n return segments.join(\"/\");\n}\n\n// ============================================================================\n// Logger\n// ============================================================================\n\ntype LogLevel = \"info\" | \"warn\" | \"error\" | \"debug\";\n\ninterface LogEntry {\n timestamp: string;\n level: LogLevel;\n message: string;\n context?: Record<string, unknown>;\n error?: Error | unknown;\n}\n\nclass Logger {\n private log(\n level: LogLevel,\n message: string,\n context?: Record<string, unknown>,\n error?: unknown,\n ) {\n const entry: LogEntry = {\n timestamp: new Date().toISOString(),\n level,\n message,\n context,\n };\n\n if (error instanceof Error) {\n entry.error = {\n name: error.name,\n message: error.message,\n stack: error.stack,\n };\n } else if (error) {\n entry.error = error;\n }\n\n const logString = JSON.stringify(entry);\n switch (level) {\n case \"error\":\n console.error(logString);\n break;\n case \"warn\":\n console.warn(logString);\n break;\n case \"debug\":\n if (process.env.NODE_ENV === \"development\") console.debug(logString);\n break;\n default:\n console.log(logString);\n }\n }\n\n info(message: string, context?: Record<string, unknown>) {\n this.log(\"info\", message, context);\n }\n warn(message: string, context?: Record<string, unknown>) {\n this.log(\"warn\", message, context);\n }\n error(message: string, error?: unknown, context?: Record<string, unknown>) {\n this.log(\"error\", message, context, error);\n }\n debug(message: string, context?: Record<string, unknown>) {\n this.log(\"debug\", message, context);\n }\n}\n\nexport const logger = new Logger();\n\n// ============================================================================\n// Tab Navigation Utilities\n// ============================================================================\n\nimport type {\n NavigationType,\n NavigationNestedItemWithHrefType,\n NavigationNestedItemWithItemsType,\n DynamicIconNameType,\n} from \"../types\";\n\nexport function shouldExcludeFromTabs(pathname: string): boolean {\n const excludePatterns = [\n \"/sign-in\",\n \"/sign-out\",\n \"/forgot-password\",\n \"/new-password\",\n \"/verify-email\",\n \"/register\",\n \"/unauthorized\",\n \"/not-found\",\n \"/maintenance\",\n \"/coming-soon\",\n ];\n if (\n pathname.includes(\"/ui/\") ||\n pathname.includes(\"/colors\") ||\n pathname.includes(\"/typography\")\n )\n return true;\n return excludePatterns.some((pattern) => pathname.includes(pattern));\n}\n\nexport function normalizePathname(pathname: string): string {\n return pathname.replace(/^\\/[a-z]{2}(\\/|$)/, \"/\") || \"/\";\n}\n\nfunction formatSegmentLabel(segment: string): string {\n return segment\n .replace(/[-_]+/g, \" \")\n .replace(/\\b\\w/g, (char) => char.toUpperCase());\n}\n\nexport function findRouteTitle(\n pathname: string,\n navigations: NavigationType[],\n): string | null {\n const result = findRouteInfo(pathname, navigations);\n return result?.title || null;\n}\n\nexport function findRouteIcon(\n pathname: string,\n navigations: NavigationType[],\n): DynamicIconNameType | null {\n const result = findRouteInfo(pathname, navigations);\n return (result?.iconName as DynamicIconNameType) || null;\n}\n\ninterface RouteInfo {\n title: string;\n iconName?: DynamicIconNameType;\n}\n\nfunction findRouteInfo(\n pathname: string,\n navigations: NavigationType[],\n): RouteInfo | null {\n const normalizedPath = normalizePathname(pathname);\n let exactMatch: RouteInfo | null = null;\n const prefixMatches: Array<{ itemPath: string; routeInfo: RouteInfo }> = [];\n\n function searchItems(\n items:\n | NavigationType[\"items\"]\n | Array<\n NavigationNestedItemWithHrefType | NavigationNestedItemWithItemsType\n >\n | undefined,\n ): void {\n if (!items) return;\n for (const item of items) {\n if (\"href\" in item && item.href) {\n const itemPath = normalizePathname(item.href);\n if (normalizedPath === itemPath) {\n exactMatch = {\n title: item.title,\n iconName:\n \"iconName\" in item\n ? (item.iconName as DynamicIconNameType | undefined)\n : undefined,\n };\n } else if (normalizedPath.startsWith(itemPath + \"/\")) {\n prefixMatches.push({\n itemPath,\n routeInfo: {\n title: item.title,\n iconName:\n \"iconName\" in item\n ? (item.iconName as DynamicIconNameType | undefined)\n : undefined,\n },\n });\n }\n }\n if (\"items\" in item && item.items) searchItems(item.items);\n }\n }\n\n for (const nav of navigations) {\n searchItems(nav.items);\n if (exactMatch) return exactMatch;\n }\n\n if (prefixMatches.length > 0) {\n prefixMatches.sort((a, b) => b.itemPath.length - a.itemPath.length);\n return prefixMatches[0].routeInfo;\n }\n\n const segments = normalizedPath.split(\"/\").filter(Boolean);\n return segments.length > 0\n ? { title: formatSegmentLabel(segments[segments.length - 1]) }\n : { title: \"Home\" };\n}\n","// @goerp/core/layout\n// Layout components for GoERP applications\n//\n// NOTE: This module contains basic layout shells.\n// Full layout components (sidebar, header, navigation) should be migrated\n// from packages/shared/layout as needed.\n\n\"use client\";\n\nimport * as React from \"react\";\nimport { cn } from \"../utils\";\n\n// ============================================================================\n// AppShell - Main application container\n// ============================================================================\n\nexport interface AppShellProps {\n children: React.ReactNode;\n sidebar?: React.ReactNode;\n header?: React.ReactNode;\n footer?: React.ReactNode;\n className?: string;\n}\n\nexport function AppShell({\n children,\n sidebar,\n header,\n footer,\n className,\n}: AppShellProps) {\n return (\n <div className={cn(\"flex min-h-screen\", className)}>\n {sidebar}\n <div className=\"flex flex-1 flex-col\">\n {header}\n <main className=\"flex-1\">{children}</main>\n {footer}\n </div>\n </div>\n );\n}\n\n// ============================================================================\n// Header\n// ============================================================================\n\nexport interface HeaderProps {\n children?: React.ReactNode;\n className?: string;\n sticky?: boolean;\n}\n\nexport function Header({ children, className, sticky = true }: HeaderProps) {\n return (\n <header\n className={cn(\n \"border-b bg-background\",\n sticky && \"sticky top-0 z-50\",\n className,\n )}\n >\n {children}\n </header>\n );\n}\n\n// ============================================================================\n// Sidebar\n// ============================================================================\n\nexport interface SidebarProps {\n children?: React.ReactNode;\n className?: string;\n collapsed?: boolean;\n width?: string | number;\n collapsedWidth?: string | number;\n}\n\nexport function Sidebar({\n children,\n className,\n collapsed = false,\n width = 280,\n collapsedWidth = 68,\n}: SidebarProps) {\n const sidebarWidth = collapsed ? collapsedWidth : width;\n\n return (\n <aside\n className={cn(\"border-r bg-background transition-all\", className)}\n style={{\n width:\n typeof sidebarWidth === \"number\" ? `${sidebarWidth}px` : sidebarWidth,\n }}\n >\n {children}\n </aside>\n );\n}\n\n// ============================================================================\n// Footer\n// ============================================================================\n\nexport interface FooterProps {\n children?: React.ReactNode;\n className?: string;\n}\n\nexport function Footer({ children, className }: FooterProps) {\n return (\n <footer className={cn(\"border-t bg-background\", className)}>\n {children}\n </footer>\n );\n}\n\n// ============================================================================\n// PageContainer\n// ============================================================================\n\nexport interface PageContainerProps {\n children: React.ReactNode;\n className?: string;\n title?: string;\n description?: string;\n}\n\nexport function PageContainer({\n children,\n className,\n title,\n description,\n}: PageContainerProps) {\n return (\n <div className={cn(\"w-full px-4 md:px-6 lg:px-8 py-6\", className)}>\n {(title || description) && (\n <div className=\"mb-6\">\n {title && <h1 className=\"text-2xl font-bold\">{title}</h1>}\n {description && (\n <p className=\"text-muted-foreground\">{description}</p>\n )}\n </div>\n )}\n {children}\n </div>\n );\n}\n\n// ============================================================================\n// Breadcrumb\n// ============================================================================\n\nexport interface BreadcrumbItem {\n label: string;\n href?: string;\n}\n\nexport interface BreadcrumbProps {\n items: BreadcrumbItem[];\n className?: string;\n}\n\nexport function Breadcrumb({ items, className }: BreadcrumbProps) {\n return (\n <nav className={cn(\"flex items-center space-x-2 text-sm\", className)}>\n {items.map((item, index) => (\n <React.Fragment key={index}>\n {index > 0 && <span className=\"text-muted-foreground\">/</span>}\n {item.href ? (\n <a\n href={item.href}\n className=\"text-muted-foreground hover:text-foreground\"\n >\n {item.label}\n </a>\n ) : (\n <span className=\"font-medium\">{item.label}</span>\n )}\n </React.Fragment>\n ))}\n </nav>\n );\n}\n"]}
@@ -1,90 +0,0 @@
1
- import * as React from 'react';
2
- import { clsx } from 'clsx';
3
- import { twMerge } from 'tailwind-merge';
4
- import { jsxs, jsx } from 'react/jsx-runtime';
5
-
6
- function cn(...inputs) {
7
- return twMerge(clsx(inputs));
8
- }
9
- function AppShell({
10
- children,
11
- sidebar,
12
- header,
13
- footer,
14
- className
15
- }) {
16
- return /* @__PURE__ */ jsxs("div", { className: cn("flex min-h-screen", className), children: [
17
- sidebar,
18
- /* @__PURE__ */ jsxs("div", { className: "flex flex-1 flex-col", children: [
19
- header,
20
- /* @__PURE__ */ jsx("main", { className: "flex-1", children }),
21
- footer
22
- ] })
23
- ] });
24
- }
25
- function Header({ children, className, sticky = true }) {
26
- return /* @__PURE__ */ jsx(
27
- "header",
28
- {
29
- className: cn(
30
- "border-b bg-background",
31
- sticky && "sticky top-0 z-50",
32
- className
33
- ),
34
- children
35
- }
36
- );
37
- }
38
- function Sidebar({
39
- children,
40
- className,
41
- collapsed = false,
42
- width = 280,
43
- collapsedWidth = 68
44
- }) {
45
- const sidebarWidth = collapsed ? collapsedWidth : width;
46
- return /* @__PURE__ */ jsx(
47
- "aside",
48
- {
49
- className: cn("border-r bg-background transition-all", className),
50
- style: {
51
- width: typeof sidebarWidth === "number" ? `${sidebarWidth}px` : sidebarWidth
52
- },
53
- children
54
- }
55
- );
56
- }
57
- function Footer({ children, className }) {
58
- return /* @__PURE__ */ jsx("footer", { className: cn("border-t bg-background", className), children });
59
- }
60
- function PageContainer({
61
- children,
62
- className,
63
- title,
64
- description
65
- }) {
66
- return /* @__PURE__ */ jsxs("div", { className: cn("w-full px-4 md:px-6 lg:px-8 py-6", className), children: [
67
- (title || description) && /* @__PURE__ */ jsxs("div", { className: "mb-6", children: [
68
- title && /* @__PURE__ */ jsx("h1", { className: "text-2xl font-bold", children: title }),
69
- description && /* @__PURE__ */ jsx("p", { className: "text-muted-foreground", children: description })
70
- ] }),
71
- children
72
- ] });
73
- }
74
- function Breadcrumb({ items, className }) {
75
- return /* @__PURE__ */ jsx("nav", { className: cn("flex items-center space-x-2 text-sm", className), children: items.map((item, index) => /* @__PURE__ */ jsxs(React.Fragment, { children: [
76
- index > 0 && /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: "/" }),
77
- item.href ? /* @__PURE__ */ jsx(
78
- "a",
79
- {
80
- href: item.href,
81
- className: "text-muted-foreground hover:text-foreground",
82
- children: item.label
83
- }
84
- ) : /* @__PURE__ */ jsx("span", { className: "font-medium", children: item.label })
85
- ] }, index)) });
86
- }
87
-
88
- export { AppShell, Breadcrumb, Footer, Header, PageContainer, Sidebar };
89
- //# sourceMappingURL=index.mjs.map
90
- //# sourceMappingURL=index.mjs.map
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../../src/utils/index.ts","../../src/layout/index.tsx"],"names":[],"mappings":"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","file":"index.mjs","sourcesContent":["// @goerp/core/utils\n// Utility functions for GoERP platform\n\nimport { clsx } from \"clsx\";\nimport type { ClassValue } from \"clsx\";\nimport { twMerge } from \"tailwind-merge\";\n\n// Import types from core/types\nimport type { LocaleType, FormatStyleType } from \"../types\";\n\n// ============================================================================\n// Class Names\n// ============================================================================\n\n/**\n * Merge Tailwind CSS classes with clsx\n */\nexport function cn(...inputs: ClassValue[]) {\n return twMerge(clsx(inputs));\n}\n\n// ============================================================================\n// String Utilities\n// ============================================================================\n\n/**\n * Get initials from full name\n */\nexport function getInitials(fullName: string): string {\n if (fullName.length === 0) return \"\";\n const names = fullName.split(\" \");\n const initials = names.map((name) => name.charAt(0).toUpperCase()).join(\"\");\n return initials;\n}\n\n/**\n * Slugify string\n */\nexport function slugify(text: string): string {\n return text\n .toLowerCase()\n .normalize(\"NFD\")\n .replace(/[\\u0300-\\u036f]/g, \"\")\n .replace(/[^a-z0-9]+/g, \"-\")\n .replace(/(^-|-$)/g, \"\");\n}\n\n/**\n * Convert camelCase to Title Case\n */\nexport function camelCaseToTitleCase(camelCaseStr: string): string {\n return camelCaseStr\n .replace(/([A-Z])/g, \" $1\")\n .replace(/^./, (char) => char.toUpperCase());\n}\n\n/**\n * Convert Title Case to camelCase\n */\nexport function titleCaseToCamelCase(titleCaseStr: string): string {\n return titleCaseStr\n .toLowerCase()\n .replace(/[-_\\s]+(.)/g, (_, char) => char.toUpperCase())\n .replace(/[-_]/g, \"\");\n}\n\n// ============================================================================\n// Number Utilities\n// ============================================================================\n\nexport const isEven = (num: number) => num % 2 === 0;\nexport const isNonNegative = (num: number) => num >= 0;\n\n/**\n * Đọc số tiền thành chữ tiếng Việt\n * Dùng chung cho tất cả các phiếu in (hợp đồng, phiếu thu, phiếu chi, phiếu nhập/xuất kho, v.v.)\n * @param amount - Số tiền (VND)\n * @returns Chuỗi đọc bằng chữ, VD: \"Bảy mươi hai triệu một trăm năm mươi chín nghìn tám trăm hai mươi đồng\"\n */\nexport function readMoney(amount: number): string {\n if (!Number.isFinite(amount)) return \"Không đồng\"\n\n const isNegative = amount < 0\n\n // Bỏ phần thập phân - VND không có đơn vị lẻ\n amount = Math.floor(Math.abs(amount))\n\n if (amount === 0) return \"Không đồng\"\n\n const unit = [\"\", \"nghìn\", \"triệu\", \"tỷ\", \"nghìn tỷ\", \"triệu tỷ\"]\n const digit = [\n \"không\",\n \"một\",\n \"hai\",\n \"ba\",\n \"bốn\",\n \"năm\",\n \"sáu\",\n \"bảy\",\n \"tám\",\n \"chín\",\n ]\n\n let str = amount.toString()\n const groups: string[] = []\n while (str.length > 0) {\n groups.push(str.slice(-3))\n str = str.slice(0, -3)\n }\n\n let result = \"\"\n for (let i = 0; i < groups.length; i++) {\n const group = groups[i]\n if (group === \"000\") continue\n\n const [a, b, c] = group.padStart(3, \"0\").split(\"\").map(Number)\n let groupResult = \"\"\n\n const hasHundreds = a !== 0 || groups.length > 1\n\n if (hasHundreds) {\n groupResult += `${digit[a]} trăm `\n }\n\n if (b === 0 && c !== 0) {\n if (hasHundreds) groupResult += \"lẻ \"\n } else if (b === 1) {\n groupResult += \"mười \"\n } else if (b > 1) {\n groupResult += `${digit[b]} mươi `\n }\n\n if (c === 1 && b > 1) {\n groupResult += \"mốt \"\n } else if (c === 5 && b > 0) {\n groupResult += \"lăm \"\n } else if (c !== 0) {\n groupResult += `${digit[c]} `\n }\n\n if (i === groups.length - 1 && a === 0) {\n groupResult = groupResult.replace(\"không trăm \", \"\")\n if (b === 0) groupResult = groupResult.replace(\"lẻ \", \"\")\n }\n\n if (groupResult.trim() !== \"\") {\n result = `${groupResult.trim()} ${unit[i]} ${result}`\n }\n }\n\n result = result.trim() + \" đồng\"\n\n if (isNegative) {\n return \"Âm \" + result\n }\n\n return result.charAt(0).toUpperCase() + result.slice(1)\n}\n\n/**\n * Format currency with locale\n */\nexport function formatCurrency(\n value: number,\n locales: LocaleType = \"vi\",\n currency: string = \"VND\",\n): string {\n return new Intl.NumberFormat(locales === \"vi\" ? \"vi-VN\" : locales, {\n style: \"decimal\",\n maximumFractionDigits: 0,\n }).format(value);\n}\n\n/**\n * Format number with locale\n */\nexport function formatNumber(\n value: number,\n options?: {\n locale?: string;\n minimumFractionDigits?: number;\n maximumFractionDigits?: number;\n },\n): string {\n const {\n locale = \"vi-VN\",\n minimumFractionDigits = 0,\n maximumFractionDigits = 2,\n } = options || {};\n\n return new Intl.NumberFormat(locale, {\n minimumFractionDigits,\n maximumFractionDigits,\n }).format(value);\n}\n\n/**\n * Format percent\n */\nexport function formatPercent(\n value: number,\n locales: LocaleType = \"vi\",\n): string {\n return new Intl.NumberFormat(locales === \"vi\" ? \"vi-VN\" : locales, {\n style: \"percent\",\n maximumFractionDigits: 0,\n }).format(value);\n}\n\n/**\n * Format number to compact (e.g., 1K, 1M)\n */\nexport function formatNumberToCompact(\n value: number,\n locales: LocaleType = \"vi\",\n): string {\n return new Intl.NumberFormat(locales === \"vi\" ? \"vi-VN\" : locales, {\n notation: \"compact\",\n compactDisplay: \"short\",\n }).format(value);\n}\n\n/**\n * Format file size\n */\nexport function formatFileSize(bytes: number, decimals: number = 2): string {\n if (bytes === 0) return \"0 Bytes\";\n\n const k = 1000;\n const dm = decimals < 0 ? 0 : decimals;\n const sizes = [\"Bytes\", \"KB\", \"MB\", \"GB\", \"TB\", \"PB\"];\n\n const i = Math.floor(Math.log(bytes) / Math.log(k));\n\n return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + \" \" + sizes[i];\n}\n\n/**\n * Format unread count\n */\nexport function formatUnreadCount(unreadCount: number): string | number {\n return unreadCount >= 100 ? \"+99\" : unreadCount;\n}\n\n// ============================================================================\n// Date Utilities\n// ============================================================================\n\n/**\n * Format date with Vietnamese locale (Asia/Ho_Chi_Minh timezone)\n */\nexport function formatDate(\n date: Date | string,\n options?: {\n locale?: string;\n format?: \"short\" | \"medium\" | \"long\" | \"full\";\n },\n): string {\n const { locale = \"vi-VN\", format = \"medium\" } = options || {};\n const dateObj = typeof date === \"string\" ? new Date(date) : date;\n\n const formatOptions: Record<string, Intl.DateTimeFormatOptions> = {\n short: { day: \"2-digit\", month: \"2-digit\", year: \"numeric\", timeZone: \"Asia/Ho_Chi_Minh\" },\n medium: { day: \"2-digit\", month: \"short\", year: \"numeric\", timeZone: \"Asia/Ho_Chi_Minh\" },\n long: { day: \"numeric\", month: \"long\", year: \"numeric\", timeZone: \"Asia/Ho_Chi_Minh\" },\n full: { weekday: \"long\", day: \"numeric\", month: \"long\", year: \"numeric\", timeZone: \"Asia/Ho_Chi_Minh\" },\n };\n\n return new Intl.DateTimeFormat(locale, formatOptions[format]).format(dateObj);\n}\n\n/**\n * Format datetime with Vietnamese locale (Asia/Ho_Chi_Minh timezone)\n */\nexport function formatDateTime(\n date: Date | string,\n options?: {\n locale?: string;\n },\n): string {\n const { locale = \"vi-VN\" } = options || {};\n const dateObj = typeof date === \"string\" ? new Date(date) : date;\n\n return new Intl.DateTimeFormat(locale, {\n day: \"2-digit\",\n month: \"2-digit\",\n year: \"numeric\",\n hour: \"2-digit\",\n minute: \"2-digit\",\n timeZone: \"Asia/Ho_Chi_Minh\",\n }).format(dateObj);\n}\n\n/**\n * Format relative date (Today, Yesterday, or date) - Asia/Ho_Chi_Minh timezone\n */\nexport function formatRelativeDate(value?: string | number | Date): string {\n if (!value) return \"No Date\";\n\n const date = new Date(value);\n const today = new Date();\n const yesterday = new Date();\n yesterday.setDate(today.getDate() - 1);\n\n // Compare dates in Vietnam timezone\n const vnDateStr = new Intl.DateTimeFormat(\"en-CA\", { timeZone: \"Asia/Ho_Chi_Minh\", year: \"numeric\", month: \"2-digit\", day: \"2-digit\" }).format(date);\n const vnTodayStr = new Intl.DateTimeFormat(\"en-CA\", { timeZone: \"Asia/Ho_Chi_Minh\", year: \"numeric\", month: \"2-digit\", day: \"2-digit\" }).format(today);\n const vnYesterdayStr = new Intl.DateTimeFormat(\"en-CA\", { timeZone: \"Asia/Ho_Chi_Minh\", year: \"numeric\", month: \"2-digit\", day: \"2-digit\" }).format(yesterday);\n\n if (vnDateStr === vnTodayStr) return \"Today\";\n if (vnDateStr === vnYesterdayStr) return \"Yesterday\";\n\n return formatDate(date);\n}\n\n/**\n * Check if date is before today (Asia/Ho_Chi_Minh timezone)\n */\nexport function isBeforeToday(date: Date): boolean {\n const vnTodayStr = new Intl.DateTimeFormat(\"en-CA\", { timeZone: \"Asia/Ho_Chi_Minh\", year: \"numeric\", month: \"2-digit\", day: \"2-digit\" }).format(new Date());\n const vnDateStr = new Intl.DateTimeFormat(\"en-CA\", { timeZone: \"Asia/Ho_Chi_Minh\", year: \"numeric\", month: \"2-digit\", day: \"2-digit\" }).format(date);\n return vnDateStr < vnTodayStr;\n}\n\n// ============================================================================\n// Path Utilities\n// ============================================================================\n\n/**\n * Ensure path has prefix\n */\nexport function ensureWithPrefix(value: string, prefix: string): string {\n return value.startsWith(prefix) ? value : `${prefix}${value}`;\n}\n\n/**\n * Ensure path has suffix\n */\nexport function ensureWithSuffix(value: string, suffix: string): string {\n return value.endsWith(suffix) ? value : `${value}${suffix}`;\n}\n\n/**\n * Ensure path without prefix\n */\nexport function ensureWithoutPrefix(value: string, prefix: string): string {\n return value.startsWith(prefix) ? value.slice(prefix.length) : value;\n}\n\n/**\n * Ensure path without suffix\n */\nexport function ensureWithoutSuffix(value: string, suffix: string): string {\n return value.endsWith(suffix) ? value.slice(0, -suffix.length) : value;\n}\n\n/**\n * Check if pathname is active\n */\nexport function isActivePathname(\n basePathname: string,\n currentPathname: string,\n exactMatch: boolean = false,\n): boolean {\n if (typeof basePathname !== \"string\" || typeof currentPathname !== \"string\") {\n throw new Error(\"Both basePathname and currentPathname must be strings\");\n }\n\n if (exactMatch) {\n return basePathname === currentPathname;\n }\n\n return (\n currentPathname.startsWith(basePathname) &&\n (currentPathname.length === basePathname.length ||\n currentPathname[basePathname.length] === \"/\")\n );\n}\n\n// ============================================================================\n// General Utilities\n// ============================================================================\n\n/**\n * Wait/sleep function\n */\nexport function wait(ms: number = 250): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n\n/**\n * Debounce function\n */\nexport function debounce<T extends (...args: unknown[]) => unknown>(\n func: T,\n wait: number,\n): (...args: Parameters<T>) => void {\n let timeout: ReturnType<typeof setTimeout> | null = null;\n\n return (...args: Parameters<T>) => {\n if (timeout) {\n clearTimeout(timeout);\n }\n timeout = setTimeout(() => func(...args), wait);\n };\n}\n\n/**\n * Deep clone object\n */\nexport function deepClone<T>(obj: T): T {\n return JSON.parse(JSON.stringify(obj));\n}\n\n/**\n * Check if value is empty\n */\nexport function isEmpty(value: unknown): boolean {\n if (value === null || value === undefined) return true;\n if (typeof value === \"string\") return value.trim() === \"\";\n if (Array.isArray(value)) return value.length === 0;\n if (typeof value === \"object\") return Object.keys(value).length === 0;\n return false;\n}\n\n/**\n * Generate a random ID\n */\nexport function generateId(prefix?: string): string {\n const id = Math.random().toString(36).substring(2, 11);\n return prefix ? `${prefix}_${id}` : id;\n}\n\n/**\n * Get dictionary value safely\n */\nexport function getDictionaryValue(\n key: string,\n section: Record<string, unknown>,\n fallback?: string,\n): string {\n const value = section[key];\n\n if (typeof value !== \"string\") {\n if (fallback !== undefined) {\n return fallback;\n }\n\n const normalizedKey = key.replace(/[-_]/g, \"\");\n const normalizedValue = section[normalizedKey];\n\n if (typeof normalizedValue === \"string\") {\n return normalizedValue;\n }\n\n return key;\n }\n\n return value;\n}\n\n/**\n * Format overview card value based on style\n */\nexport function formatOverviewCardValue(\n value: number,\n formatStyle: FormatStyleType,\n): string | number {\n switch (formatStyle) {\n case \"percent\":\n return formatPercent(value);\n case \"currency\":\n return formatCurrency(value);\n default:\n return value.toLocaleString(\"vi-VN\", {\n maximumFractionDigits: 0,\n });\n }\n}\n\n// ============================================================================\n// Additional Utilities (migrated from shared-utils)\n// ============================================================================\n\n/**\n * Get credit card brand name from number\n */\nexport function getCreditCardBrandName(number: string): string {\n const re = {\n visa: /^4/,\n mastercard: /^5[1-5]/,\n amex: /^3[47]/,\n discover: /^6(?:011|5)/,\n };\n\n for (const [type, regex] of Object.entries(re)) {\n if (regex.test(number)) return type;\n }\n return \"unknown\";\n}\n\n/**\n * Convert rem to pixels\n */\nexport function remToPx(rem: number): number {\n if (typeof document === \"undefined\") return rem * 16;\n const rootFontSize = parseFloat(\n getComputedStyle(document.documentElement).fontSize,\n );\n return rem * rootFontSize;\n}\n\n/**\n * Check if string is a valid URL\n */\nexport function isUrl(text: string): boolean {\n try {\n new URL(text);\n return true;\n } catch {\n return false;\n }\n}\n\n/**\n * Rating to percentage string\n */\nexport function ratingToPercentage(\n rating: number,\n maxRating: number,\n fractionDigits: number = 0,\n): string {\n const value = ((rating / maxRating) * 100).toFixed(fractionDigits);\n return value + \"%\";\n}\n\n/**\n * Ensure redirect pathname with query params\n */\nexport function ensureRedirectPathname(\n basePathname: string,\n redirectPathname: string,\n): string {\n const searchParams = new URLSearchParams({\n redirectTo: ensureWithoutSuffix(redirectPathname, \"/\"),\n });\n\n return ensureWithSuffix(basePathname, \"?\" + searchParams.toString());\n}\n\n/**\n * Get discounted price\n */\nexport function getDiscountedPrice(\n price: number,\n discountRate: number,\n isAnnual: boolean = false,\n): number {\n if (isAnnual) {\n const annualPrice = price * 12;\n const discountedAnnualPrice = annualPrice * (1 - discountRate);\n return discountedAnnualPrice / 12;\n } else {\n return price * (1 - discountRate);\n }\n}\n\n/**\n * Convert time string to Date\n */\nexport function timeToDate(timeString: string, baseDate = new Date()): Date {\n if (!/^\\d{2}:\\d{2}$/.test(timeString)) {\n throw new Error(\"Invalid time format. Use 'HH:mm'.\");\n }\n\n const [hours, minutes] = timeString.split(\":\").map(Number);\n const date = new Date(baseDate);\n\n date.setHours(hours, minutes, 0, 0);\n\n return date;\n}\n\n/**\n * Format file type\n */\nexport function formatFileType(type: string): string {\n return type.slice(0, type.lastIndexOf(\"/\"));\n}\n\n/**\n * Format date with time (Asia/Ho_Chi_Minh timezone)\n */\nexport function formatDateWithTime(value: string | number | Date): string {\n const date = new Date(value);\n return new Intl.DateTimeFormat(\"vi-VN\", {\n day: \"2-digit\",\n month: \"2-digit\",\n year: \"numeric\",\n hour: \"2-digit\",\n minute: \"2-digit\",\n timeZone: \"Asia/Ho_Chi_Minh\",\n }).format(date);\n}\n\n/**\n * Format date short (MMM dd) - Asia/Ho_Chi_Minh timezone\n */\nexport function formatDateShort(value: string | number | Date): string {\n const date = new Date(value);\n return new Intl.DateTimeFormat(\"en-US\", {\n month: \"short\",\n day: \"2-digit\",\n timeZone: \"Asia/Ho_Chi_Minh\",\n }).format(date);\n}\n\n/**\n * Format time (Asia/Ho_Chi_Minh timezone)\n */\nexport function formatTime(value: string | number | Date): string {\n const date = new Date(value);\n return new Intl.DateTimeFormat(\"en-US\", {\n hour: \"numeric\",\n minute: \"2-digit\",\n hour12: true,\n timeZone: \"Asia/Ho_Chi_Minh\",\n }).format(date);\n}\n\n/**\n * Format duration from milliseconds\n */\nexport function formatDuration(value: string | number | Date): string {\n const numberValue = Number(value);\n const isNegative = numberValue < 0;\n const absoluteValue = Math.abs(numberValue);\n\n const hours = Math.floor(absoluteValue / 3600000);\n const minutes = Math.floor((absoluteValue % 3600000) / 60000);\n const seconds = Math.floor((absoluteValue % 60000) / 1000);\n\n const parts = [];\n if (hours) parts.push(`${hours}h`);\n if (minutes) parts.push(`${minutes}m`);\n if (seconds) parts.push(`${seconds}s`);\n\n const formattedDuration = parts.join(\" \") || \"0s\";\n\n return isNegative ? `-${formattedDuration}` : formattedDuration;\n}\n\n/**\n * Format distance to now\n */\nexport function formatDistance(value: string | number | Date): string {\n const date = new Date(value);\n const now = new Date();\n const diffMs = now.getTime() - date.getTime();\n const diffMins = Math.floor(diffMs / 60000);\n const diffHours = Math.floor(diffMs / 3600000);\n const diffDays = Math.floor(diffMs / 86400000);\n\n if (diffMins < 1) return \"just now\";\n if (diffMins < 60) return `${diffMins} mins ago`;\n if (diffHours < 24) return `${diffHours} hrs ago`;\n if (diffDays < 30) return `${diffDays} days ago`;\n if (diffDays < 365) return `${Math.floor(diffDays / 30)} months ago`;\n return `${Math.floor(diffDays / 365)} years ago`;\n}\n\n// Re-export status constants from configs\nexport {\n STATUS_COLORS,\n STATUS_ACTIVE,\n STATUS_INACTIVE,\n STATUS_OPTIONS,\n STATUS_VALUES,\n booleanToStatus,\n statusToBoolean,\n} from \"../configs/status\";\n\n// ============================================================================\n// Localization Utilities\n// ============================================================================\n\nconst LOCALE_LIST = [\"vi\", \"en\"];\n\nexport function isPathnameMissingLocale(pathname: string) {\n return !LOCALE_LIST.some((locale) => pathname.startsWith(`/${locale}`));\n}\n\nexport function getLocaleFromPathname(pathname: string) {\n return LOCALE_LIST.find((locale) => pathname.startsWith(`/${locale}`));\n}\n\nexport function ensureLocalizedPathname(pathname: string, locale: string) {\n if (!pathname || !locale)\n throw new Error(\"Pathname or Locale cannot be empty\");\n return isPathnameMissingLocale(pathname)\n ? `${ensureWithPrefix(locale, \"/\")}${ensureWithPrefix(pathname, \"/\")}`\n : pathname;\n}\n\nexport function relocalizePathname(pathname: string, locale: string) {\n if (!pathname || !locale)\n throw new Error(\"Pathname or Locale cannot be empty\");\n const segments = pathname.split(\"/\");\n segments[1] = locale;\n return segments.join(\"/\");\n}\n\n// ============================================================================\n// Logger\n// ============================================================================\n\ntype LogLevel = \"info\" | \"warn\" | \"error\" | \"debug\";\n\ninterface LogEntry {\n timestamp: string;\n level: LogLevel;\n message: string;\n context?: Record<string, unknown>;\n error?: Error | unknown;\n}\n\nclass Logger {\n private log(\n level: LogLevel,\n message: string,\n context?: Record<string, unknown>,\n error?: unknown,\n ) {\n const entry: LogEntry = {\n timestamp: new Date().toISOString(),\n level,\n message,\n context,\n };\n\n if (error instanceof Error) {\n entry.error = {\n name: error.name,\n message: error.message,\n stack: error.stack,\n };\n } else if (error) {\n entry.error = error;\n }\n\n const logString = JSON.stringify(entry);\n switch (level) {\n case \"error\":\n console.error(logString);\n break;\n case \"warn\":\n console.warn(logString);\n break;\n case \"debug\":\n if (process.env.NODE_ENV === \"development\") console.debug(logString);\n break;\n default:\n console.log(logString);\n }\n }\n\n info(message: string, context?: Record<string, unknown>) {\n this.log(\"info\", message, context);\n }\n warn(message: string, context?: Record<string, unknown>) {\n this.log(\"warn\", message, context);\n }\n error(message: string, error?: unknown, context?: Record<string, unknown>) {\n this.log(\"error\", message, context, error);\n }\n debug(message: string, context?: Record<string, unknown>) {\n this.log(\"debug\", message, context);\n }\n}\n\nexport const logger = new Logger();\n\n// ============================================================================\n// Tab Navigation Utilities\n// ============================================================================\n\nimport type {\n NavigationType,\n NavigationNestedItemWithHrefType,\n NavigationNestedItemWithItemsType,\n DynamicIconNameType,\n} from \"../types\";\n\nexport function shouldExcludeFromTabs(pathname: string): boolean {\n const excludePatterns = [\n \"/sign-in\",\n \"/sign-out\",\n \"/forgot-password\",\n \"/new-password\",\n \"/verify-email\",\n \"/register\",\n \"/unauthorized\",\n \"/not-found\",\n \"/maintenance\",\n \"/coming-soon\",\n ];\n if (\n pathname.includes(\"/ui/\") ||\n pathname.includes(\"/colors\") ||\n pathname.includes(\"/typography\")\n )\n return true;\n return excludePatterns.some((pattern) => pathname.includes(pattern));\n}\n\nexport function normalizePathname(pathname: string): string {\n return pathname.replace(/^\\/[a-z]{2}(\\/|$)/, \"/\") || \"/\";\n}\n\nfunction formatSegmentLabel(segment: string): string {\n return segment\n .replace(/[-_]+/g, \" \")\n .replace(/\\b\\w/g, (char) => char.toUpperCase());\n}\n\nexport function findRouteTitle(\n pathname: string,\n navigations: NavigationType[],\n): string | null {\n const result = findRouteInfo(pathname, navigations);\n return result?.title || null;\n}\n\nexport function findRouteIcon(\n pathname: string,\n navigations: NavigationType[],\n): DynamicIconNameType | null {\n const result = findRouteInfo(pathname, navigations);\n return (result?.iconName as DynamicIconNameType) || null;\n}\n\ninterface RouteInfo {\n title: string;\n iconName?: DynamicIconNameType;\n}\n\nfunction findRouteInfo(\n pathname: string,\n navigations: NavigationType[],\n): RouteInfo | null {\n const normalizedPath = normalizePathname(pathname);\n let exactMatch: RouteInfo | null = null;\n const prefixMatches: Array<{ itemPath: string; routeInfo: RouteInfo }> = [];\n\n function searchItems(\n items:\n | NavigationType[\"items\"]\n | Array<\n NavigationNestedItemWithHrefType | NavigationNestedItemWithItemsType\n >\n | undefined,\n ): void {\n if (!items) return;\n for (const item of items) {\n if (\"href\" in item && item.href) {\n const itemPath = normalizePathname(item.href);\n if (normalizedPath === itemPath) {\n exactMatch = {\n title: item.title,\n iconName:\n \"iconName\" in item\n ? (item.iconName as DynamicIconNameType | undefined)\n : undefined,\n };\n } else if (normalizedPath.startsWith(itemPath + \"/\")) {\n prefixMatches.push({\n itemPath,\n routeInfo: {\n title: item.title,\n iconName:\n \"iconName\" in item\n ? (item.iconName as DynamicIconNameType | undefined)\n : undefined,\n },\n });\n }\n }\n if (\"items\" in item && item.items) searchItems(item.items);\n }\n }\n\n for (const nav of navigations) {\n searchItems(nav.items);\n if (exactMatch) return exactMatch;\n }\n\n if (prefixMatches.length > 0) {\n prefixMatches.sort((a, b) => b.itemPath.length - a.itemPath.length);\n return prefixMatches[0].routeInfo;\n }\n\n const segments = normalizedPath.split(\"/\").filter(Boolean);\n return segments.length > 0\n ? { title: formatSegmentLabel(segments[segments.length - 1]) }\n : { title: \"Home\" };\n}\n","// @goerp/core/layout\n// Layout components for GoERP applications\n//\n// NOTE: This module contains basic layout shells.\n// Full layout components (sidebar, header, navigation) should be migrated\n// from packages/shared/layout as needed.\n\n\"use client\";\n\nimport * as React from \"react\";\nimport { cn } from \"../utils\";\n\n// ============================================================================\n// AppShell - Main application container\n// ============================================================================\n\nexport interface AppShellProps {\n children: React.ReactNode;\n sidebar?: React.ReactNode;\n header?: React.ReactNode;\n footer?: React.ReactNode;\n className?: string;\n}\n\nexport function AppShell({\n children,\n sidebar,\n header,\n footer,\n className,\n}: AppShellProps) {\n return (\n <div className={cn(\"flex min-h-screen\", className)}>\n {sidebar}\n <div className=\"flex flex-1 flex-col\">\n {header}\n <main className=\"flex-1\">{children}</main>\n {footer}\n </div>\n </div>\n );\n}\n\n// ============================================================================\n// Header\n// ============================================================================\n\nexport interface HeaderProps {\n children?: React.ReactNode;\n className?: string;\n sticky?: boolean;\n}\n\nexport function Header({ children, className, sticky = true }: HeaderProps) {\n return (\n <header\n className={cn(\n \"border-b bg-background\",\n sticky && \"sticky top-0 z-50\",\n className,\n )}\n >\n {children}\n </header>\n );\n}\n\n// ============================================================================\n// Sidebar\n// ============================================================================\n\nexport interface SidebarProps {\n children?: React.ReactNode;\n className?: string;\n collapsed?: boolean;\n width?: string | number;\n collapsedWidth?: string | number;\n}\n\nexport function Sidebar({\n children,\n className,\n collapsed = false,\n width = 280,\n collapsedWidth = 68,\n}: SidebarProps) {\n const sidebarWidth = collapsed ? collapsedWidth : width;\n\n return (\n <aside\n className={cn(\"border-r bg-background transition-all\", className)}\n style={{\n width:\n typeof sidebarWidth === \"number\" ? `${sidebarWidth}px` : sidebarWidth,\n }}\n >\n {children}\n </aside>\n );\n}\n\n// ============================================================================\n// Footer\n// ============================================================================\n\nexport interface FooterProps {\n children?: React.ReactNode;\n className?: string;\n}\n\nexport function Footer({ children, className }: FooterProps) {\n return (\n <footer className={cn(\"border-t bg-background\", className)}>\n {children}\n </footer>\n );\n}\n\n// ============================================================================\n// PageContainer\n// ============================================================================\n\nexport interface PageContainerProps {\n children: React.ReactNode;\n className?: string;\n title?: string;\n description?: string;\n}\n\nexport function PageContainer({\n children,\n className,\n title,\n description,\n}: PageContainerProps) {\n return (\n <div className={cn(\"w-full px-4 md:px-6 lg:px-8 py-6\", className)}>\n {(title || description) && (\n <div className=\"mb-6\">\n {title && <h1 className=\"text-2xl font-bold\">{title}</h1>}\n {description && (\n <p className=\"text-muted-foreground\">{description}</p>\n )}\n </div>\n )}\n {children}\n </div>\n );\n}\n\n// ============================================================================\n// Breadcrumb\n// ============================================================================\n\nexport interface BreadcrumbItem {\n label: string;\n href?: string;\n}\n\nexport interface BreadcrumbProps {\n items: BreadcrumbItem[];\n className?: string;\n}\n\nexport function Breadcrumb({ items, className }: BreadcrumbProps) {\n return (\n <nav className={cn(\"flex items-center space-x-2 text-sm\", className)}>\n {items.map((item, index) => (\n <React.Fragment key={index}>\n {index > 0 && <span className=\"text-muted-foreground\">/</span>}\n {item.href ? (\n <a\n href={item.href}\n className=\"text-muted-foreground hover:text-foreground\"\n >\n {item.label}\n </a>\n ) : (\n <span className=\"font-medium\">{item.label}</span>\n )}\n </React.Fragment>\n ))}\n </nav>\n );\n}\n"]}
@@ -1,16 +0,0 @@
1
- import { NavigationRootItem, NavigationNestedItem, NavigationType } from '../types/index.mjs';
2
- import 'lucide-react';
3
- import 'react';
4
- import 'zod';
5
-
6
- /**
7
- * Filter navigation items based on permissions recursively
8
- */
9
- declare function filterNavigationItems(items: NavigationRootItem[], accessibleCodes: Set<string>): NavigationRootItem[];
10
- declare function filterNavigationItems(items: NavigationNestedItem[], accessibleCodes: Set<string>): NavigationNestedItem[];
11
- /**
12
- * Filter navigation tree based on permissions
13
- */
14
- declare function filterNavigationTree(navigation: NavigationType[], accessibleCodes: Set<string>): NavigationType[];
15
-
16
- export { filterNavigationItems, filterNavigationTree };
@@ -1,16 +0,0 @@
1
- import { NavigationRootItem, NavigationNestedItem, NavigationType } from '../types/index.js';
2
- import 'lucide-react';
3
- import 'react';
4
- import 'zod';
5
-
6
- /**
7
- * Filter navigation items based on permissions recursively
8
- */
9
- declare function filterNavigationItems(items: NavigationRootItem[], accessibleCodes: Set<string>): NavigationRootItem[];
10
- declare function filterNavigationItems(items: NavigationNestedItem[], accessibleCodes: Set<string>): NavigationNestedItem[];
11
- /**
12
- * Filter navigation tree based on permissions
13
- */
14
- declare function filterNavigationTree(navigation: NavigationType[], accessibleCodes: Set<string>): NavigationType[];
15
-
16
- export { filterNavigationItems, filterNavigationTree };
@@ -1,53 +0,0 @@
1
- 'use strict';
2
-
3
- // src/navigation/index.ts
4
- function filterNavigationItems(items, accessibleCodes) {
5
- return items.reduce(
6
- (acc, item) => {
7
- if (accessibleCodes.has("*")) {
8
- acc.push(item);
9
- return acc;
10
- }
11
- if (item.resource && !accessibleCodes.has(item.resource)) {
12
- return acc;
13
- }
14
- if ("items" in item && item.items) {
15
- const filteredChildren = filterNavigationItems(
16
- item.items,
17
- accessibleCodes
18
- );
19
- if (filteredChildren.length > 0) {
20
- acc.push({
21
- ...item,
22
- items: filteredChildren
23
- });
24
- }
25
- } else {
26
- acc.push(item);
27
- }
28
- return acc;
29
- },
30
- []
31
- );
32
- }
33
- function filterNavigationTree(navigation, accessibleCodes) {
34
- if (accessibleCodes.has("*")) {
35
- return navigation;
36
- }
37
- const filteredNavigation = [];
38
- for (const group of navigation) {
39
- const filteredItems = filterNavigationItems(group.items, accessibleCodes);
40
- if (filteredItems.length > 0) {
41
- filteredNavigation.push({
42
- ...group,
43
- items: filteredItems
44
- });
45
- }
46
- }
47
- return filteredNavigation;
48
- }
49
-
50
- exports.filterNavigationItems = filterNavigationItems;
51
- exports.filterNavigationTree = filterNavigationTree;
52
- //# sourceMappingURL=index.js.map
53
- //# sourceMappingURL=index.js.map
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../../src/navigation/index.ts"],"names":[],"mappings":";;;AAqBO,SAAS,qBAAA,CACd,OACA,eAAA,EAC+C;AAC/C,EAAA,OAAO,KAAA,CAAM,MAAA;AAAA,IACX,CAAC,KAAK,IAAA,KAAS;AAEb,MAAA,IAAI,eAAA,CAAgB,GAAA,CAAI,GAAG,CAAA,EAAG;AAC5B,QAAA,GAAA,CAAI,KAAK,IAAI,CAAA;AACb,QAAA,OAAO,GAAA;AAAA,MACT;AAIA,MAAA,IAAI,KAAK,QAAA,IAAY,CAAC,gBAAgB,GAAA,CAAI,IAAA,CAAK,QAAQ,CAAA,EAAG;AACxD,QAAA,OAAO,GAAA;AAAA,MACT;AAGA,MAAA,IAAI,OAAA,IAAW,IAAA,IAAQ,IAAA,CAAK,KAAA,EAAO;AACjC,QAAA,MAAM,gBAAA,GAAmB,qBAAA;AAAA,UACvB,IAAA,CAAK,KAAA;AAAA,UACL;AAAA,SACF;AAGA,QAAA,IAAI,gBAAA,CAAiB,SAAS,CAAA,EAAG;AAC/B,UAAA,GAAA,CAAI,IAAA,CAAK;AAAA,YACP,GAAG,IAAA;AAAA,YACH,KAAA,EAAO;AAAA,WACR,CAAA;AAAA,QACH;AAAA,MACF,CAAA,MAAO;AAGL,QAAA,GAAA,CAAI,KAAK,IAAI,CAAA;AAAA,MACf;AAEA,MAAA,OAAO,GAAA;AAAA,IACT,CAAA;AAAA,IACA;AAAC,GACH;AACF;AAKO,SAAS,oBAAA,CACd,YACA,eAAA,EACkB;AAClB,EAAA,IAAI,eAAA,CAAgB,GAAA,CAAI,GAAG,CAAA,EAAG;AAC5B,IAAA,OAAO,UAAA;AAAA,EACT;AAEA,EAAA,MAAM,qBAAuC,EAAC;AAE9C,EAAA,KAAA,MAAW,SAAS,UAAA,EAAY;AAC9B,IAAA,MAAM,aAAA,GAAgB,qBAAA,CAAsB,KAAA,CAAM,KAAA,EAAO,eAAe,CAAA;AAExE,IAAA,IAAI,aAAA,CAAc,SAAS,CAAA,EAAG;AAC5B,MAAA,kBAAA,CAAmB,IAAA,CAAK;AAAA,QACtB,GAAG,KAAA;AAAA,QACH,KAAA,EAAO;AAAA,OACR,CAAA;AAAA,IACH;AAAA,EACF;AAEA,EAAA,OAAO,kBAAA;AACT","file":"index.js","sourcesContent":["// @goerp/core/navigation\n// Core navigation utilities for GoERP platform\n// Consolidated from packages/shared/navigation\n\nimport type {\n NavigationType,\n NavigationRootItem,\n NavigationNestedItem,\n} from \"../types\";\n\n/**\n * Filter navigation items based on permissions recursively\n */\nexport function filterNavigationItems(\n items: NavigationRootItem[],\n accessibleCodes: Set<string>,\n): NavigationRootItem[];\nexport function filterNavigationItems(\n items: NavigationNestedItem[],\n accessibleCodes: Set<string>,\n): NavigationNestedItem[];\nexport function filterNavigationItems(\n items: NavigationRootItem[] | NavigationNestedItem[],\n accessibleCodes: Set<string>,\n): (NavigationRootItem | NavigationNestedItem)[] {\n return items.reduce<(NavigationRootItem | NavigationNestedItem)[]>(\n (acc, item) => {\n // If admin (special wildcard), show everything\n if (accessibleCodes.has(\"*\")) {\n acc.push(item);\n return acc;\n }\n\n // Default Deny: If resource is defined AND resource is not accessible\n // If resource is missing, we consider it public (as per comment below)\n if (item.resource && !accessibleCodes.has(item.resource)) {\n return acc;\n }\n\n // Handle nested items\n if (\"items\" in item && item.items) {\n const filteredChildren = filterNavigationItems(\n item.items as (NavigationRootItem | NavigationNestedItem)[],\n accessibleCodes,\n );\n\n // Only add parent if it has visible children\n if (filteredChildren.length > 0) {\n acc.push({\n ...item,\n items: filteredChildren,\n });\n }\n } else {\n // Leaf node (link) - already checked resource above\n // If no resource defined, assume public/visible\n acc.push(item);\n }\n\n return acc;\n },\n [],\n );\n}\n\n/**\n * Filter navigation tree based on permissions\n */\nexport function filterNavigationTree(\n navigation: NavigationType[],\n accessibleCodes: Set<string>,\n): NavigationType[] {\n if (accessibleCodes.has(\"*\")) {\n return navigation;\n }\n\n const filteredNavigation: NavigationType[] = [];\n\n for (const group of navigation) {\n const filteredItems = filterNavigationItems(group.items, accessibleCodes);\n\n if (filteredItems.length > 0) {\n filteredNavigation.push({\n ...group,\n items: filteredItems,\n });\n }\n }\n\n return filteredNavigation;\n}\n"]}
@@ -1,50 +0,0 @@
1
- // src/navigation/index.ts
2
- function filterNavigationItems(items, accessibleCodes) {
3
- return items.reduce(
4
- (acc, item) => {
5
- if (accessibleCodes.has("*")) {
6
- acc.push(item);
7
- return acc;
8
- }
9
- if (item.resource && !accessibleCodes.has(item.resource)) {
10
- return acc;
11
- }
12
- if ("items" in item && item.items) {
13
- const filteredChildren = filterNavigationItems(
14
- item.items,
15
- accessibleCodes
16
- );
17
- if (filteredChildren.length > 0) {
18
- acc.push({
19
- ...item,
20
- items: filteredChildren
21
- });
22
- }
23
- } else {
24
- acc.push(item);
25
- }
26
- return acc;
27
- },
28
- []
29
- );
30
- }
31
- function filterNavigationTree(navigation, accessibleCodes) {
32
- if (accessibleCodes.has("*")) {
33
- return navigation;
34
- }
35
- const filteredNavigation = [];
36
- for (const group of navigation) {
37
- const filteredItems = filterNavigationItems(group.items, accessibleCodes);
38
- if (filteredItems.length > 0) {
39
- filteredNavigation.push({
40
- ...group,
41
- items: filteredItems
42
- });
43
- }
44
- }
45
- return filteredNavigation;
46
- }
47
-
48
- export { filterNavigationItems, filterNavigationTree };
49
- //# sourceMappingURL=index.mjs.map
50
- //# sourceMappingURL=index.mjs.map
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../../src/navigation/index.ts"],"names":[],"mappings":";AAqBO,SAAS,qBAAA,CACd,OACA,eAAA,EAC+C;AAC/C,EAAA,OAAO,KAAA,CAAM,MAAA;AAAA,IACX,CAAC,KAAK,IAAA,KAAS;AAEb,MAAA,IAAI,eAAA,CAAgB,GAAA,CAAI,GAAG,CAAA,EAAG;AAC5B,QAAA,GAAA,CAAI,KAAK,IAAI,CAAA;AACb,QAAA,OAAO,GAAA;AAAA,MACT;AAIA,MAAA,IAAI,KAAK,QAAA,IAAY,CAAC,gBAAgB,GAAA,CAAI,IAAA,CAAK,QAAQ,CAAA,EAAG;AACxD,QAAA,OAAO,GAAA;AAAA,MACT;AAGA,MAAA,IAAI,OAAA,IAAW,IAAA,IAAQ,IAAA,CAAK,KAAA,EAAO;AACjC,QAAA,MAAM,gBAAA,GAAmB,qBAAA;AAAA,UACvB,IAAA,CAAK,KAAA;AAAA,UACL;AAAA,SACF;AAGA,QAAA,IAAI,gBAAA,CAAiB,SAAS,CAAA,EAAG;AAC/B,UAAA,GAAA,CAAI,IAAA,CAAK;AAAA,YACP,GAAG,IAAA;AAAA,YACH,KAAA,EAAO;AAAA,WACR,CAAA;AAAA,QACH;AAAA,MACF,CAAA,MAAO;AAGL,QAAA,GAAA,CAAI,KAAK,IAAI,CAAA;AAAA,MACf;AAEA,MAAA,OAAO,GAAA;AAAA,IACT,CAAA;AAAA,IACA;AAAC,GACH;AACF;AAKO,SAAS,oBAAA,CACd,YACA,eAAA,EACkB;AAClB,EAAA,IAAI,eAAA,CAAgB,GAAA,CAAI,GAAG,CAAA,EAAG;AAC5B,IAAA,OAAO,UAAA;AAAA,EACT;AAEA,EAAA,MAAM,qBAAuC,EAAC;AAE9C,EAAA,KAAA,MAAW,SAAS,UAAA,EAAY;AAC9B,IAAA,MAAM,aAAA,GAAgB,qBAAA,CAAsB,KAAA,CAAM,KAAA,EAAO,eAAe,CAAA;AAExE,IAAA,IAAI,aAAA,CAAc,SAAS,CAAA,EAAG;AAC5B,MAAA,kBAAA,CAAmB,IAAA,CAAK;AAAA,QACtB,GAAG,KAAA;AAAA,QACH,KAAA,EAAO;AAAA,OACR,CAAA;AAAA,IACH;AAAA,EACF;AAEA,EAAA,OAAO,kBAAA;AACT","file":"index.mjs","sourcesContent":["// @goerp/core/navigation\n// Core navigation utilities for GoERP platform\n// Consolidated from packages/shared/navigation\n\nimport type {\n NavigationType,\n NavigationRootItem,\n NavigationNestedItem,\n} from \"../types\";\n\n/**\n * Filter navigation items based on permissions recursively\n */\nexport function filterNavigationItems(\n items: NavigationRootItem[],\n accessibleCodes: Set<string>,\n): NavigationRootItem[];\nexport function filterNavigationItems(\n items: NavigationNestedItem[],\n accessibleCodes: Set<string>,\n): NavigationNestedItem[];\nexport function filterNavigationItems(\n items: NavigationRootItem[] | NavigationNestedItem[],\n accessibleCodes: Set<string>,\n): (NavigationRootItem | NavigationNestedItem)[] {\n return items.reduce<(NavigationRootItem | NavigationNestedItem)[]>(\n (acc, item) => {\n // If admin (special wildcard), show everything\n if (accessibleCodes.has(\"*\")) {\n acc.push(item);\n return acc;\n }\n\n // Default Deny: If resource is defined AND resource is not accessible\n // If resource is missing, we consider it public (as per comment below)\n if (item.resource && !accessibleCodes.has(item.resource)) {\n return acc;\n }\n\n // Handle nested items\n if (\"items\" in item && item.items) {\n const filteredChildren = filterNavigationItems(\n item.items as (NavigationRootItem | NavigationNestedItem)[],\n accessibleCodes,\n );\n\n // Only add parent if it has visible children\n if (filteredChildren.length > 0) {\n acc.push({\n ...item,\n items: filteredChildren,\n });\n }\n } else {\n // Leaf node (link) - already checked resource above\n // If no resource defined, assume public/visible\n acc.push(item);\n }\n\n return acc;\n },\n [],\n );\n}\n\n/**\n * Filter navigation tree based on permissions\n */\nexport function filterNavigationTree(\n navigation: NavigationType[],\n accessibleCodes: Set<string>,\n): NavigationType[] {\n if (accessibleCodes.has(\"*\")) {\n return navigation;\n }\n\n const filteredNavigation: NavigationType[] = [];\n\n for (const group of navigation) {\n const filteredItems = filterNavigationItems(group.items, accessibleCodes);\n\n if (filteredItems.length > 0) {\n filteredNavigation.push({\n ...group,\n items: filteredItems,\n });\n }\n }\n\n return filteredNavigation;\n}\n"]}