@iyulab/enterprise 0.13.0 → 0.15.0
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.
- package/CHANGELOG.md +57 -0
- package/README.md +60 -1
- package/dist/index.d.ts +660 -11
- package/dist/index.js +82 -49
- package/package.json +3 -2
- package/dist/ApiConfig.d.ts +0 -71
- package/dist/FormRow.d.ts +0 -33
- package/dist/FormSection.d.ts +0 -26
- package/dist/auth/AuthClient.d.ts +0 -46
- package/dist/auth/permissions.d.ts +0 -43
- package/dist/data/ODataService.d.ts +0 -124
- package/dist/helpers/CurrencyHelper.d.ts +0 -33
- package/dist/helpers/DateHelper.d.ts +0 -61
- package/dist/helpers/ProgressHelper.d.ts +0 -48
- package/dist/helpers/UrgencyHelper.d.ts +0 -62
- package/dist/helpers/constants.d.ts +0 -6
- package/dist/helpers/index.d.ts +0 -10
- package/dist/helpers/messages.d.ts +0 -26
package/dist/index.d.ts
CHANGED
|
@@ -1,11 +1,660 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
export
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
1
|
+
import { CSSProperties } from 'react';
|
|
2
|
+
import { HttpResponse } from '@iyulab/http-client';
|
|
3
|
+
import { JSX } from 'react';
|
|
4
|
+
import { LocaleNamespace } from '@iyulab/components/dist/utilities/Locale.js';
|
|
5
|
+
import { ReactNode } from 'react';
|
|
6
|
+
|
|
7
|
+
export declare class ApiConfig {
|
|
8
|
+
private static _baseUrl;
|
|
9
|
+
private static _odataPrefix;
|
|
10
|
+
private static _apiPrefix;
|
|
11
|
+
private static _isDevelopment;
|
|
12
|
+
/**
|
|
13
|
+
* Initialize API configuration
|
|
14
|
+
* @param options Configuration options
|
|
15
|
+
*/
|
|
16
|
+
static initialize(options?: ApiConfigOptions): void;
|
|
17
|
+
/**
|
|
18
|
+
* API Base URL (always relative path by default)
|
|
19
|
+
*/
|
|
20
|
+
static get baseUrl(): string;
|
|
21
|
+
/**
|
|
22
|
+
* Manually set Base URL (for testing or custom configurations)
|
|
23
|
+
*/
|
|
24
|
+
static setBaseUrl(url: string): void;
|
|
25
|
+
/**
|
|
26
|
+
* OData endpoint prefix
|
|
27
|
+
*/
|
|
28
|
+
static get odataPrefix(): string;
|
|
29
|
+
/**
|
|
30
|
+
* REST API endpoint prefix
|
|
31
|
+
*/
|
|
32
|
+
static get apiPrefix(): string;
|
|
33
|
+
/**
|
|
34
|
+
* Generate OData endpoint URL
|
|
35
|
+
* @param entityName Entity name for OData endpoint
|
|
36
|
+
*/
|
|
37
|
+
static getODataUrl(entityName: string): string;
|
|
38
|
+
/**
|
|
39
|
+
* Generate REST API endpoint URL
|
|
40
|
+
* @param endpoint API endpoint path
|
|
41
|
+
*/
|
|
42
|
+
static getApiUrl(endpoint: string): string;
|
|
43
|
+
/**
|
|
44
|
+
* Generate full URL with query parameters
|
|
45
|
+
* @param endpoint Endpoint path
|
|
46
|
+
* @param params Query parameters
|
|
47
|
+
*/
|
|
48
|
+
static getUrlWithParams(endpoint: string, params: Record<string, string | number | boolean | undefined>): string;
|
|
49
|
+
/**
|
|
50
|
+
* Check if running in development environment
|
|
51
|
+
* Tries to detect from various bundler environments
|
|
52
|
+
*/
|
|
53
|
+
static get isDevelopment(): boolean;
|
|
54
|
+
/**
|
|
55
|
+
* Check if running in production environment
|
|
56
|
+
*/
|
|
57
|
+
static get isProduction(): boolean;
|
|
58
|
+
/**
|
|
59
|
+
* Reset configuration to defaults
|
|
60
|
+
*/
|
|
61
|
+
static reset(): void;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* API configuration and environment-based endpoint management
|
|
66
|
+
*
|
|
67
|
+
* Supports both development (proxy) and production (same-origin) deployments
|
|
68
|
+
*/
|
|
69
|
+
export declare interface ApiConfigOptions {
|
|
70
|
+
/** Base URL for all API requests (default: '') */
|
|
71
|
+
baseUrl?: string;
|
|
72
|
+
/** OData endpoint prefix (default: '$data') */
|
|
73
|
+
odataPrefix?: string;
|
|
74
|
+
/** REST API endpoint prefix (default: 'api') */
|
|
75
|
+
apiPrefix?: string;
|
|
76
|
+
/** Force development mode detection */
|
|
77
|
+
isDevelopment?: boolean;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* API 호출 실패 에러 — HTTP status 를 실어 호출부가 상태별 분기(예: 404 도메인 문구)를 할 수 있게 한다.
|
|
82
|
+
* `Error` 를 상속하므로 기존 `e instanceof Error`/`e.message` 소비처는 그대로 동작한다.
|
|
83
|
+
*/
|
|
84
|
+
export declare class ApiError extends Error {
|
|
85
|
+
readonly status: number;
|
|
86
|
+
/** OData v4 오류 봉투의 `error.details`(필드별 검증 상세) — 서버 응답에 없거나 파싱 실패면 undefined. */
|
|
87
|
+
readonly details?: ApiErrorDetail[];
|
|
88
|
+
constructor(message: string, status: number, details?: ApiErrorDetail[]);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* OData v4 오류 봉투의 `error.details` 항목 — 필드별 검증 실패 상세.
|
|
93
|
+
*
|
|
94
|
+
* 규격(OData JSON Format v4.0)상 각 항목은 `code`/`message` 를 **반드시** 갖고
|
|
95
|
+
* `target`(오류가 난 속성 이름)은 **선택**이다 — 필수로 선언하면 target 을 생략한
|
|
96
|
+
* 서버 응답에서 타입이 거짓말을 하게 된다.
|
|
97
|
+
*/
|
|
98
|
+
export declare interface ApiErrorDetail {
|
|
99
|
+
code: string;
|
|
100
|
+
message: string;
|
|
101
|
+
target?: string;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export declare interface AuthClient<TUser, TCredentials> {
|
|
105
|
+
/** 현재 세션 조회. 미인증/세션 만료 시 `null`. 성공 시 권한 store 자동 갱신(getPermissions 지정 시). */
|
|
106
|
+
fetchMe(): Promise<TUser | null>;
|
|
107
|
+
/** 로그인. 성공 시 권한 store 자동 갱신(getPermissions 지정 시). */
|
|
108
|
+
login(credentials: TCredentials): Promise<LoginResult<TUser>>;
|
|
109
|
+
/** 로그아웃. 권한 store 자동 clear(getPermissions 지정 시). */
|
|
110
|
+
logout(): Promise<void>;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export declare interface AuthClientConfig<TUser> {
|
|
114
|
+
/** 현재 세션 조회 URL (GET) */
|
|
115
|
+
meUrl: string;
|
|
116
|
+
/** 로그인 URL (POST, 자격증명을 JSON 바디로) */
|
|
117
|
+
loginUrl: string;
|
|
118
|
+
/** 로그아웃 URL (POST) */
|
|
119
|
+
logoutUrl: string;
|
|
120
|
+
/** 상대 URL 앞에 붙일 오리진(기본 '' = same-origin). */
|
|
121
|
+
baseUrl?: string;
|
|
122
|
+
/** fetch credentials 모드(기본 'same-origin' — 쿠키 세션). */
|
|
123
|
+
credentials?: RequestCredentials;
|
|
124
|
+
/** 사용자 대면 문구(로케일). 기본 영어. */
|
|
125
|
+
messages?: Partial<AuthClientMessages>;
|
|
126
|
+
/** 로그인 실패(non-401) 응답 바디에서 서버 메시지 추출. 기본: `body.Message ?? body.message`. */
|
|
127
|
+
extractLoginError?: (body: unknown) => string | undefined;
|
|
128
|
+
/**
|
|
129
|
+
* user 에서 권한 코드 배열을 추출. 지정하면 `fetchMe`/`login` 성공 시 권한 store 를 자동 갱신,
|
|
130
|
+
* `fetchMe`→null / `logout` 시 자동 clear.
|
|
131
|
+
*/
|
|
132
|
+
getPermissions?: (user: TUser) => string[];
|
|
133
|
+
/** 권한 자동 갱신 대상 store(기본: `defaultPermissionStore`). */
|
|
134
|
+
permissionStore?: PermissionStore;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
export declare interface AuthClientMessages {
|
|
138
|
+
/** 401 로그인 실패 */
|
|
139
|
+
invalidCredentials: string;
|
|
140
|
+
/** 기타 로그인 실패(non-401) 폴백 */
|
|
141
|
+
loginFailed: string;
|
|
142
|
+
/** 네트워크/예외 */
|
|
143
|
+
networkError: string;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/** 기본 store 권한을 비운다. */
|
|
147
|
+
export declare const clearPermissions: () => void;
|
|
148
|
+
|
|
149
|
+
export declare function createAuthClient<TUser, TCredentials = Record<string, unknown>>(config: AuthClientConfig<TUser>): AuthClient<TUser, TCredentials>;
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* OData v4 + custom REST 서비스를 생성한다.
|
|
153
|
+
* 반환된 서비스는 상태를 공유하지 않는 순수 클로저이므로 여러 개를 만들어도 안전하다(테스트 격리에도 유리).
|
|
154
|
+
*/
|
|
155
|
+
export declare function createODataService(config: ODataServiceConfig): ODataService;
|
|
156
|
+
|
|
157
|
+
/** 독립적인 권한 store 를 생성한다(테스트 격리·다중 컨텍스트에 유리). */
|
|
158
|
+
export declare function createPermissionStore(initial?: Iterable<string>): PermissionStore;
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Currency formatting utility class.
|
|
162
|
+
*
|
|
163
|
+
* @deprecated The formatting logic now lives in `@iyulab/components`' `formatCurrency`
|
|
164
|
+
* (framework-neutral, lower in the dependency stack so `@iyulab/modern-app` can consume it
|
|
165
|
+
* too). This class is kept as a thin, behavior-preserving wrapper for existing callers —
|
|
166
|
+
* new code should import `formatCurrency`/`formatNumber`/`formatDate` from
|
|
167
|
+
* `@iyulab/components` directly.
|
|
168
|
+
*/
|
|
169
|
+
export declare class CurrencyHelper {
|
|
170
|
+
/**
|
|
171
|
+
* Format amount as currency string.
|
|
172
|
+
* @param amount - Amount to format
|
|
173
|
+
* @param currency - Currency code (default: 'KRW')
|
|
174
|
+
* @param locale - Locale for formatting (default: 'ko-KR')
|
|
175
|
+
*/
|
|
176
|
+
static formatCurrency(amount: number | null | undefined, currency?: string, locale?: string): string;
|
|
177
|
+
/** Format amount as Korean Won (KRW) */
|
|
178
|
+
static formatKRW(amount: number | null | undefined): string;
|
|
179
|
+
/** Format amount as US Dollar (USD) */
|
|
180
|
+
static formatUSD(amount: number | null | undefined): string;
|
|
181
|
+
/** Format amount as Euro (EUR) */
|
|
182
|
+
static formatEUR(amount: number | null | undefined): string;
|
|
183
|
+
/** Format amount as Japanese Yen (JPY) */
|
|
184
|
+
static formatJPY(amount: number | null | undefined): string;
|
|
185
|
+
/** Format amount as Chinese Yuan (CNY) */
|
|
186
|
+
static formatCNY(amount: number | null | undefined): string;
|
|
187
|
+
/**
|
|
188
|
+
* Parse currency string to number.
|
|
189
|
+
* @param value - Currency string to parse
|
|
190
|
+
*/
|
|
191
|
+
static parseCurrency(value: string): number;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Date manipulation and formatting utility class
|
|
196
|
+
*/
|
|
197
|
+
export declare class DateHelper {
|
|
198
|
+
/**
|
|
199
|
+
* Format date to YYYY-MM-DD string
|
|
200
|
+
* @param date - Date to format (Date object or ISO string)
|
|
201
|
+
*/
|
|
202
|
+
static formatDate(date: Date | string | null | undefined): string;
|
|
203
|
+
/**
|
|
204
|
+
* Format date to localized string
|
|
205
|
+
* @param date - Date to format
|
|
206
|
+
* @param locale - Locale for formatting (default: 'ko-KR')
|
|
207
|
+
* @param options - Intl.DateTimeFormat options
|
|
208
|
+
*/
|
|
209
|
+
static formatLocalDate(date: Date | string | null | undefined, locale?: string, options?: Intl.DateTimeFormatOptions): string;
|
|
210
|
+
/**
|
|
211
|
+
* Format datetime to YYYY-MM-DD HH:mm string
|
|
212
|
+
* @param date - Date to format
|
|
213
|
+
*/
|
|
214
|
+
static formatDateTime(date: Date | string | null | undefined): string;
|
|
215
|
+
/**
|
|
216
|
+
* Calculate days difference between two dates
|
|
217
|
+
* @param startDate - Start date
|
|
218
|
+
* @param endDate - End date
|
|
219
|
+
* @returns Number of days (positive if endDate > startDate)
|
|
220
|
+
*/
|
|
221
|
+
static getDaysDifference(startDate: Date | string, endDate: Date | string): number;
|
|
222
|
+
/**
|
|
223
|
+
* Calculate days from now to target date
|
|
224
|
+
* @param targetDate - Target date
|
|
225
|
+
* @returns Days remaining (negative if overdue)
|
|
226
|
+
*/
|
|
227
|
+
static getDaysFromNow(targetDate: Date | string): number;
|
|
228
|
+
/**
|
|
229
|
+
* Check if date is today
|
|
230
|
+
*/
|
|
231
|
+
static isToday(date: Date | string): boolean;
|
|
232
|
+
/**
|
|
233
|
+
* Check if date is in the past
|
|
234
|
+
*/
|
|
235
|
+
static isPast(date: Date | string): boolean;
|
|
236
|
+
/**
|
|
237
|
+
* Check if date is in the future
|
|
238
|
+
*/
|
|
239
|
+
static isFuture(date: Date | string): boolean;
|
|
240
|
+
/**
|
|
241
|
+
* Add days to date
|
|
242
|
+
* @param date - Base date
|
|
243
|
+
* @param days - Number of days to add (can be negative)
|
|
244
|
+
*/
|
|
245
|
+
static addDays(date: Date | string, days: number): Date;
|
|
246
|
+
/**
|
|
247
|
+
* Get start of day (00:00:00)
|
|
248
|
+
*/
|
|
249
|
+
static startOfDay(date: Date | string): Date;
|
|
250
|
+
/**
|
|
251
|
+
* Get end of day (23:59:59.999)
|
|
252
|
+
*/
|
|
253
|
+
static endOfDay(date: Date | string): Date;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* 기본 싱글톤 권한 store. 앱 전역에서 `hasPermission(code)` 를 어디서나 호출하는
|
|
258
|
+
* 흔한 패턴을 위해 free 함수로 바인딩해 노출한다. 격리가 필요하면 `createPermissionStore()` 를 쓴다.
|
|
259
|
+
*/
|
|
260
|
+
export declare const defaultPermissionStore: PermissionStore;
|
|
261
|
+
|
|
262
|
+
/**
|
|
263
|
+
* Display string for an absent value (`null`/`undefined`/unparsable), shared by every
|
|
264
|
+
* formatting helper in this package so "no value" renders identically everywhere and
|
|
265
|
+
* never collides with a real `0`.
|
|
266
|
+
*/
|
|
267
|
+
export declare const EMPTY_VALUE_DISPLAY = "\u2014";
|
|
268
|
+
|
|
269
|
+
/**
|
|
270
|
+
* `@iyulab/enterprise` 의 화면 문자열 — **영어 기본 + 로케일 레지스트리**.
|
|
271
|
+
*
|
|
272
|
+
* ## 왜 이 파일이 생겼나
|
|
273
|
+
*
|
|
274
|
+
* 헬퍼들이 라벨을 **한국어 리터럴로** 들고 있었다(실측 14건). 이 리포는 이미 로케일 표준을
|
|
275
|
+
* 채택했지만(영어 기본 + 레지스트리) **강제하는 장치가 없어서** 채택 이후에 들어온 코드가
|
|
276
|
+
* 한국어 기본값을 갖고 있었다.
|
|
277
|
+
*
|
|
278
|
+
* ⚠**「의견 있음」이 정당화하는 것은 스타일 값의 의견이지 언어가 아니다**(2026-08-04 결정).
|
|
279
|
+
*
|
|
280
|
+
* ## 왜 자체 레지스트리를 만들지 않았나
|
|
281
|
+
*
|
|
282
|
+
* `@iyulab/components` 의 `Locale.namespace()`(1.23.0~)를 쓴다. 자체 구현은 이 조직의
|
|
283
|
+
* **네 번째 레지스트리**가 됐을 것이고, 그러면 소비자가 앱 하나에서 로케일을 **두 번**
|
|
284
|
+
* 전환해야 한다. `Locale.set()` 한 번으로 검증 메시지·이 라벨이 함께 따라온다.
|
|
285
|
+
*
|
|
286
|
+
* ## 동작 변화
|
|
287
|
+
*
|
|
288
|
+
* ⚠기본 로케일은 브라우저 언어에서 감지된다 — **한국어 브라우저는 종전과 같은 문구**를 본다
|
|
289
|
+
* (`ko-KR` → `ko`). 그 밖의 환경은 한국어 대신 **영어**를 본다. 그것이 이 이주의 목적이다.
|
|
290
|
+
*/
|
|
291
|
+
export declare type EnterpriseMessageKey = 'progressNotStarted' | 'progressEarly' | 'progressInProgress' | 'progressPastMid' | 'progressAlmost' | 'progressDone' | 'urgencyOverdue' | 'urgencyCritical' | 'urgencyUrgent' | 'urgencySoon' | 'urgencyNormal' | 'daysOverdue' | 'daysToday' | 'daysRemaining';
|
|
292
|
+
|
|
293
|
+
/**
|
|
294
|
+
* L2 폼 행 — 기본 2열, `full` 이면 1열.
|
|
295
|
+
*
|
|
296
|
+
* 오버라이드 계약(docs/lob-layers.md §2)에 따라 `className`·`style` 을 받아 병합한다.
|
|
297
|
+
* `columns` 는 2열 고정이 맞지 않는 소비자가 **컴포넌트를 복제하지 않고** 조정하는 경로다
|
|
298
|
+
* — 복제가 시작되면 이 계층은 쓰기 전보다 나쁜 상태를 만든다.
|
|
299
|
+
*
|
|
300
|
+
* 🔴**트랙은 `minmax(0, 1fr)` 이지 `1fr` 이 아니다 — 순수 `1fr` 은 «균등» 을 약속하고**
|
|
301
|
+
* **지키지 않는다.** 그리드 아이템의 기본 `min-width: auto` 는 내용의 min-content 아래로
|
|
302
|
+
* 줄어들기를 거부하므로, 한 칸에 긴 내용이 들어오면 그 칸만 부풀고 나머지가 찌그러진다.
|
|
303
|
+
* 실측(400px 컨테이너 · 한 칸에 긴 불가분 문자열): 2열이 **425/8**, 3열이 **425/8/8** 이었고
|
|
304
|
+
* 행 자체가 컨테이너를 넘쳤다. `minmax(0, 1fr)` 은 트랙의 최소를 0 으로 만들어 **자식을**
|
|
305
|
+
* **건드리지 않고** 이를 고친다 — 같은 실측에서 **196/196** · **128/128/128**.
|
|
306
|
+
*
|
|
307
|
+
* ⚠**긴 불가분 내용 자체의 넘침은 별개 축이고 이 컴포넌트의 몫이 아니다.** 칸이 균등해져도
|
|
308
|
+
* 그 안의 긴 문자열은 여전히 자기 칸을 넘는다 — 그것은 셀의 `overflow-wrap` 이 답이다
|
|
309
|
+
* (`@iyulab/components` 의 엘리먼트는 이미 그 값을 갖는다). 같은 실측에서 `overflow-wrap` 을
|
|
310
|
+
* 주면 행의 넘침이 0 이 됐다.
|
|
311
|
+
*
|
|
312
|
+
* 계약은 `tests/form-layout-contract.test.ts` 가 고정한다 — 다만 그 파일은 **«선언» 을 재지**
|
|
313
|
+
* **«배치» 를 재지 않는다**(이 패키지엔 레이아웃을 계산하는 테스트 자리가 없다). 위 수치는
|
|
314
|
+
* 형제 패키지의 브라우저 프로젝트를 계측기로 빌린 일회성 탐침으로 쟀다.
|
|
315
|
+
*/
|
|
316
|
+
export declare function FormRow({ children, full, columns, className, style, }: {
|
|
317
|
+
children: ReactNode;
|
|
318
|
+
/** 한 행 전체를 한 칸으로 쓴다. `columns` 보다 우선한다. */
|
|
319
|
+
full?: boolean;
|
|
320
|
+
/** 열 수. 기본 2. */
|
|
321
|
+
columns?: number;
|
|
322
|
+
className?: string;
|
|
323
|
+
style?: CSSProperties;
|
|
324
|
+
}): JSX.Element;
|
|
325
|
+
|
|
326
|
+
/**
|
|
327
|
+
* L2 폼 블록 — 제목이 붙은 필드 묶음.
|
|
328
|
+
*
|
|
329
|
+
* 🔴**제목에 `text-transform: uppercase` 와 `letter-spacing` 을 걸지 않는다**(0.7.0 에서 제거).
|
|
330
|
+
* ⑴ 한글에는 대문자가 없어 `uppercase` 가 **아무 효과가 없고**, 제목에 영문이 섞이면
|
|
331
|
+
* 그것만 커져 오히려 어수선해진다.
|
|
332
|
+
* ⑵ 양수 `letter-spacing` 은 **한글 가독성을 떨어뜨린다** — 라틴 소문자 조판 관례를
|
|
333
|
+
* 그대로 옮기면 안 되는 자리다.
|
|
334
|
+
* ⇒ 위계는 **크기·굵기·색** 세 신호로만 만든다(타입 스케일의 `label` 단).
|
|
335
|
+
* 영문 전용 UI 에서 눈썹 텍스트 느낌이 필요하면 `titleStyle` 로 `--u-text-overline-*`
|
|
336
|
+
* 단을 직접 지정한다 — 그 단은 영문·숫자 라벨을 전제하고 양수 자간을 갖는다.
|
|
337
|
+
*
|
|
338
|
+
* 오버라이드 계약(docs/lob-layers.md §2): 구조는 두고 **children 치환 + prop** 으로 바꾼다.
|
|
339
|
+
* ⚠기본 스타일은 병합 가능한 형태로만 둔다 — 호출자의 `style` 이 뒤에 오므로 필요한
|
|
340
|
+
* 항목만 골라 덮을 수 있다. 인라인으로 굳혀 두면 소비자 CSS 가 이길 수 없어(`!important`
|
|
341
|
+
* 외) 계약이 원천적으로 무효가 된다.
|
|
342
|
+
*/
|
|
343
|
+
export declare function FormSection({ title, children, className, style, titleStyle, }: {
|
|
344
|
+
title: ReactNode;
|
|
345
|
+
children: ReactNode;
|
|
346
|
+
className?: string;
|
|
347
|
+
style?: CSSProperties;
|
|
348
|
+
/** 제목 줄만 따로 조정할 때. 블록을 통째로 갈아엎지 않기 위한 훅이다. */
|
|
349
|
+
titleStyle?: CSSProperties;
|
|
350
|
+
}): JSX.Element;
|
|
351
|
+
|
|
352
|
+
/** 기본 store 의 현재 권한 집합. */
|
|
353
|
+
export declare const getPermissions: () => ReadonlySet<string>;
|
|
354
|
+
|
|
355
|
+
/** 기본 store 기준 모두 보유. */
|
|
356
|
+
export declare const hasAllPermissions: (codes: Iterable<string>) => boolean;
|
|
357
|
+
|
|
358
|
+
/** 기본 store 기준 하나라도 보유. */
|
|
359
|
+
export declare const hasAnyPermission: (codes: Iterable<string>) => boolean;
|
|
360
|
+
|
|
361
|
+
/** 기본 store 기준 단일 권한 보유 여부. */
|
|
362
|
+
export declare const hasPermission: (code: string) => boolean;
|
|
363
|
+
|
|
364
|
+
export declare interface LoginResult<TUser> {
|
|
365
|
+
ok: boolean;
|
|
366
|
+
user?: TUser;
|
|
367
|
+
message?: string;
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
/** 이 패키지의 문자열 묶음. 소비자는 `messages.register(locale, table)` 로 덮거나 언어를 더한다. */
|
|
371
|
+
export declare const messages: LocaleNamespace<EnterpriseMessageKey>;
|
|
372
|
+
|
|
373
|
+
/**
|
|
374
|
+
* 한 호출에만 적용되는 요청 옵션 — 서비스 전역 정책(`ODataServiceConfig`)의 호출 단위 예외.
|
|
375
|
+
*
|
|
376
|
+
* 🔴**전역 정책을 «끄는» 축만 둔다.** 새 동작을 켜는 스위치가 아니라, config 가 세운 기본
|
|
377
|
+
* 정책이 *그 호출에서만* 틀린 경우를 위한 탈출구다 — 그래서 기본값은 항상 «config 그대로» 이고,
|
|
378
|
+
* 옵션을 생략한 호출은 이 타입이 생기기 전과 **한 글자도 다르게 동작하지 않는다.**
|
|
379
|
+
*/
|
|
380
|
+
export declare interface ODataRequestOptions {
|
|
381
|
+
/**
|
|
382
|
+
* `false` → 이 호출의 401 을 «세션 만료» 로 취급하지 않는다: 전역 `onUnauthorized` 를
|
|
383
|
+
* 부르지 않고, `messages.sessionExpired` 로 덮어쓰지도 않으며, **서버가 준 메시지**로
|
|
384
|
+
* `ApiError(…, 401)` 을 던진다. 기본값(생략 시)은 전역 정책 그대로다.
|
|
385
|
+
*
|
|
386
|
+
* ⚠**로그인 자체가 이 옵션이 태어난 자리다** — 401 이 「세션이 끊겼다」가 아니라
|
|
387
|
+
* 「자격 증명이 틀렸다」를 뜻하는 유일한 호출이라, 전역 훅이 발화하면 로그인 화면에서
|
|
388
|
+
* 로그인 화면으로 리다이렉트되고 화면이 실패 사유를 **지어내야** 한다.
|
|
389
|
+
*
|
|
390
|
+
* ⚠**`authenticate()` 같은 이름 있는 프리미티브를 두지 않은 이유**: 그것은 엔드포인트
|
|
391
|
+
* 경로·바디 모양·토큰 처리 규약을 이 라이브러리가 안다고 가정하는 **도메인 이름**이다.
|
|
392
|
+
* 그 규약은 앱마다 다르므로 adapter 에 남기고, 라이브러리는 범용 축만 연다.
|
|
393
|
+
*/
|
|
394
|
+
onUnauthorized?: false;
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
export declare interface ODataService {
|
|
398
|
+
/** 설정된 prefix + baseUrl 로 OData 엔티티 URL 을 만든다(flex-table useODataSource 등에서 사용). */
|
|
399
|
+
odataUrl(entity: string): string;
|
|
400
|
+
/** 설정된 prefix + baseUrl 로 REST 엔드포인트 URL 을 만든다. */
|
|
401
|
+
apiUrl(path: string): string;
|
|
402
|
+
/** OData GET(목록). `value` 배열을 벗겨 반환한다. */
|
|
403
|
+
odataGet<T>(entity: string, params?: Record<string, string>, opts?: ODataRequestOptions): Promise<T[]>;
|
|
404
|
+
/** OData GET(단건, key). */
|
|
405
|
+
odataGetById<T>(entity: string, id: string, opts?: ODataRequestOptions): Promise<T>;
|
|
406
|
+
/** `$count=true&$top=0` — 데이터 없이 총 건수만. */
|
|
407
|
+
odataCount(entity: string, filter?: Record<string, unknown>, opts?: ODataRequestOptions): Promise<number>;
|
|
408
|
+
/** OData POST(생성) — 토스트 없이 결과만(일괄 처리에서 토스트 폭주 방지). */
|
|
409
|
+
odataPostQuiet<T>(entity: string, body: Partial<T>, opts?: ODataRequestOptions): Promise<T>;
|
|
410
|
+
/** OData POST(생성) — 성공 시 `saved` 토스트. */
|
|
411
|
+
odataPost<T>(entity: string, body: Partial<T>, opts?: ODataRequestOptions): Promise<T>;
|
|
412
|
+
/** OData PATCH(수정) — 토스트 없이 결과만(자식 컬렉션 편집 후 부모의 파생 필드를
|
|
413
|
+
* 함께 동기화하는 등, 한 사용자 액션이 여러 mutation을 낼 때 토스트 폭주 방지). */
|
|
414
|
+
odataPatchQuiet<T>(entity: string, id: string, body: Partial<T>, opts?: ODataRequestOptions): Promise<void>;
|
|
415
|
+
/** OData PATCH(수정) — 성공 시 `updated` 토스트. */
|
|
416
|
+
odataPatch<T>(entity: string, id: string, body: Partial<T>, opts?: ODataRequestOptions): Promise<void>;
|
|
417
|
+
/** OData DELETE — 토스트 없이(`odataPatchQuiet`와 같은 이유). */
|
|
418
|
+
odataDeleteQuiet(entity: string, id: string, opts?: ODataRequestOptions): Promise<void>;
|
|
419
|
+
/** OData DELETE — 성공 시 `deleted` 토스트. */
|
|
420
|
+
odataDelete(entity: string, id: string, opts?: ODataRequestOptions): Promise<void>;
|
|
421
|
+
/** custom REST GET — 204 등 빈 바디를 안전 파싱. */
|
|
422
|
+
apiGet<T>(path: string, opts?: ODataRequestOptions): Promise<T>;
|
|
423
|
+
/** custom REST POST — 실패 시 `error` 토스트 후 rethrow(401 제외). `body`가 `FormData`
|
|
424
|
+
* 인스턴스면 그대로(직렬화 없이) 멀티파트로 전송된다 — `@iyulab/http-client`가
|
|
425
|
+
* Content-Type을 브라우저 자동 설정에 맡기고 JSON 직렬화 분기를 타지 않는다.
|
|
426
|
+
* `apiPut`/`apiPatch`도 동일하게 동작한다. */
|
|
427
|
+
apiPost<T>(path: string, body?: unknown, opts?: ODataRequestOptions): Promise<T>;
|
|
428
|
+
/** custom REST POST — 토스트 없이 결과만(`odataPostQuiet`와 같은 이유: 한 사용자 액션이
|
|
429
|
+
* 여러 요청을 내거나, 소비자가 자기 래퍼로 이미 통지할 때). */
|
|
430
|
+
apiPostQuiet<T>(path: string, body?: unknown, opts?: ODataRequestOptions): Promise<T>;
|
|
431
|
+
/** custom REST PUT(리소스 전체 교체/생성) — 실패 시 `error` 토스트 후 rethrow(401 제외).
|
|
432
|
+
* `body`의 `FormData` 처리는 `apiPost` 참조. */
|
|
433
|
+
apiPut<T>(path: string, body?: unknown, opts?: ODataRequestOptions): Promise<T>;
|
|
434
|
+
/** custom REST PUT — 토스트 없이 결과만. */
|
|
435
|
+
apiPutQuiet<T>(path: string, body?: unknown, opts?: ODataRequestOptions): Promise<T>;
|
|
436
|
+
/** custom REST PATCH — 실패 시 `error` 토스트 후 rethrow(401 제외). `body`의 `FormData`
|
|
437
|
+
* 처리는 `apiPost` 참조. */
|
|
438
|
+
apiPatch<T>(path: string, body?: unknown, opts?: ODataRequestOptions): Promise<T>;
|
|
439
|
+
/** custom REST PATCH — 토스트 없이 결과만. */
|
|
440
|
+
apiPatchQuiet<T>(path: string, body?: unknown, opts?: ODataRequestOptions): Promise<T>;
|
|
441
|
+
/** custom REST DELETE — 실패 시 `error` 토스트 후 rethrow(401 제외). 대부분 204 No Content. */
|
|
442
|
+
apiDelete<T = void>(path: string, opts?: ODataRequestOptions): Promise<T>;
|
|
443
|
+
/** custom REST DELETE — 토스트 없이 결과만. */
|
|
444
|
+
apiDeleteQuiet<T = void>(path: string, opts?: ODataRequestOptions): Promise<T>;
|
|
445
|
+
/**
|
|
446
|
+
* URL 을 직접 조립한 커스텀 조회(csv-export·연결 프로브 등)를 위해 응답을 **그대로** 반환한다.
|
|
447
|
+
*
|
|
448
|
+
* 🔴**비-2xx 에도 던지지 않고 `onUnauthorized` 도 부르지 않는다 — 판단은 호출부가 한다.**
|
|
449
|
+
* 「raw」는 정책을 태우지 않는다는 뜻이고, 이 메서드가 존재하는 이유가 그것이다.
|
|
450
|
+
* 상태에 따라 예외·토스트·세션 처리를 원하면 `apiGet` 을 쓴다.
|
|
451
|
+
*
|
|
452
|
+
* ⚠**0.15.0 이전에는 선언이 이렇게 적혀 있으면서 실제로는 비-2xx 에 던졌다**(`throwIfError`
|
|
453
|
+
* 를 거쳤다). 그 어긋남 때문에 *"응답이 왔는가"* 만 묻는 연결 프로브가 401 을 「끊김」으로
|
|
454
|
+
* 세었다 — 서버가 살아서 거절한 것인데도. 선언이 옳고 구현이 틀렸던 자리라 구현을 고쳤다.
|
|
455
|
+
*/
|
|
456
|
+
fetchRaw(url: string): Promise<HttpResponse>;
|
|
457
|
+
/**
|
|
458
|
+
* flex-table `useODataSource` 에 주입할 공용 transport 옵션.
|
|
459
|
+
* useODataSource 는 자체 fetcher 를 쓰므로 별도로 `onUnauthorized` 를 배선해야 401 처리가 걸린다.
|
|
460
|
+
*/
|
|
461
|
+
readonly sourceDefaults: {
|
|
462
|
+
baseUrl: string;
|
|
463
|
+
onUnauthorized: () => void;
|
|
464
|
+
};
|
|
465
|
+
/** `instanceof` 판정을 위해 재노출. (모듈 export `ApiError` 와 동일 클래스) */
|
|
466
|
+
readonly ApiError: typeof ApiError;
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
export declare interface ODataServiceConfig {
|
|
470
|
+
/** 모든 요청의 베이스 URL (예: `window.location.origin`). 슬래시 없이 오리진만. */
|
|
471
|
+
baseUrl: string;
|
|
472
|
+
/** OData 엔드포인트 prefix (기본 `$data`) */
|
|
473
|
+
odataPrefix?: string;
|
|
474
|
+
/** custom REST 엔드포인트 prefix (기본 `api`) */
|
|
475
|
+
apiPrefix?: string;
|
|
476
|
+
/**
|
|
477
|
+
* 401 응답 시 호출된다(모든 메서드 공통). 세션 만료 리다이렉트는 앱이 결정한다.
|
|
478
|
+
* 재진입 가드(한 번만 리다이렉트)도 앱 콜백 쪽에서 처리한다 — 라이브러리는 status만 통지.
|
|
479
|
+
*/
|
|
480
|
+
onUnauthorized?: (status: number) => void;
|
|
481
|
+
/** 성공/실패 토스트 훅. 생략하면 토스트를 내지 않는다(순수 · 테스트 용이). */
|
|
482
|
+
notify?: {
|
|
483
|
+
success?: (message: string) => void;
|
|
484
|
+
error?: (message: string) => void;
|
|
485
|
+
};
|
|
486
|
+
/** 사용자 대면 문구 오버라이드(로케일). 지정한 키만 기본값을 대체한다. */
|
|
487
|
+
messages?: Partial<ODataServiceMessages>;
|
|
488
|
+
/**
|
|
489
|
+
* 에러 메시지 포매팅 오버라이드. 반환값이 있으면 그것을 에러 메시지로 사용한다.
|
|
490
|
+
* (앱별 정책 — 예: 영문 raw OData 메시지를 로케일 친화 문구로 치환 — 을 여기에 둔다.)
|
|
491
|
+
*/
|
|
492
|
+
formatError?: (info: {
|
|
493
|
+
status: number;
|
|
494
|
+
statusText: string;
|
|
495
|
+
rawMessage?: string;
|
|
496
|
+
/** OData v4 `error.details` — 검증을 마친 항목만 실린다(`ApiError.details` 와 같은 값). */
|
|
497
|
+
details?: ApiErrorDetail[];
|
|
498
|
+
body?: unknown;
|
|
499
|
+
}) => string | undefined;
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
/** 서비스가 토스트/에러에 쓰는 사용자 대면 문구. 기본값은 영어 — 앱이 로케일별로 오버라이드한다. */
|
|
503
|
+
export declare interface ODataServiceMessages {
|
|
504
|
+
/** POST 성공 토스트 */
|
|
505
|
+
saved: string;
|
|
506
|
+
/** PATCH 성공 토스트 */
|
|
507
|
+
updated: string;
|
|
508
|
+
/** DELETE 성공 토스트 */
|
|
509
|
+
deleted: string;
|
|
510
|
+
/** 401 세션 만료 시 throw 되는 에러 메시지 */
|
|
511
|
+
sessionExpired: string;
|
|
512
|
+
/** status별 매핑이 없을 때의 폴백 */
|
|
513
|
+
requestFailed: string;
|
|
514
|
+
/** 서버 raw 메시지가 없거나 너무 길 때 status → 친화 메시지 */
|
|
515
|
+
http: Record<number, string>;
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
/**
|
|
519
|
+
* 권한 스냅샷 store — 부팅 시점의 현재 사용자 권한 코드 집합을 보관하고
|
|
520
|
+
* `hasPermission`/`hasAny`/`hasAll` 판정을 제공한다. 프레임워크 무관(React·Lit 공통)이며
|
|
521
|
+
* 권한 코드는 **불투명 문자열**로만 다룬다(도메인 의미는 앱이 소유).
|
|
522
|
+
*
|
|
523
|
+
* 부팅 시점 스냅샷 모델 — 권한 변경은 다음 로그인(재-set)까지 반영되지 않는다. 단일 운영자
|
|
524
|
+
* 모델에 충분하며, 실시간 권한 회수/멀티테넌트가 필요하면 `subscribe` 로 반응형 확장 가능.
|
|
525
|
+
*/
|
|
526
|
+
export declare interface PermissionStore {
|
|
527
|
+
/** 현재 권한 집합을 통째로 교체한다(로그인/세션 조회 성공 시). */
|
|
528
|
+
set(codes: Iterable<string>): void;
|
|
529
|
+
/** 현재 권한 집합(읽기 전용). */
|
|
530
|
+
get(): ReadonlySet<string>;
|
|
531
|
+
/** 단일 권한 코드 보유 여부. */
|
|
532
|
+
has(code: string): boolean;
|
|
533
|
+
/** 주어진 코드 중 하나라도 보유. 빈 목록은 `true`(제약 없음). */
|
|
534
|
+
hasAny(codes: Iterable<string>): boolean;
|
|
535
|
+
/** 주어진 코드를 모두 보유. 빈 목록은 `true`. */
|
|
536
|
+
hasAll(codes: Iterable<string>): boolean;
|
|
537
|
+
/** 권한을 비운다(로그아웃/세션 만료 시). */
|
|
538
|
+
clear(): void;
|
|
539
|
+
/** 권한 변경 구독. 해제 함수를 반환한다. */
|
|
540
|
+
subscribe(listener: (codes: ReadonlySet<string>) => void): () => void;
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
/**
|
|
544
|
+
* Progress bar and percentage utility class
|
|
545
|
+
*/
|
|
546
|
+
export declare class ProgressHelper {
|
|
547
|
+
/**
|
|
548
|
+
* Get color based on progress percentage
|
|
549
|
+
* @param progress - Progress value (0-100)
|
|
550
|
+
* @param thresholds - Custom thresholds { high, medium }
|
|
551
|
+
*/
|
|
552
|
+
static getProgressColor(progress: number, thresholds?: {
|
|
553
|
+
high?: number;
|
|
554
|
+
medium?: number;
|
|
555
|
+
}): string;
|
|
556
|
+
/**
|
|
557
|
+
* Validate and clamp progress to 0-100 range.
|
|
558
|
+
*
|
|
559
|
+
* Returns `null` for a missing/unparsable input rather than `0` — "no progress value yet"
|
|
560
|
+
* and "0% progress" are different facts and must not render identically.
|
|
561
|
+
* @param progress - Raw progress value
|
|
562
|
+
* @param round - Whether to round to integer (default: true)
|
|
563
|
+
*/
|
|
564
|
+
static validateProgress(progress: number, round?: boolean): number;
|
|
565
|
+
static validateProgress(progress: number | null | undefined, round?: boolean): number | null;
|
|
566
|
+
/**
|
|
567
|
+
* Convert decimal ratio to percentage.
|
|
568
|
+
*
|
|
569
|
+
* Returns `null` for a missing/unparsable input — see {@link validateProgress}.
|
|
570
|
+
* @param ratio - Decimal ratio (0-1)
|
|
571
|
+
* @param round - Whether to round to integer
|
|
572
|
+
*/
|
|
573
|
+
static ratioToPercent(ratio: number | null | undefined, round?: boolean): number | null;
|
|
574
|
+
/**
|
|
575
|
+
* Get progress label text
|
|
576
|
+
* @param progress - Progress value (0-100)
|
|
577
|
+
*/
|
|
578
|
+
static getProgressLabel(progress: number): string;
|
|
579
|
+
/**
|
|
580
|
+
* Calculate progress from current and total values
|
|
581
|
+
* @param current - Current value
|
|
582
|
+
* @param total - Total value
|
|
583
|
+
*/
|
|
584
|
+
static calculateProgress(current: number, total: number): number;
|
|
585
|
+
/**
|
|
586
|
+
* Get CSS gradient for progress bar
|
|
587
|
+
* @param progress - Progress value (0-100)
|
|
588
|
+
*/
|
|
589
|
+
static getProgressGradient(progress: number): string;
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
/** 기본 store 의 권한을 교체한다(부팅 시 `setPermissions(user.Permissions)`). */
|
|
593
|
+
export declare const setPermissions: (codes: Iterable<string>) => void;
|
|
594
|
+
|
|
595
|
+
/**
|
|
596
|
+
* Urgency configuration
|
|
597
|
+
*/
|
|
598
|
+
export declare interface UrgencyConfig {
|
|
599
|
+
/** Days threshold for critical urgency (default: 1) */
|
|
600
|
+
critical?: number;
|
|
601
|
+
/** Days threshold for urgent level (default: 3) */
|
|
602
|
+
urgent?: number;
|
|
603
|
+
/** Days threshold for soon level (default: 7) */
|
|
604
|
+
soon?: number;
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
/**
|
|
608
|
+
* Urgency calculation and display utility class
|
|
609
|
+
*/
|
|
610
|
+
export declare class UrgencyHelper {
|
|
611
|
+
private static defaultConfig;
|
|
612
|
+
/**
|
|
613
|
+
* Get urgency color based on days remaining
|
|
614
|
+
* @param daysRemaining - Days until deadline (negative if overdue)
|
|
615
|
+
* @param config - Custom urgency thresholds
|
|
616
|
+
*/
|
|
617
|
+
static getUrgencyColor(daysRemaining: number | null | undefined, config?: UrgencyConfig): string;
|
|
618
|
+
/**
|
|
619
|
+
* Get urgency level based on days remaining
|
|
620
|
+
* @param daysRemaining - Days until deadline
|
|
621
|
+
* @param config - Custom urgency thresholds
|
|
622
|
+
*/
|
|
623
|
+
static getUrgencyLevel(daysRemaining: number | null | undefined, config?: UrgencyConfig): UrgencyLevel;
|
|
624
|
+
/**
|
|
625
|
+
* Get urgency text label
|
|
626
|
+
* @param daysRemaining - Days until deadline
|
|
627
|
+
* @param config - Custom urgency thresholds
|
|
628
|
+
* @param labels - Custom label text
|
|
629
|
+
*/
|
|
630
|
+
static getUrgencyText(daysRemaining: number | null | undefined, config?: UrgencyConfig, labels?: Partial<Record<UrgencyLevel, string>>): string;
|
|
631
|
+
/**
|
|
632
|
+
* Get background and text color for urgency badge
|
|
633
|
+
* @param daysRemaining - Days until deadline
|
|
634
|
+
* @param config - Custom urgency thresholds
|
|
635
|
+
*/
|
|
636
|
+
static getUrgencyBadgeColors(daysRemaining: number | null | undefined, config?: UrgencyConfig): {
|
|
637
|
+
bg: string;
|
|
638
|
+
text: string;
|
|
639
|
+
};
|
|
640
|
+
/**
|
|
641
|
+
* Format days remaining as display text
|
|
642
|
+
* @param daysRemaining - Days until deadline
|
|
643
|
+
*/
|
|
644
|
+
static formatDaysRemaining(daysRemaining: number | null | undefined): string;
|
|
645
|
+
/**
|
|
646
|
+
* Check if deadline is overdue
|
|
647
|
+
*/
|
|
648
|
+
static isOverdue(daysRemaining: number | null | undefined): boolean;
|
|
649
|
+
/**
|
|
650
|
+
* Check if deadline requires attention
|
|
651
|
+
*/
|
|
652
|
+
static needsAttention(daysRemaining: number | null | undefined, config?: UrgencyConfig): boolean;
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
/**
|
|
656
|
+
* Urgency level types
|
|
657
|
+
*/
|
|
658
|
+
export declare type UrgencyLevel = 'overdue' | 'critical' | 'urgent' | 'soon' | 'normal';
|
|
659
|
+
|
|
660
|
+
export { }
|