diff --git a/.gitignore b/.gitignore index b2a79076..009482e0 100644 --- a/.gitignore +++ b/.gitignore @@ -136,3 +136,10 @@ playwright-report/ test-results/ # Playwright MCP 세션 아티팩트(스냅샷/콘솔 로그) — 커밋 대상 아님 .playwright-mcp/ + +# ===== 소스맵 ===== +# 소스맵은 원본 코드 전문(sourcesContent)을 담고 있어 배포 대상이 아니다. +# 로컬 개발 빌드에서는 생성되지만(디버깅용) 저장소에는 유입되지 않는다. +# 배포용 빌드는 `--production` 이 G7_BUILD_SOURCEMAP=0 을 주입해 생성 자체를 막는다. +# 경로 한정 시 새 확장이 추가될 때 조용히 누락되므로 전역 규칙으로 둔다. +*.map diff --git a/CHANGELOG.md b/CHANGELOG.md index 41d8a957..7c4e9bcf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,9 +10,11 @@ - 서버가 외부 주소를 대신 호출하는 기능들에서, 사내 서버나 클라우드 관리 주소처럼 외부에 공개되지 않은 내부 주소를 호출 대상으로 지정할 수 없도록 차단했습니다. 언어팩을 주소로 내려받을 때, 예약 작업으로 주소를 호출할 때, 쇼핑몰 배송비를 외부 계산 서버로 문의할 때가 해당합니다. 사내 서버를 주기적으로 호출하는 등 내부 주소가 꼭 필요한 운영 환경을 위해, 환경설정 > 보안에 "내부 네트워크 주소 호출 허용" 항목을 새로 추가했습니다. 이 항목은 기본적으로 꺼져 있습니다. - 주소를 검사할 때 앞부분만 맞으면 통과시키던 방식을 실제 주소가 정확히 일치하는지 확인하도록 바로잡았습니다. 이전에는 신뢰하는 주소를 앞에 붙인 가짜 주소가 검사를 통과할 수 있었습니다. 본인인증 연동과 언어팩 업데이트 확인에 적용됩니다. (KISA 측에서 제보해주셨습니다 — KVE-2026-1546) +- 배포본에 원본 소스코드가 그대로 담긴 파일(소스맵)이 함께 포함되어, 누구나 주소만 알면 내려받아 볼 수 있던 문제를 차단했습니다. 이제 배포용으로 빌드하면 이 파일이 아예 만들어지지 않으며, 기존에 포함되어 있던 파일도 모두 제거했습니다. 아울러 이 파일이 서버에 남아 있더라도 웹으로는 내려받을 수 없도록 막았습니다. 내 컴퓨터에서 개발용으로 빌드할 때는 디버깅에 필요하므로 이전과 동일하게 생성됩니다. (#72 @glitter-gim 님께서 건의해주셨습니다.) ### Fixed +- 나이스페이먼츠 결제 플러그인을 켜 두면 홈, 게시판, 로그인 등 결제와 무관한 모든 페이지에서 주문서 조회 요청이 한 번씩 불필요하게 발생하던 문제를 수정했습니다. 장바구니가 없는 비회원에게는 이 요청이 매번 실패해 브라우저 오류로 기록됐습니다. 이제 결제 페이지에서만 요청합니다. - 설치 과정에서 임시로 만들어지는 파일에 관리자 비밀번호가 그대로 남을 수 있던 문제를 수정했습니다. 설치가 정상적으로 끝나면 이 파일은 지워지지만, 설치를 중간에 그만두거나 마지막 정리가 이뤄지지 않은 경우에는 남아 있을 수 있었습니다. 이제 비밀번호는 이 파일에 아예 기록되지 않으며, 설치에 꼭 필요한 동안에만 접근이 제한된 별도 파일에 보관했다가 관리자 계정이 만들어지는 즉시 지워집니다. 이미 설치를 마친 사이트도 업데이트하면 남아 있던 파일이 자동으로 정리됩니다. (sir.kr 커뮤니티의 코ppp 님께서 제보해주셨습니다.) - 화면 편집기를 작은 화면에서 열면 상단 도구 모음의 버튼 글자가 세로로 쪼개지는 등 화면이 찌그러지던 문제를 수정했습니다. 화면 편집기는 왼쪽 화면 목록과 미리보기 화면을 나란히 놓고 쓰는 도구라 넓은 화면이 필요합니다. 이제 창이 좁아도 편집기를 억지로 우겨넣지 않고 원래 크기를 유지하며, 모자란 만큼은 가로 스크롤로 넘겨 보면 됩니다. - 모바일에서 새로고침을 연속으로 눌렀을 때 간헐적으로 "초기화 실패" 또는 "페이지 로딩 실패" 화면이 뜨던 문제를 수정했습니다. 화면을 그리는 데 필요한 요청 하나가 회선 사정으로 잠깐 끊기면 그것만으로 페이지 전체가 뜨지 않았습니다. 이제 이런 일시적인 끊김은 자동으로 다시 시도해 복구하므로, 대부분의 경우 사용자는 아무것도 느끼지 못한 채 화면이 정상적으로 열립니다. diff --git a/app/Console/Commands/Core/BuildCoreCommand.php b/app/Console/Commands/Core/BuildCoreCommand.php index 700b227a..ae68a910 100644 --- a/app/Console/Commands/Core/BuildCoreCommand.php +++ b/app/Console/Commands/Core/BuildCoreCommand.php @@ -94,9 +94,12 @@ class BuildCoreCommand extends Command return $this->runWatchBundles($projectPath); } + // 3개 번들을 npm 스크립트로 순차 호출하므로 빌드 환경변수를 세 곳 모두에 주입한다. + $buildEnv = $this->buildEnv($productionMode); + // ── 1) 템플릿 엔진 번들 (편집기 코드 제외) ────────────────────────────── $this->info('🔨 코어 빌드 시작 (템플릿 엔진)'.($productionMode ? ' (프로덕션)' : '')); - $engineResult = $this->runNpmCommand(['npm', 'run', 'build:core'], $projectPath, true); + $engineResult = $this->runNpmCommand(['npm', 'run', 'build:core'], $projectPath, true, $buildEnv); if ($engineResult !== Command::SUCCESS) { return $engineResult; @@ -104,7 +107,7 @@ class BuildCoreCommand extends Command // ── 2) 레이아웃 편집기 번들 (lazy, /admin/layout-editor/* 진입 시 로드) ── $this->info('🔨 코어 빌드 시작 (레이아웃 편집기)'.($productionMode ? ' (프로덕션)' : '')); - $editorResult = $this->runNpmCommand(['npm', 'run', 'build:core-editor'], $projectPath, true); + $editorResult = $this->runNpmCommand(['npm', 'run', 'build:core-editor'], $projectPath, true, $buildEnv); if ($editorResult !== Command::SUCCESS) { $this->error('❌ 레이아웃 편집기 번들 빌드 실패'); @@ -114,7 +117,7 @@ class BuildCoreCommand extends Command // ── 3) DevTools 번들 (lazy, 디버그 모드에서만 로드) ── $this->info('🔨 코어 빌드 시작 (DevTools)'.($productionMode ? ' (프로덕션)' : '')); - $devtoolsResult = $this->runNpmCommand(['npm', 'run', 'build:core-devtools'], $projectPath, true); + $devtoolsResult = $this->runNpmCommand(['npm', 'run', 'build:core-devtools'], $projectPath, true, $buildEnv); if ($devtoolsResult !== Command::SUCCESS) { $this->error('❌ DevTools 번들 빌드 실패'); @@ -197,7 +200,13 @@ class BuildCoreCommand extends Command $this->info('🔨 코어 빌드 시작 (전체)'.($productionMode ? ' (프로덕션)' : '')); } - $result = $this->runNpmCommand($buildCommand, $projectPath, ! $watchMode); + // 감시 모드에는 소스맵 억제를 주입하지 않는다 — 개발 중 디버깅 필요 + $result = $this->runNpmCommand( + $buildCommand, + $projectPath, + ! $watchMode, + $watchMode ? [] : $this->buildEnv($productionMode) + ); if ($result === Command::SUCCESS && ! $watchMode) { $this->info('✅ 코어 빌드 완료 (전체)'); @@ -311,15 +320,30 @@ class BuildCoreCommand extends Command } } + /** + * 빌드 프로세스에 주입할 환경변수를 구성합니다. + * + * 프로덕션 빌드에서는 소스맵을 생성하지 않습니다. 배포 산출물에 원본 코드가 + * 포함되는 것을 막기 위함이며, 각 vite config 가 이 값을 읽습니다. + * + * @param bool $productionMode 프로덕션 빌드 여부 + * @return array Process 에 주입할 환경변수 + */ + private function buildEnv(bool $productionMode): array + { + return $productionMode ? ['G7_BUILD_SOURCEMAP' => '0'] : []; + } + /** * npm 명령 실행 * * @param array $command 실행할 명령 * @param string $cwd 작업 디렉토리 * @param bool $waitForCompletion 완료 대기 여부 + * @param array $env 추가로 주입할 환경변수 (부모 환경에 병합됨) * @return int 명령 실행 결과 코드 */ - private function runNpmCommand(array $command, string $cwd, bool $waitForCompletion = true): int + private function runNpmCommand(array $command, string $cwd, bool $waitForCompletion = true, array $env = []): int { // Windows 환경에서는 cmd /c 사용 if (PHP_OS_FAMILY === 'Windows') { @@ -328,6 +352,11 @@ class BuildCoreCommand extends Command $process = new Process($command); $process->setWorkingDirectory($cwd); + + // Symfony Process 는 지정한 env 를 부모 환경에 병합하므로(PATH 등 유지) 추가분만 넘긴다. + if ($env !== []) { + $process->setEnv($env); + } $process->setTimeout(null); // 타임아웃 없음 if ($waitForCompletion) { diff --git a/app/Console/Commands/Module/BuildModuleCommand.php b/app/Console/Commands/Module/BuildModuleCommand.php index 24ab4c56..43ed1e00 100644 --- a/app/Console/Commands/Module/BuildModuleCommand.php +++ b/app/Console/Commands/Module/BuildModuleCommand.php @@ -189,8 +189,13 @@ class BuildModuleCommand extends Command $this->info("🔨 빌드 시작: {$identifier}".($productionMode ? ' (프로덕션)' : '')); } - // 빌드 실행 - $result = $this->runNpmCommand($buildCommand, $buildPath, ! $watchMode); + // 빌드 실행 (감시 모드에는 소스맵 억제를 주입하지 않는다 — 개발 중 디버깅 필요) + $result = $this->runNpmCommand( + $buildCommand, + $buildPath, + ! $watchMode, + $watchMode ? [] : $this->buildEnv($productionMode) + ); if ($result === Command::SUCCESS && ! $watchMode) { $this->info("✅ 빌드 완료: {$identifier}"); @@ -338,15 +343,30 @@ class BuildModuleCommand extends Command return $failCount > 0 ? Command::FAILURE : Command::SUCCESS; } + /** + * 빌드 프로세스에 주입할 환경변수를 구성합니다. + * + * 프로덕션 빌드에서는 소스맵을 생성하지 않습니다. 배포 산출물에 원본 코드가 + * 포함되는 것을 막기 위함이며, 각 vite config 가 이 값을 읽습니다. + * + * @param bool $productionMode 프로덕션 빌드 여부 + * @return array Process 에 주입할 환경변수 + */ + private function buildEnv(bool $productionMode): array + { + return $productionMode ? ['G7_BUILD_SOURCEMAP' => '0'] : []; + } + /** * npm 명령 실행 * * @param array $command 실행할 명령 * @param string $cwd 작업 디렉토리 * @param bool $waitForCompletion 완료 대기 여부 + * @param array $env 추가로 주입할 환경변수 (부모 환경에 병합됨) * @return int 명령 실행 결과 코드 */ - private function runNpmCommand(array $command, string $cwd, bool $waitForCompletion = true): int + private function runNpmCommand(array $command, string $cwd, bool $waitForCompletion = true, array $env = []): int { // Windows 환경에서는 cmd /c 사용 if (PHP_OS_FAMILY === 'Windows') { @@ -355,6 +375,11 @@ class BuildModuleCommand extends Command $process = new Process($command); $process->setWorkingDirectory($cwd); + + // Symfony Process 는 지정한 env 를 부모 환경에 병합하므로(PATH 등 유지) 추가분만 넘긴다. + if ($env !== []) { + $process->setEnv($env); + } $process->setTimeout(null); // 타임아웃 없음 if ($waitForCompletion) { diff --git a/app/Console/Commands/Plugin/BuildPluginCommand.php b/app/Console/Commands/Plugin/BuildPluginCommand.php index 2eb1bf82..b0704d45 100644 --- a/app/Console/Commands/Plugin/BuildPluginCommand.php +++ b/app/Console/Commands/Plugin/BuildPluginCommand.php @@ -189,8 +189,13 @@ class BuildPluginCommand extends Command $this->info("🔨 빌드 시작: {$identifier}".($productionMode ? ' (프로덕션)' : '')); } - // 빌드 실행 - $result = $this->runNpmCommand($buildCommand, $buildPath, ! $watchMode); + // 빌드 실행 (감시 모드에는 소스맵 억제를 주입하지 않는다 — 개발 중 디버깅 필요) + $result = $this->runNpmCommand( + $buildCommand, + $buildPath, + ! $watchMode, + $watchMode ? [] : $this->buildEnv($productionMode) + ); if ($result === Command::SUCCESS && ! $watchMode) { $this->info("✅ 빌드 완료: {$identifier}"); @@ -338,15 +343,30 @@ class BuildPluginCommand extends Command return $failCount > 0 ? Command::FAILURE : Command::SUCCESS; } + /** + * 빌드 프로세스에 주입할 환경변수를 구성합니다. + * + * 프로덕션 빌드에서는 소스맵을 생성하지 않습니다. 배포 산출물에 원본 코드가 + * 포함되는 것을 막기 위함이며, 각 vite config 가 이 값을 읽습니다. + * + * @param bool $productionMode 프로덕션 빌드 여부 + * @return array Process 에 주입할 환경변수 + */ + private function buildEnv(bool $productionMode): array + { + return $productionMode ? ['G7_BUILD_SOURCEMAP' => '0'] : []; + } + /** * npm 명령 실행 * * @param array $command 실행할 명령 * @param string $cwd 작업 디렉토리 * @param bool $waitForCompletion 완료 대기 여부 + * @param array $env 추가로 주입할 환경변수 (부모 환경에 병합됨) * @return int 명령 실행 결과 코드 */ - private function runNpmCommand(array $command, string $cwd, bool $waitForCompletion = true): int + private function runNpmCommand(array $command, string $cwd, bool $waitForCompletion = true, array $env = []): int { // Windows 환경에서는 cmd /c 사용 if (PHP_OS_FAMILY === 'Windows') { @@ -355,6 +375,11 @@ class BuildPluginCommand extends Command $process = new Process($command); $process->setWorkingDirectory($cwd); + + // Symfony Process 는 지정한 env 를 부모 환경에 병합하므로(PATH 등 유지) 추가분만 넘긴다. + if ($env !== []) { + $process->setEnv($env); + } $process->setTimeout(null); // 타임아웃 없음 if ($waitForCompletion) { diff --git a/app/Console/Commands/Template/BuildTemplateCommand.php b/app/Console/Commands/Template/BuildTemplateCommand.php index 45230faf..dd0f89da 100644 --- a/app/Console/Commands/Template/BuildTemplateCommand.php +++ b/app/Console/Commands/Template/BuildTemplateCommand.php @@ -181,8 +181,13 @@ class BuildTemplateCommand extends Command $this->info("🔨 빌드 시작: {$identifier}".($productionMode ? ' (프로덕션)' : '')); } - // 빌드 실행 - $result = $this->runNpmCommand($buildCommand, $buildPath, ! $watchMode); + // 빌드 실행 (감시 모드에는 소스맵 억제를 주입하지 않는다 — 개발 중 디버깅 필요) + $result = $this->runNpmCommand( + $buildCommand, + $buildPath, + ! $watchMode, + $watchMode ? [] : $this->buildEnv($productionMode) + ); if ($result === Command::SUCCESS && ! $watchMode) { $this->info("✅ 빌드 완료: {$identifier}"); @@ -285,15 +290,30 @@ class BuildTemplateCommand extends Command return $failCount > 0 ? Command::FAILURE : Command::SUCCESS; } + /** + * 빌드 프로세스에 주입할 환경변수를 구성합니다. + * + * 프로덕션 빌드에서는 소스맵을 생성하지 않습니다. 배포 산출물에 원본 코드가 + * 포함되는 것을 막기 위함이며, 각 vite config 가 이 값을 읽습니다. + * + * @param bool $productionMode 프로덕션 빌드 여부 + * @return array Process 에 주입할 환경변수 + */ + private function buildEnv(bool $productionMode): array + { + return $productionMode ? ['G7_BUILD_SOURCEMAP' => '0'] : []; + } + /** * npm 명령 실행 * * @param array $command 실행할 명령 * @param string $cwd 작업 디렉토리 * @param bool $waitForCompletion 완료 대기 여부 + * @param array $env 추가로 주입할 환경변수 (부모 환경에 병합됨) * @return int 명령 실행 결과 코드 */ - private function runNpmCommand(array $command, string $cwd, bool $waitForCompletion = true): int + private function runNpmCommand(array $command, string $cwd, bool $waitForCompletion = true, array $env = []): int { // Windows 환경에서는 cmd /c 사용 if (PHP_OS_FAMILY === 'Windows') { @@ -302,6 +322,11 @@ class BuildTemplateCommand extends Command $process = new Process($command); $process->setWorkingDirectory($cwd); + + // Symfony Process 는 지정한 env 를 부모 환경에 병합하므로(PATH 등 유지) 추가분만 넘긴다. + if ($env !== []) { + $process->setEnv($env); + } $process->setTimeout(null); // 타임아웃 없음 if ($waitForCompletion) { diff --git a/app/Rules/AllowedModuleFileType.php b/app/Rules/AllowedModuleFileType.php index 88f6b1cc..f69fd3bd 100644 --- a/app/Rules/AllowedModuleFileType.php +++ b/app/Rules/AllowedModuleFileType.php @@ -20,9 +20,6 @@ class AllowedModuleFileType implements ValidationRule // Data 'json', - // Source Maps - 'map', - // Images 'png', 'jpg', 'jpeg', 'svg', 'webp', 'gif', 'ico', diff --git a/app/Rules/AllowedPluginFileType.php b/app/Rules/AllowedPluginFileType.php index 38f2a346..0ced5c11 100644 --- a/app/Rules/AllowedPluginFileType.php +++ b/app/Rules/AllowedPluginFileType.php @@ -20,9 +20,6 @@ class AllowedPluginFileType implements ValidationRule // Data 'json', - // Source Maps - 'map', - // Images 'png', 'jpg', 'jpeg', 'svg', 'webp', 'gif', 'ico', diff --git a/app/Services/ExtensionBundleService.php b/app/Services/ExtensionBundleService.php index 0125ff39..11969719 100644 --- a/app/Services/ExtensionBundleService.php +++ b/app/Services/ExtensionBundleService.php @@ -166,6 +166,7 @@ class ExtensionBundleService public function buildCssBundle(string $type): string { $ordered = $this->getOrderedGlobalAssetPaths($type); + $isProduction = app()->environment('production'); $segments = []; foreach ($ordered as $identifier => $paths) { @@ -197,7 +198,7 @@ class ExtensionBundleService continue; } - $segments[] = $content; + $segments[] = $this->processCssSourceMap($content, $isProduction); } catch (\Throwable $e) { Log::warning('확장 CSS 번들 병합 중 오류, 해당 확장 skip', [ 'type' => $type, @@ -417,6 +418,29 @@ class ExtensionBundleService }, $content) ?? $content; } + /** + * CSS 소스맵 주석을 환경에 맞게 처리합니다. + * + * CSS 는 JS 와 주석 문법이 달라 소스맵 참조를 블록 주석으로 표기하므로 + * processJsSourceMap() 의 `//#` 패턴으로는 검출되지 않는다. + * + * prod: 주석 strip(맵 참조 제거). dev: 원본 유지. + * 병합 번들에서는 개별 맵 URL 이 어차피 어긋나므로 dev rewrite 는 하지 않는다. + * + * @param string $content 원본 CSS 내용 + * @param bool $isProduction 프로덕션 여부 + * @return string 처리된 내용 + */ + private function processCssSourceMap(string $content, bool $isProduction): string + { + if (! $isProduction) { + return $content; + } + + // 구분자로 `~` 사용 — 패턴에 `/`, `#` 가 포함됨 + return preg_replace('~/\*#\s*sourceMappingURL=\S+?\s*\*/~', '', $content) ?? $content; + } + /** * CSS 내용에 상대경로 url() 참조가 있는지 확인합니다. * diff --git a/app/Services/ModuleService.php b/app/Services/ModuleService.php index a5f5a67d..569ec88d 100644 --- a/app/Services/ModuleService.php +++ b/app/Services/ModuleService.php @@ -1043,7 +1043,6 @@ class ModuleService 'mjs' => 'application/javascript', 'css' => 'text/css', 'json' => 'application/json', - 'map' => 'application/json', 'png' => 'image/png', 'jpg' => 'image/jpeg', 'jpeg' => 'image/jpeg', diff --git a/app/Services/PluginService.php b/app/Services/PluginService.php index 3ab92f20..2139aa24 100644 --- a/app/Services/PluginService.php +++ b/app/Services/PluginService.php @@ -708,7 +708,6 @@ class PluginService 'mjs' => 'application/javascript', 'css' => 'text/css', 'json' => 'application/json', - 'map' => 'application/json', 'png' => 'image/png', 'jpg' => 'image/jpeg', 'jpeg' => 'image/jpeg', diff --git a/lang-packs/_bundled/g7-template-sirsoft-admin_basic-ja/CHANGELOG.md b/lang-packs/_bundled/g7-template-sirsoft-admin_basic-ja/CHANGELOG.md index 5d9c5be8..7027fd90 100644 --- a/lang-packs/_bundled/g7-template-sirsoft-admin_basic-ja/CHANGELOG.md +++ b/lang-packs/_bundled/g7-template-sirsoft-admin_basic-ja/CHANGELOG.md @@ -9,6 +9,7 @@ ### Added - 환경설정 > 보안 화면의 "내부 네트워크 주소 호출 허용" 항목명·설명 일본어 번역 추가 — 새로 추가된 보안 설정이 일본어 로케일에서도 올바르게 표시됩니다. +- 역할 관리 화면 새로고침 버튼의 일본어 번역 추가 — 일본어 로케일에서도 버튼 설명이 올바르게 표시됩니다. ## [1.0.2] - 2026-07-10 diff --git a/lang-packs/_bundled/g7-template-sirsoft-admin_basic-ja/frontend/partial/admin.json b/lang-packs/_bundled/g7-template-sirsoft-admin_basic-ja/frontend/partial/admin.json index 9d90d5a1..293e14e6 100644 --- a/lang-packs/_bundled/g7-template-sirsoft-admin_basic-ja/frontend/partial/admin.json +++ b/lang-packs/_bundled/g7-template-sirsoft-admin_basic-ja/frontend/partial/admin.json @@ -1089,7 +1089,8 @@ "errors": { "system_role_delete": "システムロールは削除できません。", "delete_failed": "ロール削除に失敗しました。" - } + }, + "refresh": "更新" }, "schedules": { "page_title": "スケジュール管理", diff --git a/modules/_bundled/sirsoft-board/CHANGELOG.md b/modules/_bundled/sirsoft-board/CHANGELOG.md index fe05970c..9369166f 100644 --- a/modules/_bundled/sirsoft-board/CHANGELOG.md +++ b/modules/_bundled/sirsoft-board/CHANGELOG.md @@ -4,6 +4,12 @@ 형식은 [Keep a Changelog](https://keepachangelog.com/ko/1.1.0/)를 따르며, [Semantic Versioning](https://semver.org/lang/ko/)을 준수합니다. +## [1.0.2] - 2026-07-14 + +### Fixed + +- 게시판 설정 화면의 상단 고정 영역이 좌우 여백과 어긋나 내용이 가로로 밀려 보일 수 있던 문제를 수정했습니다. + ## [1.0.1] - 2026-07-08 ### Added diff --git a/modules/_bundled/sirsoft-board/composer.json b/modules/_bundled/sirsoft-board/composer.json index 54998a9d..a59b9a81 100644 --- a/modules/_bundled/sirsoft-board/composer.json +++ b/modules/_bundled/sirsoft-board/composer.json @@ -2,7 +2,7 @@ "name": "modules/sirsoft-board", "description": "Board module for Gnuboard7", "type": "library", - "version": "1.0.1", + "version": "1.0.2", "license": "MIT", "autoload": { "psr-4": { diff --git a/modules/_bundled/sirsoft-board/module.json b/modules/_bundled/sirsoft-board/module.json index fc04ca96..1476f801 100644 --- a/modules/_bundled/sirsoft-board/module.json +++ b/modules/_bundled/sirsoft-board/module.json @@ -5,7 +5,7 @@ "ko": "게시판", "en": "Board" }, - "version": "1.0.1", + "version": "1.0.2", "license": "MIT", "description": { "ko": "게시판 관리를 위한 모듈", diff --git a/modules/_bundled/sirsoft-board/package-lock.json b/modules/_bundled/sirsoft-board/package-lock.json index 9b93fb75..1f256d45 100644 --- a/modules/_bundled/sirsoft-board/package-lock.json +++ b/modules/_bundled/sirsoft-board/package-lock.json @@ -1,12 +1,12 @@ { "name": "@g7/sirsoft-board", - "version": "1.0.1", + "version": "1.0.2", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@g7/sirsoft-board", - "version": "1.0.1", + "version": "1.0.2", "devDependencies": { "jsdom": "^27.4.0", "typescript": "^5.3.3", diff --git a/modules/_bundled/sirsoft-board/package.json b/modules/_bundled/sirsoft-board/package.json index 188f06a2..e063df2d 100644 --- a/modules/_bundled/sirsoft-board/package.json +++ b/modules/_bundled/sirsoft-board/package.json @@ -1,6 +1,6 @@ { "name": "@g7/sirsoft-board", - "version": "1.0.1", + "version": "1.0.2", "description": "그누보드7 게시판 모듈 프론트엔드 에셋", "private": true, "type": "module", diff --git a/modules/_bundled/sirsoft-board/resources/extensions/admin-ecommerce-inquiry-settings.json b/modules/_bundled/sirsoft-board/resources/extensions/admin-ecommerce-inquiry-settings.json index fcabd630..17069bd9 100644 --- a/modules/_bundled/sirsoft-board/resources/extensions/admin-ecommerce-inquiry-settings.json +++ b/modules/_bundled/sirsoft-board/resources/extensions/admin-ecommerce-inquiry-settings.json @@ -6,6 +6,7 @@ "type": "api", "endpoint": "/api/modules/sirsoft-board/admin/boards", "method": "GET", + "if": "{{(query.tab || _global.activeEcommerceSettingsTab || 'basic_info') === 'basic_info'}}", "auto_fetch": true, "auth_required": true, "params": { diff --git a/modules/_bundled/sirsoft-board/resources/js/__tests__/layouts/admin-board-settings-tab-scoped-datasources.test.tsx b/modules/_bundled/sirsoft-board/resources/js/__tests__/layouts/admin-board-settings-tab-scoped-datasources.test.tsx new file mode 100644 index 00000000..b3a26798 --- /dev/null +++ b/modules/_bundled/sirsoft-board/resources/js/__tests__/layouts/admin-board-settings-tab-scoped-datasources.test.tsx @@ -0,0 +1,89 @@ +/** + * @file admin-board-settings-tab-scoped-datasources.test.tsx + * @description 게시판 설정 데이터소스 탭 스코프 게이팅 회귀 가드 + * + * 이커머스 환경설정과 동일 결함: 진입 시 전 탭의 데이터소스를 한꺼번에 fetch 했다. + * 탭 본문은 `if` 로 게이트되어 비활성 탭은 렌더되지 않으므로 그 데이터는 아무도 읽지 않는다. + * 엔진(DataSourceManager.filterByCondition + TemplateApp.updateQueryParams)이 + * 데이터소스 `if` 로 탭별 로딩을 지원하므로, 탭 전용 소스는 `if` 게이트를 가져야 한다. + */ + +import { describe, it, expect } from 'vitest'; + +const layout = require('../../../layouts/admin/admin_board_settings.json'); + +interface AnyJson { [k: string]: any } + +const SHARED_SOURCE_IDS = ['settings']; + +const TAB_SCOPED: Record = { + boards_list: ['basic_defaults'], + roles: ['basic_defaults', 'report_policy'], + board_types: ['basic_defaults'], + boardNotificationDefinitions: ['notification_definitions', 'report_policy'], + availableChannels: ['notification_definitions'], + boardIdentityPolicies: ['identity_policies'], + boardIdentityPurposes: ['identity_policies'], + identityProviders: ['identity_policies'], +}; + +const sources: AnyJson[] = layout.data_sources; +const byId = (id: string) => sources.find(s => s.id === id); + +describe('admin_board_settings — 데이터소스 탭 스코프 게이팅', () => { + it('공유 소스(settings)는 if 게이트 없이 항상 fetch 된다', () => { + for (const id of SHARED_SOURCE_IDS) { + const s = byId(id); + expect(s).toBeTruthy(); + expect(s!.if).toBeUndefined(); + expect(s!.auto_fetch).not.toBe(false); + } + }); + + it.each(Object.keys(TAB_SCOPED))('탭 전용 소스 %s 는 if 게이트를 가진다', (id) => { + const s = byId(id); + expect(s).toBeTruthy(); + expect(typeof s!.if).toBe('string'); + }); + + it.each(Object.entries(TAB_SCOPED))('%s 의 if 가 소유 탭(%s)을 참조한다', (id, tabs) => { + const cond: string = byId(id)!.if; + for (const tab of tabs as string[]) { + expect(cond).toContain(`'${tab}'`); + } + expect(cond).toContain('query.tab'); + expect(cond).toContain('activeBoardSettingsTab'); + }); + + it('탭 전용 소스를 auto_fetch:false 로 죽여두지 않는다', () => { + for (const id of Object.keys(TAB_SCOPED)) { + expect(byId(id)!.auto_fetch).not.toBe(false); + } + }); + + it('탭 전환은 navigate + replace:true 로 수행한다 (if 재평가 필요)', () => { + const seqs: AnyJson[] = []; + const walk = (n: any) => { + if (!n || typeof n !== 'object') return; + if (Array.isArray(n)) { n.forEach(walk); return; } + if (n.handler === 'sequence' && Array.isArray(n.params?.actions)) seqs.push(n); + Object.values(n).forEach(walk); + }; + walk(layout); + + const tabSwitches = seqs.filter(s => + (s.params.actions as AnyJson[]).some( + a => a?.handler === 'setState' && 'activeBoardSettingsTab' in (a.params ?? {}) + ) + ); + expect(tabSwitches.length).toBeGreaterThan(0); + + for (const seq of tabSwitches) { + const acts = seq.params.actions as AnyJson[]; + expect(acts.some(a => a.handler === 'replaceUrl')).toBe(false); + const nav = acts.find(a => a.handler === 'navigate'); + expect(nav).toBeTruthy(); + expect(nav!.params.replace).toBe(true); + } + }); +}); diff --git a/modules/_bundled/sirsoft-board/resources/js/__tests__/layouts/admin-board-settings.test.tsx b/modules/_bundled/sirsoft-board/resources/js/__tests__/layouts/admin-board-settings.test.tsx index d5ce0ef0..9e8f02a1 100644 --- a/modules/_bundled/sirsoft-board/resources/js/__tests__/layouts/admin-board-settings.test.tsx +++ b/modules/_bundled/sirsoft-board/resources/js/__tests__/layouts/admin-board-settings.test.tsx @@ -304,8 +304,11 @@ describe('admin_board_settings.json - 메인 레이아웃', () => { expect(innerActions[1].handler).toBe('setState'); expect(innerActions[1].params.target).toBe('global'); - // replaceUrl - URL 업데이트 - expect(innerActions[2].handler).toBe('replaceUrl'); + // navigate(replace:true) - URL 업데이트 + 데이터소스 `if` 재평가 + // replaceUrl 은 history 만 교체하므로 탭 전용 데이터소스가 로드되지 않는다. + // 가드: admin-board-settings-tab-scoped-datasources.test.tsx + expect(innerActions[2].handler).toBe('navigate'); + expect(innerActions[2].params.replace).toBe(true); expect(innerActions[2].params.query.tab).toBeDefined(); // scrollIntoView - 탭 변경 후 본문 상단으로 스크롤 (#369) diff --git a/modules/_bundled/sirsoft-board/resources/layouts/admin/admin_board_settings.json b/modules/_bundled/sirsoft-board/resources/layouts/admin/admin_board_settings.json index aa3347b4..d0f09a00 100644 --- a/modules/_bundled/sirsoft-board/resources/layouts/admin/admin_board_settings.json +++ b/modules/_bundled/sirsoft-board/resources/layouts/admin/admin_board_settings.json @@ -80,6 +80,7 @@ "type": "api", "endpoint": "/api/modules/sirsoft-board/admin/boards?per_page=999&fields=id,title", "method": "GET", + "if": "{{(query.tab || _global.activeBoardSettingsTab || 'general') === 'basic_defaults'}}", "auto_fetch": true, "auth_required": true, "fallback": { @@ -94,6 +95,7 @@ "type": "api", "endpoint": "/api/admin/roles/active", "method": "GET", + "if": "{{['basic_defaults', 'report_policy'].includes(query.tab || _global.activeBoardSettingsTab || 'general')}}", "auto_fetch": true, "auth_required": true, "fallback": { @@ -106,6 +108,7 @@ "type": "api", "endpoint": "/api/modules/sirsoft-board/admin/board-types", "method": "GET", + "if": "{{(query.tab || _global.activeBoardSettingsTab || 'general') === 'basic_defaults'}}", "auto_fetch": true, "auth_required": true, "fallback": { @@ -118,6 +121,7 @@ "type": "api", "endpoint": "/api/admin/notification-definitions?extension_type=module&extension_identifier=sirsoft-board", "method": "GET", + "if": "{{['notification_definitions', 'report_policy'].includes(query.tab || _global.activeBoardSettingsTab || 'general')}}", "auto_fetch": true, "auth_required": true, "params": { @@ -145,6 +149,7 @@ "type": "api", "endpoint": "/api/admin/notification-channels", "method": "GET", + "if": "{{(query.tab || _global.activeBoardSettingsTab || 'general') === 'notification_definitions'}}", "auto_fetch": true, "auth_required": true, "fallback": { @@ -159,6 +164,7 @@ "type": "api", "endpoint": "/api/admin/identity/policies", "method": "GET", + "if": "{{(query.tab || _global.activeBoardSettingsTab || 'general') === 'identity_policies'}}", "auto_fetch": true, "auth_required": true, "params": { @@ -182,6 +188,7 @@ "type": "api", "endpoint": "/api/identity/purposes", "method": "GET", + "if": "{{(query.tab || _global.activeBoardSettingsTab || 'general') === 'identity_policies'}}", "auto_fetch": true, "auth_required": true, "fallback": { @@ -194,6 +201,7 @@ "type": "api", "endpoint": "/api/admin/identity/providers", "method": "GET", + "if": "{{(query.tab || _global.activeBoardSettingsTab || 'general') === 'identity_policies'}}", "auto_fetch": true, "auth_required": true, "fallback": { @@ -243,7 +251,7 @@ "type": "basic", "name": "Div", "props": { - "className": "sticky-tab-nav-responsive" + "className": "sticky-section-header" }, "children": [ { @@ -339,11 +347,13 @@ } }, { - "handler": "replaceUrl", + "handler": "navigate", "params": { + "path": "/admin/boards/settings", "query": { "tab": "{{$args[0]}}" - } + }, + "replace": true } }, { diff --git a/modules/_bundled/sirsoft-ecommerce/components.json b/modules/_bundled/sirsoft-ecommerce/components.json index 9709eddc..40b4cba8 100644 --- a/modules/_bundled/sirsoft-ecommerce/components.json +++ b/modules/_bundled/sirsoft-ecommerce/components.json @@ -1,7 +1,7 @@ { "$schema": "https://json-schema.org/draft/2020-12/schema", "identifier": "sirsoft-ecommerce", - "version": "1.0.0-beta.5", + "version": "1.0.3", "components": { "basic": [], "composite": [], diff --git a/modules/_bundled/sirsoft-ecommerce/dist/js/module.iife.js b/modules/_bundled/sirsoft-ecommerce/dist/js/module.iife.js index 3509466a..00640ee4 100644 --- a/modules/_bundled/sirsoft-ecommerce/dist/js/module.iife.js +++ b/modules/_bundled/sirsoft-ecommerce/dist/js/module.iife.js @@ -1,2 +1 @@ var SirsoftEcommerce=function(xe){"use strict";const gt={KRW:0,JPY:0},_t={KRW:"₩",USD:"$",JPY:"¥",CNY:"元",EUR:"€",GBP:"£"},wt={KRW:1e3,JPY:100};function yt(r){if(!r)return 1;const c=r.base_unit??wt[r.code]??1;return Math.max(1,c)}function ht(r,c,e){const t=parseFloat(c)||1;if(t<=0)return r;const o=r/t;let a;switch(e){case"ceil":a=Math.ceil(o);break;case"floor":a=Math.floor(o);break;default:a=Math.round(o)}return a*t}function he(r,c){const e=typeof c=="string"?{code:c}:c,t=e.code,o=e.symbol&&e.symbol.length>0?e.symbol:_t[t]??"",a=e.decimal_places??gt[t]??2,n=r.toLocaleString(void 0,{minimumFractionDigits:a,maximumFractionDigits:a});return o?t==="KRW"||o==="₩"||o==="원"?n+"원":o+n:n+" "+t}function ae(r,c){const{basePrice:e,currencies:t}=r,o=parseFloat(String(e))||0;if(!t||!Array.isArray(t)||t.length===0)return{};const a=t.find(i=>i.is_default),n=yt(a),s={};for(const i of t){const l=i.code;if(i.is_default??!1)s[l]={price:o,formatted:he(o,i),is_default:!0,editable:!0};else if(i.exchange_rate&&i.exchange_rate>0){const d=o/n*i.exchange_rate,m=ht(d,i.rounding_unit||"0.01",i.rounding_method||"round");s[l]={price:m,formatted:he(m,i),is_default:!1,editable:!1,exchange_rate:i.exchange_rate}}}return s}const z=window.G7Core?.createLogger?.("Ecom:UpdateProduct")??{log:(...r)=>console.log("[Ecom:UpdateProduct]",...r),warn:(...r)=>console.warn("[Ecom:UpdateProduct]",...r),error:(...r)=>console.error("[Ecom:UpdateProduct]",...r)};function Ct(r,c){const e=r.params||{},{productId:t,field:o,dataSourceId:a="products"}=e;let{value:n}=e;if(n&&typeof n=="object"&&n.target!==void 0&&(n=n.target?.value,z.log("[updateProductField] Extracted value from event:",n)),!t||!o){z.warn("[updateProductField] Missing required params:",{productId:t,field:o});return}if(n===void 0){z.warn("[updateProductField] Value is undefined, skipping update");return}const s=window.G7Core;if(!s?.dataSource?.get||!s?.dataSource?.set){z.warn("[updateProductField] G7Core.dataSource API is not available");return}const i=s.dataSource.get(a);if(!i){console.warn(`[updateProductField] DataSource '${a}' not found`);return}const l=i.data?.data||[];if(!Array.isArray(l)||l.length===0){z.warn("[updateProductField] Products data is empty or invalid");return}const u=s.state.get()||{},d=u.modules?.["sirsoft-ecommerce"]?.language_currency?.currencies;let g=[...u._local?.productFieldErrors||[]];const f=l.map(C=>{if(String(C.id)===String(t)){const A=C[o];if(typeof n=="object"&&n!==null?JSON.stringify(A)===JSON.stringify(n):String(A)===String(n))return z.log(`[updateProductField] No change for product ${t}.${o}, skipping`),C;let L=n;const R=parseFloat(n)||0;if(o==="selling_price"){const F=parseFloat(C.list_price)||0;F>0&&R>F?(g.some(oe=>oe.productId===String(t)&&oe.field===o)||g.push({productId:String(t),field:"selling_price",message:"판매가는 정가를 초과할 수 없습니다."}),L=F):g=g.filter($=>!($.productId===String(t)&&$.field===o))}else g=g.filter(F=>!(F.productId===String(t)&&F.field===o));const x={...C,[o]:L,_modified:!0};return o==="selling_price"&&d&&Array.isArray(d)&&(x.multi_currency_selling_price=ae({basePrice:L,currencies:d}),z.log(`[updateProductField] Recalculated multi_currency_selling_price for product ${t}`)),x}return C});s.dataSource.set(a,{...i,data:{...i.data,data:f}});const w=s.state.getLocal()||{},y=new Set(w.modifiedProductIds||[]);y.add(t);const _={...w.modifiedProductFields||{}},p=String(t),S=new Set(_[p]||[]);S.add(o),_[p]=Array.from(S),s.state.setLocal({modifiedProductIds:Array.from(y),modifiedProductFields:_,productFieldErrors:g}),z.log(`[updateProductField] Updated product ${t}.${o} =`,n)}const K=window.G7Core?.createLogger?.("Ecom:UpdateOption")??{log:(...r)=>console.log("[Ecom:UpdateOption]",...r),warn:(...r)=>console.warn("[Ecom:UpdateOption]",...r),error:(...r)=>console.error("[Ecom:UpdateOption]",...r)};function bt(r,c){const e=r.params||{},{productId:t,optionId:o,field:a,value:n,dataSourceId:s="products"}=e;if(!t||!o||!a){K.warn("[updateOptionField] Missing required params:",{productId:t,optionId:o,field:a});return}const i=window.G7Core;if(!i?.dataSource?.get||!i?.dataSource?.set){K.warn("[updateOptionField] G7Core.dataSource API is not available");return}const l=i.dataSource.get(s);if(!l){console.warn(`[updateOptionField] DataSource '${s}' not found`);return}const u=l.data?.data||[];if(!Array.isArray(u)||u.length===0){K.warn("[updateOptionField] Products data is empty or invalid");return}const d=i.state.get()||{},m=d.modules?.["sirsoft-ecommerce"]?.language_currency?.currencies;let f=[...d._local?.optionFieldErrors||[]],w=!1;const y=u.map(L=>{if(String(L.id)===String(t)){const R=L.options||[],x=R.findIndex(D=>String(D.id)===String(o));if(x===-1)return K.warn("[updateOptionField] Option not found:",o),L;const F=R[x],$=F[a];if(typeof n=="object"&&n!==null?JSON.stringify($)===JSON.stringify(n):String($)===String(n))return K.log(`[updateOptionField] No change for option ${o}.${a}, skipping`),L;let le=n;const we=parseFloat(n)||0;if(a==="selling_price"){const D=parseFloat(F.list_price)||0;D>0&&we>D?(f.some(ye=>ye.productId===String(t)&&ye.optionId===String(o)&&ye.field===a)||f.push({productId:String(t),optionId:String(o),field:"selling_price",message:"판매가는 정가를 초과할 수 없습니다."}),le=D):f=f.filter(H=>!(H.productId===String(t)&&H.optionId===String(o)&&H.field===a))}else f=f.filter(D=>!(D.productId===String(t)&&D.optionId===String(o)&&D.field===a));const B=[...R],re={...B[x],[a]:le,_modified:!0};let ne=null;if(a==="selling_price"){const D=parseFloat(le)||0;if(F.is_default===!0)ne=D,re.price_adjustment=0;else{const H=parseFloat(L.selling_price)||0;re.price_adjustment=D-H}re.price_adjustment_formatted=(re.price_adjustment>=0?"+":"")+re.price_adjustment.toLocaleString()+"원"}if(a==="selling_price"&&m&&Array.isArray(m)&&(re.multi_currency_selling_price=ae({basePrice:le,currencies:m}),K.log(`[updateOptionField] Recalculated multi_currency_selling_price for option ${o}`)),B[x]=re,ne!==null)for(let D=0;DH.is_active===!1?D:D+(parseInt(H.stock_quantity,10)||0),0),He=qe,K.log(`[updateOptionField] Updated product stock to option sum: ${He}`));const Ue={...L,options:B,stock_quantity:He,option_stock_sum:qe,_modified:!0};return ne!==null&&(Ue.selling_price=ne,m&&Array.isArray(m)&&(Ue.multi_currency_selling_price=ae({basePrice:ne,currencies:m})),w=!0,K.log(`[updateOptionField] Reverse-synced product ${t} selling_price to ${ne} from default option`)),Ue}return L});i.dataSource.set(s,{...l,data:{...l.data,data:y}});const _=i.state.getLocal()||{},p=new Set(_.modifiedOptionIds||[]),S=`${t}-${o}`;p.add(S);const C={..._.modifiedOptionFields||{}},A=new Set(C[S]||[]);A.add(a),C[S]=Array.from(A);const b={modifiedOptionIds:Array.from(p),modifiedOptionFields:C,optionFieldErrors:f};if(w){const L=new Set(_.modifiedProductIds||[]);L.add(t);const R={..._.modifiedProductFields||{}},x=String(t),F=new Set(R[x]||[]);F.add("selling_price"),R[x]=Array.from(F),b.modifiedProductIds=Array.from(L),b.modifiedProductFields=R}i.state.setLocal(b),K.log(`[updateOptionField] Updated option ${t}/${o}.${a} =`,n)}const It="g7_preferred_currency",St="g7_locale",Ve=window.G7Core?.createLogger?.("Handler:InitPreferredCurrency")??{log:(...r)=>console.log("[Handler:InitPreferredCurrency]",...r),warn:(...r)=>console.warn("[Handler:InitPreferredCurrency]",...r),error:(...r)=>console.error("[Handler:InitPreferredCurrency]",...r)};function vt(r){const c=window.G7Core;c?.state?.set?c.state.set(r):Ve.warn("G7Core.state.set not available")}function Lt(){try{return localStorage.getItem(St)}catch{return null}}function xt(){try{const e=(window.G7Core?.state?.get?.()||{})?.modules?.["sirsoft-ecommerce"]?.language_currency;return Array.isArray(e?.currencies)?e.currencies:[]}catch{return[]}}function Pt(r){if(!r)return null;const c=r.toLowerCase().split("-")[0].split("_")[0];if(!c)return null;const t=xt().filter(o=>(Array.isArray(o?.locales)?o.locales:[]).some(n=>String(n).toLowerCase()===c));return t.length===1?t[0]?.code??null:t.length>1?t.find(a=>a?.is_default===!0)?.code??null:null}function At(){try{const r=window.G7Config;return r?.user?r.user:(window.G7Core?.state?.get?.()||{}).currentUser||null}catch{return null}}function kt(r,c){const e=r?.params?.defaultCurrency||"KRW",t=l=>typeof l=="string"&&/^[A-Z]{3}$/.test(l),o=At(),a=r?.params?.accountCurrency??o?.ecommerce_preferred_currency;let n=null;try{n=localStorage.getItem(It)}catch{}const s=a?null:Pt(Lt()),i=t(a)?a:t(n)?n:t(s)?s:t(e)?e:"KRW";vt({preferredCurrency:i}),Ve.log("Preferred currency initialized:",i)}const Gt="g7_preferred_shipping_country",je=window.G7Core?.createLogger?.("Handler:InitPreferredShippingCountry")??{log:(...r)=>console.log("[Handler:InitPreferredShippingCountry]",...r),warn:(...r)=>console.warn("[Handler:InitPreferredShippingCountry]",...r),error:(...r)=>console.error("[Handler:InitPreferredShippingCountry]",...r)};function Ot(r){const c=window.G7Core;c?.state?.set?c.state.set(r):je.warn("G7Core.state.set not available")}function Et(){try{const r=window.G7Config;return r?.user?r.user:(window.G7Core?.state?.get?.()||{}).currentUser||null}catch{return null}}function Dt(r,c){const e=r?.params?.defaultCountry||"KR",t=i=>typeof i=="string"&&/^[A-Z]{2}$/.test(i),o=Et(),a=r?.params?.accountCountry??o?.ecommerce_preferred_shipping_country;let n=null;try{n=localStorage.getItem(Gt)}catch{}const s=t(a)?a:t(n)?n:t(e)?e:"KR";Ot({preferredShippingCountry:s}),je.log("Preferred shipping country initialized:",s)}function W(r){const c=r.getFullYear(),e=String(r.getMonth()+1).padStart(2,"0"),t=String(r.getDate()).padStart(2,"0");return`${c}-${e}-${t}`}function Ft(r){const c=new Date,e=W(c);let t;switch(r){case"today":t=e;break;case"3days":{const o=new Date(c);o.setDate(o.getDate()-2),t=W(o);break}case"week":{const o=new Date(c);o.setDate(o.getDate()-6),t=W(o);break}case"month":{const o=new Date(c);o.setMonth(o.getMonth()-1),t=W(o);break}case"3months":{const o=new Date(c);o.setMonth(o.getMonth()-3),t=W(o);break}case"6months":{const o=new Date(c);o.setMonth(o.getMonth()-6),t=W(o);break}case"1year":{const o=new Date(c);o.setFullYear(o.getFullYear()-1),t=W(o);break}case"all":default:return t="",{startDate:"",endDate:""}}return{startDate:t,endDate:e}}function $t(r){const{preset:c}=r.params||{preset:"today"},{startDate:e,endDate:t}=Ft(c);return{startDate:e,endDate:t,preset:c}}const se=window.G7Core?.createLogger?.("Ecom:DefaultOption")??{log:(...r)=>console.log("[Ecom:DefaultOption]",...r),warn:(...r)=>console.warn("[Ecom:DefaultOption]",...r),error:(...r)=>console.error("[Ecom:DefaultOption]",...r)};function Nt(r,c){const e=r.params||{},{productId:t,optionId:o,dataSourceId:a="products"}=e;if(!t||!o){se.warn("[setDefaultOption] Missing required params:",{productId:t,optionId:o});return}const n=window.G7Core;if(!n?.dataSource?.get||!n?.dataSource?.set){se.warn("[setDefaultOption] G7Core.dataSource API is not available");return}const s=n.dataSource.get(a);if(!s){console.warn(`[setDefaultOption] DataSource '${a}' not found`);return}const i=s.data?.data||[];if(!Array.isArray(i)||i.length===0){se.warn("[setDefaultOption] Products data is empty or invalid");return}const u=(n.state.get()||{}).modules?.["sirsoft-ecommerce"]?.language_currency?.currencies,d=i.map(w=>{if(String(w.id)!==String(t))return w;const y=w.options||[];if(y.length===0)return w;const _=y.find(b=>String(b.id)===String(o));if(!_)return se.warn("[setDefaultOption] Option not found:",o),w;const p=y.map(b=>{const L=String(b.id)===String(o);return{...b,is_default:L,_modified:L?!0:b._modified}}),S=_.list_price,C=_.selling_price;let A=w.multi_currency_selling_price;return u&&Array.isArray(u)&&(A=ae({basePrice:C,currencies:u})),se.log(`[setDefaultOption] Set option ${o} as default for product ${t}. Syncing prices: list_price=${S}, selling_price=${C}`),{...w,options:p,list_price:S,selling_price:C,multi_currency_selling_price:A,_modified:!0}});n.dataSource.set(a,{...s,data:{...s.data,data:d}});const m=n.state.getLocal()||{},g=new Set(m.modifiedProductIds||[]);g.add(String(t));const f=new Set(m.modifiedOptionIds||[]);f.add(`${t}-${o}`),n.state.setLocal({modifiedProductIds:Array.from(g),modifiedOptionIds:Array.from(f)}),se.log(`[setDefaultOption] Option ${o} set as default for product ${t}`)}const Pe=window.G7Core?.createLogger?.("Ecom:OptionSelect")??{log:(...r)=>console.log("[Ecom:OptionSelect]",...r),warn:(...r)=>console.warn("[Ecom:OptionSelect]",...r),error:(...r)=>console.error("[Ecom:OptionSelect]",...r)},de=(r,c)=>`${r}-${c}`,Ae=r=>r.split("-")[0];function Tt(r,c){const e=r.params||{},{productId:t,optionId:o,checked:a}=e,n=window.G7Core;if(!n?.state){Pe.warn("[toggleOption] G7Core.state를 사용할 수 없습니다.");return}const s=n.state.getLocal(),i=s.selectedOptionIds||[],l=s.selectedItems||[],u=de(t,o);let d;a?i.includes(u)?d=i:d=[...i,u]:d=i.filter(p=>p!==u);const w=(n.dataSource?.get("products")?.data?.data||[]).find(p=>String(p.id)===String(t))?.options||[];let y=[...l];const _=Number(t);w.length>0&&(w.map(C=>de(t,C.id)).filter(C=>d.includes(C)).length===w.length?y.includes(_)||y.push(_):y=y.filter(C=>C!==_)),n.state.setLocal({selectedOptionIds:d,selectedItems:y})}function Be(r,c){const e=r.params||{},{productId:t,options:o,checked:a}=e,n=window.G7Core;if(!n?.state){Pe.warn("[toggleProductOptions] G7Core.state를 사용할 수 없습니다.");return}const s=n.state.getLocal(),i=s.selectedOptionIds||[],l=s.selectedItems||[],u=(o||[]).map(f=>de(t,f.id));let d,m=[...l];const g=Number(t);a?(d=[...i.filter(w=>Ae(w)!==String(t)),...u],m.includes(g)||m.push(g)):(d=i.filter(f=>Ae(f)!==String(t)),m=m.filter(f=>f!==g)),n.state.setLocal({selectedOptionIds:d,selectedItems:m})}function Rt(r,c){return Be(r)}function Mt(r,c){const e=r.params||{products:[],selectedOptionIds:[]},{products:t,selectedOptionIds:o}=e,a=o||[];if(!t||!Array.isArray(t))return{};const n={};for(const s of t){const i=String(s.id),l=s.options||[];if(l.length===0){n[i]="none";continue}const d=l.map(m=>de(i,m.id)).filter(m=>a.includes(m)).length;d===0?n[i]="none":d===l.length?n[i]="all":n[i]="some"}return n}function Ht(r,c){const e=r.params||{},{selectedIds:t}=e,o=window.G7Core;if(!o?.state){Pe.warn("[syncProductSelection] G7Core.state를 사용할 수 없습니다.");return}const a=o.state.getLocal(),n=a.selectedItems||[],s=a.selectedOptionIds||[],l=o.dataSource?.get("products")?.data?.data||[],u=t.filter(g=>!n.includes(g)),d=n.filter(g=>!t.includes(g));let m=[...s];for(const g of u){const f=l.find(w=>Number(w.id)===g);if(f?.options)for(const w of f.options){const y=de(g,w.id);m.includes(y)||m.push(y)}}for(const g of d)m=m.filter(f=>Ae(f)!==String(g));o.state.setLocal({selectedItems:t,selectedOptionIds:m})}const Ce=window.G7Core?.createLogger?.("Ecom:CopyCode")??{log:(...r)=>console.log("[Ecom:CopyCode]",...r),warn:(...r)=>console.warn("[Ecom:CopyCode]",...r),error:(...r)=>console.error("[Ecom:CopyCode]",...r)};async function qt(r,c){const e=window.G7Core;if(!e?.state||!e?.api){Ce.warn("[generateCopyProductCode] G7Core API is not available");return}try{const t=await e.api.post("/api/modules/sirsoft-ecommerce/admin/products/generate-code");if(!t?.data?.product_code)throw new Error("Invalid API response");const o=t.data.product_code;if((window.__g7LayoutContextStack||[]).length>0&&e.state.setParentLocal)e.state.setParentLocal({"ui.copyOptions.newProductCode":o}),Ce.log(`[generateCopyProductCode] Generated (parent): ${o}`);else{const s=e.state.getLocal()||{};e.state.setLocal({ui:{...s.ui,copyOptions:{...s.ui?.copyOptions||{},newProductCode:o}}}),Ce.log(`[generateCopyProductCode] Generated (local): ${o}`)}}catch(t){Ce.error("[generateCopyProductCode] Failed:",t),e.toast?.error?.(e.t?.("sirsoft-ecommerce.admin.product.messages.code_generate_error")??"Failed to generate product code.")}}const Ke=window.G7Core?.createLogger?.("Ecom:CopyProduct")??{log:(...r)=>console.log("[Ecom:CopyProduct]",...r),warn:(...r)=>console.warn("[Ecom:CopyProduct]",...r),error:(...r)=>console.error("[Ecom:CopyProduct]",...r)};async function Ut(r,c){const e=window.G7Core;if(!e?.state){Ke.warn("[copyProduct] G7Core API is not available");return}const t=e.state.getLocal()||{},a=(e.state.getParent?.()||{})._local||{},n=a.ui?.copyOptions||t.ui?.copyOptions||{},s=t.form?.product_code||a.form?.product_code||a.productList?.copyTargetProduct?.product_code||t.productList?.copyTargetProduct?.product_code;if(!s){e.toast?.error?.(e.t?.("sirsoft-ecommerce.admin.product.messages.copy_source_not_found")??"Could not find source product information.");return}e.modal?.close?.();const i={copy_id:s,copy_images:n.images??!0?"1":"0",copy_options:n.options??!0?"1":"0",copy_categories:n.categories??!0?"1":"0",copy_sales_info:n.sales_info??!0?"1":"0",copy_description:n.description??!0?"1":"0",copy_notice:n.notice??!0?"1":"0",copy_common_info:n.common_info??!0?"1":"0",copy_other_info:n.other_info??!0?"1":"0",copy_shipping:n.shipping??!0?"1":"0",copy_seo:n.seo??!1?"1":"0",copy_identification:n.identification??!0?"1":"0"},u=`/admin/ecommerce/products/create?${new URLSearchParams(i).toString()}`;Ke.log("[copyProduct] Navigating to:",u),e.navigate?e.navigate(u):c.navigate?c.navigate(u):window.location.href=u}const P=window.G7Core?.createLogger?.("Ecom:Category")??{log:(...r)=>console.log("[Ecom:Category]",...r),warn:(...r)=>console.warn("[Ecom:Category]",...r),error:(...r)=>console.error("[Ecom:Category]",...r)};function Qe(r,c,e,t=[]){for(const o of r){const a=o.name?.[e]||o.name?.ko||"";if(o.id===c)return[...t,a].join(" > ");if(o.children&&o.children.length>0){const n=Qe(o.children,c,e,[...t,a]);if(n)return n}}return null}function Vt(r,c,e){return r.map(t=>{if(t.breadcrumb)return{id:t.id,breadcrumb:t.breadcrumb};const o=Qe(c,t.id,e);if(o)return{id:t.id,breadcrumb:o};const a=t.name?.[e]||t.name?.ko||t.name_localized||"";return{id:t.id,breadcrumb:a}})}function jt(r,c){const e=r.params||{},t=e.depth,o=e.category;if(t===void 0||!o){P.warn("[selectCategory] Missing depth or category param");return}const a=c.isolatedContext;if(a){const u=a.state.selectedCategories||[null,null,null,null];if(u[t]?.id===o.id){P.log(`[selectCategory] Same category already selected at depth ${t}, skipping`);return}const d=u.map((m,g)=>gdp0?t+2:g;a.mergeState({selectedCategories:f,categoryStep:y}),P.log(`[selectCategoryMobile] Selected category at depth ${t} (isolated):`,o.id,"nextStep:",y);return}const n=window.G7Core;if(!n?.state){P.warn("[selectCategoryMobile] G7Core.state API is not available");return}const s=n.state.getLocal()||{},l=(s.ui?.selectedCategories||[null,null,null,null]).map((m,g)=>g0?t+2:s.ui?.categoryStep;n.state.setLocal({ui:{...s.ui,selectedCategories:l,categoryStep:d}}),P.log(`[selectCategoryMobile] Selected category at depth ${t}:`,o.id,"nextStep:",d)}function Kt(r,c){P.log("[addCategoryToSelection] START");const e=window.G7Core;if(!e?.state){P.warn("[addCategoryToSelection] G7Core.state API is not available");return}const t=e.state.getLocal()||{},o=e.locale?.current?.()||"ko",a=c.isolatedContext,n=a?a.state.selectedCategories||[]:t.ui?.selectedCategories||[];P.log("[addCategoryToSelection] Current state:",{selectedCats:n.map(p=>p?.id??null),currentCategoryIds:t.form?.category_ids,currentSelectedCategoryInfos:t.selectedCategoryInfos,usingIsolatedContext:!!a});const s=n.filter(Boolean).pop();if(!s){P.warn("[addCategoryToSelection] No category selected");return}P.log("[addCategoryToSelection] Last selected category:",s.id,s.name);const i=t.form?.category_ids||[];if(i.length>=5){e.toast?.warning?.(e.t?.("sirsoft-ecommerce.admin.product.category.messages.max_5")??"You can select up to 5 categories.");return}if(i.includes(s.id)){P.log("[addCategoryToSelection] Duplicate detected:",s.id,"in",i),e.toast?.warning?.(e.t?.("sirsoft-ecommerce.admin.product.category.messages.already_selected")??"This category is already selected.");return}const l=n.filter(Boolean).map(p=>p.name?.[o]||p.name?.ko||"").join(" > "),u=[...i,s.id],d=t.form?.primary_category_id||s.id,g=(e.state.getDataSources?.()||{}).categories?.data?.data||[];let f=t.selectedCategoryInfos||[];const w=t.form?.categories||[];f.length===0&&w.length>0&&(P.log("[addCategoryToSelection] Initializing from form.categories:",w.length,"items"),f=Vt(w,g,o));const y=[...f,{id:s.id,breadcrumb:l}];P.log("[addCategoryToSelection] Before setLocal:",{newCategoryIds:u,primaryCategoryId:d,newInfos:y});try{e.state.setLocal({form:{...t.form,category_ids:u,primary_category_id:d},selectedCategoryInfos:y,hasChanges:!0}),P.log("[addCategoryToSelection] setLocal SUCCESS")}catch(p){P.error("[addCategoryToSelection] setLocal FAILED:",p)}const _=e.state.getLocal()||{};P.log("[addCategoryToSelection] After setLocal:",{category_ids:_.form?.category_ids,selectedCategoryInfos:_.selectedCategoryInfos}),P.log(`[addCategoryToSelection] Added category ${s.id} with breadcrumb: ${l}. Total: ${u.length}`)}function Qt(r,c){const t=(r.params||{}).categoryId;if(!t){P.warn("[removeCategoryFromSelection] Missing categoryId param");return}const o=window.G7Core;if(!o?.state){P.warn("[removeCategoryFromSelection] G7Core.state API is not available");return}const a=o.state.getLocal()||{},s=(a.form?.category_ids||[]).filter(d=>d!==t);let i=a.form?.primary_category_id;i===t&&(i=s[0]||null);const u=(a.selectedCategoryInfos||[]).filter(d=>d.id!==t);o.state.setLocal({form:{...a.form,category_ids:s,primary_category_id:i},selectedCategoryInfos:u,hasChanges:!0}),P.log(`[removeCategoryFromSelection] Removed category ${t}. Remaining: ${s.length}`)}function zt(r,c){const e=r.params||{},t=e.categoryId,o=e.categories;if(!t||!o||!Array.isArray(o))return"";const n=window.G7Core?.locale?.current?.()||"ko",s=(l,u,d=[])=>{for(const m of l){const g=m.name?.[n]||m.name?.ko||"";if(m.id===u)return[...d,g];if(m.children&&m.children.length>0){const f=s(m.children,u,[...d,g]);if(f)return f}}return null},i=s(o,t);return i?i.join(" > "):""}function Wt(r,c){P.log("[initCategoryInfosFromProduct] START");const e=window.G7Core;if(!e?.state){P.warn("[initCategoryInfosFromProduct] G7Core.state API is not available");return}const t=e.state.getLocal()||{},o=e.locale?.current?.()||"ko",n=(e.state.getDataSources?.()||{}).categories?.data?.data||[],s=t.form?.categories||[],i=t.form?.category_ids||[];if(P.log("[initCategoryInfosFromProduct] Product categories:",s),P.log("[initCategoryInfosFromProduct] Category IDs:",i),P.log("[initCategoryInfosFromProduct] Categories tree available:",n.length>0),i.length===0){P.log("[initCategoryInfosFromProduct] No categories to initialize");return}const l=(d,m,g=[])=>{for(const f of d){const w=f.name?.[o]||f.name?.ko||"";if(f.id===m)return[...g,w];if(f.children&&f.children.length>0){const y=l(f.children,m,[...g,w]);if(y)return y}}return null},u=[];for(const d of i){const m=l(n,d);if(m)u.push({id:d,breadcrumb:m.join(" > ")});else{const g=s.find(f=>f.id===d);if(g){const f=g.name?.[o]||g.name?.ko||g.name_localized||"";u.push({id:d,breadcrumb:f})}}}P.log("[initCategoryInfosFromProduct] Generated selectedCategoryInfos:",u),e.state.setLocal({selectedCategoryInfos:u}),P.log("[initCategoryInfosFromProduct] DONE")}function Yt(r,c){const e={category1:"",category2:"",category3:"",category4:""},t=r.params||{},o=t.categoryId1?String(t.categoryId1):"",a=t.categoryId2?String(t.categoryId2):"",n=t.categoryId3?String(t.categoryId3):"",s=t.categoryId4?String(t.categoryId4):"",i=t.categories;if(!i||!Array.isArray(i)||i.length===0)return P.warn("[validateCategoryPath] No categories data provided"),e;if(o){const l=i.find(u=>String(u.id)===o);if(l&&l.depth===0&&(e.category1=o,a&&l.children)){const u=l.children.find(d=>String(d.id)===a);if(u&&u.depth===1&&(e.category2=a,n&&u.children)){const d=u.children.find(m=>String(m.id)===n);if(d&&d.depth===2&&(e.category3=n,s&&d.children)){const m=d.children.find(g=>String(g.id)===s);m&&m.depth===3&&(e.category4=s)}}}}return P.log("[validateCategoryPath] Result:",e),e}function Jt(r,c){const e=r.params||{},t=e.brandId,o=e.brands;if(!t||!o||!Array.isArray(o))return"";const n=window.G7Core?.locale?.current?.()||"ko",s=o.find(i=>i.id===t);return s?s.name?.[n]??s.name?.ko??"":""}function Xt(r,c){const e=r.params||{},t=e.brandId,o=e.brands;if(!t||!o||!Array.isArray(o))return"";const n=window.G7Core?.locale?.current?.()||"ko",s=o.find(i=>i.id===t);return s?.description?.[n]??s?.description?.ko??""}const Y=window.G7Core?.createLogger?.("Ecom:Price")??{log:(...r)=>console.log("[Ecom:Price]",...r),warn:(...r)=>console.warn("[Ecom:Price]",...r),error:(...r)=>console.error("[Ecom:Price]",...r)},Zt={KRW:1e3,JPY:100};function eo(r,c){const t=r.find(o=>o.code===c)?.base_unit??Zt[c]??1;return Math.max(1,t)}function to(r,c,e,t){const o=r/t*c;switch(e){case"ceil":return Math.ceil(o);case"floor":return Math.floor(o);case"round":default:return Math.round(o)}}function oo(r,c){const e=r.params||{},t=e.field,o=e.currency,a=e.value,n=e.autoFill;if(!t||!o||a===void 0){Y.warn("[updatePrice] Missing required params: field, currency, or value");return}const s=window.G7Core;if(!s?.state){Y.warn("[updatePrice] G7Core.state API is not available");return}const i=s.state.getLocal()||{},l=c.datasources?.currencies?.data||{},u=l?.list||[],d=l?.default_currency||"KRW",m=l?.rounding_method||"round",g={...i.form?.multi_currency_prices||{}};if(g[o]={...g[o]||{},[t]:a},n&&o===d){const y=eo(u,d);u.forEach(_=>{if(_.code!==d){const p=_.exchange_rate??1,S=to(a,p,m,y);g[_.code]={...g[_.code]||{},[t]:S}}})}const f={list:"list_price",selling:"selling_price"},w=o===d?a:i.form?.[f[t]];s.state.setLocal({form:{...i.form,multi_currency_prices:g,[f[t]]:w},hasChanges:!0}),Y.log(`[updatePrice] Updated ${t} price for ${o}: ${a}`)}function ro(r,c){const t=(r.params||{}).options;if(!t||!Array.isArray(t)||t.length===0)return 0;const o=t.reduce((a,n)=>n.is_active===!1?a:a+(n.stock_quantity||0),0);return Y.log(`[calculateTotalOptionStock] Total stock: ${o}`),o}function no(r,c){const e=window.G7Core;if(!e?.state){Y.warn("[validatePriceRelation] G7Core.state API is not available");return}const t=e.state.getLocal()||{},o=parseFloat(String(t.form?.list_price))||0,a=parseFloat(String(t.form?.selling_price))||0,n=a>o&&o>0,s=e.t?.("sirsoft-ecommerce.admin.product.validation.selling_price_exceeds_list_price")??"판매가는 정가보다 클 수 없습니다.",i={...t.errors||{}};n?i.selling_price=[s]:delete i.selling_price;const l=Object.keys(i).length>0;e.state.setLocal({validation:{...t.validation,price_error:n},errors:l?i:null}),n?(e.toast?.warning?.(s),Y.warn(`[validatePriceRelation] Selling price (${a}) exceeds list price (${o})`)):Y.log("[validatePriceRelation] Price validation passed")}const N=window.G7Core?.createLogger?.("Ecom:ProductOption")??{log:(...r)=>console.log("[Ecom:ProductOption]",...r),warn:(...r)=>console.warn("[Ecom:ProductOption]",...r),error:(...r)=>console.error("[Ecom:ProductOption]",...r)};function ao(){return window.G7Core?.config?.("app.supported_locales")??["ko","en"]}function ze(){return ao().reduce((c,e)=>({...c,[e]:""}),{})}function be(r){return(r??[]).filter(c=>c.is_active!==!1).reduce((c,e)=>c+(parseInt(String(e.stock_quantity),10)||0),0)}const so={KRW:1e3,JPY:100};function ke(){const e=((window.G7Core?.state?.get?.()||{}).modules?.["sirsoft-ecommerce"]?.language_currency?.currencies||[]).find(o=>o.is_default);if(!e)return 1;const t=e.base_unit??so[e.code]??1;return Math.max(1,t)}function io(r,c,e){const t=parseFloat(c)||1;if(t<=0)return r;const o=r/t;let a;switch(e){case"ceil":a=Math.ceil(o);break;case"floor":a=Math.floor(o);break;default:a=Math.round(o)}return a*t}function Ge(r,c,e){const t=c.exchange_rate||0;if(t<=0)return{price:0,formatted:he(0,c)};const o=e??ke(),a=r/o*t,n=io(a,c.rounding_unit||"0.01",c.rounding_method||"round"),s=c.decimal_places??2,i=parseFloat(n.toFixed(s));return{price:i,formatted:he(i,c)}}function Oe(r,c,e){return r.map(t=>{const o=parseFloat(String(t.price_adjustment))||0,a=c+o,n={...t,selling_price:a};if(e.length>0){const s=ke(),i={};e.forEach(l=>{i[l.code]=Ge(a,l,s)}),n.multi_currency_selling_price=i}return n})}function Ee(){return((window.G7Core?.state?.get?.()||{}).modules?.["sirsoft-ecommerce"]?.language_currency?.currencies||[]).filter(c=>!c.is_default&&c.exchange_rate)}function co(r,c){const e=r.params||{},t=window.G7Core;if(!t?.state){N.warn("[setDefaultOption] G7Core.state API is not available");return}const o=t.state.getLocal()||{},a=[...o.form?.options??[]],n=e.index,s=n!=null?typeof n=="string"?parseInt(n,10):n:-1,i=e.optionCode??a[s]?.option_code;if(!i){N.warn("[setDefaultOption] Missing optionCode/index param",{index:n});return}let l=a.map(m=>({...m,is_default:m.option_code===i}));const u=l.find(m=>m.is_default===!0),d=u?parseFloat(String(u.selling_price))||parseFloat(String(o.form?.selling_price))||0:parseFloat(String(o.form?.selling_price))||0;if(u){u.price_adjustment=0;const m=Ee();l=Oe(l,d,m)}t.state.setLocal({form:{...o.form,options:l,selling_price:d},hasChanges:!0}),N.log(`[setDefaultOption] Set default option to ${i}, synced product selling_price to ${d}`)}function lo(r,c){const e=r.params||{},t=typeof e.index=="string"?parseInt(e.index,10):e.index,o=e.field,a=e.value;if(t==null||!o){N.warn("[updateFormOptionField] Missing required params:",{index:t,field:o});return}const n=window.G7Core;if(!n?.state){N.warn("[updateFormOptionField] G7Core.state API is not available");return}const s=n.state.getLocal()||{},i=[...s.form?.options??[]];if(t<0||t>=i.length){N.warn("[updateFormOptionField] Index out of bounds:",{index:t,length:i.length});return}const l=["regular_price","sale_price","list_price","selling_price","stock_quantity","safe_stock_quantity","weight","volume","mileage_value"];let u=a;if(l.includes(o)&&(u=parseFloat(a)||0),i[t]={...i[t],[o]:u},o==="selling_price"){const d=parseFloat(String(u))||0,m=Ee();if(i[t].is_default===!0){i[t].price_adjustment=0;const f=Oe(i,d,m);n.state.setLocal({form:{...s.form,options:f,selling_price:d},hasChanges:!0}),N.log(`[updateFormOptionField] Reverse-synced product selling_price to ${d} from default option`);return}const g=parseFloat(String(s.form?.selling_price))||0;if(i[t].price_adjustment=d-g,m.length>0){const f=ke(),w={};m.forEach(y=>{w[y.code]=Ge(d,y,f)}),i[t].multi_currency_selling_price=w,N.log(`[updateFormOptionField] Recalculated multi_currency_selling_price for option at index ${t}:`,m.map(y=>y.code))}}if((o==="stock_quantity"||o==="is_active")&&i.length>0){const d=be(i);n.state.setLocal({form:{...s.form,options:i,stock_quantity:d},hasChanges:!0}),N.log(`[updateFormOptionField] Updated product stock_quantity to ${d} (sum of active options)`);return}n.state.setLocal({form:{...s.form,options:i},hasChanges:!0}),N.log(`[updateFormOptionField] Updated options[${t}].${o} =`,u)}function uo(r,c){const e=window.G7Core;if(!e?.state){N.warn("[addOptionRow] G7Core.state API is not available");return}const o=r.params?.isCreate===!0||r.params?.isCreate==="true"?1:0,a=e.state.getLocal()||{},n=[...a.form?.options??[]],s=a.form?.option_groups??[],i=`OPT-${Date.now()}`,l=s.map(g=>({key:g.name,value:ze()})),u={id:null,option_code:i,option_name:ze(),option_values:l,is_default:n.length===0,regular_price:0,sale_price:0,list_price:0,selling_price:0,price_adjustment:0,multi_currency_selling_price:{},sku:"",stock_quantity:o,safe_stock_quantity:0,weight:0,volume:0,mileage_value:0,mileage_type:"percent",is_active:!0},d=[...n,u],m=be(d);e.state.setLocal({form:{...a.form,options:d,stock_quantity:m,has_options:d.length>0},hasChanges:!0}),N.log(`[addOptionRow] Added new option row: ${i}, product stock_quantity = ${m}`)}function mo(r,c){const e=window.G7Core;if(!e?.state){N.warn("[recalculateOptionPriceAdjustments] G7Core.state API is not available");return}const t=r.params||{},o=e.state.getLocal()||{},a=t.newSellingPrice!==void 0?parseFloat(String(t.newSellingPrice))||0:parseFloat(String(o.form?.selling_price))||0,n=[...o.form?.options??[]];if(n.length===0){N.log("[recalculateOptionPriceAdjustments] No options to recalculate");return}const s=Ee(),i=Oe(n,a,s);e.state.setLocal({form:{...o.form,options:i}}),N.log(`[recalculateOptionPriceAdjustments] Recalculated selling_price for ${n.length} options based on product selling_price: ${a}`)}const h=window.G7Core?.createLogger?.("Ecom:Option")??{log:(...r)=>console.log("[Ecom:Option]",...r),warn:(...r)=>console.warn("[Ecom:Option]",...r),error:(...r)=>console.error("[Ecom:Option]",...r)};function De(){return window.G7Core?.config?.("app.supported_locales")??["ko","en"]}function ie(){return window.G7Core?.config?.("app.locale")??De()[0]??"ko"}function q(){return De().reduce((c,e)=>({...c,[e]:""}),{})}function We(r){return{id:null,name:q(),price_adjustment:0,is_default:r===0,is_active:!0,allow_custom_text:!1,sort_order:r}}function po(r,c){return typeof r=="string"?r:r[c]??r.ko??Object.values(r)[0]??""}function Ye(r){return r&&typeof r=="object"&&!Array.isArray(r)}function fo(r){return Array.isArray(r)?r:r&&typeof r=="object"?Object.values(r):[]}function go(r){return r.reduce((c,e)=>{const t=[];return c.forEach(o=>{e.forEach(a=>{t.push([...o,a])})}),t},[[]])}function _o(r){const c=De(),e={};for(const t of c){const o=r.map(a=>po(a,t));e[t]=o.join("/")}return e}function wo(r,c){return c.map((e,t)=>({key:typeof e.name=="string"?{...q(),[ie()]:e.name}:e.name,value:Ye(r[t])?r[t]:{...q(),[ie()]:r[t]}}))}function yo(r,c){const e=window.G7Core;if(!e?.state){h.warn("[addOptionInput] G7Core.state API is not available");return}const t=e.state.getLocal()||{},o=t.ui?.optionInputs??[];if(o.length>=3){e.toast?.warning?.(e.t?.("sirsoft-ecommerce.admin.product.options.messages.input_max_3")??"You can add up to 3 option inputs.");return}e.state.setLocal({ui:{...t.ui,optionInputs:[...o,{name:q(),values:[]}]}}),h.log(`[addOptionInput] Added option input. Total: ${o.length+1}`)}function ho(r,c){const t=(r.params||{}).index;if(t===void 0){h.warn("[removeOptionInput] Missing index param");return}const o=window.G7Core;if(!o?.state){h.warn("[removeOptionInput] G7Core.state API is not available");return}const a=o.state.getLocal()||{},n=[...a.ui?.optionInputs??[]];n.splice(t,1),o.state.setLocal({ui:{...a.ui,optionInputs:n}}),h.log(`[removeOptionInput] Removed option input at index ${t}`)}function Co(r,c){const e=r.params||{},t=e.index,o=e.field,a=e.value;if(t===void 0||!o){h.warn("[updateOptionInput] Missing required params");return}const n=window.G7Core;if(!n?.state){h.warn("[updateOptionInput] G7Core.state API is not available");return}h.log(`[updateOptionInput] Called with index=${t}, field="${o}" (type: ${typeof o}), value=`,a);const s=n.state.getLocal()||{},i=[...s.ui?.optionInputs??[]];if(h.log(`[updateOptionInput] Current inputs[${t}]:`,i[t]),i[t]||(h.log(`[updateOptionInput] inputs[${t}] not found, initializing with empty structure`),i[t]={name:q(),values:[]}),o==="name"){const l=ie(),u=i[t].name;h.log(`[updateOptionInput] Processing 'name' field - defaultLocale: ${l}, existingName:`,u,`type: ${typeof u}`);let d;a&&typeof a=="object"&&!Array.isArray(a)?(d=a,h.log("[updateOptionInput] Using multilingual value directly:",d)):(d={...u&&typeof u=="object"&&!Array.isArray(u)?u:q(),[l]:a},h.log("[updateOptionInput] Setting single locale value:",d)),i[t]={...i[t],name:d}}else h.log(`[updateOptionInput] Processing non-name field '${o}'`),i[t]={...i[t],[o]:a};h.log(`[updateOptionInput] Final inputs[${t}]:`,i[t]),n.state.setLocal({ui:{...s.ui,optionInputs:i}})}function bo(r,c){const e=window.G7Core;if(!e?.state){h.warn("[generateOptions] G7Core.state API is not available");return}const t=e.state.getLocal()||{},o=e.state.get()||{},a=t.ui?.optionInputs??[],n=ie(),s=p=>p.name?typeof p.name=="string"?p.name:p.name[n]??"":"",i=a.map(p=>({...p,values:fo(p.values)})),l=i.filter(p=>s(p)&&p.values&&p.values.length>0);if(h.log(`[generateOptions] Validating inputs - defaultLocale: ${n}, normalizedInputs:`,i.map(p=>({name:p.name,nameType:typeof p.name,extractedName:s(p),values:p.values,valuesIsArray:Array.isArray(p.values)}))),h.log(`[generateOptions] Valid inputs count: ${l.length}`),l.length===0){e.toast?.error?.(e.t?.("sirsoft-ecommerce.admin.product.options.messages.name_value_required")??"Please enter option name and values.");return}const u=t.form?.options??[],d=r.params?.skipConfirm===!0;if(u.length>0&&!d){e.modal?.open?.("modal_confirm_regenerate");return}const g=r.params?.isCreate===!0||r.params?.isCreate==="true"?1:0,f=parseFloat(String(t.form?.list_price))||0,w=parseFloat(String(t.form?.selling_price))||0,y=t.form?.multi_currency_selling_price,_=(o.modules?.["sirsoft-ecommerce"]?.language_currency?.currencies||[]).filter(p=>!p.is_default&&p.exchange_rate);try{h.log("[generateOptions] Step 1: Creating optionGroups...");const p=l.map((x,F)=>(h.log(`[generateOptions] Processing input[${F}]:`,{name:x.name,values:x.values,valuesType:typeof x.values,valuesIsArray:Array.isArray(x.values)}),{name:typeof x.name=="string"?{...q(),[n]:x.name}:x.name,values:x.values.map($=>Ye($)?$:{...q(),[n]:$})})),C=go(l.map(x=>x.values)).map((x,F)=>{const $=wo(x,l),oe=_o(x),le=`OPT-${String(F+1).padStart(3,"0")}`;let we={};return y&&typeof y=="object"&&Object.keys(y).length>0?we=JSON.parse(JSON.stringify(y)):w>0&&_.length>0&&_.forEach(B=>{we[B.code]=Ge(w,B)}),{id:null,option_code:le,option_values:$,option_name:oe,list_price:f,selling_price:w,price_adjustment:0,stock_quantity:g,safe_stock_quantity:0,is_default:F===0,is_active:!0,sku:"",multi_currency_selling_price:we}}),A=t.form?.options??[],b=x=>x.option_name?typeof x.option_name=="string"?x.option_name:x.option_name[n]??x.option_name.ko??"":"",L=C.map(x=>{const F=b(x),$=A.find(oe=>b(oe)===F);return $?{...x,...$,id:$.id}:x}),R=be(L);e.state.setLocal({form:{...t.form,option_groups:p,options:L,has_options:L.length>0,stock_quantity:R},hasChanges:!0}),e.toast?.success?.(e.t?.("sirsoft-ecommerce.admin.product.options.messages.generated",{count:L.length})??`${L.length} options have been generated.`),h.log(`[generateOptions] Generated ${L.length} options`)}catch(p){throw h.error("[generateOptions] Error occurred:",p),h.error("[generateOptions] Error message:",p.message),h.error("[generateOptions] Error stack:",p.stack),p}}function Io(r,c){const t=(r.params||{}).rowIndex;if(t===void 0){h.warn("[deleteOption] Missing rowIndex param");return}const o=window.G7Core;if(!o?.state){h.warn("[deleteOption] G7Core.state API is not available");return}const a=o.state.getLocal()||{},n=[...a.form?.options??[]];n.splice(t,1);const s=be(n),i=n.length>0;o.state.setLocal({form:{...a.form,options:n,has_options:i,...i?{stock_quantity:s}:{}},hasChanges:!0}),h.log(`[deleteOption] Deleted option at index ${t}, product stock_quantity = ${s}`)}function So(r,c){const e=r.params||{},t=e.optionName,o=e.applyToAll,a=e.inheritPrice,n=window.G7Core;if(!n?.state){h.warn("[applyOptionAddTool] G7Core.state API is not available");return}if(!t?.trim()){n.toast?.warning?.(n.t?.("sirsoft-ecommerce.admin.product.options.messages.name_required")??"Please enter option name.");return}const s=n.state.getLocal()||{},l=[...s.form?.options??[]].map(u=>{const d={...u};return o&&(d.option_values={...u.option_values,[t]:""}),a&&(d.price_adjustment=u.price_adjustment??0),d});n.state.setLocal({form:{...s.form,options:l},ui:{...s.ui,optionAddToolName:"",optionAddToolApplyToAll:!1,optionAddToolInheritPrice:!1},hasChanges:!0}),n.toast?.success?.(n.t?.("sirsoft-ecommerce.admin.product.options.messages.add_tool_applied")??"Option add tool has been applied.")}function vo(r,c){const e=window.G7Core;if(!e?.state){h.warn("[addRequiredItem] G7Core.state API is not available");return}const t=e.state.getLocal()||{},o=t.form?.required_items??[],a={id:null,sort_order:o.length,option_name:{ko:"",en:""},description:{ko:"",en:""}};e.state.setLocal({form:{...t.form,required_items:[...o,a]},hasChanges:!0}),h.log(`[addRequiredItem] Added required item. Total: ${o.length+1}`)}function Lo(r,c){const e=r.params||{},t=e.index,o=e.field,a=e.locale,n=e.value;if(t===void 0||!o||!a){h.warn("[updateRequiredItem] Missing required params");return}const s=window.G7Core;if(!s?.state){h.warn("[updateRequiredItem] G7Core.state API is not available");return}const i=s.state.getLocal()||{},l=[...i.form?.required_items??[]];l[t]={...l[t],[o]:{...l[t][o],[a]:n}},s.state.setLocal({form:{...i.form,required_items:l},hasChanges:!0})}function xo(r,c){const t=(r.params||{}).index;if(t===void 0){h.warn("[removeRequiredItem] Missing index param");return}const o=window.G7Core;if(!o?.state){h.warn("[removeRequiredItem] G7Core.state API is not available");return}const a=o.state.getLocal()||{},n=[...a.form?.required_items??[]];n.splice(t,1),n.forEach((s,i)=>{s.sort_order=i}),o.state.setLocal({form:{...a.form,required_items:n},hasChanges:!0}),h.log(`[removeRequiredItem] Removed required item at index ${t}`)}function Po(r,c){const e=r.params||{},t=e.oldIndex,o=e.newIndex;if(t===void 0||o===void 0){h.warn("[reorderRequiredItems] Missing oldIndex or newIndex param");return}const a=window.G7Core;if(!a?.state){h.warn("[reorderRequiredItems] G7Core.state API is not available");return}const n=a.state.getLocal()||{},s=[...n.form?.required_items??[]],[i]=s.splice(t,1);s.splice(o,0,i),s.forEach((l,u)=>{l.sort_order=u}),a.state.setLocal({form:{...n.form,required_items:s},hasChanges:!0}),h.log(`[reorderRequiredItems] Moved item from ${t} to ${o}`)}function Ao(r,c){const e=window.G7Core;if(!e?.state){h.warn("[addAdditionalOption] G7Core.state API is not available");return}const t=e.state.getLocal()||{},o=t.form?.additional_options??[];if(o.length>=5){e.toast?.warning?.(e.t?.("sirsoft-ecommerce.admin.product.options.messages.additional_max_5")??"You can add up to 5 additional options.");return}const a=`temp_${Date.now()}_${Math.random().toString(36).substring(2,9)}`;e.state.setLocal({form:{...t.form,additional_options:[...o,{id:a,name:q(),is_required:!1,sort_order:o.length,values:[We(0)]}]},hasChanges:!0}),h.log(`[addAdditionalOption] Added additional option. Total: ${o.length+1}`)}function ko(r,c){const e=r.params||{},t=e.index,o=e.field,a=e.value;if(t===void 0||!o){h.warn("[updateAdditionalOption] Missing required params");return}const n=window.G7Core;if(!n?.state){h.warn("[updateAdditionalOption] G7Core.state API is not available");return}const s=n.state.getLocal()||{},i=[...s.form?.additional_options??[]];if(!i[t]){h.warn("[updateAdditionalOption] Option not found at index",t);return}if(o==="name")if(a&&typeof a=="object"&&!Array.isArray(a))i[t]={...i[t],name:a};else{const l=ie(),u=i[t].name,d=u&&typeof u=="object"&&!Array.isArray(u)?u:q();i[t]={...i[t],name:{...d,[l]:a}}}else i[t]={...i[t],[o]:a};n.state.setLocal({form:{...s.form,additional_options:i},hasChanges:!0}),h.log(`[updateAdditionalOption] Updated options[${t}].${o}`)}function Go(r,c){const t=(r.params||{}).index;if(t===void 0){h.warn("[removeAdditionalOption] Missing index param");return}const o=window.G7Core;if(!o?.state){h.warn("[removeAdditionalOption] G7Core.state API is not available");return}const a=o.state.getLocal()||{},n=[...a.form?.additional_options??[]];n.splice(t,1),n.forEach((s,i)=>{s.sort_order=i}),o.state.setLocal({form:{...a.form,additional_options:n},hasChanges:!0}),h.log(`[removeAdditionalOption] Removed additional option at index ${t}`)}function Oo(r,c){const e=r.params||{},t=parseInt(String(e.oldIndex),10),o=parseInt(String(e.newIndex),10);if(isNaN(t)||isNaN(o)||t===o)return;const a=window.G7Core;if(!a?.state){h.warn("[reorderAdditionalOptions] G7Core.state API is not available");return}const n=a.state.getLocal()||{},s=[...n.form?.additional_options??[]];if(t<0||t>=s.length||o<0||o>=s.length)return;const[i]=s.splice(t,1);s.splice(o,0,i),s.forEach((l,u)=>{l.sort_order=u}),a.state.setLocal({form:{...n.form,additional_options:s},ui:{...n.ui,dragIndex:null},hasChanges:!0}),h.log(`[reorderAdditionalOptions] Moved item from ${t} to ${o}`)}function Eo(r,c){const e=window.G7Core;if(!e?.state){h.warn("[clearAdditionalOptions] G7Core.state API is not available");return}const t=e.state.getLocal()||{};e.state.setLocal({form:{...t.form,additional_options:[]},hasChanges:!0}),h.log("[clearAdditionalOptions] Cleared all additional options.")}function Fe(r){if(!r)return[];const c=r.values;return Array.isArray(c)?c:c&&typeof c=="object"?Object.values(c):[]}function Do(r,c){const e=r.params||{},t=typeof e.groupIndex=="string"?parseInt(e.groupIndex,10):e.groupIndex;if(t==null||isNaN(t)){h.warn("[addAdditionalOptionValue] Missing groupIndex param");return}const o=window.G7Core;if(!o?.state){h.warn("[addAdditionalOptionValue] G7Core.state API is not available");return}const a=o.state.getLocal()||{},n=[...a.form?.additional_options??[]];if(!n[t]){h.warn("[addAdditionalOptionValue] Group not found at index",t);return}const s=[...Fe(n[t])];if(s.length>=20){o.toast?.warning?.(o.t?.("sirsoft-ecommerce.admin.product.options.messages.additional_value_max_20")??"You can add up to 20 choices per group.");return}s.push(We(s.length)),n[t]={...n[t],values:s},o.state.setLocal({form:{...a.form,additional_options:n},hasChanges:!0}),h.log(`[addAdditionalOptionValue] Added value to group ${t}. Total: ${s.length}`)}function Fo(r,c){const e=r.params||{},t=typeof e.groupIndex=="string"?parseInt(e.groupIndex,10):e.groupIndex,o=typeof e.valueIndex=="string"?parseInt(e.valueIndex,10):e.valueIndex,a=e.field,n=e.value;if(t===void 0||o===void 0||!a){h.warn("[updateAdditionalOptionValue] Missing required params");return}const s=window.G7Core;if(!s?.state){h.warn("[updateAdditionalOptionValue] G7Core.state API is not available");return}const i=s.state.getLocal()||{},l=[...i.form?.additional_options??[]];if(!l[t]){h.warn("[updateAdditionalOptionValue] Group not found at index",t);return}const u=[...Fe(l[t])];if(!u[o]){h.warn("[updateAdditionalOptionValue] Value not found at index",o);return}if(a==="name")if(n&&typeof n=="object"&&!Array.isArray(n))u[o]={...u[o],name:n};else{const d=ie(),m=u[o].name,g=m&&typeof m=="object"&&!Array.isArray(m)?m:q();u[o]={...u[o],name:{...g,[d]:n}}}else if(a==="price_adjustment"){let d=parseFloat(String(n));(isNaN(d)||d<0)&&(d=0),u[o]={...u[o],price_adjustment:d}}else if(a==="is_default")n===!0||n==="true"?u.forEach((m,g)=>{m.is_default=g===o}):u[o].is_default=!1;else if(a==="is_active"||a==="allow_custom_text"){const d=n===!0||n==="true";u[o]={...u[o],[a]:d}}else u[o]={...u[o],[a]:n};l[t]={...l[t],values:u},s.state.setLocal({form:{...i.form,additional_options:l},hasChanges:!0}),h.log(`[updateAdditionalOptionValue] Updated group ${t} value ${o} field ${a}`)}function $o(r,c){const e=r.params||{},t=typeof e.groupIndex=="string"?parseInt(e.groupIndex,10):e.groupIndex,o=typeof e.valueIndex=="string"?parseInt(e.valueIndex,10):e.valueIndex;if(t===void 0||o===void 0){h.warn("[removeAdditionalOptionValue] Missing required params");return}const a=window.G7Core;if(!a?.state){h.warn("[removeAdditionalOptionValue] G7Core.state API is not available");return}const n=a.state.getLocal()||{},s=[...n.form?.additional_options??[]];if(!s[t]){h.warn("[removeAdditionalOptionValue] Group not found at index",t);return}const i=[...Fe(s[t])];if(i.length<=1){a.toast?.warning?.(a.t?.("sirsoft-ecommerce.admin.product.options.messages.additional_value_min_1")??"At least one choice is required per group.");return}i.splice(o,1),i.forEach((l,u)=>{l.sort_order=u}),i.some(l=>l.is_default)||(i[0].is_default=!0),s[t]={...s[t],values:i},a.state.setLocal({form:{...n.form,additional_options:s},hasChanges:!0}),h.log(`[removeAdditionalOptionValue] Removed value ${o} from group ${t}`)}const j=window.G7Core?.createLogger?.("Ecom:Image")??{log:(...r)=>console.log("[Ecom:Image]",...r),warn:(...r)=>console.warn("[Ecom:Image]",...r),error:(...r)=>console.error("[Ecom:Image]",...r)},No=["image/jpeg","image/png","image/gif","image/webp"],To=10*1024*1024,Ro=20;async function Mo(r,c){const t=(r.params||{}).files;if(!t||t.length===0){j.warn("[uploadImages] No files provided");return}const o=window.G7Core;if(!o?.state||!o?.api){j.warn("[uploadImages] G7Core.state or G7Core.api is not available");return}const n=(o.state.getLocal()||{}).form?.images??[],s=Ro-n.length;t.length>s&&o.toast?.warning?.(o.t?.("sirsoft-ecommerce.admin.product.images.messages.max_count",{max:s})??`You can add up to ${s} images.`);const i=Array.from(t).slice(0,s);for(const l of i){if(!No.includes(l.type)){o.toast?.error?.(o.t?.("sirsoft-ecommerce.admin.product.images.messages.unsupported_format",{name:l.name})??`${l.name}: Unsupported format.`);continue}if(l.size>To){o.toast?.error?.(o.t?.("sirsoft-ecommerce.admin.product.images.messages.size_exceeded",{name:l.name})??`${l.name}: Exceeds 10MB.`);continue}const u=o.state.getLocal()||{};o.state.setLocal({ui:{...u.ui,uploadProgress:{...u.ui?.uploadProgress||{},[l.name]:0}}});try{const d=new FormData;d.append("file",l),d.append("collection","products");const m=await o.api.upload("/api/admin/media/upload",d,{onProgress:S=>{const C=o.state.getLocal()||{};o.state.setLocal({ui:{...C.ui,uploadProgress:{...C.ui?.uploadProgress||{},[l.name]:S}}})}}),g=o.state.getLocal()||{},f=g.form?.images??[],w={...m.data,is_thumbnail:f.length===0,sort_order:f.length};o.state.setLocal({form:{...g.form,images:[...f,w]},hasChanges:!0});const y=o.state.getLocal()||{},{[l.name]:_,...p}=y.ui?.uploadProgress||{};o.state.setLocal({ui:{...y.ui,uploadProgress:p}}),j.log(`[uploadImages] Uploaded ${l.name}`)}catch(d){o.toast?.error?.(o.t?.("sirsoft-ecommerce.admin.product.images.messages.upload_failed",{name:l.name})??`${l.name}: Upload failed`);const m=o.state.getLocal()||{},{[l.name]:g,...f}=m.ui?.uploadProgress||{};o.state.setLocal({ui:{...m.ui,uploadProgress:f}}),j.error(`[uploadImages] Failed to upload ${l.name}:`,d)}}}function Ho(r,c){const t=(r.params||{}).index;if(t===void 0){j.warn("[setThumbnail] Missing index param");return}const o=window.G7Core;if(!o?.state){j.warn("[setThumbnail] G7Core.state API is not available");return}const a=o.state.getLocal()||{},n=(a.form?.images??[]).map((s,i)=>({...s,is_thumbnail:i===t}));o.state.setLocal({form:{...a.form,images:n},hasChanges:!0}),j.log(`[setThumbnail] Set thumbnail to image at index ${t}`)}function qo(r,c){const e=r.params||{},t=e.oldIndex,o=e.newIndex;if(t===void 0||o===void 0){j.warn("[reorderImages] Missing oldIndex or newIndex param");return}const a=window.G7Core;if(!a?.state){j.warn("[reorderImages] G7Core.state API is not available");return}const n=a.state.getLocal()||{},s=[...n.form?.images??[]],[i]=s.splice(t,1);s.splice(o,0,i),s.forEach((l,u)=>{l.sort_order=u}),a.state.setLocal({form:{...n.form,images:s},hasChanges:!0}),j.log(`[reorderImages] Moved image from ${t} to ${o}`)}const Je=window.G7Core?.createLogger?.("Ecom:Description")??{log:(...r)=>console.log("[Ecom:Description]",...r),warn:(...r)=>console.warn("[Ecom:Description]",...r),error:(...r)=>console.error("[Ecom:Description]",...r)};function Uo(r,c){const e=r.params||{},t=e.locale,o=e.value;if(!t){Je.warn("[updateDescription] Missing locale param");return}const a=window.G7Core;if(!a?.state){Je.warn("[updateDescription] G7Core.state API is not available");return}const n=a.state.getLocal()||{};a.state.setLocal({form:{...n.form,description:{...n.form?.description,[t]:o}},hasChanges:!0})}const k=window.G7Core?.createLogger?.("Ecom:Notice")??{log:(...r)=>console.log("[Ecom:Notice]",...r),warn:(...r)=>console.warn("[Ecom:Notice]",...r),error:(...r)=>console.error("[Ecom:Notice]",...r)};function ue(r=""){const e=window.G7Core?.locale?.supported?.()??["ko","en"],t={};for(const o of e)t[o]=r;return t}function Vo(r,c){const t=(r.params||{}).templateId,o=window.G7Core;if(!o?.state){k.warn("[confirmSelectNoticeTemplate] G7Core.state API is not available");return}const n=(o.state.getLocal()||{}).form?.notice_items??[],s=n.length>0&&n.some(i=>Object.values(i.content||{}).some(u=>u&&u.trim()!==""));if(!t||!s){Xe(r);return}o.state.setGlobal({pendingNoticeTemplateId:t}),o.modal?.open?.("notice_template_confirm_modal"),k.log(`[confirmSelectNoticeTemplate] Showing confirm modal for template: ${t}`)}function Xe(r,c){const t=(r.params||{}).templateId,o=window.G7Core;if(!o?.state){k.warn("[selectNoticeTemplate] G7Core.state API is not available");return}const a=o.state.getLocal()||{},n=o.dataSource?.get?.("notice_templates"),s=n?.data?.data??n?.data??[];if(!t){o.state.setLocal({form:{...a.form,notice_items:[]},ui:{...a.ui,selectedNoticeTemplateId:null},hasChanges:!0});return}const i=s.find(d=>d.id===t);if(!i){k.warn(`[selectNoticeTemplate] Template not found: ${t}`);return}const l=a.form?.notice_items??[],u=(i.fields??[]).map((d,m)=>{const g=JSON.stringify(d.name),f=l.find(w=>JSON.stringify(w.name)===g);return{key:`field_${m}`,name:d.name,content:f?.content??d.content??ue(),sort_order:m,is_custom:!1}});o.state.setLocal({form:{...a.form,notice_items:u},ui:{...a.ui,selectedNoticeTemplateId:t},hasChanges:!0}),k.log(`[selectNoticeTemplate] Selected template: ${t}`)}function jo(r,c){const e=r.params||{},t=e.index,o=e.locale,a=e.value;if(t===void 0||!o){k.warn("[updateNoticeItem] Missing required params");return}const n=window.G7Core;if(!n?.state){k.warn("[updateNoticeItem] G7Core.state API is not available");return}const s=n.state.getLocal()||{},i=[...s.form?.notice_items??[]];i[t]={...i[t],content:{...i[t].content,[o]:a}},n.state.setLocal({form:{...s.form,notice_items:i},hasChanges:!0})}function Bo(r,c){const t=(r.params||{}).index;if(t===void 0){k.warn("[removeNoticeItem] Missing index param");return}const o=window.G7Core;if(!o?.state){k.warn("[removeNoticeItem] G7Core.state API is not available");return}const a=o.state.getLocal()||{},n=[...a.form?.notice_items??[]];n.splice(t,1),n.forEach((s,i)=>{s.sort_order=i}),o.state.setLocal({form:{...a.form,notice_items:n},hasChanges:!0}),k.log(`[removeNoticeItem] Removed notice item at index ${t}`)}function Ko(r,c){const e=r.params||{},t=e.oldIndex,o=e.newIndex;if(t===void 0||o===void 0){k.warn("[reorderNoticeItems] Missing oldIndex or newIndex param");return}const a=window.G7Core;if(!a?.state){k.warn("[reorderNoticeItems] G7Core.state API is not available");return}const n=a.state.getLocal()||{},s=[...n.form?.notice_items??[]],[i]=s.splice(t,1);s.splice(o,0,i),s.forEach((l,u)=>{l.sort_order=u}),a.state.setLocal({form:{...n.form,notice_items:s},hasChanges:!0}),k.log(`[reorderNoticeItems] Moved item from ${t} to ${o}`)}function Qo(r,c){const t=(r.params||{}).value,o=window.G7Core;if(!o?.state){k.warn("[fillNoticeWithValue] G7Core.state API is not available");return}const a=o.state.getLocal()||{},n=a.ui?.noticeLocale??"ko",s=(a.form?.notice_items??[]).map(i=>({...i,content:{...i.content,[n]:i.content?.[n]||t}}));o.state.setLocal({form:{...a.form,notice_items:s},hasChanges:!0}),o.toast?.success?.(o.t?.("sirsoft-ecommerce.admin.product.notice.messages.filled")??"Empty fields have been filled.")}function zo(r,c){const t=(r.params||{}).mode;if(!t){k.warn("[switchNoticeMode] Missing mode param");return}const o=window.G7Core;if(!o?.state){k.warn("[switchNoticeMode] G7Core.state API is not available");return}const a=o.state.getLocal()||{};o.state.setLocal({ui:{...a.ui,noticeMode:t}}),k.log(`[switchNoticeMode] Switched to ${t} mode`)}function Wo(r,c){const e=r.params||{},t=e.locale,o=e.value;if(!t){k.warn("[updateNewTemplateName] Missing locale param");return}const a=window.G7Core;if(!a?.state){k.warn("[updateNewTemplateName] G7Core.state API is not available");return}const n=a.state.getLocal()||{};a.state.setLocal({form:{...n.form,new_template_name:{...n.form?.new_template_name,[t]:o}}})}function Yo(r,c){const e=window.G7Core;if(!e?.state){k.warn("[addNoticeItem] G7Core.state API is not available");return}const t=e.state.getLocal()||{},o=t.form?.notice_items??[],a={key:`custom_${Date.now()}`,name:ue(),content:ue(),sort_order:o.length,is_custom:!0};e.state.setLocal({form:{...t.form,notice_items:[...o,a]},hasChanges:!0}),k.log(`[addNoticeItem] Added new notice item. Total: ${o.length+1}`)}function Jo(r,c){const e=r.params||{},t=e.index,o=e.locale,a=e.value;if(t===void 0||!o){k.warn("[updateNoticeItemName] Missing required params");return}const n=window.G7Core;if(!n?.state){k.warn("[updateNoticeItemName] G7Core.state API is not available");return}const s=n.state.getLocal()||{},i=[...s.form?.notice_items??[]];i[t]={...i[t],name:{...i[t].name,[o]:a}},n.state.setLocal({form:{...s.form,notice_items:i},hasChanges:!0})}function Xo(r,c){const e=window.G7Core;if(!e?.state){k.warn("[saveAsNoticeTemplate] G7Core.state API is not available");return}if(((e.state.getLocal()||{}).form?.notice_items??[]).length===0){e.toast?.warning?.(e.t?.("sirsoft-ecommerce.admin.product.notice.messages.no_items")??"No items to save.");return}e.state.set?.({"ui.saveTemplateData":{name:ue(),category_id:null,is_default:!1}}),e.dispatch?.({handler:"openModal",target:"modal_save_template"})}const Ie={ko:"상세설명참조",en:"See detail description"};function Zo(r,c){const e=window.G7Core;if(!e?.state){k.warn("[fillTemplateFieldsWithDetailReference] G7Core.state API is not available");return}const a=((r.params||{}).form||{}).fields??[];if(a.length===0){e.toast?.warning?.(e.t?.("sirsoft-ecommerce.admin.product_notice_template.messages.no_fields_to_change")??"변경할 항목이 없습니다.");return}const n=e.locale?.supported?.()??["ko","en"],s={};for(const l of n)s[l]=Ie[l]??Ie.en??"See detail description";const i=a.map(l=>({...l,content:{...s}}));e.state.setLocal({"form.fields":i}),e.toast?.success?.(e.t?.("sirsoft-ecommerce.admin.product_notice_template.messages.all_fields_changed_to_detail_ref")??"모든 항목이 '상세설명참조'로 변경되었습니다."),k.log("[fillTemplateFieldsWithDetailReference] All fields updated to detail reference")}function er(r,c){const e=window.G7Core;if(!e?.state){k.warn("[fillNoticeItemsWithDetailReference] G7Core.state API is not available");return}const a=((r.params||{}).form||{}).notice_items??[];if(a.length===0){e.toast?.warning?.(e.t?.("sirsoft-ecommerce.admin.product.notice.messages.no_items_to_change")??"변경할 항목이 없습니다.");return}const n=e.locale?.supported?.()??["ko","en"],s={};for(const l of n)s[l]=Ie[l]??Ie.en??"See detail description";const i=a.map(l=>({...l,content:{...s}}));e.state.setLocal({"form.notice_items":i}),e.toast?.success?.(e.t?.("sirsoft-ecommerce.admin.product.notice.messages.all_items_changed_to_detail_ref")??"모든 항목이 '상세설명참조'로 변경되었습니다."),k.log("[fillNoticeItemsWithDetailReference] All notice items updated to detail reference")}async function tr(r,c){const e=window.G7Core;if(!e?.state||!e?.api){k.warn("[confirmSaveNoticeTemplate] G7Core.state or G7Core.api is not available");return}const t=e.state.getLocal()||{},o=e.state.get?.()?.ui?.saveTemplateData??{},a=o.name;if(!a?.ko?.trim()){e.toast?.warning?.(e.t?.("sirsoft-ecommerce.admin.product.notice.messages.template_name_required")??"Please enter template name.");return}try{const n=t.form?.notice_items??[],s=await e.api.post("/api/modules/sirsoft-ecommerce/admin/products/notice-templates",{name:a,is_active:o.is_default??!1,fields:n.map(i=>({name:i.name,content:i.content}))});e.state.set?.({"ui.saveTemplateData":{name:ue(),category_id:null,is_default:!1}}),e.state.setLocal({ui:{...t.ui,noticeMode:"existing"},hasChanges:!0}),e.dispatch?.({handler:"closeModal"}),e.toast?.success?.(e.t?.("sirsoft-ecommerce.admin.product.notice.messages.template_saved")??"Template has been saved."),e.datasources?.refresh?.("notice_templates"),k.log(`[confirmSaveNoticeTemplate] Template saved: ${s.data.id}`)}catch(n){e.toast?.error?.(e.t?.("sirsoft-ecommerce.admin.product.notice.messages.template_save_error")??"Failed to save template."),k.error("[confirmSaveNoticeTemplate] Failed to save template:",n)}}const v=window.G7Core?.createLogger?.("Ecom:Misc")??{log:(...r)=>console.log("[Ecom:Misc]",...r),warn:(...r)=>console.warn("[Ecom:Misc]",...r),error:(...r)=>console.error("[Ecom:Misc]",...r)};function or(r,c){const t=(r.params||{}).labelId;if(!t){v.warn("[toggleLabel] Missing labelId param");return}const o=window.G7Core;if(!o?.state){v.warn("[toggleLabel] G7Core.state API is not available");return}const a=o.state.getLocal()||{},n=a.form?.labels??[],s=n.includes(t)?n.filter(i=>i!==t):[...n,t];o.state.setLocal({form:{...a.form,labels:s},hasChanges:!0}),v.log(`[toggleLabel] Toggled label ${t}. Current labels: ${s.length}`)}async function rr(r,c){const e=window.G7Core;if(!e?.state||!e?.api){v.warn("[generateProductCode] G7Core.state or G7Core.api is not available");return}try{const t=await e.api.post("/api/modules/sirsoft-ecommerce/admin/products/generate-code"),o=e.state.getLocal()||{};e.state.setLocal({form:{...o.form,product_code:t.data.product_code}}),v.log(`[generateProductCode] Generated: ${t.data.product_code}`)}catch(t){e.toast?.error?.(e.t?.("sirsoft-ecommerce.admin.product.messages.code_generate_error")??"Failed to generate product code."),v.error("[generateProductCode] Failed:",t)}}function nr(r,c){const e=r.params||{},t=e.policyId,o=e.field,a=e.policies;if(!t||!o||!a)return"-";const n=a.find(s=>s.id===t);return n?n[o]??"-":"-"}function ar(r,c){const e=r.params||{},t=e.templateId,o=e.templates;if(!t||!o)return"";const n=window.G7Core?.locale?.current?.()||"ko",s=o.find(i=>i.id===t);return s?.content?.[n]??s?.content?.ko??""}function sr(r,c){const e=r.params||{},t=e.platform,o=e.field,a=e.value;if(!t||!o){v.warn("[updateShoppingIntegration] Missing required params");return}const n=window.G7Core;if(!n?.state){v.warn("[updateShoppingIntegration] G7Core.state API is not available");return}const s=n.state.getLocal()||{},i=s.form?.shopping_integrations??{naver:{enabled:!1,sync_interval:"manual"},daum:{enabled:!1,sync_interval:"manual"},facebook:{enabled:!1,sync_interval:"manual"},google:{enabled:!1,sync_interval:"manual"}};n.state.setLocal({form:{...s.form,shopping_integrations:{...i,[t]:{...i[t],[o]:a}}},hasChanges:!0}),v.log(`[updateShoppingIntegration] Updated ${t}.${o} to ${a}`)}function ir(r,c){const t=(r.params||{}).type;if(!t){v.warn("[updateShippingType] Missing type param");return}const o=window.G7Core;if(!o?.state){v.warn("[updateShippingType] G7Core.state API is not available");return}const a=o.state.getLocal()||{};let n=0,s=0;t==="fixed_paid"?n=3e3:t==="threshold_free"&&(s=3e4),o.state.setLocal({form:{...a.form,shipping_type:t,shipping_fee:t==="fixed_free"?0:a.form?.shipping_fee??n,free_shipping_threshold:t==="threshold_free"?a.form?.free_shipping_threshold??s:0},hasChanges:!0}),v.log(`[updateShippingType] Updated shipping type to ${t}`)}function cr(r,c){const e=r.params||{},t=e.field,o=e.value;if(!t){v.warn("[updateIdentificationCode] Missing field param");return}const a=window.G7Core;if(!a?.state){v.warn("[updateIdentificationCode] G7Core.state API is not available");return}const n=a.state.getLocal()||{};a.state.setLocal({form:{...n.form,[t]:o},hasChanges:!0})}function lr(r,c){const e=r.params||{},t=e.labelId,o=e.labelName;if(!t){v.warn("[openLabelPeriodModal] Missing labelId param");return}const a=window.G7Core;if(!a?.state){v.warn("[openLabelPeriodModal] G7Core.state API is not available");return}const n=a.state.getLocal()||{},i=(n.form?.labels??[]).find(l=>l.id===t);a.state.setLocal({ui:{...n.ui,showLabelPeriodModal:!0,labelPeriodModalData:{labelId:t,labelName:o,startDate:i?.start_date??null,endDate:i?.end_date??null}}})}function dr(r,c){const e=r.params||{},t=e.labelId,o=e.startDate,a=e.endDate;if(!t){v.warn("[saveLabelPeriod] Missing labelId param");return}const n=window.G7Core;if(!n?.state){v.warn("[saveLabelPeriod] G7Core.state API is not available");return}const s=n.state.getLocal()||{},i=[...s.form?.labels??[]],l=i.findIndex(u=>u.id===t);l>=0?i[l]={...i[l],start_date:o,end_date:a}:i.push({id:t,start_date:o,end_date:a}),n.state.setLocal({form:{...s.form,labels:i},ui:{...s.ui,showLabelPeriodModal:!1,labelPeriodModalData:null},hasChanges:!0}),n.toast?.success?.(n.t?.("sirsoft-ecommerce.admin.product.messages.label_period_saved")??"Label period has been saved."),v.log(`[saveLabelPeriod] Saved period for label ${t}`)}function ur(r,c){const t=(r.params||{}).labelId;if(!t){v.warn("[removeLabelPeriod] Missing labelId param");return}const o=window.G7Core;if(!o?.state){v.warn("[removeLabelPeriod] G7Core.state API is not available");return}const a=o.state.getLocal()||{},n=[...a.form?.labels??[]],s=n.findIndex(i=>i.id===t);s>=0&&(n[s]={...n[s],start_date:null,end_date:null}),o.state.setLocal({form:{...a.form,labels:n},hasChanges:!0}),o.toast?.success?.(o.t?.("sirsoft-ecommerce.admin.product.messages.label_period_removed")??"Label period has been removed."),v.log(`[removeLabelPeriod] Removed period for label ${t}`)}function mr(r,c){const t=(r.params||{}).sort;if(!t){v.warn("[updateActivityLogSort] Missing sort param");return}const o=window.G7Core;if(!o?.state){v.warn("[updateActivityLogSort] G7Core.state API is not available");return}const a=o.state.getLocal()||{};o.state.setLocal({ui:{...a.ui,activityLogSort:t}}),o.datasources?.refresh?.("activity_logs",{params:{sort:t==="latest"?"desc":"asc",per_page:a.ui?.activityLogPerPage??10}}),v.log(`[updateActivityLogSort] Updated sort to ${t}`)}function pr(r,c){const t=(r.params||{}).perPage;if(!t){v.warn("[updateActivityLogPerPage] Missing perPage param");return}const o=window.G7Core;if(!o?.state){v.warn("[updateActivityLogPerPage] G7Core.state API is not available");return}const a=o.state.getLocal()||{};o.state.setLocal({ui:{...a.ui,activityLogPerPage:t}}),o.datasources?.refresh?.("activity_logs",{params:{sort:a.ui?.activityLogSort==="oldest"?"asc":"desc",per_page:t}}),v.log(`[updateActivityLogPerPage] Updated perPage to ${t}`)}function fr(r,c){const e=window.G7Core;if(!e?.state){v.warn("[setDefaultShippingPolicy] G7Core.state API is not available");return}const t=e.state.getLocal()||{};if(t.form?.shipping_policy_id){v.log("[setDefaultShippingPolicy] Shipping policy already set, skipping");return}if(t.ui?.useDefaultShippingPolicy===!1){v.log("[setDefaultShippingPolicy] useDefaultShippingPolicy is OFF, skipping");return}const o=e.dataSource?.get?.("shipping_policies"),a=o?.data?.data??o?.data??[];if(a.length===0){v.log("[setDefaultShippingPolicy] No shipping policies found");return}const n=a.find(s=>s.is_default===!0);n?(e.state.setLocal({form:{...t.form,shipping_policy_id:n.id}}),v.log(`[setDefaultShippingPolicy] Set default shipping policy: ${n.id} (${n.name})`)):v.log("[setDefaultShippingPolicy] No default shipping policy found")}function gr(r,c){const e=window.G7Core;if(!e?.state){v.warn("[toggleDefaultShippingPolicy] G7Core.state API is not available");return}const t=r.params?.checked??!1,o=e.state.getLocal()||{},a={...o.ui,useDefaultShippingPolicy:t};if(t){const n=e.dataSource?.get?.("shipping_policies"),i=(n?.data?.data??n?.data??[]).find(l=>l.is_default===!0);e.state.setLocal({ui:a,form:{...o.form,shipping_policy_id:i?.id??null}}),i?v.log(`[toggleDefaultShippingPolicy] ON - Selected default policy: ${i.id}`):v.log("[toggleDefaultShippingPolicy] ON - No default policy found")}else e.state.setLocal({ui:a}),v.log("[toggleDefaultShippingPolicy] OFF - Keeping current shipping policy")}function _r(r,c){const e=window.G7Core;if(!e?.state){v.warn("[setLabelDatePreset] G7Core.state API is not available");return}const t=r.params?.preset;if(!t){v.warn("[setLabelDatePreset] Missing preset param");return}const o=new Date,a=i=>{const l=i.getFullYear(),u=String(i.getMonth()+1).padStart(2,"0"),d=String(i.getDate()).padStart(2,"0");return`${l}-${u}-${d}`},n=a(o);let s=null;switch(t){case"7d":{const i=new Date(o);i.setDate(i.getDate()+7),s=a(i);break}case"14d":{const i=new Date(o);i.setDate(i.getDate()+14),s=a(i);break}case"30d":{const i=new Date(o);i.setDate(i.getDate()+30),s=a(i);break}case"permanent":s=null;break;default:v.warn(`[setLabelDatePreset] Unknown preset: ${t}`);return}e.state.setGlobal({"labelFormData.start_date":n,"labelFormData.end_date":s}),v.log(`[setLabelDatePreset] Applied preset '${t}': ${n} ~ ${s??"permanent"}`)}const wr=(r,c)=>{const e=window.G7Core,o=(e?.state?.getLocal?.()||{}).form?.label_assignments??[],a=Number(r.params?.labelId);if(!a)return;const n=[...o];if(n.find(l=>l.label_id===a)){e?.state?.setLocal?.({"ui.lastClickedLabelId":a});return}const i=[...n,{label_id:a,started_at:null,ended_at:null}];e?.state?.setLocal?.({"form.label_assignments":i,"ui.lastClickedLabelId":a,hasChanges:!0})},yr=(r,c)=>{const e=window.G7Core,o=(e?.state?.getLocal?.()||{}).form?.label_assignments??[],a=Number(r.params?.labelId),n=r.params?.field,s=r.params?.value;if(!a||!n)return;const i=[...o],l=i.findIndex(u=>u.label_id===a);l<0||(i[l]={...i[l],[n]:s||null},e?.state?.setLocal?.({"form.label_assignments":i,hasChanges:!0}))},hr=(r,c)=>{const e=window.G7Core,t=e?.state?.getLocal?.()||{},o=t.form?.label_assignments??[],a=t.ui?.lastClickedLabelId,n=r.params?.preset;if(!a||!n)return;const s=[...o],i=s.findIndex(g=>g.label_id===a);if(i<0)return;const l=new Date,u=g=>g.toISOString().split("T")[0],d=u(l);let m=null;switch(n){case"7d":m=u(new Date(l.getTime()+7*24*60*60*1e3));break;case"14d":m=u(new Date(l.getTime()+14*24*60*60*1e3));break;case"30d":m=u(new Date(l.getTime()+30*24*60*60*1e3));break;case"permanent":m=null;break}s[i]={...s[i],started_at:d,ended_at:m},e?.state?.setLocal?.({"form.label_assignments":s,hasChanges:!0})},Cr=(r,c)=>{const e=window.G7Core,o=(e?.state?.get()||{}).labelToUncheckId;if(!o)return;const i=[...(e?.state?.getParent?.()?._local||{}).form?.label_assignments??[]].filter(l=>l.label_id!==o);e?.state?.setParentLocal?.({"form.label_assignments":i,hasChanges:!0}),e?.state?.setGlobal?.({labelToUncheckId:null}),e?.modal?.close?.()},br=async(r,c)=>{const e=window.G7Core,t=e?.state?.get()||{},a=(e?.state?.getLocal?.()||{}).form?.label_assignments??[],n=t.labelToDeleteId;if(n){e?.state?.setGlobal?.({isDeletingLabel:!0});try{const s=await e.api.delete(`/api/modules/sirsoft-ecommerce/admin/product-labels/${n}`);if(!s?.success)throw new Error(s?.message||"Failed to delete label");const l=[...a].filter(u=>u.label_id!==n);e?.state?.setLocal?.({"form.label_assignments":l,"ui.lastClickedLabelId":null}),await e?.dataSource?.refetch?.("product_labels"),e?.toast?.success?.(e.t?.("sirsoft-ecommerce.admin.product.labels.delete_success")??"Label deleted."),e?.modal?.close?.()}catch(s){e?.toast?.error?.(s?.response?.data?.message||s?.message||e.t?.("sirsoft-ecommerce.admin.product.labels.delete_error")||"Failed to delete label.")}finally{e?.state?.setGlobal?.({isDeletingLabel:!1,labelToDeleteId:null})}}},Ir=async(r,c)=>{const e=window.G7Core,t=e?.state?.get()||{},a=(e?.state?.getLocal?.()||{}).form?.label_assignments??[],n=t.editingLabelId,s=t.labelFormData;if(s?.name?.ko){e?.state?.setGlobal?.({isSavingLabel:!0});try{if(n){const i=await e.api.put(`/api/modules/sirsoft-ecommerce/admin/product-labels/${n}`,{name:s.name,color:s.color});if(!i?.success)throw new Error(i?.message||"Failed to update label");const u=[...a].map(d=>d.label_id===n?{...d,started_at:s.started_at||null,ended_at:s.ended_at||null}:d);e?.state?.setLocal?.({"form.label_assignments":u,hasChanges:!0})}else{const i=await e.api.post("/api/modules/sirsoft-ecommerce/admin/product-labels",{name:s.name,color:s.color||"#6B7280"});if(!i?.success)throw new Error(i?.message||"Failed to create label");const l=Number(i?.data?.id);if(l){const d=[...[...a],{label_id:l,started_at:null,ended_at:null}];e?.state?.setLocal?.({"form.label_assignments":d,"ui.lastClickedLabelId":l,hasChanges:!0})}}await e?.dataSource?.refetch?.("product_labels"),e?.toast?.success?.(n?e.t?.("sirsoft-ecommerce.admin.product.labels.save_success")??"Label saved.":e.t?.("sirsoft-ecommerce.admin.product.labels.create_success")??"Label created."),e?.modal?.close?.()}catch(i){e?.toast?.error?.(i?.response?.data?.message||i?.message||e.t?.("sirsoft-ecommerce.admin.product.labels.save_error")||"Failed to save label.")}finally{e?.state?.setGlobal?.({isSavingLabel:!1})}}},J=window.G7Core?.createLogger?.("Ecom:DescriptionLocale")??{log:(...r)=>console.log("[Ecom:DescriptionLocale]",...r),warn:(...r)=>console.warn("[Ecom:DescriptionLocale]",...r),error:(...r)=>console.error("[Ecom:DescriptionLocale]",...r)};function Ze(){return window.G7Core?.config?.("app.supported_locales")??["ko","en"]}function Sr(r,c){const t=(r.params||{}).locale;if(!t){J.warn("[removeDescriptionLocale] Missing locale param");return}const o=window.G7Core;if(!o?.state){J.warn("[removeDescriptionLocale] G7Core.state API is not available");return}const a=o?.state?.get?.("_global.settings.general.language")??o?.config?.("app.fallback_locale")??"ko";if(t===a){o.toast?.error?.(o.t?.("sirsoft-ecommerce.admin.product.description_editor.messages.default_locale_cannot_delete")??"Default language cannot be deleted.");return}const n=o.state.getLocal()||{},s=Ze(),i={...n.form?.description};delete i[t];const l={...n.form?.description_mobile};delete l[t];const u=(n.ui?.descriptionActiveLocales??s).filter(d=>d!==t);o.state.setLocal({form:{...n.form,description:i,description_mobile:l},ui:{...n.ui,descriptionActiveLocales:u,descriptionLocale:u[0]??"ko"},hasChanges:!0}),o.toast?.success?.(o.t?.("sirsoft-ecommerce.admin.product.description_editor.messages.locale_removed",{locale:t.toUpperCase()})??`${t.toUpperCase()} language has been removed.`),J.log(`[removeDescriptionLocale] Removed locale: ${t}`)}function vr(r,c){const e=window.G7Core;if(!e?.state){J.warn("[showAddLocaleModal] G7Core.state API is not available");return}const t=e.state.getLocal()||{};e.state.setLocal({ui:{...t.ui,showAddLocaleModal:!0}})}function Lr(r,c){const t=(r.params||{}).locale;if(!t){J.warn("[addDescriptionLocale] Missing locale param");return}const o=window.G7Core;if(!o?.state){J.warn("[addDescriptionLocale] G7Core.state API is not available");return}const a=o.state.getLocal()||{},n=Ze(),s=a.ui?.descriptionActiveLocales??["ko"];if(s.includes(t)){o.toast?.warning?.(o.t?.("sirsoft-ecommerce.admin.product.description_editor.messages.locale_already_added")??"This language is already added.");return}if(!n.includes(t)){o.toast?.error?.(o.t?.("sirsoft-ecommerce.admin.product.description_editor.messages.locale_unsupported")??"Unsupported language.");return}o.state.setLocal({form:{...a.form,description:{...a.form?.description,[t]:""},description_mobile:{...a.form?.description_mobile,[t]:""}},ui:{...a.ui,descriptionActiveLocales:[...s,t],descriptionLocale:t,showAddLocaleModal:!1},hasChanges:!0}),o.toast?.success?.(o.t?.("sirsoft-ecommerce.admin.product.description_editor.messages.locale_added",{locale:t.toUpperCase()})??`${t.toUpperCase()} language has been added.`),J.log(`[addDescriptionLocale] Added locale: ${t}`)}const X=window.G7Core?.createLogger?.("Ecom:BulkUpdate")??{log:(...r)=>console.log("[Ecom:BulkUpdate]",...r),warn:(...r)=>console.warn("[Ecom:BulkUpdate]",...r),error:(...r)=>console.error("[Ecom:BulkUpdate]",...r)};function et(r,c,e){const t=c?.response?.data??{},o=t?.message||c?.message||"",a=t?.errors,n=a&&typeof a=="object"?Object.values(a).map(i=>Array.isArray(i)?i[0]:String(i)):[],s=o||r.t(e);r.toast.error(s),r.state.set({bulkUpdateErrors:n.length>0?n:s?[s]:[]})}function Z(r){return typeof r=="string"?r:r&&typeof r=="object"?r.ko||r.en||JSON.stringify(r):String(r??"")}function Se(r,c,e){return e?r.t(`sirsoft-ecommerce.enums.${c}.${e}`)||e:String(e??"")}function tt(){const r=window.G7Core;return r||(X.error("[bulkUpdate] G7Core is not available"),null)}function xr(r,c){const e=tt();if(!e)return;const t=e.state.getLocal()||{},o=e.state.get()||{},a=o.bulkSelectedItems||t.selectedItems||[],n=o.bulkSelectedOptionIds||t.selectedOptionIds||[];X.log("[bulkUpdate] globalState.bulkSelectedItems:",o.bulkSelectedItems),X.log("[bulkUpdate] localState.selectedItems:",t.selectedItems),X.log("[bulkUpdate] Final selectedItems:",a),a.length>0?Pr(e,o,t,a):n.length>0?Ar(e,o,t,n):e.toast.warning(e.t("sirsoft-ecommerce.admin.product.bulk.no_selection"))}function Pr(r,c,e,t){const a=r.dataSource.get("products")?.data?.data||[],n={};c.bulkSalesStatus&&(n.sales_status=c.bulkSalesStatus),c.bulkDisplayStatus&&(n.display_status=c.bulkDisplayStatus);const s={};if(c.bulkPriceMethod&&c.bulkPriceValue!==void 0&&c.bulkPriceValue!==null&&c.bulkPriceValue!==""){const p={fixed:"set",increase:"add",decrease:"subtract"};c.bulkPriceUnit==="percent"?s.price_adjustment={method:"percent",value:c.bulkPriceMethod==="decrease"?-(Number(c.bulkPriceValue)||0):Number(c.bulkPriceValue)||0}:s.price_adjustment={method:p[c.bulkPriceMethod]||"add",value:Number(c.bulkPriceValue)||0}}if(c.bulkStockMethod&&c.bulkStockValue!==void 0&&c.bulkStockValue!==null&&c.bulkStockValue!==""){const p={set:"set",increase:"add",decrease:"subtract"};s.stock_quantity={method:p[c.bulkStockMethod]||"add",value:Number(c.bulkStockValue)||0}}const i=e.modifiedProductIds||[],l=e.modifiedOptionIds||[],u=e.modifiedProductFields||{},d=e.modifiedOptionFields||{},m=c.bulkSelectedOptionIds||e.selectedOptionIds||[],g=new Set(t),f=[],w=[];function y(p,S,C){if(S._modified||l.includes(C)){const b={product_id:p.id,option_id:S.id},L=d[C]||[];!s.price_adjustment&&L.includes("price_adjustment")&&(b.price_adjustment=S.price_adjustment),!s.price_adjustment&&L.includes("selling_price")&&(b.price_adjustment=S.price_adjustment),!s.stock_quantity&&L.includes("stock_quantity")&&(b.stock_quantity=S.stock_quantity),L.includes("option_name")&&(b.option_name=S.option_name),L.includes("sku")&&(b.sku=S.sku),L.includes("list_price")&&(b.list_price=S.list_price),L.includes("safe_stock_quantity")&&(b.safe_stock_quantity=S.safe_stock_quantity),L.includes("is_default")&&(b.is_default=S.is_default),L.includes("is_active")&&(b.is_active=S.is_active),Object.keys(b).length>2&&w.push(b)}}a.filter(p=>t.includes(p.id)).forEach(p=>{if(p._modified||i.includes(p.id)){const C={id:p.id},A=String(p.id),b=u[A]||[];!n.sales_status&&b.includes("sales_status")&&(C.sales_status=p.sales_status),!n.display_status&&b.includes("display_status")&&(C.display_status=p.display_status),b.includes("name")&&(C.name=p.name),b.includes("list_price")&&(C.list_price=p.list_price),b.includes("selling_price")&&(C.selling_price=p.selling_price),Object.keys(C).length>1&&f.push(C)}(p.options||[]).forEach(C=>{const A=`${p.id}-${C.id}`;y(p,C,A)})}),m.length>0&&a.forEach(p=>{g.has(p.id)||(p.options||[]).forEach(S=>{const C=`${p.id}-${S.id}`;m.includes(C)&&y(p,S,C)})});const _={ids:t};Object.keys(n).length>0&&(_.bulk_changes=n),f.length>0&&(_.items=f),Object.keys(s).length>0&&(_.option_bulk_changes=s),w.length>0&&(_.option_items=w),r.state.set({isProcessing:!0,bulkUpdateErrors:[]}),r.api.patch("/api/modules/sirsoft-ecommerce/admin/products/bulk-update",_).then(p=>{r.toast.success(r.t("sirsoft-ecommerce.admin.product.bulk.update_success",{products:p?.data?.products_updated||0,options:p?.data?.options_updated||0})),ot(r),r.dataSource.refetch("products"),r.modal.close()}).catch(p=>{X.error("[bulkUpdateProducts] Error:",p),et(r,p,"sirsoft-ecommerce.admin.product.bulk.update_error")}).finally(()=>{r.state.set({isProcessing:!1})})}function Ar(r,c,e,t){const a=r.dataSource.get("products")?.data?.data||[],n={};if(c.bulkPriceMethod&&c.bulkPriceValue!==void 0&&c.bulkPriceValue!==null&&c.bulkPriceValue!==""){const d={fixed:"set",increase:"add",decrease:"subtract"};c.bulkPriceUnit==="percent"?n.price_adjustment={method:"percent",value:c.bulkPriceMethod==="decrease"?-(Number(c.bulkPriceValue)||0):Number(c.bulkPriceValue)||0}:n.price_adjustment={method:d[c.bulkPriceMethod]||"add",value:Number(c.bulkPriceValue)||0}}if(c.bulkStockMethod&&c.bulkStockValue!==void 0&&c.bulkStockValue!==null&&c.bulkStockValue!==""){const d={set:"set",increase:"add",decrease:"subtract"};n.stock_quantity={method:d[c.bulkStockMethod]||"add",value:Number(c.bulkStockValue)||0}}const s=e.modifiedOptionFields||{},i=e.modifiedOptionIds||[],l=[];a.forEach(d=>{(d.options||[]).forEach(m=>{const g=`${d.id}-${m.id}`,f=m._modified||i.includes(g);if(t.includes(g)&&f){const w={product_id:d.id,option_id:m.id},y=s[g]||[];!n.price_adjustment&&y.includes("price_adjustment")&&(w.price_adjustment=m.price_adjustment),!n.price_adjustment&&y.includes("selling_price")&&(w.price_adjustment=m.price_adjustment),!n.stock_quantity&&y.includes("stock_quantity")&&(w.stock_quantity=m.stock_quantity),y.includes("option_name")&&(w.option_name=m.option_name),y.includes("sku")&&(w.sku=m.sku),y.includes("list_price")&&(w.list_price=m.list_price),y.includes("safe_stock_quantity")&&(w.safe_stock_quantity=m.safe_stock_quantity),y.includes("is_default")&&(w.is_default=m.is_default),y.includes("is_active")&&(w.is_active=m.is_active),Object.keys(w).length>2&&l.push(w)}})});const u={ids:t};Object.keys(n).length>0&&(u.bulk_changes=n),l.length>0&&(u.items=l),r.state.set({isProcessing:!0,bulkUpdateErrors:[]}),r.api.patch("/api/modules/sirsoft-ecommerce/admin/options/bulk-update",u).then(d=>{r.toast.success(r.t("sirsoft-ecommerce.admin.product.bulk.option_update_success",{count:d?.data?.options_updated||0})),ot(r),r.dataSource.refetch("products"),r.modal.close()}).catch(d=>{X.error("[bulkUpdateOptions] Error:",d),et(r,d,"sirsoft-ecommerce.admin.product.bulk.option_update_error")}).finally(()=>{r.state.set({isProcessing:!1})})}function ot(r){r.state.setLocal({modifiedProductIds:[],modifiedOptionIds:[],modifiedProductFields:{},modifiedOptionFields:{},selectedItems:[],selectedOptionIds:[]}),r.state.set({bulkSalesStatus:null,bulkDisplayStatus:null,bulkPriceCondition:null,bulkStockCondition:null,bulkConfirmData:null,bulkUpdateErrors:[]})}function kr(r,c){const e=tt();if(!e)return;const t=e.state.getLocal()||{},o=e.state.get()||{},n=e.dataSource.get("products")?.data?.data||[],s=o.bulkSelectedItems||t.selectedItems||[],i=o.bulkSelectedOptionIds||t.selectedOptionIds||[],l={products:[],options:[],summary:{productCount:0,optionCount:0,hasBulkChanges:!1,hasInlineChanges:!1}},u=!!(o.bulkSalesStatus||o.bulkDisplayStatus||o.bulkPriceCondition||o.bulkStockCondition);l.summary.hasBulkChanges=u;const d=t.modifiedProductIds||[],m=t.modifiedOptionIds||[],g=t.modifiedProductFields||{},f=t.modifiedOptionFields||{},w=new Set(s);function y(_,p,S){const C=[],A=f[p]||[];if(o.bulkPriceCondition&&C.push(e.t("sirsoft-ecommerce.admin.product.messages.bulk_summary_price_adjustment_inline",{value:o.bulkPriceCondition})||`Price adjustment: ${o.bulkPriceCondition}`),o.bulkStockCondition&&C.push(e.t("sirsoft-ecommerce.admin.product.messages.bulk_summary_stock_inline",{value:o.bulkStockCondition})||`Stock: ${o.bulkStockCondition}`),S){if(!o.bulkPriceCondition&&A.includes("price_adjustment")){const b=_.price_adjustment?.toLocaleString?.()??_.price_adjustment;C.push(e.t("sirsoft-ecommerce.admin.product.messages.bulk_summary_price_adjustment_inline",{value:b})||`Price adjustment: ${b}`)}!o.bulkPriceCondition&&A.includes("selling_price")&&C.push(e.t("sirsoft-ecommerce.admin.product.messages.bulk_summary_selling_price",{price:_.selling_price?.toLocaleString?.()??_.selling_price})||`Selling price: ${_.selling_price}`),!o.bulkStockCondition&&A.includes("stock_quantity")&&C.push(e.t("sirsoft-ecommerce.admin.product.messages.bulk_summary_stock_inline",{value:_.stock_quantity})||`Stock: ${_.stock_quantity}`),A.includes("option_name")&&C.push(e.t("sirsoft-ecommerce.admin.product.messages.bulk_summary_option_name",{name:Z(_.option_name)})||`Option name: ${Z(_.option_name)}`),A.includes("sku")&&C.push(e.t("sirsoft-ecommerce.admin.product.messages.bulk_summary_sku",{sku:_.sku})||`SKU: ${_.sku}`),A.includes("list_price")&&C.push(e.t("sirsoft-ecommerce.admin.product.messages.bulk_summary_list_price",{price:_.list_price?.toLocaleString?.()??_.list_price})||`List price: ${_.list_price}`),A.includes("safe_stock_quantity")&&C.push(e.t("sirsoft-ecommerce.admin.product.messages.bulk_summary_safe_stock",{value:_.safe_stock_quantity})||`Safe stock: ${_.safe_stock_quantity}`),A.includes("is_active")&&C.push(e.t("sirsoft-ecommerce.admin.product.messages.bulk_summary_is_active",{value:_.is_active?"ON":"OFF"})||`Active: ${_.is_active?"ON":"OFF"}`),l.summary.hasInlineChanges=!0}return C}s.length>0&&n.filter(_=>s.includes(_.id)).forEach(_=>{const p=[],S=_._modified||d.includes(_.id),C=String(_.id),A=g[C]||[];if(o.bulkSalesStatus){const b=Se(e,"sales_status",o.bulkSalesStatus);p.push(e.t("sirsoft-ecommerce.admin.product.messages.bulk_summary_sales_status",{status:b})||`Sales status: ${b}`)}if(o.bulkDisplayStatus){const b=Se(e,"display_status",o.bulkDisplayStatus);p.push(e.t("sirsoft-ecommerce.admin.product.messages.bulk_summary_display_status",{status:b})||`Display status: ${b}`)}if(S){if(!o.bulkSalesStatus&&A.includes("sales_status")){const b=Se(e,"sales_status",_.sales_status);p.push(e.t("sirsoft-ecommerce.admin.product.messages.bulk_summary_sales_status",{status:b})||`Sales status: ${b}`)}if(!o.bulkDisplayStatus&&A.includes("display_status")){const b=Se(e,"display_status",_.display_status);p.push(e.t("sirsoft-ecommerce.admin.product.messages.bulk_summary_display_status",{status:b})||`Display status: ${b}`)}A.includes("name")&&p.push(e.t("sirsoft-ecommerce.admin.product.messages.bulk_summary_name_changed")||"Name changed"),A.includes("list_price")&&p.push(e.t("sirsoft-ecommerce.admin.product.messages.bulk_summary_list_price",{price:_.list_price?.toLocaleString?.()||_.list_price})||`List price: ${_.list_price?.toLocaleString?.()||_.list_price}`),A.includes("selling_price")&&p.push(e.t("sirsoft-ecommerce.admin.product.messages.bulk_summary_selling_price",{price:_.selling_price?.toLocaleString?.()||_.selling_price})||`Selling price: ${_.selling_price?.toLocaleString?.()||_.selling_price}`),l.summary.hasInlineChanges=!0}(p.length>0||u||S)&&l.products.push({id:_.id,name:Z(_.name),changes:p.length>0?p.join(", "):S?e.t("sirsoft-ecommerce.admin.product.messages.bulk_summary_inline_modified")||"Inline modified":e.t("sirsoft-ecommerce.admin.product.messages.bulk_summary_bulk_applied")||"Bulk changes applied"}),(_.options||[]).forEach(b=>{const L=`${_.id}-${b.id}`,R=b._modified||m.includes(L),x=y(b,L,R);(x.length>0||u&&(o.bulkPriceCondition||o.bulkStockCondition)||R)&&l.options.push({productId:_.id,optionId:b.id,productName:Z(_.name),optionName:b.option_name_localized||Z(b.option_name)||b.name||"",changes:x.length>0?x.join(", "):R?e.t("sirsoft-ecommerce.admin.product.messages.bulk_summary_inline_modified")||"Inline modified":e.t("sirsoft-ecommerce.admin.product.messages.bulk_summary_bulk_applied")||"Bulk changes applied"})})}),i.length>0&&n.forEach(_=>{w.has(_.id)||(_.options||[]).forEach(p=>{const S=`${_.id}-${p.id}`;if(i.includes(S)){const C=p._modified||m.includes(S),A=y(p,S,C);l.options.push({productId:_.id,optionId:p.id,productName:Z(_.name),optionName:p.option_name_localized||Z(p.option_name)||p.name||"",changes:A.length>0?A.join(", "):C?e.t("sirsoft-ecommerce.admin.product.messages.bulk_summary_inline_modified")||"Inline modified":e.t("sirsoft-ecommerce.admin.product.messages.bulk_summary_selected")||"Selected"})}})}),l.summary.productCount=l.products.length,l.summary.optionCount=l.options.length,e.state.set({bulkConfirmData:l}),X.log("[buildConfirmData] Built confirm data:",l)}const M=window.G7Core?.createLogger?.("Ecom:Order")??{log:(...r)=>console.log("[Ecom:Order]",...r),warn:(...r)=>console.warn("[Ecom:Order]",...r),error:(...r)=>console.error("[Ecom:Order]",...r)};function Gr(r){const{currencies:c=[]}=r.params||{},e=[{field:"no",header:"NO",width:"60px",sortable:!1,required:!0},{field:"ordered_at",header:"$t:sirsoft-ecommerce.admin.order.column.order_date",width:"140px",sortable:!0,required:!0},{field:"order_number",header:"$t:sirsoft-ecommerce.admin.order.column.order_number",width:"140px",sortable:!0,required:!0},{field:"product",header:"$t:sirsoft-ecommerce.admin.order.column.product",width:"auto",sortable:!1,required:!0},{field:"order_amount",header:"$t:sirsoft-ecommerce.admin.order.column.order_amount",width:"120px",sortable:!0,required:!1},{field:"order_status",header:"$t:sirsoft-ecommerce.admin.order.column.order_status",width:"100px",sortable:!1,required:!1},{field:"orderer",header:"$t:sirsoft-ecommerce.admin.order.column.orderer",width:"120px",sortable:!1,required:!1},{field:"payment_method",header:"$t:sirsoft-ecommerce.admin.order.column.payment_method",width:"100px",sortable:!1,required:!1},{field:"delivery_method",header:"$t:sirsoft-ecommerce.admin.order.column.delivery_method",width:"100px",sortable:!1,required:!1},{field:"country",header:"$t:sirsoft-ecommerce.admin.order.column.country",width:"80px",sortable:!1,required:!1}],t=c.filter(o=>!o.is_default).map(o=>({field:`order_amount_${o.code.toLowerCase()}`,header:`${o.name} (${o.symbol})`,width:"100px",sortable:!0,required:!1}));return[...e,...t]}function Or(r,c){const e=r.params||{},{target:t="_local",path:o,value:a}=e,n=window.G7Core;if(!n?.state)return M.warn("[toggleArrayValue] G7Core.state를 사용할 수 없습니다."),{success:!1,newValue:[]};const s=t==="_global"?n.state.getGlobal():n.state.getLocal(),i=o.split(".");let l=s;for(const d of i)l=l?.[d];Array.isArray(l)||(l=[]);let u;return l.includes(a)?u=l.filter(d=>d!==a):u=[...l,a],t==="_global"?n.state.setGlobal({[o]:u}):n.state.setLocal({[o]:u}),{success:!0,newValue:u}}function Er(r,c){const e=r.params||{},{filterKey:t,checked:o}=e,a=window.G7Core;if(!a?.state)return M.warn("[toggleVisibleFilter] G7Core.state를 사용할 수 없습니다."),{success:!1,newValue:[]};const s=a.state.getLocal().visibleFilters||[];let i;o?i=s.includes(t)?s:[...s,t]:i=s.filter(l=>l!==t),a.state.setLocal({visibleFilters:i});try{localStorage.setItem("g7_order_filter_visible_filters",JSON.stringify(i))}catch(l){M.warn("[toggleVisibleFilter] LocalStorage 저장 실패:",l)}return{success:!0,newValue:i}}function Dr(r,c){const e=r.params||{},{selectedIds:t=[]}=e,o=window.G7Core;return o?.state?(o.state.setLocal({selectedItems:t,selectAll:!1}),{success:!0}):(M.warn("[syncOrderSelection] G7Core.state를 사용할 수 없습니다."),{success:!1})}function Fr(r,c){const e=r.params||{},{actionId:t,row:o}=e,a=window.G7Core;if(!t||!o?.order_number)return M.warn("[handleOrderRowAction] actionId 또는 row.order_number가 없습니다."),{success:!1};switch(t){case"view":a?.navigate?.(`/admin/ecommerce/orders/${o.order_number}`);break;case"edit":a?.navigate?.(`/admin/ecommerce/orders/${o.order_number}`);break;case"status":case"shipping":case"memo":case"delete":a?.state?.setGlobal({orderList:{currentActionOrderId:o.id,currentActionType:t}});break;case"print":window.open(`/admin/ecommerce/orders/${o.order_number}/shipping-label`,"_blank");break;default:return console.warn(`[handleOrderRowAction] 알 수 없는 액션: ${t}`),{success:!1}}return{success:!0,action:t,targetId:o.id}}async function $r(r,c){const e=r.params||{},{actionType:t,orderIds:o,status:a,carrierId:n,trackingNumber:s}=e,i=window.G7Core;if(!o||o.length===0)throw new Error("선택된 주문이 없습니다.");let l="";const u={ids:o};switch(t){case"status":if(!a)throw new Error("변경할 상태를 선택해주세요.");l="/api/modules/sirsoft-ecommerce/admin/orders/bulk",u.order_status=a;break;case"shipping":if(!s&&!n)throw new Error("택배사 또는 운송장번호를 입력해주세요.");l="/api/modules/sirsoft-ecommerce/admin/orders/bulk",n&&(u.carrier_id=n),s&&(u.tracking_number=s);break;default:throw new Error(`알 수 없는 액션 타입: ${t}`)}const d=await i?.api?.call(l,{method:"PATCH",body:u});if(!d?.success)throw new Error(d?.message||"일괄 처리에 실패했습니다.");return{success:!0,successCount:d.data?.updated_count??o.length,failCount:d.data?.fail_count??0,errors:d.data?.errors??[]}}async function Nr(r,c){const e=r.params||{},{downloadType:t,orderIds:o,filters:a}=e,n=window.G7Core,s="/api/modules/sirsoft-ecommerce/admin/orders/export",i={};if(t==="selected"){if(!o||o.length===0)throw new Error("선택된 주문이 없습니다.");i.ids=o}else i.filters=a||{};const l=n?.auth?.getToken(),u=await fetch(s,{method:"POST",headers:{"Content-Type":"application/json",...l?{Authorization:`Bearer ${l}`}:{}},body:JSON.stringify(i)});if(!u.ok){const y=await u.json().catch(()=>null);throw new Error(y?.message||"엑셀 다운로드에 실패했습니다.")}const d=await u.blob(),m=window.URL.createObjectURL(d),g=document.createElement("a");g.href=m;const f=new Date,w=`${f.getFullYear()}-${String(f.getMonth()+1).padStart(2,"0")}-${String(f.getDate()).padStart(2,"0")}`;return g.download=`orders_${w}.xlsx`,document.body.appendChild(g),g.click(),document.body.removeChild(g),window.URL.revokeObjectURL(m),{success:!0}}function Tr(r,c){const e=r.params||{},{columns:t=[]}=e;try{localStorage.setItem("g7_order_visible_columns",JSON.stringify(t))}catch(o){return M.warn("[saveVisibleColumns] LocalStorage 저장 실패:",o),{success:!1}}return{success:!0}}function Rr(r,c){const t=(r.params||{}).defaultColumns||["order_date","order_number","order_status","order_amount","product","orderer","payment_method","delivery_method","country"];try{const o=localStorage.getItem("g7_order_visible_columns");if(o){const a=JSON.parse(o);if(Array.isArray(a))return a}}catch(o){M.warn("[loadVisibleColumns] LocalStorage 로드 실패:",o)}return t}function Mr(r,c){const e=r.params||{},{id:t,defaultFilters:o=[]}=e,a=window.G7Core;if(!t)return M.warn("[loadVisibleFilters] id 파라미터가 필요합니다."),o;let n;try{const i=a?.AuthManager?.getInstance();i&&(n=i.getUser()?.uuid)}catch{}const s=`g7_filters_${t}${n!==void 0?`_${n}`:""}`;try{const i=localStorage.getItem(s);if(i){const l=JSON.parse(i);if(Array.isArray(l)&&l.length>0)return M.log(`[loadVisibleFilters] localStorage에서 로드: ${s}`,l),l}}catch(i){M.warn("[loadVisibleFilters] LocalStorage 로드 실패:",i)}return M.log(`[loadVisibleFilters] 기본값 사용: ${s}`,o),o}function Hr(r,c){const e=window.G7Core;if(!e)return;const t=e.state.get()||{},o=t.bulkSelectedItems||[],a=t.bulkOrderStatus||"",n=t.bulkCourier||"",s=t.bulkTrackingNumber||"";if(!a){e.toast?.warning?.(e.t?.("sirsoft-ecommerce.admin.order.bulk.no_status_selected")||"변경할 주문상태를 선택해주세요.");return}if(["shipping_ready","shipping"].includes(a)&&(!n||!s)){e.toast?.warning?.(e.t?.("sirsoft-ecommerce.admin.order.bulk.carrier_required")||"해당 상태로 변경하려면 택배사와 송장번호를 모두 입력해주세요.");return}e.state.set({bulkConfirmData:{selectedItems:o,orderStatus:a,courierId:n,trackingNumber:s}}),e.modal?.open?.("modal_bulk_confirm")}async function qr(r,c){const e=window.G7Core;if(!e)return;const o=(e.state.get()||{}).bulkConfirmData;if(!o||!o.selectedItems?.length){e.toast?.error?.(e.t?.("sirsoft-ecommerce.admin.order.bulk.error")||"일괄 처리에 실패했습니다.");return}const a={ids:o.selectedItems,order_status:o.orderStatus};o.courierId&&(a.carrier_id=o.courierId),o.trackingNumber&&(a.tracking_number=o.trackingNumber);try{await e.api.patch("/api/modules/sirsoft-ecommerce/admin/orders/bulk",a),e.toast?.success?.(e.t?.("sirsoft-ecommerce.admin.order.bulk.success")||"일괄 변경이 완료되었습니다."),e.state.setLocal({selectedItems:[],selectAll:!1,bulkOrderStatus:"",bulkCourier:"",bulkTrackingNumber:""}),e.state.set({bulkConfirmData:null}),e.dataSource?.refetch?.("orders"),e.modal?.close?.()}catch(n){M.error("[executeOrderBulkAction] Error:",n);const i=(n?.response?.data||n?.data||{})?.message||n?.message;e.toast?.error?.(i||e.t?.("sirsoft-ecommerce.admin.order.bulk.error")||"일괄 처리에 실패했습니다.")}}const $e=window.G7Core?.createLogger?.("Ecom:ProductList")??{log:(...r)=>console.log("[Ecom:ProductList]",...r),warn:(...r)=>console.warn("[Ecom:ProductList]",...r),error:(...r)=>console.error("[Ecom:ProductList]",...r)};async function Ur(r,c){const e=r.params||{},{actionId:t,row:o}=e,a=window.G7Core;if(!t||!o?.id)return $e.warn("[handleProductRowAction] actionId 또는 row.id가 없습니다."),{success:!1};switch(t){case"edit":a?.navigate?.(`/admin/ecommerce/products/${o.product_code}/edit`);break;case"copy":a?.state?.setLocal({productList:{copyTargetProduct:o},ui:{showCopyModal:!0}});break;case"delete":try{const s=(await a?.api?.get(`/api/modules/sirsoft-ecommerce/admin/products/${o.id}/can-delete`))?.data;a?.state?.setLocal({productList:{deleteTargetProduct:o,canDelete:s?.canDelete??!0,deleteBlockReason:s?.reason??null,relatedData:s?.relatedData??null},ui:{showDeleteModal:!0}})}catch(n){$e.error("[handleProductRowAction] 삭제 가능 여부 확인 실패:",n),a?.state?.setLocal({productList:{deleteTargetProduct:o,canDelete:!1,deleteBlockReason:"삭제 가능 여부를 확인할 수 없습니다.",relatedData:null},ui:{showDeleteModal:!0}})}break;default:return $e.warn(`[handleProductRowAction] 알 수 없는 액션: ${t}`),{success:!1}}return{success:!0,action:t,targetId:o.id}}const U=window.G7Core?.createLogger?.("Ecom:OrderDetail")??{log:(...r)=>console.log("[Ecom:OrderDetail]",...r),warn:(...r)=>console.warn("[Ecom:OrderDetail]",...r),error:(...r)=>console.error("[Ecom:OrderDetail]",...r)};function rt(){return typeof document>"u"?"":document.querySelector('input[name="batchTrackingNumber"]')?.value?.trim()??""}function Vr(r,c){const e=window.G7Core;if(!e?.state){U.warn("[initOrderDetailForm] G7Core.state를 사용할 수 없습니다.");return}e.state.getLocal();const o=c?.data?.data??e.dataSource?.get?.("order")?.data;if(!o){U.warn("[initOrderDetailForm] order 데이터가 없습니다.");return}e.state.setLocal({form:{recipient_name:o.recipient_name||"",recipient_phone:o.recipient_phone||"",recipient_tel:o.recipient_tel||"",recipient_zipcode:o.recipient_zipcode||"",recipient_address:o.recipient_address||"",recipient_detail_address:o.recipient_detail_address||"",delivery_memo:o.delivery_memo||"",admin_memo:o.admin_memo||""}}),U.log("[initOrderDetailForm] 폼 초기화 완료")}function jr(r,c){const e=window.G7Core,{optionId:t}=r.params||{};if(!e?.state||!t)return;const a=e.state.getLocal().selectedProducts||[];let n;a.includes(t)?n=a.filter(u=>u!==t):n=[...a,t];const i=(e.dataSource?.get?.("order")?.data?.options||[]).length,l=n.length===i&&i>0;e.state.setLocal({selectedProducts:n,selectAll:l})}function Br(r,c){const e=window.G7Core;if(!e?.state)return;const o=e.state.getLocal().selectAll,n=e.dataSource?.get?.("order")?.data?.options||[];if(o)e.state.setLocal({selectedProducts:[],selectAll:!1});else{const s=n.map(i=>i.id);e.state.setLocal({selectedProducts:s,selectAll:!0})}}function Kr(r,c){const e=window.G7Core;if(!e)return;const t=r.params||{},o=t.selectedProducts||[],a=t.batchOrderStatus||"",n=t.batchCarrierId||"",s=t.batchTrackingNumber||rt();if(!a){e.toast?.warning?.(e.t?.("sirsoft-ecommerce.admin.order.bulk.no_status_selected")||"변경할 주문상태를 선택해주세요.");return}if(o.length===0){e.toast?.warning?.(e.t?.("sirsoft-ecommerce.admin.order.bulk.no_items_selected")||"변경할 상품을 선택해주세요.");return}if(a==="cancelled"){const g=t.cancelPg??!0,f=e.dataSource?.get?.("order")?.data,w=f?.options||[],y=o.map(_=>{const p=w.find(S=>S.id===_);return!p||p.option_status==="cancelled"?null:{id:p.id,product_name:p.product_name,product_option_name:p.product_option_name,thumbnail_url:p.product_snapshot?.thumbnail_url||"",unit_price:p.unit_price,quantity:p.quantity,cancel_quantity:p.quantity,option_status:p.option_status,option_status_label:p.option_status_label||p.option_status}}).filter(Boolean);if(y.length===0){e.toast?.warning?.(e.t?.("sirsoft-ecommerce.admin.order.bulk.no_cancellable_items")||"취소할 수 있는 상품이 없습니다. 이미 취소된 상품은 제외됩니다.");return}if(g){e.state.setLocal({cancelItems:y,cancelReason:"",cancelReasonDetail:"",cancelPg:!0,refundPriority:"pg_first",refundEstimate:null,refundLoading:!1,isCancelling:!1,cancelError:null,cancelValidationErrors:null}),e.modal?.open?.("modal_cancel_order");const _=f?.order_number||"";_&&e.dispatch?.({handler:"sirsoft-ecommerce.estimateRefundAmount",params:{orderId:_,cancelItems:y}})}else{const _={};for(const p of y)_[p.id]=p.quantity;e.state.setLocal({bulkConfirmItems:y,changeQuantities:_,batchOrderStatus:a}),e.modal?.open?.("modal_batch_change_confirm")}return}if(["shipping_ready","shipping","delivered"].includes(a)&&(!n||!s)){e.toast?.warning?.(e.t?.("sirsoft-ecommerce.admin.order.bulk.carrier_required")||"해당 상태로 변경하려면 택배사와 송장번호를 모두 입력해주세요.");return}const u=e.dataSource?.get?.("order")?.data?.options||[],d=[],m={};for(const g of o){const f=u.find(w=>w.id===g);f&&(d.push({id:f.id,product_name:f.product_name,product_option_name:f.product_option_name,sku:f.sku,thumbnail_url:f.product_snapshot?.thumbnail_url||"",unit_price:f.unit_price,original_price:f.product_snapshot?.original_price||f.unit_price,quantity:f.quantity,option_status:f.option_status}),m[f.id]=f.quantity)}e.state.setLocal({bulkConfirmItems:d,changeQuantities:m,batchOrderStatus:a,batchCarrierId:n,batchTrackingNumber:s}),e.modal?.open?.("modal_batch_change_confirm")}async function Qr(r,c){const e=window.G7Core,{orderId:t}=r.params||{};if(!e?.state||!t){U.warn("[processOrderDetailBulkChange] orderId가 없습니다.");return}const o=e.state.getLocal?.()??{},a=r.params?.selectedProducts??o.selectedProducts,n=r.params?.batchOrderStatus||o.batchOrderStatus,s=r.params?.batchCarrierId||o.batchCarrierId,i=r.params?.batchTrackingNumber||o.batchTrackingNumber||rt(),l=r.params?.changeQuantities??o.changeQuantities;if(!a||a.length===0){U.warn("[processOrderDetailBulkChange] 선택된 상품이 없습니다.");return}if(!n){U.warn("[processOrderDetailBulkChange] 변경할 상태가 없습니다.");return}const d=e.dataSource?.get?.("order")?.data?.options||[],g={items:a.map(f=>{const w=d.find(y=>y.id===f);return{option_id:f,quantity:l?.[f]??w?.quantity??1}}),status:n};s&&(g.carrier_id=s),i&&(g.tracking_number=i);try{const f=await e.api.patch(`/api/modules/sirsoft-ecommerce/admin/orders/${t}/options/bulk-status`,g);if(f?.success){const w=e.t,y=f.data?.changed_count??a.length;e.toast?.success?.(w?.("sirsoft-ecommerce.admin.order.detail.handler.bulk_change_success",{count:y})??`${y}개 옵션의 상태가 변경되었습니다.`),e.modal?.close?.("modal_batch_change_confirm"),e.dispatch?.({handler:"refetchDataSource",params:{dataSourceId:"order"}}),e.dispatch?.({handler:"refetchDataSource",params:{dataSourceId:"order_logs"}}),e.state.setLocal({selectedProducts:[],selectAll:!1,batchOrderStatus:"",batchCarrierId:"",batchTrackingNumber:"",changeQuantities:{}})}else throw new Error(f?.message||"상태 변경에 실패했습니다.")}catch(f){U.error("[processOrderDetailBulkChange] API 호출 실패:",f);const y=(f?.response?.data||f?.data||{})?.message||f?.message;e.toast?.error?.(y??e.t?.("sirsoft-ecommerce.admin.order.detail.handler.bulk_change_failed")??"상태 변경에 실패했습니다.")}}async function zr(r,c){const e=window.G7Core,{orderId:t}=r.params||{};if(!e?.state||!t)return;const a=e.state.getLocal().form?.admin_memo??"";try{const n=await e.api.patch(`/api/modules/sirsoft-ecommerce/admin/orders/${t}`,{admin_memo:a});if(n?.success)e.toast?.success?.(e.t?.("sirsoft-ecommerce.admin.order.detail.handler.memo_save_success")??"관리자 메모가 저장되었습니다.");else throw new Error(n?.message)}catch(n){U.error("[saveAdminMemo] 저장 실패:",n),e.toast?.error?.(n?.message??"메모 저장에 실패했습니다.")}}function Wr(r,c){const e=window.G7Core;if(!e?.state)return;const{optionId:t,maxQuantity:o}=r.params||{};if(!t||!o)return;const a=r.params?.value??r.$event?.target?.value,n=parseInt(String(a),10),s=Math.max(1,Math.min(isNaN(n)?o:n,o)),l={...e.state.getLocal().changeQuantities||{}};l[t]=s,e.state.setLocal({changeQuantities:l})}function Yr(r,c){const e=window.G7Core;if(!e?.state){U.warn("[openConfirmDepositModal] G7Core.state 를 사용할 수 없습니다.");return}const t=e.dataSource?.get?.("order")?.data,o=t?.depositor_name??"",a=t?.total_due_amount??0;e.state.setLocal({depositorName:o,depositAmount:a,depositConfirmErrors:null,isConfirmingDeposit:!1,markOrderComplete:!1}),e.modal?.open?.("modal_confirm_deposit")}async function Jr(r,c){const e=window.G7Core,{orderId:t}=r.params||{};if(!e?.state||!t){U.warn("[confirmDeposit] orderId 가 없습니다.");return}const o=e.state.getLocal(),a=r.params?.amount??o.depositAmount,n=r.params?.depositorName??o.depositorName??"",s=r.params?.markOrderComplete??o.markOrderComplete??!1,i=s===!0||s==="true";e.state.setLocal({isConfirmingDeposit:!0,depositConfirmErrors:null});try{const l=await e.api.patch(`/api/modules/sirsoft-ecommerce/admin/orders/${t}/confirm-deposit`,{amount:a,depositor_name:n,mark_order_complete:i});if(l?.success)e.state.setLocal({isConfirmingDeposit:!1,depositConfirmErrors:null}),e.toast?.success?.(e.t?.("sirsoft-ecommerce.admin.order.detail.modal.confirm_deposit.confirm_success")??"입금이 확인되어 결제완료 처리되었습니다."),e.modal?.close?.("modal_confirm_deposit"),e.dispatch?.({handler:"refetchDataSource",params:{dataSourceId:"order"}}),e.dispatch?.({handler:"refetchDataSource",params:{dataSourceId:"order_logs"}});else throw new Error(l?.message||"Deposit confirm failed")}catch(l){U.error("[confirmDeposit] 실패:",l);const u=l?.response?.data||l?.data||{},d=u?.errors?.detail||u?.message||l?.message||"",m=e.t?.("sirsoft-ecommerce.admin.order.detail.modal.confirm_deposit.confirm_failed")??"입금 확인에 실패했습니다.";e.state.setLocal({isConfirmingDeposit:!1,depositConfirmErrors:{amount:[d||m]}}),e.toast?.error?.(d||m)}}const Ne=window.G7Core?.createLogger?.("Ecom:CancelOrder")??{log:(...r)=>console.log("[Ecom:CancelOrder]",...r),warn:(...r)=>console.warn("[Ecom:CancelOrder]",...r),error:(...r)=>console.error("[Ecom:CancelOrder]",...r)};let Q=null;function Xr(){Q&&(clearTimeout(Q),Q=null,Ne.log("[clearCancelOrderTimers] debounce 타이머 정리 완료"))}function Zr(r,c){const e=window.G7Core;if(!e?.state)return;const{optionId:t,maxQuantity:o,orderId:a}=r.params||{};if(!t||!o||!a)return;const n=r.params?.value??r.$event?.target?.value,s=parseInt(String(n),10),i=Math.max(1,Math.min(isNaN(s)?o:s,o)),u=(e.state.getLocal().cancelItems||[]).map(m=>m.id===t?{...m,cancel_quantity:i}:m);e.state.setLocal({cancelItems:u});const d=window.__g7ActionContext;Q&&clearTimeout(Q),Q=setTimeout(()=>{const m=window.__g7ActionContext;window.__g7ActionContext=d;try{Te(e,a)}finally{window.__g7ActionContext=m}},500)}async function en(r,c){const e=window.G7Core;if(!e?.state)return;const{orderId:t,cancelItems:o}=r.params||{};t&&await Te(e,t,o)}async function Te(r,c,e){const t=r.state.getLocal(),o=e||t.cancelItems||[],a=t.refundPriority||"pg_first";if(o.length===0){r.state.setLocal({refundEstimate:null});return}const n=o.map(s=>({order_option_id:s.id,cancel_quantity:s.cancel_quantity}));r.state.setLocal({refundLoading:!0});try{const s=await r.api.post(`/api/modules/sirsoft-ecommerce/admin/orders/${c}/estimate-refund`,{items:n,refund_priority:a});if(s?.success)r.state.setLocal({refundEstimate:s.data,refundLoading:!1,cancelError:null});else throw new Error(s?.message||"Estimate failed")}catch(s){Ne.error("[estimateRefundAmount] 실패:",s),r.state.setLocal({refundLoading:!1,refundEstimate:null}),r.toast?.error?.(r.t?.("sirsoft-ecommerce.admin.order.detail.modal.cancel.estimate_failed")??"환불 예상금액 계산에 실패했습니다.")}}function tn(r,c){const e=window.G7Core;if(!e?.state)return;const{priority:t,orderId:o}=r.params||{};if(!t||!o)return;e.state.setLocal({refundPriority:t});const a=window.__g7ActionContext;Q&&clearTimeout(Q),Q=setTimeout(()=>{const n=window.__g7ActionContext;window.__g7ActionContext=a;try{Te(e,o)}finally{window.__g7ActionContext=n}},300)}async function on(r,c){const e=window.G7Core;if(!e?.state)return;const{orderId:t}=r.params||{};if(!t)return;const o=e.state.getLocal(),a=r.params?.cancelItems||o.cancelItems||[],n=r.params?.cancelReason||o.cancelReason||"",s=r.params?.cancelReasonDetail||o.cancelReasonDetail||"",i=r.params?.cancelPg??o.cancelPg??!0,l=r.params?.refundPriority||o.refundPriority||"pg_first",u={type:"partial",cancel_pg:i,refund_priority:l,items:a.map(d=>({order_option_id:d.id,cancel_quantity:d.cancel_quantity}))};n&&(u.reason=n),s&&(u.reason_detail=s),e.state.setLocal({isCancelling:!0,cancelError:null,cancelValidationErrors:null});try{const d=await e.api.post(`/api/modules/sirsoft-ecommerce/admin/orders/${t}/cancel`,u);if(d?.success)e.state.setLocal({isCancelling:!1}),e.toast?.success?.(e.t?.("sirsoft-ecommerce.admin.order.detail.modal.cancel.cancel_success")??"주문이 취소되었습니다."),e.modal?.close?.("modal_cancel_order"),e.dispatch?.({handler:"refetchDataSource",params:{dataSourceId:"order"}}),e.dispatch?.({handler:"refetchDataSource",params:{dataSourceId:"order_logs"}}),e.state.setLocal({selectedProducts:[],selectAll:!1,batchOrderStatus:""});else throw new Error(d?.message||"Cancel failed")}catch(d){Ne.error("[executeCancelOrder] 실패:",d);const m=d?.response?.data||d?.data,f=(d?.response?.status||d?.status)===422,w=m?.errors?.detail,y=m?.errors??null,_=w||m?.message||d?.message||"",p=e.t?.("sirsoft-ecommerce.admin.order.detail.modal.cancel.cancel_failed")??"주문 취소에 실패했습니다.";f?(e.state.setLocal({isCancelling:!1,cancelError:null,cancelValidationErrors:y}),e.toast?.error?.(_||p)):(e.state.setLocal({isCancelling:!1,cancelError:_||p,cancelValidationErrors:null}),e.toast?.error?.(_||p))}}const nt=window.G7Core?.createLogger?.("Ecom:UserCancelOrder")??{log:(...r)=>console.log("[Ecom:UserCancelOrder]",...r),warn:(...r)=>console.warn("[Ecom:UserCancelOrder]",...r),error:(...r)=>console.error("[Ecom:UserCancelOrder]",...r)};let T=null;function ve(r){const c=r?.state?.get?.("_global")?.guestOrderToken,e=r?.dataSource?.get?.("order")?.data?.order_number;return!!(c&&e)}function rn(){T&&(clearTimeout(T),T=null)}function nn(r,c){const e=window.G7Core;if(!e?.state)return;const{optionId:t}=r.params||{};if(!t)return;const a=[...e.state.getLocal().selectedItemIds||[]],n=a.indexOf(t);n>=0?a.splice(n,1):a.push(t);const i=(e.dataSource?.get?.("order")?.data?.options||[]).filter(u=>u.option_status!=="cancelled").map(u=>u.id),l=i.length>0&&i.every(u=>a.includes(u));e.state.setLocal({selectedItemIds:a,selectAllItems:l})}function an(r,c){const e=window.G7Core;if(!e?.state)return;const t=e.state.getLocal(),a=(e.dataSource?.get?.("order")?.data?.options||[]).filter(s=>s.option_status!=="cancelled").map(s=>s.id),n=!t.selectAllItems;e.state.setLocal({selectedItemIds:n?a:[],selectAllItems:n})}function sn(r,c){const e=window.G7Core;if(!e?.state)return;const t=e.dataSource?.get?.("order")?.data;if(!t)return;const a=e.state.getLocal().selectedItemIds||[];if(a.length===0){e.toast?.warning?.(e.t?.("mypage.order_detail.cancel_modal.no_selected_items")??"취소할 상품을 선택해주세요.");return}const s=(t.options||[]).filter(l=>a.includes(l.id)&&l.option_status!=="cancelled").map(l=>({id:l.id,product_name:l.product_name??"",product_option_name:l.product_option_name??"",thumbnail_url:l.product_snapshot?.thumbnail_url||"",unit_price:l.unit_price??0,quantity:l.quantity??1,cancel_quantity:l.quantity??1,option_status:l.option_status??"",selected:!0}));if(s.length===0){e.toast?.warning?.(e.t?.("mypage.order_detail.cancel_modal.no_cancellable_items")??"취소 가능한 상품이 없습니다.");return}e.state.setLocal({cancelItems:s,cancelSelectAll:!0,cancelReason:"",refundEstimate:null,refundLoading:!1,isCancelling:!1,cancelError:null}),e.modal?.open?.("modal_cancel_order");const i=r.params?.orderId||t.id;(i||t.order_number)&&ce(e,i)}function cn(r,c){const e=window.G7Core;if(!e?.state)return;const{optionId:t,orderId:o}=r.params||{};if(!t)return;const n=(e.state.getLocal().cancelItems||[]).map(i=>i.id===t?{...i,selected:!i.selected}:i),s=n.every(i=>i.selected);e.state.setLocal({cancelItems:n,cancelSelectAll:s}),T&&clearTimeout(T),T=setTimeout(()=>{ce(e,o)},500)}function ln(r,c){const e=window.G7Core;if(!e?.state)return;const{orderId:t}=r.params||{},o=e.state.getLocal(),a=!o.cancelSelectAll,n=(o.cancelItems||[]).map(s=>({...s,selected:a}));e.state.setLocal({cancelItems:n,cancelSelectAll:a}),T&&clearTimeout(T),T=setTimeout(()=>{ce(e,t)},500)}function dn(r,c){const e=window.G7Core;if(!e?.state)return;const{optionId:t,maxQuantity:o,orderId:a}=r.params||{};if(!t||!o||!a&&!ve(e))return;const n=r.params?.value??r.$event?.target?.value,s=parseInt(String(n),10),i=Math.max(1,Math.min(isNaN(s)?o:s,o)),u=(e.state.getLocal().cancelItems||[]).map(m=>m.id===t?{...m,cancel_quantity:i}:m);e.state.setLocal({cancelItems:u});const d=window.__g7ActionContext;T&&clearTimeout(T),T=setTimeout(()=>{const m=window.__g7ActionContext;window.__g7ActionContext=d;try{ce(e,a)}finally{window.__g7ActionContext=m}},500)}async function un(r,c){const e=window.G7Core;if(!e?.state)return;const{orderId:t}=r.params||{};!t&&!ve(e)||await ce(e,t)}async function ce(r,c){const e=r.state.getLocal(),t=e.cancelItems||[],o=e.refundPriority||"pg_first",a=t.filter(s=>s.selected);if(a.length===0){r.state.setLocal({refundEstimate:null,refundLoading:!1});return}const n=a.map(s=>({order_option_id:s.id,cancel_quantity:s.cancel_quantity}));r.state.setLocal({refundLoading:!0});try{const s=r.state.get?.("_global")?.guestOrderToken,i=r.dataSource?.get?.("order")?.data?.order_number,l=!!(s&&i),u=l?`/api/modules/sirsoft-ecommerce/guest/orders/${i}/estimate-refund`:`/api/modules/sirsoft-ecommerce/user/orders/${c}/estimate-refund`,d=l?{headers:{"X-Guest-Order-Token":s}}:void 0,m=await r.api.post(u,{items:n,refund_priority:o},d);if(m?.success)r.state.setLocal({refundEstimate:m.data,refundLoading:!1,cancelError:null});else throw new Error(m?.message||"Estimate failed")}catch(s){nt.error("[estimateUserRefund] 실패:",s),r.state.setLocal({refundLoading:!1,refundEstimate:null}),r.toast?.error?.(r.t?.("mypage.order_detail.cancel_modal.estimate_failed")??"환불 예상금액 계산에 실패했습니다.")}}function mn(r,c){const e=window.G7Core;if(!e?.state)return;const{priority:t,orderId:o}=r.params||{};if(!t||!o&&!ve(e))return;e.state.setLocal({refundPriority:t});const a=window.__g7ActionContext;T&&clearTimeout(T),T=setTimeout(()=>{const n=window.__g7ActionContext;window.__g7ActionContext=a;try{ce(e,o)}finally{window.__g7ActionContext=n}},300)}async function pn(r,c){const e=window.G7Core;if(!e?.state)return;const{orderId:t}=r.params||{};if(!t&&!ve(e))return;const o=e.state.getLocal(),n=(o.cancelItems||[]).filter(d=>d.selected),s=r.params?.cancelReason||o.cancelReason||"",i=r.params?.cancelReasonDetail??o.cancelReasonDetail??"",l=r.params?.refundPriority||o.refundPriority||"pg_first";if(n.length===0){e.toast?.warning?.(e.t?.("mypage.order_detail.cancel_modal.no_selected_items")??"취소할 상품을 선택해주세요.");return}if(!s){e.toast?.warning?.(e.t?.("mypage.order_detail.cancel_modal.reason_required")??"취소 사유를 선택해주세요.");return}const u={reason:s,refund_priority:l,items:n.map(d=>({order_option_id:d.id,cancel_quantity:d.cancel_quantity}))};i&&(u.reason_detail=i),e.state.setLocal({isCancelling:!0,cancelError:null,cancelValidationErrors:null});try{const d=e.state.get?.("_global")?.guestOrderToken,m=e.dataSource?.get?.("order")?.data?.order_number,g=!!(d&&m),f=g?`/api/modules/sirsoft-ecommerce/guest/orders/${m}/cancel`:`/api/modules/sirsoft-ecommerce/user/orders/${t}/cancel`,w=g?{headers:{"X-Guest-Order-Token":d}}:void 0,y=await e.api.post(f,u,w);if(y?.success)e.state.setLocal({isCancelling:!1}),e.toast?.success?.(e.t?.("mypage.order_detail.cancel_success")??"주문이 취소되었습니다."),e.modal?.close?.("modal_cancel_order"),e.dispatch?.({handler:"refetchDataSource",params:{dataSourceId:"order"}});else throw new Error(y?.message||"Cancel failed")}catch(d){nt.error("[executeUserCancelOrder] 실패:",d);const m=d?.response?.data||d?.data,g=m?.message||d?.message||"",f=m?.errors??null,w=e.t?.("mypage.order_detail.cancel_modal.cancel_failed")??"주문 취소에 실패했습니다.";e.state.setLocal({isCancelling:!1,cancelError:g||w,cancelValidationErrors:f}),e.toast?.error?.(g||w)}}const at=window.G7Core?.createLogger?.("Ecom:ConfirmOrder")??{log:(...r)=>console.log("[Ecom:ConfirmOrder]",...r),warn:(...r)=>console.warn("[Ecom:ConfirmOrder]",...r),error:(...r)=>console.error("[Ecom:ConfirmOrder]",...r)};function fn(r){const c=r?.state?.get?.("_global")?.guestOrderToken,e=r?.dataSource?.get?.("order")?.data?.order_number;return!!(c&&e)}async function gn(r,c){const e=window.G7Core;if(!e?.state)return;const{orderId:t,optionId:o}=r.params||{};if(!o||!t&&!fn(e)){at.error("[confirmOrderOption] optionId 또는 식별자 누락");return}e.state.setLocal({isConfirming:!0});try{const a=e.state.get?.("_global")?.guestOrderToken,n=e.dataSource?.get?.("order")?.data?.order_number,s=!!(a&&n),i=s?`/api/modules/sirsoft-ecommerce/guest/orders/${n}/options/${o}/confirm`:`/api/modules/sirsoft-ecommerce/user/orders/${t}/options/${o}/confirm`,l=s?{headers:{"X-Guest-Order-Token":a}}:void 0,u=await e.api.post(i,void 0,l);if(u?.success)e.state.setLocal({isConfirming:!1,confirmTarget:null}),e.modal?.close?.("modal_confirm_purchase"),e.dispatch?.({handler:"refetchDataSource",params:{dataSourceId:"order"}}),e.toast?.success?.(u.message??e.t?.("sirsoft-ecommerce::messages.order.confirmed")??"구매확정이 완료되었습니다.");else throw new Error(u?.message||"Confirm failed")}catch(a){at.error("[confirmOrderOption] 실패:",a);const n=a?.response?.data?.message||a?.data?.message||a?.message||e.t?.("sirsoft-ecommerce::messages.order.cannot_confirm")||"구매확정에 실패했습니다.";e.state.setLocal({isConfirming:!1}),e.toast?.error?.(n)}}const st=window.G7Core?.createLogger?.("Ecom:ChangeShippingAddress")??{log:(...r)=>console.log("[Ecom:ChangeShippingAddress]",...r),warn:(...r)=>console.warn("[Ecom:ChangeShippingAddress]",...r),error:(...r)=>console.error("[Ecom:ChangeShippingAddress]",...r)};function _n(r){const c=r?.state?.get?.("_global")?.guestOrderToken,e=r?.dataSource?.get?.("order")?.data?.order_number;return!!(c&&e)}async function wn(r,c){const e=window.G7Core;if(!e?.state)return;const{orderId:t}=r.params||{},o=_n(e);if(!t&&!o){st.error("[changeShippingAddress] orderId 또는 비회원 식별자 누락");return}const a=e.state.get?.("_global")??{},s=(o?"manual":a.changeAddressMode??"saved")==="saved"?{address_id:a.selectedAddressId}:a.editingShippingAddress;e.state.setLocal({isSubmittingAddress:!0});try{const i=a.guestOrderToken,l=e.dataSource?.get?.("order")?.data?.order_number,u=o?`/api/modules/sirsoft-ecommerce/guest/orders/${l}/shipping-address`:`/api/modules/sirsoft-ecommerce/user/orders/${t}/shipping-address`,d=o?{headers:{"X-Guest-Order-Token":i}}:void 0,m=await e.api.put(u,s,d);if(m?.success)e.state.setLocal({isSubmittingAddress:!1}),e.modal?.close?.("changeAddressModal"),e.dispatch?.({handler:"refetchDataSource",params:{dataSourceId:"order"}}),e.toast?.success?.(m.message??e.t?.("mypage.order_detail.shipping_address_changed")??"배송지가 변경되었습니다.");else throw new Error(m?.message||"Change shipping address failed")}catch(i){st.error("[changeShippingAddress] 실패:",i),e.state.setLocal({isSubmittingAddress:!1});const l=i?.response?.data?.message||i?.data?.message||i?.message||"배송지 변경에 실패했습니다.";e.toast?.error?.(l)}}const Re=window.G7Core?.createLogger?.("Ecom:Review")??{log:(...r)=>console.log("[Ecom:Review]",...r),warn:(...r)=>console.warn("[Ecom:Review]",...r),error:(...r)=>console.error("[Ecom:Review]",...r)};async function yn(r,c){const e=window.G7Core;if(!e?.state)return;const{orderId:t,optionId:o,productId:a}=r.params||{};if(!t||!o||!a){Re.error("[submitReview] orderId, optionId 또는 productId 누락");return}const n=e.state.getLocal(),s=r.params?.rating??n.reviewRating??5,i=(r.params?.content??n.reviewContent??"").trim(),u=(r.params?.images??n.reviewImages??[]).map(d=>d instanceof File?d:d?.file instanceof File||d?.file instanceof Blob||d?.file&&typeof d.file.name=="string"&&d.file.size>0?d.file:d instanceof Blob?d:null).filter(d=>d!==null);if(!s||s<1||s>5){e.toast?.error?.(e.t?.("sirsoft-ecommerce.user.review.validation.rating_required")??"별점을 선택해 주세요.");return}if(i.length<10){e.state.setLocal({reviewError:e.t?.("sirsoft-ecommerce.user.review.validation.content_min")??"리뷰 내용을 최소 10자 이상 입력해 주세요."});return}e.state.setLocal({isSubmittingReview:!0,reviewError:null});try{const d=await e.api.post("/api/modules/sirsoft-ecommerce/user/reviews",{order_option_id:o,product_id:a,rating:s,content:i});if(!d?.success)throw new Error(d?.message||"Review creation failed");const m=d.data?.id;if(m&&u.length>0)for(let g=0;gconsole.log("[Ecom:ShippingPolicyForm]",...r),warn:(...r)=>console.warn("[Ecom:ShippingPolicyForm]",...r),error:(...r)=>console.error("[Ecom:ShippingPolicyForm]",...r)},hn=["fixed","conditional_free","per_quantity","per_weight","per_volume","per_volume_weight","per_amount"],Cn=["conditional_free"],bn=["range_amount","range_quantity","range_weight","range_volume","range_volume_weight"],In=["api"],Sn=["per_quantity","per_weight","per_volume","per_volume_weight","per_amount"],vn={shipping_method:"parcel",custom_shipping_name:null,carrier:null,currency_code:"KRW",charge_policy:"fixed",base_fee:0,free_threshold:null,ranges:null,api_endpoint:null,api_request_fields:null,api_response_fee_field:null,api_config:null,extra_fee_enabled:!1,extra_fee_settings:[],extra_fee_multiply:!1,is_active:!0};function ee(r){return{showBaseFee:hn.includes(r),showFreeThreshold:Cn.includes(r),showRanges:bn.includes(r),showApiSettings:In.includes(r),showUnitValue:Sn.includes(r)}}function G(){const r=window.G7Core;return r?.state?r:(I.warn("G7Core.state not available"),null)}function O(r,c){r.setLocalState?r.setLocalState(c):G()?.state?.setLocal(c)}function E(r){return[...(r.state.getLocal?.()??{}).form?.country_settings??[]]}function Ln(r,c){const e=r.params||{},t=e.policy,o=!!e.isEdit,a=!!e.isCopy;if(e.availableCountries,!G())return;const s={activeCountryTab:0,rangeErrors:{}};if((o||a)&&t){const i=t.country_settings??[],u=i[0]?.charge_policy??"fixed",d=ee(u);Object.assign(s,d),I.log("[initShippingPolicyForm]",o?"Edit":"Copy","mode, countries:",i.length,"first charge_policy:",u)}else{const i=ee("fixed");Object.assign(s,i),I.log("[initShippingPolicyForm] Create mode, default flags")}O(c,s)}function xn(r,c){const t=(r.params||{}).country_code??"";if(!t){I.warn("[addCountrySetting] country_code is empty");return}const o=G();if(!o)return;const a=E(o);if(a.some(u=>u.country_code===t)){I.warn("[addCountrySetting] Duplicate country_code:",t);return}const n={country_code:t,...vn};a.push(n);const s=a.length-1,i=ee(n.charge_policy),l={"form.country_settings":a,activeCountryTab:s,...i};O(c,l),I.log("[addCountrySetting] Added country:",t,"index:",s)}function Pn(r,c){const e=r.params||{},t=Number(e.index);if(isNaN(t)||t<0){I.warn("[removeCountrySetting] Invalid index:",e.index);return}const o=G();if(!o)return;const a=E(o),n=o.state.getLocal?.()??{};if(t>=a.length){I.warn("[removeCountrySetting] Index out of bounds:",t);return}const s=a[t].country_code;a.splice(t,1);const i=n.activeCountryTab??0;let l=i;a.length===0?l=0:i>=a.length?l=a.length-1:i>t&&(l=i-1);const u={...n.rangeErrors??{}};u[s]=[];const d=a[l],m=ee(d?d.charge_policy:"fixed"),g={"form.country_settings":a,activeCountryTab:l,rangeErrors:u,...m};O(c,g),I.log("[removeCountrySetting] Removed index:",t,"country:",s,"newTab:",l)}function An(r,c){const e=r.params||{},t=Number(e.index);if(isNaN(t)||t<0){I.warn("[switchCountryTab] Invalid index:",e.index);return}const o=G();if(!o)return;const a=E(o);if(t>=a.length){I.warn("[switchCountryTab] Index out of bounds:",t);return}const n=a[t],s=ee(n.charge_policy),i={activeCountryTab:t,...s};O(c,i),I.log("[switchCountryTab] Switched to tab:",t,"country:",n.country_code,"charge_policy:",n.charge_policy)}function kn(r,c){const e=r.params||{},t=e.field??"",o=e.value;if(!t){I.warn("[updateCountryField] field is empty");return}const a=G();if(!a)return;const n=a.state.getLocal?.()??{},s=Number(n.activeCountryTab??0),i=E(a);if(s>=i.length){I.warn("[updateCountryField] Index out of bounds:",s);return}i[s]={...i[s],[t]:o},O(c,{"form.country_settings":i}),I.log("[updateCountryField] Updated",t,"=",o,"at index:",s)}function Gn(r,c){const e=r.params||{},t=e.value??"",o=Number(e.index??0);if(!t){I.warn("[onChargePolicyChange] charge_policy value is empty");return}const a=G();if(!a)return;const n=ee(t);I.log("[onChargePolicyChange]",t,n,"countryIndex:",o);const s=E(a),i=s[o];if(!i){I.warn("[onChargePolicyChange] No country setting at index:",o);return}const l={...i,charge_policy:t};if(n.showBaseFee||(l.base_fee=0),n.showFreeThreshold||(l.free_threshold=null),!n.showRanges&&!n.showUnitValue&&(l.ranges=null),n.showApiSettings||(l.api_endpoint=null,l.api_request_fields=null,l.api_response_fee_field=null,l.api_config=null),n.showRanges&&(!l.ranges||!l.ranges.tiers)){const d=t.replace("range_","");l.ranges={type:d,tiers:[{min:0,max:null,fee:0}]}}n.showUnitValue&&(!l.ranges||!l.ranges.unit_value)&&(l.ranges={type:t,unit_value:1}),s[o]=l;const u={"form.country_settings":s,...n};O(c,u)}function On(r,c){const e=r.params||{},t=Number(e.index??0),o=G();if(!o)return;const a=E(o),n=a[t];if(!n){I.warn("[addRangeTier] No country setting at index:",t);return}const s=[...n.ranges?.tiers??[]],i=s[s.length-1],u={min:i?.max!=null?i.max+1:0,max:null,fee:0};s.push(u),a[t]={...n,ranges:{...n.ranges??{},tiers:s}},O(c,{"form.country_settings":a}),Le(o,c,t,s,n.country_code),I.log("[addRangeTier] Added tier, total:",s.length)}function En(r,c){const e=r.params||{},t=Number(e.countryIndex??0),o=Number(e.tierIndex);if(isNaN(o)||o<0){I.warn("[removeRangeTier] Invalid tierIndex:",e.tierIndex);return}const a=G();if(!a)return;const n=E(a),s=n[t];if(!s){I.warn("[removeRangeTier] No country setting at index:",t);return}const i=[...s.ranges?.tiers??[]];if(o>=i.length){I.warn("[removeRangeTier] tierIndex out of bounds:",o);return}i.splice(o,1),n[t]={...s,ranges:{...s.ranges??{},tiers:i}},O(c,{"form.country_settings":n}),Le(a,c,t,i,s.country_code),I.log("[removeRangeTier] Removed tier:",o,"remaining:",i.length)}function Dn(r,c){const e=r.params||{},t=Number(e.countryIndex??0),o=Number(e.tierIndex),a=e.field,n=e.value;if(isNaN(o)||o<0){I.warn("[updateRangeTierField] Invalid tierIndex:",e.tierIndex);return}if(!["min","max","fee"].includes(a)){I.warn("[updateRangeTierField] Invalid field:",a);return}const s=G();if(!s)return;const i=E(s),l=i[t];if(!l){I.warn("[updateRangeTierField] No country setting at index:",t);return}const u=[...l.ranges?.tiers??[]];if(o>=u.length){I.warn("[updateRangeTierField] tierIndex out of bounds:",o);return}let d;a==="max"?d=n===""||n===null||n===void 0?null:Number(n):d=Number(n),u[o]={...u[o],[a]:d},i[t]={...l,ranges:{...l.ranges??{},tiers:u}},O(c,{"form.country_settings":i}),Le(s,c,t,u,l.country_code)}function Fn(r,c){const e=r.params||{},t=Number(e.countryIndex??0),o=G();if(!o)return;const n=E(o)[t];if(!n)return;const s=n.ranges?.tiers??[];Le(o,c,t,s,n.country_code)}function Le(r,c,e,t,o){const n={...(r.state.getLocal?.()??{}).rangeErrors??{}};if(t.length===0){n[o]=[],O(c,{rangeErrors:n});return}const s=new Array(t.length).fill(null).map(()=>({}));let i=!1;const l=r.t;for(let u=0;u=d.max&&(s[u].min=l?.("sirsoft-ecommerce.validation.shipping_policy.ranges.min_less_than_max")??"시작값이 종료값보다 작아야 합니다.",i=!0),u=i.length){I.warn("[removeExtraFeeRow] feeIndex out of bounds:",o);return}i.splice(o,1),n[t]={...s,extra_fee_settings:i},O(c,{"form.country_settings":n}),I.log("[removeExtraFeeRow] Removed index:",o,"remaining:",i.length)}function Tn(r,c){const e=r.params||{},t=Number(e.countryIndex??0),o=e.settings;if(!o||!Array.isArray(o)){I.warn("[applyExtraFeeTemplate] Invalid settings:",o);return}const a=G();if(!a)return;const n=E(a),s=n[t];if(!s){I.warn("[applyExtraFeeTemplate] No country setting at index:",t);return}n[t]={...s,extra_fee_settings:o},O(c,{"form.country_settings":n});const i=a.t,l=i?.("sirsoft-ecommerce.admin.shipping_policy.form.template_applied")??"템플릿이 적용되었습니다.";a.toast?.success?.(l),I.log("[applyExtraFeeTemplate] Applied template with",o.length,"rows")}function Rn(r,c){const e=r.params||{},t=parseFloat(e.value)||1,o=G();if(!o)return;const a=o.state.getLocal?.()??{},n=Number(a.activeCountryTab??0),s=E(o),i=s[n];i&&(s[n]={...i,ranges:{...i.ranges??{},type:i.charge_policy,unit_value:t}},O(c,{"form.country_settings":s}),I.log("[updateUnitValue] Updated unit_value:",t))}function Mn(r,c){const e=G();if(!e)return;const t=e.state.getLocal?.()??{},o=Number(t.activeCountryTab??0),a=E(e),n=a[o];if(!n)return;const s=[...n.api_request_fields??[]];s.push(""),a[o]={...n,api_request_fields:s},O(c,{"form.country_settings":a}),I.log("[addApiRequestField] Added field, total:",s.length)}function Hn(r,c){const e=r.params||{},t=Number(e.fieldIndex),o=e.value??"";if(isNaN(t)||t<0){I.warn("[updateApiRequestField] Invalid fieldIndex:",e.fieldIndex);return}const a=G();if(!a)return;const n=a.state.getLocal?.()??{},s=Number(n.activeCountryTab??0),i=E(a),l=i[s];if(!l)return;const u=[...l.api_request_fields??[]];t>=u.length||(u[t]=o,i[s]={...l,api_request_fields:u},O(c,{"form.country_settings":i}),I.log("[updateApiRequestField] Updated index:",t,"value:",o))}function qn(r,c){const e=r.params||{},t=Number(e.fieldIndex);if(isNaN(t)||t<0){I.warn("[removeApiRequestField] Invalid fieldIndex:",e.fieldIndex);return}const o=G();if(!o)return;const a=o.state.getLocal?.()??{},n=Number(a.activeCountryTab??0),s=E(o),i=s[n];if(!i)return;const l=[...i.api_request_fields??[]];t>=l.length||(l.splice(t,1),s[n]={...i,api_request_fields:l},O(c,{"form.country_settings":s}),I.log("[removeApiRequestField] Removed index:",t,"remaining:",l.length))}function Un(r,c){const t=(r.params||{}).field??"";if(!t){I.warn("[toggleApiRequestField] Missing field param");return}const o=G();if(!o)return;const a=o.state.getLocal?.()??{},n=Number(a.activeCountryTab??0),s=E(o),i=s[n];if(!i)return;const l=[...i.api_request_fields??[]],u=l.indexOf(t);u>=0?l.splice(u,1):l.push(t);const d=l.length>0?l:null;s[n]={...i,api_request_fields:d},O(c,{"form.country_settings":s}),I.log("[toggleApiRequestField] Toggled:",t,"selected:",d)}function Vn(r,c){const e=r.params||{},t=e.field??"";if(!t){I.warn("[updateApiConfigField] Missing field param");return}const o=G();if(!o)return;const a=o.state.getLocal?.()??{},n=Number(a.activeCountryTab??0),s=E(o),i=s[n];if(!i)return;const l={...i.api_config??{},[t]:e.value};s[n]={...i,api_config:l},O(c,{"form.country_settings":s}),I.log("[updateApiConfigField] Updated",t,"at index:",n)}function jn(r,c){const e=r.params||{},t=e.field??"";if(!t){I.warn("[updateApiFieldMap] Missing field param");return}const o=G();if(!o)return;const a=o.state.getLocal?.()??{},n=Number(a.activeCountryTab??0),s=E(o),i=s[n];if(!i)return;const l=e.value??"",u={...i.api_config?.field_map??{}};l.trim()===""?delete u[t]:u[t]=l;const d={...i.api_config??{},field_map:Object.keys(u).length>0?u:null};s[n]={...i,api_config:d},O(c,{"form.country_settings":s}),I.log("[updateApiFieldMap] Mapped",t,"→",l||"(removed)")}function Bn(r,c){const e=G();if(!e)return;const t=e.state.getLocal?.()??{},o=Number(t.activeCountryTab??0),n=E(e)[o];if(n){if(!n.api_endpoint){e.toast?.warning?.(e.t?.("sirsoft-ecommerce.admin.shipping_policy.form.api_test_endpoint_required")??"Endpoint required");return}e.state.set?.({apiTestLoading:!0,apiTestResult:null}),e.dispatch({handler:"apiCall",target:"/api/modules/sirsoft-ecommerce/admin/shipping-policies/test-api-call",auth_required:!0,params:{method:"POST",body:{endpoint:n.api_endpoint,request_fields:n.api_request_fields,config:n.api_config??{},sample:{country_code:n.country_code}}},onSuccess:{handler:"setState",params:{target:"global",apiTestLoading:!1,apiTestResult:"{{response.data}}"}},onError:{handler:"setState",params:{target:"global",apiTestLoading:!1,apiTestResult:"{{error.errors ?? error}}"}}})}}function Kn(r,c){const e=r.params||{},t=Number(e.feeIndex),o=e.field,a=e.value;if(isNaN(t)||t<0){I.warn("[updateExtraFeeField] Invalid feeIndex:",e.feeIndex);return}if(!["zipcode","fee","region"].includes(o)){I.warn("[updateExtraFeeField] Invalid field:",o);return}const n=G();if(!n)return;const s=n.state.getLocal?.()??{},i=Number(s.activeCountryTab??0),l=E(n),u=l[i];if(!u)return;const d=[...u.extra_fee_settings??[]];if(t>=d.length)return;const m=o==="fee"?parseFloat(a)||0:a;d[t]={...d[t],[o]:m},l[i]={...u,extra_fee_settings:d},O(c,{"form.country_settings":l}),I.log("[updateExtraFeeField] Updated index:",t,o,"=",m)}const me=window.G7Core?.createLogger?.("Handler:InitCategory")??{log:(...r)=>console.log("[Handler:InitCategory]",...r),warn:(...r)=>console.warn("[Handler:InitCategory]",...r),error:(...r)=>console.error("[Handler:InitCategory]",...r)};function it(r,c){for(const e of r){if(e.id===c)return e;if(e.children&&e.children.length>0){const t=it(e.children,c);if(t)return t}}return null}function ct(r){return new URLSearchParams(window.location.search).get(r)}function Qn(r){return window.G7Core?.state?.getDataSource?.(r)}async function zn(r,c,e=30,t=100){for(let o=0;o0)return n;await new Promise(s=>setTimeout(s,t))}return null}async function Wn(r,c){const e=window.G7Core,t=ct("id"),o=ct("mode");if(!t){me.log("[initCategoryFromUrl] No id parameter in URL");return}if(!e?.state?.set){me.warn("[initCategoryFromUrl] G7Core.state.set not available");return}const a=await zn("categories",l=>l?.data?.data);if(!a){me.warn("[initCategoryFromUrl] categories data not available after waiting");return}const n=parseInt(t,10),s=it(a,n);if(!s){me.warn("[initCategoryFromUrl] Category not found with id:",n);return}const i=o==="edit"?"edit":"view";e.state.set({selectedCategoryId:s.id,selectedCategory:s,panelMode:i}),me.log("[initCategoryFromUrl] Category initialized from URL:",{id:n,mode:i})}const pe=window.G7Core?.createLogger?.("Handler:InitBrand")??{log:(...r)=>console.log("[Handler:InitBrand]",...r),warn:(...r)=>console.warn("[Handler:InitBrand]",...r),error:(...r)=>console.error("[Handler:InitBrand]",...r)};function lt(r){return new URLSearchParams(window.location.search).get(r)}function Yn(r){return window.G7Core?.state?.getDataSource?.(r)}async function Jn(r,c,e=30,t=100){for(let o=0;o0)return n;await new Promise(s=>setTimeout(s,t))}return null}async function Xn(r,c){const e=window.G7Core,t=lt("id"),o=lt("mode");if(!t){pe.log("[initBrandFromUrl] No id parameter in URL");return}if(!e?.state?.set){pe.warn("[initBrandFromUrl] G7Core.state.set not available");return}const a=await Jn("brands",l=>l?.data?.data);if(!a){pe.warn("[initBrandFromUrl] brands data not available after waiting");return}const n=parseInt(t,10),s=a.find(l=>l.id===n);if(!s){pe.warn("[initBrandFromUrl] Brand not found with id:",n);return}const i=o==="edit"?"edit":"view";e.state.set({selectedBrandId:s.id,selectedBrand:s,panelMode:i}),pe.log("[initBrandFromUrl] Brand initialized from URL:",{id:n,mode:i})}const fe=window.G7Core?.createLogger?.("Handler:InitCommonInfo")??{log:(...r)=>console.log("[Handler:InitCommonInfo]",...r),warn:(...r)=>console.warn("[Handler:InitCommonInfo]",...r),error:(...r)=>console.error("[Handler:InitCommonInfo]",...r)};function dt(r){return new URLSearchParams(window.location.search).get(r)}function Zn(r){return window.G7Core?.state?.getDataSource?.(r)}async function ea(r,c,e=30,t=100){for(let o=0;o0)return n;await new Promise(s=>setTimeout(s,t))}return null}async function ta(r,c){const e=window.G7Core,t=dt("id"),o=dt("mode");if(!t){fe.log("[initCommonInfoFromUrl] No id parameter in URL");return}if(!e?.state?.set){fe.warn("[initCommonInfoFromUrl] G7Core.state.set not available");return}const a=await ea("commonInfos",l=>l?.data);if(!a){fe.warn("[initCommonInfoFromUrl] commonInfos data not available after waiting");return}const n=parseInt(t,10),s=a.find(l=>l.id===n);if(!s){fe.warn("[initCommonInfoFromUrl] Common info not found with id:",n);return}const i=o==="edit"?"edit":"view";e.state.set({selectedId:s.id,selectedItem:s,panelMode:i}),fe.log("[initCommonInfoFromUrl] Common info initialized from URL:",{id:n,mode:i})}const ge=window.G7Core?.createLogger?.("Handler:InitNotice")??{log:(...r)=>console.log("[Handler:InitNotice]",...r),warn:(...r)=>console.warn("[Handler:InitNotice]",...r),error:(...r)=>console.error("[Handler:InitNotice]",...r)};function ut(r){return new URLSearchParams(window.location.search).get(r)}function oa(r){return window.G7Core?.state?.getDataSource?.(r)}async function ra(r,c,e=30,t=100){for(let o=0;o0)return n;await new Promise(s=>setTimeout(s,t))}return null}async function na(r,c){const e=window.G7Core,t=ut("id"),o=ut("mode");if(!t){ge.log("[initNoticeFromUrl] No id parameter in URL");return}if(!e?.state?.set){ge.warn("[initNoticeFromUrl] G7Core.state.set not available");return}const a=await ra("templates",l=>l?.data);if(!a){ge.warn("[initNoticeFromUrl] templates data not available after waiting");return}const n=parseInt(t,10),s=a.find(l=>l.id===n);if(!s){ge.warn("[initNoticeFromUrl] Template not found with id:",n);return}const i=o==="edit"?"edit":"view";e.state.set({selectedTemplateId:s.id,selectedTemplate:s,panelMode:i}),ge.log("[initNoticeFromUrl] Template initialized from URL:",{id:n,mode:i})}const te={updateProductField:Ct,updateOptionField:bt,calculateCurrencyPrices:ae,initPreferredCurrency:kt,initPreferredShippingCountry:Dt,setDateRange:$t,setDefaultOption:Nt,toggleOption:Tt,toggleProductOptions:Be,toggleAllOptionsInRow:Rt,getProductOptionStates:Mt,syncProductSelection:Ht,generateCopyProductCode:qt,copyProduct:Ut,selectCategory:jt,selectCategoryMobile:Bt,addCategoryToSelection:Kt,removeCategoryFromSelection:Qt,getCategoryBreadcrumb:zt,validateCategoryPath:Yt,initCategoryInfosFromProduct:Wt,getBrandName:Jt,getBrandDescription:Xt,updatePrice:oo,calculateTotalOptionStock:ro,validatePriceRelation:no,addOptionInput:yo,removeOptionInput:ho,updateOptionInput:Co,generateOptions:bo,deleteOption:Io,applyOptionAddTool:So,addRequiredItem:vo,updateRequiredItem:Lo,removeRequiredItem:xo,reorderRequiredItems:Po,addAdditionalOption:Ao,updateAdditionalOption:ko,removeAdditionalOption:Go,reorderAdditionalOptions:Oo,clearAdditionalOptions:Eo,addAdditionalOptionValue:Do,updateAdditionalOptionValue:Fo,removeAdditionalOptionValue:$o,uploadImages:Mo,setThumbnail:Ho,reorderImages:qo,updateDescription:Uo,confirmSelectNoticeTemplate:Vo,selectNoticeTemplate:Xe,updateNoticeItem:jo,removeNoticeItem:Bo,reorderNoticeItems:Ko,fillNoticeWithValue:Qo,switchNoticeMode:zo,updateNewTemplateName:Wo,addNoticeItem:Yo,updateNoticeItemName:Jo,saveAsNoticeTemplate:Xo,confirmSaveNoticeTemplate:tr,fillTemplateFieldsWithDetailReference:Zo,fillNoticeItemsWithDetailReference:er,toggleLabel:or,generateProductCode:rr,getShippingPolicyInfo:nr,getCommonInfoContent:ar,updateShoppingIntegration:sr,updateShippingType:ir,updateIdentificationCode:cr,openLabelPeriodModal:lr,saveLabelPeriod:dr,removeLabelPeriod:ur,updateActivityLogSort:mr,updateActivityLogPerPage:pr,setDefaultShippingPolicy:fr,setLabelDatePreset:_r,toggleDefaultShippingPolicy:gr,toggleLabelAssignment:wr,saveLabelSettings:Ir,deleteLabel:br,updateLabelPeriodInline:yr,setLabelDatePresetInline:hr,confirmUncheckLabel:Cr,removeDescriptionLocale:Sr,showAddLocaleModal:vr,addDescriptionLocale:Lr,setDefaultOptionFromGrid:co,addOptionRow:uo,updateFormOptionField:lo,recalculateOptionPriceAdjustments:mo,bulkUpdate:xr,buildConfirmData:kr,buildOrderColumns:Gr,toggleArrayValue:Or,toggleVisibleFilter:Er,syncOrderSelection:Dr,handleOrderRowAction:Fr,processOrderBulkAction:$r,buildOrderBulkConfirmData:Hr,executeOrderBulkAction:qr,downloadOrderExcel:Nr,saveVisibleColumns:Tr,loadVisibleColumns:Rr,loadVisibleFilters:Mr,handleProductRowAction:Ur,initOrderDetailForm:Vr,toggleProductSelection:jr,toggleAllProducts:Br,buildOrderDetailBulkConfirmData:Kr,processOrderDetailBulkChange:Qr,saveAdminMemo:zr,updateChangeQuantity:Wr,openConfirmDepositModal:Yr,confirmDeposit:Jr,initShippingPolicyForm:Ln,addCountrySetting:xn,removeCountrySetting:Pn,switchCountryTab:An,updateCountryField:kn,onChargePolicyChange:Gn,addRangeTier:On,removeRangeTier:En,updateRangeTierField:Dn,validateRangeTiers:Fn,addExtraFeeRow:$n,removeExtraFeeRow:Nn,applyExtraFeeTemplate:Tn,updateUnitValue:Rn,addApiRequestField:Mn,updateApiRequestField:Hn,removeApiRequestField:qn,toggleApiRequestField:Un,updateApiConfigField:Vn,updateApiFieldMap:jn,testShippingApi:Bn,updateExtraFeeField:Kn,updateCancelQuantity:Zr,estimateRefundAmount:en,changeRefundPriority:tn,executeCancelOrder:on,clearCancelOrderTimers:Xr,toggleItemSelection:nn,toggleSelectAllItems:an,initUserCancelItems:sn,toggleUserCancelItem:cn,toggleUserCancelSelectAll:ln,updateUserCancelQuantity:dn,estimateUserRefund:un,changeUserRefundPriority:mn,executeUserCancelOrder:pn,clearUserCancelOrderTimers:rn,confirmOrderOption:gn,changeShippingAddress:wn,submitReview:yn,initCategoryFromUrl:Wn,initBrandFromUrl:Xn,initCommonInfoFromUrl:ta,initNoticeFromUrl:na},V="sirsoft-ecommerce",_e=window.G7Core?.createLogger?.(`Module:${V}`)??{log:(...r)=>console.log(`[Module:${V}]`,...r),warn:(...r)=>console.warn(`[Module:${V}]`,...r),error:(...r)=>console.error(`[Module:${V}]`,...r)};function mt(r=!1){const c=window.G7Core?.getActionDispatcher?.();if(c)Object.entries(te).forEach(([e,t])=>{const o=`${V}.${e}`;c.registerHandler(o,t,{category:"module",source:V})}),_e.log(`${Object.keys(te).length} handler(s) registered:`,Object.keys(te).map(e=>`${V}.${e}`));else if(r){let e=0;const t=50,o=()=>{const a=window.G7Core?.getActionDispatcher?.();a?(Object.entries(te).forEach(([n,s])=>{const i=`${V}.${n}`;a.registerHandler(i,s,{category:"module",source:V})}),_e.log(`${Object.keys(te).length} handler(s) registered:`,Object.keys(te).map(n=>`${V}.${n}`))):(e++,e<=t?(_e.warn(`ActionDispatcher not found, retrying... (${e}/${t})`),setTimeout(o,100)):_e.error("Failed to register handlers: ActionDispatcher not available after maximum retries"))};o()}else _e.warn("ActionDispatcher not found, handlers not registered")}function Me(){if(document.readyState==="loading")document.addEventListener("DOMContentLoaded",()=>mt(!0));else{const r=!!window.G7Core?.getActionDispatcher?.();mt(!r)}}return Me(),typeof window<"u"&&(window.__SirsoftEcommerce={identifier:V,handlers:Object.keys(te),initModule:Me}),xe.initModule=Me,Object.defineProperty(xe,Symbol.toStringTag,{value:"Module"}),xe}({}); -//# sourceMappingURL=module.iife.js.map diff --git a/modules/_bundled/sirsoft-ecommerce/dist/js/module.iife.js.map b/modules/_bundled/sirsoft-ecommerce/dist/js/module.iife.js.map deleted file mode 100644 index 8142f67c..00000000 --- a/modules/_bundled/sirsoft-ecommerce/dist/js/module.iife.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"module.iife.js","sources":["../../resources/js/handlers/calculateCurrencyPrices.ts","../../resources/js/handlers/updateProductField.ts","../../resources/js/handlers/updateOptionField.ts","../../resources/js/handlers/initPreferredCurrency.ts","../../resources/js/handlers/initPreferredShippingCountry.ts","../../resources/js/handlers/setDateRange.ts","../../resources/js/handlers/setDefaultOption.ts","../../resources/js/handlers/optionSelection.ts","../../resources/js/handlers/generateCopyProductCode.ts","../../resources/js/handlers/copyProduct.ts","../../resources/js/handlers/categoryHandlers.ts","../../resources/js/handlers/brandHandlers.ts","../../resources/js/handlers/priceHandlers.ts","../../resources/js/handlers/productOptionHandlers.ts","../../resources/js/handlers/optionHandlers.ts","../../resources/js/handlers/imageHandlers.ts","../../resources/js/handlers/descriptionHandlers.ts","../../resources/js/handlers/noticeHandlers.ts","../../resources/js/handlers/miscHandlers.ts","../../resources/js/handlers/labelHandlers.ts","../../resources/js/handlers/descriptionLocaleHandlers.ts","../../resources/js/handlers/bulkUpdateHandlers.ts","../../resources/js/handlers/orderHandlers.ts","../../resources/js/handlers/productListHandlers.ts","../../resources/js/handlers/orderDetailHandlers.ts","../../resources/js/handlers/cancelOrderHandlers.ts","../../resources/js/handlers/userCancelOrderHandlers.ts","../../resources/js/handlers/userConfirmOrderHandlers.ts","../../resources/js/handlers/userChangeShippingAddressHandlers.ts","../../resources/js/handlers/userReviewHandlers.ts","../../resources/js/handlers/shippingPolicyFormHandlers.ts","../../resources/js/handlers/initCategoryFromUrl.ts","../../resources/js/handlers/initBrandFromUrl.ts","../../resources/js/handlers/initCommonInfoFromUrl.ts","../../resources/js/handlers/initNoticeFromUrl.ts","../../resources/js/handlers/index.ts","../../resources/js/index.ts"],"sourcesContent":["// e2e:allow base_unit 환산 공식(÷base_unit) 정확성은 단위 테스트(calculateCurrencyPrices.test.ts: USD base ¥0 회귀 + KRW base 등가)로 전수 검증. 다통화 표시 회귀는 product-option-multicurrency-readonly.spec.ts 가 구조적으로 차단.\n/**\n * 실시간 환율 계산 핸들러\n *\n * 기본 통화 가격을 입력받아 환율 기반으로 다중 통화 가격을 계산합니다.\n * 옵션 가격 인라인 편집 시 외화 가격 실시간 업데이트에 사용됩니다.\n */\n\nimport type { ActionContext } from '../types';\n\ninterface Currency {\n code: string;\n name?: Record;\n exchange_rate?: number | null;\n is_default?: boolean;\n rounding_unit?: string;\n rounding_method?: string;\n base_unit?: number;\n symbol?: string;\n decimal_places?: number;\n}\n\n/**\n * 통화 코드별 소수 자릿수 폴백 (설정에 decimal_places 가 없을 때만 사용).\n */\nconst DECIMAL_PLACES_FALLBACK: Record = {\n KRW: 0,\n JPY: 0,\n};\n\n/**\n * 통화 코드별 기본 기호 폴백 (설정 symbol 이 비어 있을 때만 사용).\n * 위안화(CNY)는 엔화(¥)와 구분되도록 元 사용 (#402 3-3).\n */\nconst SYMBOL_FALLBACK: Record = {\n KRW: '₩',\n USD: '$',\n JPY: '¥',\n CNY: '元',\n EUR: '€',\n GBP: '£',\n};\n\n/**\n * base_unit 미설정 통화의 폴백 (소액 통화만 묶음 단위). 백엔드 CurrencyConversionService 와 동일.\n */\nconst BASE_UNIT_FALLBACK: Record = {\n KRW: 1000,\n JPY: 100,\n};\n\n/**\n * 통화의 base_unit(기본 통화일 때 환율 분모가 되는 1단위 금액)을 반환합니다.\n *\n * @param currency 통화 설정\n * @returns base_unit (최소 1)\n */\nfunction resolveBaseUnit(currency: Currency | undefined): number {\n if (!currency) {\n return 1;\n }\n const unit = currency.base_unit ?? BASE_UNIT_FALLBACK[currency.code] ?? 1;\n return Math.max(1, unit);\n}\n\ninterface CurrencyPrice {\n price: number;\n formatted: string;\n is_default: boolean;\n editable: boolean;\n exchange_rate?: number;\n}\n\ninterface CalculateCurrencyPricesParams {\n basePrice: number | string;\n currencies?: Currency[];\n}\n\n/**\n * 절사/반올림/올림을 적용합니다.\n *\n * @param price 가격\n * @param unit 절사 단위\n * @param method 방법 (floor, round, ceil)\n * @returns 처리된 가격\n */\nfunction applyRounding(price: number, unit: string, method: string): number {\n const unitValue = parseFloat(unit) || 1;\n if (unitValue <= 0) {\n return price;\n }\n\n const divided = price / unitValue;\n let rounded: number;\n\n switch (method) {\n case 'ceil':\n rounded = Math.ceil(divided);\n break;\n case 'floor':\n rounded = Math.floor(divided);\n break;\n default:\n rounded = Math.round(divided);\n }\n\n return rounded * unitValue;\n}\n\n/**\n * 통화별 가격을 포맷팅합니다.\n *\n * 통화 기호를 하드코딩하지 않고 설정(symbol)·소수 자릿수(decimal_places)를 따릅니다.\n * 원화(KRW)는 \"3,000원\"처럼 기호를 금액 뒤에 붙이고, 그 외 통화는 기호를 앞에 붙입니다\n * (백엔드 messages.currency.prefix/suffix 와 동일한 표기 규칙). 위안화(CNY)는 엔화(¥)와\n * 구분되도록 元 기호를 사용합니다. currency 객체가 없으면 코드만으로 폴백 기호를 적용합니다.\n *\n * @param price 가격\n * @param currency 통화 설정 객체 (또는 통화 코드 문자열)\n * @returns 포맷팅된 가격\n */\nexport function formatCurrency(\n price: number,\n currency: { code: string; symbol?: string; decimal_places?: number } | string\n): string {\n const c = typeof currency === 'string' ? { code: currency } : currency;\n const code = c.code;\n const symbol = (c.symbol && c.symbol.length > 0)\n ? c.symbol\n : (SYMBOL_FALLBACK[code] ?? '');\n const decimalPlaces = c.decimal_places ?? DECIMAL_PLACES_FALLBACK[code] ?? 2;\n const formattedNumber = price.toLocaleString(undefined, {\n minimumFractionDigits: decimalPlaces,\n maximumFractionDigits: decimalPlaces,\n });\n\n // 기호가 없으면 코드 접미 (백엔드 동일 규칙)\n if (!symbol) {\n return formattedNumber + ' ' + code;\n }\n\n // 원화 계열(₩/원)은 금액 뒤에 \"원\", 그 외는 기호를 앞에 표기\n if (code === 'KRW' || symbol === '₩' || symbol === '원') {\n return formattedNumber + '원';\n }\n\n return symbol + formattedNumber;\n}\n\n/**\n * 실시간 환율 계산 핸들러\n *\n * 기본 통화 가격을 기준으로 모든 통화의 가격을 계산합니다.\n *\n * @param params 핸들러 파라미터\n * @param _context 액션 컨텍스트 (미사용)\n * @returns 통화별 가격 정보 객체\n */\nexport function calculateCurrencyPricesHandler(\n params: CalculateCurrencyPricesParams,\n _context: ActionContext\n): Record {\n const { basePrice, currencies } = params;\n const price = parseFloat(String(basePrice)) || 0;\n\n if (!currencies || !Array.isArray(currencies) || currencies.length === 0) {\n return {};\n }\n\n // 기본 통화의 base_unit = 환율 분모 (백엔드 getDefaultBaseUnit 와 동일)\n const defaultCurrency = currencies.find((c) => c.is_default);\n const baseUnit = resolveBaseUnit(defaultCurrency);\n\n const result: Record = {};\n\n for (const currency of currencies) {\n const code = currency.code;\n const isDefault = currency.is_default ?? false;\n\n if (isDefault) {\n // 기본 통화\n result[code] = {\n price,\n formatted: formatCurrency(price, currency),\n is_default: true,\n editable: true,\n };\n } else if (currency.exchange_rate && currency.exchange_rate > 0) {\n // 외화: 환율 기반 계산\n // 계산: (기본통화가격 / 기본통화.base_unit) * exchange_rate\n const convertedPrice = (price / baseUnit) * currency.exchange_rate;\n const roundedPrice = applyRounding(\n convertedPrice,\n currency.rounding_unit || '0.01',\n currency.rounding_method || 'round'\n );\n\n result[code] = {\n price: roundedPrice,\n formatted: formatCurrency(roundedPrice, currency),\n is_default: false,\n editable: false,\n exchange_rate: currency.exchange_rate,\n };\n }\n }\n\n return result;\n}\n","/**\n * 상품 필드 업데이트 핸들러\n *\n * 상품 목록에서 인라인 편집 시 데이터소스를 업데이트합니다.\n * selling_price 변경 시 다중통화 가격을 자동 재계산합니다.\n * 실제 API 저장은 \"일괄 변경\" 버튼 클릭 시 별도로 처리됩니다.\n */\n\nimport type { ActionContext } from '../types';\nimport { calculateCurrencyPricesHandler } from './calculateCurrencyPrices';\n\n// Logger 설정 (G7Core 초기화 전에도 동작하도록 폴백 포함)\nconst logger = ((window as any).G7Core?.createLogger?.('Ecom:UpdateProduct')) ?? {\n log: (...args: unknown[]) => console.log('[Ecom:UpdateProduct]', ...args),\n warn: (...args: unknown[]) => console.warn('[Ecom:UpdateProduct]', ...args),\n error: (...args: unknown[]) => console.error('[Ecom:UpdateProduct]', ...args),\n};\n\ninterface UpdateProductFieldParams {\n productId: number | string;\n field: string;\n value: any;\n dataSourceId?: string;\n}\n\ninterface ProductFieldError {\n productId: string;\n field: string;\n message: string;\n}\n\n/**\n * 커스텀 핸들러에 전달되는 액션 객체 인터페이스\n * ActionDispatcher는 (action, context) 형태로 핸들러를 호출합니다.\n */\ninterface ActionWithParams {\n handler: string;\n params?: UpdateProductFieldParams;\n [key: string]: any;\n}\n\n/**\n * 상품 필드 업데이트 핸들러\n *\n * G7Core.dataSource API를 사용하여 products 데이터소스를 직접 업데이트합니다.\n * selling_price 변경 시 다중통화 가격을 자동 재계산합니다.\n *\n * @param action 액션 객체 (params 포함)\n * @param context 액션 컨텍스트\n */\nexport function updateProductFieldHandler(\n action: ActionWithParams,\n context: ActionContext\n): void {\n // 커스텀 핸들러는 (action, context) 형태로 호출되므로 action.params에서 추출\n const params = (action.params || {}) as UpdateProductFieldParams;\n const { productId, field, dataSourceId = 'products' } = params;\n let { value } = params;\n\n // value가 이벤트 객체인 경우 target.value 추출\n // MultilingualInput은 { target: { name, value: { ko: '...', en: '...' } } } 형태로 전달\n // 일반 Input은 { target: { value: '...' } } 형태로 전달\n if (value && typeof value === 'object' && value.target !== undefined) {\n value = value.target?.value;\n logger.log('[updateProductField] Extracted value from event:', value);\n }\n\n if (!productId || !field) {\n logger.warn('[updateProductField] Missing required params:', { productId, field });\n return;\n }\n\n if (value === undefined) {\n logger.warn('[updateProductField] Value is undefined, skipping update');\n return;\n }\n\n const G7Core = (window as any).G7Core;\n if (!G7Core?.dataSource?.get || !G7Core?.dataSource?.set) {\n logger.warn('[updateProductField] G7Core.dataSource API is not available');\n return;\n }\n\n // 데이터소스에서 현재 데이터 가져오기\n const currentData = G7Core.dataSource.get(dataSourceId);\n if (!currentData) {\n console.warn(`[updateProductField] DataSource '${dataSourceId}' not found`);\n return;\n }\n\n // API 응답 구조: { success: true, data: { data: [...products], pagination: {...} } }\n const productsArray = currentData.data?.data || [];\n if (!Array.isArray(productsArray) || productsArray.length === 0) {\n logger.warn('[updateProductField] Products data is empty or invalid');\n return;\n }\n\n // currencies는 _global 상태에서 가져옴\n const globalState = G7Core.state.get() || {};\n const currencies = globalState.modules?.['sirsoft-ecommerce']?.language_currency?.currencies;\n\n // 현재 에러 상태 가져오기\n const currentErrors: ProductFieldError[] = globalState._local?.productFieldErrors || [];\n let newErrors = [...currentErrors];\n\n // 상품 목록에서 해당 상품 찾아서 필드 업데이트\n const updatedProducts = productsArray.map((product: any) => {\n if (String(product.id) === String(productId)) {\n // 값이 변경되지 않았으면 업데이트하지 않음 (성능 최적화)\n const currentFieldValue = product[field];\n\n // 객체 비교 (다국어 필드 등)를 위해 JSON 문자열로 비교\n const isEqual = typeof value === 'object' && value !== null\n ? JSON.stringify(currentFieldValue) === JSON.stringify(value)\n : String(currentFieldValue) === String(value);\n\n if (isEqual) {\n logger.log(`[updateProductField] No change for product ${productId}.${field}, skipping`);\n return product;\n }\n\n let finalValue = value;\n const numericValue = parseFloat(value) || 0;\n\n // selling_price 검증: 정가를 초과할 수 없음\n if (field === 'selling_price') {\n const listPrice = parseFloat(product.list_price) || 0;\n if (listPrice > 0 && numericValue > listPrice) {\n // 에러 추가\n const errorExists = newErrors.some(\n (e) => e.productId === String(productId) && e.field === field\n );\n if (!errorExists) {\n newErrors.push({\n productId: String(productId),\n field: 'selling_price',\n message: '판매가는 정가를 초과할 수 없습니다.',\n });\n }\n // 값을 정가로 제한\n finalValue = listPrice;\n } else {\n // 에러 제거\n newErrors = newErrors.filter(\n (e) => !(e.productId === String(productId) && e.field === field)\n );\n }\n } else {\n // 다른 필드 변경 시 해당 필드 에러 제거\n newErrors = newErrors.filter(\n (e) => !(e.productId === String(productId) && e.field === field)\n );\n }\n\n const updatedProduct: any = {\n ...product,\n [field]: finalValue,\n _modified: true,\n };\n\n // selling_price 변경 시 다중통화 가격 자동 재계산\n if (field === 'selling_price' && currencies && Array.isArray(currencies)) {\n updatedProduct.multi_currency_selling_price = calculateCurrencyPricesHandler(\n { basePrice: finalValue, currencies },\n context\n );\n logger.log(`[updateProductField] Recalculated multi_currency_selling_price for product ${productId}`);\n }\n\n return updatedProduct;\n }\n return product;\n });\n\n // 데이터소스 업데이트 (UI 자동 리렌더링)\n // 구조: { success, data: { data: [...], pagination, statistics } }\n G7Core.dataSource.set(dataSourceId, {\n ...currentData,\n data: {\n ...currentData.data,\n data: updatedProducts,\n },\n });\n\n // 변경된 상품 ID와 필드명을 _local 상태에서 추적 + 에러 상태 업데이트\n // G7Core.state.setLocal을 사용하여 컴포넌트 로컬 상태 직접 업데이트\n const currentLocal = G7Core.state.getLocal() || {};\n const modifiedProductIds = new Set(currentLocal.modifiedProductIds || []);\n modifiedProductIds.add(productId);\n\n // 수정된 필드명 추적 (상품별로 어떤 필드가 수정되었는지 기록)\n const modifiedProductFields: Record = { ...(currentLocal.modifiedProductFields || {}) };\n const productKey = String(productId);\n const existingFields = new Set(modifiedProductFields[productKey] || []);\n existingFields.add(field);\n modifiedProductFields[productKey] = Array.from(existingFields);\n\n G7Core.state.setLocal({\n modifiedProductIds: Array.from(modifiedProductIds),\n modifiedProductFields,\n productFieldErrors: newErrors,\n });\n\n logger.log(`[updateProductField] Updated product ${productId}.${field} =`, value);\n}\n","/**\n * 옵션 필드 업데이트 핸들러\n *\n * 상품 옵션의 인라인 편집 시 데이터소스를 업데이트합니다.\n * selling_price 변경 시 다중통화 가격을 자동 재계산합니다.\n */\n\nimport type { ActionContext } from '../types';\nimport { calculateCurrencyPricesHandler } from './calculateCurrencyPrices';\n\n// Logger 설정 (G7Core 초기화 전에도 동작하도록 폴백 포함)\nconst logger = ((window as any).G7Core?.createLogger?.('Ecom:UpdateOption')) ?? {\n log: (...args: unknown[]) => console.log('[Ecom:UpdateOption]', ...args),\n warn: (...args: unknown[]) => console.warn('[Ecom:UpdateOption]', ...args),\n error: (...args: unknown[]) => console.error('[Ecom:UpdateOption]', ...args),\n};\n\ninterface UpdateOptionFieldParams {\n productId: number | string;\n optionId: number | string;\n field: string;\n value: any;\n dataSourceId?: string;\n}\n\ninterface OptionFieldError {\n productId: string;\n optionId: string;\n field: string;\n message: string;\n}\n\n/**\n * 커스텀 핸들러에 전달되는 액션 객체 인터페이스\n * ActionDispatcher는 (action, context) 형태로 핸들러를 호출합니다.\n */\ninterface ActionWithParams {\n handler: string;\n params?: UpdateOptionFieldParams;\n [key: string]: any;\n}\n\n/**\n * 옵션 필드 업데이트 핸들러\n *\n * G7Core.dataSource API를 사용하여 products 데이터소스를 직접 업데이트합니다.\n * selling_price 변경 시 다중통화 가격을 자동 재계산합니다.\n *\n * @param action 액션 객체 (params 포함)\n * @param context 액션 컨텍스트\n */\nexport function updateOptionFieldHandler(\n action: ActionWithParams,\n context: ActionContext\n): void {\n // 커스텀 핸들러는 (action, context) 형태로 호출되므로 action.params에서 추출\n const params = (action.params || {}) as UpdateOptionFieldParams;\n const { productId, optionId, field, value, dataSourceId = 'products' } = params;\n\n if (!productId || !optionId || !field) {\n logger.warn('[updateOptionField] Missing required params:', { productId, optionId, field });\n return;\n }\n\n const G7Core = (window as any).G7Core;\n if (!G7Core?.dataSource?.get || !G7Core?.dataSource?.set) {\n logger.warn('[updateOptionField] G7Core.dataSource API is not available');\n return;\n }\n\n // 데이터소스에서 현재 데이터 가져오기\n const currentData = G7Core.dataSource.get(dataSourceId);\n if (!currentData) {\n console.warn(`[updateOptionField] DataSource '${dataSourceId}' not found`);\n return;\n }\n\n // API 응답 구조: { success: true, data: { data: [...products], pagination: {...} } }\n const productsArray = currentData.data?.data || [];\n if (!Array.isArray(productsArray) || productsArray.length === 0) {\n logger.warn('[updateOptionField] Products data is empty or invalid');\n return;\n }\n\n // currencies는 _global 상태에서 가져옴\n const globalState = G7Core.state.get() || {};\n const currencies = globalState.modules?.['sirsoft-ecommerce']?.language_currency?.currencies;\n\n // 현재 에러 상태 가져오기\n const currentErrors: OptionFieldError[] = globalState._local?.optionFieldErrors || [];\n let newErrors = [...currentErrors];\n\n // 기본 옵션 판매가 → 상품 판매가 역동기화가 발생했는지 추적\n // (발생 시 상품의 selling_price 도 modifiedProductFields 에 기록해야 일괄 저장에 포함됨)\n let productSellingPriceSynced = false;\n\n // 상품 목록에서 해당 상품과 옵션 찾아서 업데이트\n const updatedProducts = productsArray.map((product: any) => {\n if (String(product.id) === String(productId)) {\n const options = product.options || [];\n\n // 해당 옵션 찾기\n const optionIndex = options.findIndex(\n (o: any) => String(o.id) === String(optionId)\n );\n\n if (optionIndex === -1) {\n logger.warn('[updateOptionField] Option not found:', optionId);\n return product;\n }\n\n const currentOption = options[optionIndex];\n\n // 값이 변경되지 않았으면 업데이트하지 않음 (성능 최적화)\n const currentFieldValue = currentOption[field];\n\n // 객체 비교 (다국어 필드 등)를 위해 JSON 문자열로 비교\n const isEqual = typeof value === 'object' && value !== null\n ? JSON.stringify(currentFieldValue) === JSON.stringify(value)\n : String(currentFieldValue) === String(value);\n\n if (isEqual) {\n logger.log(`[updateOptionField] No change for option ${optionId}.${field}, skipping`);\n return product;\n }\n\n let finalValue = value;\n const numericValue = parseFloat(value) || 0;\n\n // selling_price 검증: 정가를 초과할 수 없음\n if (field === 'selling_price') {\n const listPrice = parseFloat(currentOption.list_price) || 0;\n if (listPrice > 0 && numericValue > listPrice) {\n // 에러 추가\n const errorExists = newErrors.some(\n (e) =>\n e.productId === String(productId) &&\n e.optionId === String(optionId) &&\n e.field === field\n );\n if (!errorExists) {\n newErrors.push({\n productId: String(productId),\n optionId: String(optionId),\n field: 'selling_price',\n message: '판매가는 정가를 초과할 수 없습니다.',\n });\n }\n // 값을 정가로 제한\n finalValue = listPrice;\n } else {\n // 에러 제거\n newErrors = newErrors.filter(\n (e) =>\n !(\n e.productId === String(productId) &&\n e.optionId === String(optionId) &&\n e.field === field\n )\n );\n }\n } else {\n // 다른 필드 변경 시 해당 필드 에러 제거\n newErrors = newErrors.filter(\n (e) =>\n !(\n e.productId === String(productId) &&\n e.optionId === String(optionId) &&\n e.field === field\n )\n );\n }\n\n // 옵션 업데이트\n const updatedOptions = [...options];\n const updatedOption: any = {\n ...updatedOptions[optionIndex],\n [field]: finalValue,\n _modified: true,\n };\n\n // 상품 판매가 역동기화 여부 (기본 옵션 판매가 변경 시)\n // 백엔드 모델 정의(ProductOption::getSellingPrice): option.selling_price = product.selling_price + price_adjustment\n // → price_adjustment 는 \"상품 판매가 대비 가산액\". 기본 옵션은 정의상 가산액 0.\n let productSellingPriceSync: number | null = null;\n\n // selling_price 변경 시 price_adjustment 재계산 (상품 판매가 대비)\n if (field === 'selling_price') {\n const newSellingPrice = parseFloat(finalValue) || 0;\n\n if (currentOption.is_default === true) {\n // 역방향 동기화: 기본 옵션 판매가 = 상품 판매가 → price_adjustment 0\n productSellingPriceSync = newSellingPrice;\n updatedOption.price_adjustment = 0;\n } else {\n // 비기본 옵션: price_adjustment = 옵션 판매가 - 상품 판매가 (상품 판매가 불변)\n const productSellingPrice = parseFloat(product.selling_price) || 0;\n updatedOption.price_adjustment = newSellingPrice - productSellingPrice;\n }\n updatedOption.price_adjustment_formatted =\n (updatedOption.price_adjustment >= 0 ? '+' : '') +\n updatedOption.price_adjustment.toLocaleString() + '원';\n }\n\n // selling_price 변경 시 다중통화 가격 자동 재계산\n if (field === 'selling_price' && currencies && Array.isArray(currencies)) {\n updatedOption.multi_currency_selling_price = calculateCurrencyPricesHandler(\n { basePrice: finalValue, currencies },\n context\n );\n logger.log(`[updateOptionField] Recalculated multi_currency_selling_price for option ${optionId}`);\n }\n\n updatedOptions[optionIndex] = updatedOption;\n\n // 기본 옵션 판매가 변경 → 상품 판매가 동기화 + 비기본 옵션 판매가 재계산\n // (상품등록/수정 폼의 역방향 동기화와 동일 시맨틱: 상품 폼 productOptionHandlers.recalcOptionsFromProductPrice)\n if (productSellingPriceSync !== null) {\n for (let i = 0; i < updatedOptions.length; i++) {\n if (i === optionIndex) continue;\n const sibling = updatedOptions[i];\n const adj = parseFloat(sibling.price_adjustment) || 0;\n const siblingSellingPrice = productSellingPriceSync + adj;\n const updatedSibling: any = { ...sibling, selling_price: siblingSellingPrice };\n if (currencies && Array.isArray(currencies)) {\n updatedSibling.multi_currency_selling_price = calculateCurrencyPricesHandler(\n { basePrice: siblingSellingPrice, currencies },\n context\n );\n }\n updatedOptions[i] = updatedSibling;\n }\n }\n\n // stock_quantity 변경 시 상품의 총 재고를 옵션 재고 합계로 업데이트\n let updatedStockQuantity = product.stock_quantity;\n let updatedOptionStockSum = product.option_stock_sum;\n if (field === 'stock_quantity') {\n const newValue = parseInt(value, 10) || 0;\n updatedOptionStockSum = updatedOptions.reduce((sum: number, opt: any) => {\n if (opt.is_active === false) return sum;\n return sum + (parseInt(opt.stock_quantity, 10) || 0);\n }, 0);\n updatedStockQuantity = updatedOptionStockSum;\n logger.log(`[updateOptionField] Updated product stock to option sum: ${updatedStockQuantity}`);\n }\n\n const updatedProduct: any = {\n ...product,\n options: updatedOptions,\n stock_quantity: updatedStockQuantity,\n option_stock_sum: updatedOptionStockSum,\n _modified: true,\n };\n\n // 기본 옵션 판매가 → 상품 판매가 동기화 (+ 상품 다중통화 재계산)\n if (productSellingPriceSync !== null) {\n updatedProduct.selling_price = productSellingPriceSync;\n if (currencies && Array.isArray(currencies)) {\n updatedProduct.multi_currency_selling_price = calculateCurrencyPricesHandler(\n { basePrice: productSellingPriceSync, currencies },\n context\n );\n }\n productSellingPriceSynced = true;\n logger.log(`[updateOptionField] Reverse-synced product ${productId} selling_price to ${productSellingPriceSync} from default option`);\n }\n\n return updatedProduct;\n }\n return product;\n });\n\n // 데이터소스 업데이트 (UI 자동 리렌더링)\n // 구조: { success, data: { data: [...], pagination, statistics } }\n G7Core.dataSource.set(dataSourceId, {\n ...currentData,\n data: {\n ...currentData.data,\n data: updatedProducts,\n },\n });\n\n // 변경된 옵션 ID와 필드명을 _local 상태에서 추적 + 에러 상태 업데이트\n // G7Core.state.setLocal을 사용하여 컴포넌트 로컬 상태 직접 업데이트\n const currentLocal = G7Core.state.getLocal() || {};\n const modifiedOptionIds = new Set(currentLocal.modifiedOptionIds || []);\n const optionKey = `${productId}-${optionId}`;\n modifiedOptionIds.add(optionKey);\n\n // 수정된 필드명 추적 (옵션별로 어떤 필드가 수정되었는지 기록)\n const modifiedOptionFields: Record = { ...(currentLocal.modifiedOptionFields || {}) };\n const existingFields = new Set(modifiedOptionFields[optionKey] || []);\n existingFields.add(field);\n modifiedOptionFields[optionKey] = Array.from(existingFields);\n\n const setLocalPayload: Record = {\n modifiedOptionIds: Array.from(modifiedOptionIds),\n modifiedOptionFields,\n optionFieldErrors: newErrors,\n };\n\n // 기본 옵션 판매가 → 상품 판매가 역동기화가 발생했으면 상품도 수정 대상으로 기록\n // (일괄 저장이 modifiedProductFields 의 selling_price 를 보고 상품 판매가를 전송하므로 필수)\n if (productSellingPriceSynced) {\n const modifiedProductIds = new Set(currentLocal.modifiedProductIds || []);\n modifiedProductIds.add(productId);\n const modifiedProductFields: Record = { ...(currentLocal.modifiedProductFields || {}) };\n const productKey = String(productId);\n const productFields = new Set(modifiedProductFields[productKey] || []);\n productFields.add('selling_price');\n modifiedProductFields[productKey] = Array.from(productFields);\n setLocalPayload.modifiedProductIds = Array.from(modifiedProductIds);\n setLocalPayload.modifiedProductFields = modifiedProductFields;\n }\n\n G7Core.state.setLocal(setLocalPayload);\n\n logger.log(`[updateOptionField] Updated option ${productId}/${optionId}.${field} =`, value);\n}\n","/**\n * 표시 통화(preferredCurrency) 초기화 핸들러 (이커머스 모듈 소유)\n *\n * \"통화 = 커머스 책임\" 원칙에 따라, 표시 통화 초기화 핸들러는 템플릿이 아닌 이커머스 모듈이\n * 소유한다. 유저(_user_base)·관리자(_admin_base) 양 템플릿의 init_actions 에서\n * `sirsoft-ecommerce.initPreferredCurrency` 로 호출되어, 모듈 활성 시에만 동작한다.\n *\n * 두 시점에서 호출되어 이벤트 기반으로 계정 통화를 적용한다(폴링 없음):\n * 1. init_actions — 페이지 진입 즉시 세션/기본 통화로 초기화 (accountCurrency 미전달)\n * 2. current_user data_source 의 onSuccess — 로그인 유저 응답 도착 시 계정 영속 통화로\n * 덮어쓰기. onSuccess 핸들러에 `accountCurrency: {{response.data.data.ecommerce_preferred_currency}}`\n * 를 전달하므로, 응답 페이로드에서 직접 통화를 읽어 적용한다(전역 상태 반영 타이밍 비의존).\n *\n * 표준 `loadFromLocalStorage` 는 `context.setState`(React 컴포넌트 state)만 갱신해\n * init_actions(컴포넌트 컨텍스트 부재)에서는 _global 승격이 실패한다. 따라서 `G7Core.state.set`\n * 직접 호출로 _global 에 동기 주입한다(initCartKey/initGuestOrderToken 동일 패턴).\n *\n * 우선순위(§D-USERCUR-3 / D-LOGIN-CUR / D-SIGNUP 게스트 확장):\n * 1. 로그인 유저의 계정 영속 통화(onSuccess 가 전달한 params.accountCurrency) — 1순위\n * 2. localStorage 세션 선택값(g7_preferred_currency, ISO 4217 형식만) — 수동 선택\n * 3. 게스트 사이트 언어(g7_locale) 기반 통화 추정 — currencies[].locales 매핑\n * (회원가입 통화 부여 SignupCurrencyResolver 와 동일 입력·동일 우선순위)\n * 4. params.defaultCurrency(관리자 설정 default_currency)\n * 5. 'KRW' 최종 폴백\n */\n\nconst PREFERRED_CURRENCY_STORAGE_NAME = 'g7_preferred_currency';\nconst LOCALE_STORAGE_NAME = 'g7_locale';\n\n// Logger 설정 (G7Core 초기화 전에도 동작하도록 폴백 포함)\nconst logger = ((window as any).G7Core?.createLogger?.('Handler:InitPreferredCurrency')) ?? {\n log: (...args: unknown[]) => console.log('[Handler:InitPreferredCurrency]', ...args),\n warn: (...args: unknown[]) => console.warn('[Handler:InitPreferredCurrency]', ...args),\n error: (...args: unknown[]) => console.error('[Handler:InitPreferredCurrency]', ...args),\n};\n\n/**\n * 전역 상태 설정 헬퍼\n *\n * @param updates 전역 상태에 병합할 키/값\n */\nfunction setGlobalState(updates: Record): void {\n const G7Core = (window as any).G7Core;\n if (G7Core?.state?.set) {\n G7Core.state.set(updates);\n } else {\n logger.warn('G7Core.state.set not available');\n }\n}\n\n/**\n * 게스트의 현재 사이트 언어(g7_locale)를 반환합니다.\n *\n * 회원가입 통화 부여(SignupCurrencyResolver)의 입력인 `$user->language` 는 가입 폼 언어\n * 기본값 `$locale`(= g7_locale)에서 비롯되므로, 게스트 추정도 동일 입력을 사용한다.\n *\n * @return 사이트 언어 코드(예: 'ko', 'en') 또는 null\n */\nfunction getSiteLocale(): string | null {\n try {\n return localStorage.getItem(LOCALE_STORAGE_NAME);\n } catch {\n return null;\n }\n}\n\n/**\n * 환경설정의 통화 목록을 _global 에서 읽습니다.\n *\n * @return currencies 배열(각 항목에 code/is_default/locales 포함) 또는 빈 배열\n */\nfunction getCurrencies(): any[] {\n try {\n const G7Core = (window as any).G7Core;\n const state = G7Core?.state?.get?.() || {};\n const lc = state?.modules?.['sirsoft-ecommerce']?.language_currency;\n return Array.isArray(lc?.currencies) ? lc.currencies : [];\n } catch {\n return [];\n }\n}\n\n/**\n * 사이트 언어(locale)를 통화별 사용 언어(currencies[].locales) 매핑과 대조해 통화를 추정합니다.\n *\n * 백엔드 SignupCurrencyResolver 와 동일한 우선순위:\n * 1) 단일 매칭 → 그 통화\n * 2) 중복 매칭 → 매칭된 통화 중 is_default 우선, 없으면 추정 실패(모호 회피)\n * 3) 매칭 없음 → 추정 실패(상위 폴백에 위임)\n *\n * @param locale 사이트 언어 코드(ko-KR 등 region suffix 정규화)\n * @return 추정된 통화 코드 또는 null(추정 불가)\n */\nfunction resolveCurrencyByLocale(locale: string | null): string | null {\n if (!locale) {\n return null;\n }\n // locale 정규화 (ko-KR / ko_KR → ko)\n const normalized = locale.toLowerCase().split('-')[0].split('_')[0];\n if (!normalized) {\n return null;\n }\n\n const currencies = getCurrencies();\n const matched = currencies.filter((c) => {\n const locales = Array.isArray(c?.locales) ? c.locales : [];\n return locales.some((l: unknown) => String(l).toLowerCase() === normalized);\n });\n\n // 1) 단일 매칭\n if (matched.length === 1) {\n return matched[0]?.code ?? null;\n }\n // 2) 중복 매칭 → is_default 우선, 없으면 모호 → 실패\n if (matched.length > 1) {\n const def = matched.find((c) => c?.is_default === true);\n return def?.code ?? null;\n }\n // 3) 매칭 없음 → 실패(상위 폴백)\n return null;\n}\n\n/**\n * 현재 로그인 사용자 확인 헬퍼\n *\n * @return 현재 사용자 객체 또는 null\n */\nfunction getCurrentUser(): any {\n try {\n const g7Config = (window as any).G7Config;\n if (g7Config?.user) {\n return g7Config.user;\n }\n const G7Core = (window as any).G7Core;\n const globalState = G7Core?.state?.get?.() || {};\n return globalState.currentUser || null;\n } catch {\n return null;\n }\n}\n\n/**\n * 표시 통화(preferredCurrency)를 _global 에 동기 주입합니다.\n *\n * @param action params.defaultCurrency — 관리자 설정 기본 통화(미지정 시 KRW),\n * params.accountCurrency — onSuccess 가 전달하는 계정 영속 통화(init_actions 에선 미전달)\n * @param _context 액션 컨텍스트 (사용하지 않음 — _global 직접 set)\n */\nexport function initPreferredCurrencyHandler(\n action?: any,\n _context?: any\n): void {\n const defaultCurrency = action?.params?.defaultCurrency || 'KRW';\n const isValid = (code: unknown): code is string =>\n typeof code === 'string' && /^[A-Z]{3}$/.test(code);\n\n // 1순위: 로그인 유저 계정 영속 통화 (onSuccess 가 전달한 값을 우선, 폴백으로 현재 유저)\n const user = getCurrentUser();\n const persisted = action?.params?.accountCurrency ?? user?.ecommerce_preferred_currency;\n\n // 2순위: localStorage 세션 선택값\n let stored: string | null = null;\n try {\n stored = localStorage.getItem(PREFERRED_CURRENCY_STORAGE_NAME);\n } catch {\n // localStorage 접근 불가 시 무시\n }\n\n // 3순위: 게스트 사이트 언어(g7_locale) 기반 통화 추정 (회원가입과 동일 매핑)\n // 로그인 유저(persisted 존재)는 이미 1순위로 결정되므로 추정을 건너뛴다.\n const localeGuess = persisted ? null : resolveCurrencyByLocale(getSiteLocale());\n\n const resolved = isValid(persisted)\n ? persisted\n : isValid(stored)\n ? stored\n : isValid(localeGuess)\n ? localeGuess\n : isValid(defaultCurrency)\n ? defaultCurrency\n : 'KRW';\n\n setGlobalState({ preferredCurrency: resolved });\n logger.log('Preferred currency initialized:', resolved);\n}\n","/**\n * 선호 배송국가(preferredShippingCountry) 초기화 핸들러 (이커머스 모듈 소유)\n *\n * \"배송국가 = 커머스 책임\" 원칙에 따라, 선호 배송국가 초기화 핸들러는 템플릿이 아닌 이커머스\n * 모듈이 소유한다(initPreferredCurrency 미러). 유저(_user_base) 템플릿의 init_actions 에서\n * `sirsoft-ecommerce.initPreferredShippingCountry` 로 호출되어, 모듈 활성 시에만 동작한다.\n *\n * 두 시점에서 호출되어 이벤트 기반으로 계정 배송국가를 적용한다(폴링 없음):\n * 1. init_actions — 페이지 진입 즉시 세션/기본 국가로 초기화 (accountCountry 미전달)\n * 2. current_user data_source 의 onSuccess — 로그인 유저 응답 도착 시 계정 영속 배송국가로\n * 덮어쓰기. onSuccess 핸들러에 `accountCountry: {{response.data.data.ecommerce_preferred_shipping_country}}`\n * 를 전달하므로, 응답 페이로드에서 직접 국가를 읽어 적용한다.\n *\n * 우선순위:\n * 1. 로그인 유저의 계정 영속 배송국가(onSuccess 가 전달한 params.accountCountry) — 1순위\n * 2. localStorage 세션 선택값(g7_preferred_shipping_country, ISO 3166-1 alpha-2 형식만)\n * 3. params.defaultCountry(관리자 설정 default_country)\n * 4. 'KR' 최종 폴백\n */\n\nconst PREFERRED_SHIPPING_COUNTRY_STORAGE_NAME = 'g7_preferred_shipping_country';\n\n// Logger 설정 (G7Core 초기화 전에도 동작하도록 폴백 포함)\nconst logger = ((window as any).G7Core?.createLogger?.('Handler:InitPreferredShippingCountry')) ?? {\n log: (...args: unknown[]) => console.log('[Handler:InitPreferredShippingCountry]', ...args),\n warn: (...args: unknown[]) => console.warn('[Handler:InitPreferredShippingCountry]', ...args),\n error: (...args: unknown[]) => console.error('[Handler:InitPreferredShippingCountry]', ...args),\n};\n\n/**\n * 전역 상태 설정 헬퍼\n *\n * @param updates 전역 상태에 병합할 키/값\n */\nfunction setGlobalState(updates: Record): void {\n const G7Core = (window as any).G7Core;\n if (G7Core?.state?.set) {\n G7Core.state.set(updates);\n } else {\n logger.warn('G7Core.state.set not available');\n }\n}\n\n/**\n * 현재 로그인 사용자 확인 헬퍼\n *\n * @return 현재 사용자 객체 또는 null\n */\nfunction getCurrentUser(): any {\n try {\n const g7Config = (window as any).G7Config;\n if (g7Config?.user) {\n return g7Config.user;\n }\n const G7Core = (window as any).G7Core;\n const globalState = G7Core?.state?.get?.() || {};\n return globalState.currentUser || null;\n } catch {\n return null;\n }\n}\n\n/**\n * 선호 배송국가(preferredShippingCountry)를 _global 에 동기 주입합니다.\n *\n * @param action params.defaultCountry — 관리자 설정 기본 국가(미지정 시 KR),\n * params.accountCountry — onSuccess 가 전달하는 계정 영속 배송국가(init_actions 에선 미전달)\n * @param _context 액션 컨텍스트 (사용하지 않음 — _global 직접 set)\n */\nexport function initPreferredShippingCountryHandler(\n action?: any,\n _context?: any\n): void {\n const defaultCountry = action?.params?.defaultCountry || 'KR';\n const isValid = (code: unknown): code is string =>\n typeof code === 'string' && /^[A-Z]{2}$/.test(code);\n\n // 1순위: 로그인 유저 계정 영속 배송국가 (onSuccess 가 전달한 값을 우선, 폴백으로 현재 유저)\n const user = getCurrentUser();\n const persisted = action?.params?.accountCountry ?? user?.ecommerce_preferred_shipping_country;\n\n // 2순위: localStorage 세션 선택값\n let stored: string | null = null;\n try {\n stored = localStorage.getItem(PREFERRED_SHIPPING_COUNTRY_STORAGE_NAME);\n } catch {\n // localStorage 접근 불가 시 무시\n }\n\n const resolved = isValid(persisted)\n ? persisted\n : isValid(stored)\n ? stored\n : isValid(defaultCountry)\n ? defaultCountry\n : 'KR';\n\n setGlobalState({ preferredShippingCountry: resolved });\n logger.log('Preferred shipping country initialized:', resolved);\n}\n","/**\n * 날짜 범위 프리셋 핸들러\n *\n * 날짜 필터의 빠른 선택 버튼(오늘, 3일간, 일주일, 1개월, 3개월, 6개월, 1년, 전체)을 처리합니다.\n * 선택된 프리셋에 따라 시작일과 종료일을 자동으로 계산하여 반환합니다.\n * 상태 업데이트는 레이아웃 JSON에서 sequence + setState로 처리합니다.\n */\n\n/**\n * 날짜 프리셋 타입\n */\ntype DatePreset = 'today' | '3days' | 'week' | 'month' | '3months' | '6months' | '1year' | 'all';\n\ninterface SetDateRangeParams {\n /** 날짜 프리셋 타입 */\n preset: DatePreset;\n}\n\ninterface SetDateRangeResult {\n startDate: string;\n endDate: string;\n preset: DatePreset;\n}\n\n/**\n * 날짜를 YYYY-MM-DD 형식으로 포맷합니다.\n *\n * @param date Date 객체\n * @returns YYYY-MM-DD 형식의 문자열\n */\nfunction formatDate(date: Date): string {\n const year = date.getFullYear();\n const month = String(date.getMonth() + 1).padStart(2, '0');\n const day = String(date.getDate()).padStart(2, '0');\n return `${year}-${month}-${day}`;\n}\n\n/**\n * 프리셋에 따른 날짜 범위를 계산합니다.\n *\n * @param preset 날짜 프리셋\n * @returns 시작일과 종료일\n */\nfunction calculateDateRange(preset: DatePreset): { startDate: string; endDate: string } {\n const today = new Date();\n const endDate = formatDate(today);\n\n let startDate: string;\n\n switch (preset) {\n case 'today':\n startDate = endDate;\n break;\n\n case '3days': {\n const start = new Date(today);\n start.setDate(start.getDate() - 2); // 오늘 포함 3일\n startDate = formatDate(start);\n break;\n }\n\n case 'week': {\n const start = new Date(today);\n start.setDate(start.getDate() - 6); // 오늘 포함 7일\n startDate = formatDate(start);\n break;\n }\n\n case 'month': {\n const start = new Date(today);\n start.setMonth(start.getMonth() - 1);\n startDate = formatDate(start);\n break;\n }\n\n case '3months': {\n const start = new Date(today);\n start.setMonth(start.getMonth() - 3);\n startDate = formatDate(start);\n break;\n }\n\n case '6months': {\n const start = new Date(today);\n start.setMonth(start.getMonth() - 6);\n startDate = formatDate(start);\n break;\n }\n\n case '1year': {\n const start = new Date(today);\n start.setFullYear(start.getFullYear() - 1);\n startDate = formatDate(start);\n break;\n }\n\n case 'all':\n default:\n startDate = '';\n return { startDate: '', endDate: '' };\n }\n\n return { startDate, endDate };\n}\n\n/**\n * 날짜 범위 프리셋 핸들러\n *\n * 날짜 필터의 빠른 선택 버튼 클릭 시 호출됩니다.\n * 선택된 프리셋에 따라 시작일과 종료일을 계산하여 반환합니다.\n *\n * @example\n * ```json\n * {\n * \"type\": \"click\",\n * \"handler\": \"sequence\",\n * \"actions\": [\n * {\n * \"handler\": \"sirsoft-ecommerce.setDateRange\",\n * \"params\": { \"preset\": \"today\" }\n * },\n * {\n * \"handler\": \"setState\",\n * \"params\": {\n * \"target\": \"_local\",\n * \"filter.dateQuick\": \"{{$prev.preset}}\",\n * \"filter.startDate\": \"{{$prev.startDate}}\",\n * \"filter.endDate\": \"{{$prev.endDate}}\"\n * }\n * }\n * ]\n * }\n * ```\n *\n * @param action 액션 정의 (params 포함)\n * @returns 계산된 날짜 범위 정보\n */\nexport function setDateRangeHandler(\n action: { params?: SetDateRangeParams }\n): SetDateRangeResult {\n const { preset } = action.params || { preset: 'today' };\n\n // 날짜 범위 계산\n const { startDate, endDate } = calculateDateRange(preset);\n\n // 계산된 결과 반환 (상태 업데이트는 레이아웃 JSON의 sequence + setState에서 처리)\n return {\n startDate,\n endDate,\n preset,\n };\n}\n","/**\n * 기본 옵션 설정 핸들러\n *\n * 상품의 기본 옵션을 설정하고, 해당 옵션의 가격을 상품 가격에 동기화합니다.\n * 기본 옵션의 list_price와 selling_price가 원 상품의 가격으로 설정됩니다.\n */\n\nimport type { ActionContext } from '../types';\nimport { calculateCurrencyPricesHandler } from './calculateCurrencyPrices';\n\n// Logger 설정 (G7Core 초기화 전에도 동작하도록 폴백 포함)\nconst logger = ((window as any).G7Core?.createLogger?.('Ecom:DefaultOption')) ?? {\n log: (...args: unknown[]) => console.log('[Ecom:DefaultOption]', ...args),\n warn: (...args: unknown[]) => console.warn('[Ecom:DefaultOption]', ...args),\n error: (...args: unknown[]) => console.error('[Ecom:DefaultOption]', ...args),\n};\n\ninterface SetDefaultOptionParams {\n productId: number | string;\n optionId: number | string;\n dataSourceId?: string;\n}\n\n/**\n * 커스텀 핸들러에 전달되는 액션 객체 인터페이스\n */\ninterface ActionWithParams {\n handler: string;\n params?: SetDefaultOptionParams;\n [key: string]: any;\n}\n\n/**\n * 기본 옵션 설정 핸들러\n *\n * 1. 선택된 옵션을 기본 옵션으로 설정 (is_default: true)\n * 2. 다른 옵션들의 is_default를 false로 설정\n * 3. 기본 옵션의 list_price와 selling_price를 상품의 가격으로 동기화\n * 4. 다중통화 가격도 재계산\n *\n * @param action 액션 객체 (params 포함)\n * @param context 액션 컨텍스트\n */\nexport function setDefaultOptionHandler(\n action: ActionWithParams,\n context: ActionContext\n): void {\n const params = (action.params || {}) as SetDefaultOptionParams;\n const { productId, optionId, dataSourceId = 'products' } = params;\n\n if (!productId || !optionId) {\n logger.warn('[setDefaultOption] Missing required params:', { productId, optionId });\n return;\n }\n\n const G7Core = (window as any).G7Core;\n if (!G7Core?.dataSource?.get || !G7Core?.dataSource?.set) {\n logger.warn('[setDefaultOption] G7Core.dataSource API is not available');\n return;\n }\n\n // 데이터소스에서 현재 데이터 가져오기\n const currentData = G7Core.dataSource.get(dataSourceId);\n if (!currentData) {\n console.warn(`[setDefaultOption] DataSource '${dataSourceId}' not found`);\n return;\n }\n\n // API 응답 구조: { success: true, data: { data: [...products], pagination: {...} } }\n const productsArray = currentData.data?.data || [];\n if (!Array.isArray(productsArray) || productsArray.length === 0) {\n logger.warn('[setDefaultOption] Products data is empty or invalid');\n return;\n }\n\n // currencies는 _global 상태에서 가져옴\n const globalState = G7Core.state.get() || {};\n const currencies = globalState.modules?.['sirsoft-ecommerce']?.language_currency?.currencies;\n\n // 상품 목록에서 해당 상품 찾아서 업데이트\n const updatedProducts = productsArray.map((product: any) => {\n if (String(product.id) !== String(productId)) {\n return product;\n }\n\n const options = product.options || [];\n if (options.length === 0) {\n return product;\n }\n\n // 선택된 옵션 찾기\n const selectedOption = options.find(\n (o: any) => String(o.id) === String(optionId)\n );\n\n if (!selectedOption) {\n logger.warn('[setDefaultOption] Option not found:', optionId);\n return product;\n }\n\n // 모든 옵션 업데이트: 선택된 옵션만 is_default = true\n const updatedOptions = options.map((option: any) => {\n const isSelected = String(option.id) === String(optionId);\n return {\n ...option,\n is_default: isSelected,\n _modified: isSelected ? true : option._modified,\n };\n });\n\n // 기본 옵션의 가격을 상품 가격으로 동기화\n const newListPrice = selectedOption.list_price;\n const newSellingPrice = selectedOption.selling_price;\n\n // 다중통화 가격 재계산\n let multiCurrencySellingPrice = product.multi_currency_selling_price;\n if (currencies && Array.isArray(currencies)) {\n multiCurrencySellingPrice = calculateCurrencyPricesHandler(\n { basePrice: newSellingPrice, currencies },\n context\n );\n }\n\n logger.log(\n `[setDefaultOption] Set option ${optionId} as default for product ${productId}. ` +\n `Syncing prices: list_price=${newListPrice}, selling_price=${newSellingPrice}`\n );\n\n return {\n ...product,\n options: updatedOptions,\n list_price: newListPrice,\n selling_price: newSellingPrice,\n multi_currency_selling_price: multiCurrencySellingPrice,\n _modified: true,\n };\n });\n\n // 데이터소스 업데이트 (UI 자동 리렌더링)\n G7Core.dataSource.set(dataSourceId, {\n ...currentData,\n data: {\n ...currentData.data,\n data: updatedProducts,\n },\n });\n\n // 변경된 상품 ID를 _local 상태에서 추적\n const currentLocal = G7Core.state.getLocal() || {};\n const modifiedProductIds = new Set(currentLocal.modifiedProductIds || []);\n modifiedProductIds.add(String(productId));\n\n const modifiedOptionIds = new Set(currentLocal.modifiedOptionIds || []);\n modifiedOptionIds.add(`${productId}-${optionId}`);\n\n G7Core.state.setLocal({\n modifiedProductIds: Array.from(modifiedProductIds),\n modifiedOptionIds: Array.from(modifiedOptionIds),\n });\n\n logger.log(`[setDefaultOption] Option ${optionId} set as default for product ${productId}`);\n}\n","/**\n * 옵션 선택 관련 핸들러\n *\n * 상품 목록 DataGrid에서 옵션 체크박스 선택을 처리합니다.\n * 부모-자식 체크박스 연동 기능을 제공합니다.\n */\n\nimport type { ActionContext } from '../types';\n\n// Logger 설정 (G7Core 초기화 전에도 동작하도록 폴백 포함)\nconst logger = ((window as any).G7Core?.createLogger?.('Ecom:OptionSelect')) ?? {\n log: (...args: unknown[]) => console.log('[Ecom:OptionSelect]', ...args),\n warn: (...args: unknown[]) => console.warn('[Ecom:OptionSelect]', ...args),\n error: (...args: unknown[]) => console.error('[Ecom:OptionSelect]', ...args),\n};\n\n/**\n * 커스텀 핸들러에 전달되는 액션 객체 인터페이스\n * ActionDispatcher는 (action, context) 형태로 핸들러를 호출합니다.\n */\ninterface ActionWithParams> {\n handler: string;\n params?: T;\n [key: string]: any;\n}\n\n/**\n * 개별 옵션 토글 파라미터\n */\ninterface ToggleOptionParams {\n productId: number | string;\n optionId: number | string;\n checked: boolean;\n}\n\n/**\n * 상품 옵션 전체 토글 파라미터\n */\ninterface ToggleProductOptionsParams {\n productId: number | string;\n options: Array<{ id: number | string; [key: string]: any }>;\n checked: boolean;\n}\n\n/**\n * 옵션 ID 생성 헬퍼\n *\n * @param productId 상품 ID\n * @param optionId 옵션 ID\n * @returns \"productId-optionId\" 형식의 문자열\n */\nconst makeOptionKey = (productId: number | string, optionId: number | string): string => {\n return `${productId}-${optionId}`;\n};\n\n/**\n * 옵션 키에서 상품 ID 추출\n *\n * @param optionKey \"productId-optionId\" 형식의 문자열\n * @returns 상품 ID\n */\nconst getProductIdFromKey = (optionKey: string): string => {\n return optionKey.split('-')[0];\n};\n\n/**\n * 개별 옵션 체크박스 토글 핸들러\n *\n * 옵션 체크박스 클릭 시 selectedOptionIds 상태를 업데이트합니다.\n *\n * expandChildren 내에서 호출되지만, DataGrid가 renderItemChildren 호출 시\n * componentContext를 전달하므로 G7Core.state.setLocal()이 부모 DataGrid의\n * dynamicState를 업데이트합니다.\n *\n * @example\n * // 레이아웃 JSON에서 사용\n * {\n * \"type\": \"basic\",\n * \"name\": \"Checkbox\",\n * \"props\": {\n * \"checked\": \"{{(_local.selectedOptionIds || []).includes(row.id + '-' + option.id)}}\"\n * },\n * \"actions\": [{\n * \"type\": \"change\",\n * \"handler\": \"sirsoft-ecommerce.toggleOption\",\n * \"params\": {\n * \"productId\": \"{{row.id}}\",\n * \"optionId\": \"{{option.id}}\",\n * \"checked\": \"{{$event.target.checked}}\"\n * }\n * }]\n * }\n *\n * @param action 액션 객체 (params 포함)\n * @param _context 액션 컨텍스트 (사용하지 않음 - G7Core.state.setLocal() 사용)\n */\nexport function toggleOptionHandler(\n action: ActionWithParams,\n _context: ActionContext\n): void {\n const params = action.params || ({} as ToggleOptionParams);\n const { productId, optionId, checked } = params;\n const G7Core = (window as any).G7Core;\n\n if (!G7Core?.state) {\n logger.warn('[toggleOption] G7Core.state를 사용할 수 없습니다.');\n return;\n }\n\n // G7Core.state.getLocal()로 현재 로컬 상태 가져오기\n // DataGrid가 componentContext를 전달하므로 부모의 상태를 가져옴\n const currentLocal = G7Core.state.getLocal();\n const currentSelectedOptions: string[] = currentLocal.selectedOptionIds || [];\n const currentSelectedItems: number[] = currentLocal.selectedItems || [];\n const optionKey = makeOptionKey(productId, optionId);\n\n let newSelectedOptions: string[];\n if (checked) {\n // 추가\n if (!currentSelectedOptions.includes(optionKey)) {\n newSelectedOptions = [...currentSelectedOptions, optionKey];\n } else {\n newSelectedOptions = currentSelectedOptions;\n }\n } else {\n // 제거\n newSelectedOptions = currentSelectedOptions.filter((key) => key !== optionKey);\n }\n\n // 상품의 옵션 전체 선택 여부 확인하여 selectedItems 업데이트\n // 데이터소스에서 해당 상품의 옵션 목록 가져오기\n const productsData = G7Core.dataSource?.get('products');\n const products = productsData?.data?.data || [];\n const product = products.find((p: any) => String(p.id) === String(productId));\n const productOptions = product?.options || [];\n\n let newSelectedItems = [...currentSelectedItems];\n const numericProductId = Number(productId);\n\n if (productOptions.length > 0) {\n // 해당 상품의 모든 옵션 키\n const allOptionKeys = productOptions.map((opt: any) => makeOptionKey(productId, opt.id));\n // 선택된 옵션 수\n const selectedCount = allOptionKeys.filter((key: string) => newSelectedOptions.includes(key)).length;\n\n if (selectedCount === productOptions.length) {\n // 모든 옵션 선택됨 → 상품도 선택\n if (!newSelectedItems.includes(numericProductId)) {\n newSelectedItems.push(numericProductId);\n }\n } else {\n // 일부 또는 전체 미선택 → 상품 선택 해제\n newSelectedItems = newSelectedItems.filter((id) => id !== numericProductId);\n }\n }\n\n // G7Core.state.setLocal()로 부모 컴포넌트 로컬 상태 업데이트\n // DataGrid가 componentContext를 전달하므로 부모의 dynamicState가 업데이트됨\n G7Core.state.setLocal({\n selectedOptionIds: newSelectedOptions,\n selectedItems: newSelectedItems,\n });\n}\n\n/**\n * 상품의 모든 옵션 전체 선택/해제 핸들러\n *\n * 상품 체크박스 클릭 시 해당 상품의 모든 옵션을 선택하거나 해제합니다.\n *\n * @example\n * // 레이아웃 JSON에서 사용\n * {\n * \"type\": \"basic\",\n * \"name\": \"Checkbox\",\n * \"props\": {\n * \"checked\": \"{{... 모든 옵션이 선택되었는지 확인 ...}}\"\n * },\n * \"actions\": [{\n * \"type\": \"change\",\n * \"handler\": \"sirsoft-ecommerce.toggleProductOptions\",\n * \"params\": {\n * \"productId\": \"{{row.id}}\",\n * \"options\": \"{{row.options}}\",\n * \"checked\": \"{{$event.target.checked}}\"\n * }\n * }]\n * }\n *\n * @param action 액션 객체 (params 포함)\n * @param _context 액션 컨텍스트 (사용하지 않음 - G7Core.state.setLocal() 사용)\n */\nexport function toggleProductOptionsHandler(\n action: ActionWithParams,\n _context: ActionContext\n): void {\n const params = action.params || ({} as ToggleProductOptionsParams);\n const { productId, options, checked } = params;\n const G7Core = (window as any).G7Core;\n\n if (!G7Core?.state) {\n logger.warn('[toggleProductOptions] G7Core.state를 사용할 수 없습니다.');\n return;\n }\n\n // G7Core.state.getLocal()로 현재 로컬 상태 가져오기\n const currentLocal = G7Core.state.getLocal();\n const currentSelectedOptions: string[] = currentLocal.selectedOptionIds || [];\n const currentSelectedItems: number[] = currentLocal.selectedItems || [];\n\n // 해당 상품의 모든 옵션 키 생성\n const productOptionKeys = (options || []).map((opt: any) =>\n makeOptionKey(productId, opt.id)\n );\n\n let newSelectedOptions: string[];\n let newSelectedItems = [...currentSelectedItems];\n const numericProductId = Number(productId);\n\n if (checked) {\n // 모든 옵션 추가 (중복 제거)\n const existingOtherOptions = currentSelectedOptions.filter(\n (key) => getProductIdFromKey(key) !== String(productId)\n );\n newSelectedOptions = [...existingOtherOptions, ...productOptionKeys];\n // 상품도 선택\n if (!newSelectedItems.includes(numericProductId)) {\n newSelectedItems.push(numericProductId);\n }\n } else {\n // 해당 상품의 모든 옵션 제거\n newSelectedOptions = currentSelectedOptions.filter(\n (key) => getProductIdFromKey(key) !== String(productId)\n );\n // 상품도 선택 해제\n newSelectedItems = newSelectedItems.filter((id) => id !== numericProductId);\n }\n\n // G7Core.state.setLocal()로 컴포넌트 로컬 상태 업데이트\n G7Core.state.setLocal({\n selectedOptionIds: newSelectedOptions,\n selectedItems: newSelectedItems,\n });\n}\n\n/**\n * 옵션 테이블 헤더의 전체 선택 체크박스 토글 핸들러\n *\n * expandChildren 내의 옵션 테이블 헤더에서 전체 선택/해제를 처리합니다.\n *\n * @param action 액션 객체 (params 포함)\n * @param context 액션 컨텍스트\n */\nexport function toggleAllOptionsInRowHandler(\n action: ActionWithParams,\n context: ActionContext\n): void {\n // toggleProductOptionsHandler와 동일한 로직 사용\n return toggleProductOptionsHandler(action, context);\n}\n\n/**\n * 상품의 옵션 선택 상태 계산 핸들러\n *\n * computed에서 사용하여 부모 체크박스의 상태를 결정합니다.\n * - all: 모든 옵션 선택됨\n * - some: 일부 옵션 선택됨 (indeterminate)\n * - none: 선택된 옵션 없음\n *\n * @example\n * // computed에서 사용\n * \"computed\": {\n * \"productOptionStates\": \"{{sirsoft-ecommerce.getProductOptionStates(products?.data?.data, _local.selectedOptionIds)}}\"\n * }\n *\n * @param action 액션 객체 (params 포함)\n * @param _context 액션 컨텍스트 (사용하지 않음)\n * @returns 상품별 선택 상태 맵\n */\nexport function getProductOptionStatesHandler(\n action: ActionWithParams<{ products: any[]; selectedOptionIds: string[] }>,\n _context: ActionContext\n): Record {\n const params = action.params || { products: [], selectedOptionIds: [] };\n const { products, selectedOptionIds } = params;\n const selected: string[] = selectedOptionIds || [];\n\n if (!products || !Array.isArray(products)) {\n return {};\n }\n\n const states: Record = {};\n\n for (const product of products) {\n const productId = String(product.id);\n const options = product.options || [];\n\n if (options.length === 0) {\n states[productId] = 'none';\n continue;\n }\n\n const productOptionKeys = options.map((opt: any) =>\n makeOptionKey(productId, opt.id)\n );\n\n const selectedCount = productOptionKeys.filter((key: string) =>\n selected.includes(key)\n ).length;\n\n if (selectedCount === 0) {\n states[productId] = 'none';\n } else if (selectedCount === options.length) {\n states[productId] = 'all';\n } else {\n states[productId] = 'some';\n }\n }\n\n return states;\n}\n\n/**\n * 상품 선택 동기화 파라미터\n */\ninterface SyncProductSelectionParams {\n selectedIds: number[];\n previousSelectedIds?: number[];\n}\n\n/**\n * 상품 체크박스 선택 시 옵션도 함께 선택/해제하는 핸들러\n *\n * DataGrid의 onSelectionChange 이벤트에서 호출됩니다.\n * 새로 선택된 상품의 옵션은 모두 선택하고,\n * 선택 해제된 상품의 옵션은 모두 해제합니다.\n *\n * @param action 액션 객체 (params 포함)\n * @param _context 액션 컨텍스트 (사용하지 않음 - G7Core.state.setLocal() 사용)\n */\nexport function syncProductSelectionHandler(\n action: ActionWithParams,\n _context: ActionContext\n): void {\n const params = action.params || ({} as SyncProductSelectionParams);\n const { selectedIds } = params;\n const G7Core = (window as any).G7Core;\n\n if (!G7Core?.state) {\n logger.warn('[syncProductSelection] G7Core.state를 사용할 수 없습니다.');\n return;\n }\n\n // G7Core.state.getLocal()로 현재 로컬 상태 가져오기\n const currentLocal = G7Core.state.getLocal();\n const previousSelectedIds: number[] = currentLocal.selectedItems || [];\n const currentSelectedOptions: string[] = currentLocal.selectedOptionIds || [];\n\n // 데이터소스에서 상품 목록 가져오기\n const productsData = G7Core.dataSource?.get('products');\n const products = productsData?.data?.data || [];\n\n // 새로 선택된 상품 ID들\n const newlySelected = selectedIds.filter((id: number) => !previousSelectedIds.includes(id));\n // 선택 해제된 상품 ID들\n const newlyDeselected = previousSelectedIds.filter((id: number) => !selectedIds.includes(id));\n\n let newSelectedOptions = [...currentSelectedOptions];\n\n // 새로 선택된 상품의 옵션 추가\n for (const productId of newlySelected) {\n const product = products.find((p: any) => Number(p.id) === productId);\n if (product?.options) {\n for (const opt of product.options) {\n const optionKey = makeOptionKey(productId, opt.id);\n if (!newSelectedOptions.includes(optionKey)) {\n newSelectedOptions.push(optionKey);\n }\n }\n }\n }\n\n // 선택 해제된 상품의 옵션 제거\n for (const productId of newlyDeselected) {\n newSelectedOptions = newSelectedOptions.filter(\n (key) => getProductIdFromKey(key) !== String(productId)\n );\n }\n\n // G7Core.state.setLocal()로 컴포넌트 로컬 상태 업데이트\n G7Core.state.setLocal({\n selectedItems: selectedIds,\n selectedOptionIds: newSelectedOptions,\n });\n}\n","/**\n * 상품 복사 시 새 상품코드 자동 생성 핸들러\n *\n * API를 호출하여 새로운 고유 상품코드를 생성합니다.\n * 상품 등록 폼의 generateProductCode와 동일한 API를 사용합니다.\n */\n\nimport type { ActionContext } from '../types';\n\n// Logger 설정 (G7Core 초기화 전에도 동작하도록 폴백 포함)\nconst logger = ((window as any).G7Core?.createLogger?.('Ecom:CopyCode')) ?? {\n log: (...args: unknown[]) => console.log('[Ecom:CopyCode]', ...args),\n warn: (...args: unknown[]) => console.warn('[Ecom:CopyCode]', ...args),\n error: (...args: unknown[]) => console.error('[Ecom:CopyCode]', ...args),\n};\n\n/**\n * 커스텀 핸들러에 전달되는 액션 객체 인터페이스\n */\ninterface ActionWithParams {\n handler: string;\n params?: Record;\n [key: string]: any;\n}\n\n/**\n * 새 상품코드 생성 핸들러 (API 호출 방식)\n *\n * API를 호출하여 새로운 상품코드를 생성하고 상태에 설정합니다.\n * 모달 컨텍스트에서는 부모 상태에 저장하고, 폼 컨텍스트에서는 로컬 상태에 저장합니다.\n *\n * @param action 액션 객체\n * @param context 액션 컨텍스트\n */\nexport async function generateCopyProductCodeHandler(\n action: ActionWithParams,\n context: ActionContext\n): Promise {\n const G7Core = (window as any).G7Core;\n if (!G7Core?.state || !G7Core?.api) {\n logger.warn('[generateCopyProductCode] G7Core API is not available');\n return;\n }\n\n try {\n // API 호출로 상품코드 생성 (상품 등록 폼과 동일한 API)\n const response = await G7Core.api.post(\n '/api/modules/sirsoft-ecommerce/admin/products/generate-code'\n );\n\n if (!response?.data?.product_code) {\n throw new Error('Invalid API response');\n }\n\n const newProductCode = response.data.product_code;\n\n // 모달 컨텍스트 확인: __g7LayoutContextStack에 부모 컨텍스트가 있는지 확인\n const contextStack = (window as any).__g7LayoutContextStack || [];\n const isInModalContext = contextStack.length > 0;\n\n if (isInModalContext && G7Core.state.setParentLocal) {\n // 모달에서 호출된 경우 - 부모의 _local에 저장\n G7Core.state.setParentLocal({\n 'ui.copyOptions.newProductCode': newProductCode,\n });\n logger.log(`[generateCopyProductCode] Generated (parent): ${newProductCode}`);\n } else {\n // 폼에서 호출된 경우 - 현재 _local에 저장\n const localState = G7Core.state.getLocal() || {};\n G7Core.state.setLocal({\n ui: {\n ...localState.ui,\n copyOptions: {\n ...(localState.ui?.copyOptions || {}),\n newProductCode: newProductCode,\n },\n },\n });\n logger.log(`[generateCopyProductCode] Generated (local): ${newProductCode}`);\n }\n } catch (error: any) {\n logger.error('[generateCopyProductCode] Failed:', error);\n\n // 에러 토스트 표시\n G7Core.toast?.error?.(\n G7Core.t?.('sirsoft-ecommerce.admin.product.messages.code_generate_error')\n ?? 'Failed to generate product code.'\n );\n }\n}\n","/**\n * 상품 복사 실행 핸들러\n *\n * 복사 옵션을 쿼리 파라미터로 변환하여 상품 등록 페이지로 이동합니다.\n * 등록 페이지의 copy_source data_source가 옵션에 따라 데이터를 사전 입력합니다.\n */\n\nimport type { ActionContext } from '../types';\n\n// Logger 설정 (G7Core 초기화 전에도 동작하도록 폴백 포함)\nconst logger = ((window as any).G7Core?.createLogger?.('Ecom:CopyProduct')) ?? {\n log: (...args: unknown[]) => console.log('[Ecom:CopyProduct]', ...args),\n warn: (...args: unknown[]) => console.warn('[Ecom:CopyProduct]', ...args),\n error: (...args: unknown[]) => console.error('[Ecom:CopyProduct]', ...args),\n};\n\n/**\n * 커스텀 핸들러에 전달되는 액션 객체 인터페이스\n */\ninterface ActionWithParams {\n handler: string;\n params?: Record;\n [key: string]: any;\n}\n\n/**\n * 복사 옵션 인터페이스\n */\ninterface CopyOptions {\n images?: boolean;\n options?: boolean;\n categories?: boolean;\n sales_info?: boolean;\n description?: boolean;\n notice?: boolean;\n common_info?: boolean;\n other_info?: boolean;\n shipping?: boolean;\n seo?: boolean;\n identification?: boolean;\n}\n\n/**\n * 상품 복사 실행 핸들러\n *\n * 1. 복사 옵션과 원본 상품코드 수집\n * 2. 쿼리 파라미터로 변환하여 상품 등록 페이지로 navigate\n * 3. 등록 페이지의 copy_source data_source가 사전 입력 처리\n *\n * 상품 폼(_local.form)과 상품 목록(_local.productList.copyTargetProduct) 두 컨텍스트 모두 지원합니다.\n *\n * @param action 액션 객체\n * @param context 액션 컨텍스트\n */\nexport async function copyProductHandler(\n action: ActionWithParams,\n context: ActionContext\n): Promise {\n const G7Core = (window as any).G7Core;\n if (!G7Core?.state) {\n logger.warn('[copyProduct] G7Core API is not available');\n return;\n }\n\n const localState = G7Core.state.getLocal() || {};\n\n // 모달 컨텍스트: 부모 상태에서 읽기 (getParent 지원 시)\n const parentContext = G7Core.state.getParent?.() || {};\n const parentLocal = parentContext._local || {};\n\n // copyOptions: 부모 _local 우선, 폴백으로 현재 _local\n const copyOptions: CopyOptions =\n parentLocal.ui?.copyOptions ||\n localState.ui?.copyOptions ||\n {};\n\n // 상품 폼 또는 상품 목록 컨텍스트에서 원본 상품 코드 가져오기\n const originalProductCode =\n localState.form?.product_code ||\n parentLocal.form?.product_code ||\n parentLocal.productList?.copyTargetProduct?.product_code ||\n localState.productList?.copyTargetProduct?.product_code;\n\n if (!originalProductCode) {\n G7Core.toast?.error?.(\n G7Core.t?.('sirsoft-ecommerce.admin.product.messages.copy_source_not_found')\n ?? 'Could not find source product information.'\n );\n return;\n }\n\n // 모달 닫기\n G7Core.modal?.close?.();\n\n // 쿼리 파라미터 구성\n const query: Record = {\n copy_id: originalProductCode,\n copy_images: (copyOptions.images ?? true) ? '1' : '0',\n copy_options: (copyOptions.options ?? true) ? '1' : '0',\n copy_categories: (copyOptions.categories ?? true) ? '1' : '0',\n copy_sales_info: (copyOptions.sales_info ?? true) ? '1' : '0',\n copy_description: (copyOptions.description ?? true) ? '1' : '0',\n copy_notice: (copyOptions.notice ?? true) ? '1' : '0',\n copy_common_info: (copyOptions.common_info ?? true) ? '1' : '0',\n copy_other_info: (copyOptions.other_info ?? true) ? '1' : '0',\n copy_shipping: (copyOptions.shipping ?? true) ? '1' : '0',\n copy_seo: (copyOptions.seo ?? false) ? '1' : '0',\n copy_identification: (copyOptions.identification ?? true) ? '1' : '0',\n };\n\n const queryString = new URLSearchParams(query).toString();\n const targetUrl = `/admin/ecommerce/products/create?${queryString}`;\n\n logger.log('[copyProduct] Navigating to:', targetUrl);\n\n // 상품 등록 페이지로 이동\n if (G7Core.navigate) {\n G7Core.navigate(targetUrl);\n } else if (context.navigate) {\n context.navigate(targetUrl);\n } else {\n window.location.href = targetUrl;\n }\n}\n","/**\n * 카테고리 관련 핸들러\n *\n * 상품 등록/수정 화면에서 카테고리 선택 기능을 처리합니다.\n * isolatedState를 활용하여 카테고리 선택 영역의 상태를 격리합니다.\n */\n\nimport type { ActionContext } from '../types';\n\n// Logger 설정 (G7Core 초기화 전에도 동작하도록 폴백 포함)\nconst logger = ((window as any).G7Core?.createLogger?.('Ecom:Category')) ?? {\n log: (...args: unknown[]) => console.log('[Ecom:Category]', ...args),\n warn: (...args: unknown[]) => console.warn('[Ecom:Category]', ...args),\n error: (...args: unknown[]) => console.error('[Ecom:Category]', ...args),\n};\n\ninterface Category {\n id: number;\n name: Record;\n children?: Category[];\n parent_id?: number | null;\n path?: string;\n depth?: number;\n}\n\ninterface ActionWithParams {\n handler: string;\n params?: Record;\n [key: string]: any;\n}\n\ninterface IsolatedContextValue {\n state: Record;\n setState: (path: string, value: any) => void;\n getState: (path: string) => any;\n mergeState: (updates: Record) => void;\n}\n\n/**\n * 카테고리 트리에서 특정 카테고리 ID의 브레드크럼을 생성합니다.\n *\n * @param categories 카테고리 트리 배열\n * @param targetId 찾을 카테고리 ID\n * @param locale 언어 코드\n * @param path 현재까지의 경로 (재귀용)\n * @returns 브레드크럼 문자열 또는 null\n */\nfunction buildBreadcrumbFromTree(\n categories: Category[],\n targetId: number,\n locale: string,\n path: string[] = []\n): string | null {\n for (const cat of categories) {\n const catName = cat.name?.[locale] || cat.name?.ko || '';\n\n if (cat.id === targetId) {\n return [...path, catName].join(' > ');\n }\n\n if (cat.children && cat.children.length > 0) {\n const found = buildBreadcrumbFromTree(cat.children, targetId, locale, [...path, catName]);\n if (found) {\n return found;\n }\n }\n }\n return null;\n}\n\n/**\n * form.categories 배열에서 selectedCategoryInfos 형식으로 변환합니다.\n * API에서 제공하는 breadcrumb 필드를 우선 사용합니다.\n *\n * @param formCategories API에서 가져온 상품의 카테고리 배열\n * @param categoriesTree 전체 카테고리 트리\n * @param locale 언어 코드\n * @returns selectedCategoryInfos 형식의 배열\n */\nfunction initCategoryInfosFromFormCategories(\n formCategories: Array<{ id: number; name?: Record; name_localized?: string; breadcrumb?: string }>,\n categoriesTree: Category[],\n locale: string\n): Array<{ id: number; breadcrumb: string }> {\n return formCategories.map(cat => {\n // 1. API에서 제공하는 breadcrumb 우선 사용\n if (cat.breadcrumb) {\n return { id: cat.id, breadcrumb: cat.breadcrumb };\n }\n\n // 2. 카테고리 트리에서 브레드크럼 찾기\n const breadcrumb = buildBreadcrumbFromTree(categoriesTree, cat.id, locale);\n if (breadcrumb) {\n return { id: cat.id, breadcrumb };\n }\n\n // 3. 트리에서도 못 찾으면 이름만 사용\n const catName = cat.name?.[locale] || cat.name?.ko || cat.name_localized || '';\n return { id: cat.id, breadcrumb: catName };\n });\n}\n\n/**\n * 데스크톱에서 카테고리를 선택합니다.\n *\n * - depth에 해당하는 카테고리를 선택하고 하위 depth는 초기화\n * - selectedCategories 배열을 업데이트\n * - isolatedState를 사용하여 카테고리 선택 영역만 리렌더링\n *\n * @param action 액션 객체 (params.depth, params.category 필요)\n * @param context 액션 컨텍스트 (isolatedContext 포함)\n */\nexport function selectCategoryHandler(\n action: ActionWithParams,\n context: ActionContext\n): void {\n const params = action.params || {};\n const depth = params.depth as number;\n const category = params.category as Category;\n\n if (depth === undefined || !category) {\n logger.warn('[selectCategory] Missing depth or category param');\n return;\n }\n\n // isolatedContext 우선 사용 (성능 최적화)\n const isolatedContext = (context as any).isolatedContext as IsolatedContextValue | null;\n\n if (isolatedContext) {\n const currentSelected = isolatedContext.state.selectedCategories || [null, null, null, null];\n\n // 동일한 카테고리 클릭 시 상태 업데이트 스킵 (성능 최적화)\n if (currentSelected[depth]?.id === category.id) {\n logger.log(`[selectCategory] Same category already selected at depth ${depth}, skipping`);\n return;\n }\n\n // 새로운 배열 생성 - depth까지는 유지, depth 위치에 새 카테고리, 이후는 null\n const newSelected = currentSelected.map((cat: Category | null, idx: number) => {\n if (idx < depth) return cat;\n if (idx === depth) return category;\n return null;\n });\n\n isolatedContext.mergeState({ selectedCategories: newSelected });\n logger.log(`[selectCategory] Selected category at depth ${depth} (isolated):`, category.id);\n return;\n }\n\n // 폴백: G7Core.state.setLocal() 사용\n const G7Core = (window as any).G7Core;\n if (!G7Core?.state) {\n logger.warn('[selectCategory] G7Core.state API is not available');\n return;\n }\n\n const state = G7Core.state.getLocal() || {};\n const currentSelected = state.ui?.selectedCategories || [null, null, null, null];\n\n // 동일한 카테고리 클릭 시 상태 업데이트 스킵 (성능 최적화)\n if (currentSelected[depth]?.id === category.id) {\n logger.log(`[selectCategory] Same category already selected at depth ${depth}, skipping`);\n return;\n }\n\n // 새로운 배열 생성 - depth까지는 유지, depth 위치에 새 카테고리, 이후는 null\n const newSelected = currentSelected.map((cat: Category | null, idx: number) => {\n if (idx < depth) return cat;\n if (idx === depth) return category;\n return null;\n });\n\n G7Core.state.setLocal({\n ui: {\n ...state.ui,\n selectedCategories: newSelected,\n },\n });\n\n logger.log(`[selectCategory] Selected category at depth ${depth}:`, category.id);\n}\n\n/**\n * 모바일에서 카테고리를 선택합니다.\n *\n * - 카테고리 선택 후 자식이 있으면 다음 단계로 자동 이동\n * - isolatedState를 사용하여 카테고리 선택 영역만 리렌더링\n *\n * @param action 액션 객체 (params.depth, params.category 필요)\n * @param context 액션 컨텍스트 (isolatedContext 포함)\n */\nexport function selectCategoryMobileHandler(\n action: ActionWithParams,\n context: ActionContext\n): void {\n const params = action.params || {};\n const depth = params.depth as number;\n const category = params.category as Category;\n\n if (depth === undefined || !category) {\n logger.warn('[selectCategoryMobile] Missing depth or category param');\n return;\n }\n\n // isolatedContext 우선 사용 (성능 최적화)\n const isolatedContext = (context as any).isolatedContext as IsolatedContextValue | null;\n\n if (isolatedContext) {\n const currentSelected = isolatedContext.state.selectedCategories || [null, null, null, null];\n const currentStep = isolatedContext.state.categoryStep || 1;\n\n // 새로운 배열 생성\n const newSelected = currentSelected.map((cat: Category | null, idx: number) => {\n if (idx < depth) return cat;\n if (idx === depth) return category;\n return null;\n });\n\n // 자식이 있으면 다음 단계로 이동\n const hasChildren = category.children && category.children.length > 0;\n const nextStep = hasChildren ? depth + 2 : currentStep;\n\n isolatedContext.mergeState({\n selectedCategories: newSelected,\n categoryStep: nextStep,\n });\n\n logger.log(`[selectCategoryMobile] Selected category at depth ${depth} (isolated):`, category.id, 'nextStep:', nextStep);\n return;\n }\n\n // 폴백: G7Core.state.setLocal() 사용\n const G7Core = (window as any).G7Core;\n if (!G7Core?.state) {\n logger.warn('[selectCategoryMobile] G7Core.state API is not available');\n return;\n }\n\n const state = G7Core.state.getLocal() || {};\n const currentSelected = state.ui?.selectedCategories || [null, null, null, null];\n\n // 새로운 배열 생성\n const newSelected = currentSelected.map((cat: Category | null, idx: number) => {\n if (idx < depth) return cat;\n if (idx === depth) return category;\n return null;\n });\n\n // 자식이 있으면 다음 단계로 이동\n const hasChildren = category.children && category.children.length > 0;\n const nextStep = hasChildren ? depth + 2 : state.ui?.categoryStep;\n\n G7Core.state.setLocal({\n ui: {\n ...state.ui,\n selectedCategories: newSelected,\n categoryStep: nextStep,\n },\n });\n\n logger.log(`[selectCategoryMobile] Selected category at depth ${depth}:`, category.id, 'nextStep:', nextStep);\n}\n\n/**\n * 선택된 카테고리를 form.category_ids에 추가합니다.\n *\n * - 최대 5개까지 선택 가능\n * - 중복 선택 불가\n * - 첫 번째 선택된 카테고리가 primary_category_id로 설정됨\n * - selectedCategoryInfos에 브레드크럼 정보도 함께 저장\n * - isolatedContext에서 selectedCategories를 읽고, _local.form에 저장\n *\n * @param _action 액션 객체\n * @param context 액션 컨텍스트 (isolatedContext 포함)\n */\nexport function addCategoryToSelectionHandler(\n _action: ActionWithParams,\n context: ActionContext\n): void {\n logger.log('[addCategoryToSelection] START');\n\n const G7Core = (window as any).G7Core;\n if (!G7Core?.state) {\n logger.warn('[addCategoryToSelection] G7Core.state API is not available');\n return;\n }\n\n const state = G7Core.state.getLocal() || {};\n const locale = G7Core.locale?.current?.() || 'ko';\n\n // isolatedContext에서 selectedCategories 읽기 (우선), 없으면 _local.ui에서 읽기\n const isolatedContext = (context as any).isolatedContext as IsolatedContextValue | null;\n const selectedCats = isolatedContext\n ? (isolatedContext.state.selectedCategories || [])\n : (state.ui?.selectedCategories || []);\n\n logger.log('[addCategoryToSelection] Current state:', {\n selectedCats: selectedCats.map((c: Category | null) => c?.id ?? null),\n currentCategoryIds: state.form?.category_ids,\n currentSelectedCategoryInfos: state.selectedCategoryInfos,\n usingIsolatedContext: !!isolatedContext,\n });\n\n // 마지막으로 선택된 카테고리 가져오기\n const lastSelected = selectedCats.filter(Boolean).pop();\n\n if (!lastSelected) {\n logger.warn('[addCategoryToSelection] No category selected');\n return;\n }\n\n logger.log('[addCategoryToSelection] Last selected category:', lastSelected.id, lastSelected.name);\n\n const currentIds: number[] = state.form?.category_ids || [];\n\n // 최대 5개 제한\n if (currentIds.length >= 5) {\n G7Core.toast?.warning?.(\n G7Core.t?.('sirsoft-ecommerce.admin.product.category.messages.max_5')\n ?? 'You can select up to 5 categories.'\n );\n return;\n }\n\n // 중복 체크\n if (currentIds.includes(lastSelected.id)) {\n logger.log('[addCategoryToSelection] Duplicate detected:', lastSelected.id, 'in', currentIds);\n G7Core.toast?.warning?.(\n G7Core.t?.('sirsoft-ecommerce.admin.product.category.messages.already_selected')\n ?? 'This category is already selected.'\n );\n return;\n }\n\n // 브레드크럼 생성: 선택된 카테고리 경로를 ' > '로 연결\n const breadcrumb = selectedCats\n .filter(Boolean)\n .map((cat: Category) => cat.name?.[locale] || cat.name?.ko || '')\n .join(' > ');\n\n const newCategoryIds = [...currentIds, lastSelected.id];\n const primaryCategoryId = state.form?.primary_category_id || lastSelected.id;\n\n // 카테고리 트리 가져오기 (브레드크럼 생성용)\n const dataSources = G7Core.state.getDataSources?.() || {};\n const categoriesTree = dataSources.categories?.data?.data || [];\n\n // 기존 selectedCategoryInfos에 새 정보 추가\n // selectedCategoryInfos가 비어있지만 form.categories에 데이터가 있으면 먼저 초기화\n let currentInfos: Array<{ id: number; breadcrumb: string }> = state.selectedCategoryInfos || [];\n const formCategories = state.form?.categories || [];\n\n if (currentInfos.length === 0 && formCategories.length > 0) {\n logger.log('[addCategoryToSelection] Initializing from form.categories:', formCategories.length, 'items');\n currentInfos = initCategoryInfosFromFormCategories(formCategories, categoriesTree, locale);\n }\n\n const newInfos = [...currentInfos, { id: lastSelected.id, breadcrumb }];\n\n logger.log('[addCategoryToSelection] Before setLocal:', {\n newCategoryIds,\n primaryCategoryId,\n newInfos,\n });\n\n try {\n G7Core.state.setLocal({\n form: {\n ...state.form,\n category_ids: newCategoryIds,\n primary_category_id: primaryCategoryId,\n },\n selectedCategoryInfos: newInfos,\n hasChanges: true,\n });\n logger.log('[addCategoryToSelection] setLocal SUCCESS');\n } catch (error) {\n logger.error('[addCategoryToSelection] setLocal FAILED:', error);\n }\n\n // setLocal 후 상태 확인\n const afterState = G7Core.state.getLocal() || {};\n logger.log('[addCategoryToSelection] After setLocal:', {\n category_ids: afterState.form?.category_ids,\n selectedCategoryInfos: afterState.selectedCategoryInfos,\n });\n\n logger.log(`[addCategoryToSelection] Added category ${lastSelected.id} with breadcrumb: ${breadcrumb}. Total: ${newCategoryIds.length}`);\n}\n\n/**\n * 선택된 카테고리를 form.category_ids에서 제거합니다.\n *\n * - primary_category_id가 제거되면 첫 번째 남은 카테고리로 변경\n * - selectedCategoryInfos에서도 해당 카테고리 정보 제거\n *\n * @param action 액션 객체 (params.categoryId 필요)\n * @param _context 액션 컨텍스트\n */\nexport function removeCategoryFromSelectionHandler(\n action: ActionWithParams,\n _context: ActionContext\n): void {\n const params = action.params || {};\n const categoryId = params.categoryId as number;\n\n if (!categoryId) {\n logger.warn('[removeCategoryFromSelection] Missing categoryId param');\n return;\n }\n\n const G7Core = (window as any).G7Core;\n if (!G7Core?.state) {\n logger.warn('[removeCategoryFromSelection] G7Core.state API is not available');\n return;\n }\n\n const state = G7Core.state.getLocal() || {};\n const currentIds: number[] = state.form?.category_ids || [];\n\n const newIds = currentIds.filter((id: number) => id !== categoryId);\n\n // primary_category_id 업데이트\n let newPrimaryCategoryId = state.form?.primary_category_id;\n if (newPrimaryCategoryId === categoryId) {\n newPrimaryCategoryId = newIds[0] || null;\n }\n\n // selectedCategoryInfos에서도 해당 카테고리 제거\n const currentInfos: Array<{ id: number; breadcrumb: string }> = state.selectedCategoryInfos || [];\n const newInfos = currentInfos.filter((info) => info.id !== categoryId);\n\n G7Core.state.setLocal({\n form: {\n ...state.form,\n category_ids: newIds,\n primary_category_id: newPrimaryCategoryId,\n },\n selectedCategoryInfos: newInfos,\n hasChanges: true,\n });\n\n logger.log(`[removeCategoryFromSelection] Removed category ${categoryId}. Remaining: ${newIds.length}`);\n}\n\n/**\n * 카테고리 ID로 브레드크럼 문자열을 반환합니다.\n *\n * @param action 액션 객체 (params.categoryId, params.categories 필요)\n * @param _context 액션 컨텍스트\n * @returns 브레드크럼 문자열 (예: \"의류 > 남성 > 상의\")\n */\nexport function getCategoryBreadcrumbHandler(\n action: ActionWithParams,\n _context: ActionContext\n): string {\n const params = action.params || {};\n const categoryId = params.categoryId as number;\n const categories = params.categories as Category[];\n\n if (!categoryId || !categories || !Array.isArray(categories)) {\n return '';\n }\n\n const G7Core = (window as any).G7Core;\n const locale = G7Core?.locale?.current?.() || 'ko';\n\n /**\n * 재귀적으로 카테고리 경로를 찾습니다.\n */\n const findPath = (\n cats: Category[],\n targetId: number,\n path: string[] = []\n ): string[] | null => {\n for (const cat of cats) {\n const catName = cat.name?.[locale] || cat.name?.ko || '';\n\n if (cat.id === targetId) {\n return [...path, catName];\n }\n\n if (cat.children && cat.children.length > 0) {\n const found = findPath(cat.children, targetId, [...path, catName]);\n if (found) {\n return found;\n }\n }\n }\n return null;\n };\n\n const pathNames = findPath(categories, categoryId);\n return pathNames ? pathNames.join(' > ') : '';\n}\n\n/**\n * 상품 데이터 로드 시 categories 배열로부터 selectedCategoryInfos를 초기화합니다.\n *\n * - API 응답의 categories 배열에서 브레드크럼 정보 생성\n * - _local.selectedCategoryInfos를 설정하여 선택된 카테고리 UI 표시\n *\n * @param action 액션 객체 (params.categories: 상품의 카테고리 배열)\n * @param _context 액션 컨텍스트\n */\nexport function initCategoryInfosFromProductHandler(\n _action: ActionWithParams,\n _context: ActionContext\n): void {\n logger.log('[initCategoryInfosFromProduct] START');\n\n const G7Core = (window as any).G7Core;\n if (!G7Core?.state) {\n logger.warn('[initCategoryInfosFromProduct] G7Core.state API is not available');\n return;\n }\n\n const state = G7Core.state.getLocal() || {};\n const locale = G7Core.locale?.current?.() || 'ko';\n\n // categories 데이터소스에서 전체 카테고리 트리 가져오기\n const dataSources = G7Core.state.getDataSources?.() || {};\n const categoriesTree = dataSources.categories?.data?.data || [];\n\n // 상품의 카테고리 정보 (API 응답에서 가져온 것)\n const productCategories = state.form?.categories || [];\n const categoryIds = state.form?.category_ids || [];\n\n logger.log('[initCategoryInfosFromProduct] Product categories:', productCategories);\n logger.log('[initCategoryInfosFromProduct] Category IDs:', categoryIds);\n logger.log('[initCategoryInfosFromProduct] Categories tree available:', categoriesTree.length > 0);\n\n if (categoryIds.length === 0) {\n logger.log('[initCategoryInfosFromProduct] No categories to initialize');\n return;\n }\n\n // 카테고리 ID로 브레드크럼을 찾는 헬퍼 함수\n const findBreadcrumb = (\n cats: Category[],\n targetId: number,\n path: string[] = []\n ): string[] | null => {\n for (const cat of cats) {\n const catName = cat.name?.[locale] || cat.name?.ko || '';\n\n if (cat.id === targetId) {\n return [...path, catName];\n }\n\n if (cat.children && cat.children.length > 0) {\n const found = findBreadcrumb(cat.children, targetId, [...path, catName]);\n if (found) {\n return found;\n }\n }\n }\n return null;\n };\n\n // selectedCategoryInfos 생성\n const selectedCategoryInfos: Array<{ id: number; breadcrumb: string }> = [];\n\n for (const categoryId of categoryIds) {\n // 먼저 카테고리 트리에서 브레드크럼 찾기\n const pathNames = findBreadcrumb(categoriesTree, categoryId);\n\n if (pathNames) {\n selectedCategoryInfos.push({\n id: categoryId,\n breadcrumb: pathNames.join(' > '),\n });\n } else {\n // 트리에서 못 찾으면 productCategories에서 직접 이름 사용\n const productCat = productCategories.find((c: any) => c.id === categoryId);\n if (productCat) {\n const catName = productCat.name?.[locale] || productCat.name?.ko || productCat.name_localized || '';\n selectedCategoryInfos.push({\n id: categoryId,\n breadcrumb: catName,\n });\n }\n }\n }\n\n logger.log('[initCategoryInfosFromProduct] Generated selectedCategoryInfos:', selectedCategoryInfos);\n\n // 상태 업데이트\n G7Core.state.setLocal({\n selectedCategoryInfos,\n });\n\n logger.log('[initCategoryInfosFromProduct] DONE');\n}\n\n/**\n * URL 파라미터로 받은 카테고리 경로를 검증하고 유효한 것만 반환합니다.\n *\n * @param action 액션 객체\n * @param _context 액션 컨텍스트\n * @returns 유효한 카테고리 ID 객체 { category1, category2, category3, category4 }\n */\nexport function validateCategoryPathHandler(\n action: ActionWithParams,\n _context: ActionContext\n): { category1: string; category2: string; category3: string; category4: string } {\n const result = {\n category1: '',\n category2: '',\n category3: '',\n category4: '',\n };\n\n const params = action.params || {};\n const categoryId1 = params.categoryId1 ? String(params.categoryId1) : '';\n const categoryId2 = params.categoryId2 ? String(params.categoryId2) : '';\n const categoryId3 = params.categoryId3 ? String(params.categoryId3) : '';\n const categoryId4 = params.categoryId4 ? String(params.categoryId4) : '';\n\n // 카테고리 데이터가 없으면 빈 결과 반환\n const categories = params.categories as Category[] | undefined;\n if (!categories || !Array.isArray(categories) || categories.length === 0) {\n logger.warn('[validateCategoryPath] No categories data provided');\n return result;\n }\n\n // category1 검증 (depth 0인 최상위 카테고리여야 함)\n if (categoryId1) {\n const cat1 = categories.find((cat) => String(cat.id) === categoryId1);\n if (cat1 && cat1.depth === 0) {\n result.category1 = categoryId1;\n\n // category2 검증 (category1의 자식이어야 함)\n if (categoryId2 && cat1.children) {\n const cat2 = cat1.children.find((cat) => String(cat.id) === categoryId2);\n if (cat2 && cat2.depth === 1) {\n result.category2 = categoryId2;\n\n // category3 검증 (category2의 자식이어야 함)\n if (categoryId3 && cat2.children) {\n const cat3 = cat2.children.find((cat) => String(cat.id) === categoryId3);\n if (cat3 && cat3.depth === 2) {\n result.category3 = categoryId3;\n\n // category4 검증 (category3의 자식이어야 함)\n if (categoryId4 && cat3.children) {\n const cat4 = cat3.children.find((cat) => String(cat.id) === categoryId4);\n if (cat4 && cat4.depth === 3) {\n result.category4 = categoryId4;\n }\n }\n }\n }\n }\n }\n }\n }\n\n logger.log('[validateCategoryPath] Result:', result);\n return result;\n}\n","/**\n * 브랜드 관련 핸들러\n *\n * 상품 등록/수정 화면에서 브랜드 정보 조회 기능을 처리합니다.\n */\n\nimport type { ActionContext } from '../types';\n\ninterface Brand {\n id: number;\n name: Record;\n description?: Record;\n}\n\ninterface ActionWithParams {\n handler: string;\n params?: Record;\n [key: string]: any;\n}\n\n/**\n * 브랜드 ID로 브랜드명을 반환합니다.\n *\n * @param action 액션 객체 (params.brandId, params.brands 필요)\n * @param _context 액션 컨텍스트\n * @returns 브랜드명 문자열\n */\nexport function getBrandNameHandler(\n action: ActionWithParams,\n _context: ActionContext\n): string {\n const params = action.params || {};\n const brandId = params.brandId as number;\n const brands = params.brands as Brand[];\n\n if (!brandId || !brands || !Array.isArray(brands)) {\n return '';\n }\n\n const G7Core = (window as any).G7Core;\n const locale = G7Core?.locale?.current?.() || 'ko';\n\n const brand = brands.find((b) => b.id === brandId);\n return brand ? (brand.name?.[locale] ?? brand.name?.ko ?? '') : '';\n}\n\n/**\n * 브랜드 ID로 브랜드 설명을 반환합니다.\n *\n * @param action 액션 객체 (params.brandId, params.brands 필요)\n * @param _context 액션 컨텍스트\n * @returns 브랜드 설명 문자열\n */\nexport function getBrandDescriptionHandler(\n action: ActionWithParams,\n _context: ActionContext\n): string {\n const params = action.params || {};\n const brandId = params.brandId as number;\n const brands = params.brands as Brand[];\n\n if (!brandId || !brands || !Array.isArray(brands)) {\n return '';\n }\n\n const G7Core = (window as any).G7Core;\n const locale = G7Core?.locale?.current?.() || 'ko';\n\n const brand = brands.find((b) => b.id === brandId);\n return brand?.description?.[locale] ?? brand?.description?.ko ?? '';\n}\n","// e2e:allow base_unit 환산 공식(÷base_unit) 변경. 환산 정확성은 단위 테스트로 검증, 다통화 표시 회귀는 product-option-multicurrency-readonly.spec.ts 가 구조적으로 차단.\n/**\n * 가격 관련 핸들러\n *\n * 상품 등록/수정 화면에서 가격 업데이트 및 재고 계산 기능을 처리합니다.\n */\n\nimport type { ActionContext } from '../types';\n\n// Logger 설정 (G7Core 초기화 전에도 동작하도록 폴백 포함)\nconst logger = ((window as any).G7Core?.createLogger?.('Ecom:Price')) ?? {\n log: (...args: unknown[]) => console.log('[Ecom:Price]', ...args),\n warn: (...args: unknown[]) => console.warn('[Ecom:Price]', ...args),\n error: (...args: unknown[]) => console.error('[Ecom:Price]', ...args),\n};\n\ninterface Currency {\n code: string;\n exchange_rate: number;\n symbol?: string;\n is_default?: boolean;\n base_unit?: number;\n}\n\n/**\n * base_unit 미설정 통화의 폴백 (소액 통화만 묶음 단위). 백엔드 CurrencyConversionService 와 동일.\n */\nconst BASE_UNIT_FALLBACK: Record = {\n KRW: 1000,\n JPY: 100,\n};\n\n/**\n * 기본 통화의 base_unit(환율 분모)을 통화 목록에서 해석합니다.\n *\n * @param currencies 통화 목록\n * @param defaultCurrency 기본 통화 코드\n * @returns base_unit (최소 1)\n */\nfunction resolveDefaultBaseUnit(currencies: Currency[], defaultCurrency: string): number {\n const base = currencies.find((c) => c.code === defaultCurrency);\n const unit = base?.base_unit ?? BASE_UNIT_FALLBACK[defaultCurrency] ?? 1;\n return Math.max(1, unit);\n}\n\ninterface ProductOption {\n id?: number | null;\n option_code: string;\n stock_quantity: number;\n [key: string]: any;\n}\n\ninterface ActionWithParams {\n handler: string;\n params?: Record;\n [key: string]: any;\n}\n\n/**\n * 환율 기반 가격을 계산합니다.\n *\n * @param basePrice 기준 통화 가격\n * @param exchangeRate 환율\n * @param roundingMethod 반올림 방법 ('round', 'ceil', 'floor')\n * @param baseUnit 기본 통화 base_unit (환율 분모)\n * @returns 변환된 가격\n */\nfunction calculateCurrencyPrice(\n basePrice: number,\n exchangeRate: number,\n roundingMethod: string,\n baseUnit: number\n): number {\n const converted = (basePrice / baseUnit) * exchangeRate;\n\n switch (roundingMethod) {\n case 'ceil':\n return Math.ceil(converted);\n case 'floor':\n return Math.floor(converted);\n case 'round':\n default:\n return Math.round(converted);\n }\n}\n\n/**\n * 다중 통화 가격을 업데이트합니다.\n *\n * - 기준 통화 변경 시 다른 통화 가격을 자동으로 환율 기반 계산\n * - 메인 가격 필드(list_price, selling_price)도 함께 업데이트\n *\n * @param action 액션 객체 (params.field, params.currency, params.value, params.autoFill 필요)\n * @param context 액션 컨텍스트 (datasources.currencies 사용)\n */\nexport function updatePriceHandler(\n action: ActionWithParams,\n context: ActionContext\n): void {\n const params = action.params || {};\n const field = params.field as 'list' | 'selling';\n const currency = params.currency as string;\n const value = params.value as number;\n const autoFill = params.autoFill as boolean;\n\n if (!field || !currency || value === undefined) {\n logger.warn('[updatePrice] Missing required params: field, currency, or value');\n return;\n }\n\n const G7Core = (window as any).G7Core;\n if (!G7Core?.state) {\n logger.warn('[updatePrice] G7Core.state API is not available');\n return;\n }\n\n const state = G7Core.state.getLocal() || {};\n const currencyData = context.datasources?.currencies?.data || {};\n const currencies = currencyData?.list || [];\n const defaultCurrency = currencyData?.default_currency || 'KRW';\n const roundingMethod = currencyData?.rounding_method || 'round';\n\n const newPrices = { ...(state.form?.multi_currency_prices || {}) };\n newPrices[currency] = {\n ...(newPrices[currency] || {}),\n [field]: value,\n };\n\n // 기준 통화 변경 시 다른 통화 자동 계산\n if (autoFill && currency === defaultCurrency) {\n const baseUnit = resolveDefaultBaseUnit(currencies, defaultCurrency);\n currencies.forEach((curr: Currency) => {\n if (curr.code !== defaultCurrency) {\n const exchangeRate = curr.exchange_rate ?? 1;\n const convertedValue = calculateCurrencyPrice(value, exchangeRate, roundingMethod, baseUnit);\n\n newPrices[curr.code] = {\n ...(newPrices[curr.code] || {}),\n [field]: convertedValue,\n };\n }\n });\n }\n\n // 메인 가격 필드 매핑\n const mainFieldMap: Record = {\n list: 'list_price',\n selling: 'selling_price',\n };\n\n const mainFieldValue =\n currency === defaultCurrency ? value : state.form?.[mainFieldMap[field]];\n\n G7Core.state.setLocal({\n form: {\n ...state.form,\n multi_currency_prices: newPrices,\n [mainFieldMap[field]]: mainFieldValue,\n },\n hasChanges: true,\n });\n\n logger.log(`[updatePrice] Updated ${field} price for ${currency}: ${value}`);\n}\n\n/**\n * 전체 옵션 재고를 계산합니다.\n *\n * @param action 액션 객체 (params.options 필요)\n * @param _context 액션 컨텍스트\n * @returns 총 재고 수량\n */\nexport function calculateTotalOptionStockHandler(\n action: ActionWithParams,\n _context: ActionContext\n): number {\n const params = action.params || {};\n const options = params.options as ProductOption[];\n\n if (!options || !Array.isArray(options) || options.length === 0) {\n return 0;\n }\n\n const totalStock = options.reduce((sum, opt) => {\n if (opt.is_active === false) return sum;\n return sum + (opt.stock_quantity || 0);\n }, 0);\n\n logger.log(`[calculateTotalOptionStock] Total stock: ${totalStock}`);\n return totalStock;\n}\n\n/**\n * 판매가/정가 관계를 검증합니다.\n *\n * - 판매가가 정가보다 클 경우 경고 표시\n * - 검증 결과를 _local.validation.price_error와 _local.errors.selling_price에 저장\n * - 토스트 경고 + 필드 에러 강조 + 하단 에러 메시지 표시\n *\n * 참고: 자동 바인딩 + debounce 사용 시, 상태 업데이트 완료 후 이 핸들러가 호출됩니다.\n * 따라서 state.form에서 최신 값을 읽을 수 있습니다.\n *\n * @param _action 액션 객체\n * @param _context 액션 컨텍스트\n *\n * @example\n * { \"handler\": \"sirsoft-ecommerce.validatePriceRelation\" }\n */\nexport function validatePriceRelationHandler(\n _action: ActionWithParams,\n _context: ActionContext\n): void {\n const G7Core = (window as any).G7Core;\n if (!G7Core?.state) {\n logger.warn('[validatePriceRelation] G7Core.state API is not available');\n return;\n }\n\n const state = G7Core.state.getLocal() || {};\n\n // 자동 바인딩 debounce 완료 후 호출되므로 상태에서 최신 값을 읽음\n const listPrice = parseFloat(String(state.form?.list_price)) || 0;\n const sellingPrice = parseFloat(String(state.form?.selling_price)) || 0;\n\n const hasError = sellingPrice > listPrice && listPrice > 0;\n\n const errorMessage = G7Core.t?.('sirsoft-ecommerce.admin.product.validation.selling_price_exceeds_list_price')\n ?? '판매가는 정가보다 클 수 없습니다.';\n\n // errors 객체 업데이트 - null 대신 삭제하여 상단 에러 박스가 올바르게 표시되도록\n const newErrors = { ...(state.errors || {}) };\n if (hasError) {\n newErrors.selling_price = [errorMessage];\n } else {\n delete newErrors.selling_price;\n }\n\n // errors 객체가 비어있으면 null로 설정 (상단 에러 박스 숨김)\n const hasAnyError = Object.keys(newErrors).length > 0;\n\n G7Core.state.setLocal({\n validation: {\n ...state.validation,\n price_error: hasError,\n },\n errors: hasAnyError ? newErrors : null,\n });\n\n if (hasError) {\n G7Core.toast?.warning?.(errorMessage);\n logger.warn(`[validatePriceRelation] Selling price (${sellingPrice}) exceeds list price (${listPrice})`);\n } else {\n logger.log('[validatePriceRelation] Price validation passed');\n }\n}\n","// e2e:allow base_unit 환산 공식(÷base_unit) 변경. 옵션 다통화 환산 정확성은 단위 테스트로 검증, 표시 회귀는 product-option-multicurrency-readonly.spec.ts 가 구조적으로 차단.\n/**\n * 상품옵션 추가 핸들러\n *\n * 상품 등록/수정 화면에서 다중 통화 자동 입력, 기본 옵션 설정, 옵션 행 추가 기능을 처리합니다.\n * (07-시안분석-상품옵션-통화자동입력.md 참조)\n */\n\nimport type { ActionContext } from '../types';\nimport { formatCurrency } from './calculateCurrencyPrices';\n\n// Logger 설정 (G7Core 초기화 전에도 동작하도록 폴백 포함)\nconst logger = ((window as any).G7Core?.createLogger?.('Ecom:ProductOption')) ?? {\n log: (...args: unknown[]) => console.log('[Ecom:ProductOption]', ...args),\n warn: (...args: unknown[]) => console.warn('[Ecom:ProductOption]', ...args),\n error: (...args: unknown[]) => console.error('[Ecom:ProductOption]', ...args),\n};\n\n/**\n * 지원 로케일 목록을 반환합니다.\n */\nfunction getSupportedLocales(): string[] {\n const G7Core = (window as any).G7Core;\n return G7Core?.config?.('app.supported_locales') ?? ['ko', 'en'];\n}\n\n/**\n * 빈 다국어 필드를 생성합니다.\n */\nfunction createEmptyLocalizedField(): Record {\n const locales = getSupportedLocales();\n return locales.reduce((acc, locale) => ({ ...acc, [locale]: '' }), {} as Record);\n}\n\n/**\n * 활성 옵션들의 재고 합계를 계산합니다.\n *\n * 옵션 보유 상품의 원 상품 재고는 활성(`is_active !== false`) 옵션 재고의 합으로 정해진다.\n * 옵션 생성/행 추가/삭제/수정 등 옵션 목록이 바뀌는 모든 지점에서 이 합계로 상품 재고를\n * 즉시 동기화한다 (백엔드 syncProductStock 과 동일 규칙의 프론트 미러).\n *\n * @param options 옵션 목록\n * @returns 활성 옵션 재고 합계\n */\nexport function sumActiveOptionStock(options: Array<{ stock_quantity?: any; is_active?: boolean }>): number {\n return (options ?? [])\n .filter((opt) => opt.is_active !== false)\n .reduce((sum, opt) => sum + (parseInt(String(opt.stock_quantity), 10) || 0), 0);\n}\n\ninterface ProductOption {\n id?: number | null;\n option_code: string;\n option_name?: Record;\n option_values: Array<{ key: Record; value: Record }> | Record;\n is_default: boolean;\n regular_price: number;\n sale_price: number;\n list_price?: number;\n selling_price?: number;\n price_adjustment?: number;\n multi_currency_selling_price?: Record;\n sku: string;\n stock_quantity: number;\n safe_stock_quantity: number;\n weight: number;\n volume: number;\n mileage_value: number;\n mileage_type: 'percent' | 'fixed';\n is_active: boolean;\n [key: string]: any;\n}\n\ninterface Currency {\n code: string;\n name: Record | string;\n is_default: boolean;\n exchange_rate: number;\n rounding_unit?: string;\n rounding_method?: string;\n decimal_places?: number;\n base_unit?: number;\n symbol?: string;\n}\n\n/**\n * base_unit 미설정 통화의 폴백 (소액 통화만 묶음 단위). 백엔드 CurrencyConversionService 와 동일.\n */\nconst BASE_UNIT_FALLBACK: Record = {\n KRW: 1000,\n JPY: 100,\n};\n\n/**\n * 전역 통화 설정에서 기본 통화의 base_unit(환율 분모)을 해석합니다.\n *\n * @returns base_unit (최소 1)\n */\nfunction resolveDefaultBaseUnit(): number {\n const globalState = (window as any).G7Core?.state?.get?.() || {};\n const list: Currency[] = globalState.modules?.['sirsoft-ecommerce']?.language_currency?.currencies || [];\n const base = list.find((c) => c.is_default);\n if (!base) {\n return 1;\n }\n const unit = base.base_unit ?? BASE_UNIT_FALLBACK[base.code] ?? 1;\n return Math.max(1, unit);\n}\n\n/**\n * 절사/반올림/올림을 적용합니다.\n *\n * @param price 가격\n * @param unit 절사 단위 (예: '0.01', '1', '10')\n * @param method 방법 (floor, round, ceil)\n * @returns 처리된 가격\n */\nfunction applyRounding(price: number, unit: string, method: string): number {\n const unitValue = parseFloat(unit) || 1;\n if (unitValue <= 0) {\n return price;\n }\n\n const divided = price / unitValue;\n let rounded: number;\n\n switch (method) {\n case 'ceil':\n rounded = Math.ceil(divided);\n break;\n case 'floor':\n rounded = Math.floor(divided);\n break;\n default:\n rounded = Math.round(divided);\n }\n\n return rounded * unitValue;\n}\n\n/**\n * 기본통화 가격을 외화로 변환합니다.\n *\n * 계산: (basePrice / baseUnit) * exchange_rate → applyRounding → decimal_places 적용\n *\n * 관리자 상품폼·데이터그리드는 base 판매가만 입력받고 다통화는 읽기전용 환산값을\n * 표시한다. 표시용 `formatted` 를 함께 생성해 백엔드 Resource(`HasMultiCurrencyPrices`)\n * 응답이 없는 신규 등록 화면에서도 장바구니와 동일한 통화 표기를 노출한다.\n *\n * baseUnit 미전달 시 전역 통화 설정에서 기본 통화 base_unit 을 해석한다(폴백 KRW=1000 등).\n *\n * @param basePrice 기본통화 가격\n * @param currency 통화 설정\n * @param baseUnit 기본 통화 base_unit (환율 분모). 생략 시 전역 설정에서 해석.\n * @returns { price: 변환된 가격, formatted: 표시용 문자열 } 객체\n */\nexport function convertCurrencyPrice(basePrice: number, currency: Currency, baseUnit?: number): { price: number; formatted: string } {\n const exchangeRate = currency.exchange_rate || 0;\n if (exchangeRate <= 0) {\n return { price: 0, formatted: formatCurrency(0, currency) };\n }\n\n const divisor = baseUnit ?? resolveDefaultBaseUnit();\n const convertedPrice = (basePrice / divisor) * exchangeRate;\n const roundedPrice = applyRounding(\n convertedPrice,\n currency.rounding_unit || '0.01',\n currency.rounding_method || 'round'\n );\n\n // 소수 자릿수 제한 (환경설정 decimal_places 기준)\n const decimalPlaces = currency.decimal_places ?? 2;\n const finalPrice = parseFloat(roundedPrice.toFixed(decimalPlaces));\n\n return { price: finalPrice, formatted: formatCurrency(finalPrice, currency) };\n}\n\ninterface OptionGroup {\n name: Record;\n values: string[];\n}\n\n/**\n * 상품 판매가를 기준으로 모든 옵션의 selling_price 와 다중통화 가격을 재계산합니다.\n *\n * 옵션 selling_price = 상품 판매가 + 옵션 price_adjustment (가산액 유지·재계산, B1)\n * 정방향(상품가→옵션) / 역방향(기본옵션가→상품가) / 기본옵션 변경 모두 이 함수를 재사용합니다.\n *\n * @param options 옵션 배열\n * @param newProductPrice 새 상품 판매가\n * @param currencies 다중통화 자동 계산 대상 통화 목록 (빈 배열이면 다중통화 미계산)\n * @returns 재계산된 옵션 배열\n */\nexport function recalcOptionsFromProductPrice(\n options: ProductOption[],\n newProductPrice: number,\n currencies: Currency[],\n): ProductOption[] {\n return options.map((opt) => {\n const priceAdjustment = parseFloat(String(opt.price_adjustment)) || 0;\n const newOptionSellingPrice = newProductPrice + priceAdjustment;\n\n const updatedOpt: ProductOption = {\n ...opt,\n selling_price: newOptionSellingPrice,\n };\n\n if (currencies.length > 0) {\n const baseUnit = resolveDefaultBaseUnit();\n const multiCurrencyPrices: Record = {};\n currencies.forEach((currency) => {\n multiCurrencyPrices[currency.code] = convertCurrencyPrice(newOptionSellingPrice, currency, baseUnit);\n });\n updatedOpt.multi_currency_selling_price = multiCurrencyPrices;\n }\n\n return updatedOpt;\n });\n}\n\n/**\n * 다중통화 자동 계산 대상 통화 목록을 반환합니다. (기본 통화 제외, 환율 설정된 통화)\n *\n * 다통화 가격은 base 판매가에서 환율로 항상 자동 계산되어 읽기전용으로 표시되므로\n * 별도 토글 없이 상시 환율 통화를 반환한다.\n *\n * @returns 대상 통화 목록 (환율 설정된 비-기본 통화)\n */\nfunction resolveAutoCurrencies(): Currency[] {\n const globalState = (window as any).G7Core?.state?.get?.() || {};\n return (globalState.modules?.['sirsoft-ecommerce']?.language_currency?.currencies || [])\n .filter((c: Currency) => !c.is_default && c.exchange_rate);\n}\n\ninterface ActionWithParams {\n handler: string;\n params?: Record;\n [key: string]: any;\n}\n\n/**\n * 기본 옵션을 선택합니다. (Radio 버튼)\n *\n * - 모든 옵션의 is_default를 false로 설정\n * - 선택된 옵션만 is_default를 true로 설정\n *\n * @param action 액션 객체 (params.optionCode 필요)\n * @param _context 액션 컨텍스트\n */\nexport function setDefaultOptionHandler(\n action: ActionWithParams,\n _context: ActionContext\n): void {\n const params = action.params || {};\n\n const G7Core = (window as any).G7Core;\n if (!G7Core?.state) {\n logger.warn('[setDefaultOption] G7Core.state API is not available');\n return;\n }\n\n const state = G7Core.state.getLocal() || {};\n const options: ProductOption[] = [...(state.form?.options ?? [])];\n\n // 레이아웃은 옵션 라디오에서 params.index 를 전달한다(쿠폰/검수 대조 확인).\n // 하위 호환을 위해 optionCode 도 수용한다.\n const rawIndex = params.index;\n const index = rawIndex !== undefined && rawIndex !== null\n ? (typeof rawIndex === 'string' ? parseInt(rawIndex, 10) : rawIndex as number)\n : -1;\n const optionCode = (params.optionCode as string) ?? options[index]?.option_code;\n\n if (!optionCode) {\n logger.warn('[setDefaultOption] Missing optionCode/index param', { index: rawIndex });\n return;\n }\n\n // 모든 옵션의 is_default를 false로 설정하고, 선택된 옵션만 true\n let updatedOptions = options.map((opt) => ({\n ...opt,\n is_default: opt.option_code === optionCode,\n }));\n\n // 새 기본 옵션의 판매가를 상품 판매가로 동기화 + 기본옵션 adj=0 + 전체 재계산\n const newDefault = updatedOptions.find((opt) => opt.is_default === true);\n const newProductPrice = newDefault\n ? parseFloat(String(newDefault.selling_price)) || (parseFloat(String(state.form?.selling_price)) || 0)\n : parseFloat(String(state.form?.selling_price)) || 0;\n\n if (newDefault) {\n newDefault.price_adjustment = 0;\n const currencies = resolveAutoCurrencies();\n updatedOptions = recalcOptionsFromProductPrice(updatedOptions, newProductPrice, currencies);\n }\n\n G7Core.state.setLocal({\n form: { ...state.form, options: updatedOptions, selling_price: newProductPrice },\n hasChanges: true,\n });\n\n logger.log(`[setDefaultOption] Set default option to ${optionCode}, synced product selling_price to ${newProductPrice}`);\n}\n\n/**\n * 상품 폼에서 옵션 필드를 업데이트합니다.\n *\n * _local.form.options[index][field] = value\n *\n * @param action 액션 객체 (params.index, params.field, params.value 필요)\n * @param _context 액션 컨텍스트\n */\nexport function updateFormOptionFieldHandler(\n action: ActionWithParams,\n _context: ActionContext\n): void {\n const params = action.params || {};\n const index = typeof params.index === 'string' ? parseInt(params.index, 10) : params.index as number;\n const field = params.field as string;\n const value = params.value;\n\n if (index === undefined || index === null || !field) {\n logger.warn('[updateFormOptionField] Missing required params:', { index, field });\n return;\n }\n\n const G7Core = (window as any).G7Core;\n if (!G7Core?.state) {\n logger.warn('[updateFormOptionField] G7Core.state API is not available');\n return;\n }\n\n const state = G7Core.state.getLocal() || {};\n const options: ProductOption[] = [...(state.form?.options ?? [])];\n\n if (index < 0 || index >= options.length) {\n logger.warn('[updateFormOptionField] Index out of bounds:', { index, length: options.length });\n return;\n }\n\n // 숫자 필드는 숫자로 변환\n const numericFields = ['regular_price', 'sale_price', 'list_price', 'selling_price', 'stock_quantity', 'safe_stock_quantity', 'weight', 'volume', 'mileage_value'];\n let finalValue = value;\n if (numericFields.includes(field)) {\n finalValue = parseFloat(value) || 0;\n }\n\n // 옵션 업데이트\n options[index] = {\n ...options[index],\n [field]: finalValue,\n };\n\n // selling_price 변경 시 처리 (역방향 동기화 포함)\n if (field === 'selling_price') {\n const optionSellingPrice = parseFloat(String(finalValue)) || 0;\n const currencies = resolveAutoCurrencies();\n\n // 역방향 동기화: 기본 옵션 판매가 변경 → 상품 판매가 동기화 + 전체 옵션 재계산\n // (기본 옵션은 정의상 상품 판매가 = 기본 옵션 판매가, price_adjustment 항상 0)\n if (options[index].is_default === true) {\n options[index].price_adjustment = 0;\n const recalculated = recalcOptionsFromProductPrice(options, optionSellingPrice, currencies);\n\n G7Core.state.setLocal({\n form: { ...state.form, options: recalculated, selling_price: optionSellingPrice },\n hasChanges: true,\n });\n\n logger.log(`[updateFormOptionField] Reverse-synced product selling_price to ${optionSellingPrice} from default option`);\n return;\n }\n\n // 비기본 옵션: price_adjustment = 옵션 판매가 - 상품 판매가 (상품 판매가 불변)\n const productSellingPrice = parseFloat(String(state.form?.selling_price)) || 0;\n options[index].price_adjustment = optionSellingPrice - productSellingPrice;\n\n // 자기 옵션 다중통화만 재계산\n if (currencies.length > 0) {\n const baseUnit = resolveDefaultBaseUnit();\n const multiCurrencyPrices: Record = {};\n currencies.forEach((currency) => {\n multiCurrencyPrices[currency.code] = convertCurrencyPrice(optionSellingPrice, currency, baseUnit);\n });\n options[index].multi_currency_selling_price = multiCurrencyPrices;\n logger.log(`[updateFormOptionField] Recalculated multi_currency_selling_price for option at index ${index}:`, currencies.map(c => c.code));\n }\n }\n\n // stock_quantity 또는 is_active 변경 시 상품 재고 자동 합산\n if (field === 'stock_quantity' || field === 'is_active') {\n if (options.length > 0) {\n const totalStock = sumActiveOptionStock(options);\n\n G7Core.state.setLocal({\n form: { ...state.form, options, stock_quantity: totalStock },\n hasChanges: true,\n });\n\n logger.log(`[updateFormOptionField] Updated product stock_quantity to ${totalStock} (sum of active options)`);\n return;\n }\n }\n\n G7Core.state.setLocal({\n form: { ...state.form, options },\n hasChanges: true,\n });\n\n logger.log(`[updateFormOptionField] Updated options[${index}].${field} =`, finalValue);\n}\n\n/**\n * 옵션 행을 수동으로 추가합니다. (+ 행 추가 버튼)\n *\n * - 기존 옵션 그룹의 구조를 따름\n * - 첫 번째 옵션이면 기본으로 설정\n * - 신규 등록 모드(params.isCreate === true)에서는 재고 기본값을 1로 채워 입력 수고를 던다.\n * 수정 모드에서는 기존 동작(0)을 유지한다. 등록/수정 구분은 레이아웃이 route.itemCode 로\n * 판정해 params.isCreate 로 전달한다.\n *\n * @param action 액션 객체 (params.isCreate: 신규 등록 여부)\n * @param _context 액션 컨텍스트\n */\nexport function addOptionRowHandler(\n action: ActionWithParams,\n _context: ActionContext\n): void {\n const G7Core = (window as any).G7Core;\n if (!G7Core?.state) {\n logger.warn('[addOptionRow] G7Core.state API is not available');\n return;\n }\n\n const isCreate = action.params?.isCreate === true || action.params?.isCreate === 'true';\n const defaultStockQuantity = isCreate ? 1 : 0;\n\n const state = G7Core.state.getLocal() || {};\n const options: ProductOption[] = [...(state.form?.options ?? [])];\n const optionGroups: OptionGroup[] = state.form?.option_groups ?? [];\n\n // 새 옵션 행 생성\n const newOptionCode = `OPT-${Date.now()}`;\n\n // 각 옵션 그룹에 대해 배열 포맷으로 빈 값 설정\n const newOptionValues = optionGroups.map((group) => ({\n key: group.name, // {ko: \"색상\", en: \"Color\"}\n value: createEmptyLocalizedField(), // {ko: \"\", en: \"\"}\n }));\n\n const newOption: ProductOption = {\n id: null,\n option_code: newOptionCode,\n option_name: createEmptyLocalizedField(),\n option_values: newOptionValues,\n is_default: options.length === 0, // 첫 번째 옵션이면 기본으로 설정\n regular_price: 0,\n sale_price: 0,\n list_price: 0,\n selling_price: 0,\n price_adjustment: 0,\n multi_currency_selling_price: {},\n sku: '',\n stock_quantity: defaultStockQuantity,\n safe_stock_quantity: 0,\n weight: 0,\n volume: 0,\n mileage_value: 0,\n mileage_type: 'percent',\n is_active: true,\n };\n\n const nextOptions = [...options, newOption];\n const totalStock = sumActiveOptionStock(nextOptions);\n\n G7Core.state.setLocal({\n form: { ...state.form, options: nextOptions, stock_quantity: totalStock, has_options: nextOptions.length > 0 },\n hasChanges: true,\n });\n\n logger.log(`[addOptionRow] Added new option row: ${newOptionCode}, product stock_quantity = ${totalStock}`);\n}\n\n/**\n * 상품 판매가 변경 시 모든 옵션의 selling_price를 재계산합니다.\n *\n * 옵션 selling_price = 상품 판매가 + 옵션 price_adjustment\n *\n * sequence 내에서 setState 다음에 실행되므로, 타이밍 이슈를 피하기 위해\n * params.newSellingPrice로 새 상품 판매가를 직접 받습니다.\n *\n * @param action 액션 객체 (params.newSellingPrice 필요)\n * @param _context 액션 컨텍스트\n */\nexport function recalculateOptionPriceAdjustmentsHandler(\n action: ActionWithParams,\n _context: ActionContext\n): void {\n const G7Core = (window as any).G7Core;\n if (!G7Core?.state) {\n logger.warn('[recalculateOptionPriceAdjustments] G7Core.state API is not available');\n return;\n }\n\n const params = action.params || {};\n const state = G7Core.state.getLocal() || {};\n\n // params에서 새 상품 판매가를 받아옴 (타이밍 이슈 해결)\n // params가 없으면 state에서 fallback\n const newProductSellingPrice = params.newSellingPrice !== undefined\n ? parseFloat(String(params.newSellingPrice)) || 0\n : parseFloat(String(state.form?.selling_price)) || 0;\n\n const options: ProductOption[] = [...(state.form?.options ?? [])];\n\n if (options.length === 0) {\n logger.log('[recalculateOptionPriceAdjustments] No options to recalculate');\n return;\n }\n\n // 다통화는 base 판매가에서 환율로 항상 자동 계산되어 읽기전용으로 표시된다\n const currencies = resolveAutoCurrencies();\n\n const updatedOptions = recalcOptionsFromProductPrice(options, newProductSellingPrice, currencies);\n\n G7Core.state.setLocal({\n form: { ...state.form, options: updatedOptions },\n });\n\n logger.log(`[recalculateOptionPriceAdjustments] Recalculated selling_price for ${options.length} options based on product selling_price: ${newProductSellingPrice}`);\n}\n","/**\n * 옵션 관련 핸들러\n *\n * 상품 등록/수정 화면에서 상품 옵션, 추가옵션, 필수목록 관리 기능을 처리합니다.\n */\n\nimport type { ActionContext } from '../types';\nimport { convertCurrencyPrice, sumActiveOptionStock } from './productOptionHandlers';\n\n// Logger 설정 (G7Core 초기화 전에도 동작하도록 폴백 포함)\nconst logger = ((window as any).G7Core?.createLogger?.('Ecom:Option')) ?? {\n log: (...args: unknown[]) => console.log('[Ecom:Option]', ...args),\n warn: (...args: unknown[]) => console.warn('[Ecom:Option]', ...args),\n error: (...args: unknown[]) => console.error('[Ecom:Option]', ...args),\n};\n\n/**\n * 설정에서 지원되는 로케일 목록을 가져옵니다.\n *\n * @returns 지원되는 로케일 배열\n */\nfunction getSupportedLocales(): string[] {\n const G7Core = (window as any).G7Core;\n return G7Core?.config?.('app.supported_locales') ?? ['ko', 'en'];\n}\n\n/**\n * 기본 로케일을 가져옵니다.\n *\n * @returns 기본 로케일 코드\n */\nfunction getDefaultLocale(): string {\n const G7Core = (window as any).G7Core;\n return G7Core?.config?.('app.locale') ?? getSupportedLocales()[0] ?? 'ko';\n}\n\n/**\n * 빈 다국어 필드 객체를 생성합니다.\n *\n * @returns 모든 지원 로케일에 대해 빈 문자열을 가진 객체\n */\nfunction createEmptyLocalizedField(): Record {\n const locales = getSupportedLocales();\n return locales.reduce((acc, locale) => ({ ...acc, [locale]: '' }), {});\n}\n\n/**\n * 빈 추가옵션 선택지(값)를 생성합니다.\n *\n * @param sortOrder 정렬 순서\n * @returns 빈 선택지 객체\n */\nfunction createEmptyAdditionalOptionValue(sortOrder: number): AdditionalOptionValue {\n return {\n id: null,\n name: createEmptyLocalizedField(),\n price_adjustment: 0,\n is_default: sortOrder === 0,\n is_active: true,\n allow_custom_text: false,\n sort_order: sortOrder,\n };\n}\n\n/**\n * 옵션 값 항목 (다국어 지원)\n */\ntype OptionValueMultilingual = Record;\n\ninterface OptionInput {\n name: Record;\n values: string[] | OptionValueMultilingual[];\n}\n\n/**\n * 옵션 값 항목 (배열 형식 - 다국어 지원)\n */\ninterface OptionValueItem {\n key: Record;\n value: Record;\n}\n\ninterface ProductOption {\n id?: number | null;\n option_code: string;\n /** 배열 형식 (신규 다국어) 또는 객체 형식 (레거시) */\n option_values: OptionValueItem[] | Record;\n /** 다국어 객체 (신규) 또는 문자열 (레거시) */\n option_name: Record | string;\n price_adjustment: number;\n stock_quantity: number;\n safe_stock_quantity: number;\n is_default: boolean;\n is_active: boolean;\n sku: string;\n multi_currency_prices?: Record;\n [key: string]: any;\n}\n\ninterface OptionGroup {\n name: Record;\n /** 다국어 배열 (신규) 또는 문자열 배열 (레거시) */\n values: OptionValueMultilingual[] | string[];\n}\n\n/**\n * 추가옵션 선택지 (값) 항목\n */\ninterface AdditionalOptionValue {\n id?: number | null;\n name: Record;\n /** 추가금 (KRW 기준, 0 이상) */\n price_adjustment: number;\n is_default: boolean;\n is_active: boolean;\n /** 직접입력 허용 — 유저가 이 선택지 선택 시 자유 텍스트 입력 필수 */\n allow_custom_text: boolean;\n sort_order: number;\n [key: string]: any;\n}\n\ninterface AdditionalOption {\n id?: number | null;\n name: Record;\n is_required: boolean;\n sort_order: number;\n values?: AdditionalOptionValue[];\n}\n\ninterface RequiredItem {\n id?: number | null;\n sort_order: number;\n option_name: Record;\n description: Record;\n}\n\ninterface Currency {\n code: string;\n exchange_rate: number;\n}\n\ninterface ActionWithParams {\n handler: string;\n params?: Record;\n [key: string]: any;\n}\n\n/**\n * 옵션 값에서 특정 로케일의 문자열을 추출합니다.\n *\n * @param value 옵션 값 (문자열 또는 다국어 객체)\n * @param locale 로케일 코드\n * @returns 해당 로케일의 문자열\n */\nfunction getValueForLocale(value: string | OptionValueMultilingual, locale: string): string {\n if (typeof value === 'string') return value;\n return value[locale] ?? value['ko'] ?? Object.values(value)[0] ?? '';\n}\n\n/**\n * 옵션 값이 다국어 객체인지 확인합니다.\n *\n * @param value 옵션 값\n * @returns 다국어 객체 여부\n */\nfunction isMultilingualValue(value: any): value is OptionValueMultilingual {\n return value && typeof value === 'object' && !Array.isArray(value);\n}\n\n/**\n * values를 안전하게 배열로 변환합니다.\n * 배열이면 그대로 반환, 객체면 Object.values()로 변환합니다.\n *\n * @param values 옵션 값 (배열 또는 인덱스 객체)\n * @returns 배열 형태의 values\n */\nfunction ensureValuesArray(values: any): (string | OptionValueMultilingual)[] {\n if (Array.isArray(values)) {\n return values;\n }\n if (values && typeof values === 'object') {\n // { \"0\": {...}, \"1\": {...} } 형태의 객체를 배열로 변환\n return Object.values(values);\n }\n return [];\n}\n\n/**\n * 카테시안 곱을 계산합니다. (다국어 값 지원)\n *\n * @param arrays 입력 배열들 (문자열 또는 다국어 객체)\n * @returns 모든 조합의 배열\n */\nfunction cartesianProduct(arrays: T[][]): T[][] {\n return arrays.reduce(\n (acc: T[][], curr: T[]) => {\n const result: T[][] = [];\n acc.forEach((a) => {\n curr.forEach((c) => {\n result.push([...a, c]);\n });\n });\n return result;\n },\n [[]]\n );\n}\n\n/**\n * 다국어 옵션명을 생성합니다.\n *\n * @param combo 옵션 값 조합 배열\n * @returns 다국어 옵션명 객체\n */\nfunction generateMultilingualOptionName(combo: (string | OptionValueMultilingual)[]): Record {\n const locales = getSupportedLocales();\n const result: Record = {};\n\n for (const locale of locales) {\n const parts = combo.map(v => getValueForLocale(v, locale));\n result[locale] = parts.join('/');\n }\n\n return result;\n}\n\n/**\n * 다국어 option_values 배열을 생성합니다.\n *\n * @param combo 옵션 값 조합 배열\n * @param inputs 옵션 입력 배열 (name 포함)\n * @returns option_values 배열 형식\n */\nfunction generateMultilingualOptionValues(\n combo: (string | OptionValueMultilingual)[],\n inputs: OptionInput[]\n): OptionValueItem[] {\n return inputs.map((inp, i) => ({\n key: typeof inp.name === 'string'\n ? { ...createEmptyLocalizedField(), [getDefaultLocale()]: inp.name }\n : inp.name,\n value: isMultilingualValue(combo[i])\n ? combo[i] as OptionValueMultilingual\n : { ...createEmptyLocalizedField(), [getDefaultLocale()]: combo[i] as string },\n }));\n}\n\n/**\n * 옵션 입력 행을 추가합니다. (최대 3개)\n *\n * @param _action 액션 객체\n * @param _context 액션 컨텍스트\n */\nexport function addOptionInputHandler(\n _action: ActionWithParams,\n _context: ActionContext\n): void {\n const G7Core = (window as any).G7Core;\n if (!G7Core?.state) {\n logger.warn('[addOptionInput] G7Core.state API is not available');\n return;\n }\n\n const state = G7Core.state.getLocal() || {};\n const inputs = state.ui?.optionInputs ?? [];\n\n if (inputs.length >= 3) {\n G7Core.toast?.warning?.(\n G7Core.t?.('sirsoft-ecommerce.admin.product.options.messages.input_max_3')\n ?? 'You can add up to 3 option inputs.'\n );\n return;\n }\n\n G7Core.state.setLocal({\n ui: {\n ...state.ui,\n optionInputs: [...inputs, { name: createEmptyLocalizedField(), values: [] }],\n },\n });\n\n logger.log(`[addOptionInput] Added option input. Total: ${inputs.length + 1}`);\n}\n\n/**\n * 옵션 입력 행을 제거합니다.\n *\n * @param action 액션 객체 (params.index 필요)\n * @param _context 액션 컨텍스트\n */\nexport function removeOptionInputHandler(\n action: ActionWithParams,\n _context: ActionContext\n): void {\n const params = action.params || {};\n const index = params.index as number;\n\n if (index === undefined) {\n logger.warn('[removeOptionInput] Missing index param');\n return;\n }\n\n const G7Core = (window as any).G7Core;\n if (!G7Core?.state) {\n logger.warn('[removeOptionInput] G7Core.state API is not available');\n return;\n }\n\n const state = G7Core.state.getLocal() || {};\n const inputs = [...(state.ui?.optionInputs ?? [])];\n inputs.splice(index, 1);\n\n G7Core.state.setLocal({\n ui: { ...state.ui, optionInputs: inputs },\n });\n\n logger.log(`[removeOptionInput] Removed option input at index ${index}`);\n}\n\n/**\n * 옵션 입력 값을 업데이트합니다.\n *\n * @param action 액션 객체 (params.index, params.field, params.value 필요)\n * @param _context 액션 컨텍스트\n */\nexport function updateOptionInputHandler(\n action: ActionWithParams,\n _context: ActionContext\n): void {\n const params = action.params || {};\n const index = params.index as number;\n const field = params.field as string;\n const value = params.value;\n\n if (index === undefined || !field) {\n logger.warn('[updateOptionInput] Missing required params');\n return;\n }\n\n const G7Core = (window as any).G7Core;\n if (!G7Core?.state) {\n logger.warn('[updateOptionInput] G7Core.state API is not available');\n return;\n }\n\n // 디버깅: field 값과 타입 로깅\n logger.log(`[updateOptionInput] Called with index=${index}, field=\"${field}\" (type: ${typeof field}), value=`, value);\n\n const state = G7Core.state.getLocal() || {};\n const inputs = [...(state.ui?.optionInputs ?? [])];\n\n // 디버깅: 현재 inputs 상태 로깅\n logger.log(`[updateOptionInput] Current inputs[${index}]:`, inputs[index]);\n\n // Race condition 방어: 해당 인덱스에 항목이 없으면 기본 구조로 초기화\n if (!inputs[index]) {\n logger.log(`[updateOptionInput] inputs[${index}] not found, initializing with empty structure`);\n inputs[index] = { name: createEmptyLocalizedField(), values: [] };\n }\n\n // name 필드는 다국어 객체 구조를 유지\n if (field === 'name') {\n const defaultLocale = getDefaultLocale();\n const existingName = inputs[index].name;\n logger.log(`[updateOptionInput] Processing 'name' field - defaultLocale: ${defaultLocale}, existingName:`, existingName, `type: ${typeof existingName}`);\n\n let newName: Record;\n\n // value가 이미 다국어 객체인 경우 (MultilingualInput에서 전달)\n if (value && typeof value === 'object' && !Array.isArray(value)) {\n // MultilingualInput: 전체 다국어 객체를 그대로 사용\n newName = value as Record;\n logger.log(`[updateOptionInput] Using multilingual value directly:`, newName);\n } else {\n // 기본 Input (폴백): 현재 로케일에만 값 설정\n const baseName = (existingName && typeof existingName === 'object' && !Array.isArray(existingName))\n ? existingName\n : createEmptyLocalizedField();\n\n newName = {\n ...baseName,\n [defaultLocale]: value,\n };\n logger.log(`[updateOptionInput] Setting single locale value:`, newName);\n }\n\n inputs[index] = {\n ...inputs[index],\n name: newName,\n };\n } else {\n logger.log(`[updateOptionInput] Processing non-name field '${field}'`);\n inputs[index] = { ...inputs[index], [field]: value };\n }\n\n logger.log(`[updateOptionInput] Final inputs[${index}]:`, inputs[index]);\n\n G7Core.state.setLocal({\n ui: { ...state.ui, optionInputs: inputs },\n });\n}\n\n/**\n * 카테시안 곱으로 옵션 조합을 생성합니다.\n *\n * @param _action 액션 객체\n * @param context 액션 컨텍스트 (datasources.currencies 사용)\n */\nexport function generateOptionsHandler(\n _action: ActionWithParams,\n context: ActionContext\n): void {\n const G7Core = (window as any).G7Core;\n if (!G7Core?.state) {\n logger.warn('[generateOptions] G7Core.state API is not available');\n return;\n }\n\n const state = G7Core.state.getLocal() || {};\n const globalState = G7Core.state.get() || {};\n const inputs: OptionInput[] = state.ui?.optionInputs ?? [];\n\n // 유효성 검사 (동적 로케일 사용, 문자열 호환성 지원)\n const defaultLocale = getDefaultLocale();\n\n // 옵션명 추출 헬퍼 함수 (객체 또는 문자열 모두 지원)\n const getOptionName = (inp: OptionInput): string => {\n if (!inp.name) return '';\n if (typeof inp.name === 'string') return inp.name;\n return inp.name[defaultLocale] ?? '';\n };\n\n // values를 배열로 정규화 (객체 형태일 경우 변환)\n const normalizedInputs = inputs.map(inp => ({\n ...inp,\n values: ensureValuesArray(inp.values),\n }));\n\n const validInputs = normalizedInputs.filter(\n (inp) => getOptionName(inp) && inp.values && inp.values.length > 0\n );\n\n logger.log(`[generateOptions] Validating inputs - defaultLocale: ${defaultLocale}, normalizedInputs:`, normalizedInputs.map(inp => ({\n name: inp.name,\n nameType: typeof inp.name,\n extractedName: getOptionName(inp as OptionInput),\n values: inp.values,\n valuesIsArray: Array.isArray(inp.values),\n })));\n logger.log(`[generateOptions] Valid inputs count: ${validInputs.length}`);\n\n if (validInputs.length === 0) {\n G7Core.toast?.error?.(\n G7Core.t?.('sirsoft-ecommerce.admin.product.options.messages.name_value_required')\n ?? 'Please enter option name and values.'\n );\n return;\n }\n\n // 유효성 통과 후: 기존 옵션이 존재하고 skipConfirm이 아닌 경우 → 확인 모달 표시\n const existingOptions: ProductOption[] = state.form?.options ?? [];\n const skipConfirm = _action.params?.skipConfirm === true;\n if (existingOptions.length > 0 && !skipConfirm) {\n G7Core.modal?.open?.('modal_confirm_regenerate');\n return;\n }\n\n // 신규 등록 모드(params.isCreate)에서는 옵션 행 재고 기본값을 1로 채워 입력 수고를 던다.\n // 수정 모드에서는 기존 동작(0)을 유지한다. 등록/수정 구분은 레이아웃이 route.itemCode 로\n // 판정해 params.isCreate 로 전달한다.\n const isCreate = _action.params?.isCreate === true || _action.params?.isCreate === 'true';\n const defaultStockQuantity = isCreate ? 1 : 0;\n\n // 상품의 정가/판매가 읽기\n const productListPrice = parseFloat(String(state.form?.list_price)) || 0;\n const productSellingPrice = parseFloat(String(state.form?.selling_price)) || 0;\n const productMultiCurrencySP = state.form?.multi_currency_selling_price;\n\n // 다중통화 환율 정보 (기본 통화 제외, 환율이 설정된 통화만)\n const fullCurrencies: any[] = (\n globalState.modules?.['sirsoft-ecommerce']?.language_currency?.currencies || []\n ).filter((c: any) => !c.is_default && c.exchange_rate);\n\n try {\n logger.log('[generateOptions] Step 1: Creating optionGroups...');\n\n // 옵션 그룹 저장 (name과 values를 다국어 형식으로 저장)\n const optionGroups: OptionGroup[] = validInputs.map((inp, i) => {\n logger.log(`[generateOptions] Processing input[${i}]:`, {\n name: inp.name,\n values: inp.values,\n valuesType: typeof inp.values,\n valuesIsArray: Array.isArray(inp.values),\n });\n return {\n name: typeof inp.name === 'string'\n ? { ...createEmptyLocalizedField(), [defaultLocale]: inp.name }\n : inp.name,\n // values가 다국어 배열이면 그대로, 문자열 배열이면 다국어 객체로 변환\n values: inp.values.map(v =>\n isMultilingualValue(v) ? v : { ...createEmptyLocalizedField(), [defaultLocale]: v as string }\n ),\n };\n });\n\n // 카테시안 곱으로 모든 조합 생성 (다국어 값 지원)\n const combinations = cartesianProduct(validInputs.map((inp) => inp.values));\n\n // 옵션 객체 생성 (다국어 option_name, option_values 지원)\n const options: ProductOption[] = combinations.map((combo, idx) => {\n // 다국어 option_values 배열 형식으로 생성\n const optionValues = generateMultilingualOptionValues(combo, validInputs);\n\n // 다국어 option_name 생성\n const optionName = generateMultilingualOptionName(combo);\n const optionCode = `OPT-${String(idx + 1).padStart(3, '0')}`;\n\n // 다중 통화 판매가 계산\n let multiCurrencySellingPrice: Record = {};\n if (productMultiCurrencySP && typeof productMultiCurrencySP === 'object' && Object.keys(productMultiCurrencySP).length > 0) {\n // 상품에 이미 다중통화 판매가가 설정되어 있으면 복사\n multiCurrencySellingPrice = JSON.parse(JSON.stringify(productMultiCurrencySP));\n } else if (productSellingPrice > 0 && fullCurrencies.length > 0) {\n // 상품 판매가 기반으로 환율 자동 계산\n fullCurrencies.forEach((currency: any) => {\n multiCurrencySellingPrice[currency.code] = convertCurrencyPrice(productSellingPrice, currency);\n });\n }\n\n return {\n id: null,\n option_code: optionCode,\n option_values: optionValues,\n option_name: optionName,\n list_price: productListPrice,\n selling_price: productSellingPrice,\n price_adjustment: 0,\n stock_quantity: defaultStockQuantity,\n safe_stock_quantity: 0,\n is_default: idx === 0,\n is_active: true,\n sku: '',\n multi_currency_selling_price: multiCurrencySellingPrice,\n };\n });\n\n // 기존 옵션과 병합 (기존 옵션 값 유지)\n // option_name이 다국어 객체 또는 문자열일 수 있음\n const existingOptions: ProductOption[] = state.form?.options ?? [];\n const getOptionNameKey = (opt: ProductOption): string => {\n if (!opt.option_name) return '';\n if (typeof opt.option_name === 'string') return opt.option_name;\n return opt.option_name[defaultLocale] ?? opt.option_name['ko'] ?? '';\n };\n const mergedOptions = options.map((newOpt) => {\n const newKey = getOptionNameKey(newOpt);\n const existing = existingOptions.find((e) => getOptionNameKey(e) === newKey);\n return existing ? { ...newOpt, ...existing, id: existing.id } : newOpt;\n });\n\n // 옵션 생성 즉시 상품 재고를 활성 옵션 재고 합계로 동기화\n // (옵션당 기본 재고 × 생성 개수가 원 상품 재고에 바로 반영되어야 함)\n const totalStock = sumActiveOptionStock(mergedOptions);\n\n G7Core.state.setLocal({\n form: {\n ...state.form,\n option_groups: optionGroups,\n options: mergedOptions,\n has_options: mergedOptions.length > 0,\n stock_quantity: totalStock,\n },\n hasChanges: true,\n });\n\n G7Core.toast?.success?.(\n G7Core.t?.('sirsoft-ecommerce.admin.product.options.messages.generated', { count: mergedOptions.length })\n ?? `${mergedOptions.length} options have been generated.`\n );\n logger.log(`[generateOptions] Generated ${mergedOptions.length} options`);\n } catch (error) {\n logger.error('[generateOptions] Error occurred:', error);\n logger.error('[generateOptions] Error message:', (error as Error).message);\n logger.error('[generateOptions] Error stack:', (error as Error).stack);\n throw error; // 에러를 다시 던져서 ActionDispatcher가 처리하도록 함\n }\n}\n\n/**\n * 옵션을 삭제합니다.\n *\n * @param action 액션 객체 (params.rowIndex 필요)\n * @param _context 액션 컨텍스트\n */\nexport function deleteOptionHandler(\n action: ActionWithParams,\n _context: ActionContext\n): void {\n const params = action.params || {};\n const rowIndex = params.rowIndex as number;\n\n if (rowIndex === undefined) {\n logger.warn('[deleteOption] Missing rowIndex param');\n return;\n }\n\n const G7Core = (window as any).G7Core;\n if (!G7Core?.state) {\n logger.warn('[deleteOption] G7Core.state API is not available');\n return;\n }\n\n const state = G7Core.state.getLocal() || {};\n const options = [...(state.form?.options ?? [])];\n options.splice(rowIndex, 1);\n\n // 옵션 삭제 후 상품 재고를 남은 활성 옵션 재고 합계로 재동기화\n // (옵션이 모두 사라지면 has_options 도 해제)\n const totalStock = sumActiveOptionStock(options);\n const hasOptions = options.length > 0;\n\n G7Core.state.setLocal({\n form: {\n ...state.form,\n options,\n has_options: hasOptions,\n ...(hasOptions ? { stock_quantity: totalStock } : {}),\n },\n hasChanges: true,\n });\n\n logger.log(`[deleteOption] Deleted option at index ${rowIndex}, product stock_quantity = ${totalStock}`);\n}\n\n/**\n * 옵션추가도구 설정을 적용합니다.\n *\n * @param action 액션 객체 (params.optionName, params.applyToAll, params.inheritPrice 필요)\n * @param _context 액션 컨텍스트\n */\nexport function applyOptionAddToolHandler(\n action: ActionWithParams,\n _context: ActionContext\n): void {\n const params = action.params || {};\n const optionName = params.optionName as string;\n const applyToAll = params.applyToAll as boolean;\n const inheritPrice = params.inheritPrice as boolean;\n\n const G7Core = (window as any).G7Core;\n if (!G7Core?.state) {\n logger.warn('[applyOptionAddTool] G7Core.state API is not available');\n return;\n }\n\n if (!optionName?.trim()) {\n G7Core.toast?.warning?.(\n G7Core.t?.('sirsoft-ecommerce.admin.product.options.messages.name_required')\n ?? 'Please enter option name.'\n );\n return;\n }\n\n const state = G7Core.state.getLocal() || {};\n const options = [...(state.form?.options ?? [])];\n\n const updatedOptions = options.map((opt) => {\n const newOpt = { ...opt };\n\n if (applyToAll) {\n newOpt.option_values = {\n ...opt.option_values,\n [optionName]: '',\n };\n }\n\n if (inheritPrice) {\n newOpt.price_adjustment = opt.price_adjustment ?? 0;\n }\n\n return newOpt;\n });\n\n G7Core.state.setLocal({\n form: { ...state.form, options: updatedOptions },\n ui: {\n ...state.ui,\n optionAddToolName: '',\n optionAddToolApplyToAll: false,\n optionAddToolInheritPrice: false,\n },\n hasChanges: true,\n });\n\n G7Core.toast?.success?.(\n G7Core.t?.('sirsoft-ecommerce.admin.product.options.messages.add_tool_applied')\n ?? 'Option add tool has been applied.'\n );\n}\n\n/**\n * 필수 목록 항목을 추가합니다.\n *\n * @param _action 액션 객체\n * @param _context 액션 컨텍스트\n */\nexport function addRequiredItemHandler(\n _action: ActionWithParams,\n _context: ActionContext\n): void {\n const G7Core = (window as any).G7Core;\n if (!G7Core?.state) {\n logger.warn('[addRequiredItem] G7Core.state API is not available');\n return;\n }\n\n const state = G7Core.state.getLocal() || {};\n const requiredItems: RequiredItem[] = state.form?.required_items ?? [];\n\n const newItem: RequiredItem = {\n id: null,\n sort_order: requiredItems.length,\n option_name: { ko: '', en: '' },\n description: { ko: '', en: '' },\n };\n\n G7Core.state.setLocal({\n form: {\n ...state.form,\n required_items: [...requiredItems, newItem],\n },\n hasChanges: true,\n });\n\n logger.log(`[addRequiredItem] Added required item. Total: ${requiredItems.length + 1}`);\n}\n\n/**\n * 필수 목록 항목을 수정합니다.\n *\n * @param action 액션 객체 (params.index, params.field, params.locale, params.value 필요)\n * @param _context 액션 컨텍스트\n */\nexport function updateRequiredItemHandler(\n action: ActionWithParams,\n _context: ActionContext\n): void {\n const params = action.params || {};\n const index = params.index as number;\n const field = params.field as 'option_name' | 'description';\n const locale = params.locale as string;\n const value = params.value as string;\n\n if (index === undefined || !field || !locale) {\n logger.warn('[updateRequiredItem] Missing required params');\n return;\n }\n\n const G7Core = (window as any).G7Core;\n if (!G7Core?.state) {\n logger.warn('[updateRequiredItem] G7Core.state API is not available');\n return;\n }\n\n const state = G7Core.state.getLocal() || {};\n const requiredItems = [...(state.form?.required_items ?? [])];\n\n requiredItems[index] = {\n ...requiredItems[index],\n [field]: {\n ...requiredItems[index][field],\n [locale]: value,\n },\n };\n\n G7Core.state.setLocal({\n form: { ...state.form, required_items: requiredItems },\n hasChanges: true,\n });\n}\n\n/**\n * 필수 목록 항목을 삭제합니다.\n *\n * @param action 액션 객체 (params.index 필요)\n * @param _context 액션 컨텍스트\n */\nexport function removeRequiredItemHandler(\n action: ActionWithParams,\n _context: ActionContext\n): void {\n const params = action.params || {};\n const index = params.index as number;\n\n if (index === undefined) {\n logger.warn('[removeRequiredItem] Missing index param');\n return;\n }\n\n const G7Core = (window as any).G7Core;\n if (!G7Core?.state) {\n logger.warn('[removeRequiredItem] G7Core.state API is not available');\n return;\n }\n\n const state = G7Core.state.getLocal() || {};\n const requiredItems = [...(state.form?.required_items ?? [])];\n requiredItems.splice(index, 1);\n\n // sort_order 재정렬\n requiredItems.forEach((item, i) => {\n item.sort_order = i;\n });\n\n G7Core.state.setLocal({\n form: { ...state.form, required_items: requiredItems },\n hasChanges: true,\n });\n\n logger.log(`[removeRequiredItem] Removed required item at index ${index}`);\n}\n\n/**\n * 필수 목록 순서를 변경합니다.\n *\n * @param action 액션 객체 (params.oldIndex, params.newIndex 필요)\n * @param _context 액션 컨텍스트\n */\nexport function reorderRequiredItemsHandler(\n action: ActionWithParams,\n _context: ActionContext\n): void {\n const params = action.params || {};\n const oldIndex = params.oldIndex as number;\n const newIndex = params.newIndex as number;\n\n if (oldIndex === undefined || newIndex === undefined) {\n logger.warn('[reorderRequiredItems] Missing oldIndex or newIndex param');\n return;\n }\n\n const G7Core = (window as any).G7Core;\n if (!G7Core?.state) {\n logger.warn('[reorderRequiredItems] G7Core.state API is not available');\n return;\n }\n\n const state = G7Core.state.getLocal() || {};\n const requiredItems = [...(state.form?.required_items ?? [])];\n\n const [moved] = requiredItems.splice(oldIndex, 1);\n requiredItems.splice(newIndex, 0, moved);\n\n // sort_order 재설정\n requiredItems.forEach((item, i) => {\n item.sort_order = i;\n });\n\n G7Core.state.setLocal({\n form: { ...state.form, required_items: requiredItems },\n hasChanges: true,\n });\n\n logger.log(`[reorderRequiredItems] Moved item from ${oldIndex} to ${newIndex}`);\n}\n\n/**\n * 추가옵션을 추가합니다. (최대 5개)\n *\n * @param _action 액션 객체\n * @param _context 액션 컨텍스트\n */\nexport function addAdditionalOptionHandler(\n _action: ActionWithParams,\n _context: ActionContext\n): void {\n const G7Core = (window as any).G7Core;\n if (!G7Core?.state) {\n logger.warn('[addAdditionalOption] G7Core.state API is not available');\n return;\n }\n\n const state = G7Core.state.getLocal() || {};\n const options: AdditionalOption[] = state.form?.additional_options ?? [];\n\n if (options.length >= 5) {\n G7Core.toast?.warning?.(\n G7Core.t?.('sirsoft-ecommerce.admin.product.options.messages.additional_max_5')\n ?? 'You can add up to 5 additional options.'\n );\n return;\n }\n\n // 새 추가옵션에 고유 임시 ID 부여 (sortable에서 아이템 구분에 필요)\n const tempId = `temp_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`;\n\n G7Core.state.setLocal({\n form: {\n ...state.form,\n additional_options: [\n ...options,\n {\n id: tempId,\n name: createEmptyLocalizedField(),\n is_required: false,\n sort_order: options.length,\n // 빈 그룹(선택지 0개) 저장 차단(D11) — 최소 1개 선택지로 시작\n values: [createEmptyAdditionalOptionValue(0)],\n },\n ],\n },\n hasChanges: true,\n });\n\n logger.log(`[addAdditionalOption] Added additional option. Total: ${options.length + 1}`);\n}\n\n/**\n * 추가옵션을 수정합니다.\n *\n * MultilingualInput 컴포넌트와 기본 Input 모두 지원합니다.\n * - MultilingualInput: value가 다국어 객체 { ko: '...', en: '...' }\n * - 기본 Input: value가 문자열\n *\n * @param action 액션 객체 (params.index, params.field, params.value 필요)\n * @param _context 액션 컨텍스트\n */\nexport function updateAdditionalOptionHandler(\n action: ActionWithParams,\n _context: ActionContext\n): void {\n const params = action.params || {};\n const index = params.index as number;\n const field = params.field as string;\n const value = params.value;\n\n if (index === undefined || !field) {\n logger.warn('[updateAdditionalOption] Missing required params');\n return;\n }\n\n const G7Core = (window as any).G7Core;\n if (!G7Core?.state) {\n logger.warn('[updateAdditionalOption] G7Core.state API is not available');\n return;\n }\n\n const state = G7Core.state.getLocal() || {};\n const options = [...(state.form?.additional_options ?? [])];\n\n if (!options[index]) {\n logger.warn('[updateAdditionalOption] Option not found at index', index);\n return;\n }\n\n // name 필드는 다국어 객체 구조를 유지\n if (field === 'name') {\n // MultilingualInput에서 전달된 경우: value가 이미 다국어 객체\n // 기본 Input에서 전달된 경우: value가 문자열\n if (value && typeof value === 'object' && !Array.isArray(value)) {\n // MultilingualInput: 전체 다국어 객체를 그대로 저장\n options[index] = {\n ...options[index],\n name: value,\n };\n } else {\n // 기본 Input (폴백): 현재 로케일에만 값 설정\n const defaultLocale = getDefaultLocale();\n const existingName = options[index].name;\n\n const baseName = (existingName && typeof existingName === 'object' && !Array.isArray(existingName))\n ? existingName\n : createEmptyLocalizedField();\n\n options[index] = {\n ...options[index],\n name: {\n ...baseName,\n [defaultLocale]: value,\n },\n };\n }\n } else {\n // is_required, sort_order 등은 직접 할당\n options[index] = { ...options[index], [field]: value };\n }\n\n G7Core.state.setLocal({\n form: { ...state.form, additional_options: options },\n hasChanges: true,\n });\n\n logger.log(`[updateAdditionalOption] Updated options[${index}].${field}`);\n}\n\n/**\n * 추가옵션을 삭제합니다.\n *\n * @param action 액션 객체 (params.index 필요)\n * @param _context 액션 컨텍스트\n */\nexport function removeAdditionalOptionHandler(\n action: ActionWithParams,\n _context: ActionContext\n): void {\n const params = action.params || {};\n const index = params.index as number;\n\n if (index === undefined) {\n logger.warn('[removeAdditionalOption] Missing index param');\n return;\n }\n\n const G7Core = (window as any).G7Core;\n if (!G7Core?.state) {\n logger.warn('[removeAdditionalOption] G7Core.state API is not available');\n return;\n }\n\n const state = G7Core.state.getLocal() || {};\n const options = [...(state.form?.additional_options ?? [])];\n options.splice(index, 1);\n\n // sort_order 재정렬\n options.forEach((opt, i) => {\n opt.sort_order = i;\n });\n\n G7Core.state.setLocal({\n form: { ...state.form, additional_options: options },\n hasChanges: true,\n });\n\n logger.log(`[removeAdditionalOption] Removed additional option at index ${index}`);\n}\n\n/**\n * 추가옵션 순서를 변경합니다.\n *\n * 드래그 앤 드롭으로 순서를 변경할 때 사용됩니다.\n *\n * @param action 액션 객체 (params.oldIndex, params.newIndex 필요)\n * @param _context 액션 컨텍스트\n */\nexport function reorderAdditionalOptionsHandler(\n action: ActionWithParams,\n _context: ActionContext\n): void {\n const params = action.params || {};\n const oldIndex = parseInt(String(params.oldIndex), 10);\n const newIndex = parseInt(String(params.newIndex), 10);\n\n if (isNaN(oldIndex) || isNaN(newIndex) || oldIndex === newIndex) {\n return;\n }\n\n const G7Core = (window as any).G7Core;\n if (!G7Core?.state) {\n logger.warn('[reorderAdditionalOptions] G7Core.state API is not available');\n return;\n }\n\n const state = G7Core.state.getLocal() || {};\n const options = [...(state.form?.additional_options ?? [])];\n\n if (oldIndex < 0 || oldIndex >= options.length || newIndex < 0 || newIndex >= options.length) {\n return;\n }\n\n // 배열 재정렬\n const [moved] = options.splice(oldIndex, 1);\n options.splice(newIndex, 0, moved);\n\n // sort_order 재설정\n options.forEach((opt, i) => {\n opt.sort_order = i;\n });\n\n G7Core.state.setLocal({\n form: { ...state.form, additional_options: options },\n ui: { ...state.ui, dragIndex: null },\n hasChanges: true,\n });\n\n logger.log(`[reorderAdditionalOptions] Moved item from ${oldIndex} to ${newIndex}`);\n}\n\n/**\n * 추가옵션을 모두 비웁니다 (사용 → 미사용 전환 시 확인 모달의 확정 버튼).\n *\n * 레이아웃 인라인 setState 의 dot-path 갱신(`form.additional_options: []`)은 form 객체\n * 참조를 교체하지 않아 form 을 watch 하는 토글 className 표현식이 리렌더되지 않는다\n * (sortable source 는 배열을 직접 watch 하므로 갱신됨 → row 만 사라지고 토글은 \"사용\" 잔존).\n * 다른 추가옵션 핸들러(add/update/reorder)와 동일하게 form 객체를 통째 교체하여\n * 전체 파생 표현식의 일관 리렌더를 보장한다.\n *\n * @param _action 액션 객체 (파라미터 없음)\n * @param _context 액션 컨텍스트\n * @return void\n */\nexport function clearAdditionalOptionsHandler(\n _action: ActionWithParams,\n _context: ActionContext\n): void {\n const G7Core = (window as any).G7Core;\n if (!G7Core?.state) {\n logger.warn('[clearAdditionalOptions] G7Core.state API is not available');\n return;\n }\n\n const state = G7Core.state.getLocal() || {};\n\n G7Core.state.setLocal({\n form: { ...state.form, additional_options: [] },\n hasChanges: true,\n });\n\n logger.log('[clearAdditionalOptions] Cleared all additional options.');\n}\n\n/**\n * 추가옵션 그룹의 values(선택지) 배열을 안전하게 반환합니다.\n *\n * @param group 추가옵션 그룹\n * @returns values 배열 (없으면 빈 배열)\n */\nfunction ensureGroupValues(group: AdditionalOption | undefined): AdditionalOptionValue[] {\n if (!group) return [];\n const values = group.values;\n if (Array.isArray(values)) return values;\n if (values && typeof values === 'object') return Object.values(values);\n return [];\n}\n\n/**\n * 추가옵션 그룹에 선택지(값)를 추가합니다. (그룹당 최대 20개)\n *\n * @param action 액션 객체 (params.groupIndex 필요)\n * @param _context 액션 컨텍스트\n */\nexport function addAdditionalOptionValueHandler(\n action: ActionWithParams,\n _context: ActionContext\n): void {\n const params = action.params || {};\n const groupIndex = typeof params.groupIndex === 'string' ? parseInt(params.groupIndex, 10) : params.groupIndex as number;\n\n if (groupIndex === undefined || groupIndex === null || isNaN(groupIndex)) {\n logger.warn('[addAdditionalOptionValue] Missing groupIndex param');\n return;\n }\n\n const G7Core = (window as any).G7Core;\n if (!G7Core?.state) {\n logger.warn('[addAdditionalOptionValue] G7Core.state API is not available');\n return;\n }\n\n const state = G7Core.state.getLocal() || {};\n const options: AdditionalOption[] = [...(state.form?.additional_options ?? [])];\n\n if (!options[groupIndex]) {\n logger.warn('[addAdditionalOptionValue] Group not found at index', groupIndex);\n return;\n }\n\n const values = [...ensureGroupValues(options[groupIndex])];\n\n if (values.length >= 20) {\n G7Core.toast?.warning?.(\n G7Core.t?.('sirsoft-ecommerce.admin.product.options.messages.additional_value_max_20')\n ?? 'You can add up to 20 choices per group.'\n );\n return;\n }\n\n values.push(createEmptyAdditionalOptionValue(values.length));\n options[groupIndex] = { ...options[groupIndex], values };\n\n G7Core.state.setLocal({\n form: { ...state.form, additional_options: options },\n hasChanges: true,\n });\n\n logger.log(`[addAdditionalOptionValue] Added value to group ${groupIndex}. Total: ${values.length}`);\n}\n\n/**\n * 추가옵션 선택지(값)를 수정합니다.\n *\n * - name: 다국어 객체 유지 (MultilingualInput / 기본 Input 모두 지원)\n * - price_adjustment: 숫자 변환 + 음수 차단(D16) + 다중통화 환산 표시값 재계산\n * - is_default: 라디오(그룹당 1개) — 같은 그룹의 다른 선택지는 false 로 해제\n * - is_active: boolean\n *\n * @param action 액션 객체 (params.groupIndex, params.valueIndex, params.field, params.value 필요)\n * @param _context 액션 컨텍스트\n */\nexport function updateAdditionalOptionValueHandler(\n action: ActionWithParams,\n _context: ActionContext\n): void {\n const params = action.params || {};\n const groupIndex = typeof params.groupIndex === 'string' ? parseInt(params.groupIndex, 10) : params.groupIndex as number;\n const valueIndex = typeof params.valueIndex === 'string' ? parseInt(params.valueIndex, 10) : params.valueIndex as number;\n const field = params.field as string;\n const value = params.value;\n\n if (groupIndex === undefined || valueIndex === undefined || !field) {\n logger.warn('[updateAdditionalOptionValue] Missing required params');\n return;\n }\n\n const G7Core = (window as any).G7Core;\n if (!G7Core?.state) {\n logger.warn('[updateAdditionalOptionValue] G7Core.state API is not available');\n return;\n }\n\n const state = G7Core.state.getLocal() || {};\n const options: AdditionalOption[] = [...(state.form?.additional_options ?? [])];\n\n if (!options[groupIndex]) {\n logger.warn('[updateAdditionalOptionValue] Group not found at index', groupIndex);\n return;\n }\n\n const values = [...ensureGroupValues(options[groupIndex])];\n\n if (!values[valueIndex]) {\n logger.warn('[updateAdditionalOptionValue] Value not found at index', valueIndex);\n return;\n }\n\n if (field === 'name') {\n if (value && typeof value === 'object' && !Array.isArray(value)) {\n values[valueIndex] = { ...values[valueIndex], name: value };\n } else {\n const defaultLocale = getDefaultLocale();\n const existingName = values[valueIndex].name;\n const baseName = (existingName && typeof existingName === 'object' && !Array.isArray(existingName))\n ? existingName\n : createEmptyLocalizedField();\n values[valueIndex] = {\n ...values[valueIndex],\n name: { ...baseName, [defaultLocale]: value },\n };\n }\n } else if (field === 'price_adjustment') {\n // 음수 차단(D16) — 0 미만은 0 으로 보정\n let numeric = parseFloat(String(value));\n if (isNaN(numeric) || numeric < 0) {\n numeric = 0;\n }\n // 다통화 추가금은 레이아웃에서 price_adjustment × 환율 인라인 환산(읽기전용)하므로\n // 별도 mc 필드 저장 불필요. price_adjustment 만 갱신하면 표시가 반응형으로 따라간다.\n values[valueIndex] = {\n ...values[valueIndex],\n price_adjustment: numeric,\n };\n } else if (field === 'is_default') {\n // 라디오: 그룹당 1개만 기본 — 선택된 항목만 true, 나머지 false\n const checked = value === true || value === 'true';\n if (checked) {\n values.forEach((v, i) => { v.is_default = i === valueIndex; });\n } else {\n values[valueIndex].is_default = false;\n }\n } else if (field === 'is_active' || field === 'allow_custom_text') {\n // Toggle: boolean 으로 명시 변환 (직접입력 허용은 유저단 입력칸 노출/필수 판정 기준)\n const checked = value === true || value === 'true';\n values[valueIndex] = { ...values[valueIndex], [field]: checked };\n } else {\n // 기타 직접 할당\n values[valueIndex] = { ...values[valueIndex], [field]: value };\n }\n\n options[groupIndex] = { ...options[groupIndex], values };\n\n G7Core.state.setLocal({\n form: { ...state.form, additional_options: options },\n hasChanges: true,\n });\n\n logger.log(`[updateAdditionalOptionValue] Updated group ${groupIndex} value ${valueIndex} field ${field}`);\n}\n\n/**\n * 추가옵션 선택지(값)를 삭제합니다. (최소 1개 유지)\n *\n * @param action 액션 객체 (params.groupIndex, params.valueIndex 필요)\n * @param _context 액션 컨텍스트\n */\nexport function removeAdditionalOptionValueHandler(\n action: ActionWithParams,\n _context: ActionContext\n): void {\n const params = action.params || {};\n const groupIndex = typeof params.groupIndex === 'string' ? parseInt(params.groupIndex, 10) : params.groupIndex as number;\n const valueIndex = typeof params.valueIndex === 'string' ? parseInt(params.valueIndex, 10) : params.valueIndex as number;\n\n if (groupIndex === undefined || valueIndex === undefined) {\n logger.warn('[removeAdditionalOptionValue] Missing required params');\n return;\n }\n\n const G7Core = (window as any).G7Core;\n if (!G7Core?.state) {\n logger.warn('[removeAdditionalOptionValue] G7Core.state API is not available');\n return;\n }\n\n const state = G7Core.state.getLocal() || {};\n const options: AdditionalOption[] = [...(state.form?.additional_options ?? [])];\n\n if (!options[groupIndex]) {\n logger.warn('[removeAdditionalOptionValue] Group not found at index', groupIndex);\n return;\n }\n\n const values = [...ensureGroupValues(options[groupIndex])];\n\n // 빈 그룹(선택지 0개) 저장 차단(D11) — 최소 1개 유지\n if (values.length <= 1) {\n G7Core.toast?.warning?.(\n G7Core.t?.('sirsoft-ecommerce.admin.product.options.messages.additional_value_min_1')\n ?? 'At least one choice is required per group.'\n );\n return;\n }\n\n values.splice(valueIndex, 1);\n\n // sort_order 재정렬 + 기본 선택지가 사라졌으면 첫 항목을 기본으로\n values.forEach((v, i) => { v.sort_order = i; });\n if (!values.some((v) => v.is_default)) {\n values[0].is_default = true;\n }\n\n options[groupIndex] = { ...options[groupIndex], values };\n\n G7Core.state.setLocal({\n form: { ...state.form, additional_options: options },\n hasChanges: true,\n });\n\n logger.log(`[removeAdditionalOptionValue] Removed value ${valueIndex} from group ${groupIndex}`);\n}\n","/**\n * 이미지 관련 핸들러\n *\n * 상품 등록/수정 화면에서 상품 이미지 업로드, 순서 변경, 삭제 기능을 처리합니다.\n */\n\nimport type { ActionContext } from '../types';\n\n// Logger 설정 (G7Core 초기화 전에도 동작하도록 폴백 포함)\nconst logger = ((window as any).G7Core?.createLogger?.('Ecom:Image')) ?? {\n log: (...args: unknown[]) => console.log('[Ecom:Image]', ...args),\n warn: (...args: unknown[]) => console.warn('[Ecom:Image]', ...args),\n error: (...args: unknown[]) => console.error('[Ecom:Image]', ...args),\n};\n\ninterface ProductImage {\n id?: number | null;\n url: string;\n is_thumbnail: boolean;\n sort_order: number;\n [key: string]: any;\n}\n\ninterface ActionWithParams {\n handler: string;\n params?: Record;\n [key: string]: any;\n}\n\nconst ALLOWED_IMAGE_TYPES = ['image/jpeg', 'image/png', 'image/gif', 'image/webp'];\nconst MAX_FILE_SIZE = 10 * 1024 * 1024; // 10MB\nconst MAX_IMAGES = 20;\n\n/**\n * 이미지를 업로드합니다.\n *\n * - 최대 20장까지 업로드 가능\n * - 지원 형식: JPEG, PNG, GIF, WebP\n * - 최대 파일 크기: 10MB\n *\n * @param action 액션 객체 (params.files 필요)\n * @param _context 액션 컨텍스트\n */\nexport async function uploadImagesHandler(\n action: ActionWithParams,\n _context: ActionContext\n): Promise {\n const params = action.params || {};\n const files = params.files as FileList;\n\n if (!files || files.length === 0) {\n logger.warn('[uploadImages] No files provided');\n return;\n }\n\n const G7Core = (window as any).G7Core;\n if (!G7Core?.state || !G7Core?.api) {\n logger.warn('[uploadImages] G7Core.state or G7Core.api is not available');\n return;\n }\n\n const state = G7Core.state.getLocal() || {};\n const currentImages: ProductImage[] = state.form?.images ?? [];\n const maxAllowed = MAX_IMAGES - currentImages.length;\n\n if (files.length > maxAllowed) {\n G7Core.toast?.warning?.(\n G7Core.t?.('sirsoft-ecommerce.admin.product.images.messages.max_count', { max: maxAllowed })\n ?? `You can add up to ${maxAllowed} images.`\n );\n }\n\n const filesToUpload = Array.from(files).slice(0, maxAllowed);\n\n for (const file of filesToUpload) {\n // 파일 유효성 검사\n if (!ALLOWED_IMAGE_TYPES.includes(file.type)) {\n G7Core.toast?.error?.(\n G7Core.t?.('sirsoft-ecommerce.admin.product.images.messages.unsupported_format', { name: file.name })\n ?? `${file.name}: Unsupported format.`\n );\n continue;\n }\n\n if (file.size > MAX_FILE_SIZE) {\n G7Core.toast?.error?.(\n G7Core.t?.('sirsoft-ecommerce.admin.product.images.messages.size_exceeded', { name: file.name })\n ?? `${file.name}: Exceeds 10MB.`\n );\n continue;\n }\n\n // 진행률 초기화\n const currentState = G7Core.state.getLocal() || {};\n G7Core.state.setLocal({\n ui: {\n ...currentState.ui,\n uploadProgress: {\n ...(currentState.ui?.uploadProgress || {}),\n [file.name]: 0,\n },\n },\n });\n\n try {\n const formData = new FormData();\n formData.append('file', file);\n formData.append('collection', 'products');\n\n const response = await G7Core.api.upload(\n '/api/admin/media/upload',\n formData,\n {\n onProgress: (percent: number) => {\n const progressState = G7Core.state.getLocal() || {};\n G7Core.state.setLocal({\n ui: {\n ...progressState.ui,\n uploadProgress: {\n ...(progressState.ui?.uploadProgress || {}),\n [file.name]: percent,\n },\n },\n });\n },\n }\n );\n\n // 업로드 성공 - 이미지 목록에 추가\n const successState = G7Core.state.getLocal() || {};\n const existingImages: ProductImage[] = successState.form?.images ?? [];\n const newImage: ProductImage = {\n ...response.data,\n is_thumbnail: existingImages.length === 0,\n sort_order: existingImages.length,\n };\n\n G7Core.state.setLocal({\n form: {\n ...successState.form,\n images: [...existingImages, newImage],\n },\n hasChanges: true,\n });\n\n // 진행률에서 제거\n const cleanupState = G7Core.state.getLocal() || {};\n const { [file.name]: _, ...remainingProgress } =\n cleanupState.ui?.uploadProgress || {};\n G7Core.state.setLocal({\n ui: { ...cleanupState.ui, uploadProgress: remainingProgress },\n });\n\n logger.log(`[uploadImages] Uploaded ${file.name}`);\n } catch (error) {\n G7Core.toast?.error?.(\n G7Core.t?.('sirsoft-ecommerce.admin.product.images.messages.upload_failed', { name: file.name })\n ?? `${file.name}: Upload failed`\n );\n\n const errorState = G7Core.state.getLocal() || {};\n const { [file.name]: _, ...remainingProgress } =\n errorState.ui?.uploadProgress || {};\n G7Core.state.setLocal({\n ui: { ...errorState.ui, uploadProgress: remainingProgress },\n });\n\n logger.error(`[uploadImages] Failed to upload ${file.name}:`, error);\n }\n }\n}\n\n/**\n * 대표 이미지를 설정합니다.\n *\n * @param action 액션 객체 (params.index 필요)\n * @param _context 액션 컨텍스트\n */\nexport function setThumbnailHandler(\n action: ActionWithParams,\n _context: ActionContext\n): void {\n const params = action.params || {};\n const index = params.index as number;\n\n if (index === undefined) {\n logger.warn('[setThumbnail] Missing index param');\n return;\n }\n\n const G7Core = (window as any).G7Core;\n if (!G7Core?.state) {\n logger.warn('[setThumbnail] G7Core.state API is not available');\n return;\n }\n\n const state = G7Core.state.getLocal() || {};\n const images = (state.form?.images ?? []).map(\n (img: ProductImage, i: number) => ({\n ...img,\n is_thumbnail: i === index,\n })\n );\n\n G7Core.state.setLocal({\n form: { ...state.form, images },\n hasChanges: true,\n });\n\n logger.log(`[setThumbnail] Set thumbnail to image at index ${index}`);\n}\n\n// 이미지 삭제 모달(confirmDeleteImage/deleteImage)은 FileUploader 의 내장\n// confirmBeforeRemove + onRemove 흐름으로 대체되어 제거되었다 (이슈 #421 §B-FAIL-1 dead code 정리).\n\n/**\n * 이미지 순서를 변경합니다.\n *\n * @param action 액션 객체 (params.oldIndex, params.newIndex 필요)\n * @param _context 액션 컨텍스트\n */\nexport function reorderImagesHandler(\n action: ActionWithParams,\n _context: ActionContext\n): void {\n const params = action.params || {};\n const oldIndex = params.oldIndex as number;\n const newIndex = params.newIndex as number;\n\n if (oldIndex === undefined || newIndex === undefined) {\n logger.warn('[reorderImages] Missing oldIndex or newIndex param');\n return;\n }\n\n const G7Core = (window as any).G7Core;\n if (!G7Core?.state) {\n logger.warn('[reorderImages] G7Core.state API is not available');\n return;\n }\n\n const state = G7Core.state.getLocal() || {};\n const images = [...(state.form?.images ?? [])];\n\n // 배열 요소 이동\n const [movedImage] = images.splice(oldIndex, 1);\n images.splice(newIndex, 0, movedImage);\n\n // sort_order 재설정\n images.forEach((img, i) => {\n img.sort_order = i;\n });\n\n G7Core.state.setLocal({\n form: { ...state.form, images },\n hasChanges: true,\n });\n\n logger.log(`[reorderImages] Moved image from ${oldIndex} to ${newIndex}`);\n}\n","/**\n * 상세설명 관련 핸들러\n *\n * 상품 등록/수정 화면에서 상세설명 업데이트 기능을 처리합니다.\n */\n\nimport type { ActionContext } from '../types';\n\n// Logger 설정 (G7Core 초기화 전에도 동작하도록 폴백 포함)\nconst logger = ((window as any).G7Core?.createLogger?.('Ecom:Description')) ?? {\n log: (...args: unknown[]) => console.log('[Ecom:Description]', ...args),\n warn: (...args: unknown[]) => console.warn('[Ecom:Description]', ...args),\n error: (...args: unknown[]) => console.error('[Ecom:Description]', ...args),\n};\n\ninterface ActionWithParams {\n handler: string;\n params?: Record;\n [key: string]: any;\n}\n\n/**\n * 상세설명을 업데이트합니다.\n *\n * @param action 액션 객체 (params.locale, params.value 필요)\n * @param _context 액션 컨텍스트\n */\nexport function updateDescriptionHandler(\n action: ActionWithParams,\n _context: ActionContext\n): void {\n const params = action.params || {};\n const locale = params.locale as string;\n const value = params.value as string;\n\n if (!locale) {\n logger.warn('[updateDescription] Missing locale param');\n return;\n }\n\n const G7Core = (window as any).G7Core;\n if (!G7Core?.state) {\n logger.warn('[updateDescription] G7Core.state API is not available');\n return;\n }\n\n const state = G7Core.state.getLocal() || {};\n\n G7Core.state.setLocal({\n form: {\n ...state.form,\n description: {\n ...state.form?.description,\n [locale]: value,\n },\n },\n hasChanges: true,\n });\n}\n","/**\n * 상품정보제공고시 관련 핸들러\n *\n * 상품 등록/수정 화면에서 상품정보제공고시 템플릿 관리, 항목 편집 기능을 처리합니다.\n */\n\nimport type { ActionContext } from '../types';\n\n// Logger 설정 (G7Core 초기화 전에도 동작하도록 폴백 포함)\nconst logger = ((window as any).G7Core?.createLogger?.('Ecom:Notice')) ?? {\n log: (...args: unknown[]) => console.log('[Ecom:Notice]', ...args),\n warn: (...args: unknown[]) => console.warn('[Ecom:Notice]', ...args),\n error: (...args: unknown[]) => console.error('[Ecom:Notice]', ...args),\n};\n\n/**\n * 지원하는 로케일에 대해 빈 문자열 객체를 생성합니다.\n * 하드코딩된 { ko: '', en: '' } 패턴 대신 사용합니다.\n *\n * @param defaultValue 기본값 (기본: 빈 문자열)\n * @returns 로케일별 빈 문자열 객체\n */\nfunction createEmptyLocaleObject(defaultValue = ''): Record {\n const G7Core = (window as any).G7Core;\n const supportedLocales: string[] = G7Core?.locale?.supported?.() ?? ['ko', 'en'];\n const result: Record = {};\n for (const locale of supportedLocales) {\n result[locale] = defaultValue;\n }\n return result;\n}\n\ninterface NoticeTemplate {\n id: number;\n name: Record;\n fields: NoticeTemplateField[];\n}\n\ninterface NoticeTemplateField {\n name: Record;\n content: Record;\n}\n\ninterface NoticeItem {\n key: string;\n name: Record;\n content: Record;\n sort_order: number;\n is_custom?: boolean;\n}\n\ninterface ActionWithParams {\n handler: string;\n params?: Record;\n [key: string]: any;\n}\n\n/**\n * 상품정보제공고시 템플릿 선택을 확인합니다.\n * 기존 항목이 있으면 확인 모달을 표시하고, 없으면 바로 적용합니다.\n *\n * @param action 액션 객체 (params.templateId 필요)\n * @param context 액션 컨텍스트 (datasources.notice_templates 사용)\n */\nexport function confirmSelectNoticeTemplateHandler(\n action: ActionWithParams,\n context: ActionContext\n): void {\n const params = action.params || {};\n const templateId = params.templateId as number | null;\n\n const G7Core = (window as any).G7Core;\n if (!G7Core?.state) {\n logger.warn('[confirmSelectNoticeTemplate] G7Core.state API is not available');\n return;\n }\n\n const state = G7Core.state.getLocal() || {};\n const existingItems: NoticeItem[] = state.form?.notice_items ?? [];\n const hasExistingItems = existingItems.length > 0 && existingItems.some(item => {\n const hasContent = Object.values(item.content || {}).some(v => v && v.trim() !== '');\n return hasContent;\n });\n\n // 템플릿 해제(null)이거나 기존 항목이 없으면 바로 적용\n if (!templateId || !hasExistingItems) {\n selectNoticeTemplateHandler(action, context);\n return;\n }\n\n // 기존 항목이 있으면 확인 모달 표시\n G7Core.state.setGlobal({\n pendingNoticeTemplateId: templateId,\n });\n G7Core.modal?.open?.('notice_template_confirm_modal');\n\n logger.log(`[confirmSelectNoticeTemplate] Showing confirm modal for template: ${templateId}`);\n}\n\n/**\n * 상품정보제공고시 템플릿을 선택합니다.\n *\n * @param action 액션 객체 (params.templateId 필요)\n * @param context 액션 컨텍스트 (datasources.notice_templates 사용)\n */\nexport function selectNoticeTemplateHandler(\n action: ActionWithParams,\n _context: ActionContext\n): void {\n const params = action.params || {};\n const templateId = params.templateId as number | null;\n\n const G7Core = (window as any).G7Core;\n if (!G7Core?.state) {\n logger.warn('[selectNoticeTemplate] G7Core.state API is not available');\n return;\n }\n\n const state = G7Core.state.getLocal() || {};\n // G7Core.dataSource를 통해 데이터소스 접근 (context.data가 아닌 G7Core API 사용)\n const dsData = G7Core.dataSource?.get?.('notice_templates');\n const templates: NoticeTemplate[] = dsData?.data?.data ?? dsData?.data ?? [];\n\n if (!templateId) {\n G7Core.state.setLocal({\n form: {\n ...state.form,\n notice_items: [],\n },\n ui: {\n ...state.ui,\n selectedNoticeTemplateId: null, // UI용 템플릿 선택 상태\n },\n hasChanges: true,\n });\n return;\n }\n\n const template = templates.find((t) => t.id === templateId);\n if (!template) {\n logger.warn(`[selectNoticeTemplate] Template not found: ${templateId}`);\n return;\n }\n\n // 기존 값 유지하면서 새 템플릿 항목으로 매핑\n const existingItems: NoticeItem[] = state.form?.notice_items ?? [];\n const newItems: NoticeItem[] = (template.fields ?? []).map((field, index) => {\n // 템플릿 필드의 name을 기준으로 기존 값 매칭\n const fieldKey = JSON.stringify(field.name);\n const existing = existingItems.find((e) => JSON.stringify(e.name) === fieldKey);\n return {\n key: `field_${index}`,\n name: field.name,\n content: existing?.content ?? field.content ?? createEmptyLocaleObject(),\n sort_order: index,\n is_custom: false,\n };\n });\n\n G7Core.state.setLocal({\n form: {\n ...state.form,\n notice_items: newItems, // 템플릿은 UI용, form에 저장하지 않음\n },\n ui: {\n ...state.ui,\n selectedNoticeTemplateId: templateId, // UI용 템플릿 선택 상태\n },\n hasChanges: true,\n });\n\n logger.log(`[selectNoticeTemplate] Selected template: ${templateId}`);\n}\n\n/**\n * 상품정보제공고시 항목 값을 업데이트합니다.\n *\n * @param action 액션 객체 (params.index, params.locale, params.value 필요)\n * @param _context 액션 컨텍스트\n */\nexport function updateNoticeItemHandler(\n action: ActionWithParams,\n _context: ActionContext\n): void {\n const params = action.params || {};\n const index = params.index as number;\n const locale = params.locale as string;\n const value = params.value as string;\n\n if (index === undefined || !locale) {\n logger.warn('[updateNoticeItem] Missing required params');\n return;\n }\n\n const G7Core = (window as any).G7Core;\n if (!G7Core?.state) {\n logger.warn('[updateNoticeItem] G7Core.state API is not available');\n return;\n }\n\n const state = G7Core.state.getLocal() || {};\n const items = [...(state.form?.notice_items ?? [])];\n\n items[index] = {\n ...items[index],\n content: {\n ...items[index].content,\n [locale]: value,\n },\n };\n\n G7Core.state.setLocal({\n form: { ...state.form, notice_items: items },\n hasChanges: true,\n });\n}\n\n/**\n * 상품정보제공고시 항목을 삭제합니다.\n *\n * @param action 액션 객체 (params.index 필요)\n * @param _context 액션 컨텍스트\n */\nexport function removeNoticeItemHandler(\n action: ActionWithParams,\n _context: ActionContext\n): void {\n const params = action.params || {};\n const index = params.index as number;\n\n if (index === undefined) {\n logger.warn('[removeNoticeItem] Missing index param');\n return;\n }\n\n const G7Core = (window as any).G7Core;\n if (!G7Core?.state) {\n logger.warn('[removeNoticeItem] G7Core.state API is not available');\n return;\n }\n\n const state = G7Core.state.getLocal() || {};\n const items = [...(state.form?.notice_items ?? [])];\n items.splice(index, 1);\n\n // sort_order 재정렬\n items.forEach((item, i) => {\n item.sort_order = i;\n });\n\n G7Core.state.setLocal({\n form: { ...state.form, notice_items: items },\n hasChanges: true,\n });\n\n logger.log(`[removeNoticeItem] Removed notice item at index ${index}`);\n}\n\n/**\n * 상품정보제공고시 항목 순서를 변경합니다.\n *\n * @param action 액션 객체 (params.oldIndex, params.newIndex 필요)\n * @param _context 액션 컨텍스트\n */\nexport function reorderNoticeItemsHandler(\n action: ActionWithParams,\n _context: ActionContext\n): void {\n const params = action.params || {};\n const oldIndex = params.oldIndex as number;\n const newIndex = params.newIndex as number;\n\n if (oldIndex === undefined || newIndex === undefined) {\n logger.warn('[reorderNoticeItems] Missing oldIndex or newIndex param');\n return;\n }\n\n const G7Core = (window as any).G7Core;\n if (!G7Core?.state) {\n logger.warn('[reorderNoticeItems] G7Core.state API is not available');\n return;\n }\n\n const state = G7Core.state.getLocal() || {};\n const items = [...(state.form?.notice_items ?? [])];\n\n const [moved] = items.splice(oldIndex, 1);\n items.splice(newIndex, 0, moved);\n\n items.forEach((item, i) => {\n item.sort_order = i;\n });\n\n G7Core.state.setLocal({\n form: { ...state.form, notice_items: items },\n hasChanges: true,\n });\n\n logger.log(`[reorderNoticeItems] Moved item from ${oldIndex} to ${newIndex}`);\n}\n\n/**\n * 빈 항목에 일괄 입력합니다.\n *\n * @param action 액션 객체 (params.value 필요)\n * @param _context 액션 컨텍스트\n */\nexport function fillNoticeWithValueHandler(\n action: ActionWithParams,\n _context: ActionContext\n): void {\n const params = action.params || {};\n const value = params.value as string;\n\n const G7Core = (window as any).G7Core;\n if (!G7Core?.state) {\n logger.warn('[fillNoticeWithValue] G7Core.state API is not available');\n return;\n }\n\n const state = G7Core.state.getLocal() || {};\n const locale = state.ui?.noticeLocale ?? 'ko';\n\n const items = (state.form?.notice_items ?? []).map((item: NoticeItem) => ({\n ...item,\n content: {\n ...item.content,\n [locale]: item.content?.[locale] || value, // 빈 값만 채움\n },\n }));\n\n G7Core.state.setLocal({\n form: { ...state.form, notice_items: items },\n hasChanges: true,\n });\n\n G7Core.toast?.success?.(\n G7Core.t?.('sirsoft-ecommerce.admin.product.notice.messages.filled')\n ?? 'Empty fields have been filled.'\n );\n}\n\n/**\n * 기존템플릿/신규등록 탭을 전환합니다.\n *\n * @param action 액션 객체 (params.mode 필요: 'existing' | 'new')\n * @param _context 액션 컨텍스트\n */\nexport function switchNoticeModeHandler(\n action: ActionWithParams,\n _context: ActionContext\n): void {\n const params = action.params || {};\n const mode = params.mode as 'existing' | 'new';\n\n if (!mode) {\n logger.warn('[switchNoticeMode] Missing mode param');\n return;\n }\n\n const G7Core = (window as any).G7Core;\n if (!G7Core?.state) {\n logger.warn('[switchNoticeMode] G7Core.state API is not available');\n return;\n }\n\n const state = G7Core.state.getLocal() || {};\n\n G7Core.state.setLocal({\n ui: {\n ...state.ui,\n noticeMode: mode,\n },\n });\n\n logger.log(`[switchNoticeMode] Switched to ${mode} mode`);\n}\n\n/**\n * 신규 템플릿명을 업데이트합니다.\n *\n * @param action 액션 객체 (params.locale, params.value 필요)\n * @param _context 액션 컨텍스트\n */\nexport function updateNewTemplateNameHandler(\n action: ActionWithParams,\n _context: ActionContext\n): void {\n const params = action.params || {};\n const locale = params.locale as string;\n const value = params.value as string;\n\n if (!locale) {\n logger.warn('[updateNewTemplateName] Missing locale param');\n return;\n }\n\n const G7Core = (window as any).G7Core;\n if (!G7Core?.state) {\n logger.warn('[updateNewTemplateName] G7Core.state API is not available');\n return;\n }\n\n const state = G7Core.state.getLocal() || {};\n\n G7Core.state.setLocal({\n form: {\n ...state.form,\n new_template_name: {\n ...state.form?.new_template_name,\n [locale]: value,\n },\n },\n });\n}\n\n/**\n * 신규등록 모드에서 고시 항목을 추가합니다.\n *\n * @param _action 액션 객체\n * @param _context 액션 컨텍스트\n */\nexport function addNoticeItemHandler(\n _action: ActionWithParams,\n _context: ActionContext\n): void {\n const G7Core = (window as any).G7Core;\n if (!G7Core?.state) {\n logger.warn('[addNoticeItem] G7Core.state API is not available');\n return;\n }\n\n const state = G7Core.state.getLocal() || {};\n const items: NoticeItem[] = state.form?.notice_items ?? [];\n\n const newItem: NoticeItem = {\n key: `custom_${Date.now()}`,\n name: createEmptyLocaleObject(),\n content: createEmptyLocaleObject(),\n sort_order: items.length,\n is_custom: true,\n };\n\n G7Core.state.setLocal({\n form: {\n ...state.form,\n notice_items: [...items, newItem],\n },\n hasChanges: true,\n });\n\n logger.log(`[addNoticeItem] Added new notice item. Total: ${items.length + 1}`);\n}\n\n/**\n * 고시 항목명을 수정합니다. (신규등록 모드에서)\n *\n * @param action 액션 객체 (params.index, params.locale, params.value 필요)\n * @param _context 액션 컨텍스트\n */\nexport function updateNoticeItemNameHandler(\n action: ActionWithParams,\n _context: ActionContext\n): void {\n const params = action.params || {};\n const index = params.index as number;\n const locale = params.locale as string;\n const value = params.value as string;\n\n if (index === undefined || !locale) {\n logger.warn('[updateNoticeItemName] Missing required params');\n return;\n }\n\n const G7Core = (window as any).G7Core;\n if (!G7Core?.state) {\n logger.warn('[updateNoticeItemName] G7Core.state API is not available');\n return;\n }\n\n const state = G7Core.state.getLocal() || {};\n const items = [...(state.form?.notice_items ?? [])];\n\n items[index] = {\n ...items[index],\n name: {\n ...items[index].name,\n [locale]: value,\n },\n };\n\n G7Core.state.setLocal({\n form: { ...state.form, notice_items: items },\n hasChanges: true,\n });\n}\n\n/**\n * 현재 고시 항목들을 새 템플릿으로 저장 요청합니다.\n *\n * @param _action 액션 객체\n * @param _context 액션 컨텍스트\n */\nexport function saveAsNoticeTemplateHandler(\n _action: ActionWithParams,\n _context: ActionContext\n): void {\n const G7Core = (window as any).G7Core;\n if (!G7Core?.state) {\n logger.warn('[saveAsNoticeTemplate] G7Core.state API is not available');\n return;\n }\n\n const state = G7Core.state.getLocal() || {};\n\n // 템플릿 이름은 모달 안에서 입력하므로 여기서는 저장 대상 항목 존재만 검증한다\n const items: NoticeItem[] = state.form?.notice_items ?? [];\n if (items.length === 0) {\n G7Core.toast?.warning?.(\n G7Core.t?.('sirsoft-ecommerce.admin.product.notice.messages.no_items')\n ?? 'No items to save.'\n );\n return;\n }\n\n // 모달 입력 상태(_global.ui.saveTemplateData)를 초기화\n G7Core.state.set?.({\n 'ui.saveTemplateData': { name: createEmptyLocaleObject(), category_id: null, is_default: false },\n });\n\n // 루트 modals 배열의 Modal 은 show prop 이 아니라 modalStack(openModal) 으로 열린다\n G7Core.dispatch?.({ handler: 'openModal', target: 'modal_save_template' });\n}\n\n/**\n * 로케일별 '상세설명참조' 번역 맵\n * 다국어 파일(ko.json, en.json)의 admin.product_notice_template.form.detail_reference 값과 동기화 필요\n */\nconst DETAIL_REF_TRANSLATIONS: Record = {\n ko: '상세설명참조',\n en: 'See detail description',\n};\n\n/**\n * 상품정보제공고시 템플릿의 모든 필드를 '상세설명참조'로 채웁니다.\n * (템플릿 관리 화면용)\n *\n * @param _action 액션 객체\n * @param _context 액션 컨텍스트\n */\nexport function fillTemplateFieldsWithDetailReferenceHandler(\n action: ActionWithParams,\n _context: ActionContext\n): void {\n const G7Core = (window as any).G7Core;\n if (!G7Core?.state) {\n logger.warn('[fillTemplateFieldsWithDetailReference] G7Core.state API is not available');\n return;\n }\n\n // params로 전달받은 form 사용 (모달 등 별도 스코프에서 호출 시 필요)\n const params = action.params || {};\n const form = params.form || {};\n const fields = form.fields ?? [];\n\n if (fields.length === 0) {\n G7Core.toast?.warning?.(\n G7Core.t?.('sirsoft-ecommerce.admin.product_notice_template.messages.no_fields_to_change') ?? '변경할 항목이 없습니다.'\n );\n return;\n }\n\n // 지원하는 로케일 목록 가져오기\n const supportedLocales: string[] = G7Core.locale?.supported?.() ?? ['ko', 'en'];\n\n // 지원되는 로케일에 대한 '상세설명참조' content 객체 생성\n const detailRefContent: Record = {};\n for (const locale of supportedLocales) {\n detailRefContent[locale] = DETAIL_REF_TRANSLATIONS[locale] ?? DETAIL_REF_TRANSLATIONS['en'] ?? 'See detail description';\n }\n\n // 모든 필드의 content를 '상세설명참조'로 변경\n // 각 필드에 새로운 객체를 할당하여 참조 공유 문제 방지\n const updatedFields = fields.map((field: { name: Record; content: Record }) => ({\n ...field,\n content: { ...detailRefContent },\n }));\n\n // form.fields 경로를 직접 업데이트하여 DynamicFieldList 바인딩 갱신 보장\n // onComponentEvent로 부모 컨텍스트에서 실행되므로 일반 setLocal 사용\n G7Core.state.setLocal({\n 'form.fields': updatedFields,\n });\n\n G7Core.toast?.success?.(\n G7Core.t?.('sirsoft-ecommerce.admin.product_notice_template.messages.all_fields_changed_to_detail_ref') ?? '모든 항목이 \\'상세설명참조\\'로 변경되었습니다.'\n );\n logger.log('[fillTemplateFieldsWithDetailReference] All fields updated to detail reference');\n}\n\n/**\n * 상품 폼의 상품정보제공고시 항목의 모든 값을 '상세설명참조'로 채웁니다.\n * (상품 등록/수정 화면용)\n *\n * @param action 액션 객체\n * @param _context 액션 컨텍스트\n */\nexport function fillNoticeItemsWithDetailReferenceHandler(\n action: ActionWithParams,\n _context: ActionContext\n): void {\n const G7Core = (window as any).G7Core;\n if (!G7Core?.state) {\n logger.warn('[fillNoticeItemsWithDetailReference] G7Core.state API is not available');\n return;\n }\n\n // params로 전달받은 form 사용 (모달 등 별도 스코프에서 호출 시 필요)\n const params = action.params || {};\n const form = params.form || {};\n const noticeItems = form.notice_items ?? [];\n\n if (noticeItems.length === 0) {\n G7Core.toast?.warning?.(\n G7Core.t?.('sirsoft-ecommerce.admin.product.notice.messages.no_items_to_change') ?? '변경할 항목이 없습니다.'\n );\n return;\n }\n\n // 지원하는 로케일 목록 가져오기\n const supportedLocales: string[] = G7Core.locale?.supported?.() ?? ['ko', 'en'];\n\n // 지원되는 로케일에 대한 '상세설명참조' content 객체 생성\n const detailRefContent: Record = {};\n for (const locale of supportedLocales) {\n detailRefContent[locale] = DETAIL_REF_TRANSLATIONS[locale] ?? DETAIL_REF_TRANSLATIONS['en'] ?? 'See detail description';\n }\n\n // 모든 항목의 content를 '상세설명참조'로 변경\n // 각 항목에 새로운 객체를 할당하여 참조 공유 문제 방지\n const updatedItems = noticeItems.map((item: NoticeItem) => ({\n ...item,\n content: { ...detailRefContent },\n }));\n\n // form.notice_items 경로를 직접 업데이트하여 DynamicFieldList 바인딩 갱신 보장\n G7Core.state.setLocal({\n 'form.notice_items': updatedItems,\n });\n\n G7Core.toast?.success?.(\n G7Core.t?.('sirsoft-ecommerce.admin.product.notice.messages.all_items_changed_to_detail_ref') ?? '모든 항목이 \\'상세설명참조\\'로 변경되었습니다.'\n );\n logger.log('[fillNoticeItemsWithDetailReference] All notice items updated to detail reference');\n}\n\n/**\n * 템플릿 저장을 확인합니다.\n *\n * @param _action 액션 객체\n * @param _context 액션 컨텍스트\n */\nexport async function confirmSaveNoticeTemplateHandler(\n _action: ActionWithParams,\n _context: ActionContext\n): Promise {\n const G7Core = (window as any).G7Core;\n if (!G7Core?.state || !G7Core?.api) {\n logger.warn('[confirmSaveNoticeTemplate] G7Core.state or G7Core.api is not available');\n return;\n }\n\n const state = G7Core.state.getLocal() || {};\n // 템플릿 이름/분류/기본여부는 모달 입력(_global.ui.saveTemplateData)이 SSoT\n const saveTemplateData = G7Core.state.get?.()?.ui?.saveTemplateData ?? {};\n const templateName = saveTemplateData.name;\n\n if (!templateName?.ko?.trim()) {\n G7Core.toast?.warning?.(\n G7Core.t?.('sirsoft-ecommerce.admin.product.notice.messages.template_name_required')\n ?? 'Please enter template name.'\n );\n return;\n }\n\n try {\n // 백엔드(StoreProductNoticeTemplateRequest)는 name/fields.*.{name,content}/is_active 를 받는다\n const items: NoticeItem[] = state.form?.notice_items ?? [];\n const response = await G7Core.api.post(\n '/api/modules/sirsoft-ecommerce/admin/products/notice-templates',\n {\n name: templateName,\n is_active: saveTemplateData.is_default ?? false,\n fields: items.map((item) => ({\n name: item.name,\n content: item.content,\n })),\n }\n );\n\n // 모달 입력 상태 초기화 (_global)\n G7Core.state.set?.({\n 'ui.saveTemplateData': { name: createEmptyLocaleObject(), category_id: null, is_default: false },\n });\n\n G7Core.state.setLocal({\n ui: {\n ...state.ui,\n noticeMode: 'existing',\n },\n hasChanges: true,\n });\n\n // 루트 modals 배열의 Modal 은 modalStack(closeModal) 으로 닫는다\n G7Core.dispatch?.({ handler: 'closeModal' });\n\n G7Core.toast?.success?.(\n G7Core.t?.('sirsoft-ecommerce.admin.product.notice.messages.template_saved')\n ?? 'Template has been saved.'\n );\n\n // 템플릿 목록 새로고침\n G7Core.datasources?.refresh?.('notice_templates');\n\n logger.log(`[confirmSaveNoticeTemplate] Template saved: ${response.data.id}`);\n } catch (error) {\n G7Core.toast?.error?.(\n G7Core.t?.('sirsoft-ecommerce.admin.product.notice.messages.template_save_error')\n ?? 'Failed to save template.'\n );\n logger.error('[confirmSaveNoticeTemplate] Failed to save template:', error);\n }\n}\n","/**\n * 기타 섹션 핸들러\n *\n * 상품 등록/수정 화면에서 라벨, 상품코드, 배송비, 쇼핑연동, 식별코드, 처리로그 등\n * 기타 기능들을 처리합니다.\n */\n\nimport type { ActionContext } from '../types';\n\n// Logger 설정 (G7Core 초기화 전에도 동작하도록 폴백 포함)\nconst logger = ((window as any).G7Core?.createLogger?.('Ecom:Misc')) ?? {\n log: (...args: unknown[]) => console.log('[Ecom:Misc]', ...args),\n warn: (...args: unknown[]) => console.warn('[Ecom:Misc]', ...args),\n error: (...args: unknown[]) => console.error('[Ecom:Misc]', ...args),\n};\n\ninterface ShippingPolicy {\n id: number;\n name: string;\n is_default?: boolean;\n [key: string]: any;\n}\n\ninterface CommonInfoTemplate {\n id: number;\n content: Record;\n}\n\ninterface LabelWithPeriod {\n id: number;\n start_date: string | null;\n end_date: string | null;\n}\n\ninterface ActionWithParams {\n handler: string;\n params?: Record;\n [key: string]: any;\n}\n\n/**\n * 라벨 선택을 토글합니다.\n *\n * @param action 액션 객체 (params.labelId 필요)\n * @param _context 액션 컨텍스트\n */\nexport function toggleLabelHandler(\n action: ActionWithParams,\n _context: ActionContext\n): void {\n const params = action.params || {};\n const labelId = params.labelId as number;\n\n if (!labelId) {\n logger.warn('[toggleLabel] Missing labelId param');\n return;\n }\n\n const G7Core = (window as any).G7Core;\n if (!G7Core?.state) {\n logger.warn('[toggleLabel] G7Core.state API is not available');\n return;\n }\n\n const state = G7Core.state.getLocal() || {};\n const labels: number[] = state.form?.labels ?? [];\n\n const newLabels = labels.includes(labelId)\n ? labels.filter((id) => id !== labelId)\n : [...labels, labelId];\n\n G7Core.state.setLocal({\n form: { ...state.form, labels: newLabels },\n hasChanges: true,\n });\n\n logger.log(`[toggleLabel] Toggled label ${labelId}. Current labels: ${newLabels.length}`);\n}\n\n/**\n * 상품코드를 자동 생성합니다.\n *\n * @param _action 액션 객체\n * @param _context 액션 컨텍스트\n */\nexport async function generateProductCodeHandler(\n _action: ActionWithParams,\n _context: ActionContext\n): Promise {\n const G7Core = (window as any).G7Core;\n if (!G7Core?.state || !G7Core?.api) {\n logger.warn('[generateProductCode] G7Core.state or G7Core.api is not available');\n return;\n }\n\n try {\n const response = await G7Core.api.post(\n '/api/modules/sirsoft-ecommerce/admin/products/generate-code'\n );\n\n const state = G7Core.state.getLocal() || {};\n G7Core.state.setLocal({\n form: { ...state.form, product_code: response.data.product_code },\n });\n\n logger.log(`[generateProductCode] Generated: ${response.data.product_code}`);\n } catch (error) {\n G7Core.toast?.error?.(\n G7Core.t?.('sirsoft-ecommerce.admin.product.messages.code_generate_error')\n ?? 'Failed to generate product code.'\n );\n logger.error('[generateProductCode] Failed:', error);\n }\n}\n\n/**\n * 배송정책 정보를 반환합니다.\n *\n * @param action 액션 객체 (params.policyId, params.field, params.policies 필요)\n * @param _context 액션 컨텍스트\n * @returns 배송정책 필드 값\n */\nexport function getShippingPolicyInfoHandler(\n action: ActionWithParams,\n _context: ActionContext\n): string {\n const params = action.params || {};\n const policyId = params.policyId as number;\n const field = params.field as string;\n const policies = params.policies as ShippingPolicy[];\n\n if (!policyId || !field || !policies) {\n return '-';\n }\n\n const policy = policies.find((p) => p.id === policyId);\n if (!policy) return '-';\n return policy[field] ?? '-';\n}\n\n/**\n * 공통정보 템플릿 내용을 반환합니다.\n *\n * @param action 액션 객체 (params.templateId, params.templates 필요)\n * @param _context 액션 컨텍스트\n * @returns 공통정보 내용\n */\nexport function getCommonInfoContentHandler(\n action: ActionWithParams,\n _context: ActionContext\n): string {\n const params = action.params || {};\n const templateId = params.templateId as number;\n const templates = params.templates as CommonInfoTemplate[];\n\n if (!templateId || !templates) {\n return '';\n }\n\n const G7Core = (window as any).G7Core;\n const locale = G7Core?.locale?.current?.() || 'ko';\n\n const template = templates.find((t) => t.id === templateId);\n return template?.content?.[locale] ?? template?.content?.ko ?? '';\n}\n\n/**\n * 쇼핑 플랫폼 연동 설정을 업데이트합니다.\n *\n * @param action 액션 객체 (params.platform, params.field, params.value 필요)\n * @param _context 액션 컨텍스트\n */\nexport function updateShoppingIntegrationHandler(\n action: ActionWithParams,\n _context: ActionContext\n): void {\n const params = action.params || {};\n const platform = params.platform as 'naver' | 'daum' | 'facebook' | 'google';\n const field = params.field as 'enabled' | 'sync_interval';\n const value = params.value as boolean | string;\n\n if (!platform || !field) {\n logger.warn('[updateShoppingIntegration] Missing required params');\n return;\n }\n\n const G7Core = (window as any).G7Core;\n if (!G7Core?.state) {\n logger.warn('[updateShoppingIntegration] G7Core.state API is not available');\n return;\n }\n\n const state = G7Core.state.getLocal() || {};\n const integrations = state.form?.shopping_integrations ?? {\n naver: { enabled: false, sync_interval: 'manual' },\n daum: { enabled: false, sync_interval: 'manual' },\n facebook: { enabled: false, sync_interval: 'manual' },\n google: { enabled: false, sync_interval: 'manual' },\n };\n\n G7Core.state.setLocal({\n form: {\n ...state.form,\n shopping_integrations: {\n ...integrations,\n [platform]: {\n ...integrations[platform],\n [field]: value,\n },\n },\n },\n hasChanges: true,\n });\n\n logger.log(`[updateShoppingIntegration] Updated ${platform}.${field} to ${value}`);\n}\n\n/**\n * 배송비 유형을 업데이트합니다.\n *\n * @param action 액션 객체 (params.type 필요)\n * @param _context 액션 컨텍스트\n */\nexport function updateShippingTypeHandler(\n action: ActionWithParams,\n _context: ActionContext\n): void {\n const params = action.params || {};\n const type = params.type as 'fixed_free' | 'fixed_paid' | 'threshold_free' | 'qty_free' | 'weight_based';\n\n if (!type) {\n logger.warn('[updateShippingType] Missing type param');\n return;\n }\n\n const G7Core = (window as any).G7Core;\n if (!G7Core?.state) {\n logger.warn('[updateShippingType] G7Core.state API is not available');\n return;\n }\n\n const state = G7Core.state.getLocal() || {};\n\n // 배송비 유형에 따른 기본값 설정\n let defaultFee = 0;\n let defaultThreshold = 0;\n\n if (type === 'fixed_paid') {\n defaultFee = 3000;\n } else if (type === 'threshold_free') {\n defaultThreshold = 30000;\n }\n\n G7Core.state.setLocal({\n form: {\n ...state.form,\n shipping_type: type,\n shipping_fee: type === 'fixed_free' ? 0 : (state.form?.shipping_fee ?? defaultFee),\n free_shipping_threshold:\n type === 'threshold_free'\n ? (state.form?.free_shipping_threshold ?? defaultThreshold)\n : 0,\n },\n hasChanges: true,\n });\n\n logger.log(`[updateShippingType] Updated shipping type to ${type}`);\n}\n\n/**\n * 식별코드를 업데이트합니다.\n *\n * @param action 액션 객체 (params.field, params.value 필요)\n * @param _context 액션 컨텍스트\n */\nexport function updateIdentificationCodeHandler(\n action: ActionWithParams,\n _context: ActionContext\n): void {\n const params = action.params || {};\n const field = params.field as 'isbn_issn' | 'memo_id';\n const value = params.value as string;\n\n if (!field) {\n logger.warn('[updateIdentificationCode] Missing field param');\n return;\n }\n\n const G7Core = (window as any).G7Core;\n if (!G7Core?.state) {\n logger.warn('[updateIdentificationCode] G7Core.state API is not available');\n return;\n }\n\n const state = G7Core.state.getLocal() || {};\n\n G7Core.state.setLocal({\n form: {\n ...state.form,\n [field]: value,\n },\n hasChanges: true,\n });\n}\n\n/**\n * 라벨 기간 설정 모달을 엽니다.\n *\n * @param action 액션 객체 (params.labelId, params.labelName 필요)\n * @param _context 액션 컨텍스트\n */\nexport function openLabelPeriodModalHandler(\n action: ActionWithParams,\n _context: ActionContext\n): void {\n const params = action.params || {};\n const labelId = params.labelId as number;\n const labelName = params.labelName as string;\n\n if (!labelId) {\n logger.warn('[openLabelPeriodModal] Missing labelId param');\n return;\n }\n\n const G7Core = (window as any).G7Core;\n if (!G7Core?.state) {\n logger.warn('[openLabelPeriodModal] G7Core.state API is not available');\n return;\n }\n\n const state = G7Core.state.getLocal() || {};\n const labels: LabelWithPeriod[] = state.form?.labels ?? [];\n const existingPeriod = labels.find((l) => l.id === labelId);\n\n G7Core.state.setLocal({\n ui: {\n ...state.ui,\n showLabelPeriodModal: true,\n labelPeriodModalData: {\n labelId,\n labelName,\n startDate: existingPeriod?.start_date ?? null,\n endDate: existingPeriod?.end_date ?? null,\n },\n },\n });\n}\n\n/**\n * 라벨 기간을 저장합니다.\n *\n * @param action 액션 객체 (params.labelId, params.startDate, params.endDate 필요)\n * @param _context 액션 컨텍스트\n */\nexport function saveLabelPeriodHandler(\n action: ActionWithParams,\n _context: ActionContext\n): void {\n const params = action.params || {};\n const labelId = params.labelId as number;\n const startDate = params.startDate as string | null;\n const endDate = params.endDate as string | null;\n\n if (!labelId) {\n logger.warn('[saveLabelPeriod] Missing labelId param');\n return;\n }\n\n const G7Core = (window as any).G7Core;\n if (!G7Core?.state) {\n logger.warn('[saveLabelPeriod] G7Core.state API is not available');\n return;\n }\n\n const state = G7Core.state.getLocal() || {};\n const labels: LabelWithPeriod[] = [...(state.form?.labels ?? [])];\n\n const existingIndex = labels.findIndex((l) => l.id === labelId);\n\n if (existingIndex >= 0) {\n labels[existingIndex] = {\n ...labels[existingIndex],\n start_date: startDate,\n end_date: endDate,\n };\n } else {\n labels.push({\n id: labelId,\n start_date: startDate,\n end_date: endDate,\n });\n }\n\n G7Core.state.setLocal({\n form: { ...state.form, labels },\n ui: {\n ...state.ui,\n showLabelPeriodModal: false,\n labelPeriodModalData: null,\n },\n hasChanges: true,\n });\n\n G7Core.toast?.success?.(\n G7Core.t?.('sirsoft-ecommerce.admin.product.messages.label_period_saved')\n ?? 'Label period has been saved.'\n );\n logger.log(`[saveLabelPeriod] Saved period for label ${labelId}`);\n}\n\n/**\n * 라벨 기간을 제거합니다. (라벨은 유지, 기간만 삭제)\n *\n * @param action 액션 객체 (params.labelId 필요)\n * @param _context 액션 컨텍스트\n */\nexport function removeLabelPeriodHandler(\n action: ActionWithParams,\n _context: ActionContext\n): void {\n const params = action.params || {};\n const labelId = params.labelId as number;\n\n if (!labelId) {\n logger.warn('[removeLabelPeriod] Missing labelId param');\n return;\n }\n\n const G7Core = (window as any).G7Core;\n if (!G7Core?.state) {\n logger.warn('[removeLabelPeriod] G7Core.state API is not available');\n return;\n }\n\n const state = G7Core.state.getLocal() || {};\n const labels: LabelWithPeriod[] = [...(state.form?.labels ?? [])];\n\n const existingIndex = labels.findIndex((l) => l.id === labelId);\n\n if (existingIndex >= 0) {\n labels[existingIndex] = {\n ...labels[existingIndex],\n start_date: null,\n end_date: null,\n };\n }\n\n G7Core.state.setLocal({\n form: { ...state.form, labels },\n hasChanges: true,\n });\n\n G7Core.toast?.success?.(\n G7Core.t?.('sirsoft-ecommerce.admin.product.messages.label_period_removed')\n ?? 'Label period has been removed.'\n );\n logger.log(`[removeLabelPeriod] Removed period for label ${labelId}`);\n}\n\n/**\n * 처리로그 정렬을 변경합니다.\n *\n * @param action 액션 객체 (params.sort 필요: 'latest' | 'oldest')\n * @param _context 액션 컨텍스트\n */\nexport function updateActivityLogSortHandler(\n action: ActionWithParams,\n _context: ActionContext\n): void {\n const params = action.params || {};\n const sort = params.sort as 'latest' | 'oldest';\n\n if (!sort) {\n logger.warn('[updateActivityLogSort] Missing sort param');\n return;\n }\n\n const G7Core = (window as any).G7Core;\n if (!G7Core?.state) {\n logger.warn('[updateActivityLogSort] G7Core.state API is not available');\n return;\n }\n\n const state = G7Core.state.getLocal() || {};\n\n G7Core.state.setLocal({\n ui: {\n ...state.ui,\n activityLogSort: sort,\n },\n });\n\n // 데이터소스 새로고침\n G7Core.datasources?.refresh?.('activity_logs', {\n params: {\n sort: sort === 'latest' ? 'desc' : 'asc',\n per_page: state.ui?.activityLogPerPage ?? 10,\n },\n });\n\n logger.log(`[updateActivityLogSort] Updated sort to ${sort}`);\n}\n\n/**\n * 처리로그 페이지당 항목 수를 변경합니다.\n *\n * @param action 액션 객체 (params.perPage 필요: 10 | 20 | 50)\n * @param _context 액션 컨텍스트\n */\nexport function updateActivityLogPerPageHandler(\n action: ActionWithParams,\n _context: ActionContext\n): void {\n const params = action.params || {};\n const perPage = params.perPage as 10 | 20 | 50;\n\n if (!perPage) {\n logger.warn('[updateActivityLogPerPage] Missing perPage param');\n return;\n }\n\n const G7Core = (window as any).G7Core;\n if (!G7Core?.state) {\n logger.warn('[updateActivityLogPerPage] G7Core.state API is not available');\n return;\n }\n\n const state = G7Core.state.getLocal() || {};\n\n G7Core.state.setLocal({\n ui: {\n ...state.ui,\n activityLogPerPage: perPage,\n },\n });\n\n // 데이터소스 새로고침\n G7Core.datasources?.refresh?.('activity_logs', {\n params: {\n sort: state.ui?.activityLogSort === 'oldest' ? 'asc' : 'desc',\n per_page: perPage,\n },\n });\n\n logger.log(`[updateActivityLogPerPage] Updated perPage to ${perPage}`);\n}\n\n/**\n * 기본 배송정책을 자동 설정합니다.\n *\n * - init_actions에서 호출 (생성 모드, condition: !route.itemCode)\n * - ui.useDefaultShippingPolicy가 true이고 is_default=true인 정책을 form.shipping_policy_id에 설정\n * - 이미 값이 설정된 경우(수정 모드) 건너뜀\n *\n * @param _action 액션 객체\n * @param _context 액션 컨텍스트\n */\nexport function setDefaultShippingPolicyHandler(\n _action: ActionWithParams,\n _context: ActionContext\n): void {\n const G7Core = (window as any).G7Core;\n if (!G7Core?.state) {\n logger.warn('[setDefaultShippingPolicy] G7Core.state API is not available');\n return;\n }\n\n const state = G7Core.state.getLocal() || {};\n\n // 이미 배송정책이 설정된 경우 (수정 모드) 건너뜀\n if (state.form?.shipping_policy_id) {\n logger.log('[setDefaultShippingPolicy] Shipping policy already set, skipping');\n return;\n }\n\n // '기본 배송정책 사용' 토글이 OFF이면 건너뜀\n // 기본값이 true이므로 명시적으로 false인 경우에만 건너뜀\n if (state.ui?.useDefaultShippingPolicy === false) {\n logger.log('[setDefaultShippingPolicy] useDefaultShippingPolicy is OFF, skipping');\n return;\n }\n\n // G7Core.dataSource를 통해 데이터소스 접근 (init_actions에서는 context.datasources가 비어있음)\n const dsData = G7Core.dataSource?.get?.('shipping_policies');\n const policies: ShippingPolicy[] = dsData?.data?.data ?? dsData?.data ?? [];\n\n if (policies.length === 0) {\n logger.log('[setDefaultShippingPolicy] No shipping policies found');\n return;\n }\n\n // is_default=true인 정책 찾기\n const defaultPolicy = policies.find((p: ShippingPolicy) => p.is_default === true);\n\n if (defaultPolicy) {\n G7Core.state.setLocal({\n form: {\n ...state.form,\n shipping_policy_id: defaultPolicy.id,\n },\n });\n logger.log(`[setDefaultShippingPolicy] Set default shipping policy: ${defaultPolicy.id} (${defaultPolicy.name})`);\n } else {\n logger.log('[setDefaultShippingPolicy] No default shipping policy found');\n }\n}\n\n/**\n * 기본 배송정책 사용 토글 핸들러\n *\n * - ON: is_default=true인 배송정책 자동 선택\n * - OFF: 배송정책 선택 해제 (null)\n *\n * @param action 액션 객체 (params.checked: boolean)\n * @param _context 액션 컨텍스트\n */\nexport function toggleDefaultShippingPolicyHandler(\n action: ActionWithParams,\n _context: ActionContext\n): void {\n const G7Core = (window as any).G7Core;\n if (!G7Core?.state) {\n logger.warn('[toggleDefaultShippingPolicy] G7Core.state API is not available');\n return;\n }\n\n const checked = action.params?.checked ?? false;\n const state = G7Core.state.getLocal() || {};\n\n // 토글 상태 업데이트\n const newUi = { ...state.ui, useDefaultShippingPolicy: checked };\n\n if (checked) {\n // ON: 기본 배송정책 선택\n // G7Core.dataSource를 통해 데이터소스 접근\n const dsData = G7Core.dataSource?.get?.('shipping_policies');\n const policies: ShippingPolicy[] = dsData?.data?.data ?? dsData?.data ?? [];\n const defaultPolicy = policies.find((p: ShippingPolicy) => p.is_default === true);\n\n G7Core.state.setLocal({\n ui: newUi,\n form: {\n ...state.form,\n shipping_policy_id: defaultPolicy?.id ?? null,\n },\n });\n\n if (defaultPolicy) {\n logger.log(`[toggleDefaultShippingPolicy] ON - Selected default policy: ${defaultPolicy.id}`);\n } else {\n logger.log('[toggleDefaultShippingPolicy] ON - No default policy found');\n }\n } else {\n // OFF: 기존 배송정책 유지 (토글 상태만 변경)\n G7Core.state.setLocal({\n ui: newUi,\n });\n logger.log('[toggleDefaultShippingPolicy] OFF - Keeping current shipping policy');\n }\n}\n\n/**\n * 라벨 날짜 프리셋을 설정합니다.\n *\n * @param action 액션 객체 (params.preset: '7d' | '14d' | '30d' | 'permanent')\n * @param _context 액션 컨텍스트\n */\nexport function setLabelDatePresetHandler(\n action: ActionWithParams,\n _context: ActionContext\n): void {\n const G7Core = (window as any).G7Core;\n if (!G7Core?.state) {\n logger.warn('[setLabelDatePreset] G7Core.state API is not available');\n return;\n }\n\n const preset = action.params?.preset as string;\n if (!preset) {\n logger.warn('[setLabelDatePreset] Missing preset param');\n return;\n }\n\n const today = new Date();\n const formatDate = (date: Date): string => {\n const year = date.getFullYear();\n const month = String(date.getMonth() + 1).padStart(2, '0');\n const day = String(date.getDate()).padStart(2, '0');\n return `${year}-${month}-${day}`;\n };\n\n const startDate = formatDate(today);\n let endDate: string | null = null;\n\n switch (preset) {\n case '7d': {\n const end = new Date(today);\n end.setDate(end.getDate() + 7);\n endDate = formatDate(end);\n break;\n }\n case '14d': {\n const end = new Date(today);\n end.setDate(end.getDate() + 14);\n endDate = formatDate(end);\n break;\n }\n case '30d': {\n const end = new Date(today);\n end.setDate(end.getDate() + 30);\n endDate = formatDate(end);\n break;\n }\n case 'permanent':\n endDate = null;\n break;\n default:\n logger.warn(`[setLabelDatePreset] Unknown preset: ${preset}`);\n return;\n }\n\n G7Core.state.setGlobal({\n 'labelFormData.start_date': startDate,\n 'labelFormData.end_date': endDate,\n });\n\n logger.log(`[setLabelDatePreset] Applied preset '${preset}': ${startDate} ~ ${endDate ?? 'permanent'}`);\n}\n","/**\n * 상품 라벨 핸들러\n *\n * 상품 폼에서 라벨 할당 관리를 위한 핸들러들입니다.\n * - toggleLabelAssignment: ChipCheckbox 클릭 시 라벨 할당/해제 토글\n * - saveLabelSettings: 라벨 설정 모달에서 name/color API 저장 + 기간 로컬 업데이트\n */\n\n/**\n * 커스텀 핸들러에 전달되는 액션 객체 인터페이스\n * ActionDispatcher는 (action, context) 형태로 핸들러를 호출합니다.\n */\ninterface ActionWithParams {\n handler: string;\n params?: Record;\n [key: string]: any;\n}\n\n/**\n * 액션 컨텍스트 인터페이스\n */\ninterface ActionContext {\n data?: any;\n state?: any;\n setState?: (updates: any) => void;\n}\n\n/**\n * 라벨 할당 데이터 인터페이스\n */\ninterface LabelAssignment {\n id?: number;\n label_id: number;\n started_at?: string | null;\n ended_at?: string | null;\n}\n\n/**\n * 라벨 할당 토글 핸들러\n *\n * ChipCheckbox 클릭 시 라벨을 할당하거나 해제합니다.\n * - 이미 할당됨 → 제거\n * - 미할당 → 추가 (started_at: null, ended_at: null)\n * - lastClickedLabelId를 설정하여 미리보기 영역에 반영\n *\n * @param action 액션 객체 (params.labelId 필수)\n * @param _context 액션 컨텍스트 (미사용)\n */\nexport const toggleLabelAssignmentHandler = (\n action: ActionWithParams,\n _context: ActionContext\n): void => {\n const G7Core = (window as any).G7Core;\n\n // v1.17.6: getLocal()이 템플릿 엔진 레벨에서 globalLocal + pendingState를 병합하므로\n // 핸들러에서는 단순히 getLocal()만 호출하면 됨\n const localState = G7Core?.state?.getLocal?.() || {};\n const formLabelAssignments = localState.form?.label_assignments ?? [];\n\n const labelId = Number(action.params?.labelId);\n if (!labelId) {\n return;\n }\n\n const currentAssignments = [...formLabelAssignments] as LabelAssignment[];\n const existingAssignment = currentAssignments.find(a => a.label_id === labelId);\n\n // A31: 이미 할당된 칩 클릭 = 기간 패널 전환만 (할당 해제 안 함).\n // 기간 패널이 lastClickedLabelId 단일 슬롯에 결합되어 있어, 칩 토글이 label_assignments 를\n // 건드리면 다른 라벨로 패널을 옮길 때 의도치 않게 할당이 풀렸다.\n // 할당 해제는 미리보기의 분리된 \"할당 해제\" 어포던스(modal_label_uncheck_confirm)로만 수행한다.\n // ChipCheckbox checked 는 label_assignments.some(...) 파생이므로, 핸들러가 건드리지 않으면 체크 유지.\n if (existingAssignment) {\n G7Core?.state?.setLocal?.({\n 'ui.lastClickedLabelId': labelId,\n });\n return;\n }\n\n // 미할당 → 추가 + 패널 전환\n const updatedAssignments: LabelAssignment[] = [...currentAssignments, {\n label_id: labelId,\n started_at: null,\n ended_at: null,\n }];\n\n G7Core?.state?.setLocal?.({\n 'form.label_assignments': updatedAssignments,\n 'ui.lastClickedLabelId': labelId,\n hasChanges: true,\n });\n};\n\n/**\n * 인라인 기간 업데이트 핸들러\n *\n * 미리보기 영역의 날짜 입력 필드 변경 시 호출됩니다.\n * _local.form.label_assignments의 해당 라벨 기간을 직접 업데이트합니다.\n *\n * @param action 액션 객체 (params.labelId, params.field, params.value 필수)\n * @param _context 액션 컨텍스트 (미사용)\n */\nexport const updateLabelPeriodInlineHandler = (\n action: ActionWithParams,\n _context: ActionContext\n): void => {\n const G7Core = (window as any).G7Core;\n\n // v1.17.6: getLocal()이 템플릿 엔진 레벨에서 병합 처리\n const localState = G7Core?.state?.getLocal?.() || {};\n const formLabelAssignments = localState.form?.label_assignments ?? [];\n\n const labelId = Number(action.params?.labelId);\n const field = action.params?.field as 'started_at' | 'ended_at';\n const value = action.params?.value as string | null;\n\n if (!labelId || !field) {\n return;\n }\n\n const currentAssignments = [...formLabelAssignments] as LabelAssignment[];\n const idx = currentAssignments.findIndex(a => a.label_id === labelId);\n\n if (idx < 0) {\n return;\n }\n\n currentAssignments[idx] = {\n ...currentAssignments[idx],\n [field]: value || null,\n };\n\n G7Core?.state?.setLocal?.({\n 'form.label_assignments': currentAssignments,\n hasChanges: true,\n });\n};\n\n/**\n * 인라인 기간 프리셋 핸들러\n *\n * 미리보기 영역의 프리셋 버튼 클릭 시 호출됩니다.\n * 현재 선택된 라벨(lastClickedLabelId)에 프리셋 기간을 적용합니다.\n *\n * @param action 액션 객체 (params.preset: '7d' | '14d' | '30d' | 'permanent')\n * @param _context 액션 컨텍스트 (미사용)\n */\nexport const setLabelDatePresetInlineHandler = (\n action: ActionWithParams,\n _context: ActionContext\n): void => {\n const G7Core = (window as any).G7Core;\n\n // v1.17.6: getLocal()이 템플릿 엔진 레벨에서 병합 처리\n const localState = G7Core?.state?.getLocal?.() || {};\n const formLabelAssignments = localState.form?.label_assignments ?? [];\n const labelId = localState.ui?.lastClickedLabelId as number;\n const preset = action.params?.preset as string;\n\n if (!labelId || !preset) {\n return;\n }\n\n const currentAssignments = [...formLabelAssignments] as LabelAssignment[];\n const idx = currentAssignments.findIndex(a => a.label_id === labelId);\n\n if (idx < 0) {\n return;\n }\n\n const today = new Date();\n const formatDate = (date: Date): string => {\n return date.toISOString().split('T')[0];\n };\n\n const startDate = formatDate(today);\n let endDate: string | null = null;\n\n switch (preset) {\n case '7d':\n endDate = formatDate(new Date(today.getTime() + 7 * 24 * 60 * 60 * 1000));\n break;\n case '14d':\n endDate = formatDate(new Date(today.getTime() + 14 * 24 * 60 * 60 * 1000));\n break;\n case '30d':\n endDate = formatDate(new Date(today.getTime() + 30 * 24 * 60 * 60 * 1000));\n break;\n case 'permanent':\n endDate = null;\n break;\n }\n\n currentAssignments[idx] = {\n ...currentAssignments[idx],\n started_at: startDate,\n ended_at: endDate,\n };\n\n G7Core?.state?.setLocal?.({\n 'form.label_assignments': currentAssignments,\n hasChanges: true,\n });\n};\n\n/**\n * 라벨 체크해제 확인 핸들러\n *\n * 기간이 설정된 라벨의 체크해제 확인 모달에서 \"해제\" 클릭 시 호출됩니다.\n * _global.labelToUncheckId 라벨을 부모 레이아웃의 label_assignments에서 제거합니다.\n *\n * v1.16.0부터 $parent 바인딩 컨텍스트를 사용하여 부모의 _local을 직접 수정합니다.\n * - 이전: _global.labelAssignmentsSnapshot 사용 (복사 후 필터링)\n * - 현재: G7Core.state.getParent()._local 직접 접근 + setParentLocal() 수정\n *\n * @param action 액션 객체\n * @param _context 액션 컨텍스트 (미사용)\n */\nexport const confirmUncheckLabelHandler = (\n action: ActionWithParams,\n _context: ActionContext\n): void => {\n const G7Core = (window as any).G7Core;\n const currentState = G7Core?.state?.get() || {};\n\n const labelId = currentState.labelToUncheckId as number;\n if (!labelId) {\n return;\n }\n\n // $parent 바인딩 컨텍스트를 통해 부모의 _local에서 직접 읽기\n const parentContext = G7Core?.state?.getParent?.();\n const parentLocal = parentContext?._local || {};\n const currentAssignments = [...(parentLocal.form?.label_assignments ?? [])] as LabelAssignment[];\n\n // 해당 라벨 필터링\n const updatedAssignments = currentAssignments.filter(a => a.label_id !== labelId);\n\n // 부모의 _local 직접 수정\n G7Core?.state?.setParentLocal?.({\n 'form.label_assignments': updatedAssignments,\n hasChanges: true,\n });\n\n // _global 정리 (labelToUncheckId만 사용, 날짜는 $parent에서 직접 조회)\n G7Core?.state?.setGlobal?.({\n labelToUncheckId: null,\n });\n\n // 모달 닫기\n G7Core?.modal?.close?.();\n};\n\n/**\n * 라벨 설정 저장 핸들러\n *\n * 모달에서 편집한 라벨 정보를 저장합니다.\n * - name/color → PUT API로 product_labels 원본 즉시 수정 (모든 상품에 반영)\n * - started_at/ended_at → 로컬 상태의 form.label_assignments 업데이트 (상품 저장 시 반영)\n * - 성공 시 product_labels 데이터소스 리프레시 + 토스트 + 모달 닫기\n *\n * @param action 액션 객체\n * @param _context 액션 컨텍스트 (미사용)\n */\n/**\n * 라벨 삭제 핸들러\n *\n * 라벨을 DB에서 완전히 삭제합니다.\n * - DELETE API 호출로 product_labels 테이블에서 삭제\n * - 성공 시: product_labels 데이터소스 리프레시, 로컬 label_assignments에서 제거, 모달 닫기\n *\n * @param action 액션 객체\n * @param _context 액션 컨텍스트 (미사용)\n */\nexport const deleteLabelHandler = async (\n action: ActionWithParams,\n _context: ActionContext\n): Promise => {\n const G7Core = (window as any).G7Core;\n const currentState = G7Core?.state?.get() || {};\n\n // v1.17.6: getLocal()이 템플릿 엔진 레벨에서 병합 처리\n const localState = G7Core?.state?.getLocal?.() || {};\n const formLabelAssignments = localState.form?.label_assignments ?? [];\n\n const labelToDeleteId = currentState.labelToDeleteId as number | null;\n if (!labelToDeleteId) {\n return;\n }\n\n G7Core?.state?.setGlobal?.({ isDeletingLabel: true });\n\n try {\n // 1. API 호출로 DB에서 삭제\n const response = await G7Core.api.delete(\n `/api/modules/sirsoft-ecommerce/admin/product-labels/${labelToDeleteId}`\n );\n\n if (!response?.success) {\n throw new Error(response?.message || 'Failed to delete label');\n }\n\n // 2. 로컬 상태에서 해당 라벨 제거\n const currentAssignments = [...formLabelAssignments] as LabelAssignment[];\n const updatedAssignments = currentAssignments.filter(a => a.label_id !== labelToDeleteId);\n\n G7Core?.state?.setLocal?.({\n 'form.label_assignments': updatedAssignments,\n 'ui.lastClickedLabelId': null,\n });\n\n // 3. 데이터소스 리프레시\n await G7Core?.dataSource?.refetch?.('product_labels');\n\n G7Core?.toast?.success?.(\n G7Core.t?.('sirsoft-ecommerce.admin.product.labels.delete_success') ?? 'Label deleted.'\n );\n G7Core?.modal?.close?.();\n } catch (error: any) {\n G7Core?.toast?.error?.(\n error?.response?.data?.message\n || error?.message\n || G7Core.t?.('sirsoft-ecommerce.admin.product.labels.delete_error')\n || 'Failed to delete label.'\n );\n } finally {\n G7Core?.state?.setGlobal?.({ isDeletingLabel: false, labelToDeleteId: null });\n }\n};\n\nexport const saveLabelSettingsHandler = async (\n action: ActionWithParams,\n _context: ActionContext\n): Promise => {\n const G7Core = (window as any).G7Core;\n const currentState = G7Core?.state?.get() || {};\n\n // v1.17.6: getLocal()이 템플릿 엔진 레벨에서 병합 처리\n const localState = G7Core?.state?.getLocal?.() || {};\n const formLabelAssignments = localState.form?.label_assignments ?? [];\n\n const editingLabelId = currentState.editingLabelId as number | null;\n const labelFormData = currentState.labelFormData as {\n name: { ko: string; en: string };\n color: string | null;\n started_at: string | null;\n ended_at: string | null;\n };\n\n // A31: 생성/수정 겸용 — name.ko 만 필수 (editingLabelId 유무로 분기)\n if (!labelFormData?.name?.ko) {\n return;\n }\n\n G7Core?.state?.setGlobal?.({ isSavingLabel: true });\n\n try {\n if (editingLabelId) {\n // 수정: 라벨 원본 업데이트 (name, color) - API 즉시 저장\n const response = await G7Core.api.put(\n `/api/modules/sirsoft-ecommerce/admin/product-labels/${editingLabelId}`,\n {\n name: labelFormData.name,\n color: labelFormData.color,\n }\n );\n\n if (!response?.success) {\n throw new Error(response?.message || 'Failed to update label');\n }\n\n // 로컬 상태에서 기간 업데이트 (상품 저장 시 반영)\n const currentAssignments = [...formLabelAssignments] as LabelAssignment[];\n const updatedAssignments = currentAssignments.map(a => {\n if (a.label_id === editingLabelId) {\n return {\n ...a,\n started_at: labelFormData.started_at || null,\n ended_at: labelFormData.ended_at || null,\n };\n }\n return a;\n });\n\n G7Core?.state?.setLocal?.({\n 'form.label_assignments': updatedAssignments,\n hasChanges: true,\n });\n } else {\n // 생성: POST 로 신규 라벨 생성 + 새 id 를 form.label_assignments 에 추가\n const response = await G7Core.api.post(\n '/api/modules/sirsoft-ecommerce/admin/product-labels',\n {\n name: labelFormData.name,\n color: labelFormData.color || '#6B7280',\n }\n );\n\n if (!response?.success) {\n throw new Error(response?.message || 'Failed to create label');\n }\n\n const newId = Number(response?.data?.id);\n if (newId) {\n const currentAssignments = [...formLabelAssignments] as LabelAssignment[];\n const updatedAssignments = [...currentAssignments, {\n label_id: newId,\n started_at: null,\n ended_at: null,\n }];\n\n G7Core?.state?.setLocal?.({\n 'form.label_assignments': updatedAssignments,\n 'ui.lastClickedLabelId': newId,\n hasChanges: true,\n });\n }\n }\n\n // 데이터소스 리프레시 (칩 표시에 변경/추가된 name/color 반영)\n await G7Core?.dataSource?.refetch?.('product_labels');\n\n G7Core?.toast?.success?.(\n editingLabelId\n ? (G7Core.t?.('sirsoft-ecommerce.admin.product.labels.save_success') ?? 'Label saved.')\n : (G7Core.t?.('sirsoft-ecommerce.admin.product.labels.create_success') ?? 'Label created.')\n );\n G7Core?.modal?.close?.();\n } catch (error: any) {\n G7Core?.toast?.error?.(\n error?.response?.data?.message\n || error?.message\n || G7Core.t?.('sirsoft-ecommerce.admin.product.labels.save_error')\n || 'Failed to save label.'\n );\n } finally {\n G7Core?.state?.setGlobal?.({ isSavingLabel: false });\n }\n};\n","/**\n * 상세설명 다국어 관련 핸들러\n *\n * 상품 등록/수정 화면에서 상세설명의 언어 탭 추가/제거 기능을 처리합니다.\n */\n\nimport type { ActionContext } from '../types';\n\n// Logger 설정 (G7Core 초기화 전에도 동작하도록 폴백 포함)\nconst logger = ((window as any).G7Core?.createLogger?.('Ecom:DescriptionLocale')) ?? {\n log: (...args: unknown[]) => console.log('[Ecom:DescriptionLocale]', ...args),\n warn: (...args: unknown[]) => console.warn('[Ecom:DescriptionLocale]', ...args),\n error: (...args: unknown[]) => console.error('[Ecom:DescriptionLocale]', ...args),\n};\n\ninterface ActionWithParams {\n handler: string;\n params?: Record;\n [key: string]: any;\n}\n\n/**\n * 설정에서 지원되는 로케일 목록을 가져옵니다.\n *\n * @returns 지원되는 로케일 배열\n */\nfunction getSupportedLocales(): string[] {\n // G7Core 또는 window.config에서 가져오기\n const G7Core = (window as any).G7Core;\n return G7Core?.config?.('app.supported_locales') ?? ['ko', 'en'];\n}\n\n/**\n * 상세설명 언어 탭을 제거합니다.\n *\n * - 기본 언어(ko)는 삭제 불가\n * - PC 및 모바일 상세설명 모두에서 제거\n *\n * @param action 액션 객체 (params.locale 필요)\n * @param _context 액션 컨텍스트\n */\nexport function removeDescriptionLocaleHandler(\n action: ActionWithParams,\n _context: ActionContext\n): void {\n const params = action.params || {};\n const locale = params.locale as string;\n\n if (!locale) {\n logger.warn('[removeDescriptionLocale] Missing locale param');\n return;\n }\n\n const G7Core = (window as any).G7Core;\n if (!G7Core?.state) {\n logger.warn('[removeDescriptionLocale] G7Core.state API is not available');\n return;\n }\n\n // 기본 언어 삭제 불가 — 사이트 설정의 기본 언어 또는 fallback 'ko' 사용\n const defaultLocale: string = G7Core?.state?.get?.('_global.settings.general.language')\n ?? G7Core?.config?.('app.fallback_locale')\n ?? 'ko';\n if (locale === defaultLocale) {\n G7Core.toast?.error?.(\n G7Core.t?.('sirsoft-ecommerce.admin.product.description_editor.messages.default_locale_cannot_delete')\n ?? 'Default language cannot be deleted.'\n );\n return;\n }\n\n const state = G7Core.state.getLocal() || {};\n const supportedLocales = getSupportedLocales();\n\n // 해당 언어의 상세설명 삭제\n const newDescription = { ...state.form?.description };\n delete newDescription[locale];\n\n const newDescriptionMobile = { ...state.form?.description_mobile };\n delete newDescriptionMobile[locale];\n\n // 활성 로케일 목록에서 제거\n const activeLocales = (state.ui?.descriptionActiveLocales ?? supportedLocales).filter(\n (l: string) => l !== locale\n );\n\n G7Core.state.setLocal({\n form: {\n ...state.form,\n description: newDescription,\n description_mobile: newDescriptionMobile,\n },\n ui: {\n ...state.ui,\n descriptionActiveLocales: activeLocales,\n descriptionLocale: activeLocales[0] ?? 'ko',\n },\n hasChanges: true,\n });\n\n G7Core.toast?.success?.(\n G7Core.t?.('sirsoft-ecommerce.admin.product.description_editor.messages.locale_removed', { locale: locale.toUpperCase() })\n ?? `${locale.toUpperCase()} language has been removed.`\n );\n logger.log(`[removeDescriptionLocale] Removed locale: ${locale}`);\n}\n\n/**\n * 상세설명 언어 추가 모달을 표시합니다.\n *\n * @param _action 액션 객체\n * @param _context 액션 컨텍스트\n */\nexport function showAddLocaleModalHandler(\n _action: ActionWithParams,\n _context: ActionContext\n): void {\n const G7Core = (window as any).G7Core;\n if (!G7Core?.state) {\n logger.warn('[showAddLocaleModal] G7Core.state API is not available');\n return;\n }\n\n const state = G7Core.state.getLocal() || {};\n\n G7Core.state.setLocal({\n ui: {\n ...state.ui,\n showAddLocaleModal: true,\n },\n });\n}\n\n/**\n * 상세설명에 새 언어를 추가합니다.\n *\n * - 이미 추가된 언어는 추가 불가\n * - 지원되지 않는 언어는 추가 불가\n *\n * @param action 액션 객체 (params.locale 필요)\n * @param _context 액션 컨텍스트\n */\nexport function addDescriptionLocaleHandler(\n action: ActionWithParams,\n _context: ActionContext\n): void {\n const params = action.params || {};\n const locale = params.locale as string;\n\n if (!locale) {\n logger.warn('[addDescriptionLocale] Missing locale param');\n return;\n }\n\n const G7Core = (window as any).G7Core;\n if (!G7Core?.state) {\n logger.warn('[addDescriptionLocale] G7Core.state API is not available');\n return;\n }\n\n const state = G7Core.state.getLocal() || {};\n const supportedLocales = getSupportedLocales();\n const activeLocales: string[] = state.ui?.descriptionActiveLocales ?? ['ko'];\n\n // 이미 추가된 언어인지 확인\n if (activeLocales.includes(locale)) {\n G7Core.toast?.warning?.(\n G7Core.t?.('sirsoft-ecommerce.admin.product.description_editor.messages.locale_already_added')\n ?? 'This language is already added.'\n );\n return;\n }\n\n // 지원되는 언어인지 확인\n if (!supportedLocales.includes(locale)) {\n G7Core.toast?.error?.(\n G7Core.t?.('sirsoft-ecommerce.admin.product.description_editor.messages.locale_unsupported')\n ?? 'Unsupported language.'\n );\n return;\n }\n\n G7Core.state.setLocal({\n form: {\n ...state.form,\n description: {\n ...state.form?.description,\n [locale]: '',\n },\n description_mobile: {\n ...state.form?.description_mobile,\n [locale]: '',\n },\n },\n ui: {\n ...state.ui,\n descriptionActiveLocales: [...activeLocales, locale],\n descriptionLocale: locale,\n showAddLocaleModal: false,\n },\n hasChanges: true,\n });\n\n G7Core.toast?.success?.(\n G7Core.t?.('sirsoft-ecommerce.admin.product.description_editor.messages.locale_added', { locale: locale.toUpperCase() })\n ?? `${locale.toUpperCase()} language has been added.`\n );\n logger.log(`[addDescriptionLocale] Added locale: ${locale}`);\n}\n","/**\n * 통합 일괄 업데이트 핸들러\n *\n * 상품/옵션의 일괄 변경 + 개별 인라인 수정을 동시 처리합니다.\n * - 상품 체크 시: 상품 API 호출 (해당 상품의 옵션 자동 포함)\n * - 상품 미체크 + 옵션만 체크: 옵션 API 호출\n *\n * \"일괄 적용\" 버튼 → 확인 모달 → 이 핸들러 호출\n */\n\nimport type { ActionContext } from '../types';\n\n// Logger 설정 (G7Core 초기화 전에도 동작하도록 폴백 포함)\nconst logger = ((window as any).G7Core?.createLogger?.('Ecom:BulkUpdate')) ?? {\n log: (...args: unknown[]) => console.log('[Ecom:BulkUpdate]', ...args),\n warn: (...args: unknown[]) => console.warn('[Ecom:BulkUpdate]', ...args),\n error: (...args: unknown[]) => console.error('[Ecom:BulkUpdate]', ...args),\n};\n\n/**\n * 커스텀 핸들러에 전달되는 액션 객체 인터페이스\n */\ninterface ActionWithParams {\n handler: string;\n params?: Record;\n target?: string;\n onSuccess?: any;\n onError?: any;\n [key: string]: any;\n}\n\n/**\n * 일괄 변경 조건 인터페이스\n */\ninterface BulkChangeCondition {\n method: 'set' | 'add' | 'subtract' | 'percent';\n value: number;\n}\n\n/**\n * 상품 일괄 변경 조건\n */\ninterface ProductBulkChanges {\n sales_status?: string;\n display_status?: string;\n}\n\n/**\n * 옵션 일괄 변경 조건\n */\ninterface OptionBulkChanges {\n price_adjustment?: BulkChangeCondition;\n stock_quantity?: BulkChangeCondition;\n}\n\n/**\n * 상품 개별 수정 아이템\n */\ninterface ProductItem {\n id: number;\n name?: Record;\n list_price?: number;\n selling_price?: number;\n sales_status?: string;\n display_status?: string;\n}\n\n/**\n * 옵션 개별 수정 아이템\n */\ninterface OptionItem {\n product_id: number;\n option_id: number;\n option_name?: string | Record;\n sku?: string;\n list_price?: number;\n price_adjustment?: number;\n stock_quantity?: number;\n safe_stock_quantity?: number;\n is_default?: boolean;\n is_active?: boolean;\n}\n\n/**\n * 확인 데이터 인터페이스\n */\ninterface BulkConfirmData {\n products: Array<{\n id: number;\n name: string;\n changes: string;\n }>;\n options: Array<{\n productId: number;\n optionId: number;\n productName: string;\n optionName: string;\n changes: string;\n }>;\n summary: {\n productCount: number;\n optionCount: number;\n hasBulkChanges: boolean;\n hasInlineChanges: boolean;\n };\n}\n\n/**\n * G7Core 인터페이스 (타입 안전성)\n */\ninterface G7CoreInterface {\n dataSource: {\n get: (id: string) => any;\n set: (id: string, data: any) => void;\n refetch: (id: string) => Promise;\n };\n state: {\n get: () => Record;\n set: (updates: Record) => void;\n getLocal: () => Record;\n setLocal: (updates: Record) => void;\n };\n api: {\n patch: (url: string, data: any) => Promise;\n };\n toast: {\n success: (message: string) => void;\n warning: (message: string) => void;\n error: (message: string) => void;\n };\n t: (key: string, params?: Record) => string;\n modal: {\n close: (id?: string) => void;\n };\n}\n\n/**\n * 일괄 업데이트 실패 처리\n *\n * 서버가 내려준 실제 메시지/검증 에러를 토스트와 모달에 그대로 노출한다.\n * 기존에는 catch 에서 error 를 버리고 고정 t 문자열만 토스트로 띄워\n * \"왜 실패했는지\" 를 확인할 수 없었다 (모달에도 상세 미표시).\n *\n * - 토스트: 서버 message → error.message → 폴백 t 문자열 순으로 정확한 문구 표시\n * - 모달: validation errors 를 평탄화하여 `_global.bulkUpdateErrors` 에 저장하고\n * 모달을 닫지 않아 사용자가 어떤 필드가 거부됐는지 확인 가능\n *\n * @param G7Core G7Core 인스턴스\n * @param error catch 로 전달된 에러 (axios 에러: error.response.data 에 본문)\n * @param fallbackKey 서버 메시지가 없을 때 사용할 다국어 키\n * @return void\n */\nfunction handleBulkUpdateError(G7Core: G7CoreInterface, error: any, fallbackKey: string): void {\n const data = error?.response?.data ?? {};\n const serverMessage: string = data?.message || error?.message || '';\n\n // validation errors 평탄화 (필드별 첫 메시지 목록)\n const errorsObj = data?.errors;\n const detailMessages: string[] = errorsObj && typeof errorsObj === 'object'\n ? Object.values(errorsObj).map((msgs: any) => (Array.isArray(msgs) ? msgs[0] : String(msgs)))\n : [];\n\n // 토스트: 정확한 서버 메시지 우선\n const toastMessage = serverMessage || G7Core.t(fallbackKey);\n G7Core.toast.error(toastMessage);\n\n // 모달: 상세 에러를 노출하고 닫지 않음\n G7Core.state.set({\n bulkUpdateErrors: detailMessages.length > 0 ? detailMessages : (toastMessage ? [toastMessage] : []),\n });\n}\n\n/**\n * 다국어 객체에서 로컬라이즈된 문자열 추출\n * option_name, product name 등 {en: \"...\", ko: \"...\"} 형태의 객체 처리\n */\nfunction localizeValue(value: any): string {\n if (typeof value === 'string') return value;\n if (value && typeof value === 'object') {\n return value.ko || value.en || JSON.stringify(value);\n }\n return String(value ?? '');\n}\n\n/**\n * 판매/전시 상태 enum 값을 다국어 라벨로 변환\n *\n * 일괄 변경 확인 모달에서 raw enum 값(on_sale, visible 등)이 그대로 노출되던 결함을 해결한다.\n * 드롭다운 옵션과 동일한 SSoT(`enums.{sales_status,display_status}.*`)를 참조하여\n * 표시 라벨을 통일한다. 미정의 키는 원본 값으로 폴백한다.\n *\n * @param G7Core G7Core 인스턴스\n * @param type 상태 종류 ('sales_status' | 'display_status')\n * @param value 변환할 enum 값\n * @return 다국어 라벨 (미정의 시 원본 값)\n */\nfunction localizeStatus(\n G7Core: G7CoreInterface,\n type: 'sales_status' | 'display_status',\n value: string\n): string {\n if (!value) return String(value ?? '');\n const label = G7Core.t(`sirsoft-ecommerce.enums.${type}.${value}`);\n // t()가 키를 찾지 못하면 빈 문자열을 반환하므로 원본 값으로 폴백\n return label || value;\n}\n\n/**\n * G7Core 가져오기\n */\nfunction getG7Core(): G7CoreInterface | null {\n const G7Core = (window as any).G7Core;\n if (!G7Core) {\n logger.error('[bulkUpdate] G7Core is not available');\n return null;\n }\n return G7Core;\n}\n\n/**\n * 통합 일괄 업데이트 핸들러\n *\n * 상품 체크 여부에 따라 API 호출 결정:\n * - selectedItems.length > 0 → 상품 API 호출 (옵션 자동 포함)\n * - selectedItems.length === 0 && selectedOptionIds.length > 0 → 옵션 API 호출\n */\nexport function bulkUpdateHandler(action: ActionWithParams, context: ActionContext): void {\n const G7Core = getG7Core();\n if (!G7Core) return;\n\n const localState = G7Core.state.getLocal() || {};\n const globalState = G7Core.state.get() || {};\n\n // 모달에서 호출 시 _local 대신 _global.bulkSelectedItems 사용\n // (일괄 적용 버튼 클릭 시 setState로 _global에 복사됨)\n const selectedItems: number[] = globalState.bulkSelectedItems || localState.selectedItems || [];\n const selectedOptionIds: string[] = globalState.bulkSelectedOptionIds || localState.selectedOptionIds || [];\n\n logger.log('[bulkUpdate] globalState.bulkSelectedItems:', globalState.bulkSelectedItems);\n logger.log('[bulkUpdate] localState.selectedItems:', localState.selectedItems);\n logger.log('[bulkUpdate] Final selectedItems:', selectedItems);\n\n // API 호출 결정: 상품 체크가 우선\n if (selectedItems.length > 0) {\n // 상품이 체크된 경우 → 상품 API 호출 (옵션 자동 포함)\n bulkUpdateProductsInternal(G7Core, globalState, localState, selectedItems);\n } else if (selectedOptionIds.length > 0) {\n // 상품 미체크 + 옵션만 체크된 경우 → 옵션 API 호출\n bulkUpdateOptionsInternal(G7Core, globalState, localState, selectedOptionIds);\n } else {\n // 아무것도 체크 안 됨\n G7Core.toast.warning(G7Core.t('sirsoft-ecommerce.admin.product.bulk.no_selection'));\n }\n}\n\n/**\n * 상품 일괄 업데이트 내부 함수\n * 상품과 해당 옵션을 함께 처리\n */\nfunction bulkUpdateProductsInternal(\n G7Core: G7CoreInterface,\n globalState: Record,\n localState: Record,\n selectedIds: number[]\n): void {\n const dataSource = G7Core.dataSource.get('products');\n const products = dataSource?.data?.data || [];\n\n // 1. 상품 일괄 변경 조건 수집\n const productBulkChanges: ProductBulkChanges = {};\n if (globalState.bulkSalesStatus) {\n productBulkChanges.sales_status = globalState.bulkSalesStatus;\n }\n if (globalState.bulkDisplayStatus) {\n productBulkChanges.display_status = globalState.bulkDisplayStatus;\n }\n\n // 2. 옵션 일괄 변경 조건 수집\n const optionBulkChanges: OptionBulkChanges = {};\n // bulkPriceCondition은 표시용 문자열(\"+100원\")이므로, bulkPriceMethod/Value/Unit로 객체 생성\n if (globalState.bulkPriceMethod && globalState.bulkPriceValue !== undefined && globalState.bulkPriceValue !== null && globalState.bulkPriceValue !== '') {\n const priceMethodMap: Record = {\n 'fixed': 'set',\n 'increase': 'add',\n 'decrease': 'subtract',\n };\n // percent 단위인 경우 method를 'percent'로, 감소 시 음수 값 사용\n if (globalState.bulkPriceUnit === 'percent') {\n optionBulkChanges.price_adjustment = {\n method: 'percent',\n value: globalState.bulkPriceMethod === 'decrease'\n ? -(Number(globalState.bulkPriceValue) || 0)\n : Number(globalState.bulkPriceValue) || 0,\n };\n } else {\n optionBulkChanges.price_adjustment = {\n method: priceMethodMap[globalState.bulkPriceMethod] || 'add',\n value: Number(globalState.bulkPriceValue) || 0,\n };\n }\n }\n // bulkStockCondition은 표시용 문자열(\"+10개\")이므로, bulkStockMethod와 bulkStockValue로 객체 생성\n if (globalState.bulkStockMethod && globalState.bulkStockValue !== undefined && globalState.bulkStockValue !== null && globalState.bulkStockValue !== '') {\n const stockMethodMap: Record = {\n 'set': 'set',\n 'increase': 'add',\n 'decrease': 'subtract',\n };\n optionBulkChanges.stock_quantity = {\n method: stockMethodMap[globalState.bulkStockMethod] || 'add',\n value: Number(globalState.bulkStockValue) || 0,\n };\n }\n\n // 3. 개별 인라인 수정 수집 (상품 + 옵션 모두)\n // modifiedProductIds, modifiedOptionIds, modifiedFields를 함께 확인\n const modifiedProductIds: number[] = localState.modifiedProductIds || [];\n const modifiedOptionIds: string[] = localState.modifiedOptionIds || [];\n const modifiedProductFields: Record = localState.modifiedProductFields || {};\n const modifiedOptionFieldsMap: Record = localState.modifiedOptionFields || {};\n\n // 개별 체크된 옵션 (상품이 체크되지 않은 옵션들)\n const selectedOptionIds: string[] = globalState.bulkSelectedOptionIds || localState.selectedOptionIds || [];\n const selectedProductIdSet = new Set(selectedIds);\n\n const productItems: ProductItem[] = [];\n const optionItems: OptionItem[] = [];\n\n /**\n * 옵션 인라인 수정 데이터 수집 헬퍼 (실제 수정된 필드만 포함)\n */\n function collectOptionItem(p: any, opt: any, optionKey: string): void {\n const isOptionModified = opt._modified || modifiedOptionIds.includes(optionKey);\n if (isOptionModified) {\n const item: OptionItem = { product_id: p.id, option_id: opt.id };\n const optModifiedFields = modifiedOptionFieldsMap[optionKey] || [];\n\n // 실제 수정된 필드만 적용 (bulk_changes에 없는 것만)\n if (!optionBulkChanges.price_adjustment && optModifiedFields.includes('price_adjustment')) {\n item.price_adjustment = opt.price_adjustment;\n }\n if (!optionBulkChanges.price_adjustment && optModifiedFields.includes('selling_price')) {\n // selling_price 변경 시 price_adjustment도 재계산되어 있으므로 함께 전송\n item.price_adjustment = opt.price_adjustment;\n }\n if (!optionBulkChanges.stock_quantity && optModifiedFields.includes('stock_quantity')) {\n item.stock_quantity = opt.stock_quantity;\n }\n if (optModifiedFields.includes('option_name')) item.option_name = opt.option_name;\n if (optModifiedFields.includes('sku')) item.sku = opt.sku;\n if (optModifiedFields.includes('list_price')) item.list_price = opt.list_price;\n if (optModifiedFields.includes('safe_stock_quantity')) item.safe_stock_quantity = opt.safe_stock_quantity;\n if (optModifiedFields.includes('is_default')) item.is_default = opt.is_default;\n if (optModifiedFields.includes('is_active')) item.is_active = opt.is_active;\n\n // 실제 변경 사항이 있는 경우에만 추가\n if (Object.keys(item).length > 2) {\n optionItems.push(item);\n }\n }\n }\n\n products\n .filter((p: any) => selectedIds.includes(p.id))\n .forEach((p: any) => {\n // 상품 인라인 수정 (_modified 플래그 또는 modifiedProductIds 배열 확인)\n const isProductModified = p._modified || modifiedProductIds.includes(p.id);\n if (isProductModified) {\n const item: ProductItem = { id: p.id };\n const productKey = String(p.id);\n const pModifiedFields = modifiedProductFields[productKey] || [];\n\n // 실제 수정된 필드만 적용 (bulk_changes에 없는 것만)\n if (!productBulkChanges.sales_status && pModifiedFields.includes('sales_status')) {\n item.sales_status = p.sales_status;\n }\n if (!productBulkChanges.display_status && pModifiedFields.includes('display_status')) {\n item.display_status = p.display_status;\n }\n if (pModifiedFields.includes('name')) item.name = p.name;\n if (pModifiedFields.includes('list_price')) item.list_price = p.list_price;\n if (pModifiedFields.includes('selling_price')) item.selling_price = p.selling_price;\n\n // 실제 변경 사항이 있는 경우에만 추가\n if (Object.keys(item).length > 1) {\n productItems.push(item);\n }\n }\n\n // 해당 상품의 모든 옵션 인라인 수정 (상품 체크 시 옵션 자동 포함)\n (p.options || []).forEach((opt: any) => {\n const optionKey = `${p.id}-${opt.id}`;\n collectOptionItem(p, opt, optionKey);\n });\n });\n\n // 4. 체크된 상품에 속하지 않은 개별 체크된 옵션 처리\n if (selectedOptionIds.length > 0) {\n products.forEach((p: any) => {\n // 이미 체크된 상품의 옵션은 위에서 처리됨\n if (selectedProductIdSet.has(p.id)) {\n return;\n }\n\n (p.options || []).forEach((opt: any) => {\n const optionKey = `${p.id}-${opt.id}`;\n // 개별 체크된 옵션만 처리\n if (selectedOptionIds.includes(optionKey)) {\n collectOptionItem(p, opt, optionKey);\n }\n });\n });\n }\n\n // 4. 상품 API 호출 (옵션 데이터 포함)\n const payload: Record = {\n ids: selectedIds,\n };\n\n if (Object.keys(productBulkChanges).length > 0) {\n payload.bulk_changes = productBulkChanges;\n }\n if (productItems.length > 0) {\n payload.items = productItems;\n }\n if (Object.keys(optionBulkChanges).length > 0) {\n payload.option_bulk_changes = optionBulkChanges;\n }\n if (optionItems.length > 0) {\n payload.option_items = optionItems;\n }\n\n // 처리 중 상태 표시\n // 재시도 시 이전 실패 상세를 초기화하여 stale 에러가 모달에 남지 않도록 한다\n G7Core.state.set({ isProcessing: true, bulkUpdateErrors: [] });\n\n G7Core.api.patch('/api/modules/sirsoft-ecommerce/admin/products/bulk-update', payload)\n .then((response: any) => {\n G7Core.toast.success(\n G7Core.t('sirsoft-ecommerce.admin.product.bulk.update_success', {\n products: response?.data?.products_updated || 0,\n options: response?.data?.options_updated || 0,\n })\n );\n // 상태 초기화\n resetBulkState(G7Core);\n // DataSource 리프레시\n G7Core.dataSource.refetch('products');\n // 모달 닫기\n G7Core.modal.close();\n })\n .catch((error: any) => {\n logger.error('[bulkUpdateProducts] Error:', error);\n handleBulkUpdateError(G7Core, error, 'sirsoft-ecommerce.admin.product.bulk.update_error');\n })\n .finally(() => {\n G7Core.state.set({ isProcessing: false });\n });\n}\n\n/**\n * 옵션 일괄 업데이트 내부 함수\n * 특정 옵션만 개별 처리 (상품 미체크 시)\n */\nfunction bulkUpdateOptionsInternal(\n G7Core: G7CoreInterface,\n globalState: Record,\n localState: Record,\n selectedOptionIds: string[]\n): void {\n const dataSource = G7Core.dataSource.get('products');\n const products = dataSource?.data?.data || [];\n\n // 1. 일괄 변경 조건 수집\n const bulkChanges: OptionBulkChanges = {};\n // bulkPriceCondition은 표시용 문자열(\"+100원\")이므로, bulkPriceMethod/Value/Unit로 객체 생성\n if (globalState.bulkPriceMethod && globalState.bulkPriceValue !== undefined && globalState.bulkPriceValue !== null && globalState.bulkPriceValue !== '') {\n const priceMethodMap: Record = {\n 'fixed': 'set',\n 'increase': 'add',\n 'decrease': 'subtract',\n };\n // percent 단위인 경우 method를 'percent'로, 감소 시 음수 값 사용\n if (globalState.bulkPriceUnit === 'percent') {\n bulkChanges.price_adjustment = {\n method: 'percent',\n value: globalState.bulkPriceMethod === 'decrease'\n ? -(Number(globalState.bulkPriceValue) || 0)\n : Number(globalState.bulkPriceValue) || 0,\n };\n } else {\n bulkChanges.price_adjustment = {\n method: priceMethodMap[globalState.bulkPriceMethod] || 'add',\n value: Number(globalState.bulkPriceValue) || 0,\n };\n }\n }\n // bulkStockCondition은 표시용 문자열(\"+10개\")이므로, bulkStockMethod와 bulkStockValue로 객체 생성\n if (globalState.bulkStockMethod && globalState.bulkStockValue !== undefined && globalState.bulkStockValue !== null && globalState.bulkStockValue !== '') {\n const stockMethodMap: Record = {\n 'set': 'set',\n 'increase': 'add',\n 'decrease': 'subtract',\n };\n bulkChanges.stock_quantity = {\n method: stockMethodMap[globalState.bulkStockMethod] || 'add',\n value: Number(globalState.bulkStockValue) || 0,\n };\n }\n\n // 2. 개별 인라인 수정 수집 (실제 수정된 필드만)\n const modifiedOptionFieldsMap: Record = localState.modifiedOptionFields || {};\n const modifiedOptionIds: string[] = localState.modifiedOptionIds || [];\n const items: OptionItem[] = [];\n products.forEach((product: any) => {\n (product.options || []).forEach((opt: any) => {\n const optionKey = `${product.id}-${opt.id}`;\n const isOptionModified = opt._modified || modifiedOptionIds.includes(optionKey);\n if (selectedOptionIds.includes(optionKey) && isOptionModified) {\n const item: OptionItem = { product_id: product.id, option_id: opt.id };\n const optModifiedFields = modifiedOptionFieldsMap[optionKey] || [];\n\n // 실제 수정된 필드만 적용 (bulk_changes에 없는 것만)\n if (!bulkChanges.price_adjustment && optModifiedFields.includes('price_adjustment')) {\n item.price_adjustment = opt.price_adjustment;\n }\n if (!bulkChanges.price_adjustment && optModifiedFields.includes('selling_price')) {\n item.price_adjustment = opt.price_adjustment;\n }\n if (!bulkChanges.stock_quantity && optModifiedFields.includes('stock_quantity')) {\n item.stock_quantity = opt.stock_quantity;\n }\n if (optModifiedFields.includes('option_name')) item.option_name = opt.option_name;\n if (optModifiedFields.includes('sku')) item.sku = opt.sku;\n if (optModifiedFields.includes('list_price')) item.list_price = opt.list_price;\n if (optModifiedFields.includes('safe_stock_quantity')) item.safe_stock_quantity = opt.safe_stock_quantity;\n if (optModifiedFields.includes('is_default')) item.is_default = opt.is_default;\n if (optModifiedFields.includes('is_active')) item.is_active = opt.is_active;\n\n // 실제 변경 사항이 있는 경우에만 추가\n if (Object.keys(item).length > 2) {\n items.push(item);\n }\n }\n });\n });\n\n // 3. 옵션 API 호출\n const payload: Record = {\n ids: selectedOptionIds,\n };\n\n if (Object.keys(bulkChanges).length > 0) {\n payload.bulk_changes = bulkChanges;\n }\n if (items.length > 0) {\n payload.items = items;\n }\n\n // 처리 중 상태 표시\n // 재시도 시 이전 실패 상세를 초기화하여 stale 에러가 모달에 남지 않도록 한다\n G7Core.state.set({ isProcessing: true, bulkUpdateErrors: [] });\n\n G7Core.api.patch('/api/modules/sirsoft-ecommerce/admin/options/bulk-update', payload)\n .then((response: any) => {\n G7Core.toast.success(\n G7Core.t('sirsoft-ecommerce.admin.product.bulk.option_update_success', {\n count: response?.data?.options_updated || 0,\n })\n );\n // 상태 초기화\n resetBulkState(G7Core);\n // DataSource 리프레시\n G7Core.dataSource.refetch('products');\n // 모달 닫기\n G7Core.modal.close();\n })\n .catch((error: any) => {\n logger.error('[bulkUpdateOptions] Error:', error);\n handleBulkUpdateError(G7Core, error, 'sirsoft-ecommerce.admin.product.bulk.option_update_error');\n })\n .finally(() => {\n G7Core.state.set({ isProcessing: false });\n });\n}\n\n/**\n * 일괄 변경 상태 초기화\n */\nfunction resetBulkState(G7Core: G7CoreInterface): void {\n // 로컬 상태 초기화\n G7Core.state.setLocal({\n modifiedProductIds: [],\n modifiedOptionIds: [],\n modifiedProductFields: {},\n modifiedOptionFields: {},\n selectedItems: [],\n selectedOptionIds: [],\n });\n\n // 글로벌 상태 초기화\n G7Core.state.set({\n bulkSalesStatus: null,\n bulkDisplayStatus: null,\n bulkPriceCondition: null,\n bulkStockCondition: null,\n bulkConfirmData: null,\n bulkUpdateErrors: [],\n });\n}\n\n/**\n * 확인 모달에 표시할 변경 예정 데이터 빌드 핸들러\n *\n * DataSource에서 체크된 항목 중 변경 사항 추출,\n * 상단 일괄 변경 설정 + 인라인 수정 사항 병합하여 요약 생성\n */\nexport function buildConfirmDataHandler(action: ActionWithParams, context: ActionContext): void {\n const G7Core = getG7Core();\n if (!G7Core) return;\n\n const localState = G7Core.state.getLocal() || {};\n const globalState = G7Core.state.get() || {};\n const dataSource = G7Core.dataSource.get('products');\n const products = dataSource?.data?.data || [];\n\n // 단일 SSoT: 실제 API 호출(bulkUpdateHandler)과 동일하게 _global 우선으로 선택 소스를 읽는다.\n // 적용 sequence 가 setState(target:global) 로 _local.selected* → _global.bulkSelected* 복사 후\n // buildConfirmData 를 호출하므로, _local 만 읽으면 전체선택 경로에서 빈 모달이 떠 표시↔동작이 어긋났다.\n const selectedItems: number[] = globalState.bulkSelectedItems || localState.selectedItems || [];\n const selectedOptionIds: string[] = globalState.bulkSelectedOptionIds || localState.selectedOptionIds || [];\n\n const confirmData: BulkConfirmData = {\n products: [],\n options: [],\n summary: {\n productCount: 0,\n optionCount: 0,\n hasBulkChanges: false,\n hasInlineChanges: false,\n },\n };\n\n // 일괄 변경 조건 확인\n const hasBulkChanges = !!(\n globalState.bulkSalesStatus ||\n globalState.bulkDisplayStatus ||\n globalState.bulkPriceCondition ||\n globalState.bulkStockCondition\n );\n confirmData.summary.hasBulkChanges = hasBulkChanges;\n\n // modifiedProductIds, modifiedOptionIds, modifiedFields 확인 (인라인 수정 여부)\n const modifiedProductIds: number[] = localState.modifiedProductIds || [];\n const modifiedOptionIds: string[] = localState.modifiedOptionIds || [];\n const modifiedProductFields: Record = localState.modifiedProductFields || {};\n const modifiedOptionFieldsMap: Record = localState.modifiedOptionFields || {};\n\n // 상품별 변경 사항 수집\n // 체크된 상품의 ID Set (체크된 상품의 옵션은 별도로 처리하지 않음)\n const selectedProductIdSet = new Set(selectedItems);\n\n /**\n * 옵션 인라인 변경사항 수집 (중복 코드 제거를 위한 헬퍼)\n */\n function collectOptionChanges(opt: any, optionKey: string, isOptionModified: boolean): string[] {\n const optChanges: string[] = [];\n const optModifiedFields = modifiedOptionFieldsMap[optionKey] || [];\n\n // 옵션 일괄 변경 표시\n // bulkPriceCondition / bulkStockCondition 은 레이아웃이 만든 표시용 문자열(\"+1000원\", \"+10개\")이다.\n // 객체(.method/.value)로 접근하면 \"재고: undefined undefined\" 가 되므로 문자열을 그대로 사용 (이슈 #421)\n if (globalState.bulkPriceCondition) {\n optChanges.push(\n G7Core.t('sirsoft-ecommerce.admin.product.messages.bulk_summary_price_adjustment_inline', {\n value: globalState.bulkPriceCondition,\n })\n || `Price adjustment: ${globalState.bulkPriceCondition}`\n );\n }\n if (globalState.bulkStockCondition) {\n optChanges.push(\n G7Core.t('sirsoft-ecommerce.admin.product.messages.bulk_summary_stock_inline', {\n value: globalState.bulkStockCondition,\n })\n || `Stock: ${globalState.bulkStockCondition}`\n );\n }\n\n // 옵션 인라인 수정 표시 (실제 수정된 필드만 보고)\n if (isOptionModified) {\n if (!globalState.bulkPriceCondition && optModifiedFields.includes('price_adjustment')) {\n const priceValue = opt.price_adjustment?.toLocaleString?.() ?? opt.price_adjustment;\n optChanges.push(\n G7Core.t('sirsoft-ecommerce.admin.product.messages.bulk_summary_price_adjustment_inline', {\n value: priceValue,\n })\n || `Price adjustment: ${priceValue}`\n );\n }\n if (!globalState.bulkPriceCondition && optModifiedFields.includes('selling_price')) {\n optChanges.push(\n G7Core.t('sirsoft-ecommerce.admin.product.messages.bulk_summary_selling_price', {\n price: opt.selling_price?.toLocaleString?.() ?? opt.selling_price,\n })\n || `Selling price: ${opt.selling_price}`\n );\n }\n if (!globalState.bulkStockCondition && optModifiedFields.includes('stock_quantity')) {\n optChanges.push(\n G7Core.t('sirsoft-ecommerce.admin.product.messages.bulk_summary_stock_inline', {\n value: opt.stock_quantity,\n })\n || `Stock: ${opt.stock_quantity}`\n );\n }\n if (optModifiedFields.includes('option_name')) {\n optChanges.push(\n G7Core.t('sirsoft-ecommerce.admin.product.messages.bulk_summary_option_name', {\n name: localizeValue(opt.option_name),\n })\n || `Option name: ${localizeValue(opt.option_name)}`\n );\n }\n if (optModifiedFields.includes('sku')) {\n optChanges.push(\n G7Core.t('sirsoft-ecommerce.admin.product.messages.bulk_summary_sku', { sku: opt.sku })\n || `SKU: ${opt.sku}`\n );\n }\n if (optModifiedFields.includes('list_price')) {\n optChanges.push(\n G7Core.t('sirsoft-ecommerce.admin.product.messages.bulk_summary_list_price', {\n price: opt.list_price?.toLocaleString?.() ?? opt.list_price,\n })\n || `List price: ${opt.list_price}`\n );\n }\n if (optModifiedFields.includes('safe_stock_quantity')) {\n optChanges.push(\n G7Core.t('sirsoft-ecommerce.admin.product.messages.bulk_summary_safe_stock', {\n value: opt.safe_stock_quantity,\n })\n || `Safe stock: ${opt.safe_stock_quantity}`\n );\n }\n if (optModifiedFields.includes('is_active')) {\n optChanges.push(\n G7Core.t('sirsoft-ecommerce.admin.product.messages.bulk_summary_is_active', {\n value: opt.is_active ? 'ON' : 'OFF',\n })\n || `Active: ${opt.is_active ? 'ON' : 'OFF'}`\n );\n }\n confirmData.summary.hasInlineChanges = true;\n }\n\n return optChanges;\n }\n\n if (selectedItems.length > 0) {\n products\n .filter((p: any) => selectedItems.includes(p.id))\n .forEach((p: any) => {\n const changes: string[] = [];\n const isProductModified = p._modified || modifiedProductIds.includes(p.id);\n const productKey = String(p.id);\n const pModifiedFields = modifiedProductFields[productKey] || [];\n\n // 일괄 변경 표시\n if (globalState.bulkSalesStatus) {\n const salesLabel = localizeStatus(G7Core, 'sales_status', globalState.bulkSalesStatus);\n changes.push(\n G7Core.t('sirsoft-ecommerce.admin.product.messages.bulk_summary_sales_status', { status: salesLabel })\n || `Sales status: ${salesLabel}`\n );\n }\n if (globalState.bulkDisplayStatus) {\n const displayLabel = localizeStatus(G7Core, 'display_status', globalState.bulkDisplayStatus);\n changes.push(\n G7Core.t('sirsoft-ecommerce.admin.product.messages.bulk_summary_display_status', { status: displayLabel })\n || `Display status: ${displayLabel}`\n );\n }\n\n // 인라인 수정 표시 (실제 수정된 필드만 보고)\n if (isProductModified) {\n if (!globalState.bulkSalesStatus && pModifiedFields.includes('sales_status')) {\n const salesLabel = localizeStatus(G7Core, 'sales_status', p.sales_status);\n changes.push(\n G7Core.t('sirsoft-ecommerce.admin.product.messages.bulk_summary_sales_status', { status: salesLabel })\n || `Sales status: ${salesLabel}`\n );\n }\n if (!globalState.bulkDisplayStatus && pModifiedFields.includes('display_status')) {\n const displayLabel = localizeStatus(G7Core, 'display_status', p.display_status);\n changes.push(\n G7Core.t('sirsoft-ecommerce.admin.product.messages.bulk_summary_display_status', { status: displayLabel })\n || `Display status: ${displayLabel}`\n );\n }\n if (pModifiedFields.includes('name')) {\n changes.push(G7Core.t('sirsoft-ecommerce.admin.product.messages.bulk_summary_name_changed') || 'Name changed');\n }\n if (pModifiedFields.includes('list_price')) {\n changes.push(\n G7Core.t('sirsoft-ecommerce.admin.product.messages.bulk_summary_list_price', { price: p.list_price?.toLocaleString?.() || p.list_price })\n || `List price: ${p.list_price?.toLocaleString?.() || p.list_price}`\n );\n }\n if (pModifiedFields.includes('selling_price')) {\n changes.push(\n G7Core.t('sirsoft-ecommerce.admin.product.messages.bulk_summary_selling_price', { price: p.selling_price?.toLocaleString?.() || p.selling_price })\n || `Selling price: ${p.selling_price?.toLocaleString?.() || p.selling_price}`\n );\n }\n confirmData.summary.hasInlineChanges = true;\n }\n\n if (changes.length > 0 || hasBulkChanges || isProductModified) {\n confirmData.products.push({\n id: p.id,\n name: localizeValue(p.name),\n changes: changes.length > 0 ? changes.join(', ') : (isProductModified\n ? (G7Core.t('sirsoft-ecommerce.admin.product.messages.bulk_summary_inline_modified') || 'Inline modified')\n : (G7Core.t('sirsoft-ecommerce.admin.product.messages.bulk_summary_bulk_applied') || 'Bulk changes applied')),\n });\n }\n\n // 옵션 변경 사항 수집 (상품 체크 시 모든 옵션 포함)\n (p.options || []).forEach((opt: any) => {\n const optionKey = `${p.id}-${opt.id}`;\n const isOptionModified = opt._modified || modifiedOptionIds.includes(optionKey);\n\n const optChanges = collectOptionChanges(opt, optionKey, isOptionModified);\n\n if (optChanges.length > 0 || (hasBulkChanges && (globalState.bulkPriceCondition || globalState.bulkStockCondition)) || isOptionModified) {\n confirmData.options.push({\n productId: p.id,\n optionId: opt.id,\n productName: localizeValue(p.name),\n optionName: opt.option_name_localized || localizeValue(opt.option_name) || opt.name || '',\n changes: optChanges.length > 0 ? optChanges.join(', ') : (isOptionModified\n ? (G7Core.t('sirsoft-ecommerce.admin.product.messages.bulk_summary_inline_modified') || 'Inline modified')\n : (G7Core.t('sirsoft-ecommerce.admin.product.messages.bulk_summary_bulk_applied') || 'Bulk changes applied')),\n });\n }\n });\n });\n }\n\n // 체크된 옵션 처리 (체크된 상품에 속하지 않은 옵션만 처리)\n // 상품이 체크된 경우 해당 상품의 옵션은 위에서 이미 처리됨\n if (selectedOptionIds.length > 0) {\n products.forEach((p: any) => {\n // 이미 체크된 상품의 옵션은 건너뜀 (중복 방지)\n if (selectedProductIdSet.has(p.id)) {\n return;\n }\n\n (p.options || []).forEach((opt: any) => {\n const optionKey = `${p.id}-${opt.id}`;\n if (selectedOptionIds.includes(optionKey)) {\n const isOptionModified = opt._modified || modifiedOptionIds.includes(optionKey);\n\n const optChanges = collectOptionChanges(opt, optionKey, isOptionModified);\n\n confirmData.options.push({\n productId: p.id,\n optionId: opt.id,\n productName: localizeValue(p.name),\n optionName: opt.option_name_localized || localizeValue(opt.option_name) || opt.name || '',\n changes: optChanges.length > 0 ? optChanges.join(', ') : (isOptionModified\n ? (G7Core.t('sirsoft-ecommerce.admin.product.messages.bulk_summary_inline_modified') || 'Inline modified')\n : (G7Core.t('sirsoft-ecommerce.admin.product.messages.bulk_summary_selected') || 'Selected')),\n });\n }\n });\n });\n }\n\n confirmData.summary.productCount = confirmData.products.length;\n confirmData.summary.optionCount = confirmData.options.length;\n\n // 글로벌 상태에 확인 데이터 저장\n G7Core.state.set({\n bulkConfirmData: confirmData,\n });\n\n logger.log('[buildConfirmData] Built confirm data:', confirmData);\n}\n","/**\n * 주문 관리 관련 핸들러\n *\n * 주문 목록 화면에서 사용하는 커스텀 핸들러들을 정의합니다.\n * - 동적 컬럼 생성\n * - 배열 값 토글 (다중 선택 필터)\n * - 선택 상태 동기화\n * - 일괄 처리\n * - 엑셀 다운로드\n */\n\nimport type { ActionContext } from '../types';\n\n// Logger 설정 (G7Core 초기화 전에도 동작하도록 폴백 포함)\nconst logger = ((window as any).G7Core?.createLogger?.('Ecom:Order')) ?? {\n log: (...args: unknown[]) => console.log('[Ecom:Order]', ...args),\n warn: (...args: unknown[]) => console.warn('[Ecom:Order]', ...args),\n error: (...args: unknown[]) => console.error('[Ecom:Order]', ...args),\n};\n\n/**\n * 커스텀 핸들러에 전달되는 액션 객체 인터페이스\n * ActionDispatcher는 (action, context) 형태로 핸들러를 호출합니다.\n */\ninterface ActionWithParams> {\n handler: string;\n params?: T;\n [key: string]: any;\n}\n\n/**\n * 통화 정보 인터페이스\n */\ninterface Currency {\n code: string;\n symbol: string;\n name: string;\n is_default: boolean;\n}\n\n/**\n * 컬럼 정의 인터페이스\n */\ninterface ColumnDef {\n field: string;\n header: string;\n width?: string;\n sortable?: boolean;\n required?: boolean;\n}\n\n/**\n * 동적 컬럼 생성 파라미터\n */\ninterface BuildOrderColumnsParams {\n currencies?: Currency[];\n}\n\n/**\n * 주문 목록 DataGrid의 동적 컬럼 정의 생성 핸들러\n *\n * 통화 설정에 따라 금액 컬럼을 동적으로 추가합니다.\n *\n * @example\n * // init_actions에서 사용\n * {\n * \"handler\": \"sirsoft-ecommerce.buildOrderColumns\",\n * \"params\": {\n * \"currencies\": \"{{_global.modules['sirsoft-ecommerce']?.language_currency?.currencies || []}}\"\n * },\n * \"resultTo\": {\n * \"target\": \"_local\",\n * \"key\": \"orderColumns\"\n * }\n * }\n *\n * @param action 액션 객체 (params 포함)\n * @returns 컬럼 정의 배열\n */\nexport function buildOrderColumnsHandler(\n action: ActionWithParams\n): ColumnDef[] {\n const { currencies = [] } = action.params || {};\n\n const baseColumns: ColumnDef[] = [\n { field: 'no', header: 'NO', width: '60px', sortable: false, required: true },\n { field: 'ordered_at', header: '$t:sirsoft-ecommerce.admin.order.column.order_date', width: '140px', sortable: true, required: true },\n { field: 'order_number', header: '$t:sirsoft-ecommerce.admin.order.column.order_number', width: '140px', sortable: true, required: true },\n { field: 'product', header: '$t:sirsoft-ecommerce.admin.order.column.product', width: 'auto', sortable: false, required: true },\n { field: 'order_amount', header: '$t:sirsoft-ecommerce.admin.order.column.order_amount', width: '120px', sortable: true, required: false },\n { field: 'order_status', header: '$t:sirsoft-ecommerce.admin.order.column.order_status', width: '100px', sortable: false, required: false },\n { field: 'orderer', header: '$t:sirsoft-ecommerce.admin.order.column.orderer', width: '120px', sortable: false, required: false },\n { field: 'payment_method', header: '$t:sirsoft-ecommerce.admin.order.column.payment_method', width: '100px', sortable: false, required: false },\n { field: 'delivery_method', header: '$t:sirsoft-ecommerce.admin.order.column.delivery_method', width: '100px', sortable: false, required: false },\n { field: 'country', header: '$t:sirsoft-ecommerce.admin.order.column.country', width: '80px', sortable: false, required: false },\n ];\n\n // 통화별 금액 컬럼 추가 (기본 통화 제외)\n const currencyColumns: ColumnDef[] = currencies\n .filter(c => !c.is_default)\n .map(currency => ({\n field: `order_amount_${currency.code.toLowerCase()}`,\n header: `${currency.name} (${currency.symbol})`,\n width: '100px',\n sortable: true,\n required: false,\n }));\n\n return [...baseColumns, ...currencyColumns];\n}\n\n/**\n * 배열 값 토글 파라미터\n */\ninterface ToggleArrayValueParams {\n target?: '_local' | '_global';\n path: string;\n value: string;\n}\n\n/**\n * 배열 값 토글 결과\n */\ninterface ToggleArrayValueResult {\n success: boolean;\n newValue: string[];\n}\n\n/**\n * 배열 값 토글 핸들러\n *\n * 다중 선택 필터 (ChipCheckbox, Checkbox)의 배열 값을 토글합니다.\n * 배열에 값이 존재하면 제거하고, 없으면 추가합니다.\n *\n * @example\n * // 주문상태 필터에서 사용\n * {\n * \"type\": \"change\",\n * \"handler\": \"sirsoft-ecommerce.toggleArrayValue\",\n * \"params\": {\n * \"target\": \"_local\",\n * \"path\": \"filter.orderStatus\",\n * \"value\": \"paid\"\n * }\n * }\n *\n * @param action 액션 객체 (params 포함)\n * @param _context 액션 컨텍스트\n * @returns 토글 결과\n */\nexport function toggleArrayValueHandler(\n action: ActionWithParams,\n _context: ActionContext\n): ToggleArrayValueResult {\n const params = action.params || ({} as ToggleArrayValueParams);\n const { target = '_local', path, value } = params;\n const G7Core = (window as any).G7Core;\n\n if (!G7Core?.state) {\n logger.warn('[toggleArrayValue] G7Core.state를 사용할 수 없습니다.');\n return { success: false, newValue: [] };\n }\n\n // 현재 배열 값 가져오기\n const currentState = target === '_global' ? G7Core.state.getGlobal() : G7Core.state.getLocal();\n const pathParts = path.split('.');\n let currentArray: string[] = currentState;\n\n for (const part of pathParts) {\n currentArray = currentArray?.[part];\n }\n\n if (!Array.isArray(currentArray)) {\n currentArray = [];\n }\n\n // 토글 로직: 존재하면 제거, 없으면 추가\n let newArray: string[];\n if (currentArray.includes(value)) {\n // 제거\n newArray = currentArray.filter((item: string) => item !== value);\n } else {\n // 추가\n newArray = [...currentArray, value];\n }\n\n // 상태 업데이트 (dot notation 사용)\n if (target === '_global') {\n G7Core.state.setGlobal({\n [path]: newArray,\n });\n } else {\n G7Core.state.setLocal({\n [path]: newArray,\n });\n }\n\n return { success: true, newValue: newArray };\n}\n\n/**\n * 필터 표시/숨김 토글 파라미터\n */\ninterface ToggleVisibleFilterParams {\n filterKey: string;\n checked: boolean;\n}\n\n/**\n * 필터 표시/숨김 토글 결과\n */\ninterface ToggleVisibleFilterResult {\n success: boolean;\n newValue: string[];\n}\n\n/**\n * 필터 표시/숨김 토글 핸들러\n *\n * 검색 편집 모드에서 필터 기본/상세 영역 전환 시 사용합니다.\n * LocalStorage에도 저장하여 설정을 유지합니다.\n *\n * @example\n * // 편집 모드 체크박스에서 사용\n * {\n * \"type\": \"change\",\n * \"handler\": \"sirsoft-ecommerce.toggleVisibleFilter\",\n * \"params\": {\n * \"filterKey\": \"date\",\n * \"checked\": \"{{$event.target.checked}}\"\n * }\n * }\n *\n * @param action 액션 객체 (params 포함)\n * @param _context 액션 컨텍스트\n * @returns 토글 결과\n */\nexport function toggleVisibleFilterHandler(\n action: ActionWithParams,\n _context: ActionContext\n): ToggleVisibleFilterResult {\n const params = action.params || ({} as ToggleVisibleFilterParams);\n const { filterKey, checked } = params;\n const G7Core = (window as any).G7Core;\n\n if (!G7Core?.state) {\n logger.warn('[toggleVisibleFilter] G7Core.state를 사용할 수 없습니다.');\n return { success: false, newValue: [] };\n }\n\n // 현재 visibleFilters 가져오기\n const currentLocal = G7Core.state.getLocal();\n const currentFilters: string[] = currentLocal.visibleFilters || [];\n\n // 새 배열 계산\n let newFilters: string[];\n if (checked) {\n newFilters = currentFilters.includes(filterKey) ? currentFilters : [...currentFilters, filterKey];\n } else {\n newFilters = currentFilters.filter((f: string) => f !== filterKey);\n }\n\n // 상태 업데이트\n G7Core.state.setLocal({\n visibleFilters: newFilters,\n });\n\n // LocalStorage에 저장\n try {\n localStorage.setItem('g7_order_filter_visible_filters', JSON.stringify(newFilters));\n } catch (e) {\n logger.warn('[toggleVisibleFilter] LocalStorage 저장 실패:', e);\n }\n\n return { success: true, newValue: newFilters };\n}\n\n/**\n * 주문 선택 동기화 파라미터\n */\ninterface SyncOrderSelectionParams {\n selectedIds: number[];\n}\n\n/**\n * 주문 선택 동기화 결과\n */\ninterface SyncOrderSelectionResult {\n success: boolean;\n}\n\n/**\n * 주문 선택 동기화 핸들러\n *\n * DataGrid 선택 상태를 _local.selectedItems와 동기화합니다.\n *\n * @example\n * // DataGrid onSelectionChange 이벤트에서 사용\n * {\n * \"event\": \"onSelectionChange\",\n * \"type\": \"change\",\n * \"handler\": \"sirsoft-ecommerce.syncOrderSelection\",\n * \"params\": {\n * \"selectedIds\": \"{{$args[0]}}\"\n * }\n * }\n *\n * @param action 액션 객체 (params 포함)\n * @param _context 액션 컨텍스트\n * @returns 동기화 결과\n */\nexport function syncOrderSelectionHandler(\n action: ActionWithParams,\n _context: ActionContext\n): SyncOrderSelectionResult {\n const params = action.params || ({} as SyncOrderSelectionParams);\n const { selectedIds = [] } = params;\n const G7Core = (window as any).G7Core;\n\n if (!G7Core?.state) {\n logger.warn('[syncOrderSelection] G7Core.state를 사용할 수 없습니다.');\n return { success: false };\n }\n\n G7Core.state.setLocal({\n selectedItems: selectedIds,\n selectAll: false, // 개별 선택 시 전체 선택 해제\n });\n\n return { success: true };\n}\n\n/**\n * 행 액션 처리 파라미터\n */\ninterface HandleOrderRowActionParams {\n actionId: string;\n row: {\n id: number;\n order_number?: string;\n [key: string]: any;\n };\n}\n\n/**\n * 행 액션 처리 결과\n */\ninterface HandleOrderRowActionResult {\n success: boolean;\n action?: string;\n targetId?: number;\n}\n\n/**\n * 행 액션 처리 핸들러\n *\n * DataGrid 행 액션 메뉴에서 선택된 액션을 처리합니다.\n * 주로 switch 핸들러와 함께 사용됩니다.\n *\n * @example\n * // DataGrid onRowAction 이벤트에서 switch와 함께 사용\n * {\n * \"event\": \"onRowAction\",\n * \"handler\": \"switch\",\n * \"cases\": {\n * \"view\": { \"handler\": \"navigate\", \"params\": { \"path\": \"/admin/ecommerce/orders/{{$args[1].order_number}}\" } }\n * }\n * }\n *\n * @param action 액션 객체 (params 포함)\n * @param _context 액션 컨텍스트\n * @returns 처리 결과\n */\nexport function handleOrderRowActionHandler(\n action: ActionWithParams,\n _context: ActionContext\n): HandleOrderRowActionResult {\n const params = action.params || ({} as HandleOrderRowActionParams);\n const { actionId, row } = params;\n const G7Core = (window as any).G7Core;\n\n if (!actionId || !row?.order_number) {\n logger.warn('[handleOrderRowAction] actionId 또는 row.order_number가 없습니다.');\n return { success: false };\n }\n\n // 액션에 따른 처리\n switch (actionId) {\n case 'view':\n G7Core?.navigate?.(`/admin/ecommerce/orders/${row.order_number}`);\n break;\n\n case 'edit':\n G7Core?.navigate?.(`/admin/ecommerce/orders/${row.order_number}`);\n break;\n\n case 'status':\n case 'shipping':\n case 'memo':\n case 'delete':\n // 모달 열기가 필요한 액션은 상태 설정만 하고 레이아웃에서 처리\n G7Core?.state?.setGlobal({\n orderList: {\n currentActionOrderId: row.id,\n currentActionType: actionId,\n },\n });\n break;\n\n case 'print':\n // 새 창에서 배송장 출력\n window.open(`/admin/ecommerce/orders/${row.order_number}/shipping-label`, '_blank');\n break;\n\n default:\n console.warn(`[handleOrderRowAction] 알 수 없는 액션: ${actionId}`);\n return { success: false };\n }\n\n return {\n success: true,\n action: actionId,\n targetId: row.id,\n };\n}\n\n/**\n * 일괄 처리 실행 파라미터\n */\ninterface ProcessOrderBulkActionParams {\n actionType: 'status' | 'shipping';\n orderIds: number[];\n status?: string;\n carrierId?: number;\n trackingNumber?: string;\n}\n\n/**\n * 일괄 처리 실행 결과\n */\ninterface ProcessOrderBulkActionResult {\n success: boolean;\n successCount: number;\n failCount: number;\n errors: Array<{ id: number; message: string }>;\n}\n\n/**\n * 일괄 처리 실행 핸들러\n *\n * 일괄 상태 변경, 일괄 발송처리 등 일괄 작업을 실행합니다.\n *\n * @example\n * // 일괄 상태 변경\n * {\n * \"type\": \"click\",\n * \"handler\": \"sequence\",\n * \"actions\": [\n * {\n * \"handler\": \"sirsoft-ecommerce.processOrderBulkAction\",\n * \"params\": {\n * \"actionType\": \"status\",\n * \"orderIds\": \"{{_local.selectedItems}}\",\n * \"status\": \"{{_local.bulkOrderStatus}}\"\n * }\n * }\n * ]\n * }\n *\n * @param action 액션 객체 (params 포함)\n * @param _context 액션 컨텍스트\n * @returns 처리 결과\n */\nexport async function processOrderBulkActionHandler(\n action: ActionWithParams,\n _context: ActionContext\n): Promise {\n const params = action.params || ({} as ProcessOrderBulkActionParams);\n const { actionType, orderIds, status, carrierId, trackingNumber } = params;\n const G7Core = (window as any).G7Core;\n\n if (!orderIds || orderIds.length === 0) {\n throw new Error('선택된 주문이 없습니다.');\n }\n\n let endpoint = '';\n const body: Record = { ids: orderIds };\n\n switch (actionType) {\n case 'status':\n if (!status) {\n throw new Error('변경할 상태를 선택해주세요.');\n }\n endpoint = '/api/modules/sirsoft-ecommerce/admin/orders/bulk';\n body.order_status = status;\n break;\n\n case 'shipping':\n if (!trackingNumber && !carrierId) {\n throw new Error('택배사 또는 운송장번호를 입력해주세요.');\n }\n endpoint = '/api/modules/sirsoft-ecommerce/admin/orders/bulk';\n if (carrierId) body.carrier_id = carrierId;\n if (trackingNumber) body.tracking_number = trackingNumber;\n break;\n\n default:\n throw new Error(`알 수 없는 액션 타입: ${actionType}`);\n }\n\n // API 호출\n const response = await G7Core?.api?.call(endpoint, {\n method: 'PATCH',\n body,\n });\n\n if (!response?.success) {\n throw new Error(response?.message || '일괄 처리에 실패했습니다.');\n }\n\n return {\n success: true,\n successCount: response.data?.updated_count ?? orderIds.length,\n failCount: response.data?.fail_count ?? 0,\n errors: response.data?.errors ?? [],\n };\n}\n\n/**\n * 엑셀 다운로드 파라미터\n */\ninterface DownloadOrderExcelParams {\n downloadType: 'search' | 'selected';\n orderIds?: number[];\n filters?: Record;\n}\n\n/**\n * 엑셀 다운로드 결과\n */\ninterface DownloadOrderExcelResult {\n success: boolean;\n}\n\n/**\n * 엑셀 다운로드 핸들러\n *\n * 주문 목록을 엑셀 파일로 다운로드합니다.\n *\n * @example\n * // 엑셀 다운로드 모달에서 사용\n * {\n * \"type\": \"click\",\n * \"handler\": \"sequence\",\n * \"actions\": [\n * {\n * \"handler\": \"sirsoft-ecommerce.downloadOrderExcel\",\n * \"params\": {\n * \"downloadType\": \"{{_local.excelDownloadType}}\",\n * \"orderIds\": \"{{_global.orderList.selectedItemsForExcel}}\",\n * \"filters\": \"{{_local.filter}}\"\n * }\n * },\n * { \"handler\": \"closeModal\" }\n * ]\n * }\n *\n * @param action 액션 객체 (params 포함)\n * @param _context 액션 컨텍스트\n * @returns 다운로드 결과\n */\nexport async function downloadOrderExcelHandler(\n action: ActionWithParams,\n _context: ActionContext\n): Promise {\n const params = action.params || ({} as DownloadOrderExcelParams);\n const { downloadType, orderIds, filters } = params;\n const G7Core = (window as any).G7Core;\n\n const endpoint = '/api/modules/sirsoft-ecommerce/admin/orders/export';\n const body: Record = {};\n\n if (downloadType === 'selected') {\n if (!orderIds || orderIds.length === 0) {\n throw new Error('선택된 주문이 없습니다.');\n }\n body.ids = orderIds;\n } else {\n body.filters = filters || {};\n }\n\n // Blob 다운로드 처리\n const token = G7Core?.auth?.getToken();\n const response = await fetch(endpoint, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n ...(token ? { Authorization: `Bearer ${token}` } : {}),\n },\n body: JSON.stringify(body),\n });\n\n if (!response.ok) {\n const errorData = await response.json().catch(() => null);\n throw new Error(errorData?.message || '엑셀 다운로드에 실패했습니다.');\n }\n\n const blob = await response.blob();\n const url = window.URL.createObjectURL(blob);\n const a = document.createElement('a');\n a.href = url;\n\n // 파일명 생성 (YYYY-MM-DD 형식)\n const today = new Date();\n const dateStr = `${today.getFullYear()}-${String(today.getMonth() + 1).padStart(2, '0')}-${String(today.getDate()).padStart(2, '0')}`;\n a.download = `orders_${dateStr}.xlsx`;\n\n document.body.appendChild(a);\n a.click();\n document.body.removeChild(a);\n window.URL.revokeObjectURL(url);\n\n return { success: true };\n}\n\n/**\n * 컬럼 표시/숨김 설정 저장 파라미터\n */\ninterface SaveVisibleColumnsParams {\n columns: string[];\n}\n\n/**\n * 컬럼 표시/숨김 설정 저장 핸들러\n *\n * DataGrid의 표시 컬럼 설정을 LocalStorage에 저장합니다.\n *\n * @param action 액션 객체 (params 포함)\n * @param _context 액션 컨텍스트\n * @returns 저장 결과\n */\nexport function saveVisibleColumnsHandler(\n action: ActionWithParams,\n _context: ActionContext\n): { success: boolean } {\n const params = action.params || ({} as SaveVisibleColumnsParams);\n const { columns = [] } = params;\n\n try {\n localStorage.setItem('g7_order_visible_columns', JSON.stringify(columns));\n } catch (e) {\n logger.warn('[saveVisibleColumns] LocalStorage 저장 실패:', e);\n return { success: false };\n }\n\n return { success: true };\n}\n\n/**\n * 컬럼 표시/숨김 설정 로드 핸들러\n *\n * LocalStorage에서 표시 컬럼 설정을 로드합니다.\n *\n * @param action 액션 객체 (params 포함)\n * @param _context 액션 컨텍스트\n * @returns 저장된 컬럼 목록 또는 기본값\n */\nexport function loadVisibleColumnsHandler(\n action: ActionWithParams<{ defaultColumns?: string[] }>,\n _context: ActionContext\n): string[] {\n const params = action.params || {};\n const defaultColumns = params.defaultColumns || [\n 'order_date',\n 'order_number',\n 'order_status',\n 'order_amount',\n 'product',\n 'orderer',\n 'payment_method',\n 'delivery_method',\n 'country',\n ];\n\n try {\n const saved = localStorage.getItem('g7_order_visible_columns');\n if (saved) {\n const parsed = JSON.parse(saved);\n if (Array.isArray(parsed)) {\n return parsed;\n }\n }\n } catch (e) {\n logger.warn('[loadVisibleColumns] LocalStorage 로드 실패:', e);\n }\n\n return defaultColumns;\n}\n\n/**\n * 필터 표시/숨김 설정 로드 파라미터\n */\ninterface LoadVisibleFiltersParams {\n /** 필터 ID (localStorage 키 생성에 사용) */\n id: string;\n /** 기본 표시 필터 목록 */\n defaultFilters?: string[];\n}\n\n/**\n * 필터 표시/숨김 설정 로드 핸들러\n *\n * LocalStorage에서 표시 필터 설정을 로드합니다.\n * FilterVisibilitySelector와 동일한 localStorage 키 패턴(g7_filters_{id}_{userId})을 사용합니다.\n *\n * @example\n * // init_actions에서 사용\n * {\n * \"handler\": \"sirsoft-ecommerce.loadVisibleFilters\",\n * \"params\": {\n * \"id\": \"admin_ecommerce_order_filters\",\n * \"defaultFilters\": [\"searchField\", \"searchKeyword\", \"date\", \"orderStatus\"]\n * },\n * \"resultTo\": {\n * \"target\": \"_local\",\n * \"key\": \"visibleFilters\"\n * }\n * }\n *\n * @param action 액션 객체 (params 포함)\n * @param _context 액션 컨텍스트\n * @returns 저장된 필터 목록 또는 기본값\n */\nexport function loadVisibleFiltersHandler(\n action: ActionWithParams,\n _context: ActionContext\n): string[] {\n const params = action.params || {} as LoadVisibleFiltersParams;\n const { id, defaultFilters = [] } = params;\n const G7Core = (window as any).G7Core;\n\n if (!id) {\n logger.warn('[loadVisibleFilters] id 파라미터가 필요합니다.');\n return defaultFilters;\n }\n\n // 사용자 ID 가져오기 (FilterVisibilitySelector와 동일한 로직)\n let userId: string | undefined;\n try {\n const authManager = G7Core?.AuthManager?.getInstance();\n if (authManager) {\n const user = authManager.getUser();\n userId = user?.uuid;\n }\n } catch {\n // 인증 정보 없음 - 공유 설정 사용\n }\n\n // localStorage 키 생성 (FilterVisibilitySelector와 동일한 패턴)\n const storageKey = `g7_filters_${id}${userId !== undefined ? `_${userId}` : ''}`;\n\n try {\n const saved = localStorage.getItem(storageKey);\n if (saved) {\n const parsed = JSON.parse(saved);\n if (Array.isArray(parsed) && parsed.length > 0) {\n logger.log(`[loadVisibleFilters] localStorage에서 로드: ${storageKey}`, parsed);\n return parsed;\n }\n }\n } catch (e) {\n logger.warn('[loadVisibleFilters] LocalStorage 로드 실패:', e);\n }\n\n logger.log(`[loadVisibleFilters] 기본값 사용: ${storageKey}`, defaultFilters);\n return defaultFilters;\n}\n\n/**\n * 주문 일괄 변경 확인 데이터 빌드 핸들러\n *\n * 상태 미선택 시 toast 경고, 정상 시 _global에 bulkConfirmData 저장 후 모달 열기.\n * 상품관리의 buildConfirmData 패턴과 일관됨.\n *\n * @example\n * {\n * \"type\": \"click\",\n * \"handler\": \"sirsoft-ecommerce.buildOrderBulkConfirmData\"\n * }\n */\nexport function buildOrderBulkConfirmDataHandler(\n _action: ActionWithParams,\n _context: ActionContext\n): void {\n const G7Core = (window as any).G7Core;\n if (!G7Core) return;\n\n const globalState = G7Core.state.get() || {};\n\n const selectedItems: number[] = globalState.bulkSelectedItems || [];\n const bulkOrderStatus: string = globalState.bulkOrderStatus || '';\n const bulkCourier: string = globalState.bulkCourier || '';\n const bulkTrackingNumber: string = globalState.bulkTrackingNumber || '';\n\n // 검증: 상태 미선택 시 경고\n if (!bulkOrderStatus) {\n G7Core.toast?.warning?.(\n G7Core.t?.('sirsoft-ecommerce.admin.order.bulk.no_status_selected')\n || '변경할 주문상태를 선택해주세요.'\n );\n return;\n }\n\n // 검증: 배송 관련 상태 선택 시 택배사/송장번호 필수\n const shippingStatuses = ['shipping_ready', 'shipping'];\n if (shippingStatuses.includes(bulkOrderStatus)) {\n if (!bulkCourier || !bulkTrackingNumber) {\n G7Core.toast?.warning?.(\n G7Core.t?.('sirsoft-ecommerce.admin.order.bulk.carrier_required')\n || '해당 상태로 변경하려면 택배사와 송장번호를 모두 입력해주세요.'\n );\n return;\n }\n }\n\n // _global에 확인 데이터 저장\n G7Core.state.set({\n bulkConfirmData: {\n selectedItems,\n orderStatus: bulkOrderStatus,\n courierId: bulkCourier,\n trackingNumber: bulkTrackingNumber,\n },\n });\n\n // 모달 열기\n G7Core.modal?.open?.('modal_bulk_confirm');\n}\n\n/**\n * 주문 일괄 변경 실행 핸들러\n *\n * 모달 확인 버튼에서 호출. _global.bulkConfirmData를 읽어 API 호출 후\n * 상태 리셋, DataSource 리프레시, 모달 닫기를 수행합니다.\n * 상품관리의 bulkUpdate 핸들러와 동일한 패턴.\n *\n * @example\n * {\n * \"type\": \"click\",\n * \"handler\": \"sirsoft-ecommerce.executeOrderBulkAction\"\n * }\n */\nexport async function executeOrderBulkActionHandler(\n _action: ActionWithParams,\n _context: ActionContext\n): Promise {\n const G7Core = (window as any).G7Core;\n if (!G7Core) return;\n\n const globalState = G7Core.state.get() || {};\n const confirmData = globalState.bulkConfirmData;\n\n if (!confirmData || !confirmData.selectedItems?.length) {\n G7Core.toast?.error?.(\n G7Core.t?.('sirsoft-ecommerce.admin.order.bulk.error')\n || '일괄 처리에 실패했습니다.'\n );\n return;\n }\n\n const body: Record = {\n ids: confirmData.selectedItems,\n order_status: confirmData.orderStatus,\n };\n if (confirmData.courierId) {\n body.carrier_id = confirmData.courierId;\n }\n if (confirmData.trackingNumber) {\n body.tracking_number = confirmData.trackingNumber;\n }\n\n try {\n await G7Core.api.patch(\n '/api/modules/sirsoft-ecommerce/admin/orders/bulk',\n body\n );\n\n G7Core.toast?.success?.(\n G7Core.t?.('sirsoft-ecommerce.admin.order.bulk.success')\n || '일괄 변경이 완료되었습니다.'\n );\n\n // 상태 초기화\n G7Core.state.setLocal({\n selectedItems: [],\n selectAll: false,\n bulkOrderStatus: '',\n bulkCourier: '',\n bulkTrackingNumber: '',\n });\n G7Core.state.set({\n bulkConfirmData: null,\n });\n\n // DataSource 리프레시\n G7Core.dataSource?.refetch?.('orders');\n\n // 모달 닫기\n G7Core.modal?.close?.();\n } catch (error: any) {\n logger.error('[executeOrderBulkAction] Error:', error);\n // 서버 422 검증 메시지(상태 전이 차단 사유 등)를 우선 노출한다.\n // axios 는 error.message 에 \"Request failed with status code 422\" 같은 raw 문구만 담으므로\n // 실제 사유가 들어있는 error.response.data.message 를 먼저 추출한다(confirmDeposit 동일 패턴).\n const errorData = error?.response?.data || error?.data || {};\n const serverMessage = errorData?.message || error?.message;\n G7Core.toast?.error?.(\n serverMessage\n || G7Core.t?.('sirsoft-ecommerce.admin.order.bulk.error')\n || '일괄 처리에 실패했습니다.'\n );\n }\n}\n","/**\n * 상품 목록 관련 핸들러\n *\n * 상품 목록 화면에서 사용하는 커스텀 핸들러들을 정의합니다.\n * - 행 액션 처리 (수정, 복사, 삭제)\n */\n\nimport type { ActionContext } from '../types';\n\n// Logger 설정 (G7Core 초기화 전에도 동작하도록 폴백 포함)\nconst logger = ((window as any).G7Core?.createLogger?.('Ecom:ProductList')) ?? {\n log: (...args: unknown[]) => console.log('[Ecom:ProductList]', ...args),\n warn: (...args: unknown[]) => console.warn('[Ecom:ProductList]', ...args),\n error: (...args: unknown[]) => console.error('[Ecom:ProductList]', ...args),\n};\n\n/**\n * 커스텀 핸들러에 전달되는 액션 객체 인터페이스\n */\ninterface ActionWithParams> {\n handler: string;\n params?: T;\n [key: string]: any;\n}\n\n/**\n * 행 액션 파라미터\n */\ninterface HandleProductRowActionParams {\n actionId: 'edit' | 'copy' | 'delete';\n row: {\n id: number;\n product_code: string;\n name?: string;\n [key: string]: any;\n };\n}\n\n/**\n * 행 액션 결과\n */\ninterface HandleProductRowActionResult {\n success: boolean;\n action?: string;\n targetId?: number;\n}\n\n/**\n * 삭제 가능 여부 API 응답\n */\ninterface CanDeleteResponse {\n data: {\n canDelete: boolean;\n reason: string | null;\n relatedData: {\n orders: number;\n images: number;\n options: number;\n additionalOptions?: number;\n labelAssignments?: number;\n logs?: number;\n };\n };\n}\n\n/**\n * 상품 목록 DataGrid의 행 액션 처리 핸들러\n *\n * rowActions에서 호출되며, 각 액션 유형에 따라 처리합니다.\n * - edit: 상품 수정 페이지로 이동\n * - copy: 복사 모달 열기\n * - delete: 삭제 가능 여부 확인 후 삭제 모달 열기\n *\n * @example\n * // 레이아웃 JSON에서 사용\n * {\n * \"handler\": \"sirsoft-ecommerce.handleProductRowAction\",\n * \"params\": {\n * \"actionId\": \"{{actionId}}\",\n * \"row\": \"{{row}}\"\n * }\n * }\n *\n * @param action 액션 객체 (params 포함)\n * @param _context 액션 컨텍스트 (미사용)\n * @returns 처리 결과\n */\nexport async function handleProductRowActionHandler(\n action: ActionWithParams,\n _context: ActionContext\n): Promise {\n const params = action.params || ({} as HandleProductRowActionParams);\n const { actionId, row } = params;\n const G7Core = (window as any).G7Core;\n\n if (!actionId || !row?.id) {\n logger.warn('[handleProductRowAction] actionId 또는 row.id가 없습니다.');\n return { success: false };\n }\n\n // 액션에 따른 처리\n switch (actionId) {\n case 'edit':\n // 상품 수정 페이지로 이동 (product_code 사용)\n G7Core?.navigate?.(`/admin/ecommerce/products/${row.product_code}/edit`);\n break;\n\n case 'copy':\n // 복사 모달 열기\n G7Core?.state?.setLocal({\n productList: { copyTargetProduct: row },\n ui: { showCopyModal: true },\n });\n break;\n\n case 'delete':\n try {\n // 삭제 가능 여부 API 호출\n const response = await G7Core?.api?.get(\n `/api/modules/sirsoft-ecommerce/admin/products/${row.id}/can-delete`\n );\n\n const data = response?.data;\n\n // 삭제 모달 열기 (삭제 가능 여부와 관계없이 모달 표시)\n G7Core?.state?.setLocal({\n productList: {\n deleteTargetProduct: row,\n canDelete: data?.canDelete ?? true,\n deleteBlockReason: data?.reason ?? null,\n relatedData: data?.relatedData ?? null,\n },\n ui: { showDeleteModal: true },\n });\n } catch (error) {\n logger.error('[handleProductRowAction] 삭제 가능 여부 확인 실패:', error);\n // 에러 발생 시에도 모달은 열되, 삭제 불가로 표시\n G7Core?.state?.setLocal({\n productList: {\n deleteTargetProduct: row,\n canDelete: false,\n deleteBlockReason: '삭제 가능 여부를 확인할 수 없습니다.',\n relatedData: null,\n },\n ui: { showDeleteModal: true },\n });\n }\n break;\n\n default:\n logger.warn(`[handleProductRowAction] 알 수 없는 액션: ${actionId}`);\n return { success: false };\n }\n\n return {\n success: true,\n action: actionId,\n targetId: row.id,\n };\n}\n","/**\n * 주문 상세 관련 핸들러\n *\n * 주문 상세 화면에서 사용하는 커스텀 핸들러들을 정의합니다.\n * - 폼 초기화 (데이터 로드 후 수취인 정보 바인딩)\n * - 상품 선택 토글\n * - 일괄 상태 변경 API 호출\n * - 관리자 메모 저장\n */\n\nimport type { ActionContext } from '../types';\n\n// Logger 설정 (G7Core 초기화 전에도 동작하도록 폴백 포함)\nconst logger = ((window as any).G7Core?.createLogger?.('Ecom:OrderDetail')) ?? {\n log: (...args: unknown[]) => console.log('[Ecom:OrderDetail]', ...args),\n warn: (...args: unknown[]) => console.warn('[Ecom:OrderDetail]', ...args),\n error: (...args: unknown[]) => console.error('[Ecom:OrderDetail]', ...args),\n};\n\n/**\n * 커스텀 핸들러에 전달되는 액션 객체 인터페이스\n */\ninterface ActionWithParams> {\n handler: string;\n params?: T;\n [key: string]: any;\n}\n\n/**\n * 일괄변경 송장번호 입력칸의 실제 DOM 값을 읽습니다.\n *\n * 송장번호 input 은 주문정보 카드의 dataKey=\"form\" 폼 컨텍스트 안에 있어, 폼 자동바인딩이\n * 값을 _local.form.batchTrackingNumber 로 보낸다. 반면 일괄변경 핸들러는 액션 params(\n * 표현식 {{_local.batchTrackingNumber}}) 로 top-level 값을 받으므로 경로가 어긋나 빈 값이\n * 전달될 수 있다(Select 인 상태/택배사는 명시 setState 가 top-level 에 정상 기록되어 무관).\n * 저장소 분기에 의존하지 않도록, params 가 비면 화면에 실제 표시된 input 값을 직접 읽는다.\n *\n * @return 송장번호 문자열 (입력칸이 없으면 빈 문자열)\n */\nfunction readBatchTrackingFromDom(): string {\n if (typeof document === 'undefined') {\n return '';\n }\n const input = document.querySelector('input[name=\"batchTrackingNumber\"]');\n\n return input?.value?.trim() ?? '';\n}\n\n/**\n * 주문 상세 폼 초기화 핸들러\n *\n * order 데이터소스 로드 완료 후 수취인 정보를 _local.form에 바인딩합니다.\n *\n * @example\n * // data_sources.onLoaded에서 사용\n * {\n * \"handler\": \"sirsoft-ecommerce.initOrderDetailForm\"\n * }\n *\n * @param _action 액션 객체\n * @param _context 액션 컨텍스트\n */\nexport function initOrderDetailFormHandler(\n _action: ActionWithParams,\n _context: ActionContext\n): void {\n const G7Core = (window as any).G7Core;\n\n if (!G7Core?.state) {\n logger.warn('[initOrderDetailForm] G7Core.state를 사용할 수 없습니다.');\n return;\n }\n\n const local = G7Core.state.getLocal();\n // onLoaded 콜백 context에서 직접 데이터 우선 접근\n const contextData = (_context as any)?.data?.data;\n const orderData = contextData\n ?? G7Core.dataSource?.get?.('order')?.data;\n\n if (!orderData) {\n logger.warn('[initOrderDetailForm] order 데이터가 없습니다.');\n return;\n }\n\n // 수취인 정보를 폼에 바인딩\n G7Core.state.setLocal({\n form: {\n recipient_name: orderData.recipient_name || '',\n recipient_phone: orderData.recipient_phone || '',\n recipient_tel: orderData.recipient_tel || '',\n recipient_zipcode: orderData.recipient_zipcode || '',\n recipient_address: orderData.recipient_address || '',\n recipient_detail_address: orderData.recipient_detail_address || '',\n delivery_memo: orderData.delivery_memo || '',\n admin_memo: orderData.admin_memo || '',\n },\n });\n\n logger.log('[initOrderDetailForm] 폼 초기화 완료');\n}\n\n/**\n * 상품 선택 토글 핸들러\n *\n * 개별 상품의 체크박스를 토글합니다.\n *\n * @example\n * {\n * \"type\": \"change\",\n * \"handler\": \"sirsoft-ecommerce.toggleProductSelection\",\n * \"params\": { \"optionId\": \"{{option.id}}\" }\n * }\n *\n * @param action 액션 객체 (params.optionId 필수)\n * @param _context 액션 컨텍스트\n */\nexport function toggleProductSelectionHandler(\n action: ActionWithParams<{ optionId: number }>,\n _context: ActionContext\n): void {\n const G7Core = (window as any).G7Core;\n const { optionId } = action.params || {};\n\n if (!G7Core?.state || !optionId) {\n return;\n }\n\n const local = G7Core.state.getLocal();\n const selected: number[] = local.selectedProducts || [];\n\n let newSelected: number[];\n if (selected.includes(optionId)) {\n newSelected = selected.filter((id: number) => id !== optionId);\n } else {\n newSelected = [...selected, optionId];\n }\n\n // 전체 선택 상태 업데이트\n const orderData = G7Core.dataSource?.get?.('order')?.data;\n const totalOptions = (orderData?.options || []).length;\n const selectAll = newSelected.length === totalOptions && totalOptions > 0;\n\n G7Core.state.setLocal({\n selectedProducts: newSelected,\n selectAll,\n });\n}\n\n/**\n * 전체 선택/해제 토글 핸들러\n *\n * 모든 상품을 선택하거나 해제합니다.\n *\n * @example\n * {\n * \"type\": \"change\",\n * \"handler\": \"sirsoft-ecommerce.toggleAllProducts\"\n * }\n *\n * @param _action 액션 객체\n * @param _context 액션 컨텍스트\n */\nexport function toggleAllProductsHandler(\n _action: ActionWithParams,\n _context: ActionContext\n): void {\n const G7Core = (window as any).G7Core;\n\n if (!G7Core?.state) {\n return;\n }\n\n const local = G7Core.state.getLocal();\n const currentSelectAll = local.selectAll;\n const orderData = G7Core.dataSource?.get?.('order')?.data;\n const options = orderData?.options || [];\n\n if (currentSelectAll) {\n // 전체 해제\n G7Core.state.setLocal({\n selectedProducts: [],\n selectAll: false,\n });\n } else {\n // 전체 선택\n const allIds = options.map((opt: { id: number }) => opt.id);\n G7Core.state.setLocal({\n selectedProducts: allIds,\n selectAll: true,\n });\n }\n}\n\n/**\n * 주문 상세 일괄변경 확인 데이터 빌드 핸들러\n *\n * 주문관리의 buildOrderBulkConfirmData와 동일한 패턴.\n * 선택 항목과 상태를 검증 후 확인 모달을 엽니다.\n *\n * @example\n * {\n * \"type\": \"click\",\n * \"handler\": \"sirsoft-ecommerce.buildOrderDetailBulkConfirmData\"\n * }\n *\n * @param _action 액션 객체\n * @param _context 액션 컨텍스트\n */\nexport function buildOrderDetailBulkConfirmDataHandler(\n action: ActionWithParams<{\n selectedProducts?: number[];\n batchOrderStatus?: string;\n batchCarrierId?: string;\n batchTrackingNumber?: string;\n }>,\n _context: ActionContext\n): void {\n const G7Core = (window as any).G7Core;\n if (!G7Core) return;\n\n const params = action.params || {};\n const selectedProducts: number[] = params.selectedProducts || [];\n const batchOrderStatus: string = params.batchOrderStatus || '';\n const batchCarrierId: string = params.batchCarrierId || '';\n // 송장번호 입력칸(basic Input)은 dataKey=\"form\" 컨텍스트의 폼 자동바인딩 영향으로\n // 명시적 setState(target:\"local\") 가 top-level _local 에 도달하지 않고 _local.form 에만\n // 기록되는 경우가 있다(Select 는 정상, Input 만 비대칭). params 가 비면 DOM 의 실제\n // 입력값을 직접 읽어 보정한다. (택배사/상태 Select 는 params 로 정상 전달됨)\n const batchTrackingNumber: string = params.batchTrackingNumber || readBatchTrackingFromDom();\n\n // 검증: 상태 미선택 시 경고\n if (!batchOrderStatus) {\n G7Core.toast?.warning?.(\n G7Core.t?.('sirsoft-ecommerce.admin.order.bulk.no_status_selected')\n || '변경할 주문상태를 선택해주세요.'\n );\n return;\n }\n\n // 검증: 선택된 상품 없음\n if (selectedProducts.length === 0) {\n G7Core.toast?.warning?.(\n G7Core.t?.('sirsoft-ecommerce.admin.order.bulk.no_items_selected')\n || '변경할 상품을 선택해주세요.'\n );\n return;\n }\n\n // 취소 상태 선택 시 → cancelPg 여부에 따라 모달 분기\n if (batchOrderStatus === 'cancelled') {\n const cancelPg = params.cancelPg ?? true;\n const orderData = G7Core.dataSource?.get?.('order')?.data;\n const options = orderData?.options || [];\n\n // 취소 가능한 옵션만 필터 (이미 취소된 옵션 제외)\n const cancelItems = selectedProducts\n .map((optionId: number) => {\n const opt = options.find((o: { id: number }) => o.id === optionId);\n if (!opt || opt.option_status === 'cancelled') return null;\n return {\n id: opt.id,\n product_name: opt.product_name,\n product_option_name: opt.product_option_name,\n thumbnail_url: opt.product_snapshot?.thumbnail_url || '',\n unit_price: opt.unit_price,\n quantity: opt.quantity,\n cancel_quantity: opt.quantity,\n option_status: opt.option_status,\n option_status_label: opt.option_status_label || opt.option_status,\n };\n })\n .filter(Boolean);\n\n if (cancelItems.length === 0) {\n G7Core.toast?.warning?.(\n G7Core.t?.('sirsoft-ecommerce.admin.order.bulk.no_cancellable_items')\n || '취소할 수 있는 상품이 없습니다. 이미 취소된 상품은 제외됩니다.'\n );\n return;\n }\n\n if (cancelPg) {\n // PG 결제 취소 포함 → 취소/환불 모달 (환불예정금액 실시간 표시)\n // 모달 열기 전에 페이지 _local에 모든 취소 관련 상태 초기화\n // (사용자 취소 모달과 동일 패턴 — onMount setState 미사용)\n // 참고: modals 섹션의 isolated scope에서 onMount setState는\n // 모달 자체 scope에 복사하여 getLocal()/setLocal()과 단절됨\n G7Core.state.setLocal({\n cancelItems,\n cancelReason: '',\n cancelReasonDetail: '',\n cancelPg: true,\n refundPriority: 'pg_first',\n refundEstimate: null,\n refundLoading: false,\n isCancelling: false,\n cancelError: null,\n cancelValidationErrors: null,\n });\n G7Core.modal?.open?.('modal_cancel_order');\n\n // 모달 열린 후 초기 환불 예상금액 계산 (사용자 모달 initUserCancelItemsHandler와 동일)\n const orderId = orderData?.order_number || '';\n if (orderId) {\n G7Core.dispatch?.({\n handler: 'sirsoft-ecommerce.estimateRefundAmount',\n params: { orderId, cancelItems },\n });\n }\n } else {\n // PG 결제 취소 미포함 → 단순 상태 변경 확인 모달\n const changeQuantities: Record = {};\n for (const item of cancelItems) {\n changeQuantities[item.id] = item.quantity;\n }\n G7Core.state.setLocal({\n bulkConfirmItems: cancelItems,\n changeQuantities,\n batchOrderStatus,\n });\n G7Core.modal?.open?.('modal_batch_change_confirm');\n }\n return;\n }\n\n // 검증: 배송 관련 상태 선택 시 택배사/송장번호 필수\n const shippingStatuses = ['shipping_ready', 'shipping', 'delivered'];\n if (shippingStatuses.includes(batchOrderStatus)) {\n if (!batchCarrierId || !batchTrackingNumber) {\n G7Core.toast?.warning?.(\n G7Core.t?.('sirsoft-ecommerce.admin.order.bulk.carrier_required')\n || '해당 상태로 변경하려면 택배사와 송장번호를 모두 입력해주세요.'\n );\n return;\n }\n }\n\n // 선택된 상품의 상세 정보 수집 (데이터소스는 G7Core.dataSource API로 접근)\n const orderData = G7Core.dataSource?.get?.('order')?.data;\n const options = orderData?.options || [];\n const bulkConfirmItems: Record[] = [];\n const changeQuantities: Record = {};\n\n for (const optionId of selectedProducts) {\n const opt = options.find((o: { id: number }) => o.id === optionId);\n if (!opt) continue;\n\n bulkConfirmItems.push({\n id: opt.id,\n product_name: opt.product_name,\n product_option_name: opt.product_option_name,\n sku: opt.sku,\n thumbnail_url: opt.product_snapshot?.thumbnail_url || '',\n unit_price: opt.unit_price,\n original_price: opt.product_snapshot?.original_price || opt.unit_price,\n quantity: opt.quantity,\n option_status: opt.option_status,\n });\n changeQuantities[opt.id] = opt.quantity;\n }\n\n // _local에 확인 데이터 저장 후 모달 열기\n G7Core.state.setLocal({\n bulkConfirmItems,\n changeQuantities,\n batchOrderStatus,\n batchCarrierId,\n batchTrackingNumber,\n });\n\n G7Core.modal?.open?.('modal_batch_change_confirm');\n}\n\n/**\n * 일괄 상태 변경 처리 핸들러\n *\n * 선택된 주문 옵션들의 상태를 일괄 변경합니다.\n * 수량 분할을 지원합니다.\n *\n * @example\n * {\n * \"type\": \"click\",\n * \"handler\": \"sirsoft-ecommerce.processOrderDetailBulkChange\",\n * \"params\": { \"orderId\": \"{{route.orderNumber}}\" }\n * }\n *\n * @param action 액션 객체 (params.orderId 필수)\n * @param _context 액션 컨텍스트\n */\nexport async function processOrderDetailBulkChangeHandler(\n action: ActionWithParams<{\n orderId: string | number;\n selectedProducts?: number[];\n batchOrderStatus?: string;\n batchCarrierId?: string;\n batchTrackingNumber?: string;\n changeQuantities?: Record;\n }>,\n _context: ActionContext\n): Promise {\n const G7Core = (window as any).G7Core;\n const { orderId } = action.params || {};\n\n if (!G7Core?.state || !orderId) {\n logger.warn('[processOrderDetailBulkChange] orderId가 없습니다.');\n return;\n }\n\n // 확인 모달(modal_batch_change_confirm)은 격리 scope 라, 모달의 $parent._local 표현식으로\n // 넘긴 params 가 build 단계에서 setLocal 한 페이지 globalLocal 값과 어긋나 빈 값으로 도달할 수\n // 있다(carrier_id/tracking_number 누락 → DB 미저장). params 가 비면 build 가 저장한 페이지\n // _local(getLocal) 을 SSoT 로 fallback 한다. 송장은 추가로 DOM 값까지 보정.\n const pageLocal = G7Core.state.getLocal?.() ?? {};\n const selectedProducts = action.params?.selectedProducts ?? pageLocal.selectedProducts;\n const batchOrderStatus = action.params?.batchOrderStatus || pageLocal.batchOrderStatus;\n const batchCarrierId = action.params?.batchCarrierId || pageLocal.batchCarrierId;\n const batchTrackingNumber = action.params?.batchTrackingNumber\n || pageLocal.batchTrackingNumber\n || readBatchTrackingFromDom();\n const changeQuantities = action.params?.changeQuantities ?? pageLocal.changeQuantities;\n\n if (!selectedProducts || selectedProducts.length === 0) {\n logger.warn('[processOrderDetailBulkChange] 선택된 상품이 없습니다.');\n return;\n }\n\n if (!batchOrderStatus) {\n logger.warn('[processOrderDetailBulkChange] 변경할 상태가 없습니다.');\n return;\n }\n\n // 각 옵션의 수량 결정 (데이터소스는 G7Core.dataSource API로 접근)\n const orderData = G7Core.dataSource?.get?.('order')?.data;\n const options = orderData?.options || [];\n\n const items = selectedProducts.map((optionId: number) => {\n const option = options.find((opt: { id: number }) => opt.id === optionId);\n return {\n option_id: optionId,\n quantity: changeQuantities?.[optionId] ?? option?.quantity ?? 1,\n };\n });\n\n const body: Record = {\n items,\n status: batchOrderStatus,\n };\n\n if (batchCarrierId) {\n body.carrier_id = batchCarrierId;\n }\n if (batchTrackingNumber) {\n body.tracking_number = batchTrackingNumber;\n }\n\n try {\n const response = await G7Core.api.patch(\n `/api/modules/sirsoft-ecommerce/admin/orders/${orderId}/options/bulk-status`,\n body\n );\n\n if (response?.success) {\n const t = G7Core.t;\n const changedCount = response.data?.changed_count ?? selectedProducts.length;\n G7Core.toast?.success?.(\n t?.('sirsoft-ecommerce.admin.order.detail.handler.bulk_change_success', { count: changedCount })\n ?? `${changedCount}개 옵션의 상태가 변경되었습니다.`\n );\n\n // 모달 닫기 + 데이터 새로고침 + 선택 초기화\n G7Core.modal?.close?.('modal_batch_change_confirm');\n G7Core.dispatch?.({ handler: 'refetchDataSource', params: { dataSourceId: 'order' } });\n G7Core.dispatch?.({ handler: 'refetchDataSource', params: { dataSourceId: 'order_logs' } });\n G7Core.state.setLocal({\n selectedProducts: [],\n selectAll: false,\n batchOrderStatus: '',\n batchCarrierId: '',\n batchTrackingNumber: '',\n changeQuantities: {},\n });\n } else {\n throw new Error(response?.message || '상태 변경에 실패했습니다.');\n }\n } catch (error: any) {\n logger.error('[processOrderDetailBulkChange] API 호출 실패:', error);\n // 서버 422 검증 메시지(상태 전이 차단 사유 등)를 우선 노출한다.\n // 고정 문구로 사유를 가리지 않도록 error.response.data.message 를 먼저 추출한다(confirmDeposit 동일 패턴).\n const errorData = error?.response?.data || error?.data || {};\n const serverMessage = errorData?.message || error?.message;\n G7Core.toast?.error?.(\n serverMessage\n ?? G7Core.t?.('sirsoft-ecommerce.admin.order.detail.handler.bulk_change_failed')\n ?? '상태 변경에 실패했습니다.'\n );\n }\n}\n\n/**\n * 관리자 메모 저장 핸들러\n *\n * 관리자 메모를 API를 통해 저장합니다.\n *\n * @example\n * {\n * \"type\": \"click\",\n * \"handler\": \"sirsoft-ecommerce.saveAdminMemo\",\n * \"params\": { \"orderId\": \"{{route.orderNumber}}\" }\n * }\n *\n * @param action 액션 객체 (params.orderId 필수)\n * @param _context 액션 컨텍스트\n */\nexport async function saveAdminMemoHandler(\n action: ActionWithParams<{ orderId: string | number }>,\n _context: ActionContext\n): Promise {\n const G7Core = (window as any).G7Core;\n const { orderId } = action.params || {};\n\n if (!G7Core?.state || !orderId) {\n return;\n }\n\n const local = G7Core.state.getLocal();\n const adminMemo = local.form?.admin_memo ?? '';\n\n try {\n const response = await G7Core.api.patch(\n `/api/modules/sirsoft-ecommerce/admin/orders/${orderId}`,\n { admin_memo: adminMemo }\n );\n\n if (response?.success) {\n G7Core.toast?.success?.(\n G7Core.t?.('sirsoft-ecommerce.admin.order.detail.handler.memo_save_success')\n ?? '관리자 메모가 저장되었습니다.'\n );\n } else {\n throw new Error(response?.message);\n }\n } catch (error: any) {\n logger.error('[saveAdminMemo] 저장 실패:', error);\n G7Core.toast?.error?.(error?.message ?? '메모 저장에 실패했습니다.');\n }\n}\n\n/**\n * 일괄변경 모달 내 수량 변경 핸들러\n *\n * 개별 항목의 변경 수량을 1~최대수량 범위로 클램핑하여 _local에 저장합니다.\n *\n * @example\n * {\n * \"type\": \"change\",\n * \"handler\": \"sirsoft-ecommerce.updateChangeQuantity\",\n * \"params\": { \"optionId\": \"{{confirmItem.id}}\", \"maxQuantity\": \"{{confirmItem.quantity}}\" }\n * }\n *\n * @param action 액션 객체 (params.optionId, params.maxQuantity 필수)\n * @param _context 액션 컨텍스트\n */\nexport function updateChangeQuantityHandler(\n action: ActionWithParams<{ optionId: number; maxQuantity: number; value?: number }>,\n _context: ActionContext\n): void {\n const G7Core = (window as any).G7Core;\n if (!G7Core?.state) return;\n\n const { optionId, maxQuantity } = action.params || {};\n if (!optionId || !maxQuantity) return;\n\n // $event.target.value 또는 params.value에서 입력값 추출\n const rawValue = action.params?.value ?? (action as any).$event?.target?.value;\n const parsed = parseInt(String(rawValue), 10);\n const clamped = Math.max(1, Math.min(isNaN(parsed) ? maxQuantity : parsed, maxQuantity));\n\n const local = G7Core.state.getLocal();\n const currentQuantities = { ...(local.changeQuantities || {}) };\n currentQuantities[optionId] = clamped;\n\n G7Core.state.setLocal({ changeQuantities: currentQuantities });\n}\n\n/**\n * 무통장 입금확인 모달 열기 핸들러\n *\n * 모달(modals 섹션)은 격리된 scope 라 모달 내부에서 setState($parent._local) 한 값이\n * 페이지 globalLocal(핸들러 getLocal()/setLocal() 대상)과 단절된다(엔진 모달 scope 제한).\n * 이 때문에 모달 onMount setState 로 입력 시드를 깔면 입력값/검증에러가 페이지 scope 와\n * 어긋나 입금액 미전송·인라인 에러 미표시가 발생한다.\n *\n * 주문취소 모달과 동일하게, 모달을 열기 전에 페이지 _local 에 입력 시드를 setLocal() 로\n * 미리 깔고 modal.open() 한다. 이렇게 하면 모달이 페이지 _local 을 그대로 상속하여\n * 모달 input(`_local.*`, setState target:\"local\")·인라인 에러·핸들러가 모두 같은\n * 페이지 scope 를 공유한다.\n *\n * @example\n * {\n * \"type\": \"click\",\n * \"handler\": \"sirsoft-ecommerce.openConfirmDepositModal\"\n * }\n *\n * @param _action 액션 객체\n * @param _context 액션 컨텍스트\n */\nexport function openConfirmDepositModalHandler(\n _action: ActionWithParams,\n _context: ActionContext\n): void {\n const G7Core = (window as any).G7Core;\n if (!G7Core?.state) {\n logger.warn('[openConfirmDepositModal] G7Core.state 를 사용할 수 없습니다.');\n return;\n }\n\n const orderData = G7Core.dataSource?.get?.('order')?.data;\n const depositorName = orderData?.depositor_name ?? '';\n const depositAmount = orderData?.total_due_amount ?? 0;\n\n // 모달 열기 전 페이지 _local 에 입력 시드 + 상태 초기화 (취소 모달과 동일 패턴)\n G7Core.state.setLocal({\n depositorName,\n depositAmount,\n depositConfirmErrors: null,\n isConfirmingDeposit: false,\n markOrderComplete: false,\n });\n G7Core.modal?.open?.('modal_confirm_deposit');\n}\n\n/**\n * 무통장 입금확인 핸들러\n *\n * 무통장(dbank) 미결제 주문의 입금자명·입금액을 확인하여 결제완료 처리한다.\n * 모달은 페이지 _local 을 상속하므로(openConfirmDepositModalHandler 가 모달 열기 전\n * setLocal() 로 시드) 입력값은 getLocal() 로 일관되게 읽을 수 있고, 모달이 표현식으로\n * 박아 넣은 action.params 도 함께 받는다(이중 안전). 선언적 apiCall + onError(sequence\n * 내부 $parent._local setState + toast) 패턴은 setState 와 toast 의 글로벌 상태 업데이트가\n * 경합하여 422 에러 토스트/인라인 에러가 소실되는 문제가 있어(입금확인 검수 결함), 주문취소\n * 모달(executeCancelOrder)과 동일하게 JS 핸들러로 422 를 직접 처리한다.\n *\n * 428(본인인증 필요)은 G7Core.api 가 IdentityGuardInterceptor 를 거치지 않으므로\n * 여기서 명시 분기하여 본인인증 모달 launcher 를 직접 호출한다(검수 결함 N3 의 입금확인 경로 대응).\n *\n * @example\n * {\n * \"handler\": \"sirsoft-ecommerce.confirmDeposit\",\n * \"params\": { \"orderId\": \"{{route.orderNumber}}\", \"amount\": \"{{_local.depositAmount}}\" }\n * }\n *\n * @param action 액션 객체 (params.orderId 필수)\n * @param _context 액션 컨텍스트\n */\nexport async function confirmDepositHandler(\n action: ActionWithParams<{\n orderId: string | number;\n amount?: string | number;\n depositorName?: string;\n markOrderComplete?: boolean | string;\n }>,\n _context: ActionContext\n): Promise {\n const G7Core = (window as any).G7Core;\n const { orderId } = action.params || {};\n\n if (!G7Core?.state || !orderId) {\n logger.warn('[confirmDeposit] orderId 가 없습니다.');\n return;\n }\n\n // 입금액·입금자명은 모달 컨텍스트에서 해석된 action.params 로 직접 전달받는다.\n // 모달(modals 섹션)은 격리된 scope 라 모달 input 이 쓰는 $parent._local 값이\n // 페이지 globalLocal(getLocal() 반환 대상)에 도달하지 않는다(엔진 모달 scope 제한).\n // 따라서 getLocal() 로 읽으면 amount=undefined → 422(\"입금액 필수\") 가 되므로,\n // 취소 모달(executeCancelOrder)과 동일하게 모달 표현식이 박아 넣은 params 를 SSoT 로 쓴다.\n // params 미전달 시에만 페이지 _local 로 폴백(하위호환).\n const local = G7Core.state.getLocal();\n const amount = action.params?.amount ?? local.depositAmount;\n const depositorName = action.params?.depositorName ?? local.depositorName ?? '';\n // 체크박스 표현식은 boolean 또는 문자열(\"true\"/\"false\")로 도착할 수 있어 정규화한다.\n const rawMarkComplete = action.params?.markOrderComplete ?? local.markOrderComplete ?? false;\n const markOrderComplete = rawMarkComplete === true || rawMarkComplete === 'true';\n\n G7Core.state.setLocal({ isConfirmingDeposit: true, depositConfirmErrors: null });\n\n try {\n const response = await G7Core.api.patch(\n `/api/modules/sirsoft-ecommerce/admin/orders/${orderId}/confirm-deposit`,\n { amount, depositor_name: depositorName, mark_order_complete: markOrderComplete }\n );\n\n if (response?.success) {\n G7Core.state.setLocal({ isConfirmingDeposit: false, depositConfirmErrors: null });\n G7Core.toast?.success?.(\n G7Core.t?.('sirsoft-ecommerce.admin.order.detail.modal.confirm_deposit.confirm_success')\n ?? '입금이 확인되어 결제완료 처리되었습니다.'\n );\n G7Core.modal?.close?.('modal_confirm_deposit');\n G7Core.dispatch?.({ handler: 'refetchDataSource', params: { dataSourceId: 'order' } });\n G7Core.dispatch?.({ handler: 'refetchDataSource', params: { dataSourceId: 'order_logs' } });\n } else {\n throw new Error(response?.message || 'Deposit confirm failed');\n }\n } catch (error: any) {\n logger.error('[confirmDeposit] 실패:', error);\n\n const errorData = error?.response?.data || error?.data || {};\n // 428(본인인증 필요)은 ApiClient(G7Core.api) 인터셉터가 중앙에서 처리한다 —\n // 본인인증 모달 → verify → confirm-deposit 자동 재실행 후 성공 응답이 위 try 의 response 로\n // 돌아오므로(아래 catch 에 도달하지 않음), 여기서 별도 428 분기는 불필요(이중 모달 방지).\n const detailMessage = errorData?.errors?.detail || errorData?.message || error?.message || '';\n const fallbackMessage = G7Core.t?.('sirsoft-ecommerce.admin.order.detail.modal.confirm_deposit.confirm_failed')\n ?? '입금 확인에 실패했습니다.';\n\n // 422 등 검증 에러: 인라인 에러(amount 필드) + 토스트\n G7Core.state.setLocal({\n isConfirmingDeposit: false,\n depositConfirmErrors: { amount: [detailMessage || fallbackMessage] },\n });\n G7Core.toast?.error?.(detailMessage || fallbackMessage);\n }\n}\n","/**\n * 주문 취소 관련 핸들러\n *\n * 관리자 주문상세에서 주문 취소 모달의 커스텀 핸들러들을 정의합니다.\n * - 환불 예상금액 계산 (debounce)\n * - 취소 수량 변경\n * - 주문 취소 실행\n */\n\nimport type { ActionContext } from '../types';\n\nconst logger = ((window as any).G7Core?.createLogger?.('Ecom:CancelOrder')) ?? {\n log: (...args: unknown[]) => console.log('[Ecom:CancelOrder]', ...args),\n warn: (...args: unknown[]) => console.warn('[Ecom:CancelOrder]', ...args),\n error: (...args: unknown[]) => console.error('[Ecom:CancelOrder]', ...args),\n};\n\ninterface ActionWithParams> {\n handler: string;\n params?: T;\n [key: string]: any;\n}\n\n/** Debounce 타이머 */\nlet estimateDebounceTimer: ReturnType | null = null;\n\n/**\n * Debounce 타이머 정리\n *\n * 모달 닫기/페이지 이동 시 pending debounce를 취소하여\n * 잘못된 컨텍스트에서 setLocal() 호출되는 것을 방지합니다.\n */\nexport function clearCancelOrderTimers(): void {\n if (estimateDebounceTimer) {\n clearTimeout(estimateDebounceTimer);\n estimateDebounceTimer = null;\n logger.log('[clearCancelOrderTimers] debounce 타이머 정리 완료');\n }\n}\n\n/**\n * 취소 수량 변경 핸들러\n *\n * 개별 취소 항목의 수량을 변경하고, debounce로 환불 예상금액을 재계산합니다.\n *\n * @example\n * {\n * \"handler\": \"sirsoft-ecommerce.updateCancelQuantity\",\n * \"params\": { \"optionId\": 1, \"maxQuantity\": 5, \"orderId\": \"ORD-001\" }\n * }\n *\n * @param action 액션 객체\n * @param _context 액션 컨텍스트\n */\nexport function updateCancelQuantityHandler(\n action: ActionWithParams<{ optionId: number; maxQuantity: number; orderId: string }>,\n _context: ActionContext\n): void {\n const G7Core = (window as any).G7Core;\n if (!G7Core?.state) return;\n\n const { optionId, maxQuantity, orderId } = action.params || {};\n if (!optionId || !maxQuantity || !orderId) return;\n\n const rawValue = action.params?.value ?? (action as any).$event?.target?.value;\n const parsed = parseInt(String(rawValue), 10);\n const clamped = Math.max(1, Math.min(isNaN(parsed) ? maxQuantity : parsed, maxQuantity));\n\n // cancelItems 배열에서 해당 옵션의 수량 업데이트\n const local = G7Core.state.getLocal();\n const cancelItems = (local.cancelItems || []).map((item: any) => {\n if (item.id === optionId) {\n return { ...item, cancel_quantity: clamped };\n }\n return item;\n });\n\n G7Core.state.setLocal({ cancelItems });\n\n // Debounce로 환불 예상금액 재계산\n // engine-v1.24.7: ActionDispatcher의 try/finally가 debounce 콜백 전에 __g7ActionContext를\n // 복원하므로, 콜백 실행 시점에는 모달의 actionContext가 사라짐.\n // 캡처하여 복원해야 setLocal()이 모달의 actionContext.setState()를 호출할 수 있음.\n const savedActionContext = (window as any).__g7ActionContext;\n if (estimateDebounceTimer) {\n clearTimeout(estimateDebounceTimer);\n }\n estimateDebounceTimer = setTimeout(() => {\n const previousContext = (window as any).__g7ActionContext;\n (window as any).__g7ActionContext = savedActionContext;\n try {\n estimateRefundAmountInternal(G7Core, orderId);\n } finally {\n (window as any).__g7ActionContext = previousContext;\n }\n }, 500);\n}\n\n/**\n * 환불 예상금액 계산 핸들러\n *\n * estimate-refund API를 호출하여 환불 예상금액을 조회합니다.\n *\n * @example\n * {\n * \"handler\": \"sirsoft-ecommerce.estimateRefundAmount\",\n * \"params\": { \"orderId\": \"ORD-001\" }\n * }\n *\n * @param action 액션 객체\n * @param _context 액션 컨텍스트\n */\nexport async function estimateRefundAmountHandler(\n action: ActionWithParams<{ orderId: string; cancelItems?: any[] }>,\n _context: ActionContext\n): Promise {\n const G7Core = (window as any).G7Core;\n if (!G7Core?.state) return;\n\n const { orderId, cancelItems } = action.params || {};\n if (!orderId) return;\n\n await estimateRefundAmountInternal(G7Core, orderId, cancelItems);\n}\n\n/**\n * 환불 예상금액 내부 계산 함수\n *\n * @param G7Core G7Core 인스턴스\n * @param orderId 주문번호\n */\nasync function estimateRefundAmountInternal(G7Core: any, orderId: string, paramCancelItems?: any[]): Promise {\n const local = G7Core.state.getLocal();\n const cancelItems = paramCancelItems || local.cancelItems || [];\n const refundPriority = local.refundPriority || 'pg_first';\n\n if (cancelItems.length === 0) {\n G7Core.state.setLocal({ refundEstimate: null });\n return;\n }\n\n const items = cancelItems.map((item: any) => ({\n order_option_id: item.id,\n cancel_quantity: item.cancel_quantity,\n }));\n\n G7Core.state.setLocal({ refundLoading: true });\n\n try {\n const response = await G7Core.api.post(\n `/api/modules/sirsoft-ecommerce/admin/orders/${orderId}/estimate-refund`,\n { items, refund_priority: refundPriority }\n );\n\n if (response?.success) {\n G7Core.state.setLocal({\n refundEstimate: response.data,\n refundLoading: false,\n cancelError: null,\n });\n } else {\n throw new Error(response?.message || 'Estimate failed');\n }\n } catch (error: any) {\n logger.error('[estimateRefundAmount] 실패:', error);\n G7Core.state.setLocal({\n refundLoading: false,\n refundEstimate: null,\n });\n G7Core.toast?.error?.(\n G7Core.t?.('sirsoft-ecommerce.admin.order.detail.modal.cancel.estimate_failed')\n ?? '환불 예상금액 계산에 실패했습니다.'\n );\n }\n}\n\n/**\n * 환불 우선순위 변경 핸들러\n *\n * 환불 우선순위를 변경하고, debounce로 환불 예상금액을 재계산합니다.\n *\n * @example\n * {\n * \"handler\": \"sirsoft-ecommerce.changeRefundPriority\",\n * \"params\": { \"priority\": \"points_first\", \"orderId\": \"ORD-001\" }\n * }\n *\n * @param action 액션 객체\n * @param _context 액션 컨텍스트\n */\nexport function changeRefundPriorityHandler(\n action: ActionWithParams<{ priority: string; orderId: string }>,\n _context: ActionContext\n): void {\n const G7Core = (window as any).G7Core;\n if (!G7Core?.state) return;\n\n const { priority, orderId } = action.params || {};\n if (!priority || !orderId) return;\n\n G7Core.state.setLocal({ refundPriority: priority });\n\n // Debounce로 환불 예상금액 재계산\n // engine-v1.24.7: debounce 콜백에서 actionContext 캡처/복원\n const savedActionContext = (window as any).__g7ActionContext;\n if (estimateDebounceTimer) {\n clearTimeout(estimateDebounceTimer);\n }\n estimateDebounceTimer = setTimeout(() => {\n const previousContext = (window as any).__g7ActionContext;\n (window as any).__g7ActionContext = savedActionContext;\n try {\n estimateRefundAmountInternal(G7Core, orderId);\n } finally {\n (window as any).__g7ActionContext = previousContext;\n }\n }, 300);\n}\n\n/**\n * 주문 취소 실행 핸들러\n *\n * cancel API를 호출하여 주문을 취소합니다.\n * PG 실패 시 cancelError를 설정하고 모달을 유지합니다.\n *\n * @example\n * {\n * \"handler\": \"sirsoft-ecommerce.executeCancelOrder\",\n * \"params\": { \"orderId\": \"ORD-001\" }\n * }\n *\n * @param action 액션 객체\n * @param _context 액션 컨텍스트\n */\nexport async function executeCancelOrderHandler(\n action: ActionWithParams<{\n orderId: string;\n cancelItems?: any[];\n cancelReason?: string;\n cancelReasonDetail?: string;\n cancelPg?: boolean;\n refundPriority?: string;\n }>,\n _context: ActionContext\n): Promise {\n const G7Core = (window as any).G7Core;\n if (!G7Core?.state) return;\n\n const { orderId } = action.params || {};\n if (!orderId) return;\n\n const local = G7Core.state.getLocal();\n const cancelItems = action.params?.cancelItems || local.cancelItems || [];\n const cancelReason = action.params?.cancelReason || local.cancelReason || '';\n const cancelReasonDetail = action.params?.cancelReasonDetail || local.cancelReasonDetail || '';\n const cancelPg = action.params?.cancelPg ?? local.cancelPg ?? true;\n const refundPriority = action.params?.refundPriority || local.refundPriority || 'pg_first';\n\n // 선택한 항목을 항상 items(type:partial)로 전송한다. 프론트에서 전체취소를 휴리스틱으로 판단하지 않는다.\n // 진짜 전체취소(모든 활성 옵션 전량)도 items 전량으로 보내면 백엔드 shouldConvertToFullCancel 이 FULL 로\n // 승격해 동작·환불 결과가 동일하다. 종전 라인수+수량 휴리스틱은 단일 항목 주문을 항상 full 로 처리해\n // 수량을 줄여도 전량 취소되는 결함이 있었고(MP03 §9 발견#2), 유저 핸들러와도 판정 기준이 어긋났다 (#397 U20①).\n const body: Record = {\n type: 'partial',\n cancel_pg: cancelPg,\n refund_priority: refundPriority,\n items: cancelItems.map((item: any) => ({\n order_option_id: item.id,\n cancel_quantity: item.cancel_quantity,\n })),\n };\n\n if (cancelReason) {\n body.reason = cancelReason;\n }\n if (cancelReasonDetail) {\n body.reason_detail = cancelReasonDetail;\n }\n\n G7Core.state.setLocal({ isCancelling: true, cancelError: null, cancelValidationErrors: null });\n\n try {\n const response = await G7Core.api.post(\n `/api/modules/sirsoft-ecommerce/admin/orders/${orderId}/cancel`,\n body\n );\n\n if (response?.success) {\n G7Core.state.setLocal({ isCancelling: false });\n G7Core.toast?.success?.(\n G7Core.t?.('sirsoft-ecommerce.admin.order.detail.modal.cancel.cancel_success')\n ?? '주문이 취소되었습니다.'\n );\n\n G7Core.modal?.close?.('modal_cancel_order');\n G7Core.dispatch?.({ handler: 'refetchDataSource', params: { dataSourceId: 'order' } });\n G7Core.dispatch?.({ handler: 'refetchDataSource', params: { dataSourceId: 'order_logs' } });\n\n // 선택 초기화\n G7Core.state.setLocal({\n selectedProducts: [],\n selectAll: false,\n batchOrderStatus: '',\n });\n } else {\n throw new Error(response?.message || 'Cancel failed');\n }\n } catch (error: any) {\n logger.error('[executeCancelOrder] 실패:', error);\n\n const errorData = (error as any)?.response?.data || (error as any)?.data;\n const httpStatus = (error as any)?.response?.status || (error as any)?.status;\n // 428(본인인증 필요)은 ApiClient(G7Core.api) 인터셉터가 중앙에서 처리한다 —\n // 본인인증 모달 → verify → 원 요청 자동 재실행 후 성공 응답이 이 try 로 돌아오므로\n // 여기서 별도 428 분기는 불필요(이중 모달 방지). 사용자가 인증 취소 시에만 에러로 전파됨.\n const isValidationError = httpStatus === 422;\n const pgError = errorData?.errors?.detail;\n const validationErrors = errorData?.errors ?? null;\n const detailMessage = pgError || errorData?.message || error?.message || '';\n const fallbackMessage = G7Core.t?.('sirsoft-ecommerce.admin.order.detail.modal.cancel.cancel_failed')\n ?? '주문 취소에 실패했습니다.';\n\n if (isValidationError) {\n // 422 Validation 에러: 필드별 에러만 표시 (PG 에러 영역 미사용)\n G7Core.state.setLocal({\n isCancelling: false,\n cancelError: null,\n cancelValidationErrors: validationErrors,\n });\n G7Core.toast?.error?.(detailMessage || fallbackMessage);\n } else {\n // PG 에러 등 기타 에러: PG 에러 영역에 표시\n G7Core.state.setLocal({\n isCancelling: false,\n cancelError: detailMessage || fallbackMessage,\n cancelValidationErrors: null,\n });\n G7Core.toast?.error?.(detailMessage || fallbackMessage);\n }\n }\n}\n","/**\n * 사용자 주문 취소 관련 핸들러\n *\n * 사용자 마이페이지 주문상세에서 주문 취소 모달의 커스텀 핸들러들을 정의합니다.\n * - 취소 항목 초기화\n * - 항목 선택/해제\n * - 취소 수량 변경\n * - 환불 예상금액 계산 (debounce)\n * - 주문 취소 실행\n */\n\nimport type { ActionContext } from '../types';\n\nconst logger = ((window as any).G7Core?.createLogger?.('Ecom:UserCancelOrder')) ?? {\n log: (...args: unknown[]) => console.log('[Ecom:UserCancelOrder]', ...args),\n warn: (...args: unknown[]) => console.warn('[Ecom:UserCancelOrder]', ...args),\n error: (...args: unknown[]) => console.error('[Ecom:UserCancelOrder]', ...args),\n};\n\ninterface ActionWithParams> {\n handler: string;\n params?: T;\n [key: string]: any;\n}\n\ninterface CancelItem {\n id: number;\n product_name: string;\n product_option_name: string;\n thumbnail_url: string;\n unit_price: number;\n quantity: number;\n cancel_quantity: number;\n option_status: string;\n selected: boolean;\n}\n\n/** Debounce 타이머 */\nlet userEstimateDebounceTimer: ReturnType | null = null;\n\n/**\n * 비회원 컨텍스트 식별자 확보 여부 — _global.guestOrderToken 과 dataSource('order').data.order_number 가 모두 있으면 true.\n * 비회원 응답(GuestOrderResource)은 id 미노출이므로 회원 식별자(orderId) 가 비어 있어도 본 값으로 비회원 endpoint 호출 가능.\n */\nfunction hasGuestOrderContext(G7Core: any): boolean {\n const token = G7Core?.state?.get?.('_global')?.guestOrderToken;\n const orderNumber = G7Core?.dataSource?.get?.('order')?.data?.order_number;\n return Boolean(token && orderNumber);\n}\n\n/**\n * Debounce 타이머 정리 (사용자 취소 모달)\n *\n * 모달 닫기/페이지 이동 시 pending debounce를 취소합니다.\n */\nexport function clearUserCancelOrderTimers(): void {\n if (userEstimateDebounceTimer) {\n clearTimeout(userEstimateDebounceTimer);\n userEstimateDebounceTimer = null;\n }\n}\n\n/**\n * 주문 상품 목록에서 개별 아이템 선택/해제 핸들러\n *\n * 주문상세 상품 목록의 체크박스를 토글합니다.\n * selectedItemIds 배열에서 optionId를 추가/제거합니다.\n *\n * @param action 액션 객체 (params.optionId: 토글할 옵션 ID)\n * @param _context 액션 컨텍스트\n */\nexport function toggleItemSelectionHandler(\n action: ActionWithParams<{ optionId: number }>,\n _context: ActionContext\n): void {\n const G7Core = (window as any).G7Core;\n if (!G7Core?.state) return;\n\n const { optionId } = action.params || {};\n if (!optionId) return;\n\n const local = G7Core.state.getLocal();\n const selectedIds: number[] = [...(local.selectedItemIds || [])];\n const idx = selectedIds.indexOf(optionId);\n\n if (idx >= 0) {\n selectedIds.splice(idx, 1);\n } else {\n selectedIds.push(optionId);\n }\n\n // 취소 가능한 모든 아이템이 선택되었는지 확인\n const orderData = G7Core.dataSource?.get?.('order')?.data;\n const cancellableIds = (orderData?.options || [])\n .filter((opt: any) => opt.option_status !== 'cancelled')\n .map((opt: any) => opt.id);\n const allSelected = cancellableIds.length > 0 &&\n cancellableIds.every((id: number) => selectedIds.includes(id));\n\n G7Core.state.setLocal({\n selectedItemIds: selectedIds,\n selectAllItems: allSelected,\n });\n}\n\n/**\n * 주문 상품 목록에서 전체 선택/해제 핸들러\n *\n * 취소 가능한 모든 아이템을 선택하거나 전체 해제합니다.\n *\n * @param _action 액션 객체\n * @param _context 액션 컨텍스트\n */\nexport function toggleSelectAllItemsHandler(\n _action: ActionWithParams,\n _context: ActionContext\n): void {\n const G7Core = (window as any).G7Core;\n if (!G7Core?.state) return;\n\n const local = G7Core.state.getLocal();\n const orderData = G7Core.dataSource?.get?.('order')?.data;\n const cancellableIds: number[] = (orderData?.options || [])\n .filter((opt: any) => opt.option_status !== 'cancelled')\n .map((opt: any) => opt.id);\n\n const newSelectAll = !local.selectAllItems;\n\n G7Core.state.setLocal({\n selectedItemIds: newSelectAll ? cancellableIds : [],\n selectAllItems: newSelectAll,\n });\n}\n\n/**\n * 사용자 취소 항목 초기화 핸들러\n *\n * 주문 상품 목록에서 선택된 아이템(selectedItemIds)을 기반으로\n * cancelItems를 구성하고, 취소 모달을 엽니다.\n *\n * @param action 액션 객체\n * @param _context 액션 컨텍스트\n */\nexport function initUserCancelItemsHandler(\n action: ActionWithParams<{ orderId: number | string }>,\n _context: ActionContext\n): void {\n const G7Core = (window as any).G7Core;\n if (!G7Core?.state) return;\n\n const orderData = G7Core.dataSource?.get?.('order')?.data;\n if (!orderData) return;\n\n const local = G7Core.state.getLocal();\n const selectedItemIds: number[] = local.selectedItemIds || [];\n\n // 선택된 아이템이 없으면 경고\n if (selectedItemIds.length === 0) {\n G7Core.toast?.warning?.(\n G7Core.t?.('mypage.order_detail.cancel_modal.no_selected_items')\n ?? '취소할 상품을 선택해주세요.'\n );\n return;\n }\n\n const options = orderData.options || [];\n const cancelItems: CancelItem[] = options\n .filter((opt: any) => selectedItemIds.includes(opt.id) && opt.option_status !== 'cancelled')\n .map((opt: any) => ({\n id: opt.id,\n product_name: opt.product_name ?? '',\n product_option_name: opt.product_option_name ?? '',\n thumbnail_url: opt.product_snapshot?.thumbnail_url || '',\n unit_price: opt.unit_price ?? 0,\n quantity: opt.quantity ?? 1,\n cancel_quantity: opt.quantity ?? 1,\n option_status: opt.option_status ?? '',\n selected: true,\n }));\n\n if (cancelItems.length === 0) {\n G7Core.toast?.warning?.(\n G7Core.t?.('mypage.order_detail.cancel_modal.no_cancellable_items')\n ?? '취소 가능한 상품이 없습니다.'\n );\n return;\n }\n\n G7Core.state.setLocal({\n cancelItems,\n cancelSelectAll: true,\n cancelReason: '',\n refundEstimate: null,\n refundLoading: false,\n isCancelling: false,\n cancelError: null,\n });\n\n // modals 섹션 모달 열기\n G7Core.modal?.open?.('modal_cancel_order');\n\n // 초기 환불 예상금액 계산\n // 비회원 응답(GuestOrderResource)은 id 미노출 — order_number 만 있어도 estimateUserRefundInternal 내부에서 비회원 URL 분기\n const orderId = action.params?.orderId || orderData.id;\n if (orderId || orderData.order_number) {\n estimateUserRefundInternal(G7Core, orderId);\n }\n}\n\n/**\n * 사용자 취소 항목 선택/해제 핸들러\n *\n * @param action 액션 객체 (params.optionId: 토글할 옵션 ID, params.orderId: 주문 ID)\n * @param _context 액션 컨텍스트\n */\nexport function toggleUserCancelItemHandler(\n action: ActionWithParams<{ optionId: number; orderId: number | string }>,\n _context: ActionContext\n): void {\n const G7Core = (window as any).G7Core;\n if (!G7Core?.state) return;\n\n const { optionId, orderId } = action.params || {};\n if (!optionId) return;\n\n const local = G7Core.state.getLocal();\n const cancelItems: CancelItem[] = (local.cancelItems || []).map((item: CancelItem) => {\n if (item.id === optionId) {\n return { ...item, selected: !item.selected };\n }\n return item;\n });\n\n const allSelected = cancelItems.every((item: CancelItem) => item.selected);\n\n G7Core.state.setLocal({\n cancelItems,\n cancelSelectAll: allSelected,\n });\n\n // 환불 예상금액 재계산\n if (userEstimateDebounceTimer) {\n clearTimeout(userEstimateDebounceTimer);\n }\n userEstimateDebounceTimer = setTimeout(() => {\n estimateUserRefundInternal(G7Core, orderId);\n }, 500);\n}\n\n/**\n * 사용자 취소 전체 선택/해제 핸들러\n *\n * @param action 액션 객체 (params.orderId: 주문 ID)\n * @param _context 액션 컨텍스트\n */\nexport function toggleUserCancelSelectAllHandler(\n action: ActionWithParams<{ orderId: number | string }>,\n _context: ActionContext\n): void {\n const G7Core = (window as any).G7Core;\n if (!G7Core?.state) return;\n\n const { orderId } = action.params || {};\n const local = G7Core.state.getLocal();\n const newSelectAll = !local.cancelSelectAll;\n\n const cancelItems: CancelItem[] = (local.cancelItems || []).map((item: CancelItem) => ({\n ...item,\n selected: newSelectAll,\n }));\n\n G7Core.state.setLocal({\n cancelItems,\n cancelSelectAll: newSelectAll,\n });\n\n // 환불 예상금액 재계산\n if (userEstimateDebounceTimer) {\n clearTimeout(userEstimateDebounceTimer);\n }\n userEstimateDebounceTimer = setTimeout(() => {\n estimateUserRefundInternal(G7Core, orderId);\n }, 500);\n}\n\n/**\n * 사용자 취소 수량 변경 핸들러\n *\n * @param action 액션 객체 (params.optionId, params.maxQuantity, params.orderId)\n * @param _context 액션 컨텍스트\n */\nexport function updateUserCancelQuantityHandler(\n action: ActionWithParams<{ optionId: number; maxQuantity: number; orderId: number | string }>,\n _context: ActionContext\n): void {\n const G7Core = (window as any).G7Core;\n if (!G7Core?.state) return;\n\n const { optionId, maxQuantity, orderId } = action.params || {};\n // 회원: orderId 필수. 비회원: orderId 없어도 토큰+order_number 로 비회원 endpoint 분기.\n if (!optionId || !maxQuantity || (!orderId && !hasGuestOrderContext(G7Core))) return;\n\n const rawValue = action.params?.value ?? (action as any).$event?.target?.value;\n const parsed = parseInt(String(rawValue), 10);\n const clamped = Math.max(1, Math.min(isNaN(parsed) ? maxQuantity : parsed, maxQuantity));\n\n const local = G7Core.state.getLocal();\n const cancelItems: CancelItem[] = (local.cancelItems || []).map((item: CancelItem) => {\n if (item.id === optionId) {\n return { ...item, cancel_quantity: clamped };\n }\n return item;\n });\n\n G7Core.state.setLocal({ cancelItems });\n\n // Debounce로 환불 예상금액 재계산\n // engine-v1.24.7: ActionDispatcher의 try/finally가 debounce 콜백 전에 __g7ActionContext를\n // 복원하므로, 콜백 실행 시점에는 모달의 actionContext가 사라짐.\n // 캡처하여 복원해야 setLocal()이 모달의 actionContext.setState()를 호출할 수 있음.\n const savedActionContext = (window as any).__g7ActionContext;\n if (userEstimateDebounceTimer) {\n clearTimeout(userEstimateDebounceTimer);\n }\n userEstimateDebounceTimer = setTimeout(() => {\n const previousContext = (window as any).__g7ActionContext;\n (window as any).__g7ActionContext = savedActionContext;\n try {\n estimateUserRefundInternal(G7Core, orderId);\n } finally {\n (window as any).__g7ActionContext = previousContext;\n }\n }, 500);\n}\n\n/**\n * 사용자 환불 예상금액 계산 핸들러\n *\n * @param action 액션 객체 (params.orderId: 주문 ID)\n * @param _context 액션 컨텍스트\n */\nexport async function estimateUserRefundHandler(\n action: ActionWithParams<{ orderId: number | string }>,\n _context: ActionContext\n): Promise {\n const G7Core = (window as any).G7Core;\n if (!G7Core?.state) return;\n\n const { orderId } = action.params || {};\n // 회원: orderId 필수. 비회원: orderId 없어도 토큰+order_number 로 estimateUserRefundInternal 가 비회원 URL 분기.\n if (!orderId && !hasGuestOrderContext(G7Core)) return;\n\n await estimateUserRefundInternal(G7Core, orderId);\n}\n\n/**\n * 사용자 환불 예상금액 내부 계산 함수\n *\n * @param G7Core G7Core 인스턴스\n * @param orderId 주문 ID\n */\nasync function estimateUserRefundInternal(G7Core: any, orderId: number | string): Promise {\n const local = G7Core.state.getLocal();\n const cancelItems: CancelItem[] = local.cancelItems || [];\n const refundPriority = local.refundPriority || 'pg_first';\n\n const selectedItems = cancelItems.filter((item: CancelItem) => item.selected);\n\n if (selectedItems.length === 0) {\n G7Core.state.setLocal({ refundEstimate: null, refundLoading: false });\n return;\n }\n\n const items = selectedItems.map((item: CancelItem) => ({\n order_option_id: item.id,\n cancel_quantity: item.cancel_quantity,\n }));\n\n G7Core.state.setLocal({ refundLoading: true });\n\n try {\n const guestToken = G7Core.state.get?.('_global')?.guestOrderToken;\n const orderNumber = G7Core.dataSource?.get?.('order')?.data?.order_number;\n const isGuest = Boolean(guestToken && orderNumber);\n const url = isGuest\n ? `/api/modules/sirsoft-ecommerce/guest/orders/${orderNumber}/estimate-refund`\n : `/api/modules/sirsoft-ecommerce/user/orders/${orderId}/estimate-refund`;\n // ApiClient(axios) 인터셉터는 globalHeaders 를 적용하지 않으므로 비회원 endpoint 호출 시 토큰 헤더를 명시적으로 첨부.\n const config = isGuest ? { headers: { 'X-Guest-Order-Token': guestToken } } : undefined;\n\n const response = await G7Core.api.post(\n url,\n { items, refund_priority: refundPriority },\n config\n );\n\n if (response?.success) {\n G7Core.state.setLocal({\n refundEstimate: response.data,\n refundLoading: false,\n cancelError: null,\n });\n } else {\n throw new Error(response?.message || 'Estimate failed');\n }\n } catch (error: any) {\n logger.error('[estimateUserRefund] 실패:', error);\n G7Core.state.setLocal({\n refundLoading: false,\n refundEstimate: null,\n });\n G7Core.toast?.error?.(\n G7Core.t?.('mypage.order_detail.cancel_modal.estimate_failed')\n ?? '환불 예상금액 계산에 실패했습니다.'\n );\n }\n}\n\n/**\n * 사용자 환불 우선순위 변경 핸들러\n *\n * 환불 우선순위를 변경하고, debounce로 환불 예상금액을 재계산합니다.\n *\n * @example\n * {\n * \"handler\": \"sirsoft-ecommerce.changeUserRefundPriority\",\n * \"params\": { \"priority\": \"points_first\", \"orderId\": 123 }\n * }\n *\n * @param action 액션 객체\n * @param _context 액션 컨텍스트\n */\nexport function changeUserRefundPriorityHandler(\n action: ActionWithParams<{ priority: string; orderId: number | string }>,\n _context: ActionContext\n): void {\n const G7Core = (window as any).G7Core;\n if (!G7Core?.state) return;\n\n const { priority, orderId } = action.params || {};\n // 회원: orderId 필수. 비회원: orderId 없어도 토큰+order_number 로 비회원 endpoint 분기.\n if (!priority || (!orderId && !hasGuestOrderContext(G7Core))) return;\n\n G7Core.state.setLocal({ refundPriority: priority });\n\n // Debounce로 환불 예상금액 재계산\n // engine-v1.24.7: debounce 콜백에서 actionContext 캡처/복원\n const savedActionContext = (window as any).__g7ActionContext;\n if (userEstimateDebounceTimer) {\n clearTimeout(userEstimateDebounceTimer);\n }\n userEstimateDebounceTimer = setTimeout(() => {\n const previousContext = (window as any).__g7ActionContext;\n (window as any).__g7ActionContext = savedActionContext;\n try {\n estimateUserRefundInternal(G7Core, orderId);\n } finally {\n (window as any).__g7ActionContext = previousContext;\n }\n }, 300);\n}\n\n/**\n * 사용자 주문 취소 실행 핸들러\n *\n * @param action 액션 객체 (params.orderId: 주문 ID)\n * @param _context 액션 컨텍스트\n */\nexport async function executeUserCancelOrderHandler(\n action: ActionWithParams<{ orderId: number | string; cancelReason?: string; cancelReasonDetail?: string; refundPriority?: string }>,\n _context: ActionContext\n): Promise {\n const G7Core = (window as any).G7Core;\n if (!G7Core?.state) return;\n\n const { orderId } = action.params || {};\n // 회원: orderId 필수. 비회원: orderId 없어도 토큰+order_number 로 비회원 endpoint 분기.\n if (!orderId && !hasGuestOrderContext(G7Core)) return;\n\n const local = G7Core.state.getLocal();\n const cancelItems: CancelItem[] = local.cancelItems || [];\n const selectedItems = cancelItems.filter((item: CancelItem) => item.selected);\n // cancelReason/refundPriority: params 우선, fallback으로 local (모달 스코프 호환)\n const cancelReason = action.params?.cancelReason || local.cancelReason || '';\n const cancelReasonDetail = action.params?.cancelReasonDetail ?? local.cancelReasonDetail ?? '';\n const refundPriority = action.params?.refundPriority || local.refundPriority || 'pg_first';\n\n if (selectedItems.length === 0) {\n G7Core.toast?.warning?.(\n G7Core.t?.('mypage.order_detail.cancel_modal.no_selected_items')\n ?? '취소할 상품을 선택해주세요.'\n );\n return;\n }\n\n if (!cancelReason) {\n G7Core.toast?.warning?.(\n G7Core.t?.('mypage.order_detail.cancel_modal.reason_required')\n ?? '취소 사유를 선택해주세요.'\n );\n return;\n }\n\n // 선택한 항목 부분집합을 항상 items 로 전송한다. 프론트에서 전체취소를 휴리스틱으로 판단하지 않는다.\n // 진짜 전체취소(모든 활성 옵션 전량)도 items 전량으로 보내면 백엔드 shouldConvertToFullCancel 이\n // FULL 로 승격해 동작·환불 결과가 동일하다. 종전 isFullCancel 휴리스틱은 다중항목 중 일부 전량 선택을\n // 전체취소로 오판해 items 를 누락시켜 주문 전체가 취소되는 결함이 있었다 (#397 U20①).\n const body: Record = {\n reason: cancelReason,\n refund_priority: refundPriority,\n items: selectedItems.map((item: CancelItem) => ({\n order_option_id: item.id,\n cancel_quantity: item.cancel_quantity,\n })),\n };\n\n // 기타 사유 선택 시 상세 사유 전송 (선택 입력 — 관리자 패리티)\n if (cancelReasonDetail) {\n body.reason_detail = cancelReasonDetail;\n }\n\n G7Core.state.setLocal({ isCancelling: true, cancelError: null, cancelValidationErrors: null });\n\n try {\n const guestToken = G7Core.state.get?.('_global')?.guestOrderToken;\n const orderNumber = G7Core.dataSource?.get?.('order')?.data?.order_number;\n const isGuest = Boolean(guestToken && orderNumber);\n const url = isGuest\n ? `/api/modules/sirsoft-ecommerce/guest/orders/${orderNumber}/cancel`\n : `/api/modules/sirsoft-ecommerce/user/orders/${orderId}/cancel`;\n // ApiClient(axios) 인터셉터는 globalHeaders 를 적용하지 않으므로 비회원 endpoint 호출 시 토큰 헤더를 명시적으로 첨부.\n const config = isGuest ? { headers: { 'X-Guest-Order-Token': guestToken } } : undefined;\n\n const response = await G7Core.api.post(\n url,\n body,\n config\n );\n\n if (response?.success) {\n G7Core.state.setLocal({ isCancelling: false });\n G7Core.toast?.success?.(\n G7Core.t?.('mypage.order_detail.cancel_success')\n ?? '주문이 취소되었습니다.'\n );\n\n G7Core.modal?.close?.('modal_cancel_order');\n G7Core.dispatch?.({ handler: 'refetchDataSource', params: { dataSourceId: 'order' } });\n } else {\n throw new Error(response?.message || 'Cancel failed');\n }\n } catch (error: any) {\n logger.error('[executeUserCancelOrder] 실패:', error);\n\n const errorData = (error as any)?.response?.data || (error as any)?.data;\n const detailMessage = errorData?.message || error?.message || '';\n const validationErrors = errorData?.errors ?? null;\n const fallbackMessage = G7Core.t?.('mypage.order_detail.cancel_modal.cancel_failed')\n ?? '주문 취소에 실패했습니다.';\n\n G7Core.state.setLocal({\n isCancelling: false,\n cancelError: detailMessage || fallbackMessage,\n cancelValidationErrors: validationErrors,\n });\n\n G7Core.toast?.error?.(detailMessage || fallbackMessage);\n }\n}\n","/**\n * 구매확정 관련 핸들러\n *\n * 사용자 마이페이지 주문상세에서 구매확정 처리를 담당합니다.\n * - 구매확정 API 호출\n * - 성공 시 모달 닫기 + 데이터 리패치 + 토스트\n * - 실패 시 토스트 에러\n */\n\nimport type { ActionContext } from '../types';\n\nconst logger = ((window as any).G7Core?.createLogger?.('Ecom:ConfirmOrder')) ?? {\n log: (...args: unknown[]) => console.log('[Ecom:ConfirmOrder]', ...args),\n warn: (...args: unknown[]) => console.warn('[Ecom:ConfirmOrder]', ...args),\n error: (...args: unknown[]) => console.error('[Ecom:ConfirmOrder]', ...args),\n};\n\ninterface ActionWithParams> {\n handler: string;\n params?: T;\n [key: string]: any;\n}\n\n/**\n * 비회원 컨텍스트 식별자 확보 여부 — _global.guestOrderToken 과 dataSource('order').data.order_number 가 모두 있으면 true.\n * 비회원 응답(GuestOrderResource)은 id 미노출이므로 회원 식별자(orderId) 가 비어 있어도 본 값으로 비회원 endpoint 호출 가능.\n */\nfunction hasGuestOrderContext(G7Core: any): boolean {\n const token = G7Core?.state?.get?.('_global')?.guestOrderToken;\n const orderNumber = G7Core?.dataSource?.get?.('order')?.data?.order_number;\n return Boolean(token && orderNumber);\n}\n\n/**\n * 구매확정 핸들러\n *\n * 주문 옵션을 구매확정 처리합니다.\n *\n * @example\n * {\n * \"handler\": \"sirsoft-ecommerce.confirmOrderOption\",\n * \"params\": { \"orderId\": 1, \"optionId\": 2 }\n * }\n *\n * @param action 액션 객체\n * @param _context 액션 컨텍스트\n */\nexport async function confirmOrderOptionHandler(\n action: ActionWithParams<{ orderId: number; optionId: number }>,\n _context: ActionContext\n): Promise {\n const G7Core = (window as any).G7Core;\n if (!G7Core?.state) return;\n\n const { orderId, optionId } = action.params || {};\n // 회원: orderId+optionId 필수. 비회원: orderId 없어도 토큰+order_number 로 비회원 endpoint 분기, optionId 는 항상 필수.\n if (!optionId || (!orderId && !hasGuestOrderContext(G7Core))) {\n logger.error('[confirmOrderOption] optionId 또는 식별자 누락');\n return;\n }\n\n G7Core.state.setLocal({ isConfirming: true });\n\n try {\n const guestToken = G7Core.state.get?.('_global')?.guestOrderToken;\n const orderNumber = G7Core.dataSource?.get?.('order')?.data?.order_number;\n const isGuest = Boolean(guestToken && orderNumber);\n const url = isGuest\n ? `/api/modules/sirsoft-ecommerce/guest/orders/${orderNumber}/options/${optionId}/confirm`\n : `/api/modules/sirsoft-ecommerce/user/orders/${orderId}/options/${optionId}/confirm`;\n // ApiClient(axios) 인터셉터는 globalHeaders 를 적용하지 않으므로 비회원 endpoint 호출 시 토큰 헤더를 명시적으로 첨부.\n const config = isGuest ? { headers: { 'X-Guest-Order-Token': guestToken } } : undefined;\n\n const response = await G7Core.api.post(url, undefined, config);\n\n if (response?.success) {\n G7Core.state.setLocal({ isConfirming: false, confirmTarget: null });\n G7Core.modal?.close?.('modal_confirm_purchase');\n G7Core.dispatch?.({ handler: 'refetchDataSource', params: { dataSourceId: 'order' } });\n G7Core.toast?.success?.(\n response.message\n ?? G7Core.t?.('sirsoft-ecommerce::messages.order.confirmed')\n ?? '구매확정이 완료되었습니다.'\n );\n } else {\n throw new Error(response?.message || 'Confirm failed');\n }\n } catch (error: any) {\n logger.error('[confirmOrderOption] 실패:', error);\n\n const errorMessage = error?.response?.data?.message\n || error?.data?.message\n || error?.message\n || G7Core.t?.('sirsoft-ecommerce::messages.order.cannot_confirm')\n || '구매확정에 실패했습니다.';\n\n G7Core.state.setLocal({ isConfirming: false });\n G7Core.toast?.error?.(errorMessage);\n }\n}\n","/**\n * 주문 배송지 변경 핸들러\n *\n * 마이페이지 주문 상세(회원) / 비회원 주문 상세에서 배송지 변경 모달의 적용을 처리합니다.\n * - 회원: 저장된 배송지(address_id) 또는 직접 입력(editingShippingAddress) 전송\n * - 비회원: 직접 입력만 (저장된 주소 없음), guest endpoint + X-Guest-Order-Token 헤더\n * - 성공 시 모달 닫기 + order 데이터소스 리패치 + 토스트\n * - 실패 시 토스트 에러 + 처리중 상태 복구\n *\n * 토큰 분기는 cancel/estimate-refund/confirm-option 과 동일한 hasGuestOrderContext 패턴.\n */\n\nimport type { ActionContext } from '../types';\n\nconst logger = ((window as any).G7Core?.createLogger?.('Ecom:ChangeShippingAddress')) ?? {\n log: (...args: unknown[]) => console.log('[Ecom:ChangeShippingAddress]', ...args),\n warn: (...args: unknown[]) => console.warn('[Ecom:ChangeShippingAddress]', ...args),\n error: (...args: unknown[]) => console.error('[Ecom:ChangeShippingAddress]', ...args),\n};\n\ninterface ActionWithParams> {\n handler: string;\n params?: T;\n [key: string]: any;\n}\n\n/**\n * 비회원 컨텍스트 식별자 확보 여부 — _global.guestOrderToken 과 dataSource('order').data.order_number 가 모두 있으면 true.\n * 비회원 응답(GuestOrderResource)은 id 미노출이므로 회원 식별자(orderId) 가 비어 있어도 본 값으로 비회원 endpoint 호출 가능.\n *\n * @param G7Core G7Core 인스턴스\n * @return bool 비회원 컨텍스트면 true\n */\nfunction hasGuestOrderContext(G7Core: any): boolean {\n const token = G7Core?.state?.get?.('_global')?.guestOrderToken;\n const orderNumber = G7Core?.dataSource?.get?.('order')?.data?.order_number;\n return Boolean(token && orderNumber);\n}\n\n/**\n * 배송지 변경 적용 핸들러\n *\n * 회원/비회원 단일 진입점. 모달의 _global 상태(changeAddressMode, selectedAddressId,\n * editingShippingAddress)를 읽어 endpoint/body 를 분기한다.\n *\n * @example\n * {\n * \"handler\": \"sirsoft-ecommerce.changeShippingAddress\",\n * \"params\": { \"orderId\": 123 }\n * }\n *\n * @param action 액션 객체 (params.orderId: 회원 주문 ID — 비회원은 불필요)\n * @param _context 액션 컨텍스트\n */\nexport async function changeShippingAddressHandler(\n action: ActionWithParams<{ orderId: number | string }>,\n _context: ActionContext\n): Promise {\n const G7Core = (window as any).G7Core;\n if (!G7Core?.state) return;\n\n const { orderId } = action.params || {};\n const isGuest = hasGuestOrderContext(G7Core);\n\n // 회원: orderId 필수. 비회원: 토큰+order_number 로 분기.\n if (!orderId && !isGuest) {\n logger.error('[changeShippingAddress] orderId 또는 비회원 식별자 누락');\n return;\n }\n\n const globalState = G7Core.state.get?.('_global') ?? {};\n // 비회원은 탭이 없어 항상 직접 입력(manual) 취급. 회원만 saved/manual 분기.\n const mode = isGuest ? 'manual' : (globalState.changeAddressMode ?? 'saved');\n\n // body 구성: 저장된 배송지 선택(saved) 이면 address_id, 직접 입력(manual)/비회원이면 editingShippingAddress 전체\n const body =\n mode === 'saved'\n ? { address_id: globalState.selectedAddressId }\n : globalState.editingShippingAddress;\n\n G7Core.state.setLocal({ isSubmittingAddress: true });\n\n try {\n const guestToken = globalState.guestOrderToken;\n const orderNumber = G7Core.dataSource?.get?.('order')?.data?.order_number;\n const url = isGuest\n ? `/api/modules/sirsoft-ecommerce/guest/orders/${orderNumber}/shipping-address`\n : `/api/modules/sirsoft-ecommerce/user/orders/${orderId}/shipping-address`;\n // ApiClient(axios) 인터셉터는 globalHeaders 를 적용하지 않으므로 비회원 endpoint 호출 시 토큰 헤더를 명시적으로 첨부.\n const config = isGuest ? { headers: { 'X-Guest-Order-Token': guestToken } } : undefined;\n\n const response = await G7Core.api.put(url, body, config);\n\n if (response?.success) {\n G7Core.state.setLocal({ isSubmittingAddress: false });\n G7Core.modal?.close?.('changeAddressModal');\n G7Core.dispatch?.({ handler: 'refetchDataSource', params: { dataSourceId: 'order' } });\n G7Core.toast?.success?.(\n response.message\n ?? G7Core.t?.('mypage.order_detail.shipping_address_changed')\n ?? '배송지가 변경되었습니다.'\n );\n } else {\n throw new Error(response?.message || 'Change shipping address failed');\n }\n } catch (error: any) {\n logger.error('[changeShippingAddress] 실패:', error);\n G7Core.state.setLocal({ isSubmittingAddress: false });\n\n const errorMessage = error?.response?.data?.message\n || error?.data?.message\n || error?.message\n || '배송지 변경에 실패했습니다.';\n G7Core.toast?.error?.(errorMessage);\n }\n}\n","/**\n * 리뷰 작성 관련 핸들러\n *\n * 사용자 마이페이지 주문상세에서 리뷰 작성을 담당합니다.\n * - 리뷰 텍스트 등록 API 호출\n * - 이미지 순차 업로드\n * - 성공 시 모달 닫기 + 데이터 리패치 + 상태 초기화 + 토스트\n * - 실패 시 토스트 에러 + validation 에러 표시\n */\n\nimport type { ActionContext } from '../types';\n\nconst logger = ((window as any).G7Core?.createLogger?.('Ecom:Review')) ?? {\n log: (...args: unknown[]) => console.log('[Ecom:Review]', ...args),\n warn: (...args: unknown[]) => console.warn('[Ecom:Review]', ...args),\n error: (...args: unknown[]) => console.error('[Ecom:Review]', ...args),\n};\n\ninterface ActionWithParams> {\n handler: string;\n params?: T;\n [key: string]: any;\n}\n\n/**\n * 리뷰 작성 핸들러\n *\n * 리뷰를 등록하고 이미지를 순차 업로드합니다.\n *\n * @example\n * {\n * \"handler\": \"sirsoft-ecommerce.submitReview\",\n * \"params\": { \"orderId\": 1, \"optionId\": 2, \"productId\": 3 }\n * }\n *\n * @param action 액션 객체\n * @param _context 액션 컨텍스트\n */\nexport async function submitReviewHandler(\n action: ActionWithParams<{\n orderId: number; optionId: number; productId: number;\n rating?: number; content?: string; images?: File[];\n }>,\n _context: ActionContext\n): Promise {\n const G7Core = (window as any).G7Core;\n if (!G7Core?.state) return;\n\n const { orderId, optionId, productId } = action.params || {};\n if (!orderId || !optionId || !productId) {\n logger.error('[submitReview] orderId, optionId 또는 productId 누락');\n return;\n }\n\n // params 우선, fallback으로 local (모달 스코프 호환 — 취소 모달 패턴과 동일)\n const local = G7Core.state.getLocal();\n const rating = action.params?.rating ?? local.reviewRating ?? 5;\n const content = (action.params?.content ?? local.reviewContent ?? '').trim();\n // FileUploader의 onChange는 PendingFile[] 전달 (각 항목에 .file: File 포함)\n // 주의: _local 상태를 거치면 File 객체의 prototype이 소실될 수 있으므로\n // instanceof File 외에 Blob 기반 검증도 수행\n const rawImages: any[] = action.params?.images ?? local.reviewImages ?? [];\n const images: (File | Blob)[] = rawImages.map((item: any) => {\n if (item instanceof File) return item;\n if (item?.file instanceof File) return item.file;\n if (item?.file instanceof Blob) return item.file;\n // File prototype 소실 시: name, size, type이 있으면 유효한 파일로 간주\n if (item?.file && typeof item.file.name === 'string' && item.file.size > 0) return item.file;\n if (item instanceof Blob) return item;\n return null;\n }).filter((f: any) => f !== null);\n\n // 프론트엔드 검증\n if (!rating || rating < 1 || rating > 5) {\n G7Core.toast?.error?.(\n G7Core.t?.('sirsoft-ecommerce.user.review.validation.rating_required')\n ?? '별점을 선택해 주세요.'\n );\n return;\n }\n\n if (content.length < 10) {\n G7Core.state.setLocal({\n reviewError: G7Core.t?.('sirsoft-ecommerce.user.review.validation.content_min')\n ?? '리뷰 내용을 최소 10자 이상 입력해 주세요.',\n });\n return;\n }\n\n G7Core.state.setLocal({ isSubmittingReview: true, reviewError: null });\n\n try {\n // 1. 리뷰 텍스트 등록\n const response = await G7Core.api.post(\n '/api/modules/sirsoft-ecommerce/user/reviews',\n {\n order_option_id: optionId,\n product_id: productId,\n rating,\n content,\n }\n );\n\n if (!response?.success) {\n throw new Error(response?.message || 'Review creation failed');\n }\n\n const reviewId = response.data?.id;\n\n // 2. 이미지 순차 업로드\n if (reviewId && images.length > 0) {\n for (let i = 0; i < images.length; i++) {\n const formData = new FormData();\n formData.append('image', images[i]);\n formData.append('sort_order', String(i));\n\n try {\n await G7Core.api.post(\n `/api/modules/sirsoft-ecommerce/user/reviews/${reviewId}/images`,\n formData,\n { headers: { 'Content-Type': 'multipart/form-data' } }\n );\n } catch (imgError: any) {\n logger.warn(`[submitReview] 이미지 ${i + 1} 업로드 실패:`, imgError);\n // 이미지 업로드 실패는 토스트로 알리고 계속 진행\n G7Core.toast?.warning?.(\n G7Core.t?.('sirsoft-ecommerce.user.review.image_upload_partial_fail')\n ?? `이미지 ${i + 1}번 업로드에 실패했습니다.`\n );\n }\n }\n }\n\n // 3. 성공 처리\n G7Core.state.setLocal({\n isSubmittingReview: false,\n reviewTarget: null,\n reviewRating: 5,\n reviewContent: '',\n reviewImages: [],\n reviewError: null,\n });\n G7Core.modal?.close?.('modal_write_review');\n G7Core.dispatch?.({ handler: 'refetchDataSource', params: { dataSourceId: 'order' } });\n G7Core.toast?.success?.(\n response.message\n ?? G7Core.t?.('sirsoft-ecommerce.user.review.submit_success')\n ?? '리뷰가 등록되었습니다.'\n );\n } catch (error: any) {\n logger.error('[submitReview] 실패:', error);\n\n const errorData = error?.response?.data || error?.data;\n const httpStatus = error?.response?.status || error?.status;\n const errorMessage = errorData?.message || error?.message\n || G7Core.t?.('sirsoft-ecommerce.user.review.submit_failed')\n || '리뷰 등록에 실패했습니다.';\n\n if (httpStatus === 422) {\n // Validation 에러: 필드별 에러 표시\n const validationErrors = errorData?.errors ?? {};\n const firstError = Object.values(validationErrors).flat()[0] as string | undefined;\n G7Core.state.setLocal({\n isSubmittingReview: false,\n reviewError: firstError || errorMessage,\n });\n } else {\n G7Core.state.setLocal({ isSubmittingReview: false });\n }\n\n G7Core.toast?.error?.(errorMessage);\n }\n}\n","/**\n * 배송정책 등록/수정 폼 핸들러\n *\n * 배송정책 폼 화면에서 국가별 탭 관리, 부과정책 변경에 따른 필드 가시성 제어,\n * 구간 관리/검증, 도서산간 추가배송비 행 관리 등을 처리합니다.\n */\n\nimport type { ActionContext } from '../types';\n\n// Logger 설정\nconst logger = ((window as any).G7Core?.createLogger?.('Ecom:ShippingPolicyForm')) ?? {\n log: (...args: unknown[]) => console.log('[Ecom:ShippingPolicyForm]', ...args),\n warn: (...args: unknown[]) => console.warn('[Ecom:ShippingPolicyForm]', ...args),\n error: (...args: unknown[]) => console.error('[Ecom:ShippingPolicyForm]', ...args),\n};\n\ninterface ActionWithParams {\n handler: string;\n params?: Record;\n [key: string]: any;\n}\n\ninterface CountrySetting {\n country_code: string;\n shipping_method: string;\n custom_shipping_name: Record | null;\n carrier: string | null;\n currency_code: string;\n charge_policy: string;\n base_fee: number;\n free_threshold: number | null;\n ranges: { type?: string; tiers?: RangeTier[]; unit_value?: number } | null;\n api_endpoint: string | null;\n api_request_fields: string[] | null;\n api_response_fee_field: string | null;\n api_config: ApiConfig | null;\n extra_fee_enabled: boolean;\n extra_fee_settings: Array<{ zipcode: string; fee: number; region?: string }> | null;\n extra_fee_multiply: boolean;\n is_active: boolean;\n}\n\ninterface RangeTier {\n min: number;\n max: number | null;\n fee: number;\n}\n\ninterface ApiConfig {\n http_method?: string;\n auth_type?: string;\n auth_token?: string | null;\n auth_header_name?: string | null;\n response_type?: string;\n response_path?: string | null;\n field_map?: Record | null;\n has_auth_token?: boolean;\n}\n\ninterface RangeTierError {\n min?: string;\n max?: string;\n fee?: string;\n}\n\n// ===== 부과정책별 필드 요구사항 매핑 =====\n\n/** 기본 배송비가 필요한 정책 */\nconst REQUIRES_BASE_FEE = [\n 'fixed', 'conditional_free',\n 'per_quantity', 'per_weight', 'per_volume', 'per_volume_weight', 'per_amount',\n];\n\n/** 무료 기준금액이 필요한 정책 */\nconst REQUIRES_FREE_THRESHOLD = ['conditional_free'];\n\n/** 구간 설정이 필요한 정책 */\nconst REQUIRES_RANGES = [\n 'range_amount', 'range_quantity', 'range_weight', 'range_volume', 'range_volume_weight',\n];\n\n/** API 설정이 필요한 정책 */\nconst REQUIRES_API = ['api'];\n\n/** 단위당 배송비 설정이 필요한 정책 */\nconst REQUIRES_UNIT_VALUE = [\n 'per_quantity', 'per_weight', 'per_volume', 'per_volume_weight', 'per_amount',\n];\n\n// ===== 국가별 기본 설정값 =====\n\nconst DEFAULT_COUNTRY_SETTING: Omit = {\n shipping_method: 'parcel',\n custom_shipping_name: null,\n carrier: null,\n currency_code: 'KRW',\n charge_policy: 'fixed',\n base_fee: 0,\n free_threshold: null,\n ranges: null,\n api_endpoint: null,\n api_request_fields: null,\n api_response_fee_field: null,\n api_config: null,\n extra_fee_enabled: false,\n extra_fee_settings: [],\n extra_fee_multiply: false,\n is_active: true,\n};\n\n// ===== 헬퍼 함수 =====\n\n/**\n * 부과정책 값에 따른 가시성 플래그를 계산합니다.\n *\n * @param chargePolicy 부과정책 값\n * @returns 가시성 플래그 객체\n */\nfunction getVisibilityFlags(chargePolicy: string): Record {\n return {\n showBaseFee: REQUIRES_BASE_FEE.includes(chargePolicy),\n showFreeThreshold: REQUIRES_FREE_THRESHOLD.includes(chargePolicy),\n showRanges: REQUIRES_RANGES.includes(chargePolicy),\n showApiSettings: REQUIRES_API.includes(chargePolicy),\n showUnitValue: REQUIRES_UNIT_VALUE.includes(chargePolicy),\n };\n}\n\n/**\n * G7Core.state에 안전하게 접근합니다.\n */\nfunction getG7Core(): any {\n const G7Core = (window as any).G7Core;\n if (!G7Core?.state) {\n logger.warn('G7Core.state not available');\n return null;\n }\n return G7Core;\n}\n\n/**\n * 로컬 상태를 안전하게 업데이트합니다.\n */\nfunction updateLocalState(\n context: ActionContext,\n updates: Record\n): void {\n if (context.setLocalState) {\n context.setLocalState(updates);\n } else {\n const G7Core = getG7Core();\n G7Core?.state?.setLocal(updates);\n }\n}\n\n/**\n * 현재 country_settings 배열을 가져옵니다.\n */\nfunction getCountrySettings(G7Core: any): CountrySetting[] {\n const localState = G7Core.state.getLocal?.() ?? {};\n return [...(localState.form?.country_settings ?? [])];\n}\n\n// ===== 핸들러 구현 =====\n\n/**\n * 배송정책 폼을 초기화합니다.\n *\n * 수정 모드: API 응답의 country_settings를 매핑하고 첫 번째 국가의 charge_policy 기반 가시성 설정.\n * 등록 모드: 빈 country_settings, activeCountryTab=0, 기본 가시성(fixed) 설정.\n *\n * @param action params.isEdit: 수정 모드 여부, params.policy: 기존 정책 데이터, params.availableCountries: 배송가능국가 목록\n * @param context 액션 컨텍스트\n */\nexport function initShippingPolicyFormHandler(\n action: ActionWithParams,\n context: ActionContext\n): void {\n const params = action.params || {};\n const policy = params.policy as Record | undefined;\n const isEdit = !!params.isEdit;\n const isCopy = !!params.isCopy;\n const availableCountries = (params.availableCountries ?? []) as Array<{ code: string; name: Record; is_active: boolean }>;\n\n const G7Core = getG7Core();\n if (!G7Core) return;\n\n const stateUpdates: Record = {\n activeCountryTab: 0,\n rangeErrors: {},\n };\n\n if ((isEdit || isCopy) && policy) {\n // 수정/복사 모드: country_settings 배열이 API 응답에 포함\n const countrySettings = policy.country_settings ?? [];\n const firstSetting = countrySettings[0];\n const chargePolicy = firstSetting?.charge_policy ?? 'fixed';\n const flags = getVisibilityFlags(chargePolicy);\n Object.assign(stateUpdates, flags);\n logger.log('[initShippingPolicyForm]', isEdit ? 'Edit' : 'Copy', 'mode, countries:', countrySettings.length, 'first charge_policy:', chargePolicy);\n } else {\n // 등록 모드: 기본 가시성 (fixed 기준)\n const flags = getVisibilityFlags('fixed');\n Object.assign(stateUpdates, flags);\n logger.log('[initShippingPolicyForm] Create mode, default flags');\n }\n\n updateLocalState(context, stateUpdates);\n}\n\n/**\n * 국가별 설정을 추가합니다.\n *\n * @param action params.country_code: 추가할 국가코드\n * @param context 액션 컨텍스트\n */\nexport function addCountrySettingHandler(\n action: ActionWithParams,\n context: ActionContext\n): void {\n const params = action.params || {};\n const countryCode = (params.country_code ?? '') as string;\n\n if (!countryCode) {\n logger.warn('[addCountrySetting] country_code is empty');\n return;\n }\n\n const G7Core = getG7Core();\n if (!G7Core) return;\n\n const countrySettings = getCountrySettings(G7Core);\n\n // 중복 체크\n if (countrySettings.some(cs => cs.country_code === countryCode)) {\n logger.warn('[addCountrySetting] Duplicate country_code:', countryCode);\n return;\n }\n\n const newSetting: CountrySetting = {\n country_code: countryCode,\n ...DEFAULT_COUNTRY_SETTING,\n };\n\n countrySettings.push(newSetting);\n\n const newIndex = countrySettings.length - 1;\n const flags = getVisibilityFlags(newSetting.charge_policy);\n\n const stateUpdates: Record = {\n 'form.country_settings': countrySettings,\n activeCountryTab: newIndex,\n ...flags,\n };\n\n updateLocalState(context, stateUpdates);\n logger.log('[addCountrySetting] Added country:', countryCode, 'index:', newIndex);\n}\n\n/**\n * 국가별 설정을 삭제합니다.\n *\n * @param action params.index: 삭제할 인덱스\n * @param context 액션 컨텍스트\n */\nexport function removeCountrySettingHandler(\n action: ActionWithParams,\n context: ActionContext\n): void {\n const params = action.params || {};\n const index = Number(params.index);\n\n if (isNaN(index) || index < 0) {\n logger.warn('[removeCountrySetting] Invalid index:', params.index);\n return;\n }\n\n const G7Core = getG7Core();\n if (!G7Core) return;\n\n const countrySettings = getCountrySettings(G7Core);\n const localState = G7Core.state.getLocal?.() ?? {};\n\n if (index >= countrySettings.length) {\n logger.warn('[removeCountrySetting] Index out of bounds:', index);\n return;\n }\n\n const removedCode = countrySettings[index].country_code;\n countrySettings.splice(index, 1);\n\n // activeCountryTab 조정\n const currentTab = localState.activeCountryTab ?? 0;\n let newTab = currentTab;\n if (countrySettings.length === 0) {\n newTab = 0;\n } else if (currentTab >= countrySettings.length) {\n newTab = countrySettings.length - 1;\n } else if (currentTab > index) {\n newTab = currentTab - 1;\n }\n\n // rangeErrors에서 삭제된 국가 키 제거 (deepMerge 호환: delete 대신 빈 배열 할당)\n const rangeErrors = { ...(localState.rangeErrors ?? {}) };\n rangeErrors[removedCode] = [];\n\n // 새 활성 탭의 charge_policy 기반 가시성 재계산\n const activeCS = countrySettings[newTab];\n const flags = activeCS ? getVisibilityFlags(activeCS.charge_policy) : getVisibilityFlags('fixed');\n\n const stateUpdates: Record = {\n 'form.country_settings': countrySettings,\n activeCountryTab: newTab,\n rangeErrors,\n ...flags,\n };\n\n updateLocalState(context, stateUpdates);\n logger.log('[removeCountrySetting] Removed index:', index, 'country:', removedCode, 'newTab:', newTab);\n}\n\n/**\n * 국가별 탭을 전환합니다.\n *\n * @param action params.index: 전환할 탭 인덱스\n * @param context 액션 컨텍스트\n */\nexport function switchCountryTabHandler(\n action: ActionWithParams,\n context: ActionContext\n): void {\n const params = action.params || {};\n const index = Number(params.index);\n\n if (isNaN(index) || index < 0) {\n logger.warn('[switchCountryTab] Invalid index:', params.index);\n return;\n }\n\n const G7Core = getG7Core();\n if (!G7Core) return;\n\n const countrySettings = getCountrySettings(G7Core);\n\n if (index >= countrySettings.length) {\n logger.warn('[switchCountryTab] Index out of bounds:', index);\n return;\n }\n\n const targetCS = countrySettings[index];\n const flags = getVisibilityFlags(targetCS.charge_policy);\n\n const stateUpdates: Record = {\n activeCountryTab: index,\n ...flags,\n };\n\n updateLocalState(context, stateUpdates);\n logger.log('[switchCountryTab] Switched to tab:', index, 'country:', targetCS.country_code, 'charge_policy:', targetCS.charge_policy);\n}\n\n/**\n * 국가별 설정의 개별 필드를 업데이트합니다.\n *\n * setState params 키에 {{}} 표현식을 사용할 수 없으므로,\n * 배열 인덱스 기반 필드 업데이트를 커스텀 핸들러로 처리합니다.\n *\n * @param action params.field: 변경할 필드명, params.value: 새 값\n * @param context 액션 컨텍스트\n */\nexport function updateCountryFieldHandler(\n action: ActionWithParams,\n context: ActionContext\n): void {\n const params = action.params || {};\n const field = (params.field ?? '') as string;\n const value = params.value;\n\n if (!field) {\n logger.warn('[updateCountryField] field is empty');\n return;\n }\n\n const G7Core = getG7Core();\n if (!G7Core) return;\n\n const localState = G7Core.state.getLocal?.() ?? {};\n const index = Number(localState.activeCountryTab ?? 0);\n const countrySettings = getCountrySettings(G7Core);\n\n if (index >= countrySettings.length) {\n logger.warn('[updateCountryField] Index out of bounds:', index);\n return;\n }\n\n // 새 객체 생성 (원본 mutation 방지 — deepMerge 변경 감지를 위해 필수)\n countrySettings[index] = { ...countrySettings[index], [field]: value } as CountrySetting;\n\n updateLocalState(context, {\n 'form.country_settings': countrySettings,\n });\n logger.log('[updateCountryField] Updated', field, '=', value, 'at index:', index);\n}\n\n/**\n * 부과정책(charge_policy) 변경 시 가시성 플래그를 업데이트합니다.\n *\n * @param action params.value: 선택된 부과정책 값, params.index: 국가 탭 인덱스\n * @param context 액션 컨텍스트\n */\nexport function onChargePolicyChangeHandler(\n action: ActionWithParams,\n context: ActionContext\n): void {\n const params = action.params || {};\n const chargePolicy = (params.value ?? '') as string;\n const countryIndex = Number(params.index ?? 0);\n\n if (!chargePolicy) {\n logger.warn('[onChargePolicyChange] charge_policy value is empty');\n return;\n }\n\n const G7Core = getG7Core();\n if (!G7Core) return;\n\n const flags = getVisibilityFlags(chargePolicy);\n logger.log('[onChargePolicyChange]', chargePolicy, flags, 'countryIndex:', countryIndex);\n\n const countrySettings = getCountrySettings(G7Core);\n const cs = countrySettings[countryIndex];\n if (!cs) {\n logger.warn('[onChargePolicyChange] No country setting at index:', countryIndex);\n return;\n }\n\n // 배열 요소를 새 객체로 복제 후 수정\n // (shallow copy된 배열은 원본 객체와 같은 참조를 공유하므로,\n // mutate하면 deepMerge에서 변경을 감지하지 못함 — 반드시 새 객체 생성 필수)\n const updatedCS: CountrySetting = { ...cs, charge_policy: chargePolicy };\n\n // 불필요한 필드 초기화\n if (!flags.showBaseFee) {\n updatedCS.base_fee = 0;\n }\n if (!flags.showFreeThreshold) {\n updatedCS.free_threshold = null;\n }\n if (!flags.showRanges && !flags.showUnitValue) {\n updatedCS.ranges = null;\n }\n if (!flags.showApiSettings) {\n updatedCS.api_endpoint = null;\n updatedCS.api_request_fields = null;\n updatedCS.api_response_fee_field = null;\n updatedCS.api_config = null;\n }\n\n // ranges 초기화 (구간 정책 선택 시 기본 구조 제공)\n if (flags.showRanges) {\n if (!updatedCS.ranges || !updatedCS.ranges.tiers) {\n const rangeType = chargePolicy.replace('range_', '');\n updatedCS.ranges = {\n type: rangeType,\n tiers: [{ min: 0, max: null, fee: 0 }],\n };\n }\n }\n\n // unit_value 초기화 (단위당 정책 선택 시)\n if (flags.showUnitValue) {\n if (!updatedCS.ranges || !updatedCS.ranges.unit_value) {\n updatedCS.ranges = {\n type: chargePolicy,\n unit_value: 1,\n };\n }\n }\n\n countrySettings[countryIndex] = updatedCS;\n\n const stateUpdates: Record = {\n 'form.country_settings': countrySettings,\n ...flags,\n };\n\n updateLocalState(context, stateUpdates);\n}\n\n/**\n * 구간별 배송비 tier를 추가합니다.\n *\n * @param action params.index: 국가 탭 인덱스 (activeCountryTab)\n * @param context 액션 컨텍스트\n */\nexport function addRangeTierHandler(\n action: ActionWithParams,\n context: ActionContext\n): void {\n const params = action.params || {};\n const countryIndex = Number(params.index ?? 0);\n\n const G7Core = getG7Core();\n if (!G7Core) return;\n\n const countrySettings = getCountrySettings(G7Core);\n const cs = countrySettings[countryIndex];\n if (!cs) {\n logger.warn('[addRangeTier] No country setting at index:', countryIndex);\n return;\n }\n\n const currentTiers = [...(cs.ranges?.tiers ?? [])];\n\n // 마지막 tier의 max + 1 값을 새 tier의 min으로 사용 (포함 범위 기준)\n const lastTier = currentTiers[currentTiers.length - 1];\n const newMin = lastTier?.max != null ? lastTier.max + 1 : 0;\n\n const newTier: RangeTier = { min: newMin, max: null, fee: 0 };\n currentTiers.push(newTier);\n\n // 새 객체 생성 (원본 mutation 방지 — deepMerge 변경 감지를 위해 필수)\n countrySettings[countryIndex] = {\n ...cs,\n ranges: { ...(cs.ranges ?? {}), tiers: currentTiers },\n };\n\n updateLocalState(context, {\n 'form.country_settings': countrySettings,\n });\n\n // 구간 검증 실행\n validateRangeTiersInternal(G7Core, context, countryIndex, currentTiers, cs.country_code);\n logger.log('[addRangeTier] Added tier, total:', currentTiers.length);\n}\n\n/**\n * 구간별 배송비 tier를 삭제합니다.\n *\n * @param action params.countryIndex: 국가 탭 인덱스, params.tierIndex: 삭제할 tier 인덱스\n * @param context 액션 컨텍스트\n */\nexport function removeRangeTierHandler(\n action: ActionWithParams,\n context: ActionContext\n): void {\n const params = action.params || {};\n const countryIndex = Number(params.countryIndex ?? 0);\n const tierIndex = Number(params.tierIndex);\n\n if (isNaN(tierIndex) || tierIndex < 0) {\n logger.warn('[removeRangeTier] Invalid tierIndex:', params.tierIndex);\n return;\n }\n\n const G7Core = getG7Core();\n if (!G7Core) return;\n\n const countrySettings = getCountrySettings(G7Core);\n const cs = countrySettings[countryIndex];\n if (!cs) {\n logger.warn('[removeRangeTier] No country setting at index:', countryIndex);\n return;\n }\n\n const currentTiers = [...(cs.ranges?.tiers ?? [])];\n\n if (tierIndex >= currentTiers.length) {\n logger.warn('[removeRangeTier] tierIndex out of bounds:', tierIndex);\n return;\n }\n\n currentTiers.splice(tierIndex, 1);\n\n // 새 객체 생성 (원본 mutation 방지 — deepMerge 변경 감지를 위해 필수)\n countrySettings[countryIndex] = {\n ...cs,\n ranges: { ...(cs.ranges ?? {}), tiers: currentTiers },\n };\n\n updateLocalState(context, {\n 'form.country_settings': countrySettings,\n });\n\n // 구간 검증 실행\n validateRangeTiersInternal(G7Core, context, countryIndex, currentTiers, cs.country_code);\n logger.log('[removeRangeTier] Removed tier:', tierIndex, 'remaining:', currentTiers.length);\n}\n\n/**\n * 구간별 배송비 tier 필드를 업데이트합니다.\n *\n * @param action params.countryIndex, params.tierIndex, params.field ('min'|'max'|'fee'), params.value\n * @param context 액션 컨텍스트\n */\nexport function updateRangeTierFieldHandler(\n action: ActionWithParams,\n context: ActionContext\n): void {\n const params = action.params || {};\n const countryIndex = Number(params.countryIndex ?? 0);\n const tierIndex = Number(params.tierIndex);\n const field = params.field as 'min' | 'max' | 'fee';\n const value = params.value;\n\n if (isNaN(tierIndex) || tierIndex < 0) {\n logger.warn('[updateRangeTierField] Invalid tierIndex:', params.tierIndex);\n return;\n }\n if (!['min', 'max', 'fee'].includes(field)) {\n logger.warn('[updateRangeTierField] Invalid field:', field);\n return;\n }\n\n const G7Core = getG7Core();\n if (!G7Core) return;\n\n const countrySettings = getCountrySettings(G7Core);\n const cs = countrySettings[countryIndex];\n if (!cs) {\n logger.warn('[updateRangeTierField] No country setting at index:', countryIndex);\n return;\n }\n\n const currentTiers = [...(cs.ranges?.tiers ?? [])];\n\n if (tierIndex >= currentTiers.length) {\n logger.warn('[updateRangeTierField] tierIndex out of bounds:', tierIndex);\n return;\n }\n\n // min/max/fee는 숫자로 변환 (DOM input의 $event.target.value는 string)\n // max는 빈 값일 경우 null (무제한)\n let parsedValue: number | null;\n if (field === 'max') {\n parsedValue = value === '' || value === null || value === undefined ? null : Number(value);\n } else {\n parsedValue = Number(value);\n }\n\n currentTiers[tierIndex] = { ...currentTiers[tierIndex], [field]: parsedValue };\n\n // 새 객체 생성 (원본 mutation 방지 — deepMerge 변경 감지를 위해 필수)\n countrySettings[countryIndex] = {\n ...cs,\n ranges: { ...(cs.ranges ?? {}), tiers: currentTiers },\n };\n\n updateLocalState(context, {\n 'form.country_settings': countrySettings,\n });\n\n // 구간 검증 실행\n validateRangeTiersInternal(G7Core, context, countryIndex, currentTiers, cs.country_code);\n}\n\n/**\n * 구간별 배송비 tier를 검증합니다.\n *\n * @param action params.countryIndex: 국가 탭 인덱스\n * @param context 액션 컨텍스트\n */\nexport function validateRangeTiersHandler(\n action: ActionWithParams,\n context: ActionContext\n): void {\n const params = action.params || {};\n const countryIndex = Number(params.countryIndex ?? 0);\n\n const G7Core = getG7Core();\n if (!G7Core) return;\n\n const countrySettings = getCountrySettings(G7Core);\n const cs = countrySettings[countryIndex];\n if (!cs) return;\n\n const tiers = cs.ranges?.tiers ?? [];\n validateRangeTiersInternal(G7Core, context, countryIndex, tiers, cs.country_code);\n}\n\n/**\n * 구간 검증 내부 로직\n */\nfunction validateRangeTiersInternal(\n G7Core: any,\n context: ActionContext,\n _countryIndex: number,\n tiers: RangeTier[],\n countryCode: string\n): void {\n const localState = G7Core.state.getLocal?.() ?? {};\n const rangeErrors: Record = { ...(localState.rangeErrors ?? {}) };\n\n if (tiers.length === 0) {\n // deepMerge에서 delete로 키를 제거할 수 없으므로 빈 배열로 대체\n rangeErrors[countryCode] = [];\n updateLocalState(context, { rangeErrors });\n return;\n }\n\n const tierErrors: RangeTierError[] = new Array(tiers.length).fill(null).map(() => ({}));\n let hasError = false;\n\n const t = G7Core.t;\n\n for (let i = 0; i < tiers.length; i++) {\n const tier = tiers[i];\n\n // 첫 구간 min은 0이어야 함\n if (i === 0 && tier.min !== 0) {\n tierErrors[i].min = t?.('sirsoft-ecommerce.validation.shipping_policy.ranges.first_min_zero')\n ?? '첫 구간의 시작값은 0이어야 합니다.';\n hasError = true;\n }\n\n // 마지막 구간 max는 null이어야 함\n if (i === tiers.length - 1 && tier.max !== null && tier.max !== undefined) {\n tierErrors[i].max = t?.('sirsoft-ecommerce.validation.shipping_policy.ranges.last_max_unlimited')\n ?? '마지막 구간의 종료값은 비워야 합니다.';\n hasError = true;\n }\n\n // min < max (마지막 구간 제외)\n if (i < tiers.length - 1 && tier.max !== null && tier.max !== undefined) {\n if (tier.min >= tier.max) {\n tierErrors[i].min = t?.('sirsoft-ecommerce.validation.shipping_policy.ranges.min_less_than_max')\n ?? '시작값이 종료값보다 작아야 합니다.';\n hasError = true;\n }\n }\n\n // 구간 연속성: 현재 max + 1 === 다음 min (포함 범위 기준)\n if (i < tiers.length - 1) {\n const nextTier = tiers[i + 1];\n if (tier.max !== null && tier.max !== undefined && (tier.max + 1) !== nextTier.min) {\n tierErrors[i].max = t?.('sirsoft-ecommerce.validation.shipping_policy.ranges.continuity')\n ?? '구간이 연속적이지 않습니다.';\n hasError = true;\n }\n }\n\n // fee >= 0\n if (tier.fee < 0) {\n tierErrors[i].fee = t?.('sirsoft-ecommerce.validation.shipping_policy.ranges.fee_non_negative')\n ?? '배송비는 0 이상이어야 합니다.';\n hasError = true;\n }\n }\n\n // deepMerge에서 delete로 키를 제거할 수 없으므로, 에러 없음 시 빈 배열로 대체\n rangeErrors[countryCode] = hasError ? tierErrors : [];\n\n updateLocalState(context, { rangeErrors });\n}\n\n/**\n * 도서산간 추가배송비 행을 추가합니다.\n *\n * @param action params.countryIndex: 국가 탭 인덱스\n * @param context 액션 컨텍스트\n */\nexport function addExtraFeeRowHandler(\n action: ActionWithParams,\n context: ActionContext\n): void {\n const params = action.params || {};\n const countryIndex = Number(params.countryIndex ?? 0);\n\n const G7Core = getG7Core();\n if (!G7Core) return;\n\n const countrySettings = getCountrySettings(G7Core);\n const cs = countrySettings[countryIndex];\n if (!cs) {\n logger.warn('[addExtraFeeRow] No country setting at index:', countryIndex);\n return;\n }\n\n // KR 전용 체크\n if (cs.country_code !== 'KR') {\n logger.warn('[addExtraFeeRow] Extra fee only available for KR, current:', cs.country_code);\n return;\n }\n\n const currentSettings = [...(cs.extra_fee_settings ?? [])];\n currentSettings.push({ zipcode: '', fee: 0, region: '' });\n\n // 새 객체 생성 (원본 mutation 방지 — deepMerge 변경 감지를 위해 필수)\n countrySettings[countryIndex] = { ...cs, extra_fee_settings: currentSettings };\n\n updateLocalState(context, {\n 'form.country_settings': countrySettings,\n });\n logger.log('[addExtraFeeRow] Added row, total:', currentSettings.length);\n}\n\n/**\n * 도서산간 추가배송비 행을 삭제합니다.\n *\n * @param action params.countryIndex: 국가 탭 인덱스, params.feeIndex: 삭제할 행 인덱스\n * @param context 액션 컨텍스트\n */\nexport function removeExtraFeeRowHandler(\n action: ActionWithParams,\n context: ActionContext\n): void {\n const params = action.params || {};\n const countryIndex = Number(params.countryIndex ?? 0);\n const feeIndex = Number(params.feeIndex);\n\n if (isNaN(feeIndex) || feeIndex < 0) {\n logger.warn('[removeExtraFeeRow] Invalid feeIndex:', params.feeIndex);\n return;\n }\n\n const G7Core = getG7Core();\n if (!G7Core) return;\n\n const countrySettings = getCountrySettings(G7Core);\n const cs = countrySettings[countryIndex];\n if (!cs) {\n logger.warn('[removeExtraFeeRow] No country setting at index:', countryIndex);\n return;\n }\n\n const currentSettings = [...(cs.extra_fee_settings ?? [])];\n if (feeIndex >= currentSettings.length) {\n logger.warn('[removeExtraFeeRow] feeIndex out of bounds:', feeIndex);\n return;\n }\n\n currentSettings.splice(feeIndex, 1);\n\n // 새 객체 생성 (원본 mutation 방지 — deepMerge 변경 감지를 위해 필수)\n countrySettings[countryIndex] = { ...cs, extra_fee_settings: currentSettings };\n\n updateLocalState(context, {\n 'form.country_settings': countrySettings,\n });\n logger.log('[removeExtraFeeRow] Removed index:', feeIndex, 'remaining:', currentSettings.length);\n}\n\n/**\n * 도서산간 추가배송비 템플릿을 적용합니다.\n *\n * @param action params.countryIndex: 국가 탭 인덱스, params.settings: 템플릿의 추가배송비 설정 배열\n * @param context 액션 컨텍스트\n */\nexport function applyExtraFeeTemplateHandler(\n action: ActionWithParams,\n context: ActionContext\n): void {\n const params = action.params || {};\n const countryIndex = Number(params.countryIndex ?? 0);\n const settings = params.settings as Array<{ zipcode: string; fee: number; region?: string }>;\n\n if (!settings || !Array.isArray(settings)) {\n logger.warn('[applyExtraFeeTemplate] Invalid settings:', settings);\n return;\n }\n\n const G7Core = getG7Core();\n if (!G7Core) return;\n\n const countrySettings = getCountrySettings(G7Core);\n const cs = countrySettings[countryIndex];\n if (!cs) {\n logger.warn('[applyExtraFeeTemplate] No country setting at index:', countryIndex);\n return;\n }\n\n // 새 객체 생성 (원본 mutation 방지 — deepMerge 변경 감지를 위해 필수)\n countrySettings[countryIndex] = { ...cs, extra_fee_settings: settings };\n\n updateLocalState(context, {\n 'form.country_settings': countrySettings,\n });\n\n // 토스트 알림\n const t = G7Core.t;\n const message = t?.('sirsoft-ecommerce.admin.shipping_policy.form.template_applied')\n ?? '템플릿이 적용되었습니다.';\n G7Core.toast?.success?.(message);\n\n logger.log('[applyExtraFeeTemplate] Applied template with', settings.length, 'rows');\n}\n\n/**\n * 단위당 배송비(unit_value)를 업데이트합니다.\n *\n * @param action params.value: 단위당 배송비 값\n * @param context 액션 컨텍스트\n */\nexport function updateUnitValueHandler(\n action: ActionWithParams,\n context: ActionContext\n): void {\n const params = action.params || {};\n const value = parseFloat(params.value as string) || 1;\n\n const G7Core = getG7Core();\n if (!G7Core) return;\n\n const localState = G7Core.state.getLocal?.() ?? {};\n const countryIndex = Number(localState.activeCountryTab ?? 0);\n const countrySettings = getCountrySettings(G7Core);\n const cs = countrySettings[countryIndex];\n if (!cs) return;\n\n // 새 객체 생성 (원본 mutation 방지 — deepMerge 변경 감지를 위해 필수)\n countrySettings[countryIndex] = {\n ...cs,\n ranges: { ...(cs.ranges ?? {}), type: cs.charge_policy, unit_value: value },\n };\n\n updateLocalState(context, {\n 'form.country_settings': countrySettings,\n });\n logger.log('[updateUnitValue] Updated unit_value:', value);\n}\n\n/**\n * API 요청 필드를 추가합니다.\n *\n * @param action (params 없음, activeCountryTab 자동 참조)\n * @param context 액션 컨텍스트\n */\nexport function addApiRequestFieldHandler(\n _action: ActionWithParams,\n context: ActionContext\n): void {\n const G7Core = getG7Core();\n if (!G7Core) return;\n\n const localState = G7Core.state.getLocal?.() ?? {};\n const countryIndex = Number(localState.activeCountryTab ?? 0);\n const countrySettings = getCountrySettings(G7Core);\n const cs = countrySettings[countryIndex];\n if (!cs) return;\n\n const currentFields = [...(cs.api_request_fields ?? [])];\n currentFields.push('');\n\n // 새 객체 생성 (원본 mutation 방지 — deepMerge 변경 감지를 위해 필수)\n countrySettings[countryIndex] = { ...cs, api_request_fields: currentFields };\n\n updateLocalState(context, {\n 'form.country_settings': countrySettings,\n });\n logger.log('[addApiRequestField] Added field, total:', currentFields.length);\n}\n\n/**\n * API 요청 필드를 수정합니다.\n *\n * @param action params.fieldIndex: 수정할 필드 인덱스, params.value: 새 값\n * @param context 액션 컨텍스트\n */\nexport function updateApiRequestFieldHandler(\n action: ActionWithParams,\n context: ActionContext\n): void {\n const params = action.params || {};\n const fieldIndex = Number(params.fieldIndex);\n const value = (params.value ?? '') as string;\n\n if (isNaN(fieldIndex) || fieldIndex < 0) {\n logger.warn('[updateApiRequestField] Invalid fieldIndex:', params.fieldIndex);\n return;\n }\n\n const G7Core = getG7Core();\n if (!G7Core) return;\n\n const localState = G7Core.state.getLocal?.() ?? {};\n const countryIndex = Number(localState.activeCountryTab ?? 0);\n const countrySettings = getCountrySettings(G7Core);\n const cs = countrySettings[countryIndex];\n if (!cs) return;\n\n const currentFields = [...(cs.api_request_fields ?? [])];\n if (fieldIndex >= currentFields.length) return;\n\n currentFields[fieldIndex] = value;\n\n // 새 객체 생성 (원본 mutation 방지 — deepMerge 변경 감지를 위해 필수)\n countrySettings[countryIndex] = { ...cs, api_request_fields: currentFields };\n\n updateLocalState(context, {\n 'form.country_settings': countrySettings,\n });\n logger.log('[updateApiRequestField] Updated index:', fieldIndex, 'value:', value);\n}\n\n/**\n * API 요청 필드를 삭제합니다.\n *\n * @param action params.fieldIndex: 삭제할 필드 인덱스\n * @param context 액션 컨텍스트\n */\nexport function removeApiRequestFieldHandler(\n action: ActionWithParams,\n context: ActionContext\n): void {\n const params = action.params || {};\n const fieldIndex = Number(params.fieldIndex);\n\n if (isNaN(fieldIndex) || fieldIndex < 0) {\n logger.warn('[removeApiRequestField] Invalid fieldIndex:', params.fieldIndex);\n return;\n }\n\n const G7Core = getG7Core();\n if (!G7Core) return;\n\n const localState = G7Core.state.getLocal?.() ?? {};\n const countryIndex = Number(localState.activeCountryTab ?? 0);\n const countrySettings = getCountrySettings(G7Core);\n const cs = countrySettings[countryIndex];\n if (!cs) return;\n\n const currentFields = [...(cs.api_request_fields ?? [])];\n if (fieldIndex >= currentFields.length) return;\n\n currentFields.splice(fieldIndex, 1);\n\n // 새 객체 생성 (원본 mutation 방지 — deepMerge 변경 감지를 위해 필수)\n countrySettings[countryIndex] = { ...cs, api_request_fields: currentFields };\n\n updateLocalState(context, {\n 'form.country_settings': countrySettings,\n });\n logger.log('[removeApiRequestField] Removed index:', fieldIndex, 'remaining:', currentFields.length);\n}\n\n/**\n * API 요청 참고 필드 후보를 토글합니다 (체크박스 선택/해제).\n *\n * 후보 5종(policy_id/country_code/items/group_total/total_quantity) 중 하나를\n * api_request_fields 배열에 추가하거나 제거합니다. 자유 텍스트 입력을 대체하여\n * 시스템이 지원하지 않는 필드명 입력(silent drop)을 원천 차단합니다.\n *\n * @param action params.field: 토글할 후보 필드 값\n * @param context 액션 컨텍스트\n */\nexport function toggleApiRequestFieldHandler(\n action: ActionWithParams,\n context: ActionContext\n): void {\n const params = action.params || {};\n const field = (params.field ?? '') as string;\n if (!field) {\n logger.warn('[toggleApiRequestField] Missing field param');\n return;\n }\n\n const G7Core = getG7Core();\n if (!G7Core) return;\n\n const localState = G7Core.state.getLocal?.() ?? {};\n const countryIndex = Number(localState.activeCountryTab ?? 0);\n const countrySettings = getCountrySettings(G7Core);\n const cs = countrySettings[countryIndex];\n if (!cs) return;\n\n const currentFields = [...(cs.api_request_fields ?? [])];\n const existingIndex = currentFields.indexOf(field);\n\n if (existingIndex >= 0) {\n currentFields.splice(existingIndex, 1);\n } else {\n currentFields.push(field);\n }\n\n // 빈 배열은 null 로 정규화 (전체 전송 = 현 동작 유지)\n const nextFields = currentFields.length > 0 ? currentFields : null;\n\n // 새 객체 생성 (원본 mutation 방지 — deepMerge 변경 감지를 위해 필수)\n countrySettings[countryIndex] = { ...cs, api_request_fields: nextFields };\n\n updateLocalState(context, {\n 'form.country_settings': countrySettings,\n });\n logger.log('[toggleApiRequestField] Toggled:', field, 'selected:', nextFields);\n}\n\n/**\n * 계산 API 고급 설정(api_config)의 개별 필드를 업데이트합니다.\n *\n * api_config 는 중첩 객체이므로 updateCountryField(평면 필드)와 별도로 처리합니다.\n *\n * @param action params.field: api_config 하위 키, params.value: 새 값\n * @param context 액션 컨텍스트\n */\nexport function updateApiConfigFieldHandler(\n action: ActionWithParams,\n context: ActionContext\n): void {\n const params = action.params || {};\n const field = (params.field ?? '') as string;\n if (!field) {\n logger.warn('[updateApiConfigField] Missing field param');\n return;\n }\n\n const G7Core = getG7Core();\n if (!G7Core) return;\n\n const localState = G7Core.state.getLocal?.() ?? {};\n const index = Number(localState.activeCountryTab ?? 0);\n const countrySettings = getCountrySettings(G7Core);\n const cs = countrySettings[index];\n if (!cs) return;\n\n const nextConfig: ApiConfig = { ...(cs.api_config ?? {}), [field]: params.value };\n\n // 새 객체 생성 (deepMerge 변경 감지)\n countrySettings[index] = { ...cs, api_config: nextConfig };\n\n updateLocalState(context, {\n 'form.country_settings': countrySettings,\n });\n logger.log('[updateApiConfigField] Updated', field, 'at index:', index);\n}\n\n/**\n * 계산 API 요청 필드의 외부 키 매핑(field_map)을 업데이트합니다.\n *\n * @param action params.field: 우리 키(후보), params.value: 외부 키 이름(빈 값이면 매핑 제거)\n * @param context 액션 컨텍스트\n */\nexport function updateApiFieldMapHandler(\n action: ActionWithParams,\n context: ActionContext\n): void {\n const params = action.params || {};\n const field = (params.field ?? '') as string;\n if (!field) {\n logger.warn('[updateApiFieldMap] Missing field param');\n return;\n }\n\n const G7Core = getG7Core();\n if (!G7Core) return;\n\n const localState = G7Core.state.getLocal?.() ?? {};\n const index = Number(localState.activeCountryTab ?? 0);\n const countrySettings = getCountrySettings(G7Core);\n const cs = countrySettings[index];\n if (!cs) return;\n\n const externalKey = (params.value ?? '') as string;\n const fieldMap: Record = { ...(cs.api_config?.field_map ?? {}) };\n\n if (externalKey.trim() === '') {\n delete fieldMap[field];\n } else {\n fieldMap[field] = externalKey;\n }\n\n const nextConfig: ApiConfig = {\n ...(cs.api_config ?? {}),\n field_map: Object.keys(fieldMap).length > 0 ? fieldMap : null,\n };\n\n countrySettings[index] = { ...cs, api_config: nextConfig };\n\n updateLocalState(context, {\n 'form.country_settings': countrySettings,\n });\n logger.log('[updateApiFieldMap] Mapped', field, '→', externalKey || '(removed)');\n}\n\n/**\n * 현재 입력 중인 계산 API 설정으로 외부 API 를 테스트 호출합니다.\n *\n * 백엔드 test-api-call 엔드포인트로 요청하고 결과를 _global.apiTestResult 에 저장합니다.\n *\n * @param action 액션 정의\n * @param context 액션 컨텍스트\n */\nexport function testShippingApiHandler(\n action: ActionWithParams,\n context: ActionContext\n): void {\n const G7Core = getG7Core();\n if (!G7Core) return;\n\n const localState = G7Core.state.getLocal?.() ?? {};\n const index = Number(localState.activeCountryTab ?? 0);\n const countrySettings = getCountrySettings(G7Core);\n const cs = countrySettings[index];\n if (!cs) return;\n\n if (!cs.api_endpoint) {\n G7Core.toast?.warning?.(G7Core.t?.('sirsoft-ecommerce.admin.shipping_policy.form.api_test_endpoint_required') ?? 'Endpoint required');\n return;\n }\n\n // 전역 상태 설정은 객체 형태로 넘긴다 (set 의 첫 인자는 updates 객체)\n G7Core.state.set?.({ apiTestLoading: true, apiTestResult: null });\n\n // apiCall 구조: URL 은 액션 top-level target, method/body 는 params,\n // onSuccess/onError 도 top-level (CLAUDE.md apiCall 규약). 백엔드 test-api-call\n // 엔드포인트가 사용자가 입력한 설정으로 외부 API 를 대신 호출하고 결과를 서빙한다.\n G7Core.dispatch({\n handler: 'apiCall',\n target: '/api/modules/sirsoft-ecommerce/admin/shipping-policies/test-api-call',\n auth_required: true,\n params: {\n method: 'POST',\n body: {\n endpoint: cs.api_endpoint,\n request_fields: cs.api_request_fields,\n config: cs.api_config ?? {},\n sample: {\n country_code: cs.country_code,\n },\n },\n },\n onSuccess: {\n handler: 'setState',\n params: {\n target: 'global',\n apiTestLoading: false,\n apiTestResult: '{{response.data}}',\n },\n },\n onError: {\n handler: 'setState',\n params: {\n target: 'global',\n apiTestLoading: false,\n apiTestResult: '{{error.errors ?? error}}',\n },\n },\n });\n}\n\n/**\n * 도서산간 추가배송비 행의 필드를 수정합니다.\n *\n * @param action params.feeIndex: 행 인덱스, params.field: 필드명 ('zipcode'|'fee'|'region'), params.value: 새 값\n * @param context 액션 컨텍스트\n */\nexport function updateExtraFeeFieldHandler(\n action: ActionWithParams,\n context: ActionContext\n): void {\n const params = action.params || {};\n const feeIndex = Number(params.feeIndex);\n const field = params.field as 'zipcode' | 'fee' | 'region';\n const rawValue = params.value;\n\n if (isNaN(feeIndex) || feeIndex < 0) {\n logger.warn('[updateExtraFeeField] Invalid feeIndex:', params.feeIndex);\n return;\n }\n if (!['zipcode', 'fee', 'region'].includes(field)) {\n logger.warn('[updateExtraFeeField] Invalid field:', field);\n return;\n }\n\n const G7Core = getG7Core();\n if (!G7Core) return;\n\n const localState = G7Core.state.getLocal?.() ?? {};\n const countryIndex = Number(localState.activeCountryTab ?? 0);\n const countrySettings = getCountrySettings(G7Core);\n const cs = countrySettings[countryIndex];\n if (!cs) return;\n\n const currentSettings = [...(cs.extra_fee_settings ?? [])];\n if (feeIndex >= currentSettings.length) return;\n\n const value = field === 'fee' ? (parseFloat(rawValue as string) || 0) : rawValue;\n currentSettings[feeIndex] = { ...currentSettings[feeIndex], [field]: value };\n\n // 새 객체 생성 (원본 mutation 방지 — deepMerge 변경 감지를 위해 필수)\n countrySettings[countryIndex] = { ...cs, extra_fee_settings: currentSettings };\n\n updateLocalState(context, {\n 'form.country_settings': countrySettings,\n });\n logger.log('[updateExtraFeeField] Updated index:', feeIndex, field, '=', value);\n}\n","/**\n * initCategoryFromUrl 핸들러\n *\n * URL 쿼리 파라미터(id, mode)를 읽어서 카테고리 상태를 초기화합니다.\n * 카테고리 관리 페이지에서 URL로 직접 접근할 때 해당 카테고리를 선택 상태로 표시합니다.\n */\n\nconst logger = ((window as any).G7Core?.createLogger?.('Handler:InitCategory')) ?? {\n log: (...args: unknown[]) => console.log('[Handler:InitCategory]', ...args),\n warn: (...args: unknown[]) => console.warn('[Handler:InitCategory]', ...args),\n error: (...args: unknown[]) => console.error('[Handler:InitCategory]', ...args),\n};\n\ninterface CategoryItem {\n id: number;\n children?: CategoryItem[];\n [key: string]: any;\n}\n\n/**\n * 계층형 카테고리 목록에서 ID로 카테고리를 찾습니다.\n */\nfunction findCategoryById(items: CategoryItem[], id: number): CategoryItem | null {\n for (const item of items) {\n if (item.id === id) {\n return item;\n }\n if (item.children && item.children.length > 0) {\n const found = findCategoryById(item.children, id);\n if (found) {\n return found;\n }\n }\n }\n return null;\n}\n\nfunction getQueryParam(paramName: string): string | null {\n const urlParams = new URLSearchParams(window.location.search);\n return urlParams.get(paramName);\n}\n\nfunction getDataSource(dataSourceId: string): any {\n const g7Core = (window as any).G7Core;\n return g7Core?.state?.getDataSource?.(dataSourceId);\n}\n\nasync function waitForDataSource(\n dataSourceId: string,\n dataPath: (ds: any) => any[] | undefined,\n maxAttempts: number = 30,\n interval: number = 100\n): Promise {\n for (let i = 0; i < maxAttempts; i++) {\n const dataSource = getDataSource(dataSourceId);\n const data = dataPath(dataSource);\n if (Array.isArray(data) && data.length > 0) {\n return data;\n }\n await new Promise((resolve) => setTimeout(resolve, interval));\n }\n return null;\n}\n\nexport async function initCategoryFromUrlHandler(\n _action: any,\n _context?: any\n): Promise {\n const g7Core = (window as any).G7Core;\n\n const idParam = getQueryParam('id');\n const mode = getQueryParam('mode');\n\n if (!idParam) {\n logger.log('[initCategoryFromUrl] No id parameter in URL');\n return;\n }\n\n if (!g7Core?.state?.set) {\n logger.warn('[initCategoryFromUrl] G7Core.state.set not available');\n return;\n }\n\n const data = await waitForDataSource('categories', (ds) => ds?.data?.data);\n\n if (!data) {\n logger.warn('[initCategoryFromUrl] categories data not available after waiting');\n return;\n }\n\n const categoryId = parseInt(idParam, 10);\n const found = findCategoryById(data, categoryId);\n\n if (!found) {\n logger.warn('[initCategoryFromUrl] Category not found with id:', categoryId);\n return;\n }\n\n const panelMode = mode === 'edit' ? 'edit' : 'view';\n\n g7Core.state.set({\n selectedCategoryId: found.id,\n selectedCategory: found,\n panelMode: panelMode,\n });\n\n logger.log('[initCategoryFromUrl] Category initialized from URL:', {\n id: categoryId,\n mode: panelMode,\n });\n}\n","/**\n * initBrandFromUrl 핸들러\n *\n * URL 쿼리 파라미터(id, mode)를 읽어서 브랜드 상태를 초기화합니다.\n * 브랜드 관리 페이지에서 URL로 직접 접근할 때 해당 브랜드를 선택 상태로 표시합니다.\n */\n\nconst logger = ((window as any).G7Core?.createLogger?.('Handler:InitBrand')) ?? {\n log: (...args: unknown[]) => console.log('[Handler:InitBrand]', ...args),\n warn: (...args: unknown[]) => console.warn('[Handler:InitBrand]', ...args),\n error: (...args: unknown[]) => console.error('[Handler:InitBrand]', ...args),\n};\n\nfunction getQueryParam(paramName: string): string | null {\n const urlParams = new URLSearchParams(window.location.search);\n return urlParams.get(paramName);\n}\n\nfunction getDataSource(dataSourceId: string): any {\n const g7Core = (window as any).G7Core;\n return g7Core?.state?.getDataSource?.(dataSourceId);\n}\n\nasync function waitForDataSource(\n dataSourceId: string,\n dataPath: (ds: any) => any[] | undefined,\n maxAttempts: number = 30,\n interval: number = 100\n): Promise {\n for (let i = 0; i < maxAttempts; i++) {\n const dataSource = getDataSource(dataSourceId);\n const data = dataPath(dataSource);\n if (Array.isArray(data) && data.length > 0) {\n return data;\n }\n await new Promise((resolve) => setTimeout(resolve, interval));\n }\n return null;\n}\n\nexport async function initBrandFromUrlHandler(\n _action: any,\n _context?: any\n): Promise {\n const g7Core = (window as any).G7Core;\n\n const idParam = getQueryParam('id');\n const mode = getQueryParam('mode');\n\n if (!idParam) {\n logger.log('[initBrandFromUrl] No id parameter in URL');\n return;\n }\n\n if (!g7Core?.state?.set) {\n logger.warn('[initBrandFromUrl] G7Core.state.set not available');\n return;\n }\n\n const data = await waitForDataSource('brands', (ds) => ds?.data?.data);\n\n if (!data) {\n logger.warn('[initBrandFromUrl] brands data not available after waiting');\n return;\n }\n\n const brandId = parseInt(idParam, 10);\n const found = data.find((item: any) => item.id === brandId);\n\n if (!found) {\n logger.warn('[initBrandFromUrl] Brand not found with id:', brandId);\n return;\n }\n\n const panelMode = mode === 'edit' ? 'edit' : 'view';\n\n g7Core.state.set({\n selectedBrandId: found.id,\n selectedBrand: found,\n panelMode: panelMode,\n });\n\n logger.log('[initBrandFromUrl] Brand initialized from URL:', {\n id: brandId,\n mode: panelMode,\n });\n}\n","/**\n * initCommonInfoFromUrl 핸들러\n *\n * URL 쿼리 파라미터(id, mode)를 읽어서 공통정보 상태를 초기화합니다.\n * 공통정보 관리 페이지에서 URL로 직접 접근할 때 해당 항목을 선택 상태로 표시합니다.\n */\n\nconst logger = ((window as any).G7Core?.createLogger?.('Handler:InitCommonInfo')) ?? {\n log: (...args: unknown[]) => console.log('[Handler:InitCommonInfo]', ...args),\n warn: (...args: unknown[]) => console.warn('[Handler:InitCommonInfo]', ...args),\n error: (...args: unknown[]) => console.error('[Handler:InitCommonInfo]', ...args),\n};\n\nfunction getQueryParam(paramName: string): string | null {\n const urlParams = new URLSearchParams(window.location.search);\n return urlParams.get(paramName);\n}\n\nfunction getDataSource(dataSourceId: string): any {\n const g7Core = (window as any).G7Core;\n return g7Core?.state?.getDataSource?.(dataSourceId);\n}\n\nasync function waitForDataSource(\n dataSourceId: string,\n dataPath: (ds: any) => any[] | undefined,\n maxAttempts: number = 30,\n interval: number = 100\n): Promise {\n for (let i = 0; i < maxAttempts; i++) {\n const dataSource = getDataSource(dataSourceId);\n const data = dataPath(dataSource);\n if (Array.isArray(data) && data.length > 0) {\n return data;\n }\n await new Promise((resolve) => setTimeout(resolve, interval));\n }\n return null;\n}\n\nexport async function initCommonInfoFromUrlHandler(\n _action: any,\n _context?: any\n): Promise {\n const g7Core = (window as any).G7Core;\n\n const idParam = getQueryParam('id');\n const mode = getQueryParam('mode');\n\n if (!idParam) {\n logger.log('[initCommonInfoFromUrl] No id parameter in URL');\n return;\n }\n\n if (!g7Core?.state?.set) {\n logger.warn('[initCommonInfoFromUrl] G7Core.state.set not available');\n return;\n }\n\n const data = await waitForDataSource('commonInfos', (ds) => ds?.data);\n\n if (!data) {\n logger.warn('[initCommonInfoFromUrl] commonInfos data not available after waiting');\n return;\n }\n\n const itemId = parseInt(idParam, 10);\n const found = data.find((item: any) => item.id === itemId);\n\n if (!found) {\n logger.warn('[initCommonInfoFromUrl] Common info not found with id:', itemId);\n return;\n }\n\n const panelMode = mode === 'edit' ? 'edit' : 'view';\n\n g7Core.state.set({\n selectedId: found.id,\n selectedItem: found,\n panelMode: panelMode,\n });\n\n logger.log('[initCommonInfoFromUrl] Common info initialized from URL:', {\n id: itemId,\n mode: panelMode,\n });\n}\n","/**\n * initNoticeFromUrl 핸들러\n *\n * URL 쿼리 파라미터(id, mode)를 읽어서 상품정보제공고시 상태를 초기화합니다.\n * 상품정보제공고시 관리 페이지에서 URL로 직접 접근할 때 해당 템플릿을 선택 상태로 표시합니다.\n */\n\nconst logger = ((window as any).G7Core?.createLogger?.('Handler:InitNotice')) ?? {\n log: (...args: unknown[]) => console.log('[Handler:InitNotice]', ...args),\n warn: (...args: unknown[]) => console.warn('[Handler:InitNotice]', ...args),\n error: (...args: unknown[]) => console.error('[Handler:InitNotice]', ...args),\n};\n\nfunction getQueryParam(paramName: string): string | null {\n const urlParams = new URLSearchParams(window.location.search);\n return urlParams.get(paramName);\n}\n\nfunction getDataSource(dataSourceId: string): any {\n const g7Core = (window as any).G7Core;\n return g7Core?.state?.getDataSource?.(dataSourceId);\n}\n\nasync function waitForDataSource(\n dataSourceId: string,\n dataPath: (ds: any) => any[] | undefined,\n maxAttempts: number = 30,\n interval: number = 100\n): Promise {\n for (let i = 0; i < maxAttempts; i++) {\n const dataSource = getDataSource(dataSourceId);\n const data = dataPath(dataSource);\n if (Array.isArray(data) && data.length > 0) {\n return data;\n }\n await new Promise((resolve) => setTimeout(resolve, interval));\n }\n return null;\n}\n\nexport async function initNoticeFromUrlHandler(\n _action: any,\n _context?: any\n): Promise {\n const g7Core = (window as any).G7Core;\n\n const idParam = getQueryParam('id');\n const mode = getQueryParam('mode');\n\n if (!idParam) {\n logger.log('[initNoticeFromUrl] No id parameter in URL');\n return;\n }\n\n if (!g7Core?.state?.set) {\n logger.warn('[initNoticeFromUrl] G7Core.state.set not available');\n return;\n }\n\n const data = await waitForDataSource('templates', (ds) => ds?.data);\n\n if (!data) {\n logger.warn('[initNoticeFromUrl] templates data not available after waiting');\n return;\n }\n\n const templateId = parseInt(idParam, 10);\n const found = data.find((item: any) => item.id === templateId);\n\n if (!found) {\n logger.warn('[initNoticeFromUrl] Template not found with id:', templateId);\n return;\n }\n\n const panelMode = mode === 'edit' ? 'edit' : 'view';\n\n g7Core.state.set({\n selectedTemplateId: found.id,\n selectedTemplate: found,\n panelMode: panelMode,\n });\n\n logger.log('[initNoticeFromUrl] Template initialized from URL:', {\n id: templateId,\n mode: panelMode,\n });\n}\n","/**\n * sirsoft-ecommerce 모듈 핸들러\n *\n * 모듈에서 사용하는 모든 커스텀 핸들러를 정의합니다.\n */\n\n// 기존 핸들러\nimport { updateProductFieldHandler } from './updateProductField';\nimport { updateOptionFieldHandler } from './updateOptionField';\nimport { calculateCurrencyPricesHandler } from './calculateCurrencyPrices';\nimport { initPreferredCurrencyHandler } from './initPreferredCurrency';\nimport { initPreferredShippingCountryHandler } from './initPreferredShippingCountry';\nimport { setDateRangeHandler } from './setDateRange';\nimport { setDefaultOptionHandler } from './setDefaultOption';\nimport {\n toggleOptionHandler,\n toggleProductOptionsHandler,\n toggleAllOptionsInRowHandler,\n getProductOptionStatesHandler,\n syncProductSelectionHandler,\n} from './optionSelection';\nimport { generateCopyProductCodeHandler } from './generateCopyProductCode';\nimport { copyProductHandler } from './copyProduct';\n\n// 카테고리 관련 핸들러\nimport {\n selectCategoryHandler,\n selectCategoryMobileHandler,\n addCategoryToSelectionHandler,\n removeCategoryFromSelectionHandler,\n getCategoryBreadcrumbHandler,\n validateCategoryPathHandler,\n initCategoryInfosFromProductHandler,\n} from './categoryHandlers';\n\n// 브랜드 관련 핸들러\nimport {\n getBrandNameHandler,\n getBrandDescriptionHandler,\n} from './brandHandlers';\n\n// 가격 관련 핸들러\nimport {\n updatePriceHandler,\n calculateTotalOptionStockHandler,\n validatePriceRelationHandler,\n} from './priceHandlers';\n\n// 옵션 관련 핸들러\nimport {\n addOptionInputHandler,\n removeOptionInputHandler,\n updateOptionInputHandler,\n generateOptionsHandler,\n deleteOptionHandler,\n applyOptionAddToolHandler,\n addRequiredItemHandler,\n updateRequiredItemHandler,\n removeRequiredItemHandler,\n reorderRequiredItemsHandler,\n addAdditionalOptionHandler,\n updateAdditionalOptionHandler,\n removeAdditionalOptionHandler,\n reorderAdditionalOptionsHandler,\n clearAdditionalOptionsHandler,\n addAdditionalOptionValueHandler,\n updateAdditionalOptionValueHandler,\n removeAdditionalOptionValueHandler,\n} from './optionHandlers';\n\n// 이미지 관련 핸들러\nimport {\n uploadImagesHandler,\n setThumbnailHandler,\n reorderImagesHandler,\n} from './imageHandlers';\n\n// 상세설명 관련 핸들러\nimport {\n updateDescriptionHandler,\n} from './descriptionHandlers';\n\n// 상품정보제공고시 관련 핸들러\nimport {\n confirmSelectNoticeTemplateHandler,\n selectNoticeTemplateHandler,\n updateNoticeItemHandler,\n removeNoticeItemHandler,\n reorderNoticeItemsHandler,\n fillNoticeWithValueHandler,\n switchNoticeModeHandler,\n updateNewTemplateNameHandler,\n addNoticeItemHandler,\n updateNoticeItemNameHandler,\n saveAsNoticeTemplateHandler,\n confirmSaveNoticeTemplateHandler,\n fillTemplateFieldsWithDetailReferenceHandler,\n fillNoticeItemsWithDetailReferenceHandler,\n} from './noticeHandlers';\n\n// 기타 섹션 핸들러\nimport {\n toggleLabelHandler,\n generateProductCodeHandler,\n getShippingPolicyInfoHandler,\n getCommonInfoContentHandler,\n updateShoppingIntegrationHandler,\n updateShippingTypeHandler,\n updateIdentificationCodeHandler,\n openLabelPeriodModalHandler,\n saveLabelPeriodHandler,\n removeLabelPeriodHandler,\n updateActivityLogSortHandler,\n updateActivityLogPerPageHandler,\n setDefaultShippingPolicyHandler,\n setLabelDatePresetHandler,\n toggleDefaultShippingPolicyHandler,\n} from './miscHandlers';\n\n// 라벨 할당 핸들러\nimport {\n toggleLabelAssignmentHandler,\n saveLabelSettingsHandler,\n deleteLabelHandler,\n updateLabelPeriodInlineHandler,\n setLabelDatePresetInlineHandler,\n confirmUncheckLabelHandler,\n} from './labelHandlers';\n\n// 상세설명 다국어 핸들러\nimport {\n removeDescriptionLocaleHandler,\n showAddLocaleModalHandler,\n addDescriptionLocaleHandler,\n} from './descriptionLocaleHandlers';\n\n// 상품옵션 추가 핸들러\nimport {\n setDefaultOptionHandler as setDefaultOptionHandler2,\n addOptionRowHandler,\n updateFormOptionFieldHandler,\n recalculateOptionPriceAdjustmentsHandler,\n} from './productOptionHandlers';\n\n// 통합 일괄 업데이트 핸들러\nimport {\n bulkUpdateHandler,\n buildConfirmDataHandler,\n} from './bulkUpdateHandlers';\n\n// 주문관리 핸들러\nimport {\n buildOrderColumnsHandler,\n toggleArrayValueHandler,\n toggleVisibleFilterHandler,\n syncOrderSelectionHandler,\n handleOrderRowActionHandler,\n processOrderBulkActionHandler,\n buildOrderBulkConfirmDataHandler,\n executeOrderBulkActionHandler,\n downloadOrderExcelHandler,\n saveVisibleColumnsHandler,\n loadVisibleColumnsHandler,\n loadVisibleFiltersHandler,\n} from './orderHandlers';\n\n// 상품 목록 핸들러\nimport { handleProductRowActionHandler } from './productListHandlers';\n\n// 주문 상세 핸들러\nimport {\n initOrderDetailFormHandler,\n toggleProductSelectionHandler,\n toggleAllProductsHandler,\n buildOrderDetailBulkConfirmDataHandler,\n processOrderDetailBulkChangeHandler,\n saveAdminMemoHandler,\n updateChangeQuantityHandler,\n openConfirmDepositModalHandler,\n confirmDepositHandler,\n} from './orderDetailHandlers';\n\n// 주문 취소 핸들러 (관리자)\nimport {\n updateCancelQuantityHandler,\n estimateRefundAmountHandler,\n changeRefundPriorityHandler,\n executeCancelOrderHandler,\n clearCancelOrderTimers,\n} from './cancelOrderHandlers';\n\n// 주문 취소 핸들러 (사용자)\nimport {\n toggleItemSelectionHandler,\n toggleSelectAllItemsHandler,\n initUserCancelItemsHandler,\n toggleUserCancelItemHandler,\n toggleUserCancelSelectAllHandler,\n updateUserCancelQuantityHandler,\n estimateUserRefundHandler,\n changeUserRefundPriorityHandler,\n executeUserCancelOrderHandler,\n clearUserCancelOrderTimers,\n} from './userCancelOrderHandlers';\n\n// 구매확정 핸들러 (사용자)\nimport { confirmOrderOptionHandler } from './userConfirmOrderHandlers';\n\n// 배송지 변경 핸들러 (회원/비회원 공용)\nimport { changeShippingAddressHandler } from './userChangeShippingAddressHandlers';\n\n// 리뷰 작성 핸들러 (사용자)\nimport { submitReviewHandler } from './userReviewHandlers';\n\n// 배송정책 폼 핸들러\nimport {\n initShippingPolicyFormHandler,\n addCountrySettingHandler,\n removeCountrySettingHandler,\n switchCountryTabHandler,\n updateCountryFieldHandler,\n onChargePolicyChangeHandler,\n addRangeTierHandler,\n removeRangeTierHandler,\n updateRangeTierFieldHandler,\n validateRangeTiersHandler,\n addExtraFeeRowHandler,\n removeExtraFeeRowHandler,\n applyExtraFeeTemplateHandler,\n updateUnitValueHandler,\n addApiRequestFieldHandler,\n updateApiRequestFieldHandler,\n removeApiRequestFieldHandler,\n toggleApiRequestFieldHandler,\n updateApiConfigFieldHandler,\n updateApiFieldMapHandler,\n testShippingApiHandler,\n updateExtraFeeFieldHandler,\n} from './shippingPolicyFormHandlers';\n\n// URL 상태 복원 핸들러\nimport { initCategoryFromUrlHandler } from './initCategoryFromUrl';\nimport { initBrandFromUrlHandler } from './initBrandFromUrl';\nimport { initCommonInfoFromUrlHandler } from './initCommonInfoFromUrl';\nimport { initNoticeFromUrlHandler } from './initNoticeFromUrl';\n\n/**\n * 핸들러 맵\n *\n * 키: 핸들러 이름 (네임스페이스 없이)\n * 값: 핸들러 함수\n *\n * ActionDispatcher에 등록 시 모듈 식별자가 네임스페이스로 추가됩니다.\n * 예: 'updateProductField' -> 'sirsoft-ecommerce.updateProductField'\n */\nexport const handlerMap = {\n // ===== 기존 핸들러 =====\n updateProductField: updateProductFieldHandler,\n updateOptionField: updateOptionFieldHandler,\n calculateCurrencyPrices: calculateCurrencyPricesHandler,\n initPreferredCurrency: initPreferredCurrencyHandler,\n initPreferredShippingCountry: initPreferredShippingCountryHandler,\n setDateRange: setDateRangeHandler,\n setDefaultOption: setDefaultOptionHandler,\n // 옵션 선택 관련 핸들러\n toggleOption: toggleOptionHandler,\n toggleProductOptions: toggleProductOptionsHandler,\n toggleAllOptionsInRow: toggleAllOptionsInRowHandler,\n getProductOptionStates: getProductOptionStatesHandler,\n syncProductSelection: syncProductSelectionHandler,\n // 상품 폼 모달 관련 핸들러\n generateCopyProductCode: generateCopyProductCodeHandler,\n copyProduct: copyProductHandler,\n\n // ===== 카테고리 관련 =====\n selectCategory: selectCategoryHandler,\n selectCategoryMobile: selectCategoryMobileHandler,\n addCategoryToSelection: addCategoryToSelectionHandler,\n removeCategoryFromSelection: removeCategoryFromSelectionHandler,\n getCategoryBreadcrumb: getCategoryBreadcrumbHandler,\n validateCategoryPath: validateCategoryPathHandler,\n initCategoryInfosFromProduct: initCategoryInfosFromProductHandler,\n\n // ===== 브랜드 관련 =====\n getBrandName: getBrandNameHandler,\n getBrandDescription: getBrandDescriptionHandler,\n\n // ===== 가격 관련 =====\n updatePrice: updatePriceHandler,\n calculateTotalOptionStock: calculateTotalOptionStockHandler,\n validatePriceRelation: validatePriceRelationHandler,\n\n // ===== 옵션 관련 =====\n addOptionInput: addOptionInputHandler,\n removeOptionInput: removeOptionInputHandler,\n updateOptionInput: updateOptionInputHandler,\n generateOptions: generateOptionsHandler,\n deleteOption: deleteOptionHandler,\n applyOptionAddTool: applyOptionAddToolHandler,\n addRequiredItem: addRequiredItemHandler,\n updateRequiredItem: updateRequiredItemHandler,\n removeRequiredItem: removeRequiredItemHandler,\n reorderRequiredItems: reorderRequiredItemsHandler,\n addAdditionalOption: addAdditionalOptionHandler,\n updateAdditionalOption: updateAdditionalOptionHandler,\n removeAdditionalOption: removeAdditionalOptionHandler,\n reorderAdditionalOptions: reorderAdditionalOptionsHandler,\n clearAdditionalOptions: clearAdditionalOptionsHandler,\n addAdditionalOptionValue: addAdditionalOptionValueHandler,\n updateAdditionalOptionValue: updateAdditionalOptionValueHandler,\n removeAdditionalOptionValue: removeAdditionalOptionValueHandler,\n\n // ===== 이미지 관련 =====\n uploadImages: uploadImagesHandler,\n setThumbnail: setThumbnailHandler,\n reorderImages: reorderImagesHandler,\n\n // ===== 상세설명 관련 =====\n updateDescription: updateDescriptionHandler,\n\n // ===== 상품정보제공고시 관련 =====\n confirmSelectNoticeTemplate: confirmSelectNoticeTemplateHandler,\n selectNoticeTemplate: selectNoticeTemplateHandler,\n updateNoticeItem: updateNoticeItemHandler,\n removeNoticeItem: removeNoticeItemHandler,\n reorderNoticeItems: reorderNoticeItemsHandler,\n fillNoticeWithValue: fillNoticeWithValueHandler,\n switchNoticeMode: switchNoticeModeHandler,\n updateNewTemplateName: updateNewTemplateNameHandler,\n addNoticeItem: addNoticeItemHandler,\n updateNoticeItemName: updateNoticeItemNameHandler,\n saveAsNoticeTemplate: saveAsNoticeTemplateHandler,\n confirmSaveNoticeTemplate: confirmSaveNoticeTemplateHandler,\n fillTemplateFieldsWithDetailReference: fillTemplateFieldsWithDetailReferenceHandler,\n fillNoticeItemsWithDetailReference: fillNoticeItemsWithDetailReferenceHandler,\n\n // ===== 기타 섹션 =====\n toggleLabel: toggleLabelHandler,\n generateProductCode: generateProductCodeHandler,\n getShippingPolicyInfo: getShippingPolicyInfoHandler,\n getCommonInfoContent: getCommonInfoContentHandler,\n updateShoppingIntegration: updateShoppingIntegrationHandler,\n updateShippingType: updateShippingTypeHandler,\n updateIdentificationCode: updateIdentificationCodeHandler,\n openLabelPeriodModal: openLabelPeriodModalHandler,\n saveLabelPeriod: saveLabelPeriodHandler,\n removeLabelPeriod: removeLabelPeriodHandler,\n updateActivityLogSort: updateActivityLogSortHandler,\n updateActivityLogPerPage: updateActivityLogPerPageHandler,\n setDefaultShippingPolicy: setDefaultShippingPolicyHandler,\n setLabelDatePreset: setLabelDatePresetHandler,\n toggleDefaultShippingPolicy: toggleDefaultShippingPolicyHandler,\n\n // ===== 라벨 할당 =====\n toggleLabelAssignment: toggleLabelAssignmentHandler,\n saveLabelSettings: saveLabelSettingsHandler,\n deleteLabel: deleteLabelHandler,\n updateLabelPeriodInline: updateLabelPeriodInlineHandler,\n setLabelDatePresetInline: setLabelDatePresetInlineHandler,\n confirmUncheckLabel: confirmUncheckLabelHandler,\n\n // ===== 상세설명 다국어 =====\n removeDescriptionLocale: removeDescriptionLocaleHandler,\n showAddLocaleModal: showAddLocaleModalHandler,\n addDescriptionLocale: addDescriptionLocaleHandler,\n\n // ===== 상품옵션 추가 =====\n setDefaultOptionFromGrid: setDefaultOptionHandler2,\n addOptionRow: addOptionRowHandler,\n updateFormOptionField: updateFormOptionFieldHandler,\n recalculateOptionPriceAdjustments: recalculateOptionPriceAdjustmentsHandler,\n\n // ===== 통합 일괄 업데이트 =====\n bulkUpdate: bulkUpdateHandler,\n buildConfirmData: buildConfirmDataHandler,\n\n // ===== 주문관리 =====\n buildOrderColumns: buildOrderColumnsHandler,\n toggleArrayValue: toggleArrayValueHandler,\n toggleVisibleFilter: toggleVisibleFilterHandler,\n syncOrderSelection: syncOrderSelectionHandler,\n handleOrderRowAction: handleOrderRowActionHandler,\n processOrderBulkAction: processOrderBulkActionHandler,\n buildOrderBulkConfirmData: buildOrderBulkConfirmDataHandler,\n executeOrderBulkAction: executeOrderBulkActionHandler,\n downloadOrderExcel: downloadOrderExcelHandler,\n saveVisibleColumns: saveVisibleColumnsHandler,\n loadVisibleColumns: loadVisibleColumnsHandler,\n loadVisibleFilters: loadVisibleFiltersHandler,\n\n // ===== 상품 목록 =====\n handleProductRowAction: handleProductRowActionHandler,\n\n // ===== 주문 상세 =====\n initOrderDetailForm: initOrderDetailFormHandler,\n toggleProductSelection: toggleProductSelectionHandler,\n toggleAllProducts: toggleAllProductsHandler,\n buildOrderDetailBulkConfirmData: buildOrderDetailBulkConfirmDataHandler,\n processOrderDetailBulkChange: processOrderDetailBulkChangeHandler,\n saveAdminMemo: saveAdminMemoHandler,\n updateChangeQuantity: updateChangeQuantityHandler,\n openConfirmDepositModal: openConfirmDepositModalHandler,\n confirmDeposit: confirmDepositHandler,\n\n // ===== 배송정책 폼 =====\n initShippingPolicyForm: initShippingPolicyFormHandler,\n addCountrySetting: addCountrySettingHandler,\n removeCountrySetting: removeCountrySettingHandler,\n switchCountryTab: switchCountryTabHandler,\n updateCountryField: updateCountryFieldHandler,\n onChargePolicyChange: onChargePolicyChangeHandler,\n addRangeTier: addRangeTierHandler,\n removeRangeTier: removeRangeTierHandler,\n updateRangeTierField: updateRangeTierFieldHandler,\n validateRangeTiers: validateRangeTiersHandler,\n addExtraFeeRow: addExtraFeeRowHandler,\n removeExtraFeeRow: removeExtraFeeRowHandler,\n applyExtraFeeTemplate: applyExtraFeeTemplateHandler,\n updateUnitValue: updateUnitValueHandler,\n addApiRequestField: addApiRequestFieldHandler,\n updateApiRequestField: updateApiRequestFieldHandler,\n removeApiRequestField: removeApiRequestFieldHandler,\n toggleApiRequestField: toggleApiRequestFieldHandler,\n updateApiConfigField: updateApiConfigFieldHandler,\n updateApiFieldMap: updateApiFieldMapHandler,\n testShippingApi: testShippingApiHandler,\n updateExtraFeeField: updateExtraFeeFieldHandler,\n\n // ===== 주문 취소 (관리자) =====\n updateCancelQuantity: updateCancelQuantityHandler,\n estimateRefundAmount: estimateRefundAmountHandler,\n changeRefundPriority: changeRefundPriorityHandler,\n executeCancelOrder: executeCancelOrderHandler,\n clearCancelOrderTimers: clearCancelOrderTimers,\n\n // ===== 주문 상품 선택 (사용자) =====\n toggleItemSelection: toggleItemSelectionHandler,\n toggleSelectAllItems: toggleSelectAllItemsHandler,\n\n // ===== 주문 취소 (사용자) =====\n initUserCancelItems: initUserCancelItemsHandler,\n toggleUserCancelItem: toggleUserCancelItemHandler,\n toggleUserCancelSelectAll: toggleUserCancelSelectAllHandler,\n updateUserCancelQuantity: updateUserCancelQuantityHandler,\n estimateUserRefund: estimateUserRefundHandler,\n changeUserRefundPriority: changeUserRefundPriorityHandler,\n executeUserCancelOrder: executeUserCancelOrderHandler,\n clearUserCancelOrderTimers: clearUserCancelOrderTimers,\n\n // ===== 구매확정 (사용자) =====\n confirmOrderOption: confirmOrderOptionHandler,\n\n // ===== 배송지 변경 (회원/비회원 공용) =====\n changeShippingAddress: changeShippingAddressHandler,\n\n // ===== 리뷰 작성 (사용자) =====\n submitReview: submitReviewHandler,\n\n // ===== URL 상태 복원 =====\n initCategoryFromUrl: initCategoryFromUrlHandler,\n initBrandFromUrl: initBrandFromUrlHandler,\n initCommonInfoFromUrl: initCommonInfoFromUrlHandler,\n initNoticeFromUrl: initNoticeFromUrlHandler,\n} as const;\n","/**\n * sirsoft-ecommerce 모듈 엔트리포인트\n *\n * 모듈 활성화 시 자동으로 로드되어 핸들러를 등록합니다.\n */\n\n// CSS 임포트 (Vite 번들링)\nimport '../css/main.css';\n\n// 핸들러 맵 임포트\nimport { handlerMap } from './handlers';\n\n// 모듈 식별자 (전체 식별자 사용)\nconst MODULE_IDENTIFIER = 'sirsoft-ecommerce';\n\n// Logger 설정 (G7Core 초기화 전에도 동작하도록 폴백 포함)\nconst logger = ((window as any).G7Core?.createLogger?.(`Module:${MODULE_IDENTIFIER}`)) ?? {\n log: (...args: unknown[]) => console.log(`[Module:${MODULE_IDENTIFIER}]`, ...args),\n warn: (...args: unknown[]) => console.warn(`[Module:${MODULE_IDENTIFIER}]`, ...args),\n error: (...args: unknown[]) => console.error(`[Module:${MODULE_IDENTIFIER}]`, ...args),\n};\n\n/**\n * 핸들러 등록 함수 (내부용)\n *\n * ActionDispatcher에 핸들러를 등록합니다.\n * retry 옵션으로 ActionDispatcher가 없을 때 재시도 여부를 제어합니다.\n *\n * @param retry 재시도 여부 (기본: false)\n */\nfunction registerHandlers(retry: boolean = false): void {\n const actionDispatcher = (window as any).G7Core?.getActionDispatcher?.();\n\n if (actionDispatcher) {\n // handlerMap의 모든 핸들러를 자동으로 등록\n // 네임스페이스: {module-identifier}.{handler-name}\n Object.entries(handlerMap).forEach(([name, handler]) => {\n const fullName = `${MODULE_IDENTIFIER}.${name}`;\n actionDispatcher.registerHandler(fullName, handler, {\n category: 'module',\n source: MODULE_IDENTIFIER,\n });\n });\n\n logger.log(\n `${Object.keys(handlerMap).length} handler(s) registered:`,\n Object.keys(handlerMap).map(name => `${MODULE_IDENTIFIER}.${name}`)\n );\n } else if (retry) {\n // 최초 로드 시에만 재시도 (언어 전환 등 재초기화 시에는 재시도하지 않음)\n let retryCount = 0;\n const maxRetries = 50; // 최대 5초 대기 (50 * 100ms)\n\n const retryRegister = () => {\n const dispatcher = (window as any).G7Core?.getActionDispatcher?.();\n\n if (dispatcher) {\n Object.entries(handlerMap).forEach(([name, handler]) => {\n const fullName = `${MODULE_IDENTIFIER}.${name}`;\n dispatcher.registerHandler(fullName, handler, {\n category: 'module',\n source: MODULE_IDENTIFIER,\n });\n });\n\n logger.log(\n `${Object.keys(handlerMap).length} handler(s) registered:`,\n Object.keys(handlerMap).map(name => `${MODULE_IDENTIFIER}.${name}`)\n );\n } else {\n retryCount++;\n if (retryCount <= maxRetries) {\n logger.warn(\n `ActionDispatcher not found, retrying... (${retryCount}/${maxRetries})`\n );\n setTimeout(retryRegister, 100);\n } else {\n logger.error(\n `Failed to register handlers: ActionDispatcher not available after maximum retries`\n );\n }\n }\n };\n\n retryRegister();\n } else {\n logger.warn(\n `ActionDispatcher not found, handlers not registered`\n );\n }\n}\n\n/**\n * 모듈 초기화 함수\n *\n * ActionDispatcher에 핸들러를 등록합니다.\n * 모듈 식별자를 네임스페이스로 사용하여 다른 모듈과 충돌을 방지합니다.\n *\n * 이 함수는 다음 상황에서 호출됩니다:\n * 1. 최초 모듈 로드 시 (자동 실행)\n * 2. 언어 전환 시 ActionDispatcher 재생성 후 (TemplateApp.reinitializeModuleHandlers)\n */\nexport function initModule(): void {\n // DOMContentLoaded 이벤트 사용 (DOM 파싱 완료 후, init_actions보다 먼저 실행되도록)\n if (document.readyState === 'loading') {\n document.addEventListener('DOMContentLoaded', () => registerHandlers(true));\n } else {\n // DOM이 이미 로드된 경우 즉시 실행\n // ActionDispatcher가 이미 있으면 바로 등록, 없으면 재시도\n const hasDispatcher = !!(window as any).G7Core?.getActionDispatcher?.();\n registerHandlers(hasDispatcher ? false : true);\n }\n}\n\n// 모듈 초기화 자동 실행\ninitModule();\n\n// 전역 객체에 모듈 노출 (디버깅용)\nif (typeof window !== 'undefined') {\n (window as any).__SirsoftEcommerce = {\n identifier: MODULE_IDENTIFIER,\n handlers: Object.keys(handlerMap),\n initModule,\n };\n}\n"],"names":["DECIMAL_PLACES_FALLBACK","SYMBOL_FALLBACK","BASE_UNIT_FALLBACK","resolveBaseUnit","currency","unit","applyRounding","price","method","unitValue","divided","rounded","formatCurrency","c","code","symbol","decimalPlaces","formattedNumber","calculateCurrencyPricesHandler","params","_context","basePrice","currencies","defaultCurrency","baseUnit","result","convertedPrice","roundedPrice","logger","args","updateProductFieldHandler","action","context","productId","field","dataSourceId","value","G7Core","currentData","productsArray","globalState","newErrors","updatedProducts","product","currentFieldValue","finalValue","numericValue","listPrice","e","updatedProduct","currentLocal","modifiedProductIds","modifiedProductFields","productKey","existingFields","updateOptionFieldHandler","optionId","productSellingPriceSynced","options","optionIndex","o","currentOption","updatedOptions","updatedOption","productSellingPriceSync","newSellingPrice","productSellingPrice","i","sibling","adj","siblingSellingPrice","updatedSibling","updatedStockQuantity","updatedOptionStockSum","sum","opt","modifiedOptionIds","optionKey","modifiedOptionFields","setLocalPayload","productFields","PREFERRED_CURRENCY_STORAGE_NAME","LOCALE_STORAGE_NAME","setGlobalState","updates","getSiteLocale","getCurrencies","lc","resolveCurrencyByLocale","locale","normalized","matched","l","getCurrentUser","g7Config","initPreferredCurrencyHandler","isValid","user","persisted","stored","localeGuess","resolved","PREFERRED_SHIPPING_COUNTRY_STORAGE_NAME","initPreferredShippingCountryHandler","defaultCountry","formatDate","date","year","month","day","calculateDateRange","preset","today","endDate","startDate","start","setDateRangeHandler","setDefaultOptionHandler","selectedOption","option","isSelected","newListPrice","multiCurrencySellingPrice","makeOptionKey","getProductIdFromKey","toggleOptionHandler","checked","currentSelectedOptions","currentSelectedItems","newSelectedOptions","key","productOptions","newSelectedItems","numericProductId","id","toggleProductOptionsHandler","productOptionKeys","toggleAllOptionsInRowHandler","getProductOptionStatesHandler","products","selectedOptionIds","selected","states","selectedCount","syncProductSelectionHandler","selectedIds","previousSelectedIds","newlySelected","newlyDeselected","p","generateCopyProductCodeHandler","response","newProductCode","localState","error","copyProductHandler","parentLocal","copyOptions","originalProductCode","query","targetUrl","buildBreadcrumbFromTree","categories","targetId","path","cat","catName","found","initCategoryInfosFromFormCategories","formCategories","categoriesTree","breadcrumb","selectCategoryHandler","depth","category","isolatedContext","currentSelected","newSelected","idx","state","selectCategoryMobileHandler","currentStep","nextStep","addCategoryToSelectionHandler","_action","selectedCats","lastSelected","currentIds","newCategoryIds","primaryCategoryId","currentInfos","newInfos","afterState","removeCategoryFromSelectionHandler","categoryId","newIds","newPrimaryCategoryId","info","getCategoryBreadcrumbHandler","findPath","cats","pathNames","initCategoryInfosFromProductHandler","productCategories","categoryIds","findBreadcrumb","selectedCategoryInfos","productCat","validateCategoryPathHandler","categoryId1","categoryId2","categoryId3","categoryId4","cat1","cat2","cat3","cat4","getBrandNameHandler","brandId","brands","brand","b","getBrandDescriptionHandler","resolveDefaultBaseUnit","calculateCurrencyPrice","exchangeRate","roundingMethod","converted","updatePriceHandler","autoFill","currencyData","newPrices","curr","convertedValue","mainFieldMap","mainFieldValue","calculateTotalOptionStockHandler","totalStock","validatePriceRelationHandler","sellingPrice","hasError","errorMessage","hasAnyError","getSupportedLocales","createEmptyLocalizedField","acc","sumActiveOptionStock","base","convertCurrencyPrice","divisor","finalPrice","recalcOptionsFromProductPrice","newProductPrice","priceAdjustment","newOptionSellingPrice","updatedOpt","multiCurrencyPrices","resolveAutoCurrencies","rawIndex","index","optionCode","newDefault","updateFormOptionFieldHandler","numericFields","optionSellingPrice","recalculated","addOptionRowHandler","defaultStockQuantity","optionGroups","newOptionCode","newOptionValues","group","newOption","nextOptions","recalculateOptionPriceAdjustmentsHandler","newProductSellingPrice","getDefaultLocale","createEmptyAdditionalOptionValue","sortOrder","getValueForLocale","isMultilingualValue","ensureValuesArray","values","cartesianProduct","arrays","a","generateMultilingualOptionName","combo","locales","parts","v","generateMultilingualOptionValues","inputs","inp","addOptionInputHandler","removeOptionInputHandler","updateOptionInputHandler","defaultLocale","existingName","newName","generateOptionsHandler","getOptionName","normalizedInputs","validInputs","existingOptions","skipConfirm","productListPrice","productMultiCurrencySP","fullCurrencies","optionValues","optionName","getOptionNameKey","mergedOptions","newOpt","newKey","existing","deleteOptionHandler","rowIndex","hasOptions","applyOptionAddToolHandler","applyToAll","inheritPrice","addRequiredItemHandler","requiredItems","newItem","updateRequiredItemHandler","removeRequiredItemHandler","item","reorderRequiredItemsHandler","oldIndex","newIndex","moved","addAdditionalOptionHandler","tempId","updateAdditionalOptionHandler","baseName","removeAdditionalOptionHandler","reorderAdditionalOptionsHandler","clearAdditionalOptionsHandler","ensureGroupValues","addAdditionalOptionValueHandler","groupIndex","updateAdditionalOptionValueHandler","valueIndex","numeric","removeAdditionalOptionValueHandler","ALLOWED_IMAGE_TYPES","MAX_FILE_SIZE","MAX_IMAGES","uploadImagesHandler","files","currentImages","maxAllowed","filesToUpload","file","currentState","formData","percent","progressState","successState","existingImages","newImage","cleanupState","remainingProgress","errorState","_","setThumbnailHandler","images","img","reorderImagesHandler","movedImage","updateDescriptionHandler","createEmptyLocaleObject","defaultValue","supportedLocales","confirmSelectNoticeTemplateHandler","templateId","existingItems","hasExistingItems","selectNoticeTemplateHandler","dsData","templates","template","t","newItems","fieldKey","updateNoticeItemHandler","items","removeNoticeItemHandler","reorderNoticeItemsHandler","fillNoticeWithValueHandler","switchNoticeModeHandler","mode","updateNewTemplateNameHandler","addNoticeItemHandler","updateNoticeItemNameHandler","saveAsNoticeTemplateHandler","DETAIL_REF_TRANSLATIONS","fillTemplateFieldsWithDetailReferenceHandler","fields","detailRefContent","updatedFields","fillNoticeItemsWithDetailReferenceHandler","noticeItems","updatedItems","confirmSaveNoticeTemplateHandler","saveTemplateData","templateName","toggleLabelHandler","labelId","labels","newLabels","generateProductCodeHandler","getShippingPolicyInfoHandler","policyId","policies","policy","getCommonInfoContentHandler","updateShoppingIntegrationHandler","platform","integrations","updateShippingTypeHandler","type","defaultFee","defaultThreshold","updateIdentificationCodeHandler","openLabelPeriodModalHandler","labelName","existingPeriod","saveLabelPeriodHandler","existingIndex","removeLabelPeriodHandler","updateActivityLogSortHandler","sort","updateActivityLogPerPageHandler","perPage","setDefaultShippingPolicyHandler","defaultPolicy","toggleDefaultShippingPolicyHandler","newUi","setLabelDatePresetHandler","end","toggleLabelAssignmentHandler","formLabelAssignments","currentAssignments","updatedAssignments","updateLabelPeriodInlineHandler","setLabelDatePresetInlineHandler","confirmUncheckLabelHandler","deleteLabelHandler","labelToDeleteId","saveLabelSettingsHandler","editingLabelId","labelFormData","newId","removeDescriptionLocaleHandler","newDescription","newDescriptionMobile","activeLocales","showAddLocaleModalHandler","addDescriptionLocaleHandler","handleBulkUpdateError","fallbackKey","data","serverMessage","errorsObj","detailMessages","msgs","toastMessage","localizeValue","localizeStatus","getG7Core","bulkUpdateHandler","selectedItems","bulkUpdateProductsInternal","bulkUpdateOptionsInternal","productBulkChanges","optionBulkChanges","priceMethodMap","stockMethodMap","modifiedOptionFieldsMap","selectedProductIdSet","productItems","optionItems","collectOptionItem","optModifiedFields","pModifiedFields","payload","resetBulkState","bulkChanges","isOptionModified","buildConfirmDataHandler","confirmData","hasBulkChanges","collectOptionChanges","optChanges","priceValue","changes","isProductModified","salesLabel","displayLabel","buildOrderColumnsHandler","baseColumns","currencyColumns","toggleArrayValueHandler","target","pathParts","currentArray","part","newArray","toggleVisibleFilterHandler","filterKey","currentFilters","newFilters","f","syncOrderSelectionHandler","handleOrderRowActionHandler","actionId","row","processOrderBulkActionHandler","actionType","orderIds","status","carrierId","trackingNumber","endpoint","body","downloadOrderExcelHandler","downloadType","filters","token","errorData","blob","url","dateStr","saveVisibleColumnsHandler","columns","loadVisibleColumnsHandler","defaultColumns","saved","parsed","loadVisibleFiltersHandler","defaultFilters","userId","authManager","storageKey","buildOrderBulkConfirmDataHandler","bulkOrderStatus","bulkCourier","bulkTrackingNumber","executeOrderBulkActionHandler","handleProductRowActionHandler","readBatchTrackingFromDom","initOrderDetailFormHandler","orderData","toggleProductSelectionHandler","totalOptions","selectAll","toggleAllProductsHandler","currentSelectAll","allIds","buildOrderDetailBulkConfirmDataHandler","selectedProducts","batchOrderStatus","batchCarrierId","batchTrackingNumber","cancelPg","cancelItems","orderId","changeQuantities","bulkConfirmItems","processOrderDetailBulkChangeHandler","pageLocal","changedCount","saveAdminMemoHandler","adminMemo","updateChangeQuantityHandler","maxQuantity","rawValue","clamped","currentQuantities","openConfirmDepositModalHandler","depositorName","depositAmount","confirmDepositHandler","local","amount","rawMarkComplete","markOrderComplete","detailMessage","fallbackMessage","estimateDebounceTimer","clearCancelOrderTimers","updateCancelQuantityHandler","savedActionContext","previousContext","estimateRefundAmountInternal","estimateRefundAmountHandler","paramCancelItems","refundPriority","changeRefundPriorityHandler","priority","executeCancelOrderHandler","cancelReason","cancelReasonDetail","isValidationError","pgError","validationErrors","userEstimateDebounceTimer","hasGuestOrderContext","orderNumber","clearUserCancelOrderTimers","toggleItemSelectionHandler","cancellableIds","allSelected","toggleSelectAllItemsHandler","newSelectAll","initUserCancelItemsHandler","selectedItemIds","estimateUserRefundInternal","toggleUserCancelItemHandler","toggleUserCancelSelectAllHandler","updateUserCancelQuantityHandler","estimateUserRefundHandler","guestToken","isGuest","config","changeUserRefundPriorityHandler","executeUserCancelOrderHandler","confirmOrderOptionHandler","changeShippingAddressHandler","submitReviewHandler","rating","content","reviewId","imgError","httpStatus","firstError","REQUIRES_BASE_FEE","REQUIRES_FREE_THRESHOLD","REQUIRES_RANGES","REQUIRES_API","REQUIRES_UNIT_VALUE","DEFAULT_COUNTRY_SETTING","getVisibilityFlags","chargePolicy","updateLocalState","getCountrySettings","initShippingPolicyFormHandler","isEdit","isCopy","stateUpdates","countrySettings","flags","addCountrySettingHandler","countryCode","cs","newSetting","removeCountrySettingHandler","removedCode","currentTab","newTab","rangeErrors","activeCS","switchCountryTabHandler","targetCS","updateCountryFieldHandler","onChargePolicyChangeHandler","countryIndex","updatedCS","rangeType","addRangeTierHandler","currentTiers","lastTier","newTier","validateRangeTiersInternal","removeRangeTierHandler","tierIndex","updateRangeTierFieldHandler","parsedValue","validateRangeTiersHandler","tiers","_countryIndex","tierErrors","tier","nextTier","addExtraFeeRowHandler","currentSettings","removeExtraFeeRowHandler","feeIndex","applyExtraFeeTemplateHandler","settings","message","updateUnitValueHandler","addApiRequestFieldHandler","currentFields","updateApiRequestFieldHandler","fieldIndex","removeApiRequestFieldHandler","toggleApiRequestFieldHandler","nextFields","updateApiConfigFieldHandler","nextConfig","updateApiFieldMapHandler","externalKey","fieldMap","testShippingApiHandler","updateExtraFeeFieldHandler","findCategoryById","getQueryParam","paramName","getDataSource","waitForDataSource","dataPath","maxAttempts","interval","dataSource","resolve","initCategoryFromUrlHandler","g7Core","idParam","ds","panelMode","initBrandFromUrlHandler","initCommonInfoFromUrlHandler","itemId","initNoticeFromUrlHandler","handlerMap","setDefaultOptionHandler2","MODULE_IDENTIFIER","registerHandlers","retry","actionDispatcher","name","handler","fullName","retryCount","maxRetries","retryRegister","dispatcher","initModule","hasDispatcher"],"mappings":"+CAyBA,MAAMA,GAAkD,CACpD,IAAK,EACL,IAAK,CACT,EAMMC,GAA0C,CAC5C,IAAK,IACL,IAAK,IACL,IAAK,IACL,IAAK,IACL,IAAK,IACL,IAAK,GACT,EAKMC,GAA6C,CAC/C,IAAK,IACL,IAAK,GACT,EAQA,SAASC,GAAgBC,EAAwC,CAC7D,GAAI,CAACA,EACD,MAAO,GAEX,MAAMC,EAAOD,EAAS,WAAaF,GAAmBE,EAAS,IAAI,GAAK,EACxE,OAAO,KAAK,IAAI,EAAGC,CAAI,CAC3B,CAuBA,SAASC,GAAcC,EAAeF,EAAcG,EAAwB,CACxE,MAAMC,EAAY,WAAWJ,CAAI,GAAK,EACtC,GAAII,GAAa,EACb,OAAOF,EAGX,MAAMG,EAAUH,EAAQE,EACxB,IAAIE,EAEJ,OAAQH,EAAA,CACJ,IAAK,OACDG,EAAU,KAAK,KAAKD,CAAO,EAC3B,MACJ,IAAK,QACDC,EAAU,KAAK,MAAMD,CAAO,EAC5B,MACJ,QACIC,EAAU,KAAK,MAAMD,CAAO,CAAA,CAGpC,OAAOC,EAAUF,CACrB,CAcO,SAASG,GACZL,EACAH,EACM,CACN,MAAMS,EAAI,OAAOT,GAAa,SAAW,CAAE,KAAMA,GAAaA,EACxDU,EAAOD,EAAE,KACTE,EAAUF,EAAE,QAAUA,EAAE,OAAO,OAAS,EACxCA,EAAE,OACDZ,GAAgBa,CAAI,GAAK,GAC1BE,EAAgBH,EAAE,gBAAkBb,GAAwBc,CAAI,GAAK,EACrEG,EAAkBV,EAAM,eAAe,OAAW,CACpD,sBAAuBS,EACvB,sBAAuBA,CAAA,CAC1B,EAGD,OAAKD,EAKDD,IAAS,OAASC,IAAW,KAAOA,IAAW,IACxCE,EAAkB,IAGtBF,EAASE,EARLA,EAAkB,IAAMH,CASvC,CAWO,SAASI,GACZC,EACAC,EAC6B,CAC7B,KAAM,CAAE,UAAAC,EAAW,WAAAC,CAAA,EAAeH,EAC5BZ,EAAQ,WAAW,OAAOc,CAAS,CAAC,GAAK,EAE/C,GAAI,CAACC,GAAc,CAAC,MAAM,QAAQA,CAAU,GAAKA,EAAW,SAAW,EACnE,MAAO,CAAA,EAIX,MAAMC,EAAkBD,EAAW,KAAMT,GAAMA,EAAE,UAAU,EACrDW,EAAWrB,GAAgBoB,CAAe,EAE1CE,EAAwC,CAAA,EAE9C,UAAWrB,KAAYkB,EAAY,CAC/B,MAAMR,EAAOV,EAAS,KAGtB,GAFkBA,EAAS,YAAc,GAIrCqB,EAAOX,CAAI,EAAI,CACX,MAAAP,EACA,UAAWK,GAAeL,EAAOH,CAAQ,EACzC,WAAY,GACZ,SAAU,EAAA,UAEPA,EAAS,eAAiBA,EAAS,cAAgB,EAAG,CAG7D,MAAMsB,EAAkBnB,EAAQiB,EAAYpB,EAAS,cAC/CuB,EAAerB,GACjBoB,EACAtB,EAAS,eAAiB,OAC1BA,EAAS,iBAAmB,OAAA,EAGhCqB,EAAOX,CAAI,EAAI,CACX,MAAOa,EACP,UAAWf,GAAee,EAAcvB,CAAQ,EAChD,WAAY,GACZ,SAAU,GACV,cAAeA,EAAS,aAAA,CAEhC,CACJ,CAEA,OAAOqB,CACX,CCpMA,MAAMG,EAAW,OAAe,QAAQ,eAAe,oBAAoB,GAAM,CAC7E,IAAK,IAAIC,IAAoB,QAAQ,IAAI,uBAAwB,GAAGA,CAAI,EACxE,KAAM,IAAIA,IAAoB,QAAQ,KAAK,uBAAwB,GAAGA,CAAI,EAC1E,MAAO,IAAIA,IAAoB,QAAQ,MAAM,uBAAwB,GAAGA,CAAI,CAChF,EAkCO,SAASC,GACZC,EACAC,EACI,CAEJ,MAAMb,EAAUY,EAAO,QAAU,CAAA,EAC3B,CAAE,UAAAE,EAAW,MAAAC,EAAO,aAAAC,EAAe,YAAehB,EACxD,GAAI,CAAE,MAAAiB,GAAUjB,EAUhB,GALIiB,GAAS,OAAOA,GAAU,UAAYA,EAAM,SAAW,SACvDA,EAAQA,EAAM,QAAQ,MACtBR,EAAO,IAAI,mDAAoDQ,CAAK,GAGpE,CAACH,GAAa,CAACC,EAAO,CACtBN,EAAO,KAAK,gDAAiD,CAAE,UAAAK,EAAW,MAAAC,EAAO,EACjF,MACJ,CAEA,GAAIE,IAAU,OAAW,CACrBR,EAAO,KAAK,0DAA0D,EACtE,MACJ,CAEA,MAAMS,EAAU,OAAe,OAC/B,GAAI,CAACA,GAAQ,YAAY,KAAO,CAACA,GAAQ,YAAY,IAAK,CACtDT,EAAO,KAAK,6DAA6D,EACzE,MACJ,CAGA,MAAMU,EAAcD,EAAO,WAAW,IAAIF,CAAY,EACtD,GAAI,CAACG,EAAa,CACd,QAAQ,KAAK,oCAAoCH,CAAY,aAAa,EAC1E,MACJ,CAGA,MAAMI,EAAgBD,EAAY,MAAM,MAAQ,CAAA,EAChD,GAAI,CAAC,MAAM,QAAQC,CAAa,GAAKA,EAAc,SAAW,EAAG,CAC7DX,EAAO,KAAK,wDAAwD,EACpE,MACJ,CAGA,MAAMY,EAAcH,EAAO,MAAM,IAAA,GAAS,CAAA,EACpCf,EAAakB,EAAY,UAAU,mBAAmB,GAAG,mBAAmB,WAIlF,IAAIC,EAAY,CAAC,GAD0BD,EAAY,QAAQ,oBAAsB,CAAA,CACpD,EAGjC,MAAME,EAAkBH,EAAc,IAAKI,GAAiB,CACxD,GAAI,OAAOA,EAAQ,EAAE,IAAM,OAAOV,CAAS,EAAG,CAE1C,MAAMW,EAAoBD,EAAQT,CAAK,EAOvC,GAJgB,OAAOE,GAAU,UAAYA,IAAU,KACjD,KAAK,UAAUQ,CAAiB,IAAM,KAAK,UAAUR,CAAK,EAC1D,OAAOQ,CAAiB,IAAM,OAAOR,CAAK,EAG5CR,OAAAA,EAAO,IAAI,8CAA8CK,CAAS,IAAIC,CAAK,YAAY,EAChFS,EAGX,IAAIE,EAAaT,EACjB,MAAMU,EAAe,WAAWV,CAAK,GAAK,EAG1C,GAAIF,IAAU,gBAAiB,CAC3B,MAAMa,EAAY,WAAWJ,EAAQ,UAAU,GAAK,EAChDI,EAAY,GAAKD,EAAeC,GAEZN,EAAU,KACzBO,IAAMA,GAAE,YAAc,OAAOf,CAAS,GAAKe,GAAE,QAAUd,CAAA,GAGxDO,EAAU,KAAK,CACX,UAAW,OAAOR,CAAS,EAC3B,MAAO,gBACP,QAAS,sBAAA,CACZ,EAGLY,EAAaE,GAGbN,EAAYA,EAAU,OACjBO,GAAM,EAAEA,EAAE,YAAc,OAAOf,CAAS,GAAKe,EAAE,QAAUd,EAAA,CAGtE,MAEIO,EAAYA,EAAU,OACjBO,GAAM,EAAEA,EAAE,YAAc,OAAOf,CAAS,GAAKe,EAAE,QAAUd,EAAA,EAIlE,MAAMe,EAAsB,CACxB,GAAGN,EACH,CAACT,CAAK,EAAGW,EACT,UAAW,EAAA,EAIf,OAAIX,IAAU,iBAAmBZ,GAAc,MAAM,QAAQA,CAAU,IACnE2B,EAAe,6BAA+B/B,GAC1C,CAAE,UAAW2B,EAAY,WAAAvB,EAE7B,EACAM,EAAO,IAAI,8EAA8EK,CAAS,EAAE,GAGjGgB,CACX,CACA,OAAON,CACX,CAAC,EAIDN,EAAO,WAAW,IAAIF,EAAc,CAChC,GAAGG,EACH,KAAM,CACF,GAAGA,EAAY,KACf,KAAMI,CAAA,CACV,CACH,EAID,MAAMQ,EAAeb,EAAO,MAAM,SAAA,GAAc,CAAA,EAC1Cc,EAAqB,IAAI,IAAID,EAAa,oBAAsB,CAAA,CAAE,EACxEC,EAAmB,IAAIlB,CAAS,EAGhC,MAAMmB,EAAkD,CAAE,GAAIF,EAAa,uBAAyB,CAAA,CAAC,EAC/FG,EAAa,OAAOpB,CAAS,EAC7BqB,EAAiB,IAAI,IAAIF,EAAsBC,CAAU,GAAK,CAAA,CAAE,EACtEC,EAAe,IAAIpB,CAAK,EACxBkB,EAAsBC,CAAU,EAAI,MAAM,KAAKC,CAAc,EAE7DjB,EAAO,MAAM,SAAS,CAClB,mBAAoB,MAAM,KAAKc,CAAkB,EACjD,sBAAAC,EACA,mBAAoBX,CAAA,CACvB,EAEDb,EAAO,IAAI,wCAAwCK,CAAS,IAAIC,CAAK,KAAME,CAAK,CACpF,CCjMA,MAAMR,EAAW,OAAe,QAAQ,eAAe,mBAAmB,GAAM,CAC5E,IAAK,IAAIC,IAAoB,QAAQ,IAAI,sBAAuB,GAAGA,CAAI,EACvE,KAAM,IAAIA,IAAoB,QAAQ,KAAK,sBAAuB,GAAGA,CAAI,EACzE,MAAO,IAAIA,IAAoB,QAAQ,MAAM,sBAAuB,GAAGA,CAAI,CAC/E,EAoCO,SAAS0B,GACZxB,EACAC,EACI,CAEJ,MAAMb,EAAUY,EAAO,QAAU,CAAA,EAC3B,CAAE,UAAAE,EAAW,SAAAuB,EAAU,MAAAtB,EAAO,MAAAE,EAAO,aAAAD,EAAe,YAAehB,EAEzE,GAAI,CAACc,GAAa,CAACuB,GAAY,CAACtB,EAAO,CACnCN,EAAO,KAAK,+CAAgD,CAAE,UAAAK,EAAW,SAAAuB,EAAU,MAAAtB,EAAO,EAC1F,MACJ,CAEA,MAAMG,EAAU,OAAe,OAC/B,GAAI,CAACA,GAAQ,YAAY,KAAO,CAACA,GAAQ,YAAY,IAAK,CACtDT,EAAO,KAAK,4DAA4D,EACxE,MACJ,CAGA,MAAMU,EAAcD,EAAO,WAAW,IAAIF,CAAY,EACtD,GAAI,CAACG,EAAa,CACd,QAAQ,KAAK,mCAAmCH,CAAY,aAAa,EACzE,MACJ,CAGA,MAAMI,EAAgBD,EAAY,MAAM,MAAQ,CAAA,EAChD,GAAI,CAAC,MAAM,QAAQC,CAAa,GAAKA,EAAc,SAAW,EAAG,CAC7DX,EAAO,KAAK,uDAAuD,EACnE,MACJ,CAGA,MAAMY,EAAcH,EAAO,MAAM,IAAA,GAAS,CAAA,EACpCf,EAAakB,EAAY,UAAU,mBAAmB,GAAG,mBAAmB,WAIlF,IAAIC,EAAY,CAAC,GADyBD,EAAY,QAAQ,mBAAqB,CAAA,CAClD,EAI7BiB,EAA4B,GAGhC,MAAMf,EAAkBH,EAAc,IAAKI,GAAiB,CACxD,GAAI,OAAOA,EAAQ,EAAE,IAAM,OAAOV,CAAS,EAAG,CAC1C,MAAMyB,EAAUf,EAAQ,SAAW,CAAA,EAG7BgB,EAAcD,EAAQ,UACvBE,GAAW,OAAOA,EAAE,EAAE,IAAM,OAAOJ,CAAQ,CAAA,EAGhD,GAAIG,IAAgB,GAChB/B,OAAAA,EAAO,KAAK,wCAAyC4B,CAAQ,EACtDb,EAGX,MAAMkB,EAAgBH,EAAQC,CAAW,EAGnCf,EAAoBiB,EAAc3B,CAAK,EAO7C,GAJgB,OAAOE,GAAU,UAAYA,IAAU,KACjD,KAAK,UAAUQ,CAAiB,IAAM,KAAK,UAAUR,CAAK,EAC1D,OAAOQ,CAAiB,IAAM,OAAOR,CAAK,EAG5CR,OAAAA,EAAO,IAAI,4CAA4C4B,CAAQ,IAAItB,CAAK,YAAY,EAC7ES,EAGX,IAAIE,GAAaT,EACjB,MAAMU,GAAe,WAAWV,CAAK,GAAK,EAG1C,GAAIF,IAAU,gBAAiB,CAC3B,MAAMa,EAAY,WAAWc,EAAc,UAAU,GAAK,EACtDd,EAAY,GAAKD,GAAeC,GAEZN,EAAU,KACzBO,IACGA,GAAE,YAAc,OAAOf,CAAS,GAChCe,GAAE,WAAa,OAAOQ,CAAQ,GAC9BR,GAAE,QAAUd,CAAA,GAGhBO,EAAU,KAAK,CACX,UAAW,OAAOR,CAAS,EAC3B,SAAU,OAAOuB,CAAQ,EACzB,MAAO,gBACP,QAAS,sBAAA,CACZ,EAGLX,GAAaE,GAGbN,EAAYA,EAAU,OACjBO,GACG,EACIA,EAAE,YAAc,OAAOf,CAAS,GAChCe,EAAE,WAAa,OAAOQ,CAAQ,GAC9BR,EAAE,QAAUd,EAAA,CAIhC,MAEIO,EAAYA,EAAU,OACjBO,GACG,EACIA,EAAE,YAAc,OAAOf,CAAS,GAChCe,EAAE,WAAa,OAAOQ,CAAQ,GAC9BR,EAAE,QAAUd,EAAA,EAM5B,MAAM4B,EAAiB,CAAC,GAAGJ,CAAO,EAC5BK,GAAqB,CACvB,GAAGD,EAAeH,CAAW,EAC7B,CAACzB,CAAK,EAAGW,GACT,UAAW,EAAA,EAMf,IAAImB,GAAyC,KAG7C,GAAI9B,IAAU,gBAAiB,CAC3B,MAAM+B,EAAkB,WAAWpB,EAAU,GAAK,EAElD,GAAIgB,EAAc,aAAe,GAE7BG,GAA0BC,EAC1BF,GAAc,iBAAmB,MAC9B,CAEH,MAAMG,EAAsB,WAAWvB,EAAQ,aAAa,GAAK,EACjEoB,GAAc,iBAAmBE,EAAkBC,CACvD,CACAH,GAAc,4BACTA,GAAc,kBAAoB,EAAI,IAAM,IAC7CA,GAAc,iBAAiB,eAAA,EAAmB,GAC1D,CAeA,GAZI7B,IAAU,iBAAmBZ,GAAc,MAAM,QAAQA,CAAU,IACnEyC,GAAc,6BAA+B7C,GACzC,CAAE,UAAW2B,GAAY,WAAAvB,EAE7B,EACAM,EAAO,IAAI,4EAA4E4B,CAAQ,EAAE,GAGrGM,EAAeH,CAAW,EAAII,GAI1BC,KAA4B,KAC5B,QAASG,EAAI,EAAGA,EAAIL,EAAe,OAAQK,IAAK,CAC5C,GAAIA,IAAMR,EAAa,SACvB,MAAMS,EAAUN,EAAeK,CAAC,EAC1BE,GAAM,WAAWD,EAAQ,gBAAgB,GAAK,EAC9CE,GAAsBN,GAA0BK,GAChDE,GAAsB,CAAE,GAAGH,EAAS,cAAeE,EAAA,EACrDhD,GAAc,MAAM,QAAQA,CAAU,IACtCiD,GAAe,6BAA+BrD,GAC1C,CAAE,UAAWoD,GAAqB,WAAAhD,EAEtC,GAEJwC,EAAeK,CAAC,EAAII,EACxB,CAIJ,IAAIC,GAAuB7B,EAAQ,eAC/B8B,GAAwB9B,EAAQ,iBAChCT,IAAU,mBAEVuC,GAAwBX,EAAe,OAAO,CAACY,EAAaC,IACpDA,EAAI,YAAc,GAAcD,EAC7BA,GAAO,SAASC,EAAI,eAAgB,EAAE,GAAK,GACnD,CAAC,EACJH,GAAuBC,GACvB7C,EAAO,IAAI,4DAA4D4C,EAAoB,EAAE,GAGjG,MAAMvB,GAAsB,CACxB,GAAGN,EACH,QAASmB,EACT,eAAgBU,GAChB,iBAAkBC,GAClB,UAAW,EAAA,EAIf,OAAIT,KAA4B,OAC5Bf,GAAe,cAAgBe,GAC3B1C,GAAc,MAAM,QAAQA,CAAU,IACtC2B,GAAe,6BAA+B/B,GAC1C,CAAE,UAAW8C,GAAyB,WAAA1C,EAE1C,GAEJmC,EAA4B,GAC5B7B,EAAO,IAAI,8CAA8CK,CAAS,qBAAqB+B,EAAuB,sBAAsB,GAGjIf,EACX,CACA,OAAON,CACX,CAAC,EAIDN,EAAO,WAAW,IAAIF,EAAc,CAChC,GAAGG,EACH,KAAM,CACF,GAAGA,EAAY,KACf,KAAMI,CAAA,CACV,CACH,EAID,MAAMQ,EAAeb,EAAO,MAAM,SAAA,GAAc,CAAA,EAC1CuC,EAAoB,IAAI,IAAI1B,EAAa,mBAAqB,CAAA,CAAE,EAChE2B,EAAY,GAAG5C,CAAS,IAAIuB,CAAQ,GAC1CoB,EAAkB,IAAIC,CAAS,EAG/B,MAAMC,EAAiD,CAAE,GAAI5B,EAAa,sBAAwB,CAAA,CAAC,EAC7FI,EAAiB,IAAI,IAAIwB,EAAqBD,CAAS,GAAK,CAAA,CAAE,EACpEvB,EAAe,IAAIpB,CAAK,EACxB4C,EAAqBD,CAAS,EAAI,MAAM,KAAKvB,CAAc,EAE3D,MAAMyB,EAAuC,CACzC,kBAAmB,MAAM,KAAKH,CAAiB,EAC/C,qBAAAE,EACA,kBAAmBrC,CAAA,EAKvB,GAAIgB,EAA2B,CAC3B,MAAMN,EAAqB,IAAI,IAAID,EAAa,oBAAsB,CAAA,CAAE,EACxEC,EAAmB,IAAIlB,CAAS,EAChC,MAAMmB,EAAkD,CAAE,GAAIF,EAAa,uBAAyB,CAAA,CAAC,EAC/FG,EAAa,OAAOpB,CAAS,EAC7B+C,EAAgB,IAAI,IAAI5B,EAAsBC,CAAU,GAAK,CAAA,CAAE,EACrE2B,EAAc,IAAI,eAAe,EACjC5B,EAAsBC,CAAU,EAAI,MAAM,KAAK2B,CAAa,EAC5DD,EAAgB,mBAAqB,MAAM,KAAK5B,CAAkB,EAClE4B,EAAgB,sBAAwB3B,CAC5C,CAEAf,EAAO,MAAM,SAAS0C,CAAe,EAErCnD,EAAO,IAAI,sCAAsCK,CAAS,IAAIuB,CAAQ,IAAItB,CAAK,KAAME,CAAK,CAC9F,CCrSA,MAAM6C,GAAkC,wBAClCC,GAAsB,YAGtBtD,GAAW,OAAe,QAAQ,eAAe,+BAA+B,GAAM,CAC1F,IAAK,IAAIC,IAAoB,QAAQ,IAAI,kCAAmC,GAAGA,CAAI,EACnF,KAAM,IAAIA,IAAoB,QAAQ,KAAK,kCAAmC,GAAGA,CAAI,EACrF,MAAO,IAAIA,IAAoB,QAAQ,MAAM,kCAAmC,GAAGA,CAAI,CACzF,EAOA,SAASsD,GAAeC,EAAoC,CAC1D,MAAM/C,EAAU,OAAe,OAC3BA,GAAQ,OAAO,IACjBA,EAAO,MAAM,IAAI+C,CAAO,EAExBxD,GAAO,KAAK,gCAAgC,CAEhD,CAUA,SAASyD,IAA+B,CACtC,GAAI,CACF,OAAO,aAAa,QAAQH,EAAmB,CACjD,MAAQ,CACN,OAAO,IACT,CACF,CAOA,SAASI,IAAuB,CAC9B,GAAI,CAGF,MAAMC,GAFU,OAAe,QACT,OAAO,MAAA,GAAW,CAAA,IACtB,UAAU,mBAAmB,GAAG,kBAClD,OAAO,MAAM,QAAQA,GAAI,UAAU,EAAIA,EAAG,WAAa,CAAA,CACzD,MAAQ,CACN,MAAO,CAAA,CACT,CACF,CAaA,SAASC,GAAwBC,EAAsC,CACrE,GAAI,CAACA,EACH,OAAO,KAGT,MAAMC,EAAaD,EAAO,YAAA,EAAc,MAAM,GAAG,EAAE,CAAC,EAAE,MAAM,GAAG,EAAE,CAAC,EAClE,GAAI,CAACC,EACH,OAAO,KAIT,MAAMC,EADaL,GAAA,EACQ,OAAQzE,IACjB,MAAM,QAAQA,GAAG,OAAO,EAAIA,EAAE,QAAU,CAAA,GACzC,KAAM+E,GAAe,OAAOA,CAAC,EAAE,YAAA,IAAkBF,CAAU,CAC3E,EAGD,OAAIC,EAAQ,SAAW,EACdA,EAAQ,CAAC,GAAG,MAAQ,KAGzBA,EAAQ,OAAS,EACPA,EAAQ,KAAM9E,GAAMA,GAAG,aAAe,EAAI,GAC1C,MAAQ,KAGf,IACT,CAOA,SAASgF,IAAsB,CAC7B,GAAI,CACF,MAAMC,EAAY,OAAe,SACjC,OAAIA,GAAU,KACLA,EAAS,MAEF,OAAe,QACH,OAAO,MAAA,GAAW,CAAA,GAC3B,aAAe,IACpC,MAAQ,CACN,OAAO,IACT,CACF,CASO,SAASC,GACdhE,EACAX,EACM,CACN,MAAMG,EAAkBQ,GAAQ,QAAQ,iBAAmB,MACrDiE,EAAWlF,GACf,OAAOA,GAAS,UAAY,aAAa,KAAKA,CAAI,EAG9CmF,EAAOJ,GAAA,EACPK,EAAYnE,GAAQ,QAAQ,iBAAmBkE,GAAM,6BAG3D,IAAIE,EAAwB,KAC5B,GAAI,CACFA,EAAS,aAAa,QAAQlB,EAA+B,CAC/D,MAAQ,CAER,CAIA,MAAMmB,EAAcF,EAAY,KAAOV,GAAwBH,IAAe,EAExEgB,EAAWL,EAAQE,CAAS,EAC9BA,EACAF,EAAQG,CAAM,EACZA,EACAH,EAAQI,CAAW,EACjBA,EACAJ,EAAQzE,CAAe,EACrBA,EACA,MAEV4D,GAAe,CAAE,kBAAmBkB,EAAU,EAC9CzE,GAAO,IAAI,kCAAmCyE,CAAQ,CACxD,CCpKA,MAAMC,GAA0C,gCAG1C1E,GAAW,OAAe,QAAQ,eAAe,sCAAsC,GAAM,CACjG,IAAK,IAAIC,IAAoB,QAAQ,IAAI,yCAA0C,GAAGA,CAAI,EAC1F,KAAM,IAAIA,IAAoB,QAAQ,KAAK,yCAA0C,GAAGA,CAAI,EAC5F,MAAO,IAAIA,IAAoB,QAAQ,MAAM,yCAA0C,GAAGA,CAAI,CAChG,EAOA,SAASsD,GAAeC,EAAoC,CAC1D,MAAM/C,EAAU,OAAe,OAC3BA,GAAQ,OAAO,IACjBA,EAAO,MAAM,IAAI+C,CAAO,EAExBxD,GAAO,KAAK,gCAAgC,CAEhD,CAOA,SAASiE,IAAsB,CAC7B,GAAI,CACF,MAAMC,EAAY,OAAe,SACjC,OAAIA,GAAU,KACLA,EAAS,MAEF,OAAe,QACH,OAAO,MAAA,GAAW,CAAA,GAC3B,aAAe,IACpC,MAAQ,CACN,OAAO,IACT,CACF,CASO,SAASS,GACdxE,EACAX,EACM,CACN,MAAMoF,EAAiBzE,GAAQ,QAAQ,gBAAkB,KACnDiE,EAAWlF,GACf,OAAOA,GAAS,UAAY,aAAa,KAAKA,CAAI,EAG9CmF,EAAOJ,GAAA,EACPK,EAAYnE,GAAQ,QAAQ,gBAAkBkE,GAAM,qCAG1D,IAAIE,EAAwB,KAC5B,GAAI,CACFA,EAAS,aAAa,QAAQG,EAAuC,CACvE,MAAQ,CAER,CAEA,MAAMD,EAAWL,EAAQE,CAAS,EAC9BA,EACAF,EAAQG,CAAM,EACZA,EACAH,EAAQQ,CAAc,EACpBA,EACA,KAERrB,GAAe,CAAE,yBAA0BkB,EAAU,EACrDzE,GAAO,IAAI,0CAA2CyE,CAAQ,CAChE,CCrEA,SAASI,EAAWC,EAAoB,CACpC,MAAMC,EAAOD,EAAK,YAAA,EACZE,EAAQ,OAAOF,EAAK,SAAA,EAAa,CAAC,EAAE,SAAS,EAAG,GAAG,EACnDG,EAAM,OAAOH,EAAK,QAAA,CAAS,EAAE,SAAS,EAAG,GAAG,EAClD,MAAO,GAAGC,CAAI,IAAIC,CAAK,IAAIC,CAAG,EAClC,CAQA,SAASC,GAAmBC,EAA4D,CACpF,MAAMC,MAAY,KACZC,EAAUR,EAAWO,CAAK,EAEhC,IAAIE,EAEJ,OAAQH,EAAA,CACJ,IAAK,QACDG,EAAYD,EACZ,MAEJ,IAAK,QAAS,CACV,MAAME,EAAQ,IAAI,KAAKH,CAAK,EAC5BG,EAAM,QAAQA,EAAM,QAAA,EAAY,CAAC,EACjCD,EAAYT,EAAWU,CAAK,EAC5B,KACJ,CAEA,IAAK,OAAQ,CACT,MAAMA,EAAQ,IAAI,KAAKH,CAAK,EAC5BG,EAAM,QAAQA,EAAM,QAAA,EAAY,CAAC,EACjCD,EAAYT,EAAWU,CAAK,EAC5B,KACJ,CAEA,IAAK,QAAS,CACV,MAAMA,EAAQ,IAAI,KAAKH,CAAK,EAC5BG,EAAM,SAASA,EAAM,SAAA,EAAa,CAAC,EACnCD,EAAYT,EAAWU,CAAK,EAC5B,KACJ,CAEA,IAAK,UAAW,CACZ,MAAMA,EAAQ,IAAI,KAAKH,CAAK,EAC5BG,EAAM,SAASA,EAAM,SAAA,EAAa,CAAC,EACnCD,EAAYT,EAAWU,CAAK,EAC5B,KACJ,CAEA,IAAK,UAAW,CACZ,MAAMA,EAAQ,IAAI,KAAKH,CAAK,EAC5BG,EAAM,SAASA,EAAM,SAAA,EAAa,CAAC,EACnCD,EAAYT,EAAWU,CAAK,EAC5B,KACJ,CAEA,IAAK,QAAS,CACV,MAAMA,EAAQ,IAAI,KAAKH,CAAK,EAC5BG,EAAM,YAAYA,EAAM,YAAA,EAAgB,CAAC,EACzCD,EAAYT,EAAWU,CAAK,EAC5B,KACJ,CAEA,IAAK,MACL,QACI,OAAAD,EAAY,GACL,CAAE,UAAW,GAAI,QAAS,EAAA,CAAG,CAG5C,MAAO,CAAE,UAAAA,EAAW,QAAAD,CAAA,CACxB,CAkCO,SAASG,GACZrF,EACkB,CAClB,KAAM,CAAE,OAAAgF,CAAA,EAAWhF,EAAO,QAAU,CAAE,OAAQ,OAAA,EAGxC,CAAE,UAAAmF,EAAW,QAAAD,GAAYH,GAAmBC,CAAM,EAGxD,MAAO,CACH,UAAAG,EACA,QAAAD,EACA,OAAAF,CAAA,CAER,CC5IA,MAAMnF,GAAW,OAAe,QAAQ,eAAe,oBAAoB,GAAM,CAC7E,IAAK,IAAIC,IAAoB,QAAQ,IAAI,uBAAwB,GAAGA,CAAI,EACxE,KAAM,IAAIA,IAAoB,QAAQ,KAAK,uBAAwB,GAAGA,CAAI,EAC1E,MAAO,IAAIA,IAAoB,QAAQ,MAAM,uBAAwB,GAAGA,CAAI,CAChF,EA4BO,SAASwF,GACZtF,EACAC,EACI,CACJ,MAAMb,EAAUY,EAAO,QAAU,CAAA,EAC3B,CAAE,UAAAE,EAAW,SAAAuB,EAAU,aAAArB,EAAe,YAAehB,EAE3D,GAAI,CAACc,GAAa,CAACuB,EAAU,CACzB5B,GAAO,KAAK,8CAA+C,CAAE,UAAAK,EAAW,SAAAuB,EAAU,EAClF,MACJ,CAEA,MAAMnB,EAAU,OAAe,OAC/B,GAAI,CAACA,GAAQ,YAAY,KAAO,CAACA,GAAQ,YAAY,IAAK,CACtDT,GAAO,KAAK,2DAA2D,EACvE,MACJ,CAGA,MAAMU,EAAcD,EAAO,WAAW,IAAIF,CAAY,EACtD,GAAI,CAACG,EAAa,CACd,QAAQ,KAAK,kCAAkCH,CAAY,aAAa,EACxE,MACJ,CAGA,MAAMI,EAAgBD,EAAY,MAAM,MAAQ,CAAA,EAChD,GAAI,CAAC,MAAM,QAAQC,CAAa,GAAKA,EAAc,SAAW,EAAG,CAC7DX,GAAO,KAAK,sDAAsD,EAClE,MACJ,CAIA,MAAMN,GADce,EAAO,MAAM,IAAA,GAAS,CAAA,GACX,UAAU,mBAAmB,GAAG,mBAAmB,WAG5EK,EAAkBH,EAAc,IAAKI,GAAiB,CACxD,GAAI,OAAOA,EAAQ,EAAE,IAAM,OAAOV,CAAS,EACvC,OAAOU,EAGX,MAAMe,EAAUf,EAAQ,SAAW,CAAA,EACnC,GAAIe,EAAQ,SAAW,EACnB,OAAOf,EAIX,MAAM2E,EAAiB5D,EAAQ,KAC1BE,GAAW,OAAOA,EAAE,EAAE,IAAM,OAAOJ,CAAQ,CAAA,EAGhD,GAAI,CAAC8D,EACD1F,OAAAA,GAAO,KAAK,uCAAwC4B,CAAQ,EACrDb,EAIX,MAAMmB,EAAiBJ,EAAQ,IAAK6D,GAAgB,CAChD,MAAMC,EAAa,OAAOD,EAAO,EAAE,IAAM,OAAO/D,CAAQ,EACxD,MAAO,CACH,GAAG+D,EACH,WAAYC,EACZ,UAAWA,EAAa,GAAOD,EAAO,SAAA,CAE9C,CAAC,EAGKE,EAAeH,EAAe,WAC9BrD,EAAkBqD,EAAe,cAGvC,IAAII,EAA4B/E,EAAQ,6BACxC,OAAIrB,GAAc,MAAM,QAAQA,CAAU,IACtCoG,EAA4BxG,GACxB,CAAE,UAAW+C,EAAiB,WAAA3C,EAElC,GAGJM,GAAO,IACH,iCAAiC4B,CAAQ,2BAA2BvB,CAAS,gCAC/CwF,CAAY,mBAAmBxD,CAAe,EAAA,EAGzE,CACH,GAAGtB,EACH,QAASmB,EACT,WAAY2D,EACZ,cAAexD,EACf,6BAA8ByD,EAC9B,UAAW,EAAA,CAEnB,CAAC,EAGDrF,EAAO,WAAW,IAAIF,EAAc,CAChC,GAAGG,EACH,KAAM,CACF,GAAGA,EAAY,KACf,KAAMI,CAAA,CACV,CACH,EAGD,MAAMQ,EAAeb,EAAO,MAAM,SAAA,GAAc,CAAA,EAC1Cc,EAAqB,IAAI,IAAID,EAAa,oBAAsB,CAAA,CAAE,EACxEC,EAAmB,IAAI,OAAOlB,CAAS,CAAC,EAExC,MAAM2C,EAAoB,IAAI,IAAI1B,EAAa,mBAAqB,CAAA,CAAE,EACtE0B,EAAkB,IAAI,GAAG3C,CAAS,IAAIuB,CAAQ,EAAE,EAEhDnB,EAAO,MAAM,SAAS,CAClB,mBAAoB,MAAM,KAAKc,CAAkB,EACjD,kBAAmB,MAAM,KAAKyB,CAAiB,CAAA,CAClD,EAEDhD,GAAO,IAAI,6BAA6B4B,CAAQ,+BAA+BvB,CAAS,EAAE,CAC9F,CCvJA,MAAML,GAAW,OAAe,QAAQ,eAAe,mBAAmB,GAAM,CAC5E,IAAK,IAAIC,IAAoB,QAAQ,IAAI,sBAAuB,GAAGA,CAAI,EACvE,KAAM,IAAIA,IAAoB,QAAQ,KAAK,sBAAuB,GAAGA,CAAI,EACzE,MAAO,IAAIA,IAAoB,QAAQ,MAAM,sBAAuB,GAAGA,CAAI,CAC/E,EAqCM8F,GAAgB,CAAC1F,EAA4BuB,IACxC,GAAGvB,CAAS,IAAIuB,CAAQ,GAS7BoE,GAAuB/C,GAClBA,EAAU,MAAM,GAAG,EAAE,CAAC,EAkC1B,SAASgD,GACZ9F,EACAX,EACI,CACJ,MAAMD,EAASY,EAAO,QAAW,CAAA,EAC3B,CAAE,UAAAE,EAAW,SAAAuB,EAAU,QAAAsE,CAAA,EAAY3G,EACnCkB,EAAU,OAAe,OAE/B,GAAI,CAACA,GAAQ,MAAO,CAChBT,GAAO,KAAK,0CAA0C,EACtD,MACJ,CAIA,MAAMsB,EAAeb,EAAO,MAAM,SAAA,EAC5B0F,EAAmC7E,EAAa,mBAAqB,CAAA,EACrE8E,EAAiC9E,EAAa,eAAiB,CAAA,EAC/D2B,EAAY8C,GAAc1F,EAAWuB,CAAQ,EAEnD,IAAIyE,EACAH,EAEKC,EAAuB,SAASlD,CAAS,EAG1CoD,EAAqBF,EAFrBE,EAAqB,CAAC,GAAGF,EAAwBlD,CAAS,EAM9DoD,EAAqBF,EAAuB,OAAQG,GAAQA,IAAQrD,CAAS,EAQjF,MAAMsD,GAHe9F,EAAO,YAAY,IAAI,UAAU,GACvB,MAAM,MAAQ,CAAA,GACpB,KAAM,GAAW,OAAO,EAAE,EAAE,IAAM,OAAOJ,CAAS,CAAC,GAC5C,SAAW,CAAA,EAE3C,IAAImG,EAAmB,CAAC,GAAGJ,CAAoB,EAC/C,MAAMK,EAAmB,OAAOpG,CAAS,EAErCkG,EAAe,OAAS,IAEFA,EAAe,IAAKxD,GAAagD,GAAc1F,EAAW0C,EAAI,EAAE,CAAC,EAEnD,OAAQuD,GAAgBD,EAAmB,SAASC,CAAG,CAAC,EAAE,SAExEC,EAAe,OAE5BC,EAAiB,SAASC,CAAgB,GAC3CD,EAAiB,KAAKC,CAAgB,EAI1CD,EAAmBA,EAAiB,OAAQE,GAAOA,IAAOD,CAAgB,GAMlFhG,EAAO,MAAM,SAAS,CAClB,kBAAmB4F,EACnB,cAAeG,CAAA,CAClB,CACL,CA6BO,SAASG,GACZxG,EACAX,EACI,CACJ,MAAMD,EAASY,EAAO,QAAW,CAAA,EAC3B,CAAE,UAAAE,EAAW,QAAAyB,EAAS,QAAAoE,CAAA,EAAY3G,EAClCkB,EAAU,OAAe,OAE/B,GAAI,CAACA,GAAQ,MAAO,CAChBT,GAAO,KAAK,kDAAkD,EAC9D,MACJ,CAGA,MAAMsB,EAAeb,EAAO,MAAM,SAAA,EAC5B0F,EAAmC7E,EAAa,mBAAqB,CAAA,EACrE8E,EAAiC9E,EAAa,eAAiB,CAAA,EAG/DsF,GAAqB9E,GAAW,CAAA,GAAI,IAAKiB,GAC3CgD,GAAc1F,EAAW0C,EAAI,EAAE,CAAA,EAGnC,IAAIsD,EACAG,EAAmB,CAAC,GAAGJ,CAAoB,EAC/C,MAAMK,EAAmB,OAAOpG,CAAS,EAErC6F,GAKAG,EAAqB,CAAC,GAHOF,EAAuB,OAC/CG,GAAQN,GAAoBM,CAAG,IAAM,OAAOjG,CAAS,CAAA,EAEX,GAAGuG,CAAiB,EAE9DJ,EAAiB,SAASC,CAAgB,GAC3CD,EAAiB,KAAKC,CAAgB,IAI1CJ,EAAqBF,EAAuB,OACvCG,GAAQN,GAAoBM,CAAG,IAAM,OAAOjG,CAAS,CAAA,EAG1DmG,EAAmBA,EAAiB,OAAQE,GAAOA,IAAOD,CAAgB,GAI9EhG,EAAO,MAAM,SAAS,CAClB,kBAAmB4F,EACnB,cAAeG,CAAA,CAClB,CACL,CAUO,SAASK,GACZ1G,EACAC,EACI,CAEJ,OAAOuG,GAA4BxG,CAAe,CACtD,CAoBO,SAAS2G,GACZ3G,EACAX,EACuC,CACvC,MAAMD,EAASY,EAAO,QAAU,CAAE,SAAU,CAAA,EAAI,kBAAmB,EAAC,EAC9D,CAAE,SAAA4G,EAAU,kBAAAC,CAAA,EAAsBzH,EAClC0H,EAAqBD,GAAqB,CAAA,EAEhD,GAAI,CAACD,GAAY,CAAC,MAAM,QAAQA,CAAQ,EACpC,MAAO,CAAA,EAGX,MAAMG,EAAkD,CAAA,EAExD,UAAWnG,KAAWgG,EAAU,CAC5B,MAAM1G,EAAY,OAAOU,EAAQ,EAAE,EAC7Be,EAAUf,EAAQ,SAAW,CAAA,EAEnC,GAAIe,EAAQ,SAAW,EAAG,CACtBoF,EAAO7G,CAAS,EAAI,OACpB,QACJ,CAMA,MAAM8G,EAJoBrF,EAAQ,IAAKiB,GACnCgD,GAAc1F,EAAW0C,EAAI,EAAE,CAAA,EAGK,OAAQuD,GAC5CW,EAAS,SAASX,CAAG,CAAA,EACvB,OAEEa,IAAkB,EAClBD,EAAO7G,CAAS,EAAI,OACb8G,IAAkBrF,EAAQ,OACjCoF,EAAO7G,CAAS,EAAI,MAEpB6G,EAAO7G,CAAS,EAAI,MAE5B,CAEA,OAAO6G,CACX,CAoBO,SAASE,GACZjH,EACAX,EACI,CACJ,MAAMD,EAASY,EAAO,QAAW,CAAA,EAC3B,CAAE,YAAAkH,GAAgB9H,EAClBkB,EAAU,OAAe,OAE/B,GAAI,CAACA,GAAQ,MAAO,CAChBT,GAAO,KAAK,kDAAkD,EAC9D,MACJ,CAGA,MAAMsB,EAAeb,EAAO,MAAM,SAAA,EAC5B6G,EAAgChG,EAAa,eAAiB,CAAA,EAC9D6E,EAAmC7E,EAAa,mBAAqB,CAAA,EAIrEyF,EADetG,EAAO,YAAY,IAAI,UAAU,GACvB,MAAM,MAAQ,CAAA,EAGvC8G,EAAgBF,EAAY,OAAQX,GAAe,CAACY,EAAoB,SAASZ,CAAE,CAAC,EAEpFc,EAAkBF,EAAoB,OAAQZ,GAAe,CAACW,EAAY,SAASX,CAAE,CAAC,EAE5F,IAAIL,EAAqB,CAAC,GAAGF,CAAsB,EAGnD,UAAW9F,KAAakH,EAAe,CACnC,MAAMxG,EAAUgG,EAAS,KAAMU,GAAW,OAAOA,EAAE,EAAE,IAAMpH,CAAS,EACpE,GAAIU,GAAS,QACT,UAAWgC,KAAOhC,EAAQ,QAAS,CAC/B,MAAMkC,EAAY8C,GAAc1F,EAAW0C,EAAI,EAAE,EAC5CsD,EAAmB,SAASpD,CAAS,GACtCoD,EAAmB,KAAKpD,CAAS,CAEzC,CAER,CAGA,UAAW5C,KAAamH,EACpBnB,EAAqBA,EAAmB,OACnCC,GAAQN,GAAoBM,CAAG,IAAM,OAAOjG,CAAS,CAAA,EAK9DI,EAAO,MAAM,SAAS,CAClB,cAAe4G,EACf,kBAAmBhB,CAAA,CACtB,CACL,CC/XA,MAAMrG,GAAW,OAAe,QAAQ,eAAe,eAAe,GAAM,CACxE,IAAK,IAAIC,IAAoB,QAAQ,IAAI,kBAAmB,GAAGA,CAAI,EACnE,KAAM,IAAIA,IAAoB,QAAQ,KAAK,kBAAmB,GAAGA,CAAI,EACrE,MAAO,IAAIA,IAAoB,QAAQ,MAAM,kBAAmB,GAAGA,CAAI,CAC3E,EAoBA,eAAsByH,GAClBvH,EACAC,EACa,CACb,MAAMK,EAAU,OAAe,OAC/B,GAAI,CAACA,GAAQ,OAAS,CAACA,GAAQ,IAAK,CAChCT,GAAO,KAAK,uDAAuD,EACnE,MACJ,CAEA,GAAI,CAEA,MAAM2H,EAAW,MAAMlH,EAAO,IAAI,KAC9B,6DAAA,EAGJ,GAAI,CAACkH,GAAU,MAAM,aACjB,MAAM,IAAI,MAAM,sBAAsB,EAG1C,MAAMC,EAAiBD,EAAS,KAAK,aAMrC,IAHsB,OAAe,wBAA0B,CAAA,GACzB,OAAS,GAEvBlH,EAAO,MAAM,eAEjCA,EAAO,MAAM,eAAe,CACxB,gCAAiCmH,CAAA,CACpC,EACD5H,GAAO,IAAI,iDAAiD4H,CAAc,EAAE,MACzE,CAEH,MAAMC,EAAapH,EAAO,MAAM,SAAA,GAAc,CAAA,EAC9CA,EAAO,MAAM,SAAS,CAClB,GAAI,CACA,GAAGoH,EAAW,GACd,YAAa,CACT,GAAIA,EAAW,IAAI,aAAe,CAAA,EAClC,eAAAD,CAAA,CACJ,CACJ,CACH,EACD5H,GAAO,IAAI,gDAAgD4H,CAAc,EAAE,CAC/E,CACJ,OAASE,EAAY,CACjB9H,GAAO,MAAM,oCAAqC8H,CAAK,EAGvDrH,EAAO,OAAO,QACVA,EAAO,IAAI,8DAA8D,GACtE,kCAAA,CAEX,CACJ,CC/EA,MAAMT,GAAW,OAAe,QAAQ,eAAe,kBAAkB,GAAM,CAC3E,IAAK,IAAIC,IAAoB,QAAQ,IAAI,qBAAsB,GAAGA,CAAI,EACtE,KAAM,IAAIA,IAAoB,QAAQ,KAAK,qBAAsB,GAAGA,CAAI,EACxE,MAAO,IAAIA,IAAoB,QAAQ,MAAM,qBAAsB,GAAGA,CAAI,CAC9E,EAwCA,eAAsB8H,GAClB5H,EACAC,EACa,CACb,MAAMK,EAAU,OAAe,OAC/B,GAAI,CAACA,GAAQ,MAAO,CAChBT,GAAO,KAAK,2CAA2C,EACvD,MACJ,CAEA,MAAM6H,EAAapH,EAAO,MAAM,SAAA,GAAc,CAAA,EAIxCuH,GADgBvH,EAAO,MAAM,YAAA,GAAiB,CAAA,GAClB,QAAU,CAAA,EAGtCwH,EACFD,EAAY,IAAI,aAChBH,EAAW,IAAI,aACf,CAAA,EAGEK,EACFL,EAAW,MAAM,cACjBG,EAAY,MAAM,cAClBA,EAAY,aAAa,mBAAmB,cAC5CH,EAAW,aAAa,mBAAmB,aAE/C,GAAI,CAACK,EAAqB,CACtBzH,EAAO,OAAO,QACVA,EAAO,IAAI,gEAAgE,GACxE,4CAAA,EAEP,MACJ,CAGAA,EAAO,OAAO,QAAA,EAGd,MAAM0H,EAAgC,CAClC,QAASD,EACT,YAAcD,EAAY,QAAU,GAAQ,IAAM,IAClD,aAAeA,EAAY,SAAW,GAAQ,IAAM,IACpD,gBAAkBA,EAAY,YAAc,GAAQ,IAAM,IAC1D,gBAAkBA,EAAY,YAAc,GAAQ,IAAM,IAC1D,iBAAmBA,EAAY,aAAe,GAAQ,IAAM,IAC5D,YAAcA,EAAY,QAAU,GAAQ,IAAM,IAClD,iBAAmBA,EAAY,aAAe,GAAQ,IAAM,IAC5D,gBAAkBA,EAAY,YAAc,GAAQ,IAAM,IAC1D,cAAgBA,EAAY,UAAY,GAAQ,IAAM,IACtD,SAAWA,EAAY,KAAO,GAAS,IAAM,IAC7C,oBAAsBA,EAAY,gBAAkB,GAAQ,IAAM,GAAA,EAIhEG,EAAY,oCADE,IAAI,gBAAgBD,CAAK,EAAE,SAAA,CACkB,GAEjEnI,GAAO,IAAI,+BAAgCoI,CAAS,EAGhD3H,EAAO,SACPA,EAAO,SAAS2H,CAAS,EAClBhI,EAAQ,SACfA,EAAQ,SAASgI,CAAS,EAE1B,OAAO,SAAS,KAAOA,CAE/B,CCjHA,MAAMpI,EAAW,OAAe,QAAQ,eAAe,eAAe,GAAM,CACxE,IAAK,IAAIC,IAAoB,QAAQ,IAAI,kBAAmB,GAAGA,CAAI,EACnE,KAAM,IAAIA,IAAoB,QAAQ,KAAK,kBAAmB,GAAGA,CAAI,EACrE,MAAO,IAAIA,IAAoB,QAAQ,MAAM,kBAAmB,GAAGA,CAAI,CAC3E,EAiCA,SAASoI,GACLC,EACAC,EACA1E,EACA2E,EAAiB,CAAA,EACJ,CACb,UAAWC,KAAOH,EAAY,CAC1B,MAAMI,EAAUD,EAAI,OAAO5E,CAAM,GAAK4E,EAAI,MAAM,IAAM,GAEtD,GAAIA,EAAI,KAAOF,EACX,MAAO,CAAC,GAAGC,EAAME,CAAO,EAAE,KAAK,KAAK,EAGxC,GAAID,EAAI,UAAYA,EAAI,SAAS,OAAS,EAAG,CACzC,MAAME,EAAQN,GAAwBI,EAAI,SAAUF,EAAU1E,EAAQ,CAAC,GAAG2E,EAAME,CAAO,CAAC,EACxF,GAAIC,EACA,OAAOA,CAEf,CACJ,CACA,OAAO,IACX,CAWA,SAASC,GACLC,EACAC,EACAjF,EACyC,CACzC,OAAOgF,EAAe,IAAIJ,GAAO,CAE7B,GAAIA,EAAI,WACJ,MAAO,CAAE,GAAIA,EAAI,GAAI,WAAYA,EAAI,UAAA,EAIzC,MAAMM,EAAaV,GAAwBS,EAAgBL,EAAI,GAAI5E,CAAM,EACzE,GAAIkF,EACA,MAAO,CAAE,GAAIN,EAAI,GAAI,WAAAM,CAAA,EAIzB,MAAML,EAAUD,EAAI,OAAO5E,CAAM,GAAK4E,EAAI,MAAM,IAAMA,EAAI,gBAAkB,GAC5E,MAAO,CAAE,GAAIA,EAAI,GAAI,WAAYC,CAAA,CACrC,CAAC,CACL,CAYO,SAASM,GACZ7I,EACAC,EACI,CACJ,MAAMb,EAASY,EAAO,QAAU,CAAA,EAC1B8I,EAAQ1J,EAAO,MACf2J,EAAW3J,EAAO,SAExB,GAAI0J,IAAU,QAAa,CAACC,EAAU,CAClClJ,EAAO,KAAK,kDAAkD,EAC9D,MACJ,CAGA,MAAMmJ,EAAmB/I,EAAgB,gBAEzC,GAAI+I,EAAiB,CACjB,MAAMC,EAAkBD,EAAgB,MAAM,oBAAsB,CAAC,KAAM,KAAM,KAAM,IAAI,EAG3F,GAAIC,EAAgBH,CAAK,GAAG,KAAOC,EAAS,GAAI,CAC5ClJ,EAAO,IAAI,4DAA4DiJ,CAAK,YAAY,EACxF,MACJ,CAGA,MAAMI,EAAcD,EAAgB,IAAI,CAACX,EAAsBa,IACvDA,EAAML,EAAcR,EACpBa,IAAQL,EAAcC,EACnB,IACV,EAEDC,EAAgB,WAAW,CAAE,mBAAoBE,CAAAA,CAAa,EAC9DrJ,EAAO,IAAI,+CAA+CiJ,CAAK,eAAgBC,EAAS,EAAE,EAC1F,MACJ,CAGA,MAAMzI,EAAU,OAAe,OAC/B,GAAI,CAACA,GAAQ,MAAO,CAChBT,EAAO,KAAK,oDAAoD,EAChE,MACJ,CAEA,MAAMuJ,EAAQ9I,EAAO,MAAM,SAAA,GAAc,CAAA,EACnC2I,EAAkBG,EAAM,IAAI,oBAAsB,CAAC,KAAM,KAAM,KAAM,IAAI,EAG/E,GAAIH,EAAgBH,CAAK,GAAG,KAAOC,EAAS,GAAI,CAC5ClJ,EAAO,IAAI,4DAA4DiJ,CAAK,YAAY,EACxF,MACJ,CAGA,MAAMI,EAAcD,EAAgB,IAAI,CAACX,EAAsBa,IACvDA,EAAML,EAAcR,EACpBa,IAAQL,EAAcC,EACnB,IACV,EAEDzI,EAAO,MAAM,SAAS,CAClB,GAAI,CACA,GAAG8I,EAAM,GACT,mBAAoBF,CAAA,CACxB,CACH,EAEDrJ,EAAO,IAAI,+CAA+CiJ,CAAK,IAAKC,EAAS,EAAE,CACnF,CAWO,SAASM,GACZrJ,EACAC,EACI,CACJ,MAAMb,EAASY,EAAO,QAAU,CAAA,EAC1B8I,EAAQ1J,EAAO,MACf2J,EAAW3J,EAAO,SAExB,GAAI0J,IAAU,QAAa,CAACC,EAAU,CAClClJ,EAAO,KAAK,wDAAwD,EACpE,MACJ,CAGA,MAAMmJ,EAAmB/I,EAAgB,gBAEzC,GAAI+I,EAAiB,CACjB,MAAMC,EAAkBD,EAAgB,MAAM,oBAAsB,CAAC,KAAM,KAAM,KAAM,IAAI,EACrFM,EAAcN,EAAgB,MAAM,cAAgB,EAGpDE,EAAcD,EAAgB,IAAI,CAACX,EAAsBa,IACvDA,EAAML,EAAcR,EACpBa,IAAQL,EAAcC,EACnB,IACV,EAIKQ,EADcR,EAAS,UAAYA,EAAS,SAAS,OAAS,EACrCD,EAAQ,EAAIQ,EAE3CN,EAAgB,WAAW,CACvB,mBAAoBE,EACpB,aAAcK,CAAA,CACjB,EAED1J,EAAO,IAAI,qDAAqDiJ,CAAK,eAAgBC,EAAS,GAAI,YAAaQ,CAAQ,EACvH,MACJ,CAGA,MAAMjJ,EAAU,OAAe,OAC/B,GAAI,CAACA,GAAQ,MAAO,CAChBT,EAAO,KAAK,0DAA0D,EACtE,MACJ,CAEA,MAAMuJ,EAAQ9I,EAAO,MAAM,SAAA,GAAc,CAAA,EAInC4I,GAHkBE,EAAM,IAAI,oBAAsB,CAAC,KAAM,KAAM,KAAM,IAAI,GAG3C,IAAI,CAACd,EAAsBa,IACvDA,EAAML,EAAcR,EACpBa,IAAQL,EAAcC,EACnB,IACV,EAIKQ,EADcR,EAAS,UAAYA,EAAS,SAAS,OAAS,EACrCD,EAAQ,EAAIM,EAAM,IAAI,aAErD9I,EAAO,MAAM,SAAS,CAClB,GAAI,CACA,GAAG8I,EAAM,GACT,mBAAoBF,EACpB,aAAcK,CAAA,CAClB,CACH,EAED1J,EAAO,IAAI,qDAAqDiJ,CAAK,IAAKC,EAAS,GAAI,YAAaQ,CAAQ,CAChH,CAcO,SAASC,GACZC,EACAxJ,EACI,CACJJ,EAAO,IAAI,gCAAgC,EAE3C,MAAMS,EAAU,OAAe,OAC/B,GAAI,CAACA,GAAQ,MAAO,CAChBT,EAAO,KAAK,4DAA4D,EACxE,MACJ,CAEA,MAAMuJ,EAAQ9I,EAAO,MAAM,SAAA,GAAc,CAAA,EACnCoD,EAASpD,EAAO,QAAQ,UAAA,GAAe,KAGvC0I,EAAmB/I,EAAgB,gBACnCyJ,EAAeV,EACdA,EAAgB,MAAM,oBAAsB,CAAA,EAC5CI,EAAM,IAAI,oBAAsB,CAAA,EAEvCvJ,EAAO,IAAI,0CAA2C,CAClD,aAAc6J,EAAa,IAAK5K,GAAuBA,GAAG,IAAM,IAAI,EACpE,mBAAoBsK,EAAM,MAAM,aAChC,6BAA8BA,EAAM,sBACpC,qBAAsB,CAAC,CAACJ,CAAA,CAC3B,EAGD,MAAMW,EAAeD,EAAa,OAAO,OAAO,EAAE,IAAA,EAElD,GAAI,CAACC,EAAc,CACf9J,EAAO,KAAK,+CAA+C,EAC3D,MACJ,CAEAA,EAAO,IAAI,mDAAoD8J,EAAa,GAAIA,EAAa,IAAI,EAEjG,MAAMC,EAAuBR,EAAM,MAAM,cAAgB,CAAA,EAGzD,GAAIQ,EAAW,QAAU,EAAG,CACxBtJ,EAAO,OAAO,UACVA,EAAO,IAAI,yDAAyD,GACjE,oCAAA,EAEP,MACJ,CAGA,GAAIsJ,EAAW,SAASD,EAAa,EAAE,EAAG,CACtC9J,EAAO,IAAI,+CAAgD8J,EAAa,GAAI,KAAMC,CAAU,EAC5FtJ,EAAO,OAAO,UACVA,EAAO,IAAI,oEAAoE,GAC5E,oCAAA,EAEP,MACJ,CAGA,MAAMsI,EAAac,EACd,OAAO,OAAO,EACd,IAAKpB,GAAkBA,EAAI,OAAO5E,CAAM,GAAK4E,EAAI,MAAM,IAAM,EAAE,EAC/D,KAAK,KAAK,EAETuB,EAAiB,CAAC,GAAGD,EAAYD,EAAa,EAAE,EAChDG,EAAoBV,EAAM,MAAM,qBAAuBO,EAAa,GAIpEhB,GADcrI,EAAO,MAAM,iBAAA,GAAsB,CAAA,GACpB,YAAY,MAAM,MAAQ,CAAA,EAI7D,IAAIyJ,EAA0DX,EAAM,uBAAyB,CAAA,EAC7F,MAAMV,EAAiBU,EAAM,MAAM,YAAc,CAAA,EAE7CW,EAAa,SAAW,GAAKrB,EAAe,OAAS,IACrD7I,EAAO,IAAI,8DAA+D6I,EAAe,OAAQ,OAAO,EACxGqB,EAAetB,GAAoCC,EAAgBC,EAAgBjF,CAAM,GAG7F,MAAMsG,EAAW,CAAC,GAAGD,EAAc,CAAE,GAAIJ,EAAa,GAAI,WAAAf,EAAY,EAEtE/I,EAAO,IAAI,4CAA6C,CACpD,eAAAgK,EACA,kBAAAC,EACA,SAAAE,CAAA,CACH,EAED,GAAI,CACA1J,EAAO,MAAM,SAAS,CAClB,KAAM,CACF,GAAG8I,EAAM,KACT,aAAcS,EACd,oBAAqBC,CAAA,EAEzB,sBAAuBE,EACvB,WAAY,EAAA,CACf,EACDnK,EAAO,IAAI,2CAA2C,CAC1D,OAAS8H,EAAO,CACZ9H,EAAO,MAAM,4CAA6C8H,CAAK,CACnE,CAGA,MAAMsC,EAAa3J,EAAO,MAAM,SAAA,GAAc,CAAA,EAC9CT,EAAO,IAAI,2CAA4C,CACnD,aAAcoK,EAAW,MAAM,aAC/B,sBAAuBA,EAAW,qBAAA,CACrC,EAEDpK,EAAO,IAAI,2CAA2C8J,EAAa,EAAE,qBAAqBf,CAAU,YAAYiB,EAAe,MAAM,EAAE,CAC3I,CAWO,SAASK,GACZlK,EACAX,EACI,CAEJ,MAAM8K,GADSnK,EAAO,QAAU,CAAA,GACN,WAE1B,GAAI,CAACmK,EAAY,CACbtK,EAAO,KAAK,wDAAwD,EACpE,MACJ,CAEA,MAAMS,EAAU,OAAe,OAC/B,GAAI,CAACA,GAAQ,MAAO,CAChBT,EAAO,KAAK,iEAAiE,EAC7E,MACJ,CAEA,MAAMuJ,EAAQ9I,EAAO,MAAM,SAAA,GAAc,CAAA,EAGnC8J,GAFuBhB,EAAM,MAAM,cAAgB,CAAA,GAE/B,OAAQ7C,GAAeA,IAAO4D,CAAU,EAGlE,IAAIE,EAAuBjB,EAAM,MAAM,oBACnCiB,IAAyBF,IACzBE,EAAuBD,EAAO,CAAC,GAAK,MAKxC,MAAMJ,GAD0DZ,EAAM,uBAAyB,CAAA,GACjE,OAAQkB,GAASA,EAAK,KAAOH,CAAU,EAErE7J,EAAO,MAAM,SAAS,CAClB,KAAM,CACF,GAAG8I,EAAM,KACT,aAAcgB,EACd,oBAAqBC,CAAA,EAEzB,sBAAuBL,EACvB,WAAY,EAAA,CACf,EAEDnK,EAAO,IAAI,kDAAkDsK,CAAU,gBAAgBC,EAAO,MAAM,EAAE,CAC1G,CASO,SAASG,GACZvK,EACAX,EACM,CACN,MAAMD,EAASY,EAAO,QAAU,CAAA,EAC1BmK,EAAa/K,EAAO,WACpB+I,EAAa/I,EAAO,WAE1B,GAAI,CAAC+K,GAAc,CAAChC,GAAc,CAAC,MAAM,QAAQA,CAAU,EACvD,MAAO,GAIX,MAAMzE,EADU,OAAe,QACR,QAAQ,UAAA,GAAe,KAKxC8G,EAAW,CACbC,EACArC,EACAC,EAAiB,CAAA,IACC,CAClB,UAAWC,KAAOmC,EAAM,CACpB,MAAMlC,EAAUD,EAAI,OAAO5E,CAAM,GAAK4E,EAAI,MAAM,IAAM,GAEtD,GAAIA,EAAI,KAAOF,EACX,MAAO,CAAC,GAAGC,EAAME,CAAO,EAG5B,GAAID,EAAI,UAAYA,EAAI,SAAS,OAAS,EAAG,CACzC,MAAME,EAAQgC,EAASlC,EAAI,SAAUF,EAAU,CAAC,GAAGC,EAAME,CAAO,CAAC,EACjE,GAAIC,EACA,OAAOA,CAEf,CACJ,CACA,OAAO,IACX,EAEMkC,EAAYF,EAASrC,EAAYgC,CAAU,EACjD,OAAOO,EAAYA,EAAU,KAAK,KAAK,EAAI,EAC/C,CAWO,SAASC,GACZlB,EACApK,EACI,CACJQ,EAAO,IAAI,sCAAsC,EAEjD,MAAMS,EAAU,OAAe,OAC/B,GAAI,CAACA,GAAQ,MAAO,CAChBT,EAAO,KAAK,kEAAkE,EAC9E,MACJ,CAEA,MAAMuJ,EAAQ9I,EAAO,MAAM,SAAA,GAAc,CAAA,EACnCoD,EAASpD,EAAO,QAAQ,UAAA,GAAe,KAIvCqI,GADcrI,EAAO,MAAM,iBAAA,GAAsB,CAAA,GACpB,YAAY,MAAM,MAAQ,CAAA,EAGvDsK,EAAoBxB,EAAM,MAAM,YAAc,CAAA,EAC9CyB,EAAczB,EAAM,MAAM,cAAgB,CAAA,EAMhD,GAJAvJ,EAAO,IAAI,qDAAsD+K,CAAiB,EAClF/K,EAAO,IAAI,+CAAgDgL,CAAW,EACtEhL,EAAO,IAAI,4DAA6D8I,EAAe,OAAS,CAAC,EAE7FkC,EAAY,SAAW,EAAG,CAC1BhL,EAAO,IAAI,4DAA4D,EACvE,MACJ,CAGA,MAAMiL,EAAiB,CACnBL,EACArC,EACAC,EAAiB,CAAA,IACC,CAClB,UAAWC,KAAOmC,EAAM,CACpB,MAAMlC,EAAUD,EAAI,OAAO5E,CAAM,GAAK4E,EAAI,MAAM,IAAM,GAEtD,GAAIA,EAAI,KAAOF,EACX,MAAO,CAAC,GAAGC,EAAME,CAAO,EAG5B,GAAID,EAAI,UAAYA,EAAI,SAAS,OAAS,EAAG,CACzC,MAAME,EAAQsC,EAAexC,EAAI,SAAUF,EAAU,CAAC,GAAGC,EAAME,CAAO,CAAC,EACvE,GAAIC,EACA,OAAOA,CAEf,CACJ,CACA,OAAO,IACX,EAGMuC,EAAmE,CAAA,EAEzE,UAAWZ,KAAcU,EAAa,CAElC,MAAMH,EAAYI,EAAenC,EAAgBwB,CAAU,EAE3D,GAAIO,EACAK,EAAsB,KAAK,CACvB,GAAIZ,EACJ,WAAYO,EAAU,KAAK,KAAK,CAAA,CACnC,MACE,CAEH,MAAMM,EAAaJ,EAAkB,KAAM9L,GAAWA,EAAE,KAAOqL,CAAU,EACzE,GAAIa,EAAY,CACZ,MAAMzC,EAAUyC,EAAW,OAAOtH,CAAM,GAAKsH,EAAW,MAAM,IAAMA,EAAW,gBAAkB,GACjGD,EAAsB,KAAK,CACvB,GAAIZ,EACJ,WAAY5B,CAAA,CACf,CACL,CACJ,CACJ,CAEA1I,EAAO,IAAI,kEAAmEkL,CAAqB,EAGnGzK,EAAO,MAAM,SAAS,CAClB,sBAAAyK,CAAA,CACH,EAEDlL,EAAO,IAAI,qCAAqC,CACpD,CASO,SAASoL,GACZjL,EACAX,EAC8E,CAC9E,MAAMK,EAAS,CACX,UAAW,GACX,UAAW,GACX,UAAW,GACX,UAAW,EAAA,EAGTN,EAASY,EAAO,QAAU,CAAA,EAC1BkL,EAAc9L,EAAO,YAAc,OAAOA,EAAO,WAAW,EAAI,GAChE+L,EAAc/L,EAAO,YAAc,OAAOA,EAAO,WAAW,EAAI,GAChEgM,EAAchM,EAAO,YAAc,OAAOA,EAAO,WAAW,EAAI,GAChEiM,EAAcjM,EAAO,YAAc,OAAOA,EAAO,WAAW,EAAI,GAGhE+I,EAAa/I,EAAO,WAC1B,GAAI,CAAC+I,GAAc,CAAC,MAAM,QAAQA,CAAU,GAAKA,EAAW,SAAW,EACnEtI,OAAAA,EAAO,KAAK,oDAAoD,EACzDH,EAIX,GAAIwL,EAAa,CACb,MAAMI,EAAOnD,EAAW,KAAMG,GAAQ,OAAOA,EAAI,EAAE,IAAM4C,CAAW,EACpE,GAAII,GAAQA,EAAK,QAAU,IACvB5L,EAAO,UAAYwL,EAGfC,GAAeG,EAAK,UAAU,CAC9B,MAAMC,EAAOD,EAAK,SAAS,KAAMhD,GAAQ,OAAOA,EAAI,EAAE,IAAM6C,CAAW,EACvE,GAAII,GAAQA,EAAK,QAAU,IACvB7L,EAAO,UAAYyL,EAGfC,GAAeG,EAAK,UAAU,CAC9B,MAAMC,EAAOD,EAAK,SAAS,KAAMjD,GAAQ,OAAOA,EAAI,EAAE,IAAM8C,CAAW,EACvE,GAAII,GAAQA,EAAK,QAAU,IACvB9L,EAAO,UAAY0L,EAGfC,GAAeG,EAAK,UAAU,CAC9B,MAAMC,EAAOD,EAAK,SAAS,KAAMlD,GAAQ,OAAOA,EAAI,EAAE,IAAM+C,CAAW,EACnEI,GAAQA,EAAK,QAAU,IACvB/L,EAAO,UAAY2L,EAE3B,CAER,CAER,CAER,CAEAxL,OAAAA,EAAO,IAAI,iCAAkCH,CAAM,EAC5CA,CACX,CCznBO,SAASgM,GACZ1L,EACAX,EACM,CACN,MAAMD,EAASY,EAAO,QAAU,CAAA,EAC1B2L,EAAUvM,EAAO,QACjBwM,EAASxM,EAAO,OAEtB,GAAI,CAACuM,GAAW,CAACC,GAAU,CAAC,MAAM,QAAQA,CAAM,EAC5C,MAAO,GAIX,MAAMlI,EADU,OAAe,QACR,QAAQ,UAAA,GAAe,KAExCmI,EAAQD,EAAO,KAAME,GAAMA,EAAE,KAAOH,CAAO,EACjD,OAAOE,EAASA,EAAM,OAAOnI,CAAM,GAAKmI,EAAM,MAAM,IAAM,GAAM,EACpE,CASO,SAASE,GACZ/L,EACAX,EACM,CACN,MAAMD,EAASY,EAAO,QAAU,CAAA,EAC1B2L,EAAUvM,EAAO,QACjBwM,EAASxM,EAAO,OAEtB,GAAI,CAACuM,GAAW,CAACC,GAAU,CAAC,MAAM,QAAQA,CAAM,EAC5C,MAAO,GAIX,MAAMlI,EADU,OAAe,QACR,QAAQ,UAAA,GAAe,KAExCmI,EAAQD,EAAO,KAAME,GAAMA,EAAE,KAAOH,CAAO,EACjD,OAAOE,GAAO,cAAcnI,CAAM,GAAKmI,GAAO,aAAa,IAAM,EACrE,CC5DA,MAAMhM,EAAW,OAAe,QAAQ,eAAe,YAAY,GAAM,CACrE,IAAK,IAAIC,IAAoB,QAAQ,IAAI,eAAgB,GAAGA,CAAI,EAChE,KAAM,IAAIA,IAAoB,QAAQ,KAAK,eAAgB,GAAGA,CAAI,EAClE,MAAO,IAAIA,IAAoB,QAAQ,MAAM,eAAgB,GAAGA,CAAI,CACxE,EAaM3B,GAA6C,CAC/C,IAAK,IACL,IAAK,GACT,EASA,SAAS6N,GAAuBzM,EAAwBC,EAAiC,CAErF,MAAMlB,EADOiB,EAAW,KAAMT,GAAMA,EAAE,OAASU,CAAe,GAC3C,WAAarB,GAAmBqB,CAAe,GAAK,EACvE,OAAO,KAAK,IAAI,EAAGlB,CAAI,CAC3B,CAwBA,SAAS2N,GACL3M,EACA4M,EACAC,EACA1M,EACM,CACN,MAAM2M,EAAa9M,EAAYG,EAAYyM,EAE3C,OAAQC,EAAA,CACJ,IAAK,OACD,OAAO,KAAK,KAAKC,CAAS,EAC9B,IAAK,QACD,OAAO,KAAK,MAAMA,CAAS,EAC/B,IAAK,QACL,QACI,OAAO,KAAK,MAAMA,CAAS,CAAA,CAEvC,CAWO,SAASC,GACZrM,EACAC,EACI,CACJ,MAAMb,EAASY,EAAO,QAAU,CAAA,EAC1BG,EAAQf,EAAO,MACff,EAAWe,EAAO,SAClBiB,EAAQjB,EAAO,MACfkN,EAAWlN,EAAO,SAExB,GAAI,CAACe,GAAS,CAAC9B,GAAYgC,IAAU,OAAW,CAC5CR,EAAO,KAAK,kEAAkE,EAC9E,MACJ,CAEA,MAAMS,EAAU,OAAe,OAC/B,GAAI,CAACA,GAAQ,MAAO,CAChBT,EAAO,KAAK,iDAAiD,EAC7D,MACJ,CAEA,MAAMuJ,EAAQ9I,EAAO,MAAM,SAAA,GAAc,CAAA,EACnCiM,EAAetM,EAAQ,aAAa,YAAY,MAAQ,CAAA,EACxDV,EAAagN,GAAc,MAAQ,CAAA,EACnC/M,EAAkB+M,GAAc,kBAAoB,MACpDJ,EAAiBI,GAAc,iBAAmB,QAElDC,EAAY,CAAE,GAAIpD,EAAM,MAAM,uBAAyB,CAAA,CAAC,EAO9D,GANAoD,EAAUnO,CAAQ,EAAI,CAClB,GAAImO,EAAUnO,CAAQ,GAAK,CAAA,EAC3B,CAAC8B,CAAK,EAAGE,CAAA,EAITiM,GAAYjO,IAAamB,EAAiB,CAC1C,MAAMC,EAAWuM,GAAuBzM,EAAYC,CAAe,EACnED,EAAW,QAASkN,GAAmB,CACnC,GAAIA,EAAK,OAASjN,EAAiB,CAC/B,MAAM0M,EAAeO,EAAK,eAAiB,EACrCC,EAAiBT,GAAuB5L,EAAO6L,EAAcC,EAAgB1M,CAAQ,EAE3F+M,EAAUC,EAAK,IAAI,EAAI,CACnB,GAAID,EAAUC,EAAK,IAAI,GAAK,CAAA,EAC5B,CAACtM,CAAK,EAAGuM,CAAA,CAEjB,CACJ,CAAC,CACL,CAGA,MAAMC,EAAuC,CACzC,KAAM,aACN,QAAS,eAAA,EAGPC,EACFvO,IAAamB,EAAkBa,EAAQ+I,EAAM,OAAOuD,EAAaxM,CAAK,CAAC,EAE3EG,EAAO,MAAM,SAAS,CAClB,KAAM,CACF,GAAG8I,EAAM,KACT,sBAAuBoD,EACvB,CAACG,EAAaxM,CAAK,CAAC,EAAGyM,CAAA,EAE3B,WAAY,EAAA,CACf,EAED/M,EAAO,IAAI,yBAAyBM,CAAK,cAAc9B,CAAQ,KAAKgC,CAAK,EAAE,CAC/E,CASO,SAASwM,GACZ7M,EACAX,EACM,CAEN,MAAMsC,GADS3B,EAAO,QAAU,CAAA,GACT,QAEvB,GAAI,CAAC2B,GAAW,CAAC,MAAM,QAAQA,CAAO,GAAKA,EAAQ,SAAW,EAC1D,MAAO,GAGX,MAAMmL,EAAanL,EAAQ,OAAO,CAACgB,EAAKC,IAChCA,EAAI,YAAc,GAAcD,EAC7BA,GAAOC,EAAI,gBAAkB,GACrC,CAAC,EAEJ/C,OAAAA,EAAO,IAAI,4CAA4CiN,CAAU,EAAE,EAC5DA,CACX,CAkBO,SAASC,GACZtD,EACApK,EACI,CACJ,MAAMiB,EAAU,OAAe,OAC/B,GAAI,CAACA,GAAQ,MAAO,CAChBT,EAAO,KAAK,2DAA2D,EACvE,MACJ,CAEA,MAAMuJ,EAAQ9I,EAAO,MAAM,SAAA,GAAc,CAAA,EAGnCU,EAAY,WAAW,OAAOoI,EAAM,MAAM,UAAU,CAAC,GAAK,EAC1D4D,EAAe,WAAW,OAAO5D,EAAM,MAAM,aAAa,CAAC,GAAK,EAEhE6D,EAAWD,EAAehM,GAAaA,EAAY,EAEnDkM,EAAe5M,EAAO,IAAI,6EAA6E,GACtG,sBAGDI,EAAY,CAAE,GAAI0I,EAAM,QAAU,CAAA,CAAC,EACrC6D,EACAvM,EAAU,cAAgB,CAACwM,CAAY,EAEvC,OAAOxM,EAAU,cAIrB,MAAMyM,EAAc,OAAO,KAAKzM,CAAS,EAAE,OAAS,EAEpDJ,EAAO,MAAM,SAAS,CAClB,WAAY,CACR,GAAG8I,EAAM,WACT,YAAa6D,CAAA,EAEjB,OAAQE,EAAczM,EAAY,IAAA,CACrC,EAEGuM,GACA3M,EAAO,OAAO,UAAU4M,CAAY,EACpCrN,EAAO,KAAK,0CAA0CmN,CAAY,yBAAyBhM,CAAS,GAAG,GAEvGnB,EAAO,IAAI,iDAAiD,CAEpE,CClPA,MAAMA,EAAW,OAAe,QAAQ,eAAe,oBAAoB,GAAM,CAC7E,IAAK,IAAIC,IAAoB,QAAQ,IAAI,uBAAwB,GAAGA,CAAI,EACxE,KAAM,IAAIA,IAAoB,QAAQ,KAAK,uBAAwB,GAAGA,CAAI,EAC1E,MAAO,IAAIA,IAAoB,QAAQ,MAAM,uBAAwB,GAAGA,CAAI,CAChF,EAKA,SAASsN,IAAgC,CAErC,OADgB,OAAe,QAChB,SAAS,uBAAuB,GAAK,CAAC,KAAM,IAAI,CACnE,CAKA,SAASC,IAAoD,CAEzD,OADgBD,GAAA,EACD,OAAO,CAACE,EAAK5J,KAAY,CAAE,GAAG4J,EAAK,CAAC5J,CAAM,EAAG,EAAA,GAAO,CAAA,CAA4B,CACnG,CAYO,SAAS6J,GAAqB5L,EAAuE,CACxG,OAAQA,GAAW,CAAA,GACd,OAAQiB,GAAQA,EAAI,YAAc,EAAK,EACvC,OAAO,CAACD,EAAKC,IAAQD,GAAO,SAAS,OAAOC,EAAI,cAAc,EAAG,EAAE,GAAK,GAAI,CAAC,CACtF,CAwCA,MAAMzE,GAA6C,CAC/C,IAAK,IACL,IAAK,GACT,EAOA,SAAS6N,IAAiC,CAGtC,MAAMwB,IAFe,OAAe,QAAQ,OAAO,MAAA,GAAW,CAAA,GACzB,UAAU,mBAAmB,GAAG,mBAAmB,YAAc,CAAA,GACpF,KAAM1O,GAAMA,EAAE,UAAU,EAC1C,GAAI,CAAC0O,EACD,MAAO,GAEX,MAAMlP,EAAOkP,EAAK,WAAarP,GAAmBqP,EAAK,IAAI,GAAK,EAChE,OAAO,KAAK,IAAI,EAAGlP,CAAI,CAC3B,CAUA,SAASC,GAAcC,EAAeF,EAAcG,EAAwB,CACxE,MAAMC,EAAY,WAAWJ,CAAI,GAAK,EACtC,GAAII,GAAa,EACb,OAAOF,EAGX,MAAMG,EAAUH,EAAQE,EACxB,IAAIE,EAEJ,OAAQH,EAAA,CACJ,IAAK,OACDG,EAAU,KAAK,KAAKD,CAAO,EAC3B,MACJ,IAAK,QACDC,EAAU,KAAK,MAAMD,CAAO,EAC5B,MACJ,QACIC,EAAU,KAAK,MAAMD,CAAO,CAAA,CAGpC,OAAOC,EAAUF,CACrB,CAkBO,SAAS+O,GAAqBnO,EAAmBjB,EAAoBoB,EAAyD,CACjI,MAAMyM,EAAe7N,EAAS,eAAiB,EAC/C,GAAI6N,GAAgB,EAChB,MAAO,CAAE,MAAO,EAAG,UAAWrN,GAAe,EAAGR,CAAQ,CAAA,EAG5D,MAAMqP,EAAUjO,GAAYuM,GAAA,EACtBrM,EAAkBL,EAAYoO,EAAWxB,EACzCtM,EAAerB,GACjBoB,EACAtB,EAAS,eAAiB,OAC1BA,EAAS,iBAAmB,OAAA,EAI1BY,EAAgBZ,EAAS,gBAAkB,EAC3CsP,EAAa,WAAW/N,EAAa,QAAQX,CAAa,CAAC,EAEjE,MAAO,CAAE,MAAO0O,EAAY,UAAW9O,GAAe8O,EAAYtP,CAAQ,CAAA,CAC9E,CAkBO,SAASuP,GACZjM,EACAkM,EACAtO,EACe,CACf,OAAOoC,EAAQ,IAAKiB,GAAQ,CACxB,MAAMkL,EAAkB,WAAW,OAAOlL,EAAI,gBAAgB,CAAC,GAAK,EAC9DmL,EAAwBF,EAAkBC,EAE1CE,EAA4B,CAC9B,GAAGpL,EACH,cAAemL,CAAA,EAGnB,GAAIxO,EAAW,OAAS,EAAG,CACvB,MAAME,EAAWuM,GAAA,EACXiC,EAAyD,CAAA,EAC/D1O,EAAW,QAASlB,GAAa,CAC7B4P,EAAoB5P,EAAS,IAAI,EAAIoP,GAAqBM,EAAuB1P,EAAUoB,CAAQ,CACvG,CAAC,EACDuO,EAAW,6BAA+BC,CAC9C,CAEA,OAAOD,CACX,CAAC,CACL,CAUA,SAASE,IAAoC,CAEzC,QADqB,OAAe,QAAQ,OAAO,MAAA,GAAW,CAAA,GAC1C,UAAU,mBAAmB,GAAG,mBAAmB,YAAc,CAAA,GAChF,OAAQ,GAAgB,CAAC,EAAE,YAAc,EAAE,aAAa,CACjE,CAiBO,SAAS5I,GACZtF,EACAX,EACI,CACJ,MAAMD,EAASY,EAAO,QAAU,CAAA,EAE1BM,EAAU,OAAe,OAC/B,GAAI,CAACA,GAAQ,MAAO,CAChBT,EAAO,KAAK,sDAAsD,EAClE,MACJ,CAEA,MAAMuJ,EAAQ9I,EAAO,MAAM,SAAA,GAAc,CAAA,EACnCqB,EAA2B,CAAC,GAAIyH,EAAM,MAAM,SAAW,CAAA,CAAG,EAI1D+E,EAAW/O,EAAO,MAClBgP,EAAkCD,GAAa,KAC9C,OAAOA,GAAa,SAAW,SAASA,EAAU,EAAE,EAAIA,EACzD,GACAE,EAAcjP,EAAO,YAAyBuC,EAAQyM,CAAK,GAAG,YAEpE,GAAI,CAACC,EAAY,CACbxO,EAAO,KAAK,oDAAqD,CAAE,MAAOsO,EAAU,EACpF,MACJ,CAGA,IAAIpM,EAAiBJ,EAAQ,IAAKiB,IAAS,CACvC,GAAGA,EACH,WAAYA,EAAI,cAAgByL,CAAA,EAClC,EAGF,MAAMC,EAAavM,EAAe,KAAMa,GAAQA,EAAI,aAAe,EAAI,EACjEiL,EAAkBS,EAClB,WAAW,OAAOA,EAAW,aAAa,CAAC,GAAM,WAAW,OAAOlF,EAAM,MAAM,aAAa,CAAC,GAAK,EAClG,WAAW,OAAOA,EAAM,MAAM,aAAa,CAAC,GAAK,EAEvD,GAAIkF,EAAY,CACZA,EAAW,iBAAmB,EAC9B,MAAM/O,EAAa2O,GAAA,EACnBnM,EAAiB6L,GAA8B7L,EAAgB8L,EAAiBtO,CAAU,CAC9F,CAEAe,EAAO,MAAM,SAAS,CAClB,KAAM,CAAE,GAAG8I,EAAM,KAAM,QAASrH,EAAgB,cAAe8L,CAAA,EAC/D,WAAY,EAAA,CACf,EAEDhO,EAAO,IAAI,4CAA4CwO,CAAU,qCAAqCR,CAAe,EAAE,CAC3H,CAUO,SAASU,GACZvO,EACAX,EACI,CACJ,MAAMD,EAASY,EAAO,QAAU,CAAA,EAC1BoO,EAAQ,OAAOhP,EAAO,OAAU,SAAW,SAASA,EAAO,MAAO,EAAE,EAAIA,EAAO,MAC/Ee,EAAQf,EAAO,MACfiB,EAAQjB,EAAO,MAErB,GAA2BgP,GAAU,MAAQ,CAACjO,EAAO,CACjDN,EAAO,KAAK,mDAAoD,CAAE,MAAAuO,EAAO,MAAAjO,EAAO,EAChF,MACJ,CAEA,MAAMG,EAAU,OAAe,OAC/B,GAAI,CAACA,GAAQ,MAAO,CAChBT,EAAO,KAAK,2DAA2D,EACvE,MACJ,CAEA,MAAMuJ,EAAQ9I,EAAO,MAAM,SAAA,GAAc,CAAA,EACnCqB,EAA2B,CAAC,GAAIyH,EAAM,MAAM,SAAW,CAAA,CAAG,EAEhE,GAAIgF,EAAQ,GAAKA,GAASzM,EAAQ,OAAQ,CACtC9B,EAAO,KAAK,+CAAgD,CAAE,MAAAuO,EAAO,OAAQzM,EAAQ,OAAQ,EAC7F,MACJ,CAGA,MAAM6M,EAAgB,CAAC,gBAAiB,aAAc,aAAc,gBAAiB,iBAAkB,sBAAuB,SAAU,SAAU,eAAe,EACjK,IAAI1N,EAAaT,EAYjB,GAXImO,EAAc,SAASrO,CAAK,IAC5BW,EAAa,WAAWT,CAAK,GAAK,GAItCsB,EAAQyM,CAAK,EAAI,CACb,GAAGzM,EAAQyM,CAAK,EAChB,CAACjO,CAAK,EAAGW,CAAA,EAITX,IAAU,gBAAiB,CAC3B,MAAMsO,EAAqB,WAAW,OAAO3N,CAAU,CAAC,GAAK,EACvDvB,EAAa2O,GAAA,EAInB,GAAIvM,EAAQyM,CAAK,EAAE,aAAe,GAAM,CACpCzM,EAAQyM,CAAK,EAAE,iBAAmB,EAClC,MAAMM,EAAed,GAA8BjM,EAAS8M,EAAoBlP,CAAU,EAE1Fe,EAAO,MAAM,SAAS,CAClB,KAAM,CAAE,GAAG8I,EAAM,KAAM,QAASsF,EAAc,cAAeD,CAAA,EAC7D,WAAY,EAAA,CACf,EAED5O,EAAO,IAAI,mEAAmE4O,CAAkB,sBAAsB,EACtH,MACJ,CAGA,MAAMtM,EAAsB,WAAW,OAAOiH,EAAM,MAAM,aAAa,CAAC,GAAK,EAI7E,GAHAzH,EAAQyM,CAAK,EAAE,iBAAmBK,EAAqBtM,EAGnD5C,EAAW,OAAS,EAAG,CACvB,MAAME,EAAWuM,GAAA,EACXiC,EAAyD,CAAA,EAC/D1O,EAAW,QAASlB,GAAa,CAC7B4P,EAAoB5P,EAAS,IAAI,EAAIoP,GAAqBgB,EAAoBpQ,EAAUoB,CAAQ,CACpG,CAAC,EACDkC,EAAQyM,CAAK,EAAE,6BAA+BH,EAC9CpO,EAAO,IAAI,yFAAyFuO,CAAK,IAAK7O,EAAW,IAAIT,GAAKA,EAAE,IAAI,CAAC,CAC7I,CACJ,CAGA,IAAIqB,IAAU,kBAAoBA,IAAU,cACpCwB,EAAQ,OAAS,EAAG,CACpB,MAAMmL,EAAaS,GAAqB5L,CAAO,EAE/CrB,EAAO,MAAM,SAAS,CAClB,KAAM,CAAE,GAAG8I,EAAM,KAAM,QAAAzH,EAAS,eAAgBmL,CAAA,EAChD,WAAY,EAAA,CACf,EAEDjN,EAAO,IAAI,6DAA6DiN,CAAU,0BAA0B,EAC5G,MACJ,CAGJxM,EAAO,MAAM,SAAS,CAClB,KAAM,CAAE,GAAG8I,EAAM,KAAM,QAAAzH,CAAA,EACvB,WAAY,EAAA,CACf,EAED9B,EAAO,IAAI,2CAA2CuO,CAAK,KAAKjO,CAAK,KAAMW,CAAU,CACzF,CAcO,SAAS6N,GACZ3O,EACAX,EACI,CACJ,MAAMiB,EAAU,OAAe,OAC/B,GAAI,CAACA,GAAQ,MAAO,CAChBT,EAAO,KAAK,kDAAkD,EAC9D,MACJ,CAGA,MAAM+O,EADW5O,EAAO,QAAQ,WAAa,IAAQA,EAAO,QAAQ,WAAa,OACzC,EAAI,EAEtCoJ,EAAQ9I,EAAO,MAAM,SAAA,GAAc,CAAA,EACnCqB,EAA2B,CAAC,GAAIyH,EAAM,MAAM,SAAW,CAAA,CAAG,EAC1DyF,EAA8BzF,EAAM,MAAM,eAAiB,CAAA,EAG3D0F,EAAgB,OAAO,KAAK,IAAA,CAAK,GAGjCC,EAAkBF,EAAa,IAAKG,IAAW,CACjD,IAAKA,EAAM,KACX,MAAO3B,GAAA,CAA0B,EACnC,EAEI4B,EAA2B,CAC7B,GAAI,KACJ,YAAaH,EACb,YAAazB,GAAA,EACb,cAAe0B,EACf,WAAYpN,EAAQ,SAAW,EAC/B,cAAe,EACf,WAAY,EACZ,WAAY,EACZ,cAAe,EACf,iBAAkB,EAClB,6BAA8B,CAAA,EAC9B,IAAK,GACL,eAAgBiN,EAChB,oBAAqB,EACrB,OAAQ,EACR,OAAQ,EACR,cAAe,EACf,aAAc,UACd,UAAW,EAAA,EAGTM,EAAc,CAAC,GAAGvN,EAASsN,CAAS,EACpCnC,EAAaS,GAAqB2B,CAAW,EAEnD5O,EAAO,MAAM,SAAS,CAClB,KAAM,CAAE,GAAG8I,EAAM,KAAM,QAAS8F,EAAa,eAAgBpC,EAAY,YAAaoC,EAAY,OAAS,CAAA,EAC3G,WAAY,EAAA,CACf,EAEDrP,EAAO,IAAI,wCAAwCiP,CAAa,8BAA8BhC,CAAU,EAAE,CAC9G,CAaO,SAASqC,GACZnP,EACAX,EACI,CACJ,MAAMiB,EAAU,OAAe,OAC/B,GAAI,CAACA,GAAQ,MAAO,CAChBT,EAAO,KAAK,uEAAuE,EACnF,MACJ,CAEA,MAAMT,EAASY,EAAO,QAAU,CAAA,EAC1BoJ,EAAQ9I,EAAO,MAAM,SAAA,GAAc,CAAA,EAInC8O,EAAyBhQ,EAAO,kBAAoB,OACpD,WAAW,OAAOA,EAAO,eAAe,CAAC,GAAK,EAC9C,WAAW,OAAOgK,EAAM,MAAM,aAAa,CAAC,GAAK,EAEjDzH,EAA2B,CAAC,GAAIyH,EAAM,MAAM,SAAW,CAAA,CAAG,EAEhE,GAAIzH,EAAQ,SAAW,EAAG,CACtB9B,EAAO,IAAI,+DAA+D,EAC1E,MACJ,CAGA,MAAMN,EAAa2O,GAAA,EAEbnM,EAAiB6L,GAA8BjM,EAASyN,EAAwB7P,CAAU,EAEhGe,EAAO,MAAM,SAAS,CAClB,KAAM,CAAE,GAAG8I,EAAM,KAAM,QAASrH,CAAA,CAAe,CAClD,EAEDlC,EAAO,IAAI,sEAAsE8B,EAAQ,MAAM,4CAA4CyN,CAAsB,EAAE,CACvK,CCvgBA,MAAMvP,EAAW,OAAe,QAAQ,eAAe,aAAa,GAAM,CACtE,IAAK,IAAIC,IAAoB,QAAQ,IAAI,gBAAiB,GAAGA,CAAI,EACjE,KAAM,IAAIA,IAAoB,QAAQ,KAAK,gBAAiB,GAAGA,CAAI,EACnE,MAAO,IAAIA,IAAoB,QAAQ,MAAM,gBAAiB,GAAGA,CAAI,CACzE,EAOA,SAASsN,IAAgC,CAErC,OADgB,OAAe,QAChB,SAAS,uBAAuB,GAAK,CAAC,KAAM,IAAI,CACnE,CAOA,SAASiC,IAA2B,CAEhC,OADgB,OAAe,QAChB,SAAS,YAAY,GAAKjC,GAAA,EAAsB,CAAC,GAAK,IACzE,CAOA,SAASC,GAAoD,CAEzD,OADgBD,GAAA,EACD,OAAO,CAACE,EAAK5J,KAAY,CAAE,GAAG4J,EAAK,CAAC5J,CAAM,EAAG,EAAA,GAAO,CAAA,CAAE,CACzE,CAQA,SAAS4L,GAAiCC,EAA0C,CAChF,MAAO,CACH,GAAI,KACJ,KAAMlC,EAAA,EACN,iBAAkB,EAClB,WAAYkC,IAAc,EAC1B,UAAW,GACX,kBAAmB,GACnB,WAAYA,CAAA,CAEpB,CA4FA,SAASC,GAAkBnP,EAAyCqD,EAAwB,CACxF,OAAI,OAAOrD,GAAU,SAAiBA,EAC/BA,EAAMqD,CAAM,GAAKrD,EAAM,IAAS,OAAO,OAAOA,CAAK,EAAE,CAAC,GAAK,EACtE,CAQA,SAASoP,GAAoBpP,EAA8C,CACvE,OAAOA,GAAS,OAAOA,GAAU,UAAY,CAAC,MAAM,QAAQA,CAAK,CACrE,CASA,SAASqP,GAAkBC,EAAmD,CAC1E,OAAI,MAAM,QAAQA,CAAM,EACbA,EAEPA,GAAU,OAAOA,GAAW,SAErB,OAAO,OAAOA,CAAM,EAExB,CAAA,CACX,CAQA,SAASC,GAAoBC,EAAsB,CAC/C,OAAOA,EAAO,OACV,CAACvC,EAAYb,IAAc,CACvB,MAAM/M,EAAgB,CAAA,EACtB,OAAA4N,EAAI,QAASwC,GAAM,CACfrD,EAAK,QAAS3N,GAAM,CAChBY,EAAO,KAAK,CAAC,GAAGoQ,EAAGhR,CAAC,CAAC,CACzB,CAAC,CACL,CAAC,EACMY,CACX,EACA,CAAC,CAAA,CAAE,CAAA,CAEX,CAQA,SAASqQ,GAA+BC,EAAqE,CACzG,MAAMC,EAAU7C,GAAA,EACV1N,EAAiC,CAAA,EAEvC,UAAWgE,KAAUuM,EAAS,CAC1B,MAAMC,EAAQF,EAAM,OAASR,GAAkBW,EAAGzM,CAAM,CAAC,EACzDhE,EAAOgE,CAAM,EAAIwM,EAAM,KAAK,GAAG,CACnC,CAEA,OAAOxQ,CACX,CASA,SAAS0Q,GACLJ,EACAK,EACiB,CACjB,OAAOA,EAAO,IAAI,CAACC,EAAKlO,KAAO,CAC3B,IAAK,OAAOkO,EAAI,MAAS,SACnB,CAAE,GAAGjD,IAA6B,CAACgC,IAAkB,EAAGiB,EAAI,IAAA,EAC5DA,EAAI,KACV,MAAOb,GAAoBO,EAAM5N,CAAC,CAAC,EAC7B4N,EAAM5N,CAAC,EACP,CAAE,GAAGiL,EAAA,EAA6B,CAACgC,GAAA,CAAkB,EAAGW,EAAM5N,CAAC,CAAA,CAAY,EACnF,CACN,CAQO,SAASmO,GACZ9G,EACApK,EACI,CACJ,MAAMiB,EAAU,OAAe,OAC/B,GAAI,CAACA,GAAQ,MAAO,CAChBT,EAAO,KAAK,oDAAoD,EAChE,MACJ,CAEA,MAAMuJ,EAAQ9I,EAAO,MAAM,SAAA,GAAc,CAAA,EACnC+P,EAASjH,EAAM,IAAI,cAAgB,CAAA,EAEzC,GAAIiH,EAAO,QAAU,EAAG,CACpB/P,EAAO,OAAO,UACVA,EAAO,IAAI,8DAA8D,GACtE,oCAAA,EAEP,MACJ,CAEAA,EAAO,MAAM,SAAS,CAClB,GAAI,CACA,GAAG8I,EAAM,GACT,aAAc,CAAC,GAAGiH,EAAQ,CAAE,KAAMhD,EAAA,EAA6B,OAAQ,EAAC,CAAG,CAAA,CAC/E,CACH,EAEDxN,EAAO,IAAI,+CAA+CwQ,EAAO,OAAS,CAAC,EAAE,CACjF,CAQO,SAASG,GACZxQ,EACAX,EACI,CAEJ,MAAM+O,GADSpO,EAAO,QAAU,CAAA,GACX,MAErB,GAAIoO,IAAU,OAAW,CACrBvO,EAAO,KAAK,yCAAyC,EACrD,MACJ,CAEA,MAAMS,EAAU,OAAe,OAC/B,GAAI,CAACA,GAAQ,MAAO,CAChBT,EAAO,KAAK,uDAAuD,EACnE,MACJ,CAEA,MAAMuJ,EAAQ9I,EAAO,MAAM,SAAA,GAAc,CAAA,EACnC+P,EAAS,CAAC,GAAIjH,EAAM,IAAI,cAAgB,CAAA,CAAG,EACjDiH,EAAO,OAAOjC,EAAO,CAAC,EAEtB9N,EAAO,MAAM,SAAS,CAClB,GAAI,CAAE,GAAG8I,EAAM,GAAI,aAAciH,CAAA,CAAO,CAC3C,EAEDxQ,EAAO,IAAI,qDAAqDuO,CAAK,EAAE,CAC3E,CAQO,SAASqC,GACZzQ,EACAX,EACI,CACJ,MAAMD,EAASY,EAAO,QAAU,CAAA,EAC1BoO,EAAQhP,EAAO,MACfe,EAAQf,EAAO,MACfiB,EAAQjB,EAAO,MAErB,GAAIgP,IAAU,QAAa,CAACjO,EAAO,CAC/BN,EAAO,KAAK,6CAA6C,EACzD,MACJ,CAEA,MAAMS,EAAU,OAAe,OAC/B,GAAI,CAACA,GAAQ,MAAO,CAChBT,EAAO,KAAK,uDAAuD,EACnE,MACJ,CAGAA,EAAO,IAAI,yCAAyCuO,CAAK,YAAYjO,CAAK,YAAY,OAAOA,CAAK,YAAaE,CAAK,EAEpH,MAAM+I,EAAQ9I,EAAO,MAAM,SAAA,GAAc,CAAA,EACnC+P,EAAS,CAAC,GAAIjH,EAAM,IAAI,cAAgB,CAAA,CAAG,EAYjD,GATAvJ,EAAO,IAAI,sCAAsCuO,CAAK,KAAMiC,EAAOjC,CAAK,CAAC,EAGpEiC,EAAOjC,CAAK,IACbvO,EAAO,IAAI,8BAA8BuO,CAAK,gDAAgD,EAC9FiC,EAAOjC,CAAK,EAAI,CAAE,KAAMf,IAA6B,OAAQ,EAAC,GAI9DlN,IAAU,OAAQ,CAClB,MAAMuQ,EAAgBrB,GAAA,EAChBsB,EAAeN,EAAOjC,CAAK,EAAE,KACnCvO,EAAO,IAAI,gEAAgE6Q,CAAa,kBAAmBC,EAAc,SAAS,OAAOA,CAAY,EAAE,EAEvJ,IAAIC,EAGAvQ,GAAS,OAAOA,GAAU,UAAY,CAAC,MAAM,QAAQA,CAAK,GAE1DuQ,EAAUvQ,EACVR,EAAO,IAAI,yDAA0D+Q,CAAO,IAO5EA,EAAU,CACN,GALcD,GAAgB,OAAOA,GAAiB,UAAY,CAAC,MAAM,QAAQA,CAAY,EAC3FA,EACAtD,EAAA,EAIF,CAACqD,CAAa,EAAGrQ,CAAA,EAErBR,EAAO,IAAI,mDAAoD+Q,CAAO,GAG1EP,EAAOjC,CAAK,EAAI,CACZ,GAAGiC,EAAOjC,CAAK,EACf,KAAMwC,CAAA,CAEd,MACI/Q,EAAO,IAAI,kDAAkDM,CAAK,GAAG,EACrEkQ,EAAOjC,CAAK,EAAI,CAAE,GAAGiC,EAAOjC,CAAK,EAAG,CAACjO,CAAK,EAAGE,CAAA,EAGjDR,EAAO,IAAI,oCAAoCuO,CAAK,KAAMiC,EAAOjC,CAAK,CAAC,EAEvE9N,EAAO,MAAM,SAAS,CAClB,GAAI,CAAE,GAAG8I,EAAM,GAAI,aAAciH,CAAA,CAAO,CAC3C,CACL,CAQO,SAASQ,GACZpH,EACAxJ,EACI,CACJ,MAAMK,EAAU,OAAe,OAC/B,GAAI,CAACA,GAAQ,MAAO,CAChBT,EAAO,KAAK,qDAAqD,EACjE,MACJ,CAEA,MAAMuJ,EAAQ9I,EAAO,MAAM,SAAA,GAAc,CAAA,EACnCG,EAAcH,EAAO,MAAM,IAAA,GAAS,CAAA,EACpC+P,EAAwBjH,EAAM,IAAI,cAAgB,CAAA,EAGlDsH,EAAgBrB,GAAA,EAGhByB,EAAiBR,GACdA,EAAI,KACL,OAAOA,EAAI,MAAS,SAAiBA,EAAI,KACtCA,EAAI,KAAKI,CAAa,GAAK,GAFZ,GAMpBK,EAAmBV,EAAO,IAAIC,IAAQ,CACxC,GAAGA,EACH,OAAQZ,GAAkBY,EAAI,MAAM,CAAA,EACtC,EAEIU,EAAcD,EAAiB,OAChCT,GAAQQ,EAAcR,CAAG,GAAKA,EAAI,QAAUA,EAAI,OAAO,OAAS,CAAA,EAYrE,GATAzQ,EAAO,IAAI,wDAAwD6Q,CAAa,sBAAuBK,EAAiB,IAAIT,IAAQ,CAChI,KAAMA,EAAI,KACV,SAAU,OAAOA,EAAI,KACrB,cAAeQ,EAAcR,CAAkB,EAC/C,OAAQA,EAAI,OACZ,cAAe,MAAM,QAAQA,EAAI,MAAM,CAAA,EACzC,CAAC,EACHzQ,EAAO,IAAI,yCAAyCmR,EAAY,MAAM,EAAE,EAEpEA,EAAY,SAAW,EAAG,CAC1B1Q,EAAO,OAAO,QACVA,EAAO,IAAI,sEAAsE,GAC9E,sCAAA,EAEP,MACJ,CAGA,MAAM2Q,EAAmC7H,EAAM,MAAM,SAAW,CAAA,EAC1D8H,EAAczH,EAAQ,QAAQ,cAAgB,GACpD,GAAIwH,EAAgB,OAAS,GAAK,CAACC,EAAa,CAC5C5Q,EAAO,OAAO,OAAO,0BAA0B,EAC/C,MACJ,CAMA,MAAMsO,EADWnF,EAAQ,QAAQ,WAAa,IAAQA,EAAQ,QAAQ,WAAa,OAC3C,EAAI,EAGtC0H,EAAmB,WAAW,OAAO/H,EAAM,MAAM,UAAU,CAAC,GAAK,EACjEjH,EAAsB,WAAW,OAAOiH,EAAM,MAAM,aAAa,CAAC,GAAK,EACvEgI,EAAyBhI,EAAM,MAAM,6BAGrCiI,GACF5Q,EAAY,UAAU,mBAAmB,GAAG,mBAAmB,YAAc,CAAA,GAC/E,OAAQ3B,GAAW,CAACA,EAAE,YAAcA,EAAE,aAAa,EAErD,GAAI,CACAe,EAAO,IAAI,oDAAoD,EAG/D,MAAMgP,EAA8BmC,EAAY,IAAI,CAACV,EAAKlO,KACtDvC,EAAO,IAAI,sCAAsCuC,CAAC,KAAM,CACpD,KAAMkO,EAAI,KACV,OAAQA,EAAI,OACZ,WAAY,OAAOA,EAAI,OACvB,cAAe,MAAM,QAAQA,EAAI,MAAM,CAAA,CAC1C,EACM,CACH,KAAM,OAAOA,EAAI,MAAS,SACpB,CAAE,GAAGjD,IAA6B,CAACqD,CAAa,EAAGJ,EAAI,IAAA,EACvDA,EAAI,KAEV,OAAQA,EAAI,OAAO,IAAIH,GACnBV,GAAoBU,CAAC,EAAIA,EAAI,CAAE,GAAG9C,EAAA,EAA6B,CAACqD,CAAa,EAAGP,CAAA,CAAY,CAChG,EAEP,EAMCxO,EAHeiO,GAAiBoB,EAAY,IAAKV,GAAQA,EAAI,MAAM,CAAC,EAG5B,IAAI,CAACN,EAAO7G,IAAQ,CAE9D,MAAMmI,EAAelB,GAAiCJ,EAAOgB,CAAW,EAGlEO,GAAaxB,GAA+BC,CAAK,EACjD3B,GAAa,OAAO,OAAOlF,EAAM,CAAC,EAAE,SAAS,EAAG,GAAG,CAAC,GAG1D,IAAIxD,GAA+D,CAAA,EACnE,OAAIyL,GAA0B,OAAOA,GAA2B,UAAY,OAAO,KAAKA,CAAsB,EAAE,OAAS,EAErHzL,GAA4B,KAAK,MAAM,KAAK,UAAUyL,CAAsB,CAAC,EACtEjP,EAAsB,GAAKkP,EAAe,OAAS,GAE1DA,EAAe,QAAShT,GAAkB,CACtCsH,GAA0BtH,EAAS,IAAI,EAAIoP,GAAqBtL,EAAqB9D,CAAQ,CACjG,CAAC,EAGE,CACH,GAAI,KACJ,YAAagQ,GACb,cAAeiD,EACf,YAAaC,GACb,WAAYJ,EACZ,cAAehP,EACf,iBAAkB,EAClB,eAAgByM,EAChB,oBAAqB,EACrB,WAAYzF,IAAQ,EACpB,UAAW,GACX,IAAK,GACL,6BAA8BxD,EAAA,CAEtC,CAAC,EAIKsL,EAAmC7H,EAAM,MAAM,SAAW,CAAA,EAC1DoI,EAAoB5O,GACjBA,EAAI,YACL,OAAOA,EAAI,aAAgB,SAAiBA,EAAI,YAC7CA,EAAI,YAAY8N,CAAa,GAAK9N,EAAI,YAAY,IAAS,GAFrC,GAI3B6O,EAAgB9P,EAAQ,IAAK+P,GAAW,CAC1C,MAAMC,EAASH,EAAiBE,CAAM,EAChCE,EAAWX,EAAgB,KAAMhQ,IAAMuQ,EAAiBvQ,EAAC,IAAM0Q,CAAM,EAC3E,OAAOC,EAAW,CAAE,GAAGF,EAAQ,GAAGE,EAAU,GAAIA,EAAS,EAAA,EAAOF,CACpE,CAAC,EAIK5E,EAAaS,GAAqBkE,CAAa,EAErDnR,EAAO,MAAM,SAAS,CAClB,KAAM,CACF,GAAG8I,EAAM,KACT,cAAeyF,EACf,QAAS4C,EACT,YAAaA,EAAc,OAAS,EACpC,eAAgB3E,CAAA,EAEpB,WAAY,EAAA,CACf,EAEDxM,EAAO,OAAO,UACVA,EAAO,IAAI,6DAA8D,CAAE,MAAOmR,EAAc,MAAA,CAAQ,GACrG,GAAGA,EAAc,MAAM,+BAAA,EAE9B5R,EAAO,IAAI,+BAA+B4R,EAAc,MAAM,UAAU,CACxE,OAAS9J,EAAO,CACZ9H,MAAAA,EAAO,MAAM,oCAAqC8H,CAAK,EACvD9H,EAAO,MAAM,mCAAqC8H,EAAgB,OAAO,EACzE9H,EAAO,MAAM,iCAAmC8H,EAAgB,KAAK,EAC/DA,CACV,CACJ,CAQO,SAASkK,GACZ7R,EACAX,EACI,CAEJ,MAAMyS,GADS9R,EAAO,QAAU,CAAA,GACR,SAExB,GAAI8R,IAAa,OAAW,CACxBjS,EAAO,KAAK,uCAAuC,EACnD,MACJ,CAEA,MAAMS,EAAU,OAAe,OAC/B,GAAI,CAACA,GAAQ,MAAO,CAChBT,EAAO,KAAK,kDAAkD,EAC9D,MACJ,CAEA,MAAMuJ,EAAQ9I,EAAO,MAAM,SAAA,GAAc,CAAA,EACnCqB,EAAU,CAAC,GAAIyH,EAAM,MAAM,SAAW,CAAA,CAAG,EAC/CzH,EAAQ,OAAOmQ,EAAU,CAAC,EAI1B,MAAMhF,EAAaS,GAAqB5L,CAAO,EACzCoQ,EAAapQ,EAAQ,OAAS,EAEpCrB,EAAO,MAAM,SAAS,CAClB,KAAM,CACF,GAAG8I,EAAM,KACT,QAAAzH,EACA,YAAaoQ,EACb,GAAIA,EAAa,CAAE,eAAgBjF,GAAe,CAAA,CAAC,EAEvD,WAAY,EAAA,CACf,EAEDjN,EAAO,IAAI,0CAA0CiS,CAAQ,8BAA8BhF,CAAU,EAAE,CAC3G,CAQO,SAASkF,GACZhS,EACAX,EACI,CACJ,MAAMD,EAASY,EAAO,QAAU,CAAA,EAC1BuR,EAAanS,EAAO,WACpB6S,EAAa7S,EAAO,WACpB8S,EAAe9S,EAAO,aAEtBkB,EAAU,OAAe,OAC/B,GAAI,CAACA,GAAQ,MAAO,CAChBT,EAAO,KAAK,wDAAwD,EACpE,MACJ,CAEA,GAAI,CAAC0R,GAAY,OAAQ,CACrBjR,EAAO,OAAO,UACVA,EAAO,IAAI,gEAAgE,GACxE,2BAAA,EAEP,MACJ,CAEA,MAAM8I,EAAQ9I,EAAO,MAAM,SAAA,GAAc,CAAA,EAGnCyB,EAFU,CAAC,GAAIqH,EAAM,MAAM,SAAW,CAAA,CAAG,EAEhB,IAAKxG,GAAQ,CACxC,MAAM8O,EAAS,CAAE,GAAG9O,CAAA,EAEpB,OAAIqP,IACAP,EAAO,cAAgB,CACnB,GAAG9O,EAAI,cACP,CAAC2O,CAAU,EAAG,EAAA,GAIlBW,IACAR,EAAO,iBAAmB9O,EAAI,kBAAoB,GAG/C8O,CACX,CAAC,EAEDpR,EAAO,MAAM,SAAS,CAClB,KAAM,CAAE,GAAG8I,EAAM,KAAM,QAASrH,CAAA,EAChC,GAAI,CACA,GAAGqH,EAAM,GACT,kBAAmB,GACnB,wBAAyB,GACzB,0BAA2B,EAAA,EAE/B,WAAY,EAAA,CACf,EAED9I,EAAO,OAAO,UACVA,EAAO,IAAI,mEAAmE,GAC3E,mCAAA,CAEX,CAQO,SAAS6R,GACZ1I,EACApK,EACI,CACJ,MAAMiB,EAAU,OAAe,OAC/B,GAAI,CAACA,GAAQ,MAAO,CAChBT,EAAO,KAAK,qDAAqD,EACjE,MACJ,CAEA,MAAMuJ,EAAQ9I,EAAO,MAAM,SAAA,GAAc,CAAA,EACnC8R,EAAgChJ,EAAM,MAAM,gBAAkB,CAAA,EAE9DiJ,EAAwB,CAC1B,GAAI,KACJ,WAAYD,EAAc,OAC1B,YAAa,CAAE,GAAI,GAAI,GAAI,EAAA,EAC3B,YAAa,CAAE,GAAI,GAAI,GAAI,EAAA,CAAG,EAGlC9R,EAAO,MAAM,SAAS,CAClB,KAAM,CACF,GAAG8I,EAAM,KACT,eAAgB,CAAC,GAAGgJ,EAAeC,CAAO,CAAA,EAE9C,WAAY,EAAA,CACf,EAEDxS,EAAO,IAAI,iDAAiDuS,EAAc,OAAS,CAAC,EAAE,CAC1F,CAQO,SAASE,GACZtS,EACAX,EACI,CACJ,MAAMD,EAASY,EAAO,QAAU,CAAA,EAC1BoO,EAAQhP,EAAO,MACfe,EAAQf,EAAO,MACfsE,EAAStE,EAAO,OAChBiB,EAAQjB,EAAO,MAErB,GAAIgP,IAAU,QAAa,CAACjO,GAAS,CAACuD,EAAQ,CAC1C7D,EAAO,KAAK,8CAA8C,EAC1D,MACJ,CAEA,MAAMS,EAAU,OAAe,OAC/B,GAAI,CAACA,GAAQ,MAAO,CAChBT,EAAO,KAAK,wDAAwD,EACpE,MACJ,CAEA,MAAMuJ,EAAQ9I,EAAO,MAAM,SAAA,GAAc,CAAA,EACnC8R,EAAgB,CAAC,GAAIhJ,EAAM,MAAM,gBAAkB,CAAA,CAAG,EAE5DgJ,EAAchE,CAAK,EAAI,CACnB,GAAGgE,EAAchE,CAAK,EACtB,CAACjO,CAAK,EAAG,CACL,GAAGiS,EAAchE,CAAK,EAAEjO,CAAK,EAC7B,CAACuD,CAAM,EAAGrD,CAAA,CACd,EAGJC,EAAO,MAAM,SAAS,CAClB,KAAM,CAAE,GAAG8I,EAAM,KAAM,eAAgBgJ,CAAA,EACvC,WAAY,EAAA,CACf,CACL,CAQO,SAASG,GACZvS,EACAX,EACI,CAEJ,MAAM+O,GADSpO,EAAO,QAAU,CAAA,GACX,MAErB,GAAIoO,IAAU,OAAW,CACrBvO,EAAO,KAAK,0CAA0C,EACtD,MACJ,CAEA,MAAMS,EAAU,OAAe,OAC/B,GAAI,CAACA,GAAQ,MAAO,CAChBT,EAAO,KAAK,wDAAwD,EACpE,MACJ,CAEA,MAAMuJ,EAAQ9I,EAAO,MAAM,SAAA,GAAc,CAAA,EACnC8R,EAAgB,CAAC,GAAIhJ,EAAM,MAAM,gBAAkB,CAAA,CAAG,EAC5DgJ,EAAc,OAAOhE,EAAO,CAAC,EAG7BgE,EAAc,QAAQ,CAACI,EAAM,IAAM,CAC/BA,EAAK,WAAa,CACtB,CAAC,EAEDlS,EAAO,MAAM,SAAS,CAClB,KAAM,CAAE,GAAG8I,EAAM,KAAM,eAAgBgJ,CAAA,EACvC,WAAY,EAAA,CACf,EAEDvS,EAAO,IAAI,uDAAuDuO,CAAK,EAAE,CAC7E,CAQO,SAASqE,GACZzS,EACAX,EACI,CACJ,MAAMD,EAASY,EAAO,QAAU,CAAA,EAC1B0S,EAAWtT,EAAO,SAClBuT,EAAWvT,EAAO,SAExB,GAAIsT,IAAa,QAAaC,IAAa,OAAW,CAClD9S,EAAO,KAAK,2DAA2D,EACvE,MACJ,CAEA,MAAMS,EAAU,OAAe,OAC/B,GAAI,CAACA,GAAQ,MAAO,CAChBT,EAAO,KAAK,0DAA0D,EACtE,MACJ,CAEA,MAAMuJ,EAAQ9I,EAAO,MAAM,SAAA,GAAc,CAAA,EACnC8R,EAAgB,CAAC,GAAIhJ,EAAM,MAAM,gBAAkB,CAAA,CAAG,EAEtD,CAACwJ,CAAK,EAAIR,EAAc,OAAOM,EAAU,CAAC,EAChDN,EAAc,OAAOO,EAAU,EAAGC,CAAK,EAGvCR,EAAc,QAAQ,CAACI,EAAMpQ,IAAM,CAC/BoQ,EAAK,WAAapQ,CACtB,CAAC,EAED9B,EAAO,MAAM,SAAS,CAClB,KAAM,CAAE,GAAG8I,EAAM,KAAM,eAAgBgJ,CAAA,EACvC,WAAY,EAAA,CACf,EAEDvS,EAAO,IAAI,0CAA0C6S,CAAQ,OAAOC,CAAQ,EAAE,CAClF,CAQO,SAASE,GACZpJ,EACApK,EACI,CACJ,MAAMiB,EAAU,OAAe,OAC/B,GAAI,CAACA,GAAQ,MAAO,CAChBT,EAAO,KAAK,yDAAyD,EACrE,MACJ,CAEA,MAAMuJ,EAAQ9I,EAAO,MAAM,SAAA,GAAc,CAAA,EACnCqB,EAA8ByH,EAAM,MAAM,oBAAsB,CAAA,EAEtE,GAAIzH,EAAQ,QAAU,EAAG,CACrBrB,EAAO,OAAO,UACVA,EAAO,IAAI,mEAAmE,GAC3E,yCAAA,EAEP,MACJ,CAGA,MAAMwS,EAAS,QAAQ,KAAK,IAAA,CAAK,IAAI,KAAK,OAAA,EAAS,SAAS,EAAE,EAAE,UAAU,EAAG,CAAC,CAAC,GAE/ExS,EAAO,MAAM,SAAS,CAClB,KAAM,CACF,GAAG8I,EAAM,KACT,mBAAoB,CAChB,GAAGzH,EACH,CACI,GAAImR,EACJ,KAAMzF,EAAA,EACN,YAAa,GACb,WAAY1L,EAAQ,OAEpB,OAAQ,CAAC2N,GAAiC,CAAC,CAAC,CAAA,CAChD,CACJ,EAEJ,WAAY,EAAA,CACf,EAEDzP,EAAO,IAAI,yDAAyD8B,EAAQ,OAAS,CAAC,EAAE,CAC5F,CAYO,SAASoR,GACZ/S,EACAX,EACI,CACJ,MAAMD,EAASY,EAAO,QAAU,CAAA,EAC1BoO,EAAQhP,EAAO,MACfe,EAAQf,EAAO,MACfiB,EAAQjB,EAAO,MAErB,GAAIgP,IAAU,QAAa,CAACjO,EAAO,CAC/BN,EAAO,KAAK,kDAAkD,EAC9D,MACJ,CAEA,MAAMS,EAAU,OAAe,OAC/B,GAAI,CAACA,GAAQ,MAAO,CAChBT,EAAO,KAAK,4DAA4D,EACxE,MACJ,CAEA,MAAMuJ,EAAQ9I,EAAO,MAAM,SAAA,GAAc,CAAA,EACnCqB,EAAU,CAAC,GAAIyH,EAAM,MAAM,oBAAsB,CAAA,CAAG,EAE1D,GAAI,CAACzH,EAAQyM,CAAK,EAAG,CACjBvO,EAAO,KAAK,qDAAsDuO,CAAK,EACvE,MACJ,CAGA,GAAIjO,IAAU,OAGV,GAAIE,GAAS,OAAOA,GAAU,UAAY,CAAC,MAAM,QAAQA,CAAK,EAE1DsB,EAAQyM,CAAK,EAAI,CACb,GAAGzM,EAAQyM,CAAK,EAChB,KAAM/N,CAAA,MAEP,CAEH,MAAMqQ,EAAgBrB,GAAA,EAChBsB,EAAehP,EAAQyM,CAAK,EAAE,KAE9B4E,EAAYrC,GAAgB,OAAOA,GAAiB,UAAY,CAAC,MAAM,QAAQA,CAAY,EAC3FA,EACAtD,EAAA,EAEN1L,EAAQyM,CAAK,EAAI,CACb,GAAGzM,EAAQyM,CAAK,EAChB,KAAM,CACF,GAAG4E,EACH,CAACtC,CAAa,EAAGrQ,CAAA,CACrB,CAER,MAGAsB,EAAQyM,CAAK,EAAI,CAAE,GAAGzM,EAAQyM,CAAK,EAAG,CAACjO,CAAK,EAAGE,CAAA,EAGnDC,EAAO,MAAM,SAAS,CAClB,KAAM,CAAE,GAAG8I,EAAM,KAAM,mBAAoBzH,CAAA,EAC3C,WAAY,EAAA,CACf,EAED9B,EAAO,IAAI,4CAA4CuO,CAAK,KAAKjO,CAAK,EAAE,CAC5E,CAQO,SAAS8S,GACZjT,EACAX,EACI,CAEJ,MAAM+O,GADSpO,EAAO,QAAU,CAAA,GACX,MAErB,GAAIoO,IAAU,OAAW,CACrBvO,EAAO,KAAK,8CAA8C,EAC1D,MACJ,CAEA,MAAMS,EAAU,OAAe,OAC/B,GAAI,CAACA,GAAQ,MAAO,CAChBT,EAAO,KAAK,4DAA4D,EACxE,MACJ,CAEA,MAAMuJ,EAAQ9I,EAAO,MAAM,SAAA,GAAc,CAAA,EACnCqB,EAAU,CAAC,GAAIyH,EAAM,MAAM,oBAAsB,CAAA,CAAG,EAC1DzH,EAAQ,OAAOyM,EAAO,CAAC,EAGvBzM,EAAQ,QAAQ,CAACiB,EAAK,IAAM,CACxBA,EAAI,WAAa,CACrB,CAAC,EAEDtC,EAAO,MAAM,SAAS,CAClB,KAAM,CAAE,GAAG8I,EAAM,KAAM,mBAAoBzH,CAAA,EAC3C,WAAY,EAAA,CACf,EAED9B,EAAO,IAAI,+DAA+DuO,CAAK,EAAE,CACrF,CAUO,SAAS8E,GACZlT,EACAX,EACI,CACJ,MAAMD,EAASY,EAAO,QAAU,CAAA,EAC1B0S,EAAW,SAAS,OAAOtT,EAAO,QAAQ,EAAG,EAAE,EAC/CuT,EAAW,SAAS,OAAOvT,EAAO,QAAQ,EAAG,EAAE,EAErD,GAAI,MAAMsT,CAAQ,GAAK,MAAMC,CAAQ,GAAKD,IAAaC,EACnD,OAGJ,MAAMrS,EAAU,OAAe,OAC/B,GAAI,CAACA,GAAQ,MAAO,CAChBT,EAAO,KAAK,8DAA8D,EAC1E,MACJ,CAEA,MAAMuJ,EAAQ9I,EAAO,MAAM,SAAA,GAAc,CAAA,EACnCqB,EAAU,CAAC,GAAIyH,EAAM,MAAM,oBAAsB,CAAA,CAAG,EAE1D,GAAIsJ,EAAW,GAAKA,GAAY/Q,EAAQ,QAAUgR,EAAW,GAAKA,GAAYhR,EAAQ,OAClF,OAIJ,KAAM,CAACiR,CAAK,EAAIjR,EAAQ,OAAO+Q,EAAU,CAAC,EAC1C/Q,EAAQ,OAAOgR,EAAU,EAAGC,CAAK,EAGjCjR,EAAQ,QAAQ,CAACiB,EAAKR,IAAM,CACxBQ,EAAI,WAAaR,CACrB,CAAC,EAED9B,EAAO,MAAM,SAAS,CAClB,KAAM,CAAE,GAAG8I,EAAM,KAAM,mBAAoBzH,CAAA,EAC3C,GAAI,CAAE,GAAGyH,EAAM,GAAI,UAAW,IAAA,EAC9B,WAAY,EAAA,CACf,EAEDvJ,EAAO,IAAI,8CAA8C6S,CAAQ,OAAOC,CAAQ,EAAE,CACtF,CAeO,SAASQ,GACZ1J,EACApK,EACI,CACJ,MAAMiB,EAAU,OAAe,OAC/B,GAAI,CAACA,GAAQ,MAAO,CAChBT,EAAO,KAAK,4DAA4D,EACxE,MACJ,CAEA,MAAMuJ,EAAQ9I,EAAO,MAAM,SAAA,GAAc,CAAA,EAEzCA,EAAO,MAAM,SAAS,CAClB,KAAM,CAAE,GAAG8I,EAAM,KAAM,mBAAoB,CAAA,CAAC,EAC5C,WAAY,EAAA,CACf,EAEDvJ,EAAO,IAAI,0DAA0D,CACzE,CAQA,SAASuT,GAAkBpE,EAA8D,CACrF,GAAI,CAACA,EAAO,MAAO,CAAA,EACnB,MAAMW,EAASX,EAAM,OACrB,OAAI,MAAM,QAAQW,CAAM,EAAUA,EAC9BA,GAAU,OAAOA,GAAW,SAAiB,OAAO,OAAOA,CAAM,EAC9D,CAAA,CACX,CAQO,SAAS0D,GACZrT,EACAX,EACI,CACJ,MAAMD,EAASY,EAAO,QAAU,CAAA,EAC1BsT,EAAa,OAAOlU,EAAO,YAAe,SAAW,SAASA,EAAO,WAAY,EAAE,EAAIA,EAAO,WAEpG,GAAgCkU,GAAe,MAAQ,MAAMA,CAAU,EAAG,CACtEzT,EAAO,KAAK,qDAAqD,EACjE,MACJ,CAEA,MAAMS,EAAU,OAAe,OAC/B,GAAI,CAACA,GAAQ,MAAO,CAChBT,EAAO,KAAK,8DAA8D,EAC1E,MACJ,CAEA,MAAMuJ,EAAQ9I,EAAO,MAAM,SAAA,GAAc,CAAA,EACnCqB,EAA8B,CAAC,GAAIyH,EAAM,MAAM,oBAAsB,CAAA,CAAG,EAE9E,GAAI,CAACzH,EAAQ2R,CAAU,EAAG,CACtBzT,EAAO,KAAK,sDAAuDyT,CAAU,EAC7E,MACJ,CAEA,MAAM3D,EAAS,CAAC,GAAGyD,GAAkBzR,EAAQ2R,CAAU,CAAC,CAAC,EAEzD,GAAI3D,EAAO,QAAU,GAAI,CACrBrP,EAAO,OAAO,UACVA,EAAO,IAAI,0EAA0E,GAClF,yCAAA,EAEP,MACJ,CAEAqP,EAAO,KAAKL,GAAiCK,EAAO,MAAM,CAAC,EAC3DhO,EAAQ2R,CAAU,EAAI,CAAE,GAAG3R,EAAQ2R,CAAU,EAAG,OAAA3D,CAAA,EAEhDrP,EAAO,MAAM,SAAS,CAClB,KAAM,CAAE,GAAG8I,EAAM,KAAM,mBAAoBzH,CAAA,EAC3C,WAAY,EAAA,CACf,EAED9B,EAAO,IAAI,mDAAmDyT,CAAU,YAAY3D,EAAO,MAAM,EAAE,CACvG,CAaO,SAAS4D,GACZvT,EACAX,EACI,CACJ,MAAMD,EAASY,EAAO,QAAU,CAAA,EAC1BsT,EAAa,OAAOlU,EAAO,YAAe,SAAW,SAASA,EAAO,WAAY,EAAE,EAAIA,EAAO,WAC9FoU,EAAa,OAAOpU,EAAO,YAAe,SAAW,SAASA,EAAO,WAAY,EAAE,EAAIA,EAAO,WAC9Fe,EAAQf,EAAO,MACfiB,EAAQjB,EAAO,MAErB,GAAIkU,IAAe,QAAaE,IAAe,QAAa,CAACrT,EAAO,CAChEN,EAAO,KAAK,uDAAuD,EACnE,MACJ,CAEA,MAAMS,EAAU,OAAe,OAC/B,GAAI,CAACA,GAAQ,MAAO,CAChBT,EAAO,KAAK,iEAAiE,EAC7E,MACJ,CAEA,MAAMuJ,EAAQ9I,EAAO,MAAM,SAAA,GAAc,CAAA,EACnCqB,EAA8B,CAAC,GAAIyH,EAAM,MAAM,oBAAsB,CAAA,CAAG,EAE9E,GAAI,CAACzH,EAAQ2R,CAAU,EAAG,CACtBzT,EAAO,KAAK,yDAA0DyT,CAAU,EAChF,MACJ,CAEA,MAAM3D,EAAS,CAAC,GAAGyD,GAAkBzR,EAAQ2R,CAAU,CAAC,CAAC,EAEzD,GAAI,CAAC3D,EAAO6D,CAAU,EAAG,CACrB3T,EAAO,KAAK,yDAA0D2T,CAAU,EAChF,MACJ,CAEA,GAAIrT,IAAU,OACV,GAAIE,GAAS,OAAOA,GAAU,UAAY,CAAC,MAAM,QAAQA,CAAK,EAC1DsP,EAAO6D,CAAU,EAAI,CAAE,GAAG7D,EAAO6D,CAAU,EAAG,KAAMnT,CAAA,MACjD,CACH,MAAMqQ,EAAgBrB,GAAA,EAChBsB,EAAehB,EAAO6D,CAAU,EAAE,KAClCR,EAAYrC,GAAgB,OAAOA,GAAiB,UAAY,CAAC,MAAM,QAAQA,CAAY,EAC3FA,EACAtD,EAAA,EACNsC,EAAO6D,CAAU,EAAI,CACjB,GAAG7D,EAAO6D,CAAU,EACpB,KAAM,CAAE,GAAGR,EAAU,CAACtC,CAAa,EAAGrQ,CAAA,CAAM,CAEpD,SACOF,IAAU,mBAAoB,CAErC,IAAIsT,EAAU,WAAW,OAAOpT,CAAK,CAAC,GAClC,MAAMoT,CAAO,GAAKA,EAAU,KAC5BA,EAAU,GAId9D,EAAO6D,CAAU,EAAI,CACjB,GAAG7D,EAAO6D,CAAU,EACpB,iBAAkBC,CAAA,CAE1B,SAAWtT,IAAU,aAEDE,IAAU,IAAQA,IAAU,OAExCsP,EAAO,QAAQ,CAACQ,EAAG/N,IAAM,CAAE+N,EAAE,WAAa/N,IAAMoR,CAAY,CAAC,EAE7D7D,EAAO6D,CAAU,EAAE,WAAa,WAE7BrT,IAAU,aAAeA,IAAU,oBAAqB,CAE/D,MAAM4F,EAAU1F,IAAU,IAAQA,IAAU,OAC5CsP,EAAO6D,CAAU,EAAI,CAAE,GAAG7D,EAAO6D,CAAU,EAAG,CAACrT,CAAK,EAAG4F,CAAA,CAC3D,MAEI4J,EAAO6D,CAAU,EAAI,CAAE,GAAG7D,EAAO6D,CAAU,EAAG,CAACrT,CAAK,EAAGE,CAAA,EAG3DsB,EAAQ2R,CAAU,EAAI,CAAE,GAAG3R,EAAQ2R,CAAU,EAAG,OAAA3D,CAAA,EAEhDrP,EAAO,MAAM,SAAS,CAClB,KAAM,CAAE,GAAG8I,EAAM,KAAM,mBAAoBzH,CAAA,EAC3C,WAAY,EAAA,CACf,EAED9B,EAAO,IAAI,+CAA+CyT,CAAU,UAAUE,CAAU,UAAUrT,CAAK,EAAE,CAC7G,CAQO,SAASuT,GACZ1T,EACAX,EACI,CACJ,MAAMD,EAASY,EAAO,QAAU,CAAA,EAC1BsT,EAAa,OAAOlU,EAAO,YAAe,SAAW,SAASA,EAAO,WAAY,EAAE,EAAIA,EAAO,WAC9FoU,EAAa,OAAOpU,EAAO,YAAe,SAAW,SAASA,EAAO,WAAY,EAAE,EAAIA,EAAO,WAEpG,GAAIkU,IAAe,QAAaE,IAAe,OAAW,CACtD3T,EAAO,KAAK,uDAAuD,EACnE,MACJ,CAEA,MAAMS,EAAU,OAAe,OAC/B,GAAI,CAACA,GAAQ,MAAO,CAChBT,EAAO,KAAK,iEAAiE,EAC7E,MACJ,CAEA,MAAMuJ,EAAQ9I,EAAO,MAAM,SAAA,GAAc,CAAA,EACnCqB,EAA8B,CAAC,GAAIyH,EAAM,MAAM,oBAAsB,CAAA,CAAG,EAE9E,GAAI,CAACzH,EAAQ2R,CAAU,EAAG,CACtBzT,EAAO,KAAK,yDAA0DyT,CAAU,EAChF,MACJ,CAEA,MAAM3D,EAAS,CAAC,GAAGyD,GAAkBzR,EAAQ2R,CAAU,CAAC,CAAC,EAGzD,GAAI3D,EAAO,QAAU,EAAG,CACpBrP,EAAO,OAAO,UACVA,EAAO,IAAI,yEAAyE,GACjF,4CAAA,EAEP,MACJ,CAEAqP,EAAO,OAAO6D,EAAY,CAAC,EAG3B7D,EAAO,QAAQ,CAACQ,EAAG/N,IAAM,CAAE+N,EAAE,WAAa/N,CAAG,CAAC,EACzCuN,EAAO,KAAMQ,GAAMA,EAAE,UAAU,IAChCR,EAAO,CAAC,EAAE,WAAa,IAG3BhO,EAAQ2R,CAAU,EAAI,CAAE,GAAG3R,EAAQ2R,CAAU,EAAG,OAAA3D,CAAA,EAEhDrP,EAAO,MAAM,SAAS,CAClB,KAAM,CAAE,GAAG8I,EAAM,KAAM,mBAAoBzH,CAAA,EAC3C,WAAY,EAAA,CACf,EAED9B,EAAO,IAAI,+CAA+C2T,CAAU,eAAeF,CAAU,EAAE,CACnG,CC1zCA,MAAMzT,EAAW,OAAe,QAAQ,eAAe,YAAY,GAAM,CACrE,IAAK,IAAIC,IAAoB,QAAQ,IAAI,eAAgB,GAAGA,CAAI,EAChE,KAAM,IAAIA,IAAoB,QAAQ,KAAK,eAAgB,GAAGA,CAAI,EAClE,MAAO,IAAIA,IAAoB,QAAQ,MAAM,eAAgB,GAAGA,CAAI,CACxE,EAgBM6T,GAAsB,CAAC,aAAc,YAAa,YAAa,YAAY,EAC3EC,GAAgB,GAAK,KAAO,KAC5BC,GAAa,GAYnB,eAAsBC,GAClB9T,EACAX,EACa,CAEb,MAAM0U,GADS/T,EAAO,QAAU,CAAA,GACX,MAErB,GAAI,CAAC+T,GAASA,EAAM,SAAW,EAAG,CAC9BlU,EAAO,KAAK,kCAAkC,EAC9C,MACJ,CAEA,MAAMS,EAAU,OAAe,OAC/B,GAAI,CAACA,GAAQ,OAAS,CAACA,GAAQ,IAAK,CAChCT,EAAO,KAAK,4DAA4D,EACxE,MACJ,CAGA,MAAMmU,GADQ1T,EAAO,MAAM,SAAA,GAAc,CAAA,GACG,MAAM,QAAU,CAAA,EACtD2T,EAAaJ,GAAaG,EAAc,OAE1CD,EAAM,OAASE,GACf3T,EAAO,OAAO,UACVA,EAAO,IAAI,4DAA6D,CAAE,IAAK2T,CAAA,CAAY,GACxF,qBAAqBA,CAAU,UAAA,EAI1C,MAAMC,EAAgB,MAAM,KAAKH,CAAK,EAAE,MAAM,EAAGE,CAAU,EAE3D,UAAWE,KAAQD,EAAe,CAE9B,GAAI,CAACP,GAAoB,SAASQ,EAAK,IAAI,EAAG,CAC1C7T,EAAO,OAAO,QACVA,EAAO,IAAI,qEAAsE,CAAE,KAAM6T,EAAK,IAAA,CAAM,GACjG,GAAGA,EAAK,IAAI,uBAAA,EAEnB,QACJ,CAEA,GAAIA,EAAK,KAAOP,GAAe,CAC3BtT,EAAO,OAAO,QACVA,EAAO,IAAI,gEAAiE,CAAE,KAAM6T,EAAK,IAAA,CAAM,GAC5F,GAAGA,EAAK,IAAI,iBAAA,EAEnB,QACJ,CAGA,MAAMC,EAAe9T,EAAO,MAAM,SAAA,GAAc,CAAA,EAChDA,EAAO,MAAM,SAAS,CAClB,GAAI,CACA,GAAG8T,EAAa,GAChB,eAAgB,CACZ,GAAIA,EAAa,IAAI,gBAAkB,CAAA,EACvC,CAACD,EAAK,IAAI,EAAG,CAAA,CACjB,CACJ,CACH,EAED,GAAI,CACA,MAAME,EAAW,IAAI,SACrBA,EAAS,OAAO,OAAQF,CAAI,EAC5BE,EAAS,OAAO,aAAc,UAAU,EAExC,MAAM7M,EAAW,MAAMlH,EAAO,IAAI,OAC9B,0BACA+T,EACA,CACI,WAAaC,GAAoB,CAC7B,MAAMC,EAAgBjU,EAAO,MAAM,SAAA,GAAc,CAAA,EACjDA,EAAO,MAAM,SAAS,CAClB,GAAI,CACA,GAAGiU,EAAc,GACjB,eAAgB,CACZ,GAAIA,EAAc,IAAI,gBAAkB,CAAA,EACxC,CAACJ,EAAK,IAAI,EAAGG,CAAA,CACjB,CACJ,CACH,CACL,CAAA,CACJ,EAIEE,EAAelU,EAAO,MAAM,SAAA,GAAc,CAAA,EAC1CmU,EAAiCD,EAAa,MAAM,QAAU,CAAA,EAC9DE,EAAyB,CAC3B,GAAGlN,EAAS,KACZ,aAAciN,EAAe,SAAW,EACxC,WAAYA,EAAe,MAAA,EAG/BnU,EAAO,MAAM,SAAS,CAClB,KAAM,CACF,GAAGkU,EAAa,KAChB,OAAQ,CAAC,GAAGC,EAAgBC,CAAQ,CAAA,EAExC,WAAY,EAAA,CACf,EAGD,MAAMC,EAAerU,EAAO,MAAM,SAAA,GAAc,CAAA,EAC1C,CAAE,CAAC6T,EAAK,IAAI,EAAG,EAAG,GAAGS,CAAA,EACvBD,EAAa,IAAI,gBAAkB,CAAA,EACvCrU,EAAO,MAAM,SAAS,CAClB,GAAI,CAAE,GAAGqU,EAAa,GAAI,eAAgBC,CAAA,CAAkB,CAC/D,EAED/U,EAAO,IAAI,2BAA2BsU,EAAK,IAAI,EAAE,CACrD,OAASxM,EAAO,CACZrH,EAAO,OAAO,QACVA,EAAO,IAAI,gEAAiE,CAAE,KAAM6T,EAAK,IAAA,CAAM,GAC5F,GAAGA,EAAK,IAAI,iBAAA,EAGnB,MAAMU,EAAavU,EAAO,MAAM,SAAA,GAAc,CAAA,EACxC,CAAE,CAAC6T,EAAK,IAAI,EAAGW,EAAG,GAAGF,CAAA,EACvBC,EAAW,IAAI,gBAAkB,CAAA,EACrCvU,EAAO,MAAM,SAAS,CAClB,GAAI,CAAE,GAAGuU,EAAW,GAAI,eAAgBD,CAAA,CAAkB,CAC7D,EAED/U,EAAO,MAAM,mCAAmCsU,EAAK,IAAI,IAAKxM,CAAK,CACvE,CACJ,CACJ,CAQO,SAASoN,GACZ/U,EACAX,EACI,CAEJ,MAAM+O,GADSpO,EAAO,QAAU,CAAA,GACX,MAErB,GAAIoO,IAAU,OAAW,CACrBvO,EAAO,KAAK,oCAAoC,EAChD,MACJ,CAEA,MAAMS,EAAU,OAAe,OAC/B,GAAI,CAACA,GAAQ,MAAO,CAChBT,EAAO,KAAK,kDAAkD,EAC9D,MACJ,CAEA,MAAMuJ,EAAQ9I,EAAO,MAAM,SAAA,GAAc,CAAA,EACnC0U,GAAU5L,EAAM,MAAM,QAAU,CAAA,GAAI,IACtC,CAAC6L,EAAmB,KAAe,CAC/B,GAAGA,EACH,aAAc,IAAM7G,CAAA,EACxB,EAGJ9N,EAAO,MAAM,SAAS,CAClB,KAAM,CAAE,GAAG8I,EAAM,KAAM,OAAA4L,CAAA,EACvB,WAAY,EAAA,CACf,EAEDnV,EAAO,IAAI,kDAAkDuO,CAAK,EAAE,CACxE,CAWO,SAAS8G,GACZlV,EACAX,EACI,CACJ,MAAMD,EAASY,EAAO,QAAU,CAAA,EAC1B0S,EAAWtT,EAAO,SAClBuT,EAAWvT,EAAO,SAExB,GAAIsT,IAAa,QAAaC,IAAa,OAAW,CAClD9S,EAAO,KAAK,oDAAoD,EAChE,MACJ,CAEA,MAAMS,EAAU,OAAe,OAC/B,GAAI,CAACA,GAAQ,MAAO,CAChBT,EAAO,KAAK,mDAAmD,EAC/D,MACJ,CAEA,MAAMuJ,EAAQ9I,EAAO,MAAM,SAAA,GAAc,CAAA,EACnC0U,EAAS,CAAC,GAAI5L,EAAM,MAAM,QAAU,CAAA,CAAG,EAGvC,CAAC+L,CAAU,EAAIH,EAAO,OAAOtC,EAAU,CAAC,EAC9CsC,EAAO,OAAOrC,EAAU,EAAGwC,CAAU,EAGrCH,EAAO,QAAQ,CAACC,EAAK7S,IAAM,CACvB6S,EAAI,WAAa7S,CACrB,CAAC,EAED9B,EAAO,MAAM,SAAS,CAClB,KAAM,CAAE,GAAG8I,EAAM,KAAM,OAAA4L,CAAA,EACvB,WAAY,EAAA,CACf,EAEDnV,EAAO,IAAI,oCAAoC6S,CAAQ,OAAOC,CAAQ,EAAE,CAC5E,CCzPA,MAAM9S,GAAW,OAAe,QAAQ,eAAe,kBAAkB,GAAM,CAC3E,IAAK,IAAIC,IAAoB,QAAQ,IAAI,qBAAsB,GAAGA,CAAI,EACtE,KAAM,IAAIA,IAAoB,QAAQ,KAAK,qBAAsB,GAAGA,CAAI,EACxE,MAAO,IAAIA,IAAoB,QAAQ,MAAM,qBAAsB,GAAGA,CAAI,CAC9E,EAcO,SAASsV,GACZpV,EACAX,EACI,CACJ,MAAMD,EAASY,EAAO,QAAU,CAAA,EAC1B0D,EAAStE,EAAO,OAChBiB,EAAQjB,EAAO,MAErB,GAAI,CAACsE,EAAQ,CACT7D,GAAO,KAAK,0CAA0C,EACtD,MACJ,CAEA,MAAMS,EAAU,OAAe,OAC/B,GAAI,CAACA,GAAQ,MAAO,CAChBT,GAAO,KAAK,uDAAuD,EACnE,MACJ,CAEA,MAAMuJ,EAAQ9I,EAAO,MAAM,SAAA,GAAc,CAAA,EAEzCA,EAAO,MAAM,SAAS,CAClB,KAAM,CACF,GAAG8I,EAAM,KACT,YAAa,CACT,GAAGA,EAAM,MAAM,YACf,CAAC1F,CAAM,EAAGrD,CAAA,CACd,EAEJ,WAAY,EAAA,CACf,CACL,CCjDA,MAAMR,EAAW,OAAe,QAAQ,eAAe,aAAa,GAAM,CACtE,IAAK,IAAIC,IAAoB,QAAQ,IAAI,gBAAiB,GAAGA,CAAI,EACjE,KAAM,IAAIA,IAAoB,QAAQ,KAAK,gBAAiB,GAAGA,CAAI,EACnE,MAAO,IAAIA,IAAoB,QAAQ,MAAM,gBAAiB,GAAGA,CAAI,CACzE,EASA,SAASuV,GAAwBC,EAAe,GAA4B,CAExE,MAAMC,EADU,OAAe,QACY,QAAQ,eAAiB,CAAC,KAAM,IAAI,EACzE7V,EAAiC,CAAA,EACvC,UAAWgE,KAAU6R,EACjB7V,EAAOgE,CAAM,EAAI4R,EAErB,OAAO5V,CACX,CAkCO,SAAS8V,GACZxV,EACAC,EACI,CAEJ,MAAMwV,GADSzV,EAAO,QAAU,CAAA,GACN,WAEpBM,EAAU,OAAe,OAC/B,GAAI,CAACA,GAAQ,MAAO,CAChBT,EAAO,KAAK,iEAAiE,EAC7E,MACJ,CAGA,MAAM6V,GADQpV,EAAO,MAAM,SAAA,GAAc,CAAA,GACC,MAAM,cAAgB,CAAA,EAC1DqV,EAAmBD,EAAc,OAAS,GAAKA,EAAc,KAAKlD,GACjD,OAAO,OAAOA,EAAK,SAAW,CAAA,CAAE,EAAE,KAAKrC,GAAKA,GAAKA,EAAE,KAAA,IAAW,EAAE,CAEtF,EAGD,GAAI,CAACsF,GAAc,CAACE,EAAkB,CAClCC,GAA4B5V,CAAe,EAC3C,MACJ,CAGAM,EAAO,MAAM,UAAU,CACnB,wBAAyBmV,CAAA,CAC5B,EACDnV,EAAO,OAAO,OAAO,+BAA+B,EAEpDT,EAAO,IAAI,qEAAqE4V,CAAU,EAAE,CAChG,CAQO,SAASG,GACZ5V,EACAX,EACI,CAEJ,MAAMoW,GADSzV,EAAO,QAAU,CAAA,GACN,WAEpBM,EAAU,OAAe,OAC/B,GAAI,CAACA,GAAQ,MAAO,CAChBT,EAAO,KAAK,0DAA0D,EACtE,MACJ,CAEA,MAAMuJ,EAAQ9I,EAAO,MAAM,SAAA,GAAc,CAAA,EAEnCuV,EAASvV,EAAO,YAAY,MAAM,kBAAkB,EACpDwV,EAA8BD,GAAQ,MAAM,MAAQA,GAAQ,MAAQ,CAAA,EAE1E,GAAI,CAACJ,EAAY,CACbnV,EAAO,MAAM,SAAS,CAClB,KAAM,CACF,GAAG8I,EAAM,KACT,aAAc,CAAA,CAAC,EAEnB,GAAI,CACA,GAAGA,EAAM,GACT,yBAA0B,IAAA,EAE9B,WAAY,EAAA,CACf,EACD,MACJ,CAEA,MAAM2M,EAAWD,EAAU,KAAME,GAAMA,EAAE,KAAOP,CAAU,EAC1D,GAAI,CAACM,EAAU,CACXlW,EAAO,KAAK,8CAA8C4V,CAAU,EAAE,EACtE,MACJ,CAGA,MAAMC,EAA8BtM,EAAM,MAAM,cAAgB,CAAA,EAC1D6M,GAA0BF,EAAS,QAAU,CAAA,GAAI,IAAI,CAAC5V,EAAOiO,IAAU,CAEzE,MAAM8H,EAAW,KAAK,UAAU/V,EAAM,IAAI,EACpCyR,EAAW8D,EAAc,KAAMzU,GAAM,KAAK,UAAUA,EAAE,IAAI,IAAMiV,CAAQ,EAC9E,MAAO,CACH,IAAK,SAAS9H,CAAK,GACnB,KAAMjO,EAAM,KACZ,QAASyR,GAAU,SAAWzR,EAAM,SAAWkV,GAAA,EAC/C,WAAYjH,EACZ,UAAW,EAAA,CAEnB,CAAC,EAED9N,EAAO,MAAM,SAAS,CAClB,KAAM,CACF,GAAG8I,EAAM,KACT,aAAc6M,CAAA,EAElB,GAAI,CACA,GAAG7M,EAAM,GACT,yBAA0BqM,CAAA,EAE9B,WAAY,EAAA,CACf,EAED5V,EAAO,IAAI,6CAA6C4V,CAAU,EAAE,CACxE,CAQO,SAASU,GACZnW,EACAX,EACI,CACJ,MAAMD,EAASY,EAAO,QAAU,CAAA,EAC1BoO,EAAQhP,EAAO,MACfsE,EAAStE,EAAO,OAChBiB,EAAQjB,EAAO,MAErB,GAAIgP,IAAU,QAAa,CAAC1K,EAAQ,CAChC7D,EAAO,KAAK,4CAA4C,EACxD,MACJ,CAEA,MAAMS,EAAU,OAAe,OAC/B,GAAI,CAACA,GAAQ,MAAO,CAChBT,EAAO,KAAK,sDAAsD,EAClE,MACJ,CAEA,MAAMuJ,EAAQ9I,EAAO,MAAM,SAAA,GAAc,CAAA,EACnC8V,EAAQ,CAAC,GAAIhN,EAAM,MAAM,cAAgB,CAAA,CAAG,EAElDgN,EAAMhI,CAAK,EAAI,CACX,GAAGgI,EAAMhI,CAAK,EACd,QAAS,CACL,GAAGgI,EAAMhI,CAAK,EAAE,QAChB,CAAC1K,CAAM,EAAGrD,CAAA,CACd,EAGJC,EAAO,MAAM,SAAS,CAClB,KAAM,CAAE,GAAG8I,EAAM,KAAM,aAAcgN,CAAA,EACrC,WAAY,EAAA,CACf,CACL,CAQO,SAASC,GACZrW,EACAX,EACI,CAEJ,MAAM+O,GADSpO,EAAO,QAAU,CAAA,GACX,MAErB,GAAIoO,IAAU,OAAW,CACrBvO,EAAO,KAAK,wCAAwC,EACpD,MACJ,CAEA,MAAMS,EAAU,OAAe,OAC/B,GAAI,CAACA,GAAQ,MAAO,CAChBT,EAAO,KAAK,sDAAsD,EAClE,MACJ,CAEA,MAAMuJ,EAAQ9I,EAAO,MAAM,SAAA,GAAc,CAAA,EACnC8V,EAAQ,CAAC,GAAIhN,EAAM,MAAM,cAAgB,CAAA,CAAG,EAClDgN,EAAM,OAAOhI,EAAO,CAAC,EAGrBgI,EAAM,QAAQ,CAAC5D,EAAM,IAAM,CACvBA,EAAK,WAAa,CACtB,CAAC,EAEDlS,EAAO,MAAM,SAAS,CAClB,KAAM,CAAE,GAAG8I,EAAM,KAAM,aAAcgN,CAAA,EACrC,WAAY,EAAA,CACf,EAEDvW,EAAO,IAAI,mDAAmDuO,CAAK,EAAE,CACzE,CAQO,SAASkI,GACZtW,EACAX,EACI,CACJ,MAAMD,EAASY,EAAO,QAAU,CAAA,EAC1B0S,EAAWtT,EAAO,SAClBuT,EAAWvT,EAAO,SAExB,GAAIsT,IAAa,QAAaC,IAAa,OAAW,CAClD9S,EAAO,KAAK,yDAAyD,EACrE,MACJ,CAEA,MAAMS,EAAU,OAAe,OAC/B,GAAI,CAACA,GAAQ,MAAO,CAChBT,EAAO,KAAK,wDAAwD,EACpE,MACJ,CAEA,MAAMuJ,EAAQ9I,EAAO,MAAM,SAAA,GAAc,CAAA,EACnC8V,EAAQ,CAAC,GAAIhN,EAAM,MAAM,cAAgB,CAAA,CAAG,EAE5C,CAACwJ,CAAK,EAAIwD,EAAM,OAAO1D,EAAU,CAAC,EACxC0D,EAAM,OAAOzD,EAAU,EAAGC,CAAK,EAE/BwD,EAAM,QAAQ,CAAC5D,EAAMpQ,IAAM,CACvBoQ,EAAK,WAAapQ,CACtB,CAAC,EAED9B,EAAO,MAAM,SAAS,CAClB,KAAM,CAAE,GAAG8I,EAAM,KAAM,aAAcgN,CAAA,EACrC,WAAY,EAAA,CACf,EAEDvW,EAAO,IAAI,wCAAwC6S,CAAQ,OAAOC,CAAQ,EAAE,CAChF,CAQO,SAAS4D,GACZvW,EACAX,EACI,CAEJ,MAAMgB,GADSL,EAAO,QAAU,CAAA,GACX,MAEfM,EAAU,OAAe,OAC/B,GAAI,CAACA,GAAQ,MAAO,CAChBT,EAAO,KAAK,yDAAyD,EACrE,MACJ,CAEA,MAAMuJ,EAAQ9I,EAAO,MAAM,SAAA,GAAc,CAAA,EACnCoD,EAAS0F,EAAM,IAAI,cAAgB,KAEnCgN,GAAShN,EAAM,MAAM,cAAgB,IAAI,IAAKoJ,IAAsB,CACtE,GAAGA,EACH,QAAS,CACL,GAAGA,EAAK,QACR,CAAC9O,CAAM,EAAG8O,EAAK,UAAU9O,CAAM,GAAKrD,CAAA,CACxC,EACF,EAEFC,EAAO,MAAM,SAAS,CAClB,KAAM,CAAE,GAAG8I,EAAM,KAAM,aAAcgN,CAAA,EACrC,WAAY,EAAA,CACf,EAED9V,EAAO,OAAO,UACVA,EAAO,IAAI,wDAAwD,GAChE,gCAAA,CAEX,CAQO,SAASkW,GACZxW,EACAX,EACI,CAEJ,MAAMoX,GADSzW,EAAO,QAAU,CAAA,GACZ,KAEpB,GAAI,CAACyW,EAAM,CACP5W,EAAO,KAAK,uCAAuC,EACnD,MACJ,CAEA,MAAMS,EAAU,OAAe,OAC/B,GAAI,CAACA,GAAQ,MAAO,CAChBT,EAAO,KAAK,sDAAsD,EAClE,MACJ,CAEA,MAAMuJ,EAAQ9I,EAAO,MAAM,SAAA,GAAc,CAAA,EAEzCA,EAAO,MAAM,SAAS,CAClB,GAAI,CACA,GAAG8I,EAAM,GACT,WAAYqN,CAAA,CAChB,CACH,EAED5W,EAAO,IAAI,kCAAkC4W,CAAI,OAAO,CAC5D,CAQO,SAASC,GACZ1W,EACAX,EACI,CACJ,MAAMD,EAASY,EAAO,QAAU,CAAA,EAC1B0D,EAAStE,EAAO,OAChBiB,EAAQjB,EAAO,MAErB,GAAI,CAACsE,EAAQ,CACT7D,EAAO,KAAK,8CAA8C,EAC1D,MACJ,CAEA,MAAMS,EAAU,OAAe,OAC/B,GAAI,CAACA,GAAQ,MAAO,CAChBT,EAAO,KAAK,2DAA2D,EACvE,MACJ,CAEA,MAAMuJ,EAAQ9I,EAAO,MAAM,SAAA,GAAc,CAAA,EAEzCA,EAAO,MAAM,SAAS,CAClB,KAAM,CACF,GAAG8I,EAAM,KACT,kBAAmB,CACf,GAAGA,EAAM,MAAM,kBACf,CAAC1F,CAAM,EAAGrD,CAAA,CACd,CACJ,CACH,CACL,CAQO,SAASsW,GACZlN,EACApK,EACI,CACJ,MAAMiB,EAAU,OAAe,OAC/B,GAAI,CAACA,GAAQ,MAAO,CAChBT,EAAO,KAAK,mDAAmD,EAC/D,MACJ,CAEA,MAAMuJ,EAAQ9I,EAAO,MAAM,SAAA,GAAc,CAAA,EACnC8V,EAAsBhN,EAAM,MAAM,cAAgB,CAAA,EAElDiJ,EAAsB,CACxB,IAAK,UAAU,KAAK,IAAA,CAAK,GACzB,KAAMgD,GAAA,EACN,QAASA,GAAA,EACT,WAAYe,EAAM,OAClB,UAAW,EAAA,EAGf9V,EAAO,MAAM,SAAS,CAClB,KAAM,CACF,GAAG8I,EAAM,KACT,aAAc,CAAC,GAAGgN,EAAO/D,CAAO,CAAA,EAEpC,WAAY,EAAA,CACf,EAEDxS,EAAO,IAAI,iDAAiDuW,EAAM,OAAS,CAAC,EAAE,CAClF,CAQO,SAASQ,GACZ5W,EACAX,EACI,CACJ,MAAMD,EAASY,EAAO,QAAU,CAAA,EAC1BoO,EAAQhP,EAAO,MACfsE,EAAStE,EAAO,OAChBiB,EAAQjB,EAAO,MAErB,GAAIgP,IAAU,QAAa,CAAC1K,EAAQ,CAChC7D,EAAO,KAAK,gDAAgD,EAC5D,MACJ,CAEA,MAAMS,EAAU,OAAe,OAC/B,GAAI,CAACA,GAAQ,MAAO,CAChBT,EAAO,KAAK,0DAA0D,EACtE,MACJ,CAEA,MAAMuJ,EAAQ9I,EAAO,MAAM,SAAA,GAAc,CAAA,EACnC8V,EAAQ,CAAC,GAAIhN,EAAM,MAAM,cAAgB,CAAA,CAAG,EAElDgN,EAAMhI,CAAK,EAAI,CACX,GAAGgI,EAAMhI,CAAK,EACd,KAAM,CACF,GAAGgI,EAAMhI,CAAK,EAAE,KAChB,CAAC1K,CAAM,EAAGrD,CAAA,CACd,EAGJC,EAAO,MAAM,SAAS,CAClB,KAAM,CAAE,GAAG8I,EAAM,KAAM,aAAcgN,CAAA,EACrC,WAAY,EAAA,CACf,CACL,CAQO,SAASS,GACZpN,EACApK,EACI,CACJ,MAAMiB,EAAU,OAAe,OAC/B,GAAI,CAACA,GAAQ,MAAO,CAChBT,EAAO,KAAK,0DAA0D,EACtE,MACJ,CAMA,KAJcS,EAAO,MAAM,SAAA,GAAc,CAAA,GAGP,MAAM,cAAgB,CAAA,GAC9C,SAAW,EAAG,CACpBA,EAAO,OAAO,UACVA,EAAO,IAAI,0DAA0D,GAClE,mBAAA,EAEP,MACJ,CAGAA,EAAO,MAAM,MAAM,CACf,sBAAuB,CAAE,KAAM+U,GAAA,EAA2B,YAAa,KAAM,WAAY,EAAA,CAAM,CAClG,EAGD/U,EAAO,WAAW,CAAE,QAAS,YAAa,OAAQ,sBAAuB,CAC7E,CAMA,MAAMwW,GAAkD,CACpD,GAAI,SACJ,GAAI,wBACR,EASO,SAASC,GACZ/W,EACAX,EACI,CACJ,MAAMiB,EAAU,OAAe,OAC/B,GAAI,CAACA,GAAQ,MAAO,CAChBT,EAAO,KAAK,2EAA2E,EACvF,MACJ,CAKA,MAAMmX,IAFShX,EAAO,QAAU,CAAA,GACZ,MAAQ,CAAA,GACR,QAAU,CAAA,EAE9B,GAAIgX,EAAO,SAAW,EAAG,CACrB1W,EAAO,OAAO,UACVA,EAAO,IAAI,8EAA8E,GAAK,eAAA,EAElG,MACJ,CAGA,MAAMiV,EAA6BjV,EAAO,QAAQ,eAAiB,CAAC,KAAM,IAAI,EAGxE2W,EAA2C,CAAA,EACjD,UAAWvT,KAAU6R,EACjB0B,EAAiBvT,CAAM,EAAIoT,GAAwBpT,CAAM,GAAKoT,GAAwB,IAAS,yBAKnG,MAAMI,EAAgBF,EAAO,IAAK7W,IAA8E,CAC5G,GAAGA,EACH,QAAS,CAAE,GAAG8W,CAAA,CAAiB,EACjC,EAIF3W,EAAO,MAAM,SAAS,CAClB,cAAe4W,CAAA,CAClB,EAED5W,EAAO,OAAO,UACVA,EAAO,IAAI,2FAA2F,GAAK,2BAAA,EAE/GT,EAAO,IAAI,gFAAgF,CAC/F,CASO,SAASsX,GACZnX,EACAX,EACI,CACJ,MAAMiB,EAAU,OAAe,OAC/B,GAAI,CAACA,GAAQ,MAAO,CAChBT,EAAO,KAAK,wEAAwE,EACpF,MACJ,CAKA,MAAMuX,IAFSpX,EAAO,QAAU,CAAA,GACZ,MAAQ,CAAA,GACH,cAAgB,CAAA,EAEzC,GAAIoX,EAAY,SAAW,EAAG,CAC1B9W,EAAO,OAAO,UACVA,EAAO,IAAI,oEAAoE,GAAK,eAAA,EAExF,MACJ,CAGA,MAAMiV,EAA6BjV,EAAO,QAAQ,eAAiB,CAAC,KAAM,IAAI,EAGxE2W,EAA2C,CAAA,EACjD,UAAWvT,KAAU6R,EACjB0B,EAAiBvT,CAAM,EAAIoT,GAAwBpT,CAAM,GAAKoT,GAAwB,IAAS,yBAKnG,MAAMO,EAAeD,EAAY,IAAK5E,IAAsB,CACxD,GAAGA,EACH,QAAS,CAAE,GAAGyE,CAAA,CAAiB,EACjC,EAGF3W,EAAO,MAAM,SAAS,CAClB,oBAAqB+W,CAAA,CACxB,EAED/W,EAAO,OAAO,UACVA,EAAO,IAAI,iFAAiF,GAAK,2BAAA,EAErGT,EAAO,IAAI,mFAAmF,CAClG,CAQA,eAAsByX,GAClB7N,EACApK,EACa,CACb,MAAMiB,EAAU,OAAe,OAC/B,GAAI,CAACA,GAAQ,OAAS,CAACA,GAAQ,IAAK,CAChCT,EAAO,KAAK,yEAAyE,EACrF,MACJ,CAEA,MAAMuJ,EAAQ9I,EAAO,MAAM,SAAA,GAAc,CAAA,EAEnCiX,EAAmBjX,EAAO,MAAM,SAAS,IAAI,kBAAoB,CAAA,EACjEkX,EAAeD,EAAiB,KAEtC,GAAI,CAACC,GAAc,IAAI,OAAQ,CAC3BlX,EAAO,OAAO,UACVA,EAAO,IAAI,wEAAwE,GAChF,6BAAA,EAEP,MACJ,CAEA,GAAI,CAEA,MAAM8V,EAAsBhN,EAAM,MAAM,cAAgB,CAAA,EAClD5B,EAAW,MAAMlH,EAAO,IAAI,KAC9B,iEACA,CACI,KAAMkX,EACN,UAAWD,EAAiB,YAAc,GAC1C,OAAQnB,EAAM,IAAK5D,IAAU,CACzB,KAAMA,EAAK,KACX,QAASA,EAAK,OAAA,EAChB,CAAA,CACN,EAIJlS,EAAO,MAAM,MAAM,CACf,sBAAuB,CAAE,KAAM+U,GAAA,EAA2B,YAAa,KAAM,WAAY,EAAA,CAAM,CAClG,EAED/U,EAAO,MAAM,SAAS,CAClB,GAAI,CACA,GAAG8I,EAAM,GACT,WAAY,UAAA,EAEhB,WAAY,EAAA,CACf,EAGD9I,EAAO,WAAW,CAAE,QAAS,YAAA,CAAc,EAE3CA,EAAO,OAAO,UACVA,EAAO,IAAI,gEAAgE,GACxE,0BAAA,EAIPA,EAAO,aAAa,UAAU,kBAAkB,EAEhDT,EAAO,IAAI,+CAA+C2H,EAAS,KAAK,EAAE,EAAE,CAChF,OAASG,EAAO,CACZrH,EAAO,OAAO,QACVA,EAAO,IAAI,qEAAqE,GAC7E,0BAAA,EAEPT,EAAO,MAAM,uDAAwD8H,CAAK,CAC9E,CACJ,CCltBA,MAAM9H,EAAW,OAAe,QAAQ,eAAe,WAAW,GAAM,CACpE,IAAK,IAAIC,IAAoB,QAAQ,IAAI,cAAe,GAAGA,CAAI,EAC/D,KAAM,IAAIA,IAAoB,QAAQ,KAAK,cAAe,GAAGA,CAAI,EACjE,MAAO,IAAIA,IAAoB,QAAQ,MAAM,cAAe,GAAGA,CAAI,CACvE,EAgCO,SAAS2X,GACZzX,EACAX,EACI,CAEJ,MAAMqY,GADS1X,EAAO,QAAU,CAAA,GACT,QAEvB,GAAI,CAAC0X,EAAS,CACV7X,EAAO,KAAK,qCAAqC,EACjD,MACJ,CAEA,MAAMS,EAAU,OAAe,OAC/B,GAAI,CAACA,GAAQ,MAAO,CAChBT,EAAO,KAAK,iDAAiD,EAC7D,MACJ,CAEA,MAAMuJ,EAAQ9I,EAAO,MAAM,SAAA,GAAc,CAAA,EACnCqX,EAAmBvO,EAAM,MAAM,QAAU,CAAA,EAEzCwO,EAAYD,EAAO,SAASD,CAAO,EACnCC,EAAO,OAAQpR,GAAOA,IAAOmR,CAAO,EACpC,CAAC,GAAGC,EAAQD,CAAO,EAEzBpX,EAAO,MAAM,SAAS,CAClB,KAAM,CAAE,GAAG8I,EAAM,KAAM,OAAQwO,CAAA,EAC/B,WAAY,EAAA,CACf,EAED/X,EAAO,IAAI,+BAA+B6X,CAAO,qBAAqBE,EAAU,MAAM,EAAE,CAC5F,CAQA,eAAsBC,GAClBpO,EACApK,EACa,CACb,MAAMiB,EAAU,OAAe,OAC/B,GAAI,CAACA,GAAQ,OAAS,CAACA,GAAQ,IAAK,CAChCT,EAAO,KAAK,mEAAmE,EAC/E,MACJ,CAEA,GAAI,CACA,MAAM2H,EAAW,MAAMlH,EAAO,IAAI,KAC9B,6DAAA,EAGE8I,EAAQ9I,EAAO,MAAM,SAAA,GAAc,CAAA,EACzCA,EAAO,MAAM,SAAS,CAClB,KAAM,CAAE,GAAG8I,EAAM,KAAM,aAAc5B,EAAS,KAAK,YAAA,CAAa,CACnE,EAED3H,EAAO,IAAI,oCAAoC2H,EAAS,KAAK,YAAY,EAAE,CAC/E,OAASG,EAAO,CACZrH,EAAO,OAAO,QACVA,EAAO,IAAI,8DAA8D,GACtE,kCAAA,EAEPT,EAAO,MAAM,gCAAiC8H,CAAK,CACvD,CACJ,CASO,SAASmQ,GACZ9X,EACAX,EACM,CACN,MAAMD,EAASY,EAAO,QAAU,CAAA,EAC1B+X,EAAW3Y,EAAO,SAClBe,EAAQf,EAAO,MACf4Y,EAAW5Y,EAAO,SAExB,GAAI,CAAC2Y,GAAY,CAAC5X,GAAS,CAAC6X,EACxB,MAAO,IAGX,MAAMC,EAASD,EAAS,KAAM1Q,GAAMA,EAAE,KAAOyQ,CAAQ,EACrD,OAAKE,EACEA,EAAO9X,CAAK,GAAK,IADJ,GAExB,CASO,SAAS+X,GACZlY,EACAX,EACM,CACN,MAAMD,EAASY,EAAO,QAAU,CAAA,EAC1ByV,EAAarW,EAAO,WACpB0W,EAAY1W,EAAO,UAEzB,GAAI,CAACqW,GAAc,CAACK,EAChB,MAAO,GAIX,MAAMpS,EADU,OAAe,QACR,QAAQ,UAAA,GAAe,KAExCqS,EAAWD,EAAU,KAAME,GAAMA,EAAE,KAAOP,CAAU,EAC1D,OAAOM,GAAU,UAAUrS,CAAM,GAAKqS,GAAU,SAAS,IAAM,EACnE,CAQO,SAASoC,GACZnY,EACAX,EACI,CACJ,MAAMD,EAASY,EAAO,QAAU,CAAA,EAC1BoY,EAAWhZ,EAAO,SAClBe,EAAQf,EAAO,MACfiB,EAAQjB,EAAO,MAErB,GAAI,CAACgZ,GAAY,CAACjY,EAAO,CACrBN,EAAO,KAAK,qDAAqD,EACjE,MACJ,CAEA,MAAMS,EAAU,OAAe,OAC/B,GAAI,CAACA,GAAQ,MAAO,CAChBT,EAAO,KAAK,+DAA+D,EAC3E,MACJ,CAEA,MAAMuJ,EAAQ9I,EAAO,MAAM,SAAA,GAAc,CAAA,EACnC+X,EAAejP,EAAM,MAAM,uBAAyB,CACtD,MAAO,CAAE,QAAS,GAAO,cAAe,QAAA,EACxC,KAAM,CAAE,QAAS,GAAO,cAAe,QAAA,EACvC,SAAU,CAAE,QAAS,GAAO,cAAe,QAAA,EAC3C,OAAQ,CAAE,QAAS,GAAO,cAAe,QAAA,CAAS,EAGtD9I,EAAO,MAAM,SAAS,CAClB,KAAM,CACF,GAAG8I,EAAM,KACT,sBAAuB,CACnB,GAAGiP,EACH,CAACD,CAAQ,EAAG,CACR,GAAGC,EAAaD,CAAQ,EACxB,CAACjY,CAAK,EAAGE,CAAA,CACb,CACJ,EAEJ,WAAY,EAAA,CACf,EAEDR,EAAO,IAAI,uCAAuCuY,CAAQ,IAAIjY,CAAK,OAAOE,CAAK,EAAE,CACrF,CAQO,SAASiY,GACZtY,EACAX,EACI,CAEJ,MAAMkZ,GADSvY,EAAO,QAAU,CAAA,GACZ,KAEpB,GAAI,CAACuY,EAAM,CACP1Y,EAAO,KAAK,yCAAyC,EACrD,MACJ,CAEA,MAAMS,EAAU,OAAe,OAC/B,GAAI,CAACA,GAAQ,MAAO,CAChBT,EAAO,KAAK,wDAAwD,EACpE,MACJ,CAEA,MAAMuJ,EAAQ9I,EAAO,MAAM,SAAA,GAAc,CAAA,EAGzC,IAAIkY,EAAa,EACbC,EAAmB,EAEnBF,IAAS,aACTC,EAAa,IACND,IAAS,mBAChBE,EAAmB,KAGvBnY,EAAO,MAAM,SAAS,CAClB,KAAM,CACF,GAAG8I,EAAM,KACT,cAAemP,EACf,aAAcA,IAAS,aAAe,EAAKnP,EAAM,MAAM,cAAgBoP,EACvE,wBACID,IAAS,iBACFnP,EAAM,MAAM,yBAA2BqP,EACxC,CAAA,EAEd,WAAY,EAAA,CACf,EAED5Y,EAAO,IAAI,iDAAiD0Y,CAAI,EAAE,CACtE,CAQO,SAASG,GACZ1Y,EACAX,EACI,CACJ,MAAMD,EAASY,EAAO,QAAU,CAAA,EAC1BG,EAAQf,EAAO,MACfiB,EAAQjB,EAAO,MAErB,GAAI,CAACe,EAAO,CACRN,EAAO,KAAK,gDAAgD,EAC5D,MACJ,CAEA,MAAMS,EAAU,OAAe,OAC/B,GAAI,CAACA,GAAQ,MAAO,CAChBT,EAAO,KAAK,8DAA8D,EAC1E,MACJ,CAEA,MAAMuJ,EAAQ9I,EAAO,MAAM,SAAA,GAAc,CAAA,EAEzCA,EAAO,MAAM,SAAS,CAClB,KAAM,CACF,GAAG8I,EAAM,KACT,CAACjJ,CAAK,EAAGE,CAAA,EAEb,WAAY,EAAA,CACf,CACL,CAQO,SAASsY,GACZ3Y,EACAX,EACI,CACJ,MAAMD,EAASY,EAAO,QAAU,CAAA,EAC1B0X,EAAUtY,EAAO,QACjBwZ,EAAYxZ,EAAO,UAEzB,GAAI,CAACsY,EAAS,CACV7X,EAAO,KAAK,8CAA8C,EAC1D,MACJ,CAEA,MAAMS,EAAU,OAAe,OAC/B,GAAI,CAACA,GAAQ,MAAO,CAChBT,EAAO,KAAK,0DAA0D,EACtE,MACJ,CAEA,MAAMuJ,EAAQ9I,EAAO,MAAM,SAAA,GAAc,CAAA,EAEnCuY,GAD4BzP,EAAM,MAAM,QAAU,CAAA,GAC1B,KAAM,GAAM,EAAE,KAAOsO,CAAO,EAE1DpX,EAAO,MAAM,SAAS,CAClB,GAAI,CACA,GAAG8I,EAAM,GACT,qBAAsB,GACtB,qBAAsB,CAClB,QAAAsO,EACA,UAAAkB,EACA,UAAWC,GAAgB,YAAc,KACzC,QAASA,GAAgB,UAAY,IAAA,CACzC,CACJ,CACH,CACL,CAQO,SAASC,GACZ9Y,EACAX,EACI,CACJ,MAAMD,EAASY,EAAO,QAAU,CAAA,EAC1B0X,EAAUtY,EAAO,QACjB+F,EAAY/F,EAAO,UACnB8F,EAAU9F,EAAO,QAEvB,GAAI,CAACsY,EAAS,CACV7X,EAAO,KAAK,yCAAyC,EACrD,MACJ,CAEA,MAAMS,EAAU,OAAe,OAC/B,GAAI,CAACA,GAAQ,MAAO,CAChBT,EAAO,KAAK,qDAAqD,EACjE,MACJ,CAEA,MAAMuJ,EAAQ9I,EAAO,MAAM,SAAA,GAAc,CAAA,EACnCqX,EAA4B,CAAC,GAAIvO,EAAM,MAAM,QAAU,CAAA,CAAG,EAE1D2P,EAAgBpB,EAAO,UAAW9T,GAAMA,EAAE,KAAO6T,CAAO,EAE1DqB,GAAiB,EACjBpB,EAAOoB,CAAa,EAAI,CACpB,GAAGpB,EAAOoB,CAAa,EACvB,WAAY5T,EACZ,SAAUD,CAAA,EAGdyS,EAAO,KAAK,CACR,GAAID,EACJ,WAAYvS,EACZ,SAAUD,CAAA,CACb,EAGL5E,EAAO,MAAM,SAAS,CAClB,KAAM,CAAE,GAAG8I,EAAM,KAAM,OAAAuO,CAAA,EACvB,GAAI,CACA,GAAGvO,EAAM,GACT,qBAAsB,GACtB,qBAAsB,IAAA,EAE1B,WAAY,EAAA,CACf,EAED9I,EAAO,OAAO,UACVA,EAAO,IAAI,6DAA6D,GACrE,8BAAA,EAEPT,EAAO,IAAI,4CAA4C6X,CAAO,EAAE,CACpE,CAQO,SAASsB,GACZhZ,EACAX,EACI,CAEJ,MAAMqY,GADS1X,EAAO,QAAU,CAAA,GACT,QAEvB,GAAI,CAAC0X,EAAS,CACV7X,EAAO,KAAK,2CAA2C,EACvD,MACJ,CAEA,MAAMS,EAAU,OAAe,OAC/B,GAAI,CAACA,GAAQ,MAAO,CAChBT,EAAO,KAAK,uDAAuD,EACnE,MACJ,CAEA,MAAMuJ,EAAQ9I,EAAO,MAAM,SAAA,GAAc,CAAA,EACnCqX,EAA4B,CAAC,GAAIvO,EAAM,MAAM,QAAU,CAAA,CAAG,EAE1D2P,EAAgBpB,EAAO,UAAW9T,GAAMA,EAAE,KAAO6T,CAAO,EAE1DqB,GAAiB,IACjBpB,EAAOoB,CAAa,EAAI,CACpB,GAAGpB,EAAOoB,CAAa,EACvB,WAAY,KACZ,SAAU,IAAA,GAIlBzY,EAAO,MAAM,SAAS,CAClB,KAAM,CAAE,GAAG8I,EAAM,KAAM,OAAAuO,CAAA,EACvB,WAAY,EAAA,CACf,EAEDrX,EAAO,OAAO,UACVA,EAAO,IAAI,+DAA+D,GACvE,gCAAA,EAEPT,EAAO,IAAI,gDAAgD6X,CAAO,EAAE,CACxE,CAQO,SAASuB,GACZjZ,EACAX,EACI,CAEJ,MAAM6Z,GADSlZ,EAAO,QAAU,CAAA,GACZ,KAEpB,GAAI,CAACkZ,EAAM,CACPrZ,EAAO,KAAK,4CAA4C,EACxD,MACJ,CAEA,MAAMS,EAAU,OAAe,OAC/B,GAAI,CAACA,GAAQ,MAAO,CAChBT,EAAO,KAAK,2DAA2D,EACvE,MACJ,CAEA,MAAMuJ,EAAQ9I,EAAO,MAAM,SAAA,GAAc,CAAA,EAEzCA,EAAO,MAAM,SAAS,CAClB,GAAI,CACA,GAAG8I,EAAM,GACT,gBAAiB8P,CAAA,CACrB,CACH,EAGD5Y,EAAO,aAAa,UAAU,gBAAiB,CAC3C,OAAQ,CACJ,KAAM4Y,IAAS,SAAW,OAAS,MACnC,SAAU9P,EAAM,IAAI,oBAAsB,EAAA,CAC9C,CACH,EAEDvJ,EAAO,IAAI,2CAA2CqZ,CAAI,EAAE,CAChE,CAQO,SAASC,GACZnZ,EACAX,EACI,CAEJ,MAAM+Z,GADSpZ,EAAO,QAAU,CAAA,GACT,QAEvB,GAAI,CAACoZ,EAAS,CACVvZ,EAAO,KAAK,kDAAkD,EAC9D,MACJ,CAEA,MAAMS,EAAU,OAAe,OAC/B,GAAI,CAACA,GAAQ,MAAO,CAChBT,EAAO,KAAK,8DAA8D,EAC1E,MACJ,CAEA,MAAMuJ,EAAQ9I,EAAO,MAAM,SAAA,GAAc,CAAA,EAEzCA,EAAO,MAAM,SAAS,CAClB,GAAI,CACA,GAAG8I,EAAM,GACT,mBAAoBgQ,CAAA,CACxB,CACH,EAGD9Y,EAAO,aAAa,UAAU,gBAAiB,CAC3C,OAAQ,CACJ,KAAM8I,EAAM,IAAI,kBAAoB,SAAW,MAAQ,OACvD,SAAUgQ,CAAA,CACd,CACH,EAEDvZ,EAAO,IAAI,iDAAiDuZ,CAAO,EAAE,CACzE,CAYO,SAASC,GACZ5P,EACApK,EACI,CACJ,MAAMiB,EAAU,OAAe,OAC/B,GAAI,CAACA,GAAQ,MAAO,CAChBT,EAAO,KAAK,8DAA8D,EAC1E,MACJ,CAEA,MAAMuJ,EAAQ9I,EAAO,MAAM,SAAA,GAAc,CAAA,EAGzC,GAAI8I,EAAM,MAAM,mBAAoB,CAChCvJ,EAAO,IAAI,kEAAkE,EAC7E,MACJ,CAIA,GAAIuJ,EAAM,IAAI,2BAA6B,GAAO,CAC9CvJ,EAAO,IAAI,sEAAsE,EACjF,MACJ,CAGA,MAAMgW,EAASvV,EAAO,YAAY,MAAM,mBAAmB,EACrD0X,EAA6BnC,GAAQ,MAAM,MAAQA,GAAQ,MAAQ,CAAA,EAEzE,GAAImC,EAAS,SAAW,EAAG,CACvBnY,EAAO,IAAI,uDAAuD,EAClE,MACJ,CAGA,MAAMyZ,EAAgBtB,EAAS,KAAM1Q,GAAsBA,EAAE,aAAe,EAAI,EAE5EgS,GACAhZ,EAAO,MAAM,SAAS,CAClB,KAAM,CACF,GAAG8I,EAAM,KACT,mBAAoBkQ,EAAc,EAAA,CACtC,CACH,EACDzZ,EAAO,IAAI,2DAA2DyZ,EAAc,EAAE,KAAKA,EAAc,IAAI,GAAG,GAEhHzZ,EAAO,IAAI,6DAA6D,CAEhF,CAWO,SAAS0Z,GACZvZ,EACAX,EACI,CACJ,MAAMiB,EAAU,OAAe,OAC/B,GAAI,CAACA,GAAQ,MAAO,CAChBT,EAAO,KAAK,iEAAiE,EAC7E,MACJ,CAEA,MAAMkG,EAAU/F,EAAO,QAAQ,SAAW,GACpCoJ,EAAQ9I,EAAO,MAAM,SAAA,GAAc,CAAA,EAGnCkZ,EAAQ,CAAE,GAAGpQ,EAAM,GAAI,yBAA0BrD,CAAA,EAEvD,GAAIA,EAAS,CAGT,MAAM8P,EAASvV,EAAO,YAAY,MAAM,mBAAmB,EAErDgZ,GAD6BzD,GAAQ,MAAM,MAAQA,GAAQ,MAAQ,CAAA,GAC1C,KAAMvO,GAAsBA,EAAE,aAAe,EAAI,EAEhFhH,EAAO,MAAM,SAAS,CAClB,GAAIkZ,EACJ,KAAM,CACF,GAAGpQ,EAAM,KACT,mBAAoBkQ,GAAe,IAAM,IAAA,CAC7C,CACH,EAEGA,EACAzZ,EAAO,IAAI,+DAA+DyZ,EAAc,EAAE,EAAE,EAE5FzZ,EAAO,IAAI,4DAA4D,CAE/E,MAEIS,EAAO,MAAM,SAAS,CAClB,GAAIkZ,CAAA,CACP,EACD3Z,EAAO,IAAI,qEAAqE,CAExF,CAQO,SAAS4Z,GACZzZ,EACAX,EACI,CACJ,MAAMiB,EAAU,OAAe,OAC/B,GAAI,CAACA,GAAQ,MAAO,CAChBT,EAAO,KAAK,wDAAwD,EACpE,MACJ,CAEA,MAAMmF,EAAShF,EAAO,QAAQ,OAC9B,GAAI,CAACgF,EAAQ,CACTnF,EAAO,KAAK,2CAA2C,EACvD,MACJ,CAEA,MAAMoF,MAAY,KACZP,EAAcC,GAAuB,CACvC,MAAMC,EAAOD,EAAK,YAAA,EACZE,EAAQ,OAAOF,EAAK,SAAA,EAAa,CAAC,EAAE,SAAS,EAAG,GAAG,EACnDG,EAAM,OAAOH,EAAK,QAAA,CAAS,EAAE,SAAS,EAAG,GAAG,EAClD,MAAO,GAAGC,CAAI,IAAIC,CAAK,IAAIC,CAAG,EAClC,EAEMK,EAAYT,EAAWO,CAAK,EAClC,IAAIC,EAAyB,KAE7B,OAAQF,EAAA,CACJ,IAAK,KAAM,CACP,MAAM0U,EAAM,IAAI,KAAKzU,CAAK,EAC1ByU,EAAI,QAAQA,EAAI,QAAA,EAAY,CAAC,EAC7BxU,EAAUR,EAAWgV,CAAG,EACxB,KACJ,CACA,IAAK,MAAO,CACR,MAAMA,EAAM,IAAI,KAAKzU,CAAK,EAC1ByU,EAAI,QAAQA,EAAI,QAAA,EAAY,EAAE,EAC9BxU,EAAUR,EAAWgV,CAAG,EACxB,KACJ,CACA,IAAK,MAAO,CACR,MAAMA,EAAM,IAAI,KAAKzU,CAAK,EAC1ByU,EAAI,QAAQA,EAAI,QAAA,EAAY,EAAE,EAC9BxU,EAAUR,EAAWgV,CAAG,EACxB,KACJ,CACA,IAAK,YACDxU,EAAU,KACV,MACJ,QACIrF,EAAO,KAAK,wCAAwCmF,CAAM,EAAE,EAC5D,MAAA,CAGR1E,EAAO,MAAM,UAAU,CACnB,2BAA4B6E,EAC5B,yBAA0BD,CAAA,CAC7B,EAEDrF,EAAO,IAAI,wCAAwCmF,CAAM,MAAMG,CAAS,MAAMD,GAAW,WAAW,EAAE,CAC1G,CCvqBO,MAAMyU,GAA+B,CACxC3Z,EACAX,IACO,CACP,MAAMiB,EAAU,OAAe,OAKzBsZ,GADatZ,GAAQ,OAAO,WAAA,GAAgB,CAAA,GACV,MAAM,mBAAqB,CAAA,EAE7DoX,EAAU,OAAO1X,EAAO,QAAQ,OAAO,EAC7C,GAAI,CAAC0X,EACD,OAGJ,MAAMmC,EAAqB,CAAC,GAAGD,CAAoB,EAQnD,GAP2BC,EAAmB,KAAK/J,GAAKA,EAAE,WAAa4H,CAAO,EAOtD,CACpBpX,GAAQ,OAAO,WAAW,CACtB,wBAAyBoX,CAAA,CAC5B,EACD,MACJ,CAGA,MAAMoC,EAAwC,CAAC,GAAGD,EAAoB,CAClE,SAAUnC,EACV,WAAY,KACZ,SAAU,IAAA,CACb,EAEDpX,GAAQ,OAAO,WAAW,CACtB,yBAA0BwZ,EAC1B,wBAAyBpC,EACzB,WAAY,EAAA,CACf,CACL,EAWaqC,GAAiC,CAC1C/Z,EACAX,IACO,CACP,MAAMiB,EAAU,OAAe,OAIzBsZ,GADatZ,GAAQ,OAAO,WAAA,GAAgB,CAAA,GACV,MAAM,mBAAqB,CAAA,EAE7DoX,EAAU,OAAO1X,EAAO,QAAQ,OAAO,EACvCG,EAAQH,EAAO,QAAQ,MACvBK,EAAQL,EAAO,QAAQ,MAE7B,GAAI,CAAC0X,GAAW,CAACvX,EACb,OAGJ,MAAM0Z,EAAqB,CAAC,GAAGD,CAAoB,EAC7CzQ,EAAM0Q,EAAmB,UAAU/J,GAAKA,EAAE,WAAa4H,CAAO,EAEhEvO,EAAM,IAIV0Q,EAAmB1Q,CAAG,EAAI,CACtB,GAAG0Q,EAAmB1Q,CAAG,EACzB,CAAChJ,CAAK,EAAGE,GAAS,IAAA,EAGtBC,GAAQ,OAAO,WAAW,CACtB,yBAA0BuZ,EAC1B,WAAY,EAAA,CACf,EACL,EAWaG,GAAkC,CAC3Cha,EACAX,IACO,CACP,MAAMiB,EAAU,OAAe,OAGzBoH,EAAapH,GAAQ,OAAO,WAAA,GAAgB,CAAA,EAC5CsZ,EAAuBlS,EAAW,MAAM,mBAAqB,CAAA,EAC7DgQ,EAAUhQ,EAAW,IAAI,mBACzB1C,EAAShF,EAAO,QAAQ,OAE9B,GAAI,CAAC0X,GAAW,CAAC1S,EACb,OAGJ,MAAM6U,EAAqB,CAAC,GAAGD,CAAoB,EAC7CzQ,EAAM0Q,EAAmB,UAAU/J,GAAKA,EAAE,WAAa4H,CAAO,EAEpE,GAAIvO,EAAM,EACN,OAGJ,MAAMlE,MAAY,KACZP,EAAcC,GACTA,EAAK,YAAA,EAAc,MAAM,GAAG,EAAE,CAAC,EAGpCQ,EAAYT,EAAWO,CAAK,EAClC,IAAIC,EAAyB,KAE7B,OAAQF,EAAA,CACJ,IAAK,KACDE,EAAUR,EAAW,IAAI,KAAKO,EAAM,QAAA,EAAY,EAAI,GAAK,GAAK,GAAK,GAAI,CAAC,EACxE,MACJ,IAAK,MACDC,EAAUR,EAAW,IAAI,KAAKO,EAAM,QAAA,EAAY,GAAK,GAAK,GAAK,GAAK,GAAI,CAAC,EACzE,MACJ,IAAK,MACDC,EAAUR,EAAW,IAAI,KAAKO,EAAM,QAAA,EAAY,GAAK,GAAK,GAAK,GAAK,GAAI,CAAC,EACzE,MACJ,IAAK,YACDC,EAAU,KACV,KAAA,CAGR2U,EAAmB1Q,CAAG,EAAI,CACtB,GAAG0Q,EAAmB1Q,CAAG,EACzB,WAAYhE,EACZ,SAAUD,CAAA,EAGd5E,GAAQ,OAAO,WAAW,CACtB,yBAA0BuZ,EAC1B,WAAY,EAAA,CACf,CACL,EAeaI,GAA6B,CACtCja,EACAX,IACO,CACP,MAAMiB,EAAU,OAAe,OAGzBoX,GAFepX,GAAQ,OAAO,IAAA,GAAS,CAAA,GAEhB,iBAC7B,GAAI,CAACoX,EACD,OASJ,MAAMoC,EAHqB,CAAC,IAFNxZ,GAAQ,OAAO,YAAA,GACF,QAAU,CAAA,GACD,MAAM,mBAAqB,CAAA,CAAG,EAG5B,OAAOwP,GAAKA,EAAE,WAAa4H,CAAO,EAGhFpX,GAAQ,OAAO,iBAAiB,CAC5B,yBAA0BwZ,EAC1B,WAAY,EAAA,CACf,EAGDxZ,GAAQ,OAAO,YAAY,CACvB,iBAAkB,IAAA,CACrB,EAGDA,GAAQ,OAAO,QAAA,CACnB,EAuBa4Z,GAAqB,MAC9Bla,EACAX,IACgB,CAChB,MAAMiB,EAAU,OAAe,OACzB8T,EAAe9T,GAAQ,OAAO,IAAA,GAAS,CAAA,EAIvCsZ,GADatZ,GAAQ,OAAO,WAAA,GAAgB,CAAA,GACV,MAAM,mBAAqB,CAAA,EAE7D6Z,EAAkB/F,EAAa,gBACrC,GAAK+F,EAIL,CAAA7Z,GAAQ,OAAO,YAAY,CAAE,gBAAiB,GAAM,EAEpD,GAAI,CAEA,MAAMkH,EAAW,MAAMlH,EAAO,IAAI,OAC9B,uDAAuD6Z,CAAe,EAAA,EAG1E,GAAI,CAAC3S,GAAU,QACX,MAAM,IAAI,MAAMA,GAAU,SAAW,wBAAwB,EAKjE,MAAMsS,EADqB,CAAC,GAAGF,CAAoB,EACL,OAAO9J,GAAKA,EAAE,WAAaqK,CAAe,EAExF7Z,GAAQ,OAAO,WAAW,CACtB,yBAA0BwZ,EAC1B,wBAAyB,IAAA,CAC5B,EAGD,MAAMxZ,GAAQ,YAAY,UAAU,gBAAgB,EAEpDA,GAAQ,OAAO,UACXA,EAAO,IAAI,uDAAuD,GAAK,gBAAA,EAE3EA,GAAQ,OAAO,QAAA,CACnB,OAASqH,EAAY,CACjBrH,GAAQ,OAAO,QACXqH,GAAO,UAAU,MAAM,SACpBA,GAAO,SACPrH,EAAO,IAAI,qDAAqD,GAChE,yBAAA,CAEX,QAAA,CACIA,GAAQ,OAAO,YAAY,CAAE,gBAAiB,GAAO,gBAAiB,KAAM,CAChF,EACJ,EAEa8Z,GAA2B,MACpCpa,EACAX,IACgB,CAChB,MAAMiB,EAAU,OAAe,OACzB8T,EAAe9T,GAAQ,OAAO,IAAA,GAAS,CAAA,EAIvCsZ,GADatZ,GAAQ,OAAO,WAAA,GAAgB,CAAA,GACV,MAAM,mBAAqB,CAAA,EAE7D+Z,EAAiBjG,EAAa,eAC9BkG,EAAgBlG,EAAa,cAQnC,GAAKkG,GAAe,MAAM,GAI1B,CAAAha,GAAQ,OAAO,YAAY,CAAE,cAAe,GAAM,EAElD,GAAI,CACA,GAAI+Z,EAAgB,CAEhB,MAAM7S,EAAW,MAAMlH,EAAO,IAAI,IAC9B,uDAAuD+Z,CAAc,GACrE,CACI,KAAMC,EAAc,KACpB,MAAOA,EAAc,KAAA,CACzB,EAGJ,GAAI,CAAC9S,GAAU,QACX,MAAM,IAAI,MAAMA,GAAU,SAAW,wBAAwB,EAKjE,MAAMsS,EADqB,CAAC,GAAGF,CAAoB,EACL,IAAI9J,GAC1CA,EAAE,WAAauK,EACR,CACH,GAAGvK,EACH,WAAYwK,EAAc,YAAc,KACxC,SAAUA,EAAc,UAAY,IAAA,EAGrCxK,CACV,EAEDxP,GAAQ,OAAO,WAAW,CACtB,yBAA0BwZ,EAC1B,WAAY,EAAA,CACf,CACL,KAAO,CAEH,MAAMtS,EAAW,MAAMlH,EAAO,IAAI,KAC9B,sDACA,CACI,KAAMga,EAAc,KACpB,MAAOA,EAAc,OAAS,SAAA,CAClC,EAGJ,GAAI,CAAC9S,GAAU,QACX,MAAM,IAAI,MAAMA,GAAU,SAAW,wBAAwB,EAGjE,MAAM+S,EAAQ,OAAO/S,GAAU,MAAM,EAAE,EACvC,GAAI+S,EAAO,CAEP,MAAMT,EAAqB,CAAC,GADD,CAAC,GAAGF,CAAoB,EACA,CAC/C,SAAUW,EACV,WAAY,KACZ,SAAU,IAAA,CACb,EAEDja,GAAQ,OAAO,WAAW,CACtB,yBAA0BwZ,EAC1B,wBAAyBS,EACzB,WAAY,EAAA,CACf,CACL,CACJ,CAGA,MAAMja,GAAQ,YAAY,UAAU,gBAAgB,EAEpDA,GAAQ,OAAO,UACX+Z,EACO/Z,EAAO,IAAI,qDAAqD,GAAK,eACrEA,EAAO,IAAI,uDAAuD,GAAK,gBAAA,EAElFA,GAAQ,OAAO,QAAA,CACnB,OAASqH,EAAY,CACjBrH,GAAQ,OAAO,QACXqH,GAAO,UAAU,MAAM,SACpBA,GAAO,SACPrH,EAAO,IAAI,mDAAmD,GAC9D,uBAAA,CAEX,QAAA,CACIA,GAAQ,OAAO,YAAY,CAAE,cAAe,GAAO,CACvD,EACJ,EC7aMT,EAAW,OAAe,QAAQ,eAAe,wBAAwB,GAAM,CACjF,IAAK,IAAIC,IAAoB,QAAQ,IAAI,2BAA4B,GAAGA,CAAI,EAC5E,KAAM,IAAIA,IAAoB,QAAQ,KAAK,2BAA4B,GAAGA,CAAI,EAC9E,MAAO,IAAIA,IAAoB,QAAQ,MAAM,2BAA4B,GAAGA,CAAI,CACpF,EAaA,SAASsN,IAAgC,CAGrC,OADgB,OAAe,QAChB,SAAS,uBAAuB,GAAK,CAAC,KAAM,IAAI,CACnE,CAWO,SAASoN,GACZxa,EACAX,EACI,CAEJ,MAAMqE,GADS1D,EAAO,QAAU,CAAA,GACV,OAEtB,GAAI,CAAC0D,EAAQ,CACT7D,EAAO,KAAK,gDAAgD,EAC5D,MACJ,CAEA,MAAMS,EAAU,OAAe,OAC/B,GAAI,CAACA,GAAQ,MAAO,CAChBT,EAAO,KAAK,6DAA6D,EACzE,MACJ,CAGA,MAAM6Q,EAAwBpQ,GAAQ,OAAO,MAAM,mCAAmC,GAC/EA,GAAQ,SAAS,qBAAqB,GACtC,KACP,GAAIoD,IAAWgN,EAAe,CAC1BpQ,EAAO,OAAO,QACVA,EAAO,IAAI,0FAA0F,GAClG,qCAAA,EAEP,MACJ,CAEA,MAAM8I,EAAQ9I,EAAO,MAAM,SAAA,GAAc,CAAA,EACnCiV,EAAmBnI,GAAA,EAGnBqN,EAAiB,CAAE,GAAGrR,EAAM,MAAM,WAAA,EACxC,OAAOqR,EAAe/W,CAAM,EAE5B,MAAMgX,EAAuB,CAAE,GAAGtR,EAAM,MAAM,kBAAA,EAC9C,OAAOsR,EAAqBhX,CAAM,EAGlC,MAAMiX,GAAiBvR,EAAM,IAAI,0BAA4BmM,GAAkB,OAC1E1R,GAAcA,IAAMH,CAAA,EAGzBpD,EAAO,MAAM,SAAS,CAClB,KAAM,CACF,GAAG8I,EAAM,KACT,YAAaqR,EACb,mBAAoBC,CAAA,EAExB,GAAI,CACA,GAAGtR,EAAM,GACT,yBAA0BuR,EAC1B,kBAAmBA,EAAc,CAAC,GAAK,IAAA,EAE3C,WAAY,EAAA,CACf,EAEDra,EAAO,OAAO,UACVA,EAAO,IAAI,6EAA8E,CAAE,OAAQoD,EAAO,YAAA,CAAY,CAAG,GACtH,GAAGA,EAAO,aAAa,6BAAA,EAE9B7D,EAAO,IAAI,6CAA6C6D,CAAM,EAAE,CACpE,CAQO,SAASkX,GACZnR,EACApK,EACI,CACJ,MAAMiB,EAAU,OAAe,OAC/B,GAAI,CAACA,GAAQ,MAAO,CAChBT,EAAO,KAAK,wDAAwD,EACpE,MACJ,CAEA,MAAMuJ,EAAQ9I,EAAO,MAAM,SAAA,GAAc,CAAA,EAEzCA,EAAO,MAAM,SAAS,CAClB,GAAI,CACA,GAAG8I,EAAM,GACT,mBAAoB,EAAA,CACxB,CACH,CACL,CAWO,SAASyR,GACZ7a,EACAX,EACI,CAEJ,MAAMqE,GADS1D,EAAO,QAAU,CAAA,GACV,OAEtB,GAAI,CAAC0D,EAAQ,CACT7D,EAAO,KAAK,6CAA6C,EACzD,MACJ,CAEA,MAAMS,EAAU,OAAe,OAC/B,GAAI,CAACA,GAAQ,MAAO,CAChBT,EAAO,KAAK,0DAA0D,EACtE,MACJ,CAEA,MAAMuJ,EAAQ9I,EAAO,MAAM,SAAA,GAAc,CAAA,EACnCiV,EAAmBnI,GAAA,EACnBuN,EAA0BvR,EAAM,IAAI,0BAA4B,CAAC,IAAI,EAG3E,GAAIuR,EAAc,SAASjX,CAAM,EAAG,CAChCpD,EAAO,OAAO,UACVA,EAAO,IAAI,kFAAkF,GAC1F,iCAAA,EAEP,MACJ,CAGA,GAAI,CAACiV,EAAiB,SAAS7R,CAAM,EAAG,CACpCpD,EAAO,OAAO,QACVA,EAAO,IAAI,gFAAgF,GACxF,uBAAA,EAEP,MACJ,CAEAA,EAAO,MAAM,SAAS,CAClB,KAAM,CACF,GAAG8I,EAAM,KACT,YAAa,CACT,GAAGA,EAAM,MAAM,YACf,CAAC1F,CAAM,EAAG,EAAA,EAEd,mBAAoB,CAChB,GAAG0F,EAAM,MAAM,mBACf,CAAC1F,CAAM,EAAG,EAAA,CACd,EAEJ,GAAI,CACA,GAAG0F,EAAM,GACT,yBAA0B,CAAC,GAAGuR,EAAejX,CAAM,EACnD,kBAAmBA,EACnB,mBAAoB,EAAA,EAExB,WAAY,EAAA,CACf,EAEDpD,EAAO,OAAO,UACVA,EAAO,IAAI,2EAA4E,CAAE,OAAQoD,EAAO,YAAA,CAAY,CAAG,GACpH,GAAGA,EAAO,aAAa,2BAAA,EAE9B7D,EAAO,IAAI,wCAAwC6D,CAAM,EAAE,CAC/D,CCnMA,MAAM7D,EAAW,OAAe,QAAQ,eAAe,iBAAiB,GAAM,CAC1E,IAAK,IAAIC,IAAoB,QAAQ,IAAI,oBAAqB,GAAGA,CAAI,EACrE,KAAM,IAAIA,IAAoB,QAAQ,KAAK,oBAAqB,GAAGA,CAAI,EACvE,MAAO,IAAIA,IAAoB,QAAQ,MAAM,oBAAqB,GAAGA,CAAI,CAC7E,EAuIA,SAASgb,GAAsBxa,EAAyBqH,EAAYoT,EAA2B,CAC3F,MAAMC,EAAOrT,GAAO,UAAU,MAAQ,CAAA,EAChCsT,EAAwBD,GAAM,SAAWrT,GAAO,SAAW,GAG3DuT,EAAYF,GAAM,OAClBG,EAA2BD,GAAa,OAAOA,GAAc,SAC7D,OAAO,OAAOA,CAAS,EAAE,IAAKE,GAAe,MAAM,QAAQA,CAAI,EAAIA,EAAK,CAAC,EAAI,OAAOA,CAAI,CAAE,EAC1F,CAAA,EAGAC,EAAeJ,GAAiB3a,EAAO,EAAEya,CAAW,EAC1Dza,EAAO,MAAM,MAAM+a,CAAY,EAG/B/a,EAAO,MAAM,IAAI,CACb,iBAAkB6a,EAAe,OAAS,EAAIA,EAAkBE,EAAe,CAACA,CAAY,EAAI,CAAA,CAAC,CACpG,CACL,CAMA,SAASC,EAAcjb,EAAoB,CACvC,OAAI,OAAOA,GAAU,SAAiBA,EAClCA,GAAS,OAAOA,GAAU,SACnBA,EAAM,IAAMA,EAAM,IAAM,KAAK,UAAUA,CAAK,EAEhD,OAAOA,GAAS,EAAE,CAC7B,CAcA,SAASkb,GACLjb,EACAiY,EACAlY,EACM,CACN,OAAKA,EACSC,EAAO,EAAE,2BAA2BiY,CAAI,IAAIlY,CAAK,EAAE,GAEjDA,EAHG,OAAOA,GAAS,EAAE,CAIzC,CAKA,SAASmb,IAAoC,CACzC,MAAMlb,EAAU,OAAe,OAC/B,OAAKA,IACDT,EAAO,MAAM,sCAAsC,EAC5C,KAGf,CASO,SAAS4b,GAAkBzb,EAA0BC,EAA8B,CACtF,MAAMK,EAASkb,GAAA,EACf,GAAI,CAAClb,EAAQ,OAEb,MAAMoH,EAAapH,EAAO,MAAM,SAAA,GAAc,CAAA,EACxCG,EAAcH,EAAO,MAAM,IAAA,GAAS,CAAA,EAIpCob,EAA0Bjb,EAAY,mBAAqBiH,EAAW,eAAiB,CAAA,EACvFb,EAA8BpG,EAAY,uBAAyBiH,EAAW,mBAAqB,CAAA,EAEzG7H,EAAO,IAAI,8CAA+CY,EAAY,iBAAiB,EACvFZ,EAAO,IAAI,yCAA0C6H,EAAW,aAAa,EAC7E7H,EAAO,IAAI,oCAAqC6b,CAAa,EAGzDA,EAAc,OAAS,EAEvBC,GAA2Brb,EAAQG,EAAaiH,EAAYgU,CAAa,EAClE7U,EAAkB,OAAS,EAElC+U,GAA0Btb,EAAQG,EAAaiH,EAAYb,CAAiB,EAG5EvG,EAAO,MAAM,QAAQA,EAAO,EAAE,mDAAmD,CAAC,CAE1F,CAMA,SAASqb,GACLrb,EACAG,EACAiH,EACAR,EACI,CAEJ,MAAMN,EADatG,EAAO,WAAW,IAAI,UAAU,GACtB,MAAM,MAAQ,CAAA,EAGrCub,EAAyC,CAAA,EAC3Cpb,EAAY,kBACZob,EAAmB,aAAepb,EAAY,iBAE9CA,EAAY,oBACZob,EAAmB,eAAiBpb,EAAY,mBAIpD,MAAMqb,EAAuC,CAAA,EAE7C,GAAIrb,EAAY,iBAAmBA,EAAY,iBAAmB,QAAaA,EAAY,iBAAmB,MAAQA,EAAY,iBAAmB,GAAI,CACrJ,MAAMsb,EAAyE,CAC3E,MAAS,MACT,SAAY,MACZ,SAAY,UAAA,EAGZtb,EAAY,gBAAkB,UAC9Bqb,EAAkB,iBAAmB,CACjC,OAAQ,UACR,MAAOrb,EAAY,kBAAoB,WACjC,EAAE,OAAOA,EAAY,cAAc,GAAK,GACxC,OAAOA,EAAY,cAAc,GAAK,CAAA,EAGhDqb,EAAkB,iBAAmB,CACjC,OAAQC,EAAetb,EAAY,eAAe,GAAK,MACvD,MAAO,OAAOA,EAAY,cAAc,GAAK,CAAA,CAGzD,CAEA,GAAIA,EAAY,iBAAmBA,EAAY,iBAAmB,QAAaA,EAAY,iBAAmB,MAAQA,EAAY,iBAAmB,GAAI,CACrJ,MAAMub,EAA6D,CAC/D,IAAO,MACP,SAAY,MACZ,SAAY,UAAA,EAEhBF,EAAkB,eAAiB,CAC/B,OAAQE,EAAevb,EAAY,eAAe,GAAK,MACvD,MAAO,OAAOA,EAAY,cAAc,GAAK,CAAA,CAErD,CAIA,MAAMW,EAA+BsG,EAAW,oBAAsB,CAAA,EAChE7E,EAA8B6E,EAAW,mBAAqB,CAAA,EAC9DrG,EAAkDqG,EAAW,uBAAyB,CAAA,EACtFuU,EAAoDvU,EAAW,sBAAwB,CAAA,EAGvFb,EAA8BpG,EAAY,uBAAyBiH,EAAW,mBAAqB,CAAA,EACnGwU,EAAuB,IAAI,IAAIhV,CAAW,EAE1CiV,EAA8B,CAAA,EAC9BC,EAA4B,CAAA,EAKlC,SAASC,EAAkB,EAAQzZ,EAAUE,EAAyB,CAElE,GADyBF,EAAI,WAAaC,EAAkB,SAASC,CAAS,EACxD,CAClB,MAAM0P,EAAmB,CAAE,WAAY,EAAE,GAAI,UAAW5P,EAAI,EAAA,EACtD0Z,EAAoBL,EAAwBnZ,CAAS,GAAK,CAAA,EAG5D,CAACgZ,EAAkB,kBAAoBQ,EAAkB,SAAS,kBAAkB,IACpF9J,EAAK,iBAAmB5P,EAAI,kBAE5B,CAACkZ,EAAkB,kBAAoBQ,EAAkB,SAAS,eAAe,IAEjF9J,EAAK,iBAAmB5P,EAAI,kBAE5B,CAACkZ,EAAkB,gBAAkBQ,EAAkB,SAAS,gBAAgB,IAChF9J,EAAK,eAAiB5P,EAAI,gBAE1B0Z,EAAkB,SAAS,aAAa,IAAG9J,EAAK,YAAc5P,EAAI,aAClE0Z,EAAkB,SAAS,KAAK,IAAG9J,EAAK,IAAM5P,EAAI,KAClD0Z,EAAkB,SAAS,YAAY,IAAG9J,EAAK,WAAa5P,EAAI,YAChE0Z,EAAkB,SAAS,qBAAqB,IAAG9J,EAAK,oBAAsB5P,EAAI,qBAClF0Z,EAAkB,SAAS,YAAY,IAAG9J,EAAK,WAAa5P,EAAI,YAChE0Z,EAAkB,SAAS,WAAW,IAAG9J,EAAK,UAAY5P,EAAI,WAG9D,OAAO,KAAK4P,CAAI,EAAE,OAAS,GAC3B4J,EAAY,KAAK5J,CAAI,CAE7B,CACJ,CAEA5L,EACK,OAAQ,GAAWM,EAAY,SAAS,EAAE,EAAE,CAAC,EAC7C,QAAS,GAAW,CAGjB,GAD0B,EAAE,WAAa9F,EAAmB,SAAS,EAAE,EAAE,EAClD,CACnB,MAAMoR,EAAoB,CAAE,GAAI,EAAE,EAAA,EAC5BlR,EAAa,OAAO,EAAE,EAAE,EACxBib,EAAkBlb,EAAsBC,CAAU,GAAK,CAAA,EAGzD,CAACua,EAAmB,cAAgBU,EAAgB,SAAS,cAAc,IAC3E/J,EAAK,aAAe,EAAE,cAEtB,CAACqJ,EAAmB,gBAAkBU,EAAgB,SAAS,gBAAgB,IAC/E/J,EAAK,eAAiB,EAAE,gBAExB+J,EAAgB,SAAS,MAAM,IAAG/J,EAAK,KAAO,EAAE,MAChD+J,EAAgB,SAAS,YAAY,IAAG/J,EAAK,WAAa,EAAE,YAC5D+J,EAAgB,SAAS,eAAe,IAAG/J,EAAK,cAAgB,EAAE,eAGlE,OAAO,KAAKA,CAAI,EAAE,OAAS,GAC3B2J,EAAa,KAAK3J,CAAI,CAE9B,EAGC,EAAE,SAAW,CAAA,GAAI,QAAS5P,GAAa,CACpC,MAAME,EAAY,GAAG,EAAE,EAAE,IAAIF,EAAI,EAAE,GACnCyZ,EAAkB,EAAGzZ,EAAKE,CAAS,CACvC,CAAC,CACL,CAAC,EAGD+D,EAAkB,OAAS,GAC3BD,EAAS,QAAS,GAAW,CAErBsV,EAAqB,IAAI,EAAE,EAAE,IAIhC,EAAE,SAAW,CAAA,GAAI,QAAStZ,GAAa,CACpC,MAAME,EAAY,GAAG,EAAE,EAAE,IAAIF,EAAI,EAAE,GAE/BiE,EAAkB,SAAS/D,CAAS,GACpCuZ,EAAkB,EAAGzZ,EAAKE,CAAS,CAE3C,CAAC,CACL,CAAC,EAIL,MAAM0Z,EAA+B,CACjC,IAAKtV,CAAA,EAGL,OAAO,KAAK2U,CAAkB,EAAE,OAAS,IACzCW,EAAQ,aAAeX,GAEvBM,EAAa,OAAS,IACtBK,EAAQ,MAAQL,GAEhB,OAAO,KAAKL,CAAiB,EAAE,OAAS,IACxCU,EAAQ,oBAAsBV,GAE9BM,EAAY,OAAS,IACrBI,EAAQ,aAAeJ,GAK3B9b,EAAO,MAAM,IAAI,CAAE,aAAc,GAAM,iBAAkB,CAAA,EAAI,EAE7DA,EAAO,IAAI,MAAM,4DAA6Dkc,CAAO,EAChF,KAAMhV,GAAkB,CACrBlH,EAAO,MAAM,QACTA,EAAO,EAAE,sDAAuD,CAC5D,SAAUkH,GAAU,MAAM,kBAAoB,EAC9C,QAASA,GAAU,MAAM,iBAAmB,CAAA,CAC/C,CAAA,EAGLiV,GAAenc,CAAM,EAErBA,EAAO,WAAW,QAAQ,UAAU,EAEpCA,EAAO,MAAM,MAAA,CACjB,CAAC,EACA,MAAOqH,GAAe,CACnB9H,EAAO,MAAM,8BAA+B8H,CAAK,EACjDmT,GAAsBxa,EAAQqH,EAAO,mDAAmD,CAC5F,CAAC,EACA,QAAQ,IAAM,CACXrH,EAAO,MAAM,IAAI,CAAE,aAAc,GAAO,CAC5C,CAAC,CACT,CAMA,SAASsb,GACLtb,EACAG,EACAiH,EACAb,EACI,CAEJ,MAAMD,EADatG,EAAO,WAAW,IAAI,UAAU,GACtB,MAAM,MAAQ,CAAA,EAGrCoc,EAAiC,CAAA,EAEvC,GAAIjc,EAAY,iBAAmBA,EAAY,iBAAmB,QAAaA,EAAY,iBAAmB,MAAQA,EAAY,iBAAmB,GAAI,CACrJ,MAAMsb,EAAyE,CAC3E,MAAS,MACT,SAAY,MACZ,SAAY,UAAA,EAGZtb,EAAY,gBAAkB,UAC9Bic,EAAY,iBAAmB,CAC3B,OAAQ,UACR,MAAOjc,EAAY,kBAAoB,WACjC,EAAE,OAAOA,EAAY,cAAc,GAAK,GACxC,OAAOA,EAAY,cAAc,GAAK,CAAA,EAGhDic,EAAY,iBAAmB,CAC3B,OAAQX,EAAetb,EAAY,eAAe,GAAK,MACvD,MAAO,OAAOA,EAAY,cAAc,GAAK,CAAA,CAGzD,CAEA,GAAIA,EAAY,iBAAmBA,EAAY,iBAAmB,QAAaA,EAAY,iBAAmB,MAAQA,EAAY,iBAAmB,GAAI,CACrJ,MAAMub,EAA6D,CAC/D,IAAO,MACP,SAAY,MACZ,SAAY,UAAA,EAEhBU,EAAY,eAAiB,CACzB,OAAQV,EAAevb,EAAY,eAAe,GAAK,MACvD,MAAO,OAAOA,EAAY,cAAc,GAAK,CAAA,CAErD,CAGA,MAAMwb,EAAoDvU,EAAW,sBAAwB,CAAA,EACvF7E,EAA8B6E,EAAW,mBAAqB,CAAA,EAC9D0O,EAAsB,CAAA,EAC5BxP,EAAS,QAAShG,GAAiB,EAC9BA,EAAQ,SAAW,CAAA,GAAI,QAASgC,GAAa,CAC1C,MAAME,EAAY,GAAGlC,EAAQ,EAAE,IAAIgC,EAAI,EAAE,GACnC+Z,EAAmB/Z,EAAI,WAAaC,EAAkB,SAASC,CAAS,EAC9E,GAAI+D,EAAkB,SAAS/D,CAAS,GAAK6Z,EAAkB,CAC3D,MAAMnK,EAAmB,CAAE,WAAY5R,EAAQ,GAAI,UAAWgC,EAAI,EAAA,EAC5D0Z,EAAoBL,EAAwBnZ,CAAS,GAAK,CAAA,EAG5D,CAAC4Z,EAAY,kBAAoBJ,EAAkB,SAAS,kBAAkB,IAC9E9J,EAAK,iBAAmB5P,EAAI,kBAE5B,CAAC8Z,EAAY,kBAAoBJ,EAAkB,SAAS,eAAe,IAC3E9J,EAAK,iBAAmB5P,EAAI,kBAE5B,CAAC8Z,EAAY,gBAAkBJ,EAAkB,SAAS,gBAAgB,IAC1E9J,EAAK,eAAiB5P,EAAI,gBAE1B0Z,EAAkB,SAAS,aAAa,IAAG9J,EAAK,YAAc5P,EAAI,aAClE0Z,EAAkB,SAAS,KAAK,IAAG9J,EAAK,IAAM5P,EAAI,KAClD0Z,EAAkB,SAAS,YAAY,IAAG9J,EAAK,WAAa5P,EAAI,YAChE0Z,EAAkB,SAAS,qBAAqB,IAAG9J,EAAK,oBAAsB5P,EAAI,qBAClF0Z,EAAkB,SAAS,YAAY,IAAG9J,EAAK,WAAa5P,EAAI,YAChE0Z,EAAkB,SAAS,WAAW,IAAG9J,EAAK,UAAY5P,EAAI,WAG9D,OAAO,KAAK4P,CAAI,EAAE,OAAS,GAC3B4D,EAAM,KAAK5D,CAAI,CAEvB,CACJ,CAAC,CACL,CAAC,EAGD,MAAMgK,EAA+B,CACjC,IAAK3V,CAAA,EAGL,OAAO,KAAK6V,CAAW,EAAE,OAAS,IAClCF,EAAQ,aAAeE,GAEvBtG,EAAM,OAAS,IACfoG,EAAQ,MAAQpG,GAKpB9V,EAAO,MAAM,IAAI,CAAE,aAAc,GAAM,iBAAkB,CAAA,EAAI,EAE7DA,EAAO,IAAI,MAAM,2DAA4Dkc,CAAO,EAC/E,KAAMhV,GAAkB,CACrBlH,EAAO,MAAM,QACTA,EAAO,EAAE,6DAA8D,CACnE,MAAOkH,GAAU,MAAM,iBAAmB,CAAA,CAC7C,CAAA,EAGLiV,GAAenc,CAAM,EAErBA,EAAO,WAAW,QAAQ,UAAU,EAEpCA,EAAO,MAAM,MAAA,CACjB,CAAC,EACA,MAAOqH,GAAe,CACnB9H,EAAO,MAAM,6BAA8B8H,CAAK,EAChDmT,GAAsBxa,EAAQqH,EAAO,0DAA0D,CACnG,CAAC,EACA,QAAQ,IAAM,CACXrH,EAAO,MAAM,IAAI,CAAE,aAAc,GAAO,CAC5C,CAAC,CACT,CAKA,SAASmc,GAAenc,EAA+B,CAEnDA,EAAO,MAAM,SAAS,CAClB,mBAAoB,CAAA,EACpB,kBAAmB,CAAA,EACnB,sBAAuB,CAAA,EACvB,qBAAsB,CAAA,EACtB,cAAe,CAAA,EACf,kBAAmB,CAAA,CAAC,CACvB,EAGDA,EAAO,MAAM,IAAI,CACb,gBAAiB,KACjB,kBAAmB,KACnB,mBAAoB,KACpB,mBAAoB,KACpB,gBAAiB,KACjB,iBAAkB,CAAA,CAAC,CACtB,CACL,CAQO,SAASsc,GAAwB5c,EAA0BC,EAA8B,CAC5F,MAAMK,EAASkb,GAAA,EACf,GAAI,CAAClb,EAAQ,OAEb,MAAMoH,EAAapH,EAAO,MAAM,SAAA,GAAc,CAAA,EACxCG,EAAcH,EAAO,MAAM,IAAA,GAAS,CAAA,EAEpCsG,EADatG,EAAO,WAAW,IAAI,UAAU,GACtB,MAAM,MAAQ,CAAA,EAKrCob,EAA0Bjb,EAAY,mBAAqBiH,EAAW,eAAiB,CAAA,EACvFb,EAA8BpG,EAAY,uBAAyBiH,EAAW,mBAAqB,CAAA,EAEnGmV,EAA+B,CACjC,SAAU,CAAA,EACV,QAAS,CAAA,EACT,QAAS,CACL,aAAc,EACd,YAAa,EACb,eAAgB,GAChB,iBAAkB,EAAA,CACtB,EAIEC,EAAiB,CAAC,EACpBrc,EAAY,iBACZA,EAAY,mBACZA,EAAY,oBACZA,EAAY,oBAEhBoc,EAAY,QAAQ,eAAiBC,EAGrC,MAAM1b,EAA+BsG,EAAW,oBAAsB,CAAA,EAChE7E,EAA8B6E,EAAW,mBAAqB,CAAA,EAC9DrG,EAAkDqG,EAAW,uBAAyB,CAAA,EACtFuU,EAAoDvU,EAAW,sBAAwB,CAAA,EAIvFwU,EAAuB,IAAI,IAAIR,CAAa,EAKlD,SAASqB,EAAqBna,EAAUE,EAAmB6Z,EAAqC,CAC5F,MAAMK,EAAuB,CAAA,EACvBV,EAAoBL,EAAwBnZ,CAAS,GAAK,CAAA,EAuBhE,GAlBIrC,EAAY,oBACZuc,EAAW,KACP1c,EAAO,EAAE,gFAAiF,CACtF,MAAOG,EAAY,kBAAA,CACtB,GACE,qBAAqBA,EAAY,kBAAkB,EAAA,EAG1DA,EAAY,oBACZuc,EAAW,KACP1c,EAAO,EAAE,qEAAsE,CAC3E,MAAOG,EAAY,kBAAA,CACtB,GACE,UAAUA,EAAY,kBAAkB,EAAA,EAK/Ckc,EAAkB,CAClB,GAAI,CAAClc,EAAY,oBAAsB6b,EAAkB,SAAS,kBAAkB,EAAG,CACnF,MAAMW,EAAara,EAAI,kBAAkB,iBAAA,GAAsBA,EAAI,iBACnEoa,EAAW,KACP1c,EAAO,EAAE,gFAAiF,CACtF,MAAO2c,CAAA,CACV,GACE,qBAAqBA,CAAU,EAAA,CAE1C,CACI,CAACxc,EAAY,oBAAsB6b,EAAkB,SAAS,eAAe,GAC7EU,EAAW,KACP1c,EAAO,EAAE,sEAAuE,CAC5E,MAAOsC,EAAI,eAAe,iBAAA,GAAsBA,EAAI,aAAA,CACvD,GACE,kBAAkBA,EAAI,aAAa,EAAA,EAG1C,CAACnC,EAAY,oBAAsB6b,EAAkB,SAAS,gBAAgB,GAC9EU,EAAW,KACP1c,EAAO,EAAE,qEAAsE,CAC3E,MAAOsC,EAAI,cAAA,CACd,GACE,UAAUA,EAAI,cAAc,EAAA,EAGnC0Z,EAAkB,SAAS,aAAa,GACxCU,EAAW,KACP1c,EAAO,EAAE,oEAAqE,CAC1E,KAAMgb,EAAc1Y,EAAI,WAAW,CAAA,CACtC,GACE,gBAAgB0Y,EAAc1Y,EAAI,WAAW,CAAC,EAAA,EAGrD0Z,EAAkB,SAAS,KAAK,GAChCU,EAAW,KACP1c,EAAO,EAAE,4DAA6D,CAAE,IAAKsC,EAAI,GAAA,CAAK,GACnF,QAAQA,EAAI,GAAG,EAAA,EAGtB0Z,EAAkB,SAAS,YAAY,GACvCU,EAAW,KACP1c,EAAO,EAAE,mEAAoE,CACzE,MAAOsC,EAAI,YAAY,iBAAA,GAAsBA,EAAI,UAAA,CACpD,GACE,eAAeA,EAAI,UAAU,EAAA,EAGpC0Z,EAAkB,SAAS,qBAAqB,GAChDU,EAAW,KACP1c,EAAO,EAAE,mEAAoE,CACzE,MAAOsC,EAAI,mBAAA,CACd,GACE,eAAeA,EAAI,mBAAmB,EAAA,EAG7C0Z,EAAkB,SAAS,WAAW,GACtCU,EAAW,KACP1c,EAAO,EAAE,kEAAmE,CACxE,MAAOsC,EAAI,UAAY,KAAO,KAAA,CACjC,GACE,WAAWA,EAAI,UAAY,KAAO,KAAK,EAAA,EAGlDia,EAAY,QAAQ,iBAAmB,EAC3C,CAEA,OAAOG,CACX,CAEItB,EAAc,OAAS,GACvB9U,EACK,OAAQU,GAAWoU,EAAc,SAASpU,EAAE,EAAE,CAAC,EAC/C,QAASA,GAAW,CACjB,MAAM4V,EAAoB,CAAA,EACpBC,EAAoB7V,EAAE,WAAalG,EAAmB,SAASkG,EAAE,EAAE,EACnEhG,EAAa,OAAOgG,EAAE,EAAE,EACxBiV,EAAkBlb,EAAsBC,CAAU,GAAK,CAAA,EAG7D,GAAIb,EAAY,gBAAiB,CAC7B,MAAM2c,EAAa7B,GAAejb,EAAQ,eAAgBG,EAAY,eAAe,EACrFyc,EAAQ,KACJ5c,EAAO,EAAE,qEAAsE,CAAE,OAAQ8c,EAAY,GAClG,iBAAiBA,CAAU,EAAA,CAEtC,CACA,GAAI3c,EAAY,kBAAmB,CAC/B,MAAM4c,EAAe9B,GAAejb,EAAQ,iBAAkBG,EAAY,iBAAiB,EAC3Fyc,EAAQ,KACJ5c,EAAO,EAAE,uEAAwE,CAAE,OAAQ+c,EAAc,GACtG,mBAAmBA,CAAY,EAAA,CAE1C,CAGA,GAAIF,EAAmB,CACnB,GAAI,CAAC1c,EAAY,iBAAmB8b,EAAgB,SAAS,cAAc,EAAG,CAC1E,MAAMa,EAAa7B,GAAejb,EAAQ,eAAgBgH,EAAE,YAAY,EACxE4V,EAAQ,KACJ5c,EAAO,EAAE,qEAAsE,CAAE,OAAQ8c,EAAY,GAClG,iBAAiBA,CAAU,EAAA,CAEtC,CACA,GAAI,CAAC3c,EAAY,mBAAqB8b,EAAgB,SAAS,gBAAgB,EAAG,CAC9E,MAAMc,EAAe9B,GAAejb,EAAQ,iBAAkBgH,EAAE,cAAc,EAC9E4V,EAAQ,KACJ5c,EAAO,EAAE,uEAAwE,CAAE,OAAQ+c,EAAc,GACtG,mBAAmBA,CAAY,EAAA,CAE1C,CACId,EAAgB,SAAS,MAAM,GAC/BW,EAAQ,KAAK5c,EAAO,EAAE,oEAAoE,GAAK,cAAc,EAE7Gic,EAAgB,SAAS,YAAY,GACrCW,EAAQ,KACJ5c,EAAO,EAAE,mEAAoE,CAAE,MAAOgH,EAAE,YAAY,oBAAsBA,EAAE,WAAY,GACrI,eAAeA,EAAE,YAAY,iBAAA,GAAsBA,EAAE,UAAU,EAAA,EAGtEiV,EAAgB,SAAS,eAAe,GACxCW,EAAQ,KACJ5c,EAAO,EAAE,sEAAuE,CAAE,MAAOgH,EAAE,eAAe,oBAAsBA,EAAE,cAAe,GAC9I,kBAAkBA,EAAE,eAAe,iBAAA,GAAsBA,EAAE,aAAa,EAAA,EAGnFuV,EAAY,QAAQ,iBAAmB,EAC3C,EAEIK,EAAQ,OAAS,GAAKJ,GAAkBK,IACxCN,EAAY,SAAS,KAAK,CACtB,GAAIvV,EAAE,GACN,KAAMgU,EAAchU,EAAE,IAAI,EAC1B,QAAS4V,EAAQ,OAAS,EAAIA,EAAQ,KAAK,IAAI,EAAKC,EAC7C7c,EAAO,EAAE,uEAAuE,GAAK,kBACrFA,EAAO,EAAE,oEAAoE,GAAK,sBAAA,CAC5F,GAIJgH,EAAE,SAAW,CAAA,GAAI,QAAS1E,GAAa,CACpC,MAAME,EAAY,GAAGwE,EAAE,EAAE,IAAI1E,EAAI,EAAE,GAC7B+Z,EAAmB/Z,EAAI,WAAaC,EAAkB,SAASC,CAAS,EAExEka,EAAaD,EAAqBna,EAAKE,EAAW6Z,CAAgB,GAEpEK,EAAW,OAAS,GAAMF,IAAmBrc,EAAY,oBAAsBA,EAAY,qBAAwBkc,IACnHE,EAAY,QAAQ,KAAK,CACrB,UAAWvV,EAAE,GACb,SAAU1E,EAAI,GACd,YAAa0Y,EAAchU,EAAE,IAAI,EACjC,WAAY1E,EAAI,uBAAyB0Y,EAAc1Y,EAAI,WAAW,GAAKA,EAAI,MAAQ,GACvF,QAASoa,EAAW,OAAS,EAAIA,EAAW,KAAK,IAAI,EAAKL,EACnDrc,EAAO,EAAE,uEAAuE,GAAK,kBACrFA,EAAO,EAAE,oEAAoE,GAAK,sBAAA,CAC5F,CAET,CAAC,CACL,CAAC,EAKLuG,EAAkB,OAAS,GAC3BD,EAAS,QAASU,GAAW,CAErB4U,EAAqB,IAAI5U,EAAE,EAAE,IAIhCA,EAAE,SAAW,CAAA,GAAI,QAAS1E,GAAa,CACpC,MAAME,EAAY,GAAGwE,EAAE,EAAE,IAAI1E,EAAI,EAAE,GACnC,GAAIiE,EAAkB,SAAS/D,CAAS,EAAG,CACvC,MAAM6Z,EAAmB/Z,EAAI,WAAaC,EAAkB,SAASC,CAAS,EAExEka,EAAaD,EAAqBna,EAAKE,EAAW6Z,CAAgB,EAExEE,EAAY,QAAQ,KAAK,CACrB,UAAWvV,EAAE,GACb,SAAU1E,EAAI,GACd,YAAa0Y,EAAchU,EAAE,IAAI,EACjC,WAAY1E,EAAI,uBAAyB0Y,EAAc1Y,EAAI,WAAW,GAAKA,EAAI,MAAQ,GACvF,QAASoa,EAAW,OAAS,EAAIA,EAAW,KAAK,IAAI,EAAKL,EACnDrc,EAAO,EAAE,uEAAuE,GAAK,kBACrFA,EAAO,EAAE,gEAAgE,GAAK,UAAA,CACxF,CACL,CACJ,CAAC,CACL,CAAC,EAGLuc,EAAY,QAAQ,aAAeA,EAAY,SAAS,OACxDA,EAAY,QAAQ,YAAcA,EAAY,QAAQ,OAGtDvc,EAAO,MAAM,IAAI,CACb,gBAAiBuc,CAAA,CACpB,EAEDhd,EAAO,IAAI,yCAA0Cgd,CAAW,CACpE,CC52BA,MAAMhd,EAAW,OAAe,QAAQ,eAAe,YAAY,GAAM,CACrE,IAAK,IAAIC,IAAoB,QAAQ,IAAI,eAAgB,GAAGA,CAAI,EAChE,KAAM,IAAIA,IAAoB,QAAQ,KAAK,eAAgB,GAAGA,CAAI,EAClE,MAAO,IAAIA,IAAoB,QAAQ,MAAM,eAAgB,GAAGA,CAAI,CACxE,EA6DO,SAASwd,GACZtd,EACW,CACX,KAAM,CAAE,WAAAT,EAAa,CAAA,GAAOS,EAAO,QAAU,CAAA,EAEvCud,EAA2B,CAC7B,CAAE,MAAO,KAAM,OAAQ,KAAM,MAAO,OAAQ,SAAU,GAAO,SAAU,EAAA,EACvE,CAAE,MAAO,aAAc,OAAQ,qDAAsD,MAAO,QAAS,SAAU,GAAM,SAAU,EAAA,EAC/H,CAAE,MAAO,eAAgB,OAAQ,uDAAwD,MAAO,QAAS,SAAU,GAAM,SAAU,EAAA,EACnI,CAAE,MAAO,UAAW,OAAQ,kDAAmD,MAAO,OAAQ,SAAU,GAAO,SAAU,EAAA,EACzH,CAAE,MAAO,eAAgB,OAAQ,uDAAwD,MAAO,QAAS,SAAU,GAAM,SAAU,EAAA,EACnI,CAAE,MAAO,eAAgB,OAAQ,uDAAwD,MAAO,QAAS,SAAU,GAAO,SAAU,EAAA,EACpI,CAAE,MAAO,UAAW,OAAQ,kDAAmD,MAAO,QAAS,SAAU,GAAO,SAAU,EAAA,EAC1H,CAAE,MAAO,iBAAkB,OAAQ,yDAA0D,MAAO,QAAS,SAAU,GAAO,SAAU,EAAA,EACxI,CAAE,MAAO,kBAAmB,OAAQ,0DAA2D,MAAO,QAAS,SAAU,GAAO,SAAU,EAAA,EAC1I,CAAE,MAAO,UAAW,OAAQ,kDAAmD,MAAO,OAAQ,SAAU,GAAO,SAAU,EAAA,CAAM,EAI7HC,EAA+Bje,EAChC,OAAOT,GAAK,CAACA,EAAE,UAAU,EACzB,IAAIT,IAAa,CACd,MAAO,gBAAgBA,EAAS,KAAK,aAAa,GAClD,OAAQ,GAAGA,EAAS,IAAI,KAAKA,EAAS,MAAM,IAC5C,MAAO,QACP,SAAU,GACV,SAAU,EAAA,EACZ,EAEN,MAAO,CAAC,GAAGkf,EAAa,GAAGC,CAAe,CAC9C,CAyCO,SAASC,GACZzd,EACAX,EACsB,CACtB,MAAMD,EAASY,EAAO,QAAW,CAAA,EAC3B,CAAE,OAAA0d,EAAS,SAAU,KAAArV,EAAM,MAAAhI,GAAUjB,EACrCkB,EAAU,OAAe,OAE/B,GAAI,CAACA,GAAQ,MACTT,OAAAA,EAAO,KAAK,8CAA8C,EACnD,CAAE,QAAS,GAAO,SAAU,CAAA,CAAC,EAIxC,MAAMuU,EAAesJ,IAAW,UAAYpd,EAAO,MAAM,YAAcA,EAAO,MAAM,SAAA,EAC9Eqd,EAAYtV,EAAK,MAAM,GAAG,EAChC,IAAIuV,EAAyBxJ,EAE7B,UAAWyJ,KAAQF,EACfC,EAAeA,IAAeC,CAAI,EAGjC,MAAM,QAAQD,CAAY,IAC3BA,EAAe,CAAA,GAInB,IAAIE,EACJ,OAAIF,EAAa,SAASvd,CAAK,EAE3Byd,EAAWF,EAAa,OAAQpL,GAAiBA,IAASnS,CAAK,EAG/Dyd,EAAW,CAAC,GAAGF,EAAcvd,CAAK,EAIlCqd,IAAW,UACXpd,EAAO,MAAM,UAAU,CACnB,CAAC+H,CAAI,EAAGyV,CAAA,CACX,EAEDxd,EAAO,MAAM,SAAS,CAClB,CAAC+H,CAAI,EAAGyV,CAAA,CACX,EAGE,CAAE,QAAS,GAAM,SAAUA,CAAA,CACtC,CAuCO,SAASC,GACZ/d,EACAX,EACyB,CACzB,MAAMD,EAASY,EAAO,QAAW,CAAA,EAC3B,CAAE,UAAAge,EAAW,QAAAjY,CAAA,EAAY3G,EACzBkB,EAAU,OAAe,OAE/B,GAAI,CAACA,GAAQ,MACTT,OAAAA,EAAO,KAAK,iDAAiD,EACtD,CAAE,QAAS,GAAO,SAAU,CAAA,CAAC,EAKxC,MAAMoe,EADe3d,EAAO,MAAM,SAAA,EACY,gBAAkB,CAAA,EAGhE,IAAI4d,EACAnY,EACAmY,EAAaD,EAAe,SAASD,CAAS,EAAIC,EAAiB,CAAC,GAAGA,EAAgBD,CAAS,EAEhGE,EAAaD,EAAe,OAAQE,GAAcA,IAAMH,CAAS,EAIrE1d,EAAO,MAAM,SAAS,CAClB,eAAgB4d,CAAA,CACnB,EAGD,GAAI,CACA,aAAa,QAAQ,kCAAmC,KAAK,UAAUA,CAAU,CAAC,CACtF,OAASjd,EAAG,CACRpB,EAAO,KAAK,4CAA6CoB,CAAC,CAC9D,CAEA,MAAO,CAAE,QAAS,GAAM,SAAUid,CAAA,CACtC,CAoCO,SAASE,GACZpe,EACAX,EACwB,CACxB,MAAMD,EAASY,EAAO,QAAW,CAAA,EAC3B,CAAE,YAAAkH,EAAc,CAAA,CAAC,EAAM9H,EACvBkB,EAAU,OAAe,OAE/B,OAAKA,GAAQ,OAKbA,EAAO,MAAM,SAAS,CAClB,cAAe4G,EACf,UAAW,EAAA,CACd,EAEM,CAAE,QAAS,EAAA,IATdrH,EAAO,KAAK,gDAAgD,EACrD,CAAE,QAAS,EAAA,EAS1B,CA2CO,SAASwe,GACZre,EACAX,EAC0B,CAC1B,MAAMD,EAASY,EAAO,QAAW,CAAA,EAC3B,CAAE,SAAAse,EAAU,IAAAC,CAAA,EAAQnf,EACpBkB,EAAU,OAAe,OAE/B,GAAI,CAACge,GAAY,CAACC,GAAK,aACnB1e,OAAAA,EAAO,KAAK,4DAA4D,EACjE,CAAE,QAAS,EAAA,EAItB,OAAQye,EAAA,CACJ,IAAK,OACDhe,GAAQ,WAAW,2BAA2Bie,EAAI,YAAY,EAAE,EAChE,MAEJ,IAAK,OACDje,GAAQ,WAAW,2BAA2Bie,EAAI,YAAY,EAAE,EAChE,MAEJ,IAAK,SACL,IAAK,WACL,IAAK,OACL,IAAK,SAEDje,GAAQ,OAAO,UAAU,CACrB,UAAW,CACP,qBAAsBie,EAAI,GAC1B,kBAAmBD,CAAA,CACvB,CACH,EACD,MAEJ,IAAK,QAED,OAAO,KAAK,2BAA2BC,EAAI,YAAY,kBAAmB,QAAQ,EAClF,MAEJ,QACI,eAAQ,KAAK,qCAAqCD,CAAQ,EAAE,EACrD,CAAE,QAAS,EAAA,CAAM,CAGhC,MAAO,CACH,QAAS,GACT,OAAQA,EACR,SAAUC,EAAI,EAAA,CAEtB,CAiDA,eAAsBC,GAClBxe,EACAX,EACqC,CACrC,MAAMD,EAASY,EAAO,QAAW,CAAA,EAC3B,CAAE,WAAAye,EAAY,SAAAC,EAAU,OAAAC,EAAQ,UAAAC,EAAW,eAAAC,GAAmBzf,EAC9DkB,EAAU,OAAe,OAE/B,GAAI,CAACoe,GAAYA,EAAS,SAAW,EACjC,MAAM,IAAI,MAAM,eAAe,EAGnC,IAAII,EAAW,GACf,MAAMC,EAAgC,CAAE,IAAKL,CAAA,EAE7C,OAAQD,EAAA,CACJ,IAAK,SACD,GAAI,CAACE,EACD,MAAM,IAAI,MAAM,iBAAiB,EAErCG,EAAW,mDACXC,EAAK,aAAeJ,EACpB,MAEJ,IAAK,WACD,GAAI,CAACE,GAAkB,CAACD,EACpB,MAAM,IAAI,MAAM,uBAAuB,EAE3CE,EAAW,mDACPF,MAAgB,WAAaA,GAC7BC,MAAqB,gBAAkBA,GAC3C,MAEJ,QACI,MAAM,IAAI,MAAM,iBAAiBJ,CAAU,EAAE,CAAA,CAIrD,MAAMjX,EAAW,MAAMlH,GAAQ,KAAK,KAAKwe,EAAU,CAC/C,OAAQ,QACR,KAAAC,CAAA,CACH,EAED,GAAI,CAACvX,GAAU,QACX,MAAM,IAAI,MAAMA,GAAU,SAAW,gBAAgB,EAGzD,MAAO,CACH,QAAS,GACT,aAAcA,EAAS,MAAM,eAAiBkX,EAAS,OACvD,UAAWlX,EAAS,MAAM,YAAc,EACxC,OAAQA,EAAS,MAAM,QAAU,CAAA,CAAC,CAE1C,CA6CA,eAAsBwX,GAClBhf,EACAX,EACiC,CACjC,MAAMD,EAASY,EAAO,QAAW,CAAA,EAC3B,CAAE,aAAAif,EAAc,SAAAP,EAAU,QAAAQ,CAAA,EAAY9f,EACtCkB,EAAU,OAAe,OAEzBwe,EAAW,qDACXC,EAAgC,CAAA,EAEtC,GAAIE,IAAiB,WAAY,CAC7B,GAAI,CAACP,GAAYA,EAAS,SAAW,EACjC,MAAM,IAAI,MAAM,eAAe,EAEnCK,EAAK,IAAML,CACf,MACIK,EAAK,QAAUG,GAAW,CAAA,EAI9B,MAAMC,EAAQ7e,GAAQ,MAAM,SAAA,EACtBkH,EAAW,MAAM,MAAMsX,EAAU,CACnC,OAAQ,OACR,QAAS,CACL,eAAgB,mBAChB,GAAIK,EAAQ,CAAE,cAAe,UAAUA,CAAK,IAAO,CAAA,CAAC,EAExD,KAAM,KAAK,UAAUJ,CAAI,CAAA,CAC5B,EAED,GAAI,CAACvX,EAAS,GAAI,CACd,MAAM4X,EAAY,MAAM5X,EAAS,OAAO,MAAM,IAAM,IAAI,EACxD,MAAM,IAAI,MAAM4X,GAAW,SAAW,kBAAkB,CAC5D,CAEA,MAAMC,EAAO,MAAM7X,EAAS,KAAA,EACtB8X,EAAM,OAAO,IAAI,gBAAgBD,CAAI,EACrCvP,EAAI,SAAS,cAAc,GAAG,EACpCA,EAAE,KAAOwP,EAGT,MAAMra,MAAY,KACZsa,EAAU,GAAGta,EAAM,YAAA,CAAa,IAAI,OAAOA,EAAM,SAAA,EAAa,CAAC,EAAE,SAAS,EAAG,GAAG,CAAC,IAAI,OAAOA,EAAM,QAAA,CAAS,EAAE,SAAS,EAAG,GAAG,CAAC,GACnI,OAAA6K,EAAE,SAAW,UAAUyP,CAAO,QAE9B,SAAS,KAAK,YAAYzP,CAAC,EAC3BA,EAAE,MAAA,EACF,SAAS,KAAK,YAAYA,CAAC,EAC3B,OAAO,IAAI,gBAAgBwP,CAAG,EAEvB,CAAE,QAAS,EAAA,CACtB,CAkBO,SAASE,GACZxf,EACAX,EACoB,CACpB,MAAMD,EAASY,EAAO,QAAW,CAAA,EAC3B,CAAE,QAAAyf,EAAU,CAAA,CAAC,EAAMrgB,EAEzB,GAAI,CACA,aAAa,QAAQ,2BAA4B,KAAK,UAAUqgB,CAAO,CAAC,CAC5E,OAASxe,EAAG,CACRpB,OAAAA,EAAO,KAAK,2CAA4CoB,CAAC,EAClD,CAAE,QAAS,EAAA,CACtB,CAEA,MAAO,CAAE,QAAS,EAAA,CACtB,CAWO,SAASye,GACZ1f,EACAX,EACQ,CAER,MAAMsgB,GADS3f,EAAO,QAAU,CAAA,GACF,gBAAkB,CAC5C,aACA,eACA,eACA,eACA,UACA,UACA,iBACA,kBACA,SAAA,EAGJ,GAAI,CACA,MAAM4f,EAAQ,aAAa,QAAQ,0BAA0B,EAC7D,GAAIA,EAAO,CACP,MAAMC,EAAS,KAAK,MAAMD,CAAK,EAC/B,GAAI,MAAM,QAAQC,CAAM,EACpB,OAAOA,CAEf,CACJ,OAAS5e,EAAG,CACRpB,EAAO,KAAK,2CAA4CoB,CAAC,CAC7D,CAEA,OAAO0e,CACX,CAoCO,SAASG,GACZ9f,EACAX,EACQ,CACR,MAAMD,EAASY,EAAO,QAAU,CAAA,EAC1B,CAAE,GAAAuG,EAAI,eAAAwZ,EAAiB,CAAA,GAAO3gB,EAC9BkB,EAAU,OAAe,OAE/B,GAAI,CAACiG,EACD1G,OAAAA,EAAO,KAAK,sCAAsC,EAC3CkgB,EAIX,IAAIC,EACJ,GAAI,CACA,MAAMC,EAAc3f,GAAQ,aAAa,YAAA,EACrC2f,IAEAD,EADaC,EAAY,QAAA,GACV,KAEvB,MAAQ,CAER,CAGA,MAAMC,EAAa,cAAc3Z,CAAE,GAAGyZ,IAAW,OAAY,IAAIA,CAAM,GAAK,EAAE,GAE9E,GAAI,CACA,MAAMJ,EAAQ,aAAa,QAAQM,CAAU,EAC7C,GAAIN,EAAO,CACP,MAAMC,EAAS,KAAK,MAAMD,CAAK,EAC/B,GAAI,MAAM,QAAQC,CAAM,GAAKA,EAAO,OAAS,EACzChgB,OAAAA,EAAO,IAAI,2CAA2CqgB,CAAU,GAAIL,CAAM,EACnEA,CAEf,CACJ,OAAS5e,EAAG,CACRpB,EAAO,KAAK,2CAA4CoB,CAAC,CAC7D,CAEApB,OAAAA,EAAO,IAAI,gCAAgCqgB,CAAU,GAAIH,CAAc,EAChEA,CACX,CAcO,SAASI,GACZ1W,EACApK,EACI,CACJ,MAAMiB,EAAU,OAAe,OAC/B,GAAI,CAACA,EAAQ,OAEb,MAAMG,EAAcH,EAAO,MAAM,IAAA,GAAS,CAAA,EAEpCob,EAA0Bjb,EAAY,mBAAqB,CAAA,EAC3D2f,EAA0B3f,EAAY,iBAAmB,GACzD4f,EAAsB5f,EAAY,aAAe,GACjD6f,EAA6B7f,EAAY,oBAAsB,GAGrE,GAAI,CAAC2f,EAAiB,CAClB9f,EAAO,OAAO,UACVA,EAAO,IAAI,uDAAuD,GAC/D,mBAAA,EAEP,MACJ,CAIA,GADyB,CAAC,iBAAkB,UAAU,EACjC,SAAS8f,CAAe,IACrC,CAACC,GAAe,CAACC,GAAoB,CACrChgB,EAAO,OAAO,UACVA,EAAO,IAAI,qDAAqD,GAC7D,oCAAA,EAEP,MACJ,CAIJA,EAAO,MAAM,IAAI,CACb,gBAAiB,CACb,cAAAob,EACA,YAAa0E,EACb,UAAWC,EACX,eAAgBC,CAAA,CACpB,CACH,EAGDhgB,EAAO,OAAO,OAAO,oBAAoB,CAC7C,CAeA,eAAsBigB,GAClB9W,EACApK,EACa,CACb,MAAMiB,EAAU,OAAe,OAC/B,GAAI,CAACA,EAAQ,OAGb,MAAMuc,GADcvc,EAAO,MAAM,IAAA,GAAS,CAAA,GACV,gBAEhC,GAAI,CAACuc,GAAe,CAACA,EAAY,eAAe,OAAQ,CACpDvc,EAAO,OAAO,QACVA,EAAO,IAAI,0CAA0C,GAClD,gBAAA,EAEP,MACJ,CAEA,MAAMye,EAAgC,CAClC,IAAKlC,EAAY,cACjB,aAAcA,EAAY,WAAA,EAE1BA,EAAY,YACZkC,EAAK,WAAalC,EAAY,WAE9BA,EAAY,iBACZkC,EAAK,gBAAkBlC,EAAY,gBAGvC,GAAI,CACA,MAAMvc,EAAO,IAAI,MACb,mDACAye,CAAA,EAGJze,EAAO,OAAO,UACVA,EAAO,IAAI,4CAA4C,GACpD,iBAAA,EAIPA,EAAO,MAAM,SAAS,CAClB,cAAe,CAAA,EACf,UAAW,GACX,gBAAiB,GACjB,YAAa,GACb,mBAAoB,EAAA,CACvB,EACDA,EAAO,MAAM,IAAI,CACb,gBAAiB,IAAA,CACpB,EAGDA,EAAO,YAAY,UAAU,QAAQ,EAGrCA,EAAO,OAAO,QAAA,CAClB,OAASqH,EAAY,CACjB9H,EAAO,MAAM,kCAAmC8H,CAAK,EAKrD,MAAMsT,GADYtT,GAAO,UAAU,MAAQA,GAAO,MAAQ,CAAA,IACzB,SAAWA,GAAO,QACnDrH,EAAO,OAAO,QACV2a,GACG3a,EAAO,IAAI,0CAA0C,GACrD,gBAAA,CAEX,CACJ,CCh5BA,MAAMT,GAAW,OAAe,QAAQ,eAAe,kBAAkB,GAAM,CAC3E,IAAK,IAAIC,IAAoB,QAAQ,IAAI,qBAAsB,GAAGA,CAAI,EACtE,KAAM,IAAIA,IAAoB,QAAQ,KAAK,qBAAsB,GAAGA,CAAI,EACxE,MAAO,IAAIA,IAAoB,QAAQ,MAAM,qBAAsB,GAAGA,CAAI,CAC9E,EAyEA,eAAsB0gB,GAClBxgB,EACAX,EACqC,CACrC,MAAMD,EAASY,EAAO,QAAW,CAAA,EAC3B,CAAE,SAAAse,EAAU,IAAAC,CAAA,EAAQnf,EACpBkB,EAAU,OAAe,OAE/B,GAAI,CAACge,GAAY,CAACC,GAAK,GACnB1e,OAAAA,GAAO,KAAK,oDAAoD,EACzD,CAAE,QAAS,EAAA,EAItB,OAAQye,EAAA,CACJ,IAAK,OAEDhe,GAAQ,WAAW,6BAA6Bie,EAAI,YAAY,OAAO,EACvE,MAEJ,IAAK,OAEDje,GAAQ,OAAO,SAAS,CACpB,YAAa,CAAE,kBAAmBie,CAAA,EAClC,GAAI,CAAE,cAAe,EAAA,CAAK,CAC7B,EACD,MAEJ,IAAK,SACD,GAAI,CAMA,MAAMvD,GAJW,MAAM1a,GAAQ,KAAK,IAChC,iDAAiDie,EAAI,EAAE,aAAA,IAGpC,KAGvBje,GAAQ,OAAO,SAAS,CACpB,YAAa,CACT,oBAAqBie,EACrB,UAAWvD,GAAM,WAAa,GAC9B,kBAAmBA,GAAM,QAAU,KACnC,YAAaA,GAAM,aAAe,IAAA,EAEtC,GAAI,CAAE,gBAAiB,EAAA,CAAK,CAC/B,CACL,OAASrT,EAAO,CACZ9H,GAAO,MAAM,2CAA4C8H,CAAK,EAE9DrH,GAAQ,OAAO,SAAS,CACpB,YAAa,CACT,oBAAqBie,EACrB,UAAW,GACX,kBAAmB,wBACnB,YAAa,IAAA,EAEjB,GAAI,CAAE,gBAAiB,EAAA,CAAK,CAC/B,CACL,CACA,MAEJ,QACI1e,OAAAA,GAAO,KAAK,uCAAuCye,CAAQ,EAAE,EACtD,CAAE,QAAS,EAAA,CAAM,CAGhC,MAAO,CACH,QAAS,GACT,OAAQA,EACR,SAAUC,EAAI,EAAA,CAEtB,CClJA,MAAM1e,EAAW,OAAe,QAAQ,eAAe,kBAAkB,GAAM,CAC3E,IAAK,IAAIC,IAAoB,QAAQ,IAAI,qBAAsB,GAAGA,CAAI,EACtE,KAAM,IAAIA,IAAoB,QAAQ,KAAK,qBAAsB,GAAGA,CAAI,EACxE,MAAO,IAAIA,IAAoB,QAAQ,MAAM,qBAAsB,GAAGA,CAAI,CAC9E,EAsBA,SAAS2gB,IAAmC,CACxC,OAAI,OAAO,SAAa,IACb,GAEG,SAAS,cAAgC,mCAAmC,GAE5E,OAAO,KAAA,GAAU,EACnC,CAgBO,SAASC,GACZjX,EACApK,EACI,CACJ,MAAMiB,EAAU,OAAe,OAE/B,GAAI,CAACA,GAAQ,MAAO,CAChBT,EAAO,KAAK,iDAAiD,EAC7D,MACJ,CAEcS,EAAO,MAAM,SAAA,EAG3B,MAAMqgB,EADethB,GAAkB,MAAM,MAEtCiB,EAAO,YAAY,MAAM,OAAO,GAAG,KAE1C,GAAI,CAACqgB,EAAW,CACZ9gB,EAAO,KAAK,wCAAwC,EACpD,MACJ,CAGAS,EAAO,MAAM,SAAS,CAClB,KAAM,CACF,eAAgBqgB,EAAU,gBAAkB,GAC5C,gBAAiBA,EAAU,iBAAmB,GAC9C,cAAeA,EAAU,eAAiB,GAC1C,kBAAmBA,EAAU,mBAAqB,GAClD,kBAAmBA,EAAU,mBAAqB,GAClD,yBAA0BA,EAAU,0BAA4B,GAChE,cAAeA,EAAU,eAAiB,GAC1C,WAAYA,EAAU,YAAc,EAAA,CACxC,CACH,EAED9gB,EAAO,IAAI,gCAAgC,CAC/C,CAiBO,SAAS+gB,GACZ5gB,EACAX,EACI,CACJ,MAAMiB,EAAU,OAAe,OACzB,CAAE,SAAAmB,CAAA,EAAazB,EAAO,QAAU,CAAA,EAEtC,GAAI,CAACM,GAAQ,OAAS,CAACmB,EACnB,OAIJ,MAAMqF,EADQxG,EAAO,MAAM,SAAA,EACM,kBAAoB,CAAA,EAErD,IAAI4I,EACApC,EAAS,SAASrF,CAAQ,EAC1ByH,EAAcpC,EAAS,OAAQP,GAAeA,IAAO9E,CAAQ,EAE7DyH,EAAc,CAAC,GAAGpC,EAAUrF,CAAQ,EAKxC,MAAMof,GADYvgB,EAAO,YAAY,MAAM,OAAO,GAAG,MACpB,SAAW,CAAA,GAAI,OAC1CwgB,EAAY5X,EAAY,SAAW2X,GAAgBA,EAAe,EAExEvgB,EAAO,MAAM,SAAS,CAClB,iBAAkB4I,EAClB,UAAA4X,CAAA,CACH,CACL,CAgBO,SAASC,GACZtX,EACApK,EACI,CACJ,MAAMiB,EAAU,OAAe,OAE/B,GAAI,CAACA,GAAQ,MACT,OAIJ,MAAM0gB,EADQ1gB,EAAO,MAAM,SAAA,EACI,UAEzBqB,EADYrB,EAAO,YAAY,MAAM,OAAO,GAAG,MAC1B,SAAW,CAAA,EAEtC,GAAI0gB,EAEA1gB,EAAO,MAAM,SAAS,CAClB,iBAAkB,CAAA,EAClB,UAAW,EAAA,CACd,MACE,CAEH,MAAM2gB,EAAStf,EAAQ,IAAKiB,GAAwBA,EAAI,EAAE,EAC1DtC,EAAO,MAAM,SAAS,CAClB,iBAAkB2gB,EAClB,UAAW,EAAA,CACd,CACL,CACJ,CAiBO,SAASC,GACZlhB,EAMAX,EACI,CACJ,MAAMiB,EAAU,OAAe,OAC/B,GAAI,CAACA,EAAQ,OAEb,MAAMlB,EAASY,EAAO,QAAU,CAAA,EAC1BmhB,EAA6B/hB,EAAO,kBAAoB,CAAA,EACxDgiB,EAA2BhiB,EAAO,kBAAoB,GACtDiiB,EAAyBjiB,EAAO,gBAAkB,GAKlDkiB,EAA8BliB,EAAO,qBAAuBqhB,GAAA,EAGlE,GAAI,CAACW,EAAkB,CACnB9gB,EAAO,OAAO,UACVA,EAAO,IAAI,uDAAuD,GAC/D,mBAAA,EAEP,MACJ,CAGA,GAAI6gB,EAAiB,SAAW,EAAG,CAC/B7gB,EAAO,OAAO,UACVA,EAAO,IAAI,sDAAsD,GAC9D,iBAAA,EAEP,MACJ,CAGA,GAAI8gB,IAAqB,YAAa,CAClC,MAAMG,EAAWniB,EAAO,UAAY,GAC9BuhB,EAAYrgB,EAAO,YAAY,MAAM,OAAO,GAAG,KAC/CqB,EAAUgf,GAAW,SAAW,CAAA,EAGhCa,EAAcL,EACf,IAAK1f,GAAqB,CACvB,MAAMmB,EAAMjB,EAAQ,KAAME,GAAsBA,EAAE,KAAOJ,CAAQ,EACjE,MAAI,CAACmB,GAAOA,EAAI,gBAAkB,YAAoB,KAC/C,CACH,GAAIA,EAAI,GACR,aAAcA,EAAI,aAClB,oBAAqBA,EAAI,oBACzB,cAAeA,EAAI,kBAAkB,eAAiB,GACtD,WAAYA,EAAI,WAChB,SAAUA,EAAI,SACd,gBAAiBA,EAAI,SACrB,cAAeA,EAAI,cACnB,oBAAqBA,EAAI,qBAAuBA,EAAI,aAAA,CAE5D,CAAC,EACA,OAAO,OAAO,EAEnB,GAAI4e,EAAY,SAAW,EAAG,CAC1BlhB,EAAO,OAAO,UACVA,EAAO,IAAI,yDAAyD,GACjE,sCAAA,EAEP,MACJ,CAEA,GAAIihB,EAAU,CAMVjhB,EAAO,MAAM,SAAS,CAClB,YAAAkhB,EACA,aAAc,GACd,mBAAoB,GACpB,SAAU,GACV,eAAgB,WAChB,eAAgB,KAChB,cAAe,GACf,aAAc,GACd,YAAa,KACb,uBAAwB,IAAA,CAC3B,EACDlhB,EAAO,OAAO,OAAO,oBAAoB,EAGzC,MAAMmhB,EAAUd,GAAW,cAAgB,GACvCc,GACAnhB,EAAO,WAAW,CACd,QAAS,yCACT,OAAQ,CAAE,QAAAmhB,EAAS,YAAAD,CAAA,CAAY,CAClC,CAET,KAAO,CAEH,MAAME,EAA2C,CAAA,EACjD,UAAWlP,KAAQgP,EACfE,EAAiBlP,EAAK,EAAE,EAAIA,EAAK,SAErClS,EAAO,MAAM,SAAS,CAClB,iBAAkBkhB,EAClB,iBAAAE,EACA,iBAAAN,CAAA,CACH,EACD9gB,EAAO,OAAO,OAAO,4BAA4B,CACrD,CACA,MACJ,CAIA,GADyB,CAAC,iBAAkB,WAAY,WAAW,EAC9C,SAAS8gB,CAAgB,IACtC,CAACC,GAAkB,CAACC,GAAqB,CACzChhB,EAAO,OAAO,UACVA,EAAO,IAAI,qDAAqD,GAC7D,oCAAA,EAEP,MACJ,CAKJ,MAAMqB,EADYrB,EAAO,YAAY,MAAM,OAAO,GAAG,MAC1B,SAAW,CAAA,EAChCqhB,EAA8C,CAAA,EAC9CD,EAA2C,CAAA,EAEjD,UAAWjgB,KAAY0f,EAAkB,CACrC,MAAMve,EAAMjB,EAAQ,KAAME,GAAsBA,EAAE,KAAOJ,CAAQ,EAC5DmB,IAEL+e,EAAiB,KAAK,CAClB,GAAI/e,EAAI,GACR,aAAcA,EAAI,aAClB,oBAAqBA,EAAI,oBACzB,IAAKA,EAAI,IACT,cAAeA,EAAI,kBAAkB,eAAiB,GACtD,WAAYA,EAAI,WAChB,eAAgBA,EAAI,kBAAkB,gBAAkBA,EAAI,WAC5D,SAAUA,EAAI,SACd,cAAeA,EAAI,aAAA,CACtB,EACD8e,EAAiB9e,EAAI,EAAE,EAAIA,EAAI,SACnC,CAGAtC,EAAO,MAAM,SAAS,CAClB,iBAAAqhB,EACA,iBAAAD,EACA,iBAAAN,EACA,eAAAC,EACA,oBAAAC,CAAA,CACH,EAEDhhB,EAAO,OAAO,OAAO,4BAA4B,CACrD,CAkBA,eAAsBshB,GAClB5hB,EAQAX,EACa,CACb,MAAMiB,EAAU,OAAe,OACzB,CAAE,QAAAmhB,CAAA,EAAYzhB,EAAO,QAAU,CAAA,EAErC,GAAI,CAACM,GAAQ,OAAS,CAACmhB,EAAS,CAC5B5hB,EAAO,KAAK,+CAA+C,EAC3D,MACJ,CAMA,MAAMgiB,EAAYvhB,EAAO,MAAM,WAAA,GAAgB,CAAA,EACzC6gB,EAAmBnhB,EAAO,QAAQ,kBAAoB6hB,EAAU,iBAChET,EAAmBphB,EAAO,QAAQ,kBAAoB6hB,EAAU,iBAChER,EAAiBrhB,EAAO,QAAQ,gBAAkB6hB,EAAU,eAC5DP,EAAsBthB,EAAO,QAAQ,qBACpC6hB,EAAU,qBACVpB,GAAA,EACDiB,EAAmB1hB,EAAO,QAAQ,kBAAoB6hB,EAAU,iBAEtE,GAAI,CAACV,GAAoBA,EAAiB,SAAW,EAAG,CACpDthB,EAAO,KAAK,8CAA8C,EAC1D,MACJ,CAEA,GAAI,CAACuhB,EAAkB,CACnBvhB,EAAO,KAAK,8CAA8C,EAC1D,MACJ,CAIA,MAAM8B,EADYrB,EAAO,YAAY,MAAM,OAAO,GAAG,MAC1B,SAAW,CAAA,EAUhCye,EAAgC,CAClC,MATUoC,EAAiB,IAAK1f,GAAqB,CACrD,MAAM+D,EAAS7D,EAAQ,KAAMiB,GAAwBA,EAAI,KAAOnB,CAAQ,EACxE,MAAO,CACH,UAAWA,EACX,SAAUigB,IAAmBjgB,CAAQ,GAAK+D,GAAQ,UAAY,CAAA,CAEtE,CAAC,EAIG,OAAQ4b,CAAA,EAGRC,IACAtC,EAAK,WAAasC,GAElBC,IACAvC,EAAK,gBAAkBuC,GAG3B,GAAI,CACA,MAAM9Z,EAAW,MAAMlH,EAAO,IAAI,MAC9B,+CAA+CmhB,CAAO,uBACtD1C,CAAA,EAGJ,GAAIvX,GAAU,QAAS,CACnB,MAAMwO,EAAI1V,EAAO,EACXwhB,EAAeta,EAAS,MAAM,eAAiB2Z,EAAiB,OACtE7gB,EAAO,OAAO,UACV0V,IAAI,mEAAoE,CAAE,MAAO8L,EAAc,GAC5F,GAAGA,CAAY,oBAAA,EAItBxhB,EAAO,OAAO,QAAQ,4BAA4B,EAClDA,EAAO,WAAW,CAAE,QAAS,oBAAqB,OAAQ,CAAE,aAAc,OAAA,EAAW,EACrFA,EAAO,WAAW,CAAE,QAAS,oBAAqB,OAAQ,CAAE,aAAc,YAAA,EAAgB,EAC1FA,EAAO,MAAM,SAAS,CAClB,iBAAkB,CAAA,EAClB,UAAW,GACX,iBAAkB,GAClB,eAAgB,GAChB,oBAAqB,GACrB,iBAAkB,CAAA,CAAC,CACtB,CACL,KACI,OAAM,IAAI,MAAMkH,GAAU,SAAW,gBAAgB,CAE7D,OAASG,EAAY,CACjB9H,EAAO,MAAM,4CAA6C8H,CAAK,EAI/D,MAAMsT,GADYtT,GAAO,UAAU,MAAQA,GAAO,MAAQ,CAAA,IACzB,SAAWA,GAAO,QACnDrH,EAAO,OAAO,QACV2a,GACG3a,EAAO,IAAI,iEAAiE,GAC5E,gBAAA,CAEX,CACJ,CAiBA,eAAsByhB,GAClB/hB,EACAX,EACa,CACb,MAAMiB,EAAU,OAAe,OACzB,CAAE,QAAAmhB,CAAA,EAAYzhB,EAAO,QAAU,CAAA,EAErC,GAAI,CAACM,GAAQ,OAAS,CAACmhB,EACnB,OAIJ,MAAMO,EADQ1hB,EAAO,MAAM,SAAA,EACH,MAAM,YAAc,GAE5C,GAAI,CACA,MAAMkH,EAAW,MAAMlH,EAAO,IAAI,MAC9B,+CAA+CmhB,CAAO,GACtD,CAAE,WAAYO,CAAA,CAAU,EAG5B,GAAIxa,GAAU,QACVlH,EAAO,OAAO,UACVA,EAAO,IAAI,gEAAgE,GACxE,kBAAA,MAGP,OAAM,IAAI,MAAMkH,GAAU,OAAO,CAEzC,OAASG,EAAY,CACjB9H,EAAO,MAAM,yBAA0B8H,CAAK,EAC5CrH,EAAO,OAAO,QAAQqH,GAAO,SAAW,gBAAgB,CAC5D,CACJ,CAiBO,SAASsa,GACZjiB,EACAX,EACI,CACJ,MAAMiB,EAAU,OAAe,OAC/B,GAAI,CAACA,GAAQ,MAAO,OAEpB,KAAM,CAAE,SAAAmB,EAAU,YAAAygB,CAAA,EAAgBliB,EAAO,QAAU,CAAA,EACnD,GAAI,CAACyB,GAAY,CAACygB,EAAa,OAG/B,MAAMC,EAAWniB,EAAO,QAAQ,OAAUA,EAAe,QAAQ,QAAQ,MACnE6f,EAAS,SAAS,OAAOsC,CAAQ,EAAG,EAAE,EACtCC,EAAU,KAAK,IAAI,EAAG,KAAK,IAAI,MAAMvC,CAAM,EAAIqC,EAAcrC,EAAQqC,CAAW,CAAC,EAGjFG,EAAoB,CAAE,GADd/hB,EAAO,MAAM,SAAA,EACW,kBAAoB,CAAA,CAAC,EAC3D+hB,EAAkB5gB,CAAQ,EAAI2gB,EAE9B9hB,EAAO,MAAM,SAAS,CAAE,iBAAkB+hB,EAAmB,CACjE,CAwBO,SAASC,GACZ7Y,EACApK,EACI,CACJ,MAAMiB,EAAU,OAAe,OAC/B,GAAI,CAACA,GAAQ,MAAO,CAChBT,EAAO,KAAK,sDAAsD,EAClE,MACJ,CAEA,MAAM8gB,EAAYrgB,EAAO,YAAY,MAAM,OAAO,GAAG,KAC/CiiB,EAAgB5B,GAAW,gBAAkB,GAC7C6B,EAAgB7B,GAAW,kBAAoB,EAGrDrgB,EAAO,MAAM,SAAS,CAClB,cAAAiiB,EACA,cAAAC,EACA,qBAAsB,KACtB,oBAAqB,GACrB,kBAAmB,EAAA,CACtB,EACDliB,EAAO,OAAO,OAAO,uBAAuB,CAChD,CAyBA,eAAsBmiB,GAClBziB,EAMAX,EACa,CACb,MAAMiB,EAAU,OAAe,OACzB,CAAE,QAAAmhB,CAAA,EAAYzhB,EAAO,QAAU,CAAA,EAErC,GAAI,CAACM,GAAQ,OAAS,CAACmhB,EAAS,CAC5B5hB,EAAO,KAAK,kCAAkC,EAC9C,MACJ,CAQA,MAAM6iB,EAAQpiB,EAAO,MAAM,SAAA,EACrBqiB,EAAS3iB,EAAO,QAAQ,QAAU0iB,EAAM,cACxCH,EAAgBviB,EAAO,QAAQ,eAAiB0iB,EAAM,eAAiB,GAEvEE,EAAkB5iB,EAAO,QAAQ,mBAAqB0iB,EAAM,mBAAqB,GACjFG,EAAoBD,IAAoB,IAAQA,IAAoB,OAE1EtiB,EAAO,MAAM,SAAS,CAAE,oBAAqB,GAAM,qBAAsB,KAAM,EAE/E,GAAI,CACA,MAAMkH,EAAW,MAAMlH,EAAO,IAAI,MAC9B,+CAA+CmhB,CAAO,mBACtD,CAAE,OAAAkB,EAAQ,eAAgBJ,EAAe,oBAAqBM,CAAA,CAAkB,EAGpF,GAAIrb,GAAU,QACVlH,EAAO,MAAM,SAAS,CAAE,oBAAqB,GAAO,qBAAsB,KAAM,EAChFA,EAAO,OAAO,UACVA,EAAO,IAAI,4EAA4E,GACpF,wBAAA,EAEPA,EAAO,OAAO,QAAQ,uBAAuB,EAC7CA,EAAO,WAAW,CAAE,QAAS,oBAAqB,OAAQ,CAAE,aAAc,OAAA,EAAW,EACrFA,EAAO,WAAW,CAAE,QAAS,oBAAqB,OAAQ,CAAE,aAAc,YAAA,EAAgB,MAE1F,OAAM,IAAI,MAAMkH,GAAU,SAAW,wBAAwB,CAErE,OAASG,EAAY,CACjB9H,EAAO,MAAM,uBAAwB8H,CAAK,EAE1C,MAAMyX,EAAYzX,GAAO,UAAU,MAAQA,GAAO,MAAQ,CAAA,EAIpDmb,EAAgB1D,GAAW,QAAQ,QAAUA,GAAW,SAAWzX,GAAO,SAAW,GACrFob,EAAkBziB,EAAO,IAAI,2EAA2E,GACvG,iBAGPA,EAAO,MAAM,SAAS,CAClB,oBAAqB,GACrB,qBAAsB,CAAE,OAAQ,CAACwiB,GAAiBC,CAAe,CAAA,CAAE,CACtE,EACDziB,EAAO,OAAO,QAAQwiB,GAAiBC,CAAe,CAC1D,CACJ,CCvsBA,MAAMljB,GAAW,OAAe,QAAQ,eAAe,kBAAkB,GAAM,CAC3E,IAAK,IAAIC,IAAoB,QAAQ,IAAI,qBAAsB,GAAGA,CAAI,EACtE,KAAM,IAAIA,IAAoB,QAAQ,KAAK,qBAAsB,GAAGA,CAAI,EACxE,MAAO,IAAIA,IAAoB,QAAQ,MAAM,qBAAsB,GAAGA,CAAI,CAC9E,EASA,IAAIkjB,EAA8D,KAQ3D,SAASC,IAA+B,CACvCD,IACA,aAAaA,CAAqB,EAClCA,EAAwB,KACxBnjB,GAAO,IAAI,6CAA6C,EAEhE,CAgBO,SAASqjB,GACZljB,EACAX,EACI,CACJ,MAAMiB,EAAU,OAAe,OAC/B,GAAI,CAACA,GAAQ,MAAO,OAEpB,KAAM,CAAE,SAAAmB,EAAU,YAAAygB,EAAa,QAAAT,GAAYzhB,EAAO,QAAU,CAAA,EAC5D,GAAI,CAACyB,GAAY,CAACygB,GAAe,CAACT,EAAS,OAE3C,MAAMU,EAAWniB,EAAO,QAAQ,OAAUA,EAAe,QAAQ,QAAQ,MACnE6f,EAAS,SAAS,OAAOsC,CAAQ,EAAG,EAAE,EACtCC,EAAU,KAAK,IAAI,EAAG,KAAK,IAAI,MAAMvC,CAAM,EAAIqC,EAAcrC,EAAQqC,CAAW,CAAC,EAIjFV,GADQlhB,EAAO,MAAM,SAAA,EACA,aAAe,CAAA,GAAI,IAAKkS,GAC3CA,EAAK,KAAO/Q,EACL,CAAE,GAAG+Q,EAAM,gBAAiB4P,CAAA,EAEhC5P,CACV,EAEDlS,EAAO,MAAM,SAAS,CAAE,YAAAkhB,CAAA,CAAa,EAMrC,MAAM2B,EAAsB,OAAe,kBACvCH,GACA,aAAaA,CAAqB,EAEtCA,EAAwB,WAAW,IAAM,CACrC,MAAMI,EAAmB,OAAe,kBACvC,OAAe,kBAAoBD,EACpC,GAAI,CACAE,GAA6B/iB,EAAQmhB,CAAO,CAChD,QAAA,CACK,OAAe,kBAAoB2B,CACxC,CACJ,EAAG,GAAG,CACV,CAgBA,eAAsBE,GAClBtjB,EACAX,EACa,CACb,MAAMiB,EAAU,OAAe,OAC/B,GAAI,CAACA,GAAQ,MAAO,OAEpB,KAAM,CAAE,QAAAmhB,EAAS,YAAAD,CAAA,EAAgBxhB,EAAO,QAAU,CAAA,EAC7CyhB,GAEL,MAAM4B,GAA6B/iB,EAAQmhB,EAASD,CAAW,CACnE,CAQA,eAAe6B,GAA6B/iB,EAAamhB,EAAiB8B,EAAyC,CAC/G,MAAMb,EAAQpiB,EAAO,MAAM,SAAA,EACrBkhB,EAAc+B,GAAoBb,EAAM,aAAe,CAAA,EACvDc,EAAiBd,EAAM,gBAAkB,WAE/C,GAAIlB,EAAY,SAAW,EAAG,CAC1BlhB,EAAO,MAAM,SAAS,CAAE,eAAgB,KAAM,EAC9C,MACJ,CAEA,MAAM8V,EAAQoL,EAAY,IAAKhP,IAAe,CAC1C,gBAAiBA,EAAK,GACtB,gBAAiBA,EAAK,eAAA,EACxB,EAEFlS,EAAO,MAAM,SAAS,CAAE,cAAe,GAAM,EAE7C,GAAI,CACA,MAAMkH,EAAW,MAAMlH,EAAO,IAAI,KAC9B,+CAA+CmhB,CAAO,mBACtD,CAAE,MAAArL,EAAO,gBAAiBoN,CAAA,CAAe,EAG7C,GAAIhc,GAAU,QACVlH,EAAO,MAAM,SAAS,CAClB,eAAgBkH,EAAS,KACzB,cAAe,GACf,YAAa,IAAA,CAChB,MAED,OAAM,IAAI,MAAMA,GAAU,SAAW,iBAAiB,CAE9D,OAASG,EAAY,CACjB9H,GAAO,MAAM,6BAA8B8H,CAAK,EAChDrH,EAAO,MAAM,SAAS,CAClB,cAAe,GACf,eAAgB,IAAA,CACnB,EACDA,EAAO,OAAO,QACVA,EAAO,IAAI,mEAAmE,GAC3E,qBAAA,CAEX,CACJ,CAgBO,SAASmjB,GACZzjB,EACAX,EACI,CACJ,MAAMiB,EAAU,OAAe,OAC/B,GAAI,CAACA,GAAQ,MAAO,OAEpB,KAAM,CAAE,SAAAojB,EAAU,QAAAjC,CAAA,EAAYzhB,EAAO,QAAU,CAAA,EAC/C,GAAI,CAAC0jB,GAAY,CAACjC,EAAS,OAE3BnhB,EAAO,MAAM,SAAS,CAAE,eAAgBojB,EAAU,EAIlD,MAAMP,EAAsB,OAAe,kBACvCH,GACA,aAAaA,CAAqB,EAEtCA,EAAwB,WAAW,IAAM,CACrC,MAAMI,EAAmB,OAAe,kBACvC,OAAe,kBAAoBD,EACpC,GAAI,CACAE,GAA6B/iB,EAAQmhB,CAAO,CAChD,QAAA,CACK,OAAe,kBAAoB2B,CACxC,CACJ,EAAG,GAAG,CACV,CAiBA,eAAsBO,GAClB3jB,EAQAX,EACa,CACb,MAAMiB,EAAU,OAAe,OAC/B,GAAI,CAACA,GAAQ,MAAO,OAEpB,KAAM,CAAE,QAAAmhB,CAAA,EAAYzhB,EAAO,QAAU,CAAA,EACrC,GAAI,CAACyhB,EAAS,OAEd,MAAMiB,EAAQpiB,EAAO,MAAM,SAAA,EACrBkhB,EAAcxhB,EAAO,QAAQ,aAAe0iB,EAAM,aAAe,CAAA,EACjEkB,EAAe5jB,EAAO,QAAQ,cAAgB0iB,EAAM,cAAgB,GACpEmB,EAAqB7jB,EAAO,QAAQ,oBAAsB0iB,EAAM,oBAAsB,GACtFnB,EAAWvhB,EAAO,QAAQ,UAAY0iB,EAAM,UAAY,GACxDc,EAAiBxjB,EAAO,QAAQ,gBAAkB0iB,EAAM,gBAAkB,WAM1E3D,EAA4B,CAC9B,KAAM,UACN,UAAWwC,EACX,gBAAiBiC,EACjB,MAAOhC,EAAY,IAAKhP,IAAe,CACnC,gBAAiBA,EAAK,GACtB,gBAAiBA,EAAK,eAAA,EACxB,CAAA,EAGFoR,IACA7E,EAAK,OAAS6E,GAEdC,IACA9E,EAAK,cAAgB8E,GAGzBvjB,EAAO,MAAM,SAAS,CAAE,aAAc,GAAM,YAAa,KAAM,uBAAwB,KAAM,EAE7F,GAAI,CACA,MAAMkH,EAAW,MAAMlH,EAAO,IAAI,KAC9B,+CAA+CmhB,CAAO,UACtD1C,CAAA,EAGJ,GAAIvX,GAAU,QACVlH,EAAO,MAAM,SAAS,CAAE,aAAc,GAAO,EAC7CA,EAAO,OAAO,UACVA,EAAO,IAAI,kEAAkE,GAC1E,cAAA,EAGPA,EAAO,OAAO,QAAQ,oBAAoB,EAC1CA,EAAO,WAAW,CAAE,QAAS,oBAAqB,OAAQ,CAAE,aAAc,OAAA,EAAW,EACrFA,EAAO,WAAW,CAAE,QAAS,oBAAqB,OAAQ,CAAE,aAAc,YAAA,EAAgB,EAG1FA,EAAO,MAAM,SAAS,CAClB,iBAAkB,CAAA,EAClB,UAAW,GACX,iBAAkB,EAAA,CACrB,MAED,OAAM,IAAI,MAAMkH,GAAU,SAAW,eAAe,CAE5D,OAASG,EAAY,CACjB9H,GAAO,MAAM,2BAA4B8H,CAAK,EAE9C,MAAMyX,EAAazX,GAAe,UAAU,MAASA,GAAe,KAK9Dmc,GAJcnc,GAAe,UAAU,QAAWA,GAAe,UAI9B,IACnCoc,EAAU3E,GAAW,QAAQ,OAC7B4E,EAAmB5E,GAAW,QAAU,KACxC0D,EAAgBiB,GAAW3E,GAAW,SAAWzX,GAAO,SAAW,GACnEob,EAAkBziB,EAAO,IAAI,iEAAiE,GAC7F,iBAEHwjB,GAEAxjB,EAAO,MAAM,SAAS,CAClB,aAAc,GACd,YAAa,KACb,uBAAwB0jB,CAAA,CAC3B,EACD1jB,EAAO,OAAO,QAAQwiB,GAAiBC,CAAe,IAGtDziB,EAAO,MAAM,SAAS,CAClB,aAAc,GACd,YAAawiB,GAAiBC,EAC9B,uBAAwB,IAAA,CAC3B,EACDziB,EAAO,OAAO,QAAQwiB,GAAiBC,CAAe,EAE9D,CACJ,CCvUA,MAAMljB,GAAW,OAAe,QAAQ,eAAe,sBAAsB,GAAM,CAC/E,IAAK,IAAIC,IAAoB,QAAQ,IAAI,yBAA0B,GAAGA,CAAI,EAC1E,KAAM,IAAIA,IAAoB,QAAQ,KAAK,yBAA0B,GAAGA,CAAI,EAC5E,MAAO,IAAIA,IAAoB,QAAQ,MAAM,yBAA0B,GAAGA,CAAI,CAClF,EAqBA,IAAImkB,EAAkE,KAMtE,SAASC,GAAqB5jB,EAAsB,CAChD,MAAM6e,EAAQ7e,GAAQ,OAAO,MAAM,SAAS,GAAG,gBACzC6jB,EAAc7jB,GAAQ,YAAY,MAAM,OAAO,GAAG,MAAM,aAC9D,MAAO,GAAQ6e,GAASgF,EAC5B,CAOO,SAASC,IAAmC,CAC3CH,IACA,aAAaA,CAAyB,EACtCA,EAA4B,KAEpC,CAWO,SAASI,GACZrkB,EACAX,EACI,CACJ,MAAMiB,EAAU,OAAe,OAC/B,GAAI,CAACA,GAAQ,MAAO,OAEpB,KAAM,CAAE,SAAAmB,CAAA,EAAazB,EAAO,QAAU,CAAA,EACtC,GAAI,CAACyB,EAAU,OAGf,MAAMyF,EAAwB,CAAC,GADjB5G,EAAO,MAAM,SAAA,EACc,iBAAmB,CAAA,CAAG,EACzD6I,EAAMjC,EAAY,QAAQzF,CAAQ,EAEpC0H,GAAO,EACPjC,EAAY,OAAOiC,EAAK,CAAC,EAEzBjC,EAAY,KAAKzF,CAAQ,EAK7B,MAAM6iB,GADYhkB,EAAO,YAAY,MAAM,OAAO,GAAG,MAClB,SAAW,CAAA,GACzC,OAAQsC,GAAaA,EAAI,gBAAkB,WAAW,EACtD,IAAKA,GAAaA,EAAI,EAAE,EACvB2hB,EAAcD,EAAe,OAAS,GACxCA,EAAe,MAAO/d,GAAeW,EAAY,SAASX,CAAE,CAAC,EAEjEjG,EAAO,MAAM,SAAS,CAClB,gBAAiB4G,EACjB,eAAgBqd,CAAA,CACnB,CACL,CAUO,SAASC,GACZ/a,EACApK,EACI,CACJ,MAAMiB,EAAU,OAAe,OAC/B,GAAI,CAACA,GAAQ,MAAO,OAEpB,MAAMoiB,EAAQpiB,EAAO,MAAM,SAAA,EAErBgkB,GADYhkB,EAAO,YAAY,MAAM,OAAO,GAAG,MACR,SAAW,CAAA,GACnD,OAAQsC,GAAaA,EAAI,gBAAkB,WAAW,EACtD,IAAKA,GAAaA,EAAI,EAAE,EAEvB6hB,EAAe,CAAC/B,EAAM,eAE5BpiB,EAAO,MAAM,SAAS,CAClB,gBAAiBmkB,EAAeH,EAAiB,CAAA,EACjD,eAAgBG,CAAA,CACnB,CACL,CAWO,SAASC,GACZ1kB,EACAX,EACI,CACJ,MAAMiB,EAAU,OAAe,OAC/B,GAAI,CAACA,GAAQ,MAAO,OAEpB,MAAMqgB,EAAYrgB,EAAO,YAAY,MAAM,OAAO,GAAG,KACrD,GAAI,CAACqgB,EAAW,OAGhB,MAAMgE,EADQrkB,EAAO,MAAM,SAAA,EACa,iBAAmB,CAAA,EAG3D,GAAIqkB,EAAgB,SAAW,EAAG,CAC9BrkB,EAAO,OAAO,UACVA,EAAO,IAAI,oDAAoD,GAC5D,iBAAA,EAEP,MACJ,CAGA,MAAMkhB,GADUb,EAAU,SAAW,CAAA,GAEhC,OAAQ/d,GAAa+hB,EAAgB,SAAS/hB,EAAI,EAAE,GAAKA,EAAI,gBAAkB,WAAW,EAC1F,IAAKA,IAAc,CAChB,GAAIA,EAAI,GACR,aAAcA,EAAI,cAAgB,GAClC,oBAAqBA,EAAI,qBAAuB,GAChD,cAAeA,EAAI,kBAAkB,eAAiB,GACtD,WAAYA,EAAI,YAAc,EAC9B,SAAUA,EAAI,UAAY,EAC1B,gBAAiBA,EAAI,UAAY,EACjC,cAAeA,EAAI,eAAiB,GACpC,SAAU,EAAA,EACZ,EAEN,GAAI4e,EAAY,SAAW,EAAG,CAC1BlhB,EAAO,OAAO,UACVA,EAAO,IAAI,uDAAuD,GAC/D,kBAAA,EAEP,MACJ,CAEAA,EAAO,MAAM,SAAS,CAClB,YAAAkhB,EACA,gBAAiB,GACjB,aAAc,GACd,eAAgB,KAChB,cAAe,GACf,aAAc,GACd,YAAa,IAAA,CAChB,EAGDlhB,EAAO,OAAO,OAAO,oBAAoB,EAIzC,MAAMmhB,EAAUzhB,EAAO,QAAQ,SAAW2gB,EAAU,IAChDc,GAAWd,EAAU,eACrBiE,GAA2BtkB,EAAQmhB,CAAO,CAElD,CAQO,SAASoD,GACZ7kB,EACAX,EACI,CACJ,MAAMiB,EAAU,OAAe,OAC/B,GAAI,CAACA,GAAQ,MAAO,OAEpB,KAAM,CAAE,SAAAmB,EAAU,QAAAggB,CAAA,EAAYzhB,EAAO,QAAU,CAAA,EAC/C,GAAI,CAACyB,EAAU,OAGf,MAAM+f,GADQlhB,EAAO,MAAM,SAAA,EACc,aAAe,CAAA,GAAI,IAAKkS,GACzDA,EAAK,KAAO/Q,EACL,CAAE,GAAG+Q,EAAM,SAAU,CAACA,EAAK,QAAA,EAE/BA,CACV,EAEK+R,EAAc/C,EAAY,MAAOhP,GAAqBA,EAAK,QAAQ,EAEzElS,EAAO,MAAM,SAAS,CAClB,YAAAkhB,EACA,gBAAiB+C,CAAA,CACpB,EAGGN,GACA,aAAaA,CAAyB,EAE1CA,EAA4B,WAAW,IAAM,CACzCW,GAA2BtkB,EAAQmhB,CAAO,CAC9C,EAAG,GAAG,CACV,CAQO,SAASqD,GACZ9kB,EACAX,EACI,CACJ,MAAMiB,EAAU,OAAe,OAC/B,GAAI,CAACA,GAAQ,MAAO,OAEpB,KAAM,CAAE,QAAAmhB,CAAA,EAAYzhB,EAAO,QAAU,CAAA,EAC/B0iB,EAAQpiB,EAAO,MAAM,SAAA,EACrBmkB,EAAe,CAAC/B,EAAM,gBAEtBlB,GAA6BkB,EAAM,aAAe,CAAA,GAAI,IAAKlQ,IAAsB,CACnF,GAAGA,EACH,SAAUiS,CAAA,EACZ,EAEFnkB,EAAO,MAAM,SAAS,CAClB,YAAAkhB,EACA,gBAAiBiD,CAAA,CACpB,EAGGR,GACA,aAAaA,CAAyB,EAE1CA,EAA4B,WAAW,IAAM,CACzCW,GAA2BtkB,EAAQmhB,CAAO,CAC9C,EAAG,GAAG,CACV,CAQO,SAASsD,GACZ/kB,EACAX,EACI,CACJ,MAAMiB,EAAU,OAAe,OAC/B,GAAI,CAACA,GAAQ,MAAO,OAEpB,KAAM,CAAE,SAAAmB,EAAU,YAAAygB,EAAa,QAAAT,GAAYzhB,EAAO,QAAU,CAAA,EAE5D,GAAI,CAACyB,GAAY,CAACygB,GAAgB,CAACT,GAAW,CAACyC,GAAqB5jB,CAAM,EAAI,OAE9E,MAAM6hB,EAAWniB,EAAO,QAAQ,OAAUA,EAAe,QAAQ,QAAQ,MACnE6f,EAAS,SAAS,OAAOsC,CAAQ,EAAG,EAAE,EACtCC,EAAU,KAAK,IAAI,EAAG,KAAK,IAAI,MAAMvC,CAAM,EAAIqC,EAAcrC,EAAQqC,CAAW,CAAC,EAGjFV,GADQlhB,EAAO,MAAM,SAAA,EACc,aAAe,CAAA,GAAI,IAAKkS,GACzDA,EAAK,KAAO/Q,EACL,CAAE,GAAG+Q,EAAM,gBAAiB4P,CAAA,EAEhC5P,CACV,EAEDlS,EAAO,MAAM,SAAS,CAAE,YAAAkhB,CAAA,CAAa,EAMrC,MAAM2B,EAAsB,OAAe,kBACvCc,GACA,aAAaA,CAAyB,EAE1CA,EAA4B,WAAW,IAAM,CACzC,MAAMb,EAAmB,OAAe,kBACvC,OAAe,kBAAoBD,EACpC,GAAI,CACAyB,GAA2BtkB,EAAQmhB,CAAO,CAC9C,QAAA,CACK,OAAe,kBAAoB2B,CACxC,CACJ,EAAG,GAAG,CACV,CAQA,eAAsB4B,GAClBhlB,EACAX,EACa,CACb,MAAMiB,EAAU,OAAe,OAC/B,GAAI,CAACA,GAAQ,MAAO,OAEpB,KAAM,CAAE,QAAAmhB,CAAA,EAAYzhB,EAAO,QAAU,CAAA,EAEjC,CAACyhB,GAAW,CAACyC,GAAqB5jB,CAAM,GAE5C,MAAMskB,GAA2BtkB,EAAQmhB,CAAO,CACpD,CAQA,eAAemD,GAA2BtkB,EAAamhB,EAAyC,CAC5F,MAAMiB,EAAQpiB,EAAO,MAAM,SAAA,EACrBkhB,EAA4BkB,EAAM,aAAe,CAAA,EACjDc,EAAiBd,EAAM,gBAAkB,WAEzChH,EAAgB8F,EAAY,OAAQhP,GAAqBA,EAAK,QAAQ,EAE5E,GAAIkJ,EAAc,SAAW,EAAG,CAC5Bpb,EAAO,MAAM,SAAS,CAAE,eAAgB,KAAM,cAAe,GAAO,EACpE,MACJ,CAEA,MAAM8V,EAAQsF,EAAc,IAAKlJ,IAAsB,CACnD,gBAAiBA,EAAK,GACtB,gBAAiBA,EAAK,eAAA,EACxB,EAEFlS,EAAO,MAAM,SAAS,CAAE,cAAe,GAAM,EAE7C,GAAI,CACA,MAAM2kB,EAAa3kB,EAAO,MAAM,MAAM,SAAS,GAAG,gBAC5C6jB,EAAc7jB,EAAO,YAAY,MAAM,OAAO,GAAG,MAAM,aACvD4kB,EAAU,GAAQD,GAAcd,GAChC7E,EAAM4F,EACN,+CAA+Cf,CAAW,mBAC1D,8CAA8C1C,CAAO,mBAErD0D,EAASD,EAAU,CAAE,QAAS,CAAE,sBAAuBD,CAAA,GAAiB,OAExEzd,EAAW,MAAMlH,EAAO,IAAI,KAC9Bgf,EACA,CAAE,MAAAlJ,EAAO,gBAAiBoN,CAAA,EAC1B2B,CAAA,EAGJ,GAAI3d,GAAU,QACVlH,EAAO,MAAM,SAAS,CAClB,eAAgBkH,EAAS,KACzB,cAAe,GACf,YAAa,IAAA,CAChB,MAED,OAAM,IAAI,MAAMA,GAAU,SAAW,iBAAiB,CAE9D,OAASG,EAAY,CACjB9H,GAAO,MAAM,2BAA4B8H,CAAK,EAC9CrH,EAAO,MAAM,SAAS,CAClB,cAAe,GACf,eAAgB,IAAA,CACnB,EACDA,EAAO,OAAO,QACVA,EAAO,IAAI,kDAAkD,GAC1D,qBAAA,CAEX,CACJ,CAgBO,SAAS8kB,GACZplB,EACAX,EACI,CACJ,MAAMiB,EAAU,OAAe,OAC/B,GAAI,CAACA,GAAQ,MAAO,OAEpB,KAAM,CAAE,SAAAojB,EAAU,QAAAjC,CAAA,EAAYzhB,EAAO,QAAU,CAAA,EAE/C,GAAI,CAAC0jB,GAAa,CAACjC,GAAW,CAACyC,GAAqB5jB,CAAM,EAAI,OAE9DA,EAAO,MAAM,SAAS,CAAE,eAAgBojB,EAAU,EAIlD,MAAMP,EAAsB,OAAe,kBACvCc,GACA,aAAaA,CAAyB,EAE1CA,EAA4B,WAAW,IAAM,CACzC,MAAMb,EAAmB,OAAe,kBACvC,OAAe,kBAAoBD,EACpC,GAAI,CACAyB,GAA2BtkB,EAAQmhB,CAAO,CAC9C,QAAA,CACK,OAAe,kBAAoB2B,CACxC,CACJ,EAAG,GAAG,CACV,CAQA,eAAsBiC,GAClBrlB,EACAX,EACa,CACb,MAAMiB,EAAU,OAAe,OAC/B,GAAI,CAACA,GAAQ,MAAO,OAEpB,KAAM,CAAE,QAAAmhB,CAAA,EAAYzhB,EAAO,QAAU,CAAA,EAErC,GAAI,CAACyhB,GAAW,CAACyC,GAAqB5jB,CAAM,EAAG,OAE/C,MAAMoiB,EAAQpiB,EAAO,MAAM,SAAA,EAErBob,GAD4BgH,EAAM,aAAe,CAAA,GACrB,OAAQlQ,GAAqBA,EAAK,QAAQ,EAEtEoR,EAAe5jB,EAAO,QAAQ,cAAgB0iB,EAAM,cAAgB,GACpEmB,EAAqB7jB,EAAO,QAAQ,oBAAsB0iB,EAAM,oBAAsB,GACtFc,EAAiBxjB,EAAO,QAAQ,gBAAkB0iB,EAAM,gBAAkB,WAEhF,GAAIhH,EAAc,SAAW,EAAG,CAC5Bpb,EAAO,OAAO,UACVA,EAAO,IAAI,oDAAoD,GAC5D,iBAAA,EAEP,MACJ,CAEA,GAAI,CAACsjB,EAAc,CACftjB,EAAO,OAAO,UACVA,EAAO,IAAI,kDAAkD,GAC1D,gBAAA,EAEP,MACJ,CAMA,MAAMye,EAA4B,CAC9B,OAAQ6E,EACR,gBAAiBJ,EACjB,MAAO9H,EAAc,IAAKlJ,IAAsB,CAC5C,gBAAiBA,EAAK,GACtB,gBAAiBA,EAAK,eAAA,EACxB,CAAA,EAIFqR,IACA9E,EAAK,cAAgB8E,GAGzBvjB,EAAO,MAAM,SAAS,CAAE,aAAc,GAAM,YAAa,KAAM,uBAAwB,KAAM,EAE7F,GAAI,CACA,MAAM2kB,EAAa3kB,EAAO,MAAM,MAAM,SAAS,GAAG,gBAC5C6jB,EAAc7jB,EAAO,YAAY,MAAM,OAAO,GAAG,MAAM,aACvD4kB,EAAU,GAAQD,GAAcd,GAChC7E,EAAM4F,EACN,+CAA+Cf,CAAW,UAC1D,8CAA8C1C,CAAO,UAErD0D,EAASD,EAAU,CAAE,QAAS,CAAE,sBAAuBD,CAAA,GAAiB,OAExEzd,EAAW,MAAMlH,EAAO,IAAI,KAC9Bgf,EACAP,EACAoG,CAAA,EAGJ,GAAI3d,GAAU,QACVlH,EAAO,MAAM,SAAS,CAAE,aAAc,GAAO,EAC7CA,EAAO,OAAO,UACVA,EAAO,IAAI,oCAAoC,GAC5C,cAAA,EAGPA,EAAO,OAAO,QAAQ,oBAAoB,EAC1CA,EAAO,WAAW,CAAE,QAAS,oBAAqB,OAAQ,CAAE,aAAc,OAAA,EAAW,MAErF,OAAM,IAAI,MAAMkH,GAAU,SAAW,eAAe,CAE5D,OAASG,EAAY,CACjB9H,GAAO,MAAM,+BAAgC8H,CAAK,EAElD,MAAMyX,EAAazX,GAAe,UAAU,MAASA,GAAe,KAC9Dmb,EAAgB1D,GAAW,SAAWzX,GAAO,SAAW,GACxDqc,EAAmB5E,GAAW,QAAU,KACxC2D,EAAkBziB,EAAO,IAAI,gDAAgD,GAC5E,iBAEPA,EAAO,MAAM,SAAS,CAClB,aAAc,GACd,YAAawiB,GAAiBC,EAC9B,uBAAwBiB,CAAA,CAC3B,EAED1jB,EAAO,OAAO,QAAQwiB,GAAiBC,CAAe,CAC1D,CACJ,CC7iBA,MAAMljB,GAAW,OAAe,QAAQ,eAAe,mBAAmB,GAAM,CAC5E,IAAK,IAAIC,IAAoB,QAAQ,IAAI,sBAAuB,GAAGA,CAAI,EACvE,KAAM,IAAIA,IAAoB,QAAQ,KAAK,sBAAuB,GAAGA,CAAI,EACzE,MAAO,IAAIA,IAAoB,QAAQ,MAAM,sBAAuB,GAAGA,CAAI,CAC/E,EAYA,SAASokB,GAAqB5jB,EAAsB,CAChD,MAAM6e,EAAQ7e,GAAQ,OAAO,MAAM,SAAS,GAAG,gBACzC6jB,EAAc7jB,GAAQ,YAAY,MAAM,OAAO,GAAG,MAAM,aAC9D,MAAO,GAAQ6e,GAASgF,EAC5B,CAgBA,eAAsBmB,GAClBtlB,EACAX,EACa,CACb,MAAMiB,EAAU,OAAe,OAC/B,GAAI,CAACA,GAAQ,MAAO,OAEpB,KAAM,CAAE,QAAAmhB,EAAS,SAAAhgB,CAAA,EAAazB,EAAO,QAAU,CAAA,EAE/C,GAAI,CAACyB,GAAa,CAACggB,GAAW,CAACyC,GAAqB5jB,CAAM,EAAI,CAC1DT,GAAO,MAAM,yCAAyC,EACtD,MACJ,CAEAS,EAAO,MAAM,SAAS,CAAE,aAAc,GAAM,EAE5C,GAAI,CACA,MAAM2kB,EAAa3kB,EAAO,MAAM,MAAM,SAAS,GAAG,gBAC5C6jB,EAAc7jB,EAAO,YAAY,MAAM,OAAO,GAAG,MAAM,aACvD4kB,EAAU,GAAQD,GAAcd,GAChC7E,EAAM4F,EACN,+CAA+Cf,CAAW,YAAY1iB,CAAQ,WAC9E,8CAA8CggB,CAAO,YAAYhgB,CAAQ,WAEzE0jB,EAASD,EAAU,CAAE,QAAS,CAAE,sBAAuBD,CAAA,GAAiB,OAExEzd,EAAW,MAAMlH,EAAO,IAAI,KAAKgf,EAAK,OAAW6F,CAAM,EAE7D,GAAI3d,GAAU,QACVlH,EAAO,MAAM,SAAS,CAAE,aAAc,GAAO,cAAe,KAAM,EAClEA,EAAO,OAAO,QAAQ,wBAAwB,EAC9CA,EAAO,WAAW,CAAE,QAAS,oBAAqB,OAAQ,CAAE,aAAc,OAAA,EAAW,EACrFA,EAAO,OAAO,UACVkH,EAAS,SACNlH,EAAO,IAAI,6CAA6C,GACxD,gBAAA,MAGP,OAAM,IAAI,MAAMkH,GAAU,SAAW,gBAAgB,CAE7D,OAASG,EAAY,CACjB9H,GAAO,MAAM,2BAA4B8H,CAAK,EAE9C,MAAMuF,EAAevF,GAAO,UAAU,MAAM,SACrCA,GAAO,MAAM,SACbA,GAAO,SACPrH,EAAO,IAAI,kDAAkD,GAC7D,gBAEPA,EAAO,MAAM,SAAS,CAAE,aAAc,GAAO,EAC7CA,EAAO,OAAO,QAAQ4M,CAAY,CACtC,CACJ,CCrFA,MAAMrN,GAAW,OAAe,QAAQ,eAAe,4BAA4B,GAAM,CACrF,IAAK,IAAIC,IAAoB,QAAQ,IAAI,+BAAgC,GAAGA,CAAI,EAChF,KAAM,IAAIA,IAAoB,QAAQ,KAAK,+BAAgC,GAAGA,CAAI,EAClF,MAAO,IAAIA,IAAoB,QAAQ,MAAM,+BAAgC,GAAGA,CAAI,CACxF,EAeA,SAASokB,GAAqB5jB,EAAsB,CAChD,MAAM6e,EAAQ7e,GAAQ,OAAO,MAAM,SAAS,GAAG,gBACzC6jB,EAAc7jB,GAAQ,YAAY,MAAM,OAAO,GAAG,MAAM,aAC9D,MAAO,GAAQ6e,GAASgF,EAC5B,CAiBA,eAAsBoB,GAClBvlB,EACAX,EACa,CACb,MAAMiB,EAAU,OAAe,OAC/B,GAAI,CAACA,GAAQ,MAAO,OAEpB,KAAM,CAAE,QAAAmhB,CAAA,EAAYzhB,EAAO,QAAU,CAAA,EAC/BklB,EAAUhB,GAAqB5jB,CAAM,EAG3C,GAAI,CAACmhB,GAAW,CAACyD,EAAS,CACtBrlB,GAAO,MAAM,+CAA+C,EAC5D,MACJ,CAEA,MAAMY,EAAcH,EAAO,MAAM,MAAM,SAAS,GAAK,CAAA,EAK/Cye,GAHOmG,EAAU,SAAYzkB,EAAY,mBAAqB,WAIvD,QACH,CAAE,WAAYA,EAAY,mBAC1BA,EAAY,uBAEtBH,EAAO,MAAM,SAAS,CAAE,oBAAqB,GAAM,EAEnD,GAAI,CACA,MAAM2kB,EAAaxkB,EAAY,gBACzB0jB,EAAc7jB,EAAO,YAAY,MAAM,OAAO,GAAG,MAAM,aACvDgf,EAAM4F,EACN,+CAA+Cf,CAAW,oBAC1D,8CAA8C1C,CAAO,oBAErD0D,EAASD,EAAU,CAAE,QAAS,CAAE,sBAAuBD,CAAA,GAAiB,OAExEzd,EAAW,MAAMlH,EAAO,IAAI,IAAIgf,EAAKP,EAAMoG,CAAM,EAEvD,GAAI3d,GAAU,QACVlH,EAAO,MAAM,SAAS,CAAE,oBAAqB,GAAO,EACpDA,EAAO,OAAO,QAAQ,oBAAoB,EAC1CA,EAAO,WAAW,CAAE,QAAS,oBAAqB,OAAQ,CAAE,aAAc,OAAA,EAAW,EACrFA,EAAO,OAAO,UACVkH,EAAS,SACNlH,EAAO,IAAI,8CAA8C,GACzD,eAAA,MAGP,OAAM,IAAI,MAAMkH,GAAU,SAAW,gCAAgC,CAE7E,OAASG,EAAY,CACjB9H,GAAO,MAAM,8BAA+B8H,CAAK,EACjDrH,EAAO,MAAM,SAAS,CAAE,oBAAqB,GAAO,EAEpD,MAAM4M,EAAevF,GAAO,UAAU,MAAM,SACrCA,GAAO,MAAM,SACbA,GAAO,SACP,kBACPrH,EAAO,OAAO,QAAQ4M,CAAY,CACtC,CACJ,CCvGA,MAAMrN,GAAW,OAAe,QAAQ,eAAe,aAAa,GAAM,CACtE,IAAK,IAAIC,IAAoB,QAAQ,IAAI,gBAAiB,GAAGA,CAAI,EACjE,KAAM,IAAIA,IAAoB,QAAQ,KAAK,gBAAiB,GAAGA,CAAI,EACnE,MAAO,IAAIA,IAAoB,QAAQ,MAAM,gBAAiB,GAAGA,CAAI,CACzE,EAsBA,eAAsB0lB,GAClBxlB,EAIAX,EACa,CACb,MAAMiB,EAAU,OAAe,OAC/B,GAAI,CAACA,GAAQ,MAAO,OAEpB,KAAM,CAAE,QAAAmhB,EAAS,SAAAhgB,EAAU,UAAAvB,GAAcF,EAAO,QAAU,CAAA,EAC1D,GAAI,CAACyhB,GAAW,CAAChgB,GAAY,CAACvB,EAAW,CACrCL,GAAO,MAAM,kDAAkD,EAC/D,MACJ,CAGA,MAAM6iB,EAAQpiB,EAAO,MAAM,SAAA,EACrBmlB,EAASzlB,EAAO,QAAQ,QAAU0iB,EAAM,cAAgB,EACxDgD,GAAW1lB,EAAO,QAAQ,SAAW0iB,EAAM,eAAiB,IAAI,KAAA,EAKhE1N,GADmBhV,EAAO,QAAQ,QAAU0iB,EAAM,cAAgB,CAAA,GAC9B,IAAKlQ,GACvCA,aAAgB,KAAaA,EAC7BA,GAAM,gBAAgB,MACtBA,GAAM,gBAAgB,MAEtBA,GAAM,MAAQ,OAAOA,EAAK,KAAK,MAAS,UAAYA,EAAK,KAAK,KAAO,EAAUA,EAAK,KACpFA,aAAgB,KAAaA,EAC1B,IACV,EAAE,OAAQ2L,GAAWA,IAAM,IAAI,EAGhC,GAAI,CAACsH,GAAUA,EAAS,GAAKA,EAAS,EAAG,CACrCnlB,EAAO,OAAO,QACVA,EAAO,IAAI,0DAA0D,GAClE,cAAA,EAEP,MACJ,CAEA,GAAIolB,EAAQ,OAAS,GAAI,CACrBplB,EAAO,MAAM,SAAS,CAClB,YAAaA,EAAO,IAAI,sDAAsD,GACvE,2BAAA,CACV,EACD,MACJ,CAEAA,EAAO,MAAM,SAAS,CAAE,mBAAoB,GAAM,YAAa,KAAM,EAErE,GAAI,CAEA,MAAMkH,EAAW,MAAMlH,EAAO,IAAI,KAC9B,8CACA,CACI,gBAAiBmB,EACjB,WAAYvB,EACZ,OAAAulB,EACA,QAAAC,CAAA,CACJ,EAGJ,GAAI,CAACle,GAAU,QACX,MAAM,IAAI,MAAMA,GAAU,SAAW,wBAAwB,EAGjE,MAAMme,EAAWne,EAAS,MAAM,GAGhC,GAAIme,GAAY3Q,EAAO,OAAS,EAC5B,QAAS5S,EAAI,EAAGA,EAAI4S,EAAO,OAAQ5S,IAAK,CACpC,MAAMiS,EAAW,IAAI,SACrBA,EAAS,OAAO,QAASW,EAAO5S,CAAC,CAAC,EAClCiS,EAAS,OAAO,aAAc,OAAOjS,CAAC,CAAC,EAEvC,GAAI,CACA,MAAM9B,EAAO,IAAI,KACb,+CAA+CqlB,CAAQ,UACvDtR,EACA,CAAE,QAAS,CAAE,eAAgB,sBAAsB,CAAE,CAE7D,OAASuR,EAAe,CACpB/lB,GAAO,KAAK,sBAAsBuC,EAAI,CAAC,WAAYwjB,CAAQ,EAE3DtlB,EAAO,OAAO,UACVA,EAAO,IAAI,yDAAyD,GACjE,OAAO8B,EAAI,CAAC,gBAAA,CAEvB,CACJ,CAIJ9B,EAAO,MAAM,SAAS,CAClB,mBAAoB,GACpB,aAAc,KACd,aAAc,EACd,cAAe,GACf,aAAc,CAAA,EACd,YAAa,IAAA,CAChB,EACDA,EAAO,OAAO,QAAQ,oBAAoB,EAC1CA,EAAO,WAAW,CAAE,QAAS,oBAAqB,OAAQ,CAAE,aAAc,OAAA,EAAW,EACrFA,EAAO,OAAO,UACVkH,EAAS,SACNlH,EAAO,IAAI,8CAA8C,GACzD,cAAA,CAEX,OAASqH,EAAY,CACjB9H,GAAO,MAAM,qBAAsB8H,CAAK,EAExC,MAAMyX,EAAYzX,GAAO,UAAU,MAAQA,GAAO,KAC5Cke,EAAale,GAAO,UAAU,QAAUA,GAAO,OAC/CuF,EAAekS,GAAW,SAAWzX,GAAO,SAC3CrH,EAAO,IAAI,6CAA6C,GACxD,iBAEP,GAAIulB,IAAe,IAAK,CAEpB,MAAM7B,EAAmB5E,GAAW,QAAU,CAAA,EACxC0G,EAAa,OAAO,OAAO9B,CAAgB,EAAE,KAAA,EAAO,CAAC,EAC3D1jB,EAAO,MAAM,SAAS,CAClB,mBAAoB,GACpB,YAAawlB,GAAc5Y,CAAA,CAC9B,CACL,MACI5M,EAAO,MAAM,SAAS,CAAE,mBAAoB,GAAO,EAGvDA,EAAO,OAAO,QAAQ4M,CAAY,CACtC,CACJ,CClKA,MAAMrN,EAAW,OAAe,QAAQ,eAAe,yBAAyB,GAAM,CAClF,IAAK,IAAIC,IAAoB,QAAQ,IAAI,4BAA6B,GAAGA,CAAI,EAC7E,KAAM,IAAIA,IAAoB,QAAQ,KAAK,4BAA6B,GAAGA,CAAI,EAC/E,MAAO,IAAIA,IAAoB,QAAQ,MAAM,4BAA6B,GAAGA,CAAI,CACrF,EAsDMimB,GAAoB,CACtB,QAAS,mBACT,eAAgB,aAAc,aAAc,oBAAqB,YACrE,EAGMC,GAA0B,CAAC,kBAAkB,EAG7CC,GAAkB,CACpB,eAAgB,iBAAkB,eAAgB,eAAgB,qBACtE,EAGMC,GAAe,CAAC,KAAK,EAGrBC,GAAsB,CACxB,eAAgB,aAAc,aAAc,oBAAqB,YACrE,EAIMC,GAAgE,CAClE,gBAAiB,SACjB,qBAAsB,KACtB,QAAS,KACT,cAAe,MACf,cAAe,QACf,SAAU,EACV,eAAgB,KAChB,OAAQ,KACR,aAAc,KACd,mBAAoB,KACpB,uBAAwB,KACxB,WAAY,KACZ,kBAAmB,GACnB,mBAAoB,CAAA,EACpB,mBAAoB,GACpB,UAAW,EACf,EAUA,SAASC,GAAmBC,EAA+C,CACvE,MAAO,CACH,YAAaP,GAAkB,SAASO,CAAY,EACpD,kBAAmBN,GAAwB,SAASM,CAAY,EAChE,WAAYL,GAAgB,SAASK,CAAY,EACjD,gBAAiBJ,GAAa,SAASI,CAAY,EACnD,cAAeH,GAAoB,SAASG,CAAY,CAAA,CAEhE,CAKA,SAAS9K,GAAiB,CACtB,MAAMlb,EAAU,OAAe,OAC/B,OAAKA,GAAQ,MAINA,GAHHT,EAAO,KAAK,4BAA4B,EACjC,KAGf,CAKA,SAAS0mB,EACLtmB,EACAoD,EACI,CACApD,EAAQ,cACRA,EAAQ,cAAcoD,CAAO,EAEdmY,EAAA,GACP,OAAO,SAASnY,CAAO,CAEvC,CAKA,SAASmjB,EAAmBlmB,EAA+B,CAEvD,MAAO,CAAC,IADWA,EAAO,MAAM,WAAA,GAAgB,CAAA,GACzB,MAAM,kBAAoB,CAAA,CAAG,CACxD,CAaO,SAASmmB,GACZzmB,EACAC,EACI,CACJ,MAAMb,EAASY,EAAO,QAAU,CAAA,EAC1BiY,EAAS7Y,EAAO,OAChBsnB,EAAS,CAAC,CAACtnB,EAAO,OAClBunB,EAAS,CAAC,CAACvnB,EAAO,OAIxB,GAH4BA,EAAO,mBAG/B,CADWoc,EAAA,EACF,OAEb,MAAMoL,EAAoC,CACtC,iBAAkB,EAClB,YAAa,CAAA,CAAC,EAGlB,IAAKF,GAAUC,IAAW1O,EAAQ,CAE9B,MAAM4O,EAAkB5O,EAAO,kBAAoB,CAAA,EAE7CqO,EADeO,EAAgB,CAAC,GACH,eAAiB,QAC9CC,EAAQT,GAAmBC,CAAY,EAC7C,OAAO,OAAOM,EAAcE,CAAK,EACjCjnB,EAAO,IAAI,2BAA4B6mB,EAAS,OAAS,OAAQ,mBAAoBG,EAAgB,OAAQ,uBAAwBP,CAAY,CACrJ,KAAO,CAEH,MAAMQ,EAAQT,GAAmB,OAAO,EACxC,OAAO,OAAOO,EAAcE,CAAK,EACjCjnB,EAAO,IAAI,qDAAqD,CACpE,CAEA0mB,EAAiBtmB,EAAS2mB,CAAY,CAC1C,CAQO,SAASG,GACZ/mB,EACAC,EACI,CAEJ,MAAM+mB,GADShnB,EAAO,QAAU,CAAA,GACJ,cAAgB,GAE5C,GAAI,CAACgnB,EAAa,CACdnnB,EAAO,KAAK,2CAA2C,EACvD,MACJ,CAEA,MAAMS,EAASkb,EAAA,EACf,GAAI,CAAClb,EAAQ,OAEb,MAAMumB,EAAkBL,EAAmBlmB,CAAM,EAGjD,GAAIumB,EAAgB,KAAKI,GAAMA,EAAG,eAAiBD,CAAW,EAAG,CAC7DnnB,EAAO,KAAK,8CAA+CmnB,CAAW,EACtE,MACJ,CAEA,MAAME,EAA6B,CAC/B,aAAcF,EACd,GAAGZ,EAAA,EAGPS,EAAgB,KAAKK,CAAU,EAE/B,MAAMvU,EAAWkU,EAAgB,OAAS,EACpCC,EAAQT,GAAmBa,EAAW,aAAa,EAEnDN,EAAoC,CACtC,wBAAyBC,EACzB,iBAAkBlU,EAClB,GAAGmU,CAAA,EAGPP,EAAiBtmB,EAAS2mB,CAAY,EACtC/mB,EAAO,IAAI,qCAAsCmnB,EAAa,SAAUrU,CAAQ,CACpF,CAQO,SAASwU,GACZnnB,EACAC,EACI,CACJ,MAAMb,EAASY,EAAO,QAAU,CAAA,EAC1BoO,EAAQ,OAAOhP,EAAO,KAAK,EAEjC,GAAI,MAAMgP,CAAK,GAAKA,EAAQ,EAAG,CAC3BvO,EAAO,KAAK,wCAAyCT,EAAO,KAAK,EACjE,MACJ,CAEA,MAAMkB,EAASkb,EAAA,EACf,GAAI,CAAClb,EAAQ,OAEb,MAAMumB,EAAkBL,EAAmBlmB,CAAM,EAC3CoH,EAAapH,EAAO,MAAM,WAAA,GAAgB,CAAA,EAEhD,GAAI8N,GAASyY,EAAgB,OAAQ,CACjChnB,EAAO,KAAK,8CAA+CuO,CAAK,EAChE,MACJ,CAEA,MAAMgZ,EAAcP,EAAgBzY,CAAK,EAAE,aAC3CyY,EAAgB,OAAOzY,EAAO,CAAC,EAG/B,MAAMiZ,EAAa3f,EAAW,kBAAoB,EAClD,IAAI4f,EAASD,EACTR,EAAgB,SAAW,EAC3BS,EAAS,EACFD,GAAcR,EAAgB,OACrCS,EAAST,EAAgB,OAAS,EAC3BQ,EAAajZ,IACpBkZ,EAASD,EAAa,GAI1B,MAAME,EAAc,CAAE,GAAI7f,EAAW,aAAe,CAAA,CAAC,EACrD6f,EAAYH,CAAW,EAAI,CAAA,EAG3B,MAAMI,EAAWX,EAAgBS,CAAM,EACjCR,EAAmBT,GAAXmB,EAA8BA,EAAS,cAAoC,OAAvB,EAE5DZ,EAAoC,CACtC,wBAAyBC,EACzB,iBAAkBS,EAClB,YAAAC,EACA,GAAGT,CAAA,EAGPP,EAAiBtmB,EAAS2mB,CAAY,EACtC/mB,EAAO,IAAI,wCAAyCuO,EAAO,WAAYgZ,EAAa,UAAWE,CAAM,CACzG,CAQO,SAASG,GACZznB,EACAC,EACI,CACJ,MAAMb,EAASY,EAAO,QAAU,CAAA,EAC1BoO,EAAQ,OAAOhP,EAAO,KAAK,EAEjC,GAAI,MAAMgP,CAAK,GAAKA,EAAQ,EAAG,CAC3BvO,EAAO,KAAK,oCAAqCT,EAAO,KAAK,EAC7D,MACJ,CAEA,MAAMkB,EAASkb,EAAA,EACf,GAAI,CAAClb,EAAQ,OAEb,MAAMumB,EAAkBL,EAAmBlmB,CAAM,EAEjD,GAAI8N,GAASyY,EAAgB,OAAQ,CACjChnB,EAAO,KAAK,0CAA2CuO,CAAK,EAC5D,MACJ,CAEA,MAAMsZ,EAAWb,EAAgBzY,CAAK,EAChC0Y,EAAQT,GAAmBqB,EAAS,aAAa,EAEjDd,EAAoC,CACtC,iBAAkBxY,EAClB,GAAG0Y,CAAA,EAGPP,EAAiBtmB,EAAS2mB,CAAY,EACtC/mB,EAAO,IAAI,sCAAuCuO,EAAO,WAAYsZ,EAAS,aAAc,iBAAkBA,EAAS,aAAa,CACxI,CAWO,SAASC,GACZ3nB,EACAC,EACI,CACJ,MAAMb,EAASY,EAAO,QAAU,CAAA,EAC1BG,EAASf,EAAO,OAAS,GACzBiB,EAAQjB,EAAO,MAErB,GAAI,CAACe,EAAO,CACRN,EAAO,KAAK,qCAAqC,EACjD,MACJ,CAEA,MAAMS,EAASkb,EAAA,EACf,GAAI,CAAClb,EAAQ,OAEb,MAAMoH,EAAapH,EAAO,MAAM,WAAA,GAAgB,CAAA,EAC1C8N,EAAQ,OAAO1G,EAAW,kBAAoB,CAAC,EAC/Cmf,EAAkBL,EAAmBlmB,CAAM,EAEjD,GAAI8N,GAASyY,EAAgB,OAAQ,CACjChnB,EAAO,KAAK,4CAA6CuO,CAAK,EAC9D,MACJ,CAGAyY,EAAgBzY,CAAK,EAAI,CAAE,GAAGyY,EAAgBzY,CAAK,EAAG,CAACjO,CAAK,EAAGE,CAAA,EAE/DkmB,EAAiBtmB,EAAS,CACtB,wBAAyB4mB,CAAA,CAC5B,EACDhnB,EAAO,IAAI,+BAAgCM,EAAO,IAAKE,EAAO,YAAa+N,CAAK,CACpF,CAQO,SAASwZ,GACZ5nB,EACAC,EACI,CACJ,MAAMb,EAASY,EAAO,QAAU,CAAA,EAC1BsmB,EAAgBlnB,EAAO,OAAS,GAChCyoB,EAAe,OAAOzoB,EAAO,OAAS,CAAC,EAE7C,GAAI,CAACknB,EAAc,CACfzmB,EAAO,KAAK,qDAAqD,EACjE,MACJ,CAEA,MAAMS,EAASkb,EAAA,EACf,GAAI,CAAClb,EAAQ,OAEb,MAAMwmB,EAAQT,GAAmBC,CAAY,EAC7CzmB,EAAO,IAAI,yBAA0BymB,EAAcQ,EAAO,gBAAiBe,CAAY,EAEvF,MAAMhB,EAAkBL,EAAmBlmB,CAAM,EAC3C2mB,EAAKJ,EAAgBgB,CAAY,EACvC,GAAI,CAACZ,EAAI,CACLpnB,EAAO,KAAK,sDAAuDgoB,CAAY,EAC/E,MACJ,CAKA,MAAMC,EAA4B,CAAE,GAAGb,EAAI,cAAeX,CAAA,EAoB1D,GAjBKQ,EAAM,cACPgB,EAAU,SAAW,GAEpBhB,EAAM,oBACPgB,EAAU,eAAiB,MAE3B,CAAChB,EAAM,YAAc,CAACA,EAAM,gBAC5BgB,EAAU,OAAS,MAElBhB,EAAM,kBACPgB,EAAU,aAAe,KACzBA,EAAU,mBAAqB,KAC/BA,EAAU,uBAAyB,KACnCA,EAAU,WAAa,MAIvBhB,EAAM,aACF,CAACgB,EAAU,QAAU,CAACA,EAAU,OAAO,OAAO,CAC9C,MAAMC,EAAYzB,EAAa,QAAQ,SAAU,EAAE,EACnDwB,EAAU,OAAS,CACf,KAAMC,EACN,MAAO,CAAC,CAAE,IAAK,EAAG,IAAK,KAAM,IAAK,CAAA,CAAG,CAAA,CAE7C,CAIAjB,EAAM,gBACF,CAACgB,EAAU,QAAU,CAACA,EAAU,OAAO,cACvCA,EAAU,OAAS,CACf,KAAMxB,EACN,WAAY,CAAA,GAKxBO,EAAgBgB,CAAY,EAAIC,EAEhC,MAAMlB,EAAoC,CACtC,wBAAyBC,EACzB,GAAGC,CAAA,EAGPP,EAAiBtmB,EAAS2mB,CAAY,CAC1C,CAQO,SAASoB,GACZhoB,EACAC,EACI,CACJ,MAAMb,EAASY,EAAO,QAAU,CAAA,EAC1B6nB,EAAe,OAAOzoB,EAAO,OAAS,CAAC,EAEvCkB,EAASkb,EAAA,EACf,GAAI,CAAClb,EAAQ,OAEb,MAAMumB,EAAkBL,EAAmBlmB,CAAM,EAC3C2mB,EAAKJ,EAAgBgB,CAAY,EACvC,GAAI,CAACZ,EAAI,CACLpnB,EAAO,KAAK,8CAA+CgoB,CAAY,EACvE,MACJ,CAEA,MAAMI,EAAe,CAAC,GAAIhB,EAAG,QAAQ,OAAS,CAAA,CAAG,EAG3CiB,EAAWD,EAAaA,EAAa,OAAS,CAAC,EAG/CE,EAAqB,CAAE,IAFdD,GAAU,KAAO,KAAOA,EAAS,IAAM,EAAI,EAEhB,IAAK,KAAM,IAAK,CAAA,EAC1DD,EAAa,KAAKE,CAAO,EAGzBtB,EAAgBgB,CAAY,EAAI,CAC5B,GAAGZ,EACH,OAAQ,CAAE,GAAIA,EAAG,QAAU,CAAA,EAAK,MAAOgB,CAAA,CAAa,EAGxD1B,EAAiBtmB,EAAS,CACtB,wBAAyB4mB,CAAA,CAC5B,EAGDuB,GAA2B9nB,EAAQL,EAAS4nB,EAAcI,EAAchB,EAAG,YAAY,EACvFpnB,EAAO,IAAI,oCAAqCooB,EAAa,MAAM,CACvE,CAQO,SAASI,GACZroB,EACAC,EACI,CACJ,MAAMb,EAASY,EAAO,QAAU,CAAA,EAC1B6nB,EAAe,OAAOzoB,EAAO,cAAgB,CAAC,EAC9CkpB,EAAY,OAAOlpB,EAAO,SAAS,EAEzC,GAAI,MAAMkpB,CAAS,GAAKA,EAAY,EAAG,CACnCzoB,EAAO,KAAK,uCAAwCT,EAAO,SAAS,EACpE,MACJ,CAEA,MAAMkB,EAASkb,EAAA,EACf,GAAI,CAAClb,EAAQ,OAEb,MAAMumB,EAAkBL,EAAmBlmB,CAAM,EAC3C2mB,EAAKJ,EAAgBgB,CAAY,EACvC,GAAI,CAACZ,EAAI,CACLpnB,EAAO,KAAK,iDAAkDgoB,CAAY,EAC1E,MACJ,CAEA,MAAMI,EAAe,CAAC,GAAIhB,EAAG,QAAQ,OAAS,CAAA,CAAG,EAEjD,GAAIqB,GAAaL,EAAa,OAAQ,CAClCpoB,EAAO,KAAK,6CAA8CyoB,CAAS,EACnE,MACJ,CAEAL,EAAa,OAAOK,EAAW,CAAC,EAGhCzB,EAAgBgB,CAAY,EAAI,CAC5B,GAAGZ,EACH,OAAQ,CAAE,GAAIA,EAAG,QAAU,CAAA,EAAK,MAAOgB,CAAA,CAAa,EAGxD1B,EAAiBtmB,EAAS,CACtB,wBAAyB4mB,CAAA,CAC5B,EAGDuB,GAA2B9nB,EAAQL,EAAS4nB,EAAcI,EAAchB,EAAG,YAAY,EACvFpnB,EAAO,IAAI,kCAAmCyoB,EAAW,aAAcL,EAAa,MAAM,CAC9F,CAQO,SAASM,GACZvoB,EACAC,EACI,CACJ,MAAMb,EAASY,EAAO,QAAU,CAAA,EAC1B6nB,EAAe,OAAOzoB,EAAO,cAAgB,CAAC,EAC9CkpB,EAAY,OAAOlpB,EAAO,SAAS,EACnCe,EAAQf,EAAO,MACfiB,EAAQjB,EAAO,MAErB,GAAI,MAAMkpB,CAAS,GAAKA,EAAY,EAAG,CACnCzoB,EAAO,KAAK,4CAA6CT,EAAO,SAAS,EACzE,MACJ,CACA,GAAI,CAAC,CAAC,MAAO,MAAO,KAAK,EAAE,SAASe,CAAK,EAAG,CACxCN,EAAO,KAAK,wCAAyCM,CAAK,EAC1D,MACJ,CAEA,MAAMG,EAASkb,EAAA,EACf,GAAI,CAAClb,EAAQ,OAEb,MAAMumB,EAAkBL,EAAmBlmB,CAAM,EAC3C2mB,EAAKJ,EAAgBgB,CAAY,EACvC,GAAI,CAACZ,EAAI,CACLpnB,EAAO,KAAK,sDAAuDgoB,CAAY,EAC/E,MACJ,CAEA,MAAMI,EAAe,CAAC,GAAIhB,EAAG,QAAQ,OAAS,CAAA,CAAG,EAEjD,GAAIqB,GAAaL,EAAa,OAAQ,CAClCpoB,EAAO,KAAK,kDAAmDyoB,CAAS,EACxE,MACJ,CAIA,IAAIE,EACAroB,IAAU,MACVqoB,EAAcnoB,IAAU,IAAMA,IAAU,MAAQA,IAAU,OAAY,KAAO,OAAOA,CAAK,EAEzFmoB,EAAc,OAAOnoB,CAAK,EAG9B4nB,EAAaK,CAAS,EAAI,CAAE,GAAGL,EAAaK,CAAS,EAAG,CAACnoB,CAAK,EAAGqoB,CAAA,EAGjE3B,EAAgBgB,CAAY,EAAI,CAC5B,GAAGZ,EACH,OAAQ,CAAE,GAAIA,EAAG,QAAU,CAAA,EAAK,MAAOgB,CAAA,CAAa,EAGxD1B,EAAiBtmB,EAAS,CACtB,wBAAyB4mB,CAAA,CAC5B,EAGDuB,GAA2B9nB,EAAQL,EAAS4nB,EAAcI,EAAchB,EAAG,YAAY,CAC3F,CAQO,SAASwB,GACZzoB,EACAC,EACI,CACJ,MAAMb,EAASY,EAAO,QAAU,CAAA,EAC1B6nB,EAAe,OAAOzoB,EAAO,cAAgB,CAAC,EAE9CkB,EAASkb,EAAA,EACf,GAAI,CAAClb,EAAQ,OAGb,MAAM2mB,EADkBT,EAAmBlmB,CAAM,EACtBunB,CAAY,EACvC,GAAI,CAACZ,EAAI,OAET,MAAMyB,EAAQzB,EAAG,QAAQ,OAAS,CAAA,EAClCmB,GAA2B9nB,EAAQL,EAAS4nB,EAAca,EAAOzB,EAAG,YAAY,CACpF,CAKA,SAASmB,GACL9nB,EACAL,EACA0oB,EACAD,EACA1B,EACI,CAEJ,MAAMO,EAAgD,CAAE,IADrCjnB,EAAO,MAAM,WAAA,GAAgB,CAAA,GACuB,aAAe,CAAA,CAAC,EAEvF,GAAIooB,EAAM,SAAW,EAAG,CAEpBnB,EAAYP,CAAW,EAAI,CAAA,EAC3BT,EAAiBtmB,EAAS,CAAE,YAAAsnB,EAAa,EACzC,MACJ,CAEA,MAAMqB,EAA+B,IAAI,MAAMF,EAAM,MAAM,EAAE,KAAK,IAAI,EAAE,IAAI,KAAO,CAAA,EAAG,EACtF,IAAIzb,EAAW,GAEf,MAAM+I,EAAI1V,EAAO,EAEjB,QAAS8B,EAAI,EAAGA,EAAIsmB,EAAM,OAAQtmB,IAAK,CACnC,MAAMymB,EAAOH,EAAMtmB,CAAC,EA0BpB,GAvBIA,IAAM,GAAKymB,EAAK,MAAQ,IACxBD,EAAWxmB,CAAC,EAAE,IAAM4T,IAAI,oEAAoE,GACrF,uBACP/I,EAAW,IAIX7K,IAAMsmB,EAAM,OAAS,GAAKG,EAAK,MAAQ,MAAQA,EAAK,MAAQ,SAC5DD,EAAWxmB,CAAC,EAAE,IAAM4T,IAAI,wEAAwE,GACzF,wBACP/I,EAAW,IAIX7K,EAAIsmB,EAAM,OAAS,GAAKG,EAAK,MAAQ,MAAQA,EAAK,MAAQ,QACtDA,EAAK,KAAOA,EAAK,MACjBD,EAAWxmB,CAAC,EAAE,IAAM4T,IAAI,uEAAuE,GACxF,sBACP/I,EAAW,IAKf7K,EAAIsmB,EAAM,OAAS,EAAG,CACtB,MAAMI,EAAWJ,EAAMtmB,EAAI,CAAC,EACxBymB,EAAK,MAAQ,MAAQA,EAAK,MAAQ,QAAcA,EAAK,IAAM,IAAOC,EAAS,MAC3EF,EAAWxmB,CAAC,EAAE,IAAM4T,IAAI,gEAAgE,GACjF,kBACP/I,EAAW,GAEnB,CAGI4b,EAAK,IAAM,IACXD,EAAWxmB,CAAC,EAAE,IAAM4T,IAAI,sEAAsE,GACvF,oBACP/I,EAAW,GAEnB,CAGAsa,EAAYP,CAAW,EAAI/Z,EAAW2b,EAAa,CAAA,EAEnDrC,EAAiBtmB,EAAS,CAAE,YAAAsnB,EAAa,CAC7C,CAQO,SAASwB,GACZ/oB,EACAC,EACI,CACJ,MAAMb,EAASY,EAAO,QAAU,CAAA,EAC1B6nB,EAAe,OAAOzoB,EAAO,cAAgB,CAAC,EAE9CkB,EAASkb,EAAA,EACf,GAAI,CAAClb,EAAQ,OAEb,MAAMumB,EAAkBL,EAAmBlmB,CAAM,EAC3C2mB,EAAKJ,EAAgBgB,CAAY,EACvC,GAAI,CAACZ,EAAI,CACLpnB,EAAO,KAAK,gDAAiDgoB,CAAY,EACzE,MACJ,CAGA,GAAIZ,EAAG,eAAiB,KAAM,CAC1BpnB,EAAO,KAAK,6DAA8DonB,EAAG,YAAY,EACzF,MACJ,CAEA,MAAM+B,EAAkB,CAAC,GAAI/B,EAAG,oBAAsB,CAAA,CAAG,EACzD+B,EAAgB,KAAK,CAAE,QAAS,GAAI,IAAK,EAAG,OAAQ,GAAI,EAGxDnC,EAAgBgB,CAAY,EAAI,CAAE,GAAGZ,EAAI,mBAAoB+B,CAAA,EAE7DzC,EAAiBtmB,EAAS,CACtB,wBAAyB4mB,CAAA,CAC5B,EACDhnB,EAAO,IAAI,qCAAsCmpB,EAAgB,MAAM,CAC3E,CAQO,SAASC,GACZjpB,EACAC,EACI,CACJ,MAAMb,EAASY,EAAO,QAAU,CAAA,EAC1B6nB,EAAe,OAAOzoB,EAAO,cAAgB,CAAC,EAC9C8pB,EAAW,OAAO9pB,EAAO,QAAQ,EAEvC,GAAI,MAAM8pB,CAAQ,GAAKA,EAAW,EAAG,CACjCrpB,EAAO,KAAK,wCAAyCT,EAAO,QAAQ,EACpE,MACJ,CAEA,MAAMkB,EAASkb,EAAA,EACf,GAAI,CAAClb,EAAQ,OAEb,MAAMumB,EAAkBL,EAAmBlmB,CAAM,EAC3C2mB,EAAKJ,EAAgBgB,CAAY,EACvC,GAAI,CAACZ,EAAI,CACLpnB,EAAO,KAAK,mDAAoDgoB,CAAY,EAC5E,MACJ,CAEA,MAAMmB,EAAkB,CAAC,GAAI/B,EAAG,oBAAsB,CAAA,CAAG,EACzD,GAAIiC,GAAYF,EAAgB,OAAQ,CACpCnpB,EAAO,KAAK,8CAA+CqpB,CAAQ,EACnE,MACJ,CAEAF,EAAgB,OAAOE,EAAU,CAAC,EAGlCrC,EAAgBgB,CAAY,EAAI,CAAE,GAAGZ,EAAI,mBAAoB+B,CAAA,EAE7DzC,EAAiBtmB,EAAS,CACtB,wBAAyB4mB,CAAA,CAC5B,EACDhnB,EAAO,IAAI,qCAAsCqpB,EAAU,aAAcF,EAAgB,MAAM,CACnG,CAQO,SAASG,GACZnpB,EACAC,EACI,CACJ,MAAMb,EAASY,EAAO,QAAU,CAAA,EAC1B6nB,EAAe,OAAOzoB,EAAO,cAAgB,CAAC,EAC9CgqB,EAAWhqB,EAAO,SAExB,GAAI,CAACgqB,GAAY,CAAC,MAAM,QAAQA,CAAQ,EAAG,CACvCvpB,EAAO,KAAK,4CAA6CupB,CAAQ,EACjE,MACJ,CAEA,MAAM9oB,EAASkb,EAAA,EACf,GAAI,CAAClb,EAAQ,OAEb,MAAMumB,EAAkBL,EAAmBlmB,CAAM,EAC3C2mB,EAAKJ,EAAgBgB,CAAY,EACvC,GAAI,CAACZ,EAAI,CACLpnB,EAAO,KAAK,uDAAwDgoB,CAAY,EAChF,MACJ,CAGAhB,EAAgBgB,CAAY,EAAI,CAAE,GAAGZ,EAAI,mBAAoBmC,CAAA,EAE7D7C,EAAiBtmB,EAAS,CACtB,wBAAyB4mB,CAAA,CAC5B,EAGD,MAAM7Q,EAAI1V,EAAO,EACX+oB,EAAUrT,IAAI,+DAA+D,GAC5E,gBACP1V,EAAO,OAAO,UAAU+oB,CAAO,EAE/BxpB,EAAO,IAAI,gDAAiDupB,EAAS,OAAQ,MAAM,CACvF,CAQO,SAASE,GACZtpB,EACAC,EACI,CACJ,MAAMb,EAASY,EAAO,QAAU,CAAA,EAC1BK,EAAQ,WAAWjB,EAAO,KAAe,GAAK,EAE9CkB,EAASkb,EAAA,EACf,GAAI,CAAClb,EAAQ,OAEb,MAAMoH,EAAapH,EAAO,MAAM,WAAA,GAAgB,CAAA,EAC1CunB,EAAe,OAAOngB,EAAW,kBAAoB,CAAC,EACtDmf,EAAkBL,EAAmBlmB,CAAM,EAC3C2mB,EAAKJ,EAAgBgB,CAAY,EAClCZ,IAGLJ,EAAgBgB,CAAY,EAAI,CAC5B,GAAGZ,EACH,OAAQ,CAAE,GAAIA,EAAG,QAAU,CAAA,EAAK,KAAMA,EAAG,cAAe,WAAY5mB,CAAA,CAAM,EAG9EkmB,EAAiBtmB,EAAS,CACtB,wBAAyB4mB,CAAA,CAC5B,EACDhnB,EAAO,IAAI,wCAAyCQ,CAAK,EAC7D,CAQO,SAASkpB,GACZ9f,EACAxJ,EACI,CACJ,MAAMK,EAASkb,EAAA,EACf,GAAI,CAAClb,EAAQ,OAEb,MAAMoH,EAAapH,EAAO,MAAM,WAAA,GAAgB,CAAA,EAC1CunB,EAAe,OAAOngB,EAAW,kBAAoB,CAAC,EACtDmf,EAAkBL,EAAmBlmB,CAAM,EAC3C2mB,EAAKJ,EAAgBgB,CAAY,EACvC,GAAI,CAACZ,EAAI,OAET,MAAMuC,EAAgB,CAAC,GAAIvC,EAAG,oBAAsB,CAAA,CAAG,EACvDuC,EAAc,KAAK,EAAE,EAGrB3C,EAAgBgB,CAAY,EAAI,CAAE,GAAGZ,EAAI,mBAAoBuC,CAAA,EAE7DjD,EAAiBtmB,EAAS,CACtB,wBAAyB4mB,CAAA,CAC5B,EACDhnB,EAAO,IAAI,2CAA4C2pB,EAAc,MAAM,CAC/E,CAQO,SAASC,GACZzpB,EACAC,EACI,CACJ,MAAMb,EAASY,EAAO,QAAU,CAAA,EAC1B0pB,EAAa,OAAOtqB,EAAO,UAAU,EACrCiB,EAASjB,EAAO,OAAS,GAE/B,GAAI,MAAMsqB,CAAU,GAAKA,EAAa,EAAG,CACrC7pB,EAAO,KAAK,8CAA+CT,EAAO,UAAU,EAC5E,MACJ,CAEA,MAAMkB,EAASkb,EAAA,EACf,GAAI,CAAClb,EAAQ,OAEb,MAAMoH,EAAapH,EAAO,MAAM,WAAA,GAAgB,CAAA,EAC1CunB,EAAe,OAAOngB,EAAW,kBAAoB,CAAC,EACtDmf,EAAkBL,EAAmBlmB,CAAM,EAC3C2mB,EAAKJ,EAAgBgB,CAAY,EACvC,GAAI,CAACZ,EAAI,OAET,MAAMuC,EAAgB,CAAC,GAAIvC,EAAG,oBAAsB,CAAA,CAAG,EACnDyC,GAAcF,EAAc,SAEhCA,EAAcE,CAAU,EAAIrpB,EAG5BwmB,EAAgBgB,CAAY,EAAI,CAAE,GAAGZ,EAAI,mBAAoBuC,CAAA,EAE7DjD,EAAiBtmB,EAAS,CACtB,wBAAyB4mB,CAAA,CAC5B,EACDhnB,EAAO,IAAI,yCAA0C6pB,EAAY,SAAUrpB,CAAK,EACpF,CAQO,SAASspB,GACZ3pB,EACAC,EACI,CACJ,MAAMb,EAASY,EAAO,QAAU,CAAA,EAC1B0pB,EAAa,OAAOtqB,EAAO,UAAU,EAE3C,GAAI,MAAMsqB,CAAU,GAAKA,EAAa,EAAG,CACrC7pB,EAAO,KAAK,8CAA+CT,EAAO,UAAU,EAC5E,MACJ,CAEA,MAAMkB,EAASkb,EAAA,EACf,GAAI,CAAClb,EAAQ,OAEb,MAAMoH,EAAapH,EAAO,MAAM,WAAA,GAAgB,CAAA,EAC1CunB,EAAe,OAAOngB,EAAW,kBAAoB,CAAC,EACtDmf,EAAkBL,EAAmBlmB,CAAM,EAC3C2mB,EAAKJ,EAAgBgB,CAAY,EACvC,GAAI,CAACZ,EAAI,OAET,MAAMuC,EAAgB,CAAC,GAAIvC,EAAG,oBAAsB,CAAA,CAAG,EACnDyC,GAAcF,EAAc,SAEhCA,EAAc,OAAOE,EAAY,CAAC,EAGlC7C,EAAgBgB,CAAY,EAAI,CAAE,GAAGZ,EAAI,mBAAoBuC,CAAA,EAE7DjD,EAAiBtmB,EAAS,CACtB,wBAAyB4mB,CAAA,CAC5B,EACDhnB,EAAO,IAAI,yCAA0C6pB,EAAY,aAAcF,EAAc,MAAM,EACvG,CAYO,SAASI,GACZ5pB,EACAC,EACI,CAEJ,MAAME,GADSH,EAAO,QAAU,CAAA,GACV,OAAS,GAC/B,GAAI,CAACG,EAAO,CACRN,EAAO,KAAK,6CAA6C,EACzD,MACJ,CAEA,MAAMS,EAASkb,EAAA,EACf,GAAI,CAAClb,EAAQ,OAEb,MAAMoH,EAAapH,EAAO,MAAM,WAAA,GAAgB,CAAA,EAC1CunB,EAAe,OAAOngB,EAAW,kBAAoB,CAAC,EACtDmf,EAAkBL,EAAmBlmB,CAAM,EAC3C2mB,EAAKJ,EAAgBgB,CAAY,EACvC,GAAI,CAACZ,EAAI,OAET,MAAMuC,EAAgB,CAAC,GAAIvC,EAAG,oBAAsB,CAAA,CAAG,EACjDlO,EAAgByQ,EAAc,QAAQrpB,CAAK,EAE7C4Y,GAAiB,EACjByQ,EAAc,OAAOzQ,EAAe,CAAC,EAErCyQ,EAAc,KAAKrpB,CAAK,EAI5B,MAAM0pB,EAAaL,EAAc,OAAS,EAAIA,EAAgB,KAG9D3C,EAAgBgB,CAAY,EAAI,CAAE,GAAGZ,EAAI,mBAAoB4C,CAAA,EAE7DtD,EAAiBtmB,EAAS,CACtB,wBAAyB4mB,CAAA,CAC5B,EACDhnB,EAAO,IAAI,mCAAoCM,EAAO,YAAa0pB,CAAU,CACjF,CAUO,SAASC,GACZ9pB,EACAC,EACI,CACJ,MAAMb,EAASY,EAAO,QAAU,CAAA,EAC1BG,EAASf,EAAO,OAAS,GAC/B,GAAI,CAACe,EAAO,CACRN,EAAO,KAAK,4CAA4C,EACxD,MACJ,CAEA,MAAMS,EAASkb,EAAA,EACf,GAAI,CAAClb,EAAQ,OAEb,MAAMoH,EAAapH,EAAO,MAAM,WAAA,GAAgB,CAAA,EAC1C8N,EAAQ,OAAO1G,EAAW,kBAAoB,CAAC,EAC/Cmf,EAAkBL,EAAmBlmB,CAAM,EAC3C2mB,EAAKJ,EAAgBzY,CAAK,EAChC,GAAI,CAAC6Y,EAAI,OAET,MAAM8C,EAAwB,CAAE,GAAI9C,EAAG,YAAc,CAAA,EAAK,CAAC9mB,CAAK,EAAGf,EAAO,KAAA,EAG1EynB,EAAgBzY,CAAK,EAAI,CAAE,GAAG6Y,EAAI,WAAY8C,CAAA,EAE9CxD,EAAiBtmB,EAAS,CACtB,wBAAyB4mB,CAAA,CAC5B,EACDhnB,EAAO,IAAI,iCAAkCM,EAAO,YAAaiO,CAAK,CAC1E,CAQO,SAAS4b,GACZhqB,EACAC,EACI,CACJ,MAAMb,EAASY,EAAO,QAAU,CAAA,EAC1BG,EAASf,EAAO,OAAS,GAC/B,GAAI,CAACe,EAAO,CACRN,EAAO,KAAK,yCAAyC,EACrD,MACJ,CAEA,MAAMS,EAASkb,EAAA,EACf,GAAI,CAAClb,EAAQ,OAEb,MAAMoH,EAAapH,EAAO,MAAM,WAAA,GAAgB,CAAA,EAC1C8N,EAAQ,OAAO1G,EAAW,kBAAoB,CAAC,EAC/Cmf,EAAkBL,EAAmBlmB,CAAM,EAC3C2mB,EAAKJ,EAAgBzY,CAAK,EAChC,GAAI,CAAC6Y,EAAI,OAET,MAAMgD,EAAe7qB,EAAO,OAAS,GAC/B8qB,EAAmC,CAAE,GAAIjD,EAAG,YAAY,WAAa,CAAA,CAAC,EAExEgD,EAAY,KAAA,IAAW,GACvB,OAAOC,EAAS/pB,CAAK,EAErB+pB,EAAS/pB,CAAK,EAAI8pB,EAGtB,MAAMF,EAAwB,CAC1B,GAAI9C,EAAG,YAAc,CAAA,EACrB,UAAW,OAAO,KAAKiD,CAAQ,EAAE,OAAS,EAAIA,EAAW,IAAA,EAG7DrD,EAAgBzY,CAAK,EAAI,CAAE,GAAG6Y,EAAI,WAAY8C,CAAA,EAE9CxD,EAAiBtmB,EAAS,CACtB,wBAAyB4mB,CAAA,CAC5B,EACDhnB,EAAO,IAAI,6BAA8BM,EAAO,IAAK8pB,GAAe,WAAW,CACnF,CAUO,SAASE,GACZnqB,EACAC,EACI,CACJ,MAAMK,EAASkb,EAAA,EACf,GAAI,CAAClb,EAAQ,OAEb,MAAMoH,EAAapH,EAAO,MAAM,WAAA,GAAgB,CAAA,EAC1C8N,EAAQ,OAAO1G,EAAW,kBAAoB,CAAC,EAE/Cuf,EADkBT,EAAmBlmB,CAAM,EACtB8N,CAAK,EAChC,GAAK6Y,EAEL,IAAI,CAACA,EAAG,aAAc,CAClB3mB,EAAO,OAAO,UAAUA,EAAO,IAAI,yEAAyE,GAAK,mBAAmB,EACpI,MACJ,CAGAA,EAAO,MAAM,MAAM,CAAE,eAAgB,GAAM,cAAe,KAAM,EAKhEA,EAAO,SAAS,CACZ,QAAS,UACT,OAAQ,uEACR,cAAe,GACf,OAAQ,CACJ,OAAQ,OACR,KAAM,CACF,SAAU2mB,EAAG,aACb,eAAgBA,EAAG,mBACnB,OAAQA,EAAG,YAAc,CAAA,EACzB,OAAQ,CACJ,aAAcA,EAAG,YAAA,CACrB,CACJ,EAEJ,UAAW,CACP,QAAS,WACT,OAAQ,CACJ,OAAQ,SACR,eAAgB,GAChB,cAAe,mBAAA,CACnB,EAEJ,QAAS,CACL,QAAS,WACT,OAAQ,CACJ,OAAQ,SACR,eAAgB,GAChB,cAAe,2BAAA,CACnB,CACJ,CACH,EACL,CAQO,SAASmD,GACZpqB,EACAC,EACI,CACJ,MAAMb,EAASY,EAAO,QAAU,CAAA,EAC1BkpB,EAAW,OAAO9pB,EAAO,QAAQ,EACjCe,EAAQf,EAAO,MACf+iB,EAAW/iB,EAAO,MAExB,GAAI,MAAM8pB,CAAQ,GAAKA,EAAW,EAAG,CACjCrpB,EAAO,KAAK,0CAA2CT,EAAO,QAAQ,EACtE,MACJ,CACA,GAAI,CAAC,CAAC,UAAW,MAAO,QAAQ,EAAE,SAASe,CAAK,EAAG,CAC/CN,EAAO,KAAK,uCAAwCM,CAAK,EACzD,MACJ,CAEA,MAAMG,EAASkb,EAAA,EACf,GAAI,CAAClb,EAAQ,OAEb,MAAMoH,EAAapH,EAAO,MAAM,WAAA,GAAgB,CAAA,EAC1CunB,EAAe,OAAOngB,EAAW,kBAAoB,CAAC,EACtDmf,EAAkBL,EAAmBlmB,CAAM,EAC3C2mB,EAAKJ,EAAgBgB,CAAY,EACvC,GAAI,CAACZ,EAAI,OAET,MAAM+B,EAAkB,CAAC,GAAI/B,EAAG,oBAAsB,CAAA,CAAG,EACzD,GAAIiC,GAAYF,EAAgB,OAAQ,OAExC,MAAM3oB,EAAQF,IAAU,MAAS,WAAWgiB,CAAkB,GAAK,EAAKA,EACxE6G,EAAgBE,CAAQ,EAAI,CAAE,GAAGF,EAAgBE,CAAQ,EAAG,CAAC/oB,CAAK,EAAGE,CAAA,EAGrEwmB,EAAgBgB,CAAY,EAAI,CAAE,GAAGZ,EAAI,mBAAoB+B,CAAA,EAE7DzC,EAAiBtmB,EAAS,CACtB,wBAAyB4mB,CAAA,CAC5B,EACDhnB,EAAO,IAAI,uCAAwCqpB,EAAU/oB,EAAO,IAAKE,CAAK,CAClF,CC9vCA,MAAMR,GAAW,OAAe,QAAQ,eAAe,sBAAsB,GAAM,CAC/E,IAAK,IAAIC,IAAoB,QAAQ,IAAI,yBAA0B,GAAGA,CAAI,EAC1E,KAAM,IAAIA,IAAoB,QAAQ,KAAK,yBAA0B,GAAGA,CAAI,EAC5E,MAAO,IAAIA,IAAoB,QAAQ,MAAM,yBAA0B,GAAGA,CAAI,CAClF,EAWA,SAASuqB,GAAiBjU,EAAuB7P,EAAiC,CAC9E,UAAWiM,KAAQ4D,EAAO,CACtB,GAAI5D,EAAK,KAAOjM,EACZ,OAAOiM,EAEX,GAAIA,EAAK,UAAYA,EAAK,SAAS,OAAS,EAAG,CAC3C,MAAMhK,EAAQ6hB,GAAiB7X,EAAK,SAAUjM,CAAE,EAChD,GAAIiC,EACA,OAAOA,CAEf,CACJ,CACA,OAAO,IACX,CAEA,SAAS8hB,GAAcC,EAAkC,CAErD,OADkB,IAAI,gBAAgB,OAAO,SAAS,MAAM,EAC3C,IAAIA,CAAS,CAClC,CAEA,SAASC,GAAcpqB,EAA2B,CAE9C,OADgB,OAAe,QAChB,OAAO,gBAAgBA,CAAY,CACtD,CAEA,eAAeqqB,GACXrqB,EACAsqB,EACAC,EAAsB,GACtBC,EAAmB,IACE,CACrB,QAASxoB,EAAI,EAAGA,EAAIuoB,EAAavoB,IAAK,CAClC,MAAMyoB,EAAaL,GAAcpqB,CAAY,EACvC4a,EAAO0P,EAASG,CAAU,EAChC,GAAI,MAAM,QAAQ7P,CAAI,GAAKA,EAAK,OAAS,EACrC,OAAOA,EAEX,MAAM,IAAI,QAAS8P,GAAY,WAAWA,EAASF,CAAQ,CAAC,CAChE,CACA,OAAO,IACX,CAEA,eAAsBG,GAClBthB,EACApK,EACa,CACb,MAAM2rB,EAAU,OAAe,OAEzBC,EAAUX,GAAc,IAAI,EAC5B7T,EAAO6T,GAAc,MAAM,EAEjC,GAAI,CAACW,EAAS,CACVprB,GAAO,IAAI,8CAA8C,EACzD,MACJ,CAEA,GAAI,CAACmrB,GAAQ,OAAO,IAAK,CACrBnrB,GAAO,KAAK,sDAAsD,EAClE,MACJ,CAEA,MAAMmb,EAAO,MAAMyP,GAAkB,aAAeS,GAAOA,GAAI,MAAM,IAAI,EAEzE,GAAI,CAAClQ,EAAM,CACPnb,GAAO,KAAK,mEAAmE,EAC/E,MACJ,CAEA,MAAMsK,EAAa,SAAS8gB,EAAS,EAAE,EACjCziB,EAAQ6hB,GAAiBrP,EAAM7Q,CAAU,EAE/C,GAAI,CAAC3B,EAAO,CACR3I,GAAO,KAAK,oDAAqDsK,CAAU,EAC3E,MACJ,CAEA,MAAMghB,EAAY1U,IAAS,OAAS,OAAS,OAE7CuU,EAAO,MAAM,IAAI,CACb,mBAAoBxiB,EAAM,GAC1B,iBAAkBA,EAClB,UAAA2iB,CAAA,CACH,EAEDtrB,GAAO,IAAI,uDAAwD,CAC/D,GAAIsK,EACJ,KAAMghB,CAAA,CACT,CACL,CCvGA,MAAMtrB,GAAW,OAAe,QAAQ,eAAe,mBAAmB,GAAM,CAC5E,IAAK,IAAIC,IAAoB,QAAQ,IAAI,sBAAuB,GAAGA,CAAI,EACvE,KAAM,IAAIA,IAAoB,QAAQ,KAAK,sBAAuB,GAAGA,CAAI,EACzE,MAAO,IAAIA,IAAoB,QAAQ,MAAM,sBAAuB,GAAGA,CAAI,CAC/E,EAEA,SAASwqB,GAAcC,EAAkC,CAErD,OADkB,IAAI,gBAAgB,OAAO,SAAS,MAAM,EAC3C,IAAIA,CAAS,CAClC,CAEA,SAASC,GAAcpqB,EAA2B,CAE9C,OADgB,OAAe,QAChB,OAAO,gBAAgBA,CAAY,CACtD,CAEA,eAAeqqB,GACXrqB,EACAsqB,EACAC,EAAsB,GACtBC,EAAmB,IACE,CACrB,QAASxoB,EAAI,EAAGA,EAAIuoB,EAAavoB,IAAK,CAClC,MAAMyoB,EAAaL,GAAcpqB,CAAY,EACvC4a,EAAO0P,EAASG,CAAU,EAChC,GAAI,MAAM,QAAQ7P,CAAI,GAAKA,EAAK,OAAS,EACrC,OAAOA,EAEX,MAAM,IAAI,QAAS8P,GAAY,WAAWA,EAASF,CAAQ,CAAC,CAChE,CACA,OAAO,IACX,CAEA,eAAsBQ,GAClB3hB,EACApK,EACa,CACb,MAAM2rB,EAAU,OAAe,OAEzBC,EAAUX,GAAc,IAAI,EAC5B7T,EAAO6T,GAAc,MAAM,EAEjC,GAAI,CAACW,EAAS,CACVprB,GAAO,IAAI,2CAA2C,EACtD,MACJ,CAEA,GAAI,CAACmrB,GAAQ,OAAO,IAAK,CACrBnrB,GAAO,KAAK,mDAAmD,EAC/D,MACJ,CAEA,MAAMmb,EAAO,MAAMyP,GAAkB,SAAWS,GAAOA,GAAI,MAAM,IAAI,EAErE,GAAI,CAAClQ,EAAM,CACPnb,GAAO,KAAK,4DAA4D,EACxE,MACJ,CAEA,MAAM8L,EAAU,SAASsf,EAAS,EAAE,EAC9BziB,EAAQwS,EAAK,KAAMxI,GAAcA,EAAK,KAAO7G,CAAO,EAE1D,GAAI,CAACnD,EAAO,CACR3I,GAAO,KAAK,8CAA+C8L,CAAO,EAClE,MACJ,CAEA,MAAMwf,EAAY1U,IAAS,OAAS,OAAS,OAE7CuU,EAAO,MAAM,IAAI,CACb,gBAAiBxiB,EAAM,GACvB,cAAeA,EACf,UAAA2iB,CAAA,CACH,EAEDtrB,GAAO,IAAI,iDAAkD,CACzD,GAAI8L,EACJ,KAAMwf,CAAA,CACT,CACL,CC/EA,MAAMtrB,GAAW,OAAe,QAAQ,eAAe,wBAAwB,GAAM,CACjF,IAAK,IAAIC,IAAoB,QAAQ,IAAI,2BAA4B,GAAGA,CAAI,EAC5E,KAAM,IAAIA,IAAoB,QAAQ,KAAK,2BAA4B,GAAGA,CAAI,EAC9E,MAAO,IAAIA,IAAoB,QAAQ,MAAM,2BAA4B,GAAGA,CAAI,CACpF,EAEA,SAASwqB,GAAcC,EAAkC,CAErD,OADkB,IAAI,gBAAgB,OAAO,SAAS,MAAM,EAC3C,IAAIA,CAAS,CAClC,CAEA,SAASC,GAAcpqB,EAA2B,CAE9C,OADgB,OAAe,QAChB,OAAO,gBAAgBA,CAAY,CACtD,CAEA,eAAeqqB,GACXrqB,EACAsqB,EACAC,EAAsB,GACtBC,EAAmB,IACE,CACrB,QAASxoB,EAAI,EAAGA,EAAIuoB,EAAavoB,IAAK,CAClC,MAAMyoB,EAAaL,GAAcpqB,CAAY,EACvC4a,EAAO0P,EAASG,CAAU,EAChC,GAAI,MAAM,QAAQ7P,CAAI,GAAKA,EAAK,OAAS,EACrC,OAAOA,EAEX,MAAM,IAAI,QAAS8P,GAAY,WAAWA,EAASF,CAAQ,CAAC,CAChE,CACA,OAAO,IACX,CAEA,eAAsBS,GAClB5hB,EACApK,EACa,CACb,MAAM2rB,EAAU,OAAe,OAEzBC,EAAUX,GAAc,IAAI,EAC5B7T,EAAO6T,GAAc,MAAM,EAEjC,GAAI,CAACW,EAAS,CACVprB,GAAO,IAAI,gDAAgD,EAC3D,MACJ,CAEA,GAAI,CAACmrB,GAAQ,OAAO,IAAK,CACrBnrB,GAAO,KAAK,wDAAwD,EACpE,MACJ,CAEA,MAAMmb,EAAO,MAAMyP,GAAkB,cAAgBS,GAAOA,GAAI,IAAI,EAEpE,GAAI,CAAClQ,EAAM,CACPnb,GAAO,KAAK,sEAAsE,EAClF,MACJ,CAEA,MAAMyrB,EAAS,SAASL,EAAS,EAAE,EAC7BziB,EAAQwS,EAAK,KAAMxI,GAAcA,EAAK,KAAO8Y,CAAM,EAEzD,GAAI,CAAC9iB,EAAO,CACR3I,GAAO,KAAK,yDAA0DyrB,CAAM,EAC5E,MACJ,CAEA,MAAMH,EAAY1U,IAAS,OAAS,OAAS,OAE7CuU,EAAO,MAAM,IAAI,CACb,WAAYxiB,EAAM,GAClB,aAAcA,EACd,UAAA2iB,CAAA,CACH,EAEDtrB,GAAO,IAAI,4DAA6D,CACpE,GAAIyrB,EACJ,KAAMH,CAAA,CACT,CACL,CC/EA,MAAMtrB,GAAW,OAAe,QAAQ,eAAe,oBAAoB,GAAM,CAC7E,IAAK,IAAIC,IAAoB,QAAQ,IAAI,uBAAwB,GAAGA,CAAI,EACxE,KAAM,IAAIA,IAAoB,QAAQ,KAAK,uBAAwB,GAAGA,CAAI,EAC1E,MAAO,IAAIA,IAAoB,QAAQ,MAAM,uBAAwB,GAAGA,CAAI,CAChF,EAEA,SAASwqB,GAAcC,EAAkC,CAErD,OADkB,IAAI,gBAAgB,OAAO,SAAS,MAAM,EAC3C,IAAIA,CAAS,CAClC,CAEA,SAASC,GAAcpqB,EAA2B,CAE9C,OADgB,OAAe,QAChB,OAAO,gBAAgBA,CAAY,CACtD,CAEA,eAAeqqB,GACXrqB,EACAsqB,EACAC,EAAsB,GACtBC,EAAmB,IACE,CACrB,QAASxoB,EAAI,EAAGA,EAAIuoB,EAAavoB,IAAK,CAClC,MAAMyoB,EAAaL,GAAcpqB,CAAY,EACvC4a,EAAO0P,EAASG,CAAU,EAChC,GAAI,MAAM,QAAQ7P,CAAI,GAAKA,EAAK,OAAS,EACrC,OAAOA,EAEX,MAAM,IAAI,QAAS8P,GAAY,WAAWA,EAASF,CAAQ,CAAC,CAChE,CACA,OAAO,IACX,CAEA,eAAsBW,GAClB9hB,EACApK,EACa,CACb,MAAM2rB,EAAU,OAAe,OAEzBC,EAAUX,GAAc,IAAI,EAC5B7T,EAAO6T,GAAc,MAAM,EAEjC,GAAI,CAACW,EAAS,CACVprB,GAAO,IAAI,4CAA4C,EACvD,MACJ,CAEA,GAAI,CAACmrB,GAAQ,OAAO,IAAK,CACrBnrB,GAAO,KAAK,oDAAoD,EAChE,MACJ,CAEA,MAAMmb,EAAO,MAAMyP,GAAkB,YAAcS,GAAOA,GAAI,IAAI,EAElE,GAAI,CAAClQ,EAAM,CACPnb,GAAO,KAAK,gEAAgE,EAC5E,MACJ,CAEA,MAAM4V,EAAa,SAASwV,EAAS,EAAE,EACjCziB,EAAQwS,EAAK,KAAMxI,GAAcA,EAAK,KAAOiD,CAAU,EAE7D,GAAI,CAACjN,EAAO,CACR3I,GAAO,KAAK,kDAAmD4V,CAAU,EACzE,MACJ,CAEA,MAAM0V,EAAY1U,IAAS,OAAS,OAAS,OAE7CuU,EAAO,MAAM,IAAI,CACb,mBAAoBxiB,EAAM,GAC1B,iBAAkBA,EAClB,UAAA2iB,CAAA,CACH,EAEDtrB,GAAO,IAAI,qDAAsD,CAC7D,GAAI4V,EACJ,KAAM0V,CAAA,CACT,CACL,CCyKO,MAAMK,GAAa,CAEtB,mBAAoBzrB,GACpB,kBAAmByB,GACnB,wBAAyBrC,GACzB,sBAAuB6E,GACvB,6BAA8BQ,GAC9B,aAAca,GACd,iBAAkBC,GAElB,aAAcQ,GACd,qBAAsBU,GACtB,sBAAuBE,GACvB,uBAAwBC,GACxB,qBAAsBM,GAEtB,wBAAyBM,GACzB,YAAaK,GAGb,eAAgBiB,GAChB,qBAAsBQ,GACtB,uBAAwBG,GACxB,4BAA6BU,GAC7B,sBAAuBK,GACvB,qBAAsBU,GACtB,6BAA8BN,GAG9B,aAAce,GACd,oBAAqBK,GAGrB,YAAaM,GACb,0BAA2BQ,GAC3B,sBAAuBE,GAGvB,eAAgBwD,GAChB,kBAAmBC,GACnB,kBAAmBC,GACnB,gBAAiBI,GACjB,aAAcgB,GACd,mBAAoBG,GACpB,gBAAiBG,GACjB,mBAAoBG,GACpB,mBAAoBC,GACpB,qBAAsBE,GACtB,oBAAqBI,GACrB,uBAAwBE,GACxB,uBAAwBE,GACxB,yBAA0BC,GAC1B,uBAAwBC,GACxB,yBAA0BE,GAC1B,4BAA6BE,GAC7B,4BAA6BG,GAG7B,aAAcI,GACd,aAAciB,GACd,cAAeG,GAGf,kBAAmBE,GAGnB,4BAA6BI,GAC7B,qBAAsBI,GACtB,iBAAkBO,GAClB,iBAAkBE,GAClB,mBAAoBC,GACpB,oBAAqBC,GACrB,iBAAkBC,GAClB,sBAAuBE,GACvB,cAAeC,GACf,qBAAsBC,GACtB,qBAAsBC,GACtB,0BAA2BS,GAC3B,sCAAuCP,GACvC,mCAAoCI,GAGpC,YAAaM,GACb,oBAAqBI,GACrB,sBAAuBC,GACvB,qBAAsBI,GACtB,0BAA2BC,GAC3B,mBAAoBG,GACpB,yBAA0BI,GAC1B,qBAAsBC,GACtB,gBAAiBG,GACjB,kBAAmBE,GACnB,sBAAuBC,GACvB,yBAA0BE,GAC1B,yBAA0BE,GAC1B,mBAAoBI,GACpB,4BAA6BF,GAG7B,sBAAuBI,GACvB,kBAAmBS,GACnB,YAAaF,GACb,wBAAyBH,GACzB,yBAA0BC,GAC1B,oBAAqBC,GAGrB,wBAAyBO,GACzB,mBAAoBI,GACpB,qBAAsBC,GAGtB,yBAA0B4Q,GAC1B,aAAc9c,GACd,sBAAuBJ,GACvB,kCAAmCY,GAGnC,WAAYsM,GACZ,iBAAkBmB,GAGlB,kBAAmBU,GACnB,iBAAkBG,GAClB,oBAAqBM,GACrB,mBAAoBK,GACpB,qBAAsBC,GACtB,uBAAwBG,GACxB,0BAA2B2B,GAC3B,uBAAwBI,GACxB,mBAAoBvB,GACpB,mBAAoBQ,GACpB,mBAAoBE,GACpB,mBAAoBI,GAGpB,uBAAwBU,GAGxB,oBAAqBE,GACrB,uBAAwBE,GACxB,kBAAmBG,GACnB,gCAAiCG,GACjC,6BAA8BU,GAC9B,cAAeG,GACf,qBAAsBE,GACtB,wBAAyBK,GACzB,eAAgBG,GAGhB,uBAAwBgE,GACxB,kBAAmBM,GACnB,qBAAsBI,GACtB,iBAAkBM,GAClB,mBAAoBE,GACpB,qBAAsBC,GACtB,aAAcI,GACd,gBAAiBK,GACjB,qBAAsBE,GACtB,mBAAoBE,GACpB,eAAgBM,GAChB,kBAAmBE,GACnB,sBAAuBE,GACvB,gBAAiBG,GACjB,mBAAoBC,GACpB,sBAAuBE,GACvB,sBAAuBE,GACvB,sBAAuBC,GACvB,qBAAsBE,GACtB,kBAAmBE,GACnB,gBAAiBG,GACjB,oBAAqBC,GAGrB,qBAAsBlH,GACtB,qBAAsBI,GACtB,qBAAsBG,GACtB,mBAAoBE,GACpB,uBAAAV,GAGA,oBAAqBoB,GACrB,qBAAsBG,GAGtB,oBAAqBE,GACrB,qBAAsBG,GACtB,0BAA2BC,GAC3B,yBAA0BC,GAC1B,mBAAoBC,GACpB,yBAA0BI,GAC1B,uBAAwBC,GACxB,2BAAAjB,GAGA,mBAAoBkB,GAGpB,sBAAuBC,GAGvB,aAAcC,GAGd,oBAAqBuF,GACrB,iBAAkBK,GAClB,sBAAuBC,GACvB,kBAAmBE,EACvB,EClcMG,EAAoB,oBAGpB7rB,GAAW,OAAe,QAAQ,eAAe,UAAU6rB,CAAiB,EAAE,GAAM,CACtF,IAAK,IAAI5rB,IAAoB,QAAQ,IAAI,WAAW4rB,CAAiB,IAAK,GAAG5rB,CAAI,EACjF,KAAM,IAAIA,IAAoB,QAAQ,KAAK,WAAW4rB,CAAiB,IAAK,GAAG5rB,CAAI,EACnF,MAAO,IAAIA,IAAoB,QAAQ,MAAM,WAAW4rB,CAAiB,IAAK,GAAG5rB,CAAI,CACzF,EAUA,SAAS6rB,GAAiBC,EAAiB,GAAa,CACpD,MAAMC,EAAoB,OAAe,QAAQ,sBAAA,EAEjD,GAAIA,EAGA,OAAO,QAAQL,EAAU,EAAE,QAAQ,CAAC,CAACM,EAAMC,CAAO,IAAM,CACpD,MAAMC,EAAW,GAAGN,CAAiB,IAAII,CAAI,GAC7CD,EAAiB,gBAAgBG,EAAUD,EAAS,CAChD,SAAU,SACV,OAAQL,CAAA,CACX,CACL,CAAC,EAED7rB,GAAO,IACH,GAAG,OAAO,KAAK2rB,EAAU,EAAE,MAAM,0BACjC,OAAO,KAAKA,EAAU,EAAE,OAAY,GAAGE,CAAiB,IAAII,CAAI,EAAE,CAAA,UAE/DF,EAAO,CAEd,IAAIK,EAAa,EACjB,MAAMC,EAAa,GAEbC,EAAgB,IAAM,CACxB,MAAMC,EAAc,OAAe,QAAQ,sBAAA,EAEvCA,GACA,OAAO,QAAQZ,EAAU,EAAE,QAAQ,CAAC,CAACM,EAAMC,CAAO,IAAM,CACpD,MAAMC,EAAW,GAAGN,CAAiB,IAAII,CAAI,GAC7CM,EAAW,gBAAgBJ,EAAUD,EAAS,CAC1C,SAAU,SACV,OAAQL,CAAA,CACX,CACL,CAAC,EAED7rB,GAAO,IACH,GAAG,OAAO,KAAK2rB,EAAU,EAAE,MAAM,0BACjC,OAAO,KAAKA,EAAU,EAAE,OAAY,GAAGE,CAAiB,IAAII,CAAI,EAAE,CAAA,IAGtEG,IACIA,GAAcC,GACdrsB,GAAO,KACH,4CAA4CosB,CAAU,IAAIC,CAAU,GAAA,EAExE,WAAWC,EAAe,GAAG,GAE7BtsB,GAAO,MACH,mFAAA,EAIhB,EAEAssB,EAAA,CACJ,MACItsB,GAAO,KACH,qDAAA,CAGZ,CAYO,SAASwsB,IAAmB,CAE/B,GAAI,SAAS,aAAe,UACxB,SAAS,iBAAiB,mBAAoB,IAAMV,GAAiB,EAAI,CAAC,MACvE,CAGH,MAAMW,EAAgB,CAAC,CAAE,OAAe,QAAQ,sBAAA,EAChDX,GAAiB,CAAAW,CAA4B,CACjD,CACJ,CAGA,OAAAD,GAAA,EAGI,OAAO,OAAW,MACjB,OAAe,mBAAqB,CACjC,WAAYX,EACZ,SAAU,OAAO,KAAKF,EAAU,EAChC,WAAAa,EAAA"} \ No newline at end of file diff --git a/modules/_bundled/sirsoft-ecommerce/resources/js/__tests__/layouts/admin-ecommerce-settings-tab-scoped-datasources.test.tsx b/modules/_bundled/sirsoft-ecommerce/resources/js/__tests__/layouts/admin-ecommerce-settings-tab-scoped-datasources.test.tsx new file mode 100644 index 00000000..acfaa87a --- /dev/null +++ b/modules/_bundled/sirsoft-ecommerce/resources/js/__tests__/layouts/admin-ecommerce-settings-tab-scoped-datasources.test.tsx @@ -0,0 +1,101 @@ +/** + * @file admin-ecommerce-settings-tab-scoped-datasources.test.tsx + * @description 환경설정 데이터소스 탭 스코프 게이팅 회귀 가드 + * + * 배경: 환경설정 진입 시 모든 탭의 데이터소스를 한꺼번에 fetch 했다(18건). 탭 본문은 + * `if` 로 게이트되어 비활성 탭은 렌더조차 되지 않으므로, 그 데이터는 아무도 읽지 않는 + * 순수 낭비다. 게다가 탭 전환(navigate replace:true → updateQueryParams)마다 전수 재fetch 되어 + * 요청이 2배(36건)로 불어나고 탭 클릭이 1.7초 blocking 됐다. + * + * 엔진의 기본 메커니즘: 데이터소스의 `if` 를 DataSourceManager.filterByCondition 이 평가해 + * fetch 대상에서 제외하고, TemplateApp.updateQueryParams 가 탭 전환 시 `if` 를 재평가해 + * 새 탭의 소스만 fetch 한다. 따라서 탭 전용 소스는 반드시 `if` 게이트를 가져야 하며, + * 탭 전환은 navigate + replace:true 로 updateQueryParams 를 태워야 한다(replaceUrl 은 + * history 만 교체하므로 `if` 재평가가 일어나지 않음). + */ + +import { describe, it, expect } from 'vitest'; + +const layout = require('../../../layouts/admin/admin_ecommerce_settings.json'); + +interface AnyJson { [k: string]: any } + +/** 모든 탭이 공유하는 소스 (폼 본체) — 항상 fetch */ +const SHARED_SOURCE_IDS = ['settings']; + +/** 탭 전용 소스 → 소유 탭 */ +const TAB_SCOPED: Record = { + seoStats: ['seo'], + carriers: ['shipping'], + ecommerceNotificationDefinitions: ['notification_definitions', 'mileage'], + availableChannels: ['notification_definitions'], + ecommerceIdentityPolicies: ['identity_policies'], + ecommerceIdentityPurposes: ['identity_policies'], + identityProviders: ['identity_policies'], +}; + +const sources: AnyJson[] = layout.data_sources; +const byId = (id: string) => sources.find(s => s.id === id); + +describe('admin_ecommerce_settings — 데이터소스 탭 스코프 게이팅', () => { + it('공유 소스(settings)는 if 게이트 없이 항상 fetch 된다', () => { + for (const id of SHARED_SOURCE_IDS) { + const s = byId(id); + expect(s).toBeTruthy(); + expect(s!.if).toBeUndefined(); + expect(s!.auto_fetch).not.toBe(false); + } + }); + + it.each(Object.keys(TAB_SCOPED))('탭 전용 소스 %s 는 if 게이트를 가진다', (id) => { + const s = byId(id); + expect(s).toBeTruthy(); + expect(typeof s!.if).toBe('string'); + expect(s!.if.length).toBeGreaterThan(0); + }); + + it.each(Object.entries(TAB_SCOPED))('%s 의 if 가 소유 탭(%s)을 참조한다', (id, tabs) => { + const cond: string = byId(id)!.if; + for (const tab of tabs as string[]) { + expect(cond).toContain(`'${tab}'`); + } + // 활성 탭 판정에 query.tab 과 _global 을 모두 사용해야 딥링크/클릭 양쪽에서 동작 + expect(cond).toContain('query.tab'); + expect(cond).toContain('activeEcommerceSettingsTab'); + }); + + it('탭 전용 소스는 auto_fetch:false 로 죽여두지 않는다 (if 선택 시 fetch 되어야 함)', () => { + for (const id of Object.keys(TAB_SCOPED)) { + expect(byId(id)!.auto_fetch).not.toBe(false); + } + }); + + // 탭 전환은 updateQueryParams 를 태워 `if` 를 재평가해야 한다. + // replaceUrl 은 history.replaceState 만 하므로 새 탭의 소스가 영원히 로드되지 않는다. + it('탭 전환은 navigate + replace:true 로 수행한다 (replaceUrl 금지)', () => { + const seqs: AnyJson[] = []; + const walk = (n: any) => { + if (!n || typeof n !== 'object') return; + if (Array.isArray(n)) { n.forEach(walk); return; } + if (n.handler === 'sequence' && Array.isArray(n.params?.actions)) seqs.push(n); + Object.values(n).forEach(walk); + }; + walk(layout); + + const tabSwitches = seqs.filter(s => + (s.params.actions as AnyJson[]).some( + a => a?.handler === 'setState' && 'activeEcommerceSettingsTab' in (a.params ?? {}) + ) + ); + expect(tabSwitches.length).toBeGreaterThan(0); + + for (const seq of tabSwitches) { + const acts = seq.params.actions as AnyJson[]; + expect(acts.some(a => a.handler === 'replaceUrl')).toBe(false); + const nav = acts.find(a => a.handler === 'navigate'); + expect(nav).toBeTruthy(); + expect(nav!.params.replace).toBe(true); + expect(nav!.params.query?.tab).toBeTruthy(); + } + }); +}); diff --git a/modules/_bundled/sirsoft-ecommerce/resources/js/__tests__/layouts/admin_ecommerce_settings_mileage_tab.test.tsx b/modules/_bundled/sirsoft-ecommerce/resources/js/__tests__/layouts/admin_ecommerce_settings_mileage_tab.test.tsx index 38668dc4..c896ec5b 100644 --- a/modules/_bundled/sirsoft-ecommerce/resources/js/__tests__/layouts/admin_ecommerce_settings_mileage_tab.test.tsx +++ b/modules/_bundled/sirsoft-ecommerce/resources/js/__tests__/layouts/admin_ecommerce_settings_mileage_tab.test.tsx @@ -387,6 +387,9 @@ describe('유효기간 / 소멸 알림 카드', () => { expect(empty).toBeTruthy(); }); + // URL 갱신은 navigate + replace:true 로 수행한다 — updateQueryParams 를 거쳐야 + // 데이터소스의 `if` 가 재평가되어 알림 탭 전용 소스가 로드된다. + // 가드: admin-ecommerce-settings-tab-scoped-datasources.test.tsx it('알림 설정으로 이동 버튼이 환경설정 알림 탭으로 전환한다', () => { const btn = findById(notificationCard, 'go_to_notification_settings'); const seq = btn.actions[0]; @@ -394,6 +397,7 @@ describe('유효기간 / 소멸 알림 카드', () => { const navigate = seq.params.actions.find((a: any) => a.handler === 'navigate'); expect(navigate.params.path).toBe('/admin/ecommerce/settings'); expect(navigate.params.query.tab).toBe('notification_definitions'); + expect(navigate.params.replace).toBe(true); const setGlobal = seq.params.actions.find((a: any) => a.handler === 'setState'); expect(setGlobal.params.activeEcommerceSettingsTab).toBe('notification_definitions'); }); diff --git a/modules/_bundled/sirsoft-ecommerce/resources/layouts/admin/admin_ecommerce_settings.json b/modules/_bundled/sirsoft-ecommerce/resources/layouts/admin/admin_ecommerce_settings.json index 3c65cf33..cd42f434 100644 --- a/modules/_bundled/sirsoft-ecommerce/resources/layouts/admin/admin_ecommerce_settings.json +++ b/modules/_bundled/sirsoft-ecommerce/resources/layouts/admin/admin_ecommerce_settings.json @@ -56,6 +56,7 @@ "type": "api", "endpoint": "/api/modules/sirsoft-ecommerce/admin/settings/seo-cache-info", "method": "GET", + "if": "{{(query.tab || _global.activeEcommerceSettingsTab || 'basic_info') === 'seo'}}", "auto_fetch": true, "auth_required": true }, @@ -65,6 +66,7 @@ "type": "api", "endpoint": "/api/modules/sirsoft-ecommerce/admin/shipping-carriers", "method": "GET", + "if": "{{(query.tab || _global.activeEcommerceSettingsTab || 'basic_info') === 'shipping'}}", "auto_fetch": true, "auth_required": true, "errorHandling": { @@ -82,6 +84,7 @@ "type": "api", "endpoint": "/api/admin/notification-definitions?extension_type=module&extension_identifier=sirsoft-ecommerce", "method": "GET", + "if": "{{['notification_definitions', 'mileage'].includes(query.tab || _global.activeEcommerceSettingsTab || 'basic_info')}}", "auto_fetch": true, "auth_required": true, "params": { @@ -117,6 +120,7 @@ "type": "api", "endpoint": "/api/admin/notification-channels", "method": "GET", + "if": "{{(query.tab || _global.activeEcommerceSettingsTab || 'basic_info') === 'notification_definitions'}}", "auto_fetch": true, "auth_required": true, "fallback": { @@ -131,6 +135,7 @@ "type": "api", "endpoint": "/api/admin/identity/policies", "method": "GET", + "if": "{{(query.tab || _global.activeEcommerceSettingsTab || 'basic_info') === 'identity_policies'}}", "auto_fetch": true, "auth_required": true, "params": { @@ -155,6 +160,7 @@ "type": "api", "endpoint": "/api/identity/purposes", "method": "GET", + "if": "{{(query.tab || _global.activeEcommerceSettingsTab || 'basic_info') === 'identity_policies'}}", "auto_fetch": true, "auth_required": true, "fallback": { @@ -167,6 +173,7 @@ "type": "api", "endpoint": "/api/admin/identity/providers", "method": "GET", + "if": "{{(query.tab || _global.activeEcommerceSettingsTab || 'basic_info') === 'identity_policies'}}", "auto_fetch": true, "auth_required": true, "fallback": { diff --git a/modules/_bundled/sirsoft-ecommerce/vite.config.ts b/modules/_bundled/sirsoft-ecommerce/vite.config.ts index f7d2745c..8c0dddf6 100644 --- a/modules/_bundled/sirsoft-ecommerce/vite.config.ts +++ b/modules/_bundled/sirsoft-ecommerce/vite.config.ts @@ -20,7 +20,9 @@ export default defineConfig({ emptyOutDir: true, // 소스맵 생성 - sourcemap: true, + // 배포용 빌드(--production)는 G7_BUILD_SOURCEMAP=0 을 주입해 소스맵을 생성하지 않는다. + // 미설정(로컬 npm run build)이면 생성 — 개발 디버깅 경험을 유지한다. + sourcemap: !['0', 'false'].includes(process.env.G7_BUILD_SOURCEMAP ?? ''), rollupOptions: { output: { diff --git a/plugins/_bundled/sirsoft-ckeditor5/components.json b/plugins/_bundled/sirsoft-ckeditor5/components.json index 26bf84ac..f76b06f6 100644 --- a/plugins/_bundled/sirsoft-ckeditor5/components.json +++ b/plugins/_bundled/sirsoft-ckeditor5/components.json @@ -1,7 +1,7 @@ { "$schema": "https://json-schema.org/draft/2020-12/schema", "identifier": "sirsoft-ckeditor5", - "version": "1.0.0-beta.4", + "version": "1.0.0", "components": { "basic": [], "composite": [], diff --git a/plugins/_bundled/sirsoft-ckeditor5/dist/js/plugin.iife.js b/plugins/_bundled/sirsoft-ckeditor5/dist/js/plugin.iife.js index 704e3390..61d09b94 100644 --- a/plugins/_bundled/sirsoft-ckeditor5/dist/js/plugin.iife.js +++ b/plugins/_bundled/sirsoft-ckeditor5/dist/js/plugin.iife.js @@ -1,4 +1,4 @@ -var SirsoftCkeditor5=(function(G){"use strict";const E=new Map,Y="https://cdn.ckeditor.com/ckeditor5/43.3.1/ckeditor5.css",Z="https://cdn.ckeditor.com/ckeditor5/43.3.1/translations",N="ckeditor5-cdn-css",H="ckeditor5-dark-override",D="ckeditor5-tab-style",O={minimal:["bold","italic","underline","|","link","uploadImage","|","undo","redo"],standard:["heading","|","bold","italic","underline","strikethrough","|","alignment","|","link","blockQuote","|","bulletedList","numberedList","indent","outdent","|","insertTable","uploadImage","|","undo","redo"],full:["heading","|","fontSize","fontColor","fontBackgroundColor","|","bold","italic","underline","strikethrough","|","alignment","|","link","blockQuote","insertTable","uploadImage","mediaEmbed","horizontalLine","|","bulletedList","numberedList","indent","outdent","|","codeBlock","sourceEditing","|","undo","redo"]};function R(t){if(t==="en")return Promise.resolve();const o=`ckeditor5-translations-${t}`;return document.getElementById(o)?Promise.resolve():new Promise(e=>{const r=document.createElement("script");r.id=o,r.src=`${Z}/${t}.umd.js`,r.onload=()=>e(),r.onerror=()=>e(),document.head.appendChild(r)})}function K(){if(document.getElementById(N))return;const t=document.createElement("link");t.id=N,t.rel="stylesheet",t.href=Y,document.head.appendChild(t)}function tt(){if(document.getElementById(D))return;const t=document.createElement("style");t.id=D,t.textContent=` +var SirsoftCkeditor5=function(G){"use strict";const E=new Map,Y="https://cdn.ckeditor.com/ckeditor5/43.3.1/ckeditor5.css",Z="https://cdn.ckeditor.com/ckeditor5/43.3.1/translations",N="ckeditor5-cdn-css",H="ckeditor5-dark-override",D="ckeditor5-tab-style",O={minimal:["bold","italic","underline","|","link","uploadImage","|","undo","redo"],standard:["heading","|","bold","italic","underline","strikethrough","|","alignment","|","link","blockQuote","|","bulletedList","numberedList","indent","outdent","|","insertTable","uploadImage","|","undo","redo"],full:["heading","|","fontSize","fontColor","fontBackgroundColor","|","bold","italic","underline","strikethrough","|","alignment","|","link","blockQuote","insertTable","uploadImage","mediaEmbed","horizontalLine","|","bulletedList","numberedList","indent","outdent","|","codeBlock","sourceEditing","|","undo","redo"]};function R(t){if(t==="en")return Promise.resolve();const o=`ckeditor5-translations-${t}`;return document.getElementById(o)?Promise.resolve():new Promise(e=>{const r=document.createElement("script");r.id=o,r.src=`${Z}/${t}.umd.js`,r.onload=()=>e(),r.onerror=()=>e(),document.head.appendChild(r)})}function K(){if(document.getElementById(N))return;const t=document.createElement("link");t.id=N,t.rel="stylesheet",t.href=Y,document.head.appendChild(t)}function tt(){if(document.getElementById(D))return;const t=document.createElement("style");t.id=D,t.textContent=` /* 다국어 탭 컨테이너 */ .ckeditor5-locale-tabs { display: flex; @@ -369,5 +369,4 @@ var SirsoftCkeditor5=(function(G){"use strict";const E=new Map,Y="https://cdn.ck .ck-content ul ul ul { list-style-type: square; padding-left: 2em; } .ck-content ol ol { list-style-type: lower-alpha; padding-left: 2em; } .ck-content ol ol ol { list-style-type: lower-roman; padding-left: 2em; } - `,document.head.appendChild(t)}}const v={initEditor:ut,destroyEditor:ft,injectContentCss:mt},z=window.G7Core?.createLogger?.(`Plugin:${m}`)??{log:(...t)=>console.log(`[Plugin:${m}]`,...t),warn:(...t)=>console.warn(`[Plugin:${m}]`,...t),error:(...t)=>console.error(`[Plugin:${m}]`,...t)};function Q(t=!1){const o=window.G7Core?.getActionDispatcher?.();if(o)Object.entries(v).forEach(([e,r])=>{const c=`${m}.${e}`;o.registerHandler(c,r,{category:"plugin",source:m})}),z.log(`${Object.keys(v).length} handler(s) registered:`,Object.keys(v).map(e=>`${m}.${e}`));else if(t){let e=0;const r=50,c=()=>{const l=window.G7Core?.getActionDispatcher?.();l?(Object.entries(v).forEach(([i,s])=>{const b=`${m}.${i}`;l.registerHandler(b,s,{category:"plugin",source:m})}),z.log(`${Object.keys(v).length} handler(s) registered:`,Object.keys(v).map(i=>`${m}.${i}`))):(e++,e<=r?(z.warn(`ActionDispatcher not found, retrying... (${e}/${r})`),setTimeout(c,100)):z.error("Failed to register handlers: ActionDispatcher not available after maximum retries"))};c()}else z.warn("ActionDispatcher not found, handlers not registered")}function j(){if(document.readyState==="loading")document.addEventListener("DOMContentLoaded",()=>Q(!0));else{const t=!!window.G7Core?.getActionDispatcher?.();Q(!t)}}return j(),typeof window<"u"&&(window.__SirsoftCkeditor5={identifier:m,handlers:Object.keys(v),initPlugin:j}),G.initPlugin=j,Object.defineProperty(G,Symbol.toStringTag,{value:"Module"}),G})({}); -//# sourceMappingURL=plugin.iife.js.map + `,document.head.appendChild(t)}}const v={initEditor:ut,destroyEditor:ft,injectContentCss:mt},z=window.G7Core?.createLogger?.(`Plugin:${m}`)??{log:(...t)=>console.log(`[Plugin:${m}]`,...t),warn:(...t)=>console.warn(`[Plugin:${m}]`,...t),error:(...t)=>console.error(`[Plugin:${m}]`,...t)};function Q(t=!1){const o=window.G7Core?.getActionDispatcher?.();if(o)Object.entries(v).forEach(([e,r])=>{const c=`${m}.${e}`;o.registerHandler(c,r,{category:"plugin",source:m})}),z.log(`${Object.keys(v).length} handler(s) registered:`,Object.keys(v).map(e=>`${m}.${e}`));else if(t){let e=0;const r=50,c=()=>{const l=window.G7Core?.getActionDispatcher?.();l?(Object.entries(v).forEach(([i,s])=>{const b=`${m}.${i}`;l.registerHandler(b,s,{category:"plugin",source:m})}),z.log(`${Object.keys(v).length} handler(s) registered:`,Object.keys(v).map(i=>`${m}.${i}`))):(e++,e<=r?(z.warn(`ActionDispatcher not found, retrying... (${e}/${r})`),setTimeout(c,100)):z.error("Failed to register handlers: ActionDispatcher not available after maximum retries"))};c()}else z.warn("ActionDispatcher not found, handlers not registered")}function j(){if(document.readyState==="loading")document.addEventListener("DOMContentLoaded",()=>Q(!0));else{const t=!!window.G7Core?.getActionDispatcher?.();Q(!t)}}return j(),typeof window<"u"&&(window.__SirsoftCkeditor5={identifier:m,handlers:Object.keys(v),initPlugin:j}),G.initPlugin=j,Object.defineProperty(G,Symbol.toStringTag,{value:"Module"}),G}({}); diff --git a/plugins/_bundled/sirsoft-ckeditor5/dist/js/plugin.iife.js.map b/plugins/_bundled/sirsoft-ckeditor5/dist/js/plugin.iife.js.map deleted file mode 100644 index 45942f66..00000000 --- a/plugins/_bundled/sirsoft-ckeditor5/dist/js/plugin.iife.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"plugin.iife.js","sources":["../../resources/js/handlers/editorInstances.ts","../../resources/js/handlers/initEditor.ts","../../resources/js/handlers/destroyEditor.ts","../../resources/js/index.ts"],"sourcesContent":["/**\n * CKEditor5 인스턴스 공유 저장소\n *\n * containerId → locale별 에디터 인스턴스 맵을 관리합니다.\n * initEditor / destroyEditor 핸들러가 공유합니다.\n */\n\n// containerId → Map\nexport const editorInstances = new Map>();\n\n// setData() 중 change:data → syncToForm 재진입 방지 플래그\nlet _syncSuppressed = false;\nexport function isSyncSuppressed(): boolean {\n return _syncSuppressed;\n}\nexport function setSyncSuppressed(value: boolean): void {\n _syncSuppressed = value;\n}\n","/**\n * CKEditor5 초기화 핸들러\n *\n * 컴포넌트 마운트 시 CKEditor5 인스턴스를 생성합니다.\n * 다국어(multilingual) 모드: locale별 탭 + 인스턴스 생성\n * 단일 모드: 단일 인스턴스 생성\n */\n\nimport { editorInstances, isSyncSuppressed } from './editorInstances';\n\n/** CKEditor5 CDN CSS URL */\nconst CKEDITOR5_CSS_URL = 'https://cdn.ckeditor.com/ckeditor5/43.3.1/ckeditor5.css';\n/** CKEditor5 CDN 번역 파일 base URL */\nconst CKEDITOR5_TRANSLATIONS_BASE = 'https://cdn.ckeditor.com/ckeditor5/43.3.1/translations';\n/** CKEditor5 CSS 로드 상태 추적용 엘리먼트 ID */\nconst CKEDITOR5_CSS_ID = 'ckeditor5-cdn-css';\n/** CKEditor5 다크 모드 CSS 엘리먼트 ID */\nconst CKEDITOR5_DARK_CSS_ID = 'ckeditor5-dark-override';\n/** CKEditor5 다국어 탭 기본 CSS 엘리먼트 ID */\nconst CKEDITOR5_TAB_CSS_ID = 'ckeditor5-tab-style';\n\n/** CKEditor 전역 객체 타입 (CDN UMD 빌드 — window.CKEDITOR) */\ninterface CKEditorGlobal {\n ClassicEditor: any;\n Essentials: any;\n Bold: any;\n Italic: any;\n Underline: any;\n Strikethrough: any;\n Link: any;\n Paragraph: any;\n Heading: any;\n BlockQuote: any;\n List: any;\n Alignment: any;\n Table: any;\n TableToolbar: any;\n TableProperties: any;\n TableCellProperties: any;\n Image: any;\n ImageUpload: any;\n ImageResize: any;\n ImageStyle: any;\n ImageCaption: any;\n ImageToolbar: any;\n SimpleUploadAdapter: any;\n MediaEmbed: any;\n HorizontalLine: any;\n Indent: any;\n IndentBlock: any;\n CodeBlock: any;\n SourceEditing: any;\n FontSize: any;\n FontColor: any;\n FontBackgroundColor: any;\n PasteFromOffice: any;\n GeneralHtmlSupport: any;\n}\n\n/** initEditor 파라미터 타입 */\ninterface InitEditorParams {\n name?: string;\n content?: string;\n multilingual?: boolean | string;\n placeholder?: string;\n readOnly?: boolean | string;\n imageUpload?: boolean | string;\n uploadPermission?: string;\n height?: number | string;\n toolbar?: string;\n}\n\n/** 툴바 프리셋 */\nconst TOOLBAR_PRESETS: Record = {\n minimal: [\n 'bold', 'italic', 'underline', '|',\n 'link', 'uploadImage', '|',\n 'undo', 'redo',\n ],\n standard: [\n 'heading', '|',\n 'bold', 'italic', 'underline', 'strikethrough', '|',\n 'alignment', '|',\n 'link', 'blockQuote', '|',\n 'bulletedList', 'numberedList', 'indent', 'outdent', '|',\n 'insertTable', 'uploadImage', '|',\n 'undo', 'redo',\n ],\n full: [\n 'heading', '|',\n 'fontSize', 'fontColor', 'fontBackgroundColor', '|',\n 'bold', 'italic', 'underline', 'strikethrough', '|',\n 'alignment', '|',\n 'link', 'blockQuote', 'insertTable', 'uploadImage', 'mediaEmbed', 'horizontalLine', '|',\n 'bulletedList', 'numberedList', 'indent', 'outdent', '|',\n 'codeBlock', 'sourceEditing', '|',\n 'undo', 'redo',\n ],\n};\n\n/**\n * CKEditor5 번역 스크립트를 동적으로 로드합니다.\n * 영어(en)는 기본 내장이므로 로드하지 않습니다.\n */\nfunction loadCkeditorTranslations(locale: string): Promise {\n if (locale === 'en') return Promise.resolve();\n const id = `ckeditor5-translations-${locale}`;\n if (document.getElementById(id)) return Promise.resolve();\n return new Promise((resolve) => {\n const script = document.createElement('script');\n script.id = id;\n script.src = `${CKEDITOR5_TRANSLATIONS_BASE}/${locale}.umd.js`;\n script.onload = () => resolve();\n script.onerror = () => resolve(); // 번역 실패해도 에디터는 영어로 동작\n document.head.appendChild(script);\n });\n}\n\n/**\n * CKEditor5 CSS를 동적으로 로드합니다.\n * 이미 로드된 경우 중복 삽입하지 않습니다.\n */\nfunction loadCkeditorCss(): void {\n if (document.getElementById(CKEDITOR5_CSS_ID)) {\n return;\n }\n const link = document.createElement('link');\n link.id = CKEDITOR5_CSS_ID;\n link.rel = 'stylesheet';\n link.href = CKEDITOR5_CSS_URL;\n document.head.appendChild(link);\n}\n\n/**\n * 다국어 탭 버튼 기본 스타일(라이트/다크 공통 베이스)을 주입합니다.\n * 이미 주입된 경우 중복 삽입하지 않습니다.\n */\nfunction loadTabCss(): void {\n if (document.getElementById(CKEDITOR5_TAB_CSS_ID)) {\n return;\n }\n const style = document.createElement('style');\n style.id = CKEDITOR5_TAB_CSS_ID;\n style.textContent = `\n /* 다국어 탭 컨테이너 */\n .ckeditor5-locale-tabs {\n display: flex;\n gap: 8px;\n margin-bottom: 12px;\n align-items: center;\n }\n /* 다국어 탭 버튼 - HtmlEditor 언어탭 스타일 */\n .ckeditor5-locale-tabs button {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n padding: 6px 12px;\n border: none;\n border-radius: 9999px;\n cursor: pointer;\n font-size: 0.75rem;\n font-weight: 600;\n line-height: 1;\n transition: background 0.15s, color 0.15s, transform 0.15s, box-shadow 0.15s;\n }\n /* 비활성 - 기본언어 (blue) */\n .ckeditor5-locale-tabs button.is-default {\n background: #eff6ff;\n color: #2563eb;\n }\n .ckeditor5-locale-tabs button.is-default:hover {\n background: #dbeafe;\n }\n /* 비활성 - 비기본언어 (gray) */\n .ckeditor5-locale-tabs button:not(.is-default) {\n background: #f3f4f6;\n color: #4b5563;\n }\n .ckeditor5-locale-tabs button:not(.is-default):hover {\n background: #e5e7eb;\n }\n /* 활성 - 기본언어 */\n .ckeditor5-locale-tabs button.is-active.is-default {\n background: #3b82f6;\n color: #ffffff;\n box-shadow: 0 2px 6px rgba(0,0,0,0.15);\n transform: scale(1.05);\n }\n /* 활성 - 비기본언어 */\n .ckeditor5-locale-tabs button.is-active:not(.is-default) {\n background: #6b7280;\n color: #ffffff;\n box-shadow: 0 2px 6px rgba(0,0,0,0.15);\n transform: scale(1.05);\n }\n .ckeditor5-locale-tabs button .ck5-lang-icon {\n font-size: 0.75rem;\n line-height: 1;\n }\n .ckeditor5-locale-tabs button .ck5-required {\n color: #ef4444;\n }\n .ckeditor5-locale-tabs button.is-active .ck5-required {\n color: #fca5a5;\n }\n .ckeditor5-locale-tabs button .ck5-check {\n color: #22c55e;\n }\n /* 다크 모드 - 비활성 기본언어 */\n html.dark .ckeditor5-locale-tabs button.is-default {\n background: rgba(59, 130, 246, 0.15);\n color: #93c5fd;\n }\n html.dark .ckeditor5-locale-tabs button.is-default:hover {\n background: rgba(59, 130, 246, 0.25);\n }\n /* 다크 모드 - 비활성 비기본언어 */\n html.dark .ckeditor5-locale-tabs button:not(.is-default) {\n background: #374151;\n color: #d1d5db;\n }\n html.dark .ckeditor5-locale-tabs button:not(.is-default):hover {\n background: #4b5563;\n }\n /* 다크 모드 - 활성 기본언어 */\n html.dark .ckeditor5-locale-tabs button.is-active.is-default {\n background: #2563eb;\n color: #ffffff;\n }\n /* 다크 모드 - 활성 비기본언어 */\n html.dark .ckeditor5-locale-tabs button.is-active:not(.is-default) {\n background: #4b5563;\n color: #ffffff;\n }\n html.dark .ckeditor5-locale-tabs button .ck5-check {\n color: #4ade80;\n }\n /* 표 너비 인라인 스타일 무력화 - 컨테이너에 맞게 100% */\n .ck-content figure.table { width: 100%; }\n .ck-content figure.table table { width: 100%; }\n /* 표 tr 배경색 초기화 */\n .ck-content table tr { background-color: unset; }\n /* 표 col 절대 px 너비 무력화 - 컨테이너에 맞게 비율로 */\n .ck-content table col { width: auto !important; }\n /* 표 정렬 보정: CKEditor5 기본 CSS의 margin-left:auto를 float으로 덮어씀 */\n .ck-content figure.table[style*=\"float:left\"],\n .ck-content figure.table[style*=\"float: left\"] {\n margin-left: 0 !important;\n margin-right: 1em !important;\n }\n .ck-content figure.table[style*=\"float:right\"],\n .ck-content figure.table[style*=\"float: right\"] {\n margin-left: 1em !important;\n margin-right: 0 !important;\n }\n /* 이미지 캡션 - 라이트 모드 */\n .ck-content .image > figcaption {\n background-color: #f3f4f6;\n color: #374151;\n font-size: 0.875em;\n padding: 4px 8px;\n text-align: center;\n }\n /* 에디터 편집 영역 - 목록 마커 복원 (Tailwind preflight reset 대응) */\n .ck.ck-editor__editable ul,\n .ck-content ul {\n list-style-type: disc;\n padding-left: 2em;\n }\n .ck.ck-editor__editable ol,\n .ck-content ol {\n list-style-type: decimal;\n padding-left: 2em;\n }\n .ck.ck-editor__editable ul ul,\n .ck-content ul ul {\n list-style-type: circle;\n padding-left: 2em;\n }\n .ck.ck-editor__editable ul ul ul,\n .ck-content ul ul ul {\n list-style-type: square;\n padding-left: 2em;\n }\n .ck.ck-editor__editable ol ol,\n .ck-content ol ol {\n list-style-type: lower-alpha;\n padding-left: 2em;\n }\n .ck.ck-editor__editable ol ol ol,\n .ck-content ol ol ol {\n list-style-type: lower-roman;\n padding-left: 2em;\n }\n `;\n document.head.appendChild(style);\n}\n\n/**\n * 현재 다크 모드 여부를 반환합니다.\n */\nfunction isDarkMode(): boolean {\n return document.documentElement.classList.contains('dark');\n}\n\n/**\n * CKEditor5 다크 모드 CSS 변수를 동적으로 주입하거나 제거합니다.\n * document.documentElement의 'dark' 클래스를 기준으로 판단합니다.\n */\nfunction applyDarkModeOverride(): void {\n const existing = document.getElementById(CKEDITOR5_DARK_CSS_ID);\n\n if (!isDarkMode()) {\n // 라이트 모드: 다크 오버라이드 제거\n if (existing) {\n existing.remove();\n }\n return;\n }\n\n if (existing) {\n return;\n }\n\n const style = document.createElement('style');\n style.id = CKEDITOR5_DARK_CSS_ID;\n style.textContent = `\n /* CKEditor5 다크 모드 오버라이드 - CSS 변수 전역 적용 */\n :root {\n --ck-color-base-foreground: #1f2937;\n --ck-color-base-background: #1f2937;\n --ck-color-base-border: #374151;\n --ck-color-text: #f3f4f6;\n --ck-color-toolbar-background: #111827;\n --ck-color-toolbar-border: #374151;\n --ck-color-button-default-background: transparent;\n --ck-color-button-default-hover-background: #374151;\n --ck-color-button-default-active-background: #4b5563;\n --ck-color-button-on-background: #374151;\n --ck-color-button-on-hover-background: #4b5563;\n --ck-color-focus-border: #3b82f6;\n --ck-color-input-background: #1f2937;\n --ck-color-input-text: #f3f4f6;\n --ck-color-input-border: #374151;\n --ck-color-panel-background: #1f2937;\n --ck-color-panel-border: #374151;\n --ck-color-list-button-hover-background: #374151;\n --ck-color-list-button-on-background: #3b82f6;\n --ck-color-list-button-on-color: #f3f4f6;\n --ck-color-list-button-on-hover-background: #4b5563;\n --ck-color-labeled-field-label-background: #1f2937;\n --ck-color-link-default: #60a5fa;\n --ck-color-link-selected-background: rgba(96, 165, 250, 0.1);\n --ck-color-table-focused-cell-background: rgba(59, 130, 246, 0.1);\n }\n /* 에디터 본문 */\n .ck.ck-editor__editable_inline {\n background-color: #1f2937 !important;\n color: #f3f4f6 !important;\n border-color: #374151 !important;\n }\n /* 메인 툴바 */\n .ck.ck-toolbar {\n background-color: #111827 !important;\n border-color: #374151 !important;\n }\n /* 모든 버튼 텍스트 */\n .ck.ck-button,\n .ck.ck-button .ck-button__label {\n color: #f3f4f6 !important;\n }\n /* 모든 SVG 아이콘 (툴바, 드롭다운 화살표 포함) */\n .ck.ck-icon,\n .ck .ck-icon,\n .ck.ck-button .ck-icon,\n .ck.ck-dropdown__arrow {\n color: #f3f4f6 !important;\n }\n .ck.ck-button:hover,\n .ck.ck-button.ck-on {\n background-color: #374151 !important;\n }\n /* 툴팁 (호버 시 버튼 설명) */\n .ck.ck-tooltip .ck-tooltip__text {\n background-color: #1f2937 !important;\n color: #f3f4f6 !important;\n }\n /* 드롭다운 패널 / 리스트 / balloon 팝업 공통 */\n .ck.ck-dropdown__panel,\n .ck.ck-list,\n .ck.ck-balloon-panel {\n background-color: #1f2937 !important;\n border-color: #374151 !important;\n }\n /* 리스트 아이템 */\n .ck.ck-list__item .ck-button,\n .ck.ck-list__item .ck-button .ck-button__label {\n color: #f3f4f6 !important;\n }\n .ck.ck-list__item .ck-button:hover {\n background-color: #374151 !important;\n }\n .ck.ck-list__item .ck-button.ck-disabled .ck-button__label {\n color: #6b7280 !important;\n }\n /* 링크 팝업 / form */\n .ck.ck-link-form,\n .ck.ck-link-actions {\n background-color: #1f2937 !important;\n border-color: #374151 !important;\n }\n .ck.ck-input-text {\n background-color: #111827 !important;\n color: #f3f4f6 !important;\n border-color: #374151 !important;\n }\n .ck.ck-labeled-field-view__label {\n background-color: #1f2937 !important;\n color: #9ca3af !important;\n }\n /* 테이블 속성/셀 속성 폼 */\n .ck.ck-table-form,\n .ck.ck-table-cell-properties-form,\n .ck.ck-table-properties-form {\n background-color: #1f2937 !important;\n border-color: #374151 !important;\n }\n .ck.ck-table-form .ck-label,\n .ck.ck-table-cell-properties-form .ck-label,\n .ck.ck-table-properties-form .ck-label {\n color: #d1d5db !important;\n }\n /* 색상 선택 패널 */\n .ck.ck-color-table,\n .ck.ck-color-grid {\n background-color: #1f2937 !important;\n }\n .ck.ck-color-table .ck-color-table__remove-color,\n .ck.ck-color-table .ck-color-table__remove-color .ck-button__label {\n color: #f3f4f6 !important;\n }\n /* 헤딩/폰트 드롭다운 라벨 */\n .ck.ck-heading_paragraph,\n .ck.ck-heading_heading1,\n .ck.ck-heading_heading2,\n .ck.ck-heading_heading3 {\n color: #f3f4f6 !important;\n }\n /* 이미지 캡션 */\n .ck-content .image > figcaption {\n background-color: #374151 !important;\n color: #d1d5db !important;\n }\n /* 이미지 리사이즈 핸들 */\n .ck-content .image.ck-widget_selected .ck-widget__resizer__handle {\n background-color: #3b82f6 !important;\n border-color: #1d4ed8 !important;\n }\n /* 이미지 정렬 툴바 아이콘 */\n .ck.ck-toolbar .ck-button .ck-icon {\n color: #f3f4f6 !important;\n }\n /* 이미지 선택 시 balloon toolbar (floating) */\n .ck.ck-toolbar_floating {\n background-color: #111827 !important;\n border-color: #374151 !important;\n }\n /* 이미지 리사이즈 드롭다운 버튼 라벨 (25%, 50% 등) */\n .ck.ck-dropdown .ck-button__label {\n color: #f3f4f6 !important;\n }\n /* 이미지 리사이즈 드롭다운 내 선택 리스트 */\n .ck.ck-resize-image-form,\n .ck.ck-image-resize-form {\n background-color: #1f2937 !important;\n border-color: #374151 !important;\n color: #f3f4f6 !important;\n }\n .ck.ck-resize-image-form .ck-label,\n .ck.ck-image-resize-form .ck-label {\n color: #d1d5db !important;\n }\n .ck.ck-resize-image-form .ck-input-text,\n .ck.ck-image-resize-form .ck-input-text {\n background-color: #111827 !important;\n color: #f3f4f6 !important;\n border-color: #374151 !important;\n }\n /* 표 안 에디터 내용 (다크 배경) */\n .ck-content table tr {\n background-color: unset;\n }\n .ck-content table td,\n .ck-content table th {\n border-color: #4b5563;\n color: #f3f4f6 !important;\n }\n /* 표 col 절대 px 너비 무력화 */\n .ck-content table col {\n width: auto !important;\n }\n /* 목록 마커 색상 - 다크모드 */\n .ck.ck-editor__editable ul,\n .ck.ck-editor__editable ol {\n color: #f3f4f6;\n }\n .ck.ck-editor__editable li::marker {\n color: #d1d5db;\n }\n `;\n document.head.appendChild(style);\n}\n\n/** 다크 모드 MutationObserver 인스턴스 (cleanup용) */\nlet darkModeObserver: MutationObserver | null = null;\n\n/**\n * 클래스 변경을 감지하여 다크 모드 전환 시 즉시 반영합니다.\n * 최초 1회만 등록합니다.\n */\nfunction watchDarkMode(): void {\n if (darkModeObserver) {\n return;\n }\n\n darkModeObserver = new MutationObserver(() => {\n applyDarkModeOverride();\n });\n\n darkModeObserver.observe(document.documentElement, {\n attributes: true,\n attributeFilter: ['class'],\n });\n}\n\n/**\n * 다크 모드 MutationObserver를 해제합니다.\n */\nexport function disconnectDarkModeObserver(): void {\n if (darkModeObserver) {\n darkModeObserver.disconnect();\n darkModeObserver = null;\n }\n}\n\n/** locale 코드 → 표시 이름 매핑 */\nconst LOCALE_LABELS: Record = {\n ko: '한국어',\n en: 'English',\n ja: '日本語',\n zh: '中文',\n fr: 'Français',\n de: 'Deutsch',\n es: 'Español',\n};\n\n/**\n * locale 코드를 표시 이름으로 변환합니다.\n * 매핑에 없는 경우 대문자 코드를 반환합니다.\n */\nfunction getLocaleLabel(locale: string): string {\n return LOCALE_LABELS[locale] ?? locale.toUpperCase();\n}\n\n/**\n * 지원 locale 목록을 반환합니다.\n * localStorage의 g7_locale 값과 서버 설정을 활용합니다.\n */\nfunction getSupportedLocales(): string[] {\n const G7Core = (window as any).G7Core;\n if (G7Core?.locale?.supported) {\n return G7Core.locale.supported();\n }\n // 폴백: 현재 locale만 사용\n const currentLocale = localStorage.getItem('g7_locale') || 'ko';\n return [currentLocale];\n}\n\n/**\n * 현재 locale을 반환합니다.\n * G7Core.locale.current() 우선, 폴백으로 localStorage 사용.\n */\nfunction getCurrentLocale(): string {\n const G7Core = (window as any).G7Core;\n return G7Core?.locale?.current?.() ?? localStorage.getItem('g7_locale') ?? 'ko';\n}\n\n/**\n * CKEditor 플러그인 목록을 toolbar 타입에 따라 반환합니다.\n */\nfunction getPlugins(CKEDITOR: CKEditorGlobal, toolbarType: string, withImageUpload: boolean): any[] {\n const base = [\n CKEDITOR.Essentials,\n CKEDITOR.Bold,\n CKEDITOR.Italic,\n CKEDITOR.Underline,\n CKEDITOR.Strikethrough,\n CKEDITOR.Link,\n CKEDITOR.Paragraph,\n CKEDITOR.Heading,\n CKEDITOR.BlockQuote,\n CKEDITOR.List,\n CKEDITOR.Alignment,\n CKEDITOR.Table,\n CKEDITOR.TableToolbar,\n CKEDITOR.TableProperties,\n CKEDITOR.TableCellProperties,\n CKEDITOR.Indent,\n CKEDITOR.IndentBlock,\n CKEDITOR.PasteFromOffice,\n CKEDITOR.GeneralHtmlSupport,\n // 이미지 플러그인은 업로드 여부와 무관하게 항상 포함 (기존 이미지 크기 조정/정렬 지원)\n CKEDITOR.Image,\n CKEDITOR.ImageResize,\n CKEDITOR.ImageStyle,\n CKEDITOR.ImageCaption,\n CKEDITOR.ImageToolbar,\n ];\n\n if (toolbarType === 'full') {\n base.push(\n CKEDITOR.MediaEmbed,\n CKEDITOR.HorizontalLine,\n CKEDITOR.CodeBlock,\n CKEDITOR.SourceEditing,\n CKEDITOR.FontSize,\n CKEDITOR.FontColor,\n CKEDITOR.FontBackgroundColor,\n );\n }\n\n if (withImageUpload) {\n base.push(\n CKEDITOR.ImageUpload,\n CKEDITOR.SimpleUploadAdapter,\n );\n }\n\n return base;\n}\n\n/**\n * 이미지 업로드 URL을 생성합니다.\n * uploadPermission이 있으면 쿼리스트링으로 추가합니다.\n */\nfunction buildUploadUrl(uploadPermission: string): string {\n const base = '/api/plugins/sirsoft-ckeditor5/upload';\n if (uploadPermission) {\n return `${base}?permission=${encodeURIComponent(uploadPermission)}`;\n }\n return base;\n}\n\n/**\n * 이미지 업로드 헤더를 반환합니다.\n * Bearer 토큰을 Authorization 헤더에 포함합니다.\n */\nfunction getUploadHeaders(): Record {\n const token = localStorage.getItem('auth_token');\n if (token) {\n return { Authorization: `Bearer ${token}` };\n }\n return {};\n}\n\n/**\n * 단일 CKEditor 인스턴스를 생성합니다.\n */\nasync function createEditorInstance(\n element: HTMLElement,\n options: {\n initialContent: string;\n placeholder: string;\n readOnly: boolean;\n imageUpload: boolean;\n uploadPermission: string;\n height: number;\n toolbar: string;\n containerId: string;\n }\n): Promise {\n const CKEDITOR = (window as any).CKEDITOR as CKEditorGlobal;\n if (!CKEDITOR) {\n throw new Error('[sirsoft-ckeditor5] CKEDITOR 전역 객체를 찾을 수 없습니다. CDN 스크립트가 로드되었는지 확인하세요.');\n }\n\n const toolbarItems = TOOLBAR_PRESETS[options.toolbar] || TOOLBAR_PRESETS.standard;\n\n // 이미지 업로드 비활성화 시 uploadImage 버튼 제거\n const finalToolbar = options.imageUpload\n ? toolbarItems\n : toolbarItems.filter(item => item !== 'uploadImage');\n\n const editorConfig: Record = {\n initialData: options.initialContent,\n plugins: getPlugins(CKEDITOR, options.toolbar, options.imageUpload),\n toolbar: { items: finalToolbar },\n table: {\n contentToolbar: [\n 'tableColumn', 'tableRow', 'mergeTableCells',\n '|', 'tableProperties', 'tableCellProperties',\n ],\n tableProperties: {\n defaultProperties: {\n borderStyle: 'solid',\n borderColor: '#d1d5db',\n borderWidth: '1px',\n alignment: 'left',\n },\n },\n tableCellProperties: {\n defaultProperties: {\n borderStyle: 'solid',\n borderColor: '#d1d5db',\n borderWidth: '1px',\n padding: '8px',\n },\n },\n },\n image: {\n toolbar: [\n 'imageStyle:inline',\n 'imageStyle:alignLeft',\n 'imageStyle:alignCenter',\n 'imageStyle:alignRight',\n '|',\n 'toggleImageCaption',\n '|',\n 'resizeImage',\n ],\n resizeOptions: [\n { name: 'resizeImage:original', label: 'Original', value: null },\n { name: 'resizeImage:25', label: '25%', value: '25' },\n { name: 'resizeImage:50', label: '50%', value: '50' },\n { name: 'resizeImage:75', label: '75%', value: '75' },\n ],\n resizeUnit: '%' as const,\n },\n indentBlock: {\n offset: 40,\n unit: 'px',\n },\n link: {\n defaultProtocol: 'https://',\n decorators: {\n openInNewTab: {\n mode: 'automatic' as const,\n callback: (url: string) => url.startsWith('http://') || url.startsWith('https://'),\n attributes: {\n target: '_blank',\n rel: 'noopener noreferrer',\n },\n },\n },\n },\n htmlSupport: {\n allow: [\n { name: /.*/, attributes: true, classes: true, styles: true },\n ],\n },\n placeholder: options.placeholder,\n language: getCurrentLocale(),\n };\n\n if (options.imageUpload) {\n editorConfig.simpleUpload = {\n uploadUrl: buildUploadUrl(options.uploadPermission),\n headers: getUploadHeaders(),\n };\n }\n\n const editor = await CKEDITOR.ClassicEditor.create(element, editorConfig);\n\n // 높이 설정: 인라인 스타일은 포커스 시 CKEditor 내부에서 초기화될 수 있으므로\n // containerId 기반 CSS 규칙으로 강제 적용 (remount 시 동일 ID로 덮어씀)\n const heightStyleId = `ckeditor5-height-style-${options.containerId}`;\n const existingHeightStyle = document.getElementById(heightStyleId);\n if (existingHeightStyle) {\n existingHeightStyle.textContent = `#${options.containerId} .ck-editor__editable { min-height: ${options.height}px !important; }`;\n } else {\n const heightStyle = document.createElement('style');\n heightStyle.id = heightStyleId;\n heightStyle.textContent = `#${options.containerId} .ck-editor__editable { min-height: ${options.height}px !important; }`;\n document.head.appendChild(heightStyle);\n }\n\n if (options.readOnly) {\n editor.enableReadOnlyMode('read-only');\n }\n\n return editor;\n}\n\n/**\n * 다국어 탭 UI를 생성합니다.\n */\nfunction createMultilingualTabs(\n container: HTMLElement,\n locales: string[],\n activeLocale: string,\n onTabActivate?: (locale: string) => Promise,\n getEditorData?: (locale: string) => string | null\n): { tabsEl: HTMLElement; contentEls: Map; updateCheckIcons: () => void } {\n const tabsEl = document.createElement('div');\n tabsEl.className = 'ckeditor5-locale-tabs';\n\n const contentEls = new Map();\n\n // 에디터 컨텐츠 영역 먼저 생성\n // CKEditor ClassicEditor.create(element)는 element를 숨기고 nextSibling 위치에\n // .ck-editor를 삽입하므로, wrapper div로 감싸서 display:none을 wrapper에 적용해야\n // 비활성 locale의 에디터가 보이지 않음\n locales.forEach(locale => {\n const wrapperEl = document.createElement('div');\n wrapperEl.dataset.locale = locale;\n if (locale !== activeLocale) {\n wrapperEl.style.cssText = 'display:none;';\n }\n const contentEl = document.createElement('div');\n wrapperEl.appendChild(contentEl);\n contentEls.set(locale, contentEl);\n });\n\n /**\n * 비활성 탭의 체크 아이콘을 갱신합니다.\n * 에디터에 값이 있으면 체크 아이콘을 표시하고, 없으면 제거합니다.\n */\n const updateCheckIcons = () => {\n if (!getEditorData) return;\n tabsEl.querySelectorAll('button').forEach(btn => {\n const btnLocale = (btn as HTMLButtonElement).dataset.locale;\n if (!btnLocale || btn.classList.contains('is-active')) {\n // 활성 탭은 체크 아이콘 제거\n const existing = btn.querySelector('.ck5-check');\n if (existing) existing.remove();\n return;\n }\n const data = getEditorData(btnLocale);\n const hasValue = Boolean(data?.trim());\n const existing = btn.querySelector('.ck5-check');\n if (hasValue && !existing) {\n const icon = document.createElement('i');\n icon.className = 'fas fa-check ck5-check';\n btn.appendChild(icon);\n } else if (!hasValue && existing) {\n existing.remove();\n }\n });\n };\n\n // 탭 버튼 생성\n const defaultLocale = locales[0];\n locales.forEach(locale => {\n const isDefault = locale === defaultLocale;\n const isActive = locale === activeLocale;\n const tab = document.createElement('button');\n tab.type = 'button';\n tab.dataset.locale = locale;\n tab.className = [isActive ? 'is-active' : '', isDefault ? 'is-default' : ''].filter(Boolean).join(' ');\n tab.innerHTML = `${getLocaleLabel(locale)}${isDefault ? '*' : ''}`;\n tab.addEventListener('click', async () => {\n // 탭 활성화 전환 (에디터 생성 전에 먼저 실행)\n tabsEl.querySelectorAll('button').forEach(btn => {\n const btnLocale = (btn as HTMLButtonElement).dataset.locale;\n const btnIsActive = btnLocale === locale;\n btn.classList.toggle('is-active', btnIsActive);\n });\n // 에디터 컨텐츠 영역 전환 (새 locale만 표시, 나머지 숨김)\n // wrapperEl(contentEl.parentElement)에 display를 적용해야\n // CKEditor가 삽입한 .ck-editor sibling도 함께 숨겨짐\n contentEls.forEach((el, elLocale) => {\n const wrapperEl = el.parentElement;\n if (wrapperEl) {\n wrapperEl.style.display = elLocale === locale ? 'block' : 'none';\n }\n });\n // lazy 에디터 생성 (아직 없는 경우, display 전환 후 실행)\n if (onTabActivate) {\n await onTabActivate(locale);\n }\n // 탭 전환 완료 후 체크 아이콘 갱신\n updateCheckIcons();\n });\n tabsEl.appendChild(tab);\n });\n\n container.appendChild(tabsEl);\n contentEls.forEach(el => {\n if (el.parentElement) {\n container.appendChild(el.parentElement);\n }\n });\n\n return { tabsEl, contentEls, updateCheckIcons };\n}\n\n/**\n * 폼 데이터 업데이트를 위해 G7Core setState를 호출합니다.\n */\nfunction syncToForm(\n name: string,\n locale: string,\n value: string,\n isMultilingual: boolean\n): void {\n // setData() 호출 중 change:data 재진입 방지\n if (isSyncSuppressed()) return;\n\n const G7Core = (window as any).G7Core;\n if (!G7Core?.state?.setLocal) {\n return;\n }\n\n const updates: Record = {\n [`form.${name}_mode`]: 'html',\n hasChanges: true,\n };\n\n if (isMultilingual) {\n updates[`form.${name}.${locale}`] = value;\n } else {\n updates[`form.${name}`] = value;\n }\n\n // G7 표준 debounce + render: false + selfManaged: true (engine-v1.43.0+)\n // - debounce: ActionDispatcher 타이머 인프라 활용, 컴포넌트 언마운트 시 자동 정리\n // - render: false: CKEditor가 자체 DOM을 관리하므로 React 리렌더 불필요\n // 타이핑 중 전체 폼 트리 리렌더 방지 (37,000+ 바인딩 평가 제거)\n // - selfManaged: true: engine-v1.43.0 자동 승격 예외 명시. HtmlEditor 내부 Textarea가\n // form.content에 자동바인딩되어 레지스트리에 등록되지만, CKEditor는 자체 DOM 관리이므로\n // render:false를 유지해야 성능 이점(37,000+ 바인딩 재평가 방지) 보존.\n // - 저장 시: flushPendingDebounceTimers → globalStateUpdater({}) 강제 렌더 1회\n G7Core.state.setLocal(updates, {\n debounce: 300,\n debounceKey: `ckeditor-sync-${name}`,\n render: false,\n selfManaged: true,\n });\n}\n\n\n/**\n * initEditor 핸들러\n *\n * onMount 시 CKEditor5 인스턴스를 초기화합니다.\n * multilingual=true 시 locale별 탭 UI와 별도 인스턴스를 생성합니다.\n *\n * @param params 핸들러 파라미터\n * @param context 액션 컨텍스트\n */\nexport async function initEditorHandler(\n action: { params?: InitEditorParams; [key: string]: any },\n _context: unknown\n): Promise {\n // ActionHandler 시그니처: (action: ActionDefinition, context: ActionContext)\n // action.params에 실제 파라미터가 있음 (ActionDispatcher에서 resolveParams 후 전달)\n const params: InitEditorParams = (action.params as InitEditorParams) ?? {};\n // 컨테이너 ID: html-editor.json의 props.id와 동일한 패턴으로 계산\n // 동일 페이지에 에디터가 여러 개 있을 때 충돌 방지\n const editorName = (params.name as string) ?? 'content';\n const containerId = `ckeditor5-${editorName}`;\n\n // 기존 인스턴스가 있으면 먼저 정리\n const existing = editorInstances.get(containerId);\n if (existing && existing.size > 0) {\n const destroyPromises: Promise[] = [];\n existing.forEach((editor: any) => {\n if (editor && typeof editor.destroy === 'function') {\n destroyPromises.push(editor.destroy().catch(() => {}));\n }\n });\n await Promise.allSettled(destroyPromises);\n editorInstances.delete(containerId);\n }\n\n // 컨테이너 DOM 찾기\n const container = document.getElementById(containerId);\n if (!container) {\n console.warn(`[sirsoft-ckeditor5] 컨테이너 엘리먼트를 찾을 수 없습니다: #${containerId}`);\n return;\n }\n\n // CKEditor CSS 동적 로드 및 다크 모드 오버라이드 적용\n loadCkeditorCss();\n loadTabCss();\n applyDarkModeOverride();\n watchDarkMode();\n\n // 번역 파일 로드 (에디터 생성 전 완료되어야 함)\n await loadCkeditorTranslations(getCurrentLocale());\n\n // 플러그인 설정을 G7Core 전역 상태에서 직접 읽기 (params 바인딩 실패 대비 폴백)\n const G7Core = (window as any).G7Core;\n const globalState = G7Core?.state?.get() || {};\n // getGlobalState()는 { sidebarOpen, plugins, ... } 구조를 반환\n // _global 래퍼가 있는 경우와 없는 경우 모두 처리\n const pluginSettings = globalState._global?.plugins?.['sirsoft-ckeditor5']\n ?? globalState.plugins?.['sirsoft-ckeditor5']\n ?? {};\n\n // 파라미터 파싱 (params 바인딩 성공 시 우선 사용, 실패(undefined) 시 플러그인 설정 폴백)\n const name = params.name ?? 'content';\n const isMultilingual = params.multilingual === true || params.multilingual === 'true';\n const isReadOnly = params.readOnly === true || params.readOnly === 'true';\n const withImageUpload = params.imageUpload !== undefined\n ? (params.imageUpload === true || params.imageUpload === 'true')\n : (pluginSettings.imageUpload === true || pluginSettings.imageUpload === 'true');\n const uploadPermission = params.uploadPermission ?? '';\n const placeholder = params.placeholder ?? '';\n const height = params.height !== undefined ? (Number(params.height) || 400) : (Number(pluginSettings.editorHeight) || 400);\n const toolbarType = (params.toolbar !== undefined ? (params.toolbar as string) : (pluginSettings.toolbar as string)) ?? 'standard';\n\n // content 파싱: 다국어 시 객체, 단일 시 문자열\n // extensionPointProps를 통해 전달되는 경우 표현식이 평가되지 않고 raw 문자열로 전달될 수 있음\n // 이 경우 G7Core.state.getLocal().form[name] 폴백 사용\n let contentMap: Record = {};\n if (isMultilingual) {\n try {\n if (typeof params.content === 'string' && params.content.startsWith('{') && !params.content.startsWith('{{')) {\n contentMap = JSON.parse(params.content);\n } else if (typeof params.content === 'object' && params.content !== null) {\n contentMap = params.content as unknown as Record;\n }\n } catch {\n contentMap = {};\n }\n // contentMap이 비어있으면 G7Core.state.getLocal().form[name]에서 폴백\n if (Object.keys(contentMap).length === 0 && name) {\n const localState = G7Core?.state?.getLocal?.() ?? {};\n const formValue = localState?.form?.[name];\n // 배열은 content 미설정 상태(API 기본값 [])이므로 무시\n if (formValue && typeof formValue === 'object' && !Array.isArray(formValue)) {\n contentMap = formValue as Record;\n } else if (typeof formValue === 'string' && formValue) {\n // 단일 문자열인 경우 현재 로케일에 매핑\n const currentLocale = getCurrentLocale();\n contentMap = { [currentLocale]: formValue };\n }\n // form[name]도 비어있으면 _global.selectedItem[name]에서 추가 폴백\n if (Object.keys(contentMap).length === 0) {\n const globalState = G7Core?.state?.get?.() ?? {};\n const selectedItem = globalState?._global?.selectedItem ?? globalState?.selectedItem;\n const selectedValue = selectedItem?.[name];\n if (selectedValue && typeof selectedValue === 'object' && !Array.isArray(selectedValue)) {\n contentMap = selectedValue as Record;\n }\n }\n }\n }\n\n const instanceMap = new Map();\n editorInstances.set(containerId, instanceMap);\n\n const editorOptions = {\n placeholder,\n readOnly: isReadOnly,\n imageUpload: withImageUpload,\n uploadPermission,\n height,\n toolbar: toolbarType,\n containerId,\n };\n\n if (isMultilingual) {\n const locales = getSupportedLocales();\n const activeLocale = getCurrentLocale();\n\n // createLocaleEditor ↔ createMultilingualTabs 순환 참조를 끊기 위한 ref\n const contentElsRef = { current: new Map() };\n const updateCheckIconsRef = { current: () => {} };\n\n // 에디터 인스턴스 생성 헬퍼\n const createLocaleEditor = async (locale: string) => {\n if (instanceMap.has(locale)) return; // 이미 생성된 경우 skip\n const contentEl = contentElsRef.current.get(locale);\n if (!contentEl) return;\n\n const initialContent = contentMap[locale] ?? '';\n\n try {\n const editor = await createEditorInstance(contentEl, {\n ...editorOptions,\n initialContent,\n });\n\n instanceMap.set(locale, editor);\n\n // 초기화 중 change:data 이벤트 억제 (CKEditor 내부 initialData 처리 시 발생 가능)\n let isInitializing = true;\n\n (editor as any).model.document.on('change:data', () => {\n if (isInitializing) return;\n const html = (editor as any).getData();\n syncToForm(name, locale, html, true);\n updateCheckIconsRef.current();\n });\n\n isInitializing = false;\n\n // 초기 콘텐츠 재동기화 제거 — initLocal이 이미 form state에 반영\n // mode만 설정 (create 모드에서 _mode 미설정 방지)\n // hasChanges는 설정하지 않음 (사용자 변경 아님)\n if (name) {\n const G7Core = (window as any).G7Core;\n const local = G7Core?.state?.getLocal?.() ?? {};\n if (local.form?.[`${name}_mode`] !== 'html') {\n G7Core.state.setLocal({ [`form.${name}_mode`]: 'html' });\n }\n }\n } catch (err) {\n console.error(`[sirsoft-ckeditor5] 에디터 초기화 오류 (locale: ${locale}):`, err);\n }\n };\n\n // instanceMap에서 에디터 데이터를 반환하는 콜백\n const getEditorData = (locale: string): string | null => {\n const editor = instanceMap.get(locale);\n if (!editor) return null;\n return (editor as any).getData?.() ?? null;\n };\n\n // 탭 클릭 시 lazy 생성 콜백을 전달하여 탭 UI 생성\n const { contentEls, updateCheckIcons } = createMultilingualTabs(container, locales, activeLocale, createLocaleEditor, getEditorData);\n contentElsRef.current = contentEls;\n updateCheckIconsRef.current = updateCheckIcons;\n\n // 활성 locale 에디터 먼저 생성\n await createLocaleEditor(activeLocale);\n\n // 나머지 locale 에디터 백그라운드에서 미리 생성 (탭 전환 시 딜레이 제거)\n // setTimeout으로 지연하여 활성 에디터 초기 입력 반응성 확보\n setTimeout(() => {\n locales.filter(l => l !== activeLocale).forEach(locale => {\n createLocaleEditor(locale);\n });\n }, 2000);\n\n } else {\n // 단일 에디터 모드\n // params.content가 빈 문자열이거나 미평가 표현식({{...}})인 경우\n // onMount 타이밍 문제로 _local.form이 아직 미적용된 것일 수 있음\n // G7Core.state.getLocal()로 현재 상태를 직접 읽어 폴백\n const rawContent = typeof params.content === 'string' ? params.content : '';\n const isUnresolvedExpression = rawContent.startsWith('{{') && rawContent.endsWith('}}');\n let initialContent = isUnresolvedExpression ? '' : rawContent;\n if (!initialContent && name) {\n const localState = G7Core?.state?.getLocal?.() ?? {};\n const formValue = localState?.form?.[name];\n if (typeof formValue === 'string' && formValue) {\n initialContent = formValue;\n }\n }\n const editorEl = document.createElement('div');\n container.appendChild(editorEl);\n\n try {\n const editor = await createEditorInstance(editorEl, {\n ...editorOptions,\n initialContent,\n });\n\n const singleLocale = getCurrentLocale();\n instanceMap.set(singleLocale, editor);\n\n // 초기화 중 change:data 이벤트 억제 (CKEditor 내부 initialData 처리 시 발생 가능)\n let isInitializing = true;\n\n // change:data 이벤트 → 폼 상태 동기화 (디바운스는 setLocal 내부에서 처리)\n (editor as any).model.document.on('change:data', () => {\n if (isInitializing) return;\n const html = (editor as any).getData();\n syncToForm(name, singleLocale, html, false);\n });\n\n isInitializing = false;\n\n // 초기값 강제 설정 (initialData가 무시되는 경우 대비)\n if (initialContent) {\n const currentData = (editor as any).getData();\n if (!currentData || currentData.trim() === '') {\n (editor as any).setData(initialContent);\n }\n // 초기 콘텐츠 재동기화 제거 — initLocal이 이미 form state에 반영\n }\n\n // mode만 설정 (create 모드에서 _mode 미설정 방지)\n if (name) {\n const G7Core = (window as any).G7Core;\n const local = G7Core?.state?.getLocal?.() ?? {};\n if (local.form?.[`${name}_mode`] !== 'html') {\n G7Core.state.setLocal({ [`form.${name}_mode`]: 'html' });\n }\n }\n\n // 단일 에디터는 페이지 이동 없이 form이 교체되는 패턴이 없으므로\n // 외부 상태 변경 polling 불필요\n } catch (err) {\n console.error('[sirsoft-ckeditor5] 에디터 초기화 오류:', err);\n editorInstances.delete(containerId);\n }\n }\n}\n","/**\n * CKEditor5 인스턴스 정리 핸들러\n *\n * 컴포넌트 언마운트 시 CKEditor 인스턴스를 파괴합니다.\n */\n\n// 컨테이너 ID → 인스턴스 맵 (initEditor.ts와 공유)\nimport { editorInstances } from './editorInstances';\nimport { disconnectDarkModeObserver } from './initEditor';\n\n/**\n * destroyEditor 핸들러\n *\n * onUnmount 시 해당 컨테이너의 모든 CKEditor 인스턴스를 파괴합니다.\n *\n * @param params 핸들러 파라미터 (사용 안 함)\n * @param context 액션 컨텍스트\n */\nexport async function destroyEditorHandler(\n action: Record,\n _context: unknown\n): Promise {\n // ActionHandler 시그니처: (action: ActionDefinition, context: ActionContext)\n // action.params.name 기반으로 containerId 계산 (initEditor와 동일한 패턴)\n const editorName = (action.params?.name as string) ?? 'content';\n const containerId = `ckeditor5-${editorName}`;\n const instances = editorInstances.get(containerId);\n\n if (!instances || instances.size === 0) {\n return;\n }\n\n const destroyPromises: Promise[] = [];\n\n instances.forEach((editor) => {\n if (editor && typeof editor.destroy === 'function') {\n destroyPromises.push(\n editor.destroy().catch((err: unknown) => {\n console.warn('[sirsoft-ckeditor5] destroyEditor error:', err);\n })\n );\n }\n });\n\n await Promise.allSettled(destroyPromises);\n\n editorInstances.delete(containerId);\n\n // height style 요소 정리\n const heightStyle = document.getElementById(`ckeditor5-height-style-${containerId}`);\n if (heightStyle) {\n heightStyle.remove();\n }\n\n // 모든 에디터 인스턴스가 제거되면 다크모드 옵저버도 해제\n if (editorInstances.size === 0) {\n disconnectDarkModeObserver();\n }\n}\n","/**\n * sirsoft-ckeditor5 플러그인 엔트리포인트\n *\n * 플러그인 활성화 시 자동으로 로드되어 핸들러를 등록합니다.\n */\n\nimport { initEditorHandler } from './handlers/initEditor';\nimport { destroyEditorHandler } from './handlers/destroyEditor';\n\nconst PLUGIN_IDENTIFIER = 'sirsoft-ckeditor5';\n\nconst CKEDITOR5_CSS_ID = 'ckeditor5-content-styles';\nconst CKEDITOR5_CSS_OVERRIDE_ID = 'ckeditor5-content-styles-override';\nconst CKEDITOR5_CSS_URL = 'https://cdn.ckeditor.com/ckeditor5/43.3.1/ckeditor5.css';\n\n/**\n * CKEditor5 content styles CSS를 동적으로 주입하는 핸들러\n * html-content.json extension_point의 onMount에서 호출됩니다.\n *\n * HtmlContent 컴포넌트에 ck-content 클래스가 적용되어 CDN CSS가 직접 동작합니다.\n * 다크모드(CDN 미지원)와 col 너비 처리만 override로 추가합니다.\n */\nfunction injectContentCssHandler(): void {\n if (!document.getElementById(CKEDITOR5_CSS_ID)) {\n const link = document.createElement('link');\n link.id = CKEDITOR5_CSS_ID;\n link.rel = 'stylesheet';\n link.href = CKEDITOR5_CSS_URL;\n document.head.appendChild(link);\n }\n\n if (!document.getElementById(CKEDITOR5_CSS_OVERRIDE_ID)) {\n const style = document.createElement('style');\n style.id = CKEDITOR5_CSS_OVERRIDE_ID;\n // 다크모드(CDN 미지원)와 col 너비 처리만 override로 유지\n style.textContent = `\n /* 이미지 캡션 - 다크모드 */\n html.dark .ck-content .image > figcaption { color: hsl(0, 0%, 80%); background-color: hsl(0, 0%, 15%); }\n\n /* CKEditor5 표 스타일 - 다크모드 */\n html.dark .ck-content figure.table table { border-color: hsl(0, 0%, 35%); }\n html.dark .ck-content figure.table table tr { background-color: unset; }\n html.dark .ck-content figure.table table td, html.dark .ck-content figure.table table th { border-color: hsl(0, 0%, 30%); color: hsl(0, 0%, 90%); background-color: unset; }\n html.dark .ck-content figure.table table th { background-color: hsl(215, 20%, 25%); }\n\n /* figure.table 전체 너비 - prose와의 충돌 방지 */\n .ck-content figure.table { display: table; width: 100%; margin: 1em 0; }\n .ck-content figure.table table { width: 100%; }\n .ck-content table tr { background-color: unset; }\n .ck-content table col { width: auto !important; }\n\n /* 표 정렬 보정 */\n .ck-content figure.table[style*=\"float:left\"],\n .ck-content figure.table[style*=\"float: left\"] { margin-left: 0 !important; margin-right: 1em !important; }\n .ck-content figure.table[style*=\"float:right\"],\n .ck-content figure.table[style*=\"float: right\"] { margin-left: 1em !important; margin-right: 0 !important; }\n\n /* 이미지 인라인/정렬 - Tailwind preflight img:block 대응 */\n .ck-content img { display: inline; margin: 0; max-width: 100%; }\n .ck-content p:has(img) { line-height: 0; }\n .ck-content p:has(img[style*=\"float\"]) { overflow: hidden; }\n\n /* 이미지 캡션 - 라이트 모드 */\n .ck-content .image > figcaption { background-color: #f3f4f6; color: #374151; font-size: 0.875em; padding: 4px 8px; text-align: center; }\n\n /* 목록 마커 및 들여쓰기 - Tailwind preflight reset 대응 */\n .ck-content ul { list-style-type: disc; padding-left: 2em; }\n .ck-content ol { list-style-type: decimal; padding-left: 2em; }\n .ck-content ul ul { list-style-type: circle; padding-left: 2em; }\n .ck-content ul ul ul { list-style-type: square; padding-left: 2em; }\n .ck-content ol ol { list-style-type: lower-alpha; padding-left: 2em; }\n .ck-content ol ol ol { list-style-type: lower-roman; padding-left: 2em; }\n `;\n document.head.appendChild(style);\n }\n\n}\n\nconst handlerMap: Record = {\n initEditor: initEditorHandler,\n destroyEditor: destroyEditorHandler,\n injectContentCss: injectContentCssHandler,\n};\n\nconst logger = ((window as any).G7Core?.createLogger?.(`Plugin:${PLUGIN_IDENTIFIER}`)) ?? {\n log: (...args: unknown[]) => console.log(`[Plugin:${PLUGIN_IDENTIFIER}]`, ...args),\n warn: (...args: unknown[]) => console.warn(`[Plugin:${PLUGIN_IDENTIFIER}]`, ...args),\n error: (...args: unknown[]) => console.error(`[Plugin:${PLUGIN_IDENTIFIER}]`, ...args),\n};\n\nfunction registerHandlers(retry: boolean = false): void {\n const actionDispatcher = (window as any).G7Core?.getActionDispatcher?.();\n\n if (actionDispatcher) {\n Object.entries(handlerMap).forEach(([name, handler]) => {\n const fullName = `${PLUGIN_IDENTIFIER}.${name}`;\n actionDispatcher.registerHandler(fullName, handler, {\n category: 'plugin',\n source: PLUGIN_IDENTIFIER,\n });\n });\n\n logger.log(\n `${Object.keys(handlerMap).length} handler(s) registered:`,\n Object.keys(handlerMap).map(name => `${PLUGIN_IDENTIFIER}.${name}`)\n );\n } else if (retry) {\n let retryCount = 0;\n const maxRetries = 50;\n\n const retryRegister = () => {\n const dispatcher = (window as any).G7Core?.getActionDispatcher?.();\n\n if (dispatcher) {\n Object.entries(handlerMap).forEach(([name, handler]) => {\n const fullName = `${PLUGIN_IDENTIFIER}.${name}`;\n dispatcher.registerHandler(fullName, handler, {\n category: 'plugin',\n source: PLUGIN_IDENTIFIER,\n });\n });\n\n logger.log(\n `${Object.keys(handlerMap).length} handler(s) registered:`,\n Object.keys(handlerMap).map(name => `${PLUGIN_IDENTIFIER}.${name}`)\n );\n } else {\n retryCount++;\n if (retryCount <= maxRetries) {\n logger.warn(`ActionDispatcher not found, retrying... (${retryCount}/${maxRetries})`);\n setTimeout(retryRegister, 100);\n } else {\n logger.error(`Failed to register handlers: ActionDispatcher not available after maximum retries`);\n }\n }\n };\n\n retryRegister();\n } else {\n logger.warn(`ActionDispatcher not found, handlers not registered`);\n }\n}\n\nexport function initPlugin(): void {\n if (document.readyState === 'loading') {\n document.addEventListener('DOMContentLoaded', () => registerHandlers(true));\n } else {\n const hasDispatcher = !!(window as any).G7Core?.getActionDispatcher?.();\n registerHandlers(hasDispatcher ? false : true);\n }\n}\n\ninitPlugin();\n\nif (typeof window !== 'undefined') {\n (window as any).__SirsoftCkeditor5 = {\n identifier: PLUGIN_IDENTIFIER,\n handlers: Object.keys(handlerMap),\n initPlugin,\n };\n}"],"names":["editorInstances","CKEDITOR5_CSS_URL","CKEDITOR5_TRANSLATIONS_BASE","CKEDITOR5_CSS_ID","CKEDITOR5_DARK_CSS_ID","CKEDITOR5_TAB_CSS_ID","TOOLBAR_PRESETS","loadCkeditorTranslations","locale","id","resolve","script","loadCkeditorCss","link","loadTabCss","style","isDarkMode","applyDarkModeOverride","existing","darkModeObserver","watchDarkMode","disconnectDarkModeObserver","LOCALE_LABELS","getLocaleLabel","getSupportedLocales","G7Core","getCurrentLocale","getPlugins","CKEDITOR","toolbarType","withImageUpload","base","buildUploadUrl","uploadPermission","getUploadHeaders","token","createEditorInstance","element","options","toolbarItems","finalToolbar","item","editorConfig","url","editor","heightStyleId","existingHeightStyle","heightStyle","createMultilingualTabs","container","locales","activeLocale","onTabActivate","getEditorData","tabsEl","contentEls","wrapperEl","contentEl","updateCheckIcons","btn","btnLocale","hasValue","icon","defaultLocale","isDefault","isActive","tab","btnIsActive","el","elLocale","syncToForm","name","value","isMultilingual","updates","initEditorHandler","action","_context","params","containerId","destroyPromises","globalState","pluginSettings","isReadOnly","placeholder","height","contentMap","formValue","selectedValue","instanceMap","editorOptions","contentElsRef","updateCheckIconsRef","createLocaleEditor","initialContent","isInitializing","html","err","l","rawContent","editorEl","singleLocale","currentData","destroyEditorHandler","instances","PLUGIN_IDENTIFIER","CKEDITOR5_CSS_OVERRIDE_ID","injectContentCssHandler","handlerMap","logger","args","registerHandlers","retry","actionDispatcher","handler","fullName","retryCount","maxRetries","retryRegister","dispatcher","initPlugin","hasDispatcher"],"mappings":"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"} \ No newline at end of file diff --git a/plugins/_bundled/sirsoft-ckeditor5/vite.config.ts b/plugins/_bundled/sirsoft-ckeditor5/vite.config.ts index 38e4ef83..7aea4ea2 100644 --- a/plugins/_bundled/sirsoft-ckeditor5/vite.config.ts +++ b/plugins/_bundled/sirsoft-ckeditor5/vite.config.ts @@ -16,7 +16,9 @@ export default defineConfig({ outDir: 'dist', emptyOutDir: true, - sourcemap: true, + // 배포용 빌드(--production)는 G7_BUILD_SOURCEMAP=0 을 주입해 소스맵을 생성하지 않는다. + // 미설정(로컬 npm run build)이면 생성 — 개발 디버깅 경험을 유지한다. + sourcemap: !['0', 'false'].includes(process.env.G7_BUILD_SOURCEMAP ?? ''), rollupOptions: { output: { diff --git a/plugins/_bundled/sirsoft-daum_postcode/components.json b/plugins/_bundled/sirsoft-daum_postcode/components.json new file mode 100644 index 00000000..6a638c7e --- /dev/null +++ b/plugins/_bundled/sirsoft-daum_postcode/components.json @@ -0,0 +1,10 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "identifier": "sirsoft-daum_postcode", + "version": "1.0.0", + "components": { + "basic": [], + "composite": [], + "layout": [] + } +} diff --git a/plugins/_bundled/sirsoft-daum_postcode/dist/js/plugin.iife.js b/plugins/_bundled/sirsoft-daum_postcode/dist/js/plugin.iife.js index 9057a384..6005e724 100644 --- a/plugins/_bundled/sirsoft-daum_postcode/dist/js/plugin.iife.js +++ b/plugins/_bundled/sirsoft-daum_postcode/dist/js/plugin.iife.js @@ -34,4 +34,3 @@ var SirsoftDaumPostcode=function(m){"use strict";const y=window.G7Core?.createLo justify-content: center; z-index: 10; `;const r=document.createElement("div");r.style.cssText="position: relative;",r.appendChild(n),r.appendChild(t),o.appendChild(r),document.body.appendChild(o);const s=()=>{o.parentNode&&o.parentNode.removeChild(o)};n.addEventListener("click",s),o.addEventListener("click",l=>{l.target===o&&s()});const i=l=>{l.key==="Escape"&&(s(),document.removeEventListener("keydown",i))};return document.addEventListener("keydown",i),a.log("[openPostcode] Layer created with size:",{width:e,height:c}),{overlay:o,layer:t,closeLayer:s}}function L(e,c,o){const t=window.G7Core;if(!t?.dispatch){a.error("[openPostcode] G7Core.dispatch not available");return}const n=o?.state||t?.state?.getLocal?.()||{},r={state:n,setState:o?.setState,data:{...o?.data||{},_local:n,$event:c},isolatedContext:o?.isolatedContext};a.log("[openPostcode] Dispatching with componentContext:",{hasLocalState:!!n,sourceType:o?.state?"handlerContext":"getLocal",localCheckout:n?.checkout,event:c});try{const s=Array.isArray(e)?e:[e];for(const i of s)i&&typeof i=="object"&&(a.log("[openPostcode] Dispatching action:",i),t.dispatch(i,{componentContext:r}))}catch(s){a.error("[openPostcode] callbackAction failed:",s)}}function S(e,c){const o=window.G7Core;if(!o?.state?.setLocal){a.error("[openPostcode] G7Core.state.setLocal not available");return}const t={};e.zipcode&&(t[e.zipcode]=c.zipcode),e.address&&(t[e.address]=c.address),e.region&&(t[e.region]=c.region),e.city&&(t[e.city]=c.city),a.log("[openPostcode] Setting local state:",t),o.state.setLocal(t)}const p={setFieldReadOnly:C,openPostcode:x},d="sirsoft-daum_postcode",u=window.G7Core?.createLogger?.(`Plugin:${d}`)??{log:(...e)=>console.log(`[Plugin:${d}]`,...e),warn:(...e)=>console.warn(`[Plugin:${d}]`,...e),error:(...e)=>console.error(`[Plugin:${d}]`,...e)};function P(e=!1){const c=window.G7Core?.getActionDispatcher?.();if(c)Object.entries(p).forEach(([o,t])=>{const n=`${d}.${o}`;c.registerHandler(n,t,{category:"plugin",source:d})}),u.log(`${Object.keys(p).length} handler(s) registered:`,Object.keys(p).map(o=>`${d}.${o}`));else if(e){let o=0;const t=50,n=()=>{const r=window.G7Core?.getActionDispatcher?.();r?(Object.entries(p).forEach(([s,i])=>{const l=`${d}.${s}`;r.registerHandler(l,i,{category:"plugin",source:d})}),u.log(`${Object.keys(p).length} handler(s) registered:`,Object.keys(p).map(s=>`${d}.${s}`))):(o++,o<=t?(u.warn(`ActionDispatcher not found, retrying... (${o}/${t})`),setTimeout(n,100)):u.error("Failed to register handlers: ActionDispatcher not available after maximum retries"))};n()}else u.warn("ActionDispatcher not found, handlers not registered")}function w(){if(document.readyState==="loading")document.addEventListener("DOMContentLoaded",()=>P(!0));else{const e=!!window.G7Core?.getActionDispatcher?.();P(!e)}}return w(),typeof window<"u"&&(window.__SirsoftDaumPostcode={identifier:d,handlers:Object.keys(p),initPlugin:w}),m.initPlugin=w,Object.defineProperty(m,Symbol.toStringTag,{value:"Module"}),m}({}); -//# sourceMappingURL=plugin.iife.js.map diff --git a/plugins/_bundled/sirsoft-daum_postcode/dist/js/plugin.iife.js.map b/plugins/_bundled/sirsoft-daum_postcode/dist/js/plugin.iife.js.map deleted file mode 100644 index e8647f65..00000000 --- a/plugins/_bundled/sirsoft-daum_postcode/dist/js/plugin.iife.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"plugin.iife.js","sources":["../../resources/js/handlers/setFieldReadOnly.ts","../../resources/js/handlers/openPostcode.ts","../../resources/js/handlers/index.ts","../../resources/js/index.ts"],"sourcesContent":["/**\n * setFieldReadOnly 핸들러\n *\n * 지정된 필드명의 input 요소를 찾아 readOnly 속성을 설정합니다.\n * 주소 검색 플러그인이 설치된 경우, 우편번호/주소 필드를 readOnly로 설정하여\n * 사용자가 직접 입력하지 못하도록 합니다.\n */\n\nimport type { ActionContext } from '../types';\n\n// Logger 설정 (G7Core 초기화 전에도 동작하도록 폴백 포함)\nconst logger = ((window as any).G7Core?.createLogger?.('DaumPostcode:SetFieldReadOnly')) ?? {\n log: (...args: unknown[]) => console.log('[DaumPostcode:SetFieldReadOnly]', ...args),\n warn: (...args: unknown[]) => console.warn('[DaumPostcode:SetFieldReadOnly]', ...args),\n error: (...args: unknown[]) => console.error('[DaumPostcode:SetFieldReadOnly]', ...args),\n};\n\ninterface ActionWithParams {\n handler: string;\n params?: {\n /** readOnly로 설정할 필드명 배열 */\n fields?: string[];\n /** readOnly 값 (기본값: true) */\n readOnly?: boolean;\n };\n [key: string]: any;\n}\n\n/**\n * 지정된 필드명의 input 요소를 찾아 readOnly 속성을 설정합니다.\n *\n * @param action 액션 객체 (params.fields: 필드명 배열, params.readOnly: boolean)\n * @param _context 액션 컨텍스트\n *\n * @example\n * // 레이아웃 JSON에서 사용\n * {\n * \"handler\": \"sirsoft-daum_postcode.setFieldReadOnly\",\n * \"params\": {\n * \"fields\": [\"zonecode\", \"address\"],\n * \"readOnly\": true\n * }\n * }\n */\nexport function setFieldReadOnlyHandler(\n action: ActionWithParams,\n _context: ActionContext\n): void {\n const params = action.params || {};\n const fields = params.fields || [];\n const readOnly = params.readOnly !== false; // 기본값 true\n\n if (!fields.length) {\n logger.warn('[setFieldReadOnly] No fields specified');\n return;\n }\n\n logger.log(`[setFieldReadOnly] Setting readOnly=${readOnly} for fields:`, fields);\n\n // 각 필드에 대해 DOM에서 input 요소를 찾아 readOnly 설정\n fields.forEach((fieldName) => {\n // name 속성으로 input 요소 찾기\n const inputs = document.querySelectorAll(\n `input[name=\"${fieldName}\"], textarea[name=\"${fieldName}\"]`\n );\n\n if (inputs.length === 0) {\n logger.warn(`[setFieldReadOnly] No input found with name=\"${fieldName}\"`);\n return;\n }\n\n inputs.forEach((input) => {\n input.readOnly = readOnly;\n\n // readOnly 상태에 따른 시각적 피드백 (선택적)\n if (readOnly) {\n input.classList.add('readonly');\n } else {\n input.classList.remove('readonly');\n }\n\n logger.log(`[setFieldReadOnly] Set readOnly=${readOnly} on input[name=\"${fieldName}\"]`);\n });\n });\n}\n","/**\n * openPostcode 핸들러\n *\n * 다음 우편번호 API를 호출하고 G7 표준 형식으로 변환하여 콜백 실행\n * - callbackAction 있으면: 커스텀 동작 실행 (Extension Point에서 정의한 onAddressSelect)\n * - callbackAction 없으면: 플러그인 기본 동작 (설정 기반 setState)\n *\n * G7 표준 주소 이벤트 형식:\n * - zipcode: 우편번호\n * - address: 기본 주소 (도로명/지번)\n * - addressDetail: 상세 주소 (사용자 입력)\n * - region: 시/도\n * - city: 시/군/구\n * - countryCode: 국가 코드\n * - _raw: 원본 다음 API 응답 (필요 시 접근 가능)\n */\n\nimport type { ActionContext } from '../types';\n\n// Logger 설정 (G7Core 초기화 전에도 동작하도록 폴백 포함)\nconst logger = ((window as any).G7Core?.createLogger?.('DaumPostcode:OpenPostcode')) ?? {\n log: (...args: unknown[]) => console.log('[DaumPostcode:OpenPostcode]', ...args),\n warn: (...args: unknown[]) => console.warn('[DaumPostcode:OpenPostcode]', ...args),\n error: (...args: unknown[]) => console.error('[DaumPostcode:OpenPostcode]', ...args),\n};\n\ninterface ActionWithParams {\n handler: string;\n params?: {\n /** 주소 선택 시 실행할 콜백 액션 (Extension Point props에서 전달) */\n callbackAction?: any;\n /** 표시 모드 (popup | layer) */\n displayMode?: string;\n /** 팝업/레이어 너비 */\n width?: number;\n /** 팝업/레이어 높이 */\n height?: number;\n /** 테마 설정 */\n theme?: {\n searchBgColor?: string;\n queryTextColor?: string;\n };\n /** 대상 필드 매핑 (기본 동작 시 사용) */\n targetFields?: {\n zipcode?: string;\n address?: string;\n region?: string;\n city?: string;\n };\n };\n [key: string]: any;\n}\n\n/**\n * G7 표준 주소 이벤트 인터페이스\n */\ninterface G7AddressEvent {\n /** 우편번호 */\n zipcode: string;\n /** 기본 주소 (도로명/지번) */\n address: string;\n /** 상세 주소 (사용자 입력) */\n addressDetail: string;\n /** 시/도 */\n region: string;\n /** 시/군/구 */\n city: string;\n /** 국가 코드 */\n countryCode: string;\n /** 원본 다음 API 응답 */\n _raw: any;\n}\n\n/**\n * 다음 우편번호 API 응답을 G7 표준 형식으로 변환합니다.\n */\nfunction convertToG7AddressEvent(daumData: any): G7AddressEvent {\n return {\n zipcode: daumData.zonecode || '',\n address: daumData.roadAddress || daumData.jibunAddress || '',\n addressDetail: '',\n region: daumData.sido || '',\n city: daumData.sigungu || '',\n countryCode: 'KR',\n _raw: daumData,\n };\n}\n\n/**\n * 다음 우편번호 API를 호출하고 G7 표준 형식으로 변환하여 콜백 실행\n */\nexport function openPostcodeHandler(\n action: ActionWithParams,\n context: ActionContext\n): void {\n const params = action.params || {};\n const {\n callbackAction,\n displayMode = 'layer',\n width = 500,\n height = 600,\n theme,\n targetFields,\n } = params;\n\n // 플러그인 설정에서 기본 필드 매핑 조회\n const G7Core = (window as any).G7Core;\n const pluginSettings = G7Core?.plugin?.getSettings?.('sirsoft-daum_postcode') || {};\n const defaultTargetFields = pluginSettings.target_fields || {\n zipcode: 'shipping.zipcode',\n address: 'shipping.address',\n region: 'shipping.region',\n city: 'shipping.city',\n };\n\n logger.log('[openPostcode] Starting with params:', {\n displayMode,\n width,\n height,\n hasCallbackAction: !!callbackAction,\n targetFields: targetFields || defaultTargetFields,\n });\n\n // 다음 우편번호 API가 로드되었는지 확인\n if (!(window as any).daum?.Postcode) {\n logger.error('[openPostcode] Daum Postcode API not loaded');\n return;\n }\n\n const postcodeConfig: any = {\n oncomplete: (data: any) => {\n logger.log('[openPostcode] Address selected (raw):', data);\n\n // 다음 API 응답 → G7 표준 형식 변환\n const g7AddressEvent = convertToG7AddressEvent(data);\n logger.log('[openPostcode] Converted to G7 format:', g7AddressEvent);\n\n // 하이브리드 분기: callbackAction 유무에 따라 동작 결정\n if (callbackAction) {\n // 커스텀 동작: Extension Point에서 정의한 onAddressSelect 실행\n // context를 전달하여 비동기 콜백에서도 컴포넌트 상태 참조 가능\n logger.log('[openPostcode] Executing callbackAction');\n executeCallbackAction(callbackAction, g7AddressEvent, context);\n } else {\n // 기본 동작: 플러그인 설정 기반 setState\n logger.log('[openPostcode] Executing default setState');\n executeDefaultSetState(targetFields || defaultTargetFields, g7AddressEvent);\n }\n },\n width,\n height,\n };\n\n // 테마 설정 적용\n if (theme) {\n postcodeConfig.theme = theme;\n }\n\n // 다음 우편번호 인스턴스 생성\n const postcode = new (window as any).daum.Postcode(postcodeConfig);\n\n if (displayMode === 'popup') {\n // 팝업 모드: 새 창으로 열기\n postcode.open();\n } else {\n // 레이어 모드: 오버레이에 임베드\n const { layer, closeLayer } = createEmbedLayer(width, height);\n\n // oncomplete 콜백 후 레이어 닫기 처리\n const originalOncomplete = postcodeConfig.oncomplete;\n postcodeConfig.oncomplete = (data: any) => {\n closeLayer();\n originalOncomplete(data);\n };\n\n // 새 config로 인스턴스 재생성 후 임베드\n const layerPostcode = new (window as any).daum.Postcode(postcodeConfig);\n layerPostcode.embed(layer);\n }\n}\n\n/**\n * 임베드용 레이어(오버레이) 요소를 생성합니다.\n * 플러그인 설정에서 전달받은 width, height를 사용합니다.\n */\nfunction createEmbedLayer(width: number, height: number): {\n overlay: HTMLElement;\n layer: HTMLElement;\n closeLayer: () => void;\n} {\n // 오버레이 배경\n const overlay = document.createElement('div');\n overlay.id = 'daum-postcode-overlay';\n overlay.style.cssText = `\n position: fixed;\n top: 0;\n left: 0;\n width: 100%;\n height: 100%;\n background-color: rgba(0, 0, 0, 0.5);\n display: flex;\n align-items: center;\n justify-content: center;\n z-index: 9999;\n `;\n\n // 레이어 컨테이너\n const layer = document.createElement('div');\n layer.id = 'daum-postcode-layer';\n layer.style.cssText = `\n width: ${width}px;\n height: ${height}px;\n background-color: white;\n border-radius: 8px;\n overflow: hidden;\n box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.25);\n `;\n\n // 닫기 버튼\n const closeButton = document.createElement('button');\n closeButton.type = 'button';\n closeButton.innerHTML = '×';\n closeButton.style.cssText = `\n position: absolute;\n top: -12px;\n right: -12px;\n width: 32px;\n height: 32px;\n border-radius: 50%;\n background-color: #374151;\n color: white;\n border: none;\n cursor: pointer;\n font-size: 20px;\n line-height: 1;\n display: flex;\n align-items: center;\n justify-content: center;\n z-index: 10;\n `;\n\n // 레이어 래퍼 (닫기 버튼 포지셔닝용)\n const wrapper = document.createElement('div');\n wrapper.style.cssText = 'position: relative;';\n wrapper.appendChild(closeButton);\n wrapper.appendChild(layer);\n\n overlay.appendChild(wrapper);\n document.body.appendChild(overlay);\n\n // 레이어 닫기 함수\n const closeLayer = () => {\n if (overlay.parentNode) {\n overlay.parentNode.removeChild(overlay);\n }\n };\n\n // 닫기 버튼 클릭 시 레이어 닫기\n closeButton.addEventListener('click', closeLayer);\n\n // 오버레이 배경 클릭 시 레이어 닫기\n overlay.addEventListener('click', (e) => {\n if (e.target === overlay) {\n closeLayer();\n }\n });\n\n // ESC 키로 레이어 닫기\n const handleEscKey = (e: KeyboardEvent) => {\n if (e.key === 'Escape') {\n closeLayer();\n document.removeEventListener('keydown', handleEscKey);\n }\n };\n document.addEventListener('keydown', handleEscKey);\n\n logger.log('[openPostcode] Layer created with size:', { width, height });\n\n return { overlay, layer, closeLayer };\n}\n\n/**\n * callbackAction을 실행합니다.\n * G7Core.dispatch의 componentContext 옵션을 활용하여 비동기 콜백에서도\n * 버튼 클릭 시점의 컴포넌트 상태를 정확히 전달합니다.\n *\n * dispatch 내부 우선순위: options.componentContext > __g7ActionContext > global fallback\n * 따라서 componentContext를 명시적으로 전달하면 __g7ActionContext 수동 관리가 불필요합니다.\n *\n * handlerContext.state 우선 사용 이유:\n * - handleSetState의 컴포넌트 경로(isRealComponentContext=true)는 React setState만 호출하고\n * globalState._local을 업데이트하지 않음\n * - React 커밋 후 __g7PendingLocalState가 클리어되면 getLocal()은 stale globalState._local 반환\n * - handlerContext.state는 extendedDataContext._local(globalState + localDynamicState 병합)에서\n * 캡처되므로 컴포넌트 setState 결과가 반영된 최신 상태임\n */\nfunction executeCallbackAction(callbackAction: any, g7AddressEvent: G7AddressEvent, handlerContext?: ActionContext): void {\n const G7Core = (window as any).G7Core;\n\n if (!G7Core?.dispatch) {\n logger.error('[openPostcode] G7Core.dispatch not available');\n return;\n }\n\n // handlerContext.state(버튼 클릭 시점 componentContext.state)를 우선 사용\n // getLocal()은 globalState._local만 반환하여 컴포넌트 setState 결과 누락 가능\n const currentLocalState = handlerContext?.state || G7Core?.state?.getLocal?.() || {};\n\n // dispatch의 componentContext 옵션으로 전달\n // → dispatch 내부에서 __g7ActionContext보다 우선 적용됨\n const componentContext = {\n state: currentLocalState,\n setState: handlerContext?.setState,\n data: {\n ...(handlerContext?.data || {}),\n _local: currentLocalState,\n $event: g7AddressEvent,\n },\n isolatedContext: handlerContext?.isolatedContext,\n };\n\n logger.log('[openPostcode] Dispatching with componentContext:', {\n hasLocalState: !!currentLocalState,\n sourceType: handlerContext?.state ? 'handlerContext' : 'getLocal',\n localCheckout: currentLocalState?.checkout,\n event: g7AddressEvent,\n });\n\n try {\n const actions = Array.isArray(callbackAction) ? callbackAction : [callbackAction];\n for (const cbAction of actions) {\n if (cbAction && typeof cbAction === 'object') {\n logger.log('[openPostcode] Dispatching action:', cbAction);\n G7Core.dispatch(cbAction, { componentContext });\n }\n }\n } catch (error) {\n logger.error('[openPostcode] callbackAction failed:', error);\n }\n}\n\n/**\n * 기본 setState 동작을 실행합니다.\n */\nfunction executeDefaultSetState(\n targetFields: Record,\n g7AddressEvent: G7AddressEvent\n): void {\n const G7Core = (window as any).G7Core;\n\n if (!G7Core?.state?.setLocal) {\n logger.error('[openPostcode] G7Core.state.setLocal not available');\n return;\n }\n\n // targetFields 매핑에 따라 상태 업데이트\n const updates: Record = {};\n\n if (targetFields.zipcode) {\n updates[targetFields.zipcode] = g7AddressEvent.zipcode;\n }\n if (targetFields.address) {\n updates[targetFields.address] = g7AddressEvent.address;\n }\n if (targetFields.region) {\n updates[targetFields.region] = g7AddressEvent.region;\n }\n if (targetFields.city) {\n updates[targetFields.city] = g7AddressEvent.city;\n }\n\n logger.log('[openPostcode] Setting local state:', updates);\n G7Core.state.setLocal(updates);\n}\n","/**\n * sirsoft-daum_postcode 플러그인 핸들러\n *\n * 플러그인에서 사용하는 모든 커스텀 핸들러를 정의합니다.\n */\n\nimport { setFieldReadOnlyHandler } from './setFieldReadOnly';\nimport { openPostcodeHandler } from './openPostcode';\n\n/**\n * 핸들러 맵\n *\n * 키: 핸들러 이름 (네임스페이스 없이)\n * 값: 핸들러 함수\n *\n * ActionDispatcher에 등록 시 플러그인 식별자가 네임스페이스로 추가됩니다.\n * 예: 'setFieldReadOnly' -> 'sirsoft-daum_postcode.setFieldReadOnly'\n * 예: 'openPostcode' -> 'sirsoft-daum_postcode.openPostcode'\n */\nexport const handlerMap = {\n setFieldReadOnly: setFieldReadOnlyHandler,\n openPostcode: openPostcodeHandler,\n} as const;\n","/**\n * sirsoft-daum_postcode 플러그인 엔트리포인트\n *\n * 플러그인 활성화 시 자동으로 로드되어 핸들러를 등록합니다.\n */\n\n// 핸들러 맵 임포트\nimport { handlerMap } from './handlers';\n\n// 플러그인 식별자\nconst PLUGIN_IDENTIFIER = 'sirsoft-daum_postcode';\n\n// Logger 설정 (G7Core 초기화 전에도 동작하도록 폴백 포함)\nconst logger = ((window as any).G7Core?.createLogger?.(`Plugin:${PLUGIN_IDENTIFIER}`)) ?? {\n log: (...args: unknown[]) => console.log(`[Plugin:${PLUGIN_IDENTIFIER}]`, ...args),\n warn: (...args: unknown[]) => console.warn(`[Plugin:${PLUGIN_IDENTIFIER}]`, ...args),\n error: (...args: unknown[]) => console.error(`[Plugin:${PLUGIN_IDENTIFIER}]`, ...args),\n};\n\n/**\n * 핸들러 등록 함수 (내부용)\n *\n * ActionDispatcher에 핸들러를 등록합니다.\n * retry 옵션으로 ActionDispatcher가 없을 때 재시도 여부를 제어합니다.\n *\n * @param retry 재시도 여부 (기본: false)\n */\nfunction registerHandlers(retry: boolean = false): void {\n const actionDispatcher = (window as any).G7Core?.getActionDispatcher?.();\n\n if (actionDispatcher) {\n // handlerMap의 모든 핸들러를 자동으로 등록\n // 네임스페이스: {plugin-identifier}.{handler-name}\n Object.entries(handlerMap).forEach(([name, handler]) => {\n const fullName = `${PLUGIN_IDENTIFIER}.${name}`;\n actionDispatcher.registerHandler(fullName, handler, {\n category: 'plugin',\n source: PLUGIN_IDENTIFIER,\n });\n });\n\n logger.log(\n `${Object.keys(handlerMap).length} handler(s) registered:`,\n Object.keys(handlerMap).map(name => `${PLUGIN_IDENTIFIER}.${name}`)\n );\n } else if (retry) {\n // 최초 로드 시에만 재시도 (언어 전환 등 재초기화 시에는 재시도하지 않음)\n let retryCount = 0;\n const maxRetries = 50; // 최대 5초 대기 (50 * 100ms)\n\n const retryRegister = () => {\n const dispatcher = (window as any).G7Core?.getActionDispatcher?.();\n\n if (dispatcher) {\n Object.entries(handlerMap).forEach(([name, handler]) => {\n const fullName = `${PLUGIN_IDENTIFIER}.${name}`;\n dispatcher.registerHandler(fullName, handler, {\n category: 'plugin',\n source: PLUGIN_IDENTIFIER,\n });\n });\n\n logger.log(\n `${Object.keys(handlerMap).length} handler(s) registered:`,\n Object.keys(handlerMap).map(name => `${PLUGIN_IDENTIFIER}.${name}`)\n );\n } else {\n retryCount++;\n if (retryCount <= maxRetries) {\n logger.warn(\n `ActionDispatcher not found, retrying... (${retryCount}/${maxRetries})`\n );\n setTimeout(retryRegister, 100);\n } else {\n logger.error(\n `Failed to register handlers: ActionDispatcher not available after maximum retries`\n );\n }\n }\n };\n\n retryRegister();\n } else {\n logger.warn(\n `ActionDispatcher not found, handlers not registered`\n );\n }\n}\n\n/**\n * 플러그인 초기화 함수\n *\n * ActionDispatcher에 핸들러를 등록합니다.\n * 플러그인 식별자를 네임스페이스로 사용하여 다른 플러그인과 충돌을 방지합니다.\n */\nexport function initPlugin(): void {\n // DOMContentLoaded 이벤트 사용 (DOM 파싱 완료 후)\n if (document.readyState === 'loading') {\n document.addEventListener('DOMContentLoaded', () => registerHandlers(true));\n } else {\n // DOM이 이미 로드된 경우 즉시 실행\n const hasDispatcher = !!(window as any).G7Core?.getActionDispatcher?.();\n registerHandlers(hasDispatcher ? false : true);\n }\n}\n\n// 플러그인 초기화 자동 실행\ninitPlugin();\n\n// 전역 객체에 플러그인 노출 (디버깅용)\nif (typeof window !== 'undefined') {\n (window as any).__SirsoftDaumPostcode = {\n identifier: PLUGIN_IDENTIFIER,\n handlers: Object.keys(handlerMap),\n initPlugin,\n };\n}\n"],"names":["logger","args","setFieldReadOnlyHandler","action","_context","params","fields","readOnly","fieldName","inputs","input","convertToG7AddressEvent","daumData","openPostcodeHandler","context","callbackAction","displayMode","width","height","theme","targetFields","defaultTargetFields","postcodeConfig","data","g7AddressEvent","executeCallbackAction","executeDefaultSetState","postcode","layer","closeLayer","createEmbedLayer","originalOncomplete","overlay","closeButton","wrapper","e","handleEscKey","handlerContext","G7Core","currentLocalState","componentContext","actions","cbAction","error","updates","handlerMap","PLUGIN_IDENTIFIER","registerHandlers","retry","actionDispatcher","name","handler","fullName","retryCount","maxRetries","retryRegister","dispatcher","initPlugin","hasDispatcher"],"mappings":"iDAWA,MAAMA,EAAW,OAAe,QAAQ,eAAe,+BAA+B,GAAM,CACxF,IAAK,IAAIC,IAAoB,QAAQ,IAAI,kCAAmC,GAAGA,CAAI,EACnF,KAAM,IAAIA,IAAoB,QAAQ,KAAK,kCAAmC,GAAGA,CAAI,EACrF,MAAO,IAAIA,IAAoB,QAAQ,MAAM,kCAAmC,GAAGA,CAAI,CAC3F,EA6BO,SAASC,EACZC,EACAC,EACI,CACJ,MAAMC,EAASF,EAAO,QAAU,CAAA,EAC1BG,EAASD,EAAO,QAAU,CAAA,EAC1BE,EAAWF,EAAO,WAAa,GAErC,GAAI,CAACC,EAAO,OAAQ,CAChBN,EAAO,KAAK,wCAAwC,EACpD,MACJ,CAEAA,EAAO,IAAI,uCAAuCO,CAAQ,eAAgBD,CAAM,EAGhFA,EAAO,QAASE,GAAc,CAE1B,MAAMC,EAAS,SAAS,iBACpB,eAAeD,CAAS,sBAAsBA,CAAS,IAAA,EAG3D,GAAIC,EAAO,SAAW,EAAG,CACrBT,EAAO,KAAK,gDAAgDQ,CAAS,GAAG,EACxE,MACJ,CAEAC,EAAO,QAASC,GAAU,CACtBA,EAAM,SAAWH,EAGbA,EACAG,EAAM,UAAU,IAAI,UAAU,EAE9BA,EAAM,UAAU,OAAO,UAAU,EAGrCV,EAAO,IAAI,mCAAmCO,CAAQ,mBAAmBC,CAAS,IAAI,CAC1F,CAAC,CACL,CAAC,CACL,CChEA,MAAMR,EAAW,OAAe,QAAQ,eAAe,2BAA2B,GAAM,CACpF,IAAK,IAAIC,IAAoB,QAAQ,IAAI,8BAA+B,GAAGA,CAAI,EAC/E,KAAM,IAAIA,IAAoB,QAAQ,KAAK,8BAA+B,GAAGA,CAAI,EACjF,MAAO,IAAIA,IAAoB,QAAQ,MAAM,8BAA+B,GAAGA,CAAI,CACvF,EAoDA,SAASU,EAAwBC,EAA+B,CAC5D,MAAO,CACH,QAASA,EAAS,UAAY,GAC9B,QAASA,EAAS,aAAeA,EAAS,cAAgB,GAC1D,cAAe,GACf,OAAQA,EAAS,MAAQ,GACzB,KAAMA,EAAS,SAAW,GAC1B,YAAa,KACb,KAAMA,CAAA,CAEd,CAKO,SAASC,EACZV,EACAW,EACI,CACJ,MAAMT,EAASF,EAAO,QAAU,CAAA,EAC1B,CACF,eAAAY,EACA,YAAAC,EAAc,QACd,MAAAC,EAAQ,IACR,OAAAC,EAAS,IACT,MAAAC,EACA,aAAAC,CAAA,EACAf,EAKEgB,GAFU,OAAe,QACA,QAAQ,cAAc,uBAAuB,GAAK,CAAA,GACtC,eAAiB,CACxD,QAAS,mBACT,QAAS,mBACT,OAAQ,kBACR,KAAM,eAAA,EAYV,GATArB,EAAO,IAAI,uCAAwC,CAC/C,YAAAgB,EACA,MAAAC,EACA,OAAAC,EACA,kBAAmB,CAAC,CAACH,EACrB,aAAcK,GAAgBC,CAAA,CACjC,EAGG,CAAE,OAAe,MAAM,SAAU,CACjCrB,EAAO,MAAM,6CAA6C,EAC1D,MACJ,CAEA,MAAMsB,EAAsB,CACxB,WAAaC,GAAc,CACvBvB,EAAO,IAAI,yCAA0CuB,CAAI,EAGzD,MAAMC,EAAiBb,EAAwBY,CAAI,EACnDvB,EAAO,IAAI,yCAA0CwB,CAAc,EAG/DT,GAGAf,EAAO,IAAI,yCAAyC,EACpDyB,EAAsBV,EAAgBS,EAAgBV,CAAO,IAG7Dd,EAAO,IAAI,2CAA2C,EACtD0B,EAAuBN,GAAgBC,EAAqBG,CAAc,EAElF,EACA,MAAAP,EACA,OAAAC,CAAA,EAIAC,IACAG,EAAe,MAAQH,GAI3B,MAAMQ,EAAW,IAAK,OAAe,KAAK,SAASL,CAAc,EAEjE,GAAIN,IAAgB,QAEhBW,EAAS,KAAA,MACN,CAEH,KAAM,CAAE,MAAAC,EAAO,WAAAC,CAAA,EAAeC,EAAiBb,EAAOC,CAAM,EAGtDa,EAAqBT,EAAe,WAC1CA,EAAe,WAAcC,GAAc,CACvCM,EAAA,EACAE,EAAmBR,CAAI,CAC3B,EAGsB,IAAK,OAAe,KAAK,SAASD,CAAc,EACxD,MAAMM,CAAK,CAC7B,CACJ,CAMA,SAASE,EAAiBb,EAAeC,EAIvC,CAEE,MAAMc,EAAU,SAAS,cAAc,KAAK,EAC5CA,EAAQ,GAAK,wBACbA,EAAQ,MAAM,QAAU;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,MAcxB,MAAMJ,EAAQ,SAAS,cAAc,KAAK,EAC1CA,EAAM,GAAK,sBACXA,EAAM,MAAM,QAAU;AAAA,iBACTX,CAAK;AAAA,kBACJC,CAAM;AAAA;AAAA;AAAA;AAAA;AAAA,MAQpB,MAAMe,EAAc,SAAS,cAAc,QAAQ,EACnDA,EAAY,KAAO,SACnBA,EAAY,UAAY,IACxBA,EAAY,MAAM,QAAU;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,MAoB5B,MAAMC,EAAU,SAAS,cAAc,KAAK,EAC5CA,EAAQ,MAAM,QAAU,sBACxBA,EAAQ,YAAYD,CAAW,EAC/BC,EAAQ,YAAYN,CAAK,EAEzBI,EAAQ,YAAYE,CAAO,EAC3B,SAAS,KAAK,YAAYF,CAAO,EAGjC,MAAMH,EAAa,IAAM,CACjBG,EAAQ,YACRA,EAAQ,WAAW,YAAYA,CAAO,CAE9C,EAGAC,EAAY,iBAAiB,QAASJ,CAAU,EAGhDG,EAAQ,iBAAiB,QAAUG,GAAM,CACjCA,EAAE,SAAWH,GACbH,EAAA,CAER,CAAC,EAGD,MAAMO,EAAgBD,GAAqB,CACnCA,EAAE,MAAQ,WACVN,EAAA,EACA,SAAS,oBAAoB,UAAWO,CAAY,EAE5D,EACA,gBAAS,iBAAiB,UAAWA,CAAY,EAEjDpC,EAAO,IAAI,0CAA2C,CAAE,MAAAiB,EAAO,OAAAC,EAAQ,EAEhE,CAAE,QAAAc,EAAS,MAAAJ,EAAO,WAAAC,CAAA,CAC7B,CAiBA,SAASJ,EAAsBV,EAAqBS,EAAgCa,EAAsC,CACtH,MAAMC,EAAU,OAAe,OAE/B,GAAI,CAACA,GAAQ,SAAU,CACnBtC,EAAO,MAAM,8CAA8C,EAC3D,MACJ,CAIA,MAAMuC,EAAoBF,GAAgB,OAASC,GAAQ,OAAO,WAAA,GAAgB,CAAA,EAI5EE,EAAmB,CACrB,MAAOD,EACP,SAAUF,GAAgB,SAC1B,KAAM,CACF,GAAIA,GAAgB,MAAQ,CAAA,EAC5B,OAAQE,EACR,OAAQf,CAAA,EAEZ,gBAAiBa,GAAgB,eAAA,EAGrCrC,EAAO,IAAI,oDAAqD,CAC5D,cAAe,CAAC,CAACuC,EACjB,WAAYF,GAAgB,MAAQ,iBAAmB,WACvD,cAAeE,GAAmB,SAClC,MAAOf,CAAA,CACV,EAED,GAAI,CACA,MAAMiB,EAAU,MAAM,QAAQ1B,CAAc,EAAIA,EAAiB,CAACA,CAAc,EAChF,UAAW2B,KAAYD,EACfC,GAAY,OAAOA,GAAa,WAChC1C,EAAO,IAAI,qCAAsC0C,CAAQ,EACzDJ,EAAO,SAASI,EAAU,CAAE,iBAAAF,CAAA,CAAkB,EAG1D,OAASG,EAAO,CACZ3C,EAAO,MAAM,wCAAyC2C,CAAK,CAC/D,CACJ,CAKA,SAASjB,EACLN,EACAI,EACI,CACJ,MAAMc,EAAU,OAAe,OAE/B,GAAI,CAACA,GAAQ,OAAO,SAAU,CAC1BtC,EAAO,MAAM,oDAAoD,EACjE,MACJ,CAGA,MAAM4C,EAAkC,CAAA,EAEpCxB,EAAa,UACbwB,EAAQxB,EAAa,OAAO,EAAII,EAAe,SAE/CJ,EAAa,UACbwB,EAAQxB,EAAa,OAAO,EAAII,EAAe,SAE/CJ,EAAa,SACbwB,EAAQxB,EAAa,MAAM,EAAII,EAAe,QAE9CJ,EAAa,OACbwB,EAAQxB,EAAa,IAAI,EAAII,EAAe,MAGhDxB,EAAO,IAAI,sCAAuC4C,CAAO,EACzDN,EAAO,MAAM,SAASM,CAAO,CACjC,CClWO,MAAMC,EAAa,CACtB,iBAAkB3C,EAClB,aAAcW,CAClB,ECZMiC,EAAoB,wBAGpB9C,EAAW,OAAe,QAAQ,eAAe,UAAU8C,CAAiB,EAAE,GAAM,CACtF,IAAK,IAAI7C,IAAoB,QAAQ,IAAI,WAAW6C,CAAiB,IAAK,GAAG7C,CAAI,EACjF,KAAM,IAAIA,IAAoB,QAAQ,KAAK,WAAW6C,CAAiB,IAAK,GAAG7C,CAAI,EACnF,MAAO,IAAIA,IAAoB,QAAQ,MAAM,WAAW6C,CAAiB,IAAK,GAAG7C,CAAI,CACzF,EAUA,SAAS8C,EAAiBC,EAAiB,GAAa,CACpD,MAAMC,EAAoB,OAAe,QAAQ,sBAAA,EAEjD,GAAIA,EAGA,OAAO,QAAQJ,CAAU,EAAE,QAAQ,CAAC,CAACK,EAAMC,CAAO,IAAM,CACpD,MAAMC,EAAW,GAAGN,CAAiB,IAAII,CAAI,GAC7CD,EAAiB,gBAAgBG,EAAUD,EAAS,CAChD,SAAU,SACV,OAAQL,CAAA,CACX,CACL,CAAC,EAED9C,EAAO,IACH,GAAG,OAAO,KAAK6C,CAAU,EAAE,MAAM,0BACjC,OAAO,KAAKA,CAAU,EAAE,OAAY,GAAGC,CAAiB,IAAII,CAAI,EAAE,CAAA,UAE/DF,EAAO,CAEd,IAAIK,EAAa,EACjB,MAAMC,EAAa,GAEbC,EAAgB,IAAM,CACxB,MAAMC,EAAc,OAAe,QAAQ,sBAAA,EAEvCA,GACA,OAAO,QAAQX,CAAU,EAAE,QAAQ,CAAC,CAACK,EAAMC,CAAO,IAAM,CACpD,MAAMC,EAAW,GAAGN,CAAiB,IAAII,CAAI,GAC7CM,EAAW,gBAAgBJ,EAAUD,EAAS,CAC1C,SAAU,SACV,OAAQL,CAAA,CACX,CACL,CAAC,EAED9C,EAAO,IACH,GAAG,OAAO,KAAK6C,CAAU,EAAE,MAAM,0BACjC,OAAO,KAAKA,CAAU,EAAE,OAAY,GAAGC,CAAiB,IAAII,CAAI,EAAE,CAAA,IAGtEG,IACIA,GAAcC,GACdtD,EAAO,KACH,4CAA4CqD,CAAU,IAAIC,CAAU,GAAA,EAExE,WAAWC,EAAe,GAAG,GAE7BvD,EAAO,MACH,mFAAA,EAIhB,EAEAuD,EAAA,CACJ,MACIvD,EAAO,KACH,qDAAA,CAGZ,CAQO,SAASyD,GAAmB,CAE/B,GAAI,SAAS,aAAe,UACxB,SAAS,iBAAiB,mBAAoB,IAAMV,EAAiB,EAAI,CAAC,MACvE,CAEH,MAAMW,EAAgB,CAAC,CAAE,OAAe,QAAQ,sBAAA,EAChDX,EAAiB,CAAAW,CAA4B,CACjD,CACJ,CAGA,OAAAD,EAAA,EAGI,OAAO,OAAW,MACjB,OAAe,sBAAwB,CACpC,WAAYX,EACZ,SAAU,OAAO,KAAKD,CAAU,EAChC,WAAAY,CAAA"} \ No newline at end of file diff --git a/plugins/_bundled/sirsoft-daum_postcode/vite.config.ts b/plugins/_bundled/sirsoft-daum_postcode/vite.config.ts index cd724954..910d8467 100644 --- a/plugins/_bundled/sirsoft-daum_postcode/vite.config.ts +++ b/plugins/_bundled/sirsoft-daum_postcode/vite.config.ts @@ -20,7 +20,9 @@ export default defineConfig({ emptyOutDir: true, // 소스맵 생성 - sourcemap: true, + // 배포용 빌드(--production)는 G7_BUILD_SOURCEMAP=0 을 주입해 소스맵을 생성하지 않는다. + // 미설정(로컬 npm run build)이면 생성 — 개발 디버깅 경험을 유지한다. + sourcemap: !['0', 'false'].includes(process.env.G7_BUILD_SOURCEMAP ?? ''), rollupOptions: { output: { diff --git a/plugins/_bundled/sirsoft-gdpr/components.json b/plugins/_bundled/sirsoft-gdpr/components.json new file mode 100644 index 00000000..5d7da8ca --- /dev/null +++ b/plugins/_bundled/sirsoft-gdpr/components.json @@ -0,0 +1,10 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "identifier": "sirsoft-gdpr", + "version": "1.0.0", + "components": { + "basic": [], + "composite": [], + "layout": [] + } +} diff --git a/plugins/_bundled/sirsoft-gdpr/dist/css/plugin.css b/plugins/_bundled/sirsoft-gdpr/dist/css/plugin.css index fab5f2ed..a0926bef 100644 --- a/plugins/_bundled/sirsoft-gdpr/dist/css/plugin.css +++ b/plugins/_bundled/sirsoft-gdpr/dist/css/plugin.css @@ -1 +1 @@ -.gdpr-hidden{display:none!important}.gdpr-blocked-embed{display:none}.gdpr-blocked-embed[data-gdpr-blocked-category]:not([aria-hidden=true]){display:inline-flex;align-items:center;justify-content:center;min-height:1.5rem;padding:.25rem .5rem;font-size:.75rem;color:#6b7280;background-color:#f9fafb;border:1px dashed #d1d5db;border-radius:.25rem}@media (prefers-color-scheme: dark){.gdpr-blocked-embed[data-gdpr-blocked-category]:not([aria-hidden=true]){color:#9ca3af;background-color:#1f293780;border-color:#4b5563}}.dark .gdpr-blocked-embed[data-gdpr-blocked-category]:not([aria-hidden=true]){color:#9ca3af;background-color:#1f293780;border-color:#4b5563} +.gdpr-hidden{display:none!important}.gdpr-blocked-embed{display:none}.gdpr-blocked-embed[data-gdpr-blocked-category]:not([aria-hidden=true]){display:inline-flex;align-items:center;justify-content:center;min-height:1.5rem;padding:.25rem .5rem;font-size:.75rem;color:#6b7280;background-color:#f9fafb;border:1px dashed #d1d5db;border-radius:.25rem}@media(prefers-color-scheme:dark){.gdpr-blocked-embed[data-gdpr-blocked-category]:not([aria-hidden=true]){color:#9ca3af;background-color:#1f293780;border-color:#4b5563}}.dark .gdpr-blocked-embed[data-gdpr-blocked-category]:not([aria-hidden=true]){color:#9ca3af;background-color:#1f293780;border-color:#4b5563} diff --git a/plugins/_bundled/sirsoft-gdpr/dist/js/plugin.iife.js b/plugins/_bundled/sirsoft-gdpr/dist/js/plugin.iife.js index 3096a0a1..11404a61 100644 --- a/plugins/_bundled/sirsoft-gdpr/dist/js/plugin.iife.js +++ b/plugins/_bundled/sirsoft-gdpr/dist/js/plugin.iife.js @@ -1,2 +1 @@ (function(){"use strict";function Q(e,t){const n=e.toLowerCase(),r=t.toLowerCase();if(r.startsWith("*.")){const o=r.slice(1);return n.endsWith(o)&&n!==o.slice(1)}return n===r}function v(e,t){if(typeof window>"u"||typeof URL>"u")return null;let n,r;try{const o=new URL(e,window.location.href);r=o.protocol,n=o.hostname}catch{return null}if(r!=="http:"&&r!=="https:"||n===""||n===window.location.hostname)return null;for(const[o,s]of Object.entries(t))if(o!=="necessary"){for(const i of s)if(Q(n,i))return o}return null}const w="data-gdpr-category",h="gdpr-blocked-embed",x="data-gdpr-original",b=[`script[${w}]`,`iframe[${w}]`,"script[src]","iframe[src]","img[src]","link[href]"].join(", ");let l=null,g={categories:{},needs_renewal:!1},R={};function j(e){if(e&&typeof e=="object"&&"categories"in e){const t=e;return{categories:{...t.categories},needs_renewal:t.needs_renewal===!0}}return{categories:{...e},needs_renewal:!1}}function N(e){g=j(e),l!==null&&p()}function V(e){R={...e},l!==null&&p()}function Z(){typeof document>"u"||l!==null||(p(),l=new MutationObserver(e=>{for(const t of e)t.addedNodes.forEach(n=>{n instanceof HTMLElement&&te(n)})}),l.observe(document.documentElement,{childList:!0,subtree:!0}),window.addEventListener("gdpr:consent-changed",ee))}function ee(e){const t=e.detail;t!==null&&typeof t=="object"&&(g=j(t)),p()}function te(e){re(e)&&k(e),e.querySelectorAll(b).forEach(t=>k(t))}function p(){typeof document>"u"||(document.querySelectorAll(`.${h}`).forEach(e=>{const t=e.dataset.gdprBlockedCategory;t&&_(t)&&oe(e)}),document.querySelectorAll(b).forEach(e=>k(e)))}function k(e){if(e.classList.contains(h)||e.hasAttribute("data-gdpr-blocked-category"))return;const t=e.getAttribute(w);if(t!==null&&t!==""){if(_(t))return;H(e,t);return}const n=ne(e);if(n===null)return;const r=v(n,R);r!==null&&(_(r)||H(e,r))}function ne(e){return e instanceof HTMLScriptElement||e instanceof HTMLIFrameElement||e instanceof HTMLImageElement?e.getAttribute("src"):e instanceof HTMLLinkElement?e.getAttribute("href"):null}function re(e){if(typeof e.matches!="function")return!1;try{return e.matches(b)}catch{return!1}}function H(e,t){const n=document.createElement("div");n.className=h,n.dataset.gdprBlockedCategory=t,n.dataset.gdprOriginalTag=e.tagName.toLowerCase(),n.setAttribute(x,e.outerHTML),n.setAttribute("aria-hidden","true"),n.style.display="none",e.replaceWith(n)}function oe(e){const t=e.getAttribute(x);if(!t){e.remove();return}const n=document.createElement("div");n.innerHTML=t;const r=n.firstElementChild;r!==null?e.replaceWith(r):e.remove()}function _(e){return e==="necessary"?!0:g.needs_renewal?!1:g.categories[e]===!0}const se=500,ie=["click","keydown","change","submit","pointerdown","touchstart"];let B=!1,C=0;function ce(e){e.isTrusted&&(C=Date.now())}function ae(){if(!B){B=!0;for(const e of ie){const t=ce;window.addEventListener(e,t,{capture:!0,passive:!0})}}}function M(){const e=window.event;return e&&e.isTrusted?!0:C===0?!1:Date.now()-C<=se}const u=["XSRF-TOKEN","laravel_session","laravel_maintenance","gdpr_session"];let A=!1,a=null,y={functionalConsented:!1,necessaryAllowlist:u};function le(e){const t=e.indexOf("=");return t<=0?null:e.substring(0,t).trim()}function ue(e){return y.necessaryAllowlist.includes(e)}function fe(e){const t=le(e);return t===null?!1:!!(de(e)||ue(t)||y.functionalConsented||M())}function de(e){const t=e.toLowerCase(),n=t.match(/max-age\s*=\s*(-?\d+)/);return!!(n&&parseInt(n[1],10)<=0||t.includes("expires=thu, 01 jan 1970")||t.includes("expires=thu, 01-jan-1970"))}function ge(e){if(A)return;if(A=!0,y=e,a=Object.getOwnPropertyDescriptor(Document.prototype,"cookie")??null,!a||!a.set||!a.get){A=!1;return}const t=a.set,n=a.get;Object.defineProperty(Document.prototype,"cookie",{configurable:!0,get(){return n.call(this)},set(r){fe(r)&&t.call(this,r)}})}function P(e){y=e}const f=[{key:"g7_locale",storage:"localStorage",matchType:"exact"},{key:"auth_token",storage:"localStorage",matchType:"exact"},{key:"g7_cache_version",storage:"localStorage",matchType:"exact"},{key:"g7_cart_key",storage:"localStorage",matchType:"exact"},{key:"g7-devtools-panel",storage:"localStorage",matchType:"exact"},{key:"g7_devtools_",matchType:"prefix"},{key:"g7_filters_",matchType:"prefix"},{key:"g7_columns_",matchType:"prefix"},{key:"g7_order_",matchType:"prefix"}];let $=!1,E=null,m={functionalConsented:!1,necessaryAllowlist:f};function pe(e,t){for(const n of m.necessaryAllowlist){if(n.storage&&n.storage!==t)continue;const r=n.matchType??"exact";if(r==="exact"&&n.key===e||r==="prefix"&&e.startsWith(n.key))return!0}return!1}function ye(e,t){return!!(pe(e,t)||m.functionalConsented||M())}function me(e){if($)return;$=!0,m=e;const t=Storage.prototype;E=t.setItem,t.setItem=function(n,r){const o=this===window.sessionStorage?"sessionStorage":"localStorage";ye(n,o)&&E&&E.call(this,n,r)}}function G(e){m=e}function we(e,t,n){for(const r of n){if(r.storage&&r.storage!==t)continue;const o=r.matchType??"exact";if(o==="exact"&&r.key===e||o==="prefix"&&e.startsWith(r.key))return!0}return!1}function U(e,t,n){const r=[];for(let o=0;o-1?r.substring(0,o):r).trim();if(s&&!e.includes(s))try{document.cookie=`${s}=; Max-Age=0; Path=/`}catch{}}}function W(e={}){const t=e.storageAllowlist??f,n=e.cookieAllowlist??u;U(window.localStorage,"localStorage",t),U(window.sessionStorage,"sessionStorage",t),he(n)}let T=null;function be(){if(typeof window>"u")return null;const t=window.G7Core?.api;return t&&typeof t.get=="function"?t:null}async function K(e){const t=be();if(t)try{return await t.get(e)}catch{return null}try{const n=await fetch("/api"+e,{method:"GET",headers:{Accept:"application/json"},credentials:"same-origin"});return n.ok?await n.json():null}catch{return null}}async function ke(){if(T)return T;const t=(await K("/plugins/sirsoft-gdpr/settings"))?.data;return t?(T=t,t):null}async function q(){const e=await K("/plugins/sirsoft-gdpr/consent/cookie/status"),t=e?.data?.consents;return t==null?null:{categories:t,needs_renewal:e?.data?.needs_renewal===!0,current_policy_version:String(e?.data?.current_policy_version??"")}}const S="data-gdpr-blocked-src",L="data-gdpr-blocked-category",_e="data-gdpr-category";let F=!1,d={blockedDomains:{},consent:{necessary:!0},needs_renewal:!1};function D(e){return e==="necessary"?!0:d.needs_renewal===!0?!1:d.consent?.[e]===!0}function Ce(e,t){const n=e.getAttribute(_e);if(n!==null&&n!=="")return D(n)?null:n;const r=v(t,d.blockedDomains);return r===null||D(r)?null:r}function Ae(e){if(d={blockedDomains:e.blockedDomains??{},consent:e.consent??{necessary:!0},needs_renewal:e.needs_renewal===!0},F||typeof HTMLScriptElement>"u")return;const t=Object.getOwnPropertyDescriptor(HTMLScriptElement.prototype,"src");t===void 0||typeof t.set!="function"||(Object.defineProperty(HTMLScriptElement.prototype,"src",{configurable:!0,enumerable:t.enumerable??!0,get(){return t.get?.call(this)??""},set(n){const r=Ce(this,n);if(r!==null){this.setAttribute(S,n),this.setAttribute(L,r);return}t.set.call(this,n)}}),F=!0)}function I(e){d={blockedDomains:e.blockedDomains??{},consent:e.consent??{necessary:!0},needs_renewal:e.needs_renewal===!0}}function Y(e){if(!D(e)||typeof document>"u")return;document.querySelectorAll(`script[${L}="${e}"]`).forEach(n=>{const r=n.getAttribute(S);if(r===null||r==="")return;const o=document.createElement("script");for(const i of Array.from(n.attributes))i.name===S||i.name===L||i.name==="src"||o.setAttribute(i.name,i.value);const s=n.parentNode;s!==null&&(s.replaceChild(o,n),o.src=r)})}const O="sirsoft-gdpr";function z(){if(typeof window>"u")return{};const n=window.G7Config?.plugins?.[O]?.blocked_domains;if(n==null||typeof n!="object")return{};const r={};for(const[o,s]of Object.entries(n)){if(!Array.isArray(s))continue;const i=s.filter(c=>typeof c=="string"&&c!=="");i.length>0&&(r[o]=i)}return r}function Ee(){return typeof window>"u"?!1:window.G7Config?.plugins?.[O]?.banner_enabled===!0}function J(){const e=window.G7Core?.getActionDispatcher?.();if(!e){setTimeout(J,100);return}e.registerHandler(`${O}.syncConsent`,async()=>{const t=await q();if(t===null)return;const n=!t.needs_renewal&&t.categories.functional===!0;N({categories:t.categories,needs_renewal:t.needs_renewal});const r=z();if(I({blockedDomains:r,consent:t.categories,needs_renewal:t.needs_renewal}),G({functionalConsented:n,necessaryAllowlist:f}),P({functionalConsented:n,necessaryAllowlist:u}),n||W(),!t.needs_renewal)for(const[o,s]of Object.entries(t.categories))s===!0&&o!=="necessary"&&Y(o)})}async function X(){if(typeof window>"u")return;if(J(),Ee()){const c=z();Ae({blockedDomains:c,consent:{necessary:!0}}),ae(),me({functionalConsented:!1,necessaryAllowlist:f}),ge({functionalConsented:!1,necessaryAllowlist:u})}const t=await ke();if(!t||!(t.banner_enabled===!0))return;const r=t.blocked_domains??{};V(r),I({blockedDomains:r,consent:{necessary:!0}}),Z();const s=await q(),i=s!==null&&!s.needs_renewal&&s.categories.functional===!0;if(G({functionalConsented:i,necessaryAllowlist:f}),P({functionalConsented:i,necessaryAllowlist:u}),i||W(),s!==null&&(N({categories:s.categories,needs_renewal:s.needs_renewal}),I({blockedDomains:r,consent:s.categories,needs_renewal:s.needs_renewal}),!s.needs_renewal))for(const[c,Te]of Object.entries(s.categories))Te===!0&&c!=="necessary"&&Y(c)}typeof document<"u"&&(document.readyState==="loading"?document.addEventListener("DOMContentLoaded",()=>{X()}):X())})(); -//# sourceMappingURL=plugin.iife.js.map diff --git a/plugins/_bundled/sirsoft-gdpr/dist/js/plugin.iife.js.map b/plugins/_bundled/sirsoft-gdpr/dist/js/plugin.iife.js.map deleted file mode 100644 index 75c7b1f5..00000000 --- a/plugins/_bundled/sirsoft-gdpr/dist/js/plugin.iife.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"plugin.iife.js","sources":["../../resources/js/blocker-domains.ts","../../resources/js/blocker.ts","../../resources/js/userInitiatedTracker.ts","../../resources/js/cookieInterceptor.ts","../../resources/js/storageInterceptor.ts","../../resources/js/functionalCleaner.ts","../../resources/js/layouts.ts","../../resources/js/preblocker.ts","../../resources/js/index.ts"],"sourcesContent":["/**\n * GDPR 도메인 기반 차단 매칭 + 기본 카탈로그\n *\n * 운영자가 카테고리별 차단 도메인 목록을 관리하면 `data-gdpr-category` 속성\n * 없이도 외부 추적 리소스를 동의 전 차단합니다. 매칭 알고리즘은 endsWith 기반\n * 으로 정규식을 쓰지 않아 일반 페이지 영향이 미미합니다.\n *\n * @module sirsoft-gdpr/blocker-domains\n */\n\n/**\n * 호스트가 차단 도메인 패턴과 매칭되는지 확인합니다.\n *\n * 패턴 형식:\n * - 정확 매칭: `google-analytics.com` → `google-analytics.com` 만 매칭\n * - 와일드카드: `*.hotjar.com` → `static.hotjar.com` 등 매칭. `hotjar.com`\n * 자체는 매칭 안 됨 (정확 매칭으로 별도 추가 필요)\n *\n * @param host 검사 대상 호스트\n * @param pattern 차단 패턴\n * @return 매칭 여부\n */\nexport function matchesDomain(host: string, pattern: string): boolean {\n const lowerHost = host.toLowerCase();\n const lowerPattern = pattern.toLowerCase();\n\n if (lowerPattern.startsWith('*.')) {\n const suffix = lowerPattern.slice(1); // \".hotjar.com\"\n return lowerHost.endsWith(suffix) && lowerHost !== suffix.slice(1);\n }\n\n return lowerHost === lowerPattern;\n}\n\n/**\n * URL 의 호스트가 카테고리별 차단 목록에 매칭되는지 확인합니다.\n *\n * 동일 origin 은 차단 대상 외 (운영자 자체 자산 보호). `data:`/`blob:` 등 비\n * HTTP 스킴은 외부 호스트가 없으므로 차단 대상 외. `necessary` 카테고리는\n * 매칭 자체를 skip 합니다 (필수 쿠키 보호).\n *\n * @param url 검사 대상 URL (절대 또는 상대)\n * @param patterns 카테고리 → 도메인 패턴 배열\n * @return 매칭된 카테고리, 매칭 없으면 null\n */\nexport function categorizeUrl(\n url: string,\n patterns: Record,\n): string | null {\n if (typeof window === 'undefined' || typeof URL === 'undefined') return null;\n\n let host: string;\n let protocol: string;\n try {\n const parsed = new URL(url, window.location.href);\n protocol = parsed.protocol;\n host = parsed.hostname;\n } catch {\n return null;\n }\n\n if (protocol !== 'http:' && protocol !== 'https:') return null;\n if (host === '' || host === window.location.hostname) return null;\n\n for (const [category, domains] of Object.entries(patterns)) {\n if (category === 'necessary') continue;\n for (const domain of domains) {\n if (matchesDomain(host, domain)) return category;\n }\n }\n\n return null;\n}\n\n/**\n * 기본 차단 도메인 카탈로그 — 운영자가 `blocked_domains_default_catalog = true`\n * 로 켜야 적용. 운영자 커스텀 목록과 합집합으로 병합됩니다.\n *\n * 카탈로그 갱신은 코드 PR 로만 가능. 사용자별 변경 불가.\n */\nexport const DEFAULT_BLOCKED_DOMAINS: Record = {\n // functional 카테고리는 자체 호스팅 functional 만 처리 — 외부 도구 도메인은 Phase 2 에서 보강.\n // 현재 빈 배열로 두어 운영자가 admin UI 의 functional 차단 도메인 섹션을 사용할 수 있도록 함.\n functional: [],\n analytics: [\n // Google Analytics 계열\n 'google-analytics.com',\n '*.google-analytics.com',\n 'googletagmanager.com',\n '*.googletagmanager.com',\n 'ssl.google-analytics.com',\n // 행동 분석\n '*.hotjar.com',\n 'static.hotjar.com',\n '*.mixpanel.com',\n 'cdn.mxpnl.com',\n '*.amplitude.com',\n 'cdn.amplitude.com',\n '*.segment.io',\n '*.segment.com',\n // 한국 분석 도구\n 'wcs.naver.net',\n 'wcs.naver.com',\n '*.beusable.net',\n ],\n marketing: [\n // Meta (Facebook)\n 'facebook.net',\n 'connect.facebook.net',\n 'facebook.com',\n '*.facebook.com',\n // Google Ads\n 'doubleclick.net',\n '*.doubleclick.net',\n 'googleadservices.com',\n 'googlesyndication.com',\n 'ads.google.com',\n // 광고 네트워크\n '*.criteo.com',\n 'static.criteo.net',\n '*.adnxs.com',\n '*.taboola.com',\n 'cdn.taboola.com',\n '*.outbrain.com',\n // 한국 광고 네트워크\n '*.kakao.com',\n 'analytics.ad.daum.net',\n // SNS 임베드\n 'platform.twitter.com',\n '*.twitter.com',\n 'platform.linkedin.com',\n '*.linkedin.com',\n ],\n};\n\n// `mergeBlockedDomains()` 는 카탈로그 토글이 단일 개념으로 통합되면서 제거되었습니다.\n// 카탈로그 도메인은 신규 설치 시 `blocked_domains` 기본값으로 채워지며, 차단 엔진은\n// 운영자 입력 도메인만 사용합니다. 카탈로그 갱신 동기화는 관리자 UI 의 \"카탈로그에서\n// 도메인 가져오기\" 링크가 클라이언트 측 Set 합집합으로 처리합니다.","/**\n * GDPR 자동 차단 엔진\n *\n * 두 가지 매칭 방식을 병행 적용합니다:\n *\n * 1. 속성 기반 — `data-gdpr-category` 속성이 부여된 `