@sonhoseong/mfa-lib 1.3.7 → 1.3.10

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 (162) hide show
  1. package/dist/components/button/ScrollTopButton.js +5 -3
  2. package/dist/components/error/ErrorBoundary.js +14 -4
  3. package/dist/components/error/NotFound.d.ts +20 -0
  4. package/dist/components/error/NotFound.d.ts.map +1 -0
  5. package/dist/components/error/NotFound.js +84 -0
  6. package/dist/components/error/index.d.ts +2 -0
  7. package/dist/components/error/index.d.ts.map +1 -1
  8. package/dist/components/error/index.js +1 -0
  9. package/dist/components/icons/Icons.d.ts +51 -0
  10. package/dist/components/icons/Icons.d.ts.map +1 -0
  11. package/dist/components/icons/Icons.js +100 -0
  12. package/dist/components/icons/index.d.ts +5 -0
  13. package/dist/components/icons/index.d.ts.map +1 -0
  14. package/dist/components/icons/index.js +4 -0
  15. package/dist/components/index.d.ts +3 -0
  16. package/dist/components/index.d.ts.map +1 -1
  17. package/dist/components/index.js +6 -0
  18. package/dist/components/layout/Container.js +7 -2
  19. package/dist/components/loading/DeferredComponent.d.ts +19 -0
  20. package/dist/components/loading/DeferredComponent.d.ts.map +1 -0
  21. package/dist/components/loading/DeferredComponent.js +32 -0
  22. package/dist/components/loading/GlobalLoading.js +14 -3
  23. package/dist/components/loading/index.d.ts +1 -0
  24. package/dist/components/loading/index.d.ts.map +1 -1
  25. package/dist/components/loading/index.js +1 -0
  26. package/dist/components/logo/Logo.d.ts +2 -0
  27. package/dist/components/logo/Logo.d.ts.map +1 -1
  28. package/dist/components/logo/Logo.js +13 -4
  29. package/dist/components/modal/ModalContainer.js +17 -8
  30. package/dist/components/modal/ModalContext.js +2 -3
  31. package/dist/components/navigation/AppNavbar.js +21 -9
  32. package/dist/components/navigation/AppSidebar.css +58 -3
  33. package/dist/components/navigation/AppSidebar.d.ts +1 -1
  34. package/dist/components/navigation/AppSidebar.d.ts.map +1 -1
  35. package/dist/components/navigation/AppSidebar.js +58 -15
  36. package/dist/components/navigation/Footer.d.ts +15 -0
  37. package/dist/components/navigation/Footer.d.ts.map +1 -0
  38. package/dist/components/navigation/Footer.js +12 -0
  39. package/dist/components/navigation/Header.d.ts.map +1 -1
  40. package/dist/components/navigation/Header.js +17 -4
  41. package/dist/components/navigation/Lnb.d.ts +2 -7
  42. package/dist/components/navigation/Lnb.d.ts.map +1 -1
  43. package/dist/components/navigation/Lnb.js +34 -6
  44. package/dist/components/navigation/StickyNav.js +19 -11
  45. package/dist/components/navigation/index.d.ts +1 -0
  46. package/dist/components/navigation/index.d.ts.map +1 -1
  47. package/dist/components/navigation/index.js +1 -0
  48. package/dist/components/page/LoginPage.d.ts +4 -1
  49. package/dist/components/page/LoginPage.d.ts.map +1 -1
  50. package/dist/components/page/LoginPage.js +146 -21
  51. package/dist/components/remote/RemoteErrorBoundary.d.ts +28 -0
  52. package/dist/components/remote/RemoteErrorBoundary.d.ts.map +1 -0
  53. package/dist/components/remote/RemoteErrorBoundary.js +44 -0
  54. package/dist/components/remote/RemoteErrorFallback.d.ts +16 -0
  55. package/dist/components/remote/RemoteErrorFallback.d.ts.map +1 -0
  56. package/dist/components/remote/RemoteErrorFallback.js +76 -0
  57. package/dist/components/remote/index.d.ts +8 -0
  58. package/dist/components/remote/index.d.ts.map +1 -0
  59. package/dist/components/remote/index.js +5 -0
  60. package/dist/components/router/BrowserRouter.d.ts +13 -0
  61. package/dist/components/router/BrowserRouter.d.ts.map +1 -0
  62. package/dist/components/router/BrowserRouter.js +17 -0
  63. package/dist/components/router/RouteGuard.d.ts +79 -0
  64. package/dist/components/router/RouteGuard.d.ts.map +1 -0
  65. package/dist/components/router/RouteGuard.js +86 -0
  66. package/dist/components/router/index.d.ts +4 -0
  67. package/dist/components/router/index.d.ts.map +1 -0
  68. package/dist/components/router/index.js +2 -0
  69. package/dist/components/toast/ToastContainer.js +17 -6
  70. package/dist/components/toast/ToastContext.js +2 -3
  71. package/dist/hooks/index.d.ts +9 -1
  72. package/dist/hooks/index.d.ts.map +1 -1
  73. package/dist/hooks/index.js +15 -1
  74. package/dist/hooks/use-auth.d.ts +2 -1
  75. package/dist/hooks/use-auth.d.ts.map +1 -1
  76. package/dist/hooks/use-auth.js +19 -18
  77. package/dist/hooks/use-debounce.d.ts +56 -0
  78. package/dist/hooks/use-debounce.d.ts.map +1 -0
  79. package/dist/hooks/use-debounce.js +140 -0
  80. package/dist/hooks/use-effect-once.d.ts +77 -0
  81. package/dist/hooks/use-effect-once.d.ts.map +1 -0
  82. package/dist/hooks/use-effect-once.js +124 -0
  83. package/dist/hooks/use-error-notification.d.ts +1 -1
  84. package/dist/hooks/use-error-notification.js +1 -1
  85. package/dist/hooks/use-global-loading.d.ts +1 -1
  86. package/dist/hooks/use-global-loading.js +1 -1
  87. package/dist/hooks/use-initialize.d.ts +8 -1
  88. package/dist/hooks/use-initialize.d.ts.map +1 -1
  89. package/dist/hooks/use-initialize.js +126 -23
  90. package/dist/hooks/use-modal.d.ts +21 -5
  91. package/dist/hooks/use-modal.d.ts.map +1 -1
  92. package/dist/hooks/use-modal.js +57 -17
  93. package/dist/hooks/use-navigate.d.ts +1 -1
  94. package/dist/hooks/use-navigate.js +1 -1
  95. package/dist/hooks/use-network-status.d.ts +15 -0
  96. package/dist/hooks/use-network-status.d.ts.map +1 -0
  97. package/dist/hooks/use-network-status.js +49 -0
  98. package/dist/hooks/use-permission.d.ts +22 -0
  99. package/dist/hooks/use-permission.d.ts.map +1 -0
  100. package/dist/hooks/use-permission.js +73 -0
  101. package/dist/hooks/use-recent-menu.d.ts +46 -0
  102. package/dist/hooks/use-recent-menu.d.ts.map +1 -0
  103. package/dist/hooks/use-recent-menu.js +169 -0
  104. package/dist/hooks/use-scroll-restoration.d.ts +51 -0
  105. package/dist/hooks/use-scroll-restoration.d.ts.map +1 -0
  106. package/dist/hooks/use-scroll-restoration.js +143 -0
  107. package/dist/hooks/use-supabase-auth.d.ts +49 -0
  108. package/dist/hooks/use-supabase-auth.d.ts.map +1 -0
  109. package/dist/hooks/use-supabase-auth.js +229 -0
  110. package/dist/hooks/use-track-history.d.ts +2 -1
  111. package/dist/hooks/use-track-history.d.ts.map +1 -1
  112. package/dist/hooks/use-track-history.js +14 -2
  113. package/dist/index.d.ts +1 -1
  114. package/dist/index.js +1 -1
  115. package/dist/network/axios-factory.d.ts +30 -1
  116. package/dist/network/axios-factory.d.ts.map +1 -1
  117. package/dist/network/axios-factory.js +192 -24
  118. package/dist/network/index.d.ts +3 -1
  119. package/dist/network/index.d.ts.map +1 -1
  120. package/dist/network/index.js +5 -1
  121. package/dist/network/supabase-client.d.ts +28 -0
  122. package/dist/network/supabase-client.d.ts.map +1 -0
  123. package/dist/network/supabase-client.js +46 -0
  124. package/dist/store/app-store.d.ts +222 -12
  125. package/dist/store/app-store.d.ts.map +1 -1
  126. package/dist/store/app-store.js +46 -29
  127. package/dist/store/index.d.ts +2 -0
  128. package/dist/store/index.d.ts.map +1 -1
  129. package/dist/store/index.js +3 -0
  130. package/dist/store/menu-slice.d.ts +96 -0
  131. package/dist/store/menu-slice.d.ts.map +1 -0
  132. package/dist/store/menu-slice.js +98 -0
  133. package/dist/store/recent-menu-slice.d.ts +209 -0
  134. package/dist/store/recent-menu-slice.d.ts.map +1 -0
  135. package/dist/store/recent-menu-slice.js +110 -0
  136. package/dist/store/store-access.d.ts +1 -1
  137. package/dist/store/store-access.js +1 -1
  138. package/dist/types/index.d.ts +74 -17
  139. package/dist/types/index.d.ts.map +1 -1
  140. package/dist/types/service.d.ts +1 -1
  141. package/dist/types/service.js +1 -1
  142. package/dist/utils/classnames.d.ts +65 -0
  143. package/dist/utils/classnames.d.ts.map +1 -0
  144. package/dist/utils/classnames.js +98 -0
  145. package/dist/utils/formatter.d.ts +78 -0
  146. package/dist/utils/formatter.d.ts.map +1 -0
  147. package/dist/utils/formatter.js +216 -0
  148. package/dist/utils/index.d.ts +5 -0
  149. package/dist/utils/index.d.ts.map +1 -1
  150. package/dist/utils/index.js +5 -0
  151. package/dist/utils/permission.d.ts +33 -0
  152. package/dist/utils/permission.d.ts.map +1 -0
  153. package/dist/utils/permission.js +132 -0
  154. package/dist/utils/query-string.d.ts +67 -0
  155. package/dist/utils/query-string.d.ts.map +1 -0
  156. package/dist/utils/query-string.js +136 -0
  157. package/dist/utils/storage.d.ts +1 -1
  158. package/dist/utils/storage.js +1 -1
  159. package/dist/utils/validation.d.ts +98 -0
  160. package/dist/utils/validation.d.ts.map +1 -0
  161. package/dist/utils/validation.js +260 -0
  162. package/package.json +5 -3
@@ -0,0 +1,98 @@
1
+ /**
2
+ * ClassName 유틸리티
3
+ * 조건부 클래스 이름 결합 (clsx/classnames 대체)
4
+ * 외부 의존성 없이 구현
5
+ */
6
+ /**
7
+ * 여러 클래스 이름을 조건부로 결합
8
+ *
9
+ * @example
10
+ * // 기본 사용
11
+ * cn('foo', 'bar') // 'foo bar'
12
+ *
13
+ * @example
14
+ * // 조건부 클래스
15
+ * cn('btn', { 'btn-primary': isPrimary, 'btn-disabled': isDisabled })
16
+ * // isPrimary=true, isDisabled=false -> 'btn btn-primary'
17
+ *
18
+ * @example
19
+ * // 배열 사용
20
+ * cn(['foo', 'bar'], 'baz') // 'foo bar baz'
21
+ *
22
+ * @example
23
+ * // falsy 값 무시
24
+ * cn('foo', null, undefined, false, 'bar') // 'foo bar'
25
+ */
26
+ export function cn(...inputs) {
27
+ const classes = [];
28
+ for (const input of inputs) {
29
+ if (!input)
30
+ continue;
31
+ if (typeof input === 'string') {
32
+ classes.push(input);
33
+ }
34
+ else if (typeof input === 'number') {
35
+ classes.push(String(input));
36
+ }
37
+ else if (Array.isArray(input)) {
38
+ const nested = cn(...input);
39
+ if (nested)
40
+ classes.push(nested);
41
+ }
42
+ else if (typeof input === 'object') {
43
+ for (const [key, value] of Object.entries(input)) {
44
+ if (value)
45
+ classes.push(key);
46
+ }
47
+ }
48
+ }
49
+ return classes.join(' ');
50
+ }
51
+ /**
52
+ * cn의 별칭 (classNames)
53
+ */
54
+ export const classNames = cn;
55
+ /**
56
+ * cn의 별칭 (clsx 호환)
57
+ */
58
+ export const clsx = cn;
59
+ /**
60
+ * 조건부 클래스 생성 헬퍼
61
+ *
62
+ * @example
63
+ * const buttonClass = createClassVariants({
64
+ * base: 'btn',
65
+ * variants: {
66
+ * variant: {
67
+ * primary: 'btn-primary',
68
+ * secondary: 'btn-secondary',
69
+ * },
70
+ * size: {
71
+ * sm: 'btn-sm',
72
+ * md: 'btn-md',
73
+ * lg: 'btn-lg',
74
+ * },
75
+ * },
76
+ * });
77
+ *
78
+ * buttonClass({ variant: 'primary', size: 'lg' })
79
+ * // 'btn btn-primary btn-lg'
80
+ */
81
+ export function createClassVariants(config) {
82
+ const { base, variants } = config;
83
+ return (options) => {
84
+ const classes = [];
85
+ if (base)
86
+ classes.push(base);
87
+ for (const [variantKey, variantValue] of Object.entries(options)) {
88
+ const variantConfig = variants[variantKey];
89
+ if (variantConfig && variantValue) {
90
+ const className = variantConfig[variantValue];
91
+ if (className)
92
+ classes.push(className);
93
+ }
94
+ }
95
+ return classes.join(' ');
96
+ };
97
+ }
98
+ export default cn;
@@ -0,0 +1,78 @@
1
+ /**
2
+ * Formatter 유틸리티 (KOMCA 패턴)
3
+ * 전화번호, 날짜, 숫자 등의 포맷팅 함수 제공
4
+ */
5
+ /**
6
+ * 전화번호 포맷팅
7
+ * @example phoneFormatter('01012345678') // '010-1234-5678'
8
+ * @example phoneFormatter('0212345678') // '02-1234-5678'
9
+ */
10
+ export declare function phoneFormatter(value: string | null | undefined): string;
11
+ /**
12
+ * 날짜 포맷팅 (YYYYMMDD → YYYY.MM.DD 또는 YYYY-MM-DD)
13
+ * @example dateFormatter('20240315') // '2024.03.15'
14
+ * @example dateFormatter('20240315', '-') // '2024-03-15'
15
+ */
16
+ export declare function dateFormatter(value: string | null | undefined, separator?: string): string;
17
+ /**
18
+ * 날짜/시간 포맷팅 (Date 객체 → 문자열)
19
+ * @example dateTimeFormatter(new Date()) // '2024.03.15 14:30:00'
20
+ */
21
+ export declare function dateTimeFormatter(date: Date | null | undefined, options?: {
22
+ dateOnly?: boolean;
23
+ timeOnly?: boolean;
24
+ separator?: string;
25
+ }): string;
26
+ /**
27
+ * 숫자 콤마 포맷팅
28
+ * @example numberFormatter(1234567) // '1,234,567'
29
+ * @example numberFormatter(1234567.89) // '1,234,567.89'
30
+ */
31
+ export declare function numberFormatter(value: number | string | null | undefined): string;
32
+ /**
33
+ * 통화 포맷팅 (원화)
34
+ * @example currencyFormatter(1234567) // '1,234,567원'
35
+ * @example currencyFormatter(1234567, { prefix: '₩' }) // '₩1,234,567'
36
+ */
37
+ export declare function currencyFormatter(value: number | string | null | undefined, options?: {
38
+ prefix?: string;
39
+ suffix?: string;
40
+ }): string;
41
+ /**
42
+ * 바이트 사이즈 포맷팅
43
+ * @example byteSizeFormatter(1024) // '1 KB'
44
+ * @example byteSizeFormatter(1048576) // '1 MB'
45
+ */
46
+ export declare function byteSizeFormatter(bytes: number | null | undefined): string;
47
+ /**
48
+ * 마스킹 포맷팅 (개인정보 보호)
49
+ * @example maskEmail('test@example.com') // 'te**@example.com'
50
+ */
51
+ export declare function maskEmail(email: string | null | undefined): string;
52
+ /**
53
+ * 이름 마스킹
54
+ * @example maskName('홍길동') // '홍*동'
55
+ */
56
+ export declare function maskName(name: string | null | undefined): string;
57
+ /**
58
+ * 전화번호 마스킹
59
+ * @example maskPhone('010-1234-5678') // '010-****-5678'
60
+ */
61
+ export declare function maskPhone(phone: string | null | undefined): string;
62
+ /**
63
+ * 상대 시간 포맷팅
64
+ * @example relativeTimeFormatter(new Date(Date.now() - 60000)) // '1분 전'
65
+ */
66
+ export declare function relativeTimeFormatter(date: Date | string | null | undefined): string;
67
+ /**
68
+ * 퍼센트 포맷팅
69
+ * @example percentFormatter(0.1234) // '12.34%'
70
+ * @example percentFormatter(0.1234, 0) // '12%'
71
+ */
72
+ export declare function percentFormatter(value: number | null | undefined, decimals?: number): string;
73
+ /**
74
+ * 문자열 자르기 (말줄임)
75
+ * @example truncate('안녕하세요 반갑습니다', 5) // '안녕하세...'
76
+ */
77
+ export declare function truncate(str: string | null | undefined, maxLength: number, suffix?: string): string;
78
+ //# sourceMappingURL=formatter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"formatter.d.ts","sourceRoot":"","sources":["../../src/utils/formatter.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,MAAM,CAgCvE;AAED;;;;GAIG;AACH,wBAAgB,aAAa,CAC3B,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,EAChC,SAAS,GAAE,MAAY,GACtB,MAAM,CAaR;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAC/B,IAAI,EAAE,IAAI,GAAG,IAAI,GAAG,SAAS,EAC7B,OAAO,CAAC,EAAE;IACR,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,GACA,MAAM,CAuBR;AAED;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,MAAM,CAQjF;AAED;;;;GAIG;AACH,wBAAgB,iBAAiB,CAC/B,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,GAAG,SAAS,EACzC,OAAO,CAAC,EAAE;IAAE,MAAM,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,GAC7C,MAAM,CAOR;AAED;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,MAAM,CAa1E;AAED;;;GAGG;AACH,wBAAgB,SAAS,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,MAAM,CAWlE;AAED;;;GAGG;AACH,wBAAgB,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,MAAM,CAYhE;AAED;;;GAGG;AACH,wBAAgB,SAAS,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,MAAM,CAWlE;AAED;;;GAGG;AACH,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,IAAI,GAAG,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,MAAM,CAmBpF;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,CAC9B,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,EAChC,QAAQ,GAAE,MAAU,GACnB,MAAM,CAIR;AAED;;;GAGG;AACH,wBAAgB,QAAQ,CACtB,GAAG,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,EAC9B,SAAS,EAAE,MAAM,EACjB,MAAM,GAAE,MAAc,GACrB,MAAM,CAMR"}
@@ -0,0 +1,216 @@
1
+ /**
2
+ * Formatter 유틸리티 (KOMCA 패턴)
3
+ * 전화번호, 날짜, 숫자 등의 포맷팅 함수 제공
4
+ */
5
+ /**
6
+ * 전화번호 포맷팅
7
+ * @example phoneFormatter('01012345678') // '010-1234-5678'
8
+ * @example phoneFormatter('0212345678') // '02-1234-5678'
9
+ */
10
+ export function phoneFormatter(value) {
11
+ if (!value)
12
+ return '';
13
+ // 숫자만 추출
14
+ const numbers = value.replace(/[^0-9]/g, '');
15
+ // 휴대폰 번호 (010, 011, 016, 017, 018, 019)
16
+ if (numbers.match(/^01[0-9]/)) {
17
+ if (numbers.length === 11) {
18
+ return numbers.replace(/(\d{3})(\d{4})(\d{4})/, '$1-$2-$3');
19
+ }
20
+ else if (numbers.length === 10) {
21
+ return numbers.replace(/(\d{3})(\d{3})(\d{4})/, '$1-$2-$3');
22
+ }
23
+ }
24
+ // 서울 지역번호 (02)
25
+ if (numbers.startsWith('02')) {
26
+ if (numbers.length === 10) {
27
+ return numbers.replace(/(\d{2})(\d{4})(\d{4})/, '$1-$2-$3');
28
+ }
29
+ else if (numbers.length === 9) {
30
+ return numbers.replace(/(\d{2})(\d{3})(\d{4})/, '$1-$2-$3');
31
+ }
32
+ }
33
+ // 기타 지역번호 (031, 032, ...)
34
+ if (numbers.length === 11) {
35
+ return numbers.replace(/(\d{3})(\d{4})(\d{4})/, '$1-$2-$3');
36
+ }
37
+ else if (numbers.length === 10) {
38
+ return numbers.replace(/(\d{3})(\d{3})(\d{4})/, '$1-$2-$3');
39
+ }
40
+ return numbers;
41
+ }
42
+ /**
43
+ * 날짜 포맷팅 (YYYYMMDD → YYYY.MM.DD 또는 YYYY-MM-DD)
44
+ * @example dateFormatter('20240315') // '2024.03.15'
45
+ * @example dateFormatter('20240315', '-') // '2024-03-15'
46
+ */
47
+ export function dateFormatter(value, separator = '.') {
48
+ if (!value)
49
+ return '';
50
+ const numbers = value.replace(/[^0-9]/g, '');
51
+ if (numbers.length === 8) {
52
+ const year = numbers.substring(0, 4);
53
+ const month = numbers.substring(4, 6);
54
+ const day = numbers.substring(6, 8);
55
+ return `${year}${separator}${month}${separator}${day}`;
56
+ }
57
+ return value;
58
+ }
59
+ /**
60
+ * 날짜/시간 포맷팅 (Date 객체 → 문자열)
61
+ * @example dateTimeFormatter(new Date()) // '2024.03.15 14:30:00'
62
+ */
63
+ export function dateTimeFormatter(date, options) {
64
+ if (!date)
65
+ return '';
66
+ const { dateOnly = false, timeOnly = false, separator = '.' } = options || {};
67
+ const year = date.getFullYear();
68
+ const month = String(date.getMonth() + 1).padStart(2, '0');
69
+ const day = String(date.getDate()).padStart(2, '0');
70
+ const hours = String(date.getHours()).padStart(2, '0');
71
+ const minutes = String(date.getMinutes()).padStart(2, '0');
72
+ const seconds = String(date.getSeconds()).padStart(2, '0');
73
+ if (timeOnly) {
74
+ return `${hours}:${minutes}:${seconds}`;
75
+ }
76
+ const dateStr = `${year}${separator}${month}${separator}${day}`;
77
+ if (dateOnly) {
78
+ return dateStr;
79
+ }
80
+ return `${dateStr} ${hours}:${minutes}:${seconds}`;
81
+ }
82
+ /**
83
+ * 숫자 콤마 포맷팅
84
+ * @example numberFormatter(1234567) // '1,234,567'
85
+ * @example numberFormatter(1234567.89) // '1,234,567.89'
86
+ */
87
+ export function numberFormatter(value) {
88
+ if (value === null || value === undefined || value === '')
89
+ return '';
90
+ const num = typeof value === 'string' ? parseFloat(value) : value;
91
+ if (isNaN(num))
92
+ return '';
93
+ return num.toLocaleString('ko-KR');
94
+ }
95
+ /**
96
+ * 통화 포맷팅 (원화)
97
+ * @example currencyFormatter(1234567) // '1,234,567원'
98
+ * @example currencyFormatter(1234567, { prefix: '₩' }) // '₩1,234,567'
99
+ */
100
+ export function currencyFormatter(value, options) {
101
+ const { prefix = '', suffix = '원' } = options || {};
102
+ const formatted = numberFormatter(value);
103
+ if (!formatted)
104
+ return '';
105
+ return `${prefix}${formatted}${suffix}`;
106
+ }
107
+ /**
108
+ * 바이트 사이즈 포맷팅
109
+ * @example byteSizeFormatter(1024) // '1 KB'
110
+ * @example byteSizeFormatter(1048576) // '1 MB'
111
+ */
112
+ export function byteSizeFormatter(bytes) {
113
+ if (bytes === null || bytes === undefined)
114
+ return '';
115
+ const units = ['B', 'KB', 'MB', 'GB', 'TB'];
116
+ let unitIndex = 0;
117
+ let size = bytes;
118
+ while (size >= 1024 && unitIndex < units.length - 1) {
119
+ size /= 1024;
120
+ unitIndex++;
121
+ }
122
+ return `${size.toFixed(unitIndex === 0 ? 0 : 2)} ${units[unitIndex]}`;
123
+ }
124
+ /**
125
+ * 마스킹 포맷팅 (개인정보 보호)
126
+ * @example maskEmail('test@example.com') // 'te**@example.com'
127
+ */
128
+ export function maskEmail(email) {
129
+ if (!email)
130
+ return '';
131
+ const [localPart, domain] = email.split('@');
132
+ if (!domain)
133
+ return email;
134
+ const maskedLocal = localPart.length > 2
135
+ ? localPart.substring(0, 2) + '*'.repeat(localPart.length - 2)
136
+ : localPart;
137
+ return `${maskedLocal}@${domain}`;
138
+ }
139
+ /**
140
+ * 이름 마스킹
141
+ * @example maskName('홍길동') // '홍*동'
142
+ */
143
+ export function maskName(name) {
144
+ if (!name)
145
+ return '';
146
+ if (name.length === 2) {
147
+ return name.charAt(0) + '*';
148
+ }
149
+ if (name.length > 2) {
150
+ return name.charAt(0) + '*'.repeat(name.length - 2) + name.charAt(name.length - 1);
151
+ }
152
+ return name;
153
+ }
154
+ /**
155
+ * 전화번호 마스킹
156
+ * @example maskPhone('010-1234-5678') // '010-****-5678'
157
+ */
158
+ export function maskPhone(phone) {
159
+ if (!phone)
160
+ return '';
161
+ const formatted = phoneFormatter(phone);
162
+ const parts = formatted.split('-');
163
+ if (parts.length === 3) {
164
+ return `${parts[0]}-****-${parts[2]}`;
165
+ }
166
+ return formatted;
167
+ }
168
+ /**
169
+ * 상대 시간 포맷팅
170
+ * @example relativeTimeFormatter(new Date(Date.now() - 60000)) // '1분 전'
171
+ */
172
+ export function relativeTimeFormatter(date) {
173
+ if (!date)
174
+ return '';
175
+ const targetDate = typeof date === 'string' ? new Date(date) : date;
176
+ const now = new Date();
177
+ const diffMs = now.getTime() - targetDate.getTime();
178
+ const diffSec = Math.floor(diffMs / 1000);
179
+ const diffMin = Math.floor(diffSec / 60);
180
+ const diffHour = Math.floor(diffMin / 60);
181
+ const diffDay = Math.floor(diffHour / 24);
182
+ const diffMonth = Math.floor(diffDay / 30);
183
+ const diffYear = Math.floor(diffMonth / 12);
184
+ if (diffSec < 60)
185
+ return '방금 전';
186
+ if (diffMin < 60)
187
+ return `${diffMin}분 전`;
188
+ if (diffHour < 24)
189
+ return `${diffHour}시간 전`;
190
+ if (diffDay < 30)
191
+ return `${diffDay}일 전`;
192
+ if (diffMonth < 12)
193
+ return `${diffMonth}개월 전`;
194
+ return `${diffYear}년 전`;
195
+ }
196
+ /**
197
+ * 퍼센트 포맷팅
198
+ * @example percentFormatter(0.1234) // '12.34%'
199
+ * @example percentFormatter(0.1234, 0) // '12%'
200
+ */
201
+ export function percentFormatter(value, decimals = 2) {
202
+ if (value === null || value === undefined)
203
+ return '';
204
+ return `${(value * 100).toFixed(decimals)}%`;
205
+ }
206
+ /**
207
+ * 문자열 자르기 (말줄임)
208
+ * @example truncate('안녕하세요 반갑습니다', 5) // '안녕하세...'
209
+ */
210
+ export function truncate(str, maxLength, suffix = '...') {
211
+ if (!str)
212
+ return '';
213
+ if (str.length <= maxLength)
214
+ return str;
215
+ return str.substring(0, maxLength) + suffix;
216
+ }
@@ -1,2 +1,7 @@
1
1
  export * from './storage';
2
+ export * from './permission';
3
+ export * from './formatter';
4
+ export * from './validation';
5
+ export * from './query-string';
6
+ export * from './classnames';
2
7
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/utils/index.ts"],"names":[],"mappings":"AAAA,cAAc,WAAW,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/utils/index.ts"],"names":[],"mappings":"AAAA,cAAc,WAAW,CAAC;AAC1B,cAAc,cAAc,CAAC;AAC7B,cAAc,aAAa,CAAC;AAC5B,cAAc,cAAc,CAAC;AAC7B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,cAAc,CAAC"}
@@ -1 +1,6 @@
1
1
  export * from './storage';
2
+ export * from './permission';
3
+ export * from './formatter';
4
+ export * from './validation';
5
+ export * from './query-string';
6
+ export * from './classnames';
@@ -0,0 +1,33 @@
1
+ /**
2
+ * 권한 관련 유틸리티 (KOMCA 패턴)
3
+ */
4
+ import { User, UserRole, PermissionAction, MenuItem, MenuPermission } from '../types';
5
+ /**
6
+ * 특정 코드에 대한 권한이 있는지 확인
7
+ */
8
+ export declare function hasPermission(user: User | null, code: string, action?: PermissionAction): boolean;
9
+ /**
10
+ * 여러 코드 중 하나라도 권한이 있는지 확인 (OR)
11
+ */
12
+ export declare function hasAnyPermission(user: User | null, codes: string[], action?: PermissionAction): boolean;
13
+ /**
14
+ * 모든 코드에 대한 권한이 있는지 확인 (AND)
15
+ */
16
+ export declare function hasAllPermissions(user: User | null, codes: string[], action?: PermissionAction): boolean;
17
+ /**
18
+ * 특정 역할인지 확인
19
+ */
20
+ export declare function hasRole(user: User | null, roles: UserRole | UserRole[]): boolean;
21
+ /**
22
+ * 메뉴 권한 요구사항 충족 여부 확인
23
+ */
24
+ export declare function checkMenuPermission(user: User | null, permission?: MenuPermission): boolean;
25
+ /**
26
+ * 권한에 따라 메뉴 목록 필터링
27
+ */
28
+ export declare function filterMenusByPermission(menus: MenuItem[], user: User | null): MenuItem[];
29
+ /**
30
+ * 메뉴에서 특정 경로 접근 권한 확인
31
+ */
32
+ export declare function canAccessPath(menus: MenuItem[], path: string, user: User | null): boolean;
33
+ //# sourceMappingURL=permission.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"permission.d.ts","sourceRoot":"","sources":["../../src/utils/permission.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAc,gBAAgB,EAAE,QAAQ,EAAE,cAAc,EAAE,MAAM,UAAU,CAAC;AAMlG;;GAEG;AACH,wBAAgB,aAAa,CAC3B,IAAI,EAAE,IAAI,GAAG,IAAI,EACjB,IAAI,EAAE,MAAM,EACZ,MAAM,GAAE,gBAAyB,GAChC,OAAO,CAWT;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAC9B,IAAI,EAAE,IAAI,GAAG,IAAI,EACjB,KAAK,EAAE,MAAM,EAAE,EACf,MAAM,GAAE,gBAAyB,GAChC,OAAO,CAKT;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAC/B,IAAI,EAAE,IAAI,GAAG,IAAI,EACjB,KAAK,EAAE,MAAM,EAAE,EACf,MAAM,GAAE,gBAAyB,GAChC,OAAO,CAKT;AAED;;GAEG;AACH,wBAAgB,OAAO,CAAC,IAAI,EAAE,IAAI,GAAG,IAAI,EAAE,KAAK,EAAE,QAAQ,GAAG,QAAQ,EAAE,GAAG,OAAO,CAKhF;AAED;;GAEG;AACH,wBAAgB,mBAAmB,CACjC,IAAI,EAAE,IAAI,GAAG,IAAI,EACjB,UAAU,CAAC,EAAE,cAAc,GAC1B,OAAO,CAyBT;AAMD;;GAEG;AACH,wBAAgB,uBAAuB,CACrC,KAAK,EAAE,QAAQ,EAAE,EACjB,IAAI,EAAE,IAAI,GAAG,IAAI,GAChB,QAAQ,EAAE,CA4BZ;AAED;;GAEG;AACH,wBAAgB,aAAa,CAC3B,KAAK,EAAE,QAAQ,EAAE,EACjB,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,IAAI,GAAG,IAAI,GAChB,OAAO,CAmBT"}
@@ -0,0 +1,132 @@
1
+ /**
2
+ * 권한 관련 유틸리티 (KOMCA 패턴)
3
+ */
4
+ // ============================================
5
+ // 권한 체크 함수
6
+ // ============================================
7
+ /**
8
+ * 특정 코드에 대한 권한이 있는지 확인
9
+ */
10
+ export function hasPermission(user, code, action = 'read') {
11
+ if (!user)
12
+ return false;
13
+ // admin은 모든 권한
14
+ if (user.role === 'admin')
15
+ return true;
16
+ // 권한 목록에서 확인
17
+ const permission = user.permissions?.find((p) => p.code === code);
18
+ if (!permission)
19
+ return false;
20
+ return permission.actions.includes(action);
21
+ }
22
+ /**
23
+ * 여러 코드 중 하나라도 권한이 있는지 확인 (OR)
24
+ */
25
+ export function hasAnyPermission(user, codes, action = 'read') {
26
+ if (!user)
27
+ return false;
28
+ if (user.role === 'admin')
29
+ return true;
30
+ return codes.some((code) => hasPermission(user, code, action));
31
+ }
32
+ /**
33
+ * 모든 코드에 대한 권한이 있는지 확인 (AND)
34
+ */
35
+ export function hasAllPermissions(user, codes, action = 'read') {
36
+ if (!user)
37
+ return false;
38
+ if (user.role === 'admin')
39
+ return true;
40
+ return codes.every((code) => hasPermission(user, code, action));
41
+ }
42
+ /**
43
+ * 특정 역할인지 확인
44
+ */
45
+ export function hasRole(user, roles) {
46
+ if (!user || !user.role)
47
+ return false;
48
+ const roleArray = Array.isArray(roles) ? roles : [roles];
49
+ return roleArray.includes(user.role);
50
+ }
51
+ /**
52
+ * 메뉴 권한 요구사항 충족 여부 확인
53
+ */
54
+ export function checkMenuPermission(user, permission) {
55
+ // 권한 요구사항이 없으면 모두 접근 가능
56
+ if (!permission)
57
+ return true;
58
+ if (!user)
59
+ return false;
60
+ // admin은 모든 권한
61
+ if (user.role === 'admin')
62
+ return true;
63
+ const { requiredCodes, requiredRoles, requiredAction = 'read' } = permission;
64
+ // 역할 체크 (OR 조건)
65
+ if (requiredRoles && requiredRoles.length > 0) {
66
+ if (!hasRole(user, requiredRoles)) {
67
+ return false;
68
+ }
69
+ }
70
+ // 코드 체크 (OR 조건)
71
+ if (requiredCodes && requiredCodes.length > 0) {
72
+ if (!hasAnyPermission(user, requiredCodes, requiredAction)) {
73
+ return false;
74
+ }
75
+ }
76
+ return true;
77
+ }
78
+ // ============================================
79
+ // 메뉴 필터링 함수
80
+ // ============================================
81
+ /**
82
+ * 권한에 따라 메뉴 목록 필터링
83
+ */
84
+ export function filterMenusByPermission(menus, user) {
85
+ return menus
86
+ .filter((menu) => {
87
+ // hidden 메뉴 제외
88
+ if (menu.hidden)
89
+ return false;
90
+ // 권한 체크
91
+ return checkMenuPermission(user, menu.permission);
92
+ })
93
+ .map((menu) => {
94
+ // 자식 메뉴도 재귀적으로 필터링
95
+ if (menu.children && menu.children.length > 0) {
96
+ const filteredChildren = filterMenusByPermission(menu.children, user);
97
+ // 자식이 모두 필터링되면 부모도 제외
98
+ if (filteredChildren.length === 0) {
99
+ return null;
100
+ }
101
+ return {
102
+ ...menu,
103
+ children: filteredChildren,
104
+ };
105
+ }
106
+ return menu;
107
+ })
108
+ .filter((menu) => menu !== null);
109
+ }
110
+ /**
111
+ * 메뉴에서 특정 경로 접근 권한 확인
112
+ */
113
+ export function canAccessPath(menus, path, user) {
114
+ // 메뉴에서 해당 경로 찾기
115
+ const findMenu = (items) => {
116
+ for (const item of items) {
117
+ if (item.path === path)
118
+ return item;
119
+ if (item.children) {
120
+ const found = findMenu(item.children);
121
+ if (found)
122
+ return found;
123
+ }
124
+ }
125
+ return undefined;
126
+ };
127
+ const menu = findMenu(menus);
128
+ // 메뉴에 없는 경로는 기본 허용 (또는 거부로 변경 가능)
129
+ if (!menu)
130
+ return true;
131
+ return checkMenuPermission(user, menu.permission);
132
+ }
@@ -0,0 +1,67 @@
1
+ /**
2
+ * QueryString 유틸리티
3
+ * URL 쿼리 파라미터 파싱 및 생성
4
+ * KOMCA 패턴 업그레이드 버전 (lodash 의존성 제거)
5
+ */
6
+ export type QueryParams = Record<string, string | number | boolean | null | undefined>;
7
+ /**
8
+ * URL 쿼리 스트링을 객체로 파싱
9
+ * @example
10
+ * parseQueryString('?name=홍길동&age=30')
11
+ * // { name: '홍길동', age: '30' }
12
+ */
13
+ export declare function parseQueryString(searchStr?: string): Record<string, string | undefined>;
14
+ /**
15
+ * 객체를 쿼리 스트링으로 변환
16
+ * @example
17
+ * createQueryString({ name: '홍길동', age: 30 })
18
+ * // 'name=%ED%99%8D%EA%B8%B8%EB%8F%99&age=30'
19
+ */
20
+ export declare function createQueryString(params: QueryParams): string;
21
+ /**
22
+ * 현재 URL에 쿼리 파라미터 추가/업데이트
23
+ * @example
24
+ * updateQueryParams({ page: 2, sort: 'name' })
25
+ * // 현재 URL에 ?page=2&sort=name 추가
26
+ */
27
+ export declare function updateQueryParams(params: QueryParams, options?: {
28
+ replace?: boolean;
29
+ }): void;
30
+ /**
31
+ * 특정 쿼리 파라미터 값 가져오기
32
+ * @example
33
+ * getQueryParam('page') // '1'
34
+ * getQueryParam('page', '1') // 기본값 '1'
35
+ */
36
+ export declare function getQueryParam(key: string, defaultValue?: string): string | undefined;
37
+ /**
38
+ * 특정 쿼리 파라미터 삭제
39
+ * @example removeQueryParam('page')
40
+ */
41
+ export declare function removeQueryParam(key: string, options?: {
42
+ replace?: boolean;
43
+ }): void;
44
+ /**
45
+ * 모든 쿼리 파라미터 삭제
46
+ */
47
+ export declare function clearQueryParams(options?: {
48
+ replace?: boolean;
49
+ }): void;
50
+ /**
51
+ * URL에 쿼리 스트링 추가
52
+ * @example
53
+ * appendQueryString('/users', { page: 1, size: 10 })
54
+ * // '/users?page=1&size=10'
55
+ */
56
+ export declare function appendQueryString(url: string, params: QueryParams): string;
57
+ /**
58
+ * 쿼리 파라미터를 숫자로 파싱
59
+ * @example getQueryParamAsNumber('page', 1) // 1
60
+ */
61
+ export declare function getQueryParamAsNumber(key: string, defaultValue?: number): number;
62
+ /**
63
+ * 쿼리 파라미터를 boolean으로 파싱
64
+ * @example getQueryParamAsBoolean('active', false) // true
65
+ */
66
+ export declare function getQueryParamAsBoolean(key: string, defaultValue?: boolean): boolean;
67
+ //# sourceMappingURL=query-string.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"query-string.d.ts","sourceRoot":"","sources":["../../src/utils/query-string.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,MAAM,MAAM,WAAW,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,IAAI,GAAG,SAAS,CAAC,CAAC;AAEvF;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAkBvF;AAED;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,WAAW,GAAG,MAAM,CAQ7D;AAED;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAC/B,MAAM,EAAE,WAAW,EACnB,OAAO,CAAC,EAAE;IAAE,OAAO,CAAC,EAAE,OAAO,CAAA;CAAE,GAC9B,IAAI,CAuBN;AAED;;;;;GAKG;AACH,wBAAgB,aAAa,CAAC,GAAG,EAAE,MAAM,EAAE,YAAY,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAGpF;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE;IAAE,OAAO,CAAC,EAAE,OAAO,CAAA;CAAE,GAAG,IAAI,CAEnF;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,CAAC,EAAE;IAAE,OAAO,CAAC,EAAE,OAAO,CAAA;CAAE,GAAG,IAAI,CAUtE;AAED;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,WAAW,GAAG,MAAM,CAM1E;AAED;;;GAGG;AACH,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,MAAM,EAAE,YAAY,GAAE,MAAU,GAAG,MAAM,CAKnF;AAED;;;GAGG;AACH,wBAAgB,sBAAsB,CAAC,GAAG,EAAE,MAAM,EAAE,YAAY,GAAE,OAAe,GAAG,OAAO,CAI1F"}